Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Adopt the Scientific Python deprecation schedule: #3485

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ trigger:
- "*.*.x"

variables:
python.version: '3.12'
python.version: '3.13'
PYTEST_ADDOPTS: '-v --color=yes --internet-tests --nunit-xml=test-data/test-results.xml'
TEST_EXTRA: 'test-full'
DEPENDENCIES_VERSION: "latest" # |"pre-release" | "minimum-version"
Expand All @@ -15,16 +15,16 @@ jobs:
vmImage: 'ubuntu-22.04'
strategy:
matrix:
Python3.10:
python.version: '3.10'
Python3.12: {}
Python3.11:
python.version: '3.11'
Python3.13: {}
minimal_dependencies:
TEST_EXTRA: 'test-min'
anndata_dev:
DEPENDENCIES_VERSION: "pre-release"
TEST_TYPE: "coverage"
minimum_versions:
python.version: '3.10'
python.version: '3.11'
DEPENDENCIES_VERSION: "minimum-version"
TEST_TYPE: "coverage"

Expand Down Expand Up @@ -120,8 +120,8 @@ jobs:

- task: UsePythonVersion@0
inputs:
versionSpec: '3.12'
displayName: 'Use Python 3.12'
versionSpec: '3.13'
displayName: 'Use Python 3.13'

- script: |
python -m pip install --upgrade pip
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python: ["3.12"]
python: ["3.13"]
os: [ubuntu-latest]

env:
Expand Down
2 changes: 1 addition & 1 deletion .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ submodules:
build:
os: ubuntu-24.04
tools:
python: '3.12'
python: '3.13'
jobs:
post_checkout:
# unshallow so version can be derived from tag
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/asv.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@

// The Pythons you'd like to test against. If not provided, defaults
// to the current version of Python used to run `asv`.
// "pythons": ["3.10", "3.12"],
// "pythons": ["3.11", "3.13"],

// The list of conda channel names to be searched for benchmark
// dependency packages in the specified order
Expand Down
15 changes: 5 additions & 10 deletions ci/scripts/min-deps.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
#!/usr/bin/env python3
# /// script
# dependencies = [
# "tomli; python_version < '3.11'",
# "packaging",
# ]
# requires-python = ">=3.11"
# dependencies = [ "packaging" ]
# ///
"""Parse a pyproject.toml file and output a list of minimum dependencies."""

from __future__ import annotations

import argparse
import sys
import tomllib
from collections import deque
from contextlib import ExitStack
from functools import cached_property
from pathlib import Path
from typing import TYPE_CHECKING

if sys.version_info >= (3, 11):
import tomllib
else:
import tomli as tomllib

from packaging.requirements import Requirement
from packaging.version import Version

Expand All @@ -48,7 +42,8 @@ def min_dep(req: Requirement) -> Requirement:
spec for spec in req.specifier if spec.operator in {"==", "~=", ">=", ">"}
]
if not filter_specs:
return Requirement(req_name)
# TODO: handle markers
return Requirement(f"{req_name}{req.specifier}")

min_version = Version("0.0.0.a1")
for spec in filter_specs:
Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ def setup(app: Sphinx):
# -- Suppress link warnings ----------------------------------------------------

qualname_overrides = {
"pathlib._local.Path": "pathlib.Path",
"sklearn.neighbors._dist_metrics.DistanceMetric": "sklearn.metrics.DistanceMetric",
"scanpy.plotting._matrixplot.MatrixPlot": "scanpy.pl.MatrixPlot",
"scanpy.plotting._dotplot.DotPlot": "scanpy.pl.DotPlot",
Expand Down
1 change: 1 addition & 0 deletions docs/release-notes/3485.breaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adopt the Scientific Python [deprecation schedule](https://scientific-python.org/specs/spec-0000/): remove Python 3.10 support and add Python 3.13 support {smaller}`P Angerer`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add that this includes bumping minimum anndata support as well

4 changes: 2 additions & 2 deletions hatch.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ overrides.matrix.deps.pre-install-commands = [
{ if = [ "min" ], value = "uv run ci/scripts/min-deps.py pyproject.toml --all-extras -o ci/scanpy-min-deps.txt" },
]
overrides.matrix.deps.python = [
{ if = [ "min" ], value = "3.10" },
{ if = [ "stable", "full", "pre" ], value = "3.12" },
{ if = [ "min" ], value = "3.11" },
{ if = [ "stable", "full", "pre" ], value = "3.13" },
]
overrides.matrix.deps.features = [
{ if = [ "full" ], value = "test-full" },
Expand Down
26 changes: 13 additions & 13 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ requires = [ "hatchling", "hatch-vcs" ]
[project]
name = "scanpy"
description = "Single-Cell Analysis in Python."
requires-python = ">=3.10"
requires-python = ">=3.11"
license = "BSD-3-clause"
authors = [
{ name = "Alex Wolf" },
Expand Down Expand Up @@ -39,29 +39,29 @@ classifiers = [
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering :: Bio-Informatics",
"Topic :: Scientific/Engineering :: Visualization",
]
dependencies = [
"anndata>=0.8",
"numpy>=1.24",
"anndata>=0.9",
"numpy>=1.25",
"matplotlib>=3.6",
"pandas >=1.5",
"scipy>=1.8",
"pandas >=2.0",
"scipy>=1.11",
"seaborn>=0.13",
"h5py>=3.7",
"h5py>=3.8",
"tqdm",
"scikit-learn>=1.1,<1.6.0",
"scikit-learn>=1.1,<1.6",
"statsmodels>=0.13",
"patsy!=1.0.0", # https://github.com/pydata/patsy/issues/215
"networkx>=2.7",
"networkx>=2.8",
"natsort",
"joblib",
"numba>=0.57",
"umap-learn>=0.5,!=0.5.0",
"numba>=0.58",
"umap-learn>=0.5,!=0.5",
"pynndescent>=0.5",
"packaging>=21.3",
"session-info2",
Expand Down Expand Up @@ -146,10 +146,10 @@ magic = [ "magic-impute>=2.0" ] # MAGIC imputation method
skmisc = [ "scikit-misc>=0.1.3" ] # highly_variable_genes method 'seurat_v3'
harmony = [ "harmonypy" ] # Harmony dataset integration
scanorama = [ "scanorama" ] # Scanorama dataset integration
scrublet = [ "scikit-image" ] # Doublet detection with automatic thresholds
scrublet = [ "scikit-image>=0.20" ] # Doublet detection with automatic thresholds
# Acceleration
rapids = [ "cudf>=0.9", "cuml>=0.9", "cugraph>=0.9" ] # GPU accelerated calculation of neighbors
dask = [ "dask[array]>=2022.09.2,<2024.8.0" ] # Use the Dask parallelization engine
dask = [ "dask[array]>=2023.5.1,<2024.8.0" ] # Use the Dask parallelization engine
dask-ml = [ "dask-ml", "scanpy[dask]" ] # Dask-ML for sklearn-like API

[tool.hatch.build.targets.wheel]
Expand Down
34 changes: 0 additions & 34 deletions src/scanpy/_compat.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
from __future__ import annotations

import os
import sys
import warnings
from dataclasses import dataclass, field
from functools import WRAPPER_ASSIGNMENTS, cache, partial, wraps
from importlib.util import find_spec
from pathlib import Path
from typing import TYPE_CHECKING, Literal, ParamSpec, TypeVar, cast, overload

import numpy as np
Expand Down Expand Up @@ -63,25 +60,6 @@ def fullname(typ: type) -> str:
return f"{module}.{name}"


if sys.version_info >= (3, 11):
from contextlib import chdir
else:
import os
from contextlib import AbstractContextManager

@dataclass
class chdir(AbstractContextManager):
path: Path
_old_cwd: list[Path] = field(default_factory=list)

def __enter__(self) -> None:
self._old_cwd.append(Path.cwd())
os.chdir(self.path)

def __exit__(self, *_excinfo) -> None:
os.chdir(self._old_cwd.pop())


def pkg_metadata(package: str) -> PackageMetadata:
from importlib.metadata import metadata

Expand All @@ -106,18 +84,6 @@ def old_positionals(*old_positionals: str):
return lambda func: func


if sys.version_info >= (3, 11):

def add_note(exc: BaseException, note: str) -> None:
exc.add_note(note)
else:

def add_note(exc: BaseException, note: str) -> None:
if not hasattr(exc, "__notes__"):
exc.__notes__ = []
exc.__notes__.append(note)


if sys.version_info >= (3, 13):
from warnings import deprecated as _deprecated
else:
Expand Down
4 changes: 2 additions & 2 deletions src/scanpy/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import logging
import sys
from datetime import datetime, timedelta, timezone
from datetime import UTC, datetime, timedelta
from functools import partial, update_wrapper
from logging import CRITICAL, DEBUG, ERROR, INFO, WARNING
from typing import TYPE_CHECKING, overload
Expand Down Expand Up @@ -45,7 +45,7 @@ def log(
) -> datetime:
from ._settings import settings

now = datetime.now(timezone.utc)
now = datetime.now(UTC)
time_passed: timedelta = None if time is None else now - time
extra = {
**(extra or {}),
Expand Down
4 changes: 2 additions & 2 deletions src/scanpy/readwrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from matplotlib.image import imread

from . import logging as logg
from ._compat import add_note, deprecated, old_positionals
from ._compat import deprecated, old_positionals
from ._settings import settings
from ._utils import _empty

Expand Down Expand Up @@ -1025,7 +1025,7 @@
try:
from certifi import where
except ImportError as e:
add_note(e, f"{msg} Please install `certifi` and try again.")
e.add_note(f"{msg} Please install `certifi` and try again.")

Check warning on line 1028 in src/scanpy/readwrite.py

View check run for this annotation

Codecov / codecov/patch

src/scanpy/readwrite.py#L1028

Added line #L1028 was not covered by tests
raise
else:
logg.warning(f"{msg} Trying to use certifi.")
Expand Down
3 changes: 1 addition & 2 deletions src/testing/scanpy/_pytest/fixtures/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from __future__ import annotations

import warnings
from contextlib import chdir
from typing import TYPE_CHECKING

import numpy as np
Expand Down Expand Up @@ -39,8 +40,6 @@ def float_dtype(request):

@pytest.fixture
def _doctest_env(cache: pytest.Cache, tmp_path: Path) -> Generator[None, None, None]:
from scanpy._compat import chdir

showwarning_orig = warnings.showwarning

def showwarning(message, category, filename, lineno, file=None, line=None): # noqa: PLR0917
Expand Down
11 changes: 0 additions & 11 deletions tests/test_package_structure.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

import importlib
import os
from collections import defaultdict
from inspect import Parameter, signature
from pathlib import Path
Expand Down Expand Up @@ -54,16 +53,6 @@
]


@pytest.fixture
def in_project_dir():
wd_orig = Path.cwd()
os.chdir(proj_dir)
try:
yield proj_dir
finally:
os.chdir(wd_orig)


@pytest.mark.xfail(reason="TODO: unclear if we want this to totally match, let’s see")
def test_descend_classes_and_funcs():
funcs = set(descend_classes_and_funcs(scanpy, "scanpy"))
Expand Down
Loading