From ce6edbedc6c61ed7accd050ac8c74c065e29c6fe Mon Sep 17 00:00:00 2001 From: Pierre Raybaut <1311787+PierreRaybaut@users.noreply.github.com> Date: Fri, 23 Oct 2026 13:46:30 +0200 Subject: [PATCH] chore: require Python 3.10 or newer Refs DataLab-Platform/DataLab#423 Assisted-by: GPT-6.1 Sol --- .github/copilot-instructions.md | 2 +- .github/workflows/test_pyqt5.yml | 2 +- .github/workflows/test_pyqt6.yml | 2 +- README.md | 2 +- datalab/control/remote.py | 2 +- datalab/gui/roieditor.py | 2 +- datalab/plugins/base.py | 6 +-- datalab/plugins/catalogtool.py | 2 +- datalab/plugins/generator.py | 4 +- datalab/plugins/resources.py | 4 +- datalab/plugins/wheels.py | 15 +----- .../plugins/plugin_generator_unit_test.py | 2 + .../plugins/plugin_store_unit_test.py | 7 +-- .../plugins/plugin_wheels_unit_test.py | 14 +++--- .../plugins/plugins_registry_unit_test.py | 35 ------------- doc/contributing/environment.md | 1 - doc/features/advanced/plugins.rst | 4 +- doc/intro/installation_offline.rst | 4 +- .../LC_MESSAGES/contributing/environment.po | 6 --- .../LC_MESSAGES/features/advanced/plugins.po | 4 +- .../LC_MESSAGES/intro/installation_offline.po | 8 +-- .../LC_MESSAGES/release_notes/release_1.04.po | 10 +++- doc/release_notes/release_1.04.md | 5 ++ doc/requirements.rst | 9 ++-- pyproject.toml | 5 +- scripts/Run-DataLabTests.config.template.psd1 | 3 +- scripts/Run-DataLabTests.ps1 | 50 +++++++++++++++---- 27 files changed, 95 insertions(+), 115 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 567c42dbe..e64729df0 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -19,7 +19,7 @@ This document provides comprehensive guidance for AI coding agents working on th ### Technology Stack -- **Python**: 3.9+ (using `from __future__ import annotations`) +- **Python**: 3.10+ (using `from __future__ import annotations`) - **Core Libraries**: NumPy (>=1.22), SciPy (>=1.10.1), scikit-image (>=0.19.2), OpenCV (optional) - **GUI**: Qt via PlotPy (>=2.8.2) and guidata (>=3.13.4) - **Computation**: Sigima (>=1.1.2) - separate package diff --git a/.github/workflows/test_pyqt5.yml b/.github/workflows/test_pyqt5.yml index 725a19119..be6ad700c 100644 --- a/.github/workflows/test_pyqt5.yml +++ b/.github/workflows/test_pyqt5.yml @@ -36,7 +36,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.14"] + python-version: ["3.10", "3.14"] steps: - uses: actions/checkout@v7 diff --git a/.github/workflows/test_pyqt6.yml b/.github/workflows/test_pyqt6.yml index b96245b44..1f046907c 100644 --- a/.github/workflows/test_pyqt6.yml +++ b/.github/workflows/test_pyqt6.yml @@ -36,7 +36,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.14"] + python-version: ["3.10", "3.14"] steps: - uses: actions/checkout@v7 diff --git a/README.md b/README.md index 51616c76c..349c21d3f 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ feature (this requires to install DataLab as a Python package) ## Installation -DataLab requires **Python 3.9+**. +DataLab requires **Python 3.10+**. From [PyPI](https://pypi.org/project/datalab-platform/): diff --git a/datalab/control/remote.py b/datalab/control/remote.py index 677510c02..f7ca3ba13 100644 --- a/datalab/control/remote.py +++ b/datalab/control/remote.py @@ -911,7 +911,7 @@ def get_datalab_xmlrpc_port() -> str: Raises: ConnectionRefusedError: DataLab has not yet been executed """ - # The following is valid only when using Python 3.9+ with DataLab + # The following is valid when using Python 3.10+ with DataLab # installed on the client side. In any other situation, please use the # ``get_datalab_xmlrpc_port`` function from doc/remotecontrol_py27.py. ensure_initialized(load_user_config=True) diff --git a/datalab/gui/roieditor.py b/datalab/gui/roieditor.py index ff41cc6ca..b3aae4c91 100644 --- a/datalab/gui/roieditor.py +++ b/datalab/gui/roieditor.py @@ -592,7 +592,7 @@ class ImageROIEditor( ImageObj, ImageROI, MaskedXYImageItem, - # `Union` is mandatory here for Python 3.9-3.10 compatibility: + # `Union` is mandatory here for Python 3.10 compatibility: Union[AnnotatedPolygon, AnnotatedRectangle, AnnotatedCircle], ] ): diff --git a/datalab/plugins/base.py b/datalab/plugins/base.py index b726441fd..ac4dc3d98 100644 --- a/datalab/plugins/base.py +++ b/datalab/plugins/base.py @@ -1207,11 +1207,7 @@ def _find_package_desktop_module(package_name: str) -> str | None: def _get_plugin_entry_points() -> list[importlib_metadata.EntryPoint]: """Return installed DataLab plugin entry points in deterministic order.""" - entry_points = importlib_metadata.entry_points() - if hasattr(entry_points, "select"): - selected = entry_points.select(group=PLUGIN_ENTRY_POINT_GROUP) - else: # Python 3.9 compatibility - selected = entry_points.get(PLUGIN_ENTRY_POINT_GROUP, ()) + selected = importlib_metadata.entry_points().select(group=PLUGIN_ENTRY_POINT_GROUP) return sorted( selected, key=lambda entry_point: (entry_point.name, entry_point.value) ) diff --git a/datalab/plugins/catalogtool.py b/datalab/plugins/catalogtool.py index 6e1ceef2a..c8130692f 100644 --- a/datalab/plugins/catalogtool.py +++ b/datalab/plugins/catalogtool.py @@ -46,7 +46,7 @@ #: Python versions supported by DataLab: a plugin wheel must install on one of #: them (update when DataLab supports a new Python version) -DESKTOP_PYTHONS = ("3.9", "3.10", "3.11", "3.12", "3.13", "3.14") +DESKTOP_PYTHONS = ("3.10", "3.11", "3.12", "3.13", "3.14") #: Plugin fields edited in ``catalog.json`` and kept when building it again CURATED_PLUGIN_FIELDS = ( "id", diff --git a/datalab/plugins/generator.py b/datalab/plugins/generator.py index 01300502d..625b8acd4 100644 --- a/datalab/plugins/generator.py +++ b/datalab/plugins/generator.py @@ -195,7 +195,7 @@ def _render_pyproject(project: PluginProject) -> str: readme = "README.md" license = "{project.license_id}" license-files = ["LICENSE"] -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = ["{requirement}"] keywords = ["datalab", "datalab-plugin"] {urls} @@ -213,7 +213,7 @@ def _render_pyproject(project: PluginProject) -> str: [tool.ruff] line-length = 88 -target-version = "py39" +target-version = "py310" [tool.ruff.lint] select = ["E", "F", "I"] diff --git a/datalab/plugins/resources.py b/datalab/plugins/resources.py index cc5acb0c5..60d25ec46 100644 --- a/datalab/plugins/resources.py +++ b/datalab/plugins/resources.py @@ -73,8 +73,6 @@ def resolve_package_resource( traversable = resources.files(package) for part in PurePosixPath(path).parts: traversable = traversable.joinpath(part) - # Python 3.9's zipfile.Path.is_file() is also True for missing entries - missing = sys.version_info < (3, 10) and not traversable.exists() - if missing or not traversable.is_file(): + if not traversable.is_file(): raise FileNotFoundError(f"{label} not found: {resource}") return traversable diff --git a/datalab/plugins/wheels.py b/datalab/plugins/wheels.py index 0b8f452aa..609cea8e5 100644 --- a/datalab/plugins/wheels.py +++ b/datalab/plugins/wheels.py @@ -12,12 +12,10 @@ import configparser import hashlib -import importlib.util import io import pathlib import re import sys -import sysconfig import zipfile from collections.abc import Mapping, Sequence from email.parser import BytesParser @@ -109,18 +107,7 @@ def is_compatible_wheel( def _is_stdlib_module(name: str) -> bool: - stdlib_names = getattr(sys, "stdlib_module_names", None) - if stdlib_names is not None: - return name in stdlib_names - # Python 3.9 has no stdlib module list: locate the module instead. - if name in sys.builtin_module_names: - return True - spec = importlib.util.find_spec(name) - if spec is None or not spec.origin: - return False - stdlib_dir = pathlib.Path(sysconfig.get_paths()["stdlib"]).resolve() - origin = pathlib.Path(spec.origin).resolve() - return stdlib_dir in origin.parents and "site-packages" not in origin.parts + return name in sys.stdlib_module_names def _read_bytes(path_or_bytes: str | bytes | bytearray | memoryview) -> bytes: diff --git a/datalab/tests/backbone/plugins/plugin_generator_unit_test.py b/datalab/tests/backbone/plugins/plugin_generator_unit_test.py index 58339a83a..af267b7ff 100644 --- a/datalab/tests/backbone/plugins/plugin_generator_unit_test.py +++ b/datalab/tests/backbone/plugins/plugin_generator_unit_test.py @@ -192,6 +192,8 @@ def test_create_layered_plugin_project( assert 'name = "datalab-camera-characterization"' in pyproject assert 'license = "BSD-3-Clause"' in pyproject assert 'license-files = ["LICENSE"]' in pyproject + assert 'requires-python = ">=3.10"' in pyproject + assert 'target-version = "py310"' in pyproject major, minor = __version__.split(".")[:2] assert ( f'dependencies = ["datalab-platform >= {major}.{minor}, < {int(major) + 1}"]' diff --git a/datalab/tests/backbone/plugins/plugin_store_unit_test.py b/datalab/tests/backbone/plugins/plugin_store_unit_test.py index 13a908de6..8e59dbd5a 100644 --- a/datalab/tests/backbone/plugins/plugin_store_unit_test.py +++ b/datalab/tests/backbone/plugins/plugin_store_unit_test.py @@ -84,11 +84,8 @@ def write_wheel(directory: Path, suffix: str, version: str = "1.0.0", **kwargs) def select_entry_points(group: str) -> list[importlib_metadata.EntryPoint]: - """Return entry points of a group, with the Python 3.9 API too.""" - entry_points = importlib_metadata.entry_points() - if hasattr(entry_points, "select"): - return list(entry_points.select(group=group)) - return list(entry_points.get(group, ())) # Python 3.9 compatibility + """Return entry points of a group.""" + return list(importlib_metadata.entry_points().select(group=group)) def test_installed_wheel_is_discovered_from_its_archive( diff --git a/datalab/tests/backbone/plugins/plugin_wheels_unit_test.py b/datalab/tests/backbone/plugins/plugin_wheels_unit_test.py index 37164bb5d..096d51a5e 100644 --- a/datalab/tests/backbone/plugins/plugin_wheels_unit_test.py +++ b/datalab/tests/backbone/plugins/plugin_wheels_unit_test.py @@ -134,7 +134,7 @@ def test_inspection_checks_python_requirement() -> None: @pytest.mark.parametrize( ("tag", "python_version"), - [("cp313", "3.9"), ("py312", "3.11"), ("py2", "3.11"), ("cp39", "3.11")], + [("cp313", "3.10"), ("py312", "3.11"), ("py2", "3.11"), ("cp39", "3.11")], ) def test_inspection_rejects_wheels_built_for_another_python( tag: str, python_version: str @@ -232,14 +232,12 @@ def test_inspection_treats_invalid_host_version_as_unavailable() -> None: ) -def test_inspection_rejects_stdlib_names_without_stdlib_module_list( - monkeypatch: pytest.MonkeyPatch, -) -> None: - """Python 3.9 has no ``sys.stdlib_module_names``: modules are located.""" - monkeypatch.delattr(sys, "stdlib_module_names", raising=False) - data = make_plugin_wheel(top_level="json\n") +@pytest.mark.parametrize("name", ["json", "os", "sys"]) +def test_inspection_rejects_stdlib_names(name: str) -> None: + """Standard-library packages cannot be replaced by an installed plugin.""" + data = make_plugin_wheel(top_level=f"{name}\n") - with pytest.raises(WheelInspectionError, match="reserved package: 'json'"): + with pytest.raises(WheelInspectionError, match=f"reserved package: '{name}'"): inspect(data) assert inspect(make_plugin_wheel())["top_level_packages"] == [ "example_datalab_plugin" diff --git a/datalab/tests/backbone/plugins/plugins_registry_unit_test.py b/datalab/tests/backbone/plugins/plugins_registry_unit_test.py index 8a7010861..ebdfc64ec 100644 --- a/datalab/tests/backbone/plugins/plugins_registry_unit_test.py +++ b/datalab/tests/backbone/plugins/plugins_registry_unit_test.py @@ -255,41 +255,6 @@ def create_actions(self): sys.modules.pop(module_name, None) -def test_python39_entry_point_mapping_is_supported( - monkeypatch: pytest.MonkeyPatch, -) -> None: - """The Python 3.9 mapping-shaped entry-point API remains supported.""" - plugin_class = create_plugin_class( - "Python39Plugin", - "python39_plugin", - plugin_id="org.example.python39", - display_name="Python 3.9 plugin", - ) - PluginRegistry.clear_plugin_classes() - - class EntryPoint: - """Minimal mapping entry-point value.""" - - name = "python39-plugin" - value = "python39_plugin:Python39Plugin" - - @staticmethod - def load() -> type[PluginBase]: - """Return the Python 3.9 plugin class.""" - return plugin_class - - monkeypatch.setattr( - "datalab.plugins.base.importlib_metadata.entry_points", - lambda: {"datalab.plugins": [EntryPoint()]}, - ) - monkeypatch.setattr("datalab.plugins.base.pkgutil.iter_modules", lambda: []) - monkeypatch.setattr(Conf.plugins_enabled, "get", lambda: True) - - discover_plugins() - - assert PluginRegistry.get_plugin_classes() == [plugin_class] - - def test_shared_entry_point_module_is_reloaded_once( monkeypatch: pytest.MonkeyPatch, ) -> None: diff --git a/doc/contributing/environment.md b/doc/contributing/environment.md index a173897f3..c38ad81ac 100644 --- a/doc/contributing/environment.md +++ b/doc/contributing/environment.md @@ -68,7 +68,6 @@ The following table lists the currently officially used Python distributions: | Python version | Status | WinPython version | | -------------- | ------------ | ----------------- | -| 3.9 | OK | 3.9.10.0 | | 3.10 | OK | 3.10.11.1 | | 3.11 | OK | 3.11.5.0 | | 3.12 | OK | 3.12.3.0 | diff --git a/doc/features/advanced/plugins.rst b/doc/features/advanced/plugins.rst index 07299e404..e12a1f3ff 100644 --- a/doc/features/advanced/plugins.rst +++ b/doc/features/advanced/plugins.rst @@ -310,7 +310,7 @@ For the public catalog, publish the unchanged file as a named GitHub release ass - version: "1.0.0" filename: datalab_spectroscopy.py sha256: - requires_python: ">=3.9" + requires_python: ">=3.10" The public catalog verifies and mirrors the script under ``modules//...``. To use that published catalog offline, copy its JSON and complete relative tree, including both ``modules`` and ``wheels`` when present. Do not rebuild an unchanged mirror whose artifacts are in these subdirectories: the local authoring commands scan the root. Source URLs, revision checks, withdrawals and local-catalog priority follow the same rules for both formats. Module entries target Desktop only, not DataLab-Web or PyPI. @@ -924,7 +924,7 @@ To try the plugin, add the generated directory as a plugin directory (see :ref:` python -m pytest python -m ruff check . -The generated ``pyproject.toml`` requires the current DataLab major version (for example ``datalab-platform >= 1.4, < 2``) and declares the project URLs when the GitHub account is known. The generated ``.github/workflows/release.yml`` publishes the plugin without any PyPI account: pushing a tag such as ``v0.1.0`` builds the wheel, checks that the tag matches the package version, attaches the wheel to a GitHub release, and prints the entry to submit to the `DataLab plugin catalog `_, including the SHA-256 digest of the wheel. Users then install the wheel with **Plugins > Configure plugins... > Install plugins** (see :ref:`plugin_install_from_file`). Listing the plugin in the catalog is optional; once listed, it can also be installed from the **Available plugins** tab (see :ref:`plugin_catalog`). +The generated ``pyproject.toml`` requires Python 3.10 or later and the current DataLab major version (for example ``datalab-platform >= 1.4, < 2``), and declares the project URLs when the GitHub account is known. The generated ``.github/workflows/release.yml`` publishes the plugin without any PyPI account: pushing a tag such as ``v0.1.0`` builds the wheel, checks that the tag matches the package version, attaches the wheel to a GitHub release, and prints the entry to submit to the `DataLab plugin catalog `_, including the SHA-256 digest of the wheel. Users then install the wheel with **Plugins > Configure plugins... > Install plugins** (see :ref:`plugin_install_from_file`). Listing the plugin in the catalog is optional; once listed, it can also be installed from the **Available plugins** tab (see :ref:`plugin_catalog`). How to develop a plugin? ------------------------ diff --git a/doc/intro/installation_offline.rst b/doc/intro/installation_offline.rst index ef1bcc958..9e20a20d3 100644 --- a/doc/intro/installation_offline.rst +++ b/doc/intro/installation_offline.rst @@ -20,7 +20,7 @@ Offline installation via pip :octicon:`info;1em;sd-text-info` :bdg-info-line:`GNU/Linux` :bdg-info-line:`Windows` :bdg-info-line:`macOS` -This method works on **any operating system** with **Python 3.9 to 3.14**. +This method works on **any operating system** with **Python 3.10 to 3.14**. It assumes you have an online machine available to download packages, and a separate offline machine where DataLab will be installed. @@ -31,7 +31,7 @@ Offline machine ^^^^^^^^^^^^^^^ - GNU/Linux, Windows, or macOS -- Python 3.9 to 3.14 installed with the **same major version** as the online machine +- Python 3.10 to 3.14 installed with the **same major version** as the online machine - No assumptions regarding installed packages .. important:: diff --git a/doc/locale/fr/LC_MESSAGES/contributing/environment.po b/doc/locale/fr/LC_MESSAGES/contributing/environment.po index 56cc6e57c..d58cdd526 100644 --- a/doc/locale/fr/LC_MESSAGES/contributing/environment.po +++ b/doc/locale/fr/LC_MESSAGES/contributing/environment.po @@ -137,15 +137,9 @@ msgstr "Statut" msgid "WinPython version" msgstr "Version de WinPython" -msgid "3.9" -msgstr "" - msgid "OK" msgstr "" -msgid "3.9.10.0" -msgstr "" - msgid "3.10" msgstr "" diff --git a/doc/locale/fr/LC_MESSAGES/features/advanced/plugins.po b/doc/locale/fr/LC_MESSAGES/features/advanced/plugins.po index 8c55c72ce..cd638a0f4 100644 --- a/doc/locale/fr/LC_MESSAGES/features/advanced/plugins.po +++ b/doc/locale/fr/LC_MESSAGES/features/advanced/plugins.po @@ -704,8 +704,8 @@ msgstr "Le modèle reste volontairement réduit. Ajoutez des recettes métier, d msgid "To try the plugin, add the generated directory as a plugin directory (see :ref:`plugin_search_paths`) and reload the plugins: DataLab loads the plugin class from ``adapters/desktop.py`` without installing the project. To validate the project, install it with its test dependencies and run its checks from the generated directory:" msgstr "Pour essayer le plugin, ajoutez le répertoire généré comme répertoire de plugins (voir :ref:`plugin_search_paths`) et rechargez les plugins : DataLab charge la classe du plugin depuis ``adapters/desktop.py`` sans installer le projet. Pour valider le projet, installez-le avec ses dépendances de test et lancez ses vérifications depuis le répertoire généré :" -msgid "The generated ``pyproject.toml`` requires the current DataLab major version (for example ``datalab-platform >= 1.4, < 2``) and declares the project URLs when the GitHub account is known. The generated ``.github/workflows/release.yml`` publishes the plugin without any PyPI account: pushing a tag such as ``v0.1.0`` builds the wheel, checks that the tag matches the package version, attaches the wheel to a GitHub release, and prints the entry to submit to the `DataLab plugin catalog `_, including the SHA-256 digest of the wheel. Users then install the wheel with **Plugins > Configure plugins... > Install plugins** (see :ref:`plugin_install_from_file`). Listing the plugin in the catalog is optional; once listed, it can also be installed from the **Available plugins** tab (see :ref:`plugin_catalog`)." -msgstr "Le ``pyproject.toml`` généré exige la version majeure courante de DataLab (par exemple ``datalab-platform >= 1.4, < 2``) et déclare les URL du projet lorsque le compte GitHub est connu. Le fichier ``.github/workflows/release.yml`` généré publie le plugin sans compte PyPI : pousser une étiquette comme ``v0.1.0`` construit le wheel, vérifie que l'étiquette correspond à la version du package, joint le wheel à une release GitHub et affiche la fiche à soumettre au `catalogue de plugins DataLab `_, avec l'empreinte SHA-256 du wheel. Les utilisateurs installent ensuite le wheel avec **Plugins > Configurer les plugins... > Installer des plugins** (voir :ref:`plugin_install_from_file`). L'inscription du plugin au catalogue est facultative ; une fois inscrit, il peut aussi être installé depuis l'onglet **Plugins disponibles** (voir :ref:`plugin_catalog`)." +msgid "The generated ``pyproject.toml`` requires Python 3.10 or later and the current DataLab major version (for example ``datalab-platform >= 1.4, < 2``), and declares the project URLs when the GitHub account is known. The generated ``.github/workflows/release.yml`` publishes the plugin without any PyPI account: pushing a tag such as ``v0.1.0`` builds the wheel, checks that the tag matches the package version, attaches the wheel to a GitHub release, and prints the entry to submit to the `DataLab plugin catalog `_, including the SHA-256 digest of the wheel. Users then install the wheel with **Plugins > Configure plugins... > Install plugins** (see :ref:`plugin_install_from_file`). Listing the plugin in the catalog is optional; once listed, it can also be installed from the **Available plugins** tab (see :ref:`plugin_catalog`)." +msgstr "Le ``pyproject.toml`` généré exige Python 3.10 ou ultérieur et la version majeure courante de DataLab (par exemple ``datalab-platform >= 1.4, < 2``), et déclare les URL du projet lorsque le compte GitHub est connu. Le fichier ``.github/workflows/release.yml`` généré publie le plugin sans compte PyPI : pousser une étiquette comme ``v0.1.0`` construit le wheel, vérifie que l'étiquette correspond à la version du package, joint le wheel à une release GitHub et affiche la fiche à soumettre au `catalogue de plugins DataLab `_, avec l'empreinte SHA-256 du wheel. Les utilisateurs installent ensuite le wheel avec **Plugins > Configurer les plugins... > Installer des plugins** (voir :ref:`plugin_install_from_file`). L'inscription du plugin au catalogue est facultative ; une fois inscrit, il peut aussi être installé depuis l'onglet **Plugins disponibles** (voir :ref:`plugin_catalog`)." msgid "How to develop a plugin?" msgstr "Comment développer un plugin ?" diff --git a/doc/locale/fr/LC_MESSAGES/intro/installation_offline.po b/doc/locale/fr/LC_MESSAGES/intro/installation_offline.po index bcf4942fa..04dd20b88 100644 --- a/doc/locale/fr/LC_MESSAGES/intro/installation_offline.po +++ b/doc/locale/fr/LC_MESSAGES/intro/installation_offline.po @@ -30,8 +30,8 @@ msgstr "Installation hors ligne via pip" msgid ":octicon:`info;1em;sd-text-info` :bdg-info-line:`GNU/Linux` :bdg-info-line:`Windows` :bdg-info-line:`macOS`" msgstr ":octicon:`info;1em;sd-text-info` :bdg-info-line:`GNU/Linux` :bdg-info-line:`Windows` :bdg-info-line:`macOS`" -msgid "This method works on **any operating system** with **Python 3.9 to 3.14**. It assumes you have an online machine available to download packages, and a separate offline machine where DataLab will be installed." -msgstr "Cette méthode fonctionne sur **tout système d'exploitation** avec **Python 3.9 à 3.14**. Elle suppose que vous disposez d'une machine en ligne pour télécharger les paquets, et d'une machine hors ligne distincte où DataLab sera installé." +msgid "This method works on **any operating system** with **Python 3.10 to 3.14**. It assumes you have an online machine available to download packages, and a separate offline machine where DataLab will be installed." +msgstr "Cette méthode fonctionne sur **tout système d'exploitation** avec **Python 3.10 à 3.14**. Elle suppose que vous disposez d'une machine en ligne pour télécharger les paquets, et d'une machine hors ligne distincte où DataLab sera installé." msgid "Requirements" msgstr "Prérequis" @@ -42,8 +42,8 @@ msgstr "Machine hors ligne" msgid "GNU/Linux, Windows, or macOS" msgstr "GNU/Linux, Windows ou macOS" -msgid "Python 3.9 to 3.14 installed with the **same major version** as the online machine" -msgstr "Python 3.9 à 3.14 installé avec la **même version majeure** que la machine en ligne" +msgid "Python 3.10 to 3.14 installed with the **same major version** as the online machine" +msgstr "Python 3.10 à 3.14 installé avec la **même version majeure** que la machine en ligne" msgid "No assumptions regarding installed packages" msgstr "Aucune hypothèse concernant les paquets installés" diff --git a/doc/locale/fr/LC_MESSAGES/release_notes/release_1.04.po b/doc/locale/fr/LC_MESSAGES/release_notes/release_1.04.po index 475587e2b..21399400b 100644 --- a/doc/locale/fr/LC_MESSAGES/release_notes/release_1.04.po +++ b/doc/locale/fr/LC_MESSAGES/release_notes/release_1.04.po @@ -11,6 +11,15 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" +msgid "Requirements" +msgstr "Prérequis" + +msgid "The Python package and newly generated plugin projects now require Python 3.10 or later. Python 3.9 users must keep an earlier compatible release. The standalone application continues to include its own Python interpreter." +msgstr "Le package Python et les nouveaux projets de plugins générés exigent désormais Python 3.10 ou ultérieur. Les utilisateurs de Python 3.9 doivent conserver une version antérieure compatible. L'application autonome continue d'inclure son propre interpréteur Python." + +msgid "The Windows test runner excludes Python 3.9 from current matrices, while retaining explicit tests of pinned historical releases." +msgstr "Le lanceur de tests Windows exclut Python 3.9 des matrices courantes, tout en conservant les tests explicites de versions historiques précisément sélectionnées." + msgid "Version 1.4" msgstr "Version 1.4" @@ -475,4 +484,3 @@ msgstr "**Sécurité :**" msgid "Opening a workspace received from someone else can no longer make DataLab load or run other code installed on the computer. The parameters stored with objects, analysis results and History actions name their class, and DataLab imported whatever module they named and called the object named there, which a modified file could use, for example, to make DataLab quit. Only parameter classes of DataLab, Sigima, guidata and installed plugins are now used; other stored parameters are reported as unreadable (fixes [Issue #407](https://github.com/DataLab-Platform/DataLab/issues/407))" msgstr "Ouvrir un espace de travail reçu d'une autre personne ne permet plus de faire charger ou exécuter par DataLab un autre code installé sur l'ordinateur. Les paramètres enregistrés avec les objets, les résultats d'analyse et les actions de l'historique nomment leur classe, et DataLab importait le module ainsi nommé puis appelait l'objet désigné, ce qu'un fichier modifié pouvait exploiter, par exemple pour faire quitter DataLab. Seules les classes de paramètres de DataLab, Sigima, guidata et des plugins installés sont désormais utilisées ; les autres paramètres enregistrés sont signalés comme illisibles (corrige [l'issue #407](https://github.com/DataLab-Platform/DataLab/issues/407))" - diff --git a/doc/release_notes/release_1.04.md b/doc/release_notes/release_1.04.md index 71f4c01b4..97bddb90f 100644 --- a/doc/release_notes/release_1.04.md +++ b/doc/release_notes/release_1.04.md @@ -2,6 +2,11 @@ ## DataLab Version 1.4.0 ## +### Requirements ### + +* The Python package and newly generated plugin projects now require Python 3.10 or later. Python 3.9 users must keep an earlier compatible release. The standalone application continues to include its own Python interpreter. +* The Windows test runner excludes Python 3.9 from current matrices, while retaining explicit tests of pinned historical releases. + ### ✨ New Features ### **Welcome page:** diff --git a/doc/requirements.rst b/doc/requirements.rst index a6bc882e1..e3e764d03 100644 --- a/doc/requirements.rst +++ b/doc/requirements.rst @@ -8,7 +8,7 @@ The `datalab-platform` package requires the following Python modules: - Version - Summary * - Python - - >=3.9, <4 + - >=3.10, <4 - Python programming language * - guidata - >= 3.16.0 @@ -26,10 +26,10 @@ The `datalab-platform` package requires the following Python modules: - >= 0.1.0, < 0.2 - Exchange and verify scientific workspace provenance as open RO-Crate capsules (library and CLI, no DataLab required) * - NumPy - - >= 1.22, < 2.5 + - >= 1.22, < 2.6 - Fundamental package for array computing in Python * - SciPy - - >= 1.10.1, < 1.17 + - >= 1.10.1, < 1.19 - Fundamental algorithms for scientific computing in Python * - scikit-image - >= 0.19.2, < 0.27 @@ -46,6 +46,9 @@ The `datalab-platform` package requires the following Python modules: * - packaging - >= 21.3 - Core utilities for Python packages + * - pip + - >= 22.2 + - The PyPA recommended tool for installing Python packages. * - jedi - >= 0.18.2 - An autocompletion tool for Python that can be used for text editors. diff --git a/pyproject.toml b/pyproject.toml index 4ee45eed5..5cd6e25d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,6 @@ classifiers = [ "Operating System :: Microsoft :: Windows :: Windows 10", "Operating System :: Microsoft :: Windows :: Windows 11", "Operating System :: POSIX :: Linux", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -39,7 +38,7 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Widget Sets", ] -requires-python = ">=3.9, <4" +requires-python = ">=3.10, <4" dependencies = [ "guidata >= 3.16.0", "PlotPy >= 2.11.0", @@ -164,7 +163,7 @@ exclude = [ ] line-length = 88 # Same as Black. indent-width = 4 # Same as Black. -target-version = "py39" # Assume Python 3.9. +target-version = "py310" # Assume Python 3.10. [tool.ruff.lint] # all rules can be found here: https://beta.ruff.rs/docs/rules/ diff --git a/scripts/Run-DataLabTests.config.template.psd1 b/scripts/Run-DataLabTests.config.template.psd1 index ecaec50dc..071bde0ca 100644 --- a/scripts/Run-DataLabTests.config.template.psd1 +++ b/scripts/Run-DataLabTests.config.template.psd1 @@ -17,8 +17,9 @@ # matrix modes iterate over the versions listed here, in ascending order). # # The example paths below point to WinPython distributions; adapt them. + # For an explicitly pinned historical release only, optionally add a '3.9' + # entry. It is never included in the current "all" or matrix selections. PythonPaths = @{ - '3.9' = 'C:\path\to\python.exe' '3.10' = 'C:\path\to\python.exe' '3.11' = 'C:\path\to\python.exe' '3.12' = 'C:\path\to\python.exe' diff --git a/scripts/Run-DataLabTests.ps1 b/scripts/Run-DataLabTests.ps1 index 2108489bf..b5aa79830 100644 --- a/scripts/Run-DataLabTests.ps1 +++ b/scripts/Run-DataLabTests.ps1 @@ -22,8 +22,9 @@ * develop : Sigima and DataLab from the latest commit of their "develop" branch (git+URL@develop); the rest from PyPI. * main : same but from the "main" branch of Sigima and DataLab. - 2. Python version: 3.9 to 3.14, or "all" to iterate over every version in - turn (one venv + one log per version). + 2. Python version: 3.10 to 3.14, or "all" to iterate over the configured + supported versions (one venv + one log per version). Python 3.9 is + available only explicitly for a pinned historical release. 3. Whether to delete the venvs at the end of the run (default: yes). The .log files are always kept. @@ -40,6 +41,8 @@ .PARAMETER PythonVersion 3.9, 3.10, 3.11, 3.12, 3.13, 3.14 or "all". If absent: asked interactively (default 3.11). Only versions present in the config file can be run. + Python 3.9 is excluded from menus, "all" and matrix mode, and requires a + historical -Source tag or -Source pypi with an explicit -DataLabVersion. .PARAMETER DeleteVenv $true (default) deletes the venv(s) at the end of the run; $false keeps them. @@ -95,7 +98,7 @@ .PARAMETER Matrix Matrix mode: automatically runs the report matrix instead of a single run -- Source {develop, main} x PythonQwt {pypi, } x window mode(s) x - Python 3.9-3.14, sequentially. A few questions (fix variant, mode(s), venv + Python 3.10-3.14, sequentially. A few questions (fix variant, mode(s), venv deletion) then confirmation of the number of cells. Produces a console summary and an aggregated Markdown report (per-cell detail + Source x PythonQwt x mode synthesis) in runs\. In fully interactive mode (no @@ -122,7 +125,7 @@ .\Run-DataLabTests.ps1 -Source develop -PythonVersion all -DeleteVenv:$false .EXAMPLE - .\Run-DataLabTests.ps1 -PythonVersion 3.9 -PytestArgs '-k','signal','--durations=20' + .\Run-DataLabTests.ps1 -Source v1.2.0 -PythonVersion 3.9 -PytestArgs '-k','signal','--durations=20' .EXAMPLE .\Run-DataLabTests.ps1 -PythonQwtBranch v0.15.0 @@ -156,7 +159,7 @@ param( [bool] $GdiProbe = $true, # Matrix mode: automatically runs the report matrix - # (Source {develop,main} x PythonQwt {pypi, } x mode x Python 3.9-3.14). + # (Source {develop,main} x PythonQwt {pypi, } x mode x Python 3.10-3.14). # Produces a console summary + an aggregated Markdown report in runs\. [switch] $Matrix, @@ -233,12 +236,13 @@ if (-not $Config.ContainsKey('PythonPaths')) { throw "Config '$ConfigPath' must define a 'PythonPaths' hashtable (version -> python.exe)." } # Import-PowerShellDataFile returns an UNORDERED hashtable: re-sort the keys by -# version so that "all" / the matrix iterate 3.9 -> 3.14 deterministically. +# version so that "all" / the matrix iterate 3.10 -> 3.14 deterministically. $PythonPaths = [ordered]@{} foreach ($key in ($Config.PythonPaths.Keys | Sort-Object { [version]$_ })) { $PythonPaths[$key] = $Config.PythonPaths[$key] } -$AllVersions = @($PythonPaths.Keys) +$SupportedPythonVersions = @('3.10', '3.11', '3.12', '3.13', '3.14') +$AllVersions = @($PythonPaths.Keys | Where-Object { $_ -in $SupportedPythonVersions }) # requirements.txt: explicit -RequirementsPath wins, else the config override, # else \requirements.txt (resolved relative to the script location). @@ -383,7 +387,22 @@ if (-not $PSBoundParameters.ContainsKey('Branch') -and $Source -in @('develop', } if (-not $Matrix -and -not $PSBoundParameters.ContainsKey('PythonVersion')) { $PythonVersion = Select-FromMenu -Title 'Python version:' ` - -Options @('3.9', '3.10', '3.11', '3.12', '3.13', '3.14', 'all') -Default '3.11' + -Options ($SupportedPythonVersions + @('all')) -Default '3.11' +} +if ($PythonVersion -eq '3.9' -and ( + $Matrix -or -not ( + $TagCombos.Contains($Source) -or ( + $Source -eq 'pypi' -and + -not [string]::IsNullOrWhiteSpace($DataLabVersion) -and + $DataLabVersion -notmatch '\*' + ) + ))) { + throw ('Python 3.9 is reserved for a pinned historical release: use ' + + '-Source or -Source pypi -DataLabVersion . ' + + 'Current branches, floating PyPI installs and matrix mode require Python 3.10 or later.') +} +if (($Matrix -or $PythonVersion -eq 'all') -and $AllVersions.Count -eq 0) { + throw "No supported Python interpreter is configured. Add Python 3.10-3.14 to '$ConfigPath'." } if (-not $Matrix -and -not $PSBoundParameters.ContainsKey('DeleteVenv')) { $delChoice = Select-FromMenu -Title 'Delete the venv(s) at the end of the run?' ` @@ -561,9 +580,15 @@ function Invoke-DataLabTestRun { Invoke-Step -Title '2. Upgrading pip' -Exe $venvPy ` -Arguments @('-m', 'pip', 'install', '--upgrade', 'pip') | Out-Null - # 3. Install DataLab's dependencies - Invoke-Step -Title '3. Installing dependencies (requirements.txt)' -Exe $venvPy ` - -Arguments @('-m', 'pip', 'install', '-r', $RequirementsPath) | Out-Null + # Historical releases must resolve their own dependencies, not today's. + if ($PyVersion -eq '3.9') { + Invoke-Step -Title '3. Installing pytest for a historical release' -Exe $venvPy ` + -Arguments @('-m', 'pip', 'install', 'pytest') | Out-Null + } + else { + Invoke-Step -Title '3. Installing dependencies (requirements.txt)' -Exe $venvPy ` + -Arguments @('-m', 'pip', 'install', '-r', $RequirementsPath) | Out-Null + } # 4. Install DataLab (+ Sigima in develop mode) if ($Source -eq 'pypi') { @@ -1113,6 +1138,9 @@ $versionsToRun = if ($PythonVersion -eq 'all') { $AllVersions } else { @($Python # Pre-validate the interpreters to fail early. foreach ($v in $versionsToRun) { + if (-not $PythonPaths.Contains($v)) { + throw "No Python interpreter is configured for $v. Add its path to '$ConfigPath'." + } if (-not (Test-Path -LiteralPath $PythonPaths[$v])) { throw "Python interpreter not found for $v : $($PythonPaths[$v])" }