From f32e1b2aa939b96554a29bb3309d8ec515a5674b 2024-12-10 13:35:49 From: M Bussonnier Date: 2024-12-10 13:35:49 Subject: [PATCH] Drop Python 3.10 (#14589) --- diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 3340c01..d3f3b25 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -19,10 +19,10 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: ["3.10"] + python-version: ["3.13"] include: - os: macos-13 - python-version: "3.10" + python-version: "3.13" steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/nightly-wheel-build.yml b/.github/workflows/nightly-wheel-build.yml index 6f0eadd..b8b958d 100644 --- a/.github/workflows/nightly-wheel-build.yml +++ b/.github/workflows/nightly-wheel-build.yml @@ -17,7 +17,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.13" cache: pip cache-dependency-path: | pyproject.toml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 531f1de..4b818f3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,14 +21,11 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] - python-version: ["3.10", "3.11", "3.12","3.13"] + python-version: ["3.11", "3.12","3.13"] deps: [test_extra] # Test all on ubuntu, test ends on macos include: - os: macos-latest - python-version: "3.10" - deps: test_extra - - os: macos-latest python-version: "3.11" deps: test_extra # Tests minimal dependencies set @@ -40,15 +37,15 @@ jobs: python-version: "3.13" deps: test # Installing optional dependencies stuff takes ages on PyPy - - os: ubuntu-latest - python-version: "pypy-3.10" - deps: test - - os: windows-latest - python-version: "pypy-3.10" - deps: test - - os: macos-latest - python-version: "pypy-3.10" - deps: test + # - os: ubuntu-latest + # python-version: "pypy-3.11" + # deps: test + # - os: windows-latest + # python-version: "pypy-3.11" + # deps: test + # - os: macos-latest + # python-version: "pypy-3.11" + # deps: test # Temporary CI run to use entry point compatible code in matplotlib-inline. - os: ubuntu-latest python-version: "3.12" @@ -88,7 +85,7 @@ jobs: if: runner.os != 'Windows' # setup.py does not support sdist on Windows run: check-manifest - - name: Install entry point compatible code (TEMPORARY) + - name: Install entry point compatible code (TEMPORARY, April 2024) if: matrix.want-latest-entry-point-code run: | python -m pip list diff --git a/IPython/__init__.py b/IPython/__init__.py index b723548..df2f37e 100644 --- a/IPython/__init__.py +++ b/IPython/__init__.py @@ -26,9 +26,10 @@ import sys #----------------------------------------------------------------------------- # Don't forget to also update setup.py when this changes! -if sys.version_info < (3, 10): +if sys.version_info < (3, 11): raise ImportError( """ +IPython 8.31+ supports Python 3.11 and above, following SPEC0 IPython 8.19+ supports Python 3.10 and above, following SPEC0. IPython 8.13+ supports Python 3.9 and above, following NEP 29. IPython 8.0-8.12 supports Python 3.8 and above, following NEP 29. diff --git a/IPython/core/completer.py b/IPython/core/completer.py index b39a922..c9ed55e 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -243,6 +243,14 @@ from traitlets.config.configurable import Configurable import __main__ +from typing import cast + +if sys.version_info < (3, 12): + from typing_extensions import TypedDict, NotRequired, Protocol, TypeAlias, TypeGuard +else: + from typing import TypedDict, NotRequired, Protocol, TypeAlias, TypeGuard + + # skip module docstests __skip_doctest__ = True @@ -257,25 +265,6 @@ except ImportError: JEDI_INSTALLED = False -if TYPE_CHECKING or GENERATING_DOCUMENTATION and sys.version_info >= (3, 11): - from typing import cast - from typing_extensions import TypedDict, NotRequired, Protocol, TypeAlias, TypeGuard -else: - from typing import Generic - - def cast(type_, obj): - """Workaround for `TypeError: MatcherAPIv2() takes no arguments`""" - return obj - - # do not require on runtime - NotRequired = Tuple # requires Python >=3.11 - TypedDict = Dict # by extension of `NotRequired` requires 3.11 too - Protocol = object # requires Python >=3.8 - TypeAlias = Any # requires Python >=3.10 - TypeGuard = Generic # requires Python >=3.10 -if GENERATING_DOCUMENTATION: - from typing import TypedDict - # ----------------------------------------------------------------------------- # Globals #----------------------------------------------------------------------------- diff --git a/IPython/core/guarded_eval.py b/IPython/core/guarded_eval.py index 628fe2f..4044cfb 100644 --- a/IPython/core/guarded_eval.py +++ b/IPython/core/guarded_eval.py @@ -31,10 +31,7 @@ from types import MethodDescriptorType, ModuleType from IPython.utils.decorators import undoc -if sys.version_info < (3, 11): - from typing_extensions import Self, LiteralString -else: - from typing import Self, LiteralString +from typing import Self, LiteralString if sys.version_info < (3, 12): from typing_extensions import TypeAliasType diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 07fb807..bbea59e 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -149,8 +149,6 @@ try: except ImportError: sphinxify = None -if sys.version_info[:2] < (3, 11): - from exceptiongroup import BaseExceptionGroup class ProvisionalWarning(DeprecationWarning): """ diff --git a/IPython/core/tests/test_exceptiongroup_tb.py b/IPython/core/tests/test_exceptiongroup_tb.py index c8e8002..4efa3d3 100644 --- a/IPython/core/tests/test_exceptiongroup_tb.py +++ b/IPython/core/tests/test_exceptiongroup_tb.py @@ -13,7 +13,6 @@ def _exceptiongroup_common( native: bool, ) -> None: pre_raise = "exceptiongroup." if not native else "" - pre_catch = pre_raise if sys.version_info < (3, 11) else "" filestr = f""" {"import exceptiongroup" if not native else ""} import pytest @@ -42,7 +41,7 @@ def _exceptiongroup_common( def outer(outer_chain, inner_chain): try: inner(inner_chain) - except {pre_catch}BaseExceptionGroup as e: + except BaseExceptionGroup as e: if outer_chain == "none": raise if outer_chain == "from": @@ -68,7 +67,7 @@ def _exceptiongroup_common( match_lines += [ " + Exception Group Traceback (most recent call last):", - f" | {pre_catch}BaseExceptionGroup: Oops (2 sub-exceptions)", + " | BaseExceptionGroup: Oops (2 sub-exceptions)", " | ValueError: From f()", " | BaseException: From g()", ] @@ -96,9 +95,6 @@ def _exceptiongroup_common( assert False, f"{expected} not found in cap.stderr" -@pytest.mark.skipif( - sys.version_info < (3, 11), reason="Native ExceptionGroup not implemented" -) @pytest.mark.parametrize("outer_chain", ["none", "from", "another"]) @pytest.mark.parametrize("inner_chain", ["none", "from", "another"]) def test_native_exceptiongroup(outer_chain, inner_chain) -> None: diff --git a/IPython/core/tests/test_guarded_eval.py b/IPython/core/tests/test_guarded_eval.py index f9057d8..eaf6adb 100644 --- a/IPython/core/tests/test_guarded_eval.py +++ b/IPython/core/tests/test_guarded_eval.py @@ -23,10 +23,7 @@ from IPython.testing import decorators as dec import pytest -if sys.version_info < (3, 11): - from typing_extensions import Self, LiteralString -else: - from typing import Self, LiteralString +from typing import Self, LiteralString if sys.version_info < (3, 12): from typing_extensions import TypeAliasType diff --git a/IPython/core/tests/test_inputsplitter.py b/IPython/core/tests/test_inputsplitter.py index 61e06df..1ec48da 100644 --- a/IPython/core/tests/test_inputsplitter.py +++ b/IPython/core/tests/test_inputsplitter.py @@ -320,12 +320,6 @@ class InputSplitterTestCase(unittest.TestCase): self.isp.push(u'\xc3\xa9') self.isp.push(u"u'\xc3\xa9'") - @pytest.mark.xfail( - reason="Bug in python 3.9.8 – bpo 45738", - condition=sys.version_info in [(3, 11, 0, "alpha", 2)], - raises=SystemError, - strict=True, - ) def test_line_continuation(self): """ Test issue #2108.""" isp = self.isp diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py index 0f563b7..f1d2a8e 100644 --- a/IPython/core/tests/test_inputtransformer2.py +++ b/IPython/core/tests/test_inputtransformer2.py @@ -314,17 +314,7 @@ examples = [ pytest.param("\\\r\n", "incomplete", 0), pytest.param("a = '''\n hi", "incomplete", 3), pytest.param("def a():\n x=1\n global x", "invalid", None), - pytest.param( - "a \\ ", - "invalid", - None, - marks=pytest.mark.xfail( - reason="Bug in python 3.9.8 – bpo 45738", - condition=sys.version_info in [(3, 11, 0, "alpha", 2)], - raises=SystemError, - strict=True, - ), - ), # Nothing allowed after backslash, + pytest.param("a \\ ", "invalid", None), # Nothing allowed after backslash, pytest.param("1\\\n+2", "complete", None), ] @@ -336,12 +326,6 @@ def test_check_complete_param(code, expected, number): @pytest.mark.xfail(platform.python_implementation() == "PyPy", reason="fail on pypy") -@pytest.mark.xfail( - reason="Bug in python 3.9.8 – bpo 45738", - condition=sys.version_info in [(3, 11, 0, "alpha", 2)], - raises=SystemError, - strict=True, -) def test_check_complete(): cc = ipt2.TransformerManager().check_complete diff --git a/IPython/core/tests/test_magic_arguments.py b/IPython/core/tests/test_magic_arguments.py index 0044163..2f8764b 100644 --- a/IPython/core/tests/test_magic_arguments.py +++ b/IPython/core/tests/test_magic_arguments.py @@ -76,14 +76,9 @@ def foo(self, args): def test_magic_arguments(): - # “optional arguments” was replaced with “options” in argparse help - # https://docs.python.org/3/whatsnew/3.10.html#argparse - # https://bugs.python.org/issue9694 - options = "optional arguments" if sys.version_info < (3, 10) else "options" - assert ( magic_foo1.__doc__ - == f"::\n\n %foo1 [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" + == f"::\n\n %foo1 [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\noptions:\n -f FOO, --foo FOO an argument\n" ) assert getattr(magic_foo1, "argcmd_name", None) == None assert real_name(magic_foo1) == "foo1" @@ -98,7 +93,7 @@ def test_magic_arguments(): assert ( magic_foo3.__doc__ - == f"::\n\n %foo3 [-f FOO] [-b BAR] [-z BAZ]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n\nGroup:\n -b BAR, --bar BAR a grouped argument\n\nSecond Group:\n -z BAZ, --baz BAZ another grouped argument\n" + == f"::\n\n %foo3 [-f FOO] [-b BAR] [-z BAZ]\n\n{LEADING_SPACE}A docstring.\n\noptions:\n -f FOO, --foo FOO an argument\n\nGroup:\n -b BAR, --bar BAR a grouped argument\n\nSecond Group:\n -z BAZ, --baz BAZ another grouped argument\n" ) assert getattr(magic_foo3, "argcmd_name", None) == None assert real_name(magic_foo3) == "foo3" @@ -107,7 +102,7 @@ def test_magic_arguments(): assert ( magic_foo4.__doc__ - == f"::\n\n %foo4 [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" + == f"::\n\n %foo4 [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\noptions:\n -f FOO, --foo FOO an argument\n" ) assert getattr(magic_foo4, "argcmd_name", None) == None assert real_name(magic_foo4) == "foo4" @@ -116,7 +111,7 @@ def test_magic_arguments(): assert ( magic_foo5.__doc__ - == f"::\n\n %frobnicate [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" + == f"::\n\n %frobnicate [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\noptions:\n -f FOO, --foo FOO an argument\n" ) assert getattr(magic_foo5, "argcmd_name", None) == "frobnicate" assert real_name(magic_foo5) == "frobnicate" @@ -125,7 +120,7 @@ def test_magic_arguments(): assert ( magic_magic_foo.__doc__ - == f"::\n\n %magic_foo [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" + == f"::\n\n %magic_foo [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\noptions:\n -f FOO, --foo FOO an argument\n" ) assert getattr(magic_magic_foo, "argcmd_name", None) == None assert real_name(magic_magic_foo) == "magic_foo" @@ -134,7 +129,7 @@ def test_magic_arguments(): assert ( foo.__doc__ - == f"::\n\n %foo [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\n{options}:\n -f FOO, --foo FOO an argument\n" + == f"::\n\n %foo [-f FOO]\n\n{LEADING_SPACE}A docstring.\n\noptions:\n -f FOO, --foo FOO an argument\n" ) assert getattr(foo, "argcmd_name", None) == None assert real_name(foo) == "foo" diff --git a/IPython/lib/tests/test_pretty.py b/IPython/lib/tests/test_pretty.py index 57e258b..74bcc58 100644 --- a/IPython/lib/tests/test_pretty.py +++ b/IPython/lib/tests/test_pretty.py @@ -141,9 +141,7 @@ def test_pprint_heap_allocated_type(): Test that pprint works for heap allocated types. """ module_name = "xxlimited_35" - expected_output = ( - "xxlimited.Null" if sys.version_info < (3, 10, 6) else "xxlimited_35.Null" - ) + expected_output = "xxlimited_35.Null" xxlimited = pytest.importorskip(module_name) output = pretty.pretty(xxlimited.Null) assert output == expected_output diff --git a/docs/source/conf.py b/docs/source/conf.py index b5a053d..4b1f0ce 100755 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -19,10 +19,7 @@ import sys, os from pathlib import Path -if sys.version_info > (3, 11): - import tomllib -else: - import tomli as tomllib +import tomllib with open("./sphinx.toml", "rb") as f: config = tomllib.load(f) diff --git a/pyproject.toml b/pyproject.toml index e973f03..bdea229 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,11 +20,10 @@ classifiers = [ "Programming Language :: Python :: 3 :: Only", "Topic :: System :: Shells", ] -requires-python = ">=3.10" +requires-python = ">=3.11" dependencies = [ 'colorama; sys_platform == "win32"', "decorator", - "exceptiongroup; python_version<'3.11'", "jedi>=0.16", "matplotlib-inline", 'pexpect>4.3; sys_platform != "win32" and sys_platform != "emscripten"', @@ -71,8 +70,6 @@ doc = [ "sphinx-rtd-theme", "sphinx>=1.3", "sphinxcontrib-jquery", - "tomli ; python_version<'3.11'", - "typing_extensions", ] kernel = [ "ipykernel", diff --git a/setup.py b/setup.py index f9f4f3a..20a57b5 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ import sys # # This check is also made in IPython/__init__, don't forget to update both when # changing Python version requirements. -if sys.version_info < (3, 10): +if sys.version_info < (3, 11): pip_message = 'This may be due to an out of date pip. Make sure you have pip >= 9.0.1.' try: import pip @@ -39,18 +39,10 @@ if sys.version_info < (3, 10): error = """ +(information not available for more recent version of IPython) IPython 8.19+ supports Python 3.10 and above, following SPEC0 IPython 8.13+ supports Python 3.9 and above, following NEP 29. IPython 8.0-8.12 supports Python 3.8 and above, following NEP 29. -When using Python 2.7, please install IPython 5.x LTS Long Term Support version. -Python 3.3 and 3.4 were supported up to IPython 6.x. -Python 3.5 was supported with IPython 7.0 to 7.9. -Python 3.6 was supported with IPython up to 7.16. -Python 3.7 was still supported with the 7.x branch. - -See IPython `README.rst` file for more information: - - https://github.com/ipython/ipython/blob/main/README.rst Python {py} detected. {pip}