Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid importing holoviews when nbsmoke is imported. #36

Merged
merged 3 commits into from
Dec 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions nbsmoke/lint/magics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@
from . import holoviews_support
from . import builtins_support

# as noted in that file, shouldn't be there but should just be replaced iwth something better anyway
_Unavailable = holoviews_support._Unavailable

# TODO: still nede to investigate the following line magics (and what
# does ipython convert do to them?):
#
Expand Down Expand Up @@ -170,11 +167,10 @@ def _get_parser(line): # yuck
return parsers[None](line) # default/no magic

def _call_a_handler(handlers, magic):
handler = handlers[magic.name]
if isinstance(handler, _Unavailable):
# this is a bit rubbish. and how to reconstruct original error?
raise Exception("nbsmoke can't process the following '%s' magic without additional dependencies:\n %s\n. Error was:\n %s"%(magic.name, magic.line, handler.e.msg))
return handler(magic)
try:
return handlers[magic.name](magic)
except ImportError as e:
raise ImportError("nbsmoke can't process the following '%s' magic without additional dependencies:\n %s\n. Error was:\n %s"%(magic.name, magic.line, e))

def _process_magics(magic, other_magic_handlers, ignored_magics, blacklisted_magics, simple_magics=None):
if simple_magics is None:
Expand Down
177 changes: 88 additions & 89 deletions nbsmoke/lint/magics/holoviews_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,103 +6,102 @@

from itertools import groupby

# placeholder for unavailable imports - should do something better (also, not specific to hv)
class _Unavailable:
def __init__(self,e):
self.e = e
IGNORED_LINE_MAGICS = ['output']
IGNORED_CELL_MAGICS = ['output']

try:
from holoviews.util.parser import OptsSpec
except ImportError as e:
OptsSpec = _Unavailable(e)
import numpy # noqa: Some bad numpy/pytest interaction. Without importing numpy here (so it happens when nbsmoke is imported, i.e. at plugin load time,
# we get the traceback in https://github.com/numpy/numpy/issues/14012 (no idea if same cause)
except ImportError:
pass

IGNORED_LINE_MAGICS = ['output']
IGNORED_CELL_MAGICS = ['output']
def _make_optsspec():
from holoviews.util.parser import OptsSpec

class NbSmokeOptsSpec(OptsSpec):

@classmethod
def _hvparse(cls, line, ns={}):
"""
Parse an options specification, returning a dictionary with
path keys and {'plot':<options>, 'style':<options>} values.
"""
parses = [p for p in cls.opts_spec.scanString(line)]
if len(parses) != 1:
raise SyntaxError("Invalid specification syntax.")
else:
e = parses[0][2]
processed = line[:e]
if (processed.strip() != line.strip()):
raise SyntaxError("Failed to parse remainder of string: %r" % line[e:])

grouped_paths = cls._group_paths_without_options(cls.opts_spec.parseString(line))
things = []
for pathspecs, group in grouped_paths:

# normalization = cls.process_normalization(group)
# if normalization is not None:
# options['norm'] = normalization

if 'plot_options' in group:
plotopts = group['plot_options'][0]
opts = cls.todict(plotopts, 'brackets', ns=ns)
things+=opts

if 'style_options' in group:
styleopts = group['style_options'][0]
opts = cls.todict(styleopts, 'parens', ns=ns)
things+=opts

return things

@classmethod
def _hvtodict(cls, parseresult, mode='parens', ns={}):
"""
Helper function to return dictionary given the parse results
from a pyparsing.nestedExpr object (containing keywords).

The ns is a dynamic namespace (typically the IPython Notebook
namespace) used to update the class-level namespace.
"""
grouped = []
things = []
tokens = cls.collect_tokens(parseresult, mode)
# Group tokens without '=' and append to last token containing '='
for group in groupby(tokens, lambda el: '=' in el):
(val, items) = group
if val is True:
grouped += list(items)
if val is False:
elements =list(items)
# Assume anything before ) or } can be joined with commas
# (e.g tuples with spaces in them)
joiner=',' if any(((')' in el) or ('}' in el))
for el in elements) else ''
grouped[-1] += joiner + joiner.join(elements)

for keyword in grouped:
# Tuple ('a', 3) becomes (,'a',3) and '(,' is never valid
# Same for some of the other joining errors corrected here
for (fst,snd) in [('(,', '('), ('{,', '{'), ('=,','='),
(',:',':'), (':,', ':'), (',,', ','),
(',.', '.')]:
keyword = keyword.replace(fst, snd)

