Skip to main content

OPDS Client

Logo

A Python library for working with OPDS catalogs, supporting both OPDS 1.2 (Atom/XML) and OPDS 2.0 (JSON) specifications.

CI - Mainline CI - Features

Features

OPDS 2.0 (JSON)

  • ✅ Parse OPDS 2.0 feeds and publications
  • ✅ Validate against OPDS 2.0 specification
  • ✅ Navigate navigation, publications, groups, and facets collections
  • ✅ Handle pagination (first, previous, next, last links)
  • ✅ Search catalogs with URI template resolution
  • ✅ Work with acquisition links (download, buy, borrow, etc.)
  • ✅ HTTP error handling with retries
  • ✅ Pydantic models for type safety

OPDS 1.2 (Atom/XML)

  • ✅ Parse OPDS 1.2 Atom feeds and entries
  • ✅ Dublin Core metadata support
  • ✅ Navigation and Acquisition feeds
  • ✅ Facet groups with active facets and item counts
  • ✅ Acquisition links with prices and indirect acquisitions
  • ✅ OpenSearch integration
  • ✅ Multiple acquisition relations (buy, borrow, subscribe, etc.)

Installation

pip install opds-client

Quick Start

OPDS 2.0 (JSON)

from opds_client import OPDSClient

# Create a client
client = OPDSClient()

# Fetch a feed
feed = client.fetch_feed("https://example.com/opds")

# Access feed metadata
print(f"Title: {feed.metadata.title}")
print(f"Publications: {len(feed.publications or [])}")

# Navigate navigation links
for nav in feed.navigation or []:
    print(f"- {nav.title}: {nav.href}")

# Search
results = client.search(feed, "query")

OPDS 1.2 (Atom/XML)

from opds_client import OPDS12Client

# Create a client for OPDS 1.2
client = OPDS12Client()

# Fetch a feed (XML format)
feed = client.fetch_feed("https://example.com/opds.xml")

# Access feed metadata
print(f"Title: {feed.title}")
print(f"Entries: {len(feed.entries or [])}")

# Navigate navigation links
start_link = feed.get_start_link()

# Get acquisition links from entries
if feed.entries:
    entry = feed.entries[0]
    acq_links = entry.get_acquisition_links()

Using Base URL (Origin)

Both OPDS 1.2 and OPDS 2.0 clients support setting a base URL (origin) that will be automatically prepended to relative URLs. This is useful when working with catalogs that use relative paths.

OPDS 2.0 Example

from opds_client import OPDSClient

# Create client with origin
client = OPDSClient(origin="https://example.com")

# Fetch feed with relative path
feed = client.fetch_feed("/catalog")  # → https://example.com/catalog

# Absolute URLs are used as-is
feed = client.fetch_feed("https://other.com/catalog")  # → https://other.com/catalog

OPDS 1.2 Example

from opds_client import OPDS12Client

# Create client with origin
client = OPDS12Client(origin="https://api.example.com")

# Fetch feed with relative path
feed = client.fetch_feed("/opds.xml")  # → https://api.example.com/opds.xml

# Navigation links with origin
start_link = feed.get_start_link()
if start_link:
    # The client automatically prepends the origin to relative URLs
    root_feed = client.get_start_link(feed)

Authentication

Both OPDS 1.2 and OPDS 2.0 clients support HTTP Basic Authentication.

Basic Auth with Username/Password

from opds_client import OPDSClient

# For OPDS 2.0
client = OPDSClient(username="your_username", password="your_password")
feed = client.fetch_feed("https://example.com/opds")

# For OPDS 1.2
client = OPDS12Client(username="your_username", password="your_password")
feed = client.fetch_feed("https://example.com/opds.xml")

Custom Auth Objects

For advanced authentication scenarios (Digest Auth, Bearer tokens, custom auth):

from opds_client import OPDSClient
from requests.auth import HTTPDigestAuth, HTTPBasicAuth

# Digest authentication
client = OPDSClient(auth=HTTPDigestAuth("username", "password"))
feed = client.fetch_feed("https://example.com/opds")

# Custom auth (e.g., with session tokens)
class CustomAuth:
    def __call__(self, r):
        r.headers["X-Custom-Token"] = "your-token"
        return r

client = OPDSClient(auth=CustomAuth())
feed = client.fetch_feed("https://example.com/opds")

Models

OPDS 2.0 Models

from opds_client.models import Link, AcquisitionLink

# Basic link
link = Link(href="https://example.com", rel="self")

