arpakitlib 1.5.34__tar.gz → 1.5.37__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/PKG-INFO +1 -1
  2. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_arpakit_schedule_uust_api_client.py +3 -3
  3. arpakitlib-1.5.34/arpakitlib/ar_enumeration.py → arpakitlib-1.5.37/arpakitlib/ar_enumeration_util.py +1 -1
  4. arpakitlib-1.5.37/arpakitlib/ar_fastapi_util.py +533 -0
  5. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_need_type_util.py +2 -2
  6. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_openai_util.py +1 -1
  7. arpakitlib-1.5.34/arpakitlib/ar_operation_util.py → arpakitlib-1.5.37/arpakitlib/ar_operation_execution_util.py +50 -112
  8. arpakitlib-1.5.37/arpakitlib/ar_sqlalchemy_model_util.py +122 -0
  9. arpakitlib-1.5.34/arpakitlib/ar_easy_sqlalchemy_util.py → arpakitlib-1.5.37/arpakitlib/ar_sqlalchemy_util.py +2 -16
  10. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_type_util.py +4 -8
  11. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_yookassa_api_client.py +2 -2
  12. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/pyproject.toml +1 -1
  13. arpakitlib-1.5.34/arpakitlib/ar_fastapi_util.py +0 -328
  14. arpakitlib-1.5.34/arpakitlib/ar_sqlalchemy_model_util.py +0 -47
  15. arpakitlib-1.5.34/arpakitlib/ar_story_log_util.py +0 -40
  16. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/LICENSE +0 -0
  17. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/NOTICE +0 -0
  18. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/README.md +0 -0
  19. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/AUTHOR.md +0 -0
  20. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/LICENSE +0 -0
  21. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/NOTICE +0 -0
  22. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/README.md +0 -0
  23. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/__init__.py +0 -0
  24. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_additional_model_util.py +0 -0
  25. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_aiogram_util.py +0 -0
  26. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_arpakit_lib_module_util.py +0 -0
  27. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_arpakitlib_info.py +0 -0
  28. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_base64_util.py +0 -0
  29. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_base_worker.py +0 -0
  30. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_cache_file.py +0 -0
  31. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_datetime_util.py +0 -0
  32. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_dict_util.py +0 -0
  33. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_dream_ai_api_client.py +0 -0
  34. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_encrypt_and_decrypt_util.py +0 -0
  35. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_fastapi_static/redoc/redoc.standalone.js +0 -0
  36. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_fastapi_static/swagger-ui/favicon-16x16.png +0 -0
  37. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_fastapi_static/swagger-ui/favicon-32x32.png +0 -0
  38. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_fastapi_static/swagger-ui/index.css +0 -0
  39. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_fastapi_static/swagger-ui/index.html +0 -0
  40. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_fastapi_static/swagger-ui/oauth2-redirect.html +0 -0
  41. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_fastapi_static/swagger-ui/swagger-initializer.js +0 -0
  42. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui-bundle.js +0 -0
  43. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui-bundle.js.map +0 -0
  44. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui-es-bundle-core.js +0 -0
  45. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui-es-bundle-core.js.map +0 -0
  46. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui-es-bundle.js +0 -0
  47. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui-es-bundle.js.map +0 -0
  48. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui-standalone-preset.js +0 -0
  49. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui-standalone-preset.js.map +0 -0
  50. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui.css +0 -0
  51. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui.css.map +0 -0
  52. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui.js +0 -0
  53. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui.js.map +0 -0
  54. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_file_storage_in_dir.py +0 -0
  55. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_generate_env_example.py +0 -0
  56. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_hash_util.py +0 -0
  57. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_http_request_util.py +0 -0
  58. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_ip_util.py +0 -0
  59. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_json_db.py +0 -0
  60. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_json_util.py +0 -0
  61. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_jwt_util.py +0 -0
  62. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_list_of_dicts_to_xlsx.py +0 -0
  63. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_list_util.py +0 -0
  64. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_logging_util.py +0 -0
  65. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_mongodb_util.py +0 -0
  66. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_parse_command.py +0 -0
  67. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_postgresql_util.py +0 -0
  68. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_run_cmd.py +0 -0
  69. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_schedule_uust_api_client.py +0 -0
  70. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_sleep_util.py +0 -0
  71. /arpakitlib-1.5.34/arpakitlib/ar_ssh_runner.py → /arpakitlib-1.5.37/arpakitlib/ar_ssh_util.py +0 -0
  72. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_str_util.py +0 -0
  73. {arpakitlib-1.5.34 → arpakitlib-1.5.37}/arpakitlib/ar_zabbix_util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: arpakitlib
