1

I'd like to extend existing Pydantic types such as FilePath by f.i. adding a file type pattern for

  • validation
  • serialization to JSON schema.

Current approach

I'd f.i. like to define my custom type FilePathPattern extending FilePath by a pattern which is defined when using the custom type:

from typing import Annotated

from pydantic import Field, FilePath

def get_pattern(
    field_data: dict[str, str]
) -> dict[str, str]:
    return {"pattern", field_data["pattern"]}

FilePathPattern = Annotated[
    FilePath,
    Field(json_schema_extra=get_pattern),
]

class MySchema(BaseModel):
    """My schema."""

    path_with_suffix: FilePathPattern = Field(
        title="Path to CSV or TXT",
        description="something",
        default="my_data.csv",
        pattern=r".*\.(csv|txt)$",
    )
    another_path: FilePathPattern = Field(
        pattern="some_pattern",
    )

Expected outcome

Which should

  • Use the validators provided by the base type FilePath to check for existence

  • extend the validation by matching the regex pattern with str(path_with_suffix)

  • serialize the model to a JSON schema with MySchema.model_json_schema() containing the additional key pattern (which is also included in the JSON schema standard):

    {
      ...
      "properties": {
        "path_with_suffix": {
          "default": "my_data.csv",
          "description": "something",
          "format": "file-path",
          "pattern": ".*/struct.dat",
          "title": "Path to CSV or TXT"
        },
        "another_path": {...}
      },
      "title": "...",
      "type": "object"
    }
    

Problem

Unluckily this doesn't work, since

  • FilePath doesn't seem to contain a pattern, even if defined in the Field of the derived custom type FilePathPattern
  • Defining the pattern explicitly in json_schema_extra={"pattern": r".*/struct\.dat"} will work, but this drastically reduces reusability of the custom type
  • Using WithJsonSchema in the Annotated will result in dropping all of the other schema keywords such as format

How can I define a custom type extending existing types without overwriting any of the features provided by the existing type?

1 Answer 1

1

I think, the most approach type of validation, you need to add model_validator and sequentially validate by each parameter, such as existing of file, and match by regular expression.

from pydantic import BaseModel, Field, model_validator
from pathlib import Path
import re

class FilePathPatternModel(BaseModel):

    @model_validator(mode='after')
    def validate_file_patterns(self) -> 'FilePathPatternModel[BaseModel]':
        for field_name, field_info in self.model_fields.items():
            field_value = getattr(self, field_name)

            # Check if this field has a pattern
            pattern = field_info.json_schema_extra.get('pattern') if field_info.json_schema_extra else None

            if (pattern and field_value) is not None:
                path = Path(field_value)

                if not path.exists():
                    raise ValueError(f"File does not exist: {path}")


                if not re.match(pattern, str(path)):
                    raise ValueError(f"Path '{path}' does not match pattern '{pattern}'")

        return self


class MySchema(FilePathPatternModel):


    path_with_suffix: str = Field(
        title="Path to CSV or TXT",
        description="something",
        default="my_data.csv",
        pattern=r".*\.(csv|txt)$",
        json_schema_extra={'format': 'file-path', 'pattern': r".*\.(csv|txt)$"}
    )

    another_path: str = Field(
        pattern=r".*\.(json|yaml|yml)$",
        json_schema_extra={'format': 'file-path', 'pattern': r".*\.(json|yaml|yml)$"}
    )

schema = MySchema(path_with_suffix='.users/names.txt', another_path='.users/books.json')

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for your answer! This looks interesting, but I'd really like to use Custom Types instead of a tweaked Base Model.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.