diff --git a/numpydantic/__init__.py b/numpydantic/__init__.py index 8d1c8b6..ad62b4a 100644 --- a/numpydantic/__init__.py +++ b/numpydantic/__init__.py @@ -1 +1,7 @@ - +# ruff: noqa: E402 +# ruff: noqa: F401 +from numpydantic.monkeypatch import apply_patches + +apply_patches() + +from numpydantic.ndarray import NDArray diff --git a/numpydantic/linkml/__init__.py b/numpydantic/linkml/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/numpydantic/linkml/ndarraygen.py b/numpydantic/linkml/ndarraygen.py new file mode 100644 index 0000000..0d7e590 --- /dev/null +++ b/numpydantic/linkml/ndarraygen.py @@ -0,0 +1,189 @@ +""" +Isolated generator for array classes +""" + +import warnings +from abc import ABC, abstractmethod + +from linkml_runtime.linkml_model import ClassDefinition, SlotDefinition + +from numpydantic.maps import flat_to_nptyping + + +class ArrayFormat(ABC): + """ + Metaclass for different LinkML array source formats + """ + + @classmethod + def is_array(cls, cls_: ClassDefinition) -> bool: + """Check whether a given class matches one of our subclasses definitions""" + return any([subcls.check(cls_) for subcls in cls.__subclasses__()]) + + @classmethod + def get(cls, cls_: ClassDefinition) -> type["ArrayFormat"]: + """Get matching ArrayFormat subclass""" + for subcls in cls.__subclasses__(): + if subcls.check(cls_): + return subcls + + @classmethod + @abstractmethod + def check(cls, cls_: ClassDefinition) -> bool: + """Method for array format subclasses to check if they match a given source class""" + + @classmethod + @abstractmethod + def make(cls, cls_: ClassDefinition) -> str: + """ + Make an annotation string from a given array format source class + """ + + +class LinkMLNDArray(ArrayFormat): + """ + Tentative linkml-arrays style NDArray + """ + + @classmethod + def check(cls, cls_: ClassDefinition) -> bool: + """Check if linkml:NDArray in implements""" + return "linkml:NDArray" in cls_.implements + + @classmethod + def make(cls, cls_: ClassDefinition) -> str: + """Make NDArray""" + raise NotImplementedError("Havent implemented NDArrays yet!") + + +class LinkMLDataArray(ArrayFormat): + """ + Tentative linkml-arrays style annotated array with indices + """ + + @classmethod + def check(cls, cls_: ClassDefinition) -> bool: + """Check if linkml:DataArray in implements""" + return "linkml:DataArray" in cls_.implements + + @classmethod + def make(cls, cls_: ClassDefinition) -> str: + """Make DataArray""" + raise NotImplementedError("Havent generated DataArray types yet!") + + +class NWBLinkMLArraylike(ArrayFormat): + """ + Ye Olde nwb-linkml Arraylike class + + Examples: + + TimeSeries: + is_a: Arraylike + attributes: + num_times: + name: num_times + range: AnyType + required: true + num_DIM2: + name: num_DIM2 + range: AnyType + required: false + num_DIM3: + name: num_DIM3 + range: AnyType + required: false + num_DIM4: + name: num_DIM4 + range: AnyType + required: false + """ + + @classmethod + def check(cls, cls_: ClassDefinition) -> bool: + """Check if class is Arraylike""" + return cls_.is_a == "Arraylike" + + @classmethod + def make(cls, cls_: ClassDefinition) -> str: + """Make Arraylike annotation""" + return cls._array_annotation(cls_) + + @classmethod + def _array_annotation(cls, cls_: ClassDefinition) -> str: + """ + Make an annotation for an NDArray :) + + Args: + cls_: + + Returns: + + """ + # if none of the dimensions are optional, we just have one possible array shape + if all([s.required for s in cls_.attributes.values()]): # pragma: no cover + return cls._make_npytyping_range(cls_.attributes) + # otherwise we need to make permutations + # but not all permutations, because we typically just want to be able to exlude the last possible dimensions + # the array classes should always be well-defined where the optional dimensions are at the end, so + requireds = {k: v for k, v in cls_.attributes.items() if v.required} + optionals = [(k, v) for k, v in cls_.attributes.items() if not v.required] + + annotations = [] + if len(requireds) > 0: + # first the base case + annotations.append(cls._make_npytyping_range(requireds)) + # then add back each optional dimension + for i in range(len(optionals)): + attrs = {**requireds, **{k: v for k, v in optionals[0 : i + 1]}} + annotations.append(cls._make_npytyping_range(attrs)) + + # now combine with a union: + union = "Union[\n" + " " * 8 + union += (",\n" + " " * 8).join(annotations) + union += "\n" + " " * 4 + "]" + return union + + @classmethod + def _make_npytyping_range(cls, attrs: dict[str, SlotDefinition]) -> str: + # slot always starts with... + prefix = "NDArray[" + + # and then we specify the shape: + shape_prefix = 'Shape["' + + # using the cardinality from the attributes + dim_pieces = [] + for attr in attrs.values(): + shape_part = ( + str(attr.maximum_cardinality) if attr.maximum_cardinality else "*" + ) + + # do this with the most heinous chain of string replacements rather than regex + # because i am still figuring out what needs to be subbed lol + name_part = ( + attr.name.replace(",", "_") + .replace(" ", "_") + .replace("__", "_") + .replace("|", "_") + .replace("-", "_") + .replace("+", "plus") + ) + + dim_pieces.append(" ".join([shape_part, name_part])) + + dimension = ", ".join(dim_pieces) + + shape_suffix = '"], ' + + # all dimensions should be the same dtype + try: + dtype = flat_to_nptyping[list(attrs.values())[0].range] + except KeyError as e: # pragma: no cover + warnings.warn(str(e), stacklevel=2) + range = list(attrs.values())[0].range + return f"List[{range}] | {range}" + suffix = "]" + + slot = "".join([prefix, shape_prefix, dimension, shape_suffix, dtype, suffix]) + return slot diff --git a/numpydantic/linkml.py b/numpydantic/linkml/pydanticgen.py similarity index 69% rename from numpydantic/linkml.py rename to numpydantic/linkml/pydanticgen.py index 54bc115..ec24d80 100644 --- a/numpydantic/linkml.py +++ b/numpydantic/linkml/pydanticgen.py @@ -1,30 +1,16 @@ """ -Subclass of :class:`linkml.generators.PydanticGenerator` +Patched subclass of :class:`linkml.generators.PydanticGenerator` to generate NDArrays +swiped from ``nwb-linkml``. -The pydantic generator is a subclass of -- :class:`linkml.utils.generator.Generator` -- :class:`linkml.generators.oocodegen.OOCodeGenerator` +Since this is an override of the full generator originally intended for a specific format, +this is a bit more involved than the isolated ndarray type generator. The most relevant +parts here are: +- The :class:`.ArrayCheck` class, which is used to determine when an array needs to be generated - + Use this to hook into nonstandard array formats that don't match the usual ``implements`` pattern -The default `__main__` method -- Instantiates the class -- Calls :meth:`~linkml.generators.PydanticGenerator.serialize` - -The `serialize` method: - -- Accepts an optional jinja-style template, otherwise it uses the default template -- Uses :class:`linkml_runtime.utils.schemaview.SchemaView` to interact with the schema -- Generates linkML Classes - - `generate_enums` runs first - -.. note:: - - This module is heinous. We will be tidying this up and trying to pull changes upstream, - but for now this is just our hacky little secret. """ -import inspect import sys -import warnings from copy import copy from dataclasses import dataclass from pathlib import Path @@ -52,6 +38,9 @@ from linkml_runtime.utils.schemaview import SchemaView # from nwb_linkml.maps import flat_to_nptyping from pydantic import BaseModel +from numpydantic.linkml.ndarraygen import ArrayFormat +from numpydantic.linkml.template import default_template + def module_case(name: str) -> str: """ @@ -81,184 +70,20 @@ class LinkML_Meta(BaseModel): tree_root: bool = False -def default_template( - pydantic_ver: str = "2", extra_classes: list[type[BaseModel]] | None = None -) -> str: - """Constructs a default template for pydantic classes based on the version of pydantic""" - ### HEADER ### - template = """ -{#- - - Jinja2 Template for a pydantic classes --#} -from __future__ import annotations -from datetime import datetime, date -from enum import Enum -from typing import Dict, Optional, Any, Union, ClassVar, Annotated, TypeVar, List, TYPE_CHECKING -from pydantic import BaseModel as BaseModel, Field""" - if pydantic_ver == "2": - template += """ -from pydantic import ConfigDict, BeforeValidator - """ - template += """ -from nptyping import Shape, Float, Float32, Double, Float64, LongLong, Int64, Int, Int32, Int16, Short, Int8, UInt, UInt32, UInt16, UInt8, UInt64, Number, String, Unicode, Unicode, Unicode, String, Bool, Datetime64 -from nwb_linkml.types import NDArray -import sys -if sys.version_info >= (3, 8): - from typing import Literal -else: - from typing_extensions import Literal -if TYPE_CHECKING: - import numpy as np - -{% for import_module, import_classes in imports.items() %} -from {{ import_module }} import ( - {{ import_classes | join(',\n ') }} -) -{% endfor %} - -metamodel_version = "{{metamodel_version}}" -version = "{{version if version else None}}" -""" - ### BASE MODEL ### - if pydantic_ver == "1": # pragma: no cover - template += """ -List = BaseList - -class WeakRefShimBaseModel(BaseModel): - __slots__ = '__weakref__' - -class ConfiguredBaseModel(WeakRefShimBaseModel, - validate_assignment = False, - validate_all = True, - underscore_attrs_are_private = True, - extra = {% if allow_extra %}'allow'{% else %}'forbid'{% endif %}, - arbitrary_types_allowed = True, - use_enum_values = True): -""" - else: - template += """ -class ConfiguredBaseModel(BaseModel): - model_config = ConfigDict( - validate_assignment = True, - validate_default = True, - extra = {% if allow_extra %}'allow'{% else %}'forbid'{% endif %}, - arbitrary_types_allowed = True, - use_enum_values = True +def linkml_classvar(cls: ClassDefinition) -> SlotDefinition: + """A class variable that holds additional linkml attrs""" + slot = SlotDefinition(name="linkml_meta") + slot.annotations["python_range"] = Annotation( + "python_range", "ClassVar[LinkML_Meta]" ) -""" - ### Injected Fields - template += """ -{%- if injected_fields != None -%} - {% for field in injected_fields %} - {{ field }} - {% endfor %} -{%- else -%} - pass -{%- endif -%} - """ - ### Getitem - template += """ - - def __getitem__(self, i: slice|int) -> 'np.ndarray': - if hasattr(self, 'array'): - return self.array[i] - else: - return super().__getitem__(i) - - def __setitem__(self, i: slice|int, value: Any): - if hasattr(self, 'array'): - self.array[i] = value - else: - super().__setitem__(i, value) - """ - - ### Extra classes - if extra_classes is not None: - template += """{{ '\n\n' }}""" - for cls in extra_classes: - template += inspect.getsource(cls) + "\n\n" - ### ENUMS ### - template += """ -{% for e in enums.values() %} -class {{ e.name }}(str, Enum): - {% if e.description -%} - \"\"\" - {{ e.description }} - \"\"\" - {%- endif %} - {% for _, pv in e['values'].items() -%} - {% if pv.description -%} - # {{pv.description}} - {%- endif %} - {{pv.label}} = "{{pv.value}}" - {% endfor %} - {% if not e['values'] -%} - dummy = "dummy" - {% endif %} -{% endfor %} -""" - ### CLASSES ### - template += """ -{%- for c in schema.classes.values() %} -class {{ c.name }} - {%- if class_isa_plus_mixins[c.name] -%} - ({{class_isa_plus_mixins[c.name]|join(', ')}}) - {%- else -%} - (ConfiguredBaseModel) - {%- endif -%} - : - {% if c.description -%} - \"\"\" - {{ c.description }} - \"\"\" - {%- endif %} - {% for attr in c.attributes.values() if c.attributes -%} - {{attr.name}}:{{ ' ' }}{%- if attr.equals_string -%} - Literal[{{ predefined_slot_values[c.name][attr.name] }}] - {%- else -%} - {{ attr.annotations['python_range'].value }} - {%- endif -%} - {%- if attr.annotations['fixed_field'] -%} - {{ ' ' }}= {{ attr.annotations['fixed_field'].value }} - {%- else -%} - {{ ' ' }}= Field( - {%- if predefined_slot_values[c.name][attr.name] is string -%} - {{ predefined_slot_values[c.name][attr.name] }} - {%- elif attr.required -%} - ... - {%- else -%} - None - {%- endif -%} - {%- if attr.title != None %}, title="{{attr.title}}"{% endif -%} - {%- if attr.description %}, description=\"\"\"{{attr.description}}\"\"\"{% endif -%} - {%- if attr.minimum_value != None %}, ge={{attr.minimum_value}}{% endif -%} - {%- if attr.maximum_value != None %}, le={{attr.maximum_value}}{% endif -%} + meta_fields = {k: getattr(cls, k, None) for k in LinkML_Meta.model_fields} + meta_field_strings = [f"{k}={v}" for k, v in meta_fields.items() if v is not None] + meta_field_string = ", ".join(meta_field_strings) + slot.annotations["fixed_field"] = Annotation( + "fixed_field", f"Field(LinkML_Meta({meta_field_string}), frozen=True)" ) - {%- endif %} - {% else -%} - None - {% endfor %} -{% endfor %} -""" - ### FWD REFS / REBUILD MODEL ### - if pydantic_ver == "1": # pragma: no cover - template += """ -# Update forward refs -# see https://pydantic-docs.helpmanual.io/usage/postponed_annotations/ -{% for c in schema.classes.values() -%} -{{ c.name }}.update_forward_refs() -{% endfor %} -""" - else: - template += """ -# Model rebuild -# see https://pydantic-docs.helpmanual.io/usage/models/#rebuilding-a-model -{% for c in schema.classes.values() -%} -{{ c.name }}.model_rebuild() -{% endfor %} -""" - return template + + return slot @dataclass @@ -380,7 +205,7 @@ class PydanticGenerator(BasePydanticGenerator): if not self.split: # we are compiling this whole thing in one big file so we don't import anything return {} - if "is_namespace" in sv.schema.annotations.keys() and sv.schema.annotations[ + if "is_namespace" in sv.schema.annotations and sv.schema.annotations[ "is_namespace" ]["value"] in ("True", True): return self._get_namespace_imports(sv) @@ -447,7 +272,7 @@ class PydanticGenerator(BasePydanticGenerator): def _check_anyof( self, s: SlotDefinition, sn: SlotDefinitionName, sv: SchemaView - ): # pragma: no cover + ) -> None: # pragma: no cover # Confirm that the original slot range (ignoring the default that comes in from # induced_slot) isn't in addition to setting any_of if len(s.any_of) > 0 and sv.get_slot(sn).range is not None: @@ -459,94 +284,6 @@ class PydanticGenerator(BasePydanticGenerator): if not base_range_subsumes_any_of: raise ValueError("Slot cannot have both range and any_of defined") - def _make_npytyping_range(self, attrs: dict[str, SlotDefinition]) -> str: - # slot always starts with... - prefix = "NDArray[" - - # and then we specify the shape: - shape_prefix = 'Shape["' - - # using the cardinality from the attributes - dim_pieces = [] - for attr in attrs.values(): - if attr.maximum_cardinality: - shape_part = str(attr.maximum_cardinality) - else: - shape_part = "*" - - # do this with the most heinous chain of string replacements rather than regex - # because i am still figuring out what needs to be subbed lol - name_part = ( - attr.name.replace(",", "_") - .replace(" ", "_") - .replace("__", "_") - .replace("|", "_") - .replace("-", "_") - .replace("+", "plus") - ) - - dim_pieces.append(" ".join([shape_part, name_part])) - - dimension = ", ".join(dim_pieces) - - shape_suffix = '"], ' - - # all dimensions should be the same dtype - try: - dtype = flat_to_nptyping[list(attrs.values())[0].range] - except KeyError as e: # pragma: no cover - warnings.warn(str(e)) - range = list(attrs.values())[0].range - return f"List[{range}] | {range}" - suffix = "]" - - slot = "".join([prefix, shape_prefix, dimension, shape_suffix, dtype, suffix]) - return slot - - def _get_numpy_slot_range(self, cls: ClassDefinition) -> str: - # if none of the dimensions are optional, we just have one possible array shape - if all([s.required for s in cls.attributes.values()]): # pragma: no cover - return self._make_npytyping_range(cls.attributes) - # otherwise we need to make permutations - # but not all permutations, because we typically just want to be able to exlude the last possible dimensions - # the array classes should always be well-defined where the optional dimensions are at the end, so - requireds = {k: v for k, v in cls.attributes.items() if v.required} - optionals = [(k, v) for k, v in cls.attributes.items() if not v.required] - - annotations = [] - if len(requireds) > 0: - # first the base case - annotations.append(self._make_npytyping_range(requireds)) - # then add back each optional dimension - for i in range(len(optionals)): - attrs = {**requireds, **{k: v for k, v in optionals[0 : i + 1]}} - annotations.append(self._make_npytyping_range(attrs)) - - # now combine with a union: - union = "Union[\n" + " " * 8 - union += (",\n" + " " * 8).join(annotations) - union += "\n" + " " * 4 + "]" - return union - - def _get_linkml_classvar(self, cls: ClassDefinition) -> SlotDefinition: - """A class variable that holds additional linkml attrs""" - slot = SlotDefinition(name="linkml_meta") - slot.annotations["python_range"] = Annotation( - "python_range", "ClassVar[LinkML_Meta]" - ) - meta_fields = { - k: getattr(cls, k, None) for k in LinkML_Meta.model_fields.keys() - } - meta_field_strings = [ - f"{k}={v}" for k, v in meta_fields.items() if v is not None - ] - meta_field_string = ", ".join(meta_field_strings) - slot.annotations["fixed_field"] = Annotation( - "fixed_field", f"Field(LinkML_Meta({meta_field_string}), frozen=True)" - ) - - return slot - def sort_classes( self, clist: list[ClassDefinition], imports: dict[str, list[str]] ) -> list[ClassDefinition]: @@ -564,7 +301,7 @@ class PydanticGenerator(BasePydanticGenerator): clist = list(clist) clist = [c for c in clist if c.name not in self.SKIP_CLASSES] - slist = [] # type: List[ClassDefinition] + slist = [] # type: list[ClassDefinition] while len(clist) > 0: can_add = False for i in range(len(clist)): @@ -604,8 +341,8 @@ class PydanticGenerator(BasePydanticGenerator): """ sv = self.schemaview range_cls = sv.get_class(slot_range) - if range_cls.is_a == "Arraylike": - return self._get_numpy_slot_range(range_cls) + if ArrayFormat.is_array(range_cls): + return ArrayFormat.get(range_cls).make(range_cls) else: return self._get_class_slot_range_origin( slot_range, inlined, inlined_as_list @@ -619,7 +356,7 @@ class PydanticGenerator(BasePydanticGenerator): Overriding to not use strings in the type hint when a class has an identifier value - Not testing this method except for what we changes + Not testing this method except for what we changed """ sv = self.schemaview range_cls = sv.get_class(slot_range) @@ -732,6 +469,7 @@ class PydanticGenerator(BasePydanticGenerator): return slot_value def serialize(self) -> str: + """Generate LinkML models from schema!""" predefined_slot_values = {} """splitting up parent class :meth:`.get_predefined_slot_values`""" @@ -787,7 +525,7 @@ class PydanticGenerator(BasePydanticGenerator): del class_def.attributes[attribute] # make class attr that stores extra linkml attrs - class_def.attributes["linkml_meta"] = self._get_linkml_classvar(class_def) + class_def.attributes["linkml_meta"] = linkml_classvar(class_def) class_name = class_original.name predefined_slot_values[camelcase(class_name)] = {} diff --git a/numpydantic/linkml/template.py b/numpydantic/linkml/template.py new file mode 100644 index 0000000..68a7240 --- /dev/null +++ b/numpydantic/linkml/template.py @@ -0,0 +1,183 @@ +import inspect + +from pydantic import BaseModel + + +def default_template( + pydantic_ver: str = "2", extra_classes: list[type[BaseModel]] | None = None +) -> str: + """Constructs a default template for pydantic classes based on the version of pydantic""" + ### HEADER ### + template = """ +{#- + + Jinja2 Template for a pydantic classes +-#} +from __future__ import annotations +from datetime import datetime, date +from enum import Enum +from typing import Dict, Optional, Any, Union, ClassVar, Annotated, TypeVar, List, TYPE_CHECKING +from pydantic import BaseModel as BaseModel, Field""" + if pydantic_ver == "2": + template += """ +from pydantic import ConfigDict, BeforeValidator + """ + template += """ +from nptyping import Shape, Float, Float32, Double, Float64, LongLong, Int64, Int, Int32, Int16, Short, Int8, UInt, UInt32, UInt16, UInt8, UInt64, Number, String, Unicode, Unicode, Unicode, String, Bool, Datetime64 +from nwb_linkml.types import NDArray +import sys +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal +if TYPE_CHECKING: + import numpy as np + +{% for import_module, import_classes in imports.items() %} +from {{ import_module }} import ( + {{ import_classes | join(',\n ') }} +) +{% endfor %} + +metamodel_version = "{{metamodel_version}}" +version = "{{version if version else None}}" +""" + ### BASE MODEL ### + if pydantic_ver == "1": # pragma: no cover + template += """ +List = BaseList + +class WeakRefShimBaseModel(BaseModel): + __slots__ = '__weakref__' + +class ConfiguredBaseModel(WeakRefShimBaseModel, + validate_assignment = False, + validate_all = True, + underscore_attrs_are_private = True, + extra = {% if allow_extra %}'allow'{% else %}'forbid'{% endif %}, + arbitrary_types_allowed = True, + use_enum_values = True): +""" + else: + template += """ +class ConfiguredBaseModel(BaseModel): + model_config = ConfigDict( + validate_assignment = True, + validate_default = True, + extra = {% if allow_extra %}'allow'{% else %}'forbid'{% endif %}, + arbitrary_types_allowed = True, + use_enum_values = True + ) +""" + ### Injected Fields + template += """ +{%- if injected_fields != None -%} + {% for field in injected_fields %} + {{ field }} + {% endfor %} +{%- else -%} + pass +{%- endif -%} + """ + ### Getitem + template += """ + + def __getitem__(self, i: slice|int) -> 'np.ndarray': + if hasattr(self, 'array'): + return self.array[i] + else: + return super().__getitem__(i) + + def __setitem__(self, i: slice|int, value: Any): + if hasattr(self, 'array'): + self.array[i] = value + else: + super().__setitem__(i, value) + """ + + ### Extra classes + if extra_classes is not None: + template += """{{ '\n\n' }}""" + for cls in extra_classes: + template += inspect.getsource(cls) + "\n\n" + ### ENUMS ### + template += """ +{% for e in enums.values() %} +class {{ e.name }}(str, Enum): + {% if e.description -%} + \"\"\" + {{ e.description }} + \"\"\" + {%- endif %} + {% for _, pv in e['values'].items() -%} + {% if pv.description -%} + # {{pv.description}} + {%- endif %} + {{pv.label}} = "{{pv.value}}" + {% endfor %} + {% if not e['values'] -%} + dummy = "dummy" + {% endif %} +{% endfor %} +""" + ### CLASSES ### + template += """ +{%- for c in schema.classes.values() %} +class {{ c.name }} + {%- if class_isa_plus_mixins[c.name] -%} + ({{class_isa_plus_mixins[c.name]|join(', ')}}) + {%- else -%} + (ConfiguredBaseModel) + {%- endif -%} + : + {% if c.description -%} + \"\"\" + {{ c.description }} + \"\"\" + {%- endif %} + {% for attr in c.attributes.values() if c.attributes -%} + {{attr.name}}:{{ ' ' }}{%- if attr.equals_string -%} + Literal[{{ predefined_slot_values[c.name][attr.name] }}] + {%- else -%} + {{ attr.annotations['python_range'].value }} + {%- endif -%} + {%- if attr.annotations['fixed_field'] -%} + {{ ' ' }}= {{ attr.annotations['fixed_field'].value }} + {%- else -%} + {{ ' ' }}= Field( + {%- if predefined_slot_values[c.name][attr.name] is string -%} + {{ predefined_slot_values[c.name][attr.name] }} + {%- elif attr.required -%} + ... + {%- else -%} + None + {%- endif -%} + {%- if attr.title != None %}, title="{{attr.title}}"{% endif -%} + {%- if attr.description %}, description=\"\"\"{{attr.description}}\"\"\"{% endif -%} + {%- if attr.minimum_value != None %}, ge={{attr.minimum_value}}{% endif -%} + {%- if attr.maximum_value != None %}, le={{attr.maximum_value}}{% endif -%} + ) + {%- endif %} + {% else -%} + None + {% endfor %} +{% endfor %} +""" + ### FWD REFS / REBUILD MODEL ### + if pydantic_ver == "1": # pragma: no cover + template += """ +# Update forward refs +# see https://pydantic-docs.helpmanual.io/usage/postponed_annotations/ +{% for c in schema.classes.values() -%} +{{ c.name }}.update_forward_refs() +{% endfor %} +""" + else: + template += """ +# Model rebuild +# see https://pydantic-docs.helpmanual.io/usage/models/#rebuilding-a-model +{% for c in schema.classes.values() -%} +{{ c.name }}.model_rebuild() +{% endfor %} +""" + return template diff --git a/numpydantic/maps.py b/numpydantic/maps.py index 2707a8d..054fcad 100644 --- a/numpydantic/maps.py +++ b/numpydantic/maps.py @@ -42,3 +42,35 @@ np_to_python = { }, **{n: str for n in (np.character, np.str_, np.string_, np.unicode_)}, } + + +flat_to_nptyping = { + "float": "Float", + "float32": "Float32", + "double": "Double", + "float64": "Float64", + "long": "LongLong", + "int64": "Int64", + "int": "Int", + "int32": "Int32", + "int16": "Int16", + "short": "Short", + "int8": "Int8", + "uint": "UInt", + "uint32": "UInt32", + "uint16": "UInt16", + "uint8": "UInt8", + "uint64": "UInt64", + "numeric": "Number", + "text": "String", + "utf": "Unicode", + "utf8": "Unicode", + "utf_8": "Unicode", + "string": "Unicode", + "str": "Unicode", + "ascii": "String", + "bool": "Bool", + "isodatetime": "Datetime64", + "AnyType": "Any", + "object": "Object", +} diff --git a/numpydantic/monkeypatch.py b/numpydantic/monkeypatch.py new file mode 100644 index 0000000..7940ba4 --- /dev/null +++ b/numpydantic/monkeypatch.py @@ -0,0 +1,58 @@ +def patch_npytyping_perf() -> None: + """ + npytyping makes an expensive call to inspect.stack() + that makes imports of pydantic models take ~200x longer than + they should: + + References: + - https://github.com/ramonhagenaars/nptyping/issues/110 + """ + import inspect + from types import FrameType + + from nptyping import base_meta_classes, ndarray, recarray + from nptyping.pandas_ import dataframe + + # make a new __module__ methods for the affected classes + def new_module_ndarray(cls) -> str: + return cls._get_module(inspect.currentframe(), "nptyping.ndarray") + + def new_module_recarray(cls) -> str: + return cls._get_module(inspect.currentframe(), "nptyping.recarray") + + def new_module_dataframe(cls) -> str: + return cls._get_module(inspect.currentframe(), "nptyping.pandas_.dataframe") + + # and a new _get_module method for the parent class + def new_get_module(cls, stack: FrameType, module: str) -> str: + return ( + "typing" + if inspect.getframeinfo(stack.f_back).function == "formatannotation" + else module + ) + + # now apply the patches + ndarray.NDArrayMeta.__module__ = property(new_module_ndarray) + recarray.RecArrayMeta.__module__ = property(new_module_recarray) + dataframe.DataFrameMeta.__module__ = property(new_module_dataframe) + base_meta_classes.SubscriptableMeta._get_module = new_get_module + + +def patch_nptyping_warnings() -> None: + """ + nptyping shits out a bunch of numpy deprecation warnings from using + olde aliases + + References: + - https://github.com/ramonhagenaars/nptyping/issues/113 + - https://github.com/ramonhagenaars/nptyping/issues/102 + """ + import warnings + + warnings.filterwarnings("ignore", category=DeprecationWarning, module="nptyping.*") + + +def apply_patches() -> None: + """Apply all monkeypatches!""" + patch_npytyping_perf() + patch_nptyping_warnings() diff --git a/numpydantic/ndarray.py b/numpydantic/ndarray.py index fb0e3df..315d4ba 100644 --- a/numpydantic/ndarray.py +++ b/numpydantic/ndarray.py @@ -24,8 +24,7 @@ from pydantic_core.core_schema import ListSchema from numpydantic.maps import np_to_python -if TYPE_CHECKING: - from numpydantic.proxy import NDArrayProxy +from numpydantic.proxy import NDArrayProxy COMPRESSION_THRESHOLD = 16 * 1024 """ @@ -33,7 +32,7 @@ Arrays larger than this size (in bytes) will be compressed and b64 encoded when serializing to JSON. """ -ARRAY_TYPES = Union[np.ndarray, DaskArray, "NDArrayProxy"] +ARRAY_TYPES = np.ndarray | DaskArray | NDArrayProxy def list_of_lists_schema(shape: Shape, array_type_handler: dict) -> ListSchema: @@ -153,7 +152,17 @@ class NDArrayMeta(_NDArrayMeta, implementation="NDArray"): class NDArray(NPTypingType, metaclass=NDArrayMeta): """ - Following the example here: https://docs.pydantic.dev/latest/usage/types/custom/#handling-third-party-types + Constrained array type allowing npytyping syntax for dtype and shape validation and serialization. + + Integrates with pydantic such that + - JSON schema for list of list encoding + - Serialized as LoL, with automatic compression for large arrays + - Automatic coercion from lists on instantiation + + Also supports validation on :class:`.NDArrayProxy` types for lazy loading. + + References: + - https://docs.pydantic.dev/latest/usage/types/custom/#handling-third-party-types """ __args__ = (Any, Any) diff --git a/numpydantic/proxy.py b/numpydantic/proxy.py index 7663793..8470f40 100644 --- a/numpydantic/proxy.py +++ b/numpydantic/proxy.py @@ -43,11 +43,6 @@ class NDArrayProxy: _source_type: _NDArray, _handler: Callable[[Any], core_schema.CoreSchema], ) -> core_schema.CoreSchema: - # return core_schema.no_info_after_validator_function( - # serialization=core_schema.plain_serializer_function_ser_schema( - # lambda array: array.tolist(), - # when_used='json' - # ) - # ) + from numpydantic import NDArray - return NDArray_.__get_pydantic_core_schema__(cls, _source_type, _handler) + return NDArray.__get_pydantic_core_schema__(cls, _source_type, _handler) diff --git a/poetry.lock b/poetry.lock index 9e68f2b..7189208 100644 --- a/poetry.lock +++ b/poetry.lock @@ -479,13 +479,13 @@ yaml = ["PyYAML (>=3.10)"] [[package]] name = "curies" -version = "0.7.6" +version = "0.7.7" description = "Idiomatic conversion between URIs and compact URIs (CURIEs)." optional = true python-versions = ">=3.8" files = [ - {file = "curies-0.7.6-py3-none-any.whl", hash = "sha256:3307e757e47ed4384edb705c73cad40ad5e688e2dea263a60e6a5e5a6c33105d"}, - {file = "curies-0.7.6.tar.gz", hash = "sha256:f86da3539cee349249f5b64db99651053649551920b9fe945c150719c8b9b40e"}, + {file = "curies-0.7.7-py3-none-any.whl", hash = "sha256:609de3e8cdf39f410e8f4d9f06eb7df379465860f4fb441bf0e79672430f8e2a"}, + {file = "curies-0.7.7.tar.gz", hash = "sha256:a8d674029f906fb9c3564eafa0862ce96725932bd801fa751e076265b111cb34"}, ] [package.dependencies] @@ -619,13 +619,13 @@ files = [ [[package]] name = "fsspec" -version = "2023.12.2" +version = "2024.2.0" description = "File-system specification" optional = false python-versions = ">=3.8" files = [ - {file = "fsspec-2023.12.2-py3-none-any.whl", hash = "sha256:d800d87f72189a745fa3d6b033b9dc4a34ad069f60ca60b943a63599f5501960"}, - {file = "fsspec-2023.12.2.tar.gz", hash = "sha256:8548d39e8810b59c38014934f6b31e57f40c1b20f911f4cc2b85389c7e9bf0cb"}, + {file = "fsspec-2024.2.0-py3-none-any.whl", hash = "sha256:817f969556fa5916bc682e02ca2045f96ff7f586d45110fcb76022063ad2c7d8"}, + {file = "fsspec-2024.2.0.tar.gz", hash = "sha256:b6ad1a679f760dda52b1168c859d01b7b80648ea6f7f7c7f5a8a91dc3f3ecb84"}, ] [package.extras] @@ -643,7 +643,7 @@ github = ["requests"] gs = ["gcsfs"] gui = ["panel"] hdfs = ["pyarrow (>=1)"] -http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "requests"] +http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"] libarchive = ["libarchive-c"] oci = ["ocifs"] s3 = ["s3fs"] @@ -1170,71 +1170,71 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" -version = "2.1.4" +version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = true python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a07f40ef8f0fbc5ef1000d0c78771f4d5ca03b4953fc162749772916b298fc4"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d18b66fe626ac412d96c2ab536306c736c66cf2a31c243a45025156cc190dc8a"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698e84142f3f884114ea8cf83e7a67ca8f4ace8454e78fe960646c6c91c63bfa"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a3b78a5af63ec10d8604180380c13dcd870aba7928c1fe04e881d5c792dc4e"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:15866d7f2dc60cfdde12ebb4e75e41be862348b4728300c36cdf405e258415ec"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6aa5e2e7fc9bc042ae82d8b79d795b9a62bd8f15ba1e7594e3db243f158b5565"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:54635102ba3cf5da26eb6f96c4b8c53af8a9c0d97b64bdcb592596a6255d8518"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-win32.whl", hash = "sha256:3583a3a3ab7958e354dc1d25be74aee6228938312ee875a22330c4dc2e41beb0"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:d6e427c7378c7f1b2bef6a344c925b8b63623d3321c09a237b7cc0e77dd98ceb"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bf1196dcc239e608605b716e7b166eb5faf4bc192f8a44b81e85251e62584bd2"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df98d4a9cd6a88d6a585852f56f2155c9cdb6aec78361a19f938810aa020954"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b835aba863195269ea358cecc21b400276747cc977492319fd7682b8cd2c253d"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23984d1bdae01bee794267424af55eef4dfc038dc5d1272860669b2aa025c9e3"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c98c33ffe20e9a489145d97070a435ea0679fddaabcafe19982fe9c971987d5"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9896fca4a8eb246defc8b2a7ac77ef7553b638e04fbf170bff78a40fa8a91474"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b0fe73bac2fed83839dbdbe6da84ae2a31c11cfc1c777a40dbd8ac8a6ed1560f"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c7556bafeaa0a50e2fe7dc86e0382dea349ebcad8f010d5a7dc6ba568eaaa789"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-win32.whl", hash = "sha256:fc1a75aa8f11b87910ffd98de62b29d6520b6d6e8a3de69a70ca34dea85d2a8a"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:3a66c36a3864df95e4f62f9167c734b3b1192cb0851b43d7cc08040c074c6279"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:765f036a3d00395a326df2835d8f86b637dbaf9832f90f5d196c3b8a7a5080cb"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21e7af8091007bf4bebf4521184f4880a6acab8df0df52ef9e513d8e5db23411"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c31fe855c77cad679b302aabc42d724ed87c043b1432d457f4976add1c2c3e"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653fa39578957bc42e5ebc15cf4361d9e0ee4b702d7d5ec96cdac860953c5b4"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47bb5f0142b8b64ed1399b6b60f700a580335c8e1c57f2f15587bd072012decc"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fe8512ed897d5daf089e5bd010c3dc03bb1bdae00b35588c49b98268d4a01e00"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:36d7626a8cca4d34216875aee5a1d3d654bb3dac201c1c003d182283e3205949"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6f14a9cd50c3cb100eb94b3273131c80d102e19bb20253ac7bd7336118a673a"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-win32.whl", hash = "sha256:c8f253a84dbd2c63c19590fa86a032ef3d8cc18923b8049d91bcdeeb2581fbf6"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:8b570a1537367b52396e53325769608f2a687ec9a4363647af1cded8928af959"}, - {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] [[package]] @@ -1625,18 +1625,18 @@ files = [ [[package]] name = "pydantic" -version = "2.6.0" +version = "2.6.1" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.6.0-py3-none-any.whl", hash = "sha256:1440966574e1b5b99cf75a13bec7b20e3512e8a61b894ae252f56275e2c465ae"}, - {file = "pydantic-2.6.0.tar.gz", hash = "sha256:ae887bd94eb404b09d86e4d12f93893bdca79d766e738528c6fa1c849f3c6bcf"}, + {file = "pydantic-2.6.1-py3-none-any.whl", hash = "sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f"}, + {file = "pydantic-2.6.1.tar.gz", hash = "sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9"}, ] [package.dependencies] annotated-types = ">=0.4.0" -pydantic-core = "2.16.1" +pydantic-core = "2.16.2" typing-extensions = ">=4.6.1" [package.extras] @@ -1644,90 +1644,90 @@ email = ["email-validator (>=2.0.0)"] [[package]] name = "pydantic-core" -version = "2.16.1" +version = "2.16.2" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.16.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:300616102fb71241ff477a2cbbc847321dbec49428434a2f17f37528721c4948"}, - {file = "pydantic_core-2.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5511f962dd1b9b553e9534c3b9c6a4b0c9ded3d8c2be96e61d56f933feef9e1f"}, - {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98f0edee7ee9cc7f9221af2e1b95bd02810e1c7a6d115cfd82698803d385b28f"}, - {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9795f56aa6b2296f05ac79d8a424e94056730c0b860a62b0fdcfe6340b658cc8"}, - {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c45f62e4107ebd05166717ac58f6feb44471ed450d07fecd90e5f69d9bf03c48"}, - {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:462d599299c5971f03c676e2b63aa80fec5ebc572d89ce766cd11ca8bcb56f3f"}, - {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ebaa4bf6386a3b22eec518da7d679c8363fb7fb70cf6972161e5542f470798"}, - {file = "pydantic_core-2.16.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:99f9a50b56713a598d33bc23a9912224fc5d7f9f292444e6664236ae471ddf17"}, - {file = "pydantic_core-2.16.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8ec364e280db4235389b5e1e6ee924723c693cbc98e9d28dc1767041ff9bc388"}, - {file = "pydantic_core-2.16.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:653a5dfd00f601a0ed6654a8b877b18d65ac32c9d9997456e0ab240807be6cf7"}, - {file = "pydantic_core-2.16.1-cp310-none-win32.whl", hash = "sha256:1661c668c1bb67b7cec96914329d9ab66755911d093bb9063c4c8914188af6d4"}, - {file = "pydantic_core-2.16.1-cp310-none-win_amd64.whl", hash = "sha256:561be4e3e952c2f9056fba5267b99be4ec2afadc27261505d4992c50b33c513c"}, - {file = "pydantic_core-2.16.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:102569d371fadc40d8f8598a59379c37ec60164315884467052830b28cc4e9da"}, - {file = "pydantic_core-2.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:735dceec50fa907a3c314b84ed609dec54b76a814aa14eb90da31d1d36873a5e"}, - {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e83ebbf020be727d6e0991c1b192a5c2e7113eb66e3def0cd0c62f9f266247e4"}, - {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:30a8259569fbeec49cfac7fda3ec8123486ef1b729225222f0d41d5f840b476f"}, - {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:920c4897e55e2881db6a6da151198e5001552c3777cd42b8a4c2f72eedc2ee91"}, - {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5247a3d74355f8b1d780d0f3b32a23dd9f6d3ff43ef2037c6dcd249f35ecf4c"}, - {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5bea8012df5bb6dda1e67d0563ac50b7f64a5d5858348b5c8cb5043811c19d"}, - {file = "pydantic_core-2.16.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ed3025a8a7e5a59817b7494686d449ebfbe301f3e757b852c8d0d1961d6be864"}, - {file = "pydantic_core-2.16.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:06f0d5a1d9e1b7932477c172cc720b3b23c18762ed7a8efa8398298a59d177c7"}, - {file = "pydantic_core-2.16.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:150ba5c86f502c040b822777e2e519b5625b47813bd05f9273a8ed169c97d9ae"}, - {file = "pydantic_core-2.16.1-cp311-none-win32.whl", hash = "sha256:d6cbdf12ef967a6aa401cf5cdf47850559e59eedad10e781471c960583f25aa1"}, - {file = "pydantic_core-2.16.1-cp311-none-win_amd64.whl", hash = "sha256:afa01d25769af33a8dac0d905d5c7bb2d73c7c3d5161b2dd6f8b5b5eea6a3c4c"}, - {file = "pydantic_core-2.16.1-cp311-none-win_arm64.whl", hash = "sha256:1a2fe7b00a49b51047334d84aafd7e39f80b7675cad0083678c58983662da89b"}, - {file = "pydantic_core-2.16.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f478ec204772a5c8218e30eb813ca43e34005dff2eafa03931b3d8caef87d51"}, - {file = "pydantic_core-2.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f1936ef138bed2165dd8573aa65e3095ef7c2b6247faccd0e15186aabdda7f66"}, - {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99d3a433ef5dc3021c9534a58a3686c88363c591974c16c54a01af7efd741f13"}, - {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd88f40f2294440d3f3c6308e50d96a0d3d0973d6f1a5732875d10f569acef49"}, - {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fac641bbfa43d5a1bed99d28aa1fded1984d31c670a95aac1bf1d36ac6ce137"}, - {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72bf9308a82b75039b8c8edd2be2924c352eda5da14a920551a8b65d5ee89253"}, - {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb4363e6c9fc87365c2bc777a1f585a22f2f56642501885ffc7942138499bf54"}, - {file = "pydantic_core-2.16.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:20f724a023042588d0f4396bbbcf4cffd0ddd0ad3ed4f0d8e6d4ac4264bae81e"}, - {file = "pydantic_core-2.16.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fb4370b15111905bf8b5ba2129b926af9470f014cb0493a67d23e9d7a48348e8"}, - {file = "pydantic_core-2.16.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23632132f1fd608034f1a56cc3e484be00854db845b3a4a508834be5a6435a6f"}, - {file = "pydantic_core-2.16.1-cp312-none-win32.whl", hash = "sha256:b9f3e0bffad6e238f7acc20c393c1ed8fab4371e3b3bc311020dfa6020d99212"}, - {file = "pydantic_core-2.16.1-cp312-none-win_amd64.whl", hash = "sha256:a0b4cfe408cd84c53bab7d83e4209458de676a6ec5e9c623ae914ce1cb79b96f"}, - {file = "pydantic_core-2.16.1-cp312-none-win_arm64.whl", hash = "sha256:d195add190abccefc70ad0f9a0141ad7da53e16183048380e688b466702195dd"}, - {file = "pydantic_core-2.16.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:502c062a18d84452858f8aea1e520e12a4d5228fc3621ea5061409d666ea1706"}, - {file = "pydantic_core-2.16.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d8c032ccee90b37b44e05948b449a2d6baed7e614df3d3f47fe432c952c21b60"}, - {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:920f4633bee43d7a2818e1a1a788906df5a17b7ab6fe411220ed92b42940f818"}, - {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9f5d37ff01edcbace53a402e80793640c25798fb7208f105d87a25e6fcc9ea06"}, - {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:399166f24c33a0c5759ecc4801f040dbc87d412c1a6d6292b2349b4c505effc9"}, - {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ac89ccc39cd1d556cc72d6752f252dc869dde41c7c936e86beac5eb555041b66"}, - {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73802194f10c394c2bedce7a135ba1d8ba6cff23adf4217612bfc5cf060de34c"}, - {file = "pydantic_core-2.16.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8fa00fa24ffd8c31fac081bf7be7eb495be6d248db127f8776575a746fa55c95"}, - {file = "pydantic_core-2.16.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:601d3e42452cd4f2891c13fa8c70366d71851c1593ed42f57bf37f40f7dca3c8"}, - {file = "pydantic_core-2.16.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07982b82d121ed3fc1c51faf6e8f57ff09b1325d2efccaa257dd8c0dd937acca"}, - {file = "pydantic_core-2.16.1-cp38-none-win32.whl", hash = "sha256:d0bf6f93a55d3fa7a079d811b29100b019784e2ee6bc06b0bb839538272a5610"}, - {file = "pydantic_core-2.16.1-cp38-none-win_amd64.whl", hash = "sha256:fbec2af0ebafa57eb82c18c304b37c86a8abddf7022955d1742b3d5471a6339e"}, - {file = "pydantic_core-2.16.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a497be217818c318d93f07e14502ef93d44e6a20c72b04c530611e45e54c2196"}, - {file = "pydantic_core-2.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:694a5e9f1f2c124a17ff2d0be613fd53ba0c26de588eb4bdab8bca855e550d95"}, - {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d4dfc66abea3ec6d9f83e837a8f8a7d9d3a76d25c9911735c76d6745950e62c"}, - {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8655f55fe68c4685673265a650ef71beb2d31871c049c8b80262026f23605ee3"}, - {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21e3298486c4ea4e4d5cc6fb69e06fb02a4e22089304308817035ac006a7f506"}, - {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71b4a48a7427f14679f0015b13c712863d28bb1ab700bd11776a5368135c7d60"}, - {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dca874e35bb60ce4f9f6665bfbfad050dd7573596608aeb9e098621ac331dc"}, - {file = "pydantic_core-2.16.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa496cd45cda0165d597e9d6f01e36c33c9508f75cf03c0a650018c5048f578e"}, - {file = "pydantic_core-2.16.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5317c04349472e683803da262c781c42c5628a9be73f4750ac7d13040efb5d2d"}, - {file = "pydantic_core-2.16.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:42c29d54ed4501a30cd71015bf982fa95e4a60117b44e1a200290ce687d3e640"}, - {file = "pydantic_core-2.16.1-cp39-none-win32.whl", hash = "sha256:ba07646f35e4e49376c9831130039d1b478fbfa1215ae62ad62d2ee63cf9c18f"}, - {file = "pydantic_core-2.16.1-cp39-none-win_amd64.whl", hash = "sha256:2133b0e412a47868a358713287ff9f9a328879da547dc88be67481cdac529118"}, - {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d25ef0c33f22649b7a088035fd65ac1ce6464fa2876578df1adad9472f918a76"}, - {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:99c095457eea8550c9fa9a7a992e842aeae1429dab6b6b378710f62bfb70b394"}, - {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b49c604ace7a7aa8af31196abbf8f2193be605db6739ed905ecaf62af31ccae0"}, - {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c56da23034fe66221f2208c813d8aa509eea34d97328ce2add56e219c3a9f41c"}, - {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cebf8d56fee3b08ad40d332a807ecccd4153d3f1ba8231e111d9759f02edfd05"}, - {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:1ae8048cba95f382dba56766525abca438328455e35c283bb202964f41a780b0"}, - {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:780daad9e35b18d10d7219d24bfb30148ca2afc309928e1d4d53de86822593dc"}, - {file = "pydantic_core-2.16.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c94b5537bf6ce66e4d7830c6993152940a188600f6ae044435287753044a8fe2"}, - {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:adf28099d061a25fbcc6531febb7a091e027605385de9fe14dd6a97319d614cf"}, - {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:644904600c15816a1f9a1bafa6aab0d21db2788abcdf4e2a77951280473f33e1"}, - {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87bce04f09f0552b66fca0c4e10da78d17cb0e71c205864bab4e9595122cb9d9"}, - {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:877045a7969ace04d59516d5d6a7dee13106822f99a5d8df5e6822941f7bedc8"}, - {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9c46e556ee266ed3fb7b7a882b53df3c76b45e872fdab8d9cf49ae5e91147fd7"}, - {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4eebbd049008eb800f519578e944b8dc8e0f7d59a5abb5924cc2d4ed3a1834ff"}, - {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:c0be58529d43d38ae849a91932391eb93275a06b93b79a8ab828b012e916a206"}, - {file = "pydantic_core-2.16.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b1fc07896fc1851558f532dffc8987e526b682ec73140886c831d773cef44b76"}, - {file = "pydantic_core-2.16.1.tar.gz", hash = "sha256:daff04257b49ab7f4b3f73f98283d3dbb1a65bf3500d55c7beac3c66c310fe34"}, + {file = "pydantic_core-2.16.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c"}, + {file = "pydantic_core-2.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef"}, + {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990"}, + {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b"}, + {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731"}, + {file = "pydantic_core-2.16.2-cp310-none-win32.whl", hash = "sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485"}, + {file = "pydantic_core-2.16.2-cp310-none-win_amd64.whl", hash = "sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f"}, + {file = "pydantic_core-2.16.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11"}, + {file = "pydantic_core-2.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272"}, + {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113"}, + {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8"}, + {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97"}, + {file = "pydantic_core-2.16.2-cp311-none-win32.whl", hash = "sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b"}, + {file = "pydantic_core-2.16.2-cp311-none-win_amd64.whl", hash = "sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc"}, + {file = "pydantic_core-2.16.2-cp311-none-win_arm64.whl", hash = "sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0"}, + {file = "pydantic_core-2.16.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039"}, + {file = "pydantic_core-2.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380"}, + {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb"}, + {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e"}, + {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc"}, + {file = "pydantic_core-2.16.2-cp312-none-win32.whl", hash = "sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d"}, + {file = "pydantic_core-2.16.2-cp312-none-win_amd64.whl", hash = "sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890"}, + {file = "pydantic_core-2.16.2-cp312-none-win_arm64.whl", hash = "sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943"}, + {file = "pydantic_core-2.16.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17"}, + {file = "pydantic_core-2.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf"}, + {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc"}, + {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b"}, + {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f"}, + {file = "pydantic_core-2.16.2-cp38-none-win32.whl", hash = "sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a"}, + {file = "pydantic_core-2.16.2-cp38-none-win_amd64.whl", hash = "sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a"}, + {file = "pydantic_core-2.16.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77"}, + {file = "pydantic_core-2.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878"}, + {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55"}, + {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3"}, + {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2"}, + {file = "pydantic_core-2.16.2-cp39-none-win32.whl", hash = "sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469"}, + {file = "pydantic_core-2.16.2-cp39-none-win_amd64.whl", hash = "sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8"}, + {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804"}, + {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2"}, + {file = "pydantic_core-2.16.2.tar.gz", hash = "sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06"}, ] [package.dependencies] @@ -1853,20 +1853,20 @@ shexjsg = ">=0.8.1" [[package]] name = "pytest" -version = "7.4.4" +version = "8.0.0" description = "pytest: simple powerful testing with Python" optional = true -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, + {file = "pytest-8.0.0-py3-none-any.whl", hash = "sha256:50fb9cbe836c3f20f0dfa99c565201fb75dc54c8d76373cd1bde06b06657bdb6"}, + {file = "pytest-8.0.0.tar.gz", hash = "sha256:249b1b0864530ba251b7438274c4d251c58d868edaaec8762893ad4a0d71c36c"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<2.0" +pluggy = ">=1.3.0,<2.0" [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] @@ -2471,13 +2471,13 @@ test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools [[package]] name = "sphinx-autobuild" -version = "2021.3.14" +version = "2024.2.4" description = "Rebuild Sphinx documentation on changes, with live-reload in the browser." optional = true -python-versions = ">=3.6" +python-versions = ">=3.9" files = [ - {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, - {file = "sphinx_autobuild-2021.3.14-py3-none-any.whl", hash = "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac"}, + {file = "sphinx_autobuild-2024.2.4-py3-none-any.whl", hash = "sha256:63fd87ab7505872a89aef468ce6503f65e794a195f4ae62269db3b85b72d4854"}, + {file = "sphinx_autobuild-2024.2.4.tar.gz", hash = "sha256:cb9d2121a176d62d45471624872afc5fad7755ad662738abe400ecf4a7954303"}, ] [package.dependencies] @@ -2486,7 +2486,7 @@ livereload = "*" sphinx = "*" [package.extras] -test = ["pytest", "pytest-cov"] +test = ["pytest (>=6.0)", "pytest-cov"] [[package]] name = "sphinx-basic-ng" @@ -2988,12 +2988,13 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.link testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [extras] -dev = ["autodoc-pydantic", "black", "coverage", "coveralls", "dask", "furo", "h5py", "myst-parser", "pytest", "pytest-cov", "pytest-depends", "ruff", "sphinx", "sphinx-autobuild", "sphinx-design"] +dev = ["autodoc-pydantic", "black", "coverage", "coveralls", "dask", "furo", "h5py", "linkml", "linkml-runtime", "myst-parser", "pytest", "pytest-cov", "pytest-depends", "ruff", "sphinx", "sphinx-autobuild", "sphinx-design"] docs = ["autodoc-pydantic", "furo", "myst-parser", "sphinx", "sphinx-design"] +linkml = ["linkml", "linkml-runtime"] proxy = ["dask", "h5py"] tests = ["coverage", "coveralls", "pytest", "pytest-cov", "pytest-depends"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "b57dc65220117fd2b0ca76f3e55c765789a3b00eb3b28a376b92059abeebab0f" +content-hash = "d752a8794037df9c7b736d98cf82ec64e0bd4f7a32a52d996302d4277e7fbfd3" diff --git a/pyproject.toml b/pyproject.toml index f5bd505..9fe8883 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,8 +11,8 @@ python = "^3.11" pydantic = ">=2.3.0" nptyping = ">=2.5.0" blosc2 = "^2.5.1" -dask = { version = "^2024.1.1" } -h5py = { version = "^3.10.0" } +dask = "^2024.1.1" +h5py = "^3.10.0" pytest = { version=">=7.4.0", optional = true} pytest-depends = {version="^1.0.1", optional = true} coverage = {version = ">=6.1.1", optional = true} @@ -105,3 +105,8 @@ ignore = [ ] fixable = ["ALL"] + +[tool.mypy] +plugins = [ + "pydantic.mypy" +] diff --git a/tests/test_linkml.py b/tests/test_linkml.py deleted file mode 100644 index 198ff1d..0000000 --- a/tests/test_linkml.py +++ /dev/null @@ -1,120 +0,0 @@ -""" -Test custom features of the pydantic generator - -Note that since this is largely a subclass, we don't test all of the functionality of the generator -because it's tested in the base linkml package. -""" -import re -import sys -import typing - -import numpy as np -import pytest -from pydantic import BaseModel - - -def test_arraylike(imported_schema): - """ - Arraylike classes are converted to slots that specify nptyping arrays - - array: Optional[Union[ - NDArray[Shape["* x, * y"], Number], - NDArray[Shape["* x, * y, 3 z"], Number], - NDArray[Shape["* x, * y, 3 z, 4 a"], Number] - ]] = Field(None) - """ - # check that we have gotten an NDArray annotation and its shape is correct - array = imported_schema["core"].MainTopLevel.model_fields["array"].annotation - args = typing.get_args(array) - for i, shape in enumerate(("* x, * y", "* x, * y, 3 z", "* x, * y, 3 z, 4 a")): - assert isinstance(args[i], NDArrayMeta) - assert args[i].__args__[0].__args__ - assert args[i].__args__[1] == np.number - - # we shouldn't have an actual class for the array - assert not hasattr(imported_schema["core"], "MainTopLevel__Array") - assert not hasattr(imported_schema["core"], "MainTopLevelArray") - - -def test_inject_fields(imported_schema): - """ - Our root model should have the special fields we injected - """ - base = imported_schema["core"].ConfiguredBaseModel - assert "hdf5_path" in base.model_fields - assert "object_id" in base.model_fields - - -def test_linkml_meta(imported_schema): - """ - We should be able to store some linkml metadata with our classes - """ - meta = imported_schema["core"].LinkML_Meta - assert "tree_root" in meta.model_fields - assert imported_schema["core"].MainTopLevel.linkml_meta.default.tree_root == True - assert imported_schema["core"].OtherClass.linkml_meta.default.tree_root == False - - -def test_skip(linkml_schema): - """ - We can skip slots and classes - """ - modules = generate_and_import( - linkml_schema, - split=False, - generator_kwargs={ - "SKIP_SLOTS": ("SkippableSlot",), - "SKIP_CLASSES": ("Skippable", "skippable"), - }, - ) - assert not hasattr(modules["core"], "Skippable") - assert "SkippableSlot" not in modules["core"].MainTopLevel.model_fields - - -def test_inline_with_identifier(imported_schema): - """ - By default, if a class has an identifier attribute, it is inlined - as a string rather than its class. We overrode that to be able to make dictionaries of collections - """ - main = imported_schema["core"].MainTopLevel - inline = main.model_fields["inline_dict"].annotation - assert typing.get_origin(typing.get_args(inline)[0]) == dict - # god i hate pythons typing interface - otherclass, stillanother = typing.get_args( - typing.get_args(typing.get_args(inline)[0])[1] - ) - assert otherclass is imported_schema["core"].OtherClass - assert stillanother is imported_schema["core"].StillAnotherClass - - -def test_namespace(imported_schema): - """ - Namespace schema import all classes from the other schema - Returns: - - """ - ns = imported_schema["namespace"] - - for classname, modname in ( - ("MainThing", "test_schema.imported"), - ("Arraylike", "test_schema.imported"), - ("MainTopLevel", "test_schema.core"), - ("Skippable", "test_schema.core"), - ("OtherClass", "test_schema.core"), - ("StillAnotherClass", "test_schema.core"), - ): - assert hasattr(ns, classname) - if imported_schema["split"]: - assert getattr(ns, classname).__module__ == modname - - -def test_get_set_item(imported_schema): - """We can get and set without explicitly addressing array""" - cls = imported_schema["core"].MainTopLevel(array=np.array([[1, 2, 3], [4, 5, 6]])) - cls[0] = 50 - assert (cls[0] == 50).all() - assert (cls.array[0] == 50).all() - - cls[1, 1] = 100 - assert cls[1, 1] == 100 - assert cls.array[1, 1] == 100 diff --git a/tests/test_linkml/__init__.py b/tests/test_linkml/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_linkml/test_pydanticgen.py b/tests/test_linkml/test_pydanticgen.py new file mode 100644 index 0000000..d8de13f --- /dev/null +++ b/tests/test_linkml/test_pydanticgen.py @@ -0,0 +1,120 @@ +""" +Test custom features of the pydantic generator + +Note that since this is largely a subclass, we don't test all of the functionality of the generator +because it's tested in the base linkml package. +""" +import re +import sys +import typing + +import numpy as np +import pytest +from pydantic import BaseModel + + +# def test_arraylike(imported_schema): +# """ +# Arraylike classes are converted to slots that specify nptyping arrays +# +# array: Optional[Union[ +# NDArray[Shape["* x, * y"], Number], +# NDArray[Shape["* x, * y, 3 z"], Number], +# NDArray[Shape["* x, * y, 3 z, 4 a"], Number] +# ]] = Field(None) +# """ +# # check that we have gotten an NDArray annotation and its shape is correct +# array = imported_schema["core"].MainTopLevel.model_fields["array"].annotation +# args = typing.get_args(array) +# for i, shape in enumerate(("* x, * y", "* x, * y, 3 z", "* x, * y, 3 z, 4 a")): +# assert isinstance(args[i], NDArrayMeta) +# assert args[i].__args__[0].__args__ +# assert args[i].__args__[1] == np.number +# +# # we shouldn't have an actual class for the array +# assert not hasattr(imported_schema["core"], "MainTopLevel__Array") +# assert not hasattr(imported_schema["core"], "MainTopLevelArray") +# +# +# def test_inject_fields(imported_schema): +# """ +# Our root model should have the special fields we injected +# """ +# base = imported_schema["core"].ConfiguredBaseModel +# assert "hdf5_path" in base.model_fields +# assert "object_id" in base.model_fields +# +# +# def test_linkml_meta(imported_schema): +# """ +# We should be able to store some linkml metadata with our classes +# """ +# meta = imported_schema["core"].LinkML_Meta +# assert "tree_root" in meta.model_fields +# assert imported_schema["core"].MainTopLevel.linkml_meta.default.tree_root == True +# assert imported_schema["core"].OtherClass.linkml_meta.default.tree_root == False +# +# +# def test_skip(linkml_schema): +# """ +# We can skip slots and classes +# """ +# modules = generate_and_import( +# linkml_schema, +# split=False, +# generator_kwargs={ +# "SKIP_SLOTS": ("SkippableSlot",), +# "SKIP_CLASSES": ("Skippable", "skippable"), +# }, +# ) +# assert not hasattr(modules["core"], "Skippable") +# assert "SkippableSlot" not in modules["core"].MainTopLevel.model_fields +# +# +# def test_inline_with_identifier(imported_schema): +# """ +# By default, if a class has an identifier attribute, it is inlined +# as a string rather than its class. We overrode that to be able to make dictionaries of collections +# """ +# main = imported_schema["core"].MainTopLevel +# inline = main.model_fields["inline_dict"].annotation +# assert typing.get_origin(typing.get_args(inline)[0]) == dict +# # god i hate pythons typing interface +# otherclass, stillanother = typing.get_args( +# typing.get_args(typing.get_args(inline)[0])[1] +# ) +# assert otherclass is imported_schema["core"].OtherClass +# assert stillanother is imported_schema["core"].StillAnotherClass +# +# +# def test_namespace(imported_schema): +# """ +# Namespace schema import all classes from the other schema +# Returns: +# +# """ +# ns = imported_schema["namespace"] +# +# for classname, modname in ( +# ("MainThing", "test_schema.imported"), +# ("Arraylike", "test_schema.imported"), +# ("MainTopLevel", "test_schema.core"), +# ("Skippable", "test_schema.core"), +# ("OtherClass", "test_schema.core"), +# ("StillAnotherClass", "test_schema.core"), +# ): +# assert hasattr(ns, classname) +# if imported_schema["split"]: +# assert getattr(ns, classname).__module__ == modname +# +# +# def test_get_set_item(imported_schema): +# """We can get and set without explicitly addressing array""" +# cls_ = imported_schema["core"].MainTopLevel(array=np.array([[1, 2, 3], [4, 5, 6]])) +# cls_[0] = 50 +# assert (cls_[0] == 50).all() +# assert (cls_.array[0] == 50).all() +# +# cls_[1, 1] = 100 +# assert cls_[1, 1] == 100 +# assert cls_.array[1, 1] == 100