Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
71ef915
1
Parent(s): 4a8c120
Refactor array parameter formatting to reduce code duplication
Browse filesExtract common array formatting logic into format_array_parameter utility function.
Addresses automated review feedback about code duplication between path and query parameter handling.
- src/fastmcp/server/openapi.py +10 -68
- src/fastmcp/utilities/openapi.py +54 -0
src/fastmcp/server/openapi.py
CHANGED
|
@@ -27,6 +27,7 @@ from fastmcp.utilities.logging import get_logger
|
|
| 27 |
from fastmcp.utilities.openapi import (
|
| 28 |
HTTPRoute,
|
| 29 |
_combine_schemas,
|
|
|
|
| 30 |
format_description_with_responses,
|
| 31 |
)
|
| 32 |
|
|
@@ -296,46 +297,10 @@ class OpenAPITool(Tool):
|
|
| 296 |
if is_array:
|
| 297 |
# Format array values as comma-separated string
|
| 298 |
# This follows the OpenAPI 'simple' style (default for path)
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
)
|
| 303 |
-
# Handle simple array types
|
| 304 |
-
path = path.replace(
|
| 305 |
-
f"{{{param_name}}}", ",".join(str(v) for v in param_value)
|
| 306 |
-
)
|
| 307 |
-
else:
|
| 308 |
-
# Handle complex array types (containing objects/dicts)
|
| 309 |
-
try:
|
| 310 |
-
# Try to create a simple representation without Python syntax artifacts
|
| 311 |
-
formatted_parts = []
|
| 312 |
-
for item in param_value:
|
| 313 |
-
if isinstance(item, dict):
|
| 314 |
-
# For objects, serialize key-value pairs
|
| 315 |
-
item_parts = []
|
| 316 |
-
for k, v in item.items():
|
| 317 |
-
item_parts.append(f"{k}:{v}")
|
| 318 |
-
formatted_parts.append(".".join(item_parts))
|
| 319 |
-
else:
|
| 320 |
-
# Fallback for other complex types
|
| 321 |
-
formatted_parts.append(str(item))
|
| 322 |
-
|
| 323 |
-
# Join parts with commas
|
| 324 |
-
formatted_value = ",".join(formatted_parts)
|
| 325 |
-
path = path.replace(f"{{{param_name}}}", formatted_value)
|
| 326 |
-
except Exception as e:
|
| 327 |
-
logger.warning(
|
| 328 |
-
f"Failed to format complex array path parameter '{param_name}': {e}"
|
| 329 |
-
)
|
| 330 |
-
# Fallback to string representation, but remove Python syntax artifacts
|
| 331 |
-
str_value = (
|
| 332 |
-
str(param_value)
|
| 333 |
-
.replace("[", "")
|
| 334 |
-
.replace("]", "")
|
| 335 |
-
.replace("'", "")
|
| 336 |
-
.replace('"', "")
|
| 337 |
-
)
|
| 338 |
-
path = path.replace(f"{{{param_name}}}", str_value)
|
| 339 |
continue
|
| 340 |
|
| 341 |
# Default handling for non-array parameters or non-array schemas
|
|
@@ -365,34 +330,11 @@ class OpenAPITool(Tool):
|
|
| 365 |
# as multiple parameters with the same name
|
| 366 |
query_params[p.name] = param_value
|
| 367 |
else:
|
| 368 |
-
#
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
query_params[p.name] = ",".join(str(v) for v in param_value)
|
| 374 |
-
else:
|
| 375 |
-
# For complex types, try to create a simpler representation
|
| 376 |
-
try:
|
| 377 |
-
# Try to create a simple string representation
|
| 378 |
-
formatted_parts = []
|
| 379 |
-
for item in param_value:
|
| 380 |
-
if isinstance(item, dict):
|
| 381 |
-
# For objects, serialize key-value pairs
|
| 382 |
-
item_parts = []
|
| 383 |
-
for k, v in item.items():
|
| 384 |
-
item_parts.append(f"{k}:{v}")
|
| 385 |
-
formatted_parts.append(".".join(item_parts))
|
| 386 |
-
else:
|
| 387 |
-
formatted_parts.append(str(item))
|
| 388 |
-
|
| 389 |
-
query_params[p.name] = ",".join(formatted_parts)
|
| 390 |
-
except Exception as e:
|
| 391 |
-
logger.warning(
|
| 392 |
-
f"Failed to format complex array query parameter '{p.name}': {e}"
|
| 393 |
-
)
|
| 394 |
-
# Fallback to string representation
|
| 395 |
-
query_params[p.name] = param_value
|
| 396 |
else:
|
| 397 |
# Non-array parameters are passed as is
|
| 398 |
query_params[p.name] = param_value
|
|
|
|
| 27 |
from fastmcp.utilities.openapi import (
|
| 28 |
HTTPRoute,
|
| 29 |
_combine_schemas,
|
| 30 |
+
format_array_parameter,
|
| 31 |
format_description_with_responses,
|
| 32 |
)
|
| 33 |
|
|
|
|
| 297 |
if is_array:
|
| 298 |
# Format array values as comma-separated string
|
| 299 |
# This follows the OpenAPI 'simple' style (default for path)
|
| 300 |
+
formatted_value = format_array_parameter(
|
| 301 |
+
param_value, param_name, is_query_parameter=False
|
| 302 |
+
)
|
| 303 |
+
path = path.replace(f"{{{param_name}}}", str(formatted_value))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
continue
|
| 305 |
|
| 306 |
# Default handling for non-array parameters or non-array schemas
|
|
|
|
| 330 |
# as multiple parameters with the same name
|
| 331 |
query_params[p.name] = param_value
|
| 332 |
else:
|
| 333 |
+
# Format array as comma-separated string when explode=False
|
| 334 |
+
formatted_value = format_array_parameter(
|
| 335 |
+
param_value, p.name, is_query_parameter=True
|
| 336 |
+
)
|
| 337 |
+
query_params[p.name] = formatted_value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
else:
|
| 339 |
# Non-array parameters are passed as is
|
| 340 |
query_params[p.name] = param_value
|
src/fastmcp/utilities/openapi.py
CHANGED
|
@@ -39,6 +39,60 @@ ParameterLocation = Literal["path", "query", "header", "cookie"]
|
|
| 39 |
JsonSchema = dict[str, Any]
|
| 40 |
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
class ParameterInfo(FastMCPBaseModel):
|
| 43 |
"""Represents a single parameter for an HTTP operation in our IR."""
|
| 44 |
|
|
|
|
| 39 |
JsonSchema = dict[str, Any]
|
| 40 |
|
| 41 |
|
| 42 |
+
def format_array_parameter(
|
| 43 |
+
values: list, parameter_name: str, is_query_parameter: bool = False
|
| 44 |
+
) -> str | list:
|
| 45 |
+
"""
|
| 46 |
+
Format an array parameter according to OpenAPI specifications.
|
| 47 |
+
|
| 48 |
+
Args:
|
| 49 |
+
values: List of values to format
|
| 50 |
+
parameter_name: Name of the parameter (for error messages)
|
| 51 |
+
is_query_parameter: If True, can return list for explode=True behavior
|
| 52 |
+
|
| 53 |
+
Returns:
|
| 54 |
+
String (comma-separated) or list (for query params with explode=True)
|
| 55 |
+
"""
|
| 56 |
+
# For arrays of simple types (strings, numbers, etc.), join with commas
|
| 57 |
+
if all(isinstance(item, str | int | float | bool) for item in values):
|
| 58 |
+
return ",".join(str(v) for v in values)
|
| 59 |
+
|
| 60 |
+
# For complex types, try to create a simpler representation
|
| 61 |
+
try:
|
| 62 |
+
# Try to create a simple string representation
|
| 63 |
+
formatted_parts = []
|
| 64 |
+
for item in values:
|
| 65 |
+
if isinstance(item, dict):
|
| 66 |
+
# For objects, serialize key-value pairs
|
| 67 |
+
item_parts = []
|
| 68 |
+
for k, v in item.items():
|
| 69 |
+
item_parts.append(f"{k}:{v}")
|
| 70 |
+
formatted_parts.append(".".join(item_parts))
|
| 71 |
+
else:
|
| 72 |
+
formatted_parts.append(str(item))
|
| 73 |
+
|
| 74 |
+
return ",".join(formatted_parts)
|
| 75 |
+
except Exception as e:
|
| 76 |
+
param_type = "query" if is_query_parameter else "path"
|
| 77 |
+
logger.warning(
|
| 78 |
+
f"Failed to format complex array {param_type} parameter '{parameter_name}': {e}"
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
if is_query_parameter:
|
| 82 |
+
# For query parameters, fallback to original list
|
| 83 |
+
return values
|
| 84 |
+
else:
|
| 85 |
+
# For path parameters, fallback to string representation without Python syntax
|
| 86 |
+
str_value = (
|
| 87 |
+
str(values)
|
| 88 |
+
.replace("[", "")
|
| 89 |
+
.replace("]", "")
|
| 90 |
+
.replace("'", "")
|
| 91 |
+
.replace('"', "")
|
| 92 |
+
)
|
| 93 |
+
return str_value
|
| 94 |
+
|
| 95 |
+
|
| 96 |
class ParameterInfo(FastMCPBaseModel):
|
| 97 |
"""Represents a single parameter for an HTTP operation in our IR."""
|
| 98 |
|