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.
Files changed (34) hide show
  1. {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/PKG-INFO +5 -8
  2. {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/README.md +1 -3
  3. aiogram_tool-1.2.0/aiogram_tool/depend/__init__.py +16 -0
  4. aiogram_tool-1.2.0/aiogram_tool/depend/components/__init__.py +9 -0
  5. aiogram_tool-1.2.0/aiogram_tool/depend/components/exit.py +19 -0
  6. aiogram_tool-1.2.0/aiogram_tool/depend/components/handler.py +35 -0
  7. 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
  8. aiogram_tool-1.2.0/aiogram_tool/depend/depend.py +63 -0
  9. aiogram_tool-1.2.0/aiogram_tool/depend/setup.py +42 -0
  10. aiogram_tool-1.2.0/aiogram_tool/depend/types/enums.py +11 -0
  11. aiogram_tool-1.2.0/aiogram_tool/depend/types/schema.py +10 -0
  12. aiogram_tool-1.2.0/aiogram_tool/depend/utils/depend_scope.py +22 -0
  13. aiogram_tool-1.2.0/aiogram_tool/depend/utils/explore_obj.py +58 -0
  14. 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
  15. aiogram_tool-1.2.0/aiogram_tool/limit/__init__.py +12 -0
  16. {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/aiogram_tool/limit/limit.py +23 -24
  17. aiogram_tool-1.2.0/aiogram_tool/limit/setup.py +25 -0
  18. {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/aiogram_tool/limit/storage/redis_storage.py +4 -6
  19. aiogram_tool-1.2.0/aiogram_tool/limit/utils/__init__.py +6 -0
  20. aiogram_tool-1.2.0/aiogram_tool/limit/utils/callback.py +40 -0
  21. {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/pyproject.toml +5 -4
  22. aiogram_tool-1.0.1/aiogram_tool/depend/__init__.py +0 -10
  23. aiogram_tool-1.0.1/aiogram_tool/depend/components/filter.py +0 -31
  24. aiogram_tool-1.0.1/aiogram_tool/depend/depend.py +0 -104
  25. aiogram_tool-1.0.1/aiogram_tool/depend/setup.py +0 -34
  26. aiogram_tool-1.0.1/aiogram_tool/limit/__init__.py +0 -7
  27. aiogram_tool-1.0.1/aiogram_tool/limit/setup.py +0 -27
  28. {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/LICENSE.md +0 -0
  29. {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/aiogram_tool/__init__.py +0 -0
  30. {aiogram_tool-1.0.1/aiogram_tool/depend/components → aiogram_tool-1.2.0/aiogram_tool/depend/types}/__init__.py +0 -0
  31. {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/aiogram_tool/depend/utils/__init__.py +0 -0
  32. {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/aiogram_tool/limit/storage/__init__.py +0 -0
  33. {aiogram_tool-1.0.1 → aiogram_tool-1.2.0}/aiogram_tool/limit/storage/abstract_storage.py +0 -0
  34. {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.1
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.21.0,<4.0.0)
17
- Requires-Dist: redis (>=6.2.0,<7.0.0)
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 without response for Router, handler
51
- - Exit from depend before call handler
52
- - use cache
53
-
50
+ - Depend for Router
@@ -28,6 +28,4 @@ poetry add aiogram_tool
28
28
 
29
29
  ## Features `Depend`
30
30
 
31
- - Depend without response for Router, handler
32
- - Exit from depend before call handler
33
- - use cache
31
+ - 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,9 @@
1
+ from .exit import DependExit
2
+ from .handler import DependHandler
3
+ from .inner_middleware import DependInnerMiddleware
4
+
5
+
6
+ __all__ = [
7
+ "DependExit",
8
+ "DependHandler",
9
+ ]
@@ -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 Any, Callable, Awaitable
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.inject import inject_parametrs
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
- handler_object: HandlerObject = data.get("handler")
26
- dispatcher: Dispatcher = data.get("dispatcher")
22
+ ) -> Any:
23
+ data.update({"event": event})
27
24
 
28
- dependency_override: dict[str, Depend] = dispatcher.workflow_data.get("dependency_override", {})
25
+ dispatcher: Dispatcher = data.get("dispatcher")
26
+ handler_object: HandlerObject = data.get("handler")
29
27
 
30
- data.update({"event": event})
31
- async with AsyncExitStack() as stack:
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,11 @@
1
+ from enum import Enum, auto
2
+
3
+
4
+ class Scope(Enum):
5
+ """
6
+ APP: Caching the result for the entire duration of the application
7
+ \nREQUEST: New result for every request
8
+ """
9
+ APP = auto()
10
+ REQUEST = auto()
11
+
@@ -0,0 +1,10 @@
1
+ from typing import Callable
2
+ from dataclasses import dataclass
3
+
4
+ from .enums import Scope
5
+
6
+
7
+ @dataclass
8
+ class ScopeObject:
9
+ obj: Callable
10
+ scope: Scope
@@ -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, Any],
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
- data = {}
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
- data[key] = await depend_call(
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
- data[key] = await depend_call(
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 data
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: Callable[[TelegramObject, timedelta, datetime], Awaitable] | None = None
27
+ answer_callback: AnswerCallback | None = None
28
28
  ) -> None:
29
29
  if not isinstance(all_users, bool):
30
- raise TypeError("all_users must be bool type")
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 inspect.iscoroutinefunction(answer_callback):
38
- raise TypeError(f"answer_callback must be croutine function")
37
+ if not isinstance(answer_callback, AnswerCallback):
38
+ raise TypeError(f"Invalid type for answer_callback")
39
39
 
40
- # checks types
41
- self.storage = storage
42
- self.answer_callback = answer_callback
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.time = timedelta(
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
- message: TelegramObject,
54
+ event: TelegramObject,
56
55
  dispatcher: Dispatcher,
57
56
  handler: HandlerObject
58
57
  ) -> bool:
59
- storage = dispatcher.workflow_data.get("storage", MemoryStorage())
60
- answer_callback = dispatcher.workflow_data.get("answer_callback")
58
+ storage: AbstractStorage = dispatcher.get("storage", MemoryStorage())
59
+ answer_callback: AnswerCallback | None = dispatcher.get("answer_callback", None)
61
60
 
62
- if self.storage is not None:
63
- storage = self.storage
61
+ if self._storage is not None:
62
+ storage = self._storage
64
63
 
65
- if self.answer_callback is not None:
66
- answer_callback = self.answer_callback
67
-
68
- query = str(message.from_user.id) + "@" + handler.callback.__name__
69
- if self.all_users is True:
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.time)
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(message, self.time, time_last)
79
+ await answer_callback.call(event, self._time, time_last)
81
80
  else:
82
- await message.answer(f"Retry after {round(time_last.total_seconds(), 0)} seconds")
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.time)
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 argument redis")
13
+ raise TypeError("Invalid type for redis")
16
14
 
17
- self.redis = redis
15
+ self._redis = redis
18
16
 
19
17
 
20
18
  async def get(self, name: str) -> datetime | None:
21
- async with self.redis as session:
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.redis as session:
30
+ async with self._redis as session:
33
31
  await session.hset(
34
32
  name="limit_tool",
35
33
  key=name,
@@ -0,0 +1,6 @@
1
+ from .callback import AnswerCallback
2
+
3
+
4
+ __all__ = [
5
+ "AnswerCallback",
6
+ ]
@@ -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.1"
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
- "redis (>=6.2.0,<7.0.0)",
13
- "aiogram (>=3.21.0,<4.0.0)"
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,10 +0,0 @@
1
- from .depend import Depend
2
- from .components.filter import DependFilter
3
- from .setup import setup_depend_tool
4
-
5
-
6
- __all__ = [
7
- "Depend",
8
- "DependFilter",
9
- "setup_depend_tool",
10
- ]
@@ -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,7 +0,0 @@
1
- from .limit import Limit
2
- from .setup import setup_limit_tool
3
-
4
- __all__ = [
5
- "Limit",
6
- "setup_limit_tool"
7
- ]
@@ -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