baleio 0.1.0__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.
Files changed (85) hide show
  1. baleio-0.1.0/.github/workflows/ci.yml +32 -0
  2. baleio-0.1.0/.github/workflows/docs.yml +51 -0
  3. baleio-0.1.0/.github/workflows/publish.yml +54 -0
  4. baleio-0.1.0/.gitignore +15 -0
  5. baleio-0.1.0/CHANGELOG.md +29 -0
  6. baleio-0.1.0/CONTRIBUTING.md +47 -0
  7. baleio-0.1.0/LICENSE +21 -0
  8. baleio-0.1.0/PKG-INFO +389 -0
  9. baleio-0.1.0/README.fa.md +281 -0
  10. baleio-0.1.0/README.md +357 -0
  11. baleio-0.1.0/baleio/__init__.py +93 -0
  12. baleio-0.1.0/baleio/__meta__.py +2 -0
  13. baleio-0.1.0/baleio/client/__init__.py +5 -0
  14. baleio-0.1.0/baleio/client/bot.py +655 -0
  15. baleio-0.1.0/baleio/client/default.py +18 -0
  16. baleio-0.1.0/baleio/client/session/__init__.py +4 -0
  17. baleio-0.1.0/baleio/client/session/aiohttp.py +115 -0
  18. baleio-0.1.0/baleio/client/session/base.py +178 -0
  19. baleio-0.1.0/baleio/dispatcher/__init__.py +4 -0
  20. baleio-0.1.0/baleio/dispatcher/dispatcher.py +223 -0
  21. baleio-0.1.0/baleio/dispatcher/event/__init__.py +4 -0
  22. baleio-0.1.0/baleio/dispatcher/event/handler.py +23 -0
  23. baleio-0.1.0/baleio/dispatcher/event/observer.py +74 -0
  24. baleio-0.1.0/baleio/dispatcher/router.py +72 -0
  25. baleio-0.1.0/baleio/enums/__init__.py +128 -0
  26. baleio-0.1.0/baleio/exceptions.py +121 -0
  27. baleio-0.1.0/baleio/filters/__init__.py +30 -0
  28. baleio-0.1.0/baleio/filters/base.py +81 -0
  29. baleio-0.1.0/baleio/filters/callback_data.py +149 -0
  30. baleio-0.1.0/baleio/filters/command.py +96 -0
  31. baleio-0.1.0/baleio/filters/exception.py +40 -0
  32. baleio-0.1.0/baleio/filters/logic.py +41 -0
  33. baleio-0.1.0/baleio/filters/state.py +32 -0
  34. baleio-0.1.0/baleio/fsm/__init__.py +14 -0
  35. baleio-0.1.0/baleio/fsm/context.py +33 -0
  36. baleio-0.1.0/baleio/fsm/state.py +81 -0
  37. baleio-0.1.0/baleio/fsm/storage/__init__.py +4 -0
  38. baleio-0.1.0/baleio/fsm/storage/base.py +53 -0
  39. baleio-0.1.0/baleio/fsm/storage/memory.py +39 -0
  40. baleio-0.1.0/baleio/types/__init__.py +143 -0
  41. baleio-0.1.0/baleio/types/attachments.py +53 -0
  42. baleio-0.1.0/baleio/types/base.py +84 -0
  43. baleio-0.1.0/baleio/types/callback.py +41 -0
  44. baleio-0.1.0/baleio/types/chat.py +135 -0
  45. baleio-0.1.0/baleio/types/error.py +20 -0
  46. baleio-0.1.0/baleio/types/input_file.py +115 -0
  47. baleio-0.1.0/baleio/types/input_media.py +58 -0
  48. baleio-0.1.0/baleio/types/keyboards.py +59 -0
  49. baleio-0.1.0/baleio/types/media.py +98 -0
  50. baleio-0.1.0/baleio/types/message.py +272 -0
  51. baleio-0.1.0/baleio/types/misc.py +18 -0
  52. baleio-0.1.0/baleio/types/payments.py +50 -0
  53. baleio-0.1.0/baleio/types/update.py +33 -0
  54. baleio-0.1.0/baleio/types/user.py +27 -0
  55. baleio-0.1.0/baleio/utils/__init__.py +3 -0
  56. baleio-0.1.0/baleio/utils/call.py +77 -0
  57. baleio-0.1.0/baleio/utils/keyboard.py +84 -0
  58. baleio-0.1.0/baleio/utils/markdown.py +35 -0
  59. baleio-0.1.0/docs/Makefile +20 -0
  60. baleio-0.1.0/docs/_static/custom.css +27 -0
  61. baleio-0.1.0/docs/api.md +96 -0
  62. baleio-0.1.0/docs/callback_data.md +76 -0
  63. baleio-0.1.0/docs/changelog.md +2 -0
  64. baleio-0.1.0/docs/conf.py +101 -0
  65. baleio-0.1.0/docs/dispatcher.md +115 -0
  66. baleio-0.1.0/docs/errors.md +57 -0
  67. baleio-0.1.0/docs/filters.md +106 -0
  68. baleio-0.1.0/docs/fsm.md +77 -0
  69. baleio-0.1.0/docs/index.md +116 -0
  70. baleio-0.1.0/docs/installation.md +43 -0
  71. baleio-0.1.0/docs/keyboards.md +78 -0
  72. baleio-0.1.0/docs/quickstart.md +80 -0
  73. baleio-0.1.0/docs/requirements.txt +5 -0
  74. baleio-0.1.0/examples/echo_bot.py +59 -0
  75. baleio-0.1.0/examples/fsm_form_bot.py +58 -0
  76. baleio-0.1.0/examples/quickstart.py +56 -0
  77. baleio-0.1.0/examples/webhook_bot.py +43 -0
  78. baleio-0.1.0/pyproject.toml +46 -0
  79. baleio-0.1.0/tests/conftest.py +74 -0
  80. baleio-0.1.0/tests/test_bot_and_keyboard.py +118 -0
  81. baleio-0.1.0/tests/test_callback_data.py +87 -0
  82. baleio-0.1.0/tests/test_dispatch.py +146 -0
  83. baleio-0.1.0/tests/test_errors.py +96 -0
  84. baleio-0.1.0/tests/test_serialization.py +74 -0
  85. baleio-0.1.0/tests/test_types.py +63 -0
