Files
openapi-first/openapi_first/loader.py
Vishesh 'ironeagle' Bangotra a74e3d0d01 Introduce an OpenAPI-first HTTP client driven by the same specification
used for server-side route binding, and refactor documentation and
templates to treat the client as a first-class contract consumer.

Key changes:
- Add OpenAPI-first client module based on httpx
- Document client usage alongside server-side binder usage
- Update mkdocs navigation to include client documentation
- Refactor CRUD and model app templates to call APIs via operationId
  instead of hardcoded paths
- Align package documentation and public API surface with client support
- Clarify server/client dependency split (fastapi vs httpx)

This establishes strict symmetry between OpenAPI-driven server binding
and OpenAPI-driven client invocation, reinforcing OpenAPI as the single
source of truth on both sides of the contract.
2026-01-11 18:41:27 +05:30

108 lines
2.9 KiB
Python

"""
openapi_first.loaders
=============================
OpenAPI specification loading and validation utilities.
This module is responsible for loading an OpenAPI 3.x specification
from disk and validating it before it is used by the application.
It enforces the principle that an invalid or malformed OpenAPI document
must never reach the routing or runtime layers.
Design principles
-----------------
- OpenAPI is treated as an authoritative contract.
- Invalid specifications fail fast at application startup.
- Supported formats are JSON and YAML.
- Validation errors are surfaced clearly and early.
This module intentionally does NOT:
-----------------------------------
- Modify the OpenAPI document
- Infer missing fields
- Generate models or code
- Perform request/response validation at runtime
"""
import json
from pathlib import Path
from typing import Any
import yaml
from openapi_spec_validator import validate_spec
from .errors import OpenAPIFirstError
class OpenAPISpecLoadError(OpenAPIFirstError):
"""
Raised when an OpenAPI specification cannot be loaded or validated.
This error indicates that the OpenAPI document is unreadable,
malformed, or violates the OpenAPI 3.x specification.
"""
pass
def load_openapi(path: str | Path) -> dict[str, Any]:
"""
Load and validate an OpenAPI 3.x specification from disk.
The specification is parsed based on file extension and validated
using a strict OpenAPI schema validator. Any error results in an
immediate exception, preventing application startup.
Parameters
----------
path : str or pathlib.Path
Filesystem path to an OpenAPI specification file.
Supported extensions:
- `.json`
- `.yaml`
- `.yml`
Returns
-------
dict
Parsed and validated OpenAPI specification.
Raises
------
OpenAPISpecLoadError
If the file does not exist, cannot be parsed, or fails
OpenAPI schema validation.
"""
spec_path = Path(path)
if not spec_path.exists():
raise OpenAPISpecLoadError(f"OpenAPI spec not found: {spec_path}")
try:
if spec_path.suffix == ".json":
with spec_path.open("r", encoding="utf-8") as f:
spec = json.load(f)
elif spec_path.suffix in {".yaml", ".yml"}:
with spec_path.open("r", encoding="utf-8") as f:
spec = yaml.safe_load(f)
else:
raise OpenAPISpecLoadError(
f"Unsupported OpenAPI file format: {spec_path.suffix}"
)
except Exception as exc:
raise OpenAPISpecLoadError(
f"Failed to parse OpenAPI spec: {spec_path}"
) from exc
try:
validate_spec(spec)
except Exception as exc:
raise OpenAPISpecLoadError(
f"OpenAPI spec validation failed: {spec_path}"
) from exc
return spec