Skip to content
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/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_pyqt5.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_pyqt6.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/):

Expand Down
2 changes: 1 addition & 1 deletion datalab/control/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion datalab/gui/roieditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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],
]
):
Expand Down
6 changes: 1 addition & 5 deletions datalab/plugins/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
)
Expand Down
2 changes: 1 addition & 1 deletion datalab/plugins/catalogtool.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions datalab/plugins/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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"]
Expand Down
4 changes: 1 addition & 3 deletions datalab/plugins/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
15 changes: 1 addition & 14 deletions datalab/plugins/wheels.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions datalab/tests/backbone/plugins/plugin_generator_unit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}"]'
Expand Down
7 changes: 2 additions & 5 deletions datalab/tests/backbone/plugins/plugin_store_unit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
14 changes: 6 additions & 8 deletions datalab/tests/backbone/plugins/plugin_wheels_unit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down
35 changes: 0 additions & 35 deletions datalab/tests/backbone/plugins/plugins_registry_unit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 0 additions & 1 deletion doc/contributing/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
4 changes: 2 additions & 2 deletions doc/features/advanced/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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: <SHA-256 of the released file>
requires_python: ">=3.9"
requires_python: ">=3.10"

The public catalog verifies and mirrors the script under ``modules/<sha256>/...``. 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.

Expand Down Expand Up @@ -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 <https://github.com/DataLab-Platform/plugins>`_, 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 <https://github.com/DataLab-Platform/plugins>`_, 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?
------------------------
Expand Down
4 changes: 2 additions & 2 deletions doc/intro/installation_offline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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::
Expand Down
6 changes: 0 additions & 6 deletions doc/locale/fr/LC_MESSAGES/contributing/environment.po
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""

Expand Down
4 changes: 2 additions & 2 deletions doc/locale/fr/LC_MESSAGES/features/advanced/plugins.po
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://github.com/DataLab-Platform/plugins>`_, 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 <https://github.com/DataLab-Platform/plugins>`_, 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 <https://github.com/DataLab-Platform/plugins>`_, 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 <https://github.com/DataLab-Platform/plugins>`_, 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 ?"
Expand Down
8 changes: 4 additions & 4 deletions doc/locale/fr/LC_MESSAGES/intro/installation_offline.po
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down
Loading
Loading