Skip to content

Collect water module settings into Config dataclass#509

Merged
Wegatriespython merged 6 commits into
iiasa:mainfrom
Wegatriespython:wr/pr-c-water-config
May 26, 2026
Merged

Collect water module settings into Config dataclass#509
Wegatriespython merged 6 commits into
iiasa:mainfrom
Wegatriespython:wr/pr-c-water-config

Conversation

@Wegatriespython

@Wegatriespython Wegatriespython commented May 15, 2026

Copy link
Copy Markdown
Contributor

Introduce message_ix_models.model.water.Config and move water-module state from loose top-level Context attributes to context.water.

The CLI now initializes and updates this config object for water options such as RCP, SDG, REL, time slices, region type, and reduced-basin settings. Build, data, and utility code read the same settings through Config.from_context(context), matching the configuration pattern already used by transport and reporting. Following recommendation at the end of #432 from @khaeru

Also adds a fix for nested CLI option handling so an omitted subcommand --ssp does not clear a value already set on the parent water-ix command.

How to review

Look at the diff, check if the change to click.py might have un-intended side effects for other workflows.

PR checklist

  • Continuous integration checks all ✅
  • Add or expand tests; coverage checks both ✅
  • Add, expand, or update documentation.
  • Update doc/whatsnew.

@Wegatriespython Wegatriespython added the safe to test PRs from forks that do not pose security risks label May 15, 2026
@codecov

codecov Bot commented May 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.97790% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.2%. Comparing base (d47ec6e) to head (6110b41).

Files with missing lines Patch % Lines
message_ix_models/model/water/cli.py 4.0% 24 Missing ⚠️
message_ix_models/model/water/data/demands.py 81.8% 2 Missing ⚠️
message_ix_models/model/water/build.py 88.8% 1 Missing ⚠️
message_ix_models/model/water/data/__init__.py 50.0% 1 Missing ⚠️
message_ix_models/model/water/data/water_supply.py 94.4% 1 Missing ⚠️
Additional details and impacted files
@@          Coverage Diff          @@
##            main    #509   +/-   ##
=====================================
  Coverage   74.1%   74.2%           
=====================================
  Files        318     320    +2     
  Lines      25566   25655   +89     
=====================================
+ Hits       18962   19047   +85     
- Misses      6604    6608    +4     
Files with missing lines Coverage Δ
message_ix_models/model/water/__init__.py 100.0% <100.0%> (ø)
message_ix_models/model/water/config.py 100.0% <100.0%> (ø)
...ssage_ix_models/model/water/data/infrastructure.py 100.0% <100.0%> (ø)
message_ix_models/model/water/data/irrigation.py 100.0% <100.0%> (ø)
...essage_ix_models/model/water/data/water_for_ppl.py 91.6% <100.0%> (+0.1%) ⬆️
message_ix_models/model/water/utils.py 77.0% <100.0%> (+0.1%) ⬆️
message_ix_models/tests/model/water/conftest.py 98.8% <100.0%> (+<0.1%) ⬆️
message_ix_models/tests/model/water/test_build.py 98.1% <100.0%> (+0.1%) ⬆️
message_ix_models/tests/model/water/test_config.py 100.0% <100.0%> (ø)
message_ix_models/tests/model/water/test_report.py 97.3% <100.0%> (+<0.1%) ⬆️
... and 8 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@khaeru khaeru left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So far this looks great! I guess IDE tools can now jump from cfg.x references in the various functions to the places where Config.x are defined.

I see the RTD build is failing, but that is because of bugs upstream in ixmp4/pyam-iamc that I will try separately to mitigate. Those mitigations might belong here, or in message_ix or ixmp; whatever the case I'll notify to either re-run or rebase once they are in place.

Also adds a fix for nested CLI option handling so an omitted subcommand --ssp does not clear a value already set on the parent water-ix command.

I see how this fix works, but I am struggling a little bit to think about use cases. It points to possibilities like this:

mix-models foo            bar            …  # (1)
mix-models foo --ssp=SSP1 bar            …  # (2)
mix-models foo            bar --ssp=SSP1 …  # (3)
mix-models foo --ssp=SSP1 bar --ssp=SSP1 …  # (4)
mix-models foo --ssp=SSP1 bar --ssp=SSP2 …  # (5)

What would be a case of 'foo' and 'bar' such that (2) and (3) need to be distinguished? Where (4) should change behaviour vs (2) and (3)? Where (5) must be supported?

assert "ZMB" == result.output.strip()


def test_scenario_param_preserves_outer_value(mix_models_cli):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Adding type hints to the signature (even -> None: at the end of line) would allow mypy to check the contents.

results = {}

