From f10da2262f0ad2ceb0b6072af887b8ba07ac5a86 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 25 Oct 2024 17:01:33 -0400 Subject: [PATCH] Add official support for Python 3.13 (#13309) * Add official support for Python 3.13 This commit adds "official" support for Python 3.13 to Qiskit. We implicitly supported it previously because nothing in Qiskit itself was incompatible with 3.13 and we use the stable ABI from rust which is compatible with new releases. But to mark 3.13 as "official" we just need to add it to CI, including the cibuildwheel test phase, and add the trove classifier to the package metadata indicating 3.13 is supported. * Remove scipy pin on 3.13 * Fix docstring tests with 3.13 indent rules * Add 3.13 to asv config too --- .github/workflows/tests.yml | 4 +- .github/workflows/wheels-build.yml | 20 +-- asv.conf.json | 2 +- azure-pipelines.yml | 4 +- pyproject.toml | 1 + test/python/utils/test_deprecation.py | 198 +++++++++++++++++++------- tox.ini | 2 +- 7 files changed, 163 insertions(+), 68 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 20c40dc38321..39439f7dd059 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,7 +20,7 @@ jobs: matrix: # Normally we test min and max version but we can't run python # 3.9 on arm64 until actions/setup-python#808 is resolved - python-version: ["3.10", "3.12"] + python-version: ["3.10", "3.13"] steps: - uses: actions/checkout@v4 - name: Install Rust toolchain @@ -44,7 +44,7 @@ jobs: python -m pip install -U -r requirements.txt -c constraints.txt python -m pip install -U -r requirements-dev.txt -c constraints.txt python -m pip install -c constraints.txt -e . - if: matrix.python-version == '3.12' + if: matrix.python-version == '3.13' - name: 'Install optionals' run: | python -m pip install -r requirements-optional.txt -c constraints.txt diff --git a/.github/workflows/wheels-build.yml b/.github/workflows/wheels-build.yml index 30fdcd84bbb5..a7453eb05568 100644 --- a/.github/workflows/wheels-build.yml +++ b/.github/workflows/wheels-build.yml @@ -27,7 +27,7 @@ on: default: "default" required: false - wheels-32bit: + wheels-32bit: description: >- The action to take for Tier 1 wheels. Choose from 'default', 'build' or 'skip'. @@ -36,7 +36,7 @@ on: default: "default" required: false - wheels-linux-s390x: + wheels-linux-s390x: description: >- The action to take for Linux s390x wheels. Choose from 'default', 'build' or 'skip'. @@ -44,7 +44,7 @@ on: default: "default" required: false - wheels-linux-ppc64le: + wheels-linux-ppc64le: description: >- The action to take for Linux ppc64le wheels. Choose from 'default', 'build' or 'skip'. @@ -52,7 +52,7 @@ on: default: "default" required: false - wheels-linux-aarch64: + wheels-linux-aarch64: description: >- The action to take for Linux AArch64 wheels. Choose from 'default', 'build' or 'skip'. @@ -77,7 +77,7 @@ on: type: boolean default: true required: false - + jobs: wheels-tier-1: @@ -126,7 +126,7 @@ jobs: env: PGO_WORK_DIR: ${{ github.workspace }}/pgo-data PGO_OUT_PATH: ${{ github.workspace }}/merged.profdata - - uses: pypa/cibuildwheel@v2.19.2 + - uses: pypa/cibuildwheel@v2.21.3 - uses: actions/upload-artifact@v4 with: path: ./wheelhouse/*.whl @@ -151,7 +151,7 @@ jobs: with: components: llvm-tools-preview - name: Build wheels - uses: pypa/cibuildwheel@v2.19.2 + uses: pypa/cibuildwheel@v2.21.3 env: CIBW_SKIP: 'pp* cp36-* cp37-* cp38-* *musllinux* *amd64 *x86_64' - uses: actions/upload-artifact@v4 @@ -173,7 +173,7 @@ jobs: - uses: docker/setup-qemu-action@v3 with: platforms: all - - uses: pypa/cibuildwheel@v2.19.2 + - uses: pypa/cibuildwheel@v2.21.3 env: CIBW_ARCHS_LINUX: s390x CIBW_TEST_SKIP: "cp*" @@ -196,7 +196,7 @@ jobs: - uses: docker/setup-qemu-action@v3 with: platforms: all - - uses: pypa/cibuildwheel@v2.19.2 + - uses: pypa/cibuildwheel@v2.21.3 env: CIBW_ARCHS_LINUX: ppc64le CIBW_TEST_SKIP: "cp*" @@ -218,7 +218,7 @@ jobs: - uses: docker/setup-qemu-action@v3 with: platforms: all - - uses: pypa/cibuildwheel@v2.19.2 + - uses: pypa/cibuildwheel@v2.21.3 env: CIBW_ARCHS_LINUX: aarch64 CIBW_TEST_COMMAND: cp -r {project}/test . && QISKIT_PARALLEL=FALSE stestr --test-path test/python run --abbreviate -n test.python.compiler.test_transpiler diff --git a/asv.conf.json b/asv.conf.json index a75bc0c59c3a..379a01cd7edd 100644 --- a/asv.conf.json +++ b/asv.conf.json @@ -17,7 +17,7 @@ "dvcs": "git", "environment_type": "virtualenv", "show_commit_url": "http://github.com/Qiskit/qiskit/commit/", - "pythons": ["3.9", "3.10", "3.11", "3.12"], + "pythons": ["3.9", "3.10", "3.11", "3.12", "3.13"], "benchmark_dir": "test/benchmarks", "env_dir": ".asv/env", "results_dir": ".asv/results" diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 49df19808498..d182ba32f1b3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -37,7 +37,7 @@ parameters: - name: "supportedPythonVersions" displayName: "All supported versions of Python" type: object - default: ["3.9", "3.10", "3.11", "3.12"] + default: ["3.9", "3.10", "3.11", "3.12", "3.13"] - name: "minimumPythonVersion" displayName: "Minimum supported version of Python" @@ -47,7 +47,7 @@ parameters: - name: "maximumPythonVersion" displayName: "Maximum supported version of Python" type: string - default: "3.12" + default: "3.13" # These two versions of Python can be chosen somewhat arbitrarily, but we get # slightly better coverage per PR if they're neither the maximum nor minimum diff --git a/pyproject.toml b/pyproject.toml index 70a6e7c9d0cf..326d28f2a273 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Scientific/Engineering", ] # These are configured in the `tool.setuptools.dynamic` table. diff --git a/test/python/utils/test_deprecation.py b/test/python/utils/test_deprecation.py index 5d3b82e75aa2..08872b9e7a41 100644 --- a/test/python/utils/test_deprecation.py +++ b/test/python/utils/test_deprecation.py @@ -14,6 +14,7 @@ from __future__ import annotations +import sys from textwrap import dedent from qiskit.utils.deprecation import ( @@ -409,17 +410,21 @@ def func3(): to a new line.""" add_deprecation_to_docstring(func3, msg="Deprecated!", since="9.99", pending=False) - self.assertEqual( - func3.__doc__, - ( - f"""Docstring extending + expected_doc = f"""Docstring extending to a new line. {indent} .. deprecated:: 9.99 Deprecated! {indent}""" - ), - ) + if sys.version_info >= (3, 13, 0): + expected_doc = """Docstring extending +to a new line. + +.. deprecated:: 9.99 + Deprecated! +""" + + self.assertEqual(func3.__doc__, expected_doc) def func4(): """ @@ -427,10 +432,7 @@ def func4(): """ add_deprecation_to_docstring(func4, msg="Deprecated!", since="9.99", pending=False) - self.assertEqual( - func4.__doc__, - ( - f"""\ + expected_doc = f"""\ Docstring starting on a new line. {indent} @@ -438,7 +440,18 @@ def func4(): .. deprecated:: 9.99 Deprecated! {indent}""" - ), + if sys.version_info >= (3, 13, 0): + expected_doc = """\ + +Docstring starting on a new line. + +.. deprecated:: 9.99 + Deprecated! +""" + + self.assertEqual( + func4.__doc__, + expected_doc, ) def func5(): @@ -450,11 +463,7 @@ def func5(): """ - add_deprecation_to_docstring(func5, msg="Deprecated!", since="9.99", pending=False) - self.assertEqual( - func5.__doc__, - ( - f"""\ + expected_doc = f"""\ Paragraph 1, line 1. Line 2. @@ -466,7 +475,24 @@ def func5(): .. deprecated:: 9.99 Deprecated! {indent}""" - ), + + if sys.version_info >= (3, 13, 0): + expected_doc = """\ + +Paragraph 1, line 1. +Line 2. + +Paragraph 2. + + +.. deprecated:: 9.99 + Deprecated! +""" + + add_deprecation_to_docstring(func5, msg="Deprecated!", since="9.99", pending=False) + self.assertEqual( + func5.__doc__, + expected_doc, ) def func6(): @@ -478,11 +504,7 @@ def func6(): continued """ - add_deprecation_to_docstring(func6, msg="Deprecated!", since="9.99", pending=False) - self.assertEqual( - func6.__doc__, - ( - f"""Blah. + expected_doc = f"""Blah. A list. * element 1 @@ -493,7 +515,23 @@ def func6(): .. deprecated:: 9.99 Deprecated! {indent}""" - ), + + if sys.version_info >= (3, 13, 0): + expected_doc = """Blah. + +A list. + * element 1 + * element 2 + continued + +.. deprecated:: 9.99 + Deprecated! +""" + + add_deprecation_to_docstring(func6, msg="Deprecated!", since="9.99", pending=False) + self.assertEqual( + func6.__doc__, + expected_doc, ) def test_add_deprecation_docstring_meta_lines(self) -> None: @@ -511,10 +549,7 @@ def func1(): """ add_deprecation_to_docstring(func1, msg="Deprecated!", since="9.99", pending=False) - self.assertEqual( - func1.__doc__, - ( - f"""\ + expected_doc = f"""\ {indent} .. deprecated:: 9.99 Deprecated! @@ -526,7 +561,22 @@ def func1(): Raises: SomeError {indent}""" - ), + if sys.version_info >= (3, 13, 0): + expected_doc = """\ + +.. deprecated:: 9.99 + Deprecated! + + +Returns: + Content. + +Raises: + SomeError""" + + self.assertEqual( + func1.__doc__, + expected_doc, ) def func2(): @@ -537,10 +587,7 @@ def func2(): """ add_deprecation_to_docstring(func2, msg="Deprecated!", since="9.99", pending=False) - self.assertEqual( - func2.__doc__, - ( - f"""Docstring. + expected_doc = f"""Docstring. {indent} .. deprecated:: 9.99 Deprecated! @@ -549,8 +596,17 @@ def func2(): Returns: Content. {indent}""" - ), - ) + if sys.version_info >= (3, 13, 0): + expected_doc = """Docstring. + +.. deprecated:: 9.99 + Deprecated! + + +Returns: + Content.""" + + self.assertEqual(func2.__doc__, expected_doc) def func3(): """ @@ -562,11 +618,7 @@ def func3(): Content. """ - add_deprecation_to_docstring(func3, msg="Deprecated!", since="9.99", pending=False) - self.assertEqual( - func3.__doc__, - ( - f"""\ + expected_doc = f"""\ Docstring starting on a new line. @@ -579,7 +631,24 @@ def func3(): Examples: Content. {indent}""" - ), + if sys.version_info >= (3, 13, 0): + expected_doc = """\ + +Docstring starting on a new line. + +Paragraph 2. + +.. deprecated:: 9.99 + Deprecated! + + +Examples: + Content.""" + + add_deprecation_to_docstring(func3, msg="Deprecated!", since="9.99", pending=False) + self.assertEqual( + func3.__doc__, + expected_doc, ) def test_add_deprecation_docstring_multiple_entries(self) -> None: @@ -613,10 +682,7 @@ def func2(): add_deprecation_to_docstring(func2, msg="Deprecated #1!", since="9.99", pending=False) add_deprecation_to_docstring(func2, msg="Deprecated #2!", since="9.99", pending=False) - self.assertEqual( - func2.__doc__, - ( - f"""\ + expected_doc = f"""\ Docstring starting on a new line. {indent} @@ -628,7 +694,21 @@ def func2(): .. deprecated:: 9.99 Deprecated #2! {indent}""" - ), + if sys.version_info >= (3, 13, 0): + expected_doc = """\ + +Docstring starting on a new line. + +.. deprecated:: 9.99 + Deprecated #1! + +.. deprecated:: 9.99 + Deprecated #2! +""" + + self.assertEqual( + func2.__doc__, + expected_doc, ) def func3(): @@ -638,12 +718,7 @@ def func3(): Content. """ - add_deprecation_to_docstring(func3, msg="Deprecated #1!", since="9.99", pending=False) - add_deprecation_to_docstring(func3, msg="Deprecated #2!", since="9.99", pending=False) - self.assertEqual( - func3.__doc__, - ( - f"""Docstring. + expected_doc = f"""Docstring. {indent} .. deprecated:: 9.99 Deprecated #1! @@ -656,7 +731,26 @@ def func3(): Yields: Content. {indent}""" - ), + + if sys.version_info >= (3, 13, 0): + expected_doc = """Docstring. + +.. deprecated:: 9.99 + Deprecated #1! + + +.. deprecated:: 9.99 + Deprecated #2! + + +Yields: + Content.""" + + add_deprecation_to_docstring(func3, msg="Deprecated #1!", since="9.99", pending=False) + add_deprecation_to_docstring(func3, msg="Deprecated #2!", since="9.99", pending=False) + self.assertEqual( + func3.__doc__, + expected_doc, ) def test_add_deprecation_docstring_pending(self) -> None: diff --git a/tox.ini b/tox.ini index 5456e7731920..d4d822766675 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 4.0 -envlist = py39, py310, py311, py312, lint-incr +envlist = py39, py310, py311, py312, py313, lint-incr isolated_build = true [testenv]