diff --git a/benchmarks/asynchronous/test_basic_doc_ops.py b/benchmarks/asynchronous/test_basic_doc_ops.py new file mode 100644 index 000000000..a5b196074 --- /dev/null +++ b/benchmarks/asynchronous/test_basic_doc_ops.py @@ -0,0 +1,167 @@ +import asyncio +from asyncio import AbstractEventLoop +from typing import Callable + +from pytest_benchmark.fixture import BenchmarkFixture + +from mongoengine import Document, StringField, IntField, ListField, BooleanField, EmailField, EmbeddedDocument, \ + EmbeddedDocumentField +from mongoengine import async_connect, async_disconnect + + +class Book(Document): + meta = {"collection": "book"} + name = StringField() + pages = IntField() + tags = ListField(StringField()) + is_published = BooleanField() + author_email = EmailField() + + +class Contact(EmbeddedDocument): + name = StringField() + title = StringField() + address = StringField() + + +class Company(Document): + meta = {"collection": "company"} + name = StringField() + contacts = ListField(EmbeddedDocumentField(Contact)) + + +class TestBasicDocOps: + + @staticmethod + async def drop_collections(): + await Book.adrop_collection() + + @classmethod + def setup_class(cls): + cls.loop = asyncio.new_event_loop() + asyncio.set_event_loop(cls.loop) + cls.loop.run_until_complete(cls.connect()) + cls.loop.run_until_complete(cls.drop_collections()) + cls.loop.run_until_complete(cls.disconnect()) + + @classmethod + def teardown_class(cls): + cls.loop = asyncio.new_event_loop() + asyncio.set_event_loop(cls.loop) + cls.loop.run_until_complete(cls.connect()) + cls.loop.run_until_complete(cls.drop_collections()) + cls.loop.run_until_complete(cls.disconnect()) + + @staticmethod + async def connect(): + await async_connect(db="testDB") + + @staticmethod + async def disconnect(): + await async_disconnect() + + @staticmethod + def book_doc() -> Book: + return Book( + name="Always be closing", + pages=100, + tags=["-help", "sales"], + is_published=True, + author_email="alec@example.com", + ) + + def async_benchmark(self, benchmark: BenchmarkFixture, func: Callable): + async_loop: AbstractEventLoop = asyncio.new_event_loop() + async_loop.run_until_complete(self.connect()) + + def run(event_loop: AbstractEventLoop): + return event_loop.run_until_complete( + func() + ) + + benchmark(run, async_loop) + async_loop.run_until_complete(self.disconnect()) + + async def doc_save_and_delete(self): + doc = await self.book_doc().asave() + await doc.adelete() + + def test_doc_to_mongo(self, benchmark: BenchmarkFixture): + benchmark(lambda: self.book_doc().to_mongo()) + + def test_doc_create(self, benchmark: BenchmarkFixture): + benchmark(lambda: self.book_doc()) + + def test_doc_save_and_delete(self, benchmark: BenchmarkFixture): + self.async_benchmark(benchmark, lambda: self.doc_save_and_delete()) + + def test_doc_validate(self, benchmark: BenchmarkFixture): + benchmark(lambda: self.book_doc()) + + +class TestBasicLargeDocOps: + @staticmethod + async def drop_collections(): + await Company.adrop_collection() + + @classmethod + def setup_class(cls): + cls.loop = asyncio.new_event_loop() + asyncio.set_event_loop(cls.loop) + cls.loop.run_until_complete(cls.connect()) + cls.loop.run_until_complete(cls.drop_collections()) + cls.loop.run_until_complete(cls.disconnect()) + + @classmethod + def teardown_class(cls): + cls.loop = asyncio.new_event_loop() + asyncio.set_event_loop(cls.loop) + cls.loop.run_until_complete(cls.connect()) + cls.loop.run_until_complete(cls.drop_collections()) + cls.loop.run_until_complete(cls.disconnect()) + + @staticmethod + async def connect(): + await async_connect(db="testDB") + + @staticmethod + async def disconnect(): + await async_disconnect() + + @staticmethod + def company_doc() -> Company: + return Company( + name="MongoDB, Inc.", + contacts=[ + Contact(name="Contact %d" % x, title="CEO", address="Address %d" % x) + for x in range(1000) + ], + ) + + def async_benchmark(self, benchmark: BenchmarkFixture, func: Callable): + async_loop: AbstractEventLoop = asyncio.new_event_loop() + async_loop.run_until_complete(self.connect()) + + def run(event_loop: AbstractEventLoop): + return event_loop.run_until_complete( + func() + ) + + benchmark(run, async_loop) + async_loop.run_until_complete(self.disconnect()) + + async def big_doc_save_and_delete(self): + doc = await self.company_doc().asave() + await doc.adelete() + + def test_big_doc_to_mongo(self, benchmark: BenchmarkFixture): + benchmark(lambda: self.company_doc().to_mongo()) + + def test_big_doc_create(self, benchmark: BenchmarkFixture): + benchmark(lambda: self.company_doc()) + + def test_big_doc_save_and_delete(self, benchmark: BenchmarkFixture): + self.async_benchmark(benchmark, lambda: self.big_doc_save_and_delete()) + + def test_big_doc_validate(self, benchmark: BenchmarkFixture): + benchmark(lambda: self.company_doc()) diff --git a/benchmarks/asynchronous/test_save_with_indexes.py b/benchmarks/asynchronous/test_save_with_indexes.py new file mode 100644 index 000000000..08e594c36 --- /dev/null +++ b/benchmarks/asynchronous/test_save_with_indexes.py @@ -0,0 +1,100 @@ +import asyncio +from asyncio import AbstractEventLoop +from typing import Callable + +from pytest_benchmark.fixture import BenchmarkFixture + +from mongoengine import Document, IntField, StringField +from mongoengine import async_connect, async_disconnect + + +class User0(Document): + name = StringField() + age = IntField() + meta = {"collection": "company"} + + +class User1(Document): + name = StringField() + age = IntField() + meta = {"indexes": [["name"]]} + + +class User2(Document): + name = StringField() + age = IntField() + meta = {"indexes": [["name", "age"]]} + + +class User3(Document): + name = StringField() + age = IntField() + meta = {"indexes": [["name"]], "auto_create_index_on_save": True} + + +class User4(Document): + name = StringField() + age = IntField() + meta = {"indexes": [["name", "age"]], "auto_create_index_on_save": True} + + +class TestSaveWithIndexes: + + @staticmethod + async def drop_collections(): + await User0.adrop_collection() + await User1.adrop_collection() + await User2.adrop_collection() + await User3.adrop_collection() + await User4.adrop_collection() + + @classmethod + def setup_class(cls): + cls.loop = asyncio.new_event_loop() + asyncio.set_event_loop(cls.loop) + cls.loop.run_until_complete(cls.connect()) + cls.loop.run_until_complete(cls.drop_collections()) + cls.loop.run_until_complete(cls.disconnect()) + + @classmethod + def teardown_class(cls): + cls.loop = asyncio.new_event_loop() + asyncio.set_event_loop(cls.loop) + cls.loop.run_until_complete(cls.connect()) + cls.loop.run_until_complete(cls.drop_collections()) + cls.loop.run_until_complete(cls.disconnect()) + + @staticmethod + async def connect(): + await async_connect(db="testDB") + + @staticmethod + async def disconnect(): + await async_disconnect() + + def async_benchmark(self, benchmark: BenchmarkFixture, func: Callable): + async_loop: AbstractEventLoop = asyncio.new_event_loop() + async_loop.run_until_complete(self.connect()) + + def run(event_loop: AbstractEventLoop): + return event_loop.run_until_complete( + func() + ) + + benchmark(run, async_loop) + async_loop.run_until_complete(self.disconnect()) + + def test_doc_without_index(self, benchmark: BenchmarkFixture): + self.async_benchmark(benchmark, lambda: User0(name="Nunu", age=9).asave()) + + def test_doc_with_1_index(self, benchmark: BenchmarkFixture): + self.async_benchmark(benchmark, lambda: User1(name="Nunu", age=9).asave()) + + def test_doc_with_2_index(self, benchmark: BenchmarkFixture): + self.async_benchmark(benchmark, lambda: User2(name="Nunu", age=9).asave()) + + def test_doc_with_1_auto_created_index(self, benchmark: BenchmarkFixture): + self.async_benchmark(benchmark, lambda: User3(name="Nunu", age=9).asave()) + + def test_doc_with_2_auto_created_index(self, benchmark: BenchmarkFixture): + self.async_benchmark(benchmark, lambda: User4(name="Nunu", age=9).asave()) diff --git a/benchmarks/test_basic_doc_ops.py b/benchmarks/synchronous/test_basic_doc_ops.py similarity index 100% rename from benchmarks/test_basic_doc_ops.py rename to benchmarks/synchronous/test_basic_doc_ops.py diff --git a/benchmarks/test_inserts.py b/benchmarks/synchronous/test_inserts.py similarity index 100% rename from benchmarks/test_inserts.py rename to benchmarks/synchronous/test_inserts.py diff --git a/benchmarks/test_save_with_indexes.py b/benchmarks/synchronous/test_save_with_indexes.py similarity index 100% rename from benchmarks/test_save_with_indexes.py rename to benchmarks/synchronous/test_save_with_indexes.py diff --git a/pyproject.toml b/pyproject.toml index e18994071..1904d58a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,7 @@ test = [ "pillow (>=7.0.0)", "tox (>=4.32.0)", "tox-uv>=1.29.0", + "pytest-benchmark>=5.2.3", ] [project.urls] diff --git a/uv.lock b/uv.lock index 2dd989e5f..60fa68f03 100644 --- a/uv.lock +++ b/uv.lock @@ -483,6 +483,7 @@ test = [ { name = "pillow" }, { name = "pytest" }, { name = "pytest-asyncio" }, + { name = "pytest-benchmark" }, { name = "pytest-cov" }, { name = "tox" }, { name = "tox-uv" }, @@ -509,6 +510,7 @@ test = [ { name = "pillow", specifier = ">=7.0.0" }, { name = "pytest", specifier = ">=9.0" }, { name = "pytest-asyncio", specifier = ">=1.3" }, + { name = "pytest-benchmark", specifier = ">=5.2.3" }, { name = "pytest-cov", specifier = ">=7.0" }, { name = "tox", specifier = ">=4.32.0" }, { name = "tox-uv", specifier = ">=1.29.0" }, @@ -664,6 +666,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, ] +[[package]] +name = "py-cpuinfo" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716, upload-time = "2022-10-25T20:38:06.303Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335, upload-time = "2022-10-25T20:38:27.636Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -789,6 +800,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, ] +[[package]] +name = "pytest-benchmark" +version = "5.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "py-cpuinfo" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/34/9f732b76456d64faffbef6232f1f9dbec7a7c4999ff46282fa418bd1af66/pytest_benchmark-5.2.3.tar.gz", hash = "sha256:deb7317998a23c650fd4ff76e1230066a76cb45dcece0aca5607143c619e7779", size = 341340, upload-time = "2025-11-09T18:48:43.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/29/e756e715a48959f1c0045342088d7ca9762a2f509b945f362a316e9412b7/pytest_benchmark-5.2.3-py3-none-any.whl", hash = "sha256:bc839726ad20e99aaa0d11a127445457b4219bdb9e80a1afc4b51da7f96b0803", size = 45255, upload-time = "2025-11-09T18:48:39.765Z" }, +] + [[package]] name = "pytest-cov" version = "7.0.0"