aiogram-tool 1.2.2__tar.gz → 1.2.3__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 (104) hide show
  1. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/PKG-INFO +1 -1
  2. aiogram_tool-1.2.3/docs/en/setup.md +128 -0
  3. aiogram_tool-1.2.3/docs/en/storages.md +215 -0
  4. aiogram_tool-1.2.3/docs/ru/setup.md +106 -0
  5. aiogram_tool-1.2.3/docs/ru/storages.md +209 -0
  6. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/callback_data/basic.py +1 -1
  7. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/callback_data/custom_expired_answer.py +1 -1
  8. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/callback_data/redis_storage.py +1 -1
  9. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/depend/classes.py +1 -1
  10. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/depend/filter_and_exit.py +1 -1
  11. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/depend/override.py +1 -1
  12. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/depend/scopes.py +1 -1
  13. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/depend/subdepends.py +1 -1
  14. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/limit/advanced.py +1 -1
  15. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/limit/basic.py +1 -1
  16. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/limit/limit_algorithms.py +1 -1
  17. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/pyproject.toml +1 -1
  18. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/.github/workflows/main.yml +0 -0
  19. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/.github/workflows/release.yml +0 -0
  20. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/.gitignore +0 -0
  21. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/.pre-commit-config.yaml +0 -0
  22. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/.python-version +0 -0
  23. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/CONTRIBUTING.md +0 -0
  24. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/LICENSE.md +0 -0
  25. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/README.md +0 -0
  26. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/__init__.py +0 -0
  27. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/storage/__init__.py +0 -0
  28. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/storage/base.py +0 -0
  29. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/storage/impl/__init__.py +0 -0
  30. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/storage/impl/file.py +0 -0
  31. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/storage/impl/memory.py +0 -0
  32. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/storage/impl/redis.py +0 -0
  33. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/__init__.py +0 -0
  34. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/callback_data/__init__.py +0 -0
  35. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/callback_data/answer.py +0 -0
  36. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/callback_data/filter.py +0 -0
  37. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/__init__.py +0 -0
  38. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/components/__init__.py +0 -0
  39. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/components/exit.py +0 -0
  40. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/components/filter.py +0 -0
  41. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/components/inner_middleware.py +0 -0
  42. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/components/outer_middleware.py +0 -0
  43. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/depend.py +0 -0
  44. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/storage/__init__.py +0 -0
  45. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/storage/memory.py +0 -0
  46. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/tool.py +0 -0
  47. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/types/__init__.py +0 -0
  48. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/types/enums.py +0 -0
  49. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/types/exceptions.py +0 -0
  50. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/types/schema.py +0 -0
  51. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/utils/__init__.py +0 -0
  52. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/utils/inspect.py +0 -0
  53. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/utils/registry_manager.py +0 -0
  54. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/utils/resolver.py +0 -0
  55. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/utils/scope_registry.py +0 -0
  56. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/utils/stack_manager.py +0 -0
  57. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/__init__.py +0 -0
  58. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/answer.py +0 -0
  59. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/filter.py +0 -0
  60. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/rate_limit/__init__.py +0 -0
  61. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/rate_limit/base.py +0 -0
  62. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/rate_limit/fixed_window.py +0 -0
  63. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/rate_limit/sliding_window.py +0 -0
  64. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/rate_limit/token_bucket.py +0 -0
  65. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/schema.py +0 -0
  66. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/tool.py +0 -0
  67. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/setup.py +0 -0
  68. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/types.py +0 -0
  69. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/docs/en/callback_data.md +0 -0
  70. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/docs/en/depend.md +0 -0
  71. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/docs/en/limit.md +0 -0
  72. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/docs/ru/callback_data.md +0 -0
  73. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/docs/ru/depend.md +0 -0
  74. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/docs/ru/limit.md +0 -0
  75. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/pytest.ini +0 -0
  76. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/__init__.py +0 -0
  77. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/callback_data/__init__.py +0 -0
  78. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/callback_data/conftest.py +0 -0
  79. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/callback_data/test_filter.py +0 -0
  80. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/callback_data/test_pack.py +0 -0
  81. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/callback_data/test_unique_id.py +0 -0
  82. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/callback_data/test_with_handler.py +0 -0
  83. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/conftest.py +0 -0
  84. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/__init__.py +0 -0
  85. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/conftest.py +0 -0
  86. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_classes.py +0 -0
  87. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_exit.py +0 -0
  88. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_filter.py +0 -0
  89. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_from.py +0 -0
  90. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_generator.py +0 -0
  91. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_lambda.py +0 -0
  92. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_middleware_data.py +0 -0
  93. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_override.py +0 -0
  94. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_scopes.py +0 -0
  95. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_setup.py +0 -0
  96. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_singleton_lock.py +0 -0
  97. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_subdepend.py +0 -0
  98. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/limit/__init__.py +0 -0
  99. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/limit/conftest.py +0 -0
  100. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/limit/test_rate_limits.py +0 -0
  101. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/limit/test_setup.py +0 -0
  102. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/storage/__init__.py +0 -0
  103. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/storage/test_storages.py +0 -0
  104. {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: aiogram-tool
3
- Version: 1.2.2
3
+ Version: 1.2.3
4
4
  Summary: Tools which doing work with aiogram simple
5
5
  Project-URL: Repository, https://github.com/shayzi3/aiogram_tool
6
6
  Project-URL: Contributing, https://github.com/shayzi3/aiogram_tool/blob/master/CONTRIBUTING.md
@@ -0,0 +1,128 @@
1
+ # aiogram_tool_setup
2
+
3
+ `aiogram_tool_setup` is the main entry point of the library. It registers all tools on the aiogram dispatcher and must be called **before** starting polling / webhooks.
4
+
5
+ - **Module:** `aiogram_tool.tools.setup`
6
+ - **Requirements:** Python 3.11+, aiogram 3.x
7
+
8
+ ## Signature
9
+
10
+ ```python
11
+ def aiogram_tool_setup(
12
+ dispatcher: Dispatcher,
13
+ tools: Iterable[BaseTool],
14
+ ) -> None:
15
+ """Main function for registering all tools"""
16
+ ```
17
+
18
+ ### Parameters
19
+
20
+ | Parameter | Type | Description |
21
+ |---|---|---|
22
+ | `dispatcher` | `aiogram.Dispatcher` | The aiogram 3.x dispatcher instance the tools will be registered on |
23
+ | `tools` | `Iterable[BaseTool]` | An iterable (usually a list) of tools implementing the `BaseTool` interface |
24
+
25
+ The function returns nothing (`None`).
26
+
27
+ ## BaseTool
28
+
29
+ All library tools inherit from the abstract base class:
30
+
31
+ ```python
32
+ class BaseTool(ABC):
33
+ @abstractmethod
34
+ def setup(self, dispatcher: Dispatcher) -> None:
35
+ raise NotImplementedError
36
+ ```
37
+
38
+ To write your own tool, implement the `setup` method, which subscribes filters/middlewares on the dispatcher:
39
+
40
+ ```python
41
+ from aiogram import Dispatcher
42
+ from aiogram_tool.tools.setup import BaseTool
43
+
44
+
45
+ class MyTool(BaseTool):
46
+ def setup(self, dispatcher: Dispatcher) -> None:
47
+ # e.g. dispatcher.message.outer_middleware(...)
48
+ ...
49
+ ```
50
+
51
+ ## Validation
52
+
53
+ - `dispatcher` is not an instance of `Dispatcher` → `TypeError("Invalid type for dispatcher")`
54
+ - An item from `tools` is not an instance of `BaseTool` → `TypeError(f"Invalid type for tool {tool}")`
55
+
56
+ Tools are invoked in the order they appear in the iterable.
57
+
58
+ ## Available tools
59
+
60
+ | Tool | Import | Purpose |
61
+ |---|---|---|
62
+ | `DependTool` | `aiogram_tool.tools.depend` | FastAPI-style dependency injection |
63
+ | `RateLimitTool` | `aiogram_tool.tools.limit` | Rate limiting for handlers |
64
+ | `LongCallbackData` / `CallbackDataAnswer` | `aiogram_tool.tools.callback_data` | Bypassing the 64-byte `callback_data` limit (used as a base class, see the callback_data documentation) |
65
+
66
+ Details: [depend.md](depend.md), [limit.md](limit.md), [callback_data.md](callback_data.md).
67
+
68
+ ## Examples
69
+
70
+ ### Basic usage
71
+
72
+ ```python
73
+ from aiogram import Bot, Dispatcher
74
+ from aiogram_tool.tools.setup import aiogram_tool_setup
75
+ from aiogram_tool.tools.depend import DependTool
76
+ from aiogram_tool.tools.limit import RateLimitTool
77
+
78
+ bot = Bot("YOUR_TOKEN_HERE")
79
+ dp = Dispatcher()
80
+
81
+ aiogram_tool_setup(dp, [DependTool(), RateLimitTool()])
82
+
83
+ await dp.start_polling(bot)
84
+ ```
85
+
86
+ ### With custom infrastructure
87
+
88
+ Many tools accept constructor parameters (e.g. a storage or a scope registry) that are passed before `setup` is called:
89
+
90
+ ```python
91
+ from redis.asyncio import Redis as AsyncRedis
92
+
93
+ from aiogram_tool.tools.setup import aiogram_tool_setup
94
+ from aiogram_tool.tools.depend import DependTool, ScopeRegistry
95
+ from aiogram_tool.tools.limit import RateLimitTool
96
+ from aiogram_tool.storage import AsyncRedisLockStorage
97
+
98
+ scope_registry = ScopeRegistry()
99
+ redis = AsyncRedis()
100
+
101
+ aiogram_tool_setup(
102
+ dp,
103
+ [
104
+ DependTool(scope_registry=scope_registry),
105
+ RateLimitTool(storage=AsyncRedisLockStorage(redis=redis)),
106
+ ],
107
+ )
108
+ ```
109
+
110
+ > See the documentation of the corresponding tool for tool constructor signatures.
111
+
112
+ ### Registering multiple instances
113
+
114
+ The order in the list determines the registration order on the dispatcher:
115
+
116
+ ```python
117
+ aiogram_tool_setup(dp, [RateLimitTool(...), DependTool(...)])
118
+ ```
119
+
120
+ ## Errors
121
+
122
+ ```python
123
+ aiogram_tool_setup(bot, [DependTool()])
124
+ # TypeError: Invalid type for dispatcher
125
+
126
+ aiogram_tool_setup(dp, [SomeRandomObject()])
127
+ # TypeError: Invalid type for tool <SomeRandomObject ...>
128
+ ```
@@ -0,0 +1,215 @@
1
+ # Storages
2
+
3
+ The `aiogram_tool.storage` module provides backends for storing tool state (rate limiting, long callback data, etc.) and locks.
4
+
5
+ - **Package:** `aiogram_tool/storage/`
6
+ - **Public imports:** `aiogram_tool.storage` re-exports all classes (`FileStorage`, `FileLockStorage`, `MemoryStorage`, `MemoryLockStorage`, `AsyncRedisStorage`, `AsyncRedisLockStorage`)
7
+
8
+ ## Overview
9
+
10
+ | Storage | Inheritance | Persistence | Locks |
11
+ |---|---|---|---|
12
+ | `MemoryStorage` | `BaseStorage` | In memory (lost on restart) | ❌ |
13
+ | `MemoryLockStorage` | `MemoryStorage`, `BaseLockStorage` | In memory | ✅ |
14
+ | `AsyncRedisStorage` | `BaseStorage` | Redis (survives restart, shared across instances) | ❌ |
15
+ | `AsyncRedisLockStorage` | `AsyncRedisStorage`, `BaseLockStorage` | Redis | ✅ |
16
+ | `FileStorage` | `MemoryStorage` | Local file | ❌ |
17
+ | `FileLockStorage` | `FileStorage`, `BaseLockStorage` | Local file + memory | ✅ |
18
+
19
+ ## Interfaces
20
+
21
+ ### `BaseStorage` (`aiogram_tool.storage.base`)
22
+
23
+ ```python
24
+ class BaseStorage(ABC):
25
+ async def set_value(self, key: str, value: str) -> None: ...
26
+ async def get_value(self, key: str) -> str | _MISSING: ...
27
+ ```
28
+
29
+ - `get_value` returns a `str` or the `_MISSING` sentinel (`aiogram_tool.types._MISSING`) if the key is absent. Do not use `None` as an absence check.
30
+
31
+ ### `BaseLockStorage`
32
+
33
+ ```python
34
+ class BaseLockStorage(BaseStorage):
35
+ async def lock(self, key: str) -> AbstractAsyncContextManager[None]: ...
36
+ ```
37
+
38
+ The `lock(key)` method returns an async context manager that locks by key:
39
+
40
+ ```python
41
+ async with storage.lock("my_key"):
42
+ ... # critical section
43
+ ```
44
+
45
+ ---
46
+
47
+ ## MemoryStorage
48
+
49
+ The simplest storage based on `dict` (or rather, any `MutableMapping`). Suitable for development, tests, and bots without persistence requirements.
50
+
51
+ ```python
52
+ MemoryStorage(storage: MutableMapping | None = None)
53
+ ```
54
+
55
+ - `storage` — an optional external mapping (useful for tests and pre-filling).
56
+
57
+ > As a `MutableMapping` you can pass not only a `dict`, but also caches from [`cachetools`](https://github.com/uri-grants/cachetools) — for example, `Cache` or `TTLCache` (with automatic eviction by size or TTL).
58
+
59
+ ```python
60
+ from aiogram_tool.storage import MemoryStorage
61
+
62
+ storage = MemoryStorage()
63
+ await storage.set_value("key", "value")
64
+ value = await storage.get_value("key") # "value"
65
+ ```
66
+
67
+ ## MemoryLockStorage
68
+
69
+ Same as `MemoryStorage`, plus locks based on `asyncio.Lock`. `lock(key)` lazily creates and returns an `asyncio.Lock` for the key, protected by a global internal lock.
70
+
71
+ ```python
72
+ MemoryLockStorage(
73
+ storage: MutableMapping | None = None,
74
+ locks_storage: MutableMapping | None = None,
75
+ )
76
+ ```
77
+
78
+ - `locks_storage` — an external mapping for the locks themselves (optional).
79
+
80
+ ⚠️ Locks only work within the current process — not suitable for multiple bot instances.
81
+
82
+ ```python
83
+ from aiogram_tool.storage import MemoryLockStorage
84
+
85
+ storage = MemoryLockStorage()
86
+ async with storage.lock("user:123"):
87
+ ...
88
+ ```
89
+
90
+ ## AsyncRedisStorage
91
+
92
+ A Redis-backed storage (`redis.asyncio.Redis`). Recommended for production: state survives restarts and is shared between instances.
93
+
94
+ ```python
95
+ AsyncRedisStorage(redis: AsyncRedis, expire: int | None = None)
96
+ ```
97
+
98
+ - `redis` — a `redis.asyncio.Redis` client
99
+ - `expire` — TTL in seconds for each entry; `None` — no expiration
100
+
101
+ ```python
102
+ from redis.asyncio import Redis as AsyncRedis
103
+ from aiogram_tool.storage import AsyncRedisStorage
104
+
105
+ storage = AsyncRedisStorage(redis=AsyncRedis(), expire=3600)
106
+ await storage.set_value("key", "value")
107
+ value = await storage.get_value("key") # "value" (bytes are decoded automatically)
108
+ ```
109
+
110
+ The value passed to `set_value` does not have to be a `str` — it is passed to Redis as is.
111
+
112
+ ## AsyncRedisLockStorage
113
+
114
+ A Redis storage with distributed locks (`redis.asyncio.lock.Lock`).
115
+
116
+ ```python
117
+ AsyncRedisLockStorage(redis: AsyncRedis, expire: int | None = None)
118
+ ```
119
+
120
+ `lock(key)` returns a Redis lock with the key `aigram_tool_lock:{key}`, `timeout=10` and `blocking_timeout=10` (seconds). Suitable for multiple bot instances.
121
+
122
+ ```python
123
+ from redis.asyncio import Redis as AsyncRedis
124
+ from aiogram_tool.storage import AsyncRedisLockStorage
125
+
126
+ storage = AsyncRedisLockStorage(redis=AsyncRedis(), expire=3600)
127
+ async with storage.lock("my_key"):
128
+ ...
129
+ ```
130
+
131
+ ## FileStorage
132
+
133
+ A storage in a text file (format: `key&value` lines). Inherits from `MemoryStorage`.
134
+
135
+ ```python
136
+ FileStorage(file: str, storage: MutableMapping | None = None)
137
+ ```
138
+
139
+ - `file` — path to an existing file; if the file is missing, `FileNotFoundError` is raised
140
+ - `storage` — an optional internal `MutableMapping` cache; if provided, the "file + memory" mode is enabled: reads check the cache first and cache results read from the file
141
+
142
+ Features:
143
+
144
+ - The `&` symbol is forbidden in keys (`ValueError`)
145
+ - Entries are appended to the end of the file (`a`); reads go from the end of the file — the last write for a key wins
146
+ - Values may contain `&` (parsing uses `maxsplit=1`)
147
+
148
+ ```python
149
+ from aiogram_tool.storage import FileStorage
150
+
151
+ storage = FileStorage(file="state.txt", storage={})
152
+ await storage.set_value("key", "value")
153
+ value = await storage.get_value("key")
154
+ ```
155
+
156
+ > The file must exist before the storage is created — create it in advance if necessary (e.g. `Path("state.txt").touch()`).
157
+
158
+ ## FileLockStorage
159
+
160
+ `FileStorage` + locks based on `asyncio.Lock` (same as `MemoryLockStorage`).
161
+
162
+ ```python
163
+ FileLockStorage(
164
+ file: str,
165
+ storage: MutableMapping | None = None,
166
+ locks_storage: MutableMapping | None = None,
167
+ )
168
+ ```
169
+
170
+ Data is persisted in the file, but locks are process-local.
171
+
172
+ ## Passing storages to tools
173
+
174
+ Storages are usually passed to a tool's constructor when calling `aiogram_tool_setup`:
175
+
176
+ ```python
177
+ from redis.asyncio import Redis as AsyncRedis
178
+
179
+ from aiogram_tool.tools.setup import aiogram_tool_setup
180
+ from aiogram_tool.tools.limit import RateLimitTool
181
+ from aiogram_tool.storage import AsyncRedisLockStorage
182
+
183
+ aiogram_tool_setup(
184
+ dp,
185
+ [
186
+ RateLimitTool(storage=AsyncRedisLockStorage(redis=AsyncRedis(), expire=3600)),
187
+ ],
188
+ )
189
+ ```
190
+
191
+ A callback data example:
192
+
193
+ ```python
194
+ from redis.asyncio import Redis as AsyncRedis
195
+ from aiogram_tool.tools.callback_data import LongCallbackData
196
+ from aiogram_tool.storage import AsyncRedisLockStorage
197
+
198
+
199
+ class PersistentData(LongCallbackData, prefix="redis"):
200
+ _storage = AsyncRedisLockStorage(redis=AsyncRedis(), expire=3600)
201
+ user_id: int
202
+ big_context: str
203
+ ```
204
+
205
+ ## Choosing a storage
206
+
207
+ - **Development / tests** → `MemoryStorage` / `MemoryLockStorage`
208
+ - **Production, single instance, no Redis** → `FileStorage` / `FileLockStorage`
209
+ - **Production, multiple instances, or reliability needed** → `AsyncRedisStorage` / `AsyncRedisLockStorage`
210
+
211
+ ## Limitations
212
+
213
+ - Locks of `MemoryLockStorage` and `FileLockStorage` do not work across processes — use `AsyncRedisLockStorage` for multiple workers
214
+ - `FileStorage` performs a full scan of the file on every read (without a cache) — use Redis for large volumes of data
215
+ - `FileStorage` requires an existing file and forbids `&` in keys
@@ -0,0 +1,106 @@
1
+ # aiogram_tool_setup
2
+
3
+ `aiogram_tool_setup` — главная точка входа библиотеки. Она регистрирует все инструменты в диспетчере aiogram и должна вызываться **до** старта пуллинга / вебхуков.
4
+
5
+ ## Сигнатура
6
+
7
+ ```python
8
+ def aiogram_tool_setup(
9
+ dispatcher: Dispatcher,
10
+ tools: Iterable[BaseTool],
11
+ ) -> None:
12
+ """Main function for registering all tools"""
13
+ ```
14
+
15
+ ### Параметры
16
+
17
+ | Параметр | Тип | Описание |
18
+ |---|---|---|
19
+ | `dispatcher` | `aiogram.Dispatcher` | Экземпляр диспетчера aiogram 3.x, в котором будут зарегистрированы инструменты |
20
+ | `tools` | `Iterable[BaseTool]` | Итерируемый набор инструментов (обычно список), реализующих интерфейс `BaseTool` |
21
+
22
+ Функция ничего не возвращает (`None`).
23
+
24
+ ## BaseTool
25
+
26
+ Все инструменты библиотеки наследуются от абстрактного базового класса:
27
+
28
+ ```python
29
+ class BaseTool(ABC):
30
+ @abstractmethod
31
+ def setup(self, dispatcher: Dispatcher) -> None:
32
+ raise NotImplementedError
33
+ ```
34
+
35
+ Чтобы написать собственный инструмент, реализуйте метод `setup`, который сохраняет фильтры/мидлвари в диспетчере:
36
+
37
+ ```python
38
+ from aiogram import Dispatcher
39
+ from aiogram_tool.tools.setup import BaseTool
40
+
41
+
42
+ class MyTool(BaseTool):
43
+ def setup(self, dispatcher: Dispatcher) -> None:
44
+ # например, dispatcher.message.outer_middleware(...)
45
+ ...
46
+ ```
47
+
48
+ Инструменты вызываются в порядке следования в итерируемом объекте.
49
+
50
+ ## Доступные инструменты
51
+
52
+ | Инструмент | Импорт | Назначение |
53
+ |---|---|---|
54
+ | `DependTool` | `aiogram_tool.tools.depend` | Dependency injection в стиле FastAPI |
55
+ | `RateLimitTool` | `aiogram_tool.tools.limit` | Ограничение частоты вызовов хендлеров |
56
+ | `LongCallbackData` / `CallbackDataAnswer` | `aiogram_tool.tools.callback_data` | Обход лимита 64 байт для `callback_data` (используется как базовый класс, см. документацию callback_data) |
57
+
58
+ ## Примеры
59
+
60
+ ### Базовое использование
61
+
62
+ ```python
63
+ from aiogram import Bot, Dispatcher
64
+ from aiogram_tool.tools.setup import aiogram_tool_setup
65
+ from aiogram_tool.tools.depend import DependTool
66
+ from aiogram_tool.tools.limit import RateLimitTool
67
+
68
+ bot = Bot("YOUR_TOKEN_HERE")
69
+ dp = Dispatcher()
70
+
71
+ aiogram_tool_setup(dp, [DependTool(), RateLimitTool()])
72
+
73
+ await dp.start_polling(bot)
74
+ ```
75
+
76
+ ### С кастомной инфраструктурой
77
+
78
+ ```python
79
+ from redis.asyncio import Redis as AsyncRedis
80
+
81
+ from aiogram_tool.tools.setup import aiogram_tool_setup
82
+ from aiogram_tool.tools.depend import DependTool, ScopeRegistry
83
+ from aiogram_tool.tools.limit import RateLimitTool
84
+ from aiogram_tool.storage import AsyncRedisLockStorage
85
+
86
+ scope_registry = ScopeRegistry()
87
+ redis = AsyncRedis()
88
+
89
+ aiogram_tool_setup(
90
+ dp,
91
+ [
92
+ DependTool(scope_registry=scope_registry),
93
+ RateLimitTool(storage=AsyncRedisLockStorage(redis=redis)),
94
+ ],
95
+ )
96
+ ```
97
+
98
+ ## Ошибки
99
+
100
+ ```python
101
+ aiogram_tool_setup(bot, [DependTool()])
102
+ # TypeError: Invalid type for dispatcher
103
+
104
+ aiogram_tool_setup(dp, [SomeRandomObject()])
105
+ # TypeError: Invalid type for tool <SomeRandomObject ...>
106
+ ```
@@ -0,0 +1,209 @@
1
+ # Хранилища (storage)
2
+
3
+ ## Обзор
4
+
5
+ | Хранилище | Наследование | Персистентность | Блокировки |
6
+ |---|---|---|---|
7
+ | `MemoryStorage` | `BaseStorage` | В памяти (теряется при перезапуске) | ❌ |
8
+ | `MemoryLockStorage` | `MemoryStorage`, `BaseLockStorage` | В памяти | ✅ |
9
+ | `AsyncRedisStorage` | `BaseStorage` | Redis (переживает перезапуск, общий для инстансов) | ❌ |
10
+ | `AsyncRedisLockStorage` | `AsyncRedisStorage`, `BaseLockStorage` | Redis | ✅ |
11
+ | `FileStorage` | `MemoryStorage` | Локальный файл | ❌ |
12
+ | `FileLockStorage` | `FileStorage`, `BaseLockStorage` | Локальный файл + память | ✅ |
13
+
14
+ ## Интерфейсы
15
+
16
+ ### `BaseStorage` (`aiogram_tool.storage.base`)
17
+
18
+ ```python
19
+ class BaseStorage(ABC):
20
+ async def set_value(self, key: str, value: str) -> None: ...
21
+ async def get_value(self, key: str) -> str | _MISSING: ...
22
+ ```
23
+
24
+ - `get_value` возвращает `str` или sentinel `_MISSING` (`aiogram_tool.types._MISSING`), если ключа нет. Не используйте `None` как проверку отсутствия.
25
+
26
+ ### `BaseLockStorage`
27
+
28
+ ```python
29
+ class BaseLockStorage(BaseStorage):
30
+ async def lock(self, key: str) -> AbstractAsyncContextManager[None]: ...
31
+ ```
32
+
33
+ Метод `lock(key)` возвращает асинхронный контекстный менеджер блокировки по ключу:
34
+
35
+ ```python
36
+ async with storage.lock("my_key"):
37
+ ... # критическая секция
38
+ ```
39
+
40
+ ---
41
+
42
+ ## MemoryStorage
43
+
44
+ Простейшее хранилище на базе `dict` (точнее — любого `MutableMapping`). Подходит для разработки, тестов и ботов без требований к персистентности.
45
+
46
+ ```python
47
+ MemoryStorage(storage: MutableMapping | None = None)
48
+ ```
49
+
50
+ - `storage` — опциональный внешний словарь (полезно для тестов и предзаполнения).
51
+
52
+ > В качестве `MutableMapping` можно передавать не только `dict`, но и кэши из [`cachetools`](https://github.com/uri-grants/cachetools) — например, `Cache` или `TTLCache` (с автоматическим вытеснением по размеру или TTL).
53
+
54
+ ```python
55
+ from aiogram_tool.storage import MemoryStorage
56
+
57
+ storage = MemoryStorage()
58
+ await storage.set_value("key", "value")
59
+ value = await storage.get_value("key") # "value"
60
+ ```
61
+
62
+ ## MemoryLockStorage
63
+
64
+ То же, что `MemoryStorage`, плюс блокировки на `asyncio.Lock`. `lock(key)` лениво создаёт и возвращает `asyncio.Lock` для ключа под защитой глобального внутреннего лока.
65
+
66
+ ```python
67
+ MemoryLockStorage(
68
+ storage: MutableMapping | None = None,
69
+ locks_storage: MutableMapping | None = None,
70
+ )
71
+ ```
72
+
73
+ - `locks_storage` — внешний словарь для самих локов (опционально).
74
+
75
+ ⚠️ Блокировки действуют только внутри текущего процесса — не подходят для нескольких инстансов бота.
76
+
77
+ ```python
78
+ from aiogram_tool.storage import MemoryLockStorage
79
+
80
+ storage = MemoryLockStorage()
81
+ async with storage.lock("user:123"):
82
+ ...
83
+ ```
84
+ ## AsyncRedisStorage
85
+
86
+ Хранилище на Redis (`redis.asyncio.Redis`). Рекомендуется для продакшена: состояние переживает перезапуск и разделяется между инстансами.
87
+
88
+ ```python
89
+ AsyncRedisStorage(redis: AsyncRedis, expire: int | None = None)
90
+ ```
91
+
92
+ - `redis` — клиент `redis.asyncio.Redis`
93
+ - `expire` — TTL в секундах для каждой записи; `None` — без истечения
94
+
95
+ ```python
96
+ from redis.asyncio import Redis as AsyncRedis
97
+ from aiogram_tool.storage import AsyncRedisStorage
98
+
99
+ storage = AsyncRedisStorage(redis=AsyncRedis(), expire=3600)
100
+ await storage.set_value("key", "value")
101
+ value = await storage.get_value("key") # "value" (bytes автоматически декодируются)
102
+ ```
103
+
104
+ Значение, передаваемое в `set_value`, не обязано быть `str` — оно передаётся в Redis как есть.
105
+
106
+ ## AsyncRedisLockStorage
107
+
108
+ Redis-хранилище с распределёнными блокировками (`redis.asyncio.lock.Lock`).
109
+
110
+ ```python
111
+ AsyncRedisLockStorage(redis: AsyncRedis, expire: int | None = None)
112
+ ```
113
+
114
+ `lock(key)` возвращает Redis-лок с ключом `aigram_tool_lock:{key}`, `timeout=10` и `blocking_timeout=10` (секунды). Подходит для нескольких инстансов бота.
115
+
116
+ ```python
117
+ from redis.asyncio import Redis as AsyncRedis
118
+ from aiogram_tool.storage import AsyncRedisLockStorage
119
+
120
+ storage = AsyncRedisLockStorage(redis=AsyncRedis(), expire=3600)
121
+ async with storage.lock("my_key"):
122
+ ...
123
+ ```
124
+
125
+ ## FileStorage
126
+
127
+ Хранилище в текстовом файле (формат: строки `key&value`). Наследуется от `MemoryStorage`.
128
+
129
+ ```python
130
+ FileStorage(file: str, storage: MutableMapping | None = None)
131
+ ```
132
+
133
+ - `file` — путь к существующему файлу; если файла нет, выбрасывается `FileNotFoundError`
134
+ - `storage` — опциональный внутренний `MutableMapping`-кэш; если передан, включается режим «file + memory»: чтение сначала проверяет кэш и кэширует результаты чтения из файла
135
+
136
+ Особенности:
137
+
138
+ - Символ `&` запрещён в ключах (`ValueError`)
139
+ - Записи добавляются в конец файла (`a`); чтение идёт с конца файла — выигрывает последняя запись по ключу
140
+ - Значения могут содержать `&` (парсинг использует `maxsplit=1`)
141
+
142
+ ```python
143
+ from aiogram_tool.storage import FileStorage
144
+
145
+ storage = FileStorage(file="state.txt", storage={})
146
+ await storage.set_value("key", "value")
147
+ value = await storage.get_value("key")
148
+ ```
149
+
150
+ > Файл должен существовать до создания хранилища — при необходимости создайте его заранее (например, `Path("state.txt").touch()`).
151
+
152
+ ## FileLockStorage
153
+
154
+ `FileStorage` + блокировки на `asyncio.Lock` (как у `MemoryLockStorage`).
155
+
156
+ ```python
157
+ FileLockStorage(
158
+ file: str,
159
+ storage: MutableMapping | None = None,
160
+ locks_storage: MutableMapping | None = None,
161
+ )
162
+ ```
163
+
164
+ Данные персистентны в файле, но блокировки локальны для процесса.
165
+
166
+ ## Как передавать хранилища в инструменты
167
+
168
+ Хранилища обычно передаются конструктору инструмента при вызове `aiogram_tool_setup`:
169
+
170
+ ```python
171
+ from redis.asyncio import Redis as AsyncRedis
172
+
173
+ from aiogram_tool.tools.setup import aiogram_tool_setup
174
+ from aiogram_tool.tools.limit import RateLimitTool
175
+ from aiogram_tool.storage import AsyncRedisLockStorage
176
+
177
+ aiogram_tool_setup(
178
+ dp,
179
+ [
180
+ RateLimitTool(storage=AsyncRedisLockStorage(redis=AsyncRedis(), expire=3600)),
181
+ ],
182
+ )
183
+ ```
184
+
185
+ Пример с callback data:
186
+
187
+ ```python
188
+ from redis.asyncio import Redis as AsyncRedis
189
+ from aiogram_tool.tools.callback_data import LongCallbackData
190
+ from aiogram_tool.storage import AsyncRedisLockStorage
191
+
192
+
193
+ class PersistentData(LongCallbackData, prefix="redis"):
194
+ _storage = AsyncRedisLockStorage(redis=AsyncRedis(), expire=3600)
195
+ user_id: int
196
+ big_context: str
197
+ ```
198
+
199
+ ## Выбор хранилища
200
+
201
+ - **Разработка / тесты** → `MemoryStorage` / `MemoryLockStorage`
202
+ - **Продакшен, один инстанс, без Redis** → `FileStorage` / `FileLockStorage`
203
+ - **Продакшен, несколько инстансов или нужна надёжность** → `AsyncRedisStorage` / `AsyncRedisLockStorage`
204
+
205
+ ## Ограничения
206
+
207
+ - Локи `MemoryLockStorage` и `FileLockStorage` не работают между процессами — для нескольких воркеров используйте `AsyncRedisLockStorage`
208
+ - `FileStorage` выполняет полный проход по файлу при каждом чтении (без кэша) — для больших объёмов данных используйте Redis
209
+ - `FileStorage` требует существования файла и запрещает `&` в ключах
@@ -12,7 +12,7 @@ from aiogram.types import (
12
12
  # Import LongCallbackData
13
13
  from aiogram_tool.tools.callback_data import LongCallbackData
14
14
 
15
- bot = Bot("YOUR_TOKEN_HERE")
15
+ bot = Bot("123456:TEST-PLACEHOLDER-TOKEN")
16
16
  dp = Dispatcher()
17
17
 
18
18
 
@@ -15,7 +15,7 @@ from aiogram_tool.storage.impl.memory import MemoryLockStorage
15
15
  # Import base answer class
16
16
  from aiogram_tool.tools.callback_data import CallbackDataAnswer, LongCallbackData
17
17
 
18
- bot = Bot("YOUR_TOKEN_HERE")
18
+ bot = Bot("123456:TEST-PLACEHOLDER-TOKEN")
19
19
  dp = Dispatcher()
20
20
 
21
21
 
@@ -14,7 +14,7 @@ from redis.asyncio import Redis as AsyncRedis
14
14
  from aiogram_tool.storage import AsyncRedisLockStorage
15
15
  from aiogram_tool.tools.callback_data import LongCallbackData
16
16
 
17
- bot = Bot("YOUR_TOKEN_HERE")
17
+ bot = Bot("123456:TEST-PLACEHOLDER-TOKEN")
18
18
  dp = Dispatcher()
19
19
 
20
20
  # Init Redis storage (expire in 3600 seconds)
@@ -7,7 +7,7 @@ from aiogram.types import Message
7
7
  from aiogram_tool.tools.depend import Depends, DependTool, Scope, ScopeRegistry
8
8
  from aiogram_tool.tools.setup import aiogram_tool_setup
9
9
 
10
- bot = Bot("YOUR_TOKEN_HERE")
10
+ bot = Bot("123456:TEST-PLACEHOLDER-TOKEN")
11
11
  dp = Dispatcher()
12
12
 
13
13
  scope_registry = ScopeRegistry()
@@ -7,7 +7,7 @@ from aiogram.types import Message
7
7
  from aiogram_tool.tools.depend import DependExit, DependFilter, Depends, DependTool
8
8
  from aiogram_tool.tools.setup import aiogram_tool_setup
9
9
 
10
- bot = Bot("YOUR_TOKEN_HERE")
10
+ bot = Bot("123456:TEST-PLACEHOLDER-TOKEN")
11
11
  dp = Dispatcher()
12
12
 
13
13
 
@@ -7,7 +7,7 @@ from aiogram.types import Message
7
7
  from aiogram_tool.tools.depend import Depends, DependTool
8
8
  from aiogram_tool.tools.setup import aiogram_tool_setup
9
9
 
10
- bot = Bot("YOUR_TOKEN_HERE")
10
+ bot = Bot("123456:TEST-PLACEHOLDER-TOKEN")
11
11
  dp = Dispatcher()
12
12
 
13
13
 
@@ -13,7 +13,7 @@ from aiogram_tool.tools.depend import (
13
13
  )
14
14
  from aiogram_tool.tools.setup import aiogram_tool_setup
15
15
 
16
- bot = Bot("YOUR_TOKEN_HERE")
16
+ bot = Bot("123456:TEST-PLACEHOLDER-TOKEN")
17
17
  dp = Dispatcher()
18
18
 
19
19
  # Custom scope registry
@@ -12,7 +12,7 @@ from aiogram_tool.tools.depend import (
12
12
  )
13
13
  from aiogram_tool.tools.setup import aiogram_tool_setup
14
14
 
15
- bot = Bot("YOUR_TOKEN_HERE")
15
+ bot = Bot("123456:TEST-PLACEHOLDER-TOKEN")
16
16
  dp = Dispatcher()
17
17
 
18
18
 
@@ -11,7 +11,7 @@ from aiogram_tool.tools.limit import RateLimitAnswer, RateLimitFilter, RateLimit
11
11
  from aiogram_tool.tools.limit.rate_limit import SlidingWindowRateLimit
12
12
  from aiogram_tool.tools.setup import aiogram_tool_setup
13
13
 
14
- bot = Bot("YOUR_TOKEN_HERE")
14
+ bot = Bot("123456:TEST-PLACEHOLDER-TOKEN")
15
15
  dp = Dispatcher()
16
16
 
17
17
 
@@ -9,7 +9,7 @@ from aiogram_tool.tools.limit import RateLimitFilter, RateLimitTool
9
9
  from aiogram_tool.tools.limit.rate_limit import FixedWindowRateLimit
10
10
  from aiogram_tool.tools.setup import aiogram_tool_setup
11
11
 
12
- bot = Bot("YOUR_TOKEN_HERE")
12
+ bot = Bot("123456:TEST-PLACEHOLDER-TOKEN")
13
13
  dp = Dispatcher()
14
14
 
15
15
 
@@ -13,7 +13,7 @@ from aiogram_tool.tools.limit.rate_limit import (
13
13
  )
14
14
  from aiogram_tool.tools.setup import aiogram_tool_setup
15
15
 
16
- bot = Bot("YOUR_TOKEN_HERE")
16
+ bot = Bot("123456:TEST-PLACEHOLDER-TOKEN")
17
17
  dp = Dispatcher()
18
18
 
19
19
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aiogram-tool"
3
- version = "1.2.2"
3
+ version = "1.2.3"
4
4
  description = "Tools which doing work with aiogram simple"
5
5
  authors = [
6
6
  {name = "Vlad_shayzi1",email = "tecnomega@mail.ru"}
File without changes
File without changes
File without changes
File without changes
File without changes