Source code for holoviews.ipython.magics

import sys
import time

from IPython.core.magic import Magics, line_cell_magic, line_magic, magics_class
from IPython.display import HTML, display

from ..core.options import Options, Store, StoreOptions, options_policy
from ..core.pprint import InfoPrinter
from ..operation import Compositor

#========#
# Magics #
#========#


try:
    import pyparsing
except ImportError:
    pyparsing = None
else:
    from holoviews.util.parser import CompositorSpec, OptsSpec


# Set to True to automatically run notebooks.
STORE_HISTORY = False

from IPython.core import page

InfoPrinter.store = Store


[docs]@magics_class class OutputMagic(Magics): @classmethod def info(cls, obj): disabled = Store.output_settings._disable_info_output if Store.output_settings.options['info'] and not disabled: page.page(InfoPrinter.info(obj, ansi=True))
[docs] @classmethod def pprint(cls): """ Pretty print the current element options """ current, count = '', 0 for k,v in Store.output_settings.options.items(): keyword = f'{k}={v!r}' if len(current) + len(keyword) > 80: print(('%output' if count==0 else ' ') + current) count += 1 current = keyword else: current += ' '+ keyword else: # noqa: PLW0120 print(('%output' if count==0 else ' ') + current)
@classmethod def option_completer(cls, k,v): raw_line = v.text_until_cursor line = raw_line.replace('%output','') # Find the last element class mentioned completion_key = None tokens = [t for els in reversed(line.split('=')) for t in els.split()] for token in tokens: if token.strip() in Store.output_settings.allowed: completion_key = token.strip() break values = [val for val in Store.output_settings.allowed.get(completion_key, []) if val not in Store.output_settings.hidden.get(completion_key, [])] vreprs = [repr(el) for el in values if not isinstance(el, tuple)] return vreprs + [el+'=' for el in Store.output_settings.allowed.keys()] @line_cell_magic def output(self, line, cell=None): if line == '': self.pprint() print("\nFor help with the %output magic, call %output?") return def cell_runner(cell,renderer): self.shell.run_cell(cell, store_history=STORE_HISTORY) def warnfn(msg): display(HTML(f"<b>Warning:</b> {msg}")) if line: help_prompt = "For help with the %output magic, call %output?\n" else: help_prompt = "For help with the %%output magic, call %%output?\n" Store.output_settings.output(line, cell, cell_runner=cell_runner, help_prompt=help_prompt, warnfn=warnfn)
[docs]@magics_class class CompositorMagic(Magics): """ Magic allowing easy definition of compositor operations. Consult %compositor? for more information. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) lines = ['The %compositor line magic is used to define compositors.'] self.compositor.__func__.__doc__ = '\n'.join(lines + [CompositorSpec.__doc__]) @line_magic def compositor(self, line): if line.strip(): for definition in CompositorSpec.parse(line.strip(), ns=self.shell.user_ns): group = {group:Options() for group in Options._option_groups} type_name = definition.output_type.__name__ Store.options()[type_name + '.' + definition.group] = group Compositor.register(definition) else: print("For help with the %compositor magic, call %compositor?\n") @classmethod def option_completer(cls, k,v): line = v.text_until_cursor operation_openers = [op.__name__+'(' for op in Compositor.operations] modes = ['data', 'display'] op_declared = any(op in line for op in operation_openers) mode_declared = any(mode in line for mode in modes) if not mode_declared: return modes elif not op_declared: return operation_openers if op_declared and ')' not in line: return [')'] elif line.split(')')[1].strip() and ('[' not in line): return ['['] elif '[' in line: return [']']
[docs]class OptsCompleter: """ Implements the TAB-completion for the %%opts magic. """ _completions = {} # Contains valid plot and style keywords per Element
[docs] @classmethod def setup_completer(cls): "Get the dictionary of valid completions" try: for element in Store.options().keys(): options = Store.options()['.'.join(element)] plotkws = options['plot'].allowed_keywords stylekws = options['style'].allowed_keywords dotted = '.'.join(element) cls._completions[dotted] = (plotkws, stylekws if stylekws else []) except KeyError: pass return cls._completions
[docs] @classmethod def dotted_completion(cls, line, sorted_keys, compositor_defs): """ Supply the appropriate key in Store.options and supply suggestions for further completion. """ completion_key, suggestions = None, [] tokens = [t for t in reversed(line.replace('.', ' ').split())] for i, token in enumerate(tokens): key_checks =[] if i >= 0: # Undotted key key_checks.append(tokens[i]) if i >= 1: # Single dotted key key_checks.append('.'.join([key_checks[-1], tokens[i-1]])) if i >= 2: # Double dotted key key_checks.append('.'.join([key_checks[-1], tokens[i-2]])) # Check for longest potential dotted match first for key in reversed(key_checks): if key in sorted_keys: completion_key = key depth = completion_key.count('.') suggestions = [k.split('.')[depth+1] for k in sorted_keys if k.startswith(completion_key+'.')] return completion_key, suggestions # Attempting to match compositor definitions if token in compositor_defs: completion_key = compositor_defs[token] break return completion_key, suggestions
@classmethod def _inside_delims(cls, line, opener, closer): return (line.count(opener) - line.count(closer)) % 2
[docs] @classmethod def option_completer(cls, k,v): "Tab completion hook for the %%opts cell magic." line = v.text_until_cursor completions = cls.setup_completer() compositor_defs = {el.group:el.output_type.__name__ for el in Compositor.definitions if el.group} return cls.line_completer(line, completions, compositor_defs)
@classmethod def line_completer(cls, line, completions, compositor_defs): sorted_keys = sorted(completions.keys()) type_keys = [key for key in sorted_keys if ('.' not in key)] completion_key, suggestions = cls.dotted_completion(line, sorted_keys, compositor_defs) verbose_openers = ['style(', 'plot[', 'norm{'] if suggestions and line.endswith('.'): return [f"{completion_key}.{el}" for el in suggestions] elif not completion_key: return type_keys + list(compositor_defs.keys()) + verbose_openers if cls._inside_delims(line,'[', ']'): return [kw+'=' for kw in completions[completion_key][0]] if cls._inside_delims(line, '{', '}'): return ['+axiswise', '+framewise'] style_completions = [kw+'=' for kw in completions[completion_key][1]] if cls._inside_delims(line, '(', ')'): return style_completions return type_keys + list(compositor_defs.keys()) + verbose_openers
[docs]@magics_class class OptsMagic(Magics): """ Magic for easy customising of normalization, plot and style options. Consult %%opts? for more information. """ error_message = None # If not None, the error message that will be displayed opts_spec = None # Next id to propagate, binding displayed object together. strict = False
[docs] @classmethod def process_element(cls, obj): """ To be called by the display hook which supplies the element to be displayed. Any customisation of the object can then occur before final display. If there is any error, a HTML message may be returned. If None is returned, display will proceed as normal. """ if cls.error_message: if cls.strict: return cls.error_message else: sys.stderr.write(cls.error_message) if cls.opts_spec is not None: StoreOptions.set_options(obj, cls.opts_spec) cls.opts_spec = None return None
@classmethod def register_custom_spec(cls, spec): spec, _ = StoreOptions.expand_compositor_keys(spec) errmsg = StoreOptions.validation_error_message(spec) if errmsg: cls.error_message = errmsg cls.opts_spec = spec @classmethod def _partition_lines(cls, line, cell): """ Check the code for additional use of %%opts. Enables multi-line use of %%opts in a single call to the magic. """ if cell is None: return (line, cell) specs, code = [line], [] for line in cell.splitlines(): if line.strip().startswith('%%opts'): specs.append(line.strip()[7:]) else: code.append(line) return ' '.join(specs), '\n'.join(code)
[docs] @line_cell_magic def opts(self, line='', cell=None): """ The opts line/cell magic with tab-completion. %%opts [ [path] [normalization] [plotting options] [style options]]+ path: A dotted type.group.label specification (e.g. Image.Grayscale.Photo) normalization: List of normalization options delimited by braces. One of | -axiswise | -framewise | +axiswise | +framewise | E.g. { +axiswise +framewise } plotting options: List of plotting option keywords delimited by square brackets. E.g. [show_title=False] style options: List of style option keywords delimited by parentheses. E.g. (lw=10 marker='+') Note that commas between keywords are optional (not recommended) and that keywords must end in '=' without a separating space. More information may be found in the class docstring of util.parser.OptsSpec. """ line, cell = self._partition_lines(line, cell) try: spec = OptsSpec.parse(line, ns=self.shell.user_ns) except SyntaxError: display(HTML("<b>Invalid syntax</b>: Consult <tt>%%opts?</tt> for more information.")) return # Make sure the specified elements exist in the loaded backends available_elements = set() for backend in Store.loaded_backends(): available_elements |= set(Store.options(backend).children) spec_elements = {k.split('.')[0] for k in spec.keys()} unknown_elements = spec_elements - available_elements if unknown_elements: msg = ("<b>WARNING:</b> Unknown elements {unknown} not registered " "with any of the loaded backends.") display(HTML(msg.format(unknown=', '.join(unknown_elements)))) if cell: self.register_custom_spec(spec) # Process_element is invoked when the cell is run. self.shell.run_cell(cell, store_history=STORE_HISTORY) else: errmsg = StoreOptions.validation_error_message(spec) if errmsg: OptsMagic.error_message = None sys.stderr.write(errmsg) if self.strict: display(HTML('Options specification will not be applied.')) return with options_policy(skip_invalid=True, warn_on_skip=False): StoreOptions.apply_customizations(spec, Store.options()) OptsMagic.error_message = None
[docs]@magics_class class TimerMagic(Magics): """ A line magic for measuring the execution time of multiple cells. After you start/reset the timer with '%timer start' you may view elapsed time with any subsequent calls to %timer. """ start_time = None @staticmethod def elapsed_time(): seconds = time.time() - TimerMagic.start_time minutes = seconds // 60 hours = minutes // 60 return "Timer elapsed: %02d:%02d:%02d" % (hours, minutes % 60, seconds % 60) @classmethod def option_completer(cls, k,v): return ['start']
[docs] @line_magic def timer(self, line=''): """ Timer magic to print initial date/time information and subsequent elapsed time intervals. To start the timer, run: %timer start This will print the start date and time. Subsequent calls to %timer will print the elapsed time relative to the time when %timer start was called. Subsequent calls to %timer start may also be used to reset the timer. """ if line.strip() not in ['', 'start']: print("Invalid argument to %timer. For more information consult %timer?") return elif line.strip() == 'start': TimerMagic.start_time = time.time() timestamp = time.strftime("%Y/%m/%d %H:%M:%S") print(f"Timer start: {timestamp}") return elif self.start_time is None: print("Please start timer with %timer start. For more information consult %timer?") else: print(self.elapsed_time())
def load_magics(ip): ip.register_magics(TimerMagic) ip.register_magics(OutputMagic) docstring = Store.output_settings._generate_docstring() OutputMagic.output.__doc__ = docstring if pyparsing is None: print("%opts magic unavailable (pyparsing cannot be imported)") else: ip.register_magics(OptsMagic) if pyparsing is None: print("%compositor magic unavailable (pyparsing cannot be imported)") else: ip.register_magics(CompositorMagic) # Configuring tab completion ip.set_hook('complete_command', TimerMagic.option_completer, str_key = '%timer') ip.set_hook('complete_command', CompositorMagic.option_completer, str_key = '%compositor') ip.set_hook('complete_command', OutputMagic.option_completer, str_key = '%output') ip.set_hook('complete_command', OutputMagic.option_completer, str_key = '%%output') OptsCompleter.setup_completer() ip.set_hook('complete_command', OptsCompleter.option_completer, str_key = '%%opts') ip.set_hook('complete_command', OptsCompleter.option_completer, str_key = '%opts')