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

Merged
merged 8 commits into from
Nov 20, 2024
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/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
45 changes: 30 additions & 15 deletions .github/workflows/pytest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ concurrency:
cancel-in-progress: true

env:
GAMS_VERSION: 29.1.0
# Version used until 2024-11-20; disabled
# GAMS_VERSION: 29.1.0
# First version including a macOS arm64 distribution
GAMS_VERSION: 43.4.1

# See description in lint.yml
depth: 100

Expand All @@ -23,22 +27,33 @@ jobs:
matrix:
os:
- macos-13
- macos-latest
- 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

exclude:
# Specific version combinations that are invalid / not to be used
# No arm64 distributions of JPype for these Pythons
- { os: macos-latest, python-version: "3.9" }
# Redundant with macos-latest
- { os: macos-13, python-version: "3.10" }
- { os: macos-13, python-version: "3.11" }
- { os: macos-13, python-version: "3.12" }
- { os: macos-13, python-version: "3.13" }

fail-fast: false

Expand Down Expand Up @@ -76,7 +91,7 @@ jobs:
- uses: iiasa/actions/setup-gams@main
with:
version: ${{ env.GAMS_VERSION }}
# license: ${{ secrets.GAMS_LICENSE }}
license: ${{ secrets.GAMS_LICENSE }}

- name: Install Python package and dependencies
# By default, the below installs ixmp from the main branch. To run against
Expand All @@ -86,9 +101,9 @@ jobs:
pip install --upgrade "ixmp @ git+https://github.com/iiasa/ixmp.git@main"
pip install .[tests]

# TEMPORARY Work around dask v2024.11.0;
# see https://github.com/khaeru/genno/issues/149
pip install "dask < 2024.11.0"
# TEMPORARY With Python 3.13 pyam-iamc resolves to 1.3.1, which in turn
# limits pint < 0.17. Override.
pip install --upgrade pint

- name: Run test suite using pytest
env:
Expand All @@ -111,7 +126,7 @@ jobs:
strategy:
matrix:
os:
- macos-13
- macos-latest
- ubuntu-latest
- windows-latest

Expand All @@ -128,7 +143,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 Expand Up @@ -160,7 +175,7 @@ jobs:
- uses: iiasa/actions/setup-gams@main
with:
version: ${{ env.GAMS_VERSION }}
# license: ${{ secrets.GAMS_LICENSE }}
license: ${{ secrets.GAMS_LICENSE }}

- name: Install Python package and dependencies
# By default, the below installs ixmp from the main branch. To run against
Expand All @@ -170,9 +185,9 @@ jobs:
pip install --upgrade "ixmp @ git+https://github.com/iiasa/ixmp.git@main"
pip install .[tests]

# TEMPORARY Work around dask v2024.11.0;
# see https://github.com/khaeru/genno/issues/149
pip install "dask < 2024.11.0"
# TEMPORARY With Python 3.13 pyam-iamc resolves to 1.3.1, which in turn
# limits pint < 0.17. Override.
pip install --upgrade pint

- name: Install R dependencies and tutorial requirements
run: |
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
2 changes: 2 additions & 0 deletions RELEASE_NOTES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Please familiarize yourself with these to foster an open and welcoming community
All changes
-----------

- :mod:`message_ix` is tested and compatible with `Python 3.13 <https://www.python.org/downloads/release/python-3130/>`__ (:pull:`881`).
- Support for Python 3.8 is dropped (:pull:`881`), as it has reached end-of-life.
- Add option to :func:`.util.copy_model` from a non-default location of model files (:pull:`877`).

.. _v3.9.0:
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
11 changes: 6 additions & 5 deletions message_ix/core.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import logging
import os
from collections.abc import Iterable, Mapping, Sequence
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 Optional, Union
from warnings import warn

import ixmp
Expand Down Expand Up @@ -236,7 +237,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 +254,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 +451,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 +601,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
30 changes: 10 additions & 20 deletions message_ix/macro.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
import logging
import os
from collections.abc import Collection, Hashable, Iterable, Mapping, MutableMapping
from dataclasses import dataclass
from functools import partial
from operator import itemgetter, mul
from pathlib import Path
from typing import (
TYPE_CHECKING,
Collection,
Hashable,
Iterable,
List,
Mapping,
MutableMapping,
Optional,
Set,
Union,
)
from typing import TYPE_CHECKING, Optional, Union

import numpy as np
import pandas as pd
Expand Down Expand Up @@ -129,11 +119,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 +313,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 +423,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 +465,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 +492,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
7 changes: 4 additions & 3 deletions message_ix/models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import logging
from collections import ChainMap
from collections.abc import Mapping, MutableMapping
from copy import copy
from dataclasses import InitVar, dataclass, field
from functools import partial
from pathlib import Path
from typing import Mapping, MutableMapping, Optional, Tuple
from typing import Optional
from warnings import warn

import ixmp.model.gams
Expand Down Expand Up @@ -79,10 +80,10 @@ class Item:

#: Coordinates of the item; that is, the names of sets that index its dimensions.
#: The same set name may be repeated if it indexes multiple dimensions.
coords: Tuple[str, ...] = field(default_factory=tuple)
coords: tuple[str, ...] = field(default_factory=tuple)

#: Dimensions of the item.
dims: Tuple[str, ...] = field(default_factory=tuple)
dims: tuple[str, ...] = field(default_factory=tuple)

#: Text description of the item.
description: Optional[str] = None
Expand Down
13 changes: 7 additions & 6 deletions message_ix/report/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import logging
from collections.abc import Mapping
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, Union, cast

from genno.operator import broadcast_map
from ixmp.report import (
Expand Down Expand Up @@ -53,7 +54,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 +113,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 +149,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
Loading
Loading