Skip to content

fix: migrate 3 cross-field validators to Pydantic V2 - #310

Open
dulcetberg wants to merge 1 commit into
hotosm:developfrom
dulcetberg:fix/pydantic-v2-cross-field-validators
Open

fix: migrate 3 cross-field validators to Pydantic V2#310
dulcetberg wants to merge 1 commit into
hotosm:developfrom
dulcetberg:fix/pydantic-v2-cross-field-validators

Conversation

@dulcetberg

Copy link
Copy Markdown

Summary

Converts the 3 remaining @validator usages in src/validation/models.py that read other fields via V1's values parameter (deliberately deferred out of #309 since they need the mode="before" + info.data treatment rather than a plain decorator swap):

  • RawDataCurrentParams.check_bind_option (bind_zip)
  • StatsRequestParams.set_geometry_or_iso3 (geometry)
  • DynamicCategoriesModel.set_geometry_or_iso3 (geometry — this one also has extra HDX dataset/category cross-checks beyond the base geometry/iso3 logic)

@validator(field, allow_reuse=True)@field_validator(field) + @classmethod; values.get(...)info.data.get(...).

The always=True gotcha

The two set_geometry_or_iso3 validators used pre=True, always=True in V1, so they run even when geometry is omitted (this is how the "either geometry or iso3, not neither" check works). V2's field_validator has no always equivalent — mode="before" alone does not run when the field is omitted and its default is used. Verified this with an isolated test before relying on it.

The correct V2 replacement is validate_default=True on the field's own Field(...) declaration, added here to both geometry fields. Re-verified behaviorally across all 4 cases (geometry-only, iso3-only, both supplied, neither supplied) that this reproduces the original V1 semantics exactly.

Part of the pydantic v1→v2 migration tracked in #256. Continues the incremental approach from #307/#308/#309.

Test plan

  • pytest tests/test_app.py passes (5 passed)
  • No remaining deprecation warnings for these 3 validators
  • Isolated behavioral test of StatsRequestParams: neither/both/geometry-only/iso3-only all raise/pass exactly as before
  • Isolated behavioral test of RawDataCurrentParams.check_bind_option: disallowed output_type + bind_zip=False raises, allowed output_type + bind_zip=False passes, default bind_zip passes regardless of output_type
  • Isolated unit test of DynamicCategoriesModel.set_geometry_or_iso3's extra HDX dataset-required-when-hdx_upload logic

🤖 Generated with Claude Code

Converts the remaining `@validator` usages in src/validation/models.py
that read other fields via V1's `values` parameter:

- RawDataCurrentParams.check_bind_option (bind_zip)
- StatsRequestParams.set_geometry_or_iso3 (geometry)
- DynamicCategoriesModel.set_geometry_or_iso3 (geometry, plus its
  extra HDX dataset/category cross-checks)

`@validator(field, allow_reuse=True)` -> `@field_validator(field)` +
`@classmethod`; `values.get(...)` -> `info.data.get(...)`.

The two `set_geometry_or_iso3` validators additionally used
`pre=True, always=True` in V1, which has no direct V2 validator-level
equivalent. Converting to `mode="before"` alone is not sufficient: it
does not run when the field is omitted and falls back to its default.
The correct V2 replacement is `validate_default=True` on the field's
own `Field(...)` declaration, which was added to both `geometry`
fields here. Verified behaviorally (all 4 cases: geometry-only,
iso3-only, both supplied, neither supplied) that this reproduces the
original V1 semantics exactly.

Part of the pydantic v1->v2 migration tracked in hotosm#256. Continues the
incremental approach from hotosm#307/hotosm#308/hotosm#309; this covers the 3 validators
that were deliberately deferred out of hotosm#309 for needing this more
involved treatment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Brian Bergstrom <dulcetberg@gmail.com>
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.

1 participant