From 4dfef130859c470b23e74dbb3e3a5e5b097f7a99 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Fri, 5 May 2023 17:28:34 +0200 Subject: [PATCH] Drop support for Python 3.7 (#10009) * Drop support for Python 3.7 This updates our documentation and build processes to set Python 3.8 as the oldest support version of Python, consistent with our policy on Python support. This commit also removes remaining Python-version gating, since we were principally using this to account for differences between 3.7 and 3.8. Several backport packages are no longer required for any suppported Python version, because of this change. * Remove test for multiprocessing on Mac With Python 3.7 support now dropped, there are no versions of Python on macOS that we expect to support with multiprocessing again. There's no guarantee that the hypothetical Python 10.11 (???) that this test was previously checking would be any more valid than existing versions. --------- Co-authored-by: Matthew Treinish --- .github/workflows/wheels.yml | 6 +++--- CONTRIBUTING.md | 14 +++++++------- azure-pipelines.yml | 4 ++-- pyproject.toml | 4 ++-- qiskit/__init__.py | 9 --------- .../gradients/finite_diff_estimator_gradient.py | 7 +------ .../gradients/finite_diff_sampler_gradient.py | 8 +------- qiskit/algorithms/optimizers/optimizer.py | 9 +-------- qiskit/algorithms/optimizers/p_bfgs.py | 12 +++++------- qiskit/circuit/controlflow/switch_case.py | 8 +------- qiskit/compiler/transpiler.py | 9 ++------- qiskit/pulse/builder.py | 7 +------ qiskit/qasm2/__init__.py | 8 +------- qiskit/qobj/converters/pulse_instruction.py | 7 +------ qiskit/tools/parallel.py | 5 +---- qiskit/transpiler/passes/basis/basis_translator.py | 7 +------ qiskit/utils/multiprocessing.py | 13 ++----------- qiskit/version.py | 6 +----- .../notes/drop-python3.7-8689e1fa349a49df.yaml | 6 ++++++ requirements-dev.txt | 2 +- requirements.txt | 3 --- setup.py | 3 +-- test/python/test_examples.py | 4 ++-- test/python/tools/jupyter/test_notebooks.py | 2 +- test/python/tools/test_parallel.py | 7 ------- test/python/visualization/test_gate_map.py | 7 ------- tox.ini | 2 +- 27 files changed, 45 insertions(+), 134 deletions(-) create mode 100644 releasenotes/notes/drop-python3.7-8689e1fa349a49df.yaml diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index c94dd428d870..7b7bebc33e0a 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.7' + python-version: '3.10' - uses: dtolnay/rust-toolchain@stable - name: Set up QEMU uses: docker/setup-qemu-action@v1 @@ -50,7 +50,7 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.7' + python-version: '3.10' - uses: dtolnay/rust-toolchain@stable - name: Set up QEMU uses: docker/setup-qemu-action@v1 @@ -83,7 +83,7 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.7' + python-version: '3.10' - uses: dtolnay/rust-toolchain@stable - name: Set up QEMU uses: docker/setup-qemu-action@v1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 638ec5483f0d..73eacde8cf57 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -276,12 +276,12 @@ environment that tox sets up matches the CI environment more closely and it runs the tests in parallel (resulting in much faster execution). To run tests on all installed supported python versions and lint/style checks you can simply run `tox`. Or if you just want to run the tests once run for a specific python -version: `tox -epy37` (or replace py37 with the python version you want to use, -py35 or py36). +version: `tox -epy310` (or replace py310 with the python version you want to use, +py39 or py311). If you just want to run a subset of tests you can pass a selection regex to the test runner. For example, if you want to run all tests that have "dag" in -the test id you can run: `tox -epy37 -- dag`. You can pass arguments directly to +the test id you can run: `tox -epy310 -- dag`. You can pass arguments directly to the test runner after the bare `--`. To see all the options on test selection you can refer to the stestr manual: https://stestr.readthedocs.io/en/stable/MANUAL.html#test-selection @@ -291,21 +291,21 @@ you can do this faster with the `-n`/`--no-discover` option. For example: to run a module: ``` -tox -epy37 -- -n test.python.test_examples +tox -epy310 -- -n test.python.test_examples ``` or to run the same module by path: ``` -tox -epy37 -- -n test/python/test_examples.py +tox -epy310 -- -n test/python/test_examples.py ``` to run a class: ``` -tox -epy37 -- -n test.python.test_examples.TestPythonExamples +tox -epy310 -- -n test.python.test_examples.TestPythonExamples ``` to run a method: ``` -tox -epy37 -- -n test.python.test_examples.TestPythonExamples.test_all_examples +tox -epy310 -- -n test.python.test_examples.TestPythonExamples.test_all_examples ``` Alternatively there is a makefile provided to run tests, however this diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 015bf7a777ef..91aee4bd562e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -34,12 +34,12 @@ parameters: - name: "supportedPythonVersions" displayName: "All supported versions of Python" type: object - default: ["3.7", "3.8", "3.9", "3.10", "3.11"] + default: ["3.8", "3.9", "3.10", "3.11"] - name: "minimumPythonVersion" displayName: "Minimum supported version of Python" type: string - default: "3.7" + default: "3.8" - name: "maximumPythonVersion" displayName: "Maximum supported version of Python" diff --git a/pyproject.toml b/pyproject.toml index d8eebc130298..57400392b35e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta" [tool.black] line-length = 100 -target-version = ['py37', 'py38', 'py39', 'py310', 'py311'] +target-version = ['py38', 'py39', 'py310', 'py311'] [tool.cibuildwheel] manylinux-x86_64-image = "manylinux2014" manylinux-i686-image = "manylinux2014" -skip = "pp* cp36-* *musllinux*" +skip = "pp* cp36-* cp37-* *musllinux*" test-skip = "cp310-win32 cp310-manylinux_i686 cp311-win32 cp311-manylinux_i686" test-command = "python {project}/examples/python/stochastic_swap.py" # We need to use pre-built versions of Numpy and Scipy in the tests; they have a diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 76bb5dace4c5..a8d90d12220f 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -21,15 +21,6 @@ import qiskit._accelerate -if sys.version_info < (3, 8): - warnings.warn( - "Using Qiskit with Python 3.7 is deprecated as of the 0.23.0 release. " - "Support for running Qiskit with Python 3.7 will be removed in the " - "0.25.0 release", - DeprecationWarning, - ) - - # Globally define compiled submodules. The normal import mechanism will not find compiled submodules # in _accelerate because it relies on file paths, but PyO3 generates only one shared library file. # We manually define them on import so people can directly import qiskit._accelerate.* submodules diff --git a/qiskit/algorithms/gradients/finite_diff_estimator_gradient.py b/qiskit/algorithms/gradients/finite_diff_estimator_gradient.py index a588b74dedcf..f154b99db605 100644 --- a/qiskit/algorithms/gradients/finite_diff_estimator_gradient.py +++ b/qiskit/algorithms/gradients/finite_diff_estimator_gradient.py @@ -14,8 +14,8 @@ from __future__ import annotations -import sys from collections.abc import Sequence +from typing import Literal import numpy as np @@ -30,11 +30,6 @@ from ..exceptions import AlgorithmError -if sys.version_info >= (3, 8): - from typing import Literal -else: - from typing_extensions import Literal - class FiniteDiffEstimatorGradient(BaseEstimatorGradient): """ diff --git a/qiskit/algorithms/gradients/finite_diff_sampler_gradient.py b/qiskit/algorithms/gradients/finite_diff_sampler_gradient.py index 8926e13892d4..11bb2ec38749 100644 --- a/qiskit/algorithms/gradients/finite_diff_sampler_gradient.py +++ b/qiskit/algorithms/gradients/finite_diff_sampler_gradient.py @@ -14,8 +14,8 @@ from __future__ import annotations -import sys from collections import defaultdict +from typing import Literal, Sequence import numpy as np @@ -28,12 +28,6 @@ from ..exceptions import AlgorithmError -if sys.version_info >= (3, 8): - # pylint: disable=ungrouped-imports - from typing import Literal, Sequence -else: - from typing_extensions import Literal - class FiniteDiffSamplerGradient(BaseSamplerGradient): """ diff --git a/qiskit/algorithms/optimizers/optimizer.py b/qiskit/algorithms/optimizers/optimizer.py index 3f64384222d9..e253167b5d9d 100644 --- a/qiskit/algorithms/optimizers/optimizer.py +++ b/qiskit/algorithms/optimizers/optimizer.py @@ -18,20 +18,13 @@ from collections.abc import Callable from enum import IntEnum import logging -import sys -from typing import Any, Union +from typing import Any, Union, Protocol import numpy as np import scipy from qiskit.algorithms.algorithm_result import AlgorithmResult -if sys.version_info >= (3, 8): - # pylint: disable=ungrouped-imports - from typing import Protocol -else: - from typing_extensions import Protocol - logger = logging.getLogger(__name__) POINT = Union[float, np.ndarray] diff --git a/qiskit/algorithms/optimizers/p_bfgs.py b/qiskit/algorithms/optimizers/p_bfgs.py index a59a15cbca64..b06e5f703866 100644 --- a/qiskit/algorithms/optimizers/p_bfgs.py +++ b/qiskit/algorithms/optimizers/p_bfgs.py @@ -16,7 +16,6 @@ import logging import multiprocessing import platform -import sys from collections.abc import Callable from typing import SupportsFloat @@ -109,12 +108,11 @@ def minimize( # default. The fork start method should be considered unsafe as it can # lead to crashes. # However P_BFGS doesn't support spawn, so we revert to single process. - if sys.version_info >= (3, 8): - num_procs = 0 - logger.warning( - "For MacOS, python >= 3.8, using only current process. " - "Multiple core use not supported." - ) + num_procs = 0 + logger.warning( + "For MacOS, python >= 3.8, using only current process. " + "Multiple core use not supported." + ) elif platform.system() == "Windows": num_procs = 0 logger.warning( diff --git a/qiskit/circuit/controlflow/switch_case.py b/qiskit/circuit/controlflow/switch_case.py index 30586f25d9bf..179254970f90 100644 --- a/qiskit/circuit/controlflow/switch_case.py +++ b/qiskit/circuit/controlflow/switch_case.py @@ -15,8 +15,7 @@ __all__ = ("SwitchCaseOp", "CASE_DEFAULT") import contextlib -import sys -from typing import Union, Iterable, Any, Tuple, Optional, List +from typing import Union, Iterable, Any, Tuple, Optional, List, Literal from qiskit.circuit import ClassicalRegister, Clbit, QuantumCircuit from qiskit.circuit.exceptions import CircuitError @@ -25,11 +24,6 @@ from .control_flow import ControlFlowOp from ._builder_utils import unify_circuit_resources, partition_registers -if sys.version_info >= (3, 8): - from typing import Literal -else: - from typing_extensions import Literal - class _DefaultCaseType: """The type of the default-case singleton. This is used instead of just having diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 40fd9ff52659..dc93c85a5fcf 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -19,9 +19,10 @@ import logging import os import pickle -import sys from time import time from typing import List, Union, Dict, Callable, Any, Optional, Tuple, Iterable, TypeVar +from multiprocessing.shared_memory import SharedMemory +from multiprocessing.managers import SharedMemoryManager import warnings from qiskit import user_config @@ -48,12 +49,6 @@ from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.target import Target, target_to_backend_properties -if sys.version_info >= (3, 8): - from multiprocessing.shared_memory import SharedMemory - from multiprocessing.managers import SharedMemoryManager -else: - from shared_memory import SharedMemory, SharedMemoryManager - logger = logging.getLogger(__name__) _CircuitT = TypeVar("_CircuitT", bound=Union[QuantumCircuit, List[QuantumCircuit]]) diff --git a/qiskit/pulse/builder.py b/qiskit/pulse/builder.py index eda0b9946219..3a34e31e817d 100644 --- a/qiskit/pulse/builder.py +++ b/qiskit/pulse/builder.py @@ -461,10 +461,10 @@ import contextvars import functools import itertools -import sys import uuid import warnings from contextlib import contextmanager +from functools import singledispatchmethod from typing import ( Any, Callable, @@ -499,11 +499,6 @@ from qiskit.pulse.schedule import Schedule, ScheduleBlock from qiskit.pulse.transforms.alignments import AlignmentKind -if sys.version_info >= (3, 8): - from functools import singledispatchmethod -else: - from singledispatchmethod import singledispatchmethod - #: contextvars.ContextVar[BuilderContext]: active builder BUILDER_CONTEXTVAR = contextvars.ContextVar("backend") diff --git a/qiskit/qasm2/__init__.py b/qiskit/qasm2/__init__.py index 349321539435..79765f3951bb 100644 --- a/qiskit/qasm2/__init__.py +++ b/qiskit/qasm2/__init__.py @@ -385,9 +385,8 @@ def from_qasm_str(qasm_str: str): ] import os -import sys from pathlib import Path -from typing import Iterable, Union, Optional +from typing import Iterable, Union, Optional, Literal # Pylint can't handle the C-extension introspection of `_qasm2` because there's a re-import through # to `qiskit.qasm2.exceptions`, and pylint ends up trying to import `_qasm2` twice, which PyO3 @@ -405,11 +404,6 @@ def from_qasm_str(qasm_str: str): LEGACY_CUSTOM_CLASSICAL, ) -if sys.version_info >= (3, 8): - from typing import Literal -else: - from typing_extensions import Literal - LEGACY_INCLUDE_PATH = ( Path(__file__).parents[1] / "qasm" / "libs", diff --git a/qiskit/qobj/converters/pulse_instruction.py b/qiskit/qobj/converters/pulse_instruction.py index 4ea888cc8a67..9d4635ad7e7b 100644 --- a/qiskit/qobj/converters/pulse_instruction.py +++ b/qiskit/qobj/converters/pulse_instruction.py @@ -16,9 +16,9 @@ import hashlib import re -import sys import warnings from enum import Enum +from functools import singledispatchmethod from typing import Union, List, Iterator, Optional import numpy as np @@ -32,11 +32,6 @@ from qiskit.qobj.utils import MeasLevel from qiskit.utils.deprecation import deprecate_func -if sys.version_info >= (3, 8): - from functools import singledispatchmethod -else: - from singledispatchmethod import singledispatchmethod - class ParametricPulseShapes(Enum): """Map the assembled pulse names to the pulse module waveforms. diff --git a/qiskit/tools/parallel.py b/qiskit/tools/parallel.py index cfbcd6c7ceb5..1b48791e3477 100644 --- a/qiskit/tools/parallel.py +++ b/qiskit/tools/parallel.py @@ -72,10 +72,7 @@ def get_platform_parallel_default(): parallel_default = False # On macOS default false on Python >=3.8 elif sys.platform == "darwin": - if sys.version_info[0] == 3 and sys.version_info[1] >= 8: - parallel_default = False - else: - parallel_default = True + parallel_default = False # On linux (and other OSes) default to True else: parallel_default = True diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index 47796592bd11..3d74b7246754 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -13,10 +13,10 @@ """Translates gates to a target basis using a given equivalence library.""" -import sys import time import logging +from functools import singledispatchmethod from itertools import zip_longest from collections import defaultdict @@ -29,11 +29,6 @@ from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError -if sys.version_info >= (3, 8): - from functools import singledispatchmethod -else: - from singledispatchmethod import singledispatchmethod - logger = logging.getLogger(__name__) diff --git a/qiskit/utils/multiprocessing.py b/qiskit/utils/multiprocessing.py index b6617bcfd4d9..95964b3d5565 100644 --- a/qiskit/utils/multiprocessing.py +++ b/qiskit/utils/multiprocessing.py @@ -14,7 +14,6 @@ import multiprocessing as mp import platform -import sys import psutil @@ -44,14 +43,6 @@ def is_main_process(): if platform.system() == "Windows": return not isinstance(mp.current_process(), mp.context.SpawnProcess) else: - return not ( - isinstance(mp.current_process(), (mp.context.ForkProcess, mp.context.SpawnProcess)) - # In python 3.5 and 3.6, processes created by "ProcessPoolExecutor" are not - # mp.context.ForkProcess or mp.context.SpawnProcess. As a workaround, - # "name" of the process is checked instead. - or ( - sys.version_info[0] == 3 - and (sys.version_info[1] == 5 or sys.version_info[1] == 6) - and mp.current_process().name != "MainProcess" - ) + return not isinstance( + mp.current_process(), (mp.context.ForkProcess, mp.context.SpawnProcess) ) diff --git a/qiskit/version.py b/qiskit/version.py index e5c5cdb50974..6fed7c92bc7e 100644 --- a/qiskit/version.py +++ b/qiskit/version.py @@ -16,7 +16,6 @@ import os import subprocess -import sys from collections.abc import Mapping ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -102,10 +101,7 @@ def __init__(self): self._loaded = False def _load_versions(self): - if sys.version_info >= (3, 8): - from importlib.metadata import version - else: - from importlib_metadata import version + from importlib.metadata import version try: # TODO: Update to use qiskit_aer instead when we remove the diff --git a/releasenotes/notes/drop-python3.7-8689e1fa349a49df.yaml b/releasenotes/notes/drop-python3.7-8689e1fa349a49df.yaml new file mode 100644 index 000000000000..0bc05b296f2b --- /dev/null +++ b/releasenotes/notes/drop-python3.7-8689e1fa349a49df.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + Qiskit Terra 0.25 has dropped support for Python 3.7 following deprecation warnings started in + Qiskit Terra 0.23. This is consistent with Python 3.7's end-of-life on the 27th of June, 2023. + To continue using Qiskit, you must upgrade to a more recent version of Python. diff --git a/requirements-dev.txt b/requirements-dev.txt index 1ec6cb80579f..cf0990e6a36a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -26,4 +26,4 @@ scikit-quant<=0.7;platform_system != 'Windows' jax;platform_system != 'Windows' jaxlib;platform_system != 'Windows' docplex -qiskit-qasm3-import; python_version>='3.8' +qiskit-qasm3-import diff --git a/requirements.txt b/requirements.txt index 1c1b0da48a4c..bf946eda74f4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,9 +11,6 @@ stevedore>=3.0.0 # multiplication in version 0.10 wich breaks parameter assignment test # (can be removed once issue is fix) symengine>=0.9, <0.10; platform_machine == 'x86_64' or platform_machine == 'aarch64' or platform_machine == 'ppc64le' or platform_machine == 'amd64' or platform_machine == 'arm64' -shared-memory38;python_version<'3.8' -typing-extensions; python_version < '3.8' -singledispatchmethod; python_version < '3.8' # Hack around stevedore being broken by importlib_metadata 5.0; we need to pin # the requirements rather than the constraints if we need to cut a release diff --git a/setup.py b/setup.py index 8e43ffeef3c9..352df8669fe8 100755 --- a/setup.py +++ b/setup.py @@ -75,7 +75,6 @@ "Operating System :: MacOS", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -86,7 +85,7 @@ packages=find_packages(exclude=["test*"]), install_requires=REQUIREMENTS, include_package_data=True, - python_requires=">=3.7", + python_requires=">=3.8", extras_require={ "qasm3-import": qasm3_import_extras, "visualization": visualization_extras, diff --git a/test/python/test_examples.py b/test/python/test_examples.py index 398a4df920e1..4da8dc7b771f 100644 --- a/test/python/test_examples.py +++ b/test/python/test_examples.py @@ -32,7 +32,7 @@ class TestPythonExamples(QiskitTestCase): """Test example scripts""" @unittest.skipIf( - sys.platform == "darwin" and sys.version_info[1] >= 8, + sys.platform == "darwin", "Multiprocess spawn fails on macOS python >=3.8 without __name__ == '__main__' guard", ) def test_all_examples(self): @@ -59,7 +59,7 @@ def test_all_examples(self): self.assertEqual(run_example.returncode, 0, error_string) @unittest.skipIf( - sys.platform == "darwin" and sys.version_info[1] >= 8, + sys.platform == "darwin", "Multiprocess spawn fails on macOS python >=3.8 without __name__ == '__main__' guard", ) @online_test diff --git a/test/python/tools/jupyter/test_notebooks.py b/test/python/tools/jupyter/test_notebooks.py index 82b30e77e7f5..14e892947a92 100644 --- a/test/python/tools/jupyter/test_notebooks.py +++ b/test/python/tools/jupyter/test_notebooks.py @@ -78,7 +78,7 @@ def _execute_notebook(self, filename): execute_preprocessor.preprocess(notebook, {"metadata": {"path": self.execution_path}}) @unittest.skipIf( - sys.version_info >= (3, 8) and sys.platform != "linux", + sys.platform != "linux", "Fails with Python >=3.8 on osx and windows", ) def test_jupyter_jobs_pbars(self): diff --git a/test/python/tools/test_parallel.py b/test/python/tools/test_parallel.py index 9f62738b1b49..27801c1f6175 100644 --- a/test/python/tools/test_parallel.py +++ b/test/python/tools/test_parallel.py @@ -48,13 +48,6 @@ def test_windows_parallel_default(self): parallel_default = get_platform_parallel_default() self.assertEqual(parallel_default, False) - def test_mac_os_supported_version_parallel_default(self): - """Verifies the parallel default for macOS.""" - with patch("sys.platform", "darwin"): - with patch("sys.version_info", (10, 11, 0, "final", 0)): - parallel_default = get_platform_parallel_default() - self.assertEqual(parallel_default, True) - def test_mac_os_unsupported_version_parallel_default(self): """Verifies the parallel default for macOS.""" with patch("sys.platform", "darwin"): diff --git a/test/python/visualization/test_gate_map.py b/test/python/visualization/test_gate_map.py index 8b07583b6238..ead5f96e4dc3 100644 --- a/test/python/visualization/test_gate_map.py +++ b/test/python/visualization/test_gate_map.py @@ -12,7 +12,6 @@ """A test for visualizing device coupling maps""" import unittest -import sys from io import BytesIO from PIL import Image @@ -39,12 +38,6 @@ import matplotlib.pyplot as plt -@unittest.skipIf( - sys.version_info < (3, 7), - "Skipping image comparison tests on python 3.6 as they " - "depend on the local matplotlib environment matching the " - "environment for the reference images which is only >=3.7", -) @ddt class TestGateMap(QiskitVisualizationTestCase): """visual tests for plot_gate_map""" diff --git a/tox.ini b/tox.ini index 3a30fa9e1901..be1b1337a9bc 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 3.3.0 -envlist = py37, py38, py39, py310, py311, lint-incr +envlist = py38, py39, py310, py311, lint-incr isolated_build = true [testenv]