# Acquisition link
acq_link = AcquisitionLink(
    href="https://example.com/download",
    rel="download",
    type="application/epub+zip",
)

OPDS 1.2 Models

from opds_client.opds12.models import Link, AcquisitionLink

# Basic link
link = Link(
    href="https://example.com",
    rel="http://opds-spec.org/acquisition",
    type="application/epub+zip",
)

# Acquisition link with price
acq_link = AcquisitionLink(
    href="https://example.com/buy",
    rel="http://opds-spec.org/acquisition/buy",
    properties={"price": Price(value="19.99", currencycode="USD")},
)
Entry
from opds_client.opds12.models import Entry, Link, Price

entry = Entry(
    id="urn:uuid:test",
    title="Test Entry",
    updated="2010-01-10T10:00:00Z",
    authors=["Test Author"],
    links=[
        Link(
            href="https://example.com",
            rel="http://opds-spec.org/acquisition",
            type="application/epub+zip",
        ),
    ],
)
Feed
from opds_client.opds12.models import Feed, Link, Entry

feed = Feed(
    id="urn:uuid:feed",
    title="OPDS 1.2 Feed",
    updated="2010-01-10T10:00:00Z",
    links=[Link(href="https://example.com", rel="self")],
    entries=[entry],
)

Publication (OPDS 2.0)

from opds_client.models import OPDSPublication, Metadata, Link

pub = OPDSPublication(
    metadata=Metadata(
        title="Test Book",
        author="Test Author",
    ),
    links=[
        Link(href="https://example.com", rel="self"),
        Link(href="https://example.com/download", rel="download"),
    ],
)

Feed (OPDS 2.0)

from opds_client.models import OPDSFeed

# Feed with collections
feed = OPDSFeed(
    metadata=Metadata(title="My Catalog"),
    links=[Link(href="https://example.com", rel="self")],
    navigation=[...],
    publications=[...],
    facets=[...],
    groups=[...],
)

Usage Examples

OPDS 2.0 Examples

Navigation

from opds_client import OPDSClient

client = OPDSClient()
feed = client.fetch_feed("https://example.com/opds")

# Follow navigation by title
new_feed = client.follow_navigation(feed, "New Publications")

# Navigate facets
featured_feed = client.follow_facet(feed, "Category", "Featured")

OPDS 1.2 Examples

Navigation
from opds_client import OPDS12Client

client = OPDS12Client()
feed = client.fetch_feed("https://example.com/opds.xml")

# Get start link (catalog root)
start_link = feed.get_start_link()

# Get self link
self_link = feed.get_self_link()

# Get next page
next_feed = client.get_next_page(feed)

Pagination

```python
# Get next page
next_feed = client.get_next_page(feed)

# Get previous page
prev_feed = client.get_previous_page(feed)

# Get first/last page
first = client.get_first_page(feed)
last = client.get_last_page(feed)

OPDS 1.2 Pagination

from opds_client import OPDS12Client

client = OPDS12Client()
feed = client.fetch_feed("https://example.com/opds.xml")

# Get next page
next_feed = client.get_next_page(feed)

# Get previous page  
prev_feed = client.get_previous_page(feed)

# Get catalog root
root_feed = client.get_start_link(feed)
```python
# Search the catalog
results = client.search(feed, "search query", title="My Book")

# Check if search is supported
if feed.is_searchable():
    results = client.search(feed, "query")

OPDS 1.2 Acquisition

from opds_client import OPDS12Client

client = OPDS12Client()
feed = client.fetch_feed("https://example.com/opds.xml")

# Get acquisition links from entries
entry = feed.entries[0]
acq_links = entry.get_acquisition_links()

# Follow specific acquisition type
buy_link = client.follow_acquisition(entry, acquisition_type="buy")
if buy_link:
    price = buy_link.get_price()
    print(f"Price: {price.currencycode} {price.value}")

Acquisition

from opds_client.models import AcquisitionManager

