Skip to content

Commit 91d6302

Browse files
authored
Merge pull request #295 from iiasa/issue/294
Confirm support for minimum versions
2 parents 43ce610 + a5626b8 commit 91d6302

File tree

15 files changed

+83
-40
lines changed

15 files changed

+83
-40
lines changed

.github/workflows/pytest.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ jobs:
7474
# - Versions of ixmp and message_ix to test.
7575
# - Latest supported Python version for those or other dependencies.
7676
# Minimum version given in pyproject.toml + earlier version of Python
77-
- { upstream: v3.6.0, python: "3.11" } # 2022-08-18
77+
# For this job only, the oldest version of Python supported by message-ix-models
78+
- { upstream: v3.6.0, python: "3.9" } # Released 2022-08-18
7879
- { upstream: v3.7.0, python: "3.11" } # 2023-05-17
7980
- { upstream: v3.8.0, python: "3.12" } # 2024-01-12
8081
# Latest released version + latest released Python
@@ -133,6 +134,7 @@ jobs:
133134
v, result = "${{ matrix.version.upstream }}".replace("main", "vmain"), []
134135
for condition, dependency in (
135136
(v <= "v3.6.0", "dask < 2024.3.0"), # dask[dataframe] >= 2024.3.0 requires dask-expr and in turn pandas >= 2.0 (#156)
137+
(v <= "v3.6.0", "numpy < 2.0"),
136138
(v <= "v3.6.0", "pandas < 2.0"),
137139
(v >= "v3.7.0", "dask[dataframe] < 2024.11.0"), # dask >= 2024.11.0 changes handling of dict (will be addressed in #225)
138140
(v <= "v3.7.0", "genno < 1.25"), # Upstream versions < 3.8.0 import genno.computations, removed in 1.25.0 (#156)

message_ix_models/model/material/build.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22
from collections.abc import Mapping
3-
from typing import Any
3+
from typing import Any, Optional
44

55
import message_ix
66
import pandas as pd
@@ -90,7 +90,7 @@ def build(
9090
scenario: message_ix.Scenario,
9191
old_calib: bool,
9292
modify_existing_constraints: bool = True,
93-
iea_data_path: str | None = None,
93+
iea_data_path: Optional[str] = None,
9494
) -> message_ix.Scenario:
9595
"""Set up materials accounting on `scenario`."""
9696
node_suffix = context.model.regions

message_ix_models/model/material/data_petro.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from collections import defaultdict
2+
from typing import Union
23

34
import message_ix
45
import pandas as pd
@@ -233,7 +234,7 @@ def assign_input_outpt(
233234
split: str,
234235
param_name: str,
235236
regions: pd.DataFrame,
236-
val: float | int,
237+
val: Union[float, int],
237238
t: str,
238239
rg: str,
239240
global_region: str,

message_ix_models/model/material/data_util.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22
from collections.abc import Mapping
33
from functools import lru_cache
4-
from typing import TYPE_CHECKING, Literal
4+
from typing import TYPE_CHECKING, Literal, Optional
55

66
import ixmp
77
import message_ix
@@ -1013,7 +1013,7 @@ def read_iea_tec_map(tec_map_fname: str) -> pd.DataFrame:
10131013

10141014

10151015
def get_hist_act_data(
1016-
map_fname: str, years: list or None = None, iea_data_path: str | None = None
1016+
map_fname: str, years: list or None = None, iea_data_path: Optional[str] = None
10171017
) -> pd.DataFrame:
10181018
"""
10191019
reads IEA DB, maps and aggregates variables to MESSAGE technologies

message_ix_models/model/material/material_demand/material_demand_calc.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22
from pathlib import Path
3-
from typing import Literal
3+
from typing import Literal, Union
44

55
import message_ix
66
import numpy as np
@@ -57,12 +57,12 @@
5757
log = logging.getLogger(__name__)
5858

5959

60-
def steel_function(x: pd.DataFrame | float, a: float, b: float, m: float):
60+
def steel_function(x: Union[pd.DataFrame, float], a: float, b: float, m: float):
6161
gdp_pcap, del_t = x
6262
return a * np.exp(b / gdp_pcap) * (1 - m) ** del_t
6363

6464

65-
def cement_function(x: pd.DataFrame | float, a: float, b: float):
65+
def cement_function(x: Union[pd.DataFrame, float], a: float, b: float):
6666
gdp_pcap = x[0]
6767
return a * np.exp(b / gdp_pcap)
6868

@@ -92,12 +92,14 @@ def cement_function(x: pd.DataFrame | float, a: float, b: float):
9292
}
9393

9494

95-
def gompertz(phi: float, mu: float, y: pd.DataFrame | float, baseyear: int = 2020):
95+
def gompertz(
96+
phi: float, mu: float, y: Union[pd.DataFrame, float], baseyear: int = 2020
97+
):
9698
return 1 - np.exp(-phi * np.exp(-mu * (y - baseyear)))
9799

98100

99101
def read_timer_pop(
100-
datapath: str | Path, material: Literal["cement", "steel", "aluminum"]
102+
datapath: Union[str, Path], material: Literal["cement", "steel", "aluminum"]
101103
):
102104
df_population = pd.read_excel(
103105
f"{datapath}/{material_data[material]['dir']}{material_data[material]['file']}",
@@ -115,7 +117,7 @@ def read_timer_pop(
115117

116118

117119
def read_timer_gdp(
118-
datapath: str | Path, material: Literal["cement", "steel", "aluminum"]
120+
datapath: Union[str, Path], material: Literal["cement", "steel", "aluminum"]
119121
):
120122
# Read GDP per capita data
121123
df_gdp = pd.read_excel(
@@ -165,7 +167,7 @@ def project_demand(df: pd.DataFrame, phi: float, mu: float):
165167
return df_demand[["region", "year", "demand_tot"]]
166168

167169

168-
def read_base_demand(filepath: str | Path):
170+
def read_base_demand(filepath: Union[str, Path]):
169171
with open(filepath, "r") as file:
170172
yaml_data = file.read()
171173

message_ix_models/model/transport/base.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Data preparation for the MESSAGEix-GLOBIOM base model."""
22

33
from functools import partial
4-
from itertools import pairwise, product
4+
from itertools import product
55
from pathlib import Path
66
from typing import TYPE_CHECKING, Any, Optional
77

@@ -66,7 +66,7 @@ def align_and_fill(
6666
)
6767

6868

69-
@minimum_version("pandas 2")
69+
@minimum_version("pandas 2; python 3.10")
7070
def smooth(c: Computer, key: "genno.Key", *, dim: str = "ya") -> "genno.Key":
7171
"""Implement ‘smoothing’ for `key` along the dimension `dim`.
7272
@@ -76,6 +76,8 @@ def smooth(c: Computer, key: "genno.Key", *, dim: str = "ya") -> "genno.Key":
7676
2. Remove those values.
7777
3. Fill by linear interpolation.
7878
"""
79+
from itertools import pairwise
80+
7981
assert key.tag != "2"
8082
ks = KeySeq(key.remove_tag(key.tag or ""))
8183

message_ix_models/model/transport/operator.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import re
55
from collections.abc import Mapping, Sequence
66
from functools import partial, reduce
7-
from itertools import pairwise, product
7+
from itertools import product
88
from operator import gt, le, lt
99
from typing import TYPE_CHECKING, Any, Hashable, Optional, Union, cast
1010

@@ -26,6 +26,7 @@
2626
from message_ix_models.util import (
2727
MappingAdapter,
2828
datetime_now_with_tz,
29+
minimum_version,
2930
nodes_ex_world,
3031
show_versions,
3132
)
@@ -953,6 +954,7 @@ def relabel2(qty: "AnyQuantity", new_dims: dict):
953954
return result
954955

955956

957+
@minimum_version("python 3.10")
956958
def uniform_in_dim(value: "AnyQuantity", dim: str = "y") -> "AnyQuantity":
957959
"""Construct a uniform distribution from `value` along its :math:`y`-dimension.
958960
@@ -967,6 +969,8 @@ def uniform_in_dim(value: "AnyQuantity", dim: str = "y") -> "AnyQuantity":
967969
and including :math:`d_{max}`. Values are the piecewise integral of the uniform
968970
distribution in the interval ending at the respective coordinate.
969971
"""
972+
from itertools import pairwise
973+
970974
d_max = value.coords[dim].item() # Upper end of the distribution
971975
width = 2 * value.item() # Width of a uniform distribution
972976
height = 1.0 / width # Height of the distribution

message_ix_models/testing/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import logging
22
import os
33
import shutil
4-
from base64 import b32hexencode
4+
5+
try:
6+
from base64 import b32hexencode as b32encode
7+
except ImportError:
8+
from base64 import b32encode
59
from collections.abc import Generator
610
from copy import deepcopy
711
from pathlib import Path
@@ -224,7 +228,7 @@ def bare_res(request, context: Context, solved: bool = False) -> message_ix.Scen
224228
new_name = request.node.name
225229
except AttributeError:
226230
# Generate a new scenario name with a random part
227-
new_name = f"baseline {b32hexencode(randbytes(3)).decode().rstrip('=').lower()}"
231+
new_name = f"baseline {b32encode(randbytes(3)).decode().rstrip('=').lower()}"
228232

229233
log.info(f"Clone to '{model_name}/{new_name}'")
230234
return base.clone(scenario=new_name, keep_solution=solved)
@@ -425,7 +429,7 @@ def loaded_snapshot(
425429
new_name = request.node.name
426430
except AttributeError:
427431
# Generate a new scenario name with a random part
428-
new_name = f"baseline {b32hexencode(randbytes(3)).decode().rstrip('=').lower()}"
432+
new_name = f"baseline {b32encode(randbytes(3)).decode().rstrip('=').lower()}"
429433

430434
log.info(f"Clone to '{model_name}/{new_name}'")
431435
yield base.clone(scenario=new_name, keep_solution=solved)

message_ix_models/tests/model/transport/test_config.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,18 +75,14 @@ def test_navigate_scenario1(self, c, input, expected):
7575

7676
def test_scenario_conflict(self):
7777
# Giving both raises an exception
78-
with pytest.raises(
79-
ValueError,
80-
match=r"SCENARIO.A___ and T35_POLICY.ACT\|TEC are not compatible",
81-
):
78+
at = "(ACT|TEC)" # Order differs in Python 3.9
79+
expr = rf"SCENARIO.A___ and T35_POLICY.{at}\|{at} are not compatible"
80+
with pytest.raises(ValueError, match=expr):
8281
c = Config(futures_scenario="A---", navigate_scenario="act+tec")
8382

8483
# Also a conflict
8584
c = Config(navigate_scenario="act+tec")
86-
with pytest.raises(
87-
ValueError,
88-
match=r"SCENARIO.A___ and T35_POLICY.ACT\|TEC are not compatible",
89-
):
85+
with pytest.raises(ValueError, match=expr):
9086
c.set_futures_scenario("A---")
9187

9288

message_ix_models/tests/model/transport/test_operator.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
factor_ssp,
1818
sales_fraction_annual,
1919
transport_check,
20+
uniform_in_dim,
2021
)
2122
from message_ix_models.model.transport.structure import get_technology_groups
2223
from message_ix_models.project.navigate import T35_POLICY
@@ -139,6 +140,7 @@ def test_factor_ssp(test_context, ssp: SSP_2024) -> None:
139140
@pytest.mark.skipif(
140141
parse(version("genno")) < parse("1.25.0"), reason="Requires genno >= 1.25.0"
141142
)
143+
@uniform_in_dim.minimum_version
142144
def test_sales_fraction_annual():
143145
q = genno.Quantity(
144146
[[12.4, 6.1]], coords={"y": [2020], "n": list("AB")}, units="year"

0 commit comments

Comments
 (0)