Skip to main content

Async Python client for the ISBNdb REST API v2 with caching and rate limiting.

Project description

isbnator

Async Python client for the ISBNdb REST API v2 with built-in caching (file / DynamoDB / none) and rate limiting.

Installation

pip install -r requirements.txt

Dependencies: httpx, pydantic, dynamorator, logorator, python-dotenv

API Key

Resolved in order:

  1. api_key constructor argument
  2. ISBNDB_TOKEN environment variable
  3. Raises ISBNdbConfigError if neither is set

Client Initialization

from isbnator import ISBNdbClient

client = ISBNdbClient(
    api_key="<key>",               # or set ISBNDB_TOKEN env var
    cache="file",                  # "file" | "dynamodb" | "none" (default: "none")
    cache_dir=".isbndb_cache",     # directory for file cache (default: ".isbndb_cache")
    dynamo_table="isbndb-cache",   # DynamoDB table name (required if cache="dynamodb")
    cache_ttl_days=90,             # cache TTL in days (default: 90)
    max_concurrent=5,              # max parallel requests in batch (default: 5)
    max_retries=3,                 # max retries per request (default: 3)
    requests_per_second=1,         # per-second throttle (default: 1)
    request_timeout=30,            # request timeout in seconds (default: 30)
    rate_limit_warn_threshold=100, # warn when remaining quota drops below this (default: 100)
)

# Use as async context manager
async with ISBNdbClient(cache="file") as client:
    ...

Models

SearchQuery (input for batch_search)

class SearchQuery(BaseModel):
    title: str                   # required — search text
    author: str | None = None    # optional — filter by author

Book (parsed from ISBNdb responses)

class Book(BaseModel):
    title: str | None = None
    title_long: str | None = None
    isbn: str | None = None           # ISBN-10 or ISBN-13 (ISBNdb is inconsistent)
    isbn13: str | None = None         # ISBN-13
    authors: list[str] = []
    publisher: str | None = None
    date_published: str | None = None # format varies: "2015-05-12", "2015", "May 2015"
    binding: str | None = None        # e.g. "Paperback", "Hardcover", "Audio CD"
    pages: int | None = None
    dimensions: str | None = None
    image: str | None = None          # cover image URL
    language: str | None = None       # e.g. "en", "es"
    edition: str | None = None
    subjects: list[str] = []
    synopsis: str | None = None
    msrp: float | None = None

All fields are optional. Unknown fields from ISBNdb are silently ignored (extra="ignore").

QueryResult (returned by all query methods)

class QueryResult(BaseModel):
    status: str              # "success" | "not_found" | "error"
    books: list[Book] = []   # parsed Book models
    from_cache: bool = False # True if result was served from cache
    error: str | None = None # error message when status == "error"
    data: dict | None = None # raw JSON response from ISBNdb (None when from cache)

RateLimit (returned by quota())

class RateLimit(BaseModel):
    daily_limit: int = 5000
    used: int = 0
    remaining: int = 5000

Methods

All methods are async and return QueryResult unless noted.

book(isbn: str) -> QueryResult

Lookup a single book by ISBN-10 or ISBN-13.

result = await client.book("9780143127550")
# result.status == "success"
# result.books == [Book(title="Everything I Never Told You - A Novel", ...)]

books(query, column=None, page=1, page_size=20) -> QueryResult

Full-text search. column can be "title", "author", "date_published", etc.

result = await client.books("Gatsby", column="title")
# result.books == [Book(...), Book(...), ...]  (up to page_size)

author(name: str) -> QueryResult

Search by author name.

result = await client.author("F. Scott Fitzgerald")

publisher(name: str) -> QueryResult

Search by publisher name.

result = await client.publisher("Penguin")

search(**kwargs) -> QueryResult

Multi-field search via /search/books. Supported kwargs: text, author, isbn, isbn13, subject, publisher. None values are excluded.

result = await client.search(text="Great Gatsby", author="Fitzgerald")

batch_search(queries: list[SearchQuery]) -> list[QueryResult]

Primary use case. Sends multiple queries with controlled concurrency. Returns results in the same order as input.

from isbnator import SearchQuery

queries = [
    SearchQuery(title="The Great Gatsby", author="F. Scott Fitzgerald"),
    SearchQuery(title="1984", author="George Orwell"),
    SearchQuery(title="xyznonexistent"),
]

results = await client.batch_search(queries)
# len(results) == len(queries), same order

for query, result in zip(queries, results):
    print(query.title, result.status, len(result.books), result.from_cache)
    # "The Great Gatsby" "success" 20 False
    # "1984"             "success" 20 False
    # "xyznonexistent"   "not_found" 0 False

Behavior:

  • Checks cache first (batch), only queries ISBNdb for misses
  • Runs misses with max_concurrent parallelism, throttled by requests_per_second
  • Caches both successful and not_found results (negative caching)
  • Failed queries return QueryResult(status="error", error="...") — never raises

quota() -> RateLimit

Fetches API usage stats from /stats.

q = await client.quota()
# q.daily_limit == 5000, q.used == 42, q.remaining == 4958

Also accessible as client.rate_limit property (updated after calling quota()).

close()

Closes the underlying HTTP client. Called automatically when using async with.

Caching

Three cache backends, selected via cache constructor param.

Backend cache= Notes
None "none" Every call hits ISBNdb (default)
File "file" One JSON file per query in cache_dir
DynamoDB "dynamodb" Via dynamorator.DynamoDBStore, requires dynamo_table

Cache keys are SHA-256 hashes of the endpoint path + sorted query params. Both successful and not_found results are cached with the configured TTL. File cache checks expiry on read and deletes stale entries.

Exceptions

ISBNdbError              # base exception
├── ISBNdbConfigError    # missing API key or bad config
├── ISBNdbRateLimitError # daily quota exhausted (remaining == 0)
└── ISBNdbAPIError       # non-retryable API error
    .status_code: int
    .message: str

Exceptions are raised for config issues and quota exhaustion. In batch_search, individual query failures are captured in QueryResult(status="error"), not raised.

Retry Logic

Retries on: HTTP 429, 5xx, connection errors, timeouts.

  • Exponential backoff with jitter (1s, 2s, 4s base)
  • Respects Retry-After header on 429
  • After max_retries exhausted: raises ISBNdbAPIError (single queries) or returns error QueryResult (batch)

Imports

from isbnator import (
    ISBNdbClient,
    SearchQuery,
    QueryResult,
    Book,
    BooksResponse,
    RateLimit,
    ISBNdbError,
    ISBNdbConfigError,
    ISBNdbRateLimitError,
    ISBNdbAPIError,
)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

isbnator-0.0.2.tar.gz (12.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

isbnator-0.0.2-py3-none-any.whl (12.0 kB view details)

Uploaded Python 3

File details

Details for the file isbnator-0.0.2.tar.gz.

File metadata

  • Download URL: isbnator-0.0.2.tar.gz
  • Upload date:
  • Size: 12.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for isbnator-0.0.2.tar.gz
Algorithm Hash digest
SHA256 a0e408114a5b3307bc5d30a7c4b288ee5c1368241f56e47a339a33a86a3a7eaf
MD5 2650ccdea86258a0f6522307b9565325
BLAKE2b-256 255131c61564cc78acc088e47907138d396c4bbb1fdaf891eb47548185141f92

See more details on using hashes here.

File details

Details for the file isbnator-0.0.2-py3-none-any.whl.

File metadata

  • Download URL: isbnator-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 12.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.12

File hashes

Hashes for isbnator-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 09caac84b40aacf895048799453d8f05a9d12cde523ba6220c4ffaaa8f4a6407
MD5 b2776dea083ed4940383420f64f8f2f6
BLAKE2b-256 974ace63587d14d0fdf501bfc34281efd04da72c5460eddb395dc4d62094dde1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page