Skip to content

Commit

Permalink
pymongo4 now fully supported alongside pymongo3
Browse files Browse the repository at this point in the history
  • Loading branch information
dill0wn committed Aug 1, 2024
1 parent 0099e0c commit 7e42e85
Show file tree
Hide file tree
Showing 25 changed files with 639 additions and 718 deletions.
15 changes: 10 additions & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ on:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
tests:
name: Python ${{ matrix.python-version }}
name: Python ${{ matrix.python-version }}-${{ matrix.pymongo-version }}
runs-on: ubuntu-latest

strategy:
Expand All @@ -28,6 +28,9 @@ jobs:
- '3.11'
- '3.12'
- pypy3.9
pymongo-version:
- pymongo3
- pymongo4

services:
mongodb:
Expand All @@ -39,7 +42,7 @@ jobs:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v4

- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
Expand All @@ -52,12 +55,14 @@ jobs:
- name: Run tox targets for ${{ matrix.python-version }}
# sed is used to remove the '.' so '3.7' becomes '37' for tox (and pypy3 doesn't become pypypy3)
# and drop the '-dev' from any prerelease version
run: tox --skip-missing-interpreters false -e py`echo ${{ matrix.python-version }} | sed s/\\\.// | sed s/pypy/py/ | sed s/-dev//`
run: |
export PY_VER=`echo ${{ matrix.python-version }} | sed s/\\\.// | sed s/pypy/py/ | sed s/-dev//`
tox --skip-missing-interpreters false -e "py$PY_VER-${{ matrix.pymongo-version }}"
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
fail_ci_if_error: true
fail_ci_if_error: false
flags: tests-${{ matrix.python-version }}
name: codecov-umbrella
verbose: true
Expand Down
21 changes: 21 additions & 0 deletions docs/news.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,27 @@ Ming News / Release Notes

The latest releases support PyMongo 3. The older 0.5.x releases support PyMongo 2 and Python 2.

Pre-Release
---------------------
* Prepare for PyMongo 4.0 support
* Replace ``find_and_modify()`` session methods with ``find_one_and_update()``, ``find_one_and_replace()``,
and ``find_one_and_delete()`` to closer match pymongo4's API
* Remove ``group()`` session methods as they are unsupported in pymongo4. Use the aggregation pipeline.
* Remove ``map_reduce()`` and ``inline_map_reduce()`` session methods as they are unsupported in pymongo4.
Use the aggregation pipeline.
* Several operations now return their mongo-native result objects; UpdateResult, InsertResult, etc.
* MIM: Replace ``mim.Collection.insert()`` with ``insert_one()`` and ``insert_many()`` to match pymongo4
* MIM: Remove deprecated ``manipulate`` and ``safe`` args from pymongo's ``insert_one`` and ``insert_many`` methods
* MIM: Replace ``mim.Collection.update()`` with ``update_one()`` and ``update_many()`` to match pymongo4
* MIM: Replace ``mim.Collection.count()`` and ``mim.Cursor.count()`` with
``mim.Collection.estimated_document_count()`` and ``mim.Collection.count_documents()`` to match pymongo4
* MIM: Replace ``mim.Collection.remove()`` with ``mim.Collection.delete_one()``
and ``mim.Collection.delete_many()`` to match pymongo4
* MIM: Rename ``collection_names()`` and ``database_names()`` to ``list_collection_names()``
and ``list_database_names``
* MIM: Remove ``mim.Collection.map_reduce()`` and ``mim.Collection.inline_map_reduce()`` to match pymongo4
* MIM: Replace ``ensure_index()`` with ``create_index()`` to match pymongo4

0.13.0 (Mar 16, 2023)
---------------------
* remove Python 3.6 support
Expand Down
6 changes: 3 additions & 3 deletions docs/presentations/pyatl-20100114/src/demo1.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@

pages = db.pages

pages.insert(page)
pages.insert_one(page)

db.collection_names()
db.list_collection_names()

page = pages.find_one()

page['author'] = 'Rick Copeland'

pages.save(page)
pages.replace_one(dict(_id=page._id), page)

pages.find_one()
4 changes: 2 additions & 2 deletions docs/src/ming_odm_migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def snippet1():
TAGS = ['foo', 'bar', 'snafu', 'mongodb']

