Skip to content

[python] Run regex pattern validators in mode="before" so they see the wire value (#24065)#24072

Merged
wing328 merged 1 commit into
OpenAPITools:masterfrom
seonwooj0810:fix/issue-24065-python-regex-validator-mode-before
Jun 24, 2026
Merged

[python] Run regex pattern validators in mode="before" so they see the wire value (#24065)#24072
wing328 merged 1 commit into
OpenAPITools:masterfrom
seonwooj0810:fix/issue-24065-python-regex-validator-mode-before

Conversation

@seonwooj0810

@seonwooj0810 seonwooj0810 commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Fixes #24065

Problem

The Python (pydantic v2) generator emitted regex @field_validators without a mode, so they defaulted to mode="after" and ran after pydantic had already coerced the incoming value to its declared Python type:

@field_validator('created_at')
def created_at_validate_regular_expression(cls, value):
    if not isinstance(value, str):
        value = str(value)        # <- typed value stringified here
    if not re.match(PATTERN, value):
        raise ValueError(...)
    return value

Because the validator received the already-converted value, two things broke:

  • A string with format: date-time arrived as a datetime. str(datetime) produces e.g. 2026-05-29 06:25:09.281000+00:00, which no longer matches a pattern written for the RFC 3339 wire form (...T...Z) — so a valid response was rejected.
  • A string with format: uuid was coerced to UUID, then the validator returned str(value), leaving the field value a str despite the UUID annotation.

Fix

Run the pattern check in mode="before" against the raw wire value, and only when it is a str; then let pydantic perform its normal conversion. Already-typed Python values pass through untouched.

@field_validator('created_at', mode="before")
def created_at_validate_regular_expression(cls, value):
    if isinstance(value, str) and not re.match(PATTERN, value):
        raise ValueError(...)
    return value

Single template change: modules/openapi-generator/src/main/resources/python/model_generic.mustache. Affected python samples were regenerated (python, python-aiohttp, python-httpx, python-lazyImports); pydantic-v1 and fastapi use separate templates and are unaffected.

Test evidence

Built the generator (JDK 21) and regenerated the samples — the only generated diff is the validator signature/body, as intended.

Against the regenerated python sample's UuidWithPattern (id: Optional[UUID], with a UUID-v4 pattern), verified with pydantic 2.13:

  • Valid UUID-v4 string on the wire → field is a proper UUID (previously a str — bug reproduced on master then confirmed fixed).
  • Optional None accepted.
  • Malformed string still rejected by the pattern.
  • An already-typed UUID passed to the constructor is preserved (not coerced to str).

Existing sample unit tests test_uuid_with_pattern.py and test_format_test.py pass.

Verification done

(1) No in-flight PR — searched open PRs for python regex/pattern-validator work and cross-checked my own open PRs; none overlap. (2) Issue is unclaimed (no comments). (3) Code-focused — .mustache template + regenerated .py samples. (4) Confirmed the old (uncorrected) template still ships in master. (6) N/A (not a spring-projects repo).


Summary by cubic

Run pattern validators in the Python pydantic v2 generator in mode="before" so patterns check the raw wire string. This prevents false rejections for RFC 3339 date-time and preserves UUID types.

  • Bug Fixes
    • Generate regex @field_validator(..., mode="before") and run the check only for str inputs; let pydantic handle conversion afterward.
    • Prevent false negatives for date-time and stop converting UUID values to str.
    • Regenerated python, python-aiohttp, python-httpx, and python-lazyImports; pydantic v1 and fastapi templates are unchanged.

Written for commit addfe64. Summary will update on new commits.

Review in cubic

…24065)

The Python (pydantic v2) generator emitted regex `@field_validator`s
without a mode, so they defaulted to `mode="after"` and ran *after*
pydantic had already coerced the wire value to its declared Python type.

Consequences:
- A `string` with `format: date-time` reached the validator as a
  `datetime`; the template stringified it (`str(value)`), producing a
  non-RFC-3339 form that could no longer match the declared `pattern`,
  so valid responses were rejected.
- A `string` with `format: uuid` was coerced to `UUID`, then the
  validator returned `str(value)`, leaving the field value a `str`
  despite the `UUID` annotation.

Run the pattern check in `mode="before"` against the raw wire value and
only when it is a `str`, then let pydantic perform the normal
conversion. Already-typed Python values are passed through untouched.

Regenerated affected python samples (python, python-aiohttp,
python-httpx, python-lazyImports).

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 13 files

Re-trigger cubic

@wing328

wing328 commented Jun 23, 2026

Copy link
Copy Markdown
Member

thanks for the PR

cc @cbornet (2017/09) @tomplus (2018/10) @krjakbrjak (2023/02) @fa0311 (2023/10)

timonrieger added a commit to timonrieger/immichpy that referenced this pull request Jun 23, 2026
timonrieger added a commit to timonrieger/immichpy that referenced this pull request Jun 24, 2026
timonrieger added a commit to timonrieger/immichpy that referenced this pull request Jun 24, 2026
timonrieger added a commit to timonrieger/immichpy that referenced this pull request Jun 24, 2026
@wing328

wing328 commented Jun 24, 2026

Copy link
Copy Markdown
Member

the fix looks reasonable to me.

let's give it a try

thanks for the contribution.

@wing328 wing328 merged commit 1e8212b into OpenAPITools:master Jun 24, 2026
38 of 40 checks passed
@wing328 wing328 added this to the 7.24.0 milestone Jun 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants