Skip to content

Commit cfb4265

Browse files
authored
Merge pull request #2788 from bagerard/fix_no_dereference_swallowing_errors
Various fix for no_dereference context manager
2 parents bfc42d0 + a2bb72b commit cfb4265

File tree

6 files changed

+104
-107
lines changed

6 files changed

+104
-107
lines changed

docs/changelog.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ Development
1212
- Fix validate() not being called when inheritance is used in EmbeddedDocument and validate is overriden #2784
1313
- Add support for readPreferenceTags in connection parameters #2644
1414
- Use estimated_documents_count OR documents_count when count is called, based on the query #2529
15+
- Fix no_dereference context manager which wasn't turning off auto-dereferencing correctly in some cases #2788
16+
- BREAKING CHANGE: no_dereference context manager no longer returns the class in __enter__ #2788
17+
as it was useless and making it look like it was returning a different class although it was the same.
18+
Thus, it must be called like `with no_dereference(User):` and no longer `with no_dereference(User) as ...:`
1519

1620
Changes in 0.27.0
1721
=================

docs/guide/querying.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,7 @@ data. To turn off dereferencing of the results of a query use
522522
You can also turn off all dereferencing for a fixed period by using the
523523
:class:`~mongoengine.context_managers.no_dereference` context manager::
524524

525-
with no_dereference(Post) as Post:
525+
with no_dereference(Post):
526526
post = Post.objects.first()
527527
assert(isinstance(post.author, DBRef))
528528

mongoengine/context_managers.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import threading
12
from contextlib import contextmanager
23

34
from pymongo.read_concern import ReadConcern
@@ -18,6 +19,25 @@
1819
)
1920

2021

22+
thread_locals = threading.local()
23+
thread_locals.no_dereferencing_class = {}
24+
25+
26+
def no_dereferencing_active_for_class(cls):
27+
return cls in thread_locals.no_dereferencing_class
28+
29+
30+
def _register_no_dereferencing_for_class(cls):
31+
thread_locals.no_dereferencing_class.setdefault(cls, 0)
32+
thread_locals.no_dereferencing_class[cls] += 1
33+
34+
35+
def _unregister_no_dereferencing_for_class(cls):
36+
thread_locals.no_dereferencing_class[cls] -= 1
37+
if thread_locals.no_dereferencing_class[cls] == 0:
38+
thread_locals.no_dereferencing_class.pop(cls)
39+
40+
2141
class switch_db:
2242
"""switch_db alias context manager.
2343
@@ -107,7 +127,7 @@ class no_dereference:
107127
Turns off all dereferencing in Documents for the duration of the context
108128
manager::
109129
110-
with no_dereference(Group) as Group:
130+
with no_dereference(Group):
111131
Group.objects.find()
112132
"""
113133

@@ -130,15 +150,17 @@ def __init__(self, cls):
130150

131151
def __enter__(self):
132152
"""Change the objects default and _auto_dereference values."""
153+
_register_no_dereferencing_for_class(self.cls)
154+
133155
for field in self.deref_fields:
134156
self.cls._fields[field]._auto_dereference = False
135-
return self.cls
136157

137158
def __exit__(self, t, value, traceback):
138159
"""Reset the default and _auto_dereference values."""
160+
_unregister_no_dereferencing_for_class(self.cls)
161+
139162
for field in self.deref_fields:
140163
self.cls._fields[field]._auto_dereference = True
141-
return self.cls
142164

143165

144166
class no_sub_classes:

