From 2652c5a88c5ac6d65e252447a089dc2f600cff76 Mon Sep 17 00:00:00 2001 From: Roman Right Date: Fri, 9 Jun 2023 14:36:52 +0200 Subject: [PATCH] Feature: optional batteries (#575) * poetry -> flit * feature | merge indexes * version | 1.20.0b0 --- .github/workflows/github-actions-lint.yml | 2 +- .github/workflows/github-actions-mypy.yml | 2 +- .../workflows/github-actions-publish-docs.yml | 6 +- .../github-actions-publish-project.yml | 13 +-- .github/workflows/github-actions-tests.yml | 10 +- .gitignore | 3 +- .pypirc | 14 +++ beanie/__init__.py | 2 +- beanie/odm/bulk.py | 11 ++- beanie/odm/fields.py | 83 ++++++++++++++++ beanie/odm/settings/document.py | 64 +------------ beanie/odm/utils/find.py | 20 ++-- beanie/odm/utils/init.py | 28 +++++- beanie/odm/utils/parsing.py | 24 ++--- beanie/odm/utils/projection.py | 6 +- docs/changelog.md | 8 ++ pyproject.toml | 94 ++++++++++--------- tests/conftest.py | 4 +- tests/fastapi/conftest.py | 2 +- tests/migrations/conftest.py | 4 +- .../iterative/test_change_subfield.py | 2 +- .../migrations/iterative/test_change_value.py | 2 +- .../iterative/test_change_value_subfield.py | 2 +- .../migrations/iterative/test_pack_unpack.py | 2 +- .../migrations/iterative/test_rename_field.py | 2 +- tests/migrations/test_break.py | 2 +- tests/migrations/test_directions.py | 2 +- tests/migrations/test_free_fall.py | 2 +- tests/migrations/test_remove_indexes.py | 2 +- tests/odm/conftest.py | 10 +- tests/odm/documents/test_init.py | 12 +++ tests/odm/models.py | 34 +++++++ tests/test_beanie.py | 2 +- 33 files changed, 296 insertions(+), 180 deletions(-) create mode 100644 .pypirc diff --git a/.github/workflows/github-actions-lint.yml b/.github/workflows/github-actions-lint.yml index 6ea23f86..f6a45c1a 100644 --- a/.github/workflows/github-actions-lint.yml +++ b/.github/workflows/github-actions-lint.yml @@ -9,7 +9,7 @@ jobs: python-version: [ 3.10.9 ] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/github-actions-mypy.yml b/.github/workflows/github-actions-mypy.yml index 5616b8c5..58bb73ec 100644 --- a/.github/workflows/github-actions-mypy.yml +++ b/.github/workflows/github-actions-mypy.yml @@ -7,7 +7,7 @@ jobs: fail-fast: false runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/setup-python@v2 with: python-version: 3.10.9 diff --git a/.github/workflows/github-actions-publish-docs.yml b/.github/workflows/github-actions-publish-docs.yml index 3d469f82..77c0f2e8 100644 --- a/.github/workflows/github-actions-publish-docs.yml +++ b/.github/workflows/github-actions-publish-docs.yml @@ -7,13 +7,11 @@ jobs: publish_docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/setup-python@v2 with: python-version: 3.10.9 - - name: install poetry - run: pip install poetry - name: install dependencies - run: poetry install + run: pip3 instll .[doc] - name: publish docs run: bash scripts/publish_docs.sh \ No newline at end of file diff --git a/.github/workflows/github-actions-publish-project.yml b/.github/workflows/github-actions-publish-project.yml index d8022ea8..31a76f85 100644 --- a/.github/workflows/github-actions-publish-project.yml +++ b/.github/workflows/github-actions-publish-project.yml @@ -7,13 +7,8 @@ jobs: publish_project: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: 3.10.9 - - name: install poetry - run: pip install poetry - - name: install dependencies - run: poetry install + - uses: actions/checkout@v3 + - name: install flit + run: pip3 install flit - name: publish project - run: poetry publish --build --username __token__ --password ${{ secrets.PYPI_TOKEN }} \ No newline at end of file + run: flit publish \ No newline at end of file diff --git a/.github/workflows/github-actions-tests.yml b/.github/workflows/github-actions-tests.yml index bd31acd7..e9129c9e 100644 --- a/.github/workflows/github-actions-tests.yml +++ b/.github/workflows/github-actions-tests.yml @@ -11,7 +11,7 @@ jobs: pydantic-version: [ 1.10.0 ] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} @@ -20,11 +20,9 @@ jobs: with: mongodb-version: ${{ matrix.mongodb-version }} mongodb-replica-set: test-rs - - name: install poetry - run: pip install poetry - name: install dependencies - run: poetry install + run: pip install .[test] - name: specify pydantic - run: poetry add pydantic==${{ matrix.pydantic-version }} + run: pip3 install pydantic==${{ matrix.pydantic-version }} - name: run tests - run: poetry run pytest + run: pytest diff --git a/.gitignore b/.gitignore index d451d1dd..0c1f6665 100644 --- a/.gitignore +++ b/.gitignore @@ -184,4 +184,5 @@ docker-compose-aws.yml tilt_modules # Poetry stuff -poetry.lock \ No newline at end of file +poetry.lock +.pdm-python diff --git a/.pypirc b/.pypirc new file mode 100644 index 00000000..169bb656 --- /dev/null +++ b/.pypirc @@ -0,0 +1,14 @@ +[distutils] +index-servers = + pypi + testpypi + +[pypi] +repository = https://upload.pypi.org/legacy/ +username = __token__ +password = ${PYPI_TOKEN} + +[testpypi] +repository = https://test.pypi.org/legacy/ +username = roman-right +password = =$C[wT}^]5EWvX(p#9Po \ No newline at end of file diff --git a/beanie/__init__.py b/beanie/__init__.py index 417509b9..1b576505 100644 --- a/beanie/__init__.py +++ b/beanie/__init__.py @@ -28,7 +28,7 @@ from beanie.odm.views import View from beanie.odm.union_doc import UnionDoc -__version__ = "1.19.2" +__version__ = "1.20.0b0" __all__ = [ # ODM "Document", diff --git a/beanie/odm/bulk.py b/beanie/odm/bulk.py index 6ccd2611..dde27a58 100644 --- a/beanie/odm/bulk.py +++ b/beanie/odm/bulk.py @@ -42,7 +42,11 @@ async def __aenter__(self): async def __aexit__(self, exc_type, exc, tb): await self.commit() - async def commit(self) -> BulkWriteResult: + async def commit(self) -> Optional[BulkWriteResult]: + """ + Commit all the operations to the database + :return: + """ obj_class = None requests = [] if self.operations: @@ -55,16 +59,17 @@ async def commit(self) -> BulkWriteResult: "All the operations should be for a single document model" ) if op.operation in [InsertOne, DeleteOne]: - query = op.operation(op.first_query, **op.pymongo_kwargs) + query = op.operation(op.first_query, **op.pymongo_kwargs) # type: ignore else: query = op.operation( - op.first_query, op.second_query, **op.pymongo_kwargs + op.first_query, op.second_query, **op.pymongo_kwargs # type: ignore ) requests.append(query) return await obj_class.get_motor_collection().bulk_write( # type: ignore requests, session=self.session ) + return None def add_operation(self, operation: Operation): self.operations.append(operation) diff --git a/beanie/odm/fields.py b/beanie/odm/fields.py index d5025b28..0e16702f 100644 --- a/beanie/odm/fields.py +++ b/beanie/odm/fields.py @@ -33,6 +33,7 @@ In, ) from beanie.odm.utils.parsing import parse_obj +from pymongo import IndexModel if TYPE_CHECKING: from beanie.odm.documents import DocType @@ -293,3 +294,85 @@ def to_dict(self): ENCODERS_BY_TYPE[BackLink] = lambda o: o.to_dict() + + +class IndexModelField: + def __init__(self, index: IndexModel): + self.index = index + self.name = index.document["name"] + + self.fields = tuple(sorted(self.index.document["key"])) + self.options = tuple( + sorted( + (k, v) + for k, v in self.index.document.items() + if k not in ["key", "v"] + ) + ) + + def __eq__(self, other): + return self.fields == other.fields and self.options == other.options + + def __repr__(self): + return f"IndexModelField({self.name}, {self.fields}, {self.options})" + + @classmethod + def __get_validators__(cls): + yield cls.validate + + @classmethod + def validate(cls, v): + if isinstance(v, IndexModel): + return IndexModelField(v) + else: + return IndexModelField(IndexModel(v)) + + @staticmethod + def list_difference( + left: List["IndexModelField"], right: List["IndexModelField"] + ): + result = [] + for index in left: + if index not in right: + result.append(index) + return result + + @staticmethod + def list_to_index_model(left: List["IndexModelField"]): + return [index.index for index in left] + + @classmethod + def from_motor_index_information(cls, index_info: dict): + result = [] + for name, details in index_info.items(): + fields = details["key"] + if ("_id", 1) in fields: + continue + + options = {k: v for k, v in details.items() if k != "key"} + index_model = IndexModelField( + IndexModel(fields, name=name, **options) + ) + result.append(index_model) + return result + + def same_fields(self, other: "IndexModelField"): + return self.fields == other.fields + + @staticmethod + def find_index_with_the_same_fields( + indexes: List["IndexModelField"], index: "IndexModelField" + ): + for i in indexes: + if i.same_fields(index): + return i + return None + + @staticmethod + def merge_indexes( + left: List["IndexModelField"], right: List["IndexModelField"] + ): + left_dict = {index.fields: index for index in left} + right_dict = {index.fields: index for index in right} + left_dict.update(right_dict) + return list(left_dict.values()) diff --git a/beanie/odm/settings/document.py b/beanie/odm/settings/document.py index b76afe70..41497c37 100644 --- a/beanie/odm/settings/document.py +++ b/beanie/odm/settings/document.py @@ -1,73 +1,12 @@ from typing import Optional, List from pydantic import Field -from pymongo import IndexModel +from beanie.odm.fields import IndexModelField from beanie.odm.settings.base import ItemSettings from beanie.odm.settings.timeseries import TimeSeriesConfig -class IndexModelField: - def __init__(self, index: IndexModel): - self.index = index - self.name = index.document["name"] - - self.fields = tuple(sorted(self.index.document["key"])) - self.options = tuple( - sorted( - (k, v) - for k, v in self.index.document.items() - if k not in ["key", "v"] - ) - ) - - def __eq__(self, other): - return self.fields == other.fields and self.options == other.options - - def __repr__(self): - return f"IndexModelField({self.name}, {self.fields}, {self.options})" - - @classmethod - def __get_validators__(cls): - yield cls.validate - - @classmethod - def validate(cls, v): - if isinstance(v, IndexModel): - return IndexModelField(v) - else: - return IndexModelField(IndexModel(v)) - - @staticmethod - def list_difference( - left: List["IndexModelField"], right: List["IndexModelField"] - ): - result = [] - for index in left: - if index not in right: - result.append(index) - return result - - @staticmethod - def list_to_index_model(left: List["IndexModelField"]): - return [index.index for index in left] - - @classmethod - def from_motor_index_information(cls, index_info: dict): - result = [] - for name, details in index_info.items(): - fields = details["key"] - if ("_id", 1) in fields: - continue - - options = {k: v for k, v in details.items() if k != "key"} - index_model = IndexModelField( - IndexModel(fields, name=name, **options) - ) - result.append(index_model) - return result - - class DocumentSettings(ItemSettings): use_state_management: bool = False state_management_replace_objects: bool = False @@ -77,6 +16,7 @@ class DocumentSettings(ItemSettings): single_root_inheritance: bool = False indexes: List[IndexModelField] = Field(default_factory=list) + merge_indexes: bool = False timeseries: Optional[TimeSeriesConfig] = None lazy_parsing: bool = False diff --git a/beanie/odm/utils/find.py b/beanie/odm/utils/find.py index 1b227295..13e95073 100644 --- a/beanie/odm/utils/find.py +++ b/beanie/odm/utils/find.py @@ -40,8 +40,7 @@ def construct_query( lookup_steps = [ { "$lookup": { - "from": link_info.model_class.get_motor_collection().name, - # type: ignore + "from": link_info.model_class.get_motor_collection().name, # type: ignore "localField": f"{link_info.lookup_field_name}.$id", "foreignField": "_id", "as": f"_link_{link_info.field_name}", @@ -85,7 +84,7 @@ def construct_query( lookup_steps = [ { "$lookup": { - "from": link_info.model_class.get_motor_collection().name, + "from": link_info.model_class.get_motor_collection().name, # type: ignore "let": { "link_id": f"${link_info.lookup_field_name}.$id" }, @@ -139,8 +138,7 @@ def construct_query( lookup_steps = [ { "$lookup": { - "from": link_info.model_class.get_motor_collection().name, - # type: ignore + "from": link_info.model_class.get_motor_collection().name, # type: ignore "localField": "_id", "foreignField": f"{link_info.lookup_field_name}.$id", "as": f"_link_{link_info.field_name}", @@ -184,7 +182,7 @@ def construct_query( lookup_steps = [ { "$lookup": { - "from": link_info.model_class.get_motor_collection().name, + "from": link_info.model_class.get_motor_collection().name, # type: ignore "let": {"link_id": "$_id"}, "as": f"_link_{link_info.field_name}", "pipeline": [ @@ -241,8 +239,7 @@ def construct_query( queries.append( { "$lookup": { - "from": link_info.model_class.get_motor_collection().name, - # type: ignore + "from": link_info.model_class.get_motor_collection().name, # type: ignore "localField": f"{link_info.lookup_field_name}.$id", "foreignField": "_id", "as": link_info.field_name, @@ -261,7 +258,7 @@ def construct_query( else: lookup_step = { "$lookup": { - "from": link_info.model_class.get_motor_collection().name, + "from": link_info.model_class.get_motor_collection().name, # type: ignore "let": {"link_id": f"${link_info.lookup_field_name}.$id"}, "as": link_info.field_name, "pipeline": [ @@ -286,8 +283,7 @@ def construct_query( queries.append( { "$lookup": { - "from": link_info.model_class.get_motor_collection().name, - # type: ignore + "from": link_info.model_class.get_motor_collection().name, # type: ignore "localField": "_id", "foreignField": f"{link_info.lookup_field_name}.$id", "as": link_info.field_name, @@ -306,7 +302,7 @@ def construct_query( else: lookup_step = { "$lookup": { - "from": link_info.model_class.get_motor_collection().name, + "from": link_info.model_class.get_motor_collection().name, # type: ignore "let": {"link_id": "$_id"}, "as": link_info.field_name, "pipeline": [ diff --git a/beanie/odm/utils/init.py b/beanie/odm/utils/init.py index 36209af8..e0d47b3d 100644 --- a/beanie/odm/utils/init.py +++ b/beanie/odm/utils/init.py @@ -283,8 +283,7 @@ async def init_document_collection(self, cls): cls.set_collection(collection) - @staticmethod - async def init_indexes(cls, allow_index_dropping: bool = False): + async def init_indexes(self, cls, allow_index_dropping: bool = False): """ Async indexes initializer """ @@ -315,8 +314,28 @@ async def init_indexes(cls, allow_index_dropping: bool = False): if hasattr(fvalue.type_, "_indexed") and fvalue.type_._indexed ] - if document_settings.indexes: - found_indexes += document_settings.indexes + if document_settings.merge_indexes: + result: List[IndexModelField] = [] + for subclass in reversed(cls.mro()): + if issubclass(subclass, Document) and not subclass == Document: + if ( + subclass not in self.inited_classes + and not subclass == cls + ): + await self.init_class(subclass) + if subclass.get_settings().indexes: + result = IndexModelField.merge_indexes( + result, subclass.get_settings().indexes + ) + found_indexes = IndexModelField.merge_indexes( + found_indexes, result + ) + + else: + if document_settings.indexes: + found_indexes = IndexModelField.merge_indexes( + found_indexes, document_settings.indexes + ) new_indexes += found_indexes @@ -348,7 +367,6 @@ async def init_document(self, cls: Type[Document]) -> Optional[Output]: build_info = await self.database.command({"buildInfo": 1}) mongo_version = build_info["version"] cls._database_major_version = int(mongo_version.split(".")[0]) - if cls not in self.inited_classes: self.set_default_class_vars(cls) self.init_settings(cls) diff --git a/beanie/odm/utils/parsing.py b/beanie/odm/utils/parsing.py index 52728f08..4ce936d7 100644 --- a/beanie/odm/utils/parsing.py +++ b/beanie/odm/utils/parsing.py @@ -14,8 +14,10 @@ def merge_models(left: BaseModel, right: BaseModel) -> None: from beanie.odm.fields import Link - if hasattr(left, "_previous_revision_id"): - left._previous_revision_id = right._previous_revision_id + if hasattr(left, "_previous_revision_id") and hasattr( + right, "_previous_revision_id" + ): + left._previous_revision_id = right._previous_revision_id # type: ignore for k, right_value in right.__iter__(): left_value = left.__getattribute__(k) if isinstance(right_value, BaseModel) and isinstance( @@ -37,9 +39,9 @@ def merge_models(left: BaseModel, right: BaseModel) -> None: def save_state_swap_revision(item: BaseModel): if hasattr(item, "_save_state"): - item._save_state() + item._save_state() # type: ignore if hasattr(item, "_swap_revision"): - item._swap_revision() + item._swap_revision() # type: ignore def parse_obj( @@ -55,14 +57,14 @@ def parse_obj( raise UnionHasNoRegisteredDocs if isinstance(data, dict): - class_name = data[model.get_settings().class_id] + class_name = data[model.get_settings().class_id] # type: ignore else: class_name = data._class_id if class_name not in model._document_models: # type: ignore raise DocWasNotRegisteredInUnionClass return parse_obj( - model=model._document_models[class_name], + model=model._document_models[class_name], # type: ignore data=data, lazy_parse=lazy_parse, ) # type: ignore @@ -72,15 +74,15 @@ def parse_obj( and model._inheritance_inited # type: ignore ): if isinstance(data, dict): - class_name = data.get(model.get_settings().class_id) - elif hasattr(data, model.get_settings().class_id): + class_name = data.get(model.get_settings().class_id) # type: ignore + elif hasattr(data, model.get_settings().class_id): # type: ignore class_name = data._class_id else: class_name = None if model._children and class_name in model._children: # type: ignore return parse_obj( - model=model._children[class_name], + model=model._children[class_name], # type: ignore data=data, lazy_parse=lazy_parse, ) # type: ignore @@ -88,9 +90,9 @@ def parse_obj( if ( lazy_parse and hasattr(model, "get_model_type") - and model.get_model_type() == ModelType.Document + and model.get_model_type() == ModelType.Document # type: ignore ): - o = model.lazy_parse(data, {"_id"}) + o = model.lazy_parse(data, {"_id"}) # type: ignore o._saved_state = {"_id": o.id} return o result = model.parse_obj(data) diff --git a/beanie/odm/utils/projection.py b/beanie/odm/utils/projection.py index 7446ea21..b0f0d79e 100644 --- a/beanie/odm/utils/projection.py +++ b/beanie/odm/utils/projection.py @@ -11,10 +11,10 @@ def get_projection( model: Type[ProjectionModelType], ) -> Optional[Dict[str, int]]: if hasattr(model, "get_model_type") and ( - model.get_model_type() == ModelType.UnionDoc + model.get_model_type() == ModelType.UnionDoc # type: ignore or ( # type: ignore - model.get_model_type() == ModelType.Document - and model._inheritance_inited + model.get_model_type() == ModelType.Document # type: ignore + and model._inheritance_inited # type: ignore ) ): # type: ignore return None diff --git a/docs/changelog.md b/docs/changelog.md index 413349dd..28b28d27 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,14 @@ Beanie project +## [1.20.0b0] - 2023-06-09 + +### Feature: optional batteries +- Author - [Roman Right](https://github.com/roman-right) +- PR + +[1.20.0b0]: https://pypi.org/project/beanie/1.20.0b0 + ## [1.19.2] - 2023-05-25 ### Fix: issues opened before 2023.05 diff --git a/pyproject.toml b/pyproject.toml index 83e4361c..6107260f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,50 +1,60 @@ -[tool.poetry] +[build-system] +requires = ["flit_core >=3.2,<4"] +build-backend = "flit_core.buildapi" + +[project] name = "beanie" -version = "1.19.2" +version = "1.20.0b0" description = "Asynchronous Python ODM for MongoDB" -authors = ["Roman "] -license = "Apache-2.0" -homepage = "https://roman-right.github.io/beanie/" -repository = "https://github.com/roman-right/beanie" +readme = "README.md" +requires-python = ">=3.7,<4.0" +license = { file="LICENSE" } +authors = [ + {name = "Roman Right", email = "roman-right@protonmail.com"} +] keywords = ["mongodb", "odm", "orm", "pydantic", "mongo", "async", "python"] -include = [ - "LICENSE", - "beanie/py.typed" +classifiers = [ + "License :: OSI Approved :: Apache Software License", +] +dependencies = [ + "pydantic>=1.10.0,<2.0.0", + "motor>=2.5.0,<4.0.0", + "click>=7", + "toml", + "lazy-model>=0.0.3" ] -readme = "README.md" -[tool.poetry.dependencies] -python = ">=3.7,<4.0" -pydantic = ">=1.10.0" -motor = ">=2.5,<4.0" -click = ">=7" -toml = "*" -lazy-model = ">=0.0.3" - -[tool.poetry.dev-dependencies] -pre-commit = "^2.3.0" -pytest = "^6.0.0" -pytest-aiohttp = "^0.3.0" -pytest-cov = "^2.8.1" -Pygments = "^2.8.0" -dnspython = "^2.1.0" -mkdocs-material = ">=9.0" -pydoc-markdown = ">=4.6" -flake8 = ">=3" -pyright = ">=0" -mkdocs = ">=1.4" -icecream = "^2.1.1" -jinja2 = "3.0.3" -fastapi = "^0.78.0" -asgi-lifespan = "^1.0.1" -httpx = "^0.23.0" -Markdown = ">=3.3" -importlib-metadata = "^4.12.0" +[project.optional-dependencies] +test = [ + "pre-commit>=2.3.0", + "pytest>=6.0.0", + "pytest-asyncio>=0.21.0", + "pytest-cov>=2.8.1", + "dnspython>=2.1.0", + "flake8>=3", + "pyright>=0", + "fastapi>=0.78.0", + "asgi-lifespan>=1.0.1", + "httpx>=0.23.0" +] +doc = [ + "Pygments>=2.8.0", + "Markdown>=3.3", + "pydoc-markdown>=4.6", + "mkdocs>=1.4", + "mkdocs-material>=9.0", + "jinja2>=3.0.3" +] +queue = ["beanie-batteries-queue>=0.0.1"] +[project.urls] +homepage = "https://roman-right.github.io/beanie/" +repository = "https://github.com/roman-right/beanie" -[build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" +[project.scripts] +beanie = "beanie.executors.migrate:migrations" + +# TOOLS [tool.black] line-length = 79 @@ -74,9 +84,7 @@ filterwarnings = [ "ignore::DeprecationWarning", "ignore::UserWarning", ] - -[tool.poetry.scripts] -beanie = "beanie.executors.migrate:migrations" +asyncio_mode = "auto" [tool.beanie.migrations] path = "beanie/example_migration" diff --git a/tests/conftest.py b/tests/conftest.py index 5e591e52..399f1ce5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,10 +14,10 @@ def settings(): @pytest.fixture() -def cli(settings, loop): +def cli(settings): return motor.motor_asyncio.AsyncIOMotorClient(settings.mongodb_dsn) @pytest.fixture() -def db(cli, settings, loop): +def db(cli, settings): return cli[settings.mongodb_db_name] diff --git a/tests/fastapi/conftest.py b/tests/fastapi/conftest.py index 9d5f9762..81a359da 100644 --- a/tests/fastapi/conftest.py +++ b/tests/fastapi/conftest.py @@ -16,7 +16,7 @@ async def api_client(clean_db): @pytest.fixture(autouse=True) -async def clean_db(loop, db): +async def clean_db(db): models = [HouseAPI, WindowAPI, DoorAPI, RoofAPI] yield None diff --git a/tests/migrations/conftest.py b/tests/migrations/conftest.py index 26f69b3e..75591c61 100644 --- a/tests/migrations/conftest.py +++ b/tests/migrations/conftest.py @@ -5,7 +5,7 @@ @pytest.fixture(autouse=True) -async def init(db, loop): +async def init(db): await init_beanie( database=db, document_models=[ @@ -15,5 +15,5 @@ async def init(db, loop): @pytest.fixture(autouse=True) -async def remove_migrations_log(db, init, loop): +async def remove_migrations_log(db, init): await MigrationLog.delete_all() diff --git a/tests/migrations/iterative/test_change_subfield.py b/tests/migrations/iterative/test_change_subfield.py index 3a30c245..049d73be 100644 --- a/tests/migrations/iterative/test_change_subfield.py +++ b/tests/migrations/iterative/test_change_subfield.py @@ -35,7 +35,7 @@ class Settings: @pytest.fixture() -async def notes(loop, db): +async def notes(db): await init_beanie(database=db, document_models=[OldNote]) await OldNote.delete_all() for i in range(10): diff --git a/tests/migrations/iterative/test_change_value.py b/tests/migrations/iterative/test_change_value.py index 5685a2c7..a1f66560 100644 --- a/tests/migrations/iterative/test_change_value.py +++ b/tests/migrations/iterative/test_change_value.py @@ -22,7 +22,7 @@ class Settings: @pytest.fixture() -async def notes(loop, db): +async def notes(db): await init_beanie(database=db, document_models=[Note]) await Note.delete_all() for i in range(10): diff --git a/tests/migrations/iterative/test_change_value_subfield.py b/tests/migrations/iterative/test_change_value_subfield.py index d64562a8..0a8bef02 100644 --- a/tests/migrations/iterative/test_change_value_subfield.py +++ b/tests/migrations/iterative/test_change_value_subfield.py @@ -22,7 +22,7 @@ class Settings: @pytest.fixture() -async def notes(loop, db): +async def notes(db): await init_beanie(database=db, document_models=[Note]) await Note.delete_all() for i in range(10): diff --git a/tests/migrations/iterative/test_pack_unpack.py b/tests/migrations/iterative/test_pack_unpack.py index d12d2caf..17da1684 100644 --- a/tests/migrations/iterative/test_pack_unpack.py +++ b/tests/migrations/iterative/test_pack_unpack.py @@ -36,7 +36,7 @@ class Settings: @pytest.fixture() -async def notes(loop, db): +async def notes(db): await init_beanie(database=db, document_models=[OldNote]) await OldNote.delete_all() for i in range(10): diff --git a/tests/migrations/iterative/test_rename_field.py b/tests/migrations/iterative/test_rename_field.py index c294bcef..866743b6 100644 --- a/tests/migrations/iterative/test_rename_field.py +++ b/tests/migrations/iterative/test_rename_field.py @@ -30,7 +30,7 @@ class Settings: @pytest.fixture() -async def notes(loop, db): +async def notes(db): await init_beanie(database=db, document_models=[OldNote]) await OldNote.delete_all() for i in range(10): diff --git a/tests/migrations/test_break.py b/tests/migrations/test_break.py index 84d8a84c..54698cf2 100644 --- a/tests/migrations/test_break.py +++ b/tests/migrations/test_break.py @@ -30,7 +30,7 @@ class Settings: @pytest.fixture() -async def notes(loop, db): +async def notes(db): await init_beanie(database=db, document_models=[OldNote]) await OldNote.delete_all() for i in range(10): diff --git a/tests/migrations/test_directions.py b/tests/migrations/test_directions.py index 3104a9bf..776d8a0f 100644 --- a/tests/migrations/test_directions.py +++ b/tests/migrations/test_directions.py @@ -21,7 +21,7 @@ class Settings: @pytest.fixture() -async def notes(loop, db): +async def notes(db): await init_beanie(database=db, document_models=[Note]) await Note.delete_all() for i in range(1, 8): diff --git a/tests/migrations/test_free_fall.py b/tests/migrations/test_free_fall.py index 1712cb3b..e9478ed6 100644 --- a/tests/migrations/test_free_fall.py +++ b/tests/migrations/test_free_fall.py @@ -29,7 +29,7 @@ class Settings: @pytest.fixture() -async def notes(loop, db): +async def notes(db): await init_beanie(database=db, document_models=[OldNote]) await OldNote.delete_all() for i in range(10): diff --git a/tests/migrations/test_remove_indexes.py b/tests/migrations/test_remove_indexes.py index 0b8132b9..1108e0c9 100644 --- a/tests/migrations/test_remove_indexes.py +++ b/tests/migrations/test_remove_indexes.py @@ -30,7 +30,7 @@ class Settings: @pytest.fixture() -async def notes(loop, db): +async def notes(db): await init_beanie(database=db, document_models=[OldNote]) await OldNote.delete_all() for i in range(10): diff --git a/tests/odm/conftest.py b/tests/odm/conftest.py index 22665cd7..f81b5892 100644 --- a/tests/odm/conftest.py +++ b/tests/odm/conftest.py @@ -81,6 +81,8 @@ DocumentWithListOfLinks, DocumentToBeLinked, DocumentWithTimeStampToTestConsistency, + DocumentWithIndexMerging1, + DocumentWithIndexMerging2, ) from tests.odm.views import TestView, TestViewWithLink @@ -166,14 +168,14 @@ def sample_doc_not_saved(point): @pytest.fixture() -async def session(cli, loop): +async def session(cli): s = await cli.start_session() yield s await s.end_session() @pytest.fixture(autouse=True) -async def init(loop, db): +async def init(db): models = [ DocumentWithExtras, DocumentWithPydanticConfig, @@ -247,6 +249,8 @@ async def init(loop, db): DocumentWithListOfLinks, DocumentToBeLinked, DocumentWithTimeStampToTestConsistency, + DocumentWithIndexMerging1, + DocumentWithIndexMerging2, ] await init_beanie( database=db, @@ -292,7 +296,7 @@ def generate_documents( @pytest.fixture -async def document(document_not_inserted, loop) -> DocumentTestModel: +async def document(document_not_inserted) -> DocumentTestModel: return await document_not_inserted.insert() diff --git a/tests/odm/documents/test_init.py b/tests/odm/documents/test_init.py index 31f17d26..43d5c66f 100644 --- a/tests/odm/documents/test_init.py +++ b/tests/odm/documents/test_init.py @@ -13,6 +13,7 @@ DocumentTestModelWithComplexIndex, DocumentTestModelStringImport, DocumentTestModelWithDroppedIndex, + DocumentWithIndexMerging2, ) from pymongo import IndexModel @@ -272,3 +273,14 @@ class Settings: ) await db.drop_collection("sample") + + +async def test_merge_indexes(): + assert await DocumentWithIndexMerging2.get_motor_collection().index_information() == { + "_id_": {"key": [("_id", 1)], "v": 2}, + "s0_1": {"key": [("s0", 1)], "v": 2}, + "s1_1": {"key": [("s1", 1)], "v": 2}, + "s2_-1": {"key": [("s2", -1)], "v": 2}, + "s3_index": {"key": [("s3", -1)], "v": 2}, + "s4_index": {"key": [("s4", 1)], "v": 2}, + } diff --git a/tests/odm/models.py b/tests/odm/models.py index 90e3725e..ba3b2b14 100644 --- a/tests/odm/models.py +++ b/tests/odm/models.py @@ -788,3 +788,37 @@ class DocumentWithListOfLinks(Document): class DocumentWithTimeStampToTestConsistency(Document): ts: datetime.datetime = Field(default_factory=datetime.datetime.utcnow) + + +class DocumentWithIndexMerging1(Document): + class Settings: + indexes = [ + "s1", + [ + ("s2", pymongo.ASCENDING), + ], + IndexModel( + [("s3", pymongo.ASCENDING)], + name="s3_index", + ), + IndexModel( + [("s4", pymongo.ASCENDING)], + name="s4_index", + ), + ] + + +class DocumentWithIndexMerging2(DocumentWithIndexMerging1): + class Settings: + merge_indexes = True + indexes = [ + "s0", + "s1", + [ + ("s2", pymongo.DESCENDING), + ], + IndexModel( + [("s3", pymongo.DESCENDING)], + name="s3_index", + ), + ] diff --git a/tests/test_beanie.py b/tests/test_beanie.py index 4d8ab787..3540ded5 100644 --- a/tests/test_beanie.py +++ b/tests/test_beanie.py @@ -2,4 +2,4 @@ def test_version(): - assert __version__ == "1.19.2" + assert __version__ == "1.20.0b0"