Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pytest-snapshots.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ concurrency:

env:
gams-version: 43.4.1
python-version: "3.13"
python-version: "3.14"
upstream: main

jobs:
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/pytest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ defaults:
env:
gams-version: 43.4.1
label: "safe to test"
python-version: "3.13"
python-version: "3.14"

jobs:
check:
Expand Down Expand Up @@ -98,13 +98,13 @@ jobs:
# - Latest supported Python version for those or other dependencies.
# Minimum version given in pyproject.toml + earlier version of Python
# For this job only, the oldest version of Python supported by message-ix-models
- { upstream: v3.8.0, python: "3.9" } # Released 2024-01-12
- { upstream: v3.9.0, python: "3.13" } # 2024-06-04
- { upstream: v3.10.0, python: "3.13" } # 2025-02-21
- { upstream: v3.8.0, python: "3.10" } # Released 2024-01-12
- { upstream: v3.9.0, python: "3.14" } # 2024-06-04
- { upstream: v3.10.0, python: "3.14" } # 2025-02-21
# Latest released version + latest released Python
- { upstream: v3.11.0, python: "3.13" } # 2025-05-27
- { upstream: v3.11.0, python: "3.14" } # 2025-05-27
# Development version + latest released Python
- { upstream: main, python: "3.13" }
- { upstream: main, python: "3.14" }

exclude:
# Specific version combinations that are invalid / not to be used
Expand Down
4 changes: 2 additions & 2 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import os
from pathlib import Path
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING

if TYPE_CHECKING:
import sphinx.application
Expand Down Expand Up @@ -186,7 +186,7 @@ def setup(app: "sphinx.application.Sphinx") -> None:
# -- Options for sphinx.ext.intersphinx ------------------------------------------------


def local_inv(name: str, *parts: str) -> Optional[str]:
def local_inv(name: str, *parts: str) -> str | None:
"""Construct the path to a local intersphinx inventory."""
if 0 == len(parts):
parts = ("doc", "_build", "html")
Expand Down
2 changes: 2 additions & 0 deletions doc/whatsnew.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ What's new
Next release
============

- :mod:`message_ix_models` is tested and compatible with `Python 3.14 <https://www.python.org/downloads/release/python-3140/>`__ (:pull:`443`).
- Support for Python 3.9 is dropped (:pull:`443`), as it has reached end-of-life.
- Fix water module reporting failures and improve calculations (:issue:`407`, :pull:`396`).
- Improve and extend :doc:`/material/index` (:pull:`418`).
See :doc:`version 1.2.0 </material/v1.2.0>` for details.
Expand Down
5 changes: 2 additions & 3 deletions message_ix_models/model/build.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import logging
from collections.abc import Callable, Mapping
from typing import Optional, Union

import ixmp
import pandas as pd
Expand Down Expand Up @@ -30,8 +29,8 @@ def _add_unit(mp: ixmp.Platform, unit: str, comment: str) -> None:
# FIXME Reduce complexity from 14 to ≤13
def apply_spec( # noqa: C901
scenario: Scenario,
spec: Union[Spec, Mapping[str, ScenarioInfo]],
data: Optional[Callable] = None,
spec: Spec | Mapping[str, ScenarioInfo],
data: Callable | None = None,
**options,
):
"""Apply `spec` to `scenario`.
Expand Down
4 changes: 2 additions & 2 deletions message_ix_models/model/buildings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import logging
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Optional, cast
from typing import Any, cast

import ixmp
import message_ix
Expand Down Expand Up @@ -92,7 +92,7 @@ class Config:
with_materials: bool = True

#: Path for STURM output.
_output_path: Optional[Path] = None
_output_path: Path | None = None

#: Run the ACCESS model on every iteration.
run_access: bool = False
Expand Down
4 changes: 2 additions & 2 deletions message_ix_models/model/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING

from message_ix_models.util.config import ConfigHelper
from message_ix_models.util.node import identify_nodes
Expand Down Expand Up @@ -73,7 +73,7 @@ def check(self):
)

def regions_from_scenario(
self, scenario: Optional["message_ix.Scenario"] = None
self, scenario: "message_ix.Scenario | None" = None
) -> None:
"""Update :attr:`regions` by inspection of an existing `scenario`.
Expand Down
8 changes: 4 additions & 4 deletions message_ix_models/model/emissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING

import genno
import pandas as pd
Expand Down Expand Up @@ -73,7 +73,7 @@ def get(self) -> "AnyQuantity":
return load_file(self.path, dims=dims)


