Skip to content

Commit

Permalink
Drop Python 3.8, add 3.13
Browse files Browse the repository at this point in the history
  • Loading branch information
glatterf42 committed Oct 15, 2024
1 parent d3cf5bd commit 10b9e8d
Show file tree
Hide file tree
Showing 20 changed files with 84 additions and 99 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/nightly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
with:
# If the "Latest version testable on GitHub Actions" in pytest.yaml
# is not the latest 3.x stable version, adjust here to match:
python-version: "3.10"
python-version: "3.12"
cache: pip
cache-dependency-path: "**/setup.cfg"

Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/pytest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,19 @@ jobs:
- ubuntu-latest
- windows-latest
python-version:
- "3.8" # Earliest version supported by message_ix
- "3.9"
- "3.9" # Earliest version supported by message_ix
- "3.10"
- "3.11"
- "3.12" # Latest version supported by message_ix
- "3.12"
- "3.13" # Latest version supported by message_ix

# Below this comment are newly released or development versions of
# Python. For these versions, binary wheels are not available for some
# dependencies, e.g. llvmlite, numba, numpy, and/or pandas. Compiling
# these on the job runner requires a more elaborate build environment,
# currently out of scope for the message_ix project.

# - "3.13.0-alpha.1" # Development version
# - "3.14.0-alpha.1" # Development version

fail-fast: false

Expand Down Expand Up @@ -128,7 +128,7 @@ jobs:

- uses: actions/setup-python@v5
with:
python-version: "3.11"
python-version: "3.12"
cache: pip
cache-dependency-path: "**/pyproject.toml"

Expand Down
2 changes: 1 addition & 1 deletion INSTALL.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Use the :ref:`install-quick` steps on this page if *all* of the following apply:

- You have already installed on your system:

- :ref:`Python <install-python>` (version 3.8 or later) installed, along with either :program:`pip` or :program:`conda`;
- :ref:`Python <install-python>` (version 3.9 or later) installed, along with either :program:`pip` or :program:`conda`;
- a :ref:`Java Runtime Environment (JRE) <install-java>` (if *not* using :program:`conda`; see :ref:`here <install-java>`); and
- :ref:`GAMS <install-gams>` (version 24.8.1 or later).

Expand Down
4 changes: 2 additions & 2 deletions doc/install-adv.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ Install system dependencies
Python (required)
-----------------

|MESSAGEix| requires Python version 3.8 or greater.
We recommend the latest version; currently Python 3.12.
|MESSAGEix| requires Python version 3.9 or greater.
We recommend the latest version; currently Python 3.13.
Common ways to install Python include:

- Use the official `Python releases <https://www.python.org/downloads/>`_.
Expand Down
6 changes: 1 addition & 5 deletions message_ix/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import logging
import sys
from importlib.metadata import PackageNotFoundError, version
from pathlib import Path

try:
from importlib.metadata import PackageNotFoundError, version
except ImportError: # Python 3.7
from importlib_metadata import PackageNotFoundError, version # type: ignore

from ixmp import ModelError, config
from ixmp.model import MODELS
from ixmp.util import DeprecatedPathFinder
Expand Down
10 changes: 5 additions & 5 deletions message_ix/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
from functools import lru_cache, partial
from itertools import chain, product, zip_longest
from typing import Dict, Iterable, List, Mapping, Optional, Sequence, Tuple, Union
from typing import Iterable, Mapping, Optional, Sequence, Union
from warnings import warn

