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 4e89e63..f41dca4 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 @@ -41,13 +38,13 @@ jobs: deps: test # Installing optional dependencies stuff takes ages on PyPy - os: ubuntu-latest - python-version: "pypy-3.10" + python-version: "pypy-3.11" deps: test - os: windows-latest - python-version: "pypy-3.10" + python-version: "pypy-3.11" deps: test - os: macos-latest - python-version: "pypy-3.10" + python-version: "pypy-3.11" deps: test # Temporary CI run to use entry point compatible code in matplotlib-inline. - os: ubuntu-latest 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 4873be6..2750dfe 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -242,6 +242,10 @@ from traitlets.config.configurable import Configurable import __main__ +from typing import cast +from typing_extensions import TypedDict, NotRequired, Protocol, TypeAlias, TypeGuard + + # skip module docstests __skip_doctest__ = True @@ -256,22 +260,7 @@ 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 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 d5892ca..7f9d86f 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..35ae60d 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,7 +70,6 @@ doc = [ "sphinx-rtd-theme", "sphinx>=1.3", "sphinxcontrib-jquery", - "tomli ; python_version<'3.11'", "typing_extensions", ] kernel = [ 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}