things.append('dict(%s)' % keyword)

return things

return NbSmokeOptsSpec


def opts_handler(magic):
"""Given an opts magic, return line of python suitable for pyflakes."""
string_of_magic_args = magic.python
return " ; ".join(OptsSpec.parse(string_of_magic_args)) + " # noqa: here to use names for original %s magic: %s"%(magic.__class__.__name__, magic.python)

NbSmokeOptsSpec = _make_optsspec()
string_of_magic_args = magic.python
return " ; ".join(NbSmokeOptsSpec.parse(string_of_magic_args)) + " # noqa: here to use names for original %s magic: %s"%(magic.__class__.__name__, magic.python)

if isinstance(OptsSpec, _Unavailable):
opts_handler = OptsSpec # noqa: opts_handler unusable

cell_magic_handlers = {'opts': opts_handler}
line_magic_handlers = {'opts': opts_handler}


def _hvparse(cls, line, ns={}):
"""
Parse an options specification, returning a dictionary with
path keys and {'plot':<options>, 'style':<options>} values.
"""
parses = [p for p in cls.opts_spec.scanString(line)]
if len(parses) != 1:
raise SyntaxError("Invalid specification syntax.")
else:
e = parses[0][2]
processed = line[:e]
if (processed.strip() != line.strip()):
raise SyntaxError("Failed to parse remainder of string: %r" % line[e:])

grouped_paths = cls._group_paths_without_options(cls.opts_spec.parseString(line))
things = []
for pathspecs, group in grouped_paths:

# normalization = cls.process_normalization(group)
# if normalization is not None:
# options['norm'] = normalization

if 'plot_options' in group:
plotopts = group['plot_options'][0]
opts = cls.todict(plotopts, 'brackets', ns=ns)
things+=opts

if 'style_options' in group:
styleopts = group['style_options'][0]
opts = cls.todict(styleopts, 'parens', ns=ns)
things+=opts

return things


def _hvtodict(cls, parseresult, mode='parens', ns={}):
"""
Helper function to return dictionary given the parse results
from a pyparsing.nestedExpr object (containing keywords).

The ns is a dynamic namespace (typically the IPython Notebook
namespace) used to update the class-level namespace.
"""
grouped = []
things = []
tokens = cls.collect_tokens(parseresult, mode)
# Group tokens without '=' and append to last token containing '='
for group in groupby(tokens, lambda el: '=' in el):
(val, items) = group
if val is True:
grouped += list(items)
if val is False:
elements =list(items)
# Assume anything before ) or } can be joined with commas
# (e.g tuples with spaces in them)
joiner=',' if any(((')' in el) or ('}' in el))
for el in elements) else ''
grouped[-1] += joiner + joiner.join(elements)

for keyword in grouped:
# Tuple ('a', 3) becomes (,'a',3) and '(,' is never valid
# Same for some of the other joining errors corrected here
for (fst,snd) in [('(,', '('), ('{,', '{'), ('=,','='),
(',:',':'), (':,', ':'), (',,', ','),
(',.', '.')]:
keyword = keyword.replace(fst, snd)

things.append('dict(%s)' % keyword)

return things

if OptsSpec is not None:
OptsSpec.parse = classmethod(_hvparse)
OptsSpec.todict = classmethod(_hvtodict)
9 changes: 5 additions & 4 deletions tests/test_lint_magics.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,15 +428,16 @@ def test_optional_import_warn(testdir):
testdir.makefile('.ipynb', testing123=nb_optional_dep)
###
# what a hack
def not_clever_magics_handler(magic):
raise ImportError("Amazing dependency is missing")
import nbsmoke.lint.magics as M
from collections import namedtuple
M.other_line_magic_handlers['clever_magic'] = M._Unavailable(namedtuple("SomeError", "msg")("A terrible error."))
M.other_line_magic_handlers['clever_magic'] = not_clever_magics_handler
###
result = testdir.runpytest(*lint_args)
assert result.ret == 1
result.stdout.re_match_lines_random(
[".*Exception: nbsmoke can't process the following 'clever_magic' magic without additional dependencies.*",
".*A terrible error.*"])
[".*ImportError: nbsmoke can't process the following 'clever_magic' magic without additional dependencies.*",
".*Amazing dependency is missing.*"])


def test_lint_line_magics_indent(testdir):
Expand Down