arpakitlib 1.6.46__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.
Files changed (158) hide show
  1. arpakitlib/_arpakit_project_template/.gitignore +51 -0
  2. arpakitlib/_arpakit_project_template/.python-version +1 -0
  3. arpakitlib/_arpakit_project_template/ARPAKITLIB +1 -0
  4. arpakitlib/_arpakit_project_template/AUTHOR.md +4 -0
  5. arpakitlib/{LICENSE → _arpakit_project_template/LICENSE} +1 -1
  6. arpakitlib/_arpakit_project_template/NOTICE +16 -0
  7. arpakitlib/_arpakit_project_template/README.md +6 -0
  8. arpakitlib/_arpakit_project_template/example.env +22 -0
  9. arpakitlib/_arpakit_project_template/manage/__init__.py +0 -0
  10. arpakitlib/_arpakit_project_template/manage/docker_ps.sh +2 -0
  11. arpakitlib/_arpakit_project_template/manage/docker_ps_a.sh +2 -0
  12. arpakitlib/_arpakit_project_template/manage/docker_run_postgres_for_dev.sh +4 -0
  13. arpakitlib/_arpakit_project_template/manage/docker_start_postgres_for_dev.sh +2 -0
  14. arpakitlib/_arpakit_project_template/manage/docker_stop_postgres_for_dev.sh +2 -0
  15. arpakitlib/_arpakit_project_template/manage/example_nginx_proxy.nginx +14 -0
  16. arpakitlib/_arpakit_project_template/manage/example_poetry_arpakitlib.sh +1 -0
  17. arpakitlib/_arpakit_project_template/manage/example_pyproject.toml +18 -0
  18. arpakitlib/_arpakit_project_template/manage/example_systemd.service +12 -0
  19. arpakitlib/_arpakit_project_template/manage/git_branch.sh +2 -0
  20. arpakitlib/_arpakit_project_template/manage/git_commit.sh +3 -0
  21. arpakitlib/_arpakit_project_template/manage/git_push_arpakit_company_github_1.sh +4 -0
  22. arpakitlib/_arpakit_project_template/manage/git_push_arpakit_company_gitlab_1.sh +4 -0
  23. arpakitlib/_arpakit_project_template/manage/git_push_arpakit_github_1.sh +4 -0
  24. arpakitlib/_arpakit_project_template/manage/git_push_arpakit_gitlab_1.sh +4 -0
  25. arpakitlib/_arpakit_project_template/manage/git_remote_v.sh +2 -0
  26. arpakitlib/_arpakit_project_template/manage/git_set_arpakit_company_origin.sh +7 -0
  27. arpakitlib/_arpakit_project_template/manage/git_set_arpakit_origin.sh +7 -0
  28. arpakitlib/_arpakit_project_template/manage/git_status.sh +2 -0
  29. arpakitlib/_arpakit_project_template/manage/hello_world.py +6 -0
  30. arpakitlib/_arpakit_project_template/manage/json_beutify.py +10 -0
  31. arpakitlib/_arpakit_project_template/manage/logging_check.py +14 -0
  32. arpakitlib/_arpakit_project_template/manage/note/__init__.py +0 -0
  33. arpakitlib/_arpakit_project_template/manage/note/note_1.txt +0 -0
  34. arpakitlib/_arpakit_project_template/manage/note/note_2.txt +0 -0
  35. arpakitlib/_arpakit_project_template/manage/note/note_3.txt +0 -0
  36. arpakitlib/_arpakit_project_template/manage/note/note_4.txt +0 -0
  37. arpakitlib/_arpakit_project_template/manage/note/note_5.txt +0 -0
  38. arpakitlib/_arpakit_project_template/manage/poetry_add_plugin_export.sh +2 -0
  39. arpakitlib/_arpakit_project_template/manage/poetry_check.sh +2 -0
  40. arpakitlib/_arpakit_project_template/manage/poetry_clear_cache.sh +4 -0
  41. arpakitlib/_arpakit_project_template/manage/poetry_config_virtualenvs_in_project_true.sh +2 -0
  42. arpakitlib/_arpakit_project_template/manage/poetry_generate_requirements.txt.sh +1 -0
  43. arpakitlib/_arpakit_project_template/manage/poetry_install.sh +5 -0
  44. arpakitlib/_arpakit_project_template/manage/poetry_lock.sh +2 -0
  45. arpakitlib/_arpakit_project_template/manage/poetry_remove_and_add_arpakitlib.sh +7 -0
  46. arpakitlib/_arpakit_project_template/manage/poetry_show.sh +2 -0
  47. arpakitlib/_arpakit_project_template/manage/poetry_show_arpakitlib.sh +2 -0
  48. arpakitlib/_arpakit_project_template/manage/poetry_update.sh +6 -0
  49. arpakitlib/_arpakit_project_template/manage/poetry_update_arpakitlib.sh +5 -0
  50. arpakitlib/_arpakit_project_template/manage/requirements.txt +209 -0
  51. arpakitlib/_arpakit_project_template/manage/sandbox/__init__.py +0 -0
  52. arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_1.py +14 -0
  53. arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_2.py +14 -0
  54. arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_3.py +14 -0
  55. arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_4.py +14 -0
  56. arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_5.py +14 -0
  57. arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_6.py +14 -0
  58. arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_7.py +14 -0
  59. arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_8.sh +0 -0
  60. arpakitlib/_arpakit_project_template/manage/sandbox/sandbox_9.sh +0 -0
  61. arpakitlib/_arpakit_project_template/manage/settings_check.py +10 -0
  62. arpakitlib/_arpakit_project_template/manage/settings_generate_env_example.py +13 -0
  63. arpakitlib/_arpakit_project_template/manage/sqlalchemy_db_check_conn.py +11 -0
  64. arpakitlib/_arpakit_project_template/manage/sqlalchemy_db_init.py +11 -0
  65. arpakitlib/_arpakit_project_template/manage/sqlalchemy_db_reinit.py +11 -0
  66. arpakitlib/_arpakit_project_template/resource/__init__.py +0 -0
  67. arpakitlib/_arpakit_project_template/resource/static/__init__.py +0 -0
  68. arpakitlib/_arpakit_project_template/resource/static/healthcheck +1 -0
  69. arpakitlib/_arpakit_project_template/resource/static/helloworld +1 -0
  70. arpakitlib/_arpakit_project_template/src/__init__.py +0 -0
  71. arpakitlib/_arpakit_project_template/src/additional_model/__init__.py +0 -0
  72. arpakitlib/_arpakit_project_template/src/additional_model/additional_model.py +6 -0
  73. arpakitlib/_arpakit_project_template/src/api/__init__.py +0 -0
  74. arpakitlib/_arpakit_project_template/src/api/asgi.py +3 -0
  75. arpakitlib/_arpakit_project_template/src/api/auth.py +1 -0
  76. arpakitlib/_arpakit_project_template/src/api/const.py +13 -0
  77. arpakitlib/_arpakit_project_template/src/api/create_api_app.py +117 -0
  78. arpakitlib/_arpakit_project_template/src/api/event.py +20 -0
  79. arpakitlib/_arpakit_project_template/src/api/router/__init__.py +0 -0
  80. arpakitlib/_arpakit_project_template/src/api/router/main_router.py +9 -0
  81. arpakitlib/_arpakit_project_template/src/api/router/v1/__init__.py +0 -0
  82. arpakitlib/_arpakit_project_template/src/api/router/v1/get_api_error_info.py +27 -0
  83. arpakitlib/_arpakit_project_template/src/api/router/v1/main_router.py +11 -0
  84. arpakitlib/_arpakit_project_template/src/api/schema/__init__.py +0 -0
  85. arpakitlib/_arpakit_project_template/src/api/schema/v1/__init__.py +0 -0
  86. arpakitlib/_arpakit_project_template/src/api/schema/v1/in_.py +0 -0
  87. arpakitlib/_arpakit_project_template/src/api/schema/v1/out.py +6 -0
  88. arpakitlib/_arpakit_project_template/src/api/start_api_for_dev.py +17 -0
  89. arpakitlib/_arpakit_project_template/src/api/start_api_for_dev_with_reload.py +9 -0
  90. arpakitlib/_arpakit_project_template/src/api/transmitted_api_data.py +9 -0
  91. arpakitlib/_arpakit_project_template/src/api/util.py +0 -0
  92. arpakitlib/_arpakit_project_template/src/business_service/__init__.py +0 -0
  93. arpakitlib/_arpakit_project_template/src/core/__init__.py +0 -0
  94. arpakitlib/_arpakit_project_template/src/core/const.py +48 -0
  95. arpakitlib/_arpakit_project_template/src/core/settings.py +86 -0
  96. arpakitlib/_arpakit_project_template/src/core/util.py +58 -0
  97. arpakitlib/_arpakit_project_template/src/db/__init__.py +0 -0
  98. arpakitlib/_arpakit_project_template/src/db/sqlalchemy_model.py +8 -0
  99. arpakitlib/_arpakit_project_template/src/db/util.py +21 -0
  100. arpakitlib/_arpakit_project_template/src/operation_execution/__init__.py +0 -0
  101. arpakitlib/_arpakit_project_template/src/operation_execution/const.py +9 -0
  102. arpakitlib/_arpakit_project_template/src/operation_execution/operation_executor.py +14 -0
  103. arpakitlib/_arpakit_project_template/src/operation_execution/scheduled_operations.py +25 -0
  104. arpakitlib/_arpakit_project_template/src/operation_execution/start_operation_executor_worker_for_dev.py +18 -0
  105. arpakitlib/_arpakit_project_template/src/operation_execution/start_scheduled_operation_creator_worker_for_dev.py +17 -0
  106. arpakitlib/_arpakit_project_template/src/operation_execution/util.py +21 -0
  107. arpakitlib/_arpakit_project_template/src/test_data/__init__.py +0 -0
  108. arpakitlib/_arpakit_project_template/src/test_data/make_test_data_1.py +6 -0
  109. arpakitlib/_arpakit_project_template/src/test_data/make_test_data_2.py +6 -0
  110. arpakitlib/_arpakit_project_template/src/test_data/make_test_data_3.py +6 -0
  111. arpakitlib/_arpakit_project_template/src/test_data/make_test_data_4.py +6 -0
  112. arpakitlib/_arpakit_project_template/src/test_data/make_test_data_5.py +6 -0
  113. arpakitlib/_arpakit_project_template/src/util/__init__.py +0 -0
  114. arpakitlib/api_key_util.py +21 -0
  115. arpakitlib/ar_additional_model_util.py +25 -2
  116. arpakitlib/ar_aiogram_util.py +10 -18
  117. arpakitlib/ar_arpakit_lib_module_util.py +6 -1
  118. arpakitlib/ar_arpakit_project_template_util.py +96 -0
  119. arpakitlib/ar_arpakit_schedule_uust_api_client_util.py +24 -3
  120. arpakitlib/ar_arpakitlib_cli_util.py +79 -0
  121. arpakitlib/ar_base_worker_util.py +95 -48
  122. arpakitlib/ar_dream_ai_api_client_util.py +26 -52
  123. arpakitlib/ar_enumeration_util.py +11 -0
  124. arpakitlib/ar_exception_util.py +18 -0
  125. arpakitlib/ar_fastapi_static/healthcheck +1 -0
  126. arpakitlib/ar_fastapi_util.py +270 -137
  127. arpakitlib/ar_file_util.py +22 -0
  128. arpakitlib/ar_func_util.py +55 -0
  129. arpakitlib/ar_http_request_util.py +35 -6
  130. arpakitlib/ar_json_util.py +13 -7
  131. arpakitlib/ar_logging_util.py +5 -2
  132. arpakitlib/ar_need_type_util.py +12 -2
  133. arpakitlib/{ar_openai_util.py → ar_openai_api_client_util.py} +16 -2
  134. arpakitlib/ar_operation_execution_util.py +250 -105
  135. arpakitlib/ar_parse_command.py +25 -7
  136. arpakitlib/ar_schedule_uust_api_client_util.py +37 -23
  137. arpakitlib/ar_settings_util.py +37 -11
  138. arpakitlib/ar_sleep_util.py +0 -13
  139. arpakitlib/ar_sqlalchemy_model_util.py +35 -10
  140. arpakitlib/ar_sqlalchemy_util.py +4 -3
  141. arpakitlib/{ar_ssh_util.py → ar_ssh_runner_util.py} +2 -2
  142. arpakitlib/ar_str_util.py +43 -2
  143. arpakitlib/ar_type_util.py +68 -4
  144. arpakitlib/ar_yookassa_api_client_util.py +26 -44
  145. {arpakitlib-1.6.46.dist-info → arpakitlib-1.7.89.dist-info}/LICENSE +1 -1
  146. {arpakitlib-1.6.46.dist-info → arpakitlib-1.7.89.dist-info}/METADATA +17 -8
  147. arpakitlib-1.7.89.dist-info/NOTICE +16 -0
  148. arpakitlib-1.7.89.dist-info/RECORD +186 -0
  149. arpakitlib-1.7.89.dist-info/entry_points.txt +3 -0
  150. arpakitlib/AUTHOR.md +0 -7
  151. arpakitlib/NOTICE +0 -2
  152. arpakitlib/README.md +0 -7
  153. arpakitlib/ar_arpakitlib_info.py +0 -13
  154. arpakitlib/ar_generate_env_example.py +0 -22
  155. arpakitlib-1.6.46.dist-info/NOTICE +0 -2
  156. arpakitlib-1.6.46.dist-info/RECORD +0 -71
  157. /arpakitlib/{ar_zabbix_util.py → ar_zabbix_api_client_util.py} +0 -0
  158. {arpakitlib-1.6.46.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.ar_sqlalchemy_model_util import OperationDBM, StoryLogDBM
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
- sqlalchemy_db: SQLAlchemyDB,
30
- filter_operation_type: str | None = None
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
- with sqlalchemy_db.new_session() as session:
35
+ if isinstance(filter_operation_types, str):
36
+ filter_operation_types = [filter_operation_types]
37
+
38
+ def func(session_: Session):
33
39
  query = (
34
- session
40
+ session_
35
41
  .query(OperationDBM)
36
42
  .filter(OperationDBM.status == OperationDBM.Statuses.waiting_for_execution)
37
43
  )
38
- if filter_operation_type:
39
- query = query.filter(OperationDBM.type == filter_operation_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
- return operation_dbm
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
- query = (
52
- session
53
- .query(OperationDBM)
54
- .filter(OperationDBM.id == filter_operation_id)
55
- )
56
- if raise_if_not_found:
57
- return query.one()
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
- return query.one_or_none()
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
- async def async_execute_operation(self, operation_dbm: OperationDBM) -> OperationDBM:
68
- if operation_dbm.type == OperationDBM.Types.healthcheck_:
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 == OperationDBM.Types.raise_fake_exception_:
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
- async def async_safe_execute_operation(self, operation_dbm: OperationDBM) -> OperationDBM:
141
+ def sync_safe_execute_operation(self, operation_dbm: OperationDBM) -> OperationDBM:
76
142
  self._logger.info(
77
- f"start async_safe_execute_operation"
78
- f", operation_dbm.id={operation_dbm.id}"
79
- f", operation_dbm.type={operation_dbm.type}"
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
- await self.async_execute_operation(operation_dbm=operation_dbm)
162
+ self.sync_execute_operation(operation_dbm=operation_dbm)
96
163
  except BaseException as exception_:
97
- self._logger.exception(exception_)
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
- {"exception": str(exception), "traceback_str": traceback_str},
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
- story_log_dbm = StoryLogDBM(
118
- level=StoryLogDBM.Levels.error,
119
- title="Error in async_execute_operation",
120
- data={
121
- "operation_id": operation_dbm.id,
122
- "exception_str": str(exception),
123
- "traceback_str": traceback_str
124
- }
125
- )
126
- session.add(story_log_dbm)
127
- session.commit()
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 async_safe_execute_operation"
134
- f", operation_dbm.id={operation_dbm.id}"
135
- f", operation_dbm.type={operation_dbm.type}"
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 sync_execute_operation(self, operation_dbm: OperationDBM) -> OperationDBM:
141
- if operation_dbm.type == OperationDBM.Types.healthcheck_:
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 == OperationDBM.Types.raise_fake_exception_:
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 sync_safe_execute_operation(self, operation_dbm: OperationDBM) -> OperationDBM:
224
+ async def async_safe_execute_operation(self, operation_dbm: OperationDBM) -> OperationDBM:
149
225
  self._logger.info(
150
- f"start sync_safe_execute_operation"
151
- f", operation_dbm.id={operation_dbm.id}"
152
- f", operation_dbm.type={operation_dbm.type}"
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.sync_execute_operation(operation_dbm=operation_dbm)
245
+ await self.async_execute_operation(operation_dbm=operation_dbm)
169
246
  except BaseException as exception_:
170
- self._logger.exception(exception_)
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
- {"exception": str(exception), "traceback_str": traceback_str},
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
- story_log_dbm = StoryLogDBM(
191
- level=StoryLogDBM.Levels.error,
192
- title="Error in sync_execute_operation",
193
- data={
194
- "operation_id": operation_dbm.id,
195
- "exception_str": str(exception),
196
- "traceback_str": traceback_str
197
- }
198
- )
199
- session.add(story_log_dbm)
200
- session.commit()
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 sync_safe_execute_operation"
207
- f", operation_dbm.id={operation_dbm.id}"
208
- f", operation_dbm.type={operation_dbm.type}"
209
- f", operation_dbm.duration={operation_dbm.duration}"
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 ExecuteOperationWorker(BaseWorker):
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
- filter_operation_type: str | None = None,
223
- timeout_after_run=timedelta(seconds=0.1).total_seconds(),
224
- timeout_after_err_in_run=timedelta(seconds=1).total_seconds()
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.filter_operation_type = filter_operation_type
321
+ self.filter_operation_types = filter_operation_types
234
322
 
235
- async def async_on_startup(self):
323
+ def sync_on_startup(self):
236
324
  self.sqlalchemy_db.init()
325
+ self.sync_run_startup_funcs()
237
326
 
238
- async def async_execute_operation(self, operation_dbm: OperationDBM) -> OperationDBM:
239
- return await self.operation_executor.async_safe_execute_operation(operation_dbm=operation_dbm)
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
- async def async_run(self):
330
+ def sync_run(self):
242
331
  operation_dbm: OperationDBM | None = get_operation_for_execution(
243
332
  sqlalchemy_db=self.sqlalchemy_db,
244
- filter_operation_type=self.filter_operation_type
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
- await self.async_execute_operation(operation_dbm=operation_dbm)
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 sync_on_startup(self):
342
+ async def async_on_startup(self):
256
343
  self.sqlalchemy_db.init()
344
+ await self.async_run_startup_funcs()
257
345
 
258
- def sync_execute_operation(self, operation_dbm: OperationDBM) -> OperationDBM:
259
- return self.operation_executor.sync_safe_execute_operation(operation_dbm=operation_dbm)
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 sync_run(self):
349
+ async def async_run(self):
262
350
  operation_dbm: OperationDBM | None = get_operation_for_execution(
263
351
  sqlalchemy_db=self.sqlalchemy_db,
264
- filter_operation_type=self.filter_operation_type
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
- self.sync_execute_operation(operation_dbm=operation_dbm)
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 CreateScheduledOperationWorker(BaseWorker):
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).total_seconds(),
291
- timeout_after_err_in_run=timedelta(seconds=1).total_seconds()
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
- def async_on_startup(self):
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
@@ -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
- key_to_value: dict[str, Optional[str]] = {}
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) -> Optional[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) -> Optional[str]:
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: Optional[str] = None
82
+ last_key: str | None = None
65
83
  for part in parts[1:]:
66
84
  part = part.strip()
67
85