From 07c7c1f32a513544284f9c74e1fbf25e75e9585d Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Fri, 2 Jun 2017 03:01:28 +0300 Subject: [PATCH] Refactoring. --- aiogram/bot.py | 181 +++++++++++++------------- aiogram/exceptions.py | 2 +- aiogram/types/__init__.py | 180 +++++++++++-------------- aiogram/types/animation.py | 2 +- aiogram/types/audio.py | 2 +- aiogram/types/base.py | 107 +++++++++++++++ aiogram/types/callback_query.py | 2 +- aiogram/types/chat.py | 2 +- aiogram/types/chat_member.py | 2 +- aiogram/types/chosen_inline_result.py | 2 +- aiogram/types/contact.py | 2 +- aiogram/types/document.py | 2 +- aiogram/types/file.py | 2 +- aiogram/types/force_reply.py | 2 +- aiogram/types/game.py | 2 +- aiogram/types/game_high_score.py | 2 +- aiogram/types/inline_keyboard.py | 2 +- aiogram/types/inline_query.py | 2 +- aiogram/types/invoice.py | 2 +- aiogram/types/location.py | 2 +- aiogram/types/message.py | 4 +- aiogram/types/message_entity.py | 2 +- aiogram/types/order_info.py | 2 +- aiogram/types/photo_size.py | 2 +- aiogram/types/pre_checkout_query.py | 2 +- aiogram/types/reply_keyboard.py | 2 +- aiogram/types/shipping_address.py | 2 +- aiogram/types/shipping_query.py | 2 +- aiogram/types/sticker.py | 2 +- aiogram/types/successful_payment.py | 2 +- aiogram/types/update.py | 12 +- aiogram/types/user.py | 2 +- aiogram/types/user_profile_photos.py | 2 +- aiogram/types/venue.py | 2 +- aiogram/types/video.py | 2 +- aiogram/types/video_note.py | 2 +- aiogram/types/voice.py | 2 +- aiogram/types/webhook_info.py | 8 +- 38 files changed, 313 insertions(+), 243 deletions(-) create mode 100644 aiogram/types/base.py diff --git a/aiogram/bot.py b/aiogram/bot.py index 8cdc962c..80f54808 100644 --- a/aiogram/bot.py +++ b/aiogram/bot.py @@ -4,15 +4,7 @@ import json import aiohttp from . import api -from .api import ApiMethods -from .types.chat import Chat -from .types.chat_member import ChatMember -from .types.file import File -from .types.message import Message -from .types.update import Update -from .types.user import User -from .types.user_profile_photos import UserProfilePhotos -from .types.webhook_info import WebhookInfo +from . import types from .utils.payload import generate_payload @@ -45,7 +37,7 @@ class AIOGramBot: return obj @property - async def me(self) -> User: + async def me(self) -> types.User: if not hasattr(self, '_me'): setattr(self, '_me', await self.get_me()) return getattr(self, '_me') @@ -55,13 +47,13 @@ class AIOGramBot: async def _send_file(self, file_type, file, payload): methods = { - 'photo': ApiMethods.SEND_PHOTO, - 'audio': ApiMethods.SEND_AUDIO, - 'document': ApiMethods.SEND_DOCUMENT, - 'sticker': ApiMethods.SEND_STICKER, - 'video': ApiMethods.SEND_VIDEO, - 'voice': ApiMethods.SEND_VOICE, - 'video_note': ApiMethods.SEND_VIDEO_NOTE + 'photo': api.ApiMethods.SEND_PHOTO, + 'audio': api.ApiMethods.SEND_AUDIO, + 'document': api.ApiMethods.SEND_DOCUMENT, + 'sticker': api.ApiMethods.SEND_STICKER, + 'video': api.ApiMethods.SEND_VIDEO, + 'voice': api.ApiMethods.SEND_VOICE, + 'video_note': api.ApiMethods.SEND_VIDEO_NOTE } method = methods[file_type] @@ -70,41 +62,41 @@ class AIOGramBot: req = self.request(method, payload) else: data = {file_type: file} - req = self.request(ApiMethods.SEND_PHOTO, payload, data) + req = self.request(api.ApiMethods.SEND_PHOTO, payload, data) - return self.prepare_object(Message.de_json(await req)) + return self.prepare_object(types.Message.de_json(await req)) - async def get_me(self) -> User: - raw = await self.request(ApiMethods.GET_ME) - return self.prepare_object(User.de_json(raw)) + async def get_me(self) -> types.User: + raw = await self.request(api.ApiMethods.GET_ME) + return self.prepare_object(types.User.de_json(raw)) - async def get_updates(self, offset=None, limit=None, timeout=None, allowed_updates=None): + async def get_updates(self, offset=None, limit=None, timeout=None, allowed_updates=None) -> [types.Update]: payload = generate_payload(**locals()) - raw = await self.request(ApiMethods.GET_UPDATES, payload) - return [self.prepare_object(Update.de_json(raw_update)) for raw_update in raw] + raw = await self.request(api.ApiMethods.GET_UPDATES, payload) + return [self.prepare_object(types.Update.de_json(raw_update)) for raw_update in raw] - async def set_webhook(self, url, certificate=None, max_connections=None, allowed_updates=None): + async def set_webhook(self, url, certificate=None, max_connections=None, allowed_updates=None) -> types.WebhookInfo: payload = generate_payload(**locals(), exclude='certificate') if certificate: cert = {'certificate': certificate} - req = self.request(ApiMethods.SET_WEBHOOK, payload, cert) + req = self.request(api.ApiMethods.SET_WEBHOOK, payload, cert) else: - req = self.request(ApiMethods.SET_WEBHOOK, payload) + req = self.request(api.ApiMethods.SET_WEBHOOK, payload) - return self.prepare_object(WebhookInfo.de_json(await req)) + return self.prepare_object(types.WebhookInfo.de_json(await req)) - async def delete_webhook(self): + async def delete_webhook(self) -> bool: payload = {} - await self.request(ApiMethods.DELETE_WEBHOOK, payload) + await self.request(api.ApiMethods.DELETE_WEBHOOK, payload) return True - async def get_webhook_info(self): + async def get_webhook_info(self) -> types.WebhookInfo: payload = {} - webhook_info = await self.request(ApiMethods.GET_WEBHOOK_INFO, payload) + webhook_info = await self.request(api.ApiMethods.GET_WEBHOOK_INFO, payload) return self.prepare_object(webhook_info) async def send_message(self, chat_id, text, parse_mode=None, disable_web_page_preview=None, - disable_notification=None, reply_to_message_id=None, reply_markup=None): + disable_notification=None, reply_to_message_id=None, reply_markup=None) -> types.Message: if reply_markup and hasattr(reply_markup, 'to_json'): reply_markup = json.dumps(reply_markup.to_json()) @@ -112,16 +104,16 @@ class AIOGramBot: reply_to_message_id = reply_to_message_id.message_id payload = generate_payload(**locals()) - message = await self.request(ApiMethods.SEND_MESSAGE, payload) - return self.prepare_object(Message.de_json(message)) + message = await self.request(api.ApiMethods.SEND_MESSAGE, payload) + return self.prepare_object(types.Message.de_json(message)) - async def forward_message(self, chat_id, from_chat_id, message_id, disable_notification=None): + async def forward_message(self, chat_id, from_chat_id, message_id, disable_notification=None) -> types.Message: payload = generate_payload(**locals()) - message = await self.request(ApiMethods.FORWARD_MESSAGE, payload) - return self.prepare_object(Message.de_json(message)) + message = await self.request(api.ApiMethods.FORWARD_MESSAGE, payload) + return self.prepare_object(types.Message.de_json(message)) async def send_photo(self, chat_id, photo, caption=None, disable_notification=None, reply_to_message_id=None, - reply_markup=None) -> Message: + reply_markup=None) -> types.Message: _message_type = 'photo' if reply_markup and hasattr(reply_markup, 'to_json'): reply_markup = json.dumps(reply_markup.to_json()) @@ -133,7 +125,7 @@ class AIOGramBot: return await self._send_file(_message_type, photo, payload) async def send_audio(self, chat_id, audio, caption=None, duration=None, performer=None, title=None, - disable_notification=None, reply_to_message_id=None, reply_markup=None) -> Message: + disable_notification=None, reply_to_message_id=None, reply_markup=None) -> types.Message: _message_type = 'audio' if reply_markup and hasattr(reply_markup, 'to_json'): reply_markup = json.dumps(reply_markup.to_json()) @@ -145,7 +137,7 @@ class AIOGramBot: return await self._send_file(_message_type, audio, payload) async def send_document(self, chat_id, document, caption=None, disable_notification=None, reply_to_message_id=None, - reply_markup=None): + reply_markup=None) -> types.Message: _message_type = 'document' if reply_markup and hasattr(reply_markup, 'to_json'): reply_markup = json.dumps(reply_markup.to_json()) @@ -157,7 +149,7 @@ class AIOGramBot: return await self._send_file(_message_type, document, payload) async def send_sticker(self, chat_id, sticker, disable_notification=None, reply_to_message_id=None, - reply_markup=None) -> Message: + reply_markup=None) -> types.Message: _METHOD = 'sticker' if reply_markup and hasattr(reply_markup, 'to_json'): reply_markup = json.dumps(reply_markup.to_json()) @@ -169,7 +161,7 @@ class AIOGramBot: return await self._send_file(_METHOD, sticker, payload) async def send_video(self, chat_id, video, duration=None, width=None, height=None, caption=None, - disable_notification=None, reply_to_message_id=None, reply_markup=None) -> Message: + disable_notification=None, reply_to_message_id=None, reply_markup=None) -> types.Message: _message_type = 'video' if reply_markup and hasattr(reply_markup, 'to_json'): reply_markup = json.dumps(reply_markup.to_json()) @@ -181,7 +173,7 @@ class AIOGramBot: return await self._send_file(_message_type, video, payload) async def send_voice(self, chat_id, voice, caption=None, duration=None, disable_notification=None, - reply_to_message_id=None, reply_markup=None) -> Message: + reply_to_message_id=None, reply_markup=None) -> types.Message: _message_type = 'voice' if reply_markup and hasattr(reply_markup, 'to_json'): reply_markup = json.dumps(reply_markup.to_json()) @@ -193,7 +185,7 @@ class AIOGramBot: return await self._send_file(_message_type, voice, payload) async def send_video_note(self, chat_id, video_note, duration=None, length=None, disable_notification=None, - reply_to_message_id=None, reply_markup=None) -> Message: + reply_to_message_id=None, reply_markup=None) -> types.Message: _message_type = 'video_note' if reply_markup and hasattr(reply_markup, 'to_json'): reply_markup = json.dumps(reply_markup.to_json()) @@ -205,7 +197,7 @@ class AIOGramBot: return await self._send_file(_message_type, video_note, payload) async def send_location(self, chat_id, latitude, longitude, disable_notification=None, reply_to_message_id=None, - reply_markup=None): + reply_markup=None) -> types.Message: if reply_markup and hasattr(reply_markup, 'to_json'): reply_markup = json.dumps(reply_markup.to_json()) @@ -213,11 +205,11 @@ class AIOGramBot: reply_to_message_id = reply_to_message_id.message_id payload = generate_payload(**locals()) - message = await self.request(ApiMethods.SEND_LOCATION, payload) - return self.prepare_object(Message.de_json(message)) + message = await self.request(api.ApiMethods.SEND_LOCATION, payload) + return self.prepare_object(types.Message.de_json(message)) async def send_venue(self, chat_id, latitude, longitude, title, address, foursquare_id, disable_notification=None, - reply_to_message_id=None, reply_markup=None): + reply_to_message_id=None, reply_markup=None) -> types.Message: if reply_markup and hasattr(reply_markup, 'to_json'): reply_markup = json.dumps(reply_markup.to_json()) @@ -225,11 +217,11 @@ class AIOGramBot: reply_to_message_id = reply_to_message_id.message_id payload = generate_payload(**locals()) - message = await self.request(ApiMethods.SEND_VENUE, payload) - return self.prepare_object(Message.de_json(message)) + message = await self.request(api.ApiMethods.SEND_VENUE, payload) + return self.prepare_object(types.Message.de_json(message)) async def send_contact(self, chat_id, phone_number, first_name, last_name=None, disable_notification=None, - reply_to_message_id=None, reply_markup=None): + reply_to_message_id=None, reply_markup=None) -> types.Message: if reply_markup and hasattr(reply_markup, 'to_json'): reply_markup = json.dumps(reply_markup.to_json()) @@ -237,61 +229,61 @@ class AIOGramBot: reply_to_message_id = reply_to_message_id.message_id payload = generate_payload(**locals()) - message = await self.request(ApiMethods.SEND_CONTACT, payload) - return self.prepare_object(Message.de_json(message)) + message = await self.request(api.ApiMethods.SEND_CONTACT, payload) + return self.prepare_object(types.Message.de_json(message)) - async def send_chat_action(self, chat_id, action): + async def send_chat_action(self, chat_id, action) -> bool: payload = generate_payload(**locals()) - message = await self.request(ApiMethods.SEND_CHAT_ACTION, payload) - return self.prepare_object(Message.de_json(message)) + return await self.request(api.ApiMethods.SEND_CHAT_ACTION, payload) - async def get_user_profile_photos(self, user_id, offset=None, limit=None): + async def get_user_profile_photos(self, user_id, offset=None, limit=None) -> types.UserProfilePhotos: payload = generate_payload(**locals()) - message = await self.request(ApiMethods.GET_USER_PROFILE_PHOTOS, payload) - return self.prepare_object(UserProfilePhotos.de_json(message)) + message = await self.request(api.ApiMethods.GET_USER_PROFILE_PHOTOS, payload) + return self.prepare_object(types.UserProfilePhotos.de_json(message)) - async def get_file(self, file_id): + async def get_file(self, file_id) -> types.File: payload = generate_payload(**locals()) - message = await self.request(ApiMethods.GET_FILE, payload) - return self.prepare_object(File.de_json(message)) + message = await self.request(api.ApiMethods.GET_FILE, payload) + return self.prepare_object(types.File.de_json(message)) - async def kick_chat_user(self, chat_id, user_id): + async def kick_chat_user(self, chat_id, user_id) -> bool: payload = generate_payload(**locals()) - return await self.request(ApiMethods.KICK_CHAT_MEMBER, payload) + return await self.request(api.ApiMethods.KICK_CHAT_MEMBER, payload) - async def unban_chat_member(self, chat_id, user_id): + async def unban_chat_member(self, chat_id, user_id) -> bool: payload = generate_payload(**locals()) - return await self.request(ApiMethods.UNBAN_CHAT_MEMBER, payload) + return await self.request(api.ApiMethods.UNBAN_CHAT_MEMBER, payload) - async def leave_chat(self, chat_id): + async def leave_chat(self, chat_id) -> bool: payload = generate_payload(**locals()) - return await self.request(ApiMethods.LEAVE_CHAT, payload) + return await self.request(api.ApiMethods.LEAVE_CHAT, payload) - async def get_chat(self, chat_id) -> Chat: + async def get_chat(self, chat_id) -> types.Chat: payload = generate_payload(**locals()) - raw = await self.request(ApiMethods.GET_CHAT, payload) - return self.prepare_object(Chat.de_json(raw)) + raw = await self.request(api.ApiMethods.GET_CHAT, payload) + return self.prepare_object(types.Chat.de_json(raw)) - async def get_chat_administrators(self, chat_id): + async def get_chat_administrators(self, chat_id) -> [types.ChatMember]: payload = generate_payload(**locals()) - raw = await self.request(ApiMethods.GET_CHAT_ADMINISTRATORS, payload) - return [self.prepare_object(ChatMember.de_json(raw_chat_member)) for raw_chat_member in raw] + raw = await self.request(api.ApiMethods.GET_CHAT_ADMINISTRATORS, payload) + return [self.prepare_object(types.ChatMember.de_json(raw_chat_member)) for raw_chat_member in raw] - async def get_chat_members_count(self, chat_id): + async def get_chat_members_count(self, chat_id) -> int: payload = generate_payload(**locals()) - return await self.request(ApiMethods.GET_CHAT_MEMBERS_COUNT, payload) + return await self.request(api.ApiMethods.GET_CHAT_MEMBERS_COUNT, payload) - async def get_chat_member(self, chat_id, user_id): + async def get_chat_member(self, chat_id, user_id) -> types.ChatMember: payload = generate_payload(**locals()) - raw = await self.request(ApiMethods.GET_CHAT_MEMBER, payload) - return self.prepare_object(ChatMember.de_json(raw)) + raw = await self.request(api.ApiMethods.GET_CHAT_MEMBER, payload) + return self.prepare_object(types.ChatMember.de_json(raw)) - async def answer_callback_query(self, callback_query_id, text=None, show_alert=None, url=None, cache_time=None): + async def answer_callback_query(self, callback_query_id, text=None, show_alert=None, url=None, + cache_time=None) -> bool: payload = generate_payload(**locals()) - return await self.request(ApiMethods.LEAVE_CHAT, payload) + return await self.request(api.ApiMethods.LEAVE_CHAT, payload) async def edit_message_text(self, text, chat_id=None, message_id=None, inline_message_id=None, parse_mode=None, - disable_web_page_preview=None, reply_markup=None): + disable_web_page_preview=None, reply_markup=None) -> types.Message or bool: if reply_markup and hasattr(reply_markup, 'to_json'): reply_markup = json.dumps(reply_markup.to_json()) @@ -302,13 +294,13 @@ class AIOGramBot: inline_message_id = inline_message_id.message_id payload = generate_payload(**locals()) - raw = await self.request(ApiMethods.EDIT_MESSAGE_TEXT, payload) + raw = await self.request(api.ApiMethods.EDIT_MESSAGE_TEXT, payload) if raw is True: return raw - return self.prepare_object(Message.de_json(raw)) + return self.prepare_object(types.Message.de_json(raw)) async def edit_message_caption(self, chat_id=None, message_id=None, inline_message_id=None, caption=None, - reply_markup=None): + reply_markup=None) -> types.Message or bool: if reply_markup and hasattr(reply_markup, 'to_json'): reply_markup = json.dumps(reply_markup.to_json()) @@ -319,12 +311,13 @@ class AIOGramBot: inline_message_id = inline_message_id.message_id payload = generate_payload(**locals()) - raw = await self.request(ApiMethods.EDIT_MESSAGE_TEXT, payload) + raw = await self.request(api.ApiMethods.EDIT_MESSAGE_TEXT, payload) if raw is True: return raw - return self.prepare_object(Message.de_json(raw)) + return self.prepare_object(types.Message.de_json(raw)) - async def edit_message_reply_markup(self, chat_id=None, message_id=None, inline_message_id=None, reply_markup=None): + async def edit_message_reply_markup(self, chat_id=None, message_id=None, inline_message_id=None, + reply_markup=None) -> types.Message or bool: if reply_markup and hasattr(reply_markup, 'to_json'): reply_markup = json.dumps(reply_markup.to_json()) @@ -335,13 +328,13 @@ class AIOGramBot: inline_message_id = inline_message_id.message_id payload = generate_payload(**locals()) - raw = await self.request(ApiMethods.EDIT_MESSAGE_TEXT, payload) + raw = await self.request(api.ApiMethods.EDIT_MESSAGE_TEXT, payload) if raw is True: return raw - return self.prepare_object(Message.de_json(raw)) + return self.prepare_object(types.Message.de_json(raw)) - async def delete_message(self, chat_id, message_id): + async def delete_message(self, chat_id, message_id) -> bool: payload = generate_payload(**locals()) - await self.request(ApiMethods.DELETE_MESSAGE, payload) + await self.request(api.ApiMethods.DELETE_MESSAGE, payload) return True diff --git a/aiogram/exceptions.py b/aiogram/exceptions.py index 08d205cc..bade4446 100644 --- a/aiogram/exceptions.py +++ b/aiogram/exceptions.py @@ -5,7 +5,7 @@ class ValidationError(Exception): class TelegramAPIError(Exception): def __init__(self, message, method, status, body): super(TelegramAPIError, self).__init__( - f"A request to the Telegram API was unsuccessful (Status code: {status}). {message}") + f"A request to the Telegram API was unsuccessful.\n{message}") self.method = method self.status = status self.body = body diff --git a/aiogram/types/__init__.py b/aiogram/types/__init__.py index 37206d1b..3297705a 100644 --- a/aiogram/types/__init__.py +++ b/aiogram/types/__init__.py @@ -1,107 +1,75 @@ -import datetime -import json -import time +from .animation import Animation +from .audio import Audio +from .callback_query import CallbackQuery +from .chat import Chat +from .chat_member import ChatMember +from .chosen_inline_result import ChosenInlineResult +from .contact import Contact +from .document import Document +from .file import File +from .force_reply import ForceReply +from .game import Game +from .game_high_score import GameHighScore +from .inline_keyboard import InlineKeyboardButton, InlineKeyboardMarkup +from .inline_query import InlineQuery +from .invoice import Invoice +from .location import Location +from .message import Message +from .message_entity import MessageEntity +from .order_info import OrderInfo +from .photo_size import PhotoSize +from .pre_checkout_query import PreCheckoutQuery +from .reply_keyboard import KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove +from .shipping_address import ShippingAddress +from .shipping_query import ShippingQuery +from .sticker import Sticker +from .successful_payment import SuccessfulPayment +from .update import Update +from .user import User +from .user_profile_photos import UserProfilePhotos +from .venue import Venue +from .video import Video +from .video_note import VideoNote +from .voice import Voice +from .webhook_info import WebhookInfo - -def deserialize(deserializable, data): - if data: - return deserializable.de_json(data) - - -def deserialize_array(deserializable, array): - if array: - return [deserialize(deserializable, item) for item in array] - - -class Serializable: - def to_json(self): - """ - Returns a JSON string representation of this class. - - This function must be overridden by subclasses. - :return: a JSON formatted string. - """ - raise NotImplementedError - - -class Deserializable: - """ - Subclasses of this class are guaranteed to be able to be created from a json-style dict or json formatted string. - All subclasses of this class must override de_json. - """ - - def to_json(self): - result = {} - for name, attr in self.__dict__.items(): - if not attr or name == '_bot': - continue - if hasattr(attr, 'to_json'): - attr = getattr(attr, 'to_json')() - elif isinstance(attr, datetime.datetime): - attr = int(time.mktime(attr.timetuple())) - result[name] = attr - return result - - @property - def bot(self): - if not hasattr(self, '_bot'): - raise AttributeError(f"{self.__class__.__name__} is not configured.") - return getattr(self, '_bot') - - @bot.setter - def bot(self, bot): - setattr(self, '_bot', bot) - for name, attr in self.__dict__.items(): - if hasattr(attr, 'de_json'): - attr.bot = bot - - @property - def parent(self): - return getattr(self, '_parent', None) - - @parent.setter - def parent(self, value): - setattr(self, '_parent', value) - for name, attr in self.__dict__.items(): - if name.startswith('_'): - continue - if hasattr(attr, 'de_json'): - attr.parent = self - - @classmethod - def de_json(cls, raw_data): - """ - Returns an instance of this class from the given json dict or string. - - This function must be overridden by subclasses. - :return: an instance of this class created from the given json dict or string. - """ - raise NotImplementedError - - @staticmethod - def check_json(raw_data) -> dict: - """ - Checks whether json_type is a dict or a string. If it is already a dict, it is returned as-is. - If it is not, it is converted to a dict by means of json.loads(json_type) - :param raw_data: - :return: - """ - - if isinstance(raw_data, dict): - return raw_data - elif isinstance(raw_data, str): - return json.loads(raw_data) - else: - raise ValueError("data should be a json dict or string.") - - def __str__(self): - return json.dumps(self.to_json()) - - def __repr__(self): - return str(self) - - @classmethod - def deserialize(cls, obj): - if isinstance(obj, list): - return deserialize_array(cls, obj) - return deserialize(cls, obj) +__all__ = [ + 'Animation', + 'Audio', + 'Base', + 'CallbackQuery', + 'Chat', + 'ChatMember', + 'ChosenInlineResult', + 'Contact', + 'Document', + 'File', + 'ForceReply', + 'Game', + 'GameHighScore', + 'InlineKeyboardButton', + 'InlineKeyboardMarkup', + 'InlineQuery', + 'Invoice', + 'Location', + 'Message', + 'MessageEntity', + 'OrderInfo', + 'PhotoSize', + 'PreCheckoutQuery', + 'KeyboardButton', + 'ReplyKeyboardMarkup', + 'ReplyKeyboardRemove', + 'ShippingAddress', + 'ShippingQuery', + 'Sticker', + 'SuccessfulPayment', + 'Update', + 'User', + 'UserProfilePhotos', + 'Venue', + 'Video', + 'VideoNote', + 'Voice', + 'WebhookInfo' +] diff --git a/aiogram/types/animation.py b/aiogram/types/animation.py index 3e2becc0..7233e2a2 100644 --- a/aiogram/types/animation.py +++ b/aiogram/types/animation.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable from .photo_size import PhotoSize diff --git a/aiogram/types/audio.py b/aiogram/types/audio.py index 2f1f7abf..97c0305b 100644 --- a/aiogram/types/audio.py +++ b/aiogram/types/audio.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable class Audio(Deserializable): diff --git a/aiogram/types/base.py b/aiogram/types/base.py new file mode 100644 index 00000000..37206d1b --- /dev/null +++ b/aiogram/types/base.py @@ -0,0 +1,107 @@ +import datetime +import json +import time + + +def deserialize(deserializable, data): + if data: + return deserializable.de_json(data) + + +def deserialize_array(deserializable, array): + if array: + return [deserialize(deserializable, item) for item in array] + + +class Serializable: + def to_json(self): + """ + Returns a JSON string representation of this class. + + This function must be overridden by subclasses. + :return: a JSON formatted string. + """ + raise NotImplementedError + + +class Deserializable: + """ + Subclasses of this class are guaranteed to be able to be created from a json-style dict or json formatted string. + All subclasses of this class must override de_json. + """ + + def to_json(self): + result = {} + for name, attr in self.__dict__.items(): + if not attr or name == '_bot': + continue + if hasattr(attr, 'to_json'): + attr = getattr(attr, 'to_json')() + elif isinstance(attr, datetime.datetime): + attr = int(time.mktime(attr.timetuple())) + result[name] = attr + return result + + @property + def bot(self): + if not hasattr(self, '_bot'): + raise AttributeError(f"{self.__class__.__name__} is not configured.") + return getattr(self, '_bot') + + @bot.setter + def bot(self, bot): + setattr(self, '_bot', bot) + for name, attr in self.__dict__.items(): + if hasattr(attr, 'de_json'): + attr.bot = bot + + @property + def parent(self): + return getattr(self, '_parent', None) + + @parent.setter + def parent(self, value): + setattr(self, '_parent', value) + for name, attr in self.__dict__.items(): + if name.startswith('_'): + continue + if hasattr(attr, 'de_json'): + attr.parent = self + + @classmethod + def de_json(cls, raw_data): + """ + Returns an instance of this class from the given json dict or string. + + This function must be overridden by subclasses. + :return: an instance of this class created from the given json dict or string. + """ + raise NotImplementedError + + @staticmethod + def check_json(raw_data) -> dict: + """ + Checks whether json_type is a dict or a string. If it is already a dict, it is returned as-is. + If it is not, it is converted to a dict by means of json.loads(json_type) + :param raw_data: + :return: + """ + + if isinstance(raw_data, dict): + return raw_data + elif isinstance(raw_data, str): + return json.loads(raw_data) + else: + raise ValueError("data should be a json dict or string.") + + def __str__(self): + return json.dumps(self.to_json()) + + def __repr__(self): + return str(self) + + @classmethod + def deserialize(cls, obj): + if isinstance(obj, list): + return deserialize_array(cls, obj) + return deserialize(cls, obj) diff --git a/aiogram/types/callback_query.py b/aiogram/types/callback_query.py index b012898c..f4f2d5bb 100644 --- a/aiogram/types/callback_query.py +++ b/aiogram/types/callback_query.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable from .message import Message from .user import User diff --git a/aiogram/types/chat.py b/aiogram/types/chat.py index 22362531..1b43e9b5 100644 --- a/aiogram/types/chat.py +++ b/aiogram/types/chat.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable class Chat(Deserializable): diff --git a/aiogram/types/chat_member.py b/aiogram/types/chat_member.py index 9f973cb9..0b0b3b58 100644 --- a/aiogram/types/chat_member.py +++ b/aiogram/types/chat_member.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable from .user import User diff --git a/aiogram/types/chosen_inline_result.py b/aiogram/types/chosen_inline_result.py index 75655fa0..b21817a2 100644 --- a/aiogram/types/chosen_inline_result.py +++ b/aiogram/types/chosen_inline_result.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable from .location import Location from .user import User diff --git a/aiogram/types/contact.py b/aiogram/types/contact.py index eec2a4fb..5de29fda 100644 --- a/aiogram/types/contact.py +++ b/aiogram/types/contact.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable class Contact(Deserializable): diff --git a/aiogram/types/document.py b/aiogram/types/document.py index e89a988b..03e2f878 100644 --- a/aiogram/types/document.py +++ b/aiogram/types/document.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable from .photo_size import PhotoSize diff --git a/aiogram/types/file.py b/aiogram/types/file.py index 08b2768d..7243797d 100644 --- a/aiogram/types/file.py +++ b/aiogram/types/file.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable class File(Deserializable): diff --git a/aiogram/types/force_reply.py b/aiogram/types/force_reply.py index 182137b7..4fc52ec4 100644 --- a/aiogram/types/force_reply.py +++ b/aiogram/types/force_reply.py @@ -1,4 +1,4 @@ -from aiogram.types import Serializable +from .base import Serializable class ForceReply(Serializable): diff --git a/aiogram/types/game.py b/aiogram/types/game.py index ada89d44..76a27cc8 100644 --- a/aiogram/types/game.py +++ b/aiogram/types/game.py @@ -1,5 +1,5 @@ -from . import Deserializable from .animation import Animation +from .base import Deserializable from .message_entity import MessageEntity from .photo_size import PhotoSize diff --git a/aiogram/types/game_high_score.py b/aiogram/types/game_high_score.py index 35e4e834..c000fa60 100644 --- a/aiogram/types/game_high_score.py +++ b/aiogram/types/game_high_score.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable from .user import User diff --git a/aiogram/types/inline_keyboard.py b/aiogram/types/inline_keyboard.py index b5dc1f95..339a8287 100644 --- a/aiogram/types/inline_keyboard.py +++ b/aiogram/types/inline_keyboard.py @@ -1,4 +1,4 @@ -from . import Serializable +from .base import Serializable class InlineKeyboardMarkup(Serializable): diff --git a/aiogram/types/inline_query.py b/aiogram/types/inline_query.py index 1f51afac..24e48fbf 100644 --- a/aiogram/types/inline_query.py +++ b/aiogram/types/inline_query.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable from .location import Location from .user import User diff --git a/aiogram/types/invoice.py b/aiogram/types/invoice.py index 5c328ef0..83477cce 100644 --- a/aiogram/types/invoice.py +++ b/aiogram/types/invoice.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable class Invoice(Deserializable): diff --git a/aiogram/types/location.py b/aiogram/types/location.py index 6d78006f..ecaa9e31 100644 --- a/aiogram/types/location.py +++ b/aiogram/types/location.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable class Location(Deserializable): diff --git a/aiogram/types/message.py b/aiogram/types/message.py index 56546c9b..4bcd216e 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -1,8 +1,7 @@ import datetime -from aiogram.exceptions import TelegramAPIError -from . import Deserializable from .audio import Audio +from .base import Deserializable from .chat import Chat from .contact import Contact from .document import Document @@ -18,6 +17,7 @@ from .venue import Venue from .video import Video from .video_note import VideoNote from .voice import Voice +from ..exceptions import TelegramAPIError class Message(Deserializable): diff --git a/aiogram/types/message_entity.py b/aiogram/types/message_entity.py index 3897a2d2..f9ce5471 100644 --- a/aiogram/types/message_entity.py +++ b/aiogram/types/message_entity.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable from .user import User diff --git a/aiogram/types/order_info.py b/aiogram/types/order_info.py index 6f2b5a44..98e11792 100644 --- a/aiogram/types/order_info.py +++ b/aiogram/types/order_info.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable from .shipping_address import ShippingAddress diff --git a/aiogram/types/photo_size.py b/aiogram/types/photo_size.py index bc198a03..77199564 100644 --- a/aiogram/types/photo_size.py +++ b/aiogram/types/photo_size.py @@ -1,4 +1,4 @@ -from . import Deserializable, deserialize +from .base import deserialize, Deserializable class PhotoSize(Deserializable): diff --git a/aiogram/types/pre_checkout_query.py b/aiogram/types/pre_checkout_query.py index 00500e4e..7c6fa859 100644 --- a/aiogram/types/pre_checkout_query.py +++ b/aiogram/types/pre_checkout_query.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable from .order_info import OrderInfo from .user import User diff --git a/aiogram/types/reply_keyboard.py b/aiogram/types/reply_keyboard.py index 4f717436..94d02b35 100644 --- a/aiogram/types/reply_keyboard.py +++ b/aiogram/types/reply_keyboard.py @@ -1,4 +1,4 @@ -from . import Serializable +from .base import Serializable class ReplyKeyboardMarkup(Serializable): diff --git a/aiogram/types/shipping_address.py b/aiogram/types/shipping_address.py index 6c5a194d..4d170556 100644 --- a/aiogram/types/shipping_address.py +++ b/aiogram/types/shipping_address.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable class ShippingAddress(Deserializable): diff --git a/aiogram/types/shipping_query.py b/aiogram/types/shipping_query.py index 0a4d70ab..15ef1083 100644 --- a/aiogram/types/shipping_query.py +++ b/aiogram/types/shipping_query.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable from .shipping_address import ShippingAddress from .user import User diff --git a/aiogram/types/sticker.py b/aiogram/types/sticker.py index 5a9c487b..b1853d7b 100644 --- a/aiogram/types/sticker.py +++ b/aiogram/types/sticker.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable from .photo_size import PhotoSize diff --git a/aiogram/types/successful_payment.py b/aiogram/types/successful_payment.py index 542e56f4..713af3c2 100644 --- a/aiogram/types/successful_payment.py +++ b/aiogram/types/successful_payment.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable from .order_info import OrderInfo diff --git a/aiogram/types/update.py b/aiogram/types/update.py index c4622d1a..2187ac6f 100644 --- a/aiogram/types/update.py +++ b/aiogram/types/update.py @@ -1,10 +1,10 @@ -from aiogram.types.callback_query import CallbackQuery -from aiogram.types.chosen_inline_result import ChosenInlineResult -from aiogram.types.inline_query import InlineQuery -from aiogram.types.pre_checkout_query import PreCheckoutQuery -from aiogram.types.shipping_query import ShippingQuery -from . import Deserializable +from .base import Deserializable +from .callback_query import CallbackQuery +from .chosen_inline_result import ChosenInlineResult +from .inline_query import InlineQuery from .message import Message +from .pre_checkout_query import PreCheckoutQuery +from .shipping_query import ShippingQuery class Update(Deserializable): diff --git a/aiogram/types/user.py b/aiogram/types/user.py index 10c78745..f910c953 100644 --- a/aiogram/types/user.py +++ b/aiogram/types/user.py @@ -1,6 +1,6 @@ import babel -from . import Deserializable +from .base import Deserializable class User(Deserializable): diff --git a/aiogram/types/user_profile_photos.py b/aiogram/types/user_profile_photos.py index 9217a1cf..c97d3450 100644 --- a/aiogram/types/user_profile_photos.py +++ b/aiogram/types/user_profile_photos.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable from .photo_size import PhotoSize diff --git a/aiogram/types/venue.py b/aiogram/types/venue.py index 7de96d71..d5f444e5 100644 --- a/aiogram/types/venue.py +++ b/aiogram/types/venue.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable from .location import Location diff --git a/aiogram/types/video.py b/aiogram/types/video.py index f01358d7..576aadf6 100644 --- a/aiogram/types/video.py +++ b/aiogram/types/video.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable from .photo_size import PhotoSize diff --git a/aiogram/types/video_note.py b/aiogram/types/video_note.py index 6ecdc542..5b34a318 100644 --- a/aiogram/types/video_note.py +++ b/aiogram/types/video_note.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable from .photo_size import PhotoSize diff --git a/aiogram/types/voice.py b/aiogram/types/voice.py index bfb69a2a..48162e92 100644 --- a/aiogram/types/voice.py +++ b/aiogram/types/voice.py @@ -1,4 +1,4 @@ -from . import Deserializable +from .base import Deserializable class Voice(Deserializable): diff --git a/aiogram/types/webhook_info.py b/aiogram/types/webhook_info.py index 73cde70e..77b31d89 100644 --- a/aiogram/types/webhook_info.py +++ b/aiogram/types/webhook_info.py @@ -1,10 +1,11 @@ import datetime -from . import Deserializable +from .base import Deserializable class WebhookInfo(Deserializable): - def __init__(self, url, has_custom_certificate, pending_update_count, last_error_date, last_error_message, max_connections, allowed_updates): + def __init__(self, url, has_custom_certificate, pending_update_count, last_error_date, last_error_message, + max_connections, allowed_updates): self.url: str = url self.has_custom_certificate: bool = has_custom_certificate self.pending_update_count: int = pending_update_count @@ -29,4 +30,5 @@ class WebhookInfo(Deserializable): max_connections = raw_data.get('max_connections') allowed_updates = raw_data.get('allowed_updates') - return WebhookInfo(url, has_custom_certificate, pending_update_count, last_error_date, last_error_message, max_connections, allowed_updates) + return WebhookInfo(url, has_custom_certificate, pending_update_count, last_error_date, last_error_message, + max_connections, allowed_updates)