arpakitlib 1.6.47__py3-none-any.whl → 1.7.89__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/.gitignore +51 -0
- arpakitlib/_arpakit_project_template/.python-version +1 -0
- arpakitlib/_arpakit_project_template/ARPAKITLIB +1 -0
- arpakitlib/_arpakit_project_template/AUTHOR.md +4 -0
- arpakitlib/_arpakit_project_template/NOTICE +16 -0
- arpakitlib/_arpakit_project_template/README.md +6 -0
- arpakitlib/_arpakit_project_template/example.env +22 -0
- arpakitlib/_arpakit_project_template/manage/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/manage/docker_ps.sh +2 -0
- arpakitlib/_arpakit_project_template/manage/docker_ps_a.sh +2 -0
- arpakitlib/_arpakit_project_template/manage/docker_run_postgres_for_dev.sh +4 -0
- arpakitlib/_arpakit_project_template/manage/docker_start_postgres_for_dev.sh +2 -0
- arpakitlib/_arpakit_project_template/manage/docker_stop_postgres_for_dev.sh +2 -0
- arpakitlib/_arpakit_project_template/manage/example_nginx_proxy.nginx +14 -0
- arpakitlib/_arpakit_project_template/manage/example_poetry_arpakitlib.sh +1 -0
- arpakitlib/_arpakit_project_template/manage/example_pyproject.toml +18 -0
- arpakitlib/_arpakit_project_template/manage/example_systemd.service +12 -0
- arpakitlib/_arpakit_project_template/manage/git_branch.sh +2 -0
- arpakitlib/_arpakit_project_template/manage/git_commit.sh +3 -0
- arpakitlib/_arpakit_project_template/manage/git_push_arpakit_company_github_1.sh +4 -0
- arpakitlib/_arpakit_project_template/manage/git_push_arpakit_company_gitlab_1.sh +4 -0
- arpakitlib/_arpakit_project_template/manage/git_push_arpakit_github_1.sh +4 -0
- arpakitlib/_arpakit_project_template/manage/git_push_arpakit_gitlab_1.sh +4 -0
- arpakitlib/_arpakit_project_template/manage/git_remote_v.sh +2 -0
- arpakitlib/_arpakit_project_template/manage/git_set_arpakit_company_origin.sh +7 -0
- arpakitlib/_arpakit_project_template/manage/git_set_arpakit_origin.sh +7 -0
- arpakitlib/_arpakit_project_template/manage/git_status.sh +2 -0
- arpakitlib/_arpakit_project_template/manage/hello_world.py +6 -0
- arpakitlib/_arpakit_project_template/manage/json_beutify.py +10 -0
- arpakitlib/_arpakit_project_template/manage/logging_check.py +14 -0
- arpakitlib/_arpakit_project_template/manage/note/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/manage/note/note_1.txt +0 -0
- arpakitlib/_arpakit_project_template/manage/note/note_2.txt +0 -0
- arpakitlib/_arpakit_project_template/manage/note/note_3.txt +0 -0
- arpakitlib/_arpakit_project_template/manage/note/note_4.txt +0 -0
- arpakitlib/_arpakit_project_template/manage/note/note_5.txt +0 -0
- arpakitlib/_arpakit_project_template/manage/poetry_add_plugin_export.sh +2 -0
- arpakitlib/_arpakit_project_template/manage/poetry_check.sh +2 -0
- arpakitlib/_arpakit_project_template/manage/poetry_clear_cache.sh +4 -0
- arpakitlib/_arpakit_project_template/manage/poetry_config_virtualenvs_in_project_true.sh +2 -0
- arpakitlib/_arpakit_project_template/manage/poetry_generate_requirements.txt.sh +1 -0
- arpakitlib/_arpakit_project_template/manage/poetry_install.sh +5 -0
- arpakitlib/_arpakit_project_template/manage/poetry_lock.sh +2 -0
- arpakitlib/_arpakit_project_template/manage/poetry_remove_and_add_arpakitlib.sh +7 -0
- arpakitlib/_arpakit_project_template/manage/poetry_show.sh +2 -0
- arpakitlib/_arpakit_project_template/manage/poetry_show_arpakitlib.sh +2 -0
- arpakitlib/_arpakit_project_template/manage/poetry_update.sh +6 -0
- arpakitlib/_arpakit_project_template/manage/poetry_update_arpakitlib.sh +5 -0
- arpakitlib/_arpakit_project_template/manage/requirements.txt +209 -0
- arpakitlib/_arpakit_project_template/manage/sandbox/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_1.py +14 -0
- arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_2.py +14 -0
- arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_3.py +14 -0
- arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_4.py +14 -0
- arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_5.py +14 -0
- arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_6.py +14 -0
- arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_7.py +14 -0
- arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_8.sh +0 -0
- arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_9.sh +0 -0
- arpakitlib/_arpakit_project_template/manage/settings_check.py +10 -0
- arpakitlib/_arpakit_project_template/manage/settings_generate_env_example.py +13 -0
- arpakitlib/_arpakit_project_template/manage/sqlalchemy_db_check_conn.py +11 -0
- arpakitlib/_arpakit_project_template/manage/sqlalchemy_db_init.py +11 -0
- arpakitlib/_arpakit_project_template/manage/sqlalchemy_db_reinit.py +11 -0
- arpakitlib/_arpakit_project_template/resource/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/resource/static/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/resource/static/healthcheck +1 -0
- arpakitlib/_arpakit_project_template/resource/static/helloworld +1 -0
- arpakitlib/_arpakit_project_template/src/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/src/additional_model/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/src/additional_model/additional_model.py +6 -0
- arpakitlib/_arpakit_project_template/src/api/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/src/api/asgi.py +3 -0
- arpakitlib/_arpakit_project_template/src/api/auth.py +1 -0
- arpakitlib/_arpakit_project_template/src/api/const.py +13 -0
- arpakitlib/_arpakit_project_template/src/api/create_api_app.py +117 -0
- arpakitlib/_arpakit_project_template/src/api/event.py +20 -0
- arpakitlib/_arpakit_project_template/src/api/router/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/src/api/router/main_router.py +9 -0
- arpakitlib/_arpakit_project_template/src/api/router/v1/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/src/api/router/v1/get_api_error_info.py +27 -0
- arpakitlib/_arpakit_project_template/src/api/router/v1/main_router.py +11 -0
- arpakitlib/_arpakit_project_template/src/api/schema/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/src/api/schema/v1/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/src/api/schema/v1/in_.py +0 -0
- arpakitlib/_arpakit_project_template/src/api/schema/v1/out.py +6 -0
- arpakitlib/_arpakit_project_template/src/api/start_api_for_dev.py +17 -0
- arpakitlib/_arpakit_project_template/src/api/start_api_for_dev_with_reload.py +9 -0
- arpakitlib/_arpakit_project_template/src/api/transmitted_api_data.py +9 -0
- arpakitlib/_arpakit_project_template/src/api/util.py +0 -0
- arpakitlib/_arpakit_project_template/src/business_service/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/src/core/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/src/core/const.py +48 -0
- arpakitlib/_arpakit_project_template/src/core/settings.py +86 -0
- arpakitlib/_arpakit_project_template/src/core/util.py +58 -0
- arpakitlib/_arpakit_project_template/src/db/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/src/db/sqlalchemy_model.py +8 -0
- arpakitlib/_arpakit_project_template/src/db/util.py +21 -0
- arpakitlib/_arpakit_project_template/src/operation_execution/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/src/operation_execution/const.py +9 -0
- arpakitlib/_arpakit_project_template/src/operation_execution/operation_executor.py +14 -0
- arpakitlib/_arpakit_project_template/src/operation_execution/scheduled_operations.py +25 -0
- arpakitlib/_arpakit_project_template/src/operation_execution/start_operation_executor_worker_for_dev.py +18 -0
- arpakitlib/_arpakit_project_template/src/operation_execution/start_scheduled_operation_creator_worker_for_dev.py +17 -0
- arpakitlib/_arpakit_project_template/src/operation_execution/util.py +21 -0
- arpakitlib/_arpakit_project_template/src/test_data/__init__.py +0 -0
- arpakitlib/_arpakit_project_template/src/test_data/make_test_data_1.py +6 -0
- arpakitlib/_arpakit_project_template/src/test_data/make_test_data_2.py +6 -0
- arpakitlib/_arpakit_project_template/src/test_data/make_test_data_3.py +6 -0
- arpakitlib/_arpakit_project_template/src/test_data/make_test_data_4.py +6 -0
- arpakitlib/_arpakit_project_template/src/test_data/make_test_data_5.py +6 -0
- arpakitlib/_arpakit_project_template/src/util/__init__.py +0 -0
- arpakitlib/api_key_util.py +21 -0
- arpakitlib/ar_additional_model_util.py +25 -2
- arpakitlib/ar_aiogram_util.py +10 -18
- arpakitlib/ar_arpakit_lib_module_util.py +6 -1
- arpakitlib/ar_arpakit_project_template_util.py +96 -0
- arpakitlib/ar_arpakit_schedule_uust_api_client_util.py +24 -3
- arpakitlib/ar_arpakitlib_cli_util.py +79 -0
- arpakitlib/ar_base_worker_util.py +95 -48
- arpakitlib/ar_dream_ai_api_client_util.py +26 -52
- arpakitlib/ar_enumeration_util.py +11 -0
- arpakitlib/ar_exception_util.py +18 -0
- arpakitlib/ar_fastapi_static/healthcheck +1 -0
- arpakitlib/ar_fastapi_util.py +270 -137
- arpakitlib/ar_file_util.py +22 -0
- arpakitlib/ar_func_util.py +55 -0
- arpakitlib/ar_http_request_util.py +35 -6
- arpakitlib/ar_json_util.py +13 -7
- arpakitlib/ar_logging_util.py +5 -2
- arpakitlib/ar_need_type_util.py +12 -2
- arpakitlib/{ar_openai_util.py → ar_openai_api_client_util.py} +16 -2
- arpakitlib/ar_operation_execution_util.py +250 -105
- arpakitlib/ar_parse_command.py +25 -7
- arpakitlib/ar_schedule_uust_api_client_util.py +37 -23
- arpakitlib/ar_settings_util.py +37 -11
- arpakitlib/ar_sleep_util.py +0 -13
- arpakitlib/ar_sqlalchemy_model_util.py +35 -10
- arpakitlib/ar_sqlalchemy_util.py +4 -3
- arpakitlib/{ar_ssh_util.py → ar_ssh_runner_util.py} +2 -2
- arpakitlib/ar_str_util.py +43 -2
- arpakitlib/ar_type_util.py +68 -4
- arpakitlib/ar_yookassa_api_client_util.py +26 -44
- {arpakitlib-1.6.47.dist-info → arpakitlib-1.7.89.dist-info}/METADATA +17 -8
- arpakitlib-1.7.89.dist-info/NOTICE +16 -0
- arpakitlib-1.7.89.dist-info/RECORD +186 -0
- arpakitlib-1.7.89.dist-info/entry_points.txt +3 -0
- arpakitlib/AUTHOR.md +0 -7
- arpakitlib/NOTICE +0 -2
- arpakitlib/ar_arpakitlib_info.py +0 -13
- arpakitlib/ar_generate_env_example.py +0 -22
- arpakitlib-1.6.47.dist-info/NOTICE +0 -2
- arpakitlib-1.6.47.dist-info/RECORD +0 -70
- /arpakitlib/{LICENSE → _arpakit_project_template/LICENSE} +0 -0
- /arpakitlib/{ar_zabbix_util.py → ar_zabbix_api_client_util.py} +0 -0
- {arpakitlib-1.6.47.dist-info → arpakitlib-1.7.89.dist-info}/LICENSE +0 -0
- {arpakitlib-1.6.47.dist-info → arpakitlib-1.7.89.dist-info}/WHEEL +0 -0
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
5
5
|
import asyncio
|
6
6
|
import logging
|
7
7
|
import traceback
|
8
|
-
from datetime import timedelta
|
8
|
+
from datetime import timedelta, time
|
9
9
|
from typing import Any, Callable
|
10
10
|
|
11
11
|
from pydantic import ConfigDict
|
@@ -16,8 +16,10 @@ from sqlalchemy.orm import Session
|
|
16
16
|
from arpakitlib.ar_base_worker_util import BaseWorker
|
17
17
|
from arpakitlib.ar_datetime_util import now_utc_dt
|
18
18
|
from arpakitlib.ar_dict_util import combine_dicts
|
19
|
-
from arpakitlib.
|
19
|
+
from arpakitlib.ar_sleep_util import sync_safe_sleep, async_safe_sleep
|
20
|
+
from arpakitlib.ar_sqlalchemy_model_util import OperationDBM, StoryLogDBM, BaseOperationTypes
|
20
21
|
from arpakitlib.ar_sqlalchemy_util import SQLAlchemyDB
|
22
|
+
from arpakitlib.ar_type_util import raise_for_type
|
21
23
|
|
22
24
|
_ARPAKIT_LIB_MODULE_VERSION = "3.0"
|
23
25
|
|
@@ -26,37 +28,101 @@ _logger = logging.getLogger(__name__)
|
|
26
28
|
|
27
29
|
def get_operation_for_execution(
|
28
30
|
*,
|
29
|
-
|
30
|
-
|
31
|
+
session: Session | None = None,
|
32
|
+
sqlalchemy_db: SQLAlchemyDB | None = None,
|
33
|
+
filter_operation_types: list[str] | str | None = None
|
31
34
|
) -> OperationDBM | None:
|
32
|
-
|
35
|
+
if isinstance(filter_operation_types, str):
|
36
|
+
filter_operation_types = [filter_operation_types]
|
37
|
+
|
38
|
+
def func(session_: Session):
|
33
39
|
query = (
|
34
|
-
|
40
|
+
session_
|
35
41
|
.query(OperationDBM)
|
36
42
|
.filter(OperationDBM.status == OperationDBM.Statuses.waiting_for_execution)
|
37
43
|
)
|
38
|
-
if
|
39
|
-
query = query.filter(OperationDBM.type
|
44
|
+
if filter_operation_types:
|
45
|
+
query = query.filter(OperationDBM.type.in_(filter_operation_types))
|
40
46
|
query = query.order_by(asc(OperationDBM.creation_dt))
|
41
47
|
operation_dbm: OperationDBM | None = query.first()
|
42
|
-
|
48
|
+
return operation_dbm
|
49
|
+
|
50
|
+
if session is not None:
|
51
|
+
return func(session_=session)
|
52
|
+
elif sqlalchemy_db is not None:
|
53
|
+
with sqlalchemy_db.new_session() as session:
|
54
|
+
return func(session_=session)
|
55
|
+
else:
|
56
|
+
raise ValueError("session is None and sqlalchemy_db is None")
|
43
57
|
|
44
58
|
|
45
59
|
def get_operation_by_id(
|
46
60
|
*,
|
47
|
-
session: Session,
|
61
|
+
session: Session | None = None,
|
62
|
+
sqlalchemy_db: SQLAlchemyDB | None = None,
|
48
63
|
filter_operation_id: int,
|
49
64
|
raise_if_not_found: bool = False
|
50
65
|
) -> OperationDBM | None:
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
66
|
+
def func(session_: Session):
|
67
|
+
query = (
|
68
|
+
session_
|
69
|
+
.query(OperationDBM)
|
70
|
+
.filter(OperationDBM.id == filter_operation_id)
|
71
|
+
)
|
72
|
+
if raise_if_not_found:
|
73
|
+
return query.one()
|
74
|
+
else:
|
75
|
+
return query.one_or_none()
|
76
|
+
|
77
|
+
if session is not None:
|
78
|
+
return func(session_=session)
|
79
|
+
elif sqlalchemy_db is not None:
|
80
|
+
with sqlalchemy_db.new_session() as session:
|
81
|
+
return func(session_=session)
|
82
|
+
else:
|
83
|
+
raise ValueError("session is None and sqlalchemy_db is None")
|
84
|
+
|
85
|
+
|
86
|
+
def remove_operations(
|
87
|
+
*,
|
88
|
+
session: Session | None = None,
|
89
|
+
sqlalchemy_db: SQLAlchemyDB | None = None,
|
90
|
+
filter_operation_ids: list[int] | int | None = None,
|
91
|
+
filter_operation_types: list[str] | str | None = None,
|
92
|
+
filter_operation_statuses: list[str] | str | None = None
|
93
|
+
):
|
94
|
+
if isinstance(filter_operation_ids, int):
|
95
|
+
filter_operation_ids = [filter_operation_ids]
|
96
|
+
if isinstance(filter_operation_types, str):
|
97
|
+
filter_operation_types = [filter_operation_types]
|
98
|
+
if isinstance(filter_operation_statuses, str):
|
99
|
+
filter_operation_statuses = [filter_operation_statuses]
|
100
|
+
|
101
|
+
if filter_operation_ids is not None:
|
102
|
+
raise_for_type(filter_operation_ids, list)
|
103
|
+
if filter_operation_types is not None:
|
104
|
+
raise_for_type(filter_operation_types, list)
|
105
|
+
if filter_operation_statuses is not None:
|
106
|
+
raise_for_type(filter_operation_statuses, list)
|
107
|
+
|
108
|
+
def func(session_: Session):
|
109
|
+
query = session_.query(OperationDBM)
|
110
|
+
if filter_operation_ids is not None:
|
111
|
+
query = query.filter(OperationDBM.id.in_(filter_operation_ids))
|
112
|
+
if filter_operation_types is not None:
|
113
|
+
query = query.filter(OperationDBM.type.in_(filter_operation_types))
|
114
|
+
if filter_operation_statuses is not None:
|
115
|
+
query = query.filter(OperationDBM.status.in_(filter_operation_statuses))
|
116
|
+
query.delete()
|
117
|
+
session_.commit()
|
118
|
+
|
119
|
+
if session is not None:
|
120
|
+
return func(session_=session)
|
121
|
+
elif sqlalchemy_db is not None:
|
122
|
+
with sqlalchemy_db.new_session() as session:
|
123
|
+
return func(session_=session)
|
58
124
|
else:
|
59
|
-
|
125
|
+
raise ValueError("session is None and sqlalchemy_db is None")
|
60
126
|
|
61
127
|
|
62
128
|
class BaseOperationExecutor:
|
@@ -64,19 +130,20 @@ class BaseOperationExecutor:
|
|
64
130
|
self._logger = logging.getLogger(self.__class__.__name__)
|
65
131
|
self.sql_alchemy_db = sqlalchemy_db
|
66
132
|
|
67
|
-
|
68
|
-
if operation_dbm.type ==
|
133
|
+
def sync_execute_operation(self, operation_dbm: OperationDBM) -> OperationDBM:
|
134
|
+
if operation_dbm.type == BaseOperationTypes.healthcheck_:
|
69
135
|
self._logger.info("healthcheck")
|
70
|
-
elif operation_dbm.type ==
|
136
|
+
elif operation_dbm.type == BaseOperationTypes.raise_fake_exception_:
|
71
137
|
self._logger.info("raise_fake_exception")
|
72
138
|
raise Exception("raise_fake_exception")
|
73
139
|
return operation_dbm
|
74
140
|
|
75
|
-
|
141
|
+
def sync_safe_execute_operation(self, operation_dbm: OperationDBM) -> OperationDBM:
|
76
142
|
self._logger.info(
|
77
|
-
f"start
|
78
|
-
f"
|
79
|
-
f"
|
143
|
+
f"start "
|
144
|
+
f"operation_dbm.id={operation_dbm.id}, "
|
145
|
+
f"operation_dbm.type={operation_dbm.type}, "
|
146
|
+
f"operation_dbm.status={operation_dbm.status}"
|
80
147
|
)
|
81
148
|
|
82
149
|
with self.sql_alchemy_db.new_session() as session:
|
@@ -92,9 +159,12 @@ class BaseOperationExecutor:
|
|
92
159
|
traceback_str: str | None = None
|
93
160
|
|
94
161
|
try:
|
95
|
-
|
162
|
+
self.sync_execute_operation(operation_dbm=operation_dbm)
|
96
163
|
except BaseException as exception_:
|
97
|
-
self._logger.
|
164
|
+
self._logger.error(
|
165
|
+
f"error in sync_execute_operation (id={operation_dbm.id}, type={operation_dbm.type})",
|
166
|
+
exc_info=exception_
|
167
|
+
)
|
98
168
|
exception = exception_
|
99
169
|
traceback_str = traceback.format_exc()
|
100
170
|
|
@@ -107,49 +177,56 @@ class BaseOperationExecutor:
|
|
107
177
|
if exception:
|
108
178
|
operation_dbm.status = OperationDBM.Statuses.executed_with_error
|
109
179
|
operation_dbm.error_data = combine_dicts(
|
110
|
-
{
|
180
|
+
{
|
181
|
+
"exception_str": str(exception),
|
182
|
+
"traceback_str": traceback_str
|
183
|
+
},
|
111
184
|
operation_dbm.error_data
|
112
185
|
)
|
113
186
|
else:
|
114
187
|
operation_dbm.status = OperationDBM.Statuses.executed_without_error
|
115
188
|
session.commit()
|
116
189
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
190
|
+
if exception:
|
191
|
+
story_log_dbm = StoryLogDBM(
|
192
|
+
level=StoryLogDBM.Levels.error,
|
193
|
+
title=f"error in sync_execute_operation (id={operation_dbm.id}, type={operation_dbm.type})",
|
194
|
+
data={
|
195
|
+
"operation_id": operation_dbm.id,
|
196
|
+
"exception_str": str(exception),
|
197
|
+
"traceback_str": traceback_str
|
198
|
+
}
|
199
|
+
)
|
200
|
+
session.add(story_log_dbm)
|
201
|
+
session.commit()
|
202
|
+
session.refresh(story_log_dbm)
|
128
203
|
|
129
204
|
session.refresh(operation_dbm)
|
130
|
-
session.refresh(story_log_dbm)
|
131
205
|
|
132
206
|
self._logger.info(
|
133
|
-
f"finish
|
134
|
-
f"
|
135
|
-
f"
|
207
|
+
f"finish sync_safe_execute_operation, "
|
208
|
+
f"operation_dbm.id={operation_dbm.id}, "
|
209
|
+
f"operation_dbm.type={operation_dbm.type}, "
|
210
|
+
f"operation_dbm.status={operation_dbm.status}, "
|
211
|
+
f"operation_dbm.duration={operation_dbm.duration}"
|
136
212
|
)
|
137
213
|
|
138
214
|
return operation_dbm
|
139
215
|
|
140
|
-
def
|
141
|
-
if operation_dbm.type ==
|
216
|
+
async def async_execute_operation(self, operation_dbm: OperationDBM) -> OperationDBM:
|
217
|
+
if operation_dbm.type == BaseOperationTypes.healthcheck_:
|
142
218
|
self._logger.info("healthcheck")
|
143
|
-
elif operation_dbm.type ==
|
219
|
+
elif operation_dbm.type == BaseOperationTypes.raise_fake_exception_:
|
144
220
|
self._logger.info("raise_fake_exception")
|
145
221
|
raise Exception("raise_fake_exception")
|
146
222
|
return operation_dbm
|
147
223
|
|
148
|
-
def
|
224
|
+
async def async_safe_execute_operation(self, operation_dbm: OperationDBM) -> OperationDBM:
|
149
225
|
self._logger.info(
|
150
|
-
f"start
|
151
|
-
f"
|
152
|
-
f"
|
226
|
+
f"start "
|
227
|
+
f"operation_dbm.id={operation_dbm.id}, "
|
228
|
+
f"operation_dbm.type={operation_dbm.type}, "
|
229
|
+
f"operation_dbm.status={operation_dbm.status}"
|
153
230
|
)
|
154
231
|
|
155
232
|
with self.sql_alchemy_db.new_session() as session:
|
@@ -165,9 +242,12 @@ class BaseOperationExecutor:
|
|
165
242
|
traceback_str: str | None = None
|
166
243
|
|
167
244
|
try:
|
168
|
-
self.
|
245
|
+
await self.async_execute_operation(operation_dbm=operation_dbm)
|
169
246
|
except BaseException as exception_:
|
170
|
-
self._logger.
|
247
|
+
self._logger.error(
|
248
|
+
f"error in async_execute_operation (id={operation_dbm.id}, type={operation_dbm.type})",
|
249
|
+
exc_info=exception_
|
250
|
+
)
|
171
251
|
exception = exception_
|
172
252
|
traceback_str = traceback.format_exc()
|
173
253
|
|
@@ -180,97 +260,103 @@ class BaseOperationExecutor:
|
|
180
260
|
if exception:
|
181
261
|
operation_dbm.status = OperationDBM.Statuses.executed_with_error
|
182
262
|
operation_dbm.error_data = combine_dicts(
|
183
|
-
{
|
263
|
+
{
|
264
|
+
"exception_str": str(exception),
|
265
|
+
"traceback_str": traceback_str
|
266
|
+
},
|
184
267
|
operation_dbm.error_data
|
185
268
|
)
|
186
269
|
else:
|
187
270
|
operation_dbm.status = OperationDBM.Statuses.executed_without_error
|
188
271
|
session.commit()
|
189
272
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
273
|
+
if exception:
|
274
|
+
story_log_dbm = StoryLogDBM(
|
275
|
+
level=StoryLogDBM.Levels.error,
|
276
|
+
title=f"error in async_execute_operation (id={operation_dbm.id}, type={operation_dbm.type})",
|
277
|
+
data={
|
278
|
+
"operation_id": operation_dbm.id,
|
279
|
+
"exception_str": str(exception),
|
280
|
+
"traceback_str": traceback_str
|
281
|
+
}
|
282
|
+
)
|
283
|
+
session.add(story_log_dbm)
|
284
|
+
session.commit()
|
285
|
+
session.refresh(story_log_dbm)
|
201
286
|
|
202
287
|
session.refresh(operation_dbm)
|
203
|
-
session.refresh(story_log_dbm)
|
204
288
|
|
205
289
|
self._logger.info(
|
206
|
-
f"finish
|
207
|
-
f"
|
208
|
-
f"
|
209
|
-
f"
|
290
|
+
f"finish async_safe_execute_operation, "
|
291
|
+
f"operation_dbm.id={operation_dbm.id}, "
|
292
|
+
f"operation_dbm.type={operation_dbm.type}, "
|
293
|
+
f"operation_dbm.status={operation_dbm.status}, "
|
294
|
+
f"operation_dbm.duration={operation_dbm.duration}"
|
210
295
|
)
|
211
296
|
|
212
297
|
return operation_dbm
|
213
298
|
|
214
299
|
|
215
|
-
class
|
300
|
+
class OperationExecutorWorker(BaseWorker):
|
216
301
|
|
217
302
|
def __init__(
|
218
303
|
self,
|
219
304
|
*,
|
220
305
|
sqlalchemy_db: SQLAlchemyDB,
|
221
306
|
operation_executor: BaseOperationExecutor | None = None,
|
222
|
-
|
223
|
-
timeout_after_run=timedelta(seconds=0.1)
|
224
|
-
timeout_after_err_in_run=timedelta(seconds=1)
|
307
|
+
filter_operation_types: str | list[str] | None = None,
|
308
|
+
timeout_after_run=timedelta(seconds=0.1),
|
309
|
+
timeout_after_err_in_run=timedelta(seconds=1),
|
310
|
+
startup_funcs: list[Any] | None = None
|
225
311
|
):
|
226
|
-
super().__init__(
|
312
|
+
super().__init__(
|
313
|
+
timeout_after_run=timeout_after_run,
|
314
|
+
timeout_after_err_in_run=timeout_after_err_in_run,
|
315
|
+
startup_funcs=startup_funcs
|
316
|
+
)
|
227
317
|
self.sqlalchemy_db = sqlalchemy_db
|
228
|
-
self.timeout_after_run = timeout_after_run
|
229
|
-
self.timeout_after_err_in_run = timeout_after_err_in_run
|
230
318
|
if operation_executor is None:
|
231
319
|
operation_executor = BaseOperationExecutor(sqlalchemy_db=sqlalchemy_db)
|
232
320
|
self.operation_executor = operation_executor
|
233
|
-
self.
|
321
|
+
self.filter_operation_types = filter_operation_types
|
234
322
|
|
235
|
-
|
323
|
+
def sync_on_startup(self):
|
236
324
|
self.sqlalchemy_db.init()
|
325
|
+
self.sync_run_startup_funcs()
|
237
326
|
|
238
|
-
|
239
|
-
return
|
327
|
+
def sync_execute_operation(self, operation_dbm: OperationDBM) -> OperationDBM:
|
328
|
+
return self.operation_executor.sync_safe_execute_operation(operation_dbm=operation_dbm)
|
240
329
|
|
241
|
-
|
330
|
+
def sync_run(self):
|
242
331
|
operation_dbm: OperationDBM | None = get_operation_for_execution(
|
243
332
|
sqlalchemy_db=self.sqlalchemy_db,
|
244
|
-
|
333
|
+
filter_operation_types=self.filter_operation_types
|
245
334
|
)
|
246
|
-
|
247
335
|
if not operation_dbm:
|
248
336
|
return
|
337
|
+
self.sync_execute_operation(operation_dbm=operation_dbm)
|
249
338
|
|
250
|
-
|
251
|
-
|
252
|
-
async def async_run_on_error(self, exception: BaseException, **kwargs):
|
253
|
-
self._logger.exception(exception)
|
339
|
+
def sync_run_on_error(self, exception: BaseException, **kwargs):
|
340
|
+
pass
|
254
341
|
|
255
|
-
def
|
342
|
+
async def async_on_startup(self):
|
256
343
|
self.sqlalchemy_db.init()
|
344
|
+
await self.async_run_startup_funcs()
|
257
345
|
|
258
|
-
def
|
259
|
-
return self.operation_executor.
|
346
|
+
async def async_execute_operation(self, operation_dbm: OperationDBM) -> OperationDBM:
|
347
|
+
return await self.operation_executor.async_safe_execute_operation(operation_dbm=operation_dbm)
|
260
348
|
|
261
|
-
def
|
349
|
+
async def async_run(self):
|
262
350
|
operation_dbm: OperationDBM | None = get_operation_for_execution(
|
263
351
|
sqlalchemy_db=self.sqlalchemy_db,
|
264
|
-
|
352
|
+
filter_operation_types=self.filter_operation_types
|
265
353
|
)
|
266
|
-
|
267
354
|
if not operation_dbm:
|
268
355
|
return
|
356
|
+
await self.async_execute_operation(operation_dbm=operation_dbm)
|
269
357
|
|
270
|
-
|
271
|
-
|
272
|
-
def sync_run_on_error(self, exception: BaseException, **kwargs):
|
273
|
-
self._logger.exception(exception)
|
358
|
+
async def async_run_on_error(self, exception: BaseException, **kwargs):
|
359
|
+
pass
|
274
360
|
|
275
361
|
|
276
362
|
class ScheduledOperation(BaseModel):
|
@@ -279,32 +365,41 @@ class ScheduledOperation(BaseModel):
|
|
279
365
|
type: str
|
280
366
|
input_data: dict[str, Any] | None = None
|
281
367
|
is_time_func: Callable[[], bool]
|
368
|
+
timeout_after_creation: timedelta | None = None
|
282
369
|
|
283
370
|
|
284
|
-
class
|
371
|
+
class ScheduledOperationCreatorWorker(BaseWorker):
|
285
372
|
def __init__(
|
286
373
|
self,
|
287
374
|
*,
|
288
375
|
sqlalchemy_db: SQLAlchemyDB,
|
289
376
|
scheduled_operations: list[ScheduledOperation] | None = None,
|
290
|
-
timeout_after_run=timedelta(seconds=0.1)
|
291
|
-
timeout_after_err_in_run=timedelta(seconds=1)
|
377
|
+
timeout_after_run=timedelta(seconds=0.1),
|
378
|
+
timeout_after_err_in_run=timedelta(seconds=1),
|
379
|
+
startup_funcs: list[Any] | None = None
|
292
380
|
):
|
293
|
-
super().__init__(
|
381
|
+
super().__init__(
|
382
|
+
timeout_after_run=timeout_after_run,
|
383
|
+
timeout_after_err_in_run=timeout_after_err_in_run,
|
384
|
+
startup_funcs=startup_funcs
|
385
|
+
)
|
294
386
|
self.sqlalchemy_db = sqlalchemy_db
|
295
|
-
self.timeout_after_run = timeout_after_run
|
296
|
-
self.timeout_after_err_in_run = timeout_after_err_in_run
|
297
387
|
if scheduled_operations is None:
|
298
388
|
scheduled_operations = []
|
299
389
|
self.scheduled_operations = scheduled_operations
|
300
390
|
|
301
391
|
def sync_on_startup(self):
|
302
392
|
self.sqlalchemy_db.init()
|
393
|
+
self.sync_run_startup_funcs()
|
303
394
|
|
304
395
|
def sync_run(self):
|
396
|
+
timeout = None
|
397
|
+
|
305
398
|
for scheduled_operation in self.scheduled_operations:
|
399
|
+
|
306
400
|
if not scheduled_operation.is_time_func():
|
307
401
|
continue
|
402
|
+
|
308
403
|
with self.sqlalchemy_db.new_session() as session:
|
309
404
|
operation_dbm = OperationDBM(
|
310
405
|
type=scheduled_operation.type,
|
@@ -314,13 +409,28 @@ class CreateScheduledOperationWorker(BaseWorker):
|
|
314
409
|
session.commit()
|
315
410
|
session.refresh(operation_dbm)
|
316
411
|
|
317
|
-
|
412
|
+
if scheduled_operation.timeout_after_creation is not None:
|
413
|
+
if timeout is not None:
|
414
|
+
if scheduled_operation.timeout_after_creation > timeout:
|
415
|
+
timeout = scheduled_operation.timeout_after_creation
|
416
|
+
else:
|
417
|
+
timeout = scheduled_operation.timeout_after_creation
|
418
|
+
|
419
|
+
if timeout is not None:
|
420
|
+
sync_safe_sleep(n=timeout)
|
421
|
+
|
422
|
+
async def async_on_startup(self):
|
318
423
|
self.sqlalchemy_db.init()
|
424
|
+
await self.async_run_startup_funcs()
|
425
|
+
|
426
|
+
async def async_run(self):
|
427
|
+
timeout: timedelta | None = None
|
319
428
|
|
320
|
-
def async_run(self):
|
321
429
|
for scheduled_operation in self.scheduled_operations:
|
430
|
+
|
322
431
|
if not scheduled_operation.is_time_func():
|
323
432
|
continue
|
433
|
+
|
324
434
|
with self.sqlalchemy_db.new_session() as session:
|
325
435
|
operation_dbm = OperationDBM(
|
326
436
|
type=scheduled_operation.type,
|
@@ -330,6 +440,41 @@ class CreateScheduledOperationWorker(BaseWorker):
|
|
330
440
|
session.commit()
|
331
441
|
session.refresh(operation_dbm)
|
332
442
|
|
443
|
+
if scheduled_operation.timeout_after_creation is not None:
|
444
|
+
if timeout is not None:
|
445
|
+
if scheduled_operation.timeout_after_creation > timeout:
|
446
|
+
timeout = scheduled_operation.timeout_after_creation
|
447
|
+
else:
|
448
|
+
timeout = scheduled_operation.timeout_after_creation
|
449
|
+
|
450
|
+
if timeout is not None:
|
451
|
+
await async_safe_sleep(n=timeout)
|
452
|
+
|
453
|
+
|
454
|
+
def every_timedelta_is_time_func(*, td: timedelta, now_dt_func: Callable = now_utc_dt) -> Callable:
|
455
|
+
last_now_utc_dt = now_utc_dt()
|
456
|
+
|
457
|
+
def func() -> bool:
|
458
|
+
nonlocal last_now_utc_dt
|
459
|
+
now_dt_func_ = now_dt_func()
|
460
|
+
if (now_dt_func_ - last_now_utc_dt) >= td:
|
461
|
+
last_now_utc_dt = now_dt_func_
|
462
|
+
return True
|
463
|
+
return False
|
464
|
+
|
465
|
+
return func
|
466
|
+
|
467
|
+
|
468
|
+
def between_different_times_is_time_func(
|
469
|
+
*, from_time: time, to_time: time, now_dt_func: Callable = now_utc_dt
|
470
|
+
) -> Callable:
|
471
|
+
def func() -> bool:
|
472
|
+
if from_time <= now_dt_func().time() <= to_time:
|
473
|
+
return True
|
474
|
+
return False
|
475
|
+
|
476
|
+
return func
|
477
|
+
|
333
478
|
|
334
479
|
def __example():
|
335
480
|
pass
|
arpakitlib/ar_parse_command.py
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# arpakit
|
2
|
-
|
2
|
+
import os
|
3
3
|
import shlex
|
4
|
-
from typing import Optional
|
5
4
|
|
6
5
|
from pydantic import BaseModel
|
7
6
|
|
7
|
+
from arpakitlib.ar_enumeration_util import ValuesForParseType
|
8
|
+
|
8
9
|
_ARPAKIT_LIB_MODULE_VERSION = "3.0"
|
9
10
|
|
10
11
|
|
@@ -14,9 +15,19 @@ class BadCommandFormat(Exception):
|
|
14
15
|
|
15
16
|
class ParsedCommand(BaseModel):
|
16
17
|
command: str
|
17
|
-
|
18
|
+
full_command: str
|
19
|
+
key_to_value: dict[str, str | None] = {}
|
18
20
|
values_without_key: list[str] = []
|
19
21
|
|
22
|
+
def raise_for_command(self, needed_command: str, lower_: bool = True):
|
23
|
+
needed_command = needed_command.strip()
|
24
|
+
|
25
|
+
if (
|
26
|
+
(self.command.lower() if lower_ else self.command)
|
27
|
+
!= (needed_command.lower() if lower_ else needed_command)
|
28
|
+
):
|
29
|
+
raise ValuesForParseType(f"needed_command != {self.command}, lower_={lower_}")
|
30
|
+
|
20
31
|
@property
|
21
32
|
def keys(self) -> list[str]:
|
22
33
|
return [k for k, v in self.key_to_value.items() if v is not None]
|
@@ -29,9 +40,15 @@ class ParsedCommand(BaseModel):
|
|
29
40
|
def values(self) -> list[str]:
|
30
41
|
return [self.key_to_value[k] for k in self.keys]
|
31
42
|
|
32
|
-
def get_value_by_key(self, key: str) ->
|
43
|
+
def get_value_by_key(self, key: str) -> str | None:
|
33
44
|
return self.key_to_value.get(key)
|
34
45
|
|
46
|
+
def get_value_by_keys(self, keys: list[str]) -> str | None:
|
47
|
+
for key in keys:
|
48
|
+
if self.key_exists(key=key):
|
49
|
+
return self.get_value_by_key(key=key)
|
50
|
+
return None
|
51
|
+
|
35
52
|
def key_exists(self, key: str) -> bool:
|
36
53
|
return key in self.key_to_value.keys()
|
37
54
|
|
@@ -44,13 +61,14 @@ class ParsedCommand(BaseModel):
|
|
44
61
|
def has_flag(self, flag: str) -> bool:
|
45
62
|
return flag in self.flags
|
46
63
|
|
47
|
-
def get_value_by_index(self, index: int) ->
|
64
|
+
def get_value_by_index(self, index: int) -> str | None:
|
48
65
|
if index >= len(self.values_without_key):
|
49
66
|
return None
|
50
67
|
return self.values_without_key[index]
|
51
68
|
|
52
69
|
|
53
70
|
def parse_command(text: str) -> ParsedCommand:
|
71
|
+
text = text.removeprefix("/")
|
54
72
|
text = " ".join([text_.strip() for text_ in text.split(" ") if text_.strip()]).strip()
|
55
73
|
|
56
74
|
parts = shlex.split(text)
|
@@ -59,9 +77,9 @@ def parse_command(text: str) -> ParsedCommand:
|
|
59
77
|
if len(parts[0]) == 1:
|
60
78
|
raise BadCommandFormat("len(parts[0]) == 1")
|
61
79
|
|
62
|
-
res = ParsedCommand(command=parts[0])
|
80
|
+
res = ParsedCommand(full_command=parts[0], command=os.path.basename(parts[0]).removeprefix("/"))
|
63
81
|
|
64
|
-
last_key:
|
82
|
+
last_key: str | None = None
|
65
83
|
for part in parts[1:]:
|
66
84
|
part = part.strip()
|
67
85
|
|