Skip to content

[BUG] [PYTHON] Regex field validators run after type conversion, breaking date-time patterns and formatted field types #24065

@sayandipdutta

Description

@sayandipdutta

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator?
  • Have you tested with the latest master to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What's the actual output vs expected output?
  • [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description

The Python generator emits Pydantic v2 regex validators without specifying mode="before":

@field_validator("created_at")
def created_at_validate_regular_expression(cls, value):
    if not isinstance(value, str):
        value = str(value)

    if not re.match(PATTERN, value):
        raise ValueError(...)

    return value

Pydantic field validators default to mode="after". Consequently, formatted OpenAPI strings have already been converted to their Python types before the regex runs.

For a string with format: date-time, the validator receives a datetime. Converting it with str(value) produces something such as:

2026-05-29 06:25:09.281000+00:00

This cannot match a pattern expecting the original RFC 3339 representation:

2026-05-29T06:25:09.281Z

Therefore, a valid API response is rejected.

The same template also affects types such as UUID: the after validator receives a UUID, converts it to str, and returns the string. The resulting model field can therefore contain a str despite
being annotated as UUID.

The relevant template is:

modules/openapi-generator/src/main/resources/python/model_generic.mustache
openapi-generator version

Latest master. The current Python model_generic.mustache still generates regex validators without mode="before".

OpenAPI declaration file content or url
openapi: 3.0.3
info:
  title: Regex validation reproduction
  version: 1.0.0

paths: {}

components:
  schemas:
    Example:
      type: object
      required:
        - id
        - createdAt
      properties:
        id:
          type: string
          format: uuid
          pattern: '^[0-9a-fA-F-]{36}$'
        createdAt:
          type: string
          format: date-time
          pattern: '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$'
Generation Details
openapi-generator-cli generate \
  -g python \
  -i openapi.yaml \
  -o generated-client
Steps to reproduce
from uuid import UUID

from openapi_client.models.example import Example

model = Example.model_validate(
    {
        "id": "0190d9f0-ab12-7000-8000-abc123456789",
        "createdAt": "2026-05-29T06:25:09.281Z",
    }
)

assert isinstance(model.id, UUID)
Actual behavior

createdAt is parsed into a datetime before the regex validator executes. The validator converts that value using str(datetime_value), and the resulting value does not match the OpenAPI pattern.
Model validation fails.

For UUID fields with patterns, the validator returns str(value), potentially changing the field from UUID back to str.

Expected behavior

OpenAPI pattern applies to the string representation supplied on the wire. The regex should therefore be evaluated before Pydantic converts formatted strings into Python types.

After validation:

  • A valid RFC 3339 string should be accepted and stored as a datetime.
  • A valid UUID string should be accepted and stored as a UUID.
  • Strings that do not match the declared pattern should be rejected.
  • Already-typed Python values should not be unexpectedly converted to strings.
Related issues/PRs
Suggest a fix

Regex validators should use mode="before":

@field_validator("created_at", mode="before")
@classmethod
def created_at_validate_regular_expression(cls, value):
    if isinstance(value, str) and not re.match(PATTERN, value):
        raise ValueError("must validate the regular expression")

    return value

Returning the original value allows Pydantic to perform the subsequent conversion to datetime, UUID, or another declared Python type.

This change should apply to generated regex validators. Enum validators inspect already-parsed values and do not necessarily need to change.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions