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.
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/PKG-INFO +1 -1
- aiogram_tool-1.2.3/docs/en/setup.md +128 -0
- aiogram_tool-1.2.3/docs/en/storages.md +215 -0
- aiogram_tool-1.2.3/docs/ru/setup.md +106 -0
- aiogram_tool-1.2.3/docs/ru/storages.md +209 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/callback_data/basic.py +1 -1
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/callback_data/custom_expired_answer.py +1 -1
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/callback_data/redis_storage.py +1 -1
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/depend/classes.py +1 -1
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/depend/filter_and_exit.py +1 -1
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/depend/override.py +1 -1
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/depend/scopes.py +1 -1
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/depend/subdepends.py +1 -1
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/limit/advanced.py +1 -1
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/limit/basic.py +1 -1
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/examples/limit/limit_algorithms.py +1 -1
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/pyproject.toml +1 -1
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/.github/workflows/main.yml +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/.github/workflows/release.yml +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/.gitignore +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/.pre-commit-config.yaml +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/.python-version +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/CONTRIBUTING.md +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/LICENSE.md +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/README.md +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/__init__.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/storage/__init__.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/storage/base.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/storage/impl/__init__.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/storage/impl/file.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/storage/impl/memory.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/storage/impl/redis.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/__init__.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/callback_data/__init__.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/callback_data/answer.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/callback_data/filter.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/__init__.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/components/__init__.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/components/exit.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/components/filter.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/components/inner_middleware.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/components/outer_middleware.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/depend.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/storage/__init__.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/storage/memory.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/tool.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/types/__init__.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/types/enums.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/types/exceptions.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/types/schema.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/utils/__init__.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/utils/inspect.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/utils/registry_manager.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/utils/resolver.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/utils/scope_registry.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/utils/stack_manager.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/__init__.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/answer.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/filter.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/rate_limit/__init__.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/rate_limit/base.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/rate_limit/fixed_window.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/rate_limit/sliding_window.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/rate_limit/token_bucket.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/schema.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/tool.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/setup.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/types.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/docs/en/callback_data.md +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/docs/en/depend.md +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/docs/en/limit.md +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/docs/ru/callback_data.md +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/docs/ru/depend.md +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/docs/ru/limit.md +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/pytest.ini +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/__init__.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/callback_data/__init__.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/callback_data/conftest.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/callback_data/test_filter.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/callback_data/test_pack.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/callback_data/test_unique_id.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/callback_data/test_with_handler.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/conftest.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/__init__.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/conftest.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_classes.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_exit.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_filter.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_from.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_generator.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_lambda.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_middleware_data.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_override.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_scopes.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_setup.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_singleton_lock.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/depend/test_subdepend.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/limit/__init__.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/limit/conftest.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/limit/test_rate_limits.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/limit/test_setup.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/storage/__init__.py +0 -0
- {aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/tests/storage/test_storages.py +0 -0
- {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.
|
|
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` требует существования файла и запрещает `&` в ключах
|
|
@@ -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("
|
|
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("
|
|
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("
|
|
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("
|
|
10
|
+
bot = Bot("123456:TEST-PLACEHOLDER-TOKEN")
|
|
11
11
|
dp = Dispatcher()
|
|
12
12
|
|
|
13
13
|
|
|
@@ -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("
|
|
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("
|
|
12
|
+
bot = Bot("123456:TEST-PLACEHOLDER-TOKEN")
|
|
13
13
|
dp = Dispatcher()
|
|
14
14
|
|
|
15
15
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/components/inner_middleware.py
RENAMED
|
File without changes
|
{aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/components/outer_middleware.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/depend/utils/registry_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/rate_limit/fixed_window.py
RENAMED
|
File without changes
|
{aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/rate_limit/sliding_window.py
RENAMED
|
File without changes
|
{aiogram_tool-1.2.2 → aiogram_tool-1.2.3}/aiogram_tool/tools/limit/rate_limit/token_bucket.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|