From 635ef551943625f64a2de32af6f78f1576f0ad1a Mon Sep 17 00:00:00 2001 From: "Thomas S. Binns" Date: Wed, 5 Aug 2026 18:42:34 +0100 Subject: [PATCH 1/4] Bump min python to 3.11 --- .circleci/config.yml | 6 +++--- .github/workflows/scheduled_updates.yml | 2 +- .github/workflows/tests.yml | 2 +- .pre-commit-config.yaml | 1 - doc/conf.py | 21 ++++++++------------- environment.yml | 2 +- mne/tests/test_docstring_parameters.py | 6 +----- mne/utils/_typing.py | 9 +-------- mne/utils/tests/test_config.py | 2 +- pyproject.toml | 5 ++--- tools/github_actions_env_vars.sh | 2 +- tools/github_actions_verify_python.sh | 2 +- tools/hooks/update_environment_file.py | 6 +----- 13 files changed, 22 insertions(+), 44 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a8943cc0094..6f0f8cc6957 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -116,7 +116,7 @@ jobs: - uv-cache-0 - restore_cache: keys: - - user-install-bin-cache-310 + - user-install-bin-cache-311 # Hack in uninstalls of libraries as necessary if pip doesn't do the right thing in upgrading for us... - run: @@ -129,9 +129,9 @@ jobs: paths: - ~/.cache/uv - save_cache: - key: user-install-bin-cache-310 + key: user-install-bin-cache-311 paths: - - ~/.local/lib/python3.10/site-packages + - ~/.local/lib/python3.11/site-packages - ~/.local/bin - run: diff --git a/.github/workflows/scheduled_updates.yml b/.github/workflows/scheduled_updates.yml index 71f2e5f4040..11407858aa4 100644 --- a/.github/workflows/scheduled_updates.yml +++ b/.github/workflows/scheduled_updates.yml @@ -73,7 +73,7 @@ jobs: # uv pip compile requires setting the python version explicitly in the command :( run: | uv pip compile pyproject.toml \ - --python "3.10" --python-platform "x86_64-unknown-linux-gnu" \ + --python "3.11" --python-platform "x86_64-unknown-linux-gnu" \ --group test --group lockfile_extras \ --resolution lowest-direct \ --format pylock.toml --output-file tools/pylock.ci-old.toml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 561c89598e9..8b0763afd7f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -66,7 +66,7 @@ jobs: python: '3.12' kind: minimal - os: ubuntu-22.04 - python: '3.10' + python: '3.11' kind: old - os: ubuntu-latest python: '3.14t' # free-threaded diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 549ecb8fb85..6b25a8c4310 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,7 +43,6 @@ repos: - pymef # for mne/io/mef - quantities # for mne/io/neuralynx - scipy - - typing_extensions # TODO VERSION remove once we require Python 3.11 args: [--no-python-downloads, --no-project] # Codespell diff --git a/doc/conf.py b/doc/conf.py index 6cec4f97214..875b88c7935 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -13,7 +13,8 @@ import os import subprocess import sys -from datetime import datetime, timezone +import tomllib +from datetime import UTC, datetime from importlib.metadata import metadata from pathlib import Path @@ -58,7 +59,7 @@ # -- Project information ----------------------------------------------------- project = "MNE" -td = datetime.now(tz=timezone.utc) +td = datetime.now(tz=UTC) # We need to triage which date type we use so that incremental builds work # (Sphinx looks at variable changes and rewrites all files if some change) @@ -443,17 +444,11 @@ "pooch.HTTPDownloader", } numpydoc_validate = True -try: - import tomllib - # TODO VERSION: Can be removed once Python 3.11 is required -except Exception: - pass -else: - pyproject_path = Path(__file__).parent.parent / "pyproject.toml" - pyproject = tomllib.loads(pyproject_path.read_text("utf-8")) - pyproject_nv = pyproject["tool"]["numpydoc_validation"] - numpydoc_validation_checks = set(pyproject_nv["checks"]) - numpydoc_validation_exclude = set(pyproject_nv["exclude"]) +pyproject_path = Path(__file__).parent.parent / "pyproject.toml" +pyproject = tomllib.loads(pyproject_path.read_text("utf-8")) +pyproject_nv = pyproject["tool"]["numpydoc_validation"] +numpydoc_validation_checks = set(pyproject_nv["checks"]) +numpydoc_validation_exclude = set(pyproject_nv["exclude"]) # -- Sphinx-gallery configuration -------------------------------------------- diff --git a/environment.yml b/environment.yml index 4bcba1108cc..491b7aa3c52 100644 --- a/environment.yml +++ b/environment.yml @@ -3,7 +3,7 @@ name: mne channels: - conda-forge dependencies: - - python >=3.10 + - python >=3.11 - antio >=0.5.0 - conda - curryreader >=0.1.2 diff --git a/mne/tests/test_docstring_parameters.py b/mne/tests/test_docstring_parameters.py index 4602112af6a..e6f36d7ec32 100644 --- a/mne/tests/test_docstring_parameters.py +++ b/mne/tests/test_docstring_parameters.py @@ -6,6 +6,7 @@ import importlib import inspect import re +import tomllib import types import typing from pathlib import Path @@ -55,11 +56,6 @@ pyproject_path = Path(__file__).parents[2] / "pyproject.toml" if not pyproject_path.is_file(): pytest.skip(f"pyproject.toml not found: {pyproject_path}", allow_module_level=True) -try: - import tomllib -except ModuleNotFoundError: - # TODO VERSION: Remove this when Python 3.11+ is required - pytest.skip("tomllib not available", allow_module_level=True) pyproject = tomllib.loads(pyproject_path.read_text("utf-8")) numpydoc_checks = pyproject["tool"]["numpydoc_validation"]["checks"] diff --git a/mne/utils/_typing.py b/mne/utils/_typing.py index 5a047e6070c..a00d68b8380 100644 --- a/mne/utils/_typing.py +++ b/mne/utils/_typing.py @@ -4,14 +4,7 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. -import sys -from typing import IO - -if sys.version_info >= (3, 11): - from typing import Self -else: - # TODO VERSION: Remove this when Python 3.11+ is required (use typing.Self) - from typing_extensions import Self +from typing import IO, Self # A Matplotlib color: a named/hex string, or an RGB(A) tuple of floats. This is # the runtime meaning of the ``color`` numpydoc pseudo-type. diff --git a/mne/utils/tests/test_config.py b/mne/utils/tests/test_config.py index 6fd4369f15a..90f8275c765 100644 --- a/mne/utils/tests/test_config.py +++ b/mne/utils/tests/test_config.py @@ -9,6 +9,7 @@ import random import re import time +import tomllib from functools import partial from pathlib import Path from urllib.error import URLError @@ -147,7 +148,6 @@ def test_sys_info_windowing_system(monkeypatch): def test_sys_info_complete(): """Test that sys_info is sufficiently complete.""" - tomllib = pytest.importorskip("tomllib") # TODO VERSION remove on Python 3.11+ pyproject = Path(__file__).parents[3] / "pyproject.toml" if not pyproject.is_file(): pytest.skip("Does not appear to be a dev installation") diff --git a/pyproject.toml b/pyproject.toml index 56402be5563..fb5d896ea4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,7 +116,6 @@ dependencies = [ "pooch >= 1.5", "scipy >= 1.14", # released 2024-06-24, will become 1.15 on 2027-01-03 "tqdm >= 4.66", - "typing-extensions >= 4.15; python_version < '3.11'", # for typing.Self TODO VERSION ] description = "MNE-Python project for MEG and EEG data analysis." dynamic = ["version"] @@ -134,7 +133,7 @@ license-files = ["LICENSE.txt"] maintainers = [{email = "dan@mccloy.info", name = "Dan McCloy"}] name = "mne" readme = {content-type = "text/x-rst", file = "README.rst"} -requires-python = ">= 3.10" +requires-python = ">= 3.11" # ↑↑↑↑↑↑↑↑↑↑↑ when this changes, bump the `--python-version` in the `uv pip compile ...` # command in `.github/workflows/scheduled_updates.yml` (astral-sh/uv/#16333), and also # the `python` version for the `kind: old` matrix entry in `.github/workflows/tests.yml` @@ -469,7 +468,7 @@ name = "Other changes" showcontent = true [tool.ty.environment] -python-version = "3.10" # minimum supported, keep in sync with requires-python +python-version = "3.11" # minimum supported, keep in sync with requires-python # Files that are strictly type-checked with no rule overrides. A file is added # here only once it passes ty cleanly; grow this list as more of the codebase is diff --git a/tools/github_actions_env_vars.sh b/tools/github_actions_env_vars.sh index 510a50c9e66..4ee5591e323 100755 --- a/tools/github_actions_env_vars.sh +++ b/tools/github_actions_env_vars.sh @@ -13,7 +13,7 @@ case "$MNE_CI_KIND" in minimal | old) ;; # unique code paths, worth the slower C tracer *) case "$PYTHON_VERSION" in - 3.10 | 3.11 | 3.12 | 3.13) COV_ARGS="" ;; + 3.11 | 3.12 | 3.13) COV_ARGS="" ;; esac ;; esac diff --git a/tools/github_actions_verify_python.sh b/tools/github_actions_verify_python.sh index 9e5cd938782..4fae44a2ca5 100755 --- a/tools/github_actions_verify_python.sh +++ b/tools/github_actions_verify_python.sh @@ -10,7 +10,7 @@ else fi WANT_PYTHON_VERSION=$(echo "$WANT_PYTHON_VERSION" | sed 's/t$//g') if [[ -z "$WANT_PYTHON_VERSION" ]]; then - echo "✕ ERROR: Missing required argument: want Python version (e.g., 3.10)" + echo "✕ ERROR: Missing required argument: want Python version (e.g., 3.11)" exit 1 fi diff --git a/tools/hooks/update_environment_file.py b/tools/hooks/update_environment_file.py index 9c858ca70ad..aa1aee87792 100755 --- a/tools/hooks/update_environment_file.py +++ b/tools/hooks/update_environment_file.py @@ -5,9 +5,8 @@ # Copyright the MNE-Python contributors. import re -from pathlib import Path - import tomllib +from pathlib import Path repo_root = Path(__file__).resolve().parents[2] with open(repo_root / "pyproject.toml", "rb") as fid: @@ -29,9 +28,6 @@ "pymef", # not on conda-forge } deps -= pip_deps -deps -= { - "typing-extensions >= 4.15; python_version < '3.11'" -} # for typing.Self TODO VERSION def remove_spaces(version_spec): From 6400fe02d39fbb4c49eee90f01a069cfcc5094e4 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:46:55 +0000 Subject: [PATCH 2/4] [autofix.ci] apply automated fixes --- mne/_fiff/meas_info.py | 6 ++---- mne/_fiff/tests/test_meas_info.py | 14 +++++++------- mne/annotations.py | 6 +++--- mne/export/_egimff.py | 2 +- mne/export/tests/test_export.py | 10 +++++----- mne/io/brainvision/brainvision.py | 4 ++-- mne/io/brainvision/tests/test_brainvision.py | 2 +- mne/io/ctf/tests/test_ctf.py | 4 ++-- mne/io/curry/curry.py | 4 ++-- mne/io/curry/tests/test_curry.py | 4 ++-- mne/io/edf/edf.py | 16 ++++++++-------- mne/io/edf/tests/test_gdf.py | 8 ++++---- mne/io/egi/egimff.py | 2 +- mne/io/egi/tests/test_egi.py | 4 ++-- mne/io/hitachi/hitachi.py | 2 +- mne/io/hitachi/tests/test_hitachi.py | 2 +- mne/io/mef/mef.py | 2 +- mne/io/nedf/nedf.py | 4 ++-- mne/io/neuralynx/neuralynx.py | 2 +- mne/io/neuralynx/tests/test_neuralynx.py | 4 ++-- mne/io/nihon/nihon.py | 4 ++-- mne/io/nirx/nirx.py | 4 ++-- mne/io/nirx/tests/test_nirx.py | 8 ++++---- mne/io/nsx/nsx.py | 4 ++-- mne/io/persyst/persyst.py | 4 ++-- mne/io/snirf/_snirf.py | 2 +- .../tests/test_annotate_amplitude.py | 2 +- mne/tests/test_annotations.py | 14 +++++++------- mne/time_frequency/tests/test_tfr.py | 2 +- mne/utils/numerics.py | 12 ++++-------- tools/dev/check_steering_committee.py | 6 ++---- tools/dev/update_credit_json.py | 2 +- tools/generate_codemeta.py | 3 +-- 33 files changed, 80 insertions(+), 89 deletions(-) diff --git a/mne/_fiff/meas_info.py b/mne/_fiff/meas_info.py index bf0a8882759..176091c9a0b 100644 --- a/mne/_fiff/meas_info.py +++ b/mne/_fiff/meas_info.py @@ -2010,7 +2010,7 @@ def _check_consistency(self, prepend_error=""): if ( not isinstance(self["meas_date"], datetime.datetime) or self["meas_date"].tzinfo is None - or self["meas_date"].tzinfo is not datetime.timezone.utc + or self["meas_date"].tzinfo is not datetime.UTC ): raise RuntimeError( f'{prepend_error}info["meas_date"] must be a datetime object in UTC' @@ -3777,9 +3777,7 @@ def anonymize_info(info, daysback=None, keep_his=False, verbose=None): for field in keep_fields: _check_option("keep_his", field, valid_fields) - default_anon_dos = datetime.datetime( - 2000, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc - ) + default_anon_dos = datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=datetime.UTC) default_str = "mne_anonymize" default_subject_id = 0 default_sex = 0 diff --git a/mne/_fiff/tests/test_meas_info.py b/mne/_fiff/tests/test_meas_info.py index 8446c232acc..108060e2e2f 100644 --- a/mne/_fiff/tests/test_meas_info.py +++ b/mne/_fiff/tests/test_meas_info.py @@ -5,7 +5,7 @@ import json import pickle import string -from datetime import date, datetime, timedelta, timezone +from datetime import UTC, date, datetime, timedelta from pathlib import Path import numpy as np @@ -350,7 +350,7 @@ def test_read_write_info(tmp_path): # Check that having a very old date in fine until you try to save it to fif with info._unlock(check_after=True): - info["meas_date"] = datetime(1800, 1, 1, 0, 0, 0, tzinfo=timezone.utc) + info["meas_date"] = datetime(1800, 1, 1, 0, 0, 0, tzinfo=UTC) fname = tmp_path / "test.fif" with pytest.raises(RuntimeError, match="must be between "): write_info(fname, info, overwrite=True) @@ -406,7 +406,7 @@ def test_info_serialization_special_types(): info = create_info(ch_names=["EEG1"], sfreq=1000.0, ch_types="eeg") # Test meas_date (datetime) - meas_date = datetime(2023, 11, 13, 10, 30, 0, tzinfo=timezone.utc) + meas_date = datetime(2023, 11, 13, 10, 30, 0, tzinfo=UTC) with info._unlock(): info["meas_date"] = meas_date @@ -726,7 +726,7 @@ def _test_anonymize_info(base_info, tmp_path): assert isinstance(base_info, Info) base_info = base_info.copy() - default_anon_dos = datetime(2000, 1, 1, 0, 0, 0, tzinfo=timezone.utc) + default_anon_dos = datetime(2000, 1, 1, 0, 0, 0, tzinfo=UTC) default_str = "mne_anonymize" default_subject_id = 0 default_desc = "Anonymized using a time shift" + " to preserve age at acquisition" @@ -739,7 +739,7 @@ def _test_anonymize_info(base_info, tmp_path): # Fake some additional data _complete_info(base_info) - meas_date = datetime(2010, 1, 1, 0, 0, 0, tzinfo=timezone.utc) + meas_date = datetime(2010, 1, 1, 0, 0, 0, tzinfo=UTC) with base_info._unlock(): base_info["meas_date"] = meas_date base_info["subject_info"].update( @@ -940,7 +940,7 @@ def test_meas_date_convert(stamp, dt): meas_datetime = _stamp_to_dt(stamp) stamp2 = _dt_to_stamp(meas_datetime) assert stamp == stamp2 - assert meas_datetime == datetime(*dt, tzinfo=timezone.utc) + assert meas_datetime == datetime(*dt, tzinfo=UTC) # smoke test for info __repr__ info = create_info(1, 1000.0, "eeg") with info._unlock(): @@ -988,7 +988,7 @@ def _complete_info(info): info["helium_info"] = dict( he_level_raw=np.float32(12.34), helium_level=np.float32(45.67), - meas_date=datetime(2024, 11, 14, 14, 8, 2, tzinfo=timezone.utc), + meas_date=datetime(2024, 11, 14, 14, 8, 2, tzinfo=UTC), orig_file_guid="e", ) info["experimenter"] = "f" diff --git a/mne/annotations.py b/mne/annotations.py index 3d124be6a01..ab2dd9beb6c 100644 --- a/mne/annotations.py +++ b/mne/annotations.py @@ -8,7 +8,7 @@ from collections import Counter, OrderedDict, UserDict, UserList from collections.abc import Iterable from copy import deepcopy -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta from itertools import takewhile from textwrap import shorten @@ -425,7 +425,7 @@ def __init__( try: # only warn if `orig_time` is not the default '1970-01-01 00:00:00' if _handle_meas_date(0) == datetime.strptime( orig_time, "%Y-%m-%d %H:%M:%S" - ).replace(tzinfo=timezone.utc): + ).replace(tzinfo=UTC): pass except ValueError: # error if incorrect datetime format AND not the default warn( @@ -1772,7 +1772,7 @@ def _handle_meas_date(meas_date): except ValueError: meas_date = None else: - meas_date = meas_date.replace(tzinfo=timezone.utc) + meas_date = meas_date.replace(tzinfo=UTC) elif isinstance(meas_date, tuple): # old way meas_date = _stamp_to_dt(meas_date) diff --git a/mne/export/_egimff.py b/mne/export/_egimff.py index 185afb5f558..fd9491e259b 100644 --- a/mne/export/_egimff.py +++ b/mne/export/_egimff.py @@ -71,7 +71,7 @@ def export_evokeds_mff(fname, evoked, history=None, *, overwrite=False, verbose= if op.exists(fname): os.remove(fname) if op.isfile(fname) else shutil.rmtree(fname) writer = mffpy.Writer(fname) - current_time = datetime.datetime.now(datetime.timezone.utc) + current_time = datetime.datetime.now(datetime.UTC) writer.addxml("fileInfo", recordTime=current_time) try: device = info["device_info"]["type"] diff --git a/mne/export/tests/test_export.py b/mne/export/tests/test_export.py index b86d6aa8530..4423aa3b0f6 100644 --- a/mne/export/tests/test_export.py +++ b/mne/export/tests/test_export.py @@ -5,7 +5,7 @@ # Copyright the MNE-Python contributors. from contextlib import nullcontext -from datetime import date, datetime, timezone +from datetime import UTC, date, datetime from pathlib import Path import numpy as np @@ -52,7 +52,7 @@ ["meas_date", "orig_time", "ext"], [ [None, None, ".vhdr"], - [datetime(2022, 12, 3, 19, 1, 10, 720100, tzinfo=timezone.utc), None, ".eeg"], + [datetime(2022, 12, 3, 19, 1, 10, 720100, tzinfo=UTC), None, ".eeg"], ], ) def test_export_raw_pybv(tmp_path, meas_date, orig_time, ext): @@ -198,7 +198,7 @@ def _create_raw_for_edf_tests(stim_channel_index=None): def test_double_export_edf(tmp_path): """Test exporting an EDF file multiple times.""" raw = _create_raw_for_edf_tests(stim_channel_index=2) - raw.info.set_meas_date(datetime(2023, 9, 4, 14, 53, 9, tzinfo=timezone.utc)) + raw.info.set_meas_date(datetime(2023, 9, 4, 14, 53, 9, tzinfo=UTC)) raw.set_annotations(Annotations(onset=[1], duration=[0], description=["test"])) # include subject info and measurement date @@ -379,7 +379,7 @@ def test_rawarray_edf(tmp_path): hour=time_now.hour, minute=time_now.minute, second=time_now.second, - tzinfo=timezone.utc, + tzinfo=UTC, ) raw.set_meas_date(meas_date) temp_fname = tmp_path / "test.edf" @@ -427,7 +427,7 @@ def test_channel_label_too_long_for_edf_raises_error(tmp_path): def test_measurement_date_outside_range_valid_for_edf(tmp_path): """Test trying to save an EDF with a measurement date before 1985-01-01.""" raw = _create_raw_for_edf_tests() - raw.set_meas_date(datetime(year=1984, month=1, day=1, tzinfo=timezone.utc)) + raw.set_meas_date(datetime(year=1984, month=1, day=1, tzinfo=UTC)) with pytest.raises(ValueError, match="EDF only allows dates from 1985 to 2084"): raw.export(tmp_path / "test.edf", overwrite=True) diff --git a/mne/io/brainvision/brainvision.py b/mne/io/brainvision/brainvision.py index 95714717975..9c9d978a861 100644 --- a/mne/io/brainvision/brainvision.py +++ b/mne/io/brainvision/brainvision.py @@ -8,7 +8,7 @@ import os import os.path as op import re -from datetime import datetime, timezone +from datetime import UTC, datetime from io import StringIO from pathlib import Path from typing import Literal @@ -517,7 +517,7 @@ def _str_to_meas_date(date_str): else: raise - meas_date = meas_date.replace(tzinfo=timezone.utc) + meas_date = meas_date.replace(tzinfo=UTC) return meas_date diff --git a/mne/io/brainvision/tests/test_brainvision.py b/mne/io/brainvision/tests/test_brainvision.py index 6af6891aa86..3a500dc428c 100644 --- a/mne/io/brainvision/tests/test_brainvision.py +++ b/mne/io/brainvision/tests/test_brainvision.py @@ -1048,7 +1048,7 @@ def test_event_id_stability_when_save_and_fif_reload(tmp_path): def test_parse_impedance(): """Test case for parsing the impedances from header.""" expected_imp_meas_time = datetime.datetime( - 2013, 11, 13, 16, 12, 27, tzinfo=datetime.timezone.utc + 2013, 11, 13, 16, 12, 27, tzinfo=datetime.UTC ) expected_imp_unit = "kOhm" expected_electrodes = [ diff --git a/mne/io/ctf/tests/test_ctf.py b/mne/io/ctf/tests/test_ctf.py index 64f9bec1825..3cdccec03f0 100644 --- a/mne/io/ctf/tests/test_ctf.py +++ b/mne/io/ctf/tests/test_ctf.py @@ -5,7 +5,7 @@ import copy import os import shutil -from datetime import datetime, timezone +from datetime import UTC, datetime from os import path as op import numpy as np @@ -740,4 +740,4 @@ def _convert_time_bad(date_str, time_str): raw = read_raw_ctf(ctf_dir / ctf_fname_continuous, verbose=True) log = log.getvalue() assert "No date or time found" in log - assert raw.info["meas_date"] == datetime.fromtimestamp(0, tz=timezone.utc) + assert raw.info["meas_date"] == datetime.fromtimestamp(0, tz=UTC) diff --git a/mne/io/curry/curry.py b/mne/io/curry/curry.py index 066851119f2..50b87381f8e 100644 --- a/mne/io/curry/curry.py +++ b/mne/io/curry/curry.py @@ -4,7 +4,7 @@ # Copyright the MNE-Python contributors. import re -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path import numpy as np @@ -161,7 +161,7 @@ def _get_curry_meas_info(fname): try: year, month, day, hour, minute, second, millisec = meas_date meas_date = datetime( - year, month, day, hour, minute, second, millisec * 1000, timezone.utc + year, month, day, hour, minute, second, millisec * 1000, UTC ) except Exception: meas_date = None diff --git a/mne/io/curry/tests/test_curry.py b/mne/io/curry/tests/test_curry.py index 6ef4b19d46a..ae9907152fb 100644 --- a/mne/io/curry/tests/test_curry.py +++ b/mne/io/curry/tests/test_curry.py @@ -3,7 +3,7 @@ # License: BSD-3-Clause # Copyright the MNE-Python contributors. -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path from shutil import copyfile @@ -632,7 +632,7 @@ def test_read_files_missing_channel(fname, expected_channel_list): [ pytest.param( Ref_chan_omitted_file, - datetime(2018, 11, 21, 12, 53, 48, 525000, tzinfo=timezone.utc), + datetime(2018, 11, 21, 12, 53, 48, 525000, tzinfo=UTC), id="valid start date", ), pytest.param(curry7_rfDC_file, None, id="start date year is 0"), diff --git a/mne/io/edf/edf.py b/mne/io/edf/edf.py index 89c044e3e21..e818dea51f0 100644 --- a/mne/io/edf/edf.py +++ b/mne/io/edf/edf.py @@ -6,7 +6,7 @@ import os import re -from datetime import date, datetime, timedelta, timezone +from datetime import UTC, date, datetime, timedelta from enum import Enum from pathlib import Path from typing import Any, Literal @@ -1144,7 +1144,7 @@ def _read_edf_header( except Exception: hour, minute, second = 0, 0, 0 meas_date = meas_date.replace( - hour=hour, minute=minute, second=second, tzinfo=timezone.utc + hour=hour, minute=minute, second=second, tzinfo=UTC ) else: fid.read(8) # skip the file's measurement time @@ -1394,7 +1394,7 @@ def _read_gdf_header(fname, exclude, include=None): int(tm[10:12]), int(tm[12:14]), int(tm[14:16]) * pow(10, 4), - tzinfo=timezone.utc, + tzinfo=UTC, ) except Exception: pass @@ -1565,7 +1565,7 @@ def _read_gdf_header(fname, exclude, include=None): meas_date = read_from_file_or_buffer(fid, UINT64, 1)[0] if meas_date != 0: - meas_date = datetime(1, 1, 1, tzinfo=timezone.utc) + timedelta( + meas_date = datetime(1, 1, 1, tzinfo=UTC) + timedelta( meas_date * pow(2, -32) - 367 ) else: @@ -1573,14 +1573,14 @@ def _read_gdf_header(fname, exclude, include=None): birthday = read_from_file_or_buffer(fid, UINT64, 1).tolist()[0] if birthday == 0: - birthday = datetime(1, 1, 1, tzinfo=timezone.utc) + birthday = datetime(1, 1, 1, tzinfo=UTC) else: - birthday = datetime(1, 1, 1, tzinfo=timezone.utc) + timedelta( + birthday = datetime(1, 1, 1, tzinfo=UTC) + timedelta( birthday * pow(2, -32) - 367 ) patient["birthday"] = birthday - if patient["birthday"] != datetime(1, 1, 1, 0, 0, tzinfo=timezone.utc): - today = datetime.now(tz=timezone.utc) + if patient["birthday"] != datetime(1, 1, 1, 0, 0, tzinfo=UTC): + today = datetime.now(tz=UTC) patient["age"] = today.year - patient["birthday"].year # fudge the day by -1 if today happens to be a leap day day = 28 if today.month == 2 and today.day == 29 else today.day diff --git a/mne/io/edf/tests/test_gdf.py b/mne/io/edf/tests/test_gdf.py index 4a62163b89d..095232f7793 100644 --- a/mne/io/edf/tests/test_gdf.py +++ b/mne/io/edf/tests/test_gdf.py @@ -3,7 +3,7 @@ # Copyright the MNE-Python contributors. import shutil -from datetime import date, datetime, timedelta, timezone +from datetime import UTC, date, datetime, timedelta from io import BytesIO import numpy as np @@ -85,8 +85,8 @@ def test_gdf2_birthday(tmp_path): new_fname = tmp_path / "temp.gdf" shutil.copyfile(gdf2_path.with_name(gdf2_path.name + ".gdf"), new_fname) # go back 44.5 years so the subject should show up as 44 - offset_edf = datetime.now(tz=timezone.utc) - datetime( # to their ref - 1, 1, 1, tzinfo=timezone.utc + offset_edf = datetime.now(tz=UTC) - datetime( # to their ref + 1, 1, 1, tzinfo=UTC ) offset_44_yr = offset_edf - timedelta(days=int(365 * 44.5)) # 44.5 yr ago offset_44_yr_days = offset_44_yr.total_seconds() / (24 * 60 * 60) # days @@ -101,7 +101,7 @@ def test_gdf2_birthday(tmp_path): raw = read_raw_gdf(new_fname, eog=None, misc=None, preload=True) assert "subject_info" not in raw._raw_extras[0] assert raw.info["subject_info"] is not None - birthdate = datetime(1, 1, 1, tzinfo=timezone.utc) + offset_44_yr + birthdate = datetime(1, 1, 1, tzinfo=UTC) + offset_44_yr assert raw.info["subject_info"]["birthday"] == date( birthdate.year, birthdate.month, diff --git a/mne/io/egi/egimff.py b/mne/io/egi/egimff.py index c21c32b203d..4e1c5955c7c 100644 --- a/mne/io/egi/egimff.py +++ b/mne/io/egi/egimff.py @@ -458,7 +458,7 @@ def __init__( egi_info["new_trigger"] = None assert egi_events.shape[1] == egi_info["last_samps"][-1] - meas_dt_utc = egi_info["meas_dt_local"].astimezone(datetime.timezone.utc) + meas_dt_utc = egi_info["meas_dt_local"].astimezone(datetime.UTC) info = _empty_info(egi_info["sfreq"]) info["meas_date"] = _ensure_meas_date_none_or_dt(meas_dt_utc) info["utc_offset"] = egi_info["utc_offset"] diff --git a/mne/io/egi/tests/test_egi.py b/mne/io/egi/tests/test_egi.py index 427169f735b..5439a301bf4 100644 --- a/mne/io/egi/tests/test_egi.py +++ b/mne/io/egi/tests/test_egi.py @@ -5,7 +5,7 @@ import os from copy import deepcopy -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path import numpy as np @@ -543,7 +543,7 @@ def test_meas_date(fname, timestamp, utc_offset): """Test meas date conversion.""" raw = read_raw_egi(fname, verbose="warning") dt = datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%f%z") - measdate = dt.astimezone(timezone.utc) + measdate = dt.astimezone(UTC) hour_local = int(dt.strftime("%H")) hour_utc = int(raw.info["meas_date"].strftime("%H")) local_utc_diff = hour_local - hour_utc diff --git a/mne/io/hitachi/hitachi.py b/mne/io/hitachi/hitachi.py index fcc4c0df717..cfb32a08c4c 100644 --- a/mne/io/hitachi/hitachi.py +++ b/mne/io/hitachi/hitachi.py @@ -306,7 +306,7 @@ def _get_hitachi_info(fname, S_offset, D_offset, ignore_names): ) if meas_date is None: meas_date = dt.datetime(2000, 1, 1, 0, 0, 0) - meas_date = meas_date.replace(tzinfo=dt.timezone.utc) + meas_date = meas_date.replace(tzinfo=dt.UTC) if subject_info: info_extra["subject_info"] = subject_info diff --git a/mne/io/hitachi/tests/test_hitachi.py b/mne/io/hitachi/tests/test_hitachi.py index 9ec1d43545c..5784c4ceeb5 100644 --- a/mne/io/hitachi/tests/test_hitachi.py +++ b/mne/io/hitachi/tests/test_hitachi.py @@ -220,7 +220,7 @@ def test_hitachi_basic( assert raw.info["lowpass"] == lowpass assert raw.info["subject_info"]["sex"] == sex assert np.isfinite(raw.get_data()).all() - assert raw.info["meas_date"] == dt.datetime(*date, tzinfo=dt.timezone.utc) + assert raw.info["meas_date"] == dt.datetime(*date, tzinfo=dt.UTC) # bad distances (zero) distances = source_detector_distances(raw.info) want = [np.nan] * (n_ch - 4) diff --git a/mne/io/mef/mef.py b/mne/io/mef/mef.py index 46ab9fbc6c7..c4a3787c63d 100644 --- a/mne/io/mef/mef.py +++ b/mne/io/mef/mef.py @@ -132,7 +132,7 @@ def __init__(self, fname, password="", *, preload=False, verbose=None): # Indexing the start time meas_date = ( ( - dt.datetime(1970, 1, 1, tzinfo=dt.timezone.utc) + dt.datetime(1970, 1, 1, tzinfo=dt.UTC) + dt.timedelta(microseconds=int(start_uutc)) ) if start_uutc diff --git a/mne/io/nedf/nedf.py b/mne/io/nedf/nedf.py index 2d3404ecb75..63242a51bee 100644 --- a/mne/io/nedf/nedf.py +++ b/mne/io/nedf/nedf.py @@ -5,7 +5,7 @@ """Import NeuroElectrics DataFormat (NEDF) files.""" from copy import deepcopy -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path from typing import Any @@ -117,7 +117,7 @@ def _parse_nedf_header(header): dt.append(("data", np.dtype(datadt), (5,))) date = headerxml.findtext("StepDetails/StartDate_firstEEGTimestamp", 0) - info["meas_date"] = datetime.fromtimestamp(int(date) / 1000, timezone.utc) + info["meas_date"] = datetime.fromtimestamp(int(date) / 1000, UTC) n_samples = int(_getsubnodetext(eegset, "NumberOfRecordsOfEEG")) n_full, n_last = divmod(n_samples, 5) diff --git a/mne/io/neuralynx/neuralynx.py b/mne/io/neuralynx/neuralynx.py index b084eecfa5c..29e23026e66 100644 --- a/mne/io/neuralynx/neuralynx.py +++ b/mne/io/neuralynx/neuralynx.py @@ -197,7 +197,7 @@ def __init__( ] with info._unlock(): - info["meas_date"] = meas_dates[0].astimezone(datetime.timezone.utc) + info["meas_date"] = meas_dates[0].astimezone(datetime.UTC) info["highpass"] = np.max(highpass_freqs) info["lowpass"] = np.min(lowpass_freqs) diff --git a/mne/io/neuralynx/tests/test_neuralynx.py b/mne/io/neuralynx/tests/test_neuralynx.py index 18578ef4ab7..64f940f1312 100644 --- a/mne/io/neuralynx/tests/test_neuralynx.py +++ b/mne/io/neuralynx/tests/test_neuralynx.py @@ -4,7 +4,7 @@ import os from ast import literal_eval -from datetime import datetime, timezone +from datetime import UTC, datetime import numpy as np import pytest @@ -139,7 +139,7 @@ def test_neuralynx(): assert raw.info["lowpass"] == expected_lp_freq, "lowpass freq not set correctly" assert raw.info["sfreq"] == expected_sfreq, "sampling freq not set correctly" - meas_date_utc = expected_meas_date.astimezone(timezone.utc) + meas_date_utc = expected_meas_date.astimezone(UTC) assert raw.info["meas_date"] == meas_date_utc, "meas_date not set correctly" # test that channel selection worked diff --git a/mne/io/nihon/nihon.py b/mne/io/nihon/nihon.py index c1a98b3f313..61dabd4a03f 100644 --- a/mne/io/nihon/nihon.py +++ b/mne/io/nihon/nihon.py @@ -3,7 +3,7 @@ # Copyright the MNE-Python contributors. from collections import OrderedDict -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path from typing import Any @@ -91,7 +91,7 @@ def _read_nihon_metadata(fname): fid.seek(0x40) meas_str = np.fromfile(fid, "|S14", 1).astype("U14")[0] meas_date = datetime.strptime(meas_str, "%Y%m%d%H%M%S") - meas_date = meas_date.replace(tzinfo=timezone.utc) + meas_date = meas_date.replace(tzinfo=UTC) metadata["meas_date"] = meas_date return metadata diff --git a/mne/io/nirx/nirx.py b/mne/io/nirx/nirx.py index 15448ee7d46..11512b416a7 100644 --- a/mne/io/nirx/nirx.py +++ b/mne/io/nirx/nirx.py @@ -272,7 +272,7 @@ def __init__(self, fname, saturated, *, preload=False, encoding=None, verbose=No except ValueError: pass else: - meas_date = meas_date.replace(tzinfo=dt.timezone.utc) + meas_date = meas_date.replace(tzinfo=dt.UTC) do_break = True logger.debug(f"Measurement date language {loc} detected: {dt_code}") break @@ -287,7 +287,7 @@ def __init__(self, fname, saturated, *, preload=False, encoding=None, verbose=No "The date is being set to January 1st, 2000, " f"instead of {repr(datetime_str)}." ) - meas_date = dt.datetime(2000, 1, 1, 0, 0, 0, tzinfo=dt.timezone.utc) + meas_date = dt.datetime(2000, 1, 1, 0, 0, 0, tzinfo=dt.UTC) # Extract frequencies of light used by machine if is_aurora: diff --git a/mne/io/nirx/tests/test_nirx.py b/mne/io/nirx/tests/test_nirx.py index 3ba2a925139..56354e9e5a6 100644 --- a/mne/io/nirx/tests/test_nirx.py +++ b/mne/io/nirx/tests/test_nirx.py @@ -279,7 +279,7 @@ def test_nirx_15_2_short(): assert raw._data.shape == (26, 145) assert raw.info["sfreq"] == 12.5 assert raw.info["meas_date"] == dt.datetime( - 2019, 8, 23, 7, 37, 4, 540000, tzinfo=dt.timezone.utc + 2019, 8, 23, 7, 37, 4, 540000, tzinfo=dt.UTC ) # Test channel naming @@ -486,7 +486,7 @@ def test_locale_encoding(tmp_path): for line in hdr: fid.write(line) raw = read_raw_nirx(fname, verbose="debug") - want_dt = dt.datetime(2020, 1, 24, 10, 57, 41, 454000, tzinfo=dt.timezone.utc) + want_dt = dt.datetime(2020, 1, 24, 10, 57, 41, 454000, tzinfo=dt.UTC) assert raw.info["meas_date"] == want_dt @@ -499,7 +499,7 @@ def test_nirx_15_2(): assert raw._data.shape == (64, 67) assert raw.info["sfreq"] == 3.90625 assert raw.info["meas_date"] == dt.datetime( - 2019, 10, 2, 9, 8, 47, 511000, tzinfo=dt.timezone.utc + 2019, 10, 2, 9, 8, 47, 511000, tzinfo=dt.UTC ) # Test channel naming @@ -596,7 +596,7 @@ def test_nirx_15_0(): assert raw._data.shape == (20, 92) assert raw.info["sfreq"] == 6.25 assert raw.info["meas_date"] == dt.datetime( - 2019, 10, 27, 13, 53, 34, 209000, tzinfo=dt.timezone.utc + 2019, 10, 27, 13, 53, 34, 209000, tzinfo=dt.UTC ) # Test channel naming diff --git a/mne/io/nsx/nsx.py b/mne/io/nsx/nsx.py index dc7b19f31da..f582fa46135 100644 --- a/mne/io/nsx/nsx.py +++ b/mne/io/nsx/nsx.py @@ -3,7 +3,7 @@ # Copyright the MNE-Python contributors. import os -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path from typing import Any, Literal @@ -291,7 +291,7 @@ def _read_header(fname): "millisecond", ) ], - tzinfo=timezone.utc, + tzinfo=UTC, ) basic_header["meas_date"] = time_origin return basic_header diff --git a/mne/io/persyst/persyst.py b/mne/io/persyst/persyst.py index c33e70799b8..2c6bfb7f0bb 100644 --- a/mne/io/persyst/persyst.py +++ b/mne/io/persyst/persyst.py @@ -5,7 +5,7 @@ import os import os.path as op from collections import OrderedDict -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path from typing import Any @@ -183,7 +183,7 @@ def __init__(self, fname, preload=False, verbose=None): hour=testtime.hour, minute=testtime.minute, second=testtime.second, - tzinfo=timezone.utc, + tzinfo=UTC, ) # Create mne structure diff --git a/mne/io/snirf/_snirf.py b/mne/io/snirf/_snirf.py index be89f90c16a..7f0acf71443 100644 --- a/mne/io/snirf/_snirf.py +++ b/mne/io/snirf/_snirf.py @@ -593,7 +593,7 @@ def natural_keys(text): meas_date = datetime.datetime(2000, 1, 1, 0, 0, 0) else: meas_date = datetime.datetime(2000, 1, 1, 0, 0, 0) - meas_date = meas_date.replace(tzinfo=datetime.timezone.utc) + meas_date = meas_date.replace(tzinfo=datetime.UTC) with info._unlock(): info["meas_date"] = meas_date diff --git a/mne/preprocessing/tests/test_annotate_amplitude.py b/mne/preprocessing/tests/test_annotate_amplitude.py index 7f2699f6906..98850313d26 100644 --- a/mne/preprocessing/tests/test_annotate_amplitude.py +++ b/mne/preprocessing/tests/test_annotate_amplitude.py @@ -16,7 +16,7 @@ from mne.io import RawArray, read_raw_fif from mne.preprocessing import annotate_amplitude -date = datetime.datetime(2021, 12, 10, 7, 52, 24, 405305, tzinfo=datetime.timezone.utc) +date = datetime.datetime(2021, 12, 10, 7, 52, 24, 405305, tzinfo=datetime.UTC) data_path = Path(testing.data_path(download=False)) skip_fname = data_path / "misc" / "intervalrecording_raw.fif" diff --git a/mne/tests/test_annotations.py b/mne/tests/test_annotations.py index 029d1a4b564..a727cbe55ea 100644 --- a/mne/tests/test_annotations.py +++ b/mne/tests/test_annotations.py @@ -4,7 +4,7 @@ import sys from collections import OrderedDict -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta from itertools import repeat from pathlib import Path @@ -97,7 +97,7 @@ def test_basics(): assert annot.orig_time is None else: assert isinstance(annot.orig_time, datetime) - assert annot.orig_time.tzinfo is timezone.utc + assert annot.orig_time.tzinfo is UTC pytest.raises(ValueError, Annotations, onset, duration, description[:9]) pytest.raises(ValueError, Annotations, [onset, 1], duration, description) @@ -993,7 +993,7 @@ def _assert_annotations_equal(a, b, tol=0, comp_extras_as_str=False): assert exa == exb, f"extras[{i}][{col}]" -_ORIG_TIME = datetime.fromtimestamp(1038942071.7201, timezone.utc) +_ORIG_TIME = datetime.fromtimestamp(1038942071.7201, UTC) @pytest.fixture(scope="function", params=("ch_names", "fmt", "with_extras")) @@ -1250,7 +1250,7 @@ def test_handle_meas_date(meas_date, out): """Test meas date formats.""" if out is not None: assert out >= 0 # otherwise it'll break on Windows - out = datetime.fromtimestamp(out, timezone.utc) + out = datetime.fromtimestamp(out, UTC) assert _handle_meas_date(meas_date) == out @@ -1268,7 +1268,7 @@ def test_read_annotation_txt_header(tmp_path): with open(fname, "w") as f: f.write(content) orig_time, _, n_rows_header = _read_annotations_txt_parse_header(fname) - want = datetime.fromtimestamp(1038942071.7201, timezone.utc) + want = datetime.fromtimestamp(1038942071.7201, UTC) assert orig_time == want assert n_rows_header == 5 @@ -1847,9 +1847,9 @@ def test_annot_concat_crop(meas_date, first_samp_1, first_samp_2, setting): meas_date_1 = meas_date_2 = None assert meas_date in (None, "first", "second", "both") if meas_date in ("first", "both"): - meas_date_1 = datetime(2022, 1, 1, 0, 0, 0, tzinfo=timezone.utc) + meas_date_1 = datetime(2022, 1, 1, 0, 0, 0, tzinfo=UTC) if meas_date in ("second", "both"): - meas_date_2 = datetime(2022, 1, 1, 0, 0, 0, tzinfo=timezone.utc) + meas_date_2 = datetime(2022, 1, 1, 0, 0, 0, tzinfo=UTC) del meas_date def _create_raw(eeg, sfreq, onset, description, meas_date, first_samp, setting): diff --git a/mne/time_frequency/tests/test_tfr.py b/mne/time_frequency/tests/test_tfr.py index 4b5c14d5985..02e256313eb 100644 --- a/mne/time_frequency/tests/test_tfr.py +++ b/mne/time_frequency/tests/test_tfr.py @@ -668,7 +668,7 @@ def test_tfr_io(inst, average_tfr, request, tmp_path): minute=0, second=sec_microsec_tuple[0], microsecond=sec_microsec_tuple[1], - tzinfo=datetime.timezone.utc, + tzinfo=datetime.UTC, ) assert tfr_loaded.info["meas_date"] == want with tfr.info._unlock(): diff --git a/mne/utils/numerics.py b/mne/utils/numerics.py index c637bf42b20..d6b148dff8e 100644 --- a/mne/utils/numerics.py +++ b/mne/utils/numerics.py @@ -11,7 +11,7 @@ import shutil import sys from contextlib import contextmanager -from datetime import date, datetime, timedelta, timezone +from datetime import UTC, date, datetime, timedelta from io import BytesIO, StringIO from math import ceil, sqrt from pathlib import Path @@ -981,7 +981,7 @@ def _julian_to_date(jd): # https://aa.usno.navy.mil/data/docs/JulianDate.php # Thursday, A.D. 1970 Jan 1 12:00:00.0 2440588.000000 jd_t0 = 2440588 - datetime_t0 = datetime(1970, 1, 1, 12, 0, 0, 0, tzinfo=timezone.utc) + datetime_t0 = datetime(1970, 1, 1, 12, 0, 0, 0, tzinfo=UTC) dt = timedelta(days=(jd - jd_t0)) return (datetime_t0 + dt).date() @@ -1013,11 +1013,7 @@ def _date_to_julian(jd_date): def _check_dt(dt): - if ( - not isinstance(dt, datetime) - or dt.tzinfo is None - or dt.tzinfo is not timezone.utc - ): + if not isinstance(dt, datetime) or dt.tzinfo is None or dt.tzinfo is not UTC: raise ValueError(f"Date must be datetime object in UTC: {repr(dt)}") @@ -1033,7 +1029,7 @@ def _stamp_to_dt(utc_stamp): stamp = [int(s) for s in utc_stamp] if len(stamp) == 1: # In case there is no microseconds information stamp.append(0) - return datetime.fromtimestamp(0, tz=timezone.utc) + timedelta( + return datetime.fromtimestamp(0, tz=UTC) + timedelta( seconds=stamp[0], microseconds=stamp[1] ) diff --git a/tools/dev/check_steering_committee.py b/tools/dev/check_steering_committee.py index dd62b8e5130..1de8f5e1962 100755 --- a/tools/dev/check_steering_committee.py +++ b/tools/dev/check_steering_committee.py @@ -13,7 +13,7 @@ import os import pprint from collections import Counter -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta from github import Auth, Github from github.Commit import Commit @@ -30,9 +30,7 @@ team_names = [team.name for team in teams] team = teams[team_names.index("MNE-Python Maintainers")] members = list(team.get_members()) -when = (datetime.now().astimezone(timezone.utc) - timedelta(days=daysback)).replace( - tzinfo=None -) +when = (datetime.now().astimezone(UTC) - timedelta(days=daysback)).replace(tzinfo=None) events = { user.login: Counter(Total=0, Commit=0, IssueComment=0, PullRequestComment=0) for user in members diff --git a/tools/dev/update_credit_json.py b/tools/dev/update_credit_json.py index 001d6606152..f1457cb4205 100644 --- a/tools/dev/update_credit_json.py +++ b/tools/dev/update_credit_json.py @@ -36,7 +36,7 @@ # every PR merged within the lookback window is seen regardless of how old or # low-numbered it is (the monthly action runs far more often than this window). # For a full-history rebuild, remove the cutoff `break` below. -cutoff = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=90) +cutoff = datetime.datetime.now(datetime.UTC) - datetime.timedelta(days=90) pulls_iter = repo.get_pulls(state="closed", sort="updated", direction="desc") iter_ = tqdm(pulls_iter, unit="pr", desc="Traversing") n_added = 0 diff --git a/tools/generate_codemeta.py b/tools/generate_codemeta.py index a82a521e067..428f0155c2d 100644 --- a/tools/generate_codemeta.py +++ b/tools/generate_codemeta.py @@ -3,12 +3,11 @@ # Copyright the MNE-Python contributors. import subprocess +import tomllib from argparse import ArgumentParser from datetime import date from pathlib import Path -import tomllib - parser = ArgumentParser(description="Generate codemeta.json and CITATION.cff") parser.add_argument("release_version", type=str) release_version = parser.parse_args().release_version From d46be228a57908062db20e4357cfdc4a0a854879 Mon Sep 17 00:00:00 2001 From: "Thomas S. Binns" Date: Wed, 5 Aug 2026 18:51:24 +0100 Subject: [PATCH 3/4] Add changelog entry --- doc/changes/dev/14140.dependency.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/dev/14140.dependency.rst diff --git a/doc/changes/dev/14140.dependency.rst b/doc/changes/dev/14140.dependency.rst new file mode 100644 index 00000000000..2bdabad0dc5 --- /dev/null +++ b/doc/changes/dev/14140.dependency.rst @@ -0,0 +1 @@ +Updated minimum Python version to 3.11, by `Thomas Binns`_. \ No newline at end of file From adc39d49ec75f147a5c4ad6fb24852d789621fa4 Mon Sep 17 00:00:00 2001 From: "Thomas S. Binns" Date: Wed, 5 Aug 2026 19:16:03 +0100 Subject: [PATCH 4/4] Try fix bad scipy install --- .github/workflows/tests.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8b0763afd7f..4ff4a962ede 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -128,6 +128,10 @@ jobs: python-version: ${{ matrix.python }} if: matrix.kind == 'old' - run: bash ./tools/github_actions_verify_python.sh "${{ matrix.python }}" + - run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends libopenblas-dev + if: matrix.kind == 'old' && startswith(matrix.os, 'ubuntu') - run: bash ./tools/github_actions_dependencies.sh timeout-minutes: 10 - run: python ./tools/github_actions_check_old_env.py