Repository: ipython/ipython

Issue:
Python 3.10 support
In [this PR about PEP-6222](https://github.com/gvanrossum/patma/pull/117) I created an experimental build of Python 3.10 that is available [at this binder link](https://mybinder.org/v2/gh/fperez/patma/master?urlpath=lab). However if I install IPython using `py10 -m pip install IPython` and then try to use it, eventually I get a crash. I'll post the details in a comment below so we keep the top-level issue description simple. It would be great to track 3.10 compatibility, if possible, so we can use IPython for testing/experimenting with new features and ideas (though I realize that may be a big lift in that it's tracking quite a moving target).


Discussion:
Here's an example of the crash that results from `%run test_patma.py` in the binder link above. I 

```
In [6]: run test_patma.py                                                                                                          
Traceback (most recent call last):
  File "/home/jovyan/.local/lib/python3.10/site-packages/jedi/cache.py", line 109, in wrapper
    return dct[key]
KeyError: ((), frozenset())

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/jovyan/.local/lib/python3.10/site-packages/IPython/terminal/ptutils.py", line 113, in get_completions
    yield from self._get_completions(body, offset, cursor_position, self.ipy_completer)
  File "/home/jovyan/.local/lib/python3.10/site-packages/IPython/terminal/ptutils.py", line 126, in _get_completions
    for c in completions:
  File "/home/jovyan/.local/lib/python3.10/site-packages/IPython/core/completer.py", line 438, in _deduplicate_completions
    completions = list(completions)
  File "/home/jovyan/.local/lib/python3.10/site-packages/IPython/core/completer.py", line 1818, in completions
    for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000):
  File "/home/jovyan/.local/lib/python3.10/site-packages/IPython/core/completer.py", line 1861, in _completions
    matched_text, matches, matches_origin, jedi_matches = self._complete(
  File "/home/jovyan/.local/lib/python3.10/site-packages/IPython/core/completer.py", line 2029, in _complete
    completions = self._jedi_matches(
  File "/home/jovyan/.local/lib/python3.10/site-packages/IPython/core/completer.py", line 1373, in _jedi_matches
    interpreter = jedi.Interpreter(
  File "/home/jovyan/.local/lib/python3.10/site-packages/jedi/api/__init__.py", line 858, in __init__
    super(Interpreter, self).__init__(code, environment=environment,
  File "/home/jovyan/.local/lib/python3.10/site-packages/jedi/api/__init__.py", line 184, in __init__
    self._inference_state = InferenceState(
  File "/home/jovyan/.local/lib/python3.10/site-packages/jedi/inference/__init__.py", line 91, in __init__
    self.grammar = environment.get_grammar()
  File "/home/jovyan/.local/lib/python3.10/site-packages/jedi/cache.py", line 111, in wrapper
    result = method(self, *args, **kwargs)
  File "/home/jovyan/.local/lib/python3.10/site-packages/jedi/api/environment.py", line 37, in get_grammar
    return parso.load_grammar(version=version_string)
  File "/home/jovyan/.local/lib/python3.10/site-packages/parso/grammar.py", line 260, in load_grammar
    return load_grammar(**kwargs)
  File "/home/jovyan/.local/lib/python3.10/site-packages/parso/grammar.py", line 236, in load_grammar
    version_info = parse_version_string(version)
  File "/home/jovyan/.local/lib/python3.10/site-packages/parso/utils.py", line 176, in parse_version_string
    return _parse_version(version)
  File "/home/jovyan/.local/lib/python3.10/site-packages/parso/utils.py", line 125, in _parse_version
    raise ValueError('The given version is not in the right format. '
ValueError: The given version is not in the right format. Use something like "3.8" or "3".

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/jovyan/.local/bin/ipython", line 8, in <module>
    sys.exit(start_ipython())
  File "/home/jovyan/.local/lib/python3.10/site-packages/IPython/__init__.py", line 126, in start_ipython
    return launch_new_instance(argv=argv, **kwargs)
  File "/home/jovyan/.local/lib/python3.10/site-packages/traitlets/config/application.py", line 664, in launch_instance
    app.start()
  File "/home/jovyan/.local/lib/python3.10/site-packages/IPython/terminal/ipapp.py", line 356, in start
    self.shell.mainloop()
  File "/home/jovyan/.local/lib/python3.10/site-packages/IPython/terminal/interactiveshell.py", line 563, in mainloop
    self.interact()
  File "/home/jovyan/.local/lib/python3.10/site-packages/IPython/terminal/interactiveshell.py", line 546, in interact
    code = self.prompt_for_code()
  File "/home/jovyan/.local/lib/python3.10/site-packages/IPython/terminal/interactiveshell.py", line 472, in prompt_for_code
    text = self.pt_app.prompt(
  File "/home/jovyan/.local/lib/python3.10/site-packages/prompt_toolkit/shortcuts/prompt.py", line 994, in prompt
    return self.app.run(set_exception_handler=set_exception_handler)
  File "/home/jovyan/.local/lib/python3.10/site-packages/prompt_toolkit/application/application.py", line 811, in run
    return loop.run_until_complete(
  File "/home/jovyan/.local/lib/python3.10/asyncio/base_events.py", line 642, in run_until_complete
    return future.result()
  File "/home/jovyan/.local/lib/python3.10/site-packages/prompt_toolkit/application/application.py", line 778, in run_async
    return await _run_async2()
  File "/home/jovyan/.local/lib/python3.10/site-packages/prompt_toolkit/application/application.py", line 766, in _run_async2
    await self.cancel_and_wait_for_background_tasks()
  File "/home/jovyan/.local/lib/python3.10/site-packages/prompt_toolkit/application/application.py", line 867, in cancel_and_wait_for_background_tasks
    await task
  File "/home/jovyan/.local/lib/python3.10/site-packages/prompt_toolkit/buffer.py", line 1854, in new_coroutine
    await coroutine(*a, **kw)
  File "/home/jovyan/.local/lib/python3.10/site-packages/prompt_toolkit/buffer.py", line 1683, in async_completer
    async for completion in self.completer.get_completions_async(
  File "/home/jovyan/.local/lib/python3.10/site-packages/prompt_toolkit/completion/base.py", line 269, in get_completions_async
    async for completion in completer.get_completions_async(
  File "/home/jovyan/.local/lib/python3.10/site-packages/prompt_toolkit/completion/base.py", line 196, in get_completions_async
    for item in self.get_completions(document, complete_event):
  File "/home/jovyan/.local/lib/python3.10/site-packages/IPython/terminal/ptutils.py", line 116, in get_completions
    print_tb(e)
  File "/home/jovyan/.local/lib/python3.10/traceback.py", line 53, in print_tb
    print_list(extract_tb(tb, limit=limit), file=file)
  File "/home/jovyan/.local/lib/python3.10/traceback.py", line 72, in extract_tb
    return StackSummary.extract(walk_tb(tb), limit=limit)
  File "/home/jovyan/.local/lib/python3.10/traceback.py", line 347, in extract
    for f, lineno in frame_gen:
  File "/home/jovyan/.local/lib/python3.10/traceback.py", line 312, in walk_tb
    yield tb.tb_frame, tb.tb_lineno
AttributeError: 'ValueError' object has no attribute 'tb_frame'
```

It seems that something changed in the structure of exception/traceback objects.  There may be other issues as well, this is just the first problem I saw.
I don't think that's due to 3.10 , if you avoid trying to complete on magics, that should be fine.
actually just avoid completions completely, there is an issue and jedi does not work when Python version number is alpha or beta.

Resolving patch:
diff --git a/IPython/core/oinspect.py b/IPython/core/oinspect.py
--- a/IPython/core/oinspect.py
+++ b/IPython/core/oinspect.py
@@ -305,13 +305,13 @@ def find_file(obj) -> str:
     fname = None
     try:
         fname = inspect.getabsfile(obj)
-    except TypeError:
+    except (OSError, TypeError):
         # For an instance, the file that matters is where its class was
         # declared.
         if hasattr(obj, '__class__'):
             try:
                 fname = inspect.getabsfile(obj.__class__)
-            except TypeError:
+            except (OSError, TypeError):
                 # Can happen for builtins
                 pass
     except:


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -15,13 +15,13 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest]
-        python-version: ["3.7", "3.8", "3.9"]
+        python-version: ["3.7", "3.8", "3.9", "3.10"]
         # Test all on ubuntu, test ends on macos
         include:
           - os: macos-latest
             python-version: "3.7"
           - os: macos-latest
-            python-version: "3.9"
+            python-version: "3.10"
 
     steps:
     - uses: actions/checkout@v2
diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py
--- a/IPython/core/tests/test_completer.py
+++ b/IPython/core/tests/test_completer.py
@@ -26,6 +26,15 @@
     _deduplicate_completions,
 )
 
+if sys.version_info >= (3, 10):
+    import jedi
+    from pkg_resources import parse_version
+
+    # Requires https://github.com/davidhalter/jedi/pull/1795
+    jedi_issue = parse_version(jedi.__version__) <= parse_version("0.18.0")
+else:
+    jedi_issue = False
+
 # -----------------------------------------------------------------------------
 # Test functions
 # -----------------------------------------------------------------------------
@@ -442,6 +451,8 @@ def test_all_completions_dups(self):
                 matches = c.all_completions("TestCl")
                 assert matches == ['TestClass'], jedi_status
                 matches = c.all_completions("TestClass.")
+                if jedi_status and jedi_issue:
+                    continue
                 assert len(matches) > 2, jedi_status
                 matches = c.all_completions("TestClass.a")
                 assert matches == ['TestClass.a', 'TestClass.a1'], jedi_status
@@ -496,6 +507,7 @@ def test_completion_have_signature(self):
             "encoding" in c.signature
         ), "Signature of function was not found by completer"
 
+    @pytest.mark.xfail(jedi_issue, reason="Known failure on jedi<=0.18.0")
     def test_deduplicate_completions(self):
         """
         Test that completions are correctly deduplicated (even if ranges are not the same)
diff --git a/IPython/core/tests/test_magic_arguments.py b/IPython/core/tests/test_magic_arguments.py
--- a/IPython/core/tests/test_magic_arguments.py
+++ b/IPython/core/tests/test_magic_arguments.py
@@ -7,7 +7,7 @@
 #-----------------------------------------------------------------------------
 
 import argparse
-import pytest
+import sys
 
 from IPython.core.magic_arguments import (argument, argument_group, kwds,
     magic_arguments, parse_argstring, real_name)
@@ -74,9 +74,14 @@ 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__
-        == "::\n\n  %foo1 [-f FOO]\n\n A docstring.\n\noptional arguments:\n  -f FOO, --foo FOO  an argument\n"
+        == f"::\n\n  %foo1 [-f FOO]\n\n A docstring.\n\n{options}:\n  -f FOO, --foo FOO  an argument\n"
     )
     assert getattr(magic_foo1, "argcmd_name", None) == None
     assert real_name(magic_foo1) == "foo1"
@@ -91,7 +96,7 @@ def test_magic_arguments():
 
     assert (
         magic_foo3.__doc__
-        == "::\n\n  %foo3 [-f FOO] [-b BAR] [-z BAZ]\n\n A docstring.\n\noptional arguments:\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 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"
     )
     assert getattr(magic_foo3, "argcmd_name", None) == None
     assert real_name(magic_foo3) == "foo3"
@@ -100,7 +105,7 @@ def test_magic_arguments():
 
     assert (
         magic_foo4.__doc__
-        == "::\n\n  %foo4 [-f FOO]\n\n A docstring.\n\noptional arguments:\n  -f FOO, --foo FOO  an argument\n"
+        == f"::\n\n  %foo4 [-f FOO]\n\n A docstring.\n\n{options}:\n  -f FOO, --foo FOO  an argument\n"
     )
     assert getattr(magic_foo4, "argcmd_name", None) == None
     assert real_name(magic_foo4) == "foo4"
@@ -109,7 +114,7 @@ def test_magic_arguments():
 
     assert (
         magic_foo5.__doc__
-        == "::\n\n  %frobnicate [-f FOO]\n\n A docstring.\n\noptional arguments:\n  -f FOO, --foo FOO  an argument\n"
+        == f"::\n\n  %frobnicate [-f FOO]\n\n A docstring.\n\n{options}:\n  -f FOO, --foo FOO  an argument\n"
     )
     assert getattr(magic_foo5, "argcmd_name", None) == "frobnicate"
     assert real_name(magic_foo5) == "frobnicate"
@@ -118,7 +123,7 @@ def test_magic_arguments():
 
     assert (
         magic_magic_foo.__doc__
-        == "::\n\n  %magic_foo [-f FOO]\n\n A docstring.\n\noptional arguments:\n  -f FOO, --foo FOO  an argument\n"
+        == f"::\n\n  %magic_foo [-f FOO]\n\n A docstring.\n\n{options}:\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"
@@ -127,7 +132,7 @@ def test_magic_arguments():
 
     assert (
         foo.__doc__
-        == "::\n\n  %foo [-f FOO]\n\n A docstring.\n\noptional arguments:\n  -f FOO, --foo FOO  an argument\n"
+        == f"::\n\n  %foo [-f FOO]\n\n A docstring.\n\n{options}:\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
--- a/IPython/lib/tests/test_pretty.py
+++ b/IPython/lib/tests/test_pretty.py
@@ -7,14 +7,16 @@
 
 from collections import Counter, defaultdict, deque, OrderedDict
 import os
+import pytest
 import types
 import string
+import sys
 import unittest
 
 import pytest
 
 from IPython.lib import pretty
-from IPython.testing.decorators import skip_without, skip_iptest_but_not_pytest
+from IPython.testing.decorators import skip_iptest_but_not_pytest
 
 from io import StringIO
 
@@ -136,12 +138,12 @@ def test_sets(obj, expected_output):
     assert got_output == expected_output
 
 
-@skip_without('xxlimited')
 def test_pprint_heap_allocated_type():
     """
     Test that pprint works for heap allocated types.
     """
-    import xxlimited
+    module_name = "xxlimited" if sys.version_info < (3, 10) else "xxlimited_35"
+    xxlimited = pytest.importorskip(module_name)
     output = pretty.pretty(xxlimited.Null)
     assert output == "xxlimited.Null"