autonomous-app 0.3.0__tar.gz → 0.3.2__tar.gz
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_app-0.3.0 → autonomous_app-0.3.2}/PKG-INFO +2 -2
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/requirements.txt +1 -1
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/__init__.py +1 -1
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/ai/audioagent.py +1 -1
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/ai/imageagent.py +1 -1
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/ai/jsonagent.py +1 -1
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/ai/models/openai.py +81 -53
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/ai/oaiagent.py +1 -14
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/ai/textagent.py +1 -1
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/auth/autoauth.py +10 -10
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/auth/user.py +17 -2
- autonomous_app-0.3.2/src/autonomous/db/__init__.py +42 -0
- autonomous_app-0.3.2/src/autonomous/db/base/__init__.py +33 -0
- autonomous_app-0.3.2/src/autonomous/db/base/common.py +62 -0
- autonomous_app-0.3.2/src/autonomous/db/base/datastructures.py +476 -0
- autonomous_app-0.3.2/src/autonomous/db/base/document.py +1230 -0
- autonomous_app-0.3.2/src/autonomous/db/base/fields.py +767 -0
- autonomous_app-0.3.2/src/autonomous/db/base/metaclasses.py +468 -0
- autonomous_app-0.3.2/src/autonomous/db/base/utils.py +22 -0
- autonomous_app-0.3.2/src/autonomous/db/common.py +79 -0
- autonomous_app-0.3.2/src/autonomous/db/connection.py +472 -0
- autonomous_app-0.3.2/src/autonomous/db/context_managers.py +313 -0
- autonomous_app-0.3.2/src/autonomous/db/dereference.py +291 -0
- autonomous_app-0.3.2/src/autonomous/db/document.py +1141 -0
- autonomous_app-0.3.2/src/autonomous/db/errors.py +165 -0
- autonomous_app-0.3.2/src/autonomous/db/fields.py +2732 -0
- autonomous_app-0.3.2/src/autonomous/db/mongodb_support.py +24 -0
- autonomous_app-0.3.2/src/autonomous/db/pymongo_support.py +80 -0
- autonomous_app-0.3.2/src/autonomous/db/queryset/__init__.py +28 -0
- autonomous_app-0.3.2/src/autonomous/db/queryset/base.py +2033 -0
- autonomous_app-0.3.2/src/autonomous/db/queryset/field_list.py +88 -0
- autonomous_app-0.3.2/src/autonomous/db/queryset/manager.py +58 -0
- autonomous_app-0.3.2/src/autonomous/db/queryset/queryset.py +189 -0
- autonomous_app-0.3.2/src/autonomous/db/queryset/transform.py +527 -0
- autonomous_app-0.3.2/src/autonomous/db/queryset/visitor.py +189 -0
- autonomous_app-0.3.2/src/autonomous/db/signals.py +59 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/logger.py +3 -0
- autonomous_app-0.3.2/src/autonomous/model/autoattr.py +120 -0
- autonomous_app-0.3.2/src/autonomous/model/automodel.py +220 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/storage/imagestorage.py +49 -8
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous_app.egg-info/PKG-INFO +2 -2
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous_app.egg-info/SOURCES.txt +25 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous_app.egg-info/requires.txt +1 -1
- autonomous_app-0.3.0/src/autonomous/model/autoattr.py +0 -105
- autonomous_app-0.3.0/src/autonomous/model/automodel.py +0 -159
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/LICENSE +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/README.md +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/pyproject.toml +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/setup.cfg +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/setup.py +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/ai/__init__.py +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/ai/models/__init__.py +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/apis/version_control/GHCallbacks.py +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/apis/version_control/GHOrganization.py +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/apis/version_control/GHRepo.py +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/apis/version_control/GHVersionControl.py +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/apis/version_control/__init__.py +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/auth/__init__.py +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/auth/github.py +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/auth/google.py +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/cli.py +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/model/__init__.py +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/storage/__init__.py +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/storage/localstorage.py +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/tasks/__init__.py +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/tasks/autotask.py +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous/utils/markdown.py +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous_app.egg-info/dependency_links.txt +0 -0
- {autonomous_app-0.3.0 → autonomous_app-0.3.2}/src/autonomous_app.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: autonomous-app
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
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
|
|
@@ -6,7 +6,7 @@ from .models.openai import OpenAIModel
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class AudioAgent(AutoModel):
|
|
9
|
-
client = ReferenceAttr()
|
|
9
|
+
client = ReferenceAttr(choices=[OpenAIModel])
|
|
10
10
|
name = StringAttr(default="audioagent")
|
|
11
11
|
instructions = StringAttr(
|
|
12
12
|
default="You are highly skilled AI trained to assist with generating audio files."
|
|
@@ -5,7 +5,7 @@ from .models.openai import OpenAIModel
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class ImageAgent(AutoModel):
|
|
8
|
-
client = ReferenceAttr()
|
|
8
|
+
client = ReferenceAttr(choices=[OpenAIModel])
|
|
9
9
|
name = StringAttr(default="imageagent")
|
|
10
10
|
instructions = StringAttr(
|
|
11
11
|
default="You are highly skilled AI trained to assist with generating images."
|
|
@@ -7,7 +7,7 @@ from .models.openai import OpenAIModel
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class JSONAgent(AutoModel):
|
|
10
|
-
client = ReferenceAttr()
|
|
10
|
+
client = ReferenceAttr(choices=[OpenAIModel])
|
|
11
11
|
name = StringAttr(default="jsonagent")
|
|
12
12
|
instructions = StringAttr(
|
|
13
13
|
default="You are highly skilled AI trained to assist with generating JSON formatted data."
|
|
@@ -4,6 +4,7 @@ import random
|
|
|
4
4
|
import time
|
|
5
5
|
from base64 import b64decode
|
|
6
6
|
|
|
7
|
+
import openai
|
|
7
8
|
from openai import NotFoundError as openai_NotFoundError
|
|
8
9
|
from openai import OpenAI
|
|
9
10
|
|
|
@@ -14,9 +15,9 @@ from autonomous.model.automodel import AutoModel
|
|
|
14
15
|
|
|
15
16
|
class OpenAIModel(AutoModel):
|
|
16
17
|
_client = None
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
_text_model = "gpt-4o-mini"
|
|
19
|
+
_image_model = "dall-e-3"
|
|
20
|
+
_json_model = "gpt-4o"
|
|
20
21
|
agent_id = StringAttr()
|
|
21
22
|
messages = ListAttr(StringAttr(default=[]))
|
|
22
23
|
tools = DictAttr()
|
|
@@ -59,23 +60,25 @@ class OpenAIModel(AutoModel):
|
|
|
59
60
|
instructions=self.instructions,
|
|
60
61
|
description=self.description,
|
|
61
62
|
name=self.name,
|
|
62
|
-
model=self.
|
|
63
|
+
model=self._json_model,
|
|
63
64
|
)
|
|
64
65
|
self.agent_id = agent.id
|
|
65
66
|
log(f"==== Creating Agent with ID: {self.agent_id} ====")
|
|
66
67
|
self.save()
|
|
67
68
|
return self.agent_id
|
|
68
69
|
|
|
69
|
-
def clear_files(self, file_id=None):
|
|
70
|
+
def clear_files(self, file_id=None, all=False):
|
|
70
71
|
if not file_id:
|
|
71
72
|
store_files = self.client.files.list().data
|
|
72
|
-
|
|
73
|
-
self.client.files.delete(file_id=sf.id)
|
|
73
|
+
|
|
74
74
|
for vs in self.client.beta.vector_stores.list().data:
|
|
75
75
|
try:
|
|
76
76
|
self.client.beta.vector_stores.delete(vs.id)
|
|
77
77
|
except openai_NotFoundError:
|
|
78
78
|
log(f"==== Vector Store {vs.id} not found ====")
|
|
79
|
+
if all:
|
|
80
|
+
for sf in store_files:
|
|
81
|
+
self.client.files.delete(file_id=sf.id)
|
|
79
82
|
else:
|
|
80
83
|
self.client.files.delete(file_id=file_id)
|
|
81
84
|
self.tools.pop("file_search", None)
|
|
@@ -112,17 +115,18 @@ class OpenAIModel(AutoModel):
|
|
|
112
115
|
|
|
113
116
|
def _add_function(self, user_function):
|
|
114
117
|
user_function["strict"] = True
|
|
115
|
-
user_function["parameters"]["required"] = list(
|
|
116
|
-
user_function["parameters"]["properties"].keys()
|
|
117
|
-
)
|
|
118
118
|
user_function["parameters"]["additionalProperties"] = False
|
|
119
|
+
if not user_function["parameters"].get("required"):
|
|
120
|
+
user_function["parameters"]["required"] = list(
|
|
121
|
+
user_function["parameters"]["properties"].keys()
|
|
122
|
+
)
|
|
119
123
|
|
|
120
124
|
self.tools["function"] = {"type": "function", "function": user_function}
|
|
121
125
|
self.client.beta.assistants.update(
|
|
122
126
|
self._get_agent_id(), tools=list(self.tools.values())
|
|
123
127
|
)
|
|
124
128
|
return """
|
|
125
|
-
IMPORTANT: Always use the function 'response' tool to respond to the user with
|
|
129
|
+
IMPORTANT: Always use the function 'response' tool to respond to the user with only the requested JSON schema. DO NOT add any text to the response outside of the JSON schema.
|
|
126
130
|
|
|
127
131
|
"""
|
|
128
132
|
|
|
@@ -141,34 +145,64 @@ IMPORTANT: Always use the function 'response' tool to respond to the user with t
|
|
|
141
145
|
return message_list
|
|
142
146
|
|
|
143
147
|
def generate_json(self, messages, function, additional_instructions=""):
|
|
144
|
-
_instructions_addition = self._add_function(function)
|
|
145
|
-
|
|
148
|
+
# _instructions_addition = self._add_function(function)
|
|
149
|
+
function["strict"] = True
|
|
150
|
+
function["parameters"]["additionalProperties"] = False
|
|
151
|
+
function["parameters"]["required"] = list(
|
|
152
|
+
function["parameters"]["properties"].keys()
|
|
153
|
+
)
|
|
146
154
|
|
|
147
155
|
formatted_messages = self._format_messages(messages)
|
|
148
156
|
thread = self.client.beta.threads.create(messages=formatted_messages)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
157
|
+
# log(function, _print=True)
|
|
158
|
+
running_job = True
|
|
159
|
+
while running_job:
|
|
160
|
+
try:
|
|
161
|
+
run = self.client.beta.threads.runs.create_and_poll(
|
|
162
|
+
thread_id=thread.id,
|
|
163
|
+
assistant_id=self._get_agent_id(),
|
|
164
|
+
additional_instructions=additional_instructions,
|
|
165
|
+
parallel_tool_calls=False,
|
|
166
|
+
tools=[
|
|
167
|
+
{"type": "file_search"},
|
|
168
|
+
{"type": "function", "function": function},
|
|
169
|
+
],
|
|
170
|
+
tool_choice={
|
|
171
|
+
"type": "function",
|
|
172
|
+
"function": {"name": function["name"]},
|
|
173
|
+
},
|
|
174
|
+
)
|
|
175
|
+
log(f"==== Job Status: {run.status} ====", _print=True)
|
|
176
|
+
if run.status in [
|
|
177
|
+
"failed",
|
|
178
|
+
"expired",
|
|
179
|
+
"canceled",
|
|
180
|
+
"completed",
|
|
181
|
+
"incomplete",
|
|
182
|
+
"requires_action",
|
|
183
|
+
]:
|
|
184
|
+
running_job = False
|
|
185
|
+
|
|
186
|
+
except openai.error.BadRequestError as e:
|
|
187
|
+
# Handle specific bad request errors
|
|
188
|
+
error_message = e.json_body.get("error", {}).get("message", "")
|
|
189
|
+
if "already has an active run" in error_message:
|
|
190
|
+
log("Previous run is still active. Waiting...", _print=True)
|
|
191
|
+
time.sleep(2) # wait before retrying or checking run status
|
|
192
|
+
else:
|
|
193
|
+
raise e
|
|
194
|
+
|
|
195
|
+
# while run.status in ["queued", "in_progress"]:
|
|
196
|
+
# run = self.client.beta.threads.runs.retrieve(
|
|
197
|
+
# thread_id=thread.id,
|
|
198
|
+
# run_id=run.id,
|
|
199
|
+
# )
|
|
200
|
+
# time.sleep(0.5)
|
|
166
201
|
if run.status in ["failed", "expired", "canceled"]:
|
|
167
|
-
log(f"====
|
|
168
|
-
print(f"==== Error: {run.last_error} ====")
|
|
202
|
+
log(f"==== !!! ERROR !!!: {run.last_error} ====", _print=True)
|
|
169
203
|
return None
|
|
170
|
-
|
|
171
|
-
|
|
204
|
+
log("=================== RUN COMPLETED ===================", _print=True)
|
|
205
|
+
log(run.status, _print=True)
|
|
172
206
|
if run.status == "completed":
|
|
173
207
|
response = self.client.beta.threads.messages.list(thread_id=thread.id)
|
|
174
208
|
results = response.data[0].content[0].text.value
|
|
@@ -177,21 +211,18 @@ IMPORTANT: Always use the function 'response' tool to respond to the user with t
|
|
|
177
211
|
0
|
|
178
212
|
].function.arguments
|
|
179
213
|
else:
|
|
180
|
-
log(f"====Status: {run.status} Error: {run.last_error} ====")
|
|
181
|
-
print(f"====Status: {run.status} Error: {run.last_error} ====")
|
|
214
|
+
log(f"====Status: {run.status} Error: {run.last_error} ====", _print=True)
|
|
182
215
|
return None
|
|
183
216
|
|
|
184
217
|
results = results[results.find("{") : results.rfind("}") + 1]
|
|
185
218
|
try:
|
|
186
219
|
results = json.loads(results, strict=False)
|
|
187
220
|
except Exception:
|
|
188
|
-
print(f"==== Invalid JSON:\n{results}")
|
|
189
|
-
log(f"==== Invalid JSON:\n{results}")
|
|
221
|
+
print(f"==== Invalid JSON:\n{results}", _print=True)
|
|
190
222
|
return {}
|
|
191
223
|
else:
|
|
192
|
-
log(f"==== Results: {results}
|
|
193
|
-
|
|
194
|
-
print("=================== END REPORT ===================")
|
|
224
|
+
log(f"==== Results: {results}", _print=True)
|
|
225
|
+
log("=================== END REPORT ===================", _print=True)
|
|
195
226
|
return results
|
|
196
227
|
|
|
197
228
|
def generate_text(self, messages, additional_instructions=""):
|
|
@@ -211,25 +242,22 @@ IMPORTANT: Always use the function 'response' tool to respond to the user with t
|
|
|
211
242
|
run_id=run.id,
|
|
212
243
|
)
|
|
213
244
|
time.sleep(0.5)
|
|
214
|
-
log(f"==== Job Status: {run.status} ====")
|
|
215
|
-
print(f"==== Job Status: {run.status} ====")
|
|
245
|
+
log(f"==== Job Status: {run.status} ====", _print=True)
|
|
216
246
|
|
|
217
247
|
if run.status in ["failed", "expired", "canceled"]:
|
|
218
|
-
log(f"==== Error: {run.last_error} ====")
|
|
219
|
-
print(f"==== Error: {run.last_error} ====")
|
|
248
|
+
log(f"==== Error: {run.last_error} ====", _print=True)
|
|
220
249
|
return None
|
|
221
|
-
|
|
222
|
-
|
|
250
|
+
log("=================== RUN COMPLETED ===================", _print=True)
|
|
251
|
+
log(run.status, _print=True)
|
|
223
252
|
if run.status == "completed":
|
|
224
253
|
response = self.client.beta.threads.messages.list(thread_id=thread.id)
|
|
225
254
|
results = response.data[0].content[0].text.value
|
|
226
255
|
else:
|
|
227
|
-
log(f"====Status: {run.status} Error: {run.last_error} ====")
|
|
228
|
-
print(f"====Status: {run.status} Error: {run.last_error} ====")
|
|
256
|
+
log(f"====Status: {run.status} Error: {run.last_error} ====", _print=True)
|
|
229
257
|
return None
|
|
230
258
|
|
|
231
|
-
|
|
232
|
-
|
|
259
|
+
log(results, _print=True)
|
|
260
|
+
log("=================== END REPORT ===================", _print=True)
|
|
233
261
|
return results
|
|
234
262
|
|
|
235
263
|
def generate_audio(self, prompt, file_path, **kwargs):
|
|
@@ -248,7 +276,7 @@ IMPORTANT: Always use the function 'response' tool to respond to the user with t
|
|
|
248
276
|
image = None
|
|
249
277
|
try:
|
|
250
278
|
response = self.client.images.generate(
|
|
251
|
-
model=self.
|
|
279
|
+
model=self._image_model,
|
|
252
280
|
prompt=prompt,
|
|
253
281
|
response_format="b64_json",
|
|
254
282
|
**kwargs,
|
|
@@ -269,7 +297,7 @@ IMPORTANT: Always use the function 'response' tool to respond to the user with t
|
|
|
269
297
|
{"role": "user", "content": text},
|
|
270
298
|
]
|
|
271
299
|
response = self.client.chat.completions.create(
|
|
272
|
-
model=
|
|
300
|
+
model=self._text_model, messages=message
|
|
273
301
|
)
|
|
274
302
|
try:
|
|
275
303
|
result = response.choices[0].message.content
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
import io
|
|
2
|
-
import json
|
|
3
|
-
import os
|
|
4
|
-
import random
|
|
5
|
-
import time
|
|
6
|
-
from base64 import b64decode
|
|
7
|
-
|
|
8
|
-
from openai import OpenAI
|
|
9
|
-
from pydantic import model_validator
|
|
10
|
-
|
|
11
1
|
from autonomous import log
|
|
12
2
|
from autonomous.model.autoattr import ReferenceAttr
|
|
13
3
|
from autonomous.model.automodel import AutoModel
|
|
@@ -16,7 +6,7 @@ from .models.openai import OpenAIModel
|
|
|
16
6
|
|
|
17
7
|
|
|
18
8
|
class OAIAgent(AutoModel):
|
|
19
|
-
client_model = ReferenceAttr()
|
|
9
|
+
client_model = ReferenceAttr(choices=[OpenAIModel])
|
|
20
10
|
_ai_model = OpenAIModel
|
|
21
11
|
|
|
22
12
|
@property
|
|
@@ -26,9 +16,6 @@ class OAIAgent(AutoModel):
|
|
|
26
16
|
self.save()
|
|
27
17
|
return self.client_model
|
|
28
18
|
|
|
29
|
-
def __init__(self, **kwargs):
|
|
30
|
-
self._client = OpenAI(api_key=os.environ.get("OPENAI_KEY"))
|
|
31
|
-
|
|
32
19
|
def clear_files(self, file_id=None):
|
|
33
20
|
return self.client.clear_files(file_id)
|
|
34
21
|
|
|
@@ -6,7 +6,7 @@ from .models.openai import OpenAIModel
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class TextAgent(AutoModel):
|
|
9
|
-
client = ReferenceAttr()
|
|
9
|
+
client = ReferenceAttr(choices=[OpenAIModel])
|
|
10
10
|
name = StringAttr(default="textagent")
|
|
11
11
|
instructions = StringAttr(
|
|
12
12
|
default="You are highly skilled AI trained to assist with generating text according to the given requirements."
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import uuid
|
|
2
3
|
from datetime import datetime
|
|
3
4
|
from functools import wraps
|
|
@@ -7,11 +8,11 @@ from authlib.integrations.requests_client import OAuth2Auth, OAuth2Session
|
|
|
7
8
|
from flask import redirect, session, url_for
|
|
8
9
|
|
|
9
10
|
from autonomous import log
|
|
10
|
-
from autonomous.auth.user import
|
|
11
|
+
from autonomous.auth.user import User
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class AutoAuth:
|
|
14
|
-
user_class: type[
|
|
15
|
+
user_class: type[User] = User
|
|
15
16
|
|
|
16
17
|
def __init__(
|
|
17
18
|
self,
|
|
@@ -46,13 +47,12 @@ class AutoAuth:
|
|
|
46
47
|
"""
|
|
47
48
|
Returns the current user.
|
|
48
49
|
"""
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
log(e, session["user"])
|
|
50
|
+
|
|
51
|
+
user = (
|
|
52
|
+
cls.user_class.from_json(session["user"]) if session.get("user") else None
|
|
53
|
+
)
|
|
54
|
+
if not user or user.state != "authenticated":
|
|
55
|
+
user = cls.user_class.get_guest()
|
|
56
56
|
return user
|
|
57
57
|
|
|
58
58
|
def authenticate(self):
|
|
@@ -106,7 +106,7 @@ class AutoAuth:
|
|
|
106
106
|
user.last_login = datetime.now()
|
|
107
107
|
# log(user)
|
|
108
108
|
user.save()
|
|
109
|
-
session["user"] = user.
|
|
109
|
+
session["user"] = user.to_json()
|
|
110
110
|
# log(guest, user.is_guest)
|
|
111
111
|
if not guest and user.is_guest:
|
|
112
112
|
return redirect(url_for("auth.login"))
|
|
@@ -14,6 +14,11 @@ class AutoUser(AutoModel):
|
|
|
14
14
|
This class represents a user who can authenticate using OpenID.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
+
meta = {
|
|
18
|
+
"abstract": True,
|
|
19
|
+
"allow_inheritance": True,
|
|
20
|
+
"strict": False,
|
|
21
|
+
}
|
|
17
22
|
name = StringAttr(default="Anonymous")
|
|
18
23
|
email = StringAttr(required=True)
|
|
19
24
|
last_login = DateTimeAttr(default=datetime.now)
|
|
@@ -34,9 +39,10 @@ class AutoUser(AutoModel):
|
|
|
34
39
|
email = user_info["email"].strip()
|
|
35
40
|
name = user_info["name"].strip()
|
|
36
41
|
user = cls.find(email=email)
|
|
42
|
+
log(email, user)
|
|
37
43
|
if not user:
|
|
38
44
|
log(f"Creating new user for {email}")
|
|
39
|
-
user = cls(name=name, email=email)
|
|
45
|
+
# user = cls(name=name, email=email)
|
|
40
46
|
|
|
41
47
|
# parse user_info into a user object
|
|
42
48
|
user.name = name
|
|
@@ -51,7 +57,7 @@ class AutoUser(AutoModel):
|
|
|
51
57
|
"""
|
|
52
58
|
Returns a guest user.
|
|
53
59
|
"""
|
|
54
|
-
guest = cls.find(name="_GuestOfAutonomous_"
|
|
60
|
+
guest = cls.find(name="_GuestOfAutonomous_")
|
|
55
61
|
if not guest:
|
|
56
62
|
guest = cls(
|
|
57
63
|
name="_GuestOfAutonomous_",
|
|
@@ -82,3 +88,12 @@ class AutoUser(AutoModel):
|
|
|
82
88
|
Returns True if the user is a guest, False otherwise.
|
|
83
89
|
"""
|
|
84
90
|
return self.is_authenticated and self.role == "admin"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class User(AutoUser):
|
|
94
|
+
"""
|
|
95
|
+
This class represents a user who can authenticate using OpenID.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def __repr__(self):
|
|
99
|
+
return f"<User {self.pk} {self.email}>"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Import submodules so that we can expose their __all__
|
|
2
|
+
# Import everything from each submodule so that it can be accessed via
|
|
3
|
+
# autonomous.db, e.g. instead of `from autonomous.db.connection import connect`,
|
|
4
|
+
# users can simply use `from autonomous.db import connect`, or even
|
|
5
|
+
# `from autonomous.db import *` and then `connect('testdb')`.
|
|
6
|
+
from autonomous.db import (
|
|
7
|
+
connection,
|
|
8
|
+
document,
|
|
9
|
+
errors,
|
|
10
|
+
fields,
|
|
11
|
+
queryset,
|
|
12
|
+
signals,
|
|
13
|
+
)
|
|
14
|
+
from autonomous.db.connection import * # noqa: F401
|
|
15
|
+
from autonomous.db.document import * # noqa: F401
|
|
16
|
+
from autonomous.db.errors import * # noqa: F401
|
|
17
|
+
from autonomous.db.fields import * # noqa: F401
|
|
18
|
+
from autonomous.db.queryset import * # noqa: F401
|
|
19
|
+
from autonomous.db.signals import * # noqa: F401
|
|
20
|
+
|
|
21
|
+
__all__ = (
|
|
22
|
+
list(document.__all__)
|
|
23
|
+
+ list(fields.__all__)
|
|
24
|
+
+ list(connection.__all__)
|
|
25
|
+
+ list(queryset.__all__)
|
|
26
|
+
+ list(signals.__all__)
|
|
27
|
+
+ list(errors.__all__)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
VERSION = (0, 29, 0)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_version():
|
|
35
|
+
"""Return the VERSION as a string.
|
|
36
|
+
|
|
37
|
+
For example, if `VERSION == (0, 10, 7)`, return '0.10.7'.
|
|
38
|
+
"""
|
|
39
|
+
return ".".join(map(str, VERSION))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
__version__ = get_version()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Base module is split into several files for convenience. Files inside of
|
|
2
|
+
# this module should import from a specific submodule (e.g.
|
|
3
|
+
# `from autonomous.db.base.document import BaseDocument`), but all of the
|
|
4
|
+
# other modules should import directly from the top-level module (e.g.
|
|
5
|
+
# `from autonomous.db.base import BaseDocument`). This approach is cleaner and
|
|
6
|
+
# also helps with cyclical import errors.
|
|
7
|
+
from autonomous.db.base.common import *
|
|
8
|
+
from autonomous.db.base.datastructures import *
|
|
9
|
+
from autonomous.db.base.document import *
|
|
10
|
+
from autonomous.db.base.fields import *
|
|
11
|
+
from autonomous.db.base.metaclasses import *
|
|
12
|
+
|
|
13
|
+
__all__ = (
|
|
14
|
+
# common
|
|
15
|
+
"UPDATE_OPERATORS",
|
|
16
|
+
"_document_registry",
|
|
17
|
+
"get_document",
|
|
18
|
+
# datastructures
|
|
19
|
+
"BaseDict",
|
|
20
|
+
"BaseList",
|
|
21
|
+
"EmbeddedDocumentList",
|
|
22
|
+
"LazyReference",
|
|
23
|
+
# document
|
|
24
|
+
"BaseDocument",
|
|
25
|
+
# fields
|
|
26
|
+
"BaseField",
|
|
27
|
+
"ComplexBaseField",
|
|
28
|
+
"ObjectIdField",
|
|
29
|
+
"GeoJsonBaseField",
|
|
30
|
+
# metaclasses
|
|
31
|
+
"DocumentMetaclass",
|
|
32
|
+
"TopLevelDocumentMetaclass",
|
|
33
|
+
)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from autonomous.db.errors import NotRegistered
|
|
2
|
+
|
|
3
|
+
__all__ = ("UPDATE_OPERATORS", "get_document", "_document_registry")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
UPDATE_OPERATORS = {
|
|
7
|
+
"set",
|
|
8
|
+
"unset",
|
|
9
|
+
"inc",
|
|
10
|
+
"dec",
|
|
11
|
+
"mul",
|
|
12
|
+
"pop",
|
|
13
|
+
"push",
|
|
14
|
+
"push_all",
|
|
15
|
+
"pull",
|
|
16
|
+
"pull_all",
|
|
17
|
+
"add_to_set",
|
|
18
|
+
"set_on_insert",
|
|
19
|
+
"min",
|
|
20
|
+
"max",
|
|
21
|
+
"rename",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
_document_registry = {}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_document(name):
|
|
29
|
+
"""Get a registered Document class by name."""
|
|
30
|
+
doc = _document_registry.get(name, None)
|
|
31
|
+
if not doc:
|
|
32
|
+
# Possible old style name
|
|
33
|
+
single_end = name.split(".")[-1]
|
|
34
|
+
compound_end = ".%s" % single_end
|
|
35
|
+
possible_match = [
|
|
36
|
+
k for k in _document_registry if k.endswith(compound_end) or k == single_end
|
|
37
|
+
]
|
|
38
|
+
if len(possible_match) == 1:
|
|
39
|
+
doc = _document_registry.get(possible_match.pop(), None)
|
|
40
|
+
if not doc:
|
|
41
|
+
raise NotRegistered(
|
|
42
|
+
"""
|
|
43
|
+
`%s` has not been registered in the document registry.
|
|
44
|
+
Importing the document class automatically registers it, has it
|
|
45
|
+
been imported?
|
|
46
|
+
""".strip()
|
|
47
|
+
% name
|
|
48
|
+
)
|
|
49
|
+
return doc
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _get_documents_by_db(connection_alias, default_connection_alias):
|
|
53
|
+
"""Get all registered Documents class attached to a given database"""
|
|
54
|
+
|
|
55
|
+
def get_doc_alias(doc_cls):
|
|
56
|
+
return doc_cls._meta.get("db_alias", default_connection_alias)
|
|
57
|
+
|
|
58
|
+
return [
|
|
59
|
+
doc_cls
|
|
60
|
+
for doc_cls in _document_registry.values()
|
|
61
|
+
if get_doc_alias(doc_cls) == connection_alias
|
|
62
|
+
]
|