Skip to main content

BiblIndex Python client

Python uv Make Cross Platform CI

Maintainers

Name Email
Pierre Hennequart pierre@janalis.com

Documentation

https://www.biblindex.org/api

Quick Start

make setup
make run

Installation

Clone the repository

git clone <repo-url>
cd <repo-name>

Install Python environment

This project uses a version-managed Python setup.

pyenv install $(cat .python-version)
pyenv local $(cat .python-version)

Install dependencies

This project uses a modern Python packaging tool:

uv sync

Environment variables

cp .env.example .env.local

Edit .env.local with your configuration.

Run the project

make help      # List all available commands
make run

Or manually

uv run python src/example.py

Available commands

Run make help to see all available commands with descriptions:

make help

Platform Support

OS Support Notes
macOS ✅ Native supported
Linux ✅ Native supported
Windows ⚠️ Use WSL or Git Bash

Notes

  • Uses pyenv for Python version management
  • Uses uv for fast dependency resolution
  • Makefile orchestrates setup + run steps

For the smoothest experience:

  • macOS / Linux → native terminal
  • Windows → WSL2 (recommended)

Use as a library in another project

Released versions are published to PyPI.

With uv

uv add biblindex-client

With pip

pip install biblindex-client

Installing an unreleased commit

To use a version that hasn't been released to PyPI yet, install directly from the Git repository (optionally pinned to a tag, branch or commit):

uv add "biblindex-client @ git+https://github.com/janalis/biblindex-python-client.git@v0.1.0"

Or in pyproject.toml

[project]
dependencies = [
    "biblindex-client @ git+https://github.com/janalis/biblindex-python-client.git@v0.1.0",
]

Usage

from biblindex_client import BiblIndexClient

client = BiblIndexClient(
    baseUrl="https://www.biblindex.org",
    username="...",
    password="...",
    clientId="...",
    clientSecret="...",
)

quotations = client.request("/api/quotations", {"page": 1})

Reliability

Every HTTP call uses a 30-second timeout by default; pass timeout= to change it (a float, a (connect, read) tuple, or None to disable). Retries are off by default — pass retries=N to enable transport-level retries with backoff for GET requests on transient errors (429 and 5xx responses, connection failures). Token requests are never blindly retried; instead, a 401 API response triggers an automatic token renewal (refresh grant, falling back to the password grant) and a single replay of the request.

The client can be used as a context manager to release the underlying HTTP session:

with BiblIndexClient(
    baseUrl="https://www.biblindex.org",
    username="...",
    password="...",
    clientId="...",
    clientSecret="...",
    timeout=10.0,
    retries=3,
) as client:
    quotations = client.request("/api/quotations", {"page": 1})

Lazy fetching

API responses are automatically wrapped in lazy proxies that defer network requests until data is actually read:

  • LazyResource (Mapping, read-only): resource links (e.g. /api/extracts/42, {"@id": "/api/works/1"}) embedded in responses are wrapped as lazy mappings — the linked resource is fetched only when a field is accessed.
  • LazyCollection (Sequence, read-only): paginated Hydra collections and plain JSON arrays are wrapped as lazy sequences — subsequent pages are fetched on demand when iterating or indexing beyond the current page.

Hydra envelope properties are not exposed through the lazy wrappers. Both spellings are understood: API Platform 4 serves them bare (member, view, search, totalItems), API Platform 3 prefixes them (hydra:member, ...). Bare names are only treated as envelope metadata inside a real collection envelope, so a resource with a genuine field called member keeps it.

Caching ensures the same API resource is never fetched twice within a single response tree.

from biblindex_client import BiblIndexClient, LazyResource

client = BiblIndexClient(...)
collection = client.request("/api/quotations", {"page": 1})

# members is a LazyCollection — pages fetched lazily
item = collection[0]           # no network call yet
print(item["@id"])       # triggers fetch of /api/quotations/1229419

Using application/json (plain JSON)

The API also serves plain JSON arrays, selected with accept="application/json":

client = BiblIndexClient(..., accept="application/json")
collection = client.request("/api/quotations", {"page": 1})

Prefer the default application/ld+json: the JSON-LD envelope carries totalItems (so len() is the real total) and an explicit next link. With plain JSON there is no total, and pagination falls back to incrementing ?page=N until a page comes back empty. Note the two media types differ in what they carry per item — plain JSON has id but no @id, JSON-LD the reverse on some resources, which is why backfillIds exists.

Pagination

The server serves at most 100 items per page whatever itemsPerPage asks for, so a single large request is never enough. Asking for more raises PageSizeError rather than silently returning a short page.

Implicit paging is capped by maxAutoPages (default 50). Without a cap a single list(collection) on /api/quotations would issue several thousand requests. Walk a whole collection explicitly instead:

from biblindex_client import PaginationLimitError

