A stand-alone, dockerized FastAPI service that implements an OPDS 2.0 feed for Open Library, backed by pyopds2_openlibrary.
app/
main.py # FastAPI app + exception handlers
logger.py # Central logging factory (stdout only)
config/
__init__.py # Media types, env vars, featured subjects
exceptions/
__init__.py # OPDSException, EditionNotFound, UpstreamError
routes/
opds.py # All route handlers (/, /search, /books/*, /authors/*)
docker/
Dockerfile # Docker image definition
docker-compose.yml # Compose service definition
scripts/
configure.sh # Generates .env from defaults
Interactive docs: /docs (Swagger UI) · /redoc
- Docker + Docker Compose — for the recommended Docker workflow
- Python 3.12+ — only needed for local development without Docker
Alternatively Podman can be used. In that case, aardvark-dns is required.
git clone https://github.com/ArchiveLabs/opds.openlibrary.org.git
cd opds.openlibrary.org./scripts/configure.shThis creates a .env file with sensible defaults. Edit it to override any values before starting the service.
docker compose up --buildThe service is available at http://localhost:8080.
To run in the background:
docker compose up --build -ddocker compose downManaged via .env (generated by configure.sh). All are optional.
| Variable | Default | Description |
|---|---|---|
OPDS_BASE_URL |
(falls back to request base URL) | Public URL of this OPDS service, used in self-referencing links. When unset, the base URL is inferred from each incoming request (suitable for local dev). Set this when running behind a reverse proxy. |
OL_BASE_URL |
https://openlibrary.org |
OpenLibrary backend URL for API calls and alternate links. |
OL_USER_AGENT |
OPDSBot/1.0 (opds.openlibrary.org; opds@openlibrary.org) |
User-Agent sent with every request to OpenLibrary. |
OL_REQUEST_TIMEOUT |
30.0 |
Timeout in seconds for requests to the OpenLibrary API. |
# 1. Create and activate a virtual environment
python -m venv env
source env/bin/activate # Windows: env\Scripts\activate
# 2. Install dependencies
pip install -r requirements.txt
# 3. Start the server (cache disabled so every request hits the real fetch path)
make serve
# or: CACHE_ENABLED=false uvicorn app.main:app --host 127.0.0.1 --port 8090 --reloadNote on docker-compose:
docker-compose.ymlhard-codes the build context path, so it only works when the repo is checked out asopds.openlibrary.orgexactly. For worktrees or forks with different directory names, usemake serveinstead.
make test
# or: pytest tests/ -m "not e2e" -vAll unit tests mock network calls and run without a live service or Docker.
E2e tests hit a running instance and verify real response structure, including
behavioural invariants that protect against performance regressions — for
example, that availability facet links do not carry numberOfItems (which
would mean expensive per-mode Solr queries have been re-introduced).
# Start a local instance and run e2e tests in one command:
make test-e2e
# Or test against any running instance:
make test-e2e BASE_URL=https://opds.openlibrary.org
# Or run the service and tests separately:
make serve # terminal 1
pytest tests/test_e2e.py -m e2e -v # terminal 2E2e tests are skipped by default (pytest without -m e2e) to keep CI fast.
Run them manually before opening a PR or after deploying to any environment.
| Exception | HTTP status | Cause |
|---|---|---|
EditionNotFound |
404 | Edition OLID not found in OpenLibrary |
UpstreamError |
502 | OpenLibrary returned an error or is unreachable |
AuthorNotFound |
404 | Author OLID not found in OpenLibrary |
All errors are logged to stdout by the service logger.