Skip to main content

Universal, lightweight HTTP API wrapper with unified response handling, structured error diagnostics, and configurable JSON decoding strategies.

Project description

ouroboros_api

A universal, lightweight HTTP API wrapper with unified response handling, structured error diagnostics, and configurable JSON decoding strategies.


Features

  • Unified response object — every call returns APIResponse, no raw exceptions leak
  • Intelligent JSON handlingexpect_json=True | False | "auto"
  • Full HTTP verb coverage — GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
  • Generic request() method — for arbitrary HTTP verbs (LOCK, PROPFIND, etc.)
  • Retry with backoffrequest_with_retry() for transient failure recovery
  • File download — streaming download_file() with chunk-based I/O
  • Health check — lightweight health_check() for endpoint availability
  • Auth helpers — Bearer, Basic, API Key header builders
  • URL builder — safe path join with slash normalization
  • Structured errorsAPIErrorDetail with UTC timestamps
  • Exception hierarchy — typed exceptions with raise_for_error() integration
  • Fully typed — PEP 561 py.typed, type aliases, NumPy-style docstrings

Installation

pip install ouroboros_api

Or install from source:

pip install -e .

With test dependencies:

pip install -e ".[test]"

Quick Start

from ouroboros_api import BaseAPI

resp = BaseAPI.make_get_request("https://api.example.com/v1/users")
if resp:  # APIResponse supports bool() — equivalent to resp.ok
    print("Users:", resp.data)
else:
    print("Error:", resp.error)

Architecture

ouroboros_api/
├── __init__.py          # Public API exports
├── base.py              # BaseAPI facade (composes AuthMixin + VerbMixin)
├── _types.py            # Type aliases (JSONMode, Headers, Cookies, etc.)
├── _response.py         # APIResponse + APIErrorDetail
├── _auth.py             # AuthMixin: Bearer, Basic, API Key, URL join
├── _client.py           # ClientMixin: request dispatcher, retry, headers
├── _verbs.py            # VerbMixin: HTTP verb methods + download + health
├── exceptions.py        # Exception hierarchy
├── py.typed             # PEP 561 typed marker
└── tests/               # Unit tests (106 tests)

API Reference — BaseAPI

All methods are @staticmethod — no instance required.

Authentication Helpers

Method Description Returns
build_bearer_token(token) Format Authorization: Bearer header value str
build_basic_auth(username, password) Base64-encode Authorization: Basic header value str
build_api_key_header(key, header_name="X-API-Key") Build a single-entry header dict for API key auth dict[str, str]
join_url(base, *parts) Safely join base URL with path segments (slash-safe) str
# Bearer token
token = BaseAPI.build_bearer_token("eyJhbGci...")
resp = BaseAPI.make_get_request(url, auth_token=token)

# Basic auth
auth = BaseAPI.build_basic_auth("admin", "secret")
resp = BaseAPI.make_get_request(url, auth_token=auth)

# API key
headers = BaseAPI.build_api_key_header("my-key-123")
resp = BaseAPI.make_get_request(url, headers_param=headers)

# URL builder
url = BaseAPI.join_url("https://api.example.com/v1", "users", "42")
# → "https://api.example.com/v1/users/42"

HTTP Verb Methods

Method Default Success Statuses Notes
make_get_request(url, ...) 200, 201, 202, 203, 204, 304 Standard GET
make_post_request(url, ...) 200, 201, 202, 204 Supports payload, data, files
make_put_request(url, ...) 200, 201, 202, 204 Full resource replacement
make_patch_request(url, ...) 200, 201, 202, 204 Partial resource update
make_delete_request(url, ...) 200, 202, 204 Resource deletion
make_head_request(url, ...) 200, 204, 301, 302, 304 Metadata only, no body
make_options_request(url, ...) 200, 204 CORS pre-flight / allowed methods

Common parameters (all verb methods):

Parameter Type Default Description
url str Fully-qualified request URL
auth_token str "" Authorization header value
headers_param Mapping[str, str] | None None Additional headers
cookies_param dict[str, str] | None None Request cookies
params dict[str, Any] | None None Query-string parameters
expect_json bool | "auto" True JSON decoding strategy
success_statuses Sequence[int] | None varies Custom success codes
timeout float | tuple[float, float] 10 Timeout in seconds
verify bool True SSL certificate verification

Body parameters (POST, PUT, PATCH only):

Parameter Type Description
payload Any JSON-serializable body (sets Content-Type: application/json)
data Any Raw or form-encoded body
files Any Multipart file-upload mapping

Precedence: When multiple body params are supplied: files > data > payload.

Generic Request

# Arbitrary HTTP verbs
resp = BaseAPI.request("LOCK", "https://dav.example.com/file.txt")

# All keyword args from verb methods are available
resp = BaseAPI.request("POST", url, payload={"key": "value"}, timeout=30)

Retry

resp = BaseAPI.request_with_retry(
    "GET",
    "https://api.example.com/v1/data",
    retries=3,              # max attempts (default: 3)
    retry_delay=1.0,        # seconds between retries (default: 1.0)
    retry_on_statuses=(408, 429, 500, 502, 503, 504),  # default
)

File Download