import ixmp
Expand Down Expand Up @@ -236,7 +236,7 @@ def add_par(
self,
name: str,
key_or_data: Optional[
Union[int, str, Sequence[Union[int, str]], Dict, pd.DataFrame]
Union[int, str, Sequence[Union[int, str]], dict, pd.DataFrame]
] = None,
value=None,
unit: Optional[str] = None,
Expand All @@ -253,7 +253,7 @@ def add_par(
def add_set(
self,
name: str,
key: Union[int, str, Sequence[Union[str, int]], Dict, pd.DataFrame],
key: Union[int, str, Sequence[Union[str, int]], dict, pd.DataFrame],
comment: Union[str, Sequence[str], None] = None,
) -> None:
# ixmp.Scenario.add_par() is typed as accepting only str, but this method also
Expand Down Expand Up @@ -450,7 +450,7 @@ def add_horizon(

def vintage_and_active_years(
self,
ya_args: Union[Tuple[str, str], Tuple[str, str, Union[int, str]], None] = None,
ya_args: Union[tuple[str, str], tuple[str, str, Union[int, str]], None] = None,
tl_only: bool = True,
**kwargs,
) -> pd.DataFrame:
Expand Down Expand Up @@ -600,7 +600,7 @@ def _valid(elem):
#: Alias for :meth:`vintage_and_active_years`.
yv_ya = vintage_and_active_years

def years_active(self, node: str, tec: str, yr_vtg: Union[int, str]) -> List[int]:
def years_active(self, node: str, tec: str, yr_vtg: Union[int, str]) -> list[int]:
"""Return periods in which `tec` hnology of `yr_vtg` can be active in `node`.
The :ref:`parameters <params-tech>` ``duration_period`` and
Expand Down
18 changes: 8 additions & 10 deletions message_ix/macro.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@
Collection,
Hashable,
Iterable,
List,
Mapping,
MutableMapping,
Optional,
Set,
Union,
)

Expand Down Expand Up @@ -129,11 +127,11 @@ def add_par(
class Structures:
"""MACRO structure information."""

level: Set[str]
node: Set[str]
sector: Set[str]
level: set[str]
node: set[str]
sector: set[str]
#: Model years for which MACRO is calibrated.
year: Set[int]
year: set[int]


def add_structure(
Expand Down Expand Up @@ -323,7 +321,7 @@ def growth(gdp_calibrate) -> "DataFrame":
return growth.dropna()


def macro_periods(demand: "Quantity", config: "DataFrame") -> Set[int]:
def macro_periods(demand: "Quantity", config: "DataFrame") -> set[int]:
"""Periods ("years") for the MACRO model.
The intersection of those appearing in the `config` data and in the ``DEMAND``
Expand Down Expand Up @@ -433,7 +431,7 @@ def total_cost(model_cost: "DataFrame", cost_ref: "DataFrame", ym1: int) -> "Dat
)


def unique_set(column: str, df: "DataFrame") -> Set:
def unique_set(column: str, df: "DataFrame") -> set:
"""A :class:`set` of the unique elements in `column` of `df`."""
return set(df[column].dropna().unique())

Expand Down Expand Up @@ -475,7 +473,7 @@ def validate_transform(
return df.set_index(idx)["value"]


def _validate_data(name: Optional[str], df: "DataFrame", s: Structures) -> List:
def _validate_data(name: Optional[str], df: "DataFrame", s: Structures) -> list:
"""Validate input `df` against `s` for MACRO parameter `name` calibration .
Parameters
Expand All @@ -502,7 +500,7 @@ def _validate_data(name: Optional[str], df: "DataFrame", s: Structures) -> List:

# Check required dimensions
if name is None:
dims: List[str] = []
dims: list[str] = []
else:
item_name = name.replace("_ref", "_MESSAGE")
item = MACRO.items[item_name]
Expand Down
12 changes: 6 additions & 6 deletions message_ix/report/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
from functools import lru_cache, partial
from operator import itemgetter
from typing import TYPE_CHECKING, List, Mapping, Tuple, Union, cast
from typing import TYPE_CHECKING, Mapping, Union, cast

from genno.operator import broadcast_map
from ixmp.report import (
Expand Down Expand Up @@ -53,7 +53,7 @@
#: contains the value 1 at every valid (type_addon, ta) location, and 0 elsewhere.
#: 2. Simple products of 2 or mode quantities.
#: 3. Other derived quantities.
TASKS0: Tuple[Tuple, ...] = (
TASKS0: tuple[tuple, ...] = (
# Mapping sets
("map_addon", "map_as_qty", "cat_addon", "t"),
("map_emission", "map_as_qty", "cat_emission", "e"),
Expand Down Expand Up @@ -112,7 +112,7 @@

#: Quantities to automatically convert to IAMC format using
#: :func:`~genno.compat.pyam.operator.as_pyam`.
PYAM_CONVERT: List[Tuple[str, "CollapseMessageColsKw"]] = [
PYAM_CONVERT: list[tuple[str, "CollapseMessageColsKw"]] = [
("out:nl-t-ya-m-nd-c-l", dict(kind="ene", var="out")),
("in:nl-t-ya-m-no-c-l", dict(kind="ene", var="in")),
("CAP:nl-t-ya", dict(var="capacity")),
Expand Down Expand Up @@ -148,19 +148,19 @@


@lru_cache(1)
def get_tasks() -> List[Tuple[Tuple, Mapping]]:
def get_tasks() -> list[tuple[tuple, Mapping]]:
"""Return a list of tasks describing MESSAGE reporting calculations."""
# Assemble queue of items to add. Each element is a 2-tuple of (positional, keyword)
# arguments for Reporter.add()
to_add: List[Tuple[Tuple, Mapping]] = []
to_add: list[tuple[tuple, Mapping]] = []

strict = dict(strict=True)

for t in TASKS0:
if len(t) == 2 and isinstance(t[1], dict):
# (args, kwargs) → update kwargs with strict
t[1].update(strict)
to_add.append(cast(Tuple[Tuple, Mapping], t))
to_add.append(cast(tuple[tuple, Mapping], t))
else:
# args only → use strict as kwargs
to_add.append((t, strict))
Expand Down
11 changes: 4 additions & 7 deletions message_ix/report/operator.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
from typing import (
TYPE_CHECKING,
Dict,
List,
Literal,
Mapping,
Tuple,
overload,
)

Expand All @@ -30,7 +27,7 @@ def as_message_df(
dims: Mapping,
common: Mapping,
wrap: Literal[True] = True,
) -> Dict[str, pd.DataFrame]: ...
) -> dict[str, pd.DataFrame]: ...


@overload
Expand Down Expand Up @@ -87,7 +84,7 @@ def as_message_df(qty, name, dims, common, wrap=True):
return {name: df} if wrap else df


def model_periods(y: List[int], cat_year: pd.DataFrame) -> List[int]:
def model_periods(y: list[int], cat_year: pd.DataFrame) -> list[int]:
"""Return the elements of `y` beyond the firstmodelyear of `cat_year`."""
return list(
filter(
Expand All @@ -98,7 +95,7 @@ def model_periods(y: List[int], cat_year: pd.DataFrame) -> List[int]:
)


def plot_cumulative(x: "AnyQuantity", y: "AnyQuantity", labels: Tuple[str, str, str]):
def plot_cumulative(x: "AnyQuantity", y: "AnyQuantity", labels: tuple[str, str, str]):
"""Plot a supply curve.
- `x` and `y` must share the first two dimensions.
Expand Down Expand Up @@ -161,7 +158,7 @@ def plot_cumulative(x: "AnyQuantity", y: "AnyQuantity", labels: Tuple[str, str,

def stacked_bar(
qty: "AnyQuantity",
dims: Tuple[str, ...] = ("nl", "t", "ya"),
dims: tuple[str, ...] = ("nl", "t", "ya"),
units: str = "",
title: str = "",
cf: float = 1.0,
Expand Down
4 changes: 2 additions & 2 deletions message_ix/report/pyam.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, List, Optional, TypedDict
from typing import TYPE_CHECKING, Optional, TypedDict

if TYPE_CHECKING:
import pandas
Expand All @@ -10,7 +10,7 @@ class CollapseMessageColsKw(TypedDict, total=False):
df: "pandas.DataFrame"
var: Optional[str]
kind: Optional[str]
var_cols: List[str]
var_cols: list[str]


def collapse_message_cols(
Expand Down
4 changes: 2 additions & 2 deletions message_ix/testing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
from itertools import product
from pathlib import Path
from typing import TYPE_CHECKING, Generator, List, Optional, Union
from typing import TYPE_CHECKING, Generator, Optional, Union

import numpy as np
import pandas as pd
Expand Down Expand Up @@ -48,7 +48,7 @@ def pytest_sessionstart():

# Create and populate ixmp databases

_ms: List[Union[str, float]] = [
_ms: list[Union[str, float]] = [
SCENARIO["dantzig"]["model"],
SCENARIO["dantzig"]["scenario"],
]
Expand Down
4 changes: 2 additions & 2 deletions message_ix/tests/report/test_operator.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from functools import partial
from typing import Any, Mapping, Tuple
from typing import Any, Mapping

import matplotlib
import pandas as pd
Expand All @@ -17,7 +17,7 @@ def test_as_message_df(test_mp) -> None:
q = random_qty(dict(c=3, h=2, nl=5))
q.units = "kg"

args: Tuple[Any, Mapping, Mapping] = (
args: tuple[Any, Mapping, Mapping] = (
literal("demand"),
dict(commodity="c", node="nl", time="h"),
dict(level="l", year=2022),
Expand Down
26 changes: 12 additions & 14 deletions message_ix/tests/test_feature_duration_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@


# A function for generating a simple model with sub-annual time slices
# FIXME reduce complexity 14 → ≤13
def model_generator( # noqa: C901
def model_generator(
test_mp,
comment,
tec_time,
Expand Down Expand Up @@ -66,23 +65,21 @@ def model_generator( # noqa: C901
map_time = {}
for [tmp_lvl, number, parent] in time_steps:
scen.add_set("lvl_temporal", tmp_lvl)
if parent == "year":
times = [tmp_lvl[0] + "-" + str(x + 1) for x in range(number)]
else:
times = [
times = (
[tmp_lvl[0] + "-" + str(x + 1) for x in range(number)]
if parent == "year"
else [
p + "_" + tmp_lvl[0] + "-" + str(x + 1)
for (p, x) in product(map_time[parent], range(number))
]
)

map_time[tmp_lvl] = times
scen.add_set("time", times)

# Adding "map_temporal_hierarchy" and "duration_time"
for h in times:
if parent == "year":
p = "year"
else:
p = h.split("_" + tmp_lvl[0])[0]
p = "year" if parent == "year" else h.split("_" + tmp_lvl[0])[0]
# Temporal hierarchy (order: temporal level, time, parent time)
scen.add_set("map_temporal_hierarchy", [tmp_lvl, h, p])

Expand Down Expand Up @@ -111,10 +108,11 @@ def model_generator( # noqa: C901
"time"
]
# If technology is linking two different temporal levels
if tmp_lvl_in != tmp_lvl_out:
time_pairs = product(times_in, times_out)
else:
time_pairs = zip(times_in, times_out)
time_pairs = (
product(times_in, times_out)
if tmp_lvl_in != tmp_lvl_out
else zip(times_in, times_out)
)

# Configuring data for "time_origin" and "time" in "input"
for h_in, h_act in time_pairs:
Expand Down
Loading

0 comments on commit 10b9e8d

Please sign in to comment.