Skip to content

[ENH]: Introduce junifer.api.generate_yaml - #498

Open
synchon wants to merge 21 commits into
mainfrom
feat/generate-yaml-api
Open

[ENH]: Introduce junifer.api.generate_yaml#498
synchon wants to merge 21 commits into
mainfrom
feat/generate-yaml-api

Conversation

@synchon

@synchon synchon commented May 29, 2026

Copy link
Copy Markdown
Member
  • description of feature/fix
  • tests added/passed
  • add an entry for the latest changes

This PR adds generate_yaml under api to generate feature YAML from metadata. Its primary use-case is in julio's feature addition to registry.

@codecov

codecov Bot commented May 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (a8e9f31) to head (ca6cc8a).
⚠️ Report is 6 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##              main      #498   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            1         1           
  Lines            1         1           
=========================================
  Hits             1         1           
Flag Coverage Δ
docs 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@fraimondo

Copy link
Copy Markdown
Contributor

I have the impression that this way (dump_exclude) adds a lot of maintenance work: if something changes in the superclass (new internal variable), we need to go and update every subclass, including non-junifer ones.

Can't we just look at what fields are defined in the class (not the superclass) and just pass those ones to the constructor?

@synchon

synchon commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

I have the impression that this way (dump_exclude) adds a lot of maintenance work: if something changes in the superclass (new internal variable), we need to go and update every subclass, including non-junifer ones.

That's a fair argument and I see your point.

Can't we just look at what fields are defined in the class (not the superclass) and just pass those ones to the constructor?

Not with how I understand the thing works. A model's fields consist of its own fields and superclass' fields (if it has one). So apart from defining what to exclude (or include), I don't see other way. I'll push some updates to make it better.

@synchon
synchon force-pushed the feat/generate-yaml-api branch from a5c4e20 to 7e49b70 Compare June 15, 2026 09:04
@github-actions github-actions Bot added the Stale label Jul 16, 2026
@synchon synchon removed the Stale label Jul 16, 2026
@synchon
synchon force-pushed the feat/generate-yaml-api branch from 7e49b70 to 70c439b Compare July 21, 2026 12:12
@synchon

synchon commented Jul 21, 2026

Copy link
Copy Markdown
Member Author

@fraimondo I've updated the datagrabber dumping logic as discussed. Kindly review #499 before this.

@synchon synchon added this to the 0.0.8 milestone Jul 21, 2026
@synchon
synchon force-pushed the feat/generate-yaml-api branch from 83f88ed to 56c1a85 Compare July 21, 2026 14:06
Comment thread junifer/configs/juseless/datagrabbers/ucla.py Outdated
Comment thread junifer/datagrabber/aomic/id1000.py
Comment thread junifer/datagrabber/hcp1200/datalad_hcp1200.py
Comment thread junifer/datagrabber/datalad_base.py
Comment thread junifer/datagrabber/pattern_datalad.py
@fraimondo

Copy link
Copy Markdown
Contributor

Do we know how this function works when we have external imports (with statements in the YAML)?

@synchon

synchon commented Jul 22, 2026

Copy link
Copy Markdown
Member Author

Do we know how this function works when we have external imports (with statements in the YAML)?

It copies exactly what is passed. For julio, we copy exactly what is stored in h5.

@fraimondo

Copy link
Copy Markdown
Contributor

Do we know how this function works when we have external imports (with statements in the YAML)?

It copies exactly what is passed. For julio, we copy exactly what is stored in h5.

In order to create the yaml, we parse the metadata and instantiate the object. I'm not sure this will be possible if the definition of a datagrabber/marker is in an external package which is part of the with statement.

@synchon

synchon commented Jul 22, 2026

Copy link
Copy Markdown
Member Author

Do we know how this function works when we have external imports (with statements in the YAML)?

It copies exactly what is passed. For julio, we copy exactly what is stored in h5.

In order to create the yaml, we parse the metadata and instantiate the object. I'm not sure this will be possible if the definition of a datagrabber/marker is in an external package which is part of the with statement.

Let me lay it down w.r.t. julio:

  • junifer YAML gets parsed by junifer.api.parse_yaml which loads the external modules and registers componenets as needed.
  • If with is present in the junifer YAML, julio adds it directly to the generated YAML.
  • As the external components are already registered, junifer.api.generate_yaml can load them from component registry and dump as needed.

For using junifer.api.generate_yaml without julio, one would need to already register the components beforehand. Now, as it's part of junifer.api, we can presume that the user will do it like so.

Does that solve your concern?

@fraimondo

Copy link
Copy Markdown
Contributor

Do we know how this function works when we have external imports (with statements in the YAML)?

It copies exactly what is passed. For julio, we copy exactly what is stored in h5.

In order to create the yaml, we parse the metadata and instantiate the object. I'm not sure this will be possible if the definition of a datagrabber/marker is in an external package which is part of the with statement.

Let me lay it down w.r.t. julio:

* junifer YAML gets parsed by `junifer.api.parse_yaml` which loads the external modules and registers componenets as needed.

* If `with` is present in the junifer YAML, julio adds it directly to the generated YAML.

* As the external components are already registered, `junifer.api.generate_yaml` can load them from component registry and dump as needed.

For using junifer.api.generate_yaml without julio, one would need to already register the components beforehand. Now, as it's part of junifer.api, we can presume that the user will do it like so.

Does that solve your concern?

junifer.api.generate_yaml has one parameter that is a meta dict. This meta dict can be extracted from and HDF5 file, without parsing any yaml. We need to be able to support generating a YAML from any meta dict, no only after parsing the respective yaml.

Even if this means adding all variables int he meta dict and a huge comment stating that "since this is an external datagrabber/marker/etc not part of junifer core, some of this variables might not be needed or should definitely be removed."

@synchon

synchon commented Jul 22, 2026

Copy link
Copy Markdown
Member Author

Do we know how this function works when we have external imports (with statements in the YAML)?

It copies exactly what is passed. For julio, we copy exactly what is stored in h5.

In order to create the yaml, we parse the metadata and instantiate the object. I'm not sure this will be possible if the definition of a datagrabber/marker is in an external package which is part of the with statement.

Let me lay it down w.r.t. julio:

* junifer YAML gets parsed by `junifer.api.parse_yaml` which loads the external modules and registers componenets as needed.

* If `with` is present in the junifer YAML, julio adds it directly to the generated YAML.

* As the external components are already registered, `junifer.api.generate_yaml` can load them from component registry and dump as needed.

For using junifer.api.generate_yaml without julio, one would need to already register the components beforehand. Now, as it's part of junifer.api, we can presume that the user will do it like so.
Does that solve your concern?

junifer.api.generate_yaml has one parameter that is a meta dict. This meta dict can be extracted from and HDF5 file, without parsing any yaml. We need to be able to support generating a YAML from any meta dict, no only after parsing the respective yaml.

Even if this means adding all variables int he meta dict and a huge comment stating that "since this is an external datagrabber/marker/etc not part of junifer core, some of this variables might not be needed or should definitely be removed."

I don't follow. What happens if one adds "with" key to the "meta" dictionary passed without loading the external components?

@fraimondo

Copy link
Copy Markdown
Contributor

Do we know how this function works when we have external imports (with statements in the YAML)?

It copies exactly what is passed. For julio, we copy exactly what is stored in h5.

In order to create the yaml, we parse the metadata and instantiate the object. I'm not sure this will be possible if the definition of a datagrabber/marker is in an external package which is part of the with statement.

Let me lay it down w.r.t. julio:

* junifer YAML gets parsed by `junifer.api.parse_yaml` which loads the external modules and registers componenets as needed.

* If `with` is present in the junifer YAML, julio adds it directly to the generated YAML.

* As the external components are already registered, `junifer.api.generate_yaml` can load them from component registry and dump as needed.

For using junifer.api.generate_yaml without julio, one would need to already register the components beforehand. Now, as it's part of junifer.api, we can presume that the user will do it like so.
Does that solve your concern?

junifer.api.generate_yaml has one parameter that is a meta dict. This meta dict can be extracted from and HDF5 file, without parsing any yaml. We need to be able to support generating a YAML from any meta dict, no only after parsing the respective yaml.
Even if this means adding all variables int he meta dict and a huge comment stating that "since this is an external datagrabber/marker/etc not part of junifer core, some of this variables might not be needed or should definitely be removed."

I don't follow. What happens if one adds "with" key to the "meta" dictionary passed without loading the external components?

Let's asume you open and HDF5 file, you load the meta and then you pass it to generate_yaml. At no point there was a parse_yaml call, so all external modules will not be loaded. This code will fail as it needs to instantiate the elements in the meta to generate the yaml.

