feat(helpers): implement new descriptor with default value getter (#336)

* feat(helpers): implement new descriptor with default value getter

* perf(descriptor): use weakref

refuse weak reference to a value in WeakRefDict instead of polluting instance namespace

* chore(descriptor): rename descriptor class

rename `DefaultProperty` to `Default`

* style(fmt): lint code
This commit is contained in:
Martin Winks 2020-05-31 19:01:28 +04:00 committed by GitHub
parent 9f11afda5b
commit aed3642385
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 112 additions and 50 deletions

View file

@ -8,6 +8,7 @@ from typing import Any, AsyncGenerator, Callable, ClassVar, Optional, Type, Type
from aiogram.utils.exceptions import TelegramAPIError
from ....utils.helper import Default
from ...methods import Response, TelegramMethod
from ..telegram import PRODUCTION, TelegramAPIServer
@ -20,47 +21,10 @@ class BaseSession(abc.ABC):
# global session timeout
default_timeout: ClassVar[float] = 60.0
_api: TelegramAPIServer
_json_loads: _JsonLoads
_json_dumps: _JsonDumps
_timeout: float
@property
def api(self) -> TelegramAPIServer:
return getattr(self, "_api", PRODUCTION) # type: ignore
@api.setter
def api(self, value: TelegramAPIServer) -> None:
self._api = value
@property
def json_loads(self) -> _JsonLoads:
return getattr(self, "_json_loads", json.loads) # type: ignore
@json_loads.setter
def json_loads(self, value: _JsonLoads) -> None:
self._json_loads = value # type: ignore
@property
def json_dumps(self) -> _JsonDumps:
return getattr(self, "_json_dumps", json.dumps) # type: ignore
@json_dumps.setter
def json_dumps(self, value: _JsonDumps) -> None:
self._json_dumps = value # type: ignore
@property
def timeout(self) -> float:
return getattr(self, "_timeout", self.__class__.default_timeout) # type: ignore
@timeout.setter
def timeout(self, value: float) -> None:
self._timeout = value
@timeout.deleter
def timeout(self) -> None:
if hasattr(self, "_timeout"):
del self._timeout
api: Default[TelegramAPIServer] = Default(PRODUCTION)
json_loads: Default[_JsonLoads] = Default(json.loads)
json_dumps: Default[_JsonDumps] = Default(json.dumps)
timeout: Default[float] = Default(fget=lambda self: float(self.__class__.default_timeout))
@classmethod
def raise_for_status(cls, response: Response[T]) -> None:

View file

@ -14,7 +14,10 @@ Example:
<<< ['barItem', 'bazItem', 'fooItem', 'lorem']
"""
import inspect
from typing import Any, Callable, Iterable, List, Optional, Union, cast
from typing import Any, Callable, Generic, Iterable, List, Optional, TypeVar, Union, cast
from weakref import WeakKeyDictionary
T = TypeVar("T")
PROPS_KEYS_ATTR_NAME = "_props_keys"
@ -233,3 +236,56 @@ class OrderedHelper(Helper, metaclass=OrderedHelperMeta):
else:
result.append(value)
return result
class Default(Generic[T]):
"""
Descriptor that holds default value getter
Example:
>>> class MyClass:
... att = Default("dflt")
...
>>> my_instance = MyClass()
>>> my_instance.att = "not dflt"
>>> my_instance.att
'not dflt'
>>> MyClass.att
'dflt'
>>> del my_instance.att
>>> my_instance.att
'dflt'
>>>
Intended to be used as a class attribute and only internally.
"""
__slots__ = "fget", "_descriptor_instances"
def __init__(
self, default: Optional[T] = None, *, fget: Optional[Callable[[Any], T]] = None,
) -> None:
self.fget = fget or (lambda _: cast(T, default))
self._descriptor_instances = WeakKeyDictionary() # type: ignore
def __get__(self, instance: Any, owner: Any) -> T:
if instance is None:
return self.fget(instance)
return self._descriptor_instances.get(instance, self.fget(instance))
def __set__(self, instance: Any, value: T) -> None:
if instance is None or isinstance(instance, type):
raise AttributeError(
"Instance cannot be class or None. Setter must be called from a class."
)
self._descriptor_instances[instance] = value
def __delete__(self, instance: Any) -> None:
if instance is None or isinstance(instance, type):
raise AttributeError(
"Instance cannot be class or None. Deleter must be called from a class."
)
self._descriptor_instances.pop(instance, None)