├── .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 |
--------------------------------------------------------------------------------