This use case should be considered. In the case that the object can't be instantiated, the fields should be extracted from the meta dict and a comment in the yaml should be added.

@synchon

synchon commented Jul 23, 2026

Copy link
Copy Markdown
Member Author

Do we know how this function works when we have external imports (with statements in the YAML)?

It copies exactly what is passed. For julio, we copy exactly what is stored in h5.

In order to create the yaml, we parse the metadata and instantiate the object. I'm not sure this will be possible if the definition of a datagrabber/marker is in an external package which is part of the with statement.

Let me lay it down w.r.t. julio:

* junifer YAML gets parsed by `junifer.api.parse_yaml` which loads the external modules and registers componenets as needed.

* If `with` is present in the junifer YAML, julio adds it directly to the generated YAML.

* As the external components are already registered, `junifer.api.generate_yaml` can load them from component registry and dump as needed.

For using junifer.api.generate_yaml without julio, one would need to already register the components beforehand. Now, as it's part of junifer.api, we can presume that the user will do it like so.
Does that solve your concern?

junifer.api.generate_yaml has one parameter that is a meta dict. This meta dict can be extracted from and HDF5 file, without parsing any yaml. We need to be able to support generating a YAML from any meta dict, no only after parsing the respective yaml.
Even if this means adding all variables int he meta dict and a huge comment stating that "since this is an external datagrabber/marker/etc not part of junifer core, some of this variables might not be needed or should definitely be removed."

I don't follow. What happens if one adds "with" key to the "meta" dictionary passed without loading the external components?

Let's asume you open and HDF5 file, you load the meta and then you pass it to generate_yaml. At no point there was a parse_yaml call, so all external modules will not be loaded. This code will fail as it needs to instantiate the elements in the meta to generate the yaml.

This use case should be considered. In the case that the object can't be instantiated, the fields should be extracted from the meta dict and a comment in the yaml should be added.

Do the latest commits address your concern?

Comment thread junifer/api/functions.py Outdated
# Set datagrabber
meta_dg = meta["datagrabber"].copy()
a = meta_dg.pop("class")
try:

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.

I would explicity check for the datagrabber being in the registry that relying on a ValueError.

Could be that because of versions mismatchs, some parameters are renamed and then we do have errors but because of other reasons.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The check is updated to be precise now. Also, open to go the non-idiomatic route as well.

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.

We still rely on a ValueError. It should be something like

if component is registered:
Instantiate and dump
else:

  • add ALL variables in the meta to the yaml
  • Add the note: " is not a built-in component and thus could not be properly regenerated. Some of these entries in the YAML section might be redundant and not needed. Please check the documentation/implementation of this specific datagrabber and remove the unnecesary entries."

Comment thread junifer/api/functions.py Outdated
meta_p = [meta_p]
for mp in meta_p:
b = mp.pop("class")
try:

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.

Same here, explicit check

Comment thread junifer/api/functions.py
c = meta_m.pop("class")
y["markers"] = []
try:
m = PipelineComponentRegistry().get_class(step="marker", name=c)

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.

Same here

Comment thread junifer/api/functions.py Outdated
"- `datadir` is ignored and not reproduced. "
"If `datadir` used was not a temporary directory, you will have to "
"manually edit this YAML.\n"
"- In case the dataset was 'dirty', there is no guarantee that the "

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.

This note should only appear if the dataset was dirty (the meta said so)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Updated now.

Comment thread junifer/api/functions.py Outdated
# Set datagrabber
meta_dg = meta["datagrabber"].copy()
a = meta_dg.pop("class")
try:

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.

We still rely on a ValueError. It should be something like

if component is registered:
Instantiate and dump
else:

  • add ALL variables in the meta to the yaml
  • Add the note: " is not a built-in component and thus could not be properly regenerated. Some of these entries in the YAML section might be redundant and not needed. Please check the documentation/implementation of this specific datagrabber and remove the unnecesary entries."

@synchon

synchon commented Jul 23, 2026

Copy link
Copy Markdown
Member Author

We still rely on a ValueError.

ValueError is only raised if the component is not registered. try...except is the "idiomatic" way to do it. I understand if that doesn't work and it needs to be superfluous.

@fraimondo

Copy link
Copy Markdown
Contributor

We still rely on a ValueError.

ValueError is only raised if the component is not registered. try...except is the "idiomatic" way to do it. I understand if that doesn't work and it needs to be superfluous.

