This commit is contained in:
Alex Root Junior 2017-09-27 08:58:37 +03:00
parent d2694a4d3b
commit 9b96133b7a
3 changed files with 289 additions and 229 deletions

View file

@ -1,133 +1,6 @@
from . import base
from .animation import Animation
from .audio import Audio
from .callback_query import CallbackQuery
from .chat import Chat, ChatType, ChatActions
from .chat_member import ChatMember, ChatMemberStatus
from .chat_photo import ChatPhoto
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 .inline_query_result import InlineQueryResult, InlineQueryResultArticle, InlineQueryResultAudio, \
InlineQueryResultCachedAudio, InlineQueryResultCachedDocument, InlineQueryResultCachedGif, \
InlineQueryResultCachedMpeg4Gif, InlineQueryResultCachedPhoto, InlineQueryResultCachedSticker, \
InlineQueryResultCachedVideo, InlineQueryResultCachedVoice, InlineQueryResultContact, InlineQueryResultDocument, \
InlineQueryResultGame, InlineQueryResultGif, InlineQueryResultLocation, InlineQueryResultMpeg4Gif, \
InlineQueryResultPhoto, InlineQueryResultVenue, InlineQueryResultVideo, InlineQueryResultVoice, \
InputMessageContent, InputContactMessageContent, InputContactMessageContent, InputLocationMessageContent, \
InputLocationMessageContent, InputMessageContent, InputTextMessageContent, InputTextMessageContent, \
InputVenueMessageContent, InputVenueMessageContent
from .invoice import Invoice
from .labeled_price import LabeledPrice
from .location import Location
from .mask_position import MaskPosition
from .message import Message, ContentType, ParseMode
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 .response_parameters import ResponseParameters
from .shipping_address import ShippingAddress
from .shipping_option import ShippingOption
from .shipping_query import ShippingQuery
from .sticker import Sticker
from .sticker_set import StickerSet
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
from . import fields
__all__ = [
'base',
'Animation',
'Audio',
'CallbackQuery',
'Chat',
'ChatActions',
'ChatMember',
'ChatMemberStatus',
'ChatPhoto',
'ChatType',
'ChosenInlineResult',
'Contact',
'ContentType',
'Document',
'File',
'ForceReply',
'Game',
'GameHighScore',
'InlineKeyboardButton',
'InlineKeyboardMarkup',
'InlineQuery',
'InlineQueryResult',
'InlineQueryResultArticle',
'InlineQueryResultAudio',
'InlineQueryResultCachedAudio',
'InlineQueryResultCachedDocument',
'InlineQueryResultCachedGif',
'InlineQueryResultCachedMpeg4Gif',
'InlineQueryResultCachedPhoto',
'InlineQueryResultCachedSticker',
'InlineQueryResultCachedVideo',
'InlineQueryResultCachedVoice',
'InlineQueryResultContact',
'InlineQueryResultDocument',
'InlineQueryResultGame',
'InlineQueryResultGif',
'InlineQueryResultLocation',
'InlineQueryResultMpeg4Gif',
'InlineQueryResultPhoto',
'InlineQueryResultVenue',
'InlineQueryResultVideo',
'InlineQueryResultVoice',
'InputMessageContent',
'InputContactMessageContent',
'InputContactMessageContent',
'InputLocationMessageContent',
'InputLocationMessageContent',
'InputMessageContent',
'InputTextMessageContent',
'InputTextMessageContent',
'InputVenueMessageContent',
'InputVenueMessageContent',
'Invoice',
'KeyboardButton',
'LabeledPrice',
'Location',
'MaskPosition'
'Message',
'MessageEntity',
'OrderInfo',
'ParseMode',
'PhotoSize',
'PreCheckoutQuery',
'ReplyKeyboardMarkup',
'ReplyKeyboardRemove',
'ResponseParameters',
'ShippingAddress',
'ShippingOption',
'ShippingQuery',
'Sticker',
'StickerSet'
'SuccessfulPayment',
'Update',
'User',
'UserProfilePhotos',
'Venue',
'Video',
'VideoNote',
'Voice',
'WebhookInfo'
]
__all__ = (
'base', 'fields'
)

View file

@ -1,119 +1,171 @@
import datetime
import time
import typing
import ujson
from .fields import BaseField
PROPS_ATTR_NAME = '_props'
VALUES_ATTR_NAME = '_values'
ALIASES_ATTR_NAME = '_aliases'
__all__ = ('MetaTelegramObject', 'TelegramObject')
def deserialize(deserializable, data):
class MetaTelegramObject(type):
"""
Deserialize object if have data
:param deserializable: :class:`aiogram.types.Deserializable`
:param data:
:return:
Metaclass for telegram objects
"""
if data:
return deserializable.de_json(data)
_objects = {}
def __new__(mcs, name, bases, namespace, **kwargs):
cls = super(MetaTelegramObject, mcs).__new__(mcs, name, bases, namespace)
def deserialize_array(deserializable, array):
"""
Deserialize array of objects
props = {}
values = {}
aliases = {}
:param deserializable:
:param array:
:return:
"""
if array:
return [deserialize(deserializable, item) for item in array]
class Serializable:
"""
Subclasses of this class are guaranteed to be able to be created from a json-style dict.
"""
def to_json(self):
"""
Returns a JSON representation of this class.
:return: dict
"""
return {k: v.to_json() if hasattr(v, 'to_json') else v for k, v in self.__dict__.items() if
not k.startswith('_')}
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 in ['_bot', '_parent']:
# Get props, values, aliases from parent objects
for base in bases:
if not isinstance(base, MetaTelegramObject):
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
props.update(getattr(base, PROPS_ATTR_NAME))
values.update(getattr(base, VALUES_ATTR_NAME))
aliases.update(getattr(base, ALIASES_ATTR_NAME))
# Scan current object for props
for name, prop in ((name, prop) for name, prop in namespace.items() if isinstance(prop, BaseField)):
props[prop.alias] = prop
if prop.default is not None:
values[prop.alias] = prop.default
aliases[name] = prop.alias
# Set attributes
setattr(cls, PROPS_ATTR_NAME, props)
setattr(cls, VALUES_ATTR_NAME, values)
setattr(cls, ALIASES_ATTR_NAME, aliases)
mcs._objects[cls.__name__] = cls
return cls
@property
def telegram_types(cls):
return cls._objects
class TelegramObject(metaclass=MetaTelegramObject):
"""
Abstract class for telegram objects
"""
def __init__(self, conf=None, **kwargs):
"""
Deserialize object
:param conf:
:param kwargs:
"""
if conf is None:
conf = {}
for key, value in kwargs.items():
if key in self.props:
self.props[key].set_value(self, value)
else:
self.values[key] = value
self._conf = conf
@property
def conf(self) -> typing.Dict[str, typing.Any]:
return self.conf
@property
def props(self) -> typing.Dict[str, BaseField]:
"""
Get props
:return: dict with props
"""
return getattr(self, PROPS_ATTR_NAME, {})
@property
def props_aliases(self) -> typing.Dict[str, str]:
"""
Get aliases for props
:return:
"""
return getattr(self, ALIASES_ATTR_NAME, {})
@property
def values(self):
"""
Get values
:return:
"""
return getattr(self, VALUES_ATTR_NAME, {})
@property
def telegram_types(self):
return type(self).telegram_types
@classmethod
def to_object(cls, data):
"""
Deserialize object
:param data:
:return:
"""
return cls(**data)
def to_python(self) -> typing.Dict:
"""
Get object as JSON serializable
:return:
"""
self.clean()
result = {}
for name, value in self.values.items():
if name in self.props:
value = self.props[name].export(self)
if isinstance(value, TelegramObject):
value = value.to_python()
result[self.props_aliases.get(name, name)] = value
return result
@classmethod
def _parse_date(cls, unix_time):
if unix_time:
return datetime.datetime.fromtimestamp(unix_time)
@property
def bot(self) -> 'Bot':
def clean(self):
"""
Bot instance
Remove empty values
"""
if not hasattr(self, '_bot'):
raise AttributeError("{0} is not configured.".format(self.__class__.__name__))
return getattr(self, '_bot')
for key, value in self.values.copy().items():
if value is None:
del self.values[key]
@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):
def as_json(self) -> str:
"""
Parent object
Get object as JSON string
:return: JSON
:rtype: :obj:`str`
"""
return getattr(self, '_parent', None)
return ujson.dumps(self.to_python())
@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):
def __str__(self) -> str:
"""
Returns an instance of this class from the given json dict or string.
Return object as string. Alias for '.as_json()'
This function must be overridden by subclasses.
:return: an instance of this class created from the given json dict or string.
:return: str
"""
raise NotImplementedError
return self.as_json()
def __str__(self):
return str(self.to_json())
def __getitem__(self, item):
if item in self.props:
return getattr(self, item)
elif item in self.values:
return self.values[item]
def __repr__(self):
return str(self)
@classmethod
def deserialize(cls, obj):
if isinstance(obj, list):
return deserialize_array(cls, obj)
return deserialize(cls, obj)
def __setitem__(self, key, value):
if key in self.props:
setattr(self, key, value)
else:
self.values[key] = value

135
aiogram/types/fields.py Normal file
View file

@ -0,0 +1,135 @@
import abc
import datetime
__all__ = ('BaseField', 'Field', 'ListField', 'DateTimeField')
class BaseField(metaclass=abc.ABCMeta):
"""
Base field (prop)
"""
def __init__(self, *, base=None, default=None, alias=None):
"""
Init prop
:param base: class for child element
:param default: default value
:param alias: alias name (for e.g. field named 'from' must be has name 'from_user'
('from' is builtin Python keyword)
"""
self.base_object = base
self.default = default
self.alias = alias
def __set_name__(self, owner, name):
if self.alias is None:
self.alias = name
def resolve_base(self, instance):
if self.base_object is None or hasattr(self.base_object, 'telegram_types'):
return
elif isinstance(self.base_object, str):
self.base_object = instance.telegram_types.get(self.base_object)
def get_value(self, instance):
"""
Get value for current object instance
:param instance:
:return:
"""
return instance.values.get(self.alias)
def set_value(self, instance, value):
"""
Set prop value
:param instance:
:param value:
:return:
"""
self.resolve_base(instance)
value = self.deserialize(value)
instance.values[self.alias] = value
def __get__(self, instance, owner):
return self.get_value(instance)
def __set__(self, instance, value):
self.set_value(instance, value)
@abc.abstractmethod
def serialize(self, value):
"""
Serialize value to python
:param value:
:return:
"""
pass
@abc.abstractmethod
def deserialize(self, value):
"""Deserialize python object value to TelegramObject value"""
pass
def export(self, instance):
"""
Alias for `serialize` but for current Object instance
:param instance:
:return:
"""
return self.serialize(self.get_value(instance))
class Field(BaseField):
"""
Simple field
"""
def serialize(self, value):
if self.base_object is not None:
return value.to_python()
return value
def deserialize(self, value):
if self.base_object is not None and not hasattr(value, 'base_object'):
return self.base_object(**value)
return value
class ListField(Field):
"""
Field contains list ob objects
"""
def serialize(self, value):
result = []
serialize = super(ListField, self).serialize
for item in value:
result.append(serialize(item))
return result
def deserialize(self, value):
result = []
deserialize = super(ListField, self).deserialize
for item in value:
result.append(deserialize(item))
return result
class DateTimeField(BaseField):
"""
In this field stored datetime
in: unixtime
out: datetime
"""
def serialize(self, value: datetime.datetime):
return round(value.timestamp())
def deserialize(self, value):
return datetime.datetime.fromtimestamp(value)