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, you need to properly configure the per_message setting:

from telegram.ext import ConversationHandler, CommandHandler, CallbackQueryHandler

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 ConversationHandler.END  # Return END, but callbacks should still work

# โœ… CORRECT: Set per_message=True when entry points return END
conv_handler = ConversationHandler(
    entry_points=[CommandHandler("list", show_list)],
    states={},
    fallbacks=[
        CallbackQueryHandler(router.route)  # Handles menu navigation
    ],
    per_message=True  # โœ… Required for callbacks after END
)

Important: If your entry points return ConversationHandler.END but send inline keyboards, you must set per_message=True for the fallback CallbackQueryHandler to work.

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 is available in the docs/ directory:

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.1.1.tar.gz (37.4 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.1.1-py3-none-any.whl (23.9 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for telegram_menu_builder-0.1.1.tar.gz
Algorithm Hash digest
SHA256 150e595f18ff5325a90f604cc606f52d346b3623add4abf9b53eea70859c670e
MD5 d0ebb3de0439e6699b97b6b3dcf7e540
BLAKE2b-256 bccebd47cc10ae96c8c96e0a808021baf93b6cdddd325cfd4d6c2f89c66f1323

See more details on using hashes here.

Provenance

The following attestation bundles were made for telegram_menu_builder-0.1.1.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.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for telegram_menu_builder-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a7560c5a3496d07272145d2dbf3bd8efa4e256233859edab6a8c2d572d78f38a
MD5 fd6fd801590008c71d21106fbf589089
BLAKE2b-256 df926595e06ad90e935ddd71f6522e198df333fcdd1491f13af2a9567a5bed01

See more details on using hashes here.

Provenance

The following attestation bundles were made for telegram_menu_builder-0.1.1-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