This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
- Local development:
just runoruv run python -m eduzenbot - Docker development:
just start(starts with docker-compose, then tails logs) - Debug mode:
just start-debug(interactive docker session) - IPython shell:
just shell(interactive Python shell in Docker)
- Local tests:
just uv-testoruv run coverage run -m pytest - Docker tests:
just test - Single test:
uv run pytest path/to/test.py::test_name - Coverage report:
just coverage(runscoverage combinethencoverage report) - Tests use an autouse fixture (
tests/conftest.py) that binds all models to an in-memory SQLite DB per test function — no setup needed - Factory Boy factories for User and EventLog are in
tests/factories.py - HTTP mocking uses
respx(forhttpx-based API calls)
- Lint and format:
just format(orjust fmt) — runsuv run pre-commit run --all-files - Pre-commit hooks: ruff (lint + format), isort (profile=black), pyupgrade (py311+), plus standard checks (check-ast, trailing-whitespace, etc.)
- Ruff config: target Python 3.13, line-length 120
- Copy
.env.sampleto.envand configure required environment variables - Required:
TOKEN(Telegram bot token),EDUZEN_ID(admin user ID) - Optional:
SENTRY_DSN,DATABASE_URL,LOG_LEVEL,openweathermap_token,NEWS_API_KEY,TMDB_API_KEY
- Load
.env, init Sentry + Logfire, create/migrate DB tables - Build
ApplicationwithExtBotand setpost_init = on_startup on_startup: loads plugins viaload_and_register_plugins(), registers hardcoded handlers (set,configure_report,cancel_report), registers unknown command fallback, sets bot commands in Telegram, schedules existing reports from DB- Start polling
Plugins are discovered dynamically from eduzenbot/plugins/commands/<plugin_name>/.
Each plugin folder requires a command.py with a module-level docstring mapping commands to functions:
"""
clima - klima
klima - klima
"""
@create_user
async def klima(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
...The loader parses the docstring (command_name - function_name per line), finds the function by name, and creates CommandHandler instances. Commands must be unique across all plugins.
Plugins that call external APIs typically split logic into a separate api.py in the same folder (e.g., weather/api.py, btc/api.py, dolar/api.py).
@create_user(eduzenbot/decorators.py): Wraps command handlers to upsert the Telegram user in the DB, log the command toEventLog, and instrument with Logfire. Does not block the handler on DB errors.@restricted(eduzenbot/auth/restricted.py): Limits command access to theEDUZEN_IDuser only. Silently returnsNonefor unauthorized users.- Decorator stacking order matters:
@create_usershould be outermost,@restrictedinside it (seeschedule_daily_reportfor example).
- Uses Peewee ORM with
ThreadSafeDatabaseMetadata - SQLite in-memory by default (
:memory:if noDATABASE_URL), PostgreSQL viapsycopgfor production - Models:
User,EventLog,Report— all ineduzenbot/models.py - Tables created/migrated automatically at startup via
eduzenbot/scripts/initialize_db.py
eduzenbot/plugins/job_queue/alarms/command.py: Handles/setand/cancel_reportcommands for daily scheduled reportseduzenbot/domain/report_scheduler.py: On startup, reads allReportrecords and schedulesrun_dailyjobs (timezone: Europe/Amsterdam)- Reports are configurable with feature flags: weather, crypto, dollar, news
eduzenbot/plugins/messages/unknown.py: Fallback handler for unrecognized commandseduzenbot/scripts/: DB initialization and data loading utilities