diff options
author | robot-contrib <robot-contrib@yandex-team.ru> | 2022-05-18 00:43:36 +0300 |
---|---|---|
committer | robot-contrib <robot-contrib@yandex-team.ru> | 2022-05-18 00:43:36 +0300 |
commit | 9e5f436a8b2a27bcc7802e443ea3ef3e41a82a75 (patch) | |
tree | 78b522cab9f76336e62064d4d8ff7c897659b20e /contrib/python/ipython/py3/IPython/core/interactiveshell.py | |
parent | 8113a823ffca6451bb5ff8f0334560885a939a24 (diff) | |
download | ydb-9e5f436a8b2a27bcc7802e443ea3ef3e41a82a75.tar.gz |
Update contrib/python/ipython/py3 to 8.3.0
ref:e84342d4d30476f9148137f37fd0c6405fd36f55
Diffstat (limited to 'contrib/python/ipython/py3/IPython/core/interactiveshell.py')
-rw-r--r-- | contrib/python/ipython/py3/IPython/core/interactiveshell.py | 849 |
1 files changed, 354 insertions, 495 deletions
diff --git a/contrib/python/ipython/py3/IPython/core/interactiveshell.py b/contrib/python/ipython/py3/IPython/core/interactiveshell.py index 8fd546b847..ea9f6310ba 100644 --- a/contrib/python/ipython/py3/IPython/core/interactiveshell.py +++ b/contrib/python/ipython/py3/IPython/core/interactiveshell.py @@ -21,34 +21,53 @@ import inspect import os import re import runpy +import subprocess import sys import tempfile import traceback import types -import subprocess import warnings +from ast import stmt from io import open as io_open - +from logging import error from pathlib import Path -from pickleshare import PickleShareDB +from typing import Callable +from typing import List as ListType +from typing import Optional, Tuple +from warnings import warn +from pickleshare import PickleShareDB +from tempfile import TemporaryDirectory +from traitlets import ( + Any, + Bool, + CaselessStrEnum, + Dict, + Enum, + Instance, + Integer, + List, + Type, + Unicode, + default, + observe, + validate, +) from traitlets.config.configurable import SingletonConfigurable from traitlets.utils.importstring import import_item -from IPython.core import oinspect -from IPython.core import magic -from IPython.core import page -from IPython.core import prefilter -from IPython.core import ultratb + +import IPython.core.hooks +from IPython.core import magic, oinspect, page, prefilter, ultratb from IPython.core.alias import Alias, AliasManager from IPython.core.autocall import ExitAutocall from IPython.core.builtin_trap import BuiltinTrap -from IPython.core.events import EventManager, available_events from IPython.core.compilerop import CachingCompiler, check_linecache_ipython from IPython.core.debugger import InterruptiblePdb from IPython.core.display_trap import DisplayTrap from IPython.core.displayhook import DisplayHook from IPython.core.displaypub import DisplayPublisher from IPython.core.error import InputRejected, UsageError +from IPython.core.events import EventManager, available_events from IPython.core.extensions import ExtensionManager from IPython.core.formatters import DisplayFormatter from IPython.core.history import HistoryManager @@ -60,36 +79,19 @@ from IPython.core.prefilter import PrefilterManager from IPython.core.profiledir import ProfileDir from IPython.core.usage import default_banner from IPython.display import display +from IPython.paths import get_ipython_dir from IPython.testing.skipdoctest import skip_doctest -from IPython.utils import PyColorize -from IPython.utils import io -from IPython.utils import py3compat -from IPython.utils import openpy +from IPython.utils import PyColorize, io, openpy, py3compat from IPython.utils.decorators import undoc from IPython.utils.io import ask_yes_no from IPython.utils.ipstruct import Struct -from IPython.paths import get_ipython_dir -from IPython.utils.path import get_home_dir, get_py_filename, ensure_dir_exists -from IPython.utils.process import system, getoutput +from IPython.utils.path import ensure_dir_exists, get_home_dir, get_py_filename +from IPython.utils.process import getoutput, system from IPython.utils.strdispatch import StrDispatch from IPython.utils.syspathcontext import prepended_to_syspath -from IPython.utils.text import format_screen, LSString, SList, DollarFormatter -from IPython.utils.tempdir import TemporaryDirectory -from traitlets import ( - Integer, Bool, CaselessStrEnum, Enum, List, Dict, Unicode, Instance, Type, - observe, default, validate, Any -) -from warnings import warn -from logging import error -import IPython.core.hooks +from IPython.utils.text import DollarFormatter, LSString, SList, format_screen -from typing import List as ListType, Tuple, Optional -from ast import AST - -# NoOpContext is deprecated, but ipykernel imports it from here. -# See https://github.com/ipython/ipykernel/issues/157 -# (2016, let's try to remove than in IPython 8.0) -from IPython.utils.contexts import NoOpContext +sphinxify: Optional[Callable] try: import docrepr.sphinxify as sphx @@ -115,118 +117,25 @@ class ProvisionalWarning(DeprecationWarning): """ pass -if sys.version_info > (3,8): - from ast import Module -else : - # mock the new API, ignore second argument - # see https://github.com/ipython/ipython/issues/11590 - from ast import Module as OriginalModule - Module = lambda nodelist, type_ignores: OriginalModule(nodelist) - -if sys.version_info > (3,6): - _assign_nodes = (ast.AugAssign, ast.AnnAssign, ast.Assign) - _single_targets_nodes = (ast.AugAssign, ast.AnnAssign) -else: - _assign_nodes = (ast.AugAssign, ast.Assign ) - _single_targets_nodes = (ast.AugAssign, ) +from ast import Module + +_assign_nodes = (ast.AugAssign, ast.AnnAssign, ast.Assign) +_single_targets_nodes = (ast.AugAssign, ast.AnnAssign) #----------------------------------------------------------------------------- # Await Helpers #----------------------------------------------------------------------------- -def removed_co_newlocals(function:types.FunctionType) -> types.FunctionType: - """Return a function that do not create a new local scope. - - Given a function, create a clone of this function where the co_newlocal flag - has been removed, making this function code actually run in the sourounding - scope. - - We need this in order to run asynchronous code in user level namespace. - """ - from types import CodeType, FunctionType - CO_NEWLOCALS = 0x0002 - code = function.__code__ - new_co_flags = code.co_flags & ~CO_NEWLOCALS - if sys.version_info > (3, 8, 0, 'alpha', 3): - new_code = code.replace(co_flags=new_co_flags) - else: - new_code = CodeType( - code.co_argcount, - code.co_kwonlyargcount, - code.co_nlocals, - code.co_stacksize, - new_co_flags, - code.co_code, - code.co_consts, - code.co_names, - code.co_varnames, - code.co_filename, - code.co_name, - code.co_firstlineno, - code.co_lnotab, - code.co_freevars, - code.co_cellvars - ) - return FunctionType(new_code, globals(), function.__name__, function.__defaults__) - - # we still need to run things using the asyncio eventloop, but there is no # async integration -from .async_helpers import (_asyncio_runner, _asyncify, _pseudo_sync_runner) -from .async_helpers import _curio_runner, _trio_runner, _should_be_async - - -def _ast_asyncify(cell:str, wrapper_name:str) -> ast.Module: - """ - Parse a cell with top-level await and modify the AST to be able to run it later. - - Parameter - --------- - - cell: str - The code cell to asyncronify - wrapper_name: str - The name of the function to be used to wrap the passed `cell`. It is - advised to **not** use a python identifier in order to not pollute the - global namespace in which the function will be ran. - - Return - ------ - - A module object AST containing **one** function named `wrapper_name`. - - The given code is wrapped in a async-def function, parsed into an AST, and - the resulting function definition AST is modified to return the last - expression. - - The last expression or await node is moved into a return statement at the - end of the function, and removed from its original location. If the last - node is not Expr or Await nothing is done. - - The function `__code__` will need to be later modified (by - ``removed_co_newlocals``) in a subsequent step to not create new `locals()` - meaning that the local and global scope are the same, ie as if the body of - the function was at module level. - - Lastly a call to `locals()` is made just before the last expression of the - function, or just after the last assignment or statement to make sure the - global dict is updated as python function work with a local fast cache which - is updated only on `local()` calls. - """ +from .async_helpers import ( + _asyncio_runner, + _curio_runner, + _pseudo_sync_runner, + _should_be_async, + _trio_runner, +) - from ast import Expr, Await, Return - if sys.version_info >= (3,8): - return ast.parse(cell) - tree = ast.parse(_asyncify(cell)) - - function_def = tree.body[0] - function_def.name = wrapper_name - try_block = function_def.body[0] - lastexpr = try_block.body[-1] - if isinstance(lastexpr, (Expr, Await)): - try_block.body[-1] = Return(lastexpr.value) - ast.fix_missing_locations(tree) - return tree #----------------------------------------------------------------------------- # Globals #----------------------------------------------------------------------------- @@ -262,13 +171,6 @@ def no_op(*a, **kw): class SpaceInInput(Exception): pass -def get_default_colors(): - "DEPRECATED" - warn('get_default_color is deprecated since IPython 5.0, and returns `Neutral` on all platforms.', - DeprecationWarning, stacklevel=2) - return 'Neutral' - - class SeparateUnicode(Unicode): r"""A Unicode subclass to validate separate_in, separate_out, etc. @@ -329,7 +231,7 @@ class ExecutionResult(object): """ execution_count = None error_before_exec = None - error_in_exec = None + error_in_exec: Optional[BaseException] = None info = None result = None @@ -551,29 +453,6 @@ class InteractiveShell(SingletonConfigurable): will be displayed as regular output instead.""" ).tag(config=True) - # deprecated prompt traits: - - prompt_in1 = Unicode('In [\\#]: ', - help="Deprecated since IPython 4.0 and ignored since 5.0, set TerminalInteractiveShell.prompts object directly." - ).tag(config=True) - prompt_in2 = Unicode(' .\\D.: ', - help="Deprecated since IPython 4.0 and ignored since 5.0, set TerminalInteractiveShell.prompts object directly." - ).tag(config=True) - prompt_out = Unicode('Out[\\#]: ', - help="Deprecated since IPython 4.0 and ignored since 5.0, set TerminalInteractiveShell.prompts object directly." - ).tag(config=True) - prompts_pad_left = Bool(True, - help="Deprecated since IPython 4.0 and ignored since 5.0, set TerminalInteractiveShell.prompts object directly." - ).tag(config=True) - - @observe('prompt_in1', 'prompt_in2', 'prompt_out', 'prompt_pad_left') - def _prompt_trait_changed(self, change): - name = change['name'] - warn("InteractiveShell.{name} is deprecated since IPython 4.0" - " and ignored since 5.0, set TerminalInteractiveShell.prompts" - " object directly.".format(name=name)) - - # protect against weird cases where self.config may not exist: show_rewritten_input = Bool(True, help="Show rewritten input, e.g. for autocall." @@ -707,8 +586,6 @@ class InteractiveShell(SingletonConfigurable): self.init_pdb() self.init_extension_manager() self.init_payload() - self.init_deprecation_warnings() - self.hooks.late_startup_hook() self.events.trigger('shell_initialized', self) atexit.register(self.atexit_operations) @@ -775,6 +652,7 @@ class InteractiveShell(SingletonConfigurable): self.meta = Struct() # Temporary files used for various purposes. Deleted at exit. + # The files here are stored with Path from Pathlib self.tempfiles = [] self.tempdirs = [] @@ -832,16 +710,6 @@ class InteractiveShell(SingletonConfigurable): elif self.logstart: self.magic('logstart') - def init_deprecation_warnings(self): - """ - register default filter for deprecation warning. - - This will allow deprecation warning of function used interactively to show - warning to users, and still hide deprecation warning from libraries import. - """ - if sys.version_info < (3,7): - warnings.filterwarnings("default", category=DeprecationWarning, module=self.user_ns.get("__name__")) - def init_builtins(self): # A single, static flag that we set to True. Its presence indicates @@ -862,16 +730,9 @@ class InteractiveShell(SingletonConfigurable): self.object_info_string_level) def init_io(self): - # This will just use sys.stdout and sys.stderr. If you want to - # override sys.stdout and sys.stderr themselves, you need to do that - # *before* instantiating this class, because io holds onto - # references to the underlying streams. - # io.std* are deprecated, but don't show our own deprecation warnings - # during initialization of the deprecated API. - with warnings.catch_warnings(): - warnings.simplefilter('ignore', DeprecationWarning) - io.stdout = io.IOStream(sys.stdout) - io.stderr = io.IOStream(sys.stderr) + # implemented in subclasses, TerminalInteractiveShell does call + # colorama.init(). + pass def init_prompts(self): # Set system prompts, so that scripts can decide if they are running @@ -907,13 +768,42 @@ class InteractiveShell(SingletonConfigurable): # the appropriate time. self.display_trap = DisplayTrap(hook=self.displayhook) + @staticmethod + def get_path_links(p: Path): + """Gets path links including all symlinks + + Examples + -------- + In [1]: from IPython.core.interactiveshell import InteractiveShell + + In [2]: import sys, pathlib + + In [3]: paths = InteractiveShell.get_path_links(pathlib.Path(sys.executable)) + + In [4]: len(paths) == len(set(paths)) + Out[4]: True + + In [5]: bool(paths) + Out[5]: True + """ + paths = [p] + while p.is_symlink(): + new_path = Path(os.readlink(p)) + if not new_path.is_absolute(): + new_path = p.parent / new_path + p = new_path + paths.append(p) + return paths + def init_virtualenv(self): """Add the current virtualenv to sys.path so the user can import modules from it. This isn't perfect: it doesn't use the Python interpreter with which the virtualenv was built, and it ignores the --no-site-packages option. A warning will appear suggesting the user installs IPython in the virtualenv, but for many cases, it probably works well enough. + Adapted from code snippets online. + http://blog.ufsoft.org/2009/1/29/ipython-and-virtualenv """ if 'VIRTUAL_ENV' not in os.environ: @@ -930,10 +820,7 @@ class InteractiveShell(SingletonConfigurable): # stdlib venv may symlink sys.executable, so we can't use realpath. # but others can symlink *to* the venv Python, so we can't just use sys.executable. # So we just check every item in the symlink tree (generally <= 3) - paths = [p] - while p.is_symlink(): - p = Path(os.readlink(p)) - paths.append(p.resolve()) + paths = self.get_path_links(p) # In Cygwin paths like "c:\..." and '\cygdrive\c\...' are possible if p_venv.parts[1] == "cygdrive": @@ -1029,13 +916,12 @@ class InteractiveShell(SingletonConfigurable): for hook_name in hooks.__all__: # default hooks have priority 100, i.e. low; user hooks should have # 0-100 priority - self.set_hook(hook_name,getattr(hooks,hook_name), 100, _warn_deprecated=False) + self.set_hook(hook_name, getattr(hooks, hook_name), 100) if self.display_page: self.set_hook('show_in_pager', page.as_hook(page.display_page), 90) - def set_hook(self,name,hook, priority=50, str_key=None, re_key=None, - _warn_deprecated=True): + def set_hook(self, name, hook, priority=50, str_key=None, re_key=None): """set_hook(name,hook) -> sets an internal IPython hook. IPython exposes some of its internal API as user-modifiable hooks. By @@ -1065,9 +951,13 @@ class InteractiveShell(SingletonConfigurable): print("Warning! Hook '%s' is not one of %s" % \ (name, IPython.core.hooks.__all__ )) - if _warn_deprecated and (name in IPython.core.hooks.deprecated): + if name in IPython.core.hooks.deprecated: alternative = IPython.core.hooks.deprecated[name] - warn("Hook {} is deprecated. Use {} instead.".format(name, alternative), stacklevel=2) + raise ValueError( + "Hook {} has been deprecated since IPython 5.0. Use {} instead.".format( + name, alternative + ) + ) if not dp: dp = IPython.core.hooks.CommandChainDispatcher() @@ -1091,12 +981,13 @@ class InteractiveShell(SingletonConfigurable): def register_post_execute(self, func): """DEPRECATED: Use ip.events.register('post_run_cell', func) - + Register a function for calling after code execution. """ - warn("ip.register_post_execute is deprecated, use " - "ip.events.register('post_run_cell', func) instead.", stacklevel=2) - self.events.register('post_run_cell', func) + raise ValueError( + "ip.register_post_execute is deprecated since IPython 1.0, use " + "ip.events.register('post_run_cell', func) instead." + ) def _clear_warning_registry(self): # clear the warning registry, so that different code blocks with @@ -1111,14 +1002,14 @@ class InteractiveShell(SingletonConfigurable): def new_main_mod(self, filename, modname): """Return a new 'main' module object for user code execution. - + ``filename`` should be the path of the script which will be run in the module. Requests with the same filename will get the same module, with its namespace cleared. - + ``modname`` should be the module name - normally either '__main__' or the basename of the file without the extension. - + When scripts are executed via %run, we must keep a reference to their __main__ module around so that Python doesn't clear it, rendering references to module globals useless. @@ -1154,7 +1045,6 @@ class InteractiveShell(SingletonConfigurable): Examples -------- - In [15]: import IPython In [16]: m = _ip.new_main_mod(IPython.__file__, 'IPython') @@ -1304,10 +1194,10 @@ class InteractiveShell(SingletonConfigurable): def prepare_user_module(self, user_module=None, user_ns=None): """Prepare the module and namespace in which user code will be run. - + When IPython is started normally, both parameters are None: a new module is created automatically, and its __dict__ used as the namespace. - + If only user_module is provided, its __dict__ is used as the namespace. If only user_ns is provided, a dummy module is created, and user_ns becomes the global namespace. If both are provided (as they may be @@ -1428,7 +1318,7 @@ class InteractiveShell(SingletonConfigurable): def all_ns_refs(self): """Get a list of references to all the namespace dictionaries in which IPython might store a user-created object. - + Note that this does not include the displayhook, which also caches objects from the output.""" return [self.user_ns, self.user_global_ns, self.user_ns_hidden] + \ @@ -1524,8 +1414,8 @@ class InteractiveShell(SingletonConfigurable): else: # Delete by object try: obj = self.user_ns[varname] - except KeyError: - raise NameError("name '%s' is not defined" % varname) + except KeyError as e: + raise NameError("name '%s' is not defined" % varname) from e # Also check in output history ns_refs.append(self.history_manager.output_hist) for ns in ns_refs: @@ -1555,8 +1445,8 @@ class InteractiveShell(SingletonConfigurable): if regex is not None: try: m = re.compile(regex) - except TypeError: - raise TypeError('regex must be a string or compiled pattern') + except TypeError as e: + raise TypeError('regex must be a string or compiled pattern') from e # Search for keys in each namespace that match the given regex # If a match is found, delete the key/value pair. for ns in self.all_ns_refs: @@ -1615,15 +1505,15 @@ class InteractiveShell(SingletonConfigurable): def drop_by_id(self, variables): """Remove a dict of variables from the user namespace, if they are the same as the values in the dictionary. - + This is intended for use by extensions: variables that they've added can be taken back out if they are unloaded, without removing any that the user has overwritten. - + Parameters ---------- variables : dict - A dictionary mapping object names (as strings) to the objects. + A dictionary mapping object names (as strings) to the objects. """ for name, obj in variables.items(): if name in self.user_ns and self.user_ns[name] is obj: @@ -1820,7 +1710,7 @@ class InteractiveShell(SingletonConfigurable): """Get object info as formatted text""" return self.object_inspect_mime(oname, detail_level)['text/plain'] - def object_inspect_mime(self, oname, detail_level=0): + def object_inspect_mime(self, oname, detail_level=0, omit_sections=()): """Get object info as a mimebundle of formatted representations. A mimebundle is a dictionary, keyed by mime-type. @@ -1840,6 +1730,7 @@ class InteractiveShell(SingletonConfigurable): info=info, detail_level=detail_level, formatter=docformat, + omit_sections=omit_sections, ) else: raise KeyError(oname) @@ -1892,7 +1783,6 @@ class InteractiveShell(SingletonConfigurable): Parameters ---------- - exc_tuple : tuple of exception classes A *tuple* of exception classes, for which to call the defined handler. It is very important that you use a tuple, and NOT A @@ -1922,10 +1812,11 @@ class InteractiveShell(SingletonConfigurable): Notes ----- - WARNING: by putting in your own exception handler into IPython's main execution loop, you run a very good chance of nasty crashes. This - facility should only be used if you really know what you are doing.""" + facility should only be used if you really know what you are doing. + """ + if not isinstance(exc_tuple, tuple): raise TypeError("The custom exceptions must be given as a tuple.") @@ -1937,10 +1828,10 @@ class InteractiveShell(SingletonConfigurable): def validate_stb(stb): """validate structured traceback return type - + return type of CustomTB *should* be a list of strings, but allow single strings or None, which are harmless. - + This function will *always* return a list of strings, and will raise a TypeError if stb is inappropriate. """ @@ -2095,10 +1986,19 @@ class InteractiveShell(SingletonConfigurable): # Exception classes can customise their traceback - we # use this in IPython.parallel for exceptions occurring # in the engines. This should return a list of strings. - stb = value._render_traceback_() + if hasattr(value, "_render_traceback_"): + stb = value._render_traceback_() + else: + stb = self.InteractiveTB.structured_traceback( + etype, value, tb, tb_offset=tb_offset + ) + except Exception: - stb = self.InteractiveTB.structured_traceback(etype, - value, tb, tb_offset=tb_offset) + print( + "Unexpected exception formatting exception. Falling back to standard exception" + ) + traceback.print_exc() + return None self._showtraceback(etype, value, stb) if self.call_pdb: @@ -2135,7 +2035,7 @@ class InteractiveShell(SingletonConfigurable): If the syntax error occurred when running a compiled code (i.e. running_compile_code=True), longer stack trace will be displayed. - """ + """ etype, value, last_traceback = self._get_exc_info() if filename and issubclass(etype, SyntaxError): @@ -2160,19 +2060,6 @@ class InteractiveShell(SingletonConfigurable): the %paste magic.""" self.showsyntaxerror() - #------------------------------------------------------------------------- - # Things related to readline - #------------------------------------------------------------------------- - - def init_readline(self): - """DEPRECATED - - Moved to terminal subclass, here only to simplify the init logic.""" - # Set a number of methods that depend on readline to be no-op - warnings.warn('`init_readline` is no-op since IPython 5.0 and is Deprecated', - DeprecationWarning, stacklevel=2) - self.set_custom_completer = no_op - @skip_doctest def set_next_input(self, s, replace=False): """ Sets the 'default' input string for the next command line. @@ -2201,8 +2088,12 @@ class InteractiveShell(SingletonConfigurable): (typically over the network by remote frontends). """ from IPython.core.completer import IPCompleter - from IPython.core.completerlib import (module_completer, - magic_run_completer, cd_completer, reset_completer) + from IPython.core.completerlib import ( + cd_completer, + magic_run_completer, + module_completer, + reset_completer, + ) self.Completer = IPCompleter(shell=self, namespace=self.user_ns, @@ -2229,26 +2120,24 @@ class InteractiveShell(SingletonConfigurable): Parameters ---------- - - text : string - A string of text to be completed on. It can be given as empty and - instead a line/position pair are given. In this case, the - completer itself will split the line like readline does. - - line : string, optional - The complete line that text is part of. - - cursor_pos : int, optional - The position of the cursor on the input line. + text : string + A string of text to be completed on. It can be given as empty and + instead a line/position pair are given. In this case, the + completer itself will split the line like readline does. + line : string, optional + The complete line that text is part of. + cursor_pos : int, optional + The position of the cursor on the input line. Returns ------- - text : string + text : string The actual text that was completed. - - matches : list + matches : list A sorted list with all possible completions. + Notes + ----- The optional arguments allow the completion to take more context into account, and are part of the low-level completion API. @@ -2257,8 +2146,8 @@ class InteractiveShell(SingletonConfigurable): exposing it as a method, it can be used by other non-readline environments (such as GUIs) for text completion. - Simple usage example: - + Examples + -------- In [1]: x = 'hello' In [2]: _ip.complete('x.l') @@ -2341,7 +2230,7 @@ class InteractiveShell(SingletonConfigurable): func, magic_kind=magic_kind, magic_name=magic_name ) - def _find_with_lazy_load(self, type_, magic_name: str): + def _find_with_lazy_load(self, /, type_, magic_name: str): """ Try to find a magic potentially lazy-loading it. @@ -2374,14 +2263,12 @@ class InteractiveShell(SingletonConfigurable): Parameters ---------- magic_name : str - Name of the desired magic function, without '%' prefix. - + Name of the desired magic function, without '%' prefix. line : str - The rest of the input line as a single string. - + The rest of the input line as a single string. _stack_depth : int - If run_line_magic() is called from magic() then _stack_depth=2. - This is added to ensure backward compatibility for use of 'get_ipython().magic()' + If run_line_magic() is called from magic() then _stack_depth=2. + This is added to ensure backward compatibility for use of 'get_ipython().magic()' """ fn = self._find_with_lazy_load("line", magic_name) if fn is None: @@ -2423,7 +2310,7 @@ class InteractiveShell(SingletonConfigurable): Parameters ---------- stack_depth : int - Depth relative to calling frame + Depth relative to calling frame """ return sys._getframe(stack_depth + 1).f_locals @@ -2433,13 +2320,11 @@ class InteractiveShell(SingletonConfigurable): Parameters ---------- magic_name : str - Name of the desired magic function, without '%' prefix. - + Name of the desired magic function, without '%' prefix. line : str - The rest of the first input line as a single string. - + The rest of the first input line as a single string. cell : str - The body of the cell as a (possibly multiline) string. + The body of the cell as a (possibly multiline) string. """ fn = self._find_with_lazy_load("cell", magic_name) if fn is None: @@ -2491,7 +2376,11 @@ class InteractiveShell(SingletonConfigurable): return self.magics_manager.magics[magic_kind].get(magic_name) def magic(self, arg_s): - """DEPRECATED. Use run_line_magic() instead. + """ + DEPRECATED + + Deprecated since IPython 0.13 (warning added in + 8.1), use run_line_magic(magic_name, parameter_s). Call a magic function by name. @@ -2509,6 +2398,12 @@ class InteractiveShell(SingletonConfigurable): valid Python code you can type at the interpreter, including loops and compound statements. """ + warnings.warn( + "`magic(...)` is deprecated since IPython 0.13 (warning added in " + "8.1), use run_line_magic(magic_name, parameter_s).", + DeprecationWarning, + stacklevel=2, + ) # TODO: should we issue a loud deprecation warning here? magic_name, _, magic_arg_s = arg_s.partition(' ') magic_name = magic_name.lstrip(prefilter.ESC_MAGIC) @@ -2548,9 +2443,9 @@ class InteractiveShell(SingletonConfigurable): Parameters ---------- cmd : str - Command to execute (can not end in '&', as background processes are - not supported. Should not be a command that expects input - other than simple text. + Command to execute (can not end in '&', as background processes are + not supported. Should not be a command that expects input + other than simple text. """ if cmd.rstrip().endswith('&'): # this is *far* from a rigorous test @@ -2572,9 +2467,21 @@ class InteractiveShell(SingletonConfigurable): Parameters ---------- cmd : str - Command to execute. + Command to execute. """ cmd = self.var_expand(cmd, depth=1) + # warn if there is an IPython magic alternative. + main_cmd = cmd.split()[0] + has_magic_alternatives = ("pip", "conda", "cd") + + if main_cmd in has_magic_alternatives: + warnings.warn( + ( + "You executed the system command !{0} which may not work " + "as expected. Try the IPython magic %{0} instead." + ).format(main_cmd) + ) + # protect os.system from UNC paths on Windows, which it can't handle: if sys.platform == 'win32': from IPython.utils._process_win32 import AvoidUNCPath @@ -2623,18 +2530,18 @@ class InteractiveShell(SingletonConfigurable): Parameters ---------- cmd : str - Command to execute (can not end in '&', as background processes are - not supported. + Command to execute (can not end in '&', as background processes are + not supported. split : bool, optional - If True, split the output into an IPython SList. Otherwise, an - IPython LSString is returned. These are objects similar to normal - lists and strings, with a few convenience attributes for easier - manipulation of line-based output. You can use '?' on them for - details. + If True, split the output into an IPython SList. Otherwise, an + IPython LSString is returned. These are objects similar to normal + lists and strings, with a few convenience attributes for easier + manipulation of line-based output. You can use '?' on them for + details. depth : int, optional - How many frames above the caller are the local variables which should - be expanded in the command string? The default (0) assumes that the - expansion variables are in the stack frame calling this function. + How many frames above the caller are the local variables which should + be expanded in the command string? The default (0) assumes that the + expansion variables are in the stack frame calling this function. """ if cmd.rstrip().endswith('&'): # this is *far* from a rigorous test @@ -2717,10 +2624,10 @@ class InteractiveShell(SingletonConfigurable): stb = self.InteractiveTB.get_exception_only(etype, evalue) exc_info = { - u'status' : 'error', - u'traceback' : stb, - u'ename' : etype.__name__, - u'evalue' : py3compat.safe_unicode(evalue), + "status": "error", + "traceback": stb, + "ename": etype.__name__, + "evalue": py3compat.safe_unicode(evalue), } return exc_info @@ -2745,9 +2652,9 @@ class InteractiveShell(SingletonConfigurable): Parameters ---------- expressions : dict - A dict with string keys and string values. The expression values - should be valid Python expressions, each of which will be evaluated - in the user namespace. + A dict with string keys and string values. The expression values + should be valid Python expressions, each of which will be evaluated + in the user namespace. Returns ------- @@ -2794,7 +2701,7 @@ class InteractiveShell(SingletonConfigurable): ---------- fname : string The name of the file to be executed. - where : tuple + *where : tuple One or two namespaces, passed to execfile() as (globals,locals). If only one is given, it is passed as both. exit_ignore : bool (False) @@ -2809,11 +2716,11 @@ class InteractiveShell(SingletonConfigurable): __future__ imports are not shared in either direction. """ - fname = os.path.abspath(os.path.expanduser(fname)) + fname = Path(fname).expanduser().resolve() # Make sure we can open the file try: - with open(fname): + with fname.open("rb"): pass except: warn('Could not open file <%s> for safe execution.' % fname) @@ -2822,7 +2729,7 @@ class InteractiveShell(SingletonConfigurable): # Find things also in current directory. This is needed to mimic the # behavior of running a script from the system command line, where # Python inserts the script's directory into sys.path - dname = os.path.dirname(fname) + dname = str(fname.parent) with prepended_to_syspath(dname), self.builtin_trap: try: @@ -2867,11 +2774,11 @@ class InteractiveShell(SingletonConfigurable): raise_exceptions : bool (False) If True raise exceptions everywhere. Meant for testing. """ - fname = os.path.abspath(os.path.expanduser(fname)) + fname = Path(fname).expanduser().resolve() # Make sure we can open the file try: - with open(fname): + with fname.open("rb"): pass except: warn('Could not open file <%s> for safe execution.' % fname) @@ -2880,11 +2787,11 @@ class InteractiveShell(SingletonConfigurable): # Find things also in current directory. This is needed to mimic the # behavior of running a script from the system command line, where # Python inserts the script's directory into sys.path - dname = os.path.dirname(fname) - + dname = str(fname.parent) + def get_cells(): """generator for sequence of code blocks to run""" - if fname.endswith('.ipynb'): + if fname.suffix == ".ipynb": from nbformat import read nb = read(fname, as_version=4) if not nb.cells: @@ -2893,8 +2800,7 @@ class InteractiveShell(SingletonConfigurable): if cell.cell_type == 'code': yield cell.source else: - with open(fname) as f: - yield f.read() + yield fname.read_text(encoding="utf-8") with prepended_to_syspath(dname): try: @@ -2951,19 +2857,19 @@ class InteractiveShell(SingletonConfigurable): Parameters ---------- raw_cell : str - The code (including IPython code such as %magic functions) to run. + The code (including IPython code such as %magic functions) to run. store_history : bool - If True, the raw and translated cell will be stored in IPython's - history. For user code calling back into IPython's machinery, this - should be set to False. + If True, the raw and translated cell will be stored in IPython's + history. For user code calling back into IPython's machinery, this + should be set to False. silent : bool - If True, avoid side-effects, such as implicit displayhooks and - and logging. silent=True forces store_history=False. + If True, avoid side-effects, such as implicit displayhooks and + and logging. silent=True forces store_history=False. shell_futures : bool - If True, the code will share future statements with the interactive - shell. It will both be affected by previous __future__ imports, and - any __future__ imports in the code will affect the shell. If False, - __future__ imports are not shared in either direction. + If True, the code will share future statements with the interactive + shell. It will both be affected by previous __future__ imports, and + any __future__ imports in the code will affect the shell. If False, + __future__ imports are not shared in either direction. Returns ------- @@ -3035,7 +2941,6 @@ class InteractiveShell(SingletonConfigurable): result.error_in_exec = e self.showtraceback(running_compiled_code=True) return result - return def should_run_async( self, raw_cell: str, *, transformed_cell=None, preprocessing_exc_tuple=None @@ -3044,14 +2949,13 @@ class InteractiveShell(SingletonConfigurable): Parameters ---------- - raw_cell: str + raw_cell : str The code to be executed Returns ------- result: bool Whether the code needs to be run with a coroutine runner or not - .. versionadded:: 7.0 """ if not self.autoawait: @@ -3175,9 +3079,8 @@ class InteractiveShell(SingletonConfigurable): cell = raw_cell # Store raw and processed history - if store_history: - self.history_manager.store_inputs(self.execution_count, - cell, raw_cell) + if store_history and raw_cell.strip(" %") != "paste": + self.history_manager.store_inputs(self.execution_count, cell, raw_cell) if not silent: self.logger.log(cell, raw_cell) @@ -3196,35 +3099,12 @@ class InteractiveShell(SingletonConfigurable): _run_async = False with self.builtin_trap: - cell_name = self.compile.cache( - cell, self.execution_count, raw_code=raw_cell - ) + cell_name = compiler.cache(cell, self.execution_count, raw_code=raw_cell) with self.display_trap: # Compile to bytecode try: - if sys.version_info < (3,8) and self.autoawait: - if _should_be_async(cell): - # the code AST below will not be user code: we wrap it - # in an `async def`. This will likely make some AST - # transformer below miss some transform opportunity and - # introduce a small coupling to run_code (in which we - # bake some assumptions of what _ast_asyncify returns. - # they are ways around (like grafting part of the ast - # later: - # - Here, return code_ast.body[0].body[1:-1], as well - # as last expression in return statement which is - # the user code part. - # - Let it go through the AST transformers, and graft - # - it back after the AST transform - # But that seem unreasonable, at least while we - # do not need it. - code_ast = _ast_asyncify(cell, 'async-def-wrapper') - _run_async = True - else: - code_ast = compiler.ast_parse(cell, filename=cell_name) - else: - code_ast = compiler.ast_parse(cell, filename=cell_name) + code_ast = compiler.ast_parse(cell, filename=cell_name) except self.custom_exceptions as e: etype, value, tb = sys.exc_info() self.CustomTB(etype, value, tb) @@ -3250,8 +3130,6 @@ class InteractiveShell(SingletonConfigurable): # Execute the user code interactivity = "none" if silent else self.ast_node_interactivity - if _run_async: - interactivity = 'async' has_raised = await self.run_ast_nodes(code_ast.body, cell_name, interactivity=interactivity, compiler=compiler, result=result) @@ -3308,8 +3186,8 @@ class InteractiveShell(SingletonConfigurable): Parameters ---------- node : ast.Node - The root node to be transformed. Typically called with the ast.Module - produced by parsing user input. + The root node to be transformed. Typically called with the ast.Module + produced by parsing user input. Returns ------- @@ -3356,8 +3234,14 @@ class InteractiveShell(SingletonConfigurable): return code return code.replace(co_name="<cell line: %s>" % (first_real_line,)) - async def run_ast_nodes(self, nodelist:ListType[AST], cell_name:str, interactivity='last_expr', - compiler=compile, result=None): + async def run_ast_nodes( + self, + nodelist: ListType[stmt], + cell_name: str, + interactivity="last_expr", + compiler=compile, + result=None, + ): """Run a sequence of AST nodes. The execution mode depends on the interactivity parameter. @@ -3377,11 +3261,6 @@ class InteractiveShell(SingletonConfigurable): or the last assignment. Other values for this parameter will raise a ValueError. - Experimental value: 'async' Will try to run top level interactive - async/await code in default runner, this will not respect the - interactivity setting and will only run the last node if it is an - expression. - compiler : callable A function with the same interface as the built-in compile(), to turn the AST nodes into code objects. Default is the built-in compile(). @@ -3396,6 +3275,7 @@ class InteractiveShell(SingletonConfigurable): if not nodelist: return + if interactivity == 'last_expr_or_assign': if isinstance(nodelist[-1], _assign_nodes): asg = nodelist[-1] @@ -3424,53 +3304,38 @@ class InteractiveShell(SingletonConfigurable): to_run_exec, to_run_interactive = nodelist[:-1], nodelist[-1:] elif interactivity == 'all': to_run_exec, to_run_interactive = [], nodelist - elif interactivity == 'async': - to_run_exec, to_run_interactive = [], nodelist - _async = True else: raise ValueError("Interactivity was %r" % interactivity) try: - if _async and sys.version_info > (3,8): - raise ValueError("This branch should never happen on Python 3.8 and above, " - "please try to upgrade IPython and open a bug report with your case.") - if _async: - # If interactivity is async the semantics of run_code are - # completely different Skip usual machinery. - mod = Module(nodelist, []) - async_wrapper_code = compiler(mod, cell_name, 'exec') - exec(async_wrapper_code, self.user_global_ns, self.user_ns) - async_code = removed_co_newlocals(self.user_ns.pop('async-def-wrapper')).__code__ - if (await self.run_code(async_code, result, async_=True)): + + def compare(code): + is_async = inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE + return is_async + + # refactor that to just change the mod constructor. + to_run = [] + for node in to_run_exec: + to_run.append((node, "exec")) + + for node in to_run_interactive: + to_run.append((node, "single")) + + for node, mode in to_run: + if mode == "exec": + mod = Module([node], []) + elif mode == "single": + mod = ast.Interactive([node]) + with compiler.extra_flags( + getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x0) + if self.autoawait + else 0x0 + ): + code = compiler(mod, cell_name, mode) + code = self._update_code_co_name(code) + asy = compare(code) + if await self.run_code(code, result, async_=asy): return True - else: - if sys.version_info > (3, 8): - def compare(code): - is_async = (inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE) - return is_async - else: - def compare(code): - return _async - - # refactor that to just change the mod constructor. - to_run = [] - for node in to_run_exec: - to_run.append((node, 'exec')) - - for node in to_run_interactive: - to_run.append((node, 'single')) - - for node,mode in to_run: - if mode == 'exec': - mod = Module([node], []) - elif mode == 'single': - mod = ast.Interactive([node]) - with compiler.extra_flags(getattr(ast, 'PyCF_ALLOW_TOP_LEVEL_AWAIT', 0x0) if self.autoawait else 0x0): - code = compiler(mod, cell_name, mode) - code = self._update_code_co_name(code) - asy = compare(code) - if (await self.run_code(code, result, async_=asy)): - return True # Flush softspace if softspace(sys.stdout, 0): @@ -3493,21 +3358,6 @@ class InteractiveShell(SingletonConfigurable): return False - def _async_exec(self, code_obj: types.CodeType, user_ns: dict): - """ - Evaluate an asynchronous code object using a code runner - - Fake asynchronous execution of code_object in a namespace via a proxy namespace. - - Returns coroutine object, which can be executed via async loop runner - - WARNING: The semantics of `async_exec` are quite different from `exec`, - in particular you can only pass a single namespace. It also return a - handle to the value of the last things returned by code_object. - """ - - return eval(code_obj, user_ns) - async def run_code(self, code_obj, result=None, *, async_=False): """Execute a code object. @@ -3541,12 +3391,7 @@ class InteractiveShell(SingletonConfigurable): outflag = True # happens in more places, so it's easier as default try: try: - self.hooks.pre_run_code_hook() - if async_ and sys.version_info < (3,8): - last_expr = (await self._async_exec(code_obj, self.user_ns)) - code = compile('last_expr', 'fake', "single") - exec(code, {'last_expr': last_expr}) - elif async_ : + if async_: await eval(code_obj, self.user_global_ns, self.user_ns) else: exec(code_obj, self.user_global_ns, self.user_ns) @@ -3579,17 +3424,17 @@ class InteractiveShell(SingletonConfigurable): Parameters ---------- - source : string - Python input code, which can be multiline. + code : string + Python input code, which can be multiline. Returns ------- status : str - One of 'complete', 'incomplete', or 'invalid' if source is not a - prefix of valid code. + One of 'complete', 'incomplete', or 'invalid' if source is not a + prefix of valid code. indent : str - When status is 'incomplete', this is some whitespace to insert on - the next line of the prompt. + When status is 'incomplete', this is some whitespace to insert on + the next line of the prompt. """ status, nspaces = self.input_transformer_manager.check_complete(code) return status, ' ' * (nspaces or 0) @@ -3616,16 +3461,17 @@ class InteractiveShell(SingletonConfigurable): Parameters ---------- gui : optional, string - If given, dictates the choice of matplotlib GUI backend to use - (should be one of IPython's supported backends, 'qt', 'osx', 'tk', - 'gtk', 'wx' or 'inline'), otherwise we use the default chosen by - matplotlib (as dictated by the matplotlib build-time options plus the - user's matplotlibrc configuration file). Note that not all backends - make sense in all contexts, for example a terminal ipython can't - display figures inline. + If given, dictates the choice of matplotlib GUI backend to use + (should be one of IPython's supported backends, 'qt', 'osx', 'tk', + 'gtk', 'wx' or 'inline'), otherwise we use the default chosen by + matplotlib (as dictated by the matplotlib build-time options plus the + user's matplotlibrc configuration file). Note that not all backends + make sense in all contexts, for example a terminal ipython can't + display figures inline. """ - from IPython.core import pylabtools as pt from matplotlib_inline.backend_inline import configure_inline_support + + from IPython.core import pylabtools as pt gui, backend = pt.find_gui_and_backend(gui, self.pylab_gui_select) if gui != 'inline': @@ -3662,18 +3508,18 @@ class InteractiveShell(SingletonConfigurable): Parameters ---------- gui : optional, string - If given, dictates the choice of matplotlib GUI backend to use - (should be one of IPython's supported backends, 'qt', 'osx', 'tk', - 'gtk', 'wx' or 'inline'), otherwise we use the default chosen by - matplotlib (as dictated by the matplotlib build-time options plus the - user's matplotlibrc configuration file). Note that not all backends - make sense in all contexts, for example a terminal ipython can't - display figures inline. + If given, dictates the choice of matplotlib GUI backend to use + (should be one of IPython's supported backends, 'qt', 'osx', 'tk', + 'gtk', 'wx' or 'inline'), otherwise we use the default chosen by + matplotlib (as dictated by the matplotlib build-time options plus the + user's matplotlibrc configuration file). Note that not all backends + make sense in all contexts, for example a terminal ipython can't + display figures inline. import_all : optional, bool, default: True - Whether to do `from numpy import *` and `from pylab import *` - in addition to module imports. + Whether to do `from numpy import *` and `from pylab import *` + in addition to module imports. welcome_message : deprecated - This argument is ignored, no welcome message will be displayed. + This argument is ignored, no welcome message will be displayed. """ from IPython.core.pylabtools import import_pylab @@ -3738,32 +3584,19 @@ class InteractiveShell(SingletonConfigurable): - data(None): if data is given, it gets written out to the temp file immediately, and the file is closed again.""" - dirname = tempfile.mkdtemp(prefix=prefix) - self.tempdirs.append(dirname) + dir_path = Path(tempfile.mkdtemp(prefix=prefix)) + self.tempdirs.append(dir_path) - handle, filename = tempfile.mkstemp('.py', prefix, dir=dirname) + handle, filename = tempfile.mkstemp(".py", prefix, dir=str(dir_path)) os.close(handle) # On Windows, there can only be one open handle on a file - self.tempfiles.append(filename) + + file_path = Path(filename) + self.tempfiles.append(file_path) if data: - with open(filename, 'w') as tmp_file: - tmp_file.write(data) + file_path.write_text(data, encoding="utf-8") return filename - @undoc - def write(self,data): - """DEPRECATED: Write a string to the default output""" - warn('InteractiveShell.write() is deprecated, use sys.stdout instead', - DeprecationWarning, stacklevel=2) - sys.stdout.write(data) - - @undoc - def write_err(self,data): - """DEPRECATED: Write a string to the default error output""" - warn('InteractiveShell.write_err() is deprecated, use sys.stderr instead', - DeprecationWarning, stacklevel=2) - sys.stderr.write(data) - def ask_yes_no(self, prompt, default=None, interrupt=None): if self.quiet: return True @@ -3778,26 +3611,37 @@ class InteractiveShell(SingletonConfigurable): Parameters ---------- - range_str : string + range_str : str The set of slices is given as a string, like "~5/6-~4/2 4:8 9", since this function is for use by magic functions which get their arguments as strings. The number before the / is the session number: ~n goes n back from the current session. + If empty string is given, returns history of current session + without the last input. + raw : bool, optional By default, the processed input is used. If this is true, the raw input history is used instead. Notes ----- - Slices can be described with two notations: * ``N:M`` -> standard python form, means including items N...(M-1). * ``N-M`` -> include items N..M (closed endpoint). """ lines = self.history_manager.get_range_by_str(range_str, raw=raw) - return "\n".join(x for _, _, x in lines) + text = "\n".join(x for _, _, x in lines) + + # Skip the last line, as it's probably the magic that called this + if not range_str: + if "\n" not in text: + text = "" + else: + text = text[: text.rfind("\n")] + + return text def find_user_code(self, target, raw=True, py_only=False, skip_encoding_cookie=True, search_ns=False): """Get a code string from history, file, url, or a string or macro. @@ -3806,26 +3650,26 @@ class InteractiveShell(SingletonConfigurable): Parameters ---------- - target : str + A string specifying code to retrieve. This will be tried respectively + as: ranges of input history (see %history for syntax), url, + corresponding .py file, filename, or an expression evaluating to a + string or Macro in the user namespace. - A string specifying code to retrieve. This will be tried respectively - as: ranges of input history (see %history for syntax), url, - corresponding .py file, filename, or an expression evaluating to a - string or Macro in the user namespace. + If empty string is given, returns complete history of current + session, without the last line. raw : bool - If true (default), retrieve raw history. Has no effect on the other - retrieval mechanisms. + If true (default), retrieve raw history. Has no effect on the other + retrieval mechanisms. py_only : bool (default False) - Only try to fetch python code, do not try alternative methods to decode file - if unicode fails. + Only try to fetch python code, do not try alternative methods to decode file + if unicode fails. Returns ------- A string of code. - ValueError is raised if nothing is found, and TypeError if it evaluates to an object of another type. In each case, .args[0] is a printable message. @@ -3836,13 +3680,13 @@ class InteractiveShell(SingletonConfigurable): try: if target.startswith(('http://', 'https://')): return openpy.read_py_url(target, skip_encoding_cookie=skip_encoding_cookie) - except UnicodeDecodeError: + except UnicodeDecodeError as e: if not py_only : # Deferred import from urllib.request import urlopen response = urlopen(target) return response.read().decode('latin1') - raise ValueError(("'%s' seem to be unreadable.") % target) + raise ValueError(("'%s' seem to be unreadable.") % target) from e potential_target = [target] try : @@ -3854,11 +3698,11 @@ class InteractiveShell(SingletonConfigurable): if os.path.isfile(tgt): # Read file try : return openpy.read_py_file(tgt, skip_encoding_cookie=skip_encoding_cookie) - except UnicodeDecodeError : + except UnicodeDecodeError as e: if not py_only : with io_open(tgt,'r', encoding='latin1') as f : return f.read() - raise ValueError(("'%s' seem to be unreadable.") % target) + raise ValueError(("'%s' seem to be unreadable.") % target) from e elif os.path.isdir(os.path.expanduser(tgt)): raise ValueError("'%s' is a directory, not a regular file." % target) @@ -3870,9 +3714,9 @@ class InteractiveShell(SingletonConfigurable): try: # User namespace codeobj = eval(target, self.user_ns) - except Exception: + except Exception as e: raise ValueError(("'%s' was not found in history, as a file, url, " - "nor in the user namespace.") % target) + "nor in the user namespace.") % target) from e if isinstance(codeobj, str): return codeobj @@ -3882,6 +3726,22 @@ class InteractiveShell(SingletonConfigurable): raise TypeError("%s is neither a string nor a macro." % target, codeobj) + def _atexit_once(self): + """ + At exist operation that need to be called at most once. + Second call to this function per instance will do nothing. + """ + + if not getattr(self, "_atexit_once_called", False): + self._atexit_once_called = True + # Clear all user namespaces to release all references cleanly. + self.reset(new_session=False) + # Close the history session (this stores the end time and line count) + # this must be *before* the tempfile cleanup, in case of temporary + # history db + self.history_manager.end_session() + self.history_manager = None + #------------------------------------------------------------------------- # Things related to IPython exiting #------------------------------------------------------------------------- @@ -3896,29 +3756,28 @@ class InteractiveShell(SingletonConfigurable): code that has the appropriate information, rather than trying to clutter """ - # Close the history session (this stores the end time and line count) - # this must be *before* the tempfile cleanup, in case of temporary - # history db - self.history_manager.end_session() + self._atexit_once() # Cleanup all tempfiles and folders left around for tfile in self.tempfiles: try: - os.unlink(tfile) - except OSError: + tfile.unlink() + self.tempfiles.remove(tfile) + except FileNotFoundError: pass - + del self.tempfiles for tdir in self.tempdirs: try: - os.rmdir(tdir) - except OSError: + tdir.rmdir() + self.tempdirs.remove(tdir) + except FileNotFoundError: pass + del self.tempdirs - # Clear all user namespaces to release all references cleanly. - self.reset(new_session=False) - - # Run user hooks - self.hooks.shutdown_hook() + # Restore user's cursor + if hasattr(self, "editing_mode") and self.editing_mode == "vi": + sys.stdout.write("\x1b[0 q") + sys.stdout.flush() def cleanup(self): self.restore_sys_module_state() |