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.
- 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 +95 -34
- autonomous/storage/imagestorage.py +49 -8
- {autonomous_app-0.3.0.dist-info → autonomous_app-0.3.2.dist-info}/METADATA +2 -2
- autonomous_app-0.3.2.dist-info/RECORD +60 -0
- {autonomous_app-0.3.0.dist-info → autonomous_app-0.3.2.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.2.dist-info}/LICENSE +0 -0
- {autonomous_app-0.3.0.dist-info → autonomous_app-0.3.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
__all__ = ("QueryFieldList",)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class QueryFieldList:
|
|
5
|
+
"""Object that handles combinations of .only() and .exclude() calls"""
|
|
6
|
+
|
|
7
|
+
ONLY = 1
|
|
8
|
+
EXCLUDE = 0
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self, fields=None, value=ONLY, always_include=None, _only_called=False
|
|
12
|
+
):
|
|
13
|
+
"""The QueryFieldList builder
|
|
14
|
+
|
|
15
|
+
:param fields: A list of fields used in `.only()` or `.exclude()`
|
|
16
|
+
:param value: How to handle the fields; either `ONLY` or `EXCLUDE`
|
|
17
|
+
:param always_include: Any fields to always_include eg `_cls`
|
|
18
|
+
:param _only_called: Has `.only()` been called? If so its a set of fields
|
|
19
|
+
otherwise it performs a union.
|
|
20
|
+
"""
|
|
21
|
+
self.value = value
|
|
22
|
+
self.fields = set(fields or [])
|
|
23
|
+
self.always_include = set(always_include or [])
|
|
24
|
+
self._id = None
|
|
25
|
+
self._only_called = _only_called
|
|
26
|
+
self.slice = {}
|
|
27
|
+
|
|
28
|
+
def __add__(self, f):
|
|
29
|
+
if isinstance(f.value, dict):
|
|
30
|
+
for field in f.fields:
|
|
31
|
+
self.slice[field] = f.value
|
|
32
|
+
if not self.fields:
|
|
33
|
+
self.fields = f.fields
|
|
34
|
+
elif not self.fields:
|
|
35
|
+
self.fields = f.fields
|
|
36
|
+
self.value = f.value
|
|
37
|
+
self.slice = {}
|
|
38
|
+
elif self.value is self.ONLY and f.value is self.ONLY:
|
|
39
|
+
self._clean_slice()
|
|
40
|
+
if self._only_called:
|
|
41
|
+
self.fields = self.fields.union(f.fields)
|
|
42
|
+
else:
|
|
43
|
+
self.fields = f.fields
|
|
44
|
+
elif self.value is self.EXCLUDE and f.value is self.EXCLUDE:
|
|
45
|
+
self.fields = self.fields.union(f.fields)
|
|
46
|
+
self._clean_slice()
|
|
47
|
+
elif self.value is self.ONLY and f.value is self.EXCLUDE:
|
|
48
|
+
self.fields -= f.fields
|
|
49
|
+
self._clean_slice()
|
|
50
|
+
elif self.value is self.EXCLUDE and f.value is self.ONLY:
|
|
51
|
+
self.value = self.ONLY
|
|
52
|
+
self.fields = f.fields - self.fields
|
|
53
|
+
self._clean_slice()
|
|
54
|
+
|
|
55
|
+
if "_id" in f.fields:
|
|
56
|
+
self._id = f.value
|
|
57
|
+
|
|
58
|
+
if self.always_include:
|
|
59
|
+
if self.value is self.ONLY and self.fields:
|
|
60
|
+
if sorted(self.slice.keys()) != sorted(self.fields):
|
|
61
|
+
self.fields = self.fields.union(self.always_include)
|
|
62
|
+
else:
|
|
63
|
+
self.fields -= self.always_include
|
|
64
|
+
|
|
65
|
+
if getattr(f, "_only_called", False):
|
|
66
|
+
self._only_called = True
|
|
67
|
+
return self
|
|
68
|
+
|
|
69
|
+
def __bool__(self):
|
|
70
|
+
return bool(self.fields)
|
|
71
|
+
|
|
72
|
+
def as_dict(self):
|
|
73
|
+
field_list = {field: self.value for field in self.fields}
|
|
74
|
+
if self.slice:
|
|
75
|
+
field_list.update(self.slice)
|
|
76
|
+
if self._id is not None:
|
|
77
|
+
field_list["_id"] = self._id
|
|
78
|
+
return field_list
|
|
79
|
+
|
|
80
|
+
def reset(self):
|
|
81
|
+
self.fields = set()
|
|
82
|
+
self.slice = {}
|
|
83
|
+
self.value = self.ONLY
|
|
84
|
+
|
|
85
|
+
def _clean_slice(self):
|
|
86
|
+
if self.slice:
|
|
87
|
+
for field in set(self.slice.keys()) - self.fields:
|
|
88
|
+
del self.slice[field]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from functools import partial
|
|
2
|
+
|
|
3
|
+
from autonomous.db.queryset.queryset import QuerySet
|
|
4
|
+
|
|
5
|
+
__all__ = ("queryset_manager", "QuerySetManager")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class QuerySetManager:
|
|
9
|
+
"""
|
|
10
|
+
The default QuerySet Manager.
|
|
11
|
+
|
|
12
|
+
Custom QuerySet Manager functions can extend this class and users can
|
|
13
|
+
add extra queryset functionality. Any custom manager methods must accept a
|
|
14
|
+
:class:`~autonomous.db.Document` class as its first argument, and a
|
|
15
|
+
:class:`~autonomous.db.queryset.QuerySet` as its second argument.
|
|
16
|
+
|
|
17
|
+
The method function should return a :class:`~autonomous.db.queryset.QuerySet`
|
|
18
|
+
, probably the same one that was passed in, but modified in some way.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
get_queryset = None
|
|
22
|
+
default = QuerySet
|
|
23
|
+
|
|
24
|
+
def __init__(self, queryset_func=None):
|
|
25
|
+
if queryset_func:
|
|
26
|
+
self.get_queryset = queryset_func
|
|
27
|
+
|
|
28
|
+
def __get__(self, instance, owner):
|
|
29
|
+
"""Descriptor for instantiating a new QuerySet object when
|
|
30
|
+
Document.objects is accessed.
|
|
31
|
+
"""
|
|
32
|
+
if instance is not None:
|
|
33
|
+
# Document object being used rather than a document class
|
|
34
|
+
return self
|
|
35
|
+
|
|
36
|
+
# owner is the document that contains the QuerySetManager
|
|
37
|
+
queryset_class = owner._meta.get("queryset_class", self.default)
|
|
38
|
+
queryset = queryset_class(owner, owner._get_collection())
|
|
39
|
+
if self.get_queryset:
|
|
40
|
+
arg_count = self.get_queryset.__code__.co_argcount
|
|
41
|
+
if arg_count == 1:
|
|
42
|
+
queryset = self.get_queryset(queryset)
|
|
43
|
+
elif arg_count == 2:
|
|
44
|
+
queryset = self.get_queryset(owner, queryset)
|
|
45
|
+
else:
|
|
46
|
+
queryset = partial(self.get_queryset, owner, queryset)
|
|
47
|
+
return queryset
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def queryset_manager(func):
|
|
51
|
+
"""Decorator that allows you to define custom QuerySet managers on
|
|
52
|
+
:class:`~autonomous.db.Document` classes. The manager must be a function that
|
|
53
|
+
accepts a :class:`~autonomous.db.Document` class as its first argument, and a
|
|
54
|
+
:class:`~autonomous.db.queryset.QuerySet` as its second argument. The method
|
|
55
|
+
function should return a :class:`~autonomous.db.queryset.QuerySet`, probably
|
|
56
|
+
the same one that was passed in, but modified in some way.
|
|
57
|
+
"""
|
|
58
|
+
return QuerySetManager(func)
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
from autonomous.db.errors import OperationError
|
|
2
|
+
from autonomous.db.queryset.base import (
|
|
3
|
+
CASCADE,
|
|
4
|
+
DENY,
|
|
5
|
+
DO_NOTHING,
|
|
6
|
+
NULLIFY,
|
|
7
|
+
PULL,
|
|
8
|
+
BaseQuerySet,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = (
|
|
12
|
+
"QuerySet",
|
|
13
|
+
"QuerySetNoCache",
|
|
14
|
+
"DO_NOTHING",
|
|
15
|
+
"NULLIFY",
|
|
16
|
+
"CASCADE",
|
|
17
|
+
"DENY",
|
|
18
|
+
"PULL",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# The maximum number of items to display in a QuerySet.__repr__
|
|
22
|
+
REPR_OUTPUT_SIZE = 20
|
|
23
|
+
ITER_CHUNK_SIZE = 100
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class QuerySet(BaseQuerySet):
|
|
27
|
+
"""The default queryset, that builds queries and handles a set of results
|
|
28
|
+
returned from a query.
|
|
29
|
+
|
|
30
|
+
Wraps a MongoDB cursor, providing :class:`~autonomous.db.Document` objects as
|
|
31
|
+
the results.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
_has_more = True
|
|
35
|
+
_len = None
|
|
36
|
+
_result_cache = None
|
|
37
|
+
|
|
38
|
+
def __iter__(self):
|
|
39
|
+
"""Iteration utilises a results cache which iterates the cursor
|
|
40
|
+
in batches of ``ITER_CHUNK_SIZE``.
|
|
41
|
+
|
|
42
|
+
If ``self._has_more`` the cursor hasn't been exhausted so cache then
|
|
43
|
+
batch. Otherwise iterate the result_cache.
|
|
44
|
+
"""
|
|
45
|
+
self._iter = True
|
|
46
|
+
|
|
47
|
+
if self._has_more:
|
|
48
|
+
return self._iter_results()
|
|
49
|
+
|
|
50
|
+
# iterating over the cache.
|
|
51
|
+
return iter(self._result_cache)
|
|
52
|
+
|
|
53
|
+
def __len__(self):
|
|
54
|
+
"""Since __len__ is called quite frequently (for example, as part of
|
|
55
|
+
list(qs)), we populate the result cache and cache the length.
|
|
56
|
+
"""
|
|
57
|
+
if self._len is not None:
|
|
58
|
+
return self._len
|
|
59
|
+
|
|
60
|
+
# Populate the result cache with *all* of the docs in the cursor
|
|
61
|
+
if self._has_more:
|
|
62
|
+
list(self._iter_results())
|
|
63
|
+
|
|
64
|
+
# Cache the length of the complete result cache and return it
|
|
65
|
+
self._len = len(self._result_cache)
|
|
66
|
+
return self._len
|
|
67
|
+
|
|
68
|
+
def __repr__(self):
|
|
69
|
+
"""Provide a string representation of the QuerySet"""
|
|
70
|
+
if self._iter:
|
|
71
|
+
return ".. queryset mid-iteration .."
|
|
72
|
+
|
|
73
|
+
self._populate_cache()
|
|
74
|
+
data = self._result_cache[: REPR_OUTPUT_SIZE + 1]
|
|
75
|
+
if len(data) > REPR_OUTPUT_SIZE:
|
|
76
|
+
data[-1] = "...(remaining elements truncated)..."
|
|
77
|
+
return repr(data)
|
|
78
|
+
|
|
79
|
+
def _iter_results(self):
|
|
80
|
+
"""A generator for iterating over the result cache.
|
|
81
|
+
|
|
82
|
+
Also populates the cache if there are more possible results to
|
|
83
|
+
yield. Raises StopIteration when there are no more results.
|
|
84
|
+
"""
|
|
85
|
+
if self._result_cache is None:
|
|
86
|
+
self._result_cache = []
|
|
87
|
+
|
|
88
|
+
pos = 0
|
|
89
|
+
while True:
|
|
90
|
+
# For all positions lower than the length of the current result
|
|
91
|
+
# cache, serve the docs straight from the cache w/o hitting the
|
|
92
|
+
# database.
|
|
93
|
+
# XXX it's VERY important to compute the len within the `while`
|
|
94
|
+
# condition because the result cache might expand mid-iteration
|
|
95
|
+
# (e.g. if we call len(qs) inside a loop that iterates over the
|
|
96
|
+
# queryset). Fortunately len(list) is O(1) in Python, so this
|
|
97
|
+
# doesn't cause performance issues.
|
|
98
|
+
while pos < len(self._result_cache):
|
|
99
|
+
yield self._result_cache[pos]
|
|
100
|
+
pos += 1
|
|
101
|
+
|
|
102
|
+
# return if we already established there were no more
|
|
103
|
+
# docs in the db cursor.
|
|
104
|
+
if not self._has_more:
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
# Otherwise, populate more of the cache and repeat.
|
|
108
|
+
if len(self._result_cache) <= pos:
|
|
109
|
+
self._populate_cache()
|
|
110
|
+
|
|
111
|
+
def _populate_cache(self):
|
|
112
|
+
"""
|
|
113
|
+
Populates the result cache with ``ITER_CHUNK_SIZE`` more entries
|
|
114
|
+
(until the cursor is exhausted).
|
|
115
|
+
"""
|
|
116
|
+
if self._result_cache is None:
|
|
117
|
+
self._result_cache = []
|
|
118
|
+
|
|
119
|
+
# Skip populating the cache if we already established there are no
|
|
120
|
+
# more docs to pull from the database.
|
|
121
|
+
if not self._has_more:
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
# Pull in ITER_CHUNK_SIZE docs from the database and store them in
|
|
125
|
+
# the result cache.
|
|
126
|
+
try:
|
|
127
|
+
for _ in range(ITER_CHUNK_SIZE):
|
|
128
|
+
self._result_cache.append(next(self))
|
|
129
|
+
except StopIteration:
|
|
130
|
+
# Getting this exception means there are no more docs in the
|
|
131
|
+
# db cursor. Set _has_more to False so that we can use that
|
|
132
|
+
# information in other places.
|
|
133
|
+
self._has_more = False
|
|
134
|
+
|
|
135
|
+
def count(self, with_limit_and_skip=False):
|
|
136
|
+
"""Count the selected elements in the query.
|
|
137
|
+
|
|
138
|
+
:param with_limit_and_skip (optional): take any :meth:`limit` or
|
|
139
|
+
:meth:`skip` that has been applied to this cursor into account when
|
|
140
|
+
getting the count
|
|
141
|
+
"""
|
|
142
|
+
if with_limit_and_skip is False:
|
|
143
|
+
return super().count(with_limit_and_skip)
|
|
144
|
+
|
|
145
|
+
if self._len is None:
|
|
146
|
+
# cache the length
|
|
147
|
+
self._len = super().count(with_limit_and_skip)
|
|
148
|
+
|
|
149
|
+
return self._len
|
|
150
|
+
|
|
151
|
+
def no_cache(self):
|
|
152
|
+
"""Convert to a non-caching queryset"""
|
|
153
|
+
if self._result_cache is not None:
|
|
154
|
+
raise OperationError("QuerySet already cached")
|
|
155
|
+
|
|
156
|
+
return self._clone_into(QuerySetNoCache(self._document, self._collection))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class QuerySetNoCache(BaseQuerySet):
|
|
160
|
+
"""A non caching QuerySet"""
|
|
161
|
+
|
|
162
|
+
def cache(self):
|
|
163
|
+
"""Convert to a caching queryset"""
|
|
164
|
+
return self._clone_into(QuerySet(self._document, self._collection))
|
|
165
|
+
|
|
166
|
+
def __repr__(self):
|
|
167
|
+
"""Provides the string representation of the QuerySet"""
|
|
168
|
+
if self._iter:
|
|
169
|
+
return ".. queryset mid-iteration .."
|
|
170
|
+
|
|
171
|
+
data = []
|
|
172
|
+
for _ in range(REPR_OUTPUT_SIZE + 1):
|
|
173
|
+
try:
|
|
174
|
+
data.append(next(self))
|
|
175
|
+
except StopIteration:
|
|
176
|
+
break
|
|
177
|
+
|
|
178
|
+
if len(data) > REPR_OUTPUT_SIZE:
|
|
179
|
+
data[-1] = "...(remaining elements truncated)..."
|
|
180
|
+
|
|
181
|
+
self.rewind()
|
|
182
|
+
return repr(data)
|
|
183
|
+
|
|
184
|
+
def __iter__(self):
|
|
185
|
+
queryset = self
|
|
186
|
+
if queryset._iter:
|
|
187
|
+
queryset = self.clone()
|
|
188
|
+
queryset.rewind()
|
|
189
|
+
return queryset
|