Skip to content

Conversation

bgaidioz
Copy link
Contributor

@bgaidioz bgaidioz commented Aug 27, 2025

Description

Add pydantic model parameter conversion to the Python executor. Functions that expect pydantic models as parameters can now receive dictionary inputs, which are automatically converted to the appropriate model instances.

This change enables Python endpoints with pydantic model parameters to work correctly with MCP tool calls, which provide data as JSON/dictionaries.

Parameters

The user defines a Python function expecting a specific Pydantic model:

def process_user(user: UserModel) -> ResponseModel:
    # user is expected to be an instance of UserModel

but the tool is defined via YAML schema, creating a type mismatch between the YAML-generated model and the user's expected model.

The way we do it: YAML Schema → FastMCP Pydantic Model → Dictionary → User's Pydantic Model

  • YAML Schema → FastMCP Pydantic Model
    • YAML schema is converted to a Pydantic model via _create_pydantic_model_from_schema()
    • This model handles FastMCP protocol requirements and validation
  • FastMCP Pydantic Model → Dictionary
    • TypeValidator.validate_input() converts the FastMCP Pydantic model to a dictionary
    • Uses model_dump() (Pydantic v2) or dict() (Pydantic v1)
    • Ensures portability and protocol compliance
  • Dictionary → User's Pydantic Model
    • PythonExecutor._convert_parameters() uses Pydantic's TypeAdapter
    • Inspects the user function's type hints
    • Reconstructs parameters as the user's expected Pydantic model types
    • Handles complex types: Optional[Model], list[Model], dict[str, Model], etc.

Return value

User functions return their own Pydantic models, but FastMCP needs to validate and serialize them according to the YAML-defined return schema.

How it's done: User's Pydantic Model → Dictionary → Validation → Serialized Output

  • User Model → Dictionary
    • TypeConverter.validate_output() converts user's Pydantic model to dictionary
    • Uses model_dump() or dict() methods to extract data
  • Dictionary → Validation
    • Validates the dictionary against the YAML-defined return schema
    • Ensures output matches the declared interface
  • Dictionary → Serialized Output
    • TypeConverter.serialize_for_output() ensures JSON compatibility
    • Handles special cases like pandas DataFrames, numpy arrays, datetime objects

SQL

SQL queries follow a simpler path since DuckDB natively expects dictionary parameters:

YAML Schema → FastMCP Model → Dictionary → DuckDB Parameters

Dictionaries work directly with SQL parameter substitution.

Changes Made:

  • Added parameter conversion logic to PythonExecutor._convert_parameters()
  • Used TypeAdapter for robust type validation and conversion
  • Added support for complex types: Optional[Model], list[Model], dict[str, Model], etc.
  • Added forward reference resolution using proper module namespaces
  • Updated output converter to serialize pydantic models to dictionaries
  • Added comprehensive test coverage with 3 integration tests

Type of Change

  • ✨ New feature (non-breaking change which adds functionality)

Testing

  • Tests pass locally with uv run pytest
  • Linting passes with uv run ruff check .
  • Code formatting passes with uv run black --check .
  • Type checking passes with uv run mypy .
  • Added tests for new functionality (if applicable)
  • Updated documentation (if applicable)

Test Coverage:

  • Added 3 new integration tests: test_pydantic_model_input, test_pydantic_model_output, test_pydantic_validation_error
  • All existing Python endpoint tests continue to pass
  • Added test fixtures for container types (list[User], dict[str, User])

Security Considerations

  • This change does not introduce security vulnerabilities
  • Sensitive data handling reviewed (if applicable)
  • Policy enforcement implications considered (if applicable)

Breaking Changes

None. This change is backward compatible - existing Python functions continue to work unchanged.

Additional Notes

Functions that expect pydantic model parameters can now be called with dictionary arguments, which are automatically validated and converted to model instances. This enables better integration between MCP tool calls (which provide JSON data) and Python functions using pydantic models for structured input validation.

@bgaidioz bgaidioz marked this pull request as draft August 27, 2025 15:55
@bgaidioz bgaidioz changed the title Test pydantic models (parameters, return) Add pydantic model parameter conversion support Aug 29, 2025
@bgaidioz bgaidioz force-pushed the test-pydantic-models branch from f7668c4 to 0dad77b Compare August 29, 2025 15:00
@bgaidioz bgaidioz marked this pull request as ready for review August 29, 2025 15:34
@bgaidioz bgaidioz force-pushed the test-pydantic-models branch from 0dad77b to 9ed7a1f Compare September 1, 2025 16:02
Raises:
ValidationError: If pydantic validation fails
"""
from pydantic import BaseModel, TypeAdapter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move top-level

"""
import typing

from pydantic import ValidationError
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move both above to top-level

except Exception as e:
# Log unexpected errors with stack trace but continue processing other parameters
logger.exception(f"Unexpected error converting parameter '{param_name}': {e}")
converted_params[param_name] = param_value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain this one?
If a parameter can’t be converted into the expected type, it could also be reported as a validation error here?
Also, logger.exception vs logger.debug?

@bgaidioz bgaidioz force-pushed the test-pydantic-models branch from 9ed7a1f to 1db9d17 Compare September 2, 2025 16:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants