inputhooks
This commit is contained in:
parent
1cff18d1c0
commit
791484c132
@ -0,0 +1,41 @@
|
|||||||
|
"""Implementation of all the magic functions built into yap_ipython.
|
||||||
|
"""
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (c) 2012 The yap_ipython Development Team.
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
#
|
||||||
|
# The full license is in the file COPYING.txt, distributed with this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from ..magic import Magics, magics_class
|
||||||
|
from .auto import AutoMagics
|
||||||
|
from .basic import BasicMagics
|
||||||
|
from .code import CodeMagics, MacroToEdit
|
||||||
|
from .config import ConfigMagics
|
||||||
|
from .display import DisplayMagics
|
||||||
|
from .execution import ExecutionMagics
|
||||||
|
from .extension import ExtensionMagics
|
||||||
|
from .history import HistoryMagics
|
||||||
|
from .logging import LoggingMagics
|
||||||
|
from .namespace import NamespaceMagics
|
||||||
|
from .osm import OSMagics
|
||||||
|
from .pylab import PylabMagics
|
||||||
|
from .script import ScriptMagics
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Magic implementation classes
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@magics_class
|
||||||
|
class UserMagics(Magics):
|
||||||
|
"""Placeholder for user-defined magics to be added at runtime.
|
||||||
|
|
||||||
|
All magics are eventually merged into a single namespace at runtime, but we
|
||||||
|
use this class to isolate the magics defined dynamically by the user into
|
||||||
|
their own class.
|
||||||
|
"""
|
128
packages/python/yap_kernel/yap_ipython/core/magics/auto.py
Normal file
128
packages/python/yap_kernel/yap_ipython/core/magics/auto.py
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
"""Implementation of magic functions that control various automatic behaviors.
|
||||||
|
"""
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (c) 2012 The yap_ipython Development Team.
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
#
|
||||||
|
# The full license is in the file COPYING.txt, distributed with this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Our own packages
|
||||||
|
from yap_ipython.core.magic import Bunch, Magics, magics_class, line_magic
|
||||||
|
from yap_ipython.testing.skipdoctest import skip_doctest
|
||||||
|
from logging import error
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Magic implementation classes
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@magics_class
|
||||||
|
class AutoMagics(Magics):
|
||||||
|
"""Magics that control various autoX behaviors."""
|
||||||
|
|
||||||
|
def __init__(self, shell):
|
||||||
|
super(AutoMagics, self).__init__(shell)
|
||||||
|
# namespace for holding state we may need
|
||||||
|
self._magic_state = Bunch()
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def automagic(self, parameter_s=''):
|
||||||
|
"""Make magic functions callable without having to type the initial %.
|
||||||
|
|
||||||
|
Without arguments toggles on/off (when off, you must call it as
|
||||||
|
%automagic, of course). With arguments it sets the value, and you can
|
||||||
|
use any of (case insensitive):
|
||||||
|
|
||||||
|
- on, 1, True: to activate
|
||||||
|
|
||||||
|
- off, 0, False: to deactivate.
|
||||||
|
|
||||||
|
Note that magic functions have lowest priority, so if there's a
|
||||||
|
variable whose name collides with that of a magic fn, automagic won't
|
||||||
|
work for that function (you get the variable instead). However, if you
|
||||||
|
delete the variable (del var), the previously shadowed magic function
|
||||||
|
becomes visible to automagic again."""
|
||||||
|
|
||||||
|
arg = parameter_s.lower()
|
||||||
|
mman = self.shell.magics_manager
|
||||||
|
if arg in ('on', '1', 'true'):
|
||||||
|
val = True
|
||||||
|
elif arg in ('off', '0', 'false'):
|
||||||
|
val = False
|
||||||
|
else:
|
||||||
|
val = not mman.auto_magic
|
||||||
|
mman.auto_magic = val
|
||||||
|
print('\n' + self.shell.magics_manager.auto_status())
|
||||||
|
|
||||||
|
@skip_doctest
|
||||||
|
@line_magic
|
||||||
|
def autocall(self, parameter_s=''):
|
||||||
|
"""Make functions callable without having to type parentheses.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
%autocall [mode]
|
||||||
|
|
||||||
|
The mode can be one of: 0->Off, 1->Smart, 2->Full. If not given, the
|
||||||
|
value is toggled on and off (remembering the previous state).
|
||||||
|
|
||||||
|
In more detail, these values mean:
|
||||||
|
|
||||||
|
0 -> fully disabled
|
||||||
|
|
||||||
|
1 -> active, but do not apply if there are no arguments on the line.
|
||||||
|
|
||||||
|
In this mode, you get::
|
||||||
|
|
||||||
|
In [1]: callable
|
||||||
|
Out[1]: <built-in function callable>
|
||||||
|
|
||||||
|
In [2]: callable 'hello'
|
||||||
|
------> callable('hello')
|
||||||
|
Out[2]: False
|
||||||
|
|
||||||
|
2 -> Active always. Even if no arguments are present, the callable
|
||||||
|
object is called::
|
||||||
|
|
||||||
|
In [2]: float
|
||||||
|
------> float()
|
||||||
|
Out[2]: 0.0
|
||||||
|
|
||||||
|
Note that even with autocall off, you can still use '/' at the start of
|
||||||
|
a line to treat the first argument on the command line as a function
|
||||||
|
and add parentheses to it::
|
||||||
|
|
||||||
|
In [8]: /str 43
|
||||||
|
------> str(43)
|
||||||
|
Out[8]: '43'
|
||||||
|
|
||||||
|
# all-random (note for auto-testing)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if parameter_s:
|
||||||
|
arg = int(parameter_s)
|
||||||
|
else:
|
||||||
|
arg = 'toggle'
|
||||||
|
|
||||||
|
if not arg in (0, 1, 2, 'toggle'):
|
||||||
|
error('Valid modes: (0->Off, 1->Smart, 2->Full')
|
||||||
|
return
|
||||||
|
|
||||||
|
if arg in (0, 1, 2):
|
||||||
|
self.shell.autocall = arg
|
||||||
|
else: # toggle
|
||||||
|
if self.shell.autocall:
|
||||||
|
self._magic_state.autocall_save = self.shell.autocall
|
||||||
|
self.shell.autocall = 0
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.shell.autocall = self._magic_state.autocall_save
|
||||||
|
except AttributeError:
|
||||||
|
self.shell.autocall = self._magic_state.autocall_save = 1
|
||||||
|
|
||||||
|
print("Automatic calling is:",['OFF','Smart','Full'][self.shell.autocall])
|
614
packages/python/yap_kernel/yap_ipython/core/magics/basic.py
Normal file
614
packages/python/yap_kernel/yap_ipython/core/magics/basic.py
Normal file
@ -0,0 +1,614 @@
|
|||||||
|
"""Implementation of basic magic functions."""
|
||||||
|
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import textwrap
|
||||||
|
import io
|
||||||
|
import sys
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
from yap_ipython.core import magic_arguments, page
|
||||||
|
from yap_ipython.core.error import UsageError
|
||||||
|
from yap_ipython.core.magic import Magics, magics_class, line_magic, magic_escapes
|
||||||
|
from yap_ipython.utils.text import format_screen, dedent, indent
|
||||||
|
from yap_ipython.testing.skipdoctest import skip_doctest
|
||||||
|
from yap_ipython.utils.ipstruct import Struct
|
||||||
|
from warnings import warn
|
||||||
|
from logging import error
|
||||||
|
|
||||||
|
|
||||||
|
class MagicsDisplay(object):
|
||||||
|
def __init__(self, magics_manager, ignore=None):
|
||||||
|
self.ignore = ignore if ignore else []
|
||||||
|
self.magics_manager = magics_manager
|
||||||
|
|
||||||
|
def _lsmagic(self):
|
||||||
|
"""The main implementation of the %lsmagic"""
|
||||||
|
mesc = magic_escapes['line']
|
||||||
|
cesc = magic_escapes['cell']
|
||||||
|
mman = self.magics_manager
|
||||||
|
magics = mman.lsmagic()
|
||||||
|
out = ['Available line magics:',
|
||||||
|
mesc + (' '+mesc).join(sorted([m for m,v in magics['line'].items() if (v not in self.ignore)])),
|
||||||
|
'',
|
||||||
|
'Available cell magics:',
|
||||||
|
cesc + (' '+cesc).join(sorted([m for m,v in magics['cell'].items() if (v not in self.ignore)])),
|
||||||
|
'',
|
||||||
|
mman.auto_status()]
|
||||||
|
return '\n'.join(out)
|
||||||
|
|
||||||
|
def _repr_pretty_(self, p, cycle):
|
||||||
|
p.text(self._lsmagic())
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self._lsmagic()
|
||||||
|
|
||||||
|
def _jsonable(self):
|
||||||
|
"""turn magics dict into jsonable dict of the same structure
|
||||||
|
|
||||||
|
replaces object instances with their class names as strings
|
||||||
|
"""
|
||||||
|
magic_dict = {}
|
||||||
|
mman = self.magics_manager
|
||||||
|
magics = mman.lsmagic()
|
||||||
|
for key, subdict in magics.items():
|
||||||
|
d = {}
|
||||||
|
magic_dict[key] = d
|
||||||
|
for name, obj in subdict.items():
|
||||||
|
try:
|
||||||
|
classname = obj.__self__.__class__.__name__
|
||||||
|
except AttributeError:
|
||||||
|
classname = 'Other'
|
||||||
|
|
||||||
|
d[name] = classname
|
||||||
|
return magic_dict
|
||||||
|
|
||||||
|
def _repr_json_(self):
|
||||||
|
return self._jsonable()
|
||||||
|
|
||||||
|
|
||||||
|
@magics_class
|
||||||
|
class BasicMagics(Magics):
|
||||||
|
"""Magics that provide central yap_ipython functionality.
|
||||||
|
|
||||||
|
These are various magics that don't fit into specific categories but that
|
||||||
|
are all part of the base 'yap_ipython experience'."""
|
||||||
|
|
||||||
|
@magic_arguments.magic_arguments()
|
||||||
|
@magic_arguments.argument(
|
||||||
|
'-l', '--line', action='store_true',
|
||||||
|
help="""Create a line magic alias."""
|
||||||
|
)
|
||||||
|
@magic_arguments.argument(
|
||||||
|
'-c', '--cell', action='store_true',
|
||||||
|
help="""Create a cell magic alias."""
|
||||||
|
)
|
||||||
|
@magic_arguments.argument(
|
||||||
|
'name',
|
||||||
|
help="""Name of the magic to be created."""
|
||||||
|
)
|
||||||
|
@magic_arguments.argument(
|
||||||
|
'target',
|
||||||
|
help="""Name of the existing line or cell magic."""
|
||||||
|
)
|
||||||
|
@magic_arguments.argument(
|
||||||
|
'-p', '--params', default=None,
|
||||||
|
help="""Parameters passed to the magic function."""
|
||||||
|
)
|
||||||
|
@line_magic
|
||||||
|
def alias_magic(self, line=''):
|
||||||
|
"""Create an alias for an existing line or cell magic.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
::
|
||||||
|
|
||||||
|
In [1]: %alias_magic t timeit
|
||||||
|
Created `%t` as an alias for `%timeit`.
|
||||||
|
Created `%%t` as an alias for `%%timeit`.
|
||||||
|
|
||||||
|
In [2]: %t -n1 pass
|
||||||
|
1 loops, best of 3: 954 ns per loop
|
||||||
|
|
||||||
|
In [3]: %%t -n1
|
||||||
|
...: pass
|
||||||
|
...:
|
||||||
|
1 loops, best of 3: 954 ns per loop
|
||||||
|
|
||||||
|
In [4]: %alias_magic --cell whereami pwd
|
||||||
|
UsageError: Cell magic function `%%pwd` not found.
|
||||||
|
In [5]: %alias_magic --line whereami pwd
|
||||||
|
Created `%whereami` as an alias for `%pwd`.
|
||||||
|
|
||||||
|
In [6]: %whereami
|
||||||
|
Out[6]: u'/home/testuser'
|
||||||
|
|
||||||
|
In [7]: %alias_magic h history -p "-l 30" --line
|
||||||
|
Created `%h` as an alias for `%history -l 30`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
args = magic_arguments.parse_argstring(self.alias_magic, line)
|
||||||
|
shell = self.shell
|
||||||
|
mman = self.shell.magics_manager
|
||||||
|
escs = ''.join(magic_escapes.values())
|
||||||
|
|
||||||
|
target = args.target.lstrip(escs)
|
||||||
|
name = args.name.lstrip(escs)
|
||||||
|
|
||||||
|
params = args.params
|
||||||
|
if (params and
|
||||||
|
((params.startswith('"') and params.endswith('"'))
|
||||||
|
or (params.startswith("'") and params.endswith("'")))):
|
||||||
|
params = params[1:-1]
|
||||||
|
|
||||||
|
# Find the requested magics.
|
||||||
|
m_line = shell.find_magic(target, 'line')
|
||||||
|
m_cell = shell.find_magic(target, 'cell')
|
||||||
|
if args.line and m_line is None:
|
||||||
|
raise UsageError('Line magic function `%s%s` not found.' %
|
||||||
|
(magic_escapes['line'], target))
|
||||||
|
if args.cell and m_cell is None:
|
||||||
|
raise UsageError('Cell magic function `%s%s` not found.' %
|
||||||
|
(magic_escapes['cell'], target))
|
||||||
|
|
||||||
|
# If --line and --cell are not specified, default to the ones
|
||||||
|
# that are available.
|
||||||
|
if not args.line and not args.cell:
|
||||||
|
if not m_line and not m_cell:
|
||||||
|
raise UsageError(
|
||||||
|
'No line or cell magic with name `%s` found.' % target
|
||||||
|
)
|
||||||
|
args.line = bool(m_line)
|
||||||
|
args.cell = bool(m_cell)
|
||||||
|
|
||||||
|
params_str = "" if params is None else " " + params
|
||||||
|
|
||||||
|
if args.line:
|
||||||
|
mman.register_alias(name, target, 'line', params)
|
||||||
|
print('Created `%s%s` as an alias for `%s%s%s`.' % (
|
||||||
|
magic_escapes['line'], name,
|
||||||
|
magic_escapes['line'], target, params_str))
|
||||||
|
|
||||||
|
if args.cell:
|
||||||
|
mman.register_alias(name, target, 'cell', params)
|
||||||
|
print('Created `%s%s` as an alias for `%s%s%s`.' % (
|
||||||
|
magic_escapes['cell'], name,
|
||||||
|
magic_escapes['cell'], target, params_str))
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def lsmagic(self, parameter_s=''):
|
||||||
|
"""List currently available magic functions."""
|
||||||
|
return MagicsDisplay(self.shell.magics_manager, ignore=[self.pip])
|
||||||
|
|
||||||
|
def _magic_docs(self, brief=False, rest=False):
|
||||||
|
"""Return docstrings from magic functions."""
|
||||||
|
mman = self.shell.magics_manager
|
||||||
|
docs = mman.lsmagic_docs(brief, missing='No documentation')
|
||||||
|
|
||||||
|
if rest:
|
||||||
|
format_string = '**%s%s**::\n\n%s\n\n'
|
||||||
|
else:
|
||||||
|
format_string = '%s%s:\n%s\n'
|
||||||
|
|
||||||
|
return ''.join(
|
||||||
|
[format_string % (magic_escapes['line'], fname,
|
||||||
|
indent(dedent(fndoc)))
|
||||||
|
for fname, fndoc in sorted(docs['line'].items())]
|
||||||
|
+
|
||||||
|
[format_string % (magic_escapes['cell'], fname,
|
||||||
|
indent(dedent(fndoc)))
|
||||||
|
for fname, fndoc in sorted(docs['cell'].items())]
|
||||||
|
)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def magic(self, parameter_s=''):
|
||||||
|
"""Print information about the magic function system.
|
||||||
|
|
||||||
|
Supported formats: -latex, -brief, -rest
|
||||||
|
"""
|
||||||
|
|
||||||
|
mode = ''
|
||||||
|
try:
|
||||||
|
mode = parameter_s.split()[0][1:]
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
brief = (mode == 'brief')
|
||||||
|
rest = (mode == 'rest')
|
||||||
|
magic_docs = self._magic_docs(brief, rest)
|
||||||
|
|
||||||
|
if mode == 'latex':
|
||||||
|
print(self.format_latex(magic_docs))
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
magic_docs = format_screen(magic_docs)
|
||||||
|
|
||||||
|
out = ["""
|
||||||
|
yap_ipython's 'magic' functions
|
||||||
|
===========================
|
||||||
|
|
||||||
|
The magic function system provides a series of functions which allow you to
|
||||||
|
control the behavior of yap_ipython itself, plus a lot of system-type
|
||||||
|
features. There are two kinds of magics, line-oriented and cell-oriented.
|
||||||
|
|
||||||
|
Line magics are prefixed with the % character and work much like OS
|
||||||
|
command-line calls: they get as an argument the rest of the line, where
|
||||||
|
arguments are passed without parentheses or quotes. For example, this will
|
||||||
|
time the given statement::
|
||||||
|
|
||||||
|
%timeit range(1000)
|
||||||
|
|
||||||
|
Cell magics are prefixed with a double %%, and they are functions that get as
|
||||||
|
an argument not only the rest of the line, but also the lines below it in a
|
||||||
|
separate argument. These magics are called with two arguments: the rest of the
|
||||||
|
call line and the body of the cell, consisting of the lines below the first.
|
||||||
|
For example::
|
||||||
|
|
||||||
|
%%timeit x = numpy.random.randn((100, 100))
|
||||||
|
numpy.linalg.svd(x)
|
||||||
|
|
||||||
|
will time the execution of the numpy svd routine, running the assignment of x
|
||||||
|
as part of the setup phase, which is not timed.
|
||||||
|
|
||||||
|
In a line-oriented client (the terminal or Qt console yap_ipython), starting a new
|
||||||
|
input with %% will automatically enter cell mode, and yap_ipython will continue
|
||||||
|
reading input until a blank line is given. In the notebook, simply type the
|
||||||
|
whole cell as one entity, but keep in mind that the %% escape can only be at
|
||||||
|
the very start of the cell.
|
||||||
|
|
||||||
|
NOTE: If you have 'automagic' enabled (via the command line option or with the
|
||||||
|
%automagic function), you don't need to type in the % explicitly for line
|
||||||
|
magics; cell magics always require an explicit '%%' escape. By default,
|
||||||
|
yap_ipython ships with automagic on, so you should only rarely need the % escape.
|
||||||
|
|
||||||
|
Example: typing '%cd mydir' (without the quotes) changes your working directory
|
||||||
|
to 'mydir', if it exists.
|
||||||
|
|
||||||
|
For a list of the available magic functions, use %lsmagic. For a description
|
||||||
|
of any of them, type %magic_name?, e.g. '%cd?'.
|
||||||
|
|
||||||
|
Currently the magic system has the following functions:""",
|
||||||
|
magic_docs,
|
||||||
|
"Summary of magic functions (from %slsmagic):" % magic_escapes['line'],
|
||||||
|
str(self.lsmagic()),
|
||||||
|
]
|
||||||
|
page.page('\n'.join(out))
|
||||||
|
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def page(self, parameter_s=''):
|
||||||
|
"""Pretty print the object and display it through a pager.
|
||||||
|
|
||||||
|
%page [options] OBJECT
|
||||||
|
|
||||||
|
If no object is given, use _ (last output).
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-r: page str(object), don't pretty-print it."""
|
||||||
|
|
||||||
|
# After a function contributed by Olivier Aubert, slightly modified.
|
||||||
|
|
||||||
|
# Process options/args
|
||||||
|
opts, args = self.parse_options(parameter_s, 'r')
|
||||||
|
raw = 'r' in opts
|
||||||
|
|
||||||
|
oname = args and args or '_'
|
||||||
|
info = self.shell._ofind(oname)
|
||||||
|
if info['found']:
|
||||||
|
txt = (raw and str or pformat)( info['obj'] )
|
||||||
|
page.page(txt)
|
||||||
|
else:
|
||||||
|
print('Object `%s` not found' % oname)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def profile(self, parameter_s=''):
|
||||||
|
"""DEPRECATED since yap_ipython 2.0.
|
||||||
|
|
||||||
|
Raise `UsageError`. To profile code use the :magic:`prun` magic.
|
||||||
|
|
||||||
|
|
||||||
|
See Also
|
||||||
|
--------
|
||||||
|
prun : run code using the Python profiler (:magic:`prun`)
|
||||||
|
"""
|
||||||
|
raise UsageError("The `%profile` magic has been deprecated since yap_ipython 2.0. "
|
||||||
|
"and removed in yap_ipython 6.0. Please use the value of `get_ipython().profile` instead "
|
||||||
|
"to see current profile in use. Perhaps you meant to use `%prun` to profile code?")
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def pprint(self, parameter_s=''):
|
||||||
|
"""Toggle pretty printing on/off."""
|
||||||
|
ptformatter = self.shell.display_formatter.formatters['text/plain']
|
||||||
|
ptformatter.pprint = bool(1 - ptformatter.pprint)
|
||||||
|
print('Pretty printing has been turned',
|
||||||
|
['OFF','ON'][ptformatter.pprint])
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def colors(self, parameter_s=''):
|
||||||
|
"""Switch color scheme for prompts, info system and exception handlers.
|
||||||
|
|
||||||
|
Currently implemented schemes: NoColor, Linux, LightBG.
|
||||||
|
|
||||||
|
Color scheme names are not case-sensitive.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
To get a plain black and white terminal::
|
||||||
|
|
||||||
|
%colors nocolor
|
||||||
|
"""
|
||||||
|
def color_switch_err(name):
|
||||||
|
warn('Error changing %s color schemes.\n%s' %
|
||||||
|
(name, sys.exc_info()[1]), stacklevel=2)
|
||||||
|
|
||||||
|
|
||||||
|
new_scheme = parameter_s.strip()
|
||||||
|
if not new_scheme:
|
||||||
|
raise UsageError(
|
||||||
|
"%colors: you must specify a color scheme. See '%colors?'")
|
||||||
|
# local shortcut
|
||||||
|
shell = self.shell
|
||||||
|
|
||||||
|
# Set shell colour scheme
|
||||||
|
try:
|
||||||
|
shell.colors = new_scheme
|
||||||
|
shell.refresh_style()
|
||||||
|
except:
|
||||||
|
color_switch_err('shell')
|
||||||
|
|
||||||
|
# Set exception colors
|
||||||
|
try:
|
||||||
|
shell.InteractiveTB.set_colors(scheme = new_scheme)
|
||||||
|
shell.SyntaxTB.set_colors(scheme = new_scheme)
|
||||||
|
except:
|
||||||
|
color_switch_err('exception')
|
||||||
|
|
||||||
|
# Set info (for 'object?') colors
|
||||||
|
if shell.color_info:
|
||||||
|
try:
|
||||||
|
shell.inspector.set_active_scheme(new_scheme)
|
||||||
|
except:
|
||||||
|
color_switch_err('object inspector')
|
||||||
|
else:
|
||||||
|
shell.inspector.set_active_scheme('NoColor')
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def xmode(self, parameter_s=''):
|
||||||
|
"""Switch modes for the exception handlers.
|
||||||
|
|
||||||
|
Valid modes: Plain, Context and Verbose.
|
||||||
|
|
||||||
|
If called without arguments, acts as a toggle."""
|
||||||
|
|
||||||
|
def xmode_switch_err(name):
|
||||||
|
warn('Error changing %s exception modes.\n%s' %
|
||||||
|
(name,sys.exc_info()[1]))
|
||||||
|
|
||||||
|
shell = self.shell
|
||||||
|
new_mode = parameter_s.strip().capitalize()
|
||||||
|
try:
|
||||||
|
shell.InteractiveTB.set_mode(mode=new_mode)
|
||||||
|
print('Exception reporting mode:',shell.InteractiveTB.mode)
|
||||||
|
except:
|
||||||
|
xmode_switch_err('user')
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def pip(self, args=''):
|
||||||
|
"""
|
||||||
|
Intercept usage of ``pip`` in yap_ipython and direct user to run command outside of yap_ipython.
|
||||||
|
"""
|
||||||
|
print(textwrap.dedent('''
|
||||||
|
The following command must be run outside of the yap_ipython shell:
|
||||||
|
|
||||||
|
$ pip {args}
|
||||||
|
|
||||||
|
The Python package manager (pip) can only be used from outside of yap_ipython.
|
||||||
|
Please reissue the `pip` command in a separate terminal or command prompt.
|
||||||
|
|
||||||
|
See the Python documentation for more information on how to install packages:
|
||||||
|
|
||||||
|
https://docs.python.org/3/installing/'''.format(args=args)))
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def quickref(self, arg):
|
||||||
|
""" Show a quick reference sheet """
|
||||||
|
from yap_ipython.core.usage import quick_reference
|
||||||
|
qr = quick_reference + self._magic_docs(brief=True)
|
||||||
|
page.page(qr)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def doctest_mode(self, parameter_s=''):
|
||||||
|
"""Toggle doctest mode on and off.
|
||||||
|
|
||||||
|
This mode is intended to make yap_ipython behave as much as possible like a
|
||||||
|
plain Python shell, from the perspective of how its prompts, exceptions
|
||||||
|
and output look. This makes it easy to copy and paste parts of a
|
||||||
|
session into doctests. It does so by:
|
||||||
|
|
||||||
|
- Changing the prompts to the classic ``>>>`` ones.
|
||||||
|
- Changing the exception reporting mode to 'Plain'.
|
||||||
|
- Disabling pretty-printing of output.
|
||||||
|
|
||||||
|
Note that yap_ipython also supports the pasting of code snippets that have
|
||||||
|
leading '>>>' and '...' prompts in them. This means that you can paste
|
||||||
|
doctests from files or docstrings (even if they have leading
|
||||||
|
whitespace), and the code will execute correctly. You can then use
|
||||||
|
'%history -t' to see the translated history; this will give you the
|
||||||
|
input after removal of all the leading prompts and whitespace, which
|
||||||
|
can be pasted back into an editor.
|
||||||
|
|
||||||
|
With these features, you can switch into this mode easily whenever you
|
||||||
|
need to do testing and changes to doctests, without having to leave
|
||||||
|
your existing yap_ipython session.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Shorthands
|
||||||
|
shell = self.shell
|
||||||
|
meta = shell.meta
|
||||||
|
disp_formatter = self.shell.display_formatter
|
||||||
|
ptformatter = disp_formatter.formatters['text/plain']
|
||||||
|
# dstore is a data store kept in the instance metadata bag to track any
|
||||||
|
# changes we make, so we can undo them later.
|
||||||
|
dstore = meta.setdefault('doctest_mode',Struct())
|
||||||
|
save_dstore = dstore.setdefault
|
||||||
|
|
||||||
|
# save a few values we'll need to recover later
|
||||||
|
mode = save_dstore('mode',False)
|
||||||
|
save_dstore('rc_pprint',ptformatter.pprint)
|
||||||
|
save_dstore('xmode',shell.InteractiveTB.mode)
|
||||||
|
save_dstore('rc_separate_out',shell.separate_out)
|
||||||
|
save_dstore('rc_separate_out2',shell.separate_out2)
|
||||||
|
save_dstore('rc_separate_in',shell.separate_in)
|
||||||
|
save_dstore('rc_active_types',disp_formatter.active_types)
|
||||||
|
|
||||||
|
if not mode:
|
||||||
|
# turn on
|
||||||
|
|
||||||
|
# Prompt separators like plain python
|
||||||
|
shell.separate_in = ''
|
||||||
|
shell.separate_out = ''
|
||||||
|
shell.separate_out2 = ''
|
||||||
|
|
||||||
|
|
||||||
|
ptformatter.pprint = False
|
||||||
|
disp_formatter.active_types = ['text/plain']
|
||||||
|
|
||||||
|
shell.magic('xmode Plain')
|
||||||
|
else:
|
||||||
|
# turn off
|
||||||
|
shell.separate_in = dstore.rc_separate_in
|
||||||
|
|
||||||
|
shell.separate_out = dstore.rc_separate_out
|
||||||
|
shell.separate_out2 = dstore.rc_separate_out2
|
||||||
|
|
||||||
|
ptformatter.pprint = dstore.rc_pprint
|
||||||
|
disp_formatter.active_types = dstore.rc_active_types
|
||||||
|
|
||||||
|
shell.magic('xmode ' + dstore.xmode)
|
||||||
|
|
||||||
|
# mode here is the state before we switch; switch_doctest_mode takes
|
||||||
|
# the mode we're switching to.
|
||||||
|
shell.switch_doctest_mode(not mode)
|
||||||
|
|
||||||
|
# Store new mode and inform
|
||||||
|
dstore.mode = bool(not mode)
|
||||||
|
mode_label = ['OFF','ON'][dstore.mode]
|
||||||
|
print('Doctest mode is:', mode_label)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def gui(self, parameter_s=''):
|
||||||
|
"""Enable or disable yap_ipython GUI event loop integration.
|
||||||
|
|
||||||
|
%gui [GUINAME]
|
||||||
|
|
||||||
|
This magic replaces yap_ipython's threaded shells that were activated
|
||||||
|
using the (pylab/wthread/etc.) command line flags. GUI toolkits
|
||||||
|
can now be enabled at runtime and keyboard
|
||||||
|
interrupts should work without any problems. The following toolkits
|
||||||
|
are supported: wxPython, PyQt4, PyGTK, Tk and Cocoa (OSX)::
|
||||||
|
|
||||||
|
%gui wx # enable wxPython event loop integration
|
||||||
|
%gui qt4|qt # enable PyQt4 event loop integration
|
||||||
|
%gui qt5 # enable PyQt5 event loop integration
|
||||||
|
%gui gtk # enable PyGTK event loop integration
|
||||||
|
%gui gtk3 # enable Gtk3 event loop integration
|
||||||
|
%gui tk # enable Tk event loop integration
|
||||||
|
%gui osx # enable Cocoa event loop integration
|
||||||
|
# (requires %matplotlib 1.1)
|
||||||
|
%gui # disable all event loop integration
|
||||||
|
|
||||||
|
WARNING: after any of these has been called you can simply create
|
||||||
|
an application object, but DO NOT start the event loop yourself, as
|
||||||
|
we have already handled that.
|
||||||
|
"""
|
||||||
|
opts, arg = self.parse_options(parameter_s, '')
|
||||||
|
if arg=='': arg = None
|
||||||
|
try:
|
||||||
|
return self.shell.enable_gui(arg)
|
||||||
|
except Exception as e:
|
||||||
|
# print simple error message, rather than traceback if we can't
|
||||||
|
# hook up the GUI
|
||||||
|
error(str(e))
|
||||||
|
|
||||||
|
@skip_doctest
|
||||||
|
@line_magic
|
||||||
|
def precision(self, s=''):
|
||||||
|
"""Set floating point precision for pretty printing.
|
||||||
|
|
||||||
|
Can set either integer precision or a format string.
|
||||||
|
|
||||||
|
If numpy has been imported and precision is an int,
|
||||||
|
numpy display precision will also be set, via ``numpy.set_printoptions``.
|
||||||
|
|
||||||
|
If no argument is given, defaults will be restored.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
::
|
||||||
|
|
||||||
|
In [1]: from math import pi
|
||||||
|
|
||||||
|
In [2]: %precision 3
|
||||||
|
Out[2]: u'%.3f'
|
||||||
|
|
||||||
|
In [3]: pi
|
||||||
|
Out[3]: 3.142
|
||||||
|
|
||||||
|
In [4]: %precision %i
|
||||||
|
Out[4]: u'%i'
|
||||||
|
|
||||||
|
In [5]: pi
|
||||||
|
Out[5]: 3
|
||||||
|
|
||||||
|
In [6]: %precision %e
|
||||||
|
Out[6]: u'%e'
|
||||||
|
|
||||||
|
In [7]: pi**10
|
||||||
|
Out[7]: 9.364805e+04
|
||||||
|
|
||||||
|
In [8]: %precision
|
||||||
|
Out[8]: u'%r'
|
||||||
|
|
||||||
|
In [9]: pi**10
|
||||||
|
Out[9]: 93648.047476082982
|
||||||
|
"""
|
||||||
|
ptformatter = self.shell.display_formatter.formatters['text/plain']
|
||||||
|
ptformatter.float_precision = s
|
||||||
|
return ptformatter.float_format
|
||||||
|
|
||||||
|
@magic_arguments.magic_arguments()
|
||||||
|
@magic_arguments.argument(
|
||||||
|
'-e', '--export', action='store_true', default=False,
|
||||||
|
help=argparse.SUPPRESS
|
||||||
|
)
|
||||||
|
@magic_arguments.argument(
|
||||||
|
'filename', type=str,
|
||||||
|
help='Notebook name or filename'
|
||||||
|
)
|
||||||
|
@line_magic
|
||||||
|
def notebook(self, s):
|
||||||
|
"""Export and convert yap_ipython notebooks.
|
||||||
|
|
||||||
|
This function can export the current yap_ipython history to a notebook file.
|
||||||
|
For example, to export the history to "foo.ipynb" do "%notebook foo.ipynb".
|
||||||
|
|
||||||
|
The -e or --export flag is deprecated in yap_ipython 5.2, and will be
|
||||||
|
removed in the future.
|
||||||
|
"""
|
||||||
|
args = magic_arguments.parse_argstring(self.notebook, s)
|
||||||
|
|
||||||
|
from nbformat import write, v4
|
||||||
|
|
||||||
|
cells = []
|
||||||
|
hist = list(self.shell.history_manager.get_range())
|
||||||
|
if(len(hist)<=1):
|
||||||
|
raise ValueError('History is empty, cannot export')
|
||||||
|
for session, execution_count, source in hist[:-1]:
|
||||||
|
cells.append(v4.new_code_cell(
|
||||||
|
execution_count=execution_count,
|
||||||
|
source=source
|
||||||
|
))
|
||||||
|
nb = v4.new_notebook(cells=cells)
|
||||||
|
with io.open(args.filename, 'w', encoding='utf-8') as f:
|
||||||
|
write(nb, f, version=4)
|
740
packages/python/yap_kernel/yap_ipython/core/magics/code.py
Normal file
740
packages/python/yap_kernel/yap_ipython/core/magics/code.py
Normal file
@ -0,0 +1,740 @@
|
|||||||
|
"""Implementation of code management magic functions.
|
||||||
|
"""
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (c) 2012 The yap_ipython Development Team.
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
#
|
||||||
|
# The full license is in the file COPYING.txt, distributed with this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Stdlib
|
||||||
|
import inspect
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import ast
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
# Our own packages
|
||||||
|
from yap_ipython.core.error import TryNext, StdinNotImplementedError, UsageError
|
||||||
|
from yap_ipython.core.macro import Macro
|
||||||
|
from yap_ipython.core.magic import Magics, magics_class, line_magic
|
||||||
|
from yap_ipython.core.oinspect import find_file, find_source_lines
|
||||||
|
from yap_ipython.testing.skipdoctest import skip_doctest
|
||||||
|
from yap_ipython.utils import py3compat
|
||||||
|
from yap_ipython.utils.contexts import preserve_keys
|
||||||
|
from yap_ipython.utils.path import get_py_filename
|
||||||
|
from warnings import warn
|
||||||
|
from logging import error
|
||||||
|
from yap_ipython.utils.text import get_text_list
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Magic implementation classes
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Used for exception handling in magic_edit
|
||||||
|
class MacroToEdit(ValueError): pass
|
||||||
|
|
||||||
|
ipython_input_pat = re.compile(r"<ipython\-input\-(\d+)-[a-z\d]+>$")
|
||||||
|
|
||||||
|
# To match, e.g. 8-10 1:5 :10 3-
|
||||||
|
range_re = re.compile(r"""
|
||||||
|
(?P<start>\d+)?
|
||||||
|
((?P<sep>[\-:])
|
||||||
|
(?P<end>\d+)?)?
|
||||||
|
$""", re.VERBOSE)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_code_ranges(ranges_str):
|
||||||
|
"""Turn a string of range for %%load into 2-tuples of (start, stop)
|
||||||
|
ready to use as a slice of the content splitted by lines.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
list(extract_input_ranges("5-10 2"))
|
||||||
|
[(4, 10), (1, 2)]
|
||||||
|
"""
|
||||||
|
for range_str in ranges_str.split():
|
||||||
|
rmatch = range_re.match(range_str)
|
||||||
|
if not rmatch:
|
||||||
|
continue
|
||||||
|
sep = rmatch.group("sep")
|
||||||
|
start = rmatch.group("start")
|
||||||
|
end = rmatch.group("end")
|
||||||
|
|
||||||
|
if sep == '-':
|
||||||
|
start = int(start) - 1 if start else None
|
||||||
|
end = int(end) if end else None
|
||||||
|
elif sep == ':':
|
||||||
|
start = int(start) - 1 if start else None
|
||||||
|
end = int(end) - 1 if end else None
|
||||||
|
else:
|
||||||
|
end = int(start)
|
||||||
|
start = int(start) - 1
|
||||||
|
yield (start, end)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_symbols(code, symbols):
|
||||||
|
"""
|
||||||
|
Return a tuple (blocks, not_found)
|
||||||
|
where ``blocks`` is a list of code fragments
|
||||||
|
for each symbol parsed from code, and ``not_found`` are
|
||||||
|
symbols not found in the code.
|
||||||
|
|
||||||
|
For example::
|
||||||
|
|
||||||
|
In [1]: code = '''a = 10
|
||||||
|
...: def b(): return 42
|
||||||
|
...: class A: pass'''
|
||||||
|
|
||||||
|
In [2]: extract_symbols(code, 'A,b,z')
|
||||||
|
Out[2]: (['class A: pass\\n', 'def b(): return 42\\n'], ['z'])
|
||||||
|
"""
|
||||||
|
symbols = symbols.split(',')
|
||||||
|
|
||||||
|
# this will raise SyntaxError if code isn't valid Python
|
||||||
|
py_code = ast.parse(code)
|
||||||
|
|
||||||
|
marks = [(getattr(s, 'name', None), s.lineno) for s in py_code.body]
|
||||||
|
code = code.split('\n')
|
||||||
|
|
||||||
|
symbols_lines = {}
|
||||||
|
|
||||||
|
# we already know the start_lineno of each symbol (marks).
|
||||||
|
# To find each end_lineno, we traverse in reverse order until each
|
||||||
|
# non-blank line
|
||||||
|
end = len(code)
|
||||||
|
for name, start in reversed(marks):
|
||||||
|
while not code[end - 1].strip():
|
||||||
|
end -= 1
|
||||||
|
if name:
|
||||||
|
symbols_lines[name] = (start - 1, end)
|
||||||
|
end = start - 1
|
||||||
|
|
||||||
|
# Now symbols_lines is a map
|
||||||
|
# {'symbol_name': (start_lineno, end_lineno), ...}
|
||||||
|
|
||||||
|
# fill a list with chunks of codes for each requested symbol
|
||||||
|
blocks = []
|
||||||
|
not_found = []
|
||||||
|
for symbol in symbols:
|
||||||
|
if symbol in symbols_lines:
|
||||||
|
start, end = symbols_lines[symbol]
|
||||||
|
blocks.append('\n'.join(code[start:end]) + '\n')
|
||||||
|
else:
|
||||||
|
not_found.append(symbol)
|
||||||
|
|
||||||
|
return blocks, not_found
|
||||||
|
|
||||||
|
def strip_initial_indent(lines):
|
||||||
|
"""For %load, strip indent from lines until finding an unindented line.
|
||||||
|
|
||||||
|
https://github.com/ipython/ipython/issues/9775
|
||||||
|
"""
|
||||||
|
indent_re = re.compile(r'\s+')
|
||||||
|
|
||||||
|
it = iter(lines)
|
||||||
|
first_line = next(it)
|
||||||
|
indent_match = indent_re.match(first_line)
|
||||||
|
|
||||||
|
if indent_match:
|
||||||
|
# First line was indented
|
||||||
|
indent = indent_match.group()
|
||||||
|
yield first_line[len(indent):]
|
||||||
|
|
||||||
|
for line in it:
|
||||||
|
if line.startswith(indent):
|
||||||
|
yield line[len(indent):]
|
||||||
|
else:
|
||||||
|
# Less indented than the first line - stop dedenting
|
||||||
|
yield line
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
yield first_line
|
||||||
|
|
||||||
|
# Pass the remaining lines through without dedenting
|
||||||
|
for line in it:
|
||||||
|
yield line
|
||||||
|
|
||||||
|
|
||||||
|
class InteractivelyDefined(Exception):
|
||||||
|
"""Exception for interactively defined variable in magic_edit"""
|
||||||
|
def __init__(self, index):
|
||||||
|
self.index = index
|
||||||
|
|
||||||
|
|
||||||
|
@magics_class
|
||||||
|
class CodeMagics(Magics):
|
||||||
|
"""Magics related to code management (loading, saving, editing, ...)."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self._knowntemps = set()
|
||||||
|
super(CodeMagics, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def save(self, parameter_s=''):
|
||||||
|
"""Save a set of lines or a macro to a given filename.
|
||||||
|
|
||||||
|
Usage:\\
|
||||||
|
%save [options] filename n1-n2 n3-n4 ... n5 .. n6 ...
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-r: use 'raw' input. By default, the 'processed' history is used,
|
||||||
|
so that magics are loaded in their transformed version to valid
|
||||||
|
Python. If this option is given, the raw input as typed as the
|
||||||
|
command line is used instead.
|
||||||
|
|
||||||
|
-f: force overwrite. If file exists, %save will prompt for overwrite
|
||||||
|
unless -f is given.
|
||||||
|
|
||||||
|
-a: append to the file instead of overwriting it.
|
||||||
|
|
||||||
|
This function uses the same syntax as %history for input ranges,
|
||||||
|
then saves the lines to the filename you specify.
|
||||||
|
|
||||||
|
It adds a '.py' extension to the file if you don't do so yourself, and
|
||||||
|
it asks for confirmation before overwriting existing files.
|
||||||
|
|
||||||
|
If `-r` option is used, the default extension is `.ipy`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
opts,args = self.parse_options(parameter_s,'fra',mode='list')
|
||||||
|
if not args:
|
||||||
|
raise UsageError('Missing filename.')
|
||||||
|
raw = 'r' in opts
|
||||||
|
force = 'f' in opts
|
||||||
|
append = 'a' in opts
|
||||||
|
mode = 'a' if append else 'w'
|
||||||
|
ext = u'.ipy' if raw else u'.py'
|
||||||
|
fname, codefrom = args[0], " ".join(args[1:])
|
||||||
|
if not fname.endswith((u'.py',u'.ipy')):
|
||||||
|
fname += ext
|
||||||
|
file_exists = os.path.isfile(fname)
|
||||||
|
if file_exists and not force and not append:
|
||||||
|
try:
|
||||||
|
overwrite = self.shell.ask_yes_no('File `%s` exists. Overwrite (y/[N])? ' % fname, default='n')
|
||||||
|
except StdinNotImplementedError:
|
||||||
|
print("File `%s` exists. Use `%%save -f %s` to force overwrite" % (fname, parameter_s))
|
||||||
|
return
|
||||||
|
if not overwrite :
|
||||||
|
print('Operation cancelled.')
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
cmds = self.shell.find_user_code(codefrom,raw)
|
||||||
|
except (TypeError, ValueError) as e:
|
||||||
|
print(e.args[0])
|
||||||
|
return
|
||||||
|
out = py3compat.cast_unicode(cmds)
|
||||||
|
with io.open(fname, mode, encoding="utf-8") as f:
|
||||||
|
if not file_exists or not append:
|
||||||
|
f.write(u"# coding: utf-8\n")
|
||||||
|
f.write(out)
|
||||||
|
# make sure we end on a newline
|
||||||
|
if not out.endswith(u'\n'):
|
||||||
|
f.write(u'\n')
|
||||||
|
print('The following commands were written to file `%s`:' % fname)
|
||||||
|
print(cmds)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def pastebin(self, parameter_s=''):
|
||||||
|
"""Upload code to Github's Gist paste bin, returning the URL.
|
||||||
|
|
||||||
|
Usage:\\
|
||||||
|
%pastebin [-d "Custom description"] 1-7
|
||||||
|
|
||||||
|
The argument can be an input history range, a filename, or the name of a
|
||||||
|
string or macro.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-d: Pass a custom description for the gist. The default will say
|
||||||
|
"Pasted from yap_ipython".
|
||||||
|
"""
|
||||||
|
opts, args = self.parse_options(parameter_s, 'd:')
|
||||||
|
|
||||||
|
try:
|
||||||
|
code = self.shell.find_user_code(args)
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
print(e.args[0])
|
||||||
|
return
|
||||||
|
|
||||||
|
# Deferred import
|
||||||
|
try:
|
||||||
|
from urllib.request import urlopen # Py 3
|
||||||
|
except ImportError:
|
||||||
|
from urllib2 import urlopen
|
||||||
|
import json
|
||||||
|
post_data = json.dumps({
|
||||||
|
"description": opts.get('d', "Pasted from yap_ipython"),
|
||||||
|
"public": True,
|
||||||
|
"files": {
|
||||||
|
"file1.py": {
|
||||||
|
"content": code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).encode('utf-8')
|
||||||
|
|
||||||
|
response = urlopen("https://api.github.com/gists", post_data)
|
||||||
|
response_data = json.loads(response.read().decode('utf-8'))
|
||||||
|
return response_data['html_url']
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def loadpy(self, arg_s):
|
||||||
|
"""Alias of `%load`
|
||||||
|
|
||||||
|
`%loadpy` has gained some flexibility and dropped the requirement of a `.py`
|
||||||
|
extension. So it has been renamed simply into %load. You can look at
|
||||||
|
`%load`'s docstring for more info.
|
||||||
|
"""
|
||||||
|
self.load(arg_s)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def load(self, arg_s):
|
||||||
|
"""Load code into the current frontend.
|
||||||
|
|
||||||
|
Usage:\\
|
||||||
|
%load [options] source
|
||||||
|
|
||||||
|
where source can be a filename, URL, input history range, macro, or
|
||||||
|
element in the user namespace
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-r <lines>: Specify lines or ranges of lines to load from the source.
|
||||||
|
Ranges could be specified as x-y (x..y) or in python-style x:y
|
||||||
|
(x..(y-1)). Both limits x and y can be left blank (meaning the
|
||||||
|
beginning and end of the file, respectively).
|
||||||
|
|
||||||
|
-s <symbols>: Specify function or classes to load from python source.
|
||||||
|
|
||||||
|
-y : Don't ask confirmation for loading source above 200 000 characters.
|
||||||
|
|
||||||
|
-n : Include the user's namespace when searching for source code.
|
||||||
|
|
||||||
|
This magic command can either take a local filename, a URL, an history
|
||||||
|
range (see %history) or a macro as argument, it will prompt for
|
||||||
|
confirmation before loading source with more than 200 000 characters, unless
|
||||||
|
-y flag is passed or if the frontend does not support raw_input::
|
||||||
|
|
||||||
|
%load myscript.py
|
||||||
|
%load 7-27
|
||||||
|
%load myMacro
|
||||||
|
%load http://www.example.com/myscript.py
|
||||||
|
%load -r 5-10 myscript.py
|
||||||
|
%load -r 10-20,30,40: foo.py
|
||||||
|
%load -s MyClass,wonder_function myscript.py
|
||||||
|
%load -n MyClass
|
||||||
|
%load -n my_module.wonder_function
|
||||||
|
"""
|
||||||
|
opts,args = self.parse_options(arg_s,'yns:r:')
|
||||||
|
|
||||||
|
if not args:
|
||||||
|
raise UsageError('Missing filename, URL, input history range, '
|
||||||
|
'macro, or element in the user namespace.')
|
||||||
|
|
||||||
|
search_ns = 'n' in opts
|
||||||
|
|
||||||
|
contents = self.shell.find_user_code(args, search_ns=search_ns)
|
||||||
|
|
||||||
|
if 's' in opts:
|
||||||
|
try:
|
||||||
|
blocks, not_found = extract_symbols(contents, opts['s'])
|
||||||
|
except SyntaxError:
|
||||||
|
# non python code
|
||||||
|
error("Unable to parse the input as valid Python code")
|
||||||
|
return
|
||||||
|
|
||||||
|
if len(not_found) == 1:
|
||||||
|
warn('The symbol `%s` was not found' % not_found[0])
|
||||||
|
elif len(not_found) > 1:
|
||||||
|
warn('The symbols %s were not found' % get_text_list(not_found,
|
||||||
|
wrap_item_with='`')
|
||||||
|
)
|
||||||
|
|
||||||
|
contents = '\n'.join(blocks)
|
||||||
|
|
||||||
|
if 'r' in opts:
|
||||||
|
ranges = opts['r'].replace(',', ' ')
|
||||||
|
lines = contents.split('\n')
|
||||||
|
slices = extract_code_ranges(ranges)
|
||||||
|
contents = [lines[slice(*slc)] for slc in slices]
|
||||||
|
contents = '\n'.join(strip_initial_indent(chain.from_iterable(contents)))
|
||||||
|
|
||||||
|
l = len(contents)
|
||||||
|
|
||||||
|
# 200 000 is ~ 2500 full 80 character lines
|
||||||
|
# so in average, more than 5000 lines
|
||||||
|
if l > 200000 and 'y' not in opts:
|
||||||
|
try:
|
||||||
|
ans = self.shell.ask_yes_no(("The text you're trying to load seems pretty big"\
|
||||||
|
" (%d characters). Continue (y/[N]) ?" % l), default='n' )
|
||||||
|
except StdinNotImplementedError:
|
||||||
|
#assume yes if raw input not implemented
|
||||||
|
ans = True
|
||||||
|
|
||||||
|
if ans is False :
|
||||||
|
print('Operation cancelled.')
|
||||||
|
return
|
||||||
|
|
||||||
|
contents = "# %load {}\n".format(arg_s) + contents
|
||||||
|
|
||||||
|
self.shell.set_next_input(contents, replace=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _find_edit_target(shell, args, opts, last_call):
|
||||||
|
"""Utility method used by magic_edit to find what to edit."""
|
||||||
|
|
||||||
|
def make_filename(arg):
|
||||||
|
"Make a filename from the given args"
|
||||||
|
try:
|
||||||
|
filename = get_py_filename(arg)
|
||||||
|
except IOError:
|
||||||
|
# If it ends with .py but doesn't already exist, assume we want
|
||||||
|
# a new file.
|
||||||
|
if arg.endswith('.py'):
|
||||||
|
filename = arg
|
||||||
|
else:
|
||||||
|
filename = None
|
||||||
|
return filename
|
||||||
|
|
||||||
|
# Set a few locals from the options for convenience:
|
||||||
|
opts_prev = 'p' in opts
|
||||||
|
opts_raw = 'r' in opts
|
||||||
|
|
||||||
|
# custom exceptions
|
||||||
|
class DataIsObject(Exception): pass
|
||||||
|
|
||||||
|
# Default line number value
|
||||||
|
lineno = opts.get('n',None)
|
||||||
|
|
||||||
|
if opts_prev:
|
||||||
|
args = '_%s' % last_call[0]
|
||||||
|
if args not in shell.user_ns:
|
||||||
|
args = last_call[1]
|
||||||
|
|
||||||
|
# by default this is done with temp files, except when the given
|
||||||
|
# arg is a filename
|
||||||
|
use_temp = True
|
||||||
|
|
||||||
|
data = ''
|
||||||
|
|
||||||
|
# First, see if the arguments should be a filename.
|
||||||
|
filename = make_filename(args)
|
||||||
|
if filename:
|
||||||
|
use_temp = False
|
||||||
|
elif args:
|
||||||
|
# Mode where user specifies ranges of lines, like in %macro.
|
||||||
|
data = shell.extract_input_lines(args, opts_raw)
|
||||||
|
if not data:
|
||||||
|
try:
|
||||||
|
# Load the parameter given as a variable. If not a string,
|
||||||
|
# process it as an object instead (below)
|
||||||
|
|
||||||
|
#print '*** args',args,'type',type(args) # dbg
|
||||||
|
data = eval(args, shell.user_ns)
|
||||||
|
if not isinstance(data, str):
|
||||||
|
raise DataIsObject
|
||||||
|
|
||||||
|
except (NameError,SyntaxError):
|
||||||
|
# given argument is not a variable, try as a filename
|
||||||
|
filename = make_filename(args)
|
||||||
|
if filename is None:
|
||||||
|
warn("Argument given (%s) can't be found as a variable "
|
||||||
|
"or as a filename." % args)
|
||||||
|
return (None, None, None)
|
||||||
|
use_temp = False
|
||||||
|
|
||||||
|
except DataIsObject:
|
||||||
|
# macros have a special edit function
|
||||||
|
if isinstance(data, Macro):
|
||||||
|
raise MacroToEdit(data)
|
||||||
|
|
||||||
|
# For objects, try to edit the file where they are defined
|
||||||
|
filename = find_file(data)
|
||||||
|
if filename:
|
||||||
|
if 'fakemodule' in filename.lower() and \
|
||||||
|
inspect.isclass(data):
|
||||||
|
# class created by %edit? Try to find source
|
||||||
|
# by looking for method definitions instead, the
|
||||||
|
# __module__ in those classes is FakeModule.
|
||||||
|
attrs = [getattr(data, aname) for aname in dir(data)]
|
||||||
|
for attr in attrs:
|
||||||
|
if not inspect.ismethod(attr):
|
||||||
|
continue
|
||||||
|
filename = find_file(attr)
|
||||||
|
if filename and \
|
||||||
|
'fakemodule' not in filename.lower():
|
||||||
|
# change the attribute to be the edit
|
||||||
|
# target instead
|
||||||
|
data = attr
|
||||||
|
break
|
||||||
|
|
||||||
|
m = ipython_input_pat.match(os.path.basename(filename))
|
||||||
|
if m:
|
||||||
|
raise InteractivelyDefined(int(m.groups()[0]))
|
||||||
|
|
||||||
|
datafile = 1
|
||||||
|
if filename is None:
|
||||||
|
filename = make_filename(args)
|
||||||
|
datafile = 1
|
||||||
|
if filename is not None:
|
||||||
|
# only warn about this if we get a real name
|
||||||
|
warn('Could not find file where `%s` is defined.\n'
|
||||||
|
'Opening a file named `%s`' % (args, filename))
|
||||||
|
# Now, make sure we can actually read the source (if it was
|
||||||
|
# in a temp file it's gone by now).
|
||||||
|
if datafile:
|
||||||
|
if lineno is None:
|
||||||
|
lineno = find_source_lines(data)
|
||||||
|
if lineno is None:
|
||||||
|
filename = make_filename(args)
|
||||||
|
if filename is None:
|
||||||
|
warn('The file where `%s` was defined '
|
||||||
|
'cannot be read or found.' % data)
|
||||||
|
return (None, None, None)
|
||||||
|
use_temp = False
|
||||||
|
|
||||||
|
if use_temp:
|
||||||
|
filename = shell.mktempfile(data)
|
||||||
|
print('yap_ipython will make a temporary file named:',filename)
|
||||||
|
|
||||||
|
# use last_call to remember the state of the previous call, but don't
|
||||||
|
# let it be clobbered by successive '-p' calls.
|
||||||
|
try:
|
||||||
|
last_call[0] = shell.displayhook.prompt_count
|
||||||
|
if not opts_prev:
|
||||||
|
last_call[1] = args
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
return filename, lineno, use_temp
|
||||||
|
|
||||||
|
def _edit_macro(self,mname,macro):
|
||||||
|
"""open an editor with the macro data in a file"""
|
||||||
|
filename = self.shell.mktempfile(macro.value)
|
||||||
|
self.shell.hooks.editor(filename)
|
||||||
|
|
||||||
|
# and make a new macro object, to replace the old one
|
||||||
|
with open(filename) as mfile:
|
||||||
|
mvalue = mfile.read()
|
||||||
|
self.shell.user_ns[mname] = Macro(mvalue)
|
||||||
|
|
||||||
|
@skip_doctest
|
||||||
|
@line_magic
|
||||||
|
def edit(self, parameter_s='',last_call=['','']):
|
||||||
|
"""Bring up an editor and execute the resulting code.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
%edit [options] [args]
|
||||||
|
|
||||||
|
%edit runs yap_ipython's editor hook. The default version of this hook is
|
||||||
|
set to call the editor specified by your $EDITOR environment variable.
|
||||||
|
If this isn't found, it will default to vi under Linux/Unix and to
|
||||||
|
notepad under Windows. See the end of this docstring for how to change
|
||||||
|
the editor hook.
|
||||||
|
|
||||||
|
You can also set the value of this editor via the
|
||||||
|
``TerminalInteractiveShell.editor`` option in your configuration file.
|
||||||
|
This is useful if you wish to use a different editor from your typical
|
||||||
|
default with yap_ipython (and for Windows users who typically don't set
|
||||||
|
environment variables).
|
||||||
|
|
||||||
|
This command allows you to conveniently edit multi-line code right in
|
||||||
|
your yap_ipython session.
|
||||||
|
|
||||||
|
If called without arguments, %edit opens up an empty editor with a
|
||||||
|
temporary file and will execute the contents of this file when you
|
||||||
|
close it (don't forget to save it!).
|
||||||
|
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-n <number>: open the editor at a specified line number. By default,
|
||||||
|
the yap_ipython editor hook uses the unix syntax 'editor +N filename', but
|
||||||
|
you can configure this by providing your own modified hook if your
|
||||||
|
favorite editor supports line-number specifications with a different
|
||||||
|
syntax.
|
||||||
|
|
||||||
|
-p: this will call the editor with the same data as the previous time
|
||||||
|
it was used, regardless of how long ago (in your current session) it
|
||||||
|
was.
|
||||||
|
|
||||||
|
-r: use 'raw' input. This option only applies to input taken from the
|
||||||
|
user's history. By default, the 'processed' history is used, so that
|
||||||
|
magics are loaded in their transformed version to valid Python. If
|
||||||
|
this option is given, the raw input as typed as the command line is
|
||||||
|
used instead. When you exit the editor, it will be executed by
|
||||||
|
yap_ipython's own processor.
|
||||||
|
|
||||||
|
-x: do not execute the edited code immediately upon exit. This is
|
||||||
|
mainly useful if you are editing programs which need to be called with
|
||||||
|
command line arguments, which you can then do using %run.
|
||||||
|
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
If arguments are given, the following possibilities exist:
|
||||||
|
|
||||||
|
- If the argument is a filename, yap_ipython will load that into the
|
||||||
|
editor. It will execute its contents with execfile() when you exit,
|
||||||
|
loading any code in the file into your interactive namespace.
|
||||||
|
|
||||||
|
- The arguments are ranges of input history, e.g. "7 ~1/4-6".
|
||||||
|
The syntax is the same as in the %history magic.
|
||||||
|
|
||||||
|
- If the argument is a string variable, its contents are loaded
|
||||||
|
into the editor. You can thus edit any string which contains
|
||||||
|
python code (including the result of previous edits).
|
||||||
|
|
||||||
|
- If the argument is the name of an object (other than a string),
|
||||||
|
yap_ipython will try to locate the file where it was defined and open the
|
||||||
|
editor at the point where it is defined. You can use `%edit function`
|
||||||
|
to load an editor exactly at the point where 'function' is defined,
|
||||||
|
edit it and have the file be executed automatically.
|
||||||
|
|
||||||
|
- If the object is a macro (see %macro for details), this opens up your
|
||||||
|
specified editor with a temporary file containing the macro's data.
|
||||||
|
Upon exit, the macro is reloaded with the contents of the file.
|
||||||
|
|
||||||
|
Note: opening at an exact line is only supported under Unix, and some
|
||||||
|
editors (like kedit and gedit up to Gnome 2.8) do not understand the
|
||||||
|
'+NUMBER' parameter necessary for this feature. Good editors like
|
||||||
|
(X)Emacs, vi, jed, pico and joe all do.
|
||||||
|
|
||||||
|
After executing your code, %edit will return as output the code you
|
||||||
|
typed in the editor (except when it was an existing file). This way
|
||||||
|
you can reload the code in further invocations of %edit as a variable,
|
||||||
|
via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
|
||||||
|
the output.
|
||||||
|
|
||||||
|
Note that %edit is also available through the alias %ed.
|
||||||
|
|
||||||
|
This is an example of creating a simple function inside the editor and
|
||||||
|
then modifying it. First, start up the editor::
|
||||||
|
|
||||||
|
In [1]: edit
|
||||||
|
Editing... done. Executing edited code...
|
||||||
|
Out[1]: 'def foo():\\n print "foo() was defined in an editing
|
||||||
|
session"\\n'
|
||||||
|
|
||||||
|
We can then call the function foo()::
|
||||||
|
|
||||||
|
In [2]: foo()
|
||||||
|
foo() was defined in an editing session
|
||||||
|
|
||||||
|
Now we edit foo. yap_ipython automatically loads the editor with the
|
||||||
|
(temporary) file where foo() was previously defined::
|
||||||
|
|
||||||
|
In [3]: edit foo
|
||||||
|
Editing... done. Executing edited code...
|
||||||
|
|
||||||
|
And if we call foo() again we get the modified version::
|
||||||
|
|
||||||
|
In [4]: foo()
|
||||||
|
foo() has now been changed!
|
||||||
|
|
||||||
|
Here is an example of how to edit a code snippet successive
|
||||||
|
times. First we call the editor::
|
||||||
|
|
||||||
|
In [5]: edit
|
||||||
|
Editing... done. Executing edited code...
|
||||||
|
hello
|
||||||
|
Out[5]: "print 'hello'\\n"
|
||||||
|
|
||||||
|
Now we call it again with the previous output (stored in _)::
|
||||||
|
|
||||||
|
In [6]: edit _
|
||||||
|
Editing... done. Executing edited code...
|
||||||
|
hello world
|
||||||
|
Out[6]: "print 'hello world'\\n"
|
||||||
|
|
||||||
|
Now we call it with the output #8 (stored in _8, also as Out[8])::
|
||||||
|
|
||||||
|
In [7]: edit _8
|
||||||
|
Editing... done. Executing edited code...
|
||||||
|
hello again
|
||||||
|
Out[7]: "print 'hello again'\\n"
|
||||||
|
|
||||||
|
|
||||||
|
Changing the default editor hook:
|
||||||
|
|
||||||
|
If you wish to write your own editor hook, you can put it in a
|
||||||
|
configuration file which you load at startup time. The default hook
|
||||||
|
is defined in the yap_ipython.core.hooks module, and you can use that as a
|
||||||
|
starting example for further modifications. That file also has
|
||||||
|
general instructions on how to set a new hook for use once you've
|
||||||
|
defined it."""
|
||||||
|
opts,args = self.parse_options(parameter_s,'prxn:')
|
||||||
|
|
||||||
|
try:
|
||||||
|
filename, lineno, is_temp = self._find_edit_target(self.shell,
|
||||||
|
args, opts, last_call)
|
||||||
|
except MacroToEdit as e:
|
||||||
|
self._edit_macro(args, e.args[0])
|
||||||
|
return
|
||||||
|
except InteractivelyDefined as e:
|
||||||
|
print("Editing In[%i]" % e.index)
|
||||||
|
args = str(e.index)
|
||||||
|
filename, lineno, is_temp = self._find_edit_target(self.shell,
|
||||||
|
args, opts, last_call)
|
||||||
|
if filename is None:
|
||||||
|
# nothing was found, warnings have already been issued,
|
||||||
|
# just give up.
|
||||||
|
return
|
||||||
|
|
||||||
|
if is_temp:
|
||||||
|
self._knowntemps.add(filename)
|
||||||
|
elif (filename in self._knowntemps):
|
||||||
|
is_temp = True
|
||||||
|
|
||||||
|
|
||||||
|
# do actual editing here
|
||||||
|
print('Editing...', end=' ')
|
||||||
|
sys.stdout.flush()
|
||||||
|
try:
|
||||||
|
# Quote filenames that may have spaces in them
|
||||||
|
if ' ' in filename:
|
||||||
|
filename = "'%s'" % filename
|
||||||
|
self.shell.hooks.editor(filename,lineno)
|
||||||
|
except TryNext:
|
||||||
|
warn('Could not open editor')
|
||||||
|
return
|
||||||
|
|
||||||
|
# XXX TODO: should this be generalized for all string vars?
|
||||||
|
# For now, this is special-cased to blocks created by cpaste
|
||||||
|
if args.strip() == 'pasted_block':
|
||||||
|
with open(filename, 'r') as f:
|
||||||
|
self.shell.user_ns['pasted_block'] = f.read()
|
||||||
|
|
||||||
|
if 'x' in opts: # -x prevents actual execution
|
||||||
|
print()
|
||||||
|
else:
|
||||||
|
print('done. Executing edited code...')
|
||||||
|
with preserve_keys(self.shell.user_ns, '__file__'):
|
||||||
|
if not is_temp:
|
||||||
|
self.shell.user_ns['__file__'] = filename
|
||||||
|
if 'r' in opts: # Untranslated yap_ipython code
|
||||||
|
with open(filename, 'r') as f:
|
||||||
|
source = f.read()
|
||||||
|
self.shell.run_cell(source, store_history=False)
|
||||||
|
else:
|
||||||
|
self.shell.safe_execfile(filename, self.shell.user_ns,
|
||||||
|
self.shell.user_ns)
|
||||||
|
|
||||||
|
if is_temp:
|
||||||
|
try:
|
||||||
|
return open(filename).read()
|
||||||
|
except IOError as msg:
|
||||||
|
if msg.filename == filename:
|
||||||
|
warn('File not found. Did you forget to save?')
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.shell.showtraceback()
|
158
packages/python/yap_kernel/yap_ipython/core/magics/config.py
Normal file
158
packages/python/yap_kernel/yap_ipython/core/magics/config.py
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
"""Implementation of configuration-related magic functions.
|
||||||
|
"""
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (c) 2012 The yap_ipython Development Team.
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
#
|
||||||
|
# The full license is in the file COPYING.txt, distributed with this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Stdlib
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Our own packages
|
||||||
|
from yap_ipython.core.error import UsageError
|
||||||
|
from yap_ipython.core.magic import Magics, magics_class, line_magic
|
||||||
|
from logging import error
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Magic implementation classes
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
reg = re.compile('^\w+\.\w+$')
|
||||||
|
@magics_class
|
||||||
|
class ConfigMagics(Magics):
|
||||||
|
|
||||||
|
def __init__(self, shell):
|
||||||
|
super(ConfigMagics, self).__init__(shell)
|
||||||
|
self.configurables = []
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def config(self, s):
|
||||||
|
"""configure yap_ipython
|
||||||
|
|
||||||
|
%config Class[.trait=value]
|
||||||
|
|
||||||
|
This magic exposes most of the yap_ipython config system. Any
|
||||||
|
Configurable class should be able to be configured with the simple
|
||||||
|
line::
|
||||||
|
|
||||||
|
%config Class.trait=value
|
||||||
|
|
||||||
|
Where `value` will be resolved in the user's namespace, if it is an
|
||||||
|
expression or variable name.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
To see what classes are available for config, pass no arguments::
|
||||||
|
|
||||||
|
In [1]: %config
|
||||||
|
Available objects for config:
|
||||||
|
TerminalInteractiveShell
|
||||||
|
HistoryManager
|
||||||
|
PrefilterManager
|
||||||
|
AliasManager
|
||||||
|
IPCompleter
|
||||||
|
DisplayFormatter
|
||||||
|
|
||||||
|
To view what is configurable on a given class, just pass the class
|
||||||
|
name::
|
||||||
|
|
||||||
|
In [2]: %config IPCompleter
|
||||||
|
IPCompleter options
|
||||||
|
-----------------
|
||||||
|
IPCompleter.omit__names=<Enum>
|
||||||
|
Current: 2
|
||||||
|
Choices: (0, 1, 2)
|
||||||
|
Instruct the completer to omit private method names
|
||||||
|
Specifically, when completing on ``object.<tab>``.
|
||||||
|
When 2 [default]: all names that start with '_' will be excluded.
|
||||||
|
When 1: all 'magic' names (``__foo__``) will be excluded.
|
||||||
|
When 0: nothing will be excluded.
|
||||||
|
IPCompleter.merge_completions=<CBool>
|
||||||
|
Current: True
|
||||||
|
Whether to merge completion results into a single list
|
||||||
|
If False, only the completion results from the first non-empty
|
||||||
|
completer will be returned.
|
||||||
|
IPCompleter.limit_to__all__=<CBool>
|
||||||
|
Current: False
|
||||||
|
Instruct the completer to use __all__ for the completion
|
||||||
|
Specifically, when completing on ``object.<tab>``.
|
||||||
|
When True: only those names in obj.__all__ will be included.
|
||||||
|
When False [default]: the __all__ attribute is ignored
|
||||||
|
IPCompleter.greedy=<CBool>
|
||||||
|
Current: False
|
||||||
|
Activate greedy completion
|
||||||
|
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.
|
||||||
|
|
||||||
|
but the real use is in setting values::
|
||||||
|
|
||||||
|
In [3]: %config IPCompleter.greedy = True
|
||||||
|
|
||||||
|
and these values are read from the user_ns if they are variables::
|
||||||
|
|
||||||
|
In [4]: feeling_greedy=False
|
||||||
|
|
||||||
|
In [5]: %config IPCompleter.greedy = feeling_greedy
|
||||||
|
|
||||||
|
"""
|
||||||
|
from traitlets.config.loader import Config
|
||||||
|
# some yap_ipython objects are Configurable, but do not yet have
|
||||||
|
# any configurable traits. Exclude them from the effects of
|
||||||
|
# this magic, as their presence is just noise:
|
||||||
|
configurables = sorted(set([ c for c in self.shell.configurables
|
||||||
|
if c.__class__.class_traits(config=True)
|
||||||
|
]), key=lambda x: x.__class__.__name__)
|
||||||
|
classnames = [ c.__class__.__name__ for c in configurables ]
|
||||||
|
|
||||||
|
line = s.strip()
|
||||||
|
if not line:
|
||||||
|
# print available configurable names
|
||||||
|
print("Available objects for config:")
|
||||||
|
for name in classnames:
|
||||||
|
print(" ", name)
|
||||||
|
return
|
||||||
|
elif line in classnames:
|
||||||
|
# `%config TerminalInteractiveShell` will print trait info for
|
||||||
|
# TerminalInteractiveShell
|
||||||
|
c = configurables[classnames.index(line)]
|
||||||
|
cls = c.__class__
|
||||||
|
help = cls.class_get_help(c)
|
||||||
|
# strip leading '--' from cl-args:
|
||||||
|
help = re.sub(re.compile(r'^--', re.MULTILINE), '', help)
|
||||||
|
print(help)
|
||||||
|
return
|
||||||
|
elif reg.match(line):
|
||||||
|
cls, attr = line.split('.')
|
||||||
|
return getattr(configurables[classnames.index(cls)],attr)
|
||||||
|
elif '=' not in line:
|
||||||
|
msg = "Invalid config statement: %r, "\
|
||||||
|
"should be `Class.trait = value`."
|
||||||
|
|
||||||
|
ll = line.lower()
|
||||||
|
for classname in classnames:
|
||||||
|
if ll == classname.lower():
|
||||||
|
msg = msg + '\nDid you mean %s (note the case)?' % classname
|
||||||
|
break
|
||||||
|
|
||||||
|
raise UsageError( msg % line)
|
||||||
|
|
||||||
|
# otherwise, assume we are setting configurables.
|
||||||
|
# leave quotes on args when splitting, because we want
|
||||||
|
# unquoted args to eval in user_ns
|
||||||
|
cfg = Config()
|
||||||
|
exec("cfg."+line, locals(), self.shell.user_ns)
|
||||||
|
|
||||||
|
for configurable in configurables:
|
||||||
|
try:
|
||||||
|
configurable.update_config(cfg)
|
||||||
|
except Exception as e:
|
||||||
|
error(e)
|
@ -0,0 +1,70 @@
|
|||||||
|
"""Simple magics for display formats"""
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (c) 2012 The yap_ipython Development Team.
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
#
|
||||||
|
# The full license is in the file COPYING.txt, distributed with this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Our own packages
|
||||||
|
from yap_ipython.core.display import display, Javascript, Latex, SVG, HTML, Markdown
|
||||||
|
from yap_ipython.core.magic import (
|
||||||
|
Magics, magics_class, cell_magic
|
||||||
|
)
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Magic implementation classes
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@magics_class
|
||||||
|
class DisplayMagics(Magics):
|
||||||
|
"""Magics for displaying various output types with literals
|
||||||
|
|
||||||
|
Defines javascript/latex/svg/html cell magics for writing
|
||||||
|
blocks in those languages, to be rendered in the frontend.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@cell_magic
|
||||||
|
def js(self, line, cell):
|
||||||
|
"""Run the cell block of Javascript code
|
||||||
|
|
||||||
|
Alias of `%%javascript`
|
||||||
|
"""
|
||||||
|
self.javascript(line, cell)
|
||||||
|
|
||||||
|
@cell_magic
|
||||||
|
def javascript(self, line, cell):
|
||||||
|
"""Run the cell block of Javascript code"""
|
||||||
|
display(Javascript(cell))
|
||||||
|
|
||||||
|
|
||||||
|
@cell_magic
|
||||||
|
def latex(self, line, cell):
|
||||||
|
"""Render the cell as a block of latex
|
||||||
|
|
||||||
|
The subset of latex which is support depends on the implementation in
|
||||||
|
the client. In the Jupyter Notebook, this magic only renders the subset
|
||||||
|
of latex defined by MathJax
|
||||||
|
[here](https://docs.mathjax.org/en/v2.5-latest/tex.html)."""
|
||||||
|
display(Latex(cell))
|
||||||
|
|
||||||
|
@cell_magic
|
||||||
|
def svg(self, line, cell):
|
||||||
|
"""Render the cell as an SVG literal"""
|
||||||
|
display(SVG(cell))
|
||||||
|
|
||||||
|
@cell_magic
|
||||||
|
def html(self, line, cell):
|
||||||
|
"""Render the cell as a block of HTML"""
|
||||||
|
display(HTML(cell))
|
||||||
|
|
||||||
|
@cell_magic
|
||||||
|
def markdown(self, line, cell):
|
||||||
|
"""Render the cell as Markdown text block"""
|
||||||
|
display(Markdown(cell))
|
1424
packages/python/yap_kernel/yap_ipython/core/magics/execution.py
Normal file
1424
packages/python/yap_kernel/yap_ipython/core/magics/execution.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,63 @@
|
|||||||
|
"""Implementation of magic functions for the extension machinery.
|
||||||
|
"""
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (c) 2012 The yap_ipython Development Team.
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
#
|
||||||
|
# The full license is in the file COPYING.txt, distributed with this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
# Our own packages
|
||||||
|
from yap_ipython.core.error import UsageError
|
||||||
|
from yap_ipython.core.magic import Magics, magics_class, line_magic
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Magic implementation classes
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@magics_class
|
||||||
|
class ExtensionMagics(Magics):
|
||||||
|
"""Magics to manage the yap_ipython extensions system."""
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def load_ext(self, module_str):
|
||||||
|
"""Load an yap_ipython extension by its module name."""
|
||||||
|
if not module_str:
|
||||||
|
raise UsageError('Missing module name.')
|
||||||
|
res = self.shell.extension_manager.load_extension(module_str)
|
||||||
|
|
||||||
|
if res == 'already loaded':
|
||||||
|
print("The %s extension is already loaded. To reload it, use:" % module_str)
|
||||||
|
print(" %reload_ext", module_str)
|
||||||
|
elif res == 'no load function':
|
||||||
|
print("The %s module is not an yap_ipython extension." % module_str)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def unload_ext(self, module_str):
|
||||||
|
"""Unload an yap_ipython extension by its module name.
|
||||||
|
|
||||||
|
Not all extensions can be unloaded, only those which define an
|
||||||
|
``unload_ipython_extension`` function.
|
||||||
|
"""
|
||||||
|
if not module_str:
|
||||||
|
raise UsageError('Missing module name.')
|
||||||
|
|
||||||
|
res = self.shell.extension_manager.unload_extension(module_str)
|
||||||
|
|
||||||
|
if res == 'no unload function':
|
||||||
|
print("The %s extension doesn't define how to unload it." % module_str)
|
||||||
|
elif res == "not loaded":
|
||||||
|
print("The %s extension is not loaded." % module_str)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def reload_ext(self, module_str):
|
||||||
|
"""Reload an yap_ipython extension by its module name."""
|
||||||
|
if not module_str:
|
||||||
|
raise UsageError('Missing module name.')
|
||||||
|
self.shell.extension_manager.reload_extension(module_str)
|
318
packages/python/yap_kernel/yap_ipython/core/magics/history.py
Normal file
318
packages/python/yap_kernel/yap_ipython/core/magics/history.py
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
"""Implementation of magic functions related to History.
|
||||||
|
"""
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (c) 2012, yap_ipython Development Team.
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
#
|
||||||
|
# The full license is in the file COPYING.txt, distributed with this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Stdlib
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from io import open as io_open
|
||||||
|
|
||||||
|
# Our own packages
|
||||||
|
from yap_ipython.core.error import StdinNotImplementedError
|
||||||
|
from yap_ipython.core.magic import Magics, magics_class, line_magic
|
||||||
|
from yap_ipython.core.magic_arguments import (argument, magic_arguments,
|
||||||
|
parse_argstring)
|
||||||
|
from yap_ipython.testing.skipdoctest import skip_doctest
|
||||||
|
from yap_ipython.utils import io
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Magics class implementation
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
_unspecified = object()
|
||||||
|
|
||||||
|
|
||||||
|
@magics_class
|
||||||
|
class HistoryMagics(Magics):
|
||||||
|
|
||||||
|
@magic_arguments()
|
||||||
|
@argument(
|
||||||
|
'-n', dest='print_nums', action='store_true', default=False,
|
||||||
|
help="""
|
||||||
|
print line numbers for each input.
|
||||||
|
This feature is only available if numbered prompts are in use.
|
||||||
|
""")
|
||||||
|
@argument(
|
||||||
|
'-o', dest='get_output', action='store_true', default=False,
|
||||||
|
help="also print outputs for each input.")
|
||||||
|
@argument(
|
||||||
|
'-p', dest='pyprompts', action='store_true', default=False,
|
||||||
|
help="""
|
||||||
|
print classic '>>>' python prompts before each input.
|
||||||
|
This is useful for making documentation, and in conjunction
|
||||||
|
with -o, for producing doctest-ready output.
|
||||||
|
""")
|
||||||
|
@argument(
|
||||||
|
'-t', dest='raw', action='store_false', default=True,
|
||||||
|
help="""
|
||||||
|
print the 'translated' history, as yap_ipython understands it.
|
||||||
|
yap_ipython filters your input and converts it all into valid Python
|
||||||
|
source before executing it (things like magics or aliases are turned
|
||||||
|
into function calls, for example). With this option, you'll see the
|
||||||
|
native history instead of the user-entered version: '%%cd /' will be
|
||||||
|
seen as 'get_ipython().run_line_magic("cd", "/")' instead of '%%cd /'.
|
||||||
|
""")
|
||||||
|
@argument(
|
||||||
|
'-f', dest='filename',
|
||||||
|
help="""
|
||||||
|
FILENAME: instead of printing the output to the screen, redirect
|
||||||
|
it to the given file. The file is always overwritten, though *when
|
||||||
|
it can*, yap_ipython asks for confirmation first. In particular, running
|
||||||
|
the command 'history -f FILENAME' from the yap_ipython Notebook
|
||||||
|
interface will replace FILENAME even if it already exists *without*
|
||||||
|
confirmation.
|
||||||
|
""")
|
||||||
|
@argument(
|
||||||
|
'-g', dest='pattern', nargs='*', default=None,
|
||||||
|
help="""
|
||||||
|
treat the arg as a glob pattern to search for in (full) history.
|
||||||
|
This includes the saved history (almost all commands ever written).
|
||||||
|
The pattern may contain '?' to match one unknown character and '*'
|
||||||
|
to match any number of unknown characters. Use '%%hist -g' to show
|
||||||
|
full saved history (may be very long).
|
||||||
|
""")
|
||||||
|
@argument(
|
||||||
|
'-l', dest='limit', type=int, nargs='?', default=_unspecified,
|
||||||
|
help="""
|
||||||
|
get the last n lines from all sessions. Specify n as a single
|
||||||
|
arg, or the default is the last 10 lines.
|
||||||
|
""")
|
||||||
|
@argument(
|
||||||
|
'-u', dest='unique', action='store_true',
|
||||||
|
help="""
|
||||||
|
when searching history using `-g`, show only unique history.
|
||||||
|
""")
|
||||||
|
@argument('range', nargs='*')
|
||||||
|
@skip_doctest
|
||||||
|
@line_magic
|
||||||
|
def history(self, parameter_s = ''):
|
||||||
|
"""Print input history (_i<n> variables), with most recent last.
|
||||||
|
|
||||||
|
By default, input history is printed without line numbers so it can be
|
||||||
|
directly pasted into an editor. Use -n to show them.
|
||||||
|
|
||||||
|
By default, all input history from the current session is displayed.
|
||||||
|
Ranges of history can be indicated using the syntax:
|
||||||
|
|
||||||
|
``4``
|
||||||
|
Line 4, current session
|
||||||
|
``4-6``
|
||||||
|
Lines 4-6, current session
|
||||||
|
``243/1-5``
|
||||||
|
Lines 1-5, session 243
|
||||||
|
``~2/7``
|
||||||
|
Line 7, session 2 before current
|
||||||
|
``~8/1-~6/5``
|
||||||
|
From the first line of 8 sessions ago, to the fifth line of 6
|
||||||
|
sessions ago.
|
||||||
|
|
||||||
|
Multiple ranges can be entered, separated by spaces
|
||||||
|
|
||||||
|
The same syntax is used by %macro, %save, %edit, %rerun
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
::
|
||||||
|
|
||||||
|
In [6]: %history -n 4-6
|
||||||
|
4:a = 12
|
||||||
|
5:print a**2
|
||||||
|
6:%history -n 4-6
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
args = parse_argstring(self.history, parameter_s)
|
||||||
|
|
||||||
|
# For brevity
|
||||||
|
history_manager = self.shell.history_manager
|
||||||
|
|
||||||
|
def _format_lineno(session, line):
|
||||||
|
"""Helper function to format line numbers properly."""
|
||||||
|
if session in (0, history_manager.session_number):
|
||||||
|
return str(line)
|
||||||
|
return "%s/%s" % (session, line)
|
||||||
|
|
||||||
|
# Check if output to specific file was requested.
|
||||||
|
outfname = args.filename
|
||||||
|
if not outfname:
|
||||||
|
outfile = sys.stdout # default
|
||||||
|
# We don't want to close stdout at the end!
|
||||||
|
close_at_end = False
|
||||||
|
else:
|
||||||
|
if os.path.exists(outfname):
|
||||||
|
try:
|
||||||
|
ans = io.ask_yes_no("File %r exists. Overwrite?" % outfname)
|
||||||
|
except StdinNotImplementedError:
|
||||||
|
ans = True
|
||||||
|
if not ans:
|
||||||
|
print('Aborting.')
|
||||||
|
return
|
||||||
|
print("Overwriting file.")
|
||||||
|
outfile = io_open(outfname, 'w', encoding='utf-8')
|
||||||
|
close_at_end = True
|
||||||
|
|
||||||
|
print_nums = args.print_nums
|
||||||
|
get_output = args.get_output
|
||||||
|
pyprompts = args.pyprompts
|
||||||
|
raw = args.raw
|
||||||
|
|
||||||
|
pattern = None
|
||||||
|
limit = None if args.limit is _unspecified else args.limit
|
||||||
|
|
||||||
|
if args.pattern is not None:
|
||||||
|
if args.pattern:
|
||||||
|
pattern = "*" + " ".join(args.pattern) + "*"
|
||||||
|
else:
|
||||||
|
pattern = "*"
|
||||||
|
hist = history_manager.search(pattern, raw=raw, output=get_output,
|
||||||
|
n=limit, unique=args.unique)
|
||||||
|
print_nums = True
|
||||||
|
elif args.limit is not _unspecified:
|
||||||
|
n = 10 if limit is None else limit
|
||||||
|
hist = history_manager.get_tail(n, raw=raw, output=get_output)
|
||||||
|
else:
|
||||||
|
if args.range: # Get history by ranges
|
||||||
|
hist = history_manager.get_range_by_str(" ".join(args.range),
|
||||||
|
raw, get_output)
|
||||||
|
else: # Just get history for the current session
|
||||||
|
hist = history_manager.get_range(raw=raw, output=get_output)
|
||||||
|
|
||||||
|
# We could be displaying the entire history, so let's not try to pull
|
||||||
|
# it into a list in memory. Anything that needs more space will just
|
||||||
|
# misalign.
|
||||||
|
width = 4
|
||||||
|
|
||||||
|
for session, lineno, inline in hist:
|
||||||
|
# Print user history with tabs expanded to 4 spaces. The GUI
|
||||||
|
# clients use hard tabs for easier usability in auto-indented code,
|
||||||
|
# but we want to produce PEP-8 compliant history for safe pasting
|
||||||
|
# into an editor.
|
||||||
|
if get_output:
|
||||||
|
inline, output = inline
|
||||||
|
inline = inline.expandtabs(4).rstrip()
|
||||||
|
|
||||||
|
multiline = "\n" in inline
|
||||||
|
line_sep = '\n' if multiline else ' '
|
||||||
|
if print_nums:
|
||||||
|
print(u'%s:%s' % (_format_lineno(session, lineno).rjust(width),
|
||||||
|
line_sep), file=outfile, end=u'')
|
||||||
|
if pyprompts:
|
||||||
|
print(u">>> ", end=u"", file=outfile)
|
||||||
|
if multiline:
|
||||||
|
inline = "\n... ".join(inline.splitlines()) + "\n..."
|
||||||
|
print(inline, file=outfile)
|
||||||
|
if get_output and output:
|
||||||
|
print(output, file=outfile)
|
||||||
|
|
||||||
|
if close_at_end:
|
||||||
|
outfile.close()
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def recall(self, arg):
|
||||||
|
r"""Repeat a command, or get command to input line for editing.
|
||||||
|
|
||||||
|
%recall and %rep are equivalent.
|
||||||
|
|
||||||
|
- %recall (no arguments):
|
||||||
|
|
||||||
|
Place a string version of last computation result (stored in the
|
||||||
|
special '_' variable) to the next input prompt. Allows you to create
|
||||||
|
elaborate command lines without using copy-paste::
|
||||||
|
|
||||||
|
In[1]: l = ["hei", "vaan"]
|
||||||
|
In[2]: "".join(l)
|
||||||
|
Out[2]: heivaan
|
||||||
|
In[3]: %recall
|
||||||
|
In[4]: heivaan_ <== cursor blinking
|
||||||
|
|
||||||
|
%recall 45
|
||||||
|
|
||||||
|
Place history line 45 on the next input prompt. Use %hist to find
|
||||||
|
out the number.
|
||||||
|
|
||||||
|
%recall 1-4
|
||||||
|
|
||||||
|
Combine the specified lines into one cell, and place it on the next
|
||||||
|
input prompt. See %history for the slice syntax.
|
||||||
|
|
||||||
|
%recall foo+bar
|
||||||
|
|
||||||
|
If foo+bar can be evaluated in the user namespace, the result is
|
||||||
|
placed at the next input prompt. Otherwise, the history is searched
|
||||||
|
for lines which contain that substring, and the most recent one is
|
||||||
|
placed at the next input prompt.
|
||||||
|
"""
|
||||||
|
if not arg: # Last output
|
||||||
|
self.shell.set_next_input(str(self.shell.user_ns["_"]))
|
||||||
|
return
|
||||||
|
# Get history range
|
||||||
|
histlines = self.shell.history_manager.get_range_by_str(arg)
|
||||||
|
cmd = "\n".join(x[2] for x in histlines)
|
||||||
|
if cmd:
|
||||||
|
self.shell.set_next_input(cmd.rstrip())
|
||||||
|
return
|
||||||
|
|
||||||
|
try: # Variable in user namespace
|
||||||
|
cmd = str(eval(arg, self.shell.user_ns))
|
||||||
|
except Exception: # Search for term in history
|
||||||
|
histlines = self.shell.history_manager.search("*"+arg+"*")
|
||||||
|
for h in reversed([x[2] for x in histlines]):
|
||||||
|
if 'recall' in h or 'rep' in h:
|
||||||
|
continue
|
||||||
|
self.shell.set_next_input(h.rstrip())
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
self.shell.set_next_input(cmd.rstrip())
|
||||||
|
print("Couldn't evaluate or find in history:", arg)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def rerun(self, parameter_s=''):
|
||||||
|
"""Re-run previous input
|
||||||
|
|
||||||
|
By default, you can specify ranges of input history to be repeated
|
||||||
|
(as with %history). With no arguments, it will repeat the last line.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-l <n> : Repeat the last n lines of input, not including the
|
||||||
|
current command.
|
||||||
|
|
||||||
|
-g foo : Repeat the most recent line which contains foo
|
||||||
|
"""
|
||||||
|
opts, args = self.parse_options(parameter_s, 'l:g:', mode='string')
|
||||||
|
if "l" in opts: # Last n lines
|
||||||
|
n = int(opts['l'])
|
||||||
|
hist = self.shell.history_manager.get_tail(n)
|
||||||
|
elif "g" in opts: # Search
|
||||||
|
p = "*"+opts['g']+"*"
|
||||||
|
hist = list(self.shell.history_manager.search(p))
|
||||||
|
for l in reversed(hist):
|
||||||
|
if "rerun" not in l[2]:
|
||||||
|
hist = [l] # The last match which isn't a %rerun
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
hist = [] # No matches except %rerun
|
||||||
|
elif args: # Specify history ranges
|
||||||
|
hist = self.shell.history_manager.get_range_by_str(args)
|
||||||
|
else: # Last line
|
||||||
|
hist = self.shell.history_manager.get_tail(1)
|
||||||
|
hist = [x[2] for x in hist]
|
||||||
|
if not hist:
|
||||||
|
print("No lines in history match specification")
|
||||||
|
return
|
||||||
|
histlines = "\n".join(hist)
|
||||||
|
print("=== Executing: ===")
|
||||||
|
print(histlines)
|
||||||
|
print("=== Output: ===")
|
||||||
|
self.shell.run_cell("\n".join(hist), store_history=False)
|
195
packages/python/yap_kernel/yap_ipython/core/magics/logging.py
Normal file
195
packages/python/yap_kernel/yap_ipython/core/magics/logging.py
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
"""Implementation of magic functions for yap_ipython's own logging.
|
||||||
|
"""
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (c) 2012 The yap_ipython Development Team.
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
#
|
||||||
|
# The full license is in the file COPYING.txt, distributed with this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Stdlib
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Our own packages
|
||||||
|
from yap_ipython.core.magic import Magics, magics_class, line_magic
|
||||||
|
from warnings import warn
|
||||||
|
from traitlets import Bool
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Magic implementation classes
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@magics_class
|
||||||
|
class LoggingMagics(Magics):
|
||||||
|
"""Magics related to all logging machinery."""
|
||||||
|
|
||||||
|
quiet = Bool(False, help=
|
||||||
|
"""
|
||||||
|
Suppress output of log state when logging is enabled
|
||||||
|
"""
|
||||||
|
).tag(config=True)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def logstart(self, parameter_s=''):
|
||||||
|
"""Start logging anywhere in a session.
|
||||||
|
|
||||||
|
%logstart [-o|-r|-t|-q] [log_name [log_mode]]
|
||||||
|
|
||||||
|
If no name is given, it defaults to a file named 'ipython_log.py' in your
|
||||||
|
current directory, in 'rotate' mode (see below).
|
||||||
|
|
||||||
|
'%logstart name' saves to file 'name' in 'backup' mode. It saves your
|
||||||
|
history up to that point and then continues logging.
|
||||||
|
|
||||||
|
%logstart takes a second optional parameter: logging mode. This can be one
|
||||||
|
of (note that the modes are given unquoted):
|
||||||
|
|
||||||
|
append
|
||||||
|
Keep logging at the end of any existing file.
|
||||||
|
|
||||||
|
backup
|
||||||
|
Rename any existing file to name~ and start name.
|
||||||
|
|
||||||
|
global
|
||||||
|
Append to a single logfile in your home directory.
|
||||||
|
|
||||||
|
over
|
||||||
|
Overwrite any existing log.
|
||||||
|
|
||||||
|
rotate
|
||||||
|
Create rotating logs: name.1~, name.2~, etc.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-o
|
||||||
|
log also yap_ipython's output. In this mode, all commands which
|
||||||
|
generate an Out[NN] prompt are recorded to the logfile, right after
|
||||||
|
their corresponding input line. The output lines are always
|
||||||
|
prepended with a '#[Out]# ' marker, so that the log remains valid
|
||||||
|
Python code.
|
||||||
|
|
||||||
|
Since this marker is always the same, filtering only the output from
|
||||||
|
a log is very easy, using for example a simple awk call::
|
||||||
|
|
||||||
|
awk -F'#\\[Out\\]# ' '{if($2) {print $2}}' ipython_log.py
|
||||||
|
|
||||||
|
-r
|
||||||
|
log 'raw' input. Normally, yap_ipython's logs contain the processed
|
||||||
|
input, so that user lines are logged in their final form, converted
|
||||||
|
into valid Python. For example, %Exit is logged as
|
||||||
|
_ip.magic("Exit"). If the -r flag is given, all input is logged
|
||||||
|
exactly as typed, with no transformations applied.
|
||||||
|
|
||||||
|
-t
|
||||||
|
put timestamps before each input line logged (these are put in
|
||||||
|
comments).
|
||||||
|
|
||||||
|
-q
|
||||||
|
suppress output of logstate message when logging is invoked
|
||||||
|
"""
|
||||||
|
|
||||||
|
opts,par = self.parse_options(parameter_s,'ortq')
|
||||||
|
log_output = 'o' in opts
|
||||||
|
log_raw_input = 'r' in opts
|
||||||
|
timestamp = 't' in opts
|
||||||
|
quiet = 'q' in opts
|
||||||
|
|
||||||
|
logger = self.shell.logger
|
||||||
|
|
||||||
|
# if no args are given, the defaults set in the logger constructor by
|
||||||
|
# ipython remain valid
|
||||||
|
if par:
|
||||||
|
try:
|
||||||
|
logfname,logmode = par.split()
|
||||||
|
except:
|
||||||
|
logfname = par
|
||||||
|
logmode = 'backup'
|
||||||
|
else:
|
||||||
|
logfname = logger.logfname
|
||||||
|
logmode = logger.logmode
|
||||||
|
# put logfname into rc struct as if it had been called on the command
|
||||||
|
# line, so it ends up saved in the log header Save it in case we need
|
||||||
|
# to restore it...
|
||||||
|
old_logfile = self.shell.logfile
|
||||||
|
if logfname:
|
||||||
|
logfname = os.path.expanduser(logfname)
|
||||||
|
self.shell.logfile = logfname
|
||||||
|
|
||||||
|
loghead = u'# yap_ipython log file\n\n'
|
||||||
|
try:
|
||||||
|
logger.logstart(logfname, loghead, logmode, log_output, timestamp,
|
||||||
|
log_raw_input)
|
||||||
|
except:
|
||||||
|
self.shell.logfile = old_logfile
|
||||||
|
warn("Couldn't start log: %s" % sys.exc_info()[1])
|
||||||
|
else:
|
||||||
|
# log input history up to this point, optionally interleaving
|
||||||
|
# output if requested
|
||||||
|
|
||||||
|
if timestamp:
|
||||||
|
# disable timestamping for the previous history, since we've
|
||||||
|
# lost those already (no time machine here).
|
||||||
|
logger.timestamp = False
|
||||||
|
|
||||||
|
if log_raw_input:
|
||||||
|
input_hist = self.shell.history_manager.input_hist_raw
|
||||||
|
else:
|
||||||
|
input_hist = self.shell.history_manager.input_hist_parsed
|
||||||
|
|
||||||
|
if log_output:
|
||||||
|
log_write = logger.log_write
|
||||||
|
output_hist = self.shell.history_manager.output_hist
|
||||||
|
for n in range(1,len(input_hist)-1):
|
||||||
|
log_write(input_hist[n].rstrip() + u'\n')
|
||||||
|
if n in output_hist:
|
||||||
|
log_write(repr(output_hist[n]),'output')
|
||||||
|
else:
|
||||||
|
logger.log_write(u'\n'.join(input_hist[1:]))
|
||||||
|
logger.log_write(u'\n')
|
||||||
|
if timestamp:
|
||||||
|
# re-enable timestamping
|
||||||
|
logger.timestamp = True
|
||||||
|
|
||||||
|
if not (self.quiet or quiet):
|
||||||
|
print ('Activating auto-logging. '
|
||||||
|
'Current session state plus future input saved.')
|
||||||
|
logger.logstate()
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def logstop(self, parameter_s=''):
|
||||||
|
"""Fully stop logging and close log file.
|
||||||
|
|
||||||
|
In order to start logging again, a new %logstart call needs to be made,
|
||||||
|
possibly (though not necessarily) with a new filename, mode and other
|
||||||
|
options."""
|
||||||
|
self.shell.logger.logstop()
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def logoff(self, parameter_s=''):
|
||||||
|
"""Temporarily stop logging.
|
||||||
|
|
||||||
|
You must have previously started logging."""
|
||||||
|
self.shell.logger.switch_log(0)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def logon(self, parameter_s=''):
|
||||||
|
"""Restart logging.
|
||||||
|
|
||||||
|
This function is for restarting logging which you've temporarily
|
||||||
|
stopped with %logoff. For starting logging for the first time, you
|
||||||
|
must use the %logstart function, which allows you to specify an
|
||||||
|
optional log filename."""
|
||||||
|
|
||||||
|
self.shell.logger.switch_log(1)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def logstate(self, parameter_s=''):
|
||||||
|
"""Print the status of the logging system."""
|
||||||
|
|
||||||
|
self.shell.logger.logstate()
|
702
packages/python/yap_kernel/yap_ipython/core/magics/namespace.py
Normal file
702
packages/python/yap_kernel/yap_ipython/core/magics/namespace.py
Normal file
@ -0,0 +1,702 @@
|
|||||||
|
"""Implementation of namespace-related magic functions.
|
||||||
|
"""
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (c) 2012 The yap_ipython Development Team.
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
#
|
||||||
|
# The full license is in the file COPYING.txt, distributed with this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Stdlib
|
||||||
|
import gc
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Our own packages
|
||||||
|
from yap_ipython.core import page
|
||||||
|
from yap_ipython.core.error import StdinNotImplementedError, UsageError
|
||||||
|
from yap_ipython.core.magic import Magics, magics_class, line_magic
|
||||||
|
from yap_ipython.testing.skipdoctest import skip_doctest
|
||||||
|
from yap_ipython.utils.encoding import DEFAULT_ENCODING
|
||||||
|
from yap_ipython.utils.openpy import read_py_file
|
||||||
|
from yap_ipython.utils.path import get_py_filename
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Magic implementation classes
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@magics_class
|
||||||
|
class NamespaceMagics(Magics):
|
||||||
|
"""Magics to manage various aspects of the user's namespace.
|
||||||
|
|
||||||
|
These include listing variables, introspecting into them, etc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def pinfo(self, parameter_s='', namespaces=None):
|
||||||
|
"""Provide detailed information about an object.
|
||||||
|
|
||||||
|
'%pinfo object' is just a synonym for object? or ?object."""
|
||||||
|
|
||||||
|
#print 'pinfo par: <%s>' % parameter_s # dbg
|
||||||
|
# detail_level: 0 -> obj? , 1 -> obj??
|
||||||
|
detail_level = 0
|
||||||
|
# We need to detect if we got called as 'pinfo pinfo foo', which can
|
||||||
|
# happen if the user types 'pinfo foo?' at the cmd line.
|
||||||
|
pinfo,qmark1,oname,qmark2 = \
|
||||||
|
re.match('(pinfo )?(\?*)(.*?)(\??$)',parameter_s).groups()
|
||||||
|
if pinfo or qmark1 or qmark2:
|
||||||
|
detail_level = 1
|
||||||
|
if "*" in oname:
|
||||||
|
self.psearch(oname)
|
||||||
|
else:
|
||||||
|
self.shell._inspect('pinfo', oname, detail_level=detail_level,
|
||||||
|
namespaces=namespaces)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def pinfo2(self, parameter_s='', namespaces=None):
|
||||||
|
"""Provide extra detailed information about an object.
|
||||||
|
|
||||||
|
'%pinfo2 object' is just a synonym for object?? or ??object."""
|
||||||
|
self.shell._inspect('pinfo', parameter_s, detail_level=1,
|
||||||
|
namespaces=namespaces)
|
||||||
|
|
||||||
|
@skip_doctest
|
||||||
|
@line_magic
|
||||||
|
def pdef(self, parameter_s='', namespaces=None):
|
||||||
|
"""Print the call signature for any callable object.
|
||||||
|
|
||||||
|
If the object is a class, print the constructor information.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
::
|
||||||
|
|
||||||
|
In [3]: %pdef urllib.urlopen
|
||||||
|
urllib.urlopen(url, data=None, proxies=None)
|
||||||
|
"""
|
||||||
|
self.shell._inspect('pdef',parameter_s, namespaces)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def pdoc(self, parameter_s='', namespaces=None):
|
||||||
|
"""Print the docstring for an object.
|
||||||
|
|
||||||
|
If the given object is a class, it will print both the class and the
|
||||||
|
constructor docstrings."""
|
||||||
|
self.shell._inspect('pdoc',parameter_s, namespaces)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def psource(self, parameter_s='', namespaces=None):
|
||||||
|
"""Print (or run through pager) the source code for an object."""
|
||||||
|
if not parameter_s:
|
||||||
|
raise UsageError('Missing object name.')
|
||||||
|
self.shell._inspect('psource',parameter_s, namespaces)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def pfile(self, parameter_s='', namespaces=None):
|
||||||
|
"""Print (or run through pager) the file where an object is defined.
|
||||||
|
|
||||||
|
The file opens at the line where the object definition begins. yap_ipython
|
||||||
|
will honor the environment variable PAGER if set, and otherwise will
|
||||||
|
do its best to print the file in a convenient form.
|
||||||
|
|
||||||
|
If the given argument is not an object currently defined, yap_ipython will
|
||||||
|
try to interpret it as a filename (automatically adding a .py extension
|
||||||
|
if needed). You can thus use %pfile as a syntax highlighting code
|
||||||
|
viewer."""
|
||||||
|
|
||||||
|
# first interpret argument as an object name
|
||||||
|
out = self.shell._inspect('pfile',parameter_s, namespaces)
|
||||||
|
# if not, try the input as a filename
|
||||||
|
if out == 'not found':
|
||||||
|
try:
|
||||||
|
filename = get_py_filename(parameter_s)
|
||||||
|
except IOError as msg:
|
||||||
|
print(msg)
|
||||||
|
return
|
||||||
|
page.page(self.shell.pycolorize(read_py_file(filename, skip_encoding_cookie=False)))
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def psearch(self, parameter_s=''):
|
||||||
|
"""Search for object in namespaces by wildcard.
|
||||||
|
|
||||||
|
%psearch [options] PATTERN [OBJECT TYPE]
|
||||||
|
|
||||||
|
Note: ? can be used as a synonym for %psearch, at the beginning or at
|
||||||
|
the end: both a*? and ?a* are equivalent to '%psearch a*'. Still, the
|
||||||
|
rest of the command line must be unchanged (options come first), so
|
||||||
|
for example the following forms are equivalent
|
||||||
|
|
||||||
|
%psearch -i a* function
|
||||||
|
-i a* function?
|
||||||
|
?-i a* function
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
|
||||||
|
PATTERN
|
||||||
|
|
||||||
|
where PATTERN is a string containing * as a wildcard similar to its
|
||||||
|
use in a shell. The pattern is matched in all namespaces on the
|
||||||
|
search path. By default objects starting with a single _ are not
|
||||||
|
matched, many yap_ipython generated objects have a single
|
||||||
|
underscore. The default is case insensitive matching. Matching is
|
||||||
|
also done on the attributes of objects and not only on the objects
|
||||||
|
in a module.
|
||||||
|
|
||||||
|
[OBJECT TYPE]
|
||||||
|
|
||||||
|
Is the name of a python type from the types module. The name is
|
||||||
|
given in lowercase without the ending type, ex. StringType is
|
||||||
|
written string. By adding a type here only objects matching the
|
||||||
|
given type are matched. Using all here makes the pattern match all
|
||||||
|
types (this is the default).
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-a: makes the pattern match even objects whose names start with a
|
||||||
|
single underscore. These names are normally omitted from the
|
||||||
|
search.
|
||||||
|
|
||||||
|
-i/-c: make the pattern case insensitive/sensitive. If neither of
|
||||||
|
these options are given, the default is read from your configuration
|
||||||
|
file, with the option ``InteractiveShell.wildcards_case_sensitive``.
|
||||||
|
If this option is not specified in your configuration file, yap_ipython's
|
||||||
|
internal default is to do a case sensitive search.
|
||||||
|
|
||||||
|
-e/-s NAMESPACE: exclude/search a given namespace. The pattern you
|
||||||
|
specify can be searched in any of the following namespaces:
|
||||||
|
'builtin', 'user', 'user_global','internal', 'alias', where
|
||||||
|
'builtin' and 'user' are the search defaults. Note that you should
|
||||||
|
not use quotes when specifying namespaces.
|
||||||
|
|
||||||
|
'Builtin' contains the python module builtin, 'user' contains all
|
||||||
|
user data, 'alias' only contain the shell aliases and no python
|
||||||
|
objects, 'internal' contains objects used by yap_ipython. The
|
||||||
|
'user_global' namespace is only used by embedded yap_ipython instances,
|
||||||
|
and it contains module-level globals. You can add namespaces to the
|
||||||
|
search with -s or exclude them with -e (these options can be given
|
||||||
|
more than once).
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
::
|
||||||
|
|
||||||
|
%psearch a* -> objects beginning with an a
|
||||||
|
%psearch -e builtin a* -> objects NOT in the builtin space starting in a
|
||||||
|
%psearch a* function -> all functions beginning with an a
|
||||||
|
%psearch re.e* -> objects beginning with an e in module re
|
||||||
|
%psearch r*.e* -> objects that start with e in modules starting in r
|
||||||
|
%psearch r*.* string -> all strings in modules beginning with r
|
||||||
|
|
||||||
|
Case sensitive search::
|
||||||
|
|
||||||
|
%psearch -c a* list all object beginning with lower case a
|
||||||
|
|
||||||
|
Show objects beginning with a single _::
|
||||||
|
|
||||||
|
%psearch -a _* list objects beginning with a single underscore
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
parameter_s.encode('ascii')
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
print('Python identifiers can only contain ascii characters.')
|
||||||
|
return
|
||||||
|
|
||||||
|
# default namespaces to be searched
|
||||||
|
def_search = ['user_local', 'user_global', 'builtin']
|
||||||
|
|
||||||
|
# Process options/args
|
||||||
|
opts,args = self.parse_options(parameter_s,'cias:e:',list_all=True)
|
||||||
|
opt = opts.get
|
||||||
|
shell = self.shell
|
||||||
|
psearch = shell.inspector.psearch
|
||||||
|
|
||||||
|
# select case options
|
||||||
|
if 'i' in opts:
|
||||||
|
ignore_case = True
|
||||||
|
elif 'c' in opts:
|
||||||
|
ignore_case = False
|
||||||
|
else:
|
||||||
|
ignore_case = not shell.wildcards_case_sensitive
|
||||||
|
|
||||||
|
# Build list of namespaces to search from user options
|
||||||
|
def_search.extend(opt('s',[]))
|
||||||
|
ns_exclude = ns_exclude=opt('e',[])
|
||||||
|
ns_search = [nm for nm in def_search if nm not in ns_exclude]
|
||||||
|
|
||||||
|
# Call the actual search
|
||||||
|
try:
|
||||||
|
psearch(args,shell.ns_table,ns_search,
|
||||||
|
show_all=opt('a'),ignore_case=ignore_case)
|
||||||
|
except:
|
||||||
|
shell.showtraceback()
|
||||||
|
|
||||||
|
@skip_doctest
|
||||||
|
@line_magic
|
||||||
|
def who_ls(self, parameter_s=''):
|
||||||
|
"""Return a sorted list of all interactive variables.
|
||||||
|
|
||||||
|
If arguments are given, only variables of types matching these
|
||||||
|
arguments are returned.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
Define two variables and list them with who_ls::
|
||||||
|
|
||||||
|
In [1]: alpha = 123
|
||||||
|
|
||||||
|
In [2]: beta = 'test'
|
||||||
|
|
||||||
|
In [3]: %who_ls
|
||||||
|
Out[3]: ['alpha', 'beta']
|
||||||
|
|
||||||
|
In [4]: %who_ls int
|
||||||
|
Out[4]: ['alpha']
|
||||||
|
|
||||||
|
In [5]: %who_ls str
|
||||||
|
Out[5]: ['beta']
|
||||||
|
"""
|
||||||
|
|
||||||
|
user_ns = self.shell.user_ns
|
||||||
|
user_ns_hidden = self.shell.user_ns_hidden
|
||||||
|
nonmatching = object() # This can never be in user_ns
|
||||||
|
out = [ i for i in user_ns
|
||||||
|
if not i.startswith('_') \
|
||||||
|
and (user_ns[i] is not user_ns_hidden.get(i, nonmatching)) ]
|
||||||
|
|
||||||
|
typelist = parameter_s.split()
|
||||||
|
if typelist:
|
||||||
|
typeset = set(typelist)
|
||||||
|
out = [i for i in out if type(user_ns[i]).__name__ in typeset]
|
||||||
|
|
||||||
|
out.sort()
|
||||||
|
return out
|
||||||
|
|
||||||
|
@skip_doctest
|
||||||
|
@line_magic
|
||||||
|
def who(self, parameter_s=''):
|
||||||
|
"""Print all interactive variables, with some minimal formatting.
|
||||||
|
|
||||||
|
If any arguments are given, only variables whose type matches one of
|
||||||
|
these are printed. For example::
|
||||||
|
|
||||||
|
%who function str
|
||||||
|
|
||||||
|
will only list functions and strings, excluding all other types of
|
||||||
|
variables. To find the proper type names, simply use type(var) at a
|
||||||
|
command line to see how python prints type names. For example:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
In [1]: type('hello')\\
|
||||||
|
Out[1]: <type 'str'>
|
||||||
|
|
||||||
|
indicates that the type name for strings is 'str'.
|
||||||
|
|
||||||
|
``%who`` always excludes executed names loaded through your configuration
|
||||||
|
file and things which are internal to yap_ipython.
|
||||||
|
|
||||||
|
This is deliberate, as typically you may load many modules and the
|
||||||
|
purpose of %who is to show you only what you've manually defined.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
Define two variables and list them with who::
|
||||||
|
|
||||||
|
In [1]: alpha = 123
|
||||||
|
|
||||||
|
In [2]: beta = 'test'
|
||||||
|
|
||||||
|
In [3]: %who
|
||||||
|
alpha beta
|
||||||
|
|
||||||
|
In [4]: %who int
|
||||||
|
alpha
|
||||||
|
|
||||||
|
In [5]: %who str
|
||||||
|
beta
|
||||||
|
"""
|
||||||
|
|
||||||
|
varlist = self.who_ls(parameter_s)
|
||||||
|
if not varlist:
|
||||||
|
if parameter_s:
|
||||||
|
print('No variables match your requested type.')
|
||||||
|
else:
|
||||||
|
print('Interactive namespace is empty.')
|
||||||
|
return
|
||||||
|
|
||||||
|
# if we have variables, move on...
|
||||||
|
count = 0
|
||||||
|
for i in varlist:
|
||||||
|
print(i+'\t', end=' ')
|
||||||
|
count += 1
|
||||||
|
if count > 8:
|
||||||
|
count = 0
|
||||||
|
print()
|
||||||
|
print()
|
||||||
|
|
||||||
|
@skip_doctest
|
||||||
|
@line_magic
|
||||||
|
def whos(self, parameter_s=''):
|
||||||
|
"""Like %who, but gives some extra information about each variable.
|
||||||
|
|
||||||
|
The same type filtering of %who can be applied here.
|
||||||
|
|
||||||
|
For all variables, the type is printed. Additionally it prints:
|
||||||
|
|
||||||
|
- For {},[],(): their length.
|
||||||
|
|
||||||
|
- For numpy arrays, a summary with shape, number of
|
||||||
|
elements, typecode and size in memory.
|
||||||
|
|
||||||
|
- Everything else: a string representation, snipping their middle if
|
||||||
|
too long.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
Define two variables and list them with whos::
|
||||||
|
|
||||||
|
In [1]: alpha = 123
|
||||||
|
|
||||||
|
In [2]: beta = 'test'
|
||||||
|
|
||||||
|
In [3]: %whos
|
||||||
|
Variable Type Data/Info
|
||||||
|
--------------------------------
|
||||||
|
alpha int 123
|
||||||
|
beta str test
|
||||||
|
"""
|
||||||
|
|
||||||
|
varnames = self.who_ls(parameter_s)
|
||||||
|
if not varnames:
|
||||||
|
if parameter_s:
|
||||||
|
print('No variables match your requested type.')
|
||||||
|
else:
|
||||||
|
print('Interactive namespace is empty.')
|
||||||
|
return
|
||||||
|
|
||||||
|
# if we have variables, move on...
|
||||||
|
|
||||||
|
# for these types, show len() instead of data:
|
||||||
|
seq_types = ['dict', 'list', 'tuple']
|
||||||
|
|
||||||
|
# for numpy arrays, display summary info
|
||||||
|
ndarray_type = None
|
||||||
|
if 'numpy' in sys.modules:
|
||||||
|
try:
|
||||||
|
from numpy import ndarray
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
ndarray_type = ndarray.__name__
|
||||||
|
|
||||||
|
# Find all variable names and types so we can figure out column sizes
|
||||||
|
|
||||||
|
# some types are well known and can be shorter
|
||||||
|
abbrevs = {'yap_ipython.core.macro.Macro' : 'Macro'}
|
||||||
|
def type_name(v):
|
||||||
|
tn = type(v).__name__
|
||||||
|
return abbrevs.get(tn,tn)
|
||||||
|
|
||||||
|
varlist = [self.shell.user_ns[n] for n in varnames]
|
||||||
|
|
||||||
|
typelist = []
|
||||||
|
for vv in varlist:
|
||||||
|
tt = type_name(vv)
|
||||||
|
|
||||||
|
if tt=='instance':
|
||||||
|
typelist.append( abbrevs.get(str(vv.__class__),
|
||||||
|
str(vv.__class__)))
|
||||||
|
else:
|
||||||
|
typelist.append(tt)
|
||||||
|
|
||||||
|
# column labels and # of spaces as separator
|
||||||
|
varlabel = 'Variable'
|
||||||
|
typelabel = 'Type'
|
||||||
|
datalabel = 'Data/Info'
|
||||||
|
colsep = 3
|
||||||
|
# variable format strings
|
||||||
|
vformat = "{0:<{varwidth}}{1:<{typewidth}}"
|
||||||
|
aformat = "%s: %s elems, type `%s`, %s bytes"
|
||||||
|
# find the size of the columns to format the output nicely
|
||||||
|
varwidth = max(max(map(len,varnames)), len(varlabel)) + colsep
|
||||||
|
typewidth = max(max(map(len,typelist)), len(typelabel)) + colsep
|
||||||
|
# table header
|
||||||
|
print(varlabel.ljust(varwidth) + typelabel.ljust(typewidth) + \
|
||||||
|
' '+datalabel+'\n' + '-'*(varwidth+typewidth+len(datalabel)+1))
|
||||||
|
# and the table itself
|
||||||
|
kb = 1024
|
||||||
|
Mb = 1048576 # kb**2
|
||||||
|
for vname,var,vtype in zip(varnames,varlist,typelist):
|
||||||
|
print(vformat.format(vname, vtype, varwidth=varwidth, typewidth=typewidth), end=' ')
|
||||||
|
if vtype in seq_types:
|
||||||
|
print("n="+str(len(var)))
|
||||||
|
elif vtype == ndarray_type:
|
||||||
|
vshape = str(var.shape).replace(',','').replace(' ','x')[1:-1]
|
||||||
|
if vtype==ndarray_type:
|
||||||
|
# numpy
|
||||||
|
vsize = var.size
|
||||||
|
vbytes = vsize*var.itemsize
|
||||||
|
vdtype = var.dtype
|
||||||
|
|
||||||
|
if vbytes < 100000:
|
||||||
|
print(aformat % (vshape, vsize, vdtype, vbytes))
|
||||||
|
else:
|
||||||
|
print(aformat % (vshape, vsize, vdtype, vbytes), end=' ')
|
||||||
|
if vbytes < Mb:
|
||||||
|
print('(%s kb)' % (vbytes/kb,))
|
||||||
|
else:
|
||||||
|
print('(%s Mb)' % (vbytes/Mb,))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
vstr = str(var)
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
vstr = var.encode(DEFAULT_ENCODING,
|
||||||
|
'backslashreplace')
|
||||||
|
except:
|
||||||
|
vstr = "<object with id %d (str() failed)>" % id(var)
|
||||||
|
vstr = vstr.replace('\n', '\\n')
|
||||||
|
if len(vstr) < 50:
|
||||||
|
print(vstr)
|
||||||
|
else:
|
||||||
|
print(vstr[:25] + "<...>" + vstr[-25:])
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def reset(self, parameter_s=''):
|
||||||
|
"""Resets the namespace by removing all names defined by the user, if
|
||||||
|
called without arguments, or by removing some types of objects, such
|
||||||
|
as everything currently in yap_ipython's In[] and Out[] containers (see
|
||||||
|
the parameters for details).
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
-f : force reset without asking for confirmation.
|
||||||
|
|
||||||
|
-s : 'Soft' reset: Only clears your namespace, leaving history intact.
|
||||||
|
References to objects may be kept. By default (without this option),
|
||||||
|
we do a 'hard' reset, giving you a new session and removing all
|
||||||
|
references to objects from the current session.
|
||||||
|
|
||||||
|
in : reset input history
|
||||||
|
|
||||||
|
out : reset output history
|
||||||
|
|
||||||
|
dhist : reset directory history
|
||||||
|
|
||||||
|
array : reset only variables that are NumPy arrays
|
||||||
|
|
||||||
|
See Also
|
||||||
|
--------
|
||||||
|
reset_selective : invoked as ``%reset_selective``
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
::
|
||||||
|
|
||||||
|
In [6]: a = 1
|
||||||
|
|
||||||
|
In [7]: a
|
||||||
|
Out[7]: 1
|
||||||
|
|
||||||
|
In [8]: 'a' in _ip.user_ns
|
||||||
|
Out[8]: True
|
||||||
|
|
||||||
|
In [9]: %reset -f
|
||||||
|
|
||||||
|
In [1]: 'a' in _ip.user_ns
|
||||||
|
Out[1]: False
|
||||||
|
|
||||||
|
In [2]: %reset -f in
|
||||||
|
Flushing input history
|
||||||
|
|
||||||
|
In [3]: %reset -f dhist in
|
||||||
|
Flushing directory history
|
||||||
|
Flushing input history
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
Calling this magic from clients that do not implement standard input,
|
||||||
|
such as the ipython notebook interface, will reset the namespace
|
||||||
|
without confirmation.
|
||||||
|
"""
|
||||||
|
opts, args = self.parse_options(parameter_s,'sf', mode='list')
|
||||||
|
if 'f' in opts:
|
||||||
|
ans = True
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
ans = self.shell.ask_yes_no(
|
||||||
|
"Once deleted, variables cannot be recovered. Proceed (y/[n])?",
|
||||||
|
default='n')
|
||||||
|
except StdinNotImplementedError:
|
||||||
|
ans = True
|
||||||
|
if not ans:
|
||||||
|
print('Nothing done.')
|
||||||
|
return
|
||||||
|
|
||||||
|
if 's' in opts: # Soft reset
|
||||||
|
user_ns = self.shell.user_ns
|
||||||
|
for i in self.who_ls():
|
||||||
|
del(user_ns[i])
|
||||||
|
elif len(args) == 0: # Hard reset
|
||||||
|
self.shell.reset(new_session = False)
|
||||||
|
|
||||||
|
# reset in/out/dhist/array: previously extensinions/clearcmd.py
|
||||||
|
ip = self.shell
|
||||||
|
user_ns = self.shell.user_ns # local lookup, heavily used
|
||||||
|
|
||||||
|
for target in args:
|
||||||
|
target = target.lower() # make matches case insensitive
|
||||||
|
if target == 'out':
|
||||||
|
print("Flushing output cache (%d entries)" % len(user_ns['_oh']))
|
||||||
|
self.shell.displayhook.flush()
|
||||||
|
|
||||||
|
elif target == 'in':
|
||||||
|
print("Flushing input history")
|
||||||
|
pc = self.shell.displayhook.prompt_count + 1
|
||||||
|
for n in range(1, pc):
|
||||||
|
key = '_i'+repr(n)
|
||||||
|
user_ns.pop(key,None)
|
||||||
|
user_ns.update(dict(_i=u'',_ii=u'',_iii=u''))
|
||||||
|
hm = ip.history_manager
|
||||||
|
# don't delete these, as %save and %macro depending on the
|
||||||
|
# length of these lists to be preserved
|
||||||
|
hm.input_hist_parsed[:] = [''] * pc
|
||||||
|
hm.input_hist_raw[:] = [''] * pc
|
||||||
|
# hm has internal machinery for _i,_ii,_iii, clear it out
|
||||||
|
hm._i = hm._ii = hm._iii = hm._i00 = u''
|
||||||
|
|
||||||
|
elif target == 'array':
|
||||||
|
# Support cleaning up numpy arrays
|
||||||
|
try:
|
||||||
|
from numpy import ndarray
|
||||||
|
# This must be done with items and not iteritems because
|
||||||
|
# we're going to modify the dict in-place.
|
||||||
|
for x,val in list(user_ns.items()):
|
||||||
|
if isinstance(val,ndarray):
|
||||||
|
del user_ns[x]
|
||||||
|
except ImportError:
|
||||||
|
print("reset array only works if Numpy is available.")
|
||||||
|
|
||||||
|
elif target == 'dhist':
|
||||||
|
print("Flushing directory history")
|
||||||
|
del user_ns['_dh'][:]
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("Don't know how to reset ", end=' ')
|
||||||
|
print(target + ", please run `%reset?` for details")
|
||||||
|
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def reset_selective(self, parameter_s=''):
|
||||||
|
"""Resets the namespace by removing names defined by the user.
|
||||||
|
|
||||||
|
Input/Output history are left around in case you need them.
|
||||||
|
|
||||||
|
%reset_selective [-f] regex
|
||||||
|
|
||||||
|
No action is taken if regex is not included
|
||||||
|
|
||||||
|
Options
|
||||||
|
-f : force reset without asking for confirmation.
|
||||||
|
|
||||||
|
See Also
|
||||||
|
--------
|
||||||
|
reset : invoked as ``%reset``
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
We first fully reset the namespace so your output looks identical to
|
||||||
|
this example for pedagogical reasons; in practice you do not need a
|
||||||
|
full reset::
|
||||||
|
|
||||||
|
In [1]: %reset -f
|
||||||
|
|
||||||
|
Now, with a clean namespace we can make a few variables and use
|
||||||
|
``%reset_selective`` to only delete names that match our regexp::
|
||||||
|
|
||||||
|
In [2]: a=1; b=2; c=3; b1m=4; b2m=5; b3m=6; b4m=7; b2s=8
|
||||||
|
|
||||||
|
In [3]: who_ls
|
||||||
|
Out[3]: ['a', 'b', 'b1m', 'b2m', 'b2s', 'b3m', 'b4m', 'c']
|
||||||
|
|
||||||
|
In [4]: %reset_selective -f b[2-3]m
|
||||||
|
|
||||||
|
In [5]: who_ls
|
||||||
|
Out[5]: ['a', 'b', 'b1m', 'b2s', 'b4m', 'c']
|
||||||
|
|
||||||
|
In [6]: %reset_selective -f d
|
||||||
|
|
||||||
|
In [7]: who_ls
|
||||||
|
Out[7]: ['a', 'b', 'b1m', 'b2s', 'b4m', 'c']
|
||||||
|
|
||||||
|
In [8]: %reset_selective -f c
|
||||||
|
|
||||||
|
In [9]: who_ls
|
||||||
|
Out[9]: ['a', 'b', 'b1m', 'b2s', 'b4m']
|
||||||
|
|
||||||
|
In [10]: %reset_selective -f b
|
||||||
|
|
||||||
|
In [11]: who_ls
|
||||||
|
Out[11]: ['a']
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
Calling this magic from clients that do not implement standard input,
|
||||||
|
such as the ipython notebook interface, will reset the namespace
|
||||||
|
without confirmation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
opts, regex = self.parse_options(parameter_s,'f')
|
||||||
|
|
||||||
|
if 'f' in opts:
|
||||||
|
ans = True
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
ans = self.shell.ask_yes_no(
|
||||||
|
"Once deleted, variables cannot be recovered. Proceed (y/[n])? ",
|
||||||
|
default='n')
|
||||||
|
except StdinNotImplementedError:
|
||||||
|
ans = True
|
||||||
|
if not ans:
|
||||||
|
print('Nothing done.')
|
||||||
|
return
|
||||||
|
user_ns = self.shell.user_ns
|
||||||
|
if not regex:
|
||||||
|
print('No regex pattern specified. Nothing done.')
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
m = re.compile(regex)
|
||||||
|
except TypeError:
|
||||||
|
raise TypeError('regex must be a string or compiled pattern')
|
||||||
|
for i in self.who_ls():
|
||||||
|
if m.search(i):
|
||||||
|
del(user_ns[i])
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def xdel(self, parameter_s=''):
|
||||||
|
"""Delete a variable, trying to clear it from anywhere that
|
||||||
|
yap_ipython's machinery has references to it. By default, this uses
|
||||||
|
the identity of the named object in the user namespace to remove
|
||||||
|
references held under other names. The object is also removed
|
||||||
|
from the output history.
|
||||||
|
|
||||||
|
Options
|
||||||
|
-n : Delete the specified name from all namespaces, without
|
||||||
|
checking their identity.
|
||||||
|
"""
|
||||||
|
opts, varname = self.parse_options(parameter_s,'n')
|
||||||
|
try:
|
||||||
|
self.shell.del_var(varname, ('n' in opts))
|
||||||
|
except (NameError, ValueError) as e:
|
||||||
|
print(type(e).__name__ +": "+ str(e))
|
792
packages/python/yap_kernel/yap_ipython/core/magics/osm.py
Normal file
792
packages/python/yap_kernel/yap_ipython/core/magics/osm.py
Normal file
@ -0,0 +1,792 @@
|
|||||||
|
"""Implementation of magic functions for interaction with the OS.
|
||||||
|
|
||||||
|
Note: this module is named 'osm' instead of 'os' to avoid a collision with the
|
||||||
|
builtin.
|
||||||
|
"""
|
||||||
|
# Copyright (c) yap_ipython Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
from yap_ipython.core import magic_arguments
|
||||||
|
from yap_ipython.core import oinspect
|
||||||
|
from yap_ipython.core import page
|
||||||
|
from yap_ipython.core.alias import AliasError, Alias
|
||||||
|
from yap_ipython.core.error import UsageError
|
||||||
|
from yap_ipython.core.magic import (
|
||||||
|
Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic
|
||||||
|
)
|
||||||
|
from yap_ipython.testing.skipdoctest import skip_doctest
|
||||||
|
from yap_ipython.utils.openpy import source_to_unicode
|
||||||
|
from yap_ipython.utils.process import abbrev_cwd
|
||||||
|
from yap_ipython.utils.terminal import set_term_title
|
||||||
|
|
||||||
|
|
||||||
|
@magics_class
|
||||||
|
class OSMagics(Magics):
|
||||||
|
"""Magics to interact with the underlying OS (shell-type functionality).
|
||||||
|
"""
|
||||||
|
|
||||||
|
@skip_doctest
|
||||||
|
@line_magic
|
||||||
|
def alias(self, parameter_s=''):
|
||||||
|
"""Define an alias for a system command.
|
||||||
|
|
||||||
|
'%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd'
|
||||||
|
|
||||||
|
Then, typing 'alias_name params' will execute the system command 'cmd
|
||||||
|
params' (from your underlying operating system).
|
||||||
|
|
||||||
|
Aliases have lower precedence than magic functions and Python normal
|
||||||
|
variables, so if 'foo' is both a Python variable and an alias, the
|
||||||
|
alias can not be executed until 'del foo' removes the Python variable.
|
||||||
|
|
||||||
|
You can use the %l specifier in an alias definition to represent the
|
||||||
|
whole line when the alias is called. For example::
|
||||||
|
|
||||||
|
In [2]: alias bracket echo "Input in brackets: <%l>"
|
||||||
|
In [3]: bracket hello world
|
||||||
|
Input in brackets: <hello world>
|
||||||
|
|
||||||
|
You can also define aliases with parameters using %s specifiers (one
|
||||||
|
per parameter)::
|
||||||
|
|
||||||
|
In [1]: alias parts echo first %s second %s
|
||||||
|
In [2]: %parts A B
|
||||||
|
first A second B
|
||||||
|
In [3]: %parts A
|
||||||
|
Incorrect number of arguments: 2 expected.
|
||||||
|
parts is an alias to: 'echo first %s second %s'
|
||||||
|
|
||||||
|
Note that %l and %s are mutually exclusive. You can only use one or
|
||||||
|
the other in your aliases.
|
||||||
|
|
||||||
|
Aliases expand Python variables just like system calls using ! or !!
|
||||||
|
do: all expressions prefixed with '$' get expanded. For details of
|
||||||
|
the semantic rules, see PEP-215:
|
||||||
|
http://www.python.org/peps/pep-0215.html. This is the library used by
|
||||||
|
yap_ipython for variable expansion. If you want to access a true shell
|
||||||
|
variable, an extra $ is necessary to prevent its expansion by
|
||||||
|
yap_ipython::
|
||||||
|
|
||||||
|
In [6]: alias show echo
|
||||||
|
In [7]: PATH='A Python string'
|
||||||
|
In [8]: show $PATH
|
||||||
|
A Python string
|
||||||
|
In [9]: show $$PATH
|
||||||
|
/usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:...
|
||||||
|
|
||||||
|
You can use the alias facility to access all of $PATH. See the %rehashx
|
||||||
|
function, which automatically creates aliases for the contents of your
|
||||||
|
$PATH.
|
||||||
|
|
||||||
|
If called with no parameters, %alias prints the current alias table
|
||||||
|
for your system. For posix systems, the default aliases are 'cat',
|
||||||
|
'cp', 'mv', 'rm', 'rmdir', and 'mkdir', and other platform-specific
|
||||||
|
aliases are added. For windows-based systems, the default aliases are
|
||||||
|
'copy', 'ddir', 'echo', 'ls', 'ldir', 'mkdir', 'ren', and 'rmdir'.
|
||||||
|
|
||||||
|
You can see the definition of alias by adding a question mark in the
|
||||||
|
end::
|
||||||
|
|
||||||
|
In [1]: cat?
|
||||||
|
Repr: <alias cat for 'cat'>"""
|
||||||
|
|
||||||
|
par = parameter_s.strip()
|
||||||
|
if not par:
|
||||||
|
aliases = sorted(self.shell.alias_manager.aliases)
|
||||||
|
# stored = self.shell.db.get('stored_aliases', {} )
|
||||||
|
# for k, v in stored:
|
||||||
|
# atab.append(k, v[0])
|
||||||
|
|
||||||
|
print("Total number of aliases:", len(aliases))
|
||||||
|
sys.stdout.flush()
|
||||||
|
return aliases
|
||||||
|
|
||||||
|
# Now try to define a new one
|
||||||
|
try:
|
||||||
|
alias,cmd = par.split(None, 1)
|
||||||
|
except TypeError:
|
||||||
|
print(oinspect.getdoc(self.alias))
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.shell.alias_manager.define_alias(alias, cmd)
|
||||||
|
except AliasError as e:
|
||||||
|
print(e)
|
||||||
|
# end magic_alias
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def unalias(self, parameter_s=''):
|
||||||
|
"""Remove an alias"""
|
||||||
|
|
||||||
|
aname = parameter_s.strip()
|
||||||
|
try:
|
||||||
|
self.shell.alias_manager.undefine_alias(aname)
|
||||||
|
except ValueError as e:
|
||||||
|
print(e)
|
||||||
|
return
|
||||||
|
|
||||||
|
stored = self.shell.db.get('stored_aliases', {} )
|
||||||
|
if aname in stored:
|
||||||
|
print("Removing %stored alias",aname)
|
||||||
|
del stored[aname]
|
||||||
|
self.shell.db['stored_aliases'] = stored
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def rehashx(self, parameter_s=''):
|
||||||
|
"""Update the alias table with all executable files in $PATH.
|
||||||
|
|
||||||
|
rehashx explicitly checks that every entry in $PATH is a file
|
||||||
|
with execute access (os.X_OK).
|
||||||
|
|
||||||
|
Under Windows, it checks executability as a match against a
|
||||||
|
'|'-separated string of extensions, stored in the yap_ipython config
|
||||||
|
variable win_exec_ext. This defaults to 'exe|com|bat'.
|
||||||
|
|
||||||
|
This function also resets the root module cache of module completer,
|
||||||
|
used on slow filesystems.
|
||||||
|
"""
|
||||||
|
from yap_ipython.core.alias import InvalidAliasError
|
||||||
|
|
||||||
|
# for the benefit of module completer in ipy_completers.py
|
||||||
|
del self.shell.db['rootmodules_cache']
|
||||||
|
|
||||||
|
path = [os.path.abspath(os.path.expanduser(p)) for p in
|
||||||
|
os.environ.get('PATH','').split(os.pathsep)]
|
||||||
|
|
||||||
|
syscmdlist = []
|
||||||
|
# Now define isexec in a cross platform manner.
|
||||||
|
if os.name == 'posix':
|
||||||
|
isexec = lambda fname:os.path.isfile(fname) and \
|
||||||
|
os.access(fname,os.X_OK)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
winext = os.environ['pathext'].replace(';','|').replace('.','')
|
||||||
|
except KeyError:
|
||||||
|
winext = 'exe|com|bat|py'
|
||||||
|
if 'py' not in winext:
|
||||||
|
winext += '|py'
|
||||||
|
execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
|
||||||
|
isexec = lambda fname:os.path.isfile(fname) and execre.match(fname)
|
||||||
|
savedir = os.getcwd()
|
||||||
|
|
||||||
|
# Now walk the paths looking for executables to alias.
|
||||||
|
try:
|
||||||
|
# write the whole loop for posix/Windows so we don't have an if in
|
||||||
|
# the innermost part
|
||||||
|
if os.name == 'posix':
|
||||||
|
for pdir in path:
|
||||||
|
try:
|
||||||
|
os.chdir(pdir)
|
||||||
|
dirlist = os.listdir(pdir)
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
for ff in dirlist:
|
||||||
|
if isexec(ff):
|
||||||
|
try:
|
||||||
|
# Removes dots from the name since ipython
|
||||||
|
# will assume names with dots to be python.
|
||||||
|
if not self.shell.alias_manager.is_alias(ff):
|
||||||
|
self.shell.alias_manager.define_alias(
|
||||||
|
ff.replace('.',''), ff)
|
||||||
|
except InvalidAliasError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
syscmdlist.append(ff)
|
||||||
|
else:
|
||||||
|
no_alias = Alias.blacklist
|
||||||
|
for pdir in path:
|
||||||
|
try:
|
||||||
|
os.chdir(pdir)
|
||||||
|
dirlist = os.listdir(pdir)
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
for ff in dirlist:
|
||||||
|
base, ext = os.path.splitext(ff)
|
||||||
|
if isexec(ff) and base.lower() not in no_alias:
|
||||||
|
if ext.lower() == '.exe':
|
||||||
|
ff = base
|
||||||
|
try:
|
||||||
|
# Removes dots from the name since ipython
|
||||||
|
# will assume names with dots to be python.
|
||||||
|
self.shell.alias_manager.define_alias(
|
||||||
|
base.lower().replace('.',''), ff)
|
||||||
|
except InvalidAliasError:
|
||||||
|
pass
|
||||||
|
syscmdlist.append(ff)
|
||||||
|
self.shell.db['syscmdlist'] = syscmdlist
|
||||||
|
finally:
|
||||||
|
os.chdir(savedir)
|
||||||
|
|
||||||
|
@skip_doctest
|
||||||
|
@line_magic
|
||||||
|
def pwd(self, parameter_s=''):
|
||||||
|
"""Return the current working directory path.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
::
|
||||||
|
|
||||||
|
In [9]: pwd
|
||||||
|
Out[9]: '/home/tsuser/sprint/ipython'
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return os.getcwd()
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise UsageError("CWD no longer exists - please use %cd to change directory.")
|
||||||
|
|
||||||
|
@skip_doctest
|
||||||
|
@line_magic
|
||||||
|
def cd(self, parameter_s=''):
|
||||||
|
"""Change the current working directory.
|
||||||
|
|
||||||
|
This command automatically maintains an internal list of directories
|
||||||
|
you visit during your yap_ipython session, in the variable _dh. The
|
||||||
|
command %dhist shows this history nicely formatted. You can also
|
||||||
|
do 'cd -<tab>' to see directory history conveniently.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
cd 'dir': changes to directory 'dir'.
|
||||||
|
|
||||||
|
cd -: changes to the last visited directory.
|
||||||
|
|
||||||
|
cd -<n>: changes to the n-th directory in the directory history.
|
||||||
|
|
||||||
|
cd --foo: change to directory that matches 'foo' in history
|
||||||
|
|
||||||
|
cd -b <bookmark_name>: jump to a bookmark set by %bookmark
|
||||||
|
(note: cd <bookmark_name> is enough if there is no
|
||||||
|
directory <bookmark_name>, but a bookmark with the name exists.)
|
||||||
|
'cd -b <tab>' allows you to tab-complete bookmark names.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-q: quiet. Do not print the working directory after the cd command is
|
||||||
|
executed. By default yap_ipython's cd command does print this directory,
|
||||||
|
since the default prompts do not display path information.
|
||||||
|
|
||||||
|
Note that !cd doesn't work for this purpose because the shell where
|
||||||
|
!command runs is immediately discarded after executing 'command'.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
::
|
||||||
|
|
||||||
|
In [10]: cd parent/child
|
||||||
|
/home/tsuser/parent/child
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
oldcwd = os.getcwd()
|
||||||
|
except FileNotFoundError:
|
||||||
|
# Happens if the CWD has been deleted.
|
||||||
|
oldcwd = None
|
||||||
|
|
||||||
|
numcd = re.match(r'(-)(\d+)$',parameter_s)
|
||||||
|
# jump in directory history by number
|
||||||
|
if numcd:
|
||||||
|
nn = int(numcd.group(2))
|
||||||
|
try:
|
||||||
|
ps = self.shell.user_ns['_dh'][nn]
|
||||||
|
except IndexError:
|
||||||
|
print('The requested directory does not exist in history.')
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
opts = {}
|
||||||
|
elif parameter_s.startswith('--'):
|
||||||
|
ps = None
|
||||||
|
fallback = None
|
||||||
|
pat = parameter_s[2:]
|
||||||
|
dh = self.shell.user_ns['_dh']
|
||||||
|
# first search only by basename (last component)
|
||||||
|
for ent in reversed(dh):
|
||||||
|
if pat in os.path.basename(ent) and os.path.isdir(ent):
|
||||||
|
ps = ent
|
||||||
|
break
|
||||||
|
|
||||||
|
if fallback is None and pat in ent and os.path.isdir(ent):
|
||||||
|
fallback = ent
|
||||||
|
|
||||||
|
# if we have no last part match, pick the first full path match
|
||||||
|
if ps is None:
|
||||||
|
ps = fallback
|
||||||
|
|
||||||
|
if ps is None:
|
||||||
|
print("No matching entry in directory history")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
opts = {}
|
||||||
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
opts, ps = self.parse_options(parameter_s, 'qb', mode='string')
|
||||||
|
# jump to previous
|
||||||
|
if ps == '-':
|
||||||
|
try:
|
||||||
|
ps = self.shell.user_ns['_dh'][-2]
|
||||||
|
except IndexError:
|
||||||
|
raise UsageError('%cd -: No previous directory to change to.')
|
||||||
|
# jump to bookmark if needed
|
||||||
|
else:
|
||||||
|
if not os.path.isdir(ps) or 'b' in opts:
|
||||||
|
bkms = self.shell.db.get('bookmarks', {})
|
||||||
|
|
||||||
|
if ps in bkms:
|
||||||
|
target = bkms[ps]
|
||||||
|
print('(bookmark:%s) -> %s' % (ps, target))
|
||||||
|
ps = target
|
||||||
|
else:
|
||||||
|
if 'b' in opts:
|
||||||
|
raise UsageError("Bookmark '%s' not found. "
|
||||||
|
"Use '%%bookmark -l' to see your bookmarks." % ps)
|
||||||
|
|
||||||
|
# at this point ps should point to the target dir
|
||||||
|
if ps:
|
||||||
|
try:
|
||||||
|
os.chdir(os.path.expanduser(ps))
|
||||||
|
if hasattr(self.shell, 'term_title') and self.shell.term_title:
|
||||||
|
set_term_title(self.shell.term_title_format.format(cwd=abbrev_cwd()))
|
||||||
|
except OSError:
|
||||||
|
print(sys.exc_info()[1])
|
||||||
|
else:
|
||||||
|
cwd = os.getcwd()
|
||||||
|
dhist = self.shell.user_ns['_dh']
|
||||||
|
if oldcwd != cwd:
|
||||||
|
dhist.append(cwd)
|
||||||
|
self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
|
||||||
|
|
||||||
|
else:
|
||||||
|
os.chdir(self.shell.home_dir)
|
||||||
|
if hasattr(self.shell, 'term_title') and self.shell.term_title:
|
||||||
|
set_term_title(self.shell.term_title_format.format(cwd="~"))
|
||||||
|
cwd = os.getcwd()
|
||||||
|
dhist = self.shell.user_ns['_dh']
|
||||||
|
|
||||||
|
if oldcwd != cwd:
|
||||||
|
dhist.append(cwd)
|
||||||
|
self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
|
||||||
|
if not 'q' in opts and self.shell.user_ns['_dh']:
|
||||||
|
print(self.shell.user_ns['_dh'][-1])
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def env(self, parameter_s=''):
|
||||||
|
"""Get, set, or list environment variables.
|
||||||
|
|
||||||
|
Usage:\\
|
||||||
|
|
||||||
|
%env: lists all environment variables/values
|
||||||
|
%env var: get value for var
|
||||||
|
%env var val: set value for var
|
||||||
|
%env var=val: set value for var
|
||||||
|
%env var=$val: set value for var, using python expansion if possible
|
||||||
|
"""
|
||||||
|
if parameter_s.strip():
|
||||||
|
split = '=' if '=' in parameter_s else ' '
|
||||||
|
bits = parameter_s.split(split)
|
||||||
|
if len(bits) == 1:
|
||||||
|
key = parameter_s.strip()
|
||||||
|
if key in os.environ:
|
||||||
|
return os.environ[key]
|
||||||
|
else:
|
||||||
|
err = "Environment does not have key: {0}".format(key)
|
||||||
|
raise UsageError(err)
|
||||||
|
if len(bits) > 1:
|
||||||
|
return self.set_env(parameter_s)
|
||||||
|
return dict(os.environ)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def set_env(self, parameter_s):
|
||||||
|
"""Set environment variables. Assumptions are that either "val" is a
|
||||||
|
name in the user namespace, or val is something that evaluates to a
|
||||||
|
string.
|
||||||
|
|
||||||
|
Usage:\\
|
||||||
|
%set_env var val: set value for var
|
||||||
|
%set_env var=val: set value for var
|
||||||
|
%set_env var=$val: set value for var, using python expansion if possible
|
||||||
|
"""
|
||||||
|
split = '=' if '=' in parameter_s else ' '
|
||||||
|
bits = parameter_s.split(split, 1)
|
||||||
|
if not parameter_s.strip() or len(bits)<2:
|
||||||
|
raise UsageError("usage is 'set_env var=val'")
|
||||||
|
var = bits[0].strip()
|
||||||
|
val = bits[1].strip()
|
||||||
|
if re.match(r'.*\s.*', var):
|
||||||
|
# an environment variable with whitespace is almost certainly
|
||||||
|
# not what the user intended. what's more likely is the wrong
|
||||||
|
# split was chosen, ie for "set_env cmd_args A=B", we chose
|
||||||
|
# '=' for the split and should have chosen ' '. to get around
|
||||||
|
# this, users should just assign directly to os.environ or use
|
||||||
|
# standard magic {var} expansion.
|
||||||
|
err = "refusing to set env var with whitespace: '{0}'"
|
||||||
|
err = err.format(val)
|
||||||
|
raise UsageError(err)
|
||||||
|
os.environ[var] = val
|
||||||
|
print('env: {0}={1}'.format(var,val))
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def pushd(self, parameter_s=''):
|
||||||
|
"""Place the current dir on stack and change directory.
|
||||||
|
|
||||||
|
Usage:\\
|
||||||
|
%pushd ['dirname']
|
||||||
|
"""
|
||||||
|
|
||||||
|
dir_s = self.shell.dir_stack
|
||||||
|
tgt = os.path.expanduser(parameter_s)
|
||||||
|
cwd = os.getcwd().replace(self.shell.home_dir,'~')
|
||||||
|
if tgt:
|
||||||
|
self.cd(parameter_s)
|
||||||
|
dir_s.insert(0,cwd)
|
||||||
|
return self.shell.magic('dirs')
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def popd(self, parameter_s=''):
|
||||||
|
"""Change to directory popped off the top of the stack.
|
||||||
|
"""
|
||||||
|
if not self.shell.dir_stack:
|
||||||
|
raise UsageError("%popd on empty stack")
|
||||||
|
top = self.shell.dir_stack.pop(0)
|
||||||
|
self.cd(top)
|
||||||
|
print("popd ->",top)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def dirs(self, parameter_s=''):
|
||||||
|
"""Return the current directory stack."""
|
||||||
|
|
||||||
|
return self.shell.dir_stack
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def dhist(self, parameter_s=''):
|
||||||
|
"""Print your history of visited directories.
|
||||||
|
|
||||||
|
%dhist -> print full history\\
|
||||||
|
%dhist n -> print last n entries only\\
|
||||||
|
%dhist n1 n2 -> print entries between n1 and n2 (n2 not included)\\
|
||||||
|
|
||||||
|
This history is automatically maintained by the %cd command, and
|
||||||
|
always available as the global list variable _dh. You can use %cd -<n>
|
||||||
|
to go to directory number <n>.
|
||||||
|
|
||||||
|
Note that most of time, you should view directory history by entering
|
||||||
|
cd -<TAB>.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
dh = self.shell.user_ns['_dh']
|
||||||
|
if parameter_s:
|
||||||
|
try:
|
||||||
|
args = map(int,parameter_s.split())
|
||||||
|
except:
|
||||||
|
self.arg_err(self.dhist)
|
||||||
|
return
|
||||||
|
if len(args) == 1:
|
||||||
|
ini,fin = max(len(dh)-(args[0]),0),len(dh)
|
||||||
|
elif len(args) == 2:
|
||||||
|
ini,fin = args
|
||||||
|
fin = min(fin, len(dh))
|
||||||
|
else:
|
||||||
|
self.arg_err(self.dhist)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
ini,fin = 0,len(dh)
|
||||||
|
print('Directory history (kept in _dh)')
|
||||||
|
for i in range(ini, fin):
|
||||||
|
print("%d: %s" % (i, dh[i]))
|
||||||
|
|
||||||
|
@skip_doctest
|
||||||
|
@line_magic
|
||||||
|
def sc(self, parameter_s=''):
|
||||||
|
"""Shell capture - run shell command and capture output (DEPRECATED use !).
|
||||||
|
|
||||||
|
DEPRECATED. Suboptimal, retained for backwards compatibility.
|
||||||
|
|
||||||
|
You should use the form 'var = !command' instead. Example:
|
||||||
|
|
||||||
|
"%sc -l myfiles = ls ~" should now be written as
|
||||||
|
|
||||||
|
"myfiles = !ls ~"
|
||||||
|
|
||||||
|
myfiles.s, myfiles.l and myfiles.n still apply as documented
|
||||||
|
below.
|
||||||
|
|
||||||
|
--
|
||||||
|
%sc [options] varname=command
|
||||||
|
|
||||||
|
yap_ipython will run the given command using commands.getoutput(), and
|
||||||
|
will then update the user's interactive namespace with a variable
|
||||||
|
called varname, containing the value of the call. Your command can
|
||||||
|
contain shell wildcards, pipes, etc.
|
||||||
|
|
||||||
|
The '=' sign in the syntax is mandatory, and the variable name you
|
||||||
|
supply must follow Python's standard conventions for valid names.
|
||||||
|
|
||||||
|
(A special format without variable name exists for internal use)
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-l: list output. Split the output on newlines into a list before
|
||||||
|
assigning it to the given variable. By default the output is stored
|
||||||
|
as a single string.
|
||||||
|
|
||||||
|
-v: verbose. Print the contents of the variable.
|
||||||
|
|
||||||
|
In most cases you should not need to split as a list, because the
|
||||||
|
returned value is a special type of string which can automatically
|
||||||
|
provide its contents either as a list (split on newlines) or as a
|
||||||
|
space-separated string. These are convenient, respectively, either
|
||||||
|
for sequential processing or to be passed to a shell command.
|
||||||
|
|
||||||
|
For example::
|
||||||
|
|
||||||
|
# Capture into variable a
|
||||||
|
In [1]: sc a=ls *py
|
||||||
|
|
||||||
|
# a is a string with embedded newlines
|
||||||
|
In [2]: a
|
||||||
|
Out[2]: 'setup.py\\nwin32_manual_post_install.py'
|
||||||
|
|
||||||
|
# which can be seen as a list:
|
||||||
|
In [3]: a.l
|
||||||
|
Out[3]: ['setup.py', 'win32_manual_post_install.py']
|
||||||
|
|
||||||
|
# or as a whitespace-separated string:
|
||||||
|
In [4]: a.s
|
||||||
|
Out[4]: 'setup.py win32_manual_post_install.py'
|
||||||
|
|
||||||
|
# a.s is useful to pass as a single command line:
|
||||||
|
In [5]: !wc -l $a.s
|
||||||
|
146 setup.py
|
||||||
|
130 win32_manual_post_install.py
|
||||||
|
276 total
|
||||||
|
|
||||||
|
# while the list form is useful to loop over:
|
||||||
|
In [6]: for f in a.l:
|
||||||
|
...: !wc -l $f
|
||||||
|
...:
|
||||||
|
146 setup.py
|
||||||
|
130 win32_manual_post_install.py
|
||||||
|
|
||||||
|
Similarly, the lists returned by the -l option are also special, in
|
||||||
|
the sense that you can equally invoke the .s attribute on them to
|
||||||
|
automatically get a whitespace-separated string from their contents::
|
||||||
|
|
||||||
|
In [7]: sc -l b=ls *py
|
||||||
|
|
||||||
|
In [8]: b
|
||||||
|
Out[8]: ['setup.py', 'win32_manual_post_install.py']
|
||||||
|
|
||||||
|
In [9]: b.s
|
||||||
|
Out[9]: 'setup.py win32_manual_post_install.py'
|
||||||
|
|
||||||
|
In summary, both the lists and strings used for output capture have
|
||||||
|
the following special attributes::
|
||||||
|
|
||||||
|
.l (or .list) : value as list.
|
||||||
|
.n (or .nlstr): value as newline-separated string.
|
||||||
|
.s (or .spstr): value as space-separated string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
opts,args = self.parse_options(parameter_s, 'lv')
|
||||||
|
# Try to get a variable name and command to run
|
||||||
|
try:
|
||||||
|
# the variable name must be obtained from the parse_options
|
||||||
|
# output, which uses shlex.split to strip options out.
|
||||||
|
var,_ = args.split('=', 1)
|
||||||
|
var = var.strip()
|
||||||
|
# But the command has to be extracted from the original input
|
||||||
|
# parameter_s, not on what parse_options returns, to avoid the
|
||||||
|
# quote stripping which shlex.split performs on it.
|
||||||
|
_,cmd = parameter_s.split('=', 1)
|
||||||
|
except ValueError:
|
||||||
|
var,cmd = '',''
|
||||||
|
# If all looks ok, proceed
|
||||||
|
split = 'l' in opts
|
||||||
|
out = self.shell.getoutput(cmd, split=split)
|
||||||
|
if 'v' in opts:
|
||||||
|
print('%s ==\n%s' % (var, pformat(out)))
|
||||||
|
if var:
|
||||||
|
self.shell.user_ns.update({var:out})
|
||||||
|
else:
|
||||||
|
return out
|
||||||
|
|
||||||
|
@line_cell_magic
|
||||||
|
def sx(self, line='', cell=None):
|
||||||
|
"""Shell execute - run shell command and capture output (!! is short-hand).
|
||||||
|
|
||||||
|
%sx command
|
||||||
|
|
||||||
|
yap_ipython will run the given command using commands.getoutput(), and
|
||||||
|
return the result formatted as a list (split on '\\n'). Since the
|
||||||
|
output is _returned_, it will be stored in ipython's regular output
|
||||||
|
cache Out[N] and in the '_N' automatic variables.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
1) If an input line begins with '!!', then %sx is automatically
|
||||||
|
invoked. That is, while::
|
||||||
|
|
||||||
|
!ls
|
||||||
|
|
||||||
|
causes ipython to simply issue system('ls'), typing::
|
||||||
|
|
||||||
|
!!ls
|
||||||
|
|
||||||
|
is a shorthand equivalent to::
|
||||||
|
|
||||||
|
%sx ls
|
||||||
|
|
||||||
|
2) %sx differs from %sc in that %sx automatically splits into a list,
|
||||||
|
like '%sc -l'. The reason for this is to make it as easy as possible
|
||||||
|
to process line-oriented shell output via further python commands.
|
||||||
|
%sc is meant to provide much finer control, but requires more
|
||||||
|
typing.
|
||||||
|
|
||||||
|
3) Just like %sc -l, this is a list with special attributes:
|
||||||
|
::
|
||||||
|
|
||||||
|
.l (or .list) : value as list.
|
||||||
|
.n (or .nlstr): value as newline-separated string.
|
||||||
|
.s (or .spstr): value as whitespace-separated string.
|
||||||
|
|
||||||
|
This is very useful when trying to use such lists as arguments to
|
||||||
|
system commands."""
|
||||||
|
|
||||||
|
if cell is None:
|
||||||
|
# line magic
|
||||||
|
return self.shell.getoutput(line)
|
||||||
|
else:
|
||||||
|
opts,args = self.parse_options(line, '', 'out=')
|
||||||
|
output = self.shell.getoutput(cell)
|
||||||
|
out_name = opts.get('out', opts.get('o'))
|
||||||
|
if out_name:
|
||||||
|
self.shell.user_ns[out_name] = output
|
||||||
|
else:
|
||||||
|
return output
|
||||||
|
|
||||||
|
system = line_cell_magic('system')(sx)
|
||||||
|
bang = cell_magic('!')(sx)
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def bookmark(self, parameter_s=''):
|
||||||
|
"""Manage yap_ipython's bookmark system.
|
||||||
|
|
||||||
|
%bookmark <name> - set bookmark to current dir
|
||||||
|
%bookmark <name> <dir> - set bookmark to <dir>
|
||||||
|
%bookmark -l - list all bookmarks
|
||||||
|
%bookmark -d <name> - remove bookmark
|
||||||
|
%bookmark -r - remove all bookmarks
|
||||||
|
|
||||||
|
You can later on access a bookmarked folder with::
|
||||||
|
|
||||||
|
%cd -b <name>
|
||||||
|
|
||||||
|
or simply '%cd <name>' if there is no directory called <name> AND
|
||||||
|
there is such a bookmark defined.
|
||||||
|
|
||||||
|
Your bookmarks persist through yap_ipython sessions, but they are
|
||||||
|
associated with each profile."""
|
||||||
|
|
||||||
|
opts,args = self.parse_options(parameter_s,'drl',mode='list')
|
||||||
|
if len(args) > 2:
|
||||||
|
raise UsageError("%bookmark: too many arguments")
|
||||||
|
|
||||||
|
bkms = self.shell.db.get('bookmarks',{})
|
||||||
|
|
||||||
|
if 'd' in opts:
|
||||||
|
try:
|
||||||
|
todel = args[0]
|
||||||
|
except IndexError:
|
||||||
|
raise UsageError(
|
||||||
|
"%bookmark -d: must provide a bookmark to delete")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
del bkms[todel]
|
||||||
|
except KeyError:
|
||||||
|
raise UsageError(
|
||||||
|
"%%bookmark -d: Can't delete bookmark '%s'" % todel)
|
||||||
|
|
||||||
|
elif 'r' in opts:
|
||||||
|
bkms = {}
|
||||||
|
elif 'l' in opts:
|
||||||
|
bks = sorted(bkms)
|
||||||
|
if bks:
|
||||||
|
size = max(map(len, bks))
|
||||||
|
else:
|
||||||
|
size = 0
|
||||||
|
fmt = '%-'+str(size)+'s -> %s'
|
||||||
|
print('Current bookmarks:')
|
||||||
|
for bk in bks:
|
||||||
|
print(fmt % (bk, bkms[bk]))
|
||||||
|
else:
|
||||||
|
if not args:
|
||||||
|
raise UsageError("%bookmark: You must specify the bookmark name")
|
||||||
|
elif len(args)==1:
|
||||||
|
bkms[args[0]] = os.getcwd()
|
||||||
|
elif len(args)==2:
|
||||||
|
bkms[args[0]] = args[1]
|
||||||
|
self.shell.db['bookmarks'] = bkms
|
||||||
|
|
||||||
|
@line_magic
|
||||||
|
def pycat(self, parameter_s=''):
|
||||||
|
"""Show a syntax-highlighted file through a pager.
|
||||||
|
|
||||||
|
This magic is similar to the cat utility, but it will assume the file
|
||||||
|
to be Python source and will show it with syntax highlighting.
|
||||||
|
|
||||||
|
This magic command can either take a local filename, an url,
|
||||||
|
an history range (see %history) or a macro as argument ::
|
||||||
|
|
||||||
|
%pycat myscript.py
|
||||||
|
%pycat 7-27
|
||||||
|
%pycat myMacro
|
||||||
|
%pycat http://www.example.com/myscript.py
|
||||||
|
"""
|
||||||
|
if not parameter_s:
|
||||||
|
raise UsageError('Missing filename, URL, input history range, '
|
||||||
|
'or macro.')
|
||||||
|
|
||||||
|
try :
|
||||||
|
cont = self.shell.find_user_code(parameter_s, skip_encoding_cookie=False)
|
||||||
|
except (ValueError, IOError):
|
||||||
|
print("Error: no such file, variable, URL, history range or macro")
|
||||||
|
return
|
||||||
|
|
||||||
|
page.page(self.shell.pycolorize(source_to_unicode(cont)))
|
||||||
|
|
||||||
|
@magic_arguments.magic_arguments()
|
||||||
|
@magic_arguments.argument(
|
||||||
|
'-a', '--append', action='store_true', default=False,
|
||||||
|
help='Append contents of the cell to an existing file. '
|
||||||
|
'The file will be created if it does not exist.'
|
||||||
|
)
|
||||||
|
@magic_arguments.argument(
|
||||||
|
'filename', type=str,
|
||||||
|
help='file to write'
|
||||||
|
)
|
||||||
|
@cell_magic
|
||||||
|
def writefile(self, line, cell):
|
||||||
|
"""Write the contents of the cell to a file.
|
||||||
|
|
||||||
|
The file will be overwritten unless the -a (--append) flag is specified.
|
||||||
|
"""
|
||||||
|
args = magic_arguments.parse_argstring(self.writefile, line)
|
||||||
|
filename = os.path.expanduser(args.filename)
|
||||||
|
|
||||||
|
if os.path.exists(filename):
|
||||||
|
if args.append:
|
||||||
|
print("Appending to %s" % filename)
|
||||||
|
else:
|
||||||
|
print("Overwriting %s" % filename)
|
||||||
|
else:
|
||||||
|
print("Writing %s" % filename)
|
||||||
|
|
||||||
|
mode = 'a' if args.append else 'w'
|
||||||
|
with io.open(filename, mode, encoding='utf-8') as f:
|
||||||
|
f.write(cell)
|
166
packages/python/yap_kernel/yap_ipython/core/magics/pylab.py
Normal file
166
packages/python/yap_kernel/yap_ipython/core/magics/pylab.py
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
"""Implementation of magic functions for matplotlib/pylab support.
|
||||||
|
"""
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (c) 2012 The yap_ipython Development Team.
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
#
|
||||||
|
# The full license is in the file COPYING.txt, distributed with this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Our own packages
|
||||||
|
from traitlets.config.application import Application
|
||||||
|
from yap_ipython.core import magic_arguments
|
||||||
|
from yap_ipython.core.magic import Magics, magics_class, line_magic
|
||||||
|
from yap_ipython.testing.skipdoctest import skip_doctest
|
||||||
|
from warnings import warn
|
||||||
|
from yap_ipython.core.pylabtools import backends
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Magic implementation classes
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
magic_gui_arg = magic_arguments.argument(
|
||||||
|
'gui', nargs='?',
|
||||||
|
help="""Name of the matplotlib backend to use %s.
|
||||||
|
If given, the corresponding matplotlib backend is used,
|
||||||
|
otherwise it will be matplotlib's default
|
||||||
|
(which you can set in your matplotlib config file).
|
||||||
|
""" % str(tuple(sorted(backends.keys())))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@magics_class
|
||||||
|
class PylabMagics(Magics):
|
||||||
|
"""Magics related to matplotlib's pylab support"""
|
||||||
|
|
||||||
|
@skip_doctest
|
||||||
|
@line_magic
|
||||||
|
@magic_arguments.magic_arguments()
|
||||||
|
@magic_arguments.argument('-l', '--list', action='store_true',
|
||||||
|
help='Show available matplotlib backends')
|
||||||
|
@magic_gui_arg
|
||||||
|
def matplotlib(self, line=''):
|
||||||
|
"""Set up matplotlib to work interactively.
|
||||||
|
|
||||||
|
This function lets you activate matplotlib interactive support
|
||||||
|
at any point during an yap_ipython session. It does not import anything
|
||||||
|
into the interactive namespace.
|
||||||
|
|
||||||
|
If you are using the inline matplotlib backend in the yap_ipython Notebook
|
||||||
|
you can set which figure formats are enabled using the following::
|
||||||
|
|
||||||
|
In [1]: from yap_ipython.display import set_matplotlib_formats
|
||||||
|
|
||||||
|
In [2]: set_matplotlib_formats('pdf', 'svg')
|
||||||
|
|
||||||
|
The default for inline figures sets `bbox_inches` to 'tight'. This can
|
||||||
|
cause discrepancies between the displayed image and the identical
|
||||||
|
image created using `savefig`. This behavior can be disabled using the
|
||||||
|
`%config` magic::
|
||||||
|
|
||||||
|
In [3]: %config InlineBackend.print_figure_kwargs = {'bbox_inches':None}
|
||||||
|
|
||||||
|
In addition, see the docstring of
|
||||||
|
`yap_ipython.display.set_matplotlib_formats` and
|
||||||
|
`yap_ipython.display.set_matplotlib_close` for more information on
|
||||||
|
changing additional behaviors of the inline backend.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
To enable the inline backend for usage with the yap_ipython Notebook::
|
||||||
|
|
||||||
|
In [1]: %matplotlib inline
|
||||||
|
|
||||||
|
In this case, where the matplotlib default is TkAgg::
|
||||||
|
|
||||||
|
In [2]: %matplotlib
|
||||||
|
Using matplotlib backend: TkAgg
|
||||||
|
|
||||||
|
But you can explicitly request a different GUI backend::
|
||||||
|
|
||||||
|
In [3]: %matplotlib qt
|
||||||
|
|
||||||
|
You can list the available backends using the -l/--list option::
|
||||||
|
|
||||||
|
In [4]: %matplotlib --list
|
||||||
|
Available matplotlib backends: ['osx', 'qt4', 'qt5', 'gtk3', 'notebook', 'wx', 'qt', 'nbagg',
|
||||||
|
'gtk', 'tk', 'inline']
|
||||||
|
"""
|
||||||
|
args = magic_arguments.parse_argstring(self.matplotlib, line)
|
||||||
|
if args.list:
|
||||||
|
backends_list = list(backends.keys())
|
||||||
|
print("Available matplotlib backends: %s" % backends_list)
|
||||||
|
else:
|
||||||
|
gui, backend = self.shell.enable_matplotlib(args.gui)
|
||||||
|
self._show_matplotlib_backend(args.gui, backend)
|
||||||
|
|
||||||
|
@skip_doctest
|
||||||
|
@line_magic
|
||||||
|
@magic_arguments.magic_arguments()
|
||||||
|
@magic_arguments.argument(
|
||||||
|
'--no-import-all', action='store_true', default=None,
|
||||||
|
help="""Prevent yap_ipython from performing ``import *`` into the interactive namespace.
|
||||||
|
|
||||||
|
You can govern the default behavior of this flag with the
|
||||||
|
InteractiveShellApp.pylab_import_all configurable.
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
@magic_gui_arg
|
||||||
|
def pylab(self, line=''):
|
||||||
|
"""Load numpy and matplotlib to work interactively.
|
||||||
|
|
||||||
|
This function lets you activate pylab (matplotlib, numpy and
|
||||||
|
interactive support) at any point during an yap_ipython session.
|
||||||
|
|
||||||
|
%pylab makes the following imports::
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
import matplotlib
|
||||||
|
from matplotlib import pylab, mlab, pyplot
|
||||||
|
np = numpy
|
||||||
|
plt = pyplot
|
||||||
|
|
||||||
|
from yap_ipython.display import display
|
||||||
|
from yap_ipython.core.pylabtools import figsize, getfigs
|
||||||
|
|
||||||
|
from pylab import *
|
||||||
|
from numpy import *
|
||||||
|
|
||||||
|
If you pass `--no-import-all`, the last two `*` imports will be excluded.
|
||||||
|
|
||||||
|
See the %matplotlib magic for more details about activating matplotlib
|
||||||
|
without affecting the interactive namespace.
|
||||||
|
"""
|
||||||
|
args = magic_arguments.parse_argstring(self.pylab, line)
|
||||||
|
if args.no_import_all is None:
|
||||||
|
# get default from Application
|
||||||
|
if Application.initialized():
|
||||||
|
app = Application.instance()
|
||||||
|
try:
|
||||||
|
import_all = app.pylab_import_all
|
||||||
|
except AttributeError:
|
||||||
|
import_all = True
|
||||||
|
else:
|
||||||
|
# nothing specified, no app - default True
|
||||||
|
import_all = True
|
||||||
|
else:
|
||||||
|
# invert no-import flag
|
||||||
|
import_all = not args.no_import_all
|
||||||
|
|
||||||
|
gui, backend, clobbered = self.shell.enable_pylab(args.gui, import_all=import_all)
|
||||||
|
self._show_matplotlib_backend(args.gui, backend)
|
||||||
|
print ("Populating the interactive namespace from numpy and matplotlib")
|
||||||
|
if clobbered:
|
||||||
|
warn("pylab import has clobbered these variables: %s" % clobbered +
|
||||||
|
"\n`%matplotlib` prevents importing * from pylab and numpy"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _show_matplotlib_backend(self, gui, backend):
|
||||||
|
"""show matplotlib message backend message"""
|
||||||
|
if not gui or gui == 'auto':
|
||||||
|
print("Using matplotlib backend: %s" % backend)
|
279
packages/python/yap_kernel/yap_ipython/core/magics/script.py
Normal file
279
packages/python/yap_kernel/yap_ipython/core/magics/script.py
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
"""Magic functions for running cells in various scripts."""
|
||||||
|
|
||||||
|
# Copyright (c) yap_ipython Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
import errno
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import signal
|
||||||
|
import time
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
import atexit
|
||||||
|
|
||||||
|
from yap_ipython.core import magic_arguments
|
||||||
|
from yap_ipython.core.magic import (
|
||||||
|
Magics, magics_class, line_magic, cell_magic
|
||||||
|
)
|
||||||
|
from yap_ipython.lib.backgroundjobs import BackgroundJobManager
|
||||||
|
from yap_ipython.utils import py3compat
|
||||||
|
from yap_ipython.utils.process import arg_split
|
||||||
|
from traitlets import List, Dict, default
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Magic implementation classes
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def script_args(f):
|
||||||
|
"""single decorator for adding script args"""
|
||||||
|
args = [
|
||||||
|
magic_arguments.argument(
|
||||||
|
'--out', type=str,
|
||||||
|
help="""The variable in which to store stdout from the script.
|
||||||
|
If the script is backgrounded, this will be the stdout *pipe*,
|
||||||
|
instead of the stderr text itself.
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
magic_arguments.argument(
|
||||||
|
'--err', type=str,
|
||||||
|
help="""The variable in which to store stderr from the script.
|
||||||
|
If the script is backgrounded, this will be the stderr *pipe*,
|
||||||
|
instead of the stderr text itself.
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
magic_arguments.argument(
|
||||||
|
'--bg', action="store_true",
|
||||||
|
help="""Whether to run the script in the background.
|
||||||
|
If given, the only way to see the output of the command is
|
||||||
|
with --out/err.
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
magic_arguments.argument(
|
||||||
|
'--proc', type=str,
|
||||||
|
help="""The variable in which to store Popen instance.
|
||||||
|
This is used only when --bg option is given.
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
]
|
||||||
|
for arg in args:
|
||||||
|
f = arg(f)
|
||||||
|
return f
|
||||||
|
|
||||||
|
@magics_class
|
||||||
|
class ScriptMagics(Magics):
|
||||||
|
"""Magics for talking to scripts
|
||||||
|
|
||||||
|
This defines a base `%%script` cell magic for running a cell
|
||||||
|
with a program in a subprocess, and registers a few top-level
|
||||||
|
magics that call %%script with common interpreters.
|
||||||
|
"""
|
||||||
|
script_magics = List(
|
||||||
|
help="""Extra script cell magics to define
|
||||||
|
|
||||||
|
This generates simple wrappers of `%%script foo` as `%%foo`.
|
||||||
|
|
||||||
|
If you want to add script magics that aren't on your path,
|
||||||
|
specify them in script_paths
|
||||||
|
""",
|
||||||
|
).tag(config=True)
|
||||||
|
@default('script_magics')
|
||||||
|
def _script_magics_default(self):
|
||||||
|
"""default to a common list of programs"""
|
||||||
|
|
||||||
|
defaults = [
|
||||||
|
'sh',
|
||||||
|
'bash',
|
||||||
|
'perl',
|
||||||
|
'ruby',
|
||||||
|
'python',
|
||||||
|
'python2',
|
||||||
|
'python3',
|
||||||
|
'pypy',
|
||||||
|
]
|
||||||
|
if os.name == 'nt':
|
||||||
|
defaults.extend([
|
||||||
|
'cmd',
|
||||||
|
])
|
||||||
|
|
||||||
|
return defaults
|
||||||
|
|
||||||
|
script_paths = Dict(
|
||||||
|
help="""Dict mapping short 'ruby' names to full paths, such as '/opt/secret/bin/ruby'
|
||||||
|
|
||||||
|
Only necessary for items in script_magics where the default path will not
|
||||||
|
find the right interpreter.
|
||||||
|
"""
|
||||||
|
).tag(config=True)
|
||||||
|
|
||||||
|
def __init__(self, shell=None):
|
||||||
|
super(ScriptMagics, self).__init__(shell=shell)
|
||||||
|
self._generate_script_magics()
|
||||||
|
self.job_manager = BackgroundJobManager()
|
||||||
|
self.bg_processes = []
|
||||||
|
atexit.register(self.kill_bg_processes)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.kill_bg_processes()
|
||||||
|
|
||||||
|
def _generate_script_magics(self):
|
||||||
|
cell_magics = self.magics['cell']
|
||||||
|
for name in self.script_magics:
|
||||||
|
cell_magics[name] = self._make_script_magic(name)
|
||||||
|
|
||||||
|
def _make_script_magic(self, name):
|
||||||
|
"""make a named magic, that calls %%script with a particular program"""
|
||||||
|
# expand to explicit path if necessary:
|
||||||
|
script = self.script_paths.get(name, name)
|
||||||
|
|
||||||
|
@magic_arguments.magic_arguments()
|
||||||
|
@script_args
|
||||||
|
def named_script_magic(line, cell):
|
||||||
|
# if line, add it as cl-flags
|
||||||
|
if line:
|
||||||
|
line = "%s %s" % (script, line)
|
||||||
|
else:
|
||||||
|
line = script
|
||||||
|
return self.shebang(line, cell)
|
||||||
|
|
||||||
|
# write a basic docstring:
|
||||||
|
named_script_magic.__doc__ = \
|
||||||
|
"""%%{name} script magic
|
||||||
|
|
||||||
|
Run cells with {script} in a subprocess.
|
||||||
|
|
||||||
|
This is a shortcut for `%%script {script}`
|
||||||
|
""".format(**locals())
|
||||||
|
|
||||||
|
return named_script_magic
|
||||||
|
|
||||||
|
@magic_arguments.magic_arguments()
|
||||||
|
@script_args
|
||||||
|
@cell_magic("script")
|
||||||
|
def shebang(self, line, cell):
|
||||||
|
"""Run a cell via a shell command
|
||||||
|
|
||||||
|
The `%%script` line is like the #! line of script,
|
||||||
|
specifying a program (bash, perl, ruby, etc.) with which to run.
|
||||||
|
|
||||||
|
The rest of the cell is run by that program.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
::
|
||||||
|
|
||||||
|
In [1]: %%script bash
|
||||||
|
...: for i in 1 2 3; do
|
||||||
|
...: echo $i
|
||||||
|
...: done
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
"""
|
||||||
|
argv = arg_split(line, posix = not sys.platform.startswith('win'))
|
||||||
|
args, cmd = self.shebang.parser.parse_known_args(argv)
|
||||||
|
|
||||||
|
try:
|
||||||
|
p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.ENOENT:
|
||||||
|
print("Couldn't find program: %r" % cmd[0])
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
if not cell.endswith('\n'):
|
||||||
|
cell += '\n'
|
||||||
|
cell = cell.encode('utf8', 'replace')
|
||||||
|
if args.bg:
|
||||||
|
self.bg_processes.append(p)
|
||||||
|
self._gc_bg_processes()
|
||||||
|
if args.out:
|
||||||
|
self.shell.user_ns[args.out] = p.stdout
|
||||||
|
if args.err:
|
||||||
|
self.shell.user_ns[args.err] = p.stderr
|
||||||
|
self.job_manager.new(self._run_script, p, cell, daemon=True)
|
||||||
|
if args.proc:
|
||||||
|
self.shell.user_ns[args.proc] = p
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
out, err = p.communicate(cell)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
try:
|
||||||
|
p.send_signal(signal.SIGINT)
|
||||||
|
time.sleep(0.1)
|
||||||
|
if p.poll() is not None:
|
||||||
|
print("Process is interrupted.")
|
||||||
|
return
|
||||||
|
p.terminate()
|
||||||
|
time.sleep(0.1)
|
||||||
|
if p.poll() is not None:
|
||||||
|
print("Process is terminated.")
|
||||||
|
return
|
||||||
|
p.kill()
|
||||||
|
print("Process is killed.")
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
print("Error while terminating subprocess (pid=%i): %s" \
|
||||||
|
% (p.pid, e))
|
||||||
|
return
|
||||||
|
out = py3compat.decode(out)
|
||||||
|
err = py3compat.decode(err)
|
||||||
|
if args.out:
|
||||||
|
self.shell.user_ns[args.out] = out
|
||||||
|
else:
|
||||||
|
sys.stdout.write(out)
|
||||||
|
sys.stdout.flush()
|
||||||
|
if args.err:
|
||||||
|
self.shell.user_ns[args.err] = err
|
||||||
|
else:
|
||||||
|
sys.stderr.write(err)
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
def _run_script(self, p, cell):
|
||||||
|
"""callback for running the script in the background"""
|
||||||
|
p.stdin.write(cell)
|
||||||
|
p.stdin.close()
|
||||||
|
p.wait()
|
||||||
|
|
||||||
|
@line_magic("killbgscripts")
|
||||||
|
def killbgscripts(self, _nouse_=''):
|
||||||
|
"""Kill all BG processes started by %%script and its family."""
|
||||||
|
self.kill_bg_processes()
|
||||||
|
print("All background processes were killed.")
|
||||||
|
|
||||||
|
def kill_bg_processes(self):
|
||||||
|
"""Kill all BG processes which are still running."""
|
||||||
|
if not self.bg_processes:
|
||||||
|
return
|
||||||
|
for p in self.bg_processes:
|
||||||
|
if p.poll() is None:
|
||||||
|
try:
|
||||||
|
p.send_signal(signal.SIGINT)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
time.sleep(0.1)
|
||||||
|
self._gc_bg_processes()
|
||||||
|
if not self.bg_processes:
|
||||||
|
return
|
||||||
|
for p in self.bg_processes:
|
||||||
|
if p.poll() is None:
|
||||||
|
try:
|
||||||
|
p.terminate()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
time.sleep(0.1)
|
||||||
|
self._gc_bg_processes()
|
||||||
|
if not self.bg_processes:
|
||||||
|
return
|
||||||
|
for p in self.bg_processes:
|
||||||
|
if p.poll() is None:
|
||||||
|
try:
|
||||||
|
p.kill()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
self._gc_bg_processes()
|
||||||
|
|
||||||
|
def _gc_bg_processes(self):
|
||||||
|
self.bg_processes = [p for p in self.bg_processes if p.poll() is None]
|
14
packages/python/yap_kernel/yap_ipython/core/tests/bad_all.py
Normal file
14
packages/python/yap_kernel/yap_ipython/core/tests/bad_all.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
"""Module with bad __all__
|
||||||
|
|
||||||
|
To test https://github.com/ipython/ipython/issues/9678
|
||||||
|
"""
|
||||||
|
|
||||||
|
def evil():
|
||||||
|
pass
|
||||||
|
|
||||||
|
def puppies():
|
||||||
|
pass
|
||||||
|
|
||||||
|
__all__ = [evil, # Bad
|
||||||
|
'puppies', # Good
|
||||||
|
]
|
@ -0,0 +1,12 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Useless yap_ipython extension to test installing and loading extensions.
|
||||||
|
"""
|
||||||
|
some_vars = {'arq': 185}
|
||||||
|
|
||||||
|
def load_ipython_extension(ip):
|
||||||
|
# set up simplified quantity input
|
||||||
|
ip.push(some_vars)
|
||||||
|
|
||||||
|
def unload_ipython_extension(ip):
|
||||||
|
ip.drop_by_id(some_vars)
|
@ -0,0 +1,4 @@
|
|||||||
|
# coding: iso-8859-5
|
||||||
|
# (Unlikely to be the default encoding for most testers.)
|
||||||
|
# ±¶ÿàáâãäåæçèéêëìíîï <- Cyrillic characters
|
||||||
|
u = '®âðÄ'
|
@ -0,0 +1,4 @@
|
|||||||
|
# coding: iso-8859-5
|
||||||
|
# (Unlikely to be the default encoding for most testers.)
|
||||||
|
# БЖџрстуфхцчшщъыьэюя <- Cyrillic characters
|
||||||
|
'Ўт№Ф'
|
@ -0,0 +1,2 @@
|
|||||||
|
import sys
|
||||||
|
print(sys.argv[1:])
|
47
packages/python/yap_kernel/yap_ipython/core/tests/refbug.py
Normal file
47
packages/python/yap_kernel/yap_ipython/core/tests/refbug.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
"""Minimal script to reproduce our nasty reference counting bug.
|
||||||
|
|
||||||
|
The problem is related to https://github.com/ipython/ipython/issues/141
|
||||||
|
|
||||||
|
The original fix for that appeared to work, but John D. Hunter found a
|
||||||
|
matplotlib example which, when run twice in a row, would break. The problem
|
||||||
|
were references held by open figures to internals of Tkinter.
|
||||||
|
|
||||||
|
This code reproduces the problem that John saw, without matplotlib.
|
||||||
|
|
||||||
|
This script is meant to be called by other parts of the test suite that call it
|
||||||
|
via %run as if it were executed interactively by the user. As of 2011-05-29,
|
||||||
|
test_run.py calls it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Module imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from yap_ipython import get_ipython
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Globals
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# This needs to be here because nose and other test runners will import
|
||||||
|
# this module. Importing this module has potential side effects that we
|
||||||
|
# want to prevent.
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
ip = get_ipython()
|
||||||
|
|
||||||
|
if not '_refbug_cache' in ip.user_ns:
|
||||||
|
ip.user_ns['_refbug_cache'] = []
|
||||||
|
|
||||||
|
|
||||||
|
aglobal = 'Hello'
|
||||||
|
def f():
|
||||||
|
return aglobal
|
||||||
|
|
||||||
|
cache = ip.user_ns['_refbug_cache']
|
||||||
|
cache.append(f)
|
||||||
|
|
||||||
|
def call_f():
|
||||||
|
for func in cache:
|
||||||
|
print('lowercased:',func().lower())
|
@ -0,0 +1,32 @@
|
|||||||
|
"""Error script. DO NOT EDIT FURTHER! It will break exception doctests!!!"""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def div0():
|
||||||
|
"foo"
|
||||||
|
x = 1
|
||||||
|
y = 0
|
||||||
|
x/y
|
||||||
|
|
||||||
|
def sysexit(stat, mode):
|
||||||
|
raise SystemExit(stat, 'Mode = %s' % mode)
|
||||||
|
|
||||||
|
def bar(mode):
|
||||||
|
"bar"
|
||||||
|
if mode=='div':
|
||||||
|
div0()
|
||||||
|
elif mode=='exit':
|
||||||
|
try:
|
||||||
|
stat = int(sys.argv[2])
|
||||||
|
except:
|
||||||
|
stat = 1
|
||||||
|
sysexit(stat, mode)
|
||||||
|
else:
|
||||||
|
raise ValueError('Unknown mode')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
mode = sys.argv[1]
|
||||||
|
except IndexError:
|
||||||
|
mode = 'div'
|
||||||
|
|
||||||
|
bar(mode)
|
34
packages/python/yap_kernel/yap_ipython/core/tests/tclass.py
Normal file
34
packages/python/yap_kernel/yap_ipython/core/tests/tclass.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
"""Simple script to be run *twice*, to check reference counting bugs.
|
||||||
|
|
||||||
|
See test_run for details."""
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# We want to ensure that while objects remain available for immediate access,
|
||||||
|
# objects from *previous* runs of the same script get collected, to avoid
|
||||||
|
# accumulating massive amounts of old references.
|
||||||
|
class C(object):
|
||||||
|
def __init__(self,name):
|
||||||
|
self.name = name
|
||||||
|
self.p = print
|
||||||
|
self.flush_stdout = sys.stdout.flush
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.p('tclass.py: deleting object:',self.name)
|
||||||
|
self.flush_stdout()
|
||||||
|
|
||||||
|
try:
|
||||||
|
name = sys.argv[1]
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if name.startswith('C'):
|
||||||
|
c = C(name)
|
||||||
|
|
||||||
|
#print >> sys.stderr, "ARGV:", sys.argv # dbg
|
||||||
|
|
||||||
|
# This next print statement is NOT debugging, we're making the check on a
|
||||||
|
# completely separate process so we verify by capturing stdout:
|
||||||
|
print('ARGV 1-:', sys.argv[1:])
|
||||||
|
sys.stdout.flush()
|
@ -0,0 +1,62 @@
|
|||||||
|
from yap_ipython.utils.capture import capture_output
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
def test_alias_lifecycle():
|
||||||
|
name = 'test_alias1'
|
||||||
|
cmd = 'echo "Hello"'
|
||||||
|
am = _ip.alias_manager
|
||||||
|
am.clear_aliases()
|
||||||
|
am.define_alias(name, cmd)
|
||||||
|
assert am.is_alias(name)
|
||||||
|
nt.assert_equal(am.retrieve_alias(name), cmd)
|
||||||
|
nt.assert_in((name, cmd), am.aliases)
|
||||||
|
|
||||||
|
# Test running the alias
|
||||||
|
orig_system = _ip.system
|
||||||
|
result = []
|
||||||
|
_ip.system = result.append
|
||||||
|
try:
|
||||||
|
_ip.run_cell('%{}'.format(name))
|
||||||
|
result = [c.strip() for c in result]
|
||||||
|
nt.assert_equal(result, [cmd])
|
||||||
|
finally:
|
||||||
|
_ip.system = orig_system
|
||||||
|
|
||||||
|
# Test removing the alias
|
||||||
|
am.undefine_alias(name)
|
||||||
|
assert not am.is_alias(name)
|
||||||
|
with nt.assert_raises(ValueError):
|
||||||
|
am.retrieve_alias(name)
|
||||||
|
nt.assert_not_in((name, cmd), am.aliases)
|
||||||
|
|
||||||
|
|
||||||
|
def test_alias_args_error():
|
||||||
|
"""Error expanding with wrong number of arguments"""
|
||||||
|
_ip.alias_manager.define_alias('parts', 'echo first %s second %s')
|
||||||
|
# capture stderr:
|
||||||
|
with capture_output() as cap:
|
||||||
|
_ip.run_cell('parts 1')
|
||||||
|
|
||||||
|
nt.assert_equal(cap.stderr.split(':')[0], 'UsageError')
|
||||||
|
|
||||||
|
def test_alias_args_commented():
|
||||||
|
"""Check that alias correctly ignores 'commented out' args"""
|
||||||
|
_ip.magic('alias commetarg echo this is %%s a commented out arg')
|
||||||
|
|
||||||
|
with capture_output() as cap:
|
||||||
|
_ip.run_cell('commetarg')
|
||||||
|
|
||||||
|
nt.assert_equal(cap.stdout, 'this is %s a commented out arg')
|
||||||
|
|
||||||
|
def test_alias_args_commented_nargs():
|
||||||
|
"""Check that alias correctly counts args, excluding those commented out"""
|
||||||
|
am = _ip.alias_manager
|
||||||
|
alias_name = 'comargcount'
|
||||||
|
cmd = 'echo this is %%s a commented out arg and this is not %s'
|
||||||
|
|
||||||
|
am.define_alias(alias_name, cmd)
|
||||||
|
assert am.is_alias(alias_name)
|
||||||
|
|
||||||
|
thealias = am.get_alias(alias_name)
|
||||||
|
nt.assert_equal(thealias.nargs, 1)
|
@ -0,0 +1,73 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
"""Tests for yap_ipython.core.application"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from traitlets import Unicode
|
||||||
|
|
||||||
|
from yap_ipython.core.application import BaseYAPApplication
|
||||||
|
from yap_ipython.testing import decorators as dec
|
||||||
|
from yap_ipython.utils.tempdir import TemporaryDirectory
|
||||||
|
|
||||||
|
|
||||||
|
@dec.onlyif_unicode_paths
|
||||||
|
def test_unicode_cwd():
|
||||||
|
"""Check that yap_ipython starts with non-ascii characters in the path."""
|
||||||
|
wd = tempfile.mkdtemp(suffix=u"€")
|
||||||
|
|
||||||
|
old_wd = os.getcwd()
|
||||||
|
os.chdir(wd)
|
||||||
|
#raise Exception(repr(os.getcwd()))
|
||||||
|
try:
|
||||||
|
app = BaseYAPApplication()
|
||||||
|
# The lines below are copied from Application.initialize()
|
||||||
|
app.init_profile_dir()
|
||||||
|
app.init_config_files()
|
||||||
|
app.load_config_file(suppress_errors=False)
|
||||||
|
finally:
|
||||||
|
os.chdir(old_wd)
|
||||||
|
|
||||||
|
@dec.onlyif_unicode_paths
|
||||||
|
def test_unicode_ipdir():
|
||||||
|
"""Check that yap_ipython starts with non-ascii characters in the IP dir."""
|
||||||
|
ipdir = tempfile.mkdtemp(suffix=u"€")
|
||||||
|
|
||||||
|
# Create the config file, so it tries to load it.
|
||||||
|
with open(os.path.join(ipdir, 'ipython_config.py'), "w") as f:
|
||||||
|
pass
|
||||||
|
|
||||||
|
old_ipdir1 = os.environ.pop("IPYTHONDIR", None)
|
||||||
|
old_ipdir2 = os.environ.pop("IPYTHON_DIR", None)
|
||||||
|
os.environ["IPYTHONDIR"] = ipdir
|
||||||
|
try:
|
||||||
|
app = BaseYAPApplication()
|
||||||
|
# The lines below are copied from Application.initialize()
|
||||||
|
app.init_profile_dir()
|
||||||
|
app.init_config_files()
|
||||||
|
app.load_config_file(suppress_errors=False)
|
||||||
|
finally:
|
||||||
|
if old_ipdir1:
|
||||||
|
os.environ["IPYTHONDIR"] = old_ipdir1
|
||||||
|
if old_ipdir2:
|
||||||
|
os.environ["IPYTHONDIR"] = old_ipdir2
|
||||||
|
|
||||||
|
def test_cli_priority():
|
||||||
|
with TemporaryDirectory() as td:
|
||||||
|
|
||||||
|
class TestApp(BaseYAPApplication):
|
||||||
|
test = Unicode().tag(config=True)
|
||||||
|
|
||||||
|
# Create the config file, so it tries to load it.
|
||||||
|
with open(os.path.join(td, 'ipython_config.py'), "w") as f:
|
||||||
|
f.write("c.TestApp.test = 'config file'")
|
||||||
|
|
||||||
|
app = TestApp()
|
||||||
|
app.initialize(['--profile-dir', td])
|
||||||
|
nt.assert_equal(app.test, 'config file')
|
||||||
|
app = TestApp()
|
||||||
|
app.initialize(['--profile-dir', td, '--TestApp.test=cli'])
|
||||||
|
nt.assert_equal(app.test, 'cli')
|
||||||
|
|
@ -0,0 +1,73 @@
|
|||||||
|
"""These kinds of tests are less than ideal, but at least they run.
|
||||||
|
|
||||||
|
This was an old test that was being run interactively in the top-level tests/
|
||||||
|
directory, which we are removing. For now putting this here ensures at least
|
||||||
|
we do run the test, though ultimately this functionality should all be tested
|
||||||
|
with better-isolated tests that don't rely on the global instance in iptest.
|
||||||
|
"""
|
||||||
|
from yap_ipython.core.splitinput import LineInfo
|
||||||
|
from yap_ipython.core.prefilter import AutocallChecker
|
||||||
|
from yap_ipython.utils import py3compat
|
||||||
|
from yap_ipython.testing.globalipapp import get_ipython
|
||||||
|
|
||||||
|
|
||||||
|
ip = get_ipython()
|
||||||
|
|
||||||
|
|
||||||
|
@py3compat.doctest_refactor_print
|
||||||
|
def doctest_autocall():
|
||||||
|
"""
|
||||||
|
In [1]: def f1(a,b,c):
|
||||||
|
...: return a+b+c
|
||||||
|
...:
|
||||||
|
|
||||||
|
In [2]: def f2(a):
|
||||||
|
...: return a + a
|
||||||
|
...:
|
||||||
|
|
||||||
|
In [3]: def r(x):
|
||||||
|
...: return True
|
||||||
|
...:
|
||||||
|
|
||||||
|
In [4]: ;f2 a b c
|
||||||
|
Out[4]: 'a b ca b c'
|
||||||
|
|
||||||
|
In [5]: assert _ == "a b ca b c"
|
||||||
|
|
||||||
|
In [6]: ,f1 a b c
|
||||||
|
Out[6]: 'abc'
|
||||||
|
|
||||||
|
In [7]: assert _ == 'abc'
|
||||||
|
|
||||||
|
In [8]: print _
|
||||||
|
abc
|
||||||
|
|
||||||
|
In [9]: /f1 1,2,3
|
||||||
|
Out[9]: 6
|
||||||
|
|
||||||
|
In [10]: assert _ == 6
|
||||||
|
|
||||||
|
In [11]: /f2 4
|
||||||
|
Out[11]: 8
|
||||||
|
|
||||||
|
In [12]: assert _ == 8
|
||||||
|
|
||||||
|
In [12]: del f1, f2
|
||||||
|
|
||||||
|
In [13]: ,r a
|
||||||
|
Out[13]: True
|
||||||
|
|
||||||
|
In [14]: assert _ == True
|
||||||
|
|
||||||
|
In [15]: r'a'
|
||||||
|
Out[15]: 'a'
|
||||||
|
|
||||||
|
In [16]: assert _ == 'a'
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_autocall_should_ignore_raw_strings():
|
||||||
|
line_info = LineInfo("r'a'")
|
||||||
|
pm = ip.prefilter_manager
|
||||||
|
ac = AutocallChecker(shell=pm.shell, prefilter_manager=pm, config=pm.config)
|
||||||
|
assert ac.check(line_info) is None
|
@ -0,0 +1,73 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
"""Tests for the compilerop module.
|
||||||
|
"""
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2010-2011 The yap_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 linecache
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Third-party imports
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
# Our own imports
|
||||||
|
from yap_ipython.core import compilerop
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Test functions
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_code_name():
|
||||||
|
code = 'x=1'
|
||||||
|
name = compilerop.code_name(code)
|
||||||
|
nt.assert_true(name.startswith('<ipython-input-0'))
|
||||||
|
|
||||||
|
|
||||||
|
def test_code_name2():
|
||||||
|
code = 'x=1'
|
||||||
|
name = compilerop.code_name(code, 9)
|
||||||
|
nt.assert_true(name.startswith('<ipython-input-9'))
|
||||||
|
|
||||||
|
|
||||||
|
def test_cache():
|
||||||
|
"""Test the compiler correctly compiles and caches inputs
|
||||||
|
"""
|
||||||
|
cp = compilerop.CachingCompiler()
|
||||||
|
ncache = len(linecache.cache)
|
||||||
|
cp.cache('x=1')
|
||||||
|
nt.assert_true(len(linecache.cache) > ncache)
|
||||||
|
|
||||||
|
def setUp():
|
||||||
|
# Check we're in a proper Python 2 environment (some imports, such
|
||||||
|
# as GTK, can change the default encoding, which can hide bugs.)
|
||||||
|
nt.assert_equal(sys.getdefaultencoding(), "utf-8")
|
||||||
|
|
||||||
|
def test_cache_unicode():
|
||||||
|
cp = compilerop.CachingCompiler()
|
||||||
|
ncache = len(linecache.cache)
|
||||||
|
cp.cache(u"t = 'žćčšđ'")
|
||||||
|
nt.assert_true(len(linecache.cache) > ncache)
|
||||||
|
|
||||||
|
def test_compiler_check_cache():
|
||||||
|
"""Test the compiler properly manages the cache.
|
||||||
|
"""
|
||||||
|
# Rather simple-minded tests that just exercise the API
|
||||||
|
cp = compilerop.CachingCompiler()
|
||||||
|
cp.cache('x=1', 99)
|
||||||
|
# Ensure now that after clearing the cache, our entries survive
|
||||||
|
linecache.checkcache()
|
||||||
|
for k in linecache.cache:
|
||||||
|
if k.startswith('<ipython-input-99'):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise AssertionError('Entry for input-99 missing from linecache')
|
1009
packages/python/yap_kernel/yap_ipython/core/tests/test_completer.py
Normal file
1009
packages/python/yap_kernel/yap_ipython/core/tests/test_completer.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,161 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Tests for completerlib.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from os.path import join
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython.core.completerlib import magic_run_completer, module_completion
|
||||||
|
from yap_ipython.utils.tempdir import TemporaryDirectory
|
||||||
|
from yap_ipython.testing.decorators import onlyif_unicode_paths
|
||||||
|
|
||||||
|
|
||||||
|
class MockEvent(object):
|
||||||
|
def __init__(self, line):
|
||||||
|
self.line = line
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Test functions begin
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
class Test_magic_run_completer(unittest.TestCase):
|
||||||
|
files = [u"aao.py", u"a.py", u"b.py", u"aao.txt"]
|
||||||
|
dirs = [u"adir/", "bdir/"]
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.BASETESTDIR = tempfile.mkdtemp()
|
||||||
|
for fil in self.files:
|
||||||
|
with open(join(self.BASETESTDIR, fil), "w") as sfile:
|
||||||
|
sfile.write("pass\n")
|
||||||
|
for d in self.dirs:
|
||||||
|
os.mkdir(join(self.BASETESTDIR, d))
|
||||||
|
|
||||||
|
self.oldpath = os.getcwd()
|
||||||
|
os.chdir(self.BASETESTDIR)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
os.chdir(self.oldpath)
|
||||||
|
shutil.rmtree(self.BASETESTDIR)
|
||||||
|
|
||||||
|
def test_1(self):
|
||||||
|
"""Test magic_run_completer, should match two alterntives
|
||||||
|
"""
|
||||||
|
event = MockEvent(u"%run a")
|
||||||
|
mockself = None
|
||||||
|
match = set(magic_run_completer(mockself, event))
|
||||||
|
self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
|
||||||
|
|
||||||
|
def test_2(self):
|
||||||
|
"""Test magic_run_completer, should match one alterntive
|
||||||
|
"""
|
||||||
|
event = MockEvent(u"%run aa")
|
||||||
|
mockself = None
|
||||||
|
match = set(magic_run_completer(mockself, event))
|
||||||
|
self.assertEqual(match, {u"aao.py"})
|
||||||
|
|
||||||
|
def test_3(self):
|
||||||
|
"""Test magic_run_completer with unterminated " """
|
||||||
|
event = MockEvent(u'%run "a')
|
||||||
|
mockself = None
|
||||||
|
match = set(magic_run_completer(mockself, event))
|
||||||
|
self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
|
||||||
|
|
||||||
|
def test_completion_more_args(self):
|
||||||
|
event = MockEvent(u'%run a.py ')
|
||||||
|
match = set(magic_run_completer(None, event))
|
||||||
|
self.assertEqual(match, set(self.files + self.dirs))
|
||||||
|
|
||||||
|
def test_completion_in_dir(self):
|
||||||
|
# Github issue #3459
|
||||||
|
event = MockEvent(u'%run a.py {}'.format(join(self.BASETESTDIR, 'a')))
|
||||||
|
print(repr(event.line))
|
||||||
|
match = set(magic_run_completer(None, event))
|
||||||
|
# We specifically use replace here rather than normpath, because
|
||||||
|
# at one point there were duplicates 'adir' and 'adir/', and normpath
|
||||||
|
# would hide the failure for that.
|
||||||
|
self.assertEqual(match, {join(self.BASETESTDIR, f).replace('\\','/')
|
||||||
|
for f in (u'a.py', u'aao.py', u'aao.txt', u'adir/')})
|
||||||
|
|
||||||
|
class Test_magic_run_completer_nonascii(unittest.TestCase):
|
||||||
|
@onlyif_unicode_paths
|
||||||
|
def setUp(self):
|
||||||
|
self.BASETESTDIR = tempfile.mkdtemp()
|
||||||
|
for fil in [u"aaø.py", u"a.py", u"b.py"]:
|
||||||
|
with open(join(self.BASETESTDIR, fil), "w") as sfile:
|
||||||
|
sfile.write("pass\n")
|
||||||
|
self.oldpath = os.getcwd()
|
||||||
|
os.chdir(self.BASETESTDIR)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
os.chdir(self.oldpath)
|
||||||
|
shutil.rmtree(self.BASETESTDIR)
|
||||||
|
|
||||||
|
@onlyif_unicode_paths
|
||||||
|
def test_1(self):
|
||||||
|
"""Test magic_run_completer, should match two alterntives
|
||||||
|
"""
|
||||||
|
event = MockEvent(u"%run a")
|
||||||
|
mockself = None
|
||||||
|
match = set(magic_run_completer(mockself, event))
|
||||||
|
self.assertEqual(match, {u"a.py", u"aaø.py"})
|
||||||
|
|
||||||
|
@onlyif_unicode_paths
|
||||||
|
def test_2(self):
|
||||||
|
"""Test magic_run_completer, should match one alterntive
|
||||||
|
"""
|
||||||
|
event = MockEvent(u"%run aa")
|
||||||
|
mockself = None
|
||||||
|
match = set(magic_run_completer(mockself, event))
|
||||||
|
self.assertEqual(match, {u"aaø.py"})
|
||||||
|
|
||||||
|
@onlyif_unicode_paths
|
||||||
|
def test_3(self):
|
||||||
|
"""Test magic_run_completer with unterminated " """
|
||||||
|
event = MockEvent(u'%run "a')
|
||||||
|
mockself = None
|
||||||
|
match = set(magic_run_completer(mockself, event))
|
||||||
|
self.assertEqual(match, {u"a.py", u"aaø.py"})
|
||||||
|
|
||||||
|
# module_completer:
|
||||||
|
|
||||||
|
def test_import_invalid_module():
|
||||||
|
"""Testing of issue https://github.com/ipython/ipython/issues/1107"""
|
||||||
|
invalid_module_names = {'foo-bar', 'foo:bar', '10foo'}
|
||||||
|
valid_module_names = {'foobar'}
|
||||||
|
with TemporaryDirectory() as tmpdir:
|
||||||
|
sys.path.insert( 0, tmpdir )
|
||||||
|
for name in invalid_module_names | valid_module_names:
|
||||||
|
filename = os.path.join(tmpdir, name + '.py')
|
||||||
|
open(filename, 'w').close()
|
||||||
|
|
||||||
|
s = set( module_completion('import foo') )
|
||||||
|
intersection = s.intersection(invalid_module_names)
|
||||||
|
nt.assert_equal(intersection, set())
|
||||||
|
|
||||||
|
assert valid_module_names.issubset(s), valid_module_names.intersection(s)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bad_module_all():
|
||||||
|
"""Test module with invalid __all__
|
||||||
|
|
||||||
|
https://github.com/ipython/ipython/issues/9678
|
||||||
|
"""
|
||||||
|
testsdir = os.path.dirname(__file__)
|
||||||
|
sys.path.insert(0, testsdir)
|
||||||
|
try:
|
||||||
|
results = module_completion('from bad_all import ')
|
||||||
|
nt.assert_in('puppies', results)
|
||||||
|
for r in results:
|
||||||
|
nt.assert_is_instance(r, str)
|
||||||
|
finally:
|
||||||
|
sys.path.remove(testsdir)
|
@ -0,0 +1,225 @@
|
|||||||
|
"""Tests for debugging machinery.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Copyright (c) yap_ipython Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython.core import debugger
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Helper classes, from CPython's Pdb test suite
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class _FakeInput(object):
|
||||||
|
"""
|
||||||
|
A fake input stream for pdb's interactive debugger. Whenever a
|
||||||
|
line is read, print it (to simulate the user typing it), and then
|
||||||
|
return it. The set of lines to return is specified in the
|
||||||
|
constructor; they should not have trailing newlines.
|
||||||
|
"""
|
||||||
|
def __init__(self, lines):
|
||||||
|
self.lines = iter(lines)
|
||||||
|
|
||||||
|
def readline(self):
|
||||||
|
line = next(self.lines)
|
||||||
|
print(line)
|
||||||
|
return line+'\n'
|
||||||
|
|
||||||
|
class PdbTestInput(object):
|
||||||
|
"""Context manager that makes testing Pdb in doctests easier."""
|
||||||
|
|
||||||
|
def __init__(self, input):
|
||||||
|
self.input = input
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.real_stdin = sys.stdin
|
||||||
|
sys.stdin = _FakeInput(self.input)
|
||||||
|
|
||||||
|
def __exit__(self, *exc):
|
||||||
|
sys.stdin = self.real_stdin
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Tests
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_longer_repr():
|
||||||
|
try:
|
||||||
|
from reprlib import repr as trepr # Py 3
|
||||||
|
except ImportError:
|
||||||
|
from repr import repr as trepr # Py 2
|
||||||
|
|
||||||
|
a = '1234567890'* 7
|
||||||
|
ar = "'1234567890123456789012345678901234567890123456789012345678901234567890'"
|
||||||
|
a_trunc = "'123456789012...8901234567890'"
|
||||||
|
nt.assert_equal(trepr(a), a_trunc)
|
||||||
|
# The creation of our tracer modifies the repr module's repr function
|
||||||
|
# in-place, since that global is used directly by the stdlib's pdb module.
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter('ignore', DeprecationWarning)
|
||||||
|
debugger.Tracer()
|
||||||
|
nt.assert_equal(trepr(a), ar)
|
||||||
|
|
||||||
|
def test_ipdb_magics():
|
||||||
|
'''Test calling some yap_ipython magics from ipdb.
|
||||||
|
|
||||||
|
First, set up some test functions and classes which we can inspect.
|
||||||
|
|
||||||
|
>>> class ExampleClass(object):
|
||||||
|
... """Docstring for ExampleClass."""
|
||||||
|
... def __init__(self):
|
||||||
|
... """Docstring for ExampleClass.__init__"""
|
||||||
|
... pass
|
||||||
|
... def __str__(self):
|
||||||
|
... return "ExampleClass()"
|
||||||
|
|
||||||
|
>>> def example_function(x, y, z="hello"):
|
||||||
|
... """Docstring for example_function."""
|
||||||
|
... pass
|
||||||
|
|
||||||
|
>>> old_trace = sys.gettrace()
|
||||||
|
|
||||||
|
Create a function which triggers ipdb.
|
||||||
|
|
||||||
|
>>> def trigger_ipdb():
|
||||||
|
... a = ExampleClass()
|
||||||
|
... debugger.Pdb().set_trace()
|
||||||
|
|
||||||
|
>>> with PdbTestInput([
|
||||||
|
... 'pdef example_function',
|
||||||
|
... 'pdoc ExampleClass',
|
||||||
|
... 'up',
|
||||||
|
... 'down',
|
||||||
|
... 'list',
|
||||||
|
... 'pinfo a',
|
||||||
|
... 'll',
|
||||||
|
... 'continue',
|
||||||
|
... ]):
|
||||||
|
... trigger_ipdb()
|
||||||
|
--Return--
|
||||||
|
None
|
||||||
|
> <doctest ...>(3)trigger_ipdb()
|
||||||
|
1 def trigger_ipdb():
|
||||||
|
2 a = ExampleClass()
|
||||||
|
----> 3 debugger.Pdb().set_trace()
|
||||||
|
<BLANKLINE>
|
||||||
|
ipdb> pdef example_function
|
||||||
|
example_function(x, y, z='hello')
|
||||||
|
ipdb> pdoc ExampleClass
|
||||||
|
Class docstring:
|
||||||
|
Docstring for ExampleClass.
|
||||||
|
Init docstring:
|
||||||
|
Docstring for ExampleClass.__init__
|
||||||
|
ipdb> up
|
||||||
|
> <doctest ...>(11)<module>()
|
||||||
|
7 'pinfo a',
|
||||||
|
8 'll',
|
||||||
|
9 'continue',
|
||||||
|
10 ]):
|
||||||
|
---> 11 trigger_ipdb()
|
||||||
|
<BLANKLINE>
|
||||||
|
ipdb> down
|
||||||
|
None
|
||||||
|
> <doctest ...>(3)trigger_ipdb()
|
||||||
|
1 def trigger_ipdb():
|
||||||
|
2 a = ExampleClass()
|
||||||
|
----> 3 debugger.Pdb().set_trace()
|
||||||
|
<BLANKLINE>
|
||||||
|
ipdb> list
|
||||||
|
1 def trigger_ipdb():
|
||||||
|
2 a = ExampleClass()
|
||||||
|
----> 3 debugger.Pdb().set_trace()
|
||||||
|
<BLANKLINE>
|
||||||
|
ipdb> pinfo a
|
||||||
|
Type: ExampleClass
|
||||||
|
String form: ExampleClass()
|
||||||
|
Namespace: Local...
|
||||||
|
Docstring: Docstring for ExampleClass.
|
||||||
|
Init docstring: Docstring for ExampleClass.__init__
|
||||||
|
ipdb> ll
|
||||||
|
1 def trigger_ipdb():
|
||||||
|
2 a = ExampleClass()
|
||||||
|
----> 3 debugger.Pdb().set_trace()
|
||||||
|
<BLANKLINE>
|
||||||
|
ipdb> continue
|
||||||
|
|
||||||
|
Restore previous trace function, e.g. for coverage.py
|
||||||
|
|
||||||
|
>>> sys.settrace(old_trace)
|
||||||
|
'''
|
||||||
|
|
||||||
|
def test_ipdb_magics2():
|
||||||
|
'''Test ipdb with a very short function.
|
||||||
|
|
||||||
|
>>> old_trace = sys.gettrace()
|
||||||
|
|
||||||
|
>>> def bar():
|
||||||
|
... pass
|
||||||
|
|
||||||
|
Run ipdb.
|
||||||
|
|
||||||
|
>>> with PdbTestInput([
|
||||||
|
... 'continue',
|
||||||
|
... ]):
|
||||||
|
... debugger.Pdb().runcall(bar)
|
||||||
|
> <doctest ...>(2)bar()
|
||||||
|
1 def bar():
|
||||||
|
----> 2 pass
|
||||||
|
<BLANKLINE>
|
||||||
|
ipdb> continue
|
||||||
|
|
||||||
|
Restore previous trace function, e.g. for coverage.py
|
||||||
|
|
||||||
|
>>> sys.settrace(old_trace)
|
||||||
|
'''
|
||||||
|
|
||||||
|
def can_quit():
|
||||||
|
'''Test that quit work in ipydb
|
||||||
|
|
||||||
|
>>> old_trace = sys.gettrace()
|
||||||
|
|
||||||
|
>>> def bar():
|
||||||
|
... pass
|
||||||
|
|
||||||
|
>>> with PdbTestInput([
|
||||||
|
... 'quit',
|
||||||
|
... ]):
|
||||||
|
... debugger.Pdb().runcall(bar)
|
||||||
|
> <doctest ...>(2)bar()
|
||||||
|
1 def bar():
|
||||||
|
----> 2 pass
|
||||||
|
<BLANKLINE>
|
||||||
|
ipdb> quit
|
||||||
|
|
||||||
|
Restore previous trace function, e.g. for coverage.py
|
||||||
|
|
||||||
|
>>> sys.settrace(old_trace)
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
def can_exit():
|
||||||
|
'''Test that quit work in ipydb
|
||||||
|
|
||||||
|
>>> old_trace = sys.gettrace()
|
||||||
|
|
||||||
|
>>> def bar():
|
||||||
|
... pass
|
||||||
|
|
||||||
|
>>> with PdbTestInput([
|
||||||
|
... 'exit',
|
||||||
|
... ]):
|
||||||
|
... debugger.Pdb().runcall(bar)
|
||||||
|
> <doctest ...>(2)bar()
|
||||||
|
1 def bar():
|
||||||
|
----> 2 pass
|
||||||
|
<BLANKLINE>
|
||||||
|
ipdb> exit
|
||||||
|
|
||||||
|
Restore previous trace function, e.g. for coverage.py
|
||||||
|
|
||||||
|
>>> sys.settrace(old_trace)
|
||||||
|
'''
|
@ -0,0 +1,389 @@
|
|||||||
|
# Copyright (c) yap_ipython Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython.core import display
|
||||||
|
from yap_ipython.core.getipython import get_ipython
|
||||||
|
from yap_ipython.utils.io import capture_output
|
||||||
|
from yap_ipython.utils.tempdir import NamedFileInTemporaryDirectory
|
||||||
|
from yap_ipython import paths as ipath
|
||||||
|
from yap_ipython.testing.tools import AssertPrints, AssertNotPrints
|
||||||
|
|
||||||
|
import yap_ipython.testing.decorators as dec
|
||||||
|
|
||||||
|
def test_image_size():
|
||||||
|
"""Simple test for display.Image(args, width=x,height=y)"""
|
||||||
|
thisurl = 'http://www.google.fr/images/srpr/logo3w.png'
|
||||||
|
img = display.Image(url=thisurl, width=200, height=200)
|
||||||
|
nt.assert_equal(u'<img src="%s" width="200" height="200"/>' % (thisurl), img._repr_html_())
|
||||||
|
img = display.Image(url=thisurl, metadata={'width':200, 'height':200})
|
||||||
|
nt.assert_equal(u'<img src="%s" width="200" height="200"/>' % (thisurl), img._repr_html_())
|
||||||
|
img = display.Image(url=thisurl, width=200)
|
||||||
|
nt.assert_equal(u'<img src="%s" width="200"/>' % (thisurl), img._repr_html_())
|
||||||
|
img = display.Image(url=thisurl)
|
||||||
|
nt.assert_equal(u'<img src="%s"/>' % (thisurl), img._repr_html_())
|
||||||
|
img = display.Image(url=thisurl, unconfined=True)
|
||||||
|
nt.assert_equal(u'<img src="%s" class="unconfined"/>' % (thisurl), img._repr_html_())
|
||||||
|
|
||||||
|
|
||||||
|
def test_image_mimes():
|
||||||
|
fmt = get_ipython().display_formatter.format
|
||||||
|
for format in display.Image._ACCEPTABLE_EMBEDDINGS:
|
||||||
|
mime = display.Image._MIMETYPES[format]
|
||||||
|
img = display.Image(b'garbage', format=format)
|
||||||
|
data, metadata = fmt(img)
|
||||||
|
nt.assert_equal(sorted(data), sorted([mime, 'text/plain']))
|
||||||
|
|
||||||
|
|
||||||
|
def test_geojson():
|
||||||
|
|
||||||
|
gj = display.GeoJSON(data={
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [-81.327, 296.038]
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"name": "Inca City"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
|
||||||
|
layer_options={
|
||||||
|
"basemap_id": "celestia_mars-shaded-16k_global",
|
||||||
|
"attribution": "Celestia/praesepe",
|
||||||
|
"minZoom": 0,
|
||||||
|
"maxZoom": 18,
|
||||||
|
})
|
||||||
|
nt.assert_equal(u'<yap_ipython.core.display.GeoJSON object>', str(gj))
|
||||||
|
|
||||||
|
def test_retina_png():
|
||||||
|
here = os.path.dirname(__file__)
|
||||||
|
img = display.Image(os.path.join(here, "2x2.png"), retina=True)
|
||||||
|
nt.assert_equal(img.height, 1)
|
||||||
|
nt.assert_equal(img.width, 1)
|
||||||
|
data, md = img._repr_png_()
|
||||||
|
nt.assert_equal(md['width'], 1)
|
||||||
|
nt.assert_equal(md['height'], 1)
|
||||||
|
|
||||||
|
def test_retina_jpeg():
|
||||||
|
here = os.path.dirname(__file__)
|
||||||
|
img = display.Image(os.path.join(here, "2x2.jpg"), retina=True)
|
||||||
|
nt.assert_equal(img.height, 1)
|
||||||
|
nt.assert_equal(img.width, 1)
|
||||||
|
data, md = img._repr_jpeg_()
|
||||||
|
nt.assert_equal(md['width'], 1)
|
||||||
|
nt.assert_equal(md['height'], 1)
|
||||||
|
|
||||||
|
def test_base64image():
|
||||||
|
display.Image("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB94BCRQnOqNu0b4AAAAKSURBVAjXY2AAAAACAAHiIbwzAAAAAElFTkSuQmCC")
|
||||||
|
|
||||||
|
def test_image_filename_defaults():
|
||||||
|
'''test format constraint, and validity of jpeg and png'''
|
||||||
|
tpath = ipath.get_ipython_package_dir()
|
||||||
|
nt.assert_raises(ValueError, display.Image, filename=os.path.join(tpath, 'testing/tests/badformat.zip'),
|
||||||
|
embed=True)
|
||||||
|
nt.assert_raises(ValueError, display.Image)
|
||||||
|
nt.assert_raises(ValueError, display.Image, data='this is not an image', format='badformat', embed=True)
|
||||||
|
# check boths paths to allow packages to test at build and install time
|
||||||
|
imgfile = os.path.join(tpath, 'core/tests/2x2.png')
|
||||||
|
img = display.Image(filename=imgfile)
|
||||||
|
nt.assert_equal('png', img.format)
|
||||||
|
nt.assert_is_not_none(img._repr_png_())
|
||||||
|
img = display.Image(filename=os.path.join(tpath, 'testing/tests/logo.jpg'), embed=False)
|
||||||
|
nt.assert_equal('jpeg', img.format)
|
||||||
|
nt.assert_is_none(img._repr_jpeg_())
|
||||||
|
|
||||||
|
def _get_inline_config():
|
||||||
|
from yap_kernel.pylab.config import InlineBackend
|
||||||
|
return InlineBackend.instance()
|
||||||
|
|
||||||
|
@dec.skip_without('matplotlib')
|
||||||
|
def test_set_matplotlib_close():
|
||||||
|
cfg = _get_inline_config()
|
||||||
|
cfg.close_figures = False
|
||||||
|
display.set_matplotlib_close()
|
||||||
|
assert cfg.close_figures
|
||||||
|
display.set_matplotlib_close(False)
|
||||||
|
assert not cfg.close_figures
|
||||||
|
|
||||||
|
_fmt_mime_map = {
|
||||||
|
'png': 'image/png',
|
||||||
|
'jpeg': 'image/jpeg',
|
||||||
|
'pdf': 'application/pdf',
|
||||||
|
'retina': 'image/png',
|
||||||
|
'svg': 'image/svg+xml',
|
||||||
|
}
|
||||||
|
|
||||||
|
@dec.skip_without('matplotlib')
|
||||||
|
def test_set_matplotlib_formats():
|
||||||
|
from matplotlib.figure import Figure
|
||||||
|
formatters = get_ipython().display_formatter.formatters
|
||||||
|
for formats in [
|
||||||
|
('png',),
|
||||||
|
('pdf', 'svg'),
|
||||||
|
('jpeg', 'retina', 'png'),
|
||||||
|
(),
|
||||||
|
]:
|
||||||
|
active_mimes = {_fmt_mime_map[fmt] for fmt in formats}
|
||||||
|
display.set_matplotlib_formats(*formats)
|
||||||
|
for mime, f in formatters.items():
|
||||||
|
if mime in active_mimes:
|
||||||
|
nt.assert_in(Figure, f)
|
||||||
|
else:
|
||||||
|
nt.assert_not_in(Figure, f)
|
||||||
|
|
||||||
|
@dec.skip_without('matplotlib')
|
||||||
|
def test_set_matplotlib_formats_kwargs():
|
||||||
|
from matplotlib.figure import Figure
|
||||||
|
ip = get_ipython()
|
||||||
|
cfg = _get_inline_config()
|
||||||
|
cfg.print_figure_kwargs.update(dict(foo='bar'))
|
||||||
|
kwargs = dict(quality=10)
|
||||||
|
display.set_matplotlib_formats('png', **kwargs)
|
||||||
|
formatter = ip.display_formatter.formatters['image/png']
|
||||||
|
f = formatter.lookup_by_type(Figure)
|
||||||
|
cell = f.__closure__[0].cell_contents
|
||||||
|
expected = kwargs
|
||||||
|
expected.update(cfg.print_figure_kwargs)
|
||||||
|
nt.assert_equal(cell, expected)
|
||||||
|
|
||||||
|
def test_display_available():
|
||||||
|
"""
|
||||||
|
Test that display is available without import
|
||||||
|
|
||||||
|
We don't really care if it's in builtin or anything else, but it should
|
||||||
|
always be available.
|
||||||
|
"""
|
||||||
|
ip = get_ipython()
|
||||||
|
with AssertNotPrints('NameError'):
|
||||||
|
ip.run_cell('display')
|
||||||
|
try:
|
||||||
|
ip.run_cell('del display')
|
||||||
|
except NameError:
|
||||||
|
pass # it's ok, it might be in builtins
|
||||||
|
# even if deleted it should be back
|
||||||
|
with AssertNotPrints('NameError'):
|
||||||
|
ip.run_cell('display')
|
||||||
|
|
||||||
|
def test_textdisplayobj_pretty_repr():
|
||||||
|
p = display.Pretty("This is a simple test")
|
||||||
|
nt.assert_equal(repr(p), '<yap_ipython.core.display.Pretty object>')
|
||||||
|
nt.assert_equal(p.data, 'This is a simple test')
|
||||||
|
|
||||||
|
p._show_mem_addr = True
|
||||||
|
nt.assert_equal(repr(p), object.__repr__(p))
|
||||||
|
|
||||||
|
def test_displayobject_repr():
|
||||||
|
h = display.HTML('<br />')
|
||||||
|
nt.assert_equal(repr(h), '<yap_ipython.core.display.HTML object>')
|
||||||
|
h._show_mem_addr = True
|
||||||
|
nt.assert_equal(repr(h), object.__repr__(h))
|
||||||
|
h._show_mem_addr = False
|
||||||
|
nt.assert_equal(repr(h), '<yap_ipython.core.display.HTML object>')
|
||||||
|
|
||||||
|
j = display.Javascript('')
|
||||||
|
nt.assert_equal(repr(j), '<yap_ipython.core.display.Javascript object>')
|
||||||
|
j._show_mem_addr = True
|
||||||
|
nt.assert_equal(repr(j), object.__repr__(j))
|
||||||
|
j._show_mem_addr = False
|
||||||
|
nt.assert_equal(repr(j), '<yap_ipython.core.display.Javascript object>')
|
||||||
|
|
||||||
|
def test_progress():
|
||||||
|
p = display.ProgressBar(10)
|
||||||
|
nt.assert_in('0/10',repr(p))
|
||||||
|
p.html_width = '100%'
|
||||||
|
p.progress = 5
|
||||||
|
nt.assert_equal(p._repr_html_(), "<progress style='width:100%' max='10' value='5'></progress>")
|
||||||
|
|
||||||
|
def test_progress_iter():
|
||||||
|
with capture_output(display=False) as captured:
|
||||||
|
for i in display.ProgressBar(5):
|
||||||
|
out = captured.stdout
|
||||||
|
nt.assert_in('{0}/5'.format(i), out)
|
||||||
|
out = captured.stdout
|
||||||
|
nt.assert_in('5/5', out)
|
||||||
|
|
||||||
|
def test_json():
|
||||||
|
d = {'a': 5}
|
||||||
|
lis = [d]
|
||||||
|
md = {'expanded': False}
|
||||||
|
md2 = {'expanded': True}
|
||||||
|
j = display.JSON(d)
|
||||||
|
j2 = display.JSON(d, expanded=True)
|
||||||
|
nt.assert_equal(j._repr_json_(), (d, md))
|
||||||
|
nt.assert_equal(j2._repr_json_(), (d, md2))
|
||||||
|
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
j = display.JSON(json.dumps(d))
|
||||||
|
nt.assert_equal(len(w), 1)
|
||||||
|
nt.assert_equal(j._repr_json_(), (d, md))
|
||||||
|
nt.assert_equal(j2._repr_json_(), (d, md2))
|
||||||
|
|
||||||
|
j = display.JSON(lis)
|
||||||
|
j2 = display.JSON(lis, expanded=True)
|
||||||
|
nt.assert_equal(j._repr_json_(), (lis, md))
|
||||||
|
nt.assert_equal(j2._repr_json_(), (lis, md2))
|
||||||
|
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
j = display.JSON(json.dumps(lis))
|
||||||
|
nt.assert_equal(len(w), 1)
|
||||||
|
nt.assert_equal(j._repr_json_(), (lis, md))
|
||||||
|
nt.assert_equal(j2._repr_json_(), (lis, md2))
|
||||||
|
|
||||||
|
def test_video_embedding():
|
||||||
|
"""use a tempfile, with dummy-data, to ensure that video embedding doesn't crash"""
|
||||||
|
v = display.Video("http://ignored")
|
||||||
|
assert not v.embed
|
||||||
|
html = v._repr_html_()
|
||||||
|
nt.assert_not_in('src="data:', html)
|
||||||
|
nt.assert_in('src="http://ignored"', html)
|
||||||
|
|
||||||
|
with nt.assert_raises(ValueError):
|
||||||
|
v = display.Video(b'abc')
|
||||||
|
|
||||||
|
with NamedFileInTemporaryDirectory('test.mp4') as f:
|
||||||
|
f.write(b'abc')
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
v = display.Video(f.name)
|
||||||
|
assert not v.embed
|
||||||
|
html = v._repr_html_()
|
||||||
|
nt.assert_not_in('src="data:', html)
|
||||||
|
|
||||||
|
v = display.Video(f.name, embed=True)
|
||||||
|
html = v._repr_html_()
|
||||||
|
nt.assert_in('src="data:video/mp4;base64,YWJj"',html)
|
||||||
|
|
||||||
|
v = display.Video(f.name, embed=True, mimetype='video/other')
|
||||||
|
html = v._repr_html_()
|
||||||
|
nt.assert_in('src="data:video/other;base64,YWJj"',html)
|
||||||
|
|
||||||
|
v = display.Video(b'abc', embed=True, mimetype='video/mp4')
|
||||||
|
html = v._repr_html_()
|
||||||
|
nt.assert_in('src="data:video/mp4;base64,YWJj"',html)
|
||||||
|
|
||||||
|
v = display.Video(u'YWJj', embed=True, mimetype='video/xyz')
|
||||||
|
html = v._repr_html_()
|
||||||
|
nt.assert_in('src="data:video/xyz;base64,YWJj"',html)
|
||||||
|
|
||||||
|
|
||||||
|
def test_display_id():
|
||||||
|
ip = get_ipython()
|
||||||
|
with mock.patch.object(ip.display_pub, 'publish') as pub:
|
||||||
|
handle = display.display('x')
|
||||||
|
nt.assert_is(handle, None)
|
||||||
|
handle = display.display('y', display_id='secret')
|
||||||
|
nt.assert_is_instance(handle, display.DisplayHandle)
|
||||||
|
handle2 = display.display('z', display_id=True)
|
||||||
|
nt.assert_is_instance(handle2, display.DisplayHandle)
|
||||||
|
nt.assert_not_equal(handle.display_id, handle2.display_id)
|
||||||
|
|
||||||
|
nt.assert_equal(pub.call_count, 3)
|
||||||
|
args, kwargs = pub.call_args_list[0]
|
||||||
|
nt.assert_equal(args, ())
|
||||||
|
nt.assert_equal(kwargs, {
|
||||||
|
'data': {
|
||||||
|
'text/plain': repr('x')
|
||||||
|
},
|
||||||
|
'metadata': {},
|
||||||
|
})
|
||||||
|
args, kwargs = pub.call_args_list[1]
|
||||||
|
nt.assert_equal(args, ())
|
||||||
|
nt.assert_equal(kwargs, {
|
||||||
|
'data': {
|
||||||
|
'text/plain': repr('y')
|
||||||
|
},
|
||||||
|
'metadata': {},
|
||||||
|
'transient': {
|
||||||
|
'display_id': handle.display_id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
args, kwargs = pub.call_args_list[2]
|
||||||
|
nt.assert_equal(args, ())
|
||||||
|
nt.assert_equal(kwargs, {
|
||||||
|
'data': {
|
||||||
|
'text/plain': repr('z')
|
||||||
|
},
|
||||||
|
'metadata': {},
|
||||||
|
'transient': {
|
||||||
|
'display_id': handle2.display_id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_display():
|
||||||
|
ip = get_ipython()
|
||||||
|
with mock.patch.object(ip.display_pub, 'publish') as pub:
|
||||||
|
with nt.assert_raises(TypeError):
|
||||||
|
display.update_display('x')
|
||||||
|
display.update_display('x', display_id='1')
|
||||||
|
display.update_display('y', display_id='2')
|
||||||
|
args, kwargs = pub.call_args_list[0]
|
||||||
|
nt.assert_equal(args, ())
|
||||||
|
nt.assert_equal(kwargs, {
|
||||||
|
'data': {
|
||||||
|
'text/plain': repr('x')
|
||||||
|
},
|
||||||
|
'metadata': {},
|
||||||
|
'transient': {
|
||||||
|
'display_id': '1',
|
||||||
|
},
|
||||||
|
'update': True,
|
||||||
|
})
|
||||||
|
args, kwargs = pub.call_args_list[1]
|
||||||
|
nt.assert_equal(args, ())
|
||||||
|
nt.assert_equal(kwargs, {
|
||||||
|
'data': {
|
||||||
|
'text/plain': repr('y')
|
||||||
|
},
|
||||||
|
'metadata': {},
|
||||||
|
'transient': {
|
||||||
|
'display_id': '2',
|
||||||
|
},
|
||||||
|
'update': True,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def test_display_handle():
|
||||||
|
ip = get_ipython()
|
||||||
|
handle = display.DisplayHandle()
|
||||||
|
nt.assert_is_instance(handle.display_id, str)
|
||||||
|
handle = display.DisplayHandle('my-id')
|
||||||
|
nt.assert_equal(handle.display_id, 'my-id')
|
||||||
|
with mock.patch.object(ip.display_pub, 'publish') as pub:
|
||||||
|
handle.display('x')
|
||||||
|
handle.update('y')
|
||||||
|
|
||||||
|
args, kwargs = pub.call_args_list[0]
|
||||||
|
nt.assert_equal(args, ())
|
||||||
|
nt.assert_equal(kwargs, {
|
||||||
|
'data': {
|
||||||
|
'text/plain': repr('x')
|
||||||
|
},
|
||||||
|
'metadata': {},
|
||||||
|
'transient': {
|
||||||
|
'display_id': handle.display_id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
args, kwargs = pub.call_args_list[1]
|
||||||
|
nt.assert_equal(args, ())
|
||||||
|
nt.assert_equal(kwargs, {
|
||||||
|
'data': {
|
||||||
|
'text/plain': repr('y')
|
||||||
|
},
|
||||||
|
'metadata': {},
|
||||||
|
'transient': {
|
||||||
|
'display_id': handle.display_id,
|
||||||
|
},
|
||||||
|
'update': True,
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,103 @@
|
|||||||
|
from yap_ipython.testing.tools import AssertPrints, AssertNotPrints
|
||||||
|
|
||||||
|
ip = get_ipython()
|
||||||
|
|
||||||
|
def test_output_displayed():
|
||||||
|
"""Checking to make sure that output is displayed"""
|
||||||
|
|
||||||
|
with AssertPrints('2'):
|
||||||
|
ip.run_cell('1+1', store_history=True)
|
||||||
|
|
||||||
|
with AssertPrints('2'):
|
||||||
|
ip.run_cell('1+1 # comment with a semicolon;', store_history=True)
|
||||||
|
|
||||||
|
with AssertPrints('2'):
|
||||||
|
ip.run_cell('1+1\n#commented_out_function();', store_history=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_output_quiet():
|
||||||
|
"""Checking to make sure that output is quiet"""
|
||||||
|
|
||||||
|
with AssertNotPrints('2'):
|
||||||
|
ip.run_cell('1+1;', store_history=True)
|
||||||
|
|
||||||
|
with AssertNotPrints('2'):
|
||||||
|
ip.run_cell('1+1; # comment with a semicolon', store_history=True)
|
||||||
|
|
||||||
|
with AssertNotPrints('2'):
|
||||||
|
ip.run_cell('1+1;\n#commented_out_function()', store_history=True)
|
||||||
|
|
||||||
|
def test_underscore_no_overrite_user():
|
||||||
|
ip.run_cell('_ = 42', store_history=True)
|
||||||
|
ip.run_cell('1+1', store_history=True)
|
||||||
|
|
||||||
|
with AssertPrints('42'):
|
||||||
|
ip.run_cell('print(_)', store_history=True)
|
||||||
|
|
||||||
|
ip.run_cell('del _', store_history=True)
|
||||||
|
ip.run_cell('6+6', store_history=True)
|
||||||
|
with AssertPrints('12'):
|
||||||
|
ip.run_cell('_', store_history=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_underscore_no_overrite_builtins():
|
||||||
|
ip.run_cell("import gettext ; gettext.install('foo')", store_history=True)
|
||||||
|
ip.run_cell('3+3', store_history=True)
|
||||||
|
|
||||||
|
with AssertPrints('gettext'):
|
||||||
|
ip.run_cell('print(_)', store_history=True)
|
||||||
|
|
||||||
|
ip.run_cell('_ = "userset"', store_history=True)
|
||||||
|
|
||||||
|
with AssertPrints('userset'):
|
||||||
|
ip.run_cell('print(_)', store_history=True)
|
||||||
|
ip.run_cell('import builtins; del builtins._')
|
||||||
|
|
||||||
|
|
||||||
|
def test_interactivehooks_ast_modes():
|
||||||
|
"""
|
||||||
|
Test that ast nodes can be triggered with different modes
|
||||||
|
"""
|
||||||
|
saved_mode = ip.ast_node_interactivity
|
||||||
|
ip.ast_node_interactivity = 'last_expr_or_assign'
|
||||||
|
|
||||||
|
try:
|
||||||
|
with AssertPrints('2'):
|
||||||
|
ip.run_cell('a = 1+1', store_history=True)
|
||||||
|
|
||||||
|
with AssertPrints('9'):
|
||||||
|
ip.run_cell('b = 1+8 # comment with a semicolon;', store_history=False)
|
||||||
|
|
||||||
|
with AssertPrints('7'):
|
||||||
|
ip.run_cell('c = 1+6\n#commented_out_function();', store_history=True)
|
||||||
|
|
||||||
|
ip.run_cell('d = 11', store_history=True)
|
||||||
|
with AssertPrints('12'):
|
||||||
|
ip.run_cell('d += 1', store_history=True)
|
||||||
|
|
||||||
|
with AssertNotPrints('42'):
|
||||||
|
ip.run_cell('(u,v) = (41+1, 43-1)')
|
||||||
|
|
||||||
|
finally:
|
||||||
|
ip.ast_node_interactivity = saved_mode
|
||||||
|
|
||||||
|
def test_interactivehooks_ast_modes_semi_supress():
|
||||||
|
"""
|
||||||
|
Test that ast nodes can be triggered with different modes and suppressed
|
||||||
|
by semicolon
|
||||||
|
"""
|
||||||
|
saved_mode = ip.ast_node_interactivity
|
||||||
|
ip.ast_node_interactivity = 'last_expr_or_assign'
|
||||||
|
|
||||||
|
try:
|
||||||
|
with AssertNotPrints('2'):
|
||||||
|
ip.run_cell('x = 1+1;', store_history=True)
|
||||||
|
|
||||||
|
with AssertNotPrints('7'):
|
||||||
|
ip.run_cell('y = 1+6; # comment with a semicolon', store_history=True)
|
||||||
|
|
||||||
|
with AssertNotPrints('9'):
|
||||||
|
ip.run_cell('z = 1+8;\n#commented_out_function()', store_history=True)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
ip.ast_node_interactivity = saved_mode
|
@ -0,0 +1,87 @@
|
|||||||
|
from backcall import callback_prototype
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import Mock
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython.core import events
|
||||||
|
import yap_ipython.testing.tools as tt
|
||||||
|
|
||||||
|
|
||||||
|
@events._define_event
|
||||||
|
def ping_received():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@events._define_event
|
||||||
|
def event_with_argument(argument):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CallbackTests(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.em = events.EventManager(get_ipython(),
|
||||||
|
{'ping_received': ping_received,
|
||||||
|
'event_with_argument': event_with_argument})
|
||||||
|
|
||||||
|
def test_register_unregister(self):
|
||||||
|
cb = Mock()
|
||||||
|
|
||||||
|
self.em.register('ping_received', cb)
|
||||||
|
self.em.trigger('ping_received')
|
||||||
|
self.assertEqual(cb.call_count, 1)
|
||||||
|
|
||||||
|
self.em.unregister('ping_received', cb)
|
||||||
|
self.em.trigger('ping_received')
|
||||||
|
self.assertEqual(cb.call_count, 1)
|
||||||
|
|
||||||
|
def test_bare_function_missed_unregister(self):
|
||||||
|
def cb1():
|
||||||
|
...
|
||||||
|
|
||||||
|
def cb2():
|
||||||
|
...
|
||||||
|
|
||||||
|
self.em.register('ping_received', cb1)
|
||||||
|
nt.assert_raises(ValueError, self.em.unregister, 'ping_received', cb2)
|
||||||
|
self.em.unregister('ping_received', cb1)
|
||||||
|
|
||||||
|
def test_cb_error(self):
|
||||||
|
cb = Mock(side_effect=ValueError)
|
||||||
|
self.em.register('ping_received', cb)
|
||||||
|
with tt.AssertPrints("Error in callback"):
|
||||||
|
self.em.trigger('ping_received')
|
||||||
|
|
||||||
|
def test_unregister_during_callback(self):
|
||||||
|
invoked = [False] * 3
|
||||||
|
|
||||||
|
def func1(*_):
|
||||||
|
invoked[0] = True
|
||||||
|
self.em.unregister('ping_received', func1)
|
||||||
|
self.em.register('ping_received', func3)
|
||||||
|
|
||||||
|
def func2(*_):
|
||||||
|
invoked[1] = True
|
||||||
|
self.em.unregister('ping_received', func2)
|
||||||
|
|
||||||
|
def func3(*_):
|
||||||
|
invoked[2] = True
|
||||||
|
|
||||||
|
self.em.register('ping_received', func1)
|
||||||
|
self.em.register('ping_received', func2)
|
||||||
|
|
||||||
|
self.em.trigger('ping_received')
|
||||||
|
self.assertEqual([True, True, False], invoked)
|
||||||
|
self.assertEqual([func3], self.em.callbacks['ping_received'])
|
||||||
|
|
||||||
|
def test_ignore_event_arguments_if_no_argument_required(self):
|
||||||
|
call_count = [0]
|
||||||
|
def event_with_no_argument():
|
||||||
|
call_count[0] += 1
|
||||||
|
|
||||||
|
self.em.register('event_with_argument', event_with_no_argument)
|
||||||
|
self.em.trigger('event_with_argument', 'the argument')
|
||||||
|
self.assertEqual(call_count[0], 1)
|
||||||
|
|
||||||
|
self.em.unregister('event_with_argument', event_with_no_argument)
|
||||||
|
self.em.trigger('ping_received')
|
||||||
|
self.assertEqual(call_count[0], 1)
|
@ -0,0 +1,96 @@
|
|||||||
|
import os.path
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
import yap_ipython.testing.tools as tt
|
||||||
|
from yap_ipython.utils.syspathcontext import prepended_to_syspath
|
||||||
|
from yap_ipython.utils.tempdir import TemporaryDirectory
|
||||||
|
|
||||||
|
ext1_content = """
|
||||||
|
def load_ipython_extension(ip):
|
||||||
|
print("Running ext1 load")
|
||||||
|
|
||||||
|
def unload_ipython_extension(ip):
|
||||||
|
print("Running ext1 unload")
|
||||||
|
"""
|
||||||
|
|
||||||
|
ext2_content = """
|
||||||
|
def load_ipython_extension(ip):
|
||||||
|
print("Running ext2 load")
|
||||||
|
"""
|
||||||
|
|
||||||
|
ext3_content = """
|
||||||
|
def load_ipython_extension(ip):
|
||||||
|
ip2 = get_ipython()
|
||||||
|
print(ip is ip2)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_extension_loading():
|
||||||
|
em = get_ipython().extension_manager
|
||||||
|
with TemporaryDirectory() as td:
|
||||||
|
ext1 = os.path.join(td, 'ext1.py')
|
||||||
|
with open(ext1, 'w') as f:
|
||||||
|
f.write(ext1_content)
|
||||||
|
|
||||||
|
ext2 = os.path.join(td, 'ext2.py')
|
||||||
|
with open(ext2, 'w') as f:
|
||||||
|
f.write(ext2_content)
|
||||||
|
|
||||||
|
with prepended_to_syspath(td):
|
||||||
|
assert 'ext1' not in em.loaded
|
||||||
|
assert 'ext2' not in em.loaded
|
||||||
|
|
||||||
|
# Load extension
|
||||||
|
with tt.AssertPrints("Running ext1 load"):
|
||||||
|
assert em.load_extension('ext1') is None
|
||||||
|
assert 'ext1' in em.loaded
|
||||||
|
|
||||||
|
# Should refuse to load it again
|
||||||
|
with tt.AssertNotPrints("Running ext1 load"):
|
||||||
|
assert em.load_extension('ext1') == 'already loaded'
|
||||||
|
|
||||||
|
# Reload
|
||||||
|
with tt.AssertPrints("Running ext1 unload"):
|
||||||
|
with tt.AssertPrints("Running ext1 load", suppress=False):
|
||||||
|
em.reload_extension('ext1')
|
||||||
|
|
||||||
|
# Unload
|
||||||
|
with tt.AssertPrints("Running ext1 unload"):
|
||||||
|
assert em.unload_extension('ext1') is None
|
||||||
|
|
||||||
|
# Can't unload again
|
||||||
|
with tt.AssertNotPrints("Running ext1 unload"):
|
||||||
|
assert em.unload_extension('ext1') == 'not loaded'
|
||||||
|
assert em.unload_extension('ext2') == 'not loaded'
|
||||||
|
|
||||||
|
# Load extension 2
|
||||||
|
with tt.AssertPrints("Running ext2 load"):
|
||||||
|
assert em.load_extension('ext2') is None
|
||||||
|
|
||||||
|
# Can't unload this
|
||||||
|
assert em.unload_extension('ext2') == 'no unload function'
|
||||||
|
|
||||||
|
# But can reload it
|
||||||
|
with tt.AssertPrints("Running ext2 load"):
|
||||||
|
em.reload_extension('ext2')
|
||||||
|
|
||||||
|
|
||||||
|
def test_extension_builtins():
|
||||||
|
em = get_ipython().extension_manager
|
||||||
|
with TemporaryDirectory() as td:
|
||||||
|
ext3 = os.path.join(td, 'ext3.py')
|
||||||
|
with open(ext3, 'w') as f:
|
||||||
|
f.write(ext3_content)
|
||||||
|
|
||||||
|
assert 'ext3' not in em.loaded
|
||||||
|
|
||||||
|
with prepended_to_syspath(td):
|
||||||
|
# Load extension
|
||||||
|
with tt.AssertPrints("True"):
|
||||||
|
assert em.load_extension('ext3') is None
|
||||||
|
assert 'ext3' in em.loaded
|
||||||
|
|
||||||
|
|
||||||
|
def test_non_extension():
|
||||||
|
em = get_ipython().extension_manager
|
||||||
|
nt.assert_equal(em.load_extension('sys'), "no load function")
|
@ -0,0 +1,533 @@
|
|||||||
|
"""Tests for the Formatters."""
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
from math import pi
|
||||||
|
|
||||||
|
try:
|
||||||
|
import numpy
|
||||||
|
except:
|
||||||
|
numpy = None
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython import get_ipython
|
||||||
|
from traitlets.config import Config
|
||||||
|
from yap_ipython.core.formatters import (
|
||||||
|
PlainTextFormatter, HTMLFormatter, PDFFormatter, _mod_name_key,
|
||||||
|
DisplayFormatter, JSONFormatter,
|
||||||
|
)
|
||||||
|
from yap_ipython.utils.io import capture_output
|
||||||
|
|
||||||
|
class A(object):
|
||||||
|
def __repr__(self):
|
||||||
|
return 'A()'
|
||||||
|
|
||||||
|
class B(A):
|
||||||
|
def __repr__(self):
|
||||||
|
return 'B()'
|
||||||
|
|
||||||
|
class C:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class BadRepr(object):
|
||||||
|
def __repr__(self):
|
||||||
|
raise ValueError("bad repr")
|
||||||
|
|
||||||
|
class BadPretty(object):
|
||||||
|
_repr_pretty_ = None
|
||||||
|
|
||||||
|
class GoodPretty(object):
|
||||||
|
def _repr_pretty_(self, pp, cycle):
|
||||||
|
pp.text('foo')
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'GoodPretty()'
|
||||||
|
|
||||||
|
def foo_printer(obj, pp, cycle):
|
||||||
|
pp.text('foo')
|
||||||
|
|
||||||
|
def test_pretty():
|
||||||
|
f = PlainTextFormatter()
|
||||||
|
f.for_type(A, foo_printer)
|
||||||
|
nt.assert_equal(f(A()), 'foo')
|
||||||
|
nt.assert_equal(f(B()), 'foo')
|
||||||
|
nt.assert_equal(f(GoodPretty()), 'foo')
|
||||||
|
# Just don't raise an exception for the following:
|
||||||
|
f(BadPretty())
|
||||||
|
|
||||||
|
f.pprint = False
|
||||||
|
nt.assert_equal(f(A()), 'A()')
|
||||||
|
nt.assert_equal(f(B()), 'B()')
|
||||||
|
nt.assert_equal(f(GoodPretty()), 'GoodPretty()')
|
||||||
|
|
||||||
|
|
||||||
|
def test_deferred():
|
||||||
|
f = PlainTextFormatter()
|
||||||
|
|
||||||
|
def test_precision():
|
||||||
|
"""test various values for float_precision."""
|
||||||
|
f = PlainTextFormatter()
|
||||||
|
nt.assert_equal(f(pi), repr(pi))
|
||||||
|
f.float_precision = 0
|
||||||
|
if numpy:
|
||||||
|
po = numpy.get_printoptions()
|
||||||
|
nt.assert_equal(po['precision'], 0)
|
||||||
|
nt.assert_equal(f(pi), '3')
|
||||||
|
f.float_precision = 2
|
||||||
|
if numpy:
|
||||||
|
po = numpy.get_printoptions()
|
||||||
|
nt.assert_equal(po['precision'], 2)
|
||||||
|
nt.assert_equal(f(pi), '3.14')
|
||||||
|
f.float_precision = '%g'
|
||||||
|
if numpy:
|
||||||
|
po = numpy.get_printoptions()
|
||||||
|
nt.assert_equal(po['precision'], 2)
|
||||||
|
nt.assert_equal(f(pi), '3.14159')
|
||||||
|
f.float_precision = '%e'
|
||||||
|
nt.assert_equal(f(pi), '3.141593e+00')
|
||||||
|
f.float_precision = ''
|
||||||
|
if numpy:
|
||||||
|
po = numpy.get_printoptions()
|
||||||
|
nt.assert_equal(po['precision'], 8)
|
||||||
|
nt.assert_equal(f(pi), repr(pi))
|
||||||
|
|
||||||
|
def test_bad_precision():
|
||||||
|
"""test various invalid values for float_precision."""
|
||||||
|
f = PlainTextFormatter()
|
||||||
|
def set_fp(p):
|
||||||
|
f.float_precision=p
|
||||||
|
nt.assert_raises(ValueError, set_fp, '%')
|
||||||
|
nt.assert_raises(ValueError, set_fp, '%.3f%i')
|
||||||
|
nt.assert_raises(ValueError, set_fp, 'foo')
|
||||||
|
nt.assert_raises(ValueError, set_fp, -1)
|
||||||
|
|
||||||
|
def test_for_type():
|
||||||
|
f = PlainTextFormatter()
|
||||||
|
|
||||||
|
# initial return, None
|
||||||
|
nt.assert_is(f.for_type(C, foo_printer), None)
|
||||||
|
# no func queries
|
||||||
|
nt.assert_is(f.for_type(C), foo_printer)
|
||||||
|
# shouldn't change anything
|
||||||
|
nt.assert_is(f.for_type(C), foo_printer)
|
||||||
|
# None should do the same
|
||||||
|
nt.assert_is(f.for_type(C, None), foo_printer)
|
||||||
|
nt.assert_is(f.for_type(C, None), foo_printer)
|
||||||
|
|
||||||
|
def test_for_type_string():
|
||||||
|
f = PlainTextFormatter()
|
||||||
|
|
||||||
|
type_str = '%s.%s' % (C.__module__, 'C')
|
||||||
|
|
||||||
|
# initial return, None
|
||||||
|
nt.assert_is(f.for_type(type_str, foo_printer), None)
|
||||||
|
# no func queries
|
||||||
|
nt.assert_is(f.for_type(type_str), foo_printer)
|
||||||
|
nt.assert_in(_mod_name_key(C), f.deferred_printers)
|
||||||
|
nt.assert_is(f.for_type(C), foo_printer)
|
||||||
|
nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
|
||||||
|
nt.assert_in(C, f.type_printers)
|
||||||
|
|
||||||
|
def test_for_type_by_name():
|
||||||
|
f = PlainTextFormatter()
|
||||||
|
|
||||||
|
mod = C.__module__
|
||||||
|
|
||||||
|
# initial return, None
|
||||||
|
nt.assert_is(f.for_type_by_name(mod, 'C', foo_printer), None)
|
||||||
|
# no func queries
|
||||||
|
nt.assert_is(f.for_type_by_name(mod, 'C'), foo_printer)
|
||||||
|
# shouldn't change anything
|
||||||
|
nt.assert_is(f.for_type_by_name(mod, 'C'), foo_printer)
|
||||||
|
# None should do the same
|
||||||
|
nt.assert_is(f.for_type_by_name(mod, 'C', None), foo_printer)
|
||||||
|
nt.assert_is(f.for_type_by_name(mod, 'C', None), foo_printer)
|
||||||
|
|
||||||
|
def test_lookup():
|
||||||
|
f = PlainTextFormatter()
|
||||||
|
|
||||||
|
f.for_type(C, foo_printer)
|
||||||
|
nt.assert_is(f.lookup(C()), foo_printer)
|
||||||
|
with nt.assert_raises(KeyError):
|
||||||
|
f.lookup(A())
|
||||||
|
|
||||||
|
def test_lookup_string():
|
||||||
|
f = PlainTextFormatter()
|
||||||
|
type_str = '%s.%s' % (C.__module__, 'C')
|
||||||
|
|
||||||
|
f.for_type(type_str, foo_printer)
|
||||||
|
nt.assert_is(f.lookup(C()), foo_printer)
|
||||||
|
# should move from deferred to imported dict
|
||||||
|
nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
|
||||||
|
nt.assert_in(C, f.type_printers)
|
||||||
|
|
||||||
|
def test_lookup_by_type():
|
||||||
|
f = PlainTextFormatter()
|
||||||
|
f.for_type(C, foo_printer)
|
||||||
|
nt.assert_is(f.lookup_by_type(C), foo_printer)
|
||||||
|
with nt.assert_raises(KeyError):
|
||||||
|
f.lookup_by_type(A)
|
||||||
|
|
||||||
|
def test_lookup_by_type_string():
|
||||||
|
f = PlainTextFormatter()
|
||||||
|
type_str = '%s.%s' % (C.__module__, 'C')
|
||||||
|
f.for_type(type_str, foo_printer)
|
||||||
|
|
||||||
|
# verify insertion
|
||||||
|
nt.assert_in(_mod_name_key(C), f.deferred_printers)
|
||||||
|
nt.assert_not_in(C, f.type_printers)
|
||||||
|
|
||||||
|
nt.assert_is(f.lookup_by_type(type_str), foo_printer)
|
||||||
|
# lookup by string doesn't cause import
|
||||||
|
nt.assert_in(_mod_name_key(C), f.deferred_printers)
|
||||||
|
nt.assert_not_in(C, f.type_printers)
|
||||||
|
|
||||||
|
nt.assert_is(f.lookup_by_type(C), foo_printer)
|
||||||
|
# should move from deferred to imported dict
|
||||||
|
nt.assert_not_in(_mod_name_key(C), f.deferred_printers)
|
||||||
|
nt.assert_in(C, f.type_printers)
|
||||||
|
|
||||||
|
def test_in_formatter():
|
||||||
|
f = PlainTextFormatter()
|
||||||
|
f.for_type(C, foo_printer)
|
||||||
|
type_str = '%s.%s' % (C.__module__, 'C')
|
||||||
|
nt.assert_in(C, f)
|
||||||
|
nt.assert_in(type_str, f)
|
||||||
|
|
||||||
|
def test_string_in_formatter():
|
||||||
|
f = PlainTextFormatter()
|
||||||
|
type_str = '%s.%s' % (C.__module__, 'C')
|
||||||
|
f.for_type(type_str, foo_printer)
|
||||||
|
nt.assert_in(type_str, f)
|
||||||
|
nt.assert_in(C, f)
|
||||||
|
|
||||||
|
def test_pop():
|
||||||
|
f = PlainTextFormatter()
|
||||||
|
f.for_type(C, foo_printer)
|
||||||
|
nt.assert_is(f.lookup_by_type(C), foo_printer)
|
||||||
|
nt.assert_is(f.pop(C, None), foo_printer)
|
||||||
|
f.for_type(C, foo_printer)
|
||||||
|
nt.assert_is(f.pop(C), foo_printer)
|
||||||
|
with nt.assert_raises(KeyError):
|
||||||
|
f.lookup_by_type(C)
|
||||||
|
with nt.assert_raises(KeyError):
|
||||||
|
f.pop(C)
|
||||||
|
with nt.assert_raises(KeyError):
|
||||||
|
f.pop(A)
|
||||||
|
nt.assert_is(f.pop(A, None), None)
|
||||||
|
|
||||||
|
def test_pop_string():
|
||||||
|
f = PlainTextFormatter()
|
||||||
|
type_str = '%s.%s' % (C.__module__, 'C')
|
||||||
|
|
||||||
|
with nt.assert_raises(KeyError):
|
||||||
|
f.pop(type_str)
|
||||||
|
|
||||||
|
f.for_type(type_str, foo_printer)
|
||||||
|
f.pop(type_str)
|
||||||
|
with nt.assert_raises(KeyError):
|
||||||
|
f.lookup_by_type(C)
|
||||||
|
with nt.assert_raises(KeyError):
|
||||||
|
f.pop(type_str)
|
||||||
|
|
||||||
|
f.for_type(C, foo_printer)
|
||||||
|
nt.assert_is(f.pop(type_str, None), foo_printer)
|
||||||
|
with nt.assert_raises(KeyError):
|
||||||
|
f.lookup_by_type(C)
|
||||||
|
with nt.assert_raises(KeyError):
|
||||||
|
f.pop(type_str)
|
||||||
|
nt.assert_is(f.pop(type_str, None), None)
|
||||||
|
|
||||||
|
|
||||||
|
def test_error_method():
|
||||||
|
f = HTMLFormatter()
|
||||||
|
class BadHTML(object):
|
||||||
|
def _repr_html_(self):
|
||||||
|
raise ValueError("Bad HTML")
|
||||||
|
bad = BadHTML()
|
||||||
|
with capture_output() as captured:
|
||||||
|
result = f(bad)
|
||||||
|
nt.assert_is(result, None)
|
||||||
|
nt.assert_in("Traceback", captured.stdout)
|
||||||
|
nt.assert_in("Bad HTML", captured.stdout)
|
||||||
|
nt.assert_in("_repr_html_", captured.stdout)
|
||||||
|
|
||||||
|
def test_nowarn_notimplemented():
|
||||||
|
f = HTMLFormatter()
|
||||||
|
class HTMLNotImplemented(object):
|
||||||
|
def _repr_html_(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
h = HTMLNotImplemented()
|
||||||
|
with capture_output() as captured:
|
||||||
|
result = f(h)
|
||||||
|
nt.assert_is(result, None)
|
||||||
|
nt.assert_equal("", captured.stderr)
|
||||||
|
nt.assert_equal("", captured.stdout)
|
||||||
|
|
||||||
|
def test_warn_error_for_type():
|
||||||
|
f = HTMLFormatter()
|
||||||
|
f.for_type(int, lambda i: name_error)
|
||||||
|
with capture_output() as captured:
|
||||||
|
result = f(5)
|
||||||
|
nt.assert_is(result, None)
|
||||||
|
nt.assert_in("Traceback", captured.stdout)
|
||||||
|
nt.assert_in("NameError", captured.stdout)
|
||||||
|
nt.assert_in("name_error", captured.stdout)
|
||||||
|
|
||||||
|
def test_error_pretty_method():
|
||||||
|
f = PlainTextFormatter()
|
||||||
|
class BadPretty(object):
|
||||||
|
def _repr_pretty_(self):
|
||||||
|
return "hello"
|
||||||
|
bad = BadPretty()
|
||||||
|
with capture_output() as captured:
|
||||||
|
result = f(bad)
|
||||||
|
nt.assert_is(result, None)
|
||||||
|
nt.assert_in("Traceback", captured.stdout)
|
||||||
|
nt.assert_in("_repr_pretty_", captured.stdout)
|
||||||
|
nt.assert_in("given", captured.stdout)
|
||||||
|
nt.assert_in("argument", captured.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bad_repr_traceback():
|
||||||
|
f = PlainTextFormatter()
|
||||||
|
bad = BadRepr()
|
||||||
|
with capture_output() as captured:
|
||||||
|
result = f(bad)
|
||||||
|
# catches error, returns None
|
||||||
|
nt.assert_is(result, None)
|
||||||
|
nt.assert_in("Traceback", captured.stdout)
|
||||||
|
nt.assert_in("__repr__", captured.stdout)
|
||||||
|
nt.assert_in("ValueError", captured.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
class MakePDF(object):
|
||||||
|
def _repr_pdf_(self):
|
||||||
|
return 'PDF'
|
||||||
|
|
||||||
|
def test_pdf_formatter():
|
||||||
|
pdf = MakePDF()
|
||||||
|
f = PDFFormatter()
|
||||||
|
nt.assert_equal(f(pdf), 'PDF')
|
||||||
|
|
||||||
|
def test_print_method_bound():
|
||||||
|
f = HTMLFormatter()
|
||||||
|
class MyHTML(object):
|
||||||
|
def _repr_html_(self):
|
||||||
|
return "hello"
|
||||||
|
with capture_output() as captured:
|
||||||
|
result = f(MyHTML)
|
||||||
|
nt.assert_is(result, None)
|
||||||
|
nt.assert_not_in("FormatterWarning", captured.stderr)
|
||||||
|
|
||||||
|
with capture_output() as captured:
|
||||||
|
result = f(MyHTML())
|
||||||
|
nt.assert_equal(result, "hello")
|
||||||
|
nt.assert_equal(captured.stderr, "")
|
||||||
|
|
||||||
|
def test_print_method_weird():
|
||||||
|
|
||||||
|
class TextMagicHat(object):
|
||||||
|
def __getattr__(self, key):
|
||||||
|
return key
|
||||||
|
|
||||||
|
f = HTMLFormatter()
|
||||||
|
|
||||||
|
text_hat = TextMagicHat()
|
||||||
|
nt.assert_equal(text_hat._repr_html_, '_repr_html_')
|
||||||
|
with capture_output() as captured:
|
||||||
|
result = f(text_hat)
|
||||||
|
|
||||||
|
nt.assert_is(result, None)
|
||||||
|
nt.assert_not_in("FormatterWarning", captured.stderr)
|
||||||
|
|
||||||
|
class CallableMagicHat(object):
|
||||||
|
def __getattr__(self, key):
|
||||||
|
return lambda : key
|
||||||
|
|
||||||
|
call_hat = CallableMagicHat()
|
||||||
|
with capture_output() as captured:
|
||||||
|
result = f(call_hat)
|
||||||
|
|
||||||
|
nt.assert_equal(result, None)
|
||||||
|
|
||||||
|
class BadReprArgs(object):
|
||||||
|
def _repr_html_(self, extra, args):
|
||||||
|
return "html"
|
||||||
|
|
||||||
|
bad = BadReprArgs()
|
||||||
|
with capture_output() as captured:
|
||||||
|
result = f(bad)
|
||||||
|
|
||||||
|
nt.assert_is(result, None)
|
||||||
|
nt.assert_not_in("FormatterWarning", captured.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def test_format_config():
|
||||||
|
"""config objects don't pretend to support fancy reprs with lazy attrs"""
|
||||||
|
f = HTMLFormatter()
|
||||||
|
cfg = Config()
|
||||||
|
with capture_output() as captured:
|
||||||
|
result = f(cfg)
|
||||||
|
nt.assert_is(result, None)
|
||||||
|
nt.assert_equal(captured.stderr, "")
|
||||||
|
|
||||||
|
with capture_output() as captured:
|
||||||
|
result = f(Config)
|
||||||
|
nt.assert_is(result, None)
|
||||||
|
nt.assert_equal(captured.stderr, "")
|
||||||
|
|
||||||
|
def test_pretty_max_seq_length():
|
||||||
|
f = PlainTextFormatter(max_seq_length=1)
|
||||||
|
lis = list(range(3))
|
||||||
|
text = f(lis)
|
||||||
|
nt.assert_equal(text, '[0, ...]')
|
||||||
|
f.max_seq_length = 0
|
||||||
|
text = f(lis)
|
||||||
|
nt.assert_equal(text, '[0, 1, 2]')
|
||||||
|
text = f(list(range(1024)))
|
||||||
|
lines = text.splitlines()
|
||||||
|
nt.assert_equal(len(lines), 1024)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipython_display_formatter():
|
||||||
|
"""Objects with _ipython_display_ defined bypass other formatters"""
|
||||||
|
f = get_ipython().display_formatter
|
||||||
|
catcher = []
|
||||||
|
class SelfDisplaying(object):
|
||||||
|
def _ipython_display_(self):
|
||||||
|
catcher.append(self)
|
||||||
|
|
||||||
|
class NotSelfDisplaying(object):
|
||||||
|
def __repr__(self):
|
||||||
|
return "NotSelfDisplaying"
|
||||||
|
|
||||||
|
def _ipython_display_(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
save_enabled = f.ipython_display_formatter.enabled
|
||||||
|
f.ipython_display_formatter.enabled = True
|
||||||
|
|
||||||
|
yes = SelfDisplaying()
|
||||||
|
no = NotSelfDisplaying()
|
||||||
|
|
||||||
|
d, md = f.format(no)
|
||||||
|
nt.assert_equal(d, {'text/plain': repr(no)})
|
||||||
|
nt.assert_equal(md, {})
|
||||||
|
nt.assert_equal(catcher, [])
|
||||||
|
|
||||||
|
d, md = f.format(yes)
|
||||||
|
nt.assert_equal(d, {})
|
||||||
|
nt.assert_equal(md, {})
|
||||||
|
nt.assert_equal(catcher, [yes])
|
||||||
|
|
||||||
|
f.ipython_display_formatter.enabled = save_enabled
|
||||||
|
|
||||||
|
|
||||||
|
def test_json_as_string_deprecated():
|
||||||
|
class JSONString(object):
|
||||||
|
def _repr_json_(self):
|
||||||
|
return '{}'
|
||||||
|
|
||||||
|
f = JSONFormatter()
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
d = f(JSONString())
|
||||||
|
nt.assert_equal(d, {})
|
||||||
|
nt.assert_equal(len(w), 1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_repr_mime():
|
||||||
|
class HasReprMime(object):
|
||||||
|
def _repr_mimebundle_(self, include=None, exclude=None):
|
||||||
|
return {
|
||||||
|
'application/json+test.v2': {
|
||||||
|
'x': 'y'
|
||||||
|
},
|
||||||
|
'plain/text' : '<HasReprMime>',
|
||||||
|
'image/png' : 'i-overwrite'
|
||||||
|
}
|
||||||
|
|
||||||
|
def _repr_png_(self):
|
||||||
|
return 'should-be-overwritten'
|
||||||
|
def _repr_html_(self):
|
||||||
|
return '<b>hi!</b>'
|
||||||
|
|
||||||
|
f = get_ipython().display_formatter
|
||||||
|
html_f = f.formatters['text/html']
|
||||||
|
save_enabled = html_f.enabled
|
||||||
|
html_f.enabled = True
|
||||||
|
obj = HasReprMime()
|
||||||
|
d, md = f.format(obj)
|
||||||
|
html_f.enabled = save_enabled
|
||||||
|
|
||||||
|
nt.assert_equal(sorted(d), ['application/json+test.v2',
|
||||||
|
'image/png',
|
||||||
|
'plain/text',
|
||||||
|
'text/html',
|
||||||
|
'text/plain'])
|
||||||
|
nt.assert_equal(md, {})
|
||||||
|
|
||||||
|
d, md = f.format(obj, include={'image/png'})
|
||||||
|
nt.assert_equal(list(d.keys()), ['image/png'],
|
||||||
|
'Include should filter out even things from repr_mimebundle')
|
||||||
|
nt.assert_equal(d['image/png'], 'i-overwrite', '_repr_mimebundle_ take precedence')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_pass_correct_include_exclude():
|
||||||
|
class Tester(object):
|
||||||
|
|
||||||
|
def __init__(self, include=None, exclude=None):
|
||||||
|
self.include = include
|
||||||
|
self.exclude = exclude
|
||||||
|
|
||||||
|
def _repr_mimebundle_(self, include, exclude, **kwargs):
|
||||||
|
if include and (include != self.include):
|
||||||
|
raise ValueError('include got modified: display() may be broken.')
|
||||||
|
if exclude and (exclude != self.exclude):
|
||||||
|
raise ValueError('exclude got modified: display() may be broken.')
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
include = {'a', 'b', 'c'}
|
||||||
|
exclude = {'c', 'e' , 'f'}
|
||||||
|
|
||||||
|
f = get_ipython().display_formatter
|
||||||
|
f.format(Tester(include=include, exclude=exclude), include=include, exclude=exclude)
|
||||||
|
f.format(Tester(exclude=exclude), exclude=exclude)
|
||||||
|
f.format(Tester(include=include), include=include)
|
||||||
|
|
||||||
|
|
||||||
|
def test_repr_mime_meta():
|
||||||
|
class HasReprMimeMeta(object):
|
||||||
|
def _repr_mimebundle_(self, include=None, exclude=None):
|
||||||
|
data = {
|
||||||
|
'image/png': 'base64-image-data',
|
||||||
|
}
|
||||||
|
metadata = {
|
||||||
|
'image/png': {
|
||||||
|
'width': 5,
|
||||||
|
'height': 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (data, metadata)
|
||||||
|
|
||||||
|
f = get_ipython().display_formatter
|
||||||
|
obj = HasReprMimeMeta()
|
||||||
|
d, md = f.format(obj)
|
||||||
|
nt.assert_equal(sorted(d), ['image/png', 'text/plain'])
|
||||||
|
nt.assert_equal(md, {
|
||||||
|
'image/png': {
|
||||||
|
'width': 5,
|
||||||
|
'height': 10,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_repr_mime_failure():
|
||||||
|
class BadReprMime(object):
|
||||||
|
def _repr_mimebundle_(self, include=None, exclude=None):
|
||||||
|
raise RuntimeError
|
||||||
|
|
||||||
|
f = get_ipython().display_formatter
|
||||||
|
obj = BadReprMime()
|
||||||
|
d, md = f.format(obj)
|
||||||
|
nt.assert_in('text/plain', d)
|
@ -0,0 +1,97 @@
|
|||||||
|
"""Tests for input handlers.
|
||||||
|
"""
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Module imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# third party
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
# our own packages
|
||||||
|
from yap_ipython.core import autocall
|
||||||
|
from yap_ipython.testing import tools as tt
|
||||||
|
from yap_ipython.testing.globalipapp import get_ipython
|
||||||
|
from yap_ipython.utils import py3compat
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Globals
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Get the public instance of yap_ipython
|
||||||
|
ip = get_ipython()
|
||||||
|
|
||||||
|
failures = []
|
||||||
|
num_tests = 0
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Test functions
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class CallableIndexable(object):
|
||||||
|
def __getitem__(self, idx): return True
|
||||||
|
def __call__(self, *args, **kws): return True
|
||||||
|
|
||||||
|
|
||||||
|
class Autocallable(autocall.IPyAutocall):
|
||||||
|
def __call__(self):
|
||||||
|
return "called"
|
||||||
|
|
||||||
|
|
||||||
|
def run(tests):
|
||||||
|
"""Loop through a list of (pre, post) inputs, where pre is the string
|
||||||
|
handed to ipython, and post is how that string looks after it's been
|
||||||
|
transformed (i.e. ipython's notion of _i)"""
|
||||||
|
tt.check_pairs(ip.prefilter_manager.prefilter_lines, tests)
|
||||||
|
|
||||||
|
|
||||||
|
def test_handlers():
|
||||||
|
call_idx = CallableIndexable()
|
||||||
|
ip.user_ns['call_idx'] = call_idx
|
||||||
|
|
||||||
|
# For many of the below, we're also checking that leading whitespace
|
||||||
|
# turns off the esc char, which it should unless there is a continuation
|
||||||
|
# line.
|
||||||
|
run([(i,py3compat.u_format(o)) for i,o in \
|
||||||
|
[('"no change"', '"no change"'), # normal
|
||||||
|
(u"lsmagic", "get_ipython().run_line_magic('lsmagic', '')"), # magic
|
||||||
|
#("a = b # PYTHON-MODE", '_i'), # emacs -- avoids _in cache
|
||||||
|
]])
|
||||||
|
|
||||||
|
# Objects which are instances of IPyAutocall are *always* autocalled
|
||||||
|
autocallable = Autocallable()
|
||||||
|
ip.user_ns['autocallable'] = autocallable
|
||||||
|
|
||||||
|
# auto
|
||||||
|
ip.magic('autocall 0')
|
||||||
|
# Only explicit escapes or instances of IPyAutocallable should get
|
||||||
|
# expanded
|
||||||
|
run([
|
||||||
|
('len "abc"', 'len "abc"'),
|
||||||
|
('autocallable', 'autocallable()'),
|
||||||
|
# Don't add extra brackets (gh-1117)
|
||||||
|
('autocallable()', 'autocallable()'),
|
||||||
|
])
|
||||||
|
ip.magic('autocall 1')
|
||||||
|
run([
|
||||||
|
('len "abc"', 'len("abc")'),
|
||||||
|
('len "abc";', 'len("abc");'), # ; is special -- moves out of parens
|
||||||
|
# Autocall is turned off if first arg is [] and the object
|
||||||
|
# is both callable and indexable. Like so:
|
||||||
|
('len [1,2]', 'len([1,2])'), # len doesn't support __getitem__...
|
||||||
|
('call_idx [1]', 'call_idx [1]'), # call_idx *does*..
|
||||||
|
('call_idx 1', 'call_idx(1)'),
|
||||||
|
('len', 'len'), # only at 2 does it auto-call on single args
|
||||||
|
])
|
||||||
|
ip.magic('autocall 2')
|
||||||
|
run([
|
||||||
|
('len "abc"', 'len("abc")'),
|
||||||
|
('len "abc";', 'len("abc");'),
|
||||||
|
('len [1,2]', 'len([1,2])'),
|
||||||
|
('call_idx [1]', 'call_idx [1]'),
|
||||||
|
('call_idx 1', 'call_idx(1)'),
|
||||||
|
# This is what's different:
|
||||||
|
('len', 'len()'), # only at 2 does it auto-call on single args
|
||||||
|
])
|
||||||
|
ip.magic('autocall 1')
|
||||||
|
|
||||||
|
nt.assert_equal(failures, [])
|
@ -0,0 +1,210 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
"""Tests for the yap_ipython tab-completion machinery.
|
||||||
|
"""
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Module imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# stdlib
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# third party
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
# our own packages
|
||||||
|
from traitlets.config.loader import Config
|
||||||
|
from yap_ipython.utils.tempdir import TemporaryDirectory
|
||||||
|
from yap_ipython.core.history import HistoryManager, extract_hist_ranges
|
||||||
|
|
||||||
|
def setUp():
|
||||||
|
nt.assert_equal(sys.getdefaultencoding(), "utf-8")
|
||||||
|
|
||||||
|
def test_history():
|
||||||
|
ip = get_ipython()
|
||||||
|
with TemporaryDirectory() as tmpdir:
|
||||||
|
hist_manager_ori = ip.history_manager
|
||||||
|
hist_file = os.path.join(tmpdir, 'history.sqlite')
|
||||||
|
try:
|
||||||
|
ip.history_manager = HistoryManager(shell=ip, hist_file=hist_file)
|
||||||
|
hist = [u'a=1', u'def f():\n test = 1\n return test', u"b='€Æ¾÷ß'"]
|
||||||
|
for i, h in enumerate(hist, start=1):
|
||||||
|
ip.history_manager.store_inputs(i, h)
|
||||||
|
|
||||||
|
ip.history_manager.db_log_output = True
|
||||||
|
# Doesn't match the input, but we'll just check it's stored.
|
||||||
|
ip.history_manager.output_hist_reprs[3] = "spam"
|
||||||
|
ip.history_manager.store_output(3)
|
||||||
|
|
||||||
|
nt.assert_equal(ip.history_manager.input_hist_raw, [''] + hist)
|
||||||
|
|
||||||
|
# Detailed tests for _get_range_session
|
||||||
|
grs = ip.history_manager._get_range_session
|
||||||
|
nt.assert_equal(list(grs(start=2,stop=-1)), list(zip([0], [2], hist[1:-1])))
|
||||||
|
nt.assert_equal(list(grs(start=-2)), list(zip([0,0], [2,3], hist[-2:])))
|
||||||
|
nt.assert_equal(list(grs(output=True)), list(zip([0,0,0], [1,2,3], zip(hist, [None,None,'spam']))))
|
||||||
|
|
||||||
|
# Check whether specifying a range beyond the end of the current
|
||||||
|
# session results in an error (gh-804)
|
||||||
|
ip.magic('%hist 2-500')
|
||||||
|
|
||||||
|
# Check that we can write non-ascii characters to a file
|
||||||
|
ip.magic("%%hist -f %s" % os.path.join(tmpdir, "test1"))
|
||||||
|
ip.magic("%%hist -pf %s" % os.path.join(tmpdir, "test2"))
|
||||||
|
ip.magic("%%hist -nf %s" % os.path.join(tmpdir, "test3"))
|
||||||
|
ip.magic("%%save %s 1-10" % os.path.join(tmpdir, "test4"))
|
||||||
|
|
||||||
|
# New session
|
||||||
|
ip.history_manager.reset()
|
||||||
|
newcmds = [u"z=5",
|
||||||
|
u"class X(object):\n pass",
|
||||||
|
u"k='p'",
|
||||||
|
u"z=5"]
|
||||||
|
for i, cmd in enumerate(newcmds, start=1):
|
||||||
|
ip.history_manager.store_inputs(i, cmd)
|
||||||
|
gothist = ip.history_manager.get_range(start=1, stop=4)
|
||||||
|
nt.assert_equal(list(gothist), list(zip([0,0,0],[1,2,3], newcmds)))
|
||||||
|
# Previous session:
|
||||||
|
gothist = ip.history_manager.get_range(-1, 1, 4)
|
||||||
|
nt.assert_equal(list(gothist), list(zip([1,1,1],[1,2,3], hist)))
|
||||||
|
|
||||||
|
newhist = [(2, i, c) for (i, c) in enumerate(newcmds, 1)]
|
||||||
|
|
||||||
|
# Check get_hist_tail
|
||||||
|
gothist = ip.history_manager.get_tail(5, output=True,
|
||||||
|
include_latest=True)
|
||||||
|
expected = [(1, 3, (hist[-1], "spam"))] \
|
||||||
|
+ [(s, n, (c, None)) for (s, n, c) in newhist]
|
||||||
|
nt.assert_equal(list(gothist), expected)
|
||||||
|
|
||||||
|
gothist = ip.history_manager.get_tail(2)
|
||||||
|
expected = newhist[-3:-1]
|
||||||
|
nt.assert_equal(list(gothist), expected)
|
||||||
|
|
||||||
|
# Check get_hist_search
|
||||||
|
gothist = ip.history_manager.search("*test*")
|
||||||
|
nt.assert_equal(list(gothist), [(1,2,hist[1])] )
|
||||||
|
|
||||||
|
gothist = ip.history_manager.search("*=*")
|
||||||
|
nt.assert_equal(list(gothist),
|
||||||
|
[(1, 1, hist[0]),
|
||||||
|
(1, 2, hist[1]),
|
||||||
|
(1, 3, hist[2]),
|
||||||
|
newhist[0],
|
||||||
|
newhist[2],
|
||||||
|
newhist[3]])
|
||||||
|
|
||||||
|
gothist = ip.history_manager.search("*=*", n=4)
|
||||||
|
nt.assert_equal(list(gothist),
|
||||||
|
[(1, 3, hist[2]),
|
||||||
|
newhist[0],
|
||||||
|
newhist[2],
|
||||||
|
newhist[3]])
|
||||||
|
|
||||||
|
gothist = ip.history_manager.search("*=*", unique=True)
|
||||||
|
nt.assert_equal(list(gothist),
|
||||||
|
[(1, 1, hist[0]),
|
||||||
|
(1, 2, hist[1]),
|
||||||
|
(1, 3, hist[2]),
|
||||||
|
newhist[2],
|
||||||
|
newhist[3]])
|
||||||
|
|
||||||
|
gothist = ip.history_manager.search("*=*", unique=True, n=3)
|
||||||
|
nt.assert_equal(list(gothist),
|
||||||
|
[(1, 3, hist[2]),
|
||||||
|
newhist[2],
|
||||||
|
newhist[3]])
|
||||||
|
|
||||||
|
gothist = ip.history_manager.search("b*", output=True)
|
||||||
|
nt.assert_equal(list(gothist), [(1,3,(hist[2],"spam"))] )
|
||||||
|
|
||||||
|
# Cross testing: check that magic %save can get previous session.
|
||||||
|
testfilename = os.path.realpath(os.path.join(tmpdir, "test.py"))
|
||||||
|
ip.magic("save " + testfilename + " ~1/1-3")
|
||||||
|
with io.open(testfilename, encoding='utf-8') as testfile:
|
||||||
|
nt.assert_equal(testfile.read(),
|
||||||
|
u"# coding: utf-8\n" + u"\n".join(hist)+u"\n")
|
||||||
|
|
||||||
|
# Duplicate line numbers - check that it doesn't crash, and
|
||||||
|
# gets a new session
|
||||||
|
ip.history_manager.store_inputs(1, "rogue")
|
||||||
|
ip.history_manager.writeout_cache()
|
||||||
|
nt.assert_equal(ip.history_manager.session_number, 3)
|
||||||
|
finally:
|
||||||
|
# Ensure saving thread is shut down before we try to clean up the files
|
||||||
|
ip.history_manager.save_thread.stop()
|
||||||
|
# Forcibly close database rather than relying on garbage collection
|
||||||
|
ip.history_manager.db.close()
|
||||||
|
# Restore history manager
|
||||||
|
ip.history_manager = hist_manager_ori
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_hist_ranges():
|
||||||
|
instr = "1 2/3 ~4/5-6 ~4/7-~4/9 ~9/2-~7/5 ~10/"
|
||||||
|
expected = [(0, 1, 2), # 0 == current session
|
||||||
|
(2, 3, 4),
|
||||||
|
(-4, 5, 7),
|
||||||
|
(-4, 7, 10),
|
||||||
|
(-9, 2, None), # None == to end
|
||||||
|
(-8, 1, None),
|
||||||
|
(-7, 1, 6),
|
||||||
|
(-10, 1, None)]
|
||||||
|
actual = list(extract_hist_ranges(instr))
|
||||||
|
nt.assert_equal(actual, expected)
|
||||||
|
|
||||||
|
def test_magic_rerun():
|
||||||
|
"""Simple test for %rerun (no args -> rerun last line)"""
|
||||||
|
ip = get_ipython()
|
||||||
|
ip.run_cell("a = 10", store_history=True)
|
||||||
|
ip.run_cell("a += 1", store_history=True)
|
||||||
|
nt.assert_equal(ip.user_ns["a"], 11)
|
||||||
|
ip.run_cell("%rerun", store_history=True)
|
||||||
|
nt.assert_equal(ip.user_ns["a"], 12)
|
||||||
|
|
||||||
|
def test_timestamp_type():
|
||||||
|
ip = get_ipython()
|
||||||
|
info = ip.history_manager.get_session_info()
|
||||||
|
nt.assert_true(isinstance(info[1], datetime))
|
||||||
|
|
||||||
|
def test_hist_file_config():
|
||||||
|
cfg = Config()
|
||||||
|
tfile = tempfile.NamedTemporaryFile(delete=False)
|
||||||
|
cfg.HistoryManager.hist_file = tfile.name
|
||||||
|
try:
|
||||||
|
hm = HistoryManager(shell=get_ipython(), config=cfg)
|
||||||
|
nt.assert_equal(hm.hist_file, cfg.HistoryManager.hist_file)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
os.remove(tfile.name)
|
||||||
|
except OSError:
|
||||||
|
# same catch as in testing.tools.TempFileMixin
|
||||||
|
# On Windows, even though we close the file, we still can't
|
||||||
|
# delete it. I have no clue why
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_histmanager_disabled():
|
||||||
|
"""Ensure that disabling the history manager doesn't create a database."""
|
||||||
|
cfg = Config()
|
||||||
|
cfg.HistoryAccessor.enabled = False
|
||||||
|
|
||||||
|
ip = get_ipython()
|
||||||
|
with TemporaryDirectory() as tmpdir:
|
||||||
|
hist_manager_ori = ip.history_manager
|
||||||
|
hist_file = os.path.join(tmpdir, 'history.sqlite')
|
||||||
|
cfg.HistoryManager.hist_file = hist_file
|
||||||
|
try:
|
||||||
|
ip.history_manager = HistoryManager(shell=ip, config=cfg)
|
||||||
|
hist = [u'a=1', u'def f():\n test = 1\n return test', u"b='€Æ¾÷ß'"]
|
||||||
|
for i, h in enumerate(hist, start=1):
|
||||||
|
ip.history_manager.store_inputs(i, h)
|
||||||
|
nt.assert_equal(ip.history_manager.input_hist_raw, [''] + hist)
|
||||||
|
ip.history_manager.reset()
|
||||||
|
ip.history_manager.end_session()
|
||||||
|
finally:
|
||||||
|
ip.history_manager = hist_manager_ori
|
||||||
|
|
||||||
|
# hist_file should not be created
|
||||||
|
nt.assert_false(os.path.exists(hist_file))
|
@ -0,0 +1,80 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Tests for CommandChainDispatcher."""
|
||||||
|
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
from yap_ipython.core.error import TryNext
|
||||||
|
from yap_ipython.core.hooks import CommandChainDispatcher
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Local utilities
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Define two classes, one which succeeds and one which raises TryNext. Each
|
||||||
|
# sets the attribute `called` to True when it is called.
|
||||||
|
class Okay(object):
|
||||||
|
def __init__(self, message):
|
||||||
|
self.message = message
|
||||||
|
self.called = False
|
||||||
|
def __call__(self):
|
||||||
|
self.called = True
|
||||||
|
return self.message
|
||||||
|
|
||||||
|
class Fail(object):
|
||||||
|
def __init__(self, message):
|
||||||
|
self.message = message
|
||||||
|
self.called = False
|
||||||
|
def __call__(self):
|
||||||
|
self.called = True
|
||||||
|
raise TryNext(self.message)
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Test functions
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_command_chain_dispatcher_ff():
|
||||||
|
"""Test two failing hooks"""
|
||||||
|
fail1 = Fail(u'fail1')
|
||||||
|
fail2 = Fail(u'fail2')
|
||||||
|
dp = CommandChainDispatcher([(0, fail1),
|
||||||
|
(10, fail2)])
|
||||||
|
|
||||||
|
try:
|
||||||
|
dp()
|
||||||
|
except TryNext as e:
|
||||||
|
nt.assert_equal(str(e), u'fail2')
|
||||||
|
else:
|
||||||
|
assert False, "Expected exception was not raised."
|
||||||
|
|
||||||
|
nt.assert_true(fail1.called)
|
||||||
|
nt.assert_true(fail2.called)
|
||||||
|
|
||||||
|
def test_command_chain_dispatcher_fofo():
|
||||||
|
"""Test a mixture of failing and succeeding hooks."""
|
||||||
|
fail1 = Fail(u'fail1')
|
||||||
|
fail2 = Fail(u'fail2')
|
||||||
|
okay1 = Okay(u'okay1')
|
||||||
|
okay2 = Okay(u'okay2')
|
||||||
|
|
||||||
|
dp = CommandChainDispatcher([(0, fail1),
|
||||||
|
# (5, okay1), # add this later
|
||||||
|
(10, fail2),
|
||||||
|
(15, okay2)])
|
||||||
|
dp.add(okay1, 5)
|
||||||
|
|
||||||
|
nt.assert_equal(dp(), u'okay1')
|
||||||
|
|
||||||
|
nt.assert_true(fail1.called)
|
||||||
|
nt.assert_true(okay1.called)
|
||||||
|
nt.assert_false(fail2.called)
|
||||||
|
nt.assert_false(okay2.called)
|
||||||
|
|
||||||
|
def test_command_chain_dispatcher_eq_priority():
|
||||||
|
okay1 = Okay(u'okay1')
|
||||||
|
okay2 = Okay(u'okay2')
|
||||||
|
dp = CommandChainDispatcher([(1, okay1)])
|
||||||
|
dp.add(okay2, 1)
|
@ -0,0 +1,52 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
def test_import_completer():
|
||||||
|
from yap_ipython.core import completer
|
||||||
|
|
||||||
|
def test_import_crashhandler():
|
||||||
|
from yap_ipython.core import crashhandler
|
||||||
|
|
||||||
|
def test_import_debugger():
|
||||||
|
from yap_ipython.core import debugger
|
||||||
|
|
||||||
|
def test_import_excolors():
|
||||||
|
from yap_ipython.core import excolors
|
||||||
|
|
||||||
|
def test_import_history():
|
||||||
|
from yap_ipython.core import history
|
||||||
|
|
||||||
|
def test_import_hooks():
|
||||||
|
from yap_ipython.core import hooks
|
||||||
|
|
||||||
|
def test_import_getipython():
|
||||||
|
from yap_ipython.core import getipython
|
||||||
|
|
||||||
|
def test_import_interactiveshell():
|
||||||
|
from yap_ipython.core import interactiveshell
|
||||||
|
|
||||||
|
def test_import_logger():
|
||||||
|
from yap_ipython.core import logger
|
||||||
|
|
||||||
|
def test_import_macro():
|
||||||
|
from yap_ipython.core import macro
|
||||||
|
|
||||||
|
def test_import_magic():
|
||||||
|
from yap_ipython.core import magic
|
||||||
|
|
||||||
|
def test_import_oinspect():
|
||||||
|
from yap_ipython.core import oinspect
|
||||||
|
|
||||||
|
def test_import_prefilter():
|
||||||
|
from yap_ipython.core import prefilter
|
||||||
|
|
||||||
|
def test_import_prompts():
|
||||||
|
from yap_ipython.core import prompts
|
||||||
|
|
||||||
|
def test_import_release():
|
||||||
|
from yap_ipython.core import release
|
||||||
|
|
||||||
|
def test_import_ultratb():
|
||||||
|
from yap_ipython.core import ultratb
|
||||||
|
|
||||||
|
def test_import_usage():
|
||||||
|
from yap_ipython.core import usage
|
@ -0,0 +1,641 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Tests for the inputsplitter module."""
|
||||||
|
|
||||||
|
|
||||||
|
# Copyright (c) yap_ipython Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython.core import inputsplitter as isp
|
||||||
|
from yap_ipython.core.inputtransformer import InputTransformer
|
||||||
|
from yap_ipython.core.tests.test_inputtransformer import syntax, syntax_ml
|
||||||
|
from yap_ipython.testing import tools as tt
|
||||||
|
from yap_ipython.utils import py3compat
|
||||||
|
from yap_ipython.utils.py3compat import input
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Semi-complete examples (also used as tests)
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Note: at the bottom, there's a slightly more complete version of this that
|
||||||
|
# can be useful during development of code here.
|
||||||
|
|
||||||
|
def mini_interactive_loop(input_func):
|
||||||
|
"""Minimal example of the logic of an interactive interpreter loop.
|
||||||
|
|
||||||
|
This serves as an example, and it is used by the test system with a fake
|
||||||
|
raw_input that simulates interactive input."""
|
||||||
|
|
||||||
|
from yap_ipython.core.inputsplitter import InputSplitter
|
||||||
|
|
||||||
|
isp = InputSplitter()
|
||||||
|
# In practice, this input loop would be wrapped in an outside loop to read
|
||||||
|
# input indefinitely, until some exit/quit command was issued. Here we
|
||||||
|
# only illustrate the basic inner loop.
|
||||||
|
while isp.push_accepts_more():
|
||||||
|
indent = ' '*isp.get_indent_spaces()
|
||||||
|
prompt = '>>> ' + indent
|
||||||
|
line = indent + input_func(prompt)
|
||||||
|
isp.push(line)
|
||||||
|
|
||||||
|
# Here we just return input so we can use it in a test suite, but a real
|
||||||
|
# interpreter would instead send it for execution somewhere.
|
||||||
|
src = isp.source_reset()
|
||||||
|
#print 'Input source was:\n', src # dbg
|
||||||
|
return src
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Test utilities, just for local use
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def assemble(block):
|
||||||
|
"""Assemble a block into multi-line sub-blocks."""
|
||||||
|
return ['\n'.join(sub_block)+'\n' for sub_block in block]
|
||||||
|
|
||||||
|
|
||||||
|
def pseudo_input(lines):
|
||||||
|
"""Return a function that acts like raw_input but feeds the input list."""
|
||||||
|
ilines = iter(lines)
|
||||||
|
def raw_in(prompt):
|
||||||
|
try:
|
||||||
|
return next(ilines)
|
||||||
|
except StopIteration:
|
||||||
|
return ''
|
||||||
|
return raw_in
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Tests
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
def test_spaces():
|
||||||
|
tests = [('', 0),
|
||||||
|
(' ', 1),
|
||||||
|
('\n', 0),
|
||||||
|
(' \n', 1),
|
||||||
|
('x', 0),
|
||||||
|
(' x', 1),
|
||||||
|
(' x',2),
|
||||||
|
(' x',4),
|
||||||
|
# Note: tabs are counted as a single whitespace!
|
||||||
|
('\tx', 1),
|
||||||
|
('\t x', 2),
|
||||||
|
]
|
||||||
|
tt.check_pairs(isp.num_ini_spaces, tests)
|
||||||
|
|
||||||
|
|
||||||
|
def test_remove_comments():
|
||||||
|
tests = [('text', 'text'),
|
||||||
|
('text # comment', 'text '),
|
||||||
|
('text # comment\n', 'text \n'),
|
||||||
|
('text # comment \n', 'text \n'),
|
||||||
|
('line # c \nline\n','line \nline\n'),
|
||||||
|
('line # c \nline#c2 \nline\nline #c\n\n',
|
||||||
|
'line \nline\nline\nline \n\n'),
|
||||||
|
]
|
||||||
|
tt.check_pairs(isp.remove_comments, tests)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_input_encoding():
|
||||||
|
encoding = isp.get_input_encoding()
|
||||||
|
nt.assert_true(isinstance(encoding, str))
|
||||||
|
# simple-minded check that at least encoding a simple string works with the
|
||||||
|
# encoding we got.
|
||||||
|
nt.assert_equal(u'test'.encode(encoding), b'test')
|
||||||
|
|
||||||
|
|
||||||
|
class NoInputEncodingTestCase(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.old_stdin = sys.stdin
|
||||||
|
class X: pass
|
||||||
|
fake_stdin = X()
|
||||||
|
sys.stdin = fake_stdin
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
# Verify that if sys.stdin has no 'encoding' attribute we do the right
|
||||||
|
# thing
|
||||||
|
enc = isp.get_input_encoding()
|
||||||
|
self.assertEqual(enc, 'ascii')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
sys.stdin = self.old_stdin
|
||||||
|
|
||||||
|
|
||||||
|
class InputSplitterTestCase(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.isp = isp.InputSplitter()
|
||||||
|
|
||||||
|
def test_reset(self):
|
||||||
|
isp = self.isp
|
||||||
|
isp.push('x=1')
|
||||||
|
isp.reset()
|
||||||
|
self.assertEqual(isp._buffer, [])
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 0)
|
||||||
|
self.assertEqual(isp.source, '')
|
||||||
|
self.assertEqual(isp.code, None)
|
||||||
|
self.assertEqual(isp._is_complete, False)
|
||||||
|
|
||||||
|
def test_source(self):
|
||||||
|
self.isp._store('1')
|
||||||
|
self.isp._store('2')
|
||||||
|
self.assertEqual(self.isp.source, '1\n2\n')
|
||||||
|
self.assertEqual(len(self.isp._buffer)>0, True)
|
||||||
|
self.assertEqual(self.isp.source_reset(), '1\n2\n')
|
||||||
|
self.assertEqual(self.isp._buffer, [])
|
||||||
|
self.assertEqual(self.isp.source, '')
|
||||||
|
|
||||||
|
def test_indent(self):
|
||||||
|
isp = self.isp # shorthand
|
||||||
|
isp.push('x=1')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 0)
|
||||||
|
isp.push('if 1:\n x=1')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 4)
|
||||||
|
isp.push('y=2\n')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 0)
|
||||||
|
|
||||||
|
def test_indent2(self):
|
||||||
|
isp = self.isp
|
||||||
|
isp.push('if 1:')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 4)
|
||||||
|
isp.push(' x=1')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 4)
|
||||||
|
# Blank lines shouldn't change the indent level
|
||||||
|
isp.push(' '*2)
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 4)
|
||||||
|
|
||||||
|
def test_indent3(self):
|
||||||
|
isp = self.isp
|
||||||
|
# When a multiline statement contains parens or multiline strings, we
|
||||||
|
# shouldn't get confused.
|
||||||
|
isp.push("if 1:")
|
||||||
|
isp.push(" x = (1+\n 2)")
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 4)
|
||||||
|
|
||||||
|
def test_indent4(self):
|
||||||
|
isp = self.isp
|
||||||
|
# whitespace after ':' should not screw up indent level
|
||||||
|
isp.push('if 1: \n x=1')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 4)
|
||||||
|
isp.push('y=2\n')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 0)
|
||||||
|
isp.push('if 1:\t\n x=1')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 4)
|
||||||
|
isp.push('y=2\n')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 0)
|
||||||
|
|
||||||
|
def test_dedent_pass(self):
|
||||||
|
isp = self.isp # shorthand
|
||||||
|
# should NOT cause dedent
|
||||||
|
isp.push('if 1:\n passes = 5')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 4)
|
||||||
|
isp.push('if 1:\n pass')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 0)
|
||||||
|
isp.push('if 1:\n pass ')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 0)
|
||||||
|
|
||||||
|
def test_dedent_break(self):
|
||||||
|
isp = self.isp # shorthand
|
||||||
|
# should NOT cause dedent
|
||||||
|
isp.push('while 1:\n breaks = 5')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 4)
|
||||||
|
isp.push('while 1:\n break')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 0)
|
||||||
|
isp.push('while 1:\n break ')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 0)
|
||||||
|
|
||||||
|
def test_dedent_continue(self):
|
||||||
|
isp = self.isp # shorthand
|
||||||
|
# should NOT cause dedent
|
||||||
|
isp.push('while 1:\n continues = 5')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 4)
|
||||||
|
isp.push('while 1:\n continue')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 0)
|
||||||
|
isp.push('while 1:\n continue ')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 0)
|
||||||
|
|
||||||
|
def test_dedent_raise(self):
|
||||||
|
isp = self.isp # shorthand
|
||||||
|
# should NOT cause dedent
|
||||||
|
isp.push('if 1:\n raised = 4')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 4)
|
||||||
|
isp.push('if 1:\n raise TypeError()')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 0)
|
||||||
|
isp.push('if 1:\n raise')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 0)
|
||||||
|
isp.push('if 1:\n raise ')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 0)
|
||||||
|
|
||||||
|
def test_dedent_return(self):
|
||||||
|
isp = self.isp # shorthand
|
||||||
|
# should NOT cause dedent
|
||||||
|
isp.push('if 1:\n returning = 4')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 4)
|
||||||
|
isp.push('if 1:\n return 5 + 493')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 0)
|
||||||
|
isp.push('if 1:\n return')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 0)
|
||||||
|
isp.push('if 1:\n return ')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 0)
|
||||||
|
isp.push('if 1:\n return(0)')
|
||||||
|
self.assertEqual(isp.get_indent_spaces(), 0)
|
||||||
|
|
||||||
|
def test_push(self):
|
||||||
|
isp = self.isp
|
||||||
|
self.assertEqual(isp.push('x=1'), True)
|
||||||
|
|
||||||
|
def test_push2(self):
|
||||||
|
isp = self.isp
|
||||||
|
self.assertEqual(isp.push('if 1:'), False)
|
||||||
|
for line in [' x=1', '# a comment', ' y=2']:
|
||||||
|
print(line)
|
||||||
|
self.assertEqual(isp.push(line), True)
|
||||||
|
|
||||||
|
def test_push3(self):
|
||||||
|
isp = self.isp
|
||||||
|
isp.push('if True:')
|
||||||
|
isp.push(' a = 1')
|
||||||
|
self.assertEqual(isp.push('b = [1,'), False)
|
||||||
|
|
||||||
|
def test_push_accepts_more(self):
|
||||||
|
isp = self.isp
|
||||||
|
isp.push('x=1')
|
||||||
|
self.assertEqual(isp.push_accepts_more(), False)
|
||||||
|
|
||||||
|
def test_push_accepts_more2(self):
|
||||||
|
isp = self.isp
|
||||||
|
isp.push('if 1:')
|
||||||
|
self.assertEqual(isp.push_accepts_more(), True)
|
||||||
|
isp.push(' x=1')
|
||||||
|
self.assertEqual(isp.push_accepts_more(), True)
|
||||||
|
isp.push('')
|
||||||
|
self.assertEqual(isp.push_accepts_more(), False)
|
||||||
|
|
||||||
|
def test_push_accepts_more3(self):
|
||||||
|
isp = self.isp
|
||||||
|
isp.push("x = (2+\n3)")
|
||||||
|
self.assertEqual(isp.push_accepts_more(), False)
|
||||||
|
|
||||||
|
def test_push_accepts_more4(self):
|
||||||
|
isp = self.isp
|
||||||
|
# When a multiline statement contains parens or multiline strings, we
|
||||||
|
# shouldn't get confused.
|
||||||
|
# FIXME: we should be able to better handle de-dents in statements like
|
||||||
|
# multiline strings and multiline expressions (continued with \ or
|
||||||
|
# parens). Right now we aren't handling the indentation tracking quite
|
||||||
|
# correctly with this, though in practice it may not be too much of a
|
||||||
|
# problem. We'll need to see.
|
||||||
|
isp.push("if 1:")
|
||||||
|
isp.push(" x = (2+")
|
||||||
|
isp.push(" 3)")
|
||||||
|
self.assertEqual(isp.push_accepts_more(), True)
|
||||||
|
isp.push(" y = 3")
|
||||||
|
self.assertEqual(isp.push_accepts_more(), True)
|
||||||
|
isp.push('')
|
||||||
|
self.assertEqual(isp.push_accepts_more(), False)
|
||||||
|
|
||||||
|
def test_push_accepts_more5(self):
|
||||||
|
isp = self.isp
|
||||||
|
isp.push('try:')
|
||||||
|
isp.push(' a = 5')
|
||||||
|
isp.push('except:')
|
||||||
|
isp.push(' raise')
|
||||||
|
# We want to be able to add an else: block at this point, so it should
|
||||||
|
# wait for a blank line.
|
||||||
|
self.assertEqual(isp.push_accepts_more(), True)
|
||||||
|
|
||||||
|
def test_continuation(self):
|
||||||
|
isp = self.isp
|
||||||
|
isp.push("import os, \\")
|
||||||
|
self.assertEqual(isp.push_accepts_more(), True)
|
||||||
|
isp.push("sys")
|
||||||
|
self.assertEqual(isp.push_accepts_more(), False)
|
||||||
|
|
||||||
|
def test_syntax_error(self):
|
||||||
|
isp = self.isp
|
||||||
|
# Syntax errors immediately produce a 'ready' block, so the invalid
|
||||||
|
# Python can be sent to the kernel for evaluation with possible ipython
|
||||||
|
# special-syntax conversion.
|
||||||
|
isp.push('run foo')
|
||||||
|
self.assertEqual(isp.push_accepts_more(), False)
|
||||||
|
|
||||||
|
def test_unicode(self):
|
||||||
|
self.isp.push(u"Pérez")
|
||||||
|
self.isp.push(u'\xc3\xa9')
|
||||||
|
self.isp.push(u"u'\xc3\xa9'")
|
||||||
|
|
||||||
|
def test_line_continuation(self):
|
||||||
|
""" Test issue #2108."""
|
||||||
|
isp = self.isp
|
||||||
|
# A blank line after a line continuation should not accept more
|
||||||
|
isp.push("1 \\\n\n")
|
||||||
|
self.assertEqual(isp.push_accepts_more(), False)
|
||||||
|
# Whitespace after a \ is a SyntaxError. The only way to test that
|
||||||
|
# here is to test that push doesn't accept more (as with
|
||||||
|
# test_syntax_error() above).
|
||||||
|
isp.push(r"1 \ ")
|
||||||
|
self.assertEqual(isp.push_accepts_more(), False)
|
||||||
|
# Even if the line is continuable (c.f. the regular Python
|
||||||
|
# interpreter)
|
||||||
|
isp.push(r"(1 \ ")
|
||||||
|
self.assertEqual(isp.push_accepts_more(), False)
|
||||||
|
|
||||||
|
def test_check_complete(self):
|
||||||
|
isp = self.isp
|
||||||
|
self.assertEqual(isp.check_complete("a = 1"), ('complete', None))
|
||||||
|
self.assertEqual(isp.check_complete("for a in range(5):"), ('incomplete', 4))
|
||||||
|
self.assertEqual(isp.check_complete("raise = 2"), ('invalid', None))
|
||||||
|
self.assertEqual(isp.check_complete("a = [1,\n2,"), ('incomplete', 0))
|
||||||
|
self.assertEqual(isp.check_complete("def a():\n x=1\n global x"), ('invalid', None))
|
||||||
|
|
||||||
|
class InteractiveLoopTestCase(unittest.TestCase):
|
||||||
|
"""Tests for an interactive loop like a python shell.
|
||||||
|
"""
|
||||||
|
def check_ns(self, lines, ns):
|
||||||
|
"""Validate that the given input lines produce the resulting namespace.
|
||||||
|
|
||||||
|
Note: the input lines are given exactly as they would be typed in an
|
||||||
|
auto-indenting environment, as mini_interactive_loop above already does
|
||||||
|
auto-indenting and prepends spaces to the input.
|
||||||
|
"""
|
||||||
|
src = mini_interactive_loop(pseudo_input(lines))
|
||||||
|
test_ns = {}
|
||||||
|
exec(src, test_ns)
|
||||||
|
# We can't check that the provided ns is identical to the test_ns,
|
||||||
|
# because Python fills test_ns with extra keys (copyright, etc). But
|
||||||
|
# we can check that the given dict is *contained* in test_ns
|
||||||
|
for k,v in ns.items():
|
||||||
|
self.assertEqual(test_ns[k], v)
|
||||||
|
|
||||||
|
def test_simple(self):
|
||||||
|
self.check_ns(['x=1'], dict(x=1))
|
||||||
|
|
||||||
|
def test_simple2(self):
|
||||||
|
self.check_ns(['if 1:', 'x=2'], dict(x=2))
|
||||||
|
|
||||||
|
def test_xy(self):
|
||||||
|
self.check_ns(['x=1; y=2'], dict(x=1, y=2))
|
||||||
|
|
||||||
|
def test_abc(self):
|
||||||
|
self.check_ns(['if 1:','a=1','b=2','c=3'], dict(a=1, b=2, c=3))
|
||||||
|
|
||||||
|
def test_multi(self):
|
||||||
|
self.check_ns(['x =(1+','1+','2)'], dict(x=4))
|
||||||
|
|
||||||
|
|
||||||
|
class IPythonInputTestCase(InputSplitterTestCase):
|
||||||
|
"""By just creating a new class whose .isp is a different instance, we
|
||||||
|
re-run the same test battery on the new input splitter.
|
||||||
|
|
||||||
|
In addition, this runs the tests over the syntax and syntax_ml dicts that
|
||||||
|
were tested by individual functions, as part of the OO interface.
|
||||||
|
|
||||||
|
It also makes some checks on the raw buffer storage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.isp = isp.IPythonInputSplitter()
|
||||||
|
|
||||||
|
def test_syntax(self):
|
||||||
|
"""Call all single-line syntax tests from the main object"""
|
||||||
|
isp = self.isp
|
||||||
|
for example in syntax.values():
|
||||||
|
for raw, out_t in example:
|
||||||
|
if raw.startswith(' '):
|
||||||
|
continue
|
||||||
|
|
||||||
|
isp.push(raw+'\n')
|
||||||
|
out_raw = isp.source_raw
|
||||||
|
out = isp.source_reset()
|
||||||
|
self.assertEqual(out.rstrip(), out_t,
|
||||||
|
tt.pair_fail_msg.format("inputsplitter",raw, out_t, out))
|
||||||
|
self.assertEqual(out_raw.rstrip(), raw.rstrip())
|
||||||
|
|
||||||
|
def test_syntax_multiline(self):
|
||||||
|
isp = self.isp
|
||||||
|
for example in syntax_ml.values():
|
||||||
|
for line_pairs in example:
|
||||||
|
out_t_parts = []
|
||||||
|
raw_parts = []
|
||||||
|
for lraw, out_t_part in line_pairs:
|
||||||
|
if out_t_part is not None:
|
||||||
|
out_t_parts.append(out_t_part)
|
||||||
|
|
||||||
|
if lraw is not None:
|
||||||
|
isp.push(lraw)
|
||||||
|
raw_parts.append(lraw)
|
||||||
|
|
||||||
|
out_raw = isp.source_raw
|
||||||
|
out = isp.source_reset()
|
||||||
|
out_t = '\n'.join(out_t_parts).rstrip()
|
||||||
|
raw = '\n'.join(raw_parts).rstrip()
|
||||||
|
self.assertEqual(out.rstrip(), out_t)
|
||||||
|
self.assertEqual(out_raw.rstrip(), raw)
|
||||||
|
|
||||||
|
def test_syntax_multiline_cell(self):
|
||||||
|
isp = self.isp
|
||||||
|
for example in syntax_ml.values():
|
||||||
|
|
||||||
|
out_t_parts = []
|
||||||
|
for line_pairs in example:
|
||||||
|
raw = '\n'.join(r for r, _ in line_pairs if r is not None)
|
||||||
|
out_t = '\n'.join(t for _,t in line_pairs if t is not None)
|
||||||
|
out = isp.transform_cell(raw)
|
||||||
|
# Match ignoring trailing whitespace
|
||||||
|
self.assertEqual(out.rstrip(), out_t.rstrip())
|
||||||
|
|
||||||
|
def test_cellmagic_preempt(self):
|
||||||
|
isp = self.isp
|
||||||
|
for raw, name, line, cell in [
|
||||||
|
("%%cellm a\nIn[1]:", u'cellm', u'a', u'In[1]:'),
|
||||||
|
("%%cellm \nline\n>>> hi", u'cellm', u'', u'line\n>>> hi'),
|
||||||
|
(">>> %%cellm \nline\n>>> hi", u'cellm', u'', u'line\nhi'),
|
||||||
|
("%%cellm \n>>> hi", u'cellm', u'', u'>>> hi'),
|
||||||
|
("%%cellm \nline1\nline2", u'cellm', u'', u'line1\nline2'),
|
||||||
|
("%%cellm \nline1\\\\\nline2", u'cellm', u'', u'line1\\\\\nline2'),
|
||||||
|
]:
|
||||||
|
expected = "get_ipython().run_cell_magic(%r, %r, %r)" % (
|
||||||
|
name, line, cell
|
||||||
|
)
|
||||||
|
out = isp.transform_cell(raw)
|
||||||
|
self.assertEqual(out.rstrip(), expected.rstrip())
|
||||||
|
|
||||||
|
def test_multiline_passthrough(self):
|
||||||
|
isp = self.isp
|
||||||
|
class CommentTransformer(InputTransformer):
|
||||||
|
def __init__(self):
|
||||||
|
self._lines = []
|
||||||
|
|
||||||
|
def push(self, line):
|
||||||
|
self._lines.append(line + '#')
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
text = '\n'.join(self._lines)
|
||||||
|
self._lines = []
|
||||||
|
return text
|
||||||
|
|
||||||
|
isp.physical_line_transforms.insert(0, CommentTransformer())
|
||||||
|
|
||||||
|
for raw, expected in [
|
||||||
|
("a=5", "a=5#"),
|
||||||
|
("%ls foo", "get_ipython().run_line_magic(%r, %r)" % (u'ls', u'foo#')),
|
||||||
|
("!ls foo\n%ls bar", "get_ipython().system(%r)\nget_ipython().run_line_magic(%r, %r)" % (
|
||||||
|
u'ls foo#', u'ls', u'bar#'
|
||||||
|
)),
|
||||||
|
("1\n2\n3\n%ls foo\n4\n5", "1#\n2#\n3#\nget_ipython().run_line_magic(%r, %r)\n4#\n5#" % (u'ls', u'foo#')),
|
||||||
|
]:
|
||||||
|
out = isp.transform_cell(raw)
|
||||||
|
self.assertEqual(out.rstrip(), expected.rstrip())
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Main - use as a script, mostly for developer experiments
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# A simple demo for interactive experimentation. This code will not get
|
||||||
|
# picked up by any test suite.
|
||||||
|
from yap_ipython.core.inputsplitter import IPythonInputSplitter
|
||||||
|
|
||||||
|
# configure here the syntax to use, prompt and whether to autoindent
|
||||||
|
#isp, start_prompt = InputSplitter(), '>>> '
|
||||||
|
isp, start_prompt = IPythonInputSplitter(), 'In> '
|
||||||
|
|
||||||
|
autoindent = True
|
||||||
|
#autoindent = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
prompt = start_prompt
|
||||||
|
while isp.push_accepts_more():
|
||||||
|
indent = ' '*isp.get_indent_spaces()
|
||||||
|
if autoindent:
|
||||||
|
line = indent + input(prompt+indent)
|
||||||
|
else:
|
||||||
|
line = input(prompt)
|
||||||
|
isp.push(line)
|
||||||
|
prompt = '... '
|
||||||
|
|
||||||
|
# Here we just return input so we can use it in a test suite, but a
|
||||||
|
# real interpreter would instead send it for execution somewhere.
|
||||||
|
#src = isp.source; raise EOFError # dbg
|
||||||
|
raw = isp.source_raw
|
||||||
|
src = isp.source_reset()
|
||||||
|
print('Input source was:\n', src)
|
||||||
|
print('Raw source was:\n', raw)
|
||||||
|
except EOFError:
|
||||||
|
print('Bye')
|
||||||
|
|
||||||
|
# Tests for cell magics support
|
||||||
|
|
||||||
|
def test_last_blank():
|
||||||
|
nt.assert_false(isp.last_blank(''))
|
||||||
|
nt.assert_false(isp.last_blank('abc'))
|
||||||
|
nt.assert_false(isp.last_blank('abc\n'))
|
||||||
|
nt.assert_false(isp.last_blank('abc\na'))
|
||||||
|
|
||||||
|
nt.assert_true(isp.last_blank('\n'))
|
||||||
|
nt.assert_true(isp.last_blank('\n '))
|
||||||
|
nt.assert_true(isp.last_blank('abc\n '))
|
||||||
|
nt.assert_true(isp.last_blank('abc\n\n'))
|
||||||
|
nt.assert_true(isp.last_blank('abc\nd\n\n'))
|
||||||
|
nt.assert_true(isp.last_blank('abc\nd\ne\n\n'))
|
||||||
|
nt.assert_true(isp.last_blank('abc \n \n \n\n'))
|
||||||
|
|
||||||
|
|
||||||
|
def test_last_two_blanks():
|
||||||
|
nt.assert_false(isp.last_two_blanks(''))
|
||||||
|
nt.assert_false(isp.last_two_blanks('abc'))
|
||||||
|
nt.assert_false(isp.last_two_blanks('abc\n'))
|
||||||
|
nt.assert_false(isp.last_two_blanks('abc\n\na'))
|
||||||
|
nt.assert_false(isp.last_two_blanks('abc\n \n'))
|
||||||
|
nt.assert_false(isp.last_two_blanks('abc\n\n'))
|
||||||
|
|
||||||
|
nt.assert_true(isp.last_two_blanks('\n\n'))
|
||||||
|
nt.assert_true(isp.last_two_blanks('\n\n '))
|
||||||
|
nt.assert_true(isp.last_two_blanks('\n \n'))
|
||||||
|
nt.assert_true(isp.last_two_blanks('abc\n\n '))
|
||||||
|
nt.assert_true(isp.last_two_blanks('abc\n\n\n'))
|
||||||
|
nt.assert_true(isp.last_two_blanks('abc\n\n \n'))
|
||||||
|
nt.assert_true(isp.last_two_blanks('abc\n\n \n '))
|
||||||
|
nt.assert_true(isp.last_two_blanks('abc\n\n \n \n'))
|
||||||
|
nt.assert_true(isp.last_two_blanks('abc\nd\n\n\n'))
|
||||||
|
nt.assert_true(isp.last_two_blanks('abc\nd\ne\nf\n\n\n'))
|
||||||
|
|
||||||
|
|
||||||
|
class CellMagicsCommon(object):
|
||||||
|
|
||||||
|
def test_whole_cell(self):
|
||||||
|
src = "%%cellm line\nbody\n"
|
||||||
|
out = self.sp.transform_cell(src)
|
||||||
|
ref = u"get_ipython().run_cell_magic('cellm', 'line', 'body')\n"
|
||||||
|
nt.assert_equal(out, py3compat.u_format(ref))
|
||||||
|
|
||||||
|
def test_cellmagic_help(self):
|
||||||
|
self.sp.push('%%cellm?')
|
||||||
|
nt.assert_false(self.sp.push_accepts_more())
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.sp.reset()
|
||||||
|
|
||||||
|
|
||||||
|
class CellModeCellMagics(CellMagicsCommon, unittest.TestCase):
|
||||||
|
sp = isp.IPythonInputSplitter(line_input_checker=False)
|
||||||
|
|
||||||
|
def test_incremental(self):
|
||||||
|
sp = self.sp
|
||||||
|
sp.push('%%cellm firstline\n')
|
||||||
|
nt.assert_true(sp.push_accepts_more()) #1
|
||||||
|
sp.push('line2\n')
|
||||||
|
nt.assert_true(sp.push_accepts_more()) #2
|
||||||
|
sp.push('\n')
|
||||||
|
# This should accept a blank line and carry on until the cell is reset
|
||||||
|
nt.assert_true(sp.push_accepts_more()) #3
|
||||||
|
|
||||||
|
def test_no_strip_coding(self):
|
||||||
|
src = '\n'.join([
|
||||||
|
'%%writefile foo.py',
|
||||||
|
'# coding: utf-8',
|
||||||
|
'print(u"üñîçø∂é")',
|
||||||
|
])
|
||||||
|
out = self.sp.transform_cell(src)
|
||||||
|
nt.assert_in('# coding: utf-8', out)
|
||||||
|
|
||||||
|
|
||||||
|
class LineModeCellMagics(CellMagicsCommon, unittest.TestCase):
|
||||||
|
sp = isp.IPythonInputSplitter(line_input_checker=True)
|
||||||
|
|
||||||
|
def test_incremental(self):
|
||||||
|
sp = self.sp
|
||||||
|
sp.push('%%cellm line2\n')
|
||||||
|
nt.assert_true(sp.push_accepts_more()) #1
|
||||||
|
sp.push('\n')
|
||||||
|
# In this case, a blank line should end the cell magic
|
||||||
|
nt.assert_false(sp.push_accepts_more()) #2
|
||||||
|
|
||||||
|
indentation_samples = [
|
||||||
|
('a = 1', 0),
|
||||||
|
('for a in b:', 4),
|
||||||
|
('def f():', 4),
|
||||||
|
('def f(): #comment', 4),
|
||||||
|
('a = ":#not a comment"', 0),
|
||||||
|
('def f():\n a = 1', 4),
|
||||||
|
('def f():\n return 1', 0),
|
||||||
|
('for a in b:\n'
|
||||||
|
' if a < 0:'
|
||||||
|
' continue', 3),
|
||||||
|
('a = {', 4),
|
||||||
|
('a = {\n'
|
||||||
|
' 1,', 5),
|
||||||
|
('b = """123', 0),
|
||||||
|
('', 0),
|
||||||
|
('def f():\n pass', 0),
|
||||||
|
('class Bar:\n def f():\n pass', 4),
|
||||||
|
('class Bar:\n def f():\n raise', 4),
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_find_next_indent():
|
||||||
|
for code, exp in indentation_samples:
|
||||||
|
res = isp.find_next_indent(code)
|
||||||
|
msg = "{!r} != {!r} (expected)\n Code: {!r}".format(res, exp, code)
|
||||||
|
assert res == exp, msg
|
@ -0,0 +1,494 @@
|
|||||||
|
import tokenize
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython.testing import tools as tt
|
||||||
|
from yap_ipython.utils import py3compat
|
||||||
|
u_fmt = py3compat.u_format
|
||||||
|
|
||||||
|
from yap_ipython.core import inputtransformer as ipt
|
||||||
|
|
||||||
|
def transform_and_reset(transformer):
|
||||||
|
transformer = transformer()
|
||||||
|
def transform(inp):
|
||||||
|
try:
|
||||||
|
return transformer.push(inp)
|
||||||
|
finally:
|
||||||
|
transformer.reset()
|
||||||
|
|
||||||
|
return transform
|
||||||
|
|
||||||
|
# Transformer tests
|
||||||
|
def transform_checker(tests, transformer, **kwargs):
|
||||||
|
"""Utility to loop over test inputs"""
|
||||||
|
transformer = transformer(**kwargs)
|
||||||
|
try:
|
||||||
|
for inp, tr in tests:
|
||||||
|
if inp is None:
|
||||||
|
out = transformer.reset()
|
||||||
|
else:
|
||||||
|
out = transformer.push(inp)
|
||||||
|
nt.assert_equal(out, tr)
|
||||||
|
finally:
|
||||||
|
transformer.reset()
|
||||||
|
|
||||||
|
# Data for all the syntax tests in the form of lists of pairs of
|
||||||
|
# raw/transformed input. We store it here as a global dict so that we can use
|
||||||
|
# it both within single-function tests and also to validate the behavior of the
|
||||||
|
# larger objects
|
||||||
|
|
||||||
|
syntax = \
|
||||||
|
dict(assign_system =
|
||||||
|
[(i,py3compat.u_format(o)) for i,o in \
|
||||||
|
[(u'a =! ls', "a = get_ipython().getoutput('ls')"),
|
||||||
|
(u'b = !ls', "b = get_ipython().getoutput('ls')"),
|
||||||
|
(u'c= !ls', "c = get_ipython().getoutput('ls')"),
|
||||||
|
(u'd == !ls', u'd == !ls'), # Invalid syntax, but we leave == alone.
|
||||||
|
('x=1', 'x=1'), # normal input is unmodified
|
||||||
|
(' ',' '), # blank lines are kept intact
|
||||||
|
# Tuple unpacking
|
||||||
|
(u"a, b = !echo 'a\\nb'", u"a, b = get_ipython().getoutput(\"echo 'a\\\\nb'\")"),
|
||||||
|
(u"a,= !echo 'a'", u"a, = get_ipython().getoutput(\"echo 'a'\")"),
|
||||||
|
(u"a, *bc = !echo 'a\\nb\\nc'", u"a, *bc = get_ipython().getoutput(\"echo 'a\\\\nb\\\\nc'\")"),
|
||||||
|
# Tuple unpacking with regular Python expressions, not our syntax.
|
||||||
|
(u"a, b = range(2)", u"a, b = range(2)"),
|
||||||
|
(u"a, = range(1)", u"a, = range(1)"),
|
||||||
|
(u"a, *bc = range(3)", u"a, *bc = range(3)"),
|
||||||
|
]],
|
||||||
|
|
||||||
|
assign_magic =
|
||||||
|
[(i,py3compat.u_format(o)) for i,o in \
|
||||||
|
[(u'a =% who', "a = get_ipython().run_line_magic('who', '')"),
|
||||||
|
(u'b = %who', "b = get_ipython().run_line_magic('who', '')"),
|
||||||
|
(u'c= %ls', "c = get_ipython().run_line_magic('ls', '')"),
|
||||||
|
(u'd == %ls', u'd == %ls'), # Invalid syntax, but we leave == alone.
|
||||||
|
('x=1', 'x=1'), # normal input is unmodified
|
||||||
|
(' ',' '), # blank lines are kept intact
|
||||||
|
(u"a, b = %foo", u"a, b = get_ipython().run_line_magic('foo', '')"),
|
||||||
|
]],
|
||||||
|
|
||||||
|
classic_prompt =
|
||||||
|
[('>>> x=1', 'x=1'),
|
||||||
|
('x=1', 'x=1'), # normal input is unmodified
|
||||||
|
(' ', ' '), # blank lines are kept intact
|
||||||
|
],
|
||||||
|
|
||||||
|
ipy_prompt =
|
||||||
|
[('In [1]: x=1', 'x=1'),
|
||||||
|
('x=1', 'x=1'), # normal input is unmodified
|
||||||
|
(' ',' '), # blank lines are kept intact
|
||||||
|
],
|
||||||
|
|
||||||
|
# Tests for the escape transformer to leave normal code alone
|
||||||
|
escaped_noesc =
|
||||||
|
[ (' ', ' '),
|
||||||
|
('x=1', 'x=1'),
|
||||||
|
],
|
||||||
|
|
||||||
|
# System calls
|
||||||
|
escaped_shell =
|
||||||
|
[(i,py3compat.u_format(o)) for i,o in \
|
||||||
|
[ (u'!ls', "get_ipython().system('ls')"),
|
||||||
|
# Double-escape shell, this means to capture the output of the
|
||||||
|
# subprocess and return it
|
||||||
|
(u'!!ls', "get_ipython().getoutput('ls')"),
|
||||||
|
]],
|
||||||
|
|
||||||
|
# Help/object info
|
||||||
|
escaped_help =
|
||||||
|
[(i,py3compat.u_format(o)) for i,o in \
|
||||||
|
[ (u'?', 'get_ipython().show_usage()'),
|
||||||
|
(u'?x1', "get_ipython().run_line_magic('pinfo', 'x1')"),
|
||||||
|
(u'??x2', "get_ipython().run_line_magic('pinfo2', 'x2')"),
|
||||||
|
(u'?a.*s', "get_ipython().run_line_magic('psearch', 'a.*s')"),
|
||||||
|
(u'?%hist1', "get_ipython().run_line_magic('pinfo', '%hist1')"),
|
||||||
|
(u'?%%hist2', "get_ipython().run_line_magic('pinfo', '%%hist2')"),
|
||||||
|
(u'?abc = qwe', "get_ipython().run_line_magic('pinfo', 'abc')"),
|
||||||
|
]],
|
||||||
|
|
||||||
|
end_help =
|
||||||
|
[(i,py3compat.u_format(o)) for i,o in \
|
||||||
|
[ (u'x3?', "get_ipython().run_line_magic('pinfo', 'x3')"),
|
||||||
|
(u'x4??', "get_ipython().run_line_magic('pinfo2', 'x4')"),
|
||||||
|
(u'%hist1?', "get_ipython().run_line_magic('pinfo', '%hist1')"),
|
||||||
|
(u'%hist2??', "get_ipython().run_line_magic('pinfo2', '%hist2')"),
|
||||||
|
(u'%%hist3?', "get_ipython().run_line_magic('pinfo', '%%hist3')"),
|
||||||
|
(u'%%hist4??', "get_ipython().run_line_magic('pinfo2', '%%hist4')"),
|
||||||
|
(u'f*?', "get_ipython().run_line_magic('psearch', 'f*')"),
|
||||||
|
(u'ax.*aspe*?', "get_ipython().run_line_magic('psearch', 'ax.*aspe*')"),
|
||||||
|
(u'a = abc?', "get_ipython().set_next_input('a = abc');"
|
||||||
|
"get_ipython().run_line_magic('pinfo', 'abc')"),
|
||||||
|
(u'a = abc.qe??', "get_ipython().set_next_input('a = abc.qe');"
|
||||||
|
"get_ipython().run_line_magic('pinfo2', 'abc.qe')"),
|
||||||
|
(u'a = *.items?', "get_ipython().set_next_input('a = *.items');"
|
||||||
|
"get_ipython().run_line_magic('psearch', '*.items')"),
|
||||||
|
(u'plot(a?', "get_ipython().set_next_input('plot(a');"
|
||||||
|
"get_ipython().run_line_magic('pinfo', 'a')"),
|
||||||
|
(u'a*2 #comment?', 'a*2 #comment?'),
|
||||||
|
]],
|
||||||
|
|
||||||
|
# Explicit magic calls
|
||||||
|
escaped_magic =
|
||||||
|
[(i,py3compat.u_format(o)) for i,o in \
|
||||||
|
[ (u'%cd', "get_ipython().run_line_magic('cd', '')"),
|
||||||
|
(u'%cd /home', "get_ipython().run_line_magic('cd', '/home')"),
|
||||||
|
# Backslashes need to be escaped.
|
||||||
|
(u'%cd C:\\User', "get_ipython().run_line_magic('cd', 'C:\\\\User')"),
|
||||||
|
(u' %magic', " get_ipython().run_line_magic('magic', '')"),
|
||||||
|
]],
|
||||||
|
|
||||||
|
# Quoting with separate arguments
|
||||||
|
escaped_quote =
|
||||||
|
[ (',f', 'f("")'),
|
||||||
|
(',f x', 'f("x")'),
|
||||||
|
(' ,f y', ' f("y")'),
|
||||||
|
(',f a b', 'f("a", "b")'),
|
||||||
|
],
|
||||||
|
|
||||||
|
# Quoting with single argument
|
||||||
|
escaped_quote2 =
|
||||||
|
[ (';f', 'f("")'),
|
||||||
|
(';f x', 'f("x")'),
|
||||||
|
(' ;f y', ' f("y")'),
|
||||||
|
(';f a b', 'f("a b")'),
|
||||||
|
],
|
||||||
|
|
||||||
|
# Simply apply parens
|
||||||
|
escaped_paren =
|
||||||
|
[ ('/f', 'f()'),
|
||||||
|
('/f x', 'f(x)'),
|
||||||
|
(' /f y', ' f(y)'),
|
||||||
|
('/f a b', 'f(a, b)'),
|
||||||
|
],
|
||||||
|
|
||||||
|
# Check that we transform prompts before other transforms
|
||||||
|
mixed =
|
||||||
|
[(i,py3compat.u_format(o)) for i,o in \
|
||||||
|
[ (u'In [1]: %lsmagic', "get_ipython().run_line_magic('lsmagic', '')"),
|
||||||
|
(u'>>> %lsmagic', "get_ipython().run_line_magic('lsmagic', '')"),
|
||||||
|
(u'In [2]: !ls', "get_ipython().system('ls')"),
|
||||||
|
(u'In [3]: abs?', "get_ipython().run_line_magic('pinfo', 'abs')"),
|
||||||
|
(u'In [4]: b = %who', "b = get_ipython().run_line_magic('who', '')"),
|
||||||
|
]],
|
||||||
|
)
|
||||||
|
|
||||||
|
# multiline syntax examples. Each of these should be a list of lists, with
|
||||||
|
# each entry itself having pairs of raw/transformed input. The union (with
|
||||||
|
# '\n'.join() of the transformed inputs is what the splitter should produce
|
||||||
|
# when fed the raw lines one at a time via push.
|
||||||
|
syntax_ml = \
|
||||||
|
dict(classic_prompt =
|
||||||
|
[ [('>>> for i in range(10):','for i in range(10):'),
|
||||||
|
('... print i',' print i'),
|
||||||
|
('... ', ''),
|
||||||
|
],
|
||||||
|
[('>>> a="""','a="""'),
|
||||||
|
('... 123"""','123"""'),
|
||||||
|
],
|
||||||
|
[('a="""','a="""'),
|
||||||
|
('... 123','123'),
|
||||||
|
('... 456"""','456"""'),
|
||||||
|
],
|
||||||
|
[('a="""','a="""'),
|
||||||
|
('>>> 123','123'),
|
||||||
|
('... 456"""','456"""'),
|
||||||
|
],
|
||||||
|
[('a="""','a="""'),
|
||||||
|
('123','123'),
|
||||||
|
('... 456"""','... 456"""'),
|
||||||
|
],
|
||||||
|
[('....__class__','....__class__'),
|
||||||
|
],
|
||||||
|
[('a=5', 'a=5'),
|
||||||
|
('...', ''),
|
||||||
|
],
|
||||||
|
[('>>> def f(x):', 'def f(x):'),
|
||||||
|
('...', ''),
|
||||||
|
('... return x', ' return x'),
|
||||||
|
],
|
||||||
|
[('board = """....', 'board = """....'),
|
||||||
|
('....', '....'),
|
||||||
|
('...."""', '...."""'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
ipy_prompt =
|
||||||
|
[ [('In [24]: for i in range(10):','for i in range(10):'),
|
||||||
|
(' ....: print i',' print i'),
|
||||||
|
(' ....: ', ''),
|
||||||
|
],
|
||||||
|
[('In [24]: for i in range(10):','for i in range(10):'),
|
||||||
|
# Qt console prompts expand with spaces, not dots
|
||||||
|
(' ...: print i',' print i'),
|
||||||
|
(' ...: ', ''),
|
||||||
|
],
|
||||||
|
[('In [24]: for i in range(10):','for i in range(10):'),
|
||||||
|
# Sometimes whitespace preceding '...' has been removed
|
||||||
|
('...: print i',' print i'),
|
||||||
|
('...: ', ''),
|
||||||
|
],
|
||||||
|
[('In [24]: for i in range(10):','for i in range(10):'),
|
||||||
|
# Space after last continuation prompt has been removed (issue #6674)
|
||||||
|
('...: print i',' print i'),
|
||||||
|
('...:', ''),
|
||||||
|
],
|
||||||
|
[('In [2]: a="""','a="""'),
|
||||||
|
(' ...: 123"""','123"""'),
|
||||||
|
],
|
||||||
|
[('a="""','a="""'),
|
||||||
|
(' ...: 123','123'),
|
||||||
|
(' ...: 456"""','456"""'),
|
||||||
|
],
|
||||||
|
[('a="""','a="""'),
|
||||||
|
('In [1]: 123','123'),
|
||||||
|
(' ...: 456"""','456"""'),
|
||||||
|
],
|
||||||
|
[('a="""','a="""'),
|
||||||
|
('123','123'),
|
||||||
|
(' ...: 456"""',' ...: 456"""'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
multiline_datastructure_prompt =
|
||||||
|
[ [('>>> a = [1,','a = [1,'),
|
||||||
|
('... 2]','2]'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
multiline_datastructure =
|
||||||
|
[ [('b = ("%s"', None),
|
||||||
|
('# comment', None),
|
||||||
|
('%foo )', 'b = ("%s"\n# comment\n%foo )'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
multiline_string =
|
||||||
|
[ [("'''foo?", None),
|
||||||
|
("bar'''", "'''foo?\nbar'''"),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
leading_indent =
|
||||||
|
[ [(' print "hi"','print "hi"'),
|
||||||
|
],
|
||||||
|
[(' for a in range(5):','for a in range(5):'),
|
||||||
|
(' a*2',' a*2'),
|
||||||
|
],
|
||||||
|
[(' a="""','a="""'),
|
||||||
|
(' 123"""','123"""'),
|
||||||
|
],
|
||||||
|
[('a="""','a="""'),
|
||||||
|
(' 123"""',' 123"""'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
cellmagic =
|
||||||
|
[ [(u'%%foo a', None),
|
||||||
|
(None, u_fmt("get_ipython().run_cell_magic('foo', 'a', '')")),
|
||||||
|
],
|
||||||
|
[(u'%%bar 123', None),
|
||||||
|
(u'hello', None),
|
||||||
|
(None , u_fmt("get_ipython().run_cell_magic('bar', '123', 'hello')")),
|
||||||
|
],
|
||||||
|
[(u'a=5', 'a=5'),
|
||||||
|
(u'%%cellmagic', '%%cellmagic'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
escaped =
|
||||||
|
[ [('%abc def \\', None),
|
||||||
|
('ghi', u_fmt("get_ipython().run_line_magic('abc', 'def ghi')")),
|
||||||
|
],
|
||||||
|
[('%abc def \\', None),
|
||||||
|
('ghi\\', None),
|
||||||
|
(None, u_fmt("get_ipython().run_line_magic('abc', 'def ghi')")),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
assign_magic =
|
||||||
|
[ [(u'a = %bc de \\', None),
|
||||||
|
(u'fg', u_fmt("a = get_ipython().run_line_magic('bc', 'de fg')")),
|
||||||
|
],
|
||||||
|
[(u'a = %bc de \\', None),
|
||||||
|
(u'fg\\', None),
|
||||||
|
(None, u_fmt("a = get_ipython().run_line_magic('bc', 'de fg')")),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
assign_system =
|
||||||
|
[ [(u'a = !bc de \\', None),
|
||||||
|
(u'fg', u_fmt("a = get_ipython().getoutput('bc de fg')")),
|
||||||
|
],
|
||||||
|
[(u'a = !bc de \\', None),
|
||||||
|
(u'fg\\', None),
|
||||||
|
(None, u_fmt("a = get_ipython().getoutput('bc de fg')")),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_assign_system():
|
||||||
|
tt.check_pairs(transform_and_reset(ipt.assign_from_system), syntax['assign_system'])
|
||||||
|
|
||||||
|
def test_assign_magic():
|
||||||
|
tt.check_pairs(transform_and_reset(ipt.assign_from_magic), syntax['assign_magic'])
|
||||||
|
|
||||||
|
def test_classic_prompt():
|
||||||
|
tt.check_pairs(transform_and_reset(ipt.classic_prompt), syntax['classic_prompt'])
|
||||||
|
for example in syntax_ml['classic_prompt']:
|
||||||
|
transform_checker(example, ipt.classic_prompt)
|
||||||
|
for example in syntax_ml['multiline_datastructure_prompt']:
|
||||||
|
transform_checker(example, ipt.classic_prompt)
|
||||||
|
|
||||||
|
# Check that we don't transform the second line if the first is obviously
|
||||||
|
# yap_ipython syntax
|
||||||
|
transform_checker([
|
||||||
|
(u'%foo', '%foo'),
|
||||||
|
(u'>>> bar', '>>> bar'),
|
||||||
|
], ipt.classic_prompt)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipy_prompt():
|
||||||
|
tt.check_pairs(transform_and_reset(ipt.ipy_prompt), syntax['ipy_prompt'])
|
||||||
|
for example in syntax_ml['ipy_prompt']:
|
||||||
|
transform_checker(example, ipt.ipy_prompt)
|
||||||
|
|
||||||
|
# Check that we don't transform the second line if we're inside a cell magic
|
||||||
|
transform_checker([
|
||||||
|
(u'%%foo', '%%foo'),
|
||||||
|
(u'In [1]: bar', 'In [1]: bar'),
|
||||||
|
], ipt.ipy_prompt)
|
||||||
|
|
||||||
|
def test_assemble_logical_lines():
|
||||||
|
tests = \
|
||||||
|
[ [(u"a = \\", None),
|
||||||
|
(u"123", u"a = 123"),
|
||||||
|
],
|
||||||
|
[(u"a = \\", None), # Test resetting when within a multi-line string
|
||||||
|
(u"12 *\\", None),
|
||||||
|
(None, u"a = 12 *"),
|
||||||
|
],
|
||||||
|
[(u"# foo\\", u"# foo\\"), # Comments can't be continued like this
|
||||||
|
],
|
||||||
|
]
|
||||||
|
for example in tests:
|
||||||
|
transform_checker(example, ipt.assemble_logical_lines)
|
||||||
|
|
||||||
|
def test_assemble_python_lines():
|
||||||
|
tests = \
|
||||||
|
[ [(u"a = '''", None),
|
||||||
|
(u"abc'''", u"a = '''\nabc'''"),
|
||||||
|
],
|
||||||
|
[(u"a = '''", None), # Test resetting when within a multi-line string
|
||||||
|
(u"def", None),
|
||||||
|
(None, u"a = '''\ndef"),
|
||||||
|
],
|
||||||
|
[(u"a = [1,", None),
|
||||||
|
(u"2]", u"a = [1,\n2]"),
|
||||||
|
],
|
||||||
|
[(u"a = [1,", None), # Test resetting when within a multi-line string
|
||||||
|
(u"2,", None),
|
||||||
|
(None, u"a = [1,\n2,"),
|
||||||
|
],
|
||||||
|
[(u"a = '''", None), # Test line continuation within a multi-line string
|
||||||
|
(u"abc\\", None),
|
||||||
|
(u"def", None),
|
||||||
|
(u"'''", u"a = '''\nabc\\\ndef\n'''"),
|
||||||
|
],
|
||||||
|
] + syntax_ml['multiline_datastructure']
|
||||||
|
for example in tests:
|
||||||
|
transform_checker(example, ipt.assemble_python_lines)
|
||||||
|
|
||||||
|
|
||||||
|
def test_help_end():
|
||||||
|
tt.check_pairs(transform_and_reset(ipt.help_end), syntax['end_help'])
|
||||||
|
|
||||||
|
def test_escaped_noesc():
|
||||||
|
tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_noesc'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_escaped_shell():
|
||||||
|
tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_shell'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_escaped_help():
|
||||||
|
tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_help'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_escaped_magic():
|
||||||
|
tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_magic'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_escaped_quote():
|
||||||
|
tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_escaped_quote2():
|
||||||
|
tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_quote2'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_escaped_paren():
|
||||||
|
tt.check_pairs(transform_and_reset(ipt.escaped_commands), syntax['escaped_paren'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_cellmagic():
|
||||||
|
for example in syntax_ml['cellmagic']:
|
||||||
|
transform_checker(example, ipt.cellmagic)
|
||||||
|
|
||||||
|
line_example = [(u'%%bar 123', None),
|
||||||
|
(u'hello', None),
|
||||||
|
(u'' , u_fmt("get_ipython().run_cell_magic('bar', '123', 'hello')")),
|
||||||
|
]
|
||||||
|
transform_checker(line_example, ipt.cellmagic, end_on_blank_line=True)
|
||||||
|
|
||||||
|
def test_has_comment():
|
||||||
|
tests = [('text', False),
|
||||||
|
('text #comment', True),
|
||||||
|
('text #comment\n', True),
|
||||||
|
('#comment', True),
|
||||||
|
('#comment\n', True),
|
||||||
|
('a = "#string"', False),
|
||||||
|
('a = "#string" # comment', True),
|
||||||
|
('a #comment not "string"', True),
|
||||||
|
]
|
||||||
|
tt.check_pairs(ipt.has_comment, tests)
|
||||||
|
|
||||||
|
@ipt.TokenInputTransformer.wrap
|
||||||
|
def decistmt(tokens):
|
||||||
|
"""Substitute Decimals for floats in a string of statements.
|
||||||
|
|
||||||
|
Based on an example from the tokenize module docs.
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
for toknum, tokval, _, _, _ in tokens:
|
||||||
|
if toknum == tokenize.NUMBER and '.' in tokval: # replace NUMBER tokens
|
||||||
|
for newtok in [
|
||||||
|
(tokenize.NAME, 'Decimal'),
|
||||||
|
(tokenize.OP, '('),
|
||||||
|
(tokenize.STRING, repr(tokval)),
|
||||||
|
(tokenize.OP, ')')
|
||||||
|
]:
|
||||||
|
yield newtok
|
||||||
|
else:
|
||||||
|
yield (toknum, tokval)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_token_input_transformer():
|
||||||
|
tests = [(u'1.2', u_fmt(u"Decimal ('1.2')")),
|
||||||
|
(u'"1.2"', u'"1.2"'),
|
||||||
|
]
|
||||||
|
tt.check_pairs(transform_and_reset(decistmt), tests)
|
||||||
|
ml_tests = \
|
||||||
|
[ [(u"a = 1.2; b = '''x", None),
|
||||||
|
(u"y'''", u_fmt(u"a =Decimal ('1.2');b ='''x\ny'''")),
|
||||||
|
],
|
||||||
|
[(u"a = [1.2,", None),
|
||||||
|
(u"3]", u_fmt(u"a =[Decimal ('1.2'),\n3 ]")),
|
||||||
|
],
|
||||||
|
[(u"a = '''foo", None), # Test resetting when within a multi-line string
|
||||||
|
(u"bar", None),
|
||||||
|
(None, u"a = '''foo\nbar"),
|
||||||
|
],
|
||||||
|
]
|
||||||
|
for example in ml_tests:
|
||||||
|
transform_checker(example, decistmt)
|
@ -0,0 +1,924 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Tests for the key interactiveshell module.
|
||||||
|
|
||||||
|
Historically the main classes in interactiveshell have been under-tested. This
|
||||||
|
module should grow as many single-method tests as possible to trap many of the
|
||||||
|
recurring bugs we seem to encounter with high-level interaction.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Copyright (c) yap_ipython Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from os.path import join
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython.core.error import InputRejected
|
||||||
|
from yap_ipython.core.inputtransformer import InputTransformer
|
||||||
|
from yap_ipython.testing.decorators import (
|
||||||
|
skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
|
||||||
|
)
|
||||||
|
from yap_ipython.testing import tools as tt
|
||||||
|
from yap_ipython.utils.process import find_cmd
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Globals
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# This is used by every single test, no point repeating it ad nauseam
|
||||||
|
ip = get_ipython()
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Tests
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class DerivedInterrupt(KeyboardInterrupt):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class InteractiveShellTestCase(unittest.TestCase):
|
||||||
|
def test_naked_string_cells(self):
|
||||||
|
"""Test that cells with only naked strings are fully executed"""
|
||||||
|
# First, single-line inputs
|
||||||
|
ip.run_cell('"a"\n')
|
||||||
|
self.assertEqual(ip.user_ns['_'], 'a')
|
||||||
|
# And also multi-line cells
|
||||||
|
ip.run_cell('"""a\nb"""\n')
|
||||||
|
self.assertEqual(ip.user_ns['_'], 'a\nb')
|
||||||
|
|
||||||
|
def test_run_empty_cell(self):
|
||||||
|
"""Just make sure we don't get a horrible error with a blank
|
||||||
|
cell of input. Yes, I did overlook that."""
|
||||||
|
old_xc = ip.execution_count
|
||||||
|
res = ip.run_cell('')
|
||||||
|
self.assertEqual(ip.execution_count, old_xc)
|
||||||
|
self.assertEqual(res.execution_count, None)
|
||||||
|
|
||||||
|
def test_run_cell_multiline(self):
|
||||||
|
"""Multi-block, multi-line cells must execute correctly.
|
||||||
|
"""
|
||||||
|
src = '\n'.join(["x=1",
|
||||||
|
"y=2",
|
||||||
|
"if 1:",
|
||||||
|
" x += 1",
|
||||||
|
" y += 1",])
|
||||||
|
res = ip.run_cell(src)
|
||||||
|
self.assertEqual(ip.user_ns['x'], 2)
|
||||||
|
self.assertEqual(ip.user_ns['y'], 3)
|
||||||
|
self.assertEqual(res.success, True)
|
||||||
|
self.assertEqual(res.result, None)
|
||||||
|
|
||||||
|
def test_multiline_string_cells(self):
|
||||||
|
"Code sprinkled with multiline strings should execute (GH-306)"
|
||||||
|
ip.run_cell('tmp=0')
|
||||||
|
self.assertEqual(ip.user_ns['tmp'], 0)
|
||||||
|
res = ip.run_cell('tmp=1;"""a\nb"""\n')
|
||||||
|
self.assertEqual(ip.user_ns['tmp'], 1)
|
||||||
|
self.assertEqual(res.success, True)
|
||||||
|
self.assertEqual(res.result, "a\nb")
|
||||||
|
|
||||||
|
def test_dont_cache_with_semicolon(self):
|
||||||
|
"Ending a line with semicolon should not cache the returned object (GH-307)"
|
||||||
|
oldlen = len(ip.user_ns['Out'])
|
||||||
|
for cell in ['1;', '1;1;']:
|
||||||
|
res = ip.run_cell(cell, store_history=True)
|
||||||
|
newlen = len(ip.user_ns['Out'])
|
||||||
|
self.assertEqual(oldlen, newlen)
|
||||||
|
self.assertIsNone(res.result)
|
||||||
|
i = 0
|
||||||
|
#also test the default caching behavior
|
||||||
|
for cell in ['1', '1;1']:
|
||||||
|
ip.run_cell(cell, store_history=True)
|
||||||
|
newlen = len(ip.user_ns['Out'])
|
||||||
|
i += 1
|
||||||
|
self.assertEqual(oldlen+i, newlen)
|
||||||
|
|
||||||
|
def test_syntax_error(self):
|
||||||
|
res = ip.run_cell("raise = 3")
|
||||||
|
self.assertIsInstance(res.error_before_exec, SyntaxError)
|
||||||
|
|
||||||
|
def test_In_variable(self):
|
||||||
|
"Verify that In variable grows with user input (GH-284)"
|
||||||
|
oldlen = len(ip.user_ns['In'])
|
||||||
|
ip.run_cell('1;', store_history=True)
|
||||||
|
newlen = len(ip.user_ns['In'])
|
||||||
|
self.assertEqual(oldlen+1, newlen)
|
||||||
|
self.assertEqual(ip.user_ns['In'][-1],'1;')
|
||||||
|
|
||||||
|
def test_magic_names_in_string(self):
|
||||||
|
ip.run_cell('a = """\n%exit\n"""')
|
||||||
|
self.assertEqual(ip.user_ns['a'], '\n%exit\n')
|
||||||
|
|
||||||
|
def test_trailing_newline(self):
|
||||||
|
"""test that running !(command) does not raise a SyntaxError"""
|
||||||
|
ip.run_cell('!(true)\n', False)
|
||||||
|
ip.run_cell('!(true)\n\n\n', False)
|
||||||
|
|
||||||
|
def test_gh_597(self):
|
||||||
|
"""Pretty-printing lists of objects with non-ascii reprs may cause
|
||||||
|
problems."""
|
||||||
|
class Spam(object):
|
||||||
|
def __repr__(self):
|
||||||
|
return "\xe9"*50
|
||||||
|
import yap_ipython.core.formatters
|
||||||
|
f = yap_ipython.core.formatters.PlainTextFormatter()
|
||||||
|
f([Spam(),Spam()])
|
||||||
|
|
||||||
|
|
||||||
|
def test_future_flags(self):
|
||||||
|
"""Check that future flags are used for parsing code (gh-777)"""
|
||||||
|
ip.run_cell('from __future__ import barry_as_FLUFL')
|
||||||
|
try:
|
||||||
|
ip.run_cell('prfunc_return_val = 1 <> 2')
|
||||||
|
assert 'prfunc_return_val' in ip.user_ns
|
||||||
|
finally:
|
||||||
|
# Reset compiler flags so we don't mess up other tests.
|
||||||
|
ip.compile.reset_compiler_flags()
|
||||||
|
|
||||||
|
def test_can_pickle(self):
|
||||||
|
"Can we pickle objects defined interactively (GH-29)"
|
||||||
|
ip = get_ipython()
|
||||||
|
ip.reset()
|
||||||
|
ip.run_cell(("class Mylist(list):\n"
|
||||||
|
" def __init__(self,x=[]):\n"
|
||||||
|
" list.__init__(self,x)"))
|
||||||
|
ip.run_cell("w=Mylist([1,2,3])")
|
||||||
|
|
||||||
|
from pickle import dumps
|
||||||
|
|
||||||
|
# We need to swap in our main module - this is only necessary
|
||||||
|
# inside the test framework, because yap_ipython puts the interactive module
|
||||||
|
# in place (but the test framework undoes this).
|
||||||
|
_main = sys.modules['__main__']
|
||||||
|
sys.modules['__main__'] = ip.user_module
|
||||||
|
try:
|
||||||
|
res = dumps(ip.user_ns["w"])
|
||||||
|
finally:
|
||||||
|
sys.modules['__main__'] = _main
|
||||||
|
self.assertTrue(isinstance(res, bytes))
|
||||||
|
|
||||||
|
def test_global_ns(self):
|
||||||
|
"Code in functions must be able to access variables outside them."
|
||||||
|
ip = get_ipython()
|
||||||
|
ip.run_cell("a = 10")
|
||||||
|
ip.run_cell(("def f(x):\n"
|
||||||
|
" return x + a"))
|
||||||
|
ip.run_cell("b = f(12)")
|
||||||
|
self.assertEqual(ip.user_ns["b"], 22)
|
||||||
|
|
||||||
|
def test_bad_custom_tb(self):
|
||||||
|
"""Check that InteractiveShell is protected from bad custom exception handlers"""
|
||||||
|
ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
|
||||||
|
self.assertEqual(ip.custom_exceptions, (IOError,))
|
||||||
|
with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
|
||||||
|
ip.run_cell(u'raise IOError("foo")')
|
||||||
|
self.assertEqual(ip.custom_exceptions, ())
|
||||||
|
|
||||||
|
def test_bad_custom_tb_return(self):
|
||||||
|
"""Check that InteractiveShell is protected from bad return types in custom exception handlers"""
|
||||||
|
ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
|
||||||
|
self.assertEqual(ip.custom_exceptions, (NameError,))
|
||||||
|
with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
|
||||||
|
ip.run_cell(u'a=abracadabra')
|
||||||
|
self.assertEqual(ip.custom_exceptions, ())
|
||||||
|
|
||||||
|
def test_drop_by_id(self):
|
||||||
|
myvars = {"a":object(), "b":object(), "c": object()}
|
||||||
|
ip.push(myvars, interactive=False)
|
||||||
|
for name in myvars:
|
||||||
|
assert name in ip.user_ns, name
|
||||||
|
assert name in ip.user_ns_hidden, name
|
||||||
|
ip.user_ns['b'] = 12
|
||||||
|
ip.drop_by_id(myvars)
|
||||||
|
for name in ["a", "c"]:
|
||||||
|
assert name not in ip.user_ns, name
|
||||||
|
assert name not in ip.user_ns_hidden, name
|
||||||
|
assert ip.user_ns['b'] == 12
|
||||||
|
ip.reset()
|
||||||
|
|
||||||
|
def test_var_expand(self):
|
||||||
|
ip.user_ns['f'] = u'Ca\xf1o'
|
||||||
|
self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
|
||||||
|
self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
|
||||||
|
self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
|
||||||
|
self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
|
||||||
|
|
||||||
|
self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
|
||||||
|
|
||||||
|
ip.user_ns['f'] = b'Ca\xc3\xb1o'
|
||||||
|
# This should not raise any exception:
|
||||||
|
ip.var_expand(u'echo $f')
|
||||||
|
|
||||||
|
def test_var_expand_local(self):
|
||||||
|
"""Test local variable expansion in !system and %magic calls"""
|
||||||
|
# !system
|
||||||
|
ip.run_cell('def test():\n'
|
||||||
|
' lvar = "ttt"\n'
|
||||||
|
' ret = !echo {lvar}\n'
|
||||||
|
' return ret[0]\n')
|
||||||
|
res = ip.user_ns['test']()
|
||||||
|
nt.assert_in('ttt', res)
|
||||||
|
|
||||||
|
# %magic
|
||||||
|
ip.run_cell('def makemacro():\n'
|
||||||
|
' macroname = "macro_var_expand_locals"\n'
|
||||||
|
' %macro {macroname} codestr\n')
|
||||||
|
ip.user_ns['codestr'] = "str(12)"
|
||||||
|
ip.run_cell('makemacro()')
|
||||||
|
nt.assert_in('macro_var_expand_locals', ip.user_ns)
|
||||||
|
|
||||||
|
def test_var_expand_self(self):
|
||||||
|
"""Test variable expansion with the name 'self', which was failing.
|
||||||
|
|
||||||
|
See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
|
||||||
|
"""
|
||||||
|
ip.run_cell('class cTest:\n'
|
||||||
|
' classvar="see me"\n'
|
||||||
|
' def test(self):\n'
|
||||||
|
' res = !echo Variable: {self.classvar}\n'
|
||||||
|
' return res[0]\n')
|
||||||
|
nt.assert_in('see me', ip.user_ns['cTest']().test())
|
||||||
|
|
||||||
|
def test_bad_var_expand(self):
|
||||||
|
"""var_expand on invalid formats shouldn't raise"""
|
||||||
|
# SyntaxError
|
||||||
|
self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
|
||||||
|
# NameError
|
||||||
|
self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
|
||||||
|
# ZeroDivisionError
|
||||||
|
self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
|
||||||
|
|
||||||
|
def test_silent_postexec(self):
|
||||||
|
"""run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
|
||||||
|
pre_explicit = mock.Mock()
|
||||||
|
pre_always = mock.Mock()
|
||||||
|
post_explicit = mock.Mock()
|
||||||
|
post_always = mock.Mock()
|
||||||
|
all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
|
||||||
|
|
||||||
|
ip.events.register('pre_run_cell', pre_explicit)
|
||||||
|
ip.events.register('pre_execute', pre_always)
|
||||||
|
ip.events.register('post_run_cell', post_explicit)
|
||||||
|
ip.events.register('post_execute', post_always)
|
||||||
|
|
||||||
|
try:
|
||||||
|
ip.run_cell("1", silent=True)
|
||||||
|
assert pre_always.called
|
||||||
|
assert not pre_explicit.called
|
||||||
|
assert post_always.called
|
||||||
|
assert not post_explicit.called
|
||||||
|
# double-check that non-silent exec did what we expected
|
||||||
|
# silent to avoid
|
||||||
|
ip.run_cell("1")
|
||||||
|
assert pre_explicit.called
|
||||||
|
assert post_explicit.called
|
||||||
|
info, = pre_explicit.call_args[0]
|
||||||
|
result, = post_explicit.call_args[0]
|
||||||
|
self.assertEqual(info, result.info)
|
||||||
|
# check that post hooks are always called
|
||||||
|
[m.reset_mock() for m in all_mocks]
|
||||||
|
ip.run_cell("syntax error")
|
||||||
|
assert pre_always.called
|
||||||
|
assert pre_explicit.called
|
||||||
|
assert post_always.called
|
||||||
|
assert post_explicit.called
|
||||||
|
info, = pre_explicit.call_args[0]
|
||||||
|
result, = post_explicit.call_args[0]
|
||||||
|
self.assertEqual(info, result.info)
|
||||||
|
finally:
|
||||||
|
# remove post-exec
|
||||||
|
ip.events.unregister('pre_run_cell', pre_explicit)
|
||||||
|
ip.events.unregister('pre_execute', pre_always)
|
||||||
|
ip.events.unregister('post_run_cell', post_explicit)
|
||||||
|
ip.events.unregister('post_execute', post_always)
|
||||||
|
|
||||||
|
def test_silent_noadvance(self):
|
||||||
|
"""run_cell(silent=True) doesn't advance execution_count"""
|
||||||
|
ec = ip.execution_count
|
||||||
|
# silent should force store_history=False
|
||||||
|
ip.run_cell("1", store_history=True, silent=True)
|
||||||
|
|
||||||
|
self.assertEqual(ec, ip.execution_count)
|
||||||
|
# double-check that non-silent exec did what we expected
|
||||||
|
# silent to avoid
|
||||||
|
ip.run_cell("1", store_history=True)
|
||||||
|
self.assertEqual(ec+1, ip.execution_count)
|
||||||
|
|
||||||
|
def test_silent_nodisplayhook(self):
|
||||||
|
"""run_cell(silent=True) doesn't trigger displayhook"""
|
||||||
|
d = dict(called=False)
|
||||||
|
|
||||||
|
trap = ip.display_trap
|
||||||
|
save_hook = trap.hook
|
||||||
|
|
||||||
|
def failing_hook(*args, **kwargs):
|
||||||
|
d['called'] = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
trap.hook = failing_hook
|
||||||
|
res = ip.run_cell("1", silent=True)
|
||||||
|
self.assertFalse(d['called'])
|
||||||
|
self.assertIsNone(res.result)
|
||||||
|
# double-check that non-silent exec did what we expected
|
||||||
|
# silent to avoid
|
||||||
|
ip.run_cell("1")
|
||||||
|
self.assertTrue(d['called'])
|
||||||
|
finally:
|
||||||
|
trap.hook = save_hook
|
||||||
|
|
||||||
|
def test_ofind_line_magic(self):
|
||||||
|
from yap_ipython.core.magic import register_line_magic
|
||||||
|
|
||||||
|
@register_line_magic
|
||||||
|
def lmagic(line):
|
||||||
|
"A line magic"
|
||||||
|
|
||||||
|
# Get info on line magic
|
||||||
|
lfind = ip._ofind('lmagic')
|
||||||
|
info = dict(found=True, isalias=False, ismagic=True,
|
||||||
|
namespace = 'yap_ipython internal', obj= lmagic.__wrapped__,
|
||||||
|
parent = None)
|
||||||
|
nt.assert_equal(lfind, info)
|
||||||
|
|
||||||
|
def test_ofind_cell_magic(self):
|
||||||
|
from yap_ipython.core.magic import register_cell_magic
|
||||||
|
|
||||||
|
@register_cell_magic
|
||||||
|
def cmagic(line, cell):
|
||||||
|
"A cell magic"
|
||||||
|
|
||||||
|
# Get info on cell magic
|
||||||
|
find = ip._ofind('cmagic')
|
||||||
|
info = dict(found=True, isalias=False, ismagic=True,
|
||||||
|
namespace = 'yap_ipython internal', obj= cmagic.__wrapped__,
|
||||||
|
parent = None)
|
||||||
|
nt.assert_equal(find, info)
|
||||||
|
|
||||||
|
def test_ofind_property_with_error(self):
|
||||||
|
class A(object):
|
||||||
|
@property
|
||||||
|
def foo(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
a = A()
|
||||||
|
|
||||||
|
found = ip._ofind('a.foo', [('locals', locals())])
|
||||||
|
info = dict(found=True, isalias=False, ismagic=False,
|
||||||
|
namespace='locals', obj=A.foo, parent=a)
|
||||||
|
nt.assert_equal(found, info)
|
||||||
|
|
||||||
|
def test_ofind_multiple_attribute_lookups(self):
|
||||||
|
class A(object):
|
||||||
|
@property
|
||||||
|
def foo(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
a = A()
|
||||||
|
a.a = A()
|
||||||
|
a.a.a = A()
|
||||||
|
|
||||||
|
found = ip._ofind('a.a.a.foo', [('locals', locals())])
|
||||||
|
info = dict(found=True, isalias=False, ismagic=False,
|
||||||
|
namespace='locals', obj=A.foo, parent=a.a.a)
|
||||||
|
nt.assert_equal(found, info)
|
||||||
|
|
||||||
|
def test_ofind_slotted_attributes(self):
|
||||||
|
class A(object):
|
||||||
|
__slots__ = ['foo']
|
||||||
|
def __init__(self):
|
||||||
|
self.foo = 'bar'
|
||||||
|
|
||||||
|
a = A()
|
||||||
|
found = ip._ofind('a.foo', [('locals', locals())])
|
||||||
|
info = dict(found=True, isalias=False, ismagic=False,
|
||||||
|
namespace='locals', obj=a.foo, parent=a)
|
||||||
|
nt.assert_equal(found, info)
|
||||||
|
|
||||||
|
found = ip._ofind('a.bar', [('locals', locals())])
|
||||||
|
info = dict(found=False, isalias=False, ismagic=False,
|
||||||
|
namespace=None, obj=None, parent=a)
|
||||||
|
nt.assert_equal(found, info)
|
||||||
|
|
||||||
|
def test_ofind_prefers_property_to_instance_level_attribute(self):
|
||||||
|
class A(object):
|
||||||
|
@property
|
||||||
|
def foo(self):
|
||||||
|
return 'bar'
|
||||||
|
a = A()
|
||||||
|
a.__dict__['foo'] = 'baz'
|
||||||
|
nt.assert_equal(a.foo, 'bar')
|
||||||
|
found = ip._ofind('a.foo', [('locals', locals())])
|
||||||
|
nt.assert_is(found['obj'], A.foo)
|
||||||
|
|
||||||
|
def test_custom_syntaxerror_exception(self):
|
||||||
|
called = []
|
||||||
|
def my_handler(shell, etype, value, tb, tb_offset=None):
|
||||||
|
called.append(etype)
|
||||||
|
shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
|
||||||
|
|
||||||
|
ip.set_custom_exc((SyntaxError,), my_handler)
|
||||||
|
try:
|
||||||
|
ip.run_cell("1f")
|
||||||
|
# Check that this was called, and only once.
|
||||||
|
self.assertEqual(called, [SyntaxError])
|
||||||
|
finally:
|
||||||
|
# Reset the custom exception hook
|
||||||
|
ip.set_custom_exc((), None)
|
||||||
|
|
||||||
|
def test_custom_exception(self):
|
||||||
|
called = []
|
||||||
|
def my_handler(shell, etype, value, tb, tb_offset=None):
|
||||||
|
called.append(etype)
|
||||||
|
shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
|
||||||
|
|
||||||
|
ip.set_custom_exc((ValueError,), my_handler)
|
||||||
|
try:
|
||||||
|
res = ip.run_cell("raise ValueError('test')")
|
||||||
|
# Check that this was called, and only once.
|
||||||
|
self.assertEqual(called, [ValueError])
|
||||||
|
# Check that the error is on the result object
|
||||||
|
self.assertIsInstance(res.error_in_exec, ValueError)
|
||||||
|
finally:
|
||||||
|
# Reset the custom exception hook
|
||||||
|
ip.set_custom_exc((), None)
|
||||||
|
|
||||||
|
def test_mktempfile(self):
|
||||||
|
filename = ip.mktempfile()
|
||||||
|
# Check that we can open the file again on Windows
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
f.write('abc')
|
||||||
|
|
||||||
|
filename = ip.mktempfile(data='blah')
|
||||||
|
with open(filename, 'r') as f:
|
||||||
|
self.assertEqual(f.read(), 'blah')
|
||||||
|
|
||||||
|
def test_new_main_mod(self):
|
||||||
|
# Smoketest to check that this accepts a unicode module name
|
||||||
|
name = u'jiefmw'
|
||||||
|
mod = ip.new_main_mod(u'%s.py' % name, name)
|
||||||
|
self.assertEqual(mod.__name__, name)
|
||||||
|
|
||||||
|
def test_get_exception_only(self):
|
||||||
|
try:
|
||||||
|
raise KeyboardInterrupt
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
msg = ip.get_exception_only()
|
||||||
|
self.assertEqual(msg, 'KeyboardInterrupt\n')
|
||||||
|
|
||||||
|
try:
|
||||||
|
raise DerivedInterrupt("foo")
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
msg = ip.get_exception_only()
|
||||||
|
self.assertEqual(msg, 'yap_ipython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
|
||||||
|
|
||||||
|
def test_inspect_text(self):
|
||||||
|
ip.run_cell('a = 5')
|
||||||
|
text = ip.object_inspect_text('a')
|
||||||
|
self.assertIsInstance(text, str)
|
||||||
|
|
||||||
|
def test_last_execution_result(self):
|
||||||
|
""" Check that last execution result gets set correctly (GH-10702) """
|
||||||
|
result = ip.run_cell('a = 5; a')
|
||||||
|
self.assertTrue(ip.last_execution_succeeded)
|
||||||
|
self.assertEqual(ip.last_execution_result.result, 5)
|
||||||
|
|
||||||
|
result = ip.run_cell('a = x_invalid_id_x')
|
||||||
|
self.assertFalse(ip.last_execution_succeeded)
|
||||||
|
self.assertFalse(ip.last_execution_result.success)
|
||||||
|
self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
|
||||||
|
|
||||||
|
|
||||||
|
class TestSafeExecfileNonAsciiPath(unittest.TestCase):
|
||||||
|
|
||||||
|
@onlyif_unicode_paths
|
||||||
|
def setUp(self):
|
||||||
|
self.BASETESTDIR = tempfile.mkdtemp()
|
||||||
|
self.TESTDIR = join(self.BASETESTDIR, u"åäö")
|
||||||
|
os.mkdir(self.TESTDIR)
|
||||||
|
with open(join(self.TESTDIR, u"åäötestscript.py"), "w") as sfile:
|
||||||
|
sfile.write("pass\n")
|
||||||
|
self.oldpath = os.getcwd()
|
||||||
|
os.chdir(self.TESTDIR)
|
||||||
|
self.fname = u"åäötestscript.py"
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
os.chdir(self.oldpath)
|
||||||
|
shutil.rmtree(self.BASETESTDIR)
|
||||||
|
|
||||||
|
@onlyif_unicode_paths
|
||||||
|
def test_1(self):
|
||||||
|
"""Test safe_execfile with non-ascii path
|
||||||
|
"""
|
||||||
|
ip.safe_execfile(self.fname, {}, raise_exceptions=True)
|
||||||
|
|
||||||
|
class ExitCodeChecks(tt.TempFileMixin):
|
||||||
|
def test_exit_code_ok(self):
|
||||||
|
self.system('exit 0')
|
||||||
|
self.assertEqual(ip.user_ns['_exit_code'], 0)
|
||||||
|
|
||||||
|
def test_exit_code_error(self):
|
||||||
|
self.system('exit 1')
|
||||||
|
self.assertEqual(ip.user_ns['_exit_code'], 1)
|
||||||
|
|
||||||
|
@skipif(not hasattr(signal, 'SIGALRM'))
|
||||||
|
def test_exit_code_signal(self):
|
||||||
|
self.mktmp("import signal, time\n"
|
||||||
|
"signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
|
||||||
|
"time.sleep(1)\n")
|
||||||
|
self.system("%s %s" % (sys.executable, self.fname))
|
||||||
|
self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
|
||||||
|
|
||||||
|
@onlyif_cmds_exist("csh")
|
||||||
|
def test_exit_code_signal_csh(self):
|
||||||
|
SHELL = os.environ.get('SHELL', None)
|
||||||
|
os.environ['SHELL'] = find_cmd("csh")
|
||||||
|
try:
|
||||||
|
self.test_exit_code_signal()
|
||||||
|
finally:
|
||||||
|
if SHELL is not None:
|
||||||
|
os.environ['SHELL'] = SHELL
|
||||||
|
else:
|
||||||
|
del os.environ['SHELL']
|
||||||
|
|
||||||
|
class TestSystemRaw(unittest.TestCase, ExitCodeChecks):
|
||||||
|
system = ip.system_raw
|
||||||
|
|
||||||
|
@onlyif_unicode_paths
|
||||||
|
def test_1(self):
|
||||||
|
"""Test system_raw with non-ascii cmd
|
||||||
|
"""
|
||||||
|
cmd = u'''python -c "'åäö'" '''
|
||||||
|
ip.system_raw(cmd)
|
||||||
|
|
||||||
|
@mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
|
||||||
|
@mock.patch('os.system', side_effect=KeyboardInterrupt)
|
||||||
|
def test_control_c(self, *mocks):
|
||||||
|
try:
|
||||||
|
self.system("sleep 1 # wont happen")
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
self.fail("system call should intercept "
|
||||||
|
"keyboard interrupt from subprocess.call")
|
||||||
|
self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGINT)
|
||||||
|
|
||||||
|
# TODO: Exit codes are currently ignored on Windows.
|
||||||
|
class TestSystemPipedExitCode(unittest.TestCase, ExitCodeChecks):
|
||||||
|
system = ip.system_piped
|
||||||
|
|
||||||
|
@skip_win32
|
||||||
|
def test_exit_code_ok(self):
|
||||||
|
ExitCodeChecks.test_exit_code_ok(self)
|
||||||
|
|
||||||
|
@skip_win32
|
||||||
|
def test_exit_code_error(self):
|
||||||
|
ExitCodeChecks.test_exit_code_error(self)
|
||||||
|
|
||||||
|
@skip_win32
|
||||||
|
def test_exit_code_signal(self):
|
||||||
|
ExitCodeChecks.test_exit_code_signal(self)
|
||||||
|
|
||||||
|
class TestModules(unittest.TestCase, tt.TempFileMixin):
|
||||||
|
def test_extraneous_loads(self):
|
||||||
|
"""Test we're not loading modules on startup that we shouldn't.
|
||||||
|
"""
|
||||||
|
self.mktmp("import sys\n"
|
||||||
|
"print('numpy' in sys.modules)\n"
|
||||||
|
"print('ipyparallel' in sys.modules)\n"
|
||||||
|
"print('yap_kernel' in sys.modules)\n"
|
||||||
|
)
|
||||||
|
out = "False\nFalse\nFalse\n"
|
||||||
|
tt.ipexec_validate(self.fname, out)
|
||||||
|
|
||||||
|
class Negator(ast.NodeTransformer):
|
||||||
|
"""Negates all number literals in an AST."""
|
||||||
|
def visit_Num(self, node):
|
||||||
|
node.n = -node.n
|
||||||
|
return node
|
||||||
|
|
||||||
|
class TestAstTransform(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.negator = Negator()
|
||||||
|
ip.ast_transformers.append(self.negator)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
ip.ast_transformers.remove(self.negator)
|
||||||
|
|
||||||
|
def test_run_cell(self):
|
||||||
|
with tt.AssertPrints('-34'):
|
||||||
|
ip.run_cell('print (12 + 22)')
|
||||||
|
|
||||||
|
# A named reference to a number shouldn't be transformed.
|
||||||
|
ip.user_ns['n'] = 55
|
||||||
|
with tt.AssertNotPrints('-55'):
|
||||||
|
ip.run_cell('print (n)')
|
||||||
|
|
||||||
|
def test_timeit(self):
|
||||||
|
called = set()
|
||||||
|
def f(x):
|
||||||
|
called.add(x)
|
||||||
|
ip.push({'f':f})
|
||||||
|
|
||||||
|
with tt.AssertPrints("std. dev. of"):
|
||||||
|
ip.run_line_magic("timeit", "-n1 f(1)")
|
||||||
|
self.assertEqual(called, {-1})
|
||||||
|
called.clear()
|
||||||
|
|
||||||
|
with tt.AssertPrints("std. dev. of"):
|
||||||
|
ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
|
||||||
|
self.assertEqual(called, {-2, -3})
|
||||||
|
|
||||||
|
def test_time(self):
|
||||||
|
called = []
|
||||||
|
def f(x):
|
||||||
|
called.append(x)
|
||||||
|
ip.push({'f':f})
|
||||||
|
|
||||||
|
# Test with an expression
|
||||||
|
with tt.AssertPrints("Wall time: "):
|
||||||
|
ip.run_line_magic("time", "f(5+9)")
|
||||||
|
self.assertEqual(called, [-14])
|
||||||
|
called[:] = []
|
||||||
|
|
||||||
|
# Test with a statement (different code path)
|
||||||
|
with tt.AssertPrints("Wall time: "):
|
||||||
|
ip.run_line_magic("time", "a = f(-3 + -2)")
|
||||||
|
self.assertEqual(called, [5])
|
||||||
|
|
||||||
|
def test_macro(self):
|
||||||
|
ip.push({'a':10})
|
||||||
|
# The AST transformation makes this do a+=-1
|
||||||
|
ip.define_macro("amacro", "a+=1\nprint(a)")
|
||||||
|
|
||||||
|
with tt.AssertPrints("9"):
|
||||||
|
ip.run_cell("amacro")
|
||||||
|
with tt.AssertPrints("8"):
|
||||||
|
ip.run_cell("amacro")
|
||||||
|
|
||||||
|
class IntegerWrapper(ast.NodeTransformer):
|
||||||
|
"""Wraps all integers in a call to Integer()"""
|
||||||
|
def visit_Num(self, node):
|
||||||
|
if isinstance(node.n, int):
|
||||||
|
return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
|
||||||
|
args=[node], keywords=[])
|
||||||
|
return node
|
||||||
|
|
||||||
|
class TestAstTransform2(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.intwrapper = IntegerWrapper()
|
||||||
|
ip.ast_transformers.append(self.intwrapper)
|
||||||
|
|
||||||
|
self.calls = []
|
||||||
|
def Integer(*args):
|
||||||
|
self.calls.append(args)
|
||||||
|
return args
|
||||||
|
ip.push({"Integer": Integer})
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
ip.ast_transformers.remove(self.intwrapper)
|
||||||
|
del ip.user_ns['Integer']
|
||||||
|
|
||||||
|
def test_run_cell(self):
|
||||||
|
ip.run_cell("n = 2")
|
||||||
|
self.assertEqual(self.calls, [(2,)])
|
||||||
|
|
||||||
|
# This shouldn't throw an error
|
||||||
|
ip.run_cell("o = 2.0")
|
||||||
|
self.assertEqual(ip.user_ns['o'], 2.0)
|
||||||
|
|
||||||
|
def test_timeit(self):
|
||||||
|
called = set()
|
||||||
|
def f(x):
|
||||||
|
called.add(x)
|
||||||
|
ip.push({'f':f})
|
||||||
|
|
||||||
|
with tt.AssertPrints("std. dev. of"):
|
||||||
|
ip.run_line_magic("timeit", "-n1 f(1)")
|
||||||
|
self.assertEqual(called, {(1,)})
|
||||||
|
called.clear()
|
||||||
|
|
||||||
|
with tt.AssertPrints("std. dev. of"):
|
||||||
|
ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
|
||||||
|
self.assertEqual(called, {(2,), (3,)})
|
||||||
|
|
||||||
|
class ErrorTransformer(ast.NodeTransformer):
|
||||||
|
"""Throws an error when it sees a number."""
|
||||||
|
def visit_Num(self, node):
|
||||||
|
raise ValueError("test")
|
||||||
|
|
||||||
|
class TestAstTransformError(unittest.TestCase):
|
||||||
|
def test_unregistering(self):
|
||||||
|
err_transformer = ErrorTransformer()
|
||||||
|
ip.ast_transformers.append(err_transformer)
|
||||||
|
|
||||||
|
with tt.AssertPrints("unregister", channel='stderr'):
|
||||||
|
ip.run_cell("1 + 2")
|
||||||
|
|
||||||
|
# This should have been removed.
|
||||||
|
nt.assert_not_in(err_transformer, ip.ast_transformers)
|
||||||
|
|
||||||
|
|
||||||
|
class StringRejector(ast.NodeTransformer):
|
||||||
|
"""Throws an InputRejected when it sees a string literal.
|
||||||
|
|
||||||
|
Used to verify that NodeTransformers can signal that a piece of code should
|
||||||
|
not be executed by throwing an InputRejected.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def visit_Str(self, node):
|
||||||
|
raise InputRejected("test")
|
||||||
|
|
||||||
|
|
||||||
|
class TestAstTransformInputRejection(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.transformer = StringRejector()
|
||||||
|
ip.ast_transformers.append(self.transformer)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
ip.ast_transformers.remove(self.transformer)
|
||||||
|
|
||||||
|
def test_input_rejection(self):
|
||||||
|
"""Check that NodeTransformers can reject input."""
|
||||||
|
|
||||||
|
expect_exception_tb = tt.AssertPrints("InputRejected: test")
|
||||||
|
expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
|
||||||
|
|
||||||
|
# Run the same check twice to verify that the transformer is not
|
||||||
|
# disabled after raising.
|
||||||
|
with expect_exception_tb, expect_no_cell_output:
|
||||||
|
ip.run_cell("'unsafe'")
|
||||||
|
|
||||||
|
with expect_exception_tb, expect_no_cell_output:
|
||||||
|
res = ip.run_cell("'unsafe'")
|
||||||
|
|
||||||
|
self.assertIsInstance(res.error_before_exec, InputRejected)
|
||||||
|
|
||||||
|
def test__IPYTHON__():
|
||||||
|
# This shouldn't raise a NameError, that's all
|
||||||
|
__IPYTHON__
|
||||||
|
|
||||||
|
|
||||||
|
class DummyRepr(object):
|
||||||
|
def __repr__(self):
|
||||||
|
return "DummyRepr"
|
||||||
|
|
||||||
|
def _repr_html_(self):
|
||||||
|
return "<b>dummy</b>"
|
||||||
|
|
||||||
|
def _repr_javascript_(self):
|
||||||
|
return "console.log('hi');", {'key': 'value'}
|
||||||
|
|
||||||
|
|
||||||
|
def test_user_variables():
|
||||||
|
# enable all formatters
|
||||||
|
ip.display_formatter.active_types = ip.display_formatter.format_types
|
||||||
|
|
||||||
|
ip.user_ns['dummy'] = d = DummyRepr()
|
||||||
|
keys = {'dummy', 'doesnotexist'}
|
||||||
|
r = ip.user_expressions({ key:key for key in keys})
|
||||||
|
|
||||||
|
nt.assert_equal(keys, set(r.keys()))
|
||||||
|
dummy = r['dummy']
|
||||||
|
nt.assert_equal({'status', 'data', 'metadata'}, set(dummy.keys()))
|
||||||
|
nt.assert_equal(dummy['status'], 'ok')
|
||||||
|
data = dummy['data']
|
||||||
|
metadata = dummy['metadata']
|
||||||
|
nt.assert_equal(data.get('text/html'), d._repr_html_())
|
||||||
|
js, jsmd = d._repr_javascript_()
|
||||||
|
nt.assert_equal(data.get('application/javascript'), js)
|
||||||
|
nt.assert_equal(metadata.get('application/javascript'), jsmd)
|
||||||
|
|
||||||
|
dne = r['doesnotexist']
|
||||||
|
nt.assert_equal(dne['status'], 'error')
|
||||||
|
nt.assert_equal(dne['ename'], 'NameError')
|
||||||
|
|
||||||
|
# back to text only
|
||||||
|
ip.display_formatter.active_types = ['text/plain']
|
||||||
|
|
||||||
|
def test_user_expression():
|
||||||
|
# enable all formatters
|
||||||
|
ip.display_formatter.active_types = ip.display_formatter.format_types
|
||||||
|
query = {
|
||||||
|
'a' : '1 + 2',
|
||||||
|
'b' : '1/0',
|
||||||
|
}
|
||||||
|
r = ip.user_expressions(query)
|
||||||
|
import pprint
|
||||||
|
pprint.pprint(r)
|
||||||
|
nt.assert_equal(set(r.keys()), set(query.keys()))
|
||||||
|
a = r['a']
|
||||||
|
nt.assert_equal({'status', 'data', 'metadata'}, set(a.keys()))
|
||||||
|
nt.assert_equal(a['status'], 'ok')
|
||||||
|
data = a['data']
|
||||||
|
metadata = a['metadata']
|
||||||
|
nt.assert_equal(data.get('text/plain'), '3')
|
||||||
|
|
||||||
|
b = r['b']
|
||||||
|
nt.assert_equal(b['status'], 'error')
|
||||||
|
nt.assert_equal(b['ename'], 'ZeroDivisionError')
|
||||||
|
|
||||||
|
# back to text only
|
||||||
|
ip.display_formatter.active_types = ['text/plain']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TestSyntaxErrorTransformer(unittest.TestCase):
|
||||||
|
"""Check that SyntaxError raised by an input transformer is handled by run_cell()"""
|
||||||
|
|
||||||
|
class SyntaxErrorTransformer(InputTransformer):
|
||||||
|
|
||||||
|
def push(self, line):
|
||||||
|
pos = line.find('syntaxerror')
|
||||||
|
if pos >= 0:
|
||||||
|
e = SyntaxError('input contains "syntaxerror"')
|
||||||
|
e.text = line
|
||||||
|
e.offset = pos + 1
|
||||||
|
raise e
|
||||||
|
return line
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.transformer = TestSyntaxErrorTransformer.SyntaxErrorTransformer()
|
||||||
|
ip.input_splitter.python_line_transforms.append(self.transformer)
|
||||||
|
ip.input_transformer_manager.python_line_transforms.append(self.transformer)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
ip.input_splitter.python_line_transforms.remove(self.transformer)
|
||||||
|
ip.input_transformer_manager.python_line_transforms.remove(self.transformer)
|
||||||
|
|
||||||
|
def test_syntaxerror_input_transformer(self):
|
||||||
|
with tt.AssertPrints('1234'):
|
||||||
|
ip.run_cell('1234')
|
||||||
|
with tt.AssertPrints('SyntaxError: invalid syntax'):
|
||||||
|
ip.run_cell('1 2 3') # plain python syntax error
|
||||||
|
with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
|
||||||
|
ip.run_cell('2345 # syntaxerror') # input transformer syntax error
|
||||||
|
with tt.AssertPrints('3456'):
|
||||||
|
ip.run_cell('3456')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_warning_suppression():
|
||||||
|
ip.run_cell("import warnings")
|
||||||
|
try:
|
||||||
|
with tt.AssertPrints("UserWarning: asdf", channel="stderr"):
|
||||||
|
ip.run_cell("warnings.warn('asdf')")
|
||||||
|
# Here's the real test -- if we run that again, we should get the
|
||||||
|
# warning again. Traditionally, each warning was only issued once per
|
||||||
|
# yap_ipython session (approximately), even if the user typed in new and
|
||||||
|
# different code that should have also triggered the warning, leading
|
||||||
|
# to much confusion.
|
||||||
|
with tt.AssertPrints("UserWarning: asdf", channel="stderr"):
|
||||||
|
ip.run_cell("warnings.warn('asdf')")
|
||||||
|
finally:
|
||||||
|
ip.run_cell("del warnings")
|
||||||
|
|
||||||
|
|
||||||
|
def test_deprecation_warning():
|
||||||
|
ip.run_cell("""
|
||||||
|
import warnings
|
||||||
|
def wrn():
|
||||||
|
warnings.warn(
|
||||||
|
"I AM A WARNING",
|
||||||
|
DeprecationWarning
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
try:
|
||||||
|
with tt.AssertPrints("I AM A WARNING", channel="stderr"):
|
||||||
|
ip.run_cell("wrn()")
|
||||||
|
finally:
|
||||||
|
ip.run_cell("del warnings")
|
||||||
|
ip.run_cell("del wrn")
|
||||||
|
|
||||||
|
|
||||||
|
class TestImportNoDeprecate(tt.TempFileMixin):
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
"""Make a valid python temp file."""
|
||||||
|
self.mktmp("""
|
||||||
|
import warnings
|
||||||
|
def wrn():
|
||||||
|
warnings.warn(
|
||||||
|
"I AM A WARNING",
|
||||||
|
DeprecationWarning
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
def test_no_dep(self):
|
||||||
|
"""
|
||||||
|
No deprecation warning should be raised from imported functions
|
||||||
|
"""
|
||||||
|
ip.run_cell("from {} import wrn".format(self.fname))
|
||||||
|
|
||||||
|
with tt.AssertNotPrints("I AM A WARNING"):
|
||||||
|
ip.run_cell("wrn()")
|
||||||
|
ip.run_cell("del wrn")
|
254
packages/python/yap_kernel/yap_ipython/core/tests/test_iplib.py
Normal file
254
packages/python/yap_kernel/yap_ipython/core/tests/test_iplib.py
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
"""Tests for the key interactiveshell module, where the main ipython class is defined.
|
||||||
|
"""
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Module imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# third party
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
# our own packages
|
||||||
|
from yap_ipython.testing.globalipapp import get_ipython
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Globals
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Get the public instance of yap_ipython
|
||||||
|
ip = get_ipython()
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Test functions
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_reset():
|
||||||
|
"""reset must clear most namespaces."""
|
||||||
|
|
||||||
|
# Check that reset runs without error
|
||||||
|
ip.reset()
|
||||||
|
|
||||||
|
# Once we've reset it (to clear of any junk that might have been there from
|
||||||
|
# other tests, we can count how many variables are in the user's namespace
|
||||||
|
nvars_user_ns = len(ip.user_ns)
|
||||||
|
nvars_hidden = len(ip.user_ns_hidden)
|
||||||
|
|
||||||
|
# Now add a few variables to user_ns, and check that reset clears them
|
||||||
|
ip.user_ns['x'] = 1
|
||||||
|
ip.user_ns['y'] = 1
|
||||||
|
ip.reset()
|
||||||
|
|
||||||
|
# Finally, check that all namespaces have only as many variables as we
|
||||||
|
# expect to find in them:
|
||||||
|
nt.assert_equal(len(ip.user_ns), nvars_user_ns)
|
||||||
|
nt.assert_equal(len(ip.user_ns_hidden), nvars_hidden)
|
||||||
|
|
||||||
|
|
||||||
|
# Tests for reporting of exceptions in various modes, handling of SystemExit,
|
||||||
|
# and %tb functionality. This is really a mix of testing ultraTB and interactiveshell.
|
||||||
|
|
||||||
|
def doctest_tb_plain():
|
||||||
|
"""
|
||||||
|
In [18]: xmode plain
|
||||||
|
Exception reporting mode: Plain
|
||||||
|
|
||||||
|
In [19]: run simpleerr.py
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...line 32, in <module>
|
||||||
|
bar(mode)
|
||||||
|
...line 16, in bar
|
||||||
|
div0()
|
||||||
|
...line 8, in div0
|
||||||
|
x/y
|
||||||
|
ZeroDivisionError: ...
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def doctest_tb_context():
|
||||||
|
"""
|
||||||
|
In [3]: xmode context
|
||||||
|
Exception reporting mode: Context
|
||||||
|
|
||||||
|
In [4]: run simpleerr.py
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
ZeroDivisionError Traceback (most recent call last)
|
||||||
|
<BLANKLINE>
|
||||||
|
... in <module>()
|
||||||
|
30 mode = 'div'
|
||||||
|
31
|
||||||
|
---> 32 bar(mode)
|
||||||
|
<BLANKLINE>
|
||||||
|
... in bar(mode)
|
||||||
|
14 "bar"
|
||||||
|
15 if mode=='div':
|
||||||
|
---> 16 div0()
|
||||||
|
17 elif mode=='exit':
|
||||||
|
18 try:
|
||||||
|
<BLANKLINE>
|
||||||
|
... in div0()
|
||||||
|
6 x = 1
|
||||||
|
7 y = 0
|
||||||
|
----> 8 x/y
|
||||||
|
9
|
||||||
|
10 def sysexit(stat, mode):
|
||||||
|
<BLANKLINE>
|
||||||
|
ZeroDivisionError: ...
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def doctest_tb_verbose():
|
||||||
|
"""
|
||||||
|
In [5]: xmode verbose
|
||||||
|
Exception reporting mode: Verbose
|
||||||
|
|
||||||
|
In [6]: run simpleerr.py
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
ZeroDivisionError Traceback (most recent call last)
|
||||||
|
<BLANKLINE>
|
||||||
|
... in <module>()
|
||||||
|
30 mode = 'div'
|
||||||
|
31
|
||||||
|
---> 32 bar(mode)
|
||||||
|
global bar = <function bar at ...>
|
||||||
|
global mode = 'div'
|
||||||
|
<BLANKLINE>
|
||||||
|
... in bar(mode='div')
|
||||||
|
14 "bar"
|
||||||
|
15 if mode=='div':
|
||||||
|
---> 16 div0()
|
||||||
|
global div0 = <function div0 at ...>
|
||||||
|
17 elif mode=='exit':
|
||||||
|
18 try:
|
||||||
|
<BLANKLINE>
|
||||||
|
... in div0()
|
||||||
|
6 x = 1
|
||||||
|
7 y = 0
|
||||||
|
----> 8 x/y
|
||||||
|
x = 1
|
||||||
|
y = 0
|
||||||
|
9
|
||||||
|
10 def sysexit(stat, mode):
|
||||||
|
<BLANKLINE>
|
||||||
|
ZeroDivisionError: ...
|
||||||
|
"""
|
||||||
|
|
||||||
|
def doctest_tb_sysexit():
|
||||||
|
"""
|
||||||
|
In [17]: %xmode plain
|
||||||
|
Exception reporting mode: Plain
|
||||||
|
|
||||||
|
In [18]: %run simpleerr.py exit
|
||||||
|
An exception has occurred, use %tb to see the full traceback.
|
||||||
|
SystemExit: (1, 'Mode = exit')
|
||||||
|
|
||||||
|
In [19]: %run simpleerr.py exit 2
|
||||||
|
An exception has occurred, use %tb to see the full traceback.
|
||||||
|
SystemExit: (2, 'Mode = exit')
|
||||||
|
|
||||||
|
In [20]: %tb
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File ... in <module>
|
||||||
|
bar(mode)
|
||||||
|
File ... line 22, in bar
|
||||||
|
sysexit(stat, mode)
|
||||||
|
File ... line 11, in sysexit
|
||||||
|
raise SystemExit(stat, 'Mode = %s' % mode)
|
||||||
|
SystemExit: (2, 'Mode = exit')
|
||||||
|
|
||||||
|
In [21]: %xmode context
|
||||||
|
Exception reporting mode: Context
|
||||||
|
|
||||||
|
In [22]: %tb
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
SystemExit Traceback (most recent call last)
|
||||||
|
<BLANKLINE>
|
||||||
|
...<module>()
|
||||||
|
30 mode = 'div'
|
||||||
|
31
|
||||||
|
---> 32 bar(mode)
|
||||||
|
<BLANKLINE>
|
||||||
|
...bar(mode)
|
||||||
|
20 except:
|
||||||
|
21 stat = 1
|
||||||
|
---> 22 sysexit(stat, mode)
|
||||||
|
23 else:
|
||||||
|
24 raise ValueError('Unknown mode')
|
||||||
|
<BLANKLINE>
|
||||||
|
...sysexit(stat, mode)
|
||||||
|
9
|
||||||
|
10 def sysexit(stat, mode):
|
||||||
|
---> 11 raise SystemExit(stat, 'Mode = %s' % mode)
|
||||||
|
12
|
||||||
|
13 def bar(mode):
|
||||||
|
<BLANKLINE>
|
||||||
|
SystemExit: (2, 'Mode = exit')
|
||||||
|
|
||||||
|
In [23]: %xmode verbose
|
||||||
|
Exception reporting mode: Verbose
|
||||||
|
|
||||||
|
In [24]: %tb
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
SystemExit Traceback (most recent call last)
|
||||||
|
<BLANKLINE>
|
||||||
|
... in <module>()
|
||||||
|
30 mode = 'div'
|
||||||
|
31
|
||||||
|
---> 32 bar(mode)
|
||||||
|
global bar = <function bar at ...>
|
||||||
|
global mode = 'exit'
|
||||||
|
<BLANKLINE>
|
||||||
|
... in bar(mode='exit')
|
||||||
|
20 except:
|
||||||
|
21 stat = 1
|
||||||
|
---> 22 sysexit(stat, mode)
|
||||||
|
global sysexit = <function sysexit at ...>
|
||||||
|
stat = 2
|
||||||
|
mode = 'exit'
|
||||||
|
23 else:
|
||||||
|
24 raise ValueError('Unknown mode')
|
||||||
|
<BLANKLINE>
|
||||||
|
... in sysexit(stat=2, mode='exit')
|
||||||
|
9
|
||||||
|
10 def sysexit(stat, mode):
|
||||||
|
---> 11 raise SystemExit(stat, 'Mode = %s' % mode)
|
||||||
|
global SystemExit = undefined
|
||||||
|
stat = 2
|
||||||
|
mode = 'exit'
|
||||||
|
12
|
||||||
|
13 def bar(mode):
|
||||||
|
<BLANKLINE>
|
||||||
|
SystemExit: (2, 'Mode = exit')
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_cell():
|
||||||
|
import textwrap
|
||||||
|
ip.run_cell('a = 10\na+=1')
|
||||||
|
ip.run_cell('assert a == 11\nassert 1')
|
||||||
|
|
||||||
|
nt.assert_equal(ip.user_ns['a'], 11)
|
||||||
|
complex = textwrap.dedent("""
|
||||||
|
if 1:
|
||||||
|
print "hello"
|
||||||
|
if 1:
|
||||||
|
print "world"
|
||||||
|
|
||||||
|
if 2:
|
||||||
|
print "foo"
|
||||||
|
|
||||||
|
if 3:
|
||||||
|
print "bar"
|
||||||
|
|
||||||
|
if 4:
|
||||||
|
print "bar"
|
||||||
|
|
||||||
|
""")
|
||||||
|
# Simply verifies that this kind of input is run
|
||||||
|
ip.run_cell(complex)
|
||||||
|
|
||||||
|
|
||||||
|
def test_db():
|
||||||
|
"""Test the internal database used for variable persistence."""
|
||||||
|
ip.db['__unittest_'] = 12
|
||||||
|
nt.assert_equal(ip.db['__unittest_'], 12)
|
||||||
|
del ip.db['__unittest_']
|
||||||
|
assert '__unittest_' not in ip.db
|
@ -0,0 +1,32 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Test yap_ipython.core.logger"""
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
from yap_ipython.utils.tempdir import TemporaryDirectory
|
||||||
|
|
||||||
|
_ip = get_ipython()
|
||||||
|
|
||||||
|
def test_logstart_inaccessible_file():
|
||||||
|
try:
|
||||||
|
_ip.logger.logstart(logfname="/") # Opening that filename will fail.
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
nt.assert_true(False) # The try block should never pass.
|
||||||
|
|
||||||
|
try:
|
||||||
|
_ip.run_cell("a=1") # Check it doesn't try to log this
|
||||||
|
finally:
|
||||||
|
_ip.logger.log_active = False # If this fails, don't let later tests fail
|
||||||
|
|
||||||
|
def test_logstart_unicode():
|
||||||
|
with TemporaryDirectory() as tdir:
|
||||||
|
logfname = os.path.join(tdir, "test_unicode.log")
|
||||||
|
_ip.run_cell("'abc€'")
|
||||||
|
try:
|
||||||
|
_ip.magic("logstart -to %s" % logfname)
|
||||||
|
_ip.run_cell("'abc€'")
|
||||||
|
finally:
|
||||||
|
_ip.logger.logstop()
|
1073
packages/python/yap_kernel/yap_ipython/core/tests/test_magic.py
Normal file
1073
packages/python/yap_kernel/yap_ipython/core/tests/test_magic.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,118 @@
|
|||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2010-2011, yap_ipython Development Team.
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
#
|
||||||
|
# The full license is in the file COPYING.txt, distributed with this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from nose.tools import assert_equal
|
||||||
|
|
||||||
|
from yap_ipython.core.magic_arguments import (argument, argument_group, kwds,
|
||||||
|
magic_arguments, parse_argstring, real_name)
|
||||||
|
|
||||||
|
|
||||||
|
@magic_arguments()
|
||||||
|
@argument('-f', '--foo', help="an argument")
|
||||||
|
def magic_foo1(self, args):
|
||||||
|
""" A docstring.
|
||||||
|
"""
|
||||||
|
return parse_argstring(magic_foo1, args)
|
||||||
|
|
||||||
|
|
||||||
|
@magic_arguments()
|
||||||
|
def magic_foo2(self, args):
|
||||||
|
""" A docstring.
|
||||||
|
"""
|
||||||
|
return parse_argstring(magic_foo2, args)
|
||||||
|
|
||||||
|
|
||||||
|
@magic_arguments()
|
||||||
|
@argument('-f', '--foo', help="an argument")
|
||||||
|
@argument_group('Group')
|
||||||
|
@argument('-b', '--bar', help="a grouped argument")
|
||||||
|
@argument_group('Second Group')
|
||||||
|
@argument('-z', '--baz', help="another grouped argument")
|
||||||
|
def magic_foo3(self, args):
|
||||||
|
""" A docstring.
|
||||||
|
"""
|
||||||
|
return parse_argstring(magic_foo3, args)
|
||||||
|
|
||||||
|
|
||||||
|
@magic_arguments()
|
||||||
|
@kwds(argument_default=argparse.SUPPRESS)
|
||||||
|
@argument('-f', '--foo', help="an argument")
|
||||||
|
def magic_foo4(self, args):
|
||||||
|
""" A docstring.
|
||||||
|
"""
|
||||||
|
return parse_argstring(magic_foo4, args)
|
||||||
|
|
||||||
|
|
||||||
|
@magic_arguments('frobnicate')
|
||||||
|
@argument('-f', '--foo', help="an argument")
|
||||||
|
def magic_foo5(self, args):
|
||||||
|
""" A docstring.
|
||||||
|
"""
|
||||||
|
return parse_argstring(magic_foo5, args)
|
||||||
|
|
||||||
|
|
||||||
|
@magic_arguments()
|
||||||
|
@argument('-f', '--foo', help="an argument")
|
||||||
|
def magic_magic_foo(self, args):
|
||||||
|
""" A docstring.
|
||||||
|
"""
|
||||||
|
return parse_argstring(magic_magic_foo, args)
|
||||||
|
|
||||||
|
|
||||||
|
@magic_arguments()
|
||||||
|
@argument('-f', '--foo', help="an argument")
|
||||||
|
def foo(self, args):
|
||||||
|
""" A docstring.
|
||||||
|
"""
|
||||||
|
return parse_argstring(foo, args)
|
||||||
|
|
||||||
|
|
||||||
|
def test_magic_arguments():
|
||||||
|
assert_equal(magic_foo1.__doc__, '::\n\n %foo1 [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
|
||||||
|
assert_equal(getattr(magic_foo1, 'argcmd_name', None), None)
|
||||||
|
assert_equal(real_name(magic_foo1), 'foo1')
|
||||||
|
assert_equal(magic_foo1(None, ''), argparse.Namespace(foo=None))
|
||||||
|
assert hasattr(magic_foo1, 'has_arguments')
|
||||||
|
|
||||||
|
assert_equal(magic_foo2.__doc__, '::\n\n %foo2\n\n A docstring.\n')
|
||||||
|
assert_equal(getattr(magic_foo2, 'argcmd_name', None), None)
|
||||||
|
assert_equal(real_name(magic_foo2), 'foo2')
|
||||||
|
assert_equal(magic_foo2(None, ''), argparse.Namespace())
|
||||||
|
assert hasattr(magic_foo2, 'has_arguments')
|
||||||
|
|
||||||
|
assert_equal(magic_foo3.__doc__, '::\n\n %foo3 [-f FOO] [-b BAR] [-z BAZ]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n\nGroup:\n -b BAR, --bar BAR a grouped argument\n\nSecond Group:\n -z BAZ, --baz BAZ another grouped argument\n')
|
||||||
|
assert_equal(getattr(magic_foo3, 'argcmd_name', None), None)
|
||||||
|
assert_equal(real_name(magic_foo3), 'foo3')
|
||||||
|
assert_equal(magic_foo3(None, ''),
|
||||||
|
argparse.Namespace(bar=None, baz=None, foo=None))
|
||||||
|
assert hasattr(magic_foo3, 'has_arguments')
|
||||||
|
|
||||||
|
assert_equal(magic_foo4.__doc__, '::\n\n %foo4 [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
|
||||||
|
assert_equal(getattr(magic_foo4, 'argcmd_name', None), None)
|
||||||
|
assert_equal(real_name(magic_foo4), 'foo4')
|
||||||
|
assert_equal(magic_foo4(None, ''), argparse.Namespace())
|
||||||
|
assert hasattr(magic_foo4, 'has_arguments')
|
||||||
|
|
||||||
|
assert_equal(magic_foo5.__doc__, '::\n\n %frobnicate [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
|
||||||
|
assert_equal(getattr(magic_foo5, 'argcmd_name', None), 'frobnicate')
|
||||||
|
assert_equal(real_name(magic_foo5), 'frobnicate')
|
||||||
|
assert_equal(magic_foo5(None, ''), argparse.Namespace(foo=None))
|
||||||
|
assert hasattr(magic_foo5, 'has_arguments')
|
||||||
|
|
||||||
|
assert_equal(magic_magic_foo.__doc__, '::\n\n %magic_foo [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
|
||||||
|
assert_equal(getattr(magic_magic_foo, 'argcmd_name', None), None)
|
||||||
|
assert_equal(real_name(magic_magic_foo), 'magic_foo')
|
||||||
|
assert_equal(magic_magic_foo(None, ''), argparse.Namespace(foo=None))
|
||||||
|
assert hasattr(magic_magic_foo, 'has_arguments')
|
||||||
|
|
||||||
|
assert_equal(foo.__doc__, '::\n\n %foo [-f FOO]\n\n A docstring.\n\noptional arguments:\n -f FOO, --foo FOO an argument\n')
|
||||||
|
assert_equal(getattr(foo, 'argcmd_name', None), None)
|
||||||
|
assert_equal(real_name(foo), 'foo')
|
||||||
|
assert_equal(foo(None, ''), argparse.Namespace(foo=None))
|
||||||
|
assert hasattr(foo, 'has_arguments')
|
@ -0,0 +1,197 @@
|
|||||||
|
"""Tests for various magic functions specific to the terminal frontend.
|
||||||
|
|
||||||
|
Needs to be run by nose (to make ipython session available).
|
||||||
|
"""
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from io import StringIO
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython.testing import tools as tt
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Globals
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
ip = get_ipython()
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Test functions begin
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def check_cpaste(code, should_fail=False):
|
||||||
|
"""Execute code via 'cpaste' and ensure it was executed, unless
|
||||||
|
should_fail is set.
|
||||||
|
"""
|
||||||
|
ip.user_ns['code_ran'] = False
|
||||||
|
|
||||||
|
src = StringIO()
|
||||||
|
if not hasattr(src, 'encoding'):
|
||||||
|
# yap_ipython expects stdin to have an encoding attribute
|
||||||
|
src.encoding = None
|
||||||
|
src.write(code)
|
||||||
|
src.write('\n--\n')
|
||||||
|
src.seek(0)
|
||||||
|
|
||||||
|
stdin_save = sys.stdin
|
||||||
|
sys.stdin = src
|
||||||
|
|
||||||
|
try:
|
||||||
|
context = tt.AssertPrints if should_fail else tt.AssertNotPrints
|
||||||
|
with context("Traceback (most recent call last)"):
|
||||||
|
ip.magic('cpaste')
|
||||||
|
|
||||||
|
if not should_fail:
|
||||||
|
assert ip.user_ns['code_ran'], "%r failed" % code
|
||||||
|
finally:
|
||||||
|
sys.stdin = stdin_save
|
||||||
|
|
||||||
|
def test_cpaste():
|
||||||
|
"""Test cpaste magic"""
|
||||||
|
|
||||||
|
def runf():
|
||||||
|
"""Marker function: sets a flag when executed.
|
||||||
|
"""
|
||||||
|
ip.user_ns['code_ran'] = True
|
||||||
|
return 'runf' # return string so '+ runf()' doesn't result in success
|
||||||
|
|
||||||
|
tests = {'pass': ["runf()",
|
||||||
|
"In [1]: runf()",
|
||||||
|
"In [1]: if 1:\n ...: runf()",
|
||||||
|
"> > > runf()",
|
||||||
|
">>> runf()",
|
||||||
|
" >>> runf()",
|
||||||
|
],
|
||||||
|
|
||||||
|
'fail': ["1 + runf()",
|
||||||
|
"++ runf()",
|
||||||
|
]}
|
||||||
|
|
||||||
|
ip.user_ns['runf'] = runf
|
||||||
|
|
||||||
|
for code in tests['pass']:
|
||||||
|
check_cpaste(code)
|
||||||
|
|
||||||
|
for code in tests['fail']:
|
||||||
|
check_cpaste(code, should_fail=True)
|
||||||
|
|
||||||
|
|
||||||
|
class PasteTestCase(TestCase):
|
||||||
|
"""Multiple tests for clipboard pasting"""
|
||||||
|
|
||||||
|
def paste(self, txt, flags='-q'):
|
||||||
|
"""Paste input text, by default in quiet mode"""
|
||||||
|
ip.hooks.clipboard_get = lambda : txt
|
||||||
|
ip.magic('paste '+flags)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# Inject fake clipboard hook but save original so we can restore it later
|
||||||
|
self.original_clip = ip.hooks.clipboard_get
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
# Restore original hook
|
||||||
|
ip.hooks.clipboard_get = self.original_clip
|
||||||
|
|
||||||
|
def test_paste(self):
|
||||||
|
ip.user_ns.pop('x', None)
|
||||||
|
self.paste('x = 1')
|
||||||
|
nt.assert_equal(ip.user_ns['x'], 1)
|
||||||
|
ip.user_ns.pop('x')
|
||||||
|
|
||||||
|
def test_paste_pyprompt(self):
|
||||||
|
ip.user_ns.pop('x', None)
|
||||||
|
self.paste('>>> x=2')
|
||||||
|
nt.assert_equal(ip.user_ns['x'], 2)
|
||||||
|
ip.user_ns.pop('x')
|
||||||
|
|
||||||
|
def test_paste_py_multi(self):
|
||||||
|
self.paste("""
|
||||||
|
>>> x = [1,2,3]
|
||||||
|
>>> y = []
|
||||||
|
>>> for i in x:
|
||||||
|
... y.append(i**2)
|
||||||
|
...
|
||||||
|
""")
|
||||||
|
nt.assert_equal(ip.user_ns['x'], [1,2,3])
|
||||||
|
nt.assert_equal(ip.user_ns['y'], [1,4,9])
|
||||||
|
|
||||||
|
def test_paste_py_multi_r(self):
|
||||||
|
"Now, test that self.paste -r works"
|
||||||
|
self.test_paste_py_multi()
|
||||||
|
nt.assert_equal(ip.user_ns.pop('x'), [1,2,3])
|
||||||
|
nt.assert_equal(ip.user_ns.pop('y'), [1,4,9])
|
||||||
|
nt.assert_false('x' in ip.user_ns)
|
||||||
|
ip.magic('paste -r')
|
||||||
|
nt.assert_equal(ip.user_ns['x'], [1,2,3])
|
||||||
|
nt.assert_equal(ip.user_ns['y'], [1,4,9])
|
||||||
|
|
||||||
|
def test_paste_email(self):
|
||||||
|
"Test pasting of email-quoted contents"
|
||||||
|
self.paste("""\
|
||||||
|
>> def foo(x):
|
||||||
|
>> return x + 1
|
||||||
|
>> xx = foo(1.1)""")
|
||||||
|
nt.assert_equal(ip.user_ns['xx'], 2.1)
|
||||||
|
|
||||||
|
def test_paste_email2(self):
|
||||||
|
"Email again; some programs add a space also at each quoting level"
|
||||||
|
self.paste("""\
|
||||||
|
> > def foo(x):
|
||||||
|
> > return x + 1
|
||||||
|
> > yy = foo(2.1) """)
|
||||||
|
nt.assert_equal(ip.user_ns['yy'], 3.1)
|
||||||
|
|
||||||
|
def test_paste_email_py(self):
|
||||||
|
"Email quoting of interactive input"
|
||||||
|
self.paste("""\
|
||||||
|
>> >>> def f(x):
|
||||||
|
>> ... return x+1
|
||||||
|
>> ...
|
||||||
|
>> >>> zz = f(2.5) """)
|
||||||
|
nt.assert_equal(ip.user_ns['zz'], 3.5)
|
||||||
|
|
||||||
|
def test_paste_echo(self):
|
||||||
|
"Also test self.paste echoing, by temporarily faking the writer"
|
||||||
|
w = StringIO()
|
||||||
|
writer = ip.write
|
||||||
|
ip.write = w.write
|
||||||
|
code = """
|
||||||
|
a = 100
|
||||||
|
b = 200"""
|
||||||
|
try:
|
||||||
|
self.paste(code,'')
|
||||||
|
out = w.getvalue()
|
||||||
|
finally:
|
||||||
|
ip.write = writer
|
||||||
|
nt.assert_equal(ip.user_ns['a'], 100)
|
||||||
|
nt.assert_equal(ip.user_ns['b'], 200)
|
||||||
|
nt.assert_equal(out, code+"\n## -- End pasted text --\n")
|
||||||
|
|
||||||
|
def test_paste_leading_commas(self):
|
||||||
|
"Test multiline strings with leading commas"
|
||||||
|
tm = ip.magics_manager.registry['TerminalMagics']
|
||||||
|
s = '''\
|
||||||
|
a = """
|
||||||
|
,1,2,3
|
||||||
|
"""'''
|
||||||
|
ip.user_ns.pop('foo', None)
|
||||||
|
tm.store_or_execute(s, 'foo')
|
||||||
|
nt.assert_in('foo', ip.user_ns)
|
||||||
|
|
||||||
|
|
||||||
|
def test_paste_trailing_question(self):
|
||||||
|
"Test pasting sources with trailing question marks"
|
||||||
|
tm = ip.magics_manager.registry['TerminalMagics']
|
||||||
|
s = '''\
|
||||||
|
def funcfoo():
|
||||||
|
if True: #am i true?
|
||||||
|
return 'fooresult'
|
||||||
|
'''
|
||||||
|
ip.user_ns.pop('funcfoo', None)
|
||||||
|
self.paste(s)
|
||||||
|
nt.assert_equal(ip.user_ns['funcfoo'](), 'fooresult')
|
@ -0,0 +1,437 @@
|
|||||||
|
"""Tests for the object inspection functionality.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Copyright (c) yap_ipython Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
|
||||||
|
from inspect import Signature, Parameter
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from .. import oinspect
|
||||||
|
from yap_ipython.core.magic import (Magics, magics_class, line_magic,
|
||||||
|
cell_magic, line_cell_magic,
|
||||||
|
register_line_magic, register_cell_magic,
|
||||||
|
register_line_cell_magic)
|
||||||
|
from decorator import decorator
|
||||||
|
from yap_ipython import get_ipython
|
||||||
|
from yap_ipython.testing.tools import AssertPrints, AssertNotPrints
|
||||||
|
from yap_ipython.utils.path import compress_user
|
||||||
|
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Globals and constants
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
inspector = oinspect.Inspector()
|
||||||
|
ip = get_ipython()
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Local utilities
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# WARNING: since this test checks the line number where a function is
|
||||||
|
# defined, if any code is inserted above, the following line will need to be
|
||||||
|
# updated. Do NOT insert any whitespace between the next line and the function
|
||||||
|
# definition below.
|
||||||
|
THIS_LINE_NUMBER = 41 # Put here the actual number of this line
|
||||||
|
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
class Test(TestCase):
|
||||||
|
|
||||||
|
def test_find_source_lines(self):
|
||||||
|
self.assertEqual(oinspect.find_source_lines(Test.test_find_source_lines),
|
||||||
|
THIS_LINE_NUMBER+6)
|
||||||
|
|
||||||
|
|
||||||
|
# A couple of utilities to ensure these tests work the same from a source or a
|
||||||
|
# binary install
|
||||||
|
def pyfile(fname):
|
||||||
|
return os.path.normcase(re.sub('.py[co]$', '.py', fname))
|
||||||
|
|
||||||
|
|
||||||
|
def match_pyfiles(f1, f2):
|
||||||
|
nt.assert_equal(pyfile(f1), pyfile(f2))
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_file():
|
||||||
|
match_pyfiles(oinspect.find_file(test_find_file), os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_file_decorated1():
|
||||||
|
|
||||||
|
@decorator
|
||||||
|
def noop1(f):
|
||||||
|
def wrapper(*a, **kw):
|
||||||
|
return f(*a, **kw)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
@noop1
|
||||||
|
def f(x):
|
||||||
|
"My docstring"
|
||||||
|
|
||||||
|
match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
|
||||||
|
nt.assert_equal(f.__doc__, "My docstring")
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_file_decorated2():
|
||||||
|
|
||||||
|
@decorator
|
||||||
|
def noop2(f, *a, **kw):
|
||||||
|
return f(*a, **kw)
|
||||||
|
|
||||||
|
@noop2
|
||||||
|
@noop2
|
||||||
|
@noop2
|
||||||
|
def f(x):
|
||||||
|
"My docstring 2"
|
||||||
|
|
||||||
|
match_pyfiles(oinspect.find_file(f), os.path.abspath(__file__))
|
||||||
|
nt.assert_equal(f.__doc__, "My docstring 2")
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_file_magic():
|
||||||
|
run = ip.find_line_magic('run')
|
||||||
|
nt.assert_not_equal(oinspect.find_file(run), None)
|
||||||
|
|
||||||
|
|
||||||
|
# A few generic objects we can then inspect in the tests below
|
||||||
|
|
||||||
|
class Call(object):
|
||||||
|
"""This is the class docstring."""
|
||||||
|
|
||||||
|
def __init__(self, x, y=1):
|
||||||
|
"""This is the constructor docstring."""
|
||||||
|
|
||||||
|
def __call__(self, *a, **kw):
|
||||||
|
"""This is the call docstring."""
|
||||||
|
|
||||||
|
def method(self, x, z=2):
|
||||||
|
"""Some method's docstring"""
|
||||||
|
|
||||||
|
class HasSignature(object):
|
||||||
|
"""This is the class docstring."""
|
||||||
|
__signature__ = Signature([Parameter('test', Parameter.POSITIONAL_OR_KEYWORD)])
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""This is the init docstring"""
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleClass(object):
|
||||||
|
def method(self, x, z=2):
|
||||||
|
"""Some method's docstring"""
|
||||||
|
|
||||||
|
|
||||||
|
class OldStyle:
|
||||||
|
"""An old-style class for testing."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def f(x, y=2, *a, **kw):
|
||||||
|
"""A simple function."""
|
||||||
|
|
||||||
|
|
||||||
|
def g(y, z=3, *a, **kw):
|
||||||
|
pass # no docstring
|
||||||
|
|
||||||
|
|
||||||
|
@register_line_magic
|
||||||
|
def lmagic(line):
|
||||||
|
"A line magic"
|
||||||
|
|
||||||
|
|
||||||
|
@register_cell_magic
|
||||||
|
def cmagic(line, cell):
|
||||||
|
"A cell magic"
|
||||||
|
|
||||||
|
|
||||||
|
@register_line_cell_magic
|
||||||
|
def lcmagic(line, cell=None):
|
||||||
|
"A line/cell magic"
|
||||||
|
|
||||||
|
|
||||||
|
@magics_class
|
||||||
|
class SimpleMagics(Magics):
|
||||||
|
@line_magic
|
||||||
|
def Clmagic(self, cline):
|
||||||
|
"A class-based line magic"
|
||||||
|
|
||||||
|
@cell_magic
|
||||||
|
def Ccmagic(self, cline, ccell):
|
||||||
|
"A class-based cell magic"
|
||||||
|
|
||||||
|
@line_cell_magic
|
||||||
|
def Clcmagic(self, cline, ccell=None):
|
||||||
|
"A class-based line/cell magic"
|
||||||
|
|
||||||
|
|
||||||
|
class Awkward(object):
|
||||||
|
def __getattr__(self, name):
|
||||||
|
raise Exception(name)
|
||||||
|
|
||||||
|
class NoBoolCall:
|
||||||
|
"""
|
||||||
|
callable with `__bool__` raising should still be inspect-able.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
"""does nothing"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
"""just raise NotImplemented"""
|
||||||
|
raise NotImplementedError('Must be implemented')
|
||||||
|
|
||||||
|
|
||||||
|
class SerialLiar(object):
|
||||||
|
"""Attribute accesses always get another copy of the same class.
|
||||||
|
|
||||||
|
unittest.mock.call does something similar, but it's not ideal for testing
|
||||||
|
as the failure mode is to eat all your RAM. This gives up after 10k levels.
|
||||||
|
"""
|
||||||
|
def __init__(self, max_fibbing_twig, lies_told=0):
|
||||||
|
if lies_told > 10000:
|
||||||
|
raise RuntimeError('Nose too long, honesty is the best policy')
|
||||||
|
self.max_fibbing_twig = max_fibbing_twig
|
||||||
|
self.lies_told = lies_told
|
||||||
|
max_fibbing_twig[0] = max(max_fibbing_twig[0], lies_told)
|
||||||
|
|
||||||
|
def __getattr__(self, item):
|
||||||
|
return SerialLiar(self.max_fibbing_twig, self.lies_told + 1)
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Tests
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_info():
|
||||||
|
"Check that Inspector.info fills out various fields as expected."
|
||||||
|
i = inspector.info(Call, oname='Call')
|
||||||
|
nt.assert_equal(i['type_name'], 'type')
|
||||||
|
expted_class = str(type(type)) # <class 'type'> (Python 3) or <type 'type'>
|
||||||
|
nt.assert_equal(i['base_class'], expted_class)
|
||||||
|
nt.assert_regex(i['string_form'], "<class 'yap_ipython.core.tests.test_oinspect.Call'( at 0x[0-9a-f]{1,9})?>")
|
||||||
|
fname = __file__
|
||||||
|
if fname.endswith(".pyc"):
|
||||||
|
fname = fname[:-1]
|
||||||
|
# case-insensitive comparison needed on some filesystems
|
||||||
|
# e.g. Windows:
|
||||||
|
nt.assert_equal(i['file'].lower(), compress_user(fname).lower())
|
||||||
|
nt.assert_equal(i['definition'], None)
|
||||||
|
nt.assert_equal(i['docstring'], Call.__doc__)
|
||||||
|
nt.assert_equal(i['source'], None)
|
||||||
|
nt.assert_true(i['isclass'])
|
||||||
|
nt.assert_equal(i['init_definition'], "Call(x, y=1)")
|
||||||
|
nt.assert_equal(i['init_docstring'], Call.__init__.__doc__)
|
||||||
|
|
||||||
|
i = inspector.info(Call, detail_level=1)
|
||||||
|
nt.assert_not_equal(i['source'], None)
|
||||||
|
nt.assert_equal(i['docstring'], None)
|
||||||
|
|
||||||
|
c = Call(1)
|
||||||
|
c.__doc__ = "Modified instance docstring"
|
||||||
|
i = inspector.info(c)
|
||||||
|
nt.assert_equal(i['type_name'], 'Call')
|
||||||
|
nt.assert_equal(i['docstring'], "Modified instance docstring")
|
||||||
|
nt.assert_equal(i['class_docstring'], Call.__doc__)
|
||||||
|
nt.assert_equal(i['init_docstring'], Call.__init__.__doc__)
|
||||||
|
nt.assert_equal(i['call_docstring'], Call.__call__.__doc__)
|
||||||
|
|
||||||
|
def test_class_signature():
|
||||||
|
info = inspector.info(HasSignature, 'HasSignature')
|
||||||
|
nt.assert_equal(info['init_definition'], "HasSignature(test)")
|
||||||
|
nt.assert_equal(info['init_docstring'], HasSignature.__init__.__doc__)
|
||||||
|
|
||||||
|
def test_info_awkward():
|
||||||
|
# Just test that this doesn't throw an error.
|
||||||
|
inspector.info(Awkward())
|
||||||
|
|
||||||
|
def test_bool_raise():
|
||||||
|
inspector.info(NoBoolCall())
|
||||||
|
|
||||||
|
def test_info_serialliar():
|
||||||
|
fib_tracker = [0]
|
||||||
|
inspector.info(SerialLiar(fib_tracker))
|
||||||
|
|
||||||
|
# Nested attribute access should be cut off at 100 levels deep to avoid
|
||||||
|
# infinite loops: https://github.com/ipython/ipython/issues/9122
|
||||||
|
nt.assert_less(fib_tracker[0], 9000)
|
||||||
|
|
||||||
|
def test_calldef_none():
|
||||||
|
# We should ignore __call__ for all of these.
|
||||||
|
for obj in [f, SimpleClass().method, any, str.upper]:
|
||||||
|
print(obj)
|
||||||
|
i = inspector.info(obj)
|
||||||
|
nt.assert_is(i['call_def'], None)
|
||||||
|
|
||||||
|
def f_kwarg(pos, *, kwonly):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_definition_kwonlyargs():
|
||||||
|
i = inspector.info(f_kwarg, oname='f_kwarg') # analysis:ignore
|
||||||
|
nt.assert_equal(i['definition'], "f_kwarg(pos, *, kwonly)")
|
||||||
|
|
||||||
|
def test_getdoc():
|
||||||
|
class A(object):
|
||||||
|
"""standard docstring"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class B(object):
|
||||||
|
"""standard docstring"""
|
||||||
|
def getdoc(self):
|
||||||
|
return "custom docstring"
|
||||||
|
|
||||||
|
class C(object):
|
||||||
|
"""standard docstring"""
|
||||||
|
def getdoc(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
a = A()
|
||||||
|
b = B()
|
||||||
|
c = C()
|
||||||
|
|
||||||
|
nt.assert_equal(oinspect.getdoc(a), "standard docstring")
|
||||||
|
nt.assert_equal(oinspect.getdoc(b), "custom docstring")
|
||||||
|
nt.assert_equal(oinspect.getdoc(c), "standard docstring")
|
||||||
|
|
||||||
|
|
||||||
|
def test_empty_property_has_no_source():
|
||||||
|
i = inspector.info(property(), detail_level=1)
|
||||||
|
nt.assert_is(i['source'], None)
|
||||||
|
|
||||||
|
|
||||||
|
def test_property_sources():
|
||||||
|
import posixpath
|
||||||
|
# A simple adder whose source and signature stays
|
||||||
|
# the same across Python distributions
|
||||||
|
def simple_add(a, b):
|
||||||
|
"Adds two numbers"
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
class A(object):
|
||||||
|
@property
|
||||||
|
def foo(self):
|
||||||
|
return 'bar'
|
||||||
|
|
||||||
|
foo = foo.setter(lambda self, v: setattr(self, 'bar', v))
|
||||||
|
|
||||||
|
dname = property(posixpath.dirname)
|
||||||
|
adder = property(simple_add)
|
||||||
|
|
||||||
|
i = inspector.info(A.foo, detail_level=1)
|
||||||
|
nt.assert_in('def foo(self):', i['source'])
|
||||||
|
nt.assert_in('lambda self, v:', i['source'])
|
||||||
|
|
||||||
|
i = inspector.info(A.dname, detail_level=1)
|
||||||
|
nt.assert_in('def dirname(p)', i['source'])
|
||||||
|
|
||||||
|
i = inspector.info(A.adder, detail_level=1)
|
||||||
|
nt.assert_in('def simple_add(a, b)', i['source'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_property_docstring_is_in_info_for_detail_level_0():
|
||||||
|
class A(object):
|
||||||
|
@property
|
||||||
|
def foobar(self):
|
||||||
|
"""This is `foobar` property."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
ip.user_ns['a_obj'] = A()
|
||||||
|
nt.assert_equal(
|
||||||
|
'This is `foobar` property.',
|
||||||
|
ip.object_inspect('a_obj.foobar', detail_level=0)['docstring'])
|
||||||
|
|
||||||
|
ip.user_ns['a_cls'] = A
|
||||||
|
nt.assert_equal(
|
||||||
|
'This is `foobar` property.',
|
||||||
|
ip.object_inspect('a_cls.foobar', detail_level=0)['docstring'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_pdef():
|
||||||
|
# See gh-1914
|
||||||
|
def foo(): pass
|
||||||
|
inspector.pdef(foo, 'foo')
|
||||||
|
|
||||||
|
|
||||||
|
def test_pinfo_nonascii():
|
||||||
|
# See gh-1177
|
||||||
|
from . import nonascii2
|
||||||
|
ip.user_ns['nonascii2'] = nonascii2
|
||||||
|
ip._inspect('pinfo', 'nonascii2', detail_level=1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pinfo_docstring_no_source():
|
||||||
|
"""Docstring should be included with detail_level=1 if there is no source"""
|
||||||
|
with AssertPrints('Docstring:'):
|
||||||
|
ip._inspect('pinfo', 'str.format', detail_level=0)
|
||||||
|
with AssertPrints('Docstring:'):
|
||||||
|
ip._inspect('pinfo', 'str.format', detail_level=1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pinfo_no_docstring_if_source():
|
||||||
|
"""Docstring should not be included with detail_level=1 if source is found"""
|
||||||
|
def foo():
|
||||||
|
"""foo has a docstring"""
|
||||||
|
|
||||||
|
ip.user_ns['foo'] = foo
|
||||||
|
|
||||||
|
with AssertPrints('Docstring:'):
|
||||||
|
ip._inspect('pinfo', 'foo', detail_level=0)
|
||||||
|
with AssertPrints('Source:'):
|
||||||
|
ip._inspect('pinfo', 'foo', detail_level=1)
|
||||||
|
with AssertNotPrints('Docstring:'):
|
||||||
|
ip._inspect('pinfo', 'foo', detail_level=1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pinfo_docstring_if_detail_and_no_source():
|
||||||
|
""" Docstring should be displayed if source info not available """
|
||||||
|
obj_def = '''class Foo(object):
|
||||||
|
""" This is a docstring for Foo """
|
||||||
|
def bar(self):
|
||||||
|
""" This is a docstring for Foo.bar """
|
||||||
|
pass
|
||||||
|
'''
|
||||||
|
|
||||||
|
ip.run_cell(obj_def)
|
||||||
|
ip.run_cell('foo = Foo()')
|
||||||
|
|
||||||
|
with AssertNotPrints("Source:"):
|
||||||
|
with AssertPrints('Docstring:'):
|
||||||
|
ip._inspect('pinfo', 'foo', detail_level=0)
|
||||||
|
with AssertPrints('Docstring:'):
|
||||||
|
ip._inspect('pinfo', 'foo', detail_level=1)
|
||||||
|
with AssertPrints('Docstring:'):
|
||||||
|
ip._inspect('pinfo', 'foo.bar', detail_level=0)
|
||||||
|
|
||||||
|
with AssertNotPrints('Docstring:'):
|
||||||
|
with AssertPrints('Source:'):
|
||||||
|
ip._inspect('pinfo', 'foo.bar', detail_level=1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pinfo_magic():
|
||||||
|
with AssertPrints('Docstring:'):
|
||||||
|
ip._inspect('pinfo', 'lsmagic', detail_level=0)
|
||||||
|
|
||||||
|
with AssertPrints('Source:'):
|
||||||
|
ip._inspect('pinfo', 'lsmagic', detail_level=1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_init_colors():
|
||||||
|
# ensure colors are not present in signature info
|
||||||
|
info = inspector.info(HasSignature)
|
||||||
|
init_def = info['init_definition']
|
||||||
|
nt.assert_not_in('[0m', init_def)
|
||||||
|
|
||||||
|
|
||||||
|
def test_builtin_init():
|
||||||
|
info = inspector.info(list)
|
||||||
|
init_def = info['init_definition']
|
||||||
|
# Python < 3.4 can't get init definition from builtins,
|
||||||
|
# but still exercise the inspection in case of error-raising bugs.
|
||||||
|
if sys.version_info >= (3,4):
|
||||||
|
nt.assert_is_not_none(init_def)
|
||||||
|
|
@ -0,0 +1,20 @@
|
|||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2010-2011 The yap_ipython Development Team.
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the BSD License.
|
||||||
|
#
|
||||||
|
# The full license is in the file COPYING.txt, distributed with this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
import io
|
||||||
|
|
||||||
|
# N.B. For the test suite, page.page is overridden (see yap_ipython.testing.globalipapp)
|
||||||
|
from yap_ipython.core import page
|
||||||
|
|
||||||
|
def test_detect_screen_size():
|
||||||
|
"""Simple smoketest for page._detect_screen_size."""
|
||||||
|
try:
|
||||||
|
page._detect_screen_size(True, 25)
|
||||||
|
except (TypeError, io.UnsupportedOperation):
|
||||||
|
# This can happen in the test suite, because stdout may not have a
|
||||||
|
# fileno.
|
||||||
|
pass
|
200
packages/python/yap_kernel/yap_ipython/core/tests/test_paths.py
Normal file
200
packages/python/yap_kernel/yap_ipython/core/tests/test_paths.py
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
import errno
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import warnings
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
from testpath import modified_env, assert_isdir, assert_isfile
|
||||||
|
|
||||||
|
from yap_ipython import paths
|
||||||
|
from yap_ipython.testing.decorators import skip_win32
|
||||||
|
from yap_ipython.utils.tempdir import TemporaryDirectory
|
||||||
|
|
||||||
|
TMP_TEST_DIR = os.path.realpath(tempfile.mkdtemp())
|
||||||
|
HOME_TEST_DIR = os.path.join(TMP_TEST_DIR, "home_test_dir")
|
||||||
|
XDG_TEST_DIR = os.path.join(HOME_TEST_DIR, "xdg_test_dir")
|
||||||
|
XDG_CACHE_DIR = os.path.join(HOME_TEST_DIR, "xdg_cache_dir")
|
||||||
|
IP_TEST_DIR = os.path.join(HOME_TEST_DIR,'.ipython')
|
||||||
|
|
||||||
|
def setup():
|
||||||
|
"""Setup testenvironment for the module:
|
||||||
|
|
||||||
|
- Adds dummy home dir tree
|
||||||
|
"""
|
||||||
|
# Do not mask exceptions here. In particular, catching WindowsError is a
|
||||||
|
# problem because that exception is only defined on Windows...
|
||||||
|
os.makedirs(IP_TEST_DIR)
|
||||||
|
os.makedirs(os.path.join(XDG_TEST_DIR, 'ipython'))
|
||||||
|
os.makedirs(os.path.join(XDG_CACHE_DIR, 'ipython'))
|
||||||
|
|
||||||
|
|
||||||
|
def teardown():
|
||||||
|
"""Teardown testenvironment for the module:
|
||||||
|
|
||||||
|
- Remove dummy home dir tree
|
||||||
|
"""
|
||||||
|
# Note: we remove the parent test dir, which is the root of all test
|
||||||
|
# subdirs we may have created. Use shutil instead of os.removedirs, so
|
||||||
|
# that non-empty directories are all recursively removed.
|
||||||
|
shutil.rmtree(TMP_TEST_DIR)
|
||||||
|
|
||||||
|
def patch_get_home_dir(dirpath):
|
||||||
|
return patch.object(paths, 'get_home_dir', return_value=dirpath)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_ipython_dir_1():
|
||||||
|
"""test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions."""
|
||||||
|
env_ipdir = os.path.join("someplace", ".ipython")
|
||||||
|
with patch.object(paths, '_writable_dir', return_value=True), \
|
||||||
|
modified_env({'IPYTHONDIR': env_ipdir}):
|
||||||
|
ipdir = paths.get_ipython_dir()
|
||||||
|
|
||||||
|
nt.assert_equal(ipdir, env_ipdir)
|
||||||
|
|
||||||
|
def test_get_ipython_dir_2():
|
||||||
|
"""test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions."""
|
||||||
|
with patch_get_home_dir('someplace'), \
|
||||||
|
patch.object(paths, 'get_xdg_dir', return_value=None), \
|
||||||
|
patch.object(paths, '_writable_dir', return_value=True), \
|
||||||
|
patch('os.name', "posix"), \
|
||||||
|
modified_env({'IPYTHON_DIR': None,
|
||||||
|
'IPYTHONDIR': None,
|
||||||
|
'XDG_CONFIG_HOME': None
|
||||||
|
}):
|
||||||
|
ipdir = paths.get_ipython_dir()
|
||||||
|
|
||||||
|
nt.assert_equal(ipdir, os.path.join("someplace", ".ipython"))
|
||||||
|
|
||||||
|
def test_get_ipython_dir_3():
|
||||||
|
"""test_get_ipython_dir_3, move XDG if defined, and .ipython doesn't exist."""
|
||||||
|
tmphome = TemporaryDirectory()
|
||||||
|
try:
|
||||||
|
with patch_get_home_dir(tmphome.name), \
|
||||||
|
patch('os.name', 'posix'), \
|
||||||
|
modified_env({
|
||||||
|
'IPYTHON_DIR': None,
|
||||||
|
'IPYTHONDIR': None,
|
||||||
|
'XDG_CONFIG_HOME': XDG_TEST_DIR,
|
||||||
|
}), warnings.catch_warnings(record=True) as w:
|
||||||
|
ipdir = paths.get_ipython_dir()
|
||||||
|
|
||||||
|
nt.assert_equal(ipdir, os.path.join(tmphome.name, ".ipython"))
|
||||||
|
if sys.platform != 'darwin':
|
||||||
|
nt.assert_equal(len(w), 1)
|
||||||
|
nt.assert_in('Moving', str(w[0]))
|
||||||
|
finally:
|
||||||
|
tmphome.cleanup()
|
||||||
|
|
||||||
|
def test_get_ipython_dir_4():
|
||||||
|
"""test_get_ipython_dir_4, warn if XDG and home both exist."""
|
||||||
|
with patch_get_home_dir(HOME_TEST_DIR), \
|
||||||
|
patch('os.name', 'posix'):
|
||||||
|
try:
|
||||||
|
os.mkdir(os.path.join(XDG_TEST_DIR, 'ipython'))
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno != errno.EEXIST:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
with modified_env({
|
||||||
|
'IPYTHON_DIR': None,
|
||||||
|
'IPYTHONDIR': None,
|
||||||
|
'XDG_CONFIG_HOME': XDG_TEST_DIR,
|
||||||
|
}), warnings.catch_warnings(record=True) as w:
|
||||||
|
ipdir = paths.get_ipython_dir()
|
||||||
|
|
||||||
|
nt.assert_equal(ipdir, os.path.join(HOME_TEST_DIR, ".ipython"))
|
||||||
|
if sys.platform != 'darwin':
|
||||||
|
nt.assert_equal(len(w), 1)
|
||||||
|
nt.assert_in('Ignoring', str(w[0]))
|
||||||
|
|
||||||
|
def test_get_ipython_dir_5():
|
||||||
|
"""test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist."""
|
||||||
|
with patch_get_home_dir(HOME_TEST_DIR), \
|
||||||
|
patch('os.name', 'posix'):
|
||||||
|
try:
|
||||||
|
os.rmdir(os.path.join(XDG_TEST_DIR, 'ipython'))
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno != errno.ENOENT:
|
||||||
|
raise
|
||||||
|
|
||||||
|
with modified_env({
|
||||||
|
'IPYTHON_DIR': None,
|
||||||
|
'IPYTHONDIR': None,
|
||||||
|
'XDG_CONFIG_HOME': XDG_TEST_DIR,
|
||||||
|
}):
|
||||||
|
ipdir = paths.get_ipython_dir()
|
||||||
|
|
||||||
|
nt.assert_equal(ipdir, IP_TEST_DIR)
|
||||||
|
|
||||||
|
def test_get_ipython_dir_6():
|
||||||
|
"""test_get_ipython_dir_6, use home over XDG if defined and neither exist."""
|
||||||
|
xdg = os.path.join(HOME_TEST_DIR, 'somexdg')
|
||||||
|
os.mkdir(xdg)
|
||||||
|
shutil.rmtree(os.path.join(HOME_TEST_DIR, '.ipython'))
|
||||||
|
print(paths._writable_dir)
|
||||||
|
with patch_get_home_dir(HOME_TEST_DIR), \
|
||||||
|
patch.object(paths, 'get_xdg_dir', return_value=xdg), \
|
||||||
|
patch('os.name', 'posix'), \
|
||||||
|
modified_env({
|
||||||
|
'IPYTHON_DIR': None,
|
||||||
|
'IPYTHONDIR': None,
|
||||||
|
'XDG_CONFIG_HOME': None,
|
||||||
|
}), warnings.catch_warnings(record=True) as w:
|
||||||
|
ipdir = paths.get_ipython_dir()
|
||||||
|
|
||||||
|
nt.assert_equal(ipdir, os.path.join(HOME_TEST_DIR, '.ipython'))
|
||||||
|
nt.assert_equal(len(w), 0)
|
||||||
|
|
||||||
|
def test_get_ipython_dir_7():
|
||||||
|
"""test_get_ipython_dir_7, test home directory expansion on IPYTHONDIR"""
|
||||||
|
home_dir = os.path.normpath(os.path.expanduser('~'))
|
||||||
|
with modified_env({'IPYTHONDIR': os.path.join('~', 'somewhere')}), \
|
||||||
|
patch.object(paths, '_writable_dir', return_value=True):
|
||||||
|
ipdir = paths.get_ipython_dir()
|
||||||
|
nt.assert_equal(ipdir, os.path.join(home_dir, 'somewhere'))
|
||||||
|
|
||||||
|
@skip_win32
|
||||||
|
def test_get_ipython_dir_8():
|
||||||
|
"""test_get_ipython_dir_8, test / home directory"""
|
||||||
|
with patch.object(paths, '_writable_dir', lambda path: bool(path)), \
|
||||||
|
patch.object(paths, 'get_xdg_dir', return_value=None), \
|
||||||
|
modified_env({
|
||||||
|
'IPYTHON_DIR': None,
|
||||||
|
'IPYTHONDIR': None,
|
||||||
|
'HOME': '/',
|
||||||
|
}):
|
||||||
|
nt.assert_equal(paths.get_ipython_dir(), '/.ipython')
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_ipython_cache_dir():
|
||||||
|
with modified_env({'HOME': HOME_TEST_DIR}):
|
||||||
|
if os.name == 'posix' and sys.platform != 'darwin':
|
||||||
|
# test default
|
||||||
|
os.makedirs(os.path.join(HOME_TEST_DIR, ".cache"))
|
||||||
|
with modified_env({'XDG_CACHE_HOME': None}):
|
||||||
|
ipdir = paths.get_ipython_cache_dir()
|
||||||
|
nt.assert_equal(os.path.join(HOME_TEST_DIR, ".cache", "ipython"),
|
||||||
|
ipdir)
|
||||||
|
assert_isdir(ipdir)
|
||||||
|
|
||||||
|
# test env override
|
||||||
|
with modified_env({"XDG_CACHE_HOME": XDG_CACHE_DIR}):
|
||||||
|
ipdir = paths.get_ipython_cache_dir()
|
||||||
|
assert_isdir(ipdir)
|
||||||
|
nt.assert_equal(ipdir, os.path.join(XDG_CACHE_DIR, "ipython"))
|
||||||
|
else:
|
||||||
|
nt.assert_equal(paths.get_ipython_cache_dir(),
|
||||||
|
paths.get_ipython_dir())
|
||||||
|
|
||||||
|
def test_get_ipython_package_dir():
|
||||||
|
ipdir = paths.get_ipython_package_dir()
|
||||||
|
assert_isdir(ipdir)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_ipython_module_path():
|
||||||
|
ipapp_path = paths.get_ipython_module_path('yap_ipython.terminal.ipapp')
|
||||||
|
assert_isfile(ipapp_path)
|
@ -0,0 +1,119 @@
|
|||||||
|
"""Tests for input manipulation machinery."""
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython.core.prefilter import AutocallChecker
|
||||||
|
from yap_ipython.testing.globalipapp import get_ipython
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Tests
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
ip = get_ipython()
|
||||||
|
|
||||||
|
def test_prefilter():
|
||||||
|
"""Test user input conversions"""
|
||||||
|
|
||||||
|
# pairs of (raw, expected correct) input
|
||||||
|
pairs = [ ('2+2','2+2'),
|
||||||
|
]
|
||||||
|
|
||||||
|
for raw, correct in pairs:
|
||||||
|
nt.assert_equal(ip.prefilter(raw), correct)
|
||||||
|
|
||||||
|
def test_prefilter_shadowed():
|
||||||
|
def dummy_magic(line): pass
|
||||||
|
|
||||||
|
prev_automagic_state = ip.automagic
|
||||||
|
ip.automagic = True
|
||||||
|
ip.autocall = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
# These should not be transformed - they are shadowed by other names
|
||||||
|
for name in ['if', 'zip', 'get_ipython']: # keyword, builtin, global
|
||||||
|
ip.register_magic_function(dummy_magic, magic_name=name)
|
||||||
|
res = ip.prefilter(name+' foo')
|
||||||
|
nt.assert_equal(res, name+' foo')
|
||||||
|
del ip.magics_manager.magics['line'][name]
|
||||||
|
|
||||||
|
# These should be transformed
|
||||||
|
for name in ['fi', 'piz', 'nohtypi_teg']:
|
||||||
|
ip.register_magic_function(dummy_magic, magic_name=name)
|
||||||
|
res = ip.prefilter(name+' foo')
|
||||||
|
nt.assert_not_equal(res, name+' foo')
|
||||||
|
del ip.magics_manager.magics['line'][name]
|
||||||
|
|
||||||
|
finally:
|
||||||
|
ip.automagic = prev_automagic_state
|
||||||
|
|
||||||
|
def test_autocall_binops():
|
||||||
|
"""See https://github.com/ipython/ipython/issues/81"""
|
||||||
|
ip.magic('autocall 2')
|
||||||
|
f = lambda x: x
|
||||||
|
ip.user_ns['f'] = f
|
||||||
|
try:
|
||||||
|
nt.assert_equal(ip.prefilter('f 1'),'f(1)')
|
||||||
|
for t in ['f +1', 'f -1']:
|
||||||
|
nt.assert_equal(ip.prefilter(t), t)
|
||||||
|
|
||||||
|
# Run tests again with a more permissive exclude_regexp, which will
|
||||||
|
# allow transformation of binary operations ('f -1' -> 'f(-1)').
|
||||||
|
pm = ip.prefilter_manager
|
||||||
|
ac = AutocallChecker(shell=pm.shell, prefilter_manager=pm,
|
||||||
|
config=pm.config)
|
||||||
|
try:
|
||||||
|
ac.priority = 1
|
||||||
|
ac.exclude_regexp = r'^[,&^\|\*/]|^is |^not |^in |^and |^or '
|
||||||
|
pm.sort_checkers()
|
||||||
|
|
||||||
|
nt.assert_equal(ip.prefilter('f -1'), 'f(-1)')
|
||||||
|
nt.assert_equal(ip.prefilter('f +1'), 'f(+1)')
|
||||||
|
finally:
|
||||||
|
pm.unregister_checker(ac)
|
||||||
|
finally:
|
||||||
|
ip.magic('autocall 0')
|
||||||
|
del ip.user_ns['f']
|
||||||
|
|
||||||
|
|
||||||
|
def test_issue_114():
|
||||||
|
"""Check that multiline string literals don't expand as magic
|
||||||
|
see http://github.com/ipython/ipython/issues/114"""
|
||||||
|
|
||||||
|
template = '"""\n%s\n"""'
|
||||||
|
# Store the current value of multi_line_specials and turn it off before
|
||||||
|
# running test, since it could be true (case in which the test doesn't make
|
||||||
|
# sense, as multiline string literals *will* expand as magic in that case).
|
||||||
|
msp = ip.prefilter_manager.multi_line_specials
|
||||||
|
ip.prefilter_manager.multi_line_specials = False
|
||||||
|
try:
|
||||||
|
for mgk in ip.magics_manager.lsmagic()['line']:
|
||||||
|
raw = template % mgk
|
||||||
|
nt.assert_equal(ip.prefilter(raw), raw)
|
||||||
|
finally:
|
||||||
|
ip.prefilter_manager.multi_line_specials = msp
|
||||||
|
|
||||||
|
|
||||||
|
def test_prefilter_attribute_errors():
|
||||||
|
"""Capture exceptions thrown by user objects on attribute access.
|
||||||
|
|
||||||
|
See http://github.com/ipython/ipython/issues/988."""
|
||||||
|
|
||||||
|
class X(object):
|
||||||
|
def __getattr__(self, k):
|
||||||
|
raise ValueError('broken object')
|
||||||
|
def __call__(self, x):
|
||||||
|
return x
|
||||||
|
|
||||||
|
# Create a callable broken object
|
||||||
|
ip.user_ns['x'] = X()
|
||||||
|
ip.magic('autocall 2')
|
||||||
|
try:
|
||||||
|
# Even if x throws an attribute error when looking at its rewrite
|
||||||
|
# attribute, we should not crash. So the test here is simply making
|
||||||
|
# the prefilter call and not having an exception.
|
||||||
|
ip.prefilter('x 1')
|
||||||
|
finally:
|
||||||
|
del ip.user_ns['x']
|
||||||
|
ip.magic('autocall 0')
|
@ -0,0 +1,163 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
"""Tests for profile-related functions.
|
||||||
|
|
||||||
|
Currently only the startup-dir functionality is tested, but more tests should
|
||||||
|
be added for:
|
||||||
|
|
||||||
|
* ipython profile create
|
||||||
|
* ipython profile list
|
||||||
|
* ipython profile create --parallel
|
||||||
|
* security dir permissions
|
||||||
|
|
||||||
|
Authors
|
||||||
|
-------
|
||||||
|
|
||||||
|
* MinRK
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython.core.profileapp import list_profiles_in, list_bundled_profiles
|
||||||
|
from yap_ipython.core.profiledir import ProfileDir
|
||||||
|
|
||||||
|
from yap_ipython.testing import decorators as dec
|
||||||
|
from yap_ipython.testing import tools as tt
|
||||||
|
from yap_ipython.utils import py3compat
|
||||||
|
from yap_ipython.utils.process import getoutput
|
||||||
|
from yap_ipython.utils.tempdir import TemporaryDirectory
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Globals
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
TMP_TEST_DIR = tempfile.mkdtemp()
|
||||||
|
HOME_TEST_DIR = os.path.join(TMP_TEST_DIR, "home_test_dir")
|
||||||
|
IP_TEST_DIR = os.path.join(HOME_TEST_DIR,'.ipython')
|
||||||
|
|
||||||
|
#
|
||||||
|
# Setup/teardown functions/decorators
|
||||||
|
#
|
||||||
|
|
||||||
|
def setup():
|
||||||
|
"""Setup test environment for the module:
|
||||||
|
|
||||||
|
- Adds dummy home dir tree
|
||||||
|
"""
|
||||||
|
# Do not mask exceptions here. In particular, catching WindowsError is a
|
||||||
|
# problem because that exception is only defined on Windows...
|
||||||
|
os.makedirs(IP_TEST_DIR)
|
||||||
|
|
||||||
|
|
||||||
|
def teardown():
|
||||||
|
"""Teardown test environment for the module:
|
||||||
|
|
||||||
|
- Remove dummy home dir tree
|
||||||
|
"""
|
||||||
|
# Note: we remove the parent test dir, which is the root of all test
|
||||||
|
# subdirs we may have created. Use shutil instead of os.removedirs, so
|
||||||
|
# that non-empty directories are all recursively removed.
|
||||||
|
shutil.rmtree(TMP_TEST_DIR)
|
||||||
|
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Test functions
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
def win32_without_pywin32():
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
try:
|
||||||
|
import pywin32
|
||||||
|
except ImportError:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class ProfileStartupTest(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
# create profile dir
|
||||||
|
self.pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, 'test')
|
||||||
|
self.options = ['--ipython-dir', IP_TEST_DIR, '--profile', 'test']
|
||||||
|
self.fname = os.path.join(TMP_TEST_DIR, 'test.py')
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
# We must remove this profile right away so its presence doesn't
|
||||||
|
# confuse other tests.
|
||||||
|
shutil.rmtree(self.pd.location)
|
||||||
|
|
||||||
|
def init(self, startup_file, startup, test):
|
||||||
|
# write startup python file
|
||||||
|
with open(os.path.join(self.pd.startup_dir, startup_file), 'w') as f:
|
||||||
|
f.write(startup)
|
||||||
|
# write simple test file, to check that the startup file was run
|
||||||
|
with open(self.fname, 'w') as f:
|
||||||
|
f.write(py3compat.doctest_refactor_print(test))
|
||||||
|
|
||||||
|
def validate(self, output):
|
||||||
|
tt.ipexec_validate(self.fname, output, '', options=self.options)
|
||||||
|
|
||||||
|
@dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows")
|
||||||
|
def test_startup_py(self):
|
||||||
|
self.init('00-start.py', 'zzz=123\n',
|
||||||
|
py3compat.doctest_refactor_print('print zzz\n'))
|
||||||
|
self.validate('123')
|
||||||
|
|
||||||
|
@dec.skipif(win32_without_pywin32(), "Test requires pywin32 on Windows")
|
||||||
|
def test_startup_ipy(self):
|
||||||
|
self.init('00-start.ipy', '%xmode plain\n', '')
|
||||||
|
self.validate('Exception reporting mode: Plain')
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_profiles_in():
|
||||||
|
# No need to remove these directories and files, as they will get nuked in
|
||||||
|
# the module-level teardown.
|
||||||
|
td = tempfile.mkdtemp(dir=TMP_TEST_DIR)
|
||||||
|
for name in ('profile_foo', 'profile_hello', 'not_a_profile'):
|
||||||
|
os.mkdir(os.path.join(td, name))
|
||||||
|
if dec.unicode_paths:
|
||||||
|
os.mkdir(os.path.join(td, u'profile_ünicode'))
|
||||||
|
|
||||||
|
with open(os.path.join(td, 'profile_file'), 'w') as f:
|
||||||
|
f.write("I am not a profile directory")
|
||||||
|
profiles = list_profiles_in(td)
|
||||||
|
|
||||||
|
# unicode normalization can turn u'ünicode' into u'u\0308nicode',
|
||||||
|
# so only check for *nicode, and that creating a ProfileDir from the
|
||||||
|
# name remains valid
|
||||||
|
found_unicode = False
|
||||||
|
for p in list(profiles):
|
||||||
|
if p.endswith('nicode'):
|
||||||
|
pd = ProfileDir.find_profile_dir_by_name(td, p)
|
||||||
|
profiles.remove(p)
|
||||||
|
found_unicode = True
|
||||||
|
break
|
||||||
|
if dec.unicode_paths:
|
||||||
|
nt.assert_true(found_unicode)
|
||||||
|
nt.assert_equal(set(profiles), {'foo', 'hello'})
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_bundled_profiles():
|
||||||
|
# This variable will need to be updated when a new profile gets bundled
|
||||||
|
bundled = sorted(list_bundled_profiles())
|
||||||
|
nt.assert_equal(bundled, [])
|
||||||
|
|
||||||
|
|
||||||
|
def test_profile_create_ipython_dir():
|
||||||
|
"""ipython profile create respects --ipython-dir"""
|
||||||
|
with TemporaryDirectory() as td:
|
||||||
|
getoutput([sys.executable, '-m', 'yap_ipython', 'profile', 'create',
|
||||||
|
'foo', '--ipython-dir=%s' % td])
|
||||||
|
profile_dir = os.path.join(td, 'profile_foo')
|
||||||
|
assert os.path.exists(profile_dir)
|
||||||
|
ipython_config = os.path.join(profile_dir, 'ipython_config.py')
|
||||||
|
assert os.path.exists(ipython_config)
|
||||||
|
|
@ -0,0 +1,34 @@
|
|||||||
|
# -*- coding: utf-8
|
||||||
|
"""Tests for prompt generation."""
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from yap_ipython.core.prompts import LazyEvaluate
|
||||||
|
from yap_ipython.testing.globalipapp import get_ipython
|
||||||
|
|
||||||
|
ip = get_ipython()
|
||||||
|
|
||||||
|
|
||||||
|
class PromptTests(unittest.TestCase):
|
||||||
|
def test_lazy_eval_unicode(self):
|
||||||
|
u = u'ünicødé'
|
||||||
|
lz = LazyEvaluate(lambda : u)
|
||||||
|
self.assertEqual(str(lz), u)
|
||||||
|
self.assertEqual(format(lz), u)
|
||||||
|
|
||||||
|
def test_lazy_eval_nonascii_bytes(self):
|
||||||
|
u = u'ünicødé'
|
||||||
|
b = u.encode('utf8')
|
||||||
|
lz = LazyEvaluate(lambda : b)
|
||||||
|
# unicode(lz) would fail
|
||||||
|
self.assertEqual(str(lz), str(b))
|
||||||
|
self.assertEqual(format(lz), str(b))
|
||||||
|
|
||||||
|
def test_lazy_eval_float(self):
|
||||||
|
f = 0.503
|
||||||
|
lz = LazyEvaluate(lambda : f)
|
||||||
|
|
||||||
|
self.assertEqual(str(lz), str(f))
|
||||||
|
self.assertEqual(format(lz), str(f))
|
||||||
|
self.assertEqual(format(lz, '.1'), '0.5')
|
||||||
|
|
@ -0,0 +1,246 @@
|
|||||||
|
"""Tests for pylab tools module.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Copyright (c) yap_ipython Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
|
||||||
|
from io import UnsupportedOperation, BytesIO
|
||||||
|
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use('Agg')
|
||||||
|
from matplotlib.figure import Figure
|
||||||
|
|
||||||
|
from nose import SkipTest
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from matplotlib import pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from yap_ipython.core.getipython import get_ipython
|
||||||
|
from yap_ipython.core.interactiveshell import InteractiveShell
|
||||||
|
from yap_ipython.core.display import _PNG, _JPEG
|
||||||
|
from .. import pylabtools as pt
|
||||||
|
|
||||||
|
from yap_ipython.testing import decorators as dec
|
||||||
|
|
||||||
|
|
||||||
|
def test_figure_to_svg():
|
||||||
|
# simple empty-figure test
|
||||||
|
fig = plt.figure()
|
||||||
|
nt.assert_equal(pt.print_figure(fig, 'svg'), None)
|
||||||
|
|
||||||
|
plt.close('all')
|
||||||
|
|
||||||
|
# simple check for at least svg-looking output
|
||||||
|
fig = plt.figure()
|
||||||
|
ax = fig.add_subplot(1,1,1)
|
||||||
|
ax.plot([1,2,3])
|
||||||
|
plt.draw()
|
||||||
|
svg = pt.print_figure(fig, 'svg')[:100].lower()
|
||||||
|
nt.assert_in(u'doctype svg', svg)
|
||||||
|
|
||||||
|
def _check_pil_jpeg_bytes():
|
||||||
|
"""Skip if PIL can't write JPEGs to BytesIO objects"""
|
||||||
|
# PIL's JPEG plugin can't write to BytesIO objects
|
||||||
|
# Pillow fixes this
|
||||||
|
from PIL import Image
|
||||||
|
buf = BytesIO()
|
||||||
|
img = Image.new("RGB", (4,4))
|
||||||
|
try:
|
||||||
|
img.save(buf, 'jpeg')
|
||||||
|
except Exception as e:
|
||||||
|
ename = e.__class__.__name__
|
||||||
|
raise SkipTest("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e))
|
||||||
|
|
||||||
|
@dec.skip_without("PIL.Image")
|
||||||
|
def test_figure_to_jpeg():
|
||||||
|
_check_pil_jpeg_bytes()
|
||||||
|
# simple check for at least jpeg-looking output
|
||||||
|
fig = plt.figure()
|
||||||
|
ax = fig.add_subplot(1,1,1)
|
||||||
|
ax.plot([1,2,3])
|
||||||
|
plt.draw()
|
||||||
|
jpeg = pt.print_figure(fig, 'jpeg', quality=50)[:100].lower()
|
||||||
|
assert jpeg.startswith(_JPEG)
|
||||||
|
|
||||||
|
def test_retina_figure():
|
||||||
|
# simple empty-figure test
|
||||||
|
fig = plt.figure()
|
||||||
|
nt.assert_equal(pt.retina_figure(fig), None)
|
||||||
|
plt.close('all')
|
||||||
|
|
||||||
|
fig = plt.figure()
|
||||||
|
ax = fig.add_subplot(1,1,1)
|
||||||
|
ax.plot([1,2,3])
|
||||||
|
plt.draw()
|
||||||
|
png, md = pt.retina_figure(fig)
|
||||||
|
assert png.startswith(_PNG)
|
||||||
|
nt.assert_in('width', md)
|
||||||
|
nt.assert_in('height', md)
|
||||||
|
|
||||||
|
_fmt_mime_map = {
|
||||||
|
'png': 'image/png',
|
||||||
|
'jpeg': 'image/jpeg',
|
||||||
|
'pdf': 'application/pdf',
|
||||||
|
'retina': 'image/png',
|
||||||
|
'svg': 'image/svg+xml',
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_select_figure_formats_str():
|
||||||
|
ip = get_ipython()
|
||||||
|
for fmt, active_mime in _fmt_mime_map.items():
|
||||||
|
pt.select_figure_formats(ip, fmt)
|
||||||
|
for mime, f in ip.display_formatter.formatters.items():
|
||||||
|
if mime == active_mime:
|
||||||
|
nt.assert_in(Figure, f)
|
||||||
|
else:
|
||||||
|
nt.assert_not_in(Figure, f)
|
||||||
|
|
||||||
|
def test_select_figure_formats_kwargs():
|
||||||
|
ip = get_ipython()
|
||||||
|
kwargs = dict(quality=10, bbox_inches='tight')
|
||||||
|
pt.select_figure_formats(ip, 'png', **kwargs)
|
||||||
|
formatter = ip.display_formatter.formatters['image/png']
|
||||||
|
f = formatter.lookup_by_type(Figure)
|
||||||
|
cell = f.__closure__[0].cell_contents
|
||||||
|
nt.assert_equal(cell, kwargs)
|
||||||
|
|
||||||
|
# check that the formatter doesn't raise
|
||||||
|
fig = plt.figure()
|
||||||
|
ax = fig.add_subplot(1,1,1)
|
||||||
|
ax.plot([1,2,3])
|
||||||
|
plt.draw()
|
||||||
|
formatter.enabled = True
|
||||||
|
png = formatter(fig)
|
||||||
|
assert png.startswith(_PNG)
|
||||||
|
|
||||||
|
def test_select_figure_formats_set():
|
||||||
|
ip = get_ipython()
|
||||||
|
for fmts in [
|
||||||
|
{'png', 'svg'},
|
||||||
|
['png'],
|
||||||
|
('jpeg', 'pdf', 'retina'),
|
||||||
|
{'svg'},
|
||||||
|
]:
|
||||||
|
active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
|
||||||
|
pt.select_figure_formats(ip, fmts)
|
||||||
|
for mime, f in ip.display_formatter.formatters.items():
|
||||||
|
if mime in active_mimes:
|
||||||
|
nt.assert_in(Figure, f)
|
||||||
|
else:
|
||||||
|
nt.assert_not_in(Figure, f)
|
||||||
|
|
||||||
|
def test_select_figure_formats_bad():
|
||||||
|
ip = get_ipython()
|
||||||
|
with nt.assert_raises(ValueError):
|
||||||
|
pt.select_figure_formats(ip, 'foo')
|
||||||
|
with nt.assert_raises(ValueError):
|
||||||
|
pt.select_figure_formats(ip, {'png', 'foo'})
|
||||||
|
with nt.assert_raises(ValueError):
|
||||||
|
pt.select_figure_formats(ip, ['retina', 'pdf', 'bar', 'bad'])
|
||||||
|
|
||||||
|
def test_import_pylab():
|
||||||
|
ns = {}
|
||||||
|
pt.import_pylab(ns, import_all=False)
|
||||||
|
nt.assert_true('plt' in ns)
|
||||||
|
nt.assert_equal(ns['np'], np)
|
||||||
|
|
||||||
|
class TestPylabSwitch(object):
|
||||||
|
class Shell(InteractiveShell):
|
||||||
|
def enable_gui(self, gui):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
import matplotlib
|
||||||
|
def act_mpl(backend):
|
||||||
|
matplotlib.rcParams['backend'] = backend
|
||||||
|
|
||||||
|
# Save rcParams since they get modified
|
||||||
|
self._saved_rcParams = matplotlib.rcParams
|
||||||
|
self._saved_rcParamsOrig = matplotlib.rcParamsOrig
|
||||||
|
matplotlib.rcParams = dict(backend='Qt4Agg')
|
||||||
|
matplotlib.rcParamsOrig = dict(backend='Qt4Agg')
|
||||||
|
|
||||||
|
# Mock out functions
|
||||||
|
self._save_am = pt.activate_matplotlib
|
||||||
|
pt.activate_matplotlib = act_mpl
|
||||||
|
self._save_ip = pt.import_pylab
|
||||||
|
pt.import_pylab = lambda *a,**kw:None
|
||||||
|
self._save_cis = pt.configure_inline_support
|
||||||
|
pt.configure_inline_support = lambda *a,**kw:None
|
||||||
|
|
||||||
|
def teardown(self):
|
||||||
|
pt.activate_matplotlib = self._save_am
|
||||||
|
pt.import_pylab = self._save_ip
|
||||||
|
pt.configure_inline_support = self._save_cis
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.rcParams = self._saved_rcParams
|
||||||
|
matplotlib.rcParamsOrig = self._saved_rcParamsOrig
|
||||||
|
|
||||||
|
def test_qt(self):
|
||||||
|
s = self.Shell()
|
||||||
|
gui, backend = s.enable_matplotlib(None)
|
||||||
|
nt.assert_equal(gui, 'qt')
|
||||||
|
nt.assert_equal(s.pylab_gui_select, 'qt')
|
||||||
|
|
||||||
|
gui, backend = s.enable_matplotlib('inline')
|
||||||
|
nt.assert_equal(gui, 'inline')
|
||||||
|
nt.assert_equal(s.pylab_gui_select, 'qt')
|
||||||
|
|
||||||
|
gui, backend = s.enable_matplotlib('qt')
|
||||||
|
nt.assert_equal(gui, 'qt')
|
||||||
|
nt.assert_equal(s.pylab_gui_select, 'qt')
|
||||||
|
|
||||||
|
gui, backend = s.enable_matplotlib('inline')
|
||||||
|
nt.assert_equal(gui, 'inline')
|
||||||
|
nt.assert_equal(s.pylab_gui_select, 'qt')
|
||||||
|
|
||||||
|
gui, backend = s.enable_matplotlib()
|
||||||
|
nt.assert_equal(gui, 'qt')
|
||||||
|
nt.assert_equal(s.pylab_gui_select, 'qt')
|
||||||
|
|
||||||
|
def test_inline(self):
|
||||||
|
s = self.Shell()
|
||||||
|
gui, backend = s.enable_matplotlib('inline')
|
||||||
|
nt.assert_equal(gui, 'inline')
|
||||||
|
nt.assert_equal(s.pylab_gui_select, None)
|
||||||
|
|
||||||
|
gui, backend = s.enable_matplotlib('inline')
|
||||||
|
nt.assert_equal(gui, 'inline')
|
||||||
|
nt.assert_equal(s.pylab_gui_select, None)
|
||||||
|
|
||||||
|
gui, backend = s.enable_matplotlib('qt')
|
||||||
|
nt.assert_equal(gui, 'qt')
|
||||||
|
nt.assert_equal(s.pylab_gui_select, 'qt')
|
||||||
|
|
||||||
|
def test_inline_twice(self):
|
||||||
|
"Using '%matplotlib inline' twice should not reset formatters"
|
||||||
|
|
||||||
|
ip = self.Shell()
|
||||||
|
gui, backend = ip.enable_matplotlib('inline')
|
||||||
|
nt.assert_equal(gui, 'inline')
|
||||||
|
|
||||||
|
fmts = {'png'}
|
||||||
|
active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
|
||||||
|
pt.select_figure_formats(ip, fmts)
|
||||||
|
|
||||||
|
gui, backend = ip.enable_matplotlib('inline')
|
||||||
|
nt.assert_equal(gui, 'inline')
|
||||||
|
|
||||||
|
for mime, f in ip.display_formatter.formatters.items():
|
||||||
|
if mime in active_mimes:
|
||||||
|
nt.assert_in(Figure, f)
|
||||||
|
else:
|
||||||
|
nt.assert_not_in(Figure, f)
|
||||||
|
|
||||||
|
def test_qt_gtk(self):
|
||||||
|
s = self.Shell()
|
||||||
|
gui, backend = s.enable_matplotlib('qt')
|
||||||
|
nt.assert_equal(gui, 'qt')
|
||||||
|
nt.assert_equal(s.pylab_gui_select, 'qt')
|
||||||
|
|
||||||
|
gui, backend = s.enable_matplotlib('gtk')
|
||||||
|
nt.assert_equal(gui, 'qt')
|
||||||
|
nt.assert_equal(s.pylab_gui_select, 'qt')
|
||||||
|
|
560
packages/python/yap_kernel/yap_ipython/core/tests/test_run.py
Normal file
560
packages/python/yap_kernel/yap_ipython/core/tests/test_run.py
Normal file
@ -0,0 +1,560 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
"""Tests for code execution (%run and related), which is particularly tricky.
|
||||||
|
|
||||||
|
Because of how %run manages namespaces, and the fact that we are trying here to
|
||||||
|
verify subtle object deletion and reference counting issues, the %run tests
|
||||||
|
will be kept in this separate file. This makes it easier to aggregate in one
|
||||||
|
place the tricks needed to handle it; most other magics are much easier to test
|
||||||
|
and we do so in a common test_magic file.
|
||||||
|
|
||||||
|
Note that any test using `run -i` should make sure to do a `reset` afterwards,
|
||||||
|
as otherwise it may influence later tests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Copyright (c) yap_ipython Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import functools
|
||||||
|
import os
|
||||||
|
from os.path import join as pjoin
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
import sys
|
||||||
|
import textwrap
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
from nose import SkipTest
|
||||||
|
|
||||||
|
from yap_ipython.testing import decorators as dec
|
||||||
|
from yap_ipython.testing import tools as tt
|
||||||
|
from yap_ipython.utils import py3compat
|
||||||
|
from yap_ipython.utils.io import capture_output
|
||||||
|
from yap_ipython.utils.tempdir import TemporaryDirectory
|
||||||
|
from yap_ipython.core import debugger
|
||||||
|
|
||||||
|
|
||||||
|
def doctest_refbug():
|
||||||
|
"""Very nasty problem with references held by multiple runs of a script.
|
||||||
|
See: https://github.com/ipython/ipython/issues/141
|
||||||
|
|
||||||
|
In [1]: _ip.clear_main_mod_cache()
|
||||||
|
# random
|
||||||
|
|
||||||
|
In [2]: %run refbug
|
||||||
|
|
||||||
|
In [3]: call_f()
|
||||||
|
lowercased: hello
|
||||||
|
|
||||||
|
In [4]: %run refbug
|
||||||
|
|
||||||
|
In [5]: call_f()
|
||||||
|
lowercased: hello
|
||||||
|
lowercased: hello
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def doctest_run_builtins():
|
||||||
|
r"""Check that %run doesn't damage __builtins__.
|
||||||
|
|
||||||
|
In [1]: import tempfile
|
||||||
|
|
||||||
|
In [2]: bid1 = id(__builtins__)
|
||||||
|
|
||||||
|
In [3]: fname = tempfile.mkstemp('.py')[1]
|
||||||
|
|
||||||
|
In [3]: f = open(fname,'w')
|
||||||
|
|
||||||
|
In [4]: dummy= f.write('pass\n')
|
||||||
|
|
||||||
|
In [5]: f.flush()
|
||||||
|
|
||||||
|
In [6]: t1 = type(__builtins__)
|
||||||
|
|
||||||
|
In [7]: %run $fname
|
||||||
|
|
||||||
|
In [7]: f.close()
|
||||||
|
|
||||||
|
In [8]: bid2 = id(__builtins__)
|
||||||
|
|
||||||
|
In [9]: t2 = type(__builtins__)
|
||||||
|
|
||||||
|
In [10]: t1 == t2
|
||||||
|
Out[10]: True
|
||||||
|
|
||||||
|
In [10]: bid1 == bid2
|
||||||
|
Out[10]: True
|
||||||
|
|
||||||
|
In [12]: try:
|
||||||
|
....: os.unlink(fname)
|
||||||
|
....: except:
|
||||||
|
....: pass
|
||||||
|
....:
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def doctest_run_option_parser():
|
||||||
|
r"""Test option parser in %run.
|
||||||
|
|
||||||
|
In [1]: %run print_argv.py
|
||||||
|
[]
|
||||||
|
|
||||||
|
In [2]: %run print_argv.py print*.py
|
||||||
|
['print_argv.py']
|
||||||
|
|
||||||
|
In [3]: %run -G print_argv.py print*.py
|
||||||
|
['print*.py']
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@dec.skip_win32
|
||||||
|
def doctest_run_option_parser_for_posix():
|
||||||
|
r"""Test option parser in %run (Linux/OSX specific).
|
||||||
|
|
||||||
|
You need double quote to escape glob in POSIX systems:
|
||||||
|
|
||||||
|
In [1]: %run print_argv.py print\\*.py
|
||||||
|
['print*.py']
|
||||||
|
|
||||||
|
You can't use quote to escape glob in POSIX systems:
|
||||||
|
|
||||||
|
In [2]: %run print_argv.py 'print*.py'
|
||||||
|
['print_argv.py']
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@dec.skip_if_not_win32
|
||||||
|
def doctest_run_option_parser_for_windows():
|
||||||
|
r"""Test option parser in %run (Windows specific).
|
||||||
|
|
||||||
|
In Windows, you can't escape ``*` `by backslash:
|
||||||
|
|
||||||
|
In [1]: %run print_argv.py print\\*.py
|
||||||
|
['print\\*.py']
|
||||||
|
|
||||||
|
You can use quote to escape glob:
|
||||||
|
|
||||||
|
In [2]: %run print_argv.py 'print*.py'
|
||||||
|
['print*.py']
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@py3compat.doctest_refactor_print
|
||||||
|
def doctest_reset_del():
|
||||||
|
"""Test that resetting doesn't cause errors in __del__ methods.
|
||||||
|
|
||||||
|
In [2]: class A(object):
|
||||||
|
...: def __del__(self):
|
||||||
|
...: print str("Hi")
|
||||||
|
...:
|
||||||
|
|
||||||
|
In [3]: a = A()
|
||||||
|
|
||||||
|
In [4]: get_ipython().reset()
|
||||||
|
Hi
|
||||||
|
|
||||||
|
In [5]: 1+1
|
||||||
|
Out[5]: 2
|
||||||
|
"""
|
||||||
|
|
||||||
|
# For some tests, it will be handy to organize them in a class with a common
|
||||||
|
# setup that makes a temp file
|
||||||
|
|
||||||
|
class TestMagicRunPass(tt.TempFileMixin):
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
content = "a = [1,2,3]\nb = 1"
|
||||||
|
self.mktmp(content)
|
||||||
|
|
||||||
|
def run_tmpfile(self):
|
||||||
|
_ip = get_ipython()
|
||||||
|
# This fails on Windows if self.tmpfile.name has spaces or "~" in it.
|
||||||
|
# See below and ticket https://bugs.launchpad.net/bugs/366353
|
||||||
|
_ip.magic('run %s' % self.fname)
|
||||||
|
|
||||||
|
def run_tmpfile_p(self):
|
||||||
|
_ip = get_ipython()
|
||||||
|
# This fails on Windows if self.tmpfile.name has spaces or "~" in it.
|
||||||
|
# See below and ticket https://bugs.launchpad.net/bugs/366353
|
||||||
|
_ip.magic('run -p %s' % self.fname)
|
||||||
|
|
||||||
|
def test_builtins_id(self):
|
||||||
|
"""Check that %run doesn't damage __builtins__ """
|
||||||
|
_ip = get_ipython()
|
||||||
|
# Test that the id of __builtins__ is not modified by %run
|
||||||
|
bid1 = id(_ip.user_ns['__builtins__'])
|
||||||
|
self.run_tmpfile()
|
||||||
|
bid2 = id(_ip.user_ns['__builtins__'])
|
||||||
|
nt.assert_equal(bid1, bid2)
|
||||||
|
|
||||||
|
def test_builtins_type(self):
|
||||||
|
"""Check that the type of __builtins__ doesn't change with %run.
|
||||||
|
|
||||||
|
However, the above could pass if __builtins__ was already modified to
|
||||||
|
be a dict (it should be a module) by a previous use of %run. So we
|
||||||
|
also check explicitly that it really is a module:
|
||||||
|
"""
|
||||||
|
_ip = get_ipython()
|
||||||
|
self.run_tmpfile()
|
||||||
|
nt.assert_equal(type(_ip.user_ns['__builtins__']),type(sys))
|
||||||
|
|
||||||
|
def test_run_profile( self ):
|
||||||
|
"""Test that the option -p, which invokes the profiler, do not
|
||||||
|
crash by invoking execfile"""
|
||||||
|
self.run_tmpfile_p()
|
||||||
|
|
||||||
|
def test_run_debug_twice(self):
|
||||||
|
# https://github.com/ipython/ipython/issues/10028
|
||||||
|
_ip = get_ipython()
|
||||||
|
with tt.fake_input(['c']):
|
||||||
|
_ip.magic('run -d %s' % self.fname)
|
||||||
|
with tt.fake_input(['c']):
|
||||||
|
_ip.magic('run -d %s' % self.fname)
|
||||||
|
|
||||||
|
def test_run_debug_twice_with_breakpoint(self):
|
||||||
|
"""Make a valid python temp file."""
|
||||||
|
_ip = get_ipython()
|
||||||
|
with tt.fake_input(['b 2', 'c', 'c']):
|
||||||
|
_ip.magic('run -d %s' % self.fname)
|
||||||
|
|
||||||
|
with tt.fake_input(['c']):
|
||||||
|
with tt.AssertNotPrints('KeyError'):
|
||||||
|
_ip.magic('run -d %s' % self.fname)
|
||||||
|
|
||||||
|
|
||||||
|
class TestMagicRunSimple(tt.TempFileMixin):
|
||||||
|
|
||||||
|
def test_simpledef(self):
|
||||||
|
"""Test that simple class definitions work."""
|
||||||
|
src = ("class foo: pass\n"
|
||||||
|
"def f(): return foo()")
|
||||||
|
self.mktmp(src)
|
||||||
|
_ip.magic('run %s' % self.fname)
|
||||||
|
_ip.run_cell('t = isinstance(f(), foo)')
|
||||||
|
nt.assert_true(_ip.user_ns['t'])
|
||||||
|
|
||||||
|
def test_obj_del(self):
|
||||||
|
"""Test that object's __del__ methods are called on exit."""
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
try:
|
||||||
|
import win32api
|
||||||
|
except ImportError:
|
||||||
|
raise SkipTest("Test requires pywin32")
|
||||||
|
src = ("class A(object):\n"
|
||||||
|
" def __del__(self):\n"
|
||||||
|
" print 'object A deleted'\n"
|
||||||
|
"a = A()\n")
|
||||||
|
self.mktmp(py3compat.doctest_refactor_print(src))
|
||||||
|
if dec.module_not_available('sqlite3'):
|
||||||
|
err = 'WARNING: yap_ipython History requires SQLite, your history will not be saved\n'
|
||||||
|
else:
|
||||||
|
err = None
|
||||||
|
tt.ipexec_validate(self.fname, 'object A deleted', err)
|
||||||
|
|
||||||
|
def test_aggressive_namespace_cleanup(self):
|
||||||
|
"""Test that namespace cleanup is not too aggressive GH-238
|
||||||
|
|
||||||
|
Returning from another run magic deletes the namespace"""
|
||||||
|
# see ticket https://github.com/ipython/ipython/issues/238
|
||||||
|
|
||||||
|
with tt.TempFileMixin() as empty:
|
||||||
|
empty.mktmp('')
|
||||||
|
# On Windows, the filename will have \users in it, so we need to use the
|
||||||
|
# repr so that the \u becomes \\u.
|
||||||
|
src = ("ip = get_ipython()\n"
|
||||||
|
"for i in range(5):\n"
|
||||||
|
" try:\n"
|
||||||
|
" ip.magic(%r)\n"
|
||||||
|
" except NameError as e:\n"
|
||||||
|
" print(i)\n"
|
||||||
|
" break\n" % ('run ' + empty.fname))
|
||||||
|
self.mktmp(src)
|
||||||
|
_ip.magic('run %s' % self.fname)
|
||||||
|
_ip.run_cell('ip == get_ipython()')
|
||||||
|
nt.assert_equal(_ip.user_ns['i'], 4)
|
||||||
|
|
||||||
|
def test_run_second(self):
|
||||||
|
"""Test that running a second file doesn't clobber the first, gh-3547
|
||||||
|
"""
|
||||||
|
self.mktmp("avar = 1\n"
|
||||||
|
"def afunc():\n"
|
||||||
|
" return avar\n")
|
||||||
|
|
||||||
|
with tt.TempFileMixin() as empty:
|
||||||
|
empty.mktmp("")
|
||||||
|
|
||||||
|
_ip.magic('run %s' % self.fname)
|
||||||
|
_ip.magic('run %s' % empty.fname)
|
||||||
|
nt.assert_equal(_ip.user_ns['afunc'](), 1)
|
||||||
|
|
||||||
|
@dec.skip_win32
|
||||||
|
def test_tclass(self):
|
||||||
|
mydir = os.path.dirname(__file__)
|
||||||
|
tc = os.path.join(mydir, 'tclass')
|
||||||
|
src = ("%%run '%s' C-first\n"
|
||||||
|
"%%run '%s' C-second\n"
|
||||||
|
"%%run '%s' C-third\n") % (tc, tc, tc)
|
||||||
|
self.mktmp(src, '.ipy')
|
||||||
|
out = """\
|
||||||
|
ARGV 1-: ['C-first']
|
||||||
|
ARGV 1-: ['C-second']
|
||||||
|
tclass.py: deleting object: C-first
|
||||||
|
ARGV 1-: ['C-third']
|
||||||
|
tclass.py: deleting object: C-second
|
||||||
|
tclass.py: deleting object: C-third
|
||||||
|
"""
|
||||||
|
if dec.module_not_available('sqlite3'):
|
||||||
|
err = 'WARNING: yap_ipython History requires SQLite, your history will not be saved\n'
|
||||||
|
else:
|
||||||
|
err = None
|
||||||
|
tt.ipexec_validate(self.fname, out, err)
|
||||||
|
|
||||||
|
def test_run_i_after_reset(self):
|
||||||
|
"""Check that %run -i still works after %reset (gh-693)"""
|
||||||
|
src = "yy = zz\n"
|
||||||
|
self.mktmp(src)
|
||||||
|
_ip.run_cell("zz = 23")
|
||||||
|
try:
|
||||||
|
_ip.magic('run -i %s' % self.fname)
|
||||||
|
nt.assert_equal(_ip.user_ns['yy'], 23)
|
||||||
|
finally:
|
||||||
|
_ip.magic('reset -f')
|
||||||
|
|
||||||
|
_ip.run_cell("zz = 23")
|
||||||
|
try:
|
||||||
|
_ip.magic('run -i %s' % self.fname)
|
||||||
|
nt.assert_equal(_ip.user_ns['yy'], 23)
|
||||||
|
finally:
|
||||||
|
_ip.magic('reset -f')
|
||||||
|
|
||||||
|
def test_unicode(self):
|
||||||
|
"""Check that files in odd encodings are accepted."""
|
||||||
|
mydir = os.path.dirname(__file__)
|
||||||
|
na = os.path.join(mydir, 'nonascii.py')
|
||||||
|
_ip.magic('run "%s"' % na)
|
||||||
|
nt.assert_equal(_ip.user_ns['u'], u'Ўт№Ф')
|
||||||
|
|
||||||
|
def test_run_py_file_attribute(self):
|
||||||
|
"""Test handling of `__file__` attribute in `%run <file>.py`."""
|
||||||
|
src = "t = __file__\n"
|
||||||
|
self.mktmp(src)
|
||||||
|
_missing = object()
|
||||||
|
file1 = _ip.user_ns.get('__file__', _missing)
|
||||||
|
_ip.magic('run %s' % self.fname)
|
||||||
|
file2 = _ip.user_ns.get('__file__', _missing)
|
||||||
|
|
||||||
|
# Check that __file__ was equal to the filename in the script's
|
||||||
|
# namespace.
|
||||||
|
nt.assert_equal(_ip.user_ns['t'], self.fname)
|
||||||
|
|
||||||
|
# Check that __file__ was not leaked back into user_ns.
|
||||||
|
nt.assert_equal(file1, file2)
|
||||||
|
|
||||||
|
def test_run_ipy_file_attribute(self):
|
||||||
|
"""Test handling of `__file__` attribute in `%run <file.ipy>`."""
|
||||||
|
src = "t = __file__\n"
|
||||||
|
self.mktmp(src, ext='.ipy')
|
||||||
|
_missing = object()
|
||||||
|
file1 = _ip.user_ns.get('__file__', _missing)
|
||||||
|
_ip.magic('run %s' % self.fname)
|
||||||
|
file2 = _ip.user_ns.get('__file__', _missing)
|
||||||
|
|
||||||
|
# Check that __file__ was equal to the filename in the script's
|
||||||
|
# namespace.
|
||||||
|
nt.assert_equal(_ip.user_ns['t'], self.fname)
|
||||||
|
|
||||||
|
# Check that __file__ was not leaked back into user_ns.
|
||||||
|
nt.assert_equal(file1, file2)
|
||||||
|
|
||||||
|
def test_run_formatting(self):
|
||||||
|
""" Test that %run -t -N<N> does not raise a TypeError for N > 1."""
|
||||||
|
src = "pass"
|
||||||
|
self.mktmp(src)
|
||||||
|
_ip.magic('run -t -N 1 %s' % self.fname)
|
||||||
|
_ip.magic('run -t -N 10 %s' % self.fname)
|
||||||
|
|
||||||
|
def test_ignore_sys_exit(self):
|
||||||
|
"""Test the -e option to ignore sys.exit()"""
|
||||||
|
src = "import sys; sys.exit(1)"
|
||||||
|
self.mktmp(src)
|
||||||
|
with tt.AssertPrints('SystemExit'):
|
||||||
|
_ip.magic('run %s' % self.fname)
|
||||||
|
|
||||||
|
with tt.AssertNotPrints('SystemExit'):
|
||||||
|
_ip.magic('run -e %s' % self.fname)
|
||||||
|
|
||||||
|
def test_run_nb(self):
|
||||||
|
"""Test %run notebook.ipynb"""
|
||||||
|
from nbformat import v4, writes
|
||||||
|
nb = v4.new_notebook(
|
||||||
|
cells=[
|
||||||
|
v4.new_markdown_cell("The Ultimate Question of Everything"),
|
||||||
|
v4.new_code_cell("answer=42")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
src = writes(nb, version=4)
|
||||||
|
self.mktmp(src, ext='.ipynb')
|
||||||
|
|
||||||
|
_ip.magic("run %s" % self.fname)
|
||||||
|
|
||||||
|
nt.assert_equal(_ip.user_ns['answer'], 42)
|
||||||
|
|
||||||
|
def test_file_options(self):
|
||||||
|
src = ('import sys\n'
|
||||||
|
'a = " ".join(sys.argv[1:])\n')
|
||||||
|
self.mktmp(src)
|
||||||
|
test_opts = '-x 3 --verbose'
|
||||||
|
_ip.run_line_magic("run", '{0} {1}'.format(self.fname, test_opts))
|
||||||
|
nt.assert_equal(_ip.user_ns['a'], test_opts)
|
||||||
|
|
||||||
|
|
||||||
|
class TestMagicRunWithPackage(unittest.TestCase):
|
||||||
|
|
||||||
|
def writefile(self, name, content):
|
||||||
|
path = os.path.join(self.tempdir.name, name)
|
||||||
|
d = os.path.dirname(path)
|
||||||
|
if not os.path.isdir(d):
|
||||||
|
os.makedirs(d)
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
f.write(textwrap.dedent(content))
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.package = package = 'tmp{0}'.format(''.join([random.choice(string.ascii_letters) for i in range(10)]))
|
||||||
|
"""Temporary (probably) valid python package name."""
|
||||||
|
|
||||||
|
self.value = int(random.random() * 10000)
|
||||||
|
|
||||||
|
self.tempdir = TemporaryDirectory()
|
||||||
|
self.__orig_cwd = os.getcwd()
|
||||||
|
sys.path.insert(0, self.tempdir.name)
|
||||||
|
|
||||||
|
self.writefile(os.path.join(package, '__init__.py'), '')
|
||||||
|
self.writefile(os.path.join(package, 'sub.py'), """
|
||||||
|
x = {0!r}
|
||||||
|
""".format(self.value))
|
||||||
|
self.writefile(os.path.join(package, 'relative.py'), """
|
||||||
|
from .sub import x
|
||||||
|
""")
|
||||||
|
self.writefile(os.path.join(package, 'absolute.py'), """
|
||||||
|
from {0}.sub import x
|
||||||
|
""".format(package))
|
||||||
|
self.writefile(os.path.join(package, 'args.py'), """
|
||||||
|
import sys
|
||||||
|
a = " ".join(sys.argv[1:])
|
||||||
|
""".format(package))
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
os.chdir(self.__orig_cwd)
|
||||||
|
sys.path[:] = [p for p in sys.path if p != self.tempdir.name]
|
||||||
|
self.tempdir.cleanup()
|
||||||
|
|
||||||
|
def check_run_submodule(self, submodule, opts=''):
|
||||||
|
_ip.user_ns.pop('x', None)
|
||||||
|
_ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts))
|
||||||
|
self.assertEqual(_ip.user_ns['x'], self.value,
|
||||||
|
'Variable `x` is not loaded from module `{0}`.'
|
||||||
|
.format(submodule))
|
||||||
|
|
||||||
|
def test_run_submodule_with_absolute_import(self):
|
||||||
|
self.check_run_submodule('absolute')
|
||||||
|
|
||||||
|
def test_run_submodule_with_relative_import(self):
|
||||||
|
"""Run submodule that has a relative import statement (#2727)."""
|
||||||
|
self.check_run_submodule('relative')
|
||||||
|
|
||||||
|
def test_prun_submodule_with_absolute_import(self):
|
||||||
|
self.check_run_submodule('absolute', '-p')
|
||||||
|
|
||||||
|
def test_prun_submodule_with_relative_import(self):
|
||||||
|
self.check_run_submodule('relative', '-p')
|
||||||
|
|
||||||
|
def with_fake_debugger(func):
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(*args, **kwds):
|
||||||
|
with patch.object(debugger.Pdb, 'run', staticmethod(eval)):
|
||||||
|
return func(*args, **kwds)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
@with_fake_debugger
|
||||||
|
def test_debug_run_submodule_with_absolute_import(self):
|
||||||
|
self.check_run_submodule('absolute', '-d')
|
||||||
|
|
||||||
|
@with_fake_debugger
|
||||||
|
def test_debug_run_submodule_with_relative_import(self):
|
||||||
|
self.check_run_submodule('relative', '-d')
|
||||||
|
|
||||||
|
def test_module_options(self):
|
||||||
|
_ip.user_ns.pop('a', None)
|
||||||
|
test_opts = '-x abc -m test'
|
||||||
|
_ip.run_line_magic('run', '-m {0}.args {1}'.format(self.package, test_opts))
|
||||||
|
nt.assert_equal(_ip.user_ns['a'], test_opts)
|
||||||
|
|
||||||
|
def test_module_options_with_separator(self):
|
||||||
|
_ip.user_ns.pop('a', None)
|
||||||
|
test_opts = '-x abc -m test'
|
||||||
|
_ip.run_line_magic('run', '-m {0}.args -- {1}'.format(self.package, test_opts))
|
||||||
|
nt.assert_equal(_ip.user_ns['a'], test_opts)
|
||||||
|
|
||||||
|
def test_run__name__():
|
||||||
|
with TemporaryDirectory() as td:
|
||||||
|
path = pjoin(td, 'foo.py')
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
f.write("q = __name__")
|
||||||
|
|
||||||
|
_ip.user_ns.pop('q', None)
|
||||||
|
_ip.magic('run {}'.format(path))
|
||||||
|
nt.assert_equal(_ip.user_ns.pop('q'), '__main__')
|
||||||
|
|
||||||
|
_ip.magic('run -n {}'.format(path))
|
||||||
|
nt.assert_equal(_ip.user_ns.pop('q'), 'foo')
|
||||||
|
|
||||||
|
try:
|
||||||
|
_ip.magic('run -i -n {}'.format(path))
|
||||||
|
nt.assert_equal(_ip.user_ns.pop('q'), 'foo')
|
||||||
|
finally:
|
||||||
|
_ip.magic('reset -f')
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_tb():
|
||||||
|
"""Test traceback offset in %run"""
|
||||||
|
with TemporaryDirectory() as td:
|
||||||
|
path = pjoin(td, 'foo.py')
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
f.write('\n'.join([
|
||||||
|
"def foo():",
|
||||||
|
" return bar()",
|
||||||
|
"def bar():",
|
||||||
|
" raise RuntimeError('hello!')",
|
||||||
|
"foo()",
|
||||||
|
]))
|
||||||
|
with capture_output() as io:
|
||||||
|
_ip.magic('run {}'.format(path))
|
||||||
|
out = io.stdout
|
||||||
|
nt.assert_not_in("execfile", out)
|
||||||
|
nt.assert_in("RuntimeError", out)
|
||||||
|
nt.assert_equal(out.count("---->"), 3)
|
||||||
|
|
||||||
|
@dec.knownfailureif(sys.platform == 'win32', "writes to io.stdout aren't captured on Windows")
|
||||||
|
def test_script_tb():
|
||||||
|
"""Test traceback offset in `ipython script.py`"""
|
||||||
|
with TemporaryDirectory() as td:
|
||||||
|
path = pjoin(td, 'foo.py')
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
f.write('\n'.join([
|
||||||
|
"def foo():",
|
||||||
|
" return bar()",
|
||||||
|
"def bar():",
|
||||||
|
" raise RuntimeError('hello!')",
|
||||||
|
"foo()",
|
||||||
|
]))
|
||||||
|
out, err = tt.ipexec(path)
|
||||||
|
nt.assert_not_in("execfile", out)
|
||||||
|
nt.assert_in("RuntimeError", out)
|
||||||
|
nt.assert_equal(out.count("---->"), 3)
|
||||||
|
|
@ -0,0 +1,56 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Tests for shellapp module.
|
||||||
|
|
||||||
|
Authors
|
||||||
|
-------
|
||||||
|
* Bradley Froehle
|
||||||
|
"""
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2012 The yap_ipython Development Team
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the BSD License. The full license is in
|
||||||
|
# the file COPYING, distributed as part of this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from yap_ipython.testing import decorators as dec
|
||||||
|
from yap_ipython.testing import tools as tt
|
||||||
|
|
||||||
|
sqlite_err_maybe = dec.module_not_available('sqlite3')
|
||||||
|
SQLITE_NOT_AVAILABLE_ERROR = ('WARNING: yap_ipython History requires SQLite,'
|
||||||
|
' your history will not be saved\n')
|
||||||
|
|
||||||
|
class TestFileToRun(unittest.TestCase, tt.TempFileMixin):
|
||||||
|
"""Test the behavior of the file_to_run parameter."""
|
||||||
|
|
||||||
|
def test_py_script_file_attribute(self):
|
||||||
|
"""Test that `__file__` is set when running `ipython file.py`"""
|
||||||
|
src = "print(__file__)\n"
|
||||||
|
self.mktmp(src)
|
||||||
|
|
||||||
|
err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None
|
||||||
|
tt.ipexec_validate(self.fname, self.fname, err)
|
||||||
|
|
||||||
|
def test_ipy_script_file_attribute(self):
|
||||||
|
"""Test that `__file__` is set when running `ipython file.ipy`"""
|
||||||
|
src = "print(__file__)\n"
|
||||||
|
self.mktmp(src, ext='.ipy')
|
||||||
|
|
||||||
|
err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None
|
||||||
|
tt.ipexec_validate(self.fname, self.fname, err)
|
||||||
|
|
||||||
|
# The commands option to ipexec_validate doesn't work on Windows, and it
|
||||||
|
# doesn't seem worth fixing
|
||||||
|
@dec.skip_win32
|
||||||
|
def test_py_script_file_attribute_interactively(self):
|
||||||
|
"""Test that `__file__` is not set after `ipython -i file.py`"""
|
||||||
|
src = "True\n"
|
||||||
|
self.mktmp(src)
|
||||||
|
|
||||||
|
out, err = tt.ipexec(self.fname, options=['-i'],
|
||||||
|
commands=['"__file__" in globals()', 'exit()'])
|
||||||
|
self.assertIn("False", out)
|
@ -0,0 +1,38 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython.core.splitinput import split_user_input, LineInfo
|
||||||
|
from yap_ipython.testing import tools as tt
|
||||||
|
|
||||||
|
tests = [
|
||||||
|
('x=1', ('', '', 'x', '=1')),
|
||||||
|
('?', ('', '?', '', '')),
|
||||||
|
('??', ('', '??', '', '')),
|
||||||
|
(' ?', (' ', '?', '', '')),
|
||||||
|
(' ??', (' ', '??', '', '')),
|
||||||
|
('??x', ('', '??', 'x', '')),
|
||||||
|
('?x=1', ('', '?', 'x', '=1')),
|
||||||
|
('!ls', ('', '!', 'ls', '')),
|
||||||
|
(' !ls', (' ', '!', 'ls', '')),
|
||||||
|
('!!ls', ('', '!!', 'ls', '')),
|
||||||
|
(' !!ls', (' ', '!!', 'ls', '')),
|
||||||
|
(',ls', ('', ',', 'ls', '')),
|
||||||
|
(';ls', ('', ';', 'ls', '')),
|
||||||
|
(' ;ls', (' ', ';', 'ls', '')),
|
||||||
|
('f.g(x)', ('', '', 'f.g', '(x)')),
|
||||||
|
('f.g (x)', ('', '', 'f.g', '(x)')),
|
||||||
|
('?%hist1', ('', '?', '%hist1', '')),
|
||||||
|
('?%%hist2', ('', '?', '%%hist2', '')),
|
||||||
|
('??%hist3', ('', '??', '%hist3', '')),
|
||||||
|
('??%%hist4', ('', '??', '%%hist4', '')),
|
||||||
|
('?x*', ('', '?', 'x*', '')),
|
||||||
|
]
|
||||||
|
tests.append((u"Pérez Fernando", (u'', u'', u'Pérez', u'Fernando')))
|
||||||
|
|
||||||
|
def test_split_user_input():
|
||||||
|
return tt.check_pairs(split_user_input, tests)
|
||||||
|
|
||||||
|
def test_LineInfo():
|
||||||
|
"""Simple test for LineInfo construction and str()"""
|
||||||
|
linfo = LineInfo(' %cd /home')
|
||||||
|
nt.assert_equal(str(linfo), 'LineInfo [ |%|cd|/home]')
|
@ -0,0 +1,400 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
"""Tests for yap_ipython.core.ultratb
|
||||||
|
"""
|
||||||
|
import io
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import os.path
|
||||||
|
from textwrap import dedent
|
||||||
|
import traceback
|
||||||
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from ..ultratb import ColorTB, VerboseTB, find_recursion
|
||||||
|
|
||||||
|
|
||||||
|
from yap_ipython.testing import tools as tt
|
||||||
|
from yap_ipython.testing.decorators import onlyif_unicode_paths
|
||||||
|
from yap_ipython.utils.syspathcontext import prepended_to_syspath
|
||||||
|
from yap_ipython.utils.tempdir import TemporaryDirectory
|
||||||
|
|
||||||
|
ip = get_ipython()
|
||||||
|
|
||||||
|
file_1 = """1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
def f():
|
||||||
|
1/0
|
||||||
|
"""
|
||||||
|
|
||||||
|
file_2 = """def f():
|
||||||
|
1/0
|
||||||
|
"""
|
||||||
|
|
||||||
|
class ChangedPyFileTest(unittest.TestCase):
|
||||||
|
def test_changing_py_file(self):
|
||||||
|
"""Traceback produced if the line where the error occurred is missing?
|
||||||
|
|
||||||
|
https://github.com/ipython/ipython/issues/1456
|
||||||
|
"""
|
||||||
|
with TemporaryDirectory() as td:
|
||||||
|
fname = os.path.join(td, "foo.py")
|
||||||
|
with open(fname, "w") as f:
|
||||||
|
f.write(file_1)
|
||||||
|
|
||||||
|
with prepended_to_syspath(td):
|
||||||
|
ip.run_cell("import foo")
|
||||||
|
|
||||||
|
with tt.AssertPrints("ZeroDivisionError"):
|
||||||
|
ip.run_cell("foo.f()")
|
||||||
|
|
||||||
|
# Make the file shorter, so the line of the error is missing.
|
||||||
|
with open(fname, "w") as f:
|
||||||
|
f.write(file_2)
|
||||||
|
|
||||||
|
# For some reason, this was failing on the *second* call after
|
||||||
|
# changing the file, so we call f() twice.
|
||||||
|
with tt.AssertNotPrints("Internal Python error", channel='stderr'):
|
||||||
|
with tt.AssertPrints("ZeroDivisionError"):
|
||||||
|
ip.run_cell("foo.f()")
|
||||||
|
with tt.AssertPrints("ZeroDivisionError"):
|
||||||
|
ip.run_cell("foo.f()")
|
||||||
|
|
||||||
|
iso_8859_5_file = u'''# coding: iso-8859-5
|
||||||
|
|
||||||
|
def fail():
|
||||||
|
"""дбИЖ"""
|
||||||
|
1/0 # дбИЖ
|
||||||
|
'''
|
||||||
|
|
||||||
|
class NonAsciiTest(unittest.TestCase):
|
||||||
|
@onlyif_unicode_paths
|
||||||
|
def test_nonascii_path(self):
|
||||||
|
# Non-ascii directory name as well.
|
||||||
|
with TemporaryDirectory(suffix=u'é') as td:
|
||||||
|
fname = os.path.join(td, u"fooé.py")
|
||||||
|
with open(fname, "w") as f:
|
||||||
|
f.write(file_1)
|
||||||
|
|
||||||
|
with prepended_to_syspath(td):
|
||||||
|
ip.run_cell("import foo")
|
||||||
|
|
||||||
|
with tt.AssertPrints("ZeroDivisionError"):
|
||||||
|
ip.run_cell("foo.f()")
|
||||||
|
|
||||||
|
def test_iso8859_5(self):
|
||||||
|
with TemporaryDirectory() as td:
|
||||||
|
fname = os.path.join(td, 'dfghjkl.py')
|
||||||
|
|
||||||
|
with io.open(fname, 'w', encoding='iso-8859-5') as f:
|
||||||
|
f.write(iso_8859_5_file)
|
||||||
|
|
||||||
|
with prepended_to_syspath(td):
|
||||||
|
ip.run_cell("from dfghjkl import fail")
|
||||||
|
|
||||||
|
with tt.AssertPrints("ZeroDivisionError"):
|
||||||
|
with tt.AssertPrints(u'дбИЖ', suppress=False):
|
||||||
|
ip.run_cell('fail()')
|
||||||
|
|
||||||
|
def test_nonascii_msg(self):
|
||||||
|
cell = u"raise Exception('é')"
|
||||||
|
expected = u"Exception('é')"
|
||||||
|
ip.run_cell("%xmode plain")
|
||||||
|
with tt.AssertPrints(expected):
|
||||||
|
ip.run_cell(cell)
|
||||||
|
|
||||||
|
ip.run_cell("%xmode verbose")
|
||||||
|
with tt.AssertPrints(expected):
|
||||||
|
ip.run_cell(cell)
|
||||||
|
|
||||||
|
ip.run_cell("%xmode context")
|
||||||
|
with tt.AssertPrints(expected):
|
||||||
|
ip.run_cell(cell)
|
||||||
|
|
||||||
|
|
||||||
|
class NestedGenExprTestCase(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Regression test for the following issues:
|
||||||
|
https://github.com/ipython/ipython/issues/8293
|
||||||
|
https://github.com/ipython/ipython/issues/8205
|
||||||
|
"""
|
||||||
|
def test_nested_genexpr(self):
|
||||||
|
code = dedent(
|
||||||
|
"""\
|
||||||
|
class SpecificException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def foo(x):
|
||||||
|
raise SpecificException("Success!")
|
||||||
|
|
||||||
|
sum(sum(foo(x) for _ in [0]) for x in [0])
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
with tt.AssertPrints('SpecificException: Success!', suppress=False):
|
||||||
|
ip.run_cell(code)
|
||||||
|
|
||||||
|
|
||||||
|
indentationerror_file = """if True:
|
||||||
|
zoon()
|
||||||
|
"""
|
||||||
|
|
||||||
|
class IndentationErrorTest(unittest.TestCase):
|
||||||
|
def test_indentationerror_shows_line(self):
|
||||||
|
# See issue gh-2398
|
||||||
|
with tt.AssertPrints("IndentationError"):
|
||||||
|
with tt.AssertPrints("zoon()", suppress=False):
|
||||||
|
ip.run_cell(indentationerror_file)
|
||||||
|
|
||||||
|
with TemporaryDirectory() as td:
|
||||||
|
fname = os.path.join(td, "foo.py")
|
||||||
|
with open(fname, "w") as f:
|
||||||
|
f.write(indentationerror_file)
|
||||||
|
|
||||||
|
with tt.AssertPrints("IndentationError"):
|
||||||
|
with tt.AssertPrints("zoon()", suppress=False):
|
||||||
|
ip.magic('run %s' % fname)
|
||||||
|
|
||||||
|
se_file_1 = """1
|
||||||
|
2
|
||||||
|
7/
|
||||||
|
"""
|
||||||
|
|
||||||
|
se_file_2 = """7/
|
||||||
|
"""
|
||||||
|
|
||||||
|
class SyntaxErrorTest(unittest.TestCase):
|
||||||
|
def test_syntaxerror_without_lineno(self):
|
||||||
|
with tt.AssertNotPrints("TypeError"):
|
||||||
|
with tt.AssertPrints("line unknown"):
|
||||||
|
ip.run_cell("raise SyntaxError()")
|
||||||
|
|
||||||
|
def test_syntaxerror_no_stacktrace_at_compile_time(self):
|
||||||
|
syntax_error_at_compile_time = """
|
||||||
|
def foo():
|
||||||
|
..
|
||||||
|
"""
|
||||||
|
with tt.AssertPrints("SyntaxError"):
|
||||||
|
ip.run_cell(syntax_error_at_compile_time)
|
||||||
|
|
||||||
|
with tt.AssertNotPrints("foo()"):
|
||||||
|
ip.run_cell(syntax_error_at_compile_time)
|
||||||
|
|
||||||
|
def test_syntaxerror_stacktrace_when_running_compiled_code(self):
|
||||||
|
syntax_error_at_runtime = """
|
||||||
|
def foo():
|
||||||
|
eval("..")
|
||||||
|
|
||||||
|
def bar():
|
||||||
|
foo()
|
||||||
|
|
||||||
|
bar()
|
||||||
|
"""
|
||||||
|
with tt.AssertPrints("SyntaxError"):
|
||||||
|
ip.run_cell(syntax_error_at_runtime)
|
||||||
|
# Assert syntax error during runtime generate stacktrace
|
||||||
|
with tt.AssertPrints(["foo()", "bar()"]):
|
||||||
|
ip.run_cell(syntax_error_at_runtime)
|
||||||
|
|
||||||
|
def test_changing_py_file(self):
|
||||||
|
with TemporaryDirectory() as td:
|
||||||
|
fname = os.path.join(td, "foo.py")
|
||||||
|
with open(fname, 'w') as f:
|
||||||
|
f.write(se_file_1)
|
||||||
|
|
||||||
|
with tt.AssertPrints(["7/", "SyntaxError"]):
|
||||||
|
ip.magic("run " + fname)
|
||||||
|
|
||||||
|
# Modify the file
|
||||||
|
with open(fname, 'w') as f:
|
||||||
|
f.write(se_file_2)
|
||||||
|
|
||||||
|
# The SyntaxError should point to the correct line
|
||||||
|
with tt.AssertPrints(["7/", "SyntaxError"]):
|
||||||
|
ip.magic("run " + fname)
|
||||||
|
|
||||||
|
def test_non_syntaxerror(self):
|
||||||
|
# SyntaxTB may be called with an error other than a SyntaxError
|
||||||
|
# See e.g. gh-4361
|
||||||
|
try:
|
||||||
|
raise ValueError('QWERTY')
|
||||||
|
except ValueError:
|
||||||
|
with tt.AssertPrints('QWERTY'):
|
||||||
|
ip.showsyntaxerror()
|
||||||
|
|
||||||
|
|
||||||
|
class Python3ChainedExceptionsTest(unittest.TestCase):
|
||||||
|
DIRECT_CAUSE_ERROR_CODE = """
|
||||||
|
try:
|
||||||
|
x = 1 + 2
|
||||||
|
print(not_defined_here)
|
||||||
|
except Exception as e:
|
||||||
|
x += 55
|
||||||
|
x - 1
|
||||||
|
y = {}
|
||||||
|
raise KeyError('uh') from e
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXCEPTION_DURING_HANDLING_CODE = """
|
||||||
|
try:
|
||||||
|
x = 1 + 2
|
||||||
|
print(not_defined_here)
|
||||||
|
except Exception as e:
|
||||||
|
x += 55
|
||||||
|
x - 1
|
||||||
|
y = {}
|
||||||
|
raise KeyError('uh')
|
||||||
|
"""
|
||||||
|
|
||||||
|
SUPPRESS_CHAINING_CODE = """
|
||||||
|
try:
|
||||||
|
1/0
|
||||||
|
except Exception:
|
||||||
|
raise ValueError("Yikes") from None
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_direct_cause_error(self):
|
||||||
|
with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
|
||||||
|
ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
|
||||||
|
|
||||||
|
def test_exception_during_handling_error(self):
|
||||||
|
with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
|
||||||
|
ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
|
||||||
|
|
||||||
|
def test_suppress_exception_chaining(self):
|
||||||
|
with tt.AssertNotPrints("ZeroDivisionError"), \
|
||||||
|
tt.AssertPrints("ValueError", suppress=False):
|
||||||
|
ip.run_cell(self.SUPPRESS_CHAINING_CODE)
|
||||||
|
|
||||||
|
|
||||||
|
class RecursionTest(unittest.TestCase):
|
||||||
|
DEFINITIONS = """
|
||||||
|
def non_recurs():
|
||||||
|
1/0
|
||||||
|
|
||||||
|
def r1():
|
||||||
|
r1()
|
||||||
|
|
||||||
|
def r3a():
|
||||||
|
r3b()
|
||||||
|
|
||||||
|
def r3b():
|
||||||
|
r3c()
|
||||||
|
|
||||||
|
def r3c():
|
||||||
|
r3a()
|
||||||
|
|
||||||
|
def r3o1():
|
||||||
|
r3a()
|
||||||
|
|
||||||
|
def r3o2():
|
||||||
|
r3o1()
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
ip.run_cell(self.DEFINITIONS)
|
||||||
|
|
||||||
|
def test_no_recursion(self):
|
||||||
|
with tt.AssertNotPrints("frames repeated"):
|
||||||
|
ip.run_cell("non_recurs()")
|
||||||
|
|
||||||
|
def test_recursion_one_frame(self):
|
||||||
|
with tt.AssertPrints("1 frames repeated"):
|
||||||
|
ip.run_cell("r1()")
|
||||||
|
|
||||||
|
def test_recursion_three_frames(self):
|
||||||
|
with tt.AssertPrints("3 frames repeated"):
|
||||||
|
ip.run_cell("r3o2()")
|
||||||
|
|
||||||
|
def test_find_recursion(self):
|
||||||
|
captured = []
|
||||||
|
def capture_exc(*args, **kwargs):
|
||||||
|
captured.append(sys.exc_info())
|
||||||
|
with mock.patch.object(ip, 'showtraceback', capture_exc):
|
||||||
|
ip.run_cell("r3o2()")
|
||||||
|
|
||||||
|
self.assertEqual(len(captured), 1)
|
||||||
|
etype, evalue, tb = captured[0]
|
||||||
|
self.assertIn("recursion", str(evalue))
|
||||||
|
|
||||||
|
records = ip.InteractiveTB.get_records(tb, 3, ip.InteractiveTB.tb_offset)
|
||||||
|
for r in records[:10]:
|
||||||
|
print(r[1:4])
|
||||||
|
|
||||||
|
# The outermost frames should be:
|
||||||
|
# 0: the 'cell' that was running when the exception came up
|
||||||
|
# 1: r3o2()
|
||||||
|
# 2: r3o1()
|
||||||
|
# 3: r3a()
|
||||||
|
# Then repeating r3b, r3c, r3a
|
||||||
|
last_unique, repeat_length = find_recursion(etype, evalue, records)
|
||||||
|
self.assertEqual(last_unique, 2)
|
||||||
|
self.assertEqual(repeat_length, 3)
|
||||||
|
|
||||||
|
|
||||||
|
#----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# module testing (minimal)
|
||||||
|
def test_handlers():
|
||||||
|
def spam(c, d_e):
|
||||||
|
(d, e) = d_e
|
||||||
|
x = c + d
|
||||||
|
y = c * d
|
||||||
|
foo(x, y)
|
||||||
|
|
||||||
|
def foo(a, b, bar=1):
|
||||||
|
eggs(a, b + bar)
|
||||||
|
|
||||||
|
def eggs(f, g, z=globals()):
|
||||||
|
h = f + g
|
||||||
|
i = f - g
|
||||||
|
return h / i
|
||||||
|
|
||||||
|
buff = io.StringIO()
|
||||||
|
|
||||||
|
buff.write('')
|
||||||
|
buff.write('*** Before ***')
|
||||||
|
try:
|
||||||
|
buff.write(spam(1, (2, 3)))
|
||||||
|
except:
|
||||||
|
traceback.print_exc(file=buff)
|
||||||
|
|
||||||
|
handler = ColorTB(ostream=buff)
|
||||||
|
buff.write('*** ColorTB ***')
|
||||||
|
try:
|
||||||
|
buff.write(spam(1, (2, 3)))
|
||||||
|
except:
|
||||||
|
handler(*sys.exc_info())
|
||||||
|
buff.write('')
|
||||||
|
|
||||||
|
handler = VerboseTB(ostream=buff)
|
||||||
|
buff.write('*** VerboseTB ***')
|
||||||
|
try:
|
||||||
|
buff.write(spam(1, (2, 3)))
|
||||||
|
except:
|
||||||
|
handler(*sys.exc_info())
|
||||||
|
buff.write('')
|
||||||
|
|
||||||
|
|
||||||
|
class TokenizeFailureTest(unittest.TestCase):
|
||||||
|
"""Tests related to https://github.com/ipython/ipython/issues/6864."""
|
||||||
|
|
||||||
|
def testLogging(self):
|
||||||
|
message = "An unexpected error occurred while tokenizing input"
|
||||||
|
cell = 'raise ValueError("""a\nb""")'
|
||||||
|
|
||||||
|
stream = io.StringIO()
|
||||||
|
handler = logging.StreamHandler(stream)
|
||||||
|
logger = logging.getLogger()
|
||||||
|
loglevel = logger.level
|
||||||
|
logger.addHandler(handler)
|
||||||
|
self.addCleanup(lambda: logger.removeHandler(handler))
|
||||||
|
self.addCleanup(lambda: logger.setLevel(loglevel))
|
||||||
|
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
with tt.AssertNotPrints(message):
|
||||||
|
ip.run_cell(cell)
|
||||||
|
self.assertNotIn(message, stream.getvalue())
|
||||||
|
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
with tt.AssertNotPrints(message):
|
||||||
|
ip.run_cell(cell)
|
||||||
|
self.assertIn(message, stream.getvalue())
|
@ -0,0 +1,342 @@
|
|||||||
|
"""Tests for autoreload extension.
|
||||||
|
"""
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (c) 2012 yap_ipython Development Team.
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
#
|
||||||
|
# The full license is in the file COPYING.txt, distributed with this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import textwrap
|
||||||
|
import shutil
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
import yap_ipython.testing.tools as tt
|
||||||
|
|
||||||
|
from yap_ipython.testing.decorators import skipif
|
||||||
|
|
||||||
|
from yap_ipython.extensions.autoreload import AutoreloadMagics
|
||||||
|
from yap_ipython.core.events import EventManager, pre_run_cell
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Test fixture
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
noop = lambda *a, **kw: None
|
||||||
|
|
||||||
|
class FakeShell(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.ns = {}
|
||||||
|
self.events = EventManager(self, {'pre_run_cell', pre_run_cell})
|
||||||
|
self.auto_magics = AutoreloadMagics(shell=self)
|
||||||
|
self.events.register('pre_run_cell', self.auto_magics.pre_run_cell)
|
||||||
|
|
||||||
|
register_magics = set_hook = noop
|
||||||
|
|
||||||
|
def run_code(self, code):
|
||||||
|
self.events.trigger('pre_run_cell')
|
||||||
|
exec(code, self.ns)
|
||||||
|
self.auto_magics.post_execute_hook()
|
||||||
|
|
||||||
|
def push(self, items):
|
||||||
|
self.ns.update(items)
|
||||||
|
|
||||||
|
def magic_autoreload(self, parameter):
|
||||||
|
self.auto_magics.autoreload(parameter)
|
||||||
|
|
||||||
|
def magic_aimport(self, parameter, stream=None):
|
||||||
|
self.auto_magics.aimport(parameter, stream=stream)
|
||||||
|
self.auto_magics.post_execute_hook()
|
||||||
|
|
||||||
|
|
||||||
|
class Fixture(object):
|
||||||
|
"""Fixture for creating test module files"""
|
||||||
|
|
||||||
|
test_dir = None
|
||||||
|
old_sys_path = None
|
||||||
|
filename_chars = "abcdefghijklmopqrstuvwxyz0123456789"
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.test_dir = tempfile.mkdtemp()
|
||||||
|
self.old_sys_path = list(sys.path)
|
||||||
|
sys.path.insert(0, self.test_dir)
|
||||||
|
self.shell = FakeShell()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
shutil.rmtree(self.test_dir)
|
||||||
|
sys.path = self.old_sys_path
|
||||||
|
|
||||||
|
self.test_dir = None
|
||||||
|
self.old_sys_path = None
|
||||||
|
self.shell = None
|
||||||
|
|
||||||
|
def get_module(self):
|
||||||
|
module_name = "tmpmod_" + "".join(random.sample(self.filename_chars,20))
|
||||||
|
if module_name in sys.modules:
|
||||||
|
del sys.modules[module_name]
|
||||||
|
file_name = os.path.join(self.test_dir, module_name + ".py")
|
||||||
|
return module_name, file_name
|
||||||
|
|
||||||
|
def write_file(self, filename, content):
|
||||||
|
"""
|
||||||
|
Write a file, and force a timestamp difference of at least one second
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
Python's .pyc files record the timestamp of their compilation
|
||||||
|
with a time resolution of one second.
|
||||||
|
|
||||||
|
Therefore, we need to force a timestamp difference between .py
|
||||||
|
and .pyc, without having the .py file be timestamped in the
|
||||||
|
future, and without changing the timestamp of the .pyc file
|
||||||
|
(because that is stored in the file). The only reliable way
|
||||||
|
to achieve this seems to be to sleep.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Sleep one second + eps
|
||||||
|
time.sleep(1.05)
|
||||||
|
|
||||||
|
# Write
|
||||||
|
f = open(filename, 'w')
|
||||||
|
try:
|
||||||
|
f.write(content)
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
def new_module(self, code):
|
||||||
|
mod_name, mod_fn = self.get_module()
|
||||||
|
f = open(mod_fn, 'w')
|
||||||
|
try:
|
||||||
|
f.write(code)
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
return mod_name, mod_fn
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Test automatic reloading
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class TestAutoreload(Fixture):
|
||||||
|
|
||||||
|
@skipif(sys.version_info < (3, 6))
|
||||||
|
def test_reload_enums(self):
|
||||||
|
import enum
|
||||||
|
mod_name, mod_fn = self.new_module(textwrap.dedent("""
|
||||||
|
from enum import Enum
|
||||||
|
class MyEnum(Enum):
|
||||||
|
A = 'A'
|
||||||
|
B = 'B'
|
||||||
|
"""))
|
||||||
|
self.shell.magic_autoreload("2")
|
||||||
|
self.shell.magic_aimport(mod_name)
|
||||||
|
self.write_file(mod_fn, textwrap.dedent("""
|
||||||
|
from enum import Enum
|
||||||
|
class MyEnum(Enum):
|
||||||
|
A = 'A'
|
||||||
|
B = 'B'
|
||||||
|
C = 'C'
|
||||||
|
"""))
|
||||||
|
with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
|
||||||
|
self.shell.run_code("pass") # trigger another reload
|
||||||
|
|
||||||
|
|
||||||
|
def _check_smoketest(self, use_aimport=True):
|
||||||
|
"""
|
||||||
|
Functional test for the automatic reloader using either
|
||||||
|
'%autoreload 1' or '%autoreload 2'
|
||||||
|
"""
|
||||||
|
|
||||||
|
mod_name, mod_fn = self.new_module("""
|
||||||
|
x = 9
|
||||||
|
|
||||||
|
z = 123 # this item will be deleted
|
||||||
|
|
||||||
|
def foo(y):
|
||||||
|
return y + 3
|
||||||
|
|
||||||
|
class Baz(object):
|
||||||
|
def __init__(self, x):
|
||||||
|
self.x = x
|
||||||
|
def bar(self, y):
|
||||||
|
return self.x + y
|
||||||
|
@property
|
||||||
|
def quux(self):
|
||||||
|
return 42
|
||||||
|
def zzz(self):
|
||||||
|
'''This method will be deleted below'''
|
||||||
|
return 99
|
||||||
|
|
||||||
|
class Bar: # old-style class: weakref doesn't work for it on Python < 2.7
|
||||||
|
def foo(self):
|
||||||
|
return 1
|
||||||
|
""")
|
||||||
|
|
||||||
|
#
|
||||||
|
# Import module, and mark for reloading
|
||||||
|
#
|
||||||
|
if use_aimport:
|
||||||
|
self.shell.magic_autoreload("1")
|
||||||
|
self.shell.magic_aimport(mod_name)
|
||||||
|
stream = StringIO()
|
||||||
|
self.shell.magic_aimport("", stream=stream)
|
||||||
|
nt.assert_in(("Modules to reload:\n%s" % mod_name), stream.getvalue())
|
||||||
|
|
||||||
|
with nt.assert_raises(ImportError):
|
||||||
|
self.shell.magic_aimport("tmpmod_as318989e89ds")
|
||||||
|
else:
|
||||||
|
self.shell.magic_autoreload("2")
|
||||||
|
self.shell.run_code("import %s" % mod_name)
|
||||||
|
stream = StringIO()
|
||||||
|
self.shell.magic_aimport("", stream=stream)
|
||||||
|
nt.assert_true("Modules to reload:\nall-except-skipped" in
|
||||||
|
stream.getvalue())
|
||||||
|
nt.assert_in(mod_name, self.shell.ns)
|
||||||
|
|
||||||
|
mod = sys.modules[mod_name]
|
||||||
|
|
||||||
|
#
|
||||||
|
# Test module contents
|
||||||
|
#
|
||||||
|
old_foo = mod.foo
|
||||||
|
old_obj = mod.Baz(9)
|
||||||
|
old_obj2 = mod.Bar()
|
||||||
|
|
||||||
|
def check_module_contents():
|
||||||
|
nt.assert_equal(mod.x, 9)
|
||||||
|
nt.assert_equal(mod.z, 123)
|
||||||
|
|
||||||
|
nt.assert_equal(old_foo(0), 3)
|
||||||
|
nt.assert_equal(mod.foo(0), 3)
|
||||||
|
|
||||||
|
obj = mod.Baz(9)
|
||||||
|
nt.assert_equal(old_obj.bar(1), 10)
|
||||||
|
nt.assert_equal(obj.bar(1), 10)
|
||||||
|
nt.assert_equal(obj.quux, 42)
|
||||||
|
nt.assert_equal(obj.zzz(), 99)
|
||||||
|
|
||||||
|
obj2 = mod.Bar()
|
||||||
|
nt.assert_equal(old_obj2.foo(), 1)
|
||||||
|
nt.assert_equal(obj2.foo(), 1)
|
||||||
|
|
||||||
|
check_module_contents()
|
||||||
|
|
||||||
|
#
|
||||||
|
# Simulate a failed reload: no reload should occur and exactly
|
||||||
|
# one error message should be printed
|
||||||
|
#
|
||||||
|
self.write_file(mod_fn, """
|
||||||
|
a syntax error
|
||||||
|
""")
|
||||||
|
|
||||||
|
with tt.AssertPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
|
||||||
|
self.shell.run_code("pass") # trigger reload
|
||||||
|
with tt.AssertNotPrints(('[autoreload of %s failed:' % mod_name), channel='stderr'):
|
||||||
|
self.shell.run_code("pass") # trigger another reload
|
||||||
|
check_module_contents()
|
||||||
|
|
||||||
|
#
|
||||||
|
# Rewrite module (this time reload should succeed)
|
||||||
|
#
|
||||||
|
self.write_file(mod_fn, """
|
||||||
|
x = 10
|
||||||
|
|
||||||
|
def foo(y):
|
||||||
|
return y + 4
|
||||||
|
|
||||||
|
class Baz(object):
|
||||||
|
def __init__(self, x):
|
||||||
|
self.x = x
|
||||||
|
def bar(self, y):
|
||||||
|
return self.x + y + 1
|
||||||
|
@property
|
||||||
|
def quux(self):
|
||||||
|
return 43
|
||||||
|
|
||||||
|
class Bar: # old-style class
|
||||||
|
def foo(self):
|
||||||
|
return 2
|
||||||
|
""")
|
||||||
|
|
||||||
|
def check_module_contents():
|
||||||
|
nt.assert_equal(mod.x, 10)
|
||||||
|
nt.assert_false(hasattr(mod, 'z'))
|
||||||
|
|
||||||
|
nt.assert_equal(old_foo(0), 4) # superreload magic!
|
||||||
|
nt.assert_equal(mod.foo(0), 4)
|
||||||
|
|
||||||
|
obj = mod.Baz(9)
|
||||||
|
nt.assert_equal(old_obj.bar(1), 11) # superreload magic!
|
||||||
|
nt.assert_equal(obj.bar(1), 11)
|
||||||
|
|
||||||
|
nt.assert_equal(old_obj.quux, 43)
|
||||||
|
nt.assert_equal(obj.quux, 43)
|
||||||
|
|
||||||
|
nt.assert_false(hasattr(old_obj, 'zzz'))
|
||||||
|
nt.assert_false(hasattr(obj, 'zzz'))
|
||||||
|
|
||||||
|
obj2 = mod.Bar()
|
||||||
|
nt.assert_equal(old_obj2.foo(), 2)
|
||||||
|
nt.assert_equal(obj2.foo(), 2)
|
||||||
|
|
||||||
|
self.shell.run_code("pass") # trigger reload
|
||||||
|
check_module_contents()
|
||||||
|
|
||||||
|
#
|
||||||
|
# Another failure case: deleted file (shouldn't reload)
|
||||||
|
#
|
||||||
|
os.unlink(mod_fn)
|
||||||
|
|
||||||
|
self.shell.run_code("pass") # trigger reload
|
||||||
|
check_module_contents()
|
||||||
|
|
||||||
|
#
|
||||||
|
# Disable autoreload and rewrite module: no reload should occur
|
||||||
|
#
|
||||||
|
if use_aimport:
|
||||||
|
self.shell.magic_aimport("-" + mod_name)
|
||||||
|
stream = StringIO()
|
||||||
|
self.shell.magic_aimport("", stream=stream)
|
||||||
|
nt.assert_true(("Modules to skip:\n%s" % mod_name) in
|
||||||
|
stream.getvalue())
|
||||||
|
|
||||||
|
# This should succeed, although no such module exists
|
||||||
|
self.shell.magic_aimport("-tmpmod_as318989e89ds")
|
||||||
|
else:
|
||||||
|
self.shell.magic_autoreload("0")
|
||||||
|
|
||||||
|
self.write_file(mod_fn, """
|
||||||
|
x = -99
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.shell.run_code("pass") # trigger reload
|
||||||
|
self.shell.run_code("pass")
|
||||||
|
check_module_contents()
|
||||||
|
|
||||||
|
#
|
||||||
|
# Re-enable autoreload: reload should now occur
|
||||||
|
#
|
||||||
|
if use_aimport:
|
||||||
|
self.shell.magic_aimport(mod_name)
|
||||||
|
else:
|
||||||
|
self.shell.magic_autoreload("")
|
||||||
|
|
||||||
|
self.shell.run_code("pass") # trigger reload
|
||||||
|
nt.assert_equal(mod.x, -99)
|
||||||
|
|
||||||
|
def test_smoketest_aimport(self):
|
||||||
|
self._check_smoketest(use_aimport=True)
|
||||||
|
|
||||||
|
def test_smoketest_autoreload(self):
|
||||||
|
self._check_smoketest(use_aimport=False)
|
@ -0,0 +1,50 @@
|
|||||||
|
import tempfile, os
|
||||||
|
|
||||||
|
from traitlets.config.loader import Config
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
ip = get_ipython()
|
||||||
|
ip.magic('load_ext storemagic')
|
||||||
|
|
||||||
|
def test_store_restore():
|
||||||
|
ip.user_ns['foo'] = 78
|
||||||
|
ip.magic('alias bar echo "hello"')
|
||||||
|
tmpd = tempfile.mkdtemp()
|
||||||
|
ip.magic('cd ' + tmpd)
|
||||||
|
ip.magic('store foo')
|
||||||
|
ip.magic('store bar')
|
||||||
|
|
||||||
|
# Check storing
|
||||||
|
nt.assert_equal(ip.db['autorestore/foo'], 78)
|
||||||
|
nt.assert_in('bar', ip.db['stored_aliases'])
|
||||||
|
|
||||||
|
# Remove those items
|
||||||
|
ip.user_ns.pop('foo', None)
|
||||||
|
ip.alias_manager.undefine_alias('bar')
|
||||||
|
ip.magic('cd -')
|
||||||
|
ip.user_ns['_dh'][:] = []
|
||||||
|
|
||||||
|
# Check restoring
|
||||||
|
ip.magic('store -r')
|
||||||
|
nt.assert_equal(ip.user_ns['foo'], 78)
|
||||||
|
assert ip.alias_manager.is_alias('bar')
|
||||||
|
nt.assert_in(os.path.realpath(tmpd), ip.user_ns['_dh'])
|
||||||
|
|
||||||
|
os.rmdir(tmpd)
|
||||||
|
|
||||||
|
def test_autorestore():
|
||||||
|
ip.user_ns['foo'] = 95
|
||||||
|
ip.magic('store foo')
|
||||||
|
del ip.user_ns['foo']
|
||||||
|
c = Config()
|
||||||
|
c.StoreMagics.autorestore = False
|
||||||
|
orig_config = ip.config
|
||||||
|
try:
|
||||||
|
ip.config = c
|
||||||
|
ip.extension_manager.reload_extension('storemagic')
|
||||||
|
nt.assert_not_in('foo', ip.user_ns)
|
||||||
|
c.StoreMagics.autorestore = True
|
||||||
|
ip.extension_manager.reload_extension('storemagic')
|
||||||
|
nt.assert_equal(ip.user_ns['foo'], 95)
|
||||||
|
finally:
|
||||||
|
ip.config = orig_config
|
9
packages/python/yap_kernel/yap_ipython/external/decorators/__init__.py
vendored
Normal file
9
packages/python/yap_kernel/yap_ipython/external/decorators/__init__.py
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
try:
|
||||||
|
from numpy.testing.decorators import *
|
||||||
|
from numpy.testing.noseclasses import KnownFailure
|
||||||
|
except ImportError:
|
||||||
|
from ._decorators import *
|
||||||
|
try:
|
||||||
|
from ._numpy_testing_noseclasses import KnownFailure
|
||||||
|
except ImportError:
|
||||||
|
pass
|
143
packages/python/yap_kernel/yap_ipython/external/decorators/_decorators.py
vendored
Normal file
143
packages/python/yap_kernel/yap_ipython/external/decorators/_decorators.py
vendored
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
"""
|
||||||
|
Decorators for labeling and modifying behavior of test objects.
|
||||||
|
|
||||||
|
Decorators that merely return a modified version of the original
|
||||||
|
function object are straightforward. Decorators that return a new
|
||||||
|
function object need to use
|
||||||
|
::
|
||||||
|
|
||||||
|
nose.tools.make_decorator(original_function)(decorator)
|
||||||
|
|
||||||
|
in returning the decorator, in order to preserve meta-data such as
|
||||||
|
function name, setup and teardown functions and so on - see
|
||||||
|
``nose.tools`` for more information.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# yap_ipython changes: make this work if numpy not available
|
||||||
|
# Original code:
|
||||||
|
try:
|
||||||
|
from ._numpy_testing_noseclasses import KnownFailureTest
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# End yap_ipython changes
|
||||||
|
|
||||||
|
|
||||||
|
def skipif(skip_condition, msg=None):
|
||||||
|
"""
|
||||||
|
Make function raise SkipTest exception if a given condition is true.
|
||||||
|
|
||||||
|
If the condition is a callable, it is used at runtime to dynamically
|
||||||
|
make the decision. This is useful for tests that may require costly
|
||||||
|
imports, to delay the cost until the test suite is actually executed.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
skip_condition : bool or callable
|
||||||
|
Flag to determine whether to skip the decorated test.
|
||||||
|
msg : str, optional
|
||||||
|
Message to give on raising a SkipTest exception. Default is None.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
decorator : function
|
||||||
|
Decorator which, when applied to a function, causes SkipTest
|
||||||
|
to be raised when `skip_condition` is True, and the function
|
||||||
|
to be called normally otherwise.
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
The decorator itself is decorated with the ``nose.tools.make_decorator``
|
||||||
|
function in order to transmit function name, and various other metadata.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def skip_decorator(f):
|
||||||
|
# Local import to avoid a hard nose dependency and only incur the
|
||||||
|
# import time overhead at actual test-time.
|
||||||
|
import nose
|
||||||
|
|
||||||
|
# Allow for both boolean or callable skip conditions.
|
||||||
|
if callable(skip_condition):
|
||||||
|
skip_val = lambda : skip_condition()
|
||||||
|
else:
|
||||||
|
skip_val = lambda : skip_condition
|
||||||
|
|
||||||
|
def get_msg(func,msg=None):
|
||||||
|
"""Skip message with information about function being skipped."""
|
||||||
|
if msg is None:
|
||||||
|
out = 'Test skipped due to test condition'
|
||||||
|
else:
|
||||||
|
out = '\n'+msg
|
||||||
|
|
||||||
|
return "Skipping test: %s%s" % (func.__name__,out)
|
||||||
|
|
||||||
|
# We need to define *two* skippers because Python doesn't allow both
|
||||||
|
# return with value and yield inside the same function.
|
||||||
|
def skipper_func(*args, **kwargs):
|
||||||
|
"""Skipper for normal test functions."""
|
||||||
|
if skip_val():
|
||||||
|
raise nose.SkipTest(get_msg(f,msg))
|
||||||
|
else:
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
def skipper_gen(*args, **kwargs):
|
||||||
|
"""Skipper for test generators."""
|
||||||
|
if skip_val():
|
||||||
|
raise nose.SkipTest(get_msg(f,msg))
|
||||||
|
else:
|
||||||
|
for x in f(*args, **kwargs):
|
||||||
|
yield x
|
||||||
|
|
||||||
|
# Choose the right skipper to use when building the actual decorator.
|
||||||
|
if nose.util.isgenerator(f):
|
||||||
|
skipper = skipper_gen
|
||||||
|
else:
|
||||||
|
skipper = skipper_func
|
||||||
|
|
||||||
|
return nose.tools.make_decorator(f)(skipper)
|
||||||
|
|
||||||
|
return skip_decorator
|
||||||
|
|
||||||
|
def knownfailureif(fail_condition, msg=None):
|
||||||
|
"""
|
||||||
|
Make function raise KnownFailureTest exception if given condition is true.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
fail_condition : bool
|
||||||
|
Flag to determine whether to mark the decorated test as a known
|
||||||
|
failure (if True) or not (if False).
|
||||||
|
msg : str, optional
|
||||||
|
Message to give on raising a KnownFailureTest exception.
|
||||||
|
Default is None.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
decorator : function
|
||||||
|
Decorator, which, when applied to a function, causes KnownFailureTest to
|
||||||
|
be raised when `fail_condition` is True and the test fails.
|
||||||
|
|
||||||
|
Notes
|
||||||
|
-----
|
||||||
|
The decorator itself is decorated with the ``nose.tools.make_decorator``
|
||||||
|
function in order to transmit function name, and various other metadata.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if msg is None:
|
||||||
|
msg = 'Test skipped due to known failure'
|
||||||
|
|
||||||
|
def knownfail_decorator(f):
|
||||||
|
# Local import to avoid a hard nose dependency and only incur the
|
||||||
|
# import time overhead at actual test-time.
|
||||||
|
import nose
|
||||||
|
|
||||||
|
def knownfailer(*args, **kwargs):
|
||||||
|
if fail_condition:
|
||||||
|
raise KnownFailureTest(msg)
|
||||||
|
else:
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
return nose.tools.make_decorator(f)(knownfailer)
|
||||||
|
|
||||||
|
return knownfail_decorator
|
41
packages/python/yap_kernel/yap_ipython/external/decorators/_numpy_testing_noseclasses.py
vendored
Normal file
41
packages/python/yap_kernel/yap_ipython/external/decorators/_numpy_testing_noseclasses.py
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# yap_ipython: modified copy of numpy.testing.noseclasses, so
|
||||||
|
# yap_ipython.external._decorators works without numpy being installed.
|
||||||
|
|
||||||
|
# These classes implement a "known failure" error class.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from nose.plugins.errorclass import ErrorClass, ErrorClassPlugin
|
||||||
|
|
||||||
|
class KnownFailureTest(Exception):
|
||||||
|
'''Raise this exception to mark a test as a known failing test.'''
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class KnownFailure(ErrorClassPlugin):
|
||||||
|
'''Plugin that installs a KNOWNFAIL error class for the
|
||||||
|
KnownFailureClass exception. When KnownFailureTest is raised,
|
||||||
|
the exception will be logged in the knownfail attribute of the
|
||||||
|
result, 'K' or 'KNOWNFAIL' (verbose) will be output, and the
|
||||||
|
exception will not be counted as an error or failure.'''
|
||||||
|
enabled = True
|
||||||
|
knownfail = ErrorClass(KnownFailureTest,
|
||||||
|
label='KNOWNFAIL',
|
||||||
|
isfailure=False)
|
||||||
|
|
||||||
|
def options(self, parser, env=os.environ):
|
||||||
|
env_opt = 'NOSE_WITHOUT_KNOWNFAIL'
|
||||||
|
parser.add_option('--no-knownfail', action='store_true',
|
||||||
|
dest='noKnownFail', default=env.get(env_opt, False),
|
||||||
|
help='Disable special handling of KnownFailureTest '
|
||||||
|
'exceptions')
|
||||||
|
|
||||||
|
def configure(self, options, conf):
|
||||||
|
if not self.can_configure:
|
||||||
|
return
|
||||||
|
self.conf = conf
|
||||||
|
disable = getattr(options, 'noKnownFail', False)
|
||||||
|
if disable:
|
||||||
|
self.enabled = False
|
||||||
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
|||||||
|
"""Tests for pylab tools module.
|
||||||
|
"""
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (c) 2011, the yap_ipython Development Team.
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
#
|
||||||
|
# The full license is in the file COPYING.txt, distributed with this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Stdlib imports
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Third-party imports
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
# Our own imports
|
||||||
|
from yap_ipython.lib import backgroundjobs as bg
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Globals and constants
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
t_short = 0.0001 # very short interval to wait on jobs
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Local utilities
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
def sleeper(interval=t_short, *a, **kw):
|
||||||
|
args = dict(interval=interval,
|
||||||
|
other_args=a,
|
||||||
|
kw_args=kw)
|
||||||
|
time.sleep(interval)
|
||||||
|
return args
|
||||||
|
|
||||||
|
def crasher(interval=t_short, *a, **kw):
|
||||||
|
time.sleep(interval)
|
||||||
|
raise Exception("Dead job with interval %s" % interval)
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Classes and functions
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_result():
|
||||||
|
"""Test job submission and result retrieval"""
|
||||||
|
jobs = bg.BackgroundJobManager()
|
||||||
|
j = jobs.new(sleeper)
|
||||||
|
j.join()
|
||||||
|
nt.assert_equal(j.result['interval'], t_short)
|
||||||
|
|
||||||
|
|
||||||
|
def test_flush():
|
||||||
|
"""Test job control"""
|
||||||
|
jobs = bg.BackgroundJobManager()
|
||||||
|
j = jobs.new(sleeper)
|
||||||
|
j.join()
|
||||||
|
nt.assert_equal(len(jobs.completed), 1)
|
||||||
|
nt.assert_equal(len(jobs.dead), 0)
|
||||||
|
jobs.flush()
|
||||||
|
nt.assert_equal(len(jobs.completed), 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_dead():
|
||||||
|
"""Test control of dead jobs"""
|
||||||
|
jobs = bg.BackgroundJobManager()
|
||||||
|
j = jobs.new(crasher)
|
||||||
|
j.join()
|
||||||
|
nt.assert_equal(len(jobs.completed), 0)
|
||||||
|
nt.assert_equal(len(jobs.dead), 1)
|
||||||
|
jobs.flush()
|
||||||
|
nt.assert_equal(len(jobs.dead), 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_longer():
|
||||||
|
"""Test control of longer-running jobs"""
|
||||||
|
jobs = bg.BackgroundJobManager()
|
||||||
|
# Sleep for long enough for the following two checks to still report the
|
||||||
|
# job as running, but not so long that it makes the test suite noticeably
|
||||||
|
# slower.
|
||||||
|
j = jobs.new(sleeper, 0.1)
|
||||||
|
nt.assert_equal(len(jobs.running), 1)
|
||||||
|
nt.assert_equal(len(jobs.completed), 0)
|
||||||
|
j.join()
|
||||||
|
nt.assert_equal(len(jobs.running), 0)
|
||||||
|
nt.assert_equal(len(jobs.completed), 1)
|
@ -0,0 +1,21 @@
|
|||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython.core.error import TryNext
|
||||||
|
from yap_ipython.lib.clipboard import ClipboardEmpty
|
||||||
|
from yap_ipython.testing.decorators import skip_if_no_x11
|
||||||
|
|
||||||
|
@skip_if_no_x11
|
||||||
|
def test_clipboard_get():
|
||||||
|
# Smoketest for clipboard access - we can't easily guarantee that the
|
||||||
|
# clipboard is accessible and has something on it, but this tries to
|
||||||
|
# exercise the relevant code anyway.
|
||||||
|
try:
|
||||||
|
a = get_ipython().hooks.clipboard_get()
|
||||||
|
except ClipboardEmpty:
|
||||||
|
# Nothing in clipboard to get
|
||||||
|
pass
|
||||||
|
except TryNext:
|
||||||
|
# No clipboard access API available
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
nt.assert_is_instance(a, str)
|
@ -0,0 +1,34 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Test suite for the deepreload module."""
|
||||||
|
|
||||||
|
# Copyright (c) yap_ipython Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython.utils.syspathcontext import prepended_to_syspath
|
||||||
|
from yap_ipython.utils.tempdir import TemporaryDirectory
|
||||||
|
from yap_ipython.lib.deepreload import reload as dreload
|
||||||
|
|
||||||
|
def test_deepreload():
|
||||||
|
"Test that dreload does deep reloads and skips excluded modules."
|
||||||
|
with TemporaryDirectory() as tmpdir:
|
||||||
|
with prepended_to_syspath(tmpdir):
|
||||||
|
with open(os.path.join(tmpdir, 'A.py'), 'w') as f:
|
||||||
|
f.write("class Object(object):\n pass\n")
|
||||||
|
with open(os.path.join(tmpdir, 'B.py'), 'w') as f:
|
||||||
|
f.write("import A\n")
|
||||||
|
import A
|
||||||
|
import B
|
||||||
|
|
||||||
|
# Test that A is not reloaded.
|
||||||
|
obj = A.Object()
|
||||||
|
dreload(B, exclude=['A'])
|
||||||
|
nt.assert_true(isinstance(obj, A.Object))
|
||||||
|
|
||||||
|
# Test that A is reloaded.
|
||||||
|
obj = A.Object()
|
||||||
|
dreload(B)
|
||||||
|
nt.assert_false(isinstance(obj, A.Object))
|
177
packages/python/yap_kernel/yap_ipython/lib/tests/test_display.py
Normal file
177
packages/python/yap_kernel/yap_ipython/lib/tests/test_display.py
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
"""Tests for yap_ipython.lib.display.
|
||||||
|
|
||||||
|
"""
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (c) 2012, the yap_ipython Development Team.
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
#
|
||||||
|
# The full license is in the file COPYING.txt, distributed with this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
from tempfile import NamedTemporaryFile, mkdtemp
|
||||||
|
from os.path import split, join as pjoin, dirname
|
||||||
|
|
||||||
|
# Third-party imports
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
# Our own imports
|
||||||
|
from yap_ipython.lib import display
|
||||||
|
from yap_ipython.testing.decorators import skipif_not_numpy
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Classes and functions
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#--------------------------
|
||||||
|
# FileLink tests
|
||||||
|
#--------------------------
|
||||||
|
|
||||||
|
def test_instantiation_FileLink():
|
||||||
|
"""FileLink: Test class can be instantiated"""
|
||||||
|
fl = display.FileLink('example.txt')
|
||||||
|
|
||||||
|
def test_warning_on_non_existant_path_FileLink():
|
||||||
|
"""FileLink: Calling _repr_html_ on non-existant files returns a warning
|
||||||
|
"""
|
||||||
|
fl = display.FileLink('example.txt')
|
||||||
|
nt.assert_true(fl._repr_html_().startswith('Path (<tt>example.txt</tt>)'))
|
||||||
|
|
||||||
|
def test_existing_path_FileLink():
|
||||||
|
"""FileLink: Calling _repr_html_ functions as expected on existing filepath
|
||||||
|
"""
|
||||||
|
tf = NamedTemporaryFile()
|
||||||
|
fl = display.FileLink(tf.name)
|
||||||
|
actual = fl._repr_html_()
|
||||||
|
expected = "<a href='%s' target='_blank'>%s</a><br>" % (tf.name,tf.name)
|
||||||
|
nt.assert_equal(actual,expected)
|
||||||
|
|
||||||
|
def test_existing_path_FileLink_repr():
|
||||||
|
"""FileLink: Calling repr() functions as expected on existing filepath
|
||||||
|
"""
|
||||||
|
tf = NamedTemporaryFile()
|
||||||
|
fl = display.FileLink(tf.name)
|
||||||
|
actual = repr(fl)
|
||||||
|
expected = tf.name
|
||||||
|
nt.assert_equal(actual,expected)
|
||||||
|
|
||||||
|
def test_error_on_directory_to_FileLink():
|
||||||
|
"""FileLink: Raises error when passed directory
|
||||||
|
"""
|
||||||
|
td = mkdtemp()
|
||||||
|
nt.assert_raises(ValueError,display.FileLink,td)
|
||||||
|
|
||||||
|
#--------------------------
|
||||||
|
# FileLinks tests
|
||||||
|
#--------------------------
|
||||||
|
|
||||||
|
def test_instantiation_FileLinks():
|
||||||
|
"""FileLinks: Test class can be instantiated
|
||||||
|
"""
|
||||||
|
fls = display.FileLinks('example')
|
||||||
|
|
||||||
|
def test_warning_on_non_existant_path_FileLinks():
|
||||||
|
"""FileLinks: Calling _repr_html_ on non-existant files returns a warning
|
||||||
|
"""
|
||||||
|
fls = display.FileLinks('example')
|
||||||
|
nt.assert_true(fls._repr_html_().startswith('Path (<tt>example</tt>)'))
|
||||||
|
|
||||||
|
def test_existing_path_FileLinks():
|
||||||
|
"""FileLinks: Calling _repr_html_ functions as expected on existing dir
|
||||||
|
"""
|
||||||
|
td = mkdtemp()
|
||||||
|
tf1 = NamedTemporaryFile(dir=td)
|
||||||
|
tf2 = NamedTemporaryFile(dir=td)
|
||||||
|
fl = display.FileLinks(td)
|
||||||
|
actual = fl._repr_html_()
|
||||||
|
actual = actual.split('\n')
|
||||||
|
actual.sort()
|
||||||
|
# the links should always have forward slashes, even on windows, so replace
|
||||||
|
# backslashes with forward slashes here
|
||||||
|
expected = ["%s/<br>" % td,
|
||||||
|
" <a href='%s' target='_blank'>%s</a><br>" %\
|
||||||
|
(tf2.name.replace("\\","/"),split(tf2.name)[1]),
|
||||||
|
" <a href='%s' target='_blank'>%s</a><br>" %\
|
||||||
|
(tf1.name.replace("\\","/"),split(tf1.name)[1])]
|
||||||
|
expected.sort()
|
||||||
|
# We compare the sorted list of links here as that's more reliable
|
||||||
|
nt.assert_equal(actual,expected)
|
||||||
|
|
||||||
|
def test_existing_path_FileLinks_alt_formatter():
|
||||||
|
"""FileLinks: Calling _repr_html_ functions as expected w/ an alt formatter
|
||||||
|
"""
|
||||||
|
td = mkdtemp()
|
||||||
|
tf1 = NamedTemporaryFile(dir=td)
|
||||||
|
tf2 = NamedTemporaryFile(dir=td)
|
||||||
|
def fake_formatter(dirname,fnames,included_suffixes):
|
||||||
|
return ["hello","world"]
|
||||||
|
fl = display.FileLinks(td,notebook_display_formatter=fake_formatter)
|
||||||
|
actual = fl._repr_html_()
|
||||||
|
actual = actual.split('\n')
|
||||||
|
actual.sort()
|
||||||
|
expected = ["hello","world"]
|
||||||
|
expected.sort()
|
||||||
|
# We compare the sorted list of links here as that's more reliable
|
||||||
|
nt.assert_equal(actual,expected)
|
||||||
|
|
||||||
|
def test_existing_path_FileLinks_repr():
|
||||||
|
"""FileLinks: Calling repr() functions as expected on existing directory """
|
||||||
|
td = mkdtemp()
|
||||||
|
tf1 = NamedTemporaryFile(dir=td)
|
||||||
|
tf2 = NamedTemporaryFile(dir=td)
|
||||||
|
fl = display.FileLinks(td)
|
||||||
|
actual = repr(fl)
|
||||||
|
actual = actual.split('\n')
|
||||||
|
actual.sort()
|
||||||
|
expected = ['%s/' % td, ' %s' % split(tf1.name)[1],' %s' % split(tf2.name)[1]]
|
||||||
|
expected.sort()
|
||||||
|
# We compare the sorted list of links here as that's more reliable
|
||||||
|
nt.assert_equal(actual,expected)
|
||||||
|
|
||||||
|
def test_existing_path_FileLinks_repr_alt_formatter():
|
||||||
|
"""FileLinks: Calling repr() functions as expected w/ alt formatter
|
||||||
|
"""
|
||||||
|
td = mkdtemp()
|
||||||
|
tf1 = NamedTemporaryFile(dir=td)
|
||||||
|
tf2 = NamedTemporaryFile(dir=td)
|
||||||
|
def fake_formatter(dirname,fnames,included_suffixes):
|
||||||
|
return ["hello","world"]
|
||||||
|
fl = display.FileLinks(td,terminal_display_formatter=fake_formatter)
|
||||||
|
actual = repr(fl)
|
||||||
|
actual = actual.split('\n')
|
||||||
|
actual.sort()
|
||||||
|
expected = ["hello","world"]
|
||||||
|
expected.sort()
|
||||||
|
# We compare the sorted list of links here as that's more reliable
|
||||||
|
nt.assert_equal(actual,expected)
|
||||||
|
|
||||||
|
def test_error_on_file_to_FileLinks():
|
||||||
|
"""FileLinks: Raises error when passed file
|
||||||
|
"""
|
||||||
|
td = mkdtemp()
|
||||||
|
tf1 = NamedTemporaryFile(dir=td)
|
||||||
|
nt.assert_raises(ValueError,display.FileLinks,tf1.name)
|
||||||
|
|
||||||
|
def test_recursive_FileLinks():
|
||||||
|
"""FileLinks: Does not recurse when recursive=False
|
||||||
|
"""
|
||||||
|
td = mkdtemp()
|
||||||
|
tf = NamedTemporaryFile(dir=td)
|
||||||
|
subtd = mkdtemp(dir=td)
|
||||||
|
subtf = NamedTemporaryFile(dir=subtd)
|
||||||
|
fl = display.FileLinks(td)
|
||||||
|
actual = str(fl)
|
||||||
|
actual = actual.split('\n')
|
||||||
|
nt.assert_equal(len(actual), 4, actual)
|
||||||
|
fl = display.FileLinks(td, recursive=False)
|
||||||
|
actual = str(fl)
|
||||||
|
actual = actual.split('\n')
|
||||||
|
nt.assert_equal(len(actual), 2, actual)
|
||||||
|
|
||||||
|
@skipif_not_numpy
|
||||||
|
def test_audio_from_file():
|
||||||
|
path = pjoin(dirname(__file__), 'test.wav')
|
||||||
|
display.Audio(filename=path)
|
@ -0,0 +1,34 @@
|
|||||||
|
"""Test installing editor hooks"""
|
||||||
|
import sys
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython import get_ipython
|
||||||
|
from yap_ipython.lib import editorhooks
|
||||||
|
|
||||||
|
def test_install_editor():
|
||||||
|
called = []
|
||||||
|
def fake_popen(*args, **kwargs):
|
||||||
|
called.append({
|
||||||
|
'args': args,
|
||||||
|
'kwargs': kwargs,
|
||||||
|
})
|
||||||
|
return mock.MagicMock(**{'wait.return_value': 0})
|
||||||
|
editorhooks.install_editor('foo -l {line} -f {filename}', wait=False)
|
||||||
|
|
||||||
|
with mock.patch('subprocess.Popen', fake_popen):
|
||||||
|
get_ipython().hooks.editor('the file', 64)
|
||||||
|
|
||||||
|
nt.assert_equal(len(called), 1)
|
||||||
|
args = called[0]['args']
|
||||||
|
kwargs = called[0]['kwargs']
|
||||||
|
|
||||||
|
nt.assert_equal(kwargs, {'shell': True})
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
expected = ['foo', '-l', '64', '-f', 'the file']
|
||||||
|
else:
|
||||||
|
expected = "foo -l 64 -f 'the file'"
|
||||||
|
cmd = args[0]
|
||||||
|
nt.assert_equal(cmd, expected)
|
@ -0,0 +1,11 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from yap_ipython.testing import decorators as dec
|
||||||
|
|
||||||
|
def test_import_backgroundjobs():
|
||||||
|
from yap_ipython.lib import backgroundjobs
|
||||||
|
|
||||||
|
def test_import_deepreload():
|
||||||
|
from yap_ipython.lib import deepreload
|
||||||
|
|
||||||
|
def test_import_demo():
|
||||||
|
from yap_ipython.lib import demo
|
@ -0,0 +1,134 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
"""Tests for yap_ipython.utils.path.py"""
|
||||||
|
|
||||||
|
# Copyright (c) yap_ipython Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
from unittest.mock import patch
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython.lib import latextools
|
||||||
|
from yap_ipython.testing.decorators import onlyif_cmds_exist, skipif_not_matplotlib
|
||||||
|
from yap_ipython.utils.process import FindCmdError
|
||||||
|
|
||||||
|
|
||||||
|
def test_latex_to_png_dvipng_fails_when_no_cmd():
|
||||||
|
"""
|
||||||
|
`latex_to_png_dvipng` should return None when there is no required command
|
||||||
|
"""
|
||||||
|
for command in ['latex', 'dvipng']:
|
||||||
|
yield (check_latex_to_png_dvipng_fails_when_no_cmd, command)
|
||||||
|
|
||||||
|
|
||||||
|
def check_latex_to_png_dvipng_fails_when_no_cmd(command):
|
||||||
|
def mock_find_cmd(arg):
|
||||||
|
if arg == command:
|
||||||
|
raise FindCmdError
|
||||||
|
|
||||||
|
with patch.object(latextools, "find_cmd", mock_find_cmd):
|
||||||
|
nt.assert_equal(latextools.latex_to_png_dvipng("whatever", True),
|
||||||
|
None)
|
||||||
|
|
||||||
|
|
||||||
|
@onlyif_cmds_exist('latex', 'dvipng')
|
||||||
|
def test_latex_to_png_dvipng_runs():
|
||||||
|
"""
|
||||||
|
Test that latex_to_png_dvipng just runs without error.
|
||||||
|
"""
|
||||||
|
def mock_kpsewhich(filename):
|
||||||
|
nt.assert_equal(filename, "breqn.sty")
|
||||||
|
return None
|
||||||
|
|
||||||
|
for (s, wrap) in [(u"$$x^2$$", False), (u"x^2", True)]:
|
||||||
|
yield (latextools.latex_to_png_dvipng, s, wrap)
|
||||||
|
|
||||||
|
with patch.object(latextools, "kpsewhich", mock_kpsewhich):
|
||||||
|
yield (latextools.latex_to_png_dvipng, s, wrap)
|
||||||
|
|
||||||
|
@skipif_not_matplotlib
|
||||||
|
def test_latex_to_png_mpl_runs():
|
||||||
|
"""
|
||||||
|
Test that latex_to_png_mpl just runs without error.
|
||||||
|
"""
|
||||||
|
def mock_kpsewhich(filename):
|
||||||
|
nt.assert_equal(filename, "breqn.sty")
|
||||||
|
return None
|
||||||
|
|
||||||
|
for (s, wrap) in [("$x^2$", False), ("x^2", True)]:
|
||||||
|
yield (latextools.latex_to_png_mpl, s, wrap)
|
||||||
|
|
||||||
|
with patch.object(latextools, "kpsewhich", mock_kpsewhich):
|
||||||
|
yield (latextools.latex_to_png_mpl, s, wrap)
|
||||||
|
|
||||||
|
@skipif_not_matplotlib
|
||||||
|
def test_latex_to_html():
|
||||||
|
img = latextools.latex_to_html("$x^2$")
|
||||||
|
nt.assert_in("data:image/png;base64,iVBOR", img)
|
||||||
|
|
||||||
|
|
||||||
|
def test_genelatex_no_wrap():
|
||||||
|
"""
|
||||||
|
Test genelatex with wrap=False.
|
||||||
|
"""
|
||||||
|
def mock_kpsewhich(filename):
|
||||||
|
assert False, ("kpsewhich should not be called "
|
||||||
|
"(called with {0})".format(filename))
|
||||||
|
|
||||||
|
with patch.object(latextools, "kpsewhich", mock_kpsewhich):
|
||||||
|
nt.assert_equal(
|
||||||
|
'\n'.join(latextools.genelatex("body text", False)),
|
||||||
|
r'''\documentclass{article}
|
||||||
|
\usepackage{amsmath}
|
||||||
|
\usepackage{amsthm}
|
||||||
|
\usepackage{amssymb}
|
||||||
|
\usepackage{bm}
|
||||||
|
\pagestyle{empty}
|
||||||
|
\begin{document}
|
||||||
|
body text
|
||||||
|
\end{document}''')
|
||||||
|
|
||||||
|
|
||||||
|
def test_genelatex_wrap_with_breqn():
|
||||||
|
"""
|
||||||
|
Test genelatex with wrap=True for the case breqn.sty is installed.
|
||||||
|
"""
|
||||||
|
def mock_kpsewhich(filename):
|
||||||
|
nt.assert_equal(filename, "breqn.sty")
|
||||||
|
return "path/to/breqn.sty"
|
||||||
|
|
||||||
|
with patch.object(latextools, "kpsewhich", mock_kpsewhich):
|
||||||
|
nt.assert_equal(
|
||||||
|
'\n'.join(latextools.genelatex("x^2", True)),
|
||||||
|
r'''\documentclass{article}
|
||||||
|
\usepackage{amsmath}
|
||||||
|
\usepackage{amsthm}
|
||||||
|
\usepackage{amssymb}
|
||||||
|
\usepackage{bm}
|
||||||
|
\usepackage{breqn}
|
||||||
|
\pagestyle{empty}
|
||||||
|
\begin{document}
|
||||||
|
\begin{dmath*}
|
||||||
|
x^2
|
||||||
|
\end{dmath*}
|
||||||
|
\end{document}''')
|
||||||
|
|
||||||
|
|
||||||
|
def test_genelatex_wrap_without_breqn():
|
||||||
|
"""
|
||||||
|
Test genelatex with wrap=True for the case breqn.sty is not installed.
|
||||||
|
"""
|
||||||
|
def mock_kpsewhich(filename):
|
||||||
|
nt.assert_equal(filename, "breqn.sty")
|
||||||
|
return None
|
||||||
|
|
||||||
|
with patch.object(latextools, "kpsewhich", mock_kpsewhich):
|
||||||
|
nt.assert_equal(
|
||||||
|
'\n'.join(latextools.genelatex("x^2", True)),
|
||||||
|
r'''\documentclass{article}
|
||||||
|
\usepackage{amsmath}
|
||||||
|
\usepackage{amsthm}
|
||||||
|
\usepackage{amssymb}
|
||||||
|
\usepackage{bm}
|
||||||
|
\pagestyle{empty}
|
||||||
|
\begin{document}
|
||||||
|
$$x^2$$
|
||||||
|
\end{document}''')
|
129
packages/python/yap_kernel/yap_ipython/lib/tests/test_lexers.py
Normal file
129
packages/python/yap_kernel/yap_ipython/lib/tests/test_lexers.py
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
"""Test lexers module"""
|
||||||
|
|
||||||
|
# Copyright (c) yap_ipython Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
from unittest import TestCase
|
||||||
|
from pygments.token import Token
|
||||||
|
from pygments.lexers import BashLexer
|
||||||
|
|
||||||
|
from .. import lexers
|
||||||
|
|
||||||
|
|
||||||
|
class TestLexers(TestCase):
|
||||||
|
"""Collection of lexers tests"""
|
||||||
|
def setUp(self):
|
||||||
|
self.lexer = lexers.IPythonLexer()
|
||||||
|
self.bash_lexer = BashLexer()
|
||||||
|
|
||||||
|
def testIPythonLexer(self):
|
||||||
|
fragment = '!echo $HOME\n'
|
||||||
|
tokens = [
|
||||||
|
(Token.Operator, '!'),
|
||||||
|
]
|
||||||
|
tokens.extend(self.bash_lexer.get_tokens(fragment[1:]))
|
||||||
|
self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
|
||||||
|
|
||||||
|
fragment_2 = '!' + fragment
|
||||||
|
tokens_2 = [
|
||||||
|
(Token.Operator, '!!'),
|
||||||
|
] + tokens[1:]
|
||||||
|
self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
|
||||||
|
|
||||||
|
fragment_2 = '\t %%!\n' + fragment[1:]
|
||||||
|
tokens_2 = [
|
||||||
|
(Token.Text, '\t '),
|
||||||
|
(Token.Operator, '%%!'),
|
||||||
|
(Token.Text, '\n'),
|
||||||
|
] + tokens[1:]
|
||||||
|
self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
|
||||||
|
|
||||||
|
fragment_2 = 'x = ' + fragment
|
||||||
|
tokens_2 = [
|
||||||
|
(Token.Name, 'x'),
|
||||||
|
(Token.Text, ' '),
|
||||||
|
(Token.Operator, '='),
|
||||||
|
(Token.Text, ' '),
|
||||||
|
] + tokens
|
||||||
|
self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
|
||||||
|
|
||||||
|
fragment_2 = 'x, = ' + fragment
|
||||||
|
tokens_2 = [
|
||||||
|
(Token.Name, 'x'),
|
||||||
|
(Token.Punctuation, ','),
|
||||||
|
(Token.Text, ' '),
|
||||||
|
(Token.Operator, '='),
|
||||||
|
(Token.Text, ' '),
|
||||||
|
] + tokens
|
||||||
|
self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
|
||||||
|
|
||||||
|
fragment_2 = 'x, = %sx ' + fragment[1:]
|
||||||
|
tokens_2 = [
|
||||||
|
(Token.Name, 'x'),
|
||||||
|
(Token.Punctuation, ','),
|
||||||
|
(Token.Text, ' '),
|
||||||
|
(Token.Operator, '='),
|
||||||
|
(Token.Text, ' '),
|
||||||
|
(Token.Operator, '%'),
|
||||||
|
(Token.Keyword, 'sx'),
|
||||||
|
(Token.Text, ' '),
|
||||||
|
] + tokens[1:]
|
||||||
|
self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
|
||||||
|
|
||||||
|
fragment_2 = 'f = %R function () {}\n'
|
||||||
|
tokens_2 = [
|
||||||
|
(Token.Name, 'f'),
|
||||||
|
(Token.Text, ' '),
|
||||||
|
(Token.Operator, '='),
|
||||||
|
(Token.Text, ' '),
|
||||||
|
(Token.Operator, '%'),
|
||||||
|
(Token.Keyword, 'R'),
|
||||||
|
(Token.Text, ' function () {}\n'),
|
||||||
|
]
|
||||||
|
self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
|
||||||
|
|
||||||
|
fragment_2 = '\t%%xyz\n$foo\n'
|
||||||
|
tokens_2 = [
|
||||||
|
(Token.Text, '\t'),
|
||||||
|
(Token.Operator, '%%'),
|
||||||
|
(Token.Keyword, 'xyz'),
|
||||||
|
(Token.Text, '\n$foo\n'),
|
||||||
|
]
|
||||||
|
self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
|
||||||
|
|
||||||
|
fragment_2 = '%system?\n'
|
||||||
|
tokens_2 = [
|
||||||
|
(Token.Operator, '%'),
|
||||||
|
(Token.Keyword, 'system'),
|
||||||
|
(Token.Operator, '?'),
|
||||||
|
(Token.Text, '\n'),
|
||||||
|
]
|
||||||
|
self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
|
||||||
|
|
||||||
|
fragment_2 = 'x != y\n'
|
||||||
|
tokens_2 = [
|
||||||
|
(Token.Name, 'x'),
|
||||||
|
(Token.Text, ' '),
|
||||||
|
(Token.Operator, '!='),
|
||||||
|
(Token.Text, ' '),
|
||||||
|
(Token.Name, 'y'),
|
||||||
|
(Token.Text, '\n'),
|
||||||
|
]
|
||||||
|
self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
|
||||||
|
|
||||||
|
fragment_2 = ' ?math.sin\n'
|
||||||
|
tokens_2 = [
|
||||||
|
(Token.Text, ' '),
|
||||||
|
(Token.Operator, '?'),
|
||||||
|
(Token.Text, 'math.sin'),
|
||||||
|
(Token.Text, '\n'),
|
||||||
|
]
|
||||||
|
self.assertEqual(tokens_2, list(self.lexer.get_tokens(fragment_2)))
|
||||||
|
|
||||||
|
fragment = ' *int*?\n'
|
||||||
|
tokens = [
|
||||||
|
(Token.Text, ' *int*'),
|
||||||
|
(Token.Operator, '?'),
|
||||||
|
(Token.Text, '\n'),
|
||||||
|
]
|
||||||
|
self.assertEqual(tokens, list(self.lexer.get_tokens(fragment)))
|
423
packages/python/yap_kernel/yap_ipython/lib/tests/test_pretty.py
Normal file
423
packages/python/yap_kernel/yap_ipython/lib/tests/test_pretty.py
Normal file
@ -0,0 +1,423 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
"""Tests for yap_ipython.lib.pretty."""
|
||||||
|
|
||||||
|
# Copyright (c) yap_ipython Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
|
||||||
|
from collections import Counter, defaultdict, deque, OrderedDict
|
||||||
|
import types
|
||||||
|
import string
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython.lib import pretty
|
||||||
|
from yap_ipython.testing.decorators import skip_without
|
||||||
|
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
|
||||||
|
class MyList(object):
|
||||||
|
def __init__(self, content):
|
||||||
|
self.content = content
|
||||||
|
def _repr_pretty_(self, p, cycle):
|
||||||
|
if cycle:
|
||||||
|
p.text("MyList(...)")
|
||||||
|
else:
|
||||||
|
with p.group(3, "MyList(", ")"):
|
||||||
|
for (i, child) in enumerate(self.content):
|
||||||
|
if i:
|
||||||
|
p.text(",")
|
||||||
|
p.breakable()
|
||||||
|
else:
|
||||||
|
p.breakable("")
|
||||||
|
p.pretty(child)
|
||||||
|
|
||||||
|
|
||||||
|
class MyDict(dict):
|
||||||
|
def _repr_pretty_(self, p, cycle):
|
||||||
|
p.text("MyDict(...)")
|
||||||
|
|
||||||
|
class MyObj(object):
|
||||||
|
def somemethod(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Dummy1(object):
|
||||||
|
def _repr_pretty_(self, p, cycle):
|
||||||
|
p.text("Dummy1(...)")
|
||||||
|
|
||||||
|
class Dummy2(Dummy1):
|
||||||
|
_repr_pretty_ = None
|
||||||
|
|
||||||
|
class NoModule(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
NoModule.__module__ = None
|
||||||
|
|
||||||
|
class Breaking(object):
|
||||||
|
def _repr_pretty_(self, p, cycle):
|
||||||
|
with p.group(4,"TG: ",":"):
|
||||||
|
p.text("Breaking(")
|
||||||
|
p.break_()
|
||||||
|
p.text(")")
|
||||||
|
|
||||||
|
class BreakingRepr(object):
|
||||||
|
def __repr__(self):
|
||||||
|
return "Breaking(\n)"
|
||||||
|
|
||||||
|
class BreakingReprParent(object):
|
||||||
|
def _repr_pretty_(self, p, cycle):
|
||||||
|
with p.group(4,"TG: ",":"):
|
||||||
|
p.pretty(BreakingRepr())
|
||||||
|
|
||||||
|
class BadRepr(object):
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 1/0
|
||||||
|
|
||||||
|
|
||||||
|
def test_indentation():
|
||||||
|
"""Test correct indentation in groups"""
|
||||||
|
count = 40
|
||||||
|
gotoutput = pretty.pretty(MyList(range(count)))
|
||||||
|
expectedoutput = "MyList(\n" + ",\n".join(" %d" % i for i in range(count)) + ")"
|
||||||
|
|
||||||
|
nt.assert_equal(gotoutput, expectedoutput)
|
||||||
|
|
||||||
|
|
||||||
|
def test_dispatch():
|
||||||
|
"""
|
||||||
|
Test correct dispatching: The _repr_pretty_ method for MyDict
|
||||||
|
must be found before the registered printer for dict.
|
||||||
|
"""
|
||||||
|
gotoutput = pretty.pretty(MyDict())
|
||||||
|
expectedoutput = "MyDict(...)"
|
||||||
|
|
||||||
|
nt.assert_equal(gotoutput, expectedoutput)
|
||||||
|
|
||||||
|
|
||||||
|
def test_callability_checking():
|
||||||
|
"""
|
||||||
|
Test that the _repr_pretty_ method is tested for callability and skipped if
|
||||||
|
not.
|
||||||
|
"""
|
||||||
|
gotoutput = pretty.pretty(Dummy2())
|
||||||
|
expectedoutput = "Dummy1(...)"
|
||||||
|
|
||||||
|
nt.assert_equal(gotoutput, expectedoutput)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sets():
|
||||||
|
"""
|
||||||
|
Test that set and frozenset use Python 3 formatting.
|
||||||
|
"""
|
||||||
|
objects = [set(), frozenset(), set([1]), frozenset([1]), set([1, 2]),
|
||||||
|
frozenset([1, 2]), set([-1, -2, -3])]
|
||||||
|
expected = ['set()', 'frozenset()', '{1}', 'frozenset({1})', '{1, 2}',
|
||||||
|
'frozenset({1, 2})', '{-3, -2, -1}']
|
||||||
|
for obj, expected_output in zip(objects, expected):
|
||||||
|
got_output = pretty.pretty(obj)
|
||||||
|
yield nt.assert_equal, got_output, expected_output
|
||||||
|
|
||||||
|
|
||||||
|
@skip_without('xxlimited')
|
||||||
|
def test_pprint_heap_allocated_type():
|
||||||
|
"""
|
||||||
|
Test that pprint works for heap allocated types.
|
||||||
|
"""
|
||||||
|
import xxlimited
|
||||||
|
output = pretty.pretty(xxlimited.Null)
|
||||||
|
nt.assert_equal(output, 'xxlimited.Null')
|
||||||
|
|
||||||
|
def test_pprint_nomod():
|
||||||
|
"""
|
||||||
|
Test that pprint works for classes with no __module__.
|
||||||
|
"""
|
||||||
|
output = pretty.pretty(NoModule)
|
||||||
|
nt.assert_equal(output, 'NoModule')
|
||||||
|
|
||||||
|
def test_pprint_break():
|
||||||
|
"""
|
||||||
|
Test that p.break_ produces expected output
|
||||||
|
"""
|
||||||
|
output = pretty.pretty(Breaking())
|
||||||
|
expected = "TG: Breaking(\n ):"
|
||||||
|
nt.assert_equal(output, expected)
|
||||||
|
|
||||||
|
def test_pprint_break_repr():
|
||||||
|
"""
|
||||||
|
Test that p.break_ is used in repr
|
||||||
|
"""
|
||||||
|
output = pretty.pretty(BreakingReprParent())
|
||||||
|
expected = "TG: Breaking(\n ):"
|
||||||
|
nt.assert_equal(output, expected)
|
||||||
|
|
||||||
|
def test_bad_repr():
|
||||||
|
"""Don't catch bad repr errors"""
|
||||||
|
with nt.assert_raises(ZeroDivisionError):
|
||||||
|
pretty.pretty(BadRepr())
|
||||||
|
|
||||||
|
class BadException(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return -1
|
||||||
|
|
||||||
|
class ReallyBadRepr(object):
|
||||||
|
__module__ = 1
|
||||||
|
@property
|
||||||
|
def __class__(self):
|
||||||
|
raise ValueError("I am horrible")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
raise BadException()
|
||||||
|
|
||||||
|
def test_really_bad_repr():
|
||||||
|
with nt.assert_raises(BadException):
|
||||||
|
pretty.pretty(ReallyBadRepr())
|
||||||
|
|
||||||
|
|
||||||
|
class SA(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class SB(SA):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TestsPretty(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_super_repr(self):
|
||||||
|
# "<super: module_name.SA, None>"
|
||||||
|
output = pretty.pretty(super(SA))
|
||||||
|
self.assertRegex(output, r"<super: \S+.SA, None>")
|
||||||
|
|
||||||
|
# "<super: module_name.SA, <module_name.SB at 0x...>>"
|
||||||
|
sb = SB()
|
||||||
|
output = pretty.pretty(super(SA, sb))
|
||||||
|
self.assertRegex(output, r"<super: \S+.SA,\s+<\S+.SB at 0x\S+>>")
|
||||||
|
|
||||||
|
|
||||||
|
def test_long_list(self):
|
||||||
|
lis = list(range(10000))
|
||||||
|
p = pretty.pretty(lis)
|
||||||
|
last2 = p.rsplit('\n', 2)[-2:]
|
||||||
|
self.assertEqual(last2, [' 999,', ' ...]'])
|
||||||
|
|
||||||
|
def test_long_set(self):
|
||||||
|
s = set(range(10000))
|
||||||
|
p = pretty.pretty(s)
|
||||||
|
last2 = p.rsplit('\n', 2)[-2:]
|
||||||
|
self.assertEqual(last2, [' 999,', ' ...}'])
|
||||||
|
|
||||||
|
def test_long_tuple(self):
|
||||||
|
tup = tuple(range(10000))
|
||||||
|
p = pretty.pretty(tup)
|
||||||
|
last2 = p.rsplit('\n', 2)[-2:]
|
||||||
|
self.assertEqual(last2, [' 999,', ' ...)'])
|
||||||
|
|
||||||
|
def test_long_dict(self):
|
||||||
|
d = { n:n for n in range(10000) }
|
||||||
|
p = pretty.pretty(d)
|
||||||
|
last2 = p.rsplit('\n', 2)[-2:]
|
||||||
|
self.assertEqual(last2, [' 999: 999,', ' ...}'])
|
||||||
|
|
||||||
|
def test_unbound_method(self):
|
||||||
|
output = pretty.pretty(MyObj.somemethod)
|
||||||
|
self.assertIn('MyObj.somemethod', output)
|
||||||
|
|
||||||
|
|
||||||
|
class MetaClass(type):
|
||||||
|
def __new__(cls, name):
|
||||||
|
return type.__new__(cls, name, (object,), {'name': name})
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "[CUSTOM REPR FOR CLASS %s]" % self.name
|
||||||
|
|
||||||
|
|
||||||
|
ClassWithMeta = MetaClass('ClassWithMeta')
|
||||||
|
|
||||||
|
|
||||||
|
def test_metaclass_repr():
|
||||||
|
output = pretty.pretty(ClassWithMeta)
|
||||||
|
nt.assert_equal(output, "[CUSTOM REPR FOR CLASS ClassWithMeta]")
|
||||||
|
|
||||||
|
|
||||||
|
def test_unicode_repr():
|
||||||
|
u = u"üniçodé"
|
||||||
|
ustr = u
|
||||||
|
|
||||||
|
class C(object):
|
||||||
|
def __repr__(self):
|
||||||
|
return ustr
|
||||||
|
|
||||||
|
c = C()
|
||||||
|
p = pretty.pretty(c)
|
||||||
|
nt.assert_equal(p, u)
|
||||||
|
p = pretty.pretty([c])
|
||||||
|
nt.assert_equal(p, u'[%s]' % u)
|
||||||
|
|
||||||
|
|
||||||
|
def test_basic_class():
|
||||||
|
def type_pprint_wrapper(obj, p, cycle):
|
||||||
|
if obj is MyObj:
|
||||||
|
type_pprint_wrapper.called = True
|
||||||
|
return pretty._type_pprint(obj, p, cycle)
|
||||||
|
type_pprint_wrapper.called = False
|
||||||
|
|
||||||
|
stream = StringIO()
|
||||||
|
printer = pretty.RepresentationPrinter(stream)
|
||||||
|
printer.type_pprinters[type] = type_pprint_wrapper
|
||||||
|
printer.pretty(MyObj)
|
||||||
|
printer.flush()
|
||||||
|
output = stream.getvalue()
|
||||||
|
|
||||||
|
nt.assert_equal(output, '%s.MyObj' % __name__)
|
||||||
|
nt.assert_true(type_pprint_wrapper.called)
|
||||||
|
|
||||||
|
|
||||||
|
def test_collections_defaultdict():
|
||||||
|
# Create defaultdicts with cycles
|
||||||
|
a = defaultdict()
|
||||||
|
a.default_factory = a
|
||||||
|
b = defaultdict(list)
|
||||||
|
b['key'] = b
|
||||||
|
|
||||||
|
# Dictionary order cannot be relied on, test against single keys.
|
||||||
|
cases = [
|
||||||
|
(defaultdict(list), 'defaultdict(list, {})'),
|
||||||
|
(defaultdict(list, {'key': '-' * 50}),
|
||||||
|
"defaultdict(list,\n"
|
||||||
|
" {'key': '--------------------------------------------------'})"),
|
||||||
|
(a, 'defaultdict(defaultdict(...), {})'),
|
||||||
|
(b, "defaultdict(list, {'key': defaultdict(...)})"),
|
||||||
|
]
|
||||||
|
for obj, expected in cases:
|
||||||
|
nt.assert_equal(pretty.pretty(obj), expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_collections_ordereddict():
|
||||||
|
# Create OrderedDict with cycle
|
||||||
|
a = OrderedDict()
|
||||||
|
a['key'] = a
|
||||||
|
|
||||||
|
cases = [
|
||||||
|
(OrderedDict(), 'OrderedDict()'),
|
||||||
|
(OrderedDict((i, i) for i in range(1000, 1010)),
|
||||||
|
'OrderedDict([(1000, 1000),\n'
|
||||||
|
' (1001, 1001),\n'
|
||||||
|
' (1002, 1002),\n'
|
||||||
|
' (1003, 1003),\n'
|
||||||
|
' (1004, 1004),\n'
|
||||||
|
' (1005, 1005),\n'
|
||||||
|
' (1006, 1006),\n'
|
||||||
|
' (1007, 1007),\n'
|
||||||
|
' (1008, 1008),\n'
|
||||||
|
' (1009, 1009)])'),
|
||||||
|
(a, "OrderedDict([('key', OrderedDict(...))])"),
|
||||||
|
]
|
||||||
|
for obj, expected in cases:
|
||||||
|
nt.assert_equal(pretty.pretty(obj), expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_collections_deque():
|
||||||
|
# Create deque with cycle
|
||||||
|
a = deque()
|
||||||
|
a.append(a)
|
||||||
|
|
||||||
|
cases = [
|
||||||
|
(deque(), 'deque([])'),
|
||||||
|
(deque(i for i in range(1000, 1020)),
|
||||||
|
'deque([1000,\n'
|
||||||
|
' 1001,\n'
|
||||||
|
' 1002,\n'
|
||||||
|
' 1003,\n'
|
||||||
|
' 1004,\n'
|
||||||
|
' 1005,\n'
|
||||||
|
' 1006,\n'
|
||||||
|
' 1007,\n'
|
||||||
|
' 1008,\n'
|
||||||
|
' 1009,\n'
|
||||||
|
' 1010,\n'
|
||||||
|
' 1011,\n'
|
||||||
|
' 1012,\n'
|
||||||
|
' 1013,\n'
|
||||||
|
' 1014,\n'
|
||||||
|
' 1015,\n'
|
||||||
|
' 1016,\n'
|
||||||
|
' 1017,\n'
|
||||||
|
' 1018,\n'
|
||||||
|
' 1019])'),
|
||||||
|
(a, 'deque([deque(...)])'),
|
||||||
|
]
|
||||||
|
for obj, expected in cases:
|
||||||
|
nt.assert_equal(pretty.pretty(obj), expected)
|
||||||
|
|
||||||
|
def test_collections_counter():
|
||||||
|
class MyCounter(Counter):
|
||||||
|
pass
|
||||||
|
cases = [
|
||||||
|
(Counter(), 'Counter()'),
|
||||||
|
(Counter(a=1), "Counter({'a': 1})"),
|
||||||
|
(MyCounter(a=1), "MyCounter({'a': 1})"),
|
||||||
|
]
|
||||||
|
for obj, expected in cases:
|
||||||
|
nt.assert_equal(pretty.pretty(obj), expected)
|
||||||
|
|
||||||
|
def test_mappingproxy():
|
||||||
|
MP = types.MappingProxyType
|
||||||
|
underlying_dict = {}
|
||||||
|
mp_recursive = MP(underlying_dict)
|
||||||
|
underlying_dict[2] = mp_recursive
|
||||||
|
underlying_dict[3] = underlying_dict
|
||||||
|
|
||||||
|
cases = [
|
||||||
|
(MP({}), "mappingproxy({})"),
|
||||||
|
(MP({None: MP({})}), "mappingproxy({None: mappingproxy({})})"),
|
||||||
|
(MP({k: k.upper() for k in string.ascii_lowercase}),
|
||||||
|
"mappingproxy({'a': 'A',\n"
|
||||||
|
" 'b': 'B',\n"
|
||||||
|
" 'c': 'C',\n"
|
||||||
|
" 'd': 'D',\n"
|
||||||
|
" 'e': 'E',\n"
|
||||||
|
" 'f': 'F',\n"
|
||||||
|
" 'g': 'G',\n"
|
||||||
|
" 'h': 'H',\n"
|
||||||
|
" 'i': 'I',\n"
|
||||||
|
" 'j': 'J',\n"
|
||||||
|
" 'k': 'K',\n"
|
||||||
|
" 'l': 'L',\n"
|
||||||
|
" 'm': 'M',\n"
|
||||||
|
" 'n': 'N',\n"
|
||||||
|
" 'o': 'O',\n"
|
||||||
|
" 'p': 'P',\n"
|
||||||
|
" 'q': 'Q',\n"
|
||||||
|
" 'r': 'R',\n"
|
||||||
|
" 's': 'S',\n"
|
||||||
|
" 't': 'T',\n"
|
||||||
|
" 'u': 'U',\n"
|
||||||
|
" 'v': 'V',\n"
|
||||||
|
" 'w': 'W',\n"
|
||||||
|
" 'x': 'X',\n"
|
||||||
|
" 'y': 'Y',\n"
|
||||||
|
" 'z': 'Z'})"),
|
||||||
|
(mp_recursive, "mappingproxy({2: {...}, 3: {2: {...}, 3: {...}}})"),
|
||||||
|
(underlying_dict,
|
||||||
|
"{2: mappingproxy({2: {...}, 3: {...}}), 3: {...}}"),
|
||||||
|
]
|
||||||
|
for obj, expected in cases:
|
||||||
|
nt.assert_equal(pretty.pretty(obj), expected)
|
||||||
|
|
||||||
|
def test_function_pretty():
|
||||||
|
"Test pretty print of function"
|
||||||
|
# posixpath is a pure python module, its interface is consistent
|
||||||
|
# across Python distributions
|
||||||
|
import posixpath
|
||||||
|
nt.assert_equal(pretty.pretty(posixpath.join), '<function posixpath.join(a, *p)>')
|
||||||
|
|
||||||
|
# custom function
|
||||||
|
def meaning_of_life(question=None):
|
||||||
|
if question:
|
||||||
|
return 42
|
||||||
|
return "Don't panic"
|
||||||
|
|
||||||
|
nt.assert_in('meaning_of_life(question=None)', pretty.pretty(meaning_of_life))
|
||||||
|
|
@ -0,0 +1,26 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from yap_ipython.lib import passwd
|
||||||
|
from yap_ipython.lib.security import passwd_check, salt_len
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
def test_passwd_structure():
|
||||||
|
p = passwd('passphrase')
|
||||||
|
algorithm, salt, hashed = p.split(':')
|
||||||
|
nt.assert_equal(algorithm, 'sha1')
|
||||||
|
nt.assert_equal(len(salt), salt_len)
|
||||||
|
nt.assert_equal(len(hashed), 40)
|
||||||
|
|
||||||
|
def test_roundtrip():
|
||||||
|
p = passwd('passphrase')
|
||||||
|
nt.assert_equal(passwd_check(p, 'passphrase'), True)
|
||||||
|
|
||||||
|
def test_bad():
|
||||||
|
p = passwd('passphrase')
|
||||||
|
nt.assert_equal(passwd_check(p, p), False)
|
||||||
|
nt.assert_equal(passwd_check(p, 'a:b:c:d'), False)
|
||||||
|
nt.assert_equal(passwd_check(p, 'a:b'), False)
|
||||||
|
|
||||||
|
def test_passwd_check_unicode():
|
||||||
|
# GH issue #4524
|
||||||
|
phash = u'sha1:23862bc21dd3:7a415a95ae4580582e314072143d9c382c491e4f'
|
||||||
|
assert passwd_check(phash, u"łe¶ŧ←↓→")
|
@ -0,0 +1,132 @@
|
|||||||
|
"""Test embedding of yap_ipython"""
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2013 The yap_ipython Development Team
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the BSD License. The full license is in
|
||||||
|
# the file COPYING, distributed as part of this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import nose.tools as nt
|
||||||
|
from yap_ipython.utils.tempdir import NamedFileInTemporaryDirectory
|
||||||
|
from yap_ipython.testing.decorators import skip_win32
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Tests
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
_sample_embed = b"""
|
||||||
|
import yap_ipython
|
||||||
|
|
||||||
|
a = 3
|
||||||
|
b = 14
|
||||||
|
print(a, '.', b)
|
||||||
|
|
||||||
|
yap_ipython.embed()
|
||||||
|
|
||||||
|
print('bye!')
|
||||||
|
"""
|
||||||
|
|
||||||
|
_exit = b"exit\r"
|
||||||
|
|
||||||
|
def test_ipython_embed():
|
||||||
|
"""test that `yap_ipython.embed()` works"""
|
||||||
|
with NamedFileInTemporaryDirectory('file_with_embed.py') as f:
|
||||||
|
f.write(_sample_embed)
|
||||||
|
f.flush()
|
||||||
|
f.close() # otherwise msft won't be able to read the file
|
||||||
|
|
||||||
|
# run `python file_with_embed.py`
|
||||||
|
cmd = [sys.executable, f.name]
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['IPY_TEST_SIMPLE_PROMPT'] = '1'
|
||||||
|
|
||||||
|
p = subprocess.Popen(cmd, env=env, stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
out, err = p.communicate(_exit)
|
||||||
|
std = out.decode('UTF-8')
|
||||||
|
|
||||||
|
nt.assert_equal(p.returncode, 0)
|
||||||
|
nt.assert_in('3 . 14', std)
|
||||||
|
if os.name != 'nt':
|
||||||
|
# TODO: Fix up our different stdout references, see issue gh-14
|
||||||
|
nt.assert_in('yap_ipython', std)
|
||||||
|
nt.assert_in('bye!', std)
|
||||||
|
|
||||||
|
@skip_win32
|
||||||
|
def test_nest_embed():
|
||||||
|
"""test that `yap_ipython.embed()` is nestable"""
|
||||||
|
import pexpect
|
||||||
|
ipy_prompt = r']:' #ansi color codes give problems matching beyond this
|
||||||
|
env = os.environ.copy()
|
||||||
|
env['IPY_TEST_SIMPLE_PROMPT'] = '1'
|
||||||
|
|
||||||
|
|
||||||
|
child = pexpect.spawn(sys.executable, ['-m', 'yap_ipython', '--colors=nocolor'],
|
||||||
|
env=env)
|
||||||
|
child.expect(ipy_prompt)
|
||||||
|
child.sendline("import yap_ipython")
|
||||||
|
child.expect(ipy_prompt)
|
||||||
|
child.sendline("ip0 = get_ipython()")
|
||||||
|
#enter first nested embed
|
||||||
|
child.sendline("yap_ipython.embed()")
|
||||||
|
#skip the banner until we get to a prompt
|
||||||
|
try:
|
||||||
|
prompted = -1
|
||||||
|
while prompted != 0:
|
||||||
|
prompted = child.expect([ipy_prompt, '\r\n'])
|
||||||
|
except pexpect.TIMEOUT as e:
|
||||||
|
print(e)
|
||||||
|
#child.interact()
|
||||||
|
child.sendline("embed1 = get_ipython()"); child.expect(ipy_prompt)
|
||||||
|
child.sendline("print('true' if embed1 is not ip0 else 'false')")
|
||||||
|
assert(child.expect(['true\r\n', 'false\r\n']) == 0)
|
||||||
|
child.expect(ipy_prompt)
|
||||||
|
child.sendline("print('true' if yap_ipython.get_ipython() is embed1 else 'false')")
|
||||||
|
assert(child.expect(['true\r\n', 'false\r\n']) == 0)
|
||||||
|
child.expect(ipy_prompt)
|
||||||
|
#enter second nested embed
|
||||||
|
child.sendline("yap_ipython.embed()")
|
||||||
|
#skip the banner until we get to a prompt
|
||||||
|
try:
|
||||||
|
prompted = -1
|
||||||
|
while prompted != 0:
|
||||||
|
prompted = child.expect([ipy_prompt, '\r\n'])
|
||||||
|
except pexpect.TIMEOUT as e:
|
||||||
|
print(e)
|
||||||
|
#child.interact()
|
||||||
|
child.sendline("embed2 = get_ipython()"); child.expect(ipy_prompt)
|
||||||
|
child.sendline("print('true' if embed2 is not embed1 else 'false')")
|
||||||
|
assert(child.expect(['true\r\n', 'false\r\n']) == 0)
|
||||||
|
child.expect(ipy_prompt)
|
||||||
|
child.sendline("print('true' if embed2 is yap_ipython.get_ipython() else 'false')")
|
||||||
|
assert(child.expect(['true\r\n', 'false\r\n']) == 0)
|
||||||
|
child.expect(ipy_prompt)
|
||||||
|
child.sendline('exit')
|
||||||
|
#back at first embed
|
||||||
|
child.expect(ipy_prompt)
|
||||||
|
child.sendline("print('true' if get_ipython() is embed1 else 'false')")
|
||||||
|
assert(child.expect(['true\r\n', 'false\r\n']) == 0)
|
||||||
|
child.expect(ipy_prompt)
|
||||||
|
child.sendline("print('true' if yap_ipython.get_ipython() is embed1 else 'false')")
|
||||||
|
assert(child.expect(['true\r\n', 'false\r\n']) == 0)
|
||||||
|
child.expect(ipy_prompt)
|
||||||
|
child.sendline('exit')
|
||||||
|
#back at launching scope
|
||||||
|
child.expect(ipy_prompt)
|
||||||
|
child.sendline("print('true' if get_ipython() is ip0 else 'false')")
|
||||||
|
assert(child.expect(['true\r\n', 'false\r\n']) == 0)
|
||||||
|
child.expect(ipy_prompt)
|
||||||
|
child.sendline("print('true' if yap_ipython.get_ipython() is ip0 else 'false')")
|
||||||
|
assert(child.expect(['true\r\n', 'false\r\n']) == 0)
|
||||||
|
child.expect(ipy_prompt)
|
||||||
|
child.sendline('exit')
|
||||||
|
child.close()
|
@ -0,0 +1,28 @@
|
|||||||
|
"""Test help output of various yap_ipython entry points"""
|
||||||
|
|
||||||
|
# Copyright (c) yap_ipython Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
import yap_ipython.testing.tools as tt
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipython_help():
|
||||||
|
tt.help_all_output_test()
|
||||||
|
|
||||||
|
def test_profile_help():
|
||||||
|
tt.help_all_output_test("profile")
|
||||||
|
|
||||||
|
def test_profile_list_help():
|
||||||
|
tt.help_all_output_test("profile list")
|
||||||
|
|
||||||
|
def test_profile_create_help():
|
||||||
|
tt.help_all_output_test("profile create")
|
||||||
|
|
||||||
|
def test_locate_help():
|
||||||
|
tt.help_all_output_test("locate")
|
||||||
|
|
||||||
|
def test_locate_profile_help():
|
||||||
|
tt.help_all_output_test("locate profile")
|
||||||
|
|
||||||
|
def test_trust_help():
|
||||||
|
tt.help_all_output_test("trust")
|
@ -0,0 +1,176 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Tests for the TerminalInteractiveShell and related pieces."""
|
||||||
|
# Copyright (c) yap_ipython Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from yap_ipython.core.inputtransformer import InputTransformer
|
||||||
|
from yap_ipython.testing import tools as tt
|
||||||
|
from yap_ipython.utils.capture import capture_output
|
||||||
|
|
||||||
|
from yap_ipython.terminal.ptutils import _elide, _adjust_completion_text_based_on_context
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
class TestElide(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_elide(self):
|
||||||
|
_elide('concatenate((a1, a2, ...), axis') # do not raise
|
||||||
|
_elide('concatenate((a1, a2, ..), . axis') # do not raise
|
||||||
|
nt.assert_equal(_elide('aaaa.bbbb.ccccc.dddddd.eeeee.fffff.gggggg.hhhhhh'), 'aaaa.b…g.hhhhhh')
|
||||||
|
|
||||||
|
|
||||||
|
class TestContextAwareCompletion(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_adjust_completion_text_based_on_context(self):
|
||||||
|
# Adjusted case
|
||||||
|
nt.assert_equal(_adjust_completion_text_based_on_context('arg1=', 'func1(a=)', 7), 'arg1')
|
||||||
|
|
||||||
|
# Untouched cases
|
||||||
|
nt.assert_equal(_adjust_completion_text_based_on_context('arg1=', 'func1(a)', 7), 'arg1=')
|
||||||
|
nt.assert_equal(_adjust_completion_text_based_on_context('arg1=', 'func1(a', 7), 'arg1=')
|
||||||
|
nt.assert_equal(_adjust_completion_text_based_on_context('%magic', 'func1(a=)', 7), '%magic')
|
||||||
|
nt.assert_equal(_adjust_completion_text_based_on_context('func2', 'func1(a=)', 7), 'func2')
|
||||||
|
|
||||||
|
# Decorator for interaction loop tests -----------------------------------------
|
||||||
|
|
||||||
|
class mock_input_helper(object):
|
||||||
|
"""Machinery for tests of the main interact loop.
|
||||||
|
|
||||||
|
Used by the mock_input decorator.
|
||||||
|
"""
|
||||||
|
def __init__(self, testgen):
|
||||||
|
self.testgen = testgen
|
||||||
|
self.exception = None
|
||||||
|
self.ip = get_ipython()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.orig_prompt_for_code = self.ip.prompt_for_code
|
||||||
|
self.ip.prompt_for_code = self.fake_input
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, etype, value, tb):
|
||||||
|
self.ip.prompt_for_code = self.orig_prompt_for_code
|
||||||
|
|
||||||
|
def fake_input(self):
|
||||||
|
try:
|
||||||
|
return next(self.testgen)
|
||||||
|
except StopIteration:
|
||||||
|
self.ip.keep_running = False
|
||||||
|
return u''
|
||||||
|
except:
|
||||||
|
self.exception = sys.exc_info()
|
||||||
|
self.ip.keep_running = False
|
||||||
|
return u''
|
||||||
|
|
||||||
|
def mock_input(testfunc):
|
||||||
|
"""Decorator for tests of the main interact loop.
|
||||||
|
|
||||||
|
Write the test as a generator, yield-ing the input strings, which yap_ipython
|
||||||
|
will see as if they were typed in at the prompt.
|
||||||
|
"""
|
||||||
|
def test_method(self):
|
||||||
|
testgen = testfunc(self)
|
||||||
|
with mock_input_helper(testgen) as mih:
|
||||||
|
mih.ip.interact()
|
||||||
|
|
||||||
|
if mih.exception is not None:
|
||||||
|
# Re-raise captured exception
|
||||||
|
etype, value, tb = mih.exception
|
||||||
|
import traceback
|
||||||
|
traceback.print_tb(tb, file=sys.stdout)
|
||||||
|
del tb # Avoid reference loop
|
||||||
|
raise value
|
||||||
|
|
||||||
|
return test_method
|
||||||
|
|
||||||
|
# Test classes -----------------------------------------------------------------
|
||||||
|
|
||||||
|
class InteractiveShellTestCase(unittest.TestCase):
|
||||||
|
def rl_hist_entries(self, rl, n):
|
||||||
|
"""Get last n readline history entries as a list"""
|
||||||
|
return [rl.get_history_item(rl.get_current_history_length() - x)
|
||||||
|
for x in range(n - 1, -1, -1)]
|
||||||
|
|
||||||
|
@mock_input
|
||||||
|
def test_inputtransformer_syntaxerror(self):
|
||||||
|
ip = get_ipython()
|
||||||
|
transformer = SyntaxErrorTransformer()
|
||||||
|
ip.input_splitter.python_line_transforms.append(transformer)
|
||||||
|
ip.input_transformer_manager.python_line_transforms.append(transformer)
|
||||||
|
|
||||||
|
try:
|
||||||
|
#raise Exception
|
||||||
|
with tt.AssertPrints('4', suppress=False):
|
||||||
|
yield u'print(2*2)'
|
||||||
|
|
||||||
|
with tt.AssertPrints('SyntaxError: input contains', suppress=False):
|
||||||
|
yield u'print(2345) # syntaxerror'
|
||||||
|
|
||||||
|
with tt.AssertPrints('16', suppress=False):
|
||||||
|
yield u'print(4*4)'
|
||||||
|
|
||||||
|
finally:
|
||||||
|
ip.input_splitter.python_line_transforms.remove(transformer)
|
||||||
|
ip.input_transformer_manager.python_line_transforms.remove(transformer)
|
||||||
|
|
||||||
|
def test_plain_text_only(self):
|
||||||
|
ip = get_ipython()
|
||||||
|
formatter = ip.display_formatter
|
||||||
|
assert formatter.active_types == ['text/plain']
|
||||||
|
assert not formatter.ipython_display_formatter.enabled
|
||||||
|
|
||||||
|
class Test(object):
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Test %i>" % id(self)
|
||||||
|
|
||||||
|
def _repr_html_(self):
|
||||||
|
return '<html>'
|
||||||
|
|
||||||
|
# verify that HTML repr isn't computed
|
||||||
|
obj = Test()
|
||||||
|
data, _ = formatter.format(obj)
|
||||||
|
self.assertEqual(data, {'text/plain': repr(obj)})
|
||||||
|
|
||||||
|
class Test2(Test):
|
||||||
|
def _ipython_display_(self):
|
||||||
|
from yap_ipython.display import display
|
||||||
|
display('<custom>')
|
||||||
|
|
||||||
|
# verify that _ipython_display_ shortcut isn't called
|
||||||
|
obj = Test2()
|
||||||
|
with capture_output() as captured:
|
||||||
|
data, _ = formatter.format(obj)
|
||||||
|
|
||||||
|
self.assertEqual(data, {'text/plain': repr(obj)})
|
||||||
|
assert captured.stdout == ''
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SyntaxErrorTransformer(InputTransformer):
|
||||||
|
def push(self, line):
|
||||||
|
pos = line.find('syntaxerror')
|
||||||
|
if pos >= 0:
|
||||||
|
e = SyntaxError('input contains "syntaxerror"')
|
||||||
|
e.text = line
|
||||||
|
e.offset = pos + 1
|
||||||
|
raise e
|
||||||
|
return line
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TerminalMagicsTestCase(unittest.TestCase):
|
||||||
|
def test_paste_magics_blankline(self):
|
||||||
|
"""Test that code with a blank line doesn't get split (gh-3246)."""
|
||||||
|
ip = get_ipython()
|
||||||
|
s = ('def pasted_func(a):\n'
|
||||||
|
' b = a+1\n'
|
||||||
|
'\n'
|
||||||
|
' return b')
|
||||||
|
|
||||||
|
tm = ip.magics_manager.registry['TerminalMagics']
|
||||||
|
tm.store_or_execute(s, name=None)
|
||||||
|
|
||||||
|
self.assertEqual(ip.user_ns['pasted_func'](54), 55)
|
@ -0,0 +1,157 @@
|
|||||||
|
"""Simple example using doctests.
|
||||||
|
|
||||||
|
This file just contains doctests both using plain python and yap_ipython prompts.
|
||||||
|
All tests should be loaded by nose.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def pyfunc():
|
||||||
|
"""Some pure python tests...
|
||||||
|
|
||||||
|
>>> pyfunc()
|
||||||
|
'pyfunc'
|
||||||
|
|
||||||
|
>>> import os
|
||||||
|
|
||||||
|
>>> 2+3
|
||||||
|
5
|
||||||
|
|
||||||
|
>>> for i in range(3):
|
||||||
|
... print(i, end=' ')
|
||||||
|
... print(i+1, end=' ')
|
||||||
|
...
|
||||||
|
0 1 1 2 2 3
|
||||||
|
"""
|
||||||
|
return 'pyfunc'
|
||||||
|
|
||||||
|
def ipfunc():
|
||||||
|
"""Some ipython tests...
|
||||||
|
|
||||||
|
In [1]: import os
|
||||||
|
|
||||||
|
In [3]: 2+3
|
||||||
|
Out[3]: 5
|
||||||
|
|
||||||
|
In [26]: for i in range(3):
|
||||||
|
....: print(i, end=' ')
|
||||||
|
....: print(i+1, end=' ')
|
||||||
|
....:
|
||||||
|
0 1 1 2 2 3
|
||||||
|
|
||||||
|
|
||||||
|
Examples that access the operating system work:
|
||||||
|
|
||||||
|
In [1]: !echo hello
|
||||||
|
hello
|
||||||
|
|
||||||
|
In [2]: !echo hello > /tmp/foo_iptest
|
||||||
|
|
||||||
|
In [3]: !cat /tmp/foo_iptest
|
||||||
|
hello
|
||||||
|
|
||||||
|
In [4]: rm -f /tmp/foo_iptest
|
||||||
|
|
||||||
|
It's OK to use '_' for the last result, but do NOT try to use yap_ipython's
|
||||||
|
numbered history of _NN outputs, since those won't exist under the
|
||||||
|
doctest environment:
|
||||||
|
|
||||||
|
In [7]: 'hi'
|
||||||
|
Out[7]: 'hi'
|
||||||
|
|
||||||
|
In [8]: print(repr(_))
|
||||||
|
'hi'
|
||||||
|
|
||||||
|
In [7]: 3+4
|
||||||
|
Out[7]: 7
|
||||||
|
|
||||||
|
In [8]: _+3
|
||||||
|
Out[8]: 10
|
||||||
|
|
||||||
|
In [9]: ipfunc()
|
||||||
|
Out[9]: 'ipfunc'
|
||||||
|
"""
|
||||||
|
return 'ipfunc'
|
||||||
|
|
||||||
|
|
||||||
|
def ranfunc():
|
||||||
|
"""A function with some random output.
|
||||||
|
|
||||||
|
Normal examples are verified as usual:
|
||||||
|
>>> 1+3
|
||||||
|
4
|
||||||
|
|
||||||
|
But if you put '# random' in the output, it is ignored:
|
||||||
|
>>> 1+3
|
||||||
|
junk goes here... # random
|
||||||
|
|
||||||
|
>>> 1+2
|
||||||
|
again, anything goes #random
|
||||||
|
if multiline, the random mark is only needed once.
|
||||||
|
|
||||||
|
>>> 1+2
|
||||||
|
You can also put the random marker at the end:
|
||||||
|
# random
|
||||||
|
|
||||||
|
>>> 1+2
|
||||||
|
# random
|
||||||
|
.. or at the beginning.
|
||||||
|
|
||||||
|
More correct input is properly verified:
|
||||||
|
>>> ranfunc()
|
||||||
|
'ranfunc'
|
||||||
|
"""
|
||||||
|
return 'ranfunc'
|
||||||
|
|
||||||
|
|
||||||
|
def random_all():
|
||||||
|
"""A function where we ignore the output of ALL examples.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
# all-random
|
||||||
|
|
||||||
|
This mark tells the testing machinery that all subsequent examples should
|
||||||
|
be treated as random (ignoring their output). They are still executed,
|
||||||
|
so if a they raise an error, it will be detected as such, but their
|
||||||
|
output is completely ignored.
|
||||||
|
|
||||||
|
>>> 1+3
|
||||||
|
junk goes here...
|
||||||
|
|
||||||
|
>>> 1+3
|
||||||
|
klasdfj;
|
||||||
|
|
||||||
|
>>> 1+2
|
||||||
|
again, anything goes
|
||||||
|
blah...
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def iprand():
|
||||||
|
"""Some ipython tests with random output.
|
||||||
|
|
||||||
|
In [7]: 3+4
|
||||||
|
Out[7]: 7
|
||||||
|
|
||||||
|
In [8]: print('hello')
|
||||||
|
world # random
|
||||||
|
|
||||||
|
In [9]: iprand()
|
||||||
|
Out[9]: 'iprand'
|
||||||
|
"""
|
||||||
|
return 'iprand'
|
||||||
|
|
||||||
|
def iprand_all():
|
||||||
|
"""Some ipython tests with fully random output.
|
||||||
|
|
||||||
|
# all-random
|
||||||
|
|
||||||
|
In [7]: 1
|
||||||
|
Out[7]: 99
|
||||||
|
|
||||||
|
In [8]: print('hello')
|
||||||
|
world
|
||||||
|
|
||||||
|
In [9]: iprand_all()
|
||||||
|
Out[9]: 'junk'
|
||||||
|
"""
|
||||||
|
return 'iprand_all'
|
@ -0,0 +1,764 @@
|
|||||||
|
"""Nose Plugin that supports yap_ipython doctests.
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
|
||||||
|
- When generating examples for use as doctests, make sure that you have
|
||||||
|
pretty-printing OFF. This can be done either by setting the
|
||||||
|
``PlainTextFormatter.pprint`` option in your configuration file to False, or
|
||||||
|
by interactively disabling it with %Pprint. This is required so that yap_ipython
|
||||||
|
output matches that of normal Python, which is used by doctest for internal
|
||||||
|
execution.
|
||||||
|
|
||||||
|
- Do not rely on specific prompt numbers for results (such as using
|
||||||
|
'_34==True', for example). For yap_ipython tests run via an external process the
|
||||||
|
prompt numbers may be different, and yap_ipython tests run as normal python code
|
||||||
|
won't even have these special _NN variables set at all.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Module imports
|
||||||
|
|
||||||
|
# From the standard library
|
||||||
|
import builtins as builtin_mod
|
||||||
|
import doctest
|
||||||
|
import inspect
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from importlib import import_module
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
from testpath import modified_env
|
||||||
|
|
||||||
|
from inspect import getmodule
|
||||||
|
|
||||||
|
# We are overriding the default doctest runner, so we need to import a few
|
||||||
|
# things from doctest directly
|
||||||
|
from doctest import (REPORTING_FLAGS, REPORT_ONLY_FIRST_FAILURE,
|
||||||
|
_unittest_reportflags, DocTestRunner,
|
||||||
|
_extract_future_flags, pdb, _OutputRedirectingPdb,
|
||||||
|
_exception_traceback,
|
||||||
|
linecache)
|
||||||
|
|
||||||
|
# Third-party modules
|
||||||
|
|
||||||
|
from nose.plugins import doctests, Plugin
|
||||||
|
from nose.util import anyp, tolist
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Module globals and other constants
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Classes and functions
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def is_extension_module(filename):
|
||||||
|
"""Return whether the given filename is an extension module.
|
||||||
|
|
||||||
|
This simply checks that the extension is either .so or .pyd.
|
||||||
|
"""
|
||||||
|
return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
|
||||||
|
|
||||||
|
|
||||||
|
class DocTestSkip(object):
|
||||||
|
"""Object wrapper for doctests to be skipped."""
|
||||||
|
|
||||||
|
ds_skip = """Doctest to skip.
|
||||||
|
>>> 1 #doctest: +SKIP
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self,obj):
|
||||||
|
self.obj = obj
|
||||||
|
|
||||||
|
def __getattribute__(self,key):
|
||||||
|
if key == '__doc__':
|
||||||
|
return DocTestSkip.ds_skip
|
||||||
|
else:
|
||||||
|
return getattr(object.__getattribute__(self,'obj'),key)
|
||||||
|
|
||||||
|
# Modified version of the one in the stdlib, that fixes a python bug (doctests
|
||||||
|
# not found in extension modules, http://bugs.python.org/issue3158)
|
||||||
|
class DocTestFinder(doctest.DocTestFinder):
|
||||||
|
|
||||||
|
def _from_module(self, module, object):
|
||||||
|
"""
|
||||||
|
Return true if the given object is defined in the given
|
||||||
|
module.
|
||||||
|
"""
|
||||||
|
if module is None:
|
||||||
|
return True
|
||||||
|
elif inspect.isfunction(object):
|
||||||
|
return module.__dict__ is object.__globals__
|
||||||
|
elif inspect.isbuiltin(object):
|
||||||
|
return module.__name__ == object.__module__
|
||||||
|
elif inspect.isclass(object):
|
||||||
|
return module.__name__ == object.__module__
|
||||||
|
elif inspect.ismethod(object):
|
||||||
|
# This one may be a bug in cython that fails to correctly set the
|
||||||
|
# __module__ attribute of methods, but since the same error is easy
|
||||||
|
# to make by extension code writers, having this safety in place
|
||||||
|
# isn't such a bad idea
|
||||||
|
return module.__name__ == object.__self__.__class__.__module__
|
||||||
|
elif inspect.getmodule(object) is not None:
|
||||||
|
return module is inspect.getmodule(object)
|
||||||
|
elif hasattr(object, '__module__'):
|
||||||
|
return module.__name__ == object.__module__
|
||||||
|
elif isinstance(object, property):
|
||||||
|
return True # [XX] no way not be sure.
|
||||||
|
elif inspect.ismethoddescriptor(object):
|
||||||
|
# Unbound PyQt signals reach this point in Python 3.4b3, and we want
|
||||||
|
# to avoid throwing an error. See also http://bugs.python.org/issue3158
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
raise ValueError("object must be a class or function, got %r" % object)
|
||||||
|
|
||||||
|
def _find(self, tests, obj, name, module, source_lines, globs, seen):
|
||||||
|
"""
|
||||||
|
Find tests for the given object and any contained objects, and
|
||||||
|
add them to `tests`.
|
||||||
|
"""
|
||||||
|
print('_find for:', obj, name, module) # dbg
|
||||||
|
if hasattr(obj,"skip_doctest"):
|
||||||
|
#print 'SKIPPING DOCTEST FOR:',obj # dbg
|
||||||
|
obj = DocTestSkip(obj)
|
||||||
|
|
||||||
|
doctest.DocTestFinder._find(self,tests, obj, name, module,
|
||||||
|
source_lines, globs, seen)
|
||||||
|
|
||||||
|
# Below we re-run pieces of the above method with manual modifications,
|
||||||
|
# because the original code is buggy and fails to correctly identify
|
||||||
|
# doctests in extension modules.
|
||||||
|
|
||||||
|
# Local shorthands
|
||||||
|
from inspect import isroutine, isclass
|
||||||
|
|
||||||
|
# Look for tests in a module's contained objects.
|
||||||
|
if inspect.ismodule(obj) and self._recurse:
|
||||||
|
for valname, val in obj.__dict__.items():
|
||||||
|
valname1 = '%s.%s' % (name, valname)
|
||||||
|
if ( (isroutine(val) or isclass(val))
|
||||||
|
and self._from_module(module, val) ):
|
||||||
|
|
||||||
|
self._find(tests, val, valname1, module, source_lines,
|
||||||
|
globs, seen)
|
||||||
|
|
||||||
|
# Look for tests in a class's contained objects.
|
||||||
|
if inspect.isclass(obj) and self._recurse:
|
||||||
|
#print 'RECURSE into class:',obj # dbg
|
||||||
|
for valname, val in obj.__dict__.items():
|
||||||
|
# Special handling for staticmethod/classmethod.
|
||||||
|
if isinstance(val, staticmethod):
|
||||||
|
val = getattr(obj, valname)
|
||||||
|
if isinstance(val, classmethod):
|
||||||
|
val = getattr(obj, valname).__func__
|
||||||
|
|
||||||
|
# Recurse to methods, properties, and nested classes.
|
||||||
|
if ((inspect.isfunction(val) or inspect.isclass(val) or
|
||||||
|
inspect.ismethod(val) or
|
||||||
|
isinstance(val, property)) and
|
||||||
|
self._from_module(module, val)):
|
||||||
|
valname = '%s.%s' % (name, valname)
|
||||||
|
self._find(tests, val, valname, module, source_lines,
|
||||||
|
globs, seen)
|
||||||
|
|
||||||
|
|
||||||
|
class IPDoctestOutputChecker(doctest.OutputChecker):
|
||||||
|
"""Second-chance checker with support for random tests.
|
||||||
|
|
||||||
|
If the default comparison doesn't pass, this checker looks in the expected
|
||||||
|
output string for flags that tell us to ignore the output.
|
||||||
|
"""
|
||||||
|
|
||||||
|
random_re = re.compile(r'#\s*random\s+')
|
||||||
|
|
||||||
|
def check_output(self, want, got, optionflags):
|
||||||
|
"""Check output, accepting special markers embedded in the output.
|
||||||
|
|
||||||
|
If the output didn't pass the default validation but the special string
|
||||||
|
'#random' is included, we accept it."""
|
||||||
|
|
||||||
|
# Let the original tester verify first, in case people have valid tests
|
||||||
|
# that happen to have a comment saying '#random' embedded in.
|
||||||
|
ret = doctest.OutputChecker.check_output(self, want, got,
|
||||||
|
optionflags)
|
||||||
|
if not ret and self.random_re.search(want):
|
||||||
|
#print >> sys.stderr, 'RANDOM OK:',want # dbg
|
||||||
|
return True
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
class DocTestCase(doctests.DocTestCase):
|
||||||
|
"""Proxy for DocTestCase: provides an address() method that
|
||||||
|
returns the correct address for the doctest case. Otherwise
|
||||||
|
acts as a proxy to the test case. To provide hints for address(),
|
||||||
|
an obj may also be passed -- this will be used as the test object
|
||||||
|
for purposes of determining the test address, if it is provided.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Note: this method was taken from numpy's nosetester module.
|
||||||
|
|
||||||
|
# Subclass nose.plugins.doctests.DocTestCase to work around a bug in
|
||||||
|
# its constructor that blocks non-default arguments from being passed
|
||||||
|
# down into doctest.DocTestCase
|
||||||
|
|
||||||
|
def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
|
||||||
|
checker=None, obj=None, result_var='_'):
|
||||||
|
self._result_var = result_var
|
||||||
|
doctests.DocTestCase.__init__(self, test,
|
||||||
|
optionflags=optionflags,
|
||||||
|
setUp=setUp, tearDown=tearDown,
|
||||||
|
checker=checker)
|
||||||
|
# Now we must actually copy the original constructor from the stdlib
|
||||||
|
# doctest class, because we can't call it directly and a bug in nose
|
||||||
|
# means it never gets passed the right arguments.
|
||||||
|
|
||||||
|
self._dt_optionflags = optionflags
|
||||||
|
self._dt_checker = checker
|
||||||
|
self._dt_test = test
|
||||||
|
self._dt_test_globs_ori = test.globs
|
||||||
|
self._dt_setUp = setUp
|
||||||
|
self._dt_tearDown = tearDown
|
||||||
|
|
||||||
|
# XXX - store this runner once in the object!
|
||||||
|
runner = IPDocTestRunner(optionflags=optionflags,
|
||||||
|
checker=checker, verbose=False)
|
||||||
|
self._dt_runner = runner
|
||||||
|
|
||||||
|
|
||||||
|
# Each doctest should remember the directory it was loaded from, so
|
||||||
|
# things like %run work without too many contortions
|
||||||
|
self._ori_dir = os.path.dirname(test.filename)
|
||||||
|
|
||||||
|
# Modified runTest from the default stdlib
|
||||||
|
def runTest(self):
|
||||||
|
test = self._dt_test
|
||||||
|
runner = self._dt_runner
|
||||||
|
|
||||||
|
old = sys.stdout
|
||||||
|
new = StringIO()
|
||||||
|
optionflags = self._dt_optionflags
|
||||||
|
|
||||||
|
if not (optionflags & REPORTING_FLAGS):
|
||||||
|
# The option flags don't include any reporting flags,
|
||||||
|
# so add the default reporting flags
|
||||||
|
optionflags |= _unittest_reportflags
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Save our current directory and switch out to the one where the
|
||||||
|
# test was originally created, in case another doctest did a
|
||||||
|
# directory change. We'll restore this in the finally clause.
|
||||||
|
curdir = os.getcwd()
|
||||||
|
#print 'runTest in dir:', self._ori_dir # dbg
|
||||||
|
os.chdir(self._ori_dir)
|
||||||
|
|
||||||
|
runner.DIVIDER = "-"*70
|
||||||
|
failures, tries = runner.run(test,out=new.write,
|
||||||
|
clear_globs=False)
|
||||||
|
finally:
|
||||||
|
sys.stdout = old
|
||||||
|
os.chdir(curdir)
|
||||||
|
|
||||||
|
if failures:
|
||||||
|
raise self.failureException(self.format_failure(new.getvalue()))
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Modified test setup that syncs with ipython namespace"""
|
||||||
|
#print "setUp test", self._dt_test.examples # dbg
|
||||||
|
if isinstance(self._dt_test.examples[0], IPExample):
|
||||||
|
# for yap_ipython examples *only*, we swap the globals with the ipython
|
||||||
|
# namespace, after updating it with the globals (which doctest
|
||||||
|
# fills with the necessary info from the module being tested).
|
||||||
|
self.user_ns_orig = {}
|
||||||
|
self.user_ns_orig.update(_ip.user_ns)
|
||||||
|
_ip.user_ns.update(self._dt_test.globs)
|
||||||
|
# We must remove the _ key in the namespace, so that Python's
|
||||||
|
# doctest code sets it naturally
|
||||||
|
_ip.user_ns.pop('_', None)
|
||||||
|
_ip.user_ns['__builtins__'] = builtin_mod
|
||||||
|
self._dt_test.globs = _ip.user_ns
|
||||||
|
|
||||||
|
super(DocTestCase, self).setUp()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
|
||||||
|
# Undo the test.globs reassignment we made, so that the parent class
|
||||||
|
# teardown doesn't destroy the ipython namespace
|
||||||
|
if isinstance(self._dt_test.examples[0], IPExample):
|
||||||
|
self._dt_test.globs = self._dt_test_globs_ori
|
||||||
|
_ip.user_ns.clear()
|
||||||
|
_ip.user_ns.update(self.user_ns_orig)
|
||||||
|
|
||||||
|
# XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but
|
||||||
|
# it does look like one to me: its tearDown method tries to run
|
||||||
|
#
|
||||||
|
# delattr(builtin_mod, self._result_var)
|
||||||
|
#
|
||||||
|
# without checking that the attribute really is there; it implicitly
|
||||||
|
# assumes it should have been set via displayhook. But if the
|
||||||
|
# displayhook was never called, this doesn't necessarily happen. I
|
||||||
|
# haven't been able to find a little self-contained example outside of
|
||||||
|
# ipython that would show the problem so I can report it to the nose
|
||||||
|
# team, but it does happen a lot in our code.
|
||||||
|
#
|
||||||
|
# So here, we just protect as narrowly as possible by trapping an
|
||||||
|
# attribute error whose message would be the name of self._result_var,
|
||||||
|
# and letting any other error propagate.
|
||||||
|
try:
|
||||||
|
super(DocTestCase, self).tearDown()
|
||||||
|
except AttributeError as exc:
|
||||||
|
if exc.args[0] != self._result_var:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
# A simple subclassing of the original with a different class name, so we can
|
||||||
|
# distinguish and treat differently yap_ipython examples from pure python ones.
|
||||||
|
class IPExample(doctest.Example): pass
|
||||||
|
|
||||||
|
|
||||||
|
class IPExternalExample(doctest.Example):
|
||||||
|
"""Doctest examples to be run in an external process."""
|
||||||
|
|
||||||
|
def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
|
||||||
|
options=None):
|
||||||
|
# Parent constructor
|
||||||
|
doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
|
||||||
|
|
||||||
|
# An EXTRA newline is needed to prevent pexpect hangs
|
||||||
|
self.source += '\n'
|
||||||
|
|
||||||
|
|
||||||
|
class IPDocTestParser(doctest.DocTestParser):
|
||||||
|
"""
|
||||||
|
A class used to parse strings containing doctest examples.
|
||||||
|
|
||||||
|
Note: This is a version modified to properly recognize yap_ipython input and
|
||||||
|
convert any yap_ipython examples into valid Python ones.
|
||||||
|
"""
|
||||||
|
# This regular expression is used to find doctest examples in a
|
||||||
|
# string. It defines three groups: `source` is the source code
|
||||||
|
# (including leading indentation and prompts); `indent` is the
|
||||||
|
# indentation of the first (PS1) line of the source code; and
|
||||||
|
# `want` is the expected output (including leading indentation).
|
||||||
|
|
||||||
|
# Classic Python prompts or default yap_ipython ones
|
||||||
|
_PS1_PY = r'>>>'
|
||||||
|
_PS2_PY = r'\.\.\.'
|
||||||
|
|
||||||
|
_PS1_IP = r'In\ \[\d+\]:'
|
||||||
|
_PS2_IP = r'\ \ \ \.\.\.+:'
|
||||||
|
|
||||||
|
_RE_TPL = r'''
|
||||||
|
# Source consists of a PS1 line followed by zero or more PS2 lines.
|
||||||
|
(?P<source>
|
||||||
|
(?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
|
||||||
|
(?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
|
||||||
|
\n? # a newline
|
||||||
|
# Want consists of any non-blank lines that do not start with PS1.
|
||||||
|
(?P<want> (?:(?![ ]*$) # Not a blank line
|
||||||
|
(?![ ]*%s) # Not a line starting with PS1
|
||||||
|
(?![ ]*%s) # Not a line starting with PS2
|
||||||
|
.*$\n? # But any other line
|
||||||
|
)*)
|
||||||
|
'''
|
||||||
|
|
||||||
|
_EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
|
||||||
|
re.MULTILINE | re.VERBOSE)
|
||||||
|
|
||||||
|
_EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
|
||||||
|
re.MULTILINE | re.VERBOSE)
|
||||||
|
|
||||||
|
# Mark a test as being fully random. In this case, we simply append the
|
||||||
|
# random marker ('#random') to each individual example's output. This way
|
||||||
|
# we don't need to modify any other code.
|
||||||
|
_RANDOM_TEST = re.compile(r'#\s*all-random\s+')
|
||||||
|
|
||||||
|
# Mark tests to be executed in an external process - currently unsupported.
|
||||||
|
_EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
|
||||||
|
|
||||||
|
def ip2py(self,source):
|
||||||
|
"""Convert input yap_ipython source into valid Python."""
|
||||||
|
block = _ip.input_transformer_manager.transform_cell(source)
|
||||||
|
if len(block.splitlines()) == 1:
|
||||||
|
return _ip.prefilter(block)
|
||||||
|
else:
|
||||||
|
return block
|
||||||
|
|
||||||
|
def parse(self, string, name='<string>'):
|
||||||
|
"""
|
||||||
|
Divide the given string into examples and intervening text,
|
||||||
|
and return them as a list of alternating Examples and strings.
|
||||||
|
Line numbers for the Examples are 0-based. The optional
|
||||||
|
argument `name` is a name identifying this string, and is only
|
||||||
|
used for error messages.
|
||||||
|
"""
|
||||||
|
|
||||||
|
#print 'Parse string:\n',string # dbg
|
||||||
|
|
||||||
|
string = string.expandtabs()
|
||||||
|
# If all lines begin with the same indentation, then strip it.
|
||||||
|
min_indent = self._min_indent(string)
|
||||||
|
if min_indent > 0:
|
||||||
|
string = '\n'.join([l[min_indent:] for l in string.split('\n')])
|
||||||
|
|
||||||
|
output = []
|
||||||
|
charno, lineno = 0, 0
|
||||||
|
|
||||||
|
# We make 'all random' tests by adding the '# random' mark to every
|
||||||
|
# block of output in the test.
|
||||||
|
if self._RANDOM_TEST.search(string):
|
||||||
|
random_marker = '\n# random'
|
||||||
|
else:
|
||||||
|
random_marker = ''
|
||||||
|
|
||||||
|
# Whether to convert the input from ipython to python syntax
|
||||||
|
ip2py = False
|
||||||
|
# Find all doctest examples in the string. First, try them as Python
|
||||||
|
# examples, then as yap_ipython ones
|
||||||
|
terms = list(self._EXAMPLE_RE_PY.finditer(string))
|
||||||
|
if terms:
|
||||||
|
# Normal Python example
|
||||||
|
#print '-'*70 # dbg
|
||||||
|
#print 'PyExample, Source:\n',string # dbg
|
||||||
|
#print '-'*70 # dbg
|
||||||
|
Example = doctest.Example
|
||||||
|
else:
|
||||||
|
# It's an ipython example. Note that IPExamples are run
|
||||||
|
# in-process, so their syntax must be turned into valid python.
|
||||||
|
# IPExternalExamples are run out-of-process (via pexpect) so they
|
||||||
|
# don't need any filtering (a real ipython will be executing them).
|
||||||
|
terms = list(self._EXAMPLE_RE_IP.finditer(string))
|
||||||
|
if self._EXTERNAL_IP.search(string):
|
||||||
|
#print '-'*70 # dbg
|
||||||
|
#print 'IPExternalExample, Source:\n',string # dbg
|
||||||
|
#print '-'*70 # dbg
|
||||||
|
Example = IPExternalExample
|
||||||
|
else:
|
||||||
|
#print '-'*70 # dbg
|
||||||
|
#print 'IPExample, Source:\n',string # dbg
|
||||||
|
#print '-'*70 # dbg
|
||||||
|
Example = IPExample
|
||||||
|
ip2py = True
|
||||||
|
|
||||||
|
for m in terms:
|
||||||
|
# Add the pre-example text to `output`.
|
||||||
|
output.append(string[charno:m.start()])
|
||||||
|
# Update lineno (lines before this example)
|
||||||
|
lineno += string.count('\n', charno, m.start())
|
||||||
|
# Extract info from the regexp match.
|
||||||
|
(source, options, want, exc_msg) = \
|
||||||
|
self._parse_example(m, name, lineno,ip2py)
|
||||||
|
|
||||||
|
# Append the random-output marker (it defaults to empty in most
|
||||||
|
# cases, it's only non-empty for 'all-random' tests):
|
||||||
|
want += random_marker
|
||||||
|
|
||||||
|
if Example is IPExternalExample:
|
||||||
|
options[doctest.NORMALIZE_WHITESPACE] = True
|
||||||
|
want += '\n'
|
||||||
|
|
||||||
|
# Create an Example, and add it to the list.
|
||||||
|
if not self._IS_BLANK_OR_COMMENT(source):
|
||||||
|
output.append(Example(source, want, exc_msg,
|
||||||
|
lineno=lineno,
|
||||||
|
indent=min_indent+len(m.group('indent')),
|
||||||
|
options=options))
|
||||||
|
# Update lineno (lines inside this example)
|
||||||
|
lineno += string.count('\n', m.start(), m.end())
|
||||||
|
# Update charno.
|
||||||
|
charno = m.end()
|
||||||
|
# Add any remaining post-example text to `output`.
|
||||||
|
output.append(string[charno:])
|
||||||
|
return output
|
||||||
|
|
||||||
|
def _parse_example(self, m, name, lineno,ip2py=False):
|
||||||
|
"""
|
||||||
|
Given a regular expression match from `_EXAMPLE_RE` (`m`),
|
||||||
|
return a pair `(source, want)`, where `source` is the matched
|
||||||
|
example's source code (with prompts and indentation stripped);
|
||||||
|
and `want` is the example's expected output (with indentation
|
||||||
|
stripped).
|
||||||
|
|
||||||
|
`name` is the string's name, and `lineno` is the line number
|
||||||
|
where the example starts; both are used for error messages.
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
`ip2py`: if true, filter the input via yap_ipython to convert the syntax
|
||||||
|
into valid python.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Get the example's indentation level.
|
||||||
|
indent = len(m.group('indent'))
|
||||||
|
|
||||||
|
# Divide source into lines; check that they're properly
|
||||||
|
# indented; and then strip their indentation & prompts.
|
||||||
|
source_lines = m.group('source').split('\n')
|
||||||
|
|
||||||
|
# We're using variable-length input prompts
|
||||||
|
ps1 = m.group('ps1')
|
||||||
|
ps2 = m.group('ps2')
|
||||||
|
ps1_len = len(ps1)
|
||||||
|
|
||||||
|
self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
|
||||||
|
if ps2:
|
||||||
|
self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
|
||||||
|
|
||||||
|
source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
|
||||||
|
|
||||||
|
if ip2py:
|
||||||
|
# Convert source input from yap_ipython into valid Python syntax
|
||||||
|
source = self.ip2py(source)
|
||||||
|
|
||||||
|
# Divide want into lines; check that it's properly indented; and
|
||||||
|
# then strip the indentation. Spaces before the last newline should
|
||||||
|
# be preserved, so plain rstrip() isn't good enough.
|
||||||
|
want = m.group('want')
|
||||||
|
want_lines = want.split('\n')
|
||||||
|
if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
|
||||||
|
del want_lines[-1] # forget final newline & spaces after it
|
||||||
|
self._check_prefix(want_lines, ' '*indent, name,
|
||||||
|
lineno + len(source_lines))
|
||||||
|
|
||||||
|
# Remove ipython output prompt that might be present in the first line
|
||||||
|
want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
|
||||||
|
|
||||||
|
want = '\n'.join([wl[indent:] for wl in want_lines])
|
||||||
|
|
||||||
|
# If `want` contains a traceback message, then extract it.
|
||||||
|
m = self._EXCEPTION_RE.match(want)
|
||||||
|
if m:
|
||||||
|
exc_msg = m.group('msg')
|
||||||
|
else:
|
||||||
|
exc_msg = None
|
||||||
|
|
||||||
|
# Extract options from the source.
|
||||||
|
options = self._find_options(source, name, lineno)
|
||||||
|
|
||||||
|
return source, options, want, exc_msg
|
||||||
|
|
||||||
|
def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
|
||||||
|
"""
|
||||||
|
Given the lines of a source string (including prompts and
|
||||||
|
leading indentation), check to make sure that every prompt is
|
||||||
|
followed by a space character. If any line is not followed by
|
||||||
|
a space character, then raise ValueError.
|
||||||
|
|
||||||
|
Note: yap_ipython-modified version which takes the input prompt length as a
|
||||||
|
parameter, so that prompts of variable length can be dealt with.
|
||||||
|
"""
|
||||||
|
space_idx = indent+ps1_len
|
||||||
|
min_len = space_idx+1
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if len(line) >= min_len and line[space_idx] != ' ':
|
||||||
|
raise ValueError('line %r of the docstring for %s '
|
||||||
|
'lacks blank after %s: %r' %
|
||||||
|
(lineno+i+1, name,
|
||||||
|
line[indent:space_idx], line))
|
||||||
|
|
||||||
|
|
||||||
|
SKIP = doctest.register_optionflag('SKIP')
|
||||||
|
|
||||||
|
|
||||||
|
class IPDocTestRunner(doctest.DocTestRunner,object):
|
||||||
|
"""Test runner that synchronizes the yap_ipython namespace with test globals.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run(self, test, compileflags=None, out=None, clear_globs=True):
|
||||||
|
|
||||||
|
# Hack: ipython needs access to the execution context of the example,
|
||||||
|
# so that it can propagate user variables loaded by %run into
|
||||||
|
# test.globs. We put them here into our modified %run as a function
|
||||||
|
# attribute. Our new %run will then only make the namespace update
|
||||||
|
# when called (rather than unconconditionally updating test.globs here
|
||||||
|
# for all examples, most of which won't be calling %run anyway).
|
||||||
|
#_ip._ipdoctest_test_globs = test.globs
|
||||||
|
#_ip._ipdoctest_test_filename = test.filename
|
||||||
|
|
||||||
|
test.globs.update(_ip.user_ns)
|
||||||
|
|
||||||
|
# Override terminal size to standardise traceback format
|
||||||
|
with modified_env({'COLUMNS': '80', 'LINES': '24'}):
|
||||||
|
return super(IPDocTestRunner,self).run(test,
|
||||||
|
compileflags,out,clear_globs)
|
||||||
|
|
||||||
|
|
||||||
|
class DocFileCase(doctest.DocFileCase):
|
||||||
|
"""Overrides to provide filename
|
||||||
|
"""
|
||||||
|
def address(self):
|
||||||
|
return (self._dt_test.filename, None, None)
|
||||||
|
|
||||||
|
|
||||||
|
class ExtensionDoctest(doctests.Doctest):
|
||||||
|
"""Nose Plugin that supports doctests in extension modules.
|
||||||
|
"""
|
||||||
|
name = 'extdoctest' # call nosetests with --with-extdoctest
|
||||||
|
enabled = True
|
||||||
|
|
||||||
|
def options(self, parser, env=os.environ):
|
||||||
|
Plugin.options(self, parser, env)
|
||||||
|
parser.add_option('--doctest-tests', action='store_true',
|
||||||
|
dest='doctest_tests',
|
||||||
|
default=env.get('NOSE_DOCTEST_TESTS',True),
|
||||||
|
help="Also look for doctests in test modules. "
|
||||||
|
"Note that classes, methods and functions should "
|
||||||
|
"have either doctests or non-doctest tests, "
|
||||||
|
"not both. [NOSE_DOCTEST_TESTS]")
|
||||||
|
parser.add_option('--doctest-extension', action="append",
|
||||||
|
dest="doctestExtension",
|
||||||
|
help="Also look for doctests in files with "
|
||||||
|
"this extension [NOSE_DOCTEST_EXTENSION]")
|
||||||
|
# Set the default as a list, if given in env; otherwise
|
||||||
|
# an additional value set on the command line will cause
|
||||||
|
# an error.
|
||||||
|
env_setting = env.get('NOSE_DOCTEST_EXTENSION')
|
||||||
|
if env_setting is not None:
|
||||||
|
parser.set_defaults(doctestExtension=tolist(env_setting))
|
||||||
|
|
||||||
|
|
||||||
|
def configure(self, options, config):
|
||||||
|
Plugin.configure(self, options, config)
|
||||||
|
# Pull standard doctest plugin out of config; we will do doctesting
|
||||||
|
config.plugins.plugins = [p for p in config.plugins.plugins
|
||||||
|
if p.name != 'doctest']
|
||||||
|
self.doctest_tests = options.doctest_tests
|
||||||
|
self.extension = tolist(options.doctestExtension)
|
||||||
|
|
||||||
|
self.parser = doctest.DocTestParser()
|
||||||
|
self.finder = DocTestFinder()
|
||||||
|
self.checker = IPDoctestOutputChecker()
|
||||||
|
self.globs = None
|
||||||
|
self.extraglobs = None
|
||||||
|
|
||||||
|
|
||||||
|
def loadTestsFromExtensionModule(self,filename):
|
||||||
|
bpath,mod = os.path.split(filename)
|
||||||
|
modname = os.path.splitext(mod)[0]
|
||||||
|
try:
|
||||||
|
sys.path.append(bpath)
|
||||||
|
module = import_module(modname)
|
||||||
|
tests = list(self.loadTestsFromModule(module))
|
||||||
|
finally:
|
||||||
|
sys.path.pop()
|
||||||
|
return tests
|
||||||
|
|
||||||
|
# NOTE: the method below is almost a copy of the original one in nose, with
|
||||||
|
# a few modifications to control output checking.
|
||||||
|
|
||||||
|
def loadTestsFromModule(self, module):
|
||||||
|
#print '*** ipdoctest - lTM',module # dbg
|
||||||
|
|
||||||
|
if not self.matches(module.__name__):
|
||||||
|
log.debug("Doctest doesn't want module %s", module)
|
||||||
|
return
|
||||||
|
|
||||||
|
tests = self.finder.find(module,globs=self.globs,
|
||||||
|
extraglobs=self.extraglobs)
|
||||||
|
if not tests:
|
||||||
|
return
|
||||||
|
|
||||||
|
# always use whitespace and ellipsis options
|
||||||
|
optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
|
||||||
|
|
||||||
|
tests.sort()
|
||||||
|
module_file = module.__file__
|
||||||
|
if module_file[-4:] in ('.pyc', '.pyo'):
|
||||||
|
module_file = module_file[:-1]
|
||||||
|
for test in tests:
|
||||||
|
if not test.examples:
|
||||||
|
continue
|
||||||
|
if not test.filename:
|
||||||
|
test.filename = module_file
|
||||||
|
|
||||||
|
yield DocTestCase(test,
|
||||||
|
optionflags=optionflags,
|
||||||
|
checker=self.checker)
|
||||||
|
|
||||||
|
|
||||||
|
def loadTestsFromFile(self, filename):
|
||||||
|
#print "ipdoctest - from file", filename # dbg
|
||||||
|
if is_extension_module(filename):
|
||||||
|
for t in self.loadTestsFromExtensionModule(filename):
|
||||||
|
yield t
|
||||||
|
else:
|
||||||
|
if self.extension and anyp(filename.endswith, self.extension):
|
||||||
|
name = os.path.basename(filename)
|
||||||
|
dh = open(filename)
|
||||||
|
try:
|
||||||
|
doc = dh.read()
|
||||||
|
finally:
|
||||||
|
dh.close()
|
||||||
|
test = self.parser.get_doctest(
|
||||||
|
doc, globs={'__file__': filename}, name=name,
|
||||||
|
filename=filename, lineno=0)
|
||||||
|
if test.examples:
|
||||||
|
#print 'FileCase:',test.examples # dbg
|
||||||
|
yield DocFileCase(test)
|
||||||
|
else:
|
||||||
|
yield False # no tests to load
|
||||||
|
|
||||||
|
|
||||||
|
class IPythonDoctest(ExtensionDoctest):
|
||||||
|
"""Nose Plugin that supports doctests in extension modules.
|
||||||
|
"""
|
||||||
|
name = 'ipdoctest' # call nosetests with --with-ipdoctest
|
||||||
|
enabled = True
|
||||||
|
|
||||||
|
def makeTest(self, obj, parent):
|
||||||
|
"""Look for doctests in the given object, which will be a
|
||||||
|
function, method or class.
|
||||||
|
"""
|
||||||
|
#print 'Plugin analyzing:', obj, parent # dbg
|
||||||
|
# always use whitespace and ellipsis options
|
||||||
|
optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
|
||||||
|
|
||||||
|
doctests = self.finder.find(obj, module=getmodule(parent))
|
||||||
|
if doctests:
|
||||||
|
for test in doctests:
|
||||||
|
if len(test.examples) == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
yield DocTestCase(test, obj=obj,
|
||||||
|
optionflags=optionflags,
|
||||||
|
checker=self.checker)
|
||||||
|
|
||||||
|
def options(self, parser, env=os.environ):
|
||||||
|
#print "Options for nose plugin:", self.name # dbg
|
||||||
|
Plugin.options(self, parser, env)
|
||||||
|
parser.add_option('--ipdoctest-tests', action='store_true',
|
||||||
|
dest='ipdoctest_tests',
|
||||||
|
default=env.get('NOSE_IPDOCTEST_TESTS',True),
|
||||||
|
help="Also look for doctests in test modules. "
|
||||||
|
"Note that classes, methods and functions should "
|
||||||
|
"have either doctests or non-doctest tests, "
|
||||||
|
"not both. [NOSE_IPDOCTEST_TESTS]")
|
||||||
|
parser.add_option('--ipdoctest-extension', action="append",
|
||||||
|
dest="ipdoctest_extension",
|
||||||
|
help="Also look for doctests in files with "
|
||||||
|
"this extension [NOSE_IPDOCTEST_EXTENSION]")
|
||||||
|
# Set the default as a list, if given in env; otherwise
|
||||||
|
# an additional value set on the command line will cause
|
||||||
|
# an error.
|
||||||
|
env_setting = env.get('NOSE_IPDOCTEST_EXTENSION')
|
||||||
|
if env_setting is not None:
|
||||||
|
parser.set_defaults(ipdoctest_extension=tolist(env_setting))
|
||||||
|
|
||||||
|
def configure(self, options, config):
|
||||||
|
#print "Configuring nose plugin:", self.name # dbg
|
||||||
|
Plugin.configure(self, options, config)
|
||||||
|
# Pull standard doctest plugin out of config; we will do doctesting
|
||||||
|
config.plugins.plugins = [p for p in config.plugins.plugins
|
||||||
|
if p.name != 'doctest']
|
||||||
|
self.doctest_tests = options.ipdoctest_tests
|
||||||
|
self.extension = tolist(options.ipdoctest_extension)
|
||||||
|
|
||||||
|
self.parser = IPDocTestParser()
|
||||||
|
self.finder = DocTestFinder(parser=self.parser)
|
||||||
|
self.checker = IPDoctestOutputChecker()
|
||||||
|
self.globs = None
|
||||||
|
self.extraglobs = None
|
@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Nose-based test runner.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from nose.core import main
|
||||||
|
from nose.plugins.builtin import plugins
|
||||||
|
from nose.plugins.doctests import Doctest
|
||||||
|
|
||||||
|
from . import ipdoctest
|
||||||
|
from .ipdoctest import IPDocTestRunner
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print('WARNING: this code is incomplete!')
|
||||||
|
print()
|
||||||
|
|
||||||
|
pp = [x() for x in plugins] # activate all builtin plugins first
|
||||||
|
main(testRunner=IPDocTestRunner(),
|
||||||
|
plugins=pp+[ipdoctest.IPythonDoctest(),Doctest()])
|
@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""A Nose plugin to support yap_ipython doctests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup(name='yap_ipython doctest plugin',
|
||||||
|
version='0.1',
|
||||||
|
author='The yap_ipython Team',
|
||||||
|
description = 'Nose plugin to load yap_ipython-extended doctests',
|
||||||
|
license = 'LGPL',
|
||||||
|
py_modules = ['ipdoctest'],
|
||||||
|
entry_points = {
|
||||||
|
'nose.plugins.0.10': ['ipdoctest = ipdoctest:IPythonDoctest',
|
||||||
|
'extdoctest = ipdoctest:ExtensionDoctest',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
@ -0,0 +1,19 @@
|
|||||||
|
"""Simple script to show reference holding behavior.
|
||||||
|
|
||||||
|
This is used by a companion test case.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import gc
|
||||||
|
|
||||||
|
class C(object):
|
||||||
|
def __del__(self):
|
||||||
|
pass
|
||||||
|
#print 'deleting object...' # dbg
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
c = C()
|
||||||
|
|
||||||
|
c_refs = gc.get_referrers(c)
|
||||||
|
ref_ids = list(map(id,c_refs))
|
||||||
|
|
||||||
|
print('c referrers:',list(map(type,c_refs)))
|
@ -0,0 +1,33 @@
|
|||||||
|
"""Simple example using doctests.
|
||||||
|
|
||||||
|
This file just contains doctests both using plain python and yap_ipython prompts.
|
||||||
|
All tests should be loaded by nose.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def pyfunc():
|
||||||
|
"""Some pure python tests...
|
||||||
|
|
||||||
|
>>> pyfunc()
|
||||||
|
'pyfunc'
|
||||||
|
|
||||||
|
>>> import os
|
||||||
|
|
||||||
|
>>> 2+3
|
||||||
|
5
|
||||||
|
|
||||||
|
>>> for i in range(3):
|
||||||
|
... print(i, end=' ')
|
||||||
|
... print(i+1, end=' ')
|
||||||
|
...
|
||||||
|
0 1 1 2 2 3
|
||||||
|
"""
|
||||||
|
return 'pyfunc'
|
||||||
|
|
||||||
|
|
||||||
|
def ipyfunc2():
|
||||||
|
"""Some pure python tests...
|
||||||
|
|
||||||
|
>>> 1+1
|
||||||
|
2
|
||||||
|
"""
|
||||||
|
return 'pyfunc2'
|
@ -0,0 +1,2 @@
|
|||||||
|
x = 1
|
||||||
|
print('x is:',x)
|
@ -0,0 +1,80 @@
|
|||||||
|
"""Tests for the ipdoctest machinery itself.
|
||||||
|
|
||||||
|
Note: in a file named test_X, functions whose only test is their docstring (as
|
||||||
|
a doctest) and which have no test functionality of their own, should be called
|
||||||
|
'doctest_foo' instead of 'test_foo', otherwise they get double-counted (the
|
||||||
|
empty function call is counted as a test, which just inflates tests numbers
|
||||||
|
artificially).
|
||||||
|
"""
|
||||||
|
from yap_ipython.utils.py3compat import doctest_refactor_print
|
||||||
|
|
||||||
|
@doctest_refactor_print
|
||||||
|
def doctest_simple():
|
||||||
|
"""ipdoctest must handle simple inputs
|
||||||
|
|
||||||
|
In [1]: 1
|
||||||
|
Out[1]: 1
|
||||||
|
|
||||||
|
In [2]: print 1
|
||||||
|
1
|
||||||
|
"""
|
||||||
|
|
||||||
|
@doctest_refactor_print
|
||||||
|
def doctest_multiline1():
|
||||||
|
"""The ipdoctest machinery must handle multiline examples gracefully.
|
||||||
|
|
||||||
|
In [2]: for i in range(4):
|
||||||
|
...: print i
|
||||||
|
...:
|
||||||
|
0
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
"""
|
||||||
|
|
||||||
|
@doctest_refactor_print
|
||||||
|
def doctest_multiline2():
|
||||||
|
"""Multiline examples that define functions and print output.
|
||||||
|
|
||||||
|
In [7]: def f(x):
|
||||||
|
...: return x+1
|
||||||
|
...:
|
||||||
|
|
||||||
|
In [8]: f(1)
|
||||||
|
Out[8]: 2
|
||||||
|
|
||||||
|
In [9]: def g(x):
|
||||||
|
...: print 'x is:',x
|
||||||
|
...:
|
||||||
|
|
||||||
|
In [10]: g(1)
|
||||||
|
x is: 1
|
||||||
|
|
||||||
|
In [11]: g('hello')
|
||||||
|
x is: hello
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def doctest_multiline3():
|
||||||
|
"""Multiline examples with blank lines.
|
||||||
|
|
||||||
|
In [12]: def h(x):
|
||||||
|
....: if x>1:
|
||||||
|
....: return x**2
|
||||||
|
....: # To leave a blank line in the input, you must mark it
|
||||||
|
....: # with a comment character:
|
||||||
|
....: #
|
||||||
|
....: # otherwise the doctest parser gets confused.
|
||||||
|
....: else:
|
||||||
|
....: return -1
|
||||||
|
....:
|
||||||
|
|
||||||
|
In [13]: h(5)
|
||||||
|
Out[13]: 25
|
||||||
|
|
||||||
|
In [14]: h(1)
|
||||||
|
Out[14]: -1
|
||||||
|
|
||||||
|
In [15]: h(0)
|
||||||
|
Out[15]: -1
|
||||||
|
"""
|
@ -0,0 +1,46 @@
|
|||||||
|
"""Some simple tests for the plugin while running scripts.
|
||||||
|
"""
|
||||||
|
# Module imports
|
||||||
|
# Std lib
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
# Our own
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Testing functions
|
||||||
|
|
||||||
|
def test_trivial():
|
||||||
|
"""A trivial passing test."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def doctest_run():
|
||||||
|
"""Test running a trivial script.
|
||||||
|
|
||||||
|
In [13]: run simplevars.py
|
||||||
|
x is: 1
|
||||||
|
"""
|
||||||
|
|
||||||
|
def doctest_runvars():
|
||||||
|
"""Test that variables defined in scripts get loaded correcly via %run.
|
||||||
|
|
||||||
|
In [13]: run simplevars.py
|
||||||
|
x is: 1
|
||||||
|
|
||||||
|
In [14]: x
|
||||||
|
Out[14]: 1
|
||||||
|
"""
|
||||||
|
|
||||||
|
def doctest_ivars():
|
||||||
|
"""Test that variables defined interactively are picked up.
|
||||||
|
In [5]: zz=1
|
||||||
|
|
||||||
|
In [6]: zz
|
||||||
|
Out[6]: 1
|
||||||
|
"""
|
||||||
|
|
||||||
|
def doctest_refs():
|
||||||
|
"""DocTest reference holding issues when running scripts.
|
||||||
|
|
||||||
|
In [32]: run show_refs.py
|
||||||
|
c referrers: [<... 'dict'>]
|
||||||
|
"""
|
@ -0,0 +1,10 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
__docformat__ = "restructuredtext en"
|
||||||
|
#-------------------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2005 Fernando Perez <fperez@colorado.edu>
|
||||||
|
# Brian E Granger <ellisonbg@gmail.com>
|
||||||
|
# Benjamin Ragan-Kelley <benjaminrk@gmail.com>
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the BSD License. The full license is in
|
||||||
|
# the file COPYING, distributed as part of this software.
|
||||||
|
#-------------------------------------------------------------------------------
|
@ -0,0 +1,164 @@
|
|||||||
|
"""Tests for the decorators we've created for yap_ipython.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Module imports
|
||||||
|
# Std lib
|
||||||
|
import inspect
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Third party
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
# Our own
|
||||||
|
from yap_ipython.testing import decorators as dec
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Utilities
|
||||||
|
|
||||||
|
# Note: copied from OInspect, kept here so the testing stuff doesn't create
|
||||||
|
# circular dependencies and is easier to reuse.
|
||||||
|
def getargspec(obj):
|
||||||
|
"""Get the names and default values of a function's arguments.
|
||||||
|
|
||||||
|
A tuple of four things is returned: (args, varargs, varkw, defaults).
|
||||||
|
'args' is a list of the argument names (it may contain nested lists).
|
||||||
|
'varargs' and 'varkw' are the names of the * and ** arguments or None.
|
||||||
|
'defaults' is an n-tuple of the default values of the last n arguments.
|
||||||
|
|
||||||
|
Modified version of inspect.getargspec from the Python Standard
|
||||||
|
Library."""
|
||||||
|
|
||||||
|
if inspect.isfunction(obj):
|
||||||
|
func_obj = obj
|
||||||
|
elif inspect.ismethod(obj):
|
||||||
|
func_obj = obj.__func__
|
||||||
|
else:
|
||||||
|
raise TypeError('arg is not a Python function')
|
||||||
|
args, varargs, varkw = inspect.getargs(func_obj.__code__)
|
||||||
|
return args, varargs, varkw, func_obj.__defaults__
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Testing functions
|
||||||
|
|
||||||
|
@dec.as_unittest
|
||||||
|
def trivial():
|
||||||
|
"""A trivial test"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dec.skip
|
||||||
|
def test_deliberately_broken():
|
||||||
|
"""A deliberately broken test - we want to skip this one."""
|
||||||
|
1/0
|
||||||
|
|
||||||
|
@dec.skip('Testing the skip decorator')
|
||||||
|
def test_deliberately_broken2():
|
||||||
|
"""Another deliberately broken test - we want to skip this one."""
|
||||||
|
1/0
|
||||||
|
|
||||||
|
|
||||||
|
# Verify that we can correctly skip the doctest for a function at will, but
|
||||||
|
# that the docstring itself is NOT destroyed by the decorator.
|
||||||
|
def doctest_bad(x,y=1,**k):
|
||||||
|
"""A function whose doctest we need to skip.
|
||||||
|
|
||||||
|
>>> 1+1
|
||||||
|
3
|
||||||
|
"""
|
||||||
|
print('x:',x)
|
||||||
|
print('y:',y)
|
||||||
|
print('k:',k)
|
||||||
|
|
||||||
|
|
||||||
|
def call_doctest_bad():
|
||||||
|
"""Check that we can still call the decorated functions.
|
||||||
|
|
||||||
|
>>> doctest_bad(3,y=4)
|
||||||
|
x: 3
|
||||||
|
y: 4
|
||||||
|
k: {}
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_skip_dt_decorator():
|
||||||
|
"""Doctest-skipping decorator should preserve the docstring.
|
||||||
|
"""
|
||||||
|
# Careful: 'check' must be a *verbatim* copy of the doctest_bad docstring!
|
||||||
|
check = """A function whose doctest we need to skip.
|
||||||
|
|
||||||
|
>>> 1+1
|
||||||
|
3
|
||||||
|
"""
|
||||||
|
# Fetch the docstring from doctest_bad after decoration.
|
||||||
|
val = doctest_bad.__doc__
|
||||||
|
|
||||||
|
nt.assert_equal(check,val,"doctest_bad docstrings don't match")
|
||||||
|
|
||||||
|
|
||||||
|
# Doctest skipping should work for class methods too
|
||||||
|
class FooClass(object):
|
||||||
|
"""FooClass
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
>>> 1+1
|
||||||
|
2
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self,x):
|
||||||
|
"""Make a FooClass.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
>>> f = FooClass(3)
|
||||||
|
junk
|
||||||
|
"""
|
||||||
|
print('Making a FooClass.')
|
||||||
|
self.x = x
|
||||||
|
|
||||||
|
def bar(self,y):
|
||||||
|
"""Example:
|
||||||
|
|
||||||
|
>>> ff = FooClass(3)
|
||||||
|
>>> ff.bar(0)
|
||||||
|
boom!
|
||||||
|
>>> 1/0
|
||||||
|
bam!
|
||||||
|
"""
|
||||||
|
return 1/y
|
||||||
|
|
||||||
|
def baz(self,y):
|
||||||
|
"""Example:
|
||||||
|
|
||||||
|
>>> ff2 = FooClass(3)
|
||||||
|
Making a FooClass.
|
||||||
|
>>> ff2.baz(3)
|
||||||
|
True
|
||||||
|
"""
|
||||||
|
return self.x==y
|
||||||
|
|
||||||
|
|
||||||
|
def test_skip_dt_decorator2():
|
||||||
|
"""Doctest-skipping decorator should preserve function signature.
|
||||||
|
"""
|
||||||
|
# Hardcoded correct answer
|
||||||
|
dtargs = (['x', 'y'], None, 'k', (1,))
|
||||||
|
# Introspect out the value
|
||||||
|
dtargsr = getargspec(doctest_bad)
|
||||||
|
assert dtargsr==dtargs, \
|
||||||
|
"Incorrectly reconstructed args for doctest_bad: %s" % (dtargsr,)
|
||||||
|
|
||||||
|
|
||||||
|
@dec.skip_linux
|
||||||
|
def test_linux():
|
||||||
|
nt.assert_false(sys.platform.startswith('linux'),"This test can't run under linux")
|
||||||
|
|
||||||
|
@dec.skip_win32
|
||||||
|
def test_win32():
|
||||||
|
nt.assert_not_equal(sys.platform,'win32',"This test can't run under windows")
|
||||||
|
|
||||||
|
@dec.skip_osx
|
||||||
|
def test_osx():
|
||||||
|
nt.assert_not_equal(sys.platform,'darwin',"This test can't run under osx")
|
||||||
|
|
@ -0,0 +1,137 @@
|
|||||||
|
"""Tests for yap_ipython's test support utilities.
|
||||||
|
|
||||||
|
These are decorators that allow standalone functions and docstrings to be seen
|
||||||
|
as tests by unittest, replicating some of nose's functionality. Additionally,
|
||||||
|
yap_ipython-syntax docstrings can be auto-converted to '>>>' so that ipython
|
||||||
|
sessions can be copy-pasted as tests.
|
||||||
|
|
||||||
|
This file can be run as a script, and it will call unittest.main(). We must
|
||||||
|
check that it works with unittest as well as with nose...
|
||||||
|
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- Using nosetests --with-doctest --doctest-tests testfile.py
|
||||||
|
will find docstrings as tests wherever they are, even in methods. But
|
||||||
|
if we use ipython syntax in the docstrings, they must be decorated with
|
||||||
|
@ipdocstring. This is OK for test-only code, but not for user-facing
|
||||||
|
docstrings where we want to keep the ipython syntax.
|
||||||
|
|
||||||
|
- Using nosetests --with-doctest file.py
|
||||||
|
also finds doctests if the file name doesn't have 'test' in it, because it is
|
||||||
|
treated like a normal module. But if nose treats the file like a test file,
|
||||||
|
then for normal classes to be doctested the extra --doctest-tests is
|
||||||
|
necessary.
|
||||||
|
|
||||||
|
- running this script with python (it has a __main__ section at the end) misses
|
||||||
|
one docstring test, the one embedded in the Foo object method. Since our
|
||||||
|
approach relies on using decorators that create standalone TestCase
|
||||||
|
instances, it can only be used for functions, not for methods of objects.
|
||||||
|
Authors
|
||||||
|
-------
|
||||||
|
|
||||||
|
- Fernando Perez <Fernando.Perez@berkeley.edu>
|
||||||
|
"""
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2009-2011 The yap_ipython Development Team
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the BSD License. The full license is in
|
||||||
|
# the file COPYING, distributed as part of this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from yap_ipython.testing.ipunittest import ipdoctest, ipdocstring
|
||||||
|
from yap_ipython.utils.py3compat import doctest_refactor_print
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Test classes and functions
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
@ipdoctest
|
||||||
|
@doctest_refactor_print
|
||||||
|
def simple_dt():
|
||||||
|
"""
|
||||||
|
>>> print 1+1
|
||||||
|
2
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ipdoctest
|
||||||
|
@doctest_refactor_print
|
||||||
|
def ipdt_flush():
|
||||||
|
"""
|
||||||
|
In [20]: print 1
|
||||||
|
1
|
||||||
|
|
||||||
|
In [26]: for i in range(4):
|
||||||
|
....: print i
|
||||||
|
....:
|
||||||
|
....:
|
||||||
|
0
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
|
||||||
|
In [27]: 3+4
|
||||||
|
Out[27]: 7
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ipdoctest
|
||||||
|
@doctest_refactor_print
|
||||||
|
def ipdt_indented_test():
|
||||||
|
"""
|
||||||
|
In [20]: print 1
|
||||||
|
1
|
||||||
|
|
||||||
|
In [26]: for i in range(4):
|
||||||
|
....: print i
|
||||||
|
....:
|
||||||
|
....:
|
||||||
|
0
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
|
||||||
|
In [27]: 3+4
|
||||||
|
Out[27]: 7
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Foo(object):
|
||||||
|
"""For methods, the normal decorator doesn't work.
|
||||||
|
|
||||||
|
But rewriting the docstring with ip2py does, *but only if using nose
|
||||||
|
--with-doctest*. Do we want to have that as a dependency?
|
||||||
|
"""
|
||||||
|
|
||||||
|
@ipdocstring
|
||||||
|
@doctest_refactor_print
|
||||||
|
def ipdt_method(self):
|
||||||
|
"""
|
||||||
|
In [20]: print 1
|
||||||
|
1
|
||||||
|
|
||||||
|
In [26]: for i in range(4):
|
||||||
|
....: print i
|
||||||
|
....:
|
||||||
|
....:
|
||||||
|
0
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
|
||||||
|
In [27]: 3+4
|
||||||
|
Out[27]: 7
|
||||||
|
"""
|
||||||
|
|
||||||
|
@doctest_refactor_print
|
||||||
|
def normaldt_method(self):
|
||||||
|
"""
|
||||||
|
>>> print 1+1
|
||||||
|
2
|
||||||
|
"""
|
@ -0,0 +1,136 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
"""
|
||||||
|
Tests for testing.tools
|
||||||
|
"""
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2008-2011 The yap_ipython Development Team
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the BSD License. The full license is in
|
||||||
|
# the file COPYING, distributed as part of this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython.testing import decorators as dec
|
||||||
|
from yap_ipython.testing import tools as tt
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Tests
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@dec.skip_win32
|
||||||
|
def test_full_path_posix():
|
||||||
|
spath = '/foo/bar.py'
|
||||||
|
result = tt.full_path(spath,['a.txt','b.txt'])
|
||||||
|
nt.assert_equal(result, ['/foo/a.txt', '/foo/b.txt'])
|
||||||
|
spath = '/foo'
|
||||||
|
result = tt.full_path(spath,['a.txt','b.txt'])
|
||||||
|
nt.assert_equal(result, ['/a.txt', '/b.txt'])
|
||||||
|
result = tt.full_path(spath,'a.txt')
|
||||||
|
nt.assert_equal(result, ['/a.txt'])
|
||||||
|
|
||||||
|
|
||||||
|
@dec.skip_if_not_win32
|
||||||
|
def test_full_path_win32():
|
||||||
|
spath = 'c:\\foo\\bar.py'
|
||||||
|
result = tt.full_path(spath,['a.txt','b.txt'])
|
||||||
|
nt.assert_equal(result, ['c:\\foo\\a.txt', 'c:\\foo\\b.txt'])
|
||||||
|
spath = 'c:\\foo'
|
||||||
|
result = tt.full_path(spath,['a.txt','b.txt'])
|
||||||
|
nt.assert_equal(result, ['c:\\a.txt', 'c:\\b.txt'])
|
||||||
|
result = tt.full_path(spath,'a.txt')
|
||||||
|
nt.assert_equal(result, ['c:\\a.txt'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_parser():
|
||||||
|
err = ("FAILED (errors=1)", 1, 0)
|
||||||
|
fail = ("FAILED (failures=1)", 0, 1)
|
||||||
|
both = ("FAILED (errors=1, failures=1)", 1, 1)
|
||||||
|
for txt, nerr, nfail in [err, fail, both]:
|
||||||
|
nerr1, nfail1 = tt.parse_test_output(txt)
|
||||||
|
nt.assert_equal(nerr, nerr1)
|
||||||
|
nt.assert_equal(nfail, nfail1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_temp_pyfile():
|
||||||
|
src = 'pass\n'
|
||||||
|
fname, fh = tt.temp_pyfile(src)
|
||||||
|
assert os.path.isfile(fname)
|
||||||
|
fh.close()
|
||||||
|
with open(fname) as fh2:
|
||||||
|
src2 = fh2.read()
|
||||||
|
nt.assert_equal(src2, src)
|
||||||
|
|
||||||
|
class TestAssertPrints(unittest.TestCase):
|
||||||
|
def test_passing(self):
|
||||||
|
with tt.AssertPrints("abc"):
|
||||||
|
print("abcd")
|
||||||
|
print("def")
|
||||||
|
print(b"ghi")
|
||||||
|
|
||||||
|
def test_failing(self):
|
||||||
|
def func():
|
||||||
|
with tt.AssertPrints("abc"):
|
||||||
|
print("acd")
|
||||||
|
print("def")
|
||||||
|
print(b"ghi")
|
||||||
|
|
||||||
|
self.assertRaises(AssertionError, func)
|
||||||
|
|
||||||
|
|
||||||
|
class Test_ipexec_validate(unittest.TestCase, tt.TempFileMixin):
|
||||||
|
def test_main_path(self):
|
||||||
|
"""Test with only stdout results.
|
||||||
|
"""
|
||||||
|
self.mktmp("print('A')\n"
|
||||||
|
"print('B')\n"
|
||||||
|
)
|
||||||
|
out = "A\nB"
|
||||||
|
tt.ipexec_validate(self.fname, out)
|
||||||
|
|
||||||
|
def test_main_path2(self):
|
||||||
|
"""Test with only stdout results, expecting windows line endings.
|
||||||
|
"""
|
||||||
|
self.mktmp("print('A')\n"
|
||||||
|
"print('B')\n"
|
||||||
|
)
|
||||||
|
out = "A\r\nB"
|
||||||
|
tt.ipexec_validate(self.fname, out)
|
||||||
|
|
||||||
|
def test_exception_path(self):
|
||||||
|
"""Test exception path in exception_validate.
|
||||||
|
"""
|
||||||
|
self.mktmp("import sys\n"
|
||||||
|
"print('A')\n"
|
||||||
|
"print('B')\n"
|
||||||
|
"print('C', file=sys.stderr)\n"
|
||||||
|
"print('D', file=sys.stderr)\n"
|
||||||
|
)
|
||||||
|
out = "A\nB"
|
||||||
|
tt.ipexec_validate(self.fname, expected_out=out, expected_err="C\nD")
|
||||||
|
|
||||||
|
def test_exception_path2(self):
|
||||||
|
"""Test exception path in exception_validate, expecting windows line endings.
|
||||||
|
"""
|
||||||
|
self.mktmp("import sys\n"
|
||||||
|
"print('A')\n"
|
||||||
|
"print('B')\n"
|
||||||
|
"print('C', file=sys.stderr)\n"
|
||||||
|
"print('D', file=sys.stderr)\n"
|
||||||
|
)
|
||||||
|
out = "A\r\nB"
|
||||||
|
tt.ipexec_validate(self.fname, expected_out=out, expected_err="C\r\nD")
|
||||||
|
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
# tear down correctly the mixin,
|
||||||
|
# unittest.TestCase.tearDown does nothing
|
||||||
|
tt.TempFileMixin.tearDown(self)
|
@ -0,0 +1,159 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
"""Tests for yap_ipython.utils.capture"""
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2013 The yap_ipython Development Team
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the BSD License. The full license is in
|
||||||
|
# the file COPYING, distributed as part of this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython.utils import capture
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Globals
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
_mime_map = dict(
|
||||||
|
_repr_png_="image/png",
|
||||||
|
_repr_jpeg_="image/jpeg",
|
||||||
|
_repr_svg_="image/svg+xml",
|
||||||
|
_repr_html_="text/html",
|
||||||
|
_repr_json_="application/json",
|
||||||
|
_repr_javascript_="application/javascript",
|
||||||
|
)
|
||||||
|
|
||||||
|
basic_data = {
|
||||||
|
'image/png' : b'binarydata',
|
||||||
|
'text/html' : "<b>bold</b>",
|
||||||
|
}
|
||||||
|
basic_metadata = {
|
||||||
|
'image/png' : {
|
||||||
|
'width' : 10,
|
||||||
|
'height' : 20,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
full_data = {
|
||||||
|
'image/png' : b'binarydata',
|
||||||
|
'image/jpeg' : b'binarydata',
|
||||||
|
'image/svg+xml' : "<svg>",
|
||||||
|
'text/html' : "<b>bold</b>",
|
||||||
|
'application/javascript' : "alert();",
|
||||||
|
'application/json' : "{}",
|
||||||
|
}
|
||||||
|
full_metadata = {
|
||||||
|
'image/png' : {"png" : "exists"},
|
||||||
|
'image/jpeg' : {"jpeg" : "exists"},
|
||||||
|
'image/svg+xml' : {"svg" : "exists"},
|
||||||
|
'text/html' : {"html" : "exists"},
|
||||||
|
'application/javascript' : {"js" : "exists"},
|
||||||
|
'application/json' : {"json" : "exists"},
|
||||||
|
}
|
||||||
|
|
||||||
|
hello_stdout = "hello, stdout"
|
||||||
|
hello_stderr = "hello, stderr"
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Test Functions
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_rich_output_empty():
|
||||||
|
"""RichOutput with no args"""
|
||||||
|
rich = capture.RichOutput()
|
||||||
|
for method, mime in _mime_map.items():
|
||||||
|
yield nt.assert_equal, getattr(rich, method)(), None
|
||||||
|
|
||||||
|
def test_rich_output():
|
||||||
|
"""test RichOutput basics"""
|
||||||
|
data = basic_data
|
||||||
|
metadata = basic_metadata
|
||||||
|
rich = capture.RichOutput(data=data, metadata=metadata)
|
||||||
|
yield nt.assert_equal, rich._repr_html_(), data['text/html']
|
||||||
|
yield nt.assert_equal, rich._repr_png_(), (data['image/png'], metadata['image/png'])
|
||||||
|
yield nt.assert_equal, rich._repr_latex_(), None
|
||||||
|
yield nt.assert_equal, rich._repr_javascript_(), None
|
||||||
|
yield nt.assert_equal, rich._repr_svg_(), None
|
||||||
|
|
||||||
|
def test_rich_output_no_metadata():
|
||||||
|
"""test RichOutput with no metadata"""
|
||||||
|
data = full_data
|
||||||
|
rich = capture.RichOutput(data=data)
|
||||||
|
for method, mime in _mime_map.items():
|
||||||
|
yield nt.assert_equal, getattr(rich, method)(), data[mime]
|
||||||
|
|
||||||
|
def test_rich_output_metadata():
|
||||||
|
"""test RichOutput with metadata"""
|
||||||
|
data = full_data
|
||||||
|
metadata = full_metadata
|
||||||
|
rich = capture.RichOutput(data=data, metadata=metadata)
|
||||||
|
for method, mime in _mime_map.items():
|
||||||
|
yield nt.assert_equal, getattr(rich, method)(), (data[mime], metadata[mime])
|
||||||
|
|
||||||
|
def test_rich_output_display():
|
||||||
|
"""test RichOutput.display
|
||||||
|
|
||||||
|
This is a bit circular, because we are actually using the capture code we are testing
|
||||||
|
to test itself.
|
||||||
|
"""
|
||||||
|
data = full_data
|
||||||
|
rich = capture.RichOutput(data=data)
|
||||||
|
with capture.capture_output() as cap:
|
||||||
|
rich.display()
|
||||||
|
yield nt.assert_equal, len(cap.outputs), 1
|
||||||
|
rich2 = cap.outputs[0]
|
||||||
|
yield nt.assert_equal, rich2.data, rich.data
|
||||||
|
yield nt.assert_equal, rich2.metadata, rich.metadata
|
||||||
|
|
||||||
|
def test_capture_output():
|
||||||
|
"""capture_output works"""
|
||||||
|
rich = capture.RichOutput(data=full_data)
|
||||||
|
with capture.capture_output() as cap:
|
||||||
|
print(hello_stdout, end="")
|
||||||
|
print(hello_stderr, end="", file=sys.stderr)
|
||||||
|
rich.display()
|
||||||
|
yield nt.assert_equal, hello_stdout, cap.stdout
|
||||||
|
yield nt.assert_equal, hello_stderr, cap.stderr
|
||||||
|
|
||||||
|
def test_capture_output_no_stdout():
|
||||||
|
"""test capture_output(stdout=False)"""
|
||||||
|
rich = capture.RichOutput(data=full_data)
|
||||||
|
with capture.capture_output(stdout=False) as cap:
|
||||||
|
print(hello_stdout, end="")
|
||||||
|
print(hello_stderr, end="", file=sys.stderr)
|
||||||
|
rich.display()
|
||||||
|
yield nt.assert_equal, "", cap.stdout
|
||||||
|
yield nt.assert_equal, hello_stderr, cap.stderr
|
||||||
|
yield nt.assert_equal, len(cap.outputs), 1
|
||||||
|
|
||||||
|
def test_capture_output_no_stderr():
|
||||||
|
"""test capture_output(stderr=False)"""
|
||||||
|
rich = capture.RichOutput(data=full_data)
|
||||||
|
# add nested capture_output so stderr doesn't make it to nose output
|
||||||
|
with capture.capture_output(), capture.capture_output(stderr=False) as cap:
|
||||||
|
print(hello_stdout, end="")
|
||||||
|
print(hello_stderr, end="", file=sys.stderr)
|
||||||
|
rich.display()
|
||||||
|
yield nt.assert_equal, hello_stdout, cap.stdout
|
||||||
|
yield nt.assert_equal, "", cap.stderr
|
||||||
|
yield nt.assert_equal, len(cap.outputs), 1
|
||||||
|
|
||||||
|
def test_capture_output_no_display():
|
||||||
|
"""test capture_output(display=False)"""
|
||||||
|
rich = capture.RichOutput(data=full_data)
|
||||||
|
with capture.capture_output(display=False) as cap:
|
||||||
|
print(hello_stdout, end="")
|
||||||
|
print(hello_stderr, end="", file=sys.stderr)
|
||||||
|
rich.display()
|
||||||
|
yield nt.assert_equal, hello_stdout, cap.stdout
|
||||||
|
yield nt.assert_equal, hello_stderr, cap.stderr
|
||||||
|
yield nt.assert_equal, cap.outputs, []
|
@ -0,0 +1,10 @@
|
|||||||
|
from yap_ipython.utils import decorators
|
||||||
|
|
||||||
|
def test_flag_calls():
|
||||||
|
@decorators.flag_calls
|
||||||
|
def f():
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert not f.called
|
||||||
|
f()
|
||||||
|
assert f.called
|
@ -0,0 +1,58 @@
|
|||||||
|
import nose.tools as nt
|
||||||
|
from yap_ipython.utils.dir2 import dir2
|
||||||
|
|
||||||
|
|
||||||
|
class Base(object):
|
||||||
|
x = 1
|
||||||
|
z = 23
|
||||||
|
|
||||||
|
|
||||||
|
def test_base():
|
||||||
|
res = dir2(Base())
|
||||||
|
assert ('x' in res)
|
||||||
|
assert ('z' in res)
|
||||||
|
assert ('y' not in res)
|
||||||
|
assert ('__class__' in res)
|
||||||
|
nt.assert_equal(res.count('x'), 1)
|
||||||
|
nt.assert_equal(res.count('__class__'), 1)
|
||||||
|
|
||||||
|
def test_SubClass():
|
||||||
|
|
||||||
|
class SubClass(Base):
|
||||||
|
y = 2
|
||||||
|
|
||||||
|
res = dir2(SubClass())
|
||||||
|
assert ('y' in res)
|
||||||
|
nt.assert_equal(res.count('y'), 1)
|
||||||
|
nt.assert_equal(res.count('x'), 1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_SubClass_with_trait_names_attr():
|
||||||
|
# usecase: trait_names is used in a class describing psychological classification
|
||||||
|
|
||||||
|
class SubClass(Base):
|
||||||
|
y = 2
|
||||||
|
trait_names = 44
|
||||||
|
|
||||||
|
res = dir2(SubClass())
|
||||||
|
assert('trait_names' in res)
|
||||||
|
|
||||||
|
|
||||||
|
def test_misbehaving_object_without_trait_names():
|
||||||
|
# dir2 shouldn't raise even when objects are dumb and raise
|
||||||
|
# something other than AttribteErrors on bad getattr.
|
||||||
|
|
||||||
|
class MisbehavingGetattr(object):
|
||||||
|
def __getattr__(self):
|
||||||
|
raise KeyError("I should be caught")
|
||||||
|
|
||||||
|
def some_method(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class SillierWithDir(MisbehavingGetattr):
|
||||||
|
def __dir__(self):
|
||||||
|
return ['some_method']
|
||||||
|
|
||||||
|
for bad_klass in (MisbehavingGetattr, SillierWithDir):
|
||||||
|
res = dir2(bad_klass())
|
||||||
|
assert('some_method' in res)
|
@ -0,0 +1,20 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
|
||||||
|
def test_import_coloransi():
|
||||||
|
from yap_ipython.utils import coloransi
|
||||||
|
|
||||||
|
def test_import_generics():
|
||||||
|
from yap_ipython.utils import generics
|
||||||
|
|
||||||
|
def test_import_ipstruct():
|
||||||
|
from yap_ipython.utils import ipstruct
|
||||||
|
|
||||||
|
def test_import_PyColorize():
|
||||||
|
from yap_ipython.utils import PyColorize
|
||||||
|
|
||||||
|
def test_import_strdispatch():
|
||||||
|
from yap_ipython.utils import strdispatch
|
||||||
|
|
||||||
|
def test_import_wildcard():
|
||||||
|
from yap_ipython.utils import wildcard
|
||||||
|
|
@ -0,0 +1,39 @@
|
|||||||
|
"""Tests for yap_ipython.utils.importstring."""
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2013 The yap_ipython Development Team
|
||||||
|
#
|
||||||
|
# Distributed under the terms of the BSD License. The full license is in
|
||||||
|
# the file COPYING, distributed as part of this software.
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Imports
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython.utils.importstring import import_item
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Tests
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_import_plain():
|
||||||
|
"Test simple imports"
|
||||||
|
import os
|
||||||
|
os2 = import_item('os')
|
||||||
|
nt.assert_true(os is os2)
|
||||||
|
|
||||||
|
|
||||||
|
def test_import_nested():
|
||||||
|
"Test nested imports from the stdlib"
|
||||||
|
from os import path
|
||||||
|
path2 = import_item('os.path')
|
||||||
|
nt.assert_true(path is path2)
|
||||||
|
|
||||||
|
|
||||||
|
def test_import_raises():
|
||||||
|
"Test that failing imports raise the right exception"
|
||||||
|
nt.assert_raises(ImportError, import_item, 'yap_ipython.foobar')
|
||||||
|
|
@ -0,0 +1,94 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
"""Tests for io.py"""
|
||||||
|
|
||||||
|
# Copyright (c) yap_ipython Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
|
||||||
|
import io as stdlib_io
|
||||||
|
import os.path
|
||||||
|
import stat
|
||||||
|
import sys
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import nose.tools as nt
|
||||||
|
|
||||||
|
from yap_ipython.testing.decorators import skipif, skip_win32
|
||||||
|
from yap_ipython.utils.io import IOStream, Tee, capture_output
|
||||||
|
from yap_ipython.utils.py3compat import doctest_refactor_print
|
||||||
|
from yap_ipython.utils.tempdir import TemporaryDirectory
|
||||||
|
|
||||||
|
|
||||||
|
def test_tee_simple():
|
||||||
|
"Very simple check with stdout only"
|
||||||
|
chan = StringIO()
|
||||||
|
text = 'Hello'
|
||||||
|
tee = Tee(chan, channel='stdout')
|
||||||
|
print(text, file=chan)
|
||||||
|
nt.assert_equal(chan.getvalue(), text+"\n")
|
||||||
|
|
||||||
|
|
||||||
|
class TeeTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def tchan(self, channel, check='close'):
|
||||||
|
trap = StringIO()
|
||||||
|
chan = StringIO()
|
||||||
|
text = 'Hello'
|
||||||
|
|
||||||
|
std_ori = getattr(sys, channel)
|
||||||
|
setattr(sys, channel, trap)
|
||||||
|
|
||||||
|
tee = Tee(chan, channel=channel)
|
||||||
|
print(text, end='', file=chan)
|
||||||
|
setattr(sys, channel, std_ori)
|
||||||
|
trap_val = trap.getvalue()
|
||||||
|
nt.assert_equal(chan.getvalue(), text)
|
||||||
|
if check=='close':
|
||||||
|
tee.close()
|
||||||
|
else:
|
||||||
|
del tee
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
for chan in ['stdout', 'stderr']:
|
||||||
|
for check in ['close', 'del']:
|
||||||
|
self.tchan(chan, check)
|
||||||
|
|
||||||
|
def test_io_init():
|
||||||
|
"""Test that io.stdin/out/err exist at startup"""
|
||||||
|
for name in ('stdin', 'stdout', 'stderr'):
|
||||||
|
cmd = doctest_refactor_print("from yap_ipython.utils import io;print io.%s.__class__"%name)
|
||||||
|
p = Popen([sys.executable, '-c', cmd],
|
||||||
|
stdout=PIPE)
|
||||||
|
p.wait()
|
||||||
|
classname = p.stdout.read().strip().decode('ascii')
|
||||||
|
# __class__ is a reference to the class object in Python 3, so we can't
|
||||||
|
# just test for string equality.
|
||||||
|
assert 'yap_ipython.utils.io.IOStream' in classname, classname
|
||||||
|
|
||||||
|
def test_IOStream_init():
|
||||||
|
"""IOStream initializes from a file-like object missing attributes. """
|
||||||
|
# Cause a failure from getattr and dir(). (Issue #6386)
|
||||||
|
class BadStringIO(StringIO):
|
||||||
|
def __dir__(self):
|
||||||
|
attrs = super(StringIO, self).__dir__()
|
||||||
|
attrs.append('name')
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
iostream = IOStream(BadStringIO())
|
||||||
|
iostream.write('hi, bad iostream\n')
|
||||||
|
assert not hasattr(iostream, 'name')
|
||||||
|
|
||||||
|
def test_capture_output():
|
||||||
|
"""capture_output() context works"""
|
||||||
|
|
||||||
|
with capture_output() as io:
|
||||||
|
print('hi, stdout')
|
||||||
|
print('hi, stderr', file=sys.stderr)
|
||||||
|
|
||||||
|
nt.assert_equal(io.stdout, 'hi, stdout\n')
|
||||||
|
nt.assert_equal(io.stderr, 'hi, stderr\n')
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user