From 89a55d36b84ebb44bc8e3f344f0d2f8ebd46ad74 Mon Sep 17 00:00:00 2001 From: collerek Date: Mon, 14 Dec 2020 19:05:54 +0100 Subject: [PATCH] cleanup, update docs, bump version --- docs/releases.md | 3 +- ormar/__init__.py | 2 +- ormar/fields/foreign_key.py | 4 +- ormar/relations/relation.py | 22 ++++- ormar/relations/relation_proxy.py | 6 +- tests/test_selecting_proper_table_prefix.py | 93 +++++++++++++++++++++ 6 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 tests/test_selecting_proper_table_prefix.py diff --git a/docs/releases.md b/docs/releases.md index ea17b2f..097eadc 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -3,7 +3,8 @@ * Allow multiple relations to the same related model/table. * Fix for wrong relation column used in many_to_many relation joins (fix [#71][#71]) * Fix for wrong relation population for m2m relations when also fk relation present for same model. -* Add check if user provide related_name if there are multiple relations to same table on one model +* Add check if user provide related_name if there are multiple relations to same table on one model. +* More eager cleaning of the dead weak proxy models. # 0.7.3 diff --git a/ormar/__init__.py b/ormar/__init__.py index 4b9e747..ef770fb 100644 --- a/ormar/__init__.py +++ b/ormar/__init__.py @@ -44,7 +44,7 @@ class UndefinedType: # pragma no cover Undefined = UndefinedType() -__version__ = "0.7.3" +__version__ = "0.7.4" __all__ = [ "Integer", "BigInteger", diff --git a/ormar/fields/foreign_key.py b/ormar/fields/foreign_key.py index daf9137..e9121f7 100644 --- a/ormar/fields/foreign_key.py +++ b/ormar/fields/foreign_key.py @@ -97,12 +97,12 @@ class ForeignKeyField(BaseField): cls, value: List, child: "Model", to_register: bool, relation_name: str ) -> List["Model"]: return [ - cls.expand_relationship( + cls.expand_relationship( # type: ignore value=val, child=child, to_register=to_register, relation_name=relation_name, - ) # type: ignore + ) for val in value ] diff --git a/ormar/relations/relation.py b/ormar/relations/relation.py index b0183c3..95b49fd 100644 --- a/ormar/relations/relation.py +++ b/ormar/relations/relation.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import List, Optional, TYPE_CHECKING, Type, TypeVar, Union +from typing import List, Optional, Set, TYPE_CHECKING, Type, TypeVar, Union import ormar # noqa I100 from ormar.exceptions import RelationshipInstanceError # noqa I100 @@ -31,6 +31,7 @@ class Relation: self.manager = manager self._owner: "Model" = manager.owner self._type: RelationType = type_ + self._to_remove: Set = set() self.to: Type["T"] = to self.through: Optional[Type["T"]] = through self.related_models: Optional[Union[RelationProxy, "T"]] = ( @@ -39,17 +40,32 @@ class Relation: else None ) + def _clean_related(self) -> None: + cleaned_data = [ + x + for i, x in enumerate(self.related_models) # type: ignore + if i not in self._to_remove + ] + self.related_models = RelationProxy( + relation=self, type_=self._type, data_=cleaned_data + ) + relation_name = self._owner.resolve_relation_name(self._owner, self.to) + self._owner.__dict__[relation_name] = cleaned_data + self._to_remove = set() + def _find_existing( self, child: Union["NewBaseModel", Type["NewBaseModel"]] ) -> Optional[int]: if not isinstance(self.related_models, RelationProxy): # pragma nocover raise ValueError("Cannot find existing models in parent relation type") + if self._to_remove: + self._clean_related() for ind, relation_child in enumerate(self.related_models[:]): try: if relation_child == child: return ind except ReferenceError: # pragma no cover - self.related_models.pop(ind) + self._to_remove.add(ind) return None def add(self, child: "T") -> None: @@ -83,4 +99,6 @@ class Relation: return self.related_models def __repr__(self) -> str: # pragma no cover + if self._to_remove: + self._clean_related() return str(self.related_models) diff --git a/ormar/relations/relation_proxy.py b/ormar/relations/relation_proxy.py index c8eb944..28f177f 100644 --- a/ormar/relations/relation_proxy.py +++ b/ormar/relations/relation_proxy.py @@ -11,8 +11,10 @@ if TYPE_CHECKING: # pragma no cover class RelationProxy(list): - def __init__(self, relation: "Relation", type_: "RelationType") -> None: - super().__init__() + def __init__( + self, relation: "Relation", type_: "RelationType", data_: Any = None + ) -> None: + super().__init__(data_ or ()) self.relation: "Relation" = relation self.type_: "RelationType" = type_ self._owner: "Model" = self.relation.manager.owner diff --git a/tests/test_selecting_proper_table_prefix.py b/tests/test_selecting_proper_table_prefix.py new file mode 100644 index 0000000..c4e93ec --- /dev/null +++ b/tests/test_selecting_proper_table_prefix.py @@ -0,0 +1,93 @@ +from typing import List, Optional + +import databases +import pytest +import sqlalchemy +from sqlalchemy import create_engine + +import ormar +from tests.settings import DATABASE_URL + +database = databases.Database(DATABASE_URL) +metadata = sqlalchemy.MetaData() + + +class User(ormar.Model): + class Meta: + metadata = metadata + database = database + tablename = "test_users" + + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=50) + + +class Signup(ormar.Model): + class Meta: + metadata = metadata + database = database + tablename = "test_signup" + + id: int = ormar.Integer(primary_key=True) + + +class Session(ormar.Model): + class Meta: + metadata = metadata + database = database + tablename = "test_sessions" + + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=255, index=True) + some_text: str = ormar.Text() + some_other_text: Optional[str] = ormar.Text(nullable=True) + students: Optional[List[User]] = ormar.ManyToMany(User, through=Signup) + + +@pytest.fixture(autouse=True, scope="module") +def create_test_database(): + engine = create_engine(DATABASE_URL) + metadata.create_all(engine) + yield + metadata.drop_all(engine) + + +@pytest.mark.asyncio +async def test_list_sessions_for_user(): + async with database: + for user_id in [1, 2, 3, 4, 5]: + await User.objects.create(name=f"User {user_id}") + + for name, some_text, some_other_text in [ + ("Session 1", "Some text 1", "Some other text 1"), + ("Session 2", "Some text 2", "Some other text 2"), + ("Session 3", "Some text 3", "Some other text 3"), + ("Session 4", "Some text 4", "Some other text 4"), + ("Session 5", "Some text 5", "Some other text 5"), + ]: + await Session( + name=name, some_text=some_text, some_other_text=some_other_text + ).save() + + s1 = await Session.objects.get(pk=1) + s2 = await Session.objects.get(pk=2) + + users = {} + for i in range(1, 6): + user = await User.objects.get(pk=i) + users[f"user_{i}"] = user + if i % 2 == 0: + await s1.students.add(user) + else: + await s2.students.add(user) + + assert len(s1.students) == 2 + assert len(s2.students) == 3 + + assert [x.pk for x in s1.students] == [2, 4] + assert [x.pk for x in s2.students] == [1, 3, 5] + + user = await User.objects.select_related("sessions").get(pk=1) + + assert user.sessions is not None + assert len(user.sessions) > 0