books = client.fetchAll("/api/books")             # every page, explicitly
for verse in client.iterCollection("/api/verses"):  # streamed, page by page
    ...

collection = client.request("/api/books", {"page": 1})
collection.fetchAll(maxItems=500)
collection.iterPages()
collection.totalItems, collection.loadedItems, collection.isComplete

len(collection) reports the server's totalItems — the answer to "how many match" — which may exceed loadedItems. That gap is deliberate; isComplete tells you whether everything has been loaded.

Filter validation

API Platform answers 200 and silently ignores query parameters an endpoint does not declare, so an unfiltered result is indistinguishable from a filtered one. The client refuses them instead, before the request goes out:

from biblindex_client import UndeclaredParameterError

client.filtersFor("/api/quotations")   # frozenset({'work'})
client.filtersFor("/api/verses")       # bible, book, chapter, number (+ [] forms)

client.request("/api/quotations", {"author": 42})
# UndeclaredParameterError: /api/quotations does not declare 'author'. API
# Platform ignores undeclared query parameters and still answers 200, so the
# result would look filtered but would not be. Declared filters: work.

The vocabulary comes from /api/docs.jsonopenapi, fetched once per client and only when a call actually carries a filter — the plain {"page": N} browse path costs nothing extra. The search block of each collection response reinforces it for free. Pass validateParams=False (per client or per call) to opt out.

filtersFor() raises FilterVocabularyUnavailableError rather than reporting "no filters" for an endpoint it could not read — an empty answer would be indistinguishable from a fact about the API.

Errors

exception when
AccessDeniedError 403 — names ROLE_API_CLIENT when the corpus is the target
AuthenticationError 401, or the token endpoint rejecting the grant
BiblIndexHTTPError any other failing status
UndeclaredParameterError a filter the endpoint would ignore
PageSizeError itemsPerPage above the server's cap
PaginationLimitError implicit paging past maxAutoPages
FilterVocabularyUnavailableError declared filters could not be determined

All of them derive from BiblIndexError, and the HTTP ones also derive from requests.HTTPError, so existing except requests.HTTPError handlers keep working. Error bodies are read, so the server's own explanation is preserved.

Read-only

LazyResource and LazyCollection are Mapping and Sequence. The API is not writable through this client, so an assignment that could never reach the server is refused rather than kept in memory looking like a saved change. Use dict(resource) or list(collection) for a mutable copy.

Known API limitations

Verified against the live API on 02/09/2026:

behaviour consequence
itemsPerPage capped at 100 always page; fetchAll() does it for you
?locale= accepted and ignored name/description are English regardless; refused by the client
id absent in ld+json on authors, work_editions, editions backfilled from the @id IRI (backfillIds=False to opt out)
/api/quotations declares only work no filter by biblical segment or author
corpus endpoints need ROLE_API_CLIENT 403 until the account is granted it

Publishing a new version

make bump-patch   # or bump-minor / bump-major

This bumps the version in pyproject.toml, commits, tags (vX.Y.Z), and pushes to GitHub. The Release workflow then builds the distribution, publishes it to TestPyPI and then PyPI, and creates a GitHub Release with auto-generated release notes.

Publishing uses PyPI trusted publishing (OIDC) via uv publish — no API tokens or credentials are stored or needed locally. The TestPyPI upload acts as a smoke test that gates the real PyPI release.

Contributing

This project is open to contributions.

We welcome pull requests following the standard GitHub flow:

  1. Fork the repository
  2. Create a feature branch
  3. Commit your changes
  4. Open a pull request

Please ensure your changes are well tested and follow the existing code style.

API Modifications

If you need changes, extensions, or adjustments to the API, please contact the maintainers.

Metadata

Release files for biblindex-client 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for biblindex-client 0.3.0
File Size Uploaded
biblindex_client-0.3.0.tar.gz 19.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for biblindex-client 0.3.0
File Interpreter ABI Platform
biblindex_client-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 41.5 kB

Release files / biblindex_client-0.3.0.tar.gz

Download URL biblindex_client-0.3.0.tar.gz
Size 19.5 kB
Tags Source
SHA-256 checksum
How to use checksums
5dd472d183bb1e2a197f986e3ef65d5d216c1fdbdcf31e332348eef40a594057
BLAKE2b-256 checksum
How to use checksums
b44b54c5f588eb37f1ca25242d92481bbb045bd1ba8faff5dda2d3990a424b6e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / biblindex_client-0.3.0-py3-none-any.whl

Download URL biblindex_client-0.3.0-py3-none-any.whl
Size 22.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3795f0780c1e94a907c78dbf6792662dc2e2aeb1318553e06adf8fe4bdf18bd2
BLAKE2b-256 checksum
How to use checksums
f6d9a51a73f531060748271ad2b08632c67b99524200472c630ca2bd96317471
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

0.4.0

2 release files

This release

0.3.0 This release

2 release files

0.2.3

2 release files

0.2.2

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page