From 37891a99ccbf343a6062d074dd8cbf90ec01f8ac Mon Sep 17 00:00:00 2001 From: alistairmaclean <34235348+alistairmaclean@users.noreply.github.com> Date: Wed, 5 Feb 2025 17:10:29 +0100 Subject: [PATCH 1/3] Add logic to handle `globalns` and `localns` in `pydantic_model_creator` --- tortoise/contrib/pydantic/creator.py | 21 +++++++++++++++++++-- tortoise/contrib/pydantic/utils.py | 6 ++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/tortoise/contrib/pydantic/creator.py b/tortoise/contrib/pydantic/creator.py index e4a98cba6..147d557a9 100644 --- a/tortoise/contrib/pydantic/creator.py +++ b/tortoise/contrib/pydantic/creator.py @@ -61,6 +61,8 @@ def _pydantic_recursion_protector( name=None, allow_cycles: bool = False, sort_alphabetically: Optional[bool] = None, + globalns: dict = None, + localns: dict = None, ) -> Optional[type[PydanticModel]]: """ It is an inner function to protect pydantic model creator against cyclic recursion @@ -93,6 +95,8 @@ def _pydantic_recursion_protector( _stack=stack, allow_cycles=allow_cycles, sort_alphabetically=sort_alphabetically, + globalns=globalns, + localns=localns, _as_submodel=True, ) return pmc.create_pydantic_model() @@ -237,6 +241,8 @@ def __init__( model_config: Optional[ConfigDict] = None, validators: Optional[dict[str, Any]] = None, module: str = __name__, + globalns: dict = None, + localns: dict = None, _stack: tuple = (), _as_submodel: bool = False, ) -> None: @@ -288,7 +294,7 @@ def __init__( self._as_submodel = _as_submodel - self._annotations = get_annotations(cls) + self._annotations = get_annotations(cls, globalns=globalns, localns=localns) self._pconfig: ConfigDict @@ -305,6 +311,9 @@ def __init__( self._validators = validators self._module = module + self.globalns = globalns + self.localns = localns + self._stack = _stack @property @@ -523,7 +532,7 @@ def _process_computed_field( field: ComputedFieldDescription, ) -> Optional[Any]: func = field.function - annotation = get_annotations(self._cls, func).get("return", None) + annotation = get_annotations(self._cls, func, globalns=self.globalns, localns=self.localns).get("return", None) comment = _cleandoc(func) if annotation is not None: c_f = computed_field(return_type=annotation, description=comment) @@ -555,6 +564,8 @@ def get_fields_to_carry_on(field_tuple: tuple[str, ...]) -> tuple[str, ...]: stack=new_stack, allow_cycles=self.meta.allow_cycles, sort_alphabetically=self.meta.sort_alphabetically, + globalns=self.globalns, + localns=self.localns, ) else: pmodel = None @@ -581,6 +592,8 @@ def pydantic_model_creator( model_config: Optional[ConfigDict] = None, validators: Optional[dict[str, Any]] = None, module: str = __name__, + globalns: dict = None, + localns: dict = None, ) -> type[PydanticModel]: """ Function to build `Pydantic Model `__ off Tortoise Model. @@ -607,6 +620,8 @@ def pydantic_model_creator( :param model_config: A custom config to use as pydantic config. :param validators: A dictionary of methods that validate fields. :param module: The name of the module that the model belongs to. + :param globalns: If specified, use this dictionary as the globals map. + :param localns: If specified, use this dictionary as the locals map. Note: Created pydantic model uses config_class parameter and PydanticMeta's config_class as its Config class's bases(Only if provided!), but it @@ -627,5 +642,7 @@ def pydantic_model_creator( model_config=model_config, validators=validators, module=module, + globalns=globalns, + localns=localns, ) return pmc.create_pydantic_model() diff --git a/tortoise/contrib/pydantic/utils.py b/tortoise/contrib/pydantic/utils.py index e2d577326..50a9e8895 100644 --- a/tortoise/contrib/pydantic/utils.py +++ b/tortoise/contrib/pydantic/utils.py @@ -5,11 +5,13 @@ from tortoise.models import Model -def get_annotations(cls: "type[Model]", method: Optional[Callable] = None) -> dict[str, Any]: +def get_annotations(cls: "type[Model]", method: Optional[Callable] = None, globalns: dict = None, localns: dict = None) -> dict[str, Any]: """ Get all annotations including base classes :param cls: The model class we need annotations from :param method: If specified, we try to get the annotations for the callable + :param globalns: If specified, use this dictionary as the globals map + :param localns: If specified, use this dictionary as the locals map :return: The list of annotations """ - return get_type_hints(method or cls) + return get_type_hints(method or cls, globalns, localns) From f47dec6b983a850029287b342b6d8676fe9f3e07 Mon Sep 17 00:00:00 2001 From: alistairmaclean <34235348+alistairmaclean@users.noreply.github.com> Date: Wed, 5 Feb 2025 17:47:24 +0100 Subject: [PATCH 2/3] Update CHANGELOG.rst --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 893ebeda8..bde6bc6ae 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,7 @@ Changelog ------ Fixed ^^^^^ +- Add support for globalns and localns in pydantic_model_creator (#1876) - Fixed asyncio "no current event loop" deprecation warning by replacing `asyncio.get_event_loop()` with modern event loop handling using `get_running_loop()` with fallback to `new_event_loop()` (#1865) Changed From 0760b90bf5d69994a41321281df4a992e0af857b Mon Sep 17 00:00:00 2001 From: alistairmaclean <34235348+alistairmaclean@users.noreply.github.com> Date: Thu, 13 Feb 2025 09:17:32 +0100 Subject: [PATCH 3/3] Improve type hints for mypy --- tortoise/contrib/pydantic/creator.py | 16 +++++++++------- tortoise/contrib/pydantic/utils.py | 7 ++++++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/tortoise/contrib/pydantic/creator.py b/tortoise/contrib/pydantic/creator.py index 147d557a9..40fc36abf 100644 --- a/tortoise/contrib/pydantic/creator.py +++ b/tortoise/contrib/pydantic/creator.py @@ -61,8 +61,8 @@ def _pydantic_recursion_protector( name=None, allow_cycles: bool = False, sort_alphabetically: Optional[bool] = None, - globalns: dict = None, - localns: dict = None, + globalns: Optional[dict] = None, + localns: Optional[dict] = None, ) -> Optional[type[PydanticModel]]: """ It is an inner function to protect pydantic model creator against cyclic recursion @@ -241,8 +241,8 @@ def __init__( model_config: Optional[ConfigDict] = None, validators: Optional[dict[str, Any]] = None, module: str = __name__, - globalns: dict = None, - localns: dict = None, + globalns: Optional[dict] = None, + localns: Optional[dict] = None, _stack: tuple = (), _as_submodel: bool = False, ) -> None: @@ -532,7 +532,9 @@ def _process_computed_field( field: ComputedFieldDescription, ) -> Optional[Any]: func = field.function - annotation = get_annotations(self._cls, func, globalns=self.globalns, localns=self.localns).get("return", None) + annotation = get_annotations( + self._cls, func, globalns=self.globalns, localns=self.localns + ).get("return", None) comment = _cleandoc(func) if annotation is not None: c_f = computed_field(return_type=annotation, description=comment) @@ -592,8 +594,8 @@ def pydantic_model_creator( model_config: Optional[ConfigDict] = None, validators: Optional[dict[str, Any]] = None, module: str = __name__, - globalns: dict = None, - localns: dict = None, + globalns: Optional[dict] = None, + localns: Optional[dict] = None, ) -> type[PydanticModel]: """ Function to build `Pydantic Model `__ off Tortoise Model. diff --git a/tortoise/contrib/pydantic/utils.py b/tortoise/contrib/pydantic/utils.py index 50a9e8895..98ec30644 100644 --- a/tortoise/contrib/pydantic/utils.py +++ b/tortoise/contrib/pydantic/utils.py @@ -5,7 +5,12 @@ from tortoise.models import Model -def get_annotations(cls: "type[Model]", method: Optional[Callable] = None, globalns: dict = None, localns: dict = None) -> dict[str, Any]: +def get_annotations( + cls: "type[Model]", + method: Optional[Callable] = None, + globalns: Optional[dict] = None, + localns: Optional[dict] = None, +) -> dict[str, Any]: """ Get all annotations including base classes :param cls: The model class we need annotations from