def get_emission_factors(units: Optional[str] = None) -> "AnyQuantity":
def get_emission_factors(units: str | None = None) -> "AnyQuantity":
"""Return carbon emission factors.
Values are from the file :file:`message_ix_models/data/ipcc/1996_v3_t1-2.csv`, in
Expand Down Expand Up @@ -152,7 +152,7 @@ def get_emission_factors(units: Optional[str] = None) -> "AnyQuantity":
def add_tax_emission(
scen: Scenario,
price: float,
conversion_factor: Optional[float] = None,
conversion_factor: float | None = None,
drate_parameter="drate",
) -> None:
"""Add a global CO₂ price to `scen`.
Expand Down Expand Up @@ -217,7 +217,7 @@ def add_tax_emission(
scen.add_par(name, data)


def split_species(unit_expr: str) -> tuple[str, Optional[str]]:
def split_species(unit_expr: str) -> tuple[str, str | None]:
"""Split `unit_expr` to an expression without a unit mention, and maybe species."""
if match := re.fullmatch("(.*)(CO2|C)(.*)", unit_expr):
return f"{match.group(1)}{match.group(3)}", match.group(2)
Expand Down
6 changes: 3 additions & 3 deletions message_ix_models/model/macro.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from functools import lru_cache
from itertools import product
from pathlib import Path
from typing import TYPE_CHECKING, Literal, Optional, Union
from typing import TYPE_CHECKING, Literal

import pandas as pd

Expand All @@ -30,8 +30,8 @@
def generate(
parameter: Literal["aeei", "config", "depr", "drate", "lotol"],
context: "Context",
commodities: Union[list[str], list["Code"]] = COMMODITY,
value: Optional[float] = None,
commodities: list[str] | list["Code"] = COMMODITY,
value: float | None = None,
) -> pd.DataFrame:
"""Generate uniform data for one :mod:`message_ix.macro` `parameter`.
Expand Down
4 changes: 2 additions & 2 deletions message_ix_models/model/material/build.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
from collections.abc import Mapping
from typing import Any, Optional
from typing import Any

import message_ix

Expand Down Expand Up @@ -89,7 +89,7 @@ def build(
scenario: message_ix.Scenario,
old_calib: bool,
modify_existing_constraints: bool = True,
iea_data_path: Optional[str] = None,
iea_data_path: str | None = None,
) -> message_ix.Scenario:
"""Set up materials accounting on `scenario`."""
node_suffix = context.model.regions
Expand Down
4 changes: 2 additions & 2 deletions message_ix_models/model/material/data_ammonia.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
for ammonia fertilizer technologies, demand, trade, and related constraints.
"""

from typing import TYPE_CHECKING, Any, Union, cast
from typing import TYPE_CHECKING, Any, cast

import numpy as np
import pandas as pd
Expand Down Expand Up @@ -386,7 +386,7 @@ def set_exp_imp_nodes(df: pd.DataFrame) -> None:
df.loc[df["technology"].str.contains("import"), "node_origin"] = "R12_GLB"


def read_demand() -> dict[str, Union[pd.DataFrame, pd.Series]]:
def read_demand() -> dict[str, pd.DataFrame | pd.Series]:
"""Read and clean demand and trade data for ammonia fertilizer."""

N_demand_GLO = pd.read_csv(
Expand Down
4 changes: 2 additions & 2 deletions message_ix_models/model/material/data_other_industry.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"""

import os
from typing import TYPE_CHECKING, List, Union
from typing import TYPE_CHECKING, List

import pandas as pd
from message_ix.util import make_df
Expand Down Expand Up @@ -342,7 +342,7 @@ def modify_demand_and_hist_activity(scen: "Scenario") -> None:


def get_hist_act_data(
map_fname: str, iea_data_path: str, years: Union[List[int], None] = None
map_fname: str, iea_data_path: str, years: List[int] | None = None
) -> pd.DataFrame:
"""Reads IEA DB, maps and aggregates variables to MESSAGE technologies.
Expand Down
4 changes: 2 additions & 2 deletions message_ix_models/model/material/data_petro.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"""

from collections import defaultdict
from typing import TYPE_CHECKING, Union
from typing import TYPE_CHECKING

import pandas as pd
from message_ix import make_df
Expand Down Expand Up @@ -253,7 +253,7 @@ def assign_input_outpt(
split: str,
param_name: str,
regions: pd.DataFrame,
val: Union[float, int],
val: float | int,
t: str,
rg: str,
global_region: str,
Expand Down
10 changes: 5 additions & 5 deletions message_ix_models/model/material/data_util.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from collections.abc import Mapping
from functools import lru_cache
from typing import TYPE_CHECKING, Literal, Optional, Union
from typing import TYPE_CHECKING, Literal

import message_ix
import numpy as np
Expand Down Expand Up @@ -574,7 +574,7 @@ def add_emission_accounting(scen: message_ix.Scenario) -> None:


def read_sector_data(
scenario: message_ix.Scenario, sectname: str, ssp: Optional[str], filename: str
scenario: message_ix.Scenario, sectname: str, ssp: str | None, filename: str
) -> pd.DataFrame:
"""Read sector data for industry with `sectname`.
Expand Down Expand Up @@ -657,7 +657,7 @@ def read_sector_data(


def read_timeseries(
scenario: message_ix.Scenario, material: str, ssp: Optional[str], filename: str
scenario: message_ix.Scenario, material: str, ssp: str | None, filename: str
) -> pd.DataFrame:
"""Read ‘time-series’ data from a material-specific `filename`.
Expand Down Expand Up @@ -700,7 +700,7 @@ def read_timeseries(
df = pd.read_csv(package_data_path("material", material, filename))

# Function to convert string to integer if it is a digit
def convert_if_digit(col_name: Union[str, int]) -> Union[str, int]:
def convert_if_digit(col_name: str | int) -> str | int:
return int(col_name) if col_name.isdigit() else col_name

# Apply the function to the DataFrame column names
Expand Down Expand Up @@ -735,7 +735,7 @@ def convert_if_digit(col_name: Union[str, int]) -> Union[str, int]:


def read_rel(
scenario: message_ix.Scenario, material: str, ssp: Optional[str], filename: str
scenario: message_ix.Scenario, material: str, ssp: str | None, filename: str
) -> pd.DataFrame:
"""
Read ``relation_*`` type parameter data for specific industry
Expand Down
10 changes: 5 additions & 5 deletions message_ix_models/model/material/demand/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
from pathlib import Path
from typing import TYPE_CHECKING, Callable, List, Literal, Union
from typing import TYPE_CHECKING, Callable, List, Literal

import numpy as np
import pandas as pd
Expand Down Expand Up @@ -96,7 +96,7 @@


def read_timer_pop(
datapath: Union[str, Path], material: Literal["cement", "steel", "aluminum"]
datapath: str | Path, material: Literal["cement", "steel", "aluminum"]
):
"""Read population data for a given material from TIMER model.
Expand Down Expand Up @@ -126,7 +126,7 @@ def read_timer_pop(


def read_timer_gdp(
datapath: Union[str, Path], material: Literal["cement", "steel", "aluminum"]
datapath: str | Path, material: Literal["cement", "steel", "aluminum"]
):
"""Read GDP per capita data for a given material from TIMER Excel files.
Expand Down Expand Up @@ -221,7 +221,7 @@ def project_demand(df: pd.DataFrame, phi: float, mu: float):
return df_demand[["region", "year", "demand_tot"]]


def read_base_demand(filepath: Union[str, Path]):
def read_base_demand(filepath: str | Path):
"""Read base year demand data from a YAML file.
Parameters
Expand Down Expand Up @@ -517,7 +517,7 @@ def prepare_model_input(
def derive_demand(
material: Literal["cement", "steel", "aluminum"],
scen: "Scenario",
ssp: Union[Literal["SSP1", "SSP2", "SSP3", "SSP4", "SSP5", "LED"], str] = "SSP2",
ssp: Literal["SSP1", "SSP2", "SSP3", "SSP4", "SSP5", "LED"] | str = "SSP2",
new: bool = False,
model: Literal["least-squares", "quantile"] = "least-squares",
):
Expand Down
4 changes: 1 addition & 3 deletions message_ix_models/model/material/demand/math_util.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import Union

import numpy as np
import pandas as pd

Expand All @@ -18,5 +16,5 @@ def cement_function(x: tuple[pd.Series, pd.Series], a: float, b: float):
return a * np.exp(b / gdp_pcap)


def gompertz(phi: float, mu: float, y: Union[pd.Series, float], baseyear: int = 2020):
def gompertz(phi: float, mu: float, y: pd.Series | float, baseyear: int = 2020):
return 1 - np.exp(-phi * np.exp(-mu * (y - baseyear)))
4 changes: 2 additions & 2 deletions message_ix_models/model/material/report/run_reporting.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Union
from typing import List

import message_ix
import numpy as np
Expand Down Expand Up @@ -695,7 +695,7 @@ def run_all_categories(
def run(
scenario: message_ix.Scenario,
upload_ts: bool = False,
region: Union[bool, str] = False,
region: bool | str = False,
) -> pyam.IamDataFrame:
"""Run industry reporter for a given scenario.
Expand Down
8 changes: 4 additions & 4 deletions message_ix_models/model/material/share_constraints.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, List, Literal, Union
from typing import TYPE_CHECKING, List, Literal

import message_ix
import pandas as pd
Expand Down Expand Up @@ -173,7 +173,7 @@ def gen_comm_shr_map(
type_tec_shr_name: str,
tecs_all: List[str],
tecs_shr: List[str],
nodes: Union[str, List[str]] = "all",
nodes: str | List[str] = "all",
) -> tuple[pd.DataFrame, pd.DataFrame]:
"""
Expand Down Expand Up @@ -221,7 +221,7 @@ def gen_comm_shr_par(
cname: str,
shr_vals_df: pd.DataFrame,
shr_type: Literal["up", "lo"] = "up",
years: Union[str, List[int]] = "all",
years: str | List[int] = "all",
) -> DataFrame:
"""Generates data frame for "share_commodity_up/lo" parameter with given values for
node_share and broadcasts them for given "years".
Expand Down Expand Up @@ -264,7 +264,7 @@ def add_comm_share(
shr_tecs: List[str],
shr_vals: pd.DataFrame,
shr_type: Literal["up", "lo"] = "up",
years: Union[str, List[int]] = "all",
years: str | List[int] = "all",
):
"""Convenience function that adds commodity share constraint to a scenario.
Expand Down
Loading
Loading