3
- Version: 1.5.34
3
+ Version: 1.5.37
4
4
  Summary: arpakitlib
5
5
  Home-page: https://github.com/ARPAKIT-Company/arpakitlib
6
6
  License: Apache-2.0
@@ -16,7 +16,7 @@ from aiohttp import ClientResponse, ClientTimeout, ClientResponseError
16
16
  from pydantic import ConfigDict, BaseModel
17
17
 
18
18
  from arpakitlib.ar_dict_util import combine_dicts
19
- from arpakitlib.ar_enumeration import EasyEnumeration
19
+ from arpakitlib.ar_enumeration_util import Enumeration
20
20
  from arpakitlib.ar_json_util import safely_transfer_to_json_str
21
21
  from arpakitlib.ar_sleep_util import async_safe_sleep
22
22
  from arpakitlib.ar_type_util import raise_for_type
@@ -24,7 +24,7 @@ from arpakitlib.ar_type_util import raise_for_type
24
24
  _ARPAKIT_LIB_MODULE_VERSION = "3.0"
25
25
 
26
26
 
27
- class Weekdays(EasyEnumeration):
27
+ class Weekdays(Enumeration):
28
28
  monday = 1
29
29
  tuesday = 2
30
30
  wednesday = 3
@@ -34,7 +34,7 @@ class Weekdays(EasyEnumeration):
34
34
  sunday = 7
35
35
 
36
36
 
37
- class Months(EasyEnumeration):
37
+ class Months(Enumeration):
38
38
  january = 1
39
39
  february = 2
40
40
  march = 3
@@ -8,7 +8,7 @@ ValueType = Union[int, str]
8
8
  ValuesForParseType = Union[ValueType, Iterable[ValueType]]
9
9
 
10
10
 
11
- class EasyEnumeration:
11
+ class Enumeration:
12
12
  @classmethod
13
13
  def iter_values(cls) -> Iterator[ValueType]:
14
14
  big_dict = {}
