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