Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add new SyncEngine, support async and sync code #225

Closed
wants to merge 20 commits into from

Conversation

tiangolo
Copy link
Collaborator

@tiangolo tiangolo commented Jun 9, 2022

✨ Add new SyncEngine, support async and sync code

Even the same models can be shared, a model can be retrieved by one engine and saved by the other, etc.

This allows non-async code to use ODMantic, and more importantly, to gradually migrate an existing and sync codebase to ODMantic, migrating to async only/first for the parts that need optimization, but being able to keep the rest of the code.

It would also be, as far as I know, the first sync ODM for Mongo based on Pydantic and type annotations.

This is all backwards compatible.

Note 🚨

It's probably better to merge #227 first, as that fixes a bug with sessions and transactions.

Also, if that one with the session fix is accepted, then there's this PR: #231 that would supersede this one, as that one includes those session fixes for the new SyncEngine. In that case, that would be the one that should be merged and not this PR.

Implementation Details

There's a new SyncEngine besides the AIOEngine. Internally it uses pymongo, the official sync driver. It's API is pretty much the same as that one of motor, so the code changes are minimal, most of the code is copied from the existing implementation and adapted to not use async and await in those cases.

I also refactored the Engine into a BaseEngine with all the logic that can be shared (query preparation, etc) and the two engines that inherit from it, AIOEngine and SyncEngine.

Tests

For the tests, I copied each one of the tests that interact with the async engine and updated the copy to use the sync engine, making it non-async, updating mock checks, wrapping the iterator of sync_engine.find() into list for the tests, etc.

I considered having a separate file for the sync tests, but I thought it would be better to have each test very close to the original, that way it will be easier to maintain them, apply refactors, make sure they are in sync, and whenever a new test is added it will be easier to remember to add the async and sync version.

All the tests are currently passing. I'm still figuring out tox, I have never used it. Tox includes pymongo versions. ✅

Dependencies

Note: see the edit note in this section below.


Previous / old comment from 2022-06-09

It would make sense to not require motor always, as people not using async would still be able to use all of ODMantic in a sync way. I think it would make sense to have optional extra requirements to install motor or pymongo.

That way developers can install with:

$ pip install "odmantic[motor]"

or:

$ pip install "odmantic[pymongo]"

Or even both:

$ pip install "odmantic[motor,pymongo]"

Those names could also be changed, for example odmantic[async] and odmantic[sync] if that feels better, it would still install motor and pymongo respectively. For now, I put them as just odmantic[motor] and odmantic[pymongo].

2022-06-09T16:00:00Z Edit

Tweaking tox I realized that motor depends on pymongo, so, pymongo would always be required, for just sync or also for async. And then motor would be needed only for async.

In that case, it could make sense to make pymongo always required and make the optional extras just for odmantic[motor].

The other option is to keep both optional extras, I think the only possible advantage of keeping odmantic[pymongo] as an independent option would be if for any chance Motor could end up not depending on PyMongo, but I wouldn't think that's really feasible.

What do you think?

2022-06-22T13:38:00Z Edit

I realized Motor deeply depends on PyMongo, for example for bulk_write(), so I think it makes sense to make PyMongo always required, and Motor optional for async support. But let me now if it doesn't make sense to you!

Installing with Motor support looks like:

$ pip install "odmantic[motor]"

Next steps

  • Figure out tox and update it to include pymongo.
    • I'll work on that now. Done! ✅
  • Update the docs
    • I'll do it next. Maybe on this same PR or maybe on a new one, not sure what's best.
  • Make a release
    • I can't do this part 😅🙈

I'll update the docs next but I want to first wait for feedback on this PR first.

Future work

I also want to propose a new feature to allow changing the collection used during an operation that I'm needing. Similar to MongoEngine's switch_collection()... although the implementation for that one is not thread nor async safe, so that one is not really very useful in all the cases and might be dangerous. I'll propose something equivalent but safe for threads and contextvars (e.g. safe with FastAPI and any other web frameworks).

After that, I want to propose other features to use Mongo's batch updates for saving multiple documents instead of many atomic operations per document.

