From 830912bb8246ae81ce82bb7805ed27595d9fb52d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joscha=20G=C3=B6tzer?= Date: Wed, 26 Aug 2020 17:30:26 +0200 Subject: [PATCH] Updated to Pyrogram 1.0.2, but removed the dependency --- .../botkit_modules/module_manager/__init__.py | 10 +- botkit/botkit_modules/system/status_pings.py | 3 +- botkit/botkit_modules/system/system_tests.py | 9 +- .../system/sytem_management_module.py | 18 +- botkit/botkit_services/lookupservice.py | 37 ++- botkit/builders/__init__.py | 2 +- botkit/builders/inlinemenubuilder.py | 3 +- botkit/buttons.py | 4 +- botkit/clients/configured_client.py | 29 +-- botkit/commands/TODO.md | 26 +- botkit/commands/command.py | 14 +- botkit/core/startup.py | 16 +- .../callbackactiondispatcher.py | 11 +- botkit/dispatching/dispatcher.py | 4 +- botkit/future_tgtypes/message.py | 3 + .../persistence/textmessagemodel.py | 10 +- .../future_tgtypes/update_field_extractor.py | 10 +- botkit/future_tgtypes/user.py | 5 + botkit/inlinequeries/inlineresultcontainer.py | 2 +- botkit/libraries/__init__.py | 0 botkit/libraries/_checks.py | 8 + botkit/libraries/annotations.py | 40 +++ botkit/libraries/pyro_types/__init__.py | 0 .../_pyrogram_update_type_inference.py | 30 ++- botkit/routing/pipelines/callbacks.py | 13 - botkit/routing/pipelines/execution_plan.py | 2 +- .../pipelines/factories/factory_types.py | 2 - .../steps/custom_handler_step_factory.py | 24 +- .../factories/steps/gather_step_factory.py | 2 +- .../steps/render_view_step_factory.py | 11 +- .../factories/steps/send_view_step_factory.py | 10 +- .../callback_query_pipeline_factory.py | 8 +- .../updates/message_pipeline_factory.py | 8 +- .../pipelines/factories/updates/others.py | 15 +- .../update_pipeline_factory.py} | 38 +-- botkit/routing/pipelines/filters.py | 8 +- botkit/routing/route.py | 8 +- botkit/routing/route_builder/builder.py | 13 +- botkit/routing/route_builder/types.py | 2 +- botkit/routing/triggers.py | 5 +- botkit/routing/types.py | 17 +- .../update_types/update_type_inference.py | 4 +- botkit/services/companionbotservice.py | 35 ++- botkit/services/historyservice.py | 25 +- botkit/tghelpers/direct_links.py | 7 +- botkit/tghelpers/entities/message_entities.py | 14 +- botkit/tghelpers/names.py | 8 +- botkit/utils/datetime_utils.py | 2 +- botkit/utils/typed_callable.py | 12 +- botkit/views/base.py | 77 +----- botkit/views/botkit_context.py | 8 +- botkit/views/functional_views.py | 2 +- botkit/views/rendered_messages.py | 65 +++++ botkit/views/renderer_client_mixin.py | 9 +- botkit/views/sender_interface.py | 2 +- botkit/views/success_view.py | 4 +- botkit/views/types.py | 6 + botkit/views/views.py | 4 +- poetry.lock | 246 ++++++------------ pyproject.toml | 6 +- pytest.ini | 4 + setup.py | 14 - tests/builders/test_inlinemenubuilder.py | 11 +- .../test_prefixbasedinlinequeryhandler.py | 10 +- ...nc_module_tests.py => test_func_module.py} | 0 ..._tests.py => test_evaluate_send_target.py} | 3 +- tests/routing/plan/test_update_types.py | 21 +- tests/routing/test_publish_expression.py | 10 +- tests/routing/test_routing.py | 38 ++- tests/routing/test_state_machines.py | 16 +- tests/utils/test_typed_callables.py | 70 +++++ tests/utils/typed_callable_tests.py | 13 - tests/views/test_functional_views.py | 10 +- tests/views/test_simple_message_view.py | 29 --- .../views/test_typical_inline_message_view.py | 51 ---- 75 files changed, 613 insertions(+), 703 deletions(-) create mode 100644 botkit/future_tgtypes/user.py create mode 100644 botkit/libraries/__init__.py create mode 100644 botkit/libraries/_checks.py create mode 100644 botkit/libraries/annotations.py create mode 100644 botkit/libraries/pyro_types/__init__.py rename botkit/{routing/update_types => libraries/pyro_types}/_pyrogram_update_type_inference.py (63%) delete mode 100644 botkit/routing/pipelines/callbacks.py rename botkit/routing/pipelines/factories/{base.py => updates/update_pipeline_factory.py} (78%) create mode 100644 botkit/views/rendered_messages.py create mode 100644 botkit/views/types.py create mode 100644 pytest.ini delete mode 100644 setup.py rename tests/module_tests/{func_module_tests.py => test_func_module.py} (100%) rename tests/routing/pipelines/factories/steps/{evaluate_send_target_pyrogram_tests.py => test_evaluate_send_target.py} (98%) create mode 100644 tests/utils/test_typed_callables.py delete mode 100644 tests/utils/typed_callable_tests.py delete mode 100644 tests/views/test_simple_message_view.py delete mode 100644 tests/views/test_typical_inline_message_view.py diff --git a/botkit/botkit_modules/module_manager/__init__.py b/botkit/botkit_modules/module_manager/__init__.py index e4fbef0..bd8dad8 100644 --- a/botkit/botkit_modules/module_manager/__init__.py +++ b/botkit/botkit_modules/module_manager/__init__.py @@ -1,15 +1,13 @@ from haps import Inject -from pyrogram import Filters, Message +from pyrogram.filters import command from botkit.core.moduleloader import ModuleLoader from botkit.core.modules import Module -from .paged_module_view import PagedModuleView -from .view_models import ModuleInfo, ModuleInfosCollectionModel from botkit.routing.route_builder.builder import RouteBuilder from botkit.services.companionbotservice import CompanionBotService -from botkit.views.renderer_client_mixin import PyroRendererClientMixin +from .paged_module_view import PagedModuleView +from .view_models import ModuleInfo, ModuleInfosCollectionModel from ...types.client import IClient -from ...views.botkit_context import BotkitContext class ModuleManagerModule(Module): @@ -23,7 +21,7 @@ def __init__(self, user_client: IClient, bot_client: IClient): def register(self, routes: RouteBuilder): with routes.using(self.user_client): ( - routes.on(Filters.command("modules", prefixes=["#", "/"])) + routes.on(command("modules", prefixes=["#", "/"])) .gather(self.get_modules) .then_send(PagedModuleView, via=self.bot_client) ) diff --git a/botkit/botkit_modules/system/status_pings.py b/botkit/botkit_modules/system/status_pings.py index 0e0e97c..096aa07 100644 --- a/botkit/botkit_modules/system/status_pings.py +++ b/botkit/botkit_modules/system/status_pings.py @@ -5,10 +5,11 @@ from datetime import datetime, timedelta from pydantic import BaseModel from pyrogram import Client -from pyrogram import Message from typing import Literal from typing import Optional, Union, List from typing import cast + +from pyrogram.types import Message from typing_extensions import AsyncGenerator from botkit.botkit_modules.system.sytem_management_module import ToggleSystemStateCommand diff --git a/botkit/botkit_modules/system/system_tests.py b/botkit/botkit_modules/system/system_tests.py index 6662e7e..eb86a61 100644 --- a/botkit/botkit_modules/system/system_tests.py +++ b/botkit/botkit_modules/system/system_tests.py @@ -1,15 +1,13 @@ -from typing import Any, List, Optional +from typing import List from unittest.mock import Mock from haps import Inject -from pyrogram import Chat, Message, User +from pyrogram.types import Chat, Message, User from botkit.core.moduleloader import ModuleLoader -from botkit.core.modules import Module, module -from botkit.routing.pipelines.callbacks import HandlerSignature +from botkit.core.modules import Module from botkit.routing.route import RouteDefinition, RouteHandler from botkit.routing.route_builder.builder import RouteBuilder -from botkit.routing.route_builder.route_collection import RouteCollection from botkit.routing.update_types.updatetype import UpdateType from botkit.types.client import IClient @@ -44,6 +42,7 @@ async def test_module_routes(self, routes: List[RouteDefinition]): async def fire_request(self, update_type: UpdateType, route: RouteHandler): try: + # noinspection PyUnresolvedReferences should_not_test = route.callback.notests return except AttributeError: diff --git a/botkit/botkit_modules/system/sytem_management_module.py b/botkit/botkit_modules/system/sytem_management_module.py index 23fa435..1372ce8 100644 --- a/botkit/botkit_modules/system/sytem_management_module.py +++ b/botkit/botkit_modules/system/sytem_management_module.py @@ -3,10 +3,12 @@ import asyncio from buslane.commands import Command, CommandHandler from haps import Inject, Container -from pyrogram import Filters, Message, User, Client +from pyrogram import filters +from pyrogram.types import Message, User from typing import Optional, List, Any, Literal from botkit.botkit_modules.system.system_tests import notests +from botkit.libraries.annotations import IClient from botkit.persistence.callback_manager import ( RedisCallbackManager, ICallbackManager, @@ -19,7 +21,7 @@ @dataclass class _LoadCtx: - user_client_me: User + user_client_id: int @dataclass @@ -32,25 +34,25 @@ class ToggleSystemStateCommand(Command): class SystemManagementModule(Module): module_loader: ModuleLoader = Inject() - def __init__(self, user_client: Client) -> None: + def __init__(self, user_client: IClient) -> None: self.user_client = user_client self.system_paused: bool = False self.paused_modules: Optional[List[Module]] = None async def load(self) -> _LoadCtx: - return _LoadCtx(user_client_me=await self.user_client.get_me()) + return _LoadCtx(user_client_id=(await self.user_client.get_me()).id) def register(self, routes: RouteBuilder): routes.use(self.user_client) - restart_command = Filters.command("r", prefixes=[".", "#"]) | Filters.command("restart") - only_owner = Filters.user(routes.context.load_result.user_client_me.id) + restart_command = filters.command("r", prefixes=[".", "#"]) | filters.command("restart") + only_owner = filters.user(routes.context.load_result.user_client_id) routes.on(restart_command & only_owner).call(self.restart_system) - routes.on(Filters.command(["off", "pause"]) & only_owner).call(self.handle_pause_command) - routes.on(Filters.command(["on", "unpause"]) & only_owner).call( + routes.on(filters.command(["off", "pause"]) & only_owner).call(self.handle_pause_command) + routes.on(filters.command(["on", "unpause"]) & only_owner).call( self.handle_unpause_command ) diff --git a/botkit/botkit_services/lookupservice.py b/botkit/botkit_services/lookupservice.py index 79a5c02..bc13e31 100644 --- a/botkit/botkit_services/lookupservice.py +++ b/botkit/botkit_services/lookupservice.py @@ -5,14 +5,13 @@ from haps import * from telethon.tl.custom import Message, Dialog -from commons.core.routing import Event from commons.core.clients.userclient import IUserClient from commons.core.descriptors.base import EntityDescriptor from commons.core.util import string_similarity from telethon.tl import TLObject from telethon.utils import get_display_name as telethon_get_display_name -T = TypeVar('P') +T = TypeVar("P") class EntityNotFoundError(Exception): @@ -22,26 +21,32 @@ class EntityNotFoundError(Exception): def get_display_name(entity: Any): if isinstance(entity, Dialog): return entity.name - if hasattr(entity, 'title'): + if hasattr(entity, "title"): return entity.title return telethon_get_display_name(entity) + @base class ILookupService(ABC): @abstractmethod - async def resolve_peer(self, descriptor: EntityDescriptor, raise_=True): pass + async def resolve_peer(self, descriptor: EntityDescriptor, raise_=True): + pass @abstractmethod - async def resolve_full_peer(self, descriptor: EntityDescriptor): pass + async def resolve_full_peer(self, descriptor: EntityDescriptor): + pass @abstractmethod - async def get_message_by_id(self, chat: Any, message_id: int) -> Message: pass + async def get_message_by_id(self, chat: Any, message_id: int) -> Message: + pass @abstractmethod - async def get_previous_messages(self, event: Event, n: int = 1) -> List[Message]: pass + async def get_previous_messages(self, event: Event, n: int = 1) -> List[Message]: + pass @abstractmethod - async def get_last_message_in_chat(self, input_chat: Any) -> Message: pass + async def get_last_message_in_chat(self, input_chat: Any) -> Message: + pass @egg @@ -63,11 +68,7 @@ async def resolve_full_peer(self, descriptor: EntityDescriptor): return self.client.session.get_input_entity(descriptor) async def _resolve_chat_by_title( - self, - title: str, - confidence=0.9, - aggressive=False, - raise_=True + self, title: str, confidence=0.9, aggressive=False, raise_=True ) -> TLObject: results = await self.client.get_dialogs(limit=200) @@ -87,11 +88,7 @@ def matches_query(found_name): return None async def _resolve_user_by_title( - self, - title: str, - confidence=0.9, - aggressive=False, - raise_=True + self, title: str, confidence=0.9, aggressive=False, raise_=True ) -> TLObject: results = await self.client.get_dialogs(limit=40) @@ -104,7 +101,9 @@ def matches_query(found_name): # Search failed. for d in results: try: - participants = self.client.iter_participants(d.input_entity, 200, search='', aggressive=aggressive) + participants = self.client.iter_participants( + d.input_entity, 200, search="", aggressive=aggressive + ) async for p in participants: if matches_query(get_display_name(p)): return p.entity diff --git a/botkit/builders/__init__.py b/botkit/builders/__init__.py index b33f369..0ee8841 100644 --- a/botkit/builders/__init__.py +++ b/botkit/builders/__init__.py @@ -3,7 +3,7 @@ from .htmlbuilder import HtmlBuilder from .inlinemenubuilder import InlineMenuBuilder from .metabuilder import MetaBuilder -from ..views.base import RenderedMessage, RenderedTextMessage +from ..views.rendered_messages import RenderedMessage, RenderedTextMessage class ViewBuilder: diff --git a/botkit/builders/inlinemenubuilder.py b/botkit/builders/inlinemenubuilder.py index 8e1d74f..4d6d6b8 100644 --- a/botkit/builders/inlinemenubuilder.py +++ b/botkit/builders/inlinemenubuilder.py @@ -3,7 +3,7 @@ from cached_property import cached_property from haps import Container from haps.exceptions import NotConfigured -from pyrogram import InlineKeyboardButton +from pyrogram.types import InlineKeyboardButton from botkit import buttons from botkit.persistence.callback_manager import ( @@ -16,6 +16,7 @@ from botkit.utils.sentinel import NotSet, Sentinel +# noinspection PyIncorrectDocstring class InlineMenuRowBuilder: def __init__(self, state: Optional[Any], override_buttons: List[Any] = None): if override_buttons: diff --git a/botkit/buttons.py b/botkit/buttons.py index 85f635d..17aecac 100644 --- a/botkit/buttons.py +++ b/botkit/buttons.py @@ -1,4 +1,4 @@ -from pyrogram import InlineKeyboardButton +from pyrogram.types import InlineKeyboardButton from botkit.inlinequeries.contexts import IInlineModeContext, DefaultInlineModeContext @@ -14,5 +14,5 @@ def switch_inline_button( "switch_inline_query" + "_current_chat" if current_chat else "": in_context.format_query() - } + }, ) diff --git a/botkit/clients/configured_client.py b/botkit/clients/configured_client.py index f6525cd..a4b676b 100644 --- a/botkit/clients/configured_client.py +++ b/botkit/clients/configured_client.py @@ -1,8 +1,8 @@ from abc import abstractmethod from decouple import config -from pyrogram import Client, User -from tgintegration import InteractionClient +from pyrogram import Client +from pyrogram.types import User from botkit.clients.client_config import ClientConfig @@ -42,28 +42,3 @@ async def get_me(self) -> User: return self._me self._me = await super().get_me() return self._me - - -class ConfiguredInteractionClient(InteractionClient): - def __init__(self, **kwargs) -> None: - merged_args = dict( - session_name=self.config.session_name, - api_id=config("API_ID"), - api_hash=config("API_HASH"), - bot_token=self.config.bot_token, - phone_number=self.config.phone_number, - ) - merged_args.update(kwargs) - super().__init__(**merged_args) - self._me = None - - @property - @abstractmethod - def config(self) -> ClientConfig: - ... - - async def get_me(self) -> User: - if self._me is not None: - return self._me - self._me = await super().get_me() - return self._me diff --git a/botkit/commands/TODO.md b/botkit/commands/TODO.md index 49fcb20..3053391 100644 --- a/botkit/commands/TODO.md +++ b/botkit/commands/TODO.md @@ -11,9 +11,9 @@ Commands from all modules get registered in a central place. A singleton (compound, all types) internal handler goes through all registered commands and builds a list `quick_action_matches: List[CommandDefinition]`. Then it replies with reply keyboard buttons. `botkit.use_quick_actions()` ?? - + modulemanager - + ``` routes.register_command() ``` @@ -30,23 +30,23 @@ routes.register_command() ## Quick Action Registry (Ideas) -### Filters.text +### filters.text (Most common. Needs a good hierarchical menu) - **Use in inline query** -> "Which bot @?" - @letmebot -> Share button - **Remind me** - -> `ChoiceView("Who to remind?", ["Just me", "Someone else", "Me and others"])` + -> `ChoiceView("Who to remind?", ["Just me", "Someone else", "Me and others"])` -> `DateTimePickerView("When?")` - **Edit** -> `send_as_user(...)` (so that user can edit) - **Share** -> `ShareView(...)` -### Filters.text (multiple) +### filters.text (multiple) "Does this message belong to the others?" yes/no -- **Merge** -> -- **Delete** (if forwarded and user admin) +- **Merge** -> +- **Delete** (if forwarded and user admin) -> "This will delete messages 1-3, 7, and 8 from {origin_chat_title}" (yes/no) - **Open in VSCode** -### Filters.link +### filters.link - **Open in browser** -> `ChoiceView(links) if len(links) > 1 else links[0]` - **Add to Pocket** -> `ChoiceView(links, multiple=True) if len(links) > 1 else links[0]` @@ -57,17 +57,17 @@ routes.register_command() - Notion - Add to project -> `ChoiceView(...)` -### Filters.sticker +### filters.sticker - Add to pack - Optimize -### Filters.sticker (multiple) +### filters.sticker (multiple) - Merge -### Filters.command +### filters.command - Choose bot -### Filters.contains_emoji +### filters.contains_emoji - Explain emojis # Editor @@ -89,4 +89,4 @@ The `EditorView` consists of a message with quick actions above and the content ## Prototype of hierarchical settings module -Click on setting -> opens group of related settings (and back button) \ No newline at end of file +Click on setting -> opens group of related settings (and back button) diff --git a/botkit/commands/command.py b/botkit/commands/command.py index 3c17bf7..ac91570 100644 --- a/botkit/commands/command.py +++ b/botkit/commands/command.py @@ -1,8 +1,8 @@ from dataclasses import dataclass, field from mypy_extensions import TypedDict -from pyrogram import Update -from pyrogram.client.filters.filter import Filter +from pyrogram.filters import Filter +from pyrogram.types import Update from typing import Union, Dict, List, Tuple, Optional from typing_extensions import Literal import pyrogram @@ -19,9 +19,6 @@ } - - - # trigger_dict = {"private": True, "contains_url": True} # # trigger_text = "private & containsUrl" @@ -38,7 +35,7 @@ # CommandDefinition( # name="browse", # quick_action=QuickAction( -# trigger=EntityFilters.url & Filters.private, +# trigger=EntityFilters.url & filters.private, # ), # quick_action_prompt=True, # actions get included in quick actions reply keyboard # delete_query=True @@ -53,7 +50,7 @@ class CommandParser: @classmethod def from_declaration( - cls, value: Union[str, List[str], Tuple[str], "CommandParser"] + cls, value: Union[str, List[str], Tuple[str], "CommandParser"] ) -> "CommandParser": if isinstance(value, CommandParser): return value # for when user already provides initialized object @@ -83,7 +80,7 @@ class CommandDefinition: # region Routing -QUICK_ACTION_UPDATE_TYPES = (pyrogram.Message, pyrogram.Poll) +QUICK_ACTION_UPDATE_TYPES = (pyrogram.types.Message, pyrogram.types.Poll) async def match_quick_actions(commands: List[CommandDefinition], update: Update): @@ -91,5 +88,4 @@ async def match_quick_actions(commands: List[CommandDefinition], update: Update) return - # endregion diff --git a/botkit/core/startup.py b/botkit/core/startup.py index 14401aa..1853c6f 100644 --- a/botkit/core/startup.py +++ b/botkit/core/startup.py @@ -18,7 +18,7 @@ from botkit.core.moduleloader import ModuleLoader from botkit.core.modules._module import Module -from botkit.tghelpers.names import user_or_displayname +from botkit.tghelpers.names import user_or_display_name from abc import ABC @@ -47,14 +47,14 @@ async def _start_clients(self): await asyncio.gather(*start_tasks) async def __start_client(self, client): - session_path = client.session_name if hasattr(client, 'session_name') else client.session.filename - - log.debug( - f"Starting session " f"{session_path}..." + session_path = ( + client.session_name if hasattr(client, "session_name") else client.session.filename ) + + log.debug(f"Starting session " f"{session_path}...") await client.start() me = await client.get_me() - log.info(f"Started {user_or_displayname(me)} as {client.__class__.__name__}.") + log.info(f"Started {user_or_display_name(me)} as {client.__class__.__name__}.") def run(self) -> None: log.debug("Autodiscovery completed. Initializing...") @@ -90,7 +90,7 @@ async def _start_async(self): async def _shutdown(self, loop: AbstractEventLoop): tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()] [task.cancel() for task in tasks] - log.info(f"Cancelling {len(tasks)} outstanding tasks") - await asyncio.gather(*tasks, return_exceptions=True) + log.info(f"Cancelling {len(tasks)} running or outstanding tasks") + await asyncio.gather(*tasks) await self.on_shutdown() loop.stop() diff --git a/botkit/dispatching/callbackqueries/callbackactiondispatcher.py b/botkit/dispatching/callbackqueries/callbackactiondispatcher.py index 5082a98..8c32a4d 100644 --- a/botkit/dispatching/callbackqueries/callbackactiondispatcher.py +++ b/botkit/dispatching/callbackqueries/callbackactiondispatcher.py @@ -4,8 +4,9 @@ from cached_property import cached_property from haps import Container from logzero import logger as log -from pyrogram import CallbackQuery, CallbackQueryHandler, Update -from pyrogram.client.filters.filters import create +from pyrogram.filters import create +from pyrogram.handlers import CallbackQueryHandler +from pyrogram.types import CallbackQuery, Update from botkit.persistence.callback_manager import CallbackActionContext, ICallbackManager from botkit.routing.route import RouteDefinition, RouteHandler @@ -33,13 +34,13 @@ def add_action_route(self, route: RouteHandler): @property def pyrogram_handler(self) -> CallbackQueryHandler: game_callback_filter = create( - lambda _, cbq: bool(cbq.game_short_name), "GameCallbackQuery" + lambda _, __, cbq: bool(cbq.game_short_name), "GameCallbackQuery" ) return CallbackQueryHandler(self.handle, filters=~game_callback_filter) @staticmethod - def check(update: Update, route: RouteDefinition): - return route.triggers.filters(update) if callable(route.triggers.filters) else True + def check(client: IClient, update: Update, route: RouteDefinition): + return route.triggers.filters(client, update) if callable(route.triggers.filters) else True async def handle(self, client: IClient, callback_query: CallbackQuery) -> Union[bool, Any]: cb_ctx: Optional[CallbackActionContext] = await self._get_context_or_respond( diff --git a/botkit/dispatching/dispatcher.py b/botkit/dispatching/dispatcher.py index 2b345d4..c495bc3 100644 --- a/botkit/dispatching/dispatcher.py +++ b/botkit/dispatching/dispatcher.py @@ -2,7 +2,7 @@ import logzero from pyrogram import Client -from pyrogram.client.handlers.handler import Handler +from pyrogram.handlers.handler import Handler from typing_extensions import Literal from botkit.dispatching.callbackqueries.callbackactiondispatcher import CallbackActionDispatcher @@ -62,7 +62,7 @@ async def add_module_routes(self, module: Module): async def add_route_for_update_type( self, module_or_group: Union[int, Module], - client: Client, + client: IClient, update_type: UpdateType, route_handler: RouteHandler, ): diff --git a/botkit/future_tgtypes/message.py b/botkit/future_tgtypes/message.py index 4a75ad5..3bbcdf4 100644 --- a/botkit/future_tgtypes/message.py +++ b/botkit/future_tgtypes/message.py @@ -1,6 +1,9 @@ from typing import * +from botkit.future_tgtypes.user import User + class Message(Protocol): text: Optional[str] message_id: int + from_user: Optional[User] diff --git a/botkit/future_tgtypes/persistence/textmessagemodel.py b/botkit/future_tgtypes/persistence/textmessagemodel.py index 550bbf1..4c305f2 100644 --- a/botkit/future_tgtypes/persistence/textmessagemodel.py +++ b/botkit/future_tgtypes/persistence/textmessagemodel.py @@ -1,5 +1,5 @@ from pydantic import BaseModel -from pyrogram import Message +from pyrogram.types import Message from typing import Optional @@ -69,15 +69,11 @@ def from_message(cls, msg: Message) -> "TextMessageModel": from_user_id=msg.from_user.id if msg.from_user else None, forward_from_id=msg.forward_from.id if msg.forward_from else None, forward_sender_name=msg.forward_sender_name, - forward_from_chat_id=msg.forward_from_chat.id - if msg.forward_from_chat - else None, + forward_from_chat_id=msg.forward_from_chat.id if msg.forward_from_chat else None, forward_from_message_id=msg.forward_from_message_id, forward_signature=msg.forward_signature, forward_date=msg.forward_date, - reply_to_message_id=msg.reply_to_message.message_id - if msg.reply_to_message - else None, + reply_to_message_id=msg.reply_to_message.message_id if msg.reply_to_message else None, mentioned=msg.mentioned, empty=msg.empty, service=msg.service, diff --git a/botkit/future_tgtypes/update_field_extractor.py b/botkit/future_tgtypes/update_field_extractor.py index 1dd012e..37bb74f 100644 --- a/botkit/future_tgtypes/update_field_extractor.py +++ b/botkit/future_tgtypes/update_field_extractor.py @@ -3,8 +3,8 @@ from dataclasses import dataclass from typing import * -import pyrogram -from pyrogram import Update +import pyrogram.types +from pyrogram.types import Update from botkit.future_tgtypes.chat import Chat from botkit.future_tgtypes.message import Message @@ -18,13 +18,13 @@ class UpdateFieldExtractor: # TODO: implement properly @property def chat(self) -> Optional[Chat]: - if isinstance(self.update, pyrogram.Message): + if isinstance(self.update, pyrogram.types.Message): return self.update.chat return None @property def user(self) -> Optional[Chat]: - if isinstance(self.update, pyrogram.Message): + if isinstance(self.update, pyrogram.types.Message): return self.update.from_user return None @@ -66,7 +66,7 @@ def command_args_str(self) -> Optional[str]: @property def replied_to_message(self) -> Optional[Message]: # TODO: turn into protocols - if isinstance(self.update, pyrogram.Message): + if isinstance(self.update, pyrogram.types.Message): return self.update.reply_to_message @property diff --git a/botkit/future_tgtypes/user.py b/botkit/future_tgtypes/user.py new file mode 100644 index 0000000..7581d20 --- /dev/null +++ b/botkit/future_tgtypes/user.py @@ -0,0 +1,5 @@ +from typing import Protocol + + +class User(Protocol): + pass diff --git a/botkit/inlinequeries/inlineresultcontainer.py b/botkit/inlinequeries/inlineresultcontainer.py index 354ba76..f91e483 100644 --- a/botkit/inlinequeries/inlineresultcontainer.py +++ b/botkit/inlinequeries/inlineresultcontainer.py @@ -1,6 +1,6 @@ from typing import List -from pyrogram import InlineQueryResult, InlineQuery +from pyrogram.types import InlineQuery, InlineQueryResult class InlineResultContainer: diff --git a/botkit/libraries/__init__.py b/botkit/libraries/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/botkit/libraries/_checks.py b/botkit/libraries/_checks.py new file mode 100644 index 0000000..b4cd972 --- /dev/null +++ b/botkit/libraries/_checks.py @@ -0,0 +1,8 @@ +import sys +from typing import Literal + +SupportedLibrary = Literal["pyrogram"] + + +def is_installed(library: SupportedLibrary) -> bool: + return library in sys.modules diff --git a/botkit/libraries/annotations.py b/botkit/libraries/annotations.py new file mode 100644 index 0000000..99db2a0 --- /dev/null +++ b/botkit/libraries/annotations.py @@ -0,0 +1,40 @@ +from typing import Any, Iterable, Optional, TYPE_CHECKING, TypeVar, Union +from typing import Awaitable, Callable, Union + +from botkit.persistence.callback_manager import CallbackActionContext + +from botkit.libraries._checks import is_installed +from botkit.views.botkit_context import BotkitContext + +if TYPE_CHECKING: + from botkit.types.client import IClient +else: + IClient = None + +if is_installed("pyrogram"): + from pyrogram import Client + from pyrogram.types import CallbackQuery, InlineQuery, Message, Poll, Update + + TClient = TypeVar("TClient", bound=Client) + TMessage = TypeVar("TMessage", bound=Message) + TCallbackQuery = TypeVar("TCallbackQuery", bound=CallbackQuery) + TInlineQuery = TypeVar("TInlineQuery", bound=InlineQuery) + TPoll = TypeVar("TPoll", bound=Poll) + TDeletedMessages = TypeVar("TDeletedMessages", bound=Iterable[Message]) + + ReturnType = Optional[Union["_ViewBase", Any]] + + TArg = Union[IClient, TClient, TMessage, TCallbackQuery, TInlineQuery, TPoll] + + HandlerSignature = Union[ + # Plain library handler signatures + Callable[[TArg, TArg], Awaitable[ReturnType]], + Callable[[TArg, TArg, TArg, TArg], Awaitable[ReturnType]], + # Library routes with states + Callable[[TArg, TArg, BotkitContext], Awaitable[ReturnType]], + Callable[[TArg, TArg, TArg, TArg, BotkitContext], Awaitable[ReturnType]], + ] + + +else: + raise ValueError("No supported Python bot library found in installed modules.") diff --git a/botkit/libraries/pyro_types/__init__.py b/botkit/libraries/pyro_types/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/botkit/routing/update_types/_pyrogram_update_type_inference.py b/botkit/libraries/pyro_types/_pyrogram_update_type_inference.py similarity index 63% rename from botkit/routing/update_types/_pyrogram_update_type_inference.py rename to botkit/libraries/pyro_types/_pyrogram_update_type_inference.py index 1c57874..efdde12 100644 --- a/botkit/routing/update_types/_pyrogram_update_type_inference.py +++ b/botkit/libraries/pyro_types/_pyrogram_update_type_inference.py @@ -8,21 +8,21 @@ from botkit.routing.update_types.updatetype import UpdateType from botkit.utils.typed_callable import TypedCallable -PYROGRAM_UPDATE_TYPES: Dict[Type[pyrogram.Update], UpdateType] = { - pyrogram.Message: UpdateType.message, - pyrogram.CallbackQuery: UpdateType.callback_query, - pyrogram.InlineQuery: UpdateType.inline_query, - pyrogram.Poll: UpdateType.poll, +PYROGRAM_UPDATE_TYPES: Dict[Type[pyrogram.types.Update], UpdateType] = { + pyrogram.types.Message: UpdateType.message, + pyrogram.types.CallbackQuery: UpdateType.callback_query, + pyrogram.types.InlineQuery: UpdateType.inline_query, + pyrogram.types.Poll: UpdateType.poll, # pyrogram.??? TODO: there is no one type to indicate user status } # noinspection PyUnresolvedReferences -PYROGRAM_HANDLER_TYPES: Dict[UpdateType, pyrogram.client.handlers.handler.Handler] = { - UpdateType.message: pyrogram.MessageHandler, - UpdateType.callback_query: pyrogram.CallbackQueryHandler, - UpdateType.inline_query: pyrogram.InlineQueryHandler, - UpdateType.poll: pyrogram.PollHandler, - UpdateType.user_status: pyrogram.UserStatusHandler, +PYROGRAM_HANDLER_TYPES: Dict[UpdateType, pyrogram.handlers.handler.Handler] = { + UpdateType.message: pyrogram.handlers.MessageHandler, + UpdateType.callback_query: pyrogram.handlers.CallbackQueryHandler, + UpdateType.inline_query: pyrogram.handlers.InlineQueryHandler, + UpdateType.poll: pyrogram.handlers.PollHandler, + UpdateType.user_status: pyrogram.handlers.UserStatusHandler, } @@ -49,12 +49,14 @@ def determine_pyrogram_handler_update_types(handler: TypedCallable) -> Set[Updat break # inner if not found_arg_types: - raise ValueError(f"No matching update type found for handler {handler} with signature {handler.type_hints}.") + raise ValueError( + f"No matching update type found for handler {handler} with signature {handler.type_hints}." + ) return found_arg_types -def _get_update_types(t: Any) -> List[Type[pyrogram.Update]]: +def _get_update_types(t: Any) -> List[Type[pyrogram.types.Update]]: if _is_pyrogram_update_type(t): return [t] # Direct subclass else: @@ -66,4 +68,4 @@ def _get_update_types(t: Any) -> List[Type[pyrogram.Update]]: def _is_pyrogram_update_type(t: Any) -> bool: - return inspect.isclass(t) and issubclass(t, pyrogram.Update) + return inspect.isclass(t) and issubclass(t, pyrogram.types.Update) diff --git a/botkit/routing/pipelines/callbacks.py b/botkit/routing/pipelines/callbacks.py deleted file mode 100644 index e4f996f..0000000 --- a/botkit/routing/pipelines/callbacks.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Awaitable, Callable, Union - -from botkit.persistence.callback_manager import CallbackActionContext -from botkit.routing.types import ReturnType, TArg, TState - -HandlerSignature = Union[ - # Plain library handler signatures - Callable[[TArg, TArg], Awaitable[ReturnType]], - Callable[[TArg, TArg, TArg, TArg], Awaitable[ReturnType]], - # Library routes with states - Callable[[TArg, TArg, CallbackActionContext], Awaitable[ReturnType]], - Callable[[TArg, TArg, TArg, TArg, CallbackActionContext], Awaitable[ReturnType]], -] diff --git a/botkit/routing/pipelines/execution_plan.py b/botkit/routing/pipelines/execution_plan.py index 6487988..7a7a1c3 100644 --- a/botkit/routing/pipelines/execution_plan.py +++ b/botkit/routing/pipelines/execution_plan.py @@ -7,10 +7,10 @@ from typing_extensions import Literal from botkit.core.components import Component +from botkit.libraries.annotations import HandlerSignature from botkit.routing.pipelines.gatherer import GathererSignature, GathererSignatureExamplesStr from botkit.routing.pipelines.reducer import ReducerSignature, ReducerSignatureExamplesStr from botkit.routing.route_builder.types import TView -from botkit.routing.pipelines.callbacks import HandlerSignature from botkit.routing.update_types.update_type_inference import infer_update_types from botkit.routing.update_types.updatetype import UpdateType from botkit.types.client import IClient diff --git a/botkit/routing/pipelines/factories/factory_types.py b/botkit/routing/pipelines/factories/factory_types.py index 60218b2..1eead8f 100644 --- a/botkit/routing/pipelines/factories/factory_types.py +++ b/botkit/routing/pipelines/factories/factory_types.py @@ -1,9 +1,7 @@ from abc import ABC, abstractmethod -from abc import ABC, abstractmethod from typing import Callable, Generic, List, Optional, Tuple, TypeVar from boltons.typeutils import classproperty -from pyrogram import Update from botkit.routing.update_types.updatetype import UpdateType from botkit.utils.typed_callable import TypedCallable diff --git a/botkit/routing/pipelines/factories/steps/custom_handler_step_factory.py b/botkit/routing/pipelines/factories/steps/custom_handler_step_factory.py index 9225602..27feec7 100644 --- a/botkit/routing/pipelines/factories/steps/custom_handler_step_factory.py +++ b/botkit/routing/pipelines/factories/steps/custom_handler_step_factory.py @@ -1,14 +1,12 @@ -import sys import warnings from typing import Any, Awaitable, Callable, Optional -from botkit.routing.pipelines.callbacks import HandlerSignature -from botkit.routing.pipelines.factories.factory_types import IStepFactory, TResult, TUserInput -from botkit.routing.pipelines.factories.steps._base import StepError, T +from botkit.libraries.annotations import HandlerSignature +from botkit.routing.pipelines.factories.factory_types import IStepFactory +from botkit.routing.pipelines.factories.steps._base import StepError from botkit.utils.typed_callable import TypedCallable from botkit.views.base import ModelViewBase from botkit.views.botkit_context import BotkitContext -from botkit.views.views import MessageViewBase class HandleStepError(StepError[HandlerSignature]): @@ -16,7 +14,9 @@ class HandleStepError(StepError[HandlerSignature]): class CustomHandlerStepFactory( - IStepFactory[TypedCallable[HandlerSignature], Optional[Callable[[BotkitContext], Awaitable[Any]]]] + IStepFactory[ + TypedCallable[HandlerSignature], Optional[Callable[[BotkitContext], Awaitable[Any]]] + ] ): @classmethod def create_step(cls, handler): @@ -29,7 +29,11 @@ def create_step(cls, handler): async def handle_async(update, context): # TODO: Use paraminjector library to make all args optional - args = (context.client, update, context) if handler.num_parameters == 3 else (context.client, update) + args = ( + (context.client, update, context) + if handler.num_parameters == 3 + else (context.client, update) + ) try: result = await handler.func(*args) @@ -51,7 +55,11 @@ async def handle_async(update, context): def handle(update, context): # TODO: Use paraminjector library to make all args optional - args = (context.client, update, context) if handler.num_parameters == 3 else (context.client, update) + args = ( + (context.client, update, context) + if handler.num_parameters == 3 + else (context.client, update) + ) try: result = handler.func(*args) diff --git a/botkit/routing/pipelines/factories/steps/gather_step_factory.py b/botkit/routing/pipelines/factories/steps/gather_step_factory.py index 77259b6..2ec2d05 100644 --- a/botkit/routing/pipelines/factories/steps/gather_step_factory.py +++ b/botkit/routing/pipelines/factories/steps/gather_step_factory.py @@ -62,6 +62,6 @@ def gather_initial_state(context: BotkitContext): return result except Exception as e: - raise GatherStepError(gatherer) from e + raise GatherStepError(e) return gather_initial_state, is_coroutine diff --git a/botkit/routing/pipelines/factories/steps/render_view_step_factory.py b/botkit/routing/pipelines/factories/steps/render_view_step_factory.py index 4c26c22..24b894c 100644 --- a/botkit/routing/pipelines/factories/steps/render_view_step_factory.py +++ b/botkit/routing/pipelines/factories/steps/render_view_step_factory.py @@ -1,18 +1,13 @@ import inspect import warnings -from timeit import timeit -from typing import Any, Awaitable, Callable, List, Optional, Union, cast +from typing import Callable, List, Optional, cast -from pyrogram import Update -from pyrogram.errors import MessageIdInvalid - -from botkit.routing.pipelines.execution_plan import SendTarget, SendTo, ViewParameters +from botkit.routing.pipelines.execution_plan import ViewParameters from botkit.routing.pipelines.factories.factory_types import IStepFactory from botkit.routing.update_types.updatetype import UpdateType -from botkit.services.companionbotservice import CompanionBotService -from botkit.views.base import RenderedMessageBase from botkit.views.botkit_context import BotkitContext from botkit.views.functional_views import quacks_like_view_render_func, render_functional_view +from botkit.views.rendered_messages import RenderedMessageBase from botkit.views.views import MessageViewBase diff --git a/botkit/routing/pipelines/factories/steps/send_view_step_factory.py b/botkit/routing/pipelines/factories/steps/send_view_step_factory.py index 56de133..77ad96c 100644 --- a/botkit/routing/pipelines/factories/steps/send_view_step_factory.py +++ b/botkit/routing/pipelines/factories/steps/send_view_step_factory.py @@ -1,21 +1,13 @@ -import inspect -import warnings from collections import namedtuple -from dataclasses import dataclass -from timeit import timeit -from typing import Any, Awaitable, Callable, List, Optional, Tuple, TypedDict, Union, cast +from typing import Any, Awaitable, Callable, List, Optional -from pyrogram import Update from pyrogram.errors import MessageIdInvalid from botkit.routing.pipelines.execution_plan import SendTarget, SendTo, ViewParameters from botkit.routing.pipelines.factories.factory_types import IStepFactory from botkit.routing.update_types.updatetype import UpdateType from botkit.services.companionbotservice import CompanionBotService -from botkit.views.base import RenderedMessageBase from botkit.views.botkit_context import BotkitContext -from botkit.views.functional_views import quacks_like_view_render_func, render_functional_view -from botkit.views.views import MessageViewBase _EvaluatedSendTarget = namedtuple("_EvaluatedSendTarget", ["peer_id", "reply_to_msg_id"]) diff --git a/botkit/routing/pipelines/factories/updates/callback_query_pipeline_factory.py b/botkit/routing/pipelines/factories/updates/callback_query_pipeline_factory.py index 865cf5f..4e70c67 100644 --- a/botkit/routing/pipelines/factories/updates/callback_query_pipeline_factory.py +++ b/botkit/routing/pipelines/factories/updates/callback_query_pipeline_factory.py @@ -1,9 +1,11 @@ from typing import Any, Union -from pyrogram import CallbackQuery +from pyrogram.types import CallbackQuery -from botkit.routing.pipelines.callbacks import HandlerSignature -from botkit.routing.pipelines.factories.base import UpdatePipelineFactory +from botkit.libraries.annotations import HandlerSignature +from botkit.routing.pipelines.factories.updates.update_pipeline_factory import ( + UpdatePipelineFactory, +) from botkit.routing.pipelines.factories.steps.custom_handler_step_factory import ( CustomHandlerStepFactory, ) diff --git a/botkit/routing/pipelines/factories/updates/message_pipeline_factory.py b/botkit/routing/pipelines/factories/updates/message_pipeline_factory.py index eec0a8e..b93b17a 100644 --- a/botkit/routing/pipelines/factories/updates/message_pipeline_factory.py +++ b/botkit/routing/pipelines/factories/updates/message_pipeline_factory.py @@ -1,7 +1,9 @@ -from pyrogram import Message +from pyrogram.types import Message -from botkit.routing.pipelines.callbacks import HandlerSignature -from botkit.routing.pipelines.factories.base import UpdatePipelineFactory +from botkit.libraries.annotations import HandlerSignature +from botkit.routing.pipelines.factories.updates.update_pipeline_factory import ( + UpdatePipelineFactory, +) from botkit.routing.pipelines.factories.steps.custom_handler_step_factory import ( CustomHandlerStepFactory, ) diff --git a/botkit/routing/pipelines/factories/updates/others.py b/botkit/routing/pipelines/factories/updates/others.py index d16ab91..419c167 100644 --- a/botkit/routing/pipelines/factories/updates/others.py +++ b/botkit/routing/pipelines/factories/updates/others.py @@ -1,15 +1,8 @@ from typing import Dict, Type -from pyrogram import CallbackQuery - -from botkit.routing.pipelines.factories.base import UpdatePipelineFactory -from botkit.routing.pipelines.factories.steps.custom_handler_step_factory import ( - CustomHandlerStepFactory, -) -from botkit.routing.pipelines.factories.steps.gather_step_factory import GatherStepFactory -from botkit.routing.pipelines.factories.steps.reduce_step_factory import ReduceStepFactory -from botkit.routing.pipelines.factories.steps.send_view_step_factory import ( - CommitRenderedViewStepFactory, +from botkit.libraries.annotations import HandlerSignature +from botkit.routing.pipelines.factories.updates.update_pipeline_factory import ( + UpdatePipelineFactory, ) from botkit.routing.pipelines.factories.updates.callback_query_pipeline_factory import ( CallbackQueryPipelineFactory, @@ -17,9 +10,7 @@ from botkit.routing.pipelines.factories.updates.message_pipeline_factory import ( MessagePipelineFactory, ) -from botkit.routing.pipelines.callbacks import HandlerSignature from botkit.routing.update_types.updatetype import UpdateType -from botkit.types.client import IClient class InlineQueryPipelineFactory(UpdatePipelineFactory): diff --git a/botkit/routing/pipelines/factories/base.py b/botkit/routing/pipelines/factories/updates/update_pipeline_factory.py similarity index 78% rename from botkit/routing/pipelines/factories/base.py rename to botkit/routing/pipelines/factories/updates/update_pipeline_factory.py index af7aa57..81395f0 100644 --- a/botkit/routing/pipelines/factories/base.py +++ b/botkit/routing/pipelines/factories/updates/update_pipeline_factory.py @@ -1,15 +1,17 @@ import inspect +import traceback from abc import ABC, abstractmethod -from typing import cast +from typing import Awaitable, cast -from pyrogram import Update +from pyrogram.types import Update from unsync import Unfuture, unsync -from botkit.routing.pipelines.callbacks import HandlerSignature +from botkit.libraries.annotations import HandlerSignature from botkit.routing.pipelines.execution_plan import ExecutionPlan from botkit.routing.pipelines.filters import UpdateFilterSignature from botkit.routing.triggers import RouteTriggers from botkit.routing.update_types.updatetype import UpdateType +from botkit.types.client import IClient class UpdatePipelineFactory(ABC): @@ -78,30 +80,30 @@ def get_description(self) -> str: return "".join(parts) def create_update_filter(self) -> UpdateFilterSignature: - is_awaitable = None + return self.triggers.filters cond = self.triggers.condition filters = self.triggers.filters - if cond: - if inspect.isawaitable(cond): - is_awaitable = True - else: - is_awaitable = False + cond_is_awaitable = cond and inspect.isawaitable(cond) - def check(update: Update) -> bool: + async def check(client: IClient, update: Update) -> bool: if cond is not None: - @unsync - async def check_cond(): - return await cond() - - res = cast(Unfuture, check_cond()) - if not res.result: - return False + if cond_is_awaitable: + if not await cond(): + return False if filters: - return filters(update) + try: + return await filters(client, update) + except: + print(filters) + print(type(filters)) + traceback.print_exc() return False + # TODO: Hotfix for weird PR in pyro + check.__call__ = check + return check diff --git a/botkit/routing/pipelines/filters.py b/botkit/routing/pipelines/filters.py index 219d69b..cc9eaf5 100644 --- a/botkit/routing/pipelines/filters.py +++ b/botkit/routing/pipelines/filters.py @@ -1,5 +1,7 @@ -from typing import Callable +from typing import Awaitable, Callable -from pyrogram import Update +from pyrogram.types import Update -UpdateFilterSignature = Callable[[Update], bool] +from botkit.libraries.annotations import IClient + +UpdateFilterSignature = Callable[[IClient, Update], Awaitable[bool]] diff --git a/botkit/routing/route.py b/botkit/routing/route.py index 2772d20..41f8910 100644 --- a/botkit/routing/route.py +++ b/botkit/routing/route.py @@ -3,7 +3,8 @@ from typing import Dict, List, Literal, Optional, TypeVar, cast from cached_property import cached_property -from pyrogram import ( +from pyrogram.filters import Filter +from pyrogram.handlers import ( CallbackQueryHandler, InlineQueryHandler, MessageHandler, @@ -11,14 +12,13 @@ RawUpdateHandler, UserStatusHandler, ) -from pyrogram.client.filters.filter import Filter -from pyrogram.client.handlers.handler import Handler +from pyrogram.handlers.handler import Handler +from botkit.libraries.annotations import HandlerSignature from botkit.routing.pipelines.execution_plan import ExecutionPlan from botkit.routing.pipelines.factories.updates.others import PIPELINE_FACTORIES from botkit.routing.pipelines.filters import UpdateFilterSignature from botkit.routing.triggers import ActionIdTypes, RouteTriggers -from botkit.routing.pipelines.callbacks import HandlerSignature from botkit.routing.update_types.updatetype import UpdateType M = TypeVar("M") diff --git a/botkit/routing/route_builder/builder.py b/botkit/routing/route_builder/builder.py index cc7623b..9c60fea 100644 --- a/botkit/routing/route_builder/builder.py +++ b/botkit/routing/route_builder/builder.py @@ -16,12 +16,11 @@ ) from uuid import UUID, uuid4 -from pyrogram import CallbackQuery -from pyrogram.client.filters.filter import Filter -from pyrogram.client.filters.filters import create -from pyrogram.client.handlers.handler import Handler +from pyrogram.filters import Filter, create +from pyrogram.handlers.handler import Handler +from pyrogram.types import CallbackQuery -from botkit.routing.pipelines.callbacks import HandlerSignature +from botkit.libraries.annotations import HandlerSignature from botkit.routing.pipelines.execution_plan import ExecutionPlan, SendTarget, SendTo from botkit.routing.pipelines.gatherer import GathererSignature from botkit.routing.pipelines.reducer import ReducerSignature @@ -176,7 +175,7 @@ def __init__(self, routes: RouteCollection, game_short_name: str): self._route_collection = routes self._triggers = RouteTriggers( filters=create( - lambda _, cbq: cbq.game_short_name == game_short_name, "PlayGameFilter" + lambda _, __, cbq: cbq.game_short_name == game_short_name, "PlayGameFilter" ), action=None, condition=None, @@ -241,7 +240,7 @@ def gather(self, state_generator: GathererSignature): @dataclass class RouteBuilderContext: - load_result: Any = None + load_result: Optional[Any] = None class RouteBuilder: diff --git a/botkit/routing/route_builder/types.py b/botkit/routing/route_builder/types.py index b4ad5d1..92aab4b 100644 --- a/botkit/routing/route_builder/types.py +++ b/botkit/routing/route_builder/types.py @@ -1,5 +1,6 @@ from typing import Protocol, TYPE_CHECKING, Type, TypeVar, Union +from botkit.libraries.annotations import HandlerSignature from botkit.routing.triggers import RouteTriggers from botkit.views.base import InlineResultViewBase from botkit.views.views import MessageViewBase @@ -9,7 +10,6 @@ else: RouteExpression = None -from botkit.routing.pipelines.callbacks import HandlerSignature if TYPE_CHECKING: from botkit.routing.route_builder.route_collection import RouteCollection diff --git a/botkit/routing/triggers.py b/botkit/routing/triggers.py index 81412e2..2f88b83 100644 --- a/botkit/routing/triggers.py +++ b/botkit/routing/triggers.py @@ -1,8 +1,11 @@ from dataclasses import dataclass -from pyrogram.client.filters.filter import Filter from typing import Optional, Callable, Union, Awaitable +from pyrogram.filters import Filter + +from botkit.routing.pipelines.filters import UpdateFilterSignature + ActionIdTypes = Union[int, str] diff --git a/botkit/routing/types.py b/botkit/routing/types.py index a646ede..e3a3846 100644 --- a/botkit/routing/types.py +++ b/botkit/routing/types.py @@ -1,18 +1,3 @@ -from pyrogram import Client, Message, CallbackQuery, InlineQuery, Poll -from typing import Any, Optional, TypeVar, Union, Iterable +from typing import TypeVar TState = TypeVar("TState") - -TClient = TypeVar("TClient", bound=Client) -TMessage = TypeVar("TMessage", bound=Message) -TCallbackQuery = TypeVar("TCallbackQuery", bound=CallbackQuery) -TInlineQuery = TypeVar("TInlineQuery", bound=InlineQuery) -TPoll = TypeVar("TPoll", bound=Poll) -TDeletedMessages = TypeVar("TDeletedMessages", bound=Iterable[Message]) - -ReturnType = Optional[Union["_ViewBase", Any]] - -TArg = Union[TClient, TMessage, TCallbackQuery, TInlineQuery, TPoll] - - - diff --git a/botkit/routing/update_types/update_type_inference.py b/botkit/routing/update_types/update_type_inference.py index 4c1a8e2..9052a85 100644 --- a/botkit/routing/update_types/update_type_inference.py +++ b/botkit/routing/update_types/update_type_inference.py @@ -1,6 +1,8 @@ from typing import Set -from botkit.routing.update_types._pyrogram_update_type_inference import determine_pyrogram_handler_update_types +from botkit.libraries.pyro_types._pyrogram_update_type_inference import ( + determine_pyrogram_handler_update_types, +) from botkit.routing.update_types.updatetype import UpdateType from botkit.utils.typed_callable import TypedCallable diff --git a/botkit/services/companionbotservice.py b/botkit/services/companionbotservice.py index f2c88b5..070cd5a 100644 --- a/botkit/services/companionbotservice.py +++ b/botkit/services/companionbotservice.py @@ -3,37 +3,36 @@ from contextlib import asynccontextmanager from uuid import uuid4 -from haps import INSTANCE_SCOPE, inject +from haps import INSTANCE_SCOPE, SINGLETON_SCOPE, base, inject, scope from logzero import logger as log -from pyrogram import ( +from pyrogram.filters import create +from pyrogram.handlers import InlineQueryHandler +from pyrogram.handlers.handler import Handler +from pyrogram.raw.base.messages import BotResults +from pyrogram.types import ( InlineQuery, InlineQueryResultArticle, InputTextMessageContent, - InlineQueryHandler, Message, InlineQueryResultPhoto, - MessageHandler, - Filters, Photo, ) -from pyrogram.api.types.messages import BotResults -from pyrogram.client.filters.filters import create -from pyrogram.client.handlers.handler import Handler from typing import Union, Optional, AsyncIterator from botkit.botkit_services.services import service from botkit.inlinequeries.inlineresultgenerator import InlineResultGenerator from botkit.types.client import IClient -from botkit.views.base import ( +from botkit.views.base import InlineResultViewBase +from botkit.views.rendered_messages import ( RenderedMediaMessage, RenderedMessage, - InlineResultViewBase, RenderedMessageBase, RenderedTextMessage, ) -@service(scope=INSTANCE_SCOPE) +@base +@scope(SINGLETON_SCOPE) class CompanionBotService: def __init__(self, user_client: IClient, bot_client: IClient): self.user_client = user_client @@ -71,7 +70,7 @@ async def answer_inline_query(client: IClient, query: InlineQuery): await self.bot_client.answer_inline_query(query.id, results=[result], cache_time=0) - inline_id_filter = create(lambda _, ilq: ilq.query == query_text, "QueryFilter") + inline_id_filter = create(lambda _, __, ilq: ilq.query == query_text, "QueryFilter") group = -99 dispatcher = self.bot_client.dispatcher @@ -150,7 +149,7 @@ async def answer_inline_query(client: IClient, query: InlineQuery): # noinspection PyTypeChecker await self.bot_client.answer_inline_query(query.id, results=[result], cache_time=1) - inline_id_filter = create(lambda _, ilq: ilq.query == query_text, "QueryFilter") + inline_id_filter = create(lambda _, __, ilq: ilq.query == query_text, "QueryFilter") handler = InlineQueryHandler(answer_inline_query, inline_id_filter) @@ -196,7 +195,7 @@ async def send_view_via( ) -> Message: rendered: RenderedMessage = view.render() return await self.send_rendered_message( - chat_id=chat_id, view=view, reply_to=reply_to, silent=silent, hide_via=hide_via + chat_id=chat_id, rendered=rendered, reply_to=reply_to, silent=silent, hide_via=hide_via ) @asynccontextmanager @@ -224,9 +223,9 @@ async def record_message(_, message: Message): async with self.add_handler( MessageHandler( record_message, - filters=Filters.media - & Filters.chat(user_message.from_user.id) - & Filters.forwarded, + filters=filters.media + & filters.chat(user_message.from_user.id) + & filters.forwarded, ) ): await self.user_client.forward_messages( @@ -249,7 +248,7 @@ async def record_message(_, message: Message): await message.delete() async with self.add_handler( - MessageHandler(record_message, filters=Filters.photo & Filters.chat(user_id)) + MessageHandler(record_message, filters=filters.photo & filters.chat(user_id)) ): await self.user_client.send_photo(bot_id, photo=photo) await recorded_msg.wait() diff --git a/botkit/services/historyservice.py b/botkit/services/historyservice.py index 2655efe..b15bdd1 100644 --- a/botkit/services/historyservice.py +++ b/botkit/services/historyservice.py @@ -1,9 +1,11 @@ from collections import Counter from haps import SINGLETON_SCOPE, egg, base, scope -from pyrogram import Message, Client +from pyrogram import Client from typing import Union, AsyncGenerator, cast +from pyrogram.types import Message + @base @egg @@ -13,15 +15,10 @@ def __init__(self, client: Client): self.client = client async def iter_replies_to( - self, - chat_id: Union[int, str], - target_message: Union[Message, int], - vicinity: int = 500, + self, chat_id: Union[int, str], target_message: Union[Message, int], vicinity: int = 500, ) -> AsyncGenerator[Message, None]: target_message_id: int = ( - target_message - if isinstance(target_message, int) - else target_message.message_id + target_message if isinstance(target_message, int) else target_message.message_id ) history_gen = self.client.iter_history( chat_id, limit=vicinity, offset_id=target_message_id, reverse=True @@ -31,20 +28,14 @@ async def iter_replies_to( async for m in cast(AsyncGenerator[Message, None], history_gen): if m.empty: continue - if ( - m.reply_to_message - and m.reply_to_message.message_id == target_message_id - ): + if m.reply_to_message and m.reply_to_message.message_id == target_message_id: yield m - async def get_reply_counts( - self, chat_id: Union[int, str], lookback: int = 3000 - ) -> Counter: + async def get_reply_counts(self, chat_id: Union[int, str], lookback: int = 3000) -> Counter: counter: Counter = Counter() async for m in cast( - AsyncGenerator[Message, None], - self.client.iter_history(chat_id, limit=lookback), + AsyncGenerator[Message, None], self.client.iter_history(chat_id, limit=lookback), ): reply_msg = m.reply_to_message if reply_msg: diff --git a/botkit/tghelpers/direct_links.py b/botkit/tghelpers/direct_links.py index 5eafd6e..5430ee7 100644 --- a/botkit/tghelpers/direct_links.py +++ b/botkit/tghelpers/direct_links.py @@ -1,7 +1,8 @@ from enum import IntEnum -from pyrogram import Message, User, Chat, Client -from pyrogram.api.types import Channel +from pyrogram import Client +from pyrogram.raw.types import Channel +from pyrogram.types import Message, User, Chat from typing import Optional, Union, cast, Dict _links_cache: Dict[int, str] = {} @@ -49,5 +50,5 @@ async def direct_link( return f"https://web.Telegram.org/#/im?p=u{peer.id}" else: raise ValueError( - f"_IdentifiableUser has no username, creating direct link on platform {platform} not possible." + f"User has no username, creating direct link on platform {platform} not possible." ) diff --git a/botkit/tghelpers/entities/message_entities.py b/botkit/tghelpers/entities/message_entities.py index 59e1c5a..0eff2bb 100644 --- a/botkit/tghelpers/entities/message_entities.py +++ b/botkit/tghelpers/entities/message_entities.py @@ -1,10 +1,9 @@ from dataclasses import dataclass +from typing import List, Union import sys -from pyrogram import MessageEntity, Message -from pyrogram.client.filters.filter import Filter -from pyrogram.client.filters.filters import create -from typing import List, Union +from pyrogram.filters import Filter, create +from pyrogram.types import Message, MessageEntity from typing_extensions import Literal MessageEntityType = Literal[ @@ -33,9 +32,7 @@ def parse_entity(entity: MessageEntity, message_text: str) -> str: return message_text[entity.offset : entity.offset + entity.length] else: entity_text = message_text.encode("utf-16-le") - entity_text = entity_text[ - entity.offset * 2 : (entity.offset + entity.length) * 2 - ] + entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2] return entity_text.decode("utf-16-le") @@ -63,8 +60,7 @@ def parse_entities( def create_entity_filter(type_: MessageEntityType) -> Filter: return create( - lambda _, m: any(parse_entities(m, type_)) if m.entities else False, - type_.upper(), + lambda _, __, m: any(parse_entities(m, type_)) if m.entities else False, type_.upper(), ) diff --git a/botkit/tghelpers/names.py b/botkit/tghelpers/names.py index 587854e..348d4f7 100644 --- a/botkit/tghelpers/names.py +++ b/botkit/tghelpers/names.py @@ -1,17 +1,17 @@ from typing import * -def displayname(entity: Any) -> str: +def display_name(entity: Any) -> str: if hasattr(entity, "title"): return entity.title elif hasattr(entity, "first_name"): if entity.last_name: return f"{entity.first_name} {entity.last_name}" return entity.first_name - return ValueError("This entity does not seem to have a display name.") + raise ValueError("This entity does not seem to have a display name.") -def user_or_displayname(entity: Any) -> str: +def user_or_display_name(entity: Any) -> str: if entity.username: return f"@{entity.username}" - return displayname(entity) + return display_name(entity) diff --git a/botkit/utils/datetime_utils.py b/botkit/utils/datetime_utils.py index 29dda65..6529b1b 100644 --- a/botkit/utils/datetime_utils.py +++ b/botkit/utils/datetime_utils.py @@ -3,7 +3,7 @@ import pytz -from pyrogram import Message +from pyrogram.types import Message def get_message_date(message: Message, including_edits: bool = True) -> datetime: diff --git a/botkit/utils/typed_callable.py b/botkit/utils/typed_callable.py index 3ee77d6..2af71fb 100644 --- a/botkit/utils/typed_callable.py +++ b/botkit/utils/typed_callable.py @@ -3,7 +3,17 @@ from dataclasses import dataclass from cached_property import cached_property -from typing import Any, Awaitable, Callable, Dict, Generic, Protocol, TypeVar, get_type_hints +from typing import ( + Any, + Awaitable, + Callable, + Dict, + Generic, + Protocol, + TypeVar, + Union, + get_type_hints, +) T = TypeVar("T", bound=Callable) diff --git a/botkit/views/base.py b/botkit/views/base.py index 1c3291d..e15d9f3 100644 --- a/botkit/views/base.py +++ b/botkit/views/base.py @@ -1,26 +1,17 @@ -from abc import ABC, abstractmethod -from dataclasses import dataclass -from enum import Enum, IntEnum, auto -from itertools import takewhile +from abc import ABC from typing import ( - Any, Generic, - List, - Literal, - Optional, TYPE_CHECKING, - Tuple, - TypeVar, Union, overload, ) -from more_itertools import take -from pyrogram import ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardRemove -from pyrogram.api.types import ReplyKeyboardMarkup +from pyrogram.types import ForceReply, ReplyKeyboardMarkup, ReplyKeyboardRemove from botkit.builders.inlinemenubuilder import InlineMenuBuilder from botkit.builders.metabuilder import MetaBuilder +from botkit.views.rendered_messages import RenderedMessage +from botkit.views.types import TState if TYPE_CHECKING: from botkit.routing.route_builder.builder import RouteBuilder as _RouteBuilder @@ -34,66 +25,6 @@ def register(cls, routes: _RouteBuilder): pass -KeyboardTypes = Union[InlineKeyboardButton, Tuple] - - -class RenderedMessageBase: - pass - - -@dataclass -class RenderedMessageMarkup(RenderedMessageBase): - reply_markup: Union[ReplyKeyboardMarkup, ForceReply, ReplyKeyboardRemove] = None - inline_buttons: Optional[List[List[KeyboardTypes]]] = None - - @property - def inline_keyboard_markup(self) -> Optional[InlineKeyboardMarkup]: - if self.inline_buttons is None: - return None - rows = [list(x) for x in self.inline_buttons] - return InlineKeyboardMarkup(rows) - - -@dataclass -class RenderedMessage(RenderedMessageMarkup): - title: Optional[str] = None - description: Optional[str] = None - - parse_mode: str = "html" - disable_web_page_preview: bool = True - - thumb_url: str = None # TODO: implement - - -@dataclass -class RenderedStickerMessage(RenderedMessage): - sticker: Optional[str] = None - - -@dataclass -class RenderedMediaMessage(RenderedMessage): - media: Optional[Any] = None - caption: Optional[str] = None - - -@dataclass -class RenderedTextMessage(RenderedMessage): - text: Optional[str] = None - - -@dataclass -class RenderedPollMessage(RenderedMessageMarkup): - question: str = None - options: List[str] = None - is_anonymous: bool = True - allows_multiple_answers: bool = None - type: Literal["regular", "quiz"] = None - correct_option_id: int = None - - -TState = TypeVar("TState") - - class ModelViewBase(Generic[TState], ABC): def __init__(self, state: TState): self.state = state diff --git a/botkit/views/botkit_context.py b/botkit/views/botkit_context.py index f2d346d..87419d9 100644 --- a/botkit/views/botkit_context.py +++ b/botkit/views/botkit_context.py @@ -1,11 +1,11 @@ from dataclasses import dataclass -from typing import Any, DefaultDict, Dict, Generic, Optional, TypeVar, Union +from typing import Any, Generic, Optional, TypeVar, Union from botkit.dispatching.callbackqueries.types import CallbackActionType from botkit.future_tgtypes.update_field_extractor import UpdateFieldExtractor -from botkit.routing.types import TState -from botkit.views.base import RenderedMessage -from botkit.views.sender_interface import IViewSender +from .rendered_messages import RenderedMessage +from .sender_interface import IViewSender +from ..routing.types import TState TPayload = TypeVar("TPayload") diff --git a/botkit/views/functional_views.py b/botkit/views/functional_views.py index 8e93d76..2734bd4 100644 --- a/botkit/views/functional_views.py +++ b/botkit/views/functional_views.py @@ -9,7 +9,7 @@ from decorators import FuncDecorator from botkit.builders import ViewBuilder -from botkit.views.base import RenderedMessage +from botkit.views.rendered_messages import RenderedMessage T = TypeVar("T") diff --git a/botkit/views/rendered_messages.py b/botkit/views/rendered_messages.py new file mode 100644 index 0000000..aba6841 --- /dev/null +++ b/botkit/views/rendered_messages.py @@ -0,0 +1,65 @@ +from dataclasses import dataclass +from typing import Any, List, Literal, Optional, Union + +from pyrogram.types import ( + ForceReply, + InlineKeyboardMarkup, + ReplyKeyboardMarkup, + ReplyKeyboardRemove, +) + +from botkit.views.types import KeyboardTypes + + +class RenderedMessageBase: + pass + + +@dataclass +class RenderedMessageMarkup(RenderedMessageBase): + reply_markup: Union[ReplyKeyboardMarkup, ForceReply, ReplyKeyboardRemove] = None + inline_buttons: Optional[List[List[KeyboardTypes]]] = None + + @property + def inline_keyboard_markup(self) -> Optional[InlineKeyboardMarkup]: + if self.inline_buttons is None: + return None + rows = [list(x) for x in self.inline_buttons] + return InlineKeyboardMarkup(rows) + + +@dataclass +class RenderedMessage(RenderedMessageMarkup): + title: Optional[str] = None + description: Optional[str] = None + + parse_mode: str = "html" + disable_web_page_preview: bool = True + + thumb_url: str = None # TODO: implement + + +@dataclass +class RenderedStickerMessage(RenderedMessage): + sticker: Optional[str] = None + + +@dataclass +class RenderedMediaMessage(RenderedMessage): + media: Optional[Any] = None + caption: Optional[str] = None + + +@dataclass +class RenderedTextMessage(RenderedMessage): + text: Optional[str] = None + + +@dataclass +class RenderedPollMessage(RenderedMessageMarkup): + question: str = None + options: List[str] = None + is_anonymous: bool = True + allows_multiple_answers: bool = None + type: Literal["regular", "quiz"] = None + correct_option_id: int = None diff --git a/botkit/views/renderer_client_mixin.py b/botkit/views/renderer_client_mixin.py index 6ab7e18..e6c11bc 100644 --- a/botkit/views/renderer_client_mixin.py +++ b/botkit/views/renderer_client_mixin.py @@ -4,20 +4,21 @@ try: # TODO: Turn this into a contextmanager, `with lib_check('Pyrogram'): import ...` # noinspection PyPackageRequirements - from pyrogram import Client, Message, User + from pyrogram import Client + from pyrogram.types import Message, User except ImportError as e: raise ImportError( "The Pyrogram library does not seem to be installed, so using Botkit in Pyrogram flavor is not possible. " ) from e from botkit.types.client import IClient -from botkit.views.base import ( - InlineResultViewBase, +from botkit.views.base import InlineResultViewBase +from botkit.views.types import TState +from botkit.views.rendered_messages import ( RenderedMediaMessage, RenderedMessageBase, RenderedStickerMessage, RenderedTextMessage, - TState, ) from botkit.views.functional_views import ViewRenderFuncSignature from botkit.views.views import MessageViewBase diff --git a/botkit/views/sender_interface.py b/botkit/views/sender_interface.py index b52edef..706de1b 100644 --- a/botkit/views/sender_interface.py +++ b/botkit/views/sender_interface.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod from typing import Generic, Optional, TypeVar, Union, overload -from botkit.views.base import RenderedMessageBase +from botkit.views.rendered_messages import RenderedMessageBase from botkit.views.views import MediaView, MessageViewBase, StickerView, TextView Message = TypeVar("Message") diff --git a/botkit/views/success_view.py b/botkit/views/success_view.py index b2bce0e..61e0abd 100644 --- a/botkit/views/success_view.py +++ b/botkit/views/success_view.py @@ -1,4 +1,4 @@ -from pyrogram import Emoji +from pyrogram import emoji from botkit.builders.htmlbuilder import HtmlBuilder from botkit.views.views import TextView @@ -6,4 +6,4 @@ class SuccessView(TextView[str]): def render_body(self, builder: HtmlBuilder) -> None: - builder.text(Emoji.WHITE_HEAVY_CHECK_MARK).spc().text(self.state) + builder.text(emoji.CHECK_MARK).spc().text(self.state) diff --git a/botkit/views/types.py b/botkit/views/types.py new file mode 100644 index 0000000..78bf9d3 --- /dev/null +++ b/botkit/views/types.py @@ -0,0 +1,6 @@ +from typing import Tuple, TypeVar, Union + +from pyrogram.types import InlineKeyboardButton + +KeyboardTypes = Union[InlineKeyboardButton, Tuple] +TState = TypeVar("TState") diff --git a/botkit/views/views.py b/botkit/views/views.py index 0dc8b32..55e065c 100644 --- a/botkit/views/views.py +++ b/botkit/views/views.py @@ -10,11 +10,13 @@ InlineResultViewBase, ModelViewBase, RenderMarkupBase, +) +from botkit.views.types import TState +from botkit.views.rendered_messages import ( RenderedMessage, RenderedMessageMarkup, RenderedPollMessage, RenderedTextMessage, - TState, ) diff --git a/poetry.lock b/poetry.lock index aa4fd97..3ffb48f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -20,22 +20,6 @@ six = "*" [package.extras] test = ["astroid", "pytest"] -[[package]] -category = "main" -description = "Async generators and context managers for Python 3.5+" -name = "async-generator" -optional = false -python-versions = ">=3.5" -version = "1.10" - -[[package]] -category = "main" -description = "Simple lru_cache for asyncio" -name = "async-lru" -optional = false -python-versions = "*" -version = "1.0.1" - [[package]] category = "dev" description = "Atomic file writes." @@ -51,13 +35,12 @@ description = "Classes Without Boilerplate" name = "attrs" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.3.0" +version = "20.1.0" [package.extras] -azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] -dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] -docs = ["sphinx", "zope.interface"] -tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] [[package]] category = "dev" @@ -65,7 +48,7 @@ description = "Graphical Python debugger which lets you easily view the values o name = "birdseye" optional = false python-versions = "*" -version = "0.8.3" +version = "0.8.4" [package.dependencies] Flask = "*" @@ -78,6 +61,9 @@ littleutils = ">=0.2" outdated = "*" sqlalchemy = "*" +[package.extras] +tests = ["bs4", "selenium", "requests", "pytest", "numpy", "pandas"] + [[package]] category = "dev" description = "The uncompromising code formatter." @@ -104,7 +90,7 @@ description = "When they're not builtins, they're boltons." name = "boltons" optional = false python-versions = "*" -version = "20.2.0" +version = "20.2.1" [[package]] category = "main" @@ -144,7 +130,10 @@ description = "Better version of repr/reprlib for short, cheap string representa name = "cheap-repr" optional = false python-versions = "*" -version = "0.4.1" +version = "0.4.2" + +[package.extras] +tests = ["django", "numpy (>=1.16.3)", "pandas (>=0.24.2)", "pytest"] [[package]] category = "dev" @@ -224,7 +213,7 @@ description = "Python humanize utilities" name = "humanize" optional = false python-versions = ">=3.5" -version = "2.5.0" +version = "2.6.0" [package.extras] tests = ["freezegun", "pytest", "pytest-cov"] @@ -237,6 +226,14 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "2.10" +[[package]] +category = "dev" +description = "iniconfig: brain-dead simple config-ini parsing" +name = "iniconfig" +optional = false +python-versions = "*" +version = "1.0.1" + [[package]] category = "dev" description = "Various helpers to pass data to untrusted environments and back." @@ -391,14 +388,6 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "1.9.0" -[[package]] -category = "main" -description = "Pure-Python Implementation of the AES block-cipher and common modes of operation" -name = "pyaes" -optional = false -python-versions = "*" -version = "1.6.1" - [[package]] category = "main" description = "Data validation and settings management using python 3.6 type hinting" @@ -433,52 +422,27 @@ optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" version = "2.4.7" -[[package]] -category = "main" -description = "Telegram MTProto API Client Library and Framework for Python" -name = "Pyrogram" -optional = false -python-versions = "~=3.5" -version = "0.18.0-async" - -[package.dependencies] -async_generator = "1.10" -async_lru = "1.0.1" -pyaes = "1.6.1" -pysocks = "1.7.1" - -[package.source] -reference = "55fc4faf3436fbf1d47a36e4190955c686121514" -type = "git" -url = "https://github.com/pyrogram/pyrogram" -[[package]] -category = "main" -description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." -name = "pysocks" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.7.1" - [[package]] category = "dev" description = "pytest: simple powerful testing with Python" name = "pytest" optional = false python-versions = ">=3.5" -version = "5.4.3" +version = "6.0.1" [package.dependencies] atomicwrites = ">=1.0" attrs = ">=17.4.0" colorama = "*" +iniconfig = "*" more-itertools = ">=4.0.0" packaging = "*" pluggy = ">=0.12,<1.0" -py = ">=1.5.0" -wcwidth = "*" +py = ">=1.8.2" +toml = "*" [package.extras] -checkqa-mypy = ["mypy (v0.761)"] +checkqa_mypy = ["mypy (0.780)"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] @@ -487,13 +451,13 @@ description = "Pytest support for asyncio." name = "pytest-asyncio" optional = false python-versions = ">= 3.5" -version = "0.10.0" +version = "0.14.0" [package.dependencies] -pytest = ">=3.0.6" +pytest = ">=5.4.0" [package.extras] -testing = ["async-generator (>=1.3)", "coverage", "hypothesis (>=3.64)"] +testing = ["async-generator (>=1.3)", "coverage", "hypothesis (>=5.7.1)"] [[package]] category = "main" @@ -584,7 +548,7 @@ description = "Database Abstraction Library" name = "sqlalchemy" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.3.18" +version = "1.3.19" [package.extras] mssql = ["pyodbc"] @@ -606,23 +570,6 @@ optional = false python-versions = "~=3.4" version = "1.2.1" -[[package]] -category = "main" -description = "" -name = "tgintegration" -optional = false -python-versions = "==3.*,>=3.6.7" -version = "0.4.0" - -[package.dependencies] -pyrogram = "branch asyncio" -typing-extensions = "^3.7.4" - -[package.source] -reference = "86afcb19304de01df5c0d4f035a2c7437c58f856" -type = "git" -url = "https://github.com/josxa/tgintegration" - [[package]] category = "dev" description = "Python Library for Tom's Obvious, Minimal Language" @@ -645,7 +592,7 @@ description = "Typer, build great CLIs. Easy to code. Based on Python type hints name = "typer" optional = false python-versions = ">=3.6" -version = "0.3.1" +version = "0.3.2" [package.dependencies] click = ">=7.1.1,<7.2.0" @@ -654,7 +601,7 @@ click = ">=7.1.1,<7.2.0" all = ["colorama (>=0.4.3,<0.5.0)", "shellingham (>=1.3.0,<2.0.0)"] dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)"] doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=5.4.0,<6.0.0)", "markdown-include (>=0.5.1,<0.6.0)"] -test = ["shellingham (>=1.3.0,<2.0.0)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "coverage (>=5.2,<6.0)", "pytest-xdist (>=1.32.0,<2.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "mypy (0.782)", "black (>=19.10b0,<20.0b0)", "isort (>=5.0.6,<6.0.0)"] +test = ["pytest-xdist (>=1.32.0,<2.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "mypy (0.782)", "black (>=19.10b0,<20.0b0)", "isort (>=5.0.6,<6.0.0)", "shellingham (>=1.3.0,<2.0.0)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "coverage (>=5.2,<6.0)"] [[package]] category = "main" @@ -662,7 +609,7 @@ description = "Backported and Experimental Type Hints for Python 3.5+" name = "typing-extensions" optional = false python-versions = "*" -version = "3.7.4.2" +version = "3.7.4.3" [[package]] category = "main" @@ -697,14 +644,6 @@ brotli = ["brotlipy (>=0.6.0)"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] -[[package]] -category = "dev" -description = "Measures the displayed width of unicode strings in a terminal" -name = "wcwidth" -optional = false -python-versions = "*" -version = "0.2.5" - [[package]] category = "dev" description = "The comprehensive WSGI web application library." @@ -718,7 +657,7 @@ dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx- watchdog = ["watchdog"] [metadata] -content-hash = "3334186d7a04bbd8f26682cd1d97e7eb72e659a1f012b8eeaad858914cb106a8" +content-hash = "7f5b73350e0c5249fef42bef3d88c65b21dbb631af70b896e6f9785c9c0e3a7c" python-versions = "^3.8" [metadata.files] @@ -730,31 +669,24 @@ asttokens = [ {file = "asttokens-2.0.4-py2.py3-none-any.whl", hash = "sha256:766d3352908730efb20b95ae22db0f1cb1bedb67c6071fcffb5c236ea673f2f7"}, {file = "asttokens-2.0.4.tar.gz", hash = "sha256:a42e57e28f2ac1c85ed9b1f84109401427e5c63c04f61d15b8842b027eec5128"}, ] -async-generator = [ - {file = "async_generator-1.10-py3-none-any.whl", hash = "sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b"}, - {file = "async_generator-1.10.tar.gz", hash = "sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144"}, -] -async-lru = [ - {file = "async_lru-1.0.1.tar.gz", hash = "sha256:ac1f7138b54d68570391615b1ff758e189ce2b841a16653aae1255f5be5d4d0b"}, -] atomicwrites = [ {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, - {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, + {file = "attrs-20.1.0-py2.py3-none-any.whl", hash = "sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff"}, + {file = "attrs-20.1.0.tar.gz", hash = "sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a"}, ] birdseye = [ - {file = "birdseye-0.8.3.tar.gz", hash = "sha256:4dd626b9e178b2084b21b178d668e821821387dba9c572e76a9171f911cee0ad"}, + {file = "birdseye-0.8.4.tar.gz", hash = "sha256:34fbf3a042f257e981cb0d4849d279457269d212851404b19bf9635af71e0701"}, ] black = [ {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, ] boltons = [ - {file = "boltons-20.2.0-py2.py3-none-any.whl", hash = "sha256:1567a4ab991a52be4e9a778c6c433e50237018b322759146b07732b71e57ef67"}, - {file = "boltons-20.2.0.tar.gz", hash = "sha256:d367506c0b32042bb1ee3bf7899f2dcc8492dceb42ce3727b89e174d85bffe6e"}, + {file = "boltons-20.2.1-py2.py3-none-any.whl", hash = "sha256:3dd8a8e3c1886e7f7ba3422b50f55a66e1700161bf01b919d098e7d96dd2d9b6"}, + {file = "boltons-20.2.1.tar.gz", hash = "sha256:dd362291a460cc1e0c2e91cc6a60da3036ced77099b623112e8f833e6734bdc5"}, ] buslane = [ {file = "buslane-0.0.5-py3-none-any.whl", hash = "sha256:48f23c308d319c07c09cc00ff5be768b0e3e8b077c1a01e0c12ada9b1c89fe73"}, @@ -773,7 +705,7 @@ chardet = [ {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, ] cheap-repr = [ - {file = "cheap_repr-0.4.1.tar.gz", hash = "sha256:675e6fc43bcad39f796f588d492bbc402baa4efde164fdb1b3f50264779278a6"}, + {file = "cheap_repr-0.4.2.tar.gz", hash = "sha256:3d6428acab94a2fd94127ebe6a7c838ff52286632c934e0ca526c8318b27e46c"}, ] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, @@ -802,13 +734,17 @@ haps = [ {file = "haps-1.1.2.tar.gz", hash = "sha256:203f25a81d6b4c544a8dc5dbd03b14510a96bd129f961cbe3452caf938eb33cd"}, ] humanize = [ - {file = "humanize-2.5.0-py3-none-any.whl", hash = "sha256:89062c6db8601693b7d223443d0d7529aa9577df43a1387ddd4b9c273abb4a51"}, - {file = "humanize-2.5.0.tar.gz", hash = "sha256:8a68bd9bccb899fd9bfb1e6d96c1e84e4475551cc9a5b5bdbd69b9b1cfd19c80"}, + {file = "humanize-2.6.0-py3-none-any.whl", hash = "sha256:fd5b32945687443d5b8bc1e02fad027da1d293a9e963b3450122ad98ef534f21"}, + {file = "humanize-2.6.0.tar.gz", hash = "sha256:8ee358ea6c23de896b9d1925ebe6a8504bb2ba7e98d5ccf4d07ab7f3b28f3819"}, ] idna = [ {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] +iniconfig = [ + {file = "iniconfig-1.0.1-py3-none-any.whl", hash = "sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437"}, + {file = "iniconfig-1.0.1.tar.gz", hash = "sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69"}, +] itsdangerous = [ {file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"}, {file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"}, @@ -908,9 +844,6 @@ py = [ {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, ] -pyaes = [ - {file = "pyaes-1.6.1.tar.gz", hash = "sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f"}, -] pydantic = [ {file = "pydantic-1.6.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:418b84654b60e44c0cdd5384294b0e4bc1ebf42d6e873819424f3b78b8690614"}, {file = "pydantic-1.6.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4900b8820b687c9a3ed753684337979574df20e6ebe4227381d04b3c3c628f99"}, @@ -938,19 +871,13 @@ pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] -Pyrogram = [] -pysocks = [ - {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, - {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, - {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, -] pytest = [ - {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, - {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, + {file = "pytest-6.0.1-py3-none-any.whl", hash = "sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad"}, + {file = "pytest-6.0.1.tar.gz", hash = "sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4"}, ] pytest-asyncio = [ - {file = "pytest-asyncio-0.10.0.tar.gz", hash = "sha256:9fac5100fd716cbecf6ef89233e8590a4ad61d729d1732e0a96b84182df1daaf"}, - {file = "pytest_asyncio-0.10.0-py3-none-any.whl", hash = "sha256:d734718e25cfc32d2bf78d346e99d33724deeba774cc4afdf491530c6184b63b"}, + {file = "pytest-asyncio-0.14.0.tar.gz", hash = "sha256:9882c0c6b24429449f5f969a5158b528f39bde47dc32e85b9f0403965017e700"}, + {file = "pytest_asyncio-0.14.0-py3-none-any.whl", hash = "sha256:2eae1e34f6c68fc0a9dc12d4bea190483843ff4708d24277c41568d6b6044f1d"}, ] python-decouple = [ {file = "python-decouple-3.3.tar.gz", hash = "sha256:55c546b85b0c47a15a47a4312d451a437f7344a9be3e001660bccd93b637de95"}, @@ -1002,34 +929,34 @@ six = [ {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, ] sqlalchemy = [ - {file = "SQLAlchemy-1.3.18-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:f11c2437fb5f812d020932119ba02d9e2bc29a6eca01a055233a8b449e3e1e7d"}, - {file = "SQLAlchemy-1.3.18-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:0ec575db1b54909750332c2e335c2bb11257883914a03bc5a3306a4488ecc772"}, - {file = "SQLAlchemy-1.3.18-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:f57be5673e12763dd400fea568608700a63ce1c6bd5bdbc3cc3a2c5fdb045274"}, - {file = "SQLAlchemy-1.3.18-cp27-cp27m-win32.whl", hash = "sha256:8cac7bb373a5f1423e28de3fd5fc8063b9c8ffe8957dc1b1a59cb90453db6da1"}, - {file = "SQLAlchemy-1.3.18-cp27-cp27m-win_amd64.whl", hash = "sha256:adad60eea2c4c2a1875eb6305a0b6e61a83163f8e233586a4d6a55221ef984fe"}, - {file = "SQLAlchemy-1.3.18-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:57aa843b783179ab72e863512e14bdcba186641daf69e4e3a5761d705dcc35b1"}, - {file = "SQLAlchemy-1.3.18-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:621f58cd921cd71ba6215c42954ffaa8a918eecd8c535d97befa1a8acad986dd"}, - {file = "SQLAlchemy-1.3.18-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:fc728ece3d5c772c196fd338a99798e7efac7a04f9cb6416299a3638ee9a94cd"}, - {file = "SQLAlchemy-1.3.18-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:736d41cfebedecc6f159fc4ac0769dc89528a989471dc1d378ba07d29a60ba1c"}, - {file = "SQLAlchemy-1.3.18-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:427273b08efc16a85aa2b39892817e78e3ed074fcb89b2a51c4979bae7e7ba98"}, - {file = "SQLAlchemy-1.3.18-cp35-cp35m-win32.whl", hash = "sha256:cbe1324ef52ff26ccde2cb84b8593c8bf930069dfc06c1e616f1bfd4e47f48a3"}, - {file = "SQLAlchemy-1.3.18-cp35-cp35m-win_amd64.whl", hash = "sha256:8fd452dc3d49b3cc54483e033de6c006c304432e6f84b74d7b2c68afa2569ae5"}, - {file = "SQLAlchemy-1.3.18-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:e89e0d9e106f8a9180a4ca92a6adde60c58b1b0299e1b43bd5e0312f535fbf33"}, - {file = "SQLAlchemy-1.3.18-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6ac2558631a81b85e7fb7a44e5035347938b0a73f5fdc27a8566777d0792a6a4"}, - {file = "SQLAlchemy-1.3.18-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:87fad64529cde4f1914a5b9c383628e1a8f9e3930304c09cf22c2ae118a1280e"}, - {file = "SQLAlchemy-1.3.18-cp36-cp36m-win32.whl", hash = "sha256:e4624d7edb2576cd72bb83636cd71c8ce544d8e272f308bd80885056972ca299"}, - {file = "SQLAlchemy-1.3.18-cp36-cp36m-win_amd64.whl", hash = "sha256:89494df7f93b1836cae210c42864b292f9b31eeabca4810193761990dc689cce"}, - {file = "SQLAlchemy-1.3.18-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:716754d0b5490bdcf68e1e4925edc02ac07209883314ad01a137642ddb2056f1"}, - {file = "SQLAlchemy-1.3.18-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:50c4ee32f0e1581828843267d8de35c3298e86ceecd5e9017dc45788be70a864"}, - {file = "SQLAlchemy-1.3.18-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:d98bc827a1293ae767c8f2f18be3bb5151fd37ddcd7da2a5f9581baeeb7a3fa1"}, - {file = "SQLAlchemy-1.3.18-cp37-cp37m-win32.whl", hash = "sha256:0942a3a0df3f6131580eddd26d99071b48cfe5aaf3eab2783076fbc5a1c1882e"}, - {file = "SQLAlchemy-1.3.18-cp37-cp37m-win_amd64.whl", hash = "sha256:16593fd748944726540cd20f7e83afec816c2ac96b082e26ae226e8f7e9688cf"}, - {file = "SQLAlchemy-1.3.18-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:c26f95e7609b821b5f08a72dab929baa0d685406b953efd7c89423a511d5c413"}, - {file = "SQLAlchemy-1.3.18-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:512a85c3c8c3995cc91af3e90f38f460da5d3cade8dc3a229c8e0879037547c9"}, - {file = "SQLAlchemy-1.3.18-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d05c4adae06bd0c7f696ae3ec8d993ed8ffcc4e11a76b1b35a5af8a099bd2284"}, - {file = "SQLAlchemy-1.3.18-cp38-cp38-win32.whl", hash = "sha256:109581ccc8915001e8037b73c29590e78ce74be49ca0a3630a23831f9e3ed6c7"}, - {file = "SQLAlchemy-1.3.18-cp38-cp38-win_amd64.whl", hash = "sha256:8619b86cb68b185a778635be5b3e6018623c0761dde4df2f112896424aa27bd8"}, - {file = "SQLAlchemy-1.3.18.tar.gz", hash = "sha256:da2fb75f64792c1fc64c82313a00c728a7c301efe6a60b7a9fe35b16b4368ce7"}, + {file = "SQLAlchemy-1.3.19-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:f2e8a9c0c8813a468aa659a01af6592f71cd30237ec27c4cc0683f089f90dcfc"}, + {file = "SQLAlchemy-1.3.19-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:33d29ae8f1dc7c75b191bb6833f55a19c932514b9b5ce8c3ab9bc3047da5db36"}, + {file = "SQLAlchemy-1.3.19-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3292a28344922415f939ee7f4fc0c186f3d5a0bf02192ceabd4f1129d71b08de"}, + {file = "SQLAlchemy-1.3.19-cp27-cp27m-win32.whl", hash = "sha256:883c9fb62cebd1e7126dd683222b3b919657590c3e2db33bdc50ebbad53e0338"}, + {file = "SQLAlchemy-1.3.19-cp27-cp27m-win_amd64.whl", hash = "sha256:860d0fe234922fd5552b7f807fbb039e3e7ca58c18c8d38aa0d0a95ddf4f6c23"}, + {file = "SQLAlchemy-1.3.19-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:73a40d4fcd35fdedce07b5885905753d5d4edf413fbe53544dd871f27d48bd4f"}, + {file = "SQLAlchemy-1.3.19-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:5a49e8473b1ab1228302ed27365ea0fadd4bf44bc0f9e73fe38e10fdd3d6b4fc"}, + {file = "SQLAlchemy-1.3.19-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:6547b27698b5b3bbfc5210233bd9523de849b2bb8a0329cd754c9308fc8a05ce"}, + {file = "SQLAlchemy-1.3.19-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:107d4af989831d7b091e382d192955679ec07a9209996bf8090f1f539ffc5804"}, + {file = "SQLAlchemy-1.3.19-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:eb1d71643e4154398b02e88a42fc8b29db8c44ce4134cf0f4474bfc5cb5d4dac"}, + {file = "SQLAlchemy-1.3.19-cp35-cp35m-win32.whl", hash = "sha256:96f51489ac187f4bab588cf51f9ff2d40b6d170ac9a4270ffaed535c8404256b"}, + {file = "SQLAlchemy-1.3.19-cp35-cp35m-win_amd64.whl", hash = "sha256:618db68745682f64cedc96ca93707805d1f3a031747b5a0d8e150cfd5055ae4d"}, + {file = "SQLAlchemy-1.3.19-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:6557af9e0d23f46b8cd56f8af08eaac72d2e3c632ac8d5cf4e20215a8dca7cea"}, + {file = "SQLAlchemy-1.3.19-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8280f9dae4adb5889ce0bb3ec6a541bf05434db5f9ab7673078c00713d148365"}, + {file = "SQLAlchemy-1.3.19-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:b595e71c51657f9ee3235db8b53d0b57c09eee74dfb5b77edff0e46d2218dc02"}, + {file = "SQLAlchemy-1.3.19-cp36-cp36m-win32.whl", hash = "sha256:8afcb6f4064d234a43fea108859942d9795c4060ed0fbd9082b0f280181a15c1"}, + {file = "SQLAlchemy-1.3.19-cp36-cp36m-win_amd64.whl", hash = "sha256:e49947d583fe4d29af528677e4f0aa21f5e535ca2ae69c48270ebebd0d8843c0"}, + {file = "SQLAlchemy-1.3.19-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:9e865835e36dfbb1873b65e722ea627c096c11b05f796831e3a9b542926e979e"}, + {file = "SQLAlchemy-1.3.19-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:276936d41111a501cf4a1a0543e25449108d87e9f8c94714f7660eaea89ae5fe"}, + {file = "SQLAlchemy-1.3.19-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:c7adb1f69a80573698c2def5ead584138ca00fff4ad9785a4b0b2bf927ba308d"}, + {file = "SQLAlchemy-1.3.19-cp37-cp37m-win32.whl", hash = "sha256:aa0554495fe06172b550098909be8db79b5accdf6ffb59611900bea345df5eba"}, + {file = "SQLAlchemy-1.3.19-cp37-cp37m-win_amd64.whl", hash = "sha256:15c0bcd3c14f4086701c33a9e87e2c7ceb3bcb4a246cd88ec54a49cf2a5bd1a6"}, + {file = "SQLAlchemy-1.3.19-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:fe7fe11019fc3e6600819775a7d55abc5446dda07e9795f5954fdbf8a49e1c37"}, + {file = "SQLAlchemy-1.3.19-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c898b3ebcc9eae7b36bd0b4bbbafce2d8076680f6868bcbacee2d39a7a9726a7"}, + {file = "SQLAlchemy-1.3.19-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:072766c3bd09294d716b2d114d46ffc5ccf8ea0b714a4e1c48253014b771c6bb"}, + {file = "SQLAlchemy-1.3.19-cp38-cp38-win32.whl", hash = "sha256:b70bad2f1a5bd3460746c3fb3ab69e4e0eb5f59d977a23f9b66e5bdc74d97b86"}, + {file = "SQLAlchemy-1.3.19-cp38-cp38-win_amd64.whl", hash = "sha256:83469ad15262402b0e0974e612546bc0b05f379b5aa9072ebf66d0f8fef16bea"}, + {file = "SQLAlchemy-1.3.19.tar.gz", hash = "sha256:3bba2e9fbedb0511769780fe1d63007081008c5c2d7d715e91858c94dbaa260e"}, ] tgcrypto = [ {file = "TgCrypto-1.2.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:597d24debc4ef95bb14e0406e41951cfc5472853446344911214bcdd9a123b89"}, @@ -1061,7 +988,6 @@ tgcrypto = [ {file = "TgCrypto-1.2.1-cp38-cp38-win32.whl", hash = "sha256:0b7790be25c1ea5cb7c05b2efb1d40f427efe6aa31ecde080136ea5eb8406870"}, {file = "TgCrypto-1.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:822222ee462f0770a825deecbba459f74bcbba451b9f4b516590260e627b1494"}, ] -tgintegration = [] toml = [ {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, @@ -1090,13 +1016,13 @@ typed-ast = [ {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, ] typer = [ - {file = "typer-0.3.1-py3-none-any.whl", hash = "sha256:778a9695e68eb26a0a0321ca9d3f1a8809783f6f083549b84c67bc2385bf014e"}, - {file = "typer-0.3.1.tar.gz", hash = "sha256:85b1e5f6369750b4220ad548ea30b881a2c502504e5a0d849db9bdf6b487bdbf"}, + {file = "typer-0.3.2-py3-none-any.whl", hash = "sha256:ba58b920ce851b12a2d790143009fa00ac1d05b3ff3257061ff69dbdfc3d161b"}, + {file = "typer-0.3.2.tar.gz", hash = "sha256:5455d750122cff96745b0dec87368f56d023725a7ebc9d2e54dd23dc86816303"}, ] typing-extensions = [ - {file = "typing_extensions-3.7.4.2-py2-none-any.whl", hash = "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392"}, - {file = "typing_extensions-3.7.4.2-py3-none-any.whl", hash = "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5"}, - {file = "typing_extensions-3.7.4.2.tar.gz", hash = "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae"}, + {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, + {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, + {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, ] typing-inspect = [ {file = "typing_inspect-0.5.0-py2-none-any.whl", hash = "sha256:75c97b7854426a129f3184c68588db29091ff58e6908ed520add1d52fc44df6e"}, @@ -1110,10 +1036,6 @@ urllib3 = [ {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"}, {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, ] -wcwidth = [ - {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, - {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, -] werkzeug = [ {file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"}, {file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"}, diff --git a/pyproject.toml b/pyproject.toml index 089c339..222780a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,8 +6,6 @@ authors = ["JosXa"] [tool.poetry.dependencies] python = "^3.8" -pyrogram = {git = "https://github.com/pyrogram/pyrogram", rev = "55fc4faf3436fbf1d47a36e4190955c686121514"} -tgintegration = {git = "https://github.com/josxa/tgintegration", rev = "async-develop"} tgcrypto = "^1.2.0" cached_property = "^1.5.1" buslane = "^0.0.u" @@ -26,13 +24,13 @@ boltons = "^20.1.0" pytz = "2020.1" [tool.poetry.dev-dependencies] -pytest = "^5.4.3" -pytest-asyncio = "^0.10.0" black = "^19.10b0" mypy = ">0.770" birdseye = "^0.8.3" mkinit = "^0.2.0" typer = "^0.3.1" +pytest = "^6.0.1" +pytest-asyncio = "^0.14.0" [build-system] requires = ["poetry>=0.12"] diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..2c4fd03 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +filterwarnings = + error + ignore::DeprecationWarning diff --git a/setup.py b/setup.py deleted file mode 100644 index a0f3c30..0000000 --- a/setup.py +++ /dev/null @@ -1,14 +0,0 @@ -from setuptools import setup - -setup( - name="botkit", - version="0.1", - description="Opinionated, library-agnostic Python framework for rapid development of Telegram bots and userbots " - "with maintainability for large projects.", - url="https://github.com/autogram/Botkit", - author="JosXa", - author_email="joscha.goetzer@gmail.com", - license="MIT", - packages=["botkit"], - zip_safe=False, -) diff --git a/tests/builders/test_inlinemenubuilder.py b/tests/builders/test_inlinemenubuilder.py index d1868af..24c7833 100644 --- a/tests/builders/test_inlinemenubuilder.py +++ b/tests/builders/test_inlinemenubuilder.py @@ -1,5 +1,5 @@ from haps import Container, Egg -from pyrogram import InlineKeyboardButton +from pyrogram.types import InlineKeyboardButton from botkit.builders.inlinemenubuilder import InlineMenuBuilder from botkit.persistence.callback_manager import ICallbackManager, MemoryDictCallbackManager @@ -7,7 +7,14 @@ from botkit.settings import botkit_settings Container.configure( - [Egg(ICallbackManager, ICallbackManager, botkit_settings.callback_manager_qualifier, MemoryDictCallbackManager)] + [ + Egg( + ICallbackManager, + ICallbackManager, + botkit_settings.callback_manager_qualifier, + MemoryDictCallbackManager, + ) + ] ) diff --git a/tests/inlinequeries/test_prefixbasedinlinequeryhandler.py b/tests/inlinequeries/test_prefixbasedinlinequeryhandler.py index 37551b9..1024c76 100644 --- a/tests/inlinequeries/test_prefixbasedinlinequeryhandler.py +++ b/tests/inlinequeries/test_prefixbasedinlinequeryhandler.py @@ -1,15 +1,15 @@ from typing import Optional -from common.inlinequeries.contexts import PrefixBasedInlineQueryContext +from botkit.inlinequeries.contexts import PrefixBasedInlineModeContext def parse(text: str, prefix: str, delimiter: Optional[str] = None): - ctx = PrefixBasedInlineQueryContext(prefix, delimiter=delimiter or ": ") + ctx = PrefixBasedInlineModeContext(prefix, delimiter=delimiter or ": ") return ctx.parse_input(text) def format(user_input: str, prefix: str, delimiter: Optional[str] = None): - ctx = PrefixBasedInlineQueryContext(prefix, delimiter=delimiter or ": ") + ctx = PrefixBasedInlineModeContext(prefix, delimiter=delimiter or ": ") return ctx.format_query(user_input) @@ -63,9 +63,7 @@ def test_format_input_whitespace(): def test_format_input_newlines(): - assert ( - format(user_input=" a\nb\nc ", prefix="lala", delimiter=": ") == "lala: a\nb\nc" - ) + assert format(user_input=" a\nb\nc ", prefix="lala", delimiter=": ") == "lala: a\nb\nc" # endregion diff --git a/tests/module_tests/func_module_tests.py b/tests/module_tests/test_func_module.py similarity index 100% rename from tests/module_tests/func_module_tests.py rename to tests/module_tests/test_func_module.py diff --git a/tests/routing/pipelines/factories/steps/evaluate_send_target_pyrogram_tests.py b/tests/routing/pipelines/factories/steps/test_evaluate_send_target.py similarity index 98% rename from tests/routing/pipelines/factories/steps/evaluate_send_target_pyrogram_tests.py rename to tests/routing/pipelines/factories/steps/test_evaluate_send_target.py index 5baee88..1533510 100644 --- a/tests/routing/pipelines/factories/steps/evaluate_send_target_pyrogram_tests.py +++ b/tests/routing/pipelines/factories/steps/test_evaluate_send_target.py @@ -1,7 +1,7 @@ from unittest.mock import Mock import pytest -from pyrogram import Chat, Message, User +from pyrogram.types import Chat, Message, User from botkit.routing.pipelines.execution_plan import SendTo from botkit.routing.pipelines.factories.steps.send_view_step_factory import evaluate_send_target @@ -15,7 +15,6 @@ @pytest.fixture(scope="function") def context(): - (replied_to_message := Mock(Message)).configure_mock(message_id=REPLIED_TO_MESSAGE_ID) (chat := Mock(Chat)).configure_mock(id=SAME_CHAT_ID) (user := Mock(User)).configure_mock(id=USER_ID) diff --git a/tests/routing/plan/test_update_types.py b/tests/routing/plan/test_update_types.py index 6f12ad5..3bea28c 100644 --- a/tests/routing/plan/test_update_types.py +++ b/tests/routing/plan/test_update_types.py @@ -1,14 +1,21 @@ -from typing import Callable, List, Type, Union +from typing import Callable, List, Union -from pyrogram import CallbackQuery, Client, Message +from pyrogram import Client +from pyrogram.types import CallbackQuery, Message -from botkit.routing.update_types._pyrogram_update_type_inference import determine_pyrogram_handler_update_types +from botkit.libraries.annotations import IClient +from botkit.libraries.pyro_types._pyrogram_update_type_inference import ( + determine_pyrogram_handler_update_types, +) from botkit.routing.update_types.updatetype import UpdateType from botkit.utils.typed_callable import TypedCallable def make_valid_handler_variations(update_type: object) -> List[Callable]: - async def pure(client: Client, message: update_type): + async def pure(client: IClient, message: update_type): + pass + + async def pure_pyro(client: Client, message: update_type): pass async def no_client_annotation(client, x: update_type): @@ -23,7 +30,7 @@ async def more_args_inbetween(client, something, sth_else, message: update_type) def sync(client, message: update_type): pass - return [pure, no_client_annotation, with_additional_args, more_args_inbetween, sync] + return [pure, pure_pyro, no_client_annotation, with_additional_args, more_args_inbetween, sync] def test_message_handler_can_be_determined() -> None: @@ -32,7 +39,7 @@ def test_message_handler_can_be_determined() -> None: for h in handlers: res = determine_pyrogram_handler_update_types(TypedCallable(h)) - assert res == UpdateType.message + assert res == {UpdateType.message} def test_message_callback_query_union_handler_can_be_determined() -> None: @@ -41,4 +48,4 @@ def test_message_callback_query_union_handler_can_be_determined() -> None: for h in handlers: res = determine_pyrogram_handler_update_types(TypedCallable(h)) - assert res == [UpdateType.message, UpdateType.callback_query] + assert res == {UpdateType.message, UpdateType.callback_query} diff --git a/tests/routing/test_publish_expression.py b/tests/routing/test_publish_expression.py index a5d97ed..22ef97e 100644 --- a/tests/routing/test_publish_expression.py +++ b/tests/routing/test_publish_expression.py @@ -1,12 +1,14 @@ from dataclasses import dataclass from buslane.events import Event, EventHandler -from pyrogram import CallbackQuery, Client +from pyrogram.types import CallbackQuery from unittest.mock import Mock +from botkit.routing.route import RouteDefinition from botkit.routing.route_builder.builder import RouteBuilder +from botkit.types.client import IClient -client: Client = Mock(Client) +client: IClient = Mock(IClient) callback_query: CallbackQuery = Mock(CallbackQuery) @@ -30,6 +32,6 @@ def test_route_with_publish_expression_fires_event(): builder.use(client) builder.on_action(ACTION).publish(event) - route = builder._route_collection.routes_by_client[client][0] + route: RouteDefinition = builder._route_collection.routes_by_client[client][0] - assert route.callback is not None + assert len(route.pyrogram_handlers) == 1 diff --git a/tests/routing/test_routing.py b/tests/routing/test_routing.py index a8f979d..ed25c5d 100644 --- a/tests/routing/test_routing.py +++ b/tests/routing/test_routing.py @@ -3,18 +3,18 @@ from typing import List from unittest.mock import MagicMock, Mock -from pyrogram import CallbackQuery, Client, Filters -from pyrogram.client.handlers.handler import Handler +from pyrogram.types import CallbackQuery -from botkit.dispatching.callbackqueries.callbackactioncontext import CallbackActionContext from botkit.dispatching.callbackqueries.callbackactiondispatcher import CallbackActionDispatcher from botkit.dispatching.dispatcher import BotkitDispatcher +from botkit.libraries.annotations import IClient +from botkit.persistence.callback_manager import CallbackActionContext from botkit.routing.route import RouteDefinition from botkit.routing.route_builder.builder import RouteBuilder from botkit.routing.update_types.updatetype import UpdateType from botkit.utils.typed_callable import TypedCallable -client: Client = Mock(Client) +client: IClient = Mock(IClient) callback_query: CallbackQuery = Mock(CallbackQuery) @@ -30,7 +30,7 @@ def mutate_something_without_returning(self, context: CallbackActionContext): assert context.payload == PAYLOAD self.value = context.payload - async def handle(client: Client, callback_query: CallbackQuery, state: TestModel) -> str: + async def handle(client: IClient, callback_query: CallbackQuery, state: TestModel) -> str: assert client is not None assert callback_query is not None assert state is not None @@ -39,7 +39,9 @@ async def handle(client: Client, callback_query: CallbackQuery, state: TestModel builder = RouteBuilder() builder.use(client) - builder.on_action(ACTION).mutate(TestModel.mutate_something_without_returning).then_call(handle) + builder.on_action(ACTION).mutate(TestModel.mutate_something_without_returning).then_call( + handle + ) routes: List[RouteDefinition] = builder._route_collection.routes_by_client[client] route = routes[0] @@ -50,7 +52,7 @@ async def handle(client: Client, callback_query: CallbackQuery, state: TestModel assert UpdateType.callback_query in route.handler_by_update_type callback = route.handler_by_update_type[UpdateType.callback_query].callback assert TypedCallable(callback).type_hints == { - "client": Client, + "client": IClient, "callback_query": CallbackQuery, "state": TestModel, "return": str, @@ -60,7 +62,9 @@ async def handle(client: Client, callback_query: CallbackQuery, state: TestModel action_context: CallbackActionContext = CallbackActionContext(action=ACTION, payload=PAYLOAD) - result = asyncio.get_event_loop().run_until_complete(callback(client, callback_query, state, action_context)) + result = asyncio.get_event_loop().run_until_complete( + callback(client, callback_query, state, action_context) + ) assert result == handle.__name__ assert state.value == PAYLOAD @@ -80,7 +84,7 @@ async def handle(client: Client, callback_query: CallbackQuery, state: TestModel assert state is not None return state - filters = Filters.text + filters = filters.text builder = RouteBuilder() builder.use(client) @@ -101,7 +105,9 @@ async def handle(client: Client, callback_query: CallbackQuery, state: TestModel action_context: CallbackActionContext = CallbackActionContext(action=ACTION) - state = asyncio.get_event_loop().run_until_complete(callback(client, callback_query, None, action_context)) + state = asyncio.get_event_loop().run_until_complete( + callback(client, callback_query, None, action_context) + ) assert isinstance(state, TestModel) assert state.value == EXPECTATION @@ -128,7 +134,9 @@ async def handle(client: Client, callback_query: CallbackQuery, state: TestModel builder = RouteBuilder() builder.use(client) - builder.on_action(ACTION).mutate(TestModel.mutate_something_without_returning).then_call(handle) + builder.on_action(ACTION).mutate(TestModel.mutate_something_without_returning).then_call( + handle + ) routes: List[RouteDefinition] = builder._route_collection.routes_by_client[client] route = routes[0] @@ -151,7 +159,9 @@ async def handle(client: Client, callback_query: CallbackQuery, state: TestModel action_context: CallbackActionContext = CallbackActionContext(action=ACTION, payload=PAYLOAD) async def interject_handler(self, group: int, client: Client, handler: Handler): - result = asyncio.get_event_loop().run_until_complete(callback(client, callback_query, state, action_context)) + result = asyncio.get_event_loop().run_until_complete( + callback(client, callback_query, state, action_context) + ) assert result == handle.__name__ assert state.value == PAYLOAD @@ -161,4 +171,6 @@ async def interject_handler(self, group: int, client: Client, handler: Handler): dispatcher.callback_action_dispatchers[client] = callback_action_dispatcher dispatcher.add_handler = MagicMock() dispatcher.add_route_for_update_type(0, client, UpdateType.callback_query, route_handler) - dispatcher.add_handler.assert_called_once_with(0, client, callback_action_dispatcher.pyrogram_handler) + dispatcher.add_handler.assert_called_once_with( + 0, client, callback_action_dispatcher.pyrogram_handler + ) diff --git a/tests/routing/test_state_machines.py b/tests/routing/test_state_machines.py index a7b570b..105db73 100644 --- a/tests/routing/test_state_machines.py +++ b/tests/routing/test_state_machines.py @@ -2,12 +2,14 @@ from unittest.mock import Mock import pytest -from pyrogram import Client, Filters, Message +from pyrogram import filters +from pyrogram.types import Message from botkit.core.modules import Module +from botkit.libraries.annotations import IClient from botkit.routing.route_builder.builder import RouteBuilder, StateRouteBuilder -client: Client = Mock(Client) +client: IClient = Mock(IClient) def test_state_machine_initialization_via_num_arguments(): @@ -51,18 +53,18 @@ def register(self, routes: RouteBuilder): entry_points = cast(StateRouteBuilder, entry_points) ended = cast(StateRouteBuilder, ended) - def entry_point(client: Client, message: Message): + def entry_point(client: IClient, message: Message): pass - def end(client: Client, message: Message): + def end(client: IClient, message: Message): pass - entry_points.on(Filters.text).call(entry_point).and_transition_to(ended) - ended.on(Filters.text).call(end).and_exit_state() + entry_points.on(filters.text).call(entry_point).and_transition_to(ended) + ended.on(filters.text).call(end).and_exit_state() def test_state_machine_routing(): - # TODO: implement + pass # TODO: implement # module = StateMachineTestModule() # dispatcher = BotkitDispatcher() # diff --git a/tests/utils/test_typed_callables.py b/tests/utils/test_typed_callables.py new file mode 100644 index 0000000..2b4eaa5 --- /dev/null +++ b/tests/utils/test_typed_callables.py @@ -0,0 +1,70 @@ +import inspect +from typing import Optional, Union + +from pyrogram.types import Message + +from botkit.utils.typed_callable import TypedCallable +from botkit.views.botkit_context import BotkitContext + + +def my_func(ctx: BotkitContext, test_int: int = 1, test_none: Optional[str] = None): + pass + + +async def my_func_async(ctx: BotkitContext, test_int: int = 1, test_none: Optional[str] = None): + pass + + +class TestClass: + def my_method(self, ctx: BotkitContext, test_int: int = 1, test_none: Optional[str] = None): + pass + + async def my_method_async( + self, ctx: BotkitContext, test_int: int = 1, test_none: Optional[str] = None + ): + pass + + +def test_regular_function_properties(): + tc = TypedCallable(my_func) + assert not tc.is_coroutine + assert tc.name == "my_func" + assert tc.num_non_optional_params == 1 + assert tc.num_parameters == 3 + assert tc.type_hints == {"ctx": BotkitContext, "test_int": int, "test_none": Optional[str]} + + +def test_coroutine_function_properties(): + tc = TypedCallable(my_func_async) + assert tc.is_coroutine + assert tc.name == "my_func_async" + assert tc.num_non_optional_params == 1 + assert tc.num_parameters == 3 + assert tc.type_hints == {"ctx": BotkitContext, "test_int": int, "test_none": Optional[str]} + + +def test_regular_method_properties(): + cls = TestClass() + tc = TypedCallable(cls.my_method) + assert not tc.is_coroutine + assert tc.name == "my_method" + assert tc.num_non_optional_params == 1 + assert tc.num_parameters == 3 + assert tc.type_hints == {"ctx": BotkitContext, "test_int": int, "test_none": Optional[str]} + + +def test_coroutine_method_properties(): + cls = TestClass() + tc = TypedCallable(cls.my_method_async) + assert tc.is_coroutine + assert tc.name == "my_method_async" + assert tc.num_non_optional_params == 1 + assert tc.num_parameters == 3 + assert tc.type_hints == {"ctx": BotkitContext, "test_int": int, "test_none": Optional[str]} + + +def test_sth(): + m = Message(message_id=123) + edit_sig = inspect.signature(m.edit) + reply_sig = inspect.signature(m.reply) + assert edit_sig == reply_sig diff --git a/tests/utils/typed_callable_tests.py b/tests/utils/typed_callable_tests.py deleted file mode 100644 index 537d243..0000000 --- a/tests/utils/typed_callable_tests.py +++ /dev/null @@ -1,13 +0,0 @@ -import inspect - -from botkit.utils.typed_callable import TypedCallable -from botkit.views.botkit_context import BotkitContext - - -def test_num_non_optional_parameters(): - def my_func(ctx: BotkitContext): - pass - - tc = TypedCallable(my_func) - - assert tc.num_non_optional_params == 1 diff --git a/tests/views/test_functional_views.py b/tests/views/test_functional_views.py index 0d4130b..68e3b90 100644 --- a/tests/views/test_functional_views.py +++ b/tests/views/test_functional_views.py @@ -36,8 +36,8 @@ def test_full_view_can_be_rendered(di): assert res.title == TITLE -def test_using_photo_rendered_message_is_media(): - def f(_, builder: ViewBuilder): - builder. - - render_functional_view() +# def test_using_photo_rendered_message_is_media(): +# def f(_, builder: ViewBuilder): +# builder. +# +# render_functional_view() diff --git a/tests/views/test_simple_message_view.py b/tests/views/test_simple_message_view.py deleted file mode 100644 index 23c8a66..0000000 --- a/tests/views/test_simple_message_view.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import Iterable - -from dataclasses import dataclass -from pyrogram import InlineKeyboardButton - - - -def test_simple_message_view_can_be_instantiated(): - TestMessageViewSimple(MyModel(test_string="teststring"), context=ViewContext()) - - -def test_simple_message_view_can_be_rendered(): - view = TestMessageViewSimple(MyModel(test_string="teststring"), context=ViewContext()) - rendered = view._render() - assert rendered.text == "teststring" - assert rendered.buttons is None - - -@dataclass -class MyModel: - test_string: str - - -class TestMessageViewSimple(MessageView[MyModel]): - def render_body(self): - return self.state.test_string - - def render_inline_menu(self) -> Iterable[Iterable[KeyboardTypes]]: - return [[InlineKeyboardButton("test", callback_data="test")]] diff --git a/tests/views/test_typical_inline_message_view.py b/tests/views/test_typical_inline_message_view.py deleted file mode 100644 index 994b80e..0000000 --- a/tests/views/test_typical_inline_message_view.py +++ /dev/null @@ -1,51 +0,0 @@ -from typing import Iterable - -# noinspection Mypy -from dataclasses import dataclass -from pyrogram import InlineKeyboardButton - - -TextVi - -@dataclass -class InlineModel: - text: str - title: str - description: str - menu: Iterable[Iterable[InlineKeyboardButton]] - - -class TypicalInlineView(TextView[InlineModel]): - def render_body(self) -> str: - return self.state.test - - def render_title(self) -> str: - return self.state.title - - def render_description(self) -> str: - return self.state.description - - def render_inline_menu(self) -> Iterable[Iterable[KeyboardTypes]]: - return self.state.menu - - -def test_view_can_be_initialized(): - TypicalInlineView(InlineModel(text="", title="", description="", menu=[[]]), context=ViewContext()) - - -def test_view_renders_correctly(): - view = TypicalInlineView( - InlineModel( - text="text", - title="title", - description="description", - menu=[[InlineKeyboardButton("hello world", callback_data="test")]], - ) - ) - rendered = view._render() - assert rendered.text == "text" - assert rendered.title == "title" - assert rendered.description == "description" - assert rendered.buttons is not None - assert rendered.buttons[0][0].callback_data == "test" - assert rendered.buttons[0][0].text == "hello world"