Storage¶
Pluggable storage backends for callback data. Implement the StorageBackend protocol (or extend BaseStorage) to add your own.
StorageBackend¶
telegram_menu_builder.storage.base.StorageBackend ¶
Bases: Protocol
Protocol defining the interface for storage backends.
All storage implementations must provide these methods to be compatible with the menu builder system.
Example
class MyStorage: ... async def set(self, key: str, data: dict[str, Any], ttl: int | None = None) -> None: ... # Implementation ... pass ... ... async def get(self, key: str) -> dict[str, Any] | None: ... # Implementation ... pass ... ... async def delete(self, key: str) -> bool: ... # Implementation ... pass
Source code in src/telegram_menu_builder/storage/base.py
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | |
set
async
¶
Store data with an optional TTL.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier for the data |
required |
data
|
dict[str, Any]
|
Dictionary to store (must be JSON-serializable) |
required |
ttl
|
int | None
|
Time-to-live in seconds (None = no expiration) |
None
|
Raises:
| Type | Description |
|---|---|
StorageError
|
If storage operation fails |
Source code in src/telegram_menu_builder/storage/base.py
add
async
¶
Atomically store data only if the key is absent (set-if-absent).
This is the single-winner primitive behind
:meth:~telegram_menu_builder.router.MenuRouter.claim: a concurrent set
of callers racing on the same key must see exactly one True return.
An expired (non-live) value counts as absent and may be reclaimed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier for the data |
required |
data
|
dict[str, Any]
|
Dictionary to store (must be JSON-serializable) |
required |
ttl
|
int | None
|
Time-to-live in seconds (None = no expiration) |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if this call stored the value, False if a live (non-expired) |
bool
|
value already existed for the key. |
Raises:
| Type | Description |
|---|---|
StorageError
|
If storage operation fails |
Source code in src/telegram_menu_builder/storage/base.py
get
async
¶
Retrieve data by key.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier for the data |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any] | None
|
Stored dictionary or None if not found/expired |
Raises:
| Type | Description |
|---|---|
StorageError
|
If retrieval operation fails |
Source code in src/telegram_menu_builder/storage/base.py
delete
async
¶
Delete data by key.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier for the data |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if deleted, False if not found |
Raises:
| Type | Description |
|---|---|
StorageError
|
If deletion operation fails |
Source code in src/telegram_menu_builder/storage/base.py
exists
async
¶
Check if key exists in storage.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier to check |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if key exists and hasn't expired |
clear
async
¶
Clear all stored data.
Warning
This will delete ALL data in the storage backend. Use with caution in production environments.
Raises:
| Type | Description |
|---|---|
StorageError
|
If clear operation fails |
Source code in src/telegram_menu_builder/storage/base.py
keys
async
¶
Get all keys, optionally filtered by pattern.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pattern
|
str | None
|
Optional pattern for filtering keys (implementation-specific) |
None
|
Returns:
| Type | Description |
|---|---|
list[str]
|
List of matching keys |
Raises:
| Type | Description |
|---|---|
StorageError
|
If operation fails |
Source code in src/telegram_menu_builder/storage/base.py
BaseStorage¶
telegram_menu_builder.storage.base.BaseStorage ¶
Bases: ABC
Abstract base class for storage backends with common functionality.
This class provides a template and shared utilities for storage implementations. Subclasses only need to implement the abstract methods.
Source code in src/telegram_menu_builder/storage/base.py
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | |
set
abstractmethod
async
¶
add
async
¶
Store data only if the key is absent (default, NON-ATOMIC implementation).
This default exists-then-set implementation is provided so that
custom backends written before add existed keep working without
changes. It is a convenience, not a concurrency primitive.
Warning
This default is NOT atomic: it awaits :meth:exists and then
:meth:set, and another coroutine (or process) can interleave
between the two. Concurrency-safe backends MUST override add with
a genuinely atomic set-if-absent (e.g. Redis SET NX or a
single-transaction conditional INSERT) so that racing callers see
exactly one True.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier for the data |
required |
data
|
dict[str, Any]
|
Dictionary to store (must be JSON-serializable) |
required |
ttl
|
int | None
|
Time-to-live in seconds (None = no expiration) |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if this call stored the value, False if a live (non-expired) |
bool
|
value already existed for the key. |
Raises:
| Type | Description |
|---|---|
StorageError
|
If storage operation fails |
Source code in src/telegram_menu_builder/storage/base.py
get
abstractmethod
async
¶
delete
abstractmethod
async
¶
exists
abstractmethod
async
¶
clear
abstractmethod
async
¶
keys
abstractmethod
async
¶
close
async
¶
Close storage backend and cleanup resources.
Override this method if your storage needs cleanup (connections, files, etc.).
MemoryStorage¶
telegram_menu_builder.storage.memory.MemoryStorage ¶
Bases: BaseStorage
In-memory storage backend using Python dictionaries.
This storage keeps all data in memory and supports TTL-based expiration. Data is lost when the process restarts.
Attributes:
| Name | Type | Description |
|---|---|---|
_data |
dict[str, dict[str, Any]]
|
Internal dictionary storing the data |
_expiry |
dict[str, float]
|
Dictionary mapping keys to expiration timestamps |
Example
storage = MemoryStorage() await storage.set("key1", {"value": 123}, ttl=60) data = await storage.get("key1") print(data) {'value': 123}
Thread Safety
This implementation is NOT thread-safe. If you need concurrent access, consider using locks or a thread-safe storage backend.
Source code in src/telegram_menu_builder/storage/memory.py
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 | |
set
async
¶
Store data in memory with optional TTL.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier for the data |
required |
data
|
dict[str, Any]
|
Dictionary to store |
required |
ttl
|
int | None
|
Time-to-live in seconds (None = no expiration) |
None
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If storage is closed |
Source code in src/telegram_menu_builder/storage/memory.py
add
async
¶
Atomically store data only if the key is absent (set-if-absent).
A key whose TTL has already elapsed is treated as absent and may be
reclaimed. The existence check and the store happen with NO await
between them, so under a single event loop this is effectively atomic:
two coroutines racing on the same key yield exactly one True.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier for the data |
required |
data
|
dict[str, Any]
|
Dictionary to store |
required |
ttl
|
int | None
|
Time-to-live in seconds (None = no expiration) |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
True if this call stored the value, False if a live (non-expired) |
bool
|
value already existed for the key. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If storage is closed |
Source code in src/telegram_menu_builder/storage/memory.py
get
async
¶
Retrieve data from memory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier for the data |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any] | None
|
Stored dictionary or None if not found/expired |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If storage is closed |
Source code in src/telegram_menu_builder/storage/memory.py
delete
async
¶
Delete data from memory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier for the data |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if deleted, False if not found |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If storage is closed |
Source code in src/telegram_menu_builder/storage/memory.py
exists
async
¶
Check if key exists in memory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier to check |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if key exists and hasn't expired |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If storage is closed |
Source code in src/telegram_menu_builder/storage/memory.py
clear
async
¶
Clear all data from memory.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If storage is closed |
keys
async
¶
Get all keys from memory, optionally filtered by pattern.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pattern
|
str | None
|
Optional glob-style pattern (supports * and ?) |
None
|
Returns:
| Type | Description |
|---|---|
list[str]
|
List of matching keys (excluding expired ones) |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If storage is closed |
Source code in src/telegram_menu_builder/storage/memory.py
cleanup_expired
async
¶
Manually cleanup expired entries.
This method is useful for periodic cleanup in long-running applications.
Returns:
| Type | Description |
|---|---|
int
|
Number of expired entries removed |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If storage is closed |
Source code in src/telegram_menu_builder/storage/memory.py
get_stats ¶
Get storage statistics.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dictionary with storage stats (total keys, expired keys, etc.) |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If storage is closed |
Source code in src/telegram_menu_builder/storage/memory.py
close
async
¶
Close storage and free memory.
After closing, the storage cannot be used anymore.
SQLAlchemyStorage¶
telegram_menu_builder.storage.sqlalchemy.SQLAlchemyStorage ¶
Bases: BaseStorage
Async SQL storage backend using SQLAlchemy 2.0 Core.
Stores callback payloads in a single table with columns key (bounded
string primary key), value (JSON) and expires_at (nullable, indexed
timezone-aware datetime). Works across PostgreSQL/Supabase, MySQL/MariaDB
and SQLite from one code path; only the UPSERT statement is dialect-aware.
Engine ownership
If constructed from a database_url the backend creates and OWNS the
engine and disposes it on :meth:close. If an existing engine is
supplied the backend BORROWS it and never disposes it.
Example
store = SQLAlchemyStorage(database_url="sqlite+aiosqlite:///:memory:") await store.create_schema() await store.set("k", {"h": "menu", "p": {"x": 1}}) await store.get("k") {'h': 'menu', 'p': {'x': 1}} await store.close()
Note
Unlike :class:~telegram_menu_builder.storage.memory.MemoryStorage,
:meth:get_stats here is asynchronous because it queries the database.
Source code in src/telegram_menu_builder/storage/sqlalchemy.py
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 | |
create_schema
async
¶
Create the backing table and indexes if they do not exist.
This is idempotent: it uses checkfirst=True so it can be called
repeatedly without error.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the storage is closed. |
StorageError
|
If the DDL operation fails. |
Source code in src/telegram_menu_builder/storage/sqlalchemy.py
drop_schema
async
¶
Drop the backing table if it exists.
This is idempotent thanks to checkfirst=True.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the storage is closed. |
StorageError
|
If the DDL operation fails. |
Source code in src/telegram_menu_builder/storage/sqlalchemy.py
set
async
¶
Store (UPSERT) data under key with an optional TTL.
Re-setting the same key overwrites the existing value and refreshes (or clears) its expiry, matching the encoder's deterministic-key dedup.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier for the data. |
required |
data
|
dict[str, Any]
|
JSON-serializable dictionary to store. |
required |
ttl
|
int | None
|
Time-to-live in seconds ( |
None
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the storage is closed. |
StorageError
|
If the write fails. |
Source code in src/telegram_menu_builder/storage/sqlalchemy.py
add
async
¶
Atomically store data under key only if it is absent.
The whole operation runs inside a SINGLE transaction
(async with self._engine.begin()) so it is atomic against concurrent
callers and the engine's connection pool. Within that transaction it:
- DELETEs any row for
keywhose TTL has already elapsed (expires_at IS NOT NULL AND expires_at <= now), freeing an expired claim so it can be reclaimed; and then - runs a dialect INSERT that no-ops on a primary-key conflict, mirroring
the dialect branches of :meth:
_build_upsert: PostgreSQL/SQLite useINSERT ... ON CONFLICT DO NOTHING; MySQL/MariaDB useINSERT IGNORE; any other dialect falls back to a plain INSERT and treats an :class:~sqlalchemy.exc.IntegrityErroras a lost race.
The boolean result is derived from the INSERT's affected-row count: a row
was inserted (won the claim) iff rowcount > 0.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier for the data. |
required |
data
|
dict[str, Any]
|
JSON-serializable dictionary to store. |
required |
ttl
|
int | None
|
Time-to-live in seconds ( |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
|
bool
|
(non-expired) row already existed for the key. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the storage is closed. |
StorageError
|
If the write fails. |
Source code in src/telegram_menu_builder/storage/sqlalchemy.py
get
async
¶
Retrieve the (non-expired) value stored under key.
Expiry is filtered lazily at query time; an expired row is treated as
missing and None is returned (the row is not eagerly deleted here).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier for the data. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any] | None
|
The stored dictionary, or |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the storage is closed. |
StorageError
|
If the read fails. |
Source code in src/telegram_menu_builder/storage/sqlalchemy.py
delete
async
¶
Delete the row stored under key.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier for the data. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the storage is closed. |
StorageError
|
If the delete fails. |
Source code in src/telegram_menu_builder/storage/sqlalchemy.py
exists
async
¶
Check whether a non-expired row exists under key.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier to check. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the storage is closed. |
StorageError
|
If the read fails. |
Source code in src/telegram_menu_builder/storage/sqlalchemy.py
clear
async
¶
Delete every row in the backing table.
Warning
This removes ALL stored callbacks. Use with care in production.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the storage is closed. |
StorageError
|
If the delete fails. |
Source code in src/telegram_menu_builder/storage/sqlalchemy.py
keys
async
¶
Return all non-expired keys, optionally filtered by a glob pattern.
The glob pattern is translated to a SQL LIKE clause and pushed
down to the database.
Note
This diverges from :class:MemoryStorage's fnmatch semantics:
SQL LIKE supports only */? (mapped to %/_) and
has no [seq] character-class wildcards.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pattern
|
str | None
|
Optional glob pattern using |
None
|
Returns:
| Type | Description |
|---|---|
list[str]
|
A list of matching, non-expired keys. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the storage is closed. |
StorageError
|
If the read fails. |
Source code in src/telegram_menu_builder/storage/sqlalchemy.py
cleanup_expired
async
¶
Delete all rows whose TTL has elapsed.
Returns:
| Type | Description |
|---|---|
int
|
The number of expired rows removed. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the storage is closed. |
StorageError
|
If the delete fails. |
Source code in src/telegram_menu_builder/storage/sqlalchemy.py
get_stats
async
¶
Return counts describing the stored keys.
Note
This method is asynchronous (it queries the database), unlike the
synchronous get_stats on
:class:~telegram_menu_builder.storage.memory.MemoryStorage.
Returns:
| Type | Description |
|---|---|
dict[str, int]
|
A mapping with the keys |
dict[str, int]
|
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the storage is closed. |
StorageError
|
If the read fails. |
Source code in src/telegram_menu_builder/storage/sqlalchemy.py
close
async
¶
Close the storage, disposing the engine only if it is owned.
A borrowed engine (supplied via engine=) is left intact so the
caller can keep using it.
Source code in src/telegram_menu_builder/storage/sqlalchemy.py
RedisStorage¶
telegram_menu_builder.storage.redis.RedisStorage ¶
Bases: BaseStorage
Async storage backend using redis-py, compatible with Redis and Valkey.
Callback payloads are stored as JSON strings under a configurable key
namespace. The backend relies on the server to enforce TTL natively (via the
SET ... EX option), so there is no client-side expiry bookkeeping.
Valkey support
Valkey is wire-compatible with Redis, so this same class connects to a
Valkey server unchanged -- just pass a redis://valkey-host URL. A
duck-typed valkey.asyncio client can also be supplied via client=.
Client ownership
If constructed from a url the backend creates and OWNS the client and
closes it on :meth:close. If an existing client is supplied the
backend BORROWS it and never closes it.
Namespacing
Every key is prefixed with namespace (default "tmb:"). All
operations -- including :meth:clear and :meth:keys -- are scoped to
that prefix, so multiple stores can safely share one server/database and
:meth:clear never issues FLUSHDB.
Example
store = RedisStorage(url="redis://localhost:6379/0") await store.set("k", {"h": "menu", "p": {"x": 1}}) await store.get("k") {'h': 'menu', 'p': {'x': 1}} await store.close()
Note
Unlike :class:~telegram_menu_builder.storage.memory.MemoryStorage, this
backend exposes no cleanup_expired or get_stats: Redis/Valkey
enforce TTL natively, and server-side introspection is available through
the INFO and DBSIZE commands on :attr:client.
Source code in src/telegram_menu_builder/storage/redis.py
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 | |
client
property
¶
Return the underlying redis-py async client.
Exposed for advanced use and inspection (e.g. await store.client.ttl
or await store.client.info()).
Returns:
| Type | Description |
|---|---|
Redis
|
The wrapped :class: |
set
async
¶
Store data under key with an optional TTL.
The value is serialized to compact JSON. Re-setting the same key
overwrites the existing value (and resets or clears its expiry to match
ttl), exactly what the encoder's deterministic-key dedup needs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier for the data. |
required |
data
|
dict[str, Any]
|
JSON-serializable dictionary to store. |
required |
ttl
|
int | None
|
Time-to-live in seconds ( |
None
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the storage is closed. |
StorageError
|
If the write fails. |
Source code in src/telegram_menu_builder/storage/redis.py
add
async
¶
Atomically store data under key only if it is absent.
Implemented with a single SET ... NX [EX] command, which the server
applies atomically: the value is written only when the key does not
already exist. Because an expired key is gone server-side, NX
succeeds again once its TTL has elapsed (expired keys are reclaimable),
and concurrent callers see exactly one True.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier for the data. |
required |
data
|
dict[str, Any]
|
JSON-serializable dictionary to store. |
required |
ttl
|
int | None
|
Time-to-live in seconds ( |
None
|
Returns:
| Type | Description |
|---|---|
bool
|
|
bool
|
(non-expired) value already existed for the key. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the storage is closed. |
StorageError
|
If the write fails. |
Source code in src/telegram_menu_builder/storage/redis.py
get
async
¶
Retrieve the value stored under key.
Expired keys are absent server-side, so this returns None for them
without raising.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier for the data. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any] | None
|
A fresh dictionary parsed from storage, or |
dict[str, Any] | None
|
missing or expired. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the storage is closed. |
StorageError
|
If the read fails. |
Source code in src/telegram_menu_builder/storage/redis.py
delete
async
¶
Delete the value stored under key.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier for the data. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the storage is closed. |
StorageError
|
If the delete fails. |
Source code in src/telegram_menu_builder/storage/redis.py
exists
async
¶
Check whether key exists (and has not expired).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Unique identifier to check. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the storage is closed. |
StorageError
|
If the read fails. |
Source code in src/telegram_menu_builder/storage/redis.py
clear
async
¶
Delete every key in this store's namespace.
Implemented with a namespace-scoped SCAN MATCH followed by DELETE;
it NEVER issues FLUSHDB, so other namespaces sharing the same
server/database are left untouched.
Warning
This removes ALL callbacks stored under this namespace. Use with care in production.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the storage is closed. |
StorageError
|
If the operation fails. |
Source code in src/telegram_menu_builder/storage/redis.py
keys
async
¶
Return the namespace's keys, optionally filtered by a glob pattern.
The pattern is applied via SCAN MATCH and the namespace prefix is
stripped from the results so callers see the keys they supplied.
Note
SCAN MATCH uses Redis/Valkey glob syntax, which differs from
Python's :mod:fnmatch (used by MemoryStorage): character-class
negation is written [^...] here rather than [!...].
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pattern
|
str | None
|
Optional glob pattern (without the namespace prefix). |
None
|
Returns:
| Type | Description |
|---|---|
list[str]
|
A list of matching keys with the namespace prefix removed. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the storage is closed. |
StorageError
|
If the read fails. |
Source code in src/telegram_menu_builder/storage/redis.py
close
async
¶
Close the storage, closing the client only if it is owned.
A borrowed client (supplied via client=) is left open so the caller
can keep using it.