Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Python support for 3.13 #881

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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/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
Loading