diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d86f03e..f27fb6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: - name: Set up python uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: 3.11 - name: Bootstrap poetry run: | curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 @@ -31,7 +31,7 @@ jobs: - name: Set up python uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: 3.11 - name: Bootstrap poetry run: | curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 @@ -76,7 +76,7 @@ jobs: - name: Set up python uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: 3.11 - name: Bootstrap poetry run: | curl -sSL https://install.python-poetry.org | python - -y --version 1.5.1 diff --git a/src/sayari/attributes/client.py b/src/sayari/attributes/client.py index fd4aba9..7b99e66 100644 --- a/src/sayari/attributes/client.py +++ b/src/sayari/attributes/client.py @@ -5,6 +5,7 @@ from .types.add_attribute import AddAttribute from ..core.request_options import RequestOptions from .types.attribute_response import AttributeResponse +from ..core.serialization import convert_and_respect_annotation_metadata from ..core.pydantic_utilities import parse_obj_as from ..shared_errors.errors.bad_request import BadRequest from ..shared_errors.types.bad_request_response import BadRequestResponse @@ -82,7 +83,7 @@ def post_attribute( _response = self._client_wrapper.httpx_client.request( "v1/attribute", method="POST", - json=request, + json=convert_and_respect_annotation_metadata(object_=request, annotation=AddAttribute, direction="write"), request_options=request_options, omit=OMIT, ) @@ -217,7 +218,9 @@ def patch_attribute( _response = self._client_wrapper.httpx_client.request( f"v1/attribute/{jsonable_encoder(attribute_id)}", method="PATCH", - json=request, + json=convert_and_respect_annotation_metadata( + object_=request, annotation=UpdateAttribute, direction="write" + ), request_options=request_options, omit=OMIT, ) @@ -502,7 +505,7 @@ async def main() -> None: _response = await self._client_wrapper.httpx_client.request( "v1/attribute", method="POST", - json=request, + json=convert_and_respect_annotation_metadata(object_=request, annotation=AddAttribute, direction="write"), request_options=request_options, omit=OMIT, ) @@ -645,7 +648,9 @@ async def main() -> None: _response = await self._client_wrapper.httpx_client.request( f"v1/attribute/{jsonable_encoder(attribute_id)}", method="PATCH", - json=request, + json=convert_and_respect_annotation_metadata( + object_=request, annotation=UpdateAttribute, direction="write" + ), request_options=request_options, omit=OMIT, ) diff --git a/src/sayari/core/__init__.py b/src/sayari/core/__init__.py index 4213c34..f03aecb 100644 --- a/src/sayari/core/__init__.py +++ b/src/sayari/core/__init__.py @@ -3,7 +3,7 @@ from .api_error import ApiError from .client_wrapper import AsyncClientWrapper, BaseClientWrapper, SyncClientWrapper from .datetime_utils import serialize_datetime -from .file import File, convert_file_dict_to_httpx_tuples +from .file import File, convert_file_dict_to_httpx_tuples, with_content_type from .http_client import AsyncHttpClient, HttpClient from .jsonable_encoder import jsonable_encoder from .pydantic_utilities import ( @@ -43,4 +43,5 @@ "universal_field_validator", "universal_root_validator", "update_forward_refs", + "with_content_type", ] diff --git a/src/sayari/core/file.py b/src/sayari/core/file.py index 6e0f92b..b4cbba3 100644 --- a/src/sayari/core/file.py +++ b/src/sayari/core/file.py @@ -1,30 +1,30 @@ # This file was auto-generated by Fern from our API Definition. -import typing +from typing import IO, Dict, List, Mapping, Optional, Tuple, Union, cast # File typing inspired by the flexibility of types within the httpx library # https://github.com/encode/httpx/blob/master/httpx/_types.py -FileContent = typing.Union[typing.IO[bytes], bytes, str] -File = typing.Union[ +FileContent = Union[IO[bytes], bytes, str] +File = Union[ # file (or bytes) FileContent, # (filename, file (or bytes)) - typing.Tuple[typing.Optional[str], FileContent], + Tuple[Optional[str], FileContent], # (filename, file (or bytes), content_type) - typing.Tuple[typing.Optional[str], FileContent, typing.Optional[str]], + Tuple[Optional[str], FileContent, Optional[str]], # (filename, file (or bytes), content_type, headers) - typing.Tuple[ - typing.Optional[str], + Tuple[ + Optional[str], FileContent, - typing.Optional[str], - typing.Mapping[str, str], + Optional[str], + Mapping[str, str], ], ] def convert_file_dict_to_httpx_tuples( - d: typing.Dict[str, typing.Union[File, typing.List[File]]], -) -> typing.List[typing.Tuple[str, File]]: + d: Dict[str, Union[File, List[File]]], +) -> List[Tuple[str, File]]: """ The format we use is a list of tuples, where the first element is the name of the file and the second is the file object. Typically HTTPX wants @@ -41,3 +41,22 @@ def convert_file_dict_to_httpx_tuples( else: httpx_tuples.append((key, file_like)) return httpx_tuples + + +def with_content_type(*, file: File, content_type: str) -> File: + """ """ + if isinstance(file, tuple): + if len(file) == 2: + filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore + return (filename, content, content_type) + elif len(file) == 3: + filename, content, _ = cast(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore + return (filename, content, content_type) + elif len(file) == 4: + filename, content, _, headers = cast( # type: ignore + Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file + ) + return (filename, content, content_type, headers) + else: + raise ValueError(f"Unexpected tuple length: {len(file)}") + return (None, file, content_type) diff --git a/src/sayari/core/pydantic_utilities.py b/src/sayari/core/pydantic_utilities.py index eb42918..a0875ac 100644 --- a/src/sayari/core/pydantic_utilities.py +++ b/src/sayari/core/pydantic_utilities.py @@ -10,6 +10,7 @@ import pydantic from .datetime_utils import serialize_datetime +from .serialization import convert_and_respect_annotation_metadata IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.") @@ -56,11 +57,12 @@ def parse_obj_as(type_: typing.Type[T], object_: typing.Any) -> T: + dealiased_object = convert_and_respect_annotation_metadata(object_=object_, annotation=type_, direction="read") if IS_PYDANTIC_V2: adapter = pydantic.TypeAdapter(type_) # type: ignore # Pydantic v2 - return adapter.validate_python(object_) + return adapter.validate_python(dealiased_object) else: - return pydantic.parse_obj_as(type_, object_) + return pydantic.parse_obj_as(type_, dealiased_object) def to_jsonable_with_fallback( @@ -75,11 +77,40 @@ def to_jsonable_with_fallback( class UniversalBaseModel(pydantic.BaseModel): - class Config: - populate_by_name = True - smart_union = True - allow_population_by_field_name = True - json_encoders = {dt.datetime: serialize_datetime} + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + # Allow fields begining with `model_` to be used in the model + protected_namespaces=(), + ) # type: ignore # Pydantic v2 + + @pydantic.model_serializer(mode="wrap", when_used="json") # type: ignore # Pydantic v2 + def serialize_model(self, handler: pydantic.SerializerFunctionWrapHandler) -> typing.Any: # type: ignore # Pydantic v2 + serialized = handler(self) + data = {k: serialize_datetime(v) if isinstance(v, dt.datetime) else v for k, v in serialized.items()} + return data + + else: + + class Config: + smart_union = True + json_encoders = {dt.datetime: serialize_datetime} + + @classmethod + def model_construct( + cls: type[Model], _fields_set: typing.Optional[typing.Set[str]] = None, **values: typing.Any + ) -> Model: + dealiased_object = convert_and_respect_annotation_metadata(object_=values, annotation=cls, direction="read") + return cls.construct(_fields_set, **dealiased_object) + + @classmethod + def construct( + cls: type[Model], _fields_set: typing.Optional[typing.Set[str]] = None, **values: typing.Any + ) -> Model: + dealiased_object = convert_and_respect_annotation_metadata(object_=values, annotation=cls, direction="read") + if IS_PYDANTIC_V2: + return super().model_construct(_fields_set, **dealiased_object) # type: ignore # Pydantic v2 + else: + return super().construct(_fields_set, **dealiased_object) def json(self, **kwargs: typing.Any) -> str: kwargs_with_defaults: typing.Any = { @@ -97,30 +128,66 @@ def dict(self, **kwargs: typing.Any) -> typing.Dict[str, typing.Any]: Override the default dict method to `exclude_unset` by default. This function patches `exclude_unset` to work include fields within non-None default values. """ - _fields_set = self.__fields_set__ - - fields = _get_model_fields(self.__class__) - for name, field in fields.items(): - if name not in _fields_set: - default = _get_field_default(field) - - # If the default values are non-null act like they've been set - # This effectively allows exclude_unset to work like exclude_none where - # the latter passes through intentionally set none values. - if default != None: - _fields_set.add(name) - - kwargs_with_defaults_exclude_unset: typing.Any = { - "by_alias": True, - "exclude_unset": True, - "include": _fields_set, - **kwargs, - } - + # Note: the logic here is multi-plexed given the levers exposed in Pydantic V1 vs V2 + # Pydantic V1's .dict can be extremely slow, so we do not want to call it twice. + # + # We'd ideally do the same for Pydantic V2, but it shells out to a library to serialize models + # that we have less control over, and this is less intrusive than custom serializers for now. if IS_PYDANTIC_V2: - return super().model_dump(**kwargs_with_defaults_exclude_unset) # type: ignore # Pydantic v2 + kwargs_with_defaults_exclude_unset: typing.Any = { + **kwargs, + "by_alias": True, + "exclude_unset": True, + "exclude_none": False, + } + kwargs_with_defaults_exclude_none: typing.Any = { + **kwargs, + "by_alias": True, + "exclude_none": True, + "exclude_unset": False, + } + dict_dump = deep_union_pydantic_dicts( + super().model_dump(**kwargs_with_defaults_exclude_unset), # type: ignore # Pydantic v2 + super().model_dump(**kwargs_with_defaults_exclude_none), # type: ignore # Pydantic v2 + ) + else: - return super().dict(**kwargs_with_defaults_exclude_unset) + _fields_set = self.__fields_set__ + + fields = _get_model_fields(self.__class__) + for name, field in fields.items(): + if name not in _fields_set: + default = _get_field_default(field) + + # If the default values are non-null act like they've been set + # This effectively allows exclude_unset to work like exclude_none where + # the latter passes through intentionally set none values. + if default != None: + _fields_set.add(name) + + kwargs_with_defaults_exclude_unset_include_fields: typing.Any = { + "by_alias": True, + "exclude_unset": True, + "include": _fields_set, + **kwargs, + } + + dict_dump = super().dict(**kwargs_with_defaults_exclude_unset_include_fields) + + return convert_and_respect_annotation_metadata(object_=dict_dump, annotation=self.__class__, direction="write") + + +def deep_union_pydantic_dicts( + source: typing.Dict[str, typing.Any], destination: typing.Dict[str, typing.Any] +) -> typing.Dict[str, typing.Any]: + for key, value in source.items(): + if isinstance(value, dict): + node = destination.setdefault(key, {}) + deep_union_pydantic_dicts(value, node) + else: + destination[key] = value + + return destination if IS_PYDANTIC_V2: @@ -147,11 +214,11 @@ def encode_by_type(o: typing.Any) -> typing.Any: return encoder(o) -def update_forward_refs(model: typing.Type["Model"]) -> None: +def update_forward_refs(model: typing.Type["Model"], **localns: typing.Any) -> None: if IS_PYDANTIC_V2: model.model_rebuild(raise_errors=False) # type: ignore # Pydantic v2 else: - model.update_forward_refs() + model.update_forward_refs(**localns) # Mirrors Pydantic's internal typing diff --git a/src/sayari/core/serialization.py b/src/sayari/core/serialization.py index 36180ac..5605f1b 100644 --- a/src/sayari/core/serialization.py +++ b/src/sayari/core/serialization.py @@ -1,10 +1,13 @@ # This file was auto-generated by Fern from our API Definition. import collections +import inspect import typing import typing_extensions +import pydantic + class FieldMetadata: """ @@ -29,6 +32,7 @@ def convert_and_respect_annotation_metadata( object_: typing.Any, annotation: typing.Any, inner_type: typing.Optional[typing.Any] = None, + direction: typing.Literal["read", "write"], ) -> typing.Any: """ Respect the metadata annotations on a field, such as aliasing. This function effectively @@ -56,44 +60,59 @@ def convert_and_respect_annotation_metadata( inner_type = annotation clean_type = _remove_annotations(inner_type) - if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping): - return _convert_typeddict(object_, clean_type) - + # Pydantic models if ( - # If you're iterating on a string, do not bother to coerce it to a sequence. - (not isinstance(object_, str)) - and ( - ( - ( - typing_extensions.get_origin(clean_type) == typing.List - or typing_extensions.get_origin(clean_type) == list - or clean_type == typing.List + inspect.isclass(clean_type) + and issubclass(clean_type, pydantic.BaseModel) + and isinstance(object_, typing.Mapping) + ): + return _convert_mapping(object_, clean_type, direction) + # TypedDicts + if typing_extensions.is_typeddict(clean_type) and isinstance(object_, typing.Mapping): + return _convert_mapping(object_, clean_type, direction) + + # If you're iterating on a string, do not bother to coerce it to a sequence. + if not isinstance(object_, str): + if ( + typing_extensions.get_origin(clean_type) == typing.Set + or typing_extensions.get_origin(clean_type) == set + or clean_type == typing.Set + ) and isinstance(object_, typing.Set): + inner_type = typing_extensions.get_args(clean_type)[0] + return { + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, ) - and isinstance(object_, typing.List) + for item in object_ + } + elif ( + ( + typing_extensions.get_origin(clean_type) == typing.List + or typing_extensions.get_origin(clean_type) == list + or clean_type == typing.List ) - or ( - ( - typing_extensions.get_origin(clean_type) == typing.Set - or typing_extensions.get_origin(clean_type) == set - or clean_type == typing.Set - ) - and isinstance(object_, typing.Set) + and isinstance(object_, typing.List) + ) or ( + ( + typing_extensions.get_origin(clean_type) == typing.Sequence + or typing_extensions.get_origin(clean_type) == collections.abc.Sequence + or clean_type == typing.Sequence ) - or ( - ( - typing_extensions.get_origin(clean_type) == typing.Sequence - or typing_extensions.get_origin(clean_type) == collections.abc.Sequence - or clean_type == typing.Sequence + and isinstance(object_, typing.Sequence) + ): + inner_type = typing_extensions.get_args(clean_type)[0] + return [ + convert_and_respect_annotation_metadata( + object_=item, + annotation=annotation, + inner_type=inner_type, + direction=direction, ) - and isinstance(object_, typing.Sequence) - ) - ) - ): - inner_type = typing_extensions.get_args(clean_type)[0] - return [ - convert_and_respect_annotation_metadata(object_=item, annotation=annotation, inner_type=inner_type) - for item in object_ - ] + for item in object_ + ] if typing_extensions.get_origin(clean_type) == typing.Union: # We should be able to ~relatively~ safely try to convert keys against all @@ -101,7 +120,12 @@ def convert_and_respect_annotation_metadata( # of the same name to a different name from another member # Or if another member aliases a field of the same name that another member does not. for member in typing_extensions.get_args(clean_type): - object_ = convert_and_respect_annotation_metadata(object_=object_, annotation=annotation, inner_type=member) + object_ = convert_and_respect_annotation_metadata( + object_=object_, + annotation=annotation, + inner_type=member, + direction=direction, + ) return object_ annotated_type = _get_annotation(annotation) @@ -113,16 +137,34 @@ def convert_and_respect_annotation_metadata( return object_ -def _convert_typeddict(object_: typing.Mapping[str, object], expected_type: typing.Any) -> typing.Mapping[str, object]: +def _convert_mapping( + object_: typing.Mapping[str, object], + expected_type: typing.Any, + direction: typing.Literal["read", "write"], +) -> typing.Mapping[str, object]: converted_object: typing.Dict[str, object] = {} annotations = typing_extensions.get_type_hints(expected_type, include_extras=True) + aliases_to_field_names = _get_alias_to_field_name(annotations) for key, value in object_.items(): - type_ = annotations.get(key) + if direction == "read" and key in aliases_to_field_names: + dealiased_key = aliases_to_field_names.get(key) + if dealiased_key is not None: + type_ = annotations.get(dealiased_key) + else: + type_ = annotations.get(key) + # Note you can't get the annotation by the field name if you're in read mode, so you must check the aliases map + # + # So this is effectively saying if we're in write mode, and we don't have a type, or if we're in read mode and we don't have an alias + # then we can just pass the value through as is if type_ is None: converted_object[key] = value + elif direction == "read" and key not in aliases_to_field_names: + converted_object[key] = convert_and_respect_annotation_metadata( + object_=value, annotation=type_, direction=direction + ) else: - converted_object[_alias_key(key, type_)] = convert_and_respect_annotation_metadata( - object_=value, annotation=type_ + converted_object[_alias_key(key, type_, direction, aliases_to_field_names)] = ( + convert_and_respect_annotation_metadata(object_=value, annotation=type_, direction=direction) ) return converted_object @@ -156,7 +198,39 @@ def _remove_annotations(type_: typing.Any) -> typing.Any: return type_ -def _alias_key(key: str, type_: typing.Any) -> str: +def get_alias_to_field_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = typing_extensions.get_type_hints(type_, include_extras=True) + return _get_alias_to_field_name(annotations) + + +def get_field_to_alias_mapping(type_: typing.Any) -> typing.Dict[str, str]: + annotations = typing_extensions.get_type_hints(type_, include_extras=True) + return _get_field_to_alias_name(annotations) + + +def _get_alias_to_field_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[maybe_alias] = field + return aliases + + +def _get_field_to_alias_name( + field_to_hint: typing.Dict[str, typing.Any], +) -> typing.Dict[str, str]: + aliases = {} + for field, hint in field_to_hint.items(): + maybe_alias = _get_alias_from_type(hint) + if maybe_alias is not None: + aliases[field] = maybe_alias + return aliases + + +def _get_alias_from_type(type_: typing.Any) -> typing.Optional[str]: maybe_annotated_type = _get_annotation(type_) if maybe_annotated_type is not None: @@ -166,5 +240,15 @@ def _alias_key(key: str, type_: typing.Any) -> str: for annotation in annotations: if isinstance(annotation, FieldMetadata) and annotation.alias is not None: return annotation.alias + return None + - return key +def _alias_key( + key: str, + type_: typing.Any, + direction: typing.Literal["read", "write"], + aliases_to_field_names: typing.Dict[str, str], +) -> str: + if direction == "read": + return aliases_to_field_names.get(key, key) + return _get_alias_from_type(type_=type_) or key diff --git a/src/sayari/entity/types/entity_summary_response.py b/src/sayari/entity/types/entity_summary_response.py index 2a9ba8c..6543a68 100644 --- a/src/sayari/entity/types/entity_summary_response.py +++ b/src/sayari/entity/types/entity_summary_response.py @@ -1,9 +1,13 @@ # This file was auto-generated by Fern from our API Definition. +from __future__ import annotations from ...shared_types.types.entity_details import EntityDetails +from ...shared_types.types.entity_relationships import EntityRelationships +from ...shared_types.types.relationship_data import RelationshipData from ...core.pydantic_utilities import IS_PYDANTIC_V2 import typing import pydantic +from ...core.pydantic_utilities import update_forward_refs class EntitySummaryResponse(EntityDetails): @@ -1420,3 +1424,8 @@ class Config: frozen = True smart_union = True extra = pydantic.Extra.allow + + +update_forward_refs(EntityDetails, EntitySummaryResponse=EntitySummaryResponse) +update_forward_refs(EntityRelationships, EntitySummaryResponse=EntitySummaryResponse) +update_forward_refs(RelationshipData, EntitySummaryResponse=EntitySummaryResponse) diff --git a/src/sayari/entity/types/get_entity_response.py b/src/sayari/entity/types/get_entity_response.py index e60d401..085dd8f 100644 --- a/src/sayari/entity/types/get_entity_response.py +++ b/src/sayari/entity/types/get_entity_response.py @@ -1,9 +1,13 @@ # This file was auto-generated by Fern from our API Definition. +from __future__ import annotations from ...shared_types.types.entity_details import EntityDetails +from ...shared_types.types.entity_relationships import EntityRelationships +from ...shared_types.types.relationship_data import RelationshipData from ...core.pydantic_utilities import IS_PYDANTIC_V2 import typing import pydantic +from ...core.pydantic_utilities import update_forward_refs class GetEntityResponse(EntityDetails): @@ -601,3 +605,8 @@ class Config: frozen = True smart_union = True extra = pydantic.Extra.allow + + +update_forward_refs(EntityDetails, GetEntityResponse=GetEntityResponse) +update_forward_refs(EntityRelationships, GetEntityResponse=GetEntityResponse) +update_forward_refs(RelationshipData, GetEntityResponse=GetEntityResponse) diff --git a/src/sayari/generated_types/types/risk_intelligence_properties.py b/src/sayari/generated_types/types/risk_intelligence_properties.py index 0812c0c..48b1634 100644 --- a/src/sayari/generated_types/types/risk_intelligence_properties.py +++ b/src/sayari/generated_types/types/risk_intelligence_properties.py @@ -3,6 +3,8 @@ from ...core.pydantic_utilities import UniversalBaseModel import typing import pydantic +import typing_extensions +from ...core.serialization import FieldMetadata from .tag import Tag from ...core.pydantic_utilities import IS_PYDANTIC_V2 @@ -23,7 +25,7 @@ class RiskIntelligenceProperties(UniversalBaseModel): start date """ - list_: typing.Optional[str] = pydantic.Field(alias="list", default=None) + list_: typing_extensions.Annotated[typing.Optional[str], FieldMetadata(alias="list")] = pydantic.Field(default=None) """ Official list where the entity's risk information or enforcement action is recorded """ diff --git a/src/sayari/info/types/usage_response.py b/src/sayari/info/types/usage_response.py index 259fdc5..97b6c5b 100644 --- a/src/sayari/info/types/usage_response.py +++ b/src/sayari/info/types/usage_response.py @@ -3,6 +3,8 @@ from ...core.pydantic_utilities import UniversalBaseModel from .usage_info import UsageInfo import pydantic +import typing_extensions +from ...core.serialization import FieldMetadata from ...core.pydantic_utilities import IS_PYDANTIC_V2 import typing @@ -13,7 +15,7 @@ class UsageResponse(UniversalBaseModel): Usage information for each endpoint """ - from_: str = pydantic.Field(alias="from") + from_: typing_extensions.Annotated[str, FieldMetadata(alias="from")] = pydantic.Field() """ The start date of the returned usage information. """ diff --git a/src/sayari/metadata/types/user_info.py b/src/sayari/metadata/types/user_info.py index 0cc270c..05bfb87 100644 --- a/src/sayari/metadata/types/user_info.py +++ b/src/sayari/metadata/types/user_info.py @@ -2,7 +2,9 @@ from ...core.pydantic_utilities import UniversalBaseModel import pydantic +import typing_extensions import typing +from ...core.serialization import FieldMetadata from ...core.pydantic_utilities import IS_PYDANTIC_V2 @@ -12,7 +14,9 @@ class UserInfo(UniversalBaseModel): Currently logged in user ID """ - group_display_names: typing.Optional[str] = pydantic.Field(alias="groupDisplayNames", default=None) + group_display_names: typing_extensions.Annotated[typing.Optional[str], FieldMetadata(alias="groupDisplayNames")] = ( + pydantic.Field(default=None) + ) """ Name of the sayari organization tied to credentials """ diff --git a/src/sayari/project/client.py b/src/sayari/project/client.py index 3a7d737..01a3201 100644 --- a/src/sayari/project/client.py +++ b/src/sayari/project/client.py @@ -5,6 +5,7 @@ from .types.create_project_request import CreateProjectRequest from ..core.request_options import RequestOptions from .types.create_project_response import CreateProjectResponse +from ..core.serialization import convert_and_respect_annotation_metadata from ..core.pydantic_utilities import parse_obj_as from ..shared_errors.errors.bad_request import BadRequest from ..shared_errors.types.bad_request_response import BadRequestResponse @@ -76,7 +77,9 @@ def create_project( _response = self._client_wrapper.httpx_client.request( "v1/projects", method="POST", - json=request, + json=convert_and_respect_annotation_metadata( + object_=request, annotation=CreateProjectRequest, direction="write" + ), request_options=request_options, omit=OMIT, ) @@ -386,7 +389,9 @@ def get_project_entities( "shipped_hs_codes": shipped_hs_codes, "translation": translation, "sort": sort, - "filters": jsonable_encoder(filters), + "filters": convert_and_respect_annotation_metadata( + object_=filters, annotation=ProjectEntitiesFilter, direction="write" + ), "aggregations": aggregations, }, headers={ @@ -624,7 +629,9 @@ async def main() -> None: _response = await self._client_wrapper.httpx_client.request( "v1/projects", method="POST", - json=request, + json=convert_and_respect_annotation_metadata( + object_=request, annotation=CreateProjectRequest, direction="write" + ), request_options=request_options, omit=OMIT, ) @@ -950,7 +957,9 @@ async def main() -> None: "shipped_hs_codes": shipped_hs_codes, "translation": translation, "sort": sort, - "filters": jsonable_encoder(filters), + "filters": convert_and_respect_annotation_metadata( + object_=filters, annotation=ProjectEntitiesFilter, direction="write" + ), "aggregations": aggregations, }, headers={ diff --git a/src/sayari/project/types/project_entities_filter.py b/src/sayari/project/types/project_entities_filter.py index 8fc03ca..f980951 100644 --- a/src/sayari/project/types/project_entities_filter.py +++ b/src/sayari/project/types/project_entities_filter.py @@ -6,6 +6,8 @@ import pydantic from .upstream_tiers import UpstreamTiers from ...generated_types.types.country import Country +import typing_extensions +from ...core.serialization import FieldMetadata from ...generated_types.types.company_status import CompanyStatus from ...core.pydantic_utilities import IS_PYDANTIC_V2 @@ -46,32 +48,44 @@ class ProjectEntitiesFilter(UniversalBaseModel): Filter by HS code, HS code description, or business description. """ - label_fuzzy: typing.Optional[typing.List[str]] = pydantic.Field(alias="label.fuzzy", default=None) + label_fuzzy: typing_extensions.Annotated[typing.Optional[typing.List[str]], FieldMetadata(alias="label.fuzzy")] = ( + pydantic.Field(default=None) + ) """ Filter by entity label with fuzzy matching. """ - city_fuzzy: typing.Optional[typing.List[str]] = pydantic.Field(alias="city.fuzzy", default=None) + city_fuzzy: typing_extensions.Annotated[typing.Optional[typing.List[str]], FieldMetadata(alias="city.fuzzy")] = ( + pydantic.Field(default=None) + ) """ Filter by entity city with fuzzy matching. """ - state_fuzzy: typing.Optional[typing.List[str]] = pydantic.Field(alias="state.fuzzy", default=None) + state_fuzzy: typing_extensions.Annotated[typing.Optional[typing.List[str]], FieldMetadata(alias="state.fuzzy")] = ( + pydantic.Field(default=None) + ) """ Filter by entity address state with fuzzy matching. """ - identifier_fuzzy: typing.Optional[typing.List[str]] = pydantic.Field(alias="identifier.fuzzy", default=None) + identifier_fuzzy: typing_extensions.Annotated[ + typing.Optional[typing.List[str]], FieldMetadata(alias="identifier.fuzzy") + ] = pydantic.Field(default=None) """ Filter by entity identifier attributes with fuzzy matching. """ - source_exact: typing.Optional[typing.List[str]] = pydantic.Field(alias="source.exact", default=None) + source_exact: typing_extensions.Annotated[ + typing.Optional[typing.List[str]], FieldMetadata(alias="source.exact") + ] = pydantic.Field(default=None) """ Filter by entity source ID. """ - status_exact: typing.Optional[typing.List[CompanyStatus]] = pydantic.Field(alias="status.exact", default=None) + status_exact: typing_extensions.Annotated[ + typing.Optional[typing.List[CompanyStatus]], FieldMetadata(alias="status.exact") + ] = pydantic.Field(default=None) """ Filter by entity [company status](/sayari-library/ontology/enumerated-types#company-status). """ @@ -81,7 +95,9 @@ class ProjectEntitiesFilter(UniversalBaseModel): Filter by a geographical bounding box. The value is a pipe-delimited set of four values representing the top, left, bottom, and right sides of the bounding box, in that order. The pipes should be URL-encoded as `%7C`. The top coordinate must greater than the bottom coordinate, and the left coordinate must be less than the right coordinate. A sample is `55.680357237879136|-71.53607290158526|41.10876347746233|-40.963927098414736` """ - custom_field_name: typing.Optional[typing.List[str]] = pydantic.Field(alias="custom_{field name}", default=None) + custom_field_name: typing_extensions.Annotated[ + typing.Optional[typing.List[str]], FieldMetadata(alias="custom_{field name}") + ] = pydantic.Field(default=None) """ This property is in beta and is subject to change. It is provided for early access and testing purposes only. custom user key/value pairs (key must be prefixed with "custom\_" and value must be "string" type) """ diff --git a/src/sayari/resolution/client.py b/src/sayari/resolution/client.py index 4d199b8..f516c91 100644 --- a/src/sayari/resolution/client.py +++ b/src/sayari/resolution/client.py @@ -24,6 +24,7 @@ from json.decoder import JSONDecodeError from ..core.api_error import ApiError from .types.resolution_body import ResolutionBody +from ..core.serialization import convert_and_respect_annotation_metadata from .types.resolution_persisted_response import ResolutionPersistedResponse from ..core.jsonable_encoder import jsonable_encoder from ..core.client_wrapper import AsyncClientWrapper @@ -268,7 +269,7 @@ def resolution_post( "limit": limit, "offset": offset, }, - json=request, + json=convert_and_respect_annotation_metadata(object_=request, annotation=ResolutionBody, direction="write"), request_options=request_options, omit=OMIT, ) @@ -402,7 +403,7 @@ def resolution_persisted( "limit": limit, "offset": offset, }, - json=request, + json=convert_and_respect_annotation_metadata(object_=request, annotation=ResolutionBody, direction="write"), request_options=request_options, omit=OMIT, ) @@ -733,7 +734,7 @@ async def main() -> None: "limit": limit, "offset": offset, }, - json=request, + json=convert_and_respect_annotation_metadata(object_=request, annotation=ResolutionBody, direction="write"), request_options=request_options, omit=OMIT, ) @@ -875,7 +876,7 @@ async def main() -> None: "limit": limit, "offset": offset, }, - json=request, + json=convert_and_respect_annotation_metadata(object_=request, annotation=ResolutionBody, direction="write"), request_options=request_options, omit=OMIT, ) diff --git a/src/sayari/resolution/types/resolution_persisted_response_fields.py b/src/sayari/resolution/types/resolution_persisted_response_fields.py index 0ef5d14..1bdc1c9 100644 --- a/src/sayari/resolution/types/resolution_persisted_response_fields.py +++ b/src/sayari/resolution/types/resolution_persisted_response_fields.py @@ -1,13 +1,17 @@ # This file was auto-generated by Fern from our API Definition. from .resolution_response_fields import ResolutionResponseFields +import typing_extensions import typing +from ...core.serialization import FieldMetadata import pydantic from ...core.pydantic_utilities import IS_PYDANTIC_V2 class ResolutionPersistedResponseFields(ResolutionResponseFields): - custom_field_name: typing.Optional[str] = pydantic.Field(alias="custom_{field name}", default=None) + custom_field_name: typing_extensions.Annotated[typing.Optional[str], FieldMetadata(alias="custom_{field name}")] = ( + pydantic.Field(default=None) + ) """ This property is in beta and is subject to change. It is provided for early access and testing purposes only. custom user key/value pairs (key must be prefixed with "custom\_" and value must be "string" type) """ diff --git a/src/sayari/resource/client.py b/src/sayari/resource/client.py index 6ff8c72..ac3f5a7 100644 --- a/src/sayari/resource/client.py +++ b/src/sayari/resource/client.py @@ -5,6 +5,7 @@ from .types.save_entity_request import SaveEntityRequest from ..core.request_options import RequestOptions from .types.save_entity_response import SaveEntityResponse +from ..core.serialization import convert_and_respect_annotation_metadata from ..core.pydantic_utilities import parse_obj_as from ..shared_errors.errors.bad_request import BadRequest from ..shared_errors.types.bad_request_response import BadRequestResponse @@ -71,7 +72,9 @@ def save_entity( _response = self._client_wrapper.httpx_client.request( "v1/resource/entity", method="POST", - json=request, + json=convert_and_respect_annotation_metadata( + object_=request, annotation=SaveEntityRequest, direction="write" + ), request_options=request_options, omit=OMIT, ) @@ -311,7 +314,9 @@ async def main() -> None: _response = await self._client_wrapper.httpx_client.request( "v1/resource/entity", method="POST", - json=request, + json=convert_and_respect_annotation_metadata( + object_=request, annotation=SaveEntityRequest, direction="write" + ), request_options=request_options, omit=OMIT, ) diff --git a/src/sayari/search/client.py b/src/sayari/search/client.py index f3226eb..c28d22f 100644 --- a/src/sayari/search/client.py +++ b/src/sayari/search/client.py @@ -6,6 +6,7 @@ from .types.filter_list import FilterList from ..core.request_options import RequestOptions from .types.entity_search_response import EntitySearchResponse +from ..core.serialization import convert_and_respect_annotation_metadata from ..core.pydantic_utilities import parse_obj_as from ..shared_errors.errors.bad_request import BadRequest from ..shared_errors.types.bad_request_response import BadRequestResponse @@ -104,7 +105,9 @@ def search_entity( json={ "q": q, "fields": fields, - "filter": filter, + "filter": convert_and_respect_annotation_metadata( + object_=filter, annotation=FilterList, direction="write" + ), "facets": facets, "geo_facets": geo_facets, "advanced": advanced, @@ -400,7 +403,9 @@ def search_record( json={ "q": q, "fields": fields, - "filter": filter, + "filter": convert_and_respect_annotation_metadata( + object_=filter, annotation=FilterList, direction="write" + ), "facets": facets, "advanced": advanced, }, @@ -707,7 +712,9 @@ async def main() -> None: json={ "q": q, "fields": fields, - "filter": filter, + "filter": convert_and_respect_annotation_metadata( + object_=filter, annotation=FilterList, direction="write" + ), "facets": facets, "geo_facets": geo_facets, "advanced": advanced, @@ -1019,7 +1026,9 @@ async def main() -> None: json={ "q": q, "fields": fields, - "filter": filter, + "filter": convert_and_respect_annotation_metadata( + object_=filter, annotation=FilterList, direction="write" + ), "facets": facets, "advanced": advanced, }, diff --git a/src/sayari/search/types/entity_search_response.py b/src/sayari/search/types/entity_search_response.py index c730ffd..774fba2 100644 --- a/src/sayari/search/types/entity_search_response.py +++ b/src/sayari/search/types/entity_search_response.py @@ -1,10 +1,15 @@ # This file was auto-generated by Fern from our API Definition. +from __future__ import annotations from ...base_types.types.paginated_response import PaginatedResponse +from ...shared_types.types.entity_details import EntityDetails +from ...shared_types.types.entity_relationships import EntityRelationships +from ...shared_types.types.relationship_data import RelationshipData import typing from .search_results import SearchResults from ...core.pydantic_utilities import IS_PYDANTIC_V2 import pydantic +from ...core.pydantic_utilities import update_forward_refs class EntitySearchResponse(PaginatedResponse): @@ -194,3 +199,8 @@ class Config: frozen = True smart_union = True extra = pydantic.Extra.allow + + +update_forward_refs(EntityDetails, EntitySearchResponse=EntitySearchResponse) +update_forward_refs(EntityRelationships, EntitySearchResponse=EntitySearchResponse) +update_forward_refs(RelationshipData, EntitySearchResponse=EntitySearchResponse) diff --git a/src/sayari/search/types/search_results.py b/src/sayari/search/types/search_results.py index 03887f5..3e9b165 100644 --- a/src/sayari/search/types/search_results.py +++ b/src/sayari/search/types/search_results.py @@ -1,11 +1,15 @@ # This file was auto-generated by Fern from our API Definition. +from __future__ import annotations from ...shared_types.types.entity_details import EntityDetails +from ...shared_types.types.entity_relationships import EntityRelationships +from ...shared_types.types.relationship_data import RelationshipData import typing from .coordinates import Coordinates from ...shared_types.types.entity_matches import EntityMatches from ...core.pydantic_utilities import IS_PYDANTIC_V2 import pydantic +from ...core.pydantic_utilities import update_forward_refs class SearchResults(EntityDetails): @@ -20,3 +24,8 @@ class Config: frozen = True smart_union = True extra = pydantic.Extra.allow + + +update_forward_refs(EntityDetails, SearchResults=SearchResults) +update_forward_refs(EntityRelationships, SearchResults=SearchResults) +update_forward_refs(RelationshipData, SearchResults=SearchResults) diff --git a/src/sayari/shared_types/types/entity_details.py b/src/sayari/shared_types/types/entity_details.py index 377de32..9653527 100644 --- a/src/sayari/shared_types/types/entity_details.py +++ b/src/sayari/shared_types/types/entity_details.py @@ -60,5 +60,8 @@ class Config: from .entity_relationships import EntityRelationships # noqa: E402 +from .relationship_data import RelationshipData # noqa: E402 +update_forward_refs(EntityRelationships, EntityDetails=EntityDetails) +update_forward_refs(RelationshipData, EntityDetails=EntityDetails) update_forward_refs(EntityDetails) diff --git a/src/sayari/shared_types/types/entity_relationships.py b/src/sayari/shared_types/types/entity_relationships.py index d5b9e16..0df6aec 100644 --- a/src/sayari/shared_types/types/entity_relationships.py +++ b/src/sayari/shared_types/types/entity_relationships.py @@ -26,6 +26,9 @@ class Config: extra = pydantic.Extra.allow +from .entity_details import EntityDetails # noqa: E402 from .relationship_data import RelationshipData # noqa: E402 +update_forward_refs(EntityDetails, EntityRelationships=EntityRelationships) +update_forward_refs(RelationshipData, EntityRelationships=EntityRelationships) update_forward_refs(EntityRelationships) diff --git a/src/sayari/shared_types/types/relationship_data.py b/src/sayari/shared_types/types/relationship_data.py index b81f911..d299b68 100644 --- a/src/sayari/shared_types/types/relationship_data.py +++ b/src/sayari/shared_types/types/relationship_data.py @@ -35,5 +35,8 @@ class Config: from .entity_details import EntityDetails # noqa: E402 +from .entity_relationships import EntityRelationships # noqa: E402 +update_forward_refs(EntityDetails, RelationshipData=RelationshipData) +update_forward_refs(EntityRelationships, RelationshipData=RelationshipData) update_forward_refs(RelationshipData) diff --git a/src/sayari/supply_chain/client.py b/src/sayari/supply_chain/client.py index 4cd131a..80e6330 100644 --- a/src/sayari/supply_chain/client.py +++ b/src/sayari/supply_chain/client.py @@ -117,14 +117,14 @@ def upstream_trade_traversal( f"v1/supply_chain/upstream/{jsonable_encoder(id)}", method="GET", params={ - "risk": jsonable_encoder(risk), - "-risk": jsonable_encoder(not_risk), - "countries": jsonable_encoder(countries), - "-countries": jsonable_encoder(not_countries), - "product": jsonable_encoder(product), - "-product": jsonable_encoder(not_product), - "component": jsonable_encoder(component), - "-component": jsonable_encoder(not_component), + "risk": risk, + "-risk": not_risk, + "countries": countries, + "-countries": not_countries, + "product": product, + "-product": not_product, + "component": component, + "-component": not_component, "min_date": min_date, "max_date": max_date, "max_depth": max_depth, @@ -307,14 +307,14 @@ async def main() -> None: f"v1/supply_chain/upstream/{jsonable_encoder(id)}", method="GET", params={ - "risk": jsonable_encoder(risk), - "-risk": jsonable_encoder(not_risk), - "countries": jsonable_encoder(countries), - "-countries": jsonable_encoder(not_countries), - "product": jsonable_encoder(product), - "-product": jsonable_encoder(not_product), - "component": jsonable_encoder(component), - "-component": jsonable_encoder(not_component), + "risk": risk, + "-risk": not_risk, + "countries": countries, + "-countries": not_countries, + "product": product, + "-product": not_product, + "component": component, + "-component": not_component, "min_date": min_date, "max_date": max_date, "max_depth": max_depth, diff --git a/src/sayari/trade/client.py b/src/sayari/trade/client.py index 539eece..4f5f897 100644 --- a/src/sayari/trade/client.py +++ b/src/sayari/trade/client.py @@ -5,6 +5,7 @@ from .types.trade_filter_list import TradeFilterList from ..core.request_options import RequestOptions from .types.shipment_search_response import ShipmentSearchResponse +from ..core.serialization import convert_and_respect_annotation_metadata from ..core.pydantic_utilities import parse_obj_as from ..shared_errors.errors.bad_request import BadRequest from ..shared_errors.types.bad_request_response import BadRequestResponse @@ -89,7 +90,9 @@ def search_shipments( }, json={ "q": q, - "filter": filter, + "filter": convert_and_respect_annotation_metadata( + object_=filter, annotation=TradeFilterList, direction="write" + ), "facets": facets, }, request_options=request_options, @@ -218,7 +221,9 @@ def search_suppliers( }, json={ "q": q, - "filter": filter, + "filter": convert_and_respect_annotation_metadata( + object_=filter, annotation=TradeFilterList, direction="write" + ), "facets": facets, }, request_options=request_options, @@ -347,7 +352,9 @@ def search_buyers( }, json={ "q": q, - "filter": filter, + "filter": convert_and_respect_annotation_metadata( + object_=filter, annotation=TradeFilterList, direction="write" + ), "facets": facets, }, request_options=request_options, @@ -489,7 +496,9 @@ async def main() -> None: }, json={ "q": q, - "filter": filter, + "filter": convert_and_respect_annotation_metadata( + object_=filter, annotation=TradeFilterList, direction="write" + ), "facets": facets, }, request_options=request_options, @@ -626,7 +635,9 @@ async def main() -> None: }, json={ "q": q, - "filter": filter, + "filter": convert_and_respect_annotation_metadata( + object_=filter, annotation=TradeFilterList, direction="write" + ), "facets": facets, }, request_options=request_options, @@ -763,7 +774,9 @@ async def main() -> None: }, json={ "q": q, - "filter": filter, + "filter": convert_and_respect_annotation_metadata( + object_=filter, annotation=TradeFilterList, direction="write" + ), "facets": facets, }, request_options=request_options, diff --git a/src/sayari/trade/types/buyer_search_response.py b/src/sayari/trade/types/buyer_search_response.py index 57f965e..97fa17f 100644 --- a/src/sayari/trade/types/buyer_search_response.py +++ b/src/sayari/trade/types/buyer_search_response.py @@ -1,10 +1,15 @@ # This file was auto-generated by Fern from our API Definition. +from __future__ import annotations from ...base_types.types.paginated_response import PaginatedResponse +from ...shared_types.types.entity_details import EntityDetails +from ...shared_types.types.entity_relationships import EntityRelationships +from ...shared_types.types.relationship_data import RelationshipData import typing from .supplier_or_buyer import SupplierOrBuyer from ...core.pydantic_utilities import IS_PYDANTIC_V2 import pydantic +from ...core.pydantic_utilities import update_forward_refs class BuyerSearchResponse(PaginatedResponse): @@ -167,3 +172,8 @@ class Config: frozen = True smart_union = True extra = pydantic.Extra.allow + + +update_forward_refs(EntityDetails, BuyerSearchResponse=BuyerSearchResponse) +update_forward_refs(EntityRelationships, BuyerSearchResponse=BuyerSearchResponse) +update_forward_refs(RelationshipData, BuyerSearchResponse=BuyerSearchResponse) diff --git a/src/sayari/trade/types/supplier_metadata.py b/src/sayari/trade/types/supplier_metadata.py index 0642024..83c84db 100644 --- a/src/sayari/trade/types/supplier_metadata.py +++ b/src/sayari/trade/types/supplier_metadata.py @@ -1,16 +1,20 @@ # This file was auto-generated by Fern from our API Definition. from ...core.pydantic_utilities import UniversalBaseModel +import typing_extensions import typing -import pydantic +from ...core.serialization import FieldMetadata from .hs_code import HsCode from ...core.pydantic_utilities import IS_PYDANTIC_V2 +import pydantic class SupplierMetadata(UniversalBaseModel): - latest_shipment_date: typing.Optional[str] = pydantic.Field(alias="latestShipmentDate", default=None) + latest_shipment_date: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="latestShipmentDate") + ] = None shipments: int - hs_codes: typing.List[HsCode] = pydantic.Field(alias="hsCodes") + hs_codes: typing_extensions.Annotated[typing.List[HsCode], FieldMetadata(alias="hsCodes")] if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/src/sayari/trade/types/supplier_or_buyer.py b/src/sayari/trade/types/supplier_or_buyer.py index b0ddf57..eae8a11 100644 --- a/src/sayari/trade/types/supplier_or_buyer.py +++ b/src/sayari/trade/types/supplier_or_buyer.py @@ -1,10 +1,14 @@ # This file was auto-generated by Fern from our API Definition. +from __future__ import annotations from ...shared_types.types.entity_details import EntityDetails +from ...shared_types.types.entity_relationships import EntityRelationships +from ...shared_types.types.relationship_data import RelationshipData from .supplier_metadata import SupplierMetadata from ...core.pydantic_utilities import IS_PYDANTIC_V2 import typing import pydantic +from ...core.pydantic_utilities import update_forward_refs class SupplierOrBuyer(EntityDetails): @@ -18,3 +22,8 @@ class Config: frozen = True smart_union = True extra = pydantic.Extra.allow + + +update_forward_refs(EntityDetails, SupplierOrBuyer=SupplierOrBuyer) +update_forward_refs(EntityRelationships, SupplierOrBuyer=SupplierOrBuyer) +update_forward_refs(RelationshipData, SupplierOrBuyer=SupplierOrBuyer) diff --git a/src/sayari/trade/types/supplier_search_response.py b/src/sayari/trade/types/supplier_search_response.py index c80dbef..5acb848 100644 --- a/src/sayari/trade/types/supplier_search_response.py +++ b/src/sayari/trade/types/supplier_search_response.py @@ -1,10 +1,15 @@ # This file was auto-generated by Fern from our API Definition. +from __future__ import annotations from ...base_types.types.paginated_response import PaginatedResponse +from ...shared_types.types.entity_details import EntityDetails +from ...shared_types.types.entity_relationships import EntityRelationships +from ...shared_types.types.relationship_data import RelationshipData import typing from .supplier_or_buyer import SupplierOrBuyer from ...core.pydantic_utilities import IS_PYDANTIC_V2 import pydantic +from ...core.pydantic_utilities import update_forward_refs class SupplierSearchResponse(PaginatedResponse): @@ -112,3 +117,8 @@ class Config: frozen = True smart_union = True extra = pydantic.Extra.allow + + +update_forward_refs(EntityDetails, SupplierSearchResponse=SupplierSearchResponse) +update_forward_refs(EntityRelationships, SupplierSearchResponse=SupplierSearchResponse) +update_forward_refs(RelationshipData, SupplierSearchResponse=SupplierSearchResponse) diff --git a/src/sayari/traversal/types/shortest_path_data.py b/src/sayari/traversal/types/shortest_path_data.py index c424ab7..cc2076b 100644 --- a/src/sayari/traversal/types/shortest_path_data.py +++ b/src/sayari/traversal/types/shortest_path_data.py @@ -1,11 +1,15 @@ # This file was auto-generated by Fern from our API Definition. +from __future__ import annotations from ...core.pydantic_utilities import UniversalBaseModel from ...shared_types.types.entity_details import EntityDetails +from ...shared_types.types.entity_relationships import EntityRelationships +from ...shared_types.types.relationship_data import RelationshipData import typing from .traversal_path import TraversalPath from ...core.pydantic_utilities import IS_PYDANTIC_V2 import pydantic +from ...core.pydantic_utilities import update_forward_refs class ShortestPathData(UniversalBaseModel): @@ -21,3 +25,8 @@ class Config: frozen = True smart_union = True extra = pydantic.Extra.allow + + +update_forward_refs(EntityDetails, ShortestPathData=ShortestPathData) +update_forward_refs(EntityRelationships, ShortestPathData=ShortestPathData) +update_forward_refs(RelationshipData, ShortestPathData=ShortestPathData) diff --git a/src/sayari/traversal/types/shortest_path_response.py b/src/sayari/traversal/types/shortest_path_response.py index 44fb22b..2cad6d3 100644 --- a/src/sayari/traversal/types/shortest_path_response.py +++ b/src/sayari/traversal/types/shortest_path_response.py @@ -1,10 +1,15 @@ # This file was auto-generated by Fern from our API Definition. +from __future__ import annotations from ...core.pydantic_utilities import UniversalBaseModel +from ...shared_types.types.entity_details import EntityDetails +from ...shared_types.types.entity_relationships import EntityRelationships +from ...shared_types.types.relationship_data import RelationshipData import typing from .shortest_path_data import ShortestPathData from ...core.pydantic_utilities import IS_PYDANTIC_V2 import pydantic +from ...core.pydantic_utilities import update_forward_refs class ShortestPathResponse(UniversalBaseModel): @@ -438,3 +443,8 @@ class Config: frozen = True smart_union = True extra = pydantic.Extra.allow + + +update_forward_refs(EntityDetails, ShortestPathResponse=ShortestPathResponse) +update_forward_refs(EntityRelationships, ShortestPathResponse=ShortestPathResponse) +update_forward_refs(RelationshipData, ShortestPathResponse=ShortestPathResponse) diff --git a/src/sayari/traversal/types/traversal_data.py b/src/sayari/traversal/types/traversal_data.py index 58b7644..8b7f798 100644 --- a/src/sayari/traversal/types/traversal_data.py +++ b/src/sayari/traversal/types/traversal_data.py @@ -1,11 +1,15 @@ # This file was auto-generated by Fern from our API Definition. +from __future__ import annotations from ...core.pydantic_utilities import UniversalBaseModel from ...shared_types.types.entity_details import EntityDetails +from ...shared_types.types.entity_relationships import EntityRelationships +from ...shared_types.types.relationship_data import RelationshipData import typing from .traversal_path import TraversalPath from ...core.pydantic_utilities import IS_PYDANTIC_V2 import pydantic +from ...core.pydantic_utilities import update_forward_refs class TraversalData(UniversalBaseModel): @@ -21,3 +25,8 @@ class Config: frozen = True smart_union = True extra = pydantic.Extra.allow + + +update_forward_refs(EntityDetails, TraversalData=TraversalData) +update_forward_refs(EntityRelationships, TraversalData=TraversalData) +update_forward_refs(RelationshipData, TraversalData=TraversalData) diff --git a/src/sayari/traversal/types/traversal_path.py b/src/sayari/traversal/types/traversal_path.py index d8d57f0..5db9634 100644 --- a/src/sayari/traversal/types/traversal_path.py +++ b/src/sayari/traversal/types/traversal_path.py @@ -1,12 +1,16 @@ # This file was auto-generated by Fern from our API Definition. +from __future__ import annotations from ...core.pydantic_utilities import UniversalBaseModel from ...shared_types.types.entity_details import EntityDetails +from ...shared_types.types.entity_relationships import EntityRelationships +from ...shared_types.types.relationship_data import RelationshipData import typing from ...generated_types.types.relationships import Relationships from .traversal_relationship_data import TraversalRelationshipData from ...core.pydantic_utilities import IS_PYDANTIC_V2 import pydantic +from ...core.pydantic_utilities import update_forward_refs class TraversalPath(UniversalBaseModel): @@ -22,3 +26,8 @@ class Config: frozen = True smart_union = True extra = pydantic.Extra.allow + + +update_forward_refs(EntityDetails, TraversalPath=TraversalPath) +update_forward_refs(EntityRelationships, TraversalPath=TraversalPath) +update_forward_refs(RelationshipData, TraversalPath=TraversalPath) diff --git a/src/sayari/traversal/types/traversal_response.py b/src/sayari/traversal/types/traversal_response.py index d9b11f8..aafc135 100644 --- a/src/sayari/traversal/types/traversal_response.py +++ b/src/sayari/traversal/types/traversal_response.py @@ -1,12 +1,17 @@ # This file was auto-generated by Fern from our API Definition. +from __future__ import annotations from ...core.pydantic_utilities import UniversalBaseModel +from ...shared_types.types.entity_details import EntityDetails +from ...shared_types.types.entity_relationships import EntityRelationships +from ...shared_types.types.relationship_data import RelationshipData import typing from ...generated_types.types.relationships import Relationships from ...generated_types.types.country import Country from .traversal_data import TraversalData from ...core.pydantic_utilities import IS_PYDANTIC_V2 import pydantic +from ...core.pydantic_utilities import update_forward_refs class TraversalResponse(UniversalBaseModel): @@ -209,3 +214,8 @@ class Config: frozen = True smart_union = True extra = pydantic.Extra.allow + + +update_forward_refs(EntityDetails, TraversalResponse=TraversalResponse) +update_forward_refs(EntityRelationships, TraversalResponse=TraversalResponse) +update_forward_refs(RelationshipData, TraversalResponse=TraversalResponse) diff --git a/tests/utils/test_serialization.py b/tests/utils/test_serialization.py index 3c9f39d..19b795e 100644 --- a/tests/utils/test_serialization.py +++ b/tests/utils/test_serialization.py @@ -18,7 +18,9 @@ def test_convert_and_respect_annotation_metadata() -> None: "literal": "lit_one", "any": "any", } - converted = convert_and_respect_annotation_metadata(object_=data, annotation=ObjectWithOptionalFieldParams) + converted = convert_and_respect_annotation_metadata( + object_=data, annotation=ObjectWithOptionalFieldParams, direction="write" + ) assert converted == {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"} @@ -27,7 +29,9 @@ def test_convert_and_respect_annotation_metadata_in_list() -> None: {"string": "string", "long_": 12345, "bool_": True, "literal": "lit_one", "any": "any"}, {"string": "another string", "long_": 67890, "list_": [], "literal": "lit_one", "any": "any"}, ] - converted = convert_and_respect_annotation_metadata(object_=data, annotation=List[ObjectWithOptionalFieldParams]) + converted = convert_and_respect_annotation_metadata( + object_=data, annotation=List[ObjectWithOptionalFieldParams], direction="write" + ) assert converted == [ {"string": "string", "long": 12345, "bool": True, "literal": "lit_one", "any": "any"}, @@ -43,7 +47,9 @@ def test_convert_and_respect_annotation_metadata_in_nested_object() -> None: "literal": "lit_one", "any": "any", } - converted = convert_and_respect_annotation_metadata(object_=data, annotation=ObjectWithOptionalFieldParams) + converted = convert_and_respect_annotation_metadata( + object_=data, annotation=ObjectWithOptionalFieldParams, direction="write" + ) assert converted == { "string": "string", @@ -55,12 +61,12 @@ def test_convert_and_respect_annotation_metadata_in_nested_object() -> None: def test_convert_and_respect_annotation_metadata_in_union() -> None: - converted = convert_and_respect_annotation_metadata(object_=UNION_TEST, annotation=ShapeParams) + converted = convert_and_respect_annotation_metadata(object_=UNION_TEST, annotation=ShapeParams, direction="write") assert converted == UNION_TEST_CONVERTED def test_convert_and_respect_annotation_metadata_with_empty_object() -> None: data: Any = {} - converted = convert_and_respect_annotation_metadata(object_=data, annotation=ShapeParams) + converted = convert_and_respect_annotation_metadata(object_=data, annotation=ShapeParams, direction="write") assert converted == data