From 7ec124fe3800b2f56f8843ecf7c0a96cfd0af262 Mon Sep 17 00:00:00 2001 From: Jag_k <30597878+jag-k@users.noreply.github.com> Date: Tue, 7 May 2024 23:17:22 +0400 Subject: [PATCH] Remove load_envs module, refactor database implementations and upgrade pydantic Deleted the 'load_envs.py' module as its functions are no longer needed. Modified the 'reporter.py' and 'media_cache.py' classes in the database package with refactoring to improve readability and structure. Upgraded the Pydantic package to a newer version for improved data validation and settings management. The 'poetry.lock' file updated to reflect the package changes. --- app/commands/__init__.py | 2 +- app/commands/commands.py | 27 ++-- app/constants/__init__.py | 79 +----------- app/constants/context_vars.py | 20 +++ app/constants/fmt_logger.py | 23 ++++ app/constants/init_logger.py | 21 +++- app/constants/json_logger.py | 15 +-- app/constants/load_envs.py | 22 ---- app/constants/paths.py | 25 ++++ app/constants/settings.py | 63 ++++++++++ app/constants/types.py | 21 ++-- app/context/callback_context.py | 8 +- app/database/connector.py | 8 +- app/database/media_cache.py | 2 - app/database/reporter.py | 22 ++-- app/models/base.py | 44 ++++--- app/models/medias.py | 38 ++++-- app/models/report.py | 12 +- app/parsers/base.py | 8 +- app/parsers/instagram.py | 48 +------ app/settings/base.py | 16 ++- app/utils/app_patchers/json_logger.py | 2 +- app/utils/i18n/__init__.py | 4 +- app/utils/i18n/base.py | 8 +- cli/comands.py | 4 +- cli/compile.py | 3 +- cli/distributions.py | 3 +- cli/extract.py | 3 +- cli/update.py | 3 +- main.py | 16 +-- poetry.lock | 174 +++++++++++++++++++------- pyproject.toml | 4 +- 32 files changed, 438 insertions(+), 310 deletions(-) create mode 100644 app/constants/context_vars.py create mode 100644 app/constants/fmt_logger.py delete mode 100644 app/constants/load_envs.py create mode 100644 app/constants/paths.py create mode 100644 app/constants/settings.py diff --git a/app/commands/__init__.py b/app/commands/__init__.py index c8c5828..93df195 100644 --- a/app/commands/__init__.py +++ b/app/commands/__init__.py @@ -1,3 +1,3 @@ -from app.commands.commands import commands +from .commands import commands connect_commands = commands.connect_commands diff --git a/app/commands/commands.py b/app/commands/commands.py index cfcc5e7..0325c0f 100644 --- a/app/commands/commands.py +++ b/app/commands/commands.py @@ -2,6 +2,7 @@ import random from collections.abc import Callable +from constants import settings from telegram import ( ChatAdministratorRights, KeyboardButton, @@ -11,11 +12,10 @@ ) from telegram.constants import ChatType -from app import constants, settings from app.commands.registrator import CommandRegistrator -from app.constants import DEFAULT_LOCALE from app.context import CallbackContext from app.parsers.base import Parser +from app.settings import command_handler from app.utils import a, b from app.utils.i18n import _ @@ -56,24 +56,23 @@ def get(key: str) -> str | None: return res if res := obj.get(f"{key}_{ctx.user_lang.split('-')[0]}"): return res - if res := obj.get(f"{key}_{DEFAULT_LOCALE}"): + if res := obj.get(f"{key}_{settings.default_locale}"): return res return obj.get(key) return get + contacts_list = "\n".join( + f'- {g("type")}: {a(g("text"), g("url"))}' # type: ignore[arg-type] + for c in settings.contacts + if all(map(c.get, ("type", "text", "url"))) + if (g := get_by_lang(c)) # type: ignore[arg-type] + ) contacts = "" - if constants.CONTACTS: - contacts_list = "\n".join( - f'- {g("type")}: {a(g("text"), g("url"))}' # type: ignore[arg-type] - for c in constants.CONTACTS - if all(map(c.get, ("type", "text", "url"))) - if (g := get_by_lang(c)) # type: ignore[arg-type] + if contacts_list: + contacts = _("\n\nContacts:\n{contacts_list}").format( + contacts_list=contacts_list, ) - if contacts_list: - contacts = _("\n\nContacts:\n{contacts_list}").format( - contacts_list=contacts_list, - ) reply_markup = None rights = ChatAdministratorRights( @@ -146,4 +145,4 @@ async def clear_history(update: Update, ctx: CallbackContext) -> None: await update.message.reply_text(_("History cleared.")) -commands.add_handler(settings.command_handler(), _("Bot settings")) +commands.add_handler(command_handler(), _("Bot settings")) diff --git a/app/constants/__init__.py b/app/constants/__init__.py index 33e93f7..234e58c 100644 --- a/app/constants/__init__.py +++ b/app/constants/__init__.py @@ -1,76 +1,5 @@ -import json -import os -import sys -from pathlib import Path - -import pytz - -from .init_logger import init_logger_config -from .json_logger import CONTEXT_VARS -from .load_envs import load_envs +from .context_vars import * +from .init_logger import * +from .paths import * +from .settings import * from .types import * - -# region Base paths -APP_PATH = Path(__file__).resolve().parent.parent -PROJECT_PATH = APP_PATH.parent -BASE_PATH = Path(os.getenv("BASE_PATH", PROJECT_PATH)) - -CONFIG_PATH = BASE_PATH / "config" -DATA_PATH = BASE_PATH / "data" -LOG_PATH = DATA_PATH / "logs" - -CONFIG_PATH.mkdir(parents=True, exist_ok=True) -DATA_PATH.mkdir(parents=True, exist_ok=True) -LOG_PATH.mkdir(parents=True, exist_ok=True) -# endregion - -# Load envs -load_envs(BASE_PATH, CONFIG_PATH) - -# region Localizations -LOCALE_PATH = PROJECT_PATH / "locales" -DEFAULT_LOCALE = os.getenv("DEFAULT_LOCALE", "en") -DOMAIN = os.getenv("LOCALE_DOMAIN", "messages") -# endregion - -# region Other -TOKEN = os.getenv("TG_TOKEN") # Telegram token -TIME_ZONE = pytz.timezone(os.getenv("TZ", "Europe/Moscow")) - -# Contacts for help command -CONTACTS_PATH = Path(os.getenv("CONTACTS_PATH", CONFIG_PATH / "contacts.json")) -REPORT_PATH = Path(os.getenv("REPORT_PATH", CONFIG_PATH / "report.json")) -NOTIFY_PATH = Path(os.getenv("NOTIFY_PATH", CONFIG_PATH / "notify.json")) - -if not REPORT_PATH.parent.exists(): - REPORT_PATH.parent.mkdir(parents=True) - -CONTACTS: list[CONTACT] = [] - -if CONTACTS_PATH.exists(): - with open(CONTACTS_PATH) as f: - CONTACTS = json.load(f) - -# Telegram file limit -TG_FILE_LIMIT = 20 * 1024 * 1024 # 20 MB - -# LamadavaSaas API Token -LAMADAVA_SAAS_TOKEN = os.getenv("LAMADAVA_SAAS_TOKEN", None) - -# region MongoDB support -# mongodb://user:password@localhost:27017/database -# mongodb://user:password@localhost:27017/ -MONGO_URL = os.getenv("MONGO_URL", None) -MONGO_DB = os.getenv("MONGO_DB", None) - -ENABLE_MONGO = MONGO_URL and MONGO_DB -if not ENABLE_MONGO: - print( - "Bot requires MongoDB to work.\n" "Please, set MONGO_URL and MONGO_DB envs.", - file=sys.stderr, - ) - exit(1) -# endregion - -# Load custom logger config -init_logger_config(LOG_PATH, TIME_ZONE) diff --git a/app/constants/context_vars.py b/app/constants/context_vars.py new file mode 100644 index 0000000..e9b06cb --- /dev/null +++ b/app/constants/context_vars.py @@ -0,0 +1,20 @@ +from contextvars import ContextVar + +__all__ = ( + "USER_ID", + "USERNAME", + "QUERY", + "DATA_TYPE", + "CONTEXT_VARS", +) + +USER_ID: ContextVar[int] = ContextVar("USER_ID", default=0) +USERNAME: ContextVar[str] = ContextVar("USERNAME", default="") +QUERY: ContextVar[str] = ContextVar("QUERY", default="") +DATA_TYPE: ContextVar[str] = ContextVar("DATA_TYPE", default="") +CONTEXT_VARS: list[ContextVar] = [ + USER_ID, + USERNAME, + QUERY, + DATA_TYPE, +] diff --git a/app/constants/fmt_logger.py b/app/constants/fmt_logger.py new file mode 100644 index 0000000..32b6045 --- /dev/null +++ b/app/constants/fmt_logger.py @@ -0,0 +1,23 @@ +import json + +from .json_logger import JsonFormatter + +__all__ = ("FmtFormatter",) + + +def escape_string(string: str) -> str: + if " " in string or '"' in string or "'" in string: + return json.dumps(string) + return string + + +class FmtFormatter(JsonFormatter): + def format(self, record) -> str: + """ + Mostly the same as the parent's class method, the difference + being that a dict is manipulated and dumped as JSON + instead of a string. + """ + message_dict: dict[str, str] = json.loads(super().format(record)) + + return " ".join(f"{k}={escape_string(v)}" for k, v in message_dict.items() if v) diff --git a/app/constants/init_logger.py b/app/constants/init_logger.py index f2fd102..7f76b25 100644 --- a/app/constants/init_logger.py +++ b/app/constants/init_logger.py @@ -5,7 +5,12 @@ import pytz +from .fmt_logger import FmtFormatter from .json_logger import JsonFormatter +from .paths import LOG_PATH +from .settings import settings + +__all__ = ("init_logger_config",) def init_logger_config(log_path: Path, time_zone: tzinfo = pytz.timezone("Europe/Moscow")) -> None: @@ -15,16 +20,21 @@ def init_logger_config(log_path: Path, time_zone: tzinfo = pytz.timezone("Europe "disable_existing_loggers": False, "formatters": { "default": { - "format": ("%(asctime)s - %(name)s - %(levelname)s - %(message)s"), - "datefmt": "%Y-%m-%d %H:%M:%S", + "()": FmtFormatter, + "fmt_dict": { + "timestamp": "asctime", + "level": "levelname", + "loggerName": "name", + "message": "message", + }, }, "json": { "()": JsonFormatter, "fmt_dict": { "timestamp": "asctime", "level": "levelname", - "message": "message", "loggerName": "name", + "message": "message", }, }, }, @@ -76,3 +86,8 @@ def init_logger_config(log_path: Path, time_zone: tzinfo = pytz.timezone("Europe ) else: logging.config.dictConfig(config) + + +# Load custom logger config +init_logger_config(LOG_PATH, settings.time_zone) +logging.getLogger("httpx").setLevel(level=logging.ERROR) diff --git a/app/constants/json_logger.py b/app/constants/json_logger.py index 02bb3f7..1309662 100644 --- a/app/constants/json_logger.py +++ b/app/constants/json_logger.py @@ -1,20 +1,9 @@ import json import logging.config -from contextvars import ContextVar -USER_ID: ContextVar[int] = ContextVar("USER_ID", default=0) -USERNAME: ContextVar[str] = ContextVar("USERNAME", default="") -QUERY: ContextVar[str] = ContextVar("QUERY", default="") -DATA_TYPE: ContextVar[str] = ContextVar("DATA_TYPE", default="") +from .context_vars import CONTEXT_VARS -CONTEXT_VARS: list[ContextVar] = [ - USER_ID, - USERNAME, - QUERY, - DATA_TYPE, -] - -logger = logging.getLogger(__name__) +__all__ = ("JsonFormatter",) class JsonFormatter(logging.Formatter): diff --git a/app/constants/load_envs.py b/app/constants/load_envs.py deleted file mode 100644 index 090c905..0000000 --- a/app/constants/load_envs.py +++ /dev/null @@ -1,22 +0,0 @@ -from pathlib import Path - -from dotenv import load_dotenv - - -def load_envs(base_path: Path, config_path: Path, print_envs: bool = False) -> None: - env_paths = [ - base_path / ".env", - base_path / ".env.local", - config_path / ".env", - config_path / ".env.local", - ] - - for env_path in env_paths: - if env_path and env_path.exists() and env_path.is_file(): - load_dotenv(env_path) - if print_envs: - print(f"Loaded envs from {env_path}") - break - else: - if print_envs: - print("No .env file found") diff --git a/app/constants/paths.py b/app/constants/paths.py new file mode 100644 index 0000000..f7a034b --- /dev/null +++ b/app/constants/paths.py @@ -0,0 +1,25 @@ +import os +from pathlib import Path + +__all__ = ( + "APP_PATH", + "PROJECT_PATH", + "BASE_PATH", + "DATA_PATH", + "LOG_PATH", + "CONFIG_PATH", + "LOCALE_PATH", +) + +APP_PATH = Path(__file__).resolve().parent.parent +PROJECT_PATH = APP_PATH.parent +BASE_PATH = Path(os.getenv("BASE_PATH", PROJECT_PATH)) + +DATA_PATH = BASE_PATH / "data" +LOG_PATH = DATA_PATH / "logs" +CONFIG_PATH = BASE_PATH / "config" +LOCALE_PATH = PROJECT_PATH / "locales" + +DATA_PATH.mkdir(parents=True, exist_ok=True) +LOG_PATH.mkdir(parents=True, exist_ok=True) +CONFIG_PATH.mkdir(parents=True, exist_ok=True) diff --git a/app/constants/settings.py b/app/constants/settings.py new file mode 100644 index 0000000..48d37b8 --- /dev/null +++ b/app/constants/settings.py @@ -0,0 +1,63 @@ +import functools +import json +from pathlib import Path +from typing import Self + +import pytz +from pydantic import ByteSize, Field, MongoDsn, SecretStr, model_validator +from pydantic_settings import BaseSettings, SettingsConfigDict + +from .paths import BASE_PATH, CONFIG_PATH +from .types import CONTACT + +__all__ = ( + "Settings", + "settings", +) + + +class Settings(BaseSettings): + model_config = SettingsConfigDict( + env_file=[ + CONFIG_PATH / ".env.local", + CONFIG_PATH / ".env", + BASE_PATH / ".env.local", + BASE_PATH / ".env", + ], + env_ignore_empty=True, + extra="ignore", + ) + + default_locale: str = Field("en") + domain: str = Field("messages") + + token: SecretStr = Field(alias="TG_TOKEN", description="Telegram Token") + time_zone: pytz.tzinfo.BaseTzInfo = Field(pytz.timezone("Europe/Moscow"), alias="TZ") + + tg_file_size: ByteSize = Field("20 MiB", description="Telegram file size") + + report_path: Path = Field(CONFIG_PATH / "report.json") + contacts_path: Path = Field(CONFIG_PATH / "contacts.json") + + @functools.cached_property + def contacts(self) -> list[CONTACT]: + if self.contacts_path.exists(): + with self.contacts_path.open() as f: + return json.load(f) + return [] + + mongo_url: MongoDsn | None = Field(None) + mongo_db: str | None = Field(None) + + @model_validator(mode="after") + def mongo_require(self) -> Self: + if self.mongo_url is None or self.mongo_db is None: + raise ValueError("Bot requires MongoDB to work. Please, set MONGO_URL and MONGO_DB.") + self.report_path.parent.mkdir(exist_ok=True, parents=True) + return self + + +settings = Settings() + +if __name__ == "__main__": + print(repr(settings)) diff --git a/app/constants/types.py b/app/constants/types.py index c9217d0..02c0352 100644 --- a/app/constants/types.py +++ b/app/constants/types.py @@ -1,16 +1,21 @@ from enum import StrEnum from typing import TypedDict +__all__ = ( + "CONTACT", + "Keys", +) + class Keys(StrEnum): - LANGUAGE = "language" - ADD_AUTHOR_MENTION = "add_author_mention" - ADD_ORIGINAL_LINK = "add_original_link" - TIKTOK_FLAG = "tiktok_flag" - DESCRIPTION_FLAG = "description_flag" - ADD_DESCRIPTION = "add_description" - HISTORY = "history" - ADD_MEDIA_SOURCE = "add_media_source" + LANGUAGE: str = "language" + ADD_AUTHOR_MENTION: str = "add_author_mention" + ADD_ORIGINAL_LINK: str = "add_original_link" + TIKTOK_FLAG: str = "tiktok_flag" + DESCRIPTION_FLAG: str = "description_flag" + ADD_DESCRIPTION: str = "add_description" + HISTORY: str = "history" + ADD_MEDIA_SOURCE: str = "add_media_source" class CONTACT(TypedDict): diff --git a/app/context/callback_context.py b/app/context/callback_context.py index 6b10e6a..4964fd8 100644 --- a/app/context/callback_context.py +++ b/app/context/callback_context.py @@ -5,7 +5,9 @@ from telegram.ext import Application, ExtBot from telegram.ext import CallbackContext as CallbackContextBase -from app.constants import DEFAULT_LOCALE, Keys +from app.constants import Keys, settings + +__all__ = ("CallbackContext", "ContextSettings") _SET_DEFAULT = TypeVar("_SET_DEFAULT", bound=Any) @@ -99,7 +101,7 @@ def _chat_type(self, value: ChatType) -> None: @property def user_lang(self) -> str: - return self.settings.get(Keys.LANGUAGE, self._user_lang or DEFAULT_LOCALE) + return self.settings.get(Keys.LANGUAGE, self._user_lang or settings.default_locale) @classmethod def from_update( @@ -111,7 +113,7 @@ def from_update( obj._chat_type = update.effective_chat.type if update.effective_chat else ChatType.PRIVATE if update.effective_user: obj._user_lang = update.effective_user.language_code - obj.settings.setdefault(Keys.LANGUAGE, obj._user_lang or DEFAULT_LOCALE) + obj.settings.setdefault(Keys.LANGUAGE, obj._user_lang or settings.default_locale) return obj @property diff --git a/app/database/connector.py b/app/database/connector.py index eb6332a..f074ba0 100644 --- a/app/database/connector.py +++ b/app/database/connector.py @@ -3,7 +3,7 @@ from motor.motor_asyncio import AsyncIOMotorDatabase as Database from pymongo.errors import CollectionInvalid -from app import constants +from app.constants import settings _client: Client | None = None _db: Database | None = None @@ -16,8 +16,8 @@ class MongoDatabase: @classmethod def init(cls) -> None: - cls._client = Client(constants.MONGO_URL) - cls._db = cls._client.get_database(constants.MONGO_DB) + cls._client = Client(settings.mongo_url.unicode_string()) + cls._db = cls._client.get_database(settings.mongo_db) for sub_cls in cls.__subclasses__(): sub_cls._client = cls._client sub_cls._db = cls._db @@ -27,7 +27,7 @@ def close(cls) -> None: cls._client.close() @classmethod - async def _get_col(cls, name: str) -> Collection | None: + async def _get_col(cls, name: str) -> Collection: if cls._collection is not None: return cls._collection try: diff --git a/app/database/media_cache.py b/app/database/media_cache.py index 4a1b7ac..ed43f40 100644 --- a/app/database/media_cache.py +++ b/app/database/media_cache.py @@ -8,8 +8,6 @@ class MediaCache(MongoDatabase): - _collection: Collection | None = None - @classmethod async def col(cls) -> Collection | None: if cls._db is None: diff --git a/app/database/reporter.py b/app/database/reporter.py index baaa7ba..9af1641 100644 --- a/app/database/reporter.py +++ b/app/database/reporter.py @@ -1,6 +1,7 @@ import asyncio from datetime import datetime +import pytz from bson import ObjectId from motor.motor_asyncio import AsyncIOMotorCollection as Collection from pymongo.errors import OperationFailure @@ -11,8 +12,6 @@ class Reporter(MongoDatabase): - _collection: Collection | None = None - @classmethod async def col(cls) -> Collection | None: if cls._db is None: @@ -66,7 +65,7 @@ async def save_report(cls, report: Report) -> str | None: if col is None: return None - now = datetime.utcnow() + now = datetime.now(pytz.UTC) data = report.to_dict() data.pop("@type") res = await col.insert_one( @@ -80,21 +79,30 @@ async def save_report(cls, report: Report) -> str | None: @classmethod async def update_report(cls, report_id: str, report: Report) -> None: + col = await cls.col() if not cls._db: return None - col = await cls.col() await col.update_one( {"_id": report_id}, - {**report.to_dict(), "updated_at": datetime.utcnow()}, + {**report.to_dict(), "updated_at": datetime.now(pytz.UTC)}, ) @classmethod async def delete_report(cls, report_id: str) -> None: col = await cls.col() - if col is not None: + if col is None: return None await col.delete_one({"_id": report_id}) @classmethod async def from_context(cls, ctx: CallbackContext) -> list[Report]: - return await asyncio.gather(*(cls.get_report(arg[7:]) for arg in (ctx.args or []) if arg.startswith("report_"))) + report_prefix = "report_" + return list( + await asyncio.gather( + *( + cls.get_report(arg[len(report_prefix) :]) + for arg in (ctx.args or []) + if arg.startswith(report_prefix) + ), + ) + ) diff --git a/app/models/base.py b/app/models/base.py index 078f218..c2ae210 100644 --- a/app/models/base.py +++ b/app/models/base.py @@ -1,29 +1,30 @@ -from pydantic import BaseModel, Field +import json + +from pydantic import BaseModel, ConfigDict, Field + +__all__ = ("Model",) + +json_encoders = {} class Model(BaseModel): - _type = Field(None, alias="@type", exclude=True, repr=False, allow_mutation=False) + model_config = ConfigDict(json_encoders=json_encoders) + + type_: str | None = Field(None, alias="@type", exclude=True, repr=False, allow_mutation=False) def __init_subclass__(cls, **kwargs) -> None: super().__init_subclass__(**kwargs) - models: dict[str, type[Model]] | None = getattr(Model, "MODELS", None) - if models is None: - models = {} + models: dict[str, type[Model]] | None = getattr(Model, "MODELS", {}) + if not models: setattr(Model, "MODELS", models) models[cls.__name__] = cls def to_dict(self) -> dict: - def encoder(v) -> dict: - if isinstance(v, Model): - return v.to_dict() - return v - - d = self.__config__.json_loads( # type: ignore[attr-defined] - self.json( + d = json.loads( + self.model_dump_json( exclude_defaults=True, exclude_none=True, exclude_unset=True, - encoder=encoder, ) ) d["@type"] = self.__class__.__name__ @@ -31,11 +32,16 @@ def encoder(v) -> dict: @classmethod def from_dict(cls, value: dict) -> "Model": + """Create an instance of the Model class from a dictionary. + + :param value: A dictionary containing the data for the model. + :return: An instance of the Model class. + """ models: dict[str, type[Model]] = getattr(Model, "MODELS", {}) - type_: str | None = value.pop("@type", None) - return models.get(type_, cls)(**value) + type_: str | None = dict(value).pop("@type", None) + return models.get(type_, cls).model_validate(value) + - class Config: - json_encoders = { - "Model": lambda v: v.to_dict(), - } +json_encoders |= { + Model: lambda v: v.to_dict(), +} diff --git a/app/models/medias.py b/app/models/medias.py index 288a1a9..5cc5cf7 100644 --- a/app/models/medias.py +++ b/app/models/medias.py @@ -2,7 +2,7 @@ import functools import re from string import ascii_uppercase -from typing import Any, TypeVar +from typing import Any import telegram from aiohttp import ClientSession @@ -16,10 +16,28 @@ from .base import Model -MAKE_CAPTION_DEFAULT = TypeVar("MAKE_CAPTION_DEFAULT", bound=Any) +__all__ = ( + "Audio", + "Images", + "Media", + "MediaGroup", + "ParserType", + "Video", + "lang_emoji", +) + FLAG_OFFSET = ord("🇦") - ord("A") +@functools.lru_cache +def lang_emoji(lang: str) -> str: + if isinstance(lang, str) and len(lang) == 2: + lang = lang.upper() + if all(x in ascii_uppercase for x in lang): + return "".join(chr(ord(c) + FLAG_OFFSET) for c in lang) + return "" + + class ParserType(enum.StrEnum): """Parser type.""" @@ -30,15 +48,6 @@ class ParserType(enum.StrEnum): INSTAGRAM = "Instagram" -@functools.lru_cache -def lang_emoji(lang: str) -> str: - if isinstance(lang, str) and len(lang) == 2: - lang = lang.upper() - if all(x in ascii_uppercase for x in lang): - return "".join(chr(ord(c) + FLAG_OFFSET) for c in lang) - return "" - - class Media(Model): caption: str type: ParserType @@ -63,7 +72,7 @@ def language_emoji(self, value: str) -> None: def __hash__(self) -> int: return hash(self.original_url) - def real_caption( + def real_caption[MAKE_CAPTION_DEFAULT: Any]( self, ctx: CallbackContext, default: MAKE_CAPTION_DEFAULT | None = None ) -> str | MAKE_CAPTION_DEFAULT: add_caption = ctx.settings[Keys.ADD_DESCRIPTION] @@ -173,5 +182,8 @@ class Audio(Media): d = m.to_dict() print(d) m2 = Media.from_dict(d) - print(repr(m)) + print(repr(m2)) print(repr(m2.type)) + print(m2.to_dict()) + print(d) + print(d == m2.to_dict()) diff --git a/app/models/report.py b/app/models/report.py index 042da04..ebf0520 100644 --- a/app/models/report.py +++ b/app/models/report.py @@ -1,16 +1,22 @@ -from enum import Enum +from enum import StrEnum from .base import Model +__all__ = ( + "Report", + "ReportType", + "ReportPlace", +) -class ReportType(str, Enum): + +class ReportType(StrEnum): BUG = "bug" WRONG_MEDIA: str = "wrong_media" MEDIA_NOT_FOUND: str = "media_not_found" OTHER: str = "other" -class ReportPlace(str, Enum): +class ReportPlace(StrEnum): CODE = "code" INLINE = "inline" MESSAGE = "message" diff --git a/app/parsers/base.py b/app/parsers/base.py index 5cc7958..e41ebfd 100644 --- a/app/parsers/base.py +++ b/app/parsers/base.py @@ -2,7 +2,7 @@ import time from abc import ABC, abstractmethod from re import Match, Pattern -from typing import ClassVar +from typing import ClassVar, final import aiohttp @@ -11,6 +11,11 @@ logger = logging.getLogger(__name__) +__all__ = ( + "Parser", + "MediaCache", +) + class MediaCache: class FoundCache(Exception): @@ -67,6 +72,7 @@ async def _parse( raise NotImplementedError @classmethod + @final async def parse( cls, session: aiohttp.ClientSession, diff --git a/app/parsers/instagram.py b/app/parsers/instagram.py index b556b83..3fbc009 100644 --- a/app/parsers/instagram.py +++ b/app/parsers/instagram.py @@ -6,7 +6,6 @@ import aiohttp -from app.constants import CONFIG_PATH, LAMADAVA_SAAS_TOKEN from app.models.medias import Media, ParserType, Video from .base import MediaCache @@ -19,52 +18,7 @@ USER_AGENT = ( "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/91.0.4472.114 Safari/537.36" ) - -proxy_file = CONFIG_PATH / "http_proxies.txt" - -PROXIES: list[str] = list(filter(bool, proxy_file.read_text().split())) if proxy_file.exists() else [] - - -def load_proxy() -> None: - global PROXIES - PROXIES = list(filter(bool, proxy_file.read_text().split())) if proxy_file.exists() else [] - - -def del_proxy(proxy: str) -> None: - try: - PROXIES.pop(PROXIES.index(proxy.rsplit("/", 1)[-1])) - logger.info("Deleted proxy %r", proxy) - except ValueError: - pass - - -def save_proxy() -> None: - proxy_file.write_text("\n".join(PROXIES)) - - -async def make_proxy_request(session: aiohttp.ClientSession, url: str, params: dict, **kwargs) -> dict | None: - async def req(proxy) -> None: - try: - async with session.get(url, params=params, **kwargs, proxy=proxy) as resp: - return await resp.json() - except Exception as e: - del_proxy(proxy) - raise e - - load_proxy() - tasks = [asyncio.create_task(req(proxy)) for p in PROXIES for proxy in ["http://" + p, "https://" + p]] - try: - for completed_task in asyncio.as_completed(tasks): - # noinspection PyBroadException - try: - res = await completed_task - if isinstance(res, dict) and res.get("status") != "fail": - return res - except Exception: - pass - return None - finally: - save_proxy() +LAMADAVA_SAAS_TOKEN: str | None = None class Parser(BaseParser): diff --git a/app/settings/base.py b/app/settings/base.py index c656741..a8a8810 100644 --- a/app/settings/base.py +++ b/app/settings/base.py @@ -1,7 +1,7 @@ import contextvars import logging from collections.abc import Awaitable, Callable, Coroutine -from typing import Any, Generic, TypeVar, Union +from typing import Any, Union import telegram from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update @@ -16,8 +16,6 @@ BASE_SETTINGS_ID = "settings" SETTINGS_SEPARATOR = ":" -_KEY_TYPE = TypeVar("_KEY_TYPE") - logger = logging.getLogger(__name__) @@ -27,7 +25,7 @@ class Settings: False: "❌", } - class Context(Generic[_KEY_TYPE]): + class Context[_KEY_TYPE]: def __init__( self, settings: "Settings", @@ -147,7 +145,7 @@ def back_button(self) -> InlineKeyboardButton: def __str__(self) -> str: return self.current + (f"={self.result}" if self.result else "") - class SubSettings(Generic[_KEY_TYPE]): + class SubSettings[KEY_TYPE]: def __init__( self, func: Callable, @@ -155,8 +153,8 @@ def __init__( parent: Union[str, "Settings.Context"] = BASE_SETTINGS_ID, display_name: Str | None = None, settings_data_key: Keys | None = None, - settings_data_default: _KEY_TYPE | None = None, - short_display: dict[_KEY_TYPE, Str] | Callable[[_KEY_TYPE], Str] = None, + settings_data_default: KEY_TYPE | None = None, + short_display: dict[KEY_TYPE, Str] | Callable[[KEY_TYPE], Str] = None, display_in_chat: bool = True, ): self.func = func @@ -231,7 +229,7 @@ async def settings_title(update: Update, __: CallbackContext) -> str: self.settings_title: Str | Callable[[Update, CallbackContext], Awaitable[Str]] = settings_title self._settings: dict[str, Settings.SubSettings] = {BASE_SETTINGS_ID: self._base_settings} - def add_settings( + def add_settings[_KEY_TYPE]( self, display_name: Str | None = None, settings_data_key: Keys | None = None, @@ -308,7 +306,7 @@ async def wrap(ctx: Settings.Context[bool]) -> None: )(wrap) async def _base_settings(self, update: Update, context: CallbackContext) -> telegram.Message: - send_text = ( + send_text: Callable[..., Awaitable[telegram.Message]] = ( update.callback_query.edit_message_text if update.callback_query and update.callback_query.message else update.message.reply_text diff --git a/app/utils/app_patchers/json_logger.py b/app/utils/app_patchers/json_logger.py index bfa457a..8c96f08 100644 --- a/app/utils/app_patchers/json_logger.py +++ b/app/utils/app_patchers/json_logger.py @@ -3,7 +3,7 @@ from telegram import Update -from app.constants.json_logger import DATA_TYPE, QUERY, USER_ID, USERNAME +from app.constants.context_vars import DATA_TYPE, QUERY, USER_ID, USERNAME from app.context import CallbackContext from .base import HandlerCallback, Patcher diff --git a/app/utils/i18n/__init__.py b/app/utils/i18n/__init__.py index c20ffb7..fb49f49 100644 --- a/app/utils/i18n/__init__.py +++ b/app/utils/i18n/__init__.py @@ -1,2 +1,2 @@ -from app.utils.i18n.base import * -from app.utils.i18n.functions import * +from .base import * +from .functions import * diff --git a/app/utils/i18n/base.py b/app/utils/i18n/base.py index 6e8a04a..859007a 100644 --- a/app/utils/i18n/base.py +++ b/app/utils/i18n/base.py @@ -7,7 +7,7 @@ from telegram import TelegramObject from telegram.request._requestparameter import RequestParameter -from app import constants +from app.constants import paths, settings __all__ = ( "CURRENT_LANG", @@ -15,7 +15,7 @@ "Str", ) -CURRENT_LANG = ContextVar("CURRENT_LANG", default=constants.DEFAULT_LOCALE) +CURRENT_LANG = ContextVar("CURRENT_LANG", default=settings.default_locale) _translations: dict[str, NullTranslations] = {} @@ -29,11 +29,11 @@ def __init__(self, *args, type_: str = "gettext") -> None: def __str__(self) -> str: lang = CURRENT_LANG.get() - path = constants.LOCALE_PATH + path = paths.LOCALE_PATH t = _translations.get(lang, None) if t is None: t = translation( - domain=constants.DOMAIN, + domain=settings.domain, localedir=path, languages=[lang], fallback=True, diff --git a/cli/comands.py b/cli/comands.py index bc6a63d..110b0c8 100644 --- a/cli/comands.py +++ b/cli/comands.py @@ -1,7 +1,9 @@ import logging from collections.abc import Callable -from app.constants import BASE_PATH, DEFAULT_LOCALE +from constants import BASE_PATH + +from app.constants import DEFAULT_LOCALE from cli.compile import main as compile_locale from cli.extract import main as extract_locale from cli.update import main as update_locale diff --git a/cli/compile.py b/cli/compile.py index d6ee2e0..a041aa1 100644 --- a/cli/compile.py +++ b/cli/compile.py @@ -1,6 +1,7 @@ from babel.messages.frontend import compile_catalog +from constants.paths import LOCALE_PATH -from app.constants import DOMAIN, LOCALE_PATH +from app.constants import DOMAIN from cli.distributions import dist diff --git a/cli/distributions.py b/cli/distributions.py index e1a4b4e..a0daa9d 100644 --- a/cli/distributions.py +++ b/cli/distributions.py @@ -1,10 +1,9 @@ import json import os.path +from constants import PROJECT_PATH from distutils.dist import Distribution -from app.constants import PROJECT_PATH - __all__ = ["dist"] PROJECT_PATH.cwd() diff --git a/cli/extract.py b/cli/extract.py index 1e88ff6..0dad3b6 100644 --- a/cli/extract.py +++ b/cli/extract.py @@ -1,6 +1,7 @@ from babel.messages.frontend import extract_messages +from constants.paths import LOCALE_PATH -from app.constants import DOMAIN, LOCALE_PATH +from app.constants import DOMAIN from cli.distributions import dist diff --git a/cli/update.py b/cli/update.py index 2f094ea..1309c69 100644 --- a/cli/update.py +++ b/cli/update.py @@ -1,6 +1,7 @@ from babel.messages.frontend import update_catalog +from constants.paths import LOCALE_PATH -from app.constants import DEFAULT_LOCALE, DOMAIN, LOCALE_PATH +from app.constants import DEFAULT_LOCALE, DOMAIN from cli.distributions import dist diff --git a/main.py b/main.py index 134e079..388c08b 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,9 @@ import uuid import aiohttp as aiohttp +from constants import settings from mongopersistence import MongoPersistence +from settings import callback_handler from telegram import InlineQueryResult, InlineQueryResultsButton, InlineQueryResultVideo, Update from telegram import Video as TelegramVideo from telegram.constants import ChatType, MessageEntityType, ParseMode @@ -18,7 +20,7 @@ filters, ) -from app import commands, constants, settings +from app import commands from app.context import CallbackContext from app.database import Reporter from app.database.connector import MongoDatabase @@ -270,11 +272,9 @@ async def post_shutdown(app: Application) -> None: def main() -> None: """Start the bot.""" - logger.debug("Token: %r", constants.TOKEN) - persistence: MongoPersistence[dict, dict, dict] = MongoPersistence( - mongo_url=constants.MONGO_URL, - db_name=constants.MONGO_DB, + mongo_url=settings.mongo_url.unicode_string(), + db_name=settings.mongo_db, name_col_user_data="user-data", name_col_chat_data="chat-data", name_col_bot_data="bot-data", @@ -285,13 +285,13 @@ def main() -> None: ) defaults = Defaults( parse_mode=ParseMode.HTML, - tzinfo=constants.TIME_ZONE, + tzinfo=settings.time_zone, ) application = ( Application.builder() .persistence(persistence) .defaults(defaults=defaults) - .token(constants.TOKEN) + .token(settings.token.get_secret_value()) .context_types(ContextTypes(context=CallbackContext)) .post_init(post_init) .post_shutdown(post_shutdown) @@ -301,7 +301,7 @@ def main() -> None: application.add_handlers( [ ChosenInlineResultHandler(chosen_inline_query), - settings.callback_handler(), + callback_handler(), InlineQueryHandler(inline_query), MessageHandler(filters.TEXT & ~filters.COMMAND, link_parser), ] diff --git a/poetry.lock b/poetry.lock index 2ca8b91..75c665d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -120,6 +120,17 @@ files = [ [package.dependencies] frozenlist = ">=1.1.0" +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, +] + [[package]] name = "anyio" version = "4.3.0" @@ -765,55 +776,132 @@ virtualenv = ">=20.10.0" [[package]] name = "pydantic" -version = "1.10.15" -description = "Data validation and settings management using python type hints" +version = "2.7.1" +description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"}, + {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.18.2" +typing-extensions = ">=4.6.1" + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.18.2" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"}, + {file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"}, + {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"}, + {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"}, + {file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"}, + {file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"}, + {file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"}, + {file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"}, + {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"}, + {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"}, + {file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"}, + {file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"}, + {file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"}, + {file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"}, + {file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"}, + {file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"}, + {file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"}, + {file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"}, + {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"}, + {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"}, + {file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"}, + {file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"}, + {file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"}, + {file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"}, + {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"}, + {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"}, + {file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"}, + {file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"}, + {file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pydantic-settings" +version = "2.2.1" +description = "Settings management using Pydantic" +optional = false +python-versions = ">=3.8" files = [ - {file = "pydantic-1.10.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22ed12ee588b1df028a2aa5d66f07bf8f8b4c8579c2e96d5a9c1f96b77f3bb55"}, - {file = "pydantic-1.10.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:75279d3cac98186b6ebc2597b06bcbc7244744f6b0b44a23e4ef01e5683cc0d2"}, - {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50f1666a9940d3d68683c9d96e39640f709d7a72ff8702987dab1761036206bb"}, - {file = "pydantic-1.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82790d4753ee5d00739d6cb5cf56bceb186d9d6ce134aca3ba7befb1eedbc2c8"}, - {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:d207d5b87f6cbefbdb1198154292faee8017d7495a54ae58db06762004500d00"}, - {file = "pydantic-1.10.15-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e49db944fad339b2ccb80128ffd3f8af076f9f287197a480bf1e4ca053a866f0"}, - {file = "pydantic-1.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:d3b5c4cbd0c9cb61bbbb19ce335e1f8ab87a811f6d589ed52b0254cf585d709c"}, - {file = "pydantic-1.10.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3d5731a120752248844676bf92f25a12f6e45425e63ce22e0849297a093b5b0"}, - {file = "pydantic-1.10.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c365ad9c394f9eeffcb30a82f4246c0006417f03a7c0f8315d6211f25f7cb654"}, - {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3287e1614393119c67bd4404f46e33ae3be3ed4cd10360b48d0a4459f420c6a3"}, - {file = "pydantic-1.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be51dd2c8596b25fe43c0a4a59c2bee4f18d88efb8031188f9e7ddc6b469cf44"}, - {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6a51a1dd4aa7b3f1317f65493a182d3cff708385327c1c82c81e4a9d6d65b2e4"}, - {file = "pydantic-1.10.15-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4e316e54b5775d1eb59187f9290aeb38acf620e10f7fd2f776d97bb788199e53"}, - {file = "pydantic-1.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:0d142fa1b8f2f0ae11ddd5e3e317dcac060b951d605fda26ca9b234b92214986"}, - {file = "pydantic-1.10.15-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7ea210336b891f5ea334f8fc9f8f862b87acd5d4a0cbc9e3e208e7aa1775dabf"}, - {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3453685ccd7140715e05f2193d64030101eaad26076fad4e246c1cc97e1bb30d"}, - {file = "pydantic-1.10.15-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bea1f03b8d4e8e86702c918ccfd5d947ac268f0f0cc6ed71782e4b09353b26f"}, - {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:005655cabc29081de8243126e036f2065bd7ea5b9dff95fde6d2c642d39755de"}, - {file = "pydantic-1.10.15-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:af9850d98fc21e5bc24ea9e35dd80a29faf6462c608728a110c0a30b595e58b7"}, - {file = "pydantic-1.10.15-cp37-cp37m-win_amd64.whl", hash = "sha256:d31ee5b14a82c9afe2bd26aaa405293d4237d0591527d9129ce36e58f19f95c1"}, - {file = "pydantic-1.10.15-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5e09c19df304b8123938dc3c53d3d3be6ec74b9d7d0d80f4f4b5432ae16c2022"}, - {file = "pydantic-1.10.15-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7ac9237cd62947db00a0d16acf2f3e00d1ae9d3bd602b9c415f93e7a9fc10528"}, - {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:584f2d4c98ffec420e02305cf675857bae03c9d617fcfdc34946b1160213a948"}, - {file = "pydantic-1.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bbc6989fad0c030bd70a0b6f626f98a862224bc2b1e36bfc531ea2facc0a340c"}, - {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d573082c6ef99336f2cb5b667b781d2f776d4af311574fb53d908517ba523c22"}, - {file = "pydantic-1.10.15-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6bd7030c9abc80134087d8b6e7aa957e43d35714daa116aced57269a445b8f7b"}, - {file = "pydantic-1.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:3350f527bb04138f8aff932dc828f154847fbdc7a1a44c240fbfff1b57f49a12"}, - {file = "pydantic-1.10.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:51d405b42f1b86703555797270e4970a9f9bd7953f3990142e69d1037f9d9e51"}, - {file = "pydantic-1.10.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a980a77c52723b0dc56640ced396b73a024d4b74f02bcb2d21dbbac1debbe9d0"}, - {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f1a1fb467d3f49e1708a3f632b11c69fccb4e748a325d5a491ddc7b5d22383"}, - {file = "pydantic-1.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:676ed48f2c5bbad835f1a8ed8a6d44c1cd5a21121116d2ac40bd1cd3619746ed"}, - {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:92229f73400b80c13afcd050687f4d7e88de9234d74b27e6728aa689abcf58cc"}, - {file = "pydantic-1.10.15-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2746189100c646682eff0bce95efa7d2e203420d8e1c613dc0c6b4c1d9c1fde4"}, - {file = "pydantic-1.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:394f08750bd8eaad714718812e7fab615f873b3cdd0b9d84e76e51ef3b50b6b7"}, - {file = "pydantic-1.10.15-py3-none-any.whl", hash = "sha256:28e552a060ba2740d0d2aabe35162652c1459a0b9069fe0db7f4ee0e18e74d58"}, - {file = "pydantic-1.10.15.tar.gz", hash = "sha256:ca832e124eda231a60a041da4f013e3ff24949d94a01154b137fc2f2a43c3ffb"}, + {file = "pydantic_settings-2.2.1-py3-none-any.whl", hash = "sha256:0235391d26db4d2190cb9b31051c4b46882d28a51533f97440867f012d4da091"}, + {file = "pydantic_settings-2.2.1.tar.gz", hash = "sha256:00b9f6a5e95553590434c0fa01ead0b216c3e10bc54ae02e37f359948643c5ed"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +pydantic = ">=2.3.0" +python-dotenv = ">=0.21.0" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +toml = ["tomli (>=2.0.1)"] +yaml = ["pyyaml (>=6.0.1)"] [[package]] name = "pymongo" @@ -1218,4 +1306,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "4567e8e021c1d16c1b8a1178b958ed8f9238dd0eaa71c512d0a1d9232e9f2f08" +content-hash = "f32704ed341e0a56ac522bb54b9560febf6eb13069230e931af94c23d2453c0a" diff --git a/pyproject.toml b/pyproject.toml index 726b602..62ce61c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,17 +13,17 @@ pytube = "^15.0.0" python-dotenv = "^1.0.0" pytz = "^2024.1" aiofiles = "^23.1.0" -pydantic = "^1.10.15" +pydantic = "^2" motor = "^3.1.2" mongopersistence = "^0.3.1" contextvars = "^2.4" +pydantic-settings = "^2" [tool.poetry.group.dev.dependencies] babel = "*" black = "*" pre-commit = "*" ruff = "*" -#mypy = "*" [tool.poetry.group.types.dependencies] types-pytz = "*"