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