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,189 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import warnings
|
|
3
|
+
|
|
4
|
+
from autonomous.db.errors import InvalidQueryError
|
|
5
|
+
from autonomous.db.queryset import transform
|
|
6
|
+
|
|
7
|
+
__all__ = ("Q", "QNode")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def warn_empty_is_deprecated():
|
|
11
|
+
msg = "'empty' property is deprecated in favour of using 'not bool(filter)'"
|
|
12
|
+
warnings.warn(msg, DeprecationWarning, stacklevel=2)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class QNodeVisitor:
|
|
16
|
+
"""Base visitor class for visiting Q-object nodes in a query tree."""
|
|
17
|
+
|
|
18
|
+
def visit_combination(self, combination):
|
|
19
|
+
"""Called by QCombination objects."""
|
|
20
|
+
return combination
|
|
21
|
+
|
|
22
|
+
def visit_query(self, query):
|
|
23
|
+
"""Called by (New)Q objects."""
|
|
24
|
+
return query
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DuplicateQueryConditionsError(InvalidQueryError):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SimplificationVisitor(QNodeVisitor):
|
|
32
|
+
"""Simplifies query trees by combining unnecessary 'and' connection nodes
|
|
33
|
+
into a single Q-object.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def visit_combination(self, combination):
|
|
37
|
+
if combination.operation == combination.AND:
|
|
38
|
+
# The simplification only applies to 'simple' queries
|
|
39
|
+
if all(isinstance(node, Q) for node in combination.children):
|
|
40
|
+
queries = [n.query for n in combination.children]
|
|
41
|
+
try:
|
|
42
|
+
return Q(**self._query_conjunction(queries))
|
|
43
|
+
except DuplicateQueryConditionsError:
|
|
44
|
+
# Cannot be simplified
|
|
45
|
+
pass
|
|
46
|
+
return combination
|
|
47
|
+
|
|
48
|
+
def _query_conjunction(self, queries):
|
|
49
|
+
"""Merges query dicts - effectively &ing them together."""
|
|
50
|
+
query_ops = set()
|
|
51
|
+
combined_query = {}
|
|
52
|
+
for query in queries:
|
|
53
|
+
ops = set(query.keys())
|
|
54
|
+
# Make sure that the same operation isn't applied more than once
|
|
55
|
+
# to a single field
|
|
56
|
+
intersection = ops.intersection(query_ops)
|
|
57
|
+
if intersection:
|
|
58
|
+
raise DuplicateQueryConditionsError()
|
|
59
|
+
|
|
60
|
+
query_ops.update(ops)
|
|
61
|
+
combined_query.update(copy.deepcopy(query))
|
|
62
|
+
return combined_query
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class QueryCompilerVisitor(QNodeVisitor):
|
|
66
|
+
"""Compiles the nodes in a query tree to a PyMongo-compatible query
|
|
67
|
+
dictionary.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, document):
|
|
71
|
+
self.document = document
|
|
72
|
+
|
|
73
|
+
def visit_combination(self, combination):
|
|
74
|
+
operator = "$and"
|
|
75
|
+
if combination.operation == combination.OR:
|
|
76
|
+
operator = "$or"
|
|
77
|
+
return {operator: combination.children}
|
|
78
|
+
|
|
79
|
+
def visit_query(self, query):
|
|
80
|
+
return transform.query(self.document, **query.query)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class QNode:
|
|
84
|
+
"""Base class for nodes in query trees."""
|
|
85
|
+
|
|
86
|
+
AND = 0
|
|
87
|
+
OR = 1
|
|
88
|
+
|
|
89
|
+
def to_query(self, document):
|
|
90
|
+
query = self.accept(SimplificationVisitor())
|
|
91
|
+
query = query.accept(QueryCompilerVisitor(document))
|
|
92
|
+
return query
|
|
93
|
+
|
|
94
|
+
def accept(self, visitor):
|
|
95
|
+
raise NotImplementedError
|
|
96
|
+
|
|
97
|
+
def _combine(self, other, operation):
|
|
98
|
+
"""Combine this node with another node into a QCombination
|
|
99
|
+
object.
|
|
100
|
+
"""
|
|
101
|
+
# If the other Q() is empty, ignore it and just use `self`.
|
|
102
|
+
if not bool(other):
|
|
103
|
+
return self
|
|
104
|
+
|
|
105
|
+
# Or if this Q is empty, ignore it and just use `other`.
|
|
106
|
+
if not bool(self):
|
|
107
|
+
return other
|
|
108
|
+
|
|
109
|
+
return QCombination(operation, [self, other])
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def empty(self):
|
|
113
|
+
warn_empty_is_deprecated()
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
def __or__(self, other):
|
|
117
|
+
return self._combine(other, self.OR)
|
|
118
|
+
|
|
119
|
+
def __and__(self, other):
|
|
120
|
+
return self._combine(other, self.AND)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class QCombination(QNode):
|
|
124
|
+
"""Represents the combination of several conditions by a given
|
|
125
|
+
logical operator.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
def __init__(self, operation, children):
|
|
129
|
+
self.operation = operation
|
|
130
|
+
self.children = []
|
|
131
|
+
for node in children:
|
|
132
|
+
# If the child is a combination of the same type, we can merge its
|
|
133
|
+
# children directly into this combinations children
|
|
134
|
+
if isinstance(node, QCombination) and node.operation == operation:
|
|
135
|
+
self.children += node.children
|
|
136
|
+
else:
|
|
137
|
+
self.children.append(node)
|
|
138
|
+
|
|
139
|
+
def __repr__(self):
|
|
140
|
+
op = " & " if self.operation is self.AND else " | "
|
|
141
|
+
return "(%s)" % op.join([repr(node) for node in self.children])
|
|
142
|
+
|
|
143
|
+
def __bool__(self):
|
|
144
|
+
return bool(self.children)
|
|
145
|
+
|
|
146
|
+
def accept(self, visitor):
|
|
147
|
+
for i in range(len(self.children)):
|
|
148
|
+
if isinstance(self.children[i], QNode):
|
|
149
|
+
self.children[i] = self.children[i].accept(visitor)
|
|
150
|
+
|
|
151
|
+
return visitor.visit_combination(self)
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def empty(self):
|
|
155
|
+
warn_empty_is_deprecated()
|
|
156
|
+
return not bool(self.children)
|
|
157
|
+
|
|
158
|
+
def __eq__(self, other):
|
|
159
|
+
return (
|
|
160
|
+
self.__class__ == other.__class__
|
|
161
|
+
and self.operation == other.operation
|
|
162
|
+
and self.children == other.children
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class Q(QNode):
|
|
167
|
+
"""A simple query object, used in a query tree to build up more complex
|
|
168
|
+
query structures.
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
def __init__(self, **query):
|
|
172
|
+
self.query = query
|
|
173
|
+
|
|
174
|
+
def __repr__(self):
|
|
175
|
+
return "Q(**%s)" % repr(self.query)
|
|
176
|
+
|
|
177
|
+
def __bool__(self):
|
|
178
|
+
return bool(self.query)
|
|
179
|
+
|
|
180
|
+
def __eq__(self, other):
|
|
181
|
+
return self.__class__ == other.__class__ and self.query == other.query
|
|
182
|
+
|
|
183
|
+
def accept(self, visitor):
|
|
184
|
+
return visitor.visit_query(self)
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def empty(self):
|
|
188
|
+
warn_empty_is_deprecated()
|
|
189
|
+
return not bool(self.query)
|
autonomous/db/signals.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
__all__ = (
|
|
2
|
+
"pre_init",
|
|
3
|
+
"post_init",
|
|
4
|
+
"pre_save",
|
|
5
|
+
"pre_save_post_validation",
|
|
6
|
+
"post_save",
|
|
7
|
+
"pre_delete",
|
|
8
|
+
"post_delete",
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
signals_available = False
|
|
12
|
+
try:
|
|
13
|
+
from blinker import Namespace
|
|
14
|
+
|
|
15
|
+
signals_available = True
|
|
16
|
+
except ImportError:
|
|
17
|
+
|
|
18
|
+
class Namespace:
|
|
19
|
+
def signal(self, name, doc=None):
|
|
20
|
+
return _FakeSignal(name, doc)
|
|
21
|
+
|
|
22
|
+
class _FakeSignal:
|
|
23
|
+
"""If blinker is unavailable, create a fake class with the same
|
|
24
|
+
interface that allows sending of signals but will fail with an
|
|
25
|
+
error on anything else. Instead of doing anything on send, it
|
|
26
|
+
will just ignore the arguments and do nothing instead.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, name, doc=None):
|
|
30
|
+
self.name = name
|
|
31
|
+
self.__doc__ = doc
|
|
32
|
+
|
|
33
|
+
def _fail(self, *args, **kwargs):
|
|
34
|
+
raise RuntimeError(
|
|
35
|
+
"signalling support is unavailable "
|
|
36
|
+
"because the blinker library is "
|
|
37
|
+
"not installed."
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
send = lambda *a, **kw: None # noqa
|
|
41
|
+
connect = disconnect = has_receivers_for = receivers_for = (
|
|
42
|
+
temporarily_connected_to
|
|
43
|
+
) = _fail
|
|
44
|
+
del _fail
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# the namespace for code signals. If you are not autonomous.db code, do
|
|
48
|
+
# not put signals in here. Create your own namespace instead.
|
|
49
|
+
_signals = Namespace()
|
|
50
|
+
|
|
51
|
+
pre_init = _signals.signal("pre_init")
|
|
52
|
+
post_init = _signals.signal("post_init")
|
|
53
|
+
pre_save = _signals.signal("pre_save")
|
|
54
|
+
pre_save_post_validation = _signals.signal("pre_save_post_validation")
|
|
55
|
+
post_save = _signals.signal("post_save")
|
|
56
|
+
pre_delete = _signals.signal("pre_delete")
|
|
57
|
+
post_delete = _signals.signal("post_delete")
|
|
58
|
+
pre_bulk_insert = _signals.signal("pre_bulk_insert")
|
|
59
|
+
post_bulk_insert = _signals.signal("post_bulk_insert")
|
autonomous/logger.py
CHANGED
|
@@ -43,6 +43,7 @@ class Logger:
|
|
|
43
43
|
|
|
44
44
|
def __call__(self, *args, **kwargs):
|
|
45
45
|
if self.enabled:
|
|
46
|
+
is_printed = kwargs.pop("_print", False)
|
|
46
47
|
caller = inspect.stack()[1]
|
|
47
48
|
fn = caller.filename.split("/")[-1]
|
|
48
49
|
msg = f"\n\n{'='*20}\t{fn}:{caller.function}()::{caller.lineno}\t{'='*20}\n"
|
|
@@ -57,6 +58,8 @@ class Logger:
|
|
|
57
58
|
current.write(f"{msg}\n")
|
|
58
59
|
with open(self.logarchive, "a") as archive:
|
|
59
60
|
archive.write(f"{msg}\n")
|
|
61
|
+
if is_printed:
|
|
62
|
+
print(msg)
|
|
60
63
|
|
|
61
64
|
|
|
62
65
|
log = Logger()
|
autonomous/model/autoattr.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
)
|
|
4
|
-
from mongoengine.fields import (
|
|
1
|
+
from autonomous import log
|
|
2
|
+
from autonomous.db.fields import (
|
|
5
3
|
BooleanField,
|
|
6
4
|
DateTimeField,
|
|
7
5
|
DictField,
|
|
@@ -10,6 +8,7 @@ from mongoengine.fields import (
|
|
|
10
8
|
EnumField,
|
|
11
9
|
FileField,
|
|
12
10
|
FloatField,
|
|
11
|
+
GenericLazyReferenceField,
|
|
13
12
|
GenericReferenceField,
|
|
14
13
|
ImageField,
|
|
15
14
|
IntField,
|
|
@@ -17,8 +16,6 @@ from mongoengine.fields import (
|
|
|
17
16
|
StringField,
|
|
18
17
|
)
|
|
19
18
|
|
|
20
|
-
from autonomous import log
|
|
21
|
-
|
|
22
19
|
|
|
23
20
|
class StringAttr(StringField):
|
|
24
21
|
pass
|
|
@@ -55,50 +52,68 @@ class ImageAttr(ImageField):
|
|
|
55
52
|
class ReferenceAttr(GenericReferenceField):
|
|
56
53
|
def __get__(self, instance, owner):
|
|
57
54
|
try:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
# If the document doesn't exist, return None
|
|
55
|
+
result = super().__get__(instance, owner)
|
|
56
|
+
except DoesNotExist as e:
|
|
57
|
+
log(f"ReferenceAttr Error: {e}")
|
|
62
58
|
return None
|
|
59
|
+
return result
|
|
63
60
|
|
|
64
61
|
|
|
65
|
-
class
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
# class ReferenceAttr(GenericLazyReferenceField):
|
|
63
|
+
# def __get__(self, instance, owner):
|
|
64
|
+
# try:
|
|
65
|
+
# result = super().__get__(instance, owner)
|
|
66
|
+
# except DoesNotExist as e:
|
|
67
|
+
# log(f"ReferenceAttr Error: {e}")
|
|
68
|
+
# return None
|
|
69
|
+
# return result.fetch() if result and result.pk else result
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
71
|
+
# except DoesNotExist:
|
|
72
|
+
# If the document doesn't exist, return None
|
|
73
|
+
# return None
|
|
74
|
+
|
|
75
|
+
# def validate(self, value):
|
|
76
|
+
# if value is not None and not self.required:
|
|
77
|
+
# super().validate(value)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ListAttr(ListField):
|
|
81
|
+
# pass
|
|
82
|
+
def __get__(self, instance, owner):
|
|
83
|
+
# log(instance, owner)
|
|
84
|
+
results = super().__get__(instance, owner) or []
|
|
85
|
+
# print(self.name, self.field, owner, results)
|
|
86
|
+
if isinstance(self.field, ReferenceAttr):
|
|
87
|
+
i = 0
|
|
88
|
+
while i < len(results):
|
|
89
|
+
try:
|
|
90
|
+
if not results[i]:
|
|
91
|
+
log(f"Removing Object: {results[i]}")
|
|
92
|
+
results.pop(i)
|
|
93
|
+
else:
|
|
94
|
+
i += 1
|
|
95
|
+
except DoesNotExist:
|
|
96
|
+
results.pop(i)
|
|
97
|
+
log(f"Object Not Found: {results[i]}")
|
|
98
|
+
# log(results)
|
|
99
|
+
return results
|
|
83
100
|
|
|
84
101
|
|
|
85
102
|
class DictAttr(DictField):
|
|
86
|
-
def
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
103
|
+
def __get__(self, instance, owner):
|
|
104
|
+
# log(instance, owner)
|
|
105
|
+
results = super().__get__(instance, owner) or {}
|
|
106
|
+
log(self.name, self.field, owner, results)
|
|
107
|
+
for key, lazy_obj in results.items():
|
|
90
108
|
try:
|
|
91
|
-
if
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
safe_values[key] = value
|
|
96
|
-
else:
|
|
97
|
-
updated = True
|
|
109
|
+
if hasattr(lazy_obj, "fetch"):
|
|
110
|
+
lazy_obj = (
|
|
111
|
+
lazy_obj.fetch() if lazy_obj and lazy_obj.pk else lazy_obj
|
|
112
|
+
)
|
|
98
113
|
except DoesNotExist:
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return
|
|
114
|
+
log(f"Object Not Found: {lazy_obj}")
|
|
115
|
+
results[key] = lazy_obj
|
|
116
|
+
return results
|
|
102
117
|
|
|
103
118
|
|
|
104
119
|
class EnumAttr(EnumField):
|
autonomous/model/automodel.py
CHANGED
|
@@ -4,48 +4,55 @@ import urllib.parse
|
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
|
|
6
6
|
from bson import ObjectId
|
|
7
|
-
from mongoengine import Document, connect
|
|
8
|
-
from mongoengine.fields import DateTimeField
|
|
9
7
|
|
|
10
8
|
from autonomous import log
|
|
11
|
-
|
|
12
|
-
from .
|
|
9
|
+
from autonomous.db import Document, connect, signals
|
|
10
|
+
from autonomous.db.errors import ValidationError
|
|
11
|
+
from autonomous.db.fields import DateTimeField
|
|
12
|
+
from autonomous.model.autoattr import DictAttr, ListAttr
|
|
13
13
|
|
|
14
14
|
host = os.getenv("DB_HOST", "db")
|
|
15
15
|
port = os.getenv("DB_PORT", 27017)
|
|
16
16
|
password = urllib.parse.quote_plus(str(os.getenv("DB_PASSWORD")))
|
|
17
17
|
username = urllib.parse.quote_plus(str(os.getenv("DB_USERNAME")))
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
dbname = os.getenv("DB_DB")
|
|
19
|
+
# log(f"Connecting to MongoDB at {host}:{port} with {username}:{password} for {dbname}")
|
|
20
|
+
connect(host=f"mongodb://{username}:{password}@{host}:{port}/{dbname}?authSource=admin")
|
|
21
|
+
# log(f"{db}")
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
class AutoModel(Document):
|
|
23
|
-
meta = {"abstract": True, "allow_inheritance": True}
|
|
25
|
+
meta = {"abstract": True, "allow_inheritance": True, "strict": False}
|
|
24
26
|
last_updated = DateTimeField(default=datetime.now)
|
|
25
27
|
|
|
26
|
-
def __init__(self, *args, **kwargs):
|
|
27
|
-
super().__init__(*args, **kwargs)
|
|
28
|
-
if kwargs.pop("pk", None):
|
|
29
|
-
self.reload()
|
|
30
|
-
for k, v in kwargs.items():
|
|
31
|
-
setattr(self, k, v)
|
|
32
|
-
self.last_updated = datetime.now()
|
|
33
|
-
|
|
34
|
-
for field_name, field in self._fields.items():
|
|
35
|
-
value = getattr(self, field_name, None)
|
|
36
|
-
# log(
|
|
37
|
-
# f"Field: {field_name}, Type:{type(value)}, Value: {value}, {hasattr(field, "clean_references")}"
|
|
38
|
-
# )
|
|
39
|
-
|
|
40
|
-
if hasattr(field, "clean_references") and value:
|
|
41
|
-
cleaned_values, updated = field.clean_references(value)
|
|
42
|
-
# log(f"Cleaned Values: {cleaned_values}")
|
|
43
|
-
if updated:
|
|
44
|
-
setattr(self, field_name, cleaned_values)
|
|
45
|
-
|
|
46
28
|
def __eq__(self, other):
|
|
47
29
|
return self.pk == other.pk if other else False
|
|
48
30
|
|
|
31
|
+
@classmethod
|
|
32
|
+
def auto_pre_init(cls, sender, document, **kwargs):
|
|
33
|
+
values = kwargs.pop("values", None)
|
|
34
|
+
if pk := values.get("pk") or values.get("id"):
|
|
35
|
+
# Try to load the existing document from the database
|
|
36
|
+
if existing_doc := sender._get_collection().find_one({"_id": ObjectId(pk)}):
|
|
37
|
+
# Update the current instance with the existing data
|
|
38
|
+
existing_doc.pop("_id", None)
|
|
39
|
+
existing_doc.pop("_cls", None)
|
|
40
|
+
for k, v in existing_doc.items():
|
|
41
|
+
if not values.get(k):
|
|
42
|
+
values[k] = v
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def _auto_pre_init(cls, sender, document, **kwargs):
|
|
46
|
+
sender.auto_pre_init(sender, document, **kwargs)
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def auto_post_init(cls, sender, document, **kwargs):
|
|
50
|
+
document.last_updated = datetime.now()
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def _auto_post_init(cls, sender, document, **kwargs):
|
|
54
|
+
sender.auto_post_init(sender, document, **kwargs)
|
|
55
|
+
|
|
49
56
|
@classmethod
|
|
50
57
|
def model_name(cls, qualified=False):
|
|
51
58
|
"""
|
|
@@ -58,7 +65,9 @@ class AutoModel(Document):
|
|
|
58
65
|
|
|
59
66
|
@classmethod
|
|
60
67
|
def load_model(cls, model):
|
|
61
|
-
module_name, model =
|
|
68
|
+
module_name, model = (
|
|
69
|
+
model.rsplit(".", 1) if "." in model else (f"models.{model.lower()}", model)
|
|
70
|
+
)
|
|
62
71
|
module = importlib.import_module(module_name)
|
|
63
72
|
return getattr(module, model)
|
|
64
73
|
|
|
@@ -73,13 +82,22 @@ class AutoModel(Document):
|
|
|
73
82
|
Returns:
|
|
74
83
|
AutoModel or None: The retrieved AutoModel instance, or None if not found.
|
|
75
84
|
"""
|
|
76
|
-
|
|
85
|
+
|
|
86
|
+
if isinstance(pk, str):
|
|
77
87
|
pk = ObjectId(pk)
|
|
88
|
+
elif isinstance(pk, dict) and "$oid" in pk:
|
|
89
|
+
pk = ObjectId(pk["$oid"])
|
|
78
90
|
try:
|
|
79
|
-
return cls.objects(
|
|
80
|
-
except
|
|
81
|
-
log(e)
|
|
91
|
+
return cls.objects.get(id=pk)
|
|
92
|
+
except cls.DoesNotExist as e:
|
|
93
|
+
log(f"Model {cls.__name__} with pk {pk} not found : {e}")
|
|
82
94
|
return None
|
|
95
|
+
except ValidationError as e:
|
|
96
|
+
log(f"Model Validation failure {cls.__name__} [{pk}]: {e}")
|
|
97
|
+
return None
|
|
98
|
+
except Exception as e:
|
|
99
|
+
log(f"Error getting model {cls.__name__} with pk {pk}: {e}", _print=True)
|
|
100
|
+
raise e
|
|
83
101
|
|
|
84
102
|
@classmethod
|
|
85
103
|
def random(cls):
|
|
@@ -142,6 +160,20 @@ class AutoModel(Document):
|
|
|
142
160
|
"""
|
|
143
161
|
return cls.objects(**kwargs).first()
|
|
144
162
|
|
|
163
|
+
@classmethod
|
|
164
|
+
def auto_pre_save(cls, sender, document, **kwargs):
|
|
165
|
+
"""
|
|
166
|
+
Post-save hook for this model.
|
|
167
|
+
"""
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def _auto_pre_save(cls, sender, document, **kwargs):
|
|
172
|
+
"""
|
|
173
|
+
Post-save hook for this model.
|
|
174
|
+
"""
|
|
175
|
+
sender.auto_pre_save(sender, document, **kwargs)
|
|
176
|
+
|
|
145
177
|
def save(self):
|
|
146
178
|
"""
|
|
147
179
|
Save this model to the database.
|
|
@@ -149,11 +181,33 @@ class AutoModel(Document):
|
|
|
149
181
|
Returns:
|
|
150
182
|
int: The primary key (pk) of the saved model.
|
|
151
183
|
"""
|
|
152
|
-
# log(self.
|
|
153
|
-
|
|
184
|
+
# log(self.to_json())
|
|
185
|
+
obj = super().save()
|
|
186
|
+
self.pk = obj.pk
|
|
187
|
+
return self.pk
|
|
188
|
+
|
|
189
|
+
@classmethod
|
|
190
|
+
def auto_post_save(cls, sender, document, **kwargs):
|
|
191
|
+
"""
|
|
192
|
+
Post-save hook for this model.
|
|
193
|
+
"""
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
@classmethod
|
|
197
|
+
def _auto_post_save(cls, sender, document, **kwargs):
|
|
198
|
+
"""
|
|
199
|
+
Post-save hook for this model.
|
|
200
|
+
"""
|
|
201
|
+
sender.auto_post_save(sender, document, **kwargs)
|
|
154
202
|
|
|
155
203
|
def delete(self):
|
|
156
204
|
"""
|
|
157
205
|
Delete this model from the database.
|
|
158
206
|
"""
|
|
159
207
|
return super().delete()
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
signals.pre_init.connect(AutoModel._auto_pre_init)
|
|
211
|
+
signals.post_init.connect(AutoModel._auto_post_init)
|
|
212
|
+
signals.pre_save.connect(AutoModel._auto_pre_save)
|
|
213
|
+
signals.post_save.connect(AutoModel._auto_post_save)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: autonomous-app
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Containerized application framework built on Flask with additional libraries and tools for rapid development of web applications.
|
|
5
5
|
Author-email: Steven A Moore <samoore@binghamton.edu>
|
|
6
6
|
License: MIT License
|
|
@@ -34,9 +34,9 @@ License-File: LICENSE
|
|
|
34
34
|
Requires-Dist: Flask
|
|
35
35
|
Requires-Dist: setuptools
|
|
36
36
|
Requires-Dist: python-dotenv
|
|
37
|
+
Requires-Dist: blinker
|
|
37
38
|
Requires-Dist: PyGithub
|
|
38
39
|
Requires-Dist: pygit2
|
|
39
|
-
Requires-Dist: mongoengine
|
|
40
40
|
Requires-Dist: pillow
|
|
41
41
|
Requires-Dist: redis
|
|
42
42
|
Requires-Dist: jsmin
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
autonomous/__init__.py,sha256=4zG57y3yMritw2TPxGgITZCNAcpJp2ZELW1iO8Wrlp0,95
|
|
2
|
+
autonomous/cli.py,sha256=z4AaGeWNW_uBLFAHng0J_lfS9v3fXemK1PeT85u4Eo4,42
|
|
3
|
+
autonomous/logger.py,sha256=NQtgEaTWNAWfLSgqSP7ksXj1GpOuCgoUV711kSMm-WA,2022
|
|
4
|
+
autonomous/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
autonomous/ai/audioagent.py,sha256=uZEhJ8_ctEvzshxJewaGdw-gh2Ts8lIcEysh0rlL62w,1040
|
|
6
|
+
autonomous/ai/imageagent.py,sha256=Y3n4OFD-UC9lSg1j-U9wRnyLLaRl0LjibHbriJwYF2c,981
|
|
7
|
+
autonomous/ai/jsonagent.py,sha256=P5HGuN7r7whgryZ2oCvSRY7jQlq0FdDK3-DtdnUG_N0,1321
|
|
8
|
+
autonomous/ai/oaiagent.py,sha256=Zrd4iijGfkFsF1Dbhhj0SeHoezec3kae8CrFrZQeqXQ,1346
|
|
9
|
+
autonomous/ai/textagent.py,sha256=pYzuoE7ENsxXjTSoVRMGvy7KQK0cS50SlWpY-1r7PiY,1220
|
|
10
|
+
autonomous/ai/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
autonomous/ai/models/openai.py,sha256=npavtBRWaxZuO3EtdFK1pm64wporJcTb_JDdxhMyhPI,11732
|
|
12
|
+
autonomous/apis/version_control/GHCallbacks.py,sha256=AyiUlYfV5JePi11GVyqYyXoj5UTbPKzS-HRRI94rjJo,1069
|
|
13
|
+
autonomous/apis/version_control/GHOrganization.py,sha256=mi2livdsGurKiifbvuLwiFbdDzL77IlEfhwEa-tG77I,1155
|
|
14
|
+
autonomous/apis/version_control/GHRepo.py,sha256=hTFHMkxSbSlVELfh8S6mq6ijkIKPRQO-Q5775ZjRKD4,4622
|
|
15
|
+
autonomous/apis/version_control/GHVersionControl.py,sha256=VIhVRxe6gJgozFWyhyKIu4spgSJI-GChiVJudnSyggI,196
|
|
16
|
+
autonomous/apis/version_control/__init__.py,sha256=tP0bAWYl1RwBRi62HsIidmgyqHuSlCUqwGuKUKKRugc,117
|
|
17
|
+
autonomous/auth/__init__.py,sha256=IW5tQ8VYwHIbDfMYA0wYgx4PprwcjUWV4EoIJ8HTlMU,161
|
|
18
|
+
autonomous/auth/autoauth.py,sha256=6GFs8xikCvPYXZ29bbc5baf603QvnE8tZQIIrAfTziY,3624
|
|
19
|
+
autonomous/auth/github.py,sha256=dHf84bJdV9rXGcvRLzWCPW9CvuA-VEmqYi_QQFwd2kY,886
|
|
20
|
+
autonomous/auth/google.py,sha256=cHmqbyNEPTKipc3WkYcD1XPOyqcWEFW0Ks4qJYmGvPw,1049
|
|
21
|
+
autonomous/auth/user.py,sha256=t8R7KsHp-QK3B_OS5ERSnQ4P8Tnhjehhmdqp5gcKxuU,2702
|
|
22
|
+
autonomous/db/__init__.py,sha256=9frkXJrl_OUemUQteXCTPqC8ECyxjE91Gi2mgTq26Fw,1159
|
|
23
|
+
autonomous/db/common.py,sha256=BUN2x_XuQBRFcq54TGPx4yLMLJdgytdbIt07QWr4CSM,2551
|
|
24
|
+
autonomous/db/connection.py,sha256=IhfJ8H5SZ44z7ptEez3T8QUPk88en9s3y9eKIpnIfg4,17747
|
|
25
|
+
autonomous/db/context_managers.py,sha256=_nH2ajCL8Xy90AuB2rKaryR4iF8Q8ksU3Nei_mZj-DE,9918
|
|
26
|
+
autonomous/db/dereference.py,sha256=Q_LkFwG5Be8XFKuwgvOIMb87R1DpavFbCOV2HdJV56Q,12573
|
|
27
|
+
autonomous/db/document.py,sha256=PGbCbkx3Los4zOj0Da6YcLCv_rR-xXp_7X6qivjAsL4,44429
|
|
28
|
+
autonomous/db/errors.py,sha256=_QeCotid1kmr7_W0QyH6NUrwwYN9eced_yyyiop0Xlw,4108
|
|
29
|
+
autonomous/db/fields.py,sha256=S79EaZCD5WZM-z7Fo3u4LsK-Rx3J5t4wDJorSanJ2qQ,93727
|
|
30
|
+
autonomous/db/mongodb_support.py,sha256=u0X-zpqTIZZP8o2-IDyKRKHL8ALLhvW1VSGtK3fLyos,626
|
|
31
|
+
autonomous/db/pymongo_support.py,sha256=UEZ4RHAGb_t1nuMUAJXMNs0vdH3dutxAH5mwFCmG6jI,2951
|
|
32
|
+
autonomous/db/signals.py,sha256=BM-M4hh4SrTbV9bZVIEWTG8mxgKn9Lo2rC7owLJz4yQ,1791
|
|
33
|
+
autonomous/db/base/__init__.py,sha256=qbVw-SlbJxlWu8UoPLQcwyRQ7Oso0r3aUit6Jqpoz40,1026
|
|
34
|
+
autonomous/db/base/common.py,sha256=YjvDGwmn-QoRplL9Xx2q3eUXEetgo3YureIGxbR36Y8,1540
|
|
35
|
+
autonomous/db/base/datastructures.py,sha256=fcgWe2JsfzTK3jbku3Teh0Iwvn5U5EhCpyeh9xr8bZ0,15850
|
|
36
|
+
autonomous/db/base/document.py,sha256=OM7CeJFZbxha6yKMiMCrHOlTELfOXusqJ8i6FjdFd0c,46652
|
|
37
|
+
autonomous/db/base/fields.py,sha256=c_FO9mryhdqzv4NdM1TnEl4WDoopy46I9UF1L8MVFFw,29146
|
|
38
|
+
autonomous/db/base/metaclasses.py,sha256=GVvJYcCxaW1ltEqyH4oNvT_srckEXDSHOtHVU_TAN70,18138
|
|
39
|
+
autonomous/db/base/utils.py,sha256=MH4FuEwh-5IcIinwNTkyTs-PqQLyyiMctcYMsNP85qk,617
|
|
40
|
+
autonomous/db/queryset/__init__.py,sha256=XT3__0BJCvQIQj3S_Mp7mPxNBkfdvXkdw56cg2gc86o,756
|
|
41
|
+
autonomous/db/queryset/base.py,sha256=aTq4C3K_YP8H6eE_HHn82C8I81l8uw1w0EPmCzcS7HM,76077
|
|
42
|
+
autonomous/db/queryset/field_list.py,sha256=qY50kgMYzloZXrOXnWT0PS_fBJCoThSioRvW9-HmhYA,2964
|
|
43
|
+
autonomous/db/queryset/manager.py,sha256=fXu95TlGChdJWTRA4OnY_Ik25JzezJ2_qPqmH78xJsY,2238
|
|
44
|
+
autonomous/db/queryset/queryset.py,sha256=Xvt1q7Olij9STbJkHm6dKrUIrgyJeY_uwJOYE9WUZvk,5942
|
|
45
|
+
autonomous/db/queryset/transform.py,sha256=UhBdDxYR_bWH0ECnaSw9g9YMwgWRZtsRl_q6PkqO9eY,19615
|
|
46
|
+
autonomous/db/queryset/visitor.py,sha256=AN09lR6hWYUlKJC7G1sktvnWy5hrFnpoQhi58bOXbA4,5470
|
|
47
|
+
autonomous/model/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
48
|
+
autonomous/model/autoattr.py,sha256=JvPpENa-bSaRSk0s1I1y5eqrKEYX8MzZUUybZD6Guhs,2820
|
|
49
|
+
autonomous/model/automodel.py,sha256=UYaXkl27y6nuS9tS22OXiHfQRg7OFmoYjP5HK47cU3Q,6496
|
|
50
|
+
autonomous/storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
51
|
+
autonomous/storage/imagestorage.py,sha256=xxMu9gXZ3nGU9xsXNmHtk_9RtrALZ2QlX5XsR4SL7xc,4589
|
|
52
|
+
autonomous/storage/localstorage.py,sha256=FzrR6O9mMGAZt5dDgqzkeOQVfGRXCygR0kksz2MPpwE,2286
|
|
53
|
+
autonomous/tasks/__init__.py,sha256=pn7iZ14MhcHUdzcLkfkd4-45wgPP0tXahAz_cFgb_Tg,32
|
|
54
|
+
autonomous/tasks/autotask.py,sha256=aK5iapDhgcAic3F5ZYMAhNKJkOepj8yWwbMizKDzUwQ,4153
|
|
55
|
+
autonomous/utils/markdown.py,sha256=tf8vlHARiQO1X_aGbqlYozzP_TbdiDRT9EEP6aFRQo0,2153
|
|
56
|
+
autonomous_app-0.3.1.dist-info/LICENSE,sha256=-PHHSuDRkodHo3PEdMkDtoIdmLAOomMq6lsLaOetU8g,1076
|
|
57
|
+
autonomous_app-0.3.1.dist-info/METADATA,sha256=ri137pRjUmGCpW_jZvwWycCQOb6wS5hzs7pZGnopCH8,4188
|
|
58
|
+
autonomous_app-0.3.1.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
|
59
|
+
autonomous_app-0.3.1.dist-info/top_level.txt,sha256=ZyxWWDdbvZekF3UFunxl4BQsVDb_FOW3eTn0vun_jb4,11
|
|
60
|
+
autonomous_app-0.3.1.dist-info/RECORD,,
|