botty-framework 0.0.1__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.
- botty_framework-0.0.1/PKG-INFO +535 -0
- botty_framework-0.0.1/README.md +523 -0
- botty_framework-0.0.1/pyproject.toml +41 -0
- botty_framework-0.0.1/src/botty/__init__.py +36 -0
- botty_framework-0.0.1/src/botty/app.py +85 -0
- botty_framework-0.0.1/src/botty/classes.py +90 -0
- botty_framework-0.0.1/src/botty/context.py +36 -0
- botty_framework-0.0.1/src/botty/database.py +42 -0
- botty_framework-0.0.1/src/botty/exceptions.py +121 -0
- botty_framework-0.0.1/src/botty/py.typed +0 -0
- botty_framework-0.0.1/src/botty/router/__init__.py +20 -0
- botty_framework-0.0.1/src/botty/router/dependencies.py +137 -0
- botty_framework-0.0.1/src/botty/router/handlers.py +46 -0
- botty_framework-0.0.1/src/botty/router/registry.py +240 -0
- botty_framework-0.0.1/src/botty/router/response_processor.py +199 -0
- botty_framework-0.0.1/src/botty/router/router.py +124 -0
- botty_framework-0.0.1/src/botty/router/scope.py +29 -0
- botty_framework-0.0.1/src/botty/router/typing.py +72 -0
- botty_framework-0.0.1/src/botty/router/utils.py +105 -0
- botty_framework-0.0.1/src/botty/router/validation.py +163 -0
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: botty-framework
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Botty brings the elegance of FastAPI's dependency injection and type hints to Telegram bot development. Write clean, testable bot code with automatic dependency resolution, built-in message registry, and a developer-friendly API.
|
|
5
|
+
Author: melaveetha
|
|
6
|
+
Author-email: melaveetha <melaveetha@gmail.com>
|
|
7
|
+
Requires-Dist: loguru>=0.7.3
|
|
8
|
+
Requires-Dist: python-telegram-bot>=22.6
|
|
9
|
+
Requires-Dist: sqlmodel>=0.0.32
|
|
10
|
+
Requires-Python: >=3.13
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# Botty
|
|
14
|
+
|
|
15
|
+
**A FastAPI-inspired modern framework for building Telegram bots with Python**
|
|
16
|
+
|
|
17
|
+
Botty is the elegance of FastAPI's dependency injection and type hints to Telegram bot development (based on `python-telegram-bot`).
|
|
18
|
+
Write clean, code with automatic dependency resolution and a developer-friendly API.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from botty import Router, HandlerResponse, Context, Answer, Update
|
|
23
|
+
|
|
24
|
+
router = Router()
|
|
25
|
+
|
|
26
|
+
@router.command("start")
|
|
27
|
+
async def start_handler(
|
|
28
|
+
update: Update,
|
|
29
|
+
context: Context,
|
|
30
|
+
user_repo: UserRepository # Auto-injected!
|
|
31
|
+
) -> HandlerResponse:
|
|
32
|
+
user = user_repo.get_or_create(update.effective_user.id)
|
|
33
|
+
yield Answer(text=f"Welcome back, {user.name}! π")
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## π― Main Idea
|
|
37
|
+
|
|
38
|
+
Traditional Telegram bot frameworks require a lot of boilerplate and make it hard to:
|
|
39
|
+
- Share code between handlers (no clean dependency injection)
|
|
40
|
+
- Track and edit messages you've sent
|
|
41
|
+
- Write testable code (everything is tightly coupled)
|
|
42
|
+
- Get type hints and IDE support
|
|
43
|
+
|
|
44
|
+
**Botty uses** bringing FastAPI's best ideas to Telegram bots:
|
|
45
|
+
- **Dependency Injection** - Repositories and services are automatically injected
|
|
46
|
+
- **Type Hints** - Full type safety with IDE autocomplete
|
|
47
|
+
- **Async Generators** - Handlers yield responses, making sending and editing messages trivial
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install botty-framework
|
|
54
|
+
```
|
|
55
|
+
or
|
|
56
|
+
```bash
|
|
57
|
+
uv add botty-framework
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
## π Features
|
|
62
|
+
|
|
63
|
+
### FastAPI-Style Dependency Injection
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
async def get_repository(update: Update, context: Context, session: Session): # β Session handled automatically
|
|
67
|
+
return UserRepository(session)
|
|
68
|
+
|
|
69
|
+
async def get_settings(update: Update, context: Context): # Database session NOT created here
|
|
70
|
+
return SettingsService()
|
|
71
|
+
|
|
72
|
+
UserRepositoryDep = Annotated[UserRepository, Depends(get_repository)]
|
|
73
|
+
SettingsServiceDep = Annotated[SettingsService, Depends(get_settings)]
|
|
74
|
+
|
|
75
|
+
@router.command("profile")
|
|
76
|
+
async def show_profile(
|
|
77
|
+
update: Update,
|
|
78
|
+
context: Context,
|
|
79
|
+
user_repo: UserRepositoryDep, # Injected automatically
|
|
80
|
+
settings_svc: SettingsServiceDep # Services too!
|
|
81
|
+
) -> HandlerResponse:
|
|
82
|
+
user = user_repo.get(update.effective_user.id)
|
|
83
|
+
settings = settings_svc.get_user_settings(user.id)
|
|
84
|
+
|
|
85
|
+
yield Answer(f"π€ {user.name}\nβοΈ Theme: {settings.theme}")
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
### Message Registry & Smart Editing
|
|
90
|
+
|
|
91
|
+
Track messages and edit them later by key, handler name, or automatically:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
@router.command("countdown")
|
|
95
|
+
async def countdown_handler(
|
|
96
|
+
update: Update,
|
|
97
|
+
context: Context
|
|
98
|
+
) -> HandlerResponse:
|
|
99
|
+
# Send message with a key
|
|
100
|
+
yield Answer("Starting in 3...", message_key="countdown")
|
|
101
|
+
|
|
102
|
+
await asyncio.sleep(1)
|
|
103
|
+
|
|
104
|
+
# Edit by key
|
|
105
|
+
yield EditAnswer("2...", message_key="countdown")
|
|
106
|
+
|
|
107
|
+
await asyncio.sleep(1)
|
|
108
|
+
yield EditAnswer("1...", message_key="countdown")
|
|
109
|
+
|
|
110
|
+
await asyncio.sleep(1)
|
|
111
|
+
yield EditAnswer("GO! π", message_key="countdown")
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Clean Handler Syntax
|
|
115
|
+
|
|
116
|
+
Handlers are async generators that yield responses:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
@router.command("weather")
|
|
120
|
+
async def weather_handler(
|
|
121
|
+
update: Update,
|
|
122
|
+
context: Context,
|
|
123
|
+
weather_api: WeatherService
|
|
124
|
+
) -> HandlerResponse:
|
|
125
|
+
city = " ".join(context.args)
|
|
126
|
+
|
|
127
|
+
# Show loading state
|
|
128
|
+
yield Answer("π Fetching weather...", message_key="weather")
|
|
129
|
+
|
|
130
|
+
# Get data
|
|
131
|
+
data = await weather_api.get_current(city)
|
|
132
|
+
|
|
133
|
+
# Update message
|
|
134
|
+
yield EditAnswer(
|
|
135
|
+
f"π€οΈ {city}: {data.temp}Β°C, {data.condition}",
|
|
136
|
+
message_key="weather"
|
|
137
|
+
)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Repository Pattern
|
|
141
|
+
|
|
142
|
+
Built-in support for the repository pattern with SQLModel:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from botty import BaseRepository
|
|
146
|
+
from sqlmodel import Session, select
|
|
147
|
+
|
|
148
|
+
class UserRepository(BaseRepository[User]):
|
|
149
|
+
model = User
|
|
150
|
+
|
|
151
|
+
def get_by_telegram_id(self, telegram_id: int) -> User | None:
|
|
152
|
+
statement = select(User).where(User.telegram_id == telegram_id)
|
|
153
|
+
return self.session.exec(statement).first()
|
|
154
|
+
|
|
155
|
+
def get_active_users(self) -> list[User]:
|
|
156
|
+
statement = select(User).where(User.is_active == True)
|
|
157
|
+
return list(self.session.exec(statement).all())
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
UserRepositoryDep = Annotated[UserRepository, Depends(get_repository)]
|
|
161
|
+
|
|
162
|
+
# Automatically injected with proper session management!
|
|
163
|
+
@router.command("stats")
|
|
164
|
+
async def stats_handler(
|
|
165
|
+
update: Update,
|
|
166
|
+
context: Context,
|
|
167
|
+
user_repo: UserRepositoryDep
|
|
168
|
+
) -> HandlerResponse:
|
|
169
|
+
active = user_repo.get_active_users()
|
|
170
|
+
yield answer(f"π Active users: {len(active)}")
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
### Type Safety & Validation
|
|
175
|
+
|
|
176
|
+
Handlers are validated at registration time with helpful error messages:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
@router.command("test")
|
|
180
|
+
def wrong_handler(update: Update, context: Context): # β Forgot 'async'
|
|
181
|
+
yield Answer("Hi")
|
|
182
|
+
|
|
183
|
+
# Error: Handler must be an async function (use 'async def')
|
|
184
|
+
# π‘ Suggestion: Change 'def wrong_handler(...)' to 'async def wrong_handler(...)'
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Built-in Database Support
|
|
188
|
+
|
|
189
|
+
SQLModel integration with automatic session management:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
from botty import AppBuilder, SQLiteProvider
|
|
193
|
+
from sqlmodel import SQLModel, Field
|
|
194
|
+
|
|
195
|
+
# Define your models
|
|
196
|
+
class User(SQLModel, table=True):
|
|
197
|
+
id: int | None = Field(default=None, primary_key=True)
|
|
198
|
+
telegram_id: int = Field(unique=True)
|
|
199
|
+
name: str
|
|
200
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(datetime.UTC))
|
|
201
|
+
|
|
202
|
+
# Build app with database
|
|
203
|
+
app = (
|
|
204
|
+
AppBuilder(token="YOUR_TOKEN")
|
|
205
|
+
.database(SQLiteProvider("bot.db"))
|
|
206
|
+
.build()
|
|
207
|
+
)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## π¨ Inspirations
|
|
211
|
+
|
|
212
|
+
### FastAPI
|
|
213
|
+
Botty is heavily inspired by FastAPI's elegant dependency injection system:
|
|
214
|
+
- Type hints for automatic injection
|
|
215
|
+
- Clean decorator-based routing
|
|
216
|
+
- Dependency resolution with `Depends()`
|
|
217
|
+
|
|
218
|
+
### Django
|
|
219
|
+
Repository pattern and clean architecture:
|
|
220
|
+
- Repository layer for data access
|
|
221
|
+
- Service layer for business logic
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
## π Complete Example
|
|
225
|
+
|
|
226
|
+
### Project Structure
|
|
227
|
+
|
|
228
|
+
Botty auto-discovers handlers from project structure:
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
todo_bot/
|
|
232
|
+
βββ src/
|
|
233
|
+
β βββ handlers/
|
|
234
|
+
β β βββ __init__.py
|
|
235
|
+
β β βββ start.py # Start command handlers
|
|
236
|
+
β β βββ todos.py # Todo-related handlers
|
|
237
|
+
β β βββ settings.py # Settings handlers
|
|
238
|
+
β βββ repositories/
|
|
239
|
+
β β βββ __init__.py
|
|
240
|
+
β β βββ user_repository.py
|
|
241
|
+
β β βββ todo_repository.py
|
|
242
|
+
β βββ services/
|
|
243
|
+
β β βββ __init__.py
|
|
244
|
+
β β βββ notification_service.py
|
|
245
|
+
β βββ models/
|
|
246
|
+
β β βββ __init__.py
|
|
247
|
+
β β βββ user.py
|
|
248
|
+
β β βββ todo.py
|
|
249
|
+
βββ main.py # App entry point
|
|
250
|
+
βββ pyproject.toml
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Implementation
|
|
254
|
+
|
|
255
|
+
Here's a full todo bot showing all features:
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
from datetime import datetime
|
|
259
|
+
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
|
260
|
+
from sqlmodel import SQLModel, Field, Session, select
|
|
261
|
+
from botty import (
|
|
262
|
+
AppBuilder,
|
|
263
|
+
Router,
|
|
264
|
+
Context,
|
|
265
|
+
HandlerResponse,
|
|
266
|
+
BaseRepository,
|
|
267
|
+
Answer,
|
|
268
|
+
EditAnswer,
|
|
269
|
+
SQLiteProvider,
|
|
270
|
+
Update,
|
|
271
|
+
Depends
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# ============================================================================
|
|
275
|
+
# Models
|
|
276
|
+
# ============================================================================
|
|
277
|
+
|
|
278
|
+
class Todo(SQLModel, table=True):
|
|
279
|
+
id: int | None = Field(default=None, primary_key=True)
|
|
280
|
+
user_id: int
|
|
281
|
+
task: str
|
|
282
|
+
completed: bool = False
|
|
283
|
+
created_at: datetime = Field(default_factory=lambda: datetime.now(datetime.UTC))
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
# ============================================================================
|
|
287
|
+
# Repositories
|
|
288
|
+
# ============================================================================
|
|
289
|
+
|
|
290
|
+
class TodoRepository(BaseRepository[Todo]):
|
|
291
|
+
model = Todo
|
|
292
|
+
|
|
293
|
+
def get_by_user(self, user_id: int) -> list[Todo]:
|
|
294
|
+
"""Get all todos for a user."""
|
|
295
|
+
statement = select(Todo).where(Todo.user_id == user_id)
|
|
296
|
+
return list(self.session.exec(statement).all())
|
|
297
|
+
|
|
298
|
+
def get_pending(self, user_id: int) -> list[Todo]:
|
|
299
|
+
"""Get incomplete todos."""
|
|
300
|
+
statement = (
|
|
301
|
+
select(Todo)
|
|
302
|
+
.where(Todo.user_id == user_id)
|
|
303
|
+
.where(Todo.completed == False)
|
|
304
|
+
)
|
|
305
|
+
return list(self.session.exec(statement).all())
|
|
306
|
+
|
|
307
|
+
def toggle_complete(self, todo_id: int) -> Todo | None:
|
|
308
|
+
"""Toggle completion status."""
|
|
309
|
+
todo = self.get(todo_id)
|
|
310
|
+
if todo:
|
|
311
|
+
todo.completed = not todo.completed
|
|
312
|
+
self.update(todo)
|
|
313
|
+
return todo
|
|
314
|
+
|
|
315
|
+
def get_todo_repo(update: Update, context: Context, session: Session):
|
|
316
|
+
return TodoRepository(session)
|
|
317
|
+
|
|
318
|
+
TodoRepositoryDep = Annotated[TodoRepository, Depends(get_todo_repo)]
|
|
319
|
+
|
|
320
|
+
# ============================================================================
|
|
321
|
+
# Handlers
|
|
322
|
+
# ============================================================================
|
|
323
|
+
|
|
324
|
+
router = Router()
|
|
325
|
+
|
|
326
|
+
@router.command("start")
|
|
327
|
+
async def start_handler(
|
|
328
|
+
update: Update,
|
|
329
|
+
context: Context
|
|
330
|
+
) -> HandlerResponse:
|
|
331
|
+
"""Welcome message."""
|
|
332
|
+
yield answer(
|
|
333
|
+
"π Welcome to Todo Bot!\n\n"
|
|
334
|
+
"Commands:\n"
|
|
335
|
+
"/add <task> - Add a new todo\n"
|
|
336
|
+
"/list - Show all todos\n"
|
|
337
|
+
"/pending - Show incomplete todos"
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@router.command("add")
|
|
342
|
+
async def add_todo_handler(
|
|
343
|
+
update: Update,
|
|
344
|
+
context: Context,
|
|
345
|
+
todo_repo: TodoRepositoryDep # Auto-injected!
|
|
346
|
+
) -> HandlerResponse:
|
|
347
|
+
"""Add a new todo."""
|
|
348
|
+
if not context.args:
|
|
349
|
+
yield answer("β Usage: /add <task description>")
|
|
350
|
+
return
|
|
351
|
+
|
|
352
|
+
task = " ".join(context.args)
|
|
353
|
+
todo = Todo(
|
|
354
|
+
user_id=update.effective_user.id,
|
|
355
|
+
task=task
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
todo_repo.create(todo)
|
|
359
|
+
yield answer(f"β
Added: {task}")
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@router.command("list")
|
|
363
|
+
async def list_todos_handler(
|
|
364
|
+
update: Update,
|
|
365
|
+
context: Context,
|
|
366
|
+
todo_repo: TodoRepositoryDep # Auto-injected!
|
|
367
|
+
) -> HandlerResponse:
|
|
368
|
+
"""List all todos."""
|
|
369
|
+
todos = todo_repo.get_by_user(update.effective_user.id)
|
|
370
|
+
|
|
371
|
+
if not todos:
|
|
372
|
+
yield answer("π You have no todos!\nUse /add to create one.")
|
|
373
|
+
return
|
|
374
|
+
|
|
375
|
+
# Build message with buttons
|
|
376
|
+
text = "π Your todos:\n\n"
|
|
377
|
+
buttons = []
|
|
378
|
+
|
|
379
|
+
for i, todo in enumerate(todos, 1):
|
|
380
|
+
status = "β
" if todo.completed else "βΊοΈ"
|
|
381
|
+
text += f"{i}. {status} {todo.task}\n"
|
|
382
|
+
|
|
383
|
+
button_text = "β
Complete" if not todo.completed else "β©οΈ Undo"
|
|
384
|
+
buttons.append([
|
|
385
|
+
InlineKeyboardButton(
|
|
386
|
+
f"{i}. {button_text}",
|
|
387
|
+
callback_data=f"toggle_{todo.id}"
|
|
388
|
+
)
|
|
389
|
+
])
|
|
390
|
+
|
|
391
|
+
keyboard = InlineKeyboardMarkup(buttons)
|
|
392
|
+
|
|
393
|
+
yield answer(
|
|
394
|
+
text=text,
|
|
395
|
+
reply_markup=keyboard,
|
|
396
|
+
message_key="todo_list"
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
@router.command("pending")
|
|
401
|
+
async def pending_todos_handler(
|
|
402
|
+
update: Update,
|
|
403
|
+
context: Context,
|
|
404
|
+
todo_repo: TodoRepositoryDep # Auto-injected!
|
|
405
|
+
) -> HandlerResponse:
|
|
406
|
+
"""List incomplete todos."""
|
|
407
|
+
todos = todo_repo.get_pending(update.effective_user.id)
|
|
408
|
+
|
|
409
|
+
if not todos:
|
|
410
|
+
yield answer("π All done! No pending todos.")
|
|
411
|
+
return
|
|
412
|
+
|
|
413
|
+
text = "βΊοΈ Pending todos:\n\n"
|
|
414
|
+
for i, todo in enumerate(todos, 1):
|
|
415
|
+
text += f"{i}. {todo.task}\n"
|
|
416
|
+
|
|
417
|
+
yield answer(text)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@router.callback_query(r"^toggle_(\d+)")
|
|
421
|
+
async def toggle_todo_handler(
|
|
422
|
+
update: Update,
|
|
423
|
+
context: Context,
|
|
424
|
+
todo_repo: TodoRepositoryDep # Auto-injected!
|
|
425
|
+
) -> HandlerResponse:
|
|
426
|
+
"""Toggle todo completion."""
|
|
427
|
+
query = update.callback_query
|
|
428
|
+
await query.answer()
|
|
429
|
+
|
|
430
|
+
# Extract todo ID from callback data
|
|
431
|
+
todo_id = int(query.data.split("_")[1])
|
|
432
|
+
|
|
433
|
+
# Toggle the todo
|
|
434
|
+
todo = todo_repo.toggle_complete(todo_id)
|
|
435
|
+
|
|
436
|
+
if not todo:
|
|
437
|
+
yield edit_answer("β Todo not found")
|
|
438
|
+
return
|
|
439
|
+
|
|
440
|
+
# Refresh the list
|
|
441
|
+
todos = todo_repo.get_by_user(update.effective_user.id)
|
|
442
|
+
|
|
443
|
+
text = "π Your todos:\n\n"
|
|
444
|
+
buttons = []
|
|
445
|
+
|
|
446
|
+
for i, t in enumerate(todos, 1):
|
|
447
|
+
status = "β
" if t.completed else "βΊοΈ"
|
|
448
|
+
text += f"{i}. {status} {t.task}\n"
|
|
449
|
+
|
|
450
|
+
button_text = "β
Complete" if not t.completed else "β©οΈ Undo"
|
|
451
|
+
buttons.append([
|
|
452
|
+
InlineKeyboardButton(
|
|
453
|
+
f"{i}. {button_text}",
|
|
454
|
+
callback_data=f"toggle_{t.id}"
|
|
455
|
+
)
|
|
456
|
+
])
|
|
457
|
+
|
|
458
|
+
keyboard = InlineKeyboardMarkup(buttons)
|
|
459
|
+
|
|
460
|
+
yield edit_answer(
|
|
461
|
+
text=text,
|
|
462
|
+
reply_markup=keyboard,
|
|
463
|
+
message_key="todo_list"
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
# ============================================================================
|
|
468
|
+
# Application Setup
|
|
469
|
+
# ============================================================================
|
|
470
|
+
|
|
471
|
+
if __name__ == "__main__":
|
|
472
|
+
app = (
|
|
473
|
+
AppBuilder(token="YOUR_BOT_TOKEN_HERE")
|
|
474
|
+
.database(SQLiteProvider("todos.db"))
|
|
475
|
+
.build()
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
app.launch()
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## π Comparison
|
|
482
|
+
|
|
483
|
+
### vs. python-telegram-bot
|
|
484
|
+
|
|
485
|
+
**python-telegram-bot:**
|
|
486
|
+
```python
|
|
487
|
+
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
488
|
+
# Manual session management
|
|
489
|
+
session = Session(engine)
|
|
490
|
+
try:
|
|
491
|
+
user_repo = UserRepository(session)
|
|
492
|
+
user = user_repo.get(update.effective_user.id)
|
|
493
|
+
await update.message.reply_text(f"Hello {user.name}")
|
|
494
|
+
finally:
|
|
495
|
+
session.close()
|
|
496
|
+
|
|
497
|
+
application.add_handler(CommandHandler("start", start))
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
**Botty:**
|
|
501
|
+
```python
|
|
502
|
+
@router.command("start")
|
|
503
|
+
async def start_handler(
|
|
504
|
+
update: Update,
|
|
505
|
+
context: Context,
|
|
506
|
+
user_repo: UserRepositoryDep # Auto-injected!
|
|
507
|
+
) -> HandlerResponse:
|
|
508
|
+
user = user_repo.get(update.effective_user.id)
|
|
509
|
+
yield Answer(f"Hello {user.name}")
|
|
510
|
+
# Session managed automatically
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
## π License
|
|
514
|
+
|
|
515
|
+
MIT License - see LICENSE file for details
|
|
516
|
+
|
|
517
|
+
## Acknowledgments
|
|
518
|
+
|
|
519
|
+
- [FastAPI](https://fastapi.tiangolo.com/) - For the inspiration
|
|
520
|
+
- [python-telegram-bot](https://python-telegram-bot.org/) - For the excellent Telegram wrapper
|
|
521
|
+
- [SQLModel](https://sqlmodel.tiangolo.com/) - For the ORM integration
|
|
522
|
+
|
|
523
|
+
## πΊοΈ Roadmap
|
|
524
|
+
|
|
525
|
+
- [ ] More database providers (PostgreSQL, MySQL)
|
|
526
|
+
- [ ] Conversation state management
|
|
527
|
+
- [ ] Admin panel
|
|
528
|
+
- [ ] CLI for scaffolding projects
|
|
529
|
+
- [ ] Built-in middleware support
|
|
530
|
+
- [ ] Metrics and monitoring
|
|
531
|
+
- [ ] Plugin system
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
*Botty - Because building bots should be as elegant as using them*
|