From ff0c513611ea313bf36babab368609d479f59dab Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Tue, 1 Jul 2025 19:15:24 -0400 Subject: [PATCH] Refactor array parameter formatting to reduce code duplication Extract 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 | 78 ++++---------------------------- src/fastmcp/utilities/openapi.py | 54 ++++++++++++++++++++++ 2 files changed, 64 insertions(+), 68 deletions(-) diff --git a/src/fastmcp/server/openapi.py b/src/fastmcp/server/openapi.py index b839e7481..60e31c1a7 100644 --- a/src/fastmcp/server/openapi.py +++ b/src/fastmcp/server/openapi.py @@ -27,6 +27,7 @@ from fastmcp.utilities.logging import get_logger from fastmcp.utilities.openapi import ( HTTPRoute, _combine_schemas, + format_array_parameter, format_description_with_responses, ) @@ -296,46 +297,10 @@ class OpenAPITool(Tool): if is_array: # Format array values as comma-separated string # This follows the OpenAPI 'simple' style (default for path) - if all( - isinstance(item, str | int | float | bool) - for item in param_value - ): - # Handle simple array types - path = path.replace( - f"{{{param_name}}}", ",".join(str(v) for v in param_value) - ) - else: - # Handle complex array types (containing objects/dicts) - try: - # Try to create a simple representation without Python syntax artifacts - formatted_parts = [] - for item in param_value: - if isinstance(item, dict): - # For objects, serialize key-value pairs - item_parts = [] - for k, v in item.items(): - item_parts.append(f"{k}:{v}") - formatted_parts.append(".".join(item_parts)) - else: - # Fallback for other complex types - formatted_parts.append(str(item)) - - # Join parts with commas - formatted_value = ",".join(formatted_parts) - path = path.replace(f"{{{param_name}}}", formatted_value) - except Exception as e: - logger.warning( - f"Failed to format complex array path parameter '{param_name}': {e}" - ) - # Fallback to string representation, but remove Python syntax artifacts - str_value = ( - str(param_value) - .replace("[", "") - .replace("]", "") - .replace("'", "") - .replace('"', "") - ) - path = path.replace(f"{{{param_name}}}", str_value) + formatted_value = format_array_parameter( + param_value, param_name, is_query_parameter=False + ) + path = path.replace(f"{{{param_name}}}", str(formatted_value)) continue # Default handling for non-array parameters or non-array schemas @@ -365,34 +330,11 @@ class OpenAPITool(Tool): # as multiple parameters with the same name query_params[p.name] = param_value else: - # For arrays of simple types (strings, numbers, etc.), join with commas - if all( - isinstance(item, str | int | float | bool) - for item in param_value - ): - query_params[p.name] = ",".join(str(v) for v in param_value) - else: - # For complex types, try to create a simpler representation - try: - # Try to create a simple string representation - formatted_parts = [] - for item in param_value: - if isinstance(item, dict): - # For objects, serialize key-value pairs - item_parts = [] - for k, v in item.items(): - item_parts.append(f"{k}:{v}") - formatted_parts.append(".".join(item_parts)) - else: - formatted_parts.append(str(item)) - - query_params[p.name] = ",".join(formatted_parts) - except Exception as e: - logger.warning( - f"Failed to format complex array query parameter '{p.name}': {e}" - ) - # Fallback to string representation - query_params[p.name] = param_value + # Format array as comma-separated string when explode=False + formatted_value = format_array_parameter( + param_value, p.name, is_query_parameter=True + ) + query_params[p.name] = formatted_value else: # Non-array parameters are passed as is query_params[p.name] = param_value diff --git a/src/fastmcp/utilities/openapi.py b/src/fastmcp/utilities/openapi.py index 076fdb0d4..366706065 100644 --- a/src/fastmcp/utilities/openapi.py +++ b/src/fastmcp/utilities/openapi.py @@ -39,6 +39,60 @@ ParameterLocation = Literal["path", "query", "header", "cookie"] JsonSchema = dict[str, Any] +def format_array_parameter( + values: list, parameter_name: str, is_query_parameter: bool = False +) -> str | list: + """ + Format an array parameter according to OpenAPI specifications. + + Args: + values: List of values to format + parameter_name: Name of the parameter (for error messages) + is_query_parameter: If True, can return list for explode=True behavior + + Returns: + String (comma-separated) or list (for query params with explode=True) + """ + # For arrays of simple types (strings, numbers, etc.), join with commas + if all(isinstance(item, str | int | float | bool) for item in values): + return ",".join(str(v) for v in values) + + # For complex types, try to create a simpler representation + try: + # Try to create a simple string representation + formatted_parts = [] + for item in values: + if isinstance(item, dict): + # For objects, serialize key-value pairs + item_parts = [] + for k, v in item.items(): + item_parts.append(f"{k}:{v}") + formatted_parts.append(".".join(item_parts)) + else: + formatted_parts.append(str(item)) + + return ",".join(formatted_parts) + except Exception as e: + param_type = "query" if is_query_parameter else "path" + logger.warning( + f"Failed to format complex array {param_type} parameter '{parameter_name}': {e}" + ) + + if is_query_parameter: + # For query parameters, fallback to original list + return values + else: + # For path parameters, fallback to string representation without Python syntax + str_value = ( + str(values) + .replace("[", "") + .replace("]", "") + .replace("'", "") + .replace('"', "") + ) + return str_value + + class ParameterInfo(FastMCPBaseModel): """Represents a single parameter for an HTTP operation in our IR."""