From 6780c9de8a5cc82d809b0a91456d9828bba4d4d6 Mon Sep 17 00:00:00 2001 From: collerek Date: Mon, 12 Apr 2021 10:40:29 +0200 Subject: [PATCH] fix private attributes initialization --- docs/releases.md | 1 + ormar/models/newbasemodel.py | 34 +++++++++++-------- ormar/relations/querysetproxy.py | 2 +- .../test_pydantic_private_attributes.py | 34 +++++++++++++++++++ 4 files changed, 56 insertions(+), 15 deletions(-) create mode 100644 tests/test_model_definition/test_pydantic_private_attributes.py diff --git a/docs/releases.md b/docs/releases.md index 18d5a8d..69e725b 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -18,6 +18,7 @@ * Fix weakref `ReferenceError` error [#118](https://github.com/collerek/ormar/issues/118) * Fix error raised by Through fields when pydantic `Config.extra="forbid"` is set +* Fix bug with `pydantic.PrivateAttr` not being initialized at `__init__` [#149](https://github.com/collerek/ormar/issues/149) ## 💬 Other * Introduce link to `sqlalchemy-to-ormar` auto-translator for models diff --git a/ormar/models/newbasemodel.py b/ormar/models/newbasemodel.py index 356bbbe..af69e38 100644 --- a/ormar/models/newbasemodel.py +++ b/ormar/models/newbasemodel.py @@ -189,6 +189,10 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass new_kwargs.get(related), self, to_register=True, ) + if hasattr(self, "_init_private_attributes"): + # introduced in pydantic 1.7 + self._init_private_attributes() + def __setattr__(self, name: str, value: Any) -> None: # noqa CCR001 """ Overwrites setattr in object to allow for special behaviour of certain params. @@ -292,6 +296,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass value = object.__getattribute__(self, "__dict__").get(item, None) value = object.__getattribute__(self, "_convert_json")(item, value, "loads") return value + return object.__getattribute__(self, item) # pragma: no cover def _verify_model_can_be_initialized(self) -> None: @@ -590,18 +595,19 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass for field in fields: if not relation_map or field not in relation_map: continue - nested_model = getattr(self, field) - if isinstance(nested_model, MutableSequence): - dict_instance[field] = self._extract_nested_models_from_list( - relation_map=self._skip_ellipsis( # type: ignore - relation_map, field, default_return=dict() - ), - models=nested_model, - include=self._skip_ellipsis(include, field), - exclude=self._skip_ellipsis(exclude, field), - ) - elif nested_model is not None: - try: + try: + nested_model = getattr(self, field) + if isinstance(nested_model, MutableSequence): + dict_instance[field] = self._extract_nested_models_from_list( + relation_map=self._skip_ellipsis( # type: ignore + relation_map, field, default_return=dict() + ), + models=nested_model, + include=self._skip_ellipsis(include, field), + exclude=self._skip_ellipsis(exclude, field), + ) + elif nested_model is not None: + dict_instance[field] = nested_model.dict( relation_map=self._skip_ellipsis( relation_map, field, default_return=dict() @@ -609,9 +615,9 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass include=self._skip_ellipsis(include, field), exclude=self._skip_ellipsis(exclude, field), ) - except ReferenceError: + else: dict_instance[field] = None - else: + except ReferenceError: dict_instance[field] = None return dict_instance diff --git a/ormar/relations/querysetproxy.py b/ormar/relations/querysetproxy.py index c78b6d6..4578414 100644 --- a/ormar/relations/querysetproxy.py +++ b/ormar/relations/querysetproxy.py @@ -22,7 +22,7 @@ if TYPE_CHECKING: # pragma no cover from ormar.relations import Relation from ormar.models import Model, T from ormar.queryset import QuerySet - from ormar import RelationType, ForeignKeyField + from ormar import RelationType else: T = TypeVar("T", bound="Model") diff --git a/tests/test_model_definition/test_pydantic_private_attributes.py b/tests/test_model_definition/test_pydantic_private_attributes.py new file mode 100644 index 0000000..2765f60 --- /dev/null +++ b/tests/test_model_definition/test_pydantic_private_attributes.py @@ -0,0 +1,34 @@ +from typing import List + +import databases +import sqlalchemy +from pydantic import PrivateAttr + +import ormar +from tests.settings import DATABASE_URL + +database = databases.Database(DATABASE_URL, force_rollback=True) +metadata = sqlalchemy.MetaData() + + +class BaseMeta(ormar.ModelMeta): + metadata = metadata + database = database + + +class Subscription(ormar.Model): + class Meta(BaseMeta): + tablename = "subscriptions" + + id: int = ormar.Integer(primary_key=True) + stripe_subscription_id: str = ormar.String(nullable=False, max_length=256) + + _add_payments: List[str] = PrivateAttr(default_factory=list) + + def add_payment(self, payment: str): + self._add_payments.append(payment) + + +def test_private_attribute(): + sub = Subscription(stripe_subscription_id="2312312sad231") + sub.add_payment("test")