autonomous-app 0.3.0__py3-none-any.whl → 0.3.2__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.
Files changed (44) hide show
  1. autonomous/__init__.py +1 -1
  2. autonomous/ai/audioagent.py +1 -1
  3. autonomous/ai/imageagent.py +1 -1
  4. autonomous/ai/jsonagent.py +1 -1
  5. autonomous/ai/models/openai.py +81 -53
  6. autonomous/ai/oaiagent.py +1 -14
  7. autonomous/ai/textagent.py +1 -1
  8. autonomous/auth/autoauth.py +10 -10
  9. autonomous/auth/user.py +17 -2
  10. autonomous/db/__init__.py +42 -0
  11. autonomous/db/base/__init__.py +33 -0
  12. autonomous/db/base/common.py +62 -0
  13. autonomous/db/base/datastructures.py +476 -0
  14. autonomous/db/base/document.py +1230 -0
  15. autonomous/db/base/fields.py +767 -0
  16. autonomous/db/base/metaclasses.py +468 -0
  17. autonomous/db/base/utils.py +22 -0
  18. autonomous/db/common.py +79 -0
  19. autonomous/db/connection.py +472 -0
  20. autonomous/db/context_managers.py +313 -0
  21. autonomous/db/dereference.py +291 -0
  22. autonomous/db/document.py +1141 -0
  23. autonomous/db/errors.py +165 -0
  24. autonomous/db/fields.py +2732 -0
  25. autonomous/db/mongodb_support.py +24 -0
  26. autonomous/db/pymongo_support.py +80 -0
  27. autonomous/db/queryset/__init__.py +28 -0
  28. autonomous/db/queryset/base.py +2033 -0
  29. autonomous/db/queryset/field_list.py +88 -0
  30. autonomous/db/queryset/manager.py +58 -0
  31. autonomous/db/queryset/queryset.py +189 -0
  32. autonomous/db/queryset/transform.py +527 -0
  33. autonomous/db/queryset/visitor.py +189 -0
  34. autonomous/db/signals.py +59 -0
  35. autonomous/logger.py +3 -0
  36. autonomous/model/autoattr.py +56 -41
  37. autonomous/model/automodel.py +95 -34
  38. autonomous/storage/imagestorage.py +49 -8
  39. {autonomous_app-0.3.0.dist-info → autonomous_app-0.3.2.dist-info}/METADATA +2 -2
  40. autonomous_app-0.3.2.dist-info/RECORD +60 -0
  41. {autonomous_app-0.3.0.dist-info → autonomous_app-0.3.2.dist-info}/WHEEL +1 -1
  42. autonomous_app-0.3.0.dist-info/RECORD +0 -35
  43. {autonomous_app-0.3.0.dist-info → autonomous_app-0.3.2.dist-info}/LICENSE +0 -0
  44. {autonomous_app-0.3.0.dist-info → autonomous_app-0.3.2.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