Skip to content

Commit fe20b6d

Browse files
Add CodSpeed performance benchmarks (#98)
Co-authored-by: codspeed-hq[bot] <117304815+codspeed-hq[bot]@users.noreply.github.com> Co-authored-by: Tiago Silva <tiago.arasilva@gmail.com>
1 parent 17cc52e commit fe20b6d

7 files changed

Lines changed: 311 additions & 0 deletions

File tree

.github/workflows/codspeed.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
name: CodSpeed Benchmarks
3+
4+
on:
5+
push:
6+
branches:
7+
- "main"
8+
pull_request:
9+
branches:
10+
- "main"
11+
workflow_dispatch:
12+
13+
permissions:
14+
contents: read
15+
id-token: write
16+
17+
jobs:
18+
benchmarks:
19+
name: Run benchmarks
20+
runs-on: ubuntu-latest
21+
22+
steps:
23+
- uses: "actions/checkout@v4"
24+
- uses: "actions/setup-python@v5"
25+
with:
26+
python-version: "3.13"
27+
- name: Install dependencies
28+
run: |
29+
pip install hatch
30+
pip install -e .
31+
pip install pytest-codspeed pytest-asyncio
32+
- name: Run the benchmarks
33+
uses: CodSpeedHQ/action@v4
34+
with:
35+
mode: simulation
36+
run: pytest benchmarks/ --codspeed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
<a href="https://pypi.org/project/mongoz" target="_blank">
2121
<img src="https://img.shields.io/pypi/pyversions/mongoz.svg?color=%2334D058" alt="Supported Python versions">
2222
</a>
23+
24+
<a href="https://codspeed.io/dymmond/mongoz?utm_source=badge" target="_blank">
25+
<img src="https://img.shields.io/endpoint?url=https://codspeed.io/badge/dymmond/mongoz" alt="CodSpeed">
26+
</a>
2327
</p>
2428

2529
---

benchmarks/__init__.py

Whitespace-only changes.

benchmarks/conftest.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import asyncio
2+
import os
3+
import typing
4+
5+
import pytest
6+
7+
from mongoz.core.connection.registry import Registry
8+
9+
database_uri = os.environ.get(
10+
"DATABASE_URI", "mongodb://root:mongoadmin@localhost:27017"
11+
)
12+
client = Registry(database_uri, event_loop=asyncio.get_running_loop)
13+
14+
15+
@pytest.fixture(scope="module")
16+
def anyio_backend():
17+
return ("asyncio", {"debug": False})
18+
19+
20+
@pytest.fixture(scope="session")
21+
def event_loop() -> typing.Generator[asyncio.AbstractEventLoop, None, None]:
22+
loop = asyncio.get_event_loop_policy().new_event_loop()
23+
yield loop
24+
loop.close()

benchmarks/test_bench_documents.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
"""Benchmarks for Document model instantiation and serialization."""
2+
3+
from __future__ import annotations
4+
5+
from datetime import datetime
6+
from typing import List, Optional
7+
8+
import pytest
9+
10+
import mongoz
11+
from benchmarks.conftest import client
12+
13+
14+
class Movie(mongoz.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 = "bench_db"
22+
23+
24+
class Course(mongoz.EmbeddedDocument):
25+
code: str = mongoz.String()
26+
name: str = mongoz.String()
27+
start_date: datetime = mongoz.DateTime()
28+
end_date: datetime = mongoz.DateTime()
29+
30+
31+
class Student(mongoz.Document):
32+
name: str = mongoz.String()
33+
roll_no: int = mongoz.Integer()
34+
courses: List[Course] = mongoz.Array(Course, default=[])
35+
36+
class Meta:
37+
registry = client
38+
database = "bench_db"
39+
40+
41+
@pytest.mark.benchmark
42+
def test_bench_embedded_document_instantiation():
43+
"""Benchmark creating EmbeddedDocument instances."""
44+
for _ in range(100):
45+
Course(
46+
code="CS101",
47+
name="Computer Science",
48+
start_date="2024-01-15",
49+
end_date="2024-06-15",
50+
)
51+
52+
53+
@pytest.mark.benchmark
54+
def test_bench_embedded_document_model_dump():
55+
"""Benchmark serializing EmbeddedDocument to dict."""
56+
course = Course(
57+
code="CS101",
58+
name="Computer Science",
59+
start_date="2024-01-15",
60+
end_date="2024-06-15",
61+
)
62+
for _ in range(100):
63+
course.model_dump()
64+
65+
66+
@pytest.mark.benchmark
67+
def test_bench_embedded_document_model_dump_json():
68+
"""Benchmark serializing EmbeddedDocument to JSON."""
69+
course = Course(
70+
code="CS101",
71+
name="Computer Science",
72+
start_date="2024-01-15",
73+
end_date="2024-06-15",
74+
)
75+
for _ in range(100):
76+
course.model_dump_json()
77+
78+
79+
@pytest.mark.benchmark
80+
def test_bench_embedded_document_with_nested():
81+
"""Benchmark creating a Student with nested Course documents."""
82+
courses = [
83+
Course(
84+
code=f"CS{i}",
85+
name=f"Course {i}",
86+
start_date="2024-01-15",
87+
end_date="2024-06-15",
88+
)
89+
for i in range(5)
90+
]
91+
for _ in range(50):
92+
Student(name="Test Student", roll_no=2024, courses=courses)
93+
94+
95+
@pytest.mark.benchmark
96+
def test_bench_document_model_dump_with_nested():
97+
"""Benchmark serializing a Student with nested Course documents."""
98+
courses = [
99+
Course(
100+
code=f"CS{i}",
101+
name=f"Course {i}",
102+
start_date="2024-01-15",
103+
end_date="2024-06-15",
104+
)
105+
for i in range(5)
106+
]
107+
student = Student(name="Test Student", roll_no=2024, courses=courses)
108+
for _ in range(50):
109+
student.model_dump()
110+
111+
112+
@pytest.mark.benchmark
113+
def test_bench_movie_model_instantiation():
114+
"""Benchmark creating Movie document instances."""
115+
for _ in range(100):
116+
Movie(name="Benchmark Movie", year=2024, tags=["action", "sci-fi"])
117+
118+
119+
@pytest.mark.benchmark
120+
def test_bench_movie_model_dump():
121+
"""Benchmark serializing a Movie document."""
122+
movie = Movie(name="Benchmark Movie", year=2024, tags=["action", "sci-fi"])
123+
for _ in range(100):
124+
movie.model_dump()
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"""Benchmarks for Expression compilation and Q operator construction."""
2+
3+
from __future__ import annotations
4+
5+
import pytest
6+
7+
from mongoz.core.db.querysets.expressions import Expression, SortExpression
8+
from mongoz.core.db.querysets.operators import Q
9+
from mongoz.utils.enums import ExpressionOperator
10+
11+
12+
@pytest.mark.benchmark
13+
def test_bench_expression_compile_eq():
14+
"""Benchmark compiling a simple equality expression."""
15+
expr = Expression(key="name", operator=ExpressionOperator.EQUAL, value="test")
16+
for _ in range(100):
17+
expr.compile()
18+
19+
20+
@pytest.mark.benchmark
21+
def test_bench_expression_compile_regex():
22+
"""Benchmark compiling a regex expression."""
23+
expr = Expression(
24+
key="name", operator=ExpressionOperator.PATTERN, value="test.*pattern"
25+
)
26+
for _ in range(100):
27+
expr.compile()
28+
29+
30+
@pytest.mark.benchmark
31+
def test_bench_expression_compile_startswith():
32+
"""Benchmark compiling a startswith expression."""
33+
expr = Expression(
34+
key="name", operator=ExpressionOperator.STARTSWITH, value="prefix"
35+
)
36+
for _ in range(100):
37+
expr.compile()
38+
39+
40+
@pytest.mark.benchmark
41+
def test_bench_expression_compile_many():
42+
"""Benchmark compiling multiple expressions at once."""
43+
expressions = [
44+
Expression(key="name", operator=ExpressionOperator.EQUAL, value="test"),
45+
Expression(key="age", operator=ExpressionOperator.GREATER_THAN, value=18),
46+
Expression(key="year", operator=ExpressionOperator.LESS_THAN, value=2024),
47+
Expression(
48+
key="email", operator=ExpressionOperator.PATTERN, value=".*@example.com"
49+
),
50+
Expression(
51+
key="status", operator=ExpressionOperator.IN, value=["active", "pending"]
52+
),
53+
]
54+
for _ in range(100):
55+
Expression.compile_many(expressions)
56+
57+
58+
@pytest.mark.benchmark
59+
def test_bench_expression_unpack():
60+
"""Benchmark unpacking a dictionary into expressions."""
61+
d = {
62+
"name": "test",
63+
"year": {"$gt": 1990, "$lt": 2024},
64+
"status": "active",
65+
}
66+
for _ in range(100):
67+
Expression.unpack(d)
68+
69+
70+
@pytest.mark.benchmark
71+
def test_bench_q_operator_construction():
72+
"""Benchmark constructing Q operator expressions."""
73+
for _ in range(100):
74+
Q.eq("name", "test")
75+
Q.neq("status", "inactive")
76+
Q.gt("age", 18)
77+
Q.lt("year", 2024)
78+
Q.gte("score", 80)
79+
Q.lte("price", 100.0)
80+
81+
82+
@pytest.mark.benchmark
83+
def test_bench_q_in_operator():
84+
"""Benchmark constructing Q in/not_in expressions."""
85+
values = ["active", "pending", "approved", "rejected"]
86+
for _ in range(100):
87+
Q.in_("status", values)
88+
Q.not_in("status", values)
89+
90+
91+
@pytest.mark.benchmark
92+
def test_bench_q_logical_operators():
93+
"""Benchmark constructing logical Q operators (and, or, nor)."""
94+
expr1 = Q.eq("name", "test")
95+
expr2 = Q.gt("age", 18)
96+
expr3 = Q.lt("year", 2024)
97+
for _ in range(100):
98+
Q.and_(expr1, expr2, expr3)
99+
Q.or_(expr1, expr2, expr3)
100+
101+
102+
@pytest.mark.benchmark
103+
def test_bench_q_ordering():
104+
"""Benchmark constructing ordering expressions."""
105+
for _ in range(100):
106+
Q.asc("name")
107+
Q.desc("created_at")
108+
Q.asc("year")
109+
Q.desc("score")
110+
111+
112+
@pytest.mark.benchmark
113+
def test_bench_sort_expression_compile():
114+
"""Benchmark compiling sort expressions."""
115+
from mongoz.core.db.datastructures import Order
116+
117+
sort_expr = SortExpression(key="name", direction=Order.ASCENDING)
118+
for _ in range(100):
119+
sort_expr.compile()

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ dependencies = [
123123
"mypy==1.16.1",
124124
"pytest>=7.2.2,<9.0.0",
125125
"pytest-asyncio>=0.21.1,<1.0.0",
126+
"pytest-codspeed>=4.3.0",
126127
"pytest-cov>=4.0.0,<5.0.0",
127128
"requests>=2.28.2",
128129
"ruff>=0.0.256,<1.0.0",
@@ -183,6 +184,9 @@ ignore_errors = true
183184
addopts = ["--strict-config", "--strict-markers"]
184185
xfail_strict = true
185186
junit_family = "xunit2"
187+
markers = [
188+
"benchmark: mark a test as a CodSpeed benchmark",
189+
]
186190

187191
[tool.hatch.build.targets.sdist]
188192
include = ["/mongoz"]

0 commit comments

Comments
 (0)