Source code for linkml_runtime.loaders.yaml_loader

from __future__ import annotations

import os
from io import StringIO
from typing import TYPE_CHECKING, TextIO

import yaml
from hbreader import FileInfo

from linkml_runtime.loaders.loader_root import Loader
from linkml_runtime.utils.yamlutils import DupCheckYamlLoader, YAMLRoot

if TYPE_CHECKING:
    from pathlib import Path

    from pydantic import BaseModel


[docs] class YAMLLoader(Loader): """ A Loader that is capable of instantiating LinkML data objects from a YAML file """ def load_as_dict( self, source: str | dict | TextIO | Path, *, base_dir: str | None = None, metadata: FileInfo | None = None ) -> dict | list[dict]: if metadata is None: metadata = FileInfo() if base_dir and not metadata.base_path: metadata.base_path = base_dir data = self._read_source( source, base_dir=base_dir, metadata=metadata, accept_header="text/yaml, application/yaml;q=0.9" ) if isinstance(data, str): data = StringIO(data) if metadata and metadata.source_file: data.name = os.path.relpath(metadata.source_file, metadata.base_path) return yaml.load(data, DupCheckYamlLoader) return data
[docs] def load_any( self, source: str | dict | TextIO | Path, target_class: type[YAMLRoot | BaseModel], *, base_dir: str | None = None, metadata: FileInfo | None = None, **_, ) -> YAMLRoot | list[YAMLRoot]: if metadata is None: metadata = FileInfo() data_as_dict = self.load_as_dict(source, base_dir=base_dir, metadata=metadata) result = self._construct_target_class(data_as_dict, target_class) # When the source was a file (or URL), ``hbread`` resolves and records it on the metadata. # Propagate that to ``source_file`` (e.g. on a SchemaDefinition) so relative imports can be # resolved against it. Inline string sources without caller-supplied metadata leave # ``source_file`` unset, so this naturally distinguishes a path from schema text. # Gated on SchemaDefinition specifically because it owns ``source_file`` in the metamodel; # this avoids injecting the loader path into other target classes that happen to have a # ``source_file`` slot. if metadata.source_file: # Lazy import is cheap, avoids circular dependency between loaders and metamodel. from linkml_runtime.linkml_model.meta import SchemaDefinition # Prefer the path exactly as the caller supplied it. hbread always absolutizes, so # using metadata.source_file would stamp a machine-absolute path onto a schema that # was loaded via a relative path. ``source`` is only the caller's *path* when its # absolutized form is what hbread resolved - inline text with a caller-supplied # ``source_file`` name is also a str, so an isinstance check alone would record the # whole document as the path. Everything else (URLs, file-like objects, inline text, # base_dir joins) records the resolved location from the metadata. if ( base_dir is None and isinstance(source, str | os.PathLike) and os.path.abspath(str(source)) == str(metadata.source_file) ): path_to_record = str(source) else: path_to_record = str(metadata.source_file) for target in result if isinstance(result, list) else [result]: # Always overwrite: the metamodel marks source_file as # ``readonly: supplied by the schema loader``. A value # serialised into the YAML by a previous --metadata run is # stale content, not an authoritative path. if isinstance(target, SchemaDefinition): target.source_file = path_to_record return result
[docs] def loads_any( self, source: str, target_class: type[BaseModel | YAMLRoot], *, metadata: FileInfo | None = None, **_ ) -> BaseModel | YAMLRoot | list[BaseModel] | list[YAMLRoot]: """ Load source as a string @param source: source @param target_class: destination class @param metadata: metadata about the source @param _: extensions @return: instance of taarget_class """ return self.load_any(source, target_class, metadata=metadata)