Compare commits

...

23 Commits

Author SHA1 Message Date
Bastien Gérard
04c26acdd6 Merge pull request #2513 from bagerard/prepare_release
Prepare release
2021-05-04 12:15:34 +02:00
Bastien Gerard
d0a15a8924 prepare changelog 2021-05-04 10:50:56 +02:00
Bastien Gerard
2215e2746b run latest autoformatter 2021-05-04 10:50:56 +02:00
Bastien Gerard
0e6bcbc030 update precommit 2021-05-04 10:50:56 +02:00
Bastien Gérard
232071f8f4 Merge pull request #2505 from deveil/patch-1
Typo correction
2021-04-23 20:45:33 +02:00
Bastien Gérard
fc0fb31d43 Merge pull request #2503 from bagerard/precommit_update
housekeeping - pre-commit autoupdate
2021-04-23 20:44:34 +02:00
deveil
1bd8cd803e Typo correction 2021-04-23 16:08:57 +02:00
Bastien Gerard
ef57a58155 housekeeping - pre-commit autoupdate 2021-04-22 10:18:15 +02:00
Bastien Gérard
9680259904 Merge pull request #2497 from bagerard/switch_to_isort
Switch to isort + add few pre-commit hooks
2021-03-31 14:11:23 +02:00
Bastien Gerard
49a4d23371 update hooks version 2021-03-29 22:04:07 +02:00
Bastien Gerard
b9d370c885 add standard hooks 2021-03-29 22:02:57 +02:00
Bastien Gerard
e5a2714baf update doc for isort 2021-03-29 21:55:34 +02:00
Bastien Gerard
ff596fcb7e replace flake8-import-order by isort 2021-03-29 21:50:22 +02:00
Bastien Gérard
f0fad6df19 Merge pull request #2485 from janste63/fix_2484
Test case and proposed solution for #2484
2021-03-29 20:52:53 +02:00
Jan Stein
da173cf0e2 Merge branch 'master' into fix_2484 2021-03-25 15:27:48 +01:00
Bastien Gérard
1669f0c5a4 Merge pull request #2489 from bagerard/fix_one_to_many_doc
Fix one-to-many example that is actually a many to many
2021-03-09 13:41:03 +01:00
Bastien Gérard
b045925efe Merge pull request #2481 from bagerard/improve_connect_doc
improve connect() doc and put more emphasis on URI string
2021-03-09 13:40:45 +01:00
Bastien Gerard
b3ce65453a Fix one-to-many example that is actually a many to many 2021-03-08 22:21:48 +01:00
Bastien Gerard
50d891cb7b more improvement to connect doc 2021-03-08 21:52:14 +01:00
Bastien Gerard
e31f9150d2 improve connect() doc and put more emphasis on URI string 2021-03-08 21:51:43 +01:00
Bastien Gérard
74ceb9703b Merge pull request #2488 from bagerard/add_lazy_loading_method_ease_profiling
Add _lazy_load_ref methods to make profiling easier
2021-03-08 19:18:54 +01:00
Bastien Gerard
58a3c6de03 Add _lazy_load_ref methods so that the impact of lazy loading surfaces when profiling 2021-03-08 00:14:53 +01:00
Jan Stein
8fd969aba9 Test case and proposed solution for #2484 2021-03-03 14:58:03 +01:00
57 changed files with 304 additions and 176 deletions

View File

@@ -140,4 +140,4 @@ jobs:
- name: publish pypi
uses: pypa/gh-action-pypi-publish@master
with:
password: ${{ secrets.pypi_token }}
password: ${{ secrets.pypi_token }}

View File

@@ -1,17 +1,26 @@
fail_fast: false
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0
hooks:
- id: check-merge-conflict
- id: debug-statements
- id: trailing-whitespace
- id: end-of-file-fixer
- repo: https://github.com/ambv/black
rev: 20.8b1
rev: 21.4b2
hooks:
- id: black
- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.4
rev: 3.9.1
hooks:
- id: flake8
additional_dependencies:
- flake8-import-order
- repo: https://github.com/asottile/pyupgrade
rev: v2.7.4
rev: v2.14.0
hooks:
- id: pyupgrade
args: [--py36-plus]
- repo: https://github.com/pycqa/isort
rev: 5.8.0
hooks:
- id: isort

View File

