
Security News
AI Agent Submits PR to Matplotlib, Publishes Angry Blog Post After Rejection
After Matplotlib rejected an AI-written PR, the agent fired back with a blog post, igniting debate over AI contributions and maintainer burden.
openapi-client-core
Advanced tools
Shared runtime library for Python OpenAPI clients with transport layers, retry logic, auth patterns, and testing utilities
Shared runtime library for Python OpenAPI clients
openapi-client-core provides battle-tested patterns for building Python OpenAPI clients. Instead of duplicating retry
logic, error handling, and testing utilities across every client, share a common foundation.
Features:
pip install openapi-client-core
Or with UV:
uv add openapi-client-core
from openapi_client_core.transport import create_transport_stack
from openapi_client_core.auth import CredentialResolver
from your_generated_client import Client
# Resolve credentials from multiple sources
resolver = CredentialResolver()
api_key = resolver.resolve(
param_value=None, # Will check env vars and .env file
env_var_name="MY_API_KEY",
netrc_host="api.example.com",
)
# Create transport stack with retry, error logging, pagination
transport = create_transport_stack(
base_url="https://api.example.com",
retry_strategy="rate_limited", # or "idempotent_only" or "all_methods"
enable_pagination=True,
enable_error_logging=True,
enable_null_field_detection=True,
)
# Initialize your generated client with enhanced transport
client = Client(
base_url="https://api.example.com",
token=api_key,
transport=transport,
)
from openapi_client_core.utils import unwrap
from openapi_client_core.exceptions import NotFoundError, ValidationError
# Raises specific exceptions on error
try:
data = unwrap(client.get_resource(id=123))
print(f"Got resource: {data}")
except NotFoundError:
print("Resource not found")
except ValidationError as e:
print(f"Validation failed: {e.problem_details}")
# Or return None on error
data = unwrap(client.get_resource(id=123), raise_on_error=False)
if data is None:
print("Request failed")
import pytest
from openapi_client_core.testing import (
mock_api_credentials,
create_mock_response,
create_error_response,
)
def test_client_handles_404(mock_api_credentials):
"""Test client gracefully handles 404 errors."""
# Fixtures provided by openapi-client-core
client = MyClient(**mock_api_credentials)
# Mock a 404 response
response = create_error_response("404")
# Test your error handling
with pytest.raises(NotFoundError):
unwrap(response)
Every OpenAPI client duplicates the same patterns:
# In client A
class RateLimitAwareRetry(Retry):
# 60 lines of retry logic...
# In client B
class RateLimitAwareRetry(Retry):
# Same 60 lines duplicated...
# In client C
class RateLimitAwareRetry(Retry):
# Same 60 lines again...
Problems:
Share the battle-tested core:
from openapi_client_core.transport import create_transport_stack
transport = create_transport_stack(
base_url=base_url,
retry_strategy="rate_limited",
enable_pagination=True,
)
Benefits:
Composable async HTTP transport middleware:
from openapi_client_core.transport import (
ErrorLoggingTransport,
RetryTransport,
PaginationTransport,
CustomHeaderAuthTransport,
)
# Stack them in any order
transport = CustomHeaderAuthTransport(
headers_dict={"api-key": "..."},
wrapped_transport=RetryTransport(
retry_strategy="idempotent_only",
wrapped_transport=ErrorLoggingTransport(
enable_null_field_detection=True,
wrapped_transport=httpx.AsyncHTTPTransport(),
),
),
)
# Or use the factory for common patterns
from openapi_client_core.transport import create_transport_stack
transport = create_transport_stack(
base_url="https://api.example.com",
retry_strategy="rate_limited",
custom_auth_headers={"api-key": "..."},
)
Three retry strategies for different API behaviors:
IdempotentOnlyRetry (safest)
RateLimitAwareRetry (recommended)
AllMethodsRetry (use with caution)
Multi-source credential resolution:
from openapi_client_core.auth import CredentialResolver
resolver = CredentialResolver()
# Checks in order: param → env var → .env file → ~/.netrc
api_key = resolver.resolve(
param_value=None,
env_var_name="API_KEY",
netrc_host="api.example.com",
)
Custom header authentication:
from openapi_client_core.transport import CustomHeaderAuthTransport
transport = CustomHeaderAuthTransport(
headers_dict={
"api-auth-id": tenant_id,
"api-auth-signature": tenant_name,
},
wrapped_transport=base_transport,
)
Structured exceptions with RFC 7807 ProblemDetails:
from openapi_client_core.errors import (
APIError,
UnauthorizedError, # 401
ForbiddenError, # 403
NotFoundError, # 404
ValidationError, # 422
BadRequestError, # 400
ConflictError, # 409
RateLimitError, # 429
ServerError, # 5xx
raise_for_status,
detect_null_fields,
)
import httpx
# Automatic RFC 7807 parsing
response = httpx.get("https://api.example.com/users/123")
try:
raise_for_status(response)
except NotFoundError as e:
# Access structured error details
print(e.status_code) # 404
print(e.problem_detail.title) # "Resource Not Found"
print(e.problem_detail.detail) # "User with id 123 does not exist"
print(e.problem_detail.type) # "https://api.example.com/problems/not-found"
# ValidationError with structured errors
try:
raise_for_status(response)
except ValidationError as e:
print(e.validation_errors) # [{"field": "email", "message": "Invalid"}]
# RateLimitError with retry timing
try:
raise_for_status(response)
except RateLimitError as e:
print(f"Rate limited. Retry after {e.retry_after} seconds")
# Detect null fields in response data
data = {"user": {"name": "John", "email": None}}
null_fields = detect_null_fields(data)
print(null_fields) # ["user.email"]
Null field detection:
from openapi_client_core.errors import detect_null_fields, NullFieldError
# Detect null fields in API response
data = {
"user": {
"name": "John",
"email": None,
"address": {
"city": None,
"street": "123 Main St"
}
}
}
null_paths = detect_null_fields(data)
print(null_paths) # ["user.email", "user.address.city"]
# Raise helpful error for null fields
if null_paths:
raise NullFieldError(
message=f"Found {len(null_paths)} null field(s): {null_paths}",
field_path=null_paths[0]
)
Pre-built fixtures and factories:
import pytest
from openapi_client_core.testing import (
mock_api_credentials,
create_mock_response,
create_error_response,
create_paginated_mock_handler,
)
@pytest.fixture
def my_client(mock_api_credentials):
return MyClient(**mock_api_credentials)
def test_pagination(my_client):
handler = create_paginated_mock_handler([
[{"id": 1}, {"id": 2}], # Page 1
[{"id": 3}, {"id": 4}], # Page 2
])
# Test with mock handler...
Full documentation available at: https://dougborg.github.io/openapi-client-core
# Clone repository
git clone https://github.com/dougborg/openapi-client-core.git
cd openapi-client-core
# Install UV (if not already installed)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install dependencies
uv sync --all-extras
# Run all tests
uv run pytest
# Run with coverage
uv run pytest --cov=openapi_client_core --cov-report=term-missing
# Run only unit tests
uv run pytest -m unit
# Run specific test file
uv run pytest tests/unit/test_transport/test_retry.py
# Format code
uv run ruff format .
# Lint code
uv run ruff check .
# Type check
uv run ty check
# Build docs locally
uv run mkdocs build
# Serve docs locally
uv run mkdocs serve
# Open http://127.0.0.1:8000
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
MIT License - see LICENSE for details.
See CHANGELOG.md for version history.
This library extracts battle-tested patterns from:
Special thanks to the OpenAPI and httpx communities for providing excellent foundations.
FAQs
Shared runtime library for Python OpenAPI clients with transport layers, retry logic, auth patterns, and testing utilities
We found that openapi-client-core demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
After Matplotlib rejected an AI-written PR, the agent fired back with a blog post, igniting debate over AI contributions and maintainer burden.

Security News
HashiCorp disclosed a high-severity RCE in next-mdx-remote affecting versions 4.3.0 to 5.x when compiling untrusted MDX on the server.

Security News
Security researchers report widespread abuse of OpenClaw skills to deliver info-stealing malware, exposing a new supply chain risk as agent ecosystems scale.