Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion qh/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@
from qh.types import register_type, register_json_type, TypeRegistry

# OpenAPI and client generation (Phase 3)
from qh.openapi import export_openapi, enhance_openapi_schema
from qh.openapi import (
export_openapi,
enhance_openapi_schema,
install_enhanced_openapi,
python_type_to_json_schema,
)
from qh.client import (
mk_client_from_openapi,
mk_client_from_url,
Expand Down Expand Up @@ -117,6 +122,8 @@
# OpenAPI & Client (Phase 3)
"export_openapi",
"enhance_openapi_schema",
"install_enhanced_openapi",
"python_type_to_json_schema",
"mk_client_from_openapi",
"mk_client_from_url",
"mk_client_from_app",
Expand Down
18 changes: 18 additions & 0 deletions qh/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def mk_app(
use_conventions: bool = False,
async_funcs: Optional[List[Union[str, Callable]]] = None,
async_config: Optional[Union[Dict[str, Any], "TaskConfig"]] = None,
enhanced_openapi: bool = True,
**kwargs,
) -> FastAPI:
"""
Expand Down Expand Up @@ -69,6 +70,12 @@ def mk_app(
- TaskConfig object (applies to all async_funcs)
- Dict mapping function names to TaskConfig objects

enhanced_openapi: Whether to serve an enhanced OpenAPI document at
``/openapi.json`` — one with ``requestBody`` / ``responses`` /
``components.schemas`` derived from each function's Python type
hints (see :mod:`qh.openapi`). Defaults to True; the enhancement is
additive and falls back to FastAPI's plain schema if it ever fails.

**kwargs: Additional FastAPI() constructor kwargs (if creating new app)

Returns:
Expand Down Expand Up @@ -254,6 +261,13 @@ def mk_app(
if task_config and getattr(task_config, "create_task_endpoints", True):
add_task_endpoints(app, func.__name__)

# Serve an OpenAPI document with full request/response JSON Schema derived
# from the wrapped functions' Python type hints.
if enhanced_openapi:
from qh.openapi import install_enhanced_openapi

install_enhanced_openapi(app)

return app


Expand All @@ -280,6 +294,10 @@ def inspect_routes(app: FastAPI) -> List[Dict[str, Any]]:
# Include original function if available (for OpenAPI/client generation)
if hasattr(route.endpoint, "_qh_original_func"):
route_info["function"] = route.endpoint._qh_original_func
# Include the resolved param -> TransformSpec map (HTTP-location
# classification) for OpenAPI request/response schema generation.
if hasattr(route.endpoint, "_qh_param_specs"):
route_info["param_specs"] = route.endpoint._qh_param_specs
routes.append(route_info)

return routes
Expand Down
6 changes: 6 additions & 0 deletions qh/endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,12 @@ def task_wrapper(**kwargs):
endpoint.__doc__ = func.__doc__
# Store original function for OpenAPI/client generation
endpoint._qh_original_func = func # type: ignore
# Store the resolved param -> TransformSpec map (the single source of truth
# for which HTTP location each parameter is read from). OpenAPI generation
# (qh.openapi) consumes this to classify body/path/query parameters without
# re-deriving the logic.
endpoint._qh_param_specs = param_specs # type: ignore
endpoint._qh_route_config = route_config # type: ignore

return endpoint

Expand Down
Loading
Loading