mirror of
https://github.com/meta-llama/llama-stack.git
synced 2025-12-04 10:10:36 +00:00
wip2
Signed-off-by: Sébastien Han <seb@redhat.com>
This commit is contained in:
parent
38de8ea1f7
commit
357be98279
10 changed files with 53466 additions and 3369 deletions
File diff suppressed because it is too large
Load diff
18640
docs/static/deprecated-llama-stack-spec.json
vendored
Normal file
18640
docs/static/deprecated-llama-stack-spec.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
4
docs/static/deprecated-llama-stack-spec.yaml
vendored
4
docs/static/deprecated-llama-stack-spec.yaml
vendored
|
|
@ -68,9 +68,9 @@ components:
|
||||||
example:
|
example:
|
||||||
status: 500
|
status: 500
|
||||||
title: Internal Server Error
|
title: Internal Server Error
|
||||||
detail: An unexpected error occurred
|
detail: An unexpected error occurred. Our team has been notified.
|
||||||
DefaultError:
|
DefaultError:
|
||||||
description: An error occurred
|
description: An unexpected error occurred
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
|
|
|
||||||
5135
docs/static/experimental-llama-stack-spec.json
vendored
Normal file
5135
docs/static/experimental-llama-stack-spec.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
913
docs/static/experimental-llama-stack-spec.yaml
vendored
913
docs/static/experimental-llama-stack-spec.yaml
vendored
File diff suppressed because it is too large
Load diff
11541
docs/static/llama-stack-spec.json
vendored
Normal file
11541
docs/static/llama-stack-spec.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
2074
docs/static/llama-stack-spec.yaml
vendored
2074
docs/static/llama-stack-spec.yaml
vendored
File diff suppressed because it is too large
Load diff
16303
docs/static/stainless-llama-stack-spec.json
vendored
Normal file
16303
docs/static/stainless-llama-stack-spec.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
956
docs/static/stainless-llama-stack-spec.yaml
vendored
956
docs/static/stainless-llama-stack-spec.yaml
vendored
File diff suppressed because it is too large
Load diff
|
|
@ -29,6 +29,9 @@ from llama_stack.core.server.routes import get_all_api_routes
|
||||||
# Global list to store dynamic models created during endpoint generation
|
# Global list to store dynamic models created during endpoint generation
|
||||||
_dynamic_models = []
|
_dynamic_models = []
|
||||||
|
|
||||||
|
# Global mapping from (path, method) to webmethod for parameter description extraction
|
||||||
|
_path_webmethod_map: dict[tuple[str, str], Any] = {}
|
||||||
|
|
||||||
|
|
||||||
def _get_all_api_routes_with_functions():
|
def _get_all_api_routes_with_functions():
|
||||||
"""
|
"""
|
||||||
|
|
@ -107,18 +110,24 @@ def create_llama_stack_app() -> FastAPI:
|
||||||
# Create FastAPI routes from the discovered routes
|
# Create FastAPI routes from the discovered routes
|
||||||
for _, routes in api_routes.items():
|
for _, routes in api_routes.items():
|
||||||
for route, webmethod in routes:
|
for route, webmethod in routes:
|
||||||
|
# Store mapping for later use in parameter description extraction
|
||||||
|
for method in route.methods:
|
||||||
|
_path_webmethod_map[(route.path, method.lower())] = webmethod
|
||||||
# Convert the route to a FastAPI endpoint
|
# Convert the route to a FastAPI endpoint
|
||||||
_create_fastapi_endpoint(app, route, webmethod)
|
_create_fastapi_endpoint(app, route, webmethod)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
def _extract_path_parameters(path: str) -> list[dict[str, Any]]:
|
def _extract_path_parameters(path: str, webmethod=None) -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Extract path parameters from a URL path and return them as OpenAPI parameter definitions.
|
Extract path parameters from a URL path and return them as OpenAPI parameter definitions.
|
||||||
|
Parameters are returned in the order they appear in the docstring if available,
|
||||||
|
otherwise in the order they appear in the path.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
path: URL path with parameters like /v1/batches/{batch_id}/cancel
|
path: URL path with parameters like /v1/batches/{batch_id}/cancel
|
||||||
|
webmethod: Optional webmethod to extract parameter descriptions from docstring
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of parameter definitions for OpenAPI
|
List of parameter definitions for OpenAPI
|
||||||
|
|
@ -127,17 +136,60 @@ def _extract_path_parameters(path: str) -> list[dict[str, Any]]:
|
||||||
|
|
||||||
# Find all path parameters in the format {param} or {param:type}
|
# Find all path parameters in the format {param} or {param:type}
|
||||||
param_pattern = r"\{([^}:]+)(?::[^}]+)?\}"
|
param_pattern = r"\{([^}:]+)(?::[^}]+)?\}"
|
||||||
matches = re.findall(param_pattern, path)
|
path_params = set(re.findall(param_pattern, path))
|
||||||
|
|
||||||
|
# Extract parameter descriptions and order from docstring if available
|
||||||
|
param_descriptions = {}
|
||||||
|
docstring_param_order = []
|
||||||
|
if webmethod:
|
||||||
|
func = getattr(webmethod, "func", None)
|
||||||
|
if func and func.__doc__:
|
||||||
|
docstring = func.__doc__
|
||||||
|
lines = docstring.split("\n")
|
||||||
|
for line in lines:
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith(":param "):
|
||||||
|
# Extract parameter name and description
|
||||||
|
# Format: :param param_name: description
|
||||||
|
parts = line[7:].split(":", 1)
|
||||||
|
if len(parts) == 2:
|
||||||
|
param_name = parts[0].strip()
|
||||||
|
description = parts[1].strip()
|
||||||
|
# Only track path parameters that exist in the path
|
||||||
|
if param_name in path_params:
|
||||||
|
if description:
|
||||||
|
param_descriptions[param_name] = description
|
||||||
|
if param_name not in docstring_param_order:
|
||||||
|
docstring_param_order.append(param_name)
|
||||||
|
|
||||||
|
# Build parameters list preserving docstring order for path parameters found in docstring,
|
||||||
|
# then add any remaining path parameters in path order
|
||||||
parameters = []
|
parameters = []
|
||||||
for param_name in matches:
|
# First add parameters in docstring order
|
||||||
|
for param_name in docstring_param_order:
|
||||||
|
if param_name in path_params:
|
||||||
|
description = param_descriptions.get(param_name, f"Path parameter: {param_name}")
|
||||||
parameters.append(
|
parameters.append(
|
||||||
{
|
{
|
||||||
"name": param_name,
|
"name": param_name,
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": True,
|
"required": True,
|
||||||
"schema": {"type": "string"},
|
"schema": {"type": "string"},
|
||||||
"description": f"Path parameter: {param_name}",
|
"description": description,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# Then add any path parameters not in docstring, in path order
|
||||||
|
path_param_list = re.findall(param_pattern, path)
|
||||||
|
for param_name in path_param_list:
|
||||||
|
if param_name not in docstring_param_order:
|
||||||
|
description = param_descriptions.get(param_name, f"Path parameter: {param_name}")
|
||||||
|
parameters.append(
|
||||||
|
{
|
||||||
|
"name": param_name,
|
||||||
|
"in": "path",
|
||||||
|
"required": True,
|
||||||
|
"schema": {"type": "string"},
|
||||||
|
"description": description,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -166,7 +218,8 @@ def _create_fastapi_endpoint(app: FastAPI, route, webmethod):
|
||||||
f"Debug: {webmethod.route} - request_model: {request_model}, response_model: {response_model}, query_parameters: {query_parameters}"
|
f"Debug: {webmethod.route} - request_model: {request_model}, response_model: {response_model}, query_parameters: {query_parameters}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Extract response description from webmethod docstring (always try this first)
|
# Extract summary and response description from webmethod docstring
|
||||||
|
summary = _extract_summary_from_docstring(webmethod)
|
||||||
response_description = _extract_response_description_from_docstring(webmethod, response_model)
|
response_description = _extract_response_description_from_docstring(webmethod, response_model)
|
||||||
|
|
||||||
# Create endpoint function with proper typing
|
# Create endpoint function with proper typing
|
||||||
|
|
@ -316,6 +369,9 @@ def _create_fastapi_endpoint(app: FastAPI, route, webmethod):
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if summary:
|
||||||
|
route_kwargs["summary"] = summary
|
||||||
|
|
||||||
for method in methods:
|
for method in methods:
|
||||||
if method.upper() == "GET":
|
if method.upper() == "GET":
|
||||||
app.get(fastapi_path, **route_kwargs)(endpoint_func)
|
app.get(fastapi_path, **route_kwargs)(endpoint_func)
|
||||||
|
|
@ -329,32 +385,51 @@ def _create_fastapi_endpoint(app: FastAPI, route, webmethod):
|
||||||
app.patch(fastapi_path, **route_kwargs)(endpoint_func)
|
app.patch(fastapi_path, **route_kwargs)(endpoint_func)
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_summary_from_docstring(webmethod) -> str | None:
|
||||||
|
"""
|
||||||
|
Extract summary from the actual function docstring.
|
||||||
|
The summary is typically the first non-empty line of the docstring,
|
||||||
|
before any :param:, :returns:, or other docstring field markers.
|
||||||
|
"""
|
||||||
|
func = getattr(webmethod, "func", None)
|
||||||
|
if not func:
|
||||||
|
return None
|
||||||
|
|
||||||
|
docstring = func.__doc__ or ""
|
||||||
|
if not docstring:
|
||||||
|
return None
|
||||||
|
|
||||||
|
lines = docstring.split("\n")
|
||||||
|
for line in lines:
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
if line.startswith(":param:") or line.startswith(":returns:") or line.startswith(":raises:"):
|
||||||
|
break
|
||||||
|
return line
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _extract_response_description_from_docstring(webmethod, response_model) -> str:
|
def _extract_response_description_from_docstring(webmethod, response_model) -> str:
|
||||||
"""
|
"""
|
||||||
Extract response description from the actual function docstring.
|
Extract response description from the actual function docstring.
|
||||||
Looks for :returns: in the docstring and uses that as the description.
|
Looks for :returns: in the docstring and uses that as the description.
|
||||||
"""
|
"""
|
||||||
# Try to get the actual function from the webmethod
|
|
||||||
# The webmethod should have a reference to the original function
|
|
||||||
func = getattr(webmethod, "func", None)
|
func = getattr(webmethod, "func", None)
|
||||||
if not func:
|
if not func:
|
||||||
# If we can't get the function, return a generic description
|
|
||||||
return "Successful Response"
|
return "Successful Response"
|
||||||
|
|
||||||
# Get the function's docstring
|
|
||||||
docstring = func.__doc__ or ""
|
docstring = func.__doc__ or ""
|
||||||
|
|
||||||
# Look for :returns: line in the docstring
|
|
||||||
lines = docstring.split("\n")
|
lines = docstring.split("\n")
|
||||||
for line in lines:
|
for line in lines:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if line.startswith(":returns:"):
|
if line.startswith(":returns:"):
|
||||||
# Extract the description after :returns:
|
description = line[9:].strip()
|
||||||
description = line[9:].strip() # Remove ':returns:' prefix
|
|
||||||
if description:
|
if description:
|
||||||
return description
|
return description
|
||||||
|
|
||||||
# If no :returns: found, return a generic description
|
|
||||||
return "Successful Response"
|
return "Successful Response"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -842,7 +917,11 @@ def _add_error_responses(openapi_schema: dict[str, Any]) -> dict[str, Any]:
|
||||||
500: {
|
500: {
|
||||||
"name": "InternalServerError500",
|
"name": "InternalServerError500",
|
||||||
"description": "The server encountered an unexpected error",
|
"description": "The server encountered an unexpected error",
|
||||||
"example": {"status": 500, "title": "Internal Server Error", "detail": "An unexpected error occurred"},
|
"example": {
|
||||||
|
"status": 500,
|
||||||
|
"title": "Internal Server Error",
|
||||||
|
"detail": "An unexpected error occurred. Our team has been notified.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -858,7 +937,7 @@ def _add_error_responses(openapi_schema: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
|
||||||
# Add a default error response
|
# Add a default error response
|
||||||
openapi_schema["components"]["responses"]["DefaultError"] = {
|
openapi_schema["components"]["responses"]["DefaultError"] = {
|
||||||
"description": "An error occurred",
|
"description": "An unexpected error occurred",
|
||||||
"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}},
|
"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -868,29 +947,112 @@ def _add_error_responses(openapi_schema: dict[str, Any]) -> dict[str, Any]:
|
||||||
def _fix_path_parameters(openapi_schema: dict[str, Any]) -> dict[str, Any]:
|
def _fix_path_parameters(openapi_schema: dict[str, Any]) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Fix path parameter resolution issues by adding explicit parameter definitions.
|
Fix path parameter resolution issues by adding explicit parameter definitions.
|
||||||
|
Uses docstring descriptions if available.
|
||||||
"""
|
"""
|
||||||
|
global _path_webmethod_map
|
||||||
|
|
||||||
if "paths" not in openapi_schema:
|
if "paths" not in openapi_schema:
|
||||||
return openapi_schema
|
return openapi_schema
|
||||||
|
|
||||||
for path, path_item in openapi_schema["paths"].items():
|
for path, path_item in openapi_schema["paths"].items():
|
||||||
# Extract path parameters from the URL
|
|
||||||
path_params = _extract_path_parameters(path)
|
|
||||||
|
|
||||||
if not path_params:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Add parameters to each operation in this path
|
# Add parameters to each operation in this path
|
||||||
for method in ["get", "post", "put", "delete", "patch", "head", "options"]:
|
for method in ["get", "post", "put", "delete", "patch", "head", "options"]:
|
||||||
if method in path_item and isinstance(path_item[method], dict):
|
if method in path_item and isinstance(path_item[method], dict):
|
||||||
operation = path_item[method]
|
operation = path_item[method]
|
||||||
|
|
||||||
|
# Get webmethod for this path/method to extract parameter descriptions
|
||||||
|
webmethod = _path_webmethod_map.get((path, method))
|
||||||
|
|
||||||
|
# Extract path parameters from the URL with descriptions from docstring
|
||||||
|
path_params = _extract_path_parameters(path, webmethod)
|
||||||
|
|
||||||
|
if not path_params:
|
||||||
|
continue
|
||||||
|
|
||||||
if "parameters" not in operation:
|
if "parameters" not in operation:
|
||||||
operation["parameters"] = []
|
operation["parameters"] = []
|
||||||
|
|
||||||
# Add path parameters that aren't already defined
|
# Separate path and non-path parameters
|
||||||
existing_param_names = {p.get("name") for p in operation["parameters"] if p.get("in") == "path"}
|
existing_params = operation["parameters"]
|
||||||
|
non_path_params = [p for p in existing_params if p.get("in") != "path"]
|
||||||
|
existing_path_params = {p.get("name"): p for p in existing_params if p.get("in") == "path"}
|
||||||
|
|
||||||
|
# Build new parameters list: non-path params first, then path params in docstring order
|
||||||
|
new_params = non_path_params.copy()
|
||||||
|
|
||||||
|
# Add path parameters in docstring order
|
||||||
for param in path_params:
|
for param in path_params:
|
||||||
if param["name"] not in existing_param_names:
|
param_name = param["name"]
|
||||||
operation["parameters"].append(param)
|
if param_name in existing_path_params:
|
||||||
|
# Update existing parameter description if we have a better one
|
||||||
|
existing_param = existing_path_params[param_name]
|
||||||
|
if param["description"] != f"Path parameter: {param_name}":
|
||||||
|
existing_param["description"] = param["description"]
|
||||||
|
new_params.append(existing_param)
|
||||||
|
else:
|
||||||
|
# Add new path parameter
|
||||||
|
new_params.append(param)
|
||||||
|
|
||||||
|
operation["parameters"] = new_params
|
||||||
|
|
||||||
|
return openapi_schema
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_first_line_from_description(description: str) -> str:
|
||||||
|
"""
|
||||||
|
Extract all lines from a description string that don't start with docstring keywords.
|
||||||
|
Stops at the first line that starts with :param:, :returns:, :raises:, etc.
|
||||||
|
Preserves multiple lines and formatting.
|
||||||
|
"""
|
||||||
|
if not description:
|
||||||
|
return description
|
||||||
|
|
||||||
|
lines = description.split("\n")
|
||||||
|
description_lines = []
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
stripped = line.strip()
|
||||||
|
if not stripped:
|
||||||
|
# Keep empty lines in the description to preserve formatting
|
||||||
|
description_lines.append(line)
|
||||||
|
continue
|
||||||
|
if (
|
||||||
|
stripped.startswith(":param")
|
||||||
|
or stripped.startswith(":returns")
|
||||||
|
or stripped.startswith(":raises")
|
||||||
|
or (stripped.startswith(":") and len(stripped) > 1 and stripped[1].isalpha())
|
||||||
|
):
|
||||||
|
break
|
||||||
|
description_lines.append(line)
|
||||||
|
|
||||||
|
# Join lines and strip trailing whitespace/newlines
|
||||||
|
result = "\n".join(description_lines).rstrip()
|
||||||
|
return result if result else description
|
||||||
|
|
||||||
|
|
||||||
|
def _fix_component_descriptions(openapi_schema: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Fix component descriptions to only include the first line (summary),
|
||||||
|
removing :param:, :returns:, and other docstring directives.
|
||||||
|
"""
|
||||||
|
if "components" not in openapi_schema or "schemas" not in openapi_schema["components"]:
|
||||||
|
return openapi_schema
|
||||||
|
|
||||||
|
schemas = openapi_schema["components"]["schemas"]
|
||||||
|
|
||||||
|
def fix_description_in_schema(schema_def: dict[str, Any]) -> None:
|
||||||
|
if isinstance(schema_def, dict):
|
||||||
|
if "description" in schema_def and isinstance(schema_def["description"], str):
|
||||||
|
schema_def["description"] = _extract_first_line_from_description(schema_def["description"])
|
||||||
|
|
||||||
|
for value in schema_def.values():
|
||||||
|
fix_description_in_schema(value)
|
||||||
|
elif isinstance(schema_def, list):
|
||||||
|
for item in schema_def:
|
||||||
|
fix_description_in_schema(item)
|
||||||
|
|
||||||
|
for _, schema_def in schemas.items():
|
||||||
|
fix_description_in_schema(schema_def)
|
||||||
|
|
||||||
return openapi_schema
|
return openapi_schema
|
||||||
|
|
||||||
|
|
@ -1409,6 +1571,9 @@ def generate_openapi_spec(output_dir: str, format: str = "yaml", include_example
|
||||||
# Eliminate $defs section entirely for oasdiff compatibility
|
# Eliminate $defs section entirely for oasdiff compatibility
|
||||||
openapi_schema = _eliminate_defs_section(openapi_schema)
|
openapi_schema = _eliminate_defs_section(openapi_schema)
|
||||||
|
|
||||||
|
# Fix component descriptions to only include first line (summary)
|
||||||
|
openapi_schema = _fix_component_descriptions(openapi_schema)
|
||||||
|
|
||||||
# Debug: Check if there's a root-level $defs after flattening
|
# Debug: Check if there's a root-level $defs after flattening
|
||||||
if "$defs" in openapi_schema:
|
if "$defs" in openapi_schema:
|
||||||
print(f"After flattening: root-level $defs with {len(openapi_schema['$defs'])} items")
|
print(f"After flattening: root-level $defs with {len(openapi_schema['$defs'])} items")
|
||||||
|
|
@ -1485,7 +1650,7 @@ def generate_openapi_spec(output_dir: str, format: str = "yaml", include_example
|
||||||
if format in ["yaml", "both"]:
|
if format in ["yaml", "both"]:
|
||||||
yaml_path = output_path / "llama-stack-spec.yaml"
|
yaml_path = output_path / "llama-stack-spec.yaml"
|
||||||
|
|
||||||
# Use ruamel.yaml for better control over YAML serialization
|
# Use ruamel.yaml for better YAML formatting
|
||||||
try:
|
try:
|
||||||
from ruamel.yaml import YAML
|
from ruamel.yaml import YAML
|
||||||
|
|
||||||
|
|
@ -1497,11 +1662,9 @@ def generate_openapi_spec(output_dir: str, format: str = "yaml", include_example
|
||||||
|
|
||||||
with open(yaml_path, "w") as f:
|
with open(yaml_path, "w") as f:
|
||||||
yaml_writer.dump(stable_schema, f)
|
yaml_writer.dump(stable_schema, f)
|
||||||
except ImportError:
|
|
||||||
# Fallback to standard yaml if ruamel.yaml is not available
|
|
||||||
with open(yaml_path, "w") as f:
|
|
||||||
yaml.dump(stable_schema, f, default_flow_style=False, sort_keys=False)
|
|
||||||
# Post-process the YAML file to remove $defs section and fix references
|
# Post-process the YAML file to remove $defs section and fix references
|
||||||
|
# Re-read and re-write with ruamel.yaml
|
||||||
with open(yaml_path) as f:
|
with open(yaml_path) as f:
|
||||||
yaml_content = f.read()
|
yaml_content = f.read()
|
||||||
|
|
||||||
|
|
@ -1514,8 +1677,12 @@ def generate_openapi_spec(output_dir: str, format: str = "yaml", include_example
|
||||||
yaml_content = yaml_content.replace("#/$defs/", "#/components/schemas/")
|
yaml_content = yaml_content.replace("#/$defs/", "#/components/schemas/")
|
||||||
print(f"Fixed {refs_fixed} $ref references using string replacement")
|
print(f"Fixed {refs_fixed} $ref references using string replacement")
|
||||||
|
|
||||||
# Parse the YAML content
|
# Parse using PyYAML safe_load first to avoid issues with custom types
|
||||||
yaml_data = yaml.safe_load(yaml_content)
|
# This handles block scalars better during post-processing
|
||||||
|
import yaml as pyyaml
|
||||||
|
|
||||||
|
with open(yaml_path) as f:
|
||||||
|
yaml_data = pyyaml.safe_load(f)
|
||||||
|
|
||||||
# Move $defs to components/schemas if it exists
|
# Move $defs to components/schemas if it exists
|
||||||
if "$defs" in yaml_data:
|
if "$defs" in yaml_data:
|
||||||
|
|
@ -1533,10 +1700,14 @@ def generate_openapi_spec(output_dir: str, format: str = "yaml", include_example
|
||||||
del yaml_data["$defs"]
|
del yaml_data["$defs"]
|
||||||
print("Moved $defs to components/schemas")
|
print("Moved $defs to components/schemas")
|
||||||
|
|
||||||
# Write the modified YAML back
|
# Write the modified YAML back with ruamel.yaml
|
||||||
with open(yaml_path, "w") as f:
|
with open(yaml_path, "w") as f:
|
||||||
yaml.dump(yaml_data, f, default_flow_style=False, sort_keys=False)
|
yaml_writer.dump(yaml_data, f)
|
||||||
print("Updated YAML file")
|
print("Updated YAML file")
|
||||||
|
except ImportError:
|
||||||
|
# Fallback to standard yaml if ruamel.yaml is not available
|
||||||
|
with open(yaml_path, "w") as f:
|
||||||
|
yaml.dump(stable_schema, f, default_flow_style=False, sort_keys=False)
|
||||||
|
|
||||||
print(f"✅ Generated YAML (stable): {yaml_path}")
|
print(f"✅ Generated YAML (stable): {yaml_path}")
|
||||||
|
|
||||||
|
|
@ -1643,7 +1814,7 @@ def main():
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Generate OpenAPI specification using FastAPI")
|
parser = argparse.ArgumentParser(description="Generate OpenAPI specification using FastAPI")
|
||||||
parser.add_argument("output_dir", help="Output directory for generated files")
|
parser.add_argument("output_dir", help="Output directory for generated files")
|
||||||
parser.add_argument("--format", choices=["yaml", "json", "both"], default="yaml", help="Output format")
|
parser.add_argument("--format", choices=["yaml", "json", "both"], default="both", help="Output format")
|
||||||
parser.add_argument("--no-examples", action="store_true", help="Exclude examples from the specification")
|
parser.add_argument("--no-examples", action="store_true", help="Exclude examples from the specification")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--validate-only", action="store_true", help="Only validate existing schema files, don't generate new ones"
|
"--validate-only", action="store_true", help="Only validate existing schema files, don't generate new ones"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue