3 KiB
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.svgreturns an SVG placeholder.GET /placeholder.pngreturns a PNG placeholder.GET /healthzreturns{"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.