SPEC 0 — 最低支持依赖项

作者:
Andreas C. Mueller <andreas.mueller.ml@gmail.com>,Brigitta Sipőcz <brigitta.sipocz@gmail.com>,Jarrod Millman <millman@berkeley.edu>,Madicken Munk <madicken@berkeley.edu>,Matt Haberland <mhaberla@calpoly.edu>,Matthias Bussonnier <bussonniermatthias@gmail.com>,Ralf Gommers <ralf.gommers@gmail.com>,Ross Barnowski <rossbar@berkeley.edu>,Stéfan van der Walt <stefanv@berkeley.edu>,Thomas A Caswell <tcaswell@gmail.com>
讨论:
https://discuss.scientific-python.org/t/spec-0-minimum-supported-versions/33
历史:
https://github.com/scientific-python/specs/commits/main/spec-0000
获得认可:
ipythonmatplotlibnetworkxnumpyscikit-imagescipyxarrayzarr

描述#

本 SPEC 建议 Scientific Python 生态系统中的所有项目采用基于时间的通用策略来放弃依赖项。从本 SPEC 的角度来看,所讨论的依赖项包括核心包以及旧版本的 Python。

所有版本均指功能版本(例如,Python 3.8.0、NumPy 1.19.0;而非 Python 3.8.1、NumPy 1.19.2)。

具体来说,我们建议

  1. 在 Python 版本的初始发布后 **3 年** 放弃对它们的 支持。
  2. 在核心包依赖项的初始发布后 **2 年** 放弃对它们的 支持。
注意

核心包可能会也可能不会决定在发布后的 2 年内提供错误修复版本。因此,项目可能偶尔需要比本 SPEC 建议的更早地放弃对核心包依赖项的支持。例如,如果项目由于关键错误修复而需要核心包的更新的最低版本,而该修复程序未向旧版本回传。

核心项目认可#

核心项目认可本 SPEC 意味着这些项目鼓励 Scientific Python 生态系统中的所有项目限制他们支持旧版 Python 版本和旧版依赖项版本的时长。核心项目认可本 SPEC **并不**意味着该项目将在发布后的两年内提供错误修复版本。

生态系统采用#

徽章#

项目可以通过包含 SPEC 徽章来突出显示他们对本 SPEC 的采用。

