aiogram_tool 1.0.1__tar.gz → 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/PKG-INFO +5 -8
- {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/README.md +1 -3
- aiogram_tool-1.2.0/aiogram_tool/depend/__init__.py +16 -0
- aiogram_tool-1.2.0/aiogram_tool/depend/components/__init__.py +9 -0
- aiogram_tool-1.2.0/aiogram_tool/depend/components/exit.py +19 -0
- aiogram_tool-1.2.0/aiogram_tool/depend/components/handler.py +35 -0
- aiogram_tool-1.0.1/aiogram_tool/depend/components/middleware.py → aiogram_tool-1.2.0/aiogram_tool/depend/components/inner_middleware.py +19 -16
- aiogram_tool-1.2.0/aiogram_tool/depend/depend.py +63 -0
- aiogram_tool-1.2.0/aiogram_tool/depend/setup.py +42 -0
- aiogram_tool-1.2.0/aiogram_tool/depend/types/enums.py +11 -0
- aiogram_tool-1.2.0/aiogram_tool/depend/types/schema.py +10 -0
- aiogram_tool-1.2.0/aiogram_tool/depend/utils/depend_scope.py +22 -0
- aiogram_tool-1.2.0/aiogram_tool/depend/utils/explore_obj.py +58 -0
- aiogram_tool-1.0.1/aiogram_tool/depend/utils/inject.py → aiogram_tool-1.2.0/aiogram_tool/depend/utils/inject_callback.py +5 -5
- aiogram_tool-1.2.0/aiogram_tool/limit/__init__.py +12 -0
- {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/aiogram_tool/limit/limit.py +23 -24
- aiogram_tool-1.2.0/aiogram_tool/limit/setup.py +25 -0
- {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/aiogram_tool/limit/storage/redis_storage.py +4 -6
- aiogram_tool-1.2.0/aiogram_tool/limit/utils/__init__.py +6 -0
- aiogram_tool-1.2.0/aiogram_tool/limit/utils/callback.py +40 -0
- {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/pyproject.toml +5 -4
- aiogram_tool-1.0.1/aiogram_tool/depend/__init__.py +0 -10
- aiogram_tool-1.0.1/aiogram_tool/depend/components/filter.py +0 -31
- aiogram_tool-1.0.1/aiogram_tool/depend/depend.py +0 -104
- aiogram_tool-1.0.1/aiogram_tool/depend/setup.py +0 -34
- aiogram_tool-1.0.1/aiogram_tool/limit/__init__.py +0 -7
- aiogram_tool-1.0.1/aiogram_tool/limit/setup.py +0 -27
- {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/LICENSE.md +0 -0
- {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/aiogram_tool/__init__.py +0 -0
- {aiogram_tool-1.0.1/aiogram_tool/depend/components → aiogram_tool-1.2.0/aiogram_tool/depend/types}/__init__.py +0 -0
- {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/aiogram_tool/depend/utils/__init__.py +0 -0
- {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/aiogram_tool/limit/storage/__init__.py +0 -0
- {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/aiogram_tool/limit/storage/abstract_storage.py +0 -0
- {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/aiogram_tool/limit/storage/memory_storage.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: aiogram_tool
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Tools which doing work with aiogram simple
|
|
5
5
|
License: MIT
|
|
6
|
-
Keywords: aiogram,rate,throttling,limit,dependency-injection,depends,cooldown,delay
|
|
6
|
+
Keywords: aiogram,rate,throttling,limit,dependency-injection,depends,cooldown,delay,DI
|
|
7
7
|
Author: Vlad_shayzi1
|
|
8
8
|
Author-email: tecnomega@mail.ru
|
|
9
9
|
Requires-Python: >=3.10
|
|
@@ -13,8 +13,8 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
-
Requires-Dist: aiogram (>=3.
|
|
17
|
-
Requires-Dist: redis (>=6.
|
|
16
|
+
Requires-Dist: aiogram (>=3.22.0,<4.0.0)
|
|
17
|
+
Requires-Dist: redis (>=6.4.0,<7.0.0)
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
|
|
20
20
|
## aiogram_tool
|
|
@@ -47,7 +47,4 @@ poetry add aiogram_tool
|
|
|
47
47
|
|
|
48
48
|
## Features `Depend`
|
|
49
49
|
|
|
50
|
-
- Depend
|
|
51
|
-
- Exit from depend before call handler
|
|
52
|
-
- use cache
|
|
53
|
-
|
|
50
|
+
- Depend for Router
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .depend import Depend
|
|
2
|
+
from .setup import setup_depend_tool
|
|
3
|
+
from .components import DependExit, DependHandler
|
|
4
|
+
from .utils.depend_scope import dependency_scope
|
|
5
|
+
from .types.enums import Scope
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Depend",
|
|
10
|
+
"setup_depend_tool",
|
|
11
|
+
"DependExit",
|
|
12
|
+
"DependFilter",
|
|
13
|
+
"DependHandler",
|
|
14
|
+
"dependency_scope",
|
|
15
|
+
"Scope"
|
|
16
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from aiogram.types.base import TelegramObject
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DependExit:
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
event: TelegramObject | None = None,
|
|
9
|
+
**event_kwargs
|
|
10
|
+
) -> None:
|
|
11
|
+
if not hasattr(event, "answer") and event is not None:
|
|
12
|
+
raise ValueError("event haven't method answer")
|
|
13
|
+
|
|
14
|
+
self._event = event
|
|
15
|
+
self._event_kwargs = event_kwargs
|
|
16
|
+
|
|
17
|
+
async def event_answer(self) -> None:
|
|
18
|
+
if self._event is not None:
|
|
19
|
+
await self._event.answer(**self._event_kwargs)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from contextlib import AsyncExitStack
|
|
2
|
+
from aiogram.filters import Filter
|
|
3
|
+
from aiogram.types.base import TelegramObject
|
|
4
|
+
|
|
5
|
+
from ..depend import Depend
|
|
6
|
+
from .exit import DependExit
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DependHandler(Filter):
|
|
11
|
+
def __init__(self, *dependencies: Depend):
|
|
12
|
+
for dep in dependencies:
|
|
13
|
+
if not isinstance(dep, Depend):
|
|
14
|
+
raise TypeError("Invalid type in dependency")
|
|
15
|
+
|
|
16
|
+
self._dependencies = dependencies
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def __call__(self, event: TelegramObject, **kwargs) -> bool:
|
|
20
|
+
kwargs.update({"event": event})
|
|
21
|
+
|
|
22
|
+
async with AsyncExitStack() as stack:
|
|
23
|
+
for depend in self._dependencies:
|
|
24
|
+
result = await depend.call(
|
|
25
|
+
middleware_data=kwargs,
|
|
26
|
+
stack=stack
|
|
27
|
+
)
|
|
28
|
+
if isinstance(result, DependExit):
|
|
29
|
+
await result.event_answer()
|
|
30
|
+
return False
|
|
31
|
+
return True
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
@@ -1,39 +1,42 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Awaitable, Callable, Any
|
|
2
2
|
from contextlib import AsyncExitStack
|
|
3
3
|
|
|
4
|
-
from aiogram.dispatcher.middlewares.base import BaseMiddleware
|
|
5
|
-
from aiogram.types.base import TelegramObject
|
|
6
4
|
from aiogram import Dispatcher
|
|
7
5
|
from aiogram.dispatcher.event.handler import HandlerObject
|
|
6
|
+
from aiogram.types.base import TelegramObject
|
|
7
|
+
from aiogram.dispatcher.middlewares.base import BaseMiddleware
|
|
8
8
|
|
|
9
|
+
from .exit import DependExit
|
|
9
10
|
from ..depend import Depend
|
|
10
|
-
from ..utils.
|
|
11
|
+
from ..utils.inject_callback import inject_parametrs
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class DependMiddleware(BaseMiddleware):
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
class DependInnerMiddleware(BaseMiddleware):
|
|
16
|
+
|
|
19
17
|
async def __call__(
|
|
20
18
|
self,
|
|
21
|
-
handler: Callable[[TelegramObject, dict[str, Any]], Awaitable],
|
|
19
|
+
handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
|
|
22
20
|
event: TelegramObject,
|
|
23
21
|
data: dict[str, Any]
|
|
24
|
-
):
|
|
25
|
-
|
|
26
|
-
dispatcher: Dispatcher = data.get("dispatcher")
|
|
22
|
+
) -> Any:
|
|
23
|
+
data.update({"event": event})
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
dispatcher: Dispatcher = data.get("dispatcher")
|
|
26
|
+
handler_object: HandlerObject = data.get("handler")
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
dependency_override: dict[str, Depend] = dispatcher.get("dependency_override", {})
|
|
29
|
+
|
|
30
|
+
async with AsyncExitStack() as stack:
|
|
32
31
|
inject = await inject_parametrs(
|
|
33
32
|
callback=handler_object.callback,
|
|
34
33
|
dependency_override=dependency_override,
|
|
35
34
|
middleware_data=data,
|
|
36
35
|
stack=stack
|
|
37
36
|
)
|
|
37
|
+
for value in inject.values():
|
|
38
|
+
if isinstance(value, DependExit):
|
|
39
|
+
return await value.event_answer()
|
|
40
|
+
|
|
38
41
|
data.update(inject)
|
|
39
42
|
return await handler(event, data)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
|
|
3
|
+
from typing import Callable, Any
|
|
4
|
+
from contextlib import AsyncExitStack
|
|
5
|
+
|
|
6
|
+
from .utils.explore_obj import ExploreObj
|
|
7
|
+
from .types.schema import ScopeObject
|
|
8
|
+
from .types.enums import Scope
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Depend(ExploreObj):
|
|
13
|
+
app_container = {}
|
|
14
|
+
|
|
15
|
+
def __init__(self, obj: Callable) -> None:
|
|
16
|
+
self.scope = Scope.REQUEST
|
|
17
|
+
|
|
18
|
+
explore_obj = obj
|
|
19
|
+
if not inspect.iscoroutinefunction(obj):
|
|
20
|
+
try:
|
|
21
|
+
scope_object = obj()
|
|
22
|
+
if isinstance(scope_object, ScopeObject):
|
|
23
|
+
explore_obj = scope_object.obj
|
|
24
|
+
self.scope = scope_object.scope
|
|
25
|
+
except:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
super().__init__(obj=explore_obj)
|
|
29
|
+
|
|
30
|
+
async def call(
|
|
31
|
+
self,
|
|
32
|
+
middleware_data: dict[str, Any],
|
|
33
|
+
stack: AsyncExitStack
|
|
34
|
+
) -> Any:
|
|
35
|
+
if self.scope == Scope.APP:
|
|
36
|
+
if self.obj_name in self.app_container:
|
|
37
|
+
return self.app_container[self.obj_name]
|
|
38
|
+
|
|
39
|
+
inject_obj_parameters = {}
|
|
40
|
+
for key, value in self.obj_parameters.items():
|
|
41
|
+
if isinstance(value, Depend):
|
|
42
|
+
inject_obj_parameters[key] = await value.call(middleware_data, stack)
|
|
43
|
+
|
|
44
|
+
elif value is inspect._empty:
|
|
45
|
+
inject_obj_parameters[key] = middleware_data.get(key, None)
|
|
46
|
+
|
|
47
|
+
obj_returning = None
|
|
48
|
+
call_obj_with_params = self.obj(**inject_obj_parameters)
|
|
49
|
+
if self.isasync is False:
|
|
50
|
+
if self.isgenerator is True:
|
|
51
|
+
obj_returning = stack.enter_context(call_obj_with_params)
|
|
52
|
+
else:
|
|
53
|
+
obj_returning = call_obj_with_params
|
|
54
|
+
else:
|
|
55
|
+
if self.isgenerator is True:
|
|
56
|
+
obj_returning = await stack.enter_async_context(call_obj_with_params)
|
|
57
|
+
else:
|
|
58
|
+
obj_returning = await call_obj_with_params
|
|
59
|
+
|
|
60
|
+
if self.scope == Scope.APP:
|
|
61
|
+
if self.obj_name not in self.app_container:
|
|
62
|
+
self.app_container[self.obj_name] = obj_returning
|
|
63
|
+
return obj_returning
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from aiogram import Dispatcher
|
|
2
|
+
from aiogram.dispatcher.event.telegram import TelegramEventObserver
|
|
3
|
+
|
|
4
|
+
from .depend import Depend
|
|
5
|
+
from .components import DependInnerMiddleware
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def setup_depend_tool(
|
|
12
|
+
dispatcher: Dispatcher,
|
|
13
|
+
dependency_override: dict[str, Depend] = {},
|
|
14
|
+
allowed_updates: list[str] = [],
|
|
15
|
+
) -> None:
|
|
16
|
+
if not isinstance(dispatcher, Dispatcher):
|
|
17
|
+
raise TypeError("Invalid type for dispatcher")
|
|
18
|
+
|
|
19
|
+
if dependency_override:
|
|
20
|
+
for dep in dependency_override.values():
|
|
21
|
+
if not isinstance(dep, Depend):
|
|
22
|
+
raise TypeError("Invalid type in dependency_override")
|
|
23
|
+
dispatcher["dependency_override"] = dependency_override
|
|
24
|
+
|
|
25
|
+
if allowed_updates:
|
|
26
|
+
for update in allowed_updates:
|
|
27
|
+
if update not in dispatcher.observers:
|
|
28
|
+
raise TypeError(f"Invalid observer {update}")
|
|
29
|
+
else:
|
|
30
|
+
observer: TelegramEventObserver = dispatcher.observers.get(update)
|
|
31
|
+
observer.middleware(DependInnerMiddleware())
|
|
32
|
+
|
|
33
|
+
else:
|
|
34
|
+
for allow in dispatcher.resolve_used_update_types():
|
|
35
|
+
observer: TelegramEventObserver = getattr(dispatcher, allow)
|
|
36
|
+
observer.middleware(DependInnerMiddleware())
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Callable, ParamSpec, TypeVar
|
|
2
|
+
from functools import wraps
|
|
3
|
+
|
|
4
|
+
from ..types.schema import ScopeObject
|
|
5
|
+
from ..types.enums import Scope
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
P = ParamSpec("P")
|
|
9
|
+
R = TypeVar("R")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def dependency_scope(scope: Scope) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
|
14
|
+
def decorator(obj: Callable[P, R]) -> Callable[P, R]:
|
|
15
|
+
@wraps(obj)
|
|
16
|
+
def wrapper() -> ScopeObject:
|
|
17
|
+
return ScopeObject(
|
|
18
|
+
obj=obj,
|
|
19
|
+
scope=scope
|
|
20
|
+
)
|
|
21
|
+
return wrapper
|
|
22
|
+
return decorator
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
|
|
3
|
+
from typing import Callable, _AnnotatedAlias
|
|
4
|
+
from contextlib import asynccontextmanager, contextmanager
|
|
5
|
+
|
|
6
|
+
from aiogram_tool import depend
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ExploreObj:
|
|
12
|
+
def __init__(self, obj: Callable):
|
|
13
|
+
self.obj = obj
|
|
14
|
+
self.isgenerator = False
|
|
15
|
+
self.isasync = False
|
|
16
|
+
self.obj_parameters: dict[str, depend.Depend] = {}
|
|
17
|
+
self.obj_name = getattr(obj, "__name__", "")
|
|
18
|
+
|
|
19
|
+
if isinstance(self.obj, type):
|
|
20
|
+
raise TypeError("dont valid type. Initialize class")
|
|
21
|
+
|
|
22
|
+
if not callable(self.obj):
|
|
23
|
+
raise TypeError("obj must be a callable")
|
|
24
|
+
|
|
25
|
+
if not inspect.isfunction(self.obj):
|
|
26
|
+
self.obj_name = self.obj.__class__.__name__
|
|
27
|
+
self.obj = getattr(self.obj, "__call__")
|
|
28
|
+
|
|
29
|
+
self.__rule_for_call_obj()
|
|
30
|
+
self.__find_obj_parameters()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def __rule_for_call_obj(self) -> None:
|
|
34
|
+
if inspect.iscoroutinefunction(self.obj):
|
|
35
|
+
self.isasync = True
|
|
36
|
+
|
|
37
|
+
else:
|
|
38
|
+
if inspect.isasyncgenfunction(self.obj):
|
|
39
|
+
self.isgenerator = True
|
|
40
|
+
self.isasync = True
|
|
41
|
+
self.obj = asynccontextmanager(self.obj)
|
|
42
|
+
|
|
43
|
+
elif inspect.isgeneratorfunction(self.obj):
|
|
44
|
+
self.isgenerator = True
|
|
45
|
+
self.obj = contextmanager(self.obj)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def __find_obj_parameters(self) -> None:
|
|
49
|
+
signature = inspect.signature(self.obj)
|
|
50
|
+
for param, param_spec in signature.parameters.items():
|
|
51
|
+
if isinstance(param_spec.annotation, _AnnotatedAlias):
|
|
52
|
+
types = getattr(param_spec.annotation, "__metadata__")
|
|
53
|
+
for dependency in types:
|
|
54
|
+
if isinstance(dependency, depend.Depend):
|
|
55
|
+
self.obj_parameters[param] = dependency
|
|
56
|
+
else:
|
|
57
|
+
self.obj_parameters[param] = param_spec.default
|
|
58
|
+
|
|
@@ -13,7 +13,7 @@ from ..depend import Depend
|
|
|
13
13
|
|
|
14
14
|
async def depend_call(
|
|
15
15
|
depend: Depend,
|
|
16
|
-
dependency_override: dict[str,
|
|
16
|
+
dependency_override: dict[str, Depend],
|
|
17
17
|
middleware_data: dict[str, Any],
|
|
18
18
|
stack: AsyncExitStack
|
|
19
19
|
) -> Any:
|
|
@@ -30,7 +30,7 @@ async def inject_parametrs(
|
|
|
30
30
|
middleware_data: dict[str, Any],
|
|
31
31
|
dependency_override: dict[str, Depend] = {},
|
|
32
32
|
) -> dict[str, Any]:
|
|
33
|
-
|
|
33
|
+
injected_params = {}
|
|
34
34
|
signature = inspect.signature(callback)
|
|
35
35
|
for key, annotation in signature.parameters.items():
|
|
36
36
|
default_value = annotation.default
|
|
@@ -39,7 +39,7 @@ async def inject_parametrs(
|
|
|
39
39
|
types = getattr(annotation.annotation, "__metadata__")
|
|
40
40
|
for dep in types:
|
|
41
41
|
if isinstance(dep, Depend):
|
|
42
|
-
|
|
42
|
+
injected_params[key] = await depend_call(
|
|
43
43
|
depend=dep,
|
|
44
44
|
dependency_override=dependency_override,
|
|
45
45
|
middleware_data=middleware_data,
|
|
@@ -47,10 +47,10 @@ async def inject_parametrs(
|
|
|
47
47
|
)
|
|
48
48
|
|
|
49
49
|
elif isinstance(default_value, Depend):
|
|
50
|
-
|
|
50
|
+
injected_params[key] = await depend_call(
|
|
51
51
|
depend=default_value,
|
|
52
52
|
dependency_override=dependency_override,
|
|
53
53
|
middleware_data=middleware_data,
|
|
54
54
|
stack=stack
|
|
55
55
|
)
|
|
56
|
-
return
|
|
56
|
+
return injected_params
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .limit import Limit
|
|
2
|
+
from .setup import setup_limit_tool
|
|
3
|
+
from .utils import AnswerCallback
|
|
4
|
+
from .storage import RedisStorage, MemoryStorage
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"Limit",
|
|
8
|
+
"setup_limit_tool",
|
|
9
|
+
"AnswerCallback",
|
|
10
|
+
"RedisStorage",
|
|
11
|
+
"MemoryStorage",
|
|
12
|
+
]
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
|
|
3
|
-
from typing import Callable, Awaitable
|
|
4
3
|
from datetime import datetime, timedelta
|
|
5
4
|
|
|
6
5
|
from aiogram import Dispatcher
|
|
@@ -8,6 +7,7 @@ from aiogram.filters import Filter
|
|
|
8
7
|
from aiogram.types.base import TelegramObject
|
|
9
8
|
from aiogram.dispatcher.event.handler import HandlerObject
|
|
10
9
|
|
|
10
|
+
from .utils.callback import AnswerCallback
|
|
11
11
|
from .storage.abstract_storage import AbstractStorage
|
|
12
12
|
from .storage import MemoryStorage
|
|
13
13
|
|
|
@@ -24,25 +24,24 @@ class Limit(Filter):
|
|
|
24
24
|
days: float = 0,
|
|
25
25
|
all_users: bool = False,
|
|
26
26
|
storage: AbstractStorage | None = None,
|
|
27
|
-
answer_callback:
|
|
27
|
+
answer_callback: AnswerCallback | None = None
|
|
28
28
|
) -> None:
|
|
29
29
|
if not isinstance(all_users, bool):
|
|
30
|
-
raise TypeError("
|
|
30
|
+
raise TypeError("Invalid type for all_users")
|
|
31
31
|
|
|
32
32
|
if storage is not None:
|
|
33
33
|
if not issubclass(type(storage), AbstractStorage):
|
|
34
34
|
raise TypeError(f"Invalid type for storage")
|
|
35
35
|
|
|
36
36
|
if answer_callback is not None:
|
|
37
|
-
if not
|
|
38
|
-
raise TypeError(f"
|
|
37
|
+
if not isinstance(answer_callback, AnswerCallback):
|
|
38
|
+
raise TypeError(f"Invalid type for answer_callback")
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
self.
|
|
42
|
-
self.
|
|
43
|
-
self.all_users = all_users
|
|
40
|
+
self._storage = storage
|
|
41
|
+
self._answer_callback = answer_callback
|
|
42
|
+
self._all_users = all_users
|
|
44
43
|
|
|
45
|
-
self.
|
|
44
|
+
self._time = timedelta(
|
|
46
45
|
seconds=seconds,
|
|
47
46
|
minutes=minutes,
|
|
48
47
|
hours=hours,
|
|
@@ -52,36 +51,36 @@ class Limit(Filter):
|
|
|
52
51
|
|
|
53
52
|
async def __call__(
|
|
54
53
|
self,
|
|
55
|
-
|
|
54
|
+
event: TelegramObject,
|
|
56
55
|
dispatcher: Dispatcher,
|
|
57
56
|
handler: HandlerObject
|
|
58
57
|
) -> bool:
|
|
59
|
-
storage = dispatcher.
|
|
60
|
-
answer_callback = dispatcher.
|
|
58
|
+
storage: AbstractStorage = dispatcher.get("storage", MemoryStorage())
|
|
59
|
+
answer_callback: AnswerCallback | None = dispatcher.get("answer_callback", None)
|
|
61
60
|
|
|
62
|
-
if self.
|
|
63
|
-
storage = self.
|
|
61
|
+
if self._storage is not None:
|
|
62
|
+
storage = self._storage
|
|
64
63
|
|
|
65
|
-
if self.
|
|
66
|
-
answer_callback = self.
|
|
67
|
-
|
|
68
|
-
query = str(
|
|
69
|
-
if self.
|
|
64
|
+
if self._answer_callback is not None:
|
|
65
|
+
answer_callback = self._answer_callback
|
|
66
|
+
|
|
67
|
+
query = str(event.from_user.id) + "@" + handler.callback.__name__
|
|
68
|
+
if self._all_users is True:
|
|
70
69
|
query = "users" + "@" + handler.callback.__name__
|
|
71
70
|
|
|
72
71
|
user_time = await storage.get(query)
|
|
73
72
|
if user_time is None:
|
|
74
|
-
await storage.update(query, datetime.utcnow() + self.
|
|
73
|
+
await storage.update(query, datetime.utcnow() + self._time)
|
|
75
74
|
return True
|
|
76
75
|
|
|
77
76
|
if user_time >= datetime.utcnow():
|
|
78
77
|
time_last = user_time - datetime.utcnow()
|
|
79
78
|
if answer_callback is not None:
|
|
80
|
-
await answer_callback(
|
|
79
|
+
await answer_callback.call(event, self._time, time_last)
|
|
81
80
|
else:
|
|
82
|
-
await
|
|
81
|
+
await event.answer(f"Retry after {round(time_last.total_seconds(), 0)} seconds")
|
|
83
82
|
return False
|
|
84
83
|
|
|
85
|
-
await storage.update(query, datetime.utcnow() + self.
|
|
84
|
+
await storage.update(query, datetime.utcnow() + self._time)
|
|
86
85
|
return True
|
|
87
86
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from aiogram import Dispatcher
|
|
2
|
+
|
|
3
|
+
from .utils.callback import AnswerCallback
|
|
4
|
+
from .storage.abstract_storage import AbstractStorage
|
|
5
|
+
from .storage import MemoryStorage
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def setup_limit_tool(
|
|
10
|
+
dispatcher: Dispatcher,
|
|
11
|
+
storage: AbstractStorage = MemoryStorage(),
|
|
12
|
+
answer_callback: AnswerCallback | None = None
|
|
13
|
+
) -> None:
|
|
14
|
+
if not isinstance(dispatcher, Dispatcher):
|
|
15
|
+
raise TypeError("Invalid type for dispatcher")
|
|
16
|
+
|
|
17
|
+
if not issubclass(type(storage), AbstractStorage):
|
|
18
|
+
raise TypeError("Invalid type for storage")
|
|
19
|
+
|
|
20
|
+
if answer_callback is not None:
|
|
21
|
+
if not isinstance(answer_callback, AnswerCallback):
|
|
22
|
+
raise TypeError("Invalid type for answer_callback")
|
|
23
|
+
|
|
24
|
+
dispatcher["storage"] = storage
|
|
25
|
+
dispatcher["answer_callback"] = answer_callback
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import json
|
|
2
|
-
|
|
3
1
|
from datetime import datetime
|
|
4
2
|
from redis.asyncio import Redis as AsyncRedis
|
|
5
3
|
|
|
@@ -12,13 +10,13 @@ from .abstract_storage import AbstractStorage
|
|
|
12
10
|
class RedisStorage(AbstractStorage):
|
|
13
11
|
def __init__(self, redis: AsyncRedis):
|
|
14
12
|
if not isinstance(redis, AsyncRedis):
|
|
15
|
-
raise TypeError("Invalid type for
|
|
13
|
+
raise TypeError("Invalid type for redis")
|
|
16
14
|
|
|
17
|
-
self.
|
|
15
|
+
self._redis = redis
|
|
18
16
|
|
|
19
17
|
|
|
20
18
|
async def get(self, name: str) -> datetime | None:
|
|
21
|
-
async with self.
|
|
19
|
+
async with self._redis as session:
|
|
22
20
|
data = await session.hget(name="limit_tool", key=name)
|
|
23
21
|
if data is None:
|
|
24
22
|
return None
|
|
@@ -29,7 +27,7 @@ class RedisStorage(AbstractStorage):
|
|
|
29
27
|
|
|
30
28
|
|
|
31
29
|
async def update(self, name: str, value: datetime) -> None:
|
|
32
|
-
async with self.
|
|
30
|
+
async with self._redis as session:
|
|
33
31
|
await session.hset(
|
|
34
32
|
name="limit_tool",
|
|
35
33
|
key=name,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
|
|
3
|
+
from typing import Callable, Any
|
|
4
|
+
|
|
5
|
+
from datetime import timedelta, datetime
|
|
6
|
+
from aiogram.types.base import TelegramObject
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AnswerCallback:
|
|
11
|
+
def __init__(self, obj: Callable[[TelegramObject, timedelta, datetime], Any]):
|
|
12
|
+
self._isasync = False
|
|
13
|
+
self._obj = obj
|
|
14
|
+
|
|
15
|
+
if not callable(self._obj):
|
|
16
|
+
raise ValueError("obj in AnswerCallback must be callable")
|
|
17
|
+
|
|
18
|
+
self.__explore_obj()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def __explore_obj(self) -> None:
|
|
22
|
+
if not inspect.isfunction(self._obj):
|
|
23
|
+
self._obj = getattr(self._obj, "__call__", None)
|
|
24
|
+
if self._obj is None:
|
|
25
|
+
raise ValueError(f"don't find method __call__ at obj")
|
|
26
|
+
|
|
27
|
+
if inspect.iscoroutinefunction(self._obj):
|
|
28
|
+
self._isasync = True
|
|
29
|
+
else:
|
|
30
|
+
self._isasync = False
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def call(
|
|
34
|
+
self,
|
|
35
|
+
event: TelegramObject,
|
|
36
|
+
rate_time: timedelta,
|
|
37
|
+
time_last: datetime
|
|
38
|
+
) -> Any:
|
|
39
|
+
calling = self._obj(event, rate_time, time_last)
|
|
40
|
+
return await calling if self._isasync else calling
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "aiogram_tool"
|
|
3
|
-
version = "1.0
|
|
3
|
+
version = "1.2.0"
|
|
4
4
|
description = "Tools which doing work with aiogram simple"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Vlad_shayzi1",email = "tecnomega@mail.ru"}
|
|
@@ -9,8 +9,8 @@ license = "MIT"
|
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
dependencies = [
|
|
12
|
-
"
|
|
13
|
-
"
|
|
12
|
+
"aiogram (>=3.22.0,<4.0.0)",
|
|
13
|
+
"redis (>=6.4.0,<7.0.0)"
|
|
14
14
|
]
|
|
15
15
|
keywords = [
|
|
16
16
|
"aiogram",
|
|
@@ -20,7 +20,8 @@ keywords = [
|
|
|
20
20
|
"dependency-injection",
|
|
21
21
|
"depends",
|
|
22
22
|
"cooldown",
|
|
23
|
-
"delay"
|
|
23
|
+
"delay",
|
|
24
|
+
"DI"
|
|
24
25
|
]
|
|
25
26
|
|
|
26
27
|
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
from typing import Any
|
|
2
|
-
from contextlib import AsyncExitStack
|
|
3
|
-
|
|
4
|
-
from aiogram.filters import Filter
|
|
5
|
-
from aiogram import Dispatcher
|
|
6
|
-
from aiogram.dispatcher.event.handler import HandlerObject
|
|
7
|
-
|
|
8
|
-
from ..depend import Depend
|
|
9
|
-
from ..utils.inject import inject_parametrs
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class DependFilter(Filter):
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
async def __call__(self, *args: Any, **kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
18
|
-
handler_object: HandlerObject = kwargs.get("handler")
|
|
19
|
-
dispatcher: Dispatcher = kwargs.get("dispatcher")
|
|
20
|
-
|
|
21
|
-
dependency_override: dict[str, Depend] = dispatcher.workflow_data.get("dependency_override", {})
|
|
22
|
-
|
|
23
|
-
kwargs.update({"event": args[0]})
|
|
24
|
-
async with AsyncExitStack() as stack:
|
|
25
|
-
depend_kwargs = await inject_parametrs(
|
|
26
|
-
callback=handler_object.callback,
|
|
27
|
-
dependency_override=dependency_override,
|
|
28
|
-
middleware_data=kwargs,
|
|
29
|
-
stack=stack
|
|
30
|
-
)
|
|
31
|
-
return depend_kwargs
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import inspect
|
|
2
|
-
|
|
3
|
-
from contextlib import (
|
|
4
|
-
asynccontextmanager,
|
|
5
|
-
contextmanager,
|
|
6
|
-
AsyncExitStack
|
|
7
|
-
)
|
|
8
|
-
from typing import (
|
|
9
|
-
Callable,
|
|
10
|
-
Any,
|
|
11
|
-
_AnnotatedAlias
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class Depend:
|
|
17
|
-
def __init__(self, obj: Callable) -> None:
|
|
18
|
-
self.obj = obj
|
|
19
|
-
self.isgenerator = False
|
|
20
|
-
self.isasync = False
|
|
21
|
-
self.obj_name = getattr(obj, "__name__", "")
|
|
22
|
-
self.arguments = {}
|
|
23
|
-
|
|
24
|
-
if isinstance(self.obj, type):
|
|
25
|
-
raise TypeError("dont valid type. Initialize class")
|
|
26
|
-
|
|
27
|
-
if not callable(self.obj):
|
|
28
|
-
raise TypeError("obj must be a callable")
|
|
29
|
-
|
|
30
|
-
if not inspect.isfunction(self.obj):
|
|
31
|
-
self.obj_name = self.obj.__class__.__name__
|
|
32
|
-
self.obj = getattr(self.obj, "__call__")
|
|
33
|
-
|
|
34
|
-
self.__explore_obj()
|
|
35
|
-
self.__explore_signature()
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def __explore_obj(self) -> None:
|
|
39
|
-
if inspect.iscoroutinefunction(self.obj):
|
|
40
|
-
self.isasync = True
|
|
41
|
-
|
|
42
|
-
else:
|
|
43
|
-
if inspect.isasyncgenfunction(self.obj):
|
|
44
|
-
self.isgenerator = True
|
|
45
|
-
self.isasync = True
|
|
46
|
-
self.obj = asynccontextmanager(self.obj)
|
|
47
|
-
|
|
48
|
-
elif inspect.isgeneratorfunction(self.obj):
|
|
49
|
-
self.isgenerator = True
|
|
50
|
-
self.obj = contextmanager(self.obj)
|
|
51
|
-
|
|
52
|
-
else:
|
|
53
|
-
types = []
|
|
54
|
-
for closure in inspect.getclosurevars(self.obj):
|
|
55
|
-
if isinstance(closure, dict):
|
|
56
|
-
types.extend(list(closure.values()))
|
|
57
|
-
|
|
58
|
-
for type_ in types:
|
|
59
|
-
name = getattr(type_, "__name__", None)
|
|
60
|
-
if name in "_AsyncGeneratorContextManager":
|
|
61
|
-
self.isasync = True
|
|
62
|
-
self.isgenerator = True
|
|
63
|
-
|
|
64
|
-
elif name == "_GeneratorContextManager":
|
|
65
|
-
self.isgenerator = True
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def __explore_signature(self) -> None:
|
|
69
|
-
signature = inspect.signature(self.obj)
|
|
70
|
-
for key, annotation in signature.parameters.items():
|
|
71
|
-
default_value = annotation.default
|
|
72
|
-
|
|
73
|
-
if isinstance(annotation.annotation, _AnnotatedAlias):
|
|
74
|
-
types = getattr(annotation.annotation, "__metadata__")
|
|
75
|
-
for dep in types:
|
|
76
|
-
if isinstance(dep, Depend):
|
|
77
|
-
self.arguments[key] = dep
|
|
78
|
-
|
|
79
|
-
elif isinstance(default_value, Depend):
|
|
80
|
-
self.arguments[key] = default_value
|
|
81
|
-
|
|
82
|
-
else:
|
|
83
|
-
self.arguments[key] = annotation.annotation
|
|
84
|
-
|
|
85
|
-
async def call(
|
|
86
|
-
self,
|
|
87
|
-
middleware_data: dict[str, Any],
|
|
88
|
-
stack: AsyncExitStack
|
|
89
|
-
) -> Any:
|
|
90
|
-
kwargs = {}
|
|
91
|
-
for key, value in self.arguments.items():
|
|
92
|
-
if isinstance(value, Depend):
|
|
93
|
-
kwargs[key] = await value.call(middleware_data, stack)
|
|
94
|
-
else:
|
|
95
|
-
kwargs[key] = middleware_data.get(key)
|
|
96
|
-
|
|
97
|
-
if self.isasync is False:
|
|
98
|
-
if self.isgenerator is True:
|
|
99
|
-
return stack.enter_context(self.obj(**kwargs))
|
|
100
|
-
return self.obj(**kwargs)
|
|
101
|
-
else:
|
|
102
|
-
if self.isgenerator is True:
|
|
103
|
-
return await stack.enter_async_context(self.obj(**kwargs))
|
|
104
|
-
return await self.obj(**kwargs)
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
from aiogram import Dispatcher
|
|
2
|
-
from aiogram.dispatcher.event.telegram import TelegramEventObserver
|
|
3
|
-
|
|
4
|
-
from .depend import Depend
|
|
5
|
-
from .components.middleware import DependMiddleware
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def setup_depend_tool(
|
|
12
|
-
dispatcher: Dispatcher,
|
|
13
|
-
dependency_override: dict[str, Depend] = {},
|
|
14
|
-
allowed_updates: list[str] = [],
|
|
15
|
-
middleware: bool = True,
|
|
16
|
-
) -> None:
|
|
17
|
-
if not isinstance(dispatcher, Dispatcher):
|
|
18
|
-
raise TypeError("dispatcher must be Dispatcher type")
|
|
19
|
-
|
|
20
|
-
if dependency_override:
|
|
21
|
-
for dep in dependency_override.values():
|
|
22
|
-
if not isinstance(dep, Depend):
|
|
23
|
-
raise TypeError(f"Invalid type in dependency_override {dep}")
|
|
24
|
-
|
|
25
|
-
if allowed_updates:
|
|
26
|
-
for allow in allowed_updates:
|
|
27
|
-
if allow not in dispatcher.observers:
|
|
28
|
-
raise TypeError(f"observer {allow} not exists")
|
|
29
|
-
|
|
30
|
-
if middleware is True:
|
|
31
|
-
for allow in dispatcher.resolve_used_update_types() or allowed_updates:
|
|
32
|
-
observer: TelegramEventObserver = getattr(dispatcher, allow)
|
|
33
|
-
observer.middleware(DependMiddleware())
|
|
34
|
-
dispatcher.__setitem__("dependency_override", dependency_override)
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import inspect
|
|
2
|
-
|
|
3
|
-
from typing import Callable, Awaitable
|
|
4
|
-
from aiogram.types.base import TelegramObject
|
|
5
|
-
from aiogram import Dispatcher
|
|
6
|
-
|
|
7
|
-
from datetime import datetime, timedelta
|
|
8
|
-
|
|
9
|
-
from .storage.abstract_storage import AbstractStorage
|
|
10
|
-
from .storage import MemoryStorage
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def setup_limit_tool(
|
|
15
|
-
dispatcher: Dispatcher,
|
|
16
|
-
storage: AbstractStorage = MemoryStorage(),
|
|
17
|
-
answer_callback: Callable[[TelegramObject, timedelta, datetime], Awaitable] | None = None
|
|
18
|
-
) -> None:
|
|
19
|
-
if not issubclass(type(storage), AbstractStorage):
|
|
20
|
-
raise TypeError(f"Invalid type for storage")
|
|
21
|
-
|
|
22
|
-
if answer_callback is not None:
|
|
23
|
-
if not inspect.iscoroutinefunction(answer_callback):
|
|
24
|
-
raise TypeError(f"answer_callback must be croutine function")
|
|
25
|
-
|
|
26
|
-
dispatcher.__setitem__("storage", storage)
|
|
27
|
-
dispatcher.__setitem__("answer_callback", answer_callback)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|