@@ -259,3 +259,4 @@ that much better:
* Agustin Barto (https://github.com/abarto)
* Stankiewicz Mateusz (https://github.com/mas15)
* Felix Schultheiß (https://github.com/felix-smashdocs)
* Jan Stein (https://github.com/janste63)

View File

@@ -35,8 +35,8 @@ Travis runs the tests against the main Python 3.x versions.
Style Guide
-----------
MongoEngine's codebase is formatted with `black <https://github.com/python/black>`_, other tools like
flake8 are also used. Those tools will run as part of the CI and will fail in case the code is not formatted properly.
MongoEngine's codebase is auto-formatted with `black <https://github.com/python/black>`_, imports are ordered with `isort <https://pycqa.github.io/isort/>`_
and other tools like flake8 are also used. Those tools will run as part of the CI and will fail in case the code is not formatted properly.
To install all development tools, simply run the following commands:
@@ -58,6 +58,10 @@ To enable ``pre-commit`` simply run:
See the ``.pre-commit-config.yaml`` configuration file for more information
on how it works.
pre-commit will now run upon every commit and will reject anything that doesn't comply.
You can also run all the checks with ``pre-commit run -a``, this is what is used in the CI.
Testing
-------

View File

@@ -31,7 +31,7 @@ myNoddys = noddy.find()
print("-" * 100)
print("PyMongo: Creating 10000 dictionaries.")
t = timeit.Timer(stmt=stmt, setup=setup)
print("{}s".format(t.timeit(1)))
print(f"{t.timeit(1)}s")
stmt = """
from pymongo import MongoClient, WriteConcern
@@ -54,7 +54,7 @@ myNoddys = noddy.find()
print("-" * 100)
print('PyMongo: Creating 10000 dictionaries (write_concern={"w": 0}).')
t = timeit.Timer(stmt=stmt, setup=setup)
print("{}s".format(t.timeit(1)))
print(f"{t.timeit(1)}s")
setup = """
from pymongo import MongoClient
@@ -84,7 +84,7 @@ myNoddys = Noddy.objects()
print("-" * 100)
print("MongoEngine: Creating 10000 dictionaries.")
t = timeit.Timer(stmt=stmt, setup=setup)
print("{}s".format(t.timeit(1)))
print(f"{t.timeit(1)}s")
stmt = """
for i in range(10000):
@@ -102,7 +102,7 @@ myNoddys = Noddy.objects()
print("-" * 100)
print("MongoEngine: Creating 10000 dictionaries (using a single field assignment).")
t = timeit.Timer(stmt=stmt, setup=setup)
print("{}s".format(t.timeit(1)))
print(f"{t.timeit(1)}s")
stmt = """
for i in range(10000):
@@ -118,7 +118,7 @@ myNoddys = Noddy.objects()
print("-" * 100)
print('MongoEngine: Creating 10000 dictionaries (write_concern={"w": 0}).')
t = timeit.Timer(stmt=stmt, setup=setup)
print("{}s".format(t.timeit(1)))
print(f"{t.timeit(1)}s")
stmt = """
for i in range(10000):
@@ -136,7 +136,7 @@ myNoddys = Noddy.objects()
'MongoEngine: Creating 10000 dictionaries (write_concern={"w": 0}, validate=False).'
)
t = timeit.Timer(stmt=stmt, setup=setup)
print("{}s".format(t.timeit(1)))
print(f"{t.timeit(1)}s")
stmt = """
for i in range(10000):
@@ -154,7 +154,7 @@ myNoddys = Noddy.objects()
'MongoEngine: Creating 10000 dictionaries (force_insert=True, write_concern={"w": 0}, validate=False).'
)
t = timeit.Timer(stmt=stmt, setup=setup)
print("{}s".format(t.timeit(1)))
print(f"{t.timeit(1)}s")
if __name__ == "__main__":

View File

@@ -8,8 +8,13 @@ Development
===========
- (Fill this out as you fix issues and develop your features).
Changes in 0.23.0
Changes in 0.23.1
===========
- Bug fix: ignore LazyReferenceFields when clearing _changed_fields #2484
- Improve connection doc #2481
Changes in 0.23.0
=================
- Bugfix: manually setting SequenceField in DynamicDocument doesn't increment the counter #2471
- Add MongoDB 4.2 and 4.4 to CI
- Add support for allowDiskUse on querysets #2468

View File

@@ -5,7 +5,7 @@ Connecting to MongoDB
=====================
Connections in MongoEngine are registered globally and are identified with aliases.
If no `alias` is provided during the connection, it will use "default" as alias.
If no ``alias`` is provided during the connection, it will use "default" as alias.
To connect to a running instance of :program:`mongod`, use the :func:`~mongoengine.connect`
function. The first argument is the name of the database to connect to::
@@ -14,27 +14,66 @@ function. The first argument is the name of the database to connect to::
connect('project1')
By default, MongoEngine assumes that the :program:`mongod` instance is running
on **localhost** on port **27017**. If MongoDB is running elsewhere, you should
provide the :attr:`host` and :attr:`port` arguments to
:func:`~mongoengine.connect`::
on **localhost** on port **27017**.
connect('project1', host='192.168.1.35', port=12345)
If MongoDB is running elsewhere, you need to provide details on how to connect. There are two ways of
doing this. Using a connection string in URI format (**this is the preferred method**) or individual attributes
provided as keyword arguments.
Connect with URI string
=======================
When using a connection string in URI format you should specify the connection details
as the :attr:`host` to :func:`~mongoengine.connect`. In a web application context for instance, the URI
is typically read from the config file::
connect(host="mongodb://127.0.0.1:27017/my_db")
If the database requires authentication, you can specify it in the
URI. As each database can have its own users configured, you need to tell MongoDB
where to look for the user you are working with, that's what the ``?authSource=admin`` bit
of the MongoDB connection string is for::
# Connects to 'my_db' database by authenticating
# with given credentials against the 'admin' database (by default as authSource isn't provided)
connect(host="mongodb://my_user:my_password@127.0.0.1:27017/my_db")
# Equivalent to previous connection but explicitly states that
# it should use admin as the authentication source database
connect(host="mongodb://my_user:my_password@hostname:port/my_db?authSource=admin")
# Connects to 'my_db' database by authenticating
# with given credentials against that same database
connect(host="mongodb://my_user:my_password@127.0.0.1:27017/my_db?authSource=my_db")
The URI string can also be used to configure advanced parameters like ssl, replicaSet, etc. For more
information or example about URI string, you can refer to the `official doc <https://docs.mongodb.com/manual/reference/connection-string/>`_::
connect(host="mongodb://my_user:my_password@127.0.0.1:27017/my_db?authSource=admin&ssl=true&replicaSet=globaldb")
.. note:: URI containing SRV records (e.g "mongodb+srv://server.example.com/") can be used as well
Connect with keyword attributes
===============================
The second option for specifying the connection details is to provide the information as keyword
attributes to :func:`~mongoengine.connect`::
connect('my_db', host='127.0.0.1', port=27017)
If the database requires authentication, :attr:`username`, :attr:`password`
and :attr:`authentication_source` arguments should be provided::
connect('project1', username='webapp', password='pwd123', authentication_source='admin')
connect('my_db', username='my_user', password='my_password', authentication_source='admin')
URI style connections are also supported -- just supply the URI as
the :attr:`host` to
:func:`~mongoengine.connect`::
connect('project1', host='mongodb://localhost/database_name')
.. note:: URI containing SRV records (e.g mongodb+srv://server.example.com/) can be used as well as the :attr:`host`
The set of attributes that :func:`~mongoengine.connect` recognizes includes but is not limited to:
:attr:`host`, :attr:`port`, :attr:`read_preference`, :attr:`username`, :attr:`password`, :attr:`authentication_source`, :attr:`authentication_mechanism`,
:attr:`replicaset`, :attr:`tls`, etc. Most of the parameters accepted by `pymongo.MongoClient <https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient>`_
can be used with :func:`~mongoengine.connect` and will simply be forwarded when instantiating the `pymongo.MongoClient`.
.. note:: Database, username and password from URI string overrides
corresponding parameters in :func:`~mongoengine.connect`: ::
corresponding parameters in :func:`~mongoengine.connect`, this should
obviously be avoided: ::
connect(
db='test',
@@ -43,28 +82,19 @@ the :attr:`host` to
host='mongodb://admin:qwerty@localhost/production'
)
will establish connection to ``production`` database using
``admin`` username and ``12345`` password.
will establish connection to ``production`` database using ``admin`` username and ``qwerty`` password.
.. note:: Calling :func:`~mongoengine.connect` without argument will establish
a connection to the "test" database by default
Replica Sets
============
Read Preferences
================
MongoEngine supports connecting to replica sets::
from mongoengine import connect
# Regular connect
connect('dbname', replicaset='rs-name')
# MongoDB URI-style connect
connect(host='mongodb://localhost/dbname?replicaSet=rs-name')
Read preferences are supported through the connection or via individual
As stated above, Read preferences are supported through the connection but also via individual
queries by passing the read_preference ::
from pymongo import ReadPreference
Bar.objects().read_preference(ReadPreference.PRIMARY)
Bar.objects(read_preference=ReadPreference.PRIMARY)

View File

@@ -290,12 +290,12 @@ as the constructor's argument::
content = StringField()
.. _one-to-many-with-listfields:
.. _many-to-many-with-listfields:
One to Many with ListFields
Many to Many with ListFields
'''''''''''''''''''''''''''
If you are implementing a one to many relationship via a list of references,
If you are implementing a many to many relationship via a list of references,
then the references are stored as DBRefs and to query you need to pass an
instance of the object to the query::

View File

@@ -35,7 +35,7 @@ existing ``User`` model with a `default=True`. Thus you simply update the ``User
class User(Document):
name = StringField(required=True)
enabled = BooleaField(default=True)
enabled = BooleanField(default=True)
Without applying any migration, we now reload an object from the database into the ``User`` class
and checks its `enabled` attribute:

View File

@@ -120,4 +120,3 @@ the validation and cleaning of a document when you call :meth:`~mongoengine.docu
Person(age=1000).save(validate=False)
person = Person.objects.first()
assert person.age == 1000

View File

@@ -1,10 +1,12 @@
# Import submodules so that we can expose their __all__
from mongoengine import connection
from mongoengine import document
from mongoengine import errors
from mongoengine import fields
from mongoengine import queryset
from mongoengine import signals
from mongoengine import (
connection,
document,
errors,
fields,
queryset,
signals,
)
# Import everything from each submodule so that it can be accessed via
# mongoengine, e.g. instead of `from mongoengine.connection import connect`,
@@ -17,7 +19,6 @@ from mongoengine.fields import * # noqa: F401
from mongoengine.queryset import * # noqa: F401
from mongoengine.signals import * # noqa: F401
__all__ = (
list(document.__all__)
+ list(fields.__all__)
@@ -28,7 +29,7 @@ __all__ = (
)
VERSION = (0, 23, 0)
VERSION = (0, 23, 1)
def get_version():

View File

@@ -1,10 +1,9 @@
import copy
import numbers
from functools import partial
from bson import DBRef, ObjectId, SON, json_util
import pymongo
from bson import SON, DBRef, ObjectId, json_util
from mongoengine import signals
from mongoengine.base.common import get_document
@@ -615,7 +614,9 @@ class BaseDocument:
def _get_changed_fields(self):
"""Return a list of all fields that have explicitly been changed."""
EmbeddedDocument = _import_class("EmbeddedDocument")
LazyReferenceField = _import_class("LazyReferenceField")
ReferenceField = _import_class("ReferenceField")
GenericLazyReferenceField = _import_class("GenericLazyReferenceField")
GenericReferenceField = _import_class("GenericReferenceField")
SortedListField = _import_class("SortedListField")
@@ -641,7 +642,13 @@ class BaseDocument:
changed_fields += [f"{key}{k}" for k in changed if k]
elif isinstance(data, (list, tuple, dict)):
if hasattr(field, "field") and isinstance(
field.field, (ReferenceField, GenericReferenceField)
field.field,
(
LazyReferenceField,
ReferenceField,
GenericLazyReferenceField,
GenericReferenceField,
),
):
continue
elif isinstance(field, SortedListField) and field._ordering:

View File

@@ -1,11 +1,15 @@
import operator
import weakref
from bson import DBRef, ObjectId, SON
import pymongo
from bson import SON, DBRef, ObjectId
from mongoengine.base.common import UPDATE_OPERATORS
from mongoengine.base.datastructures import BaseDict, BaseList, EmbeddedDocumentList
from mongoengine.base.datastructures import (
BaseDict,
BaseList,
EmbeddedDocumentList,
)
from mongoengine.common import _import_class
from mongoengine.errors import DeprecatedError, ValidationError
@@ -267,6 +271,17 @@ class ComplexBaseField(BaseField):
self.field = field
super().__init__(**kwargs)
@staticmethod
def _lazy_load_refs(instance, name, ref_values, *, max_depth):
_dereference = _import_class("DeReference")()
documents = _dereference(
ref_values,
max_depth=max_depth,
instance=instance,
name=name,
)
return documents
def __get__(self, instance, owner):
"""Descriptor to automatically dereference references."""
if instance is None:
@@ -284,19 +299,15 @@ class ComplexBaseField(BaseField):
or isinstance(self.field, (GenericReferenceField, ReferenceField))
)
_dereference = _import_class("DeReference")()
if (
instance._initialised
and dereference
and instance._data.get(self.name)
and not getattr(instance._data[self.name], "_dereferenced", False)
):
instance._data[self.name] = _dereference(
instance._data.get(self.name),
max_depth=1,
instance=instance,
name=self.name,
ref_values = instance._data.get(self.name)
instance._data[self.name] = self._lazy_load_refs(
ref_values=ref_values, instance=instance, name=self.name, max_depth=1
)
if hasattr(instance._data[self.name], "_dereferenced"):
instance._data[self.name]._dereferenced = True
@@ -322,7 +333,9 @@ class ComplexBaseField(BaseField):
and isinstance(value, (BaseList, BaseDict))
and not value._dereferenced
):
value = _dereference(value, max_depth=1, instance=instance, name=self.name)
value = self._lazy_load_refs(
ref_values=value, instance=instance, name=self.name, max_depth=1
)
value._dereferenced = True
instance._data[self.name] = value

View File

@@ -2,7 +2,11 @@ import itertools
import warnings
from mongoengine.base.common import _document_registry
from mongoengine.base.fields import BaseField, ComplexBaseField, ObjectIdField
from mongoengine.base.fields import (
BaseField,
ComplexBaseField,
ObjectIdField,
)
from mongoengine.common import _import_class
from mongoengine.errors import InvalidDocumentError
from mongoengine.queryset import (
@@ -12,7 +16,6 @@ from mongoengine.queryset import (
QuerySetManager,
)
__all__ = ("DocumentMetaclass", "TopLevelDocumentMetaclass")

View File

@@ -217,8 +217,8 @@ def register_connection(
def disconnect(alias=DEFAULT_CONNECTION_NAME):
"""Close the connection with a given alias."""
from mongoengine.base.common import _get_documents_by_db
from mongoengine import Document
from mongoengine.base.common import _get_documents_by_db
if alias in _connections:
get_connection(alias=alias).close()

View File

@@ -1,4 +1,4 @@
from bson import DBRef, SON
from bson import SON, DBRef
from mongoengine.base import (
BaseDict,
@@ -10,7 +10,12 @@ from mongoengine.base import (
from mongoengine.base.datastructures import LazyReference
from mongoengine.connection import get_db
from mongoengine.document import Document, EmbeddedDocument
from mongoengine.fields import DictField, ListField, MapField, ReferenceField
from mongoengine.fields import (
DictField,
ListField,
MapField,
ReferenceField,
)
from mongoengine.queryset import QuerySet

View File

@@ -1,7 +1,7 @@
import re
from bson.dbref import DBRef
import pymongo
from bson.dbref import DBRef
from pymongo.read_preferences import ReadPreference
from mongoengine import signals
@@ -16,14 +16,23 @@ from mongoengine.base import (
)
from mongoengine.common import _import_class
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
from mongoengine.context_managers import set_write_concern, switch_collection, switch_db
from mongoengine.context_managers import (
set_write_concern,
switch_collection,
switch_db,
)
from mongoengine.errors import (
InvalidDocumentError,
InvalidQueryError,
SaveConditionError,
)
from mongoengine.pymongo_support import list_collection_names
from mongoengine.queryset import NotUniqueError, OperationError, QuerySet, transform
from mongoengine.queryset import (
NotUniqueError,
OperationError,
QuerySet,
transform,
)
__all__ = (
"Document",
@@ -323,7 +332,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
_refs=None,
save_condition=None,
signal_kwargs=None,
**kwargs
**kwargs,
):
"""Save the :class:`~mongoengine.Document` to the database. If the
document already exists, it will be updated, otherwise it will be
@@ -554,7 +563,7 @@ class Document(BaseDocument, metaclass=TopLevelDocumentMetaclass):
if not getattr(ref, "_changed_fields", True):
continue
ref_id = "{},{}".format(ref.__class__.__name__, str(ref._data))
ref_id = f"{ref.__class__.__name__},{str(ref._data)}"
if ref and ref_id not in _refs:
_refs.append(ref_id)
kwargs["_refs"] = _refs

View File

@@ -1,6 +1,5 @@
from collections import defaultdict
__all__ = (
"NotRegistered",
"InvalidDocumentError",

View File

@@ -9,10 +9,10 @@ import uuid
from io import BytesIO
from operator import itemgetter
from bson import Binary, DBRef, ObjectId, SON
from bson.int64 import Int64
import gridfs
import pymongo
from bson import SON, Binary, DBRef, ObjectId
from bson.int64 import Int64
from pymongo import ReturnDocument
try:
@@ -22,7 +22,6 @@ except ImportError:
else:
import dateutil.parser
from mongoengine.base import (
BaseDocument,
BaseField,
@@ -36,7 +35,11 @@ from mongoengine.base.utils import LazyRegexCompiler
from mongoengine.common import _import_class
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
from mongoengine.document import Document, EmbeddedDocument
from mongoengine.errors import DoesNotExist, InvalidQueryError, ValidationError
from mongoengine.errors import (
DoesNotExist,
InvalidQueryError,
ValidationError,
)
from mongoengine.queryset import DO_NOTHING
from mongoengine.queryset.base import BaseQuerySet
from mongoengine.queryset.transform import STRING_OPERATORS
@@ -915,7 +918,7 @@ class ListField(ComplexBaseField):
"""A list field that wraps a standard field, allowing multiple instances
of the field to be used as a list in the database.
If using with ReferenceFields see: :ref:`one-to-many-with-listfields`
If using with ReferenceFields see: :ref:`many-to-many-with-listfields`
.. note::
Required means it cannot be empty - as the default for ListFields is []
@@ -1194,6 +1197,14 @@ class ReferenceField(BaseField):
self.document_type_obj = get_document(self.document_type_obj)
return self.document_type_obj
@staticmethod
def _lazy_load_ref(ref_cls, dbref):
dereferenced_son = ref_cls._get_db().dereference(dbref)
if dereferenced_son is None:
raise DoesNotExist(f"Trying to dereference unknown document {dbref}")
return ref_cls._from_son(dereferenced_son)
def __get__(self, instance, owner):
"""Descriptor to allow lazy dereferencing."""
if instance is None:
@@ -1201,20 +1212,17 @@ class ReferenceField(BaseField):
return self
# Get value from document instance if available
value = instance._data.get(self.name)
ref_value = instance._data.get(self.name)
auto_dereference = instance._fields[self.name]._auto_dereference
# Dereference DBRefs
if auto_dereference and isinstance(value, DBRef):
if hasattr(value, "cls"):
if auto_dereference and isinstance(ref_value, DBRef):
if hasattr(ref_value, "cls"):
# Dereference using the class type specified in the reference
cls = get_document(value.cls)
cls = get_document(ref_value.cls)
else:
cls = self.document_type
dereferenced = cls._get_db().dereference(value)
if dereferenced is None:
raise DoesNotExist("Trying to dereference unknown document %s" % value)
else:
instance._data[self.name] = cls._from_son(dereferenced)
instance._data[self.name] = self._lazy_load_ref(cls, ref_value)
return super().__get__(instance, owner)
@@ -1353,6 +1361,14 @@ class CachedReferenceField(BaseField):
self.document_type_obj = get_document(self.document_type_obj)
return self.document_type_obj
@staticmethod
def _lazy_load_ref(ref_cls, dbref):
dereferenced_son = ref_cls._get_db().dereference(dbref)
if dereferenced_son is None:
raise DoesNotExist(f"Trying to dereference unknown document {dbref}")
return ref_cls._from_son(dereferenced_son)
def __get__(self, instance, owner):
if instance is None:
# Document class being used rather than a document object
@@ -1364,11 +1380,7 @@ class CachedReferenceField(BaseField):
# Dereference DBRefs
if auto_dereference and isinstance(value, DBRef):
dereferenced = self.document_type._get_db().dereference(value)
if dereferenced is None:
raise DoesNotExist("Trying to dereference unknown document %s" % value)
else:
instance._data[self.name] = self.document_type._from_son(dereferenced)
instance._data[self.name] = self._lazy_load_ref(self.document_type, value)
return super().__get__(instance, owner)
@@ -1493,6 +1505,14 @@ class GenericReferenceField(BaseField):
value = value._class_name
super()._validate_choices(value)
@staticmethod
def _lazy_load_ref(ref_cls, dbref):
dereferenced_son = ref_cls._get_db().dereference(dbref)
if dereferenced_son is None:
raise DoesNotExist(f"Trying to dereference unknown document {dbref}")
return ref_cls._from_son(dereferenced_son)
def __get__(self, instance, owner):
if instance is None:
return self
@@ -1500,12 +1520,9 @@ class GenericReferenceField(BaseField):
value = instance._data.get(self.name)
auto_dereference = instance._fields[self.name]._auto_dereference
if auto_dereference and isinstance(value, (dict, SON)):
dereferenced = self.dereference(value)
if dereferenced is None:
raise DoesNotExist("Trying to dereference unknown document %s" % value)
else:
instance._data[self.name] = dereferenced
if auto_dereference and isinstance(value, dict):
doc_cls = get_document(value["_cls"])
instance._data[self.name] = self._lazy_load_ref(doc_cls, value["_ref"])
return super().__get__(instance, owner)
@@ -1524,14 +1541,6 @@ class GenericReferenceField(BaseField):
" saved to the database"
)
def dereference(self, value):
doc_cls = get_document(value["_cls"])
reference = value["_ref"]
doc = doc_cls._get_db().dereference(reference)
if doc is not None:
doc = doc_cls._from_son(doc)
return doc
def to_mongo(self, document):
if document is None:
return None

View File

@@ -3,7 +3,6 @@ Helper functions, constants, and types to aid with MongoDB version support
"""
from mongoengine.connection import get_connection
# Constant that can be used to compare the version retrieved with
# get_mongodb_version()
MONGODB_34 = (3, 4)

View File

@@ -2,13 +2,12 @@ import copy
import itertools
import re
import warnings
from collections.abc import Mapping
from bson import SON, json_util
from bson.code import Code
import pymongo
import pymongo.errors
from bson import SON, json_util
from bson.code import Code
from pymongo.collection import ReturnDocument
from pymongo.common import validate_read_preference
from pymongo.read_concern import ReadConcern
@@ -34,7 +33,6 @@ from mongoengine.queryset import transform
from mongoengine.queryset.field_list import QueryFieldList
from mongoengine.queryset.visitor import Q, QNode
__all__ = ("BaseQuerySet", "DO_NOTHING", "NULLIFY", "CASCADE", "DENY", "PULL")
# Delete rules

View File

@@ -1,4 +1,5 @@
from functools import partial
from mongoengine.queryset.queryset import QuerySet
__all__ = ("queryset_manager", "QuerySetManager")

View File

@@ -1,11 +1,11 @@
from mongoengine.errors import OperationError
from mongoengine.queryset.base import (
BaseQuerySet,
CASCADE,
DENY,
DO_NOTHING,
NULLIFY,
PULL,
BaseQuerySet,
)
__all__ = (

View File

@@ -1,8 +1,8 @@
from collections import defaultdict
from bson import ObjectId, SON
from bson.dbref import DBRef
import pymongo
from bson import SON, ObjectId
from bson.dbref import DBRef
from mongoengine.base import UPDATE_OPERATORS
from mongoengine.common import _import_class

View File

@@ -1,6 +1,5 @@
black
flake8
flake8-import-order
pre-commit
pytest
ipdb

View File

@@ -2,9 +2,17 @@
ignore=E501,F403,F405,I201,I202,W504,W605,W503,B007
exclude=build,dist,docs,venv,venv3,.tox,.eggs,tests
max-complexity=47
application-import-names=mongoengine,tests
[tool:pytest]
# Limits the discovery to tests directory
# avoids that it runs for instance the benchmark
testpaths = tests
[isort]
known_first_party = mongoengine,tests
default_section = THIRDPARTY
multi_line_output = 3
include_trailing_comma = True
combine_as_imports = True
line_length = 70
ensure_newline_before_comments = 1

View File

@@ -53,8 +53,8 @@ class PyTest(TestCommand):
def run_tests(self):
# import here, cause outside the eggs aren't loaded
from pkg_resources import _namespace_packages
import pytest
from pkg_resources import _namespace_packages
# Purge modules under test from sys.modules. The test loader will
# re-import them from the build location. Required when 2to3 is used

View File

@@ -177,7 +177,7 @@ class TestClassMethods(unittest.TestCase):
assert BlogPostWithCustomField.compare_indexes() == {"missing": [], "extra": []}
def test_compare_indexes_for_text_indexes(self):
""" Ensure that compare_indexes behaves correctly for text indexes """
"""Ensure that compare_indexes behaves correctly for text indexes"""
class Doc(Document):
a = StringField()

View File

@@ -1,6 +1,7 @@
import unittest
from bson import SON
from mongoengine import *
from mongoengine.pymongo_support import list_collection_names
from tests.utils import MongoDBTestCase

View File

@@ -1,13 +1,16 @@
import unittest
from datetime import datetime
import pytest
from pymongo.collation import Collation
from pymongo.errors import OperationFailure
import pytest
from mongoengine import *
from mongoengine.connection import get_db
from mongoengine.mongodb_support import MONGODB_42, get_mongodb_version
from mongoengine.mongodb_support import (
MONGODB_42,
get_mongodb_version,
)
class TestIndexes(unittest.TestCase):

View File

@@ -6,9 +6,9 @@ import weakref
from datetime import datetime
import bson
import pytest
from bson import DBRef, ObjectId
from pymongo.errors import DuplicateKeyError
import pytest
from mongoengine import *
from mongoengine import signals
@@ -23,7 +23,11 @@ from mongoengine.errors import (
NotUniqueError,
SaveConditionError,
)
from mongoengine.mongodb_support import MONGODB_34, MONGODB_36, get_mongodb_version
from mongoengine.mongodb_support import (
MONGODB_34,
MONGODB_36,
get_mongodb_version,
)
from mongoengine.pymongo_support import list_collection_names
from mongoengine.queryset import NULLIFY, Q
from tests import fixtures

View File

@@ -1,7 +1,7 @@
import unittest
import uuid
from datetime import datetime
from bson import ObjectId
from mongoengine import *

View File

@@ -1,7 +1,7 @@
import uuid
from bson import Binary
import pytest
from bson import Binary
from mongoengine import *
from tests.utils import MongoDBTestCase

View File

@@ -6,7 +6,6 @@ import re
import pytest
from mongoengine import *
from tests.utils import MongoDBTestCase

View File

@@ -9,7 +9,6 @@ except ImportError:
from mongoengine import *
from mongoengine import connection
from tests.utils import MongoDBTestCase

View File

@@ -1,10 +1,12 @@
from bson import InvalidDocument
import pytest
from bson import InvalidDocument
from mongoengine import *
from mongoengine.base import BaseDict
from mongoengine.mongodb_support import MONGODB_36, get_mongodb_version
from mongoengine.mongodb_support import (
MONGODB_36,
get_mongodb_version,
)
from tests.utils import MongoDBTestCase, get_as_pymongo

View File

@@ -12,7 +12,6 @@ from mongoengine import (
StringField,
ValidationError,
)
from tests.utils import MongoDBTestCase

View File

@@ -1,7 +1,7 @@
from enum import Enum
from bson import InvalidDocument
import pytest
from bson import InvalidDocument
from mongoengine import Document, EnumField, ValidationError
from tests.utils import MongoDBTestCase, get_as_pymongo

View File

@@ -1,8 +1,8 @@
import datetime
import unittest
from bson import DBRef, ObjectId, SON
import pytest
from bson import SON, DBRef, ObjectId
from mongoengine import (
BooleanField,
@@ -34,9 +34,12 @@ from mongoengine import (
StringField,
ValidationError,
)
from mongoengine.base import BaseField, EmbeddedDocumentList, _document_registry
from mongoengine.base import (
BaseField,
EmbeddedDocumentList,
_document_registry,
)
from mongoengine.errors import DeprecatedError
from tests.utils import MongoDBTestCase

View File

@@ -308,7 +308,7 @@ class TestFileField(MongoDBTestCase):
assert test_file.the_file not in [{"test": 1}]
def test_file_disk_space(self):
""" Test disk space usage when we delete/replace a file """
"""Test disk space usage when we delete/replace a file"""
class TestFile(Document):
the_file = FileField()

View File

@@ -1,7 +1,6 @@
import pytest
from mongoengine import *
from tests.utils import MongoDBTestCase

View File

@@ -1,7 +1,6 @@
import pytest
from mongoengine import *
from tests.utils import MongoDBTestCase

View File

@@ -1,10 +1,9 @@
from bson import DBRef, ObjectId
import pytest
from bson import DBRef, ObjectId
from mongoengine import *
from mongoengine.base import LazyReference
from mongoengine.context_managers import query_counter
from tests.utils import MongoDBTestCase
@@ -375,6 +374,26 @@ class TestLazyReferenceField(MongoDBTestCase):
assert isinstance(ref.author, LazyReference)
assert isinstance(ref.author.id, ObjectId)
def test_lazy_reference_in_list_with_changed_element(self):
class Animal(Document):
name = StringField()
tag = StringField()
class Ocurrence(Document):
in_list = ListField(LazyReferenceField(Animal))
Animal.drop_collection()
Ocurrence.drop_collection()
animal1 = Animal(name="doggo").save()
animal1.tag = "blue"
occ = Ocurrence(in_list=[animal1]).save()
animal1.save()
assert isinstance(occ.in_list[0], LazyReference)
assert occ.in_list[0].pk == animal1.pk
class TestGenericLazyReferenceField(MongoDBTestCase):
def test_generic_lazy_reference_simple(self):

View File

@@ -1,9 +1,8 @@
from bson.int64 import Int64
import pytest
from bson.int64 import Int64
from mongoengine import *
from mongoengine.connection import get_db
from tests.utils import MongoDBTestCase, get_as_pymongo

View File

@@ -1,5 +1,5 @@
from bson import DBRef, SON
import pytest
from bson import SON, DBRef
from mongoengine import *
from tests.utils import MongoDBTestCase

View File

@@ -1,5 +1,4 @@
from mongoengine import *
from tests.utils import MongoDBTestCase

View File

@@ -2,7 +2,6 @@ import datetime
import unittest
from mongoengine import *
from tests.utils import MongoDBTestCase

View File

@@ -1,6 +1,12 @@
import unittest
from mongoengine import Document, IntField, ListField, StringField, connect
from mongoengine import (
Document,
IntField,
ListField,
StringField,
connect,
)
class Doc(Document):

View File

@@ -3,17 +3,20 @@ import unittest
import uuid
from decimal import Decimal
from bson import DBRef, ObjectId
import pymongo
import pytest
from bson import DBRef, ObjectId
from pymongo.read_preferences import ReadPreference
from pymongo.results import UpdateResult
import pytest
from mongoengine import *
from mongoengine.connection import get_db
from mongoengine.context_managers import query_counter, switch_db
from mongoengine.errors import InvalidQueryError
from mongoengine.mongodb_support import MONGODB_36, get_mongodb_version
from mongoengine.mongodb_support import (
MONGODB_36,
get_mongodb_version,
)
from mongoengine.queryset import (
DoesNotExist,
MultipleObjectsReturned,

View File

@@ -1,7 +1,7 @@
import unittest
from bson.son import SON
import pytest
from bson.son import SON
from mongoengine import *
from mongoengine.queryset import Q, transform

View File

@@ -2,8 +2,8 @@ import datetime
import re
import unittest
from bson import ObjectId
import pytest
from bson import ObjectId
from mongoengine import *
from mongoengine.errors import InvalidQueryError

View File

@@ -1,11 +1,11 @@
import datetime
import unittest
from bson.tz_util import utc
import pymongo
import pytest
from bson.tz_util import utc
from pymongo import MongoClient, ReadPreference
from pymongo.errors import InvalidName, OperationFailure
import pytest
import mongoengine.connection
from mongoengine import (
@@ -17,8 +17,8 @@ from mongoengine import (
register_connection,
)
from mongoengine.connection import (
ConnectionFailure,
DEFAULT_DATABASE_NAME,
ConnectionFailure,
disconnect,
get_connection,
get_db,

View File

@@ -3,15 +3,9 @@ import unittest
import pytest
import mongoengine.connection
from mongoengine import (
Document,
StringField,
connect,
disconnect_all,
)
from mongoengine import Document, StringField, connect, disconnect_all
from mongoengine.connection import get_connection
try:
import mongomock

View File

@@ -3,7 +3,11 @@ import unittest
import pytest
from mongoengine import Document
from mongoengine.base.datastructures import BaseDict, BaseList, StrictDict
from mongoengine.base.datastructures import (
BaseDict,
BaseList,
StrictDict,
)
class DocumentStub:

View File

@@ -5,7 +5,6 @@ from pymongo import MongoClient, ReadPreference
import mongoengine
from mongoengine.connection import ConnectionFailure
CONN_CLASS = MongoClient
READ_PREF = ReadPreference.SECONDARY

View File

@@ -249,7 +249,7 @@ class TestSignal(unittest.TestCase):
assert self.pre_signals == post_signals
def test_model_signals(self):
""" Model saves should throw some signals. """
"""Model saves should throw some signals."""
def create_author():
self.Author(name="Bill Shakespeare")
@@ -340,7 +340,7 @@ class TestSignal(unittest.TestCase):
]
def test_signal_kwargs(self):
""" Make sure signal_kwargs is passed to signals calls. """
"""Make sure signal_kwargs is passed to signals calls."""
def live_and_let_die():
a = self.Author(name="Bill Shakespeare")
@@ -385,7 +385,7 @@ class TestSignal(unittest.TestCase):
]
def test_queryset_delete_signals(self):
""" Queryset delete should throw some signals. """
"""Queryset delete should throw some signals."""
self.Another(name="Bill Shakespeare").save()
assert self.get_signal_output(self.Another.objects.delete) == [
@@ -396,7 +396,7 @@ class TestSignal(unittest.TestCase):
]
def test_signals_with_explicit_doc_ids(self):
""" Model saves must have a created flag the first time."""
"""Model saves must have a created flag the first time."""
ei = self.ExplicitId(id=123)
# post save must received the created flag, even if there's already
# an object id present

View File

@@ -7,7 +7,6 @@ from mongoengine import connect
from mongoengine.connection import disconnect_all, get_db
from mongoengine.mongodb_support import get_mongodb_version
MONGO_TEST_DB = "mongoenginetest" # standard name for the test database