arpakitlib 1.7.242__py3-none-any.whl → 1.7.244__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.
@@ -9,7 +9,8 @@
9
9
  # api_init_sql_db_at_start=
10
10
  # api_title=
11
11
  # api_description=
12
- # api_create_story_log_before_response_in_handle_exception=
12
+ # api_logging_func_before_response=
13
+ # api_story_log_func_before_response=
13
14
  # api_start_operation_executor_worker=
14
15
  # api_start_scheduled_operation_creator_worker=
15
16
  # api_port=
@@ -1,11 +1,7 @@
1
1
  from fastapi import FastAPI
2
- from starlette import status
3
2
 
4
- from arpakitlib.ar_fastapi_util import create_fastapi_app, \
5
- create_handle_exception, create_story_log_before_response_in_handle_exception
6
- from arpakitlib.ar_sqlalchemy_util import SQLAlchemyDB
7
- from arpakitlib.ar_type_util import raise_for_type
8
- from src.api.const import APIErrorCodes
3
+ from arpakitlib.ar_fastapi_util import create_fastapi_app
4
+ from src.api.create_handle_exception_ import create_handle_exception_
9
5
  from src.api.event import StartupAPIEvent, ShutdownAPIEvent
10
6
  from src.api.router.main_router import main_api_router
11
7
  from src.api.transmitted_api_data import TransmittedAPIData
@@ -31,23 +27,6 @@ def create_api_app() -> FastAPI:
31
27
  dump_file_storage_in_dir=get_cached_dump_file_storage_in_dir()
32
28
  )
33
29
 
34
- funcs_before_response = []
35
-
36
- if settings.api_create_story_log_before_response_in_handle_exception:
37
- raise_for_type(sqlalchemy_db, SQLAlchemyDB)
38
- funcs_before_response.append(
39
- create_story_log_before_response_in_handle_exception(
40
- sqlalchemy_db=sqlalchemy_db,
41
- ignore_api_error_codes=[APIErrorCodes.not_found],
42
- ignore_status_codes=[status.HTTP_404_NOT_FOUND]
43
- )
44
- )
45
-
46
- handle_exception = create_handle_exception(
47
- funcs_before_response=funcs_before_response,
48
- async_funcs_after_response=[]
49
- )
50
-
51
30
  startup_api_events = []
52
31
 
53
32
  startup_api_events.append(StartupAPIEvent(transmitted_api_data=transmitted_api_data))
@@ -56,11 +35,13 @@ def create_api_app() -> FastAPI:
56
35
 
57
36
  shutdown_api_events.append(ShutdownAPIEvent(transmitted_api_data=transmitted_api_data))
58
37
 
38
+ handle_exception_ = create_handle_exception_(transmitted_api_data=transmitted_api_data)
39
+
59
40
  api_app = create_fastapi_app(
60
41
  title=settings.api_title.strip(),
61
42
  description=settings.api_description.strip(),
62
43
  log_filepath=settings.log_filepath,
63
- handle_exception_=handle_exception,
44
+ handle_exception_=handle_exception_,
64
45
  startup_api_events=startup_api_events,
65
46
  shutdown_api_events=shutdown_api_events,
66
47
  transmitted_api_data=transmitted_api_data,
@@ -0,0 +1,59 @@
1
+ import fastapi.exceptions
2
+ import starlette.exceptions
3
+ import starlette.status
4
+
5
+ from arpakitlib.ar_fastapi_util import create_handle_exception, story_log_func_before_response, \
6
+ logging_func_before_response
7
+ from src.api.const import APIErrorCodes
8
+ from src.api.transmitted_api_data import TransmittedAPIData
9
+
10
+
11
+ def create_handle_exception_(*, transmitted_api_data: TransmittedAPIData):
12
+ funcs_before_response = []
13
+
14
+ if transmitted_api_data.settings.api_logging_func_before_response:
15
+ funcs_before_response.append(
16
+ logging_func_before_response(
17
+ ignore_api_error_codes=[
18
+ APIErrorCodes.cannot_authorize,
19
+ APIErrorCodes.error_in_request,
20
+ APIErrorCodes.not_found
21
+ ],
22
+ ignore_status_codes=[
23
+ starlette.status.HTTP_401_UNAUTHORIZED,
24
+ starlette.status.HTTP_422_UNPROCESSABLE_ENTITY,
25
+ starlette.status.HTTP_404_NOT_FOUND
26
+ ],
27
+ ignore_exception_types=[
28
+ fastapi.exceptions.RequestValidationError
29
+ ],
30
+ need_exc_info=False
31
+ )
32
+ )
33
+
34
+ if transmitted_api_data.settings.api_story_log_func_before_response:
35
+ funcs_before_response.append(
36
+ story_log_func_before_response(
37
+ sqlalchemy_db=transmitted_api_data.sqlalchemy_db,
38
+ ignore_api_error_codes=[
39
+ APIErrorCodes.cannot_authorize,
40
+ APIErrorCodes.error_in_request,
41
+ APIErrorCodes.not_found
42
+ ],
43
+ ignore_status_codes=[
44
+ starlette.status.HTTP_401_UNAUTHORIZED,
45
+ starlette.status.HTTP_422_UNPROCESSABLE_ENTITY,
46
+ starlette.status.HTTP_404_NOT_FOUND
47
+ ],
48
+ ignore_exception_types=[
49
+ fastapi.exceptions.RequestValidationError
50
+ ],
51
+ )
52
+ )
53
+
54
+ async_funcs_after_response = []
55
+
56
+ return create_handle_exception(
57
+ funcs_before_response=funcs_before_response,
58
+ async_funcs_after_response=async_funcs_after_response
59
+ )
@@ -8,6 +8,7 @@ from pydantic_core.core_schema import ValidationInfo
8
8
 
9
9
  from arpakitlib.ar_json_util import safely_transfer_obj_to_json_str
10
10
  from arpakitlib.ar_settings_util import SimpleSettings
11
+ from arpakitlib.ar_sqlalchemy_util import generate_sqlalchemy_url
11
12
  from src.core.const import ProjectPaths
12
13
 
13
14
 
@@ -27,14 +28,40 @@ class Settings(SimpleSettings):
27
28
  @field_validator("sql_db_url", mode="after")
28
29
  def validate_sql_db_url(cls, v: Any, validation_info: ValidationInfo) -> str | None:
29
30
  if v is not None:
30
- return None
31
- user = validation_info.data.get('sql_db_user')
32
- password = validation_info.data.get('sql_db_password')
33
- port = validation_info.data.get('sql_db_port')
34
- database = validation_info.data.get('sql_db_database')
35
- if user is not None and password is not None and port is not None and database is not None:
36
- return f"postgresql://{user}:{password}@127.0.0.1:{port}/{database}"
37
- return None
31
+ return v
32
+
33
+ user = validation_info.data.get("sql_db_user")
34
+ password = validation_info.data.get("sql_db_password")
35
+ port = validation_info.data.get("sql_db_port")
36
+ database = validation_info.data.get("sql_db_database")
37
+
38
+ return generate_sqlalchemy_url(
39
+ base="postgresql",
40
+ user=user,
41
+ password=password,
42
+ port=port,
43
+ database=database
44
+ )
45
+
46
+ async_sql_db_url: str | None = None
47
+
48
+ @field_validator("async_sql_db_url", mode="after")
49
+ def validate_async_sql_db_url(cls, v: Any, validation_info: ValidationInfo) -> str | None:
50
+ if v is not None:
51
+ return v
52
+
53
+ user = validation_info.data.get("sql_db_user")
54
+ password = validation_info.data.get("sql_db_password")
55
+ port = validation_info.data.get("sql_db_port")
56
+ database = validation_info.data.get("sql_db_database")
57
+
58
+ return generate_sqlalchemy_url(
59
+ base="postgresql+asyncpg",
60
+ user=user,
61
+ password=password,
62
+ port=port,
63
+ database=database
64
+ )
38
65
 
39
66
  sql_db_echo: bool = False
40
67
 
@@ -44,7 +71,9 @@ class Settings(SimpleSettings):
44
71
 
45
72
  api_description: str = f"{project_name} (arpakitlib)"
46
73
 
47
- api_create_story_log_before_response_in_handle_exception: bool = True
74
+ api_logging_func_before_response: bool = True
75
+
76
+ api_story_log_func_before_response: bool = True
48
77
 
49
78
  api_start_operation_executor_worker: bool = False
50
79
 
@@ -16,6 +16,7 @@ def get_base_dbm() -> type[BaseDBM]:
16
16
  def create_sqlalchemy_db() -> SQLAlchemyDB:
17
17
  return SQLAlchemyDB(
18
18
  db_url=get_cached_settings().sql_db_url,
19
+ async_db_url=get_cached_settings().async_sql_db_url,
19
20
  db_echo=get_cached_settings().sql_db_echo,
20
21
  base_dbm=get_base_dbm()
21
22
  )
@@ -0,0 +1,10 @@
1
+ from emoji import emojize
2
+
3
+ from arpakitlib.ar_blank_util import BaseBlank
4
+
5
+
6
+ class TgBotBlank(BaseBlank):
7
+
8
+ def healthcheck(self) -> str:
9
+ res = "healthcheck"
10
+ return emojize(res.strip())
@@ -0,0 +1,12 @@
1
+ from functools import lru_cache
2
+
3
+ from src.tg_bot.blank.blank import TgBotBlank
4
+
5
+
6
+ def get_create_tg_bot_blank() -> TgBotBlank:
7
+ return TgBotBlank()
8
+
9
+
10
+ @lru_cache()
11
+ def get_cached_tg_bot_blank() -> TgBotBlank:
12
+ return get_create_tg_bot_blank()
@@ -0,0 +1,19 @@
1
+ from arpakitlib.ar_enumeration_util import Enumeration
2
+
3
+
4
+ class TgBotPublicCommands(Enumeration):
5
+ start = "start"
6
+
7
+
8
+ class TgBotPrivateCommands(Enumeration):
9
+ init_db = "init_db"
10
+ reinit_db = "reinit_db"
11
+ drop_db = "drop_db"
12
+ set_tg_bot_commands = "set_tg_bot_commands"
13
+ raise_fake_err = "raise_fake_err"
14
+ log_file = "log_file"
15
+ clear_log_file = "clear_log_file"
16
+ kb_with_old_data = "kb_with_old_data"
17
+ kb_with_not_modified = "kb_with_not_modified"
18
+ kb_with_fake_error = "kb_with_fake_error"
19
+ kb_with_remove_message = "kb_with_remove_message"
@@ -0,0 +1,3 @@
1
+ from aiogram import Router
2
+
3
+ tg_bot_router = Router()
@@ -0,0 +1,7 @@
1
+ from aiogram import Router
2
+
3
+ from src.tg_bot.router import error
4
+
5
+ main_tg_bot_router = Router()
6
+
7
+ main_tg_bot_router.include_router(error.tg_bot_router)
@@ -1,5 +1,11 @@
1
+ from src.core.settings import get_cached_settings
2
+ from src.core.util import setup_logging
3
+
4
+
1
5
  def start_tg_bot():
2
- pass
6
+ setup_logging()
7
+
8
+ settings = get_cached_settings()
3
9
 
4
10
 
5
11
  if __name__ == '__main__':
@@ -2,10 +2,9 @@
2
2
 
3
3
  import asyncio
4
4
  import logging
5
- from abc import ABC
6
5
  from typing import Optional, Any, Union, Callable, Iterable
7
6
 
8
- from aiogram import types, BaseMiddleware, Bot
7
+ from aiogram import types, Bot
9
8
  from aiogram.client.default import DefaultBotProperties
10
9
  from aiogram.client.session.aiohttp import AiohttpSession
11
10
  from aiogram.enums import ChatType, ParseMode
@@ -312,12 +311,6 @@ def as_tg_command(
312
311
  return decorator
313
312
 
314
313
 
315
- class SimpleMiddleware(BaseMiddleware, ABC):
316
- def __init__(self):
317
- self.middleware_name = self.__class__.__name__
318
- self._logger = logging.getLogger(self.__class__.__name__)
319
-
320
-
321
314
  class BaseTransmittedTgBotData(BaseModel):
322
315
  model_config = ConfigDict(extra="ignore", arbitrary_types_allowed=True, from_attributes=True)
323
316
 
@@ -0,0 +1,8 @@
1
+ # arpakit
2
+
3
+ _ARPAKIT_LIB_MODULE_VERSION = "3.0"
4
+
5
+
6
+ class BaseBlank:
7
+ def __init__(self, **kwargs):
8
+ pass
@@ -7,7 +7,6 @@ import datetime as dt
7
7
  import logging
8
8
  import os.path
9
9
  import pathlib
10
- import traceback
11
10
  from contextlib import suppress
12
11
  from typing import Any, Callable
13
12
 
@@ -27,8 +26,9 @@ from starlette.staticfiles import StaticFiles
27
26
 
28
27
  from arpakitlib.ar_dict_util import combine_dicts
29
28
  from arpakitlib.ar_enumeration_util import Enumeration
30
- from arpakitlib.ar_func_util import raise_if_not_async_func, is_async_function, is_async_object
31
- from arpakitlib.ar_json_util import safely_transfer_obj_to_json_str_to_json_obj
29
+ from arpakitlib.ar_exception_util import exception_to_traceback_str
30
+ from arpakitlib.ar_func_util import raise_if_not_async_func, is_async_object
31
+ from arpakitlib.ar_json_util import safely_transfer_obj_to_json_str_to_json_obj, safely_transfer_obj_to_json_str
32
32
  from arpakitlib.ar_logging_util import setup_normal_logging
33
33
  from arpakitlib.ar_sqlalchemy_model_util import StoryLogDBM, OperationDBM
34
34
  from arpakitlib.ar_sqlalchemy_util import SQLAlchemyDB
@@ -239,19 +239,15 @@ def create_handle_exception(
239
239
  old_error_data = error_so.error_data
240
240
  error_so = exception.error_so
241
241
  error_so.error_data = combine_dicts(old_error_data, error_so.error_data)
242
- _need_exc_info = False
243
242
 
244
243
  elif isinstance(exception, starlette.exceptions.HTTPException):
245
244
  status_code = exception.status_code
246
245
  if status_code in (starlette.status.HTTP_403_FORBIDDEN, starlette.status.HTTP_401_UNAUTHORIZED):
247
246
  error_so.error_code = BaseAPIErrorCodes.cannot_authorize
248
- _need_exc_info = False
249
247
  elif status_code == starlette.status.HTTP_404_NOT_FOUND:
250
248
  error_so.error_code = BaseAPIErrorCodes.not_found
251
- _need_exc_info = False
252
249
  else:
253
250
  status_code = starlette.status.HTTP_500_INTERNAL_SERVER_ERROR
254
- _need_exc_info = True
255
251
  with suppress(Exception):
256
252
  error_so.error_data["exception.detail"] = exception.detail
257
253
 
@@ -260,13 +256,10 @@ def create_handle_exception(
260
256
  error_so.error_code = BaseAPIErrorCodes.error_in_request
261
257
  with suppress(Exception):
262
258
  error_so.error_data["exception.errors"] = str(exception.errors()) if exception.errors() else {}
263
- _need_exc_info = False
264
259
 
265
260
  else:
266
261
  status_code = starlette.status.HTTP_500_INTERNAL_SERVER_ERROR
267
262
  error_so.error_code = BaseAPIErrorCodes.unknown_error
268
- _logger.exception(exception)
269
- _need_exc_info = True
270
263
 
271
264
  if error_so.error_code:
272
265
  error_so.error_code = error_so.error_code.upper().replace(" ", "_").strip()
@@ -282,17 +275,12 @@ def create_handle_exception(
282
275
  if error_so.error_code == BaseAPIErrorCodes.cannot_authorize:
283
276
  status_code = status.HTTP_401_UNAUTHORIZED
284
277
 
285
- if _need_exc_info:
286
- _logger.error(str(exception), exc_info=exception)
287
- else:
288
- _logger.error(str(exception))
289
-
290
278
  _kwargs = {}
291
279
  for func in funcs_before_response:
292
280
  _func_data = func(
293
281
  status_code=status_code, error_so=error_so, request=request, exception=exception, **_kwargs
294
282
  )
295
- if is_async_function(_func_data):
283
+ if is_async_object(_func_data):
296
284
  _func_data = await _func_data
297
285
  if _func_data is not None:
298
286
  status_code, error_so, _kwargs = _func_data[0], _func_data[1], _func_data[2]
@@ -314,13 +302,49 @@ def create_handle_exception(
314
302
  return handle_exception
315
303
 
316
304
 
317
- def create_story_log_before_response_in_handle_exception(
305
+ def logging_func_before_response(
306
+ *,
307
+ ignore_api_error_codes: list[str] | None = None,
308
+ ignore_status_codes: list[int] | None = None,
309
+ ignore_exception_types: list[type[Exception]] | None = None,
310
+ need_exc_info: bool = False
311
+ ):
312
+ def func(
313
+ *,
314
+ status_code: int,
315
+ error_so: ErrorSO,
316
+ request: starlette.requests.Request,
317
+ exception: Exception,
318
+ **kwargs
319
+ ) -> (int, ErrorSO, dict[str, Any]):
320
+ kwargs["logging_before_response_in_handle_exception"] = True
321
+
322
+ if ignore_api_error_codes and error_so.error_code in ignore_api_error_codes:
323
+ return status_code, error_so, kwargs
324
+
325
+ if ignore_status_codes and status_code in ignore_status_codes:
326
+ return status_code, error_so, kwargs
327
+
328
+ if ignore_exception_types and (
329
+ exception in ignore_exception_types or type(exception) in ignore_exception_types
330
+ ):
331
+ return status_code, error_so, kwargs
332
+
333
+ _logger.error(safely_transfer_obj_to_json_str(error_so.model_dump()), exc_info=need_exc_info)
334
+
335
+ return func
336
+
337
+
338
+ def story_log_func_before_response(
318
339
  *,
319
340
  sqlalchemy_db: SQLAlchemyDB,
320
341
  ignore_api_error_codes: list[str] | None = None,
321
- ignore_status_codes: list[int] | None = None
342
+ ignore_status_codes: list[int] | None = None,
343
+ ignore_exception_types: list[type[Exception]] | None = None
322
344
  ) -> Callable:
323
- def func(
345
+ raise_for_type(sqlalchemy_db, SQLAlchemyDB)
346
+
347
+ async def async_func(
324
348
  *,
325
349
  status_code: int,
326
350
  error_so: ErrorSO,
@@ -328,31 +352,38 @@ def create_story_log_before_response_in_handle_exception(
328
352
  exception: Exception,
329
353
  **kwargs
330
354
  ) -> (int, ErrorSO, dict[str, Any]):
355
+ kwargs["create_story_log_before_response_in_handle_exception"] = True
356
+
331
357
  if ignore_api_error_codes and error_so.error_code in ignore_api_error_codes:
332
358
  return status_code, error_so, kwargs
333
359
 
334
360
  if ignore_status_codes and status_code in ignore_status_codes:
335
361
  return status_code, error_so, kwargs
336
362
 
337
- sqlalchemy_db.init()
338
- traceback_str = "".join(traceback.format_exception(type(exception), exception, exception.__traceback__))
339
- with sqlalchemy_db.new_session() as session:
363
+ if ignore_exception_types and (
364
+ exception in ignore_exception_types or type(exception) in ignore_exception_types
365
+ ):
366
+ return status_code, error_so, kwargs
367
+
368
+ async with sqlalchemy_db.new_async_session() as session:
340
369
  story_log_dbm = StoryLogDBM(
341
370
  level=StoryLogDBM.Levels.error,
342
- title=str(exception),
371
+ title=f"{status_code}, {type(exception)}",
343
372
  data={
344
373
  "error_so": error_so.model_dump(),
345
- "traceback_str": traceback_str
374
+ "traceback_str": exception_to_traceback_str(exception=exception)
346
375
  }
347
376
  )
348
377
  session.add(story_log_dbm)
349
- session.commit()
350
- session.refresh(story_log_dbm)
378
+ await session.commit()
379
+ await session.refresh(story_log_dbm)
380
+
351
381
  error_so.error_data.update({"story_log_long_id": story_log_dbm.long_id})
352
382
  kwargs["story_log_id"] = story_log_dbm.id
383
+
353
384
  return status_code, error_so, kwargs
354
385
 
355
- return func
386
+ return async_func
356
387
 
357
388
 
358
389
  def add_exception_handler_to_app(*, app: FastAPI, handle_exception: Callable) -> FastAPI:
@@ -3,9 +3,11 @@ import asyncio
3
3
  import logging
4
4
  from datetime import timedelta, datetime
5
5
  from typing import Any
6
+ from urllib.parse import quote_plus
6
7
  from uuid import uuid4
7
8
 
8
- from sqlalchemy import create_engine, QueuePool, text, func
9
+ from sqlalchemy import create_engine, QueuePool, text, func, inspect, AsyncAdaptedQueuePool
10
+ from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
9
11
  from sqlalchemy.orm import sessionmaker, DeclarativeBase
10
12
  from sqlalchemy.orm.session import Session
11
13
 
@@ -22,28 +24,105 @@ def get_string_info_from_declarative_base(class_: type[DeclarativeBase]):
22
24
  return res
23
25
 
24
26
 
27
+ def generate_sqlalchemy_url(
28
+ *,
29
+ base: str = "postgresql",
30
+ user: str | None = None,
31
+ password: str | None = None,
32
+ host: str = "127.0.0.1",
33
+ port: int | None = None,
34
+ database: str | None = None,
35
+ **query_params
36
+ ) -> str:
37
+ """
38
+ Генерация URL для SQLAlchemy.
39
+
40
+ :param base: Базовая строка для подключения, например "postgresql+asyncpg" или "sqlite".
41
+ :param user: Имя пользователя (необязательно).
42
+ :param password: Пароль (необязательно).
43
+ :param host: Хост (по умолчанию "127.0.0.1").
44
+ :param port: Порт (необязательно).
45
+ :param database: Имя базы данных или путь к файлу для SQLite (необязательно).
46
+ :param query_params: Дополнительные параметры строки подключения.
47
+ :return: Строка подключения для SQLAlchemy.
48
+ """
49
+ # Формируем часть с авторизацией
50
+ auth_part = ""
51
+ if user and password:
52
+ auth_part = f"{quote_plus(user)}:{quote_plus(password)}@"
53
+ elif user:
54
+ auth_part = f"{quote_plus(user)}@"
55
+
56
+ # Формируем часть с хостом и портом
57
+ host_part = ""
58
+ if base.startswith("sqlite"):
59
+ # Для SQLite хост и порт не нужны
60
+ host_part = ""
61
+ else:
62
+ host_part = f"{host}"
63
+ if port:
64
+ host_part += f":{port}"
65
+
66
+ # Формируем часть с базой данных
67
+ database_part = f"/{database}" if database else ""
68
+ if base.startswith("sqlite") and database:
69
+ # Для SQLite путь указывается как абсолютный
70
+ database_part = f"/{database}"
71
+
72
+ # Дополнительные параметры
73
+ query_part = ""
74
+ if query_params:
75
+ query_items = [f"{key}={quote_plus(str(value))}" for key, value in query_params.items()]
76
+ query_part = f"?{'&'.join(query_items)}"
77
+
78
+ return f"{base}://{auth_part}{host_part}{database_part}{query_part}"
79
+
80
+
25
81
  class SQLAlchemyDB:
26
82
  def __init__(
27
83
  self,
28
84
  *,
29
- db_url: str = "postgresql://arpakitlib:arpakitlib@localhost:50629/arpakitlib",
85
+ db_url: str | None = "postgresql://arpakitlib:arpakitlib@localhost:50517/arpakitlib",
86
+ async_db_url: str | None = "postgresql+asyncpg://arpakitlib:arpakitlib@localhost:50517/arpakitlib",
30
87
  db_echo: bool = False,
31
88
  base_dbm: type[BaseDBM] | None = None,
32
89
  db_models: list[Any] | None = None,
33
90
  ):
34
- self._logger = logging.getLogger(self.__class__.__name__)
35
- self.engine = create_engine(
36
- url=db_url,
37
- echo=db_echo,
38
- pool_size=5,
39
- max_overflow=10,
40
- poolclass=QueuePool,
41
- pool_timeout=timedelta(seconds=30).total_seconds()
42
- )
91
+ self._logger = logging.getLogger()
92
+
93
+ self.db_url = db_url
94
+ if self.db_url is not None:
95
+ self.engine = create_engine(
96
+ url=db_url,
97
+ echo=db_echo,
98
+ pool_size=10,
99
+ max_overflow=10,
100
+ poolclass=QueuePool,
101
+ pool_timeout=timedelta(seconds=30).total_seconds(),
102
+ )
43
103
  self.sessionmaker = sessionmaker(bind=self.engine)
44
104
  self.func_new_session_counter = 0
105
+
106
+ self.async_db_url = async_db_url
107
+ if self.async_db_url is not None:
108
+ self.async_engine = create_async_engine(
109
+ url=async_db_url,
110
+ echo=db_echo,
111
+ pool_size=10,
112
+ max_overflow=10,
113
+ poolclass=AsyncAdaptedQueuePool,
114
+ pool_timeout=timedelta(seconds=30).total_seconds()
115
+ )
116
+ self.async_sessionmaker = async_sessionmaker(bind=self.async_engine)
117
+ self.func_new_async_session_counter = 0
118
+
45
119
  self.base_dbm = base_dbm
46
120
 
121
+ def is_table_exists(self, table_name: str) -> bool:
122
+ with self.engine.connect() as connection:
123
+ inspector = inspect(connection)
124
+ return table_name in inspector.get_table_names()
125
+
47
126
  def drop_celery_tables(self):
48
127
  with self.engine.connect() as connection:
49
128
  connection.execute(text("DROP TABLE IF EXISTS celery_tasksetmeta CASCADE;"))
@@ -52,32 +131,42 @@ class SQLAlchemyDB:
52
131
  self._logger.info("celery tables were dropped")
53
132
 
54
133
  def remove_celery_tables_data(self):
134
+ if not self.is_table_exists("celery_tasksetmeta"):
135
+ self._logger.info("table celery_tasksetmeta not exists")
136
+ return
55
137
  with self.engine.connect() as connection:
56
138
  connection.execute(text("DELETE FROM celery_tasksetmeta;"))
57
139
  connection.execute(text("DELETE FROM celery_taskmeta;"))
58
140
  connection.commit()
59
141
  self._logger.info("celery tables data were removed")
60
142
 
143
+ def drop_alembic_tables(self):
144
+ with self.engine.connect() as connection:
145
+ connection.execute(text("DROP TABLE IF EXISTS alembic_version CASCADE;"))
146
+ connection.execute(text("DROP TABLE IF EXISTS alembic_version CASCADE;"))
147
+ connection.commit()
148
+ self._logger.info("alembic_version tables were dropped")
149
+
61
150
  def remove_alembic_tables_data(self):
151
+ if not self.is_table_exists("alembic_version"):
152
+ self._logger.info("table alembic_version not exists")
153
+ return
62
154
  with self.engine.connect() as connection:
63
155
  connection.execute(text("DELETE FROM alembic_version;"))
64
156
  connection.commit()
65
157
  self._logger.info("alembic tables data were removed")
66
158
 
67
159
  def init(self):
68
- from arpakitlib.ar_sqlalchemy_model_util import BaseDBM
69
- BaseDBM.metadata.create_all(bind=self.engine, checkfirst=True)
160
+ self.base_dbm.metadata.create_all(bind=self.engine, checkfirst=True)
70
161
  self._logger.info("db was inited")
71
162
 
72
163
  def drop(self):
73
- from arpakitlib.ar_sqlalchemy_model_util import BaseDBM
74
- BaseDBM.metadata.drop_all(bind=self.engine, checkfirst=True)
164
+ self.base_dbm.metadata.drop_all(bind=self.engine, checkfirst=True)
75
165
  self._logger.info("db was dropped")
76
166
 
77
167
  def reinit(self):
78
- from arpakitlib.ar_sqlalchemy_model_util import BaseDBM
79
- BaseDBM.metadata.drop_all(bind=self.engine, checkfirst=True)
80
- BaseDBM.metadata.create_all(bind=self.engine, checkfirst=True)
168
+ self.base_dbm.metadata.drop_all(bind=self.engine, checkfirst=True)
169
+ self.base_dbm.metadata.create_all(bind=self.engine, checkfirst=True)
81
170
  self._logger.info("db was reinited")
82
171
 
83
172
  def check_conn(self):
@@ -88,6 +177,10 @@ class SQLAlchemyDB:
88
177
  self.func_new_session_counter += 1
89
178
  return self.sessionmaker(**kwargs)
90
179
 
180
+ def new_async_session(self, **kwargs) -> AsyncSession:
181
+ self.func_new_async_session_counter += 1
182
+ return self.async_sessionmaker(**kwargs)
183
+
91
184
  def is_conn_good(self) -> bool:
92
185
  try:
93
186
  self.check_conn()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: arpakitlib
3
- Version: 1.7.242
3
+ Version: 1.7.244
4
4
  Summary: arpakitlib
5
5
  License: Apache-2.0
6
6
  Keywords: arpakitlib,arpakit,arpakit-company,arpakitcompany,arpakit_company
@@ -39,6 +39,7 @@ Requires-Dist: paramiko (>=3.5.0,<4.0.0)
39
39
  Requires-Dist: pika (>=1.3.2,<2.0.0)
40
40
  Requires-Dist: poetry (>=2.0.1,<3.0.0)
41
41
  Requires-Dist: psycopg2-binary (>=2.9.10,<3.0.0)
42
+ Requires-Dist: pulp (>=2.9.0,<3.0.0)
42
43
  Requires-Dist: pydantic (>=2.10.5,<3.0.0)
43
44
  Requires-Dist: pydantic-settings (>=2.7.1,<3.0.0)
44
45
  Requires-Dist: pyjwt (>=2.10.1,<3.0.0)
@@ -46,6 +47,7 @@ Requires-Dist: pymongo (>=4.10.1,<5.0.0)
46
47
  Requires-Dist: pytz (>=2024.2,<2025.0)
47
48
  Requires-Dist: pyzabbix (>=1.3.1,<2.0.0)
48
49
  Requires-Dist: requests[socks] (>=2.32.3,<3.0.0)
50
+ Requires-Dist: scipy (>=1.15.1,<2.0.0)
49
51
  Requires-Dist: sqladmin (>=0.20.1,<0.21.0)
50
52
  Requires-Dist: sqlalchemy (>=2.0.37,<3.0.0)
51
53
  Requires-Dist: twine (>=6.1.0,<7.0.0)
@@ -4,7 +4,7 @@ arpakitlib/_arpakit_project_template/.python-version,sha256=XMd40XBnlTFfBSmMldd-
4
4
  arpakitlib/_arpakit_project_template/ARPAKITLIB,sha256=3-iAkMXtesLzJXHw_IIv2k2M0oH8cTjHzW22Vvbi0IE,4
5
5
  arpakitlib/_arpakit_project_template/LICENSE,sha256=GPEDQMam2r7FSTYqM1mm7aKnxLaWcBotH7UvQtea-ec,11355
6
6
  arpakitlib/_arpakit_project_template/README.md,sha256=AwqCtmMeywF2dJhZbKwCBA_wPnLF_VmoLGfPbFjH3bM,62
7
- arpakitlib/_arpakit_project_template/example.env,sha256=Ko4SnDj-vlgfjmSDiZsu3qw8SJQaDgeTCnwc5RZfHFI,611
7
+ arpakitlib/_arpakit_project_template/example.env,sha256=ZlAMMUEHEYncVpGu2urz-P34CDG5Ljs8_lPp-oCCpYI,625
8
8
  arpakitlib/_arpakit_project_template/manage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  arpakitlib/_arpakit_project_template/manage/docker_ps.sh,sha256=uwm8vHgeuNLCOn0o9hgP_uc-PUkS9FwLyzZh6ItZ3do,15
10
10
  arpakitlib/_arpakit_project_template/manage/docker_ps_a.sh,sha256=nOQejihYlzstg9oROvYwHIsSLt2Sw0DWQEeT3GBaBNs,18
@@ -69,7 +69,8 @@ arpakitlib/_arpakit_project_template/src/api/_start_api_without_reload.py,sha256
69
69
  arpakitlib/_arpakit_project_template/src/api/asgi.py,sha256=a5UBxOyNC8NG3E0ayhiDo3t5tPoB3WtOf2gbZJFWBAA,74
70
70
  arpakitlib/_arpakit_project_template/src/api/auth.py,sha256=dcvj5C9E2F2KCsGZPBBncQf_EvVJAC1qQgnyD8P4ZEw,6
71
71
  arpakitlib/_arpakit_project_template/src/api/const.py,sha256=7d4qD5hedqr7QxVzbfsA7E1bNZn2Pm2U8joXGtpANu0,287
72
- arpakitlib/_arpakit_project_template/src/api/create_api_app.py,sha256=BeH-GxQyzx7t3vzUQHM9xdKEJTyKJiRjBtd2KR0D_n4,2915
72
+ arpakitlib/_arpakit_project_template/src/api/create_api_app.py,sha256=lpn_AYx39hyuwOev9Yzrji_2gJfRSgP3G1-CXTbz9rU,2211
73
+ arpakitlib/_arpakit_project_template/src/api/create_handle_exception_.py,sha256=Q0YvleHXdKQKVXslXUgSdTqkEWHVkfE0m8vrpiJSb-c,2213
73
74
  arpakitlib/_arpakit_project_template/src/api/event.py,sha256=Ld4Ww3QyglvqEDm8LD-VmBB4KwkaXj9vc7JFlbJcEkM,3035
74
75
  arpakitlib/_arpakit_project_template/src/api/router/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
76
  arpakitlib/_arpakit_project_template/src/api/router/main_router.py,sha256=Yv699WCJDcdiJMXFg1kPTvolqj-NAGoXfqe-vzbMzIU,228
@@ -89,7 +90,7 @@ arpakitlib/_arpakit_project_template/src/core/_check_logging.py,sha256=APQp8jQa3
89
90
  arpakitlib/_arpakit_project_template/src/core/_check_settings.py,sha256=BQUcr-yj3cxz5GQo0jXe99wMoqHKrrrKD3-UovsJAt0,284
90
91
  arpakitlib/_arpakit_project_template/src/core/_generate_settings_env_example.py,sha256=SiEJe8AYQPOWicsaCwf9RdXp6UAmfkBdBT18AAInGb4,483
91
92
  arpakitlib/_arpakit_project_template/src/core/const.py,sha256=e2Y0NIQHfzm3bmnbQnGy3Z5YKt6OYnIsRoqVY8oIV2E,1008
92
- arpakitlib/_arpakit_project_template/src/core/settings.py,sha256=a_HfbAkl0hUbDJHDsdkjL3rLAcC8Mkaf9hXZgc5laRc,3065
93
+ arpakitlib/_arpakit_project_template/src/core/settings.py,sha256=R4dsQHOB9wVBn-A76sbJFQbsfCHyyR3AMKV8HZHIHmM,3819
93
94
  arpakitlib/_arpakit_project_template/src/core/util.py,sha256=mcikqcjljZa2qhYeoR1tR9JUSprrVSod8XcIK_PqS6o,1547
94
95
  arpakitlib/_arpakit_project_template/src/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
95
96
  arpakitlib/_arpakit_project_template/src/db/_check_conn_sqlalchemy_db.py,sha256=GPK7-w7x8ESqbZ0v1nI8m_ysWfnmjgRwyyh6kM8vYBQ,249
@@ -98,7 +99,7 @@ arpakitlib/_arpakit_project_template/src/db/_init_sqlalchemy_db.py,sha256=tHJ1NB
98
99
  arpakitlib/_arpakit_project_template/src/db/_reinit_sqlalchemy_db.py,sha256=_4O_xr6FXt3qcWOmO82CKG3XX6_RqbqaOYSEygxpBfA,339
99
100
  arpakitlib/_arpakit_project_template/src/db/const.py,sha256=dcvj5C9E2F2KCsGZPBBncQf_EvVJAC1qQgnyD8P4ZEw,6
100
101
  arpakitlib/_arpakit_project_template/src/db/sqlalchemy_model.py,sha256=tIdSerx8m6FA0cx4PDNJF8cWY1utpS_g0sZ0jBzUAbI,286
101
- arpakitlib/_arpakit_project_template/src/db/util.py,sha256=Ww0CYifxFcNkRvMedmDu806_Ic_Hbb1C5KQX8UsiLdc,787
102
+ arpakitlib/_arpakit_project_template/src/db/util.py,sha256=wYMVXtw_XURv4DFSx4n6CXnKYE-xskzpYSRk9lBIELY,848
102
103
  arpakitlib/_arpakit_project_template/src/operation_execution/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
103
104
  arpakitlib/_arpakit_project_template/src/operation_execution/_remove_operations.py,sha256=KsvAk2zQCp0PX6-QiRW0LKHoJfmH0wVJcBZviFqAoBI,471
104
105
  arpakitlib/_arpakit_project_template/src/operation_execution/_start_operation_executor_worker.py,sha256=K861UI9LFNxfCoPpQ5AJX-oUwjB6bfFNKScKsyU9Xd8,575
@@ -114,6 +115,10 @@ arpakitlib/_arpakit_project_template/src/test_data/make_test_data_3.py,sha256=t5
114
115
  arpakitlib/_arpakit_project_template/src/test_data/make_test_data_4.py,sha256=t1iYFim7v9NNR7Y10rUVRMVyq76Pdc82d5tQKpNlUFI,100
115
116
  arpakitlib/_arpakit_project_template/src/test_data/make_test_data_5.py,sha256=ptptUxpEa7sX7coToAYZHvy8oxXXQExxS1zqng5ET2I,100
116
117
  arpakitlib/_arpakit_project_template/src/tg_bot/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
118
+ arpakitlib/_arpakit_project_template/src/tg_bot/blank/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
119
+ arpakitlib/_arpakit_project_template/src/tg_bot/blank/blank.py,sha256=ylQGIhLV3CMEW9qmQ4jr9rcXi0B72qIhbRmx9VLBzHc,204
120
+ arpakitlib/_arpakit_project_template/src/tg_bot/blank/util.py,sha256=zhMRY4BUA8XMhfo8R4Jri655w4fwl0uniGq48SjchrY,247
121
+ arpakitlib/_arpakit_project_template/src/tg_bot/const.py,sha256=oz6kWH00rtu4WvAMnrR4CNgT8rVl-gyMqEqWuZwwyZM,578
117
122
  arpakitlib/_arpakit_project_template/src/tg_bot/event.py,sha256=YMwoBH8TQhdjoTQBrxoxOvJqHqlw1yYVE5ymDp2D_Ak,1260
118
123
  arpakitlib/_arpakit_project_template/src/tg_bot/filter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
119
124
  arpakitlib/_arpakit_project_template/src/tg_bot/filter/not_prod_mode.py,sha256=uzkYsquQ5UKD68lEqe0478rbKFQYXZhbLIbxW0QhI44,235
@@ -127,12 +132,15 @@ arpakitlib/_arpakit_project_template/src/tg_bot/kb/inline_/callback.py,sha256=47
127
132
  arpakitlib/_arpakit_project_template/src/tg_bot/kb/inline_/common.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
128
133
  arpakitlib/_arpakit_project_template/src/tg_bot/kb/static_/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
129
134
  arpakitlib/_arpakit_project_template/src/tg_bot/kb/static_/common.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
135
+ arpakitlib/_arpakit_project_template/src/tg_bot/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
130
136
  arpakitlib/_arpakit_project_template/src/tg_bot/router/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
131
- arpakitlib/_arpakit_project_template/src/tg_bot/start_tg_bot.py,sha256=C-bscqmpBZgUwDbVx7e35sNDfs2f677dlvZ-RwrAa6U,77
137
+ arpakitlib/_arpakit_project_template/src/tg_bot/router/error.py,sha256=5ktxguLc2pI7BUlJ9jgMp_VnH5gtOt1jzJZskKkmhzI,53
138
+ arpakitlib/_arpakit_project_template/src/tg_bot/router/router.py,sha256=ZxfPNouuewz3uEW7jLxXdCuT7v-HVjl9YagrKbUMzwc,151
139
+ arpakitlib/_arpakit_project_template/src/tg_bot/start_tg_bot.py,sha256=7JWCdhyRyYkwV198PjKl6YPqPl7qr-_1Z2tVItfPZlQ,218
132
140
  arpakitlib/_arpakit_project_template/src/tg_bot/transmitted_tg_data.py,sha256=XPNROag9Aq-eSIY7_KxaIxNvAp4orTkpkghRCh3nuz4,585
133
141
  arpakitlib/_arpakit_project_template/src/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
134
142
  arpakitlib/ar_additional_model_util.py,sha256=GFg-glLCxH9X95R2bhTJsscVwv37FgE1qbaAAyXrnIE,917
135
- arpakitlib/ar_aiogram_util.py,sha256=lv2aXUmXLm3f2JltT1i683de6MnTTp9b5HihjO4KM5s,12194
143
+ arpakitlib/ar_aiogram_util.py,sha256=GFoIVjuKStzpR_PYPZdyfP3txpjJq1oaCsXTCNMp4Qw,11966
136
144
  arpakitlib/ar_api_key_util.py,sha256=E84JlJXiDHtxLQmV8BNHvqNKu_G8-Dox0XxknYJQ37Q,422
137
145
  arpakitlib/ar_arpakit_lib_module_util.py,sha256=g9uWwTK2eEzmErqwYeVgXDYVMREN8m5CdmgEumAEQfw,5919
138
146
  arpakitlib/ar_arpakit_project_template_util.py,sha256=c7yc8w2IvZGH5hH8eOpL7JuD005hUxZ0GVDcSkJF5iI,3705
@@ -141,6 +149,7 @@ arpakitlib/ar_arpakit_schedule_uust_site_util.py,sha256=8wLct9Gd4MWkXzB6nSmETAwT
141
149
  arpakitlib/ar_arpakitlib_cli_util.py,sha256=208k_kWc-XHTYqL39k-rYrLcTKFF-3og21PVIsq5b2k,3171
142
150
  arpakitlib/ar_base64_util.py,sha256=aZkg2cZTuAaP2IWeG_LXJ6RO7qhyskVwec-Lks0iM-k,676
143
151
  arpakitlib/ar_base_worker_util.py,sha256=8xKNo_ob-VZsZ2THwM_cJDcA3Y4R-ruM7vKUfzKI94U,5600
152
+ arpakitlib/ar_blank_util.py,sha256=6O54Z8NnCg8BXxUVWt-oo1kqzt0836k9KJeJofwmj0g,113
144
153
  arpakitlib/ar_cache_file_util.py,sha256=Fo2pH-Zqm966KWFBHG_pbiySGZvhIFCYqy7k1weRfJ0,3476
145
154
  arpakitlib/ar_class_util.py,sha256=Eb4orGm2EFSaHfmrY2A_Nis5iwFMDKFaz1_nxTnfmnQ,487
146
155
  arpakitlib/ar_datetime_util.py,sha256=Xe1NiT9oPQzNSG7RVRkhukhbg4i-hhS5ImmV7sPUc8o,971
@@ -169,7 +178,7 @@ arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui.css,sha256=jzPZlgJTFwSdSphk9C
169
178
  arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui.css.map,sha256=5wq8eXMLU6Zxb45orZPL1zAsBFJReFw6GjYqGpUX3hg,262650
170
179
  arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui.js,sha256=ffrLZHHEQ_g84A-ul3yWa10Kk09waOAxHcQXPuZuavg,339292
171
180
  arpakitlib/ar_fastapi_static/swagger-ui/swagger-ui.js.map,sha256=9UhIW7MqCOZPAz1Sl1IKfZUuhWU0p-LJqrnjjJD9Xhc,1159454
172
- arpakitlib/ar_fastapi_util.py,sha256=v4IVYpI7Vd72WwuiRH2b194pjZO66vhX0B9qNI_YFe4,27420
181
+ arpakitlib/ar_fastapi_util.py,sha256=KQTiZ9it3ZYmatwBB0k_J9QOvNGGpAtQYkGNMRWazh4,28550
173
182
  arpakitlib/ar_file_storage_in_dir_util.py,sha256=D3e3rGuHoI6xqAA5mVvEpVVpOWY1jyjNsjj2UhyHRbE,3674
174
183
  arpakitlib/ar_file_util.py,sha256=GUdJYm1tUZnYpY-SIPRHAZBHGra8NKy1eYEI0D5AfhY,489
175
184
  arpakitlib/ar_func_util.py,sha256=bCuWbSMoFXBaMNhb89sevj2oaXRk4Jk6Qjot8OXMDT4,1319
@@ -196,14 +205,14 @@ arpakitlib/ar_settings_util.py,sha256=rnoTqbRuhiq7294D4crD5kbnU8-gNWJbwGU_Ls2gJo
196
205
  arpakitlib/ar_sleep_util.py,sha256=OaLtRaJQWMkGjfj_mW1RB2P4RaSWsAIH8LUoXqsH0zM,1061
197
206
  arpakitlib/ar_sqladmin_util.py,sha256=6Nv9VQssk9PB0piyuss__soYKdjVhdbIeXIv4AgUxmQ,2660
198
207
  arpakitlib/ar_sqlalchemy_model_util.py,sha256=ezui1VEVuABYgrywpP_A2wYvDBlIxwQTZ0HQgBJfZE0,6293
199
- arpakitlib/ar_sqlalchemy_util.py,sha256=NbOGSqsnt5xGWJg80gM2CPMs2puSfzIrBapswMHQDc4,4283
208
+ arpakitlib/ar_sqlalchemy_util.py,sha256=9dXCdKWmUPyMt19sF2YVBB-4x_tl5g2Bsfhn0c_SQ6M,8184
200
209
  arpakitlib/ar_ssh_runner_util.py,sha256=e9deuUdBW7Eh0Exx2nTBhk57SaOZYaJaSjNk8q6dbJk,6804
201
210
  arpakitlib/ar_str_util.py,sha256=yU5gOwNXUQaH5b_tM5t6fXUn9oUcv5EQbVnq2wXXIpQ,3378
202
211
  arpakitlib/ar_type_util.py,sha256=9C3ErtUVs0tAUqtK-foFzjJOykfBOntfCz2IogDOgfA,4134
203
212
  arpakitlib/ar_yookassa_api_client_util.py,sha256=VozuZeCJjmLd1zj2BdC9WfiAQ3XYOrIMsdpNK-AUlm0,5347
204
213
  arpakitlib/ar_zabbix_api_client_util.py,sha256=Q-VR4MvoZ9aHwZeYZr9G3LwN-ANx1T5KFmF6pvPM-9M,6402
205
- arpakitlib-1.7.242.dist-info/LICENSE,sha256=GPEDQMam2r7FSTYqM1mm7aKnxLaWcBotH7UvQtea-ec,11355
206
- arpakitlib-1.7.242.dist-info/METADATA,sha256=NdsumMFL8O2w2vvWJZAfVjnrX3NCfTV3zw1MZHchudg,3310
207
- arpakitlib-1.7.242.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
208
- arpakitlib-1.7.242.dist-info/entry_points.txt,sha256=36xqR3PJFT2kuwjkM_EqoIy0qFUDPKSm_mJaI7emewE,87
209
- arpakitlib-1.7.242.dist-info/RECORD,,
214
+ arpakitlib-1.7.244.dist-info/LICENSE,sha256=GPEDQMam2r7FSTYqM1mm7aKnxLaWcBotH7UvQtea-ec,11355
215
+ arpakitlib-1.7.244.dist-info/METADATA,sha256=7Q2Y0Wb_OIUHWc5aXYt1L2EHIfYCEeelb-JrayEGrA0,3386
216
+ arpakitlib-1.7.244.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
217
+ arpakitlib-1.7.244.dist-info/entry_points.txt,sha256=36xqR3PJFT2kuwjkM_EqoIy0qFUDPKSm_mJaI7emewE,87
218
+ arpakitlib-1.7.244.dist-info/RECORD,,