autonomous-app 0.3.0__py3-none-any.whl → 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- autonomous/__init__.py +1 -1
- autonomous/ai/audioagent.py +1 -1
- autonomous/ai/imageagent.py +1 -1
- autonomous/ai/jsonagent.py +1 -1
- autonomous/ai/models/openai.py +81 -53
- autonomous/ai/oaiagent.py +1 -14
- autonomous/ai/textagent.py +1 -1
- autonomous/auth/autoauth.py +10 -10
- autonomous/auth/user.py +17 -2
- autonomous/db/__init__.py +42 -0
- autonomous/db/base/__init__.py +33 -0
- autonomous/db/base/common.py +62 -0
- autonomous/db/base/datastructures.py +476 -0
- autonomous/db/base/document.py +1230 -0
- autonomous/db/base/fields.py +767 -0
- autonomous/db/base/metaclasses.py +468 -0
- autonomous/db/base/utils.py +22 -0
- autonomous/db/common.py +79 -0
- autonomous/db/connection.py +472 -0
- autonomous/db/context_managers.py +313 -0
- autonomous/db/dereference.py +291 -0
- autonomous/db/document.py +1141 -0
- autonomous/db/errors.py +165 -0
- autonomous/db/fields.py +2732 -0
- autonomous/db/mongodb_support.py +24 -0
- autonomous/db/pymongo_support.py +80 -0
- autonomous/db/queryset/__init__.py +28 -0
- autonomous/db/queryset/base.py +2033 -0
- autonomous/db/queryset/field_list.py +88 -0
- autonomous/db/queryset/manager.py +58 -0
- autonomous/db/queryset/queryset.py +189 -0
- autonomous/db/queryset/transform.py +527 -0
- autonomous/db/queryset/visitor.py +189 -0
- autonomous/db/signals.py +59 -0
- autonomous/logger.py +3 -0
- autonomous/model/autoattr.py +56 -41
- autonomous/model/automodel.py +88 -34
- {autonomous_app-0.3.0.dist-info → autonomous_app-0.3.1.dist-info}/METADATA +2 -2
- autonomous_app-0.3.1.dist-info/RECORD +60 -0
- {autonomous_app-0.3.0.dist-info → autonomous_app-0.3.1.dist-info}/WHEEL +1 -1
- autonomous_app-0.3.0.dist-info/RECORD +0 -35
- {autonomous_app-0.3.0.dist-info → autonomous_app-0.3.1.dist-info}/LICENSE +0 -0
- {autonomous_app-0.3.0.dist-info → autonomous_app-0.3.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import threading
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
|
|
5
|
+
from pymongo.read_concern import ReadConcern
|
|
6
|
+
from pymongo.write_concern import WriteConcern
|
|
7
|
+
|
|
8
|
+
from autonomous.db.base.fields import _no_dereference_for_fields
|
|
9
|
+
from autonomous.db.common import _import_class
|
|
10
|
+
from autonomous.db.connection import DEFAULT_CONNECTION_NAME, get_db
|
|
11
|
+
from autonomous.db.pymongo_support import count_documents
|
|
12
|
+
|
|
13
|
+
__all__ = (
|
|
14
|
+
"switch_db",
|
|
15
|
+
"switch_collection",
|
|
16
|
+
"no_dereference",
|
|
17
|
+
"no_sub_classes",
|
|
18
|
+
"query_counter",
|
|
19
|
+
"set_write_concern",
|
|
20
|
+
"set_read_write_concern",
|
|
21
|
+
"no_dereferencing_active_for_class",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MyThreadLocals(threading.local):
|
|
26
|
+
def __init__(self):
|
|
27
|
+
# {DocCls: count} keeping track of classes with an active no_dereference context
|
|
28
|
+
self.no_dereferencing_class = {}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
thread_locals = MyThreadLocals()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def no_dereferencing_active_for_class(cls):
|
|
35
|
+
return cls in thread_locals.no_dereferencing_class
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _register_no_dereferencing_for_class(cls):
|
|
39
|
+
thread_locals.no_dereferencing_class.setdefault(cls, 0)
|
|
40
|
+
thread_locals.no_dereferencing_class[cls] += 1
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _unregister_no_dereferencing_for_class(cls):
|
|
44
|
+
thread_locals.no_dereferencing_class[cls] -= 1
|
|
45
|
+
if thread_locals.no_dereferencing_class[cls] == 0:
|
|
46
|
+
thread_locals.no_dereferencing_class.pop(cls)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class switch_db:
|
|
50
|
+
"""switch_db alias context manager.
|
|
51
|
+
|
|
52
|
+
Example ::
|
|
53
|
+
|
|
54
|
+
# Register connections
|
|
55
|
+
register_connection('default', 'mongoenginetest')
|
|
56
|
+
register_connection('testdb-1', 'mongoenginetest2')
|
|
57
|
+
|
|
58
|
+
class Group(Document):
|
|
59
|
+
name = StringField()
|
|
60
|
+
|
|
61
|
+
Group(name='test').save() # Saves in the default db
|
|
62
|
+
|
|
63
|
+
with switch_db(Group, 'testdb-1') as Group:
|
|
64
|
+
Group(name='hello testdb!').save() # Saves in testdb-1
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(self, cls, db_alias):
|
|
68
|
+
"""Construct the switch_db context manager
|
|
69
|
+
|
|
70
|
+
:param cls: the class to change the registered db
|
|
71
|
+
:param db_alias: the name of the specific database to use
|
|
72
|
+
"""
|
|
73
|
+
self.cls = cls
|
|
74
|
+
self.collection = cls._get_collection()
|
|
75
|
+
self.db_alias = db_alias
|
|
76
|
+
self.ori_db_alias = cls._meta.get("db_alias", DEFAULT_CONNECTION_NAME)
|
|
77
|
+
|
|
78
|
+
def __enter__(self):
|
|
79
|
+
"""Change the db_alias and clear the cached collection."""
|
|
80
|
+
self.cls._meta["db_alias"] = self.db_alias
|
|
81
|
+
self.cls._collection = None
|
|
82
|
+
return self.cls
|
|
83
|
+
|
|
84
|
+
def __exit__(self, t, value, traceback):
|
|
85
|
+
"""Reset the db_alias and collection."""
|
|
86
|
+
self.cls._meta["db_alias"] = self.ori_db_alias
|
|
87
|
+
self.cls._collection = self.collection
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class switch_collection:
|
|
91
|
+
"""switch_collection alias context manager.
|
|
92
|
+
|
|
93
|
+
Example ::
|
|
94
|
+
|
|
95
|
+
class Group(Document):
|
|
96
|
+
name = StringField()
|
|
97
|
+
|
|
98
|
+
Group(name='test').save() # Saves in the default db
|
|
99
|
+
|
|
100
|
+
with switch_collection(Group, 'group1') as Group:
|
|
101
|
+
Group(name='hello testdb!').save() # Saves in group1 collection
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(self, cls, collection_name):
|
|
105
|
+
"""Construct the switch_collection context manager.
|
|
106
|
+
|
|
107
|
+
:param cls: the class to change the registered db
|
|
108
|
+
:param collection_name: the name of the collection to use
|
|
109
|
+
"""
|
|
110
|
+
self.cls = cls
|
|
111
|
+
self.ori_collection = cls._get_collection()
|
|
112
|
+
self.ori_get_collection_name = cls._get_collection_name
|
|
113
|
+
self.collection_name = collection_name
|
|
114
|
+
|
|
115
|
+
def __enter__(self):
|
|
116
|
+
"""Change the _get_collection_name and clear the cached collection."""
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def _get_collection_name(cls):
|
|
120
|
+
return self.collection_name
|
|
121
|
+
|
|
122
|
+
self.cls._get_collection_name = _get_collection_name
|
|
123
|
+
self.cls._collection = None
|
|
124
|
+
return self.cls
|
|
125
|
+
|
|
126
|
+
def __exit__(self, t, value, traceback):
|
|
127
|
+
"""Reset the collection."""
|
|
128
|
+
self.cls._collection = self.ori_collection
|
|
129
|
+
self.cls._get_collection_name = self.ori_get_collection_name
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@contextlib.contextmanager
|
|
133
|
+
def no_dereference(cls):
|
|
134
|
+
"""no_dereference context manager.
|
|
135
|
+
|
|
136
|
+
Turns off all dereferencing in Documents for the duration of the context
|
|
137
|
+
manager::
|
|
138
|
+
|
|
139
|
+
with no_dereference(Group):
|
|
140
|
+
Group.objects()
|
|
141
|
+
"""
|
|
142
|
+
try:
|
|
143
|
+
cls = cls
|
|
144
|
+
|
|
145
|
+
ReferenceField = _import_class("ReferenceField")
|
|
146
|
+
GenericReferenceField = _import_class("GenericReferenceField")
|
|
147
|
+
ComplexBaseField = _import_class("ComplexBaseField")
|
|
148
|
+
|
|
149
|
+
deref_fields = [
|
|
150
|
+
field
|
|
151
|
+
for name, field in cls._fields.items()
|
|
152
|
+
if isinstance(
|
|
153
|
+
field, (ReferenceField, GenericReferenceField, ComplexBaseField)
|
|
154
|
+
)
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
_register_no_dereferencing_for_class(cls)
|
|
158
|
+
|
|
159
|
+
with _no_dereference_for_fields(*deref_fields):
|
|
160
|
+
yield None
|
|
161
|
+
finally:
|
|
162
|
+
_unregister_no_dereferencing_for_class(cls)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class no_sub_classes:
|
|
166
|
+
"""no_sub_classes context manager.
|
|
167
|
+
|
|
168
|
+
Only returns instances of this class and no sub (inherited) classes::
|
|
169
|
+
|
|
170
|
+
with no_sub_classes(Group) as Group:
|
|
171
|
+
Group.objects.find()
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
def __init__(self, cls):
|
|
175
|
+
"""Construct the no_sub_classes context manager.
|
|
176
|
+
|
|
177
|
+
:param cls: the class to turn querying subclasses on
|
|
178
|
+
"""
|
|
179
|
+
self.cls = cls
|
|
180
|
+
self.cls_initial_subclasses = None
|
|
181
|
+
|
|
182
|
+
def __enter__(self):
|
|
183
|
+
"""Change the objects default and _auto_dereference values."""
|
|
184
|
+
self.cls_initial_subclasses = self.cls._subclasses
|
|
185
|
+
self.cls._subclasses = (self.cls._class_name,)
|
|
186
|
+
return self.cls
|
|
187
|
+
|
|
188
|
+
def __exit__(self, t, value, traceback):
|
|
189
|
+
"""Reset the default and _auto_dereference values."""
|
|
190
|
+
self.cls._subclasses = self.cls_initial_subclasses
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class query_counter:
|
|
194
|
+
"""Query_counter context manager to get the number of queries.
|
|
195
|
+
This works by updating the `profiling_level` of the database so that all queries get logged,
|
|
196
|
+
resetting the db.system.profile collection at the beginning of the context and counting the new entries.
|
|
197
|
+
|
|
198
|
+
This was designed for debugging purpose. In fact it is a global counter so queries issued by other threads/processes
|
|
199
|
+
can interfere with it
|
|
200
|
+
|
|
201
|
+
Usage:
|
|
202
|
+
|
|
203
|
+
.. code-block:: python
|
|
204
|
+
|
|
205
|
+
class User(Document):
|
|
206
|
+
name = StringField()
|
|
207
|
+
|
|
208
|
+
with query_counter() as q:
|
|
209
|
+
user = User(name='Bob')
|
|
210
|
+
assert q == 0 # no query fired yet
|
|
211
|
+
user.save()
|
|
212
|
+
assert q == 1 # 1 query was fired, an 'insert'
|
|
213
|
+
user_bis = User.objects().first()
|
|
214
|
+
assert q == 2 # a 2nd query was fired, a 'find_one'
|
|
215
|
+
|
|
216
|
+
Be aware that:
|
|
217
|
+
|
|
218
|
+
- Iterating over large amount of documents (>101) makes pymongo issue `getmore` queries to fetch the next batch of documents (https://www.mongodb.com/docs/manual/tutorial/iterate-a-cursor/#cursor-batches)
|
|
219
|
+
- Some queries are ignored by default by the counter (killcursors, db.system.indexes)
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
def __init__(self, alias=DEFAULT_CONNECTION_NAME):
|
|
223
|
+
self.db = get_db(alias=alias)
|
|
224
|
+
self.initial_profiling_level = None
|
|
225
|
+
self._ctx_query_counter = 0 # number of queries issued by the context
|
|
226
|
+
|
|
227
|
+
self._ignored_query = {
|
|
228
|
+
"ns": {"$ne": "%s.system.indexes" % self.db.name},
|
|
229
|
+
"op": {"$ne": "killcursors"}, # MONGODB < 3.2
|
|
230
|
+
"command.killCursors": {"$exists": False}, # MONGODB >= 3.2
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
def _turn_on_profiling(self):
|
|
234
|
+
profile_update_res = self.db.command({"profile": 0})
|
|
235
|
+
self.initial_profiling_level = profile_update_res["was"]
|
|
236
|
+
|
|
237
|
+
self.db.system.profile.drop()
|
|
238
|
+
self.db.command({"profile": 2})
|
|
239
|
+
|
|
240
|
+
def _resets_profiling(self):
|
|
241
|
+
self.db.command({"profile": self.initial_profiling_level})
|
|
242
|
+
|
|
243
|
+
def __enter__(self):
|
|
244
|
+
self._turn_on_profiling()
|
|
245
|
+
return self
|
|
246
|
+
|
|
247
|
+
def __exit__(self, t, value, traceback):
|
|
248
|
+
self._resets_profiling()
|
|
249
|
+
|
|
250
|
+
def __eq__(self, value):
|
|
251
|
+
counter = self._get_count()
|
|
252
|
+
return value == counter
|
|
253
|
+
|
|
254
|
+
def __ne__(self, value):
|
|
255
|
+
return not self.__eq__(value)
|
|
256
|
+
|
|
257
|
+
def __lt__(self, value):
|
|
258
|
+
return self._get_count() < value
|
|
259
|
+
|
|
260
|
+
def __le__(self, value):
|
|
261
|
+
return self._get_count() <= value
|
|
262
|
+
|
|
263
|
+
def __gt__(self, value):
|
|
264
|
+
return self._get_count() > value
|
|
265
|
+
|
|
266
|
+
def __ge__(self, value):
|
|
267
|
+
return self._get_count() >= value
|
|
268
|
+
|
|
269
|
+
def __int__(self):
|
|
270
|
+
return self._get_count()
|
|
271
|
+
|
|
272
|
+
def __repr__(self):
|
|
273
|
+
"""repr query_counter as the number of queries."""
|
|
274
|
+
return "%s" % self._get_count()
|
|
275
|
+
|
|
276
|
+
def _get_count(self):
|
|
277
|
+
"""Get the number of queries by counting the current number of entries in db.system.profile
|
|
278
|
+
and substracting the queries issued by this context. In fact everytime this is called, 1 query is
|
|
279
|
+
issued so we need to balance that
|
|
280
|
+
"""
|
|
281
|
+
count = (
|
|
282
|
+
count_documents(self.db.system.profile, self._ignored_query)
|
|
283
|
+
- self._ctx_query_counter
|
|
284
|
+
)
|
|
285
|
+
self._ctx_query_counter += (
|
|
286
|
+
1 # Account for the query we just issued to gather the information
|
|
287
|
+
)
|
|
288
|
+
return count
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@contextmanager
|
|
292
|
+
def set_write_concern(collection, write_concerns):
|
|
293
|
+
combined_concerns = dict(collection.write_concern.document.items())
|
|
294
|
+
combined_concerns.update(write_concerns)
|
|
295
|
+
yield collection.with_options(write_concern=WriteConcern(**combined_concerns))
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
@contextmanager
|
|
299
|
+
def set_read_write_concern(collection, write_concerns, read_concerns):
|
|
300
|
+
combined_write_concerns = dict(collection.write_concern.document.items())
|
|
301
|
+
|
|
302
|
+
if write_concerns is not None:
|
|
303
|
+
combined_write_concerns.update(write_concerns)
|
|
304
|
+
|
|
305
|
+
combined_read_concerns = dict(collection.read_concern.document.items())
|
|
306
|
+
|
|
307
|
+
if read_concerns is not None:
|
|
308
|
+
combined_read_concerns.update(read_concerns)
|
|
309
|
+
|
|
310
|
+
yield collection.with_options(
|
|
311
|
+
write_concern=WriteConcern(**combined_write_concerns),
|
|
312
|
+
read_concern=ReadConcern(**combined_read_concerns),
|
|
313
|
+
)
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
from bson import SON, DBRef
|
|
2
|
+
|
|
3
|
+
from autonomous import log
|
|
4
|
+
from autonomous.db.base import (
|
|
5
|
+
BaseDict,
|
|
6
|
+
BaseList,
|
|
7
|
+
EmbeddedDocumentList,
|
|
8
|
+
TopLevelDocumentMetaclass,
|
|
9
|
+
get_document,
|
|
10
|
+
)
|
|
11
|
+
from autonomous.db.base.datastructures import LazyReference
|
|
12
|
+
from autonomous.db.connection import get_db
|
|
13
|
+
from autonomous.db.document import Document, EmbeddedDocument
|
|
14
|
+
from autonomous.db.fields import (
|
|
15
|
+
DictField,
|
|
16
|
+
GenericReferenceField,
|
|
17
|
+
ListField,
|
|
18
|
+
MapField,
|
|
19
|
+
ReferenceField,
|
|
20
|
+
)
|
|
21
|
+
from autonomous.db.queryset import QuerySet
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DeReference:
|
|
25
|
+
def __call__(self, items, max_depth=1, instance=None, name=None):
|
|
26
|
+
"""
|
|
27
|
+
Cheaply dereferences the items to a set depth.
|
|
28
|
+
Also handles the conversion of complex data types.
|
|
29
|
+
|
|
30
|
+
:param items: The iterable (dict, list, queryset) to be dereferenced.
|
|
31
|
+
:param max_depth: The maximum depth to recurse to
|
|
32
|
+
:param instance: The owning instance used for tracking changes by
|
|
33
|
+
:class:`~autonomous.db.base.ComplexBaseField`
|
|
34
|
+
:param name: The name of the field, used for tracking changes by
|
|
35
|
+
:class:`~autonomous.db.base.ComplexBaseField`
|
|
36
|
+
:param get: A boolean determining if being called by __get__
|
|
37
|
+
"""
|
|
38
|
+
if items is None or isinstance(items, str):
|
|
39
|
+
return items
|
|
40
|
+
|
|
41
|
+
# cheapest way to convert a queryset to a list
|
|
42
|
+
# list(queryset) uses a count() query to determine length
|
|
43
|
+
if isinstance(items, QuerySet):
|
|
44
|
+
items = [i for i in items]
|
|
45
|
+
|
|
46
|
+
self.max_depth = max_depth
|
|
47
|
+
doc_type = None
|
|
48
|
+
|
|
49
|
+
if instance and isinstance(
|
|
50
|
+
instance, (Document, EmbeddedDocument, TopLevelDocumentMetaclass)
|
|
51
|
+
):
|
|
52
|
+
doc_type = instance._fields.get(name)
|
|
53
|
+
while hasattr(doc_type, "field"):
|
|
54
|
+
doc_type = doc_type.field
|
|
55
|
+
if isinstance(doc_type, (ReferenceField)):
|
|
56
|
+
field = doc_type
|
|
57
|
+
doc_type = doc_type.document_type
|
|
58
|
+
is_list = not hasattr(items, "items")
|
|
59
|
+
|
|
60
|
+
if is_list and all(i.__class__ == doc_type for i in items):
|
|
61
|
+
return items
|
|
62
|
+
elif not is_list and all(
|
|
63
|
+
i.__class__ == doc_type for i in items.values()
|
|
64
|
+
):
|
|
65
|
+
return items
|
|
66
|
+
elif not field.dbref:
|
|
67
|
+
# We must turn the ObjectIds into DBRefs
|
|
68
|
+
|
|
69
|
+
# Recursively dig into the sub items of a list/dict
|
|
70
|
+
# to turn the ObjectIds into DBRefs
|
|
71
|
+
def _get_items_from_list(items):
|
|
72
|
+
new_items = []
|
|
73
|
+
for v in items:
|
|
74
|
+
value = v
|
|
75
|
+
if isinstance(v, dict):
|
|
76
|
+
value = _get_items_from_dict(v)
|
|
77
|
+
elif isinstance(v, list):
|
|
78
|
+
value = _get_items_from_list(v)
|
|
79
|
+
elif not isinstance(v, (DBRef, Document)):
|
|
80
|
+
value = field.to_python(v)
|
|
81
|
+
new_items.append(value)
|
|
82
|
+
return new_items
|
|
83
|
+
|
|
84
|
+
def _get_items_from_dict(items):
|
|
85
|
+
new_items = {}
|
|
86
|
+
for k, v in items.items():
|
|
87
|
+
value = v
|
|
88
|
+
if isinstance(v, list):
|
|
89
|
+
value = _get_items_from_list(v)
|
|
90
|
+
elif isinstance(v, dict):
|
|
91
|
+
value = _get_items_from_dict(v)
|
|
92
|
+
elif not isinstance(v, (DBRef, Document)):
|
|
93
|
+
value = field.to_python(v)
|
|
94
|
+
new_items[k] = value
|
|
95
|
+
return new_items
|
|
96
|
+
|
|
97
|
+
if not hasattr(items, "items"):
|
|
98
|
+
items = _get_items_from_list(items)
|
|
99
|
+
else:
|
|
100
|
+
items = _get_items_from_dict(items)
|
|
101
|
+
self.reference_map = self._find_references(items)
|
|
102
|
+
self.object_map = self._fetch_objects(doc_type=doc_type)
|
|
103
|
+
return self._attach_objects(items, 0, instance, name)
|
|
104
|
+
|
|
105
|
+
def _find_references(self, items, depth=0):
|
|
106
|
+
"""
|
|
107
|
+
Recursively finds all db references to be dereferenced
|
|
108
|
+
|
|
109
|
+
:param items: The iterable (dict, list, queryset)
|
|
110
|
+
:param depth: The current depth of recursion
|
|
111
|
+
"""
|
|
112
|
+
reference_map = {}
|
|
113
|
+
if not items or depth >= self.max_depth:
|
|
114
|
+
return reference_map
|
|
115
|
+
|
|
116
|
+
# Determine the iterator to use
|
|
117
|
+
if isinstance(items, dict):
|
|
118
|
+
iterator = items.values()
|
|
119
|
+
else:
|
|
120
|
+
iterator = items
|
|
121
|
+
|
|
122
|
+
# Recursively find dbreferences
|
|
123
|
+
depth += 1
|
|
124
|
+
for item in iterator:
|
|
125
|
+
if isinstance(item, (Document, EmbeddedDocument)):
|
|
126
|
+
for field_name, field in item._fields.items():
|
|
127
|
+
v = item._data.get(field_name, None)
|
|
128
|
+
if isinstance(v, LazyReference):
|
|
129
|
+
# LazyReference inherits DBRef but should not be dereferenced here !
|
|
130
|
+
continue
|
|
131
|
+
elif isinstance(v, DBRef):
|
|
132
|
+
reference_map.setdefault(field.document_type, set()).add(v.id)
|
|
133
|
+
elif isinstance(v, (dict, SON)) and "_ref" in v:
|
|
134
|
+
reference_map.setdefault(get_document(v["_cls"]), set()).add(
|
|
135
|
+
v["_ref"].id
|
|
136
|
+
)
|
|
137
|
+
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
|
|
138
|
+
field_cls = getattr(
|
|
139
|
+
getattr(field, "field", None), "document_type", None
|
|
140
|
+
)
|
|
141
|
+
references = self._find_references(v, depth)
|
|
142
|
+
for key, refs in references.items():
|
|
143
|
+
if isinstance(
|
|
144
|
+
field_cls, (Document, TopLevelDocumentMetaclass)
|
|
145
|
+
):
|
|
146
|
+
key = field_cls
|
|
147
|
+
reference_map.setdefault(key, set()).update(refs)
|
|
148
|
+
elif isinstance(item, LazyReference):
|
|
149
|
+
# LazyReference inherits DBRef but should not be dereferenced here !
|
|
150
|
+
continue
|
|
151
|
+
elif isinstance(item, DBRef):
|
|
152
|
+
reference_map.setdefault(item.collection, set()).add(item.id)
|
|
153
|
+
elif isinstance(item, (dict, SON)) and "_ref" in item:
|
|
154
|
+
reference_map.setdefault(get_document(item["_cls"]), set()).add(
|
|
155
|
+
item["_ref"].id
|
|
156
|
+
)
|
|
157
|
+
elif isinstance(item, (dict, list, tuple)) and depth - 1 <= self.max_depth:
|
|
158
|
+
references = self._find_references(item, depth - 1)
|
|
159
|
+
for key, refs in references.items():
|
|
160
|
+
reference_map.setdefault(key, set()).update(refs)
|
|
161
|
+
|
|
162
|
+
return reference_map
|
|
163
|
+
|
|
164
|
+
def _fetch_objects(self, doc_type=None):
|
|
165
|
+
"""Fetch all references and convert to their document objects"""
|
|
166
|
+
object_map = {}
|
|
167
|
+
for collection, dbrefs in self.reference_map.items():
|
|
168
|
+
if hasattr(collection, "objects"):
|
|
169
|
+
col_name = collection._get_collection_name()
|
|
170
|
+
refs = [
|
|
171
|
+
dbref for dbref in dbrefs if (col_name, dbref) not in object_map
|
|
172
|
+
]
|
|
173
|
+
references = collection.objects.in_bulk(refs)
|
|
174
|
+
for key, doc in references.items():
|
|
175
|
+
object_map[(col_name, key)] = doc
|
|
176
|
+
else: # Generic reference: use the refs data to convert to document
|
|
177
|
+
if isinstance(doc_type, (ListField, DictField, MapField)):
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
refs = [
|
|
181
|
+
dbref for dbref in dbrefs if (collection, dbref) not in object_map
|
|
182
|
+
]
|
|
183
|
+
# log(doc_type)
|
|
184
|
+
if isinstance(doc_type, Document):
|
|
185
|
+
references = doc_type._get_db()[collection].find(
|
|
186
|
+
{"_id": {"$in": refs}}
|
|
187
|
+
)
|
|
188
|
+
for ref in references:
|
|
189
|
+
doc = doc_type._from_son(ref)
|
|
190
|
+
object_map[(collection, doc.id)] = doc
|
|
191
|
+
else:
|
|
192
|
+
references = get_db()[collection].find({"_id": {"$in": refs}})
|
|
193
|
+
for ref in references:
|
|
194
|
+
if "_cls" in ref:
|
|
195
|
+
doc = get_document(ref["_cls"])._from_son(ref)
|
|
196
|
+
elif doc_type is None:
|
|
197
|
+
doc = get_document(
|
|
198
|
+
"".join(x.capitalize() for x in collection.split("_"))
|
|
199
|
+
)._from_son(ref)
|
|
200
|
+
else:
|
|
201
|
+
doc = doc_type._from_son(ref)
|
|
202
|
+
object_map[(collection, doc.id)] = doc
|
|
203
|
+
return object_map
|
|
204
|
+
|
|
205
|
+
def _attach_objects(self, items, depth=0, instance=None, name=None):
|
|
206
|
+
"""
|
|
207
|
+
Recursively finds all db references to be dereferenced
|
|
208
|
+
|
|
209
|
+
:param items: The iterable (dict, list, queryset)
|
|
210
|
+
:param depth: The current depth of recursion
|
|
211
|
+
:param instance: The owning instance used for tracking changes by
|
|
212
|
+
:class:`~autonomous.db.base.ComplexBaseField`
|
|
213
|
+
:param name: The name of the field, used for tracking changes by
|
|
214
|
+
:class:`~autonomous.db.base.ComplexBaseField`
|
|
215
|
+
"""
|
|
216
|
+
if not items:
|
|
217
|
+
if isinstance(items, (BaseDict, BaseList)):
|
|
218
|
+
return items
|
|
219
|
+
|
|
220
|
+
if instance:
|
|
221
|
+
if isinstance(items, dict):
|
|
222
|
+
return BaseDict(items, instance, name)
|
|
223
|
+
else:
|
|
224
|
+
return BaseList(items, instance, name)
|
|
225
|
+
|
|
226
|
+
if isinstance(items, (dict, SON)):
|
|
227
|
+
if "_ref" in items:
|
|
228
|
+
return self.object_map.get(
|
|
229
|
+
(items["_ref"].collection, items["_ref"].id), items
|
|
230
|
+
)
|
|
231
|
+
elif "_cls" in items:
|
|
232
|
+
doc = get_document(items["_cls"])._from_son(items)
|
|
233
|
+
_cls = doc._data.pop("_cls", None)
|
|
234
|
+
del items["_cls"]
|
|
235
|
+
doc._data = self._attach_objects(doc._data, depth, doc, None)
|
|
236
|
+
if _cls is not None:
|
|
237
|
+
doc._data["_cls"] = _cls
|
|
238
|
+
return doc
|
|
239
|
+
|
|
240
|
+
if not hasattr(items, "items"):
|
|
241
|
+
is_list = True
|
|
242
|
+
list_type = BaseList
|
|
243
|
+
if isinstance(items, EmbeddedDocumentList):
|
|
244
|
+
list_type = EmbeddedDocumentList
|
|
245
|
+
as_tuple = isinstance(items, tuple)
|
|
246
|
+
iterator = enumerate(items)
|
|
247
|
+
data = []
|
|
248
|
+
else:
|
|
249
|
+
is_list = False
|
|
250
|
+
iterator = items.items()
|
|
251
|
+
data = {}
|
|
252
|
+
|
|
253
|
+
depth += 1
|
|
254
|
+
for k, v in iterator:
|
|
255
|
+
if is_list:
|
|
256
|
+
data.append(v)
|
|
257
|
+
else:
|
|
258
|
+
data[k] = v
|
|
259
|
+
|
|
260
|
+
if k in self.object_map and not is_list:
|
|
261
|
+
data[k] = self.object_map[k]
|
|
262
|
+
elif isinstance(v, (Document, EmbeddedDocument)):
|
|
263
|
+
for field_name in v._fields:
|
|
264
|
+
v = data[k]._data.get(field_name, None)
|
|
265
|
+
if isinstance(v, DBRef):
|
|
266
|
+
data[k]._data[field_name] = self.object_map.get(
|
|
267
|
+
(v.collection, v.id), v
|
|
268
|
+
)
|
|
269
|
+
elif isinstance(v, (dict, SON)) and "_ref" in v:
|
|
270
|
+
data[k]._data[field_name] = self.object_map.get(
|
|
271
|
+
(v["_ref"].collection, v["_ref"].id), v
|
|
272
|
+
)
|
|
273
|
+
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
|
|
274
|
+
item_name = f"{name}.{k}.{field_name}"
|
|
275
|
+
data[k]._data[field_name] = self._attach_objects(
|
|
276
|
+
v, depth, instance=instance, name=item_name
|
|
277
|
+
)
|
|
278
|
+
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
|
|
279
|
+
item_name = f"{name}.{k}" if name else name
|
|
280
|
+
data[k] = self._attach_objects(
|
|
281
|
+
v, depth - 1, instance=instance, name=item_name
|
|
282
|
+
)
|
|
283
|
+
elif isinstance(v, DBRef) and hasattr(v, "id"):
|
|
284
|
+
data[k] = self.object_map.get((v.collection, v.id), v)
|
|
285
|
+
|
|
286
|
+
if instance and name:
|
|
287
|
+
if is_list:
|
|
288
|
+
return tuple(data) if as_tuple else list_type(data, instance, name)
|
|
289
|
+
return BaseDict(data, instance, name)
|
|
290
|
+
depth += 1
|
|
291
|
+
return data
|