Skip to main content

A powerful builder library for creating recursive inline keyboard menus in python-telegram-bot

Project description

Telegram Menu Builder

Python 3.12+ License: MIT Type Checked: pyright Code style: black Linting: ruff

A powerful, type-safe Python library for creating recursive inline keyboard menus in python-telegram-bot v20+.

โœจ Features

  • ๐Ÿ—๏ธ Builder Pattern API - Intuitive, fluent interface for menu construction
  • ๐Ÿ” Type-Safe - Full type hints with Python 3.12+, validated with Pyright
  • ๐Ÿ“ฆ Smart Callback Encoding - Automatically handles Telegram's 64-byte limit
  • ๐Ÿ’พ Hybrid Storage - Inline, temporary (Redis), and persistent (SQL) strategies
  • ๐Ÿ”„ Unlimited Nesting - Create complex multi-level menus with breadcrumb support
  • โšก Async-First - Built for modern async/await patterns
  • ๐Ÿงฉ Pluggable Storage - Bring your own storage backend
  • ๐ŸŽจ Flexible Layouts - Grid layouts, custom columns, navigation buttons
  • ๐Ÿงช Well Tested - Comprehensive test suite with 90%+ coverage

๐Ÿš€ Quick Start

Installation

pip install telegram-menu-builder

Basic Usage

from telegram import Update
from telegram.ext import Application, CallbackQueryHandler, ContextTypes
from telegram_menu_builder import MenuBuilder, MenuRouter