SPEC 0 — Minimum Supported Dependencies
[![SPEC 0 — Minimum Supported Dependencies](https://img.shields.io/badge/SPEC-0-green?labelColor=%23004811&color=%235CA038)](https://scientific-python.cn/specs/spec-0000/)
|SPEC 0 — Minimum Supported Dependencies| 

.. |SPEC 0 — Minimum Supported Dependencies| image:: https://img.shields.io/badge/SPEC-0-green?labelColor=%23004811&color=%235CA038
   :target: https://scientific-python.cn/specs/spec-0000/
要使用一个徽章指示多个 SPEC 的采用,请参见 此处

实施#

动机#

限制支持依赖项的范围是软件包限制维护负担的有效方法。需要测试软件包的组合,这也会影响持续集成时间和基础设施维护。当代码必须了解各种配置组合时,代码本身也会变得更加复杂。

采用本 SPEC 将确保软件包之间的一致支持策略,并减少单个项目制定类似策略的需要。

最终,减少的维护负担会释放开发人员的时间,这转化为为用户提供更多功能、错误修复和优化。

背景#

过去,较长的支持周期很常见。这有几个原因,包括 Python 2/3 的过渡、安装软件包的困难以及用户需要使用旧的操作系统提供的 Python 版本。由于通过二进制轮子改进安装、虚拟环境变得普遍以及对 Python 2 的支持被放弃,这种情况已经得到改善。

支持窗口#

gantt
dateFormat YYYY-MM-DD
axisFormat %m / %Y
title Support Window

section python
3.10 : 2021-10-04,2024-10-03
3.11 : 2022-10-24,2025-10-23
3.12 : 2023-10-02,2026-10-01
3.13 : 2024-10-07,2027-10-07

section numpy
1.23.0 : 2022-06-22,2024-06-21
1.24.0 : 2022-12-18,2024-12-17
1.25.0 : 2023-06-17,2025-06-16
1.26.0 : 2023-09-16,2025-09-15
2.0.0 : 2024-06-16,2026-06-16
2.1.0 : 2024-08-18,2026-08-18

section scipy
1.8.0 : 2022-02-05,2024-02-05
1.9.0 : 2022-07-29,2024-07-28
1.10.0 : 2023-01-03,2025-01-02
1.11.0 : 2023-06-25,2025-06-24
1.12.0 : 2024-01-20,2026-01-19
1.13.0 : 2024-04-02,2026-04-02
1.14.0 : 2024-06-24,2026-06-24

section matplotlib
3.6.0 : 2022-09-16,2024-09-15
3.7.0 : 2023-02-13,2025-02-12
3.8.0 : 2023-09-15,2025-09-14
3.9.0 : 2024-05-15,2026-05-15

section pandas
1.4.0 : 2022-01-22,2024-01-22
1.5.0 : 2022-09-19,2024-09-18
2.0.0 : 2023-04-03,2025-04-02
2.1.0 : 2023-08-30,2025-08-29
2.2.0 : 2024-01-20,2026-01-19

section scikit-image
0.20.0 : 2023-02-28,2025-02-27
0.21.0 : 2023-06-02,2025-06-01
0.22.0 : 2023-10-03,2025-10-02
0.23.0 : 2024-04-10,2026-04-10
0.24.0 : 2024-06-18,2026-06-18

section networkx
2.7 : 2022-02-28,2024-02-28
2.8 : 2022-04-09,2024-04-08
3.0 : 2023-01-08,2025-01-07
3.1 : 2023-04-04,2025-04-03
3.2 : 2023-10-19,2025-10-18
3.3 : 2024-04-06,2026-04-06
3.4 : 2024-10-10,2026-10-10

section scikit-learn
1.1.0 : 2022-05-12,2024-05-11
1.2.0 : 2022-12-08,2024-12-07
1.3.0 : 2023-06-30,2025-06-29
1.4.0 : 2024-01-18,2026-01-17
1.5.0 : 2024-05-21,2026-05-21

section xarray
0.21.0 : 2022-01-28,2024-01-28
2022.3.0 : 2022-03-02,2024-03-01
2022.6.0 : 2022-07-22,2024-07-21
2022.9.0 : 2022-09-29,2024-09-28
2022.10.0 : 2022-10-13,2024-10-12
2022.11.0 : 2022-11-04,2024-11-03
2022.12.0 : 2022-12-02,2024-12-01
2023.1.0 : 2023-01-18,2025-01-17
2023.2.0 : 2023-02-07,2025-02-06
2023.3.0 : 2023-03-22,2025-03-21
2023.4.0 : 2023-04-14,2025-04-13
2023.5.0 : 2023-05-19,2025-05-18
2023.6.0 : 2023-06-23,2025-06-22
2023.7.0 : 2023-07-17,2025-07-16
2023.8.0 : 2023-08-20,2025-08-19
2023.9.0 : 2023-09-26,2025-09-25
2023.10.0 : 2023-10-19,2025-10-18
2023.11.0 : 2023-11-17,2025-11-16
2023.12.0 : 2023-12-08,2025-12-07
2024.1.0 : 2024-01-17,2026-01-16
2024.2.0 : 2024-02-19,2026-02-18
2024.3.0 : 2024-03-29,2026-03-29
2024.5.0 : 2024-05-13,2026-05-13
2024.6.0 : 2024-06-13,2026-06-13
2024.7.0 : 2024-07-30,2026-07-30
2024.9.0 : 2024-09-11,2026-09-11

section ipython
7.31.0 : 2022-01-05,2024-01-05
7.32.0 : 2022-02-25,2024-02-25
7.33.0 : 2022-04-29,2024-04-28
7.34.0 : 2022-05-28,2024-05-27
8.0.0 : 2022-01-12,2024-01-12
8.1.0 : 2022-02-25,2024-02-25
8.2.0 : 2022-03-27,2024-03-26
8.3.0 : 2022-04-29,2024-04-28
8.4.0 : 2022-05-28,2024-05-27
8.5.0 : 2022-09-06,2024-09-05
8.6.0 : 2022-10-30,2024-10-29
8.7.0 : 2022-11-28,2024-11-27
8.8.0 : 2023-01-03,2025-01-02
8.9.0 : 2023-01-27,2025-01-26
8.10.0 : 2023-02-10,2025-02-09
8.11.0 : 2023-02-28,2025-02-27
8.12.0 : 2023-03-30,2025-03-29
8.13.0 : 2023-04-28,2025-04-27
8.14.0 : 2023-06-02,2025-06-01
8.15.0 : 2023-09-01,2025-08-31
8.16.0 : 2023-09-29,2025-09-28
8.17.0 : 2023-10-30,2025-10-29
8.18.0 : 2023-11-24,2025-11-23
8.19.0 : 2023-12-22,2025-12-21
8.20.0 : 2024-01-08,2026-01-07
8.21.0 : 2024-01-31,2026-01-30
8.22.0 : 2024-02-22,2026-02-21
8.23.0 : 2024-03-31,2026-03-31
8.24.0 : 2024-04-26,2026-04-26
8.25.0 : 2024-05-31,2026-05-31
8.26.0 : 2024-06-28,2026-06-28
8.27.0 : 2024-08-30,2026-08-30
8.28.0 : 2024-10-02,2026-10-02

section zarr
2.11.0 : 2022-02-07,2024-02-07
2.12.0 : 2022-06-23,2024-06-22
2.13.0 : 2022-09-22,2024-09-21
2.14.0 : 2023-02-10,2025-02-09
2.15.0 : 2023-06-14,2025-06-13
2.16.0 : 2023-07-20,2025-07-19
2.17.0 : 2024-02-14,2026-02-13
2.18.0 : 2024-05-07,2026-05-07

放弃时间表#

以下是自动生成的时间表,其中包含推荐的放弃支持日期。我们建议将给定季度中的下一个版本视为删除对给定项目的支持的版本。

您可能希望延迟删除旧版 Python 版本的支持,直到您的软件包在最新发布的 Python 上完全正常工作,从而使软件包支持的 Python 次要版本数量保持不变。

2024 年 - 第 2 季度:#

建议放弃对以下的支持:#
ipython 7.33.0 到 8.4.0 于 2022 年 4 月和 2022 年 5 月发布
networkx 2.8 于 2022 年 4 月发布
numpy 1.23.0 于 2022 年 6 月发布
scikit-learn 1.1.0 于 2022 年 5 月发布
zarr 2.12.0 于 2022 年 6 月发布

2024 年 - 第 3 季度:#

建议放弃对以下的支持:#
ipython 8.5.0 于 2022 年 9 月发布
matplotlib 3.6.0 于 2022 年 9 月发布
pandas 1.5.0 于 2022 年 9 月发布
scipy 1.9.0 于 2022 年 7 月发布
xarray 2022.6.0 到 2022.9.0 于 2022 年 7 月和 2022 年 9 月发布
zarr 2.13.0 于 2022 年 9 月发布

2024 年 - 第 4 季度:#

建议放弃对以下的支持:#
ipython 8.6.0 到 8.7.0 于 2022 年 10 月和 2022 年 11 月发布
numpy 1.24.0 于 2022 年 12 月发布
python 3.10 于 2021 年 10 月发布
scikit-learn 1.2.0 于 2022 年 12 月发布
xarray 2022.10.0 到 2022.12.0 于 2022 年 10 月和 2022 年 12 月发布

2025 年 - 第 1 季度:#

建议放弃对以下的支持:#
ipython 8.8.0 到 8.12.0 于 2023 年 1 月和 2023 年 3 月发布
matplotlib 3.7.0 于 2023 年 2 月发布
networkx 3.0 于 2023 年 1 月发布
scikit-image 0.20.0 于 2023 年 2 月发布
scipy 1.10.0 于 2023 年 1 月发布
xarray 2023.1.0 到 2023.3.0 于 2023 年 1 月和 2023 年 3 月发布
zarr 2.14.0 于 2023 年 2 月发布

2025 年 - 第 2 季度:#

建议放弃对以下的支持:#
ipython 8.13.0 到 8.14.0 于 2023 年 4 月和 2023 年 6 月发布
networkx 3.1 于 2023 年 4 月发布
numpy 1.25.0 于 2023 年 6 月发布
pandas 2.0.0 于 2023 年 4 月发布
scikit-image 0.21.0 于 2023 年 6 月发布
scikit-learn 1.3.0 于 2023 年 6 月发布
scipy 1.11.0 于 2023 年 6 月发布
xarray 2023.4.0 到 2023.6.0 于 2023 年 4 月和 2023 年 6 月发布
zarr 2.15.0 于 2023 年 6 月发布

2025 年 - 第 3 季度:#

建议放弃对以下的支持:#
ipython 8.15.0 到 8.16.0 于 2023 年 9 月和 2023 年 9 月发布
matplotlib 3.8.0 于 2023 年 9 月发布
numpy 1.26.0 于 2023 年 9 月发布
pandas 2.1.0 于 2023 年 8 月发布
xarray 2023.7.0 到 2023.9.0 于 2023 年 7 月和 2023 年 9 月发布
zarr 2.16.0 于 2023 年 7 月发布

2025 年 - 第 4 季度:#

建议放弃对以下的支持:#
ipython 8.17.0 到 8.19.0 于 2023 年 10 月和 2023 年 12 月发布
networkx 3.2 于 2023 年 10 月发布
python 3.11 于 2022 年 10 月发布
scikit-image 0.22.0 于 2023 年 10 月发布
xarray 2023.10.0 到 2023.12.0 于 2023 年 10 月和 2023 年 12 月发布

2026 年 - 第 1 季度:#

建议放弃对以下的支持:#
ipython 8.20.0 到 8.23.0 于 2024 年 1 月和 2024 年 3 月发布
pandas 2.2.0 于 2024 年 1 月发布
scikit-learn 1.4.0 于 2024 年 1 月发布
scipy 1.12.0 于 2024 年 1 月发布
xarray 2024.1.0 到 2024.3.0 于 2024 年 1 月和 2024 年 3 月发布
zarr 2.17.0 于 2024 年 2 月发布

2026 年 - 第 2 季度:#

建议放弃对以下的支持:#
ipython 8.24.0 到 8.26.0 于 2024 年 4 月和 2024 年 6 月发布
matplotlib 3.9.0 于 2024 年 5 月发布
networkx 3.3 于 2024 年 4 月发布
numpy 2.0.0 于 2024 年 6 月发布
scikit-image 0.23.0 到 0.24.0 于 2024 年 4 月和 2024 年 6 月发布
scikit-learn 1.5.0 于 2024 年 5 月发布
scipy 1.13.0 到 1.14.0 于 2024 年 4 月和 2024 年 6 月发布
xarray 2024.5.0 到 2024.6.0 于 2024 年 5 月和 2024 年 6 月发布
zarr 2.18.0 于 2024 年 5 月发布

2026 年 - 第 3 季度:#

建议放弃对以下的支持:#
ipython 8.27.0 于 2024 年 8 月发布
numpy 2.1.0 于 2024 年 8 月发布
xarray 2024.7.0 到 2024.9.0 于 2024 年 7 月和 2024 年 9 月发布

2026 年 - 第 4 季度:#

建议放弃对以下的支持:#
ipython 8.28.0 于 2024 年 10 月发布
networkx 3.4 于 2024 年 10 月发布
python 3.12 于 2023 年 10 月发布

2027 年 - 第 4 季度:#

建议放弃对以下的支持:#
python 3.13 于 2024 年 10 月发布

备注#

  • 本文档基于 NEP 29,该文档描述了几个替代方案,包括临时版本支持、所有 CPython 支持的版本、Linux 发行版上的默认版本、N 个 Python 次要版本以及从 X.Y.1 Python 版本开始的时间窗口。

  • 生成支持和放弃时间表表的代码

import requests
import collections
from datetime import datetime, timedelta

import pandas as pd
from packaging.version import Version


py_releases = {
    "3.8": "Oct 14, 2019",
    "3.9": "Oct 5, 2020",
    "3.10": "Oct 4, 2021",
    "3.11": "Oct 24, 2022",
    "3.12": "Oct 2, 2023",
    "3.13": "Oct 7, 2024",
}
core_packages = [
    # Path(x).stem for x in glob("../core-projects/*.md") if "_index" not in x
    "numpy",
    "scipy",
    "matplotlib",
    "pandas",
    "scikit-image",
    "networkx",
    "scikit-learn",
    "xarray",
    "ipython",
    "zarr",
]
plus36 = timedelta(days=int(365 * 3))
plus24 = timedelta(days=int(365 * 2))

# Release data

# put cutoff 3 quarters ago – we do not use "just" -9 month,
# to avoid the content of the quarter to change depending on when we generate this
# file during the current quarter.

current_date = pd.Timestamp.now()
current_quarter_start = pd.Timestamp(
    current_date.year, (current_date.quarter - 1) * 3 + 1, 1
)
cutoff = current_quarter_start - pd.DateOffset(months=9)


def get_release_dates(package, support_time=plus24):
    releases = {}

    print(f"Querying pypi.org for {package} versions...", end="", flush=True)
    response = requests.get(
        f"https://pypi.ac.cn/simple/{package}",
        headers={"Accept": "application/vnd.pypi.simple.v1+json"},
    ).json()
    print("OK")

    file_date = collections.defaultdict(list)
    for f in response["files"]:
        ver = f["filename"].split("-")[1]
        try:
            version = Version(ver)
        except:
            continue

        if version.is_prerelease or version.micro != 0:
            continue

        release_date = None
        for format in ["%Y-%m-%dT%H:%M:%S.%fZ", "%Y-%m-%dT%H:%M:%SZ"]:
            try:
                release_date = datetime.strptime(f["upload-time"], format)
            except:
                pass

        if not release_date:
            continue

        file_date[version].append(release_date)

    release_date = {v: min(file_date[v]) for v in file_date}

    for ver, release_date in sorted(release_date.items()):
        drop_date = release_date + support_time
        if drop_date >= cutoff:
            releases[ver] = {
                "release_date": release_date,
                "drop_date": drop_date,
            }

    return releases


package_releases = {
    "python": {
        version: {
            "release_date": datetime.strptime(release_date, "%b %d, %Y"),
            "drop_date": datetime.strptime(release_date, "%b %d, %Y") + plus36,
        }
        for version, release_date in py_releases.items()
    }
}

package_releases |= {package: get_release_dates(package) for package in core_packages}

# filter all items whose drop_date are in the past
package_releases = {
    package: {
        version: dates
        for version, dates in releases.items()
        if dates["drop_date"] > cutoff
    }
    for package, releases in package_releases.items()
}


# Save Gantt chart

print("Saving Mermaid chart to chart.md")
with open("chart.md", "w") as fh:
    fh.write(
        """gantt
dateFormat YYYY-MM-DD
axisFormat %m / %Y
title Support Window"""
    )

    for name, releases in package_releases.items():
        fh.write(f"\n\nsection {name}")
        for version, dates in releases.items():
            fh.write(
                f"\n{version} : {dates['release_date'].strftime('%Y-%m-%d')},{dates['drop_date'].strftime('%Y-%m-%d')}"
            )
    fh.write("\n")

# Print drop schedule

data = []
for k, versions in package_releases.items():
    for v, dates in versions.items():
        data.append(
            (
                k,
                v,
                pd.to_datetime(dates["release_date"]),
                pd.to_datetime(dates["drop_date"]),
            )
        )

df = pd.DataFrame(data, columns=["package", "version", "release", "drop"])

df["quarter"] = df["drop"].dt.to_period("Q")

dq = df.set_index(["quarter", "package"]).sort_index()


print("Saving drop schedule to schedule.md")


def pad_table(table):
    rows = [[el.strip() for el in row.split("|")] for row in table]
    col_widths = [max(map(len, column)) for column in zip(*rows)]
    rows[1] = [
        el if el != "----" else "-" * col_widths[i] for i, el in enumerate(rows[1])
    ]
    padded_table = []
    for row in rows:
        line = ""
        for entry, width in zip(row, col_widths):
            if not width:
                continue
            line += f"| {str.ljust(entry, width)} "
        line += f"|"
        padded_table.append(line)

    return padded_table


def make_table(sub):
    table = []
    table.append("|    |    |    |")
    table.append("|----|----|----|")
    for package in sorted(set(sub.index.get_level_values(0))):
        vers = sub.loc[[package]]["version"]
        minv, maxv = min(vers), max(vers)
        rels = sub.loc[[package]]["release"]
        rel_min, rel_max = min(rels), max(rels)
        version_range = str(minv) if minv == maxv else f"{minv} to {maxv}"
        rel_range = (
            str(rel_min.strftime("%b %Y"))
            if rel_min == rel_max
            else f"{rel_min.strftime('%b %Y')} and {rel_max.strftime('%b %Y')}"
        )
        table.append(f"|{package:<15}|{version_range:<19}|released {rel_range}|")

    return pad_table(table)


def make_quarter(quarter, dq):
    table = ["#### " + str(quarter).replace("Q", " - Quarter ") + ":\n"]
    table.append("###### Recommend drop support for:\n")
    sub = dq.loc[quarter]
    table.extend(make_table(sub))
    return "\n".join(table)


with open("schedule.md", "w") as fh:
    # we collect package 6 month in the past, and drop the first quarter
    # as we might have filtered some of the packages out depending on
    # when we ran the script.
    tb = []
    for quarter in list(sorted(set(dq.index.get_level_values(0))))[1:]:
        tb.append(make_quarter(quarter, dq))

    fh.write("\n\n".join(tb))
    fh.write("\n")
本页