In that case, if any part of the instantiation raises a ValueError (like would happen if a parameter changes options, or using an old junifer version), then we go to:

  • add ALL variables in the meta to the yaml
  • Add the note: " is not a built-in component or it failed to instantiate and thus could not be properly regenerated. Some of these entries in the YAML section might be redundant and not needed. Please check the documentation/implementation of this specific datagrabber and remove the unnecesary

@synchon

synchon commented Jul 23, 2026

Copy link
Copy Markdown
Member Author

We still rely on a ValueError.

ValueError is only raised if the component is not registered. try...except is the "idiomatic" way to do it. I understand if that doesn't work and it needs to be superfluous.

In that case, if any part of the instantiation raises a ValueError (like would happen if a parameter changes options, or using an old junifer version), then we go to:

  • add ALL variables in the meta to the yaml
  • Add the note: " is not a built-in component or it failed to instantiate and thus could not be properly regenerated. Some of these entries in the YAML section might be redundant and not needed. Please check the documentation/implementation of this specific datagrabber and remove the unnecesary

model_construct does not raise an exception so there will be no error during the model construction.

@fraimondo

Copy link
Copy Markdown
Contributor

Can we validate? I'm worried about using different junifer versions than the one that generated the meta. Or we either go full strict and not allow any mismatch (which will create a problem with julio later on), or we validate the model. Otherwise, variables that do not match will be "ignored" and not "dumped", which might yield a different yaml than expected.

I prefer to have a YAML with a note saying "check your datagrabber/marker/preprocessor due to possible changes in the API" than one without any message that actually works differently than expected.

@synchon

synchon commented Jul 23, 2026

Copy link
Copy Markdown
Member Author

Can we validate? I'm worried about using different junifer versions than the one that generated the meta. Or we either go full strict and not allow any mismatch (which will create a problem with julio later on), or we validate the model. Otherwise, variables that do not match will be "ignored" and not "dumped", which might yield a different yaml than expected.

I prefer to have a YAML with a note saying "check your datagrabber/marker/preprocessor due to possible changes in the API" than one without any message that actually works differently than expected.

model_construct is now replaced with model_validate which will fail for most due to the nature of the metadata and the models. Necessary comments will be added on generation.

@fraimondo

Copy link
Copy Markdown
Contributor

Can we validate? I'm worried about using different junifer versions than the one that generated the meta. Or we either go full strict and not allow any mismatch (which will create a problem with julio later on), or we validate the model. Otherwise, variables that do not match will be "ignored" and not "dumped", which might yield a different yaml than expected.
I prefer to have a YAML with a note saying "check your datagrabber/marker/preprocessor due to possible changes in the API" than one without any message that actually works differently than expected.

model_construct is now replaced with model_validate which will fail for most due to the nature of the metadata and the models. Necessary comments will be added on generation.

I still don't understand why it will fail for "most". As long as you choose the dump_fields and pass it to the constructor, this should recreate the same object.

It should fail in case of:

  • External components
  • Components that changed API and the meta is from a non-compatible version.

All the rest should not fail. Otherwise we are dumping the wrong variables.

@synchon

synchon commented Jul 23, 2026

Copy link
Copy Markdown
Member Author

Can we validate? I'm worried about using different junifer versions than the one that generated the meta. Or we either go full strict and not allow any mismatch (which will create a problem with julio later on), or we validate the model. Otherwise, variables that do not match will be "ignored" and not "dumped", which might yield a different yaml than expected.
I prefer to have a YAML with a note saying "check your datagrabber/marker/preprocessor due to possible changes in the API" than one without any message that actually works differently than expected.

model_construct is now replaced with model_validate which will fail for most due to the nature of the metadata and the models. Necessary comments will be added on generation.

I still don't understand why it will fail for "most". As long as you choose the dump_fields and pass it to the constructor, this should recreate the same object.

It should fail in case of:

  • External components
  • Components that changed API and the meta is from a non-compatible version.

All the rest should not fail. Otherwise we are dumping the wrong variables.

Ignore my previous reply's "fail" part, it works as intended.

Comment thread junifer/api/tests/test_functions.py Outdated
},
],
)
def test_generate_yaml(m: dict) -> None:

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.

So this tests that the actual function works. Can we test for correctness?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The latest commit should check for basic correctness.

@synchon
synchon force-pushed the feat/generate-yaml-api branch from 97a5c10 to ca6cc8a Compare July 28, 2026 10:26
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.

2 participants