[ENH]: Adopt Pydantic for schema validation #364
No reviewers
Labels
No labels
CRITICAL
Stale
WIP
bug
concept
coordinate
dataset
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
maintenance
maps
marker
mask
on hold
parcellation
preprocess
question
ready
storage
template-space
triage
wontfix
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
juaml/junifer!364
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/pydantic-validation"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Are you requiring a new dataset or marker?
Which feature do you want to include?
We want to be able to validate the yaml so we avoid issues like #296
How do you imagine this integrated in junifer?
Use pydantic and validate the yaml.
Do you have a sample code that implements this outside of junifer?
No response
Anything else to say?
No response
Codecov Report
✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (
6c48e06) to head (8851c3c).⚠️ Report is 7 commits behind head on main.
Additional details and impacted files
100.00% <ø> (ø)?Flags with carried forward coverage won't be shown. Click here to find out more.
100.00% <ø> (ø)... and 144 files with indirect coverage changes
🚀 New features to boost your workflow:
https://juaml.github.io/junifer/pr-preview/pr-364/
Built to branch
gh-pagesat 2025-12-05 13:17 UTC.Preview will be ready when the GitHub Pages deployment is complete.
CI passes on juseless.
@fraimondo I have run quite a few of the YAMLs I have, would appreciate if you could run a couple of your most-used YAMLs with this branch. The YAMLs might require minimal changes which should be evident from the error raised.
Overall looks promising. Though there are some changes that go against the easy-to-use QA:
Many parameters were
str or list of str. Now they are lists. This adds the unnecessary burden to add a list in the YAMLs where the most common use case is to use a single element.some parameters were
str or Pathand now they have to be Paths. This also makes it more complicated for users to define code. This can easily be validated later on and not with pydantic. Same forHttpUrlI think there's an issue with the
datadirwhich now will be part of the meta due to missing it's_. Also, there was a reason why it was not calledfulldirin the datababber, butdatadir, on purpose.Some enums might add validation to pydantic, but will restraint the extensibility of junifer. E.g. DataType, ConfoundFormat, etc... These should be dynamic, as I might want to add some of them in an extension, without modifying junifer code.
Since there are 197 files changed, and many changes happen because of the first item. I will review more later with the next changes.
Why suddenly this needs to be a list? It should be str or List[str]
@ -124,7 +124,7 @@ parcellation when registering it. For example, we can add amarkers:Same here: str or list[str]
Same as below.
With this change, now this line looks a bit lost in the docs. Maybe we should explicitly show that this is now a class attribute.
I will stop commenting on this
The example should work without this "complicated" things from Pydantic.
Why here and not in line 186? Why is not done in the
__init__method of theWorkDirManager?@ -19,0 +25,4 @@_types = Literal[DataType.BOLD,DataType.T1w,DataType.VBM_CSF,should be a DataType of a list of them
Same for all the datagrabbers, markers, steps, etc where it can be one or many.
@ -21,2 +25,3 @@"""Abstract base class for DataGrabber.class DataType(str, AEnum):"""Accepted data type."""Now we hit an important issue here.
How can I create a junifer extension that allows to process a new DataType (e.g. EEG)?
without an
_it will be considered part of the metadata for the computation of the hash. For Datalad-based datagrabbers, this should not count.I think that is why it's a property.
@ -87,0 +140,4 @@self._repodir.mkdir(parents=True, exist_ok=False)logger.info("Datalad dataset installation path set to: "f"{self._repodir.resolve()!s}"this is horrible, what if the datadir was set by the user to
datalad_dataset_aomic?It's missing the space in case it's dirty.
@ -18,1 +22,4 @@_types = Literal[DataType.BOLD, DataType.T1w, DataType.Warp]will this expand? Otherwise I prefer the previous docstring as it tells you exactly what your options are.
Having an enum for the confounds format will also restrict someone defining their own format and using their own preprocessor for that format.
Here's the present situation where one can't extend it:
github.com/juaml/junifer@65be1297f8/junifer/datagrabber/pattern.py (L195-L203). With this change they wouldn't be able to extend it either but can be made so with a couple of lines (seeDataType).@ -18,1 +22,4 @@_types = Literal[DataType.BOLD, DataType.T1w, DataType.Warp]Here's the doc link: https://juaml.github.io/junifer/pr-preview/pr-364/api/datagrabbers.html#junifer.datagrabber.DataladHCP1200 and here's the doc link for the enum: https://juaml.github.io/junifer/pr-preview/pr-364/api/datagrabbers.html#junifer.datagrabber.HCP1200Task
Without the change it prints double space in case it's dirty and with the change it does single space.
Here's where it gets removed and does not become part of hash computation:
github.com/juaml/junifer@0f2ecf0ce7/junifer/storage/utils.py (L81-L86)@ -21,2 +25,3 @@"""Abstract base class for DataGrabber.class DataType(str, AEnum):"""Accepted data type."""Same way as one does now.
Nothing gets created until the datagrabber object is accessed, hence kept here. I don't have a problem moving it to L186.
It gives RecursionError if kept there.
@ -87,0 +140,4 @@self._repodir.mkdir(parents=True, exist_ok=False)logger.info("Datalad dataset installation path set to: "f"{self._repodir.resolve()!s}"Hmm that's a fair argument, I can do something like:
and then check with:
All of them should be reverted now, let me know if you find any stray ones.
Not at all, users can keep passing
strand they'll be converted toPathby pydantic.The introduction of
pydanticis primarily to not hand-roll these validations anymore and users should only need to add logical validations, but anyway these can be easily added via pydantic.HttpUrlis nowAnyUrland is same as thePathargument above.Has been addressed in the specific comment.
The reason was to keep it in sync with non-datalad based datagrabbers and is already taken care of internally via introduction of
_repodir. The UX should be exactly as it is now, let me know if you find any difference while using.Has been addressed in specific comments.
but for non-datalad datagrabbers this should be counted.
@ -87,0 +140,4 @@self._repodir.mkdir(parents=True, exist_ok=False)logger.info("Datalad dataset installation path set to: "f"{self._repodir.resolve()!s}"why can't we keep a private var
_was_clonedand check that? What if we "run a test without cleaning the workdir" and then we use it?The behaviour should be "clean whatever mess you made".
@ -18,1 +22,4 @@_types = Literal[DataType.BOLD, DataType.T1w, DataType.Warp]Ok, so now I found that we have an even bigger problem with the DOC.
This is the "user documentation" where we show what we have available: https://juaml.github.io/junifer/pr-preview/pr-364/builtin.html
When you click on DataladHCP1200, it goes to the API doc, which is then this: https://juaml.github.io/junifer/pr-preview/pr-364/api/datagrabbers.html#junifer.datagrabber.DataladHCP1200
This is terrible for users.
typesis not a string anymore, neithertasksnorphase_encodingswhich makes it cryptic. "What shall I put in the field?" The previous docstring was easier to understand E.g. "use the string "REST1" and it will work!"I still don't understand how can I add a datatype in an extension.
You'd do as shown here: https://juaml.github.io/junifer/pr-preview/pr-364/extending/data_types.html
Should be fixed with the latest commit.
@ -87,0 +140,4 @@self._repodir.mkdir(parents=True, exist_ok=False)logger.info("Datalad dataset installation path set to: "f"{self._repodir.resolve()!s}"We already have
_was_clonedfor a similar purpose. I don't think I understand how you want it to be implemented.@ -18,1 +22,4 @@_types = Literal[DataType.BOLD, DataType.T1w, DataType.Warp]Should be addressed with the latest commit.
Can you implement the possibility to extend confound formats?
@fraimondo I've pushed the further changes, feel free to review.
Last questions
We can already leave the typing in the template here
Since they are datatypes, shouldn't they be DataType.T1w, etc? Or it is ok to keep them as strings?
@ -17,4 +17,5 @@ queue:env:kind: condaname: junifershell: bashWe don't have bash as default here?
This will happen a alot, might be worth having a "util" function to "update patterns".
Do we still need the
onhere? It's inherited fromBaseMarkerThere are many ocasions in which the
onis repeated on the inherited marker.They get automatically converted.
What do you mean?
@ -17,4 +17,5 @@ queue:env:kind: condaname: junifershell: bashYes the default is bash, this is to be explicit.
Yes we need it to constrain the correct data types else any DataType will be valid.
I agree it has value and it's a dict so it should be straightforward to make one. I wouldn't want to do it in this PR though, might be good to tackle in another one.
This is supposed to be a template to create your own preprocessor:
@ -17,4 +17,5 @@ queue:env:kind: condaname: junifershell: bashok
Before it was part of the "init" of each class, but now it has became something more "systematic" in the validation, which also makes the term validation a bit too loose. If you dont' want it in the PR, make an issue.
thought so.
One checks pass, good to go.
Opened here: https://github.com/juaml/junifer/issues/497
Addressed in latest commit.