From a4520b1c0752061a8bde63bf876caf9539e90ffc Mon Sep 17 00:00:00 2001 From: Martin Winks <50446230+uwinx@users.noreply.github.com> Date: Sun, 7 Jun 2020 12:30:51 +0400 Subject: [PATCH] refactor(): move to 0.3.x fixes () * fix(minor): minor fixes remove support for events.([un]/register). clean up code * featref(): rename "file" module to "json" Breaking(!): . remove FSMProxy avoid races . fix frozen_list decorator to reuse decorated functions . update docs * chore(): remove print statement :) * handle commands with bot username (#12) Co-authored-by: GauthamramRavichandran <30320759+GauthamramRavichandran@users.noreply.github.com> --- examples/fsm.py | 58 +-- examples/locker.py | 4 +- garnet/__init__.py | 3 +- garnet/client.py | 38 +- garnet/events/__init__.py | 64 --- garnet/filters/__init__.py | 1 + garnet/filters/state.py | 12 +- garnet/filters/text.py | 2 +- garnet/functions/messages.py | 62 +-- garnet/helpers/frozen_list.py | 34 +- garnet/helpers/var.py | 9 +- garnet/jsonlib.py | 2 +- garnet/patch_tl_methods.py | 12 + garnet/router/router.py | 13 +- garnet/storages/base.py | 212 +--------- garnet/storages/{file.py => json.py} | 25 +- poetry.lock | 572 +++++++++++++++++---------- pyproject.toml | 17 +- readme.rst | 151 ++++--- 19 files changed, 640 insertions(+), 651 deletions(-) create mode 100644 garnet/patch_tl_methods.py rename garnet/storages/{file.py => json.py} (72%) diff --git a/examples/fsm.py b/examples/fsm.py index 9a5a487..dfbeb44 100644 --- a/examples/fsm.py +++ b/examples/fsm.py @@ -1,14 +1,23 @@ -from telethon import custom +from functools import partial +from telethon import custom, events from garnet import TelegramClient, FSMContext, MessageText, CurrentState +from garnet.router import Router +from garnet.helpers import var +POSSIBLE_PETS = ("Doggggee |}", "Cat >.<", "Human <|", "Goose 8D") +PETS = tuple([ + (custom.Button.text(_petty, resize=True),) + for _petty in POSSIBLE_PETS +]) -# // genders in parallel universe buttons. just imagine πŸ€” -buttons = ( - (custom.Button.text("Abuser", resize=True),), - (custom.Button.text("Dishwasher"),), - (custom.Button.text("Cat"),), -) + +class States: + _auto = partial(var.Var, prefix="state:") + + name: str = _auto() # state:name + age: str = _auto() # state:age + pet: str = _auto() # state:pet # /* @@ -17,63 +26,66 @@ # */ bot = TelegramClient.from_env() +router = Router(event=events.NewMessage()) + -@bot.on(MessageText.commands("start")) +@router.on(MessageText.commands("start")) async def handler(update: custom.Message, context: FSMContext): await update.reply(f"Hello, please enter your name!") - await context.set_state("stateName") + await context.set_state(States.name) # // handle all /cancel's from any state if only state is not None -@bot.on(MessageText.commands("cancel"), CurrentState == any) +@router.on(MessageText.commands("cancel"), CurrentState == any) async def cancel_handler(update: custom.Message, context: FSMContext): await update.reply("Ok. Resetting!") await context.reset_state(with_data=True) -@bot.on(CurrentState == "stateName") +@router.on(CurrentState == States.name) async def name_handler(update: custom.Message, context: FSMContext): await context.set_data({"name": update.raw_text}) await update.reply( f"{update.raw_text}, please enter your age:", buttons=custom.Button.force_reply(), ) - await context.set_state("stateAge") + await context.set_state(States.age) # if we can use one Filter, then let's create filter which will be cached by garnet. Optimizations 8> -ageStateFilter = CurrentState.exact("stateAge") +ageStateFilter = CurrentState.exact(States.age) -@bot.on(ageStateFilter & MessageText.isdigit()) +@router.on(ageStateFilter & MessageText.isdigit()) async def age_correct_handler(update: custom.Message, context: FSMContext): await context.update_data(age=int(update.raw_text)) - await update.reply(f"Cool! Now please select your gender:", buttons=buttons) - await context.set_state("stateGender") + await update.reply(f"Cool! Now please select your favourite pet:", buttons=PETS) + await context.set_state(States.pet) -@bot.on(ageStateFilter & ~MessageText.isdigit()) +@router.on(ageStateFilter & ~MessageText.isdigit()) async def age_incorrect_handler(update: custom.Message): await update.reply(f"Please try again! Age must be digit :D") -@bot.on(CurrentState.exact("stateGender") & MessageText.between("Abuser", "Dishwasher", "Cat")) -async def gender_correct_handler(update: custom.Message, context: FSMContext): - await context.update_data(gender=update.raw_text) +@router.on(CurrentState.exact(States.pet) & MessageText.between(*POSSIBLE_PETS)) +async def pet_correct_handler(update: custom.Message, context: FSMContext): + await context.update_data(pet=update.raw_text) data = await context.get_data() await update.reply( f"Your age: {data['age']}\n" f"Name: {data['name']}\n" - f"Gender: {data['gender']}\n" - f"Thank you! You can participate again!", + f"Favourite pert: {data['pet']}\n" + f"Thank you! To participate again send /start", buttons=custom.Button.clear(), ) await context.reset_state(with_data=True) if __name__ == '__main__': - @bot.on_start + @bot.on_start() async def main(_): + bot.bind_routers(router) await bot.start_as_bot() diff --git a/examples/locker.py b/examples/locker.py index 0e91c4b..f0a2a08 100644 --- a/examples/locker.py +++ b/examples/locker.py @@ -49,13 +49,13 @@ async def menu(_): await messages.reply("Sorry, I'm not ukrainian cat-pic sender...") -@bot.on_start +@bot.on_start() async def on_start(client: TelegramClient): await client.start() await client.send_message("smn", "Bot is starting") -@bot.on_finish +@bot.on_finish() async def on_finish(client: TelegramClient): await client.send_message("smn", "GoodBye!") diff --git a/garnet/__init__.py b/garnet/__init__.py index 93b3f9d..5b074f2 100644 --- a/garnet/__init__.py +++ b/garnet/__init__.py @@ -23,7 +23,7 @@ # from .client import TelegramClient -from .storages.base import BaseStorage, FSMContext, FSMContextProxy +from .storages.base import BaseStorage, FSMContext from .filters import Filter, text, CurrentState from .jsonlib import json from .callbacks.base import Callback @@ -38,7 +38,6 @@ "TelegramClient", "events", "FSMContext", - "FSMContextProxy", "CurrentState", "text", "MessageText", # bc&c diff --git a/garnet/client.py b/garnet/client.py index 780c71f..dbde1fb 100644 --- a/garnet/client.py +++ b/garnet/client.py @@ -2,6 +2,7 @@ from typing import ( Union, Callable, + Dict, Sequence, List, Optional, @@ -15,12 +16,10 @@ from telethon.client.telegramclient import TelegramClient as _TelethonTelegramClient -from garnet.events import StopPropagation, NewMessage, Raw - -from .events import EventBuilderDict +from .events import EventBuilderDict, StopPropagation, NewMessage, Raw from .filters import state from .filters.base import Filter -from .storages import base, file, memory +from .storages import base, memory from .callbacks.base import ( Callback, CURRENT_CLIENT_KEY, @@ -35,7 +34,10 @@ class TelegramClient(_TelethonTelegramClient, ctx.ContextInstanceMixin): - storage: base.BaseStorage = None + # todo how hard would be using forwardable library to use composition instead of inheritance. + + storage: base.BaseStorage + conf: Dict[str, Any] class Env: default_session_dsn_key = "SESSION" @@ -57,19 +59,13 @@ def __init__( super().__init__(session, api_id, api_hash, *args, **kwargs) + self.conf: Dict[str, Any] = {} self.storage = storage self.__bot_token = None - _action_containerT = PseudoFrozenList[Callable] - self.on_start: _action_containerT - self.on_finish: _action_containerT - self.on_background: _action_containerT - - self.on_start, self.on_finish, self.on_background = ( - PseudoFrozenList(), - PseudoFrozenList(), - PseudoFrozenList(), - ) + self.on_start: PseudoFrozenList[Callable] = PseudoFrozenList() + self.on_finish: PseudoFrozenList[Callable] = PseudoFrozenList() + self.on_background: PseudoFrozenList[Callable] = PseudoFrozenList() self.set_current(self) @@ -100,7 +96,7 @@ def from_env( obj.__bot_token = os.getenv(cls.Env.default_bot_token_key, bot_token) obj.set_current(obj) - if isinstance(storage, (file.JSONStorage,)): + if isinstance(storage, base.FileStorageProto): async def close_storage(*_): await storage.close() @@ -134,12 +130,12 @@ async def start_as_bot(self, token: str = None) -> TelegramClient: ) @classmethod - def make_fsm_key(cls, update, *, _check=True) -> dict: + def make_fsm_key(cls, event) -> dict: try: - return {"chat": update.chat_id, "user": update.from_id} + return {"chat": event.chat_id, "user": event.from_id} except AttributeError: raise Warning( - f"Standard make_fsm_key method was not designed for {update!r}" + f"Standard make_fsm_key method was not designed for {event!r}" ) def current_state(self, *, user, chat): @@ -171,7 +167,7 @@ def decorator(f): def add_event_handler( self, callback: Union[Callable, Callback], - event=None, + event: Any = None, *filters: Union[Callable, Filter], ): if filters is not None and not isinstance(filters, Sequence): @@ -293,7 +289,7 @@ async def _dispatch_update(self, update, others, channel_id, pts_date): coro = callback.__call__(event, **kwargs) else: - coro = callback.__call__(**kwargs) + coro = callback.__call__() if not callback.continue_prop: return await coro diff --git a/garnet/events/__init__.py b/garnet/events/__init__.py index 3bb6312..00ed54d 100644 --- a/garnet/events/__init__.py +++ b/garnet/events/__init__.py @@ -1,7 +1,5 @@ from typing import Type -from warnings import warn - from telethon.events.raw import Raw from telethon.events.album import Album from telethon.events.chataction import ChatAction @@ -85,15 +83,6 @@ def __getitem__(self, builder): ) -def _deprecated(): - warn( - message="event.Register/Unregister is deprecated in " - "Garnet, use garnet::router::Router", - category=Exception, - stacklevel=5, - ) - - class StopPropagation(Exception): """ If this exception is raised in any of the handlers for a given event, @@ -120,56 +109,3 @@ class StopPropagation(Exception): # For some reason Sphinx wants the silly >>> or # it will show warnings and look bad when generated. pass - - -def register(event=None): - """ - DEPRECATED FUNCTION - """ - _deprecated() - - -def unregister(*_): - """ - Inverse operation of `register` (though not a decorator). Client-less - `remove_event_handler - ` - variant. **Note that this won't remove handlers from the client**, - because it simply can't, so you would generally use this before - adding the handlers to the client. - - This method is here for symmetry. You will rarely need to - unregister events, since you can simply just not add them - to any client. - - If no event is given, all events for this callback are removed. - Returns how many callbacks were removed. - """ - _deprecated() - - -def is_handler(*_): - """ - DEPRECATED - Returns `True` if the given callback is an - event handler (i.e. you used `register` on it). - """ - _deprecated() - - -def list(*_): - """ - DEPRECATED - Returns a list containing the registered event - builders inside the specified callback handler. - """ - - _deprecated() - - -def _get_handlers(*_): - """ - DEPRECATED - Like ``list`` but returns `None` if the callback was never registered. - """ - _deprecated() diff --git a/garnet/filters/__init__.py b/garnet/filters/__init__.py index 9c6fb8b..e3686b7 100644 --- a/garnet/filters/__init__.py +++ b/garnet/filters/__init__.py @@ -1,3 +1,4 @@ from .base import Filter from .state import CurrentState from . import text +from .file_ext import File diff --git a/garnet/filters/state.py b/garnet/filters/state.py index 52f69b0..1d8382f 100644 --- a/garnet/filters/state.py +++ b/garnet/filters/state.py @@ -1,4 +1,4 @@ -from typing import List, Callable, Dict, Any, Tuple, Union, Sequence +from typing import Callable, Dict, Any, Tuple, Union, Container from telethon.events import common @@ -27,13 +27,13 @@ def __rmatmul__(mcs, key_maker: Tuple[str, KeyMakerType]) -> Filter: return CurrentState @ key_maker @classmethod - def __eq__(mcs, _s: Union[str, List[str]]) -> Filter: + def __eq__(mcs, _s: Union[str, Container[str]]) -> Filter: if id(_s) in ANY_STATE: async def _f(_, context): return (await context.get_state()) is not None - elif isinstance(_s, Sequence): + elif not isinstance(_s, str) and isinstance(_s, Container): async def _f(_, context): return (await context.get_state()) in _s @@ -46,13 +46,13 @@ async def _f(_, context): return Filter(_f, requires_context=True, state_op=True) @classmethod - def exact(mcs, state: Union[str, List[str]]) -> Filter: + def exact(mcs, state: Union[str, Container[str]]) -> Filter: # noinspection PyTypeChecker return CurrentState == state # type: ignore @classmethod def with_key(mcs, state: str, key_maker: KeyMakerType): - return CurrentState @ (state, key_maker) + return mcs.__matmul__((state, key_maker)) class CurrentState(metaclass=_MetaCurrentState): @@ -63,7 +63,7 @@ class CurrentState(metaclass=_MetaCurrentState): key_maker is a callable with signature: (event) -> Dict[str, Any] For instance: >>> def my_key_maker(event) -> Dict[str, Any]: - ... return {"chat": event.chat.id, event.user.id} + ... return {"chat": event.chat.id, "user": event.user.id} ... >>> CurrentState @ ("equlas_to_the_state", my_key_maker) ... diff --git a/garnet/filters/text.py b/garnet/filters/text.py index adcf806..c459d08 100644 --- a/garnet/filters/text.py +++ b/garnet/filters/text.py @@ -33,7 +33,7 @@ def commands( return Filter( lambda update: isinstance(update.text, str) and any(update.text.startswith(prefix) for prefix in prefixes) - and update.text.split()[0][1:] in cmd + and update.text.split()[0][1:].split('@')[0] in cmd ) diff --git a/garnet/functions/messages.py b/garnet/functions/messages.py index e1d9043..bd86147 100644 --- a/garnet/functions/messages.py +++ b/garnet/functions/messages.py @@ -5,41 +5,45 @@ from garnet.events import NewMessage +VT_co = typing.TypeVar("VT_co") + + def message() -> custom.Message: # noinspection PyTypeChecker return NewMessage.Event.current() # type: ignore +class _CurrentClsDescriptor(typing.Generic[VT_co]): + __slots__ = ("_fget",) + + def __init__(self, fget: typing.Callable[[], VT_co]): + self._fget = fget + + def __get__(self, *_) -> VT_co: + return self._fget() # type: VT_co + + # noinspection PyPep8Naming -class current(object): # type: ignore - @property - def chat_id(self) -> typing.Optional[int]: - """ - get current chat's ID - """ - return message().chat_id - - @property - def chat(self) -> typing.Union[types.User, types.Chat, types.Channel]: - """ - get current chat - """ - return message().chat - - @property - def fmt_text(self) -> typing.Optional[str]: - """ - Get formatted text with respect to current parse_mode - e.g. ... - """ - return message().text - - @property - def text(self) -> typing.Optional[str]: - """ - Get RAW text from update, text without formatting - """ - return message().raw_text +class current: + chat_id: _CurrentClsDescriptor[typing.Optional[int]] = _CurrentClsDescriptor( + lambda: message().chat_id + ) + """get current chat id""" + + chat: _CurrentClsDescriptor[ + typing.Union[types.User, types.Chat, types.Channel] + ] = _CurrentClsDescriptor(lambda: message().chat) + "get current chat" + + fmt_text: _CurrentClsDescriptor[typing.Optional[str]] = _CurrentClsDescriptor( + lambda: message().text + ) + """get formatted text with respect to current parse_mode e.g. ...""" + + text: _CurrentClsDescriptor[typing.Optional[str]] = _CurrentClsDescriptor( + lambda: message().raw_text + ) + """get the raw text from message""" async def respond( diff --git a/garnet/helpers/frozen_list.py b/garnet/helpers/frozen_list.py index e1e4407..c95ba33 100644 --- a/garnet/helpers/frozen_list.py +++ b/garnet/helpers/frozen_list.py @@ -1,4 +1,6 @@ -from typing import List, TypeVar, Generic, Optional +import inspect +from functools import update_wrapper +from typing import Any, List, TypeVar, Generic, Iterator, Callable T = TypeVar("T") @@ -6,11 +8,11 @@ class PseudoFrozenList(Generic[T]): # we don't need python list's all methods, so don't inherit from it def __init__(self): - self.__items: List[T] = list() + self.__items: List[T] = [] self.__frozen = False @property - def frozen(self): + def frozen(self) -> bool: return self.__frozen def append(self, *items: T) -> None: @@ -23,16 +25,24 @@ def remove(self, *items: T) -> None: for val in items: rm(val) - def freeze(self): + def freeze(self) -> None: self.__frozen = True - def __iter__(self): - return list.__iter__(self.__items) + def __iter__(self) -> Iterator[T]: + return iter(self.__items) - # decorator for append - @property - def __call__(self): - def wrp(item: T): - self.append(item) + # decorator for append, when T is Callable[..., Any] + def __call__(self) -> Callable[[T], T]: + def decorator(func) -> T: + self.append(func) + + if inspect.iscoroutinefunction(func): + async def wrp(*args, **kwargs) -> Any: + return await func(*args, **kwargs) + else: + def wrp(*args, **kwargs) -> Any: + return func(*args, **kwargs) + + return update_wrapper(wrp, func) - return wrp + return decorator diff --git a/garnet/helpers/var.py b/garnet/helpers/var.py index e27c4a1..694b339 100644 --- a/garnet/helpers/var.py +++ b/garnet/helpers/var.py @@ -3,18 +3,19 @@ class Var: Set string object values of class attributes as their link names. Pulled from https://github.com/uwinx/tamtam.py [modified] """ + __slots__ = "value", "prefix", "suffix" - def __init__(self, prefix: str = "", value=None, suffix: str = ""): + def __init__(self, prefix: str = "", value=None, suffix: str = "") -> None: self.value = value self.prefix = prefix self.suffix = suffix - def __set_name__(self, owner, val): + def __set_name__(self, owner, val) -> None: if not self.value: self.value = self.prefix + val + self.suffix - def __get__(self, instance, owner): + def __get__(self, instance, owner) -> str: return self.value - def __str__(self): + def __str__(self) -> str: return self.value.__str__() diff --git a/garnet/jsonlib.py b/garnet/jsonlib.py index a85eab9..6e50890 100644 --- a/garnet/jsonlib.py +++ b/garnet/jsonlib.py @@ -4,7 +4,7 @@ json = _std_json -for __json_lib in ("orjson", "ujson", "rapidjson"): +for __json_lib in ("orjson", "ujson"): try: json = importlib.import_module(__json_lib) dumps = json.dumps diff --git a/garnet/patch_tl_methods.py b/garnet/patch_tl_methods.py new file mode 100644 index 0000000..330a81e --- /dev/null +++ b/garnet/patch_tl_methods.py @@ -0,0 +1,12 @@ +from telethon.tl.functions import TLRequest +from garnet.client import TelegramClient + + +def _g_await(self): + client = TelegramClient.current(no_error=False) + + return client.__call__(self, ordered=False).__await__() + + +def install(): + TLRequest.__await__ = _g_await diff --git a/garnet/router/router.py b/garnet/router/router.py index 94516c5..f084f39 100644 --- a/garnet/router/router.py +++ b/garnet/router/router.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Union, Callable, NoReturn, Sequence, TYPE_CHECKING +from typing import Union, Callable, Optional, TYPE_CHECKING from ..events import CallbackQuery, ChatAction, NewMessage, MessageEdited from ..helpers.frozen_list import PseudoFrozenList @@ -11,8 +11,7 @@ class AbstractRouter(ABC): - frozen: bool - handlers: Sequence + __slots__ = "event", "frozen", "handlers", "filters" @abstractmethod def __call__(self, *args, **kwargs): @@ -41,7 +40,7 @@ def non_blocking(self, *args, **kwargs): class BaseRouter(AbstractRouter, ABC): def __init__( - self, event: "common.EventBuilder", frozen: bool = False, *filters: Filter + self, event: Optional["common.EventBuilder"], frozen: bool = False, *filters: Filter ): """ BaseRouter @@ -68,12 +67,12 @@ def chat_action_handler(self, *filters: Union[Callable, Filter]): def message_edited_handler(self, *filters: Union[Callable, Filter]): return self.register(*(*filters, *self.filters), event=MessageEdited) - def add_router(self, router: "BaseRouter") -> NoReturn: + def add_router(self, router: "BaseRouter") -> "BaseRouter": if type(self) != type(router): - raise ValueError(f"Can bind {router!r} into {self!r}") + raise ValueError(f"Can't bind {router!r} into {self!r}") for handler in router.handlers: self.handlers.append(handler) - return self, router + return self class TelethonRouter(BaseRouter): diff --git a/garnet/storages/base.py b/garnet/storages/base.py index 0e79bc4..4d357c3 100644 --- a/garnet/storages/base.py +++ b/garnet/storages/base.py @@ -1,6 +1,6 @@ # Source: https://github.com/aiogram/aiogram +from abc import ABC -import copy import typing from warnings import warn @@ -311,9 +311,6 @@ def __init__(self, storage, chat, user): self.storage: BaseStorage = storage self.chat, self.user = self.storage.check_address(chat=chat, user=user) - def proxy(self): - return FSMContextProxy(self) - async def get_state(self) -> typing.Optional[str]: return await self.storage.get_state(chat=self.chat, user=self.user) @@ -343,208 +340,13 @@ async def finish(self): await self.storage.finish(chat=self.chat, user=self.user) -class FSMContextProxy: - def __init__(self, fsm_context: FSMContext): - super(FSMContextProxy, self).__init__() - self.fsm_context = fsm_context - self._copy = {} - self._data = {} - self._state = None - self._is_dirty = False - - self._closed = True - - async def __aenter__(self): - await self.load() - return self - - async def __aexit__(self, exc_type, exc_val, exc_tb): - if exc_type is None: - await self.save() - self._closed = True - - def _check_closed(self): - if self._closed: - raise LookupError("Proxy is closed!") - - @classmethod - async def create(cls, fsm_context: FSMContext): - """ - :param fsm_context: - :return: - """ - proxy = cls(fsm_context) - await proxy.load() - return proxy - - async def load(self): - self._closed = False - - self.clear() - self._state = await self.fsm_context.get_state() - self.update(await self.fsm_context.get_data()) - self._copy = copy.deepcopy(self._data) - self._is_dirty = False - - @property - def state(self): - return self._state - - @state.setter - def state(self, value): - self._check_closed() - - self._state = value - self._is_dirty = True - - @state.deleter - def state(self): - self._check_closed() - - self._state = None - self._is_dirty = True - - async def save(self, force=False): - self._check_closed() - - if self._copy != self._data or force: - await self.fsm_context.set_data(data=self._data) - if self._is_dirty or force: - await self.fsm_context.set_state(self.state) - self._is_dirty = False - self._copy = copy.deepcopy(self._data) - - def clear(self): - del self.state - return self._data.clear() - - def get(self, value, default=None): - return self._data.get(value, default) - - def setdefault(self, key, default): - self._check_closed() - - self._data.setdefault(key, default) - - def update(self, data=None, **kwargs): - self._check_closed() - - self._data.update(data, **kwargs) - - def pop(self, key, default=None): - self._check_closed() - - return self._data.pop(key, default) - - def keys(self): - return self._data.keys() - - def values(self): - return self._data.values() - - def items(self): - return self._data.items() - - def as_dict(self): - return copy.deepcopy(self._data) - - def __len__(self): - return len(self._data) - - def __iter__(self): - return self._data.__iter__() - - def __getitem__(self, item): - return self._data[item] - - def __setitem__(self, key, value): - self._check_closed() - - self._data[key] = value - - def __delitem__(self, key): - self._check_closed() - - del self._data[key] - - def __contains__(self, item): - return item in self._data - - def __str__(self): - readable_state = f"'{self.state}'" if self.state else "" - result = ( - f"{self.__class__.__name__} state = {readable_state}, data = {self._data}" - ) - if self._closed: - result += ", closed = True" - return result - - -class DisabledStorage(BaseStorage): +class FileStorageProto(ABC): """ - Empty storage. Use it if you don't want to use Finite-State Machine + Base class for all fs based storage s """ - async def close(self): - pass - - async def wait_closed(self): - pass - - async def get_state( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - default: typing.Optional[str] = None, - ) -> typing.Optional[str]: - return None - - async def get_data( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - default: typing.Optional[str] = None, - ) -> typing.Dict: - self._warn() - return {} - - async def update_data( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - data: typing.Dict = None, - **kwargs, - ): - self._warn() - - async def set_state( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - state: typing.Optional[typing.AnyStr] = None, - ): - self._warn() + async def read(self) -> None: + raise NotImplementedError - async def set_data( - self, - *, - chat: typing.Union[str, int, None] = None, - user: typing.Union[str, int, None] = None, - data: typing.Dict = None, - ): - self._warn() - - @staticmethod - def _warn(): - warn( - message=( - f"You haven’t set any storage yet so no states and no data will be saved. \n" - "You can connect MemoryStorage for debug purposes or non-essential data." - ), - category=Warning, - stacklevel=5, - ) + async def save(self) -> None: + raise NotImplementedError diff --git a/garnet/storages/file.py b/garnet/storages/json.py similarity index 72% rename from garnet/storages/file.py rename to garnet/storages/json.py index 7602e7f..36686f2 100644 --- a/garnet/storages/file.py +++ b/garnet/storages/json.py @@ -8,6 +8,7 @@ from ..jsonlib import json from .memory import MemoryStorage +from .base import FileStorageProto executor = ThreadPoolExecutor() loop = asyncio.get_event_loop() @@ -17,11 +18,12 @@ def _create_json_file(path: pathlib.Path): """ Since `orJSON` does not provide .dump write by hand. """ - with path.open("w", encoding="utf-8") as f: - f.write("{}") + if not path.exists(): + with path.open("w", encoding="utf-8") as f: + f.write("{}") -class JSONStorage(MemoryStorage): +class JSONStorage(MemoryStorage, FileStorageProto): """ JSON File storage based on MemoryStorage """ @@ -32,17 +34,12 @@ def __init__(self, path: typing.Union[pathlib.Path, str]): Could be more reliable. :param path: file path """ - self.path = path = pathlib.Path(path) - try: - data = self.__read(path) - except FileNotFoundError: - _create_json_file(path) - data = self.__read(path) - - super().__init__(data) + super().__init__() + self.path = pathlib.Path(path) # noinspection PyMethodMayBeStatic def __read(self, path: pathlib.Path): + _create_json_file(path) with path.open("r", encoding="utf-8") as f: return json.loads(f.read()) @@ -51,7 +48,11 @@ def _save(self, path: pathlib.Path): serialized = json.dumps(self.data) # orjson returns bytes for dumps return f.write(serialized if isinstance(serialized, bytes) else serialized.encode()) - async def save(self, close: bool = False): + async def read(self) -> None: + data = await loop.run_in_executor(executor, self.__read, self.path) + self.data = data + + async def save(self, close: bool = False) -> None: if self.data: await loop.run_in_executor(executor, self.save, self.path) if close: diff --git a/poetry.lock b/poetry.lock index 270bcc0..238260c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,23 +1,5 @@ [[package]] -category = "dev" -description = "Async http client/server framework (asyncio)" -name = "aiohttp" -optional = false -python-versions = ">=3.5.3" -version = "3.6.2" - -[package.dependencies] -async-timeout = ">=3.0,<4.0" -attrs = ">=17.3.0" -chardet = ">=2.0,<4.0" -multidict = ">=4.5,<5.0" -yarl = ">=1.0,<2.0" - -[package.extras] -speedups = ["aiodns", "brotlipy", "cchardet"] - -[[package]] -category = "dev" +category = "main" description = "asyncio (PEP 3156) Redis support" name = "aioredis" optional = false @@ -30,6 +12,23 @@ hiredis = "*" [[package]] category = "dev" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +name = "appdirs" +optional = false +python-versions = "*" +version = "1.4.4" + +[[package]] +category = "dev" +description = "Disable App Nap on OS X 10.9" +marker = "sys_platform == \"darwin\"" +name = "appnope" +optional = false +python-versions = "*" +version = "0.1.0" + +[[package]] +category = "main" description = "Timeout context manager for asyncio programs" name = "async-timeout" optional = false @@ -52,48 +51,59 @@ tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.i [[package]] category = "dev" -description = "Foreign Function Interface for Python calling C code." -name = "cffi" -optional = true +description = "Specifications for callback functions passed in to an API" +name = "backcall" +optional = false python-versions = "*" -version = "1.14.0" +version = "0.1.0" + +[[package]] +category = "dev" +description = "The uncompromising code formatter." +name = "black" +optional = false +python-versions = ">=3.6" +version = "19.10b0" [package.dependencies] -pycparser = "*" +appdirs = "*" +attrs = ">=18.1.0" +click = ">=6.5" +pathspec = ">=0.6,<1" +regex = "*" +toml = ">=0.9.4" +typed-ast = ">=1.4.0" + +[package.extras] +d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] category = "dev" -description = "Universal encoding detector for Python 2 and 3" -name = "chardet" +description = "Composable command line interface toolkit" +name = "click" optional = false -python-versions = "*" -version = "3.0.4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "7.1.2" [[package]] category = "dev" -description = "Cryptographic utilities for Telegram." -name = "cryptg" -optional = true -python-versions = ">=3.3" -version = "0.2.post0" - -[package.dependencies] -cffi = ">=1.0.0" -pycparser = "*" +description = "Cross-platform colored terminal text." +marker = "sys_platform == \"win32\"" +name = "colorama" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.3" [[package]] category = "dev" -description = "Package of Hachoir parsers used to open binary files" -name = "hachoir" +description = "Decorators for Humans" +name = "decorator" optional = false -python-versions = "*" -version = "3.1.1" - -[package.extras] -urwid = ["urwid (1.3.1)"] +python-versions = ">=2.6, !=3.0.*, !=3.1.*" +version = "4.4.2" [[package]] -category = "dev" +category = "main" description = "Python wrapper for hiredis" name = "hiredis" optional = false @@ -102,27 +112,125 @@ version = "1.0.1" [[package]] category = "dev" -description = "Internationalized Domain Names in Applications (IDNA)" -name = "idna" +description = "IPython: Productive Interactive Computing" +name = "ipython" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.9" +python-versions = ">=3.6" +version = "7.15.0" + +[package.dependencies] +appnope = "*" +backcall = "*" +colorama = "*" +decorator = "*" +jedi = ">=0.10" +pexpect = "*" +pickleshare = "*" +prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" +pygments = "*" +setuptools = ">=18.5" +traitlets = ">=4.2" + +[package.extras] +all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.14)", "pygments", "qtconsole", "requests", "testpath"] +doc = ["Sphinx (>=1.3)"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["notebook", "ipywidgets"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] [[package]] category = "dev" -description = "multidict implementation" -name = "multidict" +description = "Vestigial utilities from IPython" +name = "ipython-genutils" optional = false -python-versions = ">=3.5" -version = "4.7.5" +python-versions = "*" +version = "0.2.0" [[package]] category = "dev" +description = "An autocompletion tool for Python that can be used for text editors." +name = "jedi" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.17.0" + +[package.dependencies] +parso = ">=0.7.0" + +[package.extras] +qa = ["flake8 (3.7.9)"] +testing = ["colorama", "docopt", "pytest (>=3.9.0,<5.0.0)"] + +[[package]] +category = "main" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" name = "orjson" optional = false python-versions = ">=3.6" -version = "2.6.5" +version = "2.6.8" + +[[package]] +category = "dev" +description = "A Python Parser" +name = "parso" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.7.0" + +[package.extras] +testing = ["docopt", "pytest (>=3.0.7)"] + +[[package]] +category = "dev" +description = "Utility library for gitignore style pattern matching of file paths." +name = "pathspec" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.8.0" + +[[package]] +category = "dev" +description = "Pexpect allows easy control of interactive console applications." +marker = "sys_platform != \"win32\"" +name = "pexpect" +optional = false +python-versions = "*" +version = "4.8.0" + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +category = "dev" +description = "Tiny 'shelve'-like database with concurrency support" +name = "pickleshare" +optional = false +python-versions = "*" +version = "0.7.5" + +[[package]] +category = "dev" +description = "Library for building powerful interactive command lines in Python" +name = "prompt-toolkit" +optional = false +python-versions = ">=3.6.1" +version = "3.0.5" + +[package.dependencies] +wcwidth = "*" + +[[package]] +category = "dev" +description = "Run a subprocess in a pseudo terminal" +marker = "sys_platform != \"win32\"" +name = "ptyprocess" +optional = false +python-versions = "*" +version = "0.6.0" [[package]] category = "main" @@ -142,11 +250,19 @@ version = "0.4.8" [[package]] category = "dev" -description = "C parser in Python" -name = "pycparser" -optional = true -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.20" +description = "Pygments is a syntax highlighting package written in Python." +name = "pygments" +optional = false +python-versions = ">=3.5" +version = "2.6.1" + +[[package]] +category = "dev" +description = "Alternative regular expression module, to replace re." +name = "regex" +optional = false +python-versions = "*" +version = "2020.5.14" [[package]] category = "main" @@ -159,13 +275,21 @@ version = "4.0" [package.dependencies] pyasn1 = ">=0.1.3" +[[package]] +category = "dev" +description = "Python 2 and 3 compatibility utilities" +name = "six" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +version = "1.15.0" + [[package]] category = "main" description = "Full-featured Telegram client library for Python 3" name = "telethon" optional = false python-versions = ">=3.5" -version = "1.11.3" +version = "1.14.0" [package.dependencies] pyaes = "*" @@ -176,6 +300,38 @@ cryptg = ["cryptg"] [[package]] category = "dev" +description = "Python Library for Tom's Obvious, Minimal Language" +name = "toml" +optional = false +python-versions = "*" +version = "0.10.1" + +[[package]] +category = "dev" +description = "Traitlets Python config system" +name = "traitlets" +optional = false +python-versions = "*" +version = "4.3.3" + +[package.dependencies] +decorator = "*" +ipython-genutils = "*" +six = "*" + +[package.extras] +test = ["pytest", "mock"] + +[[package]] +category = "dev" +description = "a fork of Python 2 and 3 ast modules with type comment support" +name = "typed-ast" +optional = false +python-versions = "*" +version = "1.4.1" + +[[package]] +category = "main" description = "Ultra fast JSON encoder and decoder for Python" name = "ujson" optional = false @@ -184,39 +340,34 @@ version = "2.0.3" [[package]] category = "dev" -description = "Yet another URL library" -name = "yarl" +description = "Measures the displayed width of unicode strings in a terminal" +name = "wcwidth" optional = false -python-versions = ">=3.5" -version = "1.4.2" +python-versions = "*" +version = "0.2.3" -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" +[extras] +orjson = ["orjson"] +redis = ["aioredis"] +ujson = ["ujson"] [metadata] -content-hash = "ddfe046242938353950380f38c17ac760dc359e35b8af2779e9a82f88dc22cb2" +content-hash = "4189e0812c078bcfb30de25b48348b72f395ce254baf2b4fb2a45326a61603b8" python-versions = "^3.7" [metadata.files] -aiohttp = [ - {file = "aiohttp-3.6.2-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:1e984191d1ec186881ffaed4581092ba04f7c61582a177b187d3a2f07ed9719e"}, - {file = "aiohttp-3.6.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:50aaad128e6ac62e7bf7bd1f0c0a24bc968a0c0590a726d5a955af193544bcec"}, - {file = "aiohttp-3.6.2-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:65f31b622af739a802ca6fd1a3076fd0ae523f8485c52924a89561ba10c49b48"}, - {file = "aiohttp-3.6.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ae55bac364c405caa23a4f2d6cfecc6a0daada500274ffca4a9230e7129eac59"}, - {file = "aiohttp-3.6.2-cp36-cp36m-win32.whl", hash = "sha256:344c780466b73095a72c616fac5ea9c4665add7fc129f285fbdbca3cccf4612a"}, - {file = "aiohttp-3.6.2-cp36-cp36m-win_amd64.whl", hash = "sha256:4c6efd824d44ae697814a2a85604d8e992b875462c6655da161ff18fd4f29f17"}, - {file = "aiohttp-3.6.2-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:2f4d1a4fdce595c947162333353d4a44952a724fba9ca3205a3df99a33d1307a"}, - {file = "aiohttp-3.6.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6206a135d072f88da3e71cc501c59d5abffa9d0bb43269a6dcd28d66bfafdbdd"}, - {file = "aiohttp-3.6.2-cp37-cp37m-win32.whl", hash = "sha256:b778ce0c909a2653741cb4b1ac7015b5c130ab9c897611df43ae6a58523cb965"}, - {file = "aiohttp-3.6.2-cp37-cp37m-win_amd64.whl", hash = "sha256:32e5f3b7e511aa850829fbe5aa32eb455e5534eaa4b1ce93231d00e2f76e5654"}, - {file = "aiohttp-3.6.2-py3-none-any.whl", hash = "sha256:460bd4237d2dbecc3b5ed57e122992f60188afe46e7319116da5eb8a9dfedba4"}, - {file = "aiohttp-3.6.2.tar.gz", hash = "sha256:259ab809ff0727d0e834ac5e8a283dc5e3e0ecc30c4d80b3cd17a4139ce1f326"}, -] aioredis = [ {file = "aioredis-1.3.1-py3-none-any.whl", hash = "sha256:b61808d7e97b7cd5a92ed574937a079c9387fdadd22bfbfa7ad2fd319ecc26e3"}, {file = "aioredis-1.3.1.tar.gz", hash = "sha256:15f8af30b044c771aee6787e5ec24694c048184c7b9e54c3b60c750a4b93273a"}, ] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +appnope = [ + {file = "appnope-0.1.0-py2.py3-none-any.whl", hash = "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0"}, + {file = "appnope-0.1.0.tar.gz", hash = "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"}, +] async-timeout = [ {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, @@ -225,68 +376,25 @@ attrs = [ {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, ] -cffi = [ - {file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"}, - {file = "cffi-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30"}, - {file = "cffi-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"}, - {file = "cffi-1.14.0-cp27-cp27m-win32.whl", hash = "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78"}, - {file = "cffi-1.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793"}, - {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e"}, - {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a"}, - {file = "cffi-1.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff"}, - {file = "cffi-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f"}, - {file = "cffi-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa"}, - {file = "cffi-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5"}, - {file = "cffi-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4"}, - {file = "cffi-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d"}, - {file = "cffi-1.14.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc"}, - {file = "cffi-1.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac"}, - {file = "cffi-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f"}, - {file = "cffi-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b"}, - {file = "cffi-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3"}, - {file = "cffi-1.14.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66"}, - {file = "cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0"}, - {file = "cffi-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f"}, - {file = "cffi-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26"}, - {file = "cffi-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd"}, - {file = "cffi-1.14.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55"}, - {file = "cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2"}, - {file = "cffi-1.14.0-cp38-cp38-win32.whl", hash = "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8"}, - {file = "cffi-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b"}, - {file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"}, -] -chardet = [ - {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, - {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, -] -cryptg = [ - {file = "cryptg-0.2.post0-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:2729f468e729efd99f2edac7ec1fb92288d6d9ea3ecf3a4e7ce7a7305e03ba16"}, - {file = "cryptg-0.2.post0-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:9b1478fbabfb2a83718cae7041923f49b355597bb63ac211422510a22dc70765"}, - {file = "cryptg-0.2.post0-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:63d5b527c300115d39754d37993c1391b32e8a0c876fae9c3308c0fd1e9cbb23"}, - {file = "cryptg-0.2.post0-cp34-cp34m-win32.whl", hash = "sha256:629f673eb77a8de6a2658d2da47a11dc3c47e349ae51539d990c81359753db27"}, - {file = "cryptg-0.2.post0-cp34-cp34m-win_amd64.whl", hash = "sha256:b17211792e3232261d229c38792b343fc5395226c3de283999ea6439202a4645"}, - {file = "cryptg-0.2.post0-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:80bf192bbcbbc420b91ba52a7793b4186e6a607ce07eb98efdd1b1ac1cefe615"}, - {file = "cryptg-0.2.post0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:badc10c2e9c384afc9ae5ab52cf57f3fe1ce59aaadd677c8f27699abb877578c"}, - {file = "cryptg-0.2.post0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:2f721d5f208b944ad959a9af51a4d5b519c3dfd46950d3685301b0d18d7c5371"}, - {file = "cryptg-0.2.post0-cp35-cp35m-win32.whl", hash = "sha256:79fdfc8e5f6aed0e8aaa012afa4e247a29f28cdbce8ebb94d30d9ad8cff6351c"}, - {file = "cryptg-0.2.post0-cp35-cp35m-win_amd64.whl", hash = "sha256:8fe45cd8f330ff2f501436d7705b5d019a9a9746c9e5acbc7ff5d931a76ef8a4"}, - {file = "cryptg-0.2.post0-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:48c3c5112a4e83a2b7a945fd54f0ffb24979fe978fd2aa5c4b5f37c49164ac3b"}, - {file = "cryptg-0.2.post0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:cbf37574c35de3da11b20d3857e51a2460a3ebfdd23c5a3ffd5c6ca8f56e7336"}, - {file = "cryptg-0.2.post0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c7957624ce0a5643d057b896abe50695d5b3e62a59508736d9f5eece258bd5c"}, - {file = "cryptg-0.2.post0-cp36-cp36m-win32.whl", hash = "sha256:59152d1986eff0c4b34c78c2846a5691c3f9110b0c63c59c839892eb9df7f452"}, - {file = "cryptg-0.2.post0-cp36-cp36m-win_amd64.whl", hash = "sha256:3a0133b4a2403b88cd2adc1dc6c4f141344f93d6d9a777d2df529e5789b07027"}, - {file = "cryptg-0.2.post0-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:604b32406ce28c52fbc5c8c67c0a03464dc59f601462cfabe87acec87101ce87"}, - {file = "cryptg-0.2.post0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:abb017a286c709e43c5477b9a74f393c711a161b66527d3aea21984ca8fb51a9"}, - {file = "cryptg-0.2.post0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3c002b7558ca40a7a84927872fcdefa0e494265f4b47d573f4368c22b84d4e93"}, - {file = "cryptg-0.2.post0-cp37-cp37m-win32.whl", hash = "sha256:2af0fd1627c745f1ab3ef114cfbafd82415e5f1e275c6232b96ef4289e1d6218"}, - {file = "cryptg-0.2.post0-cp37-cp37m-win_amd64.whl", hash = "sha256:93935c19d2ba5192c5a42b21bc4c3e229e0f7f8180c0d04a09d359c850a8d047"}, - {file = "cryptg-0.2.post0-cp38-cp38-win32.whl", hash = "sha256:9e470cd175e80a7355801626fb1c37b5fb7445858e7a871bf1fe294859da4d2f"}, - {file = "cryptg-0.2.post0-cp38-cp38-win_amd64.whl", hash = "sha256:ab851e301231e48ff581da42b25ea12913d4e1d3ba0dc6452734794bfb1fc3a7"}, - {file = "cryptg-0.2.post0.tar.gz", hash = "sha256:2e82c082f61f692c417f84351a3462fcbc83ed5a52768221ad1c55b6ced9e01b"}, -] -hachoir = [ - {file = "hachoir-3.1.1-py3-none-any.whl", hash = "sha256:353a1efc84a971a0fe6f85e425f2d1d23ab5e702d2c8a0d12da62115a33cbb7d"}, - {file = "hachoir-3.1.1.tar.gz", hash = "sha256:0aa71a39f8c32d3e65902285fefd155d96c2beaec2697a230b67709aaf73140c"}, +backcall = [ + {file = "backcall-0.1.0.tar.gz", hash = "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4"}, + {file = "backcall-0.1.0.zip", hash = "sha256:bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"}, +] +black = [ + {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, + {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, +] +click = [ + {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, + {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, +] +colorama = [ + {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, + {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, +] +decorator = [ + {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, + {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, ] hiredis = [ {file = "hiredis-1.0.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:38437a681f17c975fd22349e72c29bc643f8e7eb2d6dc5df419eac59afa4d7ce"}, @@ -330,45 +438,58 @@ hiredis = [ {file = "hiredis-1.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:a7754a783b1e5d6f627c19d099b178059c62f782ab62b4d8ba165b9fbc2ee34c"}, {file = "hiredis-1.0.1.tar.gz", hash = "sha256:aa59dd63bb3f736de4fc2d080114429d5d369dfb3265f771778e8349d67a97a4"}, ] -idna = [ - {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, - {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, -] -multidict = [ - {file = "multidict-4.7.5-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:fc3b4adc2ee8474cb3cd2a155305d5f8eda0a9c91320f83e55748e1fcb68f8e3"}, - {file = "multidict-4.7.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:42f56542166040b4474c0c608ed051732033cd821126493cf25b6c276df7dd35"}, - {file = "multidict-4.7.5-cp35-cp35m-win32.whl", hash = "sha256:7774e9f6c9af3f12f296131453f7b81dabb7ebdb948483362f5afcaac8a826f1"}, - {file = "multidict-4.7.5-cp35-cp35m-win_amd64.whl", hash = "sha256:c2c37185fb0af79d5c117b8d2764f4321eeb12ba8c141a95d0aa8c2c1d0a11dd"}, - {file = "multidict-4.7.5-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:e439c9a10a95cb32abd708bb8be83b2134fa93790a4fb0535ca36db3dda94d20"}, - {file = "multidict-4.7.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:85cb26c38c96f76b7ff38b86c9d560dea10cf3459bb5f4caf72fc1bb932c7136"}, - {file = "multidict-4.7.5-cp36-cp36m-win32.whl", hash = "sha256:620b37c3fea181dab09267cd5a84b0f23fa043beb8bc50d8474dd9694de1fa6e"}, - {file = "multidict-4.7.5-cp36-cp36m-win_amd64.whl", hash = "sha256:6e6fef114741c4d7ca46da8449038ec8b1e880bbe68674c01ceeb1ac8a648e78"}, - {file = "multidict-4.7.5-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:a326f4240123a2ac66bb163eeba99578e9d63a8654a59f4688a79198f9aa10f8"}, - {file = "multidict-4.7.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:dc561313279f9d05a3d0ffa89cd15ae477528ea37aa9795c4654588a3287a9ab"}, - {file = "multidict-4.7.5-cp37-cp37m-win32.whl", hash = "sha256:4b7df040fb5fe826d689204f9b544af469593fb3ff3a069a6ad3409f742f5928"}, - {file = "multidict-4.7.5-cp37-cp37m-win_amd64.whl", hash = "sha256:317f96bc0950d249e96d8d29ab556d01dd38888fbe68324f46fd834b430169f1"}, - {file = "multidict-4.7.5-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:b51249fdd2923739cd3efc95a3d6c363b67bbf779208e9f37fd5e68540d1a4d4"}, - {file = "multidict-4.7.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:ae402f43604e3b2bc41e8ea8b8526c7fa7139ed76b0d64fc48e28125925275b2"}, - {file = "multidict-4.7.5-cp38-cp38-win32.whl", hash = "sha256:bb519becc46275c594410c6c28a8a0adc66fe24fef154a9addea54c1adb006f5"}, - {file = "multidict-4.7.5-cp38-cp38-win_amd64.whl", hash = "sha256:544fae9261232a97102e27a926019100a9db75bec7b37feedd74b3aa82f29969"}, - {file = "multidict-4.7.5.tar.gz", hash = "sha256:aee283c49601fa4c13adc64c09c978838a7e812f85377ae130a24d7198c0331e"}, +ipython = [ + {file = "ipython-7.15.0-py3-none-any.whl", hash = "sha256:1b85d65632211bf5d3e6f1406f3393c8c429a47d7b947b9a87812aa5bce6595c"}, + {file = "ipython-7.15.0.tar.gz", hash = "sha256:0ef1433879816a960cd3ae1ae1dc82c64732ca75cec8dab5a4e29783fb571d0e"}, +] +ipython-genutils = [ + {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, + {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, +] +jedi = [ + {file = "jedi-0.17.0-py2.py3-none-any.whl", hash = "sha256:cd60c93b71944d628ccac47df9a60fec53150de53d42dc10a7fc4b5ba6aae798"}, + {file = "jedi-0.17.0.tar.gz", hash = "sha256:df40c97641cb943661d2db4c33c2e1ff75d491189423249e989bcea4464f3030"}, ] orjson = [ - {file = "orjson-2.6.5-cp36-cp36m-macosx_10_7_x86_64.whl", hash = "sha256:2b73518e036fcaabc03f88c951350d971493346f55cdc7368c10a13ff62a4f6f"}, - {file = "orjson-2.6.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2f2b6a425b43174ea4a34dcee67e6c07f6df88baaf7098d1f41bf3e9eb996b00"}, - {file = "orjson-2.6.5-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:2a2e5f0dca7203f8779c20c3c0c5246bbeb62d48a72fa81d30fdae6508acb48f"}, - {file = "orjson-2.6.5-cp36-none-win_amd64.whl", hash = "sha256:1393d45a08799f5058aa92e480f8da42e06a288af41bfc58fb48b37a66d711d5"}, - {file = "orjson-2.6.5-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:dae4c6d25f09069127deb3866c8f22f9b25530df7ac93ae57068b9c10557d106"}, - {file = "orjson-2.6.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b424eab367173bf9d56ef5242371dc1258bf573dcb4517291ec3b1e4e8020abc"}, - {file = "orjson-2.6.5-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b10bda292e91df63c2ced92c0bdd4d74615acaf58b1f3742a19a82e84a1cb4ee"}, - {file = "orjson-2.6.5-cp37-none-win_amd64.whl", hash = "sha256:211d03974a2141e1a52fc1c284ffbbf3c37e9d5c6dd9303aa0817158f800092c"}, - {file = "orjson-2.6.5-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:411d3823f6b9cbb43a43942f0bc263720ad7c94d0c68d258cfbf76a0f3c0ce0d"}, - {file = "orjson-2.6.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:dd9a62914fabe387cc3c8ff50c9ffd6a7009b422cea270c1527d98b87034ed01"}, - {file = "orjson-2.6.5-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a889288e111dbb36c740911232ccee97c86f25c70e7a666daca94c5b1f70d7f7"}, - {file = "orjson-2.6.5-cp38-none-win_amd64.whl", hash = "sha256:c7d64e3452396af7810efe9fcb0a33c269a4b20e569e51ec9d1522ab4b6aa97b"}, - {file = "orjson-2.6.5-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b15831f847ecae767aed19e0365aefb5315a7834410ab6c59e046f0d199e1593"}, - {file = "orjson-2.6.5-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:9f817e770cf4cf919cba6950a47f1b19e3c201dc02a2176821c9c63eff41ad86"}, - {file = "orjson-2.6.5.tar.gz", hash = "sha256:89c853658e3dab6e553205159c809f84fd8a69886fe0ef7e4e5eeac407254926"}, + {file = "orjson-2.6.8-cp36-cp36m-macosx_10_7_x86_64.whl", hash = "sha256:440acee918752157b578e489656776b17704089ab6f06d669409e1f1bfe431ac"}, + {file = "orjson-2.6.8-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:71011e91875e823d526f10c136391aadb64a87bdb4175079581a918a8bd104e7"}, + {file = "orjson-2.6.8-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:02f8887b8b3a77e758cca2f900ed2168a636c5c5d375dc5b800477f8a2ef8382"}, + {file = "orjson-2.6.8-cp36-none-win_amd64.whl", hash = "sha256:0b2674d6bcc6b547d415be309951b40dd99d7b8a73f57ac3b215859ba83792fa"}, + {file = "orjson-2.6.8-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:42eb3fa39f46c06e8ea82c43e8b133adc2e5d76f41bc5d6379bf731f35ecf963"}, + {file = "orjson-2.6.8-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:df50e971e1b286d2b4d9dbdfb97bf8a5cfcb1158fc77f53074a911a19427dd87"}, + {file = "orjson-2.6.8-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:282f7e9d2226afd64e638ed66f95118c90b7b041cda387a7741be7940298e8a1"}, + {file = "orjson-2.6.8-cp37-none-win_amd64.whl", hash = "sha256:88c3a7d1b652617ef2630241e86acf60f5c741cc2e107b3d21d763fecccb49f5"}, + {file = "orjson-2.6.8-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:a1519f3830b9e6cfd06853a418616a9b56a1866f8aef58c4b15e0e8ccf1f254f"}, + {file = "orjson-2.6.8-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:667defa97b2b03fc653caeabfa60c260277b98d3e3d896c32f0cb7d05a8e23c7"}, + {file = "orjson-2.6.8-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:78e9ec09d81bf18f3259be2f82cb27269c8948dabf3c5c7438ce1a74e90158b5"}, + {file = "orjson-2.6.8-cp38-none-win_amd64.whl", hash = "sha256:861a47ce0878d629b623a775952f7d2b9cab0e462916ab2e13dcf6a8819435aa"}, + {file = "orjson-2.6.8-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b6cc790dfb813c9d08eb2c63742931b42c515345a494411c8b14b03e17c162c9"}, + {file = "orjson-2.6.8-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:dd5003b248d9789b25bd991bd3d0bac305b327f019eb649d76894a229d7a6a0d"}, + {file = "orjson-2.6.8.tar.gz", hash = "sha256:3a143c80afa35557584414f67070e09cf7ce5dc316de5acf3fe8c64fbc58d3c3"}, +] +parso = [ + {file = "parso-0.7.0-py2.py3-none-any.whl", hash = "sha256:158c140fc04112dc45bca311633ae5033c2c2a7b732fa33d0955bad8152a8dd0"}, + {file = "parso-0.7.0.tar.gz", hash = "sha256:908e9fae2144a076d72ae4e25539143d40b8e3eafbaeae03c1bfe226f4cdf12c"}, +] +pathspec = [ + {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"}, + {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"}, +] +pexpect = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] +pickleshare = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] +prompt-toolkit = [ + {file = "prompt_toolkit-3.0.5-py3-none-any.whl", hash = "sha256:df7e9e63aea609b1da3a65641ceaf5bc7d05e0a04de5bd45d05dbeffbabf9e04"}, + {file = "prompt_toolkit-3.0.5.tar.gz", hash = "sha256:563d1a4140b63ff9dd587bda9557cffb2fe73650205ab6f4383092fb882e7dc8"}, +] +ptyprocess = [ + {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, + {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"}, ] pyaes = [ {file = "pyaes-1.6.1.tar.gz", hash = "sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f"}, @@ -388,17 +509,75 @@ pyasn1 = [ {file = "pyasn1-0.4.8-py3.7.egg", hash = "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776"}, {file = "pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"}, ] -pycparser = [ - {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, - {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, +pygments = [ + {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"}, + {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"}, +] +regex = [ + {file = "regex-2020.5.14-cp27-cp27m-win32.whl", hash = "sha256:e565569fc28e3ba3e475ec344d87ed3cd8ba2d575335359749298a0899fe122e"}, + {file = "regex-2020.5.14-cp27-cp27m-win_amd64.whl", hash = "sha256:d466967ac8e45244b9dfe302bbe5e3337f8dc4dec8d7d10f5e950d83b140d33a"}, + {file = "regex-2020.5.14-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:27ff7325b297fb6e5ebb70d10437592433601c423f5acf86e5bc1ee2919b9561"}, + {file = "regex-2020.5.14-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ea55b80eb0d1c3f1d8d784264a6764f931e172480a2f1868f2536444c5f01e01"}, + {file = "regex-2020.5.14-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c9bce6e006fbe771a02bda468ec40ffccbf954803b470a0345ad39c603402577"}, + {file = "regex-2020.5.14-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:d881c2e657c51d89f02ae4c21d9adbef76b8325fe4d5cf0e9ad62f850f3a98fd"}, + {file = "regex-2020.5.14-cp36-cp36m-win32.whl", hash = "sha256:99568f00f7bf820c620f01721485cad230f3fb28f57d8fbf4a7967ec2e446994"}, + {file = "regex-2020.5.14-cp36-cp36m-win_amd64.whl", hash = "sha256:70c14743320a68c5dac7fc5a0f685be63bc2024b062fe2aaccc4acc3d01b14a1"}, + {file = "regex-2020.5.14-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a7c37f048ec3920783abab99f8f4036561a174f1314302ccfa4e9ad31cb00eb4"}, + {file = "regex-2020.5.14-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:89d76ce33d3266173f5be80bd4efcbd5196cafc34100fdab814f9b228dee0fa4"}, + {file = "regex-2020.5.14-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:51f17abbe973c7673a61863516bdc9c0ef467407a940f39501e786a07406699c"}, + {file = "regex-2020.5.14-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ce5cc53aa9fbbf6712e92c7cf268274eaff30f6bd12a0754e8133d85a8fb0f5f"}, + {file = "regex-2020.5.14-cp37-cp37m-win32.whl", hash = "sha256:8044d1c085d49673aadb3d7dc20ef5cb5b030c7a4fa253a593dda2eab3059929"}, + {file = "regex-2020.5.14-cp37-cp37m-win_amd64.whl", hash = "sha256:c2062c7d470751b648f1cacc3f54460aebfc261285f14bc6da49c6943bd48bdd"}, + {file = "regex-2020.5.14-cp38-cp38-manylinux1_i686.whl", hash = "sha256:329ba35d711e3428db6b45a53b1b13a0a8ba07cbbcf10bbed291a7da45f106c3"}, + {file = "regex-2020.5.14-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:579ea215c81d18da550b62ff97ee187b99f1b135fd894a13451e00986a080cad"}, + {file = "regex-2020.5.14-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:3a9394197664e35566242686d84dfd264c07b20f93514e2e09d3c2b3ffdf78fe"}, + {file = "regex-2020.5.14-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ce367d21f33e23a84fb83a641b3834dd7dd8e9318ad8ff677fbfae5915a239f7"}, + {file = "regex-2020.5.14-cp38-cp38-win32.whl", hash = "sha256:1386e75c9d1574f6aa2e4eb5355374c8e55f9aac97e224a8a5a6abded0f9c927"}, + {file = "regex-2020.5.14-cp38-cp38-win_amd64.whl", hash = "sha256:7e61be8a2900897803c293247ef87366d5df86bf701083b6c43119c7c6c99108"}, + {file = "regex-2020.5.14.tar.gz", hash = "sha256:ce450ffbfec93821ab1fea94779a8440e10cf63819be6e176eb1973a6017aff5"}, ] rsa = [ {file = "rsa-4.0-py2.py3-none-any.whl", hash = "sha256:14ba45700ff1ec9eeb206a2ce76b32814958a98e372006c8fb76ba820211be66"}, {file = "rsa-4.0.tar.gz", hash = "sha256:1a836406405730121ae9823e19c6e806c62bbad73f890574fff50efa4122c487"}, ] +six = [ + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, +] telethon = [ - {file = "Telethon-1.11.3-py3-none-any.whl", hash = "sha256:d9a7fcb465561bd839a0ca609d01321cc0f76e07d96a3977280fd1623b26c6a9"}, - {file = "Telethon-1.11.3.tar.gz", hash = "sha256:d065f130ad61b6b0d914fbd1d0ca239df8cd20299d3bdc92ce90f44e30579899"}, + {file = "Telethon-1.14.0-py3-none-any.whl", hash = "sha256:c8b56e26bd4588e8e4e13b7866b86eab360d806358b110066af7a2b25be14fb9"}, + {file = "Telethon-1.14.0.tar.gz", hash = "sha256:d8bb37f80c4a8befa92d0525f00a578a74064645e48d7aa0cc4731f3d813e1b9"}, +] +toml = [ + {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"}, + {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"}, +] +traitlets = [ + {file = "traitlets-4.3.3-py2.py3-none-any.whl", hash = "sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44"}, + {file = "traitlets-4.3.3.tar.gz", hash = "sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"}, +] +typed-ast = [ + {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, + {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, + {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, + {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, + {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, + {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, + {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, + {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, + {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, + {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, + {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, + {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, + {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, + {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, + {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, ] ujson = [ {file = "ujson-2.0.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:7ae13733d9467d16ccac2f38212cdee841b49ae927085c533425be9076b0bc9d"}, @@ -409,22 +588,7 @@ ujson = [ {file = "ujson-2.0.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2ab88e330405315512afe9276f29a60e9b3439187b273665630a57ed7fe1d936"}, {file = "ujson-2.0.3.tar.gz", hash = "sha256:bd2deffc983827510e5145fb66e4cc0f577480c62fe0b4882139f8f7d27ae9a3"}, ] -yarl = [ - {file = "yarl-1.4.2-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:3ce3d4f7c6b69c4e4f0704b32eca8123b9c58ae91af740481aa57d7857b5e41b"}, - {file = "yarl-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:a4844ebb2be14768f7994f2017f70aca39d658a96c786211be5ddbe1c68794c1"}, - {file = "yarl-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:d8cdee92bc930d8b09d8bd2043cedd544d9c8bd7436a77678dd602467a993080"}, - {file = "yarl-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:c2b509ac3d4b988ae8769901c66345425e361d518aecbe4acbfc2567e416626a"}, - {file = "yarl-1.4.2-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:308b98b0c8cd1dfef1a0311dc5e38ae8f9b58349226aa0533f15a16717ad702f"}, - {file = "yarl-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:944494be42fa630134bf907714d40207e646fd5a94423c90d5b514f7b0713fea"}, - {file = "yarl-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:5b10eb0e7f044cf0b035112446b26a3a2946bca9d7d7edb5e54a2ad2f6652abb"}, - {file = "yarl-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:a161de7e50224e8e3de6e184707476b5a989037dcb24292b391a3d66ff158e70"}, - {file = "yarl-1.4.2-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:26d7c90cb04dee1665282a5d1a998defc1a9e012fdca0f33396f81508f49696d"}, - {file = "yarl-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0c2ab325d33f1b824734b3ef51d4d54a54e0e7a23d13b86974507602334c2cce"}, - {file = "yarl-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:e15199cdb423316e15f108f51249e44eb156ae5dba232cb73be555324a1d49c2"}, - {file = "yarl-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:2098a4b4b9d75ee352807a95cdf5f10180db903bc5b7270715c6bbe2551f64ce"}, - {file = "yarl-1.4.2-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c9959d49a77b0e07559e579f38b2f3711c2b8716b8410b320bf9713013215a1b"}, - {file = "yarl-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:25e66e5e2007c7a39541ca13b559cd8ebc2ad8fe00ea94a2aad28a9b1e44e5ae"}, - {file = "yarl-1.4.2-cp38-cp38-win32.whl", hash = "sha256:6faa19d3824c21bcbfdfce5171e193c8b4ddafdf0ac3f129ccf0cdfcb083e462"}, - {file = "yarl-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:0ca2f395591bbd85ddd50a82eb1fde9c1066fafe888c5c7cc1d810cf03fd3cc6"}, - {file = "yarl-1.4.2.tar.gz", hash = "sha256:58cd9c469eced558cd81aa3f484b2924e8897049e06889e8ff2510435b7ef74b"}, +wcwidth = [ + {file = "wcwidth-0.2.3-py2.py3-none-any.whl", hash = "sha256:980fbf4f3c196c0f329cdcd1e84c554d6a211f18e252e525a0cf4223154a41d6"}, + {file = "wcwidth-0.2.3.tar.gz", hash = "sha256:edbc2b718b4db6cdf393eefe3a420183947d6aa312505ce6754516f458ff8830"}, ] diff --git a/pyproject.toml b/pyproject.toml index ad49eaf..a04aa0f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "telegram-garnet" -version = "0.2.4" +version = "0.3.0" description = "Garnet - cool add-on for Telethon" authors = ["Martin Winks "] readme = 'readme.rst' @@ -19,15 +19,22 @@ packages = [ [tool.poetry.dependencies] python = "^3.7" -Telethon = ">=1.10.8,<2.0" +Telethon = "^1.10.8" +aioredis = {version = "^1.3.1", optional = true} +orjson = {version = "^2.6.3", optional = true} +ujson = {version = "^2.0.3", optional = true} [tool.poetry.dev-dependencies] aioredis = "^1.3.1" -aiohttp = "^3.6.2" -cryptg = {version = "^0.2.post0", optional = true} -hachoir = "^3.1.1" orjson = "^2.6.3" ujson = "^2.0.3" +black = "^19.10b0" +ipython = "^7.15.0" + +[tool.poetry.extras] +redis = ["aioredis"] +orjson = ["orjson"] +ujson = ["ujson"] [build-system] requires = ["poetry>=0.12"] diff --git a/readme.rst b/readme.rst index 0362829..fa535f5 100644 --- a/readme.rst +++ b/readme.rst @@ -1,8 +1,8 @@ -🍷 Garnet -=================================== +Garnet +=========== Garnet β€” bot-friendly telethon ------------------------------------ +******************************** .. invisible-content-till-nel .. _aioredis: https://github.com/aio-libs/aioredis @@ -16,26 +16,25 @@ Garnet β€” bot-friendly telethon .. image:: https://raw.githubusercontent.com/uwinx/garnet/master/static/pomegranate.jpg -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/python/black - :alt: aioqiwi-code-style - -Install:: +************ +Installation +************ pip install telegram-garnet -**Dependencies:** - - ``telethon`` - main dependency telethon_ -**Extras:** - - ``aioredis`` - redis driver if you use RedisStorage* aioredis_ - - ``orjson`` || ``ujson`` - RedisStorage/JSONStorage de-&& serialization orjson_ ujson_ - - ``cryptg``, ``hachoir``, ``aiohttp`` - boost telethon itself cryptg_ hachoir_ aiohttp_ +^^^^^^^ +Extras +^^^^^^^ +- ``aioredis`` - redis driver required if you're using RedisStorage* aioredis_ +- ``orjson`` or ``ujson`` - RedisStorage/JSONStorage not required at all (ser/deser)ialization orjson_ ujson_ + ---------------------------------- +**************************** 🌚 🌝 FSM-Storage types ---------------------------------- +**************************** + - File - json storage, the main idea behind JSON storage is a custom reload of file and usage of memory session for writing, so the data in json storage not always actual @@ -46,19 +45,25 @@ Install:: Pomegranate implements updates dispatching and checks callback's filters wrapping all callbacks into ``garnet::Callback`` object ----------------- +*********************** πŸ˜‹ Filters ----------------- +*********************** ``Filter`` object is the essential part of Pomegranate, the do state checking and other stuff. Basically, it's ``func`` from ``MyEventBuilder(func=lambda self: )`` but a way more complicated and not stored in EventBuilder, it's stored in callback object -Useful filters +Filters support bitwise operations :: + + # & (conjunction), | (disjunction), ~ (inversion), ^ (exclusive disjunction) + # also: ==, != (idk why) + @bot.on(MessageText.exact(".") | MessageText.exact("..")) -1) πŸ“¨ **Messages** +^^^^^^^^^^^^^^^^^^^^^^^ +πŸ“¨ Messages +^^^^^^^^^^^^^^^^^^^^^^^ `Following examples will include pattern` @@ -94,8 +99,19 @@ MessageText or text(``from garnet import text``) includes following comparisons - ``.startswith(x)`` -> ``event.raw_text.startswith(x)`` +``Len`` attribute in ``MessageText`` which has cmp methods:: + + + @bot.on((MessageText.Len <= 14) | (MessageText.Len >= 88)) + + + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +πŸ‘€ CurrentState class +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``from garnet import CurrentState`` -2) πŸ‘€ **CurrentState class** [``from garnet import CurrentState``] Once great minds decided that state checking will be in filters without adding ``state`` as handler decorator parameter and further storing state in ``callback.(arg)`` ``CurrentState`` class methods return ``Filter``. There are two problems that Filter object really solves, ``Filter``'s function can be any kind of callable(async,sync), filters also have a flag ``requires_context``, FSMProxy is passed if true @@ -107,8 +123,9 @@ Includes following methods all returning - ``CurrentState == [x, y, z]`` -> ``await context.get_state() in [x, y, z]`` - ``CurrentState == all`` or ``CurrentState == any`` -> ``await context.get_state() is not None`` - -3) πŸ¦” Custom **Filter** +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +πŸ¦” Custom Filter +^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you want to write your own filter, do it. @@ -129,9 +146,20 @@ If you want to write your own filter, do it. So the handler can take strict ``context`` argument and also ignore it ------------------------ -On start|finish ------------------------ +^^^^ +Also +^^^^ + +There're file extension filters in ``garnet.filters.file_ext::File``, import as ``from garnet.filters import File`` + +Some of filters are ported from ``telethon.utils`` as ``File.is_gif``, ``Filter.is_audio``, ``Filter.is_image``, ``Filter.is_video`` + +And bunch of file extensions such as ``File.png``, ``File.ogg`` which are filters. + + +***************************** +On start|finish|background +***************************** ``garnet::TelegramClient`` contains three lists on_start on_background and on_finish, their instance is ``PseudoFrozenList`` which freezes at calling ``.run_until_disconnected`` ``PseudoFrozenList`` has three main methods:: @@ -139,11 +167,12 @@ On start|finish .append(*items) .remove(*items) .freeze() - .__call__(func) # for shiny decorator + .__call__() => (func) => (wrapped_func) # for shiny decorator ``items`` in case of TelegramClient means unpacked container of async-defined functions taking on position arguments -Usage example: +**Usage:** + .. code-block:: python @@ -163,7 +192,7 @@ Usage example: bot.on_finish.append(db.close_pool) ... - @bot.on_background + @bot.on_background() async def xyz(cl: TelegramClient): while True: ... @@ -171,9 +200,9 @@ Usage example: bot.run_until_connected() -------------------------------------------------- +**************************************************** πŸ“¦ Router and Migrating to garnet using Router -------------------------------------------------- +**************************************************** Think of router as just a dummy container of handlers(callbacks) @@ -203,9 +232,7 @@ Think of router as just a dummy container of handlers(callbacks) The advantage of routers is evidence of registering handlers when you have module-separated handlers. `events.register` was doing well, but blindly importing modules to register handlers and don't use them(modules) doesn't seem like a good idea. - -Example of registering router in bot application - +**Example of registering router in bot application** .. code-block:: python @@ -235,7 +262,7 @@ Example of registering router in bot application tg.bind_routers(messages, cb_query) ... -`TelethonRouter` and `Router` both have following remarkable methods: +``TelethonRouter`` and ``Router`` both have following methods: :: @@ -245,9 +272,9 @@ Example of registering router in bot application .message_edited_handler(*filters) .album_handler(*filters) --------------------- +********************* 🍬 Context magic --------------------- +********************* One of the sweetest parts of garnet. Using `contextvars` we reach incredibly beautiful code :D *this is not FSMContext don't confuse with context magic provided by contextvars* @@ -268,37 +295,55 @@ As an example, bot that doesn't requires `TelegramClient` to answer messages dir await reply("Ok") +``garnet.functions.messages`` contains ``current`` class with handy shortcuts: + +.. code-block:: python + + from garnet.functions.messages import current + + current.text # raw text + current.fmt_text # formatted text according to default parse mode + current.chat # current chat + current.chat_id # current chat identifier + ------------------ +****************** What's more ❓ ------------------ +****************** -Class-based handlers are also can be implemented with garnet conveniently. Use your imagination and ``garnet::callbacks::base::Callback`` as a parent class +1. ``garner.client::TelegramClient.conf`` is an attribute for your stuff you should share "globally". Be careful using it. -Awesome bitwise operation supported filters(I highly recommend to use them):: - # & (conjunction), | (disjunction), ~ (inversion), ^ (exclusive disjunction) - # also: ==, != (idk why) - @bot.on(MessageText.exact(".") | MessageText.exact("..")) +2. Garnet can patch ``TLRequest.__await__`` method. To do something like: -``Len`` attribute in ``MessageText`` which has cmp methods:: +.. code-block:: python + from garnet.patch_tl_methods import install + from telethon.tl.functions.users import GetUsersRequest - @bot.on((MessageText.Len <= 14) | (MessageText.Len >= 88)) + install() + + for user in await GetUsersRequest(["martin_winks", "YOURUSERNAME"]): + print(user.username) -Using `client = TelegramClient.start` assignment and start client on the fly, make annotation or typing.cast to have better hints. +Just to have fun with debugging something with raw API. ---------------- -About ---------------- +******************* +Contacts/Community +******************* -You can find me in tg by `@martin_winks `_ and yeah I receive donates as well as all contributors do(support `lonamiwebs `_ and `JRootJunior `_). +You can find me on telegram by `@martin_winks `_ +Our small telegram `group `_ ---------------------- +********************** πŸ€— Credits ---------------------- +********************** Finite-state machine was ported from cool BotAPI library 'aiogram', special thanks to Alex_ + +Support lonamiwebs: `lonamiwebs `_ + +Support aiogram project: `JRootJunior `_