# Reference to the water configuration
cfg = Config.from_context(context)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One thing that's worth thinking about briefly: is it the duty of these individual functions, or of higher-level code, to ensure the Context instance is properly prepared with a Config instance?

If the latter, this code could simply do:

cfg: "Config" = context.water

…which would raise an exception if parent/calling code had not set up the proper context.

I believe it should work fine either way, since the way .water.Config.from_context() is currently written ensures that 2nd, 3rd, etc. calls will always get the same Config instance as the first (thus, the same settings). (It does differ slightly from .transport.Config.from_context(), which always creates a new instance. But IMO this difference is fine, for now, as long as the method docstrings are clear.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think making it stricter makes sense, but currently there are a fair bit of changes coming up on water and I would like to leave it more defensive, with a docstring update.

"""Settings for :mod:`message_ix_models.model.water`."""

#: Water build variant.
nexus_set: Literal["cooling", "nexus"] = "nexus"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is great for now. In some cases it might make sense, in the future, to add an Enum, which can also help to sanitize user input:

class NexusSet(Enum):
    #: (can add verbose description here)
    cooling = auto()
    #: (ditto)
    nexus = auto()

class Config(ConfigHelper):
    nexus_set: NexusSet = NexusSet.nexus

    def __post_init__(self) -> None:
        if isinstance(self.nexus_set, str):
            self.nexus_set = NexusSet[self.nexus_set]

@Wegatriespython

Copy link
Copy Markdown
Contributor Author

So the issue is more the water cli takes in some redundancies for options, which would be good to clean up as well.

Currently for the water-ix group, --ssp uses store_context, which (pre-fix) when passed with water-ix --ssp=SSP1 nexus would result in None from the missing ssp option to the nexus sub-command resetting the parent's value.

With the fix, 2 and 3 will produce SSP1 and 5 will deliver SSP2.

mix-models foo            bar            …  # (1)
mix-models foo --ssp=SSP1 bar            …  # (2)
mix-models foo            bar --ssp=SSP1 …  # (3)
mix-models foo --ssp=SSP1 bar --ssp=SSP1 …  # (4)
mix-models foo --ssp=SSP1 bar --ssp=SSP2 …  # (5)

@khaeru

khaeru commented May 18, 2026

Copy link
Copy Markdown
Member

So the issue is more the water cli takes in some redundancies for options, which would be good to clean up as well.

Currently for the water-ix group, --ssp uses store_context, which (pre-fix) when passed with water-ix --ssp=SSP1 nexus would result in None from the missing ssp option to the nexus sub-command resetting the parent's value.

OK, agreed that cleaning up those redundancies (in a future PR) would be the preferred approach, but again the current fix is good for the code as it is.

In general/the long run, my preference would be:

  • It would be an error to give the same option twice, as in example (4) or (5).
  • For simplicity, we stick with the default click behaviour that each option name is attached to 1 specific group or (sub)command in the hierarchy, and must appear between the group/command name and the following subcommand (if any). We clearly document for users where each option must occur.
  • There may be cases where two options with similar meaning are needed, but then we use different strings to distinguish them. For example: mix-models foo-project --regions=R12 prep-data --from-regions=R99 could mean "take data from R99 and prepare data for an R12 model".

@adrivinca adrivinca left a comment

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.

Also had a look. great tidy up!
I think it can be merged if the test failures are solved (and not due to changes here)

@khaeru

khaeru commented May 18, 2026

Copy link
Copy Markdown
Member

I see the RTD build is failing, but that is because of bugs upstream in ixmp4/pyam-iamc that I will try separately to mitigate. Those mitigations might belong here, or in message_ix or ixmp; whatever the case I'll notify to either re-run or rebase once they are in place.

Now hopefully done in #511. Please rebase on main and let me know if that fails to fix CI issues.

Add message_ix_models.model.water.config.Config holding water-module
settings/state (RCP, SDG, REL, time, type_reg, map_ISO_c, nexus_set,
reduced-basin settings, valid_basins, all_nodes). Build/data/utility
code reads Config.from_context(context); the CLI populates context.water
at setup. Config.apply() remains as an explicit backward-compat helper
for callers that build a Context directly.
store_context() now skips assignment when value is None and the
parameter is already set on the Context. Keeps a group-level
`water-ix --ssp SSPx` alive when a subcommand declares its own --ssp
with default=None; the subcommand callback used to overwrite the
group's value at the end of parsing.
@khaeru khaeru added the water MESSAGEix-Nexus (water) variant label May 22, 2026
@Wegatriespython Wegatriespython merged commit b6511ef into iiasa:main May 26, 2026
45 of 46 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

safe to test PRs from forks that do not pose security risks water MESSAGEix-Nexus (water) variant

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants