diff --git a/pyproject.toml b/pyproject.toml index 937a11e..227abb4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,18 +8,27 @@ authors = [ ] requires-python = ">=3.13" dependencies = [ + "aiogram>=3.21.0", + "aiogram-dialog>=2.4.0", "aiogtts>=1.1.1", "alembic>=1.16.1", "asyncpg>=0.30.0", "click>=8.2.1", "dependency-injector>=4.47.1", "greenlet>=3.2.3", + "legacy-cgi>=2.6.3", "openai>=1.84.0", + "orjson>=3.11.1", "pendulum>=3.1.0", "psycopg2-binary>=2.9.10", "pydantic>=2.11.5", "pydantic-settings>=2.9.1", + "python-json-logger>=3.3.0", + "redis>=6.4.0", + "requests>=2.32.4", + "sentry-sdk>=2.34.1", "sqlalchemy>=2.0.41", + "telebot>=0.0.5", ] [project.scripts] diff --git a/src/greek_lang/configs/container.py b/src/greek_lang/configs/container.py index e5d2e8b..8084677 100644 --- a/src/greek_lang/configs/container.py +++ b/src/greek_lang/configs/container.py @@ -3,6 +3,7 @@ from dependency_injector import containers, providers from .db_config import PostgresConfig from .log_config import LoggerConfig from .openai_config import OpenAiConfig +from .redis_conn import RedisConfig from .tg_bot_config import TgBotConfig @@ -11,5 +12,6 @@ class ConfigContainer(containers.DeclarativeContainer): postgres_config: providers.Provider[PostgresConfig] = providers.Singleton( PostgresConfig ) + redis_config: providers.Provider[RedisConfig] = providers.Singleton(RedisConfig) tg_bot_config: providers.Provider[TgBotConfig] = providers.Singleton(TgBotConfig) openai_config: providers.Provider[OpenAiConfig] = providers.Singleton(OpenAiConfig) diff --git a/src/greek_lang/configs/log_config.py b/src/greek_lang/configs/log_config.py index dca415a..01c1688 100644 --- a/src/greek_lang/configs/log_config.py +++ b/src/greek_lang/configs/log_config.py @@ -1,4 +1,5 @@ import pydantic +from pydantic_settings import SettingsConfigDict from . import EnvConfig @@ -6,3 +7,7 @@ from . import EnvConfig class LoggerConfig(EnvConfig): telegram_bot_token: pydantic.SecretStr | None = None telegram_chat_id: int | None = None + + model_config = SettingsConfigDict( + env_prefix="LOG_", + ) diff --git a/src/greek_lang/configs/redis_conn.py b/src/greek_lang/configs/redis_conn.py new file mode 100644 index 0000000..33d54f3 --- /dev/null +++ b/src/greek_lang/configs/redis_conn.py @@ -0,0 +1,17 @@ +import pydantic +from pydantic_settings import SettingsConfigDict + +from . import EnvConfig + + +class RedisConfig(EnvConfig): + host: str = pydantic.Field(default="127.0.0.1") + port: int = pydantic.Field(default=6379) + db: int = pydantic.Field(default=0) + username: str | None = pydantic.Field(default=None) + password: pydantic.SecretStr | None = pydantic.Field(default=None) + pool_size: int = pydantic.Field(default=100) + + model_config = SettingsConfigDict( + env_prefix="REDIS_", + ) diff --git a/src/greek_lang/configs/tg_bot_config.py b/src/greek_lang/configs/tg_bot_config.py index 2c71158..918e629 100644 --- a/src/greek_lang/configs/tg_bot_config.py +++ b/src/greek_lang/configs/tg_bot_config.py @@ -1,7 +1,12 @@ import pydantic +from pydantic_settings import SettingsConfigDict from . import EnvConfig class TgBotConfig(EnvConfig): + model_config = SettingsConfigDict( + env_prefix="TG_", + ) + token: pydantic.SecretStr diff --git a/src/greek_lang/container.py b/src/greek_lang/container.py index d694cc3..c065839 100644 --- a/src/greek_lang/container.py +++ b/src/greek_lang/container.py @@ -6,6 +6,7 @@ from dependency_injector import containers, providers from .configs.container import ConfigContainer from .database.container import DatabaseContainer from .openai_manager.container import OpenAiContainer +from .redis_db.container import RedisContainer class MainContainer(containers.DeclarativeContainer): @@ -18,6 +19,9 @@ class MainContainer(containers.DeclarativeContainer): openai_container = providers.Container( OpenAiContainer, config_container=config_container ) + redis_container = providers.Container( + RedisContainer, config_container=config_container + ) @contextlib.asynccontextmanager diff --git a/src/greek_lang/database/migrations/versions/20250716_1013_6b43c7ed8c78_.py b/src/greek_lang/database/migrations/versions/20250716_1013_6b43c7ed8c78_.py index dc9db29..53d5085 100644 --- a/src/greek_lang/database/migrations/versions/20250716_1013_6b43c7ed8c78_.py +++ b/src/greek_lang/database/migrations/versions/20250716_1013_6b43c7ed8c78_.py @@ -5,6 +5,7 @@ Revises: 78357f437f61 Create Date: 2025-07-16 10:13:26.574794 """ + from typing import Sequence, Union from alembic import op @@ -12,8 +13,8 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. -revision: str = '6b43c7ed8c78' -down_revision: Union[str, None] = '78357f437f61' +revision: str = "6b43c7ed8c78" +down_revision: Union[str, None] = "78357f437f61" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -21,16 +22,22 @@ depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: """Upgrade schema.""" # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('openai_token_usage', 'response_fingerprint', - existing_type=sa.TEXT(), - nullable=True) + op.alter_column( + "openai_token_usage", + "response_fingerprint", + existing_type=sa.TEXT(), + nullable=True, + ) # ### end Alembic commands ### def downgrade() -> None: """Downgrade schema.""" # ### commands auto generated by Alembic - please adjust! ### - op.alter_column('openai_token_usage', 'response_fingerprint', - existing_type=sa.TEXT(), - nullable=False) + op.alter_column( + "openai_token_usage", + "response_fingerprint", + existing_type=sa.TEXT(), + nullable=False, + ) # ### end Alembic commands ### diff --git a/src/greek_lang/database/migrations/versions/20250810_1240_d30d80dee5a3_.py b/src/greek_lang/database/migrations/versions/20250810_1240_d30d80dee5a3_.py index e3c39d7..c55aef6 100644 --- a/src/greek_lang/database/migrations/versions/20250810_1240_d30d80dee5a3_.py +++ b/src/greek_lang/database/migrations/versions/20250810_1240_d30d80dee5a3_.py @@ -5,14 +5,15 @@ Revises: 6b43c7ed8c78 Create Date: 2025-08-10 12:40:24.118166 """ + from typing import Sequence, Union from alembic import op # revision identifiers, used by Alembic. -revision: str = 'd30d80dee5a3' -down_revision: Union[str, None] = '6b43c7ed8c78' +revision: str = "d30d80dee5a3" +down_revision: Union[str, None] = "6b43c7ed8c78" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -20,12 +21,14 @@ depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: """Upgrade schema.""" # ### commands auto generated by Alembic - please adjust! ### - op.create_unique_constraint(op.f('uq_glossary_word_term'), 'glossary_word', ['term']) + op.create_unique_constraint( + op.f("uq_glossary_word_term"), "glossary_word", ["term"] + ) # ### end Alembic commands ### def downgrade() -> None: """Downgrade schema.""" # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(op.f('uq_glossary_word_term'), 'glossary_word', type_='unique') + op.drop_constraint(op.f("uq_glossary_word_term"), "glossary_word", type_="unique") # ### end Alembic commands ### diff --git a/src/greek_lang/logger.py b/src/greek_lang/logger.py new file mode 100644 index 0000000..4f7d2f5 --- /dev/null +++ b/src/greek_lang/logger.py @@ -0,0 +1,255 @@ +import base64 +import contextlib +import contextvars +import datetime +import logging +import socket +import sys +import typing +from collections.abc import Iterator +from logging.config import dictConfig +from types import TracebackType + +import orjson +import sentry_sdk + +from .configs.log_config import LoggerConfig + + +extra_log_context: contextvars.ContextVar[dict[str, str]] = contextvars.ContextVar( + "extra_log_context" +) + + +@contextlib.contextmanager +def extra_log_context_manager(new_context: dict[str, str]) -> Iterator[None]: + extra_log_context_data = {} + with contextlib.suppress(LookupError): + extra_log_context_data = extra_log_context.get() + extra_log_context_data.update(new_context) + token = extra_log_context.set(extra_log_context_data) + try: + yield + finally: + extra_log_context.reset(token) + + +class NonLoggableExceptionsFilter(logging.Filter): + exclude_exception_types: typing.Sequence[type[Exception]] + + def __init__( + self, + *, + exclude_exception_types: typing.Sequence[type[Exception]] = (), + name: str = "", + ): + self.exclude_exception_types = exclude_exception_types + super().__init__(name=name) + + def filter(self, record: logging.LogRecord) -> bool: + if record.exc_info is None: + return True + try: + exception_type = record.exc_info[0] + except TypeError: + return True + return exception_type not in self.exclude_exception_types + + +def default_json_serializer(obj: object) -> str: + match obj: + case bytes() as b: + try: + return b.decode("utf-8") + except UnicodeDecodeError: + return base64.b64encode(b).decode("ascii") + case datetime.timedelta() as td: + return str(td.total_seconds()) + case datetime.datetime() as dt: + return dt.isoformat() + case datetime.date() as d: + return d.isoformat() + case _: + raise TypeError(f"Type {type(obj)} not serializable") + + +def json_serializer(data: dict[str, typing.Any], **_: typing.Any) -> str: + extra_log_context_data = {} + with contextlib.suppress(LookupError): + extra_log_context_data = extra_log_context.get() + data.update({"extra_log_context": extra_log_context_data}) + return orjson.dumps( + data, + default=default_json_serializer, + ).decode() + + +def get_dict_config( + *, + sentry_dsn: str | None = None, + tg_token: str | None = None, + tg_chat: int | None = None, + exclude_exception_types: typing.Sequence[type[Exception]] = (), + formatters_extension_dict: dict[str, typing.Any] | None = None, + filters_extension_dict: dict[str, typing.Any] | None = None, + handlers_extension_dict: dict[str, typing.Any] | None = None, + loggers_extension_dict: dict[str, typing.Any] | None = None, +) -> dict[str, typing.Any]: + hostname: str = socket.gethostname() + null_handler: dict[str, str] = { + "class": "logging.NullHandler", + } + formatters = { + "verbose": { + "format": f"%(asctime)s [%(levelname)s] [{hostname} %(name)s:%(lineno)s] %(message)s" + }, + "json": { + "()": "pythonjsonlogger.jsonlogger.JsonFormatter", + "json_serializer": json_serializer, + "format": "%(asctime)s %(levelname)s %(name)s %(filename)s %(lineno)s %(message)s", + }, + } | (formatters_extension_dict or {}) + filters = { + "non_loggable_exceptions": { + "()": NonLoggableExceptionsFilter, + "exclude_exception_types": exclude_exception_types, + }, + } | (filters_extension_dict or {}) + handlers = { + "console_handler": { + "class": "logging.StreamHandler", + "formatter": "verbose", + "filters": [], + }, + "telegram_handler": { + "class": "greek_lang.utils.telegram_log.handler.TelegramHandler", + "token": tg_token, + "chat_id": tg_chat, + "logger_name": "console_handler", + "level": "ERROR", + "formatter": "verbose", + "filters": ["non_loggable_exceptions"], + } + if tg_token and tg_token + else null_handler, + "sentry_handler": { + "class": "sentry_sdk.integrations.logging.EventHandler", + "level": "ERROR", + "formatter": "verbose", + "filters": ["non_loggable_exceptions"], + } + if sentry_dsn is not None + else null_handler, + } | (handlers_extension_dict or {}) + loggers = { + "root": { + "level": "DEBUG", + "handlers": ["console_handler", "telegram_handler", "sentry_handler"], + }, + "console": { + "level": "DEBUG", + "handlers": ["console_handler"], + "propagate": False, + }, + "telegram.bot": { + "propagate": False, + }, + "httpx": { + "level": "DEBUG", + "propagate": True, + }, + } | (loggers_extension_dict or {}) + return { + "version": 1, + "disable_existing_loggers": False, + "formatters": formatters, + "filters": filters, + "handlers": handlers, + "loggers": loggers, + } + + +def create_tg_info_logger( + *, + tg_token: str, + tg_chat: str, +) -> logging.Logger: + logger_name = "tg_info" + dict_config = { + "version": 1, + "disable_existing_loggers": False, + "handlers": { + "telegram_handler": { + "class": "petuh_bot.utils.telegram_log.handler.TelegramHandler", + "logger_name": "console", + "token": tg_token, + "chat_id": tg_chat, + "level": "INFO", + } + }, + "loggers": { + logger_name: { + "handlers": ["telegram_handler"], + "propagate": False, + }, + }, + } + logging.config.dictConfig(dict_config) + return logging.getLogger(logger_name) + + +def init_root_logger( + sentry_dsn: str | None = None, + tg_token: str | None = None, + tg_chat: int | None = None, + exclude_exception_types: typing.Sequence[type[Exception]] = (), + formatters_extension_dict: dict[str, typing.Any] | None = None, + filters_extension_dict: dict[str, typing.Any] | None = None, + handlers_extension_dict: dict[str, typing.Any] | None = None, + loggers_extension_dict: dict[str, typing.Any] | None = None, +) -> logging.Logger: + if sentry_dsn is not None: + sentry_sdk.init( + dsn=sentry_dsn, + traces_sample_rate=1.0, + default_integrations=True, + ) + dict_config = get_dict_config( + sentry_dsn=sentry_dsn, + tg_token=tg_token, + tg_chat=tg_chat, + exclude_exception_types=exclude_exception_types, + formatters_extension_dict=formatters_extension_dict, + filters_extension_dict=filters_extension_dict, + handlers_extension_dict=handlers_extension_dict, + loggers_extension_dict=loggers_extension_dict, + ) + dictConfig(dict_config) + return logging.getLogger() + + +loggers_ext: dict[str, typing.Any] = {} + + +def _exc_hook_patched( + exc_type: type[BaseException], + exc_val: BaseException, + exc_tb: TracebackType, +) -> None: + if isinstance(exc_val, KeyboardInterrupt): + return + logging.critical( + f"Uncaught exception: {exc_type}", exc_info=(exc_type, exc_val, exc_tb) + ) + + +def setup() -> None: + config = LoggerConfig() + init_root_logger( + tg_token=config.telegram_bot_token.get_secret_value() + if config.telegram_bot_token + else None, + tg_chat=config.telegram_chat_id, + loggers_extension_dict=loggers_ext, + ) + sys.excepthook = _exc_hook_patched # type: ignore[assignment] diff --git a/src/greek_lang/redis_db/__init__.py b/src/greek_lang/redis_db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/greek_lang/redis_db/container.py b/src/greek_lang/redis_db/container.py new file mode 100644 index 0000000..2e5f154 --- /dev/null +++ b/src/greek_lang/redis_db/container.py @@ -0,0 +1,24 @@ +from collections.abc import AsyncIterator + +from dependency_injector import containers, providers + +from ..configs.redis_conn import RedisConfig +from .redis_conn import create_redis_pool, RedisPool + + +async def create_redis_pool_resource( + redis_config: RedisConfig, +) -> AsyncIterator[RedisPool]: + redis_pool = await create_redis_pool(redis_config) + try: + yield redis_pool + finally: + await redis_pool.aclose() + + +class RedisContainer(containers.DeclarativeContainer): + config_container = providers.DependenciesContainer() + redis_pool: providers.Resource[RedisPool] = providers.Resource( + create_redis_pool_resource, + redis_config=config_container.redis_config, + ) diff --git a/src/greek_lang/redis_db/redis_conn.py b/src/greek_lang/redis_db/redis_conn.py new file mode 100644 index 0000000..a043a5d --- /dev/null +++ b/src/greek_lang/redis_db/redis_conn.py @@ -0,0 +1,51 @@ +import dataclasses +from typing import TypeAlias + +import redis + +from ..configs.redis_conn import RedisConfig + + +RedisPool: TypeAlias = redis.asyncio.Redis + + +@dataclasses.dataclass(frozen=True) +class RedisConnectionParams: + host: str = "127.0.0.1" + port: int = 6379 + db: int = 0 + username: str | None = None + password: str | None = None + max_connections: int = 2**31 + socket_timeout: float = 5.0 + + +def create_redis_single_pool( + redis_conn_params: RedisConnectionParams, +) -> redis.asyncio.Redis: + redis_url = f"redis://{redis_conn_params.host}:{redis_conn_params.port}/{redis_conn_params.db}" + connection: redis.asyncio.Redis = redis.asyncio.from_url( # type: ignore[no-untyped-call] + redis_url, + username=redis_conn_params.username, + password=redis_conn_params.password, + decode_responses=False, + socket_connect_timeout=redis_conn_params.socket_timeout, + max_connections=redis_conn_params.max_connections, + ) + return connection + + +def create_redis_pool( + redis_config: RedisConfig, +) -> RedisPool: + redis_conn_params = RedisConnectionParams( + host=redis_config.host, + port=redis_config.port, + db=redis_config.db, + username=redis_config.username, + password=( + redis_config.password.get_secret_value() if redis_config.password else None + ), + max_connections=redis_config.pool_size, + ) + return create_redis_single_pool(redis_conn_params) diff --git a/src/greek_lang/tg_bot/__init__.py b/src/greek_lang/tg_bot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/greek_lang/tg_bot/__main__.py b/src/greek_lang/tg_bot/__main__.py new file mode 100644 index 0000000..e76e65a --- /dev/null +++ b/src/greek_lang/tg_bot/__main__.py @@ -0,0 +1,17 @@ +import asyncio + +from greek_lang import logger +from greek_lang.container import init_main_container +from greek_lang.tg_bot import app + + +async def main() -> None: + logger.setup() + async with init_main_container(): + bot = app.create_bot() + dispatcher = await app.create_dispatcher() + await app.run_bot(bot=bot, dispatcher=dispatcher) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/src/greek_lang/tg_bot/app.py b/src/greek_lang/tg_bot/app.py new file mode 100644 index 0000000..e6093a5 --- /dev/null +++ b/src/greek_lang/tg_bot/app.py @@ -0,0 +1,77 @@ +import pydantic +from aiogram import Bot, Dispatcher, BaseMiddleware +from aiogram.enums import ParseMode +from aiogram.filters import CommandStart +from aiogram.fsm.storage.base import BaseStorage, DefaultKeyBuilder +from aiogram.fsm.storage.redis import RedisStorage +from aiogram.types import BotCommandScopeAllPrivateChats, Message +from aiogram_dialog import DialogManager, setup_dialogs +from dependency_injector.wiring import Provide, inject + +from ..configs.container import ConfigContainer +from ..redis_db.container import RedisContainer +from ..redis_db.redis_conn import RedisPool + + +@inject +def create_bot( + bot_token: pydantic.SecretStr = Provide[ + ConfigContainer.tg_bot_config.provided.token + ], +) -> Bot: + bot = Bot( + token=bot_token.get_secret_value(), + ) + return bot + + +async def create_dispatcher() -> Dispatcher: + async def start(message: Message, dialog_manager: DialogManager) -> None: + user = message.from_user + if user is None: + return + await message.answer( + text="TEST", + parse_mode=ParseMode.HTML, + ) + + fsm_storage = await create_fsm_storage() + + dp = Dispatcher( + storage=fsm_storage, + ) + + middlewares: list[BaseMiddleware] = [] + for middleware in middlewares: + dp.update.middleware(middleware) + + dp.message.register(start, CommandStart()) + dp.business_message.register(start, CommandStart()) + + setup_dialogs(dp) + return dp + + +@inject +async def create_fsm_storage( + redis_pool: RedisPool = Provide[RedisContainer.redis_pool], +) -> BaseStorage: + storage = RedisStorage( + redis=redis_pool, + key_builder=DefaultKeyBuilder( + prefix="fsm", + with_destiny=True, + ), + ) + return storage + + +async def run_bot( + bot: Bot, + dispatcher: Dispatcher, +) -> None: + await bot.delete_webhook(drop_pending_updates=True) + await bot.delete_my_commands(scope=BotCommandScopeAllPrivateChats()) + await dispatcher.start_polling( + bot, allowed_updates=dispatcher.resolve_used_update_types() + ) diff --git a/src/greek_lang/utils/__init__.py b/src/greek_lang/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/greek_lang/utils/telegram_log/__init__.py b/src/greek_lang/utils/telegram_log/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/greek_lang/utils/telegram_log/handler.py b/src/greek_lang/utils/telegram_log/handler.py new file mode 100644 index 0000000..94a3cbb --- /dev/null +++ b/src/greek_lang/utils/telegram_log/handler.py @@ -0,0 +1,103 @@ +import contextlib +import copy +import datetime +import io +import logging +import logging.config +import types + +import cgitb +import telebot +from requests import ReadTimeout + + +ExcInfoType = tuple[type[BaseException], BaseException, types.TracebackType] + + +class TelegramHandler(logging.Handler): + bot: telebot.TeleBot + chat_id: int + logger_name: logging.Logger | None + + def __init__(self, *, token: str, chat_id: int, logger_name: str | None = None): + logging.Handler.__init__(self) + self.bot = telebot.TeleBot(token) + self.chat_id = chat_id + self.logger = logging.getLogger(logger_name) if logger_name else None + + @staticmethod + def get_tb_data(exc_info: ExcInfoType, output_format: str = "html") -> io.BytesIO: + string_io_buffer = io.StringIO() + context_width = 11 + cgitb.Hook( + context=context_width, + file=string_io_buffer, + format=output_format, + ).handle(info=exc_info) + string_io_buffer.seek(0) + encoding = "utf-8" + bytes_io_buffer = io.BytesIO(string_io_buffer.read().encode(encoding)) + bytes_io_buffer.seek(0) + return bytes_io_buffer + + @staticmethod + def prepare(log_data: str, length: int) -> str: + message = log_data[:length] + return message + + def emit(self, record: logging.LogRecord) -> None: + try: + if record.exc_info is None: + self.send_plain_text(record) + else: + self.send_traceback(record) + except ReadTimeout: + if self.logger: + self.logger.error("Telegram request timed out") + except BaseException as exc: + if self.logger: + self.logger.exception( + f"Telegram Log Handler Unexpected Exception Occurred: {exc}" + ) + + def send_traceback(self, record: logging.LogRecord) -> None: + tb_data_html = self.get_tb_data(record.exc_info, output_format="html") # type: ignore + tb_data_plain = self.get_tb_data(record.exc_info, output_format="plain") # type: ignore + with contextlib.closing(tb_data_html), contextlib.closing(tb_data_plain): + filename = datetime.datetime.now().strftime("python_tb_%Y-%m-%d_%H_%M_%S") + caption = self.get_exc_caption_text(record) + self.bot.send_media_group( + chat_id=self.chat_id, + media=[ + telebot.types.InputMediaDocument( + telebot.types.InputFile( + tb_data_html, file_name=filename + ".html" + ), + caption=caption, + ), + telebot.types.InputMediaDocument( + telebot.types.InputFile( + tb_data_plain, file_name=filename + ".txt" + ) + ), + ], + timeout=5, + ) + + def get_exc_caption_text(self, record: logging.LogRecord) -> str: + caption_length = 200 + no_exc_record = self.get_no_exc_record_copy(record) + caption = self.prepare(self.format(no_exc_record), caption_length) + return caption + + @staticmethod + def get_no_exc_record_copy(record: logging.LogRecord) -> logging.LogRecord: + no_exc_record = copy.copy(record) + no_exc_record.exc_info = None + no_exc_record.exc_text = None + return no_exc_record + + def send_plain_text(self, record: logging.LogRecord) -> None: + message_length = 4096 + text = self.prepare(self.format(record), message_length) + self.bot.send_message(self.chat_id, text, timeout=5) diff --git a/uv.lock b/uv.lock index 6c24a47..2ae29da 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,46 @@ version = 1 revision = 2 requires-python = ">=3.13" +[[package]] +name = "aiofiles" +version = "24.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247, upload-time = "2024-06-24T11:02:03.584Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896, upload-time = "2024-06-24T11:02:01.529Z" }, +] + +[[package]] +name = "aiogram" +version = "3.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "aiohttp" }, + { name = "certifi" }, + { name = "magic-filter" }, + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/11/414c49e31ac353b12dba4f89e66141e4504d359c453dfdd5d259cefb97a4/aiogram-3.21.0.tar.gz", hash = "sha256:24cd0015ed73471fa3028b47788e57b43a475c66d176a857de2b6b67bd37e1dd", size = 1500486, upload-time = "2025-07-05T00:20:03.203Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/0b/606c9cdff82f5a5e004d72a14368d46ed00298c94a8d83b6faf8fb70499f/aiogram-3.21.0-py3-none-any.whl", hash = "sha256:0995e11be66bba46c0aab4ac8a41587fb2749b72399aff46a20e6610ccebe3ae", size = 677316, upload-time = "2025-07-05T00:20:01.322Z" }, +] + +[[package]] +name = "aiogram-dialog" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiogram" }, + { name = "cachetools" }, + { name = "jinja2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/b9/cfadb823578f9aff44f10928f8020dc4abda82078fba62295aae8869e894/aiogram_dialog-2.4.0.tar.gz", hash = "sha256:e8f0a811be3a58d1e48dfb9dd79108fd0e25c8ab7f8067c31c49f2f583ba4ecc", size = 80359, upload-time = "2025-07-08T22:41:41.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/d0/1c6511eb6840d06efbfba12cf6e59f688f9e314ab90af34027fa8069af75/aiogram_dialog-2.4.0-py3-none-any.whl", hash = "sha256:4e5fd8f7db66b6d252983c4025968b379069f4a2022db60bde36e5b91268b6ad", size = 112911, upload-time = "2025-07-08T22:41:47.61Z" }, +] + [[package]] name = "aiogtts" version = "1.1.1" @@ -159,6 +199,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload-time = "2025-04-15T17:05:12.221Z" }, ] +[[package]] +name = "cachetools" +version = "5.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload-time = "2025-02-20T21:01:19.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" }, +] + [[package]] name = "certifi" version = "2025.8.3" @@ -177,6 +226,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, ] +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, + { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +] + [[package]] name = "click" version = "8.2.1" @@ -306,18 +386,27 @@ name = "greek-lang" version = "0.1.0" source = { editable = "." } dependencies = [ + { name = "aiogram" }, + { name = "aiogram-dialog" }, { name = "aiogtts" }, { name = "alembic" }, { name = "asyncpg" }, { name = "click" }, { name = "dependency-injector" }, { name = "greenlet" }, + { name = "legacy-cgi" }, { name = "openai" }, + { name = "orjson" }, { name = "pendulum" }, { name = "psycopg2-binary" }, { name = "pydantic" }, { name = "pydantic-settings" }, + { name = "python-json-logger" }, + { name = "redis" }, + { name = "requests" }, + { name = "sentry-sdk" }, { name = "sqlalchemy" }, + { name = "telebot" }, ] [package.dev-dependencies] @@ -332,18 +421,27 @@ dev = [ [package.metadata] requires-dist = [ + { name = "aiogram", specifier = ">=3.21.0" }, + { name = "aiogram-dialog", specifier = ">=2.4.0" }, { name = "aiogtts", specifier = ">=1.1.1" }, { name = "alembic", specifier = ">=1.16.1" }, { name = "asyncpg", specifier = ">=0.30.0" }, { name = "click", specifier = ">=8.2.1" }, { name = "dependency-injector", specifier = ">=4.47.1" }, { name = "greenlet", specifier = ">=3.2.3" }, + { name = "legacy-cgi", specifier = ">=2.6.3" }, { name = "openai", specifier = ">=1.84.0" }, + { name = "orjson", specifier = ">=3.11.1" }, { name = "pendulum", specifier = ">=3.1.0" }, { name = "psycopg2-binary", specifier = ">=2.9.10" }, { name = "pydantic", specifier = ">=2.11.5" }, { name = "pydantic-settings", specifier = ">=2.9.1" }, + { name = "python-json-logger", specifier = ">=3.3.0" }, + { name = "redis", specifier = ">=6.4.0" }, + { name = "requests", specifier = ">=2.32.4" }, + { name = "sentry-sdk", specifier = ">=2.34.1" }, { name = "sqlalchemy", specifier = ">=2.0.41" }, + { name = "telebot", specifier = ">=0.0.5" }, ] [package.metadata.requires-dev] @@ -435,6 +533,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + [[package]] name = "jiter" version = "0.10.0" @@ -471,6 +590,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/4a/4175a563579e884192ba6e81725fc0448b042024419be8d83aa8a80a3f44/jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5", size = 354213, upload-time = "2025-05-18T19:04:41.894Z" }, ] +[[package]] +name = "legacy-cgi" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/ed/300cabc9693209d5a03e2ebc5eb5c4171b51607c08ed84a2b71c9015e0f3/legacy_cgi-2.6.3.tar.gz", hash = "sha256:4c119d6cb8e9d8b6ad7cc0ddad880552c62df4029622835d06dfd18f438a8154", size = 24401, upload-time = "2025-03-27T00:48:56.957Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/33/68c6c38193684537757e0d50a7ccb4f4656e5c2f7cd2be737a9d4a1bff71/legacy_cgi-2.6.3-py3-none-any.whl", hash = "sha256:6df2ea5ae14c71ef6f097f8b6372b44f6685283dc018535a75c924564183cdab", size = 19851, upload-time = "2025-03-27T00:48:55.366Z" }, +] + +[[package]] +name = "magic-filter" +version = "1.0.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/08/da7c2cc7398cc0376e8da599d6330a437c01d3eace2f2365f300e0f3f758/magic_filter-1.0.12.tar.gz", hash = "sha256:4751d0b579a5045d1dc250625c4c508c18c3def5ea6afaf3957cb4530d03f7f9", size = 11071, upload-time = "2023-10-01T12:33:19.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/75/f620449f0056eff0ec7c1b1e088f71068eb4e47a46eb54f6c065c6ad7675/magic_filter-1.0.12-py3-none-any.whl", hash = "sha256:e5929e544f310c2b1f154318db8c5cdf544dd658efa998172acd2e4ba0f6c6a6", size = 11335, upload-time = "2023-10-01T12:33:17.711Z" }, +] + [[package]] name = "mako" version = "1.3.10" @@ -640,6 +777,49 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d6/dd/9aa956485c2856346b3181542fbb0aea4e5b457fa7a523944726746da8da/openai-1.99.6-py3-none-any.whl", hash = "sha256:e40d44b2989588c45ce13819598788b77b8fb80ba2f7ae95ce90d14e46f1bd26", size = 786296, upload-time = "2025-08-09T15:20:51.95Z" }, ] +[[package]] +name = "orjson" +version = "3.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/3b/fd9ff8ff64ae3900f11554d5cfc835fb73e501e043c420ad32ec574fe27f/orjson-3.11.1.tar.gz", hash = "sha256:48d82770a5fd88778063604c566f9c7c71820270c9cc9338d25147cbf34afd96", size = 5393373, upload-time = "2025-07-25T14:33:52.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/e9/880ef869e6f66279ce3a381a32afa0f34e29a94250146911eee029e56efc/orjson-3.11.1-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53cfefe4af059e65aabe9683f76b9c88bf34b4341a77d329227c2424e0e59b0e", size = 240835, upload-time = "2025-07-25T14:32:54.507Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1f/52039ef3d03eeea21763b46bc99ebe11d9de8510c72b7b5569433084a17e/orjson-3.11.1-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:93d5abed5a6f9e1b6f9b5bf6ed4423c11932b5447c2f7281d3b64e0f26c6d064", size = 129226, upload-time = "2025-07-25T14:32:55.908Z" }, + { url = "https://files.pythonhosted.org/packages/ee/da/59fdffc9465a760be2cd3764ef9cd5535eec8f095419f972fddb123b6d0e/orjson-3.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbf06642f3db2966df504944cdd0eb68ca2717f0353bb20b20acd78109374a6", size = 132261, upload-time = "2025-07-25T14:32:57.538Z" }, + { url = "https://files.pythonhosted.org/packages/bb/5c/8610911c7e969db7cf928c8baac4b2f1e68d314bc3057acf5ca64f758435/orjson-3.11.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dddf4e78747fa7f2188273f84562017a3c4f0824485b78372513c1681ea7a894", size = 128614, upload-time = "2025-07-25T14:32:58.808Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a1/a1db9d4310d014c90f3b7e9b72c6fb162cba82c5f46d0b345669eaebdd3a/orjson-3.11.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa3fe8653c9f57f0e16f008e43626485b6723b84b2f741f54d1258095b655912", size = 130968, upload-time = "2025-07-25T14:33:00.038Z" }, + { url = "https://files.pythonhosted.org/packages/56/ff/11acd1fd7c38ea7a1b5d6bf582ae3da05931bee64620995eb08fd63c77fe/orjson-3.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6334d2382aff975a61f6f4d1c3daf39368b887c7de08f7c16c58f485dcf7adb2", size = 132439, upload-time = "2025-07-25T14:33:01.354Z" }, + { url = "https://files.pythonhosted.org/packages/70/f9/bb564dd9450bf8725e034a8ad7f4ae9d4710a34caf63b85ce1c0c6d40af0/orjson-3.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3d0855b643f259ee0cb76fe3df4c04483354409a520a902b067c674842eb6b8", size = 135299, upload-time = "2025-07-25T14:33:03.079Z" }, + { url = "https://files.pythonhosted.org/packages/94/bb/c8eafe6051405e241dda3691db4d9132d3c3462d1d10a17f50837dd130b4/orjson-3.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0eacdfeefd0a79987926476eb16e0245546bedeb8febbbbcf4b653e79257a8e4", size = 131004, upload-time = "2025-07-25T14:33:04.416Z" }, + { url = "https://files.pythonhosted.org/packages/a2/40/bed8d7dcf1bd2df8813bf010a25f645863a2f75e8e0ebdb2b55784cf1a62/orjson-3.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0ed07faf9e4873518c60480325dcbc16d17c59a165532cccfb409b4cdbaeff24", size = 130583, upload-time = "2025-07-25T14:33:05.768Z" }, + { url = "https://files.pythonhosted.org/packages/57/e7/cfa2eb803ad52d74fbb5424a429b5be164e51d23f1d853e5e037173a5c48/orjson-3.11.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d308dd578ae3658f62bb9eba54801533225823cd3248c902be1ebc79b5e014", size = 404218, upload-time = "2025-07-25T14:33:07.117Z" }, + { url = "https://files.pythonhosted.org/packages/d5/21/bc703af5bc6e9c7e18dcf4404dcc4ec305ab9bb6c82d3aee5952c0c56abf/orjson-3.11.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c4aa13ca959ba6b15c0a98d3d204b850f9dc36c08c9ce422ffb024eb30d6e058", size = 146605, upload-time = "2025-07-25T14:33:08.55Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d26a0150534c4965a06f556aa68bf3c3b82999d5d7b0facd3af7b390c4af/orjson-3.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:be3d0653322abc9b68e5bcdaee6cfd58fcbe9973740ab222b87f4d687232ab1f", size = 135434, upload-time = "2025-07-25T14:33:09.967Z" }, + { url = "https://files.pythonhosted.org/packages/89/b6/1cb28365f08cbcffc464f8512320c6eb6db6a653f03d66de47ea3c19385f/orjson-3.11.1-cp313-cp313-win32.whl", hash = "sha256:4dd34e7e2518de8d7834268846f8cab7204364f427c56fb2251e098da86f5092", size = 136596, upload-time = "2025-07-25T14:33:11.333Z" }, + { url = "https://files.pythonhosted.org/packages/f9/35/7870d0d3ed843652676d84d8a6038791113eacc85237b673b925802826b8/orjson-3.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:d6895d32032b6362540e6d0694b19130bb4f2ad04694002dce7d8af588ca5f77", size = 131319, upload-time = "2025-07-25T14:33:12.614Z" }, + { url = "https://files.pythonhosted.org/packages/b7/3e/5bcd50fd865eb664d4edfdaaaff51e333593ceb5695a22c0d0a0d2b187ba/orjson-3.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:bb7c36d5d3570fcbb01d24fa447a21a7fe5a41141fd88e78f7994053cc4e28f4", size = 126613, upload-time = "2025-07-25T14:33:13.927Z" }, + { url = "https://files.pythonhosted.org/packages/61/d8/0a5cd31ed100b4e569e143cb0cddefc21f0bcb8ce284f44bca0bb0e10f3d/orjson-3.11.1-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7b71ef394327b3d0b39f6ea7ade2ecda2731a56c6a7cbf0d6a7301203b92a89b", size = 240819, upload-time = "2025-07-25T14:33:15.223Z" }, + { url = "https://files.pythonhosted.org/packages/b9/95/7eb2c76c92192ceca16bc81845ff100bbb93f568b4b94d914b6a4da47d61/orjson-3.11.1-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:77c0fe28ed659b62273995244ae2aa430e432c71f86e4573ab16caa2f2e3ca5e", size = 129218, upload-time = "2025-07-25T14:33:16.637Z" }, + { url = "https://files.pythonhosted.org/packages/da/84/e6b67f301b18adbbc346882f456bea44daebbd032ba725dbd7b741e3a7f1/orjson-3.11.1-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:1495692f1f1ba2467df429343388a0ed259382835922e124c0cfdd56b3d1f727", size = 132238, upload-time = "2025-07-25T14:33:17.934Z" }, + { url = "https://files.pythonhosted.org/packages/84/78/a45a86e29d9b2f391f9d00b22da51bc4b46b86b788fd42df2c5fcf3e8005/orjson-3.11.1-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:08c6a762fca63ca4dc04f66c48ea5d2428db55839fec996890e1bfaf057b658c", size = 130998, upload-time = "2025-07-25T14:33:19.282Z" }, + { url = "https://files.pythonhosted.org/packages/ea/8f/6eb3ee6760d93b2ce996a8529164ee1f5bafbdf64b74c7314b68db622b32/orjson-3.11.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e26794fe3976810b2c01fda29bd9ac7c91a3c1284b29cc9a383989f7b614037", size = 130559, upload-time = "2025-07-25T14:33:20.589Z" }, + { url = "https://files.pythonhosted.org/packages/1b/78/9572ae94bdba6813917c9387e7834224c011ea6b4530ade07d718fd31598/orjson-3.11.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4b4b4f8f0b1d3ef8dc73e55363a0ffe012a42f4e2f1a140bf559698dca39b3fa", size = 404231, upload-time = "2025-07-25T14:33:22.019Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a3/68381ad0757e084927c5ee6cfdeab1c6c89405949ee493db557e60871c4c/orjson-3.11.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:848be553ea35aa89bfefbed2e27c8a41244c862956ab8ba00dc0b27e84fd58de", size = 146658, upload-time = "2025-07-25T14:33:23.675Z" }, + { url = "https://files.pythonhosted.org/packages/00/db/fac56acf77aab778296c3f541a3eec643266f28ecd71d6c0cba251e47655/orjson-3.11.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c964c29711a4b1df52f8d9966f015402a6cf87753a406c1c4405c407dd66fd45", size = 135443, upload-time = "2025-07-25T14:33:25.04Z" }, + { url = "https://files.pythonhosted.org/packages/76/b1/326fa4b87426197ead61c1eec2eeb3babc9eb33b480ac1f93894e40c8c08/orjson-3.11.1-cp314-cp314-win32.whl", hash = "sha256:33aada2e6b6bc9c540d396528b91e666cedb383740fee6e6a917f561b390ecb1", size = 136643, upload-time = "2025-07-25T14:33:26.449Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8e/2987ae2109f3bfd39680f8a187d1bc09ad7f8fb019dcdc719b08c7242ade/orjson-3.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:68e10fd804e44e36188b9952543e3fa22f5aa8394da1b5283ca2b423735c06e8", size = 131324, upload-time = "2025-07-25T14:33:27.896Z" }, + { url = "https://files.pythonhosted.org/packages/21/5f/253e08e6974752b124fbf3a4de3ad53baa766b0cb4a333d47706d307e396/orjson-3.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:f3cf6c07f8b32127d836be8e1c55d4f34843f7df346536da768e9f73f22078a1", size = 126605, upload-time = "2025-07-25T14:33:29.244Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + [[package]] name = "pathspec" version = "0.12.1" @@ -693,6 +873,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, ] +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + [[package]] name = "pre-commit" version = "4.3.0" @@ -835,6 +1024,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] +[[package]] +name = "pytelegrambotapi" +version = "4.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "pytest" }, + { name = "requests" }, + { name = "wheel" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/81/57590c3c481ff3e848b5ae17371a01d903fd21ec1cfa0e7aeaf75e2db3d2/pytelegrambotapi-4.28.0.tar.gz", hash = "sha256:54efd71efb3b48b27301a6cec9ac27bfbd3feb1b5ec1fa23ab26ab4d2df7d376", size = 1358887, upload-time = "2025-07-26T07:29:05.464Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/80/c5ed58a0a75200814ba0238103f713edeb4c1039d4deb9a255b7d79c5a9d/pytelegrambotapi-4.28.0-py3-none-any.whl", hash = "sha256:dc9156a781ebf67cb87ce5cca963a827b871578fafac40eb4e072535cdcb9696", size = 290699, upload-time = "2025-07-26T07:29:03.581Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -856,6 +1076,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, ] +[[package]] +name = "python-json-logger" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/de/d3144a0bceede957f961e975f3752760fbe390d57fbe194baf709d8f1f7b/python_json_logger-3.3.0.tar.gz", hash = "sha256:12b7e74b17775e7d565129296105bbe3910842d9d0eb083fc83a6a617aa8df84", size = 16642, upload-time = "2025-03-07T07:08:27.301Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/20/0f2523b9e50a8052bc6a8b732dfc8568abbdc42010aef03a2d750bdab3b2/python_json_logger-3.3.0-py3-none-any.whl", hash = "sha256:dd980fae8cffb24c13caf6e158d3d61c0d6d22342f932cb6e9deedab3d35eec7", size = 15163, upload-time = "2025-03-07T07:08:25.627Z" }, +] + [[package]] name = "pyupgrade" version = "3.20.0" @@ -885,6 +1114,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, ] +[[package]] +name = "redis" +version = "6.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/d6/e8b92798a5bd67d659d51a18170e91c16ac3b59738d91894651ee255ed49/redis-6.4.0.tar.gz", hash = "sha256:b01bc7282b8444e28ec36b261df5375183bb47a07eb9c603f284e89cbc5ef010", size = 4647399, upload-time = "2025-08-07T08:10:11.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/02/89e2ed7e85db6c93dfa9e8f691c5087df4e3551ab39081a4d7c6d1f90e05/redis-6.4.0-py3-none-any.whl", hash = "sha256:f0544fa9604264e9464cdf4814e7d4830f74b165d52f2a330a760a88dd248b7f", size = 279847, upload-time = "2025-08-07T08:10:09.84Z" }, +] + +[[package]] +name = "requests" +version = "2.32.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, +] + [[package]] name = "rich" version = "14.1.0" @@ -923,6 +1176,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/5c/799a1efb8b5abab56e8a9f2a0b72d12bd64bb55815e9476c7d0a2887d2f7/ruff-0.12.8-py3-none-win_arm64.whl", hash = "sha256:c90e1a334683ce41b0e7a04f41790c429bf5073b62c1ae701c9dc5b3d14f0749", size = 11884718, upload-time = "2025-08-07T19:05:42.866Z" }, ] +[[package]] +name = "sentry-sdk" +version = "2.34.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/38/10d6bfe23df1bfc65ac2262ed10b45823f47f810b0057d3feeea1ca5c7ed/sentry_sdk-2.34.1.tar.gz", hash = "sha256:69274eb8c5c38562a544c3e9f68b5be0a43be4b697f5fd385bf98e4fbe672687", size = 336969, upload-time = "2025-07-30T11:13:37.93Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/3e/bb34de65a5787f76848a533afbb6610e01fbcdd59e76d8679c254e02255c/sentry_sdk-2.34.1-py2.py3-none-any.whl", hash = "sha256:b7a072e1cdc5abc48101d5146e1ae680fa81fe886d8d95aaa25a0b450c818d32", size = 357743, upload-time = "2025-07-30T11:13:36.145Z" }, +] + [[package]] name = "setuptools" version = "80.9.0" @@ -992,6 +1258,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/45/8c4ebc0c460e6ec38e62ab245ad3c7fc10b210116cea7c16d61602aa9558/stevedore-5.4.1-py3-none-any.whl", hash = "sha256:d10a31c7b86cba16c1f6e8d15416955fc797052351a56af15e608ad20811fcfe", size = 49533, upload-time = "2025-02-20T14:03:55.849Z" }, ] +[[package]] +name = "telebot" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytelegrambotapi" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/3b/e4188cffaf370f432f15d518cbb53a438200ee7ef68c6db1acab337634e1/telebot-0.0.5.tar.gz", hash = "sha256:848a5885b57247b65edd0e92647b4372ebd8d41611dca3f344788c11deac2571", size = 4752, upload-time = "2023-02-14T15:13:44.409Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/2f/d8d669be86c42dd2075d7b25bb6de73a3181661a79ef6a3c53dc811f17a1/telebot-0.0.5-py3-none-any.whl", hash = "sha256:50327c240243480d40ade4ea5bea4f464bf57d4ff000ea669fcd705396074863", size = 4840, upload-time = "2023-02-14T15:13:42.698Z" }, +] + [[package]] name = "tokenize-rt" version = "6.2.0" @@ -1043,6 +1322,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, ] +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + [[package]] name = "virtualenv" version = "20.33.1" @@ -1057,6 +1345,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ca/ff/ded57ac5ff40a09e6e198550bab075d780941e0b0f83cbeabd087c59383a/virtualenv-20.33.1-py3-none-any.whl", hash = "sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67", size = 6060362, upload-time = "2025-08-05T16:10:52.81Z" }, ] +[[package]] +name = "wheel" +version = "0.45.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", size = 107545, upload-time = "2024-11-23T00:18:23.513Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494, upload-time = "2024-11-23T00:18:21.207Z" }, +] + [[package]] name = "yarl" version = "1.20.1"