Skip to content

Commit 10b459f

Browse files
jeonghanjooclaude
andcommitted
fix: Add PyMongo 4.13+ async dependency management and documentation
- Add HAS_ASYNC_SUPPORT check with proper error messages - Update setup.py with async extras requirement - Add PyMongo 4.13+ version to tox.ini for testing - Update README.rst with async installation instructions - Fix pre-commit formatting and linting issues - Support Python 3.7-3.13 with proper version constraints 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 92fa9f7 commit 10b459f

22 files changed

+1339
-1113
lines changed

README.rst

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,22 @@ to both create the virtual environment and install the package. Otherwise, you c
5151
download the source from `GitHub <https://github.com/MongoEngine/mongoengine>`_ and
5252
run ``python setup.py install``.
5353

54+
For async support, you need PyMongo 4.13+ and can install it with:
55+
56+
.. code-block:: shell
57+
58+
# Install MongoEngine with async support
59+
$ python -m pip install mongoengine[async]
60+
5461
The support for Python2 was dropped with MongoEngine 0.20.0
5562

5663
Dependencies
5764
============
5865
All of the dependencies can easily be installed via `python -m pip <https://pip.pypa.io/>`_.
59-
At the very least, you'll need these two packages to use MongoEngine:
66+
At the very least, you'll need these packages to use MongoEngine:
6067

61-
- pymongo>=3.12
68+
- pymongo>=3.12 (for synchronous operations)
69+
- pymongo>=4.13 (for asynchronous operations)
6270

6371
If you utilize a ``DateTimeField``, you might also use a more flexible date parser:
6472

@@ -152,7 +160,7 @@ All major database operations are available with async/await syntax:
152160
post = await TextPost.objects.async_get(title='Async Post')
153161
posts = await TextPost.objects.filter(tags='python').async_to_list()
154162
count = await TextPost.objects.async_count()
155-
163+
156164
# Async iteration
157165
async for post in TextPost.objects.filter(published=True):
158166
print(post.title)
@@ -176,10 +184,10 @@ All major database operations are available with async/await syntax:
176184
from mongoengine import FileField
177185
class MyDoc(Document):
178186
file = FileField()
179-
187+
180188
doc = MyDoc()
181189
await MyDoc.file.async_put(file_data, instance=doc)
182-
190+
183191
# Context managers
184192
from mongoengine import async_switch_db
185193
async with async_switch_db(MyDoc, 'other_db'):
@@ -205,20 +213,20 @@ All major database operations are available with async/await syntax:
205213
The following features are intentionally not implemented due to low priority or complexity:
206214

207215
- **async_values()**, **async_values_list()**: Field projection methods
208-
216+
209217
*Reason*: Low usage frequency in typical applications. Can be implemented if needed.
210218

211219
- **async_explain()**: Query execution plan analysis
212-
220+
213221
*Reason*: Debugging/optimization feature with limited general use.
214222

215223
- **Hybrid Signal System**: Automatic sync/async signal handling
216-
217-
*Reason*: High complexity due to backward compatibility requirements.
224+
225+
*Reason*: High complexity due to backward compatibility requirements.
218226
Consider as separate project if needed.
219227

220228
- **ListField with ReferenceField**: Automatic AsyncReferenceProxy conversion
221-
229+
222230
*Reason*: Complex implementation requiring deep changes to ListField.
223231
Manual async dereferencing is required for now.
224232

