├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── 1-issue.md │ └── config.yml ├── dependbot.yml └── workflows │ ├── publish.yml │ └── test-suite.yml ├── .gitignore ├── .pdbrc ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── compose.overrides.yml ├── compose.yml ├── docs ├── en │ ├── docs │ │ ├── contributing.md │ │ ├── documents.md │ │ ├── embedded-documents.md │ │ ├── exceptions.md │ │ ├── fields.md │ │ ├── index.md │ │ ├── managers.md │ │ ├── queries.md │ │ ├── registry.md │ │ ├── release-notes.md │ │ ├── settings.md │ │ ├── signals.md │ │ ├── sponsorship.md │ │ ├── statics │ │ │ └── images │ │ │ │ ├── favicon.ico │ │ │ │ └── white.png │ │ └── tips-and-tricks.md │ ├── mkdocs.yml │ └── overrides │ │ ├── assets │ │ ├── bootstrap │ │ │ ├── css │ │ │ │ └── bootstrap.min.css │ │ │ └── js │ │ │ │ └── bootstrap.min.js │ │ ├── css │ │ │ ├── bs-theme-overrides.css │ │ │ └── esmerald.css │ │ ├── img │ │ │ ├── favicon.ico │ │ │ ├── illustrations │ │ │ │ ├── meeting.svg │ │ │ │ ├── presentation.svg │ │ │ │ ├── teamwork.svg │ │ │ │ └── web-development.svg │ │ │ └── white.png │ │ └── js │ │ │ ├── esmerald.js │ │ │ └── startup-modern.js │ │ ├── home.html │ │ └── nav.html ├── language_names.yml ├── missing-translation.md └── pt │ ├── docs │ ├── contributing.md │ ├── documents.md │ ├── embedded-documents.md │ ├── exceptions.md │ ├── fields.md │ ├── index.md │ ├── managers.md │ ├── queries.md │ ├── registry.md │ ├── settings.md │ ├── signals.md │ ├── sponsorship.md │ └── tips-and-tricks.md │ └── mkdocs.yml ├── docs_src ├── documents │ ├── abstract │ │ ├── common.py │ │ └── simple.py │ ├── declaring_models.py │ ├── default_model.py │ ├── embed.py │ ├── indexes │ │ ├── complex_together.py │ │ ├── simple.py │ │ └── simple2.py │ ├── registry │ │ ├── inheritance_abstract.py │ │ ├── inheritance_no_repeat.py │ │ └── nutshell.py │ └── tablename │ │ ├── model_diff_tn.py │ │ ├── model_no_tablename.py │ │ └── model_with_tablename.py ├── managers │ ├── custom.py │ ├── example.py │ ├── override.py │ └── simple.py ├── queries │ ├── document.py │ └── embed.py ├── quickstart │ └── quickstart.py ├── registry │ ├── asgi_fw.py │ ├── custom_registry.py │ ├── document_checks.py │ └── model.py ├── settings │ └── custom_settings.py ├── signals │ ├── custom.py │ ├── disconnect.py │ ├── logic.py │ ├── receiver │ │ ├── disconnect.py │ │ ├── document.py │ │ ├── multiple.py │ │ ├── multiple_receivers.py │ │ ├── post_multiple.py │ │ └── post_save.py │ └── register.py └── tips │ ├── connection.py │ ├── lru.py │ ├── models.py │ └── settings.py ├── mongoz ├── __init__.py ├── conf │ ├── __init__.py │ ├── functional.py │ ├── global_settings.py │ └── module_import.py ├── core │ ├── __init__.py │ ├── connection │ │ ├── __init__.py │ │ ├── collections.py │ │ ├── database.py │ │ └── registry.py │ ├── db │ │ ├── __init__.py │ │ ├── datastructures.py │ │ ├── documents │ │ │ ├── __init__.py │ │ │ ├── _internal.py │ │ │ ├── base.py │ │ │ ├── document.py │ │ │ ├── document_proxy.py │ │ │ ├── document_row.py │ │ │ ├── managers.py │ │ │ └── metaclasses.py │ │ ├── fields │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ └── core.py │ │ └── querysets │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ ├── core │ │ │ ├── constants.py │ │ │ ├── manager.py │ │ │ ├── protocols.py │ │ │ └── queryset.py │ │ │ ├── expressions.py │ │ │ └── operators.py │ ├── signals │ │ ├── __init__.py │ │ ├── handlers.py │ │ └── signal.py │ └── utils │ │ ├── __init__.py │ │ ├── documents.py │ │ ├── functional.py │ │ ├── hashable.py │ │ └── sync.py ├── exceptions.py ├── protocols │ ├── __init__.py │ └── queryset.py ├── py.typed ├── types.py └── utils │ ├── __init__.py │ ├── enums.py │ ├── inspect.py │ └── mixins.py ├── pyproject.toml ├── scripts ├── clean ├── docs.py ├── hooks.py ├── install └── publish └── tests ├── conftest.py ├── databases └── test_database.py ├── embedded_documents ├── test_embedded_doc_decimal.py ├── test_embedded_documents.py └── test_nest.py ├── indexes ├── conftest.py ├── test_indexes.py ├── test_indexes_auto_generate_true.py ├── test_indexes_drop_indexes.py ├── test_indexes_duplicate_error.py ├── test_indexes_from_field.py ├── test_indexes_raise_error.py ├── test_unique.py └── test_using_different_dbs.py ├── inheritance ├── test_abs_override.py ├── test_abstract_class.py ├── test_inheritance.py ├── test_inheritance_non_abstract.py └── test_inherited_client_and_database.py ├── integrations ├── test_esmerald.py └── test_lilya.py ├── models ├── manager │ ├── __init__.py │ ├── test_all.py │ ├── test_array_and_array_field.py │ ├── test_array_insert.py │ ├── test_boolean_default.py │ ├── test_bulk_update.py │ ├── test_choice_field.py │ ├── test_count.py │ ├── test_create.py │ ├── test_create_many.py │ ├── test_create_on_save.py │ ├── test_custom_query_operators.py │ ├── test_decimal.py │ ├── test_decimal_update.py │ ├── test_defer.py │ ├── test_delete.py │ ├── test_distinct_values.py │ ├── test_exclude.py │ ├── test_exists.py │ ├── test_filter.py │ ├── test_first.py │ ├── test_foreign_field.py │ ├── test_get.py │ ├── test_get_by_id.py │ ├── test_get_or_create.py │ ├── test_get_or_none.py │ ├── test_last.py │ ├── test_limit.py │ ├── test_none.py │ ├── test_object_id_null.py │ ├── test_only.py │ ├── test_query_builder.py │ ├── test_raw_queries.py │ ├── test_skip.py │ ├── test_sort.py │ ├── test_sort_three.py │ ├── test_sort_two.py │ ├── test_update_and_save.py │ ├── test_using.py │ ├── test_values.py │ ├── test_values_list.py │ └── test_where.py └── querysets │ ├── test_all.py │ ├── test_array_and_array_field.py │ ├── test_bulk_update.py │ ├── test_count.py │ ├── test_create.py │ ├── test_create_many.py │ ├── test_create_on_save.py │ ├── test_custom_query_operators.py │ ├── test_defer.py │ ├── test_delete.py │ ├── test_distinct_values.py │ ├── test_first.py │ ├── test_get.py │ ├── test_get_by_id.py │ ├── test_get_or_create.py │ ├── test_get_or_none.py │ ├── test_last.py │ ├── test_limit.py │ ├── test_models.py │ ├── test_none.py │ ├── test_only.py │ ├── test_query_builder.py │ ├── test_raw_queries.py │ ├── test_skip.py │ ├── test_sort.py │ ├── test_update_and_save.py │ ├── test_values.py │ ├── test_values_list.py │ └── test_where.py ├── registry └── test_registry.py ├── settings.py ├── signals └── test_signals.py ├── test_collections.py ├── test_managers.py └── test_meta_errors.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [tarsil] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue 3 | about: Please only raise an issue if you've been advised to do so after discussion. Much appreciated! 🙏 4 | --- 5 | 6 | The starting point for issues should usually be a discussion... 7 | 8 | https://github.com/dymmond/mongoz/discussions 9 | 10 | Potential bugs may be raised as a "Potential Issue" discussion. The feature requests may be raised as an 11 | "Ideas" discussion. 12 | 13 | We can then decide if the discussion needs to be escalated into an "Issue" or not. 14 | 15 | This will make sure that everything is organised properly. 16 | --- 17 | 18 | **Esmerald version**: 19 | **Python version**: 20 | **OS**: 21 | **Platform**: 22 | 23 | 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser 2 | blank_issues_enabled: false 3 | contact_links: 4 | - name: Discussions 5 | url: https://github.com/dymmond/mongoz/discussions 6 | about: > 7 | The "Discussions" forum is where you want to start. 8 | -------------------------------------------------------------------------------- /.github/dependbot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | commit-message: 8 | prefix: ⬆ 9 | - package-ecosystem: "pip" 10 | directory: "/" 11 | schedule: 12 | interval: "daily" 13 | commit-message: 14 | prefix: ⬆ 15 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Publish 3 | 4 | on: 5 | push: 6 | tags: 7 | - '*' 8 | 9 | jobs: 10 | publish: 11 | name: "Publish release" 12 | runs-on: "ubuntu-latest" 13 | 14 | steps: 15 | - uses: "actions/checkout@v4" 16 | - uses: "actions/setup-python@v5" 17 | with: 18 | python-version: 3.8 19 | - name: "Install dependencies" 20 | if: steps.cache.outputs.cache-hit != 'true' 21 | run: pip install hatch 22 | - name: "Build package" 23 | run: "hatch run build_with_check" 24 | - name: "Publish to PyPI" 25 | run: "scripts/publish" 26 | env: 27 | TWINE_USERNAME: __token__ 28 | TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} 29 | - name: "Deploy docs" 30 | run: | 31 | curl -X POST '${{ secrets.DEPLOY_DOCS }}' 32 | -------------------------------------------------------------------------------- /.github/workflows/test-suite.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Test Suite 3 | 4 | on: 5 | push: 6 | branches: 7 | - "**" 8 | paths-ignore: 9 | - "docs/**" 10 | pull_request: 11 | branches: ["main"] 12 | paths-ignore: 13 | - "docs/**" 14 | schedule: 15 | - cron: "0 0 * * *" 16 | 17 | jobs: 18 | tests: 19 | name: "Python ${{ matrix.python-version }}" 20 | runs-on: "ubuntu-latest" 21 | strategy: 22 | matrix: 23 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 24 | 25 | services: 26 | mongodb: 27 | image: mongo:latest 28 | env: 29 | MONGO_INITDB_ROOT_USERNAME: root 30 | MONGO_INITDB_ROOT_PASSWORD: mongoadmin 31 | MONGO_INITDB_DATABASE: mongodb 32 | ports: 33 | - 27017:27017 34 | steps: 35 | - uses: "actions/checkout@v4" 36 | - uses: "actions/setup-python@v5" 37 | with: 38 | python-version: "${{ matrix.python-version }}" 39 | allow-prereleases: true 40 | - uses: actions/cache@v4 41 | id: cache 42 | with: 43 | path: ${{ env.pythonLocation }} 44 | key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-test-v02 45 | - name: Install UV 46 | run: | 47 | curl -LsSf https://astral.sh/uv/install.sh | sh 48 | - name: "Install dependencies" 49 | if: steps.cache.outputs.cache-hit != 'true' 50 | run: | 51 | scripts/install 52 | - name: "Run linting checks" 53 | run: "hatch run lint" 54 | - name: "Run typing checks" 55 | run: "hatch run test:check_types" 56 | - name: "Run tests" 57 | env: 58 | DATABASE_URI: "mongodb://root:mongoadmin@localhost:27017" 59 | run: "hatch run test:test" 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # folders 2 | *.egg-info/ 3 | .hypothesis/ 4 | .idea/ 5 | .mypy_cache/ 6 | .pytest_cache/ 7 | .scannerwork/ 8 | .tox/ 9 | .venv/ 10 | .vscode/ 11 | __pycache__/ 12 | virtualenv/ 13 | build/ 14 | dist/ 15 | node_modules/ 16 | results/ 17 | site/ 18 | site_lang/ 19 | target/ 20 | 21 | # files 22 | **/*.so 23 | **/*.sqlite 24 | *.iml 25 | **/*_test* 26 | .DS_Store 27 | .coverage 28 | .coverage.* 29 | .python-version 30 | coverage.* 31 | example.sqlite 32 | -------------------------------------------------------------------------------- /.pdbrc: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | alias kkk os.system('kill -9 %d' % os.getpid()) 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information. 2 | # See https://pre-commit.com/hooks.html for more hooks. 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v4.4.0 6 | hooks: 7 | - id: check-added-large-files 8 | - id: check-toml 9 | - id: check-yaml 10 | args: 11 | - --unsafe 12 | - id: end-of-file-fixer 13 | - id: debug-statements 14 | - id: trailing-whitespace 15 | - repo: https://github.com/charliermarsh/ruff-pre-commit 16 | rev: v0.3.0 17 | hooks: 18 | - id: ruff 19 | args: ["--fix", "--line-length=99"] 20 | ci: 21 | autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks 22 | autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2024, Dymmond Ltd. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /compose.overrides.yml: -------------------------------------------------------------------------------- 1 | services: 2 | mongodb: 3 | restart: always 4 | image: mongo:latest 5 | container_name: mongo 6 | environment: 7 | MONGO_INITDB_ROOT_USERNAME: root 8 | MONGO_INITDB_ROOT_PASSWORD: mongoadmin 9 | MONGO_INITDB_DATABASE: mongodb 10 | volumes: 11 | - "mongo_db_data:/data/db" 12 | expose: 13 | - 27017 14 | ports: 15 | - 27017:27017 16 | 17 | mongo-express: 18 | image: mongo-express 19 | restart: always 20 | ports: 21 | - 8081:8081 22 | environment: 23 | ME_CONFIG_MONGODB_ENABLE_ADMIN: 'true' 24 | ME_CONFIG_MONGODB_SERVER: mongodb 25 | ME_CONFIG_MONGODB_ADMINUSERNAME: root 26 | ME_CONFIG_MONGODB_ADMINPASSWORD: mongoadmin 27 | ME_CONFIG_BASICAUTH_USERNAME: admin 28 | ME_CONFIG_BASICAUTH_PASSWORD: password 29 | 30 | volumes: 31 | mongo_db_data: 32 | external: true 33 | -------------------------------------------------------------------------------- /compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | mongodb: 4 | restart: always 5 | image: mongo:latest 6 | container_name: mongo 7 | environment: 8 | MONGO_INITDB_ROOT_USERNAME: root 9 | MONGO_INITDB_ROOT_PASSWORD: mongoadmin 10 | MONGO_INITDB_DATABASE: mongodb 11 | expose: 12 | - 27017 13 | ports: 14 | - 27017:27017 15 | 16 | mongo-express: 17 | image: mongo-express 18 | restart: always 19 | ports: 20 | - 8081:8081 21 | environment: 22 | ME_CONFIG_MONGODB_ENABLE_ADMIN: 'true' 23 | ME_CONFIG_MONGODB_SERVER: mongodb 24 | ME_CONFIG_MONGODB_ADMINUSERNAME: root 25 | ME_CONFIG_MONGODB_ADMINPASSWORD: mongoadmin 26 | ME_CONFIG_BASICAUTH_USERNAME: admin 27 | ME_CONFIG_BASICAUTH_PASSWORD: password 28 | -------------------------------------------------------------------------------- /docs/en/docs/embedded-documents.md: -------------------------------------------------------------------------------- 1 | # Embedded documents 2 | 3 | Embedded documents are document to be embedded in `mongoz.Document`. The difference between one and 4 | the other is that the `EmbeddedDocument` are not inserted separately and do not have a separate 5 | `_id`. 6 | 7 | To define an `EmbeddedDocument` you should inherit from `mongoz.EmbeddedDocument` and define the 8 | [fields](./fields.md) in the way you would define for any other `mongoz.Document`. 9 | 10 | ```python hl_lines="4 10 14-15 19 23 28 30-31" 11 | {!> ../../../docs_src/documents/embed.py !} 12 | ``` 13 | 14 | As you can see, the `EmbeddedDocument` is not a standlone document itself but part of the 15 | [document](./documents.md) when declaring. 16 | 17 | An `EmbeddedDocument` canm be used inside another and therefore creating a nested declaration 18 | when saving the results. 19 | -------------------------------------------------------------------------------- /docs/en/docs/exceptions.md: -------------------------------------------------------------------------------- 1 | # Exceptions 2 | 3 | All **Mongoz** custom exceptions derive from the base `MongozException`. 4 | 5 | ## DocumentNotFound 6 | 7 | Raised when querying a document instance and it does not exist. 8 | 9 | ```python 10 | from mongoz.exceptions import DocumentNotFound 11 | ``` 12 | 13 | Or simply: 14 | 15 | ```python 16 | from mongoz import DocumentNotFound 17 | ``` 18 | 19 | ## MultipleDocumentsReturned 20 | 21 | Raised when querying a document and returns multiple results for the given query result. 22 | 23 | ```python 24 | from mongoz.exceptions import MultipleDocumentsReturned 25 | ``` 26 | 27 | Or simply: 28 | 29 | ```python 30 | from mongoz import MultipleDocumentsReturned 31 | ``` 32 | 33 | ## AbstractDocumentError 34 | 35 | Raised when an abstract document `abstract=True` is trying to be saved. 36 | 37 | ```python 38 | from mongoz.exceptions import AbstractDocumentError 39 | ``` 40 | 41 | ## ImproperlyConfigured 42 | 43 | Raised when misconfiguration in the models and metaclass is passed. 44 | 45 | ```python 46 | from mongoz.exceptions import ImproperlyConfigured 47 | ``` 48 | 49 | Or simply: 50 | 51 | ```python 52 | from mongoz import ImproperlyConfigured 53 | ``` 54 | 55 | ## IndexError 56 | 57 | Raised when there is a misconfiguration with the indexes. 58 | 59 | ```python 60 | from mongoz.exceptions import IndexError 61 | ``` 62 | 63 | ## FieldDefinitionError 64 | 65 | Raised when there is a misconfiguration with the definition of the fields. 66 | 67 | ```python 68 | from mongoz.exceptions import FieldDefinitionError 69 | ``` 70 | 71 | ## SignalError 72 | 73 | Raised when there is a misconfiguration with the document signals. 74 | 75 | ```python 76 | from mongoz.exceptions import SignalError 77 | ``` 78 | -------------------------------------------------------------------------------- /docs/en/docs/managers.md: -------------------------------------------------------------------------------- 1 | # Managers 2 | 3 | The managers are a great tool that **Mongoz** offers. Heavily inspired by [Edgy](https://edgy.tarsild.io), the managers 4 | allow you to build unique tailored queries ready to be used by your documents. 5 | 6 | **Mongoz** by default uses the the manager called `objects` which it makes it simple to understand. 7 | 8 | Let us see an example. 9 | 10 | ```python 11 | {!> ../../../docs_src/managers/simple.py !} 12 | ``` 13 | 14 | When querying the `User` table, the `objects` (manager) is the default and **should** be always 15 | presented when doing it so. 16 | 17 | !!! Danger 18 | This is only applied to the `manager` side of Mongoz, for example, when using 19 | `Document.objects....`. **When using `Document.query...` it will not work**. 20 | 21 | ### Custom manager 22 | 23 | It is also possible to have your own custom managers and to do it so, you **should inherit** 24 | the **QuerySetManager** class and override the `get_queryset()`. 25 | 26 | For those familiar with Django managers, the principle is exactly the same. 😀 27 | 28 | **The managers must be type annotated ClassVar** or an error be raised. 29 | 30 | ```python 31 | {!> ../../../docs_src/managers/example.py !} 32 | ``` 33 | 34 | Let us now create new manager and use it with our previous example. 35 | 36 | ```python 37 | {!> ../../../docs_src/managers/custom.py !} 38 | ``` 39 | 40 | These managers can be as complex as you like with as many filters as you desire. What you need is 41 | simply override the `get_queryset()` and add it to your documents. 42 | 43 | ### Override the default manager 44 | 45 | Overriding the default manager is also possible by creating the custom manager and overriding 46 | the `objects` manager. 47 | 48 | ```python 49 | {!> ../../../docs_src/managers/override.py !} 50 | ``` 51 | 52 | !!! Warning 53 | Be careful when overriding the default manager as you might not get all the results from the 54 | `.all()` if you don't filter properly. 55 | -------------------------------------------------------------------------------- /docs/en/docs/registry.md: -------------------------------------------------------------------------------- 1 | # Registry 2 | 3 | When using the **Mongoz**, you must use the **Registry** object to tell exactly where the 4 | database is going to be. 5 | 6 | Imagine the registry as a mapping between your documents and the database where is going to be written. 7 | 8 | And is just that, nothing else and very simple but effective object. 9 | 10 | The registry is also the object that you might want to use when generating migrations using 11 | Alembic. 12 | 13 | ```python hl_lines="19" 14 | {!> ../../../docs_src/registry/model.py !} 15 | ``` 16 | 17 | ## Parameters 18 | 19 | * **url** - The database URL to connect to. 20 | 21 | ```python 22 | from mongoz import Registry 23 | 24 | registry = Registry(url="mongodb://localhost:27017") 25 | ``` 26 | 27 | ## Custom registry 28 | 29 | Can you have your own custom Registry? Yes, of course! You simply need to subclass the `Registry` 30 | class and continue from there like any other python class. 31 | 32 | ```python 33 | {!> ../../../docs_src/registry/custom_registry.py !} 34 | ``` 35 | 36 | ## Run some document checks 37 | 38 | Sometimes you might want to make sure that all the documents have the indexes up to date beforehand. This 39 | can be particularly useful if you already have a document and some indexes or were updated, added or removed. This 40 | functionality runs those checks for all the documents of the given registry. 41 | 42 | ```python 43 | {!> ../../../docs_src/registry/document_checks.py !} 44 | ``` 45 | 46 | ### Using within a framework 47 | 48 | This functionality can be useful to be also plugged if you use, for example, an ASGI Framework such as Starlette, 49 | [Lilya](https://lilya.dev) or [Esmerald](https://esmerald.dev). 50 | 51 | These frameworks handle the event lifecycle for you and this is where you want to make sure these checks are run beforehand. 52 | 53 | Since Mongoz is from the same team as [Lilya](https://lilya.dev) and [Esmerald](https://esmerald.dev), let us see how it 54 | would look like with Esmerald. 55 | 56 | ```python 57 | {!> ../../../docs_src/registry/asgi_fw.py !} 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/en/docs/settings.md: -------------------------------------------------------------------------------- 1 | # Settings 2 | 3 | Who never had that feeling that sometimes haing some database settings would be nice? Well, since 4 | Mongoz is from the same author of [Esmerald][esmerald] and since [Esmerald][esmerald] is [settings][esmerald_settings] oriented, why not apply 5 | the same principle but in a simpler manner but to Mongoz? 6 | 7 | This is exactly what happened. 8 | 9 | ## Mongoz Settings Module 10 | 11 | The way of using the settings object within a Mongoz use of the ORM is via: 12 | 13 | * **MONGOZ_SETTINGS_MODULE** environment variable. 14 | 15 | All the settings are **[Pydantic BaseSettings](https://pypi.org/project/pydantic-settings/)** objects which makes it easier to use and override 16 | when needed. 17 | 18 | ### MONGOZ_SETTINGS_MODULE 19 | 20 | Mongoz by default uses is looking for a `MONGOZ_SETTINGS_MODULE` environment variable to run and 21 | apply the given settings to your instance. 22 | 23 | If no `MONGOZ_SETTINGS_MODULE` is found, Mongoz then uses its own internal settings which are 24 | widely applied across the system. 25 | 26 | #### Custom settings 27 | 28 | When creating your own custom settings class, you should inherit from `MongozSettings` which is 29 | the class responsible for all internal settings of Mongoz and those can be extended and overriden 30 | with ease. 31 | 32 | Something like this: 33 | 34 | ```python title="myproject/configs/settings.py" 35 | {!> ../../../docs_src/settings/custom_settings.py !} 36 | ``` 37 | 38 | Super simple right? Yes and that is the intention. Mongoz does not have a lot of settings but 39 | has some which are used across the codebase and those can be overriden easily. 40 | 41 | !!! Danger 42 | Be careful when overriding the settings as you might break functionality. It is your own risk 43 | doing it. 44 | 45 | 46 | 47 | [esmerald_settings]: https://esmerald.dev/application/settings/ 48 | [esmerald]: https://esmerald.dev/ 49 | -------------------------------------------------------------------------------- /docs/en/docs/sponsorship.md: -------------------------------------------------------------------------------- 1 | # Help MongoZ 2 | 3 | Do you like **MongoZ** and would like to help MongoZ, other user and the author? 4 | 5 | ## ⭐ Star **MongoZ** on GitHub 6 | 7 | Giving a star to MongoZ is very simple and helps promoting the work across the developers around the world. 8 | 9 | The button is located at the top right. 10 | 11 | [https://github.com/dymmond/mongoz](https://github.com/dymmond/mongoz). 12 | 13 | This will help spreading the word about the tool and how helpful has been. 14 | 15 | ## 👀 Follow the GitHub repo 16 | 17 | Following the GitHub repo will allow you to "watch" for any new release of MongoZ and be always up to date. 18 | 19 | You can click on "***watch***" and select "***custom***" -> "***Releases***"or any other you may find particular 20 | interesting to you. 21 | 22 | ## 💬 Join the official MongoZ discord channel 23 | 24 | Our official chat is on discord, we find it very useful and free for people to discuss issues, helping and contributing 25 | in a more organised manner. 26 | 27 | MongoZ discord channel. Join us! 28 | 29 | ## 🔥 Sponsor the author 30 | 31 | The author built this framework with all of his heart and dedication and will continue to do it so but that also 32 | requires time and resources and when they are limited, the process still gets there but takes a bit longer. 33 | 34 | You can financially help and support the author though [GitHub sponsors](https://github.com/sponsors/tarsil) 35 | 36 | He can afterwards go for a coffee☕, on him, to say thanks🙏. 37 | -------------------------------------------------------------------------------- /docs/en/docs/statics/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/mongoz/31651a3112ddfb4cbbd06a0b18c26288eebd969b/docs/en/docs/statics/images/favicon.ico -------------------------------------------------------------------------------- /docs/en/docs/statics/images/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/mongoz/31651a3112ddfb4cbbd06a0b18c26288eebd969b/docs/en/docs/statics/images/white.png -------------------------------------------------------------------------------- /docs/en/overrides/assets/css/bs-theme-overrides.css: -------------------------------------------------------------------------------- 1 | :root, 2 | [data-bs-theme=light] { 3 | --bs-primary: #546d78; 4 | --bs-primary-rgb: 233, 32, 99; 5 | --bs-primary-text-emphasis: #5D0D28; 6 | --bs-primary-bg-subtle: #FBD2E0; 7 | --bs-primary-border-subtle: #F6A6C1; 8 | } 9 | 10 | .btn-primary { 11 | --bs-btn-color: #000000; 12 | --bs-btn-bg: #546d78; 13 | --bs-btn-border-color: #546d78; 14 | --bs-btn-hover-color: #000000; 15 | --bs-btn-hover-bg: #324147; 16 | --bs-btn-hover-border-color: #546d78; 17 | --bs-btn-focus-shadow-rgb: 35, 5, 15; 18 | --bs-btn-active-color: #000000; 19 | --bs-btn-active-bg: #546d78; 20 | --bs-btn-active-border-color: #546d78; 21 | --bs-btn-disabled-color: #000000; 22 | --bs-btn-disabled-bg: #546d78; 23 | --bs-btn-disabled-border-color: #546d78; 24 | } 25 | 26 | .btn-outline-primary { 27 | --bs-btn-color: #546d78; 28 | --bs-btn-border-color: #546d78; 29 | --bs-btn-focus-shadow-rgb: 233, 32, 99; 30 | --bs-btn-hover-color: #000000; 31 | --bs-btn-hover-bg: #546d78; 32 | --bs-btn-hover-border-color: #546d78; 33 | --bs-btn-active-color: #000000; 34 | --bs-btn-active-bg: #546d78; 35 | --bs-btn-active-border-color: #546d78; 36 | --bs-btn-disabled-color: #546d78; 37 | --bs-btn-disabled-bg: transparent; 38 | --bs-btn-disabled-border-color: #546d78; 39 | } 40 | -------------------------------------------------------------------------------- /docs/en/overrides/assets/css/esmerald.css: -------------------------------------------------------------------------------- 1 | .bs-icon.bs-icon-secondary { 2 | color: var(--bs-primary); 3 | background: var(--bs-primary); 4 | } 5 | 6 | .underline:after { 7 | content: ""; 8 | position: absolute; 9 | bottom: -2px; 10 | left: 0; 11 | width: 100%; 12 | height: 8px; 13 | border-radius: 5px; 14 | background: var(--bs-primary); 15 | z-index: -1; 16 | } 17 | -------------------------------------------------------------------------------- /docs/en/overrides/assets/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/mongoz/31651a3112ddfb4cbbd06a0b18c26288eebd969b/docs/en/overrides/assets/img/favicon.ico -------------------------------------------------------------------------------- /docs/en/overrides/assets/img/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/mongoz/31651a3112ddfb4cbbd06a0b18c26288eebd969b/docs/en/overrides/assets/img/white.png -------------------------------------------------------------------------------- /docs/en/overrides/assets/js/esmerald.js: -------------------------------------------------------------------------------- 1 | hljs.highlightAll(); 2 | -------------------------------------------------------------------------------- /docs/en/overrides/assets/js/startup-modern.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; // Start of use strict 3 | 4 | var mainNav = document.querySelector('#mainNav'); 5 | 6 | if (mainNav) { 7 | 8 | // Collapse Navbar 9 | var collapseNavbar = function() { 10 | 11 | var scrollTop = (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop; 12 | 13 | if (scrollTop > 100) { 14 | mainNav.classList.add("navbar-shrink"); 15 | } else { 16 | mainNav.classList.remove("navbar-shrink"); 17 | } 18 | }; 19 | // Collapse now if page is not at top 20 | collapseNavbar(); 21 | // Collapse the navbar when page is scrolled 22 | document.addEventListener("scroll", collapseNavbar); 23 | } 24 | 25 | })(); // End of use strict 26 | -------------------------------------------------------------------------------- /docs/en/overrides/nav.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} {% block outdated %} You're not viewing the latest 2 | version. 3 | 4 | Click here to go to latest. 5 | 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /docs/missing-translation.md: -------------------------------------------------------------------------------- 1 | !!! warning 2 | The current page still doesn't have a translation for this language. 3 | 4 | But you can help translating it: [Contributing](https://esmerald.dev/contributing/#documentation){.internal-link target=_blank}. 5 | -------------------------------------------------------------------------------- /docs/pt/docs/embedded-documents.md: -------------------------------------------------------------------------------- 1 | # Documentos incorporados 2 | 3 | Documentos incorporados são documentos que são incorporados no `mongoz.Document`. A diferença entre um e 4 | outro é que o `EmbeddedDocument` não é inserido separadamente e não possui um `_id` separado. 5 | 6 | Para definir um `EmbeddedDocument`, deve herdar de `mongoz.EmbeddedDocument` e definir os 7 | [campos](./fields.md) da mesma forma que definiria para qualquer outro `mongoz.Document`. 8 | 9 | ```python hl_lines="4 10 14-15 19 23 28 30-31" 10 | {!> ../../../docs_src/documents/embed.py !} 11 | ``` 12 | 13 | Como pode ver, o `EmbeddedDocument` não é um documento independente em si, mas parte do 14 | [documento](./documents.md) ao declarar. 15 | 16 | Um `EmbeddedDocument` pode ser usado dentro de outro e, portanto, criar uma declaração *nested* 17 | ao guardar os resultados. 18 | -------------------------------------------------------------------------------- /docs/pt/docs/exceptions.md: -------------------------------------------------------------------------------- 1 | # Excepções 2 | 3 | Todas as exceções personalizadas do **Mongoz** derivam da base `MongozException`. 4 | 5 | ## DocumentNotFound 6 | 7 | Lançada quando uma instância de documento é pesquisado e não existe. 8 | 9 | ```python 10 | from mongoz.exceptions import DocumentNotFound 11 | ``` 12 | 13 | Ou simplesmente: 14 | 15 | ```python 16 | from mongoz import DocumentNotFound 17 | ``` 18 | 19 | ## MultipleDocumentsReturned 20 | 21 | Lançada quando uma consulta a um documento retorna vários resultados para o resultado da pequisa fornecida. 22 | 23 | ```python 24 | from mongoz.exceptions import MultipleDocumentsReturned 25 | ``` 26 | 27 | Ou simplesmente: 28 | 29 | ```python 30 | from mongoz import MultipleDocumentsReturned 31 | ``` 32 | 33 | ## AbstractDocumentError 34 | 35 | Lançada quando um documento abstrato `abstract=True` está a tentar ser guardado. 36 | 37 | ```python 38 | from mongoz.exceptions import AbstractDocumentError 39 | ``` 40 | 41 | ## ImproperlyConfigured 42 | 43 | Lançada quando há uma má configuração nos documentos e metaclasses. 44 | 45 | ```python 46 | from mongoz.exceptions import ImproperlyConfigured 47 | ``` 48 | 49 | Ou simplesmente: 50 | 51 | ```python 52 | from mongoz import ImproperlyConfigured 53 | ``` 54 | 55 | ## IndexError 56 | 57 | Lançada quando há uma má configuração nos índices. 58 | 59 | ```python 60 | from mongoz.exceptions import IndexError 61 | ``` 62 | 63 | ## FieldDefinitionError 64 | 65 | Lançada quando há uma má configuração na definição dos campos. 66 | 67 | ```python 68 | from mongoz.exceptions import FieldDefinitionError 69 | ``` 70 | 71 | ## SignalError 72 | 73 | Lançada quando há uma má configuração nos sinais do documento. 74 | 75 | ```python 76 | from mongoz.exceptions import SignalError 77 | ``` 78 | -------------------------------------------------------------------------------- /docs/pt/docs/managers.md: -------------------------------------------------------------------------------- 1 | # Managers 2 | 3 | Os gestores são uma ótima ferramenta que o **Mongoz** oferece. Fortemente inspirados pelo [Edgy](https://edgy.tarsild.io), os gestores permitem que construa pesquisas personalizadas prontas para serem usadas pelos documentos. 4 | 5 | O **Mongoz** por defeito usa o gestor chamado `objects`, o que torna simples de perceber. 6 | 7 | Vamos ver um exemplo. 8 | 9 | ```python 10 | {!> ../../../docs_src/managers/simple.py !} 11 | ``` 12 | 13 | Ao consultar a tabela `User`, o `objects` (gestor) é o padrão e **deve** estar sempre presente ao fazê-lo. 14 | 15 | !!! Danger 16 | Isto aplica-se apenas ao lado do `manager` do Mongoz, por exemplo, ao usar `Document.objects....`. **Ao usar `Document.query...` isto não funcionará**. 17 | 18 | ### Gestor personalizado 19 | 20 | Também é possível ter os próprios gestores personalizados e para fazer isso, **deve herdar** 21 | a classe **QuerySetManager** e substituir o `get_queryset()`. 22 | 23 | Para aqueles familiarizados com os gestores do Django, o princípio é exatamente o mesmo. 😀 24 | 25 | **Os gestores devem ser anotados como ClassVar** ou um erro será lançado. 26 | 27 | ```python 28 | {!> ../../../docs_src/managers/example.py !} 29 | ``` 30 | 31 | Agora vamos criar um novo gestor e usá-lo com nosso exemplo anterior. 32 | 33 | ```python 34 | {!> ../../../docs_src/managers/custom.py !} 35 | ``` 36 | 37 | Estes gestores podem ser tão complexos quanto desejar, com quantos filtros desejar. O que precisa fazer é 38 | simplesmente substituir o `get_queryset()` e adicioná-lo aos seus documentos. 39 | 40 | ### Substituir o gestor padrão 41 | 42 | Também é possível substituir o gestor padrão criando o gestor personalizado e substituindo 43 | o gestor `objects`. 44 | 45 | ```python 46 | {!> ../../../docs_src/managers/override.py !} 47 | ``` 48 | 49 | !!! Warning 50 | Tenha cuidado ao substituir o gestor padrão, pois pode não obter todos os resultados do 51 | `.all()` se não filtrar correctamente. 52 | -------------------------------------------------------------------------------- /docs/pt/docs/registry.md: -------------------------------------------------------------------------------- 1 | # Registo 2 | 3 | Ao utilizar o **Mongoz**, é necessário utilizar o objeto **Registo** para indicar exatamente onde a base de dados será escrita. 4 | 5 | Imagine o registo como um mapeamento entre os seus documentos e a base de dados onde serão escritos. 6 | 7 | E é apenas isso, nada mais, um objeto muito simples mas eficaz. 8 | 9 | O registo também é o objeto que pode ser utilizado ao gerar migrações utilizando o Alembic. 10 | 11 | ```python hl_lines="19" 12 | {!> ../../../docs_src/registry/model.py !} 13 | ``` 14 | 15 | ## Parâmetros 16 | 17 | * **url** - O URL da base de dados para se conectar. 18 | 19 | ```python 20 | from mongoz import Registo 21 | 22 | registo = Registo(url="mongodb://localhost:27017") 23 | ``` 24 | 25 | ## Registo personalizado 26 | 27 | É possível ter o seu próprio Registo personalizado? Sim, claro! Basta criar uma subclasse da classe `Registry` e continuar a partir daí, como qualquer outra classe Python. 28 | 29 | ```python 30 | {!> ../../../docs_src/registry/custom_registry.py !} 31 | ``` 32 | 33 | ## Executar algumas verificações de documentos 34 | 35 | Por vezes, pode ser útil garantir que todos os documentos têm os índices atualizados antecipadamente. Isso pode ser especialmente útil se já tiver um documento e alguns índices foram atualizados, adicionados ou removidos. Esta funcionalidade executa essas verificações para todos os documentos do registo fornecido. 36 | 37 | ```python 38 | {!> ../../../docs_src/registry/document_checks.py !} 39 | ``` 40 | 41 | ### Utilização numa framework 42 | 43 | Esta funcionalidade também pode ser útil se estiver a utilizar, por exemplo, uma framework ASGI como o Starlette, [Lilya](https://lilya.dev) ou [Esmerald](https://esmerald.dev). 44 | 45 | Estas frameworks tratam do ciclo de vida do evento para si e é aqui que deseja garantir que essas verificações são executadas antecipadamente. 46 | 47 | Como o Mongoz é da mesma equipa que o [Lilya](https://lilya.dev) e o [Esmerald](https://esmerald.dev), vamos ver como ficaria com o Esmerald. 48 | 49 | ```python 50 | {!> ../../../docs_src/registry/asgi_fw.py !} 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/pt/docs/settings.md: -------------------------------------------------------------------------------- 1 | # Definições 2 | 3 | Quem nunca teve aquela sensação de que às vezes seria bom ter algumas configurações de base de dados? Bem, como o Mongoz é do mesmo autor do [Esmerald][esmerald] e como o [Esmerald][esmerald] é orientado a [configurações][esmerald_settings], por que não aplicar o mesmo princípio, mas de maneira mais simples, ao Mongoz? 4 | 5 | Foi exatamente isso que aconteceu. 6 | 7 | ## Módulo de Configurações do Mongoz 8 | 9 | A forma de usar o objeto de configurações no ORM do Mongoz é através de: 10 | 11 | * **MONGOZ_SETTINGS_MODULE** variável de ambiente. 12 | 13 | Todas as configurações são objetos **[Pydantic BaseSettings](https://pypi.org/project/pydantic-settings/)**, o que torna mais fácil de usar e substituir quando necessário. 14 | 15 | ### MONGOZ_SETTINGS_MODULE 16 | 17 | Por padrão, o Mongoz procura por uma variável de ambiente `MONGOZ_SETTINGS_MODULE` para executar e aplicar as configurações fornecidas à sua instância. 18 | 19 | Se nenhuma `MONGOZ_SETTINGS_MODULE` for encontrada, o Mongoz usa as suas próprias configurações internas, que são amplamente aplicadas em todo o sistema. 20 | 21 | #### Configurações personalizadas 22 | 23 | Ao criar sua própria classe de configurações personalizadas, deve herdar de `MongozSettings`, que é a classe responsável por todas as configurações internas do Mongoz e que podem ser estendidas e substituídas com facilidade. 24 | 25 | Algo como isto: 26 | 27 | ```python title="myproject/configs/settings.py" 28 | {!> ../../../docs_src/settings/custom_settings.py !} 29 | ``` 30 | 31 | Super simples, certo? Sim, e essa é a intenção. O Mongoz não tem muitas configurações, mas tem algumas que são usadas em todo o código e que podem ser facilmente substituídas. 32 | 33 | !!! Danger 34 | Tenha cuidado ao substituir as configurações, pois pode quebrar a funcionalidade. É por sua conta e risco. 35 | 36 | [esmerald_settings]: https://esmerald.dev/application/settings/ 37 | [esmerald]: https://esmerald.dev/ 38 | -------------------------------------------------------------------------------- /docs/pt/docs/sponsorship.md: -------------------------------------------------------------------------------- 1 | # Ajude 2 | 3 | Gosta do **Mongoz** e gostaria de ajudar Mongoz, outros utilizadores e o autor? 4 | 5 | ## ⭐ Adicione uma estrela ao **Mongoz** no GitHub 6 | 7 | Dar uma estrela ao Mongoz é muito simples e ajuda a promover o trabalho entre os programadores de todo o mundo. 8 | 9 | O botão está localizado no canto superior direito. 10 | 11 | [https://github.com/dymmond/mongoz](https://github.com/dymmond/mongoz). 12 | 13 | Isto ajudará a divulgar a ferramenta e o quão útil tem sido. 14 | 15 | ## 👀 Siga o repositório do GitHub 16 | 17 | Seguir o repositório do GitHub permitirá que "observe" qualquer nova versão do Mongoz e esteja sempre atualizado. 18 | 19 | Você pode clicar em "***watch***" e seleccionar "***custom***" -> "***Releases***" ou qualquer outro que você ache interessante. 20 | 21 | ## 💬 Junte-se ao canal oficial do Mongoz no Discord 22 | 23 | O nosso chat oficial está no Discord, achamos que é muito útil e gratuito para as pessoas discutirem problemas, ajudarem e contribuírem de forma mais organizada. 24 | 25 | Canal do Mongoz no Discord. Junte-se a nós! 🗸 26 | 27 | ## 🔥 Patrocine o autor 28 | 29 | O autor construiu esta framework com todo o seu coração e dedicação e continuará a fazê-lo, mas isso também requer tempo e recursos e quando eles são limitados, o processo ainda acontece, mas leva um pouco mais de tempo. 30 | 31 | Pode ajudar financeiramente e apoiar o autor através dos [patrocinadores do GitHub](https://github.com/sponsors/tarsil) 32 | 33 | Ele pode mais tarde tomar um café☕, por conta dele, para agradecer🙏. 34 | -------------------------------------------------------------------------------- /docs/pt/mkdocs.yml: -------------------------------------------------------------------------------- 1 | INHERIT: ../en/mkdocs.yml 2 | site_dir: '../../site_lang/pt' 3 | -------------------------------------------------------------------------------- /docs_src/documents/abstract/common.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | import mongoz 4 | 5 | database_uri = "mongodb://localhost:27017" 6 | registry = mongoz.Registry(database_uri) 7 | 8 | 9 | class BaseDocument(mongoz.Document): 10 | name: str = mongoz.String(max_length=255) 11 | 12 | class Meta: 13 | abstract = True 14 | registry = registry 15 | database = "my_db" 16 | 17 | def get_description(self): 18 | """ 19 | Returns the description of a record 20 | """ 21 | return getattr(self, "description", None) 22 | 23 | 24 | class User(BaseDocument): 25 | """ 26 | Inheriting the fields from the abstract class 27 | as well as the Meta data. 28 | """ 29 | 30 | phone_number: str = mongoz.String(max_length=15) 31 | description: str = mongoz.String() 32 | 33 | def transform_phone_number(self): 34 | # logic here for the phone number 35 | ... 36 | 37 | 38 | class Product(BaseDocument): 39 | """ 40 | Inheriting the fields from the abstract class 41 | as well as the Meta data. 42 | """ 43 | 44 | sku: str = mongoz.String(max_length=255) 45 | description: str = mongoz.String() 46 | 47 | def get_sku(self): 48 | # Logic to obtain the SKU 49 | ... 50 | -------------------------------------------------------------------------------- /docs_src/documents/abstract/simple.py: -------------------------------------------------------------------------------- 1 | import mongoz 2 | 3 | database_uri = "mongodb://localhost:27017" 4 | registry = mongoz.Registry(database_uri) 5 | 6 | 7 | class BaseDocument(mongoz.Document): 8 | class Meta: 9 | abstract = True 10 | registry = registry 11 | database = "my_db" 12 | -------------------------------------------------------------------------------- /docs_src/documents/declaring_models.py: -------------------------------------------------------------------------------- 1 | import mongoz 2 | 3 | database_uri = "mongodb://localhost:27017" 4 | registry = mongoz.Registry(database_uri) 5 | 6 | 7 | class User(mongoz.Document): 8 | name: str = mongoz.String(max_length=255) 9 | age: int = mongoz.Integer() 10 | is_active: bool = mongoz.Boolean(default=True) 11 | 12 | class Meta: 13 | registry = registry 14 | database = "my_db" 15 | -------------------------------------------------------------------------------- /docs_src/documents/default_model.py: -------------------------------------------------------------------------------- 1 | import mongoz 2 | 3 | database_uri = "mongodb://localhost:27017" 4 | registry = mongoz.Registry(database_uri) 5 | 6 | 7 | class User(mongoz.Document): 8 | name: str = mongoz.String(max_length=255) 9 | age: int = mongoz.Integer() 10 | is_active: bool = mongoz.Boolean(default=True) 11 | 12 | class Meta: 13 | registry = registry 14 | database = "my_db" 15 | -------------------------------------------------------------------------------- /docs_src/documents/embed.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import mongoz 4 | from mongoz import Document, EmbeddedDocument 5 | 6 | database_uri = "mongodb://localhost:27017" 7 | registry = mongoz.Registry(database_uri) 8 | 9 | 10 | class Award(EmbeddedDocument): 11 | name: str = mongoz.String() 12 | 13 | 14 | class Crew(EmbeddedDocument): 15 | award: Award = mongoz.Embed(Award) 16 | name: str = mongoz.String() 17 | 18 | 19 | class Actor(EmbeddedDocument): 20 | name: str = mongoz.String() 21 | 22 | 23 | class Genre(EmbeddedDocument): 24 | title: str = mongoz.String() 25 | 26 | 27 | class Movie(Document): 28 | actors: List[Actor] = mongoz.Array(Actor) 29 | name: str = mongoz.String() 30 | director: Crew = mongoz.Embed(Crew) 31 | genre: Genre = mongoz.Embed(Genre) 32 | year: int = mongoz.Integer() 33 | 34 | class Meta: 35 | registry = registry 36 | database = "my_db" 37 | -------------------------------------------------------------------------------- /docs_src/documents/indexes/complex_together.py: -------------------------------------------------------------------------------- 1 | import mongoz 2 | from mongoz import Database, Index, IndexType, Order, Registry 3 | 4 | database_uri = "mongodb://localhost:27017" 5 | registry = mongoz.Registry(database_uri) 6 | 7 | 8 | class User(mongoz.Model): 9 | name: str = mongoz.String(max_length=255, index=True, unique=True) 10 | age: int = mongoz.Integer() 11 | email: str = mongoz.Email(max_length=70) 12 | is_active: bool = mongoz.Boolean(default=True) 13 | status: str = mongoz.String(max_length=255) 14 | 15 | class Meta: 16 | registry = registry 17 | database = "my_db" 18 | indexes = [ 19 | Index(keys=[("age", Order.DESCENDING), ("email", IndexType.HASHED)]), 20 | ] 21 | -------------------------------------------------------------------------------- /docs_src/documents/indexes/simple.py: -------------------------------------------------------------------------------- 1 | import mongoz 2 | from mongoz import Index 3 | 4 | database_uri = "mongodb://localhost:27017" 5 | registry = mongoz.Registry(database_uri) 6 | 7 | 8 | class User(mongoz.Model): 9 | name: str = mongoz.String(max_length=255) 10 | age: int = mongoz.Integer() 11 | email: str = mongoz.Email(max_length=70, index=True, unique=True) 12 | is_active: bool = mongoz.Boolean(default=True) 13 | status: str = mongoz.String(max_length=255) 14 | 15 | class Meta: 16 | registry = registry 17 | database = "my_db" 18 | -------------------------------------------------------------------------------- /docs_src/documents/indexes/simple2.py: -------------------------------------------------------------------------------- 1 | import mongoz 2 | from mongoz import Index 3 | 4 | database_uri = "mongodb://localhost:27017" 5 | registry = mongoz.Registry(database_uri) 6 | 7 | 8 | class User(mongoz.Model): 9 | name: str = mongoz.String(max_length=255) 10 | age: int = mongoz.Integer() 11 | email: str = mongoz.Email(max_length=70) 12 | is_active: bool = mongoz.Boolean(default=True) 13 | status: str = mongoz.String(max_length=255) 14 | 15 | class Meta: 16 | registry = registry 17 | database = "my_db" 18 | indexes = [Index("name", unique=True)] 19 | -------------------------------------------------------------------------------- /docs_src/documents/registry/inheritance_abstract.py: -------------------------------------------------------------------------------- 1 | import mongoz 2 | 3 | database_uri = "mongodb://localhost:27017" 4 | registry = mongoz.Registry(database_uri) 5 | 6 | 7 | class BaseDocument(mongoz.Document): 8 | """ 9 | The base document for all documents using the `registry` registry. 10 | """ 11 | 12 | class Meta: 13 | abstract = True 14 | registry = registry 15 | database = "my_db" 16 | 17 | 18 | class User(BaseDocument): 19 | name: str = mongoz.String(max_length=255) 20 | is_active: bool = mongoz.Boolean(default=True) 21 | 22 | 23 | class Product(BaseDocument): 24 | sku: str = mongoz.String(max_length=255, null=False) 25 | -------------------------------------------------------------------------------- /docs_src/documents/registry/inheritance_no_repeat.py: -------------------------------------------------------------------------------- 1 | import mongoz 2 | 3 | database_uri = "mongodb://localhost:27017" 4 | registry = mongoz.Registry(database_uri) 5 | 6 | 7 | class BaseDocument(mongoz.Document): 8 | """ 9 | The base document for all documents using the `registry` registry. 10 | """ 11 | 12 | class Meta: 13 | abstract = True 14 | registry = registry 15 | database = "my_db" 16 | 17 | 18 | class User(BaseDocument): 19 | name: str = mongoz.String(max_length=255) 20 | is_active: bool = mongoz.Boolean(default=True) 21 | 22 | 23 | class Product(BaseDocument): 24 | sku: str = mongoz.String(max_length=255, null=False) 25 | -------------------------------------------------------------------------------- /docs_src/documents/registry/nutshell.py: -------------------------------------------------------------------------------- 1 | import mongoz 2 | 3 | database_uri = "mongodb://localhost:27017" 4 | registry = mongoz.Registry(database_uri) 5 | 6 | 7 | class User(mongoz.Model): 8 | name: str = mongoz.String(max_length=255) 9 | is_active: bool = mongoz.Boolean(default=True) 10 | 11 | class Meta: 12 | registry = registry 13 | database = "my_db" 14 | -------------------------------------------------------------------------------- /docs_src/documents/tablename/model_diff_tn.py: -------------------------------------------------------------------------------- 1 | import mongoz 2 | 3 | database_uri = "mongodb://localhost:27017" 4 | registry = mongoz.Registry(database_uri) 5 | 6 | 7 | class User(mongoz.Document): 8 | name: str = mongoz.String(max_length=255) 9 | age: int = mongoz.Integer() 10 | is_active: bool = mongoz.Boolean(default=True) 11 | 12 | class Meta: 13 | registry = registry 14 | collection = "db_users" 15 | database = "my_db" 16 | -------------------------------------------------------------------------------- /docs_src/documents/tablename/model_no_tablename.py: -------------------------------------------------------------------------------- 1 | import mongoz 2 | 3 | database_uri = "mongodb://localhost:27017" 4 | registry = mongoz.Registry(database_uri) 5 | 6 | 7 | class User(mongoz.Document): 8 | """ 9 | If the `tablename` is not declared in the `Meta`, 10 | edgy will pluralise the class name. 11 | 12 | This table will be called in the database `users`. 13 | """ 14 | 15 | name: str = mongoz.String(max_length=255) 16 | age: int = mongoz.Integer() 17 | is_active: bool = mongoz.Boolean(default=True) 18 | 19 | class Meta: 20 | registry = registry 21 | database = "my_db" 22 | -------------------------------------------------------------------------------- /docs_src/documents/tablename/model_with_tablename.py: -------------------------------------------------------------------------------- 1 | import mongoz 2 | 3 | database_uri = "mongodb://localhost:27017" 4 | registry = mongoz.Registry(database_uri) 5 | 6 | 7 | class User(mongoz.Document): 8 | name: str = mongoz.String(max_length=255) 9 | age: int = mongoz.Integer() 10 | is_active: bool = mongoz.Boolean(default=True) 11 | 12 | class Meta: 13 | registry = registry 14 | collection = "users" 15 | database = "my_db" 16 | -------------------------------------------------------------------------------- /docs_src/managers/custom.py: -------------------------------------------------------------------------------- 1 | from typing import ClassVar 2 | 3 | import mongoz 4 | from mongoz import QuerySetManager 5 | 6 | database_uri = "mongodb://localhost:27017" 7 | registry = mongoz.Registry(database_uri) 8 | 9 | 10 | class InactiveManager(QuerySetManager): 11 | """ 12 | Custom manager that will return only active users 13 | """ 14 | 15 | def get_queryset(self) -> "QuerySetManager": 16 | queryset = super().get_queryset().filter(is_active=False) 17 | return queryset 18 | 19 | 20 | class User(mongoz.Document): 21 | # Add the new manager 22 | inactives: ClassVar[QuerySetManager] = InactiveManager() 23 | 24 | name: str = mongoz.String(max_length=255) 25 | email: str = mongoz.Email(max_length=70) 26 | is_active: bool = mongoz.Boolean(default=True) 27 | 28 | 29 | # Create an inactive user 30 | await User.objects.create(name="Edgy", email="foo@bar.com", is_active=False) # noqa 31 | 32 | # You can also create a user using the new manager 33 | await User.inactives.create(name="Another Edgy", email="bar@foo.com", is_active=False) # noqa 34 | 35 | # Querying using the new manager 36 | user = await User.inactives.get(email="foo@bar.com") # noqa 37 | # User(ObjectId(...)) 38 | 39 | user = await User.inactives.get(email="bar@foo.com") # noqa 40 | # User(ObjectId(...)) 41 | 42 | # Create a user using the default manager 43 | await User.objects.create(name="Edgy", email="user@edgy.com") # noqa 44 | 45 | # Querying all inactives only 46 | users = await User.inactives.all() # noqa 47 | # [User(ObjectId(...)), User(ObjectId(...))] 48 | 49 | # Querying them all 50 | user = await User.objects.all() # noqa 51 | # [User(ObjectId(...)), User(ObjectId(...)), User(ObjectId(...))] 52 | -------------------------------------------------------------------------------- /docs_src/managers/example.py: -------------------------------------------------------------------------------- 1 | from typing import ClassVar 2 | 3 | import mongoz 4 | from mongoz import QuerySetManager 5 | 6 | database_uri = "mongodb://localhost:27017" 7 | registry = mongoz.Registry(database_uri) 8 | 9 | 10 | class InactiveManager(QuerySetManager): 11 | """ 12 | Custom manager that will return only active users 13 | """ 14 | 15 | def get_queryset(self) -> "QuerySetManager": 16 | queryset = super().get_queryset().filter(is_active=False) 17 | return queryset 18 | 19 | 20 | class User(mongoz.Document): 21 | # Add the new manager 22 | inactives: ClassVar[QuerySetManager] = InactiveManager() 23 | -------------------------------------------------------------------------------- /docs_src/managers/override.py: -------------------------------------------------------------------------------- 1 | from typing import ClassVar 2 | 3 | import mongoz 4 | from mongoz import QuerySetManager 5 | 6 | database_uri = "mongodb://localhost:27017" 7 | registry = mongoz.Registry(database_uri) 8 | 9 | 10 | class InactiveManager(QuerySetManager): 11 | """ 12 | Custom manager that will return only active users 13 | """ 14 | 15 | def get_queryset(self) -> "QuerySetManager": 16 | queryset = super().get_queryset().filter(is_active=False) 17 | return queryset 18 | 19 | 20 | class User(mongoz.Document): 21 | # Add the new manager 22 | objects: ClassVar[QuerySetManager] = InactiveManager() 23 | 24 | name: str = mongoz.String(max_length=255) 25 | email: str = mongoz.Email(max_length=70) 26 | is_active: bool = mongoz.Boolean(default=True) 27 | 28 | 29 | # Create an inactive user 30 | await User.objects.create(name="Edgy", email="foo@bar.com", is_active=False) # noqa 31 | 32 | # You can also create a user using the new manager 33 | await User.objects.create(name="Another Edgy", email="bar@foo.com", is_active=False) # noqa 34 | 35 | # Create a user using the default manager 36 | await User.objects.create(name="Edgy", email="user@edgy.com") # noqa 37 | 38 | # Querying them all 39 | user = await User.objects.all() # noqa 40 | # [User(ObjectId(...)), User(ObjectId(...))] 41 | -------------------------------------------------------------------------------- /docs_src/managers/simple.py: -------------------------------------------------------------------------------- 1 | import mongoz 2 | 3 | database_uri = "mongodb://localhost:27017" 4 | registry = mongoz.Registry(database_uri) 5 | 6 | 7 | class User(mongoz.Document): 8 | name: str = mongoz.String(max_length=255) 9 | email: str = mongoz.String(max_length=70) 10 | is_active: bool = mongoz.Boolean(default=True) 11 | 12 | class Meta: 13 | registry = registry 14 | database = "my_db" 15 | 16 | 17 | await User.objects.create(name="Mongoz", email="foo@bar.com") # noqa 18 | 19 | user = await User.objects.get(name="Mongoz") # noqa 20 | # User(id=ObjectId(...)) 21 | -------------------------------------------------------------------------------- /docs_src/queries/document.py: -------------------------------------------------------------------------------- 1 | import mongoz 2 | 3 | database_uri = "mongodb://localhost:27017" 4 | registry = mongoz.Registry(database_uri) 5 | 6 | 7 | class User(mongoz.Document): 8 | is_active: bool = mongoz.Boolean(default=True) 9 | first_name: str = mongoz.String(max_length=50) 10 | last_name: str = mongoz.String(max_length=50) 11 | email: str = mongoz.Email(max_lengh=100) 12 | password: str = mongoz.String(max_length=1000) 13 | 14 | class Meta: 15 | registry = registry 16 | database = "my_db" 17 | -------------------------------------------------------------------------------- /docs_src/queries/embed.py: -------------------------------------------------------------------------------- 1 | import mongoz 2 | 3 | database_uri = "mongodb://localhost:27017" 4 | registry = mongoz.Registry(database_uri) 5 | 6 | 7 | class UserType(mongoz.EmbeddedDocument): 8 | level: str = mongoz.String() 9 | 10 | 11 | class User(mongoz.Document): 12 | is_active: bool = mongoz.Boolean(default=True) 13 | first_name: str = mongoz.String(max_length=50) 14 | last_name: str = mongoz.String(max_length=50) 15 | email: str = mongoz.Email(max_lengh=100) 16 | password: str = mongoz.String(max_length=1000) 17 | user_type: UserType = mongoz.Embed(UserType) 18 | 19 | class Meta: 20 | registry = registry 21 | database = "my_db" 22 | -------------------------------------------------------------------------------- /docs_src/quickstart/quickstart.py: -------------------------------------------------------------------------------- 1 | import mongoz 2 | 3 | database_uri = "mongodb://localhost:27017" 4 | registry = mongoz.Registry(database_uri) 5 | 6 | 7 | class User(mongoz.Document): 8 | name: str = mongoz.String(max_length=255) 9 | email: str = mongoz.Email(max_length=255) 10 | is_verified: bool = mongoz.Boolean(default=False) 11 | 12 | class Meta: 13 | registry = registry 14 | database = "my_db" 15 | -------------------------------------------------------------------------------- /docs_src/registry/asgi_fw.py: -------------------------------------------------------------------------------- 1 | from contextlib import asynccontextmanager 2 | 3 | from esmerald import Esmerald 4 | 5 | import mongoz 6 | 7 | database_uri = "mongodb://localhost:27017" 8 | registry = mongoz.Registry(database_uri) 9 | 10 | 11 | class User(mongoz.Document): 12 | """ 13 | The User document to be created in the database as a table 14 | If no name is provided the in Meta class, it will generate 15 | a "users" table for you. 16 | """ 17 | 18 | is_active: bool = mongoz.Boolean(default=False) 19 | 20 | class Meta: 21 | registry = registry 22 | database = "my_db" 23 | 24 | 25 | # Declare the Esmerald instance 26 | app = Esmerald(routes=[...], on_startup=[registry.document_checks]) 27 | 28 | 29 | # Or using the lifespan 30 | @asynccontextmanager 31 | async def lifespan(app: Esmerald): 32 | # What happens on startup 33 | await registry.document_checks() 34 | yield 35 | # What happens on shutdown 36 | 37 | 38 | app = Esmerald(routes=[...], lifespan=lifespan) 39 | -------------------------------------------------------------------------------- /docs_src/registry/custom_registry.py: -------------------------------------------------------------------------------- 1 | import mongoz 2 | 3 | database_uri = "mongodb://localhost:27017" 4 | registry = mongoz.Registry(database_uri) 5 | 6 | 7 | class MyRegistry(mongoz.Registry): 8 | """ 9 | Add logic unique to your registry or override 10 | existing functionality. 11 | """ 12 | 13 | ... 14 | 15 | 16 | models = MyRegistry(database_uri) 17 | 18 | 19 | class User(mongoz.Document): 20 | """ 21 | The User document to be created in the database as a table 22 | If no name is provided the in Meta class, it will generate 23 | a "users" table for you. 24 | """ 25 | 26 | is_active: bool = mongoz.Boolean(default=False) 27 | 28 | class Meta: 29 | registry = registry 30 | database = "my_db" 31 | -------------------------------------------------------------------------------- /docs_src/registry/document_checks.py: -------------------------------------------------------------------------------- 1 | import mongoz 2 | 3 | database_uri = "mongodb://localhost:27017" 4 | registry = mongoz.Registry(database_uri) 5 | 6 | 7 | class User(mongoz.Document): 8 | """ 9 | The User document to be created in the database as a table 10 | If no name is provided the in Meta class, it will generate 11 | a "users" table for you. 12 | """ 13 | 14 | is_active: bool = mongoz.Boolean(default=False) 15 | 16 | class Meta: 17 | registry = registry 18 | database = "my_db" 19 | 20 | 21 | # Make sure the document checks are run 22 | await registry.document_checks() 23 | -------------------------------------------------------------------------------- /docs_src/registry/model.py: -------------------------------------------------------------------------------- 1 | import mongoz 2 | 3 | database_uri = "mongodb://localhost:27017" 4 | registry = mongoz.Registry(database_uri) 5 | 6 | 7 | class User(mongoz.Document): 8 | """ 9 | The User document to be created in the database as a table 10 | If no name is provided the in Meta class, it will generate 11 | a "users" table for you. 12 | """ 13 | 14 | is_active: bool = mongoz.Boolean(default=False) 15 | 16 | class Meta: 17 | registry = registry 18 | database = "my_db" 19 | -------------------------------------------------------------------------------- /docs_src/settings/custom_settings.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from mongoz import MongozSettings 4 | 5 | 6 | class MyCustomSettings(MongozSettings): 7 | """ 8 | My settings overriding default values and add new ones. 9 | """ 10 | 11 | parsed_ids: List[str] = ["id", "pk"] 12 | -------------------------------------------------------------------------------- /docs_src/signals/custom.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import mongoz 4 | 5 | database_uri = "mongodb://localhost:27017" 6 | registry = mongoz.Registry(database_uri) 7 | 8 | 9 | class User(mongoz.Document): 10 | name: str = mongoz.String(max_length=255) 11 | email: str = mongoz.Email(max_length=255) 12 | is_verified: bool = mongoz.Boolean(default=False) 13 | 14 | class Meta: 15 | registry = registry 16 | database = "my_db" 17 | 18 | 19 | # Create the custom signal 20 | User.meta.signals.on_verify = mongoz.Signal() 21 | -------------------------------------------------------------------------------- /docs_src/signals/disconnect.py: -------------------------------------------------------------------------------- 1 | async def trigger_notifications(sender, instance, **kwargs): 2 | """ 3 | Sends email and push notification 4 | """ 5 | send_email(instance.email) 6 | send_push_notification(instance.email) 7 | 8 | 9 | # Disconnect the given function 10 | User.meta.signals.on_verify.disconnect(trigger_notifications) 11 | -------------------------------------------------------------------------------- /docs_src/signals/logic.py: -------------------------------------------------------------------------------- 1 | async def create_user(**kwargs): 2 | """ 3 | Creates a user 4 | """ 5 | await User.query.create(**kwargs) 6 | 7 | 8 | async def is_verified_user(id: str): 9 | """ 10 | Checks if user is verified and sends notification 11 | if true. 12 | """ 13 | user = await User.get_document_by_id(id)) 14 | 15 | if user.is_verified: 16 | # triggers the custom signal 17 | await User.meta.signals.on_verify.send(sender=User, instance=user) 18 | -------------------------------------------------------------------------------- /docs_src/signals/receiver/disconnect.py: -------------------------------------------------------------------------------- 1 | from mongoz.core.signals import post_save 2 | 3 | 4 | def send_notification(email: str) -> None: 5 | """ 6 | Sends a notification to the user 7 | """ 8 | send_email_confirmation(email) 9 | 10 | 11 | @post_save(User) 12 | async def after_creation(sender, instance, **kwargs): 13 | """ 14 | Sends a notification to the user 15 | """ 16 | send_notification(instance.email) 17 | 18 | 19 | # Disconnect the given function 20 | User.meta.signals.post_save.disconnect(after_creation) 21 | 22 | # Signals are also exposed via instance 23 | user.signals.post_save.disconnect(after_creation) 24 | -------------------------------------------------------------------------------- /docs_src/signals/receiver/document.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import mongoz 4 | 5 | database_uri = "mongodb://localhost:27017" 6 | registry = mongoz.Registry(database_uri) 7 | 8 | 9 | class User(mongoz.Document): 10 | name: str = mongoz.String(max_length=255) 11 | email: str = mongoz.Email(max_length=255) 12 | is_verified: bool = mongoz.Boolean(default=False) 13 | 14 | class Meta: 15 | registry = registry 16 | database = "my_db" 17 | -------------------------------------------------------------------------------- /docs_src/signals/receiver/multiple.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import mongoz 4 | 5 | database_uri = "mongodb://localhost:27017" 6 | registry = mongoz.Registry(database_uri) 7 | 8 | 9 | class User(mongoz.Document): 10 | name: str = mongoz.String(max_length=255) 11 | email: str = mongoz.Email(max_length=255) 12 | is_verified: bool = mongoz.Boolean(default=False) 13 | 14 | class Meta: 15 | registry = registry 16 | database = "my_db" 17 | 18 | 19 | class Profile(mongoz.Document): 20 | profile_type: str = mongoz.String(max_length=255) 21 | 22 | class Meta: 23 | registry = registry 24 | database = "my_db" 25 | -------------------------------------------------------------------------------- /docs_src/signals/receiver/multiple_receivers.py: -------------------------------------------------------------------------------- 1 | from mongoz.core.signals import post_save 2 | 3 | 4 | def push_notification(email: str) -> None: 5 | # Sends a push notification 6 | ... 7 | 8 | 9 | def send_email(email: str) -> None: 10 | # Sends an email 11 | ... 12 | 13 | 14 | @post_save(User) 15 | async def after_creation(sender, instance, **kwargs): 16 | """ 17 | Sends a notification to the user 18 | """ 19 | send_email(instance.email) 20 | 21 | 22 | @post_save(User) 23 | async def do_something_else(sender, instance, **kwargs): 24 | """ 25 | Sends a notification to the user 26 | """ 27 | push_notification(instance.email) 28 | -------------------------------------------------------------------------------- /docs_src/signals/receiver/post_multiple.py: -------------------------------------------------------------------------------- 1 | from mongoz.core.signals import post_save 2 | 3 | 4 | def send_notification(email: str) -> None: 5 | """ 6 | Sends a notification to the user 7 | """ 8 | send_email_confirmation(email) 9 | 10 | 11 | @post_save([User, Profile]) 12 | async def after_creation(sender, instance, **kwargs): 13 | """ 14 | Sends a notification to the user 15 | """ 16 | if isinstance(instance, User): 17 | send_notification(instance.email) 18 | else: 19 | # something else for Profile 20 | ... 21 | -------------------------------------------------------------------------------- /docs_src/signals/receiver/post_save.py: -------------------------------------------------------------------------------- 1 | from mongoz.core.signals import post_save 2 | 3 | 4 | def send_notification(email: str) -> None: 5 | """ 6 | Sends a notification to the user 7 | """ 8 | send_email_confirmation(email) 9 | 10 | 11 | @post_save(User) 12 | async def after_creation(sender, instance, **kwargs): 13 | """ 14 | Sends a notification to the user 15 | """ 16 | send_notification(instance.email) 17 | -------------------------------------------------------------------------------- /docs_src/signals/register.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | import mongoz 4 | 5 | database_uri = "mongodb://localhost:27017" 6 | registry = mongoz.Registry(database_uri) 7 | 8 | 9 | class User(mongoz.Document): 10 | name: str = mongoz.String(max_length=255) 11 | email: str = mongoz.Email(max_length=255) 12 | is_verified: bool = mongoz.Boolean(default=False) 13 | 14 | class Meta: 15 | registry = registry 16 | database = "my_db" 17 | 18 | 19 | # Create the custom signal 20 | User.meta.signals.on_verify = mongoz.Signal() 21 | 22 | 23 | # Create the receiver 24 | async def trigger_notifications(sender, instance, **kwargs): 25 | """ 26 | Sends email and push notification 27 | """ 28 | send_email(instance.email) 29 | send_push_notification(instance.email) 30 | 31 | 32 | # Register the receiver into the new Signal. 33 | User.meta.signals.on_verify.connect(trigger_notifications) 34 | -------------------------------------------------------------------------------- /docs_src/tips/connection.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Generated by 'esmerald-admin createproject' 4 | """ 5 | import os 6 | import sys 7 | from pathlib import Path 8 | 9 | from esmerald import Esmerald, Include 10 | 11 | 12 | def build_path(): 13 | """ 14 | Builds the path of the project and project root. 15 | """ 16 | Path(__file__).resolve().parent.parent 17 | SITE_ROOT = os.path.dirname(os.path.realpath(__file__)) 18 | 19 | if SITE_ROOT not in sys.path: 20 | sys.path.append(SITE_ROOT) 21 | sys.path.append(os.path.join(SITE_ROOT, "apps")) 22 | 23 | 24 | def get_application(): 25 | """ 26 | This is optional. The function is only used for organisation purposes. 27 | """ 28 | build_path() 29 | 30 | app = Esmerald(routes=[Include(namespace="my_project.urls")]) 31 | return app 32 | 33 | 34 | app = get_application() 35 | -------------------------------------------------------------------------------- /docs_src/tips/lru.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | 3 | from esmerald.conf import settings 4 | 5 | 6 | @lru_cache() 7 | def get_db_connection(): 8 | registry = settings.db_connection 9 | return registry 10 | -------------------------------------------------------------------------------- /docs_src/tips/models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from my_project.utils import get_db_connection 4 | 5 | import mongoz 6 | 7 | registry = get_db_connection() 8 | 9 | 10 | class BaseDocument(mongoz.Document): 11 | class Meta: 12 | abstract = True 13 | registry = registry 14 | database = "my_db" 15 | 16 | 17 | class User(BaseDocument): 18 | """ 19 | Base document for a user 20 | """ 21 | 22 | first_name: str = mongoz.String(max_length=150) 23 | last_name: str = mongoz.String(max_length=150) 24 | username: str = mongoz.String(max_length=150, unique=True) 25 | email: str = mongoz.Email(max_length=120, unique=True) 26 | password: str = mongoz.String(max_length=128) 27 | last_login: datetime = mongoz.DateTime(null=True) 28 | is_active: bool = mongoz.Boolean(default=True) 29 | is_staff: bool = mongoz.Boolean(default=False) 30 | is_superuser: bool = mongoz.Boolean(default=False) 31 | -------------------------------------------------------------------------------- /docs_src/tips/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generated by 'esmerald createproject' 3 | """ 4 | from functools import cached_property 5 | from typing import Optional 6 | 7 | from esmerald.conf.enums import EnvironmentType 8 | from esmerald.conf.global_settings import EsmeraldAPISettings 9 | 10 | from mongoz import Registry 11 | 12 | 13 | class AppSettings(EsmeraldAPISettings): 14 | app_name: str = "My application in production mode." 15 | environment: Optional[str] = EnvironmentType.PRODUCTION 16 | secret_key: str = "esmerald-insecure-h35r*b9$+hw-x2hnt5c)vva=!zn$*a7#" # auto generated 17 | 18 | @cached_property 19 | def db_connection(self) -> Registry: 20 | """ 21 | To make sure the registry and the database connection remains the same 22 | all the time, always use the cached_property. 23 | """ 24 | database = "mongodb://root:mongoadmin@localhost:27017" 25 | return Registry(database=database) 26 | -------------------------------------------------------------------------------- /mongoz/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.11.5" 2 | 3 | from .conf import settings 4 | from .conf.global_settings import MongozSettings 5 | from .core.connection.database import Database 6 | from .core.connection.registry import Registry 7 | from .core.db import fields 8 | from .core.db.datastructures import Index, IndexType, Order 9 | from .core.db.documents import Document, EmbeddedDocument 10 | from .core.db.documents.managers import QuerySetManager 11 | from .core.db.fields import ( 12 | UUID, 13 | Array, 14 | ArrayList, 15 | Binary, 16 | Boolean, 17 | Date, 18 | DateTime, 19 | Decimal, 20 | Double, 21 | Email, 22 | Embed, 23 | ForeignKey, 24 | Integer, 25 | NullableObjectId, 26 | Object, 27 | ObjectId, 28 | String, 29 | Time, 30 | ) 31 | from .core.db.querysets.base import Manager, QuerySet 32 | from .core.db.querysets.expressions import Expression, SortExpression 33 | from .core.db.querysets.operators import Q 34 | from .core.signals import Signal 35 | from .core.utils.sync import run_sync 36 | from .exceptions import ( 37 | DocumentNotFound, 38 | ImproperlyConfigured, 39 | MultipleDocumentsReturned, 40 | ) 41 | 42 | __all__ = [ 43 | "Array", 44 | "ArrayList", 45 | "Binary", 46 | "Boolean", 47 | "Database", 48 | "Date", 49 | "DateTime", 50 | "Decimal", 51 | "Document", 52 | "DocumentNotFound", 53 | "Double", 54 | "Embed", 55 | "Email", 56 | "EmbeddedDocument", 57 | "Expression", 58 | "fields", 59 | "ImproperlyConfigured", 60 | "Index", 61 | "IndexType", 62 | "Integer", 63 | "NullableObjectId", 64 | "ForeignKey", 65 | "Manager", 66 | "MongozSettings", 67 | "MultipleDocumentsReturned", 68 | "Object", 69 | "ObjectId", 70 | "Order", 71 | "Q", 72 | "QuerySet", 73 | "QuerySetManager", 74 | "Registry", 75 | "Signal", 76 | "SortExpression", 77 | "String", 78 | "Time", 79 | "UUID", 80 | "settings", 81 | "run_sync", 82 | ] 83 | -------------------------------------------------------------------------------- /mongoz/conf/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | from typing import TYPE_CHECKING, Any 5 | 6 | from mongoz.conf.functional import LazyObject, empty 7 | from mongoz.conf.module_import import import_string 8 | 9 | if TYPE_CHECKING: 10 | from mongoz.conf.global_settings import MongozSettings 11 | 12 | ENVIRONMENT_VARIABLE = "MONGOZ_SETTINGS_MODULE" 13 | 14 | 15 | class MongozLazySettings(LazyObject): 16 | """ 17 | A lazy proxy for either global Mongoz settings or a custom settings object. 18 | The user can manually configure settings prior to using them. Otherwise, 19 | Mongoz uses the settings module pointed to by MONGOZ_SETTINGS_MODULE. 20 | """ 21 | 22 | def _setup(self, name: str | None = None) -> None: 23 | """ 24 | Load the settings module pointed to by the environment variable. This 25 | is used the first time settings are needed, if the user hasn't 26 | configured settings manually. 27 | """ 28 | settings_module: str = os.environ.get( 29 | ENVIRONMENT_VARIABLE, "mongoz.conf.global_settings.MongozSettings" 30 | ) 31 | 32 | settings: type[MongozSettings] = import_string(settings_module) 33 | 34 | for setting, _ in settings().dict().items(): 35 | assert setting.islower(), "%s should be in lowercase." % setting 36 | 37 | self._wrapped = settings() 38 | 39 | def __repr__(self: MongozLazySettings) -> str: 40 | # Hardcode the class name as otherwise it yields 'MongozSettings'. 41 | if self._wrapped is empty: 42 | return "" 43 | return f'' 44 | 45 | @property 46 | def configured(self) -> Any: 47 | """Return True if the settings have already been configured.""" 48 | return self._wrapped is not empty 49 | 50 | 51 | settings: type[MongozSettings] = MongozLazySettings() # type: ignore 52 | -------------------------------------------------------------------------------- /mongoz/conf/global_settings.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from functools import cached_property 3 | from typing import TYPE_CHECKING, ClassVar, Dict, List, cast 4 | 5 | from dymmond_settings import Settings 6 | 7 | from mongoz.exceptions import OperatorInvalid 8 | 9 | if TYPE_CHECKING: 10 | from mongoz import Expression 11 | 12 | 13 | @dataclass 14 | class MongozSettings(Settings): 15 | ipython_args: ClassVar[List[str]] = ["--no-banner"] 16 | ptpython_config_file: str = "~/.config/ptpython/config.py" 17 | 18 | parsed_ids: ClassVar[List[str]] = ["id", "pk"] 19 | 20 | filter_operators: ClassVar[Dict[str, str]] = { 21 | "exact": "eq", 22 | "neq": "neq", 23 | "contains": "contains", 24 | "icontains": "icontains", 25 | "in": "in_", 26 | "not_in": "not_in", 27 | "pattern": "pattern", 28 | "where": "where", 29 | "gte": "gte", 30 | "gt": "gt", 31 | "lt": "lt", 32 | "lte": "lte", 33 | "asc": "asc", 34 | "desc": "desc", 35 | "not": "not_", 36 | "startswith": "startswith", 37 | "istartswith": "istartswith", 38 | "endswith": "endswith", 39 | "iendswith": "iendswith", 40 | "date": "date", 41 | } 42 | 43 | def get_operator(self, name: str) -> "Expression": 44 | """ 45 | Returns the operator associated to the given expression passed. 46 | """ 47 | from mongoz.core.db.querysets.operators import Q 48 | 49 | if name not in self.filter_operators: 50 | raise OperatorInvalid(f"`{name}` is not a valid operator.") 51 | return cast("Expression", getattr(Q, self.filter_operators[name])) 52 | 53 | @cached_property 54 | def operators(self) -> List[str]: 55 | """ 56 | Returns a list of valid operators. 57 | """ 58 | return list(self.filter_operators.keys()) 59 | 60 | @cached_property 61 | def stringified_operators(self) -> str: 62 | """ 63 | Returns a list of valid operators. 64 | """ 65 | return ", ".join(self.operators) 66 | -------------------------------------------------------------------------------- /mongoz/conf/module_import.py: -------------------------------------------------------------------------------- 1 | from importlib import import_module 2 | from typing import Any 3 | 4 | 5 | def import_string(dotted_path: str) -> Any: 6 | """ 7 | Import a dotted module path and return the attribute/class designated by the 8 | last name in the path. Raise ImportError if the import failed. 9 | """ 10 | try: 11 | module_path, class_name = dotted_path.rsplit(".", 1) 12 | except ValueError as err: 13 | raise ImportError("%s doesn't look like a module path" % dotted_path) from err 14 | 15 | module = import_module(module_path) 16 | 17 | try: 18 | return getattr(module, class_name) 19 | except AttributeError as err: 20 | raise ImportError( 21 | 'Module "{}" does not define a "{}" attribute/class'.format(module_path, class_name) 22 | ) from err 23 | -------------------------------------------------------------------------------- /mongoz/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/mongoz/31651a3112ddfb4cbbd06a0b18c26288eebd969b/mongoz/core/__init__.py -------------------------------------------------------------------------------- /mongoz/core/connection/__init__.py: -------------------------------------------------------------------------------- 1 | from .collections import Collection as Collection 2 | from .database import Database as Database 3 | from .registry import Registry as Registry 4 | -------------------------------------------------------------------------------- /mongoz/core/connection/collections.py: -------------------------------------------------------------------------------- 1 | from motor.motor_asyncio import AsyncIOMotorCollection 2 | 3 | 4 | class Collection: 5 | """ 6 | MongoDB collection object referencing Motor collection 7 | """ 8 | 9 | def __init__(self, name: str, collection: AsyncIOMotorCollection) -> None: 10 | self._collection: AsyncIOMotorCollection = collection 11 | self.name = name 12 | -------------------------------------------------------------------------------- /mongoz/core/connection/database.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict, List, Optional 2 | 3 | from bson import CodecOptions, UuidRepresentation 4 | from motor.motor_asyncio import AsyncIOMotorDatabase 5 | 6 | from mongoz.core.connection.collections import Collection 7 | 8 | 9 | class Database: 10 | """ 11 | MongoDB database object referencing Motor database 12 | """ 13 | 14 | def __init__( 15 | self, 16 | name: str, 17 | database: AsyncIOMotorDatabase, 18 | codec_options: Optional[Dict[str, Any]] = None, 19 | ) -> None: 20 | self._db = database 21 | self.name = name 22 | 23 | self._codec_options = ( 24 | codec_options 25 | if codec_options 26 | else {"uuid_representation": UuidRepresentation.STANDARD} 27 | ) 28 | 29 | @property 30 | def codec_options(self) -> CodecOptions: 31 | return CodecOptions(**self._codec_options) 32 | 33 | def get_collection(self, name: str) -> Collection: 34 | collection = self._db.get_collection(name, codec_options=self.codec_options) 35 | return Collection(name, collection=collection) 36 | 37 | async def get_collections(self) -> List[Collection]: 38 | collections = await self._db.list_collection_names() 39 | return list(map(self.get_collection, collections)) 40 | -------------------------------------------------------------------------------- /mongoz/core/connection/registry.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from typing import TYPE_CHECKING, Callable, Dict, Sequence, Tuple, Union, cast 3 | 4 | from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase 5 | 6 | from mongoz.core.connection.database import Database 7 | 8 | if TYPE_CHECKING: 9 | from mongoz import Document 10 | 11 | 12 | class Registry: 13 | """ 14 | MongoDB client object referencing the Async Motor Client 15 | """ 16 | 17 | def __init__( 18 | self, 19 | url: str, 20 | event_loop: Union[Callable[[], asyncio.AbstractEventLoop], None] = None, 21 | ) -> None: 22 | self.event_loop = event_loop or asyncio.get_event_loop 23 | self.url = url 24 | self._client: AsyncIOMotorClient = AsyncIOMotorClient(self.url) 25 | self._client.get_io_loop = self.event_loop # type: ignore 26 | self.documents: Dict[str, "Document"] = {} 27 | 28 | @property 29 | def address(self) -> Tuple[str, int]: 30 | return cast(Tuple[str, int], self._client.address) 31 | 32 | @property 33 | def host(self) -> str: 34 | return self._client.HOST 35 | 36 | @property 37 | def port(self) -> str: 38 | return cast(str, self._client.PORT) 39 | 40 | @property 41 | def driver(self) -> AsyncIOMotorDatabase: 42 | return self._client.driver 43 | 44 | async def drop_database(self, database: Union[str, Database]) -> None: 45 | """ 46 | Drops an existing mongo db database/ 47 | """ 48 | if not isinstance(database, Database): 49 | await self._client.drop_database(database) 50 | else: 51 | await self._client.drop_database(database._db) 52 | 53 | def get_database(self, name: str) -> Database: 54 | database = self._client.get_database(name) 55 | return Database(name=name, database=database) 56 | 57 | async def get_databases(self) -> Sequence[Database]: 58 | databases = await self._client.list_database_names() 59 | return list(map(self.get_database, databases)) 60 | 61 | async def document_checks(self) -> None: 62 | """ 63 | Runs the document checks for all the documents in the registry. 64 | """ 65 | for document in self.documents.values(): 66 | await document.check_indexes() 67 | -------------------------------------------------------------------------------- /mongoz/core/db/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/mongoz/31651a3112ddfb4cbbd06a0b18c26288eebd969b/mongoz/core/db/__init__.py -------------------------------------------------------------------------------- /mongoz/core/db/datastructures.py: -------------------------------------------------------------------------------- 1 | import enum 2 | from typing import Any, List, Tuple, Union 3 | 4 | import pymongo 5 | 6 | 7 | class Order(int, enum.Enum): 8 | ASCENDING = pymongo.ASCENDING 9 | DESCENDING = pymongo.DESCENDING 10 | 11 | 12 | class IndexType(str, enum.Enum): 13 | GEO2D = pymongo.GEO2D 14 | GEOSPHERE = pymongo.GEOSPHERE 15 | HASHED = pymongo.HASHED 16 | TEXT = pymongo.TEXT 17 | 18 | 19 | class Index(pymongo.IndexModel): 20 | """ 21 | Class responsible for handling and declaring the database indexes. 22 | """ 23 | 24 | def __init__( 25 | self, 26 | key: Union[str, None] = None, 27 | keys: List[Union[Tuple[str, Order], Tuple[str, IndexType]]] = None, 28 | name: str = None, 29 | background: bool = False, 30 | unique: bool = False, 31 | sparse: bool = False, 32 | **kwargs: Any, 33 | ) -> None: 34 | keys = [(key, Order.ASCENDING)] if key else keys or [] 35 | self.name = name or "_".join([key[0] for key in keys]) 36 | self.unique = unique 37 | 38 | kwargs["name"] = self.name 39 | kwargs["background"] = background 40 | kwargs["sparse"] = sparse 41 | kwargs["unique"] = unique 42 | return super().__init__(keys, **kwargs) 43 | -------------------------------------------------------------------------------- /mongoz/core/db/documents/__init__.py: -------------------------------------------------------------------------------- 1 | from .document import Document, EmbeddedDocument 2 | 3 | __all__ = ["Document", "EmbeddedDocument"] 4 | -------------------------------------------------------------------------------- /mongoz/core/db/documents/document_row.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Any, Dict, Sequence, Type, Union, cast 2 | 3 | from motor.motor_asyncio import AsyncIOMotorCollection 4 | 5 | from mongoz.core.db.documents.base import MongozBaseModel 6 | 7 | if TYPE_CHECKING: # pragma: no cover 8 | from mongoz import Document 9 | 10 | 11 | class DocumentRow(MongozBaseModel): 12 | """ 13 | Builds a row for a specific document 14 | """ 15 | 16 | @classmethod 17 | def from_row( 18 | cls: "Document", 19 | row: Dict[str, Any], 20 | is_only_fields: bool = False, 21 | is_defer_fields: bool = False, 22 | only_fields: Union[Sequence[str], None] = None, 23 | defer_fields: Union[Sequence[str], None] = None, 24 | from_collection: Union[AsyncIOMotorCollection, None] = None 25 | ) -> Union[Type["Document"], None]: 26 | """ 27 | Class method to convert a dictionary row result into a Document row type. 28 | :return: Document class. 29 | """ 30 | item: Dict[str, Any] = {} 31 | 32 | if is_only_fields or is_defer_fields: 33 | mapping = ( 34 | only_fields 35 | if is_only_fields 36 | else [ 37 | cls.validate_id_field(name) for name in row.keys() if name not in defer_fields # type: ignore 38 | ] 39 | ) 40 | 41 | for column, value in row.items(): 42 | column = cls.validate_id_field(column) 43 | 44 | if column not in mapping: # type: ignore 45 | continue 46 | 47 | if column not in item: 48 | item[column] = value 49 | 50 | # We need to generify the document fields to make sure we can populate the 51 | # model without mandatory fields 52 | model = cast("Type[Document]", cls.proxy_document(**item)) 53 | return model 54 | else: 55 | for column, value in row.items(): 56 | column = cls.validate_id_field(column) 57 | if column not in item: 58 | item[column] = value 59 | 60 | model = cast("Type[Document]", cls(**item)) # type: ignore 61 | model.Meta.from_collection = from_collection 62 | return model 63 | 64 | @classmethod 65 | def validate_id_field(cls, field: str) -> str: 66 | if field in ["_id", "id", "pk"]: 67 | field = "id" 68 | return field 69 | -------------------------------------------------------------------------------- /mongoz/core/db/documents/managers.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Type, cast 2 | 3 | from mongoz.core.db.querysets.core.manager import Manager as BaseManager 4 | 5 | 6 | class QuerySetManager: 7 | 8 | def __init__(self, model_class: Any = None): 9 | self.model_class = model_class 10 | 11 | def __get__(self, _: Any, owner: Any) -> Type["BaseManager"]: 12 | return cast("Type[BaseManager]", self.__class__(model_class=owner)) 13 | 14 | def get_queryset(self) -> "BaseManager": 15 | """ 16 | Returns the queryset object. 17 | 18 | Checks for a global possible tenant and returns the corresponding queryset. 19 | """ 20 | return BaseManager(self.model_class) 21 | 22 | def __getattr__(self, item: Any) -> Any: 23 | """ 24 | Gets the attribute from the queryset and if it does not 25 | exist, then lookup in the model. 26 | """ 27 | try: 28 | return getattr(self.get_queryset(), item) 29 | except AttributeError: 30 | return getattr(self.model_class, item) 31 | -------------------------------------------------------------------------------- /mongoz/core/db/fields/__init__.py: -------------------------------------------------------------------------------- 1 | from .core import ( 2 | UUID, 3 | Array, 4 | ArrayList, 5 | AutoNowMixin, 6 | BaseField, 7 | Binary, 8 | Boolean, 9 | Date, 10 | DateTime, 11 | Decimal, 12 | Double, 13 | Email, 14 | Embed, 15 | ForeignKey, 16 | Integer, 17 | NullableObjectId, 18 | Object, 19 | ObjectId, 20 | String, 21 | Time, 22 | ) 23 | 24 | __all__ = [ 25 | "Array", 26 | "ArrayList", 27 | "AutoNowMixin", 28 | "BaseField", 29 | "Binary", 30 | "Boolean", 31 | "String", 32 | "Date", 33 | "DateTime", 34 | "Decimal", 35 | "Email", 36 | "Double", 37 | "Integer", 38 | "Object", 39 | "ObjectId", 40 | "Time", 41 | "UUID", 42 | "Embed", 43 | "NullableObjectId", 44 | "ForeignKey", 45 | ] 46 | -------------------------------------------------------------------------------- /mongoz/core/db/querysets/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import Manager, QuerySet 2 | from .expressions import Expression, SortExpression 3 | from .operators import Q 4 | 5 | __all__ = ["Expression", "Q", "QuerySet", "Manager", "SortExpression"] 6 | -------------------------------------------------------------------------------- /mongoz/core/db/querysets/base.py: -------------------------------------------------------------------------------- 1 | from mongoz.core.db.querysets.core.manager import Manager as Manager 2 | from mongoz.core.db.querysets.core.queryset import QuerySet as QuerySet 3 | -------------------------------------------------------------------------------- /mongoz/core/db/querysets/core/constants.py: -------------------------------------------------------------------------------- 1 | VALUE_EQUALITY = ["eq", "neq", "contains", "icontains", "pattern", "startswith", "endswith", "istartswith", "iendswith"] 2 | LIST_EQUALITY = ["in", "not_in"] 3 | ORDER_EQUALITY = ["asc", "desc"] 4 | GREATNESS_EQUALITY = ["lt", "lte", "gt", "gte", "exists"] 5 | -------------------------------------------------------------------------------- /mongoz/core/db/querysets/core/protocols.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | if typing.TYPE_CHECKING: 4 | from mongoz.core.db.documents import Document 5 | 6 | # Create a var type for the Edgy Model 7 | MongozDocument = typing.TypeVar("MongozDocument", bound="Document") 8 | 9 | 10 | class AwaitableQuery(typing.Generic[MongozDocument]): 11 | __slots__ = ("model_class",) 12 | 13 | def __init__(self, model_class: typing.Type[MongozDocument]) -> None: 14 | self.model_class: typing.Type[MongozDocument] = model_class 15 | 16 | async def execute(self) -> typing.Any: 17 | raise NotImplementedError() 18 | -------------------------------------------------------------------------------- /mongoz/core/signals/__init__.py: -------------------------------------------------------------------------------- 1 | from .handlers import post_delete, post_save, post_update, pre_delete, pre_save, pre_update 2 | from .signal import Broadcaster, Signal 3 | 4 | __all__ = [ 5 | "Broadcaster", 6 | "Signal", 7 | "post_delete", 8 | "post_save", 9 | "post_update", 10 | "pre_delete", 11 | "pre_save", 12 | "pre_update", 13 | ] 14 | -------------------------------------------------------------------------------- /mongoz/core/signals/handlers.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Callable, List, Type, Union 2 | 3 | if TYPE_CHECKING: # pragma: no cover 4 | from mongoz import Document 5 | 6 | 7 | class Send: 8 | """ 9 | Base for all the wrappers handling the signals. 10 | """ 11 | 12 | def consumer( 13 | signal: str, senders: Union[Type["Document"], List[Type["Document"]]] 14 | ) -> Callable: 15 | """ 16 | Connects the function to all the senders. 17 | """ 18 | 19 | def wrapper(func: Callable) -> Callable: 20 | _senders = [senders] if not isinstance(senders, list) else senders 21 | 22 | for sender in _senders: 23 | signals = getattr(sender.meta.signals, signal) 24 | signals.connect(func) 25 | return func 26 | 27 | return wrapper 28 | 29 | 30 | def pre_save(senders: Union[Type["Document"], List[Type["Document"]]]) -> Callable: 31 | """ 32 | Connects all the senders to pre_save. 33 | """ 34 | return Send.consumer(signal="pre_save", senders=senders) 35 | 36 | 37 | def pre_update(senders: Union[Type["Document"], List[Type["Document"]]]) -> Callable: 38 | """ 39 | Connects all the senders to pre_update. 40 | """ 41 | return Send.consumer(signal="pre_update", senders=senders) 42 | 43 | 44 | def pre_delete(senders: Union[Type["Document"], List[Type["Document"]]]) -> Callable: 45 | """ 46 | Connects all the senders to pre_delete. 47 | """ 48 | return Send.consumer(signal="pre_delete", senders=senders) 49 | 50 | 51 | def post_save(senders: Union[Type["Document"], List[Type["Document"]]]) -> Callable: 52 | """ 53 | Connects all the senders to post_save. 54 | """ 55 | return Send.consumer(signal="post_save", senders=senders) 56 | 57 | 58 | def post_update(senders: Union[Type["Document"], List[Type["Document"]]]) -> Callable: 59 | """ 60 | Connects all the senders to post_update. 61 | """ 62 | return Send.consumer(signal="post_update", senders=senders) 63 | 64 | 65 | def post_delete(senders: Union[Type["Document"], List[Type["Document"]]]) -> Callable: 66 | """ 67 | Connects all the senders to post_delete. 68 | """ 69 | return Send.consumer(signal="post_delete", senders=senders) 70 | -------------------------------------------------------------------------------- /mongoz/core/signals/signal.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from typing import TYPE_CHECKING, Any, Callable, Dict, Tuple, Type, Union 3 | 4 | from mongoz.exceptions import SignalError 5 | from mongoz.utils.inspect import func_accepts_kwargs 6 | 7 | if TYPE_CHECKING: 8 | from mongoz import Document 9 | 10 | 11 | def make_id(target: Any) -> Union[int, Tuple[int, int]]: 12 | """ 13 | Creates an id for a function. 14 | """ 15 | if hasattr(target, "__func__"): 16 | return (id(target.__self__), id(target.__func__)) 17 | return id(target) 18 | 19 | 20 | class Signal: 21 | """ 22 | Base class for all Mongoz signals. 23 | """ 24 | 25 | def __init__(self, **kwargs: Any) -> None: 26 | """ 27 | Creates a new signal. 28 | """ 29 | self.receivers: Dict[Union[int, Tuple[int, int]], Callable] = {} 30 | 31 | def connect(self, receiver: Callable) -> None: 32 | """ 33 | Connects a given receiver to the the signal. 34 | """ 35 | if not callable(receiver): 36 | raise SignalError("The signals should be callables") 37 | 38 | if not func_accepts_kwargs(receiver): 39 | raise SignalError("Signal receivers must accept keyword arguments (**kwargs).") 40 | 41 | key = make_id(receiver) 42 | if key not in self.receivers: 43 | self.receivers[key] = receiver 44 | 45 | def disconnect(self, receiver: Callable) -> bool: 46 | """ 47 | Removes the receiver from the signal. 48 | """ 49 | key = make_id(receiver) 50 | func: Union[Callable, None] = self.receivers.pop(key, None) 51 | return True if func is not None else False 52 | 53 | async def send(self, sender: Type["Document"], **kwargs: Any) -> None: 54 | """ 55 | Sends the notification to all the receivers. 56 | """ 57 | receivers = [func(sender=sender, **kwargs) for func in self.receivers.values()] 58 | await asyncio.gather(*receivers) 59 | 60 | 61 | class Broadcaster(dict): 62 | def __getattr__(self, item: str) -> Signal: 63 | return self.setdefault(item, Signal()) # type: ignore 64 | 65 | def __setattr__(self, __name: str, __value: Signal) -> None: 66 | if not isinstance(__value, Signal): 67 | raise SignalError(f"{__value} is not valid signal") 68 | self[__name] = __value 69 | -------------------------------------------------------------------------------- /mongoz/core/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/mongoz/31651a3112ddfb4cbbd06a0b18c26288eebd969b/mongoz/core/utils/__init__.py -------------------------------------------------------------------------------- /mongoz/core/utils/documents.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Any, Dict, Optional, Set, Tuple, Type, Union 2 | 3 | from pydantic import ConfigDict 4 | 5 | import mongoz 6 | 7 | if TYPE_CHECKING: 8 | from mongoz import Document 9 | from mongoz.core.db.documents.metaclasses import MetaInfo 10 | 11 | edgy_setattr = object.__setattr__ 12 | 13 | 14 | def create_mongoz_document( 15 | __name__: str, 16 | __module__: str, 17 | __definitions__: Optional[Dict[Any, Any]] = None, 18 | __metadata__: Optional[Type["MetaInfo"]] = None, 19 | __qualname__: Optional[str] = None, 20 | __config__: Optional[ConfigDict] = None, 21 | __bases__: Optional[Tuple[Type["Document"]]] = None, 22 | __proxy__: bool = False, 23 | __pydantic_extra__: Any = None, 24 | ) -> Type["Document"]: 25 | """ 26 | Generates an `mongoz.Document` with all the required definitions to generate the pydantic 27 | like model. 28 | """ 29 | if not __bases__: 30 | __bases__ = (mongoz.Document,) 31 | 32 | qualname = __qualname__ or __name__ 33 | core_definitions = { 34 | "__module__": __module__, 35 | "__qualname__": qualname, 36 | "is_proxy_document": __proxy__, 37 | } 38 | if not __definitions__: 39 | __definitions__ = {} 40 | 41 | core_definitions.update(**__definitions__) 42 | 43 | if __config__: 44 | core_definitions.update(**{"model_config": __config__}) 45 | if __metadata__: 46 | core_definitions.update(**{"Meta": __metadata__}) 47 | if __pydantic_extra__: 48 | core_definitions.update(**{"__pydantic_extra__": __pydantic_extra__}) 49 | 50 | model: Type["Document"] = type(__name__, __bases__, core_definitions) 51 | return model 52 | 53 | 54 | def generify_model_fields( 55 | model: Type["Document"], exclude: Union[Set[str], None] = None 56 | ) -> Dict[Any, Any]: 57 | """ 58 | Makes all fields generic when a partial model is generated or used. 59 | This also removes any metadata for the field such as validations making 60 | it a clean slate to be used internally to process dynamic data and removing 61 | the constraints of the original model fields. 62 | """ 63 | fields = {} 64 | 65 | if exclude is None: 66 | exclude = set() 67 | 68 | # handle the nested non existing results 69 | for name, field in model.model_fields.items(): 70 | if name in exclude: 71 | continue 72 | 73 | edgy_setattr(field, "annotation", Any) 74 | edgy_setattr(field, "null", True) 75 | edgy_setattr(field, "metadata", []) 76 | fields[name] = field 77 | return fields 78 | -------------------------------------------------------------------------------- /mongoz/core/utils/functional.py: -------------------------------------------------------------------------------- 1 | """ 2 | All functional common to MongoZ 3 | """ 4 | 5 | from typing import Any, Dict, Tuple, Union 6 | 7 | from mongoz.core.db.fields.base import BaseField 8 | 9 | mongoz_setattr = object.__setattr__ 10 | 11 | 12 | def extract_field_annotations_and_defaults( 13 | attrs: Dict[Any, Any] 14 | ) -> Tuple[Dict[Any, Any], Dict[Any, Any]]: 15 | """ 16 | Extracts annotations from class namespace dict and triggers 17 | extraction of ormar model_fields. 18 | """ 19 | key = "__annotations__" 20 | attrs[key] = attrs.get(key, {}) 21 | attrs, model_fields = populate_pydantic_default_values(attrs) 22 | return attrs, model_fields 23 | 24 | 25 | def get_model_fields(attrs: Union[Dict, Any]) -> Dict: 26 | """ 27 | Gets all the fields in current model class that are Mongoz Fields. 28 | """ 29 | return {k: v for k, v in attrs.items() if isinstance(v, BaseField)} 30 | 31 | 32 | def populate_pydantic_default_values(attrs: Dict) -> Tuple[Dict, Dict]: 33 | """ 34 | Making sure the fields from Mongoz are the ones being validated by Mongoz documents 35 | and delegates the validations from pydantic to that functionality. 36 | """ 37 | model_fields = {} 38 | potential_fields = {} 39 | 40 | potential_fields.update(get_model_fields(attrs)) 41 | for field_name, field in potential_fields.items(): 42 | model_fields[field_name] = field 43 | field.name = field_name 44 | 45 | default_type = field.field_type if not field.null else Union[field.field_type, None] 46 | overwrite_type = ( 47 | field.__original_type__ if field.field_type != field.__original_type__ else None 48 | ) 49 | field.annotation = overwrite_type or default_type 50 | attrs["__annotations__"][field_name] = overwrite_type or default_type 51 | 52 | return attrs, model_fields 53 | -------------------------------------------------------------------------------- /mongoz/core/utils/hashable.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Iterable 2 | from typing import Any 3 | 4 | 5 | def make_hashable(value: Any) -> Any: 6 | """ 7 | Attempt to make value hashable or raise a TypeError if it fails. 8 | 9 | The returned value should generate the same hash for equal values. 10 | """ 11 | if isinstance(value, dict): 12 | return tuple( 13 | [ 14 | (key, make_hashable(nested_value)) 15 | for key, nested_value in sorted(value.items()) 16 | ] 17 | ) 18 | # Try hash to avoid converting a hashable iterable (e.g. string, frozenset) 19 | # to a tuple. 20 | try: 21 | hash(value) 22 | except TypeError: 23 | if isinstance(value, Iterable): 24 | return tuple(map(make_hashable, value)) 25 | # Non-hashable, non-iterable. 26 | raise 27 | return value 28 | -------------------------------------------------------------------------------- /mongoz/core/utils/sync.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from concurrent import futures 3 | from concurrent.futures import Future 4 | from typing import Any, Awaitable 5 | 6 | 7 | def run_sync(async_function: Awaitable) -> Any: 8 | """ 9 | Runs the queries in sync mode 10 | """ 11 | try: 12 | return asyncio.run(async_function) 13 | except RuntimeError: 14 | with futures.ThreadPoolExecutor(max_workers=1) as executor: 15 | future: Future = executor.submit(asyncio.run, async_function) 16 | return future.result() 17 | -------------------------------------------------------------------------------- /mongoz/exceptions.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | 4 | class MongozException(Exception): 5 | def __init__( 6 | self, 7 | *args: typing.Any, 8 | detail: str = "", 9 | ): 10 | self.detail = detail 11 | super().__init__(*(str(arg) for arg in args if arg), self.detail) 12 | 13 | def __repr__(self) -> str: 14 | if self.detail: 15 | return f"{self.__class__.__name__} - {self.detail}" 16 | return self.__class__.__name__ 17 | 18 | def __str__(self) -> str: 19 | return "".join(self.args).strip() 20 | 21 | 22 | class DocumentNotFound(MongozException): ... 23 | 24 | 25 | class MultipleDocumentsReturned(MongozException): ... 26 | 27 | 28 | class FieldDefinitionError(MongozException): ... 29 | 30 | 31 | class ImproperlyConfigured(MongozException): ... 32 | 33 | 34 | class InvalidObjectIdError(MongozException): ... 35 | 36 | 37 | class InvalidKeyError(MongozException): ... 38 | 39 | 40 | class SignalError(MongozException): ... 41 | 42 | 43 | class AbstractDocumentError(MongozException): ... 44 | 45 | 46 | class OperatorInvalid(MongozException): ... 47 | 48 | 49 | class IndexError(MongozException): ... 50 | -------------------------------------------------------------------------------- /mongoz/protocols/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/mongoz/31651a3112ddfb4cbbd06a0b18c26288eebd969b/mongoz/protocols/__init__.py -------------------------------------------------------------------------------- /mongoz/protocols/queryset.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Any, Dict, List, Protocol, Tuple, TypeVar, Union, overload 2 | 3 | if TYPE_CHECKING: 4 | from mongoz.core.db.datastructures import Order 5 | from mongoz.core.db.documents import Document 6 | from mongoz.core.db.querysets.base import QuerySet 7 | from mongoz.core.db.querysets.expressions import SortExpression 8 | 9 | 10 | T = TypeVar("T", bound="Document") 11 | 12 | 13 | class QuerySetProtocol(Protocol): 14 | async def all(self) -> List[T]: ... 15 | 16 | async def count(self) -> int: ... 17 | 18 | async def delete(self) -> int: ... 19 | 20 | async def first(self) -> Union[T, None]: ... 21 | 22 | async def last(self) -> Union[T, None]: ... 23 | 24 | async def get(self) -> T: ... 25 | 26 | async def get_or_create(self, defaults: Union[Dict[str, Any], None]) -> T: ... 27 | 28 | async def limit(self, count: int = 0) -> "QuerySet[T]": # pragma: no cover 29 | ... 30 | 31 | async def skip(self, count: int = 0) -> "QuerySet[T]": # pragma: no cover 32 | ... 33 | 34 | @overload 35 | def sort(self, key: "SortExpression") -> "QuerySet[T]": # pragma: no cover 36 | ... 37 | 38 | @overload 39 | def sort(self, key: Any, direction: "Order") -> "QuerySet[T]": # pragma: no cover 40 | ... 41 | 42 | @overload 43 | def sort(self, key: List[Tuple[Any, "Order"]]) -> "QuerySet[T]": # pragma: no cover 44 | ... 45 | 46 | def sort(self, key: Any, direction: Union["Order", None] = None) -> "QuerySet[T]": ... 47 | 48 | async def update_many(self, **kwargs: Any) -> List[T]: ... 49 | -------------------------------------------------------------------------------- /mongoz/py.typed: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /mongoz/types.py: -------------------------------------------------------------------------------- 1 | from pydantic_core import PydanticUndefined 2 | 3 | Undefined = PydanticUndefined 4 | -------------------------------------------------------------------------------- /mongoz/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/mongoz/31651a3112ddfb4cbbd06a0b18c26288eebd969b/mongoz/utils/__init__.py -------------------------------------------------------------------------------- /mongoz/utils/enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class OrderEnum(str, Enum): 5 | ASCENDING = "asc" 6 | DESCENDING = "desc" 7 | 8 | def __str__(self) -> str: 9 | return self.value 10 | 11 | def __repr__(self) -> str: 12 | return str(self) 13 | 14 | 15 | class ExpressionOperator(str, Enum): 16 | IN = "$in" 17 | NOT_IN = "$nin" 18 | EQUAL = "$eq" 19 | NOT_EQUAL = "$ne" 20 | PATTERN = "$regex" 21 | WHERE = "$where" 22 | GREATER_THAN_EQUAL = "$gte" 23 | GREATER_THAN = "$gt" 24 | LESS_THAN_EQUAL = "$lte" 25 | LESS_THAN = "$lt" 26 | AND = "$and" 27 | OR = "$or" 28 | NOR = "$nor" 29 | NOT = "$not" 30 | EXISTS = "$exists" 31 | STARTSWITH = "startswith" 32 | ENDSWITH = "endswith" 33 | ISTARTSWITH = "istartswith" 34 | IENDSWITH = "iendswith" 35 | 36 | def __str__(self) -> str: 37 | return self.value 38 | 39 | def __repr__(self) -> str: 40 | return str(self) 41 | -------------------------------------------------------------------------------- /mongoz/utils/inspect.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from inspect import isclass 3 | from typing import Any, Callable 4 | 5 | from typing_extensions import get_origin 6 | 7 | 8 | def func_accepts_kwargs(func: Callable) -> bool: 9 | """ 10 | Checks if a function accepts **kwargs. 11 | """ 12 | return any( 13 | param 14 | for param in inspect.signature(func).parameters.values() 15 | if param.kind == param.VAR_KEYWORD 16 | ) 17 | 18 | 19 | def is_class_and_subclass(value: Any, _type: Any) -> bool: 20 | """ 21 | Checks if a `value` is of type class and subclass. 22 | by checking the origin of the value against the type being 23 | verified. 24 | """ 25 | original = get_origin(value) 26 | if not original and not isclass(value): 27 | return False 28 | 29 | try: 30 | if original: 31 | return original and issubclass(original, _type) 32 | return issubclass(value, _type) 33 | except TypeError: 34 | return False 35 | -------------------------------------------------------------------------------- /mongoz/utils/mixins.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Type, TypeVar 2 | 3 | from mongoz.exceptions import AbstractDocumentError 4 | 5 | if TYPE_CHECKING: 6 | from mongoz import Document 7 | 8 | T = TypeVar("T", bound="Document") 9 | 10 | 11 | def is_operation_allowed(self: Type["Document"]) -> bool: 12 | if self.meta.abstract: 13 | raise AbstractDocumentError( 14 | f"{str(self)} is an abstract class. This operation is not allowed" 15 | ) 16 | return bool(self.meta.abstract is not None and self.meta.abstract is not False) 17 | -------------------------------------------------------------------------------- /scripts/clean: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | if [ -d 'dist' ] ; then 4 | rm -r dist 5 | fi 6 | if [ -d 'site' ] ; then 7 | rm -r site 8 | fi 9 | if [ -d 'site_lang' ] ; then 10 | rm -r site_lang 11 | fi 12 | if [ -d 'htmlcov' ] ; then 13 | rm -r htmlcov 14 | fi 15 | if [ -d 'mongoz.egg-info' ] ; then 16 | rm -r mongoz.egg-info 17 | fi 18 | if [ -d '.hypothesis' ] ; then 19 | rm -r .hypothesis 20 | fi 21 | if [ -d '.mypy_cache' ] ; then 22 | rm -r .mypy_cache 23 | fi 24 | if [ -d '.pytest_cache' ] ; then 25 | rm -r .pytest_cache 26 | fi 27 | if [ -d '.ruff_cache' ] ; then 28 | rm -r .ruff_cache 29 | fi 30 | 31 | find mongoz -type f -name "*.py[co]" -delete 32 | find mongoz -type d -name __pycache__ -delete 33 | -------------------------------------------------------------------------------- /scripts/install: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # Use the Python executable provided from the `-p` option, or default to `python3` 4 | [ "$1" = "-p" ] && PYTHON=$2 || PYTHON="python3" 5 | 6 | set -x 7 | 8 | # If inside a virtualenv, use it; otherwise, let uv handle env creation 9 | if [ "$VIRTUAL_ENV" != '' ]; then 10 | UV="uv" 11 | elif [ -z "$GITHUB_ACTIONS" ]; then 12 | # Local dev, create a new .venv if not already active 13 | uv venv 14 | . .venv/bin/activate 15 | UV="uv" 16 | else 17 | # On CI, assume global Python and uv are pre-installed 18 | UV="uv" 19 | fi 20 | 21 | # Install the project in editable mode with extras 22 | $UV venv 23 | $UV pip install hatch 24 | $UV pip install -e ".[testing]" 25 | -------------------------------------------------------------------------------- /scripts/publish: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | VERSION_FILE="mongoz/__init__.py" 4 | 5 | if [ -d 'venv' ] ; then 6 | PREFIX="venv/bin/" 7 | else 8 | PREFIX="" 9 | fi 10 | 11 | if [ ! -z "$GITHUB_ACTIONS" ]; then 12 | git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" 13 | git config --local user.name "GitHub Action" 14 | 15 | VERSION=`grep __version__ ${VERSION_FILE} | grep -o '[0-9][^"]*'` 16 | 17 | if [ "refs/tags/${VERSION}" != "${GITHUB_REF}" ] ; then 18 | echo "GitHub Ref '${GITHUB_REF}' did not match package version '${VERSION}'" 19 | exit 1 20 | fi 21 | fi 22 | 23 | set -x 24 | 25 | hatch run twine upload dist/* 26 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import typing 3 | 4 | import pytest 5 | 6 | from mongoz.core.connection.registry import Registry 7 | from tests.settings import TEST_DATABASE_URL 8 | 9 | database_uri = TEST_DATABASE_URL 10 | client = Registry(database_uri, event_loop=asyncio.get_running_loop) 11 | 12 | 13 | @pytest.fixture(scope="module") 14 | def anyio_backend(): 15 | return ("asyncio", {"debug": False}) 16 | 17 | 18 | @pytest.fixture(scope="session") 19 | def event_loop() -> typing.Generator[asyncio.AbstractEventLoop, None, None]: 20 | loop = asyncio.get_event_loop_policy().new_event_loop() 21 | yield loop 22 | loop.close() 23 | 24 | 25 | @pytest.fixture(autouse=True) 26 | async def test_database() -> typing.AsyncGenerator: 27 | await client.drop_database("test_db") 28 | yield 29 | await client.drop_database("test_db") 30 | -------------------------------------------------------------------------------- /tests/databases/test_database.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from tests.conftest import client 4 | 5 | db = client.get_database("test_db") 6 | collection = db.get_collection("movies") 7 | pytestmark = pytest.mark.asyncio 8 | 9 | 10 | async def test_client_class() -> None: 11 | assert client.host == "localhost" 12 | assert client.port == 27017 13 | 14 | await client.drop_database("sample") 15 | await client.drop_database(db) 16 | 17 | databases = await client.get_databases() 18 | assert "admin" in [db.name for db in databases] 19 | 20 | 21 | async def test_database_class() -> None: 22 | collection = await db._db.create_collection("movies") 23 | 24 | collections = await db.get_collections() 25 | assert collections[0].name == collection.name 26 | -------------------------------------------------------------------------------- /tests/embedded_documents/test_embedded_doc_decimal.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import pytest 4 | 5 | import mongoz 6 | from mongoz import Document, EmbeddedDocument 7 | from tests.conftest import client 8 | 9 | pytestmark = pytest.mark.anyio 10 | 11 | 12 | class Actor(EmbeddedDocument): 13 | name: str = mongoz.String() 14 | price: float = mongoz.Decimal(max_digits=5, decimal_places=2, null=True) 15 | 16 | 17 | class Movie(Document): 18 | actors: List[Actor] = mongoz.Array(Actor) 19 | 20 | class Meta: 21 | registry = client 22 | database = "test_db" 23 | 24 | 25 | async def test_embedded_model() -> None: 26 | actor = Actor(name="Tom Hanks", price=100.00) 27 | 28 | await Movie( 29 | actors=[actor], 30 | name="Saving Private Ryan", 31 | ).create() 32 | 33 | movie = await Movie.objects.last() 34 | 35 | assert movie.actors[0].name == "Tom Hanks" 36 | assert float(str(movie.actors[0].price)) == 100.00 37 | -------------------------------------------------------------------------------- /tests/embedded_documents/test_embedded_documents.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import pytest 4 | 5 | import mongoz 6 | from mongoz import Document, EmbeddedDocument 7 | from mongoz.exceptions import InvalidKeyError 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | 12 | 13 | class Award(EmbeddedDocument): 14 | name: str = mongoz.String() 15 | 16 | 17 | class Crew(EmbeddedDocument): 18 | award: Award = mongoz.Embed(Award) 19 | name: str = mongoz.String() 20 | 21 | 22 | class Actor(EmbeddedDocument): 23 | name: str = mongoz.String() 24 | 25 | 26 | class Genre(EmbeddedDocument): 27 | title: str = mongoz.String() 28 | 29 | 30 | class Movie(Document): 31 | actors: List[Actor] = mongoz.Array(Actor) 32 | director: Crew = mongoz.Embed(Crew) 33 | name: str = mongoz.String() 34 | genre: Genre = mongoz.Embed(Genre) 35 | year: int = mongoz.Integer() 36 | 37 | class Meta: 38 | registry = client 39 | database = "test_db" 40 | 41 | 42 | async def test_embedded_model() -> None: 43 | actor = Actor(name="Tom Hanks") 44 | genre = Genre(title="Action") 45 | award = Award(name="Academy Award") 46 | director = Crew(name="Steven Spielberg", award=award) 47 | 48 | await Movie( 49 | actors=[actor], 50 | name="Saving Private Ryan", 51 | director=director, 52 | year=1990, 53 | genre=genre, 54 | ).create() 55 | 56 | movie = await Movie.query(Movie.genre.title == "Action").get() 57 | assert movie.name == "Saving Private Ryan" 58 | 59 | movie = await Movie.query({Movie.genre.title: "Action"}).get() 60 | assert movie.name == "Saving Private Ryan" 61 | 62 | movie = await Movie.query({"genre.title": "Action"}).get() 63 | assert movie.name == "Saving Private Ryan" 64 | 65 | movie = await Movie.query(Movie.genre == genre).get() 66 | assert movie.name == "Saving Private Ryan" 67 | 68 | movie = await Movie.query(Movie.actors == [actor]).get() 69 | assert movie.name == "Saving Private Ryan" 70 | 71 | movie = await Movie.query(Movie.director.award.name == "Academy Award").get() 72 | assert movie.name == "Saving Private Ryan" 73 | 74 | movie = await Movie.query(Movie.director.award == award).get() 75 | assert movie.name == "Saving Private Ryan" 76 | 77 | with pytest.raises(InvalidKeyError): 78 | await Movie.query(Movie.director.award.year == 1990).get() # type: ignore 79 | -------------------------------------------------------------------------------- /tests/embedded_documents/test_nest.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from typing import List 3 | 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, EmbeddedDocument 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | 12 | 13 | class EmployeeSupervisor(EmbeddedDocument): 14 | supervisor: mongoz.ObjectId = mongoz.ObjectId() # Object id of employee document. 15 | effective_from: datetime.datetime = mongoz.DateTime(auto_now=True) 16 | effective_to: datetime.datetime = mongoz.DateTime(auto_now_add=True) 17 | 18 | 19 | class Employee(Document): 20 | full_name: str = mongoz.String(max_length=255) 21 | employee_code: str = mongoz.String(max_length=255) 22 | date_of_joining: datetime.datetime = mongoz.DateTime(null=True) 23 | e_mail: str = mongoz.String(null=True) 24 | mobile_no: str = mongoz.String(null=True) 25 | supervisors: List[EmployeeSupervisor] = mongoz.Array(EmployeeSupervisor) 26 | 27 | class Meta: 28 | registry = client 29 | database = "test_db" 30 | 31 | 32 | async def test_embedded_model() -> None: 33 | sv = EmployeeSupervisor( 34 | supervisor=mongoz.ObjectId(), 35 | effective_from=datetime.datetime.now(), 36 | effective_to=datetime.datetime.now(), 37 | ) 38 | 39 | employee = await Employee.objects.create( 40 | full_name="A name", employee_code="1234", supervisors=[sv] 41 | ) 42 | 43 | assert employee.supervisors[0].supervisor == sv.supervisor 44 | -------------------------------------------------------------------------------- /tests/indexes/conftest.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import typing 3 | 4 | import pytest 5 | 6 | from mongoz.core.connection.registry import Registry 7 | from tests.settings import TEST_DATABASE_URL 8 | 9 | database_uri = TEST_DATABASE_URL 10 | client = Registry(database_uri, event_loop=asyncio.get_running_loop) 11 | 12 | 13 | @pytest.fixture(scope="module") 14 | def anyio_backend(): 15 | return ("asyncio", {"debug": False}) 16 | 17 | 18 | @pytest.fixture(scope="session") 19 | def event_loop() -> typing.Generator[asyncio.AbstractEventLoop, None, None]: 20 | loop = asyncio.get_event_loop_policy().new_event_loop() 21 | yield loop 22 | loop.close() 23 | 24 | 25 | @pytest.fixture(autouse=True) 26 | async def test_database() -> typing.AsyncGenerator: 27 | yield 28 | await client.drop_database("test_db") 29 | await client.drop_database("test_my_db") 30 | await client.drop_database("test_second_db") 31 | -------------------------------------------------------------------------------- /tests/indexes/test_indexes.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import pytest 4 | 5 | import mongoz 6 | from mongoz import Document, Index, IndexType, ObjectId, Order 7 | from mongoz.exceptions import InvalidKeyError 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | 12 | indexes = [ 13 | Index("name", unique=True), 14 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 15 | ] 16 | 17 | 18 | class Movie(Document): 19 | name: str = mongoz.String() 20 | year: int = mongoz.Integer() 21 | uuid: Optional[ObjectId] = mongoz.ObjectId(null=True) 22 | 23 | class Meta: 24 | registry = client 25 | indexes = indexes 26 | database = "test_db" 27 | 28 | 29 | async def test_create_indexes() -> None: 30 | index_names = await Movie.create_indexes() 31 | assert index_names == ["name", "year_genre"] 32 | 33 | await Movie.drop_indexes() 34 | 35 | index = await Movie.create_index("name") 36 | assert index == "name" 37 | 38 | with pytest.raises(InvalidKeyError): 39 | await Movie.create_index("random_index") 40 | -------------------------------------------------------------------------------- /tests/indexes/test_indexes_auto_generate_true.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import pytest 4 | from pymongo.errors import DuplicateKeyError 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | 12 | indexes = [ 13 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 14 | ] 15 | 16 | 17 | class AnotherMovie(Document): 18 | name: str = mongoz.String() 19 | email: str = mongoz.Email(index=True, unique=True) 20 | year: int = mongoz.Integer() 21 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 22 | 23 | class Meta: 24 | registry = client 25 | indexes = indexes 26 | database = "test_db" 27 | autogenerate_index = True 28 | 29 | 30 | async def test_raises_duplicate_error() -> None: 31 | await AnotherMovie.create_indexes() 32 | movie = await AnotherMovie.objects.create(name="Mongoz", email="mongoz@mongoz.com", year=2023) 33 | 34 | assert movie is not None 35 | 36 | with pytest.raises(DuplicateKeyError): 37 | await AnotherMovie.objects.create(name="Mongoz", email="mongoz@mongoz.com", year=2023) 38 | -------------------------------------------------------------------------------- /tests/indexes/test_indexes_duplicate_error.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import pytest 4 | from pymongo.errors import DuplicateKeyError 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | 12 | indexes = [ 13 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 14 | ] 15 | 16 | 17 | class AnotherMovie(Document): 18 | name: str = mongoz.String() 19 | email: str = mongoz.Email(index=True, unique=True) 20 | year: int = mongoz.Integer() 21 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 22 | 23 | class Meta: 24 | registry = client 25 | indexes = indexes 26 | database = "test_db" 27 | 28 | 29 | async def test_raises_duplicate_error() -> None: 30 | await AnotherMovie.create_indexes() 31 | movie = await AnotherMovie.objects.create( 32 | name="Mongoz", email="mongoz@mongoz.com", year=2023 33 | ) 34 | 35 | assert movie is not None 36 | 37 | with pytest.raises(DuplicateKeyError): 38 | await AnotherMovie.objects.create( 39 | name="Mongoz", email="mongoz@mongoz.com", year=2023 40 | ) 41 | -------------------------------------------------------------------------------- /tests/indexes/test_indexes_from_field.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import pytest 4 | 5 | import mongoz 6 | from mongoz import Document, Index, IndexType, ObjectId, Order 7 | from mongoz.exceptions import InvalidKeyError 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | 12 | indexes = [ 13 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 14 | ] 15 | 16 | 17 | class Movie(Document): 18 | name: str = mongoz.String(index=True, unique=True) 19 | year: int = mongoz.Integer() 20 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 21 | 22 | class Meta: 23 | registry = client 24 | indexes = indexes 25 | database = "test_db" 26 | 27 | 28 | async def test_create_indexes() -> None: 29 | index_names = [index.name for index in Movie.meta.indexes] 30 | assert index_names == ["name", "year_genre"] 31 | 32 | await Movie.drop_indexes() 33 | 34 | index = await Movie.create_index("name") 35 | assert index == "name" 36 | 37 | with pytest.raises(InvalidKeyError): 38 | await Movie.create_index("random_index") 39 | -------------------------------------------------------------------------------- /tests/indexes/test_indexes_raise_error.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | import pytest 4 | 5 | import mongoz 6 | from mongoz import Document, Index, IndexType, ObjectId, Order 7 | from mongoz.exceptions import IndexError 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | 12 | 13 | async def test_create_indexes() -> None: 14 | with pytest.raises(IndexError): 15 | 16 | class Movie(Document): 17 | name: str = mongoz.String(index=True, unique=True) 18 | year: int = mongoz.Integer() 19 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 20 | 21 | class Meta: 22 | registry = client 23 | indexes = [ 24 | Index("name", unique=True), 25 | Index( 26 | keys=[ 27 | ("year", Order.DESCENDING), 28 | ("genre", IndexType.HASHED), 29 | ] 30 | ), 31 | ] 32 | database = "test_db" 33 | -------------------------------------------------------------------------------- /tests/indexes/test_unique.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator 2 | 3 | import pydantic 4 | import pytest 5 | from pymongo.errors import DuplicateKeyError 6 | 7 | import mongoz 8 | from mongoz import Document 9 | from tests.conftest import client 10 | 11 | pytestmark = pytest.mark.anyio 12 | pydantic_version = pydantic.__version__[:3] 13 | 14 | 15 | class Movie(Document): 16 | name: str = mongoz.String(unique=True) 17 | is_published: bool = mongoz.Boolean(default=False) 18 | 19 | class Meta: 20 | registry = client 21 | database = "test_db" 22 | 23 | 24 | @pytest.fixture(scope="function", autouse=True) 25 | async def prepare_database() -> AsyncGenerator: 26 | await Movie.drop_indexes(force=True) 27 | await Movie.objects.delete() 28 | await Movie.create_indexes() 29 | yield 30 | await Movie.drop_indexes(force=True) 31 | await Movie.objects.delete() 32 | await Movie.create_indexes() 33 | 34 | 35 | async def test_unique(): 36 | movie = await Movie.objects.create(name="Barbie") 37 | 38 | assert movie.is_published is False 39 | 40 | with pytest.raises(DuplicateKeyError): 41 | await Movie.objects.create(name="Barbie") 42 | -------------------------------------------------------------------------------- /tests/inheritance/test_abs_override.py: -------------------------------------------------------------------------------- 1 | from typing import ClassVar, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, ObjectId 8 | from mongoz.core.db.querysets.base import Manager 9 | from tests.conftest import client 10 | 11 | pytestmark = pytest.mark.anyio 12 | pydantic_version = pydantic.__version__[:3] 13 | 14 | indexes = [ 15 | Index("name", unique=True), 16 | Index("year", unique=True), 17 | ] 18 | 19 | 20 | class CustomManager(Manager): ... 21 | 22 | 23 | class BaseDocument(Document): 24 | objects: ClassVar[CustomManager] = CustomManager() 25 | 26 | class Meta: 27 | abstract = True 28 | registry = client 29 | database = "test_db" 30 | 31 | 32 | class Movie(BaseDocument): 33 | all_manager: ClassVar[CustomManager] = CustomManager() 34 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 35 | name: str = mongoz.String() 36 | year: int = mongoz.Integer() 37 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 38 | 39 | 40 | @pytest.fixture(scope="function", autouse=True) 41 | async def prepare_database(): 42 | await Movie.query().delete() 43 | yield 44 | await Movie.query().delete() 45 | 46 | 47 | async def test_custom_abstraction(): 48 | await Movie.all_manager.create(name="Barbie", year=2023) 49 | 50 | total = await Movie.objects.count() 51 | 52 | assert total == 1 53 | -------------------------------------------------------------------------------- /tests/inheritance/test_abstract_class.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, ObjectId 8 | from mongoz.exceptions import AbstractDocumentError 9 | from tests.conftest import client 10 | 11 | pytestmark = pytest.mark.anyio 12 | pydantic_version = pydantic.__version__[:3] 13 | 14 | indexes = [ 15 | Index("name", unique=True), 16 | Index("year", unique=True), 17 | ] 18 | 19 | 20 | class BaseDocument(Document): 21 | name: str = mongoz.String() 22 | year: int = mongoz.Integer() 23 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 24 | 25 | class Meta: 26 | abstract = True 27 | registry = client 28 | database = "test_db" 29 | 30 | 31 | class Movie(BaseDocument): 32 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 33 | 34 | class Meta: 35 | indexes = indexes 36 | 37 | 38 | async def test_abstract_error() -> None: 39 | with pytest.raises(AbstractDocumentError): 40 | await BaseDocument.query().all() 41 | 42 | with pytest.raises(AbstractDocumentError): 43 | await BaseDocument(name="Barbie", year=2003).create() 44 | -------------------------------------------------------------------------------- /tests/inheritance/test_inheritance.py: -------------------------------------------------------------------------------- 1 | from typing import Any, AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, ObjectId 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | 14 | class BaseDocument(Document): 15 | name: str = mongoz.String() 16 | year: int = mongoz.Integer() 17 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 18 | 19 | class Meta: 20 | abstract = True 21 | registry = client 22 | database = "test_db" 23 | 24 | 25 | class Movie(BaseDocument): 26 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 27 | 28 | 29 | class Actor(BaseDocument): 30 | movies: Optional[List[Any]] = mongoz.ArrayList(null=True) 31 | 32 | 33 | @pytest.fixture(scope="function", autouse=True) 34 | async def prepare_database() -> AsyncGenerator: 35 | await Movie.query().delete() 36 | await Actor.query().delete() 37 | yield 38 | await Movie.query().delete() 39 | await Actor.query().delete() 40 | 41 | 42 | async def test_model_all() -> None: 43 | movies = await Movie.query().all() 44 | assert len(movies) == 0 45 | 46 | await Movie(name="Barbie", year=2003).create() 47 | 48 | movies = await Movie.query().all() 49 | assert len(movies) == 1 50 | 51 | cursor = Movie.query() 52 | async for movie in cursor: 53 | assert movie.name == "Barbie" 54 | 55 | 56 | async def test_model_nested_inherited() -> None: 57 | actors = await Actor.query().all() 58 | assert len(actors) == 0 59 | 60 | await Actor( 61 | name="Paul Rudd", year=1972, movies=["Only Murders in the Building"] 62 | ).create() 63 | 64 | actors = await Actor.query().all() 65 | assert len(actors) == 1 66 | 67 | actors = Actor.query() 68 | async for movie in actors: 69 | assert movie.name == "Paul Rudd" 70 | assert movie.movies[0] == "Only Murders in the Building" 71 | -------------------------------------------------------------------------------- /tests/inheritance/test_inheritance_non_abstract.py: -------------------------------------------------------------------------------- 1 | from typing import Any, AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, ObjectId 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | 14 | class BaseDocument(Document): 15 | name: str = mongoz.String() 16 | year: int = mongoz.Integer() 17 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 18 | 19 | class Meta: 20 | registry = client 21 | database = "test_db" 22 | 23 | 24 | class Movie(BaseDocument): 25 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 26 | 27 | 28 | class Actor(BaseDocument): 29 | movies: Optional[List[Any]] = mongoz.ArrayList(null=True) 30 | 31 | 32 | @pytest.fixture(scope="function", autouse=True) 33 | async def prepare_database() -> AsyncGenerator: 34 | await BaseDocument.query().delete() 35 | await Movie.query().delete() 36 | await Actor.query().delete() 37 | yield 38 | await BaseDocument.query().delete() 39 | await Movie.query().delete() 40 | await Actor.query().delete() 41 | 42 | 43 | async def test_model_non_abstract() -> None: 44 | movies = await BaseDocument.query().all() 45 | assert len(movies) == 0 46 | 47 | await BaseDocument(name="Barbie", year=2003).create() 48 | 49 | movies = await BaseDocument.query().all() 50 | assert len(movies) == 1 51 | 52 | cursor = BaseDocument.query() 53 | async for movie in cursor: 54 | assert movie.name == "Barbie" 55 | 56 | 57 | async def test_model_all() -> None: 58 | movies = await Movie.query().all() 59 | assert len(movies) == 0 60 | 61 | await Movie(name="Barbie", year=2003).create() 62 | 63 | movies = await Movie.query().all() 64 | assert len(movies) == 1 65 | 66 | cursor = Movie.query() 67 | async for movie in cursor: 68 | assert movie.name == "Barbie" 69 | 70 | 71 | async def test_model_nested_inherited() -> None: 72 | actors = await Actor.query().all() 73 | assert len(actors) == 0 74 | 75 | await Actor( 76 | name="Paul Rudd", year=1972, movies=["Only Murders in the Building"] 77 | ).create() 78 | 79 | actors = await Actor.query().all() 80 | assert len(actors) == 1 81 | 82 | actors = Actor.query() 83 | async for movie in actors: 84 | assert movie.name == "Paul Rudd" 85 | assert movie.movies[0] == "Only Murders in the Building" 86 | -------------------------------------------------------------------------------- /tests/inheritance/test_inherited_client_and_database.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, ObjectId 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index("year", unique=True), 16 | ] 17 | 18 | 19 | class BaseDocument(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | 24 | class Meta: 25 | abstract = True 26 | registry = client 27 | database = "test_db" 28 | 29 | 30 | class Movie(BaseDocument): 31 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 32 | 33 | class Meta: 34 | indexes = indexes 35 | 36 | 37 | @pytest.fixture(scope="function", autouse=True) 38 | async def prepare_database() -> AsyncGenerator: 39 | await Movie.drop_indexes(force=True) 40 | await Movie.query().delete() 41 | await Movie.create_indexes() 42 | yield 43 | await Movie.drop_indexes(force=True) 44 | await Movie.query().delete() 45 | await Movie.create_indexes() 46 | 47 | 48 | async def test_model_all() -> None: 49 | movies = await Movie.query().all() 50 | assert len(movies) == 0 51 | 52 | await Movie(name="Barbie", year=2003).create() 53 | 54 | movies = await Movie.query().all() 55 | assert len(movies) == 1 56 | 57 | cursor = Movie.query() 58 | async for movie in cursor: 59 | assert movie.name == "Barbie" 60 | 61 | 62 | async def test_model_inherited_fields() -> None: 63 | assert Movie.meta.database == BaseDocument.meta.database 64 | assert Movie.meta.registry == BaseDocument.meta.registry 65 | assert Movie.meta.abstract is False 66 | assert BaseDocument.meta.abstract is True 67 | -------------------------------------------------------------------------------- /tests/integrations/test_esmerald.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import pytest 4 | from esmerald import Esmerald, Gateway, JSONResponse, Request, get, post 5 | from esmerald.testclient import EsmeraldTestClient 6 | 7 | import mongoz 8 | from mongoz import Document 9 | from tests.conftest import client 10 | 11 | pytestmark = pytest.mark.anyio 12 | 13 | 14 | class Movie(Document): 15 | name: str = mongoz.String() 16 | year: int = mongoz.Integer() 17 | 18 | class Meta: 19 | registry = client 20 | database = "test_db" 21 | 22 | 23 | @pytest.fixture(scope="module", autouse=True) 24 | async def prepare_database() -> None: 25 | await Movie.query().delete() 26 | 27 | 28 | @post("/create") 29 | async def create_movies(request: Request) -> JSONResponse: 30 | data = await request.json() 31 | movie = await Movie(**data).create() 32 | return JSONResponse(json.loads(movie.model_dump_json())) 33 | 34 | 35 | @get("/all") 36 | async def get_movies(request: Request) -> JSONResponse: 37 | movie = await Movie.query().get() 38 | return JSONResponse(json.loads(movie.model_dump_json())) 39 | 40 | 41 | app = Esmerald( 42 | routes=[ 43 | Gateway(handler=get_movies), 44 | Gateway(handler=create_movies), 45 | ] 46 | ) 47 | 48 | 49 | async def test_esmerald_integration_create() -> None: 50 | with EsmeraldTestClient(app) as client: 51 | response = client.post("/create", json={"name": "Barbie", "year": 2023}) 52 | 53 | assert response.json()["name"] == "Barbie" 54 | assert response.json()["year"] == 2023 55 | assert response.status_code == 201 56 | 57 | 58 | async def test_esmerald_integration_read() -> None: 59 | with EsmeraldTestClient(app) as client: 60 | client.post("/create", json={"name": "Barbie", "year": 2023}) 61 | response = client.get("/all") 62 | 63 | assert response.json()["name"] == "Barbie" 64 | assert response.json()["year"] == 2023 65 | assert response.status_code == 200 66 | -------------------------------------------------------------------------------- /tests/integrations/test_lilya.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import pytest 4 | from lilya import status 5 | from lilya.apps import Lilya 6 | from lilya.requests import Request 7 | from lilya.responses import Ok 8 | from lilya.routing import Path 9 | from lilya.testclient import TestClient 10 | 11 | import mongoz 12 | from mongoz import Document 13 | from tests.conftest import client 14 | 15 | pytestmark = pytest.mark.anyio 16 | 17 | 18 | class Movie(Document): 19 | name: str = mongoz.String() 20 | year: int = mongoz.Integer() 21 | 22 | class Meta: 23 | registry = client 24 | database = "test_db" 25 | 26 | 27 | @pytest.fixture(scope="module", autouse=True) 28 | async def prepare_database() -> None: 29 | await Movie.query().delete() 30 | 31 | 32 | async def create_movies(request: Request): 33 | data = await request.json() 34 | movie = await Movie(**data).create() 35 | return Ok( 36 | json.loads(movie.model_dump_json()), 37 | status_code=status.HTTP_201_CREATED, 38 | ) 39 | 40 | 41 | async def get_movies(request: Request): 42 | movie = await Movie.query().get() 43 | return Ok(json.loads(movie.model_dump_json())) 44 | 45 | 46 | app = Lilya( 47 | routes=[ 48 | Path("/all", handler=get_movies), 49 | Path("/create", handler=create_movies, methods=["POST"]), 50 | ] 51 | ) 52 | 53 | 54 | async def test_lilya_integration_create() -> None: 55 | with TestClient(app) as client: 56 | response = client.post("/create", json={"name": "Barbie", "year": 2023}) 57 | 58 | assert response.json()["name"] == "Barbie" 59 | assert response.json()["year"] == 2023 60 | assert response.status_code == 201 61 | 62 | 63 | async def test_lilya_integration_read() -> None: 64 | with TestClient(app) as client: 65 | client.post("/create", json={"name": "Barbie", "year": 2023}) 66 | response = client.get("/all") 67 | 68 | assert response.json()["name"] == "Barbie" 69 | assert response.json()["year"] == 2023 70 | assert response.status_code == 200 71 | -------------------------------------------------------------------------------- /tests/models/manager/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dymmond/mongoz/31651a3112ddfb4cbbd06a0b18c26288eebd969b/tests/models/manager/__init__.py -------------------------------------------------------------------------------- /tests/models/manager/test_all.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | is_published: bool = mongoz.Boolean(default=False) 25 | 26 | class Meta: 27 | registry = client 28 | database = "test_db" 29 | indexes = indexes 30 | 31 | 32 | @pytest.fixture(scope="function", autouse=True) 33 | async def prepare_database() -> AsyncGenerator: 34 | await Movie.drop_indexes(force=True) 35 | await Movie.objects.delete() 36 | await Movie.create_indexes() 37 | yield 38 | await Movie.drop_indexes(force=True) 39 | await Movie.objects.delete() 40 | await Movie.create_indexes() 41 | 42 | 43 | async def test_model_all() -> None: 44 | movies = await Movie.objects.all() 45 | assert len(movies) == 0 46 | 47 | await Movie.objects.create( 48 | name="Forrest Gump", year=2003, is_published=True 49 | ) 50 | 51 | movies = await Movie.objects.all() 52 | assert len(movies) == 1 53 | 54 | cursor = Movie.query() 55 | async for movie in cursor: 56 | assert movie.name == "Forrest Gump" 57 | assert movie.is_published is True 58 | -------------------------------------------------------------------------------- /tests/models/manager/test_array_insert.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Union 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz.core.db import fields 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | 14 | class Student(mongoz.Document): 15 | student_scores: List[Union[str, float]] = fields.ArrayList() 16 | 17 | class Meta: 18 | registry = client 19 | database = "test_db" 20 | 21 | 22 | @pytest.fixture(scope="function", autouse=True) 23 | async def prepare_database() -> AsyncGenerator: 24 | await Student.objects.delete() 25 | yield 26 | await Student.objects.delete() 27 | 28 | 29 | async def test_model_all() -> None: 30 | data = [["202405", 85.45], ["202406", 90.14]] 31 | 32 | await Student.objects.create(student_scores=data) 33 | 34 | student = await Student.objects.last() 35 | 36 | assert student.student_scores == data 37 | -------------------------------------------------------------------------------- /tests/models/manager/test_boolean_default.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | is_published: bool = mongoz.Boolean(default=False) 25 | 26 | class Meta: 27 | registry = client 28 | database = "test_db" 29 | indexes = indexes 30 | 31 | 32 | @pytest.fixture(scope="function", autouse=True) 33 | async def prepare_database() -> AsyncGenerator: 34 | await Movie.drop_indexes(force=True) 35 | await Movie.objects.delete() 36 | await Movie.create_indexes() 37 | yield 38 | await Movie.drop_indexes(force=True) 39 | await Movie.objects.delete() 40 | await Movie.create_indexes() 41 | 42 | 43 | async def test_model_bool_default() -> None: 44 | movies = await Movie.objects.all() 45 | assert len(movies) == 0 46 | 47 | await Movie.objects.create(name="Forrest Gump", year=2003) 48 | 49 | movies = await Movie.objects.all() 50 | assert len(movies) == 1 51 | 52 | cursor = Movie.query() 53 | async for movie in cursor: 54 | assert movie.name == "Forrest Gump" 55 | assert movie.is_published is False 56 | -------------------------------------------------------------------------------- /tests/models/manager/test_choice_field.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import bson 4 | import pydantic 5 | import pytest 6 | 7 | import mongoz 8 | from mongoz import Document, EmbeddedDocument 9 | from tests.conftest import client 10 | 11 | pytestmark = pytest.mark.anyio 12 | pydantic_version = pydantic.__version__[:3] 13 | 14 | 15 | class Producer(EmbeddedDocument): 16 | name: str = mongoz.String() 17 | gender: str = mongoz.String( 18 | choices=( 19 | ("M", "Male"), 20 | ("F", "Female"), 21 | ("O", "Others"), 22 | ) 23 | ) 24 | 25 | 26 | class Movie(Document): 27 | name: str = mongoz.String(index=True, unique=True) 28 | year: int = mongoz.Integer() 29 | movie_type: str = mongoz.String( 30 | choices=( 31 | ("H", "Horror"), 32 | ("C", "Comedy"), 33 | ("T", "Thriller"), 34 | ("K", "Kids"), 35 | ) 36 | ) 37 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 38 | producer: Producer = mongoz.Embed(Producer, null=True) 39 | 40 | class Meta: 41 | registry = client 42 | database = "test_db" 43 | 44 | 45 | @pytest.fixture(scope="function", autouse=True) 46 | async def prepare_database() -> AsyncGenerator: 47 | await Movie.drop_indexes(force=True) 48 | await Movie.objects.delete() 49 | await Movie.create_indexes() 50 | yield 51 | await Movie.drop_indexes(force=True) 52 | await Movie.objects.delete() 53 | await Movie.create_indexes() 54 | 55 | 56 | async def test_model_create() -> None: 57 | producer = Producer(name="Harshali", gender="F") 58 | movie = await Movie.objects.create( 59 | name="Barbie", year=2023, movie_type="K", producer=producer 60 | ) 61 | assert movie.name == "Barbie" 62 | assert movie.year == 2023 63 | assert movie.movie_type == "K" 64 | assert isinstance(movie.id, bson.ObjectId) 65 | assert movie.producer.name == "Harshali" 66 | assert movie.producer.gender == "F" 67 | assert movie.get_movie_type_display() == "Kids" 68 | assert movie.producer.get_gender_display() == "Female" 69 | 70 | try: 71 | movie = await Movie.objects.create( 72 | name="Barbie Part2", 73 | year=2024, 74 | movie_type="K2", 75 | ) 76 | except ValueError as exc: 77 | assert ( 78 | exc.__str__() 79 | == "Invalid choice for field 'movie_type'. Input provided as 'K2'" 80 | ) 81 | -------------------------------------------------------------------------------- /tests/models/manager/test_count.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | 25 | class Meta: 26 | registry = client 27 | database = "test_db" 28 | indexes = indexes 29 | 30 | 31 | @pytest.fixture(scope="function", autouse=True) 32 | async def prepare_database() -> AsyncGenerator: 33 | await Movie.drop_indexes(force=True) 34 | await Movie.objects.delete() 35 | await Movie.create_indexes() 36 | yield 37 | await Movie.drop_indexes(force=True) 38 | await Movie.objects.delete() 39 | await Movie.create_indexes() 40 | 41 | 42 | async def test_model_count() -> None: 43 | count = await Movie.objects.count() 44 | assert count == 0 45 | 46 | await Movie.objects.create(name="Batman", year=2013) 47 | 48 | count = await Movie.objects.filter(year=2013).count() 49 | assert count == 1 50 | -------------------------------------------------------------------------------- /tests/models/manager/test_create.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import bson 4 | import pydantic 5 | import pytest 6 | from pydantic import ValidationError 7 | from pymongo import errors 8 | 9 | import mongoz 10 | from mongoz import Document, Index, IndexType, ObjectId, Order 11 | from tests.conftest import client 12 | 13 | pytestmark = pytest.mark.anyio 14 | pydantic_version = pydantic.__version__[:3] 15 | 16 | indexes = [ 17 | Index("name", unique=True), 18 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 19 | ] 20 | 21 | 22 | class Movie(Document): 23 | name: str = mongoz.String() 24 | year: int = mongoz.Integer() 25 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 26 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 27 | 28 | class Meta: 29 | registry = client 30 | database = "test_db" 31 | indexes = indexes 32 | 33 | 34 | @pytest.fixture(scope="function", autouse=True) 35 | async def prepare_database() -> AsyncGenerator: 36 | await Movie.drop_indexes(force=True) 37 | await Movie.objects.delete() 38 | await Movie.create_indexes() 39 | yield 40 | await Movie.drop_indexes(force=True) 41 | await Movie.objects.delete() 42 | await Movie.create_indexes() 43 | 44 | 45 | async def test_model_create() -> None: 46 | movie = await Movie.objects.create(name="Barbie", year=2023) 47 | assert movie.name == "Barbie" 48 | assert movie.year == 2023 49 | assert isinstance(movie.id, bson.ObjectId) 50 | 51 | with pytest.raises(ValidationError) as exc: 52 | await Movie.objects.create(name="Justice League", year=2017, uuid="1") 53 | 54 | error = exc.value.errors()[0] 55 | 56 | assert error["type"] == "uuid_parsing" 57 | assert error["loc"] == ("uuid",) 58 | assert ( 59 | error["msg"] 60 | == "Input should be a valid UUID, invalid length: expected length 32 for simple format, found 1" 61 | ) 62 | assert error["input"] == "1" 63 | 64 | with pytest.raises(errors.DuplicateKeyError): 65 | await Movie.objects.create(name="Barbie", year=2023) 66 | -------------------------------------------------------------------------------- /tests/models/manager/test_create_on_save.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, ObjectId 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | 14 | class Movie(Document): 15 | name: str = mongoz.String() 16 | year: int = mongoz.Integer() 17 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 18 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 19 | 20 | class Meta: 21 | registry = client 22 | database = "test_db" 23 | 24 | 25 | @pytest.fixture(scope="function", autouse=True) 26 | async def prepare_database() -> AsyncGenerator: 27 | await Movie.objects.delete() 28 | yield 29 | await Movie.objects.delete() 30 | 31 | 32 | async def test_model_create_id_none() -> None: 33 | movie = await Movie.objects.create(name="Barbie", year=2023) 34 | movie.id = None 35 | 36 | await movie.save() 37 | 38 | movies = await Movie.objects.all() 39 | 40 | assert len(movies) == 2 41 | -------------------------------------------------------------------------------- /tests/models/manager/test_decimal.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from decimal import Decimal 3 | from typing import AsyncGenerator 4 | 5 | import bson 6 | import pydantic 7 | import pytest 8 | from bson import Decimal128 9 | 10 | import mongoz 11 | from mongoz import Document 12 | from tests.conftest import client 13 | 14 | pytestmark = pytest.mark.anyio 15 | pydantic_version = pydantic.__version__[:3] 16 | 17 | 18 | class Archive(Document): 19 | name: str = mongoz.String() 20 | price: Decimal = mongoz.Decimal(max_digits=5, decimal_places=2, null=True) 21 | 22 | class Meta: 23 | registry = client 24 | database = "test_db" 25 | 26 | 27 | @pytest.fixture(scope="function", autouse=True) 28 | async def prepare_database() -> AsyncGenerator: 29 | await Archive.objects.delete() 30 | yield 31 | await Archive.objects.delete() 32 | 33 | 34 | async def test_decimal_128() -> None: 35 | await Archive.objects.create(name="Batman", price=22.246) 36 | 37 | arch = await Archive.objects.last() 38 | assert float(str(arch.price)) == 22.246 39 | 40 | 41 | async def test_decimal_128_two() -> None: 42 | await Archive.objects.create(name="Batman", price="22.246") 43 | 44 | arch = await Archive.objects.last() 45 | assert float(str(arch.price)) == 22.246 46 | 47 | 48 | @pytest.mark.skipif(sys.version_info < (3, 10), reason="zip() implementation refactored in 3.10+") 49 | async def test_decimal_128_create_many() -> None: 50 | archives = [] 51 | archive_names = ("The Dark Knight", "The Dark Knight Rises", "The Godfather") 52 | for movie_name in archive_names: 53 | archives.append(Archive(name=movie_name, price=Decimal("22.246"))) 54 | 55 | archives_db = await Archive.objects.create_many(archives) 56 | for archive, archive_db in zip(archives, archives_db): 57 | assert archive.name == archive_db.name 58 | assert archive.price == archive_db.price 59 | assert isinstance(archive.id, bson.ObjectId) 60 | 61 | 62 | async def test_decimal_on_update() -> None: 63 | await Archive.objects.create(name="Batman", price="22.246") 64 | 65 | arch = await Archive.objects.last() 66 | 67 | arch.price = Decimal("28") 68 | await arch.save() 69 | 70 | arch = await Archive.objects.last() 71 | 72 | assert arch.price == Decimal128("28") 73 | 74 | await arch.update(price=Decimal("30")) 75 | 76 | arch = await Archive.objects.last() 77 | 78 | assert arch.price == Decimal128("30") 79 | 80 | await Archive.objects.filter().update(price=Decimal("40")) 81 | 82 | arch = await Archive.objects.last() 83 | 84 | assert arch.price == Decimal128("40") 85 | -------------------------------------------------------------------------------- /tests/models/manager/test_decimal_update.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from typing import AsyncGenerator, List 3 | 4 | import pydantic 5 | import pytest 6 | 7 | import mongoz 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | 14 | class Badges(mongoz.EmbeddedDocument): 15 | score: Decimal = mongoz.Decimal(max_digits=5, decimal_places=2, null=True) 16 | name: str = mongoz.String() 17 | 18 | 19 | class Achievement(mongoz.Document): 20 | name: str = mongoz.String() 21 | total_score: Decimal = mongoz.Decimal(max_digits=5, decimal_places=2, null=True) # noqa 22 | badges: List[Badges] = mongoz.Array(Badges, null=True) 23 | 24 | class Meta: 25 | registry = client 26 | database = "test_db" 27 | 28 | 29 | @pytest.fixture(scope="function", autouse=True) 30 | async def prepare_database() -> AsyncGenerator: 31 | await Achievement.objects.delete() 32 | yield 33 | await Achievement.objects.delete() 34 | 35 | 36 | async def test_decimal_on_update() -> None: 37 | badges = [{"name": "badge1", "score": "32083.33"}] 38 | await Achievement.objects.create(name="Batman", total_score="22.246") 39 | 40 | arch = await Achievement.objects.last() 41 | 42 | arch.total_score = Decimal("28") 43 | await arch.save() 44 | 45 | arch = await Achievement.objects.last() 46 | 47 | await arch.update(total_score=Decimal("30")) 48 | 49 | arch = await Achievement.objects.last() 50 | 51 | await Achievement.objects.filter().update(total_score=Decimal("40"), badges=badges) 52 | 53 | arch = await Achievement.objects.last() 54 | -------------------------------------------------------------------------------- /tests/models/manager/test_delete.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | 25 | class Meta: 26 | registry = client 27 | database = "test_db" 28 | indexes = indexes 29 | 30 | 31 | @pytest.fixture(scope="function", autouse=True) 32 | async def prepare_database() -> AsyncGenerator: 33 | await Movie.drop_indexes(force=True) 34 | await Movie.objects.delete() 35 | await Movie.create_indexes() 36 | yield 37 | await Movie.drop_indexes(force=True) 38 | await Movie.objects.delete() 39 | await Movie.create_indexes() 40 | 41 | 42 | async def test_model_delete() -> None: 43 | await Movie.objects.create(name="Barbie", year=2023) 44 | await Movie.objects.create(name="Batman", year=2022) 45 | 46 | count = await Movie.objects.filter(name="Batman").delete() 47 | assert count == 1 48 | 49 | count = await Movie.objects.delete() 50 | assert count == 1 51 | 52 | movie = await Movie.objects.create(name="Batman", year=2013) 53 | count = await movie.delete() 54 | assert count == 1 55 | -------------------------------------------------------------------------------- /tests/models/manager/test_distinct_values.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import AsyncGenerator, List, Optional 3 | 4 | import pydantic 5 | import pytest 6 | 7 | import mongoz 8 | from mongoz import Document, ObjectId 9 | from tests.conftest import client 10 | 11 | pytestmark = pytest.mark.anyio 12 | pydantic_version = pydantic.__version__[:3] 13 | 14 | 15 | class Movie(Document): 16 | name: str = mongoz.String() 17 | year: int = mongoz.Integer() 18 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 19 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 20 | created_at: datetime = mongoz.DateTime(auto_now=True) 21 | 22 | class Meta: 23 | registry = client 24 | database = "test_db" 25 | 26 | 27 | @pytest.fixture(scope="function", autouse=True) 28 | async def prepare_database() -> AsyncGenerator: 29 | await Movie.drop_indexes(force=True) 30 | await Movie.objects.delete() 31 | yield 32 | await Movie.drop_indexes(force=True) 33 | await Movie.objects.delete() 34 | 35 | 36 | async def test_model_distinct() -> None: 37 | await Movie(name="Batman", year=2022).create() 38 | await Movie(name="Barbie", year=2023).create() 39 | 40 | movies = await Movie.objects.distinct_values("name") 41 | assert len(movies) == 2 42 | 43 | assert "Barbie" in movies 44 | assert "Batman" in movies 45 | -------------------------------------------------------------------------------- /tests/models/manager/test_exists.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | is_published: bool = mongoz.Boolean(default=False) 25 | 26 | class Meta: 27 | registry = client 28 | database = "test_db" 29 | indexes = indexes 30 | 31 | 32 | @pytest.fixture(scope="function", autouse=True) 33 | async def prepare_database() -> AsyncGenerator: 34 | await Movie.drop_indexes(force=True) 35 | await Movie.objects.delete() 36 | await Movie.create_indexes() 37 | yield 38 | await Movie.drop_indexes(force=True) 39 | await Movie.objects.delete() 40 | await Movie.create_indexes() 41 | 42 | 43 | async def test_exists_true() -> None: 44 | movies = await Movie.objects.all() 45 | assert len(movies) == 0 46 | 47 | await Movie.objects.create( 48 | name="Forrest Gump", year=2003, is_published=True 49 | ) 50 | 51 | assert await Movie.objects.exists(name="Forrest Gump") is True 52 | assert await Movie.objects.exists(name="Forrest Gump", year=2003) is True 53 | assert await Movie.objects.exists(name="Forrest Gump", year=2004) is False 54 | 55 | assert await Movie.objects.filter(name="Forrest Gump").exists() is True 56 | assert ( 57 | await Movie.objects.filter(name="Forrest Gump", year=2003).exists() 58 | is True 59 | ) 60 | assert ( 61 | await Movie.objects.filter(name="Forrest Gump", year=2004).exists() 62 | is False 63 | ) 64 | -------------------------------------------------------------------------------- /tests/models/manager/test_filter.py: -------------------------------------------------------------------------------- 1 | from typing import Any, AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, ObjectId 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | 14 | class BaseDocument(Document): 15 | name: str = mongoz.String() 16 | year: int = mongoz.Integer() 17 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 18 | 19 | class Meta: 20 | abstract = True 21 | registry = client 22 | database = "test_db" 23 | 24 | 25 | class Movie(BaseDocument): 26 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 27 | 28 | 29 | class Actor(BaseDocument): 30 | movies: Optional[List[Any]] = mongoz.ArrayList(null=True) 31 | 32 | 33 | @pytest.fixture(scope="function", autouse=True) 34 | async def prepare_database() -> AsyncGenerator: 35 | await Movie.objects.delete() 36 | await Actor.objects.delete() 37 | yield 38 | await Movie.objects.delete() 39 | await Actor.objects.delete() 40 | 41 | 42 | async def test_filter() -> None: 43 | barbie = await Movie.objects.create( 44 | name="Barbie", year=2023, tags=["barbie"] 45 | ) 46 | 47 | movies = await Movie.objects.all() 48 | assert len(movies) == 1 49 | 50 | movies = await Movie.objects.filter(name="Barbie") 51 | 52 | assert len(movies) == 1 53 | assert movies[0].name == barbie.name 54 | assert movies[0].tags == barbie.tags 55 | 56 | cursor = Movie.query() 57 | async for movie in cursor: 58 | assert movie.name == "Barbie" 59 | assert movie.tags == ["barbie"] 60 | 61 | batman = await Movie.objects.create( 62 | name="Batman", year=2022, tags=["detective", "dc"] 63 | ) 64 | movies = await Movie.objects.all() 65 | 66 | assert len(movies) == 2 67 | 68 | movie = movies[1] 69 | assert movie.name == batman.name 70 | assert movie.tags == batman.tags 71 | -------------------------------------------------------------------------------- /tests/models/manager/test_first.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | 25 | class Meta: 26 | registry = client 27 | database = "test_db" 28 | indexes = indexes 29 | 30 | 31 | @pytest.fixture(scope="function", autouse=True) 32 | async def prepare_database() -> AsyncGenerator: 33 | await Movie.drop_indexes(force=True) 34 | await Movie.objects.delete() 35 | await Movie.create_indexes() 36 | yield 37 | await Movie.drop_indexes(force=True) 38 | await Movie.objects.delete() 39 | await Movie.create_indexes() 40 | 41 | 42 | async def test_model_first_simple() -> None: 43 | await Movie.objects.create(name="Batman", year=2022) 44 | 45 | movie = await Movie.objects.first() 46 | assert movie is not None 47 | assert movie.name == "Batman" 48 | 49 | 50 | async def test_model_first() -> None: 51 | await Movie.objects.create(name="Batman", year=2022) 52 | 53 | movie = await Movie.objects.filter(name="Justice League").first() 54 | assert movie is None 55 | 56 | movie = await Movie.objects.filter(name="Batman").first() 57 | assert movie is not None 58 | assert movie.name == "Batman" 59 | -------------------------------------------------------------------------------- /tests/models/manager/test_get_by_id.py: -------------------------------------------------------------------------------- 1 | import secrets 2 | from typing import AsyncGenerator, List, Optional 3 | 4 | import pydantic 5 | import pytest 6 | 7 | import mongoz 8 | from mongoz import Document, Index, IndexType, ObjectId, Order 9 | from mongoz.exceptions import DocumentNotFound, InvalidKeyError 10 | from tests.conftest import client 11 | 12 | pytestmark = pytest.mark.anyio 13 | pydantic_version = pydantic.__version__[:3] 14 | 15 | indexes = [ 16 | Index("name", unique=True), 17 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 18 | ] 19 | 20 | 21 | class Movie(Document): 22 | name: str = mongoz.String() 23 | year: int = mongoz.Integer() 24 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 25 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 26 | 27 | class Meta: 28 | registry = client 29 | database = "test_db" 30 | indexes = indexes 31 | 32 | 33 | @pytest.fixture(scope="function", autouse=True) 34 | async def prepare_database() -> AsyncGenerator: 35 | await Movie.drop_indexes(force=True) 36 | await Movie.objects.delete() 37 | await Movie.create_indexes() 38 | yield 39 | await Movie.drop_indexes(force=True) 40 | await Movie.objects.delete() 41 | await Movie.create_indexes() 42 | 43 | 44 | async def test_model_get_document_by_id() -> None: 45 | movie = await Movie.objects.create(name="Barbie", year=2023) 46 | 47 | a_movie = await Movie.objects.get_document_by_id(str(movie.id)) 48 | assert movie.name == a_movie.name 49 | 50 | b_movie = await Movie.objects.get_document_by_id(movie.id) 51 | assert movie.name == b_movie.name 52 | 53 | with pytest.raises(InvalidKeyError): 54 | await Movie.objects.get_document_by_id("invalid_id") 55 | 56 | with pytest.raises(DocumentNotFound): 57 | await Movie.objects.get_document_by_id(secrets.token_hex(12)) 58 | -------------------------------------------------------------------------------- /tests/models/manager/test_get_or_create.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | from pydantic import ValidationError 6 | 7 | import mongoz 8 | from mongoz import Document, Index, IndexType, ObjectId, Order 9 | from tests.conftest import client 10 | 11 | pytestmark = pytest.mark.anyio 12 | pydantic_version = pydantic.__version__[:3] 13 | 14 | indexes = [ 15 | Index("name", unique=True), 16 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 17 | ] 18 | 19 | 20 | class Movie(Document): 21 | name: str = mongoz.String() 22 | year: int = mongoz.Integer() 23 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 24 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 25 | 26 | class Meta: 27 | registry = client 28 | database = "test_db" 29 | indexes = indexes 30 | 31 | 32 | @pytest.fixture(scope="function", autouse=True) 33 | async def prepare_database() -> AsyncGenerator: 34 | await Movie.drop_indexes(force=True) 35 | await Movie.objects.delete() 36 | await Movie.create_indexes() 37 | yield 38 | await Movie.drop_indexes(force=True) 39 | await Movie.objects.delete() 40 | await Movie.create_indexes() 41 | 42 | 43 | async def test_model_get_or_create() -> None: 44 | movie = await Movie.objects.filter(name="Barbie").get_or_create( 45 | {"year": 2023} 46 | ) 47 | assert movie.name == "Barbie" 48 | assert movie.year == 2023 49 | 50 | movie = await Movie.objects.filter( 51 | name="Barbie", year=2023 52 | ).get_or_create() 53 | assert movie.name == "Barbie" 54 | assert movie.year == 2023 55 | 56 | movie = await Movie.objects.filter(name="Venom").get_or_create( 57 | {"year": 2021} 58 | ) 59 | assert movie.name == "Venom" 60 | assert movie.year == 2021 61 | 62 | movie = await Movie.objects.filter( 63 | name="Eternals", year=2021 64 | ).get_or_create() 65 | assert movie.name == "Eternals" 66 | assert movie.year == 2021 67 | 68 | with pytest.raises(ValidationError): 69 | await movie.objects.filter(name="Venom 2").get_or_create( 70 | {"year": "year 2021"} 71 | ) 72 | -------------------------------------------------------------------------------- /tests/models/manager/test_get_or_none.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | 25 | class Meta: 26 | registry = client 27 | database = "test_db" 28 | indexes = indexes 29 | 30 | 31 | @pytest.fixture(scope="function", autouse=True) 32 | async def prepare_database() -> AsyncGenerator: 33 | await Movie.drop_indexes(force=True) 34 | await Movie.objects.delete() 35 | await Movie.create_indexes() 36 | yield 37 | await Movie.drop_indexes(force=True) 38 | await Movie.objects.delete() 39 | await Movie.create_indexes() 40 | 41 | 42 | async def test_model_get_or_none() -> None: 43 | movie = await Movie.objects.filter(name="Barbie").get_or_create( 44 | {"year": 2023} 45 | ) 46 | assert movie.name == "Barbie" 47 | assert movie.year == 2023 48 | 49 | movie = await Movie.objects.get_or_none(id=1) 50 | assert movie is None 51 | -------------------------------------------------------------------------------- /tests/models/manager/test_last.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | 25 | class Meta: 26 | registry = client 27 | database = "test_db" 28 | indexes = indexes 29 | 30 | 31 | @pytest.fixture(scope="function", autouse=True) 32 | async def prepare_database() -> AsyncGenerator: 33 | await Movie.drop_indexes(force=True) 34 | await Movie.objects.delete() 35 | await Movie.create_indexes() 36 | yield 37 | await Movie.drop_indexes(force=True) 38 | await Movie.objects.delete() 39 | await Movie.create_indexes() 40 | 41 | 42 | async def test_model_last() -> None: 43 | await Movie.objects.create(name="Batman", year=2022) 44 | 45 | movie = await Movie.objects.filter(name="Justice League").last() 46 | assert movie is None 47 | 48 | movie = await Movie.objects.filter(name="Batman").last() 49 | assert movie is not None 50 | assert movie.name == "Batman" 51 | 52 | 53 | async def test_model_last_two() -> None: 54 | await Movie.objects.create(name="Batman", year=2022) 55 | await Movie.objects.create(name="Barbie", year=2023) 56 | 57 | movie = await Movie.objects.last() 58 | assert movie is not None 59 | assert movie.name == "Barbie" 60 | -------------------------------------------------------------------------------- /tests/models/manager/test_limit.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | 25 | class Meta: 26 | registry = client 27 | database = "test_db" 28 | indexes = indexes 29 | 30 | 31 | @pytest.fixture(scope="function", autouse=True) 32 | async def prepare_database() -> AsyncGenerator: 33 | await Movie.drop_indexes(force=True) 34 | await Movie.objects.delete() 35 | await Movie.create_indexes() 36 | yield 37 | await Movie.drop_indexes(force=True) 38 | await Movie.objects.delete() 39 | await Movie.create_indexes() 40 | 41 | 42 | async def test_model_limit() -> None: 43 | await Movie.objects.create(name="Oppenheimer", year=2023) 44 | await Movie.objects.create(name="Batman", year=2022) 45 | 46 | movies = await Movie.objects.sort("name", Order.ASCENDING).limit(1) 47 | assert len(movies) == 1 48 | assert movies[0].name == "Batman" 49 | 50 | movies = ( 51 | await Movie.objects.sort("name", Order.ASCENDING) 52 | .skip(1) 53 | .limit(1) 54 | .all() 55 | ) 56 | assert len(movies) == 1 57 | assert movies[0].name == "Oppenheimer" 58 | 59 | 60 | async def test_model_limit_with_filter() -> None: 61 | await Movie.objects.create(name="Oppenheimer", year=2023) 62 | await Movie.objects.create(name="Batman", year=2022) 63 | 64 | movies = await Movie.objects.filter(name__icontains="b").limit(1) 65 | assert len(movies) == 1 66 | assert movies[0].name == "Batman" 67 | -------------------------------------------------------------------------------- /tests/models/manager/test_none.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Manager, ObjectId 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | 14 | class Movie(Document): 15 | name: str = mongoz.String() 16 | year: int = mongoz.Integer() 17 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 18 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 19 | is_published: bool = mongoz.Boolean(default=False) 20 | 21 | class Meta: 22 | registry = client 23 | database = "test_db" 24 | 25 | 26 | @pytest.fixture(scope="function", autouse=True) 27 | async def prepare_database() -> AsyncGenerator: 28 | await Movie.objects.delete() 29 | yield 30 | await Movie.objects.delete() 31 | 32 | 33 | async def test_model_none() -> None: 34 | manager = await Movie.objects.none() 35 | 36 | assert isinstance(manager, Manager) 37 | assert manager._collection == Movie.meta.collection._collection 38 | -------------------------------------------------------------------------------- /tests/models/manager/test_object_id_null.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator 2 | 3 | import pydantic 4 | import pytest 5 | from pydantic_core import ValidationError 6 | 7 | import mongoz 8 | from mongoz import Document, ObjectId 9 | from tests.conftest import client 10 | 11 | pytestmark = pytest.mark.anyio 12 | pydantic_version = pydantic.__version__[:3] 13 | 14 | 15 | class Movie(Document): 16 | name: str = mongoz.String() 17 | year: int = mongoz.Integer() 18 | producer_id: mongoz.ObjectId = mongoz.NullableObjectId() 19 | 20 | class Meta: 21 | registry = client 22 | database = "test_db" 23 | 24 | 25 | @pytest.fixture(scope="function", autouse=True) 26 | async def prepare_database() -> AsyncGenerator: 27 | await Movie.objects.delete() 28 | yield 29 | await Movie.objects.delete() 30 | 31 | 32 | async def test_nullable_objectid() -> None: 33 | await Movie.objects.using("test_my_db").delete() 34 | await Movie.objects.using("test_my_db").create(name="latest_movie", year=2024) 35 | 36 | movie = await Movie.objects.using("test_my_db").get() 37 | assert movie.name == "latest_movie" 38 | assert movie.year == 2024 39 | assert movie.producer_id is None 40 | 41 | 42 | async def test_nullable_objectid_raises_error_on_type() -> None: 43 | with pytest.raises(ValidationError): 44 | await Movie.objects.create(name="latest_movie", year=2024, producer_id=123) 45 | 46 | await Movie.objects.create(name="latest_movie", year=2024) 47 | 48 | movie = await Movie.objects.last() 49 | 50 | with pytest.raises(ValueError): 51 | movie.producer_id = 1234 52 | 53 | 54 | async def test_nullable_objectid_on_set() -> None: 55 | await Movie.objects.create(name="latest_movie", year=2024) 56 | 57 | movie = await Movie.objects.last() 58 | producer_id = ObjectId() 59 | 60 | movie.producer_id = producer_id 61 | await movie.save() 62 | 63 | movie = await Movie.objects.last() 64 | assert movie.name == "latest_movie" 65 | assert movie.year == 2024 66 | assert str(movie.producer_id) == str(producer_id) 67 | 68 | 69 | async def test_nullable_objectid_result() -> None: 70 | producer_id = ObjectId() 71 | 72 | await Movie.objects.create(name="latest_movie", year=2024, producer_id=producer_id) 73 | 74 | movie = await Movie.objects.last() 75 | 76 | assert movie.name == "latest_movie" 77 | assert movie.year == 2024 78 | assert str(movie.producer_id) == str(producer_id) 79 | -------------------------------------------------------------------------------- /tests/models/manager/test_skip.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | 25 | class Meta: 26 | registry = client 27 | database = "test_db" 28 | indexes = indexes 29 | 30 | 31 | @pytest.fixture(scope="function", autouse=True) 32 | async def prepare_database() -> AsyncGenerator: 33 | await Movie.drop_indexes(force=True) 34 | await Movie.objects.delete() 35 | await Movie.create_indexes() 36 | yield 37 | await Movie.drop_indexes(force=True) 38 | await Movie.objects.delete() 39 | await Movie.create_indexes() 40 | 41 | 42 | async def test_model_skip() -> None: 43 | await Movie.objects.create(name="Oppenheimer", year=2003) 44 | await Movie(name="Batman", year=2022).create() 45 | 46 | movies = await Movie.objects.sort("name", Order.ASCENDING).skip(1) 47 | assert len(movies) == 1 48 | assert movies[0].name == "Oppenheimer" 49 | 50 | movie = await Movie.objects.sort("name", Order.ASCENDING).skip(1).get() 51 | assert movie.name == "Oppenheimer" 52 | -------------------------------------------------------------------------------- /tests/models/manager/test_sort_two.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | 14 | class Movie(Document): 15 | idx: str = mongoz.Integer() 16 | 17 | class Meta: 18 | registry = client 19 | database = "test_db" 20 | 21 | 22 | @pytest.fixture(scope="function", autouse=True) 23 | async def prepare_database() -> AsyncGenerator: 24 | await Movie.drop_indexes(force=True) 25 | await Movie.objects.delete() 26 | yield 27 | await Movie.drop_indexes(force=True) 28 | await Movie.objects.delete() 29 | 30 | 31 | async def test_model_sort_asc() -> None: 32 | for i in range(10): 33 | await Movie.objects.create(idx=i) 34 | 35 | movies = await Movie.objects.sort(idx__asc=True).values_list(["idx"], flat=True) 36 | 37 | assert movies == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 38 | 39 | 40 | async def test_model_sort_desc() -> None: 41 | for i in range(10): 42 | await Movie.objects.create(idx=i) 43 | 44 | movies = await Movie.objects.sort(idx__desc=True).values_list(["idx"], flat=True) 45 | 46 | assert movies == [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] 47 | 48 | 49 | async def test_model_sort_asc_obj() -> None: 50 | for i in range(10): 51 | await Movie.objects.create(idx=i) 52 | 53 | movies = await Movie.objects.sort("idx", Order.ASCENDING).values_list(["idx"], flat=True) 54 | 55 | assert movies == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 56 | 57 | 58 | async def test_model_sort_obj() -> None: 59 | for i in range(10): 60 | await Movie.objects.create(idx=i) 61 | 62 | movies = await Movie.objects.sort("idx", Order.DESCENDING).values_list(["idx"], flat=True) 63 | 64 | assert movies == [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] 65 | -------------------------------------------------------------------------------- /tests/models/manager/test_update_and_save.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | 25 | class Meta: 26 | registry = client 27 | database = "test_db" 28 | indexes = indexes 29 | 30 | 31 | @pytest.fixture(scope="function", autouse=True) 32 | async def prepare_database() -> AsyncGenerator: 33 | await Movie.drop_indexes(force=True) 34 | await Movie.objects.delete() 35 | await Movie.create_indexes() 36 | yield 37 | await Movie.drop_indexes(force=True) 38 | await Movie.objects.delete() 39 | await Movie.create_indexes() 40 | 41 | 42 | async def test_model_update_save() -> None: 43 | await Movie(name="Downfall", year=2002).create() 44 | 45 | movie = await Movie.objects.get() 46 | movie.year = 2003 47 | await movie.save() 48 | 49 | movie = await Movie.objects.get() 50 | assert movie.year == 2003 51 | 52 | movie.year += 1 53 | await movie.save() 54 | 55 | movie = await Movie.objects.get() 56 | assert movie.year == 2004 57 | -------------------------------------------------------------------------------- /tests/models/manager/test_where.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import AsyncGenerator, List, Optional 3 | 4 | import pydantic 5 | import pytest 6 | from bson import Code 7 | 8 | import mongoz 9 | from mongoz import Document, ObjectId 10 | from tests.conftest import client 11 | 12 | pytestmark = pytest.mark.anyio 13 | pydantic_version = pydantic.__version__[:3] 14 | 15 | 16 | class Movie(Document): 17 | name: str = mongoz.String() 18 | year: int = mongoz.Integer() 19 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 20 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 21 | created_at: datetime = mongoz.DateTime(auto_now=True) 22 | 23 | class Meta: 24 | registry = client 25 | database = "test_db" 26 | 27 | 28 | @pytest.fixture(scope="function", autouse=True) 29 | async def prepare_database() -> AsyncGenerator: 30 | await Movie.drop_indexes(force=True) 31 | await Movie.objects.delete() 32 | yield 33 | await Movie.drop_indexes(force=True) 34 | await Movie.objects.delete() 35 | 36 | 37 | async def test_model_where() -> None: 38 | await Movie.objects.create(name="Batman", year=2022) 39 | await Movie.objects.create(name="Barbie", year=2023) 40 | 41 | movies = await Movie.objects.where("this.name == 'Batman'") 42 | assert len(movies) == 1 43 | 44 | assert movies[0].name == "Batman" 45 | assert movies[0].year == 2022 46 | 47 | movies = await Movie.objects.where("this.name == 'Barbie'") 48 | assert len(movies) == 1 49 | 50 | assert movies[0].name == "Barbie" 51 | assert movies[0].year == 2023 52 | 53 | movies = await Movie.objects.where("this.name == 'barbie'") 54 | assert len(movies) == 0 55 | 56 | 57 | async def test_model_where_with_object() -> None: 58 | await Movie.objects.create(name="Batman", year=2022) 59 | await Movie.objects.create(name="Barbie", year=2023) 60 | 61 | code = Code("this.name == 'Batman'") 62 | movies = await Movie.objects.where(code) 63 | assert len(movies) == 1 64 | 65 | assert movies[0].name == "Batman" 66 | assert movies[0].year == 2022 67 | 68 | code = Code("this.name == 'Barbie'") 69 | movies = await Movie.objects.where(code) 70 | assert len(movies) == 1 71 | 72 | assert movies[0].name == "Barbie" 73 | assert movies[0].year == 2023 74 | 75 | code = Code("this.name == 'barbie'") 76 | movies = await Movie.objects.where(code) 77 | assert len(movies) == 0 78 | -------------------------------------------------------------------------------- /tests/models/querysets/test_all.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | is_published: bool = mongoz.Boolean(default=False) 25 | 26 | class Meta: 27 | registry = client 28 | database = "test_db" 29 | indexes = indexes 30 | 31 | 32 | @pytest.fixture(scope="function", autouse=True) 33 | async def prepare_database() -> AsyncGenerator: 34 | await Movie.drop_indexes(force=True) 35 | await Movie.query().delete() 36 | await Movie.create_indexes() 37 | yield 38 | await Movie.drop_indexes(force=True) 39 | await Movie.query().delete() 40 | await Movie.create_indexes() 41 | 42 | 43 | async def test_model_all() -> None: 44 | movies = await Movie.query().all() 45 | assert len(movies) == 0 46 | 47 | await Movie(name="Forrest Gump", year=2003, is_published=True).create() 48 | 49 | movies = await Movie.query().all() 50 | assert len(movies) == 1 51 | 52 | cursor = Movie.query() 53 | async for movie in cursor: 54 | assert movie.name == "Forrest Gump" 55 | assert movie.is_published is True 56 | 57 | 58 | async def test_model_default() -> None: 59 | movies = await Movie.query().all() 60 | assert len(movies) == 0 61 | 62 | await Movie(name="Ghostbusters - Afterlife 2", year=2024).create() 63 | 64 | movies = await Movie.query().all() 65 | assert len(movies) == 1 66 | 67 | cursor = Movie.query() 68 | async for movie in cursor: 69 | assert movie.name == "Ghostbusters - Afterlife 2" 70 | assert movie.is_published is False 71 | -------------------------------------------------------------------------------- /tests/models/querysets/test_count.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | 25 | class Meta: 26 | registry = client 27 | database = "test_db" 28 | indexes = indexes 29 | 30 | 31 | @pytest.fixture(scope="function", autouse=True) 32 | async def prepare_database() -> AsyncGenerator: 33 | await Movie.drop_indexes(force=True) 34 | await Movie.query().delete() 35 | await Movie.create_indexes() 36 | yield 37 | await Movie.drop_indexes(force=True) 38 | await Movie.query().delete() 39 | await Movie.create_indexes() 40 | 41 | 42 | async def test_model_count() -> None: 43 | count = await Movie.query().count() 44 | assert count == 0 45 | 46 | await Movie(name="Batman", year=2013).create() 47 | 48 | count = await Movie.query({Movie.year: 2013}).count() 49 | assert count == 1 50 | -------------------------------------------------------------------------------- /tests/models/querysets/test_create.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import bson 4 | import pydantic 5 | import pytest 6 | from pydantic import ValidationError 7 | from pymongo import errors 8 | 9 | import mongoz 10 | from mongoz import Document, Index, IndexType, ObjectId, Order 11 | from tests.conftest import client 12 | 13 | pytestmark = pytest.mark.anyio 14 | pydantic_version = pydantic.__version__[:3] 15 | 16 | indexes = [ 17 | Index("name", unique=True), 18 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 19 | ] 20 | 21 | 22 | class Movie(Document): 23 | name: str = mongoz.String() 24 | year: int = mongoz.Integer() 25 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 26 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 27 | 28 | class Meta: 29 | registry = client 30 | database = "test_db" 31 | indexes = indexes 32 | 33 | 34 | @pytest.fixture(scope="function", autouse=True) 35 | async def prepare_database() -> AsyncGenerator: 36 | await Movie.drop_indexes(force=True) 37 | await Movie.query().delete() 38 | await Movie.create_indexes() 39 | yield 40 | await Movie.drop_indexes(force=True) 41 | await Movie.query().delete() 42 | await Movie.create_indexes() 43 | 44 | 45 | async def test_model_create() -> None: 46 | movie = await Movie(name="Barbie", year=2023).create() 47 | assert movie.name == "Barbie" 48 | assert movie.year == 2023 49 | assert isinstance(movie.id, bson.ObjectId) 50 | 51 | with pytest.raises(ValidationError) as exc: 52 | await Movie(name="Justice League", year=2017, uuid="1").create() 53 | 54 | error = exc.value.errors()[0] 55 | 56 | assert error["type"] == "uuid_parsing" 57 | assert error["loc"] == ("uuid",) 58 | assert ( 59 | error["msg"] 60 | == "Input should be a valid UUID, invalid length: expected length 32 for simple format, found 1" 61 | ) 62 | assert error["input"] == "1" 63 | 64 | with pytest.raises(errors.DuplicateKeyError): 65 | await Movie(name="Barbie", year=2023).create() 66 | -------------------------------------------------------------------------------- /tests/models/querysets/test_create_on_save.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, ObjectId 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | 14 | class Movie(Document): 15 | name: str = mongoz.String() 16 | year: int = mongoz.Integer() 17 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 18 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 19 | 20 | class Meta: 21 | registry = client 22 | database = "test_db" 23 | 24 | 25 | @pytest.fixture(scope="function", autouse=True) 26 | async def prepare_database() -> AsyncGenerator: 27 | await Movie.objects.delete() 28 | yield 29 | await Movie.objects.delete() 30 | 31 | 32 | async def test_model_create_id_none() -> None: 33 | movie = await Movie(name="Barbie", year=2023).create() 34 | movie.id = None 35 | 36 | await movie.save() 37 | 38 | movies = await Movie.query().all() 39 | 40 | assert len(movies) == 2 41 | -------------------------------------------------------------------------------- /tests/models/querysets/test_delete.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | 25 | class Meta: 26 | registry = client 27 | database = "test_db" 28 | indexes = indexes 29 | 30 | 31 | @pytest.fixture(scope="function", autouse=True) 32 | async def prepare_database() -> AsyncGenerator: 33 | await Movie.drop_indexes(force=True) 34 | await Movie.query().delete() 35 | await Movie.create_indexes() 36 | yield 37 | await Movie.drop_indexes(force=True) 38 | await Movie.query().delete() 39 | await Movie.create_indexes() 40 | 41 | 42 | async def test_model_delete() -> None: 43 | await Movie(name="Barbie", year=2023).create() 44 | await Movie(name="Batman", year=2022).create() 45 | 46 | count = await Movie.query({Movie.name: "Batman"}).delete() 47 | assert count == 1 48 | 49 | count = await Movie.query().delete() 50 | assert count == 1 51 | 52 | movie = await Movie(name="Batman", year=2013).create() 53 | count = await movie.delete() 54 | assert count == 1 55 | -------------------------------------------------------------------------------- /tests/models/querysets/test_distinct_values.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import AsyncGenerator, List, Optional 3 | 4 | import pydantic 5 | import pytest 6 | 7 | import mongoz 8 | from mongoz import Document, ObjectId 9 | from tests.conftest import client 10 | 11 | pytestmark = pytest.mark.anyio 12 | pydantic_version = pydantic.__version__[:3] 13 | 14 | 15 | class Movie(Document): 16 | name: str = mongoz.String() 17 | year: int = mongoz.Integer() 18 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 19 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 20 | created_at: datetime = mongoz.DateTime(auto_now=True) 21 | 22 | class Meta: 23 | registry = client 24 | database = "test_db" 25 | 26 | 27 | @pytest.fixture(scope="function", autouse=True) 28 | async def prepare_database() -> AsyncGenerator: 29 | await Movie.drop_indexes(force=True) 30 | await Movie.query().delete() 31 | yield 32 | await Movie.drop_indexes(force=True) 33 | await Movie.query().delete() 34 | 35 | 36 | async def test_model_distinct() -> None: 37 | await Movie(name="Batman", year=2022).create() 38 | await Movie(name="Barbie", year=2023).create() 39 | 40 | movies = await Movie.query().distinct_values("name") 41 | assert len(movies) == 2 42 | 43 | assert "Barbie" in movies 44 | assert "Batman" in movies 45 | -------------------------------------------------------------------------------- /tests/models/querysets/test_first.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | 25 | class Meta: 26 | registry = client 27 | database = "test_db" 28 | indexes = indexes 29 | 30 | 31 | @pytest.fixture(scope="function", autouse=True) 32 | async def prepare_database() -> AsyncGenerator: 33 | await Movie.drop_indexes(force=True) 34 | await Movie.query().delete() 35 | await Movie.create_indexes() 36 | yield 37 | await Movie.drop_indexes(force=True) 38 | await Movie.query().delete() 39 | await Movie.create_indexes() 40 | 41 | 42 | async def test_model_first_simple() -> None: 43 | await Movie(name="Batman", year=2022).create() 44 | 45 | movie = await Movie.query().first() 46 | assert movie is not None 47 | assert movie.name == "Batman" 48 | 49 | 50 | async def test_model_first() -> None: 51 | await Movie(name="Batman", year=2022).create() 52 | 53 | movie = await Movie.query({Movie.name: "Justice League"}).first() 54 | assert movie is None 55 | 56 | movie = await Movie.query({Movie.name: "Batman"}).first() 57 | assert movie is not None 58 | assert movie.name == "Batman" 59 | -------------------------------------------------------------------------------- /tests/models/querysets/test_get.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from mongoz.exceptions import DocumentNotFound, MultipleDocumentsReturned 9 | from tests.conftest import client 10 | 11 | pytestmark = pytest.mark.anyio 12 | pydantic_version = pydantic.__version__[:3] 13 | 14 | indexes = [ 15 | Index("name", unique=True), 16 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 17 | ] 18 | 19 | 20 | class Movie(Document): 21 | name: str = mongoz.String() 22 | year: int = mongoz.Integer() 23 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 24 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 25 | 26 | class Meta: 27 | registry = client 28 | database = "test_db" 29 | indexes = indexes 30 | 31 | 32 | @pytest.fixture(scope="function", autouse=True) 33 | async def prepare_database() -> AsyncGenerator: 34 | await Movie.drop_indexes(force=True) 35 | await Movie.query().delete() 36 | await Movie.create_indexes() 37 | yield 38 | await Movie.drop_indexes(force=True) 39 | await Movie.query().delete() 40 | await Movie.create_indexes() 41 | 42 | 43 | async def test_model_get() -> None: 44 | await Movie(name="Barbie", year=2023).create() 45 | 46 | movie = await Movie.query().get() 47 | assert movie.name == "Barbie" 48 | 49 | await Movie(name="Batman", year=2013).create() 50 | 51 | movie = await Movie.query({Movie.name: "Barbie"}).get() 52 | assert movie.name == "Barbie" 53 | 54 | movie = await Movie.query({"_id": movie.id}).get() 55 | assert movie.name == "Barbie" 56 | 57 | movie = await Movie.query({Movie.id: movie.id}).get() 58 | assert movie.name == "Barbie" 59 | 60 | with pytest.raises(DocumentNotFound): 61 | await Movie.query({Movie.name: "Interstellar"}).get() 62 | 63 | with pytest.raises(MultipleDocumentsReturned): 64 | await Movie.query().get() 65 | -------------------------------------------------------------------------------- /tests/models/querysets/test_get_by_id.py: -------------------------------------------------------------------------------- 1 | import secrets 2 | from typing import AsyncGenerator, List, Optional 3 | 4 | import pydantic 5 | import pytest 6 | 7 | import mongoz 8 | from mongoz import Document, Index, IndexType, ObjectId, Order 9 | from mongoz.exceptions import DocumentNotFound, InvalidKeyError 10 | from tests.conftest import client 11 | 12 | pytestmark = pytest.mark.anyio 13 | pydantic_version = pydantic.__version__[:3] 14 | 15 | indexes = [ 16 | Index("name", unique=True), 17 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 18 | ] 19 | 20 | 21 | class Movie(Document): 22 | name: str = mongoz.String() 23 | year: int = mongoz.Integer() 24 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 25 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 26 | 27 | class Meta: 28 | registry = client 29 | database = "test_db" 30 | indexes = indexes 31 | 32 | 33 | @pytest.fixture(scope="function", autouse=True) 34 | async def prepare_database() -> AsyncGenerator: 35 | await Movie.drop_indexes(force=True) 36 | await Movie.query().delete() 37 | await Movie.create_indexes() 38 | yield 39 | await Movie.drop_indexes(force=True) 40 | await Movie.query().delete() 41 | await Movie.create_indexes() 42 | 43 | 44 | async def test_model_get_document_by_id() -> None: 45 | movie = await Movie(name="Barbie", year=2023).create() 46 | 47 | a_movie = await Movie.get_document_by_id(str(movie.id)) 48 | assert movie.name == a_movie.name 49 | 50 | b_movie = await Movie.get_document_by_id(movie.id) 51 | assert movie.name == b_movie.name 52 | 53 | with pytest.raises(InvalidKeyError): 54 | await Movie.get_document_by_id("invalid_id") 55 | 56 | with pytest.raises(DocumentNotFound): 57 | await Movie.get_document_by_id(secrets.token_hex(12)) 58 | 59 | 60 | async def test_model_get_document_by_id_in_queryset() -> None: 61 | movie = await Movie(name="Barbie", year=2023).create() 62 | 63 | a_movie = await Movie.query().get_document_by_id(str(movie.id)) 64 | assert movie.name == a_movie.name 65 | 66 | b_movie = await Movie.query().get_document_by_id(movie.id) 67 | assert movie.name == b_movie.name 68 | 69 | with pytest.raises(InvalidKeyError): 70 | await Movie.query().get_document_by_id("invalid_id") 71 | 72 | with pytest.raises(DocumentNotFound): 73 | await Movie.query().get_document_by_id(secrets.token_hex(12)) 74 | -------------------------------------------------------------------------------- /tests/models/querysets/test_get_or_create.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | from pydantic import ValidationError 6 | 7 | import mongoz 8 | from mongoz import Document, Index, IndexType, ObjectId, Order 9 | from tests.conftest import client 10 | 11 | pytestmark = pytest.mark.anyio 12 | pydantic_version = pydantic.__version__[:3] 13 | 14 | indexes = [ 15 | Index("name", unique=True), 16 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 17 | ] 18 | 19 | 20 | class Movie(Document): 21 | name: str = mongoz.String() 22 | year: int = mongoz.Integer() 23 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 24 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 25 | 26 | class Meta: 27 | registry = client 28 | database = "test_db" 29 | indexes = indexes 30 | 31 | 32 | @pytest.fixture(scope="function", autouse=True) 33 | async def prepare_database() -> AsyncGenerator: 34 | await Movie.drop_indexes(force=True) 35 | await Movie.query().delete() 36 | await Movie.create_indexes() 37 | yield 38 | await Movie.drop_indexes(force=True) 39 | await Movie.query().delete() 40 | await Movie.create_indexes() 41 | 42 | 43 | async def test_model_get_or_create() -> None: 44 | movie = await Movie.query({Movie.name: "Barbie"}).get_or_create( 45 | {Movie.year: 2023} 46 | ) 47 | assert movie.name == "Barbie" 48 | assert movie.year == 2023 49 | 50 | movie = await Movie.query( 51 | {Movie.name: "Barbie", Movie.year: 2023} 52 | ).get_or_create() 53 | assert movie.name == "Barbie" 54 | assert movie.year == 2023 55 | 56 | movie = await Movie.query({Movie.name: "Venom"}).get_or_create( 57 | {Movie.year: 2021} 58 | ) 59 | assert movie.name == "Venom" 60 | assert movie.year == 2021 61 | 62 | movie = await Movie.query( 63 | {Movie.name: "Eternals", Movie.year: 2021} 64 | ).get_or_create() 65 | assert movie.name == "Eternals" 66 | assert movie.year == 2021 67 | 68 | with pytest.raises(ValidationError): 69 | await movie.query({Movie.name: "Venom 2"}).get_or_create( 70 | {Movie.year: "year 2021"} 71 | ) 72 | -------------------------------------------------------------------------------- /tests/models/querysets/test_get_or_none.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | 25 | class Meta: 26 | registry = client 27 | database = "test_db" 28 | indexes = indexes 29 | 30 | 31 | @pytest.fixture(scope="function", autouse=True) 32 | async def prepare_database() -> AsyncGenerator: 33 | await Movie.drop_indexes(force=True) 34 | await Movie.query().delete() 35 | await Movie.create_indexes() 36 | yield 37 | await Movie.drop_indexes(force=True) 38 | await Movie.query().delete() 39 | await Movie.create_indexes() 40 | 41 | 42 | async def test_model_get_or_none() -> None: 43 | movie = await Movie.query({Movie.name: "Barbie"}).get_or_create( 44 | {Movie.year: 2023} 45 | ) 46 | assert movie.name == "Barbie" 47 | assert movie.year == 2023 48 | 49 | movie = await Movie.query( 50 | {Movie.name: "Barbie", Movie.year: 2025} 51 | ).get_or_none() 52 | assert movie is None 53 | -------------------------------------------------------------------------------- /tests/models/querysets/test_last.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | 25 | class Meta: 26 | registry = client 27 | database = "test_db" 28 | indexes = indexes 29 | 30 | 31 | @pytest.fixture(scope="function", autouse=True) 32 | async def prepare_database() -> AsyncGenerator: 33 | await Movie.drop_indexes(force=True) 34 | await Movie.query().delete() 35 | await Movie.create_indexes() 36 | yield 37 | await Movie.drop_indexes(force=True) 38 | await Movie.query().delete() 39 | await Movie.create_indexes() 40 | 41 | 42 | async def test_model_last() -> None: 43 | await Movie(name="Batman", year=2022).create() 44 | 45 | movie = await Movie.query({Movie.name: "Justice League"}).last() 46 | assert movie is None 47 | 48 | movie = await Movie.query({Movie.name: "Batman"}).last() 49 | assert movie is not None 50 | assert movie.name == "Batman" 51 | 52 | 53 | async def test_model_last_two() -> None: 54 | await Movie(name="Batman", year=2022).create() 55 | await Movie(name="Barbie", year=2023).create() 56 | 57 | movie = await Movie.query().last() 58 | assert movie is not None 59 | assert movie.name == "Barbie" 60 | -------------------------------------------------------------------------------- /tests/models/querysets/test_limit.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | 25 | class Meta: 26 | registry = client 27 | database = "test_db" 28 | indexes = indexes 29 | 30 | 31 | @pytest.fixture(scope="function", autouse=True) 32 | async def prepare_database() -> AsyncGenerator: 33 | await Movie.drop_indexes(force=True) 34 | await Movie.query().delete() 35 | await Movie.create_indexes() 36 | yield 37 | await Movie.drop_indexes(force=True) 38 | await Movie.query().delete() 39 | await Movie.create_indexes() 40 | 41 | 42 | async def test_model_limit() -> None: 43 | await Movie(name="Oppenheimer", year=2023).create() 44 | await Movie(name="Batman", year=2022).create() 45 | 46 | movies = ( 47 | await Movie.query().sort(Movie.name, Order.ASCENDING).limit(1).all() 48 | ) 49 | assert len(movies) == 1 50 | assert movies[0].name == "Batman" 51 | 52 | movies = ( 53 | await Movie.query() 54 | .sort(Movie.name, Order.ASCENDING) 55 | .skip(1) 56 | .limit(1) 57 | .all() 58 | ) 59 | assert len(movies) == 1 60 | assert movies[0].name == "Oppenheimer" 61 | -------------------------------------------------------------------------------- /tests/models/querysets/test_models.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pytest 4 | 5 | import mongoz 6 | from mongoz import Document, Index, IndexType, ObjectId, Order 7 | from tests.conftest import client 8 | 9 | pytestmark = pytest.mark.anyio 10 | 11 | indexes = [ 12 | Index("name", unique=True), 13 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 14 | ] 15 | 16 | 17 | class Movie(Document): 18 | name: str = mongoz.String() 19 | year: int = mongoz.Integer() 20 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 21 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 22 | 23 | class Meta: 24 | registry = client 25 | database = "test_db" 26 | indexes = indexes 27 | 28 | 29 | @pytest.fixture(scope="function", autouse=True) 30 | async def prepare_database() -> AsyncGenerator: 31 | await Movie.drop_indexes(force=True) 32 | await Movie.query().delete() 33 | await Movie.create_indexes() 34 | yield 35 | await Movie.drop_indexes(force=True) 36 | await Movie.query().delete() 37 | await Movie.create_indexes() 38 | 39 | 40 | def test_model_class() -> None: 41 | class Product(Document): 42 | sku: str = mongoz.String() 43 | 44 | class Meta: 45 | registry = client 46 | database = "test_db" 47 | 48 | with pytest.raises(ValueError): 49 | Product(sku=12345) 50 | 51 | movie = Movie(name="Batman", year=2009) 52 | movie_dump = movie.model_dump() 53 | 54 | assert movie_dump == dict(movie) 55 | assert movie_dump["name"] == "Batman" 56 | assert movie_dump["year"] == 2009 57 | assert movie_dump["id"] is None 58 | assert movie_dump["tags"] is None 59 | 60 | assert Movie.meta.database.name == "test_db" 61 | assert Movie.meta.collection.name == "movies" 62 | -------------------------------------------------------------------------------- /tests/models/querysets/test_none.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, ObjectId, QuerySet 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | 14 | class Movie(Document): 15 | name: str = mongoz.String() 16 | year: int = mongoz.Integer() 17 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 18 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 19 | is_published: bool = mongoz.Boolean(default=False) 20 | 21 | class Meta: 22 | registry = client 23 | database = "test_db" 24 | 25 | 26 | @pytest.fixture(scope="function", autouse=True) 27 | async def prepare_database() -> AsyncGenerator: 28 | await Movie.query().delete() 29 | yield 30 | await Movie.query().delete() 31 | 32 | 33 | async def test_model_none() -> None: 34 | queryset = await Movie.query().none() 35 | 36 | assert isinstance(queryset, QuerySet) 37 | assert queryset._collection == Movie.meta.collection._collection 38 | -------------------------------------------------------------------------------- /tests/models/querysets/test_skip.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | 25 | class Meta: 26 | registry = client 27 | database = "test_db" 28 | indexes = indexes 29 | 30 | 31 | @pytest.fixture(scope="function", autouse=True) 32 | async def prepare_database() -> AsyncGenerator: 33 | await Movie.drop_indexes(force=True) 34 | await Movie.query().delete() 35 | await Movie.create_indexes() 36 | yield 37 | await Movie.drop_indexes(force=True) 38 | await Movie.query().delete() 39 | await Movie.create_indexes() 40 | 41 | 42 | async def test_model_skip() -> None: 43 | await Movie(name="Oppenheimer", year=2003).create() 44 | await Movie(name="Batman", year=2022).create() 45 | 46 | movies = ( 47 | await Movie.query().sort(Movie.name, Order.ASCENDING).skip(1).all() 48 | ) 49 | assert len(movies) == 1 50 | assert movies[0].name == "Oppenheimer" 51 | 52 | movie = await Movie.query().sort(Movie.name, Order.ASCENDING).skip(1).get() 53 | assert movie.name == "Oppenheimer" 54 | -------------------------------------------------------------------------------- /tests/models/querysets/test_update_and_save.py: -------------------------------------------------------------------------------- 1 | from typing import AsyncGenerator, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, IndexType, ObjectId, Order 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index(keys=[("year", Order.DESCENDING), ("genre", IndexType.HASHED)]), 16 | ] 17 | 18 | 19 | class Movie(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 24 | 25 | class Meta: 26 | registry = client 27 | database = "test_db" 28 | indexes = indexes 29 | 30 | 31 | @pytest.fixture(scope="function", autouse=True) 32 | async def prepare_database() -> AsyncGenerator: 33 | await Movie.drop_indexes(force=True) 34 | await Movie.query().delete() 35 | await Movie.create_indexes() 36 | yield 37 | await Movie.drop_indexes(force=True) 38 | await Movie.query().delete() 39 | await Movie.create_indexes() 40 | 41 | 42 | async def test_model_update_save() -> None: 43 | await Movie(name="Downfall", year=2002).create() 44 | 45 | movie = await Movie.query().get() 46 | movie.year = 2003 47 | await movie.save() 48 | 49 | movie = await Movie.query().get() 50 | assert movie.year == 2003 51 | 52 | movie.year += 1 53 | await movie.save() 54 | 55 | movie = await Movie.query().get() 56 | assert movie.year == 2004 57 | -------------------------------------------------------------------------------- /tests/models/querysets/test_where.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import AsyncGenerator, List, Optional 3 | 4 | import pydantic 5 | import pytest 6 | from bson import Code 7 | 8 | import mongoz 9 | from mongoz import Document, ObjectId 10 | from tests.conftest import client 11 | 12 | pytestmark = pytest.mark.anyio 13 | pydantic_version = pydantic.__version__[:3] 14 | 15 | 16 | class Movie(Document): 17 | name: str = mongoz.String() 18 | year: int = mongoz.Integer() 19 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 20 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 21 | created_at: datetime = mongoz.DateTime(auto_now=True) 22 | 23 | class Meta: 24 | registry = client 25 | database = "test_db" 26 | 27 | 28 | @pytest.fixture(scope="function", autouse=True) 29 | async def prepare_database() -> AsyncGenerator: 30 | await Movie.drop_indexes(force=True) 31 | await Movie.query().delete() 32 | yield 33 | await Movie.drop_indexes(force=True) 34 | await Movie.query().delete() 35 | 36 | 37 | async def test_model_where() -> None: 38 | await Movie(name="Batman", year=2022).create() 39 | await Movie(name="Barbie", year=2023).create() 40 | 41 | movies = await Movie.query().where("this.name == 'Batman'") 42 | assert len(movies) == 1 43 | 44 | assert movies[0].name == "Batman" 45 | assert movies[0].year == 2022 46 | 47 | movies = await Movie.query().where("this.name == 'Barbie'") 48 | assert len(movies) == 1 49 | 50 | assert movies[0].name == "Barbie" 51 | assert movies[0].year == 2023 52 | 53 | movies = await Movie.query().where("this.name == 'barbie'") 54 | assert len(movies) == 0 55 | 56 | 57 | async def test_model_where_with_object() -> None: 58 | await Movie(name="Batman", year=2022).create() 59 | await Movie(name="Barbie", year=2023).create() 60 | 61 | code = Code("this.name == 'Batman'") 62 | movies = await Movie.query().where(code) 63 | assert len(movies) == 1 64 | 65 | assert movies[0].name == "Batman" 66 | assert movies[0].year == 2022 67 | 68 | code = Code("this.name == 'Barbie'") 69 | movies = await Movie.query().where(code) 70 | assert len(movies) == 1 71 | 72 | assert movies[0].name == "Barbie" 73 | assert movies[0].year == 2023 74 | 75 | code = Code("this.name == 'barbie'") 76 | movies = await Movie.query().where(code) 77 | assert len(movies) == 0 78 | -------------------------------------------------------------------------------- /tests/registry/test_registry.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, ObjectId 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index("year", unique=True), 16 | ] 17 | 18 | 19 | class BaseDocument(Document): 20 | name: str = mongoz.String() 21 | year: int = mongoz.Integer() 22 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 23 | 24 | class Meta: 25 | abstract = True 26 | registry = client 27 | database = "test_db" 28 | 29 | 30 | class Movie(BaseDocument): 31 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 32 | 33 | class Meta: 34 | indexes = indexes 35 | 36 | 37 | class Actor(BaseDocument): 38 | actor: str = mongoz.String(null=True) 39 | 40 | 41 | async def test_registry() -> None: 42 | assert len(client.documents) > 0 43 | 44 | assert "Actor" in client.documents 45 | assert "Movie" in client.documents 46 | -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | TEST_DATABASE_URL = os.environ.get("DATABASE_URI", "mongodb://root:mongoadmin@localhost:27017") 4 | -------------------------------------------------------------------------------- /tests/test_collections.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Index 8 | from tests.conftest import client 9 | 10 | pytestmark = pytest.mark.anyio 11 | pydantic_version = pydantic.__version__[:3] 12 | 13 | indexes = [ 14 | Index("name", unique=True), 15 | Index("year", unique=True), 16 | ] 17 | 18 | 19 | class CompanyBranch(mongoz.EmbeddedDocument): 20 | """ 21 | **Summary** This Class represents the company branch. 22 | **Fields:** 23 | - name (str): It contains the branch name. 24 | - address (str): It contains the branch address. 25 | """ 26 | 27 | name: str = mongoz.String(max_length=100, null=True) 28 | address: str = mongoz.String(max_length=100, null=True) 29 | 30 | 31 | class Company(mongoz.Document): 32 | """ 33 | **Summary** This Class represents the company. 34 | **Fields:** 35 | - code (str): It contains the company code. 36 | - name (str): It contains the company name. 37 | - branches (List[CompanyBranch]): It contains the embedded \ 38 | documents of company_branch model. 39 | """ 40 | 41 | code: str = mongoz.String() 42 | display_name: str = mongoz.String(null=True) 43 | branches: List[CompanyBranch] = mongoz.Array(CompanyBranch, null=True) 44 | 45 | def __str__(self): 46 | return f"<{self.display_name}: ({self.code})>" 47 | 48 | class Meta: 49 | registry = client 50 | database = "test_db" 51 | collection = "dymmond_companies" 52 | indexes = [ 53 | mongoz.Index("code", unique=True), 54 | ] 55 | 56 | 57 | async def test_create_company_with_collection(): 58 | company = await Company.objects.create(code="123", display_name="Dymmond") 59 | 60 | assert str(company) == "" 61 | -------------------------------------------------------------------------------- /tests/test_managers.py: -------------------------------------------------------------------------------- 1 | from typing import ClassVar, List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, Index, ObjectId 8 | from mongoz.core.db.documents.managers import QuerySetManager 9 | from mongoz.core.db.querysets.base import Manager 10 | from tests.conftest import client 11 | 12 | pytestmark = pytest.mark.anyio 13 | pydantic_version = pydantic.__version__[:3] 14 | 15 | indexes = [ 16 | Index("name", unique=True), 17 | Index("year", unique=True), 18 | ] 19 | 20 | 21 | class BiggerManager(QuerySetManager): 22 | 23 | def get_queryset(self) -> Manager: 24 | return super().get_queryset().filter(year__gte=2023) 25 | 26 | 27 | class SmallerManager(QuerySetManager): 28 | 29 | def get_queryset(self) -> Manager: 30 | return super().get_queryset().filter(year__lte=2022) 31 | 32 | 33 | class BaseDocument(Document): 34 | bigger: ClassVar[BiggerManager] = BiggerManager() 35 | smaller: ClassVar[SmallerManager] = SmallerManager() 36 | 37 | class Meta: 38 | abstract = True 39 | registry = client 40 | database = "test_db" 41 | 42 | 43 | class Movie(BaseDocument): 44 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 45 | name: str = mongoz.String() 46 | year: int = mongoz.Integer() 47 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 48 | 49 | 50 | @pytest.fixture(scope="function", autouse=True) 51 | async def prepare_database(): 52 | await Movie.query().delete() 53 | yield 54 | await Movie.query().delete() 55 | 56 | 57 | async def test_custom_abstraction(): 58 | barbie = await Movie.bigger.create(name="Barbie", year=2023) 59 | die_hard = await Movie.smaller.create(name="Die Hard", year=2022) 60 | 61 | total = await Movie.objects.count() 62 | 63 | assert total == 2 64 | 65 | total_bigger = await Movie.bigger.all() 66 | assert total_bigger[0].id == barbie.id 67 | 68 | total_smaller = await Movie.smaller.all() 69 | assert total_smaller[0].id == die_hard.id 70 | -------------------------------------------------------------------------------- /tests/test_meta_errors.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | import pydantic 4 | import pytest 5 | 6 | import mongoz 7 | from mongoz import Document, ObjectId 8 | from mongoz.exceptions import ImproperlyConfigured 9 | from tests.conftest import client 10 | 11 | pytestmark = pytest.mark.anyio 12 | pydantic_version = pydantic.__version__[:3] 13 | 14 | 15 | async def test_improperly_configured_for_missing_database(): 16 | with pytest.raises(ImproperlyConfigured) as raised: 17 | 18 | class Movie(Document): 19 | name: str = mongoz.String() 20 | year: int = mongoz.Integer() 21 | tags: Optional[List[str]] = mongoz.Array(str, null=True) 22 | uuid: Optional[ObjectId] = mongoz.UUID(null=True) 23 | is_published: bool = mongoz.Boolean(default=False) 24 | 25 | class Meta: 26 | registry = client 27 | 28 | assert ( 29 | raised.value.args[0] 30 | == "'database' for the table not found in the Meta class or any of the superclasses. You must set the database in the Meta." 31 | ) 32 | --------------------------------------------------------------------------------