diff --git a/docs/openapi_first/templates/crud_app/data.md b/docs/openapi_first/templates/crud_app/data.md new file mode 100644 index 0000000..df37dde --- /dev/null +++ b/docs/openapi_first/templates/crud_app/data.md @@ -0,0 +1,3 @@ +# Data + +::: openapi_first.templates.crud_app.data diff --git a/docs/openapi_first/templates/crud_app/index.md b/docs/openapi_first/templates/crud_app/index.md new file mode 100644 index 0000000..3057c84 --- /dev/null +++ b/docs/openapi_first/templates/crud_app/index.md @@ -0,0 +1,3 @@ +# Crud App + +::: openapi_first.templates.crud_app diff --git a/docs/openapi_first/templates/crud_app/main.md b/docs/openapi_first/templates/crud_app/main.md new file mode 100644 index 0000000..a2673ae --- /dev/null +++ b/docs/openapi_first/templates/crud_app/main.md @@ -0,0 +1,3 @@ +# Main + +::: openapi_first.templates.crud_app.main diff --git a/docs/openapi_first/templates/crud_app/routes.md b/docs/openapi_first/templates/crud_app/routes.md new file mode 100644 index 0000000..2950fcc --- /dev/null +++ b/docs/openapi_first/templates/crud_app/routes.md @@ -0,0 +1,3 @@ +# Routes + +::: openapi_first.templates.crud_app.routes diff --git a/docs/openapi_first/templates/crud_app/test_crud_app.md b/docs/openapi_first/templates/crud_app/test_crud_app.md new file mode 100644 index 0000000..b876492 --- /dev/null +++ b/docs/openapi_first/templates/crud_app/test_crud_app.md @@ -0,0 +1,3 @@ +# Test Crud App + +::: openapi_first.templates.crud_app.test_crud_app diff --git a/docs/openapi_first/templates/model_app/data.md b/docs/openapi_first/templates/model_app/data.md new file mode 100644 index 0000000..d26cb7a --- /dev/null +++ b/docs/openapi_first/templates/model_app/data.md @@ -0,0 +1,3 @@ +# Data + +::: openapi_first.templates.model_app.data diff --git a/docs/openapi_first/templates/model_app/index.md b/docs/openapi_first/templates/model_app/index.md new file mode 100644 index 0000000..b2a1e48 --- /dev/null +++ b/docs/openapi_first/templates/model_app/index.md @@ -0,0 +1,3 @@ +# Model App + +::: openapi_first.templates.model_app diff --git a/docs/openapi_first/templates/model_app/main.md b/docs/openapi_first/templates/model_app/main.md new file mode 100644 index 0000000..1434dc4 --- /dev/null +++ b/docs/openapi_first/templates/model_app/main.md @@ -0,0 +1,3 @@ +# Main + +::: openapi_first.templates.model_app.main diff --git a/docs/openapi_first/templates/model_app/models.md b/docs/openapi_first/templates/model_app/models.md new file mode 100644 index 0000000..ba61897 --- /dev/null +++ b/docs/openapi_first/templates/model_app/models.md @@ -0,0 +1,3 @@ +# Models + +::: openapi_first.templates.model_app.models diff --git a/docs/openapi_first/templates/model_app/routes.md b/docs/openapi_first/templates/model_app/routes.md new file mode 100644 index 0000000..7bc6e3c --- /dev/null +++ b/docs/openapi_first/templates/model_app/routes.md @@ -0,0 +1,3 @@ +# Routes + +::: openapi_first.templates.model_app.routes diff --git a/docs/openapi_first/templates/model_app/test_model_app.md b/docs/openapi_first/templates/model_app/test_model_app.md new file mode 100644 index 0000000..36c6a81 --- /dev/null +++ b/docs/openapi_first/templates/model_app/test_model_app.md @@ -0,0 +1,3 @@ +# Test Model App + +::: openapi_first.templates.model_app.test_model_app diff --git a/mkdocs.yml b/mkdocs.yml index efe989c..6f390d6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -49,10 +49,9 @@ nav: - Templates: - Home: openapi_first/templates/index.md - - Health App: - - Home: openapi_first/templates/health_app/index.md - - App: openapi_first/templates/health_app/main.md - - Routes: openapi_first/templates/health_app/routes.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 diff --git a/openapi_first/templates/crud_app/__init__.py b/openapi_first/templates/crud_app/__init__.py new file mode 100644 index 0000000..c3d6bc3 --- /dev/null +++ b/openapi_first/templates/crud_app/__init__.py @@ -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. +""" diff --git a/openapi_first/templates/crud_app/data.py b/openapi_first/templates/crud_app/data.py index 6966f36..82c5fa8 100644 --- a/openapi_first/templates/crud_app/data.py +++ b/openapi_first/templates/crud_app/data.py @@ -3,27 +3,88 @@ 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 @@ -32,10 +93,40 @@ def create_item(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} _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] diff --git a/openapi_first/templates/crud_app/main.py b/openapi_first/templates/crud_app/main.py index cf8cdc8..f07cc9c 100644 --- a/openapi_first/templates/crud_app/main.py +++ b/openapi_first/templates/crud_app/main.py @@ -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 import routes diff --git a/openapi_first/templates/crud_app/routes.py b/openapi_first/templates/crud_app/routes.py index 257255e..2ffeede 100644 --- a/openapi_first/templates/crud_app/routes.py +++ b/openapi_first/templates/crud_app/routes.py @@ -3,6 +3,19 @@ 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 @@ -17,10 +30,42 @@ from data import ( 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: @@ -28,12 +73,53 @@ def get_item(item_id: int): 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: @@ -41,10 +127,32 @@ def update_item(item_id: int, payload: dict): 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 diff --git a/openapi_first/templates/crud_app/test_crud_app.py b/openapi_first/templates/crud_app/test_crud_app.py index e8c81dc..32439ab 100644 --- a/openapi_first/templates/crud_app/test_crud_app.py +++ b/openapi_first/templates/crud_app/test_crud_app.py @@ -3,11 +3,20 @@ 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 diff --git a/openapi_first/templates/model_app/__init__.py b/openapi_first/templates/model_app/__init__.py new file mode 100644 index 0000000..d6c56d8 --- /dev/null +++ b/openapi_first/templates/model_app/__init__.py @@ -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. +""" diff --git a/openapi_first/templates/model_app/data.py b/openapi_first/templates/model_app/data.py index 51cb18e..fae35b1 100644 --- a/openapi_first/templates/model_app/data.py +++ b/openapi_first/templates/model_app/data.py @@ -2,29 +2,86 @@ 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 @@ -33,6 +90,29 @@ def create_item(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: raise KeyError(item_id) 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: + """ + 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] diff --git a/openapi_first/templates/model_app/main.py b/openapi_first/templates/model_app/main.py index c0b50a2..2cf86ca 100644 --- a/openapi_first/templates/model_app/main.py +++ b/openapi_first/templates/model_app/main.py @@ -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 import routes diff --git a/openapi_first/templates/model_app/models.py b/openapi_first/templates/model_app/models.py index f5b9402..01f4fb0 100644 --- a/openapi_first/templates/model_app/models.py +++ b/openapi_first/templates/model_app/models.py @@ -1,18 +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 diff --git a/openapi_first/templates/model_app/routes.py b/openapi_first/templates/model_app/routes.py index 82d6d23..1ffcdef 100644 --- a/openapi_first/templates/model_app/routes.py +++ b/openapi_first/templates/model_app/routes.py @@ -1,5 +1,17 @@ """ 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 @@ -15,10 +27,42 @@ from data import ( 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: @@ -26,12 +70,53 @@ def get_item(item_id: int): 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: @@ -39,6 +124,29 @@ def update_item(item_id: int, payload: ItemCreate): 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: diff --git a/openapi_first/templates/model_app/test_model_app.py b/openapi_first/templates/model_app/test_model_app.py index 051e136..bc4fa6d 100644 --- a/openapi_first/templates/model_app/test_model_app.py +++ b/openapi_first/templates/model_app/test_model_app.py @@ -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 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