# Insert the documents through PyMongo so that Ming is not involved
session.db.wiki_page.insert([
session.db.wiki_page.insert_many([
dict(title='Page %s' % idx, text='Text of Page %s' %idx, tags=random.sample(TAGS, 2)) for idx in range(10)
])

Expand All @@ -134,7 +134,7 @@ def snippet5():
next(session.db.wiki_page.find()).get('metadata')

def snippet6():
session.db.mymodel.insert(dict(name='desrever'))
session.db.mymodel.insert_one(dict(name='desrever'))
session.db.mymodel.find_one()

# Apply migration to version 1 and then to version 2
Expand Down
4 changes: 2 additions & 2 deletions docs/src/ming_odm_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ def snippet1_2():

def snippet1_3():
Contact.query.remove({})
session.db.contact.insert(dict(name='Invalid Contact',
email='this-is-invalid'))
session.db.contact.insert_one(dict(name='Invalid Contact',
email='this-is-invalid'))

try:
c1 = Contact.query.find().first()
Expand Down
6 changes: 3 additions & 3 deletions docs/src/ming_odm_tutorial.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,9 @@ def snippet5_1():
session.flush()

def snippet5_3():
WikiPage.query.find_and_modify({'title': 'MyFirstPage'},
update={'$set': {'text': 'This is my first page'}},
new=True)
WikiPage.query.find_one_and_update({'title': 'MyFirstPage'},
update={'$set': {'text': 'This is my first page'}},
upsert=True)

def snippet5_4():
wp = WikiPage.query.get(title='MyFirstPage')
Expand Down
4 changes: 2 additions & 2 deletions docs/src/ming_welcome.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ class __mongometa__:


def snippet1():
session.db.wiki_page.insert({'title': 'FirstPage',
'text': 'This is my first page'})
session.db.wiki_page.insert_one({'title': 'FirstPage',
'text': 'This is my first page'})
session.db.wiki_page.find_one({'title': 'FirstPage'})


Expand Down
6 changes: 3 additions & 3 deletions docs/userguide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ Querying Objects
Once we have a `WikiPage` in the database, we can retrieve it using the `.query`
attribute. The query attribute is a proxy to the Session query features which expose
three methods that make possible to query objects :meth:`._ClassQuery.get`,
:meth:`.ODMSession.find` and :meth:`.ODMSession.find_and_modify`:
:meth:`.ODMSession.find` and :meth:`.ODMSession.find_one_and_update`:

.. run-pysnippet:: ming_odm_tutorial snippet2

Expand Down Expand Up @@ -340,7 +340,7 @@ will track that the object needs to be updated:
:skip: 1
:emphasize-lines: 17

Another option to edit an object is to actually rely on :meth:`.ODMSession.find_and_modify`
Another option to edit an object is to actually rely on :meth:`.ODMSession.find_one_and_update`
method which will query the object and update it atomically:

.. run-pysnippet:: ming_odm_tutorial snippet5_3
Expand All @@ -349,7 +349,7 @@ This is often used to increment counters or acquire global locks in mongodb

.. note::

``find_and_modify`` always refreshes the object in the IdentityMap, so the object
``find_one_and_update`` always refreshes the object in the IdentityMap, so the object
in your IdentityMap will always get replaced with the newly retrieved value. Make
sure you properly flushed any previous change to the object and use the ``new`` option
to avoid retrieving a stale version of the object if you plan to modify it.
Expand Down
10 changes: 8 additions & 2 deletions ming/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,12 @@ class Cursor:
def __bool__(self):
raise MingException('Cannot evaluate Cursor to a boolean')

def __init__(self, cls, cursor, allow_extra=True, strip_extra=True):
def __init__(self, cls, cursor, allow_extra=True, strip_extra=True, find_spec=None):
self.cls = cls
self.cursor = cursor
self._allow_extra = allow_extra
self._strip_extra = strip_extra
self.find_spec = find_spec

def __iter__(self):
return self
Expand All @@ -89,7 +90,12 @@ def next(self):
__next__ = next

def count(self):
return self.cursor.count()
"""
This method, although deprecated by pymongo, is kept for backcompat with existing code.
It is inaccurate when used with a cursor that has been limited or skipped. However,
this behavior is consistent with previous pymongo (3.X) and mongo shell (4.X) behavior.
"""
return self.cursor.collection.count_documents(self.find_spec)

def distinct(self, *args, **kwargs):
return self.cursor.distinct(*args, **kwargs)
Expand Down
7 changes: 4 additions & 3 deletions ming/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,9 @@ class _ClassManager(metaclass=_CurriedProxyClass):
_proxy_args=('cls',)
_proxy_methods = (
'get', 'find', 'find_by', 'remove', 'count', 'update_partial',
'group', 'ensure_index', 'ensure_indexes', 'index_information', 'drop_indexes',
'find_and_modify', 'aggregate', 'distinct', 'map_reduce', 'inline_map_reduce',
'create_index', 'ensure_index', 'ensure_indexes', 'index_information', 'drop_indexes',
'find_one_and_update', 'find_one_and_replace', 'find_one_and_delete',
'aggregate', 'distinct',
)
InstanceManagerClass=_InstanceManager

Expand Down Expand Up @@ -358,7 +359,7 @@ def _ensure_indexes(self):
try:
with self._lock:
for idx in self.manager.indexes:
collection.ensure_index(idx.index_spec, background=True,
collection.create_index(idx.index_spec, background=True,
**idx.index_options)
except (MongoGone, ConnectionFailure) as e:
if e.args[0] == 'not master':
Expand Down
18 changes: 9 additions & 9 deletions ming/metadata.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from bson import ObjectId
from datetime import datetime
from typing import Generic, TypeVar, Any, Optional, overload, List, Dict, Type, Union, Mapping, type_check_only
from pymongo.results import UpdateResult, DeleteResult

import bson
from ming.base import Cursor
Expand Down Expand Up @@ -178,7 +179,6 @@ class Field:
M = TypeVar('M')

MongoFilter = dict
ChangeResult = dict
SaveResult = Union[ObjectId, Any]
class _ClassManager(Generic[M]):
# proxies these from Session
Expand All @@ -190,28 +190,28 @@ class _ClassManager(Generic[M]):
def find_by(self, filter: MongoFilter, *args, validate: bool = True, **kwargs) -> Cursor[M]: ...
#@overload
#def find_by(self, filter: MongoFilter, *args, validate: Literal[False], **kwargs) -> Generator[M]: ...
def remove(self, spec_or_id: Union[MongoFilter, ObjectId] = None, **kwargs) -> ChangeResult: ...
def remove(self, spec_or_id: Union[MongoFilter, ObjectId] = None, **kwargs) -> DeleteResult: ...
def count(self) -> int: ...
def update_partial(self, filter: MongoFilter, fields: dict, **kwargs) -> ChangeResult: ...
def update_partial(self, filter: MongoFilter, fields: dict, **kwargs) -> UpdateResult: ...
def find_one_and_update(self, **kwargs) -> M: ...
def find_one_and_replace(self, **kwargs) -> M: ...
def find_one_and_delete(self, **kwargs) -> M: ...
"""
def group(self) -> int: ...
def ensure_indexes(self) -> int: ...
def index_information(self) -> int: ...
def drop_indexes(self) -> int: ...
def find_and_modify(self) -> int: ...
def aggregate(self) -> int: ...
def distinct(self) -> int: ...
def map_reduce(self) -> int: ...
def inline_map_reduce(self) -> int: ...
"""

class _InstanceManager:
# proxies these from Session
def save(self, *args: str, **kwargs) -> SaveResult: ...
def insert(self, **kwargs) -> SaveResult: ...
def upsert(self, spec_fields: List[str], **kwargs) -> ChangeResult: ...
def delete(self) -> ChangeResult: ...
def set(self, fields_values: Mapping[str, Any]) -> ChangeResult: ...
def upsert(self, spec_fields: List[str], **kwargs) -> UpdateResult: ...
def delete(self) -> DeleteResult: ...
def set(self, fields_values: Mapping[str, Any]) -> UpdateResult: ...
def increase_field(self, **kwargs) -> None: ...


Expand Down
Loading

0 comments on commit 7e42e85

Please sign in to comment.