arpakitlib 1.7.66__py3-none-any.whl → 1.7.124__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.
- arpakitlib/_arpakit_project_template/example.env +4 -0
- arpakitlib/_arpakit_project_template/manage/hello_world.py +2 -2
- arpakitlib/_arpakit_project_template/manage/json_beutify.py +4 -4
- arpakitlib/_arpakit_project_template/manage/poetry_config.sh +2 -0
- arpakitlib/_arpakit_project_template/manage/poetry_self_add_plugin_export.sh +2 -0
- arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_1.py +4 -4
- arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_2.py +4 -4
- arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_3.py +4 -4
- arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_4.py +4 -4
- arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_5.py +4 -4
- arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_6.py +4 -4
- arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_7.py +4 -4
- arpakitlib/_arpakit_project_template/resource/static/healthcheck +1 -0
- arpakitlib/_arpakit_project_template/src/admin1/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/src/admin1/add_admin_in_app.py +25 -0
- arpakitlib/_arpakit_project_template/src/admin1/admin_auth.py +29 -0
- arpakitlib/_arpakit_project_template/src/admin1/model_view.py +19 -0
- arpakitlib/_arpakit_project_template/src/api/auth.py +1 -6
- arpakitlib/_arpakit_project_template/src/api/create_api_app.py +13 -52
- arpakitlib/_arpakit_project_template/src/api/event.py +51 -2
- arpakitlib/_arpakit_project_template/src/api/router/main_router.py +3 -0
- arpakitlib/_arpakit_project_template/src/api/router/v1/get_api_error_info.py +6 -4
- arpakitlib/_arpakit_project_template/src/api/start_api_for_dev_with_reload.py +12 -4
- arpakitlib/_arpakit_project_template/src/api/{start_api_for_dev.py → start_api_for_dev_without_reload.py} +3 -3
- arpakitlib/_arpakit_project_template/src/api/transmitted_api_data.py +4 -0
- arpakitlib/_arpakit_project_template/src/business_service/hello_world.py +12 -0
- arpakitlib/_arpakit_project_template/{manage/logging_check.py → src/core/check_logging.py} +3 -3
- arpakitlib/_arpakit_project_template/src/core/check_settings.py +12 -0
- arpakitlib/_arpakit_project_template/src/core/generate_settings_env_example.py +16 -0
- arpakitlib/_arpakit_project_template/src/core/settings.py +13 -5
- arpakitlib/_arpakit_project_template/src/core/util.py +0 -18
- arpakitlib/_arpakit_project_template/{manage/sqlalchemy_db_check_conn.py → src/db/check_conn_sqlalchemy_db.py} +2 -1
- arpakitlib/_arpakit_project_template/src/db/const.py +0 -0
- arpakitlib/_arpakit_project_template/src/db/init_sqlalchemy_db.py +11 -0
- arpakitlib/_arpakit_project_template/src/db/reinit_sqlalchemy_db.py +13 -0
- arpakitlib/_arpakit_project_template/src/db/util.py +21 -0
- arpakitlib/_arpakit_project_template/src/operation_execution/operation_executor.py +6 -4
- arpakitlib/_arpakit_project_template/src/operation_execution/scheduled_operations.py +3 -3
- arpakitlib/_arpakit_project_template/src/operation_execution/{start_operation_executor_worker_for_dev.py → start_operation_executor_worker.py} +7 -5
- arpakitlib/_arpakit_project_template/src/operation_execution/start_scheduled_operation_creator_worker.py +19 -0
- arpakitlib/_arpakit_project_template/src/operation_execution/util.py +0 -0
- arpakitlib/_arpakit_project_template/src/tg_bot/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/src/tg_bot/router/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/src/tg_bot/start_tg_bot.py +0 -0
- arpakitlib/api_key_util.py +12 -0
- arpakitlib/ar_additional_model_util.py +18 -1
- arpakitlib/ar_arpakit_lib_module_util.py +13 -1
- arpakitlib/ar_arpakit_schedule_uust_api_client_util.py +24 -3
- arpakitlib/ar_arpakitlib_cli_util.py +2 -0
- arpakitlib/ar_base_worker_util.py +67 -18
- arpakitlib/ar_exception_util.py +13 -0
- arpakitlib/ar_fastapi_util.py +129 -98
- arpakitlib/ar_file_util.py +2 -0
- arpakitlib/ar_func_util.py +55 -0
- arpakitlib/ar_json_util.py +11 -9
- arpakitlib/ar_need_type_util.py +8 -1
- arpakitlib/ar_openai_api_client_util.py +16 -2
- arpakitlib/ar_operation_execution_util.py +143 -141
- arpakitlib/ar_schedule_uust_api_client_util.py +13 -6
- arpakitlib/ar_settings_util.py +24 -5
- arpakitlib/ar_sqlalchemy_model_util.py +37 -7
- arpakitlib/ar_ssh_runner_util.py +2 -2
- arpakitlib/ar_str_util.py +30 -1
- arpakitlib/ar_type_util.py +52 -7
- {arpakitlib-1.7.66.dist-info → arpakitlib-1.7.124.dist-info}/METADATA +27 -20
- {arpakitlib-1.7.66.dist-info → arpakitlib-1.7.124.dist-info}/RECORD +70 -62
- {arpakitlib-1.7.66.dist-info → arpakitlib-1.7.124.dist-info}/WHEEL +1 -1
- arpakitlib/_arpakit_project_template/AUTHOR.md +0 -4
- arpakitlib/_arpakit_project_template/manage/example_nginx_proxy.nginx +0 -14
- arpakitlib/_arpakit_project_template/manage/example_poetry_arpakitlib.sh +0 -1
- arpakitlib/_arpakit_project_template/manage/example_pyproject.toml +0 -18
- arpakitlib/_arpakit_project_template/manage/example_systemd.service +0 -12
- arpakitlib/_arpakit_project_template/manage/requirements.txt +0 -209
- arpakitlib/_arpakit_project_template/manage/settings_check.py +0 -10
- arpakitlib/_arpakit_project_template/manage/settings_generate_env_example.py +0 -13
- arpakitlib/_arpakit_project_template/manage/sqlalchemy_db_init.py +0 -10
- arpakitlib/_arpakit_project_template/manage/sqlalchemy_db_reinit.py +0 -10
- arpakitlib/_arpakit_project_template/src/operation_execution/start_scheduled_operation_creator_worker_for_dev.py +0 -16
- {arpakitlib-1.7.66.dist-info → arpakitlib-1.7.124.dist-info}/LICENSE +0 -0
- {arpakitlib-1.7.66.dist-info → arpakitlib-1.7.124.dist-info}/NOTICE +0 -0
- {arpakitlib-1.7.66.dist-info → arpakitlib-1.7.124.dist-info}/entry_points.txt +0 -0
@@ -11,6 +11,7 @@ from typing import Any, Callable
|
|
11
11
|
from pydantic import ConfigDict
|
12
12
|
from pydantic.v1 import BaseModel
|
13
13
|
from sqlalchemy import asc
|
14
|
+
from sqlalchemy.exc import NoResultFound
|
14
15
|
from sqlalchemy.orm import Session
|
15
16
|
|
16
17
|
from arpakitlib.ar_base_worker_util import BaseWorker
|
@@ -30,7 +31,8 @@ def get_operation_for_execution(
|
|
30
31
|
*,
|
31
32
|
session: Session | None = None,
|
32
33
|
sqlalchemy_db: SQLAlchemyDB | None = None,
|
33
|
-
filter_operation_types: list[str] | str | None = None
|
34
|
+
filter_operation_types: list[str] | str | None = None,
|
35
|
+
lock: bool = False
|
34
36
|
) -> OperationDBM | None:
|
35
37
|
if isinstance(filter_operation_types, str):
|
36
38
|
filter_operation_types = [filter_operation_types]
|
@@ -43,6 +45,10 @@ def get_operation_for_execution(
|
|
43
45
|
)
|
44
46
|
if filter_operation_types:
|
45
47
|
query = query.filter(OperationDBM.type.in_(filter_operation_types))
|
48
|
+
|
49
|
+
if lock:
|
50
|
+
query = query.with_for_update()
|
51
|
+
|
46
52
|
query = query.order_by(asc(OperationDBM.creation_dt))
|
47
53
|
operation_dbm: OperationDBM | None = query.first()
|
48
54
|
return operation_dbm
|
@@ -61,7 +67,8 @@ def get_operation_by_id(
|
|
61
67
|
session: Session | None = None,
|
62
68
|
sqlalchemy_db: SQLAlchemyDB | None = None,
|
63
69
|
filter_operation_id: int,
|
64
|
-
raise_if_not_found: bool = False
|
70
|
+
raise_if_not_found: bool = False,
|
71
|
+
lock: bool = False
|
65
72
|
) -> OperationDBM | None:
|
66
73
|
def func(session_: Session):
|
67
74
|
query = (
|
@@ -69,8 +76,16 @@ def get_operation_by_id(
|
|
69
76
|
.query(OperationDBM)
|
70
77
|
.filter(OperationDBM.id == filter_operation_id)
|
71
78
|
)
|
79
|
+
|
80
|
+
if lock:
|
81
|
+
query = query.with_for_update()
|
82
|
+
|
72
83
|
if raise_if_not_found:
|
73
|
-
|
84
|
+
try:
|
85
|
+
return query.one()
|
86
|
+
except NoResultFound:
|
87
|
+
if raise_if_not_found:
|
88
|
+
raise ValueError("Operation not found")
|
74
89
|
else:
|
75
90
|
return query.one_or_none()
|
76
91
|
|
@@ -130,21 +145,17 @@ class BaseOperationExecutor:
|
|
130
145
|
self._logger = logging.getLogger(self.__class__.__name__)
|
131
146
|
self.sql_alchemy_db = sqlalchemy_db
|
132
147
|
|
133
|
-
def sync_execute_operation(self, operation_dbm: OperationDBM) -> OperationDBM:
|
148
|
+
def sync_execute_operation(self, operation_dbm: OperationDBM, session: Session) -> OperationDBM:
|
134
149
|
if operation_dbm.type == BaseOperationTypes.healthcheck_:
|
135
150
|
self._logger.info("healthcheck")
|
136
151
|
elif operation_dbm.type == BaseOperationTypes.raise_fake_exception_:
|
137
152
|
self._logger.info("raise_fake_exception")
|
138
153
|
raise Exception("raise_fake_exception")
|
139
|
-
else:
|
140
|
-
raise Exception(
|
141
|
-
f"unknown operation_dbm.type,"
|
142
|
-
f" operation_dbm.id={operation_dbm.id},"
|
143
|
-
f" operation_dbm.type={operation_dbm.type}"
|
144
|
-
)
|
145
154
|
return operation_dbm
|
146
155
|
|
147
|
-
def sync_safe_execute_operation(
|
156
|
+
def sync_safe_execute_operation(
|
157
|
+
self, operation_dbm: OperationDBM, worker: OperationExecutorWorker, session: Session
|
158
|
+
) -> OperationDBM:
|
148
159
|
self._logger.info(
|
149
160
|
f"start "
|
150
161
|
f"operation_dbm.id={operation_dbm.id}, "
|
@@ -152,20 +163,21 @@ class BaseOperationExecutor:
|
|
152
163
|
f"operation_dbm.status={operation_dbm.status}"
|
153
164
|
)
|
154
165
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
166
|
+
operation_dbm.execution_start_dt = now_utc_dt()
|
167
|
+
operation_dbm.status = OperationDBM.Statuses.executing
|
168
|
+
operation_dbm.output_data = combine_dicts(
|
169
|
+
operation_dbm.output_data,
|
170
|
+
{
|
171
|
+
worker.worker_fullname: True
|
172
|
+
}
|
173
|
+
)
|
174
|
+
session.commit()
|
163
175
|
|
164
176
|
exception: BaseException | None = None
|
165
177
|
traceback_str: str | None = None
|
166
178
|
|
167
179
|
try:
|
168
|
-
self.sync_execute_operation(operation_dbm=operation_dbm)
|
180
|
+
self.sync_execute_operation(operation_dbm=operation_dbm, session=session)
|
169
181
|
except BaseException as exception_:
|
170
182
|
self._logger.error(
|
171
183
|
f"error in sync_execute_operation (id={operation_dbm.id}, type={operation_dbm.type})",
|
@@ -174,40 +186,34 @@ class BaseOperationExecutor:
|
|
174
186
|
exception = exception_
|
175
187
|
traceback_str = traceback.format_exc()
|
176
188
|
|
177
|
-
|
178
|
-
|
179
|
-
operation_dbm
|
180
|
-
|
189
|
+
operation_dbm.execution_finish_dt = now_utc_dt()
|
190
|
+
if exception:
|
191
|
+
operation_dbm.status = OperationDBM.Statuses.executed_with_error
|
192
|
+
operation_dbm.error_data = combine_dicts(
|
193
|
+
{
|
194
|
+
"exception_str": str(exception),
|
195
|
+
"traceback_str": traceback_str
|
196
|
+
},
|
197
|
+
operation_dbm.error_data
|
181
198
|
)
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
199
|
+
else:
|
200
|
+
operation_dbm.status = OperationDBM.Statuses.executed_without_error
|
201
|
+
session.commit()
|
202
|
+
|
203
|
+
if exception:
|
204
|
+
story_log_dbm = StoryLogDBM(
|
205
|
+
level=StoryLogDBM.Levels.error,
|
206
|
+
title=f"error in sync_execute_operation (id={operation_dbm.id}, type={operation_dbm.type})",
|
207
|
+
data={
|
208
|
+
"operation_id": operation_dbm.id,
|
209
|
+
"exception_str": str(exception),
|
210
|
+
"traceback_str": traceback_str
|
211
|
+
}
|
212
|
+
)
|
213
|
+
session.add(story_log_dbm)
|
194
214
|
session.commit()
|
195
215
|
|
196
|
-
|
197
|
-
story_log_dbm = StoryLogDBM(
|
198
|
-
level=StoryLogDBM.Levels.error,
|
199
|
-
title=f"error in sync_execute_operation (id={operation_dbm.id}, type={operation_dbm.type})",
|
200
|
-
data={
|
201
|
-
"operation_id": operation_dbm.id,
|
202
|
-
"exception_str": str(exception),
|
203
|
-
"traceback_str": traceback_str
|
204
|
-
}
|
205
|
-
)
|
206
|
-
session.add(story_log_dbm)
|
207
|
-
session.commit()
|
208
|
-
session.refresh(story_log_dbm)
|
209
|
-
|
210
|
-
session.refresh(operation_dbm)
|
216
|
+
session.refresh(operation_dbm)
|
211
217
|
|
212
218
|
self._logger.info(
|
213
219
|
f"finish sync_safe_execute_operation, "
|
@@ -219,21 +225,17 @@ class BaseOperationExecutor:
|
|
219
225
|
|
220
226
|
return operation_dbm
|
221
227
|
|
222
|
-
async def async_execute_operation(self, operation_dbm: OperationDBM) -> OperationDBM:
|
228
|
+
async def async_execute_operation(self, operation_dbm: OperationDBM, session: Session) -> OperationDBM:
|
223
229
|
if operation_dbm.type == BaseOperationTypes.healthcheck_:
|
224
230
|
self._logger.info("healthcheck")
|
225
231
|
elif operation_dbm.type == BaseOperationTypes.raise_fake_exception_:
|
226
232
|
self._logger.info("raise_fake_exception")
|
227
233
|
raise Exception("raise_fake_exception")
|
228
|
-
else:
|
229
|
-
raise Exception(
|
230
|
-
f"unknown operation_dbm.type,"
|
231
|
-
f" operation_dbm.id={operation_dbm.id},"
|
232
|
-
f" operation_dbm.type={operation_dbm.type}"
|
233
|
-
)
|
234
234
|
return operation_dbm
|
235
235
|
|
236
|
-
async def async_safe_execute_operation(
|
236
|
+
async def async_safe_execute_operation(
|
237
|
+
self, operation_dbm: OperationDBM, worker: OperationExecutorWorker, session: Session
|
238
|
+
) -> OperationDBM:
|
237
239
|
self._logger.info(
|
238
240
|
f"start "
|
239
241
|
f"operation_dbm.id={operation_dbm.id}, "
|
@@ -241,20 +243,21 @@ class BaseOperationExecutor:
|
|
241
243
|
f"operation_dbm.status={operation_dbm.status}"
|
242
244
|
)
|
243
245
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
246
|
+
operation_dbm.execution_start_dt = now_utc_dt()
|
247
|
+
operation_dbm.status = OperationDBM.Statuses.executing
|
248
|
+
operation_dbm.output_data = combine_dicts(
|
249
|
+
operation_dbm.output_data,
|
250
|
+
{
|
251
|
+
worker.worker_fullname: True
|
252
|
+
}
|
253
|
+
)
|
254
|
+
session.commit()
|
252
255
|
|
253
256
|
exception: BaseException | None = None
|
254
257
|
traceback_str: str | None = None
|
255
258
|
|
256
259
|
try:
|
257
|
-
await self.async_execute_operation(operation_dbm=operation_dbm)
|
260
|
+
await self.async_execute_operation(operation_dbm=operation_dbm, session=session)
|
258
261
|
except BaseException as exception_:
|
259
262
|
self._logger.error(
|
260
263
|
f"error in async_execute_operation (id={operation_dbm.id}, type={operation_dbm.type})",
|
@@ -263,40 +266,34 @@ class BaseOperationExecutor:
|
|
263
266
|
exception = exception_
|
264
267
|
traceback_str = traceback.format_exc()
|
265
268
|
|
266
|
-
|
267
|
-
|
268
|
-
operation_dbm
|
269
|
-
|
269
|
+
operation_dbm.execution_finish_dt = now_utc_dt()
|
270
|
+
if exception:
|
271
|
+
operation_dbm.status = OperationDBM.Statuses.executed_with_error
|
272
|
+
operation_dbm.error_data = combine_dicts(
|
273
|
+
{
|
274
|
+
"exception_str": str(exception),
|
275
|
+
"traceback_str": traceback_str
|
276
|
+
},
|
277
|
+
operation_dbm.error_data
|
270
278
|
)
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
279
|
+
else:
|
280
|
+
operation_dbm.status = OperationDBM.Statuses.executed_without_error
|
281
|
+
session.commit()
|
282
|
+
|
283
|
+
if exception:
|
284
|
+
story_log_dbm = StoryLogDBM(
|
285
|
+
level=StoryLogDBM.Levels.error,
|
286
|
+
title=f"error in async_execute_operation (id={operation_dbm.id}, type={operation_dbm.type})",
|
287
|
+
data={
|
288
|
+
"operation_id": operation_dbm.id,
|
289
|
+
"exception_str": str(exception),
|
290
|
+
"traceback_str": traceback_str
|
291
|
+
}
|
292
|
+
)
|
293
|
+
session.add(story_log_dbm)
|
283
294
|
session.commit()
|
284
295
|
|
285
|
-
|
286
|
-
story_log_dbm = StoryLogDBM(
|
287
|
-
level=StoryLogDBM.Levels.error,
|
288
|
-
title=f"error in async_execute_operation (id={operation_dbm.id}, type={operation_dbm.type})",
|
289
|
-
data={
|
290
|
-
"operation_id": operation_dbm.id,
|
291
|
-
"exception_str": str(exception),
|
292
|
-
"traceback_str": traceback_str
|
293
|
-
}
|
294
|
-
)
|
295
|
-
session.add(story_log_dbm)
|
296
|
-
session.commit()
|
297
|
-
session.refresh(story_log_dbm)
|
298
|
-
|
299
|
-
session.refresh(operation_dbm)
|
296
|
+
session.refresh(operation_dbm)
|
300
297
|
|
301
298
|
self._logger.info(
|
302
299
|
f"finish async_safe_execute_operation, "
|
@@ -317,53 +314,56 @@ class OperationExecutorWorker(BaseWorker):
|
|
317
314
|
sqlalchemy_db: SQLAlchemyDB,
|
318
315
|
operation_executor: BaseOperationExecutor | None = None,
|
319
316
|
filter_operation_types: str | list[str] | None = None,
|
320
|
-
|
321
|
-
timeout_after_err_in_run=timedelta(seconds=1).total_seconds()
|
317
|
+
startup_funcs: list[Any] | None = None
|
322
318
|
):
|
323
|
-
super().__init__()
|
319
|
+
super().__init__(startup_funcs=startup_funcs)
|
324
320
|
self.sqlalchemy_db = sqlalchemy_db
|
325
|
-
self.timeout_after_run = timeout_after_run
|
326
|
-
self.timeout_after_err_in_run = timeout_after_err_in_run
|
327
321
|
if operation_executor is None:
|
328
322
|
operation_executor = BaseOperationExecutor(sqlalchemy_db=sqlalchemy_db)
|
329
323
|
self.operation_executor = operation_executor
|
324
|
+
if isinstance(filter_operation_types, str):
|
325
|
+
filter_operation_types = [filter_operation_types]
|
330
326
|
self.filter_operation_types = filter_operation_types
|
331
327
|
|
332
328
|
def sync_on_startup(self):
|
333
329
|
self.sqlalchemy_db.init()
|
330
|
+
self.sync_run_startup_funcs()
|
334
331
|
|
335
|
-
def sync_execute_operation(self, operation_dbm: OperationDBM) -> OperationDBM:
|
336
|
-
return self.operation_executor.sync_safe_execute_operation(
|
337
|
-
|
338
|
-
def sync_run(self):
|
339
|
-
operation_dbm: OperationDBM | None = get_operation_for_execution(
|
340
|
-
sqlalchemy_db=self.sqlalchemy_db,
|
341
|
-
filter_operation_types=self.filter_operation_types
|
332
|
+
def sync_execute_operation(self, operation_dbm: OperationDBM, session: Session) -> OperationDBM:
|
333
|
+
return self.operation_executor.sync_safe_execute_operation(
|
334
|
+
operation_dbm=operation_dbm, worker=self, session=session
|
342
335
|
)
|
343
|
-
if not operation_dbm:
|
344
|
-
return
|
345
|
-
self.sync_execute_operation(operation_dbm=operation_dbm)
|
346
336
|
|
347
|
-
def
|
348
|
-
|
337
|
+
def sync_run(self):
|
338
|
+
with self.sqlalchemy_db.new_session() as session:
|
339
|
+
operation_dbm: OperationDBM | None = get_operation_for_execution(
|
340
|
+
session=session,
|
341
|
+
filter_operation_types=self.filter_operation_types,
|
342
|
+
lock=True
|
343
|
+
)
|
344
|
+
if not operation_dbm:
|
345
|
+
return
|
346
|
+
self.sync_execute_operation(operation_dbm=operation_dbm, session=session)
|
349
347
|
|
350
348
|
async def async_on_startup(self):
|
351
349
|
self.sqlalchemy_db.init()
|
350
|
+
await self.async_run_startup_funcs()
|
352
351
|
|
353
|
-
async def async_execute_operation(self, operation_dbm: OperationDBM) -> OperationDBM:
|
354
|
-
return await self.operation_executor.async_safe_execute_operation(
|
355
|
-
|
356
|
-
async def async_run(self):
|
357
|
-
operation_dbm: OperationDBM | None = get_operation_for_execution(
|
358
|
-
sqlalchemy_db=self.sqlalchemy_db,
|
359
|
-
filter_operation_types=self.filter_operation_types
|
352
|
+
async def async_execute_operation(self, operation_dbm: OperationDBM, session: Session) -> OperationDBM:
|
353
|
+
return await self.operation_executor.async_safe_execute_operation(
|
354
|
+
operation_dbm=operation_dbm, worker=self, session=session
|
360
355
|
)
|
361
|
-
if not operation_dbm:
|
362
|
-
return
|
363
|
-
await self.async_execute_operation(operation_dbm=operation_dbm)
|
364
356
|
|
365
|
-
async def
|
366
|
-
|
357
|
+
async def async_run(self):
|
358
|
+
with self.sqlalchemy_db.new_session() as session:
|
359
|
+
operation_dbm: OperationDBM | None = get_operation_for_execution(
|
360
|
+
sqlalchemy_db=self.sqlalchemy_db,
|
361
|
+
filter_operation_types=self.filter_operation_types,
|
362
|
+
lock=True
|
363
|
+
)
|
364
|
+
if not operation_dbm:
|
365
|
+
return
|
366
|
+
await self.async_execute_operation(operation_dbm=operation_dbm, session=session)
|
367
367
|
|
368
368
|
|
369
369
|
class ScheduledOperation(BaseModel):
|
@@ -380,20 +380,20 @@ class ScheduledOperationCreatorWorker(BaseWorker):
|
|
380
380
|
self,
|
381
381
|
*,
|
382
382
|
sqlalchemy_db: SQLAlchemyDB,
|
383
|
-
scheduled_operations: list[ScheduledOperation] | None = None,
|
384
|
-
|
385
|
-
timeout_after_err_in_run=timedelta(seconds=1).total_seconds()
|
383
|
+
scheduled_operations: ScheduledOperation | list[ScheduledOperation] | None = None,
|
384
|
+
startup_funcs: list[Any] | None = None
|
386
385
|
):
|
387
|
-
super().__init__()
|
386
|
+
super().__init__(startup_funcs=startup_funcs)
|
388
387
|
self.sqlalchemy_db = sqlalchemy_db
|
389
|
-
self.timeout_after_run = timeout_after_run
|
390
|
-
self.timeout_after_err_in_run = timeout_after_err_in_run
|
391
388
|
if scheduled_operations is None:
|
392
389
|
scheduled_operations = []
|
390
|
+
if isinstance(scheduled_operations, ScheduledOperation):
|
391
|
+
scheduled_operations = [scheduled_operations]
|
393
392
|
self.scheduled_operations = scheduled_operations
|
394
393
|
|
395
394
|
def sync_on_startup(self):
|
396
395
|
self.sqlalchemy_db.init()
|
396
|
+
self.sync_run_startup_funcs()
|
397
397
|
|
398
398
|
def sync_run(self):
|
399
399
|
timeout = None
|
@@ -424,6 +424,7 @@ class ScheduledOperationCreatorWorker(BaseWorker):
|
|
424
424
|
|
425
425
|
async def async_on_startup(self):
|
426
426
|
self.sqlalchemy_db.init()
|
427
|
+
await self.async_run_startup_funcs()
|
427
428
|
|
428
429
|
async def async_run(self):
|
429
430
|
timeout: timedelta | None = None
|
@@ -453,24 +454,25 @@ class ScheduledOperationCreatorWorker(BaseWorker):
|
|
453
454
|
await async_safe_sleep(n=timeout)
|
454
455
|
|
455
456
|
|
456
|
-
def every_timedelta_is_time_func(*, td: timedelta) -> Callable:
|
457
|
+
def every_timedelta_is_time_func(*, td: timedelta, now_dt_func: Callable = now_utc_dt) -> Callable:
|
457
458
|
last_now_utc_dt = now_utc_dt()
|
458
459
|
|
459
460
|
def func() -> bool:
|
460
461
|
nonlocal last_now_utc_dt
|
461
|
-
|
462
|
-
if (
|
463
|
-
last_now_utc_dt =
|
462
|
+
now_dt_func_ = now_dt_func()
|
463
|
+
if (now_dt_func_ - last_now_utc_dt) >= td:
|
464
|
+
last_now_utc_dt = now_dt_func_
|
464
465
|
return True
|
465
466
|
return False
|
466
467
|
|
467
468
|
return func
|
468
469
|
|
469
470
|
|
470
|
-
def between_different_times_is_time_func(
|
471
|
+
def between_different_times_is_time_func(
|
472
|
+
*, from_time: time, to_time: time, now_dt_func: Callable = now_utc_dt
|
473
|
+
) -> Callable:
|
471
474
|
def func() -> bool:
|
472
|
-
|
473
|
-
if from_time <= now_utc_dt_.time() <= to_time:
|
475
|
+
if from_time <= now_dt_func().time() <= to_time:
|
474
476
|
return True
|
475
477
|
return False
|
476
478
|
|
@@ -189,18 +189,25 @@ class ScheduleUUSTAPIClient:
|
|
189
189
|
return False
|
190
190
|
return True
|
191
191
|
|
192
|
-
async def check_all(self):
|
192
|
+
async def check_all(self) -> dict[str, Any]:
|
193
|
+
current_semester = await self.get_current_semester()
|
194
|
+
self._logger.info(f"current_semester: {current_semester}")
|
195
|
+
|
196
|
+
current_week = await self.get_current_week()
|
197
|
+
self._logger.info(f"current_week: {current_week}")
|
198
|
+
|
193
199
|
groups = await self.get_groups()
|
194
200
|
self._logger.info(f"groups len: {len(groups)}")
|
195
201
|
|
196
202
|
teachers = await self.get_teachers()
|
197
203
|
self._logger.info(f"teachers len: {len(teachers)}")
|
198
204
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
205
|
+
return {
|
206
|
+
"current_semester": current_semester,
|
207
|
+
"current_week": current_week,
|
208
|
+
"len(groups)": len(groups),
|
209
|
+
"len(teachers)": len(teachers)
|
210
|
+
}
|
204
211
|
|
205
212
|
|
206
213
|
def __example():
|
arpakitlib/ar_settings_util.py
CHANGED
@@ -8,6 +8,8 @@ from pydantic_settings import BaseSettings
|
|
8
8
|
|
9
9
|
from arpakitlib.ar_enumeration_util import Enumeration
|
10
10
|
|
11
|
+
_ARPAKIT_LIB_MODULE_VERSION = "3.0"
|
12
|
+
|
11
13
|
|
12
14
|
def generate_env_example(settings_class: Union[BaseSettings, type[BaseSettings]]):
|
13
15
|
res = ""
|
@@ -19,29 +21,38 @@ def generate_env_example(settings_class: Union[BaseSettings, type[BaseSettings]]
|
|
19
21
|
return res
|
20
22
|
|
21
23
|
|
24
|
+
class ModeTypes(Enumeration):
|
25
|
+
not_prod: str = "not_prod"
|
26
|
+
prod: str = "prod"
|
27
|
+
|
28
|
+
|
22
29
|
class SimpleSettings(BaseSettings):
|
23
30
|
model_config = ConfigDict(extra="ignore")
|
24
31
|
|
25
|
-
class ModeTypes(Enumeration):
|
26
|
-
not_prod: str = "not_prod"
|
27
|
-
prod: str = "prod"
|
28
|
-
|
29
32
|
mode_type: str = ModeTypes.not_prod
|
30
33
|
|
31
34
|
@field_validator("mode_type")
|
32
35
|
@classmethod
|
33
36
|
def validate_mode_type(cls, v: str):
|
34
|
-
|
37
|
+
ModeTypes.parse_and_validate_values(v.lower().strip())
|
35
38
|
return v
|
36
39
|
|
37
40
|
@property
|
38
41
|
def is_mode_type_not_prod(self) -> bool:
|
39
42
|
return self.mode_type == self.ModeTypes.not_prod
|
40
43
|
|
44
|
+
def raise_if_mode_type_not_prod(self):
|
45
|
+
if self.is_mode_type_not_prod:
|
46
|
+
raise ValueError(f"mode type = {self.mode_type}")
|
47
|
+
|
41
48
|
@property
|
42
49
|
def is_mode_type_prod(self) -> bool:
|
43
50
|
return self.mode_type == self.ModeTypes.prod
|
44
51
|
|
52
|
+
def raise_if_mode_type_prod(self):
|
53
|
+
if self.is_mode_type_prod:
|
54
|
+
raise ValueError(f"mode type = {self.mode_type}")
|
55
|
+
|
45
56
|
@classmethod
|
46
57
|
def generate_env_example(cls) -> str:
|
47
58
|
return generate_env_example(settings_class=cls)
|
@@ -52,3 +63,11 @@ class SimpleSettings(BaseSettings):
|
|
52
63
|
with open(filepath, mode="w") as f:
|
53
64
|
f.write(env_example)
|
54
65
|
return env_example
|
66
|
+
|
67
|
+
|
68
|
+
def __example():
|
69
|
+
pass
|
70
|
+
|
71
|
+
|
72
|
+
if __name__ == '__main__':
|
73
|
+
__example()
|
@@ -4,13 +4,13 @@ from datetime import datetime, timedelta
|
|
4
4
|
from typing import Any
|
5
5
|
from uuid import uuid4
|
6
6
|
|
7
|
-
from sqlalchemy import inspect, INTEGER, TEXT, TIMESTAMP
|
7
|
+
from sqlalchemy import inspect, INTEGER, TEXT, TIMESTAMP, func
|
8
8
|
from sqlalchemy.dialects.postgresql import JSONB
|
9
9
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
10
10
|
|
11
11
|
from arpakitlib.ar_datetime_util import now_utc_dt
|
12
12
|
from arpakitlib.ar_enumeration_util import Enumeration
|
13
|
-
from arpakitlib.ar_json_util import
|
13
|
+
from arpakitlib.ar_json_util import safely_transfer_obj_to_json_str
|
14
14
|
|
15
15
|
_ARPAKIT_LIB_MODULE_VERSION = "3.0"
|
16
16
|
|
@@ -34,17 +34,41 @@ class BaseDBM(DeclarativeBase):
|
|
34
34
|
return self._bus_data
|
35
35
|
|
36
36
|
def simple_dict(self, *, include_sd_properties: bool = True) -> dict[str, Any]:
|
37
|
-
res = {
|
37
|
+
res = {}
|
38
|
+
|
39
|
+
for c in inspect(self).mapper.column_attrs:
|
40
|
+
value = getattr(self, c.key)
|
41
|
+
if isinstance(value, BaseDBM):
|
42
|
+
res[c.key] = value.simple_dict(include_sd_properties=include_sd_properties)
|
43
|
+
elif isinstance(value, list):
|
44
|
+
res[c.key] = [
|
45
|
+
item.simple_dict(include_sd_properties=include_sd_properties)
|
46
|
+
if isinstance(item, BaseDBM) else item
|
47
|
+
for item in value
|
48
|
+
]
|
49
|
+
else:
|
50
|
+
res[c.key] = value
|
38
51
|
|
39
52
|
if include_sd_properties:
|
40
53
|
for attr_name in dir(self):
|
41
54
|
if attr_name.startswith("sdp_") and isinstance(getattr(type(self), attr_name, None), property):
|
42
|
-
|
55
|
+
prop_name = attr_name.removeprefix("sdp_")
|
56
|
+
value = getattr(self, attr_name)
|
57
|
+
if isinstance(value, BaseDBM):
|
58
|
+
res[prop_name] = value.simple_dict(include_sd_properties=include_sd_properties)
|
59
|
+
elif isinstance(value, list):
|
60
|
+
res[prop_name] = [
|
61
|
+
item.simple_dict(include_sd_properties=include_sd_properties)
|
62
|
+
if isinstance(item, BaseDBM) else item
|
63
|
+
for item in value
|
64
|
+
]
|
65
|
+
else:
|
66
|
+
res[prop_name] = value
|
43
67
|
|
44
68
|
return res
|
45
69
|
|
46
70
|
def simple_json(self, *, include_sd_properties: bool = True) -> str:
|
47
|
-
return
|
71
|
+
return safely_transfer_obj_to_json_str(self.simple_dict(include_sd_properties=include_sd_properties))
|
48
72
|
|
49
73
|
|
50
74
|
class SimpleDBM(BaseDBM):
|
@@ -54,10 +78,12 @@ class SimpleDBM(BaseDBM):
|
|
54
78
|
INTEGER, primary_key=True, autoincrement=True, sort_order=-3, nullable=False
|
55
79
|
)
|
56
80
|
long_id: Mapped[str] = mapped_column(
|
57
|
-
TEXT, insert_default=generate_default_long_id,
|
81
|
+
TEXT, insert_default=generate_default_long_id, server_default=func.gen_random_uuid(),
|
82
|
+
unique=True, sort_order=-2, nullable=False
|
58
83
|
)
|
59
84
|
creation_dt: Mapped[datetime] = mapped_column(
|
60
|
-
TIMESTAMP(timezone=True), insert_default=now_utc_dt,
|
85
|
+
TIMESTAMP(timezone=True), insert_default=now_utc_dt, server_default=func.now(),
|
86
|
+
index=True, sort_order=-1, nullable=False
|
61
87
|
)
|
62
88
|
|
63
89
|
def __repr__(self):
|
@@ -137,6 +163,10 @@ class OperationDBM(SimpleDBM):
|
|
137
163
|
return None
|
138
164
|
return self.duration.total_seconds()
|
139
165
|
|
166
|
+
@property
|
167
|
+
def sdp_duration_total_seconds(self) -> float | None:
|
168
|
+
return self.duration_total_seconds
|
169
|
+
|
140
170
|
|
141
171
|
def __example():
|
142
172
|
pass
|
arpakitlib/ar_ssh_runner_util.py
CHANGED
@@ -11,7 +11,7 @@ import asyncssh
|
|
11
11
|
import paramiko
|
12
12
|
from pydantic import BaseModel
|
13
13
|
|
14
|
-
from arpakitlib.ar_json_util import
|
14
|
+
from arpakitlib.ar_json_util import safely_transfer_obj_to_json_str
|
15
15
|
|
16
16
|
_ARPAKIT_LIB_MODULE_VERSION = "3.0"
|
17
17
|
|
@@ -61,7 +61,7 @@ class SSHRunRes(BaseModel):
|
|
61
61
|
}
|
62
62
|
|
63
63
|
def simple_json(self) -> str:
|
64
|
-
return
|
64
|
+
return safely_transfer_obj_to_json_str(self.simple_dict())
|
65
65
|
|
66
66
|
def __repr__(self) -> str:
|
67
67
|
return self.simple_json()
|