Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# WorldCat API Client ID & Secret
WORLDCAT_API_CLIENT_ID=<client-id>
WORLDCAT_API_SECRET=<secret>

# WorldCat API Client Timeout (seconds)
WORLDCAT_API_TIMEOUT=10
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Changelog
All notable changes to this project will be documented in this file.

## [0.8.2] - 2026-04-24
### Added
- Support for improved HTTP request retry handling.

## [0.8.1] - 2026-04-22
### Added
- Support for fully parsing the record payloads returned from WorldCat API search calls.
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# WorldCat Client: Package Dependencies
classicist==1.0.5
configures>=0.9.2
enumerific>=1.1.0
fluently==0.9.1
requests==2.32.*
Expand Down
6 changes: 5 additions & 1 deletion source/worldcatclient/client/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from worldcatclient.configuration import secrets
from worldcatclient.logging import logger
from worldcatclient.enumerations import (
WorldCatScope,
Expand Down Expand Up @@ -50,7 +51,10 @@ def __init__(
self._secret: str = secret

if endpoint is None:
endpoint: str = WORLDCAT_API_ENDPOINT
endpoint: str = secrets.get(
"WORLDCAT_API_ENDPOINT",
default=WORLDCAT_API_ENDPOINT,
)
elif not isinstance(endpoint, str):
raise TypeError("The 'endpoint' argument must have a string value!")
elif not (endpoint.startswith("http://") or endpoint.startswith("https://")):
Expand Down
13 changes: 13 additions & 0 deletions source/worldcatclient/configuration/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from configures import Secrets

import os

secrets = Secrets(
specification=os.path.join(
os.path.dirname(__file__),
"..",
"data",
"configuration",
"required.spec",
),
)
9 changes: 9 additions & 0 deletions source/worldcatclient/data/configuration/required.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# WorldCat Client: Required Runtime Configuration Specification
# This file lists the environment variables required by the application, and defines the
# acceptable format for each variables' value. The runtime environment must provide the
# environment variables listed below, and the value of each must conform to the regexes.

# WorldCat API Client Configuration
WORLDCAT_API_ENDPOINT=(http(s)?://[a-z0-9\-]+(?:\.[a-z0-9\-]+){1,}(\:[0-9]{1,5})?(/[\w\-\_]+)*/?)
WORLDCAT_API_TOKEN=([0-9a-f]{32})
WORLDCAT_API_TIMEOUT=([0-9]+(\.[0-9]+)?)
55 changes: 55 additions & 0 deletions source/worldcatclient/session/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import requests
import json

from urllib3.util import Retry

logger = logger.getChild(__name__)


Expand Down Expand Up @@ -67,6 +69,16 @@ def __init__(
authendpoint: str = None,
scopes: list[WorldCatScope] | list[str] = None,
retries: int = 3,
retry_statuses: tuple[int] | list[int] | set[int] = (
408,
429,
500,
502,
503,
504,
522,
),
backoff_factor: float | int = 1.0,
**kwargs,
):
super().__init__(*args, **kwargs)
Expand Down Expand Up @@ -128,6 +140,49 @@ def __init__(

self._retries: int = retries

if isinstance(retry_statuses, (tuple, list, set)):
retry_statuses = list(retry_statuses)

for retry_status in retry_statuses:
if not isinstance(retry_status, int):
raise TypeError(
"Each entry in the 'retry_statuses' argument must have an integer value!"
)
elif not 400 <= retry_status <= 599:
raise ValueError(
"Each entry in the 'retry_statuses' argument must have an integer value between 400-599!"
)
else:
raise TypeError(
"The 'retry_statuses' argument must have a tuple, list or set value of integers!"
)

if isinstance(backoff_factor, (float, int)):
backoff_factor = float(backoff_factor)

if not 0.1 <= backoff_factor <= 10.0:
raise ValueError(
"The 'backoff_factor' argument must have a value between 0.1 – 10.0!"
)
else:
raise TypeError(
"The 'backoff_factor' argument must have a float or integer value!"
)

# Create and configure a HTTP adapter with the configured retry strategy
adapter = requests.adapters.HTTPAdapter(
max_retries=Retry(
total=retries, # Total number of retries per request
status_forcelist=retry_statuses, # Statuses to retry
backoff_factor=backoff_factor, # Exponential backoff (e.g., 0s, 2s, 4s...)
allowed_methods=["HEAD", "GET"], # Methods to retry requests for
),
)

# Mount the adapters into the current session
self.mount("http://", adapter)
self.mount("https://", adapter)

def __del__(self):
logger.debug("%s.__del__()", self.__class__.__name__)

Expand Down
2 changes: 1 addition & 1 deletion source/worldcatclient/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.8.1
0.8.2
Loading