autonomous-app 0.2.25__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 +5 -2
- autonomous/ai/audioagent.py +32 -0
- autonomous/ai/imageagent.py +31 -0
- autonomous/ai/jsonagent.py +40 -0
- autonomous/ai/models/__init__.py +0 -0
- autonomous/ai/models/openai.py +308 -0
- autonomous/ai/oaiagent.py +20 -194
- autonomous/ai/textagent.py +35 -0
- autonomous/auth/autoauth.py +11 -11
- autonomous/auth/user.py +24 -11
- autonomous/db/__init__.py +41 -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 +120 -0
- autonomous/model/automodel.py +121 -308
- autonomous/storage/imagestorage.py +9 -54
- autonomous/tasks/autotask.py +0 -25
- {autonomous_app-0.2.25.dist-info → autonomous_app-0.3.1.dist-info}/METADATA +7 -8
- autonomous_app-0.3.1.dist-info/RECORD +60 -0
- {autonomous_app-0.2.25.dist-info → autonomous_app-0.3.1.dist-info}/WHEEL +1 -1
- autonomous/db/autodb.py +0 -86
- autonomous/db/table.py +0 -156
- autonomous/errors/__init__.py +0 -1
- autonomous/errors/danglingreferenceerror.py +0 -8
- autonomous/model/autoattribute.py +0 -20
- autonomous/model/orm.py +0 -86
- autonomous/model/serializer.py +0 -110
- autonomous_app-0.2.25.dist-info/RECORD +0 -36
- /autonomous/{storage → apis}/version_control/GHCallbacks.py +0 -0
- /autonomous/{storage → apis}/version_control/GHOrganization.py +0 -0
- /autonomous/{storage → apis}/version_control/GHRepo.py +0 -0
- /autonomous/{storage → apis}/version_control/GHVersionControl.py +0 -0
- /autonomous/{storage → apis}/version_control/__init__.py +0 -0
- /autonomous/{storage → utils}/markdown.py +0 -0
- {autonomous_app-0.2.25.dist-info → autonomous_app-0.3.1.dist-info}/LICENSE +0 -0
- {autonomous_app-0.2.25.dist-info → autonomous_app-0.3.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import weakref
|
|
2
|
+
|
|
3
|
+
from bson import DBRef
|
|
4
|
+
|
|
5
|
+
from autonomous import log
|
|
6
|
+
from autonomous.db.common import _import_class
|
|
7
|
+
from autonomous.db.errors import DoesNotExist, MultipleObjectsReturned
|
|
8
|
+
|
|
9
|
+
__all__ = (
|
|
10
|
+
"BaseDict",
|
|
11
|
+
"StrictDict",
|
|
12
|
+
"BaseList",
|
|
13
|
+
"EmbeddedDocumentList",
|
|
14
|
+
"LazyReference",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def mark_as_changed_wrapper(parent_method):
|
|
19
|
+
"""Decorator that ensures _mark_as_changed method gets called."""
|
|
20
|
+
|
|
21
|
+
def wrapper(self, *args, **kwargs):
|
|
22
|
+
# Can't use super() in the decorator.
|
|
23
|
+
# log(args, kwargs)
|
|
24
|
+
result = parent_method(self, *args, **kwargs)
|
|
25
|
+
self._mark_as_changed()
|
|
26
|
+
return result
|
|
27
|
+
|
|
28
|
+
return wrapper
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def mark_key_as_changed_wrapper(parent_method):
|
|
32
|
+
"""Decorator that ensures _mark_as_changed method gets called with the key argument"""
|
|
33
|
+
|
|
34
|
+
def wrapper(self, key, *args, **kwargs):
|
|
35
|
+
# Can't use super() in the decorator.
|
|
36
|
+
if not args or key not in self or self[key] != args[0]:
|
|
37
|
+
self._mark_as_changed(key)
|
|
38
|
+
return parent_method(self, key, *args, **kwargs)
|
|
39
|
+
|
|
40
|
+
return wrapper
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class BaseDict(dict):
|
|
44
|
+
"""A special dict so we can watch any changes."""
|
|
45
|
+
|
|
46
|
+
_dereferenced = False
|
|
47
|
+
_instance = None
|
|
48
|
+
_name = None
|
|
49
|
+
|
|
50
|
+
def __init__(self, dict_items, instance, name):
|
|
51
|
+
BaseDocument = _import_class("BaseDocument")
|
|
52
|
+
|
|
53
|
+
if isinstance(instance, BaseDocument):
|
|
54
|
+
self._instance = weakref.proxy(instance)
|
|
55
|
+
self._name = name
|
|
56
|
+
super().__init__(dict_items)
|
|
57
|
+
|
|
58
|
+
def get(self, key, default=None):
|
|
59
|
+
# get does not use __getitem__ by default so we must override it as well
|
|
60
|
+
try:
|
|
61
|
+
return self.__getitem__(key)
|
|
62
|
+
except KeyError:
|
|
63
|
+
return default
|
|
64
|
+
|
|
65
|
+
def __getitem__(self, key):
|
|
66
|
+
value = super().__getitem__(key)
|
|
67
|
+
|
|
68
|
+
EmbeddedDocument = _import_class("EmbeddedDocument")
|
|
69
|
+
if isinstance(value, EmbeddedDocument) and value._instance is None:
|
|
70
|
+
value._instance = self._instance
|
|
71
|
+
elif isinstance(value, dict) and not isinstance(value, BaseDict):
|
|
72
|
+
value = BaseDict(value, None, f"{self._name}.{key}")
|
|
73
|
+
super().__setitem__(key, value)
|
|
74
|
+
value._instance = self._instance
|
|
75
|
+
elif isinstance(value, list) and not isinstance(value, BaseList):
|
|
76
|
+
value = BaseList(value, None, f"{self._name}.{key}")
|
|
77
|
+
super().__setitem__(key, value)
|
|
78
|
+
value._instance = self._instance
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
def __getstate__(self):
|
|
82
|
+
self.instance = None
|
|
83
|
+
self._dereferenced = False
|
|
84
|
+
return self
|
|
85
|
+
|
|
86
|
+
def __setstate__(self, state):
|
|
87
|
+
self = state
|
|
88
|
+
return self
|
|
89
|
+
|
|
90
|
+
__setitem__ = mark_key_as_changed_wrapper(dict.__setitem__)
|
|
91
|
+
__delattr__ = mark_key_as_changed_wrapper(dict.__delattr__)
|
|
92
|
+
__delitem__ = mark_key_as_changed_wrapper(dict.__delitem__)
|
|
93
|
+
pop = mark_as_changed_wrapper(dict.pop)
|
|
94
|
+
clear = mark_as_changed_wrapper(dict.clear)
|
|
95
|
+
update = mark_as_changed_wrapper(dict.update)
|
|
96
|
+
popitem = mark_as_changed_wrapper(dict.popitem)
|
|
97
|
+
setdefault = mark_as_changed_wrapper(dict.setdefault)
|
|
98
|
+
|
|
99
|
+
def _mark_as_changed(self, key=None):
|
|
100
|
+
if hasattr(self._instance, "_mark_as_changed"):
|
|
101
|
+
if key:
|
|
102
|
+
self._instance._mark_as_changed(f"{self._name}.{key}")
|
|
103
|
+
else:
|
|
104
|
+
self._instance._mark_as_changed(self._name)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class BaseList(list):
|
|
108
|
+
"""A special list so we can watch any changes."""
|
|
109
|
+
|
|
110
|
+
_dereferenced = False
|
|
111
|
+
_instance = None
|
|
112
|
+
_name = None
|
|
113
|
+
|
|
114
|
+
def __init__(self, list_items, instance, name):
|
|
115
|
+
BaseDocument = _import_class("BaseDocument")
|
|
116
|
+
|
|
117
|
+
if isinstance(instance, BaseDocument):
|
|
118
|
+
if isinstance(instance, weakref.ProxyTypes):
|
|
119
|
+
self._instance = instance
|
|
120
|
+
else:
|
|
121
|
+
self._instance = weakref.proxy(instance)
|
|
122
|
+
|
|
123
|
+
self._name = name
|
|
124
|
+
super().__init__(list_items)
|
|
125
|
+
|
|
126
|
+
def __getitem__(self, key):
|
|
127
|
+
# change index to positive value because MongoDB does not support negative one
|
|
128
|
+
if isinstance(key, int) and key < 0:
|
|
129
|
+
key = len(self) + key
|
|
130
|
+
value = super().__getitem__(key)
|
|
131
|
+
|
|
132
|
+
if isinstance(key, slice):
|
|
133
|
+
# When receiving a slice operator, we don't convert the structure and bind
|
|
134
|
+
# to parent's instance. This is buggy for now but would require more work to be handled properly
|
|
135
|
+
return value
|
|
136
|
+
|
|
137
|
+
EmbeddedDocument = _import_class("EmbeddedDocument")
|
|
138
|
+
if isinstance(value, EmbeddedDocument) and value._instance is None:
|
|
139
|
+
value._instance = self._instance
|
|
140
|
+
elif isinstance(value, dict) and not isinstance(value, BaseDict):
|
|
141
|
+
# Replace dict by BaseDict
|
|
142
|
+
value = BaseDict(value, None, f"{self._name}.{key}")
|
|
143
|
+
super().__setitem__(key, value)
|
|
144
|
+
value._instance = self._instance
|
|
145
|
+
elif isinstance(value, list) and not isinstance(value, BaseList):
|
|
146
|
+
# Replace list by BaseList
|
|
147
|
+
value = BaseList(value, None, f"{self._name}.{key}")
|
|
148
|
+
super().__setitem__(key, value)
|
|
149
|
+
value._instance = self._instance
|
|
150
|
+
return value
|
|
151
|
+
|
|
152
|
+
def __iter__(self):
|
|
153
|
+
yield from super().__iter__()
|
|
154
|
+
|
|
155
|
+
def __getstate__(self):
|
|
156
|
+
self.instance = None
|
|
157
|
+
self._dereferenced = False
|
|
158
|
+
return self
|
|
159
|
+
|
|
160
|
+
def __setstate__(self, state):
|
|
161
|
+
self = state
|
|
162
|
+
return self
|
|
163
|
+
|
|
164
|
+
def __setitem__(self, key, value):
|
|
165
|
+
# log(key, value)
|
|
166
|
+
changed_key = key
|
|
167
|
+
if isinstance(key, slice):
|
|
168
|
+
# In case of slice, we don't bother to identify the exact elements being updated
|
|
169
|
+
# instead, we simply marks the whole list as changed
|
|
170
|
+
changed_key = None
|
|
171
|
+
|
|
172
|
+
result = super().__setitem__(key, value)
|
|
173
|
+
self._mark_as_changed(changed_key)
|
|
174
|
+
return result
|
|
175
|
+
|
|
176
|
+
append = mark_as_changed_wrapper(list.append)
|
|
177
|
+
extend = mark_as_changed_wrapper(list.extend)
|
|
178
|
+
insert = mark_as_changed_wrapper(list.insert)
|
|
179
|
+
pop = mark_as_changed_wrapper(list.pop)
|
|
180
|
+
remove = mark_as_changed_wrapper(list.remove)
|
|
181
|
+
reverse = mark_as_changed_wrapper(list.reverse)
|
|
182
|
+
sort = mark_as_changed_wrapper(list.sort)
|
|
183
|
+
__delitem__ = mark_as_changed_wrapper(list.__delitem__)
|
|
184
|
+
__iadd__ = mark_as_changed_wrapper(list.__iadd__)
|
|
185
|
+
__imul__ = mark_as_changed_wrapper(list.__imul__)
|
|
186
|
+
|
|
187
|
+
def _mark_as_changed(self, key=None):
|
|
188
|
+
if hasattr(self._instance, "_mark_as_changed"):
|
|
189
|
+
if key is not None:
|
|
190
|
+
self._instance._mark_as_changed(f"{self._name}.{key % len(self)}")
|
|
191
|
+
else:
|
|
192
|
+
self._instance._mark_as_changed(self._name)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class EmbeddedDocumentList(BaseList):
|
|
196
|
+
@classmethod
|
|
197
|
+
def __match_all(cls, embedded_doc, kwargs):
|
|
198
|
+
"""Return True if a given embedded doc matches all the filter
|
|
199
|
+
kwargs. If it doesn't return False.
|
|
200
|
+
"""
|
|
201
|
+
for key, expected_value in kwargs.items():
|
|
202
|
+
doc_val = getattr(embedded_doc, key)
|
|
203
|
+
if doc_val != expected_value and str(doc_val) != expected_value:
|
|
204
|
+
return False
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
@classmethod
|
|
208
|
+
def __only_matches(cls, embedded_docs, kwargs):
|
|
209
|
+
"""Return embedded docs that match the filter kwargs."""
|
|
210
|
+
if not kwargs:
|
|
211
|
+
return embedded_docs
|
|
212
|
+
return [doc for doc in embedded_docs if cls.__match_all(doc, kwargs)]
|
|
213
|
+
|
|
214
|
+
def filter(self, **kwargs):
|
|
215
|
+
"""
|
|
216
|
+
Filters the list by only including embedded documents with the
|
|
217
|
+
given keyword arguments.
|
|
218
|
+
|
|
219
|
+
This method only supports simple comparison (e.g. .filter(name='John Doe'))
|
|
220
|
+
and does not support operators like __gte, __lte, __icontains like queryset.filter does
|
|
221
|
+
|
|
222
|
+
:param kwargs: The keyword arguments corresponding to the fields to
|
|
223
|
+
filter on. *Multiple arguments are treated as if they are ANDed
|
|
224
|
+
together.*
|
|
225
|
+
:return: A new ``EmbeddedDocumentList`` containing the matching
|
|
226
|
+
embedded documents.
|
|
227
|
+
|
|
228
|
+
Raises ``AttributeError`` if a given keyword is not a valid field for
|
|
229
|
+
the embedded document class.
|
|
230
|
+
"""
|
|
231
|
+
values = self.__only_matches(self, kwargs)
|
|
232
|
+
return EmbeddedDocumentList(values, self._instance, self._name)
|
|
233
|
+
|
|
234
|
+
def exclude(self, **kwargs):
|
|
235
|
+
"""
|
|
236
|
+
Filters the list by excluding embedded documents with the given
|
|
237
|
+
keyword arguments.
|
|
238
|
+
|
|
239
|
+
:param kwargs: The keyword arguments corresponding to the fields to
|
|
240
|
+
exclude on. *Multiple arguments are treated as if they are ANDed
|
|
241
|
+
together.*
|
|
242
|
+
:return: A new ``EmbeddedDocumentList`` containing the non-matching
|
|
243
|
+
embedded documents.
|
|
244
|
+
|
|
245
|
+
Raises ``AttributeError`` if a given keyword is not a valid field for
|
|
246
|
+
the embedded document class.
|
|
247
|
+
"""
|
|
248
|
+
exclude = self.__only_matches(self, kwargs)
|
|
249
|
+
values = [item for item in self if item not in exclude]
|
|
250
|
+
return EmbeddedDocumentList(values, self._instance, self._name)
|
|
251
|
+
|
|
252
|
+
def count(self):
|
|
253
|
+
"""
|
|
254
|
+
The number of embedded documents in the list.
|
|
255
|
+
|
|
256
|
+
:return: The length of the list, equivalent to the result of ``len()``.
|
|
257
|
+
"""
|
|
258
|
+
return len(self)
|
|
259
|
+
|
|
260
|
+
def get(self, **kwargs):
|
|
261
|
+
"""
|
|
262
|
+
Retrieves an embedded document determined by the given keyword
|
|
263
|
+
arguments.
|
|
264
|
+
|
|
265
|
+
:param kwargs: The keyword arguments corresponding to the fields to
|
|
266
|
+
search on. *Multiple arguments are treated as if they are ANDed
|
|
267
|
+
together.*
|
|
268
|
+
:return: The embedded document matched by the given keyword arguments.
|
|
269
|
+
|
|
270
|
+
Raises ``DoesNotExist`` if the arguments used to query an embedded
|
|
271
|
+
document returns no results. ``MultipleObjectsReturned`` if more
|
|
272
|
+
than one result is returned.
|
|
273
|
+
"""
|
|
274
|
+
values = self.__only_matches(self, kwargs)
|
|
275
|
+
if len(values) == 0:
|
|
276
|
+
raise DoesNotExist("%s matching query does not exist." % self._name)
|
|
277
|
+
elif len(values) > 1:
|
|
278
|
+
raise MultipleObjectsReturned(
|
|
279
|
+
"%d items returned, instead of 1" % len(values)
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
return values[0]
|
|
283
|
+
|
|
284
|
+
def first(self):
|
|
285
|
+
"""Return the first embedded document in the list, or ``None``
|
|
286
|
+
if empty.
|
|
287
|
+
"""
|
|
288
|
+
if len(self) > 0:
|
|
289
|
+
return self[0]
|
|
290
|
+
|
|
291
|
+
def create(self, **values):
|
|
292
|
+
"""
|
|
293
|
+
Creates a new instance of the EmbeddedDocument and appends it to this EmbeddedDocumentList.
|
|
294
|
+
|
|
295
|
+
.. note::
|
|
296
|
+
the instance of the EmbeddedDocument is not automatically saved to the database.
|
|
297
|
+
You still need to call .save() on the parent Document.
|
|
298
|
+
|
|
299
|
+
:param values: A dictionary of values for the embedded document.
|
|
300
|
+
:return: The new embedded document instance.
|
|
301
|
+
"""
|
|
302
|
+
name = self._name
|
|
303
|
+
EmbeddedClass = self._instance._fields[name].field.document_type_obj
|
|
304
|
+
self._instance[self._name].append(EmbeddedClass(**values))
|
|
305
|
+
|
|
306
|
+
return self._instance[self._name][-1]
|
|
307
|
+
|
|
308
|
+
def save(self, *args, **kwargs):
|
|
309
|
+
"""
|
|
310
|
+
Saves the ancestor document.
|
|
311
|
+
|
|
312
|
+
:param args: Arguments passed up to the ancestor Document's save
|
|
313
|
+
method.
|
|
314
|
+
:param kwargs: Keyword arguments passed up to the ancestor Document's
|
|
315
|
+
save method.
|
|
316
|
+
"""
|
|
317
|
+
self._instance.save(*args, **kwargs)
|
|
318
|
+
|
|
319
|
+
def delete(self):
|
|
320
|
+
"""
|
|
321
|
+
Deletes the embedded documents from the database.
|
|
322
|
+
|
|
323
|
+
.. note::
|
|
324
|
+
The embedded document changes are not automatically saved
|
|
325
|
+
to the database after calling this method.
|
|
326
|
+
|
|
327
|
+
:return: The number of entries deleted.
|
|
328
|
+
"""
|
|
329
|
+
values = list(self)
|
|
330
|
+
for item in values:
|
|
331
|
+
self._instance[self._name].remove(item)
|
|
332
|
+
|
|
333
|
+
return len(values)
|
|
334
|
+
|
|
335
|
+
def update(self, **update):
|
|
336
|
+
"""
|
|
337
|
+
Updates the embedded documents with the given replacement values. This
|
|
338
|
+
function does not support mongoDB update operators such as ``inc__``.
|
|
339
|
+
|
|
340
|
+
.. note::
|
|
341
|
+
The embedded document changes are not automatically saved
|
|
342
|
+
to the database after calling this method.
|
|
343
|
+
|
|
344
|
+
:param update: A dictionary of update values to apply to each
|
|
345
|
+
embedded document.
|
|
346
|
+
:return: The number of entries updated.
|
|
347
|
+
"""
|
|
348
|
+
if len(update) == 0:
|
|
349
|
+
return 0
|
|
350
|
+
values = list(self)
|
|
351
|
+
for item in values:
|
|
352
|
+
for k, v in update.items():
|
|
353
|
+
setattr(item, k, v)
|
|
354
|
+
|
|
355
|
+
return len(values)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class StrictDict:
|
|
359
|
+
__slots__ = ()
|
|
360
|
+
_special_fields = {"get", "pop", "iteritems", "items", "keys", "create"}
|
|
361
|
+
_classes = {}
|
|
362
|
+
|
|
363
|
+
def __init__(self, **kwargs):
|
|
364
|
+
for k, v in kwargs.items():
|
|
365
|
+
setattr(self, k, v)
|
|
366
|
+
|
|
367
|
+
def __getitem__(self, key):
|
|
368
|
+
key = "_reserved_" + key if key in self._special_fields else key
|
|
369
|
+
try:
|
|
370
|
+
return getattr(self, key)
|
|
371
|
+
except AttributeError:
|
|
372
|
+
raise KeyError(key)
|
|
373
|
+
|
|
374
|
+
def __setitem__(self, key, value):
|
|
375
|
+
key = "_reserved_" + key if key in self._special_fields else key
|
|
376
|
+
return setattr(self, key, value)
|
|
377
|
+
|
|
378
|
+
def __contains__(self, key):
|
|
379
|
+
return hasattr(self, key)
|
|
380
|
+
|
|
381
|
+
def get(self, key, default=None):
|
|
382
|
+
try:
|
|
383
|
+
return self[key]
|
|
384
|
+
except KeyError:
|
|
385
|
+
return default
|
|
386
|
+
|
|
387
|
+
def pop(self, key, default=None):
|
|
388
|
+
v = self.get(key, default)
|
|
389
|
+
try:
|
|
390
|
+
delattr(self, key)
|
|
391
|
+
except AttributeError:
|
|
392
|
+
pass
|
|
393
|
+
return v
|
|
394
|
+
|
|
395
|
+
def iteritems(self):
|
|
396
|
+
for key in self:
|
|
397
|
+
yield key, self[key]
|
|
398
|
+
|
|
399
|
+
def items(self):
|
|
400
|
+
return [(k, self[k]) for k in iter(self)]
|
|
401
|
+
|
|
402
|
+
def iterkeys(self):
|
|
403
|
+
return iter(self)
|
|
404
|
+
|
|
405
|
+
def keys(self):
|
|
406
|
+
return list(iter(self))
|
|
407
|
+
|
|
408
|
+
def __iter__(self):
|
|
409
|
+
return (key for key in self.__slots__ if hasattr(self, key))
|
|
410
|
+
|
|
411
|
+
def __len__(self):
|
|
412
|
+
return len(list(self.items()))
|
|
413
|
+
|
|
414
|
+
def __eq__(self, other):
|
|
415
|
+
return list(self.items()) == list(other.items())
|
|
416
|
+
|
|
417
|
+
def __ne__(self, other):
|
|
418
|
+
return not (self == other)
|
|
419
|
+
|
|
420
|
+
@classmethod
|
|
421
|
+
def create(cls, allowed_keys):
|
|
422
|
+
allowed_keys_tuple = tuple(
|
|
423
|
+
("_reserved_" + k if k in cls._special_fields else k) for k in allowed_keys
|
|
424
|
+
)
|
|
425
|
+
allowed_keys = frozenset(allowed_keys_tuple)
|
|
426
|
+
if allowed_keys not in cls._classes:
|
|
427
|
+
|
|
428
|
+
class SpecificStrictDict(cls):
|
|
429
|
+
__slots__ = allowed_keys_tuple
|
|
430
|
+
|
|
431
|
+
def __repr__(self):
|
|
432
|
+
return "{%s}" % ", ".join(
|
|
433
|
+
f'"{k!s}": {v!r}' for k, v in self.items()
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
cls._classes[allowed_keys] = SpecificStrictDict
|
|
437
|
+
return cls._classes[allowed_keys]
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
class LazyReference(DBRef):
|
|
441
|
+
__slots__ = ("_cached_doc", "passthrough", "document_type")
|
|
442
|
+
|
|
443
|
+
def fetch(self, force=False):
|
|
444
|
+
if not self._cached_doc or force:
|
|
445
|
+
self._cached_doc = self.document_type.objects.get(pk=self.pk)
|
|
446
|
+
if not self._cached_doc:
|
|
447
|
+
raise DoesNotExist("Trying to dereference unknown document %s" % (self))
|
|
448
|
+
return self._cached_doc
|
|
449
|
+
|
|
450
|
+
@property
|
|
451
|
+
def pk(self):
|
|
452
|
+
return self.id
|
|
453
|
+
|
|
454
|
+
def __init__(self, document_type, pk, cached_doc=None, passthrough=False):
|
|
455
|
+
self.document_type = document_type
|
|
456
|
+
self._cached_doc = cached_doc
|
|
457
|
+
self.passthrough = passthrough
|
|
458
|
+
super().__init__(self.document_type._get_collection_name(), pk)
|
|
459
|
+
|
|
460
|
+
def __getitem__(self, name):
|
|
461
|
+
if not self.passthrough:
|
|
462
|
+
raise KeyError()
|
|
463
|
+
document = self.fetch()
|
|
464
|
+
return document[name]
|
|
465
|
+
|
|
466
|
+
def __getattr__(self, name):
|
|
467
|
+
if not object.__getattribute__(self, "passthrough"):
|
|
468
|
+
raise AttributeError()
|
|
469
|
+
document = self.fetch()
|
|
470
|
+
try:
|
|
471
|
+
return document[name]
|
|
472
|
+
except KeyError:
|
|
473
|
+
raise AttributeError()
|
|
474
|
+
|
|
475
|
+
def __repr__(self):
|
|
476
|
+
return f"<LazyReference({self.document_type}, {self.pk!r})>"
|