Pydantic#
Example Output#
Overview#
The Pydantic Generator produces Pydantic flavored python dataclasses from a linkml model, with optional support for user-supplied jinja2 templates to generate alternate classes.
Example#
Given a definition of a Person class:
Person:
is_a: NamedThing
description: >-
A person (alive, dead, undead, or fictional).
class_uri: schema:Person
mixins:
- HasAliases
slots:
- primary_email
- birth_date
- age_in_years
- gender
- current_address
- has_employment_history
- has_familial_relationships
- has_medical_history
(some details omitted for brevity, including slot definitions and parent classes)
The generate python looks like this:
class Person(NamedThing):
"""
A person (alive, dead, undead, or fictional).
"""
primary_email: Optional[str] = Field(None)
birth_date: Optional[str] = Field(None)
age_in_years: Optional[int] = Field(None, ge=0, le=999)
gender: Optional[GenderType] = Field(None)
current_address: Optional[Address] = Field(None, description="""The address at which a person currently lives""")
has_employment_history: Optional[List[EmploymentEvent]] = Field(None)
has_familial_relationships: Optional[List[FamilialRelationship]] = Field(None)
has_medical_history: Optional[List[MedicalEvent]] = Field(None)
aliases: Optional[List[str]] = Field(None)
id: Optional[str] = Field(None)
name: Optional[str] = Field(None)
description: Optional[str] = Field(None)
image: Optional[str] = Field(None)
Command Line#
gen-pydantic#
Generate pydantic classes to represent a LinkML model
gen-pydantic [OPTIONS] YAMLFILE
Options
- -V, --version#
Show the version and exit.
- --extra-fields <extra_fields>#
How to handle extra fields in BaseModel.
- Options:
allow | ignore | forbid
- --array-representations <array_representations>#
List of array representations to accept for array slots. Default is list of lists.
- Options:
list | nparray
- --pydantic-version <pydantic_version>#
Pydantic version to use (1 or 2)
- --template-dir <template_dir>#
Optional jinja2 template directory to use for class generation.
Pass a directory containing templates with the same name as any of the default
TemplateModel
templates to override them. The given directory will be searched for matching templates, and use the default templates as a fallback if an override is not foundAvailable templates to override:
- attribute.py.jinja- base_model.py.jinja- class.py.jinja- conditional_import.py.jinja- enum.py.jinja- imports.py.jinja- module.py.jinja- validator.py.jinja
- -f, --format <format>#
Output format
- Default:
pydantic
- Options:
pydantic
- --metadata, --no-metadata#
Include metadata in output
- Default:
True
- --useuris, --metauris#
Use class and slot URIs over model uris
- Default:
True
- -im, --importmap <importmap>#
Import mapping file
- --log_level <log_level>#
Logging level
- Default:
WARNING
- Options:
CRITICAL | ERROR | WARNING | INFO | DEBUG
- -v, --verbose#
Verbosity. Takes precedence over –log_level.
- --mergeimports, --no-mergeimports#
Merge imports into source file (default=mergeimports)
- --stacktrace, --no-stacktrace#
Print a stack trace when an error occurs
- Default:
False
Arguments
- YAMLFILE#
Required argument
Generator#
- class linkml.generators.pydanticgen.PydanticGenerator(schema: str | ~typing.TextIO | ~linkml_runtime.linkml_model.meta.SchemaDefinition | Generator | ~pathlib.Path, schemaview: ~linkml_runtime.utils.schemaview.SchemaView | None = None, format: str | None = None, metadata: bool = True, useuris: bool | None = None, log_level: int | None = 30, mergeimports: bool | None = True, source_file_date: str | None = None, source_file_size: int | None = None, logger: ~logging.Logger | None = None, verbose: bool | None = None, output: str | None = None, namespaces: ~linkml_runtime.utils.namespaces.Namespaces | None = None, directory_output: bool = False, base_dir: str = None, metamodel_name_map: ~typing.Dict[str, str] = None, importmap: str | ~typing.Mapping[str, str] | None = None, emit_prefixes: ~typing.Set[str] = <factory>, metamodel: ~linkml.utils.schemaloader.SchemaLoader = None, stacktrace: bool = False, template_file: str = None, package: str = 'example', array_representations: ~typing.List[~linkml.generators.pydanticgen.array.ArrayRepresentation] = <factory>, black: bool = False, pydantic_version: int = 2, template_dir: str | ~pathlib.Path | None = None, extra_fields: ~typing.Literal['allow', 'forbid', 'ignore'] = 'forbid', gen_mixin_inheritance: bool = True, injected_classes: ~typing.List[str | ~typing.Type] | None = None, injected_fields: ~typing.List[str] | None = None, imports: ~typing.List[~linkml.generators.pydanticgen.template.Import] | None = None, gen_classvars: bool = True, gen_slots: bool = True, genmeta: bool = False, emit_metadata: bool = True, **_kwargs)[source]#
Generates Pydantic-compliant classes from a schema
This is an alternative to the dataclasses-based Pythongen
- generate_collection_key(slot_ranges: List[str], slot_def: SlotDefinition, class_def: ClassDefinition) str | None [source]#
Find the python range value (str, int, etc) for the identifier slot of a class used as a slot range.
If a pyrange value matches a class name, the range of the identifier slot will be returned. If more than one match is found and they don’t match, an exception will be raised.
- Parameters:
slot_ranges – list of python range values
- generate_python_range(slot_range, slot_def: SlotDefinition, class_def: ClassDefinition) str [source]#
Generate the python range for a slot range value
- generatorname: ClassVar[str] = 'pydanticgen.py'#
Name of the generator. Override with os.path.basename(__file__)
- generatorversion: ClassVar[str] = '0.0.2'#
Version of the generator. Consider deprecating and instead use overall linkml version
- get_array_representations_range(slot: SlotDefinition, range: str) List[SlotResult] [source]#
Generate the python range for array representations
- get_class_isa_plus_mixins() Dict[str, List[str]] [source]#
Generate the inheritance list for each class from is_a plus mixins :return:
- get_predefined_slot_values() Dict[str, Dict[str, str]] [source]#
- Returns:
Dictionary of dictionaries with predefined slot values for each class
- imports: List[Import] | None = None#
Additional imports to inject into generated module.
Examples:
from linkml.generators.pydanticgen.template import ( ConditionalImport, ObjectImport, Import, Imports ) imports = (Imports() + Import(module='sys') + Import(module='numpy', alias='np') + Import(module='pathlib', objects=[ ObjectImport(name="Path"), ObjectImport(name="PurePath", alias="RenamedPurePath") ]) + ConditionalImport( module="typing", objects=[ObjectImport(name="Literal")], condition="sys.version_info >= (3, 8)", alternative=Import( module="typing_extensions", objects=[ObjectImport(name="Literal")] ), ).imports )
becomes:
import sys import numpy as np from pathlib import ( Path, PurePath as RenamedPurePath ) if sys.version_info >= (3, 8): from typing import Literal else: from typing_extensions import Literal
- injected_classes: List[str | Type] | None = None#
A list/tuple of classes to inject into the generated module.
Accepts either live classes or strings. Live classes will have their source code extracted with inspect.get - so they need to be standard python classes declared in a source file (ie. the module they are contained in needs a
__file__
attr, see:inspect.getsource()
)
- injected_fields: List[str] | None = None#
A list/tuple of field strings to inject into the base class.
Examples:
injected_fields = ( 'object_id: Optional[str] = Field(None, description="Unique UUID for each object")', )
- range_class_has_identifier_slot(slot)[source]#
Check if the range class of a slot has an identifier slot, via both slot.any_of and slot.range Should return False if the range is not a class, and also if the range is a class but has no identifier slot
- Parameters:
slot – SlotDefinition
- Returns:
bool
- serialize() str [source]#
Generate output in the required format
- Parameters:
kwargs – Generator specific parameters
- Returns:
Generated output
- static sort_classes(clist: List[ClassDefinition]) List[ClassDefinition] [source]#
sort classes such that if C is a child of P then C appears after P in the list
Overridden method include mixin classes
TODO: This should move to SchemaView
- template_dir: str | Path | None = None#
Override templates for each TemplateModel.
Directory with templates that override the default
TemplateModel.template
for each class. If a matching template is not found in the override directory, the default templates will be used.
Templates#
The pydanticgen module has a templating system that allows each part of a schema to be generated independently and customized. See the documentation for the individual classes, but in short - each part of the output pydantic domain has a model with a corresponding template. At render time, each model is recursively rendered.
The PydanticGenerator
then serves as a translation layer between
the source models from linkml_runtime
and the target models in
pydanticgen.template
, making clear what is needed to generate
pydantic code as well as what parts of the linkml metamodel are supported.
Usage example:
Imports:
imports = (Imports() +
Import(module="sys") +
Import(module="pydantic", objects=[{"name": "BaseModel"}, {"name": "Field"}])
)
renders to:
import sys
from pydantic import (
BaseModel,
Field
)
Attributes:
attr = PydanticAttribute(
name="my_field",
annotations={"python_range": {"value": "str"}},
title="My Field!",
description="A Field that is mine!",
pattern="my_.*",
)
By itself, renders to:
my_field: str = Field(None, title="My Field!", description="""A Field that is mine!""")
Classes:
cls = PydanticClass(
name="MyClass",
bases="BaseModel",
description="A Class I Made!",
attributes={"my_field": attr},
)
Renders to (along with the validator for the attribute):
class MyClass(BaseModel):
my_field: str = Field(None, title="My Field!", description="""A Field that is mine!""")
@validator('my_field', allow_reuse=True)
def pattern_my_field(cls, v):
pattern=re.compile(r"my_.*")
if isinstance(v,list):
for element in v:
if not pattern.match(element):
raise ValueError(f"Invalid my_field format: {element}")
elif isinstance(v,str):
if not pattern.match(v):
raise ValueError(f"Invalid my_field format: {v}")
return v
Modules:
module = PydanticModule(imports=imports, classes={cls.name: cls})
Combine all the pieces:
import sys
from pydantic import (
BaseModel,
Field
)
metamodel_version = "None"
version = "None"
class WeakRefShimBaseModel(BaseModel):
__slots__ = '__weakref__'
class ConfiguredBaseModel(WeakRefShimBaseModel,
validate_assignment = True,
validate_all = True,
underscore_attrs_are_private = True,
extra = "forbid",
arbitrary_types_allowed = True,
use_enum_values = True):
pass
class MyClass(BaseModel):
my_field: str = Field(None, title="My Field!", description="""A Field that is mine!""")
@validator('my_field', allow_reuse=True)
def pattern_my_field(cls, v):
pattern=re.compile(r"my_.*")
if isinstance(v,list):
for element in v:
if not pattern.match(element):
raise ValueError(f"Invalid my_field format: {element}")
elif isinstance(v,str):
if not pattern.match(v):
raise ValueError(f"Invalid my_field format: {v}")
return v
# Update forward refs
# see https://pydantic-docs.helpmanual.io/usage/postponed_annotations/
MyClass.update_forward_refs()
- class linkml.generators.pydanticgen.template.TemplateModel(*, pydantic_ver: int = 2)[source]#
Metaclass to render pydantic models with jinja templates.
Each subclass needs to declare a
typing.ClassVar
for a jinja template within the templates directory.Templates are written expecting each of the other TemplateModels to already be rendered to strings - ie. rather than the
class.py.jinja
template receiving a fullPydanticAttribute
object or dictionary, it receives it having already been rendered to a string. See therender()
method.Black Formatting
Template models will try to use
black
to format results when it is available in the environment when render is called withblack = True
. If it isn’t, then the string is returned without any formatting beyond the template. This is mostly important for complex annotations like those produced for arrays, as otherwise the templates are acceptable looking.To install linkml with black, use the extra
black
dependency.e.g. with pip:
pip install linkml[black]
or with poetry:
poetry install -E black
- render(environment: Environment | None = None, black: bool = False) str [source]#
Recursively render a template model to a string.
For each field in the model, recurse through, rendering each
TemplateModel
using the template set inTemplateModel.template
, but preserving the structure of lists and dictionaries. RegularBaseModel
s are rendered to dictionaries. Any other value is passed through unchanged.- Parameters:
environment (
jinja2.Environment
) – Template environment - seeenvironment()
black (bool) – if
True
, format template with black. (default False)
- classmethod environment() Environment [source]#
Default environment for Template models. uses a
jinja2.PackageLoader
for the templates directory within this module with thetrim_blocks
andlstrip_blocks
parameters set toTrue
so that the default templates could be written in a more readable way.
- model_fields: ClassVar[Dict[str, 'ModelField']] = {'pydantic_ver': FieldInfo(annotation=int, required=False, default=2)}#
Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].
This replaces Model.__fields__ from Pydantic V1.
- model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}#
A dictionary of computed field names and their corresponding ComputedFieldInfo objects.
- model_config: ClassVar[ConfigDict] = {}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class linkml.generators.pydanticgen.template.EnumValue(*, label: str, value: str, description: str | None = None)[source]#
A single value within an
Enum
- model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}#
A dictionary of computed field names and their corresponding ComputedFieldInfo objects.
- model_config: ClassVar[ConfigDict] = {}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_fields: ClassVar[dict[str, FieldInfo]] = {'description': FieldInfo(annotation=Union[str, NoneType], required=False, default=None), 'label': FieldInfo(annotation=str, required=True), 'value': FieldInfo(annotation=str, required=True)}#
Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].
This replaces Model.__fields__ from Pydantic V1.
- class linkml.generators.pydanticgen.template.PydanticEnum(*, pydantic_ver: int = 2, name: str, description: str | None = None, values: Dict[str, EnumValue] = None)[source]#
Model used to render a
enum.Enum
- model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}#
A dictionary of computed field names and their corresponding ComputedFieldInfo objects.
- model_config: ClassVar[ConfigDict] = {}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_fields: ClassVar[Dict[str, 'ModelField']] = {'description': FieldInfo(annotation=Union[str, NoneType], required=False, default=None), 'name': FieldInfo(annotation=str, required=True), 'pydantic_ver': FieldInfo(annotation=int, required=False, default=2), 'values': FieldInfo(annotation=Dict[str, linkml.generators.pydanticgen.template.EnumValue], required=False, default_factory=dict)}#
Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].
This replaces Model.__fields__ from Pydantic V1.
- class linkml.generators.pydanticgen.template.PydanticBaseModel(*, pydantic_ver: int = 2, name: str = None, extra_fields: Literal['allow', 'forbid', 'ignore'] = 'forbid', fields: List[str] | None = None, strict: bool = False)[source]#
Parameterization of the base model that generated pydantic classes inherit from
- fields: List[str] | None#
Extra fields that are typically injected into the base model via
injected_fields
- strict: bool#
Enable strict mode in the base model.
Note
Pydantic 2 only! Pydantic 1 only has strict types, not strict mode. See: https://github.com/linkml/linkml/issues/1955
References
- model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}#
A dictionary of computed field names and their corresponding ComputedFieldInfo objects.
- model_config: ClassVar[ConfigDict] = {}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_fields: ClassVar[Dict[str, 'ModelField']] = {'extra_fields': FieldInfo(annotation=Literal['allow', 'forbid', 'ignore'], required=False, default='forbid'), 'fields': FieldInfo(annotation=Union[List[str], NoneType], required=False, default=None), 'name': FieldInfo(annotation=str, required=False, default_factory=<lambda>), 'pydantic_ver': FieldInfo(annotation=int, required=False, default=2), 'strict': FieldInfo(annotation=bool, required=False, default=False)}#
Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].
This replaces Model.__fields__ from Pydantic V1.
- class linkml.generators.pydanticgen.template.PydanticAttribute(*, pydantic_ver: int = 2, name: str, required: bool = False, identifier: bool = False, key: bool = False, predefined: str | None = None, annotations: dict | None = None, title: str | None = None, description: str | None = None, equals_number: int | float | None = None, minimum_value: int | float | None = None, maximum_value: int | float | None = None, pattern: str | None = None)[source]#
Reduced version of SlotDefinition that carries all and only the information needed by the template
- model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {'field': ComputedFieldInfo(wrapped_property=<property object>, return_type=<class 'str'>, alias=None, alias_priority=None, title=None, description='Computed value to use inside of the generated Field', deprecated=None, examples=None, json_schema_extra=None, repr=True)}#
A dictionary of computed field names and their corresponding ComputedFieldInfo objects.
- model_config: ClassVar[ConfigDict] = {}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_fields: ClassVar[Dict[str, 'ModelField']] = {'annotations': FieldInfo(annotation=Union[dict, NoneType], required=False, default=None), 'description': FieldInfo(annotation=Union[str, NoneType], required=False, default=None), 'equals_number': FieldInfo(annotation=Union[int, float, NoneType], required=False, default=None), 'identifier': FieldInfo(annotation=bool, required=False, default=False), 'key': FieldInfo(annotation=bool, required=False, default=False), 'maximum_value': FieldInfo(annotation=Union[int, float, NoneType], required=False, default=None), 'minimum_value': FieldInfo(annotation=Union[int, float, NoneType], required=False, default=None), 'name': FieldInfo(annotation=str, required=True), 'pattern': FieldInfo(annotation=Union[str, NoneType], required=False, default=None), 'predefined': FieldInfo(annotation=Union[str, NoneType], required=False, default=None), 'pydantic_ver': FieldInfo(annotation=int, required=False, default=2), 'required': FieldInfo(annotation=bool, required=False, default=False), 'title': FieldInfo(annotation=Union[str, NoneType], required=False, default=None)}#
Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].
This replaces Model.__fields__ from Pydantic V1.
- class linkml.generators.pydanticgen.template.PydanticValidator(*, pydantic_ver: int = 2, name: str, required: bool = False, identifier: bool = False, key: bool = False, predefined: str | None = None, annotations: dict | None = None, title: str | None = None, description: str | None = None, equals_number: int | float | None = None, minimum_value: int | float | None = None, maximum_value: int | float | None = None, pattern: str | None = None)[source]#
Trivial subclass of
PydanticAttribute
that uses thevalidator.py.jinja
template instead- model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {'field': ComputedFieldInfo(wrapped_property=<property object>, return_type=<class 'str'>, alias=None, alias_priority=None, title=None, description='Computed value to use inside of the generated Field', deprecated=None, examples=None, json_schema_extra=None, repr=True)}#
A dictionary of computed field names and their corresponding ComputedFieldInfo objects.
- model_config: ClassVar[ConfigDict] = {}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_fields: ClassVar[Dict[str, 'ModelField']] = {'annotations': FieldInfo(annotation=Union[dict, NoneType], required=False, default=None), 'description': FieldInfo(annotation=Union[str, NoneType], required=False, default=None), 'equals_number': FieldInfo(annotation=Union[int, float, NoneType], required=False, default=None), 'identifier': FieldInfo(annotation=bool, required=False, default=False), 'key': FieldInfo(annotation=bool, required=False, default=False), 'maximum_value': FieldInfo(annotation=Union[int, float, NoneType], required=False, default=None), 'minimum_value': FieldInfo(annotation=Union[int, float, NoneType], required=False, default=None), 'name': FieldInfo(annotation=str, required=True), 'pattern': FieldInfo(annotation=Union[str, NoneType], required=False, default=None), 'predefined': FieldInfo(annotation=Union[str, NoneType], required=False, default=None), 'pydantic_ver': FieldInfo(annotation=int, required=False, default=2), 'required': FieldInfo(annotation=bool, required=False, default=False), 'title': FieldInfo(annotation=Union[str, NoneType], required=False, default=None)}#
Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].
This replaces Model.__fields__ from Pydantic V1.
- class linkml.generators.pydanticgen.template.PydanticClass(*, pydantic_ver: int = 2, name: str, bases: List[str] | str = 'ConfiguredBaseModel', description: str | None = None, attributes: Dict[str, PydanticAttribute] | None = None)[source]#
Reduced version of ClassDefinition that carries all and only the information needed by the template.
On instantiation and rendering, will create any additional
validators
that are implied by the givenattributes
. Currently the only kind of slot-level validators that are created are for those slots that have apattern
property.- attributes: Dict[str, PydanticAttribute] | None#
- property validators: Dict[str, PydanticValidator] | None#
- model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {'validators': ComputedFieldInfo(wrapped_property=<property object>, return_type=typing.Optional[typing.Dict[str, linkml.generators.pydanticgen.template.PydanticValidator]], alias=None, alias_priority=None, title=None, description=None, deprecated=None, examples=None, json_schema_extra=None, repr=True)}#
A dictionary of computed field names and their corresponding ComputedFieldInfo objects.
- model_config: ClassVar[ConfigDict] = {}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_fields: ClassVar[Dict[str, 'ModelField']] = {'attributes': FieldInfo(annotation=Union[Dict[str, linkml.generators.pydanticgen.template.PydanticAttribute], NoneType], required=False, default=None), 'bases': FieldInfo(annotation=Union[List[str], str], required=False, default='ConfiguredBaseModel'), 'description': FieldInfo(annotation=Union[str, NoneType], required=False, default=None), 'name': FieldInfo(annotation=str, required=True), 'pydantic_ver': FieldInfo(annotation=int, required=False, default=2)}#
Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].
This replaces Model.__fields__ from Pydantic V1.
- class linkml.generators.pydanticgen.template.ObjectImport(*, name: str, alias: str | None = None)[source]#
An object to be imported from within a module.
See
Import
for examples- model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}#
A dictionary of computed field names and their corresponding ComputedFieldInfo objects.
- model_config: ClassVar[ConfigDict] = {}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_fields: ClassVar[dict[str, FieldInfo]] = {'alias': FieldInfo(annotation=Union[str, NoneType], required=False, default=None), 'name': FieldInfo(annotation=str, required=True)}#
Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].
This replaces Model.__fields__ from Pydantic V1.
- class linkml.generators.pydanticgen.template.Import(*, pydantic_ver: int = 2, module: str, alias: str | None = None, objects: List[ObjectImport] | None = None)[source]#
A python module, or module and classes to be imported.
Examples
Module import:
>>> Import(module='sys').render() import sys >>> Import(module='numpy', alias='np').render() import numpy as np
Class import:
>>> Import(module='pathlib', objects=[ >>> ObjectImport(name="Path"), >>> ObjectImport(name="PurePath", alias="RenamedPurePath") >>> ]).render() from pathlib import ( Path, PurePath as RenamedPurePath )
- objects: List[ObjectImport] | None#
- merge(other: Import) List[Import] [source]#
Merge one import with another, see
Imports()
for an example.If module don’t match, return both
If one or the other are a
ConditionalImport
, return bothIf modules match, neither contain objects, but the other has an alias, return the other
If modules match, one contains objects but the other doesn’t, return both
If modules match, both contain objects, merge the object lists, preferring objects with aliases
- model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}#
A dictionary of computed field names and their corresponding ComputedFieldInfo objects.
- model_config: ClassVar[ConfigDict] = {}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_fields: ClassVar[Dict[str, 'ModelField']] = {'alias': FieldInfo(annotation=Union[str, NoneType], required=False, default=None), 'module': FieldInfo(annotation=str, required=True), 'objects': FieldInfo(annotation=Union[List[linkml.generators.pydanticgen.template.ObjectImport], NoneType], required=False, default=None), 'pydantic_ver': FieldInfo(annotation=int, required=False, default=2)}#
Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].
This replaces Model.__fields__ from Pydantic V1.
- class linkml.generators.pydanticgen.template.ConditionalImport(*, pydantic_ver: int = 2, module: str, alias: str | None = None, objects: List[ObjectImport] | None = None, condition: str, alternative: Import)[source]#
Import that depends on some condition in the environment, common when using backported features or straddling dependency versions.
Make sure that everything that is needed to evaluate the condition is imported before this is added to the injected imports!
Examples
conditionally import Literal from
typing_extensions
if on python <= 3.8imports = (Imports() + Import(module='sys') + ConditionalImport( module="typing", objects=[ObjectImport(name="Literal")], condition="sys.version_info >= (3, 8)", alternative=Import( module="typing_extensions", objects=[ObjectImport(name="Literal")] ) )
Renders to:
import sys if sys.version_info >= (3, 8): from typing import Literal else: from typing_extensions import Literal
- model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}#
A dictionary of computed field names and their corresponding ComputedFieldInfo objects.
- model_config: ClassVar[ConfigDict] = {}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_fields: ClassVar[Dict[str, 'ModelField']] = {'alias': FieldInfo(annotation=Union[str, NoneType], required=False, default=None), 'alternative': FieldInfo(annotation=Import, required=True), 'condition': FieldInfo(annotation=str, required=True), 'module': FieldInfo(annotation=str, required=True), 'objects': FieldInfo(annotation=Union[List[linkml.generators.pydanticgen.template.ObjectImport], NoneType], required=False, default=None), 'pydantic_ver': FieldInfo(annotation=int, required=False, default=2)}#
Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].
This replaces Model.__fields__ from Pydantic V1.
- class linkml.generators.pydanticgen.template.Imports(*, pydantic_ver: int = 2, imports: List[Import | ConditionalImport] = None)[source]#
Container class for imports that can handle merging!
See
Import
andConditionalImport
for examples of declaring individual importsUseful for generation, because each build stage will potentially generate overlapping imports. This ensures that we can keep a collection of imports without having many duplicates.
Defines methods for adding, iterating, and indexing from within the
Imports.imports
list.Examples
imports = (Imports() + Import(module="sys") + Import(module="pathlib", objects=[ObjectImport(name="Path")]) + Import(module="sys") )
Renders to:
from pathlib import Path import sys
- imports: List[Import | ConditionalImport]#
- model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {}#
A dictionary of computed field names and their corresponding ComputedFieldInfo objects.
- model_config: ClassVar[ConfigDict] = {}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_fields: ClassVar[Dict[str, 'ModelField']] = {'imports': FieldInfo(annotation=List[Union[linkml.generators.pydanticgen.template.Import, linkml.generators.pydanticgen.template.ConditionalImport]], required=False, default_factory=list), 'pydantic_ver': FieldInfo(annotation=int, required=False, default=2)}#
Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].
This replaces Model.__fields__ from Pydantic V1.
- class linkml.generators.pydanticgen.template.PydanticModule(*, pydantic_ver: int = 2, metamodel_version: str | None = None, version: str | None = None, base_model: PydanticBaseModel = PydanticBaseModel(pydantic_ver=2, name='ConfiguredBaseModel', extra_fields='forbid', fields=None, strict=False), injected_classes: List[str] | None = None, imports: List[Import | ConditionalImport] = None, enums: Dict[str, PydanticEnum] = None, classes: Dict[str, PydanticClass] = None)[source]#
Top-level container model for generating a pydantic module :)
- model_computed_fields: ClassVar[dict[str, ComputedFieldInfo]] = {'class_names': ComputedFieldInfo(wrapped_property=<property object>, return_type=typing.List[str], alias=None, alias_priority=None, title=None, description=None, deprecated=None, examples=None, json_schema_extra=None, repr=True)}#
A dictionary of computed field names and their corresponding ComputedFieldInfo objects.
- model_config: ClassVar[ConfigDict] = {}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_fields: ClassVar[Dict[str, 'ModelField']] = {'base_model': FieldInfo(annotation=PydanticBaseModel, required=False, default=PydanticBaseModel(pydantic_ver=2, name='ConfiguredBaseModel', extra_fields='forbid', fields=None, strict=False)), 'classes': FieldInfo(annotation=Dict[str, linkml.generators.pydanticgen.template.PydanticClass], required=False, default_factory=dict), 'enums': FieldInfo(annotation=Dict[str, linkml.generators.pydanticgen.template.PydanticEnum], required=False, default_factory=dict), 'imports': FieldInfo(annotation=List[Union[linkml.generators.pydanticgen.template.Import, linkml.generators.pydanticgen.template.ConditionalImport]], required=False, default_factory=list), 'injected_classes': FieldInfo(annotation=Union[List[str], NoneType], required=False, default=None), 'metamodel_version': FieldInfo(annotation=Union[str, NoneType], required=False, default=None), 'pydantic_ver': FieldInfo(annotation=int, required=False, default=2), 'version': FieldInfo(annotation=Union[str, NoneType], required=False, default=None)}#
Metadata about the fields defined on the model, mapping of field names to [FieldInfo][pydantic.fields.FieldInfo].
This replaces Model.__fields__ from Pydantic V1.
- base_model: PydanticBaseModel#
- imports: List[Import | ConditionalImport]#
- enums: Dict[str, PydanticEnum]#
- classes: Dict[str, PydanticClass]#
Arrays#
TODO
Narrative documentation for pydantic LoL Arrays. Subsection this by different array reps
See Schemas/Arrays
- class linkml.generators.pydanticgen.array.ArrayRangeGenerator(array: ArrayExpression | None, dtype: str | Element, pydantic_ver: str = '2.7.0')[source]#
Metaclass for generating a given format of array annotation.
See Shape Forms for more details on array range forms.
These classes do only enough validation of the array specification to decide which kind of representation to generate. Proper value validation should happen elsewhere (ie. in the metamodel and generated
ArrayExpression
class.)Each of the array representation generation methods should be able to handle the supported pydantic versions (currently still 1 and 2).
Notes
When checking for array specification, recall that there is a semantic difference between
None
andFalse
, particularly forArrayExpression.max_number_dimensions
- check for absence of specification withis None
rather than checking for truthiness/falsiness (unless that’s what you intend to do ofc ;)- array#
Array to create an annotation for
- Type:
- dtype#
dtype of the entire array as a string
- Type:
Union[str,
Element
- pydantic_ver#
Pydantic version to generate array form for - currently only pydantic 1 and 2 are differentiated, and pydantic 1 will be deprecated soon.
- Type:
- property has_bounded_dimensions: bool#
Whether the
ArrayExpression
has some shape specification aside fromdimensions
- classmethod get_generator(repr: ArrayRepresentation) Type[ArrayRangeGenerator] [source]#
Get the generator class for a given array representation
- abstract any_shape(array: ArrayRepresentation | None = None) SlotResult [source]#
Any shaped array!
- abstract bounded_dimensions(array: ArrayExpression) SlotResult [source]#
Array shape specified numerically, without axis parameterization
- abstract parameterized_dimensions(array: ArrayExpression) SlotResult [source]#
Array shape specified with
dimensions
without additional parameterized dimensions
- abstract complex_dimensions(array: ArrayExpression) SlotResult [source]#
Array shape with both
parameterized
andbounded
dimensions
- class linkml.generators.pydanticgen.array.ListOfListsArray(array: ArrayExpression | None, dtype: str | Element, pydantic_ver: str = '2.7.0')[source]#
Represent arrays as lists of lists!
TODO: Move all validation of values (eg. anywhere we raise a ValueError) to the ArrayExpression dataclass and out of the generator class
- any_shape(array: ArrayExpression | None = None, with_inner_union: bool = False) SlotResult [source]#
An AnyShaped array (using
AnyShapeArray
)- Parameters:
array (
ArrayExpression
) – The array expression (not used)with_inner_union (bool) – If
True
, the innermost type is aUnion
of theAnyShapeArray
class anddtype
(default:False
)
- bounded_dimensions(array: ArrayExpression) SlotResult [source]#
A nested series of
List[]
annotations withdtype
at the center.When an array expression allows for a range of dimensions, each set of
List
s is joined by aUnion
.
- parameterized_dimensions(array: ArrayExpression) SlotResult [source]#
Constrained shapes using
pydantic.conlist()
TODO: - preservation of aliases - (what other metadata is allowable on labeled dimensions?)
- complex_dimensions(array: ArrayExpression) SlotResult [source]#
Mixture of parameterized dimensions with a max or min (or both) shape for anonymous dimensions.
A mixture of
List
,conlist
, andAnyShapeArray
.
Additional Notes#
LinkML contains two Python generators. The Pydantic dataclass generator is specifically useful for FastAPI, but is newer and less full featured than the standard Python generator.
Biolink Example#
Begin by downloading the Biolink Model YAML and adding a virtual environment and installing linkml.
curl -OJ https://raw.githubusercontent.com/biolink/biolink-model/master/biolink-model.yaml
python3 -m venv venv
source venv/bin/activate
pip install linkml
Now generate the classes using the gen-pydantic command
gen-pydantic biolink-model.yaml > biolink-model.py