# Placeholder API Generate SVG and PNG placeholder images with configurable dimensions and font size. ## Prerequisites Local development uses uv and Python 3.12 or newer. PNG rendering also needs Cairo, which you install with your system package manager before running `uv sync`. Run the appropriate command for your distribution as root: | Distribution | Install Cairo | | --- | --- | | [Alpine](https://pkgs.alpinelinux.org/packages?name=cairo) | `apk add cairo` | | [Arch](https://archlinux.org/packages/extra/x86_64/cairo/) | `pacman -S cairo` | | [Debian](https://packages.debian.org/en/trixie/libcairo2) | `apt install libcairo2` | | [Void](https://github.com/void-linux/void-packages/blob/master/srcpkgs/cairo/template) | `xbps-install -S cairo` | For a container setup, you only need Podman on the host. The image includes Python, Cairo, and fonts. ## Running the app Run the development server: ```sh uv sync uv run flask --app app:create_app run --debug ``` Alternatively, `uv run app` starts the development server without debug mode. - `GET /placeholder.svg` returns an SVG placeholder. - `GET /placeholder.png` returns a PNG placeholder. - `GET /healthz` returns `{"status": "ok"}`. Image endpoints accept `width`, `height`, and `font_size` query parameters. For example, `/placeholder.png?width=320&height=180&font_size=20` returns a 320 × 180 PNG with 20-pixel text. Run the Gunicorn server locally: ```sh uv run gunicorn --bind 0.0.0.0:8000 --workers 2 --access-logfile - --error-logfile - 'app:create_app()' ``` Build and run the container: ```sh podman build -t placeholder-api -f Containerfile . podman run --rm -p 8000:8000 placeholder-api ``` The container runs as an unprivileged user and serves HTTP on port 8000. Check it with `curl http://localhost:8000/healthz`. Additional Gunicorn options can be passed through `GUNICORN_CMD_ARGS`. The application factory is in `src/app/__init__.py`; API routes live in `src/app/routes/api.py`. ## Development checks Install the Git hooks once after cloning and running `uv sync`: ```sh uv run pre-commit install ``` Run all checks on tracked files: ```sh uv run pre-commit run --all-files ``` The hooks run Ruff, Pyright, and pytest before commits, and validate commit messages with Commitizen. To run just the tests: ```sh uv run pytest -q ``` ## Caching and rate limits Successful image responses allow public caching for one day. Rendered images are also cached in memory on the server with a one-day expiration. Rate limits apply per client IP, per Gunicorn worker: | Format | Requests per minute | Uncached renders per minute | | --- | ---: | ---: | | SVG | 900 | 300 | | PNG | 900 | 30 | Exceeding a limit returns HTTP 429. Server-cached images remain available after the render limit is reached, subject to the request limit. Responses served directly from a browser or CDN cache do not reach the app or consume its quotas. Each Gunicorn worker maintains its own cache and rate-limit counters. Defaults are configured in `src/app/config.py`.