# Create a menu
async def show_settings(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    menu = (MenuBuilder()
        .add_item("๐ŸŒ Language", handler="set_language")
        .add_item("๐Ÿ‘ค Profile", handler="edit_profile")
        .add_item("๐Ÿ”” Notifications", handler="notifications")
        .columns(2)
        .add_back_button()
        .build())
    
    await update.message.reply_text("โš™๏ธ Settings", reply_markup=menu)

# Route callbacks
router = MenuRouter()

@router.handler("set_language")
async def handle_language(update: Update, context: ContextTypes.DEFAULT_TYPE, data: dict) -> None:
    # Handle language selection
    pass

# Register with application
app = Application.builder().token("YOUR_TOKEN").build()
app.add_handler(CallbackQueryHandler(router.route))

๐Ÿ“š Advanced Examples

Multi-Level Menus with Parameters

menu = (MenuBuilder()
    .add_item(
        "๐Ÿ“ Edit User",
        handler="edit_user",
        user_id=123,
        field="email",
        breadcrumb=["settings", "users"],
        validation_required=True
    )
    .add_item(
        "๐Ÿ—‘๏ธ Delete User",
        handler="delete_user",
        user_id=123,
        confirm=True
    )
    .columns(1)
    .add_back_button(handler="user_list", page=2)
    .build())

Using with ConversationHandler

When integrating MenuBuilder with python-telegram-bot's ConversationHandler, use states for menu navigation:

from telegram.ext import ConversationHandler, CommandHandler, CallbackQueryHandler

BROWSING = 0  # Conversation state

async def show_list(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
    """Command that shows paginated list."""
    menu = (MenuBuilder()
        .add_item("Next โžก๏ธ", handler="paginate", page=1)
        .build())
    
    await update.message.reply_text("Page 1", reply_markup=menu)
    return BROWSING  # โœ… Keep conversation active

# โœ… CORRECT: Use states, not fallbacks
conv_handler = ConversationHandler(
    entry_points=[CommandHandler("list", show_list)],
    states={
        BROWSING: [
            CallbackQueryHandler(router.route)  # โœ… Handles menu navigation
        ]
    },
    fallbacks=[CommandHandler("cancel", cancel)],
    per_message=False  # โœ… Default works fine
)

Important:

  • โœ… DO return a state from entry points (keeps conversation active)
  • โœ… DO put CallbackQueryHandler in states (not fallbacks)
  • โŒ DON'T return ConversationHandler.END if buttons need to work
  • โŒ DON'T use per_message=True with CommandHandler (triggers warning)

See the complete guide: Using MenuBuilder with ConversationHandler

Submenu Navigation

# Main menu
main_menu = MenuBuilder()

# Create submenu
user_submenu = (MenuBuilder()
    .add_item("Add User", handler="add_user")
    .add_item("List Users", handler="list_users")
    .add_back_button())

# Add submenu to main menu
main_menu.add_submenu("๐Ÿ‘ฅ Users", user_submenu)

Custom Storage Backend

from telegram_menu_builder import StorageBackend, MenuBuilder
from redis.asyncio import Redis

class RedisStorage(StorageBackend):
    def __init__(self, redis_client: Redis) -> None:
        self.redis = redis_client
    
    async def set(self, key: str, data: dict, ttl: int) -> None:
        await self.redis.setex(key, ttl, json.dumps(data))
    
    async def get(self, key: str) -> dict | None:
        value = await self.redis.get(key)
        return json.loads(value) if value else None

# Use custom storage
storage = RedisStorage(redis_client)
builder = MenuBuilder(storage_manager=storage)

๐Ÿ—๏ธ Architecture

The library follows a clean architecture with separation of concerns:

MenuBuilder (API Layer)
    โ†“
MenuAction (Data encoding/decoding)
    โ†“
StorageManager (Strategy selection)
    โ†“
StorageBackend (Interface)
    โ”œโ”€โ”€ MemoryStorage
    โ”œโ”€โ”€ RedisStorage
    โ””โ”€โ”€ SQLStorage

Callback Data Encoding

The library intelligently encodes callback data based on size:

  1. Inline (< 60 bytes): Encoded directly in callback_data
  2. Short-term (60-500 bytes): Stored in Redis/Memory with TTL
  3. Persistent (> 500 bytes): Stored in database
# Automatic strategy selection
action = MenuAction(
    handler="complex_handler",
    params={
        "user_id": 123,
        "filters": {"active": True, "role": "admin"},
        "breadcrumb": ["main", "users", "edit"],
        "metadata": {...}
    }
)
# Library automatically chooses best storage strategy

๐Ÿงช Development

Setup Development Environment

# Clone repository
git clone https://github.com/smoxy/telegram-menu-builder.git
cd telegram-menu-builder

# Create virtual environment
python -m venv .venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate

# Install development dependencies
pip install -e ".[dev]"

# Install pre-commit hooks
pre-commit install

Run Tests

# Run all tests
pytest

# Run with coverage
pytest --cov

# Run specific test file
pytest tests/test_builder.py

# Run type checking
mypy src
pyright

Code Quality

# Format code
black src tests

# Lint code
ruff check src tests

# Run all pre-commit hooks
pre-commit run --all-files

๐Ÿ“– Documentation

๐Ÿ“š Full documentation site: https://smoxy.github.io/telegram-menu-builder/

Handy entry points:

Examples

๐Ÿค Contributing

Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.

๐Ÿ“ License

This project is licensed under the MIT License - see the LICENSE file for details.

๐Ÿ—๏ธ Architecture Highlights

Intelligent Callback Data Management

  • Automatic Strategy Selection: Chooses between inline, short-term, and persistent storage
  • Compression: Zlib compression for inline data
  • Deduplication: Same data = same storage key
  • 64-byte Limit: Automatically handles Telegram's callback_data constraint

Type Safety

  • Pydantic v2: Runtime validation with static type support
  • Pyright Strict: 100% type coverage with strict checking
  • MyPy Compatible: Dual type checker validation
  • Python 3.12+: Modern type hints (generics, Self, Protocol)

Storage Architecture

CallbackData โ†’ Encoder โ†’ Strategy Selector
                              โ”œโ”€ Inline (< 60 bytes)
                              โ”œโ”€ Short-term Storage (60-500 bytes, TTL)
                              โ””โ”€ Persistent Storage (> 500 bytes)

๐Ÿ™ Acknowledgments

๐Ÿ“Š Project Status

This project is currently in alpha stage. APIs may change before 1.0.0 release.

  • โœ… Core builder API
  • โœ… Callback encoding/decoding
  • โœ… Memory storage backend
  • ๐Ÿšง Redis storage backend (in progress)
  • ๐Ÿšง SQL storage backend (in progress)
  • ๐Ÿ“… Pagination support (planned)
  • ๐Ÿ“… Template system (planned)
  • ๐Ÿ“… Form wizard support (planned)

๏ฟฝ Publishing to PyPI

If you want to contribute or publish your own fork:

Build the Package

# Install build tools
pip install build twine

# Build distribution files
python -m build

# Verify the build
twine check dist/*

Upload to PyPI

See PYPI_CONFIG.md for detailed instructions on configuring your PyPI token.

# Using the provided script
python upload_to_pypi.py

# Or manually
twine upload dist/*

๏ฟฝ๐Ÿ’ฌ Support

๐Ÿ“œ License

This project is licensed under the MIT License - see the LICENSE file for details.

Why MIT License?

The MIT License is chosen to maximize accessibility and enable diverse use cases:

  • โœ… Free to use - Commercial, private, or personal projects
  • โœ… Free to modify - Adapt the code to your needs
  • โœ… Free to distribute - Share with others or on package managers
  • โœ… Training AI Models - Explicitly permitted - this code can be used for training machine learning models and AI systems
  • โœ… Minimal requirements - Only requires attribution and license inclusion

IMPORTANT: This project can be used for training AI models. If you use this code to train language models, code-generation models, or any other AI systems, you are explicitly permitted to do so under the MIT License.

Attribution

If you use this project, we appreciate (but don't require) attribution:

Telegram Menu Builder - MIT License
Copyright (c) 2025 Simone Flavio Paris
https://github.com/smoxy/telegram-menu-builder

Made with โค๏ธ for the Telegram Bot community

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

telegram_menu_builder-0.2.0.tar.gz (61.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

telegram_menu_builder-0.2.0-py3-none-any.whl (25.6 kB view details)

Uploaded Python 3

File details

Details for the file telegram_menu_builder-0.2.0.tar.gz.

File metadata

  • Download URL: telegram_menu_builder-0.2.0.tar.gz
  • Upload date:
  • Size: 61.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for telegram_menu_builder-0.2.0.tar.gz
Algorithm Hash digest
SHA256 1de7fd669327c0afd2b7d4b2a97c8c3e3fbfe157fdabed35cbd3e09a599f845f
MD5 7d761213e859aee7d2050911d4c0943a
BLAKE2b-256 496264cdf2ba9426c8ecad3c4648767969324483edaadeeeaea4e011a19714cc

See more details on using hashes here.

Provenance

The following attestation bundles were made for telegram_menu_builder-0.2.0.tar.gz:

Publisher: python-publish.yml on smoxy/telegram-menu-builder

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file telegram_menu_builder-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for telegram_menu_builder-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b084b1ea45f95dc454d9c1e6cf62347cb2342ca0a96c96b4fa274ef0a97add61
MD5 50faa313a2f7a0aa241f9b87d5a15de7
BLAKE2b-256 57306624da04e273b97a5016cae0af766a734c31702f041333d3666d874b9736

See more details on using hashes here.

Provenance

The following attestation bundles were made for telegram_menu_builder-0.2.0-py3-none-any.whl:

Publisher: python-publish.yml on smoxy/telegram-menu-builder

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page