```python
# Get acquisition links
pub = client.fetch_publication("https://example.com/publication")
acq_links = pub.get_acquisition_links()

# Use acquisition manager
manager = AcquisitionManager(acq_links[0])

# Check for open access
if manager.get_open_access_link():
    # Download directly
    pass

# Check for buy link
buy_link = manager.get_buy_link()
if buy_link:
    price = buy_link.get_price()
    print(f"Price: {price.currency} {price.value}")

OPDS 1.2 vs OPDS 2.0

Feature OPDS 1.2 OPDS 2.0
Format Atom/XML JSON
Parser OPDS12Parser OPDSParser
Client OPDS12Client OPDSClient
Feed Type Feed OPDSFeed
Entry Type Entry OPDSPublication
Metadata Dublin Core + Atom JSON-LD (schema.org)
Acquisition Links XML opds:price JSON properties.price

Choose the appropriate client based on your catalog's OPDS version.

Testing

# Install dev dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Run with coverage
pytest --cov=opds_client

# Run type checks
mypy opds_client

Project Structure

opds_client/
├── __init__.py          # Package exports (OPDS 1.2 & 2.0)
├── client.py            # OPDS 2.0 Client
├── parser.py            # JSON parsing/validation (OPDS 2.0)
├── exceptions.py        # Custom exceptions
├── utils.py             # Utility functions
├── models/              # OPDS 2.0 models
│   ├── __init__.py
│   ├── base.py          # Base types (Link, Metadata)
│   ├── publication.py   # Publication models
│   ├── feed.py          # Feed/Collection models
│   └── acquisition.py   # Acquisition models
└── opds12/              # OPDS 1.2 specific module
    ├── __init__.py
    ├── client.py        # OPDS 1.2 Client
    ├── parser.py        # Atom/XML parsing (OPDS 1.2)
    ├── models.py        # OPDS 1.2 models
    ├── namespace.py     # XML namespace constants
    └── exceptions.py    # OPDS 1.2 exceptions

tests/
├── conftest.py
├── models/                # OPDS 2.0 model tests (mirrors opds_client/models/)
│   ├── __init__.py
│   ├── test_base.py       # Base types (Link, Metadata)
│   ├── test_publication.py  # Publication models
│   └── test_feed.py       # Feed/Collection models
├── opds12/                # OPDS 1.2 tests (mirrors opds_client/opds12/)
│   ├── __init__.py
│   ├── test_models.py     # OPDS 1.2 models
│   ├── test_parser.py     # Atom/XML parsing
│   ├── test_client.py     # OPDS 1.2 Client
│   └── fixtures/          # OPDS 1.2 XML fixtures
│       ├── root.xml
│       ├── acquisition.xml
│       └── facets.xml
├── test_parser.py         # OPDS 2.0 parser tests
├── test_client.py         # OPDS 2.0 client tests
├── test_acquisition.py    # Acquisition tests
├── test_exceptions.py     # Exception tests
├── test_integration.py    # Integration tests
├── test_utils.py          # Utility tests
└── fixtures/              # Test fixtures (JSON & XML)
    ├── opds12/            # OPDS 1.2 XML fixtures
    │   ├── root.xml
    │   ├── acquisition.xml
    │   └── facets.xml
    ├── feed.json
    ├── publication.json
    ├── facets.json
    └── groups.json

License

MIT License - see LICENSE file for details.

References

Metadata

Release files for opds-client 0.0.2

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

Source distribution (sdist)

Source distribution for opds-client 0.0.2
File Size Uploaded Provenance Actions
opds_client-0.0.2.tar.gz 26.0 kB With provenance Details for opds_client-0.0.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for opds-client 0.0.2
File Interpreter ABI Platform Actions
opds_client-0.0.2-py3-none-any.whl Python 3 none any Details for opds_client-0.0.2-py3-none-any.whl

Total release size: 45.7 kB

Release files / opds_client-0.0.2.tar.gz

Download URL opds_client-0.0.2.tar.gz
Size 26.0 kB
Tags Source
SHA-256 checksum
How to use checksums
aaec9554176a39ac9a6fb16445afc1a87fe01ba0f033aecee5ea16d18c9eeaa6
BLAKE2b-256 checksum
How to use checksums
d8b8c7af6962742f9bfa0854edf65c1c6fba2498d8a749d0536a5a5de90501a8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 14, 2026.

Transparency log

Release files / opds_client-0.0.2-py3-none-any.whl

Download URL opds_client-0.0.2-py3-none-any.whl
Size 19.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c8f99489b4af87eb79837ca0fd6c30e68e289c16bef8d8d1d4505ae9d51619c5
BLAKE2b-256 checksum
How to use checksums
6a78508e95b899fac1514019aaeb12879f9460f3a3a88977efffc172f34ed317
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 14, 2026.

Transparency log

Security for opds-client 0.0.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for opds_client-0.0.2.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 14, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.0.2 This release

2 release files Full provenance

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