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
autonomous/model/automodel.py
CHANGED
|
@@ -1,221 +1,57 @@
|
|
|
1
|
-
# opt : Optional[str] # for optional attributes
|
|
2
|
-
# default : Optional[str] = "value" # for default values
|
|
3
|
-
import copy
|
|
4
1
|
import importlib
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
from abc import ABC
|
|
2
|
+
import os
|
|
3
|
+
import urllib.parse
|
|
8
4
|
from datetime import datetime
|
|
9
5
|
|
|
10
|
-
from
|
|
11
|
-
from autonomous.errors import DanglingReferenceError
|
|
12
|
-
|
|
13
|
-
from .autoattribute import AutoAttribute
|
|
14
|
-
from .orm import ORM
|
|
15
|
-
from .serializer import AutoDecoder, AutoEncoder
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class DelayedModel:
|
|
19
|
-
def __init__(self, model, pk):
|
|
20
|
-
# log(model, pk)
|
|
21
|
-
assert model
|
|
22
|
-
assert pk
|
|
23
|
-
module_name, class_name = model.rsplit(".", 1)
|
|
24
|
-
try:
|
|
25
|
-
module = importlib.import_module(module_name)
|
|
26
|
-
model = getattr(module, class_name)
|
|
27
|
-
except (ModuleNotFoundError, AttributeError) as e:
|
|
28
|
-
# stack = traceback.extract_stack()
|
|
29
|
-
# function_names = [
|
|
30
|
-
# f"{frame.filename}:{frame.lineno} - {frame.name} "
|
|
31
|
-
# for frame in stack[:-1]
|
|
32
|
-
# if "__" not in frame.filename
|
|
33
|
-
# ]
|
|
34
|
-
# log(e, *function_names)
|
|
35
|
-
raise DanglingReferenceError(model, pk, None)
|
|
36
|
-
else:
|
|
37
|
-
object.__setattr__(self, "_delayed_model", model)
|
|
38
|
-
object.__setattr__(self, "_delayed_pk", pk)
|
|
39
|
-
object.__setattr__(self, "_delayed_obj", None)
|
|
40
|
-
|
|
41
|
-
def _instance(self):
|
|
42
|
-
#### DO NOT TO ANY LOGGING IN THIS METHOD; IT CAUSES INFINITE RECURSION ####
|
|
43
|
-
if not object.__getattribute__(self, "_delayed_obj"):
|
|
44
|
-
_pk = object.__getattribute__(self, "_delayed_pk")
|
|
45
|
-
_model = object.__getattribute__(self, "_delayed_model")
|
|
46
|
-
_obj = _model.get(_pk)
|
|
47
|
-
if not _pk or not _model or _obj is None:
|
|
48
|
-
raise DanglingReferenceError(_model, _pk, _obj)
|
|
49
|
-
else:
|
|
50
|
-
object.__setattr__(self, "_delayed_obj", _obj)
|
|
51
|
-
|
|
52
|
-
return object.__getattribute__(self, "_delayed_obj")
|
|
53
|
-
|
|
54
|
-
def __getattribute__(self, name):
|
|
55
|
-
# log(name)
|
|
56
|
-
if name in [
|
|
57
|
-
"_delayed_model",
|
|
58
|
-
"_delayed_pk",
|
|
59
|
-
"_delayed_obj",
|
|
60
|
-
"_instance",
|
|
61
|
-
]:
|
|
62
|
-
return object.__getattribute__(self, name)
|
|
63
|
-
try:
|
|
64
|
-
return object.__getattribute__(self._instance(), name)
|
|
65
|
-
except DanglingReferenceError as e:
|
|
66
|
-
log(e)
|
|
67
|
-
return None
|
|
68
|
-
|
|
69
|
-
def __setattr__(self, name, value):
|
|
70
|
-
if name.startswith("_delayed"):
|
|
71
|
-
object.__setattr__(self, name, value)
|
|
72
|
-
else:
|
|
73
|
-
setattr(self._instance(), name, value)
|
|
74
|
-
|
|
75
|
-
def __delattr__(self, name):
|
|
76
|
-
delattr(self._instance(), name)
|
|
77
|
-
|
|
78
|
-
def __nonzero__(self):
|
|
79
|
-
return bool(self._instance())
|
|
80
|
-
|
|
81
|
-
def __str__(self):
|
|
82
|
-
return str(self._instance().__dict__)
|
|
83
|
-
|
|
84
|
-
def __repr__(self):
|
|
85
|
-
msg = f"\n<<DelayedModel {self._delayed_model.__name__}:{self._delayed_pk}>>"
|
|
86
|
-
return msg
|
|
87
|
-
|
|
88
|
-
def __hash__(self):
|
|
89
|
-
return hash(self._instance())
|
|
6
|
+
from bson import ObjectId
|
|
90
7
|
|
|
8
|
+
from autonomous import log
|
|
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
|
|
91
13
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
14
|
+
host = os.getenv("DB_HOST", "db")
|
|
15
|
+
port = os.getenv("DB_PORT", 27017)
|
|
16
|
+
password = urllib.parse.quote_plus(str(os.getenv("DB_PASSWORD")))
|
|
17
|
+
username = urllib.parse.quote_plus(str(os.getenv("DB_USERNAME")))
|
|
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}")
|
|
97
22
|
|
|
98
|
-
def __new__(cls, *args, **kwargs):
|
|
99
|
-
"""
|
|
100
|
-
Create a new instance of the AutoModel.
|
|
101
23
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
Args:
|
|
107
|
-
cls: The class itself.
|
|
108
|
-
*args: Positional arguments.
|
|
109
|
-
**kwargs: Keyword arguments, including 'pk' for primary key.
|
|
110
|
-
|
|
111
|
-
Returns:
|
|
112
|
-
obj: The created AutoModel instance.
|
|
113
|
-
"""
|
|
114
|
-
obj = super().__new__(cls)
|
|
115
|
-
pk = kwargs.pop("pk", None)
|
|
116
|
-
# set default attributes
|
|
117
|
-
# Get model data from database
|
|
118
|
-
result = cls.table().get(pk) or {}
|
|
119
|
-
|
|
120
|
-
# set object attributes
|
|
121
|
-
for k, v in cls.attributes.items():
|
|
122
|
-
if isinstance(v, AutoAttribute):
|
|
123
|
-
v = v.default
|
|
124
|
-
setattr(obj, k, result.get(k, copy.deepcopy(v)))
|
|
125
|
-
obj.pk = pk
|
|
126
|
-
for key, val in list(kwargs.items()):
|
|
127
|
-
if (
|
|
128
|
-
getattr(cls, key, None)
|
|
129
|
-
and getattr(cls, key).fset
|
|
130
|
-
and f"_{key}" in cls.attributes
|
|
131
|
-
):
|
|
132
|
-
kwargs[f"_{key}"] = kwargs.pop(key)
|
|
133
|
-
obj.__dict__ |= kwargs
|
|
134
|
-
# breakpoint()
|
|
135
|
-
obj.__dict__ = AutoDecoder.decode(obj.__dict__)
|
|
136
|
-
obj._automodel = obj.model_name(qualified=True)
|
|
137
|
-
obj.last_updated = datetime.now()
|
|
138
|
-
return obj
|
|
139
|
-
|
|
140
|
-
def __getattribute__(self, name):
|
|
141
|
-
obj = super().__getattribute__(name)
|
|
142
|
-
if not name.startswith("__"):
|
|
143
|
-
if isinstance(obj, DelayedModel):
|
|
144
|
-
try:
|
|
145
|
-
result = obj._instance()
|
|
146
|
-
except DanglingReferenceError as e:
|
|
147
|
-
log(e)
|
|
148
|
-
super().__setattr__(name, None)
|
|
149
|
-
return None
|
|
150
|
-
else:
|
|
151
|
-
super().__setattr__(name, result)
|
|
152
|
-
|
|
153
|
-
elif isinstance(obj, list):
|
|
154
|
-
results = []
|
|
155
|
-
scrubbed = False
|
|
156
|
-
for i, item in enumerate(obj):
|
|
157
|
-
if isinstance(item, DelayedModel):
|
|
158
|
-
try:
|
|
159
|
-
result = item._instance()
|
|
160
|
-
except DanglingReferenceError as e:
|
|
161
|
-
log(e)
|
|
162
|
-
scrubbed = True
|
|
163
|
-
else:
|
|
164
|
-
results.append(result)
|
|
165
|
-
if scrubbed:
|
|
166
|
-
super().__setattr__(name, results)
|
|
167
|
-
obj = results
|
|
168
|
-
|
|
169
|
-
elif isinstance(obj, dict):
|
|
170
|
-
results = {}
|
|
171
|
-
scrubbed = False
|
|
172
|
-
for key, item in obj.items():
|
|
173
|
-
if isinstance(item, DelayedModel):
|
|
174
|
-
try:
|
|
175
|
-
result = item._instance()
|
|
176
|
-
except DanglingReferenceError as e:
|
|
177
|
-
log(e)
|
|
178
|
-
scrubbed = True
|
|
179
|
-
else:
|
|
180
|
-
results[key] = result
|
|
181
|
-
if scrubbed:
|
|
182
|
-
super().__setattr__(name, results)
|
|
183
|
-
obj = results
|
|
184
|
-
return obj
|
|
185
|
-
|
|
186
|
-
def __str__(self) -> str:
|
|
187
|
-
return self.__repr__()
|
|
188
|
-
|
|
189
|
-
def __repr__(self) -> str:
|
|
190
|
-
"""
|
|
191
|
-
Return a string representation of the AutoModel instance.
|
|
192
|
-
|
|
193
|
-
Returns:
|
|
194
|
-
str: A string representation of the AutoModel instance.
|
|
195
|
-
"""
|
|
196
|
-
return str(self.__dict__)
|
|
24
|
+
class AutoModel(Document):
|
|
25
|
+
meta = {"abstract": True, "allow_inheritance": True, "strict": False}
|
|
26
|
+
last_updated = DateTimeField(default=datetime.now)
|
|
197
27
|
|
|
198
28
|
def __eq__(self, other):
|
|
199
29
|
return self.pk == other.pk if other else False
|
|
200
30
|
|
|
201
31
|
@classmethod
|
|
202
|
-
def
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
|
207
43
|
|
|
208
44
|
@classmethod
|
|
209
|
-
def
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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)
|
|
219
55
|
|
|
220
56
|
@classmethod
|
|
221
57
|
def model_name(cls, qualified=False):
|
|
@@ -227,74 +63,13 @@ class AutoModel(ABC):
|
|
|
227
63
|
"""
|
|
228
64
|
return f"{cls.__module__}.{cls.__name__}" if qualified else cls.__name__
|
|
229
65
|
|
|
230
|
-
@
|
|
231
|
-
def
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
"""
|
|
238
|
-
return self.pk
|
|
239
|
-
|
|
240
|
-
@_id.setter
|
|
241
|
-
def _id(self, _id):
|
|
242
|
-
"""
|
|
243
|
-
Get the primary key of this model.
|
|
244
|
-
|
|
245
|
-
Returns:
|
|
246
|
-
int: The primary key of this model.
|
|
247
|
-
"""
|
|
248
|
-
self.pk = str(_id)
|
|
249
|
-
|
|
250
|
-
def validate(self):
|
|
251
|
-
"""
|
|
252
|
-
Validate this model.
|
|
253
|
-
"""
|
|
254
|
-
for key, vattr in self.attributes.items():
|
|
255
|
-
if isinstance(vattr, AutoAttribute):
|
|
256
|
-
val = getattr(self, key)
|
|
257
|
-
if vattr.type == "TEXT":
|
|
258
|
-
if not isinstance(val, str):
|
|
259
|
-
raise TypeError(
|
|
260
|
-
f"{key} value must be a string, not {type(val)}"
|
|
261
|
-
)
|
|
262
|
-
elif vattr.type == "NUMERIC":
|
|
263
|
-
if not isinstance(val, (int, float)):
|
|
264
|
-
raise TypeError(
|
|
265
|
-
f"{key} value must be a number, not {type(val)}"
|
|
266
|
-
)
|
|
267
|
-
elif vattr.type == "MODEL":
|
|
268
|
-
# log(isinstance(val, (AutoModel, DelayedModel)), type(val))
|
|
269
|
-
if val is not None and not isinstance(
|
|
270
|
-
val, (AutoModel, DelayedModel)
|
|
271
|
-
):
|
|
272
|
-
raise TypeError(
|
|
273
|
-
f"{key} value must be an AutoModel or None, not {type(val)}"
|
|
274
|
-
)
|
|
275
|
-
else:
|
|
276
|
-
raise ValueError(f"{key}: Invalid type {self.type}")
|
|
277
|
-
|
|
278
|
-
if vattr.required and val is None:
|
|
279
|
-
raise ValueError(f"{key} is required")
|
|
280
|
-
if vattr.unique and len(self.search(**{key: val})) > 1:
|
|
281
|
-
raise ValueError(f"{key} must be unique")
|
|
282
|
-
if vattr.primary_key:
|
|
283
|
-
self.pk = val
|
|
284
|
-
|
|
285
|
-
def save(self):
|
|
286
|
-
"""
|
|
287
|
-
Save this model to the database.
|
|
288
|
-
|
|
289
|
-
Returns:
|
|
290
|
-
int: The primary key (pk) of the saved model.
|
|
291
|
-
"""
|
|
292
|
-
self.validate()
|
|
293
|
-
serialized_obj = self.serialize()
|
|
294
|
-
serialized_obj["pk"] = self.pk
|
|
295
|
-
self.pk = self.table().save(serialized_obj)
|
|
296
|
-
|
|
297
|
-
return self.pk
|
|
66
|
+
@classmethod
|
|
67
|
+
def load_model(cls, model):
|
|
68
|
+
module_name, model = (
|
|
69
|
+
model.rsplit(".", 1) if "." in model else (f"models.{model.lower()}", model)
|
|
70
|
+
)
|
|
71
|
+
module = importlib.import_module(module_name)
|
|
72
|
+
return getattr(module, model)
|
|
298
73
|
|
|
299
74
|
@classmethod
|
|
300
75
|
def get(cls, pk):
|
|
@@ -307,10 +82,22 @@ class AutoModel(ABC):
|
|
|
307
82
|
Returns:
|
|
308
83
|
AutoModel or None: The retrieved AutoModel instance, or None if not found.
|
|
309
84
|
"""
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
85
|
+
|
|
86
|
+
if isinstance(pk, str):
|
|
87
|
+
pk = ObjectId(pk)
|
|
88
|
+
elif isinstance(pk, dict) and "$oid" in pk:
|
|
89
|
+
pk = ObjectId(pk["$oid"])
|
|
90
|
+
try:
|
|
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}")
|
|
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
|
|
314
101
|
|
|
315
102
|
@classmethod
|
|
316
103
|
def random(cls):
|
|
@@ -323,9 +110,11 @@ class AutoModel(ABC):
|
|
|
323
110
|
Returns:
|
|
324
111
|
AutoModel or None: The retrieved AutoModel instance, or None if not found.
|
|
325
112
|
"""
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
113
|
+
pipeline = [{"$sample": {"size": 1}}]
|
|
114
|
+
|
|
115
|
+
result = cls.objects.aggregate(pipeline)
|
|
116
|
+
random_document = next(result, None)
|
|
117
|
+
return cls._from_son(random_document) if random_document else None
|
|
329
118
|
|
|
330
119
|
@classmethod
|
|
331
120
|
def all(cls):
|
|
@@ -335,10 +124,10 @@ class AutoModel(ABC):
|
|
|
335
124
|
Returns:
|
|
336
125
|
list: A list of AutoModel instances.
|
|
337
126
|
"""
|
|
338
|
-
return
|
|
127
|
+
return list(cls.objects())
|
|
339
128
|
|
|
340
129
|
@classmethod
|
|
341
|
-
def search(cls, **kwargs):
|
|
130
|
+
def search(cls, _order_by=None, _limit=None, **kwargs):
|
|
342
131
|
"""
|
|
343
132
|
Search for models containing the keyword values.
|
|
344
133
|
|
|
@@ -348,9 +137,15 @@ class AutoModel(ABC):
|
|
|
348
137
|
Returns:
|
|
349
138
|
list: A list of AutoModel instances that match the search criteria.
|
|
350
139
|
"""
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
140
|
+
results = cls.objects(**kwargs)
|
|
141
|
+
if _order_by:
|
|
142
|
+
results = results.order_by(*_order_by)
|
|
143
|
+
if _limit:
|
|
144
|
+
if isinstance(_limit, list):
|
|
145
|
+
results = results[_limit[0] : _limit[-1]]
|
|
146
|
+
else:
|
|
147
|
+
results = results[:_limit]
|
|
148
|
+
return list(results)
|
|
354
149
|
|
|
355
150
|
@classmethod
|
|
356
151
|
def find(cls, **kwargs):
|
|
@@ -363,38 +158,56 @@ class AutoModel(ABC):
|
|
|
363
158
|
Returns:
|
|
364
159
|
AutoModel or None: The first matching AutoModel instance, or None if not found.
|
|
365
160
|
"""
|
|
366
|
-
|
|
367
|
-
kwargs[k] = AutoEncoder.encode(v)
|
|
368
|
-
attribs = cls.table().find(**kwargs)
|
|
369
|
-
return cls(**attribs) if attribs else None
|
|
161
|
+
return cls.objects(**kwargs).first()
|
|
370
162
|
|
|
371
|
-
|
|
163
|
+
@classmethod
|
|
164
|
+
def auto_pre_save(cls, sender, document, **kwargs):
|
|
372
165
|
"""
|
|
373
|
-
|
|
166
|
+
Post-save hook for this model.
|
|
374
167
|
"""
|
|
375
|
-
|
|
376
|
-
raise ValueError("Values must be a dictionary")
|
|
377
|
-
for k, v in values.items():
|
|
378
|
-
if k in self.attributes or f"_{k}" in self.attributes:
|
|
379
|
-
if getattr(self.__class__, k, None) and getattr(self.__class__, k).fset:
|
|
380
|
-
getattr(self.__class__, k).fset(self, v)
|
|
381
|
-
elif k in self.attributes:
|
|
382
|
-
setattr(self, k, v)
|
|
383
|
-
self.save()
|
|
168
|
+
pass
|
|
384
169
|
|
|
385
|
-
|
|
170
|
+
@classmethod
|
|
171
|
+
def _auto_pre_save(cls, sender, document, **kwargs):
|
|
386
172
|
"""
|
|
387
|
-
|
|
173
|
+
Post-save hook for this model.
|
|
388
174
|
"""
|
|
389
|
-
|
|
175
|
+
sender.auto_pre_save(sender, document, **kwargs)
|
|
390
176
|
|
|
391
|
-
def
|
|
177
|
+
def save(self):
|
|
392
178
|
"""
|
|
393
|
-
|
|
179
|
+
Save this model to the database.
|
|
394
180
|
|
|
395
181
|
Returns:
|
|
396
|
-
|
|
182
|
+
int: The primary key (pk) of the saved model.
|
|
397
183
|
"""
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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)
|
|
202
|
+
|
|
203
|
+
def delete(self):
|
|
204
|
+
"""
|
|
205
|
+
Delete this model from the database.
|
|
206
|
+
"""
|
|
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,4 +1,3 @@
|
|
|
1
|
-
import glob
|
|
2
1
|
import io
|
|
3
2
|
import os
|
|
4
3
|
import shutil
|
|
@@ -15,12 +14,6 @@ class ImageStorage:
|
|
|
15
14
|
def __init__(self, path="static/images"):
|
|
16
15
|
self.base_path = path
|
|
17
16
|
|
|
18
|
-
def scan_storage(self, path=None):
|
|
19
|
-
for root, dirs, files in os.walk(path or self.base_path):
|
|
20
|
-
for file in files:
|
|
21
|
-
if file == "orig.webp":
|
|
22
|
-
yield os.path.join(root, file)
|
|
23
|
-
|
|
24
17
|
@classmethod
|
|
25
18
|
def _get_key(cls, folder="", pkey=None):
|
|
26
19
|
if folder and not folder.endswith("/"):
|
|
@@ -75,10 +68,10 @@ class ImageStorage:
|
|
|
75
68
|
if not asset_id:
|
|
76
69
|
return ""
|
|
77
70
|
original_path = f"{self.get_path(asset_id)}"
|
|
78
|
-
# log(f"Getting image: {asset_id}", original_path)
|
|
71
|
+
# log(f"Getting image: {asset_id}.{size}", original_path)
|
|
79
72
|
if not os.path.exists(original_path):
|
|
80
73
|
log(f"Original image not found: {original_path}")
|
|
81
|
-
return
|
|
74
|
+
return ""
|
|
82
75
|
file_path = f"{original_path}/{size}.webp"
|
|
83
76
|
# log(file_path)
|
|
84
77
|
result_url = f"/{file_path}"
|
|
@@ -88,11 +81,7 @@ class ImageStorage:
|
|
|
88
81
|
# os.path.exists(original_path),
|
|
89
82
|
# os.path.exists(file_path),
|
|
90
83
|
# )
|
|
91
|
-
if (
|
|
92
|
-
size != "orig"
|
|
93
|
-
and os.path.exists(original_path)
|
|
94
|
-
and not os.path.exists(file_path)
|
|
95
|
-
):
|
|
84
|
+
if size != "orig" and not os.path.exists(file_path):
|
|
96
85
|
# If the file doesn't exist, create it
|
|
97
86
|
if result := self._resize_image(asset_id, size):
|
|
98
87
|
with open(file_path, "wb") as asset:
|
|
@@ -115,7 +104,10 @@ class ImageStorage:
|
|
|
115
104
|
|
|
116
105
|
def get_path(self, asset_id):
|
|
117
106
|
if asset_id:
|
|
118
|
-
|
|
107
|
+
asset_path = asset_id.replace(".", "/")
|
|
108
|
+
if asset_path.endswith("/"):
|
|
109
|
+
asset_path = asset_path[:-1]
|
|
110
|
+
return os.path.join(self.base_path, f"{asset_path}")
|
|
119
111
|
else:
|
|
120
112
|
return self.base_path
|
|
121
113
|
|
|
@@ -132,45 +124,8 @@ class ImageStorage:
|
|
|
132
124
|
return imgs
|
|
133
125
|
|
|
134
126
|
def remove(self, asset_id):
|
|
135
|
-
if not asset_id:
|
|
136
|
-
return False
|
|
137
|
-
file_path = self.get_path(asset_id)
|
|
138
|
-
if os.path.isdir(file_path):
|
|
139
|
-
print(f"Removing {file_path}")
|
|
140
|
-
# return shutil.rmtree(file_path, ignore_errors=True)
|
|
141
|
-
return False
|
|
142
|
-
|
|
143
|
-
def clear_cached(self, asset_id):
|
|
144
127
|
file_path = self.get_path(asset_id)
|
|
145
128
|
if os.path.isdir(file_path):
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
os.remove(file)
|
|
149
|
-
return False
|
|
150
|
-
|
|
151
|
-
def rotate(self, asset_id, amount=-90):
|
|
152
|
-
file_path = self.get_path(asset_id)
|
|
153
|
-
log(asset_id)
|
|
154
|
-
with Image.open(f"{file_path}/orig.webp") as img:
|
|
155
|
-
# Rotate the image 90 degrees
|
|
156
|
-
rotated_img = img.rotate(amount, expand=True)
|
|
157
|
-
# Save the rotated image
|
|
158
|
-
log(img, rotated_img)
|
|
159
|
-
# img = img.copy()
|
|
160
|
-
# img_byte_arr = io.BytesIO()
|
|
161
|
-
# img.save(img_byte_arr, )
|
|
162
|
-
self.clear_cached(asset_id)
|
|
163
|
-
rotated_img.save(f"{file_path}/orig.webp", format="WEBP")
|
|
164
|
-
return False
|
|
165
|
-
|
|
166
|
-
def flip(self, asset_id, flipx=True, flipy=True):
|
|
167
|
-
file_path = self.get_path(asset_id)
|
|
168
|
-
with Image.open(f"{file_path}/orig.webp") as img:
|
|
169
|
-
if flipx:
|
|
170
|
-
rotated_img = img.transpose(Image.FLIP_LEFT_RIGHT)
|
|
171
|
-
if flipy:
|
|
172
|
-
rotated_img = img.transpose(Image.FLIP_TOP_BOTTOM)
|
|
173
|
-
# Save the rotated image
|
|
174
|
-
rotated_img.save(f"{file_path}/orig.webp", format="WEBP")
|
|
175
|
-
self.clear_cached(asset_id)
|
|
129
|
+
shutil.rmtree(file_path)
|
|
130
|
+
return True
|
|
176
131
|
return False
|
autonomous/tasks/autotask.py
CHANGED
|
@@ -1,31 +1,12 @@
|
|
|
1
1
|
import importlib
|
|
2
2
|
import os
|
|
3
3
|
import subprocess
|
|
4
|
-
import time
|
|
5
4
|
|
|
6
5
|
from redis import Redis
|
|
7
6
|
from rq import Queue, Worker
|
|
8
|
-
from rq.job import Job
|
|
9
7
|
|
|
10
8
|
from autonomous import log
|
|
11
9
|
|
|
12
|
-
# class AutoJob(Job):
|
|
13
|
-
# def perform(self):
|
|
14
|
-
# start = time.time()
|
|
15
|
-
# result = super().perform()
|
|
16
|
-
# end = time.time()
|
|
17
|
-
# log.info(
|
|
18
|
-
# "Job Finished",
|
|
19
|
-
# key=self.key,
|
|
20
|
-
# seconds=(end - start),
|
|
21
|
-
# method=self.func_name,
|
|
22
|
-
# data_size=len(self._data),
|
|
23
|
-
# queue=self.origin,
|
|
24
|
-
# enqueued_at=self.enqueued_at.timestamp() if self.enqueued_at else None,
|
|
25
|
-
# started_at=self.started_at.timestamp() if self.started_at else None,
|
|
26
|
-
# )
|
|
27
|
-
# return result
|
|
28
|
-
|
|
29
10
|
|
|
30
11
|
class AutoTask:
|
|
31
12
|
def __init__(self, job):
|
|
@@ -161,9 +142,3 @@ class AutoTasks:
|
|
|
161
142
|
def clear(self):
|
|
162
143
|
AutoTasks.queue.empty()
|
|
163
144
|
AutoTasks.all_tasks = []
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
# if __name__ == "__main__":
|
|
167
|
-
# autotasks = AutoTasks()
|
|
168
|
-
# for _ in range(autotasks.workers):
|
|
169
|
-
# create_worker(autotasks.queue.name)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: autonomous-app
|
|
3
|
-
Version: 0.
|
|
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
|
|
@@ -25,27 +25,26 @@ License: MIT License
|
|
|
25
25
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
26
|
SOFTWARE.
|
|
27
27
|
Project-URL: homepage, https://github.com/Sallenmoore/autonomous
|
|
28
|
-
Classifier: Programming Language :: Python :: 3.
|
|
28
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
29
29
|
Classifier: License :: OSI Approved :: MIT License
|
|
30
30
|
Classifier: Operating System :: OS Independent
|
|
31
|
-
Requires-Python: >=3.
|
|
31
|
+
Requires-Python: >=3.12
|
|
32
32
|
Description-Content-Type: text/markdown
|
|
33
33
|
License-File: LICENSE
|
|
34
34
|
Requires-Dist: Flask
|
|
35
|
-
Requires-Dist: icecream
|
|
36
35
|
Requires-Dist: setuptools
|
|
37
36
|
Requires-Dist: python-dotenv
|
|
37
|
+
Requires-Dist: blinker
|
|
38
38
|
Requires-Dist: PyGithub
|
|
39
39
|
Requires-Dist: pygit2
|
|
40
|
-
Requires-Dist: cloudinary
|
|
41
40
|
Requires-Dist: pillow
|
|
42
|
-
Requires-Dist:
|
|
41
|
+
Requires-Dist: redis
|
|
43
42
|
Requires-Dist: jsmin
|
|
44
43
|
Requires-Dist: requests
|
|
45
44
|
Requires-Dist: gunicorn
|
|
46
45
|
Requires-Dist: Authlib
|
|
47
|
-
Requires-Dist:
|
|
48
|
-
Requires-Dist:
|
|
46
|
+
Requires-Dist: rq
|
|
47
|
+
Requires-Dist: openai>=1.42
|
|
49
48
|
Requires-Dist: dateparser
|
|
50
49
|
Requires-Dist: python-slugify
|
|
51
50
|
|