arpakitlib 1.4.0__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.
Potentially problematic release.
This version of arpakitlib might be problematic. Click here for more details.
- arpakitlib/AUTHOR.md +6 -0
- arpakitlib/LICENSE +201 -0
- arpakitlib/NOTICE +2 -0
- arpakitlib/README.md +6 -0
- arpakitlib/__init__.py +0 -0
- arpakitlib/ar_additional_model_util.py +8 -0
- arpakitlib/ar_aiogram_util.py +363 -0
- arpakitlib/ar_arpakit_lib_module_util.py +150 -0
- arpakitlib/ar_arpakit_schedule_uust_api_client.py +527 -0
- arpakitlib/ar_arpakitlib_info.py +11 -0
- arpakitlib/ar_base64_util.py +30 -0
- arpakitlib/ar_base_worker.py +77 -0
- arpakitlib/ar_cache_file.py +124 -0
- arpakitlib/ar_datetime_util.py +38 -0
- arpakitlib/ar_dict_util.py +24 -0
- arpakitlib/ar_dream_ai_api_client.py +120 -0
- arpakitlib/ar_encrypt_and_decrypt_util.py +23 -0
- arpakitlib/ar_enumeration.py +76 -0
- arpakitlib/ar_fastapi_static/redoc/redoc.standalone.js +1826 -0
- arpakitlib/ar_fastapi_static/swagger-ui/favicon-16x16.png +0 -0
- arpakitlib/ar_fastapi_static/swagger-ui/favicon-32x32.png +0 -0
- arpakitlib/ar_fastapi_static/swagger-ui/index.css +16 -0
- arpakitlib/ar_fastapi_static/swagger-ui/index.html +19 -0
- arpakitlib/ar_fastapi_static/swagger-ui/oauth2-redirect.html +79 -0
- arpakitlib/ar_fastapi_static/swagger-ui/swagger-initializer.js +20 -0
- arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui-bundle.js +2 -0
- arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui-bundle.js.map +1 -0
- arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui-es-bundle-core.js +3 -0
- arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui-es-bundle-core.js.map +1 -0
- arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui-es-bundle.js +2 -0
- arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui-es-bundle.js.map +1 -0
- arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui-standalone-preset.js +2 -0
- arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui-standalone-preset.js.map +1 -0
- arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui.css +3 -0
- arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui.css.map +1 -0
- arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui.js +2 -0
- arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui.js.map +1 -0
- arpakitlib/ar_fastapi_util.py +294 -0
- arpakitlib/ar_file_storage_in_dir.py +127 -0
- arpakitlib/ar_generate_env_example.py +16 -0
- arpakitlib/ar_hash_util.py +19 -0
- arpakitlib/ar_http_request_util.py +75 -0
- arpakitlib/ar_ip_util.py +50 -0
- arpakitlib/ar_json_db.py +231 -0
- arpakitlib/ar_json_util.py +28 -0
- arpakitlib/ar_jwt_util.py +38 -0
- arpakitlib/ar_list_of_dicts_to_xlsx.py +32 -0
- arpakitlib/ar_list_util.py +26 -0
- arpakitlib/ar_logging_util.py +45 -0
- arpakitlib/ar_mongodb_util.py +143 -0
- arpakitlib/ar_need_type_util.py +58 -0
- arpakitlib/ar_openai_util.py +59 -0
- arpakitlib/ar_parse_command.py +102 -0
- arpakitlib/ar_postgresql_util.py +45 -0
- arpakitlib/ar_run_cmd.py +48 -0
- arpakitlib/ar_safe_sleep.py +23 -0
- arpakitlib/ar_schedule_uust_api_client.py +216 -0
- arpakitlib/ar_sqlalchemy_util.py +124 -0
- arpakitlib/ar_ssh_runner.py +260 -0
- arpakitlib/ar_str_util.py +79 -0
- arpakitlib/ar_type_util.py +82 -0
- arpakitlib/ar_yookassa_api_client.py +224 -0
- arpakitlib/ar_zabbix_util.py +190 -0
- arpakitlib-1.4.0.dist-info/LICENSE +201 -0
- arpakitlib-1.4.0.dist-info/METADATA +327 -0
- arpakitlib-1.4.0.dist-info/NOTICE +2 -0
- arpakitlib-1.4.0.dist-info/RECORD +68 -0
- arpakitlib-1.4.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import os.path
|
|
4
|
+
import pathlib
|
|
5
|
+
from typing import Any, Callable
|
|
6
|
+
|
|
7
|
+
import fastapi.exceptions
|
|
8
|
+
import fastapi.responses
|
|
9
|
+
import starlette.exceptions
|
|
10
|
+
import starlette.requests
|
|
11
|
+
import starlette.status
|
|
12
|
+
from fastapi import FastAPI
|
|
13
|
+
from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html
|
|
14
|
+
from pydantic import BaseModel, ConfigDict
|
|
15
|
+
from starlette.middleware.cors import CORSMiddleware
|
|
16
|
+
from starlette.staticfiles import StaticFiles
|
|
17
|
+
|
|
18
|
+
from arpakitlib.ar_enumeration import EasyEnumeration
|
|
19
|
+
|
|
20
|
+
_ARPAKIT_LIB_MODULE_VERSION = "3.0"
|
|
21
|
+
|
|
22
|
+
_logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BaseAPISchema(BaseModel):
|
|
26
|
+
model_config = ConfigDict(extra="ignore", arbitrary_types_allowed=True, from_attributes=True)
|
|
27
|
+
_bus_data: dict[str, Any] = {}
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def __pydantic_init_subclass__(cls, **kwargs: Any) -> None:
|
|
31
|
+
if not (
|
|
32
|
+
cls.__name__.endswith("SO")
|
|
33
|
+
or cls.__name__.endswith("SI")
|
|
34
|
+
or cls.__name__.endswith("SchemaIn")
|
|
35
|
+
or cls.__name__.endswith("SchemaOut")
|
|
36
|
+
):
|
|
37
|
+
raise ValueError("APISchema class should ends with SO | SI | SchemaIn | SchemaOut")
|
|
38
|
+
super().__init_subclass__(**kwargs)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class BaseAPISI(BaseAPISchema):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class BaseAPISO(BaseAPISchema):
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class APIJSONResponse(fastapi.responses.JSONResponse):
|
|
50
|
+
def __init__(self, *, content: BaseAPISO, status_code: int = starlette.status.HTTP_200_OK):
|
|
51
|
+
super().__init__(
|
|
52
|
+
content=content.model_dump(mode="json"),
|
|
53
|
+
status_code=status_code
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class APIErrorSO(BaseAPISO):
|
|
58
|
+
class ErrorCodes(EasyEnumeration):
|
|
59
|
+
cannot_authorize = "CANNOT_AUTHORIZE"
|
|
60
|
+
unknown_error = "UNKNOWN_ERROR"
|
|
61
|
+
error_in_request = "ERROR_IN_REQUEST"
|
|
62
|
+
not_found = "NOT_FOUND"
|
|
63
|
+
|
|
64
|
+
has_error: bool = True
|
|
65
|
+
error_code: str | None = None
|
|
66
|
+
error_code_specification: str | None = None
|
|
67
|
+
error_description: str | None = None
|
|
68
|
+
error_data: dict[str, Any] = {}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class APIException(fastapi.exceptions.HTTPException):
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
*,
|
|
75
|
+
status_code: int = starlette.status.HTTP_400_BAD_REQUEST,
|
|
76
|
+
error_code: str | None = APIErrorSO.ErrorCodes.unknown_error,
|
|
77
|
+
error_code_specification: str | None = None,
|
|
78
|
+
error_description: str | None = None,
|
|
79
|
+
error_data: dict[str, Any] | None = None
|
|
80
|
+
):
|
|
81
|
+
self.status_code = status_code
|
|
82
|
+
self.error_code = error_code
|
|
83
|
+
self.error_code_specification = error_code_specification
|
|
84
|
+
self.error_description = error_description
|
|
85
|
+
if error_data is None:
|
|
86
|
+
error_data = {}
|
|
87
|
+
self.error_data = error_data
|
|
88
|
+
|
|
89
|
+
self.easy_api_error_so = APIErrorSO(
|
|
90
|
+
has_error=True,
|
|
91
|
+
error_code=self.error_code,
|
|
92
|
+
error_specification=self.error_code_specification,
|
|
93
|
+
error_description=self.error_description,
|
|
94
|
+
error_data=self.error_data
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
super().__init__(
|
|
98
|
+
status_code=self.status_code,
|
|
99
|
+
detail=self.easy_api_error_so.model_dump(mode="json")
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def simple_api_handle_exception(request: starlette.requests.Request, exception: Exception) -> APIJSONResponse:
|
|
104
|
+
return from_exception_to_api_json_response(request=request, exception=exception)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def from_exception_to_api_json_response(
|
|
108
|
+
request: starlette.requests.Request, exception: Exception
|
|
109
|
+
) -> APIJSONResponse:
|
|
110
|
+
easy_api_error_so = APIErrorSO(
|
|
111
|
+
has_error=True,
|
|
112
|
+
error_code=APIErrorSO.ErrorCodes.unknown_error
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
status_code = starlette.status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
116
|
+
|
|
117
|
+
if isinstance(exception, APIException):
|
|
118
|
+
easy_api_error_so = exception.easy_api_error_so
|
|
119
|
+
|
|
120
|
+
elif isinstance(exception, starlette.exceptions.HTTPException):
|
|
121
|
+
status_code = exception.status_code
|
|
122
|
+
if status_code in (starlette.status.HTTP_403_FORBIDDEN, starlette.status.HTTP_401_UNAUTHORIZED):
|
|
123
|
+
easy_api_error_so.error_code = APIErrorSO.ErrorCodes.cannot_authorize
|
|
124
|
+
elif status_code == starlette.status.HTTP_404_NOT_FOUND:
|
|
125
|
+
easy_api_error_so.error_code = APIErrorSO.ErrorCodes.not_found
|
|
126
|
+
else:
|
|
127
|
+
easy_api_error_so.error_code = APIErrorSO.ErrorCodes.unknown_error
|
|
128
|
+
if (
|
|
129
|
+
isinstance(exception.detail, dict)
|
|
130
|
+
or isinstance(exception.detail, list)
|
|
131
|
+
or isinstance(exception.detail, str)
|
|
132
|
+
or isinstance(exception.detail, int)
|
|
133
|
+
or isinstance(exception.detail, float)
|
|
134
|
+
or isinstance(exception.detail, bool)
|
|
135
|
+
):
|
|
136
|
+
easy_api_error_so.error_data["raw"] = exception.detail
|
|
137
|
+
|
|
138
|
+
elif isinstance(exception, fastapi.exceptions.RequestValidationError):
|
|
139
|
+
status_code = starlette.status.HTTP_422_UNPROCESSABLE_ENTITY
|
|
140
|
+
easy_api_error_so.error_code = APIErrorSO.ErrorCodes.error_in_request
|
|
141
|
+
easy_api_error_so.error_data["raw"] = str(exception.errors()) if exception.errors() else {}
|
|
142
|
+
|
|
143
|
+
else:
|
|
144
|
+
status_code = starlette.status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
145
|
+
easy_api_error_so.error_code = APIErrorSO.ErrorCodes.unknown_error
|
|
146
|
+
easy_api_error_so.error_data["raw"] = str(exception)
|
|
147
|
+
_logger.exception(exception)
|
|
148
|
+
|
|
149
|
+
if easy_api_error_so.error_code:
|
|
150
|
+
easy_api_error_so.error_code = easy_api_error_so.error_code.upper().replace(" ", "_").strip()
|
|
151
|
+
|
|
152
|
+
if easy_api_error_so.error_code_specification:
|
|
153
|
+
easy_api_error_so.error_code_specification = (
|
|
154
|
+
easy_api_error_so.error_code_specification.upper().replace(" ", "_").strip()
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
return APIJSONResponse(
|
|
158
|
+
content=easy_api_error_so,
|
|
159
|
+
status_code=status_code
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def add_exception_handler_to_fastapi_app(*, fastapi_app: FastAPI, api_handle_exception_: Callable) -> FastAPI:
|
|
164
|
+
fastapi_app.add_exception_handler(
|
|
165
|
+
exc_class_or_status_code=Exception,
|
|
166
|
+
handler=api_handle_exception_
|
|
167
|
+
)
|
|
168
|
+
fastapi_app.add_exception_handler(
|
|
169
|
+
exc_class_or_status_code=ValueError,
|
|
170
|
+
handler=api_handle_exception_
|
|
171
|
+
)
|
|
172
|
+
fastapi_app.add_exception_handler(
|
|
173
|
+
exc_class_or_status_code=fastapi.exceptions.RequestValidationError,
|
|
174
|
+
handler=api_handle_exception_
|
|
175
|
+
)
|
|
176
|
+
fastapi_app.add_exception_handler(
|
|
177
|
+
exc_class_or_status_code=starlette.exceptions.HTTPException,
|
|
178
|
+
handler=api_handle_exception_
|
|
179
|
+
)
|
|
180
|
+
return fastapi_app
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def add_middleware_cors_to_fastapi_app(*, fastapi_app: FastAPI) -> FastAPI:
|
|
184
|
+
fastapi_app.add_middleware(
|
|
185
|
+
CORSMiddleware,
|
|
186
|
+
allow_origins=["*"],
|
|
187
|
+
allow_credentials=True,
|
|
188
|
+
allow_methods=["*"],
|
|
189
|
+
allow_headers=["*"],
|
|
190
|
+
)
|
|
191
|
+
return fastapi_app
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def add_ar_fastapi_static_to_fastapi_app(*, fastapi_app: FastAPI):
|
|
195
|
+
ar_fastapi_static_dirpath = os.path.join(str(pathlib.Path(__file__).parent), "ar_fastapi_static")
|
|
196
|
+
fastapi_app.mount(
|
|
197
|
+
"/ar_fastapi_static",
|
|
198
|
+
StaticFiles(directory=ar_fastapi_static_dirpath),
|
|
199
|
+
name="ar_fastapi_static"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def add_ar_fastapi_static_docs_and_redoc_handlers_to_fastapi_app(
|
|
204
|
+
*,
|
|
205
|
+
fastapi_app: FastAPI,
|
|
206
|
+
favicon_url: str | None = None
|
|
207
|
+
):
|
|
208
|
+
add_ar_fastapi_static_to_fastapi_app(fastapi_app=fastapi_app)
|
|
209
|
+
|
|
210
|
+
@fastapi_app.get("/docs", include_in_schema=False)
|
|
211
|
+
async def custom_swagger_ui_html():
|
|
212
|
+
return get_swagger_ui_html(
|
|
213
|
+
openapi_url=fastapi_app.openapi_url,
|
|
214
|
+
title=fastapi_app.title,
|
|
215
|
+
swagger_js_url="/ar_fastapi_static/swagger-ui/swagger-ui-bundle.js",
|
|
216
|
+
swagger_css_url="/ar_fastapi_static/swagger-ui/swagger-ui.css",
|
|
217
|
+
swagger_favicon_url=favicon_url
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
@fastapi_app.get("/redoc", include_in_schema=False)
|
|
221
|
+
async def custom_redoc_html():
|
|
222
|
+
return get_redoc_html(
|
|
223
|
+
openapi_url=fastapi_app.openapi_url,
|
|
224
|
+
title=fastapi_app.title,
|
|
225
|
+
redoc_js_url="/ar_fastapi_static/redoc/redoc.standalone.js",
|
|
226
|
+
redoc_favicon_url=favicon_url
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
return fastapi_app
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class BaseAPIStartupEvent:
|
|
233
|
+
def __init__(self, *args, **kwargs):
|
|
234
|
+
self._logger = logging.getLogger(self.__class__.__name__)
|
|
235
|
+
|
|
236
|
+
async def on_startup(self, *args, **kwargs):
|
|
237
|
+
self._logger.info("on_startup")
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class BaseAPIShutdownEvent:
|
|
241
|
+
def __init__(self, *args, **kwargs):
|
|
242
|
+
self._logger = logging.getLogger(self.__class__.__name__)
|
|
243
|
+
|
|
244
|
+
async def on_startup(self, *args, **kwargs):
|
|
245
|
+
self._logger.info("on_startup")
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def create_fastapi_app(
|
|
249
|
+
*,
|
|
250
|
+
title: str,
|
|
251
|
+
description: str | None = None,
|
|
252
|
+
api_startup_event: BaseAPIStartupEvent | None = None,
|
|
253
|
+
api_shutdown_event: BaseAPIShutdownEvent | None = None,
|
|
254
|
+
api_handle_exception_: Callable | None = simple_api_handle_exception
|
|
255
|
+
):
|
|
256
|
+
app = FastAPI(
|
|
257
|
+
title=title,
|
|
258
|
+
description=description,
|
|
259
|
+
docs_url=None,
|
|
260
|
+
redoc_url=None,
|
|
261
|
+
openapi_url="/openapi",
|
|
262
|
+
on_startup=[api_startup_event.on_startup] if api_startup_event else [],
|
|
263
|
+
on_shutdown=[api_shutdown_event.on_startup] if api_shutdown_event else []
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
add_middleware_cors_to_fastapi_app(fastapi_app=app)
|
|
267
|
+
|
|
268
|
+
add_ar_fastapi_static_docs_and_redoc_handlers_to_fastapi_app(fastapi_app=app)
|
|
269
|
+
|
|
270
|
+
if api_handle_exception_:
|
|
271
|
+
add_exception_handler_to_fastapi_app(
|
|
272
|
+
fastapi_app=app,
|
|
273
|
+
api_handle_exception_=api_handle_exception_
|
|
274
|
+
)
|
|
275
|
+
else:
|
|
276
|
+
add_exception_handler_to_fastapi_app(
|
|
277
|
+
fastapi_app=app,
|
|
278
|
+
api_handle_exception_=simple_api_handle_exception
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
return app
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def __example():
|
|
285
|
+
pass
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
async def __async_example():
|
|
289
|
+
pass
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
if __name__ == '__main__':
|
|
293
|
+
__example()
|
|
294
|
+
asyncio.run(__async_example())
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import uuid
|
|
5
|
+
from typing import Optional, Union, Iterator
|
|
6
|
+
|
|
7
|
+
_ARPAKIT_LIB_MODULE_VERSION = "3.0"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FileStorageInDir:
|
|
11
|
+
|
|
12
|
+
def __init__(self, dirpath: str):
|
|
13
|
+
if not isinstance(dirpath, str):
|
|
14
|
+
raise TypeError("not isinstance(dirpath, str)")
|
|
15
|
+
self.dirpath = os.path.abspath(dirpath)
|
|
16
|
+
self._logger = logging.getLogger(self.__class__.__name__)
|
|
17
|
+
|
|
18
|
+
def __str__(self) -> str:
|
|
19
|
+
return self.dirpath
|
|
20
|
+
|
|
21
|
+
def __repr__(self) -> str:
|
|
22
|
+
return self.dirpath
|
|
23
|
+
|
|
24
|
+
def __len__(self) -> int:
|
|
25
|
+
return len(os.listdir(self.dirpath))
|
|
26
|
+
|
|
27
|
+
def __iter__(self) -> Iterator[str]:
|
|
28
|
+
self.init()
|
|
29
|
+
for path in self.get_paths():
|
|
30
|
+
yield path
|
|
31
|
+
|
|
32
|
+
def init(self):
|
|
33
|
+
if not os.path.exists(self.dirpath):
|
|
34
|
+
os.makedirs(self.dirpath, exist_ok=True)
|
|
35
|
+
|
|
36
|
+
def reinit(self):
|
|
37
|
+
if os.path.exists(self.dirpath):
|
|
38
|
+
shutil.rmtree(self.dirpath)
|
|
39
|
+
self.init()
|
|
40
|
+
|
|
41
|
+
def get_paths(self) -> list[str]:
|
|
42
|
+
return [os.path.join(self.dirpath, path) for path in os.listdir(self.dirpath)]
|
|
43
|
+
|
|
44
|
+
def generate_filepath(
|
|
45
|
+
self,
|
|
46
|
+
*,
|
|
47
|
+
filename: Optional[str] = None,
|
|
48
|
+
file_extension: Optional[str] = None,
|
|
49
|
+
content_to_write: Optional[Union[str, bytes]] = None,
|
|
50
|
+
create: bool = False,
|
|
51
|
+
raise_if_exists: bool = True
|
|
52
|
+
) -> str:
|
|
53
|
+
self.init()
|
|
54
|
+
|
|
55
|
+
if filename is not None:
|
|
56
|
+
if file_extension is not None:
|
|
57
|
+
filename += f".{file_extension}"
|
|
58
|
+
else:
|
|
59
|
+
filename = str(uuid.uuid4())
|
|
60
|
+
if file_extension is not None:
|
|
61
|
+
filename += f".{file_extension}"
|
|
62
|
+
while filename in os.listdir(self.dirpath):
|
|
63
|
+
filename = str(uuid.uuid4())
|
|
64
|
+
if file_extension is not None:
|
|
65
|
+
filename += f".{file_extension}"
|
|
66
|
+
|
|
67
|
+
if raise_if_exists and filename in os.listdir(self.dirpath):
|
|
68
|
+
raise ValueError(f"file ({filename}) already exists")
|
|
69
|
+
|
|
70
|
+
filepath = os.path.join(self.dirpath, filename)
|
|
71
|
+
|
|
72
|
+
if create is True:
|
|
73
|
+
content_to_write = ""
|
|
74
|
+
|
|
75
|
+
if isinstance(content_to_write, str):
|
|
76
|
+
content_to_write = content_to_write.encode()
|
|
77
|
+
if isinstance(content_to_write, bytes):
|
|
78
|
+
with open(filepath, mode="wb") as f:
|
|
79
|
+
f.write(content_to_write)
|
|
80
|
+
|
|
81
|
+
return filepath
|
|
82
|
+
|
|
83
|
+
def generate_dirpath(
|
|
84
|
+
self, *, dirname: Optional[str] = None, create: bool = True, raise_if_exists: bool = True
|
|
85
|
+
) -> str:
|
|
86
|
+
self.init()
|
|
87
|
+
|
|
88
|
+
if dirname is None:
|
|
89
|
+
dirname = str(uuid.uuid4())
|
|
90
|
+
while dirname in os.listdir(self.dirpath):
|
|
91
|
+
dirname = str(uuid.uuid4())
|
|
92
|
+
|
|
93
|
+
if raise_if_exists and dirname in os.listdir(self.dirpath):
|
|
94
|
+
raise ValueError(f"dir ({dirname}) already exists")
|
|
95
|
+
|
|
96
|
+
dirpath = os.path.join(self.dirpath, dirname)
|
|
97
|
+
|
|
98
|
+
if create:
|
|
99
|
+
os.mkdir(dirpath)
|
|
100
|
+
|
|
101
|
+
return dirpath
|
|
102
|
+
|
|
103
|
+
def rm_by_filepath(self, filepath: str):
|
|
104
|
+
if os.path.exists(filepath):
|
|
105
|
+
os.remove(filepath)
|
|
106
|
+
|
|
107
|
+
def rm_by_filename(self, filename: str):
|
|
108
|
+
filepath = os.path.join(self.dirpath, filename)
|
|
109
|
+
if os.path.exists(filepath):
|
|
110
|
+
os.remove(filepath)
|
|
111
|
+
|
|
112
|
+
def rm_by_dirpath(self, dirpath: str):
|
|
113
|
+
if os.path.exists(dirpath):
|
|
114
|
+
shutil.rmtree(dirpath)
|
|
115
|
+
|
|
116
|
+
def rm_by_dirname(self, dirname: str):
|
|
117
|
+
dirpath = os.path.join(self.dirpath, dirname)
|
|
118
|
+
if os.path.exists(dirpath):
|
|
119
|
+
shutil.rmtree(dirpath)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def __example():
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
if __name__ == '__main__':
|
|
127
|
+
__example()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
from pydantic_settings import BaseSettings
|
|
4
|
+
|
|
5
|
+
_ARPAKIT_LIB_MODULE_VERSION = "3.0"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def generate_env_example(settings_class: Union[BaseSettings, type[BaseSettings]]):
|
|
9
|
+
res = ""
|
|
10
|
+
for k in settings_class.model_fields:
|
|
11
|
+
res += f"{k}=\n"
|
|
12
|
+
return res
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def __example():
|
|
16
|
+
pass
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
|
|
3
|
+
_ARPAKIT_LIB_MODULE_VERSION = "3.0"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def hash_string(string: str) -> str:
|
|
7
|
+
return hashlib.sha256(string.encode()).hexdigest()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def check_string_hash(string: str, string_hash: str) -> bool:
|
|
11
|
+
return hash_string(string) == string_hash
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def __example():
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if __name__ == '__main__':
|
|
19
|
+
__example()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import inspect
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import timedelta
|
|
5
|
+
|
|
6
|
+
import aiohttp
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
from arpakitlib.ar_safe_sleep import safe_sleep
|
|
10
|
+
|
|
11
|
+
_ARPAKIT_LIB_MODULE_VERSION = "3.0"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def sync_make_request(*, method: str, url: str, **kwargs) -> requests.Response:
|
|
15
|
+
_logger = logging.getLogger(inspect.currentframe().f_code.co_name)
|
|
16
|
+
|
|
17
|
+
max_tries = 7
|
|
18
|
+
tries = 0
|
|
19
|
+
|
|
20
|
+
kwargs["method"] = method
|
|
21
|
+
kwargs["url"] = url
|
|
22
|
+
if "timeout" not in kwargs:
|
|
23
|
+
kwargs["timeout"] = (timedelta(seconds=15).total_seconds(), timedelta(seconds=15).total_seconds())
|
|
24
|
+
|
|
25
|
+
while True:
|
|
26
|
+
tries += 1
|
|
27
|
+
_logger.info(f"{method} {url}")
|
|
28
|
+
try:
|
|
29
|
+
return requests.request(**kwargs)
|
|
30
|
+
except Exception as err:
|
|
31
|
+
_logger.warning(f"{tries}/{max_tries} {method} {url} {err}")
|
|
32
|
+
if tries >= max_tries:
|
|
33
|
+
raise Exception(err)
|
|
34
|
+
safe_sleep(timedelta(seconds=0.1).total_seconds())
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def async_make_request(*, method: str, url: str, **kwargs) -> aiohttp.ClientResponse:
|
|
39
|
+
_logger = logging.getLogger(inspect.currentframe().f_code.co_name)
|
|
40
|
+
|
|
41
|
+
max_tries = 7
|
|
42
|
+
tries = 0
|
|
43
|
+
|
|
44
|
+
kwargs["method"] = method
|
|
45
|
+
kwargs["url"] = url
|
|
46
|
+
if "timeout" not in kwargs:
|
|
47
|
+
kwargs["timeout"] = aiohttp.ClientTimeout(total=timedelta(seconds=15).total_seconds())
|
|
48
|
+
|
|
49
|
+
while True:
|
|
50
|
+
tries += 1
|
|
51
|
+
_logger.info(f"{method} {url}")
|
|
52
|
+
try:
|
|
53
|
+
async with aiohttp.ClientSession() as session:
|
|
54
|
+
async with session.request(**kwargs) as response:
|
|
55
|
+
await response.read()
|
|
56
|
+
return response
|
|
57
|
+
except Exception as err:
|
|
58
|
+
_logger.warning(f"{tries}/{max_tries} {method} {url} {err}")
|
|
59
|
+
if tries >= max_tries:
|
|
60
|
+
raise Exception(err)
|
|
61
|
+
await asyncio.sleep(timedelta(seconds=0.1).total_seconds())
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def __example():
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
async def __async_example():
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == '__main__':
|
|
74
|
+
__example()
|
|
75
|
+
asyncio.run(__async_example())
|
arpakitlib/ar_ip_util.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import ipaddress
|
|
2
|
+
|
|
3
|
+
_ARPAKIT_LIB_MODULE_VERSION = "3.0"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def is_ipv4_address(value: str) -> bool:
|
|
7
|
+
try:
|
|
8
|
+
ipaddress.IPv4Address(value)
|
|
9
|
+
except ValueError:
|
|
10
|
+
return False
|
|
11
|
+
return True
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def raise_if_not_ipv4_address(value: str):
|
|
15
|
+
if not is_ipv4_address(value):
|
|
16
|
+
raise ValueError(f"not is_ipv4_address({value})")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def is_ipv6_address(value: str) -> bool:
|
|
20
|
+
try:
|
|
21
|
+
ipaddress.IPv6Address(value)
|
|
22
|
+
except ValueError:
|
|
23
|
+
return False
|
|
24
|
+
return True
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def raise_if_not_ipv6_address(value: str):
|
|
28
|
+
if not is_ipv6_address(value):
|
|
29
|
+
raise ValueError(f"not is_ipv6_address({value})")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def is_ipv4_interface(value: str) -> bool:
|
|
33
|
+
try:
|
|
34
|
+
ipaddress.IPv4Interface(value)
|
|
35
|
+
except ValueError:
|
|
36
|
+
return False
|
|
37
|
+
return True
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def raise_if_not_ipv4_interface(value: str):
|
|
41
|
+
if not is_ipv4_interface(value):
|
|
42
|
+
raise ValueError(f"not is_ipv4_interface({value})")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def __example():
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if __name__ == '__main__':
|
|
50
|
+
__example()
|