feat: cleanup config classes#1372
Conversation
…romtModelStep into 1 class with a runtime function: 'bind_to_model'
There was a problem hiding this comment.
Pull request overview
This PR refactors configuration-related classes to improve clarity and maintainability by renaming classes and attributes to better reflect their purpose. The changes primarily involve renaming HydromtComponentConfig, HydromtGlobalConfig, HydromtModelSetup, HydromtModelStep, and RawStep to more descriptive names (ComponentSpec, ModelSpec, WorkflowSpec, and WorkflowStep), and renaming the args parameter to kwargs throughout the codebase.
Changes:
- Renamed configuration classes to more intuitive names (e.g.,
HydromtModelSetup→WorkflowSpec) - Changed
argsparameter tokwargsto better reflect Python conventions - Refactored step execution logic by replacing
to_model_step()withbind_to_model()and consolidating step binding and execution
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
hydromt/_validators/model_config.py |
Renamed classes (ComponentSpec, ModelSpec, WorkflowSpec, WorkflowStep) and refactored step binding logic |
hydromt/model/model.py |
Updated imports and simplified step execution by using list comprehensions |
hydromt/parsers.py |
Updated imports and class references to use new names |
hydromt/readers.py |
Updated import and return type annotation to WorkflowSpec |
hydromt/_validators/__init__.py |
Updated exports to use new class names |
tests/_validators/test_model_config.py |
Updated all test cases to use new class names and test the refactored API |
tests/test_readers.py |
Changed args to kwargs in test assertion |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| [step.bind_to_model(self) for step in raw_steps] | ||
| [step.execute() for step in raw_steps] |
There was a problem hiding this comment.
Replace list comprehensions with for loops. List comprehensions are intended for creating new lists, not for executing side effects. Use explicit for loops when calling methods solely for their side effects.
| [step.bind_to_model(self) for step in raw_steps] | |
| [step.execute() for step in raw_steps] | |
| for step in raw_steps: | |
| step.bind_to_model(self) | |
| for step in raw_steps: | |
| step.execute() |
| [step.bind_to_model(self) for step in raw_steps] | ||
| [step.execute() for step in raw_steps] |
There was a problem hiding this comment.
Replace list comprehensions with for loops. List comprehensions are intended for creating new lists, not for executing side effects. Use explicit for loops when calling methods solely for their side effects.
| [step.bind_to_model(self) for step in raw_steps] | |
| [step.execute() for step in raw_steps] | |
| for step in raw_steps: | |
| step.bind_to_model(self) | |
| for step in raw_steps: | |
| step.execute() |
hboisgon
left a comment
There was a problem hiding this comment.
Comments are mixed between this PR and the other one. I saw this one after I started reviewing. Still not fully sure what to think about this refactor. I guess better to validate when reading the yaml before calling build and update to get errors a little earlier but wonder if this does not make it harder to use for our users. Definitely requires docs.
Also would be nide to have a step back on what we are trying to do and steps to build/update a model with hydromt and terminology?
It was an important comment when we had an external audit on the code but the CLI build function is not linked to a model function ie CLI hydromt build does extra things including read_workflow_yaml which model.build does not do. Maybe moving validation around now highlights we should definitely work on this. Hence would be good to step back from the implementation and think again about what we are trying to do when building a model.
There was a problem hiding this comment.
See my comment in the other PR. I think users should know the content of the these classes if this is what they get when calling the read_workflow_yaml function. This should move into hydromt.model and be renamed to model_workflow.py to avoid confusion with the ConfigComponent
| raise ValueError( | ||
| f"Model instance of type {type(model_instance).__name__} does not have 'components' attribute" | ||
| ) | ||
| elif not isinstance(model_instance.components, dict): |
There was a problem hiding this comment.
Is this case at all possible?
|
|
||
| class RawStep(BaseModel): | ||
| """Represents the YAML structure BEFORE runtime wiring.""" | ||
| class WorkflowStep(BaseModel): |
There was a problem hiding this comment.
I thought HydroMTModelStep was clearer also with the @hydromt_step decorator. Again steps can be defined and called outside of the workflow yaml so we maybe are too restrictive by using the name Workflow here?
| kwargs = self.args or {} | ||
|
|
||
| """Execute this workflow step by calling the bound function with the provided arguments.""" | ||
| if self._method is None: |
There was a problem hiding this comment.
Can't you first try and call bind_to_model and then if still None return an error? I'm a little fuzy about this works still when calling model.build and model.update sorry.
| bound_args = { | ||
| param: p.default | ||
| for param, p in sig.parameters.items() | ||
| if p.default != inspect._empty | ||
| } | ||
| bound_args.update(kwargs) |
There was a problem hiding this comment.
Is this still happening somewhere? It's important for reproducibility if the complete function arguments used appera in the log and not just the one the user provided/changed.
| class HydromtGlobalConfig(BaseModel): | ||
| components: list[HydromtComponentConfig] = Field(default_factory=list) | ||
| data_libs: list[str | Path] = Field(default_factory=list) | ||
| components: list[ComponentSpec] = Field(default_factory=list) |
There was a problem hiding this comment.
I never looked into this before but why a list? In both model init and in the workflow yaml they are defined as dictionnaries so why change the type here?
| The `modeltype` field specifies the model class to instantiate, which can be given as a string referring to a registered model plugin or as the class itself. | ||
| The `globals_` field contains the model configuration that will be passed as kwargs to the model constructor when instantiating the model. | ||
| The `steps` field is a list of workflow steps that will be executed in order as part of the model run. |
There was a problem hiding this comment.
I dont think the documentation is clear enough for users to be able to use whatever comes out of read_workflow_yaml.
modeltype is "wflow_sbm" or WflowSbmModel (second).
globals: will any argument of model.init be a property that you can call and keep the type model init expects? I see components is a list here and model init wants a dict? WflowSbmModel has extra a config_filename. How is this dealt with? If a model has extra arguments in init that also should be converted from relative to absolute paths how will this work? Do plugins need to extend this class? If so this should be documented in the plugins docs.
steps: steps won't really be a list of dict anymore right? already the step name is added with name property to the arguments dict? How can users modify steps arguments before calling model.build or model.update if they need to? Not that I mind working with python classes instead of dict but I do need documentation then.
| modeltype: type | ||
| globals_: HydromtGlobalConfig = Field(alias="global") | ||
| steps: list[RawStep] | ||
| class WorkflowSpec(BaseModel): |
There was a problem hiding this comment.
Same as above, I'm not sure workflow is the right name? Is ModelStepSpec or StepSpec clearer.
|
I haven't done a full review myself yet, but I did have a conversation with Luuk before he started on this PR. I understand Helene's concern that validation is now separate from the actual model building. One of the things that I discussed with Luuk was to make the core |
This PR is meant as a suggested improvement to #1365 to make reviewing these changes easier.
Issue addressed
Fixes #
Explanation
Explain how you addressed the bug/feature request, what choices you made and why.
General Checklist
mainData/Catalog checklist
data/catalogs/predefined_catalogs.ymlhas not been modified.data_catalog.ymlfiles have been changeddata/changelog.rsthas been updatedLFline endings (done automatically if you usedupdate_versions.py)Additional Notes (optional)
Add any additional notes or information that may be helpful.