autonomous-app 0.2.25__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 +25 -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 -311
- autonomous/storage/imagestorage.py +9 -54
- autonomous/tasks/autotask.py +0 -25
- {autonomous_app-0.2.25.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.25.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 -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.0.dist-info}/LICENSE +0 -0
- {autonomous_app-0.2.25.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,222 +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
|
-
|
|
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
|
|
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())
|
|
90
|
-
|
|
6
|
+
from bson import ObjectId
|
|
7
|
+
from mongoengine import Document, connect
|
|
8
|
+
from mongoengine.fields import DateTimeField
|
|
91
9
|
|
|
92
|
-
|
|
93
|
-
attributes = {}
|
|
94
|
-
_table_name = ""
|
|
95
|
-
_table = None
|
|
96
|
-
_orm = ORM
|
|
97
|
-
|
|
98
|
-
def __new__(cls, *args, **kwargs):
|
|
99
|
-
"""
|
|
100
|
-
Create a new instance of the AutoModel.
|
|
101
|
-
|
|
102
|
-
This method is responsible for creating a new instance of the AutoModel class.
|
|
103
|
-
It sets default attributes, populates the object from the database if a primary key is provided,
|
|
104
|
-
and handles additional keyword arguments.
|
|
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 {}
|
|
10
|
+
from autonomous import log
|
|
119
11
|
|
|
120
|
-
|
|
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
|
|
12
|
+
from .autoattr import DictAttr, ListAttr
|
|
139
13
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
except DanglingReferenceError as e:
|
|
147
|
-
log(e)
|
|
148
|
-
super().__setattr__(name, None)
|
|
149
|
-
return None
|
|
150
|
-
else:
|
|
151
|
-
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}")
|
|
152
20
|
|
|
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
21
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
|
22
|
+
class AutoModel(Document):
|
|
23
|
+
meta = {"abstract": True, "allow_inheritance": True}
|
|
24
|
+
last_updated = DateTimeField(default=datetime.now)
|
|
185
25
|
|
|
186
|
-
def
|
|
187
|
-
|
|
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()
|
|
188
33
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
+
# )
|
|
192
39
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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)
|
|
197
45
|
|
|
198
46
|
def __eq__(self, other):
|
|
199
47
|
return self.pk == other.pk if other else False
|
|
200
48
|
|
|
201
|
-
@classmethod
|
|
202
|
-
def load_model(cls, model, module=None):
|
|
203
|
-
if not module:
|
|
204
|
-
module = f"models.{model.lower()}"
|
|
205
|
-
module = importlib.import_module(module)
|
|
206
|
-
return getattr(module, model)
|
|
207
|
-
|
|
208
|
-
@classmethod
|
|
209
|
-
def table(cls):
|
|
210
|
-
# breakpoint()
|
|
211
|
-
if not cls._table or cls._table.name != cls.__name__:
|
|
212
|
-
cls.attributes["pk"] = None
|
|
213
|
-
cls.attributes["last_updated"] = datetime.now()
|
|
214
|
-
cls.attributes["_automodel"] = AutoAttribute(
|
|
215
|
-
"TEXT", default=cls.model_name(qualified=True)
|
|
216
|
-
)
|
|
217
|
-
cls._table = cls._orm(cls._table_name or cls.__name__, cls.attributes)
|
|
218
|
-
return cls._table
|
|
219
|
-
|
|
220
49
|
@classmethod
|
|
221
50
|
def model_name(cls, qualified=False):
|
|
222
51
|
"""
|
|
@@ -227,74 +56,11 @@ class AutoModel(ABC):
|
|
|
227
56
|
"""
|
|
228
57
|
return f"{cls.__module__}.{cls.__name__}" if qualified else cls.__name__
|
|
229
58
|
|
|
230
|
-
@
|
|
231
|
-
def
|
|
232
|
-
"""
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
Returns:
|
|
236
|
-
int: The primary key of this model.
|
|
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
|
|
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)
|
|
298
64
|
|
|
299
65
|
@classmethod
|
|
300
66
|
def get(cls, pk):
|
|
@@ -307,10 +73,13 @@ class AutoModel(ABC):
|
|
|
307
73
|
Returns:
|
|
308
74
|
AutoModel or None: The retrieved AutoModel instance, or None if not found.
|
|
309
75
|
"""
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
|
314
83
|
|
|
315
84
|
@classmethod
|
|
316
85
|
def random(cls):
|
|
@@ -323,9 +92,11 @@ class AutoModel(ABC):
|
|
|
323
92
|
Returns:
|
|
324
93
|
AutoModel or None: The retrieved AutoModel instance, or None if not found.
|
|
325
94
|
"""
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
|
329
100
|
|
|
330
101
|
@classmethod
|
|
331
102
|
def all(cls):
|
|
@@ -335,10 +106,10 @@ class AutoModel(ABC):
|
|
|
335
106
|
Returns:
|
|
336
107
|
list: A list of AutoModel instances.
|
|
337
108
|
"""
|
|
338
|
-
return
|
|
109
|
+
return list(cls.objects())
|
|
339
110
|
|
|
340
111
|
@classmethod
|
|
341
|
-
def search(cls, **kwargs):
|
|
112
|
+
def search(cls, _order_by=None, _limit=None, **kwargs):
|
|
342
113
|
"""
|
|
343
114
|
Search for models containing the keyword values.
|
|
344
115
|
|
|
@@ -348,9 +119,15 @@ class AutoModel(ABC):
|
|
|
348
119
|
Returns:
|
|
349
120
|
list: A list of AutoModel instances that match the search criteria.
|
|
350
121
|
"""
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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)
|
|
354
131
|
|
|
355
132
|
@classmethod
|
|
356
133
|
def find(cls, **kwargs):
|
|
@@ -363,38 +140,20 @@ class AutoModel(ABC):
|
|
|
363
140
|
Returns:
|
|
364
141
|
AutoModel or None: The first matching AutoModel instance, or None if not found.
|
|
365
142
|
"""
|
|
366
|
-
|
|
367
|
-
kwargs[k] = AutoEncoder.encode(v)
|
|
368
|
-
attribs = cls.table().find(**kwargs)
|
|
369
|
-
return cls(**attribs) if attribs else None
|
|
143
|
+
return cls.objects(**kwargs).first()
|
|
370
144
|
|
|
371
|
-
def
|
|
145
|
+
def save(self):
|
|
372
146
|
"""
|
|
373
|
-
|
|
147
|
+
Save this model to the database.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
int: The primary key (pk) of the saved model.
|
|
374
151
|
"""
|
|
375
|
-
|
|
376
|
-
|
|
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()
|
|
152
|
+
# log(self.model_dump_json())
|
|
153
|
+
return super().save()
|
|
384
154
|
|
|
385
155
|
def delete(self):
|
|
386
156
|
"""
|
|
387
157
|
Delete this model from the database.
|
|
388
158
|
"""
|
|
389
|
-
return
|
|
390
|
-
|
|
391
|
-
def serialize(self):
|
|
392
|
-
"""
|
|
393
|
-
Serialize this model to a dictionary.
|
|
394
|
-
|
|
395
|
-
Returns:
|
|
396
|
-
dict: A dictionary representation of the serialized model.
|
|
397
|
-
"""
|
|
398
|
-
vars = {k: v for k, v in self.__dict__.items() if k in self.attributes}
|
|
399
|
-
json_vars = AutoEncoder.encode(vars)
|
|
400
|
-
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
|