From 5721b785060e79ccdd469fea12f2df2a8b9c202a Mon Sep 17 00:00:00 2001 From: ali moradi Date: Sun, 24 Mar 2024 00:45:54 +0330 Subject: [PATCH 1/6] add document with soft delete --- beanie/__init__.py | 2 +- beanie/odm/documents.py | 132 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 132 insertions(+), 2 deletions(-) diff --git a/beanie/__init__.py b/beanie/__init__.py index 91f233de..37d3c302 100644 --- a/beanie/__init__.py +++ b/beanie/__init__.py @@ -16,7 +16,7 @@ from beanie.odm.bulk import BulkWriter from beanie.odm.custom_types import DecimalAnnotation from beanie.odm.custom_types.bson.binary import BsonBinary -from beanie.odm.documents import Document, MergeStrategy +from beanie.odm.documents import Document, MergeStrategy, DocumentWithSoftDelete from beanie.odm.enums import SortDirection from beanie.odm.fields import ( BackLink, diff --git a/beanie/odm/documents.py b/beanie/odm/documents.py index 94d7faf6..a29916ab 100644 --- a/beanie/odm/documents.py +++ b/beanie/odm/documents.py @@ -1,5 +1,6 @@ import asyncio import warnings +from datetime import datetime from enum import Enum from typing import ( Any, @@ -11,7 +12,7 @@ Optional, Type, TypeVar, - Union, + Union, Tuple, ) from uuid import UUID, uuid4 @@ -98,11 +99,14 @@ save_state_after, saved_state_needed, ) +from beanie.odm.queries.find import FindMany, FindOne from beanie.odm.utils.typing import extract_id_class +from beanie.odm.enums import SortDirection if IS_PYDANTIC_V2: from pydantic import model_validator +FindType = TypeVar("FindType", bound=Union["Document", "View"]) DocType = TypeVar("DocType", bound="Document") DocumentProjectionType = TypeVar("DocumentProjectionType", bound=BaseModel) @@ -1187,3 +1191,129 @@ async def distinct( def link_from_id(cls, id: Any): ref = DBRef(id=id, collection=cls.get_collection_name()) return Link(ref, document_class=cls) + + +class DocumentWithSoftDelete(Document): + deleted_at: Optional[datetime] = None + + def is_deleted(self) -> bool: + return self.deleted_at is None + + async def hard_delete( + self, + session: Optional[ClientSession] = None, + bulk_writer: Optional[BulkWriter] = None, + link_rule: DeleteRules = DeleteRules.DO_NOTHING, + skip_actions: Optional[List[Union[ActionDirections, str]]] = None, + **pymongo_kwargs, + ) -> Optional[DeleteResult]: + return await super().delete( + session=session, + bulk_writer=bulk_writer, + link_rule=link_rule, + skip_actions=skip_actions, + **pymongo_kwargs, + ) + + async def delete( + self, + session: Optional[ClientSession] = None, + bulk_writer: Optional[BulkWriter] = None, + link_rule: DeleteRules = DeleteRules.DO_NOTHING, + skip_actions: Optional[List[Union[ActionDirections, str]]] = None, + **pymongo_kwargs, + ) -> Optional[DeleteResult]: + self.deleted_at = datetime.utcnow() + await self.save() + return + + @classmethod + def find_many_in_all( + cls: Type[FindType], + *args: Union[Mapping[str, Any], bool], + projection_model: None = None, + skip: Optional[int] = None, + limit: Optional[int] = None, + sort: Union[None, str, List[Tuple[str, SortDirection]]] = None, + session: Optional[ClientSession] = None, + ignore_cache: bool = False, + fetch_links: bool = False, + with_children: bool = False, + lazy_parse: bool = False, + nesting_depth: Optional[int] = None, + nesting_depths_per_field: Optional[Dict[str, int]] = None, + **pymongo_kwargs, + ) -> FindMany[FindType]: + return super().find_many( + *args, + sort=sort, + skip=skip, + limit=limit, + projection_model=projection_model, + session=session, + ignore_cache=ignore_cache, + fetch_links=fetch_links, + lazy_parse=lazy_parse, + with_children=with_children, + nesting_depth=nesting_depth, + nesting_depths_per_field=nesting_depths_per_field, + **pymongo_kwargs, + ) + + @classmethod + def find_many( + cls: Type[FindType], + *args: Union[Mapping[str, Any], bool], + projection_model: None = None, + skip: Optional[int] = None, + limit: Optional[int] = None, + sort: Union[None, str, List[Tuple[str, SortDirection]]] = None, + session: Optional[ClientSession] = None, + ignore_cache: bool = False, + fetch_links: bool = False, + with_children: bool = False, + lazy_parse: bool = False, + nesting_depth: Optional[int] = None, + nesting_depths_per_field: Optional[Dict[str, int]] = None, + **pymongo_kwargs, + ) -> FindMany[FindType]: + args = cls._add_class_id_filter(args, with_children) + ({'deleted_at': None},) + return cls._find_many_query_class(document_model=cls).find_many( + *args, + sort=sort, + skip=skip, + limit=limit, + projection_model=projection_model, + session=session, + ignore_cache=ignore_cache, + fetch_links=fetch_links, + lazy_parse=lazy_parse, + nesting_depth=nesting_depth, + nesting_depths_per_field=nesting_depths_per_field, + **pymongo_kwargs, + ) + + @classmethod + def find_one( + cls: Type[FindType], + *args: Union[Mapping[str, Any], bool], + projection_model: None = None, + session: Optional[ClientSession] = None, + ignore_cache: bool = False, + fetch_links: bool = False, + with_children: bool = False, + nesting_depth: Optional[int] = None, + nesting_depths_per_field: Optional[Dict[str, int]] = None, + **pymongo_kwargs, + ) -> FindOne[FindType]: + args = cls._add_class_id_filter(args, with_children) + ({'deleted_at': None},) + return cls._find_one_query_class(document_model=cls).find_one( + *args, + projection_model=projection_model, + session=session, + ignore_cache=ignore_cache, + fetch_links=fetch_links, + nesting_depth=nesting_depth, + nesting_depths_per_field=nesting_depths_per_field, + **pymongo_kwargs, + ) From af24bec4af6be6fca779fd1c1e741e534ef914f7 Mon Sep 17 00:00:00 2001 From: ali moradi Date: Sun, 24 Mar 2024 01:17:27 +0330 Subject: [PATCH 2/6] reformat --- beanie/odm/documents.py | 133 +++++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 62 deletions(-) diff --git a/beanie/odm/documents.py b/beanie/odm/documents.py index a29916ab..b0954dbc 100644 --- a/beanie/odm/documents.py +++ b/beanie/odm/documents.py @@ -3,6 +3,7 @@ from datetime import datetime from enum import Enum from typing import ( + TYPE_CHECKING, Any, ClassVar, Dict, @@ -10,9 +11,10 @@ List, Mapping, Optional, + Tuple, Type, TypeVar, - Union, Tuple, + Union, ) from uuid import UUID, uuid4 @@ -49,6 +51,7 @@ ) from beanie.odm.bulk import BulkWriter, Operation from beanie.odm.cache import LRUCache +from beanie.odm.enums import SortDirection from beanie.odm.fields import ( BackLink, DeleteRules, @@ -80,6 +83,7 @@ from beanie.odm.operators.update.general import ( Set as SetOperator, ) +from beanie.odm.queries.find import FindMany, FindOne from beanie.odm.queries.update import UpdateMany, UpdateResponse from beanie.odm.settings.document import DocumentSettings from beanie.odm.utils.dump import get_dict, get_top_level_nones @@ -99,13 +103,14 @@ save_state_after, saved_state_needed, ) -from beanie.odm.queries.find import FindMany, FindOne from beanie.odm.utils.typing import extract_id_class -from beanie.odm.enums import SortDirection if IS_PYDANTIC_V2: from pydantic import model_validator +if TYPE_CHECKING: + from beanie.odm.views import View + FindType = TypeVar("FindType", bound=Union["Document", "View"]) DocType = TypeVar("DocType", bound="Document") DocumentProjectionType = TypeVar("DocumentProjectionType", bound=BaseModel) @@ -1200,12 +1205,12 @@ def is_deleted(self) -> bool: return self.deleted_at is None async def hard_delete( - self, - session: Optional[ClientSession] = None, - bulk_writer: Optional[BulkWriter] = None, - link_rule: DeleteRules = DeleteRules.DO_NOTHING, - skip_actions: Optional[List[Union[ActionDirections, str]]] = None, - **pymongo_kwargs, + self, + session: Optional[ClientSession] = None, + bulk_writer: Optional[BulkWriter] = None, + link_rule: DeleteRules = DeleteRules.DO_NOTHING, + skip_actions: Optional[List[Union[ActionDirections, str]]] = None, + **pymongo_kwargs, ) -> Optional[DeleteResult]: return await super().delete( session=session, @@ -1216,35 +1221,35 @@ async def hard_delete( ) async def delete( - self, - session: Optional[ClientSession] = None, - bulk_writer: Optional[BulkWriter] = None, - link_rule: DeleteRules = DeleteRules.DO_NOTHING, - skip_actions: Optional[List[Union[ActionDirections, str]]] = None, - **pymongo_kwargs, + self, + session: Optional[ClientSession] = None, + bulk_writer: Optional[BulkWriter] = None, + link_rule: DeleteRules = DeleteRules.DO_NOTHING, + skip_actions: Optional[List[Union[ActionDirections, str]]] = None, + **pymongo_kwargs, ) -> Optional[DeleteResult]: self.deleted_at = datetime.utcnow() await self.save() - return + return None @classmethod - def find_many_in_all( - cls: Type[FindType], - *args: Union[Mapping[str, Any], bool], - projection_model: None = None, - skip: Optional[int] = None, - limit: Optional[int] = None, - sort: Union[None, str, List[Tuple[str, SortDirection]]] = None, - session: Optional[ClientSession] = None, - ignore_cache: bool = False, - fetch_links: bool = False, - with_children: bool = False, - lazy_parse: bool = False, - nesting_depth: Optional[int] = None, - nesting_depths_per_field: Optional[Dict[str, int]] = None, - **pymongo_kwargs, + def find_many_in_all( # type: ignore + cls: Type[FindType], + *args: Union[Mapping[str, Any], bool], + projection_model: None = None, + skip: Optional[int] = None, + limit: Optional[int] = None, + sort: Union[None, str, List[Tuple[str, SortDirection]]] = None, + session: Optional[ClientSession] = None, + ignore_cache: bool = False, + fetch_links: bool = False, + with_children: bool = False, + lazy_parse: bool = False, + nesting_depth: Optional[int] = None, + nesting_depths_per_field: Optional[Dict[str, int]] = None, + **pymongo_kwargs, ) -> FindMany[FindType]: - return super().find_many( + return Document.find_many( *args, sort=sort, skip=skip, @@ -1261,23 +1266,25 @@ def find_many_in_all( ) @classmethod - def find_many( - cls: Type[FindType], - *args: Union[Mapping[str, Any], bool], - projection_model: None = None, - skip: Optional[int] = None, - limit: Optional[int] = None, - sort: Union[None, str, List[Tuple[str, SortDirection]]] = None, - session: Optional[ClientSession] = None, - ignore_cache: bool = False, - fetch_links: bool = False, - with_children: bool = False, - lazy_parse: bool = False, - nesting_depth: Optional[int] = None, - nesting_depths_per_field: Optional[Dict[str, int]] = None, - **pymongo_kwargs, - ) -> FindMany[FindType]: - args = cls._add_class_id_filter(args, with_children) + ({'deleted_at': None},) + def find_many( # type: ignore + cls: Type[FindType], + *args: Union[Mapping[str, Any], bool], + projection_model: Optional[Type["DocumentProjectionType"]] = None, + skip: Optional[int] = None, + limit: Optional[int] = None, + sort: Union[None, str, List[Tuple[str, SortDirection]]] = None, + session: Optional[ClientSession] = None, + ignore_cache: bool = False, + fetch_links: bool = False, + with_children: bool = False, + lazy_parse: bool = False, + nesting_depth: Optional[int] = None, + nesting_depths_per_field: Optional[Dict[str, int]] = None, + **pymongo_kwargs, + ) -> Union[FindMany[FindType], FindMany["DocumentProjectionType"]]: + args = cls._add_class_id_filter(args, with_children) + ( + {"deleted_at": None}, + ) return cls._find_many_query_class(document_model=cls).find_many( *args, sort=sort, @@ -1294,19 +1301,21 @@ def find_many( ) @classmethod - def find_one( - cls: Type[FindType], - *args: Union[Mapping[str, Any], bool], - projection_model: None = None, - session: Optional[ClientSession] = None, - ignore_cache: bool = False, - fetch_links: bool = False, - with_children: bool = False, - nesting_depth: Optional[int] = None, - nesting_depths_per_field: Optional[Dict[str, int]] = None, - **pymongo_kwargs, - ) -> FindOne[FindType]: - args = cls._add_class_id_filter(args, with_children) + ({'deleted_at': None},) + def find_one( # type: ignore + cls: Type[FindType], + *args: Union[Mapping[str, Any], bool], + projection_model: Optional[Type["DocumentProjectionType"]] = None, + session: Optional[ClientSession] = None, + ignore_cache: bool = False, + fetch_links: bool = False, + with_children: bool = False, + nesting_depth: Optional[int] = None, + nesting_depths_per_field: Optional[Dict[str, int]] = None, + **pymongo_kwargs, + ) -> Union[FindOne[FindType], FindOne["DocumentProjectionType"]]: + args = cls._add_class_id_filter(args, with_children) + ( + {"deleted_at": None}, + ) return cls._find_one_query_class(document_model=cls).find_one( *args, projection_model=projection_model, From 8b63d7b881270da63680a281f6f99e2134b58697 Mon Sep 17 00:00:00 2001 From: ali moradi Date: Sun, 24 Mar 2024 00:45:54 +0330 Subject: [PATCH 3/6] add document with soft delete --- beanie/__init__.py | 2 +- beanie/odm/documents.py | 139 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) diff --git a/beanie/__init__.py b/beanie/__init__.py index 91f233de..37d3c302 100644 --- a/beanie/__init__.py +++ b/beanie/__init__.py @@ -16,7 +16,7 @@ from beanie.odm.bulk import BulkWriter from beanie.odm.custom_types import DecimalAnnotation from beanie.odm.custom_types.bson.binary import BsonBinary -from beanie.odm.documents import Document, MergeStrategy +from beanie.odm.documents import Document, MergeStrategy, DocumentWithSoftDelete from beanie.odm.enums import SortDirection from beanie.odm.fields import ( BackLink, diff --git a/beanie/odm/documents.py b/beanie/odm/documents.py index 94d7faf6..b0954dbc 100644 --- a/beanie/odm/documents.py +++ b/beanie/odm/documents.py @@ -1,7 +1,9 @@ import asyncio import warnings +from datetime import datetime from enum import Enum from typing import ( + TYPE_CHECKING, Any, ClassVar, Dict, @@ -9,6 +11,7 @@ List, Mapping, Optional, + Tuple, Type, TypeVar, Union, @@ -48,6 +51,7 @@ ) from beanie.odm.bulk import BulkWriter, Operation from beanie.odm.cache import LRUCache +from beanie.odm.enums import SortDirection from beanie.odm.fields import ( BackLink, DeleteRules, @@ -79,6 +83,7 @@ from beanie.odm.operators.update.general import ( Set as SetOperator, ) +from beanie.odm.queries.find import FindMany, FindOne from beanie.odm.queries.update import UpdateMany, UpdateResponse from beanie.odm.settings.document import DocumentSettings from beanie.odm.utils.dump import get_dict, get_top_level_nones @@ -103,6 +108,10 @@ if IS_PYDANTIC_V2: from pydantic import model_validator +if TYPE_CHECKING: + from beanie.odm.views import View + +FindType = TypeVar("FindType", bound=Union["Document", "View"]) DocType = TypeVar("DocType", bound="Document") DocumentProjectionType = TypeVar("DocumentProjectionType", bound=BaseModel) @@ -1187,3 +1196,133 @@ async def distinct( def link_from_id(cls, id: Any): ref = DBRef(id=id, collection=cls.get_collection_name()) return Link(ref, document_class=cls) + + +class DocumentWithSoftDelete(Document): + deleted_at: Optional[datetime] = None + + def is_deleted(self) -> bool: + return self.deleted_at is None + + async def hard_delete( + self, + session: Optional[ClientSession] = None, + bulk_writer: Optional[BulkWriter] = None, + link_rule: DeleteRules = DeleteRules.DO_NOTHING, + skip_actions: Optional[List[Union[ActionDirections, str]]] = None, + **pymongo_kwargs, + ) -> Optional[DeleteResult]: + return await super().delete( + session=session, + bulk_writer=bulk_writer, + link_rule=link_rule, + skip_actions=skip_actions, + **pymongo_kwargs, + ) + + async def delete( + self, + session: Optional[ClientSession] = None, + bulk_writer: Optional[BulkWriter] = None, + link_rule: DeleteRules = DeleteRules.DO_NOTHING, + skip_actions: Optional[List[Union[ActionDirections, str]]] = None, + **pymongo_kwargs, + ) -> Optional[DeleteResult]: + self.deleted_at = datetime.utcnow() + await self.save() + return None + + @classmethod + def find_many_in_all( # type: ignore + cls: Type[FindType], + *args: Union[Mapping[str, Any], bool], + projection_model: None = None, + skip: Optional[int] = None, + limit: Optional[int] = None, + sort: Union[None, str, List[Tuple[str, SortDirection]]] = None, + session: Optional[ClientSession] = None, + ignore_cache: bool = False, + fetch_links: bool = False, + with_children: bool = False, + lazy_parse: bool = False, + nesting_depth: Optional[int] = None, + nesting_depths_per_field: Optional[Dict[str, int]] = None, + **pymongo_kwargs, + ) -> FindMany[FindType]: + return Document.find_many( + *args, + sort=sort, + skip=skip, + limit=limit, + projection_model=projection_model, + session=session, + ignore_cache=ignore_cache, + fetch_links=fetch_links, + lazy_parse=lazy_parse, + with_children=with_children, + nesting_depth=nesting_depth, + nesting_depths_per_field=nesting_depths_per_field, + **pymongo_kwargs, + ) + + @classmethod + def find_many( # type: ignore + cls: Type[FindType], + *args: Union[Mapping[str, Any], bool], + projection_model: Optional[Type["DocumentProjectionType"]] = None, + skip: Optional[int] = None, + limit: Optional[int] = None, + sort: Union[None, str, List[Tuple[str, SortDirection]]] = None, + session: Optional[ClientSession] = None, + ignore_cache: bool = False, + fetch_links: bool = False, + with_children: bool = False, + lazy_parse: bool = False, + nesting_depth: Optional[int] = None, + nesting_depths_per_field: Optional[Dict[str, int]] = None, + **pymongo_kwargs, + ) -> Union[FindMany[FindType], FindMany["DocumentProjectionType"]]: + args = cls._add_class_id_filter(args, with_children) + ( + {"deleted_at": None}, + ) + return cls._find_many_query_class(document_model=cls).find_many( + *args, + sort=sort, + skip=skip, + limit=limit, + projection_model=projection_model, + session=session, + ignore_cache=ignore_cache, + fetch_links=fetch_links, + lazy_parse=lazy_parse, + nesting_depth=nesting_depth, + nesting_depths_per_field=nesting_depths_per_field, + **pymongo_kwargs, + ) + + @classmethod + def find_one( # type: ignore + cls: Type[FindType], + *args: Union[Mapping[str, Any], bool], + projection_model: Optional[Type["DocumentProjectionType"]] = None, + session: Optional[ClientSession] = None, + ignore_cache: bool = False, + fetch_links: bool = False, + with_children: bool = False, + nesting_depth: Optional[int] = None, + nesting_depths_per_field: Optional[Dict[str, int]] = None, + **pymongo_kwargs, + ) -> Union[FindOne[FindType], FindOne["DocumentProjectionType"]]: + args = cls._add_class_id_filter(args, with_children) + ( + {"deleted_at": None}, + ) + return cls._find_one_query_class(document_model=cls).find_one( + *args, + projection_model=projection_model, + session=session, + ignore_cache=ignore_cache, + fetch_links=fetch_links, + nesting_depth=nesting_depth, + nesting_depths_per_field=nesting_depths_per_field, + **pymongo_kwargs, + ) From 0f629ec9175e681c3cf2f09672ca16935ef146e8 Mon Sep 17 00:00:00 2001 From: ali moradi Date: Sun, 24 Mar 2024 00:45:54 +0330 Subject: [PATCH 4/6] fix imports add DocumentWithSoftDelete to init beanie add test insert one and delete one --- beanie/__init__.py | 7 ++++++- beanie/odm/documents.py | 2 +- tests/odm/conftest.py | 10 ++++++++++ tests/odm/documents/test_soft_delete.py | 23 +++++++++++++++++++++++ tests/odm/models.py | 6 ++++++ 5 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 tests/odm/documents/test_soft_delete.py diff --git a/beanie/__init__.py b/beanie/__init__.py index 37d3c302..5a41b445 100644 --- a/beanie/__init__.py +++ b/beanie/__init__.py @@ -16,7 +16,11 @@ from beanie.odm.bulk import BulkWriter from beanie.odm.custom_types import DecimalAnnotation from beanie.odm.custom_types.bson.binary import BsonBinary -from beanie.odm.documents import Document, MergeStrategy, DocumentWithSoftDelete +from beanie.odm.documents import ( + Document, + DocumentWithSoftDelete, + MergeStrategy, +) from beanie.odm.enums import SortDirection from beanie.odm.fields import ( BackLink, @@ -37,6 +41,7 @@ __all__ = [ # ODM "Document", + "DocumentWithSoftDelete", "View", "UnionDoc", "init_beanie", diff --git a/beanie/odm/documents.py b/beanie/odm/documents.py index b0954dbc..4b1e143a 100644 --- a/beanie/odm/documents.py +++ b/beanie/odm/documents.py @@ -1202,7 +1202,7 @@ class DocumentWithSoftDelete(Document): deleted_at: Optional[datetime] = None def is_deleted(self) -> bool: - return self.deleted_at is None + return self.deleted_at is not None async def hard_delete( self, diff --git a/tests/odm/conftest.py b/tests/odm/conftest.py index 085f45b7..5191ab59 100644 --- a/tests/odm/conftest.py +++ b/tests/odm/conftest.py @@ -27,6 +27,7 @@ DocumentTestModelWithIndexFlagsAliases, DocumentTestModelWithLink, DocumentTestModelWithSimpleIndex, + DocumentTestModelWithSoftDelete, DocumentToBeLinked, DocumentToTestSync, DocumentUnion, @@ -199,6 +200,7 @@ async def init(db): DocumentWithExtras, DocumentWithPydanticConfig, DocumentTestModel, + DocumentTestModelWithSoftDelete, DocumentTestModelWithLink, DocumentTestModelWithCustomCollectionName, DocumentTestModelWithSimpleIndex, @@ -333,6 +335,14 @@ def generate_documents( return generate_documents +@pytest.fixture +def document_soft_delete_not_inserted(): + return DocumentTestModelWithSoftDelete( + test_int=randint(0, 1000000), + test_str="kipasa", + ) + + @pytest.fixture async def document(document_not_inserted) -> DocumentTestModel: return await document_not_inserted.insert() diff --git a/tests/odm/documents/test_soft_delete.py b/tests/odm/documents/test_soft_delete.py new file mode 100644 index 00000000..2f902d2f --- /dev/null +++ b/tests/odm/documents/test_soft_delete.py @@ -0,0 +1,23 @@ +from tests.odm.models import DocumentTestModelWithSoftDelete + + +async def test_insert_one_and_delete_one(document_soft_delete_not_inserted): + # insert a document with soft delete + result = await document_soft_delete_not_inserted.insert() + + # get from db by id + document = await DocumentTestModelWithSoftDelete.get(document_id=result.id) + + assert document.is_deleted() is False + assert document.deleted_at is None + assert document.test_int == result.test_int + assert document.test_str == result.test_str + + # # delete the document + await document.delete() + assert document.is_deleted() is True + + # check if document exist + document = await DocumentTestModelWithSoftDelete.get(document_id=result.id) + + assert document is None diff --git a/tests/odm/models.py b/tests/odm/models.py index c1664f78..223568af 100644 --- a/tests/odm/models.py +++ b/tests/odm/models.py @@ -42,6 +42,7 @@ from beanie import ( DecimalAnnotation, Document, + DocumentWithSoftDelete, Indexed, Insert, Replace, @@ -140,6 +141,11 @@ class Sample(Document): const: str = "TEST" +class DocumentTestModelWithSoftDelete(DocumentWithSoftDelete): + test_int: int + test_str: str + + class SubDocument(BaseModel): test_str: str test_int: int = 42 From ac3ac25d76f9a4aef55d4b95183ea92afddd9f58 Mon Sep 17 00:00:00 2001 From: ali moradi Date: Fri, 19 Apr 2024 01:22:43 +0330 Subject: [PATCH 5/6] add test for find_many method for soft delete --- tests/odm/conftest.py | 13 +++++++++++++ tests/odm/documents/test_soft_delete.py | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/tests/odm/conftest.py b/tests/odm/conftest.py index 5191ab59..afb582e7 100644 --- a/tests/odm/conftest.py +++ b/tests/odm/conftest.py @@ -343,6 +343,19 @@ def document_soft_delete_not_inserted(): ) +@pytest.fixture +def documents_soft_delete_not_inserted(): + docs = [] + for i in range(3): + docs.append( + DocumentTestModelWithSoftDelete( + test_int=randint(0, 1000000), + test_str="kipasa", + ) + ) + return docs + + @pytest.fixture async def document(document_not_inserted) -> DocumentTestModel: return await document_not_inserted.insert() diff --git a/tests/odm/documents/test_soft_delete.py b/tests/odm/documents/test_soft_delete.py index 2f902d2f..8985286a 100644 --- a/tests/odm/documents/test_soft_delete.py +++ b/tests/odm/documents/test_soft_delete.py @@ -21,3 +21,21 @@ async def test_insert_one_and_delete_one(document_soft_delete_not_inserted): document = await DocumentTestModelWithSoftDelete.get(document_id=result.id) assert document is None + + +async def test_find_many(documents_soft_delete_not_inserted): + # insert 2 documents + item_1 = await documents_soft_delete_not_inserted[0].insert() + item_2 = await documents_soft_delete_not_inserted[1].insert() + + # use `.find_many()` to get them all + results = await DocumentTestModelWithSoftDelete.find_many().to_list() + assert len(results) == 2 + + # delete one of them + await item_1.delete() + + results = await DocumentTestModelWithSoftDelete.find_many().to_list() + + assert len(results) == 1 + assert results[0].id == item_2.id From 694ec28fb80d82d1b20b293c9f68fb13c3810b6b Mon Sep 17 00:00:00 2001 From: ali moradi Date: Fri, 19 Apr 2024 11:33:56 +0330 Subject: [PATCH 6/6] fix `.find_many_in_all()` add more test --- beanie/odm/documents.py | 5 +- tests/odm/documents/test_soft_delete.py | 73 ++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/beanie/odm/documents.py b/beanie/odm/documents.py index 4b1e143a..a2109999 100644 --- a/beanie/odm/documents.py +++ b/beanie/odm/documents.py @@ -1248,8 +1248,8 @@ def find_many_in_all( # type: ignore nesting_depth: Optional[int] = None, nesting_depths_per_field: Optional[Dict[str, int]] = None, **pymongo_kwargs, - ) -> FindMany[FindType]: - return Document.find_many( + ) -> Union[FindMany[FindType], FindMany["DocumentProjectionType"]]: + return cls._find_many_query_class(document_model=cls).find_many( *args, sort=sort, skip=skip, @@ -1259,7 +1259,6 @@ def find_many_in_all( # type: ignore ignore_cache=ignore_cache, fetch_links=fetch_links, lazy_parse=lazy_parse, - with_children=with_children, nesting_depth=nesting_depth, nesting_depths_per_field=nesting_depths_per_field, **pymongo_kwargs, diff --git a/tests/odm/documents/test_soft_delete.py b/tests/odm/documents/test_soft_delete.py index 8985286a..d3cb4f95 100644 --- a/tests/odm/documents/test_soft_delete.py +++ b/tests/odm/documents/test_soft_delete.py @@ -1,7 +1,7 @@ from tests.odm.models import DocumentTestModelWithSoftDelete -async def test_insert_one_and_delete_one(document_soft_delete_not_inserted): +async def test_get_item(document_soft_delete_not_inserted): # insert a document with soft delete result = await document_soft_delete_not_inserted.insert() @@ -17,12 +17,59 @@ async def test_insert_one_and_delete_one(document_soft_delete_not_inserted): await document.delete() assert document.is_deleted() is True - # check if document exist + # check if document exist with `.get()` document = await DocumentTestModelWithSoftDelete.get(document_id=result.id) + assert document is None + + # check document exist in trashed + results = ( + await DocumentTestModelWithSoftDelete.find_many_in_all().to_list() + ) + assert len(results) == 1 + + +async def test_find_one(document_soft_delete_not_inserted): + result = await document_soft_delete_not_inserted.insert() + + # # delete the document + await result.delete() + # check if document exist with `.find_one()` + document = await DocumentTestModelWithSoftDelete.find_one( + DocumentTestModelWithSoftDelete.id == result.id + ) assert document is None +async def test_find(documents_soft_delete_not_inserted): + # insert 3 documents + inserted_docs = [] + for doc in documents_soft_delete_not_inserted: + result = await doc.insert() + inserted_docs.append(result) + + # use `.find_many()` to get them all + results = await DocumentTestModelWithSoftDelete.find().to_list() + assert len(results) == 3 + + # delete one of them + await inserted_docs[0].delete() + + # check items in with `.find_many()` + results = await DocumentTestModelWithSoftDelete.find_many().to_list() + + assert len(results) == 2 + + founded_documents_id = [doc.id for doc in results] + assert inserted_docs[0].id not in founded_documents_id + + # check in trashed items + results = ( + await DocumentTestModelWithSoftDelete.find_many_in_all().to_list() + ) + assert len(results) == 3 + + async def test_find_many(documents_soft_delete_not_inserted): # insert 2 documents item_1 = await documents_soft_delete_not_inserted[0].insert() @@ -35,7 +82,29 @@ async def test_find_many(documents_soft_delete_not_inserted): # delete one of them await item_1.delete() + # check items in with `.find_many()` results = await DocumentTestModelWithSoftDelete.find_many().to_list() assert len(results) == 1 assert results[0].id == item_2.id + + # check in trashed items + results = ( + await DocumentTestModelWithSoftDelete.find_many_in_all().to_list() + ) + assert len(results) == 2 + + +async def test_hard_delete(document_soft_delete_not_inserted): + result = await document_soft_delete_not_inserted.insert() + await result.hard_delete() + + # check items in with `.find_many()` + results = await DocumentTestModelWithSoftDelete.find_many().to_list() + assert len(results) == 0 + + # check in trashed + results = ( + await DocumentTestModelWithSoftDelete.find_many_in_all().to_list() + ) + assert len(results) == 0