From 8f39368bcade7119128e50deebb33f2f16e4a85f Mon Sep 17 00:00:00 2001 From: Tiago Silva Date: Mon, 29 Jul 2024 16:36:16 +0100 Subject: [PATCH] Add exists() (#56) * Add extra test * Add exists --- docs/en/docs/queries.md | 12 +- docs/en/mkdocs.yml | 164 ++++++++++---------- docs/pt/docs/queries.md | 11 ++ mongoz/core/db/querysets/core/manager.py | 12 ++ tests/models/manager/test_decimal_update.py | 53 +++++++ tests/models/manager/test_exists.py | 55 +++++++ 6 files changed, 224 insertions(+), 83 deletions(-) create mode 100644 tests/models/manager/test_decimal_update.py create mode 100644 tests/models/manager/test_exists.py diff --git a/docs/en/docs/queries.md b/docs/en/docs/queries.md index fb4a034..6253865 100644 --- a/docs/en/docs/queries.md +++ b/docs/en/docs/queries.md @@ -772,7 +772,6 @@ The `values_list()` can also be combined with `filter`, `only` as per usual. * **exclude_none** - Boolean flag indicating if the fields with `None` should be excluded. * **flat** - Boolean flag indicating the results should be flattened. - ### Only Returns the results containing **only** the fields in the query and nothing else. @@ -893,6 +892,17 @@ Get a document by the `_id`. This functionality accepts the parameter `id` as st user = await User.query().get_document_by_id(user.id) ``` +### Exists + +The `exists()` is used when you want to check if a record exists in the DB or not. + +=== "Manager" + + ```python + await User.objects.exists(email="example@example.com", is_active=False) + await User.objects.filter(email="example@example.com", is_active=False).exists() + ``` + ## Useful methods ### Get or create diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index a7810b4..4dc78ec 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -6,96 +6,96 @@ theme: custom_dir: ../en/overrides language: en palette: - - scheme: default - primary: blue grey - accent: red - media: "(prefers-color-scheme: light)" - toggle: - icon: material/lightbulb - name: Switch to dark mode - - scheme: slate - media: "(prefers-color-scheme: dark)" - primary: blue grey - accent: red - toggle: - icon: material/lightbulb-outline - name: Switch to light mode + - scheme: default + primary: blue grey + accent: red + media: '(prefers-color-scheme: light)' + toggle: + icon: material/lightbulb + name: Switch to dark mode + - scheme: slate + media: '(prefers-color-scheme: dark)' + primary: blue grey + accent: red + toggle: + icon: material/lightbulb-outline + name: Switch to light mode favicon: statics/images/favicon.ico logo: statics/images/white.png features: - - search.suggest - - search.highlight - - content.tabs.link - - content.code.copy + - search.suggest + - search.highlight + - content.tabs.link + - content.code.copy repo_name: dymmond/mongoz repo_url: https://github.com/dymmond/mongoz -edit_uri: "" +edit_uri: '' plugins: - - search - - meta-descriptions: - export_csv: false - quiet: false - enable_checks: false - min_length: 50 - max_length: 160 - trim: false - - mkdocstrings: - handlers: - python: - options: - extensions: - - griffe_typingdoc - show_root_heading: true - show_if_no_docstring: true - preload_modules: - - httpx - - a2wsgi - inherited_members: true - members_order: source - separate_signature: true - unwrap_annotated: true - filters: - - "!^_" - merge_init_into_class: true - docstring_section_style: spacy - signature_crossrefs: true - show_symbol_type_heading: true - show_symbol_type_toc: true +- search +- meta-descriptions: + export_csv: false + quiet: false + enable_checks: false + min_length: 50 + max_length: 160 + trim: false +- mkdocstrings: + handlers: + python: + options: + extensions: + - griffe_typingdoc + show_root_heading: true + show_if_no_docstring: true + preload_modules: + - httpx + - a2wsgi + inherited_members: true + members_order: source + separate_signature: true + unwrap_annotated: true + filters: + - '!^_' + merge_init_into_class: true + docstring_section_style: spacy + signature_crossrefs: true + show_symbol_type_heading: true + show_symbol_type_toc: true nav: - - index.md - - documents.md - - embedded-documents.md - - fields.md - - queries.md - - managers.md - - signals.md - - settings.md - - registry.md - - exceptions.md - - tips-and-tricks.md - - contributing.md - - sponsorship.md - - release-notes.md +- index.md +- documents.md +- embedded-documents.md +- fields.md +- queries.md +- managers.md +- signals.md +- settings.md +- registry.md +- exceptions.md +- tips-and-tricks.md +- contributing.md +- sponsorship.md +- release-notes.md markdown_extensions: - - attr_list - - toc: - permalink: true - - markdown.extensions.codehilite: - guess_lang: false - - mdx_include: - base_path: docs - - admonition - - codehilite - - extra - - pymdownx.superfences - - pymdownx.tabbed: - alternate_style: true - - md_in_html +- attr_list +- toc: + permalink: true +- markdown.extensions.codehilite: + guess_lang: false +- mdx_include: + base_path: docs +- admonition +- codehilite +- extra +- pymdownx.superfences +- pymdownx.tabbed: + alternate_style: true +- md_in_html extra: alternate: - - link: / - name: en - English - - link: /pt/ - name: pt - português + - link: / + name: en - English + - link: /pt/ + name: pt - português hooks: - - ../../scripts/hooks.py +- ../../scripts/hooks.py diff --git a/docs/pt/docs/queries.md b/docs/pt/docs/queries.md index b020900..3ce881c 100644 --- a/docs/pt/docs/queries.md +++ b/docs/pt/docs/queries.md @@ -872,6 +872,17 @@ Obter um documento pelo `_id`. Esta funcionalidade aceita o parâmetro `id` como user = await User.query().get_document_by_id(user.id) ``` +### Exists + +O `exists()` é utilizado quando deseja verificar se um registro existe na base de dados ou não. + +=== "Manager" + + ```python + await User.objects.exists(email="example@example.com", is_active=False) + await User.objects.filter(email="example@example.com", is_active=False).exists() + ``` + ## Métodos úteis ### Get or create diff --git a/mongoz/core/db/querysets/core/manager.py b/mongoz/core/db/querysets/core/manager.py index 315a7e0..5826254 100644 --- a/mongoz/core/db/querysets/core/manager.py +++ b/mongoz/core/db/querysets/core/manager.py @@ -636,6 +636,18 @@ async def values_list( __as_tuple__=True, ) + async def exists(self, **kwargs: Any) -> bool: + """ + Returns a boolean checking if the record exists. + """ + manager: "Manager" = self.clone() + if kwargs: + result = await manager.filter(**kwargs) + return bool(len(result) > 0) + + objects = await manager.limit(2).all() + return bool(len(objects) > 0) + async def exclude(self, **kwargs: Any) -> List["Document"]: """ Filters everything and excludes based on a specific condition. diff --git a/tests/models/manager/test_decimal_update.py b/tests/models/manager/test_decimal_update.py new file mode 100644 index 0000000..7501f67 --- /dev/null +++ b/tests/models/manager/test_decimal_update.py @@ -0,0 +1,53 @@ +from decimal import Decimal +from typing import AsyncGenerator, List + +import pydantic +import pytest + +import mongoz +from tests.conftest import client + +pytestmark = pytest.mark.anyio +pydantic_version = pydantic.__version__[:3] + + +class Badges(mongoz.EmbeddedDocument): + score: Decimal = mongoz.Decimal(max_digits=5, decimal_places=2, null=True) + name: str = mongoz.String() + + +class Achievement(mongoz.Document): + name: str = mongoz.String() + total_score: Decimal = mongoz.Decimal(max_digits=5, decimal_places=2, null=True) # noqa + badges: List[Badges] = mongoz.Array(Badges, null=True) + + class Meta: + registry = client + database = "test_db" + + +@pytest.fixture(scope="function", autouse=True) +async def prepare_database() -> AsyncGenerator: + await Achievement.objects.delete() + yield + await Achievement.objects.delete() + + +async def test_decimal_on_update() -> None: + badges = [{"name": "badge1", "score": "32083.33"}] + await Achievement.objects.create(name="Batman", total_score="22.246") + + arch = await Achievement.objects.last() + + arch.total_score = Decimal("28") + await arch.save() + + arch = await Achievement.objects.last() + + await arch.update(total_score=Decimal("30")) + + arch = await Achievement.objects.last() + + await Achievement.objects.filter().update(total_score=Decimal("40"), badges=badges) + + arch = await Achievement.objects.last() diff --git a/tests/models/manager/test_exists.py b/tests/models/manager/test_exists.py new file mode 100644 index 0000000..d4d46ee --- /dev/null +++ b/tests/models/manager/test_exists.py @@ -0,0 +1,55 @@ +from typing import AsyncGenerator, List, Optional + +import pydantic +import pytest + +import mongoz +from mongoz import Document, Index, IndexType, ObjectId, Order +from tests.conftest import client + +pytestmark = pytest.mark.anyio +pydantic_version = pydantic.__version__[:3] + +indexes = [ + Index("name", unique=True), + Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), +] + + +class Movie(Document): + name: str = mongoz.String() + year: int = mongoz.Integer() + tags: Optional[List[str]] = mongoz.Array(str, null=True) + uuid: Optional[ObjectId] = mongoz.ObjectId(null=True) + is_published: bool = mongoz.Boolean(default=False) + + class Meta: + registry = client + database = "test_db" + indexes = indexes + + +@pytest.fixture(scope="function", autouse=True) +async def prepare_database() -> AsyncGenerator: + await Movie.drop_indexes(force=True) + await Movie.objects.delete() + await Movie.create_indexes() + yield + await Movie.drop_indexes(force=True) + await Movie.objects.delete() + await Movie.create_indexes() + + +async def test_exists_true() -> None: + movies = await Movie.objects.all() + assert len(movies) == 0 + + await Movie.objects.create(name="Forrest Gump", year=2003, is_published=True) + + assert await Movie.objects.exists(name="Forrest Gump") is True + assert await Movie.objects.exists(name="Forrest Gump", year=2003) is True + assert await Movie.objects.exists(name="Forrest Gump", year=2004) is False + + assert await Movie.objects.filter(name="Forrest Gump").exists() is True + assert await Movie.objects.filter(name="Forrest Gump", year=2003).exists() is True + assert await Movie.objects.filter(name="Forrest Gump", year=2004).exists() is False