This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Printomat lets people submit text and images to be printed on a receipt printer. Three components: a FastAPI server that queues print requests, a client that connects via WebSocket and drives an ESC/POS receipt printer, and services that automatically generate and submit print jobs (weather forecasts, chess puzzles, etc.).
# Run server
uv run python -m server
# Run client (--no-printer for testing without hardware)
uv run python -m client
# Run a service (from repo root; each service is a separate process)
uv run python -m services.weather_service
uv run python -m services.chess_puzzle_service
uv run python -m services.echo_service
# Run a service with immediate print (skip waiting for scheduled time)
uv run python -m services.weather_service --print
# Database migrations (from server/)
uv run alembic upgrade headEach component needs a config.toml copied from its config.example.toml before first run.
UV workspace — root pyproject.toml declares three workspace members (server, client, services), each with its own pyproject.toml. Single uv.lock at root. Requires Python 3.13+.
Server (server/) — FastAPI app with SQLite (SQLAlchemy + Alembic). REST API for submissions (/submit), WebSocket for client connection. Rate limiting by IP, friendship tokens to bypass limits, service tokens for automated services. Interactive admin console (console.py).
Client (client/) — WebSocket client that receives jobs from the server and prints via python-escpos. Handles text rendering (custom fonts in client/font/) and image processing (Pillow for resizing/conversion, ESC/POS dithering for monochrome output).
Services (services/) — BaseService ABC in base.py provides the framework. Each service inherits it, implements loop() for its periodic task, and calls send_print_request() to POST to the server's /submit endpoint. Services are configured and enabled/disabled in services/config.toml. Entry point is ClassName.run_from_config() which handles arg parsing, config loading, and the asyncio event loop.
To add a new service: create services/foo_service.py, define FooService(BaseService), implement loop() and from_config(), call FooService.run_from_config() at module level, add a [services.foo] section to config.example.toml.
- All services and the client are async (
asyncio). Services useaiohttpfor HTTP, client useswebsockets. - Config is TOML-based (
tomlifor parsing). Each component reads its ownconfig.tomlrelative to its package directory. - Service name is derived from class name:
WeatherService→weather, used to look up[services.weather]in config. - Images are transmitted as base64-encoded PNG strings.
- Services run from the repo root (imports are relative to package, e.g.
from config import ServiceConfig).