11 Commits
0.0.1 ... 0.0.4

Author SHA1 Message Date
29c1579f40 v0.0.4 — Documents and packages OpenAPI-first application templates, adds multi-template CLI scaffolding, and enforces package-bound documentation generation.
All checks were successful
continuous-integration/drone/tag Build is passing
2026-01-11 21:44:11 +05:30
a3c063b569 docs(templates): document CRUD and model CRUD apps and expose them in mkdocs
- Add comprehensive module and function docstrings to crud_app and model_app templates
- Document OpenAPI-first guarantees, non-goals, and usage patterns in templates
- Add template-level __init__.py files with CLI and client usage examples
- Update mkdocs.yml to include CRUD and model-based CRUD template documentation
- Ensure template documentation follows strict package-bound mkdocstrings rules
2026-01-11 21:42:30 +05:30
72b5be6976 docs, cli: enforce package-bound docs, add template scaffolding, and document CLI usage
- Restrict mkdocstrings generation to real Python packages (require __init__.py)
- Add explicit documentation section for CLI scaffolding and templates
- Generalize CLI to support multiple templates with dynamic discovery
- Package templates correctly for importlib.resources access
- Add fully documented health_app template (app entry point and handlers)
- Fix setuptools package-data configuration for bundled templates

These changes make documentation import-safe, clarify package boundaries,
and provide a deterministic, OpenAPI-first scaffolding workflow via CLI.
2026-01-11 19:26:21 +05:30
6180443327 v0.0.3 — Introduces an OpenAPI-first HTTP client, updates documentation and templates to use operationId-based calls, and adds httpx as a client-side dependency.
All checks were successful
continuous-integration/drone/tag Build is passing
2026-01-11 18:43:04 +05:30
1b26021725 added explicitly httpx as it's being used in client.py 2026-01-11 18:42:13 +05:30
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
31bf1b1b6b cleanup requirements.txt 2026-01-11 17:12:03 +05:30
7b4583f305 feat(templates): add CRUD and model CRUD app scaffolds; bump version to 0.0.2
All checks were successful
continuous-integration/drone/tag Build is passing
- add OpenAPI-first CRUD app template with in-memory mock data
- add model-based CRUD app template with Pydantic layer
- include end-to-end tests for both templates
- document explicit runtime status handling in routes
- add Pydantic dependency for model_app support
2026-01-10 18:07:01 +05:30
fc8346fcda added pydantic with pin 2026-01-10 18:05:51 +05:30
40d91bc52b feat(scaffold): add model-backed CRUD service template
Provides a complete OpenAPI-first CRUD example with a Pydantic
model layer, explicit runtime semantics, and integration tests
to support developer onboarding and real-world service structure.
2026-01-10 18:05:39 +05:30
2ac342240b feat(templates): add OpenAPI-first CRUD app scaffold with mock data
- include CRUD OpenAPI spec
- add in-memory mock data store
- implement OpenAPI-bound route handlers
- provide runnable FastAPI bootstrap
- include end-to-end integration test
2026-01-10 17:59:11 +05:30
41 changed files with 1935 additions and 85 deletions

View File

@@ -0,0 +1,3 @@
# Client
::: openapi_first.client

View File

@@ -0,0 +1,3 @@
# Data
::: openapi_first.templates.crud_app.data

View File

@@ -0,0 +1,3 @@
# Crud App
::: openapi_first.templates.crud_app

View File

@@ -0,0 +1,3 @@
# Main
::: openapi_first.templates.crud_app.main

View File

@@ -0,0 +1,3 @@
# Routes
::: openapi_first.templates.crud_app.routes

View File

@@ -0,0 +1,3 @@
# Test Crud App
::: openapi_first.templates.crud_app.test_crud_app

View File

@@ -0,0 +1,3 @@
# Health App
::: openapi_first.templates.health_app

View File

@@ -0,0 +1,3 @@
# Main
::: openapi_first.templates.health_app.main

View File

@@ -0,0 +1,3 @@
# Routes
::: openapi_first.templates.health_app.routes

View File

@@ -0,0 +1,3 @@
# Templates
::: openapi_first.templates

View File

@@ -0,0 +1,3 @@
# Data
::: openapi_first.templates.model_app.data

View File

@@ -0,0 +1,3 @@
# Model App
::: openapi_first.templates.model_app

View File

@@ -0,0 +1,3 @@
# Main
::: openapi_first.templates.model_app.main

View File

@@ -0,0 +1,3 @@
# Models
::: openapi_first.templates.model_app.models

View File

@@ -0,0 +1,3 @@
# Routes
::: openapi_first.templates.model_app.routes

View File

@@ -0,0 +1,3 @@
# Test Model App
::: openapi_first.templates.model_app.test_model_app

View File

@@ -68,28 +68,42 @@ def generate_docs_from_nav(
docs_root.mkdir(parents=True, exist_ok=True)
for py_file in package_dir.rglob("*.py"):
rel = py_file.relative_to(project_root)
# Collect all package directories (those containing __init__.py)
package_dirs: set[Path] = {
p.parent
for p in package_dir.rglob("__init__.py")
}
if py_file.name == "__init__.py":
# Package → index.md
module_path = ".".join(rel.parent.parts)
md_path = docs_root / rel.parent / "index.md"
title = rel.parent.name.replace("_", " ").title()
else:
# Regular module → <module>.md
module_path = ".".join(rel.with_suffix("").parts)
md_path = docs_root / rel.with_suffix(".md")
title = md_path.stem.replace("_", " ").title()
for pkg_dir in sorted(package_dirs):
rel_pkg = pkg_dir.relative_to(project_root)
module_base = ".".join(rel_pkg.parts)
md_path.parent.mkdir(parents=True, exist_ok=True)
# index.md for the package itself
index_md = docs_root / rel_pkg / "index.md"
index_md.parent.mkdir(parents=True, exist_ok=True)
content = f"""# {title}
title = pkg_dir.name.replace("_", " ").title()
index_md.write_text(
f"# {title}\n\n::: {module_base}\n",
encoding="utf-8",
)
::: {module_path}
"""
# Document modules inside this package only
for py_file in pkg_dir.iterdir():
if (
py_file.suffix != ".py"
or py_file.name == "__init__.py"
):
continue
md_path.write_text(content, encoding="utf-8")
module_path = f"{module_base}.{py_file.stem}"
md_path = docs_root / rel_pkg / f"{py_file.stem}.md"
title = py_file.stem.replace("_", " ").title()
md_path.write_text(
f"# {title}\n\n::: {module_path}\n",
encoding="utf-8",
)
def load_mkdocs_config():

View File

@@ -42,9 +42,16 @@ nav:
- OpenAPI-First App: openapi_first/app.md
- Route Binder: openapi_first/binder.md
- Spec Loaders: openapi_first/loader.md
- Client: openapi_first/client.md
- CLI:
- Home: openapi_first/cli.md
- Templates:
- Home: openapi_first/templates/index.md
- Health App: openapi_first/templates/health_app/index.md
- CRUD App: openapi_first/templates/crud_app/index.md
- Model App: openapi_first/templates/model_app/index.md
- Errors:
- Error Hierarchy: openapi_first/errors.md

View File

@@ -17,11 +17,12 @@ convenience facades.
Architecture Overview
----------------------------------------------------------------------
The library is structured around three core responsibilities:
The library is structured around four core responsibilities:
- loader: load and validate OpenAPI 3.x specifications (JSON/YAML)
- binder: bind OpenAPI operations to FastAPI routes via operationId
- app: OpenAPI-first FastAPI application bootstrap
- client: OpenAPI-first HTTP client driven by the same specification
- errors: explicit error hierarchy for contract violations
The package root acts as a **namespace**, not a facade. Consumers are
@@ -40,7 +41,8 @@ Or with Poetry:
poetry add openapi-first
Runtime dependencies are intentionally minimal:
- fastapi
- fastapi (server-side)
- httpx (client-side)
- openapi-spec-validator
- pyyaml (optional, for YAML specs)
@@ -48,7 +50,33 @@ The ASGI server (e.g., uvicorn) is an application-level dependency and is
not bundled with this library.
----------------------------------------------------------------------
Basic Usage
Command-Line Interface (Scaffolding, Templates)
----------------------------------------------------------------------
FastAPI OpenAPI First ships with a small CLI for bootstrapping
OpenAPI-first FastAPI applications from bundled templates.
List available application templates:
openapi-first --list
Create a new application using the default template:
openapi-first
Create a new application using a specific template:
openapi-first health_app
Create a new application in a custom directory:
openapi-first health_app my-service
The CLI copies template files verbatim into the target directory.
No code is generated or modified beyond the copied scaffold.
----------------------------------------------------------------------
Server-Side Usage (OpenAPI → FastAPI)
----------------------------------------------------------------------
Minimal OpenAPI-first FastAPI application:
@@ -57,7 +85,7 @@ Minimal OpenAPI-first FastAPI application:
import my_service.routes as routes
api = app.OpenAPIFirstApp(
openapi_path="openapi.json",
openapi_path="openapi.yaml",
routes_module=routes,
title="My Service",
version="1.0.0",
@@ -81,6 +109,56 @@ OpenAPI snippet:
"200":
description: OK
The binder guarantees:
- Every OpenAPI operationId has exactly one handler
- No undocumented routes exist
- All mismatches fail at application startup
----------------------------------------------------------------------
Client-Side Usage (OpenAPI → HTTP Client)
----------------------------------------------------------------------
The same OpenAPI specification can be used to construct a strict,
operationId-driven HTTP client.
Client construction:
from openapi_first.loader import load_openapi
from openapi_first.client import OpenAPIClient
spec = load_openapi("openapi.yaml")
client = OpenAPIClient(spec)
Calling operations (operationId is the API):
response = client.get_health()
assert response.status_code == 200
assert response.json() == {"status": "ok"}
Path parameters must match the OpenAPI specification exactly:
response = client.get_item(
path_params={"item_id": 1}
)
Request bodies are passed explicitly:
response = client.create_item(
body={"name": "Orange", "price": 0.8}
)
Client guarantees:
- One callable per OpenAPI operationId
- No hardcoded URLs or HTTP methods in user code
- Path and body parameters must match the spec exactly
- Invalid or incomplete OpenAPI specs fail at client construction time
- No schema inference or mutation is performed
The client is transport-level only and returns `httpx.Response`
objects directly. Response interpretation and validation are left to
the consumer or higher-level layers.
----------------------------------------------------------------------
Extensibility Model
----------------------------------------------------------------------
@@ -107,6 +185,7 @@ The supported public API consists of the following top-level modules:
- openapi_first.app
- openapi_first.binder
- openapi_first.loader
- openapi_first.client
- openapi_first.errors
Classes and functions should be imported explicitly from these modules.
@@ -118,8 +197,8 @@ Design Guarantees
- OpenAPI is the single source of truth
- No undocumented routes can exist
- No OpenAPI operation can exist without a handler
- All contract violations fail at application startup
- No OpenAPI operation can exist without a handler or client callable
- All contract violations fail at application startup or client creation
- No hidden FastAPI magic or implicit behavior
- Deterministic, testable application assembly
- CI-friendly failure modes
@@ -131,11 +210,13 @@ enforcement over convenience shortcuts.
from . import app
from . import binder
from . import loader
from . import client
from . import errors
__all__ = [
"app",
"binder",
"loader",
"client",
"errors",
]

View File

@@ -4,16 +4,8 @@ openapi_first.cli
Command-line interface for FastAPI OpenAPI-first scaffolding utilities.
This module provides a small, focused CLI intended to help developers
quickly bootstrap OpenAPI-first FastAPI services using bundled project
templates.
Currently supported scaffolds:
- Health check service (minimal OpenAPI-first application)
The CLI copies versioned templates packaged with the library into a
user-specified directory, allowing rapid local development without
manual setup.
This CLI bootstraps OpenAPI-first FastAPI applications from versioned,
bundled templates packaged with the library.
"""
import argparse
@@ -22,68 +14,75 @@ from pathlib import Path
from importlib import resources
def copy_health_app_template(target_dir: Path) -> None:
DEFAULT_TEMPLATE = "health_app"
def available_templates() -> list[str]:
"""
Copy the bundled OpenAPI-first health app template into a directory.
Return a list of available application templates.
"""
root = resources.files("openapi_first.templates")
return sorted(
item.name
for item in root.iterdir()
if item.is_dir() and not item.name.startswith("_")
)
This function copies a fully working, minimal OpenAPI-first FastAPI
health check application from the package's embedded templates into
the specified target directory.
The target directory will be created if it does not already exist.
Existing files may be overwritten.
Parameters
----------
target_dir : pathlib.Path
Destination directory into which the health app template
should be copied.
Raises
------
FileNotFoundError
If the bundled health app template cannot be located.
def copy_template(template: str, target_dir: Path) -> None:
"""
Copy a bundled OpenAPI-first application template into a directory.
"""
target_dir = target_dir.resolve()
target_dir.mkdir(parents=True, exist_ok=True)
with resources.files("openapi_first.templates").joinpath(
"health_app"
) as src:
shutil.copytree(src, target_dir, dirs_exist_ok=True)
root = resources.files("openapi_first.templates")
src = root / template
if not src.exists():
raise FileNotFoundError(
f"Template '{template}' not found. "
f"Available templates: {', '.join(available_templates())}"
)
with resources.as_file(src) as path:
shutil.copytree(path, target_dir, dirs_exist_ok=True)
def main() -> None:
"""
Entry point for the FastAPI OpenAPI-first CLI.
Parses command-line arguments and initializes a new OpenAPI-first
health check application by copying the bundled template into the
specified directory.
If no target path is provided, the scaffold is created in a directory
named ``health-app`` in the current working directory.
Example
-------
Create a health app in the default directory::
openapi-first
Create a health app in a custom directory::
openapi-first my-service
"""
parser = argparse.ArgumentParser(
description="FastAPI OpenAPI-first scaffolding tools"
)
parser.add_argument(
"template",
nargs="?",
default=DEFAULT_TEMPLATE,
help=f"Template name (default: {DEFAULT_TEMPLATE})",
)
parser.add_argument(
"path",
nargs="?",
default="health-app",
help="Target directory for the health app",
default=None,
help="Target directory (defaults to template name)",
)
parser.add_argument(
"--list",
action="store_true",
help="List available templates and exit",
)
args = parser.parse_args()
copy_health_app_template(Path(args.path))
print(f"Health app created at {args.path}")
if args.list:
for name in available_templates():
print(name)
return
target = Path(args.path or args.template.replace("_", "-"))
try:
copy_template(args.template, target)
except Exception as exc:
raise SystemExit(str(exc)) from exc
print(f"Template '{args.template}' created at {target}")

176
openapi_first/client.py Normal file
View File

@@ -0,0 +1,176 @@
from typing import Any, Callable, Dict, Optional
from urllib.parse import urljoin
import httpx
from .errors import OpenAPIFirstError
class OpenAPIClientError(OpenAPIFirstError):
"""Raised when an OpenAPI client operation fails."""
class OpenAPIClient:
"""
OpenAPI-first HTTP client (httpx-based).
- One callable per operationId
- Explicit parameters (path, query, headers, body)
- No implicit schema inference or mutation
"""
def __init__(
self,
spec: dict[str, Any],
base_url: Optional[str] = None,
client: Optional[httpx.Client] = None,
) -> None:
self.spec = spec
self.base_url = base_url or self._resolve_base_url(spec)
self.client = client or httpx.Client(base_url=self.base_url)
self._operations: Dict[str, Callable[..., httpx.Response]] = {}
self._build_operations()
# ------------------------------------------------------------------ #
# Public API
# ------------------------------------------------------------------ #
def __getattr__(self, name: str) -> Callable[..., httpx.Response]:
try:
return self._operations[name]
except KeyError:
raise AttributeError(f"No such operationId: {name}") from None
def operations(self) -> Dict[str, Callable[..., httpx.Response]]:
return dict(self._operations)
# ------------------------------------------------------------------ #
# Internal mechanics
# ------------------------------------------------------------------ #
def _resolve_base_url(self, spec: dict[str, Any]) -> str:
servers = spec.get("servers")
if not servers:
raise OpenAPIClientError("No servers defined in OpenAPI spec")
url = servers[0].get("url")
if not url:
raise OpenAPIClientError("Server entry missing 'url'")
return url.rstrip("/") + "/"
def _build_operations(self) -> None:
paths = self.spec.get("paths", {})
if not paths:
raise OpenAPIClientError("OpenAPI spec contains no paths")
for path, path_item in paths.items():
for method, operation in path_item.items():
if method.lower() not in {
"get", "post", "put", "patch", "delete", "head", "options"
}:
continue
operation_id = operation.get("operationId")
if not operation_id:
raise OpenAPIClientError(
f"Missing operationId for {method.upper()} {path}"
)
if operation_id in self._operations:
raise OpenAPIClientError(
f"Duplicate operationId detected: {operation_id}"
)
self._operations[operation_id] = self._make_operation(
method=method.upper(),
path=path,
operation=operation,
)
def _make_operation(
self,
*,
method: str,
path: str,
operation: dict[str, Any],
) -> Callable[..., httpx.Response]:
request_body = operation.get("requestBody")
def call(
*,
path_params: Optional[dict[str, Any]] = None,
query: Optional[dict[str, Any]] = None,
headers: Optional[dict[str, str]] = None,
body: Optional[Any] = None,
timeout: Optional[float] = None,
) -> httpx.Response:
url = self._build_url(path, path_params or {})
req_headers = headers.copy() if headers else {}
json_data = None
content = None
if request_body is not None:
if body is None:
raise OpenAPIClientError(
f"Request body required for operation {operation['operationId']}"
)
media_types = request_body.get("content", {})
if "application/json" in media_types:
json_data = body
req_headers.setdefault("Content-Type", "application/json")
else:
content = body
response = self.client.request(
method=method,
url=url,
params=query,
headers=req_headers,
json=json_data,
content=content,
timeout=timeout,
)
return response
call.__name__ = operation["operationId"]
call.__doc__ = self._build_docstring(method, path, operation)
return call
def _build_url(self, path: str, path_params: dict[str, Any]) -> str:
try:
formatted_path = path.format(**path_params)
except KeyError as exc:
raise OpenAPIClientError(
f"Missing path parameter: {exc.args[0]}"
) from exc
return urljoin(self.base_url, formatted_path.lstrip("/"))
def _build_docstring(
self,
method: str,
path: str,
operation: dict[str, Any],
) -> str:
lines = [
f"{method} {path}",
"",
operation.get("summary", ""),
"",
"Parameters:",
" path_params: dict | None",
" query: dict | None",
" headers: dict | None",
" body: Any | None",
"",
"Returns:",
" httpx.Response",
]
return "\n".join(lines)

View File

@@ -25,8 +25,6 @@ This module intentionally does NOT:
- Perform request/response validation at runtime
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any

View File

@@ -0,0 +1,18 @@
"""
Application templates for FastAPI OpenAPI First.
This package contains example and scaffolding templates intended to be
copied into user projects via the ``openapi-first`` CLI.
Templates in this package are:
- Reference implementations of OpenAPI-first services
- Not part of the ``openapi_first`` public or internal API
- Not intended to be imported as runtime dependencies
The presence of this file exists solely to:
- Mark the directory as an explicit Python package
- Enable deterministic tooling behavior (documentation, packaging)
- Avoid accidental traversal of non-package directories
No code in this package should be imported by library consumers.
"""

View File

@@ -0,0 +1,99 @@
"""
OpenAPI-first CRUD application template.
This package contains a complete, minimal example of an OpenAPI-first
CRUD service built using the ``openapi_first`` library.
The application is assembled exclusively from:
- an OpenAPI specification (``openapi.yaml``)
- a handler namespace implementing CRUD operations (``routes``)
- an in-memory mock data store (``data``)
All HTTP routes, methods, schemas, and operation bindings are defined
in the OpenAPI specification and enforced at application startup.
No decorator-driven routing or implicit framework behavior is used.
This template demonstrates:
- operationId-driven server-side route binding
- explicit HTTP status code control in handlers
- operationId-driven client usage against the same OpenAPI contract
- end-to-end validation using in-memory data and tests
----------------------------------------------------------------------
Scaffolding via CLI
----------------------------------------------------------------------
Create a new CRUD example service using the bundled template:
openapi-first crud_app
Create the service in a custom directory:
openapi-first crud_app my-crud-service
List all available application templates:
openapi-first --list
The CLI copies template files verbatim into the target directory.
No code is generated or modified beyond the copied scaffold.
----------------------------------------------------------------------
Client Usage Example
----------------------------------------------------------------------
The same OpenAPI specification used by the server can be used to
construct a strict, operationId-driven HTTP client.
Example client calls for CRUD operations:
from openapi_first.loader import load_openapi
from openapi_first.client import OpenAPIClient
spec = load_openapi("openapi.yaml")
client = OpenAPIClient(spec)
# List items
response = client.list_items()
# Get item by ID
response = client.get_item(
path_params={"item_id": 1}
)
# Create item
response = client.create_item(
body={"name": "Orange", "price": 0.8}
)
# Update item
response = client.update_item(
path_params={"item_id": 1},
body={"name": "Green Apple", "price": 0.6},
)
# Delete item
response = client.delete_item(
path_params={"item_id": 1}
)
Client guarantees:
- One callable per OpenAPI ``operationId``
- No hardcoded URLs or HTTP methods in user code
- Path and request parameters must match the OpenAPI specification
- Invalid or incomplete OpenAPI specs fail at client construction time
----------------------------------------------------------------------
Non-Goals
----------------------------------------------------------------------
This template is intentionally minimal and is NOT:
- production-ready
- persistent or concurrency-safe
- a reference architecture for data storage
It exists solely as a copyable example for learning, testing, and
bootstrapping OpenAPI-first services.
This package is not part of the ``openapi_first`` library API surface.
"""

View File

@@ -0,0 +1,132 @@
"""
In-memory mock data store for CRUD example.
This module intentionally avoids persistence and concurrency guarantees.
It is suitable for demos, tests, and scaffolding only.
It intentionally avoids
- persistence
- concurrency guarantees
- validation
- error handling
The implementation is suitable for:
- demonstrations
- tests
- scaffolding and example services
It is explicitly NOT suitable for production use.
This module is not part of the ``openapi_first`` library API surface.
"""
from typing import Dict
# In-memory storage keyed by item ID.
_items: Dict[int, dict] = {
1: {"id": 1, "name": "Apple", "price": 0.5},
2: {"id": 2, "name": "Banana", "price": 0.3},
}
# Auto-incrementing ID counter.
_next_id = 3
def list_items():
"""
Return all items in the data store.
This function performs no filtering, pagination, or sorting.
The returned collection reflects the current in-memory state.
Returns
-------
list[dict]
A list of item representations.
"""
return list(_items.values())
def get_item(item_id: int):
"""
Retrieve a single item by ID.
This function assumes the item exists and will raise ``KeyError``
if the ID is not present in the store.
Parameters
----------
item_id : int
Identifier of the item to retrieve.
Returns
-------
dict
The stored item representation.
"""
return _items[item_id]
def create_item(payload: dict):
"""
Create a new item in the data store.
A new integer ID is assigned automatically. No validation is
performed on the provided payload.
Parameters
----------
payload : dict
Item attributes excluding the ``id`` field.
Returns
-------
dict
The newly created item, including its assigned ID.
"""
global _next_id
item = {"id": _next_id, **payload}
_items[_next_id] = item
_next_id += 1
return item
def update_item(item_id: int, payload: dict):
"""
Replace an existing item in the data store.
This function overwrites the existing item entirely and does not
perform partial updates or validation. If the item does not exist,
it will be created implicitly.
Parameters
----------
item_id : int
Identifier of the item to update.
payload : dict
Item attributes excluding the ``id`` field.
Returns
-------
dict
The updated item representation.
"""
item = {"id": item_id, **payload}
_items[item_id] = item
return item
def delete_item(item_id: int):
"""
Remove an item from the data store.
This function assumes the item exists and will raise ``KeyError``
if the ID is not present.
Parameters
----------
item_id : int
Identifier of the item to delete.
"""
del _items[item_id]

View File

@@ -0,0 +1,35 @@
"""
Application entry point for an OpenAPI-first CRUD example service.
This module constructs a FastAPI application exclusively from an
OpenAPI specification and a handler namespace, without using
decorator-driven routing.
All HTTP routes, methods, request/response schemas, and operation
bindings are defined in the OpenAPI document referenced by
``openapi_path``. Python callables defined in the ``routes`` module are
bound to OpenAPI operations strictly via ``operationId``.
This module contains no routing logic, persistence concerns, or
framework configuration beyond application assembly.
Design guarantees:
- OpenAPI is the single source of truth
- No undocumented routes can exist
- Every OpenAPI operationId must resolve to exactly one handler
- All contract violations fail at application startup
This file is intended to be used as the ASGI entry point.
Example:
uvicorn main:app
"""
from openapi_first.app import OpenAPIFirstApp
import routes
app = OpenAPIFirstApp(
openapi_path="openapi.yaml",
routes_module=routes,
title="CRUD Example Service",
)

View File

@@ -0,0 +1,115 @@
openapi: 3.0.3
info:
title: CRUD Example Service
version: "1.0.0"
description: Minimal OpenAPI-first CRUD service with in-memory mock data.
paths:
/items:
get:
operationId: list_items
summary: List all items
responses:
"200":
description: List of items
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Item"
post:
operationId: create_item
summary: Create a new item
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ItemCreate"
responses:
"201":
description: Item created
content:
application/json:
schema:
$ref: "#/components/schemas/Item"
/items/{item_id}:
get:
operationId: get_item
summary: Get item by ID
parameters:
- name: item_id
in: path
required: true
schema:
type: integer
responses:
"200":
description: Item found
content:
application/json:
schema:
$ref: "#/components/schemas/Item"
put:
operationId: update_item
summary: Update an item
parameters:
- name: item_id
in: path
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ItemCreate"
responses:
"200":
description: Item updated
content:
application/json:
schema:
$ref: "#/components/schemas/Item"
delete:
operationId: delete_item
summary: Delete an item
parameters:
- name: item_id
in: path
required: true
schema:
type: integer
responses:
"204":
description: Item deleted
components:
schemas:
Item:
type: object
properties:
id:
type: integer
name:
type: string
price:
type: number
format: float
required: [id, name, price]
ItemCreate:
type: object
properties:
name:
type: string
price:
type: number
format: float
required: [name, price]

View File

@@ -0,0 +1,158 @@
"""
CRUD route handlers bound via OpenAPI operationId.
These handlers explicitly control HTTP status codes to ensure
runtime behavior matches the OpenAPI contract.
This module defines OpenAPI-bound operation handlers for a simple CRUD
service. Functions in this module are bound to HTTP routes exclusively
via OpenAPI ``operationId`` values.
Handlers explicitly control HTTP response status codes to ensure runtime
behavior matches the OpenAPI contract. Error conditions are translated
into explicit HTTP responses rather than relying on implicit framework
behavior.
No routing decorators or path definitions appear in this module. All
routing, HTTP methods, and schemas are defined in the OpenAPI
specification.
"""
from fastapi import Response, HTTPException
from data import (
list_items as _list_items,
get_item as _get_item,
create_item as _create_item,
update_item as _update_item,
delete_item as _delete_item,
)
def list_items():
"""
List all items.
Implements the OpenAPI operation identified by
``operationId: list_items``.
Returns
-------
list[dict]
A list of item representations.
"""
return _list_items()
def get_item(item_id: int):
"""
Retrieve a single item by ID.
Implements the OpenAPI operation identified by
``operationId: get_item``.
Parameters
----------
item_id : int
Identifier of the item to retrieve.
Returns
-------
dict
The requested item.
Raises
------
HTTPException
404 if the item does not exist.
"""
try:
return _get_item(item_id)
except KeyError:
raise HTTPException(status_code=404, detail="Item not found")
def create_item(payload: dict, response: Response):
"""
Create a new item.
Implements the OpenAPI operation identified by
``operationId: create_item``.
Parameters
----------
payload : dict
Item attributes excluding the ``id`` field.
response : fastapi.Response
Response object used to set the HTTP status code.
Returns
-------
dict
The newly created item.
"""
item = _create_item(payload)
response.status_code = 201
return item
def update_item(item_id: int, payload: dict):
"""
Update an existing item.
Implements the OpenAPI operation identified by
``operationId: update_item``.
Parameters
----------
item_id : int
Identifier of the item to update.
payload : dict
Item attributes excluding the ``id`` field.
Returns
-------
dict
The updated item.
Raises
------
HTTPException
404 if the item does not exist.
"""
try:
return _update_item(item_id, payload)
except KeyError:
raise HTTPException(status_code=404, detail="Item not found")
def delete_item(item_id: int, response: Response):
"""
Delete an existing item.
Implements the OpenAPI operation identified by
``operationId: delete_item``.
Parameters
----------
item_id : int
Identifier of the item to delete.
response : fastapi.Response
Response object used to set the HTTP status code.
Returns
-------
None
No content.
Raises
------
HTTPException
404 if the item does not exist.
"""
try:
_delete_item(item_id)
except KeyError:
raise HTTPException(status_code=404, detail="Item not found")
response.status_code = 204

View File

@@ -0,0 +1,125 @@
"""
End-to-end tests for the OpenAPI-first CRUD example app.
These tests validate that all CRUD operations behave correctly
against the in-memory mock data store.
- OpenAPI specification loading
- OperationId-driven route binding on the server
- OperationId-driven client invocation
- Correct HTTP status codes and response payloads
The tests exercise all CRUD operations against an in-memory mock data
store and assume deterministic behavior within a single process.
The tests assume:
- OpenAPI-first route binding
- In-memory storage (no persistence guarantees)
- Deterministic behavior in a single process
- One-to-one correspondence between OpenAPI operationId values and
server/client callables
"""
from fastapi.testclient import TestClient
from main import app
from openapi_first.loader import load_openapi
from openapi_first.client import OpenAPIClient
client = TestClient(app)
spec = load_openapi("openapi.yaml")
client = OpenAPIClient(
spec=spec,
base_url="http://testserver",
client=client,
)
def test_list_items_initial():
"""Initial items should be present."""
response = client.list_items()
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
assert len(data) >= 2
ids = {item["id"] for item in data}
assert 1 in ids
assert 2 in ids
def test_get_item():
"""Existing item should be retrievable by ID."""
response = client.get_item(
path_params={"item_id": 1}
)
assert response.status_code == 200
item = response.json()
assert item["id"] == 1
assert "name" in item
assert "price" in item
def test_create_item():
"""Creating a new item should return the created entity."""
payload = {
"name": "Orange",
"price": 0.8,
}
response = client.create_item(
body=payload
)
assert response.status_code == 201
item = response.json()
assert "id" in item
assert item["name"] == payload["name"]
assert item["price"] == payload["price"]
# Verify it appears in list
list_response = client.list_items()
ids = {i["id"] for i in list_response.json()}
assert item["id"] in ids
def test_update_item():
"""Updating an item should replace its values."""
payload = {
"name": "Green Apple",
"price": 0.6,
}
response = client.update_item(
path_params={"item_id": 1},
body=payload,
)
assert response.status_code == 200
item = response.json()
assert item["id"] == 1
assert item["name"] == payload["name"]
assert item["price"] == payload["price"]
# Verify persisted update
get_response = client.get_item(
path_params={"item_id": 1}
)
updated = get_response.json()
assert updated["name"] == payload["name"]
assert updated["price"] == payload["price"]
def test_delete_item():
"""Deleting an item should remove it from the store."""
response = client.delete_item(
path_params={"item_id": 2}
)
assert response.status_code == 204
# Verify deletion
list_response = client.list_items()
ids = {item["id"] for item in list_response.json()}
assert 2 not in ids

View File

@@ -0,0 +1,65 @@
"""
OpenAPI-first FastAPI application template.
This package contains a minimal, fully working example of an
OpenAPI-first FastAPI service built using the ``openapi_first`` library.
The application is assembled exclusively from:
- an OpenAPI specification (``openapi.yaml``)
- a handler namespace (``routes``)
No routing decorators, implicit behavior, or framework-specific
convenience abstractions are used. All HTTP routes, methods, and
operation bindings are defined in OpenAPI and enforced at application
startup.
This package is intended to be copied as a starting point for new
services via the ``openapi-first`` CLI. It is not part of the
``openapi_first`` library API surface.
----------------------------------------------------------------------
Scaffolding via CLI
----------------------------------------------------------------------
Create a new OpenAPI-first health check service using the bundled
template:
openapi-first health_app
Create the service in a custom directory:
openapi-first health_app my-health-service
List all available application templates:
openapi-first --list
The CLI copies template files verbatim into the target directory.
No code is generated or modified beyond the copied scaffold.
----------------------------------------------------------------------
Client Usage Example
----------------------------------------------------------------------
The same OpenAPI specification used by the server can be used to
construct a strict, operationId-driven HTTP client.
Example client call for the ``get_health`` operation:
from openapi_first.loader import load_openapi
from openapi_first.client import OpenAPIClient
spec = load_openapi("openapi.yaml")
client = OpenAPIClient(spec)
response = client.get_health()
assert response.status_code == 200
assert response.json() == {"status": "ok"}
Client guarantees:
- One callable per OpenAPI ``operationId``
- No hardcoded URLs or HTTP methods in user code
- Path and request parameters must match the OpenAPI specification
- Invalid or incomplete OpenAPI specs fail at client construction time
"""

View File

@@ -1,3 +1,31 @@
"""
Application entry point for an OpenAPI-first FastAPI service.
This module constructs a FastAPI application exclusively from an
OpenAPI specification and a handler namespace, without using
decorator-driven routing.
All HTTP routes, methods, and operation bindings are defined in the
OpenAPI document referenced by ``openapi_path``. Python callables
defined in the ``routes`` module are bound to OpenAPI operations
strictly via ``operationId``.
This module contains no routing logic, request handling, or framework
configuration beyond application assembly.
Design guarantees:
- OpenAPI is the single source of truth
- No undocumented routes can exist
- Every OpenAPI operationId must resolve to exactly one handler
- All contract violations fail at application startup
This file is intended to be used as the ASGI entry point.
Example:
uvicorn main:app
"""
from openapi_first.app import OpenAPIFirstApp
import routes

View File

@@ -1,2 +1,32 @@
"""
OpenAPI operation handlers.
This module defines pure Python callables that implement OpenAPI
operations for this service. Functions in this module are bound to HTTP
routes exclusively via OpenAPI ``operationId`` values.
No routing decorators, HTTP metadata, or framework-specific logic
should appear here. All request/response semantics are defined in the
OpenAPI specification.
This module serves solely as an operationId namespace.
"""
def get_health():
"""
Health check operation handler.
This function implements the OpenAPI operation identified by
``operationId: get_health``.
It contains no routing metadata or framework-specific logic.
Request binding, HTTP method, and response semantics are defined
exclusively by the OpenAPI specification.
Returns
-------
dict
A minimal liveness payload indicating service health.
"""
return {"status": "ok"}

View File

@@ -0,0 +1,102 @@
"""
OpenAPI-first model-based CRUD application template.
This package contains a complete, minimal example of an OpenAPI-first
CRUD service that uses explicit Pydantic domain models for request and
response schemas.
The application is assembled exclusively from:
- an OpenAPI specification (``openapi.yaml``)
- a handler namespace implementing CRUD operations (``routes``)
- Pydantic domain models (``models``)
- an in-memory mock data store (``data``)
All HTTP routes, methods, schemas, and operation bindings are defined
in the OpenAPI specification and enforced at application startup.
No decorator-driven routing or implicit framework behavior is used.
This template demonstrates:
- operationId-driven server-side route binding
- explicit request and response modeling with Pydantic
- explicit HTTP status code control in handlers
- operationId-driven client usage against the same OpenAPI contract
- end-to-end validation using in-memory data and tests
----------------------------------------------------------------------
Scaffolding via CLI
----------------------------------------------------------------------
Create a new model-based CRUD example service using the bundled template:
openapi-first model_app
Create the service in a custom directory:
openapi-first model_app my-model-service
List all available application templates:
openapi-first --list
The CLI copies template files verbatim into the target directory.
No code is generated or modified beyond the copied scaffold.
----------------------------------------------------------------------
Client Usage Example
----------------------------------------------------------------------
The same OpenAPI specification used by the server can be used to
construct a strict, operationId-driven HTTP client.
Example client calls for model-based CRUD operations:
from openapi_first.loader import load_openapi
from openapi_first.client import OpenAPIClient
spec = load_openapi("openapi.yaml")
client = OpenAPIClient(spec)
# List items
response = client.list_items()
# Get item by ID
response = client.get_item(
path_params={"item_id": 1}
)
# Create item
response = client.create_item(
body={"name": "Orange", "price": 0.8}
)
# Update item
response = client.update_item(
path_params={"item_id": 1},
body={"name": "Green Apple", "price": 0.6},
)
# Delete item
response = client.delete_item(
path_params={"item_id": 1}
)
Client guarantees:
- One callable per OpenAPI ``operationId``
- No hardcoded URLs or HTTP methods in user code
- Request and response payloads conform to Pydantic models
- Invalid or incomplete OpenAPI specs fail at client construction time
----------------------------------------------------------------------
Non-Goals
----------------------------------------------------------------------
This template is intentionally minimal and is NOT:
- production-ready
- persistent or concurrency-safe
- a reference architecture for data storage
It exists solely as a copyable example for learning, testing, and
bootstrapping OpenAPI-first services.
This package is not part of the ``openapi_first`` library API surface.
"""

View File

@@ -0,0 +1,137 @@
"""
In-memory data store using Pydantic models.
This module is NOT thread-safe and is intended for demos and scaffolds only.
This module provides a minimal, process-local data store for the
model-based CRUD example application. It stores and returns domain
objects defined using Pydantic models and is intended solely for
demonstration and scaffolding purposes.
The implementation intentionally avoids:
- persistence
- concurrency guarantees
- transactional semantics
- validation beyond what Pydantic provides
It is not part of the ``openapi_first`` library API surface.
"""
from typing import Dict
from models import Item, ItemCreate
# In-memory storage keyed by item ID.
_items: Dict[int, Item] = {
1: Item(id=1, name="Apple", price=0.5),
2: Item(id=2, name="Banana", price=0.3),
}
# Auto-incrementing identifier.
_next_id = 3
def list_items() -> list[Item]:
"""
Return all items in the data store.
Returns
-------
list[Item]
A list of item domain objects.
"""
return list(_items.values())
def get_item(item_id: int) -> Item:
"""
Retrieve a single item by ID.
Parameters
----------
item_id : int
Identifier of the item to retrieve.
Returns
-------
Item
The requested item.
Raises
------
KeyError
If the item does not exist.
"""
return _items[item_id]
def create_item(payload: ItemCreate) -> Item:
"""
Create a new item in the data store.
A new identifier is assigned automatically. No additional validation
is performed beyond Pydantic model validation.
Parameters
----------
payload : ItemCreate
Data required to create a new item.
Returns
-------
Item
The newly created item.
"""
global _next_id
item = Item(id=_next_id, **payload.model_dump())
_items[_next_id] = item
_next_id += 1
return item
def update_item(item_id: int, payload: ItemCreate) -> Item:
"""
Replace an existing item in the data store.
This function performs a full replacement of the stored item.
Partial updates are not supported.
Parameters
----------
item_id : int
Identifier of the item to update.
payload : ItemCreate
New item data.
Returns
-------
Item
The updated item.
Raises
------
KeyError
If the item does not exist.
"""
if item_id not in _items:
raise KeyError(item_id)
item = Item(id=item_id, **payload.model_dump())
_items[item_id] = item
return item
def delete_item(item_id: int) -> None:
"""
Remove an item from the data store.
Parameters
----------
item_id : int
Identifier of the item to delete.
Raises
------
KeyError
If the item does not exist.
"""
del _items[item_id]

View File

@@ -0,0 +1,35 @@
"""
Application entry point for an OpenAPI-first model-based CRUD example service.
This module constructs a FastAPI application exclusively from an
OpenAPI specification and a handler namespace, without using
decorator-driven routing.
All HTTP routes, methods, request/response schemas, and operation
bindings are defined in the OpenAPI document referenced by
``openapi_path``. Python callables defined in the ``routes`` module are
bound to OpenAPI operations strictly via ``operationId``.
This module contains no routing logic, persistence concerns, or
framework configuration beyond application assembly.
Design guarantees:
- OpenAPI is the single source of truth
- No undocumented routes can exist
- Every OpenAPI operationId must resolve to exactly one handler
- All contract violations fail at application startup
This file is intended to be used as the ASGI entry point.
Example:
uvicorn main:app
"""
from openapi_first.app import OpenAPIFirstApp
import routes
app = OpenAPIFirstApp(
openapi_path="openapi.yaml",
routes_module=routes,
title="Model CRUD Example Service",
)

View File

@@ -0,0 +1,50 @@
"""
Pydantic domain models for the CRUD example.
This module defines Pydantic models that represent the domain entities
used by the service. These models are referenced by the OpenAPI
specification for request and response schemas.
The models are declarative and framework-agnostic. They contain no
persistence logic, validation beyond type constraints, or business
behavior.
This module is not part of the ``openapi_first`` library API surface.
It exists solely to support the example application template.
"""
from pydantic import BaseModel
class ItemBase(BaseModel):
"""
Base domain model for an item.
Defines fields common to all item representations.
"""
name: str
price: float
class ItemCreate(ItemBase):
"""
Domain model for item creation requests.
This model is used for request bodies when creating new items.
It intentionally excludes the ``id`` field, which is assigned
by the service.
"""
pass
class Item(ItemBase):
"""
Domain model for a persisted item.
This model represents the full item state returned in responses,
including the server-assigned identifier.
"""
id: int

View File

@@ -0,0 +1,116 @@
openapi: 3.0.3
info:
title: Model CRUD Example Service
version: "1.0.0"
description: OpenAPI-first CRUD service with Pydantic models.
paths:
/items:
get:
operationId: list_items
responses:
"200":
description: List of items
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Item"
post:
operationId: create_item
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ItemCreate"
responses:
"201":
description: Item created
content:
application/json:
schema:
$ref: "#/components/schemas/Item"
/items/{item_id}:
get:
operationId: get_item
parameters:
- name: item_id
in: path
required: true
schema:
type: integer
responses:
"200":
description: Item found
content:
application/json:
schema:
$ref: "#/components/schemas/Item"
"404":
description: Item not found
put:
operationId: update_item
parameters:
- name: item_id
in: path
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ItemCreate"
responses:
"200":
description: Item updated
content:
application/json:
schema:
$ref: "#/components/schemas/Item"
"404":
description: Item not found
delete:
operationId: delete_item
parameters:
- name: item_id
in: path
required: true
schema:
type: integer
responses:
"204":
description: Item deleted
"404":
description: Item not found
components:
schemas:
Item:
type: object
required: [id, name, price]
properties:
id:
type: integer
name:
type: string
price:
type: number
format: float
ItemCreate:
type: object
required: [name, price]
properties:
name:
type: string
price:
type: number
format: float

View File

@@ -0,0 +1,156 @@
"""
CRUD route handlers bound via OpenAPI operationId.
This module defines OpenAPI-bound operation handlers for a model-based
CRUD service. Functions in this module are bound to HTTP routes
exclusively via OpenAPI ``operationId`` values.
Handlers explicitly control HTTP response status codes to ensure runtime
behavior matches the OpenAPI contract. Domain models defined using
Pydantic are used for request and response payloads.
No routing decorators, path definitions, or implicit framework behavior
appear in this module. All routing, HTTP methods, and schemas are defined
in the OpenAPI specification.
"""
from fastapi import Response, HTTPException
from models import ItemCreate
from data import (
list_items as _list_items,
get_item as _get_item,
create_item as _create_item,
update_item as _update_item,
delete_item as _delete_item,
)
def list_items():
"""
List all items.
Implements the OpenAPI operation identified by
``operationId: list_items``.
Returns
-------
list[Item]
A list of item domain objects.
"""
return _list_items()
def get_item(item_id: int):
"""
Retrieve a single item by ID.
Implements the OpenAPI operation identified by
``operationId: get_item``.
Parameters
----------
item_id : int
Identifier of the item to retrieve.
Returns
-------
Item
The requested item.
Raises
------
HTTPException
404 if the item does not exist.
"""
try:
return _get_item(item_id)
except KeyError:
raise HTTPException(status_code=404, detail="Item not found")
def create_item(payload: ItemCreate, response: Response):
"""
Create a new item.
Implements the OpenAPI operation identified by
``operationId: create_item``.
Parameters
----------
payload : ItemCreate
Request body describing the item to create.
response : fastapi.Response
Response object used to set the HTTP status code.
Returns
-------
Item
The newly created item.
"""
item = _create_item(payload)
response.status_code = 201
return item
def update_item(item_id: int, payload: ItemCreate):
"""
Update an existing item.
Implements the OpenAPI operation identified by
``operationId: update_item``.
Parameters
----------
item_id : int
Identifier of the item to update.
payload : ItemCreate
New item data.
Returns
-------
Item
The updated item.
Raises
------
HTTPException
404 if the item does not exist.
"""
try:
return _update_item(item_id, payload)
except KeyError:
raise HTTPException(status_code=404, detail="Item not found")
def delete_item(item_id: int, response: Response):
"""
Delete an existing item.
Implements the OpenAPI operation identified by
``operationId: delete_item``.
Parameters
----------
item_id : int
Identifier of the item to delete.
response : fastapi.Response
Response object used to set the HTTP status code.
Returns
-------
None
No content.
Raises
------
HTTPException
404 if the item does not exist.
"""
try:
_delete_item(item_id)
except KeyError:
raise HTTPException(status_code=404, detail="Item not found")
response.status_code = 204
return None

View File

@@ -0,0 +1,124 @@
"""
End-to-end tests for the OpenAPI-first model CRUD example app.
These tests validate that all CRUD operations behave correctly
against the in-memory mock data store using Pydantic models.
- OpenAPI specification loading
- OperationId-driven route binding on the server
- OperationId-driven client invocation
- Pydantic model-based request and response handling
All CRUD operations are exercised against an in-memory mock data store
backed by Pydantic domain models.
The tests assume:
- OpenAPI-first route binding
- Pydantic model validation
- In-memory storage (no persistence guarantees)
- Deterministic behavior in a single process
"""
from fastapi.testclient import TestClient
from main import app
from openapi_first.loader import load_openapi
from openapi_first.client import OpenAPIClient
client = TestClient(app)
spec = load_openapi("openapi.yaml")
client = OpenAPIClient(
spec=spec,
base_url="http://testserver",
client=client,
)
def test_list_items_initial():
"""Initial items should be present."""
response = client.list_items()
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
assert len(data) >= 2
ids = {item["id"] for item in data}
assert 1 in ids
assert 2 in ids
def test_get_item():
"""Existing item should be retrievable by ID."""
response = client.get_item(
path_params={"item_id": 1}
)
assert response.status_code == 200
item = response.json()
assert item["id"] == 1
assert isinstance(item["name"], str)
assert isinstance(item["price"], float)
def test_create_item():
"""Creating a new item should return the created entity."""
payload = {
"name": "Orange",
"price": 0.8,
}
response = client.create_item(
body=payload
)
assert response.status_code == 201
item = response.json()
assert "id" in item
assert item["name"] == payload["name"]
assert item["price"] == payload["price"]
# Verify it appears in list
list_response = client.list_items()
ids = {i["id"] for i in list_response.json()}
assert item["id"] in ids
def test_update_item():
"""Updating an item should replace its values."""
payload = {
"name": "Green Apple",
"price": 0.6,
}
response = client.update_item(
path_params={"item_id": 1},
body=payload,
)
assert response.status_code == 200
item = response.json()
assert item["id"] == 1
assert item["name"] == payload["name"]
assert item["price"] == payload["price"]
# Verify persisted update
get_response = client.get_item(
path_params={"item_id": 1}
)
updated = get_response.json()
assert updated["name"] == payload["name"]
assert updated["price"] == payload["price"]
def test_delete_item():
"""Deleting an item should remove it from the store."""
response = client.delete_item(
path_params={"item_id": 2}
)
assert response.status_code == 204
# Verify deletion
list_response = client.list_items()
ids = {item["id"] for item in list_response.json()}
assert 2 not in ids

View File

@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "openapi-first"
version = "0.0.1"
version = "0.0.4"
description = "Strict OpenAPI-first application bootstrap for FastAPI."
readme = "README.md"
requires-python = ">=3.10"
@@ -84,7 +84,7 @@ Versions = "https://git.aetoskia.com/aetos/openapi-first/tags"
packages = { find = { include = ["openapi_first*"] } }
[tool.setuptools.package-data]
fastapi_openapi_first = ["templates/**/*"]
openapi_first = ["templates/**/*"]
[tool.ruff]

View File

@@ -1,7 +1,9 @@
fastapi==0.128.0,
openapi-spec-validator==0.7.2,
pyyaml==6.0.3,
openapi-spec-validator==0.7.2
pyyaml==6.0.3
uvicorn==0.40.0
pydantic==2.12.5
httpx==0.28.1
# Test Packages
pytest==7.4.0