@@ -0,0 +1,32 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Set up Python ${{ matrix.python-version }}
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: ${{ matrix.python-version }}
24
+ cache: pip
25
+
26
+ - name: Install
27
+ run: |
28
+ python -m pip install --upgrade pip
29
+ pip install -e ".[dev]"
30
+
31
+ - name: Run tests
32
+ run: pytest -q
@@ -0,0 +1,51 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+ pages: write
11
+ id-token: write
12
+
13
+ concurrency:
14
+ group: pages
15
+ cancel-in-progress: true
16
+
17
+ jobs:
18
+ build:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+
23
+ - uses: actions/setup-python@v5
24
+ with:
25
+ python-version: "3.12"
26
+ cache: pip
27
+
28
+ - name: Install package + docs deps
29
+ run: |
30
+ python -m pip install --upgrade pip
31
+ pip install -e .
32
+ pip install -r docs/requirements.txt
33
+
34
+ - name: Build HTML
35
+ run: sphinx-build -b html docs docs/_build/html
36
+
37
+ - name: Upload artifact
38
+ uses: actions/upload-pages-artifact@v3
39
+ with:
40
+ path: docs/_build/html
41
+
42
+ deploy:
43
+ needs: build
44
+ runs-on: ubuntu-latest
45
+ environment:
46
+ name: github-pages
47
+ url: ${{ steps.deployment.outputs.page_url }}
48
+ steps:
49
+ - name: Deploy to GitHub Pages
50
+ id: deployment
51
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,54 @@
1
+ name: Publish to PyPI
2
+
3
+ # Publishes to PyPI when you publish a GitHub Release.
4
+ # Uses PyPI Trusted Publishing (OIDC) — no API tokens or secrets to store.
5
+
6
+ on:
7
+ release:
8
+ types: [published]
9
+ workflow_dispatch:
10
+
11
+ jobs:
12
+ build:
13
+ name: Build distributions
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: "3.12"
21
+
22
+ - name: Build sdist and wheel
23
+ run: |
24
+ python -m pip install --upgrade build
25
+ python -m build
26
+
27
+ - name: Check metadata
28
+ run: |
29
+ python -m pip install --upgrade twine
30
+ twine check dist/*
31
+
32
+ - uses: actions/upload-artifact@v4
33
+ with:
34
+ name: dist
35
+ path: dist/
36
+
37
+ publish:
38
+ name: Publish to PyPI
39
+ needs: build
40
+ runs-on: ubuntu-latest
41
+ # A GitHub "environment" you can protect with required reviewers if you like.
42
+ environment:
43
+ name: pypi
44
+ url: https://pypi.org/p/baleio
45
+ permissions:
46
+ id-token: write # REQUIRED for trusted publishing
47
+ steps:
48
+ - uses: actions/download-artifact@v4
49
+ with:
50
+ name: dist
51
+ path: dist/
52
+
53
+ - name: Publish
54
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,15 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+ build/
6
+ dist/
7
+ .venv/
8
+ venv/
9
+ .env
10
+ .mypy_cache/
11
+ .pytest_cache/
12
+ .ruff_cache/
13
+ .DS_Store
14
+ docs/_build/
15
+ docs/api/_autosummary/
@@ -0,0 +1,29 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/), and this project adheres to
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-07-06
10
+
11
+ ### Added
12
+ - Initial release of **baleio**, an aiogram-style async framework for the Bale bot API.
13
+ - `Bot` client covering every documented Bale method, with `aiohttp` session and
14
+ automatic JSON / `multipart/form-data` request serialization.
15
+ - Pydantic v2 models for all Bale API types.
16
+ - `Dispatcher` / `Router` with per-event observers, nested routers, and dependency
17
+ injection.
18
+ - Filters: `Command`, `CommandStart`, `StateFilter`, magic filter `F`, `&` / `|` / `~`
19
+ combinators, and the `CallbackData` factory.
20
+ - Finite State Machine: `StatesGroup`, `State`, `FSMContext`, `MemoryStorage`.
21
+ - Inline and reply keyboard builders.
22
+ - Centralized error handling via `@dp.errors()` and `ErrorEvent`
23
+ (`ExceptionTypeFilter`, `ExceptionMessageFilter`).
24
+ - Message shortcuts (`answer`, `reply`, `send_copy`, `edit_text`, …) and Markdown
25
+ helpers (`md`).
26
+ - Long polling and webhook support.
27
+
28
+ [Unreleased]: https://github.com/ehsndvr/baleio/compare/v0.1.0...HEAD
29
+ [0.1.0]: https://github.com/ehsndvr/baleio/releases/tag/v0.1.0
@@ -0,0 +1,47 @@
1
+ # Contributing to baleio
2
+
3
+ Thanks for your interest in improving **baleio**! 🎉
4
+
5
+ ## Getting started
6
+
7
+ ```bash
8
+ git clone https://github.com/ehsndvr/baleio && cd baleio
9
+ python -m venv .venv && source .venv/bin/activate
10
+ pip install -e ".[dev]"
11
+ pytest
12
+ ```
13
+
14
+ ## Guidelines
15
+
16
+ - **Match the surrounding style.** baleio mirrors aiogram's public API where it makes
17
+ sense; keep naming and patterns consistent with existing code.
18
+ - **Type everything.** All public functions are type-annotated (Pydantic v2 models for
19
+ API objects).
20
+ - **Add tests.** New behaviour needs coverage in `tests/`. Tests run fully offline via
21
+ the `FakeSession` fixture — no network or real bot token required.
22
+ - **Keep it async-first.** The whole framework runs on `asyncio` / `aiohttp`.
23
+ - **Document public API.** Update the relevant page under `docs/` and, if user-facing,
24
+ both `README.md` and `README.fa.md`.
25
+
26
+ ## Running the docs locally
27
+
28
+ ```bash
29
+ pip install -r docs/requirements.txt
30
+ sphinx-build -b html docs docs/_build/html
31
+ # open docs/_build/html/index.html
32
+ ```
33
+
34
+ ## Pull requests
35
+
36
+ 1. Fork and create a feature branch.
37
+ 2. Make your change with tests and docs.
38
+ 3. Ensure `pytest` passes.
39
+ 4. Open a PR describing the change and linking any related issue.
40
+
41
+ ## Reporting bugs
42
+
43
+ Open an issue with a minimal reproduction, the traceback, and your Python / baleio
44
+ versions. For API mismatches with Bale, include the method name and the raw request /
45
+ response if possible (redact your token!).
46
+
47
+ By contributing, you agree that your contributions are licensed under the MIT License.
baleio-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 baleio contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
baleio-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,389 @@
1
+ Metadata-Version: 2.4
2
+ Name: baleio
3
+ Version: 0.1.0
4
+ Summary: Modern, fully async framework for Bale (بله) messenger bots — inspired by aiogram.
5
+ Project-URL: Homepage, https://github.com/ehsndvr/baleio
6
+ Project-URL: Documentation, https://ehsndvr.github.io/baleio/
7
+ Project-URL: Repository, https://github.com/ehsndvr/baleio
8
+ Project-URL: Issues, https://github.com/ehsndvr/baleio/issues
9
+ Project-URL: Changelog, https://github.com/ehsndvr/baleio/blob/main/CHANGELOG.md
10
+ Author: baleio contributors
11
+ License: MIT
12
+ License-File: LICENSE
13
+ Keywords: aiogram,api,async,bale,bot,بازو,بله
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Framework :: AsyncIO
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Topic :: Communications :: Chat
21
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
22
+ Requires-Python: >=3.9
23
+ Requires-Dist: aiohttp>=3.9.0
24
+ Requires-Dist: certifi>=2023.7.22
25
+ Requires-Dist: magic-filter>=1.0.12
26
+ Requires-Dist: pydantic<3.0,>=2.4.1
27
+ Provides-Extra: dev
28
+ Requires-Dist: mypy>=1.5; extra == 'dev'
29
+ Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
30
+ Requires-Dist: pytest>=7.4; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ <div align="center">
34
+
35
+ # baleio
36
+
37
+ **A modern, fully-async framework for [Bale](https://bale.ai) (بله) bots — inspired by [aiogram](https://github.com/aiogram/aiogram).**
38
+
39
+ [![CI](https://github.com/ehsndvr/baleio/actions/workflows/ci.yml/badge.svg)](https://github.com/ehsndvr/baleio/actions/workflows/ci.yml)
40
+ [![Docs](https://github.com/ehsndvr/baleio/actions/workflows/docs.yml/badge.svg)](https://ehsndvr.github.io/baleio/)
41
+ [![Python](https://img.shields.io/badge/python-3.9%2B-blue)](https://www.python.org/)
42
+ [![Pydantic v2](https://img.shields.io/badge/pydantic-v2-e92063)](https://docs.pydantic.dev/)
43
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
44
+
45
+ [**Documentation**](https://ehsndvr.github.io/baleio/) · [**Quick start**](#quick-start) · [**Examples**](examples/) · [**فارسی 🇮🇷**](README.fa.md)
46
+
47
+ </div>
48
+
49
+ ---
50
+
51
+ If you have ever worked with `aiogram`, you already know `baleio`. Same architecture,
52
+ same patterns — this time for the **Bale bot API** (`https://tapi.bale.ai`). Routers,
53
+ magic filters, FSM, dependency injection, keyboard builders, callback-data factories
54
+ and centralized error handling all work the way you expect.
55
+
56
+ ## Features
57
+
58
+ - ⚡️ **Fully asynchronous** on top of `aiohttp`.
59
+ - 🧩 **`Dispatcher` / `Router`** with nested routing and per-event observers.
60
+ - 🎯 **Filters**: `Command`, `CommandStart`, `StateFilter`, the magic filter `F`, plain
61
+ callables, and `&` / `|` / `~` combinators.
62
+ - 🏷️ **`CallbackData` factory** — type-safe, packed/unpacked structured callback payloads.
63
+ - 💾 **Finite State Machine** with `StatesGroup` / `State` and pluggable storage (`MemoryStorage`).
64
+ - ⌨️ **Keyboard builders** for inline and reply keyboards.
65
+ - 🧱 **Pydantic v2 models** for every Bale API type.
66
+ - 🔌 **Middlewares** at the event level (inner & outer).
67
+ - 🧯 **Centralized error handling** via `@dp.errors()` and `ErrorEvent`.
68
+ - 📎 File uploads (`multipart/form-data`), sending by `file_id` or URL, and media groups.
69
+ - 💳 Wallet payments (`sendInvoice`, `PreCheckoutQuery`, `answerPreCheckoutQuery`, …).
70
+ - 🪝 Long polling **and** webhook support.
71
+
72
+ ## Installation
73
+
74
+ ```bash
75
+ pip install baleio # once published to PyPI
76
+ # or, from source:
77
+ git clone https://github.com/ehsndvr/baleio && cd baleio
78
+ pip install -e .
79
+ ```
80
+
81
+ **Requirements:** Python 3.9+, `aiohttp`, `pydantic>=2`, `magic-filter`.
82
+
83
+ ## Quick start
84
+
85
+ The snippet below is the **official aiogram quickstart**, ported to `baleio` almost
86
+ line-for-line. Only the import roots and the text formatter change (Bale renders
87
+ Markdown, so `md` replaces aiogram's `html`).
88
+
89
+ ```python
90
+ import asyncio
91
+ import logging
92
+ import sys
93
+ from os import getenv
94
+
95
+ from baleio import Bot, Dispatcher, md
96
+ from baleio.client.default import DefaultBotProperties
97
+ from baleio.enums import ParseMode
98
+ from baleio.filters import CommandStart
99
+ from baleio.types import Message
100
+
101
+ TOKEN = getenv("BOT_TOKEN") # get one from @botfather in Bale
102
+
103
+ dp = Dispatcher()
104
+
105
+
106
+ @dp.message(CommandStart())
107
+ async def command_start_handler(message: Message) -> None:
108
+ await message.answer(f"Hello, {md.bold(message.from_user.full_name)}!")
109
+
110
+
111
+ @dp.message()
112
+ async def echo_handler(message: Message) -> None:
113
+ try:
114
+ await message.send_copy(chat_id=message.chat.id)
115
+ except TypeError:
116
+ await message.answer("Nice try!")
117
+
118
+
119
+ async def main() -> None:
120
+ bot = Bot(token=TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.MARKDOWN))
121
+ await dp.start_polling(bot)
122
+
123
+
124
+ if __name__ == "__main__":
125
+ logging.basicConfig(level=logging.INFO, stream=sys.stdout)
126
+ asyncio.run(main())
127
+ ```
128
+
129
+ > **Compatibility note:** Bale does not accept a `parse_mode` request parameter (it
130
+ > always renders Markdown), but `DefaultBotProperties(parse_mode=...)` is accepted for
131
+ > full aiogram parity and safely ignored. Use `md` for bold/italic/link formatting.
132
+
133
+ ## Core concepts
134
+
135
+ ### The Bot client
136
+
137
+ ```python
138
+ bot = Bot("123456:TOKEN")
139
+
140
+ me = await bot.get_me()
141
+ await bot.send_message(chat_id, "Hello")
142
+ await bot.send_photo(chat_id, "https://example.com/pic.jpg", caption="A photo")
143
+ await bot.send_photo(chat_id, FSInputFile("local.jpg")) # upload from disk
144
+ await bot.edit_message_text(chat_id, message_id, "New text")
145
+ await bot.delete_message(chat_id, message_id)
146
+ ```
147
+
148
+ Every documented Bale method is implemented: `sendMessage`, `forwardMessage`,
149
+ `copyMessage`, `sendPhoto/Audio/Document/Video/Animation/Voice`, `sendMediaGroup`,
150
+ `sendLocation`, `sendContact`, `sendChatAction`, `getFile`, `answerCallbackQuery`,
151
+ `askReview`, chat administration (`banChatMember`, `promoteChatMember`, `getChat`,
152
+ `getChatAdministrators`, `pinChatMessage`, …), message editing/deletion, stickers,
153
+ and payments (`sendInvoice`, `createInvoiceLink`, `answerPreCheckoutQuery`,
154
+ `inquireTransaction`).
155
+
156
+ ### Dispatcher & Router
157
+
158
+ ```python
159
+ from baleio import Dispatcher, Router
160
+ from baleio.filters import Command
161
+
162
+ dp = Dispatcher()
163
+ admin = Router(name="admin")
164
+ dp.include_router(admin)
165
+
166
+ @dp.message(Command("start"))
167
+ async def start(message): ...
168
+
169
+ @admin.message(Command("ban"))
170
+ async def ban(message): ...
171
+ ```
172
+
173
+ Handlers are checked in registration order; the first whose filters all pass runs.
174
+
175
+ ### Dependency injection
176
+
177
+ Each handler receives the event as its first argument. Remaining parameters are
178
+ filled **by name** from the propagated context:
179
+
180
+ ```python
181
+ @dp.message(Command("me"))
182
+ async def me(message: Message, bot: Bot, state: FSMContext):
183
+ ... # bot and state are injected automatically
184
+ ```
185
+
186
+ Available keys: `bot`, `state`, `raw_state`, `event_update` (the `Update`),
187
+ `event_router`, and anything a filter returned (e.g. `command`).
188
+
189
+ ### Filters
190
+
191
+ ```python
192
+ from baleio.filters import Command, CommandStart, StateFilter, F
193
+
194
+ @dp.message(Command("help")) # /help
195
+ @dp.message(CommandStart()) # /start
196
+ @dp.message(F.text == "hello") # magic filter
197
+ @dp.message(F.text.startswith("/"))
198
+ @dp.message(F.from_user.id == 12345)
199
+ @dp.message(lambda m: m.text and len(m.text) > 100) # plain callable
200
+ @dp.message(Command("go") & (F.from_user.id == 42)) # combinators
201
+ ```
202
+
203
+ `Command` also injects a `CommandObject`:
204
+
205
+ ```python
206
+ @dp.message(Command("say"))
207
+ async def say(message: Message, command: CommandObject):
208
+ await message.answer(command.args or "Say what?")
209
+ ```
210
+
211
+ ### CallbackData factory
212
+
213
+ Type-safe, structured callback payloads — exactly like aiogram:
214
+
215
+ ```python
216
+ from baleio import F
217
+ from baleio.filters import CallbackData
218
+ from baleio.types import CallbackQuery
219
+ from baleio.utils import InlineKeyboardBuilder
220
+
221
+ class Vote(CallbackData, prefix="vote"): # optional: prefix="v", sep="|"
222
+ action: str
223
+ post_id: int
224
+
225
+ kb = (
226
+ InlineKeyboardBuilder()
227
+ .button("👍", callback_data=Vote(action="up", post_id=7).pack()) # -> "vote:up:7"
228
+ .button("👎", callback_data=Vote(action="down", post_id=7).pack())
229
+ .as_markup()
230
+ )
231
+
232
+ @dp.callback_query(Vote.filter(F.action == "up"))
233
+ async def upvote(query: CallbackQuery, callback_data: Vote):
234
+ await query.answer(f"Post {callback_data.post_id} liked")
235
+ ```
236
+
237
+ Fields are coerced back to their declared types on `unpack` (`post_id` is an `int`),
238
+ and Bale's 64-byte `callback_data` limit is enforced automatically.
239
+
240
+ ### Finite State Machine
241
+
242
+ ```python
243
+ from baleio.fsm import State, StatesGroup, FSMContext
244
+
245
+ class Form(StatesGroup):
246
+ name = State()
247
+ age = State()
248
+
249
+ @dp.message(Command("start"))
250
+ async def start(message: Message, state: FSMContext):
251
+ await state.set_state(Form.name)
252
+ await message.answer("What's your name?")
253
+
254
+ @dp.message(Form.name) # a State used directly as a filter
255
+ async def got_name(message: Message, state: FSMContext):
256
+ await state.update_data(name=message.text)
257
+ await state.set_state(Form.age)
258
+ await message.answer("How old are you?")
259
+ ```
260
+
261
+ ### Keyboards
262
+
263
+ ```python
264
+ from baleio.utils import InlineKeyboardBuilder
265
+
266
+ kb = (
267
+ InlineKeyboardBuilder()
268
+ .button("Yes", callback_data="yes")
269
+ .button("No", callback_data="no")
270
+ .adjust(2)
271
+ .as_markup()
272
+ )
273
+ await message.answer("Agree?", reply_markup=kb)
274
+
275
+ @dp.callback_query(F.data == "yes")
276
+ async def yes(cb):
277
+ await cb.answer("Great!", show_alert=True)
278
+ await cb.message.edit_text("You chose: Yes")
279
+ ```
280
+
281
+ > ⚠️ You must call `answerCallbackQuery` (or `cb.answer()`) to release the button.
282
+
283
+ ### Centralized error handling
284
+
285
+ If a handler raises, an `ErrorEvent` is routed to `@dp.errors()` handlers:
286
+
287
+ ```python
288
+ from baleio.filters import ExceptionTypeFilter
289
+ from baleio.types import ErrorEvent
290
+
291
+ @dp.errors(ExceptionTypeFilter(ValueError))
292
+ async def on_value_error(event: ErrorEvent):
293
+ await event.update.message.answer("Something went wrong 😔")
294
+
295
+ @dp.errors() # any other unhandled error
296
+ async def on_any_error(event: ErrorEvent):
297
+ logging.exception("Unhandled", exc_info=event.exception)
298
+ ```
299
+
300
+ If no error handler matches, the exception is re-raised (logged in polling, so the bot
301
+ stays alive; handled by you in webhook mode).
302
+
303
+ ### Shortcut methods
304
+
305
+ Every received object is bound to its `Bot`, so you never pass `bot` around:
306
+
307
+ ```python
308
+ await message.answer("...") # sendMessage to the same chat
309
+ await message.reply("...") # reply (reply_to_message_id)
310
+ await message.answer_photo(photo)
311
+ await message.send_copy(chat_id) # content-aware copy
312
+ await message.edit_text("...")
313
+ await message.delete()
314
+ await callback.answer("...")
315
+ await callback.message.edit_text("...")
316
+ ```
317
+
318
+ ## Receiving updates
319
+
320
+ ### Long polling
321
+
322
+ ```python
323
+ await dp.start_polling(bot) # async
324
+ dp.run_polling(bot) # blocking (wraps asyncio.run)
325
+ await dp.start_polling(bot, drop_pending_updates=True)
326
+ ```
327
+
328
+ ### Webhook
329
+
330
+ ```python
331
+ from aiohttp import web
332
+ from baleio.types import Update
333
+
334
+ async def handler(request):
335
+ await dp.feed_update(bot, Update.model_validate(await request.json()))
336
+ return web.Response()
337
+ ```
338
+
339
+ See [`examples/webhook_bot.py`](examples/webhook_bot.py).
340
+
341
+ ## Examples
342
+
343
+ - [`examples/quickstart.py`](examples/quickstart.py) — the aiogram quickstart, ported.
344
+ - [`examples/echo_bot.py`](examples/echo_bot.py) — echo + inline keyboard + callbacks.
345
+ - [`examples/fsm_form_bot.py`](examples/fsm_form_bot.py) — a registration form with FSM.
346
+ - [`examples/webhook_bot.py`](examples/webhook_bot.py) — receiving updates via webhook.
347
+
348
+ ```bash
349
+ export BOT_TOKEN=123456:xxxxxxxx
350
+ python examples/echo_bot.py
351
+ ```
352
+
353
+ ## Development
354
+
355
+ ```bash
356
+ python -m venv .venv && source .venv/bin/activate
357
+ pip install -e ".[dev]"
358
+ pytest
359
+ ```
360
+
361
+ ## aiogram → baleio cheat sheet
362
+
363
+ | aiogram | baleio |
364
+ |---|---|
365
+ | `from aiogram import Bot, Dispatcher, F` | `from baleio import Bot, Dispatcher, F` |
366
+ | `aiogram.types.Message` | `baleio.types.Message` |
367
+ | `aiogram.filters.Command` | `baleio.filters.Command` |
368
+ | `aiogram.filters.callback_data.CallbackData` | `baleio.filters.CallbackData` |
369
+ | `aiogram.fsm.state.StatesGroup` | `baleio.fsm.StatesGroup` |
370
+ | `@dp.errors()` + `ErrorEvent` | `@dp.errors()` + `baleio.types.ErrorEvent` |
371
+ | `dp.start_polling(bot)` | `dp.start_polling(bot)` |
372
+ | `InlineKeyboardBuilder` | `baleio.utils.InlineKeyboardBuilder` |
373
+ | `aiogram.html.bold` | `baleio.md.bold` (Bale is Markdown-only) |
374
+
375
+ **Key differences from Telegram/aiogram:** Bale has no `parse_mode` parameter (always
376
+ Markdown), its updates are limited to `message`, `edited_message`, `callback_query`
377
+ and `pre_checkout_query`, and payments are wallet-only.
378
+
379
+ ## Contributing
380
+
381
+ Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) and open an
382
+ issue or pull request.
383
+
384
+ ## License
385
+
386
+ [MIT](LICENSE) © baleio contributors.
387
+
388
+ > **Disclaimer:** `baleio` is an independent, community project and is not affiliated
389
+ > with or endorsed by Bale Messenger or the aiogram project.