import ast import json from typing import Dict # Registry of known types for annotation resolution BUILTIN_TYPES = { "int": int, "float": float, "str": str, "dict": dict, "list": list, "set": set, "tuple": tuple, "bool": bool, } def resolve_type(annotation: str): """ Resolve a type annotation string into a Python type. Args: annotation (str): The annotation string (e.g., 'int', 'list', etc.). Returns: type: The corresponding Python type. Raises: ValueError: If the annotation is unsupported or invalid. """ if annotation in BUILTIN_TYPES: return BUILTIN_TYPES[annotation] try: parsed = ast.literal_eval(annotation) if isinstance(parsed, type): return parsed raise ValueError(f"Annotation '{annotation}' is not a recognized type.") except (ValueError, SyntaxError): raise ValueError(f"Unsupported annotation: {annotation}") def get_function_annotations_from_source(source_code: str, function_name: str) -> Dict[str, str]: """ Parse the source code to extract annotations for a given function name. Args: source_code (str): The Python source code containing the function. function_name (str): The name of the function to extract annotations for. Returns: Dict[str, str]: A dictionary of argument names to their annotation strings. Raises: ValueError: If the function is not found in the source code. """ tree = ast.parse(source_code) for node in ast.iter_child_nodes(tree): if isinstance(node, ast.FunctionDef) and node.name == function_name: annotations = {} for arg in node.args.args: if arg.annotation is not None: annotation_str = ast.unparse(arg.annotation) annotations[arg.arg] = annotation_str return annotations raise ValueError(f"Function '{function_name}' not found in the provided source code.") def coerce_dict_args_by_annotations(function_args: dict, annotations: Dict[str, str]) -> dict: """ Coerce arguments in a dictionary to their annotated types. Args: function_args (dict): The original function arguments. annotations (Dict[str, str]): Argument annotations as strings. Returns: dict: The updated dictionary with coerced argument types. Raises: ValueError: If type coercion fails for an argument. """ coerced_args = dict(function_args) # Shallow copy for mutation safety for arg_name, value in coerced_args.items(): if arg_name in annotations: annotation_str = annotations[arg_name] try: # Resolve the type from the annotation arg_type = resolve_type(annotation_str) # Handle JSON-like inputs for dict and list types if arg_type in {dict, list} and isinstance(value, str): try: # First, try JSON parsing value = json.loads(value) except json.JSONDecodeError: # Fall back to literal_eval for Python-specific literals value = ast.literal_eval(value) # Coerce the value to the resolved type coerced_args[arg_name] = arg_type(value) except (TypeError, ValueError, json.JSONDecodeError, SyntaxError) as e: raise ValueError(f"Failed to coerce argument '{arg_name}' to {annotation_str}: {e}") return coerced_args