resp = BaseAPI.download_file(
    "https://cdn.example.com/report.pdf",
    "/tmp/report.pdf",
    auth_token=token,
    chunk_size=8192,        # bytes per chunk (default: 8192)
    timeout=60,             # default: 60
)
if resp.ok:
    print(f"Saved to: {resp.data}")  # data contains local file path

Health Check

if BaseAPI.health_check("https://api.example.com/health"):
    print("Service is up")
else:
    print("Service is down")

APIResponse

Every HTTP call returns an APIResponse dataclass.

Fields

Field Type Description
ok bool True if status is in success codes and body satisfies expect_json
status int HTTP status code (0 when request could not be sent)
data Any Parsed JSON, raw bytes, or None on error
error str | None Multi-line error report (None on success)
raw_text str Full response body as text
raw_bytes bytes Original binary payload
headers dict[str, str] Response headers
elapsed float | None Wall-clock request time in seconds

Properties & Methods

Name Returns Description
bool(resp) bool Shorthand for resp.ok
resp.is_json bool True if data is dict or list
resp.json_data dict | list | None Returns data if JSON, else None
resp.content_type str Content-Type header (empty if missing)
resp.content_length int Content-Length header (0 if missing)
resp.raise_for_error() None Raises typed exception if ok is False

JSON Decoding Strategies

expect_json Behaviour
True (default) JSON mandatory — non-JSON body is treated as an error
False Raw bytes returned in data — no JSON parsing
"auto" Try JSON first, silently fall back to raw bytes
# Strict JSON (default)
resp = BaseAPI.make_get_request(url)

# Raw binary download
resp = BaseAPI.make_get_request(url, expect_json=False)
print(type(resp.data))  # <class 'bytes'>

# Auto-detect
resp = BaseAPI.make_delete_request(url, expect_json="auto")
if resp.is_json:
    print(resp.json_data)

Error Handling

Pattern 1: Check .ok

resp = BaseAPI.make_post_request(url, payload={"key": "value"})
if not resp.ok:
    print(resp.error)  # multi-line formatted error report

Pattern 2: Raise Exceptions

from ouroboros_api import APIResponseError, APITimeoutError, APIConnectionError

try:
    resp = BaseAPI.make_post_request(url, payload={"key": "value"})
    resp.raise_for_error()
except APITimeoutError:
    print("Request timed out")
except APIConnectionError:
    print("Cannot reach server")
except APIResponseError as e:
    print(f"API error (HTTP {e.status_code}): {e}")

Exception Hierarchy

APIError
├── APIRequestError          ← network / transport failures
│   ├── APITimeoutError      ← connect or read timeout
│   └── APIConnectionError   ← DNS / TCP / TLS failures
└── APIResponseError         ← HTTP response received but invalid / unexpected

All exceptions have a status_code attribute (except APIError base).


Type Aliases

Importable from ouroboros_api for use in your own type hints:

Alias Definition Description
JSONMode bool | Literal["auto"] JSON decoding strategy
Headers Mapping[str, str] | None HTTP headers
Cookies dict[str, str] | None Cookie jar
QueryParams dict[str, Any] | None Query parameters
TimeoutType float | tuple[float, float] Timeout value
SuccessStatuses Sequence[int] | None Success status codes
from ouroboros_api import Headers, TimeoutType

def fetch(url: str, headers: Headers = None, timeout: TimeoutType = 10):
    return BaseAPI.make_get_request(url, headers_param=headers, timeout=timeout)

Timeout

# Single timeout (connect + read)
resp = BaseAPI.make_get_request(url, timeout=30)

# Tuple timeout (connect, read) — independent control
resp = BaseAPI.make_get_request(url, timeout=(5, 30))

Multipart Upload

resp = BaseAPI.make_post_request(
    "https://api.example.com/upload",
    files={"file": open("report.zip", "rb")},
)

Content-Type is automatically set by requests with the multipart boundary.


Running Tests

pip install -e ".[test]"
python -m pytest tests/ -v

License

This project is licensed under the MIT License - see the LICENSE file for details.


Authors

Flavio Brandolini

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

ouroboros_api-1.0.0.tar.gz (25.2 kB view details)

Uploaded Source

Built Distribution

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

ouroboros_api-1.0.0-py3-none-any.whl (20.4 kB view details)

Uploaded Python 3

File details

Details for the file ouroboros_api-1.0.0.tar.gz.

File metadata

  • Download URL: ouroboros_api-1.0.0.tar.gz
  • Upload date:
  • Size: 25.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for ouroboros_api-1.0.0.tar.gz
Algorithm Hash digest
SHA256 9c95f934801e89207a55819b6cf7b744c381d7d80d22d235d46cf56c7d92cf21
MD5 306e725f71311baed9c77194183969dd
BLAKE2b-256 5d2bf67898f3abe5339bf120c46492f780fd9d646d418c2da5eae47ed76d218e

See more details on using hashes here.

File details

Details for the file ouroboros_api-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: ouroboros_api-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 20.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for ouroboros_api-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 eea918b21c94b40263331afcdaee0e8abbc2664202b9667ee6a83c743ca53e24
MD5 7ba5da4c176d151e1ccee20269bcdfad
BLAKE2b-256 d604c672e90be281b381e3c7f2d6a93f65d09c1f50e509de2358091428b54c3f

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