docs/guide/async-support.rst

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,17 @@ Basic Queries
6262
6363
# Get single document
6464
user = await User.objects.async_get(name="John")
65-
65+
6666
# Get first matching document
6767
user = await User.objects.filter(email__contains="@gmail.com").async_first()
68-
68+
6969
# Count documents
7070
count = await User.objects.async_count()
7171
total_users = await User.objects.filter(active=True).async_count()
72-
72+
7373
# Check existence
7474
exists = await User.objects.filter(name="John").async_exists()
75-
75+
7676
# Convert to list
7777
users = await User.objects.filter(active=True).async_to_list()
7878
@@ -116,10 +116,10 @@ Update Operations
116116
117117
# Update single document
118118
result = await User.objects.filter(name="John").async_update_one(email="[email protected]")
119-
119+
120120
# Bulk update
121121
result = await User.objects.filter(active=False).async_update(active=True)
122-
122+
123123
# Update with operators
124124
await User.objects.filter(name="John").async_update(inc__login_count=1)
125125
await User.objects.filter(email="[email protected]").async_update(
@@ -134,7 +134,7 @@ Delete Operations
134134
135135
# Delete matching documents
136136
result = await User.objects.filter(active=False).async_delete()
137-
137+
138138
# Delete with cascade (if references exist)
139139
await User.objects.filter(name="John").async_delete()
140140
@@ -160,7 +160,7 @@ In async context, reference fields return an ``AsyncReferenceProxy`` that requir
160160
# Create and save documents
161161
user = User(name="Alice", email="[email protected]")
162162
await user.async_save()
163-
163+
164164
post = Post(title="My Post", author=user)
165165
await post.async_save()
166166
@@ -193,31 +193,31 @@ File Storage
193193
.. code-block:: python
194194
195195
from mongoengine import Document, FileField
196-
196+
197197
class MyDocument(Document):
198198
name = StringField()
199199
file = FileField()
200200
201201
# Store file asynchronously
202202
doc = MyDocument(name="My Document")
203-
203+
204204
with open("example.txt", "rb") as f:
205205
file_data = f.read()
206-
206+
207207
# Put file
208208
await MyDocument.file.async_put(file_data, instance=doc, filename="example.txt")
209209
await doc.async_save()
210210
211211
# Read file
212212
file_content = await MyDocument.file.async_read(doc)
213-
213+
214214
# Get file metadata
215215
file_proxy = await MyDocument.file.async_get(doc)
216216
print(f"File size: {file_proxy.length}")
217-
217+
218218
# Delete file
219219
await MyDocument.file.async_delete(doc)
220-
220+
221221
# Replace file
222222
await MyDocument.file.async_replace(new_file_data, doc, filename="new_example.txt")
223223
@@ -235,13 +235,13 @@ MongoDB transactions are supported through the ``async_run_in_transaction`` cont
235235
# All operations within this block are transactional
236236
sender = await Account.objects.async_get(user_id="sender123")
237237
receiver = await Account.objects.async_get(user_id="receiver456")
238-
238+
239239
sender.balance -= 100
240240
receiver.balance += 100
241-
241+
242242
await sender.async_save()
243243
await receiver.async_save()
244-
244+
245245
# Automatically commits on success, rolls back on exception
246246
247247
# Usage
@@ -305,7 +305,7 @@ Aggregation Pipelines
305305
pipeline = [
306306
{"$match": {"active": True}},
307307
{"$group": {
308-
"_id": "$department",
308+
"_id": "$department",
309309
"count": {"$sum": 1},
310310
"avg_salary": {"$avg": "$salary"}
311311
}},
@@ -327,7 +327,7 @@ Distinct Values
327327
# Get unique values
328328
departments = await User.objects.async_distinct("department")
329329
active_emails = await User.objects.filter(active=True).async_distinct("email")
330-
330+
331331
# Distinct on embedded documents
332332
cities = await User.objects.async_distinct("address.city")
333333
@@ -362,7 +362,7 @@ You can use both sync and async operations in the same application by using diff
362362
# Sync connection
363363
connect('mydb', alias='sync_conn')
364364
365-
# Async connection
365+
# Async connection
366366
await connect_async('mydb', alias='async_conn')
367367
368368
# Configure models to use specific connections
@@ -409,14 +409,14 @@ Batch Processing
409409
async def process_users_in_batches(batch_size=100):
410410
total = await User.objects.async_count()
411411
processed = 0
412-
412+
413413
while processed < total:
414414
batch = await User.objects.skip(processed).limit(batch_size).async_to_list()
415-
415+
416416
for user in batch:
417417
await process_single_user(user)
418418
await user.async_save()
419-
419+
420420
processed += len(batch)
421421
print(f"Processed {processed}/{total} users")
422422
@@ -475,7 +475,7 @@ Query execution plan analysis is not implemented as it's primarily a debugging f
475475
.. code-block:: python
476476
477477
from mongoengine.connection import get_db
478-
478+
479479
db = get_db() # Get async database
480480
collection = db[User._get_collection_name()]
481481
explanation = await collection.find({"active": True}).explain()
@@ -516,13 +516,13 @@ Step-by-Step Migration
516516
----------------------
517517

518518
1. **Update connection**:
519-
519+
520520
.. code-block:: python
521521
522522
# Before
523523
connect('mydb')
524-
525-
# After
524+
525+
# After
526526
await connect_async('mydb')
527527
528528
2. **Update function signatures**:
@@ -532,7 +532,7 @@ Step-by-Step Migration
532532
# Before
533533
def get_user(name):
534534
return User.objects.get(name=name)
535-
535+
536536
# After
537537
async def get_user(name):
538538
return await User.objects.async_get(name=name)
@@ -546,7 +546,7 @@ Step-by-Step Migration
546546
users = User.objects.filter(active=True)
547547
for user in users:
548548
process(user)
549-
549+
550550
# After
551551
await user.async_save()
552552
async for user in User.objects.filter(active=True):
@@ -558,7 +558,7 @@ Step-by-Step Migration
558558
559559
# Before (sync context)
560560
author = post.author # Automatic dereferencing
561-
561+
562562
# After (async context)
563563
author = await post.author.async_fetch() # Explicit fetching
564564
@@ -571,4 +571,4 @@ Compatibility Notes
571571
- **Performance**: Async operations provide better I/O concurrency
572572
- **MongoDB Support**: Works with all MongoDB versions supported by MongoEngine
573573

574-
For more examples and advanced usage, see the `API Reference <../apireference.html>`_.
574+
For more examples and advanced usage, see the `API Reference <../apireference.html>`_.

0 commit comments

Comments
 (0)