vitrine-tg 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 target111
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.
@@ -0,0 +1,192 @@
1
+ Metadata-Version: 2.4
2
+ Name: vitrine-tg
3
+ Version: 0.1.0
4
+ Summary: A batteries-included framework on top of python-telegram-bot: typed callbacks, screens, DI, auth, conversations, and supervised workers.
5
+ Keywords: telegram,bot,framework,python-telegram-bot,asyncio
6
+ Author: target111
7
+ Author-email: target111 <target111@protonmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Framework :: AsyncIO
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Communications :: Chat
20
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
21
+ Classifier: Typing :: Typed
22
+ Requires-Dist: pydantic>=2.13.4
23
+ Requires-Dist: python-telegram-bot[job-queue]>=22.8
24
+ Requires-Python: >=3.11
25
+ Project-URL: Homepage, https://github.com/target111/vitrine
26
+ Project-URL: Repository, https://github.com/target111/vitrine
27
+ Project-URL: Issues, https://github.com/target111/vitrine/issues
28
+ Project-URL: Changelog, https://github.com/target111/vitrine/blob/main/CHANGELOG.md
29
+ Description-Content-Type: text/markdown
30
+
31
+ # vitrine
32
+
33
+ [![CI](https://github.com/target111/vitrine/actions/workflows/ci.yml/badge.svg)](https://github.com/target111/vitrine/actions/workflows/ci.yml)
34
+ [![PyPI](https://img.shields.io/pypi/v/vitrine-tg)](https://pypi.org/project/vitrine-tg/)
35
+
36
+ A framework for building Telegram bots with [python-telegram-bot](https://python-telegram-bot.org/) that doesn't get in your way.
37
+
38
+ Handlers are just async functions. You get dependency injection, auth, robust message delivery, background workers, typed callbacks, rate limiting, and decent error handling -- the stuff every real bot needs. Everything from PTB stays accessible.
39
+
40
+ ```python
41
+ from vitrine import Bot, Button, CallbackData, Screen
42
+
43
+ bot = Bot(token="...")
44
+
45
+ class MenuCB(CallbackData, prefix="menu"):
46
+ section: str
47
+
48
+ @bot.command("start", description="Open the menu")
49
+ async def start(update):
50
+ return Screen(text="Welcome!", keyboard=[[Button("Shop", callback=MenuCB(section="shop"))]])
51
+
52
+ @bot.callback(MenuCB)
53
+ async def menu(data: MenuCB): # decoded, validated payload injected
54
+ return Screen(text=f"Section: {data.section}")
55
+
56
+ bot.run()
57
+ ```
58
+
59
+ ## Install
60
+
61
+ ```bash
62
+ uv add vitrine-tg
63
+ # or
64
+ pip install vitrine-tg
65
+ ```
66
+
67
+ The distribution is named `vitrine-tg`; the import name is `vitrine`.
68
+
69
+ ## Local development
70
+
71
+ ```bash
72
+ uv sync # install deps (PTB 22, pydantic 2)
73
+ uv run pytest # run tests
74
+ BOT_TOKEN=... uv run python examples/small_bot.py
75
+ BOT_TOKEN=... uv run python examples/launcher_bot.py
76
+ BOT_TOKEN=... ADMIN_IDS=123 uv run python examples/shop/main.py
77
+ ```
78
+
79
+ ## Core features
80
+
81
+ ### Dependency injection (`vitrine.injection`)
82
+
83
+ Handlers declare what they need by parameter name; the framework supplies it. Framework values (`update`, `context`, `bot`, `data`, `state`, `event`, `delivery`), your registered providers, command args, and middleware extras all live in one namespace:
84
+
85
+ ```python
86
+ bot.provide_value("orders", OrderService(...)) # constants / singletons
87
+
88
+ @bot.provide("session") # factories; may be async
89
+ async def session(db): # ...and depend on each other
90
+ async with db.begin() as s:
91
+ yield s # cleanup after handler
92
+
93
+ @bot.callback(OrderCB)
94
+ async def view_order(data: OrderCB, user: User, orders: OrderService, session):
95
+ ...
96
+ ```
97
+
98
+ Dependencies are resolved once per handler call. Bad parameter names fail at startup, not production. `Depends(fn)` is available for explicit one-offs.
99
+
100
+ ### Identity & auth (`vitrine.auth`)
101
+
102
+ You define the principal type. The framework handles resolve-once-per-update, caching, injection, and guards:
103
+
104
+ ```python
105
+ auth = Auth(resolve_user, name="user",
106
+ roles=lambda u: u.roles, is_banned=lambda u: u.banned)
107
+ bot = Bot(token, auth=auth)
108
+
109
+ @bot.command("refund", scope="admin")
110
+ @requires("support") # or @admin_only
111
+ async def refund(user: User, order_id: int): ...
112
+
113
+ @bot.command("profile")
114
+ @requires_principal # resolver returned None? -> "not registered" UX
115
+ async def profile(user: User): ...
116
+ ```
117
+
118
+ Bans are enforced bot-wide. Guard failures turn into friendly error messages: a caller with no resolvable principal gets `NotRegisteredError` (point them at /start), one missing a role gets `NotAuthorizedError`. Any handler asking for `user` gets the same instance -- no re-fetching during an update.
119
+
120
+ ### Screens & delivery (`vitrine.screens`)
121
+
122
+ A `Screen` is a value object (text + keyboard + media + options) that doesn't need an `Update` -- unit-test your views by just calling them. `Delivery` sends it three ways: reply, edit, or proactively to any chat. It handles the annoying stuff:
123
+
124
+ - text↔media transitions send the new message first, then delete the old one
125
+ - all media types are detected (photo/video/animation/document/audio/voice/video-note/sticker)
126
+ - uploads are cached by content hash and re-sent as `file_id`; rejected IDs trigger exactly one retry
127
+ - "message is not modified" errors are silently skipped; `fresh=True` forces a new message anyway
128
+
129
+ Return a `Screen` from a handler and it renders automatically (edit for buttons, reply for commands). Or call `screen.render(update, context)` and `delivery.send(chat_id, screen)` manually.
130
+
131
+ Screens can also carry a **persistent reply keyboard** — the launcher pattern:
132
+
133
+ ```python
134
+ LAUNCHER = ReplyKeyboard([["🛍 Shop", "ℹ️ Help"]]) # persistent by default
135
+
136
+ @bot.command("start")
137
+ async def start():
138
+ return Screen(text="Welcome!", reply_keyboard=LAUNCHER) # set once, sticks around
139
+
140
+ @bot.reply_button("🛍 Shop") # presses route like messages
141
+ async def shop():
142
+ return shop_screen() # jump here from anywhere
143
+ ```
144
+
145
+ A message carries an inline *or* a reply keyboard, never both, and Telegram can't attach reply keyboards to edits — `Delivery` turns such edits into replaces automatically. `Screen(reply_keyboard=REMOVE_REPLY_KEYBOARD)` takes the keyboard away. See `examples/launcher_bot.py`.
146
+
147
+ ### Lifecycle & workers (`vitrine.workers`)
148
+
149
+ ```python
150
+ @bot.on_startup
151
+ async def warmup(delivery): ...
152
+
153
+ @bot.worker(every=30)
154
+ async def reconcile(orders, delivery):
155
+ for o in await orders.confirmed():
156
+ await delivery.send(o.chat_id, receipt_screen(o))
157
+
158
+ @bot.worker()
159
+ async def chain_watcher(feed): ...
160
+ ```
161
+
162
+ Workers get DI, start after init, and shut down gracefully. Crashes restart automatically with exponential backoff -- no manual task supervision needed.
163
+
164
+ ## Features
165
+
166
+ | Feature | Module | Details |
167
+ |---|---|---|
168
+ | Typed callbacks | `callbacks` | Pydantic models with a prefix. Stale/corrupt data returns "button expired" instead of crashing. Keyed encoding (`keyed=True`) uses query strings and tolerates schema changes; `unpack()` auto-detects either format so live buttons survive upgrades. |
169
+ | Reply keyboards | `screens` | `ReplyKeyboard` value object (persistent + resized by default), `@bot.reply_button("label")` routes presses through the full pipeline, `REMOVE_REPLY_KEYBOARD` clears it. |
170
+ | Markdown builder | `markdown` | Composable/nestable nodes, safe escaping for V1+V2, `raw()` escape hatch. |
171
+ | Routers | `routing` | `@router.command/callback/message`, sub-routers, `router.raw()` for plain PTB handlers. |
172
+ | Command args | `args` | Typed params from the signature; required/optional/`Greedy`; auto usage messages. |
173
+ | Pagination | `pagination` | Implement `count()`/`fetch(offset, limit)`, use `Paginator` and `nav_row()` buttons. |
174
+ | Conversations | `conversations` | Dataclass state per run, string transitions, timeout, `on_exit(reason)` hooks. Full DI/middleware/principal interop. |
175
+ | Files/media | `media` | `download()` with timeout and cleanup; content-hash `file_id` cache shared with Screen rendering. |
176
+ | Rate limiting | `ratelimit` | `@throttle(3, per=60)`, custom keys and custom behavior on limits. |
177
+ | Logging | `logging` | Key=value format, one line per update, `audit()` convention. |
178
+ | Errors | `errors` | `@bot.on_error(Type)` registry dispatched by MRO. Handlers can return a `Screen` to render into the current message. |
179
+ | Command discovery | `commands` | Auto `/help` filtered by caller's scopes. `set_my_commands()` per scope. `hidden=True` for internal handlers. |
180
+ | Middleware | `middleware` | `async def mw(event, call_next)` at bot or router scope. `event.extras` values become injectable. |
181
+
182
+ ## Example: scaled mode
183
+
184
+ `examples/shop/` is a full app: a storefront with a **domain layer that never touches the bot layer** (`domain/`), views as pure functions (`domain -> Screen`), services injected into handlers, a `User` principal for guards and menus, a purchase conversation, admin commands, and a reconciler worker that messages buyers. Only `main.py` knows all the layers.
185
+
186
+ ## Escape hatches
187
+
188
+ Not a fork or parallel dispatcher. `bot.build()` returns the PTB `Application` for webhooks. `router.raw(handler, group=...)` registers plain PTB handlers. `Screen.extra` passes kwargs to PTB's send methods. Everything from PTB stays accessible.
189
+
190
+ ## Testing
191
+
192
+ Views are pure functions -- test them without a live bot. `Screen.content()`/`markup()` show what would be sent. `Delivery` accepts any object with `send`/`edit` methods (see `tests/conftest.py` for a mock). This repo has 102 tests that exercise dispatch and conversations the same way you can.
@@ -0,0 +1,162 @@
1
+ # vitrine
2
+
3
+ [![CI](https://github.com/target111/vitrine/actions/workflows/ci.yml/badge.svg)](https://github.com/target111/vitrine/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/vitrine-tg)](https://pypi.org/project/vitrine-tg/)
5
+
6
+ A framework for building Telegram bots with [python-telegram-bot](https://python-telegram-bot.org/) that doesn't get in your way.
7
+
8
+ Handlers are just async functions. You get dependency injection, auth, robust message delivery, background workers, typed callbacks, rate limiting, and decent error handling -- the stuff every real bot needs. Everything from PTB stays accessible.
9
+
10
+ ```python
11
+ from vitrine import Bot, Button, CallbackData, Screen
12
+
13
+ bot = Bot(token="...")
14
+
15
+ class MenuCB(CallbackData, prefix="menu"):
16
+ section: str
17
+
18
+ @bot.command("start", description="Open the menu")
19
+ async def start(update):
20
+ return Screen(text="Welcome!", keyboard=[[Button("Shop", callback=MenuCB(section="shop"))]])
21
+
22
+ @bot.callback(MenuCB)
23
+ async def menu(data: MenuCB): # decoded, validated payload injected
24
+ return Screen(text=f"Section: {data.section}")
25
+
26
+ bot.run()
27
+ ```
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ uv add vitrine-tg
33
+ # or
34
+ pip install vitrine-tg
35
+ ```
36
+
37
+ The distribution is named `vitrine-tg`; the import name is `vitrine`.
38
+
39
+ ## Local development
40
+
41
+ ```bash
42
+ uv sync # install deps (PTB 22, pydantic 2)
43
+ uv run pytest # run tests
44
+ BOT_TOKEN=... uv run python examples/small_bot.py
45
+ BOT_TOKEN=... uv run python examples/launcher_bot.py
46
+ BOT_TOKEN=... ADMIN_IDS=123 uv run python examples/shop/main.py
47
+ ```
48
+
49
+ ## Core features
50
+
51
+ ### Dependency injection (`vitrine.injection`)
52
+
53
+ Handlers declare what they need by parameter name; the framework supplies it. Framework values (`update`, `context`, `bot`, `data`, `state`, `event`, `delivery`), your registered providers, command args, and middleware extras all live in one namespace:
54
+
55
+ ```python
56
+ bot.provide_value("orders", OrderService(...)) # constants / singletons
57
+
58
+ @bot.provide("session") # factories; may be async
59
+ async def session(db): # ...and depend on each other
60
+ async with db.begin() as s:
61
+ yield s # cleanup after handler
62
+
63
+ @bot.callback(OrderCB)
64
+ async def view_order(data: OrderCB, user: User, orders: OrderService, session):
65
+ ...
66
+ ```
67
+
68
+ Dependencies are resolved once per handler call. Bad parameter names fail at startup, not production. `Depends(fn)` is available for explicit one-offs.
69
+
70
+ ### Identity & auth (`vitrine.auth`)
71
+
72
+ You define the principal type. The framework handles resolve-once-per-update, caching, injection, and guards:
73
+
74
+ ```python
75
+ auth = Auth(resolve_user, name="user",
76
+ roles=lambda u: u.roles, is_banned=lambda u: u.banned)
77
+ bot = Bot(token, auth=auth)
78
+
79
+ @bot.command("refund", scope="admin")
80
+ @requires("support") # or @admin_only
81
+ async def refund(user: User, order_id: int): ...
82
+
83
+ @bot.command("profile")
84
+ @requires_principal # resolver returned None? -> "not registered" UX
85
+ async def profile(user: User): ...
86
+ ```
87
+
88
+ Bans are enforced bot-wide. Guard failures turn into friendly error messages: a caller with no resolvable principal gets `NotRegisteredError` (point them at /start), one missing a role gets `NotAuthorizedError`. Any handler asking for `user` gets the same instance -- no re-fetching during an update.
89
+
90
+ ### Screens & delivery (`vitrine.screens`)
91
+
92
+ A `Screen` is a value object (text + keyboard + media + options) that doesn't need an `Update` -- unit-test your views by just calling them. `Delivery` sends it three ways: reply, edit, or proactively to any chat. It handles the annoying stuff:
93
+
94
+ - text↔media transitions send the new message first, then delete the old one
95
+ - all media types are detected (photo/video/animation/document/audio/voice/video-note/sticker)
96
+ - uploads are cached by content hash and re-sent as `file_id`; rejected IDs trigger exactly one retry
97
+ - "message is not modified" errors are silently skipped; `fresh=True` forces a new message anyway
98
+
99
+ Return a `Screen` from a handler and it renders automatically (edit for buttons, reply for commands). Or call `screen.render(update, context)` and `delivery.send(chat_id, screen)` manually.
100
+
101
+ Screens can also carry a **persistent reply keyboard** — the launcher pattern:
102
+
103
+ ```python
104
+ LAUNCHER = ReplyKeyboard([["🛍 Shop", "ℹ️ Help"]]) # persistent by default
105
+
106
+ @bot.command("start")
107
+ async def start():
108
+ return Screen(text="Welcome!", reply_keyboard=LAUNCHER) # set once, sticks around
109
+
110
+ @bot.reply_button("🛍 Shop") # presses route like messages
111
+ async def shop():
112
+ return shop_screen() # jump here from anywhere
113
+ ```
114
+
115
+ A message carries an inline *or* a reply keyboard, never both, and Telegram can't attach reply keyboards to edits — `Delivery` turns such edits into replaces automatically. `Screen(reply_keyboard=REMOVE_REPLY_KEYBOARD)` takes the keyboard away. See `examples/launcher_bot.py`.
116
+
117
+ ### Lifecycle & workers (`vitrine.workers`)
118
+
119
+ ```python
120
+ @bot.on_startup
121
+ async def warmup(delivery): ...
122
+
123
+ @bot.worker(every=30)
124
+ async def reconcile(orders, delivery):
125
+ for o in await orders.confirmed():
126
+ await delivery.send(o.chat_id, receipt_screen(o))
127
+
128
+ @bot.worker()
129
+ async def chain_watcher(feed): ...
130
+ ```
131
+
132
+ Workers get DI, start after init, and shut down gracefully. Crashes restart automatically with exponential backoff -- no manual task supervision needed.
133
+
134
+ ## Features
135
+
136
+ | Feature | Module | Details |
137
+ |---|---|---|
138
+ | Typed callbacks | `callbacks` | Pydantic models with a prefix. Stale/corrupt data returns "button expired" instead of crashing. Keyed encoding (`keyed=True`) uses query strings and tolerates schema changes; `unpack()` auto-detects either format so live buttons survive upgrades. |
139
+ | Reply keyboards | `screens` | `ReplyKeyboard` value object (persistent + resized by default), `@bot.reply_button("label")` routes presses through the full pipeline, `REMOVE_REPLY_KEYBOARD` clears it. |
140
+ | Markdown builder | `markdown` | Composable/nestable nodes, safe escaping for V1+V2, `raw()` escape hatch. |
141
+ | Routers | `routing` | `@router.command/callback/message`, sub-routers, `router.raw()` for plain PTB handlers. |
142
+ | Command args | `args` | Typed params from the signature; required/optional/`Greedy`; auto usage messages. |
143
+ | Pagination | `pagination` | Implement `count()`/`fetch(offset, limit)`, use `Paginator` and `nav_row()` buttons. |
144
+ | Conversations | `conversations` | Dataclass state per run, string transitions, timeout, `on_exit(reason)` hooks. Full DI/middleware/principal interop. |
145
+ | Files/media | `media` | `download()` with timeout and cleanup; content-hash `file_id` cache shared with Screen rendering. |
146
+ | Rate limiting | `ratelimit` | `@throttle(3, per=60)`, custom keys and custom behavior on limits. |
147
+ | Logging | `logging` | Key=value format, one line per update, `audit()` convention. |
148
+ | Errors | `errors` | `@bot.on_error(Type)` registry dispatched by MRO. Handlers can return a `Screen` to render into the current message. |
149
+ | Command discovery | `commands` | Auto `/help` filtered by caller's scopes. `set_my_commands()` per scope. `hidden=True` for internal handlers. |
150
+ | Middleware | `middleware` | `async def mw(event, call_next)` at bot or router scope. `event.extras` values become injectable. |
151
+
152
+ ## Example: scaled mode
153
+
154
+ `examples/shop/` is a full app: a storefront with a **domain layer that never touches the bot layer** (`domain/`), views as pure functions (`domain -> Screen`), services injected into handlers, a `User` principal for guards and menus, a purchase conversation, admin commands, and a reconciler worker that messages buyers. Only `main.py` knows all the layers.
155
+
156
+ ## Escape hatches
157
+
158
+ Not a fork or parallel dispatcher. `bot.build()` returns the PTB `Application` for webhooks. `router.raw(handler, group=...)` registers plain PTB handlers. `Screen.extra` passes kwargs to PTB's send methods. Everything from PTB stays accessible.
159
+
160
+ ## Testing
161
+
162
+ Views are pure functions -- test them without a live bot. `Screen.content()`/`markup()` show what would be sent. `Delivery` accepts any object with `send`/`edit` methods (see `tests/conftest.py` for a mock). This repo has 102 tests that exercise dispatch and conversations the same way you can.
@@ -0,0 +1,64 @@
1
+ [project]
2
+ name = "vitrine-tg"
3
+ version = "0.1.0"
4
+ description = "A batteries-included framework on top of python-telegram-bot: typed callbacks, screens, DI, auth, conversations, and supervised workers."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [
9
+ { name = "target111", email = "target111@protonmail.com" }
10
+ ]
11
+ requires-python = ">=3.11"
12
+ keywords = ["telegram", "bot", "framework", "python-telegram-bot", "asyncio"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Framework :: AsyncIO",
16
+ "Intended Audience :: Developers",
17
+ "Operating System :: OS Independent",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Programming Language :: Python :: 3.13",
22
+ "Programming Language :: Python :: 3.14",
23
+ "Topic :: Communications :: Chat",
24
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
25
+ "Typing :: Typed",
26
+ ]
27
+ dependencies = [
28
+ "pydantic>=2.13.4",
29
+ "python-telegram-bot[job-queue]>=22.8",
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/target111/vitrine"
34
+ Repository = "https://github.com/target111/vitrine"
35
+ Issues = "https://github.com/target111/vitrine/issues"
36
+ Changelog = "https://github.com/target111/vitrine/blob/main/CHANGELOG.md"
37
+
38
+ [build-system]
39
+ requires = ["uv_build>=0.11.28,<0.12.0"]
40
+ build-backend = "uv_build"
41
+
42
+ [tool.uv.build-backend]
43
+ module-name = "vitrine"
44
+
45
+ [dependency-groups]
46
+ dev = [
47
+ "pytest>=9.1.1",
48
+ "pytest-asyncio>=1.4.0",
49
+ "ruff>=0.14.4",
50
+ ]
51
+
52
+ [tool.ruff]
53
+ line-length = 92
54
+
55
+ [tool.ruff.lint]
56
+ select = ["E", "F", "I", "UP"]
57
+
58
+ [tool.pytest.ini_options]
59
+ asyncio_mode = "auto"
60
+ asyncio_default_fixture_loop_scope = "function"
61
+ testpaths = ["tests"]
62
+ filterwarnings = [
63
+ "ignore::telegram.warnings.PTBUserWarning",
64
+ ]
@@ -0,0 +1,106 @@
1
+ """vitrine — a batteries-included foundation on top of python-telegram-bot.
2
+
3
+ Thin in ceremony, deep in capability: typed callback routing, screens with
4
+ robust delivery, dependency injection, an app-defined principal, guided
5
+ conversations, supervised workers, rate limiting, structured logging, and
6
+ typed error UX — while PTB's dispatcher, handlers, and filters stay fully
7
+ reachable underneath.
8
+ """
9
+
10
+ from .app import Bot, VitrineContext
11
+ from .args import Greedy
12
+ from .auth import Auth, admin_only, requires, requires_principal
13
+ from .callbacks import CallbackData
14
+ from .conversations import END, Conversation, ExitReason
15
+ from .exceptions import (
16
+ AuthError,
17
+ BannedError,
18
+ CallbackDataError,
19
+ ConfigurationError,
20
+ FrameworkError,
21
+ InjectionError,
22
+ NotAuthorizedError,
23
+ NotRegisteredError,
24
+ RateLimitedError,
25
+ UsageError,
26
+ UserFacingError,
27
+ )
28
+ from .injection import Depends, Invocation, Providers
29
+ from .logging import audit, log_event, setup_logging
30
+ from .media import FileIdCache, InMemoryFileIdCache, download
31
+ from .middleware import Event
32
+ from .pagination import ListSource, Page, PageSource, Paginator, nav_row
33
+ from .ratelimit import throttle
34
+ from .routing import Router
35
+ from .screens import (
36
+ REMOVE_REPLY_KEYBOARD,
37
+ Animation,
38
+ Audio,
39
+ Button,
40
+ ButtonStyle,
41
+ Delivery,
42
+ Document,
43
+ Media,
44
+ Photo,
45
+ ReplyButton,
46
+ ReplyKeyboard,
47
+ Screen,
48
+ Video,
49
+ Voice,
50
+ )
51
+
52
+ __all__ = [
53
+ "Animation",
54
+ "Audio",
55
+ "Auth",
56
+ "AuthError",
57
+ "BannedError",
58
+ "Bot",
59
+ "Button",
60
+ "ButtonStyle",
61
+ "CallbackData",
62
+ "CallbackDataError",
63
+ "ConfigurationError",
64
+ "Conversation",
65
+ "Delivery",
66
+ "Depends",
67
+ "Document",
68
+ "END",
69
+ "Event",
70
+ "ExitReason",
71
+ "VitrineContext",
72
+ "FileIdCache",
73
+ "FrameworkError",
74
+ "Greedy",
75
+ "InMemoryFileIdCache",
76
+ "InjectionError",
77
+ "Invocation",
78
+ "ListSource",
79
+ "Media",
80
+ "NotAuthorizedError",
81
+ "NotRegisteredError",
82
+ "Page",
83
+ "PageSource",
84
+ "Paginator",
85
+ "Photo",
86
+ "Providers",
87
+ "REMOVE_REPLY_KEYBOARD",
88
+ "RateLimitedError",
89
+ "ReplyButton",
90
+ "ReplyKeyboard",
91
+ "Router",
92
+ "Screen",
93
+ "UsageError",
94
+ "UserFacingError",
95
+ "Video",
96
+ "Voice",
97
+ "admin_only",
98
+ "audit",
99
+ "download",
100
+ "log_event",
101
+ "nav_row",
102
+ "requires",
103
+ "requires_principal",
104
+ "setup_logging",
105
+ "throttle",
106
+ ]