From 9e5f436a8b2a27bcc7802e443ea3ef3e41a82a75 Mon Sep 17 00:00:00 2001 From: robot-contrib Date: Wed, 18 May 2022 00:43:36 +0300 Subject: Update contrib/python/ipython/py3 to 8.3.0 ref:e84342d4d30476f9148137f37fd0c6405fd36f55 --- contrib/python/ipython/py3/.dist-info/METADATA | 94 ++- .../python/ipython/py3/.dist-info/entry_points.txt | 2 - contrib/python/ipython/py3/IPython/__init__.py | 54 +- contrib/python/ipython/py3/IPython/config.py | 19 - .../python/ipython/py3/IPython/core/application.py | 146 ++-- .../ipython/py3/IPython/core/async_helpers.py | 173 ++--- .../python/ipython/py3/IPython/core/autocall.py | 6 +- .../python/ipython/py3/IPython/core/compilerop.py | 24 +- .../python/ipython/py3/IPython/core/completer.py | 575 +++++++++----- .../ipython/py3/IPython/core/completerlib.py | 18 +- .../ipython/py3/IPython/core/crashhandler.py | 37 +- .../python/ipython/py3/IPython/core/debugger.py | 255 ++---- contrib/python/ipython/py3/IPython/core/display.py | 525 +++---------- .../ipython/py3/IPython/core/display_functions.py | 391 ++++++++++ .../python/ipython/py3/IPython/core/displayhook.py | 2 +- .../python/ipython/py3/IPython/core/displaypub.py | 6 +- contrib/python/ipython/py3/IPython/core/events.py | 38 +- .../python/ipython/py3/IPython/core/excolors.py | 18 - .../python/ipython/py3/IPython/core/extensions.py | 27 +- .../python/ipython/py3/IPython/core/formatters.py | 59 +- .../python/ipython/py3/IPython/core/getipython.py | 2 +- contrib/python/ipython/py3/IPython/core/history.py | 364 +++++---- .../python/ipython/py3/IPython/core/historyapp.py | 24 +- contrib/python/ipython/py3/IPython/core/hooks.py | 33 +- .../ipython/py3/IPython/core/inputsplitter.py | 52 +- .../ipython/py3/IPython/core/inputtransformer.py | 24 +- .../ipython/py3/IPython/core/inputtransformer2.py | 90 ++- .../ipython/py3/IPython/core/interactiveshell.py | 849 +++++++++----------- contrib/python/ipython/py3/IPython/core/magic.py | 95 ++- .../ipython/py3/IPython/core/magic_arguments.py | 32 + .../python/ipython/py3/IPython/core/magics/auto.py | 24 +- .../ipython/py3/IPython/core/magics/basic.py | 23 +- .../python/ipython/py3/IPython/core/magics/code.py | 61 +- .../ipython/py3/IPython/core/magics/config.py | 81 +- .../ipython/py3/IPython/core/magics/display.py | 21 +- .../ipython/py3/IPython/core/magics/execution.py | 148 ++-- .../ipython/py3/IPython/core/magics/extension.py | 2 +- .../ipython/py3/IPython/core/magics/history.py | 37 +- .../ipython/py3/IPython/core/magics/namespace.py | 39 +- .../python/ipython/py3/IPython/core/magics/osm.py | 97 ++- .../ipython/py3/IPython/core/magics/packaging.py | 36 +- .../ipython/py3/IPython/core/magics/pylab.py | 3 + .../ipython/py3/IPython/core/magics/script.py | 180 +++-- .../python/ipython/py3/IPython/core/oinspect.py | 106 ++- contrib/python/ipython/py3/IPython/core/page.py | 37 +- .../python/ipython/py3/IPython/core/payloadpage.py | 7 +- .../python/ipython/py3/IPython/core/prefilter.py | 13 +- .../python/ipython/py3/IPython/core/profiledir.py | 10 +- .../python/ipython/py3/IPython/core/pylabtools.py | 17 +- contrib/python/ipython/py3/IPython/core/release.py | 73 +- .../python/ipython/py3/IPython/core/shellapp.py | 18 - contrib/python/ipython/py3/IPython/core/ultratb.py | 817 +++++++------------- contrib/python/ipython/py3/IPython/display.py | 38 +- .../ipython/py3/IPython/extensions/autoreload.py | 197 +++-- .../ipython/py3/IPython/extensions/cythonmagic.py | 21 - .../ipython/py3/IPython/extensions/rmagic.py | 12 - .../ipython/py3/IPython/extensions/storemagic.py | 25 +- .../py3/IPython/extensions/sympyprinting.py | 32 - .../ipython/py3/IPython/external/__init__.py | 4 +- .../py3/IPython/external/decorators/__init__.py | 8 - .../py3/IPython/external/decorators/_decorators.py | 143 ---- .../decorators/_numpy_testing_noseclasses.py | 41 - .../python/ipython/py3/IPython/external/mathjax.py | 13 - .../ipython/py3/IPython/external/qt_for_kernel.py | 5 +- .../ipython/py3/IPython/external/qt_loaders.py | 54 +- contrib/python/ipython/py3/IPython/frontend.py | 29 - contrib/python/ipython/py3/IPython/html.py | 28 - .../python/ipython/py3/IPython/kernel/__init__.py | 35 - .../python/ipython/py3/IPython/kernel/__main__.py | 3 - .../python/ipython/py3/IPython/kernel/adapter.py | 1 - .../python/ipython/py3/IPython/kernel/channels.py | 1 - .../ipython/py3/IPython/kernel/channelsabc.py | 1 - .../python/ipython/py3/IPython/kernel/client.py | 1 - .../python/ipython/py3/IPython/kernel/clientabc.py | 1 - .../python/ipython/py3/IPython/kernel/connect.py | 2 - .../ipython/py3/IPython/kernel/kernelspec.py | 1 - .../ipython/py3/IPython/kernel/kernelspecapp.py | 1 - .../python/ipython/py3/IPython/kernel/launcher.py | 1 - .../python/ipython/py3/IPython/kernel/manager.py | 1 - .../ipython/py3/IPython/kernel/managerabc.py | 1 - .../py3/IPython/kernel/multikernelmanager.py | 1 - .../python/ipython/py3/IPython/kernel/restarter.py | 1 - .../python/ipython/py3/IPython/kernel/threaded.py | 1 - contrib/python/ipython/py3/IPython/lib/__init__.py | 10 - .../ipython/py3/IPython/lib/backgroundjobs.py | 4 +- .../python/ipython/py3/IPython/lib/clipboard.py | 16 +- .../python/ipython/py3/IPython/lib/deepreload.py | 83 +- contrib/python/ipython/py3/IPython/lib/demo.py | 9 +- contrib/python/ipython/py3/IPython/lib/display.py | 102 +-- .../python/ipython/py3/IPython/lib/inputhook.py | 666 ---------------- .../ipython/py3/IPython/lib/inputhookglut.py | 172 ----- .../python/ipython/py3/IPython/lib/inputhookgtk.py | 35 - .../ipython/py3/IPython/lib/inputhookgtk3.py | 34 - .../ipython/py3/IPython/lib/inputhookgtk4.py | 43 -- .../ipython/py3/IPython/lib/inputhookpyglet.py | 111 --- .../python/ipython/py3/IPython/lib/inputhookqt4.py | 180 ----- .../python/ipython/py3/IPython/lib/inputhookwx.py | 167 ---- contrib/python/ipython/py3/IPython/lib/kernel.py | 13 - .../python/ipython/py3/IPython/lib/latextools.py | 39 +- contrib/python/ipython/py3/IPython/lib/lexers.py | 14 +- contrib/python/ipython/py3/IPython/lib/pretty.py | 212 +++-- contrib/python/ipython/py3/IPython/lib/security.py | 114 --- contrib/python/ipython/py3/IPython/nbconvert.py | 19 - contrib/python/ipython/py3/IPython/nbformat.py | 19 - contrib/python/ipython/py3/IPython/parallel.py | 20 - contrib/python/ipython/py3/IPython/paths.py | 21 +- contrib/python/ipython/py3/IPython/qt.py | 24 - .../py3/IPython/sphinxext/custom_doctests.py | 4 +- .../py3/IPython/sphinxext/ipython_directive.py | 18 +- .../ipython/py3/IPython/terminal/debugger.py | 55 +- .../python/ipython/py3/IPython/terminal/embed.py | 91 ++- .../py3/IPython/terminal/interactiveshell.py | 297 ++++--- .../python/ipython/py3/IPython/terminal/ipapp.py | 42 +- .../python/ipython/py3/IPython/terminal/magics.py | 25 +- .../py3/IPython/terminal/pt_inputhooks/asyncio.py | 11 +- .../py3/IPython/terminal/pt_inputhooks/glut.py | 6 +- .../py3/IPython/terminal/pt_inputhooks/osx.py | 4 +- .../py3/IPython/terminal/pt_inputhooks/qt.py | 2 + .../python/ipython/py3/IPython/terminal/ptshell.py | 8 - .../python/ipython/py3/IPython/terminal/ptutils.py | 4 +- .../ipython/py3/IPython/terminal/shortcuts.py | 294 ++++++- .../python/ipython/py3/IPython/testing/__init__.py | 29 - .../python/ipython/py3/IPython/testing/__main__.py | 3 - .../ipython/py3/IPython/testing/decorators.py | 215 +----- .../ipython/py3/IPython/testing/globalipapp.py | 28 +- .../python/ipython/py3/IPython/testing/iptest.py | 460 ----------- .../py3/IPython/testing/iptestcontroller.py | 491 ------------ .../py3/IPython/testing/plugin/dtexample.py | 40 +- .../py3/IPython/testing/plugin/ipdoctest.py | 479 +----------- .../ipython/py3/IPython/testing/plugin/iptest.py | 18 - .../py3/IPython/testing/plugin/pytest_ipdoctest.py | 860 +++++++++++++++++++++ .../py3/IPython/testing/plugin/show_refs.py | 19 - .../ipython/py3/IPython/testing/plugin/simple.py | 23 +- .../py3/IPython/testing/plugin/test_exampleip.txt | 2 +- .../py3/IPython/testing/plugin/test_ipdoctest.py | 16 + .../py3/IPython/testing/plugin/test_refs.py | 7 - .../ipython/py3/IPython/testing/skipdoctest.py | 2 +- .../python/ipython/py3/IPython/testing/tools.py | 65 +- .../ipython/py3/IPython/utils/_process_cli.py | 9 - .../ipython/py3/IPython/utils/_process_common.py | 26 +- .../ipython/py3/IPython/utils/_process_posix.py | 19 +- .../ipython/py3/IPython/utils/_process_win32.py | 27 +- .../py3/IPython/utils/_process_win32_controller.py | 4 +- .../python/ipython/py3/IPython/utils/_sysinfo.py | 2 +- .../python/ipython/py3/IPython/utils/coloransi.py | 4 +- .../python/ipython/py3/IPython/utils/contexts.py | 14 - .../python/ipython/py3/IPython/utils/decorators.py | 2 +- .../python/ipython/py3/IPython/utils/encoding.py | 4 +- contrib/python/ipython/py3/IPython/utils/frame.py | 4 +- .../python/ipython/py3/IPython/utils/generics.py | 1 - .../ipython/py3/IPython/utils/importstring.py | 8 +- contrib/python/ipython/py3/IPython/utils/io.py | 111 +-- .../python/ipython/py3/IPython/utils/ipstruct.py | 28 +- .../ipython/py3/IPython/utils/module_paths.py | 13 +- contrib/python/ipython/py3/IPython/utils/openpy.py | 28 +- contrib/python/ipython/py3/IPython/utils/path.py | 72 +- .../python/ipython/py3/IPython/utils/pickleutil.py | 5 - .../python/ipython/py3/IPython/utils/py3compat.py | 142 +--- .../python/ipython/py3/IPython/utils/shimmodule.py | 47 +- .../python/ipython/py3/IPython/utils/sysinfo.py | 66 +- .../ipython/py3/IPython/utils/syspathcontext.py | 11 +- .../python/ipython/py3/IPython/utils/tempdir.py | 11 +- .../python/ipython/py3/IPython/utils/terminal.py | 2 +- contrib/python/ipython/py3/IPython/utils/text.py | 23 +- contrib/python/ipython/py3/IPython/utils/timing.py | 9 +- .../python/ipython/py3/IPython/utils/tokenutil.py | 19 +- .../python/ipython/py3/IPython/utils/version.py | 21 +- contrib/python/ipython/py3/README.rst | 33 +- .../python/ipython/py3/patches/01-arcadia.patch | 20 +- .../ipython/py3/patches/02-fix-ya.make.patch | 15 +- .../03-dissable-backgroud-highlighting.patch | 11 + contrib/python/ipython/py3/patches/04-fix.patch | 11 + 172 files changed, 5281 insertions(+), 7873 deletions(-) delete mode 100644 contrib/python/ipython/py3/IPython/config.py create mode 100644 contrib/python/ipython/py3/IPython/core/display_functions.py delete mode 100644 contrib/python/ipython/py3/IPython/extensions/cythonmagic.py delete mode 100644 contrib/python/ipython/py3/IPython/extensions/rmagic.py delete mode 100644 contrib/python/ipython/py3/IPython/extensions/sympyprinting.py delete mode 100644 contrib/python/ipython/py3/IPython/external/decorators/__init__.py delete mode 100644 contrib/python/ipython/py3/IPython/external/decorators/_decorators.py delete mode 100644 contrib/python/ipython/py3/IPython/external/decorators/_numpy_testing_noseclasses.py delete mode 100644 contrib/python/ipython/py3/IPython/external/mathjax.py delete mode 100644 contrib/python/ipython/py3/IPython/frontend.py delete mode 100644 contrib/python/ipython/py3/IPython/html.py delete mode 100644 contrib/python/ipython/py3/IPython/kernel/__init__.py delete mode 100644 contrib/python/ipython/py3/IPython/kernel/__main__.py delete mode 100644 contrib/python/ipython/py3/IPython/kernel/adapter.py delete mode 100644 contrib/python/ipython/py3/IPython/kernel/channels.py delete mode 100644 contrib/python/ipython/py3/IPython/kernel/channelsabc.py delete mode 100644 contrib/python/ipython/py3/IPython/kernel/client.py delete mode 100644 contrib/python/ipython/py3/IPython/kernel/clientabc.py delete mode 100644 contrib/python/ipython/py3/IPython/kernel/connect.py delete mode 100644 contrib/python/ipython/py3/IPython/kernel/kernelspec.py delete mode 100644 contrib/python/ipython/py3/IPython/kernel/kernelspecapp.py delete mode 100644 contrib/python/ipython/py3/IPython/kernel/launcher.py delete mode 100644 contrib/python/ipython/py3/IPython/kernel/manager.py delete mode 100644 contrib/python/ipython/py3/IPython/kernel/managerabc.py delete mode 100644 contrib/python/ipython/py3/IPython/kernel/multikernelmanager.py delete mode 100644 contrib/python/ipython/py3/IPython/kernel/restarter.py delete mode 100644 contrib/python/ipython/py3/IPython/kernel/threaded.py delete mode 100644 contrib/python/ipython/py3/IPython/lib/inputhook.py delete mode 100644 contrib/python/ipython/py3/IPython/lib/inputhookglut.py delete mode 100644 contrib/python/ipython/py3/IPython/lib/inputhookgtk.py delete mode 100644 contrib/python/ipython/py3/IPython/lib/inputhookgtk3.py delete mode 100644 contrib/python/ipython/py3/IPython/lib/inputhookgtk4.py delete mode 100644 contrib/python/ipython/py3/IPython/lib/inputhookpyglet.py delete mode 100644 contrib/python/ipython/py3/IPython/lib/inputhookqt4.py delete mode 100644 contrib/python/ipython/py3/IPython/lib/inputhookwx.py delete mode 100644 contrib/python/ipython/py3/IPython/lib/kernel.py delete mode 100644 contrib/python/ipython/py3/IPython/lib/security.py delete mode 100644 contrib/python/ipython/py3/IPython/nbconvert.py delete mode 100644 contrib/python/ipython/py3/IPython/nbformat.py delete mode 100644 contrib/python/ipython/py3/IPython/parallel.py delete mode 100644 contrib/python/ipython/py3/IPython/qt.py delete mode 100644 contrib/python/ipython/py3/IPython/terminal/ptshell.py delete mode 100644 contrib/python/ipython/py3/IPython/testing/__main__.py delete mode 100644 contrib/python/ipython/py3/IPython/testing/iptest.py delete mode 100644 contrib/python/ipython/py3/IPython/testing/iptestcontroller.py delete mode 100644 contrib/python/ipython/py3/IPython/testing/plugin/iptest.py create mode 100644 contrib/python/ipython/py3/IPython/testing/plugin/pytest_ipdoctest.py delete mode 100644 contrib/python/ipython/py3/IPython/testing/plugin/show_refs.py delete mode 100644 contrib/python/ipython/py3/IPython/utils/pickleutil.py create mode 100644 contrib/python/ipython/py3/patches/03-dissable-backgroud-highlighting.patch create mode 100644 contrib/python/ipython/py3/patches/04-fix.patch (limited to 'contrib/python/ipython') diff --git a/contrib/python/ipython/py3/.dist-info/METADATA b/contrib/python/ipython/py3/.dist-info/METADATA index 998a98b150d..b2118bad267 100644 --- a/contrib/python/ipython/py3/.dist-info/METADATA +++ b/contrib/python/ipython/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: ipython -Version: 7.33.0 +Version: 8.3.0 Summary: IPython: Productive Interactive Computing Home-page: https://ipython.org Author: The IPython Development Team @@ -15,6 +15,7 @@ Platform: Linux Platform: Mac OSX Platform: Windows Classifier: Framework :: IPython +Classifier: Framework :: Jupyter Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: BSD License @@ -22,34 +23,42 @@ Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Topic :: System :: Shells -Requires-Python: >=3.7 +Requires-Python: >=3.8 +Description-Content-Type: text/x-rst License-File: LICENSE -Requires-Dist: setuptools (>=18.5) -Requires-Dist: jedi (>=0.13) +Requires-Dist: backcall Requires-Dist: decorator +Requires-Dist: jedi (>=0.13) +Requires-Dist: matplotlib-inline Requires-Dist: pickleshare -Requires-Dist: traitlets (>=4.2) Requires-Dist: prompt-toolkit (!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0) -Requires-Dist: pygments -Requires-Dist: backcall -Requires-Dist: matplotlib-inline +Requires-Dist: pygments (>=2.4.0) +Requires-Dist: setuptools (>=18.5) +Requires-Dist: stack-data +Requires-Dist: traitlets (>=5) Requires-Dist: pexpect (>4.3) ; sys_platform != "win32" Requires-Dist: appnope ; sys_platform == "darwin" Requires-Dist: colorama ; sys_platform == "win32" Provides-Extra: all +Requires-Dist: black ; extra == 'all' Requires-Dist: Sphinx (>=1.3) ; extra == 'all' Requires-Dist: ipykernel ; extra == 'all' -Requires-Dist: ipyparallel ; extra == 'all' -Requires-Dist: ipywidgets ; extra == 'all' Requires-Dist: nbconvert ; extra == 'all' Requires-Dist: nbformat ; extra == 'all' -Requires-Dist: nose (>=0.10.1) ; extra == 'all' +Requires-Dist: ipywidgets ; extra == 'all' Requires-Dist: notebook ; extra == 'all' -Requires-Dist: numpy (>=1.17) ; extra == 'all' -Requires-Dist: pygments ; extra == 'all' +Requires-Dist: ipyparallel ; extra == 'all' Requires-Dist: qtconsole ; extra == 'all' -Requires-Dist: requests ; extra == 'all' +Requires-Dist: pytest (<7.1) ; extra == 'all' +Requires-Dist: pytest-asyncio ; extra == 'all' Requires-Dist: testpath ; extra == 'all' +Requires-Dist: curio ; extra == 'all' +Requires-Dist: matplotlib (!=3.2.0) ; extra == 'all' +Requires-Dist: numpy (>=1.19) ; extra == 'all' +Requires-Dist: pandas ; extra == 'all' +Requires-Dist: trio ; extra == 'all' +Provides-Extra: black +Requires-Dist: black ; extra == 'black' Provides-Extra: doc Requires-Dist: Sphinx (>=1.3) ; extra == 'doc' Provides-Extra: kernel @@ -59,57 +68,62 @@ Requires-Dist: nbconvert ; extra == 'nbconvert' Provides-Extra: nbformat Requires-Dist: nbformat ; extra == 'nbformat' Provides-Extra: notebook -Requires-Dist: notebook ; extra == 'notebook' Requires-Dist: ipywidgets ; extra == 'notebook' +Requires-Dist: notebook ; extra == 'notebook' Provides-Extra: parallel Requires-Dist: ipyparallel ; extra == 'parallel' Provides-Extra: qtconsole Requires-Dist: qtconsole ; extra == 'qtconsole' Provides-Extra: terminal Provides-Extra: test -Requires-Dist: nose (>=0.10.1) ; extra == 'test' -Requires-Dist: requests ; extra == 'test' +Requires-Dist: pytest (<7.1) ; extra == 'test' +Requires-Dist: pytest-asyncio ; extra == 'test' Requires-Dist: testpath ; extra == 'test' -Requires-Dist: pygments ; extra == 'test' -Requires-Dist: nbformat ; extra == 'test' -Requires-Dist: ipykernel ; extra == 'test' -Requires-Dist: numpy (>=1.17) ; extra == 'test' - +Provides-Extra: test_extra +Requires-Dist: pytest (<7.1) ; extra == 'test_extra' +Requires-Dist: pytest-asyncio ; extra == 'test_extra' +Requires-Dist: testpath ; extra == 'test_extra' +Requires-Dist: curio ; extra == 'test_extra' +Requires-Dist: matplotlib (!=3.2.0) ; extra == 'test_extra' +Requires-Dist: nbformat ; extra == 'test_extra' +Requires-Dist: numpy (>=1.19) ; extra == 'test_extra' +Requires-Dist: pandas ; extra == 'test_extra' +Requires-Dist: trio ; extra == 'test_extra' IPython provides a rich toolkit to help you make the most out of using Python interactively. Its main components are: -* A powerful interactive Python shell -* A `Jupyter `_ kernel to work with Python code in Jupyter - notebooks and other interactive frontends. + * A powerful interactive Python shell + * A `Jupyter `_ kernel to work with Python code in Jupyter + notebooks and other interactive frontends. The enhanced interactive Python shells have the following main features: -* Comprehensive object introspection. + * Comprehensive object introspection. -* Input history, persistent across sessions. + * Input history, persistent across sessions. -* Caching of output results during a session with automatically generated - references. + * Caching of output results during a session with automatically generated + references. -* Extensible tab completion, with support by default for completion of python - variables and keywords, filenames and function keywords. + * Extensible tab completion, with support by default for completion of python + variables and keywords, filenames and function keywords. -* Extensible system of 'magic' commands for controlling the environment and - performing many tasks related either to IPython or the operating system. + * Extensible system of 'magic' commands for controlling the environment and + performing many tasks related either to IPython or the operating system. -* A rich configuration system with easy switching between different setups - (simpler than changing $PYTHONSTARTUP environment variables every time). + * A rich configuration system with easy switching between different setups + (simpler than changing $PYTHONSTARTUP environment variables every time). -* Session logging and reloading. + * Session logging and reloading. -* Extensible syntax processing for special purpose situations. + * Extensible syntax processing for special purpose situations. -* Access to the system shell with user-extensible alias system. + * Access to the system shell with user-extensible alias system. -* Easily embeddable in other Python programs and GUIs. + * Easily embeddable in other Python programs and GUIs. -* Integrated access to the pdb debugger and the Python profiler. + * Integrated access to the pdb debugger and the Python profiler. The latest development version is always available from IPython's `GitHub site `_. diff --git a/contrib/python/ipython/py3/.dist-info/entry_points.txt b/contrib/python/ipython/py3/.dist-info/entry_points.txt index 30c576bd75c..3de4479bae1 100644 --- a/contrib/python/ipython/py3/.dist-info/entry_points.txt +++ b/contrib/python/ipython/py3/.dist-info/entry_points.txt @@ -1,6 +1,4 @@ [console_scripts] -iptest = IPython.testing.iptestcontroller:main -iptest3 = IPython.testing.iptestcontroller:main ipython = IPython:start_ipython ipython3 = IPython:start_ipython diff --git a/contrib/python/ipython/py3/IPython/__init__.py b/contrib/python/ipython/py3/IPython/__init__.py index c17ec76a602..7ebb80b3621 100644 --- a/contrib/python/ipython/py3/IPython/__init__.py +++ b/contrib/python/ipython/py3/IPython/__init__.py @@ -1,4 +1,3 @@ -# encoding: utf-8 """ IPython: tools for interactive and parallel computing in Python. @@ -27,24 +26,22 @@ import sys #----------------------------------------------------------------------------- # Don't forget to also update setup.py when this changes! -if sys.version_info < (3, 6): +if sys.version_info < (3, 8): raise ImportError( -""" -IPython 7.10+ supports Python 3.6 and above. + """ +IPython 8+ 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/master/README.rst -""") - -# Make it easy to import extensions - they are always directly on pythonpath. -# Therefore, non-IPython modules can be added to extensions directory. -# This should probably be in ipapp.py. -sys.path.append(os.path.join(os.path.dirname(__file__), "extensions")) +""" + ) #----------------------------------------------------------------------------- # Setup the top level names @@ -56,7 +53,6 @@ from .core.application import Application from .terminal.embed import embed from .core.interactiveshell import InteractiveShell -from .testing import test from .utils.sysinfo import sys_info from .utils.frame import extract_module_locals @@ -72,20 +68,19 @@ __patched_cves__ = {"CVE-2022-21699"} def embed_kernel(module=None, local_ns=None, **kwargs): """Embed and start an IPython kernel in a given scope. - + If you don't want the kernel to initialize the namespace from the scope of the surrounding function, and/or you want to load full IPython configuration, you probably want `IPython.start_kernel()` instead. - + Parameters ---------- module : types.ModuleType, optional The module to load into IPython globals (default: caller) local_ns : dict, optional The namespace to load into IPython user namespace (default: caller) - - kwargs : various, optional + **kwargs : various, optional Further keyword args are relayed to the IPKernelApp constructor, allowing configuration of the Kernel. Will only have an effect on the first embed_kernel call for a given process. @@ -103,26 +98,25 @@ def embed_kernel(module=None, local_ns=None, **kwargs): def start_ipython(argv=None, **kwargs): """Launch a normal IPython instance (as opposed to embedded) - + `IPython.embed()` puts a shell in a particular calling scope, such as a function or method for debugging purposes, which is often not desirable. - + `start_ipython()` does full, regular IPython initialization, including loading startup files, configuration, etc. much of which is skipped by `embed()`. - + This is a public API method, and will survive implementation changes. - + Parameters ---------- - argv : list or None, optional If unspecified or None, IPython will parse command-line options from sys.argv. To prevent any command-line parsing, pass an empty list: `argv=[]`. user_ns : dict, optional specify this dictionary to initialize the IPython user namespace with particular values. - kwargs : various, optional + **kwargs : various, optional Any other kwargs will be passed to the Application constructor, such as `config`. """ @@ -131,26 +125,32 @@ def start_ipython(argv=None, **kwargs): def start_kernel(argv=None, **kwargs): """Launch a normal IPython kernel instance (as opposed to embedded) - + `IPython.embed_kernel()` puts a shell in a particular calling scope, such as a function or method for debugging purposes, which is often not desirable. - + `start_kernel()` does full, regular IPython initialization, including loading startup files, configuration, etc. much of which is skipped by `embed()`. - + Parameters ---------- - argv : list or None, optional If unspecified or None, IPython will parse command-line options from sys.argv. To prevent any command-line parsing, pass an empty list: `argv=[]`. user_ns : dict, optional specify this dictionary to initialize the IPython user namespace with particular values. - kwargs : various, optional + **kwargs : various, optional Any other kwargs will be passed to the Application constructor, such as `config`. """ - from IPython.kernel.zmq.kernelapp import launch_new_instance + import warnings + + warnings.warn( + "start_kernel is deprecated since IPython 8.0, use from `ipykernel.kernelapp.launch_new_instance`", + DeprecationWarning, + stacklevel=2, + ) + from ipykernel.kernelapp import launch_new_instance return launch_new_instance(argv=argv, **kwargs) diff --git a/contrib/python/ipython/py3/IPython/config.py b/contrib/python/ipython/py3/IPython/config.py deleted file mode 100644 index 964f46f10ac..00000000000 --- a/contrib/python/ipython/py3/IPython/config.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -Shim to maintain backwards compatibility with old IPython.config imports. -""" -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import sys -from warnings import warn - -from .utils.shimmodule import ShimModule, ShimWarning - -warn("The `IPython.config` package has been deprecated since IPython 4.0. " - "You should import from traitlets.config instead.", ShimWarning) - - -# Unconditionally insert the shim into sys.modules so that further import calls -# trigger the custom attribute access above - -sys.modules['IPython.config'] = ShimModule(src='IPython.config', mirror='traitlets.config') diff --git a/contrib/python/ipython/py3/IPython/core/application.py b/contrib/python/ipython/py3/IPython/core/application.py index b319888b59b..0cdea5c69b8 100644 --- a/contrib/python/ipython/py3/IPython/core/application.py +++ b/contrib/python/ipython/py3/IPython/core/application.py @@ -20,6 +20,8 @@ import os import shutil import sys +from pathlib import Path + from traitlets.config.application import Application, catch_config_error from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader from IPython.core import release, crashhandler @@ -31,10 +33,10 @@ from traitlets import ( default, observe, ) -if os.name == 'nt': - programdata = os.environ.get('PROGRAMDATA', None) - if programdata: - SYSTEM_CONFIG_DIRS = [os.path.join(programdata, 'ipython')] +if os.name == "nt": + programdata = os.environ.get("PROGRAMDATA", None) + if programdata is not None: + SYSTEM_CONFIG_DIRS = [str(Path(programdata) / "ipython")] else: # PROGRAMDATA is not defined by default on XP. SYSTEM_CONFIG_DIRS = [] else: @@ -64,27 +66,49 @@ else: # aliases and flags -base_aliases = { - 'profile-dir' : 'ProfileDir.location', - 'profile' : 'BaseIPythonApplication.profile', - 'ipython-dir' : 'BaseIPythonApplication.ipython_dir', - 'log-level' : 'Application.log_level', - 'config' : 'BaseIPythonApplication.extra_config_file', -} - -base_flags = dict( - debug = ({'Application' : {'log_level' : logging.DEBUG}}, - "set log level to logging.DEBUG (maximize logging output)"), - quiet = ({'Application' : {'log_level' : logging.CRITICAL}}, - "set log level to logging.CRITICAL (minimize logging output)"), - init = ({'BaseIPythonApplication' : { - 'copy_config_files' : True, - 'auto_create' : True} - }, """Initialize profile with default config files. This is equivalent +base_aliases = {} +if isinstance(Application.aliases, dict): + # traitlets 5 + base_aliases.update(Application.aliases) +base_aliases.update( + { + "profile-dir": "ProfileDir.location", + "profile": "BaseIPythonApplication.profile", + "ipython-dir": "BaseIPythonApplication.ipython_dir", + "log-level": "Application.log_level", + "config": "BaseIPythonApplication.extra_config_file", + } +) + +base_flags = dict() +if isinstance(Application.flags, dict): + # traitlets 5 + base_flags.update(Application.flags) +base_flags.update( + dict( + debug=( + {"Application": {"log_level": logging.DEBUG}}, + "set log level to logging.DEBUG (maximize logging output)", + ), + quiet=( + {"Application": {"log_level": logging.CRITICAL}}, + "set log level to logging.CRITICAL (minimize logging output)", + ), + init=( + { + "BaseIPythonApplication": { + "copy_config_files": True, + "auto_create": True, + } + }, + """Initialize profile with default config files. This is equivalent to running `ipython profile create ` prior to startup. - """) + """, + ), + ) ) + class ProfileAwareConfigLoader(PyFileConfigLoader): """A Python file config loader that is aware of IPython profiles.""" def load_subconfig(self, fname, path=None, profile=None): @@ -161,6 +185,17 @@ class BaseIPythonApplication(Application): get_ipython_package_dir(), u'config', u'profile', change['new'] ) + add_ipython_dir_to_sys_path = Bool( + False, + """Should the IPython profile directory be added to sys path ? + + This option was non-existing before IPython 8.0, and ipython_dir was added to + sys path to allow import of extensions present there. This was historical + baggage from when pip did not exist. This now default to false, + but can be set to true for legacy reasons. + """, + ).tag(config=True) + ipython_dir = Unicode( help=""" The name of the IPython directory. This directory is used for logging @@ -232,16 +267,6 @@ class BaseIPythonApplication(Application): # Various stages of Application creation #------------------------------------------------------------------------- - deprecated_subcommands = {} - - def initialize_subcommand(self, subc, argv=None): - if subc in self.deprecated_subcommands: - self.log.warning("Subcommand `ipython {sub}` is deprecated and will be removed " - "in future versions.".format(sub=subc)) - self.log.warning("You likely want to use `jupyter {sub}` in the " - "future".format(sub=subc)) - return super(BaseIPythonApplication, self).initialize_subcommand(subc, argv) - def init_crash_handler(self): """Create a crash handler, typically setting sys.excepthook to it.""" self.crash_handler = self.crash_handler_class(self) @@ -252,7 +277,7 @@ class BaseIPythonApplication(Application): def excepthook(self, etype, evalue, tb): """this is sys.excepthook after init_crashhandler - + set self.verbose_crash=True to use our full crashhandler, instead of a regular traceback with a short message (crash_handler_lite) """ @@ -270,21 +295,24 @@ class BaseIPythonApplication(Application): str_old = os.path.abspath(old) if str_old in sys.path: sys.path.remove(str_old) - str_path = os.path.abspath(new) - sys.path.append(str_path) - ensure_dir_exists(new) - readme = os.path.join(new, 'README') - readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README') - if not os.path.exists(readme) and os.path.exists(readme_src): - shutil.copy(readme_src, readme) - for d in ('extensions', 'nbextensions'): - path = os.path.join(new, d) - try: - ensure_dir_exists(path) - except OSError as e: - # this will not be EEXIST - self.log.error("couldn't create path %s: %s", path, e) - self.log.debug("IPYTHONDIR set to: %s" % new) + if self.add_ipython_dir_to_sys_path: + str_path = os.path.abspath(new) + sys.path.append(str_path) + ensure_dir_exists(new) + readme = os.path.join(new, "README") + readme_src = os.path.join( + get_ipython_package_dir(), "config", "profile", "README" + ) + if not os.path.exists(readme) and os.path.exists(readme_src): + shutil.copy(readme_src, readme) + for d in ("extensions", "nbextensions"): + path = os.path.join(new, d) + try: + ensure_dir_exists(path) + except OSError as e: + # this will not be EEXIST + self.log.error("couldn't create path %s: %s", path, e) + self.log.debug("IPYTHONDIR set to: %s" % new) def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS): """Load the config file. @@ -409,14 +437,15 @@ class BaseIPythonApplication(Application): self.config_file_paths.extend(ENV_CONFIG_DIRS) self.config_file_paths.extend(SYSTEM_CONFIG_DIRS) # copy config files - path = self.builtin_profile_dir + path = Path(self.builtin_profile_dir) if self.copy_config_files: src = self.profile cfg = self.config_file_name - if path and os.path.exists(os.path.join(path, cfg)): - self.log.warning("Staging %r from %s into %r [overwrite=%s]"%( - cfg, src, self.profile_dir.location, self.overwrite) + if path and (path / cfg).exists(): + self.log.warning( + "Staging %r from %s into %r [overwrite=%s]" + % (cfg, src, self.profile_dir.location, self.overwrite) ) self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite) else: @@ -425,9 +454,9 @@ class BaseIPythonApplication(Application): # Still stage *bundled* config files, but not generated ones # This is necessary for `ipython profile=sympy` to load the profile # on the first go - files = glob.glob(os.path.join(path, '*.py')) + files = path.glob("*.py") for fullpath in files: - cfg = os.path.basename(fullpath) + cfg = fullpath.name if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False): # file was copied self.log.warning("Staging bundled %s from %s into %r"%( @@ -438,11 +467,10 @@ class BaseIPythonApplication(Application): def stage_default_config_file(self): """auto generate default config file, and stage it into the profile.""" s = self.generate_config_file() - fname = os.path.join(self.profile_dir.location, self.config_file_name) - if self.overwrite or not os.path.exists(fname): - self.log.warning("Generating default config file: %r"%(fname)) - with open(fname, 'w') as f: - f.write(s) + config_file = Path(self.profile_dir.location) / self.config_file_name + if self.overwrite or not config_file.exists(): + self.log.warning("Generating default config file: %r" % (config_file)) + config_file.write_text(s, encoding="utf-8") @catch_config_error def initialize(self, argv=None): diff --git a/contrib/python/ipython/py3/IPython/core/async_helpers.py b/contrib/python/ipython/py3/IPython/core/async_helpers.py index fca78def85a..0e7db0bb54d 100644 --- a/contrib/python/ipython/py3/IPython/core/async_helpers.py +++ b/contrib/python/ipython/py3/IPython/core/async_helpers.py @@ -12,37 +12,89 @@ Python semantics. import ast -import sys import asyncio import inspect -from textwrap import dedent, indent +from functools import wraps +_asyncio_event_loop = None -class _AsyncIORunner: - def __init__(self): - self._loop = None - - @property - def loop(self): - """Always returns a non-closed event loop""" - if self._loop is None or self._loop.is_closed(): - policy = asyncio.get_event_loop_policy() - self._loop = policy.new_event_loop() - policy.set_event_loop(self._loop) - return self._loop +def get_asyncio_loop(): + """asyncio has deprecated get_event_loop + + Replicate it here, with our desired semantics: + + - always returns a valid, not-closed loop + - not thread-local like asyncio's, + because we only want one loop for IPython + - if called from inside a coroutine (e.g. in ipykernel), + return the running loop + + .. versionadded:: 8.0 + """ + try: + return asyncio.get_running_loop() + except RuntimeError: + # not inside a coroutine, + # track our own global + pass + + # not thread-local like asyncio's, + # because we only track one event loop to run for IPython itself, + # always in the main thread. + global _asyncio_event_loop + if _asyncio_event_loop is None or _asyncio_event_loop.is_closed(): + _asyncio_event_loop = asyncio.new_event_loop() + return _asyncio_event_loop + + +class _AsyncIORunner: def __call__(self, coro): """ Handler for asyncio autoawait """ - return self.loop.run_until_complete(coro) + return get_asyncio_loop().run_until_complete(coro) def __str__(self): - return 'asyncio' + return "asyncio" + _asyncio_runner = _AsyncIORunner() +class _AsyncIOProxy: + """Proxy-object for an asyncio + + Any coroutine methods will be wrapped in event_loop.run_ + """ + + def __init__(self, obj, event_loop): + self._obj = obj + self._event_loop = event_loop + + def __repr__(self): + return f"<_AsyncIOProxy({self._obj!r})>" + + def __getattr__(self, key): + attr = getattr(self._obj, key) + if inspect.iscoroutinefunction(attr): + # if it's a coroutine method, + # return a threadsafe wrapper onto the _current_ asyncio loop + @wraps(attr) + def _wrapped(*args, **kwargs): + concurrent_future = asyncio.run_coroutine_threadsafe( + attr(*args, **kwargs), self._event_loop + ) + return asyncio.wrap_future(concurrent_future) + + return _wrapped + else: + return attr + + def __dir__(self): + return dir(self._obj) + + def _curio_runner(coroutine): """ handler for curio autoawait @@ -72,7 +124,6 @@ def _pseudo_sync_runner(coro): See discussion in https://github.com/python-trio/trio/issues/608, Credit to Nathaniel Smith - """ try: coro.send(None) @@ -85,69 +136,6 @@ def _pseudo_sync_runner(coro): ) -def _asyncify(code: str) -> str: - """wrap code in async def definition. - - And setup a bit of context to run it later. - """ - res = dedent( - """ - async def __wrapper__(): - try: - {usercode} - finally: - locals() - """ - ).format(usercode=indent(code, " " * 8)) - return res - - -class _AsyncSyntaxErrorVisitor(ast.NodeVisitor): - """ - Find syntax errors that would be an error in an async repl, but because - the implementation involves wrapping the repl in an async function, it - is erroneously allowed (e.g. yield or return at the top level) - """ - def __init__(self): - if sys.version_info >= (3,8): - raise ValueError('DEPRECATED in Python 3.8+') - self.depth = 0 - super().__init__() - - def generic_visit(self, node): - func_types = (ast.FunctionDef, ast.AsyncFunctionDef) - invalid_types_by_depth = { - 0: (ast.Return, ast.Yield, ast.YieldFrom), - 1: (ast.Nonlocal,) - } - - should_traverse = self.depth < max(invalid_types_by_depth.keys()) - if isinstance(node, func_types) and should_traverse: - self.depth += 1 - super().generic_visit(node) - self.depth -= 1 - elif isinstance(node, invalid_types_by_depth[self.depth]): - raise SyntaxError() - else: - super().generic_visit(node) - - -def _async_parse_cell(cell: str) -> ast.AST: - """ - This is a compatibility shim for pre-3.7 when async outside of a function - is a syntax error at the parse stage. - - It will return an abstract syntax tree parsed as if async and await outside - of a function were not a syntax error. - """ - if sys.version_info < (3, 7): - # Prior to 3.7 you need to asyncify before parse - wrapped_parse_tree = ast.parse(_asyncify(cell)) - return wrapped_parse_tree.body[0].body[0] - else: - return ast.parse(cell) - - def _should_be_async(cell: str) -> bool: """Detect if a block of code need to be wrapped in an `async def` @@ -159,25 +147,10 @@ def _should_be_async(cell: str) -> bool: Not handled yet: If the block of code has a return statement as the top level, it will be seen as async. This is a know limitation. """ - if sys.version_info > (3, 8): - try: - code = compile(cell, "<>", "exec", flags=getattr(ast,'PyCF_ALLOW_TOP_LEVEL_AWAIT', 0x0)) - return inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE - except (SyntaxError, MemoryError): - return False try: - # we can't limit ourself to ast.parse, as it __accepts__ to parse on - # 3.7+, but just does not _compile_ - code = compile(cell, "<>", "exec") + code = compile( + cell, "<>", "exec", flags=getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x0) + ) + return inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE except (SyntaxError, MemoryError): - try: - parse_tree = _async_parse_cell(cell) - - # Raise a SyntaxError if there are top-level return or yields - v = _AsyncSyntaxErrorVisitor() - v.visit(parse_tree) - - except (SyntaxError, MemoryError): - return False - return True - return False + return False diff --git a/contrib/python/ipython/py3/IPython/core/autocall.py b/contrib/python/ipython/py3/IPython/core/autocall.py index bab7f859c96..54beec3f58d 100644 --- a/contrib/python/ipython/py3/IPython/core/autocall.py +++ b/contrib/python/ipython/py3/IPython/core/autocall.py @@ -40,10 +40,10 @@ class IPyAutocall(object): self._ip = ip def set_ip(self, ip): - """ Will be used to set _ip point to current ipython instance b/f call - + """Will be used to set _ip point to current ipython instance b/f call + Override this method if you don't want this to happen. - + """ self._ip = ip diff --git a/contrib/python/ipython/py3/IPython/core/compilerop.py b/contrib/python/ipython/py3/IPython/core/compilerop.py index 50672a19541..b43e570b3ad 100644 --- a/contrib/python/ipython/py3/IPython/core/compilerop.py +++ b/contrib/python/ipython/py3/IPython/core/compilerop.py @@ -92,6 +92,10 @@ class CachingCompiler(codeop.Compile): # (otherwise we'd lose our tracebacks). linecache.checkcache = check_linecache_ipython + # Caching a dictionary { filename: execution_count } for nicely + # rendered tracebacks. The filename corresponds to the filename + # argument used for the builtins.compile function. + self._filename_map = {} def ast_parse(self, source, filename='', symbol='exec'): """Parse code to an AST with the current compiler flags active. @@ -118,12 +122,12 @@ class CachingCompiler(codeop.Compile): Parameters ---------- raw_code : str - The raw cell code. + The raw cell code. transformed_code : str - The executable Python source code to cache and compile. + The executable Python source code to cache and compile. number : int - A number which forms part of the code's name. Used for the execution - counter. + A number which forms part of the code's name. Used for the execution + counter. Returns ------- @@ -137,12 +141,12 @@ class CachingCompiler(codeop.Compile): Parameters ---------- transformed_code : str - The executable Python source code to cache and compile. + The executable Python source code to cache and compile. number : int - A number which forms part of the code's name. Used for the execution - counter. + A number which forms part of the code's name. Used for the execution + counter. raw_code : str - The raw code before transformation, if None, set to `transformed_code`. + The raw code before transformation, if None, set to `transformed_code`. Returns ------- @@ -153,6 +157,10 @@ class CachingCompiler(codeop.Compile): raw_code = transformed_code name = self.get_code_name(raw_code, transformed_code, number) + + # Save the execution count + self._filename_map[name] = number + entry = ( len(transformed_code), time.time(), diff --git a/contrib/python/ipython/py3/IPython/core/completer.py b/contrib/python/ipython/py3/IPython/core/completer.py index 6c6fa7e7e54..fcc9d20d596 100644 --- a/contrib/python/ipython/py3/IPython/core/completer.py +++ b/contrib/python/ipython/py3/IPython/core/completer.py @@ -35,7 +35,7 @@ or using unicode completion: .. code:: - \\greek small letter alpha + \\GREEK SMALL LETTER ALPHA α @@ -50,7 +50,7 @@ Backward latex completion It is sometime challenging to know how to type a character, if you are using IPython, or any compatible frontend you can prepend backslash to the character -and press `` to expand it to its latex form. +and press ```` to expand it to its latex form. .. code:: @@ -121,26 +121,29 @@ import string import sys import time import unicodedata +import uuid import warnings from contextlib import contextmanager from importlib import import_module from types import SimpleNamespace -from typing import Iterable, Iterator, List, Tuple +from typing import Iterable, Iterator, List, Tuple, Union, Any, Sequence, Dict, NamedTuple, Pattern, Optional from IPython.core.error import TryNext from IPython.core.inputtransformer2 import ESC_MAGIC from IPython.core.latex_symbols import latex_symbols, reverse_latex_symbol from IPython.core.oinspect import InspectColors +from IPython.testing.skipdoctest import skip_doctest from IPython.utils import generics from IPython.utils.dir2 import dir2, get_real_method +from IPython.utils.path import ensure_dir_exists from IPython.utils.process import arg_split -from traitlets import Bool, Enum, Int, observe +from traitlets import Bool, Enum, Int, List as ListTrait, Unicode, default, observe from traitlets.config.configurable import Configurable import __main__ # skip module docstests -skip_doctest = True +__skip_doctest__ = True try: import jedi @@ -154,6 +157,14 @@ except ImportError: # Globals #----------------------------------------------------------------------------- +# ranges where we have most of the valid unicode names. We could be more finer +# grained but is it worth it for performance While unicode have character in the +# range 0, 0x110000, we seem to have name for about 10% of those. (131808 as I +# write this). With below range we cover them all, with a density of ~67% +# biggest next gap we consider only adds up about 1% density and there are 600 +# gaps that would need hard coding. +_UNICODE_RANGES = [(32, 0x3134b), (0xe0001, 0xe01f0)] + # Public API __all__ = ['Completer','IPCompleter'] @@ -167,14 +178,6 @@ else: MATCHES_LIMIT = 500 -class Sentinel: - def __repr__(self): - return "" - - -_deprecation_readline_sentinel = Sentinel() - - class ProvisionalCompleterWarning(FutureWarning): """ Exception raise by an experimental feature in this module. @@ -186,11 +189,11 @@ class ProvisionalCompleterWarning(FutureWarning): warnings.filterwarnings('error', category=ProvisionalCompleterWarning) + +@skip_doctest @contextmanager def provisionalcompleter(action='ignore'): """ - - This context manager has to be used in any place where unstable completer behavior and API may be called. @@ -260,17 +263,17 @@ def expand_user(path:str) -> Tuple[str, bool, str]: Parameters ---------- path : str - String to be expanded. If no ~ is present, the output is the same as the - input. + String to be expanded. If no ~ is present, the output is the same as the + input. Returns ------- newpath : str - Result of ~ expansion in the input path. + Result of ~ expansion in the input path. tilde_expand : bool - Whether any expansion was performed or not. + Whether any expansion was performed or not. tilde_val : str - The value that ~ was replaced with. + The value that ~ was replaced with. """ # Default values tilde_expand = False @@ -429,20 +432,17 @@ def _deduplicate_completions(text: str, completions: _IC)-> _IC: Parameters ---------- - text: str + text : str text that should be completed. - completions: Iterator[Completion] + completions : Iterator[Completion] iterator over the completions to deduplicate Yields ------ `Completions` objects - - Completions coming from multiple sources, may be different but end up having the same effect when applied to ``text``. If this is the case, this will consider completions as equal and only emit the first encountered. - Not folded in `completions()` yet for debugging purpose, and to detect when the IPython completer does return things that Jedi does not, but should be at some point. @@ -462,7 +462,7 @@ def _deduplicate_completions(text: str, completions: _IC)-> _IC: seen.add(new_text) -def rectify_completions(text: str, completions: _IC, *, _debug=False)->_IC: +def rectify_completions(text: str, completions: _IC, *, _debug: bool = False) -> _IC: """ Rectify a set of completions to all have the same ``start`` and ``end`` @@ -475,12 +475,15 @@ def rectify_completions(text: str, completions: _IC, *, _debug=False)->_IC: Parameters ---------- - text: str + text : str text that should be completed. - completions: Iterator[Completion] + completions : Iterator[Completion] iterator over the completions to rectify + _debug : bool + Log failed completion - + Notes + ----- :any:`jedi.api.classes.Completion` s returned by Jedi may not have the same start and end, though the Jupyter Protocol requires them to behave like so. This will readjust the completion to have the same ``start`` and ``end`` by padding both @@ -582,11 +585,11 @@ class Completer(Configurable): greedy = Bool(False, help="""Activate greedy completion - PENDING DEPRECTION. this is now mostly taken care of with Jedi. + PENDING DEPRECATION. this is now mostly taken care of with Jedi. This will enable completion on elements of lists, results of function calls, etc., but can be unsafe because the code is actually evaluated on TAB. - """ + """, ).tag(config=True) use_jedi = Bool(default_value=JEDI_INSTALLED, @@ -609,8 +612,6 @@ class Completer(Configurable): "Includes completion of latex commands, unicode names, and expanding " "unicode characters back to latex commands.").tag(config=True) - - def __init__(self, namespace=None, global_namespace=None, **kwargs): """Create a new completer for the command line. @@ -757,44 +758,77 @@ def get__all__entries(obj): return [w for w in words if isinstance(w, str)] -def match_dict_keys(keys: List[str], prefix: str, delims: str): +def match_dict_keys(keys: List[Union[str, bytes, Tuple[Union[str, bytes]]]], prefix: str, delims: str, + extra_prefix: Optional[Tuple[str, bytes]]=None) -> Tuple[str, int, List[str]]: """Used by dict_key_matches, matching the prefix to a list of keys Parameters - ========== - keys: + ---------- + keys list of keys in dictionary currently being completed. - prefix: - Part of the text already typed by the user. e.g. `mydict[b'fo` - delims: + prefix + Part of the text already typed by the user. E.g. `mydict[b'fo` + delims String of delimiters to consider when finding the current key. + extra_prefix : optional + Part of the text already typed in multi-key index cases. E.g. for + `mydict['foo', "bar", 'b`, this would be `('foo', 'bar')`. Returns - ======= - + ------- A tuple of three elements: ``quote``, ``token_start``, ``matched``, with ``quote`` being the quote that need to be used to close current string. ``token_start`` the position where the replacement should start occurring, ``matches`` a list of replacement/completion """ + prefix_tuple = extra_prefix if extra_prefix else () + Nprefix = len(prefix_tuple) + def filter_prefix_tuple(key): + # Reject too short keys + if len(key) <= Nprefix: + return False + # Reject keys with non str/bytes in it + for k in key: + if not isinstance(k, (str, bytes)): + return False + # Reject keys that do not match the prefix + for k, pt in zip(key, prefix_tuple): + if k != pt: + return False + # All checks passed! + return True + + filtered_keys:List[Union[str,bytes]] = [] + def _add_to_filtered_keys(key): + if isinstance(key, (str, bytes)): + filtered_keys.append(key) + + for k in keys: + if isinstance(k, tuple): + if filter_prefix_tuple(k): + _add_to_filtered_keys(k[Nprefix]) + else: + _add_to_filtered_keys(k) + if not prefix: - return None, 0, [repr(k) for k in keys - if isinstance(k, (str, bytes))] + return '', 0, [repr(k) for k in filtered_keys] quote_match = re.search('["\']', prefix) + assert quote_match is not None # silence mypy quote = quote_match.group() try: prefix_str = eval(prefix + quote, {}) except Exception: - return None, 0, [] + return '', 0, [] pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$' token_match = re.search(pattern, prefix, re.UNICODE) + assert token_match is not None # silence mypy token_start = token_match.start() token_prefix = token_match.group() - matched = [] - for key in keys: + matched:List[str] = [] + for key in filtered_keys: try: if not key.startswith(prefix_str): continue @@ -806,14 +840,6 @@ def match_dict_keys(keys: List[str], prefix: str, delims: str): rem = key[len(prefix_str):] # force repr wrapped in ' rem_repr = repr(rem + '"') if isinstance(rem, str) else repr(rem + b'"') - if rem_repr.startswith('u') and prefix[0] not in 'uU': - # Found key is unicode, but prefix is Py2 string. - # Therefore attempt to interpret key as string. - try: - rem_repr = repr(rem.encode('ascii') + '"') - except UnicodeEncodeError: - continue - rem_repr = rem_repr[1 + rem_repr.index("'"):-2] if quote == '"': # The entered prefix is quoted with ", @@ -828,13 +854,11 @@ def match_dict_keys(keys: List[str], prefix: str, delims: str): def cursor_to_position(text:str, line:int, column:int)->int: """ - Convert the (line,column) position of the cursor in text to an offset in a string. Parameters ---------- - text : str The text in which to calculate the cursor offset line : int @@ -842,13 +866,13 @@ def cursor_to_position(text:str, line:int, column:int)->int: column : int Column of the cursor 0-indexed - Return - ------ - Position of the cursor in ``text``, 0-indexed. + Returns + ------- + Position of the cursor in ``text``, 0-indexed. See Also -------- - position_to_cursor: reciprocal of this function + position_to_cursor : reciprocal of this function """ lines = text.split('\n') @@ -865,23 +889,20 @@ def position_to_cursor(text:str, offset:int)->Tuple[int, int]: Parameters ---------- - text : str The text in which to calculate the cursor offset offset : int Position of the cursor in ``text``, 0-indexed. - Return - ------ + Returns + ------- (line, column) : (int, int) Line of the cursor; 0-indexed, column of the cursor 0-indexed - See Also -------- cursor_to_position : reciprocal of this function - """ assert 0 <= offset <= len(text) , "0 <= %s <= %s" % (offset , len(text)) @@ -899,9 +920,8 @@ def _safe_isinstance(obj, module, class_name): return (module in sys.modules and isinstance(obj, getattr(import_module(module), class_name))) - -def back_unicode_name_matches(text): - u"""Match unicode characters back to unicode name +def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]: + """Match Unicode characters back to Unicode name This does ``☃`` -> ``\\snowman`` @@ -910,52 +930,60 @@ def back_unicode_name_matches(text): This will not either back-complete standard sequences like \\n, \\b ... - Used on Python 3 only. + Returns + ======= + + Return a tuple with two elements: + + - The Unicode character that was matched (preceded with a backslash), or + empty string, + - a sequence (of 1), name for the match Unicode character, preceded by + backslash, or empty if no match. + """ if len(text)<2: - return u'', () + return '', () maybe_slash = text[-2] if maybe_slash != '\\': - return u'', () + return '', () char = text[-1] # no expand on quote for completion in strings. # nor backcomplete standard ascii keys - if char in string.ascii_letters or char in ['"',"'"]: - return u'', () + if char in string.ascii_letters or char in ('"',"'"): + return '', () try : unic = unicodedata.name(char) - return '\\'+char,['\\'+unic] + return '\\'+char,('\\'+unic,) except KeyError: pass - return u'', () + return '', () -def back_latex_name_matches(text:str): +def back_latex_name_matches(text:str) -> Tuple[str, Sequence[str]] : """Match latex characters back to unicode name This does ``\\ℵ`` -> ``\\aleph`` - Used on Python 3 only. """ if len(text)<2: - return u'', () + return '', () maybe_slash = text[-2] if maybe_slash != '\\': - return u'', () + return '', () char = text[-1] # no expand on quote for completion in strings. # nor backcomplete standard ascii keys - if char in string.ascii_letters or char in ['"',"'"]: - return u'', () + if char in string.ascii_letters or char in ('"',"'"): + return '', () try : latex = reverse_latex_symbol[char] # '\\' replace the \ as well return '\\'+char,[latex] except KeyError: pass - return u'', () + return '', () def _formatparamchildren(parameter) -> str: @@ -964,18 +992,15 @@ def _formatparamchildren(parameter) -> str: Jedi does not expose a simple way to get `param=value` from its API. - Parameter - ========= - - parameter: + Parameters + ---------- + parameter Jedi's function `Param` Returns - ======= - + ------- A string like 'a', 'b=1', '*args', '**kwargs' - """ description = parameter.description if not description.startswith('param '): @@ -987,15 +1012,13 @@ def _make_signature(completion)-> str: """ Make the signature from a jedi completion - Parameter - ========= - - completion: jedi.Completion + Parameters + ---------- + completion : jedi.Completion object does not complete a function type Returns - ======= - + ------- a string consisting of the function signature, with the parenthesis but without the function name. example: `(a, *args, b=1, **kwargs)` @@ -1015,10 +1038,18 @@ def _make_signature(completion)-> str: return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for signature in completion.get_signatures() for p in signature.defined_names()) if f]) + +class _CompleteResult(NamedTuple): + matched_text : str + matches: Sequence[str] + matches_origin: Sequence[str] + jedi_matches: Any + + class IPCompleter(Completer): """Extension of the completer class with IPython-specific features""" - _names = None + __dict_key_regexps: Optional[Dict[bool,Pattern]] = None @observe('greedy') def _greedy_changed(self, change): @@ -1064,6 +1095,16 @@ class IPCompleter(Completer): """, ).tag(config=True) + profile_completions = Bool( + default_value=False, + help="If True, emit profiling data for completion subsystem using cProfile." + ).tag(config=True) + + profiler_output_dir = Unicode( + default_value=".completion_profiles", + help="Template for path at which to output profile data for completions." + ).tag(config=True) + @observe('limit_to__all__') def _limit_to_all_changed(self, change): warnings.warn('`IPython.core.IPCompleter.limit_to__all__` configuration ' @@ -1071,42 +1112,41 @@ class IPCompleter(Completer): 'no effects and then removed in future version of IPython.', UserWarning) - def __init__(self, shell=None, namespace=None, global_namespace=None, - use_readline=_deprecation_readline_sentinel, config=None, **kwargs): + def __init__( + self, shell=None, namespace=None, global_namespace=None, config=None, **kwargs + ): """IPCompleter() -> completer Return a completer object. Parameters ---------- - shell a pointer to the ipython shell itself. This is needed because this completer knows about magic functions, and those can only be accessed via the ipython instance. - namespace : dict, optional an optional dict where completions are performed. - global_namespace : dict, optional secondary optional dict for completions, to handle cases (such as IPython embedded inside functions) where both Python scopes are visible. - - use_readline : bool, optional - DEPRECATED, ignored since IPython 6.0, will have no effects + config : Config + traitlet's config object + **kwargs + passed to super class unmodified. """ self.magic_escape = ESC_MAGIC self.splitter = CompletionSplitter() - if use_readline is not _deprecation_readline_sentinel: - warnings.warn('The `use_readline` parameter is deprecated and ignored since IPython 6.0.', - DeprecationWarning, stacklevel=2) - # _greedy_changed() depends on splitter and readline being defined: - Completer.__init__(self, namespace=namespace, global_namespace=global_namespace, - config=config, **kwargs) + super().__init__( + namespace=namespace, + global_namespace=global_namespace, + config=config, + **kwargs + ) # List where completion matches will be stored self.matches = [] @@ -1141,8 +1181,14 @@ class IPCompleter(Completer): # This is set externally by InteractiveShell self.custom_completers = None + # This is a list of names of unicode characters that can be completed + # into their corresponding unicode value. The list is large, so we + # lazily initialize it on first use. Consuming code should access this + # attribute through the `@unicode_names` property. + self._unicode_names = None + @property - def matchers(self): + def matchers(self) -> List[Any]: """All active matcher routines for completion""" if self.dict_keys_only: return [self.dict_key_matches] @@ -1164,7 +1210,7 @@ class IPCompleter(Completer): self.python_func_kw_matches, ] - def all_completions(self, text) -> List[str]: + def all_completions(self, text:str) -> List[str]: """ Wrapper around the completion methods for the benefit of emacs. """ @@ -1175,14 +1221,14 @@ class IPCompleter(Completer): return self.complete(text)[1] - def _clean_glob(self, text): + def _clean_glob(self, text:str): return self.glob("%s*" % text) - def _clean_glob_win32(self,text): + def _clean_glob_win32(self, text:str): return [f.replace("\\","/") for f in self.glob("%s*" % text)] - def file_matches(self, text): + def file_matches(self, text:str)->List[str]: """Match filenames, expanding ~USER type strings. Most of the seemingly convoluted logic in this completer is an @@ -1264,7 +1310,7 @@ class IPCompleter(Completer): # Mark directories in input list by appending '/' to their names. return [x+'/' if os.path.isdir(x) else x for x in matches] - def magic_matches(self, text): + def magic_matches(self, text:str): """Match magics""" # Get all shell magics now rather than statically, so magics loaded at # runtime show up too. @@ -1355,9 +1401,8 @@ class IPCompleter(Completer): if color.startswith(prefix) ] return [] - def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str): + def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str) -> Iterable[Any]: """ - Return a list of :any:`jedi.api.Completions` object from a ``text`` and cursor position. @@ -1370,9 +1415,8 @@ class IPCompleter(Completer): text : str text to complete - Debugging - --------- - + Notes + ----- If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion` object containing a string with the Jedi debug information attached. """ @@ -1429,7 +1473,7 @@ class IPCompleter(Completer): else: return [] - def python_matches(self, text): + def python_matches(self, text:str)->List[str]: """Match attributes or global python names""" if "." in text: try: @@ -1511,7 +1555,7 @@ class IPCompleter(Completer): return list(set(ret)) - def python_func_kw_matches(self,text): + def python_func_kw_matches(self, text): """Match named parameters (kwargs) of the last open function""" if "." in text: # a parameter cannot be dotted @@ -1581,42 +1625,54 @@ class IPCompleter(Completer): # Remove used named arguments from the list, no need to show twice for namedArg in set(namedArgs) - usedNamedArgs: if namedArg.startswith(text): - argMatches.append(u"%s=" %namedArg) + argMatches.append("%s=" %namedArg) except: pass return argMatches - def dict_key_matches(self, text): + @staticmethod + def _get_keys(obj: Any) -> List[Any]: + # Objects can define their own completions by defining an + # _ipy_key_completions_() method. + method = get_real_method(obj, '_ipython_key_completions_') + if method is not None: + return method() + + # Special case some common in-memory dict-like types + if isinstance(obj, dict) or\ + _safe_isinstance(obj, 'pandas', 'DataFrame'): + try: + return list(obj.keys()) + except Exception: + return [] + elif _safe_isinstance(obj, 'numpy', 'ndarray') or\ + _safe_isinstance(obj, 'numpy', 'void'): + return obj.dtype.names or [] + return [] + + def dict_key_matches(self, text:str) -> List[str]: "Match string keys in a dictionary, after e.g. 'foo[' " - def get_keys(obj): - # Objects can define their own completions by defining an - # _ipy_key_completions_() method. - method = get_real_method(obj, '_ipython_key_completions_') - if method is not None: - return method() - - # Special case some common in-memory dict-like types - if isinstance(obj, dict) or\ - _safe_isinstance(obj, 'pandas', 'DataFrame'): - try: - return list(obj.keys()) - except Exception: - return [] - elif _safe_isinstance(obj, 'numpy', 'ndarray') or\ - _safe_isinstance(obj, 'numpy', 'void'): - return obj.dtype.names or [] - return [] - try: + + if self.__dict_key_regexps is not None: regexps = self.__dict_key_regexps - except AttributeError: + else: dict_key_re_fmt = r'''(?x) ( # match dict-referring expression wrt greedy setting %s ) \[ # open bracket \s* # and optional whitespace + # Capture any number of str-like objects (e.g. "a", "b", 'c') + ((?:[uUbB]? # string prefix (r not handled) + (?: + '(?:[^']|(? Tuple[str, List[str]] : + """Match Latex-like syntax for unicode characters base on the name of the character. This does ``\\GREEK SMALL LETTER ETA`` -> ``η`` Works only on valid python 3 identifier, or on combining characters that will combine to form a valid identifier. - - Used on Python 3 only. """ slashpos = text.rfind('\\') if slashpos > -1: @@ -1716,11 +1775,11 @@ class IPCompleter(Completer): return '\\'+s,[unic] except KeyError: pass - return u'', [] + return '', [] - def latex_matches(self, text): - u"""Match Latex syntax for unicode characters. + def latex_matches(self, text:str) -> Tuple[str, Sequence[str]]: + """Match Latex syntax for unicode characters. This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α`` """ @@ -1737,7 +1796,7 @@ class IPCompleter(Completer): matches = [k for k in latex_symbols if k.startswith(s)] if matches: return s, matches - return u'', [] + return '', () def dispatch_custom_completer(self, text): if not self.custom_completers: @@ -1800,18 +1859,18 @@ class IPCompleter(Completer): Parameters ---------- - - text:str + text : str Full text of the current input, multi line string. - offset:int + offset : int Integer representing the position of the cursor in ``text``. Offset is 0-based indexed. Yields ------ - :any:`Completion` object - + Completion + Notes + ----- The cursor on a text can either be seen as being "in between" characters or "On" a character depending on the interface visible to the user. For consistency the cursor being on "in between" characters X @@ -1821,7 +1880,6 @@ class IPCompleter(Completer): Combining characters may span more that one position in the text. - .. note:: If ``IPCompleter.debug`` is :any:`True` will yield a ``--jedi/ipython--`` @@ -1840,7 +1898,15 @@ class IPCompleter(Completer): category=ProvisionalCompleterWarning, stacklevel=2) seen = set() + profiler:Optional[cProfile.Profile] try: + if self.profile_completions: + import cProfile + profiler = cProfile.Profile() + profiler.enable() + else: + profiler = None + for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000): if c and (c in seen): continue @@ -1850,13 +1916,19 @@ class IPCompleter(Completer): """if completions take too long and users send keyboard interrupt, do not crash and return ASAP. """ pass - - def _completions(self, full_text: str, offset: int, *, _timeout)->Iterator[Completion]: + finally: + if profiler is not None: + profiler.disable() + ensure_dir_exists(self.profiler_output_dir) + output_path = os.path.join(self.profiler_output_dir, str(uuid.uuid4())) + print("Writing profiler output to", output_path) + profiler.dump_stats(output_path) + + def _completions(self, full_text: str, offset: int, *, _timeout) -> Iterator[Completion]: """ Core completion module.Same signature as :any:`completions`, with the extra `timeout` parameter (in seconds). - Computing jedi's completion ``.type`` can be quite expensive (it is a lazy property) and can require some warm-up, more warm up than just computing the ``name`` of a completion. The warm-up can be : @@ -1936,7 +2008,7 @@ class IPCompleter(Completer): yield Completion(start=start_offset, end=offset, text=m, _origin=t, signature='', type='') - def complete(self, text=None, line_buffer=None, cursor_pos=None): + def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]: """Find completions for the given text and line context. Note that both the text and the line_buffer are optional, but at least @@ -1944,35 +2016,31 @@ class IPCompleter(Completer): Parameters ---------- - text : string, optional + text : string, optional Text to perform the completion on. If not given, the line buffer is split using the instance's CompletionSplitter object. - - line_buffer : string, optional + line_buffer : string, optional If not given, the completer attempts to obtain the current line buffer via readline. This keyword allows clients which are requesting for text completions in non-readline contexts to inform the completer of the entire text. - - cursor_pos : int, optional + cursor_pos : int, optional Index of the cursor in the full line buffer. Should be provided by remote frontends where kernel has no access to frontend state. Returns ------- + Tuple of two items: text : str - Text that was actually used in the completion. - + Text that was actually used in the completion. matches : list - A list of completion matches. - - - .. note:: + A list of completion matches. + Notes + ----- This API is likely to be deprecated and replaced by :any:`IPCompleter.completions` in the future. - """ warnings.warn('`Completer.complete` is pending deprecation since ' 'IPython 6.0 and will be replaced by `Completer.completions`.', @@ -1982,21 +2050,46 @@ class IPCompleter(Completer): return self._complete(line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0)[:2] def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None, - full_text=None) -> Tuple[str, List[str], List[str], Iterable[_FakeJediCompletion]]: + full_text=None) -> _CompleteResult: """ - Like complete but can also returns raw jedi completions as well as the origin of the completion text. This could (and should) be made much cleaner but that will be simpler once we drop the old (and stateful) :any:`complete` API. - With current provisional API, cursor_pos act both (depending on the caller) as the offset in the ``text`` or ``line_buffer``, or as the ``column`` when passing multiline strings this could/should be renamed but would add extra noise. + + Parameters + ---------- + cursor_line + Index of the line the cursor is on. 0 indexed. + cursor_pos + Position of the cursor in the current line/line_buffer/text. 0 + indexed. + line_buffer : optional, str + The current line the cursor is in, this is mostly due to legacy + reason that readline could only give a us the single current line. + Prefer `full_text`. + text : str + The current "token" the cursor is in, mostly also for historical + reasons. as the completer would trigger only after the current line + was parsed. + full_text : str + Full text of the current cell. + + Returns + ------- + A tuple of N elements which are (likely): + matched_text: ? the text that the complete matched + matches: list of completions ? + matches_origin: ? list same length as matches, and where each completion came from + jedi_matches: list of Jedi matches, have it's own structure. """ + # if the cursor position isn't given, the only sane assumption we can # make is that it's at the end of the line (the common case) if cursor_pos is None: @@ -2014,17 +2107,16 @@ class IPCompleter(Completer): if self.backslash_combining_completions: # allow deactivation of these on windows. base_text = text if not line_buffer else line_buffer[:cursor_pos] - latex_text, latex_matches = self.latex_matches(base_text) - if latex_matches: - return latex_text, latex_matches, ['latex_matches']*len(latex_matches), () - name_text = '' - name_matches = [] - # need to add self.fwd_unicode_match() function here when done - for meth in (self.unicode_name_matches, back_latex_name_matches, back_unicode_name_matches, self.fwd_unicode_match): + + for meth in (self.latex_matches, + self.unicode_name_matches, + back_latex_name_matches, + back_unicode_name_matches, + self.fwd_unicode_match): name_text, name_matches = meth(base_text) if name_text: - return name_text, name_matches[:MATCHES_LIMIT], \ - [meth.__qualname__]*min(len(name_matches), MATCHES_LIMIT), () + return _CompleteResult(name_text, name_matches[:MATCHES_LIMIT], \ + [meth.__qualname__]*min(len(name_matches), MATCHES_LIMIT), ()) # If no line buffer is given, assume the input text is all there was @@ -2039,22 +2131,23 @@ class IPCompleter(Completer): matches = list(matcher(line_buffer))[:MATCHES_LIMIT] if matches: origins = [matcher.__qualname__] * len(matches) - return text, matches, origins, () + return _CompleteResult(text, matches, origins, ()) # Start with a clean slate of completions matches = [] - + # FIXME: we should extend our api to return a dict with completions for # different types of objects. The rlcomplete() method could then # simply collapse the dict into a list for readline, but we'd have # richer completion semantics in other environments. - completions = () - if self.use_jedi: + is_magic_prefix = len(text) > 0 and text[0] == "%" + completions: Iterable[Any] = [] + if self.use_jedi and not is_magic_prefix: if not full_text: full_text = line_buffer completions = self._jedi_matches( cursor_pos, cursor_line, full_text) - + if self.merge_completions: matches = [] for matcher in self.matchers: @@ -2092,27 +2185,89 @@ class IPCompleter(Completer): self.matches = _matches - return text, _matches, origins, completions + return _CompleteResult(text, _matches, origins, completions) - def fwd_unicode_match(self, text:str) -> Tuple[str, list]: - if self._names is None: - self._names = [] - for c in range(0,0x10FFFF + 1): - try: - self._names.append(unicodedata.name(chr(c))) - except ValueError: - pass + def fwd_unicode_match(self, text:str) -> Tuple[str, Sequence[str]]: + """ + Forward match a string starting with a backslash with a list of + potential Unicode completions. + + Will compute list list of Unicode character names on first call and cache it. + + Returns + ------- + At tuple with: + - matched text (empty if no matches) + - list of potential completions, empty tuple otherwise) + """ + # TODO: self.unicode_names is here a list we traverse each time with ~100k elements. + # We could do a faster match using a Trie. + + # Using pygtrie the following seem to work: + + # s = PrefixSet() + + # for c in range(0,0x10FFFF + 1): + # try: + # s.add(unicodedata.name(chr(c))) + # except ValueError: + # pass + # [''.join(k) for k in s.iter(prefix)] + + # But need to be timed and adds an extra dependency. slashpos = text.rfind('\\') # if text starts with slash if slashpos > -1: - s = text[slashpos+1:] - candidates = [x for x in self._names if x.startswith(s)] + # PERF: It's important that we don't access self._unicode_names + # until we're inside this if-block. _unicode_names is lazily + # initialized, and it takes a user-noticeable amount of time to + # initialize it, so we don't want to initialize it unless we're + # actually going to use it. + s = text[slashpos + 1 :] + sup = s.upper() + candidates = [x for x in self.unicode_names if x.startswith(sup)] + if candidates: + return s, candidates + candidates = [x for x in self.unicode_names if sup in x] + if candidates: + return s, candidates + splitsup = sup.split(" ") + candidates = [ + x for x in self.unicode_names if all(u in x for u in splitsup) + ] if candidates: return s, candidates - else: - return '', () + + return "", () # if text does not start with slash else: - return u'', () + return '', () + + @property + def unicode_names(self) -> List[str]: + """List of names of unicode code points that can be completed. + + The list is lazily initialized on first access. + """ + if self._unicode_names is None: + names = [] + for c in range(0,0x10FFFF + 1): + try: + names.append(unicodedata.name(chr(c))) + except ValueError: + pass + self._unicode_names = _unicode_name_compute(_UNICODE_RANGES) + + return self._unicode_names + +def _unicode_name_compute(ranges:List[Tuple[int,int]]) -> List[str]: + names = [] + for start,stop in ranges: + for c in range(start, stop) : + try: + names.append(unicodedata.name(chr(c))) + except ValueError: + pass + return names diff --git a/contrib/python/ipython/py3/IPython/core/completerlib.py b/contrib/python/ipython/py3/IPython/core/completerlib.py index bda665d8a2b..65efa42254c 100644 --- a/contrib/python/ipython/py3/IPython/core/completerlib.py +++ b/contrib/python/ipython/py3/IPython/core/completerlib.py @@ -201,6 +201,17 @@ def is_importable(module, attr, only_modules): else: return not(attr[:2] == '__' and attr[-2:] == '__') +def is_possible_submodule(module, attr): + try: + obj = getattr(module, attr) + except AttributeError: + # Is possilby an unimported submodule + return True + except TypeError: + # https://github.com/ipython/ipython/issues/9678 + return False + return inspect.ismodule(obj) + def try_import(mod: str, only_modules=False) -> List[str]: """ @@ -220,7 +231,12 @@ def try_import(mod: str, only_modules=False) -> List[str]: completions.extend( [attr for attr in dir(m) if is_importable(m, attr, only_modules)]) - completions.extend(getattr(m, '__all__', [])) + m_all = getattr(m, "__all__", []) + if only_modules: + completions.extend(attr for attr in m_all if is_possible_submodule(m, attr)) + else: + completions.extend(m_all) + if m_is_init: completions.extend(arcadia_module_list(mod)) completions_set = {c for c in completions if isinstance(c, str)} diff --git a/contrib/python/ipython/py3/IPython/core/crashhandler.py b/contrib/python/ipython/py3/IPython/core/crashhandler.py index 1e0b429d09a..4af39361e80 100644 --- a/contrib/python/ipython/py3/IPython/core/crashhandler.py +++ b/contrib/python/ipython/py3/IPython/core/crashhandler.py @@ -23,6 +23,7 @@ import os import sys import traceback from pprint import pformat +from pathlib import Path from IPython.core import ultratb from IPython.core.release import author_email @@ -31,6 +32,8 @@ from IPython.utils.py3compat import input from IPython.core.release import __version__ as version +from typing import Optional + #----------------------------------------------------------------------------- # Code #----------------------------------------------------------------------------- @@ -94,34 +97,40 @@ class CrashHandler(object): message_template = _default_message_template section_sep = '\n\n'+'*'*75+'\n\n' - def __init__(self, app, contact_name=None, contact_email=None, - bug_tracker=None, show_crash_traceback=True, call_pdb=False): + def __init__( + self, + app, + contact_name: Optional[str] = None, + contact_email: Optional[str] = None, + bug_tracker: Optional[str] = None, + show_crash_traceback: bool = True, + call_pdb: bool = False, + ): """Create a new crash handler Parameters ---------- - app : Application + app : Application A running :class:`Application` instance, which will be queried at crash time for internal information. - contact_name : str A string with the name of the person to contact. - contact_email : str A string with the email address of the contact. - bug_tracker : str A string with the URL for your project's bug tracker. - show_crash_traceback : bool If false, don't print the crash traceback on stderr, only generate the on-disk report + call_pdb + Whether to call pdb on crash - Non-argument instance attributes: - + Attributes + ---------- These instances contain some non-argument attributes which allow for further customization of the crash handler's behavior. Please see the source for further details. + """ self.crash_report_fname = "Crash_report_%s.txt" % app.name self.app = app @@ -151,10 +160,10 @@ class CrashHandler(object): try: rptdir = self.app.ipython_dir except: - rptdir = os.getcwd() - if rptdir is None or not os.path.isdir(rptdir): - rptdir = os.getcwd() - report_name = os.path.join(rptdir,self.crash_report_fname) + rptdir = Path.cwd() + if rptdir is None or not Path.is_dir(rptdir): + rptdir = Path.cwd() + report_name = rptdir / self.crash_report_fname # write the report filename into the instance dict so it can get # properly expanded out in the user message template self.crash_report_fname = report_name @@ -176,7 +185,7 @@ class CrashHandler(object): # and generate a complete report on disk try: - report = open(report_name,'w') + report = open(report_name, "w", encoding="utf-8") except: print('Could not create crash report on disk.', file=sys.stderr) return diff --git a/contrib/python/ipython/py3/IPython/core/debugger.py b/contrib/python/ipython/py3/IPython/core/debugger.py index 1744bdb8a8e..8e3dd9678cd 100644 --- a/contrib/python/ipython/py3/IPython/core/debugger.py +++ b/contrib/python/ipython/py3/IPython/core/debugger.py @@ -69,8 +69,8 @@ or configure it in your ``.pdbrc`` -Licencse --------- +License +------- Modified from the standard pdb.Pdb class to avoid including readline, so that the command line completion of other programs which include this isn't @@ -102,7 +102,6 @@ All the changes since then are under the same license as IPython. #***************************************************************************** import bdb -import functools import inspect import linecache import sys @@ -114,12 +113,13 @@ from IPython import get_ipython from IPython.utils import PyColorize from IPython.utils import coloransi, py3compat from IPython.core.excolors import exception_colors -from IPython.testing.skipdoctest import skip_doctest +# skip module docstests +__skip_doctest__ = True prompt = 'ipdb> ' -#We have to check this directly from sys.argv, config struct not yet available +# We have to check this directly from sys.argv, config struct not yet available from pdb import Pdb as OldPdb # Allow the set_trace code to operate outside of an ipython instance, even if @@ -144,112 +144,15 @@ def BdbQuit_excepthook(et, ev, tb, excepthook=None): All other exceptions are processed using the `excepthook` parameter. """ - warnings.warn("`BdbQuit_excepthook` is deprecated since version 5.1", - DeprecationWarning, stacklevel=2) - if et==bdb.BdbQuit: - print('Exiting Debugger.') - elif excepthook is not None: - excepthook(et, ev, tb) - else: - # Backwards compatibility. Raise deprecation warning? - BdbQuit_excepthook.excepthook_ori(et,ev,tb) - - -def BdbQuit_IPython_excepthook(self,et,ev,tb,tb_offset=None): - warnings.warn( - "`BdbQuit_IPython_excepthook` is deprecated since version 5.1", - DeprecationWarning, stacklevel=2) - print('Exiting Debugger.') - - -class Tracer(object): - """ - DEPRECATED - - Class for local debugging, similar to pdb.set_trace. - - Instances of this class, when called, behave like pdb.set_trace, but - providing IPython's enhanced capabilities. - - This is implemented as a class which must be initialized in your own code - and not as a standalone function because we need to detect at runtime - whether IPython is already active or not. That detection is done in the - constructor, ensuring that this code plays nicely with a running IPython, - while functioning acceptably (though with limitations) if outside of it. - """ - - @skip_doctest - def __init__(self, colors=None): - """ - DEPRECATED - - Create a local debugger instance. - - Parameters - ---------- + raise ValueError( + "`BdbQuit_excepthook` is deprecated since version 5.1", + ) - colors : str, optional - The name of the color scheme to use, it must be one of IPython's - valid color schemes. If not given, the function will default to - the current IPython scheme when running inside IPython, and to - 'NoColor' otherwise. - Examples - -------- - :: - - from IPython.core.debugger import Tracer; debug_here = Tracer() - - Later in your code:: - - debug_here() # -> will open up the debugger at that point. - - Once the debugger activates, you can use all of its regular commands to - step through code, set breakpoints, etc. See the pdb documentation - from the Python standard library for usage details. - """ - warnings.warn("`Tracer` is deprecated since version 5.1, directly use " - "`IPython.core.debugger.Pdb.set_trace()`", - DeprecationWarning, stacklevel=2) - - ip = get_ipython() - if ip is None: - # Outside of ipython, we set our own exception hook manually - sys.excepthook = functools.partial(BdbQuit_excepthook, - excepthook=sys.excepthook) - def_colors = 'NoColor' - else: - # In ipython, we use its custom exception handler mechanism - def_colors = ip.colors - ip.set_custom_exc((bdb.BdbQuit,), BdbQuit_IPython_excepthook) - - if colors is None: - colors = def_colors - - # The stdlib debugger internally uses a modified repr from the `repr` - # module, that limits the length of printed strings to a hardcoded - # limit of 30 characters. That much trimming is too aggressive, let's - # at least raise that limit to 80 chars, which should be enough for - # most interactive uses. - try: - from reprlib import aRepr - aRepr.maxstring = 80 - except: - # This is only a user-facing convenience, so any error we encounter - # here can be warned about but can be otherwise ignored. These - # printouts will tell us about problems if this API changes - import traceback - traceback.print_exc() - - self.debugger = Pdb(colors) - - def __call__(self): - """Starts an interactive debugger at the point where called. - - This is similar to the pdb.set_trace() function from the std lib, but - using IPython's enhanced debugger.""" - - self.debugger.set_trace(sys._getframe().f_back) +def BdbQuit_IPython_excepthook(self, et, ev, tb, tb_offset=None): + raise ValueError( + "`BdbQuit_IPython_excepthook` is deprecated since version 5.1", + DeprecationWarning, stacklevel=2) RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+') @@ -291,14 +194,11 @@ class Pdb(OldPdb): "debuggerskip": True, } - def __init__(self, color_scheme=None, completekey=None, - stdin=None, stdout=None, context=5, **kwargs): + def __init__(self, completekey=None, stdin=None, stdout=None, context=5, **kwargs): """Create a new IPython debugger. Parameters ---------- - color_scheme : default None - Deprecated, do not use. completekey : default None Passed to pdb.Pdb. stdin : default None @@ -322,8 +222,8 @@ class Pdb(OldPdb): self.context = int(context) if self.context <= 0: raise ValueError("Context must be a positive integer") - except (TypeError, ValueError): - raise ValueError("Context must be a positive integer") + except (TypeError, ValueError) as e: + raise ValueError("Context must be a positive integer") from e # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`. OldPdb.__init__(self, completekey, stdin, stdout, **kwargs) @@ -339,14 +239,10 @@ class Pdb(OldPdb): self.shell = TerminalInteractiveShell.instance() # needed by any code which calls __import__("__main__") after # the debugger was entered. See also #9941. - sys.modules['__main__'] = save_main + sys.modules["__main__"] = save_main - if color_scheme is not None: - warnings.warn( - "The `color_scheme` argument is deprecated since version 5.1", - DeprecationWarning, stacklevel=2) - else: - color_scheme = self.shell.colors + + color_scheme = self.shell.colors self.aliases = {} @@ -374,7 +270,6 @@ class Pdb(OldPdb): cst['Neutral'].colors.breakpoint_enabled = C.LightRed cst['Neutral'].colors.breakpoint_disabled = C.Red - # Add a python parser so we can syntax highlight source while # debugging. self.parser = PyColorize.Parser(style=color_scheme) @@ -423,14 +318,14 @@ class Pdb(OldPdb): def hidden_frames(self, stack): """ - Given an index in the stack return wether it should be skipped. + Given an index in the stack return whether it should be skipped. This is used in up/down and where to skip frames. """ # The f_locals dictionary is updated from the actual frame # locals whenever the .f_locals accessor is called, so we # avoid calling it here to preserve self.curframe_locals. - # Futhermore, there is no good reason to hide the current frame. + # Furthermore, there is no good reason to hide the current frame. ip_hide = [self._hidden_predicate(s[0]) for s in stack] ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"] if ip_start and self._predicates["ipython_internal"]: @@ -443,13 +338,25 @@ class Pdb(OldPdb): except KeyboardInterrupt: self.stdout.write("\n" + self.shell.get_exception_only()) + def precmd(self, line): + """Perform useful escapes on the command before it is executed.""" + + if line.endswith("??"): + line = "pinfo2 " + line[:-2] + elif line.endswith("?"): + line = "pinfo " + line[:-1] + + line = super().precmd(line) + + return line + def new_do_frame(self, arg): OldPdb.do_frame(self, arg) def new_do_quit(self, arg): if hasattr(self, 'old_all_completions'): - self.shell.Completer.all_completions=self.old_all_completions + self.shell.Completer.all_completions = self.old_all_completions return OldPdb.do_quit(self, arg) @@ -467,11 +374,11 @@ class Pdb(OldPdb): if context is None: context = self.context try: - context=int(context) + context = int(context) if context <= 0: raise ValueError("Context must be a positive integer") - except (TypeError, ValueError): - raise ValueError("Context must be a positive integer") + except (TypeError, ValueError) as e: + raise ValueError("Context must be a positive integer") from e try: skipped = 0 for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack): @@ -496,11 +403,11 @@ class Pdb(OldPdb): if context is None: context = self.context try: - context=int(context) + context = int(context) if context <= 0: raise ValueError("Context must be a positive integer") - except (TypeError, ValueError): - raise ValueError("Context must be a positive integer") + except (TypeError, ValueError) as e: + raise ValueError("Context must be a positive integer") from e print(self.format_stack_entry(frame_lineno, '', context), file=self.stdout) # vds: >> @@ -511,8 +418,8 @@ class Pdb(OldPdb): def _get_frame_locals(self, frame): """ " - Acessing f_local of current frame reset the namespace, so we want to avoid - that or the following can happend + Accessing f_local of current frame reset the namespace, so we want to avoid + that or the following can happen ipdb> foo "old" @@ -535,25 +442,22 @@ class Pdb(OldPdb): if context is None: context = self.context try: - context=int(context) + context = int(context) if context <= 0: print("Context must be a positive integer", file=self.stdout) except (TypeError, ValueError): print("Context must be a positive integer", file=self.stdout) - try: - import reprlib # Py 3 - except ImportError: - import repr as reprlib # Py 2 + + import reprlib ret = [] Colors = self.color_scheme_table.active_colors ColorsNormal = Colors.Normal - tpl_link = u'%s%%s%s' % (Colors.filenameEm, ColorsNormal) - tpl_call = u'%s%%s%s%%s%s' % (Colors.vName, Colors.valEm, ColorsNormal) - tpl_line = u'%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal) - tpl_line_em = u'%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, - ColorsNormal) + tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal) + tpl_call = "%s%%s%s%%s%s" % (Colors.vName, Colors.valEm, ColorsNormal) + tpl_line = "%%s%s%%s %s%%s" % (Colors.lineno, ColorsNormal) + tpl_line_em = "%%s%s%%s %s%%s%s" % (Colors.linenoEm, Colors.line, ColorsNormal) frame, lineno = frame_lineno @@ -587,8 +491,8 @@ class Pdb(OldPdb): if frame is self.curframe: ret.append('> ') else: - ret.append(' ') - ret.append(u'%s(%s)%s\n' % (link,lineno,call)) + ret.append(" ") + ret.append("%s(%s)%s\n" % (link, lineno, call)) start = lineno - 1 - context//2 lines = linecache.getlines(filename) @@ -596,17 +500,17 @@ class Pdb(OldPdb): start = max(start, 0) lines = lines[start : start + context] - for i,line in enumerate(lines): - show_arrow = (start + 1 + i == lineno) - linetpl = (frame is self.curframe or show_arrow) \ - and tpl_line_em \ - or tpl_line - ret.append(self.__format_line(linetpl, filename, - start + 1 + i, line, - arrow = show_arrow) ) - return ''.join(ret) - - def __format_line(self, tpl_line, filename, lineno, line, arrow = False): + for i, line in enumerate(lines): + show_arrow = start + 1 + i == lineno + linetpl = (frame is self.curframe or show_arrow) and tpl_line_em or tpl_line + ret.append( + self.__format_line( + linetpl, filename, start + 1 + i, line, arrow=show_arrow + ) + ) + return "".join(ret) + + def __format_line(self, tpl_line, filename, lineno, line, arrow=False): bp_mark = "" bp_mark_color = "" @@ -636,7 +540,6 @@ class Pdb(OldPdb): return tpl_line % (bp_mark_color + bp_mark, num, line) - def print_list_lines(self, filename, first, last): """The printing (as opposed to the parsing part of a 'list' command.""" @@ -655,9 +558,13 @@ class Pdb(OldPdb): break if lineno == self.curframe.f_lineno: - line = self.__format_line(tpl_line_em, filename, lineno, line, arrow = True) + line = self.__format_line( + tpl_line_em, filename, lineno, line, arrow=True + ) else: - line = self.__format_line(tpl_line, filename, lineno, line, arrow = False) + line = self.__format_line( + tpl_line, filename, lineno, line, arrow=False + ) src.append(line) self.lineno = lineno @@ -891,7 +798,6 @@ class Pdb(OldPdb): def break_anywhere(self, frame): """ - _stop_in_decorator_internals is overly restrictive, as we may still want to trace function calls, so we need to also update break_anywhere so that is we don't `stop_here`, because of debugger skip, we may still @@ -909,13 +815,10 @@ class Pdb(OldPdb): return True return False - @skip_doctest def _is_in_decorator_internal_and_should_skip(self, frame): """ Utility to tell us whether we are in a decorator internal and should stop. - - """ # if we are disabled don't skip @@ -937,9 +840,6 @@ class Pdb(OldPdb): return False def stop_here(self, frame): - """Check if pdb should stop here""" - if not super().stop_here(frame): - return False if self._is_in_decorator_internal_and_should_skip(frame) is True: return False @@ -951,9 +851,10 @@ class Pdb(OldPdb): if self.report_skipped: Colors = self.color_scheme_table.active_colors ColorsNormal = Colors.Normal - print(f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n") - return False - return True + print( + f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n" + ) + return super().stop_here(frame) def do_up(self, arg): """u(p) [count] @@ -976,11 +877,9 @@ class Pdb(OldPdb): if count < 0: _newframe = 0 else: - _newindex = self.curindex counter = 0 hidden_frames = self.hidden_frames(self.stack) for i in range(self.curindex - 1, -1, -1): - frame = self.stack[i][0] if hidden_frames[i] and self.skip_hidden: skipped += 1 continue @@ -988,8 +887,10 @@ class Pdb(OldPdb): if counter >= count: break else: - # if no break occured. - self.error("all frames above hidden") + # if no break occurred. + self.error( + "all frames above hidden, use `skip_hidden False` to get get into those." + ) return Colors = self.color_scheme_table.active_colors @@ -1019,12 +920,10 @@ class Pdb(OldPdb): if count < 0: _newframe = len(self.stack) - 1 else: - _newindex = self.curindex counter = 0 skipped = 0 hidden_frames = self.hidden_frames(self.stack) for i in range(self.curindex + 1, len(self.stack)): - frame = self.stack[i][0] if hidden_frames[i] and self.skip_hidden: skipped += 1 continue @@ -1032,7 +931,9 @@ class Pdb(OldPdb): if counter >= count: break else: - self.error("all frames bellow hidden") + self.error( + "all frames below hidden, use `skip_hidden False` to get get into those." + ) return Colors = self.color_scheme_table.active_colors diff --git a/contrib/python/ipython/py3/IPython/core/display.py b/contrib/python/ipython/py3/IPython/core/display.py index f45e7599c9e..933295ad6ce 100644 --- a/contrib/python/ipython/py3/IPython/core/display.py +++ b/contrib/python/ipython/py3/IPython/core/display.py @@ -5,12 +5,12 @@ # Distributed under the terms of the Modified BSD License. -from binascii import b2a_hex, b2a_base64, hexlify +from binascii import b2a_base64, hexlify +import html import json import mimetypes import os import struct -import sys import warnings from copy import deepcopy from os.path import splitext @@ -18,14 +18,37 @@ from pathlib import Path, PurePath from IPython.utils.py3compat import cast_unicode from IPython.testing.skipdoctest import skip_doctest +from . import display_functions + + +__all__ = ['display_pretty', 'display_html', 'display_markdown', + 'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json', + 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject', + 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'ProgressBar', 'JSON', + 'GeoJSON', 'Javascript', 'Image', 'set_matplotlib_formats', + 'set_matplotlib_close', + 'Video'] + +_deprecated_names = ["display", "clear_output", "publish_display_data", "update_display", "DisplayHandle"] + +__all__ = __all__ + _deprecated_names + + +# ----- warn to import from IPython.display ----- + +from warnings import warn + + +def __getattr__(name): + if name in _deprecated_names: + warn(f"Importing {name} from IPython.core.display is deprecated since IPython 7.14, please import from IPython display", DeprecationWarning, stacklevel=2) + return getattr(display_functions, name) + + if name in globals().keys(): + return globals()[name] + else: + raise AttributeError(f"module {__name__} has no attribute {name}") -__all__ = ['display', 'display_pretty', 'display_html', 'display_markdown', -'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json', -'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject', -'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'ProgressBar', 'JSON', -'GeoJSON', 'Javascript', 'Image', 'clear_output', 'set_matplotlib_formats', -'set_matplotlib_close', 'publish_display_data', 'update_display', 'DisplayHandle', -'Video'] #----------------------------------------------------------------------------- # utility functions @@ -38,17 +61,6 @@ def _safe_exists(path): except Exception: return False -def _merge(d1, d2): - """Like update, but merges sub-dicts instead of clobbering at the top level. - - Updates d1 in-place - """ - - if not isinstance(d2, dict) or not isinstance(d1, dict): - return d2 - for key, value in d2.items(): - d1[key] = _merge(d1.get(key), value) - return d1 def _display_mimetype(mimetype, objs, raw=False, metadata=None): """internal implementation of all display_foo methods @@ -71,334 +83,12 @@ def _display_mimetype(mimetype, objs, raw=False, metadata=None): if raw: # turn list of pngdata into list of { 'image/png': pngdata } objs = [ {mimetype: obj} for obj in objs ] - display(*objs, raw=raw, metadata=metadata, include=[mimetype]) + display_functions.display(*objs, raw=raw, metadata=metadata, include=[mimetype]) #----------------------------------------------------------------------------- # Main functions #----------------------------------------------------------------------------- -# use * to indicate transient is keyword-only -def publish_display_data(data, metadata=None, source=None, *, transient=None, **kwargs): - """Publish data and metadata to all frontends. - - See the ``display_data`` message in the messaging documentation for - more details about this message type. - - Keys of data and metadata can be any mime-type. - - Parameters - ---------- - data : dict - A dictionary having keys that are valid MIME types (like - 'text/plain' or 'image/svg+xml') and values that are the data for - that MIME type. The data itself must be a JSON'able data - structure. Minimally all data should have the 'text/plain' data, - which can be displayed by all frontends. If more than the plain - text is given, it is up to the frontend to decide which - representation to use. - metadata : dict - A dictionary for metadata related to the data. This can contain - arbitrary key, value pairs that frontends can use to interpret - the data. mime-type keys matching those in data can be used - to specify metadata about particular representations. - source : str, deprecated - Unused. - transient : dict, keyword-only - A dictionary of transient data, such as display_id. - """ - from IPython.core.interactiveshell import InteractiveShell - - display_pub = InteractiveShell.instance().display_pub - - # only pass transient if supplied, - # to avoid errors with older ipykernel. - # TODO: We could check for ipykernel version and provide a detailed upgrade message. - if transient: - kwargs['transient'] = transient - - display_pub.publish( - data=data, - metadata=metadata, - **kwargs - ) - - -def _new_id(): - """Generate a new random text id with urandom""" - return b2a_hex(os.urandom(16)).decode('ascii') - - -def display(*objs, include=None, exclude=None, metadata=None, transient=None, display_id=None, **kwargs): - """Display a Python object in all frontends. - - By default all representations will be computed and sent to the frontends. - Frontends can decide which representation is used and how. - - In terminal IPython this will be similar to using :func:`print`, for use in richer - frontends see Jupyter notebook examples with rich display logic. - - Parameters - ---------- - *objs : object - The Python objects to display. - raw : bool, optional - Are the objects to be displayed already mimetype-keyed dicts of raw display data, - or Python objects that need to be formatted before display? [default: False] - include : list, tuple or set, optional - A list of format type strings (MIME types) to include in the - format data dict. If this is set *only* the format types included - in this list will be computed. - exclude : list, tuple or set, optional - A list of format type strings (MIME types) to exclude in the format - data dict. If this is set all format types will be computed, - except for those included in this argument. - metadata : dict, optional - A dictionary of metadata to associate with the output. - mime-type keys in this dictionary will be associated with the individual - representation formats, if they exist. - transient : dict, optional - A dictionary of transient data to associate with the output. - Data in this dict should not be persisted to files (e.g. notebooks). - display_id : str, bool optional - Set an id for the display. - This id can be used for updating this display area later via update_display. - If given as `True`, generate a new `display_id` - clear : bool, optional - Should the output area be cleared before displaying anything? If True, - this will wait for additional output before clearing. [default: False] - kwargs: additional keyword-args, optional - Additional keyword-arguments are passed through to the display publisher. - - Returns - ------- - - handle: DisplayHandle - Returns a handle on updatable displays for use with :func:`update_display`, - if `display_id` is given. Returns :any:`None` if no `display_id` is given - (default). - - Examples - -------- - - >>> class Json(object): - ... def __init__(self, json): - ... self.json = json - ... def _repr_pretty_(self, pp, cycle): - ... import json - ... pp.text(json.dumps(self.json, indent=2)) - ... def __repr__(self): - ... return str(self.json) - ... - - >>> d = Json({1:2, 3: {4:5}}) - - >>> print(d) - {1: 2, 3: {4: 5}} - - >>> display(d) - { - "1": 2, - "3": { - "4": 5 - } - } - - >>> def int_formatter(integer, pp, cycle): - ... pp.text('I'*integer) - - >>> plain = get_ipython().display_formatter.formatters['text/plain'] - >>> plain.for_type(int, int_formatter) - - >>> display(7-5) - II - - >>> del plain.type_printers[int] - >>> display(7-5) - 2 - - See Also - -------- - - :func:`update_display` - - Notes - ----- - - In Python, objects can declare their textual representation using the - `__repr__` method. IPython expands on this idea and allows objects to declare - other, rich representations including: - - - HTML - - JSON - - PNG - - JPEG - - SVG - - LaTeX - - A single object can declare some or all of these representations; all are - handled by IPython's display system. - - The main idea of the first approach is that you have to implement special - display methods when you define your class, one for each representation you - want to use. Here is a list of the names of the special methods and the - values they must return: - - - `_repr_html_`: return raw HTML as a string, or a tuple (see below). - - `_repr_json_`: return a JSONable dict, or a tuple (see below). - - `_repr_jpeg_`: return raw JPEG data, or a tuple (see below). - - `_repr_png_`: return raw PNG data, or a tuple (see below). - - `_repr_svg_`: return raw SVG data as a string, or a tuple (see below). - - `_repr_latex_`: return LaTeX commands in a string surrounded by "$", - or a tuple (see below). - - `_repr_mimebundle_`: return a full mimebundle containing the mapping - from all mimetypes to data. - Use this for any mime-type not listed above. - - The above functions may also return the object's metadata alonside the - data. If the metadata is available, the functions will return a tuple - containing the data and metadata, in that order. If there is no metadata - available, then the functions will return the data only. - - When you are directly writing your own classes, you can adapt them for - display in IPython by following the above approach. But in practice, you - often need to work with existing classes that you can't easily modify. - - You can refer to the documentation on integrating with the display system in - order to register custom formatters for already existing types - (:ref:`integrating_rich_display`). - - .. versionadded:: 5.4 display available without import - .. versionadded:: 6.1 display available without import - - Since IPython 5.4 and 6.1 :func:`display` is automatically made available to - the user without import. If you are using display in a document that might - be used in a pure python context or with older version of IPython, use the - following import at the top of your file:: - - from IPython.display import display - - """ - from IPython.core.interactiveshell import InteractiveShell - - if not InteractiveShell.initialized(): - # Directly print objects. - print(*objs) - return - - raw = kwargs.pop("raw", False) - clear = kwargs.pop("clear", False) - if transient is None: - transient = {} - if metadata is None: - metadata={} - if display_id: - if display_id is True: - display_id = _new_id() - transient['display_id'] = display_id - if kwargs.get('update') and 'display_id' not in transient: - raise TypeError('display_id required for update_display') - if transient: - kwargs['transient'] = transient - - if not objs and display_id: - # if given no objects, but still a request for a display_id, - # we assume the user wants to insert an empty output that - # can be updated later - objs = [{}] - raw = True - - if not raw: - format = InteractiveShell.instance().display_formatter.format - - if clear: - clear_output(wait=True) - - for obj in objs: - if raw: - publish_display_data(data=obj, metadata=metadata, **kwargs) - else: - format_dict, md_dict = format(obj, include=include, exclude=exclude) - if not format_dict: - # nothing to display (e.g. _ipython_display_ took over) - continue - if metadata: - # kwarg-specified metadata gets precedence - _merge(md_dict, metadata) - publish_display_data(data=format_dict, metadata=md_dict, **kwargs) - if display_id: - return DisplayHandle(display_id) - - -# use * for keyword-only display_id arg -def update_display(obj, *, display_id, **kwargs): - """Update an existing display by id - - Parameters - ---------- - - obj: - The object with which to update the display - display_id: keyword-only - The id of the display to update - - See Also - -------- - - :func:`display` - """ - kwargs['update'] = True - display(obj, display_id=display_id, **kwargs) - - -class DisplayHandle(object): - """A handle on an updatable display - - Call `.update(obj)` to display a new object. - - Call `.display(obj`) to add a new instance of this display, - and update existing instances. - - See Also - -------- - - :func:`display`, :func:`update_display` - - """ - - def __init__(self, display_id=None): - if display_id is None: - display_id = _new_id() - self.display_id = display_id - - def __repr__(self): - return "<%s display_id=%s>" % (self.__class__.__name__, self.display_id) - - def display(self, obj, **kwargs): - """Make a new display with my id, updating existing instances. - - Parameters - ---------- - - obj: - object to display - **kwargs: - additional keyword arguments passed to display - """ - display(obj, display_id=self.display_id, **kwargs) - - def update(self, obj, **kwargs): - """Update existing displays with my id - - Parameters - ---------- - - obj: - object to display - **kwargs: - additional keyword arguments passed to update_display - """ - update_display(obj, display_id=self.display_id, **kwargs) - def display_pretty(*objs, **kwargs): """Display the pretty (default) representation of an object. @@ -659,7 +349,8 @@ class DisplayObject(object): def reload(self): """Reload the raw data from file or URL.""" if self.filename is not None: - with open(self.filename, self._read_flags) as f: + encoding = None if "b" in self._read_flags else "utf-8" + with open(self.filename, self._read_flags, encoding=encoding) as f: self.data = f.read() elif self.url is not None: # Deferred import @@ -679,7 +370,11 @@ class DisplayObject(object): if 'gzip' in response.headers['content-encoding']: import gzip from io import BytesIO - with gzip.open(BytesIO(data), 'rt', encoding=encoding) as fp: + + # assume utf-8 if encoding is not specified + with gzip.open( + BytesIO(data), "rt", encoding=encoding or "utf-8" + ) as fp: encoding = None data = fp.read() @@ -792,16 +487,16 @@ class SVG(DisplayObject): pass svg = cast_unicode(svg) self._data = svg - + def _repr_svg_(self): return self._data_and_metadata() class ProgressBar(DisplayObject): - """Progressbar supports displaying a progressbar like element + """Progressbar supports displaying a progressbar like element """ def __init__(self, total): """Creates a new progressbar - + Parameters ---------- total : int @@ -827,10 +522,10 @@ class ProgressBar(DisplayObject): self.html_width, self.total, self.progress) def display(self): - display(self, display_id=self._display_id) + display_functions.display(self, display_id=self._display_id) def update(self): - display(self, display_id=self._display_id, update=True) + display_functions.display(self, display_id=self._display_id, update=True) @property def progress(self): @@ -878,10 +573,10 @@ class JSON(DisplayObject): Path to a local file to load the data from. expanded : boolean Metadata to control whether a JSON display component is expanded. - metadata: dict + metadata : dict Specify extra metadata to attach to the json display object. root : str - The name of the root element of the JSON tree + The name of the root element of the JSON tree """ self.metadata = { 'expanded': expanded, @@ -944,7 +639,7 @@ class GeoJSON(JSON): Scalar types (None, number, string) are not allowed, only dict containers. """ - + def __init__(self, *args, **kwargs): """Create a GeoJSON display object given raw data. @@ -962,12 +657,11 @@ class GeoJSON(JSON): A URL to download the data from. filename : unicode Path to a local file to load the data from. - metadata: dict + metadata : dict Specify extra metadata to attach to the json display object. Examples -------- - The following will display an interactive map of Mars with a point of interest on frontend that do support GeoJSON display. @@ -993,7 +687,7 @@ class GeoJSON(JSON): the GeoJSON object. """ - + super(GeoJSON, self).__init__(*args, **kwargs) @@ -1005,7 +699,7 @@ class GeoJSON(JSON): metadata = { 'application/geo+json': self.metadata } - display(bundle, metadata=metadata, raw=True) + display_functions.display(bundle, metadata=metadata, raw=True) class Javascript(TextDisplayObject): @@ -1034,7 +728,7 @@ class Javascript(TextDisplayObject): running the source code. The full URLs of the libraries should be given. A single Javascript library URL can also be given as a string. - css: : list or str + css : list or str A sequence of css files to load before running the source code. The full URLs of the css files should be given. A single css URL can also be given as a string. @@ -1112,9 +806,20 @@ class Image(DisplayObject): _FMT_GIF: 'image/gif', } - def __init__(self, data=None, url=None, filename=None, format=None, - embed=None, width=None, height=None, retina=False, - unconfined=False, metadata=None): + def __init__( + self, + data=None, + url=None, + filename=None, + format=None, + embed=None, + width=None, + height=None, + retina=False, + unconfined=False, + metadata=None, + alt=None, + ): """Create a PNG/JPEG/GIF image object given raw data. When this object is returned by an input cell or passed to the @@ -1126,15 +831,19 @@ class Image(DisplayObject): data : unicode, str or bytes The raw image data or a URL or filename to load the data from. This always results in embedded image data. + url : unicode A URL to download the data from. If you specify `url=`, the image data will not be embedded unless you also specify `embed=True`. + filename : unicode Path to a local file to load the data from. Images from a file are always embedded. + format : unicode The format of the image data (png/jpeg/jpg/gif). If a filename or URL is given for format will be inferred from the filename extension. + embed : bool Should the image data be embedded using a data URI (True) or be loaded using an tag. Set this to True if you want the image @@ -1144,10 +853,13 @@ class Image(DisplayObject): default value is `False`. Note that QtConsole is not able to display images if `embed` is set to `False` + width : int Width in pixels to which to constrain the image in html + height : int Height in pixels to which to constrain the image in html + retina : bool Automatically set the width and height to half of the measured width and height. @@ -1155,25 +867,38 @@ class Image(DisplayObject): from image data. For non-embedded images, you can just set the desired display width and height directly. - unconfined: bool + + unconfined : bool Set unconfined=True to disable max-width confinement of the image. - metadata: dict + + metadata : dict Specify extra metadata to attach to the image. + alt : unicode + Alternative text for the image, for use by screen readers. + Examples -------- - # embedded image data, works in qtconsole and notebook - # when passed positionally, the first arg can be any of raw image data, - # a URL, or a filename from which to load image data. - # The result is always embedding image data for inline images. - Image('http://www.google.fr/images/srpr/logo3w.png') - Image('/path/to/image.jpg') - Image(b'RAW_PNG_DATA...') - - # Specifying Image(url=...) does not embed the image data, - # it only generates `` tag with a link to the source. - # This will not work in the qtconsole or offline. - Image(url='http://www.google.fr/images/srpr/logo3w.png') + embedded image data, works in qtconsole and notebook + when passed positionally, the first arg can be any of raw image data, + a URL, or a filename from which to load image data. + The result is always embedding image data for inline images. + + >>> Image('https://www.google.fr/images/srpr/logo3w.png') # doctest: +SKIP + + + >>> Image('/path/to/image.jpg') + + + >>> Image(b'RAW_PNG_DATA...') + + + Specifying Image(url=...) does not embed the image data, + it only generates ```` tag with a link to the source. + This will not work in the qtconsole or offline. + + >>> Image(url='https://www.google.fr/images/srpr/logo3w.png') + """ if isinstance(data, (Path, PurePath)): @@ -1228,7 +953,8 @@ class Image(DisplayObject): self.height = height self.retina = retina self.unconfined = unconfined - super(Image, self).__init__(data=data, url=url, filename=filename, + self.alt = alt + super(Image, self).__init__(data=data, url=url, filename=filename, metadata=metadata) if self.width is None and self.metadata.get('width', {}): @@ -1237,6 +963,9 @@ class Image(DisplayObject): if self.height is None and self.metadata.get('height', {}): self.height = metadata['height'] + if self.alt is None and self.metadata.get("alt", {}): + self.alt = metadata["alt"] + if retina: self._retina_shape() @@ -1266,18 +995,21 @@ class Image(DisplayObject): def _repr_html_(self): if not self.embed: - width = height = klass = '' + width = height = klass = alt = "" if self.width: width = ' width="%d"' % self.width if self.height: height = ' height="%d"' % self.height if self.unconfined: klass = ' class="unconfined"' - return u''.format( + if self.alt: + alt = ' alt="%s"' % html.escape(self.alt) + return ''.format( url=self.url, width=width, height=height, klass=klass, + alt=alt, ) def _repr_mimebundle_(self, include=None, exclude=None): @@ -1298,9 +1030,9 @@ class Image(DisplayObject): """shortcut for returning metadata with shape information, if defined""" try: b64_data = b2a_base64(self.data).decode('ascii') - except TypeError: + except TypeError as e: raise FileNotFoundError( - "No such file or directory: '%s'" % (self.data)) + "No such file or directory: '%s'" % (self.data)) from e md = {} if self.metadata: md.update(self.metadata) @@ -1310,6 +1042,8 @@ class Image(DisplayObject): md['height'] = self.height if self.unconfined: md['unconfined'] = self.unconfined + if self.alt: + md["alt"] = self.alt if md or always_both: return b64_data, md else: @@ -1348,12 +1082,15 @@ class Video(DisplayObject): data : unicode, str or bytes The raw video data or a URL or filename to load the data from. Raw data will require passing ``embed=True``. + url : unicode A URL for the video. If you specify ``url=``, the image data will not be embedded. + filename : unicode Path to a local file containing the video. Will be interpreted as a local URL unless ``embed=True``. + embed : bool Should the video be embedded using a data URI (True) or be loaded using a