Pydantic

Pydantic is confined to one job: validating and documenting the shape of requests and responses at the FastAPI boundary. It does not appear in core or adaptation, which use plain dataclasses instead (see Python). This chapter is about that boundary, not about Pydantic in general.

Pydantic v2, explicitly

src/test_data_workbench/api/models.py imports BaseModel, ConfigDict, and Field from pydantic, the v2 API. Every request and response type used by the FastAPI app (see FastAPI) is a BaseModel subclass defined here: DatabaseConnection, SchemaAnalysisRequest/Response, GeneratorCreationRequest/ Response, DataGenerationRequest/Response, RapidDeploymentRequest/Response, and several more. pyproject.toml pins pydantic>=2.5.0.

ConfigDict over the old inner class Config

Every model that needs configuration uses the v2 model_config = ConfigDict(...) class attribute rather than the v1-style nested class Config::

class DatabaseConnection(BaseModel):
    """Database connection information."""
    model_config = ConfigDict(json_schema_extra={
        "examples": [{
            "connection_string": "postgresql://user:pass@localhost:5432/dbname",
            "timeout": 30,
        }]
    })

    connection_string: str = Field(..., description="Database connection string")
    timeout: Optional[int] = Field(30, description="Connection timeout in seconds")

json_schema_extra is used consistently across the request/response models to attach a worked example to each schema. Because FastAPI derives its OpenAPI document from these models automatically, that example shows up directly in the /docs Swagger UI (see FastAPI) without any separate documentation-writing step, the example lives next to the field definitions it describes.

Field for defaults, requiredness, and description

Field(...) marks a value required (connection_string: str = Field(..., description=...)); a concrete default marks it optional (timeout: Optional[int] = Field(30, ...)). The description on every field is not decoration, it is what populates the parameter descriptions in the generated OpenAPI schema and therefore in /docs. A model with undocumented fields would still validate correctly; it would just produce a less useful API reference, which is the whole reason to bother with the description= kwarg at all here rather than relying on type hints alone.

Aliasing around a name collision

DataGenerationRequest has one field worth calling out specifically:

class DataGenerationRequest(BaseModel):
    model_config = ConfigDict(populate_by_name=True, json_schema_extra={...})

    ...
    validate_output: bool = Field(True, description="Validate generated data", alias="validate")

The Python attribute is validate_output, but the wire field (what a client actually sends and what shows up in the OpenAPI schema) is validate, via alias="validate". validate is a natural field name from an API consumer’s point of view, but it collides with meaning inside this codebase, core/validators.py and ValidationResult already use “validate” as a term of art for AST-level generator/contribution checking (see Architecture), and a field literally named validate on a Pydantic model risks shadowing conventions elsewhere in the code that reason about “validation” as a concept. The alias keeps the public API ergonomic while keeping the internal attribute name unambiguous. populate_by_name=True is what allows the model to also be constructed from Python code using the attribute name validate_output directly, rather than only accepting the alias.

Boundary, not backbone

Nothing constructed inside adaptation or core is a Pydantic model; SchemaInfo, Table, Column, and the rest are the dataclasses from Python. An endpoint function is where the translation happens: it receives a validated Pydantic request model, calls into the dataclass-based core, and builds a Pydantic response model from the result to return. Keeping that boundary sharp means the validation and JSON-schema machinery Pydantic brings is paid for exactly once, at the edge, rather than threaded through every internal function call.