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
This commit is contained in:
2026-01-11 21:42:30 +05:30
parent 72b5be6976
commit a3c063b569
23 changed files with 740 additions and 5 deletions

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 @@
# 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

@@ -49,10 +49,9 @@ nav:
- Templates: - Templates:
- Home: openapi_first/templates/index.md - Home: openapi_first/templates/index.md
- Health App: - Health App: openapi_first/templates/health_app/index.md
- Home: openapi_first/templates/health_app/index.md - CRUD App: openapi_first/templates/crud_app/index.md
- App: openapi_first/templates/health_app/main.md - Model App: openapi_first/templates/model_app/index.md
- Routes: openapi_first/templates/health_app/routes.md
- Errors: - Errors:
- Error Hierarchy: openapi_first/errors.md - Error Hierarchy: openapi_first/errors.md

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

@@ -3,27 +3,88 @@ In-memory mock data store for CRUD example.
This module intentionally avoids persistence and concurrency guarantees. This module intentionally avoids persistence and concurrency guarantees.
It is suitable for demos, tests, and scaffolding only. 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 from typing import Dict
# In-memory storage keyed by item ID.
_items: Dict[int, dict] = { _items: Dict[int, dict] = {
1: {"id": 1, "name": "Apple", "price": 0.5}, 1: {"id": 1, "name": "Apple", "price": 0.5},
2: {"id": 2, "name": "Banana", "price": 0.3}, 2: {"id": 2, "name": "Banana", "price": 0.3},
} }
# Auto-incrementing ID counter.
_next_id = 3 _next_id = 3
def list_items(): 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()) return list(_items.values())
def get_item(item_id: int): 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] return _items[item_id]
def create_item(payload: dict): 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 global _next_id
item = {"id": _next_id, **payload} item = {"id": _next_id, **payload}
_items[_next_id] = item _items[_next_id] = item
@@ -32,10 +93,40 @@ def create_item(payload: dict):
def update_item(item_id: int, payload: dict): 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} item = {"id": item_id, **payload}
_items[item_id] = item _items[item_id] = item
return item return item
def delete_item(item_id: int): 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] del _items[item_id]

View File

@@ -1,3 +1,30 @@
"""
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 from openapi_first.app import OpenAPIFirstApp
import routes import routes

View File

@@ -3,6 +3,19 @@ CRUD route handlers bound via OpenAPI operationId.
These handlers explicitly control HTTP status codes to ensure These handlers explicitly control HTTP status codes to ensure
runtime behavior matches the OpenAPI contract. 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 fastapi import Response, HTTPException
@@ -17,10 +30,42 @@ from data import (
def list_items(): 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() return _list_items()
def get_item(item_id: int): 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: try:
return _get_item(item_id) return _get_item(item_id)
except KeyError: except KeyError:
@@ -28,12 +73,53 @@ def get_item(item_id: int):
def create_item(payload: dict, response: Response): 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) item = _create_item(payload)
response.status_code = 201 response.status_code = 201
return item return item
def update_item(item_id: int, payload: dict): 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: try:
return _update_item(item_id, payload) return _update_item(item_id, payload)
except KeyError: except KeyError:
@@ -41,10 +127,32 @@ def update_item(item_id: int, payload: dict):
def delete_item(item_id: int, response: Response): 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: try:
_delete_item(item_id) _delete_item(item_id)
except KeyError: except KeyError:
raise HTTPException(status_code=404, detail="Item not found") raise HTTPException(status_code=404, detail="Item not found")
response.status_code = 204 response.status_code = 204
return None

View File

@@ -3,11 +3,20 @@ End-to-end tests for the OpenAPI-first CRUD example app.
These tests validate that all CRUD operations behave correctly These tests validate that all CRUD operations behave correctly
against the in-memory mock data store. 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: The tests assume:
- OpenAPI-first route binding - OpenAPI-first route binding
- In-memory storage (no persistence guarantees) - In-memory storage (no persistence guarantees)
- Deterministic behavior in a single process - Deterministic behavior in a single process
- One-to-one correspondence between OpenAPI operationId values and
server/client callables
""" """
from fastapi.testclient import TestClient from fastapi.testclient import TestClient

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

@@ -2,29 +2,86 @@
In-memory data store using Pydantic models. In-memory data store using Pydantic models.
This module is NOT thread-safe and is intended for demos and scaffolds only. 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 typing import Dict
from models import Item, ItemCreate from models import Item, ItemCreate
# In-memory storage keyed by item ID.
_items: Dict[int, Item] = { _items: Dict[int, Item] = {
1: Item(id=1, name="Apple", price=0.5), 1: Item(id=1, name="Apple", price=0.5),
2: Item(id=2, name="Banana", price=0.3), 2: Item(id=2, name="Banana", price=0.3),
} }
# Auto-incrementing identifier.
_next_id = 3 _next_id = 3
def list_items() -> list[Item]: 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()) return list(_items.values())
def get_item(item_id: int) -> Item: 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] return _items[item_id]
def create_item(payload: ItemCreate) -> Item: 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 global _next_id
item = Item(id=_next_id, **payload.model_dump()) item = Item(id=_next_id, **payload.model_dump())
_items[_next_id] = item _items[_next_id] = item
@@ -33,6 +90,29 @@ def create_item(payload: ItemCreate) -> Item:
def update_item(item_id: int, payload: ItemCreate) -> 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: if item_id not in _items:
raise KeyError(item_id) raise KeyError(item_id)
item = Item(id=item_id, **payload.model_dump()) item = Item(id=item_id, **payload.model_dump())
@@ -41,4 +121,17 @@ def update_item(item_id: int, payload: ItemCreate) -> Item:
def delete_item(item_id: int) -> None: 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] del _items[item_id]

View File

@@ -1,3 +1,30 @@
"""
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 from openapi_first.app import OpenAPIFirstApp
import routes import routes

View File

@@ -1,18 +1,50 @@
""" """
Pydantic domain models for the CRUD example. 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 from pydantic import BaseModel
class ItemBase(BaseModel): class ItemBase(BaseModel):
"""
Base domain model for an item.
Defines fields common to all item representations.
"""
name: str name: str
price: float price: float
class ItemCreate(ItemBase): 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 pass
class Item(ItemBase): 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 id: int

View File

@@ -1,5 +1,17 @@
""" """
CRUD route handlers bound via OpenAPI operationId. 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 fastapi import Response, HTTPException
@@ -15,10 +27,42 @@ from data import (
def list_items(): 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() return _list_items()
def get_item(item_id: int): 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: try:
return _get_item(item_id) return _get_item(item_id)
except KeyError: except KeyError:
@@ -26,12 +70,53 @@ def get_item(item_id: int):
def create_item(payload: ItemCreate, response: Response): 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) item = _create_item(payload)
response.status_code = 201 response.status_code = 201
return item return item
def update_item(item_id: int, payload: ItemCreate): 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: try:
return _update_item(item_id, payload) return _update_item(item_id, payload)
except KeyError: except KeyError:
@@ -39,6 +124,29 @@ def update_item(item_id: int, payload: ItemCreate):
def delete_item(item_id: int, response: Response): 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: try:
_delete_item(item_id) _delete_item(item_id)
except KeyError: except KeyError:

View File

@@ -3,6 +3,13 @@ End-to-end tests for the OpenAPI-first model CRUD example app.
These tests validate that all CRUD operations behave correctly These tests validate that all CRUD operations behave correctly
against the in-memory mock data store using Pydantic models. 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: The tests assume:
- OpenAPI-first route binding - OpenAPI-first route binding