Skip to content

Commit

Permalink
fix "caching" scope to prevent duplicate but not equal models
Browse files Browse the repository at this point in the history
  • Loading branch information
mstingl committed Mar 30, 2024
1 parent 07823e9 commit b6e7a96
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 49 deletions.
105 changes: 57 additions & 48 deletions djdantic/utils/pydantic.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import typing
from functools import cache
from types import FunctionType
from typing import Any, Callable, ForwardRef, Optional, Type, Union

Expand All @@ -13,6 +14,8 @@

TypingGenericAlias = type(Any)

_recreated_models = {}


def _new_field_from_model_field(field: ModelField, default: Any = Undefined, required: Optional[bool] = None):
if default is not Undefined:
Expand Down Expand Up @@ -47,51 +50,59 @@ class OptionalModel(BaseModel):
pass


def to_optional(id_key: str = 'id'):
def wrapped(cls: Type[BaseModel]):
def optional_model(c, __module__: str, __parent__module__: str):
try:
if issubclass(c, BaseModel):
field: ModelField
fields = {}
for key, field in c.__fields__.items():
# TODO handle ForwardRef
if field.shape == SHAPE_SINGLETON:
field_type = optional_model(
field.outer_type_,
__module__=__module__,
__parent__module__=__parent__module__,
)

else:
# TODO pydantic.get_origin ??
field_type = field.outer_type_

default = field.default
if key == id_key and not field.allow_none:
default = default or ...

elif not field.allow_none:
field_type = Optional[field_type]

elif field.required:
default = default or ...

fields[key] = (field_type, _new_field_from_model_field(field, default, required=False))

return create_model(
f'{c.__qualname__} [O]',
__base__=(c, OptionalModel),
__module__=c.__module__ if c.__module__ != __parent__module__ else __module__,
**fields,
@cache
def optional_model(c, __module__: str, __parent__module__: str, id_key: str):
try:
if issubclass(c, BaseModel):
field: ModelField
fields = {}
for key, field in c.__fields__.items():
# TODO handle ForwardRef
if field.shape == SHAPE_SINGLETON:
field_type = optional_model(
field.outer_type_,
__module__=__module__,
__parent__module__=__parent__module__,
)

except TypeError:
pass
else:
# TODO pydantic.get_origin ??
field_type = field.outer_type_

default = field.default
if key == id_key and not field.allow_none:
default = default or ...

elif not field.allow_none:
field_type = Optional[field_type]

elif field.required:
default = default or ...

fields[key] = (field_type, _new_field_from_model_field(field, default, required=False))

return create_model(
f'{c.__qualname__} [O]',
__base__=(c, OptionalModel),
__module__=c.__module__ if c.__module__ != __parent__module__ else __module__,
**fields,
)

return c
except TypeError:
pass

return optional_model(cls, __module__=cls.__module__, __parent__module__=cls.__base__.__module__)
return c


def to_optional(id_key: str = 'id'):
def wrapped(cls: Type[BaseModel]):

return optional_model(
cls,
__module__=cls.__module__,
__parent__module__=cls.__base__.__module__,
id_key=id_key,
)

return wrapped

Expand Down Expand Up @@ -124,8 +135,6 @@ def __init_subclass__(cls, rel: Optional[str] = None, rel_params: Optional[Calla


def include_reference(reference_key: str = '$rel', reference_params_key: str = '$rel_params'):
recreated_models = {}

def wrapped(cls: Type[BaseModel]):
def model_with_rel(c: Type, __parent__: Type, __module__: str, __parent__module__: str):
if isinstance(c, ForwardRef):
Expand Down Expand Up @@ -196,19 +205,19 @@ def model_with_rel(c: Type, __parent__: Type, __module__: str, __parent__module_
)

if recreate_model:
if c not in recreated_models:
recreated_models[c] = create_model(
if c not in _recreated_models:
_recreated_models[c] = create_model(
f'{c.__qualname__} [R]',
__base__=(c, ReferencedModel),
__module__=c.__module__ if c.__module__ != __parent__module__ else __module__,
**fields,
)
recreated_models[c].__recreated__ = True
_recreated_models[c].__recreated__ = True

if __parent__:
setattr(__parent__, c.__name__, recreated_models[c])
setattr(__parent__, c.__name__, _recreated_models[c])

return recreated_models[c], True
return _recreated_models[c], True

return c, False

Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = djdantic
version = 0.0.25b2
version = 0.0.25b3
author = Manuel Stingl
author_email = opensource@voltane.eu
description = Utilities to use pydantic with the django orm
Expand Down

0 comments on commit b6e7a96

Please sign in to comment.