@@ -0,0 +1,533 @@
1
+ # arpakit
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ import os.path
8
+ import pathlib
9
+ import threading
10
+ import traceback
11
+ from datetime import datetime
12
+ from typing import Any, Callable
13
+
14
+ import fastapi.exceptions
15
+ import fastapi.responses
16
+ import starlette.exceptions
17
+ import starlette.requests
18
+ import starlette.status
19
+ from fastapi import FastAPI, APIRouter, Query
20
+ from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html
21
+ from jaraco.context import suppress
22
+ from pydantic import BaseModel, ConfigDict
23
+ from starlette.middleware.cors import CORSMiddleware
24
+ from starlette.staticfiles import StaticFiles
25
+
26
+ from arpakitlib.ar_dict_util import combine_dicts
27
+ from arpakitlib.ar_enumeration_util import Enumeration
28
+ from arpakitlib.ar_json_util import safely_transfer_to_json_str_to_json_obj
29
+ from arpakitlib.ar_logging_util import setup_normal_logging
30
+ from arpakitlib.ar_operation_execution_util import ExecuteOperationWorker
31
+ from arpakitlib.ar_sqlalchemy_model_util import StoryLogDBM
32
+ from arpakitlib.ar_sqlalchemy_util import SQLAlchemyDB
33
+ from arpakitlib.ar_type_util import raise_for_type, raise_if_not_async_func
34
+
35
+ _ARPAKIT_LIB_MODULE_VERSION = "3.0"
36
+
37
+ _logger = logging.getLogger(__name__)
38
+
39
+
40
+ class BaseSchema(BaseModel):
41
+ model_config = ConfigDict(extra="ignore", arbitrary_types_allowed=True, from_attributes=True)
42
+
43
+ @classmethod
44
+ def __pydantic_init_subclass__(cls, **kwargs: Any) -> None:
45
+ if not (
46
+ cls.__name__.endswith("SO")
47
+ or cls.__name__.endswith("SI")
48
+ or cls.__name__.endswith("SchemaIn")
49
+ or cls.__name__.endswith("SchemaOut")
50
+ ):
51
+ raise ValueError("APISchema class should ends with SO | SI | SchemaIn | SchemaOut")
52
+ super().__init_subclass__(**kwargs)
53
+
54
+
55
+ class BaseSI(BaseSchema):
56
+ pass
57
+
58
+
59
+ class BaseSO(BaseSchema):
60
+ pass
61
+
62
+
63
+ class SimpleSO(BaseSO):
64
+ id: int
65
+ long_id: str
66
+ creation_dt: datetime
67
+
68
+
69
+ class ErrorSO(BaseSO):
70
+ class APIErrorCodes(Enumeration):
71
+ cannot_authorize = "CANNOT_AUTHORIZE"
72
+ unknown_error = "UNKNOWN_ERROR"
73
+ error_in_request = "ERROR_IN_REQUEST"
74
+ not_found = "NOT_FOUND"
75
+
76
+ has_error: bool = True
77
+ error_code: str | None = None
78
+ error_code_specification: str | None = None
79
+ error_description: str | None = None
80
+ error_data: dict[str, Any] = {}
81
+
82
+
83
+ class RawDataSO(BaseSO):
84
+ data: dict[str, Any] = {}
85
+
86
+
87
+ class StoryLogSO(SimpleSO):
88
+ level: str
89
+ title: str | None
90
+ data: dict[str, Any]
91
+
92
+
93
+ class OperationSO(SimpleSO):
94
+ execution_start_dt: datetime | None
95
+ execution_finish_dt: datetime | None
96
+ status: str
97
+ type: str
98
+ input_data: dict[str, Any]
99
+ output_data: dict[str, Any]
100
+ error_data: dict[str, Any]
101
+ duration_total_seconds: float | None
102
+
103
+
104
+ class APIJSONResponse(fastapi.responses.JSONResponse):
105
+ def __init__(self, *, content: BaseSO, status_code: int = starlette.status.HTTP_200_OK):
106
+ self.content_ = content
107
+ self.status_code_ = status_code
108
+ super().__init__(
109
+ content=safely_transfer_to_json_str_to_json_obj(content.model_dump()),
110
+ status_code=status_code
111
+ )
112
+
113
+
114
+ class APIException(fastapi.exceptions.HTTPException):
115
+ def __init__(
116
+ self,
117
+ *,
118
+ status_code: int = starlette.status.HTTP_400_BAD_REQUEST,
119
+ error_code: str | None = ErrorSO.APIErrorCodes.unknown_error,
120
+ error_code_specification: str | None = None,
121
+ error_description: str | None = None,
122
+ error_data: dict[str, Any] | None = None
123
+ ):
124
+ self.status_code = status_code
125
+ self.error_code = error_code
126
+ self.error_code_specification = error_code_specification
127
+ self.error_description = error_description
128
+ if error_data is None:
129
+ error_data = {}
130
+ self.error_data = error_data
131
+
132
+ self.error_so = ErrorSO(
133
+ has_error=True,
134
+ error_code=self.error_code,
135
+ error_specification=self.error_code_specification,
136
+ error_description=self.error_description,
137
+ error_data=self.error_data
138
+ )
139
+
140
+ super().__init__(
141
+ status_code=self.status_code,
142
+ detail=self.error_so.model_dump(mode="json")
143
+ )
144
+
145
+
146
+ def create_handle_exception(
147
+ *,
148
+ funcs_before_response: list[Callable] | None = None,
149
+ async_funcs_after_response: list[Callable] | None = None,
150
+ ) -> Any:
151
+ if funcs_before_response is None:
152
+ funcs_before_response = []
153
+
154
+ if async_funcs_after_response is None:
155
+ async_funcs_after_response = []
156
+
157
+ async def handle_exception(
158
+ request: starlette.requests.Request, exception: Exception
159
+ ) -> APIJSONResponse:
160
+ status_code = starlette.status.HTTP_500_INTERNAL_SERVER_ERROR
161
+
162
+ error_so = ErrorSO(
163
+ has_error=True,
164
+ error_code=ErrorSO.APIErrorCodes.unknown_error,
165
+ error_data={
166
+ "exception_type": str(type(exception)),
167
+ "exception_str": str(exception),
168
+ "request.method": str(request.method),
169
+ "request.url": str(request.url),
170
+ }
171
+ )
172
+
173
+ if isinstance(exception, APIException):
174
+ old_error_data = error_so.error_data
175
+ error_so = exception.error_so
176
+ error_so.error_data = combine_dicts(old_error_data, error_so.error_data)
177
+ _need_exc_info = False
178
+
179
+ elif isinstance(exception, starlette.exceptions.HTTPException):
180
+ status_code = exception.status_code
181
+ if status_code in (starlette.status.HTTP_403_FORBIDDEN, starlette.status.HTTP_401_UNAUTHORIZED):
182
+ error_so.error_code = ErrorSO.APIErrorCodes.cannot_authorize
183
+ _need_exc_info = False
184
+ elif status_code == starlette.status.HTTP_404_NOT_FOUND:
185
+ error_so.error_code = ErrorSO.APIErrorCodes.not_found
186
+ _need_exc_info = False
187
+ else:
188
+ status_code = starlette.status.HTTP_500_INTERNAL_SERVER_ERROR
189
+ _need_exc_info = True
190
+ with suppress(Exception):
191
+ error_so.error_data["exception.detail"] = exception.detail
192
+
193
+ elif isinstance(exception, fastapi.exceptions.RequestValidationError):
194
+ status_code = starlette.status.HTTP_422_UNPROCESSABLE_ENTITY
195
+ error_so.error_code = ErrorSO.APIErrorCodes.error_in_request
196
+ with suppress(Exception):
197
+ error_so.error_data["exception.errors"] = str(exception.errors()) if exception.errors() else {}
198
+ _need_exc_info = False
199
+
200
+ else:
201
+ status_code = starlette.status.HTTP_500_INTERNAL_SERVER_ERROR
202
+ error_so.error_code = ErrorSO.APIErrorCodes.unknown_error
203
+ _logger.exception(exception)
204
+ _need_exc_info = True
205
+
206
+ if error_so.error_code:
207
+ error_so.error_code = error_so.error_code.upper().replace(" ", "_").strip()
208
+
209
+ if error_so.error_code_specification:
210
+ error_so.error_code_specification = (
211
+ error_so.error_code_specification.upper().replace(" ", "_").strip()
212
+ )
213
+
214
+ if _need_exc_info:
215
+ _logger.error(str(exception), exc_info=exception)
216
+ else:
217
+ _logger.error(str(exception))
218
+
219
+ _kwargs = {}
220
+ for func in funcs_before_response:
221
+ data = func(
222
+ error_so=error_so, status_code=status_code, request=request, exception=exception, **_kwargs
223
+ )
224
+ if asyncio.iscoroutine(data):
225
+ data = await data
226
+ if data is not None:
227
+ status_code, error_so, _kwargs = data[0], data[1], data[2]
228
+ raise_for_type(status_code, int)
229
+ raise_for_type(error_so, ErrorSO)
230
+ raise_for_type(_kwargs, dict)
231
+
232
+ for async_func_after_response in async_funcs_after_response:
233
+ raise_if_not_async_func(async_func_after_response)
234
+ _ = asyncio.create_task(async_func_after_response(
235
+ error_so=error_so, status_code=status_code, request=request, exception=exception
236
+ ))
237
+
238
+ return APIJSONResponse(
239
+ content=error_so,
240
+ status_code=status_code
241
+ )
242
+
243
+ return handle_exception
244
+
245
+
246
+ def create_handle_exception_creating_story_log(
247
+ *,
248
+ sqlalchemy_db: SQLAlchemyDB
249
+ ) -> Callable:
250
+ def handle_exception(
251
+ *,
252
+ error_so: ErrorSO,
253
+ status_code: int,
254
+ request: starlette.requests.Request,
255
+ exception: Exception,
256
+ **kwargs
257
+ ) -> (int, ErrorSO, dict[str, Any]):
258
+ sqlalchemy_db.init()
259
+ traceback_str = "".join(traceback.format_exception(type(exception), exception, exception.__traceback__))
260
+ with sqlalchemy_db.new_session() as session:
261
+ story_log_dbm = StoryLogDBM(
262
+ level=StoryLogDBM.Levels.error,
263
+ title=str(exception),
264
+ data={
265
+ "error_so": error_so.model_dump(),
266
+ "traceback_str": traceback_str
267
+ }
268
+ )
269
+ session.add(story_log_dbm)
270
+ session.commit()
271
+ session.refresh(story_log_dbm)
272
+ error_so.error_data.update({"story_log_long_id": story_log_dbm.long_id})
273
+ kwargs["story_log_id"] = story_log_dbm.id
274
+ return status_code, error_so, kwargs
275
+
276
+ return handle_exception
277
+
278
+
279
+ def add_exception_handler_to_app(*, app: FastAPI, handle_exception: Callable) -> FastAPI:
280
+ app.add_exception_handler(
281
+ exc_class_or_status_code=Exception,
282
+ handler=handle_exception
283
+ )
284
+ app.add_exception_handler(
285
+ exc_class_or_status_code=ValueError,
286
+ handler=handle_exception
287
+ )
288
+ app.add_exception_handler(
289
+ exc_class_or_status_code=fastapi.exceptions.RequestValidationError,
290
+ handler=handle_exception
291
+ )
292
+ app.add_exception_handler(
293
+ exc_class_or_status_code=starlette.exceptions.HTTPException,
294
+ handler=handle_exception
295
+ )
296
+ return app
297
+
298
+
299
+ def add_swagger_to_app(
300
+ *,
301
+ app: FastAPI,
302
+ favicon_url: str | None = None
303
+ ):
304
+ app.mount(
305
+ "/ar_fastapi_static",
306
+ StaticFiles(directory=os.path.join(str(pathlib.Path(__file__).parent), "ar_fastapi_static")),
307
+ name="ar_fastapi_static"
308
+ )
309
+
310
+ @app.get("/docs", include_in_schema=False)
311
+ async def custom_swagger_ui_html():
312
+ return get_swagger_ui_html(
313
+ openapi_url=app.openapi_url,
314
+ title=app.title,
315
+ swagger_js_url="/ar_fastapi_static/swagger-ui/swagger-ui-bundle.js",
316
+ swagger_css_url="/ar_fastapi_static/swagger-ui/swagger-ui.css",
317
+ swagger_favicon_url=favicon_url
318
+ )
319
+
320
+ @app.get("/redoc", include_in_schema=False)
321
+ async def custom_redoc_html():
322
+ return get_redoc_html(
323
+ openapi_url=app.openapi_url,
324
+ title=app.title,
325
+ redoc_js_url="/ar_fastapi_static/redoc/redoc.standalone.js",
326
+ redoc_favicon_url=favicon_url
327
+ )
328
+
329
+ return app
330
+
331
+
332
+ def add_cors_to_app(*, app: FastAPI):
333
+ app.add_middleware(
334
+ CORSMiddleware,
335
+ allow_origins=["*"],
336
+ allow_credentials=True,
337
+ allow_methods=["*"],
338
+ allow_headers=["*"],
339
+ )
340
+ return app
341
+
342
+
343
+ def add_needed_api_router_to_app(*, app: FastAPI):
344
+ api_router = APIRouter()
345
+
346
+ @api_router.get(
347
+ "/healthcheck",
348
+ response_model=ErrorSO,
349
+ status_code=starlette.status.HTTP_200_OK,
350
+ tags=["Healthcheck"]
351
+ )
352
+ async def _():
353
+ return APIJSONResponse(
354
+ status_code=starlette.status.HTTP_200_OK,
355
+ content=RawDataSO(data={"healthcheck": "healthcheck"})
356
+ )
357
+
358
+ @api_router.get(
359
+ "/arpakitlib",
360
+ response_model=ErrorSO,
361
+ status_code=starlette.status.HTTP_200_OK,
362
+ tags=["arpakitlib"]
363
+ )
364
+ async def _():
365
+ return APIJSONResponse(
366
+ status_code=starlette.status.HTTP_200_OK,
367
+ content=RawDataSO(data={"arpakitlib": "arpakitlib"})
368
+ )
369
+
370
+ app.include_router(router=api_router, prefix="")
371
+
372
+ return app
373
+
374
+
375
+ class BaseStartupAPIEvent:
376
+ def __init__(self, *args, **kwargs):
377
+ self._logger = logging.getLogger(self.__class__.__name__)
378
+
379
+ async def async_on_startup(self, *args, **kwargs):
380
+ self._logger.info("on_startup starts")
381
+ self._logger.info("on_startup ends")
382
+
383
+
384
+ class InitSqlalchemyDBStartupAPIEvent(BaseStartupAPIEvent):
385
+ def __init__(self, sqlalchemy_db: SQLAlchemyDB):
386
+ super().__init__()
387
+ self.sqlalchemy_db = sqlalchemy_db
388
+
389
+ def async_on_startup(self, *args, **kwargs):
390
+ self.sqlalchemy_db.init()
391
+
392
+
393
+ class SyncSafeRunExecuteOperationWorkerStartupAPIEvent(BaseStartupAPIEvent):
394
+ def __init__(self, execute_operation_worker: ExecuteOperationWorker):
395
+ super().__init__()
396
+ self.execute_operation_worker = execute_operation_worker
397
+
398
+ def async_on_startup(self, *args, **kwargs):
399
+ thread = threading.Thread(
400
+ target=self.execute_operation_worker.sync_safe_run,
401
+ daemon=True
402
+ )
403
+ thread.start()
404
+
405
+
406
+ class AsyncSafeRunExecuteOperationWorkerStartupAPIEvent(BaseStartupAPIEvent):
407
+ def __init__(self, execute_operation_worker: ExecuteOperationWorker):
408
+ super().__init__()
409
+ self.execute_operation_worker = execute_operation_worker
410
+
411
+ def async_on_startup(self, *args, **kwargs):
412
+ _ = asyncio.create_task(self.execute_operation_worker.async_safe_run())
413
+
414
+
415
+ class BaseShutdownAPIEvent:
416
+ def __init__(self, *args, **kwargs):
417
+ self._logger = logging.getLogger(self.__class__.__name__)
418
+
419
+ async def async_on_shutdown(self, *args, **kwargs):
420
+ self._logger.info("on_shutdown starts")
421
+ self._logger.info("on_shutdown ends")
422
+
423
+
424
+ class BaseTransmittedAPIData(BaseModel):
425
+ model_config = ConfigDict(extra="ignore", arbitrary_types_allowed=True, from_attributes=True)
426
+
427
+
428
+ def get_transmitted_api_data(request: starlette.requests.Request) -> BaseTransmittedAPIData:
429
+ return request.app.state.transmitted_api_data
430
+
431
+
432
+ def simple_api_router_for_testing():
433
+ router = APIRouter(tags=["Testing"])
434
+
435
+ @router.get(
436
+ "/raise_fake_exception_1",
437
+ response_model=ErrorSO
438
+ )
439
+ async def _():
440
+ raise fastapi.HTTPException(status_code=starlette.status.HTTP_500_INTERNAL_SERVER_ERROR)
441
+
442
+ @router.get(
443
+ "/raise_fake_exception_2",
444
+ response_model=ErrorSO
445
+ )
446
+ async def _():
447
+ raise APIException(
448
+ error_code="raise_fake_exception_2",
449
+ error_code_specification="raise_fake_exception_2",
450
+ error_description="raise_fake_exception_2"
451
+ )
452
+
453
+ @router.get(
454
+ "/raise_fake_exception_3",
455
+ response_model=ErrorSO
456
+ )
457
+ async def _():
458
+ raise Exception("raise_fake_exception_3")
459
+
460
+ @router.get(
461
+ "/check_params",
462
+ response_model=ErrorSO
463
+ )
464
+ async def _(name: int = Query()):
465
+ return RawDataSO(data={"name": name})
466
+
467
+ return router
468
+
469
+
470
+ def create_fastapi_app(
471
+ *,
472
+ title: str = "ARPAKITLIB FastAPI",
473
+ description: str | None = "ARPAKITLIB FastAPI",
474
+ log_filepath: str | None = "./story.log",
475
+ handle_exception_: Callable | None = create_handle_exception(),
476
+ startup_api_events: list[BaseStartupAPIEvent] | None = None,
477
+ shutdown_api_events: list[BaseStartupAPIEvent] | None = None,
478
+ transmitted_api_data: BaseTransmittedAPIData = BaseTransmittedAPIData(),
479
+ api_router: APIRouter = simple_api_router_for_testing()
480
+ ):
481
+ setup_normal_logging(log_filepath=log_filepath)
482
+
483
+ if not startup_api_events:
484
+ startup_api_events = [BaseStartupAPIEvent()]
485
+
486
+ if not shutdown_api_events:
487
+ shutdown_api_events = [BaseShutdownAPIEvent()]
488
+
489
+ app = FastAPI(
490
+ title=title,
491
+ description=description,
492
+ docs_url=None,
493
+ redoc_url=None,
494
+ openapi_url="/openapi",
495
+ on_startup=[api_startup_event.async_on_startup for api_startup_event in startup_api_events],
496
+ on_shutdown=[api_shutdown_event.async_on_shutdown for api_shutdown_event in shutdown_api_events]
497
+ )
498
+
499
+ app.state.transmitted_api_data = transmitted_api_data
500
+
501
+ add_cors_to_app(app=app)
502
+
503
+ add_swagger_to_app(app=app)
504
+
505
+ if handle_exception_:
506
+ add_exception_handler_to_app(
507
+ app=app,
508
+ handle_exception=handle_exception_
509
+ )
510
+ else:
511
+ add_exception_handler_to_app(
512
+ app=app,
513
+ handle_exception=create_handle_exception()
514
+ )
515
+
516
+ add_needed_api_router_to_app(app=app)
517
+
518
+ app.include_router(router=api_router)
519
+
520
+ return app
521
+
522
+
523
+ def __example():
524
+ pass
525
+
526
+
527
+ async def __async_example():
528
+ pass
529
+
530
+
531
+ if __name__ == '__main__':
532
+ __example()
533
+ asyncio.run(__async_example())
@@ -3,12 +3,12 @@
3
3
  import json
4
4
  from typing import Any
5
5
 
6
- from arpakitlib.ar_enumeration import EasyEnumeration
6
+ from arpakitlib.ar_enumeration_util import Enumeration
7
7
 
8
8
  _ARPAKIT_LIB_MODULE_VERSION = "3.0"
9
9
 
10
10
 
11
- class NeedTypes(EasyEnumeration):
11
+ class NeedTypes(Enumeration):
12
12
  str_ = "str"
13
13
  int_ = "int"
14
14
  bool_ = "bool"
@@ -13,7 +13,7 @@ https://platform.openai.com/docs/
13
13
  """
14
14
 
15
15
 
16
- class EasyOpenAI:
16
+ class OpenAIAPIClient:
17
17
  def __init__(
18
18
  self,
19
19
  *,