fix: migrate validation models to Pydantic V2 - #315
dulcetberg wants to merge 1 commit into
Conversation
Replaces every Pydantic V1 construct in src/validation/models.py with its V2 equivalent: - 9 `@validator` -> `@field_validator` + `@classmethod` - 4 `class Config` -> `model_config = ConfigDict(...)` - cross-field validators take `info: ValidationInfo`, reading `info.data` instead of `values` - `pre=True, always=True` -> `mode="before"` plus `validate_default=True` on the corresponding Field - drops the now-unused `validator` import Supersedes the five split PRs (hotosm#307, hotosm#308, hotosm#309, hotosm#310, hotosm#311), which each touched the same import line and so could not be merged independently.
|
Two corrections to the CI notes in my description, both of which I got wrong. black passed. I based that bullet on The Unit Test failure isn't the Kontur timeouts either. It fails well before the tests, during collection:
That should affect any PR opened from a fork, not just this one, so it may be worth a look independently of this change. Happy to open a separate issue if that's useful. The practical consequence here is that CI can't tell you anything about this PR either way. The before/after comparison in the description was run locally, which is why it's there. |
What type of PR is this?
Related Issue :
None. This replaces my own PRs #307, #308, #309, #310 and #311. I'll close those once this is open.
What does this PR do ?
Moves the last Pydantic V1 constructs in
src/validation/models.pyover to V2:@validatorbecome@field_validator+@classmethodclass Configbecomemodel_config = ConfigDict(...)info.datainstead ofvaluespre=True, always=Truebecomesmode="before"plusvalidate_default=Trueon the fieldvalidatorimport is dropped, since nothing uses it nowThat file is the only place in the repo with these constructs, so there are none left after this. The other modules importing pydantic (
API/auth/,API/download_metrics.py,API/custom_exports.py) only use plainBaseModel,FieldandValidationError, which didn't change between V1 and V2.Nothing breaks today either way. The repo already runs Pydantic 2.x because
geojson-pydantic==1.0.1requires it, so these currently work through compatibility shims. The shims are marked "Deprecated in Pydantic V2.0 to be removed in V3.0", so this is about not being stuck when a V3 bump comes up.Consideration :
Why this is one PR and not the five I opened before. I split it originally thinking smaller PRs were easier to review. That was wrong. All five came off the same commit and all five edited the same import line, so merging any one would have put the other four in conflict straight away. And each one on its own left the file half migrated, still importing
validatornext tofield_validator, which is what @spwoodcock flagged on #307. One commit for the whole file is less work to review, not more.Scope. Only
src/validation/models.py, and only the V1 to V2 constructs. I didn't reformat anything else.Left out on purpose. There are 27 more deprecation warnings from
Field(example=...), which V2 wants asjson_schema_extra. That's a big noisy diff and it changes the generated OpenAPI docs, so it isn't in here. Happy to do it separately if it's wanted.On
info.dataordering. This is the part I'd check first if I were reviewing. In V2info.dataonly holds fields validated before the current one, so the three cross-field validators only work if the declaration order cooperates. It does, in all three:check_bind_optionvalidatesbind_zip, declared inRawDataCurrentParams, and readsoutput_type, declared in the parentRawDataCurrentParamsBase. Parent fields are ordered first, sooutput_typeis there.set_geometry_or_iso3validators readiso3, and theDynamicCategoriesModelone also readsdataset,hdx_uploadandcategories. In both modelsgeometryis declared after all of them.The behaviour checks below cover this rather than leaving it to my reading of the code.
How to test ?
I couldn't run the full
py.test -v -sagainst a populated OSM database, so I checked the part I changed directly instead of reporting a pass I didn't see.The validators in this file can be exercised without a database or GDAL. I imported the models before and after and ran the same checks against both:
That covers both cross-field validators, the camelCase alias generator and
populate_by_name, the HDX tag and category type/format validators, theFeatureCollectionhandling inGeometryValidatorMixin,geometry_typede-duplication, and thatjson_schema_extrastill reaches the schema. I can turn these into a pytest module undertests/if you'd want that in the repo. They don't need a database, so they'd run in CI cleanly.Two CI results that aren't from this PR
developitself is currently 2 failed, 37 passed. Both failures aretest_stats_endpoint_custom_polygonandtest_stats_endpoint_iso3, both connection timeouts toapps.kontur.iofrom the runner rather than assertion failures. Expect the same two here.black --check .fails on unmodifieddevelopfor 4 files (API/raw_data.py,src/app.py,src/query_builder/builder.py,src/validation/models.py). The action tracks latest black and the repo was formatted with an older version. I didn't run black over the file, because that would mix a reformat-everything diff into a behaviour change. Say the word if you'd rather I did, or if a separate formatting PR would be useful.Screenshots :
AI Tool Usage
How much of this PR was AI-assisted? (check one)
If level 1 or above:
info.dataordering argument above myself instead of taking it on trust. I decided to drop the five way split and to leave theField(example=...)deprecations out of scope.Checklists:
Checklist before requesting review :
📖 Read the HOT Code of Conduct: https://docs.hotosm.org/code-of-conduct
👷♀️ Create small PRs. In most cases, this will be possible.
✅ Provide tests for your changes.
📝 Use descriptive commit messages.
📗 Update any related documentation and include any relevant screenshots.
📖 Read the HOT Code of Conduct: https://docs.hotosm.org/code-of-conduct
👷♀️ Create small PRs. In most cases, this will be possible.
✅ Provide tests for your changes.
📝 Use descriptive commit messages.
📗 Update any related documentation and include any relevant screenshots.
[optional] What gif best describes this PR or how it makes you feel?