Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add logic to handle globalns and localns in pydantic_model_creator #1876

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 21 additions & 2 deletions tortoise/contrib/pydantic/creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ def _pydantic_recursion_protector(
name=None,
allow_cycles: bool = False,
sort_alphabetically: Optional[bool] = 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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -237,6 +241,8 @@ def __init__(
model_config: Optional[ConfigDict] = None,
validators: Optional[dict[str, Any]] = None,
module: str = __name__,
globalns: Optional[dict] = None,
localns: Optional[dict] = None,
_stack: tuple = (),
_as_submodel: bool = False,
) -> None:
Expand Down Expand Up @@ -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

Expand All @@ -305,6 +311,9 @@ def __init__(
self._validators = validators
self._module = module

self.globalns = globalns
self.localns = localns

self._stack = _stack

@property
Expand Down Expand Up @@ -523,7 +532,9 @@ 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)
Expand Down Expand Up @@ -555,6 +566,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
Expand All @@ -581,6 +594,8 @@ def pydantic_model_creator(
model_config: Optional[ConfigDict] = None,
validators: Optional[dict[str, Any]] = None,
module: str = __name__,
globalns: Optional[dict] = None,
localns: Optional[dict] = None,
) -> type[PydanticModel]:
"""
Function to build `Pydantic Model <https://docs.pydantic.dev/latest/concepts/models/>`__ off Tortoise Model.
Expand All @@ -607,6 +622,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
Expand All @@ -627,5 +644,7 @@ def pydantic_model_creator(
model_config=model_config,
validators=validators,
module=module,
globalns=globalns,
localns=localns,
)
return pmc.create_pydantic_model()
11 changes: 9 additions & 2 deletions tortoise/contrib/pydantic/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@
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: 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
: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)