From 2ac011521c5f774f480b52b3fdfb0a11d53d6114 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Fri, 4 Aug 2017 17:56:05 +0300 Subject: [PATCH] Remove deprecated code-stuff. --- aiogram/dispatcher/__init__.py | 28 +- aiogram/dispatcher/handler.py | 68 +- aiogram/dispatcher/state.py | 799 ------------------ examples/deprecated_state_machine.py | 91 -- .../deprecated_steps_and_downloading_file.py | 71 -- 5 files changed, 4 insertions(+), 1053 deletions(-) delete mode 100644 aiogram/dispatcher/state.py delete mode 100644 examples/deprecated_state_machine.py delete mode 100644 examples/deprecated_steps_and_downloading_file.py diff --git a/aiogram/dispatcher/__init__.py b/aiogram/dispatcher/__init__.py index aeca4f42..08f23d2c 100644 --- a/aiogram/dispatcher/__init__.py +++ b/aiogram/dispatcher/__init__.py @@ -3,12 +3,10 @@ import logging import typing from .filters import CommandsFilter, RegexpFilter, ContentTypeFilter, generate_default_filters -from .handler import Handler, NextStepHandler +from .handler import Handler from .storage import DisabledStorage, BaseStorage, FSMContext -from .. import types from ..bot import Bot from ..types.message import ContentType -from ..utils.deprecated import deprecated log = logging.getLogger(__name__) @@ -22,7 +20,7 @@ class Dispatcher: Provide next step handler and etc. """ - def __init__(self, bot, loop=None, storage: typing.Optional[BaseStorage]=None): + def __init__(self, bot, loop=None, storage: typing.Optional[BaseStorage] = None): if loop is None: loop = bot.loop if storage is None: @@ -45,9 +43,7 @@ class Dispatcher: self.shipping_query_handlers = Handler(self) self.pre_checkout_query_handlers = Handler(self) - self.next_step_message_handlers = NextStepHandler(self) self.updates_handler.register(self.process_update) - # self.message_handlers.register(self._notify_next_message) self._pooling = False @@ -90,8 +86,7 @@ class Dispatcher: """ self.last_update_id = update.update_id if update.message: - if not await self.next_step_message_handlers.notify(update.message): - await self.message_handlers.notify(update.message) + await self.message_handlers.notify(update.message) if update.edited_message: await self.edited_message_handlers.notify(update.edited_message) if update.channel_post: @@ -687,23 +682,6 @@ class Dispatcher: return decorator - @deprecated("Use FSM instead of next step message handler.") - async def next_message(self, message: types.Message, otherwise=None, once=False, include_cancel=True, - regexp=None, content_types=None, func=None, custom_filters=None, **kwargs): - if content_types is None: - content_types = [] - if custom_filters is None: - custom_filters = [] - - filters_set = generate_default_filters(self, - *custom_filters, - regexp=regexp, - content_types=content_types, - func=func, - **kwargs) - self.next_step_message_handlers.register(message, otherwise, once, include_cancel, filters_set) - return await self.next_step_message_handlers.wait(message) - def current_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None) -> FSMContext: diff --git a/aiogram/dispatcher/handler.py b/aiogram/dispatcher/handler.py index d0eb9b57..1cd5bb98 100644 --- a/aiogram/dispatcher/handler.py +++ b/aiogram/dispatcher/handler.py @@ -1,7 +1,4 @@ -from asyncio import Event - -from .filters import check_filters, CancelFilter -from .. import types +from .filters import check_filters class SkipHandler(BaseException): @@ -47,66 +44,3 @@ class Handler: continue except CancelHandler: break - - -class NextStepHandler: - def __init__(self, dispatcher): - self.dispatcher = dispatcher - self.handlers = {} - - def register(self, message, otherwise=None, once=False, include_cancel=False, filters=None): - identifier = gen_identifier(message.chat.id, message.chat.id) - - if identifier not in self.handlers: - self.handlers[identifier] = {'event': Event(), 'filters': filters, - 'otherwise': otherwise, 'once': once, - 'include_cancel': include_cancel, - 'message': None} - return True - - # In normal it's impossible. - raise RuntimeError('Dialog already wait message.') - # return False - - async def notify(self, message): - identifier = gen_identifier(message.chat.id, message.chat.id) - if identifier not in self.handlers: - return False - handler = self.handlers[identifier] - - include_cancel = handler['include_cancel'] - if include_cancel: - filter_ = CancelFilter(include_cancel if isinstance(include_cancel, (list, set, tuple)) else None) - if filter_.check(message): - handler['event'].set() - return True - - if handler['filters'] and not await check_filters(handler['filters'], [message], {}): - otherwise = handler['otherwise'] - if otherwise: - await otherwise(message) - if handler['once']: - handler['event'].set() - return True - - handler['message'] = message - handler['event'].set() - return True - - async def wait(self, message) -> types.Message: - identifier = gen_identifier(message.chat.id, message.chat.id) - - handler = self.handlers[identifier] - event = handler.get('event') - - await event.wait() - message = self.handlers[identifier]['message'] - self.reset(identifier) - return message - - def reset(self, identifier): - del self.handlers[identifier] - - -def gen_identifier(chat_id, from_user_id): - return "{0}:{1}".format(chat_id, from_user_id) diff --git a/aiogram/dispatcher/state.py b/aiogram/dispatcher/state.py deleted file mode 100644 index 45339ace..00000000 --- a/aiogram/dispatcher/state.py +++ /dev/null @@ -1,799 +0,0 @@ -import logging -import os - -from .handler import SkipHandler -from ..utils import json -from ..utils.deprecated import deprecated - -log = logging.getLogger('aiogram.StateMachine') - - -@deprecated -class BaseStorage: - """ - Skeleton for states storage - """ - - @staticmethod - def _prepare_state_name(value): - if callable(value): - if hasattr(value, '__name__'): - return value.__name__ - else: - return value.__class__.__name__ - return value - - def set_state(self, chat, user, state): - """ - Set state - - :param chat: chat_id - :param user: user_id - :param state: value - """ - raise NotImplementedError - - def get_state(self, chat, user): - """ - Get user state from - - :param chat: - :param user: - :return: - """ - raise NotImplementedError - - def del_state(self, chat, user): - """ - Clear user state - :param chat: cha - :param user: - :return: - """ - raise NotImplementedError - - def all_states(self, chat=None, user=None, state=None): - """ - Yield all states (Can use filters) - - :param chat: - :param user: - :param state: - :return: - """ - raise NotImplementedError - - def set_value(self, chat, user, key, value): - """ - Set value for user in storage - - :param chat: - :param user: - :param key: - :param value: - :return: - """ - raise NotImplementedError - - def get_value(self, chat, user, key, default=None): - """ - Get value from storage - - By default, this method calls `self.get_data(chat, user).get(key, default)` - :param chat: - :param user: - :param key: - :param default: - :return: - """ - return self.get_data(chat, user).get(key, default) - - def del_value(self, chat, user, key): - """ - Delete value from storage - - :param chat: - :param user: - :param key: - """ - raise NotImplementedError - - def get_data(self, chat, user): - """ - Get all stored data for user - - :param chat: - :param user: - :return: dict - """ - raise NotImplementedError - - def update_data(self, chat, user, data): - """ - Update data in storage - - :param chat: - :param user: - :param data: - :return: - """ - raise NotImplementedError - - def clear_data(self, chat, user, key): - """ - Clear data in storage - - :param chat: - :param user: - :param key: - :return: - """ - raise NotImplementedError - - -class BaseAsyncStorage(BaseStorage): - async def set_state(self, chat, user, state): - """ - Set state - - :param chat: chat_id - :param user: user_id - :param state: value - """ - raise NotImplementedError - - async def get_state(self, chat, user): - """ - Get user state from - - :param chat: - :param user: - :return: - """ - raise NotImplementedError - - async def del_state(self, chat, user): - """ - Clear user state - :param chat: cha - :param user: - :return: - """ - raise NotImplementedError - - async def all_states(self, chat=None, user=None, state=None): - """ - Yield all states (Can use filters) - - :param chat: - :param user: - :param state: - :return: - """ - raise NotImplementedError - - async def set_value(self, chat, user, key, value): - """ - Set value for user in storage - - :param chat: - :param user: - :param key: - :param value: - :return: - """ - raise NotImplementedError - - async def get_value(self, chat, user, key, default=None): - """ - Get value from storage - - By default, this method calls `(await self.get_data(chat, user)).get(key, default)` - :param chat: - :param user: - :param key: - :param default: - :return: - """ - return (await self.get_data(chat, user)).get(key, default) - - async def del_value(self, chat, user, key): - """ - Delete value from storage - - :param chat: - :param user: - :param key: - """ - raise NotImplementedError - - async def get_data(self, chat, user): - """ - Get all stored data for user - - :param chat: - :param user: - :return: dict - """ - raise NotImplementedError - - async def update_data(self, chat, user, data): - """ - Update data in storage - - :param chat: - :param user: - :param data: - :return: - """ - raise NotImplementedError - - async def clear_data(self, chat, user, key): - """ - Clear data in storage - - :param chat: - :param user: - :param key: - :return: - """ - raise NotImplementedError - - -class MemoryStorage(BaseStorage): - """ - Simple in-memory state storage - Based on builtin dict - """ - - def __init__(self, data=None): - if data is None: - data = {} - self.data = data - - def _prepare(self, chat, user): - """ - Add chat and user to storage if they are not exist - :param chat: - :param user: - :return: - """ - result = False - - chat = str(chat) - user = str(user) - - if chat not in self.data: - self.data[chat] = {} - result = True - - if user not in self.data[chat]: - self.data[chat][user] = {'state': None, 'data': {}} - result = True - - return result - - def set_state(self, chat, user, state): - chat = str(chat) - user = str(user) - - self._prepare(chat, user) - self.data[chat][user]['state'] = self._prepare_state_name(state) - - def get_state(self, chat, user): - chat = str(chat) - user = str(user) - - self._prepare(chat, user) - return self.data[chat][user]['state'] - - def del_state(self, chat, user): - chat = str(chat) - user = str(user) - - self._prepare(chat, user) - self.data[chat][user] = {'state': None, 'data': {}} - - def all_states(self, chat=None, user=None, state=None): - for chat_id, chat in self.data.items(): - if chat is not None and chat != chat_id: - continue - for user_id, user_state in chat.items(): - if user is not None and user != user_id: - continue - if state is not None and user_state == state: - continue - yield chat_id, user_id, user_state - - def set_value(self, chat, user, key, value): - chat = str(chat) - user = str(user) - - self._prepare(chat, user) - self.data[chat][user]['data'][key] = value - - def del_value(self, chat, user, key): - chat = str(chat) - user = str(user) - - self._prepare(chat, user) - del self.data[chat][user]['data'][key] - - def get_data(self, chat, user): - chat = str(chat) - user = str(user) - - self._prepare(chat, user) - return self.data[chat][user]['data'] - - def update_data(self, chat, user, data): - chat = str(chat) - user = str(user) - - self._prepare(chat, user) - self.data[chat][user]['data'].update(data) - - def clear_data(self, chat, user, key): - chat = str(chat) - user = str(user) - - self._prepare(chat, user) - self.data[chat][user]['data'].clear() - - -class FileStorage(MemoryStorage): - """ - File-like storage for states. - """ - - def __init__(self, filename): - self.filename = filename - super(FileStorage, self).__init__(self.load(filename)) - - @staticmethod - def load(filename): - """ - Load data from file - - :param filename: - :return: dict - """ - if os.path.isfile(filename): - with open(filename, 'r') as file: - return json.load(file) - return {} - - def save(self): - """ - Write states to file - - :return: - """ - with open(self.filename, 'w') as file: - json.dump(self.data, file, indent=2) - - def set_state(self, chat, user, state): - super(FileStorage, self).set_state(chat, user, state) - self.save() - - def del_state(self, chat, user): - super(FileStorage, self).del_state(chat, user) - self.save() - - def set_value(self, chat, user, key, value): - super(FileStorage, self).set_value(chat, user, key, value) - self.save() - - def del_value(self, chat, user, key): - super(FileStorage, self).del_value(chat, user, key) - self.save() - - def update_data(self, chat, user, data): - super(FileStorage, self).update_data(chat, user, data) - self.save() - - def clear_data(self, chat, user, key): - super(FileStorage, self).clear_data(chat, user, key) - self.save() - - -@deprecated -class Controller: - """ - Storage controller - - Make easy access from callback's - """ - - def __init__(self, state_machine, chat, user, state): - self._state_machine = state_machine - self._chat = chat - self._user = user - self._state = state - - def set_state(self, value): - """ - Set state - - :param value: - :return: - """ - self._state_machine.set_state(self._chat, self._user, value) - - def get_state(self): - """ - Get current state - - :return: - """ - return self._state_machine.get_state(self._chat, self._user) - - def clear(self): - """ - Reset state - - :return: - """ - self._state_machine.del_state(self._chat, self._user) - - def get(self, key, default=None): - """ - Get value from storage - - :param key: - :param default: - :return: - """ - return self._state_machine.storage.get_value(self._chat, self._user, key, default) - - def pop(self, key, default=None): - """ - Pop item from storage - - :param key: - :param default: - :return: - """ - result = self.get(key, default) - self.delete(key) - return result - - def set(self, key, value): - """ - Set new value in user storage - - :param key: - :param value: - :return: - """ - self._state_machine.storage.set_value(self._chat, self._user, key, value) - - def delete(self, key): - """ - Delete key from user storage - - :param key: - :return: - """ - self._state_machine.storage.del_value(self._chat, self._user, key) - - def update(self, data): - """ - Update user storage - - :param data: - :return: - """ - self._state_machine.storage.update_data(self._chat, self._user, data) - - @property - def data(self): - """ - User data - :return: - """ - return self._state_machine.storage.get_value - - def __setitem__(self, key, value): - self.set(key, value) - - def __getitem__(self, item): - return self.get(item) - - def __delitem__(self, key): - self.delete(key) - - def __str__(self): - return "{0}:{1} - {2}".format( - self._chat, self._user, self._state - ) - - -@deprecated -class AsyncController: - """ - Storage controller - - Make easy access from callback's - """ - - def __init__(self, state_machine, chat, user, state): - self._state_machine = state_machine - self._chat = chat - self._user = user - self._state = state - - async def set_state(self, value): - """ - Set state - - :param value: - :return: - """ - await self._state_machine.set_state(self._chat, self._user, value) - - async def get_state(self): - """ - Get current state - - :return: - """ - return await self._state_machine.get_state(self._chat, self._user) - - async def clear(self): - """ - Reset state - - :return: - """ - await self._state_machine.del_state(self._chat, self._user) - - async def get(self, key, default=None): - """ - Get value from storage - - :param key: - :param default: - :return: - """ - return await self._state_machine.storage.get_value(self._chat, self._user, key, default) - - async def pop(self, key, default=None): - """ - Pop item from storage - - :param key: - :param default: - :return: - """ - result = await self.get(key, default) - await self.delete(key) - return result - - async def set(self, key, value): - """ - Set new value in user storage - - :param key: - :param value: - :return: - """ - await self._state_machine.storage.set_value(self._chat, self._user, key, value) - - async def delete(self, key): - """ - Delete key from user storage - - :param key: - :return: - """ - await self._state_machine.storage.del_value(self._chat, self._user, key) - - async def update(self, data): - """ - Update user storage - - :param data: - :return: - """ - await self._state_machine.storage.update_data(self._chat, self._user, data) - - @property - async def data(self): - """ - User data - - :return: - """ - return await self._state_machine.storage.get_data(self._chat, self._user) - - def __setitem__(self, key, value): - raise RuntimeError("Item assignment not allowed with async storage") - - def __getitem__(self, item): - raise RuntimeError("Item assignment not allowed with async storage") - - def __delitem__(self, key): - raise RuntimeError("Item assignment not allowed with async storage") - - def __str__(self): - return "{0}:{1} - {2}".format( - self._chat, self._user, self._state - ) - - -@deprecated('Use new FSM builded inside Dispatcher.') -class StateMachine: - """ - Manage state - """ - - def __init__(self, dispatcher, states, storage=None): - if storage is None: - storage = MemoryStorage() - - self.steps = self._prepare_states(states) - self.storage = storage - - dispatcher.message_handlers.register(self.process_message, index=0) - - @staticmethod - def _prepare_states(states): - if isinstance(states, dict): - return states - elif isinstance(states, (list, tuple, set)): - prepared_states = {} - for state in states: - if not callable(state): - raise TypeError('State must be an callable') - state_name = state.__name__ - prepared_states[state_name] = state - return prepared_states - raise TypeError('States must be an dict or list!') - - def set_state(self, chat, user, state): - """ - Save state to storage - :param chat: - :param user: - :param state: - :return: - """ - log.debug("Set state for {0}:{1} to '{2}'".format( - chat, user, state - )) - self.storage.set_state(chat, user, state) - - def get_state(self, chat, user): - """ - Get state from storage - :param chat: - :param user: - :return: - """ - return self.storage.get_state(chat, user) - - def del_state(self, chat, user): - """ - Clear user state - :param chat: - :param user: - :return: - """ - log.debug("Reset state for {0}:{1}".format(chat, user)) - self.storage.del_state(chat, user) - - async def process_message(self, message): - """ - Read message and process it - :param message: - :return: - """ - chat_id = message.chat.id - from_user_id = message.from_user.id - - state = self.get_state(chat_id, from_user_id) - if state is None: - raise SkipHandler() - - if state not in self.steps: - log.warning("Found unknown state '{0}' for {1}:{2}. Condition will be reset.".format( - state, chat_id, from_user_id - )) - self.del_state(chat_id, from_user_id) - raise SkipHandler() - - log.debug("Process state for {0}:{1} - '{2}'".format( - chat_id, from_user_id, state - )) - callback = self.steps[state] - controller = Controller(self, chat_id, from_user_id, state) - await callback(message, controller) - - -@deprecated('Use new FSM builded inside Dispatcher.') -class AsyncStateMachine: - """ - Manage state - """ - - def __init__(self, dispatcher, states, storage=None): - assert isinstance(storage, BaseAsyncStorage) - - self.steps = self._prepare_states(states) - self.storage = storage - - dispatcher.message_handlers.register(self.process_message, index=0) - - @staticmethod - def _prepare_states(states): - if isinstance(states, dict): - return states - elif isinstance(states, (list, tuple, set)): - prepared_states = {} - for state in states: - if not callable(state): - raise TypeError('State must be an callable') - state_name = state.__name__ - prepared_states[state_name] = state - return prepared_states - raise TypeError('States must be an dict or list!') - - async def set_state(self, chat, user, state): - """ - Save state to storage - :param chat: - :param user: - :param state: - :return: - """ - log.debug("Set state for {0}:{1} to '{2}'".format( - chat, user, state - )) - await self.storage.set_state(chat, user, state) - - async def get_state(self, chat, user): - """ - Get state from storage - :param chat: - :param user: - :return: - """ - return await self.storage.get_state(chat, user) - - async def del_state(self, chat, user): - """ - Clear user state - :param chat: - :param user: - :return: - """ - log.debug("Reset state for {0}:{1}".format(chat, user)) - await self.storage.del_state(chat, user) - - async def process_message(self, message): - """ - Read message and process it - :param message: - :return: - """ - chat_id = message.chat.id - from_user_id = message.from_user.id - - state = await self.get_state(chat_id, from_user_id) - if state is None: - raise SkipHandler() - - if state not in self.steps: - log.warning("Found unknown state '{0}' for {1}:{2}. Condition will be reset.".format( - state, chat_id, from_user_id - )) - await self.del_state(chat_id, from_user_id) - raise SkipHandler() - - log.debug("Process state for {0}:{1} - '{2}'".format( - chat_id, from_user_id, state - )) - callback = self.steps[state] - controller = AsyncController(self, chat_id, from_user_id, state) - await callback(message, controller) diff --git a/examples/deprecated_state_machine.py b/examples/deprecated_state_machine.py deleted file mode 100644 index c2caae83..00000000 --- a/examples/deprecated_state_machine.py +++ /dev/null @@ -1,91 +0,0 @@ -import asyncio - -from aiogram import Bot, types -from aiogram.dispatcher import Dispatcher -from aiogram.dispatcher.state import StateMachine, Controller - -API_TOKEN = 'BOT TOKEN HERE' - -loop = asyncio.get_event_loop() -bot = Bot(token=API_TOKEN, loop=loop) -dp = Dispatcher(bot) - - -@dp.message_handler(commands=['start']) -async def send_welcome(message: types.Message): - """ - Entry point to conversation - """ - await message.reply("Hi there! What's your name?") - - # Set state - # This method lock all messages from user and all messages will be redirected to next step handler in state machine - state.set_state(message.chat.id, message.from_user.id, "name") - - -async def process_name(message: types.Message, controller: Controller): - """ - Process user name - """ - # Save name to storage - controller["name"] = message.text - - await message.reply("How old are you?") - - # Go to next state - controller.set_state('age') - - -async def process_age(message: types.Message, controller: Controller): - # Check age. Age must be is digit - if not message.text.isdigit(): - return await message.reply("Age should be a number.\nHow old are you?") - - # Save age to storage - controller["age"] = int(message.text) - - # Configure ReplyKeyboardMarkup - markup = types.ReplyKeyboardMarkup() - markup.add("Male", "Female") - markup.add("Other") - - await message.reply("What is your gender?", reply_markup=markup) - - # Go to next state - controller.set_state("sex") - - -async def process_sex(message: types.Message, controller: Controller): - # Check reply - if message.text not in ["Male", "Female", "Other"]: - return await message.reply("Bad gender name. Choose you gender from keyboard.") - - # Save sex to storage - controller["sex"] = message.text - - # Remove keyboard - markup = types.ReplyKeyboardRemove() - # And send message - await bot.send_message(message.chat.id, - f"Hi!\n" - f"Nice to meet you, {controller['name']}.\n" - f"Age: {controller['age']}\n" - f"Sex: {controller['sex']}", - reply_markup=markup) - - # Finish conversation - controller.clear() - - -# Configure state machine -state = StateMachine(dp, { - "name": process_name, - "age": process_age, - "sex": process_sex -}) - -if __name__ == '__main__': - try: - loop.run_until_complete(dp.start_pooling()) - except KeyboardInterrupt: - loop.stop() diff --git a/examples/deprecated_steps_and_downloading_file.py b/examples/deprecated_steps_and_downloading_file.py deleted file mode 100644 index 63f38777..00000000 --- a/examples/deprecated_steps_and_downloading_file.py +++ /dev/null @@ -1,71 +0,0 @@ -import asyncio -import logging - -from aiogram import Bot, types -from aiogram.dispatcher import Dispatcher -from aiogram.types import ContentType - -API_TOKEN = TOKEN = 'BOT TOKEN HERE' - -logging.basicConfig(level=logging.INFO) - -loop = asyncio.get_event_loop() -bot = Bot(token=API_TOKEN, loop=loop) -dp = Dispatcher(bot) - - -@dp.message_handler(commands=['start']) -async def send_welcome(message: types.Message): - await message.reply("Hi!\nI'm EchoBot!\nPowered by aiogram.") - - -@dp.message_handler(commands=['sticker']) -async def save_sticker(message: types.Message): - async def handle_bad_message(msg: types.Message): - """ - Handler for unknown messages - """ - await msg.reply('That is not a sticker!') - - # Create an reply markup (ForceReply) - markup = types.ForceReply(selective=True) - - # Send reply to user - await message.reply('Please send me a sticker.', reply_markup=markup) - - # Wait next message - # It can only be a sticker - msg = await dp.next_message(message, - content_types=ContentType.STICKER, - otherwise=handle_bad_message, - include_cancel=True) - - if not msg: - # If user send /cancel - return await message.reply('Canceled.') - - # Download file to memory (io.BytesIO) - photo = await bot.download_file_by_id(msg.sticker.file_id) - - # And you can use other syntax: - # photo = io.BytesIO() - # await bot.download_file(msg.sticker.file_id, photo) - # Or use filename for download file to filesystem: - # await bot.download_file(msg.sticker.file_id, 'sticker.webp') - - # Send document to user - await bot.send_document(message.chat.id, photo, caption=msg.sticker.emoji, - reply_to_message_id=message.message_id) - - -async def main(): - count = await dp.skip_updates() - print(f"Skipped {count} updates.") - await dp.start_pooling() - - -if __name__ == '__main__': - try: - loop.run_until_complete(main()) - except KeyboardInterrupt: - loop.stop()