"""
This file contains Pydantic model definitions for data validation.
Pydantic is a data validation library that uses Python type annotations.
It allows you to define data models with type hints that are validated
at runtime while providing static type checking.
Usage example:
```python
from my_model import MyModel
# Validates data at runtime
my_model = MyModel(name="John", age=30)
# Type-safe - my_model has correct type hints
print(my_model.name)
# Will raise error if validation fails
try:
MyModel(name="", age=30)
except ValidationError as e:
print(e)
```
For more information see:
https://docs.pydantic.dev/
WARNING: This is an auto-generated file.
Do not edit directly - any changes will be overwritten.
"""
{% import "python-macros.jinja" as utils %}
## This is a generated file. Do not modify it manually!
from __future__ import annotations
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional, Generic, TypeVar, Union
from enum import Enum
from uuid import uuid4
from datetime import date, datetime
{%- if config.astropy %}
from mdmodels.units.annotation import UnitDefinitionAnnot
{%- endif %}
# Filter Wrapper definition used to filter a list of objects
# based on their attributes
Cls = TypeVar("Cls")
class FilterWrapper(Generic[Cls]):
"""Wrapper class to filter a list of objects based on their attributes"""
def __init__(self, collection: list[Cls], **kwargs):
self.collection = collection
self.kwargs = kwargs
def filter(self) -> list[Cls]:
for key, value in self.kwargs.items():
self.collection = [
item for item in self.collection if self._fetch_attr(key, item) == value
]
return self.collection
def _fetch_attr(self, name: str, item: Cls):
try:
return getattr(item, name)
except AttributeError:
raise AttributeError(f"{item} does not have attribute {name}")
# JSON-LD Helper Functions
def add_namespace(obj, prefix: str | None, iri: str | None):
"""Adds a namespace to the JSON-LD context
Args:
prefix (str): The prefix to add
iri (str): The IRI to add
"""
if prefix is None and iri is None:
return
elif prefix and iri is None:
raise ValueError("If prefix is provided, iri must also be provided")
elif iri and prefix is None:
raise ValueError("If iri is provided, prefix must also be provided")
obj.ld_context[prefix] = iri # type: ignore
def validate_prefix(term: str | dict, prefix: str):
"""Validates that a term is prefixed with a given prefix
Args:
term (str): The term to validate
prefix (str): The prefix to validate against
Returns:
bool: True if the term is prefixed with the prefix, False otherwise
"""
if isinstance(term, dict) and not term["@id"].startswith(prefix + ":"):
raise ValueError(f"Term {term} is not prefixed with {prefix}")
elif isinstance(term, str) and not term.startswith(prefix + ":"):
raise ValueError(f"Term {term} is not prefixed with {prefix}")
# Model Definitions
{% for object in objects %}
{%- if not (astropy and object.name in ["UnitDefinition", "BaseUnit"]) %}
class {{ object.name }}(BaseModel):
model_config: ConfigDict = ConfigDict( # type: ignore
validate_assignment = True,
populate_by_name = True,
) # type: ignore
{% for attribute in object.attributes %}
{%- if attribute.multiple is true %}
{{ attribute.name | to_identifier }}: {{ utils.get_type(attribute) }} = Field(
default_factory=list,
{%- if (attribute.name | to_identifier) != attribute.name %}
alias="{{ attribute.name }}",
{%- endif %}
description="""{{ wrap(attribute.docstring, 50, "", " ", None ) }}""",
)
{%- elif 'default' in attribute%}
{{ attribute.name | to_identifier }}: {{ utils.get_type(attribute) }} = Field(
default{{ utils.get_default(attribute) }},
{%- if (attribute.name | to_identifier) != attribute.name %}
alias="{{ attribute.name }}",
{%- endif %}
description="""{{ wrap(attribute.docstring, 50, "", " ", None ) }}""",
)
{%- elif attribute.required is true %}
{{ attribute.name | to_identifier }}: {{ utils.get_type(attribute) }} = Field(
default=...,
{%- if (attribute.name | to_identifier) != attribute.name %}
alias="{{ attribute.name }}",
{%- endif %}
description="""{{ wrap(attribute.docstring, 50, "", " ", None ) }}""",
)
{%- else %}
{{ attribute.name | to_identifier }}: {{ utils.get_type(attribute) }} = Field(
default=None,
{%- if (attribute.name | to_identifier) != attribute.name %}
alias="{{ attribute.name }}",
{%- endif %}
description="""{{ wrap(attribute.docstring, 50, "", " ", None ) }}""",
)
{%- endif %}
{%- endfor %}
# JSON-LD fields
ld_id: str = Field(
serialization_alias="@id",
default_factory=lambda: "{{ prefix }}:{{ object.name }}/" + str(uuid4())
)
ld_type: list[str] = Field(
serialization_alias="@type",
default_factory = lambda: [
"{{ prefix }}:{{ object.name }}",
{%- if object.term -%}"{{ object.term }}"{%- endif %}
],
)
ld_context: dict[str, str | dict] = Field(
serialization_alias="@context",
default_factory = lambda: {
"{{ prefix }}": "{{ repo }}",
{%- for prefix, address in prefixes %}
"{{ prefix }}": "{{ address }}",
{%- endfor %}
{%- for attribute in object.attributes %}
{%- if attribute.dtypes[0] in object_names %}
"{{ attribute.dtypes[0] }}": "{{ repo }}{%- if repo[-1] != "/" -%}#{%- endif -%}{{ attribute.dtypes[0] }}/",
{%- elif attribute.dtypes[0] in enum_names %}
"{{ attribute.dtypes[0] }}": "{{ repo }}{%- if repo[-1] != "/" -%}#{%- endif -%}{{ attribute.dtypes[0] }}/",
{%- endif %}
{%- endfor %}
{%- for attribute in object.attributes %}
{%- if attribute.is_id %}
"{{ attribute.name }}": {
{%- if attribute.term %}
"@id": "{{ attribute.term }}",
{%- endif %}
"@type": "@id",
},
{%- elif attribute.term %}
"{{ attribute.name }}": "{{ attribute.term }}",
{%- endif -%}
{%- endfor %}
}
)
{% for attr in object.attributes -%}
{%- if attr.multiple is true and attr.dtypes[0] in object_names %}
def filter_{{ attr.name | to_identifier }}(self, **kwargs) -> list[{{ attr.dtypes[0] }}]:
"""Filters the {{ attr.name }} attribute based on the given kwargs
Args:
**kwargs: The attributes to filter by.
Returns:
list[{{ attr.dtypes[0] }}]: The filtered list of {{ attr.dtypes[0] }} objects
"""
return FilterWrapper[{{ attr.dtypes[0] }}](self.{{ attr.name | to_identifier }}, **kwargs).filter()
{% endif %}
{%- endfor %}
def set_attr_term(
self,
attr: str,
term: str | dict,
prefix: str | None = None,
iri: str | None = None
):
"""Sets the term for a given attribute in the JSON-LD object
Example:
# Using an IRI term
>> obj.set_attr_term("name", "http://schema.org/givenName")
# Using a prefix and term
>> obj.set_attr_term("name", "schema:givenName", "schema", "http://schema.org")
# Usinng a dictionary term
>> obj.set_attr_term("name", {"@id": "http://schema.org/givenName", "@type": "@id"})
Args:
attr (str): The attribute to set the term for
term (str | dict): The term to set for the attribute
Raises:
AssertionError: If the attribute is not found in the model
"""
assert attr in self.model_fields, f"Attribute {attr} not found in {self.__class__.__name__}"
if prefix:
validate_prefix(term, prefix)
add_namespace(self, prefix, iri)
self.ld_context[attr] = term
def add_type_term(
self,
term: str,
prefix: str | None = None,
iri: str | None = None
):
"""Adds a term to the @type field of the JSON-LD object
Example:
# Using a term
>> obj.add_type_term("https://schema.org/Person")
# Using a prefixed term
>> obj.add_type_term("schema:Person", "schema", "https://schema.org/Person")
Args:
term (str): The term to add to the @type field
prefix (str, optional): The prefix to use for the term. Defaults to None.
iri (str, optional): The IRI to use for the term prefix. Defaults to None.
Raises:
ValueError: If prefix is provided but iri is not
ValueError: If iri is provided but prefix is not
"""
if prefix:
validate_prefix(term, prefix)
add_namespace(self, prefix, iri)
self.ld_type.append(term)
{% for attr in object.attributes %}
{% for dtype in attr.dtypes %}
{%- if dtype in object_names and attr.multiple is true %}
def add_to_{{ attr.name | to_identifier }}(
{{ utils.signature(objects, dtype) }}
):
params = { {{ utils.params(objects, dtype) }}
}
if "id" in kwargs:
params["id"] = kwargs["id"]
self.{{ attr.name | to_identifier }}.append(
{{ dtype }}(**params)
)
return self.{{ attr.name | to_identifier }}[-1]
{%- endif %}
{%- endfor %}
{% endfor %}
{%- endif %}
{%- endfor %}
{%- for enum in enums %}
class {{ enum.name }}(Enum):
{%- for key, value in enum.mappings | dictsort %}
{{ key }} = "{{ value }}"
{%- endfor %}
{% endfor %}