Skip to content

Changelog

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Unreleased

0.4.0 - 2026-06-04

Added

  • Synchronous, storage-free build surface on MenuBuilder for application-free / budget-enforced menus: to_markup() returns a telegram.InlineKeyboardMarkup, to_raw() returns a plain Telegram Bot API dict ({"inline_keyboard": [[...]]}) built directly from items, and assert_inline() is a cheap pre-flight check. All three run with no event loop, no storage access, and no worker thread: every button is encoded inline and any item that would otherwise spill to storage raises EncodingError (use build_async() for those). The new on_oversize="spill" | "error" constructor policy makes build_async() raise (via the new sync CallbackEncoder.encode_inline()) instead of spilling when set to "error".
  • Atomic set-if-absent primitive StorageBackend.add(key, data, ttl=None) -> bool on the storage Protocol, with a non-atomic default implementation on BaseStorage (existing custom backends keep working) and concurrency-safe per-backend overrides: MemoryStorage (no await between check and store, expired keys reclaimable), RedisStorage (server-side SET ... NX), and SQLAlchemyStorage (single transaction that frees expired rows then dialect INSERT no-op-on-conflict). On top of it, MenuRouter gains single-winner claim helpers claim(key, user_id, *, ttl=3600), who_claimed(key), and release(key).
  • New telegram_menu_builder.testing helper module (stdlib unittest.mock only, imported as a submodule): simulate_tap(router, callback_data, ...) and tap(router, handler, /, **params) fabricate a mock callback-query Update, route it, and return a TapResult (answered, answer_text, edited_text, handler_error); assert_inline(target) verifies that a MenuBuilder, InlineKeyboardMarkup, or raw dict contains only inline I:/IC: callback buttons (no S:/P: storage references).

0.3.0 - 2026-06-04

Added

  • Built-in async Redis storage backend (RedisStorage) on redis-py, with first-class Valkey support (RESP-compatible — point a redis:// URL at a Valkey server); native server-side TTL, namespaced keys, and owns-vs-borrows client lifecycle. Valkey is the recommended option, and the backend is verified end-to-end against live Redis 7.4.9 and Valkey 8.1.8 (parametrized suite gated by TMB_TEST_REDIS_URL / TMB_TEST_VALKEY_URL, falling back to fakeredis).
  • Built-in async SQL storage backend (SQLAlchemyStorage) for PostgreSQL/Supabase, MySQL/MariaDB, and SQLite via SQLAlchemy 2.0 (async); new optional extras [postgres] and [mysql]; explicit schema management via create_schema()/drop_schema().
  • SQLAlchemyStorage is now verified end-to-end against live PostgreSQL 16 and MariaDB 12.3.2. The SQL test suite (tests/test_sql_storage.py) is parametrized to run the full behavioral suite against any database given by TMB_TEST_POSTGRES_URL / TMB_TEST_MYSQL_URL, falling back to in-memory SQLite by default.

Changed

  • The [sql] extra now pins sqlalchemy[asyncio]>=2.0.30,<3.0.
  • Documentation refreshed across the SQL storage, installation, dependency-audit, and development guides: the per-DB driver matrix (including the pure-Python aiomysql alternative to asyncmy), UTC expires_at handling, portable get_stats, and Docker-based integration testing. Corrected the documented python-telegram-bot range to >=20.0,<22.8.

0.2.0 - 2026-06-03

Fixed

  • Critical: callback data is no longer lost when a menu is built inside a running event loop. add_item() previously discarded the handler and parameters in async contexts, producing buttons with empty callback_data; encoding is now deferred to build_async() so every item is encoded correctly.
  • MenuBuilder.build() now works when called inside a running event loop (it runs the async build on a short-lived worker thread) instead of raising RuntimeError. In async code, prefer await build_async().
  • add_submenu() no longer stores the non-serializable submenu builder in callback params (which broke encoding the moment it ran); only a JSON-safe _submenu_id is stored, with the builder kept in an internal registry accessible via the new get_submenu().
  • Corrected __version__, which was hard-coded to 0.1.0 while the package was 0.1.1; it is now single-sourced from the installed package metadata.
  • Hardened the deterministic dedup hash in CallbackEncoder with hashlib.md5(..., usedforsecurity=False) (resolves Bandit B324).

Added

  • Exported the exception hierarchy (MenuBuilderError, EncodingError, DecodingError, StorageError, ValidationError) from the package root.
  • MenuBuilder.get_submenu() to retrieve a registered submenu builder by id.
  • MkDocs Material documentation site (mkdocs.yml, docs/), including a dependency/CVE audit page and a Python-compatibility analysis.
  • Tests for the router, storage backends, type validators, and package metadata (overall coverage raised from ~62% to ~90%).
  • Continuous-integration workflow (ruff, black, mypy, pyright, pytest, pip-audit) on every push and pull request, plus a documentation-deploy workflow.
  • Project metadata: SECURITY.md, Dependabot configuration, and issue/PR templates.
  • Claude Code maintenance tooling (CLAUDE.md, AGENTS.md, .claude/ agents and skills).

Changed

  • Raised the pydantic floor to >=2.4 to exclude CVE-2024-3772 (ReDoS in pydantic email validation, fixed in 2.4.0). The library does not use email validation, so it was never exploitable here; the bump is defense-in-depth.
  • StorageStrategy now subclasses enum.StrEnum.
  • Refactored the navigation-button methods and MenuRouter.route() for clarity with no behavior change; removed the # noqa: PLR0912 suppression.
  • Added a [docs] optional-dependency group, pip-audit to [dev], and a [tool.bandit] configuration.

0.1.1 - 2025-11-08

Added

  • Comprehensive guide for using MenuBuilder with ConversationHandler
  • New example conversation_handler_menu.py demonstrating proper per_message configuration
  • Documentation file docs/conversation_handler_guide.md with best practices
  • Support for python-telegram-bot up to version 22.5

Changed

  • Updated dependency range: python-telegram-bot>=20.0,<22.6 (was <22.0)
  • Clarified ConversationHandler integration requirements in documentation

Fixed

  • Documented solution for CallbackQueryHandler in fallbacks not working with per_message=False
  • Added warning about PTBUserWarning when using incompatible handler types with per_message=True

0.1.0 - 2025-11-08

Added

  • Initial alpha release
  • Core functionality for menu building
  • MenuBuilder with fluent API
  • CallbackEncoder with intelligent storage strategies
  • MenuRouter for callback handling
  • MemoryStorage backend
  • Type-safe interfaces with Pydantic v2
  • Full Pyright/MyPy type checking
  • Comprehensive test suite
  • Example applications
  • Documentation