placeholder-api/README.md

3 KiB
Raw Permalink Blame History

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 apk add cairo
Arch pacman -S cairo
Debian apt install libcairo2
Void 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:

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:

uv run gunicorn --bind 0.0.0.0:8000 --workers 2 --access-logfile - --error-logfile - 'app:create_app()'

Build and run the container:

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:

uv run pre-commit install

Run all checks on tracked files:

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:

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.

License

Licensed under the BSD 3-Clause License.