autonomous-app 0.2.24__py3-none-any.whl → 0.3.0__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 +280 -0
- autonomous/ai/oaiagent.py +24 -186
- autonomous/ai/textagent.py +35 -0
- autonomous/auth/autoauth.py +2 -2
- autonomous/auth/user.py +8 -10
- autonomous/model/autoattr.py +105 -0
- autonomous/model/automodel.py +70 -305
- autonomous/storage/imagestorage.py +9 -54
- autonomous/tasks/autotask.py +0 -25
- {autonomous_app-0.2.24.dist-info → autonomous_app-0.3.0.dist-info}/METADATA +7 -8
- autonomous_app-0.3.0.dist-info/RECORD +35 -0
- {autonomous_app-0.2.24.dist-info → autonomous_app-0.3.0.dist-info}/WHEEL +1 -1
- autonomous/db/__init__.py +0 -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 -102
- autonomous_app-0.2.24.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.24.dist-info → autonomous_app-0.3.0.dist-info}/LICENSE +0 -0
- {autonomous_app-0.2.24.dist-info → autonomous_app-0.3.0.dist-info}/top_level.txt +0 -0
autonomous/auth/user.py
CHANGED
|
@@ -5,7 +5,7 @@ This module provides a User class that uses the OpenIDAuth class for authenticat
|
|
|
5
5
|
from datetime import datetime
|
|
6
6
|
|
|
7
7
|
from autonomous import log
|
|
8
|
-
from autonomous.model.
|
|
8
|
+
from autonomous.model.autoattr import DateTimeAttr, StringAttr
|
|
9
9
|
from autonomous.model.automodel import AutoModel
|
|
10
10
|
|
|
11
11
|
|
|
@@ -14,15 +14,13 @@ class AutoUser(AutoModel):
|
|
|
14
14
|
This class represents a user who can authenticate using OpenID.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"token": None,
|
|
25
|
-
}
|
|
17
|
+
name = StringAttr(default="Anonymous")
|
|
18
|
+
email = StringAttr(required=True)
|
|
19
|
+
last_login = DateTimeAttr(default=datetime.now)
|
|
20
|
+
state = StringAttr(default="unauthenticated")
|
|
21
|
+
provider = StringAttr()
|
|
22
|
+
role = StringAttr(default="guest")
|
|
23
|
+
token = StringAttr()
|
|
26
24
|
|
|
27
25
|
def __eq__(self, other):
|
|
28
26
|
return self.pk == other.pk
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from mongoengine.base import (
|
|
2
|
+
get_document,
|
|
3
|
+
)
|
|
4
|
+
from mongoengine.fields import (
|
|
5
|
+
BooleanField,
|
|
6
|
+
DateTimeField,
|
|
7
|
+
DictField,
|
|
8
|
+
DoesNotExist,
|
|
9
|
+
EmailField,
|
|
10
|
+
EnumField,
|
|
11
|
+
FileField,
|
|
12
|
+
FloatField,
|
|
13
|
+
GenericReferenceField,
|
|
14
|
+
ImageField,
|
|
15
|
+
IntField,
|
|
16
|
+
ListField,
|
|
17
|
+
StringField,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from autonomous import log
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class StringAttr(StringField):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class IntAttr(IntField):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class FloatAttr(FloatField):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class BoolAttr(BooleanField):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DateTimeAttr(DateTimeField):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class EmailAttr(EmailField):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class FileAttr(FileField):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ImageAttr(ImageField):
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ReferenceAttr(GenericReferenceField):
|
|
56
|
+
def __get__(self, instance, owner):
|
|
57
|
+
try:
|
|
58
|
+
# Attempt to retrieve the referenced document
|
|
59
|
+
return super().__get__(instance, owner)
|
|
60
|
+
except DoesNotExist:
|
|
61
|
+
# If the document doesn't exist, return None
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ListAttr(ListField):
|
|
66
|
+
def clean_references(self, values):
|
|
67
|
+
safe_values = []
|
|
68
|
+
updated = False
|
|
69
|
+
|
|
70
|
+
for value in values:
|
|
71
|
+
try:
|
|
72
|
+
if isinstance(value, dict) and "_cls" in value:
|
|
73
|
+
doc_cls = get_document(value["_cls"])
|
|
74
|
+
value = doc_cls._get_db().dereference(value["_ref"])
|
|
75
|
+
if value:
|
|
76
|
+
safe_values.append(value)
|
|
77
|
+
else:
|
|
78
|
+
updated = True
|
|
79
|
+
except DoesNotExist:
|
|
80
|
+
updated = True
|
|
81
|
+
log("hi")
|
|
82
|
+
return safe_values, updated
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class DictAttr(DictField):
|
|
86
|
+
def clean_references(self, values):
|
|
87
|
+
safe_values = {}
|
|
88
|
+
updated = False
|
|
89
|
+
for key, value in values.items():
|
|
90
|
+
try:
|
|
91
|
+
if isinstance(value, dict) and "_cls" in value:
|
|
92
|
+
doc_cls = get_document(value["_cls"])
|
|
93
|
+
value = doc_cls._get_db().dereference(value["_ref"])
|
|
94
|
+
if value:
|
|
95
|
+
safe_values[key] = value
|
|
96
|
+
else:
|
|
97
|
+
updated = True
|
|
98
|
+
except DoesNotExist:
|
|
99
|
+
updated = True
|
|
100
|
+
|
|
101
|
+
return safe_values, updated
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class EnumAttr(EnumField):
|
|
105
|
+
pass
|
autonomous/model/automodel.py
CHANGED
|
@@ -1,217 +1,51 @@
|
|
|
1
|
-
# opt : Optional[str] # for optional attributes
|
|
2
|
-
# default : Optional[str] = "value" # for default values
|
|
3
|
-
import copy
|
|
4
1
|
import importlib
|
|
5
|
-
|
|
2
|
+
import os
|
|
3
|
+
import urllib.parse
|
|
6
4
|
from datetime import datetime
|
|
7
5
|
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
|
|
11
|
-
from .autoattribute import AutoAttribute
|
|
12
|
-
from .orm import ORM
|
|
13
|
-
from .serializer import AutoDecoder, AutoEncoder
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class DelayedModel:
|
|
17
|
-
def __init__(self, model, pk):
|
|
18
|
-
# log(model, pk)
|
|
19
|
-
assert model
|
|
20
|
-
assert pk
|
|
21
|
-
module_name, class_name = model.rsplit(".", 1)
|
|
22
|
-
try:
|
|
23
|
-
module = importlib.import_module(module_name)
|
|
24
|
-
model = getattr(module, class_name)
|
|
25
|
-
except (ModuleNotFoundError, AttributeError) as e:
|
|
26
|
-
log(e)
|
|
27
|
-
raise DanglingReferenceError(model, pk, None)
|
|
28
|
-
else:
|
|
29
|
-
object.__setattr__(self, "_delayed_model", model)
|
|
30
|
-
object.__setattr__(self, "_delayed_pk", pk)
|
|
31
|
-
object.__setattr__(self, "_delayed_obj", None)
|
|
32
|
-
|
|
33
|
-
def _instance(self):
|
|
34
|
-
#### DO NOT TO ANY LOGGING IN THIS METHOD; IT CAUSES INFINITE RECURSION ####
|
|
35
|
-
if not object.__getattribute__(self, "_delayed_obj"):
|
|
36
|
-
_pk = object.__getattribute__(self, "_delayed_pk")
|
|
37
|
-
_model = object.__getattribute__(self, "_delayed_model")
|
|
38
|
-
_obj = _model.get(_pk)
|
|
39
|
-
if not _pk or not _model or _obj is None:
|
|
40
|
-
raise DanglingReferenceError(_model, _pk, _obj)
|
|
41
|
-
else:
|
|
42
|
-
object.__setattr__(self, "_delayed_obj", _obj)
|
|
43
|
-
|
|
44
|
-
return object.__getattribute__(self, "_delayed_obj")
|
|
45
|
-
|
|
46
|
-
# def __getattr__(self, name):
|
|
47
|
-
# return getattr(self._instance(), name)
|
|
48
|
-
|
|
49
|
-
def __getattribute__(self, name):
|
|
50
|
-
# log(name)
|
|
51
|
-
if name in [
|
|
52
|
-
"_delayed_model",
|
|
53
|
-
"_delayed_pk",
|
|
54
|
-
"_delayed_obj",
|
|
55
|
-
"_instance",
|
|
56
|
-
]:
|
|
57
|
-
return object.__getattribute__(self, name)
|
|
58
|
-
try:
|
|
59
|
-
return object.__getattribute__(self._instance(), name)
|
|
60
|
-
except DanglingReferenceError as e:
|
|
61
|
-
log(e)
|
|
62
|
-
return None
|
|
63
|
-
|
|
64
|
-
def __setattr__(self, name, value):
|
|
65
|
-
if name.startswith("_delayed"):
|
|
66
|
-
object.__setattr__(self, name, value)
|
|
67
|
-
else:
|
|
68
|
-
setattr(self._instance(), name, value)
|
|
69
|
-
|
|
70
|
-
def __delattr__(self, name):
|
|
71
|
-
delattr(self._instance(), name)
|
|
72
|
-
|
|
73
|
-
def __nonzero__(self):
|
|
74
|
-
return bool(self._instance())
|
|
75
|
-
|
|
76
|
-
def __str__(self):
|
|
77
|
-
return str(self._instance().__dict__)
|
|
78
|
-
|
|
79
|
-
def __repr__(self):
|
|
80
|
-
msg = f"\n<<DelayedModel {self._delayed_model.__name__}:{self._delayed_pk}>>"
|
|
81
|
-
return msg
|
|
82
|
-
|
|
83
|
-
def __hash__(self):
|
|
84
|
-
return hash(self._instance())
|
|
85
|
-
|
|
6
|
+
from bson import ObjectId
|
|
7
|
+
from mongoengine import Document, connect
|
|
8
|
+
from mongoengine.fields import DateTimeField
|
|
86
9
|
|
|
87
|
-
|
|
88
|
-
attributes = {}
|
|
89
|
-
_table_name = ""
|
|
90
|
-
_table = None
|
|
91
|
-
_orm = ORM
|
|
92
|
-
|
|
93
|
-
def __new__(cls, *args, **kwargs):
|
|
94
|
-
"""
|
|
95
|
-
Create a new instance of the AutoModel.
|
|
96
|
-
|
|
97
|
-
This method is responsible for creating a new instance of the AutoModel class.
|
|
98
|
-
It sets default attributes, populates the object from the database if a primary key is provided,
|
|
99
|
-
and handles additional keyword arguments.
|
|
100
|
-
|
|
101
|
-
Args:
|
|
102
|
-
cls: The class itself.
|
|
103
|
-
*args: Positional arguments.
|
|
104
|
-
**kwargs: Keyword arguments, including 'pk' for primary key.
|
|
105
|
-
|
|
106
|
-
Returns:
|
|
107
|
-
obj: The created AutoModel instance.
|
|
108
|
-
"""
|
|
109
|
-
obj = super().__new__(cls)
|
|
110
|
-
pk = kwargs.pop("pk", None)
|
|
111
|
-
# set default attributes
|
|
112
|
-
# Get model data from database
|
|
113
|
-
result = cls.table().get(pk) or {}
|
|
10
|
+
from autonomous import log
|
|
114
11
|
|
|
115
|
-
|
|
116
|
-
for k, v in cls.attributes.items():
|
|
117
|
-
if isinstance(v, AutoAttribute):
|
|
118
|
-
v = v.default
|
|
119
|
-
setattr(obj, k, result.get(k, copy.deepcopy(v)))
|
|
120
|
-
obj.pk = pk
|
|
121
|
-
for key, val in list(kwargs.items()):
|
|
122
|
-
if (
|
|
123
|
-
getattr(cls, key, None)
|
|
124
|
-
and getattr(cls, key).fset
|
|
125
|
-
and f"_{key}" in cls.attributes
|
|
126
|
-
):
|
|
127
|
-
kwargs[f"_{key}"] = kwargs.pop(key)
|
|
128
|
-
obj.__dict__ |= kwargs
|
|
129
|
-
# breakpoint()
|
|
130
|
-
obj.__dict__ = AutoDecoder.decode(obj.__dict__)
|
|
131
|
-
obj._automodel = obj.model_name(qualified=True)
|
|
132
|
-
obj.last_updated = datetime.now()
|
|
133
|
-
return obj
|
|
12
|
+
from .autoattr import DictAttr, ListAttr
|
|
134
13
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
except DanglingReferenceError as e:
|
|
142
|
-
log(e)
|
|
143
|
-
super().__setattr__(name, None)
|
|
144
|
-
return None
|
|
145
|
-
else:
|
|
146
|
-
super().__setattr__(name, result)
|
|
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
|
+
db = os.getenv("DB_DB")
|
|
19
|
+
connect(host=f"mongodb://{username}:{password}@{host}:{port}")
|
|
147
20
|
|
|
148
|
-
elif isinstance(obj, list):
|
|
149
|
-
results = []
|
|
150
|
-
scrubbed = False
|
|
151
|
-
for i, item in enumerate(obj):
|
|
152
|
-
if isinstance(item, DelayedModel):
|
|
153
|
-
try:
|
|
154
|
-
result = item._instance()
|
|
155
|
-
except DanglingReferenceError as e:
|
|
156
|
-
log(e)
|
|
157
|
-
scrubbed = True
|
|
158
|
-
else:
|
|
159
|
-
results.append(result)
|
|
160
|
-
if scrubbed:
|
|
161
|
-
super().__setattr__(name, results)
|
|
162
|
-
obj = results
|
|
163
21
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
for key, item in obj.items():
|
|
168
|
-
if isinstance(item, DelayedModel):
|
|
169
|
-
try:
|
|
170
|
-
result = item._instance()
|
|
171
|
-
except DanglingReferenceError as e:
|
|
172
|
-
log(e)
|
|
173
|
-
scrubbed = True
|
|
174
|
-
else:
|
|
175
|
-
results[key] = result
|
|
176
|
-
if scrubbed:
|
|
177
|
-
super().__setattr__(name, results)
|
|
178
|
-
obj = results
|
|
179
|
-
return obj
|
|
22
|
+
class AutoModel(Document):
|
|
23
|
+
meta = {"abstract": True, "allow_inheritance": True}
|
|
24
|
+
last_updated = DateTimeField(default=datetime.now)
|
|
180
25
|
|
|
181
|
-
def
|
|
182
|
-
|
|
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()
|
|
183
33
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
+
# )
|
|
187
39
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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)
|
|
192
45
|
|
|
193
46
|
def __eq__(self, other):
|
|
194
47
|
return self.pk == other.pk if other else False
|
|
195
48
|
|
|
196
|
-
@classmethod
|
|
197
|
-
def load_model(cls, model, module=None):
|
|
198
|
-
if not module:
|
|
199
|
-
module = f"models.{model.lower()}"
|
|
200
|
-
module = importlib.import_module(module)
|
|
201
|
-
return getattr(module, model)
|
|
202
|
-
|
|
203
|
-
@classmethod
|
|
204
|
-
def table(cls):
|
|
205
|
-
# breakpoint()
|
|
206
|
-
if not cls._table or cls._table.name != cls.__name__:
|
|
207
|
-
cls.attributes["pk"] = None
|
|
208
|
-
cls.attributes["last_updated"] = datetime.now()
|
|
209
|
-
cls.attributes["_automodel"] = AutoAttribute(
|
|
210
|
-
"TEXT", default=cls.model_name(qualified=True)
|
|
211
|
-
)
|
|
212
|
-
cls._table = cls._orm(cls._table_name or cls.__name__, cls.attributes)
|
|
213
|
-
return cls._table
|
|
214
|
-
|
|
215
49
|
@classmethod
|
|
216
50
|
def model_name(cls, qualified=False):
|
|
217
51
|
"""
|
|
@@ -222,74 +56,11 @@ class AutoModel(ABC):
|
|
|
222
56
|
"""
|
|
223
57
|
return f"{cls.__module__}.{cls.__name__}" if qualified else cls.__name__
|
|
224
58
|
|
|
225
|
-
@
|
|
226
|
-
def
|
|
227
|
-
"""
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
Returns:
|
|
231
|
-
int: The primary key of this model.
|
|
232
|
-
"""
|
|
233
|
-
return self.pk
|
|
234
|
-
|
|
235
|
-
@_id.setter
|
|
236
|
-
def _id(self, _id):
|
|
237
|
-
"""
|
|
238
|
-
Get the primary key of this model.
|
|
239
|
-
|
|
240
|
-
Returns:
|
|
241
|
-
int: The primary key of this model.
|
|
242
|
-
"""
|
|
243
|
-
self.pk = str(_id)
|
|
244
|
-
|
|
245
|
-
def validate(self):
|
|
246
|
-
"""
|
|
247
|
-
Validate this model.
|
|
248
|
-
"""
|
|
249
|
-
for key, vattr in self.attributes.items():
|
|
250
|
-
if isinstance(vattr, AutoAttribute):
|
|
251
|
-
val = getattr(self, key)
|
|
252
|
-
if vattr.type == "TEXT":
|
|
253
|
-
if not isinstance(val, str):
|
|
254
|
-
raise TypeError(
|
|
255
|
-
f"{key} value must be a string, not {type(val)}"
|
|
256
|
-
)
|
|
257
|
-
elif vattr.type == "NUMERIC":
|
|
258
|
-
if not isinstance(val, (int, float)):
|
|
259
|
-
raise TypeError(
|
|
260
|
-
f"{key} value must be a number, not {type(val)}"
|
|
261
|
-
)
|
|
262
|
-
elif vattr.type == "MODEL":
|
|
263
|
-
# log(isinstance(val, (AutoModel, DelayedModel)), type(val))
|
|
264
|
-
if val is not None and not isinstance(
|
|
265
|
-
val, (AutoModel, DelayedModel)
|
|
266
|
-
):
|
|
267
|
-
raise TypeError(
|
|
268
|
-
f"{key} value must be an AutoModel or None, not {type(val)}"
|
|
269
|
-
)
|
|
270
|
-
else:
|
|
271
|
-
raise ValueError(f"{key}: Invalid type {self.type}")
|
|
272
|
-
|
|
273
|
-
if vattr.required and val is None:
|
|
274
|
-
raise ValueError(f"{key} is required")
|
|
275
|
-
if vattr.unique and len(self.search(**{key: val})) > 1:
|
|
276
|
-
raise ValueError(f"{key} must be unique")
|
|
277
|
-
if vattr.primary_key:
|
|
278
|
-
self.pk = val
|
|
279
|
-
|
|
280
|
-
def save(self):
|
|
281
|
-
"""
|
|
282
|
-
Save this model to the database.
|
|
283
|
-
|
|
284
|
-
Returns:
|
|
285
|
-
int: The primary key (pk) of the saved model.
|
|
286
|
-
"""
|
|
287
|
-
self.validate()
|
|
288
|
-
serialized_obj = self.serialize()
|
|
289
|
-
serialized_obj["pk"] = self.pk
|
|
290
|
-
self.pk = self.table().save(serialized_obj)
|
|
291
|
-
|
|
292
|
-
return self.pk
|
|
59
|
+
@classmethod
|
|
60
|
+
def load_model(cls, model):
|
|
61
|
+
module_name, model = model.rsplit(".", 1) if "." in model else ("models", model)
|
|
62
|
+
module = importlib.import_module(module_name)
|
|
63
|
+
return getattr(module, model)
|
|
293
64
|
|
|
294
65
|
@classmethod
|
|
295
66
|
def get(cls, pk):
|
|
@@ -302,9 +73,13 @@ class AutoModel(ABC):
|
|
|
302
73
|
Returns:
|
|
303
74
|
AutoModel or None: The retrieved AutoModel instance, or None if not found.
|
|
304
75
|
"""
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
76
|
+
if pk and isinstance(pk, str):
|
|
77
|
+
pk = ObjectId(pk)
|
|
78
|
+
try:
|
|
79
|
+
return cls.objects(pk=pk).get()
|
|
80
|
+
except Exception as e:
|
|
81
|
+
log(e)
|
|
82
|
+
return None
|
|
308
83
|
|
|
309
84
|
@classmethod
|
|
310
85
|
def random(cls):
|
|
@@ -317,9 +92,11 @@ class AutoModel(ABC):
|
|
|
317
92
|
Returns:
|
|
318
93
|
AutoModel or None: The retrieved AutoModel instance, or None if not found.
|
|
319
94
|
"""
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
95
|
+
pipeline = [{"$sample": {"size": 1}}]
|
|
96
|
+
|
|
97
|
+
result = cls.objects.aggregate(pipeline)
|
|
98
|
+
random_document = next(result, None)
|
|
99
|
+
return cls._from_son(random_document) if random_document else None
|
|
323
100
|
|
|
324
101
|
@classmethod
|
|
325
102
|
def all(cls):
|
|
@@ -329,10 +106,10 @@ class AutoModel(ABC):
|
|
|
329
106
|
Returns:
|
|
330
107
|
list: A list of AutoModel instances.
|
|
331
108
|
"""
|
|
332
|
-
return
|
|
109
|
+
return list(cls.objects())
|
|
333
110
|
|
|
334
111
|
@classmethod
|
|
335
|
-
def search(cls, **kwargs):
|
|
112
|
+
def search(cls, _order_by=None, _limit=None, **kwargs):
|
|
336
113
|
"""
|
|
337
114
|
Search for models containing the keyword values.
|
|
338
115
|
|
|
@@ -342,9 +119,15 @@ class AutoModel(ABC):
|
|
|
342
119
|
Returns:
|
|
343
120
|
list: A list of AutoModel instances that match the search criteria.
|
|
344
121
|
"""
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
122
|
+
results = cls.objects(**kwargs)
|
|
123
|
+
if _order_by:
|
|
124
|
+
results = results.order_by(*_order_by)
|
|
125
|
+
if _limit:
|
|
126
|
+
if isinstance(_limit, list):
|
|
127
|
+
results = results[_limit[0] : _limit[-1]]
|
|
128
|
+
else:
|
|
129
|
+
results = results[:_limit]
|
|
130
|
+
return list(results)
|
|
348
131
|
|
|
349
132
|
@classmethod
|
|
350
133
|
def find(cls, **kwargs):
|
|
@@ -357,38 +140,20 @@ class AutoModel(ABC):
|
|
|
357
140
|
Returns:
|
|
358
141
|
AutoModel or None: The first matching AutoModel instance, or None if not found.
|
|
359
142
|
"""
|
|
360
|
-
|
|
361
|
-
kwargs[k] = AutoEncoder.encode(v)
|
|
362
|
-
attribs = cls.table().find(**kwargs)
|
|
363
|
-
return cls(**attribs) if attribs else None
|
|
143
|
+
return cls.objects(**kwargs).first()
|
|
364
144
|
|
|
365
|
-
def
|
|
145
|
+
def save(self):
|
|
366
146
|
"""
|
|
367
|
-
|
|
147
|
+
Save this model to the database.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
int: The primary key (pk) of the saved model.
|
|
368
151
|
"""
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
for k, v in values.items():
|
|
372
|
-
if k in self.attributes or f"_{k}" in self.attributes:
|
|
373
|
-
if getattr(self.__class__, k, None) and getattr(self.__class__, k).fset:
|
|
374
|
-
getattr(self.__class__, k).fset(self, v)
|
|
375
|
-
elif k in self.attributes:
|
|
376
|
-
setattr(self, k, v)
|
|
377
|
-
self.save()
|
|
152
|
+
# log(self.model_dump_json())
|
|
153
|
+
return super().save()
|
|
378
154
|
|
|
379
155
|
def delete(self):
|
|
380
156
|
"""
|
|
381
157
|
Delete this model from the database.
|
|
382
158
|
"""
|
|
383
|
-
return
|
|
384
|
-
|
|
385
|
-
def serialize(self):
|
|
386
|
-
"""
|
|
387
|
-
Serialize this model to a dictionary.
|
|
388
|
-
|
|
389
|
-
Returns:
|
|
390
|
-
dict: A dictionary representation of the serialized model.
|
|
391
|
-
"""
|
|
392
|
-
vars = {k: v for k, v in self.__dict__.items() if k in self.attributes}
|
|
393
|
-
json_vars = AutoEncoder.encode(vars)
|
|
394
|
-
return json_vars
|
|
159
|
+
return super().delete()
|
|
@@ -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
|