mongoengine/queryset/base.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from mongoengine.common import _import_class
1818
from mongoengine.connection import get_db
1919
from mongoengine.context_managers import (
20+
no_dereferencing_active_for_class,
2021
set_read_write_concern,
2122
set_write_concern,
2223
switch_db,
@@ -51,9 +52,6 @@ class BaseQuerySet:
5152
providing :class:`~mongoengine.Document` objects as the results.
5253
"""
5354

54-
__dereference = False
55-
_auto_dereference = True
56-
5755
def __init__(self, document, collection):
5856
self._document = document
5957
self._collection_obj = collection
@@ -74,6 +72,9 @@ def __init__(self, document, collection):
7472
self._as_pymongo = False
7573
self._search_text = None
7674

75+
self.__dereference = False
76+
self.__auto_dereference = True
77+
7778
# If inheritance is allowed, only return instances and instances of
7879
# subclasses of the class being used
7980
if document._meta.get("allow_inheritance") is True:
@@ -795,7 +796,7 @@ def clone(self):
795796
return self._clone_into(self.__class__(self._document, self._collection_obj))
796797

797798
def _clone_into(self, new_qs):
798-
"""Copy all of the relevant properties of this queryset to
799+
"""Copy all the relevant properties of this queryset to
799800
a new queryset (which has to be an instance of
800801
:class:`~mongoengine.queryset.base.BaseQuerySet`).
801802
"""
@@ -825,7 +826,6 @@ def _clone_into(self, new_qs):
825826
"_empty",
826827
"_hint",
827828
"_collation",
828-
"_auto_dereference",
829829
"_search_text",
830830
"_max_time_ms",
831831
"_comment",
@@ -836,6 +836,8 @@ def _clone_into(self, new_qs):
836836
val = getattr(self, prop)
837837
setattr(new_qs, prop, copy.copy(val))
838838

839+
new_qs.__auto_dereference = self._BaseQuerySet__auto_dereference
840+
839841
if self._cursor_obj:
840842
new_qs._cursor_obj = self._cursor_obj.clone()
841843

@@ -1741,10 +1743,15 @@ def _dereference(self):
17411743
self.__dereference = _import_class("DeReference")()
17421744
return self.__dereference
17431745

1746+
@property
1747+
def _auto_dereference(self):
1748+
should_deref = not no_dereferencing_active_for_class(self._document)
1749+
return should_deref and self.__auto_dereference
1750+
17441751
def no_dereference(self):
17451752
"""Turn off any dereferencing for the results of this queryset."""
17461753
queryset = self.clone()
1747-
queryset._auto_dereference = False
1754+
queryset.__auto_dereference = False
17481755
return queryset
17491756

17501757
# Helper Functions

tests/document/test_indexes.py

Lines changed: 11 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from mongoengine.connection import get_db
1010
from mongoengine.mongodb_support import (
1111
MONGODB_42,
12-
MONGODB_70,
1312
get_mongodb_version,
1413
)
1514
from mongoengine.pymongo_support import PYMONGO_VERSION
@@ -451,89 +450,29 @@ class Test(Document):
451450
# the documents returned might have more keys in that here.
452451
query_plan = Test.objects(id=obj.id).exclude("a").explain()
453452
assert (
454-
query_plan.get("queryPlanner")
455-
.get("winningPlan")
456-
.get("inputStage")
457-
.get("stage")
458-
== "IDHACK"
453+
query_plan["queryPlanner"]["winningPlan"]["inputStage"]["stage"] == "IDHACK"
459454
)
460455

461456
query_plan = Test.objects(id=obj.id).only("id").explain()
462457
assert (
463-
query_plan.get("queryPlanner")
464-
.get("winningPlan")
465-
.get("inputStage")
466-
.get("stage")
467-
== "IDHACK"
458+
query_plan["queryPlanner"]["winningPlan"]["inputStage"]["stage"] == "IDHACK"
468459
)
469460

470461
mongo_db = get_mongodb_version()
471462
query_plan = Test.objects(a=1).only("a").exclude("id").explain()
472-
if mongo_db < MONGODB_70:
473-
assert (
474-
query_plan.get("queryPlanner")
475-
.get("winningPlan")
476-
.get("inputStage")
477-
.get("stage")
478-
== "IXSCAN"
479-
)
480-
else:
481-
assert (
482-
query_plan.get("queryPlanner")
483-
.get("winningPlan")
484-
.get("queryPlan")
485-
.get("inputStage")
486-
.get("stage")
487-
== "IXSCAN"
488-
)
463+
assert (
464+
query_plan["queryPlanner"]["winningPlan"]["inputStage"]["stage"] == "IXSCAN"
465+
)
489466

490467
PROJECTION_STR = "PROJECTION" if mongo_db < MONGODB_42 else "PROJECTION_COVERED"
491-
if mongo_db < MONGODB_70:
492-
assert (
493-
query_plan.get("queryPlanner").get("winningPlan").get("stage")
494-
== PROJECTION_STR
495-
)
496-
else:
497-
assert (
498-
query_plan.get("queryPlanner")
499-
.get("winningPlan")
500-
.get("queryPlan")
501-
.get("stage")
502-
== PROJECTION_STR
503-
)
468+
assert query_plan["queryPlanner"]["winningPlan"]["stage"] == PROJECTION_STR
504469

505470
query_plan = Test.objects(a=1).explain()
506-
if mongo_db < MONGODB_70:
507-
assert (
508-
query_plan.get("queryPlanner")
509-
.get("winningPlan")
510-
.get("inputStage")
511-
.get("stage")
512-
== "IXSCAN"
513-
)
514-
else:
515-
assert (
516-
query_plan.get("queryPlanner")
517-
.get("winningPlan")
518-
.get("queryPlan")
519-
.get("inputStage")
520-
.get("stage")
521-
== "IXSCAN"
522-
)
523-
524-
if mongo_db < MONGODB_70:
525-
assert (
526-
query_plan.get("queryPlanner").get("winningPlan").get("stage")
527-
== "FETCH"
528-
)
529-
else:
530-
assert (
531-
query_plan.get("queryPlanner")
532-
.get("winningPlan")
533-
.get("queryPlan")
534-
.get("stage")
535-
== "FETCH"
536-
)
471+
assert (
472+
query_plan["queryPlanner"]["winningPlan"]["inputStage"]["stage"] == "IXSCAN"
473+
)
474+
475+
assert query_plan.get("queryPlanner").get("winningPlan").get("stage") == "FETCH"
537476

538477
def test_index_on_id(self):
539478
class BlogPost(Document):

0 commit comments

Comments
 (0)