But all this would be after this initial step with the sync engine.

@codecov
Copy link

codecov bot commented Jun 9, 2022

Codecov Report

Merging #225 (f7460a6) into master (623fa27) will decrease coverage by 0.05%.
The diff coverage is 99.78%.

@@             Coverage Diff             @@
##            master     #225      +/-   ##
===========================================
- Coverage   100.00%   99.94%   -0.06%     
===========================================
  Files           38       38              
  Lines         2725     3374     +649     
  Branches       413      477      +64     
===========================================
+ Hits          2725     3372     +647     
- Misses           0        1       +1     
- Partials         0        1       +1     
Flag Coverage Δ
tests-3.6-4-standalone 99.70% <99.78%> (-0.01%) ⬇️
tests-3.6-4.2-standalone 99.70% <99.78%> (-0.01%) ⬇️
tests-3.6-4.4-standalone 99.70% <99.78%> (-0.01%) ⬇️
tests-3.7-4-standalone 99.55% <99.78%> (+0.03%) ⬆️
tests-3.7-4.2-standalone 99.55% <99.78%> (+0.03%) ⬆️
tests-3.7-4.4-standalone 99.55% <99.78%> (+0.03%) ⬆️
tests-3.8-4-replicaSet 98.28% <99.78%> (+0.33%) ⬆️
tests-3.8-4-standalone 99.49% <99.78%> (+0.04%) ⬆️
tests-3.8-4.2-sharded 98.28% <99.78%> (+0.33%) ⬆️
tests-3.8-4.2-standalone 99.49% <99.78%> (+0.04%) ⬆️
tests-3.8-4.4-standalone 99.49% <99.78%> (+0.04%) ⬆️
tests-3.9-4-standalone 99.37% <99.56%> (+0.07%) ⬆️
tests-3.9-4.2-standalone 99.37% <99.56%> (+0.07%) ⬆️
tests-3.9-4.4-standalone 99.37% <99.56%> (+0.07%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
odmantic/engine.py 99.20% <98.42%> (-0.80%) ⬇️
odmantic/__init__.py 100.00% <100.00%> (ø)
tests/integration/fastapi/test_doc_example.py 100.00% <100.00%> (ø)
tests/integration/test_embedded_model.py 100.00% <100.00%> (ø)
tests/integration/test_engine.py 100.00% <100.00%> (ø)
tests/integration/test_engine_reference.py 100.00% <100.00%> (ø)
tests/integration/test_query.py 100.00% <100.00%> (ø)
tests/integration/test_types.py 100.00% <100.00%> (ø)
tests/integration/test_zoo.py 100.00% <100.00%> (ø)

📣 Codecov can now indicate which changes are the most critical in Pull Requests. Learn more

@tiangolo
Copy link
Collaborator Author

tiangolo commented Jun 9, 2022

(also added as an edit in the PR description) 👇

Tweaking tox I realized that motor depends on pymongo, so, pymongo would always be required, for just sync or also for async. And then motor would be needed only for async.

In that case, it could make sense to make pymongo always required and make the optional extras just for odmantic[motor].

The other option is to keep both optional extras, I think the only possible advantage of keeping odmantic[pymongo] as an independent option would be if for any chance Motor could end up not depending on PyMongo, but I wouldn't think that's really feasible.

What do you think?

Note: check the comment below, I think it makes more sense to just make PyMongo required as it's always gonna be required either way. But let me know if it doesn't make sense to you!

@tiangolo
Copy link
Collaborator Author

I realized that Motor deeply depends on PyMongo, for example, it requires PyMongo objects for bulk_write().

So I think it doesn't really make much sense to consider PyMongo an "optional" dependency, and it would just add maintenance burden.

I went ahead and removed the "optional" PyMongo parts and made them required.


As a side note, I'm working on adding support for bulk writes, that's how I came to this realization.

@art049
Copy link
Owner

art049 commented Aug 19, 2022

I went ahead and removed the "optional" PyMongo parts and made them required.

I agree, having pymongo required makes complete sense!

Closing this one in favor of #231

@art049 art049 closed this Aug 19, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants