##// END OF EJS Templates
Refuse to install event loop hooks when not using `prompt_toolkit` (#14132)...
Refuse to install event loop hooks when not using `prompt_toolkit` (#14132) Without this, `%gui` is effectively a no-op but the user thinks it works. For example. If running `ipython`: ``` In [1]: import matplotlib; matplotlib.use('QtAgg'); from matplotlib import pyplot; pyplot.ion(); pyplot.plot([1, 2, 3, 4]) Installed qt6 event loop hook. Out[1]: [<matplotlib.lines.Line2D at 0x1ba2f59d2a0>] ``` The window appears and responds as expected. If running `ipython --simple-prompt`, the user would see the same output, when in fact no event loop hook was installed since it's not supported without `prompt_toolkit`. The resulting Qt window is unresponsive because the event loop is not running, i.e. with `--simple-prompt`, Qt windows should block (but `pyplot` doesn't/can't know to do that) With this PR, the user will see: ``` In [1]: import matplotlib; matplotlib.use('QtAgg'); from matplotlib import pyplot; pyplot.ion(); pyplot.plot([1, 2, 3, 4]) Cannot install event loop hook for "qt" when running with `--simple-prompt`. NOTE: Tk is supported natively; use Tk apps and Tk backends with `--simple-prompt`. Out[1]: [<matplotlib.lines.Line2D at 0x170be0c0310>] ``` They'll still get an unresponsive Qt window, but they'll at least be told this can't work (while anything using Tk will work just fine).

File last commit:

r27875:1c3678bf
r28368:22fc5ab5 merge
Show More
compilerop.py
214 lines | 7.5 KiB | text/x-python | PythonLexer
"""Compiler tools with improved interactive support.
Provides compilation machinery similar to codeop, but with caching support so
we can provide interactive tracebacks.
Authors
-------
* Robert Kern
* Fernando Perez
* Thomas Kluyver
"""
# Note: though it might be more natural to name this module 'compiler', that
# name is in the stdlib and name collisions with the stdlib tend to produce
# weird problems (often with third-party tools).
#-----------------------------------------------------------------------------
# Copyright (C) 2010-2011 The IPython Development Team.
#
# Distributed under the terms of the BSD License.
#
# The full license is in the file COPYING.txt, distributed with this software.
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
# Stdlib imports
import __future__
from ast import PyCF_ONLY_AST
import codeop
import functools
import hashlib
import linecache
import operator
import time
from contextlib import contextmanager
#-----------------------------------------------------------------------------
# Constants
#-----------------------------------------------------------------------------
# Roughly equal to PyCF_MASK | PyCF_MASK_OBSOLETE as defined in pythonrun.h,
# this is used as a bitmask to extract future-related code flags.
PyCF_MASK = functools.reduce(operator.or_,
(getattr(__future__, fname).compiler_flag
for fname in __future__.all_feature_names))
#-----------------------------------------------------------------------------
# Local utilities
#-----------------------------------------------------------------------------
def code_name(code, number=0):
""" Compute a (probably) unique name for code for caching.
This now expects code to be unicode.
"""
hash_digest = hashlib.sha1(code.encode("utf-8")).hexdigest()
# Include the number and 12 characters of the hash in the name. It's
# pretty much impossible that in a single session we'll have collisions
# even with truncated hashes, and the full one makes tracebacks too long
return '<ipython-input-{0}-{1}>'.format(number, hash_digest[:12])
#-----------------------------------------------------------------------------
# Classes and functions
#-----------------------------------------------------------------------------
class CachingCompiler(codeop.Compile):
"""A compiler that caches code compiled from interactive statements.
"""
def __init__(self):
codeop.Compile.__init__(self)
# 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='<unknown>', symbol='exec'):
"""Parse code to an AST with the current compiler flags active.
Arguments are exactly the same as ast.parse (in the standard library),
and are passed to the built-in compile function."""
return compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1)
def reset_compiler_flags(self):
"""Reset compiler flags to default state."""
# This value is copied from codeop.Compile.__init__, so if that ever
# changes, it will need to be updated.
self.flags = codeop.PyCF_DONT_IMPLY_DEDENT
@property
def compiler_flags(self):
"""Flags currently active in the compilation process.
"""
return self.flags
def get_code_name(self, raw_code, transformed_code, number):
"""Compute filename given the code, and the cell number.
Parameters
----------
raw_code : str
The raw cell code.
transformed_code : str
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.
Returns
-------
The computed filename.
"""
return code_name(transformed_code, number)
def format_code_name(self, name):
"""Return a user-friendly label and name for a code block.
Parameters
----------
name : str
The name for the code block returned from get_code_name
Returns
-------
A (label, name) pair that can be used in tracebacks, or None if the default formatting should be used.
"""
if name in self._filename_map:
return "Cell", "In[%s]" % self._filename_map[name]
def cache(self, transformed_code, number=0, raw_code=None):
"""Make a name for a block of code, and cache the code.
Parameters
----------
transformed_code : str
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.
raw_code : str
The raw code before transformation, if None, set to `transformed_code`.
Returns
-------
The name of the cached code (as a string). Pass this as the filename
argument to compilation, so that tracebacks are correctly hooked up.
"""
if raw_code is None:
raw_code = transformed_code
name = self.get_code_name(raw_code, transformed_code, number)
# Save the execution count
self._filename_map[name] = number
# Since Python 2.5, setting mtime to `None` means the lines will
# never be removed by `linecache.checkcache`. This means all the
# monkeypatching has *never* been necessary, since this code was
# only added in 2010, at which point IPython had already stopped
# supporting Python 2.4.
#
# Note that `linecache.clearcache` and `linecache.updatecache` may
# still remove our code from the cache, but those show explicit
# intent, and we should not try to interfere. Normally the former
# is never called except when out of memory, and the latter is only
# called for lines *not* in the cache.
entry = (
len(transformed_code),
None,
[line + "\n" for line in transformed_code.splitlines()],
name,
)
linecache.cache[name] = entry
return name
@contextmanager
def extra_flags(self, flags):
## bits that we'll set to 1
turn_on_bits = ~self.flags & flags
self.flags = self.flags | flags
try:
yield
finally:
# turn off only the bits we turned on so that something like
# __future__ that set flags stays.
self.flags &= ~turn_on_bits
def check_linecache_ipython(*args):
"""Deprecated since IPython 8.6. Call linecache.checkcache() directly.
It was already not necessary to call this function directly. If no
CachingCompiler had been created, this function would fail badly. If
an instance had been created, this function would've been monkeypatched
into place.
As of IPython 8.6, the monkeypatching has gone away entirely. But there
were still internal callers of this function, so maybe external callers
also existed?
"""
import warnings
warnings.warn(
"Deprecated Since IPython 8.6, Just call linecache.checkcache() directly.",
DeprecationWarning,
stacklevel=2,
)
linecache.checkcache()