diff --git a/.gitignore b/.gitignore index e227fac..86345c8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ _build docs/man/*.gz docs/source/api/generated docs/source/config/options +docs/source/interactive/magics-generated.txt docs/gh-pages IPython/html/notebook/static/mathjax IPython/html/static/style/*.map @@ -16,3 +17,6 @@ __pycache__ .ipynb_checkpoints .tox .DS_Store +\#*# +.#* +.coverage diff --git a/.travis.yml b/.travis.yml index 7648837..5455b1f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,14 +11,15 @@ before_install: # workaround for https://github.com/travis-ci/travis-cookbooks/issues/155 - sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm # Pierre Carrier's PPA for PhantomJS and CasperJS - - time sudo add-apt-repository -y ppa:pcarrier/ppa - - time sudo apt-get update - - time sudo apt-get install pandoc casperjs libzmq3-dev - # pin tornado < 4 for js tests while phantom is on super old webkit - - if [[ $GROUP == 'js' ]]; then pip install 'tornado<4'; fi - - time pip install -f https://nipy.bic.berkeley.edu/wheelhouse/travis jinja2 sphinx pygments tornado requests mock pyzmq jsonschema jsonpointer mistune + - sudo add-apt-repository -y ppa:pcarrier/ppa + # Needed to get recent version of pandoc in ubntu 12.04 + - sudo add-apt-repository -y ppa:marutter/c2d4u + - sudo apt-get update + - sudo apt-get install pandoc casperjs libzmq3-dev + - git clone --quiet --depth 1 https://github.com/minrk/travis-wheels travis-wheels + - 'if [[ $GROUP == js* ]]; then python -m IPython.external.mathjax; fi' install: - - time python setup.py install -q + - pip install -f travis-wheels/wheelhouse file://$PWD#egg=ipython[all] script: - cd /tmp && iptest $GROUP diff --git a/Dockerfile b/Dockerfile index d511df1..8765647 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,13 +5,11 @@ FROM ubuntu:14.04 MAINTAINER IPython Project -# Make sure apt is up to date -RUN apt-get update -RUN apt-get upgrade -y +ENV DEBIAN_FRONTEND noninteractive # Not essential, but wise to set the lang # Note: Users with other languages should set this in their derivative image -RUN apt-get install -y language-pack-en +RUN apt-get update && apt-get install -y language-pack-en ENV LANGUAGE en_US.UTF-8 ENV LANG en_US.UTF-8 ENV LC_ALL en_US.UTF-8 @@ -20,14 +18,32 @@ RUN locale-gen en_US.UTF-8 RUN dpkg-reconfigure locales # Python binary dependencies, developer tools -RUN apt-get install -y -q build-essential make gcc zlib1g-dev git && \ - apt-get install -y -q python python-dev python-pip python3-dev python3-pip && \ - apt-get install -y -q libzmq3-dev sqlite3 libsqlite3-dev pandoc libcurl4-openssl-dev nodejs nodejs-legacy npm +RUN apt-get update && apt-get install -y -q \ + build-essential \ + make \ + gcc \ + zlib1g-dev \ + git \ + python \ + python-dev \ + python-pip \ + python3-dev \ + python3-pip \ + python-sphinx \ + python3-sphinx \ + libzmq3-dev \ + sqlite3 \ + libsqlite3-dev \ + pandoc \ + libcurl4-openssl-dev \ + nodejs \ + nodejs-legacy \ + npm # In order to build from source, need less -RUN npm install -g less +RUN npm install -g less@1.7.5 -RUN apt-get -y install fabric +RUN pip install invoke RUN mkdir -p /srv/ WORKDIR /srv/ @@ -37,10 +53,14 @@ RUN chmod -R +rX /srv/ipython # .[all] only works with -e, so use file://path#egg # Can't use -e because ipython2 and ipython3 will clobber each other -RUN pip2 install --upgrade file:///srv/ipython#egg=ipython[all] -RUN pip3 install --upgrade file:///srv/ipython#egg=ipython[all] +RUN pip2 install file:///srv/ipython#egg=ipython[all] +RUN pip3 install file:///srv/ipython#egg=ipython[all] # install kernels RUN python2 -m IPython kernelspec install-self --system RUN python3 -m IPython kernelspec install-self --system +WORKDIR /tmp/ + +RUN iptest2 +RUN iptest3 diff --git a/IPython/config/application.py b/IPython/config/application.py index be907ed..69a317f 100644 --- a/IPython/config/application.py +++ b/IPython/config/application.py @@ -6,6 +6,7 @@ from __future__ import print_function +import json import logging import os import re @@ -123,7 +124,16 @@ class Application(SingletonConfigurable): # A sequence of Configurable subclasses whose config=True attributes will # be exposed at the command line. - classes = List([]) + classes = [] + @property + def _help_classes(self): + """Define `App.help_classes` if CLI classes should differ from config file classes""" + return getattr(self, 'help_classes', self.classes) + + @property + def _config_classes(self): + """Define `App.config_classes` if config file classes should differ from CLI classes.""" + return getattr(self, 'config_classes', self.classes) # The version string of this application. version = Unicode(u'0.0') @@ -256,7 +266,7 @@ class Application(SingletonConfigurable): lines = [] classdict = {} - for cls in self.classes: + for cls in self._help_classes: # include all parents (up to, but excluding Configurable) in available names for c in cls.mro()[:-3]: classdict[c.__name__] = c @@ -331,7 +341,8 @@ class Application(SingletonConfigurable): self.print_options() if classes: - if self.classes: + help_classes = self._help_classes + if help_classes: print("Class parameters") print("----------------") print() @@ -339,7 +350,7 @@ class Application(SingletonConfigurable): print(p) print() - for cls in self.classes: + for cls in help_classes: cls.class_print_help() print() else: @@ -412,7 +423,7 @@ class Application(SingletonConfigurable): # it will be a dict by parent classname of classes in our list # that are descendents mro_tree = defaultdict(list) - for cls in self.classes: + for cls in self._help_classes: clsname = cls.__name__ for parent in cls.mro()[1:-3]: # exclude cls itself and Configurable,HasTraits,object @@ -491,27 +502,32 @@ class Application(SingletonConfigurable): yield each config object in turn. """ - pyloader = PyFileConfigLoader(basefilename+'.py', path=path, log=log) - jsonloader = JSONFileConfigLoader(basefilename+'.json', path=path, log=log) - config = None - for loader in [pyloader, jsonloader]: - try: - config = loader.load_config() - except ConfigFileNotFound: - pass - except Exception: - # try to get the full filename, but it will be empty in the - # unlikely event that the error raised before filefind finished - filename = loader.full_filename or basefilename - # problem while running the file - if log: - log.error("Exception while loading config file %s", - filename, exc_info=True) - else: - if log: - log.debug("Loaded config file: %s", loader.full_filename) - if config: - yield config + + if not isinstance(path, list): + path = [path] + for path in path[::-1]: + # path list is in descending priority order, so load files backwards: + pyloader = PyFileConfigLoader(basefilename+'.py', path=path, log=log) + jsonloader = JSONFileConfigLoader(basefilename+'.json', path=path, log=log) + config = None + for loader in [pyloader, jsonloader]: + try: + config = loader.load_config() + except ConfigFileNotFound: + pass + except Exception: + # try to get the full filename, but it will be empty in the + # unlikely event that the error raised before filefind finished + filename = loader.full_filename or basefilename + # problem while running the file + if log: + log.error("Exception while loading config file %s", + filename, exc_info=True) + else: + if log: + log.debug("Loaded config file: %s", loader.full_filename) + if config: + yield config raise StopIteration @@ -520,8 +536,17 @@ class Application(SingletonConfigurable): def load_config_file(self, filename, path=None): """Load config files by filename and path.""" filename, ext = os.path.splitext(filename) + loaded = [] for config in self._load_config_files(filename, path=path, log=self.log): + loaded.append(config) self.update_config(config) + if len(loaded) > 1: + collisions = loaded[0].collisions(loaded[1]) + if collisions: + self.log.warn("Collisions detected in {0}.py and {0}.json config files." + " {0}.json has higher priority: {1}".format( + filename, json.dumps(collisions, indent=2), + )) def generate_config_file(self): @@ -530,7 +555,7 @@ class Application(SingletonConfigurable): lines.append('') lines.append('c = get_config()') lines.append('') - for cls in self.classes: + for cls in self._config_classes: lines.append(cls.class_config_section()) return '\n'.join(lines) diff --git a/IPython/config/loader.py b/IPython/config/loader.py index 160739b..b731b8d 100644 --- a/IPython/config/loader.py +++ b/IPython/config/loader.py @@ -193,7 +193,27 @@ class Config(dict): to_update[k] = copy.deepcopy(v) self.update(to_update) - + + def collisions(self, other): + """Check for collisions between two config objects. + + Returns a dict of the form {"Class": {"trait": "collision message"}}`, + indicating which values have been ignored. + + An empty dict indicates no collisions. + """ + collisions = {} + for section in self: + if section not in other: + continue + mine = self[section] + theirs = other[section] + for key in mine: + if key in theirs and mine[key] != theirs[key]: + collisions.setdefault(section, {}) + collisions[section][key] = "%r ignored, using %r" % (mine[key], theirs[key]) + return collisions + def __contains__(self, key): # allow nested contains of the form `"Section.key" in config` if '.' in key: @@ -565,7 +585,7 @@ class KeyValueConfigLoader(CommandLineConfigLoader): def _decode_argv(self, argv, enc=None): - """decode argv if bytes, using stin.encoding, falling back on default enc""" + """decode argv if bytes, using stdin.encoding, falling back on default enc""" uargv = [] if enc is None: enc = DEFAULT_ENCODING diff --git a/IPython/config/tests/test_application.py b/IPython/config/tests/test_application.py index d40939c..a03d548 100644 --- a/IPython/config/tests/test_application.py +++ b/IPython/config/tests/test_application.py @@ -1,27 +1,18 @@ # coding: utf-8 """ Tests for IPython.config.application.Application - -Authors: - -* Brian Granger """ -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The 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 -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. import logging +import os from io import StringIO from unittest import TestCase +pjoin = os.path.join + import nose.tools as nt from IPython.config.configurable import Configurable @@ -31,13 +22,11 @@ from IPython.config.application import ( Application ) +from IPython.utils.tempdir import TemporaryDirectory from IPython.utils.traitlets import ( Bool, Unicode, Integer, List, Dict ) -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- class Foo(Configurable): @@ -189,5 +178,21 @@ class TestApplication(TestCase): def test_unicode_argv(self): app = MyApp() app.parse_command_line(['ünîcødé']) - + + def test_multi_file(self): + app = MyApp() + app.log = logging.getLogger() + name = 'config.py' + with TemporaryDirectory('_1') as td1: + with open(pjoin(td1, name), 'w') as f1: + f1.write("get_config().MyApp.Bar.b = 1") + with TemporaryDirectory('_2') as td2: + with open(pjoin(td2, name), 'w') as f2: + f2.write("get_config().MyApp.Bar.b = 2") + app.load_config_file(name, path=[td2, td1]) + app.init_bar() + self.assertEqual(app.bar.b, 2) + app.load_config_file(name, path=[td1, td2]) + app.init_bar() + self.assertEqual(app.bar.b, 1) diff --git a/IPython/config/tests/test_loader.py b/IPython/config/tests/test_loader.py index 0238285..a5fc91d 100644 --- a/IPython/config/tests/test_loader.py +++ b/IPython/config/tests/test_loader.py @@ -1,28 +1,12 @@ # encoding: utf-8 -""" -Tests for IPython.config.loader - -Authors: - -* Brian Granger -* Fernando Perez (design help) -""" +"""Tests for IPython.config.loader""" -#----------------------------------------------------------------------------- -# Copyright (C) 2008 The 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 -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. import os import pickle import sys -import json from tempfile import mkstemp from unittest import TestCase @@ -43,10 +27,6 @@ from IPython.config.loader import ( ConfigError, ) -#----------------------------------------------------------------------------- -# Actual tests -#----------------------------------------------------------------------------- - pyfile = """ c = get_config() @@ -117,6 +97,34 @@ class TestFileCL(TestCase): cl = JSONFileConfigLoader(fname, log=log) config = cl.load_config() self._check_conf(config) + + def test_collision(self): + a = Config() + b = Config() + self.assertEqual(a.collisions(b), {}) + a.A.trait1 = 1 + b.A.trait2 = 2 + self.assertEqual(a.collisions(b), {}) + b.A.trait1 = 1 + self.assertEqual(a.collisions(b), {}) + b.A.trait1 = 0 + self.assertEqual(a.collisions(b), { + 'A': { + 'trait1': "1 ignored, using 0", + } + }) + self.assertEqual(b.collisions(a), { + 'A': { + 'trait1': "0 ignored, using 1", + } + }) + a.A.trait2 = 3 + self.assertEqual(b.collisions(a), { + 'A': { + 'trait1': "0 ignored, using 1", + 'trait2': "2 ignored, using 3", + } + }) def test_v2raise(self): fd, fname = mkstemp('.json') diff --git a/IPython/consoleapp.py b/IPython/consoleapp.py index c7c5f0e..83c5e8e 100644 --- a/IPython/consoleapp.py +++ b/IPython/consoleapp.py @@ -7,11 +7,6 @@ refactoring of what used to be the IPython/qt/console/qtconsoleapp.py # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -# stdlib imports import atexit import os import signal @@ -19,7 +14,6 @@ import sys import uuid -# Local imports from IPython.config.application import boolean_flag from IPython.core.profiledir import ProfileDir from IPython.kernel.blocking import BlockingKernelClient @@ -40,18 +34,9 @@ from IPython.kernel.zmq.session import Session, default_secure from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell from IPython.kernel.connect import ConnectionFileMixin -#----------------------------------------------------------------------------- -# Network Constants -#----------------------------------------------------------------------------- - from IPython.utils.localinterfaces import localhost #----------------------------------------------------------------------------- -# Globals -#----------------------------------------------------------------------------- - - -#----------------------------------------------------------------------------- # Aliases and Flags #----------------------------------------------------------------------------- @@ -98,11 +83,7 @@ aliases.update(app_aliases) # Classes #----------------------------------------------------------------------------- -#----------------------------------------------------------------------------- -# IPythonConsole -#----------------------------------------------------------------------------- - -classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session, InlineBackend] +classes = [KernelManager, ProfileDir, Session] class IPythonConsoleApp(ConnectionFileMixin): name = 'ipython-console-mixin' @@ -158,8 +139,15 @@ class IPythonConsoleApp(ConnectionFileMixin): Set to display confirmation dialog on exit. You can always use 'exit' or 'quit', to force a direct exit without any confirmation.""", ) - - + + @property + def help_classes(self): + """ConsoleApps can configure kernels on the command-line + + But this shouldn't be written to a file + """ + return self.classes + [IPKernelApp] + IPKernelApp.classes + def build_kernel_argv(self, argv=None): """build argv to be passed to kernel subprocess""" if argv is None: @@ -303,7 +291,11 @@ class IPythonConsoleApp(ConnectionFileMixin): self.exit(1) self.kernel_manager.client_factory = self.kernel_client_class - self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv) + # FIXME: remove special treatment of IPython kernels + kwargs = {} + if self.kernel_manager.ipython_kernel: + kwargs['extra_arguments'] = self.kernel_argv + self.kernel_manager.start_kernel(**kwargs) atexit.register(self.kernel_manager.cleanup_ipc_files) if self.sshserver: diff --git a/IPython/core/alias.py b/IPython/core/alias.py index 040fad5..e1a6242 100644 --- a/IPython/core/alias.py +++ b/IPython/core/alias.py @@ -69,6 +69,21 @@ def default_aliases(): # things which are executable ('lx', 'ls -F -o --color %l | grep ^-..x'), ] + elif sys.platform.startswith('openbsd') or sys.platform.startswith('netbsd'): + # OpenBSD, NetBSD. The ls implementation on these platforms do not support + # the -G switch and lack the ability to use colorized output. + ls_aliases = [('ls', 'ls -F'), + # long ls + ('ll', 'ls -F -l'), + # ls normal files only + ('lf', 'ls -F -l %l | grep ^-'), + # ls symbolic links + ('lk', 'ls -F -l %l | grep ^l'), + # directories or links to directories, + ('ldir', 'ls -F -l %l | grep /$'), + # things which are executable + ('lx', 'ls -F -l %l | grep ^-..x'), + ] else: # BSD, OSX, etc. ls_aliases = [('ls', 'ls -F -G'), diff --git a/IPython/core/application.py b/IPython/core/application.py index 7770013..eb12377 100644 --- a/IPython/core/application.py +++ b/IPython/core/application.py @@ -7,25 +7,10 @@ handling configuration and creating configurables. The job of an :class:`Application` is to create the master configuration object and then create the configurable objects, passing the config to them. - -Authors: - -* Brian Granger -* Fernando Perez -* Min RK - """ -#----------------------------------------------------------------------------- -# Copyright (C) 2008 The 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 -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. import atexit import glob @@ -42,14 +27,18 @@ from IPython.utils.path import get_ipython_dir, get_ipython_package_dir, ensure_ from IPython.utils import py3compat from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance -#----------------------------------------------------------------------------- -# Classes and functions -#----------------------------------------------------------------------------- - +if os.name == 'nt': + programdata = os.environ.get('PROGRAMDATA', None) + if programdata: + SYSTEM_CONFIG_DIRS = [os.path.join(programdata, 'ipython')] + else: # PROGRAMDATA is not defined by default on XP. + SYSTEM_CONFIG_DIRS = [] +else: + SYSTEM_CONFIG_DIRS = [ + "/usr/local/etc/ipython", + "/etc/ipython", + ] -#----------------------------------------------------------------------------- -# Base Application Class -#----------------------------------------------------------------------------- # aliases and flags @@ -100,7 +89,7 @@ class BaseIPythonApplication(Application): builtin_profile_dir = Unicode( os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default') ) - + config_file_paths = List(Unicode) def _config_file_paths_default(self): return [py3compat.getcwd()] @@ -210,11 +199,12 @@ class BaseIPythonApplication(Application): return crashhandler.crash_handler_lite(etype, evalue, tb) def _ipython_dir_changed(self, name, old, new): - str_old = py3compat.cast_bytes_py2(os.path.abspath(old), - sys.getfilesystemencoding() - ) - if str_old in sys.path: - sys.path.remove(str_old) + if old is not None: + str_old = py3compat.cast_bytes_py2(os.path.abspath(old), + sys.getfilesystemencoding() + ) + if str_old in sys.path: + sys.path.remove(str_old) str_path = py3compat.cast_bytes_py2(os.path.abspath(new), sys.getfilesystemencoding() ) @@ -336,6 +326,7 @@ class BaseIPythonApplication(Application): def init_config_files(self): """[optionally] copy default config files into profile dir.""" + self.config_file_paths.extend(SYSTEM_CONFIG_DIRS) # copy config files path = self.builtin_profile_dir if self.copy_config_files: diff --git a/IPython/core/debugger.py b/IPython/core/debugger.py index d156a0f..80c3ada 100644 --- a/IPython/core/debugger.py +++ b/IPython/core/debugger.py @@ -277,7 +277,7 @@ class Pdb(OldPdb): try: OldPdb.interaction(self, frame, traceback) except KeyboardInterrupt: - self.shell.write("\nKeyboardInterrupt\n") + self.shell.write('\n' + self.shell.get_exception_only()) break else: break diff --git a/IPython/core/display.py b/IPython/core/display.py index b53a85c..2c9cc01 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -21,6 +21,7 @@ from __future__ import print_function import os import struct +import mimetypes from IPython.core.formatters import _safe_get_formatter_method from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode, @@ -781,6 +782,90 @@ class Image(DisplayObject): def _find_ext(self, s): return unicode_type(s.split('.')[-1].lower()) +class Video(DisplayObject): + + def __init__(self, data=None, url=None, filename=None, embed=None, mimetype=None): + """Create a video object given raw data or an URL. + + When this object is returned by an input cell or passed to the + display function, it will result in the video being displayed + in the frontend. + + Parameters + ---------- + data : unicode, str or bytes + The raw image data or a URL or filename to load the data from. + This always results in embedded image data. + url : unicode + A URL to download the data from. If you specify `url=`, + the image data will not be embedded unless you also specify `embed=True`. + filename : unicode + Path to a local file to load the data from. + Videos from a file are always embedded. + embed : bool + Should the image data be embedded using a data URI (True) or be + loaded using an tag. Set this to True if you want the image + to be viewable later with no internet connection in the notebook. + + Default is `True`, unless the keyword argument `url` is set, then + default value is `False`. + + Note that QtConsole is not able to display images if `embed` is set to `False` + mimetype: unicode + Specify the mimetype in case you load in a encoded video. + Examples + -------- + Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4') + Video('path/to/video.mp4') + Video('path/to/video.mp4', embed=False) + """ + if url is None and (data.startswith('http') or data.startswith('https')): + url = data + data = None + embed = False + elif os.path.exists(data): + filename = data + data = None + + self.mimetype = mimetype + self.embed = embed if embed is not None else (filename is not None) + super(Video, self).__init__(data=data, url=url, filename=filename) + + def _repr_html_(self): + # External URLs and potentially local files are not embedded into the + # notebook output. + if not self.embed: + url = self.url if self.url is not None else self.filename + output = """""".format(url) + return output + # Embedded videos uses base64 encoded videos. + if self.filename is not None: + mimetypes.init() + mimetype, encoding = mimetypes.guess_type(self.filename) + + video = open(self.filename, 'rb').read() + video_encoded = video.encode('base64') + else: + video_encoded = self.data + mimetype = self.mimetype + output = """""".format(mimetype, video_encoded) + return output + + def reload(self): + # TODO + pass + + def _repr_png_(self): + # TODO + pass + def _repr_jpeg_(self): + # TODO + pass def clear_output(wait=False): """Clear the output of the current cell receiving output. diff --git a/IPython/core/displayhook.py b/IPython/core/displayhook.py index 8addec1..544867a 100644 --- a/IPython/core/displayhook.py +++ b/IPython/core/displayhook.py @@ -2,25 +2,11 @@ """Displayhook for IPython. This defines a callable class that IPython uses for `sys.displayhook`. - -Authors: - -* Fernando Perez -* Brian Granger -* Robert Kern """ -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The IPython Development Team -# Copyright (C) 2001-2007 Fernando Perez -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + from __future__ import print_function import sys @@ -29,13 +15,9 @@ from IPython.core.formatters import _safe_get_formatter_method from IPython.config.configurable import Configurable from IPython.utils import io from IPython.utils.py3compat import builtin_mod -from IPython.utils.traitlets import Instance +from IPython.utils.traitlets import Instance, Float from IPython.utils.warn import warn -#----------------------------------------------------------------------------- -# Main displayhook class -#----------------------------------------------------------------------------- - # TODO: Move the various attributes (cache_size, [others now moved]). Some # of these are also attributes of InteractiveShell. They should be on ONE object # only and the other objects should ask that one object for their values. @@ -48,10 +30,10 @@ class DisplayHook(Configurable): """ shell = Instance('IPython.core.interactiveshell.InteractiveShellABC') + cull_fraction = Float(0.2) def __init__(self, shell=None, cache_size=1000, **kwargs): super(DisplayHook, self).__init__(shell=shell, **kwargs) - cache_size_min = 3 if cache_size <= 0: self.do_full_cache = 0 @@ -168,6 +150,9 @@ class DisplayHook(Configurable): md_dict : dict (optional) The metadata dict to be associated with the display data. """ + if 'text/plain' not in format_dict: + # nothing to do + return # We want to print because we want to always make sure we have a # newline, even if all the prompt separators are ''. This is the # standard IPython behavior. @@ -193,13 +178,7 @@ class DisplayHook(Configurable): # Avoid recursive reference when displaying _oh/Out if result is not self.shell.user_ns['_oh']: if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache: - warn('Output cache limit (currently '+ - repr(self.cache_size)+' entries) hit.\n' - 'Flushing cache and resetting history counter...\n' - 'The only history variables available will be _,__,___ and _1\n' - 'with the current result.') - - self.flush() + self.cull_cache() # Don't overwrite '_' and friends if '_' is in __builtin__ (otherwise # we cause buggy behavior for things like gettext). @@ -221,6 +200,9 @@ class DisplayHook(Configurable): def log_output(self, format_dict): """Log the output.""" + if 'text/plain' not in format_dict: + # nothing to do + return if self.shell.logger.log_output: self.shell.logger.log_write(format_dict['text/plain'], 'output') self.shell.history_manager.output_hist_reprs[self.prompt_count] = \ @@ -255,6 +237,21 @@ class DisplayHook(Configurable): self.log_output(format_dict) self.finish_displayhook() + def cull_cache(self): + """Output cache is full, cull the oldest entries""" + oh = self.shell.user_ns.get('_oh', {}) + sz = len(oh) + cull_count = max(int(sz * self.cull_fraction), 2) + warn('Output cache limit (currently {sz} entries) hit.\n' + 'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count)) + + for i, n in enumerate(sorted(oh)): + if i >= cull_count: + break + self.shell.user_ns.pop('_%i' % n, None) + oh.pop(n, None) + + def flush(self): if not self.do_full_cache: raise ValueError("You shouldn't have reached the cache flush " diff --git a/IPython/core/events.py b/IPython/core/events.py index dcdc669..798ef01 100644 --- a/IPython/core/events.py +++ b/IPython/core/events.py @@ -63,14 +63,6 @@ class EventManager(object): """Remove a callback from the given event.""" self.callbacks[event].remove(function) - def reset(self, event): - """Clear all callbacks for the given event.""" - self.callbacks[event] = [] - - def reset_all(self): - """Clear all callbacks for all events.""" - self.callbacks = {n:[] for n in self.callbacks} - def trigger(self, event, *args, **kwargs): """Call callbacks for ``event``. diff --git a/IPython/core/formatters.py b/IPython/core/formatters.py index 1e810fe..e66d569 100644 --- a/IPython/core/formatters.py +++ b/IPython/core/formatters.py @@ -5,35 +5,22 @@ Inheritance diagram: .. inheritance-diagram:: IPython.core.formatters :parts: 3 - -Authors: - -* Robert Kern -* Brian Granger """ -#----------------------------------------------------------------------------- -# Copyright (C) 2010-2011, 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 -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. -# Stdlib imports import abc import inspect import sys +import traceback import types import warnings from IPython.external.decorator import decorator -# Our own imports from IPython.config.configurable import Configurable +from IPython.core.getipython import get_ipython from IPython.lib import pretty from IPython.utils.traitlets import ( Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List, @@ -223,6 +210,18 @@ class DisplayFormatter(Configurable): # Formatters for specific format types (text, html, svg, etc.) #----------------------------------------------------------------------------- + +def _safe_repr(obj): + """Try to return a repr of an object + + always returns a string, at least. + """ + try: + return repr(obj) + except Exception as e: + return "un-repr-able object (%r)" % e + + class FormatterWarning(UserWarning): """Warning class for errors in formatters""" @@ -231,13 +230,16 @@ def warn_format_error(method, self, *args, **kwargs): """decorator for warning on failed format call""" try: r = method(self, *args, **kwargs) - except NotImplementedError as e: + except NotImplementedError: # don't warn on NotImplementedErrors return None - except Exception as e: - warnings.warn("Exception in %s formatter: %s" % (self.format_type, e), - FormatterWarning, - ) + except Exception: + exc_info = sys.exc_info() + ip = get_ipython() + if ip is not None: + ip.showtraceback(exc_info) + else: + traceback.print_exception(*exc_info) return None if r is None or isinstance(r, self._return_type) or \ (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)): @@ -245,7 +247,7 @@ def warn_format_error(method, self, *args, **kwargs): else: warnings.warn( "%s formatter returned invalid type %s (expected %s) for object: %s" % \ - (self.format_type, type(r), self._return_type, pretty._safe_repr(args[0])), + (self.format_type, type(r), self._return_type, _safe_repr(args[0])), FormatterWarning ) @@ -588,7 +590,14 @@ class PlainTextFormatter(BaseFormatter): # This subclass ignores this attribute as it always need to return # something. enabled = Bool(True, config=False) - + + max_seq_length = Integer(pretty.MAX_SEQ_LENGTH, config=True, + help="""Truncate large collections (lists, dicts, tuples, sets) to this size. + + Set to 0 to disable truncation. + """ + ) + # Look for a _repr_pretty_ methods to use for pretty printing. print_method = ObjectName('_repr_pretty_') @@ -672,7 +681,7 @@ class PlainTextFormatter(BaseFormatter): def __call__(self, obj): """Compute the pretty representation of the object.""" if not self.pprint: - return pretty._safe_repr(obj) + return repr(obj) else: # This uses use StringIO, as cStringIO doesn't handle unicode. stream = StringIO() @@ -681,6 +690,7 @@ class PlainTextFormatter(BaseFormatter): # or it will cause trouble. printer = pretty.RepresentationPrinter(stream, self.verbose, self.max_width, unicode_to_str(self.newline), + max_seq_length=self.max_seq_length, singleton_pprinters=self.singleton_printers, type_pprinters=self.type_printers, deferred_pprinters=self.deferred_printers) @@ -836,6 +846,8 @@ class PDFFormatter(BaseFormatter): print_method = ObjectName('_repr_pdf_') + _return_type = (bytes, unicode_type) + FormatterABC.register(BaseFormatter) FormatterABC.register(PlainTextFormatter) diff --git a/IPython/core/history.py b/IPython/core/history.py index c9b8435..c615ad8 100644 --- a/IPython/core/history.py +++ b/IPython/core/history.py @@ -98,9 +98,24 @@ def catch_corrupt_db(f, self, *a, **kw): # The hist_file is probably :memory: or something else. raise +class HistoryAccessorBase(Configurable): + """An abstract class for History Accessors """ + def get_tail(self, n=10, raw=True, output=False, include_latest=False): + raise NotImplementedError + + def search(self, pattern="*", raw=True, search_raw=True, + output=False, n=None, unique=False): + raise NotImplementedError + + def get_range(self, session, start=1, stop=None, raw=True,output=False): + raise NotImplementedError -class HistoryAccessor(Configurable): + def get_range_by_str(self, rangestr, raw=True, output=False): + raise NotImplementedError + + +class HistoryAccessor(HistoryAccessorBase): """Access the history database without adding to it. This is intended for use by standalone history tools. IPython shells use @@ -544,7 +559,7 @@ class HistoryManager(HistoryAccessor): self.input_hist_parsed[:] = [""] self.input_hist_raw[:] = [""] self.new_session() - + # ------------------------------ # Methods for retrieving history # ------------------------------ diff --git a/IPython/core/inputsplitter.py b/IPython/core/inputsplitter.py index 5d1a90d..34f3b72 100644 --- a/IPython/core/inputsplitter.py +++ b/IPython/core/inputsplitter.py @@ -20,6 +20,7 @@ import ast import codeop import re import sys +import warnings from IPython.utils.py3compat import cast_unicode from IPython.core.inputtransformer import (leading_indent, @@ -208,6 +209,8 @@ class InputSplitter(object): _full_dedent = False # Boolean indicating whether the current block is complete _is_complete = None + # Boolean indicating whether the current block has an unrecoverable syntax error + _is_invalid = False def __init__(self): """Create a new InputSplitter instance. @@ -223,6 +226,7 @@ class InputSplitter(object): self.source = '' self.code = None self._is_complete = False + self._is_invalid = False self._full_dedent = False def source_reset(self): @@ -232,6 +236,42 @@ class InputSplitter(object): self.reset() return out + def check_complete(self, source): + """Return whether a block of code is ready to execute, or should be continued + + This is a non-stateful API, and will reset the state of this InputSplitter. + + Parameters + ---------- + source : string + Python input code, which can be multiline. + + Returns + ------- + status : str + One of 'complete', 'incomplete', or 'invalid' if source is not a + prefix of valid code. + indent_spaces : int or None + The number of spaces by which to indent the next line of code. If + status is not 'incomplete', this is None. + """ + self.reset() + try: + self.push(source) + except SyntaxError: + # Transformers in IPythonInputSplitter can raise SyntaxError, + # which push() will not catch. + return 'invalid', None + else: + if self._is_invalid: + return 'invalid', None + elif self.push_accepts_more(): + return 'incomplete', self.indent_spaces + else: + return 'complete', None + finally: + self.reset() + def push(self, lines): """Push one or more lines of input. @@ -261,6 +301,7 @@ class InputSplitter(object): # exception is raised in compilation, we don't mislead by having # inconsistent code/source attributes. self.code, self._is_complete = None, None + self._is_invalid = False # Honor termination lines properly if source.endswith('\\\n'): @@ -268,15 +309,18 @@ class InputSplitter(object): self._update_indent(lines) try: - self.code = self._compile(source, symbol="exec") + with warnings.catch_warnings(): + warnings.simplefilter('error', SyntaxWarning) + self.code = self._compile(source, symbol="exec") # Invalid syntax can produce any of a number of different errors from # inside the compiler, so we have to catch them all. 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. except (SyntaxError, OverflowError, ValueError, TypeError, - MemoryError): + MemoryError, SyntaxWarning): self._is_complete = True + self._is_invalid = True else: # Compilation didn't produce any exceptions (though it may not have # given a complete code object) diff --git a/IPython/core/inputtransformer.py b/IPython/core/inputtransformer.py index f20b1a1..daf40b0 100644 --- a/IPython/core/inputtransformer.py +++ b/IPython/core/inputtransformer.py @@ -461,7 +461,7 @@ def classic_prompt(): def ipy_prompt(): """Strip IPython's In [1]:/...: prompts.""" # FIXME: non-capturing version (?:...) usable? - prompt_re = re.compile(r'^(In \[\d+\]: |\ {3,}\.{3,}: )') + prompt_re = re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)') return _strip_prompts(prompt_re) diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 56aea6d..a736d32 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -22,6 +22,7 @@ import re import runpy import sys import tempfile +import traceback import types import subprocess from io import open as io_open @@ -424,7 +425,7 @@ class InteractiveShell(SingletonConfigurable): display_trap = Instance('IPython.core.display_trap.DisplayTrap') extension_manager = Instance('IPython.core.extensions.ExtensionManager') payload_manager = Instance('IPython.core.payload.PayloadManager') - history_manager = Instance('IPython.core.history.HistoryManager') + history_manager = Instance('IPython.core.history.HistoryAccessorBase') magics_manager = Instance('IPython.core.magic.MagicsManager') profile_dir = Instance('IPython.core.application.ProfileDir') @@ -523,7 +524,6 @@ class InteractiveShell(SingletonConfigurable): self.init_pdb() self.init_extension_manager() self.init_payload() - self.init_comms() self.hooks.late_startup_hook() self.events.trigger('shell_initialized', self) atexit.register(self.atexit_operations) @@ -874,6 +874,8 @@ class InteractiveShell(SingletonConfigurable): def init_events(self): self.events = EventManager(self, available_events) + self.events.register("pre_execute", self._clear_warning_registry) + def register_post_execute(self, func): """DEPRECATED: Use ip.events.register('post_run_cell', func) @@ -883,6 +885,13 @@ class InteractiveShell(SingletonConfigurable): "ip.events.register('post_run_cell', func) instead.") self.events.register('post_run_cell', func) + def _clear_warning_registry(self): + # clear the warning registry, so that different code blocks with + # overlapping line number ranges don't cause spurious suppression of + # warnings (see gh-6611 for details) + if "__warningregistry__" in self.user_global_ns: + del self.user_global_ns["__warningregistry__"] + #------------------------------------------------------------------------- # Things related to the "main" module #------------------------------------------------------------------------- @@ -1778,6 +1787,15 @@ class InteractiveShell(SingletonConfigurable): """ self.write_err("UsageError: %s" % exc) + def get_exception_only(self, exc_tuple=None): + """ + Return as a string (ending with a newline) the exception that + just occurred, without any traceback. + """ + etype, value, tb = self._get_exc_info(exc_tuple) + msg = traceback.format_exception_only(etype, value) + return ''.join(msg) + def showtraceback(self, exc_tuple=None, filename=None, tb_offset=None, exception_only=False): """Display the exception that just occurred. @@ -1830,7 +1848,7 @@ class InteractiveShell(SingletonConfigurable): self._showtraceback(etype, value, stb) except KeyboardInterrupt: - self.write_err("\nKeyboardInterrupt\n") + self.write_err('\n' + self.get_exception_only()) def _showtraceback(self, etype, evalue, stb): """Actually show a traceback. @@ -2344,22 +2362,38 @@ class InteractiveShell(SingletonConfigurable): if path is not None: cmd = '"pushd %s &&"%s' % (path, cmd) cmd = py3compat.unicode_to_str(cmd) - ec = os.system(cmd) + try: + ec = os.system(cmd) + except KeyboardInterrupt: + self.write_err('\n' + self.get_exception_only()) + ec = -2 else: cmd = py3compat.unicode_to_str(cmd) - # Call the cmd using the OS shell, instead of the default /bin/sh, if set. - ec = subprocess.call(cmd, shell=True, executable=os.environ.get('SHELL', None)) - # exit code is positive for program failure, or negative for - # terminating signal number. - - # Interpret ec > 128 as signal - # Some shells (csh, fish) don't follow sh/bash conventions for exit codes + # For posix the result of the subprocess.call() below is an exit + # code, which by convention is zero for success, positive for + # program failure. Exit codes above 128 are reserved for signals, + # and the formula for converting a signal to an exit code is usually + # signal_number+128. To more easily differentiate between exit + # codes and signals, ipython uses negative numbers. For instance + # since control-c is signal 2 but exit code 130, ipython's + # _exit_code variable will read -2. Note that some shells like + # csh and fish don't follow sh/bash conventions for exit codes. + executable = os.environ.get('SHELL', None) + try: + # Use env shell instead of default /bin/sh + ec = subprocess.call(cmd, shell=True, executable=executable) + except KeyboardInterrupt: + # intercept control-C; a long traceback is not useful here + self.write_err('\n' + self.get_exception_only()) + ec = 130 if ec > 128: ec = -(ec - 128) # We explicitly do NOT return the subprocess status code, because # a non-None value would trigger :func:`sys.displayhook` calls. - # Instead, we store the exit_code in user_ns. + # Instead, we store the exit_code in user_ns. Note the semantics + # of _exit_code: for control-c, _exit_code == -signal.SIGNIT, + # but raising SystemExit(_exit_code) will give status 254! self.user_ns['_exit_code'] = ec # use piped system by default, because it is better behaved @@ -2419,14 +2453,6 @@ class InteractiveShell(SingletonConfigurable): self.configurables.append(self.payload_manager) #------------------------------------------------------------------------- - # Things related to widgets - #------------------------------------------------------------------------- - - def init_comms(self): - # not implemented in the base class - pass - - #------------------------------------------------------------------------- # Things related to the prefilter #------------------------------------------------------------------------- @@ -2565,10 +2591,16 @@ class InteractiveShell(SingletonConfigurable): silenced for zero status, as it is so common). raise_exceptions : bool (False) If True raise exceptions everywhere. Meant for testing. + shell_futures : bool (False) + If True, the code will share future statements with the interactive + shell. It will both be affected by previous __future__ imports, and + any __future__ imports in the code will affect the shell. If False, + __future__ imports are not shared in either direction. """ kw.setdefault('exit_ignore', False) kw.setdefault('raise_exceptions', False) + kw.setdefault('shell_futures', False) fname = os.path.abspath(os.path.expanduser(fname)) @@ -2587,7 +2619,10 @@ class InteractiveShell(SingletonConfigurable): with prepended_to_syspath(dname): try: - py3compat.execfile(fname,*where) + glob, loc = (where + (None, ))[:2] + py3compat.execfile( + fname, glob, loc, + self.compile if kw['shell_futures'] else None) except SystemExit as status: # If the call was made with 0 or None exit status (sys.exit(0) # or sys.exit() ), don't bother showing a traceback, as both of @@ -2608,7 +2643,7 @@ class InteractiveShell(SingletonConfigurable): # tb offset is 2 because we wrap execfile self.showtraceback(tb_offset=2) - def safe_execfile_ipy(self, fname): + def safe_execfile_ipy(self, fname, shell_futures=False): """Like safe_execfile, but for .ipy or .ipynb files with IPython syntax. Parameters @@ -2616,6 +2651,11 @@ class InteractiveShell(SingletonConfigurable): fname : str The name of the file to execute. The filename must have a .ipy or .ipynb extension. + shell_futures : bool (False) + If True, the code will share future statements with the interactive + shell. It will both be affected by previous __future__ imports, and + any __future__ imports in the code will affect the shell. If False, + __future__ imports are not shared in either direction. """ fname = os.path.abspath(os.path.expanduser(fname)) @@ -2635,14 +2675,14 @@ class InteractiveShell(SingletonConfigurable): def get_cells(): """generator for sequence of code blocks to run""" if fname.endswith('.ipynb'): - from IPython.nbformat import current - with open(fname) as f: - nb = current.read(f, 'json') - if not nb.worksheets: + from IPython.nbformat import read + with io_open(fname) as f: + nb = read(f, as_version=4) + if not nb.cells: return - for cell in nb.worksheets[0].cells: + for cell in nb.cells: if cell.cell_type == 'code': - yield cell.input + yield cell.source else: with open(fname) as f: yield f.read() @@ -2654,7 +2694,7 @@ class InteractiveShell(SingletonConfigurable): # raised in user code. It would be nice if there were # versions of run_cell that did raise, so # we could catch the errors. - self.run_cell(cell, silent=True, shell_futures=False) + self.run_cell(cell, silent=True, shell_futures=shell_futures) except: self.showtraceback() warn('Unknown failure executing file: <%s>' % fname) @@ -3072,7 +3112,15 @@ class InteractiveShell(SingletonConfigurable): namespace. """ ns = self.user_ns.copy() - ns.update(sys._getframe(depth+1).f_locals) + try: + frame = sys._getframe(depth+1) + except ValueError: + # This is thrown if there aren't that many frames on the stack, + # e.g. if a script called run_line_magic() directly. + pass + else: + ns.update(frame.f_locals) + try: # We have to use .vformat() here, because 'self' is a valid and common # name, and expanding **ns for .format() would make it collide with diff --git a/IPython/core/magics/basic.py b/IPython/core/magics/basic.py index 6e39335..c4c48cb 100644 --- a/IPython/core/magics/basic.py +++ b/IPython/core/magics/basic.py @@ -1,25 +1,12 @@ -"""Implementation of basic magic functions. -""" -#----------------------------------------------------------------------------- -# Copyright (c) 2012 The 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 -#----------------------------------------------------------------------------- +"""Implementation of basic magic functions.""" + from __future__ import print_function -# Stdlib import io import json import sys from pprint import pformat -# Our own packages from IPython.core import magic_arguments, page from IPython.core.error import UsageError from IPython.core.magic import Magics, magics_class, line_magic, magic_escapes @@ -30,9 +17,6 @@ from IPython.utils.path import unquote_filename from IPython.utils.py3compat import unicode_type from IPython.utils.warn import warn, error -#----------------------------------------------------------------------------- -# Magics class implementation -#----------------------------------------------------------------------------- class MagicsDisplay(object): def __init__(self, magics_manager): @@ -362,9 +346,6 @@ Currently the magic system has the following functions:""", Proper color support under MS Windows requires the pyreadline library. You can find it at: http://ipython.org/pyreadline.html -Gary's readline needs the ctypes module, from: -http://starship.python.net/crew/theller/ctypes -(Note that ctypes is already part of Python versions 2.5 and newer). Defaulting color scheme to 'NoColor'""" new_scheme = 'NoColor' @@ -602,13 +583,6 @@ Defaulting color scheme to 'NoColor'""" 'file extension will write the notebook as a Python script' ) @magic_arguments.argument( - '-f', '--format', - help='Convert an existing IPython notebook to a new format. This option ' - 'specifies the new format and can have the values: json, py. ' - 'The target filename is chosen automatically based on the new ' - 'format. The filename argument gives the name of the source file.' - ) - @magic_arguments.argument( 'filename', type=unicode_type, help='Notebook name or filename' ) @@ -616,41 +590,22 @@ Defaulting color scheme to 'NoColor'""" def notebook(self, s): """Export and convert IPython notebooks. - This function can export the current IPython history to a notebook file - or can convert an existing notebook file into a different format. For - example, to export the history to "foo.ipynb" do "%notebook -e foo.ipynb". - To export the history to "foo.py" do "%notebook -e foo.py". To convert - "foo.ipynb" to "foo.json" do "%notebook -f json foo.ipynb". Possible - formats include (json/ipynb, py). + This function can export the current IPython history to a notebook file. + For example, to export the history to "foo.ipynb" do "%notebook -e foo.ipynb". + To export the history to "foo.py" do "%notebook -e foo.py". """ args = magic_arguments.parse_argstring(self.notebook, s) - from IPython.nbformat import current + from IPython.nbformat import write, v4 args.filename = unquote_filename(args.filename) if args.export: - fname, name, format = current.parse_filename(args.filename) cells = [] hist = list(self.shell.history_manager.get_range()) - for session, prompt_number, input in hist[:-1]: - cells.append(current.new_code_cell(prompt_number=prompt_number, - input=input)) - worksheet = current.new_worksheet(cells=cells) - nb = current.new_notebook(name=name,worksheets=[worksheet]) - with io.open(fname, 'w', encoding='utf-8') as f: - current.write(nb, f, format); - elif args.format is not None: - old_fname, old_name, old_format = current.parse_filename(args.filename) - new_format = args.format - if new_format == u'xml': - raise ValueError('Notebooks cannot be written as xml.') - elif new_format == u'ipynb' or new_format == u'json': - new_fname = old_name + u'.ipynb' - new_format = u'json' - elif new_format == u'py': - new_fname = old_name + u'.py' - else: - raise ValueError('Invalid notebook format: %s' % new_format) - with io.open(old_fname, 'r', encoding='utf-8') as f: - nb = current.read(f, old_format) - with io.open(new_fname, 'w', encoding='utf-8') as f: - current.write(nb, f, new_format) + for session, execution_count, input 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) diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index eaeb2fa..e4ca148 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -1027,7 +1027,10 @@ python-profiler package from non-free.""") worst = max(worst, worst_tuning) # Check best timing is greater than zero to avoid a # ZeroDivisionError. - if worst > 4 * best and best > 0: + # In cases where the slowest timing is lesser than a micosecond + # we assume that it does not really matter if the fastest + # timing is 4 times faster than the slowest timing or not. + if worst > 4 * best and best > 0 and worst > 1e-6: print("The slowest run took %0.2f times longer than the " "fastest. This could mean that an intermediate result " "is being cached " % (worst / best)) @@ -1057,7 +1060,7 @@ python-profiler package from non-free.""") following statement raises an error). This function provides very basic timing functionality. Use the timeit - magic for more controll over the measurement. + magic for more control over the measurement. Examples -------- diff --git a/IPython/core/magics/osm.py b/IPython/core/magics/osm.py index ccb2105..553f49f 100644 --- a/IPython/core/magics/osm.py +++ b/IPython/core/magics/osm.py @@ -371,14 +371,52 @@ class OSMagics(Magics): 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=''): """List environment variables.""" - + 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 + """ + 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[py3compat.cast_bytes_py2(var)] = py3compat.cast_bytes_py2(val) + print('env: {0}={1}'.format(var,val)) + + @line_magic def pushd(self, parameter_s=''): """Place the current dir on stack and change directory. diff --git a/IPython/core/oinspect.py b/IPython/core/oinspect.py index 68adadb..6e6b31b 100644 --- a/IPython/core/oinspect.py +++ b/IPython/core/oinspect.py @@ -40,6 +40,7 @@ from IPython.utils.text import indent from IPython.utils.wildcard import list_namespace from IPython.utils.coloransi import TermColors, ColorScheme, ColorSchemeTable from IPython.utils.py3compat import cast_unicode, string_types, PY3 +from IPython.utils.signatures import signature # builtin docstrings to ignore _func_call_docstring = types.FunctionType.__call__.__doc__ @@ -390,7 +391,7 @@ class Inspector: If any exception is generated, None is returned instead and the exception is suppressed.""" try: - hdef = oname + inspect.formatargspec(*getargspec(obj)) + hdef = oname + str(signature(obj)) return cast_unicode(hdef) except: return None diff --git a/IPython/core/payloadpage.py b/IPython/core/payloadpage.py index 4ac0e38..a1f7650 100644 --- a/IPython/core/payloadpage.py +++ b/IPython/core/payloadpage.py @@ -38,7 +38,6 @@ def page(strng, start=0, screen_lines=0, pager_cmd=None): source='page', data=data, start=start, - screen_lines=screen_lines, ) shell.payload_manager.write_payload(payload) diff --git a/IPython/core/profileapp.py b/IPython/core/profileapp.py index d5a6604..b5b2fdc 100644 --- a/IPython/core/profileapp.py +++ b/IPython/core/profileapp.py @@ -261,6 +261,8 @@ class ProfileCreate(BaseIPythonApplication): from IPython.terminal.ipapp import TerminalIPythonApp apps = [TerminalIPythonApp] for app_path in ( + 'IPython.kernel.zmq.kernelapp.IPKernelApp', + 'IPython.terminal.console.app.ZMQTerminalIPythonApp', 'IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp', 'IPython.html.notebookapp.NotebookApp', 'IPython.nbconvert.nbconvertapp.NbConvertApp', diff --git a/IPython/core/pylabtools.py b/IPython/core/pylabtools.py index a4b396a..682617d 100644 --- a/IPython/core/pylabtools.py +++ b/IPython/core/pylabtools.py @@ -1,24 +1,9 @@ # -*- coding: utf-8 -*- -"""Pylab (matplotlib) support utilities. - -Authors -------- - -* Fernando Perez. -* Brian Granger -""" +"""Pylab (matplotlib) support utilities.""" from __future__ import print_function -#----------------------------------------------------------------------------- -# Copyright (C) 2009 The 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 -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. from io import BytesIO @@ -34,7 +19,9 @@ backends = {'tk': 'TkAgg', 'wx': 'WXAgg', 'qt': 'Qt4Agg', # qt3 not supported 'qt4': 'Qt4Agg', + 'qt5': 'Qt5Agg', 'osx': 'MacOSX', + 'nbagg': 'nbAgg', 'inline' : 'module://IPython.kernel.zmq.pylab.backend_inline'} # We also need a reverse backends2guis mapping that will properly choose which diff --git a/IPython/core/shellapp.py b/IPython/core/shellapp.py index e7b57b1..aa3d6ec 100644 --- a/IPython/core/shellapp.py +++ b/IPython/core/shellapp.py @@ -324,7 +324,7 @@ class InteractiveShellApp(Configurable): self.log.warn("Unknown error in handling IPythonApp.exec_lines:") self.shell.showtraceback() - def _exec_file(self, fname): + def _exec_file(self, fname, shell_futures=False): try: full_filename = filefind(fname, [u'.', self.ipython_dir]) except IOError as e: @@ -346,11 +346,13 @@ class InteractiveShellApp(Configurable): with preserve_keys(self.shell.user_ns, '__file__'): self.shell.user_ns['__file__'] = fname if full_filename.endswith('.ipy'): - self.shell.safe_execfile_ipy(full_filename) + self.shell.safe_execfile_ipy(full_filename, + shell_futures=shell_futures) else: # default to python, even without extension self.shell.safe_execfile(full_filename, - self.shell.user_ns) + self.shell.user_ns, + shell_futures=shell_futures) finally: sys.argv = save_argv @@ -418,7 +420,7 @@ class InteractiveShellApp(Configurable): elif self.file_to_run: fname = self.file_to_run try: - self._exec_file(fname) + self._exec_file(fname, shell_futures=True) except: self.log.warn("Error in executing file in user namespace: %s" % fname) diff --git a/IPython/core/tests/test_display.py b/IPython/core/tests/test_display.py index a1c26d1..2e8a5d1 100644 --- a/IPython/core/tests/test_display.py +++ b/IPython/core/tests/test_display.py @@ -52,9 +52,9 @@ def test_image_filename_defaults(): nt.assert_raises(ValueError, display.Image, data='this is not an image', format='badformat', embed=True) from IPython.html import DEFAULT_STATIC_FILES_PATH # check boths paths to allow packages to test at build and install time - imgfile = os.path.join(tpath, 'html/static/base/images/ipynblogo.png') + imgfile = os.path.join(tpath, 'html/static/base/images/logo.png') if not os.path.exists(imgfile): - imgfile = os.path.join(DEFAULT_STATIC_FILES_PATH, 'base/images/ipynblogo.png') + imgfile = os.path.join(DEFAULT_STATIC_FILES_PATH, 'base/images/logo.png') img = display.Image(filename=imgfile) nt.assert_equal('png', img.format) nt.assert_is_not_none(img._repr_png_()) diff --git a/IPython/core/tests/test_events.py b/IPython/core/tests/test_events.py index e265999..8e8402c 100644 --- a/IPython/core/tests/test_events.py +++ b/IPython/core/tests/test_events.py @@ -25,22 +25,8 @@ class CallbackTests(unittest.TestCase): self.em.trigger('ping_received') self.assertEqual(cb.call_count, 1) - def test_reset(self): - cb = Mock() - self.em.register('ping_received', cb) - self.em.reset('ping_received') - self.em.trigger('ping_received') - assert not cb.called - - def test_reset_all(self): - cb = Mock() - self.em.register('ping_received', cb) - self.em.reset_all() - self.em.trigger('ping_received') - assert not cb.called - 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') \ No newline at end of file + self.em.trigger('ping_received') diff --git a/IPython/core/tests/test_formatters.py b/IPython/core/tests/test_formatters.py index 65105d8..1c511ea 100644 --- a/IPython/core/tests/test_formatters.py +++ b/IPython/core/tests/test_formatters.py @@ -25,6 +25,10 @@ class B(A): class C: pass +class BadRepr(object): + def __repr__(self): + raise ValueError("bad repr") + class BadPretty(object): _repr_pretty_ = None @@ -234,30 +238,30 @@ def test_pop_string(): nt.assert_is(f.pop(type_str, None), None) -def test_warn_error_method(): +def test_error_method(): f = HTMLFormatter() class BadHTML(object): def _repr_html_(self): - return 1/0 + raise ValueError("Bad HTML") bad = BadHTML() with capture_output() as captured: result = f(bad) nt.assert_is(result, None) - nt.assert_in("FormatterWarning", captured.stderr) - nt.assert_in("text/html", captured.stderr) - nt.assert_in("zero", captured.stderr) + 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 - return 1/0 h = HTMLNotImplemented() with capture_output() as captured: result = f(h) nt.assert_is(result, None) - nt.assert_not_in("FormatterWarning", captured.stderr) + nt.assert_equal("", captured.stderr) + nt.assert_equal("", captured.stdout) def test_warn_error_for_type(): f = HTMLFormatter() @@ -265,11 +269,11 @@ def test_warn_error_for_type(): with capture_output() as captured: result = f(5) nt.assert_is(result, None) - nt.assert_in("FormatterWarning", captured.stderr) - nt.assert_in("text/html", captured.stderr) - nt.assert_in("name_error", captured.stderr) + nt.assert_in("Traceback", captured.stdout) + nt.assert_in("NameError", captured.stdout) + nt.assert_in("name_error", captured.stdout) -def test_warn_error_pretty_method(): +def test_error_pretty_method(): f = PlainTextFormatter() class BadPretty(object): def _repr_pretty_(self): @@ -278,9 +282,23 @@ def test_warn_error_pretty_method(): with capture_output() as captured: result = f(bad) nt.assert_is(result, None) - nt.assert_in("FormatterWarning", captured.stderr) - nt.assert_in("text/plain", captured.stderr) - nt.assert_in("argument", captured.stderr) + 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): @@ -320,3 +338,15 @@ def test_format_config(): 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) diff --git a/IPython/core/tests/test_inputsplitter.py b/IPython/core/tests/test_inputsplitter.py index 6447f0c..c657d7f 100644 --- a/IPython/core/tests/test_inputsplitter.py +++ b/IPython/core/tests/test_inputsplitter.py @@ -342,6 +342,14 @@ class InputSplitterTestCase(unittest.TestCase): isp.push(r"(1 \ ") self.assertFalse(isp.push_accepts_more()) + 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. """ diff --git a/IPython/core/tests/test_inputtransformer.py b/IPython/core/tests/test_inputtransformer.py index ff136e2..4f04032 100644 --- a/IPython/core/tests/test_inputtransformer.py +++ b/IPython/core/tests/test_inputtransformer.py @@ -228,6 +228,16 @@ syntax_ml = \ (' ...: 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"""'), ], diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py index 4d95a68..9f8da7a 100644 --- a/IPython/core/tests/test_interactiveshell.py +++ b/IPython/core/tests/test_interactiveshell.py @@ -301,7 +301,10 @@ class InteractiveShellTestCase(unittest.TestCase): assert post_explicit.called finally: # remove post-exec - ip.events.reset_all() + 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""" @@ -479,6 +482,24 @@ class InteractiveShellTestCase(unittest.TestCase): 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') + + class DerivedInterrupt(KeyboardInterrupt): + pass + try: + raise DerivedInterrupt("foo") + except KeyboardInterrupt: + msg = ip.get_exception_only() + if sys.version_info[0] <= 2: + self.assertEqual(msg, 'DerivedInterrupt: foo\n') + else: + self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n') + class TestSafeExecfileNonAsciiPath(unittest.TestCase): @onlyif_unicode_paths @@ -541,6 +562,16 @@ class TestSystemRaw(unittest.TestCase, ExitCodeChecks): 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 @@ -840,3 +871,17 @@ class TestSyntaxErrorTransformer(unittest.TestCase): +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 + # 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") diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index ea617bc..a8b9cc6 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -5,10 +5,6 @@ Needs to be run by nose (to make ipython session available). """ from __future__ import absolute_import -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - import io import os import sys @@ -23,6 +19,7 @@ except ImportError: import nose.tools as nt from IPython.core import magic +from IPython.core.error import UsageError from IPython.core.magic import (Magics, magics_class, line_magic, cell_magic, line_cell_magic, register_line_magic, register_cell_magic, @@ -40,9 +37,6 @@ if py3compat.PY3: else: from StringIO import StringIO -#----------------------------------------------------------------------------- -# Test functions begin -#----------------------------------------------------------------------------- @magic.magics_class class DummyMagics(magic.Magics): pass @@ -624,7 +618,7 @@ def test_extension(): # The nose skip decorator doesn't work on classes, so this uses unittest's skipIf -@skipIf(dec.module_not_available('IPython.nbformat.current'), 'nbformat not importable') +@skipIf(dec.module_not_available('IPython.nbformat'), 'nbformat not importable') class NotebookExportMagicTests(TestCase): def test_notebook_export_json(self): with TemporaryDirectory() as td: @@ -632,39 +626,36 @@ class NotebookExportMagicTests(TestCase): _ip.ex(py3compat.u_format(u"u = {u}'héllo'")) _ip.magic("notebook -e %s" % outfile) - def test_notebook_export_py(self): - with TemporaryDirectory() as td: - outfile = os.path.join(td, "nb.py") - _ip.ex(py3compat.u_format(u"u = {u}'héllo'")) - _ip.magic("notebook -e %s" % outfile) - def test_notebook_reformat_py(self): - from IPython.nbformat.v3.tests.nbexamples import nb0 - from IPython.nbformat import current - with TemporaryDirectory() as td: - infile = os.path.join(td, "nb.ipynb") - with io.open(infile, 'w', encoding='utf-8') as f: - current.write(nb0, f, 'json') +class TestEnv(TestCase): - _ip.ex(py3compat.u_format(u"u = {u}'héllo'")) - _ip.magic("notebook -f py %s" % infile) + def test_env(self): + env = _ip.magic("env") + self.assertTrue(isinstance(env, dict)) - def test_notebook_reformat_json(self): - from IPython.nbformat.v3.tests.nbexamples import nb0 - from IPython.nbformat import current - with TemporaryDirectory() as td: - infile = os.path.join(td, "nb.py") - with io.open(infile, 'w', encoding='utf-8') as f: - current.write(nb0, f, 'py') + def test_env_get_set_simple(self): + env = _ip.magic("env var val1") + self.assertEqual(env, None) + self.assertEqual(os.environ['var'], 'val1') + self.assertEqual(_ip.magic("env var"), 'val1') + env = _ip.magic("env var=val2") + self.assertEqual(env, None) + self.assertEqual(os.environ['var'], 'val2') - _ip.ex(py3compat.u_format(u"u = {u}'héllo'")) - _ip.magic("notebook -f ipynb %s" % infile) - _ip.magic("notebook -f json %s" % infile) + def test_env_get_set_complex(self): + env = _ip.magic("env var 'val1 '' 'val2") + self.assertEqual(env, None) + self.assertEqual(os.environ['var'], "'val1 '' 'val2") + self.assertEqual(_ip.magic("env var"), "'val1 '' 'val2") + env = _ip.magic('env var=val2 val3="val4') + self.assertEqual(env, None) + self.assertEqual(os.environ['var'], 'val2 val3="val4') + def test_env_set_bad_input(self): + self.assertRaises(UsageError, lambda: _ip.magic("set_env var")) -def test_env(): - env = _ip.magic("env") - assert isinstance(env, dict), type(env) + def test_env_set_whitespace(self): + self.assertRaises(UsageError, lambda: _ip.magic("env var A=B")) class CellMagicTestCase(TestCase): diff --git a/IPython/core/tests/test_run.py b/IPython/core/tests/test_run.py index 2816e36..2316a3d 100644 --- a/IPython/core/tests/test_run.py +++ b/IPython/core/tests/test_run.py @@ -7,11 +7,12 @@ 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. """ + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + from __future__ import absolute_import -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- import functools import os @@ -32,9 +33,6 @@ from IPython.utils.io import capture_output from IPython.utils.tempdir import TemporaryDirectory from IPython.core import debugger -#----------------------------------------------------------------------------- -# Test functions begin -#----------------------------------------------------------------------------- def doctest_refbug(): """Very nasty problem with references held by multiple runs of a script. @@ -372,19 +370,17 @@ tclass.py: deleting object: C-third with tt.AssertNotPrints('SystemExit'): _ip.magic('run -e %s' % self.fname) - @dec.skip_without('IPython.nbformat.current') # Requires jsonschema + @dec.skip_without('IPython.nbformat') # Requires jsonschema def test_run_nb(self): """Test %run notebook.ipynb""" - from IPython.nbformat import current - nb = current.new_notebook( - worksheets=[ - current.new_worksheet(cells=[ - current.new_text_cell("The Ultimate Question of Everything"), - current.new_code_cell("answer=42") - ]) + from IPython.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 = current.writes(nb, 'json') + src = writes(nb, version=4) self.mktmp(src, ext='.ipynb') _ip.magic("run %s" % self.fname) diff --git a/IPython/core/tests/test_shellapp.py b/IPython/core/tests/test_shellapp.py index 5ca5252..197e828 100644 --- a/IPython/core/tests/test_shellapp.py +++ b/IPython/core/tests/test_shellapp.py @@ -19,6 +19,11 @@ import unittest from IPython.testing import decorators as dec from IPython.testing import tools as tt +from IPython.utils.py3compat import PY3 + +sqlite_err_maybe = dec.module_not_available('sqlite3') +SQLITE_NOT_AVAILABLE_ERROR = ('WARNING: 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.""" @@ -28,10 +33,7 @@ class TestFileToRun(unittest.TestCase, tt.TempFileMixin): src = "print(__file__)\n" self.mktmp(src) - if dec.module_not_available('sqlite3'): - err = 'WARNING: IPython History requires SQLite, your history will not be saved\n' - else: - err = None + 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): @@ -39,11 +41,28 @@ class TestFileToRun(unittest.TestCase, tt.TempFileMixin): src = "print(__file__)\n" self.mktmp(src, ext='.ipy') - if dec.module_not_available('sqlite3'): - err = 'WARNING: IPython History requires SQLite, your history will not be saved\n' - else: - err = None + err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None tt.ipexec_validate(self.fname, self.fname, err) - # Ideally we would also test that `__file__` is not set in the - # interactive namespace after running `ipython -i `. + # 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) + + err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None + tt.ipexec_validate(self.fname, 'False', err, options=['-i'], + commands=['"__file__" in globals()', 'exit()']) + + @dec.skip_win32 + @dec.skipif(PY3) + def test_py_script_file_compiler_directive(self): + """Test `__future__` compiler directives with `ipython -i file.py`""" + src = "from __future__ import division\n" + self.mktmp(src) + + err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None + tt.ipexec_validate(self.fname, 'float', err, options=['-i'], + commands=['type(1/2)', 'exit()']) diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py index 5fa0113..eedc2e6 100644 --- a/IPython/core/ultratb.py +++ b/IPython/core/ultratb.py @@ -722,15 +722,23 @@ class VerboseTB(TBTools): #print '*** record:',file,lnum,func,lines,index # dbg if not file: file = '?' - elif not (file.startswith(str("<")) and file.endswith(str(">"))): - # Guess that filenames like aren't real filenames, so - # don't call abspath on them. - try: - file = abspath(file) - except OSError: - # Not sure if this can still happen: abspath now works with - # file names like - pass + elif file.startswith(str("<")) and file.endswith(str(">")): + # Not a real filename, no problem... + pass + elif not os.path.isabs(file): + # Try to make the filename absolute by trying all + # sys.path entries (which is also what linecache does) + for dirname in sys.path: + try: + fullname = os.path.join(dirname, file) + if os.path.isfile(fullname): + file = os.path.abspath(fullname) + break + except Exception: + # Just in case that sys.path contains very + # strange entries... + pass + file = py3compat.cast_unicode(file, util_path.fs_encoding) link = tpl_link % file args, varargs, varkw, locals = inspect.getargvalues(frame) diff --git a/IPython/core/usage.py b/IPython/core/usage.py index bb05a64..9d73c82 100644 --- a/IPython/core/usage.py +++ b/IPython/core/usage.py @@ -103,11 +103,6 @@ MAIN FEATURES If you just want to see an object's docstring, type '%pdoc object' (without quotes, and without % if you have automagic on). - Both %pdoc and ?/?? give you access to documentation even on things which are - not explicitely defined. Try for example typing {}.get? or after import os, - type os.path.abspath??. The magic functions %pdef, %source and %file operate - similarly. - * Completion in the local namespace, by typing TAB at the prompt. At any time, hitting tab will complete any available python commands or diff --git a/IPython/extensions/autoreload.py b/IPython/extensions/autoreload.py index 0b65758..8459dc7 100644 --- a/IPython/extensions/autoreload.py +++ b/IPython/extensions/autoreload.py @@ -183,7 +183,7 @@ class ModuleReloader(object): return top_module, top_name def filename_and_mtime(self, module): - if not hasattr(module, '__file__'): + if not hasattr(module, '__file__') or module.__file__ is None: return None, None if module.__name__ == '__main__': diff --git a/IPython/extensions/cythonmagic.py b/IPython/extensions/cythonmagic.py index 2773afd..f1ea6a4 100644 --- a/IPython/extensions/cythonmagic.py +++ b/IPython/extensions/cythonmagic.py @@ -1,37 +1,10 @@ # -*- coding: utf-8 -*- """ -===================== -Cython related magics -===================== +The cython magic has been integrated into Cython itself, +which is now released in version 0.21. -Magic command interface for interactive work with Cython - -.. note:: - - The ``Cython`` package needs to be installed separately. It - can be obtained using ``easy_install`` or ``pip``. - -Usage -===== - -To enable the magics below, execute ``%load_ext cythonmagic``. - -``%%cython`` - -{CYTHON_DOC} - -``%%cython_inline`` - -{CYTHON_INLINE_DOC} - -``%%cython_pyximport`` - -{CYTHON_PYXIMPORT_DOC} - -Author: -* Brian Granger - -Parts of this code were taken from Cython.inline. +cf github `Cython` organisation, `Cython` repo, under the +file `Cython/Build/IpythonMagic.py` """ #----------------------------------------------------------------------------- # Copyright (C) 2010-2011, IPython Development Team. @@ -43,303 +16,28 @@ Parts of this code were taken from Cython.inline. from __future__ import print_function -import imp -import io -import os -import re -import sys -import time +import IPython.utils.version as version try: - reload -except NameError: # Python 3 - from imp import reload + import Cython +except: + Cython = None try: - import hashlib -except ImportError: - import md5 as hashlib - -from distutils.core import Distribution, Extension -from distutils.command.build_ext import build_ext - -from IPython.core import display -from IPython.core import magic_arguments -from IPython.core.magic import Magics, magics_class, cell_magic -from IPython.utils import py3compat -from IPython.utils.path import get_ipython_cache_dir -from IPython.utils.text import dedent - -import Cython -from Cython.Compiler.Errors import CompileError -from Cython.Build.Dependencies import cythonize - - -@magics_class -class CythonMagics(Magics): - - def __init__(self, shell): - super(CythonMagics,self).__init__(shell) - self._reloads = {} - self._code_cache = {} - - def _import_all(self, module): - for k,v in module.__dict__.items(): - if not k.startswith('__'): - self.shell.push({k:v}) - - @cell_magic - def cython_inline(self, line, cell): - """Compile and run a Cython code cell using Cython.inline. - - This magic simply passes the body of the cell to Cython.inline - and returns the result. If the variables `a` and `b` are defined - in the user's namespace, here is a simple example that returns - their sum:: - - %%cython_inline - return a+b - - For most purposes, we recommend the usage of the `%%cython` magic. - """ - locs = self.shell.user_global_ns - globs = self.shell.user_ns - return Cython.inline(cell, locals=locs, globals=globs) - - @cell_magic - def cython_pyximport(self, line, cell): - """Compile and import a Cython code cell using pyximport. + from Cython.Build.IpythonMagic import CythonMagics +except : + pass - The contents of the cell are written to a `.pyx` file in the current - working directory, which is then imported using `pyximport`. This - magic requires a module name to be passed:: - - %%cython_pyximport modulename - def f(x): - return 2.0*x - - The compiled module is then imported and all of its symbols are - injected into the user's namespace. For most purposes, we recommend - the usage of the `%%cython` magic. - """ - module_name = line.strip() - if not module_name: - raise ValueError('module name must be given') - fname = module_name + '.pyx' - with io.open(fname, 'w', encoding='utf-8') as f: - f.write(cell) - if 'pyximport' not in sys.modules: - import pyximport - pyximport.install(reload_support=True) - if module_name in self._reloads: - module = self._reloads[module_name] - reload(module) - else: - __import__(module_name) - module = sys.modules[module_name] - self._reloads[module_name] = module - self._import_all(module) - - @magic_arguments.magic_arguments() - @magic_arguments.argument( - '-c', '--compile-args', action='append', default=[], - help="Extra flags to pass to compiler via the `extra_compile_args` " - "Extension flag (can be specified multiple times)." - ) - @magic_arguments.argument( - '--link-args', action='append', default=[], - help="Extra flags to pass to linker via the `extra_link_args` " - "Extension flag (can be specified multiple times)." - ) - @magic_arguments.argument( - '-l', '--lib', action='append', default=[], - help="Add a library to link the extension against (can be specified " - "multiple times)." - ) - @magic_arguments.argument( - '-n', '--name', - help="Specify a name for the Cython module." - ) - @magic_arguments.argument( - '-L', dest='library_dirs', metavar='dir', action='append', default=[], - help="Add a path to the list of libary directories (can be specified " - "multiple times)." - ) - @magic_arguments.argument( - '-I', '--include', action='append', default=[], - help="Add a path to the list of include directories (can be specified " - "multiple times)." - ) - @magic_arguments.argument( - '-+', '--cplus', action='store_true', default=False, - help="Output a C++ rather than C file." - ) - @magic_arguments.argument( - '-f', '--force', action='store_true', default=False, - help="Force the compilation of a new module, even if the source has been " - "previously compiled." - ) - @magic_arguments.argument( - '-a', '--annotate', action='store_true', default=False, - help="Produce a colorized HTML version of the source." - ) - @cell_magic - def cython(self, line, cell): - """Compile and import everything from a Cython code cell. - - The contents of the cell are written to a `.pyx` file in the - directory `IPYTHONDIR/cython` using a filename with the hash of the - code. This file is then cythonized and compiled. The resulting module - is imported and all of its symbols are injected into the user's - namespace. The usage is similar to that of `%%cython_pyximport` but - you don't have to pass a module name:: - - %%cython - def f(x): - return 2.0*x - - To compile OpenMP codes, pass the required `--compile-args` - and `--link-args`. For example with gcc:: - - %%cython --compile-args=-fopenmp --link-args=-fopenmp - ... - """ - args = magic_arguments.parse_argstring(self.cython, line) - code = cell if cell.endswith('\n') else cell+'\n' - lib_dir = os.path.join(get_ipython_cache_dir(), 'cython') - quiet = True - key = code, sys.version_info, sys.executable, Cython.__version__ - - if not os.path.exists(lib_dir): - os.makedirs(lib_dir) - - if args.force: - # Force a new module name by adding the current time to the - # key which is hashed to determine the module name. - key += time.time(), - - if args.name: - module_name = py3compat.unicode_to_str(args.name) - else: - module_name = "_cython_magic_" + hashlib.md5(str(key).encode('utf-8')).hexdigest() - module_path = os.path.join(lib_dir, module_name + self.so_ext) - - have_module = os.path.isfile(module_path) - need_cythonize = not have_module - - if args.annotate: - html_file = os.path.join(lib_dir, module_name + '.html') - if not os.path.isfile(html_file): - need_cythonize = True - - if need_cythonize: - c_include_dirs = args.include - if 'numpy' in code: - import numpy - c_include_dirs.append(numpy.get_include()) - pyx_file = os.path.join(lib_dir, module_name + '.pyx') - pyx_file = py3compat.cast_bytes_py2(pyx_file, encoding=sys.getfilesystemencoding()) - with io.open(pyx_file, 'w', encoding='utf-8') as f: - f.write(code) - extension = Extension( - name = module_name, - sources = [pyx_file], - include_dirs = c_include_dirs, - library_dirs = args.library_dirs, - extra_compile_args = args.compile_args, - extra_link_args = args.link_args, - libraries = args.lib, - language = 'c++' if args.cplus else 'c', - ) - build_extension = self._get_build_extension() - try: - opts = dict( - quiet=quiet, - annotate = args.annotate, - force = True, - ) - build_extension.extensions = cythonize([extension], **opts) - except CompileError: - return - - if not have_module: - build_extension.build_temp = os.path.dirname(pyx_file) - build_extension.build_lib = lib_dir - build_extension.run() - self._code_cache[key] = module_name - - module = imp.load_dynamic(module_name, module_path) - self._import_all(module) - - if args.annotate: - try: - with io.open(html_file, encoding='utf-8') as f: - annotated_html = f.read() - except IOError as e: - # File could not be opened. Most likely the user has a version - # of Cython before 0.15.1 (when `cythonize` learned the - # `force` keyword argument) and has already compiled this - # exact source without annotation. - print('Cython completed successfully but the annotated ' - 'source could not be read.', file=sys.stderr) - print(e, file=sys.stderr) - else: - return display.HTML(self.clean_annotated_html(annotated_html)) - - @property - def so_ext(self): - """The extension suffix for compiled modules.""" - try: - return self._so_ext - except AttributeError: - self._so_ext = self._get_build_extension().get_ext_filename('') - return self._so_ext - - def _clear_distutils_mkpath_cache(self): - """clear distutils mkpath cache - - prevents distutils from skipping re-creation of dirs that have been removed - """ - try: - from distutils.dir_util import _path_created - except ImportError: - pass - else: - _path_created.clear() - - def _get_build_extension(self): - self._clear_distutils_mkpath_cache() - dist = Distribution() - config_files = dist.find_config_files() - try: - config_files.remove('setup.cfg') - except ValueError: - pass - dist.parse_config_files(config_files) - build_extension = build_ext(dist) - build_extension.finalize_options() - return build_extension - - @staticmethod - def clean_annotated_html(html): - """Clean up the annotated HTML source. - - Strips the link to the generated C or C++ file, which we do not - present to the user. - """ - r = re.compile('

Raw output: (.*)') - html = '\n'.join(l for l in html.splitlines() if not r.match(l)) - return html - -__doc__ = __doc__.format( - # rST doesn't see the -+ flag as part of an option list, so we - # hide it from the module-level docstring. - CYTHON_DOC = dedent(CythonMagics.cython.__doc__\ - .replace('-+, --cplus','--cplus ')), - CYTHON_INLINE_DOC = dedent(CythonMagics.cython_inline.__doc__), - CYTHON_PYXIMPORT_DOC = dedent(CythonMagics.cython_pyximport.__doc__), -) +## still load the magic in IPython 3.x, remove completely in future versions. def load_ipython_extension(ip): """Load the extension in IPython.""" - ip.register_magics(CythonMagics) + + print("""The Cython magic has been move to the Cython package, hence """) + print("""`%load_ext cythonmagic` is deprecated; Please use `%load_ext Cython` instead.""") + + if Cython is None or not version.check_version(Cython.__version__, "0.21"): + print("You need Cython version >=0.21 to use the Cython magic") + return + print("""\nThough, because I am nice, I'll still try to load it for you this time.""") + Cython.load_ipython_extension(ip) diff --git a/IPython/extensions/tests/test_cythonmagic.py b/IPython/extensions/tests/test_cythonmagic.py deleted file mode 100644 index 41dea38..0000000 --- a/IPython/extensions/tests/test_cythonmagic.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -"""Tests for the Cython magics extension.""" - -import os -import nose.tools as nt - -from IPython.testing import decorators as dec -from IPython.utils import py3compat - -code = py3compat.str_to_unicode("""def f(x): - return 2*x -""") - -try: - import Cython -except: - __test__ = False - -ip = get_ipython() - - -def setup(): - ip.extension_manager.load_extension('cythonmagic') - - -def test_cython_inline(): - ip.ex('a=10; b=20') - result = ip.run_cell_magic('cython_inline','','return a+b') - nt.assert_equal(result, 30) - - -@dec.skip_win32 -def test_cython_pyximport(): - module_name = '_test_cython_pyximport' - ip.run_cell_magic('cython_pyximport', module_name, code) - ip.ex('g = f(10)') - nt.assert_equal(ip.user_ns['g'], 20.0) - ip.run_cell_magic('cython_pyximport', module_name, code) - ip.ex('h = f(-10)') - nt.assert_equal(ip.user_ns['h'], -20.0) - try: - os.remove(module_name+'.pyx') - except OSError: - pass - - -def test_cython(): - ip.run_cell_magic('cython', '', code) - ip.ex('g = f(10)') - nt.assert_equal(ip.user_ns['g'], 20.0) - - -def test_cython_name(): - # The Cython module named 'mymodule' defines the function f. - ip.run_cell_magic('cython', '--name=mymodule', code) - # This module can now be imported in the interactive namespace. - ip.ex('import mymodule; g = mymodule.f(10)') - nt.assert_equal(ip.user_ns['g'], 20.0) - - -@dec.skip_win32 -def test_extlibs(): - code = py3compat.str_to_unicode(""" -from libc.math cimport sin -x = sin(0.0) - """) - ip.user_ns['x'] = 1 - ip.run_cell_magic('cython', '-l m', code) - nt.assert_equal(ip.user_ns['x'], 0) - diff --git a/IPython/external/mathjax.py b/IPython/external/mathjax.py index a2a705c..2371aea 100644 --- a/IPython/external/mathjax.py +++ b/IPython/external/mathjax.py @@ -132,7 +132,6 @@ def extract_zip(fd, dest): z.extractall(parent) # it will be mathjax-MathJax-, rename to just mathjax - d = os.path.join(parent, topdir) os.rename(os.path.join(parent, topdir), dest) diff --git a/IPython/external/qt_loaders.py b/IPython/external/qt_loaders.py index 7cb90d8..34c7be8 100644 --- a/IPython/external/qt_loaders.py +++ b/IPython/external/qt_loaders.py @@ -57,11 +57,11 @@ def commit_api(api): if api == QT_API_PYSIDE: ID.forbid('PyQt4') ID.forbid('PyQt5') - elif api == QT_API_PYQT: + elif api == QT_API_PYQT5: ID.forbid('PySide') - ID.forbid('PyQt5') - else: ID.forbid('PyQt4') + else: # There are three other possibilities, all representing PyQt4 + ID.forbid('PyQt5') ID.forbid('PySide') @@ -241,7 +241,7 @@ def load_qt(api_options): ---------- api_options: List of strings The order of APIs to try. Valid items are 'pyside', - 'pyqt', 'pyqt5' and 'pyqtv1' + 'pyqt', 'pyqt5', 'pyqtv1' and 'pyqtdefault' Returns ------- diff --git a/IPython/html/README.md b/IPython/html/README.md index e354f71..892cb82 100644 --- a/IPython/html/README.md +++ b/IPython/html/README.md @@ -4,10 +4,9 @@ Developers of the IPython Notebook will need to install the following tools: -* fabric +* invoke * node.js * less (`npm install -g less`) -* bower (`npm install -g bower`) ## Components @@ -15,14 +14,13 @@ We are moving to a model where our JavaScript dependencies are managed using [bower](http://bower.io/). These packages are installed in `static/components` and committed into a separate git repo [ipython/ipython-components](ipython/ipython-components). Our dependencies are described in the file -`static/components/bower.json`. To update our bower packages, run `fab update` +`static/components/bower.json`. To update our bower packages, run `bower install` in this directory. ## less If you edit our `.less` files you will need to run the less compiler to build -our minified css files. This can be done by running `fab css` from this directory, -or `python setup.py css` from the root of the repository. +our minified css files. This can be done by running `python setup.py css` from the root of the repository. If you are working frequently with `.less` files please consider installing git hooks that rebuild the css files and corresponding maps in `${RepoRoot}/git-hooks/install-hooks.sh`. diff --git a/IPython/html/__init__.py b/IPython/html/__init__.py index 97e2c98..a58d482 100644 --- a/IPython/html/__init__.py +++ b/IPython/html/__init__.py @@ -4,6 +4,22 @@ import os # Packagers: modify this line if you store the notebook static files elsewhere DEFAULT_STATIC_FILES_PATH = os.path.join(os.path.dirname(__file__), "static") +# Packagers: modify the next line if you store the notebook template files +# elsewhere + +# Include both IPython/html/ and IPython/html/templates/. This makes it +# possible for users to override a template with a file that inherits from that +# template. +# +# For example, if you want to override a specific block of notebook.html, you +# can create a file called notebook.html that inherits from +# templates/notebook.html, and the latter will resolve correctly to the base +# implementation. +DEFAULT_TEMPLATE_PATH_LIST = [ + os.path.dirname(__file__), + os.path.join(os.path.dirname(__file__), "templates"), +] + del os -from .nbextensions import install_nbextension \ No newline at end of file +from .nbextensions import install_nbextension diff --git a/IPython/html/allow76.py b/IPython/html/allow76.py new file mode 100644 index 0000000..67e87e8 --- /dev/null +++ b/IPython/html/allow76.py @@ -0,0 +1,311 @@ +"""WebsocketProtocol76 from tornado 3.2.2 for tornado >= 4.0 + +The contents of this file are Copyright (c) Tornado +Used under the Apache 2.0 license +""" + + +from __future__ import absolute_import, division, print_function, with_statement +# Author: Jacob Kristhammar, 2010 + +import functools +import hashlib +import struct +import time +import tornado.escape +import tornado.web + +from tornado.log import gen_log, app_log +from tornado.util import bytes_type, unicode_type + +from tornado.websocket import WebSocketHandler, WebSocketProtocol13 + +class AllowDraftWebSocketHandler(WebSocketHandler): + """Restore Draft76 support for tornado 4 + + Remove when we can run tests without phantomjs + qt4 + """ + + # get is unmodified except between the BEGIN/END PATCH lines + @tornado.web.asynchronous + def get(self, *args, **kwargs): + self.open_args = args + self.open_kwargs = kwargs + + # Upgrade header should be present and should be equal to WebSocket + if self.request.headers.get("Upgrade", "").lower() != 'websocket': + self.set_status(400) + self.finish("Can \"Upgrade\" only to \"WebSocket\".") + return + + # Connection header should be upgrade. Some proxy servers/load balancers + # might mess with it. + headers = self.request.headers + connection = map(lambda s: s.strip().lower(), headers.get("Connection", "").split(",")) + if 'upgrade' not in connection: + self.set_status(400) + self.finish("\"Connection\" must be \"Upgrade\".") + return + + # Handle WebSocket Origin naming convention differences + # The difference between version 8 and 13 is that in 8 the + # client sends a "Sec-Websocket-Origin" header and in 13 it's + # simply "Origin". + if "Origin" in self.request.headers: + origin = self.request.headers.get("Origin") + else: + origin = self.request.headers.get("Sec-Websocket-Origin", None) + + + # If there was an origin header, check to make sure it matches + # according to check_origin. When the origin is None, we assume it + # did not come from a browser and that it can be passed on. + if origin is not None and not self.check_origin(origin): + self.set_status(403) + self.finish("Cross origin websockets not allowed") + return + + self.stream = self.request.connection.detach() + self.stream.set_close_callback(self.on_connection_close) + + if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8", "13"): + self.ws_connection = WebSocketProtocol13(self) + self.ws_connection.accept_connection() + #--------------- BEGIN PATCH ---------------- + elif (self.allow_draft76() and + "Sec-WebSocket-Version" not in self.request.headers): + self.ws_connection = WebSocketProtocol76(self) + self.ws_connection.accept_connection() + #--------------- END PATCH ---------------- + else: + if not self.stream.closed(): + self.stream.write(tornado.escape.utf8( + "HTTP/1.1 426 Upgrade Required\r\n" + "Sec-WebSocket-Version: 8\r\n\r\n")) + self.stream.close() + + # 3.2 methods removed in 4.0: + def allow_draft76(self): + """Using this class allows draft76 connections by default""" + return True + + def get_websocket_scheme(self): + """Return the url scheme used for this request, either "ws" or "wss". + This is normally decided by HTTPServer, but applications + may wish to override this if they are using an SSL proxy + that does not provide the X-Scheme header as understood + by HTTPServer. + Note that this is only used by the draft76 protocol. + """ + return "wss" if self.request.protocol == "https" else "ws" + + + +# No modifications from tornado-3.2.2 below this line + +class WebSocketProtocol(object): + """Base class for WebSocket protocol versions. + """ + def __init__(self, handler): + self.handler = handler + self.request = handler.request + self.stream = handler.stream + self.client_terminated = False + self.server_terminated = False + + def async_callback(self, callback, *args, **kwargs): + """Wrap callbacks with this if they are used on asynchronous requests. + + Catches exceptions properly and closes this WebSocket if an exception + is uncaught. + """ + if args or kwargs: + callback = functools.partial(callback, *args, **kwargs) + + def wrapper(*args, **kwargs): + try: + return callback(*args, **kwargs) + except Exception: + app_log.error("Uncaught exception in %s", + self.request.path, exc_info=True) + self._abort() + return wrapper + + def on_connection_close(self): + self._abort() + + def _abort(self): + """Instantly aborts the WebSocket connection by closing the socket""" + self.client_terminated = True + self.server_terminated = True + self.stream.close() # forcibly tear down the connection + self.close() # let the subclass cleanup + + +class WebSocketProtocol76(WebSocketProtocol): + """Implementation of the WebSockets protocol, version hixie-76. + + This class provides basic functionality to process WebSockets requests as + specified in + http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 + """ + def __init__(self, handler): + WebSocketProtocol.__init__(self, handler) + self.challenge = None + self._waiting = None + + def accept_connection(self): + try: + self._handle_websocket_headers() + except ValueError: + gen_log.debug("Malformed WebSocket request received") + self._abort() + return + + scheme = self.handler.get_websocket_scheme() + + # draft76 only allows a single subprotocol + subprotocol_header = '' + subprotocol = self.request.headers.get("Sec-WebSocket-Protocol", None) + if subprotocol: + selected = self.handler.select_subprotocol([subprotocol]) + if selected: + assert selected == subprotocol + subprotocol_header = "Sec-WebSocket-Protocol: %s\r\n" % selected + + # Write the initial headers before attempting to read the challenge. + # This is necessary when using proxies (such as HAProxy), which + # need to see the Upgrade headers before passing through the + # non-HTTP traffic that follows. + self.stream.write(tornado.escape.utf8( + "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" + "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n" + "Server: TornadoServer/%(version)s\r\n" + "Sec-WebSocket-Origin: %(origin)s\r\n" + "Sec-WebSocket-Location: %(scheme)s://%(host)s%(uri)s\r\n" + "%(subprotocol)s" + "\r\n" % (dict( + version=tornado.version, + origin=self.request.headers["Origin"], + scheme=scheme, + host=self.request.host, + uri=self.request.uri, + subprotocol=subprotocol_header)))) + self.stream.read_bytes(8, self._handle_challenge) + + def challenge_response(self, challenge): + """Generates the challenge response that's needed in the handshake + + The challenge parameter should be the raw bytes as sent from the + client. + """ + key_1 = self.request.headers.get("Sec-Websocket-Key1") + key_2 = self.request.headers.get("Sec-Websocket-Key2") + try: + part_1 = self._calculate_part(key_1) + part_2 = self._calculate_part(key_2) + except ValueError: + raise ValueError("Invalid Keys/Challenge") + return self._generate_challenge_response(part_1, part_2, challenge) + + def _handle_challenge(self, challenge): + try: + challenge_response = self.challenge_response(challenge) + except ValueError: + gen_log.debug("Malformed key data in WebSocket request") + self._abort() + return + self._write_response(challenge_response) + + def _write_response(self, challenge): + self.stream.write(challenge) + self.async_callback(self.handler.open)(*self.handler.open_args, **self.handler.open_kwargs) + self._receive_message() + + def _handle_websocket_headers(self): + """Verifies all invariant- and required headers + + If a header is missing or have an incorrect value ValueError will be + raised + """ + fields = ("Origin", "Host", "Sec-Websocket-Key1", + "Sec-Websocket-Key2") + if not all(map(lambda f: self.request.headers.get(f), fields)): + raise ValueError("Missing/Invalid WebSocket headers") + + def _calculate_part(self, key): + """Processes the key headers and calculates their key value. + + Raises ValueError when feed invalid key.""" + # pyflakes complains about variable reuse if both of these lines use 'c' + number = int(''.join(c for c in key if c.isdigit())) + spaces = len([c2 for c2 in key if c2.isspace()]) + try: + key_number = number // spaces + except (ValueError, ZeroDivisionError): + raise ValueError + return struct.pack(">I", key_number) + + def _generate_challenge_response(self, part_1, part_2, part_3): + m = hashlib.md5() + m.update(part_1) + m.update(part_2) + m.update(part_3) + return m.digest() + + def _receive_message(self): + self.stream.read_bytes(1, self._on_frame_type) + + def _on_frame_type(self, byte): + frame_type = ord(byte) + if frame_type == 0x00: + self.stream.read_until(b"\xff", self._on_end_delimiter) + elif frame_type == 0xff: + self.stream.read_bytes(1, self._on_length_indicator) + else: + self._abort() + + def _on_end_delimiter(self, frame): + if not self.client_terminated: + self.async_callback(self.handler.on_message)( + frame[:-1].decode("utf-8", "replace")) + if not self.client_terminated: + self._receive_message() + + def _on_length_indicator(self, byte): + if ord(byte) != 0x00: + self._abort() + return + self.client_terminated = True + self.close() + + def write_message(self, message, binary=False): + """Sends the given message to the client of this Web Socket.""" + if binary: + raise ValueError( + "Binary messages not supported by this version of websockets") + if isinstance(message, unicode_type): + message = message.encode("utf-8") + assert isinstance(message, bytes_type) + self.stream.write(b"\x00" + message + b"\xff") + + def write_ping(self, data): + """Send ping frame.""" + raise ValueError("Ping messages not supported by this version of websockets") + + def close(self): + """Closes the WebSocket connection.""" + if not self.server_terminated: + if not self.stream.closed(): + self.stream.write("\xff\x00") + self.server_terminated = True + if self.client_terminated: + if self._waiting is not None: + self.stream.io_loop.remove_timeout(self._waiting) + self._waiting = None + self.stream.close() + elif self._waiting is None: + self._waiting = self.stream.io_loop.add_timeout( + time.time() + 5, self._abort) + diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py index 07bc059..fa58c2b 100644 --- a/IPython/html/base/handlers.py +++ b/IPython/html/base/handlers.py @@ -24,33 +24,45 @@ try: except ImportError: app_log = logging.getLogger() +import IPython +from IPython.utils.sysinfo import get_sys_info + from IPython.config import Application from IPython.utils.path import filefind from IPython.utils.py3compat import string_types from IPython.html.utils import is_hidden, url_path_join, url_escape +from IPython.html.services.security import csp_report_uri + #----------------------------------------------------------------------------- # Top-level handlers #----------------------------------------------------------------------------- non_alphanum = re.compile(r'[^A-Za-z0-9]') +sys_info = json.dumps(get_sys_info()) + class AuthenticatedHandler(web.RequestHandler): """A RequestHandler with an authenticated user.""" def set_default_headers(self): headers = self.settings.get('headers', {}) - if "X-Frame-Options" not in headers: - headers["X-Frame-Options"] = "SAMEORIGIN" + if "Content-Security-Policy" not in headers: + headers["Content-Security-Policy"] = ( + "frame-ancestors 'self'; " + # Make sure the report-uri is relative to the base_url + "report-uri " + url_path_join(self.base_url, csp_report_uri) + ";" + ) + # Allow for overriding headers for header_name,value in headers.items() : try: self.set_header(header_name, value) - except Exception: + except Exception as e: # tornado raise Exception (not a subclass) # if method is unsupported (websocket and Access-Control-Allow-Origin # for example, so just ignore) - pass + self.log.debug(e) def clear_login_cookie(self): self.clear_cookie(self.cookie_name) @@ -121,6 +133,11 @@ class IPythonHandler(AuthenticatedHandler): #--------------------------------------------------------------- @property + def version_hash(self): + """The version hash to use for cache hints for static files""" + return self.settings.get('version_hash', '') + + @property def mathjax_url(self): return self.settings.get('mathjax_url', '') @@ -131,6 +148,12 @@ class IPythonHandler(AuthenticatedHandler): @property def ws_url(self): return self.settings.get('websocket_url', '') + + @property + def contents_js_source(self): + self.log.debug("Using contents: %s", self.settings.get('contents_js_source', + 'services/contents')) + return self.settings.get('contents_js_source', 'services/contents') #--------------------------------------------------------------- # Manager objects @@ -153,9 +176,17 @@ class IPythonHandler(AuthenticatedHandler): return self.settings['session_manager'] @property + def terminal_manager(self): + return self.settings['terminal_manager'] + + @property def kernel_spec_manager(self): return self.settings['kernel_spec_manager'] + @property + def config_manager(self): + return self.settings['config_manager'] + #--------------------------------------------------------------- # CORS #--------------------------------------------------------------- @@ -219,6 +250,9 @@ class IPythonHandler(AuthenticatedHandler): logged_in=self.logged_in, login_available=self.login_available, static_url=self.static_url, + sys_info=sys_info, + contents_js_source=self.contents_js_source, + version_hash=self.version_hash, ) def get_json_body(self): @@ -285,12 +319,18 @@ class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler): @web.authenticated def get(self, path): if os.path.splitext(path)[1] == '.ipynb': - name = os.path.basename(path) + name = path.rsplit('/', 1)[-1] self.set_header('Content-Type', 'application/json') self.set_header('Content-Disposition','attachment; filename="%s"' % name) return web.StaticFileHandler.get(self, path) + def set_headers(self): + super(AuthenticatedFileHandler, self).set_headers() + # disable browser caching, rely on 304 replies for savings + if "v" not in self.request.arguments: + self.add_header("Cache-Control", "no-cache") + def compute_etag(self): return None @@ -359,7 +399,16 @@ class FileFindHandler(web.StaticFileHandler): # cache search results, don't search for files more than once _static_paths = {} - def initialize(self, path, default_filename=None): + def set_headers(self): + super(FileFindHandler, self).set_headers() + # disable browser caching, rely on 304 replies for savings + if "v" not in self.request.arguments or \ + any(self.request.path.startswith(path) for path in self.no_cache_paths): + self.add_header("Cache-Control", "no-cache") + + def initialize(self, path, default_filename=None, no_cache_paths=None): + self.no_cache_paths = no_cache_paths or [] + if isinstance(path, string_types): path = [path] @@ -398,43 +447,49 @@ class FileFindHandler(web.StaticFileHandler): return super(FileFindHandler, self).validate_absolute_path(root, absolute_path) +class ApiVersionHandler(IPythonHandler): + + @json_errors + def get(self): + # not authenticated, so give as few info as possible + self.finish(json.dumps({"version":IPython.__version__})) + + class TrailingSlashHandler(web.RequestHandler): """Simple redirect handler that strips trailing slashes This should be the first, highest priority handler. """ - SUPPORTED_METHODS = ['GET'] - def get(self): self.redirect(self.request.uri.rstrip('/')) + + post = put = get class FilesRedirectHandler(IPythonHandler): """Handler for redirecting relative URLs to the /files/ handler""" def get(self, path=''): cm = self.contents_manager - if cm.path_exists(path): + if cm.dir_exists(path): # it's a *directory*, redirect to /tree url = url_path_join(self.base_url, 'tree', path) else: orig_path = path # otherwise, redirect to /files parts = path.split('/') - path = '/'.join(parts[:-1]) - name = parts[-1] - if not cm.file_exists(name=name, path=path) and 'files' in parts: + if not cm.file_exists(path=path) and 'files' in parts: # redirect without files/ iff it would 404 # this preserves pre-2.0-style 'files/' links self.log.warn("Deprecated files/ URL: %s", orig_path) parts.remove('files') - path = '/'.join(parts[:-1]) + path = '/'.join(parts) - if not cm.file_exists(name=name, path=path): + if not cm.file_exists(path=path): raise web.HTTPError(404) - url = url_path_join(self.base_url, 'files', path, name) + url = url_path_join(self.base_url, 'files', path) url = url_escape(url) self.log.debug("Redirecting %s to %s", self.request.path, url) self.redirect(url) @@ -444,11 +499,9 @@ class FilesRedirectHandler(IPythonHandler): # URL pattern fragments for re-use #----------------------------------------------------------------------------- -path_regex = r"(?P(?:/.*)*)" -notebook_name_regex = r"(?P[^/]+\.ipynb)" -notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex) -file_name_regex = r"(?P[^/]+)" -file_path_regex = "%s/%s" % (path_regex, file_name_regex) +# path matches any number of `/foo[/bar...]` or just `/` or '' +path_regex = r"(?P(?:(?:/[^/]+)+|/?))" +notebook_path_regex = r"(?P(?:/[^/]+)+\.ipynb)" #----------------------------------------------------------------------------- # URL to handler mappings @@ -456,5 +509,6 @@ file_path_regex = "%s/%s" % (path_regex, file_name_regex) default_handlers = [ - (r".*/", TrailingSlashHandler) + (r".*/", TrailingSlashHandler), + (r"api", ApiVersionHandler) ] diff --git a/IPython/html/base/zmqhandlers.py b/IPython/html/base/zmqhandlers.py index f9f1eb1..7eec55b 100644 --- a/IPython/html/base/zmqhandlers.py +++ b/IPython/html/base/zmqhandlers.py @@ -1,34 +1,98 @@ +# coding: utf-8 """Tornado handlers for WebSocket <-> ZMQ sockets.""" # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import os import json +import struct +import warnings try: from urllib.parse import urlparse # Py 3 except ImportError: from urlparse import urlparse # Py 2 -try: - from http.cookies import SimpleCookie # Py 3 -except ImportError: - from Cookie import SimpleCookie # Py 2 -import logging - import tornado -from tornado import ioloop -from tornado import web -from tornado import websocket +from tornado import gen, ioloop, web +from tornado.websocket import WebSocketHandler from IPython.kernel.zmq.session import Session -from IPython.utils.jsonutil import date_default -from IPython.utils.py3compat import PY3, cast_unicode +from IPython.utils.jsonutil import date_default, extract_dates +from IPython.utils.py3compat import cast_unicode from .handlers import IPythonHandler +def serialize_binary_message(msg): + """serialize a message as a binary blob + + Header: + + 4 bytes: number of msg parts (nbufs) as 32b int + 4 * nbufs bytes: offset for each buffer as integer as 32b int + + Offsets are from the start of the buffer, including the header. + + Returns + ------- + + The message serialized to bytes. + + """ + # don't modify msg or buffer list in-place + msg = msg.copy() + buffers = list(msg.pop('buffers')) + bmsg = json.dumps(msg, default=date_default).encode('utf8') + buffers.insert(0, bmsg) + nbufs = len(buffers) + offsets = [4 * (nbufs + 1)] + for buf in buffers[:-1]: + offsets.append(offsets[-1] + len(buf)) + offsets_buf = struct.pack('!' + 'I' * (nbufs + 1), nbufs, *offsets) + buffers.insert(0, offsets_buf) + return b''.join(buffers) + + +def deserialize_binary_message(bmsg): + """deserialize a message from a binary blog + + Header: + + 4 bytes: number of msg parts (nbufs) as 32b int + 4 * nbufs bytes: offset for each buffer as integer as 32b int -class ZMQStreamHandler(websocket.WebSocketHandler): + Offsets are from the start of the buffer, including the header. + + Returns + ------- + + message dictionary + """ + nbufs = struct.unpack('!i', bmsg[:4])[0] + offsets = list(struct.unpack('!' + 'I' * nbufs, bmsg[4:4*(nbufs+1)])) + offsets.append(None) + bufs = [] + for start, stop in zip(offsets[:-1], offsets[1:]): + bufs.append(bmsg[start:stop]) + msg = json.loads(bufs[0].decode('utf8')) + msg['header'] = extract_dates(msg['header']) + msg['parent_header'] = extract_dates(msg['parent_header']) + msg['buffers'] = bufs[1:] + return msg + +# ping interval for keeping websockets alive (30 seconds) +WS_PING_INTERVAL = 30000 + +if os.environ.get('IPYTHON_ALLOW_DRAFT_WEBSOCKETS_FOR_PHANTOMJS', False): + warnings.warn("""Allowing draft76 websocket connections! + This should only be done for testing with phantomjs!""") + from IPython.html import allow76 + WebSocketHandler = allow76.AllowDraftWebSocketHandler + # draft 76 doesn't support ping + WS_PING_INTERVAL = 0 + +class ZMQStreamHandler(WebSocketHandler): def check_origin(self, origin): """Check Origin == Host or Access-Control-Allow-Origin. @@ -77,23 +141,19 @@ class ZMQStreamHandler(websocket.WebSocketHandler): def _reserialize_reply(self, msg_list): """Reserialize a reply message using JSON. - This takes the msg list from the ZMQ socket, unserializes it using + This takes the msg list from the ZMQ socket, deserializes it using self.session and then serializes the result using JSON. This method should be used by self._on_zmq_reply to build messages that can be sent back to the browser. """ idents, msg_list = self.session.feed_identities(msg_list) - msg = self.session.unserialize(msg_list) - try: - msg['header'].pop('date') - except KeyError: - pass - try: - msg['parent_header'].pop('date') - except KeyError: - pass - msg.pop('buffers') - return json.dumps(msg, default=date_default) + msg = self.session.deserialize(msg_list) + if msg['buffers']: + buf = serialize_binary_message(msg) + return buf + else: + smsg = json.dumps(msg, default=date_default) + return cast_unicode(smsg) def _on_zmq_reply(self, msg_list): # Sometimes this gets triggered when the on_close method is scheduled in the @@ -104,18 +164,7 @@ class ZMQStreamHandler(websocket.WebSocketHandler): except Exception: self.log.critical("Malformed message: %r" % msg_list, exc_info=True) else: - self.write_message(msg) - - def allow_draft76(self): - """Allow draft 76, until browsers such as Safari update to RFC 6455. - - This has been disabled by default in tornado in release 2.2.0, and - support will be removed in later versions. - """ - return True - -# ping interval for keeping websockets alive (30 seconds) -WS_PING_INTERVAL = 30000 + self.write_message(msg, binary=isinstance(msg, bytes)) class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler): ping_callback = None @@ -146,18 +195,37 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler): which doesn't make sense for websockets """ pass - - def open(self, kernel_id): - self.kernel_id = cast_unicode(kernel_id, 'ascii') - # Check to see that origin matches host directly, including ports - # Tornado 4 already does CORS checking - if tornado.version_info[0] < 4: - if not self.check_origin(self.get_origin()): - raise web.HTTPError(403) - + + def pre_get(self): + """Run before finishing the GET request + + Extend this method to add logic that should fire before + the websocket finishes completing. + """ + # authenticate the request before opening the websocket + if self.get_current_user() is None: + self.log.warn("Couldn't authenticate WebSocket connection") + raise web.HTTPError(403) + + if self.get_argument('session_id', False): + self.session.session = cast_unicode(self.get_argument('session_id')) + else: + self.log.warn("No session ID specified") + + @gen.coroutine + def get(self, *args, **kwargs): + # pre_get can be a coroutine in subclasses + # assign and yield in two step to avoid tornado 3 issues + res = self.pre_get() + yield gen.maybe_future(res) + super(AuthenticatedZMQStreamHandler, self).get(*args, **kwargs) + + def initialize(self): + self.log.debug("Initializing websocket connection %s", self.request.path) self.session = Session(config=self.config) - self.save_on_message = self.on_message - self.on_message = self.on_first_message + + def open(self, *args, **kwargs): + self.log.debug("Opening websocket %s", self.request.path) # start the pinging if self.ping_interval > 0: @@ -187,28 +255,3 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler): def on_pong(self, data): self.last_pong = ioloop.IOLoop.instance().time() - - def _inject_cookie_message(self, msg): - """Inject the first message, which is the document cookie, - for authentication.""" - if not PY3 and isinstance(msg, unicode): - # Cookie constructor doesn't accept unicode strings - # under Python 2.x for some reason - msg = msg.encode('utf8', 'replace') - try: - identity, msg = msg.split(':', 1) - self.session.session = cast_unicode(identity, 'ascii') - except Exception: - logging.error("First ws message didn't have the form 'identity:[cookie]' - %r", msg) - - try: - self.request._cookies = SimpleCookie(msg) - except: - self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True) - - def on_first_message(self, msg): - self._inject_cookie_message(msg) - if self.get_current_user() is None: - self.log.warn("Couldn't authenticate WebSocket connection") - raise web.HTTPError(403) - self.on_message = self.save_on_message diff --git a/IPython/html/edit/__init__.py b/IPython/html/edit/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/IPython/html/edit/__init__.py diff --git a/IPython/html/edit/handlers.py b/IPython/html/edit/handlers.py new file mode 100644 index 0000000..521bd2c --- /dev/null +++ b/IPython/html/edit/handlers.py @@ -0,0 +1,29 @@ +#encoding: utf-8 +"""Tornado handlers for the terminal emulator.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from tornado import web +from ..base.handlers import IPythonHandler, path_regex +from ..utils import url_escape + +class EditorHandler(IPythonHandler): + """Render the text editor interface.""" + @web.authenticated + def get(self, path): + path = path.strip('/') + if not self.contents_manager.file_exists(path): + raise web.HTTPError(404, u'File does not exist: %s' % path) + + basename = path.rsplit('/', 1)[-1] + self.write(self.render_template('edit.html', + file_path=url_escape(path), + basename=basename, + page_title=basename + " (editing)", + ) + ) + +default_handlers = [ + (r"/edit%s" % path_regex, EditorHandler), +] \ No newline at end of file diff --git a/IPython/html/files/__init__.py b/IPython/html/files/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/IPython/html/files/__init__.py diff --git a/IPython/html/files/handlers.py b/IPython/html/files/handlers.py new file mode 100644 index 0000000..e78374e --- /dev/null +++ b/IPython/html/files/handlers.py @@ -0,0 +1,54 @@ +"""Serve files directly from the ContentsManager.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import os +import mimetypes +import json +import base64 + +from tornado import web + +from IPython.html.base.handlers import IPythonHandler + +class FilesHandler(IPythonHandler): + """serve files via ContentsManager""" + + @web.authenticated + def get(self, path): + cm = self.contents_manager + if cm.is_hidden(path): + self.log.info("Refusing to serve hidden file, via 404 Error") + raise web.HTTPError(404) + + path = path.strip('/') + if '/' in path: + _, name = path.rsplit('/', 1) + else: + name = path + + model = cm.get(path) + + if self.get_argument("download", False): + self.set_header('Content-Disposition','attachment; filename="%s"' % name) + + if model['type'] == 'notebook': + self.set_header('Content-Type', 'application/json') + else: + cur_mime = mimetypes.guess_type(name)[0] + if cur_mime is not None: + self.set_header('Content-Type', cur_mime) + + if model['format'] == 'base64': + b64_bytes = model['content'].encode('ascii') + self.write(base64.decodestring(b64_bytes)) + elif model['format'] == 'json': + self.write(json.dumps(model['content'])) + else: + self.write(model['content']) + self.flush() + +default_handlers = [ + (r"/files/(.*)", FilesHandler), +] \ No newline at end of file diff --git a/IPython/html/nbconvert/handlers.py b/IPython/html/nbconvert/handlers.py index f6e1094..63ba266 100644 --- a/IPython/html/nbconvert/handlers.py +++ b/IPython/html/nbconvert/handlers.py @@ -13,7 +13,7 @@ from ..base.handlers import ( IPythonHandler, FilesRedirectHandler, notebook_path_regex, path_regex, ) -from IPython.nbformat.current import to_notebook_json +from IPython.nbformat import from_dict from IPython.utils.py3compat import cast_bytes @@ -43,7 +43,7 @@ def respond_zip(handler, name, output, resources): # Prepare the zip file buffer = io.BytesIO() zipf = zipfile.ZipFile(buffer, mode='w', compression=zipfile.ZIP_DEFLATED) - output_filename = os.path.splitext(name)[0] + '.' + resources['output_extension'] + output_filename = os.path.splitext(name)[0] + resources['output_extension'] zipf.writestr(output_filename, cast_bytes(output, 'utf-8')) for filename, data in output_files.items(): zipf.writestr(os.path.basename(filename), data) @@ -76,12 +76,13 @@ class NbconvertFileHandler(IPythonHandler): SUPPORTED_METHODS = ('GET',) @web.authenticated - def get(self, format, path='', name=None): + def get(self, format, path): exporter = get_exporter(format, config=self.config, log=self.log) path = path.strip('/') - model = self.contents_manager.get_model(name=name, path=path) + model = self.contents_manager.get(path=path) + name = model['name'] self.set_header('Last-Modified', model['last_modified']) @@ -95,7 +96,7 @@ class NbconvertFileHandler(IPythonHandler): # Force download if requested if self.get_argument('download', 'false').lower() == 'true': - filename = os.path.splitext(name)[0] + '.' + resources['output_extension'] + filename = os.path.splitext(name)[0] + resources['output_extension'] self.set_header('Content-Disposition', 'attachment; filename="%s"' % filename) @@ -109,19 +110,20 @@ class NbconvertFileHandler(IPythonHandler): class NbconvertPostHandler(IPythonHandler): SUPPORTED_METHODS = ('POST',) - @web.authenticated + @web.authenticated def post(self, format): exporter = get_exporter(format, config=self.config) model = self.get_json_body() - nbnode = to_notebook_json(model['content']) + name = model.get('name', 'notebook.ipynb') + nbnode = from_dict(model['content']) try: output, resources = exporter.from_notebook_node(nbnode) except Exception as e: raise web.HTTPError(500, "nbconvert failed: %s" % e) - if respond_zip(self, nbnode.metadata.name, output, resources): + if respond_zip(self, name, output, resources): return # MIME type diff --git a/IPython/html/nbconvert/tests/test_nbconvert_handlers.py b/IPython/html/nbconvert/tests/test_nbconvert_handlers.py index ea44217..88ff3a7 100644 --- a/IPython/html/nbconvert/tests/test_nbconvert_handlers.py +++ b/IPython/html/nbconvert/tests/test_nbconvert_handlers.py @@ -10,9 +10,10 @@ import requests from IPython.html.utils import url_path_join from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error -from IPython.nbformat.current import (new_notebook, write, new_worksheet, - new_heading_cell, new_code_cell, - new_output) +from IPython.nbformat import write +from IPython.nbformat.v4 import ( + new_notebook, new_markdown_cell, new_code_cell, new_output, +) from IPython.testing.decorators import onlyif_cmds_exist @@ -43,7 +44,8 @@ class NbconvertAPI(object): png_green_pixel = base64.encodestring(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00' b'\x00\x00\x01\x00\x00x00\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDAT' -b'\x08\xd7c\x90\xfb\xcf\x00\x00\x02\\\x01\x1e.~d\x87\x00\x00\x00\x00IEND\xaeB`\x82') +b'\x08\xd7c\x90\xfb\xcf\x00\x00\x02\\\x01\x1e.~d\x87\x00\x00\x00\x00IEND\xaeB`\x82' +).decode('ascii') class APITest(NotebookTestBase): def setUp(self): @@ -52,19 +54,20 @@ class APITest(NotebookTestBase): if not os.path.isdir(pjoin(nbdir, 'foo')): os.mkdir(pjoin(nbdir, 'foo')) - nb = new_notebook(name='testnb') + nb = new_notebook() - ws = new_worksheet() - nb.worksheets = [ws] - ws.cells.append(new_heading_cell(u'Created by test ³')) - cc1 = new_code_cell(input=u'print(2*6)') - cc1.outputs.append(new_output(output_text=u'12', output_type='stream')) - cc1.outputs.append(new_output(output_png=png_green_pixel, output_type='pyout')) - ws.cells.append(cc1) + nb.cells.append(new_markdown_cell(u'Created by test ³')) + cc1 = new_code_cell(source=u'print(2*6)') + cc1.outputs.append(new_output(output_type="stream", text=u'12')) + cc1.outputs.append(new_output(output_type="execute_result", + data={'image/png' : png_green_pixel}, + execution_count=1, + )) + nb.cells.append(cc1) with io.open(pjoin(nbdir, 'foo', 'testnb.ipynb'), 'w', encoding='utf-8') as f: - write(nb, f, format='ipynb') + write(nb, f, version=4) self.nbconvert_api = NbconvertAPI(self.base_url()) diff --git a/IPython/html/nbextensions.py b/IPython/html/nbextensions.py index 78359f8..d254b76 100644 --- a/IPython/html/nbextensions.py +++ b/IPython/html/nbextensions.py @@ -93,7 +93,9 @@ def install_nbextension(files, overwrite=False, symlink=False, ipython_dir=None, If True, always install the files, regardless of what may already be installed. symlink : bool [default: False] If True, create a symlink in nbextensions, rather than copying files. - Not allowed with URLs or archives. + Not allowed with URLs or archives. Windows support for symlinks requires + Vista or above, Python 3, and a permission bit which only admin users + have by default, so don't rely on it. ipython_dir : str [optional] The path to an IPython directory, if the default value is not desired. get_ipython_dir() is used by default. @@ -147,7 +149,7 @@ def install_nbextension(files, overwrite=False, symlink=False, ipython_dir=None, if overwrite and os.path.exists(dest): if verbose >= 1: print("removing %s" % dest) - if os.path.isdir(dest): + if os.path.isdir(dest) and not os.path.islink(dest): shutil.rmtree(dest) else: os.remove(dest) diff --git a/IPython/html/notebook/handlers.py b/IPython/html/notebook/handlers.py index 31e5ee7..f6e1012 100644 --- a/IPython/html/notebook/handlers.py +++ b/IPython/html/notebook/handlers.py @@ -17,18 +17,16 @@ from ..utils import url_escape class NotebookHandler(IPythonHandler): @web.authenticated - def get(self, path='', name=None): + def get(self, path): """get renders the notebook template if a name is given, or redirects to the '/files/' handler if the name is not given.""" path = path.strip('/') cm = self.contents_manager - if name is None: - raise web.HTTPError(500, "This shouldn't be accessible: %s" % self.request.uri) # a .ipynb filename was given - if not cm.file_exists(name, path): - raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name)) - name = url_escape(name) + if not cm.file_exists(path): + raise web.HTTPError(404, u'Notebook does not exist: %s' % path) + name = url_escape(path.rsplit('/', 1)[-1]) path = url_escape(path) self.write(self.render_template('notebook.html', notebook_path=path, diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py index b1b5c84..24d18e8 100644 --- a/IPython/html/notebookapp.py +++ b/IPython/html/notebookapp.py @@ -7,6 +7,7 @@ from __future__ import print_function import base64 +import datetime import errno import io import json @@ -35,7 +36,7 @@ from zmq.eventloop import ioloop ioloop.install() # check for tornado 3.1.0 -msg = "The IPython Notebook requires tornado >= 3.1.0" +msg = "The IPython Notebook requires tornado >= 4.0" try: import tornado except ImportError: @@ -44,14 +45,17 @@ try: version_info = tornado.version_info except AttributeError: raise ImportError(msg + ", but you have < 1.1.0") -if version_info < (3,1,0): +if version_info < (4,0): raise ImportError(msg + ", but you have %s" % tornado.version) from tornado import httpserver from tornado import web -from tornado.log import LogFormatter +from tornado.log import LogFormatter, app_log, access_log, gen_log -from IPython.html import DEFAULT_STATIC_FILES_PATH +from IPython.html import ( + DEFAULT_STATIC_FILES_PATH, + DEFAULT_TEMPLATE_PATH_LIST, +) from .base.handlers import Template404 from .log import log_request from .services.kernels.kernelmanager import MappingKernelManager @@ -81,6 +85,7 @@ from IPython.utils.traitlets import ( ) from IPython.utils import py3compat from IPython.utils.path import filefind, get_ipython_dir +from IPython.utils.sysinfo import get_sys_info from .utils import url_path_join @@ -122,37 +127,43 @@ def load_handlers(name): class NotebookWebApplication(web.Application): def __init__(self, ipython_app, kernel_manager, contents_manager, - cluster_manager, session_manager, kernel_spec_manager, log, + cluster_manager, session_manager, kernel_spec_manager, + config_manager, log, base_url, default_url, settings_overrides, jinja_env_options): settings = self.init_settings( ipython_app, kernel_manager, contents_manager, cluster_manager, - session_manager, kernel_spec_manager, log, base_url, default_url, - settings_overrides, jinja_env_options) + session_manager, kernel_spec_manager, config_manager, log, base_url, + default_url, settings_overrides, jinja_env_options) handlers = self.init_handlers(settings) super(NotebookWebApplication, self).__init__(handlers, **settings) def init_settings(self, ipython_app, kernel_manager, contents_manager, cluster_manager, session_manager, kernel_spec_manager, + config_manager, log, base_url, default_url, settings_overrides, jinja_env_options=None): - # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and - # base_url will always be unicode, which will in turn - # make the patterns unicode, and ultimately result in unicode - # keys in kwargs to handler._execute(**kwargs) in tornado. - # This enforces that base_url be ascii in that situation. - # - # Note that the URLs these patterns check against are escaped, - # and thus guaranteed to be ASCII: 'héllo' is really 'h%C3%A9llo'. - base_url = py3compat.unicode_to_str(base_url, 'ascii') - _template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates")) + + _template_path = settings_overrides.get( + "template_path", + ipython_app.template_file_path, + ) if isinstance(_template_path, str): _template_path = (_template_path,) template_path = [os.path.expanduser(path) for path in _template_path] jenv_opt = jinja_env_options if jinja_env_options else {} env = Environment(loader=FileSystemLoader(template_path), **jenv_opt) + + sys_info = get_sys_info() + if sys_info['commit_source'] == 'repository': + # don't cache (rely on 304) when working from master + version_hash = '' + else: + # reset the cache on server restart + version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + settings = dict( # basics log_function=log_request, @@ -162,6 +173,11 @@ class NotebookWebApplication(web.Application): static_path=ipython_app.static_file_path, static_handler_class = FileFindHandler, static_url_prefix = url_path_join(base_url,'/static/'), + static_handler_args = { + # don't cache custom.js + 'no_cache_paths': [url_path_join(base_url, 'static', 'custom')], + }, + version_hash=version_hash, # authentication cookie_secret=ipython_app.cookie_secret, @@ -174,6 +190,7 @@ class NotebookWebApplication(web.Application): cluster_manager=cluster_manager, session_manager=session_manager, kernel_spec_manager=kernel_spec_manager, + config_manager=config_manager, # IPython stuff nbextensions_path = ipython_app.nbextensions_path, @@ -181,6 +198,7 @@ class NotebookWebApplication(web.Application): mathjax_url=ipython_app.mathjax_url, config=ipython_app.config, jinja2_env=env, + terminals_available=False, # Set later if terminals are available ) # allow custom overrides for the tornado web app. @@ -188,30 +206,34 @@ class NotebookWebApplication(web.Application): return settings def init_handlers(self, settings): - # Load the (URL pattern, handler) tuples for each component. + """Load the (URL pattern, handler) tuples for each component.""" + + # Order matters. The first handler to match the URL will handle the request. handlers = [] - handlers.extend(load_handlers('base.handlers')) handlers.extend(load_handlers('tree.handlers')) handlers.extend(load_handlers('auth.login')) handlers.extend(load_handlers('auth.logout')) + handlers.extend(load_handlers('files.handlers')) handlers.extend(load_handlers('notebook.handlers')) handlers.extend(load_handlers('nbconvert.handlers')) handlers.extend(load_handlers('kernelspecs.handlers')) + handlers.extend(load_handlers('edit.handlers')) + handlers.extend(load_handlers('services.config.handlers')) handlers.extend(load_handlers('services.kernels.handlers')) handlers.extend(load_handlers('services.contents.handlers')) handlers.extend(load_handlers('services.clusters.handlers')) handlers.extend(load_handlers('services.sessions.handlers')) handlers.extend(load_handlers('services.nbconvert.handlers')) handlers.extend(load_handlers('services.kernelspecs.handlers')) - # FIXME: /files/ should be handled by the Contents service when it exists - cm = settings['contents_manager'] - if hasattr(cm, 'root_dir'): - handlers.append( - (r"/files/(.*)", AuthenticatedFileHandler, {'path' : cm.root_dir}), - ) + handlers.extend(load_handlers('services.security.handlers')) handlers.append( - (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}), + (r"/nbextensions/(.*)", FileFindHandler, { + 'path': settings['nbextensions_path'], + 'no_cache_paths': ['/'], # don't cache anything in nbextensions + }), ) + # register base handlers last + handlers.extend(load_handlers('base.handlers')) # set the URL that will be redirected from `/` handlers.append( (r'/?', web.RedirectHandler, { @@ -325,7 +347,7 @@ class NotebookApp(BaseIPythonApplication): list=(NbserverListApp, NbserverListApp.description.splitlines()[0]), ) - kernel_argv = List(Unicode) + ipython_kernel_argv = List(Unicode) _log_formatter_cls = LogFormatter @@ -345,11 +367,6 @@ class NotebookApp(BaseIPythonApplication): # file to be opened in the notebook server file_to_run = Unicode('', config=True) - def _file_to_run_changed(self, name, old, new): - path, base = os.path.split(new) - if path: - self.file_to_run = base - self.notebook_dir = path # Network related information @@ -531,7 +548,20 @@ class NotebookApp(BaseIPythonApplication): def static_file_path(self): """return extra paths + the default location""" return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH] - + + extra_template_paths = List(Unicode, config=True, + help="""Extra paths to search for serving jinja templates. + + Can be used to override templates from IPython.html.templates.""" + ) + def _extra_template_paths_default(self): + return [] + + @property + def template_file_path(self): + """return extra paths + the default locations""" + return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST + nbextensions_path = List(Unicode, config=True, help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions""" ) @@ -599,26 +629,38 @@ class NotebookApp(BaseIPythonApplication): help='The cluster manager class to use.' ) + config_manager_class = DottedObjectName('IPython.html.services.config.manager.ConfigManager', + config = True, + help='The config manager class to use' + ) + kernel_spec_manager = Instance(KernelSpecManager) def _kernel_spec_manager_default(self): return KernelSpecManager(ipython_dir=self.ipython_dir) + + kernel_spec_manager_class = DottedObjectName('IPython.kernel.kernelspec.KernelSpecManager', + config=True, + help=""" + The kernel spec manager class to use. Should be a subclass + of `IPython.kernel.kernelspec.KernelSpecManager`. + + The Api of KernelSpecManager is provisional and might change + without warning between this version of IPython and the next stable one. + """) + trust_xheaders = Bool(False, config=True, help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers" "sent by the upstream reverse proxy. Necessary if the proxy handles SSL") ) - + info_file = Unicode() def _info_file_default(self): info_file = "nbserver-%s.json"%os.getpid() return os.path.join(self.profile_dir.security_dir, info_file) - notebook_dir = Unicode(py3compat.getcwd(), config=True, - help="The directory to use for notebooks and kernels." - ) - pylab = Unicode('disabled', config=True, help=""" DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib. @@ -636,6 +678,16 @@ class NotebookApp(BaseIPythonApplication): ) self.exit(1) + notebook_dir = Unicode(config=True, + help="The directory to use for notebooks and kernels." + ) + + def _notebook_dir_default(self): + if self.file_to_run: + return os.path.dirname(os.path.abspath(self.file_to_run)) + else: + return py3compat.getcwd() + def _notebook_dir_changed(self, name, old, new): """Do a bit of validation of the notebook dir.""" if not os.path.isabs(new): @@ -671,16 +723,20 @@ class NotebookApp(BaseIPythonApplication): self.update_config(c) def init_kernel_argv(self): - """construct the kernel arguments""" + """add the profile-dir to arguments to be passed to IPython kernels""" + # FIXME: remove special treatment of IPython kernels # Kernel should get *absolute* path to profile directory - self.kernel_argv = ["--profile-dir", self.profile_dir.location] + self.ipython_kernel_argv = ["--profile-dir", self.profile_dir.location] def init_configurables(self): # force Session default to be secure default_secure(self.config) + kls = import_item(self.kernel_spec_manager_class) + self.kernel_spec_manager = kls(ipython_dir=self.ipython_dir) + kls = import_item(self.kernel_manager_class) self.kernel_manager = kls( - parent=self, log=self.log, kernel_argv=self.kernel_argv, + parent=self, log=self.log, ipython_kernel_argv=self.ipython_kernel_argv, connection_dir = self.profile_dir.security_dir, ) kls = import_item(self.contents_manager_class) @@ -693,12 +749,19 @@ class NotebookApp(BaseIPythonApplication): self.cluster_manager = kls(parent=self, log=self.log) self.cluster_manager.update_profiles() + kls = import_item(self.config_manager_class) + self.config_manager = kls(parent=self, log=self.log, + profile_dir=self.profile_dir.location) + def init_logging(self): # This prevents double log messages because tornado use a root logger that # self.log is a child of. The logging module dipatches log messages to a log # and all of its ancenstors until propagate is set to False. self.log.propagate = False + for log in app_log, access_log, gen_log: + # consistent log output name (NotebookApp instead of tornado.access, etc.) + log.name = self.log.name # hook up tornado 3's loggers to our app handlers logger = logging.getLogger('tornado') logger.propagate = True @@ -715,6 +778,7 @@ class NotebookApp(BaseIPythonApplication): self.web_app = NotebookWebApplication( self, self.kernel_manager, self.contents_manager, self.cluster_manager, self.session_manager, self.kernel_spec_manager, + self.config_manager, self.log, self.base_url, self.default_url, self.tornado_settings, self.jinja_environment_options ) @@ -771,6 +835,14 @@ class NotebookApp(BaseIPythonApplication): proto = 'https' if self.certfile else 'http' return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url) + def init_terminals(self): + try: + from .terminal import initialize + initialize(self.web_app) + self.web_app.settings['terminals_available'] = True + except ImportError as e: + self.log.info("Terminals not available (error was %s)", e) + def init_signal(self): if not sys.platform.startswith('win'): signal.signal(signal.SIGINT, self._handle_sigint) @@ -850,6 +922,7 @@ class NotebookApp(BaseIPythonApplication): self.init_configurables() self.init_components() self.init_webapp() + self.init_terminals() self.init_signal() def cleanup_kernels(self): @@ -917,12 +990,12 @@ class NotebookApp(BaseIPythonApplication): browser = None if self.file_to_run: - fullpath = os.path.join(self.notebook_dir, self.file_to_run) - if not os.path.exists(fullpath): - self.log.critical("%s does not exist" % fullpath) + if not os.path.exists(self.file_to_run): + self.log.critical("%s does not exist" % self.file_to_run) self.exit(1) - - uri = url_path_join('notebooks', self.file_to_run) + + relpath = os.path.relpath(self.file_to_run, self.notebook_dir) + uri = url_path_join('notebooks', *relpath.split(os.sep)) else: uri = 'tree' if browser: diff --git a/IPython/html/services/clusters/clustermanager.py b/IPython/html/services/clusters/clustermanager.py index fecde70..c0a8776 100644 --- a/IPython/html/services/clusters/clustermanager.py +++ b/IPython/html/services/clusters/clustermanager.py @@ -1,44 +1,23 @@ -"""Manage IPython.parallel clusters in the notebook. +"""Manage IPython.parallel clusters in the notebook.""" -Authors: - -* Brian Granger -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The 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 -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. from tornado import web -from zmq.eventloop import ioloop from IPython.config.configurable import LoggingConfigurable -from IPython.utils.traitlets import Dict, Instance, CFloat +from IPython.utils.traitlets import Dict, Instance, Float from IPython.core.profileapp import list_profiles_in from IPython.core.profiledir import ProfileDir from IPython.utils import py3compat from IPython.utils.path import get_ipython_dir -#----------------------------------------------------------------------------- -# Classes -#----------------------------------------------------------------------------- - - - - class ClusterManager(LoggingConfigurable): profiles = Dict() - delay = CFloat(1., config=True, + delay = Float(1., config=True, help="delay (in s) between starting the controller and the engines") loop = Instance('zmq.eventloop.ioloop.IOLoop') @@ -75,16 +54,24 @@ class ClusterManager(LoggingConfigurable): def update_profiles(self): """List all profiles in the ipython_dir and cwd. """ + + stale = set(self.profiles) for path in [get_ipython_dir(), py3compat.getcwd()]: for profile in list_profiles_in(path): + if profile in stale: + stale.remove(profile) pd = self.get_profile_dir(profile, path) if profile not in self.profiles: - self.log.debug("Adding cluster profile '%s'" % profile) + self.log.debug("Adding cluster profile '%s'", profile) self.profiles[profile] = { 'profile': profile, 'profile_dir': pd, 'status': 'stopped' } + for profile in stale: + # remove profiles that no longer exist + self.log.debug("Profile '%s' no longer exists", profile) + self.profiles.pop(stale) def list_profiles(self): self.update_profiles() @@ -133,11 +120,13 @@ class ClusterManager(LoggingConfigurable): esl.stop() clean_data() cl.on_stop(controller_stopped) - - dc = ioloop.DelayedCallback(lambda: cl.start(), 0, self.loop) - dc.start() - dc = ioloop.DelayedCallback(lambda: esl.start(n), 1000*self.delay, self.loop) - dc.start() + loop = self.loop + + def start(): + """start the controller, then the engines after a delay""" + cl.start() + loop.add_timeout(self.loop.time() + self.delay, lambda : esl.start(n)) + self.loop.add_callback(start) self.log.debug('Cluster started') data['controller_launcher'] = cl diff --git a/IPython/html/services/config/__init__.py b/IPython/html/services/config/__init__.py new file mode 100644 index 0000000..d8d9380 --- /dev/null +++ b/IPython/html/services/config/__init__.py @@ -0,0 +1 @@ +from .manager import ConfigManager diff --git a/IPython/html/services/config/handlers.py b/IPython/html/services/config/handlers.py new file mode 100644 index 0000000..a7ff896 --- /dev/null +++ b/IPython/html/services/config/handlers.py @@ -0,0 +1,44 @@ +"""Tornado handlers for frontend config storage.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. +import json +import os +import io +import errno +from tornado import web + +from IPython.utils.py3compat import PY3 +from ...base.handlers import IPythonHandler, json_errors + +class ConfigHandler(IPythonHandler): + SUPPORTED_METHODS = ('GET', 'PUT', 'PATCH') + + @web.authenticated + @json_errors + def get(self, section_name): + self.set_header("Content-Type", 'application/json') + self.finish(json.dumps(self.config_manager.get(section_name))) + + @web.authenticated + @json_errors + def put(self, section_name): + data = self.get_json_body() # Will raise 400 if content is not valid JSON + self.config_manager.set(section_name, data) + self.set_status(204) + + @web.authenticated + @json_errors + def patch(self, section_name): + new_data = self.get_json_body() + section = self.config_manager.update(section_name, new_data) + self.finish(json.dumps(section)) + + +# URL to handler mappings + +section_name_regex = r"(?P\w+)" + +default_handlers = [ + (r"/api/config/%s" % section_name_regex, ConfigHandler), +] diff --git a/IPython/html/services/config/manager.py b/IPython/html/services/config/manager.py new file mode 100644 index 0000000..429da07 --- /dev/null +++ b/IPython/html/services/config/manager.py @@ -0,0 +1,90 @@ +"""Manager to read and modify frontend config data in JSON files. +""" +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. +import errno +import io +import json +import os + +from IPython.config import LoggingConfigurable +from IPython.utils.path import locate_profile +from IPython.utils.py3compat import PY3 +from IPython.utils.traitlets import Unicode + + +def recursive_update(target, new): + """Recursively update one dictionary using another. + + None values will delete their keys. + """ + for k, v in new.items(): + if isinstance(v, dict): + if k not in target: + target[k] = {} + recursive_update(target[k], v) + if not target[k]: + # Prune empty subdicts + del target[k] + + elif v is None: + target.pop(k, None) + + else: + target[k] = v + + +class ConfigManager(LoggingConfigurable): + profile_dir = Unicode() + def _profile_dir_default(self): + return locate_profile() + + @property + def config_dir(self): + return os.path.join(self.profile_dir, 'nbconfig') + + def ensure_config_dir_exists(self): + try: + os.mkdir(self.config_dir, 0o755) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + def file_name(self, section_name): + return os.path.join(self.config_dir, section_name+'.json') + + def get(self, section_name): + """Retrieve the config data for the specified section. + + Returns the data as a dictionary, or an empty dictionary if the file + doesn't exist. + """ + filename = self.file_name(section_name) + if os.path.isfile(filename): + with io.open(filename, encoding='utf-8') as f: + return json.load(f) + else: + return {} + + def set(self, section_name, data): + """Store the given config data. + """ + filename = self.file_name(section_name) + self.ensure_config_dir_exists() + + if PY3: + f = io.open(filename, 'w', encoding='utf-8') + else: + f = open(filename, 'wb') + with f: + json.dump(data, f) + + def update(self, section_name, new_data): + """Modify the config section by recursively updating it with new_data. + + Returns the modified config data as a dictionary. + """ + data = self.get(section_name) + recursive_update(data, new_data) + self.set(section_name, data) + return data diff --git a/IPython/html/services/config/tests/__init__.py b/IPython/html/services/config/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/IPython/html/services/config/tests/__init__.py diff --git a/IPython/html/services/config/tests/test_config_api.py b/IPython/html/services/config/tests/test_config_api.py new file mode 100644 index 0000000..463fc4e --- /dev/null +++ b/IPython/html/services/config/tests/test_config_api.py @@ -0,0 +1,68 @@ +# coding: utf-8 +"""Test the config webservice API.""" + +import json + +import requests + +from IPython.html.utils import url_path_join +from IPython.html.tests.launchnotebook import NotebookTestBase + + +class ConfigAPI(object): + """Wrapper for notebook API calls.""" + def __init__(self, base_url): + self.base_url = base_url + + def _req(self, verb, section, body=None): + response = requests.request(verb, + url_path_join(self.base_url, 'api/config', section), + data=body, + ) + response.raise_for_status() + return response + + def get(self, section): + return self._req('GET', section) + + def set(self, section, values): + return self._req('PUT', section, json.dumps(values)) + + def modify(self, section, values): + return self._req('PATCH', section, json.dumps(values)) + +class APITest(NotebookTestBase): + """Test the config web service API""" + def setUp(self): + self.config_api = ConfigAPI(self.base_url()) + + def test_create_retrieve_config(self): + sample = {'foo': 'bar', 'baz': 73} + r = self.config_api.set('example', sample) + self.assertEqual(r.status_code, 204) + + r = self.config_api.get('example') + self.assertEqual(r.status_code, 200) + self.assertEqual(r.json(), sample) + + def test_modify(self): + sample = {'foo': 'bar', 'baz': 73, + 'sub': {'a': 6, 'b': 7}, 'sub2': {'c': 8}} + self.config_api.set('example', sample) + + r = self.config_api.modify('example', {'foo': None, # should delete foo + 'baz': 75, + 'wib': [1,2,3], + 'sub': {'a': 8, 'b': None, 'd': 9}, + 'sub2': {'c': None} # should delete sub2 + }) + self.assertEqual(r.status_code, 200) + self.assertEqual(r.json(), {'baz': 75, 'wib': [1,2,3], + 'sub': {'a': 8, 'd': 9}}) + + def test_get_unknown(self): + # We should get an empty config dictionary instead of a 404 + r = self.config_api.get('nonexistant') + self.assertEqual(r.status_code, 200) + self.assertEqual(r.json(), {}) + diff --git a/IPython/html/services/contents/clientsidenbmanager.py b/IPython/html/services/contents/clientsidenbmanager.py new file mode 100644 index 0000000..b1c8dcc --- /dev/null +++ b/IPython/html/services/contents/clientsidenbmanager.py @@ -0,0 +1,23 @@ +"""A dummy contents manager for when the logic is done client side (in JavaScript).""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from .manager import ContentsManager + +class ClientSideContentsManager(ContentsManager): + """Dummy contents manager for use with client-side contents APIs like GDrive + + The view handlers for notebooks and directories (/tree/) check with the + ContentsManager that their target exists so they can return 404 if not. Using + this class as the contents manager allows those pages to render without + checking something that the server doesn't know about. + """ + def dir_exists(self, path): + return True + + def is_hidden(self, path): + return False + + def file_exists(self, name, path=''): + return True diff --git a/IPython/html/services/contents/filemanager.py b/IPython/html/services/contents/filemanager.py index 1329319..2ca1f41 100644 --- a/IPython/html/services/contents/filemanager.py +++ b/IPython/html/services/contents/filemanager.py @@ -4,27 +4,66 @@ # Distributed under the terms of the Modified BSD License. import base64 +import errno import io import os -import glob import shutil +from contextlib import contextmanager +import mimetypes from tornado import web from .manager import ContentsManager -from IPython.nbformat import current +from IPython import nbformat from IPython.utils.io import atomic_writing from IPython.utils.path import ensure_dir_exists from IPython.utils.traitlets import Unicode, Bool, TraitError -from IPython.utils.py3compat import getcwd +from IPython.utils.py3compat import getcwd, str_to_unicode from IPython.utils import tz -from IPython.html.utils import is_hidden, to_os_path, url_path_join +from IPython.html.utils import is_hidden, to_os_path, to_api_path class FileContentsManager(ContentsManager): - root_dir = Unicode(getcwd(), config=True) + root_dir = Unicode(config=True) + def _root_dir_default(self): + try: + return self.parent.notebook_dir + except AttributeError: + return getcwd() + + @contextmanager + def perm_to_403(self, os_path=''): + """context manager for turning permission errors into 403""" + try: + yield + except OSError as e: + if e.errno in {errno.EPERM, errno.EACCES}: + # make 403 error message without root prefix + # this may not work perfectly on unicode paths on Python 2, + # but nobody should be doing that anyway. + if not os_path: + os_path = str_to_unicode(e.filename or 'unknown file') + path = to_api_path(os_path, self.root_dir) + raise web.HTTPError(403, u'Permission denied: %s' % path) + else: + raise + + @contextmanager + def open(self, os_path, *args, **kwargs): + """wrapper around io.open that turns permission errors into 403""" + with self.perm_to_403(os_path): + with io.open(os_path, *args, **kwargs) as f: + yield f + + @contextmanager + def atomic_writing(self, os_path, *args, **kwargs): + """wrapper around atomic_writing that turns permission errors into 403""" + with self.perm_to_403(os_path): + with atomic_writing(os_path, *args, **kwargs) as f: + yield f + save_script = Bool(False, config=True, help='DEPRECATED, IGNORED') def _save_script_changed(self): self.log.warn(""" @@ -61,27 +100,22 @@ class FileContentsManager(ContentsManager): except OSError as e: self.log.debug("copystat on %s failed", dest, exc_info=True) - def _get_os_path(self, name=None, path=''): - """Given a filename and API path, return its file system - path. + def _get_os_path(self, path): + """Given an API path, return its file system path. Parameters ---------- - name : string - A filename path : string The relative API path to the named file. Returns ------- path : string - API path to be evaluated relative to root_dir. + Native, absolute OS path to for a file. """ - if name is not None: - path = url_path_join(path, name) return to_os_path(path, self.root_dir) - def path_exists(self, path): + def dir_exists(self, path): """Does the API-style path refer to an extant directory? API-style wrapper for os.path.isdir @@ -112,25 +146,22 @@ class FileContentsManager(ContentsManager): Returns ------- - exists : bool - Whether the path is hidden. - + hidden : bool + Whether the path exists and is hidden. """ path = path.strip('/') os_path = self._get_os_path(path=path) return is_hidden(os_path, self.root_dir) - def file_exists(self, name, path=''): + def file_exists(self, path): """Returns True if the file exists, else returns False. API-style wrapper for os.path.isfile Parameters ---------- - name : string - The name of the file you are checking. path : string - The relative path to the file's directory (with '/' as separator) + The relative path to the file (with '/' as separator) Returns ------- @@ -138,20 +169,18 @@ class FileContentsManager(ContentsManager): Whether the file exists. """ path = path.strip('/') - nbpath = self._get_os_path(name, path=path) - return os.path.isfile(nbpath) + os_path = self._get_os_path(path) + return os.path.isfile(os_path) - def exists(self, name=None, path=''): - """Returns True if the path [and name] exists, else returns False. + def exists(self, path): + """Returns True if the path exists, else returns False. API-style wrapper for os.path.exists Parameters ---------- - name : string - The name of the file you are checking. path : string - The relative path to the file's directory (with '/' as separator) + The API path to the file (with '/' as separator) Returns ------- @@ -159,33 +188,39 @@ class FileContentsManager(ContentsManager): Whether the target exists. """ path = path.strip('/') - os_path = self._get_os_path(name, path=path) + os_path = self._get_os_path(path=path) return os.path.exists(os_path) - def _base_model(self, name, path=''): + def _base_model(self, path): """Build the common base of a contents model""" - os_path = self._get_os_path(name, path) + os_path = self._get_os_path(path) info = os.stat(os_path) last_modified = tz.utcfromtimestamp(info.st_mtime) created = tz.utcfromtimestamp(info.st_ctime) # Create the base model. model = {} - model['name'] = name + model['name'] = path.rsplit('/', 1)[-1] model['path'] = path model['last_modified'] = last_modified model['created'] = created model['content'] = None model['format'] = None + model['mimetype'] = None + try: + model['writable'] = os.access(os_path, os.W_OK) + except OSError: + self.log.error("Failed to check write permissions on %s", os_path) + model['writable'] = False return model - def _dir_model(self, name, path='', content=True): + def _dir_model(self, path, content=True): """Build a model for a directory if content is requested, will include a listing of the directory """ - os_path = self._get_os_path(name, path) + os_path = self._get_os_path(path) - four_o_four = u'directory does not exist: %r' % os_path + four_o_four = u'directory does not exist: %r' % path if not os.path.isdir(os_path): raise web.HTTPError(404, four_o_four) @@ -195,80 +230,105 @@ class FileContentsManager(ContentsManager): ) raise web.HTTPError(404, four_o_four) - if name is None: - if '/' in path: - path, name = path.rsplit('/', 1) - else: - name = '' - model = self._base_model(name, path) + model = self._base_model(path) model['type'] = 'directory' - dir_path = u'{}/{}'.format(path, name) if content: model['content'] = contents = [] - for os_path in glob.glob(self._get_os_path('*', dir_path)): - name = os.path.basename(os_path) + os_dir = self._get_os_path(path) + for name in os.listdir(os_dir): + os_path = os.path.join(os_dir, name) # skip over broken symlinks in listing if not os.path.exists(os_path): self.log.warn("%s doesn't exist", os_path) continue + elif not os.path.isfile(os_path) and not os.path.isdir(os_path): + self.log.debug("%s not a regular file", os_path) + continue if self.should_list(name) and not is_hidden(os_path, self.root_dir): - contents.append(self.get_model(name=name, path=dir_path, content=False)) + contents.append(self.get( + path='%s/%s' % (path, name), + content=False) + ) model['format'] = 'json' return model - def _file_model(self, name, path='', content=True): + def _file_model(self, path, content=True, format=None): """Build a model for a file if content is requested, include the file contents. - UTF-8 text files will be unicode, binary files will be base64-encoded. + + format: + If 'text', the contents will be decoded as UTF-8. + If 'base64', the raw bytes contents will be encoded as base64. + If not specified, try to decode as UTF-8, and fall back to base64 """ - model = self._base_model(name, path) + model = self._base_model(path) model['type'] = 'file' + + os_path = self._get_os_path(path) + model['mimetype'] = mimetypes.guess_type(os_path)[0] or 'text/plain' + if content: - os_path = self._get_os_path(name, path) - with io.open(os_path, 'rb') as f: + if not os.path.isfile(os_path): + # could be FIFO + raise web.HTTPError(400, "Cannot get content of non-file %s" % os_path) + with self.open(os_path, 'rb') as f: bcontent = f.read() - try: - model['content'] = bcontent.decode('utf8') - except UnicodeError as e: + + if format != 'base64': + try: + model['content'] = bcontent.decode('utf8') + except UnicodeError as e: + if format == 'text': + raise web.HTTPError(400, "%s is not UTF-8 encoded" % path) + else: + model['format'] = 'text' + + if model['content'] is None: model['content'] = base64.encodestring(bcontent).decode('ascii') model['format'] = 'base64' - else: - model['format'] = 'text' + return model - def _notebook_model(self, name, path='', content=True): + def _notebook_model(self, path, content=True): """Build a notebook model if content is requested, the notebook content will be populated as a JSON structure (not double-serialized) """ - model = self._base_model(name, path) + model = self._base_model(path) model['type'] = 'notebook' if content: - os_path = self._get_os_path(name, path) - with io.open(os_path, 'r', encoding='utf-8') as f: + os_path = self._get_os_path(path) + with self.open(os_path, 'r', encoding='utf-8') as f: try: - nb = current.read(f, u'json') + nb = nbformat.read(f, as_version=4) except Exception as e: - raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e)) - self.mark_trusted_cells(nb, name, path) + raise web.HTTPError(400, u"Unreadable Notebook: %s %r" % (os_path, e)) + self.mark_trusted_cells(nb, path) model['content'] = nb model['format'] = 'json' + self.validate_notebook_model(model) return model - def get_model(self, name, path='', content=True): - """ Takes a path and name for an entity and returns its model + def get(self, path, content=True, type_=None, format=None): + """ Takes a path for an entity and returns its model Parameters ---------- - name : str - the name of the target path : str the API path that describes the relative path for the target + content : bool + Whether to include the contents in the reply + type_ : str, optional + The requested type - 'file', 'notebook', or 'directory'. + Will raise HTTPError 400 if the content doesn't match. + format : str, optional + The requested format for file contents. 'text' or 'base64'. + Ignored if this returns a notebook or directory model. Returns ------- @@ -278,32 +338,35 @@ class FileContentsManager(ContentsManager): """ path = path.strip('/') - if not self.exists(name=name, path=path): - raise web.HTTPError(404, u'No such file or directory: %s/%s' % (path, name)) + if not self.exists(path): + raise web.HTTPError(404, u'No such file or directory: %s' % path) - os_path = self._get_os_path(name, path) + os_path = self._get_os_path(path) if os.path.isdir(os_path): - model = self._dir_model(name, path, content) - elif name.endswith('.ipynb'): - model = self._notebook_model(name, path, content) + if type_ not in (None, 'directory'): + raise web.HTTPError(400, + u'%s is a directory, not a %s' % (path, type_)) + model = self._dir_model(path, content=content) + elif type_ == 'notebook' or (type_ is None and path.endswith('.ipynb')): + model = self._notebook_model(path, content=content) else: - model = self._file_model(name, path, content) + if type_ == 'directory': + raise web.HTTPError(400, + u'%s is not a directory') + model = self._file_model(path, content=content, format=format) return model - def _save_notebook(self, os_path, model, name='', path=''): + def _save_notebook(self, os_path, model, path=''): """save a notebook file""" # Save the notebook file - nb = current.to_notebook_json(model['content']) + nb = nbformat.from_dict(model['content']) - self.check_and_sign(nb, name, path) + self.check_and_sign(nb, path) - if 'name' in nb['metadata']: - nb['metadata']['name'] = u'' + with self.atomic_writing(os_path, encoding='utf-8') as f: + nbformat.write(nb, f, version=nbformat.NO_CONVERT) - with atomic_writing(os_path, encoding='utf-8') as f: - current.write(nb, f, u'json') - - def _save_file(self, os_path, model, name='', path=''): + def _save_file(self, os_path, model, path=''): """save a non-notebook file""" fmt = model.get('format', None) if fmt not in {'text', 'base64'}: @@ -317,21 +380,22 @@ class FileContentsManager(ContentsManager): bcontent = base64.decodestring(b64_bytes) except Exception as e: raise web.HTTPError(400, u'Encoding error saving %s: %s' % (os_path, e)) - with atomic_writing(os_path, text=False) as f: + with self.atomic_writing(os_path, text=False) as f: f.write(bcontent) - def _save_directory(self, os_path, model, name='', path=''): + def _save_directory(self, os_path, model, path=''): """create a directory""" if is_hidden(os_path, self.root_dir): raise web.HTTPError(400, u'Cannot create hidden directory %r' % os_path) if not os.path.exists(os_path): - os.mkdir(os_path) + with self.perm_to_403(): + os.mkdir(os_path) elif not os.path.isdir(os_path): raise web.HTTPError(400, u'Not a directory: %s' % (os_path)) else: self.log.debug("Directory %r already exists", os_path) - def save(self, model, name='', path=''): + def save(self, model, path=''): """Save the file model and return the model with no content.""" path = path.strip('/') @@ -341,52 +405,53 @@ class FileContentsManager(ContentsManager): raise web.HTTPError(400, u'No file content provided') # One checkpoint should always exist - if self.file_exists(name, path) and not self.list_checkpoints(name, path): - self.create_checkpoint(name, path) - - new_path = model.get('path', path).strip('/') - new_name = model.get('name', name) + if self.file_exists(path) and not self.list_checkpoints(path): + self.create_checkpoint(path) - if path != new_path or name != new_name: - self.rename(name, path, new_name, new_path) - - os_path = self._get_os_path(new_name, new_path) + os_path = self._get_os_path(path) self.log.debug("Saving %s", os_path) try: if model['type'] == 'notebook': - self._save_notebook(os_path, model, new_name, new_path) + self._save_notebook(os_path, model, path) elif model['type'] == 'file': - self._save_file(os_path, model, new_name, new_path) + self._save_file(os_path, model, path) elif model['type'] == 'directory': - self._save_directory(os_path, model, new_name, new_path) + self._save_directory(os_path, model, path) else: raise web.HTTPError(400, "Unhandled contents type: %s" % model['type']) except web.HTTPError: raise except Exception as e: - raise web.HTTPError(400, u'Unexpected error while saving file: %s %s' % (os_path, e)) + self.log.error(u'Error while saving file: %s %s', path, e, exc_info=True) + raise web.HTTPError(500, u'Unexpected error while saving file: %s %s' % (path, e)) + + validation_message = None + if model['type'] == 'notebook': + self.validate_notebook_model(model) + validation_message = model.get('message', None) - model = self.get_model(new_name, new_path, content=False) + model = self.get(path, content=False) + if validation_message: + model['message'] = validation_message return model - def update(self, model, name, path=''): - """Update the file's path and/or name + def update(self, model, path): + """Update the file's path For use in PATCH requests, to enable renaming a file without re-uploading its contents. Only used for renaming at the moment. """ path = path.strip('/') - new_name = model.get('name', name) new_path = model.get('path', path).strip('/') - if path != new_path or name != new_name: - self.rename(name, path, new_name, new_path) - model = self.get_model(new_name, new_path, content=False) + if path != new_path: + self.rename(path, new_path) + model = self.get(new_path, content=False) return model - def delete(self, name, path=''): - """Delete file by name and path.""" + def delete(self, path): + """Delete file at path.""" path = path.strip('/') - os_path = self._get_os_path(name, path) + os_path = self._get_os_path(path) rm = os.unlink if os.path.isdir(os_path): listing = os.listdir(os_path) @@ -397,71 +462,81 @@ class FileContentsManager(ContentsManager): raise web.HTTPError(404, u'File does not exist: %s' % os_path) # clear checkpoints - for checkpoint in self.list_checkpoints(name, path): + for checkpoint in self.list_checkpoints(path): checkpoint_id = checkpoint['id'] - cp_path = self.get_checkpoint_path(checkpoint_id, name, path) + cp_path = self.get_checkpoint_path(checkpoint_id, path) if os.path.isfile(cp_path): self.log.debug("Unlinking checkpoint %s", cp_path) - os.unlink(cp_path) + with self.perm_to_403(): + rm(cp_path) if os.path.isdir(os_path): self.log.debug("Removing directory %s", os_path) - shutil.rmtree(os_path) + with self.perm_to_403(): + shutil.rmtree(os_path) else: self.log.debug("Unlinking file %s", os_path) - rm(os_path) + with self.perm_to_403(): + rm(os_path) - def rename(self, old_name, old_path, new_name, new_path): + def rename(self, old_path, new_path): """Rename a file.""" old_path = old_path.strip('/') new_path = new_path.strip('/') - if new_name == old_name and new_path == old_path: + if new_path == old_path: return - new_os_path = self._get_os_path(new_name, new_path) - old_os_path = self._get_os_path(old_name, old_path) + new_os_path = self._get_os_path(new_path) + old_os_path = self._get_os_path(old_path) # Should we proceed with the move? - if os.path.isfile(new_os_path): - raise web.HTTPError(409, u'File with name already exists: %s' % new_os_path) + if os.path.exists(new_os_path): + raise web.HTTPError(409, u'File already exists: %s' % new_path) # Move the file try: - shutil.move(old_os_path, new_os_path) + with self.perm_to_403(): + shutil.move(old_os_path, new_os_path) + except web.HTTPError: + raise except Exception as e: - raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_os_path, e)) + raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_path, e)) # Move the checkpoints - old_checkpoints = self.list_checkpoints(old_name, old_path) + old_checkpoints = self.list_checkpoints(old_path) for cp in old_checkpoints: checkpoint_id = cp['id'] - old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, old_path) - new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, new_path) + old_cp_path = self.get_checkpoint_path(checkpoint_id, old_path) + new_cp_path = self.get_checkpoint_path(checkpoint_id, new_path) if os.path.isfile(old_cp_path): self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path) - shutil.move(old_cp_path, new_cp_path) + with self.perm_to_403(): + shutil.move(old_cp_path, new_cp_path) # Checkpoint-related utilities - def get_checkpoint_path(self, checkpoint_id, name, path=''): + def get_checkpoint_path(self, checkpoint_id, path): """find the path to a checkpoint""" path = path.strip('/') + parent, name = ('/' + path).rsplit('/', 1) + parent = parent.strip('/') basename, ext = os.path.splitext(name) filename = u"{name}-{checkpoint_id}{ext}".format( name=basename, checkpoint_id=checkpoint_id, ext=ext, ) - os_path = self._get_os_path(path=path) + os_path = self._get_os_path(path=parent) cp_dir = os.path.join(os_path, self.checkpoint_dir) - ensure_dir_exists(cp_dir) + with self.perm_to_403(): + ensure_dir_exists(cp_dir) cp_path = os.path.join(cp_dir, filename) return cp_path - def get_checkpoint_model(self, checkpoint_id, name, path=''): + def get_checkpoint_model(self, checkpoint_id, path): """construct the info dict for a given checkpoint""" path = path.strip('/') - cp_path = self.get_checkpoint_path(checkpoint_id, name, path) + cp_path = self.get_checkpoint_path(checkpoint_id, path) stats = os.stat(cp_path) last_modified = tz.utcfromtimestamp(stats.st_mtime) info = dict( @@ -472,58 +547,62 @@ class FileContentsManager(ContentsManager): # public checkpoint API - def create_checkpoint(self, name, path=''): + def create_checkpoint(self, path): """Create a checkpoint from the current state of a file""" path = path.strip('/') - src_path = self._get_os_path(name, path) + if not self.file_exists(path): + raise web.HTTPError(404) + src_path = self._get_os_path(path) # only the one checkpoint ID: checkpoint_id = u"checkpoint" - cp_path = self.get_checkpoint_path(checkpoint_id, name, path) - self.log.debug("creating checkpoint for %s", name) - self._copy(src_path, cp_path) + cp_path = self.get_checkpoint_path(checkpoint_id, path) + self.log.debug("creating checkpoint for %s", path) + with self.perm_to_403(): + self._copy(src_path, cp_path) # return the checkpoint info - return self.get_checkpoint_model(checkpoint_id, name, path) + return self.get_checkpoint_model(checkpoint_id, path) - def list_checkpoints(self, name, path=''): + def list_checkpoints(self, path): """list the checkpoints for a given file This contents manager currently only supports one checkpoint per file. """ path = path.strip('/') checkpoint_id = "checkpoint" - os_path = self.get_checkpoint_path(checkpoint_id, name, path) + os_path = self.get_checkpoint_path(checkpoint_id, path) if not os.path.exists(os_path): return [] else: - return [self.get_checkpoint_model(checkpoint_id, name, path)] + return [self.get_checkpoint_model(checkpoint_id, path)] - def restore_checkpoint(self, checkpoint_id, name, path=''): + def restore_checkpoint(self, checkpoint_id, path): """restore a file to a checkpointed state""" path = path.strip('/') - self.log.info("restoring %s from checkpoint %s", name, checkpoint_id) - nb_path = self._get_os_path(name, path) - cp_path = self.get_checkpoint_path(checkpoint_id, name, path) + self.log.info("restoring %s from checkpoint %s", path, checkpoint_id) + nb_path = self._get_os_path(path) + cp_path = self.get_checkpoint_path(checkpoint_id, path) if not os.path.isfile(cp_path): self.log.debug("checkpoint file does not exist: %s", cp_path) raise web.HTTPError(404, - u'checkpoint does not exist: %s-%s' % (name, checkpoint_id) + u'checkpoint does not exist: %s@%s' % (path, checkpoint_id) ) # ensure notebook is readable (never restore from an unreadable notebook) if cp_path.endswith('.ipynb'): - with io.open(cp_path, 'r', encoding='utf-8') as f: - current.read(f, u'json') - self._copy(cp_path, nb_path) + with self.open(cp_path, 'r', encoding='utf-8') as f: + nbformat.read(f, as_version=4) self.log.debug("copying %s -> %s", cp_path, nb_path) + with self.perm_to_403(): + self._copy(cp_path, nb_path) - def delete_checkpoint(self, checkpoint_id, name, path=''): + def delete_checkpoint(self, checkpoint_id, path): """delete a file's checkpoint""" path = path.strip('/') - cp_path = self.get_checkpoint_path(checkpoint_id, name, path) + cp_path = self.get_checkpoint_path(checkpoint_id, path) if not os.path.isfile(cp_path): raise web.HTTPError(404, - u'Checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id) + u'Checkpoint does not exist: %s@%s' % (path, checkpoint_id) ) self.log.debug("unlinking %s", cp_path) os.unlink(cp_path) @@ -531,6 +610,10 @@ class FileContentsManager(ContentsManager): def info_string(self): return "Serving notebooks from local directory: %s" % self.root_dir - def get_kernel_path(self, name, path='', model=None): - """Return the initial working dir a kernel associated with a given notebook""" - return os.path.join(self.root_dir, path) + def get_kernel_path(self, path, model=None): + """Return the initial API path of a kernel associated with a given notebook""" + if '/' in path: + parent_dir = path.rsplit('/', 1)[0] + else: + parent_dir = '' + return parent_dir diff --git a/IPython/html/services/contents/handlers.py b/IPython/html/services/contents/handlers.py index 72860ad..ea33f17 100644 --- a/IPython/html/services/contents/handlers.py +++ b/IPython/html/services/contents/handlers.py @@ -10,9 +10,9 @@ from tornado import web from IPython.html.utils import url_path_join, url_escape from IPython.utils.jsonutil import date_default -from IPython.html.base.handlers import (IPythonHandler, json_errors, - file_path_regex, path_regex, - file_name_regex) +from IPython.html.base.handlers import ( + IPythonHandler, json_errors, path_regex, +) def sort_key(model): @@ -29,38 +29,44 @@ class ContentsHandler(IPythonHandler): SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE') - def location_url(self, name, path): + def location_url(self, path): """Return the full URL location of a file. Parameters ---------- - name : unicode - The base name of the file, such as "foo.ipynb". path : unicode - The API path of the file, such as "foo/bar". + The API path of the file, such as "foo/bar.txt". """ return url_escape(url_path_join( - self.base_url, 'api', 'contents', path, name + self.base_url, 'api', 'contents', path )) def _finish_model(self, model, location=True): """Finish a JSON request with a model, setting relevant headers, etc.""" if location: - location = self.location_url(model['name'], model['path']) + location = self.location_url(model['path']) self.set_header('Location', location) self.set_header('Last-Modified', model['last_modified']) self.finish(json.dumps(model, default=date_default)) @web.authenticated @json_errors - def get(self, path='', name=None): + def get(self, path=''): """Return a model for a file or directory. A directory model contains a list of models (without content) of the files and directories it contains. """ path = path or '' - model = self.contents_manager.get_model(name=name, path=path) + type_ = self.get_query_argument('type', default=None) + if type_ not in {None, 'directory', 'file', 'notebook'}: + raise web.HTTPError(400, u'Type %r is invalid' % type_) + + format = self.get_query_argument('format', default=None)# + if format not in {None, 'text', 'base64'}: + raise web.HTTPError(400, u'Format %r is invalid' % format) + + model = self.contents_manager.get(path=path, type_=type_, format=format) if model['type'] == 'directory': # group listing by type, then by name (case-insensitive) # FIXME: sorting should be done in the frontends @@ -69,112 +75,83 @@ class ContentsHandler(IPythonHandler): @web.authenticated @json_errors - def patch(self, path='', name=None): - """PATCH renames a notebook without re-uploading content.""" + def patch(self, path=''): + """PATCH renames a file or directory without re-uploading content.""" cm = self.contents_manager - if name is None: - raise web.HTTPError(400, u'Filename missing') model = self.get_json_body() if model is None: raise web.HTTPError(400, u'JSON body missing') - model = cm.update(model, name, path) + model = cm.update(model, path) self._finish_model(model) - def _copy(self, copy_from, path, copy_to=None): - """Copy a file, optionally specifying the new name. - """ - self.log.info(u"Copying {copy_from} to {path}/{copy_to}".format( + def _copy(self, copy_from, copy_to=None): + """Copy a file, optionally specifying a target directory.""" + self.log.info(u"Copying {copy_from} to {copy_to}".format( copy_from=copy_from, - path=path, copy_to=copy_to or '', )) - model = self.contents_manager.copy(copy_from, copy_to, path) + model = self.contents_manager.copy(copy_from, copy_to) self.set_status(201) self._finish_model(model) - def _upload(self, model, path, name=None): - """Handle upload of a new file - - If name specified, create it in path/name, - otherwise create a new untitled file in path. - """ - self.log.info(u"Uploading file to %s/%s", path, name or '') - if name: - model['name'] = name - - model = self.contents_manager.create_file(model, path) + def _upload(self, model, path): + """Handle upload of a new file to path""" + self.log.info(u"Uploading file to %s", path) + model = self.contents_manager.new(model, path) self.set_status(201) self._finish_model(model) - - def _create_empty_file(self, path, name=None, ext='.ipynb'): - """Create an empty file in path - - If name specified, create it in path/name. - """ - self.log.info(u"Creating new file in %s/%s", path, name or '') - model = {} - if name: - model['name'] = name - model = self.contents_manager.create_file(model, path=path, ext=ext) + + def _new_untitled(self, path, type='', ext=''): + """Create a new, empty untitled entity""" + self.log.info(u"Creating new %s in %s", type or 'file', path) + model = self.contents_manager.new_untitled(path=path, type=type, ext=ext) self.set_status(201) self._finish_model(model) - def _save(self, model, path, name): + def _save(self, model, path): """Save an existing file.""" - self.log.info(u"Saving file at %s/%s", path, name) - model = self.contents_manager.save(model, name, path) - if model['path'] != path.strip('/') or model['name'] != name: - # a rename happened, set Location header - location = True - else: - location = False - self._finish_model(model, location) + self.log.info(u"Saving file at %s", path) + model = self.contents_manager.save(model, path) + self._finish_model(model) @web.authenticated @json_errors - def post(self, path='', name=None): - """Create a new file or directory in the specified path. + def post(self, path=''): + """Create a new file in the specified path. - POST creates new files or directories. The server always decides on the name. + POST creates new files. The server always decides on the name. POST /api/contents/path - New untitled notebook in path. If content specified, upload a - notebook, otherwise start empty. + New untitled, empty file or directory. POST /api/contents/path - with body {"copy_from" : "OtherNotebook.ipynb"} + with body {"copy_from" : "/path/to/OtherNotebook.ipynb"} New copy of OtherNotebook in path """ - if name is not None: - path = u'{}/{}'.format(path, name) - cm = self.contents_manager if cm.file_exists(path): - raise web.HTTPError(400, "Cannot POST to existing files, use PUT instead.") + raise web.HTTPError(400, "Cannot POST to files, use PUT instead.") - if not cm.path_exists(path): + if not cm.dir_exists(path): raise web.HTTPError(404, "No such directory: %s" % path) model = self.get_json_body() if model is not None: copy_from = model.get('copy_from') - ext = model.get('ext', '.ipynb') - if model.get('content') is not None: - if copy_from: - raise web.HTTPError(400, "Can't upload and copy at the same time.") - self._upload(model, path) - elif copy_from: + ext = model.get('ext', '') + type = model.get('type', '') + if copy_from: self._copy(copy_from, path) else: - self._create_empty_file(path, ext=ext) + self._new_untitled(path, type=type, ext=ext) else: - self._create_empty_file(path) + self._new_untitled(path) @web.authenticated @json_errors - def put(self, path='', name=None): + def put(self, path=''): """Saves the file in the location specified by name and path. PUT is very similar to POST, but the requester specifies the name, @@ -184,39 +161,25 @@ class ContentsHandler(IPythonHandler): Save notebook at ``path/Name.ipynb``. Notebook structure is specified in `content` key of JSON request body. If content is not specified, create a new empty notebook. - PUT /api/contents/path/Name.ipynb - with JSON body:: - - { - "copy_from" : "[path/to/]OtherNotebook.ipynb" - } - - Copy OtherNotebook to Name """ - if name is None: - raise web.HTTPError(400, "name must be specified with PUT.") - model = self.get_json_body() if model: - copy_from = model.get('copy_from') - if copy_from: - if model.get('content'): - raise web.HTTPError(400, "Can't upload and copy at the same time.") - self._copy(copy_from, path, name) - elif self.contents_manager.file_exists(name, path): - self._save(model, path, name) + if model.get('copy_from'): + raise web.HTTPError(400, "Cannot copy with PUT, only POST") + if self.contents_manager.file_exists(path): + self._save(model, path) else: - self._upload(model, path, name) + self._upload(model, path) else: - self._create_empty_file(path, name) + self._new_untitled(path) @web.authenticated @json_errors - def delete(self, path='', name=None): + def delete(self, path=''): """delete a file in the given path""" cm = self.contents_manager - self.log.warn('delete %s:%s', path, name) - cm.delete(name, path) + self.log.warn('delete %s', path) + cm.delete(path) self.set_status(204) self.finish() @@ -227,22 +190,22 @@ class CheckpointsHandler(IPythonHandler): @web.authenticated @json_errors - def get(self, path='', name=None): + def get(self, path=''): """get lists checkpoints for a file""" cm = self.contents_manager - checkpoints = cm.list_checkpoints(name, path) + checkpoints = cm.list_checkpoints(path) data = json.dumps(checkpoints, default=date_default) self.finish(data) @web.authenticated @json_errors - def post(self, path='', name=None): + def post(self, path=''): """post creates a new checkpoint""" cm = self.contents_manager - checkpoint = cm.create_checkpoint(name, path) + checkpoint = cm.create_checkpoint(path) data = json.dumps(checkpoint, default=date_default) location = url_path_join(self.base_url, 'api/contents', - path, name, 'checkpoints', checkpoint['id']) + path, 'checkpoints', checkpoint['id']) self.set_header('Location', url_escape(location)) self.set_status(201) self.finish(data) @@ -254,22 +217,38 @@ class ModifyCheckpointsHandler(IPythonHandler): @web.authenticated @json_errors - def post(self, path, name, checkpoint_id): + def post(self, path, checkpoint_id): """post restores a file from a checkpoint""" cm = self.contents_manager - cm.restore_checkpoint(checkpoint_id, name, path) + cm.restore_checkpoint(checkpoint_id, path) self.set_status(204) self.finish() @web.authenticated @json_errors - def delete(self, path, name, checkpoint_id): + def delete(self, path, checkpoint_id): """delete clears a checkpoint for a given file""" cm = self.contents_manager - cm.delete_checkpoint(checkpoint_id, name, path) + cm.delete_checkpoint(checkpoint_id, path) self.set_status(204) self.finish() + +class NotebooksRedirectHandler(IPythonHandler): + """Redirect /api/notebooks to /api/contents""" + SUPPORTED_METHODS = ('GET', 'PUT', 'PATCH', 'POST', 'DELETE') + + def get(self, path): + self.log.warn("/api/notebooks is deprecated, use /api/contents") + self.redirect(url_path_join( + self.base_url, + 'api/contents', + path + )) + + put = patch = post = delete = get + + #----------------------------------------------------------------------------- # URL to handler mappings #----------------------------------------------------------------------------- @@ -278,9 +257,9 @@ class ModifyCheckpointsHandler(IPythonHandler): _checkpoint_id_regex = r"(?P[\w-]+)" default_handlers = [ - (r"/api/contents%s/checkpoints" % file_path_regex, CheckpointsHandler), - (r"/api/contents%s/checkpoints/%s" % (file_path_regex, _checkpoint_id_regex), + (r"/api/contents%s/checkpoints" % path_regex, CheckpointsHandler), + (r"/api/contents%s/checkpoints/%s" % (path_regex, _checkpoint_id_regex), ModifyCheckpointsHandler), - (r"/api/contents%s" % file_path_regex, ContentsHandler), (r"/api/contents%s" % path_regex, ContentsHandler), + (r"/api/notebooks/?(.*)", NotebooksRedirectHandler), ] diff --git a/IPython/html/services/contents/manager.py b/IPython/html/services/contents/manager.py index e6a11ed..1c87064 100644 --- a/IPython/html/services/contents/manager.py +++ b/IPython/html/services/contents/manager.py @@ -5,14 +5,18 @@ from fnmatch import fnmatch import itertools +import json import os +import re from tornado.web import HTTPError from IPython.config.configurable import LoggingConfigurable -from IPython.nbformat import current, sign +from IPython.nbformat import sign, validate, ValidationError +from IPython.nbformat.v4 import new_notebook from IPython.utils.traitlets import Instance, Unicode, List +copy_pat = re.compile(r'\-Copy\d*\.') class ContentsManager(LoggingConfigurable): """Base class for serving files and directories. @@ -31,14 +35,6 @@ class ContentsManager(LoggingConfigurable): - if unspecified, path defaults to '', indicating the root path. - name is also unicode, and refers to a specfic target: - - - unicode, not url-escaped - - must not contain '/' - - It refers to an individual filename - - It may refer to a directory name, - in the case of listing or creating directories. - """ notary = Instance(sign.NotebookNotary) @@ -67,7 +63,7 @@ class ContentsManager(LoggingConfigurable): # ContentsManager API part 1: methods that must be # implemented in subclasses. - def path_exists(self, path): + def dir_exists(self, path): """Does the API-style path (directory) actually exist? Like os.path.isdir @@ -103,8 +99,8 @@ class ContentsManager(LoggingConfigurable): """ raise NotImplementedError - def file_exists(self, name, path=''): - """Does a file exist at the given name and path? + def file_exists(self, path=''): + """Does a file exist at the given path? Like os.path.isfile @@ -124,15 +120,13 @@ class ContentsManager(LoggingConfigurable): """ raise NotImplementedError('must be implemented in a subclass') - def exists(self, name, path=''): - """Does a file or directory exist at the given name and path? + def exists(self, path): + """Does a file or directory exist at the given path? Like os.path.exists Parameters ---------- - name : string - The name of the file you are checking. path : string The relative path to the file's directory (with '/' as separator) @@ -141,17 +135,17 @@ class ContentsManager(LoggingConfigurable): exists : bool Whether the target exists. """ - return self.file_exists(name, path) or self.path_exists("%s/%s" % (path, name)) + return self.file_exists(path) or self.dir_exists(path) - def get_model(self, name, path='', content=True): + def get(self, path, content=True, type_=None, format=None): """Get the model of a file or directory with or without content.""" raise NotImplementedError('must be implemented in a subclass') - def save(self, model, name, path=''): + def save(self, model, path): """Save the file or directory and return the model with no content.""" raise NotImplementedError('must be implemented in a subclass') - def update(self, model, name, path=''): + def update(self, model, path): """Update the file or directory and return the model with no content. For use in PATCH requests, to enable renaming a file without @@ -159,26 +153,26 @@ class ContentsManager(LoggingConfigurable): """ raise NotImplementedError('must be implemented in a subclass') - def delete(self, name, path=''): - """Delete file or directory by name and path.""" + def delete(self, path): + """Delete file or directory by path.""" raise NotImplementedError('must be implemented in a subclass') - def create_checkpoint(self, name, path=''): + def create_checkpoint(self, path): """Create a checkpoint of the current state of a file Returns a checkpoint_id for the new checkpoint. """ raise NotImplementedError("must be implemented in a subclass") - def list_checkpoints(self, name, path=''): + def list_checkpoints(self, path): """Return a list of checkpoints for a given file""" return [] - def restore_checkpoint(self, checkpoint_id, name, path=''): + def restore_checkpoint(self, checkpoint_id, path): """Restore a file from one of its checkpoints""" raise NotImplementedError("must be implemented in a subclass") - def delete_checkpoint(self, checkpoint_id, name, path=''): + def delete_checkpoint(self, checkpoint_id, path): """delete a checkpoint for a file""" raise NotImplementedError("must be implemented in a subclass") @@ -188,11 +182,19 @@ class ContentsManager(LoggingConfigurable): def info_string(self): return "Serving contents" - def get_kernel_path(self, name, path='', model=None): - """ Return the path to start kernel in """ - return path + def get_kernel_path(self, path, model=None): + """Return the API path for the kernel + + KernelManagers can turn this value into a filesystem path, + or ignore it altogether. - def increment_filename(self, filename, path=''): + The default value here will start kernels in the directory of the + notebook server. FileContentsManager overrides this to use the + directory containing the notebook. + """ + return '' + + def increment_filename(self, filename, path='', insert=''): """Increment a filename until it is unique. Parameters @@ -210,87 +212,140 @@ class ContentsManager(LoggingConfigurable): path = path.strip('/') basename, ext = os.path.splitext(filename) for i in itertools.count(): - name = u'{basename}{i}{ext}'.format(basename=basename, i=i, - ext=ext) - if not self.file_exists(name, path): + if i: + insert_i = '{}{}'.format(insert, i) + else: + insert_i = '' + name = u'{basename}{insert}{ext}'.format(basename=basename, + insert=insert_i, ext=ext) + if not self.exists(u'{}/{}'.format(path, name)): break return name - def create_file(self, model=None, path='', ext='.ipynb'): - """Create a new file or directory and return its model with no content.""" + def validate_notebook_model(self, model): + """Add failed-validation message to model""" + try: + validate(model['content']) + except ValidationError as e: + model['message'] = u'Notebook Validation failed: {}:\n{}'.format( + e.message, json.dumps(e.instance, indent=1, default=lambda obj: ''), + ) + return model + + def new_untitled(self, path='', type='', ext=''): + """Create a new untitled file or directory in path + + path must be a directory + + File extension can be specified. + + Use `new` to create files with a fully specified path (including filename). + """ + path = path.strip('/') + if not self.dir_exists(path): + raise HTTPError(404, 'No such directory: %s' % path) + + model = {} + if type: + model['type'] = type + + if ext == '.ipynb': + model.setdefault('type', 'notebook') + else: + model.setdefault('type', 'file') + + insert = '' + if model['type'] == 'directory': + untitled = self.untitled_directory + insert = ' ' + elif model['type'] == 'notebook': + untitled = self.untitled_notebook + ext = '.ipynb' + elif model['type'] == 'file': + untitled = self.untitled_file + else: + raise HTTPError(400, "Unexpected model type: %r" % model['type']) + + name = self.increment_filename(untitled + ext, path, insert=insert) + path = u'{0}/{1}'.format(path, name) + return self.new(model, path) + + def new(self, model=None, path=''): + """Create a new file or directory and return its model with no content. + + To create a new untitled entity in a directory, use `new_untitled`. + """ path = path.strip('/') if model is None: model = {} - if 'content' not in model and model.get('type', None) != 'directory': - if ext == '.ipynb': - metadata = current.new_metadata(name=u'') - model['content'] = current.new_notebook(metadata=metadata) - model['type'] = 'notebook' + + if path.endswith('.ipynb'): + model.setdefault('type', 'notebook') + else: + model.setdefault('type', 'file') + + # no content, not a directory, so fill out new-file model + if 'content' not in model and model['type'] != 'directory': + if model['type'] == 'notebook': + model['content'] = new_notebook() model['format'] = 'json' else: model['content'] = '' model['type'] = 'file' model['format'] = 'text' - if 'name' not in model: - if model['type'] == 'directory': - untitled = self.untitled_directory - elif model['type'] == 'notebook': - untitled = self.untitled_notebook - elif model['type'] == 'file': - untitled = self.untitled_file - else: - raise HTTPError(400, "Unexpected model type: %r" % model['type']) - model['name'] = self.increment_filename(untitled + ext, path) - - model['path'] = path - model = self.save(model, model['name'], model['path']) + + model = self.save(model, path) return model - def copy(self, from_name, to_name=None, path=''): + def copy(self, from_path, to_path=None): """Copy an existing file and return its new model. - If to_name not specified, increment `from_name-Copy#.ext`. + If to_path not specified, it will be the parent directory of from_path. + If to_path is a directory, filename will increment `from_path-Copy#.ext`. - copy_from can be a full path to a file, - or just a base name. If a base name, `path` is used. + from_path must be a full path to a file. """ - path = path.strip('/') - if '/' in from_name: - from_path, from_name = from_name.rsplit('/', 1) + path = from_path.strip('/') + if '/' in path: + from_dir, from_name = path.rsplit('/', 1) else: - from_path = path - model = self.get_model(from_name, from_path) + from_dir = '' + from_name = path + + model = self.get(path) + model.pop('path', None) + model.pop('name', None) if model['type'] == 'directory': raise HTTPError(400, "Can't copy directories") - if not to_name: - base, ext = os.path.splitext(from_name) - copy_name = u'{0}-Copy{1}'.format(base, ext) - to_name = self.increment_filename(copy_name, path) - model['name'] = to_name - model['path'] = path - model = self.save(model, to_name, path) + + if not to_path: + to_path = from_dir + if self.dir_exists(to_path): + name = copy_pat.sub(u'.', from_name) + to_name = self.increment_filename(name, to_path, insert='-Copy') + to_path = u'{0}/{1}'.format(to_path, to_name) + + model = self.save(model, to_path) return model def log_info(self): self.log.info(self.info_string()) - def trust_notebook(self, name, path=''): + def trust_notebook(self, path): """Explicitly trust a notebook Parameters ---------- - name : string - The filename of the notebook path : string - The notebook's directory + The path of a notebook """ - model = self.get_model(name, path) + model = self.get(path) nb = model['content'] - self.log.warn("Trusting notebook %s/%s", path, name) + self.log.warn("Trusting notebook %s", path) self.notary.mark_cells(nb, True) - self.save(model, name, path) + self.save(model, path) - def check_and_sign(self, nb, name='', path=''): + def check_and_sign(self, nb, path=''): """Check for trusted cells, and sign the notebook. Called as a part of saving notebooks. @@ -298,18 +353,16 @@ class ContentsManager(LoggingConfigurable): Parameters ---------- nb : dict - The notebook object (in nbformat.current format) - name : string - The filename of the notebook (for logging) + The notebook dict path : string - The notebook's directory (for logging) + The notebook's path (for logging) """ if self.notary.check_cells(nb): self.notary.sign(nb) else: - self.log.warn("Saving untrusted notebook %s/%s", path, name) + self.log.warn("Saving untrusted notebook %s", path) - def mark_trusted_cells(self, nb, name='', path=''): + def mark_trusted_cells(self, nb, path=''): """Mark cells as trusted if the notebook signature matches. Called as a part of loading notebooks. @@ -317,15 +370,13 @@ class ContentsManager(LoggingConfigurable): Parameters ---------- nb : dict - The notebook object (in nbformat.current format) - name : string - The filename of the notebook (for logging) + The notebook object (in current nbformat) path : string - The notebook's directory (for logging) + The notebook's path (for logging) """ trusted = self.notary.check_signature(nb) if not trusted: - self.log.warn("Notebook %s/%s is not trusted", path, name) + self.log.warn("Notebook %s is not trusted", path) self.notary.mark_cells(nb, trusted) def should_list(self, name): diff --git a/IPython/html/services/contents/tests/test_contents_api.py b/IPython/html/services/contents/tests/test_contents_api.py index bac91de..bda7c77 100644 --- a/IPython/html/services/contents/tests/test_contents_api.py +++ b/IPython/html/services/contents/tests/test_contents_api.py @@ -14,9 +14,10 @@ import requests from IPython.html.utils import url_path_join, url_escape from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error -from IPython.nbformat import current -from IPython.nbformat.current import (new_notebook, write, read, new_worksheet, - new_heading_cell, to_notebook_json) +from IPython.nbformat import read, write, from_dict +from IPython.nbformat.v4 import ( + new_notebook, new_markdown_cell, +) from IPython.nbformat import v2 from IPython.utils import py3compat from IPython.utils.data import uniq_stable @@ -34,10 +35,10 @@ class API(object): def __init__(self, base_url): self.base_url = base_url - def _req(self, verb, path, body=None): + def _req(self, verb, path, body=None, params=None): response = requests.request(verb, url_path_join(self.base_url, 'api/contents', path), - data=body, + data=body, params=params, ) response.raise_for_status() return response @@ -45,56 +46,64 @@ class API(object): def list(self, path='/'): return self._req('GET', path) - def read(self, name, path='/'): - return self._req('GET', url_path_join(path, name)) + def read(self, path, type_=None, format=None): + params = {} + if type_ is not None: + params['type'] = type_ + if format is not None: + params['format'] = format + return self._req('GET', path, params=params) - def create_untitled(self, path='/', ext=None): + def create_untitled(self, path='/', ext='.ipynb'): body = None if ext: body = json.dumps({'ext': ext}) return self._req('POST', path, body) - def upload_untitled(self, body, path='/'): - return self._req('POST', path, body) + def mkdir_untitled(self, path='/'): + return self._req('POST', path, json.dumps({'type': 'directory'})) - def copy_untitled(self, copy_from, path='/'): + def copy(self, copy_from, path='/'): body = json.dumps({'copy_from':copy_from}) return self._req('POST', path, body) - def create(self, name, path='/'): - return self._req('PUT', url_path_join(path, name)) + def create(self, path='/'): + return self._req('PUT', path) + + def upload(self, path, body): + return self._req('PUT', path, body) - def upload(self, name, body, path='/'): - return self._req('PUT', url_path_join(path, name), body) + def mkdir_untitled(self, path='/'): + return self._req('POST', path, json.dumps({'type': 'directory'})) - def mkdir(self, name, path='/'): - return self._req('PUT', url_path_join(path, name), json.dumps({'type': 'directory'})) + def mkdir(self, path='/'): + return self._req('PUT', path, json.dumps({'type': 'directory'})) - def copy(self, copy_from, copy_to, path='/'): + def copy_put(self, copy_from, path='/'): body = json.dumps({'copy_from':copy_from}) - return self._req('PUT', url_path_join(path, copy_to), body) + return self._req('PUT', path, body) - def save(self, name, body, path='/'): - return self._req('PUT', url_path_join(path, name), body) + def save(self, path, body): + return self._req('PUT', path, body) - def delete(self, name, path='/'): - return self._req('DELETE', url_path_join(path, name)) + def delete(self, path='/'): + return self._req('DELETE', path) - def rename(self, name, path, new_name): - body = json.dumps({'name': new_name}) - return self._req('PATCH', url_path_join(path, name), body) + def rename(self, path, new_path): + body = json.dumps({'path': new_path}) + return self._req('PATCH', path, body) - def get_checkpoints(self, name, path): - return self._req('GET', url_path_join(path, name, 'checkpoints')) + def get_checkpoints(self, path): + return self._req('GET', url_path_join(path, 'checkpoints')) - def new_checkpoint(self, name, path): - return self._req('POST', url_path_join(path, name, 'checkpoints')) + def new_checkpoint(self, path): + return self._req('POST', url_path_join(path, 'checkpoints')) - def restore_checkpoint(self, name, path, checkpoint_id): - return self._req('POST', url_path_join(path, name, 'checkpoints', checkpoint_id)) + def restore_checkpoint(self, path, checkpoint_id): + return self._req('POST', url_path_join(path, 'checkpoints', checkpoint_id)) - def delete_checkpoint(self, name, path, checkpoint_id): - return self._req('DELETE', url_path_join(path, name, 'checkpoints', checkpoint_id)) + def delete_checkpoint(self, path, checkpoint_id): + return self._req('DELETE', url_path_join(path, 'checkpoints', checkpoint_id)) class APITest(NotebookTestBase): """Test the kernels web service API""" @@ -130,8 +139,6 @@ class APITest(NotebookTestBase): self.blob = os.urandom(100) self.b64_blob = base64.encodestring(self.blob).decode('ascii') - - for d in (self.dirs + self.hidden_dirs): d.replace('/', os.sep) if not os.path.isdir(pjoin(nbdir, d)): @@ -142,8 +149,8 @@ class APITest(NotebookTestBase): # create a notebook with io.open(pjoin(nbdir, d, '%s.ipynb' % name), 'w', encoding='utf-8') as f: - nb = new_notebook(name=name) - write(nb, f, format='ipynb') + nb = new_notebook() + write(nb, f, version=4) # create a text file with io.open(pjoin(nbdir, d, '%s.txt' % name), 'w', @@ -177,12 +184,12 @@ class APITest(NotebookTestBase): nbs = notebooks_only(self.api.list(u'/unicodé/').json()) self.assertEqual(len(nbs), 1) self.assertEqual(nbs[0]['name'], 'innonascii.ipynb') - self.assertEqual(nbs[0]['path'], u'unicodé') + self.assertEqual(nbs[0]['path'], u'unicodé/innonascii.ipynb') nbs = notebooks_only(self.api.list('/foo/bar/').json()) self.assertEqual(len(nbs), 1) self.assertEqual(nbs[0]['name'], 'baz.ipynb') - self.assertEqual(nbs[0]['path'], 'foo/bar') + self.assertEqual(nbs[0]['path'], 'foo/bar/baz.ipynb') nbs = notebooks_only(self.api.list('foo').json()) self.assertEqual(len(nbs), 4) @@ -197,8 +204,11 @@ class APITest(NotebookTestBase): self.assertEqual(nbnames, expected) def test_list_dirs(self): + print(self.api.list().json()) dirs = dirs_only(self.api.list().json()) dir_names = {normalize('NFC', d['name']) for d in dirs} + print(dir_names) + print(self.top_level_dirs) self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs def test_list_nonexistant_dir(self): @@ -207,8 +217,10 @@ class APITest(NotebookTestBase): def test_get_nb_contents(self): for d, name in self.dirs_nbs: - nb = self.api.read('%s.ipynb' % name, d+'/').json() + path = url_path_join(d, name + '.ipynb') + nb = self.api.read(path).json() self.assertEqual(nb['name'], u'%s.ipynb' % name) + self.assertEqual(nb['path'], path) self.assertEqual(nb['type'], 'notebook') self.assertIn('content', nb) self.assertEqual(nb['format'], 'json') @@ -219,12 +231,14 @@ class APITest(NotebookTestBase): def test_get_contents_no_such_file(self): # Name that doesn't exist - should be a 404 with assert_http_error(404): - self.api.read('q.ipynb', 'foo') + self.api.read('foo/q.ipynb') def test_get_text_file_contents(self): for d, name in self.dirs_nbs: - model = self.api.read(u'%s.txt' % name, d+'/').json() + path = url_path_join(d, name + '.txt') + model = self.api.read(path).json() self.assertEqual(model['name'], u'%s.txt' % name) + self.assertEqual(model['path'], path) self.assertIn('content', model) self.assertEqual(model['format'], 'text') self.assertEqual(model['type'], 'file') @@ -232,12 +246,18 @@ class APITest(NotebookTestBase): # Name that doesn't exist - should be a 404 with assert_http_error(404): - self.api.read('q.txt', 'foo') + self.api.read('foo/q.txt') + + # Specifying format=text should fail on a non-UTF-8 file + with assert_http_error(400): + self.api.read('foo/bar/baz.blob', type_='file', format='text') def test_get_binary_file_contents(self): for d, name in self.dirs_nbs: - model = self.api.read(u'%s.blob' % name, d+'/').json() + path = url_path_join(d, name + '.blob') + model = self.api.read(path).json() self.assertEqual(model['name'], u'%s.blob' % name) + self.assertEqual(model['path'], path) self.assertIn('content', model) self.assertEqual(model['format'], 'base64') self.assertEqual(model['type'], 'file') @@ -246,66 +266,78 @@ class APITest(NotebookTestBase): # Name that doesn't exist - should be a 404 with assert_http_error(404): - self.api.read('q.txt', 'foo') + self.api.read('foo/q.txt') - def _check_created(self, resp, name, path, type='notebook'): + def test_get_bad_type(self): + with assert_http_error(400): + self.api.read(u'unicodé', type_='file') # this is a directory + + with assert_http_error(400): + self.api.read(u'unicodé/innonascii.ipynb', type_='directory') + + def _check_created(self, resp, path, type='notebook'): self.assertEqual(resp.status_code, 201) location_header = py3compat.str_to_unicode(resp.headers['Location']) - self.assertEqual(location_header, url_escape(url_path_join(u'/api/contents', path, name))) + self.assertEqual(location_header, url_escape(url_path_join(u'/api/contents', path))) rjson = resp.json() - self.assertEqual(rjson['name'], name) + self.assertEqual(rjson['name'], path.rsplit('/', 1)[-1]) self.assertEqual(rjson['path'], path) self.assertEqual(rjson['type'], type) isright = os.path.isdir if type == 'directory' else os.path.isfile assert isright(pjoin( self.notebook_dir.name, path.replace('/', os.sep), - name, )) def test_create_untitled(self): resp = self.api.create_untitled(path=u'å b') - self._check_created(resp, 'Untitled0.ipynb', u'å b') + self._check_created(resp, u'å b/Untitled.ipynb') # Second time resp = self.api.create_untitled(path=u'å b') - self._check_created(resp, 'Untitled1.ipynb', u'å b') + self._check_created(resp, u'å b/Untitled1.ipynb') # And two directories down resp = self.api.create_untitled(path='foo/bar') - self._check_created(resp, 'Untitled0.ipynb', 'foo/bar') + self._check_created(resp, 'foo/bar/Untitled.ipynb') def test_create_untitled_txt(self): resp = self.api.create_untitled(path='foo/bar', ext='.txt') - self._check_created(resp, 'untitled0.txt', 'foo/bar', type='file') + self._check_created(resp, 'foo/bar/untitled.txt', type='file') - resp = self.api.read(path='foo/bar', name='untitled0.txt') + resp = self.api.read(path='foo/bar/untitled.txt') model = resp.json() self.assertEqual(model['type'], 'file') self.assertEqual(model['format'], 'text') self.assertEqual(model['content'], '') - def test_upload_untitled(self): - nb = new_notebook(name='Upload test') - nbmodel = {'content': nb, 'type': 'notebook'} - resp = self.api.upload_untitled(path=u'å b', - body=json.dumps(nbmodel)) - self._check_created(resp, 'Untitled0.ipynb', u'å b') - def test_upload(self): - nb = new_notebook(name=u'ignored') + nb = new_notebook() nbmodel = {'content': nb, 'type': 'notebook'} - resp = self.api.upload(u'Upload tést.ipynb', path=u'å b', - body=json.dumps(nbmodel)) - self._check_created(resp, u'Upload tést.ipynb', u'å b') + path = u'å b/Upload tést.ipynb' + resp = self.api.upload(path, body=json.dumps(nbmodel)) + self._check_created(resp, path) + + def test_mkdir_untitled(self): + resp = self.api.mkdir_untitled(path=u'å b') + self._check_created(resp, u'å b/Untitled Folder', type='directory') + + # Second time + resp = self.api.mkdir_untitled(path=u'å b') + self._check_created(resp, u'å b/Untitled Folder 1', type='directory') + + # And two directories down + resp = self.api.mkdir_untitled(path='foo/bar') + self._check_created(resp, 'foo/bar/Untitled Folder', type='directory') def test_mkdir(self): - resp = self.api.mkdir(u'New ∂ir', path=u'å b') - self._check_created(resp, u'New ∂ir', u'å b', type='directory') + path = u'å b/New ∂ir' + resp = self.api.mkdir(path) + self._check_created(resp, path, type='directory') def test_mkdir_hidden_400(self): with assert_http_error(400): - resp = self.api.mkdir(u'.hidden', path=u'å b') + resp = self.api.mkdir(u'å b/.hidden') def test_upload_txt(self): body = u'ünicode téxt' @@ -314,11 +346,11 @@ class APITest(NotebookTestBase): 'format' : 'text', 'type' : 'file', } - resp = self.api.upload(u'Upload tést.txt', path=u'å b', - body=json.dumps(model)) + path = u'å b/Upload tést.txt' + resp = self.api.upload(path, body=json.dumps(model)) # check roundtrip - resp = self.api.read(path=u'å b', name=u'Upload tést.txt') + resp = self.api.read(path) model = resp.json() self.assertEqual(model['type'], 'file') self.assertEqual(model['format'], 'text') @@ -332,13 +364,14 @@ class APITest(NotebookTestBase): 'format' : 'base64', 'type' : 'file', } - resp = self.api.upload(u'Upload tést.blob', path=u'å b', - body=json.dumps(model)) + path = u'å b/Upload tést.blob' + resp = self.api.upload(path, body=json.dumps(model)) # check roundtrip - resp = self.api.read(path=u'å b', name=u'Upload tést.blob') + resp = self.api.read(path) model = resp.json() self.assertEqual(model['type'], 'file') + self.assertEqual(model['path'], path) self.assertEqual(model['format'], 'base64') decoded = base64.decodestring(model['content'].encode('ascii')) self.assertEqual(decoded, body) @@ -349,46 +382,62 @@ class APITest(NotebookTestBase): nb.worksheets.append(ws) ws.cells.append(v2.new_code_cell(input='print("hi")')) nbmodel = {'content': nb, 'type': 'notebook'} - resp = self.api.upload(u'Upload tést.ipynb', path=u'å b', - body=json.dumps(nbmodel)) - self._check_created(resp, u'Upload tést.ipynb', u'å b') - resp = self.api.read(u'Upload tést.ipynb', u'å b') + path = u'å b/Upload tést.ipynb' + resp = self.api.upload(path, body=json.dumps(nbmodel)) + self._check_created(resp, path) + resp = self.api.read(path) data = resp.json() - self.assertEqual(data['content']['nbformat'], current.nbformat) - self.assertEqual(data['content']['orig_nbformat'], 2) - - def test_copy_untitled(self): - resp = self.api.copy_untitled(u'ç d.ipynb', path=u'å b') - self._check_created(resp, u'ç d-Copy0.ipynb', u'å b') + self.assertEqual(data['content']['nbformat'], 4) def test_copy(self): - resp = self.api.copy(u'ç d.ipynb', u'cøpy.ipynb', path=u'å b') - self._check_created(resp, u'cøpy.ipynb', u'å b') - + resp = self.api.copy(u'å b/ç d.ipynb', u'å b') + self._check_created(resp, u'å b/ç d-Copy1.ipynb') + + resp = self.api.copy(u'å b/ç d.ipynb', u'å b') + self._check_created(resp, u'å b/ç d-Copy2.ipynb') + + def test_copy_copy(self): + resp = self.api.copy(u'å b/ç d.ipynb', u'å b') + self._check_created(resp, u'å b/ç d-Copy1.ipynb') + + resp = self.api.copy(u'å b/ç d-Copy1.ipynb', u'å b') + self._check_created(resp, u'å b/ç d-Copy2.ipynb') + def test_copy_path(self): - resp = self.api.copy(u'foo/a.ipynb', u'cøpyfoo.ipynb', path=u'å b') - self._check_created(resp, u'cøpyfoo.ipynb', u'å b') + resp = self.api.copy(u'foo/a.ipynb', u'å b') + self._check_created(resp, u'å b/a.ipynb') + + resp = self.api.copy(u'foo/a.ipynb', u'å b') + self._check_created(resp, u'å b/a-Copy1.ipynb') + + def test_copy_put_400(self): + with assert_http_error(400): + resp = self.api.copy_put(u'å b/ç d.ipynb', u'å b/cøpy.ipynb') def test_copy_dir_400(self): # can't copy directories with assert_http_error(400): - resp = self.api.copy(u'å b', u'å c') + resp = self.api.copy(u'å b', u'foo') def test_delete(self): for d, name in self.dirs_nbs: - resp = self.api.delete('%s.ipynb' % name, d) + print('%r, %r' % (d, name)) + resp = self.api.delete(url_path_join(d, name + '.ipynb')) self.assertEqual(resp.status_code, 204) for d in self.dirs + ['/']: nbs = notebooks_only(self.api.list(d).json()) - self.assertEqual(len(nbs), 0) + print('------') + print(d) + print(nbs) + self.assertEqual(nbs, []) def test_delete_dirs(self): # depth-first delete everything, so we don't try to delete empty directories for name in sorted(self.dirs + ['/'], key=len, reverse=True): listing = self.api.list(name).json()['content'] for model in listing: - self.api.delete(model['name'], model['path']) + self.api.delete(model['path']) listing = self.api.list('/').json()['content'] self.assertEqual(listing, []) @@ -398,9 +447,10 @@ class APITest(NotebookTestBase): self.api.delete(u'å b') def test_rename(self): - resp = self.api.rename('a.ipynb', 'foo', 'z.ipynb') + resp = self.api.rename('foo/a.ipynb', 'foo/z.ipynb') self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb') self.assertEqual(resp.json()['name'], 'z.ipynb') + self.assertEqual(resp.json()['path'], 'foo/z.ipynb') assert os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'z.ipynb')) nbs = notebooks_only(self.api.list('foo').json()) @@ -410,43 +460,31 @@ class APITest(NotebookTestBase): def test_rename_existing(self): with assert_http_error(409): - self.api.rename('a.ipynb', 'foo', 'b.ipynb') + self.api.rename('foo/a.ipynb', 'foo/b.ipynb') def test_save(self): - resp = self.api.read('a.ipynb', 'foo') + resp = self.api.read('foo/a.ipynb') nbcontent = json.loads(resp.text)['content'] - nb = to_notebook_json(nbcontent) - ws = new_worksheet() - nb.worksheets = [ws] - ws.cells.append(new_heading_cell(u'Created by test ³')) + nb = from_dict(nbcontent) + nb.cells.append(new_markdown_cell(u'Created by test ³')) - nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'} - resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel)) + nbmodel= {'content': nb, 'type': 'notebook'} + resp = self.api.save('foo/a.ipynb', body=json.dumps(nbmodel)) nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb') with io.open(nbfile, 'r', encoding='utf-8') as f: - newnb = read(f, format='ipynb') - self.assertEqual(newnb.worksheets[0].cells[0].source, + newnb = read(f, as_version=4) + self.assertEqual(newnb.cells[0].source, u'Created by test ³') - nbcontent = self.api.read('a.ipynb', 'foo').json()['content'] - newnb = to_notebook_json(nbcontent) - self.assertEqual(newnb.worksheets[0].cells[0].source, + nbcontent = self.api.read('foo/a.ipynb').json()['content'] + newnb = from_dict(nbcontent) + self.assertEqual(newnb.cells[0].source, u'Created by test ³') - # Save and rename - nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb, 'type': 'notebook'} - resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel)) - saved = resp.json() - self.assertEqual(saved['name'], 'a2.ipynb') - self.assertEqual(saved['path'], 'foo/bar') - assert os.path.isfile(pjoin(self.notebook_dir.name,'foo','bar','a2.ipynb')) - assert not os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')) - with assert_http_error(404): - self.api.read('a.ipynb', 'foo') def test_checkpoints(self): - resp = self.api.read('a.ipynb', 'foo') - r = self.api.new_checkpoint('a.ipynb', 'foo') + resp = self.api.read('foo/a.ipynb') + r = self.api.new_checkpoint('foo/a.ipynb') self.assertEqual(r.status_code, 201) cp1 = r.json() self.assertEqual(set(cp1), {'id', 'last_modified'}) @@ -454,32 +492,30 @@ class APITest(NotebookTestBase): # Modify it nbcontent = json.loads(resp.text)['content'] - nb = to_notebook_json(nbcontent) - ws = new_worksheet() - nb.worksheets = [ws] - hcell = new_heading_cell('Created by test') - ws.cells.append(hcell) + nb = from_dict(nbcontent) + hcell = new_markdown_cell('Created by test') + nb.cells.append(hcell) # Save - nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb, 'type': 'notebook'} - resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel)) + nbmodel= {'content': nb, 'type': 'notebook'} + resp = self.api.save('foo/a.ipynb', body=json.dumps(nbmodel)) # List checkpoints - cps = self.api.get_checkpoints('a.ipynb', 'foo').json() + cps = self.api.get_checkpoints('foo/a.ipynb').json() self.assertEqual(cps, [cp1]) - nbcontent = self.api.read('a.ipynb', 'foo').json()['content'] - nb = to_notebook_json(nbcontent) - self.assertEqual(nb.worksheets[0].cells[0].source, 'Created by test') + nbcontent = self.api.read('foo/a.ipynb').json()['content'] + nb = from_dict(nbcontent) + self.assertEqual(nb.cells[0].source, 'Created by test') # Restore cp1 - r = self.api.restore_checkpoint('a.ipynb', 'foo', cp1['id']) + r = self.api.restore_checkpoint('foo/a.ipynb', cp1['id']) self.assertEqual(r.status_code, 204) - nbcontent = self.api.read('a.ipynb', 'foo').json()['content'] - nb = to_notebook_json(nbcontent) - self.assertEqual(nb.worksheets, []) + nbcontent = self.api.read('foo/a.ipynb').json()['content'] + nb = from_dict(nbcontent) + self.assertEqual(nb.cells, []) # Delete cp1 - r = self.api.delete_checkpoint('a.ipynb', 'foo', cp1['id']) + r = self.api.delete_checkpoint('foo/a.ipynb', cp1['id']) self.assertEqual(r.status_code, 204) - cps = self.api.get_checkpoints('a.ipynb', 'foo').json() + cps = self.api.get_checkpoints('foo/a.ipynb').json() self.assertEqual(cps, []) diff --git a/IPython/html/services/contents/tests/test_manager.py b/IPython/html/services/contents/tests/test_manager.py index 26bd7e3..647578c 100644 --- a/IPython/html/services/contents/tests/test_manager.py +++ b/IPython/html/services/contents/tests/test_manager.py @@ -9,7 +9,7 @@ from tornado.web import HTTPError from unittest import TestCase from tempfile import NamedTemporaryFile -from IPython.nbformat import current +from IPython.nbformat import v4 as nbformat from IPython.utils.tempdir import TemporaryDirectory from IPython.utils.traitlets import TraitError @@ -42,7 +42,7 @@ class TestFileContentsManager(TestCase): with TemporaryDirectory() as td: root = td fm = FileContentsManager(root_dir=root) - path = fm._get_os_path('test.ipynb', '/path/to/notebook/') + path = fm._get_os_path('/path/to/notebook/test.ipynb') rel_path_list = '/path/to/notebook/test.ipynb'.split('/') fs_path = os.path.join(fm.root_dir, *rel_path_list) self.assertEqual(path, fs_path) @@ -53,7 +53,7 @@ class TestFileContentsManager(TestCase): self.assertEqual(path, fs_path) fm = FileContentsManager(root_dir=root) - path = fm._get_os_path('test.ipynb', '////') + path = fm._get_os_path('////test.ipynb') fs_path = os.path.join(fm.root_dir, 'test.ipynb') self.assertEqual(path, fs_path) @@ -64,8 +64,8 @@ class TestFileContentsManager(TestCase): root = td os.mkdir(os.path.join(td, subd)) fm = FileContentsManager(root_dir=root) - cp_dir = fm.get_checkpoint_path('cp', 'test.ipynb', '/') - cp_subdir = fm.get_checkpoint_path('cp', 'test.ipynb', '/%s/' % subd) + cp_dir = fm.get_checkpoint_path('cp', 'test.ipynb') + cp_subdir = fm.get_checkpoint_path('cp', '/%s/test.ipynb' % subd) self.assertNotEqual(cp_dir, cp_subdir) self.assertEqual(cp_dir, os.path.join(root, fm.checkpoint_dir, cp_name)) self.assertEqual(cp_subdir, os.path.join(root, subd, fm.checkpoint_dir, cp_name)) @@ -95,71 +95,98 @@ class TestContentsManager(TestCase): return os_path def add_code_cell(self, nb): - output = current.new_output("display_data", output_javascript="alert('hi');") - cell = current.new_code_cell("print('hi')", outputs=[output]) - if not nb.worksheets: - nb.worksheets.append(current.new_worksheet()) - nb.worksheets[0].cells.append(cell) + output = nbformat.new_output("display_data", {'application/javascript': "alert('hi');"}) + cell = nbformat.new_code_cell("print('hi')", outputs=[output]) + nb.cells.append(cell) def new_notebook(self): cm = self.contents_manager - model = cm.create_file() + model = cm.new_untitled(type='notebook') name = model['name'] path = model['path'] - full_model = cm.get_model(name, path) + full_model = cm.get(path) nb = full_model['content'] self.add_code_cell(nb) - cm.save(full_model, name, path) + cm.save(full_model, path) return nb, name, path - def test_create_file(self): + def test_new_untitled(self): cm = self.contents_manager # Test in root directory - model = cm.create_file() + model = cm.new_untitled(type='notebook') assert isinstance(model, dict) self.assertIn('name', model) self.assertIn('path', model) - self.assertEqual(model['name'], 'Untitled0.ipynb') - self.assertEqual(model['path'], '') + self.assertIn('type', model) + self.assertEqual(model['type'], 'notebook') + self.assertEqual(model['name'], 'Untitled.ipynb') + self.assertEqual(model['path'], 'Untitled.ipynb') # Test in sub-directory - sub_dir = '/foo/' - self.make_dir(cm.root_dir, 'foo') - model = cm.create_file(None, sub_dir) + model = cm.new_untitled(type='directory') assert isinstance(model, dict) self.assertIn('name', model) self.assertIn('path', model) - self.assertEqual(model['name'], 'Untitled0.ipynb') - self.assertEqual(model['path'], sub_dir.strip('/')) + self.assertIn('type', model) + self.assertEqual(model['type'], 'directory') + self.assertEqual(model['name'], 'Untitled Folder') + self.assertEqual(model['path'], 'Untitled Folder') + sub_dir = model['path'] + + model = cm.new_untitled(path=sub_dir) + assert isinstance(model, dict) + self.assertIn('name', model) + self.assertIn('path', model) + self.assertIn('type', model) + self.assertEqual(model['type'], 'file') + self.assertEqual(model['name'], 'untitled') + self.assertEqual(model['path'], '%s/untitled' % sub_dir) def test_get(self): cm = self.contents_manager # Create a notebook - model = cm.create_file() + model = cm.new_untitled(type='notebook') name = model['name'] path = model['path'] # Check that we 'get' on the notebook we just created - model2 = cm.get_model(name, path) + model2 = cm.get(path) assert isinstance(model2, dict) self.assertIn('name', model2) self.assertIn('path', model2) self.assertEqual(model['name'], name) self.assertEqual(model['path'], path) + nb_as_file = cm.get(path, content=True, type_='file') + self.assertEqual(nb_as_file['path'], path) + self.assertEqual(nb_as_file['type'], 'file') + self.assertEqual(nb_as_file['format'], 'text') + self.assertNotIsInstance(nb_as_file['content'], dict) + + nb_as_bin_file = cm.get(path, content=True, type_='file', format='base64') + self.assertEqual(nb_as_bin_file['format'], 'base64') + # Test in sub-directory sub_dir = '/foo/' self.make_dir(cm.root_dir, 'foo') - model = cm.create_file(None, sub_dir) - model2 = cm.get_model(name, sub_dir) + model = cm.new_untitled(path=sub_dir, ext='.ipynb') + model2 = cm.get(sub_dir + name) assert isinstance(model2, dict) self.assertIn('name', model2) self.assertIn('path', model2) self.assertIn('content', model2) - self.assertEqual(model2['name'], 'Untitled0.ipynb') - self.assertEqual(model2['path'], sub_dir.strip('/')) + self.assertEqual(model2['name'], 'Untitled.ipynb') + self.assertEqual(model2['path'], '{0}/{1}'.format(sub_dir.strip('/'), name)) + + # Test getting directory model + dirmodel = cm.get('foo') + self.assertEqual(dirmodel['type'], 'directory') + + with self.assertRaises(HTTPError): + cm.get('foo', type_='file') + @dec.skip_win32 def test_bad_symlink(self): @@ -167,26 +194,27 @@ class TestContentsManager(TestCase): path = 'test bad symlink' os_path = self.make_dir(cm.root_dir, path) - file_model = cm.create_file(path=path, ext='.txt') + file_model = cm.new_untitled(path=path, ext='.txt') # create a broken symlink os.symlink("target", os.path.join(os_path, "bad symlink")) - model = cm.get_model(path) + model = cm.get(path) self.assertEqual(model['content'], [file_model]) @dec.skip_win32 def test_good_symlink(self): cm = self.contents_manager - path = 'test good symlink' - os_path = self.make_dir(cm.root_dir, path) + parent = 'test good symlink' + name = 'good symlink' + path = '{0}/{1}'.format(parent, name) + os_path = self.make_dir(cm.root_dir, parent) - file_model = cm.create_file(path=path, ext='.txt') + file_model = cm.new(path=parent + '/zfoo.txt') # create a good symlink - os.symlink(file_model['name'], os.path.join(os_path, "good symlink")) - symlink_model = cm.get_model(name="good symlink", path=path, content=False) - - dir_model = cm.get_model(path) + os.symlink(file_model['name'], os.path.join(os_path, name)) + symlink_model = cm.get(path, content=False) + dir_model = cm.get(parent) self.assertEqual( sorted(dir_model['content'], key=lambda x: x['name']), [symlink_model, file_model], @@ -195,53 +223,54 @@ class TestContentsManager(TestCase): def test_update(self): cm = self.contents_manager # Create a notebook - model = cm.create_file() + model = cm.new_untitled(type='notebook') name = model['name'] path = model['path'] # Change the name in the model for rename - model['name'] = 'test.ipynb' - model = cm.update(model, name, path) + model['path'] = 'test.ipynb' + model = cm.update(model, path) assert isinstance(model, dict) self.assertIn('name', model) self.assertIn('path', model) self.assertEqual(model['name'], 'test.ipynb') # Make sure the old name is gone - self.assertRaises(HTTPError, cm.get_model, name, path) + self.assertRaises(HTTPError, cm.get, path) # Test in sub-directory # Create a directory and notebook in that directory sub_dir = '/foo/' self.make_dir(cm.root_dir, 'foo') - model = cm.create_file(None, sub_dir) + model = cm.new_untitled(path=sub_dir, type='notebook') name = model['name'] path = model['path'] # Change the name in the model for rename - model['name'] = 'test_in_sub.ipynb' - model = cm.update(model, name, path) + d = path.rsplit('/', 1)[0] + new_path = model['path'] = d + '/test_in_sub.ipynb' + model = cm.update(model, path) assert isinstance(model, dict) self.assertIn('name', model) self.assertIn('path', model) self.assertEqual(model['name'], 'test_in_sub.ipynb') - self.assertEqual(model['path'], sub_dir.strip('/')) + self.assertEqual(model['path'], new_path) # Make sure the old name is gone - self.assertRaises(HTTPError, cm.get_model, name, path) + self.assertRaises(HTTPError, cm.get, path) def test_save(self): cm = self.contents_manager # Create a notebook - model = cm.create_file() + model = cm.new_untitled(type='notebook') name = model['name'] path = model['path'] # Get the model with 'content' - full_model = cm.get_model(name, path) + full_model = cm.get(path) # Save the notebook - model = cm.save(full_model, name, path) + model = cm.save(full_model, path) assert isinstance(model, dict) self.assertIn('name', model) self.assertIn('path', model) @@ -252,18 +281,18 @@ class TestContentsManager(TestCase): # Create a directory and notebook in that directory sub_dir = '/foo/' self.make_dir(cm.root_dir, 'foo') - model = cm.create_file(None, sub_dir) + model = cm.new_untitled(path=sub_dir, type='notebook') name = model['name'] path = model['path'] - model = cm.get_model(name, path) + model = cm.get(path) # Change the name in the model for rename - model = cm.save(model, name, path) + model = cm.save(model, path) assert isinstance(model, dict) self.assertIn('name', model) self.assertIn('path', model) - self.assertEqual(model['name'], 'Untitled0.ipynb') - self.assertEqual(model['path'], sub_dir.strip('/')) + self.assertEqual(model['name'], 'Untitled.ipynb') + self.assertEqual(model['path'], 'foo/Untitled.ipynb') def test_delete(self): cm = self.contents_manager @@ -271,36 +300,42 @@ class TestContentsManager(TestCase): nb, name, path = self.new_notebook() # Delete the notebook - cm.delete(name, path) + cm.delete(path) # Check that a 'get' on the deleted notebook raises and error - self.assertRaises(HTTPError, cm.get_model, name, path) + self.assertRaises(HTTPError, cm.get, path) def test_copy(self): cm = self.contents_manager - path = u'å b' + parent = u'å b' name = u'nb √.ipynb' - os.mkdir(os.path.join(cm.root_dir, path)) - orig = cm.create_file({'name' : name}, path=path) + path = u'{0}/{1}'.format(parent, name) + os.mkdir(os.path.join(cm.root_dir, parent)) + orig = cm.new(path=path) # copy with unspecified name - copy = cm.copy(name, path=path) - self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy0.ipynb')) + copy = cm.copy(path) + self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy1.ipynb')) # copy with specified name - copy2 = cm.copy(name, u'copy 2.ipynb', path=path) + copy2 = cm.copy(path, u'å b/copy 2.ipynb') self.assertEqual(copy2['name'], u'copy 2.ipynb') + self.assertEqual(copy2['path'], u'å b/copy 2.ipynb') + # copy with specified path + copy2 = cm.copy(path, u'/') + self.assertEqual(copy2['name'], name) + self.assertEqual(copy2['path'], name) def test_trust_notebook(self): cm = self.contents_manager nb, name, path = self.new_notebook() - untrusted = cm.get_model(name, path)['content'] + untrusted = cm.get(path)['content'] assert not cm.notary.check_cells(untrusted) # print(untrusted) - cm.trust_notebook(name, path) - trusted = cm.get_model(name, path)['content'] + cm.trust_notebook(path) + trusted = cm.get(path)['content'] # print(trusted) assert cm.notary.check_cells(trusted) @@ -308,27 +343,27 @@ class TestContentsManager(TestCase): cm = self.contents_manager nb, name, path = self.new_notebook() - cm.mark_trusted_cells(nb, name, path) - for cell in nb.worksheets[0].cells: + cm.mark_trusted_cells(nb, path) + for cell in nb.cells: if cell.cell_type == 'code': - assert not cell.trusted + assert not cell.metadata.trusted - cm.trust_notebook(name, path) - nb = cm.get_model(name, path)['content'] - for cell in nb.worksheets[0].cells: + cm.trust_notebook(path) + nb = cm.get(path)['content'] + for cell in nb.cells: if cell.cell_type == 'code': - assert cell.trusted + assert cell.metadata.trusted def test_check_and_sign(self): cm = self.contents_manager nb, name, path = self.new_notebook() - cm.mark_trusted_cells(nb, name, path) - cm.check_and_sign(nb, name, path) + cm.mark_trusted_cells(nb, path) + cm.check_and_sign(nb, path) assert not cm.notary.check_signature(nb) - cm.trust_notebook(name, path) - nb = cm.get_model(name, path)['content'] - cm.mark_trusted_cells(nb, name, path) - cm.check_and_sign(nb, name, path) + cm.trust_notebook(path) + nb = cm.get(path)['content'] + cm.mark_trusted_cells(nb, path) + cm.check_and_sign(nb, path) assert cm.notary.check_signature(nb) diff --git a/IPython/html/services/kernels/handlers.py b/IPython/html/services/kernels/handlers.py index b51861e..681a7fc 100644 --- a/IPython/html/services/kernels/handlers.py +++ b/IPython/html/services/kernels/handlers.py @@ -5,14 +5,16 @@ import json import logging -from tornado import web +from tornado import gen, web +from tornado.concurrent import Future +from tornado.ioloop import IOLoop from IPython.utils.jsonutil import date_default -from IPython.utils.py3compat import string_types +from IPython.utils.py3compat import cast_unicode from IPython.html.utils import url_path_join, url_escape from ...base.handlers import IPythonHandler, json_errors -from ...base.zmqhandlers import AuthenticatedZMQStreamHandler +from ...base.zmqhandlers import AuthenticatedZMQStreamHandler, deserialize_binary_message from IPython.core.release import kernel_protocol_version @@ -27,16 +29,16 @@ class MainKernelHandler(IPythonHandler): @web.authenticated @json_errors def post(self): + km = self.kernel_manager model = self.get_json_body() if model is None: - raise web.HTTPError(400, "No JSON data provided") - try: - name = model['name'] - except KeyError: - raise web.HTTPError(400, "Missing field in JSON data: name") + model = { + 'name': km.default_kernel_name + } + else: + model.setdefault('name', km.default_kernel_name) - km = self.kernel_manager - kernel_id = km.start_kernel(kernel_name=name) + kernel_id = km.start_kernel(kernel_name=model['name']) model = km.kernel_model(kernel_id) location = url_path_join(self.base_url, 'api', 'kernels', kernel_id) self.set_header('Location', url_escape(location)) @@ -84,6 +86,10 @@ class KernelActionHandler(IPythonHandler): class ZMQChannelHandler(AuthenticatedZMQStreamHandler): + @property + def kernel_info_timeout(self): + return self.settings.get('kernel_info_timeout', 10) + def __repr__(self): return "%s(%s)" % (self.__class__.__name__, getattr(self, 'kernel_id', 'uninitialized')) @@ -91,17 +97,29 @@ class ZMQChannelHandler(AuthenticatedZMQStreamHandler): km = self.kernel_manager meth = getattr(km, 'connect_%s' % self.channel) self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession) - # Create a kernel_info channel to query the kernel protocol version. - # This channel will be closed after the kernel_info reply is received. - self.kernel_info_channel = None - self.kernel_info_channel = km.connect_shell(self.kernel_id) - self.kernel_info_channel.on_recv(self._handle_kernel_info_reply) - self._request_kernel_info() - def _request_kernel_info(self): + def request_kernel_info(self): """send a request for kernel_info""" - self.log.debug("requesting kernel info") - self.session.send(self.kernel_info_channel, "kernel_info_request") + km = self.kernel_manager + kernel = km.get_kernel(self.kernel_id) + try: + # check for previous request + future = kernel._kernel_info_future + except AttributeError: + self.log.debug("Requesting kernel info from %s", self.kernel_id) + # Create a kernel_info channel to query the kernel protocol version. + # This channel will be closed after the kernel_info reply is received. + if self.kernel_info_channel is None: + self.kernel_info_channel = km.connect_shell(self.kernel_id) + self.kernel_info_channel.on_recv(self._handle_kernel_info_reply) + self.session.send(self.kernel_info_channel, "kernel_info_request") + # store the future on the kernel, so only one request is sent + kernel._kernel_info_future = self._kernel_info_future + else: + if not future.done(): + self.log.debug("Waiting for pending kernel_info request") + future.add_done_callback(lambda f: self._finish_kernel_info(f.result())) + return self._kernel_info_future def _handle_kernel_info_reply(self, msg): """process the kernel_info_reply @@ -110,35 +128,75 @@ class ZMQChannelHandler(AuthenticatedZMQStreamHandler): """ idents,msg = self.session.feed_identities(msg) try: - msg = self.session.unserialize(msg) + msg = self.session.deserialize(msg) except: self.log.error("Bad kernel_info reply", exc_info=True) - self._request_kernel_info() + self._kernel_info_future.set_result({}) return else: - if msg['msg_type'] != 'kernel_info_reply' or 'protocol_version' not in msg['content']: - self.log.error("Kernel info request failed, assuming current %s", msg['content']) - else: - protocol_version = msg['content']['protocol_version'] - if protocol_version != kernel_protocol_version: - self.session.adapt_version = int(protocol_version.split('.')[0]) - self.log.info("adapting kernel to %s" % protocol_version) - self.kernel_info_channel.close() + info = msg['content'] + self.log.debug("Received kernel info: %s", info) + if msg['msg_type'] != 'kernel_info_reply' or 'protocol_version' not in info: + self.log.error("Kernel info request failed, assuming current %s", info) + info = {} + self._finish_kernel_info(info) + + # close the kernel_info channel, we don't need it anymore + if self.kernel_info_channel: + self.kernel_info_channel.close() self.kernel_info_channel = None + def _finish_kernel_info(self, info): + """Finish handling kernel_info reply + + Set up protocol adaptation, if needed, + and signal that connection can continue. + """ + protocol_version = info.get('protocol_version', kernel_protocol_version) + if protocol_version != kernel_protocol_version: + self.session.adapt_version = int(protocol_version.split('.')[0]) + self.log.info("Kernel %s speaks protocol %s", self.kernel_id, protocol_version) + if not self._kernel_info_future.done(): + self._kernel_info_future.set_result(info) - def initialize(self, *args, **kwargs): + def initialize(self): + super(ZMQChannelHandler, self).initialize() self.zmq_stream = None + self.kernel_id = None + self.kernel_info_channel = None + self._kernel_info_future = Future() - def on_first_message(self, msg): - try: - super(ZMQChannelHandler, self).on_first_message(msg) - except web.HTTPError: - self.close() - return + @gen.coroutine + def pre_get(self): + # authenticate first + super(ZMQChannelHandler, self).pre_get() + # then request kernel info, waiting up to a certain time before giving up. + # We don't want to wait forever, because browsers don't take it well when + # servers never respond to websocket connection requests. + future = self.request_kernel_info() + + def give_up(): + """Don't wait forever for the kernel to reply""" + if future.done(): + return + self.log.warn("Timeout waiting for kernel_info reply from %s", self.kernel_id) + future.set_result({}) + loop = IOLoop.current() + loop.add_timeout(loop.time() + self.kernel_info_timeout, give_up) + # actually wait for it + yield future + + @gen.coroutine + def get(self, kernel_id): + self.kernel_id = cast_unicode(kernel_id, 'ascii') + yield super(ZMQChannelHandler, self).get(kernel_id=kernel_id) + + def open(self, kernel_id): + super(ZMQChannelHandler, self).open() try: self.create_stream() - except web.HTTPError: + except web.HTTPError as e: + self.log.error("Error opening stream: %s", e) # WebSockets don't response to traditional error codes so we # close the connection. if not self.stream.closed(): @@ -154,7 +212,10 @@ class ZMQChannelHandler(AuthenticatedZMQStreamHandler): self.log.info("%s closed, closing websocket.", self) self.close() return - msg = json.loads(msg) + if isinstance(msg, bytes): + msg = deserialize_binary_message(msg) + else: + msg = json.loads(msg) self.session.send(self.zmq_stream, msg) def on_close(self): diff --git a/IPython/html/services/kernels/kernelmanager.py b/IPython/html/services/kernels/kernelmanager.py index 3132efb..db73aa4 100644 --- a/IPython/html/services/kernels/kernelmanager.py +++ b/IPython/html/services/kernels/kernelmanager.py @@ -1,20 +1,11 @@ -"""A kernel manager relating notebooks and kernels +"""A MultiKernelManager for use in the notebook webserver -Authors: - -* Brian Granger +- raises HTTPErrors +- creates REST API models """ -#----------------------------------------------------------------------------- -# Copyright (C) 2013 The 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 -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. import os @@ -26,10 +17,6 @@ from IPython.utils.traitlets import List, Unicode, TraitError from IPython.html.utils import to_os_path from IPython.utils.py3compat import getcwd -#----------------------------------------------------------------------------- -# Classes -#----------------------------------------------------------------------------- - class MappingKernelManager(MultiKernelManager): """A KernelManager that handles notebook mapping and HTTP error handling""" @@ -39,7 +26,13 @@ class MappingKernelManager(MultiKernelManager): kernel_argv = List(Unicode) - root_dir = Unicode(getcwd(), config=True) + root_dir = Unicode(config=True) + + def _root_dir_default(self): + try: + return self.parent.notebook_dir + except AttributeError: + return getcwd() def _root_dir_changed(self, name, old, new): """Do a bit of validation of the root dir.""" @@ -61,14 +54,10 @@ class MappingKernelManager(MultiKernelManager): def cwd_for_path(self, path): """Turn API path into absolute OS path.""" - # short circuit for NotebookManagers that pass in absolute paths - if os.path.exists(path): - return path - os_path = to_os_path(path, self.root_dir) # in the case of notebooks and kernels not being on the same filesystem, # walk up to root_dir if the paths don't exist - while not os.path.exists(os_path) and os_path != self.root_dir: + while not os.path.isdir(os_path) and os_path != self.root_dir: os_path = os.path.dirname(os_path) return os_path @@ -89,7 +78,6 @@ class MappingKernelManager(MultiKernelManager): an existing kernel is returned, but it may be checked in the future. """ if kernel_id is None: - kwargs['extra_arguments'] = self.kernel_argv if path is not None: kwargs['cwd'] = self.cwd_for_path(path) kernel_id = super(MappingKernelManager, self).start_kernel( diff --git a/IPython/html/services/kernels/tests/test_kernels_api.py b/IPython/html/services/kernels/tests/test_kernels_api.py index c3e3c97..b33142c 100644 --- a/IPython/html/services/kernels/tests/test_kernels_api.py +++ b/IPython/html/services/kernels/tests/test_kernels_api.py @@ -57,6 +57,19 @@ class KernelAPITest(NotebookTestBase): kernels = self.kern_api.list().json() self.assertEqual(kernels, []) + def test_default_kernel(self): + # POST request + r = self.kern_api._req('POST', '') + kern1 = r.json() + self.assertEqual(r.headers['location'], '/api/kernels/' + kern1['id']) + self.assertEqual(r.status_code, 201) + self.assertIsInstance(kern1, dict) + + self.assertEqual(r.headers['Content-Security-Policy'], ( + "frame-ancestors 'self'; " + "report-uri /api/security/csp-report;" + )) + def test_main_kernel_handler(self): # POST request r = self.kern_api.start() @@ -65,7 +78,10 @@ class KernelAPITest(NotebookTestBase): self.assertEqual(r.status_code, 201) self.assertIsInstance(kern1, dict) - self.assertEqual(r.headers['x-frame-options'], "SAMEORIGIN") + self.assertEqual(r.headers['Content-Security-Policy'], ( + "frame-ancestors 'self'; " + "report-uri /api/security/csp-report;" + )) # GET request r = self.kern_api.list() diff --git a/IPython/html/services/kernelspecs/handlers.py b/IPython/html/services/kernelspecs/handlers.py index 6561b0b..f810414 100644 --- a/IPython/html/services/kernelspecs/handlers.py +++ b/IPython/html/services/kernelspecs/handlers.py @@ -19,7 +19,11 @@ class MainKernelSpecHandler(IPythonHandler): ksm = self.kernel_spec_manager results = [] for kernel_name in sorted(ksm.find_kernel_specs(), key=_pythonfirst): - d = ksm.get_kernel_spec(kernel_name).to_dict() + try: + d = ksm.get_kernel_spec(kernel_name).to_dict() + except Exception: + self.log.error("Failed to load kernel spec: '%s'", kernel_name, exc_info=True) + continue d['name'] = kernel_name results.append(d) diff --git a/IPython/html/services/kernelspecs/tests/__init__.py b/IPython/html/services/kernelspecs/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/IPython/html/services/kernelspecs/tests/__init__.py diff --git a/IPython/html/services/kernelspecs/tests/test_kernelspecs_api.py b/IPython/html/services/kernelspecs/tests/test_kernelspecs_api.py index 871f163..a82a751 100644 --- a/IPython/html/services/kernelspecs/tests/test_kernelspecs_api.py +++ b/IPython/html/services/kernelspecs/tests/test_kernelspecs_api.py @@ -5,6 +5,7 @@ import errno import io import json import os +import shutil pjoin = os.path.join @@ -18,7 +19,6 @@ from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_erro # break these tests sample_kernel_json = {'argv':['cat', '{connection_file}'], 'display_name':'Test kernel', - 'language':'bash', } some_resource = u"The very model of a modern major general" @@ -66,6 +66,25 @@ class APITest(NotebookTestBase): self.ks_api = KernelSpecAPI(self.base_url()) + def test_list_kernelspecs_bad(self): + """Can list kernelspecs when one is invalid""" + bad_kernel_dir = pjoin(self.ipython_dir.name, 'kernels', 'bad') + try: + os.makedirs(bad_kernel_dir) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + with open(pjoin(bad_kernel_dir, 'kernel.json'), 'w') as f: + f.write("garbage") + + specs = self.ks_api.list().json() + assert isinstance(specs, list) + # 2: the sample kernelspec created in setUp, and the native Python kernel + self.assertGreaterEqual(len(specs), 2) + + shutil.rmtree(bad_kernel_dir) + def test_list_kernelspecs(self): specs = self.ks_api.list().json() assert isinstance(specs, list) @@ -84,7 +103,7 @@ class APITest(NotebookTestBase): def test_get_kernelspec(self): spec = self.ks_api.kernel_spec_info('Sample').json() # Case insensitive - self.assertEqual(spec['language'], 'bash') + self.assertEqual(spec['display_name'], 'Test kernel') def test_get_nonexistant_kernelspec(self): with assert_http_error(404): diff --git a/IPython/html/services/security/__init__.py b/IPython/html/services/security/__init__.py new file mode 100644 index 0000000..9cf0d47 --- /dev/null +++ b/IPython/html/services/security/__init__.py @@ -0,0 +1,4 @@ +# URI for the CSP Report. Included here to prevent a cyclic dependency. +# csp_report_uri is needed both by the BaseHandler (for setting the report-uri) +# and by the CSPReportHandler (which depends on the BaseHandler). +csp_report_uri = r"/api/security/csp-report" diff --git a/IPython/html/services/security/handlers.py b/IPython/html/services/security/handlers.py new file mode 100644 index 0000000..18f7874 --- /dev/null +++ b/IPython/html/services/security/handlers.py @@ -0,0 +1,23 @@ +"""Tornado handlers for security logging.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from tornado import gen, web + +from ...base.handlers import IPythonHandler, json_errors +from . import csp_report_uri + +class CSPReportHandler(IPythonHandler): + '''Accepts a content security policy violation report''' + @web.authenticated + @json_errors + def post(self): + '''Log a content security policy violation report''' + csp_report = self.get_json_body() + self.log.warn("Content security violation: %s", + self.request.body.decode('utf8', 'replace')) + +default_handlers = [ + (csp_report_uri, CSPReportHandler) +] diff --git a/IPython/html/services/sessions/handlers.py b/IPython/html/services/sessions/handlers.py index 691339f..9d0a5e4 100644 --- a/IPython/html/services/sessions/handlers.py +++ b/IPython/html/services/sessions/handlers.py @@ -10,6 +10,7 @@ from tornado import web from ...base.handlers import IPythonHandler, json_errors from IPython.utils.jsonutil import date_default from IPython.html.utils import url_path_join, url_escape +from IPython.kernel.kernelspec import NoSuchKernel class SessionRootHandler(IPythonHandler): @@ -35,23 +36,30 @@ class SessionRootHandler(IPythonHandler): if model is None: raise web.HTTPError(400, "No JSON data provided") try: - name = model['notebook']['name'] - except KeyError: - raise web.HTTPError(400, "Missing field in JSON data: notebook.name") - try: path = model['notebook']['path'] except KeyError: raise web.HTTPError(400, "Missing field in JSON data: notebook.path") try: kernel_name = model['kernel']['name'] except KeyError: - raise web.HTTPError(400, "Missing field in JSON data: kernel.name") + self.log.debug("No kernel name specified, using default kernel") + kernel_name = None # Check to see if session exists - if sm.session_exists(name=name, path=path): - model = sm.get_session(name=name, path=path) + if sm.session_exists(path=path): + model = sm.get_session(path=path) else: - model = sm.create_session(name=name, path=path, kernel_name=kernel_name) + try: + model = sm.create_session(path=path, kernel_name=kernel_name) + except NoSuchKernel: + msg = ("The '%s' kernel is not available. Please pick another " + "suitable kernel instead, or install that kernel." % kernel_name) + status_msg = '%s not found' % kernel_name + self.log.warn('Kernel not found: %s' % kernel_name) + self.set_status(501) + self.finish(json.dumps(dict(message=msg, short_message=status_msg))) + return + location = url_path_join(self.base_url, 'api', 'sessions', model['id']) self.set_header('Location', url_escape(location)) self.set_status(201) @@ -80,8 +88,6 @@ class SessionHandler(IPythonHandler): changes = {} if 'notebook' in model: notebook = model['notebook'] - if 'name' in notebook: - changes['name'] = notebook['name'] if 'path' in notebook: changes['path'] = notebook['path'] @@ -94,7 +100,11 @@ class SessionHandler(IPythonHandler): def delete(self, session_id): # Deletes the session with given session_id sm = self.session_manager - sm.delete_session(session_id) + try: + sm.delete_session(session_id) + except KeyError: + # the kernel was deleted but the session wasn't! + raise web.HTTPError(410, "Kernel deleted before session") self.set_status(204) self.finish() diff --git a/IPython/html/services/sessions/sessionmanager.py b/IPython/html/services/sessions/sessionmanager.py index b105344..4f05bf1 100644 --- a/IPython/html/services/sessions/sessionmanager.py +++ b/IPython/html/services/sessions/sessionmanager.py @@ -1,20 +1,7 @@ -"""A base class session manager. +"""A base class session manager.""" -Authors: - -* Zach Sailer -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2013 The 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 -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. import uuid import sqlite3 @@ -25,9 +12,6 @@ from IPython.config.configurable import LoggingConfigurable from IPython.utils.py3compat import unicode_type from IPython.utils.traitlets import Instance -#----------------------------------------------------------------------------- -# Classes -#----------------------------------------------------------------------------- class SessionManager(LoggingConfigurable): @@ -37,7 +21,7 @@ class SessionManager(LoggingConfigurable): # Session database initialized below _cursor = None _connection = None - _columns = {'session_id', 'name', 'path', 'kernel_id'} + _columns = {'session_id', 'path', 'kernel_id'} @property def cursor(self): @@ -45,7 +29,7 @@ class SessionManager(LoggingConfigurable): if self._cursor is None: self._cursor = self.connection.cursor() self._cursor.execute("""CREATE TABLE session - (session_id, name, path, kernel_id)""") + (session_id, path, kernel_id)""") return self._cursor @property @@ -60,9 +44,9 @@ class SessionManager(LoggingConfigurable): """Close connection once SessionManager closes""" self.cursor.close() - def session_exists(self, name, path): + def session_exists(self, path): """Check to see if the session for a given notebook exists""" - self.cursor.execute("SELECT * FROM session WHERE name=? AND path=?", (name, path)) + self.cursor.execute("SELECT * FROM session WHERE path=?", (path,)) reply = self.cursor.fetchone() if reply is None: return False @@ -73,17 +57,17 @@ class SessionManager(LoggingConfigurable): "Create a uuid for a new session" return unicode_type(uuid.uuid4()) - def create_session(self, name=None, path=None, kernel_name='python'): + def create_session(self, path=None, kernel_name=None): """Creates a session and returns its model""" session_id = self.new_session_id() # allow nbm to specify kernels cwd - kernel_path = self.contents_manager.get_kernel_path(name=name, path=path) + kernel_path = self.contents_manager.get_kernel_path(path=path) kernel_id = self.kernel_manager.start_kernel(path=kernel_path, kernel_name=kernel_name) - return self.save_session(session_id, name=name, path=path, + return self.save_session(session_id, path=path, kernel_id=kernel_id) - def save_session(self, session_id, name=None, path=None, kernel_id=None): + def save_session(self, session_id, path=None, kernel_id=None): """Saves the items for the session with the given session_id Given a session_id (and any other of the arguments), this method @@ -94,10 +78,8 @@ class SessionManager(LoggingConfigurable): ---------- session_id : str uuid for the session; this method must be given a session_id - name : str - the .ipynb notebook name that started the session path : str - the path to the named notebook + the path for the given notebook kernel_id : str a uuid for the kernel associated with this session @@ -106,8 +88,8 @@ class SessionManager(LoggingConfigurable): model : dict a dictionary of the session model """ - self.cursor.execute("INSERT INTO session VALUES (?,?,?,?)", - (session_id, name, path, kernel_id) + self.cursor.execute("INSERT INTO session VALUES (?,?,?)", + (session_id, path, kernel_id) ) return self.get_session(session_id=session_id) @@ -121,7 +103,7 @@ class SessionManager(LoggingConfigurable): ---------- **kwargs : keyword argument must be given one of the keywords and values from the session database - (i.e. session_id, name, path, kernel_id) + (i.e. session_id, path, kernel_id) Returns ------- @@ -198,7 +180,6 @@ class SessionManager(LoggingConfigurable): model = { 'id': row['session_id'], 'notebook': { - 'name': row['name'], 'path': row['path'] }, 'kernel': self.kernel_manager.kernel_model(row['kernel_id']) diff --git a/IPython/html/services/sessions/tests/test_sessionmanager.py b/IPython/html/services/sessions/tests/test_sessionmanager.py index 6383a0b..36980bd 100644 --- a/IPython/html/services/sessions/tests/test_sessionmanager.py +++ b/IPython/html/services/sessions/tests/test_sessionmanager.py @@ -32,24 +32,24 @@ class TestSessionManager(TestCase): def test_get_session(self): sm = SessionManager(kernel_manager=DummyMKM()) - session_id = sm.create_session(name='test.ipynb', path='/path/to/', + session_id = sm.create_session(path='/path/to/test.ipynb', kernel_name='bar')['id'] model = sm.get_session(session_id=session_id) expected = {'id':session_id, - 'notebook':{'name':u'test.ipynb', 'path': u'/path/to/'}, + 'notebook':{'path': u'/path/to/test.ipynb'}, 'kernel': {'id':u'A', 'name': 'bar'}} self.assertEqual(model, expected) def test_bad_get_session(self): # Should raise error if a bad key is passed to the database. sm = SessionManager(kernel_manager=DummyMKM()) - session_id = sm.create_session(name='test.ipynb', path='/path/to/', + session_id = sm.create_session(path='/path/to/test.ipynb', kernel_name='foo')['id'] self.assertRaises(TypeError, sm.get_session, bad_id=session_id) # Bad keyword def test_get_session_dead_kernel(self): sm = SessionManager(kernel_manager=DummyMKM()) - session = sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python') + session = sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python') # kill the kernel sm.kernel_manager.shutdown_kernel(session['kernel']['id']) with self.assertRaises(KeyError): @@ -61,24 +61,33 @@ class TestSessionManager(TestCase): def test_list_sessions(self): sm = SessionManager(kernel_manager=DummyMKM()) sessions = [ - sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'), - sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'), - sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'), + sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python'), + sm.create_session(path='/path/to/2/test2.ipynb', kernel_name='python'), + sm.create_session(path='/path/to/3/test3.ipynb', kernel_name='python'), ] sessions = sm.list_sessions() - expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb', - 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}}, - {'id':sessions[1]['id'], 'notebook': {'name':u'test2.ipynb', - 'path': u'/path/to/2/'}, 'kernel':{'id':u'B', 'name':'python'}}, - {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb', - 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}] + expected = [ + { + 'id':sessions[0]['id'], + 'notebook':{'path': u'/path/to/1/test1.ipynb'}, + 'kernel':{'id':u'A', 'name':'python'} + }, { + 'id':sessions[1]['id'], + 'notebook': {'path': u'/path/to/2/test2.ipynb'}, + 'kernel':{'id':u'B', 'name':'python'} + }, { + 'id':sessions[2]['id'], + 'notebook':{'path': u'/path/to/3/test3.ipynb'}, + 'kernel':{'id':u'C', 'name':'python'} + } + ] self.assertEqual(sessions, expected) def test_list_sessions_dead_kernel(self): sm = SessionManager(kernel_manager=DummyMKM()) sessions = [ - sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'), - sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'), + sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python'), + sm.create_session(path='/path/to/2/test2.ipynb', kernel_name='python'), ] # kill one of the kernels sm.kernel_manager.shutdown_kernel(sessions[0]['kernel']['id']) @@ -87,8 +96,7 @@ class TestSessionManager(TestCase): { 'id': sessions[1]['id'], 'notebook': { - 'name': u'test2.ipynb', - 'path': u'/path/to/2/', + 'path': u'/path/to/2/test2.ipynb', }, 'kernel': { 'id': u'B', @@ -100,41 +108,47 @@ class TestSessionManager(TestCase): def test_update_session(self): sm = SessionManager(kernel_manager=DummyMKM()) - session_id = sm.create_session(name='test.ipynb', path='/path/to/', + session_id = sm.create_session(path='/path/to/test.ipynb', kernel_name='julia')['id'] - sm.update_session(session_id, name='new_name.ipynb') + sm.update_session(session_id, path='/path/to/new_name.ipynb') model = sm.get_session(session_id=session_id) expected = {'id':session_id, - 'notebook':{'name':u'new_name.ipynb', 'path': u'/path/to/'}, + 'notebook':{'path': u'/path/to/new_name.ipynb'}, 'kernel':{'id':u'A', 'name':'julia'}} self.assertEqual(model, expected) def test_bad_update_session(self): # try to update a session with a bad keyword ~ raise error sm = SessionManager(kernel_manager=DummyMKM()) - session_id = sm.create_session(name='test.ipynb', path='/path/to/', + session_id = sm.create_session(path='/path/to/test.ipynb', kernel_name='ir')['id'] self.assertRaises(TypeError, sm.update_session, session_id=session_id, bad_kw='test.ipynb') # Bad keyword def test_delete_session(self): sm = SessionManager(kernel_manager=DummyMKM()) sessions = [ - sm.create_session(name='test1.ipynb', path='/path/to/1/', kernel_name='python'), - sm.create_session(name='test2.ipynb', path='/path/to/2/', kernel_name='python'), - sm.create_session(name='test3.ipynb', path='/path/to/3/', kernel_name='python'), + sm.create_session(path='/path/to/1/test1.ipynb', kernel_name='python'), + sm.create_session(path='/path/to/2/test2.ipynb', kernel_name='python'), + sm.create_session(path='/path/to/3/test3.ipynb', kernel_name='python'), ] sm.delete_session(sessions[1]['id']) new_sessions = sm.list_sessions() - expected = [{'id':sessions[0]['id'], 'notebook':{'name':u'test1.ipynb', - 'path': u'/path/to/1/'}, 'kernel':{'id':u'A', 'name':'python'}}, - {'id':sessions[2]['id'], 'notebook':{'name':u'test3.ipynb', - 'path': u'/path/to/3/'}, 'kernel':{'id':u'C', 'name':'python'}}] + expected = [{ + 'id': sessions[0]['id'], + 'notebook': {'path': u'/path/to/1/test1.ipynb'}, + 'kernel': {'id':u'A', 'name':'python'} + }, { + 'id': sessions[2]['id'], + 'notebook': {'path': u'/path/to/3/test3.ipynb'}, + 'kernel': {'id':u'C', 'name':'python'} + } + ] self.assertEqual(new_sessions, expected) def test_bad_delete_session(self): # try to delete a session that doesn't exist ~ raise error sm = SessionManager(kernel_manager=DummyMKM()) - sm.create_session(name='test.ipynb', path='/path/to/', kernel_name='python') + sm.create_session(path='/path/to/test.ipynb', kernel_name='python') self.assertRaises(TypeError, sm.delete_session, bad_kwarg='23424') # Bad keyword self.assertRaises(web.HTTPError, sm.delete_session, session_id='23424') # nonexistant diff --git a/IPython/html/services/sessions/tests/test_sessions_api.py b/IPython/html/services/sessions/tests/test_sessions_api.py index 9623415..e721201 100644 --- a/IPython/html/services/sessions/tests/test_sessions_api.py +++ b/IPython/html/services/sessions/tests/test_sessions_api.py @@ -11,7 +11,8 @@ pjoin = os.path.join from IPython.html.utils import url_path_join from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error -from IPython.nbformat.current import new_notebook, write +from IPython.nbformat.v4 import new_notebook +from IPython.nbformat import write class SessionAPI(object): """Wrapper for notebook API calls.""" @@ -37,13 +38,13 @@ class SessionAPI(object): def get(self, id): return self._req('GET', id) - def create(self, name, path, kernel_name='python'): - body = json.dumps({'notebook': {'name':name, 'path':path}, + def create(self, path, kernel_name='python'): + body = json.dumps({'notebook': {'path':path}, 'kernel': {'name': kernel_name}}) return self._req('POST', '', body) - def modify(self, id, name, path): - body = json.dumps({'notebook': {'name':name, 'path':path}}) + def modify(self, id, path): + body = json.dumps({'notebook': {'path':path}}) return self._req('PATCH', id, body) def delete(self, id): @@ -62,8 +63,8 @@ class SessionAPITest(NotebookTestBase): with io.open(pjoin(nbdir, 'foo', 'nb1.ipynb'), 'w', encoding='utf-8') as f: - nb = new_notebook(name='nb1') - write(nb, f, format='ipynb') + nb = new_notebook() + write(nb, f, version=4) self.sess_api = SessionAPI(self.base_url()) @@ -77,12 +78,11 @@ class SessionAPITest(NotebookTestBase): sessions = self.sess_api.list().json() self.assertEqual(len(sessions), 0) - resp = self.sess_api.create('nb1.ipynb', 'foo') + resp = self.sess_api.create('foo/nb1.ipynb') self.assertEqual(resp.status_code, 201) newsession = resp.json() self.assertIn('id', newsession) - self.assertEqual(newsession['notebook']['name'], 'nb1.ipynb') - self.assertEqual(newsession['notebook']['path'], 'foo') + self.assertEqual(newsession['notebook']['path'], 'foo/nb1.ipynb') self.assertEqual(resp.headers['Location'], '/api/sessions/{0}'.format(newsession['id'])) sessions = self.sess_api.list().json() @@ -94,7 +94,7 @@ class SessionAPITest(NotebookTestBase): self.assertEqual(got, newsession) def test_delete(self): - newsession = self.sess_api.create('nb1.ipynb', 'foo').json() + newsession = self.sess_api.create('foo/nb1.ipynb').json() sid = newsession['id'] resp = self.sess_api.delete(sid) @@ -107,10 +107,9 @@ class SessionAPITest(NotebookTestBase): self.sess_api.get(sid) def test_modify(self): - newsession = self.sess_api.create('nb1.ipynb', 'foo').json() + newsession = self.sess_api.create('foo/nb1.ipynb').json() sid = newsession['id'] - changed = self.sess_api.modify(sid, 'nb2.ipynb', '').json() + changed = self.sess_api.modify(sid, 'nb2.ipynb').json() self.assertEqual(changed['id'], sid) - self.assertEqual(changed['notebook']['name'], 'nb2.ipynb') - self.assertEqual(changed['notebook']['path'], '') + self.assertEqual(changed['notebook']['path'], 'nb2.ipynb') diff --git a/IPython/html/static/base/images/favicon.ico b/IPython/html/static/base/images/favicon.ico index 4e323758171fc7a2fd6eb358f9b0db255530d2e0..6c3169068e2e7371a887a0a3593c54eb4495db69 100644 GIT binary patch literal 34494 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 AJ^%m! literal 1430 zc$~GAT}YEr7{|}MP3GnrHdif+oVl-%KBx;Jv4u!UA4C_0&{cL}lp~1<`k>ycWkDfI zYbgm*!653Q52GCUQhQq?SPZFTNLtY(Ms2S-+PklR#}&JJ4QA_ic+SQDInSSS9v%)M z3|%@Mp}Uc=WrQpxgxF|MiI~W5{eQwRjHX0ZGQJ3gy&T`u>*fbsy*&5y0abi?-RJZ1 zD1v*T&(JKkLBB`^_sKS75?_lcFYqlPA`lu!{Z0prY8k>@e<3%>O{22fiW^6oFe8en z-FphVELz0F(pOFAayWSL5eTD%tHTat6M6l*Hv+kK1+KMkf{+xkWczV6S2Ku>KbQ2G zPNbo4I0QLiV7KTnJN05-pC$u?3IBaWqfrR4@5p6S`LpyY7zjkb$g8lYr4jl{1NfU~ z=Q5H$6>@?#=2~o6zYbd)8_<9LD3U@1GwB>!I9c*yhD{-m>6jC>TJC^zH4 z788#34MC|;!S8m^H)h}(c?XqhB|PjtNuSNj7>teH!Ni9tT)4|aR#t`1*0mUW8-)KQ zkCgBw-;>yE3Va}hPvM}XkDB(!T33VBW-AnGEtXd(@#@w&TAH4Bw%$zsK=4xBvhE8FWQhbW?9;ba!ELWdL_~cP?peYja~^ zaAhuUa%Y?FJQ@H18*NEMK~#90?VWqvty7)Azsotfg>$IL5h*fCHIZd7!br)zj9Zoz zqs0teN4eG{!x%-AOl2uDX1Rovj#LaHrYzT7;-s8H7`M*hbO>k7A8WnmJp0+}*?T{i zz4yD%yT6~${v7Xm_OqU!y`TO3*7{uq@Gvl){<4kt{{Av22q7*3wkmm@mw;!1`M^Bj zG2jm1cHnmJ{hvzCH)hP3DT37Y4BDSLbDU9o4?~_Y)sWiG240sN{-HDKbN#yJbl|nX zw0>P6bDgrR2CUJ*wLcX9eOU7eS`Hum= z1fBt&j<08O|2|Fn!894z2UuAM@&2)?ccfS@zVd7-wcXwsb#(*t=b1{@n=xa?lwi8| zel9S#)0>14hXY?r9J<|FA+pE;u*?T0)AALWm84!+?LJIH>ACA%yYX|DvQUhp{A>sTXbn2BJuTHGvCCp0g7$ z>WXw*sqL1|sGEi@$fzK-U50QUuNsH$7@|01#*7?Gy}8PJf1mgMW5D0g*~I+s0}^RW z|2cNp0*n~%WQh_TGiFRlmU=}xf%pDaV0+;I3f{7{5MsRniZrHw>?F0_c-TUW5bqhb z0At3CGNWkPt&I2n0U^XOz_$wCvIFq-8kd+2e5S+kKi8PMEOQC@RGs+s0OS}J#{hc` zTZj>1`(X<(X3Quvr4(u8JHQucuY3XUUg{~*`Oc{OYMkE~&Zxft9c7W)dX|oa^QVB7 zfn_sedq{0JoGjnRWJPMbB2X)3Xv~0tVE^wkV>U%>JX@)cE0jceGfe&WJ zrvslQq&`g=qH^B=SUtX`0gn>`;qG@v{cb>V2!cCf(jv8871)?1pVdkPQ~nc#DE!-< zQT_bA7REJ5ZPx?d1k55V!sijv#~uXkbVdzqH!HPWju3tQTEgeFT9oH;LgAcSoKepY zN}h~d8xkyiuZeQ`DAVJ>J4*ap>w_WmsOZ&FF<96 z&hy@1T35alQrp)7mjTPiq1Awkq_!J5qvrJEnmsTh{(BOzFRgyx1EsbnIHUeJc(Ils zwS61myWR!Zu*-YzCVa+cJEP_{bp08GA|-Dp6lQ;W{QH%Ox9<}dOJ$hvjM|`{lmV&j zD!_rj?!dd~{G57HYWrj09B0%uO;_q0ZL?@U|*x3wAkuEE>UBMZ3 zx77A>!h>ak<$!;VuYnU69w!i#Xs@o6x5n3JrM9O5Cpn{@t8ncVfC~wV^YtZ8U7q&u zxR-NkA+?lfWTT z+kXL0b4KM|50!|PgnfY%>AkYrO2D4+b-UE|lg_B?n~*0X*MDz#m^37P8BMTvd~P|m2AVCn`NZCNlr zcaTCv^656Z~PXgyh6LDpvwx0veq8V4=TY;OTwr^=*uF6Pl*QZ#CegiltYaX9+rd=V#bc!pv z{B)4AM6M(#@O9(g`w1cb*?ZsBlqZA`1hduz&jH_UTEHAv0(a8d0QomSUwpD9$ohv) zq&*t-aH;K=oKf@pEr`^158&GchZhpkuYO3_t^Ob30lhlGce*R#L7DGBGZXl!)OIUp z)U7>=vH;kz%X=;+nEkR`4V>EJH72OkJt;Op*CrEAYc1C@_id-2sB`j96O7ypcMbfpvBZLlDGYC8X-HBbL5_A1Mp-{*PWrc{l z*(+(aK4rNN_#WZ2d<^&#!AkU6f;;8|q&s-+OV9(AikC=jKS?mfroYZe^4a)5FG;JsfY zgg6uUw>Y#CA&|lJaQe1{-O2vyzd;T?Hx4(wz8!r z7ZTzTzBysi%W)SlTWWhea4x~roaY#+?fK5A-H3$SAe z)~=Ijj}h5ZYI~?N>YHT*iUD*d7aTqx_*6jv(Hx=~KbG=56Og(0tOi)A?KVyMmV2R# z5o?ol$2LDABA;4+HNb>{#{j!fCbjKWQZy^mG{BPX-FuQVO7#+j>x{ac5Sg8_2L3HD zuy%e@+c#0HM2{^}qA-ONX)H*86~WYzqMwnj9r4~j0bG(9+N4|1kq}}9LImCfbG-LI z?}vONLW?4GM*WEPc3`g|M1}SRsjVbDK=b^ZoKK}2@x2EUc4m1#EVWIU=|+YaUYGXE>vJwpcl%UL^cJpGh3vrDQb~TRdI5Mp!9|qk2&rvRfMa8HQKFxAMx}xn7qiqWQX$0ZLWrXY zW|yKow;y@$`;miIhJ4Ubw_WM`Y4yVlWH&mANd-)#ZvG?c23K?-70|Q@_;?lbR8*!P zk8@-Z1jRARyqC1k?#L=`f>>Jtei>fS^QYg=o%(D$?Wqxr3b6=PkTY>rtVkpE2)Krb%I!Y90)B@720pAg;B~@j1)f1gjPZ5I1 z@@yov%|}b8NM#s2QZQ#3xf~(DEziz9oL*F)`I3V1GF+4xTa(tHuPU@f>vvG1FA;J? z&+T>cQBfszu%nx*>JmvQ(s|CPhw4u3jCzug8<=NQ73t;$#OXMXAVkr2B301lz8GLtmwRcC34r?d`r-{r z4D|XR+S4Ex0=qe*eo%Jusgy_h8$yWuIo|tJB;91;8?^cJ*O*7GK$I_9w)V}TW7VXU_n)X z@`4)v(FuwaajI%gmef53&D~c;^Sqf*aH5Ua{9yHd$>fZBUTXV0+H;HR-s|&V@h(4A z>hU>XS7+39RVH8B6zL+u%HvOjR%y2Zzl^VY2V9W865jiJgb>%WWCgab5aJWw`-!MC zl{;l@*6l+4ah^)3<(cIe+7)T({*zMMWk^@odA#gg9Z1c!TrAc<>|z*YDN1KZ#soA( zQKO1(G?63ybisHxnTnKuQ}inMMD%zUZs@`OrRDRcQpt13|# zP1Ek)gfBDyS_PQyyDT$0x}8_HOpxH@WLs zn)xQ}5426Cwmam;sx)$*N!kS0WTA?px%!@C*9V3XZCv$P*Qw=VuPi7Suc}C!y05C- zQ!@+3n=HMVnycu#z49#Z-xQmzKUXIpZ4?#hbKd(uC0@^tn6m7|_h?-$?hhb@*n-yn ze&gL}kHnICrzZ~OlgF!=7K?u5gHWV(A27pUl+t#Z*zFf7<4);q(Et0Y$~{$dtjW@( zskx^280B2V7YVhFi+Bs6C%KD^rD-juL~Z7xIl7rc`+Z*(&C@9u zv*=izJl#AgxfV1O6$~Mr>8pgm&U~Ssk4tTTUABP#ct<)(@!mf~Yg)@5ghT%5T_;G&NTfnHCEPHPttZ#TE`A9i{&@scliwi6mo1I=RUP9epJ8 z-@hZBm33z$6s``Nj(K6p@(!8@mw7s+`#ep$_iR%#70p#Q1?e8(ZO*7u zCK@6*qb?yd1jsWTxVTJXh_ND_++4+y9JH*WI%imcRnecAuL`siy_FW5EPRRfhr#=$ zwi99TRTSwj5@YL2ZBuQgN)ZDfHcT9Hb>|)uMU{fO6(|@6g5rG6UMV{N-Y(~*?kQ-s zOx2-ip2ChY(Y%^!EZ>MsQrk622cwnha+Bb;R1etL)g@J@d?ul{Y)?_~V?{cNiOMo> zt6xND8aymgt&=*9Pdc1Ye;|ayXAy+FQYdNKja;7?n@0T_p%4lfq;^F&HZb=vC@Lja zRPkuoq!uw{C@KZmvGkxb>ORu>nMFk>%93)+JWqF}mAcnw#isUkDRLF{^Ircb>GG88 z1DD3y&>a~o(#g*cbAMkI{inmCDAKTQMVdi#GVP_v_LbVck!m~ApB0R!I>MBtXpSZx zE-A}KU!M!ks9%#dTkb8jos0pHrTk@CqRV5)#G&Wtt@L0@X>Hd-J=|_`twcJK^%iGT zx8L$G?)AR)T8{s}A0W8Q@@z|ci_12~igfa`Ywqu1=Z^l8^uxDSH;O^yjG9AxXO?Ne zQB>HGI-~9;_>J@ITgSW`6-%n(&%>l2vSmpHE2WwnH%+Qj$Xc{EtDh|FTq6cxE7B$P zuI{n$e9HP%bWfCFZ_=$L&+p;fs|cO5@*Ge{pjduz_rxKWGRrnetY^Io=>V{UrM9VF zsf!sa(#g-kng4#8P~vK6IHT?&q>oIjT3-T=cShYbJgF*^YO(fVswx;EBsf@mq(i*z3Om!!746rDR(q>~sS#MZR> zXU_KC*GWol1ZUKjol(mrg8RQGFMMCcWn=UAz2={8Hol(8IHO?e$x%qPJu2KrA?N-3Y5{Dk5nV!Bq;f%VfK#paA z3#7KmSdg(Io!n$sjV~~umDv>I#HJ>SEA>MXhgJf9BDL)~vN1LtzKV27wxgU;O?5Cz z`JSc`64v|8aio2v+XFY0Nk7OUwG~p^cS&u(CACeJg{;B`z~xfglsPmd!`mq~lb+Ip zA2Q2XK+58C0PvMw%i2k8myz0@1RR<;JeRbECP{2qenjHXEZ{1sZMV)gS-CbREND|f zJ%=}BdFh#0j3IUZuLS&1YP&}MKvzX7gjkv4`id(GAyySaj1_Xj2q8X5aCA>_uJ=CW zml#t6XVfo%YZ{#I3esk;*8;amZBLNe=G&nyMrymd)b{hhouqdSS30B4XhvEf?2J+m zsvV`a|DAehq^J@k@;(8XNg{Yh6s`UG%`)OPzKg&Gy;HiS5a zl&Se;;6u)+QibQE!pR3i0{j)>bNX~_Tb4v>ySCK!KM0Gt)MsU!QJ0sIsX;zX+G_B6 z;QRTwhv`CylL*!Rvz=6CX8!H8+Swz%ybxj@A#mZD%xmKBQ@r_yP9!`eo8mKsl!vWyC@0RS z-%D*R!8e>o0lP?TZy-c8J`5~J@MEn^D4?(our@(KY+K>_H%M*&iEzK?|G%dZc3lSm zFG_76BKUNlB~%<*1$Z^-V4kKpl~C+_-T3#q1XJUDsqMAEAEHdF5`5X)Q(O({FMzI6 z>dqPUpw#vw1ouH+iNAwjy?9D$dp+TMdXn&5UyIQ3q|<`vXlGQ_sMvoXMDgbPg|1Ba z?EYP9dlSLW`zRq$Y8D|~u2bHyqh4%5Eu2w{qQCb>+Ee>BCj@;iZfQB45I9n);(Z1| zP`Wc|?M^bmcc@>PViedzYWrA+@s9)Nh|MfN+ZUWs z&&B|wn`vK&>?gIo&lzZJYgqu3Te}2uXsX)=il{7iQ{>^OW@LJrun;?kbZT6$yhzx^?(U4L zQWT|eV)2&UNq6EoUTV7+j1}o*W)ARf?|r^s=9vC48~9a|6FQ?_c19gZs7#cP4(^3( z0qKnTaK9fc&Zs*GCc=woFH|uQoKg2Uqc$V#fa@sKdBAxDQ}f|9=D9i}+g${|?UyOn zX72~UTbxnfuOUYZ&Zyf6-_>EH)0a9ik5IvD183Bg4a&688FdiB%2IaM?SadHjhs%vx%;|kyksqOlN?_+y{%j4Aq_fgg*u^`HEKVczs4RCYCV*Zw3#+#reXFFOM zJ|nd~g^*Xe7a^K=4T1@C5kav%NLV;s?2NjAz!84`gdoY m37t_FNo|)Q1XAVM -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Notebook - - -IP - - - - - -[ - - - - - -] - - - - - -: - - - - - -y - - - - - - - \ No newline at end of file diff --git a/IPython/html/static/base/images/logo.png b/IPython/html/static/base/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7175984c054b602ea1101853ebb05c44f6affbbc GIT binary patch literal 5420 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').css('color', 'red'); @@ -130,7 +142,9 @@ define([ buttons: { OK: { class : "btn-primary", click: function() { - // validate json and set it + /** + * validate json and set it + */ var new_md; try { new_md = JSON.parse(editor.getValue()); @@ -153,6 +167,7 @@ define([ var dialog = { modal : modal, + kernel_modal : kernel_modal, edit_metadata : edit_metadata, }; diff --git a/IPython/html/static/base/js/keyboard.js b/IPython/html/static/base/js/keyboard.js index 211ce2a..474b240 100644 --- a/IPython/html/static/base/js/keyboard.js +++ b/IPython/html/static/base/js/keyboard.js @@ -1,22 +1,33 @@ // Copyright (c) IPython Development Team. // Distributed under the terms of the Modified BSD License. +/** + * + * + * @module keyboard + * @namespace keyboard + * @class ShortcutManager + */ define([ 'base/js/namespace', 'jquery', 'base/js/utils', -], function(IPython, $, utils) { + 'underscore', +], function(IPython, $, utils, _) { "use strict"; - // Setup global keycodes and inverse keycodes. + /** + * Setup global keycodes and inverse keycodes. + * + * See http://unixpapa.com/js/key.html for a complete description. The short of + * it is that there are different keycode sets. Firefox uses the "Mozilla keycodes" + * and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same + * but have minor differences. + **/ - // See http://unixpapa.com/js/key.html for a complete description. The short of - // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes" - // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same - // but have minor differences. - - // These apply to Firefox, (Webkit and IE) + // These apply to Firefox, (Webkit and IE) + // This does work **only** on US keyboard. var _keycodes = { 'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73, 'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82, @@ -77,13 +88,32 @@ define([ }; var normalize_shortcut = function (shortcut) { - // Put a shortcut into normalized form: - // 1. Make lowercase - // 2. Replace cmd by meta - // 3. Sort '-' separated modifiers into the order alt-ctrl-meta-shift - // 4. Normalize keys + /** + * @function _normalize_shortcut + * @private + * return a dict containing the normalized shortcut and the number of time it should be pressed: + * + * Put a shortcut into normalized form: + * 1. Make lowercase + * 2. Replace cmd by meta + * 3. Sort '-' separated modifiers into the order alt-ctrl-meta-shift + * 4. Normalize keys + **/ + if (platform === 'MacOS') { + shortcut = shortcut.toLowerCase().replace('cmdtrl-', 'cmd-'); + } else { + shortcut = shortcut.toLowerCase().replace('cmdtrl-', 'ctrl-'); + } + shortcut = shortcut.toLowerCase().replace('cmd', 'meta'); shortcut = shortcut.replace(/-$/, '_'); // catch shortcuts using '-' key + shortcut = shortcut.replace(/,$/, 'comma'); // catch shortcuts using '-' key + if(shortcut.indexOf(',') !== -1){ + var sht = shortcut.split(','); + sht = _.map(sht, normalize_shortcut); + return shortcut; + } + shortcut = shortcut.replace(/comma/g, ','); // catch shortcuts using '-' key var values = shortcut.split("-"); if (values.length === 1) { return normalize_key(values[0]); @@ -96,7 +126,9 @@ define([ }; var shortcut_to_event = function (shortcut, type) { - // Convert a shortcut (shift-r) to a jQuery Event object + /** + * Convert a shortcut (shift-r) to a jQuery Event object + **/ type = type || 'keydown'; shortcut = normalize_shortcut(shortcut); shortcut = shortcut.replace(/-$/, '_'); // catch shortcuts using '-' key @@ -111,8 +143,21 @@ define([ return $.Event(type, opts); }; + var only_modifier_event = function(event){ + /** + * Return `true` if the event only contains modifiers keys. + * false otherwise + **/ + var key = inv_keycodes[event.which]; + return ((event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) && + (key === 'alt'|| key === 'ctrl'|| key === 'meta'|| key === 'shift')); + + }; + var event_to_shortcut = function (event) { - // Convert a jQuery Event object to a shortcut (shift-r) + /** + * Convert a jQuery Event object to a normalized shortcut string (shift-r) + **/ var shortcut = ''; var key = inv_keycodes[event.which]; if (event.altKey && key !== 'alt') {shortcut += 'alt-';} @@ -125,35 +170,86 @@ define([ // Shortcut manager class - var ShortcutManager = function (delay, events) { + var ShortcutManager = function (delay, events, actions, env) { + /** + * A class to deal with keyboard event and shortcut + * + * @class ShortcutManager + * @constructor + */ this._shortcuts = {}; - this._counts = {}; - this._timers = {}; this.delay = delay || 800; // delay in milliseconds this.events = events; + this.actions = actions; + this.actions.extend_env(env); + this._queue = []; + this._cleartimeout = null; + Object.seal(this); + }; + + ShortcutManager.prototype.clearsoon = function(){ + /** + * Clear the pending shortcut soon, and cancel previous clearing + * that might be registered. + **/ + var that = this; + clearTimeout(this._cleartimeout); + this._cleartimeout = setTimeout(function(){that.clearqueue();}, this.delay); + }; + + + ShortcutManager.prototype.clearqueue = function(){ + /** + * clear the pending shortcut sequence now. + **/ + this._queue = []; + clearTimeout(this._cleartimeout); + }; + + + var flatten_shorttree = function(tree){ + /** + * Flatten a tree of shortcut sequences. + * use full to iterate over all the key/values of available shortcuts. + **/ + var dct = {}; + for(var key in tree){ + var value = tree[key]; + if(typeof(value) === 'string'){ + dct[key] = value; + } else { + var ftree=flatten_shorttree(value); + for(var subkey in ftree){ + dct[key+','+subkey] = ftree[subkey]; + } + } + } + return dct; }; ShortcutManager.prototype.help = function () { var help = []; - for (var shortcut in this._shortcuts) { - var help_string = this._shortcuts[shortcut].help; - var help_index = this._shortcuts[shortcut].help_index; + var ftree = flatten_shorttree(this._shortcuts); + for (var shortcut in ftree) { + var action = this.actions.get(ftree[shortcut]); + var help_string = action.help||'== no help =='; + var help_index = action.help_index; if (help_string) { - if (platform === 'MacOS') { - shortcut = shortcut.replace('meta', 'cmd'); - } + var shortstring = (action.shortstring||shortcut); help.push({ - shortcut: shortcut, + shortcut: shortstring, help: help_string, help_index: help_index} ); } } help.sort(function (a, b) { - if (a.help_index > b.help_index) + if (a.help_index > b.help_index){ return 1; - if (a.help_index < b.help_index) + } + if (a.help_index < b.help_index){ return -1; + } return 0; }); return help; @@ -163,19 +259,105 @@ define([ this._shortcuts = {}; }; - ShortcutManager.prototype.add_shortcut = function (shortcut, data, suppress_help_update) { - if (typeof(data) === 'function') { - data = {help: '', help_index: '', handler: data}; + ShortcutManager.prototype.get_shortcut = function (shortcut){ + /** + * return a node of the shortcut tree which an action name (string) if leaf, + * and an object with `object.subtree===true` + **/ + if(typeof(shortcut) === 'string'){ + shortcut = shortcut.split(','); + } + + return this._get_leaf(shortcut, this._shortcuts); + }; + + + ShortcutManager.prototype._get_leaf = function(shortcut_array, tree){ + /** + * @private + * find a leaf/node in a subtree of the keyboard shortcut + * + **/ + if(shortcut_array.length === 1){ + return tree[shortcut_array[0]]; + } else if( typeof(tree[shortcut_array[0]]) !== 'string'){ + return this._get_leaf(shortcut_array.slice(1), tree[shortcut_array[0]]); + } + return null; + }; + + ShortcutManager.prototype.set_shortcut = function( shortcut, action_name){ + if( typeof(action_name) !== 'string'){ throw('action is not a string', action_name);} + if( typeof(shortcut) === 'string'){ + shortcut = shortcut.split(','); + } + return this._set_leaf(shortcut, action_name, this._shortcuts); + }; + + ShortcutManager.prototype._is_leaf = function(shortcut_array, tree){ + if(shortcut_array.length === 1){ + return(typeof(tree[shortcut_array[0]]) === 'string'); + } else { + var subtree = tree[shortcut_array[0]]; + return this._is_leaf(shortcut_array.slice(1), subtree ); } - data.help_index = data.help_index || ''; - data.help = data.help || ''; - data.count = data.count || 1; - if (data.help_index === '') { - data.help_index = 'zz'; + }; + + ShortcutManager.prototype._remove_leaf = function(shortcut_array, tree, allow_node){ + if(shortcut_array.length === 1){ + var current_node = tree[shortcut_array[0]]; + if(typeof(current_node) === 'string'){ + delete tree[shortcut_array[0]]; + } else { + throw('try to delete non-leaf'); + } + } else { + this._remove_leaf(shortcut_array.slice(1), tree[shortcut_array[0]], allow_node); + if(_.keys(tree[shortcut_array[0]]).length === 0){ + delete tree[shortcut_array[0]]; + } } + }; + + ShortcutManager.prototype._set_leaf = function(shortcut_array, action_name, tree){ + var current_node = tree[shortcut_array[0]]; + if(shortcut_array.length === 1){ + if(current_node !== undefined && typeof(current_node) !== 'string'){ + console.warn('[warning], you are overriting a long shortcut with a shorter one'); + } + tree[shortcut_array[0]] = action_name; + return true; + } else { + if(typeof(current_node) === 'string'){ + console.warn('you are trying to set a shortcut that will be shadowed'+ + 'by a more specific one. Aborting for :', action_name, 'the follwing '+ + 'will take precedence', current_node); + return false; + } else { + tree[shortcut_array[0]] = tree[shortcut_array[0]]||{}; + } + this._set_leaf(shortcut_array.slice(1), action_name, tree[shortcut_array[0]]); + return true; + } + }; + + ShortcutManager.prototype.add_shortcut = function (shortcut, data, suppress_help_update) { + /** + * Add a action to be handled by shortcut manager. + * + * - `shortcut` should be a `Shortcut Sequence` of the for `Ctrl-Alt-C,Meta-X`... + * - `data` could be an `action name`, an `action` or a `function`. + * if a `function` is passed it will be converted to an anonymous `action`. + * + **/ + var action_name = this.actions.get_name(data); + if (! action_name){ + throw('does nto know how to deal with ', data); + } + shortcut = normalize_shortcut(shortcut); - this._counts[shortcut] = 0; - this._shortcuts[shortcut] = data; + this.set_shortcut(shortcut, action_name); + if (!suppress_help_update) { // update the keyboard shortcuts notebook help this.events.trigger('rebuild.QuickHelp'); @@ -183,6 +365,11 @@ define([ }; ShortcutManager.prototype.add_shortcuts = function (data) { + /** + * Convenient methods to call `add_shortcut(key, value)` on several items + * + * data : Dict of the form {key:value, ...} + **/ for (var shortcut in data) { this.add_shortcut(shortcut, data[shortcut], true); } @@ -191,55 +378,63 @@ define([ }; ShortcutManager.prototype.remove_shortcut = function (shortcut, suppress_help_update) { + /** + * Remove the binding of shortcut `sortcut` with its action. + * throw an error if trying to remove a non-exiting shortcut + **/ shortcut = normalize_shortcut(shortcut); - delete this._counts[shortcut]; - delete this._shortcuts[shortcut]; + if( typeof(shortcut) === 'string'){ + shortcut = shortcut.split(','); + } + this._remove_leaf(shortcut, this._shortcuts); if (!suppress_help_update) { // update the keyboard shortcuts notebook help this.events.trigger('rebuild.QuickHelp'); } }; - ShortcutManager.prototype.count_handler = function (shortcut, event, data) { - var that = this; - var c = this._counts; - var t = this._timers; - var timer = null; - if (c[shortcut] === data.count-1) { - c[shortcut] = 0; - timer = t[shortcut]; - if (timer) {clearTimeout(timer); delete t[shortcut];} - return data.handler(event); - } else { - c[shortcut] = c[shortcut] + 1; - timer = setTimeout(function () { - c[shortcut] = 0; - }, that.delay); - t[shortcut] = timer; - } - return false; - }; + ShortcutManager.prototype.call_handler = function (event) { + /** + * Call the corresponding shortcut handler for a keyboard event + * @method call_handler + * @return {Boolean} `true|false`, `false` if no handler was found, otherwise the value return by the handler. + * @param event {event} + * + * given an event, call the corresponding shortcut. + * return false is event wan handled, true otherwise + * in any case returning false stop event propagation + **/ + + + this.clearsoon(); + if(only_modifier_event(event)){ + return true; + } var shortcut = event_to_shortcut(event); - var data = this._shortcuts[shortcut]; - if (data) { - var handler = data.handler; - if (handler) { - if (data.count === 1) { - return handler(event); - } else if (data.count > 1) { - return this.count_handler(shortcut, event, data); - } - } + this._queue.push(shortcut); + var action_name = this.get_shortcut(this._queue); + + if (typeof(action_name) === 'undefined'|| action_name === null){ + this.clearqueue(); + return true; } - return true; + + if (this.actions.exists(action_name)) { + event.preventDefault(); + this.clearqueue(); + return this.actions.call(action_name, event); + } + + return false; }; + ShortcutManager.prototype.handles = function (event) { var shortcut = event_to_shortcut(event); - var data = this._shortcuts[shortcut]; - return !( data === undefined || data.handler === undefined ); + var action_name = this.get_shortcut(this._queue.concat(shortcut)); + return (typeof(action_name) !== 'undefined'); }; var keyboard = { @@ -249,10 +444,10 @@ define([ normalize_key : normalize_key, normalize_shortcut : normalize_shortcut, shortcut_to_event : shortcut_to_event, - event_to_shortcut : event_to_shortcut + event_to_shortcut : event_to_shortcut, }; - // For backwards compatability. + // For backwards compatibility. IPython.keyboard = keyboard; return keyboard; diff --git a/IPython/html/static/base/js/namespace.js b/IPython/html/static/base/js/namespace.js index c89cbc7..291e113 100644 --- a/IPython/html/static/base/js/namespace.js +++ b/IPython/html/static/base/js/namespace.js @@ -3,6 +3,7 @@ var IPython = IPython || {}; define([], function(){ + "use strict"; IPython.version = "3.0.0-dev"; return IPython; }); diff --git a/IPython/html/static/base/js/notificationarea.js b/IPython/html/static/base/js/notificationarea.js new file mode 100644 index 0000000..53607fe --- /dev/null +++ b/IPython/html/static/base/js/notificationarea.js @@ -0,0 +1,83 @@ +// Copyright (c) IPython Development Team. +// Distributed under the terms of the Modified BSD License. + +define([ + 'jquery', + 'base/js/notificationwidget', +], function($, notificationwidget) { + "use strict"; + + // store reference to the NotificationWidget class + var NotificationWidget = notificationwidget.NotificationWidget; + + /** + * Construct the NotificationArea object. Options are: + * events: $(Events) instance + * save_widget: SaveWidget instance + * notebook: Notebook instance + * keyboard_manager: KeyboardManager instance + * + * @constructor + * @param {string} selector - a jQuery selector string for the + * notification area element + * @param {Object} [options] - a dictionary of keyword arguments. + */ + var NotificationArea = function (selector, options) { + this.selector = selector; + this.events = options.events; + if (this.selector !== undefined) { + this.element = $(selector); + } + this.widget_dict = {}; + }; + + /** + * Get a widget by name, creating it if it doesn't exist. + * + * @method widget + * @param {string} name - the widget name + */ + NotificationArea.prototype.widget = function (name) { + if (this.widget_dict[name] === undefined) { + return this.new_notification_widget(name); + } + return this.get_widget(name); + }; + + /** + * Get a widget by name, throwing an error if it doesn't exist. + * + * @method get_widget + * @param {string} name - the widget name + */ + NotificationArea.prototype.get_widget = function (name) { + if(this.widget_dict[name] === undefined) { + throw('no widgets with this name'); + } + return this.widget_dict[name]; + }; + + /** + * Create a new notification widget with the given name. The + * widget must not already exist. + * + * @method new_notification_widget + * @param {string} name - the widget name + */ + NotificationArea.prototype.new_notification_widget = function (name) { + if (this.widget_dict[name] !== undefined) { + throw('widget with that name already exists!'); + } + + // create the element for the notification widget and add it + // to the notification aread element + var div = $('

').attr('id', 'notification_' + name); + $(this.selector).append(div); + + // create the widget object and return it + this.widget_dict[name] = new NotificationWidget('#notification_' + name); + return this.widget_dict[name]; + }; + + return {'NotificationArea': NotificationArea}; +}); diff --git a/IPython/html/static/notebook/js/notificationwidget.js b/IPython/html/static/base/js/notificationwidget.js similarity index 51% rename from IPython/html/static/notebook/js/notificationwidget.js rename to IPython/html/static/base/js/notificationwidget.js index d4ed891..0c7f672 100644 --- a/IPython/html/static/notebook/js/notificationwidget.js +++ b/IPython/html/static/base/js/notificationwidget.js @@ -7,6 +7,13 @@ define([ ], function(IPython, $) { "use strict"; + /** + * Construct a NotificationWidget object. + * + * @constructor + * @param {string} selector - a jQuery selector string for the + * notification widget element + */ var NotificationWidget = function (selector) { this.selector = selector; this.timeout = null; @@ -16,27 +23,41 @@ define([ this.style(); } this.element.hide(); - var that = this; - this.inner = $(''); this.element.append(this.inner); - }; + /** + * Add the 'notification_widget' CSS class to the widget element. + * + * @method style + */ NotificationWidget.prototype.style = function () { this.element.addClass('notification_widget'); }; - // msg : message to display - // timeout : time in ms before diseapearing - // - // if timeout <= 0 - // click_callback : function called if user click on notification - // could return false to prevent the notification to be dismissed + /** + * Set the notification widget message to display for a certain + * amount of time (timeout). The widget will be shown forever if + * timeout is <= 0 or undefined. If the widget is clicked while it + * is still displayed, execute an optional callback + * (click_callback). If the callback returns false, it will + * prevent the notification from being dismissed. + * + * Options: + * class - CSS class name for styling + * icon - CSS class name for the widget icon + * title - HTML title attribute for the widget + * + * @method set_message + * @param {string} msg - The notification to display + * @param {integer} [timeout] - The amount of time in milliseconds to display the widget + * @param {function} [click_callback] - The function to run when the widget is clicked + * @param {Object} [options] - Additional options + */ NotificationWidget.prototype.set_message = function (msg, timeout, click_callback, options) { - var options = options || {}; - var callback = click_callback || function() {return true;}; - var that = this; + options = options || {}; + // unbind potential previous callback this.element.unbind('click'); this.inner.attr('class', options.icon); @@ -47,52 +68,87 @@ define([ // reset previous set style this.element.removeClass(); this.style(); - if (options.class){ - - this.element.addClass(options.class) + if (options.class) { + this.element.addClass(options.class); } + + // clear previous timer if (this.timeout !== null) { clearTimeout(this.timeout); this.timeout = null; } - if (timeout !== undefined && timeout >=0) { + + // set the timer if a timeout is given + var that = this; + if (timeout !== undefined && timeout >= 0) { this.timeout = setTimeout(function () { that.element.fadeOut(100, function () {that.inner.text('');}); + that.element.unbind('click'); that.timeout = null; }, timeout); - } else { - this.element.click(function() { - if( callback() !== false ) { + } + + // bind the click callback if it is given + if (click_callback !== undefined) { + this.element.click(function () { + if (click_callback() !== false) { that.element.fadeOut(100, function () {that.inner.text('');}); - that.element.unbind('click'); } - if (that.timeout !== undefined) { - that.timeout = undefined; + that.element.unbind('click'); + if (that.timeout !== null) { clearTimeout(that.timeout); + that.timeout = null; } }); } }; - + /** + * Display an information message (styled with the 'info' + * class). Arguments are the same as in set_message. Default + * timeout is 3500 milliseconds. + * + * @method info + */ NotificationWidget.prototype.info = function (msg, timeout, click_callback, options) { - var options = options || {}; - options.class = options.class +' info'; - var timeout = timeout || 3500; + options = options || {}; + options.class = options.class + ' info'; + timeout = timeout || 3500; this.set_message(msg, timeout, click_callback, options); - } + }; + + /** + * Display a warning message (styled with the 'warning' + * class). Arguments are the same as in set_message. Messages are + * sticky by default. + * + * @method warning + */ NotificationWidget.prototype.warning = function (msg, timeout, click_callback, options) { - var options = options || {}; - options.class = options.class +' warning'; + options = options || {}; + options.class = options.class + ' warning'; this.set_message(msg, timeout, click_callback, options); - } + }; + + /** + * Display a danger message (styled with the 'danger' + * class). Arguments are the same as in set_message. Messages are + * sticky by default. + * + * @method danger + */ NotificationWidget.prototype.danger = function (msg, timeout, click_callback, options) { - var options = options || {}; - options.class = options.class +' danger'; + options = options || {}; + options.class = options.class + ' danger'; this.set_message(msg, timeout, click_callback, options); - } - + }; + /** + * Get the text of the widget message. + * + * @method get_message + * @return {string} - the message text + */ NotificationWidget.prototype.get_message = function () { return this.inner.html(); }; diff --git a/IPython/html/static/base/js/page.js b/IPython/html/static/base/js/page.js index 5fba330..7c25285 100644 --- a/IPython/html/static/base/js/page.js +++ b/IPython/html/static/base/js/page.js @@ -15,23 +15,29 @@ define([ }; Page.prototype.show = function () { - // The header and site divs start out hidden to prevent FLOUC. - // Main scripts should call this method after styling everything. + /** + * The header and site divs start out hidden to prevent FLOUC. + * Main scripts should call this method after styling everything. + */ this.show_header(); this.show_site(); }; Page.prototype.show_header = function () { - // The header and site divs start out hidden to prevent FLOUC. - // Main scripts should call this method after styling everything. - // TODO: selector are hardcoded, pass as constructor argument + /** + * The header and site divs start out hidden to prevent FLOUC. + * Main scripts should call this method after styling everything. + * TODO: selector are hardcoded, pass as constructor argument + */ $('div#header').css('display','block'); }; Page.prototype.show_site = function () { - // The header and site divs start out hidden to prevent FLOUC. - // Main scripts should call this method after styling everything. - // TODO: selector are hardcoded, pass as constructor argument + /** + * The header and site divs start out hidden to prevent FLOUC. + * Main scripts should call this method after styling everything. + * TODO: selector are hardcoded, pass as constructor argument + */ $('div#site').css('display','block'); }; diff --git a/IPython/html/static/base/js/security.js b/IPython/html/static/base/js/security.js index c8301f1..11ba8da 100644 --- a/IPython/html/static/base/js/security.js +++ b/IPython/html/static/base/js/security.js @@ -18,8 +18,10 @@ define([ } var sanitizeAttribs = function (tagName, attribs, opt_naiveUriRewriter, opt_nmTokenPolicy, opt_logger) { - // add trusting data-attributes to the default sanitizeAttribs from caja - // this function is mostly copied from the caja source + /** + * add trusting data-attributes to the default sanitizeAttribs from caja + * this function is mostly copied from the caja source + */ var ATTRIBS = caja.html4.ATTRIBS; for (var i = 0; i < attribs.length; i += 2) { var attribName = attribs[i]; @@ -34,9 +36,11 @@ define([ }; var sanitize_css = function (css, tagPolicy) { - // sanitize CSS - // like sanitize_html, but for CSS - // called by sanitize_stylesheets + /** + * sanitize CSS + * like sanitize_html, but for CSS + * called by sanitize_stylesheets + */ return caja.sanitizeStylesheet( window.location.pathname, css, @@ -51,8 +55,10 @@ define([ }; var sanitize_stylesheets = function (html, tagPolicy) { - // sanitize just the css in style tags in a block of html - // called by sanitize_html, if allow_css is true + /** + * sanitize just the css in style tags in a block of html + * called by sanitize_html, if allow_css is true + */ var h = $("
").append(html); var style_tags = h.find("style"); if (!style_tags.length) { @@ -66,9 +72,11 @@ define([ }; var sanitize_html = function (html, allow_css) { - // sanitize HTML - // if allow_css is true (default: false), CSS is sanitized as well. - // otherwise, CSS elements and attributes are simply removed. + /** + * sanitize HTML + * if allow_css is true (default: false), CSS is sanitized as well. + * otherwise, CSS elements and attributes are simply removed. + */ var html4 = caja.html4; if (allow_css) { diff --git a/IPython/html/static/base/js/utils.js b/IPython/html/static/base/js/utils.js index aa85d17..ce4ed4b 100644 --- a/IPython/html/static/base/js/utils.js +++ b/IPython/html/static/base/js/utils.js @@ -4,7 +4,8 @@ define([ 'base/js/namespace', 'jquery', -], function(IPython, $){ + 'codemirror/lib/codemirror', +], function(IPython, $, CodeMirror){ "use strict"; IPython.load_extensions = function () { @@ -153,7 +154,9 @@ define([ var uuid = function () { - // http://www.ietf.org/rfc/rfc4122.txt + /** + * http://www.ietf.org/rfc/rfc4122.txt + */ var s = []; var hexDigits = "0123456789ABCDEF"; for (var i = 0; i < 32; i++) { @@ -271,11 +274,11 @@ define([ } else { line = "background-color: "; } - line = line + "rgb(" + r + "," + g + "," + b + ");" - if ( !attrs["style"] ) { - attrs["style"] = line; + line = line + "rgb(" + r + "," + g + "," + b + ");"; + if ( !attrs.style ) { + attrs.style = line; } else { - attrs["style"] += " " + line; + attrs.style += " " + line; } } } @@ -284,27 +287,36 @@ define([ function ansispan(str) { // ansispan function adapted from github.com/mmalecki/ansispan (MIT License) // regular ansi escapes (using the table above) + var is_open = false; return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) { if (!pattern) { // [(01|22|39|)m close spans - return ""; - } - // consume sequence of color escapes - var numbers = pattern.match(/\d+/g); - var attrs = {}; - while (numbers.length > 0) { - _process_numbers(attrs, numbers); - } - - var span = ""; + } else { + return ""; + } + } else { + is_open = true; + + // consume sequence of color escapes + var numbers = pattern.match(/\d+/g); + var attrs = {}; + while (numbers.length > 0) { + _process_numbers(attrs, numbers); + } + + var span = ""; } - return span + ">"; }); - }; - + } + // Transform ANSI color escape codes into HTML tags with css // classes listed in the above ansi_colormap object. The actual color used // are set in the css file. @@ -345,7 +357,9 @@ define([ } var points_to_pixels = function (points) { - // A reasonably good way of converting between points and pixels. + /** + * A reasonably good way of converting between points and pixels. + */ var test = $('
'); $(body).append(test); var pixel_per_point = test.width()/10000; @@ -354,10 +368,12 @@ define([ }; var always_new = function (constructor) { - // wrapper around contructor to avoid requiring `var a = new constructor()` - // useful for passing constructors as callbacks, - // not for programmer laziness. - // from http://programmers.stackexchange.com/questions/118798 + /** + * wrapper around contructor to avoid requiring `var a = new constructor()` + * useful for passing constructors as callbacks, + * not for programmer laziness. + * from http://programmers.stackexchange.com/questions/118798 + */ return function () { var obj = Object.create(constructor.prototype); constructor.apply(obj, arguments); @@ -366,7 +382,9 @@ define([ }; var url_path_join = function () { - // join a sequence of url components with '/' + /** + * join a sequence of url components with '/' + */ var url = ''; for (var i = 0; i < arguments.length; i++) { if (arguments[i] === '') { @@ -382,36 +400,58 @@ define([ return url; }; + var url_path_split = function (path) { + /** + * Like os.path.split for URLs. + * Always returns two strings, the directory path and the base filename + */ + + var idx = path.lastIndexOf('/'); + if (idx === -1) { + return ['', path]; + } else { + return [ path.slice(0, idx), path.slice(idx + 1) ]; + } + }; + var parse_url = function (url) { - // an `a` element with an href allows attr-access to the parsed segments of a URL - // a = parse_url("http://localhost:8888/path/name#hash") - // a.protocol = "http:" - // a.host = "localhost:8888" - // a.hostname = "localhost" - // a.port = 8888 - // a.pathname = "/path/name" - // a.hash = "#hash" + /** + * an `a` element with an href allows attr-access to the parsed segments of a URL + * a = parse_url("http://localhost:8888/path/name#hash") + * a.protocol = "http:" + * a.host = "localhost:8888" + * a.hostname = "localhost" + * a.port = 8888 + * a.pathname = "/path/name" + * a.hash = "#hash" + */ var a = document.createElement("a"); a.href = url; return a; }; var encode_uri_components = function (uri) { - // encode just the components of a multi-segment uri, - // leaving '/' separators + /** + * encode just the components of a multi-segment uri, + * leaving '/' separators + */ return uri.split('/').map(encodeURIComponent).join('/'); }; var url_join_encode = function () { - // join a sequence of url components with '/', - // encoding each component with encodeURIComponent + /** + * join a sequence of url components with '/', + * encoding each component with encodeURIComponent + */ return encode_uri_components(url_path_join.apply(null, arguments)); }; var splitext = function (filename) { - // mimic Python os.path.splitext - // Returns ['base', '.ext'] + /** + * mimic Python os.path.splitext + * Returns ['base', '.ext'] + */ var idx = filename.lastIndexOf('.'); if (idx > 0) { return [filename.slice(0, idx), filename.slice(idx)]; @@ -422,20 +462,26 @@ define([ var escape_html = function (text) { - // escape text to HTML + /** + * escape text to HTML + */ return $("
").text(text).html(); }; var get_body_data = function(key) { - // get a url-encoded item from body.data and decode it - // we should never have any encoded URLs anywhere else in code - // until we are building an actual request + /** + * get a url-encoded item from body.data and decode it + * we should never have any encoded URLs anywhere else in code + * until we are building an actual request + */ return decodeURIComponent($('body').data(key)); }; var to_absolute_cursor_pos = function (cm, cursor) { - // get the absolute cursor position from CodeMirror's col, ch + /** + * get the absolute cursor position from CodeMirror's col, ch + */ if (!cursor) { cursor = cm.getCursor(); } @@ -447,7 +493,9 @@ define([ }; var from_absolute_cursor_pos = function (cm, cursor_pos) { - // turn absolute cursor postion into CodeMirror col, ch cursor + /** + * turn absolute cursor postion into CodeMirror col, ch cursor + */ var i, line; var offset = 0; for (i = 0, line=cm.getLine(i); line !== undefined; i++, line=cm.getLine(i)) { @@ -495,12 +543,16 @@ define([ })(); var is_or_has = function (a, b) { - // Is b a child of a or a itself? + /** + * Is b a child of a or a itself? + */ return a.has(b).length !==0 || a.is(b); }; var is_focused = function (e) { - // Is element e, or one of its children focused? + /** + * Is element e, or one of its children focused? + */ e = $(e); var target = $(document.activeElement); if (target.length > 0) { @@ -521,21 +573,198 @@ define([ }; var ajax_error_msg = function (jqXHR) { - // Return a JSON error message if there is one, - // otherwise the basic HTTP status text. - if (jqXHR.responseJSON && jqXHR.responseJSON.message) { + /** + * Return a JSON error message if there is one, + * otherwise the basic HTTP status text. + */ + if (jqXHR.responseJSON && jqXHR.responseJSON.traceback) { + return jqXHR.responseJSON.traceback; + } else if (jqXHR.responseJSON && jqXHR.responseJSON.message) { return jqXHR.responseJSON.message; } else { return jqXHR.statusText; } - } + }; var log_ajax_error = function (jqXHR, status, error) { - // log ajax failures with informative messages + /** + * log ajax failures with informative messages + */ var msg = "API request failed (" + jqXHR.status + "): "; console.log(jqXHR); msg += ajax_error_msg(jqXHR); console.log(msg); }; + + var requireCodeMirrorMode = function (mode, callback, errback) { + /** + * load a mode with requirejs + */ + if (typeof mode != "string") mode = mode.name; + if (CodeMirror.modes.hasOwnProperty(mode)) { + callback(CodeMirror.modes.mode); + return; + } + require([ + // might want to use CodeMirror.modeURL here + ['codemirror/mode', mode, mode].join('/'), + ], callback, errback + ); + }; + + /** Error type for wrapped XHR errors. */ + var XHR_ERROR = 'XhrError'; + + /** + * Wraps an AJAX error as an Error object. + */ + var wrap_ajax_error = function (jqXHR, status, error) { + var wrapped_error = new Error(ajax_error_msg(jqXHR)); + wrapped_error.name = XHR_ERROR; + // provide xhr response + wrapped_error.xhr = jqXHR; + wrapped_error.xhr_status = status; + wrapped_error.xhr_error = error; + return wrapped_error; + }; + + var promising_ajax = function(url, settings) { + /** + * Like $.ajax, but returning an ES6 promise. success and error settings + * will be ignored. + */ + return new Promise(function(resolve, reject) { + settings.success = function(data, status, jqXHR) { + resolve(data); + }; + settings.error = function(jqXHR, status, error) { + log_ajax_error(jqXHR, status, error); + reject(wrap_ajax_error(jqXHR, status, error)); + }; + $.ajax(url, settings); + }); + }; + + var WrappedError = function(message, error){ + /** + * Wrappable Error class + * + * The Error class doesn't actually act on `this`. Instead it always + * returns a new instance of Error. Here we capture that instance so we + * can apply it's properties to `this`. + */ + var tmp = Error.apply(this, [message]); + + // Copy the properties of the error over to this. + var properties = Object.getOwnPropertyNames(tmp); + for (var i = 0; i < properties.length; i++) { + this[properties[i]] = tmp[properties[i]]; + } + + // Keep a stack of the original error messages. + if (error instanceof WrappedError) { + this.error_stack = error.error_stack; + } else { + this.error_stack = [error]; + } + this.error_stack.push(tmp); + + return this; + }; + + WrappedError.prototype = Object.create(Error.prototype, {}); + + + var load_class = function(class_name, module_name, registry) { + /** + * Tries to load a class + * + * Tries to load a class from a module using require.js, if a module + * is specified, otherwise tries to load a class from the global + * registry, if the global registry is provided. + */ + return new Promise(function(resolve, reject) { + + // Try loading the view module using require.js + if (module_name) { + require([module_name], function(module) { + if (module[class_name] === undefined) { + reject(new Error('Class '+class_name+' not found in module '+module_name)); + } else { + resolve(module[class_name]); + } + }, reject); + } else { + if (registry && registry[class_name]) { + resolve(registry[class_name]); + } else { + reject(new Error('Class '+class_name+' not found in registry ')); + } + } + }); + }; + + var resolve_promises_dict = function(d) { + /** + * Resolve a promiseful dictionary. + * Returns a single Promise. + */ + var keys = Object.keys(d); + var values = []; + keys.forEach(function(key) { + values.push(d[key]); + }); + return Promise.all(values).then(function(v) { + d = {}; + for(var i=0; i - * MIT license - * - * Includes enhancements by Scott Trenda - * and Kris Kowal - * - * Accepts a date, a mask, or a date and a mask. - * Returns a formatted version of the given date. - * The date defaults to the current date/time. - * The mask defaults to dateFormat.masks.default. - */ - -var dateFormat = function () { - var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g, - timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g, - timezoneClip = /[^-+\dA-Z]/g, - pad = function (val, len) { - val = String(val); - len = len || 2; - while (val.length < len) val = "0" + val; - return val; - }; - - // Regexes and supporting functions are cached through closure - return function (date, mask, utc) { - var dF = dateFormat; - - // You can't provide utc if you skip other args (use the "UTC:" mask prefix) - if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) { - mask = date; - date = undefined; - } - - // Passing date through Date applies Date.parse, if necessary - date = date ? new Date(date) : new Date; - if (isNaN(date)) throw SyntaxError("invalid date"); - - mask = String(dF.masks[mask] || mask || dF.masks["default"]); - - // Allow setting the utc argument via the mask - if (mask.slice(0, 4) == "UTC:") { - mask = mask.slice(4); - utc = true; - } - - var _ = utc ? "getUTC" : "get", - d = date[_ + "Date"](), - D = date[_ + "Day"](), - m = date[_ + "Month"](), - y = date[_ + "FullYear"](), - H = date[_ + "Hours"](), - M = date[_ + "Minutes"](), - s = date[_ + "Seconds"](), - L = date[_ + "Milliseconds"](), - o = utc ? 0 : date.getTimezoneOffset(), - flags = { - d: d, - dd: pad(d), - ddd: dF.i18n.dayNames[D], - dddd: dF.i18n.dayNames[D + 7], - m: m + 1, - mm: pad(m + 1), - mmm: dF.i18n.monthNames[m], - mmmm: dF.i18n.monthNames[m + 12], - yy: String(y).slice(2), - yyyy: y, - h: H % 12 || 12, - hh: pad(H % 12 || 12), - H: H, - HH: pad(H), - M: M, - MM: pad(M), - s: s, - ss: pad(s), - l: pad(L, 3), - L: pad(L > 99 ? Math.round(L / 10) : L), - t: H < 12 ? "a" : "p", - tt: H < 12 ? "am" : "pm", - T: H < 12 ? "A" : "P", - TT: H < 12 ? "AM" : "PM", - Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""), - o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4), - S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10] - }; - - return mask.replace(token, function ($0) { - return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); - }); - }; -}(); - -// Some common format strings -dateFormat.masks = { - "default": "ddd mmm dd yyyy HH:MM:ss", - shortDate: "m/d/yy", - mediumDate: "mmm d, yyyy", - longDate: "mmmm d, yyyy", - fullDate: "dddd, mmmm d, yyyy", - shortTime: "h:MM TT", - mediumTime: "h:MM:ss TT", - longTime: "h:MM:ss TT Z", - isoDate: "yyyy-mm-dd", - isoTime: "HH:MM:ss", - isoDateTime: "yyyy-mm-dd'T'HH:MM:ss", - isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'" -}; - -// Internationalization strings -dateFormat.i18n = { - dayNames: [ - "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", - "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" - ], - monthNames: [ - "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", - "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" - ] -}; - -// For convenience... -Date.prototype.format = function (mask, utc) { - return dateFormat(this, mask, utc); -}; \ No newline at end of file diff --git a/IPython/html/static/edit/js/editor.js b/IPython/html/static/edit/js/editor.js new file mode 100644 index 0000000..f2db0c8 --- /dev/null +++ b/IPython/html/static/edit/js/editor.js @@ -0,0 +1,78 @@ +// Copyright (c) IPython Development Team. +// Distributed under the terms of the Modified BSD License. + +define([ + 'jquery', + 'base/js/utils', + 'codemirror/lib/codemirror', + 'codemirror/mode/meta', + 'codemirror/addon/search/search' + ], +function($, + utils, + CodeMirror +) { + var Editor = function(selector, options) { + this.selector = selector; + this.contents = options.contents; + this.events = options.events; + this.base_url = options.base_url; + this.file_path = options.file_path; + + this.codemirror = CodeMirror($(this.selector)[0]); + + // It appears we have to set commands on the CodeMirror class, not the + // instance. I'd like to be wrong, but since there should only be one CM + // instance on the page, this is good enough for now. + CodeMirror.commands.save = $.proxy(this.save, this); + + this.save_enabled = false; + }; + + Editor.prototype.load = function() { + var that = this; + var cm = this.codemirror; + this.contents.get(this.file_path, {type: 'file', format: 'text'}) + .then(function(model) { + cm.setValue(model.content); + + // Setting the file's initial value creates a history entry, + // which we don't want. + cm.clearHistory(); + + // Find and load the highlighting mode + var modeinfo = CodeMirror.findModeByMIME(model.mimetype); + if (modeinfo) { + utils.requireCodeMirrorMode(modeinfo.mode, function() { + cm.setOption('mode', modeinfo.mode); + }); + } + that.save_enabled = true; + }, + function(error) { + cm.setValue("Error! " + error.message + + "\nSaving disabled."); + that.save_enabled = false; + } + ); + }; + + Editor.prototype.save = function() { + if (!this.save_enabled) { + console.log("Not saving, save disabled"); + return; + } + var model = { + path: this.file_path, + type: 'file', + format: 'text', + content: this.codemirror.getValue(), + }; + var that = this; + this.contents.save(this.file_path, model).then(function() { + that.events.trigger("save_succeeded.TextEditor"); + }); + }; + + return {Editor: Editor}; +}); diff --git a/IPython/html/static/edit/js/main.js b/IPython/html/static/edit/js/main.js new file mode 100644 index 0000000..ec787d4 --- /dev/null +++ b/IPython/html/static/edit/js/main.js @@ -0,0 +1,64 @@ +// Copyright (c) IPython Development Team. +// Distributed under the terms of the Modified BSD License. + +require([ + 'base/js/namespace', + 'base/js/utils', + 'base/js/page', + 'base/js/events', + 'contents', + 'services/config', + 'edit/js/editor', + 'edit/js/menubar', + 'edit/js/notificationarea', + 'custom/custom', +], function( + IPython, + utils, + page, + events, + contents, + configmod, + editor, + menubar, + notificationarea + ){ + page = new page.Page(); + + var base_url = utils.get_body_data('baseUrl'); + var file_path = utils.get_body_data('filePath'); + contents = new contents.Contents({base_url: base_url}); + var config = new configmod.ConfigSection('edit', {base_url: base_url}) + config.load(); + + var editor = new editor.Editor('#texteditor-container', { + base_url: base_url, + events: events, + contents: contents, + file_path: file_path, + }); + + // Make it available for debugging + IPython.editor = editor; + + var menus = new menubar.MenuBar('#menubar', { + base_url: base_url, + editor: editor, + }); + + var notification_area = new notificationarea.EditorNotificationArea( + '#notification_area', { + events: events, + }); + notification_area.init_notification_widgets(); + + config.loaded.then(function() { + if (config.data.load_extensions) { + var nbextension_paths = Object.getOwnPropertyNames( + config.data.load_extensions); + IPython.load_extensions.apply(this, nbextension_paths); + } + }); + editor.load(); + page.show(); +}); diff --git a/IPython/html/static/edit/js/menubar.js b/IPython/html/static/edit/js/menubar.js new file mode 100644 index 0000000..15b1b4e --- /dev/null +++ b/IPython/html/static/edit/js/menubar.js @@ -0,0 +1,50 @@ +// Copyright (c) IPython Development Team. +// Distributed under the terms of the Modified BSD License. + +define([ + 'base/js/namespace', + 'jquery', + 'base/js/utils', + 'bootstrap', +], function(IPython, $, utils, bootstrap) { + "use strict"; + + var MenuBar = function (selector, options) { + /** + * Constructor + * + * A MenuBar Class to generate the menubar of IPython notebook + * + * Parameters: + * selector: string + * options: dictionary + * Dictionary of keyword arguments. + * codemirror: CodeMirror instance + * contents: ContentManager instance + * events: $(Events) instance + * base_url : string + * file_path : string + */ + options = options || {}; + this.base_url = options.base_url || utils.get_body_data("baseUrl"); + this.selector = selector; + this.editor = options.editor; + + if (this.selector !== undefined) { + this.element = $(selector); + this.bind_events(); + } + }; + + MenuBar.prototype.bind_events = function () { + /** + * File + */ + var that = this; + this.element.find('#save_file').click(function () { + that.editor.save(); + }); + }; + + return {'MenuBar': MenuBar}; +}); diff --git a/IPython/html/static/edit/js/notificationarea.js b/IPython/html/static/edit/js/notificationarea.js new file mode 100644 index 0000000..fd98ebf --- /dev/null +++ b/IPython/html/static/edit/js/notificationarea.js @@ -0,0 +1,29 @@ +define([ + 'base/js/notificationarea' +], function(notificationarea) { + "use strict"; + var NotificationArea = notificationarea.NotificationArea; + + var EditorNotificationArea = function(selector, options) { + NotificationArea.apply(this, [selector, options]); + } + + EditorNotificationArea.prototype = Object.create(NotificationArea.prototype); + + /** + * Initialize the default set of notification widgets. + * + * @method init_notification_widgets + */ + EditorNotificationArea.prototype.init_notification_widgets = function () { + var that = this; + var enw = this.new_notification_widget('editor'); + + this.events.on("save_succeeded.TextEditor", function() { + enw.set_message("File saved", 2000); + }); + }; + + + return {EditorNotificationArea: EditorNotificationArea}; +}); diff --git a/IPython/html/static/notebook/js/about.js b/IPython/html/static/notebook/js/about.js new file mode 100644 index 0000000..b8f106e --- /dev/null +++ b/IPython/html/static/notebook/js/about.js @@ -0,0 +1,38 @@ +// Copyright (c) IPython Development Team. +// Distributed under the terms of the Modified BSD License. +require([ + 'jquery', + 'base/js/dialog', + 'underscore', + 'base/js/namespace' +], function ($, dialog, _, IPython) { + 'use strict'; + $('#notebook_about').click(function () { + // use underscore template to auto html escape + var text = 'You are using IPython notebook.

'; + text = text + 'The version of the notebook server is '; + text = text + _.template('<%- version %>')({ version: sys_info.ipython_version }); + if (sys_info.commit_hash) { + text = text + _.template('-<%- hash %>')({ hash: sys_info.commit_hash }); + } + text = text + _.template(' and is running on:
Python <%- pyver %>
')({ pyver: sys_info.sys_version }); + var kinfo = $('
').attr('id', '#about-kinfo').text('Waiting for kernel to be available...'); + var body = $('
'); + body.append($('

').text('Server Information:')); + body.append($('

').html(text)); + body.append($('

').text('Current Kernel Information:')); + body.append(kinfo); + dialog.modal({ + title: 'About IPython Notebook', + body: body, + buttons: { 'OK': {} } + }); + try { + IPython.notebook.session.kernel.kernel_info(function (data) { + kinfo.html($('
').text(data.content.banner));
+            });
+        } catch (e) {
+            kinfo.html($('

').text('unable to contact kernel')); + } + }); +}); diff --git a/IPython/html/static/notebook/js/actions.js b/IPython/html/static/notebook/js/actions.js new file mode 100644 index 0000000..f91091d --- /dev/null +++ b/IPython/html/static/notebook/js/actions.js @@ -0,0 +1,503 @@ +// Copyright (c) IPython Development Team. +// Distributed under the terms of the Modified BSD License. + +define(['require' +], function(require) { + "use strict"; + + var ActionHandler = function (env) { + this.env = env || {}; + Object.seal(this); + }; + + /** + * A bunch of predefined `Simple Actions` used by IPython. + * `Simple Actions` have the following keys: + * help (optional): a short string the describe the action. + * will be used in various context, like as menu name, tool tips on buttons, + * and short description in help menu. + * help_index (optional): a string used to sort action in help menu. + * icon (optional): a short string that represent the icon that have to be used with this + * action. this should mainly correspond to a Font_awesome class. + * handler : a function which is called when the action is activated. It will receive at first parameter + * a dictionary containing various handle to element of the notebook. + * + * action need to be registered with a **name** that can be use to refer to this action. + * + * + * if `help` is not provided it will be derived by replacing any dash by space + * in the **name** of the action. It is advised to provide a prefix to action name to + * avoid conflict the prefix should be all lowercase and end with a dot `.` + * in the absence of a prefix the behavior of the action is undefined. + * + * All action provided by IPython are prefixed with `ipython.`. + * + * One can register extra actions or replace an existing action with another one is possible + * but is considered undefined behavior. + * + **/ + var _action = { + 'run-select-next': { + icon: 'fa-play', + help : 'run cell, select below', + help_index : 'ba', + handler : function (env) { + env.notebook.execute_cell_and_select_below(); + } + }, + 'execute-in-place':{ + help : 'run cell', + help_index : 'bb', + handler : function (env) { + env.notebook.execute_cell(); + } + }, + 'execute-and-insert-after':{ + help : 'run cell, insert below', + help_index : 'bc', + handler : function (env) { + env.notebook.execute_cell_and_insert_below(); + } + }, + 'go-to-command-mode': { + help : 'command mode', + help_index : 'aa', + handler : function (env) { + env.notebook.command_mode(); + } + }, + 'split-cell-at-cursor': { + help : 'split cell', + help_index : 'ea', + handler : function (env) { + env.notebook.split_cell(); + } + }, + 'enter-edit-mode' : { + help_index : 'aa', + handler : function (env) { + env.notebook.edit_mode(); + } + }, + 'select-previous-cell' : { + help_index : 'da', + handler : function (env) { + var index = env.notebook.get_selected_index(); + if (index !== 0 && index !== null) { + env.notebook.select_prev(); + env.notebook.focus_cell(); + } + } + }, + 'select-next-cell' : { + help_index : 'db', + handler : function (env) { + var index = env.notebook.get_selected_index(); + if (index !== (env.notebook.ncells()-1) && index !== null) { + env.notebook.select_next(); + env.notebook.focus_cell(); + } + } + }, + 'cut-selected-cell' : { + icon: 'fa-cut', + help_index : 'ee', + handler : function (env) { + env.notebook.cut_cell(); + } + }, + 'copy-selected-cell' : { + icon: 'fa-copy', + help_index : 'ef', + handler : function (env) { + env.notebook.copy_cell(); + } + }, + 'paste-cell-before' : { + help_index : 'eg', + handler : function (env) { + env.notebook.paste_cell_above(); + } + }, + 'paste-cell-after' : { + icon: 'fa-paste', + help_index : 'eh', + handler : function (env) { + env.notebook.paste_cell_below(); + } + }, + 'insert-cell-before' : { + help_index : 'ec', + handler : function (env) { + env.notebook.insert_cell_above(); + env.notebook.select_prev(); + env.notebook.focus_cell(); + } + }, + 'insert-cell-after' : { + icon : 'fa-plus', + help_index : 'ed', + handler : function (env) { + env.notebook.insert_cell_below(); + env.notebook.select_next(); + env.notebook.focus_cell(); + } + }, + 'change-selected-cell-to-code-cell' : { + help : 'to code', + help_index : 'ca', + handler : function (env) { + env.notebook.to_code(); + } + }, + 'change-selected-cell-to-markdown-cell' : { + help : 'to markdown', + help_index : 'cb', + handler : function (env) { + env.notebook.to_markdown(); + } + }, + 'change-selected-cell-to-raw-cell' : { + help : 'to raw', + help_index : 'cc', + handler : function (env) { + env.notebook.to_raw(); + } + }, + 'change-selected-cell-to-heading-1' : { + help : 'to heading 1', + help_index : 'cd', + handler : function (env) { + env.notebook.to_heading(undefined, 1); + } + }, + 'change-selected-cell-to-heading-2' : { + help : 'to heading 2', + help_index : 'ce', + handler : function (env) { + env.notebook.to_heading(undefined, 2); + } + }, + 'change-selected-cell-to-heading-3' : { + help : 'to heading 3', + help_index : 'cf', + handler : function (env) { + env.notebook.to_heading(undefined, 3); + } + }, + 'change-selected-cell-to-heading-4' : { + help : 'to heading 4', + help_index : 'cg', + handler : function (env) { + env.notebook.to_heading(undefined, 4); + } + }, + 'change-selected-cell-to-heading-5' : { + help : 'to heading 5', + help_index : 'ch', + handler : function (env) { + env.notebook.to_heading(undefined, 5); + } + }, + 'change-selected-cell-to-heading-6' : { + help : 'to heading 6', + help_index : 'ci', + handler : function (env) { + env.notebook.to_heading(undefined, 6); + } + }, + 'toggle-output-visibility-selected-cell' : { + help : 'toggle output', + help_index : 'gb', + handler : function (env) { + env.notebook.toggle_output(); + } + }, + 'toggle-output-scrolling-selected-cell' : { + help : 'toggle output scrolling', + help_index : 'gc', + handler : function (env) { + env.notebook.toggle_output_scroll(); + } + }, + 'move-selected-cell-down' : { + icon: 'fa-arrow-down', + help_index : 'eb', + handler : function (env) { + env.notebook.move_cell_down(); + } + }, + 'move-selected-cell-up' : { + icon: 'fa-arrow-up', + help_index : 'ea', + handler : function (env) { + env.notebook.move_cell_up(); + } + }, + 'toggle-line-number-selected-cell' : { + help : 'toggle line numbers', + help_index : 'ga', + handler : function (env) { + env.notebook.cell_toggle_line_numbers(); + } + }, + 'show-keyboard-shortcut-help-dialog' : { + help_index : 'ge', + handler : function (env) { + env.quick_help.show_keyboard_shortcuts(); + } + }, + 'delete-cell': { + help_index : 'ej', + handler : function (env) { + env.notebook.delete_cell(); + } + }, + 'interrupt-kernel':{ + icon: 'fa-stop', + help_index : 'ha', + handler : function (env) { + env.notebook.kernel.interrupt(); + } + }, + 'restart-kernel':{ + icon: 'fa-repeat', + help_index : 'hb', + handler : function (env) { + env.notebook.restart_kernel(); + } + }, + 'undo-last-cell-deletion' : { + help_index : 'ei', + handler : function (env) { + env.notebook.undelete_cell(); + } + }, + 'merge-selected-cell-with-cell-after' : { + help : 'merge cell below', + help_index : 'ek', + handler : function (env) { + env.notebook.merge_cell_below(); + } + }, + 'close-pager' : { + help_index : 'gd', + handler : function (env) { + env.pager.collapse(); + } + } + + }; + + /** + * A bunch of `Advance actions` for IPython. + * Cf `Simple Action` plus the following properties. + * + * handler: first argument of the handler is the event that triggerd the action + * (typically keypress). The handler is responsible for any modification of the + * event and event propagation. + * Is also responsible for returning false if the event have to be further ignored, + * true, to tell keyboard manager that it ignored the event. + * + * the second parameter of the handler is the environemnt passed to Simple Actions + * + **/ + var custom_ignore = { + 'ignore':{ + handler : function () { + return true; + } + }, + 'move-cursor-up-or-previous-cell':{ + handler : function (env, event) { + var index = env.notebook.get_selected_index(); + var cell = env.notebook.get_cell(index); + var cm = env.notebook.get_selected_cell().code_mirror; + var cur = cm.getCursor(); + if (cell && cell.at_top() && index !== 0 && cur.ch === 0) { + if(event){ + event.preventDefault(); + } + env.notebook.command_mode(); + env.notebook.select_prev(); + env.notebook.edit_mode(); + cm = env.notebook.get_selected_cell().code_mirror; + cm.setCursor(cm.lastLine(), 0); + } + return false; + } + }, + 'move-cursor-down-or-next-cell':{ + handler : function (env, event) { + var index = env.notebook.get_selected_index(); + var cell = env.notebook.get_cell(index); + if (cell.at_bottom() && index !== (env.notebook.ncells()-1)) { + if(event){ + event.preventDefault(); + } + env.notebook.command_mode(); + env.notebook.select_next(); + env.notebook.edit_mode(); + var cm = env.notebook.get_selected_cell().code_mirror; + cm.setCursor(0, 0); + } + return false; + } + }, + 'scroll-down': { + handler: function(env, event) { + if(event){ + event.preventDefault(); + } + return env.notebook.scroll_manager.scroll(1); + }, + }, + 'scroll-up': { + handler: function(env, event) { + if(event){ + event.preventDefault(); + } + return env.notebook.scroll_manager.scroll(-1); + }, + }, + 'save-notebook':{ + help: "Save and Checkpoint", + help_index : 'fb', + icon: 'fa-save', + handler : function (env, event) { + env.notebook.save_checkpoint(); + if(event){ + event.preventDefault(); + } + return false; + } + }, + }; + + // private stuff that prepend `.ipython` to actions names + // and uniformize/fill in missing pieces in of an action. + var _prepare_handler = function(registry, subkey, source){ + registry['ipython.'+subkey] = {}; + registry['ipython.'+subkey].help = source[subkey].help||subkey.replace(/-/g,' '); + registry['ipython.'+subkey].help_index = source[subkey].help_index; + registry['ipython.'+subkey].icon = source[subkey].icon; + return source[subkey].handler; + }; + + // Will actually generate/register all the IPython actions + var fun = function(){ + var final_actions = {}; + for(var k in _action){ + // Js closure are function level not block level need to wrap in a IIFE + // and append ipython to event name these things do intercept event so are wrapped + // in a function that return false. + var handler = _prepare_handler(final_actions, k, _action); + (function(key, handler){ + final_actions['ipython.'+key].handler = function(env, event){ + handler(env); + if(event){ + event.preventDefault(); + } + return false; + }; + })(k, handler); + } + + for(var k in custom_ignore){ + // Js closure are function level not block level need to wrap in a IIFE + // same as above, but decide for themselves wether or not they intercept events. + var handler = _prepare_handler(final_actions, k, custom_ignore); + (function(key, handler){ + final_actions['ipython.'+key].handler = function(env, event){ + return handler(env, event); + }; + })(k, handler); + } + + return final_actions; + }; + ActionHandler.prototype._actions = fun(); + + + /** + * extend the environment variable that will be pass to handlers + **/ + ActionHandler.prototype.extend_env = function(env){ + for(var k in env){ + this.env[k] = env[k]; + } + }; + + ActionHandler.prototype.register = function(action, name, prefix){ + /** + * Register an `action` with an optional name and prefix. + * + * if name and prefix are not given they will be determined automatically. + * if action if just a `function` it will be wrapped in an anonymous action. + * + * @return the full name to access this action . + **/ + action = this.normalise(action); + if( !name ){ + name = 'autogenerated-'+String(action.handler); + } + prefix = prefix || 'auto'; + var full_name = prefix+'.'+name; + this._actions[full_name] = action; + return full_name; + + }; + + + ActionHandler.prototype.normalise = function(data){ + /** + * given an `action` or `function`, return a normalised `action` + * by setting all known attributes and removing unknown attributes; + **/ + if(typeof(data) === 'function'){ + data = {handler:data}; + } + if(typeof(data.handler) !== 'function'){ + throw('unknown datatype, cannot register'); + } + var _data = data; + data = {}; + data.handler = _data.handler; + data.help = data.help || ''; + data.icon = data.icon || ''; + data.help_index = data.help_index || ''; + return data; + }; + + ActionHandler.prototype.get_name = function(name_or_data){ + /** + * given an `action` or `name` of a action, return the name attached to this action. + * if given the name of and corresponding actions does not exist in registry, return `null`. + **/ + + if(typeof(name_or_data) === 'string'){ + if(this.exists(name_or_data)){ + return name_or_data; + } else { + return null; + } + } else { + return this.register(name_or_data); + } + }; + + ActionHandler.prototype.get = function(name){ + return this._actions[name]; + }; + + ActionHandler.prototype.call = function(name, event, env){ + return this._actions[name].handler(env|| this.env, event); + }; + + ActionHandler.prototype.exists = function(name){ + return (typeof(this._actions[name]) !== 'undefined'); + }; + + return {init:ActionHandler}; + +}); diff --git a/IPython/html/static/notebook/js/cell.js b/IPython/html/static/notebook/js/cell.js index e695b3e..b122c2a 100644 --- a/IPython/html/static/notebook/js/cell.js +++ b/IPython/html/static/notebook/js/cell.js @@ -1,44 +1,39 @@ // Copyright (c) IPython Development Team. // Distributed under the terms of the Modified BSD License. +/** + * + * + * @module cell + * @namespace cell + * @class Cell + */ + + define([ 'base/js/namespace', 'jquery', 'base/js/utils', -], function(IPython, $, utils) { + 'codemirror/lib/codemirror', + 'codemirror/addon/edit/matchbrackets', + 'codemirror/addon/edit/closebrackets', + 'codemirror/addon/comment/comment' +], function(IPython, $, utils, CodeMirror, cm_match, cm_closeb, cm_comment) { // TODO: remove IPython dependency here "use strict"; - // monkey patch CM to be able to syntax highlight cell magics - // bug reported upstream, - // see https://github.com/codemirror/CodeMirror/issues/670 - if(CodeMirror.getMode(1,'text/plain').indent === undefined ){ - CodeMirror.modes.null = function() { - return {token: function(stream) {stream.skipToEnd();},indent : function(){return 0;}}; - }; - } - - CodeMirror.patchedGetMode = function(config, mode){ - var cmmode = CodeMirror.getMode(config, mode); - if(cmmode.indent === null) { - console.log('patch mode "' , mode, '" on the fly'); - cmmode.indent = function(){return 0;}; - } - return cmmode; - }; - // end monkey patching CodeMirror - var Cell = function (options) { - // Constructor - // - // The Base `Cell` class from which to inherit. - // - // Parameters: - // options: dictionary - // Dictionary of keyword arguments. - // events: $(Events) instance - // config: dictionary - // keyboard_manager: KeyboardManager instance + /* Constructor + * + * The Base `Cell` class from which to inherit. + * @constructor + * @param: + * options: dictionary + * Dictionary of keyword arguments. + * events: $(Events) instance + * config: dictionary + * keyboard_manager: KeyboardManager instance + */ options = options || {}; this.keyboard_manager = options.keyboard_manager; this.events = options.events; @@ -50,7 +45,20 @@ define([ this.selected = false; this.rendered = false; this.mode = 'command'; - this.metadata = {}; + + // Metadata property + var that = this; + this._metadata = {}; + Object.defineProperty(this, 'metadata', { + get: function() { return that._metadata; }, + set: function(value) { + that._metadata = value; + if (that.celltoolbar) { + that.celltoolbar.rebuild(); + } + } + }); + // load this from metadata later ? this.user_highlight = 'auto'; this.cm_config = config.cm_config; @@ -104,8 +112,10 @@ define([ }; Cell.prototype.init_classes = function () { - // Call after this.element exists to initialize the css classes - // related to selected, rendered and mode. + /** + * Call after this.element exists to initialize the css classes + * related to selected, rendered and mode. + */ if (this.selected) { this.element.addClass('selected'); } else { @@ -157,6 +167,16 @@ define([ that.events.trigger('command_mode.Cell', {cell: that}); }); } + + this.element.dblclick(function () { + if (that.selected === false) { + this.events.trigger('select.Cell', {'cell':that}); + } + var cont = that.unrender(); + if (cont) { + that.focus_editor(); + } + }); }; /** @@ -174,9 +194,22 @@ define([ Cell.prototype.handle_codemirror_keyevent = function (editor, event) { var shortcuts = this.keyboard_manager.edit_shortcuts; + var cur = editor.getCursor(); + if((cur.line !== 0 || cur.ch !==0) && event.keyCode === 38){ + event._ipkmIgnore = true; + } + var nLastLine = editor.lastLine(); + if ((event.keyCode === 40) && + ((cur.line !== nLastLine) || + (cur.ch !== editor.getLineHandle(nLastLine).text.length)) + ) { + event._ipkmIgnore = true; + } // if this is an edit_shortcuts shortcut, the global keyboard/shortcut // manager will handle it - if (shortcuts.handles(event)) { return true; } + if (shortcuts.handles(event)) { + return true; + } return false; }; @@ -226,6 +259,14 @@ define([ }; /** + * should be overritten by subclass + * @method execute + */ + Cell.prototype.execute = function () { + return; + }; + + /** * handle cell level logic when a cell is rendered * @method render * @return is the action being taken @@ -267,9 +308,6 @@ define([ * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise */ Cell.prototype.handle_keyevent = function (editor, event) { - - // console.log('CM', this.mode, event.which, event.type) - if (this.mode === 'command') { return true; } else if (this.mode === 'edit') { @@ -360,7 +398,9 @@ define([ * @method refresh */ Cell.prototype.refresh = function () { - this.code_mirror.refresh(); + if (this.code_mirror) { + this.code_mirror.refresh(); + } }; /** @@ -385,12 +425,12 @@ define([ **/ Cell.prototype.toJSON = function () { var data = {}; - data.metadata = this.metadata; + // deepcopy the metadata so copied cells don't share the same object + data.metadata = JSON.parse(JSON.stringify(this.metadata)); data.cell_type = this.cell_type; return data; }; - /** * should be overritten by subclass * @method fromJSON @@ -399,27 +439,39 @@ define([ if (data.metadata !== undefined) { this.metadata = data.metadata; } - this.celltoolbar.rebuild(); }; /** - * can the cell be split into two cells + * can the cell be split into two cells (false if not deletable) * @method is_splittable **/ Cell.prototype.is_splittable = function () { - return true; + return this.is_deletable(); }; /** - * can the cell be merged with other cells + * can the cell be merged with other cells (false if not deletable) * @method is_mergeable **/ Cell.prototype.is_mergeable = function () { - return true; + return this.is_deletable(); }; + /** + * is the cell deletable? only false (undeletable) if + * metadata.deletable is explicitly false -- everything else + * counts as true + * + * @method is_deletable + **/ + Cell.prototype.is_deletable = function () { + if (this.metadata.deletable === false) { + return false; + } + return true; + }; /** * @return {String} - the text before the cursor @@ -484,7 +536,10 @@ define([ * @param {String|object|undefined} - CodeMirror mode | 'auto' **/ Cell.prototype._auto_highlight = function (modes) { - //Here we handle manually selected modes + /** + *Here we handle manually selected modes + */ + var that = this; var mode; if( this.user_highlight !== undefined && this.user_highlight != 'auto' ) { @@ -506,33 +561,34 @@ define([ return; } if (mode.search('magic_') !== 0) { - this.code_mirror.setOption('mode', mode); - CodeMirror.autoLoadMode(this.code_mirror, mode); + utils.requireCodeMirrorMode(mode, function () { + that.code_mirror.setOption('mode', mode); + }); return; } var open = modes[mode].open || "%%"; var close = modes[mode].close || "%%end"; - var mmode = mode; - mode = mmode.substr(6); - if(current_mode == mode){ + var magic_mode = mode; + mode = magic_mode.substr(6); + if(current_mode == magic_mode){ return; } - CodeMirror.autoLoadMode(this.code_mirror, mode); - // create on the fly a mode that swhitch between - // plain/text and smth else otherwise `%%` is - // source of some highlight issues. - // we use patchedGetMode to circumvent a bug in CM - CodeMirror.defineMode(mmode , function(config) { - return CodeMirror.multiplexingMode( - CodeMirror.patchedGetMode(config, 'text/plain'), - // always set someting on close - {open: open, close: close, - mode: CodeMirror.patchedGetMode(config, mode), - delimStyle: "delimit" - } - ); + utils.requireCodeMirrorMode(mode, function () { + // create on the fly a mode that switch between + // plain/text and something else, otherwise `%%` is + // source of some highlight issues. + CodeMirror.defineMode(magic_mode, function(config) { + return CodeMirror.multiplexingMode( + CodeMirror.getMode(config, 'text/plain'), + // always set something on close + {open: open, close: close, + mode: CodeMirror.getMode(config, mode), + delimStyle: "delimit" + } + ); + }); + that.code_mirror.setOption('mode', magic_mode); }); - this.code_mirror.setOption('mode', mmode); return; } } @@ -550,8 +606,76 @@ define([ this.code_mirror.setOption('mode', default_mode); }; + var UnrecognizedCell = function (options) { + /** Constructor for unrecognized cells */ + Cell.apply(this, arguments); + this.cell_type = 'unrecognized'; + this.celltoolbar = null; + this.data = {}; + + Object.seal(this); + }; + + UnrecognizedCell.prototype = Object.create(Cell.prototype); + + + // cannot merge or split unrecognized cells + UnrecognizedCell.prototype.is_mergeable = function () { + return false; + }; + + UnrecognizedCell.prototype.is_splittable = function () { + return false; + }; + + UnrecognizedCell.prototype.toJSON = function () { + /** + * deepcopy the metadata so copied cells don't share the same object + */ + return JSON.parse(JSON.stringify(this.data)); + }; + + UnrecognizedCell.prototype.fromJSON = function (data) { + this.data = data; + if (data.metadata !== undefined) { + this.metadata = data.metadata; + } else { + data.metadata = this.metadata; + } + this.element.find('.inner_cell').find("a").text("Unrecognized cell type: " + data.cell_type); + }; + + UnrecognizedCell.prototype.create_element = function () { + Cell.prototype.create_element.apply(this, arguments); + var cell = this.element = $("

").addClass('cell unrecognized_cell'); + cell.attr('tabindex','2'); + + var prompt = $('
').addClass('prompt input_prompt'); + cell.append(prompt); + var inner_cell = $('
').addClass('inner_cell'); + inner_cell.append( + $("") + .attr("href", "#") + .text("Unrecognized cell type") + ); + cell.append(inner_cell); + this.element = cell; + }; + + UnrecognizedCell.prototype.bind_events = function () { + Cell.prototype.bind_events.apply(this, arguments); + var cell = this; + + this.element.find('.inner_cell').find("a").click(function () { + cell.events.trigger('unrecognized_cell.Cell', {cell: cell}) + }); + }; + // Backwards compatibility. IPython.Cell = Cell; - return {'Cell': Cell}; + return { + Cell: Cell, + UnrecognizedCell: UnrecognizedCell + }; }); diff --git a/IPython/html/static/notebook/js/celltoolbar.js b/IPython/html/static/notebook/js/celltoolbar.js index 887eba3..03910b2 100644 --- a/IPython/html/static/notebook/js/celltoolbar.js +++ b/IPython/html/static/notebook/js/celltoolbar.js @@ -9,17 +9,19 @@ define([ "use strict"; var CellToolbar = function (options) { - // Constructor - // - // Parameters: - // options: dictionary - // Dictionary of keyword arguments. - // events: $(Events) instance - // cell: Cell instance - // notebook: Notebook instance - // - // TODO: This leaks, when cell are deleted - // There is still a reference to each celltoolbars. + /** + * Constructor + * + * Parameters: + * options: dictionary + * Dictionary of keyword arguments. + * events: $(Events) instance + * cell: Cell instance + * notebook: Notebook instance + * + * TODO: This leaks, when cell are deleted + * There is still a reference to each celltoolbars. + */ CellToolbar._instances.push(this); this.notebook = options.notebook; this.cell = options.cell; @@ -114,7 +116,7 @@ define([ * @param name {String} name to use to refer to the callback. It is advised to use a prefix with the name * for easier sorting and avoid collision * @param callback {function(div, cell)} callback that will be called to generate the ui element - * @param [cell_types] {List of String|undefined} optional list of cell types. If present the UI element + * @param [cell_types] {List_of_String|undefined} optional list of cell types. If present the UI element * will be added only to cells of types in the list. * * @@ -163,7 +165,7 @@ define([ * @method register_preset * @param name {String} name to use to refer to the preset. It is advised to use a prefix with the name * for easier sorting and avoid collision - * @param preset_list {List of String} reverse order of the button in the toolbar. Each String of the list + * @param preset_list {List_of_String} reverse order of the button in the toolbar. Each String of the list * should correspond to a name of a registerd callback. * * @private @@ -248,9 +250,11 @@ define([ * @method rebuild */ CellToolbar.prototype.rebuild = function(){ - // strip evrything from the div - // which is probably inner_element - // or this.element. + /** + * strip evrything from the div + * which is probably inner_element + * or this.element. + */ this.inner_element.empty(); this.ui_controls_list = []; @@ -288,8 +292,6 @@ define([ }; - /** - */ CellToolbar.utils = {}; @@ -385,7 +387,7 @@ define([ * @method utils.select_ui_generator * @static * - * @param list_list {list of sublist} List of sublist of metadata value and name in the dropdown list. + * @param list_list {list_of_sublist} List of sublist of metadata value and name in the dropdown list. * subslit shoud contain 2 element each, first a string that woul be displayed in the dropdown list, * and second the corresponding value to be passed to setter/return by getter. the corresponding value * should not be "undefined" or behavior can be unexpected. diff --git a/IPython/html/static/notebook/js/celltoolbarpresets/example.js b/IPython/html/static/notebook/js/celltoolbarpresets/example.js index 5f1a6da..dd1653c 100644 --- a/IPython/html/static/notebook/js/celltoolbarpresets/example.js +++ b/IPython/html/static/notebook/js/celltoolbarpresets/example.js @@ -119,7 +119,9 @@ define([ width: 650, modal: true, close: function() { - //cleanup on close + /** + *cleanup on close + */ $(this).remove(); } }); diff --git a/IPython/html/static/notebook/js/codecell.js b/IPython/html/static/notebook/js/codecell.js index 2703042..497266f 100644 --- a/IPython/html/static/notebook/js/codecell.js +++ b/IPython/html/static/notebook/js/codecell.js @@ -1,5 +1,13 @@ // Copyright (c) IPython Development Team. // Distributed under the terms of the Modified BSD License. +/** + * + * + * @module codecell + * @namespace codecell + * @class CodeCell + */ + define([ 'base/js/namespace', @@ -10,8 +18,12 @@ define([ 'notebook/js/outputarea', 'notebook/js/completer', 'notebook/js/celltoolbar', -], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar) { + 'codemirror/lib/codemirror', + 'codemirror/mode/python/python', + 'notebook/js/codemirror-ipython' +], function(IPython, $, utils, keyboard, cell, outputarea, completer, celltoolbar, CodeMirror, cmpython, cmip) { "use strict"; + var Cell = cell.Cell; /* local util for codemirror */ @@ -41,21 +53,23 @@ define([ var keycodes = keyboard.keycodes; var CodeCell = function (kernel, options) { - // Constructor - // - // A Cell conceived to write code. - // - // Parameters: - // kernel: Kernel instance - // The kernel doesn't have to be set at creation time, in that case - // it will be null and set_kernel has to be called later. - // options: dictionary - // Dictionary of keyword arguments. - // events: $(Events) instance - // config: dictionary - // keyboard_manager: KeyboardManager instance - // notebook: Notebook instance - // tooltip: Tooltip instance + /** + * Constructor + * + * A Cell conceived to write code. + * + * Parameters: + * kernel: Kernel instance + * The kernel doesn't have to be set at creation time, in that case + * it will be null and set_kernel has to be called later. + * options: dictionary + * Dictionary of keyword arguments. + * events: $(Events) instance + * config: dictionary + * keyboard_manager: KeyboardManager instance + * notebook: Notebook instance + * tooltip: Tooltip instance + */ this.kernel = kernel || null; this.notebook = options.notebook; this.collapsed = false; @@ -68,15 +82,28 @@ define([ this.input_prompt_number = null; this.celltoolbar = null; this.output_area = null; + // Keep a stack of the 'active' output areas (where active means the + // output area that recieves output). When a user activates an output + // area, it gets pushed to the stack. Then, when the output area is + // deactivated, it's popped from the stack. When the stack is empty, + // the cell's output area is used. + this.active_output_areas = []; + var that = this; + Object.defineProperty(this, 'active_output_area', { + get: function() { + if (that.active_output_areas && that.active_output_areas.length > 0) { + return that.active_output_areas[that.active_output_areas.length-1]; + } else { + return that.output_area; + } + }, + }); + this.last_msg_id = null; this.completer = null; - var cm_overwrite_options = { - onKeyEvent: $.proxy(this.handle_keyevent,this) - }; - - var config = utils.mergeopt(CodeCell, this.config, {cm_config: cm_overwrite_options}); + var config = utils.mergeopt(CodeCell, this.config); Cell.apply(this,[{ config: config, keyboard_manager: options.keyboard_manager, @@ -84,8 +111,6 @@ define([ // Attributes we want to override in this subclass. this.cell_type = "code"; - - var that = this; this.element.focusout( function() { that.auto_highlight(); } ); @@ -102,15 +127,30 @@ define([ }, mode: 'ipython', theme: 'ipython', - matchBrackets: true, - // don't auto-close strings because of CodeMirror #2385 - autoCloseBrackets: "()[]{}" + matchBrackets: true } }; CodeCell.msg_cells = {}; - CodeCell.prototype = new Cell(); + CodeCell.prototype = Object.create(Cell.prototype); + + /** + * @method push_output_area + */ + CodeCell.prototype.push_output_area = function (output_area) { + this.active_output_areas.push(output_area); + }; + + /** + * @method pop_output_area + */ + CodeCell.prototype.pop_output_area = function (output_area) { + var index = this.active_output_areas.lastIndexOf(output_area); + if (index > -1) { + this.active_output_areas.splice(index, 1); + } + }; /** * @method auto_highlight @@ -135,6 +175,7 @@ define([ inner_cell.append(this.celltoolbar.element); var input_area = $('
').addClass('input_area'); this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config); + this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this)) $(this.code_mirror.getInputField()).attr("spellcheck", "false"); inner_cell.append(input_area); input.append(prompt).append(inner_cell); @@ -187,6 +228,7 @@ define([ * true = ignore, false = don't ignore. * @method handle_codemirror_keyevent */ + CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) { var that = this; @@ -220,10 +262,11 @@ define([ } // If we closed the tooltip, don't let CM or the global handlers // handle this event. - event.stop(); + event.codemirrorIgnore = true; + event.preventDefault(); return true; } else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) { - if (editor.somethingSelected()){ + if (editor.somethingSelected() || editor.getSelections().length !== 1){ var anchor = editor.getCursor("anchor"); var head = editor.getCursor("head"); if( anchor.line != head.line){ @@ -231,12 +274,15 @@ define([ } } this.tooltip.request(that); - event.stop(); + event.codemirrorIgnore = true; + event.preventDefault(); return true; } else if (event.keyCode === keycodes.tab && event.type == 'keydown') { // Tab completion. this.tooltip.remove_and_cancel_tooltip(); - if (editor.somethingSelected()) { + + // completion does not work on multicursor, it might be possible though in some cases + if (editor.somethingSelected() || editor.getSelections().length > 1) { return false; } var pre_cursor = editor.getRange({line:cur.line,ch:0},cur); @@ -245,7 +291,8 @@ define([ // is empty. In this case, let CodeMirror handle indentation. return false; } else { - event.stop(); + event.codemirrorIgnore = true; + event.preventDefault(); this.completer.startCompletion(); return true; } @@ -267,7 +314,12 @@ define([ * @method execute */ CodeCell.prototype.execute = function () { - this.output_area.clear_output(); + if (!this.kernel || !this.kernel.is_connected()) { + console.log("Can't execute, kernel is not connected."); + return; + } + + this.active_output_area.clear_output(); // Clear widget area this.widget_subarea.html(''); @@ -288,6 +340,8 @@ define([ delete CodeCell.msg_cells[old_msg_id]; } CodeCell.msg_cells[this.last_msg_id] = this; + this.render(); + this.events.trigger('execute.CodeCell', {cell: this}); }; /** @@ -295,6 +349,7 @@ define([ * @method get_callbacks */ CodeCell.prototype.get_callbacks = function () { + var that = this; return { shell : { reply : $.proxy(this._handle_execute_reply, this), @@ -304,8 +359,12 @@ define([ } }, iopub : { - output : $.proxy(this.output_area.handle_output, this.output_area), - clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area), + output : function() { + that.active_output_area.handle_output.apply(that.active_output_area, arguments); + }, + clear_output : function() { + that.active_output_area.handle_clear_output.apply(that.active_output_area, arguments); + }, }, input : $.proxy(this._handle_input_request, this) }; @@ -339,7 +398,7 @@ define([ * @private */ CodeCell.prototype._handle_input_request = function (msg) { - this.output_area.append_raw_input(msg); + this.active_output_area.append_raw_input(msg); }; @@ -360,11 +419,6 @@ define([ return cont; }; - CodeCell.prototype.unrender = function () { - // CodeCell is always rendered - return false; - }; - CodeCell.prototype.select_all = function () { var start = {line: 0, ch: 0}; var nlines = this.code_mirror.lineCount(); @@ -375,13 +429,11 @@ define([ CodeCell.prototype.collapse_output = function () { - this.collapsed = true; this.output_area.collapse(); }; CodeCell.prototype.expand_output = function () { - this.collapsed = false; this.output_area.expand(); this.output_area.unscroll_area(); }; @@ -392,7 +444,6 @@ define([ }; CodeCell.prototype.toggle_output = function () { - this.collapsed = Boolean(1 - this.collapsed); this.output_area.toggle_output(); }; @@ -403,7 +454,7 @@ define([ CodeCell.input_prompt_classical = function (prompt_value, lines_number) { var ns; - if (prompt_value === undefined) { + if (prompt_value === undefined || prompt_value === null) { ns = " "; } else { ns = encodeURIComponent(prompt_value); @@ -450,7 +501,7 @@ define([ CodeCell.prototype.clear_output = function (wait) { - this.output_area.clear_output(wait); + this.active_output_area.clear_output(wait); this.set_input_prompt(); }; @@ -460,22 +511,18 @@ define([ CodeCell.prototype.fromJSON = function (data) { Cell.prototype.fromJSON.apply(this, arguments); if (data.cell_type === 'code') { - if (data.input !== undefined) { - this.set_text(data.input); + if (data.source !== undefined) { + this.set_text(data.source); // make this value the starting point, so that we can only undo // to this state, instead of a blank cell this.code_mirror.clearHistory(); this.auto_highlight(); } - if (data.prompt_number !== undefined) { - this.set_input_prompt(data.prompt_number); - } else { - this.set_input_prompt(); - } - this.output_area.trusted = data.trusted || false; + this.set_input_prompt(data.execution_count); + this.output_area.trusted = data.metadata.trusted || false; this.output_area.fromJSON(data.outputs); - if (data.collapsed !== undefined) { - if (data.collapsed) { + if (data.metadata.collapsed !== undefined) { + if (data.metadata.collapsed) { this.collapse_output(); } else { this.expand_output(); @@ -487,16 +534,17 @@ define([ CodeCell.prototype.toJSON = function () { var data = Cell.prototype.toJSON.apply(this); - data.input = this.get_text(); + data.source = this.get_text(); // is finite protect against undefined and '*' value if (isFinite(this.input_prompt_number)) { - data.prompt_number = this.input_prompt_number; + data.execution_count = this.input_prompt_number; + } else { + data.execution_count = null; } var outputs = this.output_area.toJSON(); data.outputs = outputs; - data.language = 'python'; - data.trusted = this.output_area.trusted; - data.collapsed = this.collapsed; + data.metadata.trusted = this.output_area.trusted; + data.metadata.collapsed = this.output_area.collapsed; return data; }; diff --git a/IPython/html/static/notebook/js/codemirror-ipython.js b/IPython/html/static/notebook/js/codemirror-ipython.js index d01abe9..b58229a 100644 --- a/IPython/html/static/notebook/js/codemirror-ipython.js +++ b/IPython/html/static/notebook/js/codemirror-ipython.js @@ -3,7 +3,18 @@ // callback to auto-load python mode, which is more likely not the best things // to do, but at least the simple one for now. -CodeMirror.requireMode('python',function(){ +(function(mod) { + if (typeof exports == "object" && typeof module == "object"){ // CommonJS + mod(require("codemirror/lib/codemirror"), + require("codemirror/mode/python/python") + ); + } else if (typeof define == "function" && define.amd){ // AMD + define(["codemirror/lib/codemirror", + "codemirror/mode/python/python"], mod); + } else {// Plain browser env + mod(CodeMirror); + } +})(function(CodeMirror) { "use strict"; CodeMirror.defineMode("ipython", function(conf, parserConf) { diff --git a/IPython/html/static/notebook/js/codemirror-ipythongfm.js b/IPython/html/static/notebook/js/codemirror-ipythongfm.js index 68b61b3..9a6bbc3 100644 --- a/IPython/html/static/notebook/js/codemirror-ipythongfm.js +++ b/IPython/html/static/notebook/js/codemirror-ipythongfm.js @@ -1,44 +1,62 @@ -// IPython GFM (GitHub Flavored Markdown) mode is just a slightly altered GFM -// Mode with support for latex. +// IPython GFM (GitHub Flavored Markdown) mode is just a slightly altered GFM +// Mode with support for latex. // -// Latex support was supported by Codemirror GFM as of +// Latex support was supported by Codemirror GFM as of // https://github.com/codemirror/CodeMirror/pull/567 // But was later removed in // https://github.com/codemirror/CodeMirror/commit/d9c9f1b1ffe984aee41307f3e927f80d1f23590c -CodeMirror.requireMode('gfm', function(){ - CodeMirror.requireMode('stex', function(){ - CodeMirror.defineMode("ipythongfm", function(config, parserConfig) { - - var gfm_mode = CodeMirror.getMode(config, "gfm"); - var tex_mode = CodeMirror.getMode(config, "stex"); - - return CodeMirror.multiplexingMode( - gfm_mode, - { - open: "$", close: "$", - mode: tex_mode, - delimStyle: "delimit" - }, - { - open: "$$", close: "$$", - mode: tex_mode, - delimStyle: "delimit" - }, - { - open: "\\(", close: "\\)", - mode: tex_mode, - delimStyle: "delimit" - }, - { - open: "\\[", close: "\\]", - mode: tex_mode, - delimStyle: "delimit" - } - // .. more multiplexed styles can follow here - ); - }, 'gfm'); - - CodeMirror.defineMIME("text/x-ipythongfm", "ipythongfm"); - }); -}); + +(function(mod) { + if (typeof exports == "object" && typeof module == "object"){ // CommonJS + mod(require("codemirror/lib/codemirror") + ,require("codemirror/addon/mode/multiplex") + ,require("codemirror/mode/gfm/gfm") + ,require("codemirror/mode/stex/stex") + ); + } else if (typeof define == "function" && define.amd){ // AMD + define(["codemirror/lib/codemirror" + ,"codemirror/addon/mode/multiplex" + ,"codemirror/mode/python/python" + ,"codemirror/mode/stex/stex" + ], mod); + } else {// Plain browser env + mod(CodeMirror); + } +})( function(CodeMirror){ + "use strict"; + + CodeMirror.defineMode("ipythongfm", function(config, parserConfig) { + + var gfm_mode = CodeMirror.getMode(config, "gfm"); + var tex_mode = CodeMirror.getMode(config, "stex"); + + return CodeMirror.multiplexingMode( + gfm_mode, + { + open: "$", close: "$", + mode: tex_mode, + delimStyle: "delimit" + }, + { + // not sure this works as $$ is interpreted at (opening $, closing $, as defined just above) + open: "$$", close: "$$", + mode: tex_mode, + delimStyle: "delimit" + }, + { + open: "\\(", close: "\\)", + mode: tex_mode, + delimStyle: "delimit" + }, + { + open: "\\[", close: "\\]", + mode: tex_mode, + delimStyle: "delimit" + } + // .. more multiplexed styles can follow here + ); + }, 'gfm'); + + CodeMirror.defineMIME("text/x-ipythongfm", "ipythongfm"); +}) diff --git a/IPython/html/static/notebook/js/completer.js b/IPython/html/static/notebook/js/completer.js index ec72878..4a929d3 100644 --- a/IPython/html/static/notebook/js/completer.js +++ b/IPython/html/static/notebook/js/completer.js @@ -7,7 +7,8 @@ define([ 'base/js/utils', 'base/js/keyboard', 'notebook/js/contexthint', -], function(IPython, $, utils, keyboard) { + 'codemirror/lib/codemirror', +], function(IPython, $, utils, keyboard, CodeMirror) { "use strict"; // easier key mapping @@ -82,18 +83,20 @@ define([ this.cell = cell; this.editor = cell.code_mirror; var that = this; - events.on('status_busy.Kernel', function () { + events.on('kernel_busy.Kernel', function () { that.skip_kernel_completion = true; }); - events.on('status_idle.Kernel', function () { + events.on('kernel_idle.Kernel', function () { that.skip_kernel_completion = false; }); }; Completer.prototype.startCompletion = function () { - // call for a 'first' completion, that will set the editor and do some - // special behavior like autopicking if only one completion available. - if (this.editor.somethingSelected()) return; + /** + * call for a 'first' completion, that will set the editor and do some + * special behavior like autopicking if only one completion available. + */ + if (this.editor.somethingSelected()|| this.editor.getSelections().length > 1) return; this.done = false; // use to get focus back on opera this.carry_on_completion(true); @@ -118,9 +121,11 @@ define([ * shared start **/ Completer.prototype.carry_on_completion = function (first_invocation) { - // Pass true as parameter if you want the completer to autopick when - // only one completion. This function is automatically reinvoked at - // each keystroke with first_invocation = false + /** + * Pass true as parameter if you want the completer to autopick when + * only one completion. This function is automatically reinvoked at + * each keystroke with first_invocation = false + */ var cur = this.editor.getCursor(); var line = this.editor.getLine(cur.line); var pre_cursor = this.editor.getRange({ @@ -142,7 +147,7 @@ define([ } // We want a single cursor position. - if (this.editor.somethingSelected()) { + if (this.editor.somethingSelected()|| this.editor.getSelections().length > 1) { return; } @@ -163,8 +168,10 @@ define([ }; Completer.prototype.finish_completing = function (msg) { - // let's build a function that wrap all that stuff into what is needed - // for the new completer: + /** + * let's build a function that wrap all that stuff into what is needed + * for the new completer: + */ var content = msg.content; var start = content.cursor_start; var end = content.cursor_end; @@ -316,11 +323,15 @@ define([ // Enter if (code == keycodes.enter) { - CodeMirror.e_stop(event); + event.codemirrorIgnore = true; + event._ipkmIgnore = true; + event.preventDefault(); this.pick(); // Escape or backspace } else if (code == keycodes.esc || code == keycodes.backspace) { - CodeMirror.e_stop(event); + event.codemirrorIgnore = true; + event._ipkmIgnore = true; + event.preventDefault(); this.close(); } else if (code == keycodes.tab) { //all the fastforwarding operation, @@ -339,7 +350,9 @@ define([ } else if (code == keycodes.up || code == keycodes.down) { // need to do that to be able to move the arrow // when on the first or last line ofo a code cell - CodeMirror.e_stop(event); + event.codemirrorIgnore = true; + event._ipkmIgnore = true; + event.preventDefault(); var options = this.sel.find('option'); var index = this.sel[0].selectedIndex; @@ -352,7 +365,7 @@ define([ index = Math.min(Math.max(index, 0), options.length-1); this.sel[0].selectedIndex = index; } else if (code == keycodes.pageup || code == keycodes.pagedown) { - CodeMirror.e_stop(event); + event._ipkmIgnore = true; var options = this.sel.find('option'); var index = this.sel[0].selectedIndex; @@ -369,11 +382,13 @@ define([ }; Completer.prototype.keypress = function (event) { - // FIXME: This is a band-aid. - // on keypress, trigger insertion of a single character. - // This simulates the old behavior of completion as you type, - // before events were disconnected and CodeMirror stopped - // receiving events while the completer is focused. + /** + * FIXME: This is a band-aid. + * on keypress, trigger insertion of a single character. + * This simulates the old behavior of completion as you type, + * before events were disconnected and CodeMirror stopped + * receiving events while the completer is focused. + */ var that = this; var code = event.keyCode; diff --git a/IPython/html/static/notebook/js/config.js b/IPython/html/static/notebook/js/config.js index 8db7d18..1c60b6f 100644 --- a/IPython/html/static/notebook/js/config.js +++ b/IPython/html/static/notebook/js/config.js @@ -1,6 +1,15 @@ // Copyright (c) IPython Development Team. // Distributed under the terms of the Modified BSD License. +/** + * + * + * @module config + * @namespace config + * @class Config + */ + + define([], function() { "use strict"; diff --git a/IPython/html/static/notebook/js/contexthint.js b/IPython/html/static/notebook/js/contexthint.js index c64b67b..30b7cd7 100644 --- a/IPython/html/static/notebook/js/contexthint.js +++ b/IPython/html/static/notebook/js/contexthint.js @@ -2,7 +2,7 @@ // Distributed under the terms of the Modified BSD License. // highly adapted for codemiror jshint -define([], function() { +define(['codemirror/lib/codemirror'], function(CodeMirror) { "use strict"; var forEach = function(arr, f) { diff --git a/IPython/html/static/notebook/js/kernelselector.js b/IPython/html/static/notebook/js/kernelselector.js index ce9b919..00da819 100644 --- a/IPython/html/static/notebook/js/kernelselector.js +++ b/IPython/html/static/notebook/js/kernelselector.js @@ -12,7 +12,7 @@ define([ this.selector = selector; this.notebook = notebook; this.events = notebook.events; - this.current_selection = notebook.default_kernel_name; + this.current_selection = null; this.kernelspecs = {}; if (this.selector !== undefined) { this.element = $(selector); @@ -76,12 +76,12 @@ define([ that.element.find("#current_kernel_spec").find('.kernel_name').text(data.display_name); }); - this.events.on('started.Session', function(events, session) { - if (session.kernel_name !== that.current_selection) { + this.events.on('kernel_created.Session', function(event, data) { + if (data.kernel.name !== that.current_selection) { // If we created a 'python' session, we only know if it's Python // 3 or 2 on the server's reply, so we fire the event again to // set things up. - var ks = that.kernelspecs[session.kernel_name]; + var ks = that.kernelspecs[data.kernel.name]; that.events.trigger('spec_changed.Kernel', ks); } }); diff --git a/IPython/html/static/notebook/js/keyboardmanager.js b/IPython/html/static/notebook/js/keyboardmanager.js index 3ff69ba..4c6323f 100644 --- a/IPython/html/static/notebook/js/keyboardmanager.js +++ b/IPython/html/static/notebook/js/keyboardmanager.js @@ -1,5 +1,12 @@ // Copyright (c) IPython Development Team. // Distributed under the terms of the Modified BSD License. +/** + * + * + * @module keyboardmanager + * @namespace keyboardmanager + * @class KeyboardManager + */ define([ 'base/js/namespace', @@ -9,491 +16,138 @@ define([ ], function(IPython, $, utils, keyboard) { "use strict"; - var browser = utils.browser[0]; - var platform = utils.platform; - // Main keyboard manager for the notebook var keycodes = keyboard.keycodes; var KeyboardManager = function (options) { - // Constructor - // - // Parameters: - // options: dictionary - // Dictionary of keyword arguments. - // events: $(Events) instance - // pager: Pager instance + /** + * A class to deal with keyboard event and shortcut + * + * @class KeyboardManager + * @constructor + * @param options {dict} Dictionary of keyword arguments : + * @param options.events {$(Events)} instance + * @param options.pager: {Pager} pager instance + */ this.mode = 'command'; this.enabled = true; this.pager = options.pager; this.quick_help = undefined; this.notebook = undefined; + this.last_mode = undefined; this.bind_events(); - this.command_shortcuts = new keyboard.ShortcutManager(undefined, options.events); + this.env = {pager:this.pager}; + this.actions = options.actions; + this.command_shortcuts = new keyboard.ShortcutManager(undefined, options.events, this.actions, this.env ); this.command_shortcuts.add_shortcuts(this.get_default_common_shortcuts()); this.command_shortcuts.add_shortcuts(this.get_default_command_shortcuts()); - this.edit_shortcuts = new keyboard.ShortcutManager(undefined, options.events); + this.edit_shortcuts = new keyboard.ShortcutManager(undefined, options.events, this.actions, this.env); this.edit_shortcuts.add_shortcuts(this.get_default_common_shortcuts()); this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts()); + Object.seal(this); }; + + + + /** + * Return a dict of common shortcut + * @method get_default_common_shortcuts + * + * @example Example of returned shortcut + * ``` + * 'shortcut-key': 'action-name' + * // a string representing the shortcut as dash separated value. + * // e.g. 'shift' , 'shift-enter', 'cmd-t' + *``` + */ KeyboardManager.prototype.get_default_common_shortcuts = function() { - var that = this; - var shortcuts = { - 'shift' : { - help : '', - help_index : '', - handler : function (event) { - // ignore shift keydown - return true; - } - }, - 'shift-enter' : { - help : 'run cell, select below', - help_index : 'ba', - handler : function (event) { - that.notebook.execute_cell_and_select_below(); - return false; - } - }, - 'ctrl-enter' : { - help : 'run cell', - help_index : 'bb', - handler : function (event) { - that.notebook.execute_cell(); - return false; - } - }, - 'alt-enter' : { - help : 'run cell, insert below', - help_index : 'bc', - handler : function (event) { - that.notebook.execute_cell_and_insert_below(); - return false; - } - } + return { + 'shift' : 'ipython.ignore', + 'shift-enter' : 'ipython.run-select-next', + 'ctrl-enter' : 'ipython.execute-in-place', + 'alt-enter' : 'ipython.execute-and-insert-after', + // cmd on mac, ctrl otherwise + 'cmdtrl-s' : 'ipython.save-notebook', }; - - if (platform === 'MacOS') { - shortcuts['cmd-s'] = - { - help : 'save notebook', - help_index : 'fb', - handler : function (event) { - that.notebook.save_checkpoint(); - event.preventDefault(); - return false; - } - }; - } else { - shortcuts['ctrl-s'] = - { - help : 'save notebook', - help_index : 'fb', - handler : function (event) { - that.notebook.save_checkpoint(); - event.preventDefault(); - return false; - } - }; - } - return shortcuts; }; KeyboardManager.prototype.get_default_edit_shortcuts = function() { - var that = this; return { - 'esc' : { - help : 'command mode', - help_index : 'aa', - handler : function (event) { - that.notebook.command_mode(); - return false; - } - }, - 'ctrl-m' : { - help : 'command mode', - help_index : 'ab', - handler : function (event) { - that.notebook.command_mode(); - return false; - } - }, - 'up' : { - help : '', - help_index : '', - handler : function (event) { - var index = that.notebook.get_selected_index(); - var cell = that.notebook.get_cell(index); - if (cell && cell.at_top() && index !== 0) { - event.preventDefault(); - that.notebook.command_mode(); - that.notebook.select_prev(); - that.notebook.edit_mode(); - var cm = that.notebook.get_selected_cell().code_mirror; - cm.setCursor(cm.lastLine(), 0); - return false; - } else if (cell) { - var cm = cell.code_mirror; - cm.execCommand('goLineUp'); - return false; - } - } - }, - 'down' : { - help : '', - help_index : '', - handler : function (event) { - var index = that.notebook.get_selected_index(); - var cell = that.notebook.get_cell(index); - if (cell.at_bottom() && index !== (that.notebook.ncells()-1)) { - event.preventDefault(); - that.notebook.command_mode(); - that.notebook.select_next(); - that.notebook.edit_mode(); - var cm = that.notebook.get_selected_cell().code_mirror; - cm.setCursor(0, 0); - return false; - } else { - var cm = cell.code_mirror; - cm.execCommand('goLineDown'); - return false; - } - } - }, - 'ctrl-shift--' : { - help : 'split cell', - help_index : 'ea', - handler : function (event) { - that.notebook.split_cell(); - return false; - } - }, - 'ctrl-shift-subtract' : { - help : '', - help_index : 'eb', - handler : function (event) { - that.notebook.split_cell(); - return false; - } - }, + 'esc' : 'ipython.go-to-command-mode', + 'ctrl-m' : 'ipython.go-to-command-mode', + 'up' : 'ipython.move-cursor-up-or-previous-cell', + 'down' : 'ipython.move-cursor-down-or-next-cell', + 'ctrl-shift--' : 'ipython.split-cell-at-cursor', + 'ctrl-shift-subtract' : 'ipython.split-cell-at-cursor' }; }; KeyboardManager.prototype.get_default_command_shortcuts = function() { - var that = this; return { - 'space': { - help: "Scroll down", - handler: function(event) { - return that.notebook.scroll_manager.scroll(1); - }, - }, - 'shift-space': { - help: "Scroll up", - handler: function(event) { - return that.notebook.scroll_manager.scroll(-1); - }, - }, - 'enter' : { - help : 'edit mode', - help_index : 'aa', - handler : function (event) { - that.notebook.edit_mode(); - return false; - } - }, - 'up' : { - help : 'select previous cell', - help_index : 'da', - handler : function (event) { - var index = that.notebook.get_selected_index(); - if (index !== 0 && index !== null) { - that.notebook.select_prev(); - that.notebook.focus_cell(); - } - return false; - } - }, - 'down' : { - help : 'select next cell', - help_index : 'db', - handler : function (event) { - var index = that.notebook.get_selected_index(); - if (index !== (that.notebook.ncells()-1) && index !== null) { - that.notebook.select_next(); - that.notebook.focus_cell(); - } - return false; - } - }, - 'k' : { - help : 'select previous cell', - help_index : 'dc', - handler : function (event) { - var index = that.notebook.get_selected_index(); - if (index !== 0 && index !== null) { - that.notebook.select_prev(); - that.notebook.focus_cell(); - } - return false; - } - }, - 'j' : { - help : 'select next cell', - help_index : 'dd', - handler : function (event) { - var index = that.notebook.get_selected_index(); - if (index !== (that.notebook.ncells()-1) && index !== null) { - that.notebook.select_next(); - that.notebook.focus_cell(); - } - return false; - } - }, - 'x' : { - help : 'cut cell', - help_index : 'ee', - handler : function (event) { - that.notebook.cut_cell(); - return false; - } - }, - 'c' : { - help : 'copy cell', - help_index : 'ef', - handler : function (event) { - that.notebook.copy_cell(); - return false; - } - }, - 'shift-v' : { - help : 'paste cell above', - help_index : 'eg', - handler : function (event) { - that.notebook.paste_cell_above(); - return false; - } - }, - 'v' : { - help : 'paste cell below', - help_index : 'eh', - handler : function (event) { - that.notebook.paste_cell_below(); - return false; - } - }, - 'd' : { - help : 'delete cell (press twice)', - help_index : 'ej', - count: 2, - handler : function (event) { - that.notebook.delete_cell(); - return false; - } - }, - 'a' : { - help : 'insert cell above', - help_index : 'ec', - handler : function (event) { - that.notebook.insert_cell_above(); - that.notebook.select_prev(); - that.notebook.focus_cell(); - return false; - } - }, - 'b' : { - help : 'insert cell below', - help_index : 'ed', - handler : function (event) { - that.notebook.insert_cell_below(); - that.notebook.select_next(); - that.notebook.focus_cell(); - return false; - } - }, - 'y' : { - help : 'to code', - help_index : 'ca', - handler : function (event) { - that.notebook.to_code(); - return false; - } - }, - 'm' : { - help : 'to markdown', - help_index : 'cb', - handler : function (event) { - that.notebook.to_markdown(); - return false; - } - }, - 'r' : { - help : 'to raw', - help_index : 'cc', - handler : function (event) { - that.notebook.to_raw(); - return false; - } - }, - '1' : { - help : 'to heading 1', - help_index : 'cd', - handler : function (event) { - that.notebook.to_heading(undefined, 1); - return false; - } - }, - '2' : { - help : 'to heading 2', - help_index : 'ce', - handler : function (event) { - that.notebook.to_heading(undefined, 2); - return false; - } - }, - '3' : { - help : 'to heading 3', - help_index : 'cf', - handler : function (event) { - that.notebook.to_heading(undefined, 3); - return false; - } - }, - '4' : { - help : 'to heading 4', - help_index : 'cg', - handler : function (event) { - that.notebook.to_heading(undefined, 4); - return false; - } - }, - '5' : { - help : 'to heading 5', - help_index : 'ch', - handler : function (event) { - that.notebook.to_heading(undefined, 5); - return false; - } - }, - '6' : { - help : 'to heading 6', - help_index : 'ci', - handler : function (event) { - that.notebook.to_heading(undefined, 6); - return false; - } - }, - 'o' : { - help : 'toggle output', - help_index : 'gb', - handler : function (event) { - that.notebook.toggle_output(); - return false; - } - }, - 'shift-o' : { - help : 'toggle output scrolling', - help_index : 'gc', - handler : function (event) { - that.notebook.toggle_output_scroll(); - return false; - } - }, - 's' : { - help : 'save notebook', - help_index : 'fa', - handler : function (event) { - that.notebook.save_checkpoint(); - return false; - } - }, - 'ctrl-j' : { - help : 'move cell down', - help_index : 'eb', - handler : function (event) { - that.notebook.move_cell_down(); - return false; - } - }, - 'ctrl-k' : { - help : 'move cell up', - help_index : 'ea', - handler : function (event) { - that.notebook.move_cell_up(); - return false; - } - }, - 'l' : { - help : 'toggle line numbers', - help_index : 'ga', - handler : function (event) { - that.notebook.cell_toggle_line_numbers(); - return false; - } - }, - 'i' : { - help : 'interrupt kernel (press twice)', - help_index : 'ha', - count: 2, - handler : function (event) { - that.notebook.kernel.interrupt(); - return false; - } - }, - '0' : { - help : 'restart kernel (press twice)', - help_index : 'hb', - count: 2, - handler : function (event) { - that.notebook.restart_kernel(); - return false; - } - }, - 'h' : { - help : 'keyboard shortcuts', - help_index : 'ge', - handler : function (event) { - that.quick_help.show_keyboard_shortcuts(); - return false; - } - }, - 'z' : { - help : 'undo last delete', - help_index : 'ei', - handler : function (event) { - that.notebook.undelete_cell(); - return false; - } - }, - 'shift-m' : { - help : 'merge cell below', - help_index : 'ek', - handler : function (event) { - that.notebook.merge_cell_below(); - return false; - } - }, - 'q' : { - help : 'close pager', - help_index : 'gd', - handler : function (event) { - that.pager.collapse(); - return false; - } - }, + 'shift-space': 'ipython.scroll-up', + 'shift-v' : 'ipython.paste-cell-before', + 'shift-m' : 'ipython.merge-selected-cell-with-cell-after', + 'shift-o' : 'ipython.toggle-output-scrolling-selected-cell', + 'ctrl-j' : 'ipython.move-selected-cell-down', + 'ctrl-k' : 'ipython.move-selected-cell-up', + 'enter' : 'ipython.enter-edit-mode', + 'space' : 'ipython.scroll-down', + 'down' : 'ipython.select-next-cell', + 'i,i' : 'ipython.interrupt-kernel', + '0,0' : 'ipython.restart-kernel', + 'd,d' : 'ipython.delete-cell', + 'esc': 'ipython.close-pager', + 'up' : 'ipython.select-previous-cell', + 'k' : 'ipython.select-previous-cell', + 'j' : 'ipython.select-next-cell', + 'x' : 'ipython.cut-selected-cell', + 'c' : 'ipython.copy-selected-cell', + 'v' : 'ipython.paste-cell-after', + 'a' : 'ipython.insert-cell-before', + 'b' : 'ipython.insert-cell-after', + 'y' : 'ipython.change-selected-cell-to-code-cell', + 'm' : 'ipython.change-selected-cell-to-markdown-cell', + 'r' : 'ipython.change-selected-cell-to-raw-cell', + '1' : 'ipython.change-selected-cell-to-heading-1', + '2' : 'ipython.change-selected-cell-to-heading-2', + '3' : 'ipython.change-selected-cell-to-heading-3', + '4' : 'ipython.change-selected-cell-to-heading-4', + '5' : 'ipython.change-selected-cell-to-heading-5', + '6' : 'ipython.change-selected-cell-to-heading-6', + 'o' : 'ipython.toggle-output-visibility-selected-cell', + 's' : 'ipython.save-notebook', + 'l' : 'ipython.toggle-line-number-selected-cell', + 'h' : 'ipython.show-keyboard-shortcut-help-dialog', + 'z' : 'ipython.undo-last-cell-deletion', + 'q' : 'ipython.close-pager', }; }; KeyboardManager.prototype.bind_events = function () { var that = this; $(document).keydown(function (event) { + if(event._ipkmIgnore===true||(event.originalEvent||{})._ipkmIgnore===true){ + return false; + } return that.handle_keydown(event); }); }; + KeyboardManager.prototype.set_notebook = function (notebook) { + this.notebook = notebook; + this.actions.extend_env({notebook:notebook}); + }; + + KeyboardManager.prototype.set_quickhelp = function (notebook) { + this.actions.extend_env({quick_help:notebook}); + }; + + KeyboardManager.prototype.handle_keydown = function (event) { - var notebook = this.notebook; + /** + * returning false from this will stop event propagation + **/ if (event.which === keycodes.esc) { // Intercept escape at highest level to avoid closing @@ -503,8 +157,7 @@ define([ if (!this.enabled) { if (event.which === keycodes.esc) { - // ESC - notebook.command_mode(); + this.notebook.command_mode(); return false; } return true; @@ -571,7 +224,8 @@ define([ }); }; - // For backwards compatability. + + // For backwards compatibility. IPython.KeyboardManager = KeyboardManager; return {'KeyboardManager': KeyboardManager}; diff --git a/IPython/html/static/notebook/js/main.js b/IPython/html/static/notebook/js/main.js index 4ddee4b..fb325bc 100644 --- a/IPython/html/static/notebook/js/main.js +++ b/IPython/html/static/notebook/js/main.js @@ -5,6 +5,8 @@ require([ 'base/js/namespace', 'jquery', 'notebook/js/notebook', + 'contents', + 'services/config', 'base/js/utils', 'base/js/page', 'notebook/js/layoutmanager', @@ -16,15 +18,20 @@ require([ 'notebook/js/menubar', 'notebook/js/notificationarea', 'notebook/js/savewidget', + 'notebook/js/actions', 'notebook/js/keyboardmanager', 'notebook/js/config', 'notebook/js/kernelselector', - // only loaded, not used: - 'custom/custom', + 'codemirror/lib/codemirror', + 'notebook/js/about', + // only loaded, not used, please keep sure this is loaded last + 'custom/custom' ], function( IPython, $, notebook, + contents, + configmod, utils, page, layoutmanager, @@ -35,16 +42,24 @@ require([ quickhelp, menubar, notificationarea, - savewidget, + savewidget, + actions, keyboardmanager, config, - kernelselector + kernelselector, + CodeMirror, + about, + // please keep sure that even if not used, this is loaded last + custom ) { "use strict"; + // compat with old IPython, remove for IPython > 3.0 + window.CodeMirror = CodeMirror; + var common_options = { + ws_url : utils.get_body_data("wsUrl"), base_url : utils.get_body_data("baseUrl"), - ws_url : IPython.utils.get_body_data("wsUrl"), notebook_path : utils.get_body_data("notebookPath"), notebook_name : utils.get_body_data('notebookName') }; @@ -55,34 +70,46 @@ require([ var pager = new pager.Pager('div#pager', 'div#pager_splitter', { layout_manager: layout_manager, events: events}); + var acts = new actions.init(); var keyboard_manager = new keyboardmanager.KeyboardManager({ pager: pager, - events: events}); + events: events, + actions: acts }); var save_widget = new savewidget.SaveWidget('span#save_widget', { events: events, keyboard_manager: keyboard_manager}); + var contents = new contents.Contents($.extend({ + events: events}, + common_options)); + var config_section = new configmod.ConfigSection('notebook', common_options); + config_section.load(); var notebook = new notebook.Notebook('div#notebook', $.extend({ events: events, keyboard_manager: keyboard_manager, save_widget: save_widget, + contents: contents, config: user_config}, common_options)); var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options); var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', { notebook: notebook, - events: events}); + events: events, + actions: acts}); var quick_help = new quickhelp.QuickHelp({ keyboard_manager: keyboard_manager, events: events, notebook: notebook}); + keyboard_manager.set_notebook(notebook); + keyboard_manager.set_quickhelp(quick_help); var menubar = new menubar.MenuBar('#menubar', $.extend({ notebook: notebook, + contents: contents, layout_manager: layout_manager, events: events, save_widget: save_widget, quick_help: quick_help}, common_options)); - var notification_area = new notificationarea.NotificationArea( + var notification_area = new notificationarea.NotebookNotificationArea( '#notification_area', { events: events, save_widget: save_widget, @@ -122,6 +149,7 @@ require([ IPython.page = page; IPython.layout_manager = layout_manager; IPython.notebook = notebook; + IPython.contents = contents; IPython.pager = pager; IPython.quick_help = quick_help; IPython.login_widget = login_widget; @@ -134,6 +162,13 @@ require([ IPython.tooltip = notebook.tooltip; events.trigger('app_initialized.NotebookApp'); - notebook.load_notebook(common_options.notebook_name, common_options.notebook_path); + config_section.loaded.then(function() { + if (config_section.data.load_extensions) { + var nbextension_paths = Object.getOwnPropertyNames( + config_section.data.load_extensions); + IPython.load_extensions.apply(this, nbextension_paths); + } + }); + notebook.load_notebook(common_options.notebook_path); }); diff --git a/IPython/html/static/notebook/js/maintoolbar.js b/IPython/html/static/notebook/js/maintoolbar.js index 56293b8..a32a4e8 100644 --- a/IPython/html/static/notebook/js/maintoolbar.js +++ b/IPython/html/static/notebook/js/maintoolbar.js @@ -10,14 +10,16 @@ define([ "use strict"; var MainToolBar = function (selector, options) { - // Constructor - // - // Parameters: - // selector: string - // options: dictionary - // Dictionary of keyword arguments. - // events: $(Events) instance - // notebook: Notebook instance + /** + * Constructor + * + * Parameters: + * selector: string + * options: dictionary + * Dictionary of keyword arguments. + * events: $(Events) instance + * notebook: Notebook instance + */ toolbar.ToolBar.apply(this, arguments); this.events = options.events; this.notebook = options.notebook; @@ -27,7 +29,7 @@ define([ this.bind_events(); }; - MainToolBar.prototype = new toolbar.ToolBar(); + MainToolBar.prototype = Object.create(toolbar.ToolBar.prototype); MainToolBar.prototype.construct = function () { var that = this; @@ -108,7 +110,9 @@ define([ label : 'Run Cell', icon : 'fa-play', callback : function () { - // emulate default shift-enter behavior + /** + * emulate default shift-enter behavior + */ that.notebook.execute_cell_and_select_below(); } }, @@ -117,7 +121,7 @@ define([ label : 'Interrupt', icon : 'fa-stop', callback : function () { - that.notebook.session.interrupt_kernel(); + that.notebook.kernel.interrupt(); } }, { @@ -139,12 +143,7 @@ define([ .append($('
').text(
+                '## This is a level 2 heading'
+            )),
+            buttons : {
+                "OK" : {}
+            }
+        });
+    };
+    
     /**
-     * Turn a cell into a heading cell.
+     * Turn a cell into a markdown cell with a heading.
      * 
      * @method to_heading
      * @param {Number} [index] A cell's index
-     * @param {Number} [level] A heading level (e.g., 1 becomes <h1>)
+     * @param {Number} [level] A heading level (e.g., 1 for h1)
      */
     Notebook.prototype.to_heading = function (index, level) {
+        this.to_markdown(index);
         level = level || 1;
         var i = this.index_or_selected(index);
         if (this.is_valid_cell_index(i)) {
-            var source_cell = this.get_cell(i);
-            var target_cell = null;
-            if (source_cell instanceof textcell.HeadingCell) {
-                source_cell.set_level(level);
-            } else {
-                target_cell = this.insert_cell_below('heading',i);
-                var text = source_cell.get_text();
-                if (text === source_cell.placeholder) {
-                    text = '';
-                }
-                //metadata
-                target_cell.metadata = source_cell.metadata;
-                // We must show the editor before setting its contents
-                target_cell.set_level(level);
-                target_cell.unrender();
-                target_cell.set_text(text);
-                // make this value the starting point, so that we can only undo
-                // to this state, instead of a blank cell
-                target_cell.code_mirror.clearHistory();
-                source_cell.element.remove();
-                this.select(i);
-                var cursor = source_cell.code_mirror.getCursor();
-                target_cell.code_mirror.setCursor(cursor);
-                if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
-                    target_cell.render();
-                }
-            }
+            var cell = this.get_cell(i);
+            cell.set_heading_level(level);
             this.set_dirty(true);
-            this.events.trigger('selected_cell_type_changed.Notebook',
-                {'cell_type':'heading',level:level}
-            );
         }
     };
 
@@ -1193,6 +1235,10 @@ define([
     Notebook.prototype.copy_cell = function () {
         var cell = this.get_selected_cell();
         this.clipboard = cell.toJSON();
+        // remove undeletable status from the copied cell
+        if (this.clipboard.metadata.deletable !== undefined) {
+            delete this.clipboard.metadata.deletable;
+        }
         this.enable_paste();
     };
 
@@ -1248,8 +1294,6 @@ define([
      * @method split_cell
      */
     Notebook.prototype.split_cell = function () {
-        var mdc = textcell.MarkdownCell;
-        var rc = textcell.RawCell;
         var cell = this.get_selected_cell();
         if (cell.is_splittable()) {
             var texta = cell.get_pre_cursor();
@@ -1268,8 +1312,6 @@ define([
      * @method merge_cell_above
      */
     Notebook.prototype.merge_cell_above = function () {
-        var mdc = textcell.MarkdownCell;
-        var rc = textcell.RawCell;
         var index = this.get_selected_index();
         var cell = this.get_cell(index);
         var render = cell.rendered;
@@ -1305,8 +1347,6 @@ define([
      * @method merge_cell_below
      */
     Notebook.prototype.merge_cell_below = function () {
-        var mdc = textcell.MarkdownCell;
-        var rc = textcell.RawCell;
         var index = this.get_selected_index();
         var cell = this.get_cell(index);
         var render = cell.rendered;
@@ -1360,7 +1400,7 @@ define([
      * @method collapse_all_output
      */
     Notebook.prototype.collapse_all_output = function () {
-        $.map(this.get_cells(), function (cell, i) {
+        this.get_cells().map(function (cell, i) {
             if (cell instanceof codecell.CodeCell) {
                 cell.collapse_output();
             }
@@ -1390,7 +1430,7 @@ define([
      * @method expand_all_output
      */
     Notebook.prototype.expand_all_output = function () {
-        $.map(this.get_cells(), function (cell, i) {
+        this.get_cells().map(function (cell, i) {
             if (cell instanceof codecell.CodeCell) {
                 cell.expand_output();
             }
@@ -1420,7 +1460,7 @@ define([
      * @method clear_all_output
      */
     Notebook.prototype.clear_all_output = function () {
-        $.map(this.get_cells(), function (cell, i) {
+        this.get_cells().map(function (cell, i) {
             if (cell instanceof codecell.CodeCell) {
                 cell.clear_output();
             }
@@ -1449,7 +1489,7 @@ define([
      * @method scroll_all_output
      */
     Notebook.prototype.scroll_all_output = function () {
-        $.map(this.get_cells(), function (cell, i) {
+        this.get_cells().map(function (cell, i) {
             if (cell instanceof codecell.CodeCell) {
                 cell.scroll_output();
             }
@@ -1478,7 +1518,7 @@ define([
      * @method toggle_all_output
      */
     Notebook.prototype.toggle_all_output = function () {
-        $.map(this.get_cells(), function (cell, i) {
+        this.get_cells().map(function (cell, i) {
             if (cell instanceof codecell.CodeCell) {
                 cell.toggle_output();
             }
@@ -1508,7 +1548,7 @@ define([
      * @method toggle_all_output_scrolling
      */
     Notebook.prototype.toggle_all_output_scroll = function () {
-        $.map(this.get_cells(), function (cell, i) {
+        this.get_cells().map(function (cell, i) {
             if (cell instanceof codecell.CodeCell) {
                 cell.toggle_output_scroll();
             }
@@ -1540,11 +1580,11 @@ define([
         }
         this.codemirror_mode = newmode;
         codecell.CodeCell.options_default.cm_config.mode = newmode;
-        modename = newmode.name || newmode
-
-        that = this;
-        CodeMirror.requireMode(modename, function(){
-            $.map(that.get_cells(), function(cell, i) {
+        var modename = newmode.mode || newmode.name || newmode;
+        
+        var that = this;
+        utils.requireCodeMirrorMode(modename, function () {
+            that.get_cells().map(function(cell, i) {
                 if (cell.cell_type === 'code'){
                     cell.code_mirror.setOption('mode', newmode);
                     // This is currently redundant, because cm_config ends up as
@@ -1553,7 +1593,7 @@ define([
                     cell.cm_config.mode = newmode;
                 }
             });
-        })
+        });
     };
 
     // Session related things
@@ -1564,53 +1604,29 @@ define([
      * @method start_session
      */
     Notebook.prototype.start_session = function (kernel_name) {
-        var that = this;
-        if (kernel_name === undefined) {
-            kernel_name = this.default_kernel_name;
-        }
         if (this._session_starting) {
             throw new session.SessionAlreadyStarting();
         }
         this._session_starting = true;
-        
-        if (this.session !== null) {
-            var s = this.session;
-            this.session = null;
-            // need to start the new session in a callback after delete,
-            // because javascript does not guarantee the ordering of AJAX requests (?!)
-            s.delete(function () {
-                    // on successful delete, start new session
-                    that._session_starting = false;
-                    that.start_session(kernel_name);
-                }, function (jqXHR, status, error) {
-                    // log the failed delete, but still create a new session
-                    // 404 just means it was already deleted by someone else,
-                    // but other errors are possible.
-                    utils.log_ajax_error(jqXHR, status, error);
-                    that._session_starting = false;
-                    that.start_session(kernel_name);
-                }
-            );
-            return;
-        }
-        
-        
-    
-        this.session = new session.Session({
+
+        var options = {
             base_url: this.base_url,
             ws_url: this.ws_url,
             notebook_path: this.notebook_path,
             notebook_name: this.notebook_name,
-            // For now, create all sessions with the 'python' kernel, which is the
-            // default. Later, the user will be able to select kernels. This is
-            // overridden if KernelManager.kernel_cmd is specified for the server.
             kernel_name: kernel_name,
-            notebook: this});
+            notebook: this
+        };
 
-        this.session.start(
-            $.proxy(this._session_started, this),
-            $.proxy(this._session_start_failed, this)
-        );
+        var success = $.proxy(this._session_started, this);
+        var failure = $.proxy(this._session_start_failed, this);
+
+        if (this.session !== null) {
+            this.session.restart(options, success, failure);
+        } else {
+            this.session = new session.Session(options);
+            this.session.start(success, failure);
+        }
     };
 
 
@@ -1654,7 +1670,7 @@ define([
                 "Restart" : {
                     "class" : "btn-danger",
                     "click" : function() {
-                        that.session.restart_kernel();
+                        that.kernel.restart();
                     }
                 }
             }
@@ -1667,9 +1683,10 @@ define([
      * @method execute_cell
      */
     Notebook.prototype.execute_cell = function () {
-        // mode = shift, ctrl, alt
+        /**
+         * mode = shift, ctrl, alt
+         */
         var cell = this.get_selected_cell();
-        var cell_index = this.find_cell_index(cell);
         
         cell.execute();
         this.command_mode();
@@ -1798,7 +1815,9 @@ define([
      * @param {String} name A new name for this notebook
      */
     Notebook.prototype.set_notebook_name = function (name) {
+        var parent = utils.url_path_split(this.notebook_path)[0];
         this.notebook_name = name;
+        this.notebook_path = utils.url_path_join(parent, name);
     };
 
     /**
@@ -1820,12 +1839,11 @@ define([
     /**
      * Load a notebook from JSON (.ipynb).
      * 
-     * This currently handles one worksheet: others are deleted.
-     * 
      * @method fromJSON
      * @param {Object} data JSON representation of a notebook
      */
     Notebook.prototype.fromJSON = function (data) {
+
         var content = data.content;
         var ncells = this.ncells();
         var i;
@@ -1836,6 +1854,7 @@ define([
         // Save the metadata and name.
         this.metadata = content.metadata;
         this.notebook_name = data.name;
+        this.notebook_path = data.path;
         var trusted = true;
         
         // Trigger an event changing the kernel spec - this will set the default
@@ -1844,49 +1863,29 @@ define([
             this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
         }
         
-        // Only handle 1 worksheet for now.
-        var worksheet = content.worksheets[0];
-        if (worksheet !== undefined) {
-            if (worksheet.metadata) {
-                this.worksheet_metadata = worksheet.metadata;
-            }
-            var new_cells = worksheet.cells;
-            ncells = new_cells.length;
-            var cell_data = null;
-            var new_cell = null;
-            for (i=0; i raw
-                // handle never-released plaintext name for raw cells
-                if (cell_data.cell_type === 'plaintext'){
-                    cell_data.cell_type = 'raw';
-                }
-
-                new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
-                new_cell.fromJSON(cell_data);
-                if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
-                    trusted = false;
-                }
+        // Set the codemirror mode from language_info metadata
+        if (this.metadata.language_info !== undefined) {
+            var langinfo = this.metadata.language_info;
+            // Mode 'null' should be plain, unhighlighted text.
+            var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
+            this.set_codemirror_mode(cm_mode);
+        }
+        
+        var new_cells = content.cells;
+        ncells = new_cells.length;
+        var cell_data = null;
+        var new_cell = null;
+        for (i=0; i 1) {
-            dialog.modal({
-                notebook: this,
-                keyboard_manager: this.keyboard_manager,
-                title : "Multiple worksheets",
-                body : "This notebook has " + data.worksheets.length + " worksheets, " +
-                    "but this version of IPython can only handle the first.  " +
-                    "If you save this notebook, worksheets after the first will be lost.",
-                buttons : {
-                    OK : {
-                        class : "btn-danger"
-                    }
-                }
-            });
+            this.events.trigger("trust_changed.Notebook", trusted);
         }
     };
 
@@ -1897,6 +1896,12 @@ define([
      * @return {Object} A JSON-friendly representation of this notebook.
      */
     Notebook.prototype.toJSON = function () {
+        /**
+         * remove the conversion indicator, which only belongs in-memory
+         */
+        delete this.metadata.orig_nbformat;
+        delete this.metadata.orig_nbformat_minor;
+
         var cells = this.get_cells();
         var ncells = cells.length;
         var cell_array = new Array(ncells);
@@ -1909,12 +1914,10 @@ define([
             cell_array[i] = cell.toJSON();
         }
         var data = {
-            // Only handle 1 worksheet for now.
-            worksheets : [{
-                cells: cell_array,
-                metadata: this.worksheet_metadata
-            }],
-            metadata : this.metadata
+            cells: cell_array,
+            metadata: this.metadata,
+            nbformat: this.nbformat,
+            nbformat_minor: this.nbformat_minor
         };
         if (trusted != this.trusted) {
             this.trusted = trusted;
@@ -1935,6 +1938,10 @@ define([
         if (this.autosave_timer) {
             clearInterval(this.autosave_timer);
         }
+        if (!this.writable) {
+            // disable autosave if not writable
+            interval = 0;
+        }
         
         this.autosave_interval = this.minimum_autosave_interval = interval;
         if (interval) {
@@ -1956,54 +1963,69 @@ define([
      * 
      * @method save_notebook
      */
-    Notebook.prototype.save_notebook = function (extra_settings) {
+    Notebook.prototype.save_notebook = function () {
+        if (!this._fully_loaded) {
+            this.events.trigger('notebook_save_failed.Notebook',
+                new Error("Load failed, save is disabled")
+            );
+            return;
+        } else if (!this.writable) {
+            this.events.trigger('notebook_save_failed.Notebook',
+                new Error("Notebook is read-only")
+            );
+            return;
+        }
+
         // Create a JSON model to be sent to the server.
-        var model = {};
-        model.name = this.notebook_name;
-        model.path = this.notebook_path;
-        model.type = 'notebook';
-        model.format = 'json';
-        model.content = this.toJSON();
-        model.content.nbformat = this.nbformat;
-        model.content.nbformat_minor = this.nbformat_minor;
+        var model = {
+            type : "notebook",
+            content : this.toJSON()
+        };
         // time the ajax call for autosave tuning purposes.
         var start =  new Date().getTime();
-        // We do the call with settings so we can set cache to false.
-        var settings = {
-            processData : false,
-            cache : false,
-            type : "PUT",
-            data : JSON.stringify(model),
-            headers : {'Content-Type': 'application/json'},
-            success : $.proxy(this.save_notebook_success, this, start),
-            error : $.proxy(this.save_notebook_error, this)
-        };
-        if (extra_settings) {
-            for (var key in extra_settings) {
-                settings[key] = extra_settings[key];
-            }
-        }
-        this.events.trigger('notebook_saving.Notebook');
-        var url = utils.url_join_encode(
-            this.base_url,
-            'api/contents',
-            this.notebook_path,
-            this.notebook_name
-        );
-        $.ajax(url, settings);
+
+        var that = this;
+        return this.contents.save(this.notebook_path, model).then(
+                $.proxy(this.save_notebook_success, this, start),
+                function (error) {
+                    that.events.trigger('notebook_save_failed.Notebook', error);
+                }
+            );
     };
     
     /**
      * Success callback for saving a notebook.
      * 
      * @method save_notebook_success
-     * @param {Integer} start the time when the save request started
+     * @param {Integer} start Time when the save request start
      * @param {Object} data JSON representation of a notebook
-     * @param {String} status Description of response status
-     * @param {jqXHR} xhr jQuery Ajax object
      */
-    Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
+    Notebook.prototype.save_notebook_success = function (start, data) {
         this.set_dirty(false);
+        if (data.message) {
+            // save succeeded, but validation failed.
+            var body = $("
"); + var title = "Notebook validation failed"; + + body.append($("

").text( + "The save operation succeeded," + + " but the notebook does not appear to be valid." + + " The validation error was:" + )).append($("

").addClass("validation-error").append( + $("
").text(data.message)
+            ));
+            dialog.modal({
+                notebook: this,
+                keyboard_manager: this.keyboard_manager,
+                title: title,
+                body: body,
+                buttons : {
+                    OK : {
+                        "class" : "btn-primary"
+                    }
+                }
+            });
+        }
         this.events.trigger('notebook_saved.Notebook');
         this._update_autosave_interval(start);
         if (this._checkpoint_after_save) {
@@ -2031,25 +2053,13 @@ define([
             }
         }
     };
-    
-    /**
-     * Failure callback for saving a notebook.
-     * 
-     * @method save_notebook_error
-     * @param {jqXHR} xhr jQuery Ajax object
-     * @param {String} status Description of response status
-     * @param {String} error HTTP error message
-     */
-    Notebook.prototype.save_notebook_error = function (xhr, status, error) {
-        this.events.trigger('notebook_save_failed.Notebook', [xhr, status, error]);
-    };
 
     /**
      * Explicitly trust the output of this notebook.
      *
      * @method trust_notebook
      */
-    Notebook.prototype.trust_notebook = function (extra_settings) {
+    Notebook.prototype.trust_notebook = function () {
         var body = $("
").append($("

") .text("A trusted IPython notebook may execute hidden malicious code ") .append($("") @@ -2094,177 +2104,60 @@ define([ }); }; - Notebook.prototype.new_notebook = function(){ - var path = this.notebook_path; + Notebook.prototype.copy_notebook = function () { + var that = this; var base_url = this.base_url; - var settings = { - processData : false, - cache : false, - type : "POST", - dataType : "json", - async : false, - success : function (data, status, xhr){ - var notebook_name = data.name; - window.open( - utils.url_join_encode( - base_url, - 'notebooks', - path, - notebook_name - ), - '_blank' + var w = window.open(); + var parent = utils.url_path_split(this.notebook_path)[0]; + this.contents.copy(this.notebook_path, parent).then( + function (data) { + w.location = utils.url_join_encode( + base_url, 'notebooks', data.path ); }, - error : utils.log_ajax_error, - }; - var url = utils.url_join_encode( - base_url, - 'api/contents', - path + function(error) { + w.close(); + that.events.trigger('notebook_copy_failed', error); + } ); - $.ajax(url,settings); }; + Notebook.prototype.rename = function (new_name) { + if (!new_name.match(/\.ipynb$/)) { + new_name = new_name + ".ipynb"; + } - Notebook.prototype.copy_notebook = function(){ - var path = this.notebook_path; - var base_url = this.base_url; - var settings = { - processData : false, - cache : false, - type : "POST", - dataType : "json", - data : JSON.stringify({copy_from : this.notebook_name}), - async : false, - success : function (data, status, xhr) { - window.open(utils.url_join_encode( - base_url, - 'notebooks', - data.path, - data.name - ), '_blank'); - }, - error : utils.log_ajax_error, - }; - var url = utils.url_join_encode( - base_url, - 'api/contents', - path - ); - $.ajax(url,settings); - }; - - Notebook.prototype.rename = function (nbname) { var that = this; - if (!nbname.match(/\.ipynb$/)) { - nbname = nbname + ".ipynb"; - } - var data = {name: nbname}; - var settings = { - processData : false, - cache : false, - type : "PATCH", - data : JSON.stringify(data), - dataType: "json", - headers : {'Content-Type': 'application/json'}, - success : $.proxy(that.rename_success, this), - error : $.proxy(that.rename_error, this) - }; - this.events.trigger('rename_notebook.Notebook', data); - var url = utils.url_join_encode( - this.base_url, - 'api/contents', - this.notebook_path, - this.notebook_name + var parent = utils.url_path_split(this.notebook_path)[0]; + var new_path = utils.url_path_join(parent, new_name); + return this.contents.rename(this.notebook_path, new_path).then( + function (json) { + that.notebook_name = json.name; + that.notebook_path = json.path; + that.session.rename_notebook(json.path); + that.events.trigger('notebook_renamed.Notebook', json); + } ); - $.ajax(url, settings); }; Notebook.prototype.delete = function () { - var that = this; - var settings = { - processData : false, - cache : false, - type : "DELETE", - dataType: "json", - error : utils.log_ajax_error, - }; - var url = utils.url_join_encode( - this.base_url, - 'api/contents', - this.notebook_path, - this.notebook_name - ); - $.ajax(url, settings); - }; - - - Notebook.prototype.rename_success = function (json, status, xhr) { - var name = this.notebook_name = json.name; - var path = json.path; - this.session.rename_notebook(name, path); - this.events.trigger('notebook_renamed.Notebook', json); - }; - - Notebook.prototype.rename_error = function (xhr, status, error) { - var that = this; - var dialog_body = $('

').append( - $("

").text('This notebook name already exists.') - ); - this.events.trigger('notebook_rename_failed.Notebook', [xhr, status, error]); - dialog.modal({ - notebook: this, - keyboard_manager: this.keyboard_manager, - title: "Notebook Rename Error!", - body: dialog_body, - buttons : { - "Cancel": {}, - "OK": { - class: "btn-primary", - click: function () { - this.save_widget.rename_notebook({notebook:that}); - }} - }, - open : function (event, ui) { - var that = $(this); - // Upon ENTER, click the OK button. - that.find('input[type="text"]').keydown(function (event, ui) { - if (event.which === this.keyboard.keycodes.enter) { - that.find('.btn-primary').first().click(); - } - }); - that.find('input[type="text"]').focus(); - } - }); + this.contents.delete(this.notebook_path); }; /** * Request a notebook's data from the server. * * @method load_notebook - * @param {String} notebook_name and path A notebook to load + * @param {String} notebook_path A notebook to load */ - Notebook.prototype.load_notebook = function (notebook_name, notebook_path) { - var that = this; - this.notebook_name = notebook_name; + Notebook.prototype.load_notebook = function (notebook_path) { this.notebook_path = notebook_path; - // We do the call with settings so we can set cache to false. - var settings = { - processData : false, - cache : false, - type : "GET", - dataType : "json", - success : $.proxy(this.load_notebook_success,this), - error : $.proxy(this.load_notebook_error,this), - }; + this.notebook_name = utils.url_path_split(this.notebook_path)[1]; this.events.trigger('notebook_loading.Notebook'); - var url = utils.url_join_encode( - this.base_url, - 'api/contents', - this.notebook_path, - this.notebook_name + this.contents.get(notebook_path, {type: 'notebook'}).then( + $.proxy(this.load_notebook_success, this), + $.proxy(this.load_notebook_error, this) ); - $.ajax(url, settings); }; /** @@ -2274,11 +2167,58 @@ define([ * * @method load_notebook_success * @param {Object} data JSON representation of a notebook - * @param {String} status Description of response status - * @param {jqXHR} xhr jQuery Ajax object */ - Notebook.prototype.load_notebook_success = function (data, status, xhr) { - this.fromJSON(data); + Notebook.prototype.load_notebook_success = function (data) { + var failed, msg; + try { + this.fromJSON(data); + } catch (e) { + failed = e; + console.log("Notebook failed to load from JSON:", e); + } + if (failed || data.message) { + // *either* fromJSON failed or validation failed + var body = $("

"); + var title; + if (failed) { + title = "Notebook failed to load"; + body.append($("

").text( + "The error was: " + )).append($("

").addClass("js-error").text( + failed.toString() + )).append($("

").text( + "See the error console for details." + )); + } else { + title = "Notebook validation failed"; + } + + if (data.message) { + if (failed) { + msg = "The notebook also failed validation:"; + } else { + msg = "An invalid notebook may not function properly." + + " The validation error was:"; + } + body.append($("

").text( + msg + )).append($("

").addClass("validation-error").append( + $("
").text(data.message)
+                ));
+            }
+
+            dialog.modal({
+                notebook: this,
+                keyboard_manager: this.keyboard_manager,
+                title: title,
+                body: body,
+                buttons : {
+                    OK : {
+                        "class" : "btn-primary"
+                    }
+                }
+            });
+        }
         if (this.ncells() === 0) {
             this.insert_cell_below('code');
             this.edit_mode(0);
@@ -2288,13 +2228,30 @@ define([
         }
         this.set_dirty(false);
         this.scroll_to_top();
-        if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
-            var msg = "This notebook has been converted from an older " +
-            "notebook format (v"+data.orig_nbformat+") to the current notebook " +
-            "format (v"+data.nbformat+"). The next time you save this notebook, the " +
-            "newer notebook format will be used and older versions of IPython " +
-            "may not be able to read it. To keep the older version, close the " +
-            "notebook without saving it.";
+        this.writable = data.writable || false;
+        var nbmodel = data.content;
+        var orig_nbformat = nbmodel.metadata.orig_nbformat;
+        var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
+        if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
+            var src;
+            if (nbmodel.nbformat > orig_nbformat) {
+                src = " an older notebook format ";
+            } else {
+                src = " a newer notebook format ";
+            }
+            
+            msg = "This notebook has been converted from" + src +
+            "(v"+orig_nbformat+") to the current notebook " +
+            "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
+            "current notebook format will be used.";
+            
+            if (nbmodel.nbformat > orig_nbformat) {
+                msg += " Older versions of IPython may not be able to read the new format.";
+            } else {
+                msg += " Some features of the original notebook may not be available.";
+            }
+            msg += " To preserve the original version, close the " +
+                "notebook without saving it.";
             dialog.modal({
                 notebook: this,
                 keyboard_manager: this.keyboard_manager,
@@ -2306,33 +2263,15 @@ define([
                     }
                 }
             });
-        } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
-            var that = this;
-            var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
-            var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
-            var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
-            this_vs + ".  You can still work with this notebook, but some features " +
-            "introduced in later notebook versions may not be available.";
-
-            dialog.modal({
-                notebook: this,
-                keyboard_manager: this.keyboard_manager,
-                title : "Newer Notebook",
-                body : msg,
-                buttons : {
-                    OK : {
-                        class : "btn-danger"
-                    }
-                }
-            });
-
+        } else if (this.nbformat_minor < nbmodel.nbformat_minor) {
+            this.nbformat_minor = nbmodel.nbformat_minor;
         }
         
         // Create the session after the notebook is completely loaded to prevent
         // code execution upon loading, which is a security risk.
         if (this.session === null) {
             var kernelspec = this.metadata.kernelspec || {};
-            var kernel_name = kernelspec.name || this.default_kernel_name;
+            var kernel_name = kernelspec.name;
 
             this.start_session(kernel_name);
         }
@@ -2346,9 +2285,14 @@ define([
         } else {
             celltoolbar.CellToolbar.global_hide();
         }
-
+        
+        if (!this.writable) {
+            this.set_autosave_interval(0);
+            this.events.trigger('notebook_read_only.Notebook');
+        }
+        
         // now that we're fully loaded, it is safe to restore save functionality
-        delete(this.save_notebook);
+        this._fully_loaded = true;
         this.events.trigger('notebook_loaded.Notebook');
     };
 
@@ -2356,20 +2300,18 @@ define([
      * Failure callback for loading a notebook from the server.
      * 
      * @method load_notebook_error
-     * @param {jqXHR} xhr jQuery Ajax object
-     * @param {String} status Description of response status
-     * @param {String} error HTTP error message
+     * @param {Error} error
      */
-    Notebook.prototype.load_notebook_error = function (xhr, status, error) {
-        this.events.trigger('notebook_load_failed.Notebook', [xhr, status, error]);
-        utils.log_ajax_error(xhr, status, error);
+    Notebook.prototype.load_notebook_error = function (error) {
+        this.events.trigger('notebook_load_failed.Notebook', error);
         var msg;
-        if (xhr.status === 400) {
-            msg = escape(utils.ajax_error_msg(xhr));
-        } else if (xhr.status === 500) {
+        if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
+            utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
             msg = "An unknown error occurred while loading this notebook. " +
             "This version can load notebook formats " +
             "v" + this.nbformat + " or earlier. See the server log for details.";
+        } else {
+            msg = error.message;
         }
         dialog.modal({
             notebook: this,
@@ -2422,17 +2364,12 @@ define([
      * @method list_checkpoints
      */
     Notebook.prototype.list_checkpoints = function () {
-        var url = utils.url_join_encode(
-            this.base_url,
-            'api/contents',
-            this.notebook_path,
-            this.notebook_name,
-            'checkpoints'
-        );
-        $.get(url).done(
-            $.proxy(this.list_checkpoints_success, this)
-        ).fail(
-            $.proxy(this.list_checkpoints_error, this)
+        var that = this;
+        this.contents.list_checkpoints(this.notebook_path).then(
+            $.proxy(this.list_checkpoints_success, this),
+            function(error) {
+                that.events.trigger('list_checkpoints_failed.Notebook', error);
+            }
         );
     };
 
@@ -2441,11 +2378,8 @@ define([
      * 
      * @method list_checkpoint_success
      * @param {Object} data JSON representation of a checkpoint
-     * @param {String} status Description of response status
-     * @param {jqXHR} xhr jQuery Ajax object
      */
-    Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
-        data = $.parseJSON(data);
+    Notebook.prototype.list_checkpoints_success = function (data) {
         this.checkpoints = data;
         if (data.length) {
             this.last_checkpoint = data[data.length - 1];
@@ -2456,34 +2390,17 @@ define([
     };
 
     /**
-     * Failure callback for listing a checkpoint.
-     * 
-     * @method list_checkpoint_error
-     * @param {jqXHR} xhr jQuery Ajax object
-     * @param {String} status Description of response status
-     * @param {String} error_msg HTTP error message
-     */
-    Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
-        this.events.trigger('list_checkpoints_failed.Notebook');
-    };
-    
-    /**
      * Create a checkpoint of this notebook on the server from the most recent save.
      * 
      * @method create_checkpoint
      */
     Notebook.prototype.create_checkpoint = function () {
-        var url = utils.url_join_encode(
-            this.base_url,
-            'api/contents',
-            this.notebook_path,
-            this.notebook_name,
-            'checkpoints'
-        );
-        $.post(url).done(
-            $.proxy(this.create_checkpoint_success, this)
-        ).fail(
-            $.proxy(this.create_checkpoint_error, this)
+        var that = this;
+        this.contents.create_checkpoint(this.notebook_path).then(
+            $.proxy(this.create_checkpoint_success, this),
+            function (error) {
+                that.events.trigger('checkpoint_failed.Notebook', error);
+            }
         );
     };
 
@@ -2492,27 +2409,12 @@ define([
      * 
      * @method create_checkpoint_success
      * @param {Object} data JSON representation of a checkpoint
-     * @param {String} status Description of response status
-     * @param {jqXHR} xhr jQuery Ajax object
      */
-    Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
-        data = $.parseJSON(data);
+    Notebook.prototype.create_checkpoint_success = function (data) {
         this.add_checkpoint(data);
         this.events.trigger('checkpoint_created.Notebook', data);
     };
 
-    /**
-     * Failure callback for creating a checkpoint.
-     * 
-     * @method create_checkpoint_error
-     * @param {jqXHR} xhr jQuery Ajax object
-     * @param {String} status Description of response status
-     * @param {String} error_msg HTTP error message
-     */
-    Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
-        this.events.trigger('checkpoint_failed.Notebook');
-    };
-    
     Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
         var that = this;
         checkpoint = checkpoint || this.last_checkpoint;
@@ -2562,18 +2464,12 @@ define([
      */
     Notebook.prototype.restore_checkpoint = function (checkpoint) {
         this.events.trigger('notebook_restoring.Notebook', checkpoint);
-        var url = utils.url_join_encode(
-            this.base_url,
-            'api/contents',
-            this.notebook_path,
-            this.notebook_name,
-            'checkpoints',
-            checkpoint
-        );
-        $.post(url).done(
-            $.proxy(this.restore_checkpoint_success, this)
-        ).fail(
-            $.proxy(this.restore_checkpoint_error, this)
+        var that = this;
+        this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
+            $.proxy(this.restore_checkpoint_success, this),
+            function (error) {
+                that.events.trigger('checkpoint_restore_failed.Notebook', error);
+            }
         );
     };
     
@@ -2581,28 +2477,13 @@ define([
      * Success callback for restoring a notebook to a checkpoint.
      * 
      * @method restore_checkpoint_success
-     * @param {Object} data (ignored, should be empty)
-     * @param {String} status Description of response status
-     * @param {jqXHR} xhr jQuery Ajax object
      */
-    Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
+    Notebook.prototype.restore_checkpoint_success = function () {
         this.events.trigger('checkpoint_restored.Notebook');
-        this.load_notebook(this.notebook_name, this.notebook_path);
+        this.load_notebook(this.notebook_path);
     };
 
     /**
-     * Failure callback for restoring a notebook to a checkpoint.
-     * 
-     * @method restore_checkpoint_error
-     * @param {jqXHR} xhr jQuery Ajax object
-     * @param {String} status Description of response status
-     * @param {String} error_msg HTTP error message
-     */
-    Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
-        this.events.trigger('checkpoint_restore_failed.Notebook');
-    };
-    
-    /**
      * Delete a notebook checkpoint.
      * 
      * @method delete_checkpoint
@@ -2610,44 +2491,23 @@ define([
      */
     Notebook.prototype.delete_checkpoint = function (checkpoint) {
         this.events.trigger('notebook_restoring.Notebook', checkpoint);
-        var url = utils.url_join_encode(
-            this.base_url,
-            'api/contents',
-            this.notebook_path,
-            this.notebook_name,
-            'checkpoints',
-            checkpoint
+        var that = this;
+        this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
+            $.proxy(this.delete_checkpoint_success, this),
+            function (error) {
+                that.events.trigger('checkpoint_delete_failed.Notebook', error);
+            }
         );
-        $.ajax(url, {
-            type: 'DELETE',
-            success: $.proxy(this.delete_checkpoint_success, this),
-            error: $.proxy(this.delete_checkpoint_error, this)
-        });
     };
     
     /**
      * Success callback for deleting a notebook checkpoint
      * 
      * @method delete_checkpoint_success
-     * @param {Object} data (ignored, should be empty)
-     * @param {String} status Description of response status
-     * @param {jqXHR} xhr jQuery Ajax object
-     */
-    Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
-        this.events.trigger('checkpoint_deleted.Notebook', data);
-        this.load_notebook(this.notebook_name, this.notebook_path);
-    };
-
-    /**
-     * Failure callback for deleting a notebook checkpoint.
-     * 
-     * @method delete_checkpoint_error
-     * @param {jqXHR} xhr jQuery Ajax object
-     * @param {String} status Description of response status
-     * @param {String} error HTTP error message
      */
-    Notebook.prototype.delete_checkpoint_error = function (xhr, status, error) {
-        this.events.trigger('checkpoint_delete_failed.Notebook', [xhr, status, error]);
+    Notebook.prototype.delete_checkpoint_success = function () {
+        this.events.trigger('checkpoint_deleted.Notebook');
+        this.load_notebook(this.notebook_path);
     };
 
 
diff --git a/IPython/html/static/notebook/js/notificationarea.js b/IPython/html/static/notebook/js/notificationarea.js
index 8c406ad..2cc4371 100644
--- a/IPython/html/static/notebook/js/notificationarea.js
+++ b/IPython/html/static/notebook/js/notificationarea.js
@@ -1,90 +1,51 @@
-// Copyright (c) IPython Development Team.
-// Distributed under the terms of the Modified BSD License.
-
 define([
     'base/js/namespace',
     'jquery',
     'base/js/utils',
     'base/js/dialog',
-    'notebook/js/notificationwidget',
+    'base/js/notificationarea',
     'moment'
-], function(IPython, $, utils, dialog, notificationwidget, moment) {
+], function(IPython, $, utils, dialog, notificationarea, moment) {
     "use strict";
-
-    var NotificationArea = function (selector, options) {
-        // Constructor
-        //
-        // Parameters:
-        //  selector: string
-        //  options: dictionary
-        //      Dictionary of keyword arguments.
-        //          notebook: Notebook instance
-        //          events: $(Events) instance
-        //          save_widget: SaveWidget instance
-        this.selector = selector;
-        this.events = options.events;
+    var NotificationArea = notificationarea.NotificationArea;
+    
+    var NotebookNotificationArea = function(selector, options) {
+        NotificationArea.apply(this, [selector, options]);
         this.save_widget = options.save_widget;
         this.notebook = options.notebook;
         this.keyboard_manager = options.keyboard_manager;
-        if (this.selector !== undefined) {
-            this.element = $(selector);
-        }
-        this.widget_dict = {};
-    };
-
-    NotificationArea.prototype.temp_message = function (msg, timeout, css_class) {
-        var tdiv = $('
') - .addClass('notification_widget') - .addClass(css_class) - .hide() - .text(msg); - - $(this.selector).append(tdiv); - var tmout = Math.max(1500,(timeout||1500)); - tdiv.fadeIn(100); - - setTimeout(function () { - tdiv.fadeOut(100, function () {tdiv.remove();}); - }, tmout); - }; - - NotificationArea.prototype.widget = function(name) { - if(this.widget_dict[name] === undefined) { - return this.new_notification_widget(name); - } - return this.get_widget(name); - }; - - NotificationArea.prototype.get_widget = function(name) { - if(this.widget_dict[name] === undefined) { - throw('no widgets with this name'); - } - return this.widget_dict[name]; - }; - - NotificationArea.prototype.new_notification_widget = function(name) { - if(this.widget_dict[name] !== undefined) { - throw('widget with that name already exists ! '); - } - var div = $('
').attr('id','notification_'+name); - $(this.selector).append(div); - this.widget_dict[name] = new notificationwidget.NotificationWidget('#notification_'+name); - return this.widget_dict[name]; + } + + NotebookNotificationArea.prototype = Object.create(NotificationArea.prototype); + + /** + * Initialize the default set of notification widgets. + * + * @method init_notification_widgets + */ + NotebookNotificationArea.prototype.init_notification_widgets = function () { + this.init_kernel_notification_widget(); + this.init_notebook_notification_widget(); }; - NotificationArea.prototype.init_notification_widgets = function() { + /** + * Initialize the notification widget for kernel status messages. + * + * @method init_kernel_notification_widget + */ + NotebookNotificationArea.prototype.init_kernel_notification_widget = function () { var that = this; var knw = this.new_notification_widget('kernel'); var $kernel_ind_icon = $("#kernel_indicator_icon"); var $modal_ind_icon = $("#modal_indicator_icon"); // Command/Edit mode - this.events.on('edit_mode.Notebook',function () { + this.events.on('edit_mode.Notebook', function () { that.save_widget.update_document_title(); $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode'); }); - this.events.on('command_mode.Notebook',function () { + this.events.on('command_mode.Notebook', function () { that.save_widget.update_document_title(); $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode'); }); @@ -92,110 +53,210 @@ define([ // Implicitly start off in Command mode, switching to Edit mode will trigger event $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode'); - // Kernel events - this.events.on('status_idle.Kernel',function () { - that.save_widget.update_document_title(); - $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle'); + // Kernel events + + // this can be either kernel_created.Kernel or kernel_created.Session + this.events.on('kernel_created.Kernel kernel_created.Session', function () { + knw.info("Kernel Created", 500); }); - this.events.on('status_busy.Kernel',function () { - window.document.title='(Busy) '+window.document.title; - $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy'); + this.events.on('kernel_reconnecting.Kernel', function () { + knw.warning("Connecting to kernel"); }); - this.events.on('status_restarting.Kernel',function () { + this.events.on('kernel_connection_dead.Kernel', function (evt, info) { + knw.danger("Not Connected", undefined, function () { + // schedule reconnect a short time in the future, don't reconnect immediately + setTimeout($.proxy(info.kernel.reconnect, info.kernel), 500); + }, {title: 'click to reconnect'}); + }); + + this.events.on('kernel_connected.Kernel', function () { + knw.info("Connected", 500); + }); + + this.events.on('kernel_restarting.Kernel', function () { that.save_widget.update_document_title(); knw.set_message("Restarting kernel", 2000); }); - this.events.on('status_dead.Kernel',function () { + this.events.on('kernel_autorestarting.Kernel', function (evt, info) { + // Only show the dialog on the first restart attempt. This + // number gets tracked by the `Kernel` object and passed + // along here, because we don't want to show the user 5 + // dialogs saying the same thing (which is the number of + // times it tries restarting). + if (info.attempt === 1) { + + dialog.kernel_modal({ + notebook: that.notebook, + keyboard_manager: that.keyboard_manager, + title: "Kernel Restarting", + body: "The kernel appears to have died. It will restart automatically.", + buttons: { + OK : { + class : "btn-primary" + } + } + }); + }; + that.save_widget.update_document_title(); knw.danger("Dead kernel"); $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead'); }); - this.events.on('status_interrupting.Kernel',function () { + this.events.on('kernel_interrupting.Kernel', function () { knw.set_message("Interrupting kernel", 2000); }); - - // Start the kernel indicator in the busy state, and send a kernel_info request. - // When the kernel_info reply arrives, the kernel is idle. - $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy'); - - this.events.on('status_started.Kernel', function (evt, data) { - knw.info("Websockets Connected", 500); - that.events.trigger('status_busy.Kernel'); - data.kernel.kernel_info(function () { - that.events.trigger('status_idle.Kernel'); - }); - }); - - this.events.on('status_dead.Kernel',function () { - var msg = 'The kernel has died, and the automatic restart has failed.' + - ' It is possible the kernel cannot be restarted.' + - ' If you are not able to restart the kernel, you will still be able to save' + - ' the notebook, but running code will no longer work until the notebook' + - ' is reopened.'; - - dialog.modal({ - title: "Dead kernel", - body : msg, - keyboard_manager: that.keyboard_manager, - notebook: that.notebook, - buttons : { - "Manual Restart": { - class: "btn-danger", - click: function () { - that.events.trigger('status_restarting.Kernel'); - that.notebook.start_kernel(); - } - }, - "Don't restart": {} - } - }); - }); - - this.events.on('websocket_closed.Kernel', function (event, data) { - var kernel = data.kernel; - var ws_url = data.ws_url; - var early = data.early; - var msg; + this.events.on('kernel_disconnected.Kernel', function () { $kernel_ind_icon .attr('class', 'kernel_disconnected_icon') .attr('title', 'No Connection to Kernel'); - - if (!early) { - knw.warning('Reconnecting'); - setTimeout(function () { - kernel.start_channels(); - }, 5000); - return; + }); + + this.events.on('kernel_connection_failed.Kernel', function (evt, info) { + // only show the dialog if this is the first failed + // connect attempt, because the kernel will continue + // trying to reconnect and we don't want to spam the user + // with messages + if (info.attempt === 1) { + + var msg = "A connection to the notebook server could not be established." + + " The notebook will continue trying to reconnect, but" + + " until it does, you will NOT be able to run code. Check your" + + " network connection or notebook server configuration."; + + dialog.kernel_modal({ + title: "Connection failed", + body: msg, + keyboard_manager: that.keyboard_manager, + notebook: that.notebook, + buttons : { + "OK": {} + } + }); } - console.log('WebSocket connection failed: ', ws_url); - msg = "A WebSocket connection could not be established." + - " You will NOT be able to run code. Check your" + - " network connection or notebook server configuration."; - dialog.modal({ - title: "WebSocket connection failed", - body: msg, - keyboard_manager: that.keyboard_manager, - notebook: that.notebook, - buttons : { - "OK": {}, - "Reconnect": { - click: function () { - knw.warning('Reconnecting'); - setTimeout(function () { - kernel.start_channels(); - }, 5000); - } + }); + + this.events.on('kernel_killed.Kernel kernel_killed.Session', function () { + that.save_widget.update_document_title(); + knw.danger("Dead kernel"); + $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead'); + }); + + this.events.on('kernel_dead.Kernel', function () { + + var showMsg = function () { + + var msg = 'The kernel has died, and the automatic restart has failed.' + + ' It is possible the kernel cannot be restarted.' + + ' If you are not able to restart the kernel, you will still be able to save' + + ' the notebook, but running code will no longer work until the notebook' + + ' is reopened.'; + + dialog.kernel_modal({ + title: "Dead kernel", + body : msg, + keyboard_manager: that.keyboard_manager, + notebook: that.notebook, + buttons : { + "Manual Restart": { + class: "btn-danger", + click: function () { + that.notebook.start_session(); + } + }, + "Don't restart": {} } + }); + + return false; + }; + + that.save_widget.update_document_title(); + knw.danger("Dead kernel", undefined, showMsg); + $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead'); + + showMsg(); + }); + + this.events.on('kernel_dead.Session', function (evt, info) { + var full = info.xhr.responseJSON.message; + var short = info.xhr.responseJSON.short_message || 'Kernel error'; + var traceback = info.xhr.responseJSON.traceback; + + var showMsg = function () { + var msg = $('
').append($('

').text(full)); + var cm, cm_elem, cm_open; + + if (traceback) { + cm_elem = $('

') + .css('margin-top', '1em') + .css('padding', '1em') + .addClass('output_scroll'); + msg.append(cm_elem); + cm = CodeMirror(cm_elem.get(0), { + mode: "python", + readOnly : true + }); + cm.setValue(traceback); + cm_open = $.proxy(cm.refresh, cm); } - }); + + dialog.kernel_modal({ + title: "Failed to start the kernel", + body : msg, + keyboard_manager: that.keyboard_manager, + notebook: that.notebook, + open: cm_open, + buttons : { + "Ok": { class: 'btn-primary' } + } + }); + + return false; + }; + + that.save_widget.update_document_title(); + $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead'); + knw.danger(short, undefined, showMsg); }); + this.events.on('kernel_starting.Kernel', function () { + window.document.title='(Starting) '+window.document.title; + $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy'); + knw.set_message("Kernel starting, please wait..."); + }); + + this.events.on('kernel_ready.Kernel', function () { + that.save_widget.update_document_title(); + $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle'); + knw.info("Kernel ready", 500); + }); + + this.events.on('kernel_idle.Kernel', function () { + that.save_widget.update_document_title(); + $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle'); + }); + + this.events.on('kernel_busy.Kernel', function () { + window.document.title='(Busy) '+window.document.title; + $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy'); + }); + + // Start the kernel indicator in the busy state, and send a kernel_info request. + // When the kernel_info reply arrives, the kernel is idle. + $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy'); + }; + /** + * Initialize the notification widget for notebook status messages. + * + * @method init_notebook_notification_widget + */ + NotebookNotificationArea.prototype.init_notebook_notification_widget = function () { var nnw = this.new_notification_widget('notebook'); // Notebook events @@ -211,8 +272,11 @@ define([ this.events.on('notebook_saved.Notebook', function () { nnw.set_message("Notebook saved",2000); }); - this.events.on('notebook_save_failed.Notebook', function (evt, xhr, status, data) { - nnw.warning(data || "Notebook save failed"); + this.events.on('notebook_save_failed.Notebook', function (evt, error) { + nnw.warning(error.message || "Notebook save failed"); + }); + this.events.on('notebook_copy_failed.Notebook', function (evt, error) { + nnw.warning(error.message || "Notebook copy failed"); }); // Checkpoint events @@ -247,10 +311,10 @@ define([ this.events.on('autosave_enabled.Notebook', function (evt, interval) { nnw.set_message("Saving every " + interval / 1000 + "s", 1000); }); - }; - IPython.NotificationArea = NotificationArea; - - return {'NotificationArea': NotificationArea}; + // Backwards compatibility. + IPython.NotificationArea = NotebookNotificationArea; + + return {'NotebookNotificationArea': NotebookNotificationArea}; }); diff --git a/IPython/html/static/notebook/js/outputarea.js b/IPython/html/static/notebook/js/outputarea.js index b3e11e5..bb9b218 100644 --- a/IPython/html/static/notebook/js/outputarea.js +++ b/IPython/html/static/notebook/js/outputarea.js @@ -81,7 +81,7 @@ define([ * */ OutputArea.prototype._should_scroll = function (lines) { - if (lines <=0 ){ return } + if (lines <=0 ){ return; } if (!lines) { lines = 100; } @@ -177,7 +177,7 @@ define([ OutputArea.prototype.scroll_if_long = function (lines) { var n = lines | OutputArea.minimum_scroll_threshold; if(n <= 0){ - return + return; } if (this._should_scroll(n)) { @@ -210,17 +210,17 @@ define([ var msg_type = json.output_type = msg.header.msg_type; var content = msg.content; if (msg_type === "stream") { - json.text = content.data; - json.stream = content.name; + json.text = content.text; + json.name = content.name; } else if (msg_type === "display_data") { - json = content.data; + json.data = content.data; json.output_type = msg_type; json.metadata = content.metadata; } else if (msg_type === "execute_result") { - json = content.data; + json.data = content.data; json.output_type = msg_type; json.metadata = content.metadata; - json.prompt_number = content.execution_count; + json.execution_count = content.execution_count; } else if (msg_type === "error") { json.ename = content.ename; json.evalue = content.evalue; @@ -233,16 +233,6 @@ define([ }; - OutputArea.prototype.rename_keys = function (data, key_map) { - var remapped = {}; - for (var key in data) { - var new_key = key_map[key] || key; - remapped[new_key] = data[key]; - } - return remapped; - }; - - OutputArea.output_types = [ 'application/javascript', 'text/html', @@ -255,14 +245,18 @@ define([ 'text/plain' ]; - OutputArea.prototype.validate_output = function (json) { - // scrub invalid outputs - // TODO: right now everything is a string, but JSON really shouldn't be. - // nbformat 4 will fix that. + OutputArea.prototype.validate_mimebundle = function (json) { + /** + * scrub invalid outputs + */ + var data = json.data; $.map(OutputArea.output_types, function(key){ - if (json[key] !== undefined && typeof json[key] !== 'string') { - console.log("Invalid type for " + key, json[key]); - delete json[key]; + if (key !== 'application/json' && + data[key] !== undefined && + typeof data[key] !== 'string' + ) { + console.log("Invalid type for " + key, data[key]); + delete data[key]; } }); return json; @@ -271,9 +265,6 @@ define([ OutputArea.prototype.append_output = function (json) { this.expand(); - // validate output data types - json = this.validate_output(json); - // Clear the output if clear is queued. var needs_height_reset = false; if (this.clear_queued) { @@ -282,14 +273,25 @@ define([ } var record_output = true; - - if (json.output_type === 'execute_result') { - this.append_execute_result(json); - } else if (json.output_type === 'error') { - this.append_error(json); - } else if (json.output_type === 'stream') { - // append_stream might have merged the output with earlier stream output - record_output = this.append_stream(json); + switch(json.output_type) { + case 'execute_result': + json = this.validate_mimebundle(json); + this.append_execute_result(json); + break; + case 'stream': + // append_stream might have merged the output with earlier stream output + record_output = this.append_stream(json); + break; + case 'error': + this.append_error(json); + break; + case 'display_data': + // append handled below + json = this.validate_mimebundle(json); + break; + default: + console.log("unrecognized output type: " + json.output_type); + this.append_unrecognized(json); } // We must release the animation fixed height in a callback since Gecko @@ -297,8 +299,10 @@ define([ // available. var that = this; var handle_appended = function ($el) { - // Only reset the height to automatic if the height is currently - // fixed (done by wait=True flag on clear_output). + /** + * Only reset the height to automatic if the height is currently + * fixed (done by wait=True flag on clear_output). + */ if (needs_height_reset) { that.element.height(''); } @@ -376,12 +380,14 @@ define([ } else { return subarea; } - } + }; OutputArea.prototype._append_javascript_error = function (err, element) { - // display a message when a javascript error occurs in display output - var msg = "Javascript error adding output!" + /** + * display a message when a javascript error occurs in display output + */ + var msg = "Javascript error adding output!"; if ( element === undefined ) return; element .append($('
').text(msg).addClass('js-error')) @@ -390,10 +396,12 @@ define([ }; OutputArea.prototype._safe_append = function (toinsert) { - // safely append an item to the document - // this is an object created by user code, - // and may have errors, which should not be raised - // under any circumstances. + /** + * safely append an item to the document + * this is an object created by user code, + * and may have errors, which should not be raised + * under any circumstances. + */ try { this.element.append(toinsert); } catch(err) { @@ -406,11 +414,14 @@ define([ this._append_javascript_error(err, subarea); this.element.append(toinsert); } + + // Notify others of changes. + this.element.trigger('changed'); }; OutputArea.prototype.append_execute_result = function (json) { - var n = json.prompt_number || ' '; + var n = json.execution_count || ' '; var toinsert = this.create_output_area(); if (this.prompt_area) { toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:'); @@ -421,9 +432,9 @@ define([ } this._safe_append(toinsert); // If we just output latex, typeset it. - if ((json['text/latex'] !== undefined) || - (json['text/html'] !== undefined) || - (json['text/markdown'] !== undefined)) { + if ((json.data['text/latex'] !== undefined) || + (json.data['text/html'] !== undefined) || + (json.data['text/markdown'] !== undefined)) { this.typeset(); } }; @@ -449,17 +460,12 @@ define([ OutputArea.prototype.append_stream = function (json) { - // temporary fix: if stream undefined (json file written prior to this patch), - // default to most likely stdout: - if (json.stream === undefined){ - json.stream = 'stdout'; - } var text = json.text; - var subclass = "output_"+json.stream; + var subclass = "output_"+json.name; if (this.outputs.length > 0){ // have at least one output to consider var last = this.outputs[this.outputs.length-1]; - if (last.output_type == 'stream' && json.stream == last.stream){ + if (last.output_type == 'stream' && json.name == last.name){ // latest output was in the same stream, // so append directly into its pre tag // escape ANSI & HTML specials: @@ -493,14 +499,31 @@ define([ }; + OutputArea.prototype.append_unrecognized = function (json) { + var that = this; + var toinsert = this.create_output_area(); + var subarea = $('
').addClass('output_subarea output_unrecognized'); + toinsert.append(subarea); + subarea.append( + $("") + .attr("href", "#") + .text("Unrecognized output: " + json.output_type) + .click(function () { + that.events.trigger('unrecognized_output.OutputArea', {output: json}) + }) + ); + this._safe_append(toinsert); + }; + + OutputArea.prototype.append_display_data = function (json, handle_inserted) { var toinsert = this.create_output_area(); if (this.append_mime_type(json, toinsert, handle_inserted)) { this._safe_append(toinsert); // If we just output latex, typeset it. - if ((json['text/latex'] !== undefined) || - (json['text/html'] !== undefined) || - (json['text/markdown'] !== undefined)) { + if ((json.data['text/latex'] !== undefined) || + (json.data['text/html'] !== undefined) || + (json.data['text/markdown'] !== undefined)) { this.typeset(); } } @@ -518,8 +541,8 @@ define([ for (var i=0; i < OutputArea.display_order.length; i++) { var type = OutputArea.display_order[i]; var append = OutputArea.append_map[type]; - if ((json[type] !== undefined) && append) { - var value = json[type]; + if ((json.data[type] !== undefined) && append) { + var value = json.data[type]; if (!this.trusted && !OutputArea.safe_outputs[type]) { // not trusted, sanitize HTML if (type==='text/html' || type==='text/svg') { @@ -563,16 +586,19 @@ define([ var text_and_math = mathjaxutils.remove_math(markdown); var text = text_and_math[0]; var math = text_and_math[1]; - var html = marked.parser(marked.lexer(text)); - html = mathjaxutils.replace_math(html, math); - toinsert.append(html); + marked(text, function (err, html) { + html = mathjaxutils.replace_math(html, math); + toinsert.append(html); + }); element.append(toinsert); return toinsert; }; var append_javascript = function (js, md, element) { - // We just eval the JS code, element appears in the local scope. + /** + * We just eval the JS code, element appears in the local scope. + */ var type = 'application/javascript'; var toinsert = this.create_output_subarea(md, "output_javascript", type); this.keyboard_manager.register_events(toinsert); @@ -636,14 +662,16 @@ define([ }; OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) { - // Add a resize handler to an element - // - // img: jQuery element - // immediately: bool=False - // Wait for the element to load before creating the handle. - // resize_parent: bool=True - // Should the parent of the element be resized when the element is - // reset (by double click). + /** + * Add a resize handler to an element + * + * img: jQuery element + * immediately: bool=False + * Wait for the element to load before creating the handle. + * resize_parent: bool=True + * Should the parent of the element be resized when the element is + * reset (by double click). + */ var callback = function (){ var h0 = img.height(); var w0 = img.width(); @@ -674,7 +702,9 @@ define([ }; var set_width_height = function (img, md, mime) { - // set width and height of an img element from metadata + /** + * set width and height of an img element from metadata + */ var height = _get_metadata_key(md, 'height', mime); if (height !== undefined) img.attr('height', height); var width = _get_metadata_key(md, 'width', mime); @@ -722,15 +752,17 @@ define([ var toinsert = this.create_output_subarea(md, "output_pdf", type); var a = $('').attr('href', 'data:application/pdf;base64,'+pdf); a.attr('target', '_blank'); - a.text('View PDF') + a.text('View PDF'); toinsert.append(a); element.append(toinsert); return toinsert; - } + }; var append_latex = function (latex, md, element) { - // This method cannot do the typesetting because the latex first has to - // be on the page. + /** + * This method cannot do the typesetting because the latex first has to + * be on the page. + */ var type = 'text/latex'; var toinsert = this.create_output_subarea(md, "output_latex", type); toinsert.append(latex); @@ -783,7 +815,7 @@ define([ // This seemed to be needed otherwise only the cell would be focused. // But with the modal UI, this seems to work fine with one call to focus(). raw_input.focus(); - } + }; OutputArea.prototype._submit_raw_input = function (evt) { var container = this.element.find("div.raw_input_container"); @@ -797,23 +829,25 @@ define([ } var content = { output_type : 'stream', - stream : 'stdout', + name : 'stdout', text : theprompt.text() + echo + '\n' - } + }; // remove form container container.parent().remove(); // replace with plaintext version in stdout this.append_output(content, false); this.events.trigger('send_input_reply.Kernel', value); - } + }; OutputArea.prototype.handle_clear_output = function (msg) { - // msg spec v4 had stdout, stderr, display keys - // v4.1 replaced these with just wait - // The default behavior is the same (stdout=stderr=display=True, wait=False), - // so v4 messages will still be properly handled, - // except for the rarely used clearing less than all output. + /** + * msg spec v4 had stdout, stderr, display keys + * v4.1 replaced these with just wait + * The default behavior is the same (stdout=stderr=display=True, wait=False), + * so v4 messages will still be properly handled, + * except for the rarely used clearing less than all output. + */ this.clear_output(msg.content.wait || false); }; @@ -824,7 +858,7 @@ define([ // If a clear is queued, clear before adding another to the queue. if (this.clear_queued) { this.clear_output(false); - }; + } this.clear_queued = true; } else { @@ -842,80 +876,47 @@ define([ // them to fire if the image is never added to the page. this.element.find('img').off('load'); this.element.html(""); + + // Notify others of changes. + this.element.trigger('changed'); + this.outputs = []; this.trusted = true; this.unscroll_area(); return; - }; + } }; // JSON serialization - OutputArea.prototype.fromJSON = function (outputs) { + OutputArea.prototype.fromJSON = function (outputs, metadata) { var len = outputs.length; - var data; + metadata = metadata || {}; for (var i=0; i').html(utils.fixCarriageReturn(utils.fixConsole(text)))); }; diff --git a/IPython/html/static/notebook/js/quickhelp.js b/IPython/html/static/notebook/js/quickhelp.js index 4bc179a..b6b1b01 100644 --- a/IPython/html/static/notebook/js/quickhelp.js +++ b/IPython/html/static/notebook/js/quickhelp.js @@ -11,14 +11,16 @@ define([ var platform = utils.platform; var QuickHelp = function (options) { - // Constructor - // - // Parameters: - // options: dictionary - // Dictionary of keyword arguments. - // events: $(Events) instance - // keyboard_manager: KeyboardManager instance - // notebook: Notebook instance + /** + * Constructor + * + * Parameters: + * options: dictionary + * Dictionary of keyword arguments. + * events: $(Events) instance + * keyboard_manager: KeyboardManager instance + * notebook: Notebook instance + */ this.keyboard_manager = options.keyboard_manager; this.notebook = options.notebook; this.keyboard_manager.quick_help = this; @@ -34,10 +36,10 @@ define([ platform_specific = [ { shortcut: "Cmd-Up", help:"go to cell start" }, { shortcut: "Cmd-Down", help:"go to cell end" }, - { shortcut: "Opt-Left", help:"go one word left" }, - { shortcut: "Opt-Right", help:"go one word right" }, - { shortcut: "Opt-Backspace", help:"del word before" }, - { shortcut: "Opt-Delete", help:"del word after" }, + { shortcut: "Alt-Left", help:"go one word left" }, + { shortcut: "Alt-Right", help:"go one word right" }, + { shortcut: "Alt-Backspace", help:"del word before" }, + { shortcut: "Alt-Delete", help:"del word after" }, ]; } else { // PC specific @@ -65,12 +67,10 @@ define([ ].concat( platform_specific ); - - - - QuickHelp.prototype.show_keyboard_shortcuts = function () { - // toggles display of keyboard shortcut dialog + /** + * toggles display of keyboard shortcut dialog + */ var that = this; if ( this.force_rebuild ) { this.shortcut_dialog.remove(); @@ -139,7 +139,9 @@ define([ keys[i] = "" + k + ""; continue; // leave individual keys lower-cased } - keys[i] = ( special_case[k] ? special_case[k] : k.charAt(0).toUpperCase() + k.slice(1) ); + if (k.indexOf(',') === -1){ + keys[i] = ( special_case[k] ? special_case[k] : k.charAt(0).toUpperCase() + k.slice(1) ); + } keys[i] = "" + keys[i] + ""; } return keys.join('-'); diff --git a/IPython/html/static/notebook/js/savewidget.js b/IPython/html/static/notebook/js/savewidget.js index 72cddf9..d69b32c 100644 --- a/IPython/html/static/notebook/js/savewidget.js +++ b/IPython/html/static/notebook/js/savewidget.js @@ -12,7 +12,9 @@ define([ "use strict"; var SaveWidget = function (selector, options) { - // TODO: Remove circular ref. + /** + * TODO: Remove circular ref. + */ this.notebook = undefined; this.selector = selector; this.events = options.events; @@ -28,7 +30,7 @@ define([ SaveWidget.prototype.bind_events = function () { var that = this; this.element.find('span#notebook_name').click(function () { - that.rename_notebook(); + that.rename_notebook({notebook: that.notebook}); }); this.events.on('notebook_loaded.Notebook', function () { that.update_notebook_name(); @@ -46,6 +48,11 @@ define([ this.events.on('notebook_save_failed.Notebook', function () { that.set_save_status('Autosave Failed!'); }); + this.events.on('notebook_read_only.Notebook', function () { + that.set_save_status('(read only)'); + // disable future set_save_status + that.set_save_status = function () {}; + }); this.events.on('checkpoints_listed.Notebook', function (event, data) { that._set_last_checkpoint(data[0]); }); @@ -69,41 +76,53 @@ define([ $("
") ).append( $('').attr('type','text').attr('size','25').addClass('form-control') - .val(that.notebook.get_notebook_name()) + .val(options.notebook.get_notebook_name()) ); - dialog.modal({ + var d = dialog.modal({ title: "Rename Notebook", body: dialog_body, notebook: options.notebook, keyboard_manager: this.keyboard_manager, buttons : { - "Cancel": {}, "OK": { class: "btn-primary", click: function () { - var new_name = $(this).find('input').val(); - if (!that.notebook.test_notebook_name(new_name)) { - $(this).find('.rename-message').text( - "Invalid notebook name. Notebook names must "+ - "have 1 or more characters and can contain any characters " + - "except :/\\. Please enter a new notebook name:" - ); - return false; - } else { - that.notebook.rename(new_name); + var new_name = d.find('input').val(); + if (!options.notebook.test_notebook_name(new_name)) { + d.find('.rename-message').text( + "Invalid notebook name. Notebook names must "+ + "have 1 or more characters and can contain any characters " + + "except :/\\. Please enter a new notebook name:" + ); + return false; + } else { + d.find('.rename-message').text("Renaming..."); + d.find('input[type="text"]').prop('disabled', true); + that.notebook.rename(new_name).then( + function () { + d.modal('hide'); + }, function (error) { + d.find('.rename-message').text(error.message || 'Unknown error'); + d.find('input[type="text"]').prop('disabled', false).focus().select(); + } + ); + return false; + } } - }} }, - open : function (event, ui) { - var that = $(this); - // Upon ENTER, click the OK button. - that.find('input[type="text"]').keydown(function (event, ui) { + "Cancel": {} + }, + open : function () { + /** + * Upon ENTER, click the OK button. + */ + d.find('input[type="text"]').keydown(function (event) { if (event.which === keyboard.keycodes.enter) { - that.find('.btn-primary').first().click(); + d.find('.btn-primary').first().click(); return false; } }); - that.find('input[type="text"]').focus().select(); + d.find('input[type="text"]').focus().select(); } }); }; @@ -122,14 +141,12 @@ define([ SaveWidget.prototype.update_address_bar = function(){ var base_url = this.notebook.base_url; - var nbname = this.notebook.notebook_name; var path = this.notebook.notebook_path; - var state = {path : path, name: nbname}; + var state = {path : path}; window.history.replaceState(state, "", utils.url_join_encode( base_url, "notebooks", - path, - nbname) + path) ); }; @@ -193,13 +210,15 @@ define([ var that = this; var recall = function(t){ - // recall slightly later (1s) as long timeout in js might be imprecise, - // and you want to be call **after** the change of formatting should happend. + /** + * recall slightly later (1s) as long timeout in js might be imprecise, + * and you want to be call **after** the change of formatting should happend. + */ return setTimeout( $.proxy(that._regularly_update_checkpoint_date, that), t + 1000 ); - } + }; var tdelta = Math.ceil(new Date()-this._checkpoint_date); // update regularly for the first 6hours and show diff --git a/IPython/html/static/notebook/js/scrollmanager.js b/IPython/html/static/notebook/js/scrollmanager.js index a93dcbc..daebf23 100644 --- a/IPython/html/static/notebook/js/scrollmanager.js +++ b/IPython/html/static/notebook/js/scrollmanager.js @@ -4,47 +4,57 @@ define(['jquery'], function($){ "use strict"; var ScrollManager = function(notebook, options) { - // Public constructor. + /** + * Public constructor. + */ this.notebook = notebook; options = options || {}; this.animation_speed = options.animation_speed || 250; //ms }; ScrollManager.prototype.scroll = function (delta) { - // Scroll the document. - // - // Parameters - // ---------- - // delta: integer - // direction to scroll the document. Positive is downwards. - // Unit is one page length. + /** + * Scroll the document. + * + * Parameters + * ---------- + * delta: integer + * direction to scroll the document. Positive is downwards. + * Unit is one page length. + */ this.scroll_some(delta); return false; }; ScrollManager.prototype.scroll_to = function(selector) { - // Scroll to an element in the notebook. + /** + * Scroll to an element in the notebook. + */ $('#notebook').animate({'scrollTop': $(selector).offset().top + $('#notebook').scrollTop() - $('#notebook').offset().top}, this.animation_speed); }; ScrollManager.prototype.scroll_some = function(pages) { - // Scroll up or down a given number of pages. - // - // Parameters - // ---------- - // pages: integer - // number of pages to scroll the document, may be positive or negative. + /** + * Scroll up or down a given number of pages. + * + * Parameters + * ---------- + * pages: integer + * number of pages to scroll the document, may be positive or negative. + */ $('#notebook').animate({'scrollTop': $('#notebook').scrollTop() + pages * $('#notebook').height()}, this.animation_speed); }; ScrollManager.prototype.get_first_visible_cell = function() { - // Gets the index of the first visible cell in the document. - - // First, attempt to be smart by guessing the index of the cell we are - // scrolled to. Then, walk from there up or down until the right cell - // is found. To guess the index, get the top of the last cell, and - // divide that by the number of cells to get an average cell height. - // Then divide the scroll height by the average cell height. + /** + * Gets the index of the first visible cell in the document. + * + * First, attempt to be smart by guessing the index of the cell we are + * scrolled to. Then, walk from there up or down until the right cell + * is found. To guess the index, get the top of the last cell, and + * divide that by the number of cells to get an average cell height. + * Then divide the scroll height by the average cell height. + */ var cell_count = this.notebook.ncells(); var first_cell_top = this.notebook.get_cell(0).element.offset().top; var last_cell_top = this.notebook.get_cell(cell_count-1).element.offset().top; @@ -65,34 +75,40 @@ define(['jquery'], function($){ var TargetScrollManager = function(notebook, options) { - // Public constructor. + /** + * Public constructor. + */ ScrollManager.apply(this, [notebook, options]); }; - TargetScrollManager.prototype = new ScrollManager(); + TargetScrollManager.prototype = Object.create(ScrollManager.prototype); TargetScrollManager.prototype.is_target = function (index) { - // Check if a cell should be a scroll stop. - // - // Returns `true` if the cell is a cell that the scroll manager - // should scroll to. Otherwise, false is returned. - // - // Parameters - // ---------- - // index: integer - // index of the cell to test. + /** + * Check if a cell should be a scroll stop. + * + * Returns `true` if the cell is a cell that the scroll manager + * should scroll to. Otherwise, false is returned. + * + * Parameters + * ---------- + * index: integer + * index of the cell to test. + */ return false; }; TargetScrollManager.prototype.scroll = function (delta) { - // Scroll the document. - // - // Parameters - // ---------- - // delta: integer - // direction to scroll the document. Positive is downwards. - // Units are targets. - - // Try to scroll to the next slide. + /** + * Scroll the document. + * + * Parameters + * ---------- + * delta: integer + * direction to scroll the document. Positive is downwards. + * Units are targets. + * + * Try to scroll to the next slide. + */ var cell_count = this.notebook.ncells(); var selected_index = this.get_first_visible_cell() + delta; while (0 <= selected_index && selected_index < cell_count && !this.is_target(selected_index)) { @@ -111,10 +127,12 @@ define(['jquery'], function($){ var SlideScrollManager = function(notebook, options) { - // Public constructor. + /** + * Public constructor. + */ TargetScrollManager.apply(this, [notebook, options]); }; - SlideScrollManager.prototype = new TargetScrollManager(); + SlideScrollManager.prototype = Object.create(TargetScrollManager.prototype); SlideScrollManager.prototype.is_target = function (index) { var cell = this.notebook.get_cell(index); @@ -126,24 +144,28 @@ define(['jquery'], function($){ var HeadingScrollManager = function(notebook, options) { - // Public constructor. + /** + * Public constructor. + */ ScrollManager.apply(this, [notebook, options]); options = options || {}; this._level = options.heading_level || 1; }; - HeadingScrollManager.prototype = new ScrollManager(); + HeadingScrollManager.prototype = Object.create(ScrollManager.prototype) HeadingScrollManager.prototype.scroll = function (delta) { - // Scroll the document. - // - // Parameters - // ---------- - // delta: integer - // direction to scroll the document. Positive is downwards. - // Units are headers. - - // Get all of the header elements that match the heading level or are of - // greater magnitude (a smaller header number). + /** + * Scroll the document. + * + * Parameters + * ---------- + * delta: integer + * direction to scroll the document. Positive is downwards. + * Units are headers. + * + * Get all of the header elements that match the heading level or are of + * greater magnitude (a smaller header number). + */ var headers = $(); var i; for (i = 1; i <= this._level; i++) { diff --git a/IPython/html/static/notebook/js/textcell.js b/IPython/html/static/notebook/js/textcell.js index 4264ebf..cf3feed 100644 --- a/IPython/html/static/notebook/js/textcell.js +++ b/IPython/html/static/notebook/js/textcell.js @@ -10,23 +10,28 @@ define([ 'notebook/js/mathjaxutils', 'notebook/js/celltoolbar', 'components/marked/lib/marked', -], function(IPython, utils, $, cell, security, mathjaxutils, celltoolbar, marked) { + 'codemirror/lib/codemirror', + 'codemirror/mode/gfm/gfm', + 'notebook/js/codemirror-ipythongfm' +], function(IPython,utils , $, cell, security, mathjaxutils, celltoolbar, marked, CodeMirror, gfm, ipgfm) { "use strict"; var Cell = cell.Cell; var TextCell = function (options) { - // Constructor - // - // Construct a new TextCell, codemirror mode is by default 'htmlmixed', - // and cell type is 'text' cell start as not redered. - // - // Parameters: - // options: dictionary - // Dictionary of keyword arguments. - // events: $(Events) instance - // config: dictionary - // keyboard_manager: KeyboardManager instance - // notebook: Notebook instance + /** + * Constructor + * + * Construct a new TextCell, codemirror mode is by default 'htmlmixed', + * and cell type is 'text' cell start as not redered. + * + * Parameters: + * options: dictionary + * Dictionary of keyword arguments. + * events: $(Events) instance + * config: dictionary + * keyboard_manager: KeyboardManager instance + * notebook: Notebook instance + */ options = options || {}; // in all TextCell/Cell subclasses @@ -38,10 +43,7 @@ define([ this.config = options.config; // we cannot put this as a class key as it has handle to "this". - var cm_overwrite_options = { - onKeyEvent: $.proxy(this.handle_keyevent,this) - }; - var config = utils.mergeopt(TextCell, this.config, {cm_config:cm_overwrite_options}); + var config = utils.mergeopt(TextCell, this.config); Cell.apply(this, [{ config: config, keyboard_manager: options.keyboard_manager, @@ -52,7 +54,7 @@ define([ this.rendered = false; }; - TextCell.prototype = new Cell(); + TextCell.prototype = Object.create(Cell.prototype); TextCell.options_default = { cm_config : { @@ -83,6 +85,7 @@ define([ inner_cell.append(this.celltoolbar.element); var input_area = $('
').addClass('input_area'); this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config); + this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this)) // The tabindex=-1 makes this div focusable. var render_area = $('
').addClass('text_cell_render rendered_html') .attr('tabindex','-1'); @@ -92,27 +95,6 @@ define([ }; - /** - * Bind the DOM evet to cell actions - * Need to be called after TextCell.create_element - * @private - * @method bind_event - */ - TextCell.prototype.bind_events = function () { - Cell.prototype.bind_events.apply(this); - var that = this; - - this.element.dblclick(function () { - if (that.selected === false) { - this.events.trigger('select.Cell', {'cell':that}); - } - var cont = that.unrender(); - if (cont) { - that.focus_editor(); - } - }); - }; - // Cell level actions TextCell.prototype.select = function () { @@ -131,8 +113,6 @@ define([ if (cont) { var text_cell = this.element; var output = text_cell.find("div.text_cell_render"); - output.hide(); - text_cell.find('div.input_area').show(); if (this.get_text() === this.placeholder) { this.set_text(''); } @@ -217,15 +197,17 @@ define([ var MarkdownCell = function (options) { - // Constructor - // - // Parameters: - // options: dictionary - // Dictionary of keyword arguments. - // events: $(Events) instance - // config: dictionary - // keyboard_manager: KeyboardManager instance - // notebook: Notebook instance + /** + * Constructor + * + * Parameters: + * options: dictionary + * Dictionary of keyword arguments. + * events: $(Events) instance + * config: dictionary + * keyboard_manager: KeyboardManager instance + * notebook: Notebook instance + */ options = options || {}; var config = utils.mergeopt(MarkdownCell, options.config); TextCell.apply(this, [$.extend({}, options, {config: config})]); @@ -240,7 +222,22 @@ define([ placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$" }; - MarkdownCell.prototype = new TextCell(); + MarkdownCell.prototype = Object.create(TextCell.prototype); + + MarkdownCell.prototype.set_heading_level = function (level) { + /** + * make a markdown cell a heading + */ + level = level || 1; + var source = this.get_text(); + source = source.replace(/^(#*)\s?/, + new Array(level + 1).join('#') + ' '); + this.set_text(source); + this.refresh(); + if (this.rendered) { + this.render(); + } + }; /** * @method render @@ -248,43 +245,56 @@ define([ MarkdownCell.prototype.render = function () { var cont = TextCell.prototype.render.apply(this); if (cont) { + var that = this; var text = this.get_text(); var math = null; if (text === "") { text = this.placeholder; } var text_and_math = mathjaxutils.remove_math(text); text = text_and_math[0]; math = text_and_math[1]; - var html = marked.parser(marked.lexer(text)); - html = mathjaxutils.replace_math(html, math); - html = security.sanitize_html(html); - html = $($.parseHTML(html)); - // links in markdown cells should open in new tabs - html.find("a[href]").not('[href^="#"]').attr("target", "_blank"); - this.set_rendered(html); - this.element.find('div.input_area').hide(); - this.element.find("div.text_cell_render").show(); - this.typeset(); + marked(text, function (err, html) { + html = mathjaxutils.replace_math(html, math); + html = security.sanitize_html(html); + html = $($.parseHTML(html)); + // add anchors to headings + html.find(":header").addBack(":header").each(function (i, h) { + h = $(h); + var hash = h.text().replace(/ /g, '-'); + h.attr('id', hash); + h.append( + $('') + .addClass('anchor-link') + .attr('href', '#' + hash) + .text('¶') + ); + }); + // links in markdown cells should open in new tabs + html.find("a[href]").not('[href^="#"]').attr("target", "_blank"); + that.set_rendered(html); + that.typeset(); + that.events.trigger("rendered.MarkdownCell", {cell: that}); + }); } return cont; }; var RawCell = function (options) { - // Constructor - // - // Parameters: - // options: dictionary - // Dictionary of keyword arguments. - // events: $(Events) instance - // config: dictionary - // keyboard_manager: KeyboardManager instance - // notebook: Notebook instance + /** + * Constructor + * + * Parameters: + * options: dictionary + * Dictionary of keyword arguments. + * events: $(Events) instance + * config: dictionary + * keyboard_manager: KeyboardManager instance + * notebook: Notebook instance + */ options = options || {}; var config = utils.mergeopt(RawCell, options.config); TextCell.apply(this, [$.extend({}, options, {config: config})]); - // RawCell should always hide its rendered div - this.element.find('div.text_cell_render').hide(); this.cell_type = 'raw'; }; @@ -294,7 +304,7 @@ define([ "When passing through nbconvert, a Raw Cell's content is added to the output unmodified." }; - RawCell.prototype = new TextCell(); + RawCell.prototype = Object.create(TextCell.prototype); /** @method bind_events **/ RawCell.prototype.bind_events = function () { @@ -328,123 +338,15 @@ define([ return cont; }; - - var HeadingCell = function (options) { - // Constructor - // - // Parameters: - // options: dictionary - // Dictionary of keyword arguments. - // events: $(Events) instance - // config: dictionary - // keyboard_manager: KeyboardManager instance - // notebook: Notebook instance - options = options || {}; - var config = utils.mergeopt(HeadingCell, options.config); - TextCell.apply(this, [$.extend({}, options, {config: config})]); - - this.level = 1; - this.cell_type = 'heading'; - }; - - HeadingCell.options_default = { - cm_config: { - theme: 'heading-1' - }, - placeholder: "Type Heading Here" - }; - - HeadingCell.prototype = new TextCell(); - - /** @method fromJSON */ - HeadingCell.prototype.fromJSON = function (data) { - if (data.level !== undefined){ - this.level = data.level; - } - TextCell.prototype.fromJSON.apply(this, arguments); - this.code_mirror.setOption("theme", "heading-"+this.level); - }; - - - /** @method toJSON */ - HeadingCell.prototype.toJSON = function () { - var data = TextCell.prototype.toJSON.apply(this); - data.level = this.get_level(); - return data; - }; - - /** - * Change heading level of cell, and re-render - * @method set_level - */ - HeadingCell.prototype.set_level = function (level) { - this.level = level; - this.code_mirror.setOption("theme", "heading-"+level); - - if (this.rendered) { - this.rendered = false; - this.render(); - } - }; - - /** The depth of header cell, based on html (h1 to h6) - * @method get_level - * @return {integer} level - for 1 to 6 - */ - HeadingCell.prototype.get_level = function () { - return this.level; - }; - - - HeadingCell.prototype.get_rendered = function () { - var r = this.element.find("div.text_cell_render"); - return r.children().first().html(); - }; - - HeadingCell.prototype.render = function () { - var cont = TextCell.prototype.render.apply(this); - if (cont) { - var text = this.get_text(); - var math = null; - // Markdown headings must be a single line - text = text.replace(/\n/g, ' '); - if (text === "") { text = this.placeholder; } - text = new Array(this.level + 1).join("#") + " " + text; - var text_and_math = mathjaxutils.remove_math(text); - text = text_and_math[0]; - math = text_and_math[1]; - var html = marked.parser(marked.lexer(text)); - html = mathjaxutils.replace_math(html, math); - html = security.sanitize_html(html); - var h = $($.parseHTML(html)); - // add id and linkback anchor - var hash = h.text().trim().replace(/ /g, '-'); - h.attr('id', hash); - h.append( - $('') - .addClass('anchor-link') - .attr('href', '#' + hash) - .text('¶') - ); - this.set_rendered(h); - this.element.find('div.input_area').hide(); - this.element.find("div.text_cell_render").show(); - this.typeset(); - } - return cont; - }; - // Backwards compatability. IPython.TextCell = TextCell; IPython.MarkdownCell = MarkdownCell; IPython.RawCell = RawCell; - IPython.HeadingCell = HeadingCell; var textcell = { - 'TextCell': TextCell, - 'MarkdownCell': MarkdownCell, - 'RawCell': RawCell, - 'HeadingCell': HeadingCell, + TextCell: TextCell, + MarkdownCell: MarkdownCell, + RawCell: RawCell }; return textcell; }); diff --git a/IPython/html/static/notebook/js/tooltip.js b/IPython/html/static/notebook/js/tooltip.js index bec9b41..b60cfea 100644 --- a/IPython/html/static/notebook/js/tooltip.js +++ b/IPython/html/static/notebook/js/tooltip.js @@ -116,12 +116,10 @@ define([ }; Tooltip.prototype.showInPager = function (cell) { - // reexecute last call in pager by appending ? to show back in pager - var that = this; - var payload = {}; - payload.text = that._reply.content.data['text/plain']; - - this.events.trigger('open_with_text.Pager', payload); + /** + * reexecute last call in pager by appending ? to show back in pager + */ + this.events.trigger('open_with_text.Pager', this._reply.content); this.remove_and_cancel_tooltip(); }; @@ -148,9 +146,11 @@ define([ // return true on successfully removing a visible tooltip; otherwise return // false. Tooltip.prototype.remove_and_cancel_tooltip = function (force) { - // note that we don't handle closing directly inside the calltip - // as in the completer, because it is not focusable, so won't - // get the event. + /** + * note that we don't handle closing directly inside the calltip + * as in the completer, because it is not focusable, so won't + * get the event. + */ this.cancel_pending(); if (!this._hidden) { if (force || !this._sticky) { @@ -183,39 +183,18 @@ define([ // easy access for julia monkey patching. Tooltip.last_token_re = /[a-z_][0-9a-z._]*$/gi; - Tooltip.prototype.extract_oir_token = function(line){ - // use internally just to make the request to the kernel - // Feel free to shorten this logic if you are better - // than me in regEx - // basicaly you shoul be able to get xxx.xxx.xxx from - // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2, - // remove everything between matchin bracket (need to iterate) - var matchBracket = /\([^\(\)]+\)/g; - var endBracket = /\([^\(]*$/g; - var oldline = line; - - line = line.replace(matchBracket, ""); - while (oldline != line) { - oldline = line; - line = line.replace(matchBracket, ""); - } - // remove everything after last open bracket - line = line.replace(endBracket, ""); - // reset the regex object - Tooltip.last_token_re.lastIndex = 0; - return Tooltip.last_token_re.exec(line); - }; - Tooltip.prototype._request_tooltip = function (cell, text, cursor_pos) { var callbacks = $.proxy(this._show, this); var msg_id = cell.kernel.inspect(text, cursor_pos, callbacks); }; - // make an imediate completion request + // make an immediate completion request Tooltip.prototype.request = function (cell, hide_if_no_docstring) { - // request(codecell) - // Deal with extracting the text from the cell and counting - // call in a row + /** + * request(codecell) + * Deal with extracting the text from the cell and counting + * call in a row + */ this.cancel_pending(); var editor = cell.code_mirror; var cursor = editor.getCursor(); @@ -225,10 +204,11 @@ define([ this._hide_if_no_docstring = hide_if_no_docstring; if(editor.somethingSelected()){ + // get only the most recent selection. text = editor.getSelection(); } - // need a permanent handel to code_mirror for future auto recall + // need a permanent handle to code_mirror for future auto recall this.code_mirror = editor; // now we treat the different number of keypress @@ -276,8 +256,10 @@ define([ // should be called with the kernel reply to actually show the tooltip Tooltip.prototype._show = function (reply) { - // move the bubble if it is not hidden - // otherwise fade it + /** + * move the bubble if it is not hidden + * otherwise fade it + */ this._reply = reply; var content = reply.content; if (!content.found) { @@ -328,7 +310,7 @@ define([ this.text.scrollTop(0); }; - // Backwards compatability. + // Backwards compatibility. IPython.Tooltip = Tooltip; return {'Tooltip': Tooltip}; diff --git a/IPython/html/static/notebook/js/tour.js b/IPython/html/static/notebook/js/tour.js index dcd4e34..718938b 100644 --- a/IPython/html/static/notebook/js/tour.js +++ b/IPython/html/static/notebook/js/tour.js @@ -11,13 +11,13 @@ define([ var tour_style = "
\n" + "
\n" + "
\n" + - "\n" + + "\n" + "

\n" + "
\n" + "
\n" + - "\n" + - "\n" + - "\n" + + "\n" + + "\n" + + "\n" + "
\n" + "
"; @@ -31,7 +31,7 @@ define([ title: "Welcome to the Notebook Tour", placement: 'bottom', orphan: true, - content: "You can use the left and right arrow keys to go backwards and forwards.", + content: "You can use the left and right arrow keys to go backwards and forwards." }, { element: "#notebook_name", title: "Filename", @@ -91,31 +91,31 @@ define([ element: "#kernel_indicator", title: "Kernel Indicator", placement: 'bottom', - onShow: function(tour) { events.trigger('status_idle.Kernel');}, - content: "This is the Kernel indicator. It looks like this when the Kernel is idle.", + onShow: function(tour) { events.trigger('kernel_idle.Kernel');}, + content: "This is the Kernel indicator. It looks like this when the Kernel is idle." }, { element: "#kernel_indicator", title: "Kernel Indicator", placement: 'bottom', - onShow: function(tour) { events.trigger('status_busy.Kernel'); }, - content: "The Kernel indicator looks like this when the Kernel is busy.", + onShow: function(tour) { events.trigger('kernel_busy.Kernel'); }, + content: "The Kernel indicator looks like this when the Kernel is busy." }, { - element: ".icon-stop", + element: ".fa-stop", placement: 'bottom', title: "Interrupting the Kernel", - onHide: function(tour) { events.trigger('status_idle.Kernel'); }, + onHide: function(tour) { events.trigger('kernel_idle.Kernel'); }, content: "To cancel a computation in progress, you can click here." }, { element: "#notification_kernel", placement: 'bottom', - onShow: function(tour) { $('.icon-stop').click(); }, + onShow: function(tour) { $('.fa-stop').click(); }, title: "Notification Area", content: "Messages in response to user actions (Save, Interrupt, etc) appear here." }, { title: "Fin.", placement: 'bottom', orphan: true, - content: "This concludes the IPython Notebook User Interface tour.Tour. Happy hacking!", + content: "This concludes the IPython Notebook User Interface Tour. Happy hacking!" } ]; @@ -156,7 +156,7 @@ define([ }; NotebookTour.prototype.toggle_pause_play = function () { - $('#tour-pause').toggleClass('icon-pause icon-play'); + $('#tour-pause').toggleClass('fa-pause fa-play'); }; NotebookTour.prototype.edit_mode = function() { diff --git a/IPython/html/static/notebook/less/ansicolors.less b/IPython/html/static/notebook/less/ansicolors.less index 05009fb..7f15301 100644 --- a/IPython/html/static/notebook/less/ansicolors.less +++ b/IPython/html/static/notebook/less/ansicolors.less @@ -1,13 +1,12 @@ /* CSS font colors for translated ANSI colors. */ - .ansibold {font-weight: bold;} /* use dark versions for foreground, to improve visibility */ .ansiblack {color: black;} .ansired {color: darkred;} .ansigreen {color: darkgreen;} -.ansiyellow {color: brown;} +.ansiyellow {color: #c4a000;} .ansiblue {color: darkblue;} .ansipurple {color: darkviolet;} .ansicyan {color: steelblue;} @@ -22,4 +21,3 @@ .ansibgpurple {background-color: magenta;} .ansibgcyan {background-color: cyan;} .ansibggray {background-color: gray;} - diff --git a/IPython/html/static/notebook/less/cell.less b/IPython/html/static/notebook/less/cell.less index 8bf3f85..ed4ad4a 100644 --- a/IPython/html/static/notebook/less/cell.less +++ b/IPython/html/static/notebook/less/cell.less @@ -61,3 +61,34 @@ div.prompt:empty { padding-top: 0; padding-bottom: 0; } + +div.unrecognized_cell { + // from text_cell + padding: 5px 5px 5px 0px; + .hbox(); + + .inner_cell { + .border-radius(@border-radius-base); + padding: 5px; + font-weight: bold; + color: red; + border: 1px solid @light_border_color; + background: darken(@cell_background, 5%); + // remove decoration from link + a { + color: inherit; + text-decoration: none; + + &:hover { + color: inherit; + text-decoration: none; + } + } + } +} +@media (max-width: 480px) { + // remove prompt indentation on small screens + div.unrecognized_cell > div.prompt { + display: none; + } +} diff --git a/IPython/html/static/notebook/less/celltoolbar.less b/IPython/html/static/notebook/less/celltoolbar.less index b19577f..295aaf1 100644 --- a/IPython/html/static/notebook/less/celltoolbar.less +++ b/IPython/html/static/notebook/less/celltoolbar.less @@ -5,13 +5,13 @@ border: thin solid #CFCFCF; border-bottom: none; background : #EEE; - border-radius : 3px 3px 0px 0px; + border-radius : @border-radius-base @border-radius-base 0px 0px; width:100%; -webkit-box-pack: end; height: @celltoolbar-height; padding-right: 4px; .hbox(); - .reverse(); + .end(); } .ctb_hideshow { diff --git a/IPython/html/static/notebook/less/menubar.less b/IPython/html/static/notebook/less/menubar.less index 69ea262..63e784a 100644 --- a/IPython/html/static/notebook/less/menubar.less +++ b/IPython/html/static/notebook/less/menubar.less @@ -8,8 +8,14 @@ border-top: 1px; border-radius: 0px 0px @border-radius-base @border-radius-base; } - - + + .navbar-toggle { + float: left; + } + .navbar-collapse { + clear: left; + } + li.dropdown { line-height: 12px; @@ -21,6 +27,7 @@ ul.navbar-right { padding-top: 2px; + margin-bottom: 0px; } } diff --git a/IPython/html/static/notebook/less/outputarea.less b/IPython/html/static/notebook/less/outputarea.less index 033359b..07f8276 100644 --- a/IPython/html/static/notebook/less/outputarea.less +++ b/IPython/html/static/notebook/less/outputarea.less @@ -172,3 +172,19 @@ input.raw_input:focus { p.p-space { margin-bottom: 10px; } + +div.output_unrecognized { + padding: 5px; + font-weight: bold; + color: red; + // remove decoration from link + a { + color: inherit; + text-decoration: none; + + &:hover { + color: inherit; + text-decoration: none; + } + } +} \ No newline at end of file diff --git a/IPython/html/static/notebook/less/terminal.less b/IPython/html/static/notebook/less/terminal.less new file mode 100644 index 0000000..f20cefa --- /dev/null +++ b/IPython/html/static/notebook/less/terminal.less @@ -0,0 +1,17 @@ +.terminal { + float: left; + border: black solid 5px; + font-family: "DejaVu Sans Mono", "Liberation Mono", monospace; + font-size: 11px; + color: white; + background: black; +} + +.terminal-cursor { + color: black; + background: white; +} + +#terminado-container { + margin: 8px; +} diff --git a/IPython/html/static/notebook/less/textcell.less b/IPython/html/static/notebook/less/textcell.less index 67e14ad..4f6b0c7 100644 --- a/IPython/html/static/notebook/less/textcell.less +++ b/IPython/html/static/notebook/less/textcell.less @@ -32,29 +32,33 @@ h1,h2,h3,h4,h5,h6 { } } -div.cell.text_cell.rendered { - padding: 0px; +.text_cell.rendered .input_area { + display: none; } -.cm-s-heading-1, -.cm-s-heading-2, -.cm-s-heading-3, -.cm-s-heading-4, -.cm-s-heading-5, -.cm-s-heading-6 { +.text_cell.unrendered .text_cell_render { + display:none; +} + +.cm-header-1, +.cm-header-2, +.cm-header-3, +.cm-header-4, +.cm-header-5, +.cm-header-6 { font-weight: bold; font-family: @font-family-sans-serif; } -.cm-s-heading-1 { font-size:150%; } -.cm-s-heading-2 { font-size: 130%; } -.cm-s-heading-3 { font-size: 120%; } -.cm-s-heading-4 { font-size: 110%; } -.cm-s-heading-5 { - font-size: 100%; +.cm-header-1 { font-size: 185.7%; } +.cm-header-2 { font-size: 157.1%; } +.cm-header-3 { font-size: 128.6%; } +.cm-header-4 { font-size: 110%; } +.cm-header-5 { + font-size: 100%; font-style: italic; } -.cm-s-heading-6 { - font-size: 90%; +.cm-header-6 { + font-size: 100%; font-style: italic; } diff --git a/IPython/html/static/services/config.js b/IPython/html/static/services/config.js new file mode 100644 index 0000000..a3632f7 --- /dev/null +++ b/IPython/html/static/services/config.js @@ -0,0 +1,67 @@ +// Copyright (c) IPython Development Team. +// Distributed under the terms of the Modified BSD License. + +define([ + 'jquery', + 'base/js/utils', + ], +function($, utils) { + var ConfigSection = function(section_name, options) { + this.section_name = section_name; + this.base_url = options.base_url; + this.data = {}; + + var that = this; + + /* .loaded is a promise, fulfilled the first time the config is loaded + * from the server. Code can do: + * conf.loaded.then(function() { ... using conf.data ... }); + */ + this._one_load_finished = false; + this.loaded = new Promise(function(resolve, reject) { + that._finish_firstload = resolve; + }); + }; + + ConfigSection.prototype.api_url = function() { + return utils.url_join_encode(this.base_url, 'api/config', this.section_name); + }; + + ConfigSection.prototype._load_done = function() { + if (!this._one_load_finished) { + this._one_load_finished = true; + this._finish_firstload(); + } + }; + + ConfigSection.prototype.load = function() { + var that = this; + return utils.promising_ajax(this.api_url(), { + cache: false, + type: "GET", + dataType: "json", + }).then(function(data) { + that.data = data; + that._load_done(); + return data; + }); + }; + + ConfigSection.prototype.update = function(newdata) { + var that = this; + return utils.promising_ajax(this.api_url(), { + processData: false, + type : "PATCH", + data: JSON.stringify(newdata), + dataType : "json", + contentType: 'application/json', + }).then(function(data) { + that.data = data; + that._load_done(); + return data; + }); + }; + + return {ConfigSection: ConfigSection}; + +}); diff --git a/IPython/html/static/services/contents.js b/IPython/html/static/services/contents.js new file mode 100644 index 0000000..f9bb1a2 --- /dev/null +++ b/IPython/html/static/services/contents.js @@ -0,0 +1,250 @@ +// Copyright (c) IPython Development Team. +// Distributed under the terms of the Modified BSD License. + +define([ + 'base/js/namespace', + 'jquery', + 'base/js/utils', +], function(IPython, $, utils) { + var Contents = function(options) { + /** + * Constructor + * + * A contents handles passing file operations + * to the back-end. This includes checkpointing + * with the normal file operations. + * + * Parameters: + * options: dictionary + * Dictionary of keyword arguments. + * base_url: string + */ + this.base_url = options.base_url; + }; + + /** Error type */ + Contents.DIRECTORY_NOT_EMPTY_ERROR = 'DirectoryNotEmptyError'; + + Contents.DirectoryNotEmptyError = function() { + // Constructor + // + // An error representing the result of attempting to delete a non-empty + // directory. + this.message = 'A directory must be empty before being deleted.'; + }; + + Contents.DirectoryNotEmptyError.prototype = Object.create(Error.prototype); + Contents.DirectoryNotEmptyError.prototype.name = + Contents.DIRECTORY_NOT_EMPTY_ERROR; + + + Contents.prototype.api_url = function() { + var url_parts = [this.base_url, 'api/contents'].concat( + Array.prototype.slice.apply(arguments)); + return utils.url_join_encode.apply(null, url_parts); + }; + + /** + * Creates a basic error handler that wraps a jqXHR error as an Error. + * + * Takes a callback that accepts an Error, and returns a callback that can + * be passed directly to $.ajax, which will wrap the error from jQuery + * as an Error, and pass that to the original callback. + * + * @method create_basic_error_handler + * @param{Function} callback + * @return{Function} + */ + Contents.prototype.create_basic_error_handler = function(callback) { + if (!callback) { + return utils.log_ajax_error; + } + return function(xhr, status, error) { + callback(utils.wrap_ajax_error(xhr, status, error)); + }; + }; + + /** + * File Functions (including notebook operations) + */ + + /** + * Get a file. + * + * Calls success with file JSON model, or error with error. + * + * @method get + * @param {String} path + * @param {Object} options + * type : 'notebook', 'file', or 'directory' + * format: 'text' or 'base64'; only relevant for type: 'file' + */ + Contents.prototype.get = function (path, options) { + /** + * We do the call with settings so we can set cache to false. + */ + var settings = { + processData : false, + cache : false, + type : "GET", + dataType : "json", + }; + var url = this.api_url(path); + params = {}; + if (options.type) { params.type = options.type; } + if (options.format) { params.format = options.format; } + return utils.promising_ajax(url + '?' + $.param(params), settings); + }; + + + /** + * Creates a new untitled file or directory in the specified directory path. + * + * @method new + * @param {String} path: the directory in which to create the new file/directory + * @param {Object} options: + * ext: file extension to use + * type: model type to create ('notebook', 'file', or 'directory') + */ + Contents.prototype.new_untitled = function(path, options) { + var data = JSON.stringify({ + ext: options.ext, + type: options.type + }); + + var settings = { + processData : false, + type : "POST", + data: data, + dataType : "json", + }; + return utils.promising_ajax(this.api_url(path), settings); + }; + + Contents.prototype.delete = function(path) { + var settings = { + processData : false, + type : "DELETE", + dataType : "json", + }; + var url = this.api_url(path); + return utils.promising_ajax(url, settings).catch( + // Translate certain errors to more specific ones. + function(error) { + // TODO: update IPEP27 to specify errors more precisely, so + // that error types can be detected here with certainty. + if (error.xhr.status === 400) { + throw new Contents.DirectoryNotEmptyError(); + } + throw error; + } + ); + }; + + Contents.prototype.rename = function(path, new_path) { + var data = {path: new_path}; + var settings = { + processData : false, + type : "PATCH", + data : JSON.stringify(data), + dataType: "json", + contentType: 'application/json', + }; + var url = this.api_url(path); + return utils.promising_ajax(url, settings); + }; + + Contents.prototype.save = function(path, model) { + /** + * We do the call with settings so we can set cache to false. + */ + var settings = { + processData : false, + type : "PUT", + data : JSON.stringify(model), + contentType: 'application/json', + }; + var url = this.api_url(path); + return utils.promising_ajax(url, settings); + }; + + Contents.prototype.copy = function(from_file, to_dir) { + /** + * Copy a file into a given directory via POST + * The server will select the name of the copied file + */ + var url = this.api_url(to_dir); + + var settings = { + processData : false, + type: "POST", + data: JSON.stringify({copy_from: from_file}), + dataType : "json", + }; + return utils.promising_ajax(url, settings); + }; + + /** + * Checkpointing Functions + */ + + Contents.prototype.create_checkpoint = function(path) { + var url = this.api_url(path, 'checkpoints'); + var settings = { + type : "POST", + dataType : "json", + }; + return utils.promising_ajax(url, settings); + }; + + Contents.prototype.list_checkpoints = function(path) { + var url = this.api_url(path, 'checkpoints'); + var settings = { + type : "GET", + cache: false, + dataType: "json", + }; + return utils.promising_ajax(url, settings); + }; + + Contents.prototype.restore_checkpoint = function(path, checkpoint_id) { + var url = this.api_url(path, 'checkpoints', checkpoint_id); + var settings = { + type : "POST", + }; + return utils.promising_ajax(url, settings); + }; + + Contents.prototype.delete_checkpoint = function(path, checkpoint_id) { + var url = this.api_url(path, 'checkpoints', checkpoint_id); + var settings = { + type : "DELETE", + }; + return utils.promising_ajax(url, settings); + }; + + /** + * File management functions + */ + + /** + * List notebooks and directories at a given path + * + * On success, load_callback is called with an array of dictionaries + * representing individual files or directories. Each dictionary has + * the keys: + * type: "notebook" or "directory" + * created: created date + * last_modified: last modified dat + * @method list_notebooks + * @param {String} path The path to list notebooks in + */ + Contents.prototype.list_contents = function(path) { + return this.get(path, {type: 'directory'}); + }; + + + IPython.Contents = Contents; + + return {'Contents': Contents}; +}); diff --git a/IPython/html/static/services/kernels/js/comm.js b/IPython/html/static/services/kernels/comm.js similarity index 55% rename from IPython/html/static/services/kernels/js/comm.js rename to IPython/html/static/services/kernels/comm.js index 04307cf..4af2ba6 100644 --- a/IPython/html/static/services/kernels/js/comm.js +++ b/IPython/html/static/services/kernels/comm.js @@ -21,7 +21,9 @@ define([ }; CommManager.prototype.init_kernel = function (kernel) { - // connect the kernel, and register message handlers + /** + * connect the kernel, and register message handlers + */ this.kernel = kernel; var msg_types = ['comm_open', 'comm_msg', 'comm_close']; for (var i = 0; i < msg_types.length; i++) { @@ -31,8 +33,10 @@ define([ }; CommManager.prototype.new_comm = function (target_name, data, callbacks, metadata) { - // Create a new Comm, register it, and open its Kernel-side counterpart - // Mimics the auto-registration in `Comm.__init__` in the IPython Comm + /** + * Create a new Comm, register it, and open its Kernel-side counterpart + * Mimics the auto-registration in `Comm.__init__` in the IPython Comm + */ var comm = new Comm(target_name); this.register_comm(comm); comm.open(data, callbacks, metadata); @@ -40,24 +44,32 @@ define([ }; CommManager.prototype.register_target = function (target_name, f) { - // Register a target function for a given target name + /** + * Register a target function for a given target name + */ this.targets[target_name] = f; }; CommManager.prototype.unregister_target = function (target_name, f) { - // Unregister a target function for a given target name + /** + * Unregister a target function for a given target name + */ delete this.targets[target_name]; }; CommManager.prototype.register_comm = function (comm) { - // Register a comm in the mapping - this.comms[comm.comm_id] = comm; + /** + * Register a comm in the mapping + */ + this.comms[comm.comm_id] = Promise.resolve(comm); comm.kernel = this.kernel; return comm.comm_id; }; CommManager.prototype.unregister_comm = function (comm) { - // Remove a comm from the mapping + /** + * Remove a comm from the mapping + */ delete this.comms[comm.comm_id]; }; @@ -65,48 +77,63 @@ define([ CommManager.prototype.comm_open = function (msg) { var content = msg.content; - var f = this.targets[content.target_name]; - if (f === undefined) { - console.log("No such target registered: ", content.target_name); - console.log("Available targets are: ", this.targets); - return; - } - var comm = new Comm(content.target_name, content.comm_id); - this.register_comm(comm); - try { - f(comm, msg); - } catch (e) { - console.log("Exception opening new comm:", e, e.stack, msg); - comm.close(); - this.unregister_comm(comm); - } - }; - - CommManager.prototype.comm_close = function (msg) { + var that = this; + var comm_id = content.comm_id; + + this.comms[comm_id] = utils.load_class(content.target_name, content.target_module, + this.targets).then(function(target) { + var comm = new Comm(content.target_name, comm_id); + comm.kernel = that.kernel; + try { + var response = target(comm, msg); + } catch (e) { + comm.close(); + that.unregister_comm(comm); + var wrapped_error = new utils.WrappedError("Exception opening new comm", e); + console.error(wrapped_error); + return Promise.reject(wrapped_error); + } + // Regardless of the target return value, we need to + // then return the comm + return Promise.resolve(response).then(function() {return comm;}); + }, utils.reject('Could not open comm', true)); + return this.comms[comm_id]; + }; + + CommManager.prototype.comm_close = function(msg) { var content = msg.content; - var comm = this.comms[content.comm_id]; - if (comm === undefined) { + if (this.comms[content.comm_id] === undefined) { + console.error('Comm promise not found for comm id ' + content.comm_id); return; } - this.unregister_comm(comm); - try { - comm.handle_close(msg); - } catch (e) { - console.log("Exception closing comm: ", e, e.stack, msg); - } + + this.comms[content.comm_id] = this.comms[content.comm_id].then(function(comm) { + this.unregister_comm(comm); + try { + comm.handle_close(msg); + } catch (e) { + console.log("Exception closing comm: ", e, e.stack, msg); + } + // don't return a comm, so that further .then() functions + // get an undefined comm input + }); }; - CommManager.prototype.comm_msg = function (msg) { + CommManager.prototype.comm_msg = function(msg) { var content = msg.content; - var comm = this.comms[content.comm_id]; - if (comm === undefined) { + if (this.comms[content.comm_id] === undefined) { + console.error('Comm promise not found for comm id ' + content.comm_id); return; } - try { - comm.handle_msg(msg); - } catch (e) { - console.log("Exception handling comm msg: ", e, e.stack, msg); - } + + this.comms[content.comm_id] = this.comms[content.comm_id].then(function(comm) { + try { + comm.handle_msg(msg); + } catch (e) { + console.log("Exception handling comm msg: ", e, e.stack, msg); + } + return comm; + }); }; //----------------------------------------------------------------------- @@ -129,12 +156,12 @@ define([ return this.kernel.send_shell_message("comm_open", content, callbacks, metadata); }; - Comm.prototype.send = function (data, callbacks, metadata) { + Comm.prototype.send = function (data, callbacks, metadata, buffers) { var content = { comm_id : this.comm_id, data : data || {}, }; - return this.kernel.send_shell_message("comm_msg", content, callbacks, metadata); + return this.kernel.send_shell_message("comm_msg", content, callbacks, metadata, buffers); }; Comm.prototype.close = function (data, callbacks, metadata) { @@ -160,7 +187,7 @@ define([ // methods for handling incoming messages - Comm.prototype._maybe_callback = function (key, msg) { + Comm.prototype._callback = function (key, msg) { var callback = this['_' + key + '_callback']; if (callback) { try { @@ -172,11 +199,11 @@ define([ }; Comm.prototype.handle_msg = function (msg) { - this._maybe_callback('msg', msg); + this._callback('msg', msg); }; Comm.prototype.handle_close = function (msg) { - this._maybe_callback('close', msg); + this._callback('close', msg); }; // For backwards compatability. diff --git a/IPython/html/static/services/kernels/js/kernel.js b/IPython/html/static/services/kernels/js/kernel.js deleted file mode 100644 index 9b4937a..0000000 --- a/IPython/html/static/services/kernels/js/kernel.js +++ /dev/null @@ -1,626 +0,0 @@ -// Copyright (c) IPython Development Team. -// Distributed under the terms of the Modified BSD License. - -define([ - 'base/js/namespace', - 'jquery', - 'base/js/utils', - 'services/kernels/js/comm', - 'widgets/js/init', -], function(IPython, $, utils, comm, widgetmanager) { - "use strict"; - - // Initialization and connection. - /** - * A Kernel Class to communicate with the Python kernel - * @Class Kernel - */ - var Kernel = function (kernel_service_url, ws_url, notebook, name) { - this.events = notebook.events; - this.kernel_id = null; - this.shell_channel = null; - this.iopub_channel = null; - this.stdin_channel = null; - this.kernel_service_url = kernel_service_url; - this.name = name; - this.ws_url = ws_url || IPython.utils.get_body_data("wsUrl"); - if (!this.ws_url) { - // trailing 's' in https will become wss for secure web sockets - this.ws_url = location.protocol.replace('http', 'ws') + "//" + location.host; - } - this.running = false; - this.username = "username"; - this.session_id = utils.uuid(); - this._msg_callbacks = {}; - this.post = $.post; - - if (typeof(WebSocket) !== 'undefined') { - this.WebSocket = WebSocket; - } else if (typeof(MozWebSocket) !== 'undefined') { - this.WebSocket = MozWebSocket; - } else { - alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox ≥ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.'); - } - - this.bind_events(); - this.init_iopub_handlers(); - this.comm_manager = new comm.CommManager(this); - this.widget_manager = new widgetmanager.WidgetManager(this.comm_manager, notebook); - - this.last_msg_id = null; - this.last_msg_callbacks = {}; - }; - - - Kernel.prototype._get_msg = function (msg_type, content, metadata) { - var msg = { - header : { - msg_id : utils.uuid(), - username : this.username, - session : this.session_id, - msg_type : msg_type, - version : "5.0" - }, - metadata : metadata || {}, - content : content, - parent_header : {} - }; - return msg; - }; - - Kernel.prototype.bind_events = function () { - var that = this; - this.events.on('send_input_reply.Kernel', function(evt, data) { - that.send_input_reply(data); - }); - }; - - // Initialize the iopub handlers - - Kernel.prototype.init_iopub_handlers = function () { - var output_msg_types = ['stream', 'display_data', 'execute_result', 'error']; - this._iopub_handlers = {}; - this.register_iopub_handler('status', $.proxy(this._handle_status_message, this)); - this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this)); - - for (var i=0; i < output_msg_types.length; i++) { - this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this)); - } - }; - - /** - * Start the Python kernel - * @method start - */ - Kernel.prototype.start = function (params) { - params = params || {}; - if (!this.running) { - var qs = $.param(params); - this.post(utils.url_join_encode(this.kernel_service_url) + '?' + qs, - $.proxy(this._kernel_started, this), - 'json' - ); - } - }; - - /** - * Restart the python kernel. - * - * Emit a 'status_restarting.Kernel' event with - * the current object as parameter - * - * @method restart - */ - Kernel.prototype.restart = function () { - this.events.trigger('status_restarting.Kernel', {kernel: this}); - if (this.running) { - this.stop_channels(); - this.post(utils.url_join_encode(this.kernel_url, "restart"), - $.proxy(this._kernel_started, this), - 'json' - ); - } - }; - - - Kernel.prototype._kernel_started = function (json) { - console.log("Kernel started: ", json.id); - this.running = true; - this.kernel_id = json.id; - this.kernel_url = utils.url_path_join(this.kernel_service_url, this.kernel_id); - this.start_channels(); - }; - - - Kernel.prototype._websocket_closed = function(ws_url, early) { - this.stop_channels(); - this.events.trigger('websocket_closed.Kernel', - {ws_url: ws_url, kernel: this, early: early} - ); - }; - - /** - * Start the `shell`and `iopub` channels. - * Will stop and restart them if they already exist. - * - * @method start_channels - */ - Kernel.prototype.start_channels = function () { - var that = this; - this.stop_channels(); - var ws_host_url = this.ws_url + this.kernel_url; - console.log("Starting WebSockets:", ws_host_url); - this.shell_channel = new this.WebSocket( - this.ws_url + utils.url_join_encode(this.kernel_url, "shell") - ); - this.stdin_channel = new this.WebSocket( - this.ws_url + utils.url_join_encode(this.kernel_url, "stdin") - ); - this.iopub_channel = new this.WebSocket( - this.ws_url + utils.url_join_encode(this.kernel_url, "iopub") - ); - - var already_called_onclose = false; // only alert once - var ws_closed_early = function(evt){ - if (already_called_onclose){ - return; - } - already_called_onclose = true; - if ( ! evt.wasClean ){ - that._websocket_closed(ws_host_url, true); - } - }; - var ws_closed_late = function(evt){ - if (already_called_onclose){ - return; - } - already_called_onclose = true; - if ( ! evt.wasClean ){ - that._websocket_closed(ws_host_url, false); - } - }; - var ws_error = function(evt){ - if (already_called_onclose){ - return; - } - already_called_onclose = true; - that._websocket_closed(ws_host_url, false); - }; - var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel]; - for (var i=0; i < channels.length; i++) { - channels[i].onopen = $.proxy(this._ws_opened, this); - channels[i].onclose = ws_closed_early; - channels[i].onerror = ws_error; - } - // switch from early-close to late-close message after 1s - setTimeout(function() { - for (var i=0; i < channels.length; i++) { - if (channels[i] !== null) { - channels[i].onclose = ws_closed_late; - } - } - }, 1000); - this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this); - this.iopub_channel.onmessage = $.proxy(this._handle_iopub_message, this); - this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this); - }; - - /** - * Handle a websocket entering the open state - * sends session and cookie authentication info as first message. - * Once all sockets are open, signal the Kernel.status_started event. - * @method _ws_opened - */ - Kernel.prototype._ws_opened = function (evt) { - // send the session id so the Session object Python-side - // has the same identity - evt.target.send(this.session_id + ':' + document.cookie); - - var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel]; - for (var i=0; i < channels.length; i++) { - // if any channel is not ready, don't trigger event. - if ( channels[i].readyState !== WebSocket.OPEN ) return; - } - // all events ready, trigger started event. - this.events.trigger('status_started.Kernel', {kernel: this}); - }; - - /** - * Stop the websocket channels. - * @method stop_channels - */ - Kernel.prototype.stop_channels = function () { - var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel]; - for (var i=0; i < channels.length; i++) { - if ( channels[i] !== null ) { - channels[i].onclose = null; - channels[i].close(); - } - } - this.shell_channel = this.iopub_channel = this.stdin_channel = null; - }; - - // Main public methods. - - // send a message on the Kernel's shell channel - Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata) { - var msg = this._get_msg(msg_type, content, metadata); - this.shell_channel.send(JSON.stringify(msg)); - this.set_callbacks_for_msg(msg.header.msg_id, callbacks); - return msg.header.msg_id; - }; - - /** - * Get kernel info - * - * @param callback {function} - * @method kernel_info - * - * When calling this method, pass a callback function that expects one argument. - * The callback will be passed the complete `kernel_info_reply` message documented - * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info) - */ - Kernel.prototype.kernel_info = function (callback) { - var callbacks; - if (callback) { - callbacks = { shell : { reply : callback } }; - } - return this.send_shell_message("kernel_info_request", {}, callbacks); - }; - - /** - * Get info on an object - * - * @param code {string} - * @param cursor_pos {integer} - * @param callback {function} - * @method inspect - * - * When calling this method, pass a callback function that expects one argument. - * The callback will be passed the complete `inspect_reply` message documented - * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information) - */ - Kernel.prototype.inspect = function (code, cursor_pos, callback) { - var callbacks; - if (callback) { - callbacks = { shell : { reply : callback } }; - } - - var content = { - code : code, - cursor_pos : cursor_pos, - detail_level : 0, - }; - return this.send_shell_message("inspect_request", content, callbacks); - }; - - /** - * Execute given code into kernel, and pass result to callback. - * - * @async - * @method execute - * @param {string} code - * @param [callbacks] {Object} With the following keys (all optional) - * @param callbacks.shell.reply {function} - * @param callbacks.shell.payload.[payload_name] {function} - * @param callbacks.iopub.output {function} - * @param callbacks.iopub.clear_output {function} - * @param callbacks.input {function} - * @param {object} [options] - * @param [options.silent=false] {Boolean} - * @param [options.user_expressions=empty_dict] {Dict} - * @param [options.allow_stdin=false] {Boolean} true|false - * - * @example - * - * The options object should contain the options for the execute call. Its default - * values are: - * - * options = { - * silent : true, - * user_expressions : {}, - * allow_stdin : false - * } - * - * When calling this method pass a callbacks structure of the form: - * - * callbacks = { - * shell : { - * reply : execute_reply_callback, - * payload : { - * set_next_input : set_next_input_callback, - * } - * }, - * iopub : { - * output : output_callback, - * clear_output : clear_output_callback, - * }, - * input : raw_input_callback - * } - * - * Each callback will be passed the entire message as a single arugment. - * Payload handlers will be passed the corresponding payload and the execute_reply message. - */ - Kernel.prototype.execute = function (code, callbacks, options) { - - var content = { - code : code, - silent : true, - store_history : false, - user_expressions : {}, - allow_stdin : false - }; - callbacks = callbacks || {}; - if (callbacks.input !== undefined) { - content.allow_stdin = true; - } - $.extend(true, content, options); - this.events.trigger('execution_request.Kernel', {kernel: this, content:content}); - return this.send_shell_message("execute_request", content, callbacks); - }; - - /** - * When calling this method, pass a function to be called with the `complete_reply` message - * as its only argument when it arrives. - * - * `complete_reply` is documented - * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete) - * - * @method complete - * @param code {string} - * @param cursor_pos {integer} - * @param callback {function} - * - */ - Kernel.prototype.complete = function (code, cursor_pos, callback) { - var callbacks; - if (callback) { - callbacks = { shell : { reply : callback } }; - } - var content = { - code : code, - cursor_pos : cursor_pos, - }; - return this.send_shell_message("complete_request", content, callbacks); - }; - - - Kernel.prototype.interrupt = function () { - if (this.running) { - this.events.trigger('status_interrupting.Kernel', {kernel: this}); - this.post(utils.url_join_encode(this.kernel_url, "interrupt")); - } - }; - - - Kernel.prototype.kill = function (success, error) { - if (this.running) { - this.running = false; - var settings = { - cache : false, - type : "DELETE", - success : success, - error : error || utils.log_ajax_error, - }; - $.ajax(utils.url_join_encode(this.kernel_url), settings); - this.stop_channels(); - } - }; - - Kernel.prototype.send_input_reply = function (input) { - var content = { - value : input, - }; - this.events.trigger('input_reply.Kernel', {kernel: this, content:content}); - var msg = this._get_msg("input_reply", content); - this.stdin_channel.send(JSON.stringify(msg)); - return msg.header.msg_id; - }; - - - // Reply handlers - - Kernel.prototype.register_iopub_handler = function (msg_type, callback) { - this._iopub_handlers[msg_type] = callback; - }; - - Kernel.prototype.get_iopub_handler = function (msg_type) { - // get iopub handler for a specific message type - return this._iopub_handlers[msg_type]; - }; - - - Kernel.prototype.get_callbacks_for_msg = function (msg_id) { - // get callbacks for a specific message - if (msg_id == this.last_msg_id) { - return this.last_msg_callbacks; - } else { - return this._msg_callbacks[msg_id]; - } - }; - - - Kernel.prototype.clear_callbacks_for_msg = function (msg_id) { - if (this._msg_callbacks[msg_id] !== undefined ) { - delete this._msg_callbacks[msg_id]; - } - }; - - Kernel.prototype._finish_shell = function (msg_id) { - var callbacks = this._msg_callbacks[msg_id]; - if (callbacks !== undefined) { - callbacks.shell_done = true; - if (callbacks.iopub_done) { - this.clear_callbacks_for_msg(msg_id); - } - } - }; - - Kernel.prototype._finish_iopub = function (msg_id) { - var callbacks = this._msg_callbacks[msg_id]; - if (callbacks !== undefined) { - callbacks.iopub_done = true; - if (callbacks.shell_done) { - this.clear_callbacks_for_msg(msg_id); - } - } - }; - - /* Set callbacks for a particular message. - * Callbacks should be a struct of the following form: - * shell : { - * - * } - - */ - Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) { - this.last_msg_id = msg_id; - if (callbacks) { - // shallow-copy mapping, because we will modify it at the top level - var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {}; - cbcopy.shell = callbacks.shell; - cbcopy.iopub = callbacks.iopub; - cbcopy.input = callbacks.input; - cbcopy.shell_done = (!callbacks.shell); - cbcopy.iopub_done = (!callbacks.iopub); - } else { - this.last_msg_callbacks = {}; - } - }; - - - Kernel.prototype._handle_shell_reply = function (e) { - var reply = $.parseJSON(e.data); - this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply}); - var content = reply.content; - var metadata = reply.metadata; - var parent_id = reply.parent_header.msg_id; - var callbacks = this.get_callbacks_for_msg(parent_id); - if (!callbacks || !callbacks.shell) { - return; - } - var shell_callbacks = callbacks.shell; - - // signal that shell callbacks are done - this._finish_shell(parent_id); - - if (shell_callbacks.reply !== undefined) { - shell_callbacks.reply(reply); - } - if (content.payload && shell_callbacks.payload) { - this._handle_payloads(content.payload, shell_callbacks.payload, reply); - } - }; - - - Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) { - var l = payloads.length; - // Payloads are handled by triggering events because we don't want the Kernel - // to depend on the Notebook or Pager classes. - for (var i=0; i div.prompt { + display: none; + } +} /* any special styling for code cells that are currently running goes here */ div.input { page-break-inside: avoid; @@ -448,12 +474,6 @@ div.input { display: flex; flex-direction: row; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } @media (max-width: 480px) { div.input { @@ -471,12 +491,6 @@ div.input { display: flex; flex-direction: column; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } } /* input_area and input_prompt must match in top border and margin for alignment */ @@ -730,12 +744,6 @@ div.output_wrapper { display: flex; flex-direction: column; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } /* class for the output area when it should be height-limited */ div.output_scroll { @@ -767,12 +775,6 @@ div.output_collapsed { display: flex; flex-direction: column; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } div.out_prompt_overlay { height: 100%; @@ -807,12 +809,6 @@ div.output_area { display: flex; flex-direction: row; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } div.output_area .MathJax_Display { text-align: left !important; @@ -842,12 +838,6 @@ div.output_area .rendered_html img { display: flex; flex-direction: column; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } @media (max-width: 480px) { div.output_area { @@ -865,12 +855,6 @@ div.output_area .rendered_html img { display: flex; flex-direction: column; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } } div.output_area pre { @@ -942,6 +926,19 @@ input.raw_input:focus { p.p-space { margin-bottom: 10px; } +div.output_unrecognized { + padding: 5px; + font-weight: bold; + color: red; +} +div.output_unrecognized a { + color: inherit; + text-decoration: none; +} +div.output_unrecognized a:hover { + color: inherit; + text-decoration: none; +} .rendered_html { color: #000000; /* any extras will just be numbers: */ @@ -1130,12 +1127,6 @@ div.text_cell { display: flex; flex-direction: row; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } @media (max-width: 480px) { div.text_cell > div.prompt { @@ -1167,36 +1158,39 @@ h5:hover .anchor-link, h6:hover .anchor-link { visibility: visible; } -div.cell.text_cell.rendered { - padding: 0px; +.text_cell.rendered .input_area { + display: none; } -.cm-s-heading-1, -.cm-s-heading-2, -.cm-s-heading-3, -.cm-s-heading-4, -.cm-s-heading-5, -.cm-s-heading-6 { +.text_cell.unrendered .text_cell_render { + display: none; +} +.cm-header-1, +.cm-header-2, +.cm-header-3, +.cm-header-4, +.cm-header-5, +.cm-header-6 { font-weight: bold; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } -.cm-s-heading-1 { - font-size: 150%; +.cm-header-1 { + font-size: 185.7%; } -.cm-s-heading-2 { - font-size: 130%; +.cm-header-2 { + font-size: 157.1%; } -.cm-s-heading-3 { - font-size: 120%; +.cm-header-3 { + font-size: 128.6%; } -.cm-s-heading-4 { +.cm-header-4 { font-size: 110%; } -.cm-s-heading-5 { +.cm-header-5 { font-size: 100%; font-style: italic; } -.cm-s-heading-6 { - font-size: 90%; +.cm-header-6 { + font-size: 100%; font-style: italic; } .widget-area { @@ -1229,12 +1223,6 @@ div.cell.text_cell.rendered { display: flex; flex-direction: row; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } .widget-area .widget-subarea { padding: 0.44em 0.4em 0.4em 1px; @@ -1257,12 +1245,6 @@ div.cell.text_cell.rendered { flex-direction: column; align-items: stretch; /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; - /* Old browsers */ -webkit-box-flex: 2; -moz-box-flex: 2; box-flex: 2; @@ -1336,12 +1318,6 @@ div.cell.text_cell.rendered { display: flex; flex-direction: row; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } .widget-hslider .ui-slider { /* Inner, invisible slide div */ @@ -1362,12 +1338,6 @@ div.cell.text_cell.rendered { flex-direction: row; align-items: stretch; /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; - /* Old browsers */ -webkit-box-flex: 1; -moz-box-flex: 1; box-flex: 1; @@ -1415,12 +1385,6 @@ div.cell.text_cell.rendered { display: flex; flex-direction: column; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } .widget-vslider .ui-slider { /* Inner, invisible slide div */ @@ -1443,12 +1407,6 @@ div.cell.text_cell.rendered { flex-direction: column; align-items: stretch; /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; - /* Old browsers */ -webkit-box-flex: 1; -moz-box-flex: 1; box-flex: 1; @@ -1515,16 +1473,14 @@ div.cell.text_cell.rendered { display: flex; flex-direction: row; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; - margin: 5px; + margin-top: 0px !important; + margin-bottom: 0px !important; + margin-right: 5px; + margin-left: 5px; } .widget-hbox input[type="checkbox"] { margin-top: 9px; + margin-bottom: 10px; } .widget-hbox .widget-label { /* Horizontal Label */ @@ -1556,12 +1512,6 @@ div.cell.text_cell.rendered { display: flex; flex-direction: column; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } .widget-vbox .widget-label { /* Vertical Label */ @@ -1615,12 +1565,6 @@ div.cell.text_cell.rendered { display: flex; flex-direction: column; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; diff --git a/IPython/html/static/style/style.less b/IPython/html/static/style/style.less index 488c1ef..ad5130d 100644 --- a/IPython/html/static/style/style.less +++ b/IPython/html/static/style/style.less @@ -4,7 +4,6 @@ * */ @import "../components/bootstrap/less/bootstrap.less"; -@import "../components/bootstrap/less/responsive-utilities.less"; /*! * @@ -27,4 +26,4 @@ // notebook @import "../notebook/less/style.less"; - +@import "../notebook/less/terminal.less"; diff --git a/IPython/html/static/style/style.min.css b/IPython/html/static/style/style.min.css index 73170c5..8c276b3 100644 --- a/IPython/html/static/style/style.min.css +++ b/IPython/html/static/style/style.min.css @@ -603,7 +603,7 @@ dt { dd { margin-left: 0; } -@media (min-width: 768px) { +@media (min-width: 540px) { .dl-horizontal dt { float: left; width: 160px; @@ -3113,7 +3113,7 @@ input[type="button"].btn-block { bottom: 100%; margin-bottom: 1px; } -@media (min-width: 768px) { +@media (min-width: 540px) { .navbar-right .dropdown-menu { left: auto; right: 0; @@ -3640,12 +3640,12 @@ select[multiple].input-group-sm > .input-group-btn > .btn { margin-bottom: 18px; border: 1px solid transparent; } -@media (min-width: 768px) { +@media (min-width: 540px) { .navbar { border-radius: 4px; } } -@media (min-width: 768px) { +@media (min-width: 540px) { .navbar-header { float: left; } @@ -3662,7 +3662,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { .navbar-collapse.in { overflow-y: auto; } -@media (min-width: 768px) { +@media (min-width: 540px) { .navbar-collapse { width: auto; border-top: 0; @@ -3691,7 +3691,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { margin-right: -15px; margin-left: -15px; } -@media (min-width: 768px) { +@media (min-width: 540px) { .container > .navbar-header, .container-fluid > .navbar-header, .container > .navbar-collapse, @@ -3704,7 +3704,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { z-index: 1000; border-width: 0 0 1px; } -@media (min-width: 768px) { +@media (min-width: 540px) { .navbar-static-top { border-radius: 0; } @@ -3716,7 +3716,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { left: 0; z-index: 1030; } -@media (min-width: 768px) { +@media (min-width: 540px) { .navbar-fixed-top, .navbar-fixed-bottom { border-radius: 0; @@ -3742,7 +3742,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { .navbar-brand:focus { text-decoration: none; } -@media (min-width: 768px) { +@media (min-width: 540px) { .navbar > .container .navbar-brand, .navbar > .container-fluid .navbar-brand { margin-left: -15px; @@ -3772,7 +3772,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { .navbar-toggle .icon-bar + .icon-bar { margin-top: 4px; } -@media (min-width: 768px) { +@media (min-width: 540px) { .navbar-toggle { display: none; } @@ -3785,7 +3785,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { padding-bottom: 10px; line-height: 18px; } -@media (max-width: 767px) { +@media (max-width: 539px) { .navbar-nav .open .dropdown-menu { position: static; float: none; @@ -3807,7 +3807,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { background-image: none; } } -@media (min-width: 768px) { +@media (min-width: 540px) { .navbar-nav { float: left; margin: 0; @@ -3823,7 +3823,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { margin-right: -15px; } } -@media (min-width: 768px) { +@media (min-width: 540px) { .navbar-left { float: left !important; float: left; @@ -3879,12 +3879,12 @@ select[multiple].input-group-sm > .input-group-btn > .btn { top: 0; } } -@media (max-width: 767px) { +@media (max-width: 539px) { .navbar-form .form-group { margin-bottom: 5px; } } -@media (min-width: 768px) { +@media (min-width: 540px) { .navbar-form { width: auto; border: 0; @@ -3924,7 +3924,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { margin-top: 9px; margin-bottom: 9px; } -@media (min-width: 768px) { +@media (min-width: 540px) { .navbar-text { float: left; margin-left: 15px; @@ -3989,7 +3989,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { background-color: #e7e7e7; color: #555555; } -@media (max-width: 767px) { +@media (max-width: 539px) { .navbar-default .navbar-nav .open .dropdown-menu > li > a { color: #777777; } @@ -4072,7 +4072,7 @@ select[multiple].input-group-sm > .input-group-btn > .btn { background-color: #080808; color: #ffffff; } -@media (max-width: 767px) { +@media (max-width: 539px) { .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { border-color: #080808; } @@ -5785,24 +5785,23 @@ button.close { * */ /*! - * Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome + * Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */ /* FONT PATH * -------------------------- */ @font-face { font-family: 'FontAwesome'; - src: url('../components/font-awesome/fonts/fontawesome-webfont.eot?v=4.1.0'); - src: url('../components/font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'), url('../components/font-awesome/fonts/fontawesome-webfont.woff?v=4.1.0') format('woff'), url('../components/font-awesome/fonts/fontawesome-webfont.ttf?v=4.1.0') format('truetype'), url('../components/font-awesome/fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg'); + src: url('../components/font-awesome/fonts/fontawesome-webfont.eot?v=4.2.0'); + src: url('../components/font-awesome/fonts/fontawesome-webfont.eot?#iefix&v=4.2.0') format('embedded-opentype'), url('../components/font-awesome/fonts/fontawesome-webfont.woff?v=4.2.0') format('woff'), url('../components/font-awesome/fonts/fontawesome-webfont.ttf?v=4.2.0') format('truetype'), url('../components/font-awesome/fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular') format('svg'); font-weight: normal; font-style: normal; } .fa { display: inline-block; - font-family: FontAwesome; - font-style: normal; - font-weight: normal; - line-height: 1; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } @@ -5864,36 +5863,20 @@ button.close { margin-left: .3em; } .fa-spin { - -webkit-animation: spin 2s infinite linear; - -moz-animation: spin 2s infinite linear; - -o-animation: spin 2s infinite linear; - animation: spin 2s infinite linear; -} -@-moz-keyframes spin { - 0% { - -moz-transform: rotate(0deg); - } - 100% { - -moz-transform: rotate(359deg); - } + -webkit-animation: fa-spin 2s infinite linear; + animation: fa-spin 2s infinite linear; } -@-webkit-keyframes spin { +@-webkit-keyframes fa-spin { 0% { -webkit-transform: rotate(0deg); + transform: rotate(0deg); } 100% { -webkit-transform: rotate(359deg); + transform: rotate(359deg); } } -@-o-keyframes spin { - 0% { - -o-transform: rotate(0deg); - } - 100% { - -o-transform: rotate(359deg); - } -} -@keyframes spin { +@keyframes fa-spin { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); @@ -5906,43 +5889,40 @@ button.close { .fa-rotate-90 { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); -webkit-transform: rotate(90deg); - -moz-transform: rotate(90deg); -ms-transform: rotate(90deg); - -o-transform: rotate(90deg); transform: rotate(90deg); } .fa-rotate-180 { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); -webkit-transform: rotate(180deg); - -moz-transform: rotate(180deg); -ms-transform: rotate(180deg); - -o-transform: rotate(180deg); transform: rotate(180deg); } .fa-rotate-270 { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); -webkit-transform: rotate(270deg); - -moz-transform: rotate(270deg); -ms-transform: rotate(270deg); - -o-transform: rotate(270deg); transform: rotate(270deg); } .fa-flip-horizontal { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); -webkit-transform: scale(-1, 1); - -moz-transform: scale(-1, 1); -ms-transform: scale(-1, 1); - -o-transform: scale(-1, 1); transform: scale(-1, 1); } .fa-flip-vertical { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1); -webkit-transform: scale(1, -1); - -moz-transform: scale(1, -1); -ms-transform: scale(1, -1); - -o-transform: scale(1, -1); transform: scale(1, -1); } +:root .fa-rotate-90, +:root .fa-rotate-180, +:root .fa-rotate-270, +:root .fa-flip-horizontal, +:root .fa-flip-vertical { + filter: none; +} .fa-stack { position: relative; display: inline-block; @@ -6008,6 +5988,8 @@ button.close { .fa-check:before { content: "\f00c"; } +.fa-remove:before, +.fa-close:before, .fa-times:before { content: "\f00d"; } @@ -6337,7 +6319,8 @@ button.close { .fa-arrows-h:before { content: "\f07e"; } -.fa-bar-chart-o:before { +.fa-bar-chart-o:before, +.fa-bar-chart:before { content: "\f080"; } .fa-twitter-square:before { @@ -7166,7 +7149,6 @@ button.close { .fa-digg:before { content: "\f1a6"; } -.fa-pied-piper-square:before, .fa-pied-piper:before { content: "\f1a7"; } @@ -7283,6 +7265,7 @@ button.close { content: "\f1cc"; } .fa-life-bouy:before, +.fa-life-buoy:before, .fa-life-saver:before, .fa-support:before, .fa-life-ring:before { @@ -7350,6 +7333,129 @@ button.close { .fa-bomb:before { content: "\f1e2"; } +.fa-soccer-ball-o:before, +.fa-futbol-o:before { + content: "\f1e3"; +} +.fa-tty:before { + content: "\f1e4"; +} +.fa-binoculars:before { + content: "\f1e5"; +} +.fa-plug:before { + content: "\f1e6"; +} +.fa-slideshare:before { + content: "\f1e7"; +} +.fa-twitch:before { + content: "\f1e8"; +} +.fa-yelp:before { + content: "\f1e9"; +} +.fa-newspaper-o:before { + content: "\f1ea"; +} +.fa-wifi:before { + content: "\f1eb"; +} +.fa-calculator:before { + content: "\f1ec"; +} +.fa-paypal:before { + content: "\f1ed"; +} +.fa-google-wallet:before { + content: "\f1ee"; +} +.fa-cc-visa:before { + content: "\f1f0"; +} +.fa-cc-mastercard:before { + content: "\f1f1"; +} +.fa-cc-discover:before { + content: "\f1f2"; +} +.fa-cc-amex:before { + content: "\f1f3"; +} +.fa-cc-paypal:before { + content: "\f1f4"; +} +.fa-cc-stripe:before { + content: "\f1f5"; +} +.fa-bell-slash:before { + content: "\f1f6"; +} +.fa-bell-slash-o:before { + content: "\f1f7"; +} +.fa-trash:before { + content: "\f1f8"; +} +.fa-copyright:before { + content: "\f1f9"; +} +.fa-at:before { + content: "\f1fa"; +} +.fa-eyedropper:before { + content: "\f1fb"; +} +.fa-paint-brush:before { + content: "\f1fc"; +} +.fa-birthday-cake:before { + content: "\f1fd"; +} +.fa-area-chart:before { + content: "\f1fe"; +} +.fa-pie-chart:before { + content: "\f200"; +} +.fa-line-chart:before { + content: "\f201"; +} +.fa-lastfm:before { + content: "\f202"; +} +.fa-lastfm-square:before { + content: "\f203"; +} +.fa-toggle-off:before { + content: "\f204"; +} +.fa-toggle-on:before { + content: "\f205"; +} +.fa-bicycle:before { + content: "\f206"; +} +.fa-bus:before { + content: "\f207"; +} +.fa-ioxhost:before { + content: "\f208"; +} +.fa-angellist:before { + content: "\f209"; +} +.fa-cc:before { + content: "\f20a"; +} +.fa-shekel:before, +.fa-sheqel:before, +.fa-ils:before { + content: "\f20b"; +} +.fa-meanpath:before { + content: "\f20c"; +} /*! * * IPython base @@ -7839,12 +7945,6 @@ span#login_widget > .button .badge, display: flex; flex-direction: column; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; min-height: 80%; } .modal_stretch .modal-dialog .modal-body { @@ -7970,6 +8070,9 @@ ul.breadcrumb span { .item_buttons { line-height: 1em; } +.item_buttons .btn { + min-width: 13ex; +} .toolbar_info { height: 24px; line-height: 24px; @@ -8000,10 +8103,9 @@ input.engine_num_input { } .folder_icon:before { display: inline-block; - font-family: FontAwesome; - font-style: normal; - font-weight: normal; - line-height: 1; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; content: "\f114"; @@ -8016,10 +8118,9 @@ input.engine_num_input { } .notebook_icon:before { display: inline-block; - font-family: FontAwesome; - font-style: normal; - font-weight: normal; - line-height: 1; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; content: "\f02d"; @@ -8032,10 +8133,9 @@ input.engine_num_input { } .file_icon:before { display: inline-block; - font-family: FontAwesome; - font-style: normal; - font-weight: normal; - line-height: 1; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; content: "\f016"; @@ -8066,7 +8166,7 @@ input.engine_num_input { color: darkgreen; } .ansiyellow { - color: brown; + color: #c4a000; } .ansiblue { color: darkblue; @@ -8121,12 +8221,6 @@ div.cell { display: flex; flex-direction: column; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; border-radius: 4px; box-sizing: border-box; -moz-box-sizing: border-box; @@ -8177,12 +8271,6 @@ div.inner_cell { flex-direction: column; align-items: stretch; /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; - /* Old browsers */ -webkit-box-flex: 1; -moz-box-flex: 1; box-flex: 1; @@ -8203,6 +8291,44 @@ div.prompt:empty { padding-top: 0; padding-bottom: 0; } +div.unrecognized_cell { + padding: 5px 5px 5px 0px; + /* Old browsers */ + display: -webkit-box; + -webkit-box-orient: horizontal; + -webkit-box-align: stretch; + display: -moz-box; + -moz-box-orient: horizontal; + -moz-box-align: stretch; + display: box; + box-orient: horizontal; + box-align: stretch; + /* Modern browsers */ + display: flex; + flex-direction: row; + align-items: stretch; +} +div.unrecognized_cell .inner_cell { + border-radius: 4px; + padding: 5px; + font-weight: bold; + color: red; + border: 1px solid #cfcfcf; + background: #eaeaea; +} +div.unrecognized_cell .inner_cell a { + color: inherit; + text-decoration: none; +} +div.unrecognized_cell .inner_cell a:hover { + color: inherit; + text-decoration: none; +} +@media (max-width: 480px) { + div.unrecognized_cell > div.prompt { + display: none; + } +} /* any special styling for code cells that are currently running goes here */ div.input { page-break-inside: avoid; @@ -8220,12 +8346,6 @@ div.input { display: flex; flex-direction: row; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } @media (max-width: 480px) { div.input { @@ -8243,12 +8363,6 @@ div.input { display: flex; flex-direction: column; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } } /* input_area and input_prompt must match in top border and margin for alignment */ @@ -8502,12 +8616,6 @@ div.output_wrapper { display: flex; flex-direction: column; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } /* class for the output area when it should be height-limited */ div.output_scroll { @@ -8539,12 +8647,6 @@ div.output_collapsed { display: flex; flex-direction: column; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } div.out_prompt_overlay { height: 100%; @@ -8579,12 +8681,6 @@ div.output_area { display: flex; flex-direction: row; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } div.output_area .MathJax_Display { text-align: left !important; @@ -8614,12 +8710,6 @@ div.output_area .rendered_html img { display: flex; flex-direction: column; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } @media (max-width: 480px) { div.output_area { @@ -8637,12 +8727,6 @@ div.output_area .rendered_html img { display: flex; flex-direction: column; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } } div.output_area pre { @@ -8714,6 +8798,19 @@ input.raw_input:focus { p.p-space { margin-bottom: 10px; } +div.output_unrecognized { + padding: 5px; + font-weight: bold; + color: red; +} +div.output_unrecognized a { + color: inherit; + text-decoration: none; +} +div.output_unrecognized a:hover { + color: inherit; + text-decoration: none; +} .rendered_html { color: #000000; /* any extras will just be numbers: */ @@ -8902,12 +8999,6 @@ div.text_cell { display: flex; flex-direction: row; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } @media (max-width: 480px) { div.text_cell > div.prompt { @@ -8939,36 +9030,39 @@ h5:hover .anchor-link, h6:hover .anchor-link { visibility: visible; } -div.cell.text_cell.rendered { - padding: 0px; +.text_cell.rendered .input_area { + display: none; } -.cm-s-heading-1, -.cm-s-heading-2, -.cm-s-heading-3, -.cm-s-heading-4, -.cm-s-heading-5, -.cm-s-heading-6 { +.text_cell.unrendered .text_cell_render { + display: none; +} +.cm-header-1, +.cm-header-2, +.cm-header-3, +.cm-header-4, +.cm-header-5, +.cm-header-6 { font-weight: bold; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } -.cm-s-heading-1 { - font-size: 150%; +.cm-header-1 { + font-size: 185.7%; } -.cm-s-heading-2 { - font-size: 130%; +.cm-header-2 { + font-size: 157.1%; } -.cm-s-heading-3 { - font-size: 120%; +.cm-header-3 { + font-size: 128.6%; } -.cm-s-heading-4 { +.cm-header-4 { font-size: 110%; } -.cm-s-heading-5 { +.cm-header-5 { font-size: 100%; font-style: italic; } -.cm-s-heading-6 { - font-size: 90%; +.cm-header-6 { + font-size: 100%; font-style: italic; } .widget-area { @@ -9001,12 +9095,6 @@ div.cell.text_cell.rendered { display: flex; flex-direction: row; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } .widget-area .widget-subarea { padding: 0.44em 0.4em 0.4em 1px; @@ -9029,12 +9117,6 @@ div.cell.text_cell.rendered { flex-direction: column; align-items: stretch; /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; - /* Old browsers */ -webkit-box-flex: 2; -moz-box-flex: 2; box-flex: 2; @@ -9108,12 +9190,6 @@ div.cell.text_cell.rendered { display: flex; flex-direction: row; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } .widget-hslider .ui-slider { /* Inner, invisible slide div */ @@ -9134,12 +9210,6 @@ div.cell.text_cell.rendered { flex-direction: row; align-items: stretch; /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; - /* Old browsers */ -webkit-box-flex: 1; -moz-box-flex: 1; box-flex: 1; @@ -9187,12 +9257,6 @@ div.cell.text_cell.rendered { display: flex; flex-direction: column; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } .widget-vslider .ui-slider { /* Inner, invisible slide div */ @@ -9215,12 +9279,6 @@ div.cell.text_cell.rendered { flex-direction: column; align-items: stretch; /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; - /* Old browsers */ -webkit-box-flex: 1; -moz-box-flex: 1; box-flex: 1; @@ -9287,16 +9345,14 @@ div.cell.text_cell.rendered { display: flex; flex-direction: row; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; - margin: 5px; + margin-top: 0px !important; + margin-bottom: 0px !important; + margin-right: 5px; + margin-left: 5px; } .widget-hbox input[type="checkbox"] { margin-top: 9px; + margin-bottom: 10px; } .widget-hbox .widget-label { /* Horizontal Label */ @@ -9328,12 +9384,6 @@ div.cell.text_cell.rendered { display: flex; flex-direction: column; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } .widget-vbox .widget-label { /* Vertical Label */ @@ -9387,12 +9437,6 @@ div.cell.text_cell.rendered { display: flex; flex-direction: column; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; @@ -9503,9 +9547,8 @@ p { border: thin solid #CFCFCF; border-bottom: none; background: #EEE; - border-radius: 3px 3px 0px 0px; + border-radius: 4px 4px 0px 0px; width: 100%; - -webkit-box-pack: end; height: 29px; padding-right: 4px; /* Old browsers */ @@ -9523,17 +9566,11 @@ p { flex-direction: row; align-items: stretch; /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; - /* Old browsers */ - -webkit-box-direction: reverse; - -moz-box-direction: reverse; - box-direction: reverse; + -webkit-box-pack: end; + -moz-box-pack: end; + box-pack: end; /* Modern browsers */ - flex-direction: row-reverse; + justify-content: flex-end; } .ctb_hideshow { display: none; @@ -9752,6 +9789,12 @@ fieldset[disabled] #kernel_selector_widget > button.active { border-top: 1px; border-radius: 0px 0px 4px 4px; } +#menubar .navbar-toggle { + float: left; +} +#menubar .navbar-collapse { + clear: left; +} #menubar li.dropdown { line-height: 12px; } @@ -9761,6 +9804,7 @@ fieldset[disabled] #kernel_selector_widget > button.active { } #menubar ul.navbar-right { padding-top: 2px; + margin-bottom: 0px; } .nav-wrapper { border-bottom: 1px solid #e7e7e7; @@ -9856,10 +9900,9 @@ ul#help_menu li a i { } .edit_mode_icon:before { display: inline-block; - font-family: FontAwesome; - font-style: normal; - font-weight: normal; - line-height: 1; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; content: "\f040"; @@ -9872,10 +9915,9 @@ ul#help_menu li a i { } .command_mode_icon:before { display: inline-block; - font-family: FontAwesome; - font-style: normal; - font-weight: normal; - line-height: 1; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; content: ' '; @@ -9888,10 +9930,9 @@ ul#help_menu li a i { } .kernel_idle_icon:before { display: inline-block; - font-family: FontAwesome; - font-style: normal; - font-weight: normal; - line-height: 1; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; content: "\f10c"; @@ -9904,10 +9945,9 @@ ul#help_menu li a i { } .kernel_busy_icon:before { display: inline-block; - font-family: FontAwesome; - font-style: normal; - font-weight: normal; - line-height: 1; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; content: "\f111"; @@ -9920,10 +9960,9 @@ ul#help_menu li a i { } .kernel_dead_icon:before { display: inline-block; - font-family: FontAwesome; - font-style: normal; - font-weight: normal; - line-height: 1; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; content: "\f1e2"; @@ -9936,10 +9975,9 @@ ul#help_menu li a i { } .kernel_disconnected_icon:before { display: inline-block; - font-family: FontAwesome; - font-style: normal; - font-weight: normal; - line-height: 1; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; content: "\f127"; @@ -10261,12 +10299,6 @@ div#pager pre { display: flex; flex-direction: row; align-items: stretch; - /* Old browsers */ - -webkit-box-flex: 0; - -moz-box-flex: 0; - box-flex: 0; - /* Modern browsers */ - flex: none; } .shortcut_key { display: inline-block; @@ -10503,4 +10535,19 @@ span#autosave_status { -ms-transform: rotate(45deg); -o-transform: rotate(45deg); } +.terminal { + float: left; + border: black solid 5px; + font-family: "DejaVu Sans Mono", "Liberation Mono", monospace; + font-size: 11px; + color: white; + background: black; +} +.terminal-cursor { + color: black; + background: white; +} +#terminado-container { + margin: 8px; +} /*# sourceMappingURL=../style/style.min.css.map */ \ No newline at end of file diff --git a/IPython/html/static/terminal/js/main.js b/IPython/html/static/terminal/js/main.js new file mode 100644 index 0000000..12c4e37 --- /dev/null +++ b/IPython/html/static/terminal/js/main.js @@ -0,0 +1,53 @@ +// Copyright (c) IPython Development Team. +// Distributed under the terms of the Modified BSD License. + +require([ + 'jquery', + 'termjs', + 'base/js/utils', + 'base/js/page', + 'terminal/js/terminado', + 'custom/custom', +], function( + $, + termjs, + utils, + page, + terminado + ){ + page = new page.Page(); + // Test size: 25x80 + var termRowHeight = function(){ return 1.00 * $("#dummy-screen")[0].offsetHeight / 25;}; + // 1.02 here arrived at by trial and error to make the spacing look right + var termColWidth = function() { return 1.02 * $("#dummy-screen-rows")[0].offsetWidth / 80;}; + + var base_url = utils.get_body_data('baseUrl'); + var ws_path = utils.get_body_data('wsPath'); + var ws_url = location.protocol.replace('http', 'ws') + "//" + location.host + + base_url + ws_path; + + var header = $("#header")[0] + function calculate_size() { + height = window.innerHeight - header.offsetHeight; + width = window.innerWidth; + var rows = Math.min(1000, Math.max(20, Math.floor(height/termRowHeight())-1)); + var cols = Math.min(1000, Math.max(40, Math.floor(width/termColWidth())-1)); + console.log("resize to :", rows , 'rows by ', cols, 'columns'); + return {rows: rows, cols: cols}; + } + + page.show_header(); + + size = calculate_size(); + var terminal = terminado.make_terminal($("#terminado-container")[0], size, ws_url); + + page.show_site(); + + window.onresize = function() { + var geom = calculate_size(); + terminal.term.resize(geom.cols, geom.rows); + terminal.socket.send(JSON.stringify(["set_size", geom.rows, geom.cols, + window.innerHeight, window.innerWidth])); + }; + +}); diff --git a/IPython/html/static/terminal/js/terminado.js b/IPython/html/static/terminal/js/terminado.js new file mode 100644 index 0000000..2fda37a --- /dev/null +++ b/IPython/html/static/terminal/js/terminado.js @@ -0,0 +1,39 @@ +define ([], function() { + function make_terminal(element, size, ws_url) { + var ws = new WebSocket(ws_url); + var term = new Terminal({ + cols: size.cols, + rows: size.rows, + screenKeys: true, + useStyle: false + }); + ws.onopen = function(event) { + ws.send(JSON.stringify(["set_size", size.rows, size.cols, + window.innerHeight, window.innerWidth])); + term.on('data', function(data) { + ws.send(JSON.stringify(['stdin', data])); + }); + + term.on('title', function(title) { + document.title = title; + }); + + term.open(element); + + ws.onmessage = function(event) { + json_msg = JSON.parse(event.data); + switch(json_msg[0]) { + case "stdout": + term.write(json_msg[1]); + break; + case "disconnect": + term.write("\r\n\r\n[CLOSED]\r\n"); + break; + } + }; + }; + return {socket: ws, term: term}; + } + + return {make_terminal: make_terminal}; +}); diff --git a/IPython/html/static/tree/js/kernellist.js b/IPython/html/static/tree/js/kernellist.js index a4c318f..838d804 100644 --- a/IPython/html/static/tree/js/kernellist.js +++ b/IPython/html/static/tree/js/kernellist.js @@ -9,15 +9,17 @@ define([ "use strict"; var KernelList = function (selector, options) { - // Constructor - // - // Parameters: - // selector: string - // options: dictionary - // Dictionary of keyword arguments. - // session_list: SessionList instance - // base_url: string - // notebook_path: string + /** + * Constructor + * + * Parameters: + * selector: string + * options: dictionary + * Dictionary of keyword arguments. + * session_list: SessionList instance + * base_url: string + * notebook_path: string + */ notebooklist.NotebookList.call(this, selector, $.extend({ element_name: 'running'}, options)); @@ -25,22 +27,27 @@ define([ KernelList.prototype = Object.create(notebooklist.NotebookList.prototype); + KernelList.prototype.add_duplicate_button = function () { + /** + * do nothing + */ + }; + KernelList.prototype.sessions_loaded = function (d) { this.sessions = d; this.clear_list(); - var item, path_name; - for (path_name in d) { - if (!d.hasOwnProperty(path_name)) { + var item, path; + for (path in d) { + if (!d.hasOwnProperty(path)) { // nothing is safe in javascript continue; } item = this.new_item(-1); this.add_link({ - name: path_name, - path: '', + name: path, + path: path, type: 'notebook', }, item); - this.add_shutdown_button(item, this.sessions[path_name]); } $('#running_list_header').toggle($.isEmptyObject(d)); }; diff --git a/IPython/html/static/tree/js/main.js b/IPython/html/static/tree/js/main.js index ab0e08c..617f5f5 100644 --- a/IPython/html/static/tree/js/main.js +++ b/IPython/html/static/tree/js/main.js @@ -2,31 +2,38 @@ // Distributed under the terms of the Modified BSD License. require([ - 'base/js/namespace', 'jquery', + 'base/js/namespace', + 'base/js/dialog', 'base/js/events', 'base/js/page', 'base/js/utils', + 'contents', 'tree/js/notebooklist', 'tree/js/clusterlist', 'tree/js/sessionlist', 'tree/js/kernellist', + 'tree/js/terminallist', 'auth/js/loginwidget', // only loaded, not used: 'jqueryui', 'bootstrap', 'custom/custom', ], function( - IPython, - $, + $, + IPython, + dialog, events, - page, - utils, + page, + utils, + contents_service, notebooklist, clusterlist, sesssionlist, - kernellist, + kernellist, + terminallist, loginwidget){ + "use strict"; page = new page.Page(); @@ -34,20 +41,45 @@ require([ base_url: utils.get_body_data("baseUrl"), notebook_path: utils.get_body_data("notebookPath"), }; - session_list = new sesssionlist.SesssionList($.extend({ + var session_list = new sesssionlist.SesssionList($.extend({ events: events}, common_options)); - notebook_list = new notebooklist.NotebookList('#notebook_list', $.extend({ + var contents = new contents_service.Contents($.extend({ + events: events}, + common_options)); + var notebook_list = new notebooklist.NotebookList('#notebook_list', $.extend({ + contents: contents, session_list: session_list}, common_options)); - cluster_list = new clusterlist.ClusterList('#cluster_list', common_options); - kernel_list = new kernellist.KernelList('#running_list', $.extend({ + var cluster_list = new clusterlist.ClusterList('#cluster_list', common_options); + var kernel_list = new kernellist.KernelList('#running_list', $.extend({ session_list: session_list}, common_options)); - login_widget = new loginwidget.LoginWidget('#login_widget', common_options); + + var terminal_list; + if (utils.get_body_data("terminalsAvailable") === "True") { + terminal_list = new terminallist.TerminalList('#terminal_list', common_options); + } + + var login_widget = new loginwidget.LoginWidget('#login_widget', common_options); $('#new_notebook').click(function (e) { - notebook_list.new_notebook(); + var w = window.open(); + contents.new_untitled(common_options.notebook_path, {type: "notebook"}).then( + function (data) { + w.location = utils.url_join_encode( + common_options.base_url, 'notebooks', data.path + ); + }, + function(error) { + w.close(); + dialog.modal({ + title : 'Creating Notebook Failed', + body : "The error was: " + error.message, + buttons : {'OK' : {'class' : 'btn-primary'}} + }); + } + ); }); var interval_id=0; @@ -56,13 +88,21 @@ require([ var time_refresh = 60; // in sec var enable_autorefresh = function(){ - //refresh immediately , then start interval + /** + *refresh immediately , then start interval + */ session_list.load_sessions(); cluster_list.load_list(); + if (terminal_list) { + terminal_list.load_terminals(); + } if (!interval_id){ interval_id = setInterval(function(){ session_list.load_sessions(); cluster_list.load_list(); + if (terminal_list) { + terminal_list.load_terminals(); + } }, time_refresh*1000); } }; @@ -111,5 +151,4 @@ require([ if (window.location.hash) { $("#tabs").find("a[href=" + window.location.hash + "]").click(); } - }); diff --git a/IPython/html/static/tree/js/notebooklist.js b/IPython/html/static/tree/js/notebooklist.js index d74a154..984faf5 100644 --- a/IPython/html/static/tree/js/notebooklist.js +++ b/IPython/html/static/tree/js/notebooklist.js @@ -6,20 +6,24 @@ define([ 'jquery', 'base/js/utils', 'base/js/dialog', -], function(IPython, $, utils, dialog) { + 'base/js/events', +], function(IPython, $, utils, dialog, events) { "use strict"; var NotebookList = function (selector, options) { - // Constructor - // - // Parameters: - // selector: string - // options: dictionary - // Dictionary of keyword arguments. - // session_list: SessionList instance - // element_name: string - // base_url: string - // notebook_path: string + /** + * Constructor + * + * Parameters: + * selector: string + * options: dictionary + * Dictionary of keyword arguments. + * session_list: SessionList instance + * element_name: string + * base_url: string + * notebook_path: string + * contents: Contents instance + */ var that = this; this.session_list = options.session_list; // allow code re-use by just changing element_name in kernellist.js @@ -34,6 +38,7 @@ define([ this.sessions = {}; this.base_url = options.base_url || utils.get_body_data("baseUrl"); this.notebook_path = options.notebook_path || utils.get_body_data("notebookPath"); + this.contents = options.contents; if (this.session_list && this.session_list.events) { this.session_list.events.on('sessions_loaded.Dashboard', function(e, d) { that.sessions_loaded(d); }); @@ -98,7 +103,7 @@ define([ }; reader.onerror = function (event) { var item = $(event.target).data('item'); - var name = item.data('name') + var name = item.data('name'); item.remove(); dialog.modal({ title : 'Failed to read file', @@ -116,11 +121,13 @@ define([ }; NotebookList.prototype.clear_list = function (remove_uploads) { - // Clears the navigation tree. - // - // Parameters - // remove_uploads: bool=False - // Should upload prompts also be removed from the tree. + /** + * Clears the navigation tree. + * + * Parameters + * remove_uploads: bool=False + * Should upload prompts also be removed from the tree. + */ if (remove_uploads) { this.element.children('.list_item').remove(); } else { @@ -140,37 +147,26 @@ define([ NotebookList.prototype.load_list = function () { var that = this; - var settings = { - processData : false, - cache : false, - type : "GET", - dataType : "json", - success : $.proxy(this.list_loaded, this), - error : $.proxy( function(xhr, status, error){ - utils.log_ajax_error(xhr, status, error); - that.list_loaded([], null, null, {msg:"Error connecting to server."}); - },this) - }; - - var url = utils.url_join_encode( - this.base_url, - 'api', - 'contents', - this.notebook_path + this.contents.list_contents(that.notebook_path).then( + $.proxy(this.draw_notebook_list, this), + function(error) { + that.draw_notebook_list({content: []}, "Server error: " + error.message); + } ); - $.ajax(url, settings); }; - - NotebookList.prototype.list_loaded = function (data, status, xhr, param) { - var message = 'Notebook list empty.'; - if (param !== undefined && param.msg) { - message = param.msg; - } + /** + * Draw the list of notebooks + * @method draw_notebook_list + * @param {Array} list An array of dictionaries representing files or + * directories. + * @param {String} error_msg An error message + */ + NotebookList.prototype.draw_notebook_list = function (list, error_msg) { + var message = error_msg || 'Notebook list empty.'; var item = null; var model = null; - var list = data.content; - var len = list.length; + var len = list.content.length; this.clear_list(); var n_uploads = this.element.children('.list_item').length; if (len === 0) { @@ -186,16 +182,18 @@ define([ model = { type: 'directory', name: '..', - path: path, + path: utils.url_path_split(path)[0], }; this.add_link(model, item); offset += 1; } for (var i=0; i").text("Duplicate").addClass("btn btn-default btn-xs"). + click(function (e) { + // $(this) is the button that was clicked. + var that = $(this); + var name = item.data('name'); + var path = item.data('path'); + var message = 'Are you sure you want to duplicate ' + name + '?'; + var copy_from = {copy_from : path}; + IPython.dialog.modal({ + title : "Duplicate " + name, + body : message, + buttons : { + Duplicate : { + class: "btn-primary", + click: function() { + notebooklist.contents.copy(path, notebooklist.notebook_path).then(function () { + notebooklist.load_list(); + }); + } + }, + Cancel : {} + } + }); + return false; + }); + item.find(".item_buttons").append(duplicate_button); }; NotebookList.prototype.add_delete_button = function (item) { - var new_buttons = $('').addClass("btn-group pull-right"); var notebooklist = this; var delete_button = $("
+ + +
+
+
+
+ +
+ +{% endblock %} + +{% block script %} + + {{super()}} + + +{% endblock %} diff --git a/IPython/html/templates/notebook.html b/IPython/html/templates/notebook.html index 67087f0..1f8b554 100644 --- a/IPython/html/templates/notebook.html +++ b/IPython/html/templates/notebook.html @@ -42,7 +42,7 @@ class="notebook_app" - @@ -59,7 +59,21 @@ class="notebook_app"
@@ -310,26 +317,11 @@ class="notebook_app" {% block script %} {{super()}} - - - - - - - - - - - - - - - - - + + diff --git a/IPython/html/templates/page.html b/IPython/html/templates/page.html index ea0a89d..df49518 100644 --- a/IPython/html/templates/page.html +++ b/IPython/html/templates/page.html @@ -5,7 +5,7 @@ {% block title %}IPython Notebook{% endblock %} - + {% block favicon %}{% endblock %} @@ -14,9 +14,13 @@ {% endblock %} + @@ -76,7 +82,7 @@
+ + {% if terminals_available %} +
+ +
+
+ Currently running terminals +
+
+ + + + +
+
+ +
+
+
There are no terminals running.
+
+
+
+ {% endif %}
diff --git a/IPython/html/terminal/__init__.py b/IPython/html/terminal/__init__.py new file mode 100644 index 0000000..d8d9945 --- /dev/null +++ b/IPython/html/terminal/__init__.py @@ -0,0 +1,18 @@ +import os +from terminado import NamedTermManager +from IPython.html.utils import url_path_join as ujoin +from .handlers import TerminalHandler, TermSocket +from . import api_handlers + +def initialize(webapp): + shell = os.environ.get('SHELL', 'sh') + terminal_manager = webapp.settings['terminal_manager'] = NamedTermManager(shell_command=[shell]) + base_url = webapp.settings['base_url'] + handlers = [ + (ujoin(base_url, r"/terminals/(\w+)"), TerminalHandler), + (ujoin(base_url, r"/terminals/websocket/(\w+)"), TermSocket, + {'term_manager': terminal_manager}), + (ujoin(base_url, r"/api/terminals"), api_handlers.TerminalRootHandler), + (ujoin(base_url, r"/api/terminals/(\w+)"), api_handlers.TerminalHandler), + ] + webapp.add_handlers(".*$", handlers) \ No newline at end of file diff --git a/IPython/html/terminal/api_handlers.py b/IPython/html/terminal/api_handlers.py new file mode 100644 index 0000000..4c7600c --- /dev/null +++ b/IPython/html/terminal/api_handlers.py @@ -0,0 +1,44 @@ +import json +from tornado import web +from ..base.handlers import IPythonHandler, json_errors +from ..utils import url_path_join + +class TerminalRootHandler(IPythonHandler): + @web.authenticated + @json_errors + def get(self): + tm = self.terminal_manager + terms = [{'name': name} for name in tm.terminals] + self.finish(json.dumps(terms)) + + @web.authenticated + @json_errors + def post(self): + """POST /terminals creates a new terminal and redirects to it""" + name, _ = self.terminal_manager.new_named_terminal() + self.finish(json.dumps({'name': name})) + + +class TerminalHandler(IPythonHandler): + SUPPORTED_METHODS = ('GET', 'DELETE') + + @web.authenticated + @json_errors + def get(self, name): + tm = self.terminal_manager + if name in tm.terminals: + self.finish(json.dumps({'name': name})) + else: + raise web.HTTPError(404, "Terminal not found: %r" % name) + + @web.authenticated + @json_errors + def delete(self, name): + tm = self.terminal_manager + if name in tm.terminals: + tm.kill(name) + # XXX: Should this wait for terminal to finish before returning? + self.set_status(204) + self.finish() + else: + raise web.HTTPError(404, "Terminal not found: %r" % name) \ No newline at end of file diff --git a/IPython/html/terminal/handlers.py b/IPython/html/terminal/handlers.py new file mode 100644 index 0000000..06769eb --- /dev/null +++ b/IPython/html/terminal/handlers.py @@ -0,0 +1,28 @@ +#encoding: utf-8 +"""Tornado handlers for the terminal emulator.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import tornado +from tornado import web +import terminado +from ..base.handlers import IPythonHandler + +class TerminalHandler(IPythonHandler): + """Render the terminal interface.""" + @web.authenticated + def get(self, term_name): + self.write(self.render_template('terminal.html', + ws_path="terminals/websocket/%s" % term_name)) + +class TermSocket(terminado.TermSocket, IPythonHandler): + def get(self, *args, **kwargs): + if not self.get_current_user(): + raise web.HTTPError(403) + return super(TermSocket, self).get(*args, **kwargs) + + def clear_cookie(self, *args, **kwargs): + """meaningless for websockets""" + pass + diff --git a/IPython/html/tests/base/keyboard.js b/IPython/html/tests/base/keyboard.js index d74113f..0c50cf0 100644 --- a/IPython/html/tests/base/keyboard.js +++ b/IPython/html/tests/base/keyboard.js @@ -13,7 +13,9 @@ var to_normalize = [ var unshifted = "` 1 2 3 4 5 6 7 8 9 0 - = q w e r t y u i o p [ ] \\ a s d f g h j k l ; ' z x c v b n m , . /"; // shifted = '~ ! @ # $ % ^ & * ( ) _ + Q W E R T Y U I O P { } | A S D F G H J K L : " Z X C V B N M < > ?'; + casper.notebook_test(function () { + var that = this; this.then(function () { this.each(unshifted.split(' '), function (self, item) { @@ -46,4 +48,37 @@ casper.notebook_test(function () { }); }); + this.then(function(){ + + var shortcuts_test = { + 'i,e,e,e,e,e' : '[[5E]]', + 'i,d,d,q,d' : '[[TEST1]]', + 'i,d,d' : '[[TEST1 WILL BE SHADOWED]]', + 'i,d,k' : '[[SHOULD SHADOW TEST2]]', + 'i,d,k,r,q' : '[[TEST2 NOT SHADOWED]]', + ';,up,down,up,down,left,right,left,right,b,a' : '[[KONAMI]]', + 'ctrl-x,meta-c,meta-b,u,t,t,e,r,f,l,y' : '[[XKCD]]' + + }; + + that.msgs = []; + that.on('remote.message', function(msg) { + that.msgs.push(msg); + }) + that.evaluate(function (obj) { + for(var k in obj){ + IPython.keyboard_manager.command_shortcuts.add_shortcut(k, function(){console.log(obj[k])}); + } + }, shortcuts_test); + + var longer_first = false; + var longer_last = false; + for(var m in that.msgs){ + longer_first = longer_first||(that.msgs[m].match(/you are overriting/)!= null); + longer_last = longer_last ||(that.msgs[m].match(/will be shadowed/) != null); + } + this.test.assert(longer_first, 'no warnign if registering shorter shortut'); + this.test.assert(longer_last , 'no warnign if registering longer shortut'); + }) + }); diff --git a/IPython/html/tests/base/utils.js b/IPython/html/tests/base/utils.js new file mode 100644 index 0000000..fe5c878 --- /dev/null +++ b/IPython/html/tests/base/utils.js @@ -0,0 +1,23 @@ +casper.notebook_test(function () { + var input = [ + "\033[0m[\033[0minfo\033[0m] \033[0mtext\033[0m", + "\033[0m[\033[33mwarn\033[0m] \033[0m\tmore text\033[0m", + "\033[0m[\033[33mwarn\033[0m] \033[0m https://some/url/to/a/file.ext\033[0m", + "\033[0m[\033[31merror\033[0m] \033[0m\033[0m", + "\033[0m[\033[31merror\033[0m] \033[0m\teven more text\033[0m", + "\033[0m[\033[31merror\033[0m] \033[0m\t\tand more more text\033[0m"].join("\n"); + + var output = [ + "[info] text", + "[warn] \tmore text", + "[warn] https://some/url/to/a/file.ext", + "[error] ", + "[error] \teven more text", + "[error] \t\tand more more text"].join("\n"); + + var result = this.evaluate(function (input) { + return IPython.utils.fixConsole(input); + }, input); + + this.test.assertEquals(result, output, "IPython.utils.fixConsole() handles [0m correctly"); +}); diff --git a/IPython/html/tests/notebook/deletecell.js b/IPython/html/tests/notebook/deletecell.js new file mode 100644 index 0000000..6c98f28 --- /dev/null +++ b/IPython/html/tests/notebook/deletecell.js @@ -0,0 +1,107 @@ + +// Test +casper.notebook_test(function () { + var that = this; + var cell_is_deletable = function (index) { + // Get the deletable status of a cell. + return that.evaluate(function (index) { + var cell = IPython.notebook.get_cell(index); + return cell.is_deletable(); + }, index); + }; + + var a = 'print("a")'; + var index = this.append_cell(a); + + var b = 'print("b")'; + index = this.append_cell(b); + + var c = 'print("c")'; + index = this.append_cell(c); + + this.thenEvaluate(function() { + IPython.notebook.get_cell(1).metadata.deletable = false; + IPython.notebook.get_cell(2).metadata.deletable = 0; // deletable only when exactly false + IPython.notebook.get_cell(3).metadata.deletable = true; + }); + + this.then(function () { + // Check deletable status of the cells + this.test.assert(cell_is_deletable(0), 'Cell 0 is deletable'); + this.test.assert(!cell_is_deletable(1), 'Cell 1 is not deletable'); + this.test.assert(cell_is_deletable(2), 'Cell 2 is deletable'); + this.test.assert(cell_is_deletable(3), 'Cell 3 is deletable'); + }); + + // Try to delete cell 0 (should succeed) + this.then(function () { + this.select_cell(0); + this.trigger_keydown('esc'); + this.trigger_keydown('d', 'd'); + this.test.assertEquals(this.get_cells_length(), 3, 'Delete cell 0: There are now 3 cells'); + this.test.assertEquals(this.get_cell_text(0), a, 'Delete cell 0: Cell 1 is now cell 0'); + this.test.assertEquals(this.get_cell_text(1), b, 'Delete cell 0: Cell 2 is now cell 1'); + this.test.assertEquals(this.get_cell_text(2), c, 'Delete cell 0: Cell 3 is now cell 2'); + this.validate_notebook_state('dd', 'command', 0); + }); + + // Try to delete cell 0 (should fail) + this.then(function () { + this.select_cell(0); + this.trigger_keydown('d', 'd'); + this.test.assertEquals(this.get_cells_length(), 3, 'Delete cell 0: There are still 3 cells'); + this.test.assertEquals(this.get_cell_text(0), a, 'Delete cell 0: Cell 0 was not deleted'); + this.test.assertEquals(this.get_cell_text(1), b, 'Delete cell 0: Cell 1 was not affected'); + this.test.assertEquals(this.get_cell_text(2), c, 'Delete cell 0: Cell 2 was not affected'); + this.validate_notebook_state('dd', 'command', 0); + }); + + // Try to delete cell 1 (should succeed) + this.then(function () { + this.select_cell(1); + this.trigger_keydown('d', 'd'); + this.test.assertEquals(this.get_cells_length(), 2, 'Delete cell 1: There are now 2 cells'); + this.test.assertEquals(this.get_cell_text(0), a, 'Delete cell 1: Cell 0 was not affected'); + this.test.assertEquals(this.get_cell_text(1), c, 'Delete cell 1: Cell 1 was not affected'); + this.validate_notebook_state('dd', 'command', 1); + }); + + // Try to delete cell 1 (should succeed) + this.then(function () { + this.select_cell(1); + this.trigger_keydown('d', 'd'); + this.test.assertEquals(this.get_cells_length(), 1, 'Delete cell 1: There is now 1 cell'); + this.test.assertEquals(this.get_cell_text(0), a, 'Delete cell 2: Cell 0 was not affected'); + this.validate_notebook_state('dd', 'command', 0); + }); + + // Change the deletable status of the last cells + this.thenEvaluate(function() { + IPython.notebook.get_cell(0).metadata.deletable = true; + }); + + this.then(function () { + // Check deletable status of the cell + this.test.assert(cell_is_deletable(0), 'Cell 0 is deletable'); + + // Try to delete the last cell (should succeed) + this.select_cell(0); + this.trigger_keydown('d', 'd'); + this.test.assertEquals(this.get_cells_length(), 1, 'Delete last cell: There is still 1 cell'); + this.test.assertEquals(this.get_cell_text(0), "", 'Delete last cell: Cell 0 was deleted'); + this.validate_notebook_state('dd', 'command', 0); + }); + + // Make sure copied cells are deletable + this.thenEvaluate(function() { + IPython.notebook.get_cell(0).metadata.deletable = false; + }); + this.then(function () { + this.select_cell(0); + this.trigger_keydown('c', 'v'); + this.test.assertEquals(this.get_cells_length(), 2, 'Copy cell: There are 2 cells'); + this.test.assert(!cell_is_deletable(0), 'Cell 0 is not deletable'); + this.test.assert(cell_is_deletable(1), 'Cell 1 is deletable'); + this.validate_notebook_state('cv', 'command', 1); + }); +}); diff --git a/IPython/html/tests/notebook/dualmode_cellinsert.js b/IPython/html/tests/notebook/dualmode_cellinsert.js index 039babe..1764042 100644 --- a/IPython/html/tests/notebook/dualmode_cellinsert.js +++ b/IPython/html/tests/notebook/dualmode_cellinsert.js @@ -52,12 +52,12 @@ casper.notebook_test(function () { this.then(function () { this.select_cell(2); - this.trigger_keydown('1'); // switch it to heading for the next test - this.test.assertEquals(this.get_cell(2).cell_type, 'heading', 'test cell is heading'); + this.trigger_keydown('y'); // switch it to code for the next test + this.test.assertEquals(this.get_cell(2).cell_type, 'code', 'test cell is code'); this.trigger_keydown('b'); // new cell below - this.test.assertEquals(this.get_cell(3).cell_type, 'heading', 'b; inserts a heading cell below heading cell'); + this.test.assertEquals(this.get_cell(3).cell_type, 'code', 'b; inserts a code cell below code cell'); this.trigger_keydown('a'); // new cell above - this.test.assertEquals(this.get_cell(3).cell_type, 'heading', 'a; inserts a heading cell below heading cell'); + this.test.assertEquals(this.get_cell(3).cell_type, 'code', 'a; inserts a code cell above code cell'); }); this.thenEvaluate(function() { @@ -72,6 +72,6 @@ casper.notebook_test(function () { this.test.assertEquals(this.get_cell(2).cell_type, 'raw', 'a; inserts a raw cell above raw cell'); this.trigger_keydown('y'); // switch it to code for the next test this.trigger_keydown('b'); // new cell below - this.test.assertEquals(this.get_cell(3).cell_type, 'raw', 'b; inserts a raw cell above raw cell'); + this.test.assertEquals(this.get_cell(3).cell_type, 'raw', 'b; inserts a raw cell below raw cell'); }); }); diff --git a/IPython/html/tests/notebook/dualmode_cellmode.js b/IPython/html/tests/notebook/dualmode_cellmode.js index d4bf5f0..3701792 100644 --- a/IPython/html/tests/notebook/dualmode_cellmode.js +++ b/IPython/html/tests/notebook/dualmode_cellmode.js @@ -4,25 +4,38 @@ casper.notebook_test(function () { this.then(function () { // Cell mode change - this.select_cell(0); + var index = 0; + this.select_cell(index); + var a = 'hello\nmulti\nline'; + this.set_cell_text(index, a); this.trigger_keydown('esc','r'); - this.test.assertEquals(this.get_cell(0).cell_type, 'raw', 'r; cell is raw'); + this.test.assertEquals(this.get_cell(index).cell_type, 'raw', 'r; cell is raw'); this.trigger_keydown('1'); - this.test.assertEquals(this.get_cell(0).cell_type, 'heading', '1; cell is heading'); - this.test.assertEquals(this.get_cell(0).level, 1, '1; cell is level 1 heading'); + this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '1; cell is markdown'); + this.test.assertEquals(this.get_cell_text(index), '# ' + a, '1; markdown heading'); this.trigger_keydown('2'); - this.test.assertEquals(this.get_cell(0).level, 2, '2; cell is level 2 heading'); + this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '2; cell is markdown'); + this.test.assertEquals(this.get_cell_text(index), '## ' + a, '2; markdown heading'); this.trigger_keydown('3'); - this.test.assertEquals(this.get_cell(0).level, 3, '3; cell is level 3 heading'); + this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '3; cell is markdown'); + this.test.assertEquals(this.get_cell_text(index), '### ' + a, '3; markdown heading'); this.trigger_keydown('4'); - this.test.assertEquals(this.get_cell(0).level, 4, '4; cell is level 4 heading'); + this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '4; cell is markdown'); + this.test.assertEquals(this.get_cell_text(index), '#### ' + a, '4; markdown heading'); this.trigger_keydown('5'); - this.test.assertEquals(this.get_cell(0).level, 5, '5; cell is level 5 heading'); + this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '5; cell is markdown'); + this.test.assertEquals(this.get_cell_text(index), '##### ' + a, '5; markdown heading'); this.trigger_keydown('6'); - this.test.assertEquals(this.get_cell(0).level, 6, '6; cell is level 6 heading'); + this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '6; cell is markdown'); + this.test.assertEquals(this.get_cell_text(index), '###### ' + a, '6; markdown heading'); this.trigger_keydown('m'); - this.test.assertEquals(this.get_cell(0).cell_type, 'markdown', 'm; cell is markdown'); + this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', 'm; cell is markdown'); + this.test.assertEquals(this.get_cell_text(index), '###### ' + a, 'm; still markdown heading'); this.trigger_keydown('y'); - this.test.assertEquals(this.get_cell(0).cell_type, 'code', 'y; cell is code'); + this.test.assertEquals(this.get_cell(index).cell_type, 'code', 'y; cell is code'); + this.test.assertEquals(this.get_cell_text(index), '###### ' + a, 'y; still has hashes'); + this.trigger_keydown('1'); + this.test.assertEquals(this.get_cell(index).cell_type, 'markdown', '1; cell is markdown'); + this.test.assertEquals(this.get_cell_text(index), '# ' + a, '1; markdown heading'); }); }); \ No newline at end of file diff --git a/IPython/html/tests/notebook/dualmode_merge.js b/IPython/html/tests/notebook/dualmode_merge.js index 573b457..8ec3240 100644 --- a/IPython/html/tests/notebook/dualmode_merge.js +++ b/IPython/html/tests/notebook/dualmode_merge.js @@ -1,6 +1,33 @@ // Test casper.notebook_test(function () { + var a = 'ab\ncd'; + var b = 'print("b")'; + var c = 'print("c")'; + + var that = this; + var cell_is_mergeable = function (index) { + // Get the mergeable status of a cell. + return that.evaluate(function (index) { + var cell = IPython.notebook.get_cell(index); + return cell.is_mergeable(); + }, index); + }; + + var cell_is_splittable = function (index) { + // Get the splittable status of a cell. + return that.evaluate(function (index) { + var cell = IPython.notebook.get_cell(index); + return cell.is_splittable(); + }, index); + }; + + var close_dialog = function () { + this.evaluate(function(){ + $('div.modal-footer button.btn-default').click(); + }, {}); + }; + this.then(function () { // Split and merge cells this.select_cell(0); @@ -16,6 +43,93 @@ casper.notebook_test(function () { this.select_cell(0); // Move up to cell 0 this.trigger_keydown('shift-m'); // Merge this.validate_notebook_state('merge', 'command', 0); - this.test.assertEquals(this.get_cell_text(0), 'ab\ncd', 'merge; Verify that cell 0 has the merged contents.'); + this.test.assertEquals(this.get_cell_text(0), a, 'merge; Verify that cell 0 has the merged contents.'); + }); + + // add some more cells and test splitting/merging when a cell is not deletable + this.then(function () { + this.append_cell(b); + this.append_cell(c); + }); + + this.thenEvaluate(function() { + IPython.notebook.get_cell(1).metadata.deletable = false; + }); + + // Check that merge/split status are correct + this.then(function () { + this.test.assert(cell_is_splittable(0), 'Cell 0 is splittable'); + this.test.assert(cell_is_mergeable(0), 'Cell 0 is mergeable'); + this.test.assert(!cell_is_splittable(1), 'Cell 1 is not splittable'); + this.test.assert(!cell_is_mergeable(1), 'Cell 1 is not mergeable'); + this.test.assert(cell_is_splittable(2), 'Cell 2 is splittable'); + this.test.assert(cell_is_mergeable(2), 'Cell 2 is mergeable'); + }); + + // Try to merge cell 0 below with cell 1 + this.then(function () { + this.select_cell(0); + this.trigger_keydown('esc'); + this.trigger_keydown('shift-m'); + this.test.assertEquals(this.get_cells_length(), 3, 'Merge cell 0 down: There are still 3 cells'); + this.test.assertEquals(this.get_cell_text(0), a, 'Merge cell 0 down: Cell 0 is unchanged'); + this.test.assertEquals(this.get_cell_text(1), b, 'Merge cell 0 down: Cell 1 is unchanged'); + this.test.assertEquals(this.get_cell_text(2), c, 'Merge cell 0 down: Cell 2 is unchanged'); + this.validate_notebook_state('shift-m', 'command', 0); + }); + + // Try to merge cell 1 above with cell 0 + this.then(function () { + this.select_cell(1); + }); + this.thenEvaluate(function () { + IPython.notebook.merge_cell_above(); + }); + this.then(function () { + this.test.assertEquals(this.get_cells_length(), 3, 'Merge cell 1 up: There are still 3 cells'); + this.test.assertEquals(this.get_cell_text(0), a, 'Merge cell 1 up: Cell 0 is unchanged'); + this.test.assertEquals(this.get_cell_text(1), b, 'Merge cell 1 up: Cell 1 is unchanged'); + this.test.assertEquals(this.get_cell_text(2), c, 'Merge cell 1 up: Cell 2 is unchanged'); + this.validate_notebook_state('merge up', 'command', 1); + }); + + // Try to split cell 1 + this.then(function () { + this.select_cell(1); + this.trigger_keydown('enter'); + this.set_cell_editor_cursor(1, 0, 2); + this.trigger_keydown('ctrl-shift-subtract'); // Split + this.test.assertEquals(this.get_cells_length(), 3, 'Split cell 1: There are still 3 cells'); + this.test.assertEquals(this.get_cell_text(0), a, 'Split cell 1: Cell 0 is unchanged'); + this.test.assertEquals(this.get_cell_text(1), b, 'Split cell 1: Cell 1 is unchanged'); + this.test.assertEquals(this.get_cell_text(2), c, 'Split cell 1: Cell 2 is unchanged'); + this.validate_notebook_state('ctrl-shift-subtract', 'edit', 1); + }); + + // Try to merge cell 1 down + this.then(function () { + this.select_cell(1); + this.trigger_keydown('esc'); + this.trigger_keydown('shift-m'); + this.test.assertEquals(this.get_cells_length(), 3, 'Merge cell 1 down: There are still 3 cells'); + this.test.assertEquals(this.get_cell_text(0), a, 'Merge cell 1 down: Cell 0 is unchanged'); + this.test.assertEquals(this.get_cell_text(1), b, 'Merge cell 1 down: Cell 1 is unchanged'); + this.test.assertEquals(this.get_cell_text(2), c, 'Merge cell 1 down: Cell 2 is unchanged'); + this.validate_notebook_state('shift-m', 'command', 1); + }); + + // Try to merge cell 2 above with cell 1 + this.then(function () { + this.select_cell(2); + }); + this.thenEvaluate(function () { + IPython.notebook.merge_cell_above(); + }); + this.then(function () { + this.test.assertEquals(this.get_cells_length(), 3, 'Merge cell 2 up: There are still 3 cells'); + this.test.assertEquals(this.get_cell_text(0), a, 'Merge cell 2 up: Cell 0 is unchanged'); + this.test.assertEquals(this.get_cell_text(1), b, 'Merge cell 2 up: Cell 1 is unchanged'); + this.test.assertEquals(this.get_cell_text(2), c, 'Merge cell 2 up: Cell 2 is unchanged'); + this.validate_notebook_state('merge up', 'command', 2); }); -}); \ No newline at end of file +}); diff --git a/IPython/html/tests/notebook/inject_js.js b/IPython/html/tests/notebook/inject_js.js index 4f72a36..03757aa 100644 --- a/IPython/html/tests/notebook/inject_js.js +++ b/IPython/html/tests/notebook/inject_js.js @@ -13,7 +13,7 @@ casper.notebook_test(function () { this.evaluate(function () { var cell = IPython.notebook.get_cell(0); var json = cell.toJSON(); - json.prompt_number = ""; + json.execution_count = ""; cell.fromJSON(json); }); diff --git a/IPython/html/tests/notebook/markdown.js b/IPython/html/tests/notebook/markdown.js index 5f28d31..4d7897f 100644 --- a/IPython/html/tests/notebook/markdown.js +++ b/IPython/html/tests/notebook/markdown.js @@ -10,38 +10,53 @@ casper.notebook_test(function () { cell.render(); return cell.get_rendered(); }); - this.test.assertEquals(output.trim(), '

Foo

', 'Markdown JS API works.'); + this.test.assertEquals(output.trim(), '

Foo

', 'Markdown JS API works.'); // Test menubar entries. output = this.evaluate(function () { $('#to_code').mouseenter().click(); $('#to_markdown').mouseenter().click(); var cell = IPython.notebook.get_selected_cell(); - cell.set_text('# Bar'); + cell.set_text('**Bar**'); $('#run_cell').mouseenter().click(); return cell.get_rendered(); }); - this.test.assertEquals(output.trim(), '

Bar

', 'Markdown menubar items work.'); + this.test.assertEquals(output.trim(), '

Bar

', 'Markdown menubar items work.'); // Test toolbar buttons. output = this.evaluate(function () { $('#cell_type').val('code').change(); $('#cell_type').val('markdown').change(); var cell = IPython.notebook.get_selected_cell(); - cell.set_text('# Baz'); + cell.set_text('*Baz*'); $('#run_b').click(); return cell.get_rendered(); }); - this.test.assertEquals(output.trim(), '

Baz

', 'Markdown toolbar items work.'); + this.test.assertEquals(output.trim(), '

Baz

', 'Markdown toolbar items work.'); - // Test JavaScript models. - var output = this.evaluate(function () { + // Test markdown headings + var text = 'multi\nline'; + + this.evaluate(function (text) { var cell = IPython.notebook.insert_cell_at_index('markdown', 0); - cell.set_text('# Qux'); - cell.render(); - return cell.get_rendered(); - }); - this.test.assertEquals(output.trim(), '

Qux

', 'Markdown JS API works.'); + cell.set_text(text); + }, {text: text}); + + var set_level = function (level) { + return casper.evaluate(function (level) { + var cell = IPython.notebook.get_cell(0); + cell.set_heading_level(level); + return cell.get_text(); + }, {level: level}); + }; + var level_text; + var levels = [ 1, 2, 3, 4, 5, 6, 2, 1 ]; + for (var idx=0; idx < levels.length; idx++) { + var level = levels[idx]; + level_text = set_level(level); + hashes = new Array(level + 1).join('#'); + this.test.assertEquals(level_text, hashes + ' ' + text, 'markdown set_heading_level ' + level); + } }); diff --git a/IPython/html/tests/notebook/notifications.js b/IPython/html/tests/notebook/notifications.js new file mode 100644 index 0000000..7366930 --- /dev/null +++ b/IPython/html/tests/notebook/notifications.js @@ -0,0 +1,116 @@ +// Test the notification area and widgets + +casper.notebook_test(function () { + var that = this; + var widget = function (name) { + return that.evaluate(function (name) { + return (IPython.notification_area.widget(name) !== undefined); + }, name); + }; + + var get_widget = function (name) { + return that.evaluate(function (name) { + return (IPython.notification_area.get_widget(name) !== undefined); + }, name); + }; + + var new_notification_widget = function (name) { + return that.evaluate(function (name) { + return (IPython.notification_area.new_notification_widget(name) !== undefined); + }, name); + }; + + var widget_has_class = function (name, class_name) { + return that.evaluate(function (name, class_name) { + var w = IPython.notification_area.get_widget(name); + return w.element.hasClass(class_name); + }, name, class_name); + }; + + var widget_message = function (name) { + return that.evaluate(function (name) { + var w = IPython.notification_area.get_widget(name); + return w.get_message(); + }, name); + }; + + this.then(function () { + // check that existing widgets are there + this.test.assert(get_widget('kernel') && widget('kernel'), 'The kernel notification widget exists'); + this.test.assert(get_widget('notebook') && widget('notbook'), 'The notebook notification widget exists'); + + // try getting a non-existant widget + this.test.assertRaises(get_widget, 'foo', 'get_widget: error is thrown'); + + // try creating a non-existant widget + this.test.assert(widget('bar'), 'widget: new widget is created'); + + // try creating a widget that already exists + this.test.assertRaises(new_notification_widget, 'kernel', 'new_notification_widget: error is thrown'); + }); + + // test creating 'info' messages + this.thenEvaluate(function () { + var tnw = IPython.notification_area.widget('test'); + tnw.info('test info'); + }); + this.waitUntilVisible('#notification_test', function () { + this.test.assert(widget_has_class('test', 'info'), 'info: class is correct'); + this.test.assertEquals(widget_message('test'), 'test info', 'info: message is correct'); + }); + + // test creating 'warning' messages + this.thenEvaluate(function () { + var tnw = IPython.notification_area.widget('test'); + tnw.warning('test warning'); + }); + this.waitUntilVisible('#notification_test', function () { + this.test.assert(widget_has_class('test', 'warning'), 'warning: class is correct'); + this.test.assertEquals(widget_message('test'), 'test warning', 'warning: message is correct'); + }); + + // test creating 'danger' messages + this.thenEvaluate(function () { + var tnw = IPython.notification_area.widget('test'); + tnw.danger('test danger'); + }); + this.waitUntilVisible('#notification_test', function () { + this.test.assert(widget_has_class('test', 'danger'), 'danger: class is correct'); + this.test.assertEquals(widget_message('test'), 'test danger', 'danger: message is correct'); + }); + + // test message timeout + this.thenEvaluate(function () { + var tnw = IPython.notification_area.widget('test'); + tnw.set_message('test timeout', 1000); + }); + this.waitUntilVisible('#notification_test', function () { + this.test.assertEquals(widget_message('test'), 'test timeout', 'timeout: message is correct'); + }); + this.waitWhileVisible('#notification_test', function () { + this.test.assertEquals(widget_message('test'), '', 'timeout: message was cleared'); + }); + + // test click callback + this.thenEvaluate(function () { + var tnw = IPython.notification_area.widget('test'); + tnw._clicked = false; + tnw.set_message('test click', undefined, function () { + tnw._clicked = true; + return true; + }); + }); + this.waitUntilVisible('#notification_test', function () { + this.test.assertEquals(widget_message('test'), 'test click', 'callback: message is correct'); + this.click('#notification_test'); + }); + this.waitFor(function () { + return this.evaluate(function () { + return IPython.notification_area.widget('test')._clicked; + }); + }, function () { + this.waitWhileVisible('#notification_test', function () { + this.test.assertEquals(widget_message('test'), '', 'callback: message was cleared'); + }); + }); +}); diff --git a/IPython/html/tests/notebook/output.js b/IPython/html/tests/notebook/output.js index 1fcfe44..b519b64 100644 --- a/IPython/html/tests/notebook/output.js +++ b/IPython/html/tests/notebook/output.js @@ -29,7 +29,7 @@ casper.notebook_test(function () { var ex = expected[i]; this.test.assertEquals(r.output_type, ex.output_type, "output " + i); if (r.output_type === 'stream') { - this.test.assertEquals(r.stream, ex.stream, "stream " + i); + this.test.assertEquals(r.name, ex.name, "stream " + i); this.test.assertEquals(r.text, ex.text, "content " + i); } } @@ -57,7 +57,7 @@ casper.notebook_test(function () { "print(3)" ].join("\n"), [{ output_type: "stream", - stream: "stdout", + name: "stdout", text: "1\n2\n3\n" }] ); @@ -69,11 +69,11 @@ casper.notebook_test(function () { "print(3, file=sys.stderr)" ].join("\n"), [{ output_type: "stream", - stream: "stdout", + name: "stdout", text: "1\n2\n" },{ output_type: "stream", - stream: "stderr", + name: "stderr", text: "3\n" }] ); @@ -85,13 +85,13 @@ casper.notebook_test(function () { "print(3)" ].join("\n"), [{ output_type: "stream", - stream: "stdout", + name: "stdout", text: "1\n" },{ output_type: "display_data", },{ output_type: "stream", - stream: "stdout", + name: "stdout", text: "3\n" }] ); diff --git a/IPython/html/tests/notebook/prompt_numbers.js b/IPython/html/tests/notebook/prompt_numbers.js new file mode 100644 index 0000000..21e6acd --- /dev/null +++ b/IPython/html/tests/notebook/prompt_numbers.js @@ -0,0 +1,36 @@ + +// Test +casper.notebook_test(function () { + + var that = this; + var set_prompt = function (i, val) { + that.evaluate(function (i, val) { + var cell = IPython.notebook.get_cell(i); + cell.set_input_prompt(val); + }, [i, val]); + }; + + var get_prompt = function (i) { + return that.evaluate(function (i) { + var elem = IPython.notebook.get_cell(i).element; + return elem.find('div.input_prompt').html(); + }, [i]); + }; + + this.then(function () { + var a = 'print("a")'; + var index = this.append_cell(a); + + this.test.assertEquals(get_prompt(index), "In [ ]:", "prompt number is   by default"); + set_prompt(index, 2); + this.test.assertEquals(get_prompt(index), "In [2]:", "prompt number is 2"); + set_prompt(index, 0); + this.test.assertEquals(get_prompt(index), "In [0]:", "prompt number is 0"); + set_prompt(index, "*"); + this.test.assertEquals(get_prompt(index), "In [*]:", "prompt number is *"); + set_prompt(index, undefined); + this.test.assertEquals(get_prompt(index), "In [ ]:", "prompt number is  "); + set_prompt(index, null); + this.test.assertEquals(get_prompt(index), "In [ ]:", "prompt number is  "); + }); +}); diff --git a/IPython/html/tests/notebook/roundtrip.js b/IPython/html/tests/notebook/roundtrip.js index 38ef881..1523cf9 100644 --- a/IPython/html/tests/notebook/roundtrip.js +++ b/IPython/html/tests/notebook/roundtrip.js @@ -14,7 +14,7 @@ mime = { "json" : "application/json", "javascript" : "application/javascript", }; - + var black_dot_jpeg="u\"\"\"/9j/4AAQSkZJRgABAQEASABIAAD/2wBDACodICUgGiolIiUvLSoyP2lEPzo6P4FcYUxpmYagnpaG\nk5GovfLNqLPltZGT0v/V5fr/////o8v///////L/////2wBDAS0vLz83P3xERHz/rpOu////////\n////////////////////////////////////////////////////////////wgARCAABAAEDAREA\nAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAABP/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEA\nAhADEAAAARn/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAEFAn//xAAUEQEAAAAAAAAAAAAA\nAAAAAAAA/9oACAEDAQE/AX//xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oACAECAQE/AX//xAAUEAEA\nAAAAAAAAAAAAAAAAAAAA/9oACAEBAAY/An//xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAE/\nIX//2gAMAwEAAgADAAAAEB//xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oACAEDAQE/EH//xAAUEQEA\nAAAAAAAAAAAAAAAAAAAA/9oACAECAQE/EH//xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAE/\nEH//2Q==\"\"\""; var black_dot_png = 'u\"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAWJLR0QA\\niAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB94BCRQnOqNu0b4AAAAKSURBVAjXY2AA\\nAAACAAHiIbwzAAAAAElFTkSuQmCC\"'; var svg = "\"\""; @@ -24,18 +24,20 @@ var svg = "\"raw html" - ] - }, - { - "cell_type": "raw", - "metadata": { - "raw_mimetype": "text/markdown" - }, - "source": [ - "* raw markdown\n", - "* bullet\n", - "* list" - ] - }, - { - "cell_type": "raw", - "metadata": { - "raw_mimetype": "text/restructuredtext" - }, - "source": [ - "``raw rst``\n", - "\n", - ".. sourcecode:: python\n", - "\n", - " def foo(): pass\n" - ] - }, - { - "cell_type": "raw", - "metadata": { - "raw_mimetype": "text/x-python" - }, - "source": [ - "def bar():\n", - " \"\"\"raw python\"\"\"\n", - " pass" - ] - }, - { - "cell_type": "raw", - "metadata": { - "raw_mimetype": "text/latex" - }, - "source": [ - "\\LaTeX\n", - "% raw latex" - ] - }, - { - "cell_type": "raw", - "metadata": {}, - "source": [ - "# no raw_mimetype metadata, should be included by default" - ] - }, - { - "cell_type": "raw", - "metadata": { - "raw_mimetype": "doesnotexist" - }, - "source": [ - "garbage format defined, should never be included" - ] - } - ], - "metadata": {} + "cell_type": "raw", + "metadata": { + "raw_mimetype": "text/html" + }, + "source": [ + "raw html" + ] + }, + { + "cell_type": "raw", + "metadata": { + "raw_mimetype": "text/markdown" + }, + "source": [ + "* raw markdown\n", + "* bullet\n", + "* list" + ] + }, + { + "cell_type": "raw", + "metadata": { + "raw_mimetype": "text/restructuredtext" + }, + "source": [ + "``raw rst``\n", + "\n", + ".. sourcecode:: python\n", + "\n", + " def foo(): pass\n" + ] + }, + { + "cell_type": "raw", + "metadata": { + "raw_mimetype": "text/x-python" + }, + "source": [ + "def bar():\n", + " \"\"\"raw python\"\"\"\n", + " pass" + ] + }, + { + "cell_type": "raw", + "metadata": { + "raw_mimetype": "text/latex" + }, + "source": [ + "\\LaTeX\n", + "% raw latex" + ] + }, + { + "cell_type": "raw", + "metadata": {}, + "source": [ + "# no raw_mimetype metadata, should be included by default" + ] + }, + { + "cell_type": "raw", + "metadata": { + "raw_mimetype": "doesnotexist" + }, + "source": [ + "garbage format defined, should never be included" + ] } - ] -} + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/IPython/nbconvert/exporters/tests/test_export.py b/IPython/nbconvert/exporters/tests/test_export.py index ccf7de5..1b7292a 100644 --- a/IPython/nbconvert/exporters/tests/test_export.py +++ b/IPython/nbconvert/exporters/tests/test_export.py @@ -2,29 +2,17 @@ Module with tests for export.py """ -#----------------------------------------------------------------------------- -# Copyright (c) 2013, the IPython Development Team. -# +# Copyright (c) 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 -from IPython.nbformat import current as nbformat +from IPython import nbformat from .base import ExportersTestsBase from ..export import * from ..python import PythonExporter -#----------------------------------------------------------------------------- -# Class -#----------------------------------------------------------------------------- class TestExport(ExportersTestsBase): """Contains test functions for export.py""" @@ -53,7 +41,7 @@ class TestExport(ExportersTestsBase): Can a notebook be exported by a notebook node handle? """ with open(self._get_notebook(), 'r') as f: - notebook = nbformat.read(f, 'json') + notebook = nbformat.read(f, 4) (output, resources) = export_by_name('python', notebook) assert len(output) > 0 diff --git a/IPython/nbconvert/exporters/tests/test_html.py b/IPython/nbconvert/exporters/tests/test_html.py index ca4941a..be1b2b1 100644 --- a/IPython/nbconvert/exporters/tests/test_html.py +++ b/IPython/nbconvert/exporters/tests/test_html.py @@ -15,6 +15,7 @@ from .base import ExportersTestsBase from ..html import HTMLExporter from IPython.testing.decorators import onlyif_any_cmd_exists +import re #----------------------------------------------------------------------------- # Class @@ -59,3 +60,18 @@ class TestHTMLExporter(ExportersTestsBase): (output, resources) = HTMLExporter(template_file='full').from_filename(self._get_notebook()) assert len(output) > 0 + @onlyif_any_cmd_exists('nodejs', 'node', 'pandoc') + def test_prompt_number(self): + """ + Does HTMLExporter properly format input and output prompts? + """ + (output, resources) = HTMLExporter(template_file='full').from_filename( + self._get_notebook(nb_name="prompt_numbers.ipynb")) + in_regex = r"In \[(.*)\]:" + out_regex = r"Out\[(.*)\]:" + + ins = ["2", "10", " ", " ", "*", "0"] + outs = ["10"] + + assert re.findall(in_regex, output) == ins + assert re.findall(out_regex, output) == outs diff --git a/IPython/nbconvert/exporters/tests/test_latex.py b/IPython/nbconvert/exporters/tests/test_latex.py index a6effc9..28807af 100644 --- a/IPython/nbconvert/exporters/tests/test_latex.py +++ b/IPython/nbconvert/exporters/tests/test_latex.py @@ -5,10 +5,12 @@ import os.path import textwrap +import re from .base import ExportersTestsBase from ..latex import LatexExporter -from IPython.nbformat import current +from IPython.nbformat import write +from IPython.nbformat import v4 from IPython.testing.decorators import onlyif_cmds_exist from IPython.utils.tempdir import TemporaryDirectory @@ -84,18 +86,32 @@ class TestLatexExporter(ExportersTestsBase): large_lorem_ipsum_text = "".join([lorem_ipsum_text]*3000) notebook_name = "lorem_ipsum_long.ipynb" - nb = current.new_notebook( - worksheets=[ - current.new_worksheet(cells=[ - current.new_text_cell('markdown',source=large_lorem_ipsum_text) - ]) + nb = v4.new_notebook( + cells=[ + v4.new_markdown_cell(source=large_lorem_ipsum_text) ] ) with TemporaryDirectory() as td: nbfile = os.path.join(td, notebook_name) with open(nbfile, 'w') as f: - current.write(nb, f, 'ipynb') + write(nb, f, 4) - (output, resources) = LatexExporter(template_file='article').from_filename(nbfile) + (output, resources) = LatexExporter(template_file='article').from_filename(nbfile) assert len(output) > 0 + + @onlyif_cmds_exist('pandoc') + def test_prompt_number_color(self): + """ + Does LatexExporter properly format input and output prompts in color? + """ + (output, resources) = LatexExporter().from_filename( + self._get_notebook(nb_name="prompt_numbers.ipynb")) + in_regex = r"In \[\{\\color\{incolor\}(.*)\}\]:" + out_regex = r"Out\[\{\\color\{outcolor\}(.*)\}\]:" + + ins = ["2", "10", " ", " ", "*", "0"] + outs = ["10"] + + assert re.findall(in_regex, output) == ins + assert re.findall(out_regex, output) == outs diff --git a/IPython/nbconvert/exporters/tests/test_notebook.py b/IPython/nbconvert/exporters/tests/test_notebook.py index a5d3a04..4fefb1c 100644 --- a/IPython/nbconvert/exporters/tests/test_notebook.py +++ b/IPython/nbconvert/exporters/tests/test_notebook.py @@ -3,9 +3,14 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import json + from .base import ExportersTestsBase from ..notebook import NotebookExporter +from IPython.nbformat import validate +from IPython.testing.tools import assert_big_text_equal + class TestNotebookExporter(ExportersTestsBase): """Contains test functions for notebook.py""" @@ -17,6 +22,18 @@ class TestNotebookExporter(ExportersTestsBase): """ with open(self._get_notebook()) as f: file_contents = f.read() - (output, resources) = NotebookExporter().from_filename(self._get_notebook()) + (output, resources) = self.exporter_class().from_filename(self._get_notebook()) assert len(output) > 0 - assert output == file_contents + assert_big_text_equal(output, file_contents) + + def test_downgrade_3(self): + exporter = self.exporter_class(nbformat_version=3) + (output, resources) = exporter.from_filename(self._get_notebook()) + nb = json.loads(output) + validate(nb) + + def test_downgrade_2(self): + exporter = self.exporter_class(nbformat_version=2) + (output, resources) = exporter.from_filename(self._get_notebook()) + nb = json.loads(output) + self.assertEqual(nb['nbformat'], 2) diff --git a/IPython/nbconvert/exporters/tests/test_rst.py b/IPython/nbconvert/exporters/tests/test_rst.py index c9840a9..1ccdb80 100644 --- a/IPython/nbconvert/exporters/tests/test_rst.py +++ b/IPython/nbconvert/exporters/tests/test_rst.py @@ -1,28 +1,17 @@ """Tests for RSTExporter""" -#----------------------------------------------------------------------------- -# Copyright (c) 2013, the IPython Development Team. -# +# Copyright (c) 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 io -from IPython.nbformat import current +from IPython import nbformat +from IPython.nbformat import v4 from .base import ExportersTestsBase from ..rst import RSTExporter from IPython.testing.decorators import onlyif_cmds_exist -#----------------------------------------------------------------------------- -# Class -#----------------------------------------------------------------------------- class TestRSTExporter(ExportersTestsBase): """Tests for RSTExporter""" @@ -50,14 +39,14 @@ class TestRSTExporter(ExportersTestsBase): """No empty code cells in rst""" nbname = self._get_notebook() with io.open(nbname, encoding='utf8') as f: - nb = current.read(f, 'json') + nb = nbformat.read(f, 4) exporter = self.exporter_class() (output, resources) = exporter.from_notebook_node(nb) # add an empty code cell - nb.worksheets[0].cells.append( - current.new_code_cell(input="") + nb.cells.append( + v4.new_code_cell(source="") ) (output2, resources) = exporter.from_notebook_node(nb) # adding an empty code cell shouldn't change output diff --git a/IPython/nbconvert/filters/highlight.py b/IPython/nbconvert/filters/highlight.py index 61cb654..59c0df1 100644 --- a/IPython/nbconvert/filters/highlight.py +++ b/IPython/nbconvert/filters/highlight.py @@ -11,6 +11,7 @@ from within Jinja templates. # not import time, when it may not be needed. from IPython.nbconvert.utils.base import NbConvertBase +from warnings import warn MULTILINE_OUTPUTS = ['text', 'html', 'svg', 'latex', 'javascript', 'json'] @@ -20,6 +21,14 @@ __all__ = [ ] class Highlight2HTML(NbConvertBase): + def __init__(self, pygments_lexer=None, **kwargs): + self.pygments_lexer = pygments_lexer or 'ipython3' + super(Highlight2HTML, self).__init__(**kwargs) + + def _default_language_changed(self, name, old, new): + warn('Setting default_language in config is deprecated, ' + 'please use language_info metadata instead.') + self.pygments_lexer = new def __call__(self, source, language=None, metadata=None): """ @@ -35,8 +44,9 @@ class Highlight2HTML(NbConvertBase): metadata of the cell to highlight """ from pygments.formatters import HtmlFormatter + if not language: - language=self.default_language + language=self.pygments_lexer return _pygments_highlight(source if len(source) > 0 else ' ', # needed to help post processors: @@ -45,6 +55,14 @@ class Highlight2HTML(NbConvertBase): class Highlight2Latex(NbConvertBase): + def __init__(self, pygments_lexer=None, **kwargs): + self.pygments_lexer = pygments_lexer or 'ipython3' + super(Highlight2Latex, self).__init__(**kwargs) + + def _default_language_changed(self, name, old, new): + warn('Setting default_language in config is deprecated, ' + 'please use language_info metadata instead.') + self.pygments_lexer = new def __call__(self, source, language=None, metadata=None, strip_verbatim=False): """ @@ -63,7 +81,7 @@ class Highlight2Latex(NbConvertBase): """ from pygments.formatters import LatexFormatter if not language: - language=self.default_language + language=self.pygments_lexer latex = _pygments_highlight(source, LatexFormatter(), language, metadata) if strip_verbatim: @@ -90,7 +108,8 @@ def _pygments_highlight(source, output_formatter, language='ipython', metadata=N """ from pygments import highlight from pygments.lexers import get_lexer_by_name - from IPython.nbconvert.utils.lexers import IPythonLexer + from pygments.util import ClassNotFound + from IPython.nbconvert.utils.lexers import IPythonLexer, IPython3Lexer # If the cell uses a magic extension language, # use the magic language instead. @@ -100,9 +119,17 @@ def _pygments_highlight(source, output_formatter, language='ipython', metadata=N language = metadata['magics_language'] - if language == 'ipython': + if language == 'ipython2': lexer = IPythonLexer() + elif language == 'ipython3': + lexer = IPython3Lexer() else: - lexer = get_lexer_by_name(language, stripall=True) + try: + lexer = get_lexer_by_name(language, stripall=True) + except ClassNotFound: + warn("No lexer found for language %r. Treating as plain text." % language) + from pygments.lexers.special import TextLexer + lexer = TextLexer() + return highlight(source, lexer, output_formatter) diff --git a/IPython/nbconvert/filters/markdown.py b/IPython/nbconvert/filters/markdown.py index c880b0e..b36f336 100755 --- a/IPython/nbconvert/filters/markdown.py +++ b/IPython/nbconvert/filters/markdown.py @@ -21,6 +21,7 @@ from pygments.formatters import HtmlFormatter from pygments.util import ClassNotFound # IPython imports +from IPython.nbconvert.filters.strings import add_anchor from IPython.nbconvert.utils.pandoc import pandoc from IPython.nbconvert.utils.exceptions import ConversionException from IPython.utils.decorators import undoc @@ -45,7 +46,7 @@ class NodeJSMissing(ConversionException): """Exception raised when node.js is missing.""" pass -def markdown2latex(source, extra_args=None): +def markdown2latex(source, markup='markdown', extra_args=None): """Convert a markdown string to LaTeX via pandoc. This function will raise an error if pandoc is not installed. @@ -55,13 +56,17 @@ def markdown2latex(source, extra_args=None): ---------- source : string Input string, assumed to be valid markdown. + markup : string + Markup used by pandoc's reader + default : pandoc extended markdown + (see http://johnmacfarlane.net/pandoc/README.html#pandocs-markdown) Returns ------- out : string Output as returned by pandoc. """ - return pandoc(source, 'markdown', 'latex', extra_args=extra_args) + return pandoc(source, markup, 'latex', extra_args=extra_args) @undoc @@ -96,6 +101,7 @@ class MathBlockLexer(mistune.BlockLexer): @undoc class MathInlineGrammar(mistune.InlineGrammar): math = re.compile("^\$(.+?)\$") + text = re.compile(r'^[\s\S]+?(?=[\\test', 'id="test"', u'¶', "anchor-link") + ), + ('###test head space', + ('test head space', 'id="test-head-space"', u'¶', "anchor-link") + ) + ]: + self._try_markdown(markdown2html, md, tokens) + def test_markdown2html_math(self): # Mathematical expressions should be passed through unaltered cases = [("\\begin{equation*}\n" @@ -86,6 +123,12 @@ class TestMarkdown(TestsBase): for case in cases: self.assertIn(case, markdown2html(case)) + def test_markdown2html_math_paragraph(self): + # https://github.com/ipython/ipython/issues/6724 + a = """Water that is stored in $t$, $s_t$, must equal the storage content of the previous stage, +$s_{t-1}$, plus a stochastic inflow, $I_t$, minus what is being released in $t$, $r_t$. +With $s_0$ defined as the initial storage content in $t=1$, we have""" + self.assertIn(a, markdown2html(a)) @dec.onlyif_cmds_exist('pandoc') def test_markdown2rst(self): diff --git a/IPython/nbconvert/filters/tests/test_strings.py b/IPython/nbconvert/filters/tests/test_strings.py index 7b24590..0340cbb 100644 --- a/IPython/nbconvert/filters/tests/test_strings.py +++ b/IPython/nbconvert/filters/tests/test_strings.py @@ -18,7 +18,7 @@ import os from ...tests.base import TestsBase from ..strings import (wrap_text, html2text, add_anchor, strip_dollars, strip_files_prefix, get_lines, comment_lines, ipython2python, posix_path, - add_prompts + add_prompts, prevent_list_blocks ) @@ -151,3 +151,15 @@ class TestStrings(TestsBase): text1 = """for i in range(10):\n i += 1\n print i""" text2 = """>>> for i in range(10):\n... i += 1\n... print i""" self.assertEqual(text2, add_prompts(text1)) + + def test_prevent_list_blocks(self): + """prevent_list_blocks test""" + tests = [ + ('1. arabic point', '1\\. arabic point'), + ('* bullet asterisk', '\\* bullet asterisk'), + ('+ bullet Plus Sign', '\\+ bullet Plus Sign'), + ('- bullet Hyphen-Minus', '\\- bullet Hyphen-Minus'), + (' 1. spaces + arabic point', ' 1\\. spaces + arabic point'), + ] + for test in tests: + self.assertEqual(prevent_list_blocks(test[0]), test[1]) diff --git a/IPython/nbconvert/nbconvertapp.py b/IPython/nbconvert/nbconvertapp.py index c105728..115b645 100755 --- a/IPython/nbconvert/nbconvertapp.py +++ b/IPython/nbconvert/nbconvertapp.py @@ -53,11 +53,16 @@ nbconvert_aliases.update({ 'post': 'NbConvertApp.postprocessor_class', 'output': 'NbConvertApp.output_base', 'reveal-prefix': 'RevealHelpPreprocessor.url_prefix', + 'nbformat': 'NotebookExporter.nbformat_version', }) nbconvert_flags = {} nbconvert_flags.update(base_flags) nbconvert_flags.update({ + 'execute' : ( + {'ExecutePreprocessor' : {'enabled' : True}}, + "Execute the notebook prior to export." + ), 'stdout' : ( {'NbConvertApp' : {'writer_class' : "StdoutWriter"}}, "Write notebook output to stdout instead of files." @@ -92,7 +97,7 @@ class NbConvertApp(BaseIPythonApplication): WARNING: THE COMMANDLINE INTERFACE MAY CHANGE IN FUTURE RELEASES.""") output_base = Unicode('', config=True, help='''overwrite base name use for output files. - can only be use when converting one notebook at a time. + can only be used when converting one notebook at a time. ''') examples = Unicode(u""" @@ -281,7 +286,7 @@ class NbConvertApp(BaseIPythonApplication): # strip duplicate extension from output_base, to avoid Basname.ext.ext if getattr(exporter, 'file_extension', False): base, ext = os.path.splitext(self.output_base) - if ext == '.' + exporter.file_extension: + if ext == exporter.file_extension: self.output_base = base notebook_name = self.output_base resources = {} @@ -298,6 +303,8 @@ class NbConvertApp(BaseIPythonApplication): exc_info=True) self.exit(1) else: + if 'output_suffix' in resources and not self.output_base: + notebook_name += resources['output_suffix'] write_results = self.writer.write(output, resources, notebook_name=notebook_name) #Post-process if post processor has been defined. diff --git a/IPython/nbconvert/preprocessors/base.py b/IPython/nbconvert/preprocessors/base.py index 62e4d4a..30843a8 100755 --- a/IPython/nbconvert/preprocessors/base.py +++ b/IPython/nbconvert/preprocessors/base.py @@ -66,9 +66,8 @@ class Preprocessor(NbConvertBase): Additional resources used in the conversion process. Allows preprocessors to pass variables into the Jinja engine. """ - for worksheet in nb.worksheets: - for index, cell in enumerate(worksheet.cells): - worksheet.cells[index], resources = self.preprocess_cell(cell, resources, index) + for index, cell in enumerate(nb.cells): + nb.cells[index], resources = self.preprocess_cell(cell, resources, index) return nb, resources diff --git a/IPython/nbconvert/preprocessors/clearoutput.py b/IPython/nbconvert/preprocessors/clearoutput.py index 61c4b7f..f28f189 100644 --- a/IPython/nbconvert/preprocessors/clearoutput.py +++ b/IPython/nbconvert/preprocessors/clearoutput.py @@ -3,16 +3,8 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - from .base import Preprocessor - -#----------------------------------------------------------------------------- -# Classes -#----------------------------------------------------------------------------- class ClearOutputPreprocessor(Preprocessor): """ Removes the output from all code cells in a notebook. @@ -24,5 +16,5 @@ class ClearOutputPreprocessor(Preprocessor): """ if cell.cell_type == 'code': cell.outputs = [] - cell.prompt_number = None + cell.execution_count = None return cell, resources diff --git a/IPython/nbconvert/preprocessors/coalescestreams.py b/IPython/nbconvert/preprocessors/coalescestreams.py index 12dbc51..f7d4aba 100644 --- a/IPython/nbconvert/preprocessors/coalescestreams.py +++ b/IPython/nbconvert/preprocessors/coalescestreams.py @@ -4,6 +4,7 @@ # Distributed under the terms of the Modified BSD License. import re +from IPython.utils.log import get_logger def cell_preprocessor(function): """ @@ -21,14 +22,11 @@ def cell_preprocessor(function): """ def wrappedfunc(nb, resources): - from IPython.config import Application - if Application.initialized(): - Application.instance().log.debug( + get_logger().debug( "Applying preprocessor: %s", function.__name__ ) - for worksheet in nb.worksheets: - for index, cell in enumerate(worksheet.cells): - worksheet.cells[index], resources = function(cell, resources, index) + for index, cell in enumerate(nb.cells): + nb.cells[index], resources = function(cell, resources, index) return nb, resources return wrappedfunc @@ -60,7 +58,7 @@ def coalesce_streams(cell, resources, index): for output in outputs[1:]: if (output.output_type == 'stream' and last.output_type == 'stream' and - last.stream == output.stream + last.name == output.name ): last.text += output.text diff --git a/IPython/nbconvert/preprocessors/convertfigures.py b/IPython/nbconvert/preprocessors/convertfigures.py index d0dd3dc..3fafe73 100644 --- a/IPython/nbconvert/preprocessors/convertfigures.py +++ b/IPython/nbconvert/preprocessors/convertfigures.py @@ -47,18 +47,12 @@ class ConvertFiguresPreprocessor(Preprocessor): """ # Loop through all of the datatypes of the outputs in the cell. - for index, cell_out in enumerate(cell.get('outputs', [])): - for data_type, data in list(cell_out.items()): - # this must run *before* extract outputs, - # so figure_name and filename do not exist - self._convert_figure(cell_out, resources, data_type, data) - return cell, resources + for output in cell.get('outputs', []): + if output.output_type in {'execute_result', 'display_data'} \ + and self.from_format in output.data \ + and self.to_format not in output.data: + output.data[self.to_format] = self.convert_figure( + self.from_format, output.data[self.from_format]) - def _convert_figure(self, cell_out, resources, data_type, data): - """ - Convert a figure and output the results to the cell output - """ - if not self.to_format in cell_out and data_type == self.from_format: - data = self.convert_figure(data_type, data) - cell_out[self.to_format] = data + return cell, resources diff --git a/IPython/nbconvert/preprocessors/execute.py b/IPython/nbconvert/preprocessors/execute.py index 1d54c91..4360b6b 100644 --- a/IPython/nbconvert/preprocessors/execute.py +++ b/IPython/nbconvert/preprocessors/execute.py @@ -4,7 +4,6 @@ # Distributed under the terms of the Modified BSD License. import os -import sys try: from queue import Empty # Py 3 @@ -13,10 +12,11 @@ except ImportError: from IPython.utils.traitlets import List, Unicode -from IPython.nbformat.current import reads, NotebookNode, writes +from IPython.nbformat.v4 import output_from_msg from .base import Preprocessor from IPython.utils.traitlets import Integer + class ExecutePreprocessor(Preprocessor): """ Executes all the cells in a notebook @@ -25,31 +25,13 @@ class ExecutePreprocessor(Preprocessor): timeout = Integer(30, config=True, help="The time to wait (in seconds) for output from executions." ) - # FIXME: to be removed with nbformat v4 - # map msg_type to v3 output_type - msg_type_map = { - "error" : "pyerr", - "execute_result" : "pyout", - } - - # FIXME: to be removed with nbformat v4 - # map mime-type to v3 mime-type keys - mime_map = { - "text/plain" : "text", - "text/html" : "html", - "image/svg+xml" : "svg", - "image/png" : "png", - "image/jpeg" : "jpeg", - "text/latex" : "latex", - "application/json" : "json", - "application/javascript" : "javascript", - } extra_arguments = List(Unicode) def preprocess(self, nb, resources): from IPython.kernel import run_kernel kernel_name = nb.metadata.get('kernelspec', {}).get('name', 'python') + self.log.info("Executing notebook with kernel: %s" % kernel_name) with run_kernel(kernel_name=kernel_name, extra_arguments=self.extra_arguments, stderr=open(os.devnull, 'w')) as kc: @@ -67,14 +49,14 @@ class ExecutePreprocessor(Preprocessor): outputs = self.run_cell(self.kc.shell_channel, self.kc.iopub_channel, cell) except Exception as e: self.log.error("failed to run cell: " + repr(e)) - self.log.error(str(cell.input)) + self.log.error(str(cell.source)) raise cell.outputs = outputs return cell, resources def run_cell(self, shell, iopub, cell): - msg_id = shell.execute(cell.input) - self.log.debug("Executing cell:\n%s", cell.input) + msg_id = shell.execute(cell.source) + self.log.debug("Executing cell:\n%s", cell.source) # wait for finish, with timeout while True: try: @@ -103,40 +85,27 @@ class ExecutePreprocessor(Preprocessor): msg_type = msg['msg_type'] self.log.debug("output: %s", msg_type) content = msg['content'] - out = NotebookNode(output_type=self.msg_type_map.get(msg_type, msg_type)) # set the prompt number for the input and the output if 'execution_count' in content: - cell['prompt_number'] = content['execution_count'] - out.prompt_number = content['execution_count'] + cell['execution_count'] = content['execution_count'] if msg_type == 'status': if content['execution_state'] == 'idle': break else: continue - elif msg_type in {'execute_input', 'pyin'}: + elif msg_type == 'execute_input': continue elif msg_type == 'clear_output': outs = [] continue - if msg_type == 'stream': - out.stream = content['name'] - out.text = content['data'] - elif msg_type in ('display_data', 'execute_result'): - out['metadata'] = content['metadata'] - for mime, data in content['data'].items(): - # map mime-type keys to nbformat v3 keys - # this will be unnecessary in nbformat v4 - key = self.mime_map.get(mime, mime) - out[key] = data - elif msg_type == 'error': - out.ename = content['ename'] - out.evalue = content['evalue'] - out.traceback = content['traceback'] - else: + try: + out = output_from_msg(msg) + except ValueError: self.log.error("unhandled iopub msg: " + msg_type) + else: + outs.append(out) - outs.append(out) return outs diff --git a/IPython/nbconvert/preprocessors/extractoutput.py b/IPython/nbconvert/preprocessors/extractoutput.py index 2e70338..ee1e0af 100755 --- a/IPython/nbconvert/preprocessors/extractoutput.py +++ b/IPython/nbconvert/preprocessors/extractoutput.py @@ -1,17 +1,9 @@ -"""Module containing a preprocessor that extracts all of the outputs from the +"""A preprocessor that extracts all of the outputs from the notebook file. The extracted outputs are returned in the 'resources' dictionary. """ -#----------------------------------------------------------------------------- -# Copyright (c) 2013, the 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 -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. import base64 import sys @@ -22,9 +14,6 @@ from IPython.utils.traitlets import Unicode, Set from .base import Preprocessor from IPython.utils import py3compat -#----------------------------------------------------------------------------- -# Classes -#----------------------------------------------------------------------------- class ExtractOutputPreprocessor(Preprocessor): """ @@ -35,7 +24,7 @@ class ExtractOutputPreprocessor(Preprocessor): output_filename_template = Unicode( "{unique_key}_{cell_index}_{index}{extension}", config=True) - extract_output_types = Set({'png', 'jpeg', 'svg', 'application/pdf'}, config=True) + extract_output_types = Set({'image/png', 'image/jpeg', 'image/svg+xml', 'application/pdf'}, config=True) def preprocess_cell(self, cell, resources, cell_index): """ @@ -64,14 +53,15 @@ class ExtractOutputPreprocessor(Preprocessor): #Loop through all of the outputs in the cell for index, out in enumerate(cell.get('outputs', [])): - + if out.output_type not in {'display_data', 'execute_result'}: + continue #Get the output in data formats that the template needs extracted - for out_type in self.extract_output_types: - if out_type in out: - data = out[out_type] + for mime_type in self.extract_output_types: + if mime_type in out.data: + data = out.data[mime_type] #Binary files are base64-encoded, SVG is already XML - if out_type in {'png', 'jpeg', 'application/pdf'}: + if mime_type in {'image/png', 'image/jpeg', 'application/pdf'}: # data is b64-encoded as text (str, unicode) # decodestring only accepts bytes @@ -82,14 +72,9 @@ class ExtractOutputPreprocessor(Preprocessor): else: data = data.encode("UTF-8") - # Build an output name - # filthy hack while we have some mimetype output, and some not - if '/' in out_type: - ext = guess_extension(out_type) - if ext is None: - ext = '.' + out_type.rsplit('/')[-1] - else: - ext = '.' + out_type + ext = guess_extension(mime_type) + if ext is None: + ext = '.' + mime_type.rsplit('/')[-1] filename = self.output_filename_template.format( unique_key=unique_key, @@ -97,13 +82,14 @@ class ExtractOutputPreprocessor(Preprocessor): index=index, extension=ext) - #On the cell, make the figure available via - # cell.outputs[i].svg_filename ... etc (svg in example) - # Where - # cell.outputs[i].svg contains the data + # On the cell, make the figure available via + # cell.outputs[i].metadata.filenames['mime/type'] + # where + # cell.outputs[i].data['mime/type'] contains the data if output_files_dir is not None: filename = os.path.join(output_files_dir, filename) - out[out_type + '_filename'] = filename + out.metadata.setdefault('filenames', {}) + out.metadata['filenames'][mime_type] = filename #In the resources, make the figure available via # resources['outputs']['filename'] = data diff --git a/IPython/nbconvert/preprocessors/highlightmagics.py b/IPython/nbconvert/preprocessors/highlightmagics.py index 135fe4e..83356d6 100644 --- a/IPython/nbconvert/preprocessors/highlightmagics.py +++ b/IPython/nbconvert/preprocessors/highlightmagics.py @@ -4,17 +4,8 @@ so that the appropriate highlighter can be used in the `highlight` filter. """ -#----------------------------------------------------------------------------- -# Copyright (c) 2013, the IPython Development Team. -# +# Copyright (c) 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 __future__ import print_function, absolute_import @@ -24,10 +15,6 @@ import re from .base import Preprocessor from IPython.utils.traitlets import Dict -#----------------------------------------------------------------------------- -# Classes -#----------------------------------------------------------------------------- - class HighlightMagicsPreprocessor(Preprocessor): """ @@ -106,8 +93,8 @@ class HighlightMagicsPreprocessor(Preprocessor): """ # Only tag code cells - if hasattr(cell, "input") and cell.cell_type == "code": - magic_language = self.which_magic_language(cell.input) + if cell.cell_type == "code": + magic_language = self.which_magic_language(cell.source) if magic_language: cell['metadata']['magics_language'] = magic_language return cell, resources diff --git a/IPython/nbconvert/preprocessors/revealhelp.py b/IPython/nbconvert/preprocessors/revealhelp.py index a4d90fe..7b09c7f 100755 --- a/IPython/nbconvert/preprocessors/revealhelp.py +++ b/IPython/nbconvert/preprocessors/revealhelp.py @@ -1,23 +1,11 @@ -"""Module that pre-processes the notebook for export via Reveal. -""" -#----------------------------------------------------------------------------- -# Copyright (c) 2013, the 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. -#----------------------------------------------------------------------------- +"""Module that pre-processes the notebook for export via Reveal.""" -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. from .base import Preprocessor from IPython.utils.traitlets import Unicode -#----------------------------------------------------------------------------- -# Classes and functions -#----------------------------------------------------------------------------- class RevealHelpPreprocessor(Preprocessor): @@ -43,31 +31,30 @@ class RevealHelpPreprocessor(Preprocessor): preprocessors to pass variables into the Jinja engine. """ - for worksheet in nb.worksheets: - for index, cell in enumerate(worksheet.cells): - - #Make sure the cell has slideshow metadata. - cell.metadata.slide_type = cell.get('metadata', {}).get('slideshow', {}).get('slide_type', '-') - - #Get the slide type. If type is start of subslide or slide, - #end the last subslide/slide. - if cell.metadata.slide_type in ['slide']: - worksheet.cells[index - 1].metadata.slide_helper = 'slide_end' - if cell.metadata.slide_type in ['subslide']: - worksheet.cells[index - 1].metadata.slide_helper = 'subslide_end' - #Prevent the rendering of "do nothing" cells before fragments - #Group fragments passing frag_number to the data-fragment-index - if cell.metadata.slide_type in ['fragment']: - worksheet.cells[index].metadata.frag_number = index - i = 1 - while i < len(worksheet.cells) - index: - worksheet.cells[index + i].metadata.frag_helper = 'fragment_end' - worksheet.cells[index + i].metadata.frag_number = index - i += 1 - #Restart the slide_helper when the cell status is changed - #to other types. - if cell.metadata.slide_type in ['-', 'skip', 'notes', 'fragment']: - worksheet.cells[index - 1].metadata.slide_helper = '-' + for index, cell in enumerate(nb.cells): + + #Make sure the cell has slideshow metadata. + cell.metadata.slide_type = cell.get('metadata', {}).get('slideshow', {}).get('slide_type', '-') + + # Get the slide type. If type is start, subslide, or slide, + # end the last subslide/slide. + if cell.metadata.slide_type in ['slide']: + nb.cells[index - 1].metadata.slide_helper = 'slide_end' + if cell.metadata.slide_type in ['subslide']: + nb.cells[index - 1].metadata.slide_helper = 'subslide_end' + # Prevent the rendering of "do nothing" cells before fragments + # Group fragments passing frag_number to the data-fragment-index + if cell.metadata.slide_type in ['fragment']: + nb.cells[index].metadata.frag_number = index + i = 1 + while i < len(nb.cells) - index: + nb.cells[index + i].metadata.frag_helper = 'fragment_end' + nb.cells[index + i].metadata.frag_number = index + i += 1 + # Restart the slide_helper when the cell status is changed + # to other types. + if cell.metadata.slide_type in ['-', 'skip', 'notes', 'fragment']: + nb.cells[index - 1].metadata.slide_helper = '-' if not isinstance(resources['reveal'], dict): resources['reveal'] = {} diff --git a/IPython/nbconvert/preprocessors/svg2pdf.py b/IPython/nbconvert/preprocessors/svg2pdf.py index c97860e..02aee30 100644 --- a/IPython/nbconvert/preprocessors/svg2pdf.py +++ b/IPython/nbconvert/preprocessors/svg2pdf.py @@ -1,17 +1,9 @@ """Module containing a preprocessor that converts outputs in the notebook from one format to another. """ -#----------------------------------------------------------------------------- -# Copyright (c) 2013, the 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 -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. import base64 import io @@ -19,21 +11,15 @@ import os import sys import subprocess +from IPython.utils.py3compat import cast_unicode_py2 from IPython.utils.tempdir import TemporaryDirectory from IPython.utils.traitlets import Unicode from .convertfigures import ConvertFiguresPreprocessor -#----------------------------------------------------------------------------- -# Constants -#----------------------------------------------------------------------------- - INKSCAPE_APP = '/Applications/Inkscape.app/Contents/Resources/bin/inkscape' -#----------------------------------------------------------------------------- -# Classes -#----------------------------------------------------------------------------- class SVG2PDFPreprocessor(ConvertFiguresPreprocessor): """ @@ -41,7 +27,7 @@ class SVG2PDFPreprocessor(ConvertFiguresPreprocessor): """ def _from_format_default(self): - return 'svg' + return 'image/svg+xml' def _to_format_default(self): return 'application/pdf' @@ -76,10 +62,10 @@ class SVG2PDFPreprocessor(ConvertFiguresPreprocessor): with TemporaryDirectory() as tmpdir: #Write fig to temp file - input_filename = os.path.join(tmpdir, 'figure.' + data_format) + input_filename = os.path.join(tmpdir, 'figure.svg') # SVG data is unicode text with io.open(input_filename, 'w', encoding='utf8') as f: - f.write(data) + f.write(cast_unicode_py2(data)) #Call conversion application output_filename = os.path.join(tmpdir, 'figure.pdf') diff --git a/IPython/nbconvert/preprocessors/tests/base.py b/IPython/nbconvert/preprocessors/tests/base.py index 81fe492..e27cd57 100644 --- a/IPython/nbconvert/preprocessors/tests/base.py +++ b/IPython/nbconvert/preprocessors/tests/base.py @@ -1,27 +1,13 @@ -""" -Module with utility functions for preprocessor tests -""" +"""utility functions for preprocessor tests""" -#----------------------------------------------------------------------------- -# Copyright (c) 2013, the IPython Development Team. -# +# Copyright (c) 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 IPython.nbformat import current as nbformat +from IPython.nbformat import v4 as nbformat from ...tests.base import TestsBase from ...exporters.exporter import ResourcesDict -#----------------------------------------------------------------------------- -# Class -#----------------------------------------------------------------------------- class PreprocessorTestsBase(TestsBase): """Contains test functions preprocessor tests""" @@ -30,22 +16,21 @@ class PreprocessorTestsBase(TestsBase): def build_notebook(self): """Build a notebook in memory for use with preprocessor tests""" - outputs = [nbformat.new_output(output_type="stream", stream="stdout", output_text="a"), - nbformat.new_output(output_type="text", output_text="b"), - nbformat.new_output(output_type="stream", stream="stdout", output_text="c"), - nbformat.new_output(output_type="stream", stream="stdout", output_text="d"), - nbformat.new_output(output_type="stream", stream="stderr", output_text="e"), - nbformat.new_output(output_type="stream", stream="stderr", output_text="f"), - nbformat.new_output(output_type="png", output_png='Zw==')] # g - out = nbformat.new_output(output_type="application/pdf") - out['application/pdf'] = 'aA==' # h - outputs.append(out) + outputs = [ + nbformat.new_output("stream", name="stdout", text="a"), + nbformat.new_output("display_data", data={'text/plain': 'b'}), + nbformat.new_output("stream", name="stdout", text="c"), + nbformat.new_output("stream", name="stdout", text="d"), + nbformat.new_output("stream", name="stderr", text="e"), + nbformat.new_output("stream", name="stderr", text="f"), + nbformat.new_output("display_data", data={'image/png': 'Zw=='}), # g + nbformat.new_output("display_data", data={'application/pdf': 'aA=='}), # h + ] - cells=[nbformat.new_code_cell(input="$ e $", prompt_number=1,outputs=outputs), - nbformat.new_text_cell('markdown', source="$ e $")] - worksheets = [nbformat.new_worksheet(cells=cells)] + cells=[nbformat.new_code_cell(source="$ e $", execution_count=1, outputs=outputs), + nbformat.new_markdown_cell(source="$ e $")] - return nbformat.new_notebook(name="notebook1", worksheets=worksheets) + return nbformat.new_notebook(cells=cells) def build_resources(self): diff --git a/IPython/nbconvert/preprocessors/tests/files/Clear Output.ipynb b/IPython/nbconvert/preprocessors/tests/files/Clear Output.ipynb index 758e84b..dfada39 100644 --- a/IPython/nbconvert/preprocessors/tests/files/Clear Output.ipynb +++ b/IPython/nbconvert/preprocessors/tests/files/Clear Output.ipynb @@ -1,46 +1,39 @@ { - "metadata": { - "name": "" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import clear_output" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 1 - }, + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from IPython.display import clear_output" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "for i in range(10):\n", - " clear_output()\n", - " print(i)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "9\n" - ] - } - ], - "prompt_number": 2 + "name": "stdout", + "output_type": "stream", + "text": [ + "9\n" + ] } ], - "metadata": {} + "source": [ + "for i in range(10):\n", + " clear_output()\n", + " print(i)" + ] } - ] + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/IPython/nbconvert/preprocessors/tests/files/Factorials.ipynb b/IPython/nbconvert/preprocessors/tests/files/Factorials.ipynb index bc74e2c..939c49f 100644 --- a/IPython/nbconvert/preprocessors/tests/files/Factorials.ipynb +++ b/IPython/nbconvert/preprocessors/tests/files/Factorials.ipynb @@ -1,55 +1,48 @@ { - "metadata": { - "name": "" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ - { - "cell_type": "code", - "collapsed": false, - "input": [ - "i, j = 1, 1" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 1 - }, + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "i, j = 1, 1" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "for m in range(10):\n", - " i, j = j, i + j\n", - " print(j)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "2\n", - "3\n", - "5\n", - "8\n", - "13\n", - "21\n", - "34\n", - "55\n", - "89\n", - "144\n" - ] - } - ], - "prompt_number": 2 + "name": "stdout", + "output_type": "stream", + "text": [ + "2\n", + "3\n", + "5\n", + "8\n", + "13\n", + "21\n", + "34\n", + "55\n", + "89\n", + "144\n" + ] } ], - "metadata": {} + "source": [ + "for m in range(10):\n", + " i, j = j, i + j\n", + " print(j)" + ] } - ] + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/IPython/nbconvert/preprocessors/tests/files/HelloWorld.ipynb b/IPython/nbconvert/preprocessors/tests/files/HelloWorld.ipynb index bd405d1..9b8f550 100644 --- a/IPython/nbconvert/preprocessors/tests/files/HelloWorld.ipynb +++ b/IPython/nbconvert/preprocessors/tests/files/HelloWorld.ipynb @@ -1,33 +1,26 @@ { - "metadata": { - "name": "" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "print(\"Hello World\")" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Hello World\n" - ] - } - ], - "prompt_number": 1 + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello World\n" + ] } ], - "metadata": {} + "source": [ + "print(\"Hello World\")" + ] } - ] + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/IPython/nbconvert/preprocessors/tests/files/Inline Image.ipynb b/IPython/nbconvert/preprocessors/tests/files/Inline Image.ipynb index c3527e1..48423aa 100644 --- a/IPython/nbconvert/preprocessors/tests/files/Inline Image.ipynb +++ b/IPython/nbconvert/preprocessors/tests/files/Inline Image.ipynb @@ -1,36 +1,29 @@ { - "metadata": { - "name": "" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import Image" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 1 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "Image('../input/python.png');" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 2 - } - ], - "metadata": {} + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from IPython.display import Image" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "Image('../input/python.png');" + ] } - ] + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/IPython/nbconvert/preprocessors/tests/files/SVG.ipynb b/IPython/nbconvert/preprocessors/tests/files/SVG.ipynb index 4c9e40a..2769ab2 100644 --- a/IPython/nbconvert/preprocessors/tests/files/SVG.ipynb +++ b/IPython/nbconvert/preprocessors/tests/files/SVG.ipynb @@ -1,53 +1,48 @@ { - "metadata": { - "name": "" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import SVG" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 1 - }, + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from IPython.display import SVG" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "SVG(data='''\n", - "\n", - " \n", - "''')" - ], - "language": "python", + "data": { + "image/svg+xml": [ + "\n", + " \n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 2, "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 2, - "svg": [ - "\n", - " \n", - "" - ], - "text": [ - "" - ] - } - ], - "prompt_number": 2 + "output_type": "execute_result" } ], - "metadata": {} + "source": [ + "SVG(data='''\n", + "\n", + " \n", + "''')" + ] } - ] + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/IPython/nbconvert/preprocessors/tests/files/Skip Exceptions.ipynb b/IPython/nbconvert/preprocessors/tests/files/Skip Exceptions.ipynb index 2f20604..452a454 100644 --- a/IPython/nbconvert/preprocessors/tests/files/Skip Exceptions.ipynb +++ b/IPython/nbconvert/preprocessors/tests/files/Skip Exceptions.ipynb @@ -1,57 +1,51 @@ { - "metadata": { - "name": "", - "signature": "sha256:9d47889f0678e9685429071216d0f3354db59bb66489f3225bcadfb6a1a9bbba" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "raise Exception(\"message\")" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "ename": "Exception", - "evalue": "message", - "output_type": "pyerr", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mException\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"message\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[1;31mException\u001b[0m: message" - ] - } - ], - "prompt_number": 1 - }, + "ename": "Exception", + "evalue": "message", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mException\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"message\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;31mException\u001b[0m: message" + ] + } + ], + "source": [ + "raise Exception(\"message\")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "print('ok')" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "ok\n" - ] - } - ], - "prompt_number": 2 + "name": "stdout", + "output_type": "stream", + "text": [ + "ok\n" + ] } ], - "metadata": {} + "source": [ + "print('ok')" + ] } - ] + ], + "metadata": { + "signature": "sha256:9d47889f0678e9685429071216d0f3354db59bb66489f3225bcadfb6a1a9bbba" + }, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/IPython/nbconvert/preprocessors/tests/files/Unicode.ipynb b/IPython/nbconvert/preprocessors/tests/files/Unicode.ipynb index 43ebb82..cdbcb10 100644 --- a/IPython/nbconvert/preprocessors/tests/files/Unicode.ipynb +++ b/IPython/nbconvert/preprocessors/tests/files/Unicode.ipynb @@ -1,33 +1,26 @@ { - "metadata": { - "name": "" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "print('\u2603')" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\u2603\n" - ] - } - ], - "prompt_number": 1 + "name": "stdout", + "output_type": "stream", + "text": [ + "\u2603\n" + ] } ], - "metadata": {} + "source": [ + "print('\u2603')" + ] } - ] + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/IPython/nbconvert/preprocessors/tests/test_clearoutput.py b/IPython/nbconvert/preprocessors/tests/test_clearoutput.py index 9434f7c..78db651 100644 --- a/IPython/nbconvert/preprocessors/tests/test_clearoutput.py +++ b/IPython/nbconvert/preprocessors/tests/test_clearoutput.py @@ -5,19 +5,10 @@ Module with tests for the clearoutput preprocessor. # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- -from IPython.nbformat import current as nbformat - from .base import PreprocessorTestsBase from ..clearoutput import ClearOutputPreprocessor -#----------------------------------------------------------------------------- -# Class -#----------------------------------------------------------------------------- - class TestClearOutput(PreprocessorTestsBase): """Contains test functions for clearoutput.py""" @@ -38,5 +29,5 @@ class TestClearOutput(PreprocessorTestsBase): res = self.build_resources() preprocessor = self.build_preprocessor() nb, res = preprocessor(nb, res) - assert nb.worksheets[0].cells[0].outputs == [] - assert nb.worksheets[0].cells[0].prompt_number is None + assert nb.cells[0].outputs == [] + assert nb.cells[0].execution_count is None diff --git a/IPython/nbconvert/preprocessors/tests/test_coalescestreams.py b/IPython/nbconvert/preprocessors/tests/test_coalescestreams.py index 1df7e0e..bded340 100644 --- a/IPython/nbconvert/preprocessors/tests/test_coalescestreams.py +++ b/IPython/nbconvert/preprocessors/tests/test_coalescestreams.py @@ -1,27 +1,14 @@ -""" -Module with tests for the coalescestreams preprocessor -""" +"""Tests for the coalescestreams preprocessor""" -#----------------------------------------------------------------------------- -# Copyright (c) 2013, the IPython Development Team. -# +# Copyright (c) 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 IPython.nbformat import current as nbformat +from IPython.nbformat import v4 as nbformat from .base import PreprocessorTestsBase from ..coalescestreams import coalesce_streams -#----------------------------------------------------------------------------- -# Class -#----------------------------------------------------------------------------- class TestCoalesceStreams(PreprocessorTestsBase): """Contains test functions for coalescestreams.py""" @@ -30,44 +17,42 @@ class TestCoalesceStreams(PreprocessorTestsBase): nb = self.build_notebook() res = self.build_resources() nb, res = coalesce_streams(nb, res) - outputs = nb.worksheets[0].cells[0].outputs + outputs = nb.cells[0].outputs self.assertEqual(outputs[0].text, "a") - self.assertEqual(outputs[1].output_type, "text") + self.assertEqual(outputs[1].output_type, "display_data") self.assertEqual(outputs[2].text, "cd") self.assertEqual(outputs[3].text, "ef") def test_coalesce_sequenced_streams(self): """Can the coalesce streams preprocessor merge a sequence of streams?""" - outputs = [nbformat.new_output(output_type="stream", stream="stdout", output_text="0"), - nbformat.new_output(output_type="stream", stream="stdout", output_text="1"), - nbformat.new_output(output_type="stream", stream="stdout", output_text="2"), - nbformat.new_output(output_type="stream", stream="stdout", output_text="3"), - nbformat.new_output(output_type="stream", stream="stdout", output_text="4"), - nbformat.new_output(output_type="stream", stream="stdout", output_text="5"), - nbformat.new_output(output_type="stream", stream="stdout", output_text="6"), - nbformat.new_output(output_type="stream", stream="stdout", output_text="7")] - cells=[nbformat.new_code_cell(input="# None", prompt_number=1,outputs=outputs)] - worksheets = [nbformat.new_worksheet(name="worksheet1", cells=cells)] - - nb = nbformat.new_notebook(name="notebook1", worksheets=worksheets) + outputs = [nbformat.new_output(output_type="stream", name="stdout", text="0"), + nbformat.new_output(output_type="stream", name="stdout", text="1"), + nbformat.new_output(output_type="stream", name="stdout", text="2"), + nbformat.new_output(output_type="stream", name="stdout", text="3"), + nbformat.new_output(output_type="stream", name="stdout", text="4"), + nbformat.new_output(output_type="stream", name="stdout", text="5"), + nbformat.new_output(output_type="stream", name="stdout", text="6"), + nbformat.new_output(output_type="stream", name="stdout", text="7")] + cells=[nbformat.new_code_cell(source="# None", execution_count=1,outputs=outputs)] + + nb = nbformat.new_notebook(cells=cells) res = self.build_resources() nb, res = coalesce_streams(nb, res) - outputs = nb.worksheets[0].cells[0].outputs + outputs = nb.cells[0].outputs self.assertEqual(outputs[0].text, u'01234567') def test_coalesce_replace_streams(self): """Are \\r characters handled?""" - outputs = [nbformat.new_output(output_type="stream", stream="stdout", output_text="z"), - nbformat.new_output(output_type="stream", stream="stdout", output_text="\ra"), - nbformat.new_output(output_type="stream", stream="stdout", output_text="\nz\rb"), - nbformat.new_output(output_type="stream", stream="stdout", output_text="\nz"), - nbformat.new_output(output_type="stream", stream="stdout", output_text="\rc\n"), - nbformat.new_output(output_type="stream", stream="stdout", output_text="z\rz\rd")] - cells=[nbformat.new_code_cell(input="# None", prompt_number=1,outputs=outputs)] - worksheets = [nbformat.new_worksheet(name="worksheet1", cells=cells)] - - nb = nbformat.new_notebook(name="notebook1", worksheets=worksheets) + outputs = [nbformat.new_output(output_type="stream", name="stdout", text="z"), + nbformat.new_output(output_type="stream", name="stdout", text="\ra"), + nbformat.new_output(output_type="stream", name="stdout", text="\nz\rb"), + nbformat.new_output(output_type="stream", name="stdout", text="\nz"), + nbformat.new_output(output_type="stream", name="stdout", text="\rc\n"), + nbformat.new_output(output_type="stream", name="stdout", text="z\rz\rd")] + cells=[nbformat.new_code_cell(source="# None", execution_count=1,outputs=outputs)] + + nb = nbformat.new_notebook(cells=cells) res = self.build_resources() nb, res = coalesce_streams(nb, res) - outputs = nb.worksheets[0].cells[0].outputs + outputs = nb.cells[0].outputs self.assertEqual(outputs[0].text, u'a\nb\nc\nd') diff --git a/IPython/nbconvert/preprocessors/tests/test_execute.py b/IPython/nbconvert/preprocessors/tests/test_execute.py index cb30e78..2cf426f 100644 --- a/IPython/nbconvert/preprocessors/tests/test_execute.py +++ b/IPython/nbconvert/preprocessors/tests/test_execute.py @@ -7,10 +7,11 @@ Module with tests for the execute preprocessor. import copy import glob +import io import os import re -from IPython.nbformat import current as nbformat +from IPython import nbformat from .base import PreprocessorTestsBase from ..execute import ExecutePreprocessor @@ -32,8 +33,9 @@ class TestExecute(PreprocessorTestsBase): del output['metadata'] if 'text' in output: output['text'] = re.sub(addr_pat, '', output['text']) - if 'svg' in output: - del output['text'] + if 'text/plain' in output.get('data', {}): + output['data']['text/plain'] = \ + re.sub(addr_pat, '', output['data']['text/plain']) if 'traceback' in output: tb = [] for line in output['traceback']: @@ -44,20 +46,20 @@ class TestExecute(PreprocessorTestsBase): def assert_notebooks_equal(self, expected, actual): - expected_cells = expected['worksheets'][0]['cells'] - actual_cells = actual['worksheets'][0]['cells'] - assert len(expected_cells) == len(actual_cells) + expected_cells = expected['cells'] + actual_cells = actual['cells'] + self.assertEqual(len(expected_cells), len(actual_cells)) for expected_cell, actual_cell in zip(expected_cells, actual_cells): expected_outputs = expected_cell.get('outputs', []) actual_outputs = actual_cell.get('outputs', []) normalized_expected_outputs = list(map(self.normalize_output, expected_outputs)) normalized_actual_outputs = list(map(self.normalize_output, actual_outputs)) - assert normalized_expected_outputs == normalized_actual_outputs + self.assertEqual(normalized_expected_outputs, normalized_actual_outputs) - expected_prompt_number = expected_cell.get('prompt_number', None) - actual_prompt_number = actual_cell.get('prompt_number', None) - assert expected_prompt_number == actual_prompt_number + expected_execution_count = expected_cell.get('execution_count', None) + actual_execution_count = actual_cell.get('execution_count', None) + self.assertEqual(expected_execution_count, actual_execution_count) def build_preprocessor(self): @@ -77,14 +79,14 @@ class TestExecute(PreprocessorTestsBase): current_dir = os.path.dirname(__file__) input_files = glob.glob(os.path.join(current_dir, 'files', '*.ipynb')) for filename in input_files: - with open(os.path.join(current_dir, 'files', filename)) as f: - input_nb = nbformat.read(f, 'ipynb') + with io.open(os.path.join(current_dir, 'files', filename)) as f: + input_nb = nbformat.read(f, 4) res = self.build_resources() preprocessor = self.build_preprocessor() cleaned_input_nb = copy.deepcopy(input_nb) - for cell in cleaned_input_nb.worksheets[0].cells: - if 'prompt_number' in cell: - del cell['prompt_number'] + for cell in cleaned_input_nb.cells: + if 'execution_count' in cell: + del cell['execution_count'] cell['outputs'] = [] output_nb, _ = preprocessor(cleaned_input_nb, res) self.assert_notebooks_equal(output_nb, input_nb) diff --git a/IPython/nbconvert/preprocessors/tests/test_extractoutput.py b/IPython/nbconvert/preprocessors/tests/test_extractoutput.py index 4b2c515..e7c19ea 100644 --- a/IPython/nbconvert/preprocessors/tests/test_extractoutput.py +++ b/IPython/nbconvert/preprocessors/tests/test_extractoutput.py @@ -1,43 +1,25 @@ -""" -Module with tests for the extractoutput preprocessor -""" +"""Tests for the extractoutput preprocessor""" -#----------------------------------------------------------------------------- -# Copyright (c) 2013, the IPython Development Team. -# +# Copyright (c) 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 .base import PreprocessorTestsBase from ..extractoutput import ExtractOutputPreprocessor -#----------------------------------------------------------------------------- -# Class -#----------------------------------------------------------------------------- - class TestExtractOutput(PreprocessorTestsBase): """Contains test functions for extractoutput.py""" - def build_preprocessor(self): """Make an instance of a preprocessor""" preprocessor = ExtractOutputPreprocessor() - preprocessor.extract_output_types = {'text', 'png', 'application/pdf'} + preprocessor.extract_output_types = {'text/plain', 'image/png', 'application/pdf'} preprocessor.enabled = True return preprocessor - def test_constructor(self): """Can a ExtractOutputPreprocessor be constructed?""" self.build_preprocessor() - def test_output(self): """Test the output of the ExtractOutputPreprocessor""" @@ -45,30 +27,32 @@ class TestExtractOutput(PreprocessorTestsBase): res = self.build_resources() preprocessor = self.build_preprocessor() nb, res = preprocessor(nb, res) - # Check if text was extracted. - output = nb.worksheets[0].cells[0].outputs[1] - assert 'text_filename' in output - text_filename = output['text_filename'] + output = nb.cells[0].outputs[1] + self.assertIn('filenames', output.metadata) + self.assertIn('text/plain', output.metadata.filenames) + text_filename = output.metadata.filenames['text/plain'] # Check if png was extracted. - output = nb.worksheets[0].cells[0].outputs[6] - assert 'png_filename' in output - png_filename = output['png_filename'] + output = nb.cells[0].outputs[6] + self.assertIn('filenames', output.metadata) + self.assertIn('image/png', output.metadata.filenames) + png_filename = output.metadata.filenames['image/png'] # Check that pdf was extracted - output = nb.worksheets[0].cells[0].outputs[7] - assert 'application/pdf_filename' in output - pdf_filename = output['application/pdf_filename'] + output = nb.cells[0].outputs[7] + self.assertIn('filenames', output.metadata) + self.assertIn('application/pdf', output.metadata.filenames) + pdf_filename = output.metadata.filenames['application/pdf'] # Verify text output - assert text_filename in res['outputs'] + self.assertIn(text_filename, res['outputs']) self.assertEqual(res['outputs'][text_filename], b'b') # Verify png output - assert png_filename in res['outputs'] + self.assertIn(png_filename, res['outputs']) self.assertEqual(res['outputs'][png_filename], b'g') # Verify pdf output - assert pdf_filename in res['outputs'] + self.assertIn(pdf_filename, res['outputs']) self.assertEqual(res['outputs'][pdf_filename], b'h') diff --git a/IPython/nbconvert/preprocessors/tests/test_highlightmagics.py b/IPython/nbconvert/preprocessors/tests/test_highlightmagics.py index ec21374..81364f2 100644 --- a/IPython/nbconvert/preprocessors/tests/test_highlightmagics.py +++ b/IPython/nbconvert/preprocessors/tests/test_highlightmagics.py @@ -1,27 +1,9 @@ -""" -Module with tests for the HighlightMagics preprocessor -""" - -#----------------------------------------------------------------------------- -# Copyright (c) 2013, the 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 -#----------------------------------------------------------------------------- +"""Tests for the HighlightMagics preprocessor""" from .base import PreprocessorTestsBase from ..highlightmagics import HighlightMagicsPreprocessor -#----------------------------------------------------------------------------- -# Class -#----------------------------------------------------------------------------- - class TestHighlightMagics(PreprocessorTestsBase): """Contains test functions for highlightmagics.py""" @@ -41,7 +23,7 @@ class TestHighlightMagics(PreprocessorTestsBase): nb = self.build_notebook() res = self.build_resources() preprocessor = self.build_preprocessor() - nb.worksheets[0].cells[0].input = """%%R -i x,y -o XYcoef + nb.cells[0].source = """%%R -i x,y -o XYcoef lm.fit <- lm(y~x) par(mfrow=c(2,2)) print(summary(lm.fit)) @@ -50,19 +32,19 @@ class TestHighlightMagics(PreprocessorTestsBase): nb, res = preprocessor(nb, res) - assert('magics_language' in nb.worksheets[0].cells[0]['metadata']) + assert('magics_language' in nb.cells[0]['metadata']) - self.assertEqual(nb.worksheets[0].cells[0]['metadata']['magics_language'], 'r') + self.assertEqual(nb.cells[0]['metadata']['magics_language'], 'r') def test_no_false_positive(self): """Test that HighlightMagicsPreprocessor does not tag false positives""" nb = self.build_notebook() res = self.build_resources() preprocessor = self.build_preprocessor() - nb.worksheets[0].cells[0].input = """# this should not be detected + nb.cells[0].source = """# this should not be detected print(\""" %%R -i x, y \""")""" nb, res = preprocessor(nb, res) - assert('magics_language' not in nb.worksheets[0].cells[0]['metadata']) \ No newline at end of file + assert('magics_language' not in nb.cells[0]['metadata']) \ No newline at end of file diff --git a/IPython/nbconvert/preprocessors/tests/test_latex.py b/IPython/nbconvert/preprocessors/tests/test_latex.py index 4687b37..b9a76e7 100644 --- a/IPython/nbconvert/preprocessors/tests/test_latex.py +++ b/IPython/nbconvert/preprocessors/tests/test_latex.py @@ -1,27 +1,12 @@ -""" -Module with tests for the latex preprocessor -""" +"""Tests for the latex preprocessor""" -#----------------------------------------------------------------------------- -# Copyright (c) 2013, the IPython Development Team. -# +# Copyright (c) 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 .base import PreprocessorTestsBase from ..latex import LatexPreprocessor -#----------------------------------------------------------------------------- -# Class -#----------------------------------------------------------------------------- - class TestLatex(PreprocessorTestsBase): """Contains test functions for latex.py""" @@ -45,7 +30,7 @@ class TestLatex(PreprocessorTestsBase): nb, res = preprocessor(nb, res) # Make sure the code cell wasn't modified. - self.assertEqual(nb.worksheets[0].cells[0].input, '$ e $') + self.assertEqual(nb.cells[0].source, '$ e $') # Verify that the markdown cell wasn't processed. - self.assertEqual(nb.worksheets[0].cells[1].source, '$ e $') + self.assertEqual(nb.cells[1].source, '$ e $') diff --git a/IPython/nbconvert/preprocessors/tests/test_revealhelp.py b/IPython/nbconvert/preprocessors/tests/test_revealhelp.py index 07b27fc..07fb23a 100644 --- a/IPython/nbconvert/preprocessors/tests/test_revealhelp.py +++ b/IPython/nbconvert/preprocessors/tests/test_revealhelp.py @@ -1,29 +1,14 @@ -""" -Module with tests for the revealhelp preprocessor -""" +"""Tests for the revealhelp preprocessor""" -#----------------------------------------------------------------------------- -# Copyright (c) 2013, the IPython Development Team. -# +# Copyright (c) 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 IPython.nbformat import current as nbformat +from IPython.nbformat import v4 as nbformat from .base import PreprocessorTestsBase from ..revealhelp import RevealHelpPreprocessor -#----------------------------------------------------------------------------- -# Class -#----------------------------------------------------------------------------- - class Testrevealhelp(PreprocessorTestsBase): """Contains test functions for revealhelp.py""" @@ -31,19 +16,18 @@ class Testrevealhelp(PreprocessorTestsBase): """Build a reveal slides notebook in memory for use with tests. Overrides base in PreprocessorTestsBase""" - outputs = [nbformat.new_output(output_type="stream", stream="stdout", output_text="a")] + outputs = [nbformat.new_output(output_type="stream", name="stdout", text="a")] slide_metadata = {'slideshow' : {'slide_type': 'slide'}} subslide_metadata = {'slideshow' : {'slide_type': 'subslide'}} - cells=[nbformat.new_code_cell(input="", prompt_number=1, outputs=outputs), - nbformat.new_text_cell('markdown', source="", metadata=slide_metadata), - nbformat.new_code_cell(input="", prompt_number=2, outputs=outputs), - nbformat.new_text_cell('markdown', source="", metadata=slide_metadata), - nbformat.new_text_cell('markdown', source="", metadata=subslide_metadata)] - worksheets = [nbformat.new_worksheet(name="worksheet1", cells=cells)] + cells=[nbformat.new_code_cell(source="", execution_count=1, outputs=outputs), + nbformat.new_markdown_cell(source="", metadata=slide_metadata), + nbformat.new_code_cell(source="", execution_count=2, outputs=outputs), + nbformat.new_markdown_cell(source="", metadata=slide_metadata), + nbformat.new_markdown_cell(source="", metadata=subslide_metadata)] - return nbformat.new_notebook(name="notebook1", worksheets=worksheets) + return nbformat.new_notebook(cells=cells) def build_preprocessor(self): @@ -74,7 +58,7 @@ class Testrevealhelp(PreprocessorTestsBase): res = self.build_resources() preprocessor = self.build_preprocessor() nb, res = preprocessor(nb, res) - cells = nb.worksheets[0].cells + cells = nb.cells # Make sure correct metadata tags are available on every cell. for cell in cells: diff --git a/IPython/nbconvert/preprocessors/tests/test_svg2pdf.py b/IPython/nbconvert/preprocessors/tests/test_svg2pdf.py index 9e8fb81..6b672f6 100644 --- a/IPython/nbconvert/preprocessors/tests/test_svg2pdf.py +++ b/IPython/nbconvert/preprocessors/tests/test_svg2pdf.py @@ -1,30 +1,15 @@ -""" -Module with tests for the svg2pdf preprocessor -""" +"""Tests for the svg2pdf preprocessor""" -#----------------------------------------------------------------------------- -# Copyright (c) 2013, the IPython Development Team. -# +# Copyright (c) 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 IPython.testing import decorators as dec -from IPython.nbformat import current as nbformat +from IPython.nbformat import v4 as nbformat from .base import PreprocessorTestsBase from ..svg2pdf import SVG2PDFPreprocessor -#----------------------------------------------------------------------------- -# Class -#----------------------------------------------------------------------------- - class Testsvg2pdf(PreprocessorTestsBase): """Contains test functions for svg2pdf.py""" @@ -54,18 +39,16 @@ class Testsvg2pdf(PreprocessorTestsBase): """ def build_notebook(self): - """Build a reveal slides notebook in memory for use with tests. + """Build a reveal slides notebook in memory for use with tests. Overrides base in PreprocessorTestsBase""" - outputs = [nbformat.new_output(output_type="svg", output_svg=self.simple_svg)] - - slide_metadata = {'slideshow' : {'slide_type': 'slide'}} - subslide_metadata = {'slideshow' : {'slide_type': 'subslide'}} + outputs = [nbformat.new_output(output_type='display_data', + data={'image/svg+xml':self.simple_svg}) + ] - cells=[nbformat.new_code_cell(input="", prompt_number=1, outputs=outputs)] - worksheets = [nbformat.new_worksheet(name="worksheet1", cells=cells)] + cells=[nbformat.new_code_cell(source="", execution_count=1, outputs=outputs)] - return nbformat.new_notebook(name="notebook1", worksheets=worksheets) + return nbformat.new_notebook(cells=cells) def build_preprocessor(self): @@ -87,4 +70,4 @@ class Testsvg2pdf(PreprocessorTestsBase): res = self.build_resources() preprocessor = self.build_preprocessor() nb, res = preprocessor(nb, res) - assert 'svg' in nb.worksheets[0].cells[0].outputs[0] + self.assertIn('application/pdf', nb.cells[0].outputs[0].data) diff --git a/IPython/nbconvert/templates/html/basic.tpl b/IPython/nbconvert/templates/html/basic.tpl index db287f3..40ba790 100644 --- a/IPython/nbconvert/templates/html/basic.tpl +++ b/IPython/nbconvert/templates/html/basic.tpl @@ -23,7 +23,11 @@ {% block in_prompt -%}
-In [{{ cell.prompt_number }}]: +{%- if cell.execution_count is defined -%} +In [{{ cell.execution_count|replace(None, " ") }}]: +{%- else -%} +In [ ]: +{%- endif -%}
{%- endblock in_prompt %} @@ -42,16 +46,20 @@ In [{{ cell.prompt_number }}]: {% block input %}
-{{ cell.input | highlight2html(language=resources.get('language'), metadata=cell.metadata) }} +{{ cell.source | highlight_code(metadata=cell.metadata) }}
{%- endblock input %} {% block output %}
-{%- if output.output_type == 'pyout' -%} +{%- if output.output_type == 'execute_result' -%}
- Out[{{ cell.prompt_number }}]: +{%- if cell.execution_count is defined -%} + Out[{{ cell.execution_count|replace(None, " ") }}]: +{%- else -%} + Out[ ]: +{%- endif -%} {%- else -%}
{%- endif -%} @@ -71,28 +79,17 @@ In [{{ cell.prompt_number }}]:
{%- endblock markdowncell %} -{% block headingcell scoped %} -
-{{ self.empty_in_prompt() }} -
-
-{{ ("#" * cell.level + cell.source) | replace('\n', ' ') | markdown2html | strip_files_prefix | add_anchor }} -
-
-
-{% endblock headingcell %} - {% block unknowncell scoped %} unknown type {{ cell.type }} {% endblock unknowncell %} -{% block pyout -%} -{%- set extra_class="output_pyout" -%} +{% block execute_result -%} +{%- set extra_class="output_execute_result" -%} {% block data_priority scoped %} {{ super() }} {% endblock %} {%- set extra_class="" -%} -{%- endblock pyout %} +{%- endblock execute_result %} {% block stream_stdout -%}
@@ -115,28 +112,28 @@ unknown type {{ cell.type }} {%- if output.svg_filename %} {%- endblock data_svg %} {% block data_html scoped -%}
-{{ output.html }} +{{ output.data['text/html'] }}
{%- endblock data_html %} {% block data_png scoped %}
-{%- if output.png_filename %} - @@ -145,16 +142,16 @@ height={{output.metadata['png']['height']}} {% block data_jpg scoped %}
-{%- if output.jpeg_filename %} -
@@ -162,17 +159,17 @@ height={{output.metadata['jpeg']['height']}} {% block data_latex scoped %}
-{{ output.latex }} +{{ output.data['text/latex'] }}
{%- endblock data_latex %} -{% block pyerr -%} -
+{% block error -%} +
 {{- super() -}}
 
-{%- endblock pyerr %} +{%- endblock error %} {%- block traceback_line %} {{ line | ansi2html }} @@ -181,7 +178,7 @@ height={{output.metadata['jpeg']['height']}} {%- block data_text scoped %}
-{{- output.text | ansi2html -}}
+{{- output.data['text/plain'] | ansi2html -}}
 
{%- endblock -%} @@ -189,7 +186,7 @@ height={{output.metadata['jpeg']['height']}} {%- block data_javascript scoped %}
{%- endblock -%} diff --git a/IPython/nbconvert/templates/latex/base.tplx b/IPython/nbconvert/templates/latex/base.tplx index 204de96..24e0d09 100644 --- a/IPython/nbconvert/templates/latex/base.tplx +++ b/IPython/nbconvert/templates/latex/base.tplx @@ -135,16 +135,16 @@ This template does not define a docclass, the inheriting class must define this. % Displaying simple data text ((* block data_text *)) \begin{verbatim} -((( output.text ))) +((( output.data['text/plain'] ))) \end{verbatim} ((* endblock data_text *)) % Display python error text as-is -((* block pyerr *)) +((* block error *)) \begin{Verbatim}[commandchars=\\\{\}] ((( super() ))) \end{Verbatim} -((* endblock pyerr *)) +((* endblock error *)) ((* block traceback_line *)) ((( line | indent | strip_ansi | escape_latex ))) ((* endblock traceback_line *)) @@ -158,21 +158,21 @@ This template does not define a docclass, the inheriting class must define this. % Display latex ((* block data_latex -*)) - ((*- if output.latex.startswith('$'): -*)) + ((*- if output.data['text/latex'].startswith('$'): -*)) ((= Replace $ symbols with more explicit, equation block. =)) \begin{equation*}\adjustbox{max width=\hsize}{$ - ((( output.latex | strip_dollars ))) + ((( output.data['text/latex'] | strip_dollars ))) $}\end{equation*} ((*- else -*)) - ((( output.latex ))) + ((( output.data['text/latex'] ))) ((*- endif *)) ((* endblock data_latex *)) % Default mechanism for rendering figures -((*- block data_png -*))((( draw_figure(output.png_filename) )))((*- endblock -*)) -((*- block data_jpg -*))((( draw_figure(output.jpeg_filename) )))((*- endblock -*)) -((*- block data_svg -*))((( draw_figure(output.svg_filename) )))((*- endblock -*)) -((*- block data_pdf -*))((( draw_figure(output['application/pdf_filename']) )))((*- endblock -*)) +((*- block data_png -*))((( draw_figure(output.metadata.filenames['image/png']) )))((*- endblock -*)) +((*- block data_jpg -*))((( draw_figure(output.metadata.filenames['image/jpeg']) )))((*- endblock -*)) +((*- block data_svg -*))((( draw_figure(output.metadata.filenames['image/svg+xml']) )))((*- endblock -*)) +((*- block data_pdf -*))((( draw_figure(output.metadata.filenames['application/pdf']) )))((*- endblock -*)) % Draw a figure using the graphicx package. ((* macro draw_figure(filename) -*)) @@ -185,32 +185,12 @@ This template does not define a docclass, the inheriting class must define this. ((*- endblock figure -*)) ((*- endmacro *)) -% Draw heading cell. Explicitly map different cell levels. -((* block headingcell scoped *)) - - ((* if cell.level == 1 -*)) - ((* block h1 -*))\section((* endblock h1 -*)) - ((* elif cell.level == 2 -*)) - ((* block h2 -*))\subsection((* endblock h2 -*)) - ((* elif cell.level == 3 -*)) - ((* block h3 -*))\subsubsection((* endblock h3 -*)) - ((* elif cell.level == 4 -*)) - ((* block h4 -*))\paragraph((* endblock h4 -*)) - ((* elif cell.level == 5 -*)) - ((* block h5 -*))\subparagraph((* endblock h5 -*)) - ((* elif cell.level == 6 -*)) - ((* block h6 -*))\\*\textit((* endblock h6 -*)) - ((*- endif -*)) - {((( cell.source | replace('\n', ' ') | citation2latex | strip_files_prefix | markdown2latex )))} - -((* endblock headingcell *)) - -% Redirect pyout to display data priority. -((* block pyout scoped *)) +% Redirect execute_result to display data priority. +((* block execute_result scoped *)) ((* block data_priority scoped *)) ((( super() ))) ((* endblock *)) -((* endblock pyout *)) +((* endblock execute_result *)) % Render markdown ((* block markdowncell scoped *)) diff --git a/IPython/nbconvert/templates/latex/skeleton/display_priority.tplx b/IPython/nbconvert/templates/latex/skeleton/display_priority.tplx index f8c2308..5788f7b 100644 --- a/IPython/nbconvert/templates/latex/skeleton/display_priority.tplx +++ b/IPython/nbconvert/templates/latex/skeleton/display_priority.tplx @@ -8,38 +8,34 @@ ((*- block data_priority scoped -*)) - ((*- for type in output | filter_data_type -*)) - ((*- if type in ['application/pdf']*)) + ((*- for type in output.data | filter_data_type -*)) + ((*- if type == 'application/pdf' -*)) ((*- block data_pdf -*)) ((*- endblock -*)) - ((*- endif -*)) - ((*- if type in ['svg']*)) + ((*- elif type == 'image/svg+xml' -*)) ((*- block data_svg -*)) ((*- endblock -*)) - ((*- endif -*)) - ((*- if type in ['png']*)) + ((*- elif type == 'image/png' -*)) ((*- block data_png -*)) ((*- endblock -*)) - ((*- endif -*)) - ((*- if type in ['html']*)) + ((*- elif type == 'text/html' -*)) ((*- block data_html -*)) ((*- endblock -*)) - ((*- endif -*)) - ((*- if type in ['jpeg']*)) + ((*- elif type == 'image/jpeg' -*)) ((*- block data_jpg -*)) ((*- endblock -*)) - ((*- endif -*)) - ((*- if type in ['text']*)) + ((*- elif type == 'text/plain' -*)) ((*- block data_text -*)) ((*- endblock -*)) - ((*- endif -*)) - ((*- if type in ['latex']*)) + ((*- elif type == 'text/latex' -*)) ((*- block data_latex -*)) ((*- endblock -*)) - ((*- endif -*)) - ((*- if type in ['javascript']*)) + ((*- elif type == 'application/javascript' -*)) ((*- block data_javascript -*)) ((*- endblock -*)) + ((*- else -*)) + ((*- block data_other -*)) + ((*- endblock -*)) ((*- endif -*)) ((*- endfor -*)) ((*- endblock data_priority -*)) diff --git a/IPython/nbconvert/templates/latex/skeleton/null.tplx b/IPython/nbconvert/templates/latex/skeleton/null.tplx index 2327e84..b5197fa 100644 --- a/IPython/nbconvert/templates/latex/skeleton/null.tplx +++ b/IPython/nbconvert/templates/latex/skeleton/null.tplx @@ -28,69 +28,64 @@ consider calling super even if it is a leave block, we might insert more blocks ((*- block header -*)) ((*- endblock header -*)) ((*- block body -*)) -((*- for worksheet in nb.worksheets -*)) - ((*- for cell in worksheet.cells -*)) - ((*- block any_cell scoped -*)) - ((*- if cell.cell_type in ['code'] -*)) - ((*- block codecell scoped -*)) - ((*- block input_group -*)) - ((*- block in_prompt -*))((*- endblock in_prompt -*)) - ((*- block input -*))((*- endblock input -*)) - ((*- endblock input_group -*)) - ((*- if cell.outputs -*)) - ((*- block output_group -*)) - ((*- block output_prompt -*))((*- endblock output_prompt -*)) - ((*- block outputs scoped -*)) - ((*- for output in cell.outputs -*)) - ((*- block output scoped -*)) - ((*- if output.output_type in ['pyout'] -*)) - ((*- block pyout scoped -*))((*- endblock pyout -*)) - ((*- elif output.output_type in ['stream'] -*)) - ((*- block stream scoped -*)) - ((*- if output.stream in ['stdout'] -*)) - ((*- block stream_stdout scoped -*)) - ((*- endblock stream_stdout -*)) - ((*- elif output.stream in ['stderr'] -*)) - ((*- block stream_stderr scoped -*)) - ((*- endblock stream_stderr -*)) - ((*- endif -*)) - ((*- endblock stream -*)) - ((*- elif output.output_type in ['display_data'] -*)) - ((*- block display_data scoped -*)) - ((*- block data_priority scoped -*)) - ((*- endblock data_priority -*)) - ((*- endblock display_data -*)) - ((*- elif output.output_type in ['pyerr'] -*)) - ((*- block pyerr scoped -*)) - ((*- for line in output.traceback -*)) - ((*- block traceback_line scoped -*))((*- endblock traceback_line -*)) - ((*- endfor -*)) - ((*- endblock pyerr -*)) - ((*- endif -*)) - ((*- endblock output -*)) - ((*- endfor -*)) - ((*- endblock outputs -*)) - ((*- endblock output_group -*)) - ((*- endif -*)) - ((*- endblock codecell -*)) - ((*- elif cell.cell_type in ['markdown'] -*)) - ((*- block markdowncell scoped-*)) - ((*- endblock markdowncell -*)) - ((*- elif cell.cell_type in ['heading'] -*)) - ((*- block headingcell scoped-*)) - ((*- endblock headingcell -*)) - ((*- elif cell.cell_type in ['raw'] -*)) - ((*- block rawcell scoped -*)) - ((* if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) *)) - ((( cell.source ))) - ((* endif *)) - ((*- endblock rawcell -*)) - ((*- else -*)) - ((*- block unknowncell scoped-*)) - ((*- endblock unknowncell -*)) - ((*- endif -*)) - ((*- endblock any_cell -*)) - ((*- endfor -*)) +((*- for cell in nb.cells -*)) + ((*- block any_cell scoped -*)) + ((*- if cell.cell_type == 'code' -*)) + ((*- block codecell scoped -*)) + ((*- block input_group -*)) + ((*- block in_prompt -*))((*- endblock in_prompt -*)) + ((*- block input -*))((*- endblock input -*)) + ((*- endblock input_group -*)) + ((*- if cell.outputs -*)) + ((*- block output_group -*)) + ((*- block output_prompt -*))((*- endblock output_prompt -*)) + ((*- block outputs scoped -*)) + ((*- for output in cell.outputs -*)) + ((*- block output scoped -*)) + ((*- if output.output_type == 'execute_result' -*)) + ((*- block execute_result scoped -*))((*- endblock execute_result -*)) + ((*- elif output.output_type == 'stream' -*)) + ((*- block stream scoped -*)) + ((*- if output.name == 'stdout' -*)) + ((*- block stream_stdout scoped -*)) + ((*- endblock stream_stdout -*)) + ((*- elif output.name == 'stderr' -*)) + ((*- block stream_stderr scoped -*)) + ((*- endblock stream_stderr -*)) + ((*- endif -*)) + ((*- endblock stream -*)) + ((*- elif output.output_type == 'display_data' -*)) + ((*- block display_data scoped -*)) + ((*- block data_priority scoped -*)) + ((*- endblock data_priority -*)) + ((*- endblock display_data -*)) + ((*- elif output.output_type == 'error' -*)) + ((*- block error scoped -*)) + ((*- for line in output.traceback -*)) + ((*- block traceback_line scoped -*))((*- endblock traceback_line -*)) + ((*- endfor -*)) + ((*- endblock error -*)) + ((*- endif -*)) + ((*- endblock output -*)) + ((*- endfor -*)) + ((*- endblock outputs -*)) + ((*- endblock output_group -*)) + ((*- endif -*)) + ((*- endblock codecell -*)) + ((*- elif cell.cell_type in ['markdown'] -*)) + ((*- block markdowncell scoped-*)) + ((*- endblock markdowncell -*)) + ((*- elif cell.cell_type in ['raw'] -*)) + ((*- block rawcell scoped -*)) + ((* if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) *)) + ((( cell.source ))) + ((* endif *)) + ((*- endblock rawcell -*)) + ((*- else -*)) + ((*- block unknowncell scoped-*)) + ((*- endblock unknowncell -*)) + ((*- endif -*)) + ((*- endblock any_cell -*)) ((*- endfor -*)) ((*- endblock body -*)) diff --git a/IPython/nbconvert/templates/latex/style_bw_ipython.tplx b/IPython/nbconvert/templates/latex/style_bw_ipython.tplx index 84859ab..7edf508 100644 --- a/IPython/nbconvert/templates/latex/style_bw_ipython.tplx +++ b/IPython/nbconvert/templates/latex/style_bw_ipython.tplx @@ -7,7 +7,7 @@ %=============================================================================== ((* block input scoped *)) -((( add_prompt(cell.input, cell, 'In ') ))) +((( add_prompt(cell.source, cell, 'In ') ))) ((* endblock input *)) @@ -15,15 +15,15 @@ % Output %=============================================================================== -((* block pyout scoped *)) - ((*- for type in output | filter_data_type -*)) - ((*- if type in ['text']*)) -((( add_prompt(output.text, cell, 'Out') ))) +((* block execute_result scoped *)) + ((*- for type in output.data | filter_data_type -*)) + ((*- if type in ['text/plain']*)) +((( add_prompt(output.data['text/plain'], cell, 'Out') ))) ((*- else -*)) -\verb+Out[((( cell.prompt_number )))]:+((( super() ))) +\verb+Out[((( cell.execution_count )))]:+((( super() ))) ((*- endif -*)) ((*- endfor -*)) -((* endblock pyout *)) +((* endblock execute_result *)) %============================================================================== @@ -33,9 +33,13 @@ % Name: draw_prompt % Purpose: Renders an output/input prompt ((* macro add_prompt(text, cell, prompt) -*)) - ((*- set prompt_number = "" ~ cell.prompt_number -*)) - ((*- set indentation = " " * (prompt_number | length + 7) -*)) + ((*- if cell.execution_count is defined -*)) + ((*- set execution_count = "" ~ (cell.execution_count | replace(None, " ")) -*)) + ((*- else -*)) + ((*- set execution_count = " " -*)) + ((*- endif -*)) + ((*- set indentation = " " * (execution_count | length + 7) -*)) \begin{verbatim} -(((- text | add_prompts(first=prompt ~ '[' ~ prompt_number ~ ']: ', cont=indentation) -))) +(((- text | add_prompts(first=prompt ~ '[' ~ execution_count ~ ']: ', cont=indentation) -))) \end{verbatim} ((*- endmacro *)) diff --git a/IPython/nbconvert/templates/latex/style_bw_python.tplx b/IPython/nbconvert/templates/latex/style_bw_python.tplx index e10d4a2..5371900 100644 --- a/IPython/nbconvert/templates/latex/style_bw_python.tplx +++ b/IPython/nbconvert/templates/latex/style_bw_python.tplx @@ -8,6 +8,6 @@ ((* block input scoped *)) \begin{verbatim} -((( cell.input | add_prompts ))) +((( cell.source | add_prompts ))) \end{verbatim} ((* endblock input *)) diff --git a/IPython/nbconvert/templates/latex/style_ipython.tplx b/IPython/nbconvert/templates/latex/style_ipython.tplx index 7799d18..86c7a03 100644 --- a/IPython/nbconvert/templates/latex/style_ipython.tplx +++ b/IPython/nbconvert/templates/latex/style_ipython.tplx @@ -20,7 +20,7 @@ %=============================================================================== ((* block input scoped *)) - ((( add_prompt(cell.input | highlight2latex(strip_verbatim=True), cell, 'In ', 'incolor') ))) + ((( add_prompt(cell.source | highlight_code(strip_verbatim=True), cell, 'In ', 'incolor') ))) ((* endblock input *)) @@ -28,15 +28,15 @@ % Output %=============================================================================== -((* block pyout scoped *)) - ((*- for type in output | filter_data_type -*)) - ((*- if type in ['text']*)) - ((( add_prompt(output.text | escape_latex, cell, 'Out', 'outcolor') ))) +((* block execute_result scoped *)) + ((*- for type in output.data | filter_data_type -*)) + ((*- if type in ['text/plain']*)) + ((( add_prompt(output.data['text/plain'] | escape_latex, cell, 'Out', 'outcolor') ))) ((* else -*)) -\texttt{\color{outcolor}Out[{\color{outcolor}((( cell.prompt_number )))}]:}((( super() ))) +\texttt{\color{outcolor}Out[{\color{outcolor}((( cell.execution_count )))}]:}((( super() ))) ((*- endif -*)) ((*- endfor -*)) -((* endblock pyout *)) +((* endblock execute_result *)) %============================================================================== @@ -46,9 +46,13 @@ % Name: draw_prompt % Purpose: Renders an output/input prompt ((* macro add_prompt(text, cell, prompt, prompt_color) -*)) - ((*- set prompt_number = "" ~ cell.prompt_number -*)) - ((*- set indention = " " * (prompt_number | length + 7) -*)) + ((*- if cell.execution_count is defined -*)) + ((*- set execution_count = "" ~ (cell.execution_count | replace(None, " ")) -*)) + ((*- else -*)) + ((*- set execution_count = " " -*)) + ((*- endif -*)) + ((*- set indention = " " * (execution_count | length + 7) -*)) \begin{Verbatim}[commandchars=\\\{\}] -((( text | add_prompts(first='{\color{' ~ prompt_color ~ '}' ~ prompt ~ '[{\\color{' ~ prompt_color ~ '}' ~ prompt_number ~ '}]:} ', cont=indention) ))) +((( text | add_prompts(first='{\color{' ~ prompt_color ~ '}' ~ prompt ~ '[{\\color{' ~ prompt_color ~ '}' ~ execution_count ~ '}]:} ', cont=indention) ))) \end{Verbatim} ((*- endmacro *)) diff --git a/IPython/nbconvert/templates/latex/style_python.tplx b/IPython/nbconvert/templates/latex/style_python.tplx index 43ec107..6673744 100644 --- a/IPython/nbconvert/templates/latex/style_python.tplx +++ b/IPython/nbconvert/templates/latex/style_python.tplx @@ -16,6 +16,6 @@ ((* block input scoped *)) \begin{Verbatim}[commandchars=\\\{\}] -((( cell.input | highlight2latex(language=resources.get('language'), strip_verbatim=True) | add_prompts ))) +((( cell.source | highlight_code(strip_verbatim=True) | add_prompts ))) \end{Verbatim} ((* endblock input *)) diff --git a/IPython/nbconvert/templates/markdown.tpl b/IPython/nbconvert/templates/markdown.tpl index ff32189..a6b9d35 100644 --- a/IPython/nbconvert/templates/markdown.tpl +++ b/IPython/nbconvert/templates/markdown.tpl @@ -8,23 +8,23 @@ {%- endblock output_prompt %} {% block input %} -{{ cell.input | indent(4)}} +{{ cell.source | indent(4)}} {% endblock input %} -{% block pyerr %} +{% block error %} {{ super() }} -{% endblock pyerr %} +{% endblock error %} {% block traceback_line %} {{ line | indent | strip_ansi }} {% endblock traceback_line %} -{% block pyout %} +{% block execute_result %} {% block data_priority scoped %} {{ super() }} {% endblock %} -{% endblock pyout %} +{% endblock execute_result %} {% block stream %} {{ output.text | indent }} @@ -35,34 +35,29 @@ {% endblock data_svg %} {% block data_png %} -![png]({{ output.png_filename | path2url }}) +![png]({{ output.metadata.filenames['image/png'] | path2url }}) {% endblock data_png %} {% block data_jpg %} -![jpeg]({{ output.jpeg_filename | path2url }}) +![jpeg]({{ output.metadata.filenames['image/jpeg'] | path2url }}) {% endblock data_jpg %} {% block data_latex %} -{{ output.latex }} +{{ output.data['text/latex'] }} {% endblock data_latex %} {% block data_html scoped %} -{{ output.html }} +{{ output.data['text/html'] }} {% endblock data_html %} {% block data_text scoped %} -{{ output.text | indent }} +{{ output.data['text/plain'] | indent }} {% endblock data_text %} {% block markdowncell scoped %} {{ cell.source }} {% endblock markdowncell %} - -{% block headingcell scoped %} -{{ '#' * cell.level }} {{ cell.source | replace('\n', ' ') }} -{% endblock headingcell %} - {% block unknowncell scoped %} unknown type {{ cell.type }} {% endblock unknowncell %} \ No newline at end of file diff --git a/IPython/nbconvert/templates/python.tpl b/IPython/nbconvert/templates/python.tpl index 0a13fb0..3918e25 100644 --- a/IPython/nbconvert/templates/python.tpl +++ b/IPython/nbconvert/templates/python.tpl @@ -5,17 +5,13 @@ {% endblock header %} {% block in_prompt %} -# In[{{ cell.prompt_number if cell.prompt_number else ' ' }}]: +# In[{{ cell.execution_count if cell.execution_count else ' ' }}]: {% endblock in_prompt %} {% block input %} -{{ cell.input | ipython2python }} +{{ cell.source | ipython2python }} {% endblock input %} {% block markdowncell scoped %} {{ cell.source | comment_lines }} {% endblock markdowncell %} - -{% block headingcell scoped %} -{{ '#' * cell.level }}{{ cell.source | replace('\n', ' ') | comment_lines }} -{% endblock headingcell %} diff --git a/IPython/nbconvert/templates/rst.tpl b/IPython/nbconvert/templates/rst.tpl index 1dc7f69..526078b 100644 --- a/IPython/nbconvert/templates/rst.tpl +++ b/IPython/nbconvert/templates/rst.tpl @@ -8,28 +8,28 @@ {% endblock output_prompt %} {% block input %} -{%- if cell.input.strip() -%} +{%- if cell.source.strip() -%} .. code:: python -{{ cell.input | indent}} +{{ cell.source | indent}} {%- endif -%} {% endblock input %} -{% block pyerr %} +{% block error %} :: {{ super() }} -{% endblock pyerr %} +{% endblock error %} {% block traceback_line %} {{ line | indent | strip_ansi }} {% endblock traceback_line %} -{% block pyout %} +{% block execute_result %} {% block data_priority scoped %} {{ super() }} {% endblock %} -{% endblock pyout %} +{% endblock execute_result %} {% block stream %} .. parsed-literal:: @@ -38,33 +38,33 @@ {% endblock stream %} {% block data_svg %} -.. image:: {{ output.svg_filename|urlencode }} +.. image:: {{ output.metadata.filenames['image/svg+xml'] | urlencode }} {% endblock data_svg %} {% block data_png %} -.. image:: {{ output.png_filename|urlencode }} +.. image:: {{ output.metadata.filenames['image/png'] | urlencode }} {% endblock data_png %} {% block data_jpg %} -.. image:: {{ output.jpeg_filename|urlencode }} +.. image:: {{ output.metadata.filenames['image/jpeg'] | urlencode }} {% endblock data_jpg %} {% block data_latex %} .. math:: -{{ output.latex | strip_dollars | indent }} +{{ output.data['text/latex'] | strip_dollars | indent }} {% endblock data_latex %} {% block data_text scoped %} .. parsed-literal:: -{{ output.text | indent }} +{{ output.data['text/plain'] | indent }} {% endblock data_text %} {% block data_html scoped %} .. raw:: html -{{ output.html | indent }} +{{ output.data['text/html'] | indent }} {% endblock data_html %} {% block markdowncell scoped %} diff --git a/IPython/nbconvert/templates/script.tpl b/IPython/nbconvert/templates/script.tpl new file mode 100644 index 0000000..cbd971d --- /dev/null +++ b/IPython/nbconvert/templates/script.tpl @@ -0,0 +1,5 @@ +{%- extends 'null.tpl' -%} + +{% block input %} +{{ cell.source }} +{% endblock input %} diff --git a/IPython/nbconvert/templates/skeleton/display_priority.tpl b/IPython/nbconvert/templates/skeleton/display_priority.tpl index 1765694..0ed3afc 100644 --- a/IPython/nbconvert/templates/skeleton/display_priority.tpl +++ b/IPython/nbconvert/templates/skeleton/display_priority.tpl @@ -4,38 +4,34 @@ {%- block data_priority scoped -%} - {%- for type in output | filter_data_type -%} - {%- if type in ['application/pdf']%} + {%- for type in output.data | filter_data_type -%} + {%- if type == 'application/pdf' -%} {%- block data_pdf -%} {%- endblock -%} - {%- endif -%} - {%- if type in ['svg']%} + {%- elif type == 'image/svg+xml' -%} {%- block data_svg -%} {%- endblock -%} - {%- endif -%} - {%- if type in ['png']%} + {%- elif type == 'image/png' -%} {%- block data_png -%} {%- endblock -%} - {%- endif -%} - {%- if type in ['html']%} + {%- elif type == 'text/html' -%} {%- block data_html -%} {%- endblock -%} - {%- endif -%} - {%- if type in ['jpeg']%} + {%- elif type == 'image/jpeg' -%} {%- block data_jpg -%} {%- endblock -%} - {%- endif -%} - {%- if type in ['text']%} + {%- elif type == 'text/plain' -%} {%- block data_text -%} {%- endblock -%} - {%- endif -%} - {%- if type in ['latex']%} + {%- elif type == 'text/latex' -%} {%- block data_latex -%} {%- endblock -%} - {%- endif -%} - {%- if type in ['javascript']%} + {%- elif type == 'application/javascript' -%} {%- block data_javascript -%} {%- endblock -%} + {%- else -%} + {%- block data_other -%} + {%- endblock -%} {%- endif -%} {%- endfor -%} {%- endblock data_priority -%} diff --git a/IPython/nbconvert/templates/skeleton/null.tpl b/IPython/nbconvert/templates/skeleton/null.tpl index 9779043..ae22cd7 100644 --- a/IPython/nbconvert/templates/skeleton/null.tpl +++ b/IPython/nbconvert/templates/skeleton/null.tpl @@ -24,69 +24,64 @@ consider calling super even if it is a leave block, we might insert more blocks {%- block header -%} {%- endblock header -%} {%- block body -%} -{%- for worksheet in nb.worksheets -%} - {%- for cell in worksheet.cells -%} - {%- block any_cell scoped -%} - {%- if cell.cell_type in ['code'] -%} - {%- block codecell scoped -%} - {%- block input_group -%} - {%- block in_prompt -%}{%- endblock in_prompt -%} - {%- block input -%}{%- endblock input -%} - {%- endblock input_group -%} - {%- if cell.outputs -%} - {%- block output_group -%} - {%- block output_prompt -%}{%- endblock output_prompt -%} - {%- block outputs scoped -%} - {%- for output in cell.outputs -%} - {%- block output scoped -%} - {%- if output.output_type in ['pyout'] -%} - {%- block pyout scoped -%}{%- endblock pyout -%} - {%- elif output.output_type in ['stream'] -%} - {%- block stream scoped -%} - {%- if output.stream in ['stdout'] -%} - {%- block stream_stdout scoped -%} - {%- endblock stream_stdout -%} - {%- elif output.stream in ['stderr'] -%} - {%- block stream_stderr scoped -%} - {%- endblock stream_stderr -%} - {%- endif -%} - {%- endblock stream -%} - {%- elif output.output_type in ['display_data'] -%} - {%- block display_data scoped -%} - {%- block data_priority scoped -%} - {%- endblock data_priority -%} - {%- endblock display_data -%} - {%- elif output.output_type in ['pyerr'] -%} - {%- block pyerr scoped -%} - {%- for line in output.traceback -%} - {%- block traceback_line scoped -%}{%- endblock traceback_line -%} - {%- endfor -%} - {%- endblock pyerr -%} - {%- endif -%} - {%- endblock output -%} - {%- endfor -%} - {%- endblock outputs -%} - {%- endblock output_group -%} - {%- endif -%} - {%- endblock codecell -%} - {%- elif cell.cell_type in ['markdown'] -%} - {%- block markdowncell scoped-%} - {%- endblock markdowncell -%} - {%- elif cell.cell_type in ['heading'] -%} - {%- block headingcell scoped-%} - {%- endblock headingcell -%} - {%- elif cell.cell_type in ['raw'] -%} - {%- block rawcell scoped -%} - {% if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) %} - {{ cell.source }} - {% endif %} - {%- endblock rawcell -%} - {%- else -%} - {%- block unknowncell scoped-%} - {%- endblock unknowncell -%} - {%- endif -%} - {%- endblock any_cell -%} - {%- endfor -%} +{%- for cell in nb.cells -%} + {%- block any_cell scoped -%} + {%- if cell.cell_type == 'code' -%} + {%- block codecell scoped -%} + {%- block input_group -%} + {%- block in_prompt -%}{%- endblock in_prompt -%} + {%- block input -%}{%- endblock input -%} + {%- endblock input_group -%} + {%- if cell.outputs -%} + {%- block output_group -%} + {%- block output_prompt -%}{%- endblock output_prompt -%} + {%- block outputs scoped -%} + {%- for output in cell.outputs -%} + {%- block output scoped -%} + {%- if output.output_type == 'execute_result' -%} + {%- block execute_result scoped -%}{%- endblock execute_result -%} + {%- elif output.output_type == 'stream' -%} + {%- block stream scoped -%} + {%- if output.name == 'stdout' -%} + {%- block stream_stdout scoped -%} + {%- endblock stream_stdout -%} + {%- elif output.name == 'stderr' -%} + {%- block stream_stderr scoped -%} + {%- endblock stream_stderr -%} + {%- endif -%} + {%- endblock stream -%} + {%- elif output.output_type == 'display_data' -%} + {%- block display_data scoped -%} + {%- block data_priority scoped -%} + {%- endblock data_priority -%} + {%- endblock display_data -%} + {%- elif output.output_type == 'error' -%} + {%- block error scoped -%} + {%- for line in output.traceback -%} + {%- block traceback_line scoped -%}{%- endblock traceback_line -%} + {%- endfor -%} + {%- endblock error -%} + {%- endif -%} + {%- endblock output -%} + {%- endfor -%} + {%- endblock outputs -%} + {%- endblock output_group -%} + {%- endif -%} + {%- endblock codecell -%} + {%- elif cell.cell_type in ['markdown'] -%} + {%- block markdowncell scoped-%} + {%- endblock markdowncell -%} + {%- elif cell.cell_type in ['raw'] -%} + {%- block rawcell scoped -%} + {% if cell.metadata.get('raw_mimetype', '').lower() in resources.get('raw_mimetypes', ['']) %} + {{ cell.source }} + {% endif %} + {%- endblock rawcell -%} + {%- else -%} + {%- block unknowncell scoped-%} + {%- endblock unknowncell -%} + {%- endif -%} + {%- endblock any_cell -%} {%- endfor -%} {%- endblock body -%} diff --git a/IPython/nbconvert/tests/base.py b/IPython/nbconvert/tests/base.py index 5cd58a4..27c3caf 100644 --- a/IPython/nbconvert/tests/base.py +++ b/IPython/nbconvert/tests/base.py @@ -1,17 +1,7 @@ -""" -Contains base test class for nbconvert -""" -#----------------------------------------------------------------------------- -#Copyright (c) 2013, the 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 -#----------------------------------------------------------------------------- +"""Base test class for nbconvert""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. import io import os @@ -20,7 +10,7 @@ import shutil import unittest import IPython -from IPython.nbformat import current +from IPython.nbformat import v4, write from IPython.utils.tempdir import TemporaryWorkingDirectory from IPython.utils.path import get_ipython_package_dir from IPython.utils.process import get_output_error_code @@ -29,10 +19,6 @@ from IPython.testing.tools import get_ipython_cmd # a trailing space allows for simpler concatenation with the other arguments ipy_cmd = get_ipython_cmd(as_string=True) + " " -#----------------------------------------------------------------------------- -# Classes and functions -#----------------------------------------------------------------------------- - class TestsBase(unittest.TestCase): """Base tests class. Contains useful fuzzy comparison and nbconvert @@ -115,11 +101,9 @@ class TestsBase(unittest.TestCase): return temp_dir def create_empty_notebook(self, path): - nb = current.new_notebook() - nb.worksheets.append(current.new_worksheet()) + nb = v4.new_notebook() with io.open(path, 'w', encoding='utf-8') as f: - current.write(nb, f, 'json') - + write(nb, f, 4) def copy_files_to(self, copy_filenames, dest='.'): "Copy test files into the destination directory" diff --git a/IPython/nbconvert/tests/files/notebook1.ipynb b/IPython/nbconvert/tests/files/notebook1.ipynb index 0ee2ca0..9ffa91d 100644 --- a/IPython/nbconvert/tests/files/notebook1.ipynb +++ b/IPython/nbconvert/tests/files/notebook1.ipynb @@ -1,149 +1,189 @@ { - "metadata": { - "name": "notebook1" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "A simple SymPy example" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First we import SymPy and initialize printing:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from sympy import init_printing\n", - "from sympy import *\n", - " init_printing()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 2 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Create a few symbols:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x,y,z = symbols('x y z')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 4 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here is a basic expression:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "e = x**2 + 2.0*y + sin(z); e" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "latex": [ - "$$x^{2} + 2.0 y + \\sin{\\left (z \\right )}$$" - ], - "metadata": {}, - "output_type": "pyout", - "png": "iVBORw0KGgoAAAANSUhEUgAAAKMAAAAZBAMAAACvE4OgAAAAMFBMVEX///8AAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv3aB7AAAAD3RSTlMAEHarIkSJZt3NVLsy\nme8Q6PJIAAACz0lEQVRIDa1UTWjUQBT+ZpvdzW7TGlrxItjYSg/C6vbiDwjmoCgUpHioPYhdqig9\nFJYiPYmW4klB14NgFGnw4EHpj7UgUtTFXhSEBgVBxIOFggWVrrUqiMY3mZkkLNIK7oN575vvvfky\n8yYJIGzgkSlRrULKrivVSkvq6LbxtcaSjV3aSo0lgWyl5pK69V+SRlEsPxNTGYhhDrV3M2Ue2etc\nEDmuMmM+IjolrCuHXNoLoQDNSAXdzbjsfFVKTY1vCgFXFIxenG4cFSSzRewAPnN0FugXjPDr45MQ\nJwoKtitgXL9zT+CsJeIHYG+Z4H1gwhRU4G/FcAQbbYU3KdDo+0sCK8lRU0guA72uKqMYk9RehHxP\niDIu0NS2v90KGShJYi7T7tgvkrQ2vIT2XtRISWNra6lzGc8/PW3ji4PL7Vmge095YIX0iB71NCaZ\n5N3XyM0VCuNIyFNIyY3AMG/KDUvjn90DGmwq9wpIl5AyU5WsTYy0aJf6JFGB5An3Der5jExKHjNR\n4JKPge/EXqDBoOXpkxkmkJHFfAFRVhDIveWA0S57N2Me6yw+DSX1n1uCq3sIfCF2IcjNkjeWyKli\nginHubboOB4vSNAjyaiXE26ygrkyTfod55Lj3CTE+n2P73ImJpnk6wJJKjYJSwt3OQbNJu4icM5s\nKGGbzMuD70N6JSbJD44x7pLDyJrbkfiLpOEhYVMJSVEj83x5YFLyNrAzJsmvJ+uhLrieXvcJDshy\nHtQuD54c2IWWEnSXfUTDZJJfAjcpOW5imp9aHvw4ZZ4NDV4FGjw0tzadKgbFwinJUd//AT0P1tdW\nBtuRU39oKdk9ONQ163fM+nvu/s4D/FX30otdQIZGlSnJKpq6KUxKVqV1WxGHFIhishjhEO1Gi3r4\nkZCMg+hH1henV8EjmFoly1PTMs/Uadaox+FceY2STpmvt9co/Pe0Jvt1GvgDK/Osw/4jQ4wAAAAA\nSUVORK5CYII=\n", - "prompt_number": 6, - "text": [ - " 2 \n", - "x + 2.0\u22c5y + sin(z)" - ] - } - ], - "prompt_number": 6 - }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# A simple SymPy example" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First we import SymPy and initialize printing:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from sympy import init_printing\n", + "from sympy import *\n", + " init_printing()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create a few symbols:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "x,y,z = symbols('x y z')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here is a basic expression:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "diff(e, x)" - ], - "language": "python", + "data": { + "image/png": [ + "iVBORw0KGgoAAAANSUhEUgAAAKMAAAAZBAMAAACvE4OgAAAAMFBMVEX///8AAAAAAAAAAAAAAAAA\n", + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv3aB7AAAAD3RSTlMAEHarIkSJZt3NVLsy\n", + "me8Q6PJIAAACz0lEQVRIDa1UTWjUQBT+ZpvdzW7TGlrxItjYSg/C6vbiDwjmoCgUpHioPYhdqig9\n", + "FJYiPYmW4klB14NgFGnw4EHpj7UgUtTFXhSEBgVBxIOFggWVrrUqiMY3mZkkLNIK7oN575vvvfky\n", + "8yYJIGzgkSlRrULKrivVSkvq6LbxtcaSjV3aSo0lgWyl5pK69V+SRlEsPxNTGYhhDrV3M2Ue2etc\n", + "EDmuMmM+IjolrCuHXNoLoQDNSAXdzbjsfFVKTY1vCgFXFIxenG4cFSSzRewAPnN0FugXjPDr45MQ\n", + "JwoKtitgXL9zT+CsJeIHYG+Z4H1gwhRU4G/FcAQbbYU3KdDo+0sCK8lRU0guA72uKqMYk9RehHxP\n", + "iDIu0NS2v90KGShJYi7T7tgvkrQ2vIT2XtRISWNra6lzGc8/PW3ji4PL7Vmge095YIX0iB71NCaZ\n", + "5N3XyM0VCuNIyFNIyY3AMG/KDUvjn90DGmwq9wpIl5AyU5WsTYy0aJf6JFGB5An3Der5jExKHjNR\n", + "4JKPge/EXqDBoOXpkxkmkJHFfAFRVhDIveWA0S57N2Me6yw+DSX1n1uCq3sIfCF2IcjNkjeWyKli\n", + "ginHubboOB4vSNAjyaiXE26ygrkyTfod55Lj3CTE+n2P73ImJpnk6wJJKjYJSwt3OQbNJu4icM5s\n", + "KGGbzMuD70N6JSbJD44x7pLDyJrbkfiLpOEhYVMJSVEj83x5YFLyNrAzJsmvJ+uhLrieXvcJDshy\n", + "HtQuD54c2IWWEnSXfUTDZJJfAjcpOW5imp9aHvw4ZZ4NDV4FGjw0tzadKgbFwinJUd//AT0P1tdW\n", + "BtuRU39oKdk9ONQ163fM+nvu/s4D/FX30otdQIZGlSnJKpq6KUxKVqV1WxGHFIhishjhEO1Gi3r4\n", + "kZCMg+hH1henV8EjmFoly1PTMs/Uadaox+FceY2STpmvt9co/Pe0Jvt1GvgDK/Osw/4jQ4wAAAAA\n", + "SUVORK5CYII=\n" + ], + "text/latex": [ + "$$x^{2} + 2.0 y + \\sin{\\left (z \\right )}$$" + ], + "text/plain": [ + " 2 \n", + "x + 2.0\u22c5y + sin(z)" + ] + }, + "execution_count": 6, "metadata": {}, - "outputs": [ - { - "latex": [ - "$$2 x$$" - ], - "metadata": {}, - "output_type": "pyout", - "png": "iVBORw0KGgoAAAANSUhEUgAAABQAAAAOBAMAAADd6iHDAAAAMFBMVEX///8AAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv3aB7AAAAD3RSTlMAIpm7MhCriUTv3c12\nVGZoascqAAAAgElEQVQIHWNgVDJ2YICAMAb2H1BmKgPDTChzFgNDvgOEvT8AzgQKrA9gPZPYUwNk\ncXxnCGd4dWA1kMllwFDKUB9wEchUZmAIYNgMZDDwJIDIPyDiEgOjAAPLFwZWBhYFBh6BqzwfGI4y\nSJUXZXH8Zf7A+IBh////v1hzjh5/xwAAW80hUDE8HYkAAAAASUVORK5CYII=\n", - "prompt_number": 7, - "text": [ - "2\u22c5x" - ] - } - ], - "prompt_number": 7 - }, + "output_type": "execute_result" + } + ], + "source": [ + "e = x**2 + 2.0*y + sin(z); e" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "integrate(e, z)" - ], - "language": "python", + "data": { + "image/png": [ + "iVBORw0KGgoAAAANSUhEUgAAABQAAAAOBAMAAADd6iHDAAAAMFBMVEX///8AAAAAAAAAAAAAAAAA\n", + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv3aB7AAAAD3RSTlMAIpm7MhCriUTv3c12\n", + "VGZoascqAAAAgElEQVQIHWNgVDJ2YICAMAb2H1BmKgPDTChzFgNDvgOEvT8AzgQKrA9gPZPYUwNk\n", + "cXxnCGd4dWA1kMllwFDKUB9wEchUZmAIYNgMZDDwJIDIPyDiEgOjAAPLFwZWBhYFBh6BqzwfGI4y\n", + "SJUXZXH8Zf7A+IBh////v1hzjh5/xwAAW80hUDE8HYkAAAAASUVORK5CYII=\n" + ], + "text/latex": [ + "$$2 x$$" + ], + "text/plain": [ + "2\u22c5x" + ] + }, + "execution_count": 7, "metadata": {}, - "outputs": [ - { - "latex": [ - "$$x^{2} z + 2.0 y z - \\cos{\\left (z \\right )}$$" - ], - "metadata": {}, - "output_type": "pyout", - "png": "iVBORw0KGgoAAAANSUhEUgAAALsAAAAZBAMAAACbakK8AAAAMFBMVEX///8AAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv3aB7AAAAD3RSTlMAEHarIkSJZt3NVLsy\nme8Q6PJIAAADAklEQVRIDbVVS2gTURQ90/wmk0k6tCJCsR1SKShIsxE3CgNWBKUxq9qFmqFqShfF\nUKQrkaDiF0pcCKYgBBcuBLV+wIWKARe6kQ4UhNKKWdiF4KIptmA/xPvmzZuMxdYUzIPcd+655568\nvLlJAL6G32oOasQWNHz5Rvg6nrKh/mygfSzlX2ygPaBUGmov6//NXs1yq4sex2EPrsHemTd2snNg\ntkb+Cx1zBL6SqwxZLvQAKYHzKZaPY4fh4TeHd0S5Nox9OClItm/jiU9DrEwwVEawpiVis9VkimqX\nAOr4o2cCs/0BT2I5+FYJRhJbePQxgzcD7QLEqtV5gdnu2Icr3L45gcCyt74Z7neL4SLQ0nm4S+dM\nYCz1gSPHnhKZDWyHhcCCNKwjqaF/TkwGl0L6nClie/wc1D1xdoNsSLhT0IJkhi7Lzr22xb8keE/N\nPm0Sc9yEuhRUyuiG9HzvFNeImCyq39SriOhtQI7IV/TiTqE8glqwohjE0NJwiANxOZTdZoxtfzSa\nx2tI8DtHcKQoQFmV6f1XT2swibxFL+6k5EgenhBCqKLTPX3ULnaYdDlaTMcCSd8zuXTvBq2bJUJr\nlE4WgSV5ZRdBzLFgO6nzhJp1ltvrlB2HCoWxQuG+jTvt2GxBWUZaU2mMApZNuSHA3vJpCliRhqqs\nZtvbTrb9ZIk+i70Ut1OcnpgeKskTCFUwjaYy8Jhr3eiefq0HIfa7yC6HOwVyULRuNDn21JngbcL+\nE8A+MNnSxb+w59+Cj2tELJBbjEZr8SGwn0j2aLkTPdp08R2OcKV6fXB3ikPH3n8tM5WTfrETtZcw\ng3QWH0dH7nKNiMkszqo/EDafaHhJ5Bm6ee4UtdAabxnMcmUUl0SnYx+uVqs5XAGN9QGgdeCrASv0\n3TmCsJcOdhnozexD38goK9HXynEKr1OKDs9guhQD039kGySyIQpJAdbvJ9YTlPvyUl3/aLUf34G/\nuGxIyXpE37DoLbAHwJaU53t9MRCfrU8o/k4iRn36Lar8Wd5wAfgN4R6xelyy/ssAAAAASUVORK5C\nYII=\n", - "prompt_number": 8, - "text": [ - " 2 \n", - "x \u22c5z + 2.0\u22c5y\u22c5z - cos(z)" - ] - } - ], - "prompt_number": 8 - }, + "output_type": "execute_result" + } + ], + "source": [ + "diff(e, x)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [], - "language": "python", + "data": { + "image/png": [ + "iVBORw0KGgoAAAANSUhEUgAAALsAAAAZBAMAAACbakK8AAAAMFBMVEX///8AAAAAAAAAAAAAAAAA\n", + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv3aB7AAAAD3RSTlMAEHarIkSJZt3NVLsy\n", + "me8Q6PJIAAADAklEQVRIDbVVS2gTURQ90/wmk0k6tCJCsR1SKShIsxE3CgNWBKUxq9qFmqFqShfF\n", + "UKQrkaDiF0pcCKYgBBcuBLV+wIWKARe6kQ4UhNKKWdiF4KIptmA/xPvmzZuMxdYUzIPcd+655568\n", + "vLlJAL6G32oOasQWNHz5Rvg6nrKh/mygfSzlX2ygPaBUGmov6//NXs1yq4sex2EPrsHemTd2snNg\n", + "tkb+Cx1zBL6SqwxZLvQAKYHzKZaPY4fh4TeHd0S5Nox9OClItm/jiU9DrEwwVEawpiVis9VkimqX\n", + "AOr4o2cCs/0BT2I5+FYJRhJbePQxgzcD7QLEqtV5gdnu2Icr3L45gcCyt74Z7neL4SLQ0nm4S+dM\n", + "YCz1gSPHnhKZDWyHhcCCNKwjqaF/TkwGl0L6nClie/wc1D1xdoNsSLhT0IJkhi7Lzr22xb8keE/N\n", + "Pm0Sc9yEuhRUyuiG9HzvFNeImCyq39SriOhtQI7IV/TiTqE8glqwohjE0NJwiANxOZTdZoxtfzSa\n", + "x2tI8DtHcKQoQFmV6f1XT2swibxFL+6k5EgenhBCqKLTPX3ULnaYdDlaTMcCSd8zuXTvBq2bJUJr\n", + "lE4WgSV5ZRdBzLFgO6nzhJp1ltvrlB2HCoWxQuG+jTvt2GxBWUZaU2mMApZNuSHA3vJpCliRhqqs\n", + "ZtvbTrb9ZIk+i70Ut1OcnpgeKskTCFUwjaYy8Jhr3eiefq0HIfa7yC6HOwVyULRuNDn21JngbcL+\n", + "E8A+MNnSxb+w59+Cj2tELJBbjEZr8SGwn0j2aLkTPdp08R2OcKV6fXB3ikPH3n8tM5WTfrETtZcw\n", + "g3QWH0dH7nKNiMkszqo/EDafaHhJ5Bm6ee4UtdAabxnMcmUUl0SnYx+uVqs5XAGN9QGgdeCrASv0\n", + "3TmCsJcOdhnozexD38goK9HXynEKr1OKDs9guhQD039kGySyIQpJAdbvJ9YTlPvyUl3/aLUf34G/\n", + "uGxIyXpE37DoLbAHwJaU53t9MRCfrU8o/k4iRn36Lar8Wd5wAfgN4R6xelyy/ssAAAAASUVORK5C\n", + "YII=\n" + ], + "text/latex": [ + "$$x^{2} z + 2.0 y z - \\cos{\\left (z \\right )}$$" + ], + "text/plain": [ + " 2 \n", + "x \u22c5z + 2.0\u22c5y\u22c5z - cos(z)" + ] + }, + "execution_count": 8, "metadata": {}, - "outputs": [] + "output_type": "execute_result" } ], - "metadata": {} + "source": [ + "integrate(e, z)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [] } - ] + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/IPython/nbconvert/tests/files/notebook2.ipynb b/IPython/nbconvert/tests/files/notebook2.ipynb index e92fe3f..dd5786e 100644 --- a/IPython/nbconvert/tests/files/notebook2.ipynb +++ b/IPython/nbconvert/tests/files/notebook2.ipynb @@ -1,224 +1,1584 @@ { - "metadata": { - "name": "", - "signature": "sha256:9fffd84e69e3d9b8aee7b4cde2099ca5d4158a45391698b191f94fabaf394b41" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "NumPy and Matplotlib examples" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First import NumPy and Matplotlib:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%matplotlib inline\n", - "import matplotlib\n", - "import matplotlib.pyplot as plt\n", - "print(matplotlib.backends.backend)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "module://IPython.kernel.zmq.pylab.backend_inline\n" - ] - } - ], - "prompt_number": 1 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import set_matplotlib_formats\n", - "set_matplotlib_formats('png', 'pdf')\n", - "matplotlib.rcParams['figure.figsize'] = (2,1)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 2 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "ip.display_formatter.formatters['application/pdf'].type_printers" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 3, - "text": [ - "{matplotlib.figure.Figure: >}" - ] - } - ], - "prompt_number": 3 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import numpy as np" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 4 - }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# NumPy and Matplotlib examples" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First import NumPy and Matplotlib:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we show some very basic examples of how they can be used." + "name": "stdout", + "output_type": "stream", + "text": [ + "module://IPython.kernel.zmq.pylab.backend_inline\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "a = np.random.uniform(size=(100,100))" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 5 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "a.shape" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 6, - "text": [ - "(100, 100)" - ] - } - ], - "prompt_number": 6 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "evs = np.linalg.eigvals(a)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 7 - }, + } + ], + "source": [ + "%matplotlib inline\n", + "import matplotlib\n", + "import matplotlib.pyplot as plt\n", + "print(matplotlib.backends.backend)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from IPython.display import set_matplotlib_formats\n", + "set_matplotlib_formats('png', 'pdf')\n", + "matplotlib.rcParams['figure.figsize'] = (2,1)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "evs.shape" - ], - "language": "python", + "data": { + "text/plain": [ + "{matplotlib.figure.Figure: >}" + ] + }, + "execution_count": 3, "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 8, - "text": [ - "(100,)" - ] - } - ], - "prompt_number": 8 - }, + "output_type": "execute_result" + } + ], + "source": [ + "ip.display_formatter.formatters['application/pdf'].type_printers" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we show some very basic examples of how they can be used." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "a = np.random.uniform(size=(100,100))" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "heading", - "level": 2, + "data": { + "text/plain": [ + "(100, 100)" + ] + }, + "execution_count": 6, "metadata": {}, - "source": [ - "Here is a very long heading that pandoc will wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap" - ] - }, + "output_type": "execute_result" + } + ], + "source": [ + "a.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "evs = np.linalg.eigvals(a)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", + "data": { + "text/plain": [ + "(100,)" + ] + }, + "execution_count": 8, "metadata": {}, - "source": [ - "Here is a cell that has both text and PNG output:" - ] - }, + "output_type": "execute_result" + } + ], + "source": [ + "evs.shape" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Here is a very long heading that pandoc will wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap and wrap" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here is a cell that has both text and PNG output:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "plt.hist(evs.real)" - ], - "language": "python", + "data": { + "text/plain": [ + "(array([97, 2, 0, 0, 0, 0, 0, 0, 0, 1]),\n", + " array([ -2.59479443, 2.67371141, 7.94221725, 13.21072308,\n", + " 18.47922892, 23.74773476, 29.0162406 , 34.28474644,\n", + " 39.55325228, 44.82175812, 50.09026395]),\n", + " )" + ] + }, + "execution_count": 9, "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 9, - "text": [ - "(array([97, 2, 0, 0, 0, 0, 0, 0, 0, 1]),\n", - " array([ -2.59479443, 2.67371141, 7.94221725, 13.21072308,\n", - " 18.47922892, 23.74773476, 29.0162406 , 34.28474644,\n", - " 39.55325228, 44.82175812, 50.09026395]),\n", - " )" - ] - }, - { - "application/pdf": "JVBERi0xLjQKJazcIKu6CjEgMCBvYmoKPDwgL1R5cGUgL0NhdGFsb2cgL1BhZ2VzIDIgMCBSID4+\nCmVuZG9iago4IDAgb2JqCjw8IC9YT2JqZWN0IDcgMCBSIC9QYXR0ZXJuIDUgMCBSCi9Qcm9jU2V0\nIFsgL1BERiAvVGV4dCAvSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdIC9FeHRHU3RhdGUgNCAwIFIK\nL1NoYWRpbmcgNiAwIFIgL0ZvbnQgMyAwIFIgPj4KZW5kb2JqCjEwIDAgb2JqCjw8IC9Hcm91cCA8\nPCAvQ1MgL0RldmljZVJHQiAvUyAvVHJhbnNwYXJlbmN5IC9UeXBlIC9Hcm91cCA+PiAvUGFyZW50\nIDIgMCBSCi9NZWRpYUJveCBbIDAgMCAxNTIuMzk4NDM3NSA4Ny4xOTIxODc1IF0gL1Jlc291cmNl\ncyA4IDAgUiAvVHlwZSAvUGFnZQovQ29udGVudHMgOSAwIFIgPj4KZW5kb2JqCjkgMCBvYmoKPDwg\nL0ZpbHRlciAvRmxhdGVEZWNvZGUgL0xlbmd0aCAxMSAwIFIgPj4Kc3RyZWFtCnicxZi/btwwDMYz\na+wTCJ3aRSEpiZLGHtAG6FbkgL5AmwTBXYCmQ16/dJycRcWq7SV3k81Pf76fzyZpo703l1/Q3v61\nZL9bsE8W7ZUBOTpajOR8ycGnKOcHdZ6Tw0KY5fAgojq9Mw+yKA2Lgiv+9WdvZeCVoewCR6ZoCVyk\njHIkewVw0IYPdTix8y/hao0qKvveiHOhcF5t6qJgqWDPRRXlkwmzm92vHp1g8rYzzf5on8xuby+/\noUWw+xsjO2L0w+gk06Ld/zKfPn64wAv4bPf39uu+XeGZwITgAuSQCQMVZbtRlHWt1fYbZRNCIAdA\n5F/mnjAWEFiiEb0vlGNSCI2iELRWIzTKJoQYXSglJU7PcyeEpf9BDFAJWDhQZgXRKApCazVEo2yC\nSOiQ0efhCnANQQsQhRz5BNHHhEFBNIqC0FoN0SibICR5QCjxde4E4RcgELLDVAqCTPQ6nzSSwmjE\nmqOVNoEgkCuFwuvkiSQskVBwSDTejKhJGkmTaFGRNNI2Esyu5PLyp9QkcYlkLpHr/K4J5jK8Gr/R\nuQ9Sc8bxlW1esL1YD6BjermIrTGdHI8VAXm47sDr8unkz6Pj/Mb1FG1c18Nnw6tcyzM/bIahMryU\neCZzUkOk+rSWp2hjuR4+G15pGUazvjgPle+lJ3RyGIPUvje+p2jjux4+G159qSPIZpXl9fc0RykT\nbyxP0cZyPXw2vNby8yy5p6hynVe77vRaXKeDFemDNxVUuc6JXKqfQWkIJs9/ZpMColBaaSmyffxt\nf9qHsZ12BFKZMbIUZxkbEBOHBCw20unEPk49atUtXxlhoITscwhNv5cdJ5TWC1TVO2ghBUkqYQRX\nS1WC9Mw788O+J9S896ON0gXIxBDZqwp4aBUxFQb3puE9CefA6rk/Dk+NzJQcSZLgFZdSzH+IK+Xd\nwXr2pW/1LnNhOaeowZRiusjnBevZP9o8ZK4i60pTrp8vpZgu8nnBevalSQfHsiYDSJekTCrFdJHP\nC9azL2BFsn2W/MaQGrBaMV3kM4N17A+vI0k8JOZEgM2nESWZLvR50boAwoaylaTvBEneMzSbkkwf\n+8xwPYLx7YtYXAafC2s4JRkpW5B5jtvW0gg3mk4+UZSmm9SHrBX9z/WKNxc9fsvXuu7w+ebt2ph/\nACMXFgplbmRzdHJlYW0KZW5kb2JqCjExIDAgb2JqCjg3MAplbmRvYmoKMTYgMCBvYmoKPDwgL0Zp\nbHRlciAvRmxhdGVEZWNvZGUgL0xlbmd0aDEgMTkgMCBSIC9MZW5ndGggMjAgMCBSID4+CnN0cmVh\nbQp4nNS9eYAdVZ0vfurudavq3rr7vvftvdOdTtIJi6QTsnQATdhCgsYECRAWIVFAwAUGWQL6JjDj\nAEEQRJYAvsnNnbZZxjfkzVMExhl7HImgeZBRCIn6BnUcRudB8vt8zqm66QScef/+DqnvreXUWb6f\n73pO2QpNCBEH8Ynq8iVLlwleawdAhLl81cozz//y80lcm0LoZyw/8+zFIiXCQvvNBj5feebw6M3v\n9H4RL+zA9YbzP3ne5v959xPrhTj9QSEC2fOvvrIaXKRVhTj3RDyvXrj5ok9etDjUI8Q5T+Od0y+6\n7NoLR5ee8b+EWLdJaGdt2LTxk9fkfn3RLCFCWSFWbN10wXkbn8ze+XPUHcT7Y5twIzjoXYPrjbju\n2vTJK68pvnny6UJ4QujPvvSCT12+7NTTbxHarheEqC+57Irzz1v629+uRv8fE8Ib/uR512z23hfG\n2LWtHM/l533ygq985Ml/EdqPMD/jbzdf8ekr77zla3cJ7fVpjGHW5k9dsPmkX106T4i1eK4NKd6I\n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\nc+tTOz5911Weq+KfyX7mK9f4ru2/9t5rv3Pdjs+e9Lmxz09d/+ANX/uTxTce/8W5N+Vv+sub/vam\nvTcdutm+uf/m3bc8sXXktk/dduNtk7d/4kuXfelzX/7Uf3vov/3sT/N/etmffvtPX9n2uW3T2/Zv\ne/eO+p32nVfe+fCdu+985c5Df9b1Zz/88xu+kv/K1r84/i+uu2vwbnH3GXdfdvef3f303b+455R7\n7tpe3X7a9s9tv2P7S9v3bP/ZvcvuPePej997yb1X33vjV6/56k1fveOr9311x32R+/L3dd83et9J\n951y30P3/eV9z973/P3x+8v3998/dv/i+z9y/7n3X3j/p+7/wv2333/X/Q/d/5f3P3v/8/f/8P7X\nvrb7a9//2itfe+Nrv35g9IGTHjjlgdUPfOKByx+MPJh/sPvB0QdPevCUB1d/fcnXV339Y1/f9PUr\nv37DQ2se2vjQ5oc+99DWb4x848RvTHzjrG9s+MZl3/jdw+Jh8+GtD08/vPfhAw//7hHxiPlI9pGu\nR0YeOfGRiUfOemTDI5c9cs0jNz1yxyP3PbLj0Tseve/RHY9OPrr7sfHHTntszWMbH9v82Od29O6Y\nu2N8x2k71uzYuGPzjjt23PH4xidGGLbLSFJ4/gGetwHbPiBGW4PDraHh1qDd6p5udQ/vSvvebQ3Z\nrebeXUXfu+KvvFqXb+CvmloO1KdpvoGR2fPnzUn19MydPzY2/yTvvLndjXog2DM2Nmc0nUryLwIG\nUplYLabheG3BPI8VTMfsZNg3VKkMBUaDp4yNLct1NwOB5w5t1P7hkLjq5JOvii3IWaVYNJOI6V2z\nB+eEJhYtP7E6r1FLJOc+7bn4vbs99703iiELoaJ1z196P+3phucXWhBe6XXEDh9qX2Fo6yYXGisN\nzxbajS2TO6PPRT3rpnZHp6P7ot51grZmHe3UOhqhdbQruETNkdlH2r240+5G2e6YbLe90NC2jFsP\nGDuN54wfGK8bbxuBde0rSnxSkk9KO0vPlX5Qer30dimwbmT2B4zzetneQPsKC29xgKJ9mKdvcxgL\n3bF0RoVGZs73nk47fyvbWdleFUPtK2KofQPIlIjZsWrMu2Vqd2w6ti/mxZ2aXavWvKhUY481Dc9q\n07V9NS9ewS3RXp9kX8Vj+/pop69/lH012wvz6Atk3dQD+Z355/Jo4Yq8bKGCFg73qha8sLDCczzs\n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\neZnDvTDnpW15FMecizPZO9u9Le+ZJTtX3t783qOrMa9dC84q3FMTcg5yLytRVqPgq8w9vknuOmc/\nPR5lys63JNwR8+s1z9HZPwdhKHH7aW+r6GPj97OSu8K2X3Fa2ae3o0xbVLqupgrbKrRY99j4tDtj\nszhbi6jkd5v8Ph2TI+ZOMtF/8p5ds0iViz6cPx2Sjntotbi/7rZPlYiNdpxxoLjDn8lFuYhrw2Je\nZ2udK6mzrvmTIOGekF89w7S0DL/j19QeWqQrtV5cDS2uvtZppQ1yXP+pe0bnPS1/+i+gju98rx4v\n7czeXuL+0ijin9cn7+Nv/9TQ2TKOUizWcO/Tc6sMS4nLn1z59Zt1PA/PFsu8VhWOKNP+t8+9rojO\nROTG823graNFjdtWyxJvS1WiP/EsnMM7Tctas9Bpve97vRP973J/lFrpOjj9uah+2dmO8beXQp/v\n3Mln3Hx3lv/5++0zyXJ1ud+2CP+8necX3Ifch638K91VVt7vuOPud93fuI+4q901drWccL/nft/9\ngb0hzbHa/BlvVXusbP0jwn9otflj9/3uF+2NaoG73t3g/sLeGv/enstfdS9bS1+xp/RCu3Pc5P7W\nPedudn9lb0S6Nz1k7f2ivV1Mtdr3WYxnug9YW5e5j7md7i63y93iFrk33Dfcg9bn/tTa9XV3v40V\n3uvOdZ9zx6zHjLkvu0/buOtZqavdq9Z7honI3e497in3K+6Xjatv2ojhp6xHf8ld6n7T/Yy7xD3v\nVrvPGIfvt3fcJ91vuBesl71p7/prLbojxkLb3Wq9Yb17n3vC/bnb4LvcP7gfcf/oNrrLrYd027vo\nqLvH3et+yb1ld6eL3L+4f3X/5A67R91PuvvcZhsTTrdRRK873092L7pzbOy7xe5Uj9to5bfcr7pn\nbGz4a+4rbprbaiOfP3G3ub90D7hZ7jL3bhsXvWTjytvdt9zF7t/cP7vX3efdu9y33R3uE+6T7lPu\ngPF7p9vmPu62u79zR90Pux3ur92Fforv9j2+10/10/x0P8PP9Of4c/15/nx/gb/QX+Tf5S92j/lL\n/KXuJ/y7bWT3AzYS/4L7afdjNhb/df8eG7P+rI3Ofshf7t/rZ/n3+ZL7L/9+d9Jf4T/gP+g/5D/s\nr7QR1Uf81e7f/TV+tr/Wz/Ef9XP9PD/fX+cX+Ov9DT7woY/8Qn+jv8nf7G/xH/OLfJ9f7Je4//RL\n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\n0ALYRzoAqQCkAZACQOv/B6ys2f+vd9C1/wd3UY3cZ2lVVmuyWpHVeqxWY7UWy0qs1mG1Cqs1WK3A\nav1Vq69l2jyL+/BLeBQm0WrV+EJ8E3JNyDMhx4T8EnJLyCshp4R8Ep1PSa2uam1VK6taV9WqqtZU\n8xXV/J1Kf+5/AIGHtqIKZW5kc3RyZWFtCmVuZG9iagoyMCAwIG9iago3MDc0OQplbmRvYmoKMTkg\nMCBvYmoKMTI3MzQwCmVuZG9iagoxNSAwIG9iago8PCAvRmlsdGVyIC9GbGF0ZURlY29kZSAvTGVu\nZ3RoIDY3ID4+CnN0cmVhbQp4nO3NMQ0AIQAEwVNMTYKOV4AZKhosIOQxQUNmuq02uWynZ2WmpWac\nLreHAAAAAAAAAAAAAAAAAAAAAPCY7weB+gXnCmVuZHN0cmVhbQplbmRvYmoKMTggMCBvYmoKPDwg\nL0ZpbHRlciAvRmxhdGVEZWNvZGUgL0xlbmd0aCAyNjEgPj4Kc3RyZWFtCnicXVE9b8MgEN35FTem\nQ0Rst5UHC6lKFw9Jq7qdogw2HBZSDQjjwf++fCRu1ZPg6T7ece+gx/a11coDfXeGd+hBKi0czmZx\nHGHAUWlSlCAU9zcv3XzqLaGB3K2zx6nV0pCmAfoRkrN3K+xehBnwgQAAfXMCndIj7L6OXQ51i7Xf\nOKH2cCCMgUAZ2p16e+4nBJrI+1aEvPLrPtB+Kz5Xi1Amv8gjcSNwtj1H1+sRSXMIxqCRwRhBLf7l\nq8wa5FZexfIAzwwuf9wiQ5mhyvCY4enOuKYGdXbrW4M6hsuyiNQMl4zXOM/95Tha3OOmmy/OBclp\n2UlrVKk0bv9hjY2seH4AHtCFLgplbmRzdHJlYW0KZW5kb2JqCjEzIDAgb2JqCjw8IC9DSURUb0dJ\nRE1hcCAxNSAwIFIgL0ZvbnREZXNjcmlwdG9yIDEyIDAgUiAvQmFzZUZvbnQgL0F2ZW5pci1Cb29r\nCi9DSURTeXN0ZW1JbmZvIDw8IC9PcmRlcmluZyAoSWRlbnRpdHkpIC9TdXBwbGVtZW50IDAgL1Jl\nZ2lzdHJ5IChBZG9iZSkgPj4KL1N1YnR5cGUgL0NJREZvbnRUeXBlMiAvVyAxNyAwIFIgL1R5cGUg\nL0ZvbnQgPj4KZW5kb2JqCjE0IDAgb2JqCjw8IC9FbmNvZGluZyAvSWRlbnRpdHktSCAvQmFzZUZv\nbnQgL0F2ZW5pci1Cb29rCi9EZXNjZW5kYW50Rm9udHMgWyAxMyAwIFIgXSAvU3VidHlwZSAvVHlw\nZTAgL1RvVW5pY29kZSAxOCAwIFIgL1R5cGUgL0ZvbnQKPj4KZW5kb2JqCjEyIDAgb2JqCjw8IC9E\nZXNjZW50IC0zNjYgL0ZvbnRCQm94IFsgLTE2NyAtMjg4IDEwMDAgOTQwIF0gL1N0ZW1WIDAgL0Zs\nYWdzIDMyCi9YSGVpZ2h0IDAgL1R5cGUgL0ZvbnREZXNjcmlwdG9yIC9Gb250RmlsZTIgMTYgMCBS\nIC9Gb250TmFtZSAvQXZlbmlyLUJvb2sKL01heFdpZHRoIDY4MiAvQ2FwSGVpZ2h0IDAgL0l0YWxp\nY0FuZ2xlIDAgL0FzY2VudCAxMDAwID4+CmVuZG9iagoxNyAwIG9iagpbIDQ4ClsgNTY5LjMzMzMz\nMzMzMzMgNTY5LjMzMzMzMzMzMzMgNTY5LjMzMzMzMzMzMzMgNTY5LjMzMzMzMzMzMzMKNTY5LjMz\nMzMzMzMzMzMgNTY5LjMzMzMzMzMzMzMgNTY5LjMzMzMzMzMzMzMgXQo1NiBbIDU2OS4zMzMzMzMz\nMzMzIF0gODcyMiBbIDY4MiBdIF0KZW5kb2JqCjMgMCBvYmoKPDwgL0YxIDE0IDAgUiA+PgplbmRv\nYmoKNCAwIG9iago8PCAvQTEgPDwgL0NBIDAgL1R5cGUgL0V4dEdTdGF0ZSAvY2EgMCA+PgovQTIg\nPDwgL0NBIDEgL1R5cGUgL0V4dEdTdGF0ZSAvY2EgMSA+PiA+PgplbmRvYmoKNSAwIG9iago8PCA+\nPgplbmRvYmoKNiAwIG9iago8PCA+PgplbmRvYmoKNyAwIG9iago8PCA+PgplbmRvYmoKMiAwIG9i\nago8PCAvQ291bnQgMSAvS2lkcyBbIDEwIDAgUiBdIC9UeXBlIC9QYWdlcyA+PgplbmRvYmoKMjEg\nMCBvYmoKPDwgL0NyZWF0aW9uRGF0ZSAoRDoyMDE0MDIyMDE3NTMyNS0wNycwMCcpCi9Qcm9kdWNl\nciAobWF0cGxvdGxpYiBwZGYgYmFja2VuZCkKL0NyZWF0b3IgKG1hdHBsb3RsaWIgMS4xLjEsIGh0\ndHA6Ly9tYXRwbG90bGliLnNmLm5ldCkgPj4KZW5kb2JqCnhyZWYKMCAyMgowMDAwMDAwMDAwIDY1\nNTM1IGYgCjAwMDAwMDAwMTYgMDAwMDAgbiAKMDAwMDA3MzYzOCAwMDAwMCBuIAowMDAwMDczNDQ0\nIDAwMDAwIG4gCjAwMDAwNzM0NzYgMDAwMDAgbiAKMDAwMDA3MzU3NSAwMDAwMCBuIAowMDAwMDcz\nNTk2IDAwMDAwIG4gCjAwMDAwNzM2MTcgMDAwMDAgbiAKMDAwMDAwMDA2NSAwMDAwMCBuIAowMDAw\nMDAwMzg4IDAwMDAwIG4gCjAwMDAwMDAyMDggMDAwMDAgbiAKMDAwMDAwMTMzMyAwMDAwMCBuIAow\nMDAwMDczMDYwIDAwMDAwIG4gCjAwMDAwNzI3MTIgMDAwMDAgbiAKMDAwMDA3MjkxOSAwMDAwMCBu\nIAowMDAwMDcyMjM5IDAwMDAwIG4gCjAwMDAwMDEzNTMgMDAwMDAgbiAKMDAwMDA3MzI3NyAwMDAw\nMCBuIAowMDAwMDcyMzc4IDAwMDAwIG4gCjAwMDAwNzIyMTYgMDAwMDAgbiAKMDAwMDA3MjE5NCAw\nMDAwMCBuIAowMDAwMDczNjk4IDAwMDAwIG4gCnRyYWlsZXIKPDwgL0luZm8gMjEgMCBSIC9Sb290\nIDEgMCBSIC9TaXplIDIyID4+CnN0YXJ0eHJlZgo3Mzg0OQolJUVPRgo=\n", - "metadata": {}, - "output_type": "display_data", - "png": "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\n", - "text": [ - "" - ] - } - ], - "prompt_number": 9 + "output_type": "execute_result" }, { - "cell_type": "markdown", + "data": { + "application/pdf": [ + "JVBERi0xLjQKJazcIKu6CjEgMCBvYmoKPDwgL1R5cGUgL0NhdGFsb2cgL1BhZ2VzIDIgMCBSID4+\n", + "CmVuZG9iago4IDAgb2JqCjw8IC9YT2JqZWN0IDcgMCBSIC9QYXR0ZXJuIDUgMCBSCi9Qcm9jU2V0\n", + "IFsgL1BERiAvVGV4dCAvSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdIC9FeHRHU3RhdGUgNCAwIFIK\n", + "L1NoYWRpbmcgNiAwIFIgL0ZvbnQgMyAwIFIgPj4KZW5kb2JqCjEwIDAgb2JqCjw8IC9Hcm91cCA8\n", + "PCAvQ1MgL0RldmljZVJHQiAvUyAvVHJhbnNwYXJlbmN5IC9UeXBlIC9Hcm91cCA+PiAvUGFyZW50\n", + "IDIgMCBSCi9NZWRpYUJveCBbIDAgMCAxNTIuMzk4NDM3NSA4Ny4xOTIxODc1IF0gL1Jlc291cmNl\n", + "cyA4IDAgUiAvVHlwZSAvUGFnZQovQ29udGVudHMgOSAwIFIgPj4KZW5kb2JqCjkgMCBvYmoKPDwg\n", + "L0ZpbHRlciAvRmxhdGVEZWNvZGUgL0xlbmd0aCAxMSAwIFIgPj4Kc3RyZWFtCnicxZi/btwwDMYz\n", + "a+wTCJ3aRSEpiZLGHtAG6FbkgL5AmwTBXYCmQ16/dJycRcWq7SV3k81Pf76fzyZpo703l1/Q3v61\n", + "ZL9bsE8W7ZUBOTpajOR8ycGnKOcHdZ6Tw0KY5fAgojq9Mw+yKA2Lgiv+9WdvZeCVoewCR6ZoCVyk\n", + "jHIkewVw0IYPdTix8y/hao0qKvveiHOhcF5t6qJgqWDPRRXlkwmzm92vHp1g8rYzzf5on8xuby+/\n", + "oUWw+xsjO2L0w+gk06Ld/zKfPn64wAv4bPf39uu+XeGZwITgAuSQCQMVZbtRlHWt1fYbZRNCIAdA\n", + "5F/mnjAWEFiiEb0vlGNSCI2iELRWIzTKJoQYXSglJU7PcyeEpf9BDFAJWDhQZgXRKApCazVEo2yC\n", + "SOiQ0efhCnANQQsQhRz5BNHHhEFBNIqC0FoN0SibICR5QCjxde4E4RcgELLDVAqCTPQ6nzSSwmjE\n", + "mqOVNoEgkCuFwuvkiSQskVBwSDTejKhJGkmTaFGRNNI2Esyu5PLyp9QkcYlkLpHr/K4J5jK8Gr/R\n", + "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\n", + "c+tTOz5911Weq+KfyX7mK9f4ru2/9t5rv3Pdjs+e9Lmxz09d/+ANX/uTxTce/8W5N+Vv+sub/vam\n", + "vTcdutm+uf/m3bc8sXXktk/dduNtk7d/4kuXfelzX/7Uf3vov/3sT/N/etmffvtPX9n2uW3T2/Zv\n", + "e/eO+p32nVfe+fCdu+985c5Df9b1Zz/88xu+kv/K1r84/i+uu2vwbnH3GXdfdvef3f303b+455R7\n", + "7tpe3X7a9s9tv2P7S9v3bP/ZvcvuPePej997yb1X33vjV6/56k1fveOr9311x32R+/L3dd83et9J\n", + "951y30P3/eV9z973/P3x+8v3998/dv/i+z9y/7n3X3j/p+7/wv2333/X/Q/d/5f3P3v/8/f/8P7X\n", + "vrb7a9//2itfe+Nrv35g9IGTHjjlgdUPfOKByx+MPJh/sPvB0QdPevCUB1d/fcnXV339Y1/f9PUr\n", + "v37DQ2se2vjQ5oc+99DWb4x848RvTHzjrG9s+MZl3/jdw+Jh8+GtD08/vPfhAw//7hHxiPlI9pGu\n", + "R0YeOfGRiUfOemTDI5c9cs0jNz1yxyP3PbLj0Tseve/RHY9OPrr7sfHHTntszWMbH9v82Od29O6Y\n", + "u2N8x2k71uzYuGPzjjt23PH4xidGGLbLSFJ4/gGetwHbPiBGW4PDraHh1qDd6p5udQ/vSvvebQ3Z\n", + "rebeXUXfu+KvvFqXb+CvmloO1KdpvoGR2fPnzUn19MydPzY2/yTvvLndjXog2DM2Nmc0nUryLwIG\n", + "UplYLabheG3BPI8VTMfsZNg3VKkMBUaDp4yNLct1NwOB5w5t1P7hkLjq5JOvii3IWaVYNJOI6V2z\n", + "B+eEJhYtP7E6r1FLJOc+7bn4vbs99703iiELoaJ1z196P+3phucXWhBe6XXEDh9qX2Fo6yYXGisN\n", + "zxbajS2TO6PPRT3rpnZHp6P7ot51grZmHe3UOhqhdbQruETNkdlH2r240+5G2e6YbLe90NC2jFsP\n", + "GDuN54wfGK8bbxuBde0rSnxSkk9KO0vPlX5Qer30dimwbmT2B4zzetneQPsKC29xgKJ9mKdvcxgL\n", + "3bF0RoVGZs73nk47fyvbWdleFUPtK2KofQPIlIjZsWrMu2Vqd2w6ti/mxZ2aXavWvKhUY481Dc9q\n", + "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\n", + "8nhdaeWPtzq2X+MRXM7vGlfxRN0HC/abRZ3KRXzGa3d631Ht98JcicgNdcQqKUqZ6E3DnLF1FvY7\n", + "eZnDvTDnpW15FMecizPZO9u9Le+ZJTtX3t783qOrMa9dC84q3FMTcg5yLytRVqPgq8w9vknuOmc/\n", + "PR5lys63JNwR8+s1z9HZPwdhKHH7aW+r6GPj97OSu8K2X3Fa2ae3o0xbVLqupgrbKrRY99j4tDtj\n", + "szhbi6jkd5v8Ph2TI+ZOMtF/8p5ds0iViz6cPx2Sjntotbi/7rZPlYiNdpxxoLjDn8lFuYhrw2Je\n", + "Z2udK6mzrvmTIOGekF89w7S0DL/j19QeWqQrtV5cDS2uvtZppQ1yXP+pe0bnPS1/+i+gju98rx4v\n", + "7czeXuL+0ijin9cn7+Nv/9TQ2TKOUizWcO/Tc6sMS4nLn1z59Zt1PA/PFsu8VhWOKNP+t8+9rojO\n", + "ROTG823graNFjdtWyxJvS1WiP/EsnMM7Tctas9Bpve97vRP973J/lFrpOjj9uah+2dmO8beXQp/v\n", + "3Mln3Hx3lv/5++0zyXJ1ud+2CP+8necX3Ifch638K91VVt7vuOPud93fuI+4q901drWccL/nft/9\n", + "gb0hzbHa/BlvVXusbP0jwn9otflj9/3uF+2NaoG73t3g/sLeGv/enstfdS9bS1+xp/RCu3Pc5P7W\n", + "Pedudn9lb0S6Nz1k7f2ivV1Mtdr3WYxnug9YW5e5j7md7i63y93iFrk33Dfcg9bn/tTa9XV3v40V\n", + "3uvOdZ9zx6zHjLkvu0/buOtZqavdq9Z7honI3e497in3K+6Xjatv2ojhp6xHf8ld6n7T/Yy7xD3v\n", + "VrvPGIfvt3fcJ91vuBesl71p7/prLbojxkLb3Wq9Yb17n3vC/bnb4LvcP7gfcf/oNrrLrYd027vo\n", + "qLvH3et+yb1ld6eL3L+4f3X/5A67R91PuvvcZhsTTrdRRK873092L7pzbOy7xe5Uj9to5bfcr7pn\n", + "bGz4a+4rbprbaiOfP3G3ub90D7hZ7jL3bhsXvWTjytvdt9zF7t/cP7vX3efdu9y33R3uE+6T7lPu\n", + "gPF7p9vmPu62u79zR90Pux3ur92Fforv9j2+10/10/x0P8PP9Of4c/15/nx/gb/QX+Tf5S92j/lL\n", + "/KXuJ/y7bWT3AzYS/4L7afdjNhb/df8eG7P+rI3Ofshf7t/rZ/n3+ZL7L/9+d9Jf4T/gP+g/5D/s\n", + "r7QR1Uf81e7f/TV+tr/Wz/Ef9XP9PD/fX+cX+Ov9DT7woY/8Qn+jv8nf7G/xH/OLfJ9f7Je4//RL\n", + "/TK/3K/wK/0qv9qv8Wv9rX6dX+83+I1+k9/st/it7r/9bd752/0d/k6/zX/cb/c7/F02Uv4Pv8uX\n", + "/W5f8f0+9nv8gB/0iU9tTF71Q77m6zZ6vts3fNO3fNuP+L1+nx913/H7/T3+Xn+f/4Q/4D/pP2Uj\n", + "20/7+/1B/4B/0B/yn/Gf9Z/zn3cfdN83ZU6tXa12D5UrjXrtnOG4kdT7bXDFiGnysnajPnWgUR6J\n", + "51TKw1PLlXaL1DmVpFFpD+2pxvvYUSnbwR2pcrU1tZVU+8k8oz+xwppJUz+m5SdSsqddS+bOXxJN\n", + "3d2I8xP0NpLagBLnDbZrA+VGe6habre0YWZ/vVWuqF76Nb1SHxoq57/P7UjrvFOWxtVWmbKvixbk\n", + "331R/r14ydTyniS5Yd78MJoaN1vJULkV92vf8nD5cn3Pnz/v+uI76unL69rdRwV7+uoD9VqcTV8y\n", + "0fhpS07Vq3spTbevRr3c6l7Gr55lRRHLKGLaslPZe5YVpa3oKG3Fqd0zVnQ0a/rKiTyTV+4uN7pX\n", + "EdyeVXnp01ZNFLuqKHb1xCEz1nSU1b0WErvXUr8Zazt2TV5rxXSvy/evy/ev69jfs75ozHoaM3N9\n", + "J0ndm/LjNuXHbeo85WZ2Td/cUaXNnfu35Mds6TwXfWNe3+Qtau7WvLlbi/Nv5fxTtqq3zNzaWYue\n", + "rUXzb5841/Q7J9Ld26jKtG0TASsXhZZzkstFAZUOWioTJPfnJPfnJMc5yXFRRJyTHE8UHhelDXSU\n", + "NjBB8kAnyYMdJA+q1Une6iQvvSfJy+q1w6txs5lOTzvimXXGs5pTUc3DWu2kuCqKa/n+Wr6/1lmJ\n", + "Wnm43mw16sODcU+9aFY9p7t+Gt0NypjR6DxvIw9OM6e72VG9Zme2Vn7e1nfTvXhySw1v5w1vF+dv\n", + "53S3obt9Gt3tIr57O+ge7aB7f073/lMhn7Rq9aQk5XRz+5YW33NPzd65cQeZPZ9m2VPfL12+dos9\n", + "y/gXrk+eZI/P4kbNtuX5vO2bxHevfWrknN97YvzPvdK10Pd23dt11C+c/J0p39bfpKWT1k3aMmms\n", + "90TX3N5ne7/MH7m7FhZ/9/J3NP/Tcd3f6Lmt5yv66x3hmBP61/jsbFPsidxj577Q3guuxsFzg717\n", + "5O8bI/asP2ZP/uP2FvE1e3t4071VPB/Hn2n5U2z86aUn1hq/s3i+DPMMGbNnubwdcnbI1yFXhzwd\n", + "cmjIUXH85EtyRMgPITcEDoQZ6IalGpZmWIph6YXlQpIHSatqWi/TapnWyqR9XXzWc8g1Is+IHCPy\n", + "i8hnIX+FnCLbKfEAHhE5ROQPkTtE3hDpVuULkStEnhA5QuQHkRtEXhDVWz4QuUDkAZEDRP4PuT/k\n", + "/ZDzQ/pPqT9RU3a0z+ogp4d8HnJ5yOMhh4f8HXJ3yNshZ4d8HXJ1yNMhR4f8HHJzyMshJ4d8HHJx\n", + "yMOBRvGAHafYTUIFfIy5ZWmAp6MBlgJY+t8TllMr4loP12q41sKHbdvd9pHe9xr0vlL7SlMqpa90\n", + "vlL5SuMrha/0vVL3KkZS9krXK1WvNL1S9ErPKzWvtLxS8kqhqhUIrT9o9UFrD1p50LqD1hu02qC1\n", + "Bq00aJ1BqwxaY9AKg9YXtLqgtQWtLGhdQasKWlOQNncSSlnpZM/DISenm9xx8sbJGSdfnFxx8sTJ\n", + "ESe9odSG0hpKaSidoVSG8sCdi3dNTjT51uRak2dNjjX51eRWewvtXTfaPCnzpMuTKk/+tA340+RO\n", + "kzdNzjT50uRKkydNjjT50eTUkkNLTjT50ORCkwdNDjT5z+Q+k/dM6mxps+U6k5JdjjP5zeQ2k9dM\n", + "TjP5zOQyk8dsnF25y+Qtk7NMvjK5yuQpk6NMfjK5yeQlkzpD2gwpM6TLkCpDmgwpMqTHkBpDWgwp\n", + "MaTDkApDGgwpMKS/0FqztBdSXkh3odV0raVrJf3M3iWdhVQW0lhIYSF9hdQV0lZIWaG1ZznAbsZ1\n", + "JM+RHEfyG8ltJK+RnEbyGcllJNeO3DryF22nlx45aw+Vn+jte+QR/ENyD8k7JOeQfENyDckz9DpX\n", + "7LdQQ0gLISWEdBBSQZytx0r50NErUTywpmcfaR2kdJDOQSoHaRxGuVovxf1z81nvdPJFyBUhT4Qc\n", + "EfITyEcgL4ScEPJByAUhD4QcEPI/yP0g70PeX57A9SDPgxwP8jvI7fAUV8thfA5yOcjjIIeD/A1y\n", + "N8jbIGfDG9yTJ+6w0iJIiSAdglQI0iBIgSD9gdQHeX94At2BVAfSHEhxIL2B1AbSGkhpkHN9xEaX\n", + "a22crLH4iI1MD9r3IRuVPWSfhy39iH2O2OdZez4dtTHvc/Z53vYds88L9pGOQCoCaQikIJB+QOoB\n", + "aQekHJBuQKoBaQYOWH6dbYP0AlILSCsgpYB0AlIJSCMghYD0AVIHSBsgZYB0AVIFoAmQIkB6AKkB\n", + "0ALYRzoAqQCkAZACQOv/B6ys2f+vd9C1/wd3UY3cZ2lVVmuyWpHVeqxWY7UWy0qs1mG1Cqs1WK3A\n", + "av1Vq69l2jyL+/BLeBQm0WrV+EJ8E3JNyDMhx4T8EnJLyCshp4R8Ep1PSa2uam1VK6taV9WqqtZU\n", + "8xXV/J1Kf+5/AIGHtqIKZW5kc3RyZWFtCmVuZG9iagoyMCAwIG9iago3MDc0OQplbmRvYmoKMTkg\n", + "MCBvYmoKMTI3MzQwCmVuZG9iagoxNSAwIG9iago8PCAvRmlsdGVyIC9GbGF0ZURlY29kZSAvTGVu\n", + "Z3RoIDY3ID4+CnN0cmVhbQp4nO3NMQ0AIQAEwVNMTYKOV4AZKhosIOQxQUNmuq02uWynZ2WmpWac\n", + "LreHAAAAAAAAAAAAAAAAAAAAAPCY7weB+gXnCmVuZHN0cmVhbQplbmRvYmoKMTggMCBvYmoKPDwg\n", + "L0ZpbHRlciAvRmxhdGVEZWNvZGUgL0xlbmd0aCAyNjEgPj4Kc3RyZWFtCnicXVE9b8MgEN35FTem\n", + "Q0Rst5UHC6lKFw9Jq7qdogw2HBZSDQjjwf++fCRu1ZPg6T7ece+gx/a11coDfXeGd+hBKi0czmZx\n", + "HGHAUWlSlCAU9zcv3XzqLaGB3K2zx6nV0pCmAfoRkrN3K+xehBnwgQAAfXMCndIj7L6OXQ51i7Xf\n", + "OKH2cCCMgUAZ2p16e+4nBJrI+1aEvPLrPtB+Kz5Xi1Amv8gjcSNwtj1H1+sRSXMIxqCRwRhBLf7l\n", + "q8wa5FZexfIAzwwuf9wiQ5mhyvCY4enOuKYGdXbrW4M6hsuyiNQMl4zXOM/95Tha3OOmmy/OBclp\n", + "2UlrVKk0bv9hjY2seH4AHtCFLgplbmRzdHJlYW0KZW5kb2JqCjEzIDAgb2JqCjw8IC9DSURUb0dJ\n", + "RE1hcCAxNSAwIFIgL0ZvbnREZXNjcmlwdG9yIDEyIDAgUiAvQmFzZUZvbnQgL0F2ZW5pci1Cb29r\n", + "Ci9DSURTeXN0ZW1JbmZvIDw8IC9PcmRlcmluZyAoSWRlbnRpdHkpIC9TdXBwbGVtZW50IDAgL1Jl\n", + "Z2lzdHJ5IChBZG9iZSkgPj4KL1N1YnR5cGUgL0NJREZvbnRUeXBlMiAvVyAxNyAwIFIgL1R5cGUg\n", + "L0ZvbnQgPj4KZW5kb2JqCjE0IDAgb2JqCjw8IC9FbmNvZGluZyAvSWRlbnRpdHktSCAvQmFzZUZv\n", + "bnQgL0F2ZW5pci1Cb29rCi9EZXNjZW5kYW50Rm9udHMgWyAxMyAwIFIgXSAvU3VidHlwZSAvVHlw\n", + "ZTAgL1RvVW5pY29kZSAxOCAwIFIgL1R5cGUgL0ZvbnQKPj4KZW5kb2JqCjEyIDAgb2JqCjw8IC9E\n", + "ZXNjZW50IC0zNjYgL0ZvbnRCQm94IFsgLTE2NyAtMjg4IDEwMDAgOTQwIF0gL1N0ZW1WIDAgL0Zs\n", + "YWdzIDMyCi9YSGVpZ2h0IDAgL1R5cGUgL0ZvbnREZXNjcmlwdG9yIC9Gb250RmlsZTIgMTYgMCBS\n", + "IC9Gb250TmFtZSAvQXZlbmlyLUJvb2sKL01heFdpZHRoIDY4MiAvQ2FwSGVpZ2h0IDAgL0l0YWxp\n", + "Y0FuZ2xlIDAgL0FzY2VudCAxMDAwID4+CmVuZG9iagoxNyAwIG9iagpbIDQ4ClsgNTY5LjMzMzMz\n", + "MzMzMzMgNTY5LjMzMzMzMzMzMzMgNTY5LjMzMzMzMzMzMzMgNTY5LjMzMzMzMzMzMzMKNTY5LjMz\n", + "MzMzMzMzMzMgNTY5LjMzMzMzMzMzMzMgNTY5LjMzMzMzMzMzMzMgXQo1NiBbIDU2OS4zMzMzMzMz\n", + "MzMzIF0gODcyMiBbIDY4MiBdIF0KZW5kb2JqCjMgMCBvYmoKPDwgL0YxIDE0IDAgUiA+PgplbmRv\n", + "YmoKNCAwIG9iago8PCAvQTEgPDwgL0NBIDAgL1R5cGUgL0V4dEdTdGF0ZSAvY2EgMCA+PgovQTIg\n", + "PDwgL0NBIDEgL1R5cGUgL0V4dEdTdGF0ZSAvY2EgMSA+PiA+PgplbmRvYmoKNSAwIG9iago8PCA+\n", + "PgplbmRvYmoKNiAwIG9iago8PCA+PgplbmRvYmoKNyAwIG9iago8PCA+PgplbmRvYmoKMiAwIG9i\n", + "ago8PCAvQ291bnQgMSAvS2lkcyBbIDEwIDAgUiBdIC9UeXBlIC9QYWdlcyA+PgplbmRvYmoKMjEg\n", + "MCBvYmoKPDwgL0NyZWF0aW9uRGF0ZSAoRDoyMDE0MDIyMDE3NTMyNS0wNycwMCcpCi9Qcm9kdWNl\n", + "ciAobWF0cGxvdGxpYiBwZGYgYmFja2VuZCkKL0NyZWF0b3IgKG1hdHBsb3RsaWIgMS4xLjEsIGh0\n", + "dHA6Ly9tYXRwbG90bGliLnNmLm5ldCkgPj4KZW5kb2JqCnhyZWYKMCAyMgowMDAwMDAwMDAwIDY1\n", + "NTM1IGYgCjAwMDAwMDAwMTYgMDAwMDAgbiAKMDAwMDA3MzYzOCAwMDAwMCBuIAowMDAwMDczNDQ0\n", + "IDAwMDAwIG4gCjAwMDAwNzM0NzYgMDAwMDAgbiAKMDAwMDA3MzU3NSAwMDAwMCBuIAowMDAwMDcz\n", + "NTk2IDAwMDAwIG4gCjAwMDAwNzM2MTcgMDAwMDAgbiAKMDAwMDAwMDA2NSAwMDAwMCBuIAowMDAw\n", + "MDAwMzg4IDAwMDAwIG4gCjAwMDAwMDAyMDggMDAwMDAgbiAKMDAwMDAwMTMzMyAwMDAwMCBuIAow\n", + "MDAwMDczMDYwIDAwMDAwIG4gCjAwMDAwNzI3MTIgMDAwMDAgbiAKMDAwMDA3MjkxOSAwMDAwMCBu\n", + "IAowMDAwMDcyMjM5IDAwMDAwIG4gCjAwMDAwMDEzNTMgMDAwMDAgbiAKMDAwMDA3MzI3NyAwMDAw\n", + "MCBuIAowMDAwMDcyMzc4IDAwMDAwIG4gCjAwMDAwNzIyMTYgMDAwMDAgbiAKMDAwMDA3MjE5NCAw\n", + "MDAwMCBuIAowMDAwMDczNjk4IDAwMDAwIG4gCnRyYWlsZXIKPDwgL0luZm8gMjEgMCBSIC9Sb290\n", + "IDEgMCBSIC9TaXplIDIyID4+CnN0YXJ0eHJlZgo3Mzg0OQolJUVPRgo=\n" + ], + "image/png": [ + "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\n" + ], + "text/plain": [ + "" + ] + }, "metadata": {}, - "source": [ - "```python\n", - "def foo(bar=1):\n", - " \"\"\"docstring\"\"\"\n", - " raise Exception(\"message\")\n", - "```" - ] + "output_type": "display_data" } ], - "metadata": {} + "source": [ + "plt.hist(evs.real)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```python\n", + "def foo(bar=1):\n", + " \"\"\"docstring\"\"\"\n", + " raise Exception(\"message\")\n", + "```" + ] } - ] + ], + "metadata": { + "signature": "sha256:9fffd84e69e3d9b8aee7b4cde2099ca5d4158a45391698b191f94fabaf394b41" + }, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/IPython/nbconvert/utils/base.py b/IPython/nbconvert/utils/base.py index d6cf2de..fb6e3ca 100644 --- a/IPython/nbconvert/utils/base.py +++ b/IPython/nbconvert/utils/base.py @@ -1,31 +1,19 @@ """Global configuration class.""" -#----------------------------------------------------------------------------- -# Copyright (c) 2013, the 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 -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. from IPython.utils.traitlets import List from IPython.config.configurable import LoggingConfigurable from IPython.utils.traitlets import Unicode -#----------------------------------------------------------------------------- -# Classes and functions -#----------------------------------------------------------------------------- - class NbConvertBase(LoggingConfigurable): """Global configurable class for shared config Useful for display data priority that might be use by many transformers """ - display_data_priority = List(['html', 'application/pdf', 'svg', 'latex', 'png', 'jpg', 'jpeg' , 'text'], + display_data_priority = List(['text/html', 'application/pdf', 'image/svg+xml', 'text/latex', 'image/png', 'image/jpeg', 'text/plain'], config=True, help= """ An ordered list of preferred output type, the first @@ -34,7 +22,8 @@ class NbConvertBase(LoggingConfigurable): """ ) - default_language = Unicode('ipython', config=True, help='default highlight language') + default_language = Unicode('ipython', config=True, + help='DEPRECATED default highlight language, please use language_info metadata instead') def __init__(self, **kw): super(NbConvertBase, self).__init__(**kw) diff --git a/IPython/nbconvert/writers/files.py b/IPython/nbconvert/writers/files.py index 01062e2..d124fb9 100644 --- a/IPython/nbconvert/writers/files.py +++ b/IPython/nbconvert/writers/files.py @@ -93,7 +93,7 @@ class FilesWriter(WriterBase): # Determine where to write conversion results. if output_extension is not None: - dest = notebook_name + '.' + output_extension + dest = notebook_name + output_extension else: dest = notebook_name if self.build_directory: diff --git a/IPython/nbconvert/writers/tests/test_files.py b/IPython/nbconvert/writers/tests/test_files.py index a859a85..38793d1 100644 --- a/IPython/nbconvert/writers/tests/test_files.py +++ b/IPython/nbconvert/writers/tests/test_files.py @@ -59,7 +59,7 @@ class Testfiles(TestsBase): with self.create_temp_cwd(): # Create the resoruces dictionary - res = {'output_extension': 'txt'} + res = {'output_extension': '.txt'} # Create files writer, test output writer = FilesWriter() diff --git a/IPython/nbformat/__init__.py b/IPython/nbformat/__init__.py index e69de29..d5ba916 100644 --- a/IPython/nbformat/__init__.py +++ b/IPython/nbformat/__init__.py @@ -0,0 +1,150 @@ +"""The IPython notebook format + +Use this module to read or write notebook files as particular nbformat versions. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from IPython.utils.log import get_logger + +from . import v1 +from . import v2 +from . import v3 +from . import v4 + +__all__ = ['versions', 'validate', 'ValidationError', 'convert', 'from_dict', + 'NotebookNode', 'current_nbformat', 'current_nbformat_minor', + 'NBFormatError', 'NO_CONVERT', 'reads', 'read', 'writes', 'write'] + +versions = { + 1: v1, + 2: v2, + 3: v3, + 4: v4, +} + +from .validator import validate, ValidationError +from .converter import convert +from . import reader +from .notebooknode import from_dict, NotebookNode + +from .v4 import ( + nbformat as current_nbformat, + nbformat_minor as current_nbformat_minor, +) + +class NBFormatError(ValueError): + pass + +# no-conversion singleton +NO_CONVERT = object() + +def reads(s, as_version, **kwargs): + """Read a notebook from a string and return the NotebookNode object as the given version. + + The string can contain a notebook of any version. + The notebook will be returned `as_version`, converting, if necessary. + + Notebook format errors will be logged. + + Parameters + ---------- + s : unicode + The raw unicode string to read the notebook from. + as_version : int + The version of the notebook format to return. + The notebook will be converted, if necessary. + Pass nbformat.NO_CONVERT to prevent conversion. + + Returns + ------- + nb : NotebookNode + The notebook that was read. + """ + nb = reader.reads(s, **kwargs) + if as_version is not NO_CONVERT: + nb = convert(nb, as_version) + try: + validate(nb) + except ValidationError as e: + get_logger().error("Notebook JSON is invalid: %s", e) + return nb + + +def writes(nb, version=NO_CONVERT, **kwargs): + """Write a notebook to a string in a given format in the given nbformat version. + + Any notebook format errors will be logged. + + Parameters + ---------- + nb : NotebookNode + The notebook to write. + version : int, optional + The nbformat version to write. + If unspecified, or specified as nbformat.NO_CONVERT, + the notebook's own version will be used and no conversion performed. + + Returns + ------- + s : unicode + The notebook as a JSON string. + """ + if version is not NO_CONVERT: + nb = convert(nb, version) + else: + version, _ = reader.get_version(nb) + try: + validate(nb) + except ValidationError as e: + get_logger().error("Notebook JSON is invalid: %s", e) + return versions[version].writes_json(nb, **kwargs) + + +def read(fp, as_version, **kwargs): + """Read a notebook from a file as a NotebookNode of the given version. + + The string can contain a notebook of any version. + The notebook will be returned `as_version`, converting, if necessary. + + Notebook format errors will be logged. + + Parameters + ---------- + fp : file + Any file-like object with a read method. + as_version: int + The version of the notebook format to return. + The notebook will be converted, if necessary. + Pass nbformat.NO_CONVERT to prevent conversion. + + Returns + ------- + nb : NotebookNode + The notebook that was read. + """ + return reads(fp.read(), as_version, **kwargs) + + +def write(nb, fp, version=NO_CONVERT, **kwargs): + """Write a notebook to a file in a given nbformat version. + + The file-like object must accept unicode input. + + Parameters + ---------- + nb : NotebookNode + The notebook to write. + fp : file + Any file-like object with a write method that accepts unicode. + version : int, optional + The nbformat version to write. + If nb is not this version, it will be converted. + If unspecified, or specified as nbformat.NO_CONVERT, + the notebook's own version will be used and no conversion performed. + """ + s = writes(nb, version, **kwargs) + if isinstance(s, bytes): + s = s.decode('utf8') + return fp.write(s) diff --git a/IPython/nbformat/convert.py b/IPython/nbformat/converter.py similarity index 70% rename from IPython/nbformat/convert.py rename to IPython/nbformat/converter.py index 049883a..a2ea892 100644 --- a/IPython/nbformat/convert.py +++ b/IPython/nbformat/converter.py @@ -1,26 +1,11 @@ -"""API for converting notebooks between versions. +"""API for converting notebooks between versions.""" -Authors: +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. -* Jonathan Frederic -""" +from . import versions +from .reader import get_version -#----------------------------------------------------------------------------- -# Copyright (C) 2013 The 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 .reader import get_version, versions - -#----------------------------------------------------------------------------- -# Functions -#----------------------------------------------------------------------------- def convert(nb, to_version): """Convert a notebook node object to a specific version. Assumes that @@ -40,7 +25,7 @@ def convert(nb, to_version): # Get input notebook version. (version, version_minor) = get_version(nb) - # Check if destination is current version, if so return contents + # Check if destination is target version, if so return contents if version == to_version: return nb @@ -60,11 +45,10 @@ def convert(nb, to_version): # Convert and make sure version changed during conversion. converted = convert_function(nb) if converted.get('nbformat', 1) == version: - raise Exception("Cannot convert notebook from v%d to v%d. Operation" \ - "failed silently." % (version, step_version)) + raise ValueError("Failed to convert notebook from v%d to v%d." % (version, step_version)) # Recursively convert until target version is reached. return convert(converted, to_version) else: - raise Exception("Cannot convert notebook to v%d because that " \ + raise ValueError("Cannot convert notebook to v%d because that " \ "version doesn't exist" % (to_version)) diff --git a/IPython/nbformat/current.py b/IPython/nbformat/current.py index aa00cb4..bf4adb7 100644 --- a/IPython/nbformat/current.py +++ b/IPython/nbformat/current.py @@ -1,23 +1,35 @@ -"""The official API for working with notebooks in the current format version.""" +"""Deprecated API for working with notebooks + +- use IPython.nbformat for read/write/validate public API +- use IPython.nbformat.vX directly for Python API for composing notebooks +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. from __future__ import print_function import re +import warnings + +warnings.warn("""IPython.nbformat.current is deprecated. -from IPython.utils.py3compat import unicode_type +- use IPython.nbformat for read/write/validate public API +- use IPython.nbformat.vX directly to composing notebooks of a particular version +""") from IPython.nbformat.v3 import ( NotebookNode, new_code_cell, new_text_cell, new_notebook, new_output, new_worksheet, parse_filename, new_metadata, new_author, new_heading_cell, nbformat, - nbformat_minor, nbformat_schema, to_notebook_json + nbformat_minor, nbformat_schema, to_notebook_json, ) from IPython.nbformat import v3 as _v_latest from .reader import reads as reader_reads -from .reader import versions -from .convert import convert -from .validator import validate +from . import versions +from .converter import convert +from .validator import validate, ValidationError from IPython.utils.log import get_logger @@ -37,6 +49,11 @@ class NBFormatError(ValueError): pass +def _warn_format(): + warnings.warn("""Non-JSON file support in nbformat is deprecated. + Use nbconvert to create files of other formats.""") + + def parse_py(s, **kwargs): """Parse a string into a (nbformat, string) tuple.""" nbf = current_nbformat @@ -54,36 +71,18 @@ def parse_py(s, **kwargs): def reads_json(nbjson, **kwargs): - """Read a JSON notebook from a string and return the NotebookNode - object. Report if any JSON format errors are detected. - - """ - nb = reader_reads(nbjson, **kwargs) - nb_current = convert(nb, current_nbformat) - errors = validate(nb_current) - if errors: - get_logger().error( - "Notebook JSON is invalid (%d errors detected during read)", - len(errors)) - return nb_current - + """DEPRECATED, use reads""" + warnings.warn("reads_json is deprecated, use reads") + return reads(nbjson) def writes_json(nb, **kwargs): - """Take a NotebookNode object and write out a JSON string. Report if - any JSON format errors are detected. - - """ - errors = validate(nb) - if errors: - get_logger().error( - "Notebook JSON is invalid (%d errors detected during write)", - len(errors)) - nbjson = versions[current_nbformat].writes_json(nb, **kwargs) - return nbjson - + """DEPRECATED, use writes""" + warnings.warn("writes_json is deprecated, use writes") + return writes(nb, **kwargs) def reads_py(s, **kwargs): - """Read a .py notebook from a string and return the NotebookNode object.""" + """DEPRECATED: use nbconvert""" + _warn_format() nbf, nbm, s = parse_py(s, **kwargs) if nbf in (2, 3): nb = versions[nbf].to_notebook_py(s, **kwargs) @@ -91,16 +90,16 @@ def reads_py(s, **kwargs): raise NBFormatError('Unsupported PY nbformat version: %i' % nbf) return nb - def writes_py(nb, **kwargs): - # nbformat 3 is the latest format that supports py + """DEPRECATED: use nbconvert""" + _warn_format() return versions[3].writes_py(nb, **kwargs) # High level API -def reads(s, format, **kwargs): +def reads(s, format='DEPRECATED', version=current_nbformat, **kwargs): """Read a notebook from a string and return the NotebookNode object. This function properly handles notebooks of any version. The notebook @@ -110,24 +109,24 @@ def reads(s, format, **kwargs): ---------- s : unicode The raw unicode string to read the notebook from. - format : (u'json', u'ipynb', u'py') - The format that the string is in. Returns ------- nb : NotebookNode The notebook that was read. """ - format = unicode_type(format) - if format == u'json' or format == u'ipynb': - return reads_json(s, **kwargs) - elif format == u'py': - return reads_py(s, **kwargs) - else: - raise NBFormatError('Unsupported format: %s' % format) + if format not in {'DEPRECATED', 'json'}: + _warn_format() + nb = reader_reads(s, **kwargs) + nb = convert(nb, version) + try: + validate(nb) + except ValidationError as e: + get_logger().error("Notebook JSON is invalid: %s", e) + return nb -def writes(nb, format, **kwargs): +def writes(nb, format='DEPRECATED', version=current_nbformat, **kwargs): """Write a notebook to a string in a given format in the current nbformat version. This function always writes the notebook in the current nbformat version. @@ -136,24 +135,26 @@ def writes(nb, format, **kwargs): ---------- nb : NotebookNode The notebook to write. - format : (u'json', u'ipynb', u'py') - The format to write the notebook in. + version : int + The nbformat version to write. + Used for downgrading notebooks. Returns ------- s : unicode The notebook string. """ - format = unicode_type(format) - if format == u'json' or format == u'ipynb': - return writes_json(nb, **kwargs) - elif format == u'py': - return writes_py(nb, **kwargs) - else: - raise NBFormatError('Unsupported format: %s' % format) + if format not in {'DEPRECATED', 'json'}: + _warn_format() + nb = convert(nb, version) + try: + validate(nb) + except ValidationError as e: + get_logger().error("Notebook JSON is invalid: %s", e) + return versions[version].writes_json(nb, **kwargs) -def read(fp, format, **kwargs): +def read(fp, format='DEPRECATED', **kwargs): """Read a notebook from a file and return the NotebookNode object. This function properly handles notebooks of any version. The notebook @@ -163,18 +164,16 @@ def read(fp, format, **kwargs): ---------- fp : file Any file-like object with a read method. - format : (u'json', u'ipynb', u'py') - The format that the string is in. Returns ------- nb : NotebookNode The notebook that was read. """ - return reads(fp.read(), format, **kwargs) + return reads(fp.read(), **kwargs) -def write(nb, fp, format, **kwargs): +def write(nb, fp, format='DEPRECATED', **kwargs): """Write a notebook to a file in a given format in the current nbformat version. This function always writes the notebook in the current nbformat version. @@ -185,28 +184,9 @@ def write(nb, fp, format, **kwargs): The notebook to write. fp : file Any file-like object with a write method. - format : (u'json', u'ipynb', u'py') - The format to write the notebook in. - - Returns - ------- - s : unicode - The notebook string. """ - return fp.write(writes(nb, format, **kwargs)) - -def _convert_to_metadata(): - """Convert to a notebook having notebook metadata.""" - import glob - for fname in glob.glob('*.ipynb'): - print('Converting file:',fname) - with open(fname,'r') as f: - nb = read(f,u'json') - md = new_metadata() - if u'name' in nb: - md.name = nb.name - del nb[u'name'] - nb.metadata = md - with open(fname,'w') as f: - write(nb, f, u'json') + s = writes(nb, **kwargs) + if isinstance(s, bytes): + s = s.decode('utf8') + return fp.write(s) diff --git a/IPython/nbformat/notebooknode.py b/IPython/nbformat/notebooknode.py new file mode 100644 index 0000000..76e7141 --- /dev/null +++ b/IPython/nbformat/notebooknode.py @@ -0,0 +1,21 @@ +"""NotebookNode - adding attribute access to dicts""" + +from IPython.utils.ipstruct import Struct + +class NotebookNode(Struct): + """A dict-like node with attribute-access""" + pass + +def from_dict(d): + """Convert dict to dict-like NotebookNode + + Recursively converts any dict in the container to a NotebookNode + """ + if isinstance(d, dict): + return NotebookNode({k:from_dict(v) for k,v in d.items()}) + elif isinstance(d, (tuple, list)): + return [from_dict(i) for i in d] + else: + return d + + diff --git a/IPython/nbformat/reader.py b/IPython/nbformat/reader.py index bb94ee4..20ddaef 100644 --- a/IPython/nbformat/reader.py +++ b/IPython/nbformat/reader.py @@ -1,37 +1,10 @@ -"""API for reading notebooks. +"""API for reading notebooks of different versions""" -Authors: - -* Jonathan Frederic -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2013 The 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 -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. import json -from . import v1 -from . import v2 -from . import v3 - -versions = { - 1: v1, - 2: v2, - 3: v3, - } - -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- - class NotJSONError(ValueError): pass @@ -80,7 +53,7 @@ def reads(s, **kwargs): nb : NotebookNode The notebook that was read. """ - from .current import NBFormatError + from . import versions, NBFormatError nb_dict = parse_json(s, **kwargs) (major, minor) = get_version(nb_dict) diff --git a/IPython/nbformat/sign.py b/IPython/nbformat/sign.py index 90e578b..fb7b426 100644 --- a/IPython/nbformat/sign.py +++ b/IPython/nbformat/sign.py @@ -1,14 +1,7 @@ """Functions for signing notebooks""" -#----------------------------------------------------------------------------- -# Copyright (C) 2014, The 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 -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. import base64 from contextlib import contextmanager @@ -17,22 +10,21 @@ from hmac import HMAC import io import os +from IPython.utils.io import atomic_writing from IPython.utils.py3compat import string_types, unicode_type, cast_bytes from IPython.utils.traitlets import Instance, Bytes, Enum, Any, Unicode, Bool from IPython.config import LoggingConfigurable, MultipleInstanceError from IPython.core.application import BaseIPythonApplication, base_flags -from .current import read, write +from . import read, write, NO_CONVERT -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- try: # Python 3 algorithms = hashlib.algorithms_guaranteed except AttributeError: algorithms = hashlib.algorithms + def yield_everything(obj): """Yield every item in a container as bytes @@ -54,6 +46,20 @@ def yield_everything(obj): else: yield unicode_type(obj).encode('utf8') +def yield_code_cells(nb): + """Iterator that yields all cells in a notebook + + nbformat version independent + """ + if nb.nbformat >= 4: + for cell in nb['cells']: + if cell['cell_type'] == 'code': + yield cell + elif nb.nbformat == 3: + for ws in nb['worksheets']: + for cell in ws['cells']: + if cell['cell_type'] == 'code': + yield cell @contextmanager def signature_removed(nb): @@ -160,6 +166,8 @@ class NotebookNotary(LoggingConfigurable): - the requested scheme is available from hashlib - the computed hash from notebook_signature matches the stored hash """ + if nb.nbformat < 3: + return False stored_signature = nb['metadata'].get('signature', None) if not stored_signature \ or not isinstance(stored_signature, string_types) \ @@ -178,25 +186,26 @@ class NotebookNotary(LoggingConfigurable): e.g. 'sha256:deadbeef123...' """ + if nb.nbformat < 3: + return signature = self.compute_signature(nb) nb['metadata']['signature'] = "%s:%s" % (self.algorithm, signature) def mark_cells(self, nb, trusted): """Mark cells as trusted if the notebook's signature can be verified - Sets ``cell.trusted = True | False`` on all code cells, + Sets ``cell.metadata.trusted = True | False`` on all code cells, depending on whether the stored signature can be verified. This function is the inverse of check_cells """ - if not nb['worksheets']: - # nothing to mark if there are no cells + if nb.nbformat < 3: return - for cell in nb['worksheets'][0]['cells']: - if cell['cell_type'] == 'code': - cell['trusted'] = trusted + + for cell in yield_code_cells(nb): + cell['metadata']['trusted'] = trusted - def _check_cell(self, cell): + def _check_cell(self, cell, nbformat_version): """Do we trust an individual cell? Return True if: @@ -208,21 +217,25 @@ class NotebookNotary(LoggingConfigurable): it will always be trusted. """ # explicitly trusted - if cell.pop("trusted", False): + if cell['metadata'].pop("trusted", False): return True # explicitly safe output - safe = { - 'text/plain', 'image/png', 'image/jpeg', - 'text', 'png', 'jpg', # v3-style short keys - } + if nbformat_version >= 4: + safe = {'text/plain', 'image/png', 'image/jpeg'} + unsafe_output_types = ['execute_result', 'display_data'] + safe_keys = {"output_type", "execution_count", "metadata"} + else: # v3 + safe = {'text', 'png', 'jpeg'} + unsafe_output_types = ['pyout', 'display_data'] + safe_keys = {"output_type", "prompt_number", "metadata"} for output in cell['outputs']: output_type = output['output_type'] - if output_type in ('pyout', 'display_data'): + if output_type in unsafe_output_types: # if there are any data keys not in the safe whitelist - output_keys = set(output).difference({"output_type", "prompt_number", "metadata"}) - if output_keys.difference(safe): + output_keys = set(output) + if output_keys.difference(safe_keys): return False return True @@ -234,15 +247,14 @@ class NotebookNotary(LoggingConfigurable): This function is the inverse of mark_cells. """ - if not nb['worksheets']: - return True + if nb.nbformat < 3: + return False trusted = True - for cell in nb['worksheets'][0]['cells']: - if cell['cell_type'] != 'code': - continue + for cell in yield_code_cells(nb): # only distrust a cell if it actually has some output to distrust - if not self._check_cell(cell): + if not self._check_cell(cell, nb.nbformat): trusted = False + return trusted @@ -292,14 +304,14 @@ class TrustNotebookApp(BaseIPythonApplication): self.log.error("Notebook missing: %s" % notebook_path) self.exit(1) with io.open(notebook_path, encoding='utf8') as f: - nb = read(f, 'json') + nb = read(f, NO_CONVERT) if self.notary.check_signature(nb): print("Notebook already signed: %s" % notebook_path) else: print("Signing notebook: %s" % notebook_path) self.notary.sign(nb) - with io.open(notebook_path, 'w', encoding='utf8') as f: - write(nb, f, 'json') + with atomic_writing(notebook_path) as f: + write(nb, f, NO_CONVERT) def generate_new_key(self): """Generate a new notebook signature key""" diff --git a/IPython/nbformat/tests/invalid.ipynb b/IPython/nbformat/tests/invalid.ipynb index d53686d..23fc399 100644 --- a/IPython/nbformat/tests/invalid.ipynb +++ b/IPython/nbformat/tests/invalid.ipynb @@ -1,152 +1,306 @@ { - "metadata": { - "cell_tags": [ - [ - "", - null + "cells": [ + { + "cell_type": "markdown", + "metadata": {} + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Lorem ipsum** dolor sit amet, consectetur adipiscing elit. Nunc luctus bibendum felis dictum sodales. Ut suscipit, orci ut interdum imperdiet, purus ligula mollis *justo*, non malesuada nisl augue eget lorem. Donec bibendum, erat sit amet porttitor aliquam, urna lorem ornare libero, in vehicula diam diam ut ante. Nam non urna rhoncus, accumsan elit sit amet, mollis tellus. Vestibulum nec tellus metus. Vestibulum tempor, ligula et vehicula rhoncus, sapien turpis faucibus lorem, id dapibus turpis mauris ac orci. Sed volutpat vestibulum venenatis." ] - ], - "name": 0 - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + }, { - "cells": [ - { - "cell_type": "heading", - "level": 1, - "source": [ - "nbconvert latex test" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Lorem ipsum** dolor sit amet, consectetur adipiscing elit. Nunc luctus bibendum felis dictum sodales. Ut suscipit, orci ut interdum imperdiet, purus ligula mollis *justo*, non malesuada nisl augue eget lorem. Donec bibendum, erat sit amet porttitor aliquam, urna lorem ornare libero, in vehicula diam diam ut ante. Nam non urna rhoncus, accumsan elit sit amet, mollis tellus. Vestibulum nec tellus metus. Vestibulum tempor, ligula et vehicula rhoncus, sapien turpis faucibus lorem, id dapibus turpis mauris ac orci. Sed volutpat vestibulum venenatis." - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Printed Using Python" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print(\"hello\")" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "hello\n" - ] - } - ], - "prompt_number": 1 - }, + "cell_type": "heading", + "level": 2, + "metadata": {}, + "source": [ + "Printed Using Python" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "heading", - "level": 1000, - "metadata": {}, - "source": [ - "Pyout" + "name": "stdout", + "output_type": "bad stream", + "text": [ + "hello\n" ] - }, + } + ], + "source": [ + "print(\"hello\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Pyout" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import HTML\n", - "HTML(\"\"\"\n", - "\n", - "HTML\n", - "\"\"\")" - ], - "language": "python", + "data": { + "text/html": [ + "\n", + "\n", + "HTML\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 3, "metadata": {}, - "outputs": [ - { - "html": [ - "\n", - "\n", - "HTML\n" - ], - "metadata": {}, - "output_type": "pyout", - "prompt_number": 3, - "text": [ - "" - ] - } - ], - "prompt_number": 3 - }, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.display import HTML\n", + "HTML(\"\"\"\n", + "\n", + "HTML\n", + "\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%javascript\n", - "console.log(\"hi\");" - ], - "language": "python", + "data": { + "application/javascript": [ + "console.log(\"hi\");" + ], + "text/plain": [ + "" + ] + }, "metadata": {}, - "outputs": [ - { - "javascript": [ - "console.log(\"hi\");" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "" - ] - } - ], - "prompt_number": 7 - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {} - }, + "output_type": "display_data" + } + ], + "source": [ + "%%javascript\n", + "console.log(\"hi\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Image" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import Image\n", - "Image(\"http://ipython.org/_static/IPy_header.png\")" - ], - "language": "python", + "data": { + "image/png": [ + "iVBORw0KGgoAAAANSUhEUgAAAggAAABDCAYAAAD5/P3lAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n", + "AAAH3AAAB9wBYvxo6AAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAACAASURB\n", + "VHic7Z15uBxF1bjfugkJhCWBsCSAJGACNg4QCI3RT1lEAVE+UEBNOmwCDcjHT1wQgU+WD3dFxA1o\n", + "CAikAZFFVlnCjizpsCUjHQjBIAkQlpCFJGS79fvjdGf69vTsc2fuza33eeaZmeqq6jM9vZw6dc4p\n", + "BUwC+tE+fqW1fqmRDpRSHjCggS40sBxYDCxKvL8KzNBaL21EPoPB0DPIWVY/4NlE0ffzYfhgu+Qx\n", + "GHoy/YFjaK+CcB3QkIIAHAWs3wRZsuhUSs0CXgQeBm7UWi/spn0Z+jA5yxpEfYruqnwYllRic5a1\n", + "MaWv8U5gaT4M19Sx396IAnZLfB/SLkEMhp5O/3YL0AvoAHaKXl8HLlZK3QZcpbWe0lbJDOsaHuDU\n", + "0e4u4JAy2wPk/C1JzrKWArOQ0fUtwH35MOysQxaDwbCO0NFuAXoh6wPjgQeUUvcqpUa0WyCDoQls\n", + "CIwBjgfuAV7KWdY+7RWpmJxlXZezrEdylvXxdstiMKzrGAtCYxwI/EspdZbW+g/tFsbQ67kQuBHY\n", + "FNgseh9FV6vCbUAeWBC9PgBeq2EfS6J2MQOBrRDTe5KdgAdzlvW1fBjeUUP/3UbOsoYBE6OvG7VT\n", + "FoOhL9Af+BUwFLkZpV+DaY6V4UPkRpb1+ncT+m8nGwK/V0oN01qf025hDL2XfBi+DLycLMtZVo6u\n", + "CsKfGnSq8/NheEpqHwOBEcDBwJnAsGhTP2ByzrJG5cPwnQb22Sy+0G4BDIa+RH+t9dmlNiqlFKIk\n", + "JJWGi+jq5JPmq8BbJJQArfXqpkncczlbKbVQa/3rdgtiMNRCPgxXAK8Ar+Qs63LgXmDvaPPGwPeA\n", + "H7VJvCRfbLcABkNfouwUg9ZaAwuj178BlFLvVejzgR4WFviM1npcuQpKqf6IyXIjxLS7GzAWuUnu\n", + "XsO+fqWUellr3ZBJdq/jr9+BDn1uve07O9Rz0y6f8PtGZGgWe53oT6SBkZ/q1/nHZy47aloTRTKU\n", + "IR+Gy3OWNR6Zxtg0Kv4KRkEwGPocxgcBiCwcsSI0F5iOhF+ilPok8C3gVGS+thK/VErdrbWuO2ys\n", + "s/+aLZTuOKbe9krrIUCPUBB0B+PQ1P1bdKe6EzAKQgvJh+GbOct6gkJkxM45y+qXDIWMHBhjBWJe\n", + "PgyDWvaRs6zPIVObAG/nw/DpEvUGAp8E9gGGJzbtl7Os7cvs4skqp0V0Yl8jgcOBjyMDhbmIZeWl\n", + "fBg+UUVfReQsayhwELAnsAXi6/E28BxwTz4MP6iyn92RaSCA+/NhuCwqXx9R4MYhU0MfRTK/AjyW\n", + "D8MFGd0ZDFVhFIQKaK3/BXxfKXUlklTq0xWafAI4Driyu2UzGLqRlygoCArYHJif2H4gcFb0+Z2c\n", + "ZW2bD8NV1XScs6yNgH8g/jsAPwCeTmzfFPgjYsnbiez71MUVdnMQcF8V4nyUs6whwB8QX4+0s2Ys\n", + "0yPAt/NhGFbRZ/wbzgO+DaxXotqqnGX9GbigCkXhf5CBCsDngYdzljURGQhsWqLN+znL+iFwdT4M\n", + "dYk6BkNJTJhjlWitQ2Bf4P4qqv848t8wGHor6Yd9+ruHJFkC2BI4rIa+D6egHKwmstYlGAxMQCwH\n", + "rRjEPI5ER5S7ZvcFXsxZ1phKneUsawSi8HyH0soB0bbvAM9Ebaplt5xlnYkct1LKAYiFZhJwSQ19\n", + "GwxrMRaEGtBar1RKfRX4JxIzXortou3PN1mE+YgJsSwaeoLHOQCqUy3QSr9eqZ6G/gq2aYVMhqrY\n", + "OfF5FeJwvJZ8GM7JWdY/gC9HRS7wtyr7Pjrx+e6MqYC3KLbU7Qhck/h+FJIKvRRVjfSREXicU8EH\n", + "pgAvIIqLBZwGfC7avl5Uf29KkLOsTZCMq8npj9sQx89no37HIlaAODplNPBIzrJ2z4dhNVlaT0HC\n", + "XwFmIkrAC4if2PaIz8/3KCgn385Z1pX5MJxeRd8Gw1qMglAjWutlSqnTgUcqVP0SzVYQtP5mcMXE\n", + "SvvtUUy9YsK5QEWHy7EnTB6lOtSsFohkqEDOsgYAdqJoagkT9Z8pKAj75yzr4/kwnF2h748ho/GY\n", + "q9J1oqiKLj4JOctKK8Yz8mH4Yrl9VcnHkXVYTsyHoZ8WJWdZNyPThbF5/3M5yzowH4alpi9+T0E5\n", + "WA18Nx+Gf0zVeRG4KmdZ90R9bwCMRKwyX69C5h2j91uA4/JhuCSxbTYwJWdZtwNPIFbifsAFSISZ\n", + "wVA1ZoqhDrTWjyIjjXIc3ApZDIZu4ELgY4nvt5Wody8wJ/qsgBOr6HsihfvOfCRrY7v5dYZyAECk\n", + "GP0ISEZmZYZ55yxrB8SyEXNxhnKQ7Pt64H8TRUfmLGuXKmWeC4xPKQfJvp9CLCJlZTYYymEUhPq5\n", + "tcL2XVsihcHQJHKWtU3Osi5GnAZj5iKWgiKitRouTxQdl7OscnPu0HV64dp8GLY7R8pyxEGxJPkw\n", + "fBcZ9ceUSvN8IoV76upK/UZcgawcG3NKqYopfleFU+gDic/b5SzLWIwNNWFOmPqp5CG9sVJqPa11\n", + "VZ7dBkOL2D1nWcmcBkOR8MFtgM/QdTXJZcCR+TBcXqa/SYj5egAFZ8VMX4ScZe2FRPnEXF2z9M3n\n", + "3nwYVsrtAmK6/0z0uVR4ZXLtivvzYfhGpU7zYbgkZ1k3ACdHRQdWIQsUO3ZmkUzB3Q/xjaolLbeh\n", + "j2MUhDrRWr+mlFpJ+eV5hyIxz4YWs98Fj/Rf8uZbozo0/ZYt7D8rf9ORK9stUw/hU9GrEnMAp1R+\n", + "gph8GL4bzdNPiIpOorSzYtJ68FS1IYPdTLWp3hcnPm+Q3pizrA7E+TCmFn+aZN0dcpY1LB+G5e4b\n", + "y6rM8bA49X39GmQyGMwUQ4NUGnkMrbDd0A3sdeLk4z6cN+89pTtDTWd+gyErF+7pTv5eu+XqJbyK\n", + "TDHsmg/DJ6tsc2ni8+dzljUqXSGaevhmoqjIObFNVBzlV8kQug4W5tbQNl13WGatAv+poW+DoW6M\n", + "BaExPgC2LrO9nHWhpSilDqI4NPMhrfXUJvS9M/DfqeJXtdY3N9p3rex50uQ9lFKT6BrTvoFCXbTX\n", + "yZNfmnrZxHtbLVMP4xng74nvK5DzeD7wfIWRayb5MHwiZ1kzgF0oOCuemar2ZQoK8zLgr7Xup5t4\n", + "s0n9DEl9b0RBSPeV5q0a+jYY6sYoCI1RacnZ91siRXUMAH6eKnsYicdulDOAY1NlpzWh35pRqG9R\n", + "IuGN7uw4AfG878s8nw/DX3RDv5dScGY8NmdZP86HYXJaJzm9cHMp7/s2UHdK9BTpKaxBNbRN163k\n", + "t9Rux05DH8FMMTTGZhW2v9sSKarjbopNk/sqpUY30qlSahCSGS/JCuD6RvqtF6UpMm/HaHTJbYaG\n", + "mQzED/0umRVzlrUZhXwJ0HOmF5pJOlXyxzJrZbNt6rtZP8HQIzAKQp0opTZAlsItxTKtdTnv75YS\n", + "LR7lpYqrjV0vx2EUH4fbtdZtucnpMqOrDjPy6jYii8DkRFHSYnAEhem22cBjrZKrVeTDcCldTf/p\n", + "h345ksrEGprnF2EwNIRREOrnMxW2z2uJFLVxJcXmy2OVUo34ShydUda+EaIq7T2u0SZTY/eSdFY8\n", + "MGdZm0efk86J6/LCQUnFp5pIkZjkcvQz8mH4YZPkMRgawigI9VNp7v7BlkhRA1rr+RQneNqC2hba\n", + "WYtSajiS9z3JXLomaGktq/VllLIUdKqSWe0MjZMPwxlIel8Q/6Zv5CxrGIX8AJ10XU+hFtIRQ+UW\n", + "KWoXyYyTu+Qsa79KDXKWNRpJyx5zZ9OlMhjqxCgIdaCU6g98o0K1npBCNotLM8rcOvuagCRgSXKN\n", + "1rozq3IrCCZNfFkrfRjotWsCaJinUBODK51/tkuuPkTy/DoYOIDCfeb+fBjW4t2/lqhdcmRdbUri\n", + "VnILXS2HZ1WRvfAcCk61K4A/dYdgBkM9GAWhPr5F6XSrIBf6Qy2SpSaidSReShV/XilV7veUIj29\n", + "oOkB2fGmXT7x7sCbOGpFf7VZx4A1m0/znG2nehMyc+0bms7NFJxzxwH7J7Y1OvWUPG9/mLOsLRvs\n", + "r6lEaaOT0TtfBB5ITLWsJWdZg3KWdRNwTKL4wnwYzu9mMQ2GqjFhjjWilBqBpJYtx51a66UV6rST\n", + "S+maJz52VvxRdvVilFK7UbzexGNa67Kr+bWS6X+ekPYs79HkLGt34JOI+Xyz6D2d1vfMnGUdini6\n", + "L0C851/Oh2HD+SyaQT4MV+YsaxJyLm1Gwf9gAXBHg93/JNHHtsArOcuajCztPBDYCkkytBXg5sOw\n", + "5QmF8mF4W86yLgK+HxXtC8zKWVaALMm8CslHsicS7RFzL8VhyAZDWzEKQg0opbYE7qd8prPVdF2h\n", + "rSdyLfALYMNE2XFKqR/XsHbEURll62L4Wiv5PuBUqPPF6JXkLuCQbpGoPi4HfohYKGMHWD9axrlu\n", + "8mF4Z7RuwfioaDBwaonqRemQW0U+DH+Qs6xFwHnIFNwQsv+3mMnA8dHiVwZDj8FMMVSJUuow4DkK\n", + "a7GX4gqt9cstEKlutNaL6boULMho5tBq2iul+lH8IFuCmJcNfZx8GM6hOCFVU5THfBhOQHxfylkH\n", + "3gY+asb+6iUfhhcCewC3l5BlFbJk/P75MDwqlVTKYOgRKK1rizhSSk2h67ximo1abV5XSi2n9EIk\n", + "z2itx5XYVqnfQcjI7DiqW2XtfeCTUbRA3ex50nWfUrqjeJEcrfcLrpj4SCN9xyilxgDPp4of0Fof\n", + "UEXbg4B/pIqv1FrXnVNh7AmTR3V0qIwwRH1E4E28pd5+De0hZ1m/Bb4bfX0+H4Z7dMM+hgGjkDwC\n", + "S5FpjFk9bR4/Z1mDkGmF4VHR20g4Y3oxJYOhR9EXphg6lFLlVjFbH0mZvDGwCTAayCFe0ntTOZ1y\n", + "zDLgkEaVg1ahtX5BKfUU8OlE8ReUUjtorSstCduzch8YehSR5/6ERFG3nBvRuhE9frXUfBguA6pd\n", + "+Mpg6DH0BQXBBro7o+Ea4Bta66e6eT/N5lK6KggKOAE4u1QDpdTGFOdNmNkLf7uh+zgYcRQEMa+3\n", + "Je22wWBoDOOD0DhLgYla67vaLUgd3ETxglLHRXkeSnEExQ5gbQ9tNPQokis5TsqHoVlbwGDohRgF\n", + "oTECYHet9Y3tFqQetNYrKDb/DqN46eYk6emF1UhUhMFAzrImUEhDvgr4VRvFMRgMDWAUhPpYAvwf\n", + "8Bmte31+/8uQBEdJMjMrKqW2o5A2N+YfWusePw9s6F5yltWRs6zxwKRE8RXtyEVgMBiaQ1/wQWgm\n", + "eWTe/jqtdU9Zz74htNavKaXuAw5KFB+glBqptZ6Tqj6RQlrYGDO90AfJWdY5wNeQFQwHIAmetk5U\n", + "eZFCsiCDwdALMQpCed5AphEC4NF12BHvUroqCAoJ7TwvVS+d++BdJEmPoe+xKRLnn0UeODwfhm3N\n", + "RWAwGBqjLygIbwN/LbNdI1MGH6ReL/eWkMUmcDeSeGa7RNlRSqnzdZQoQym1C7Bzqt11NWReNKxb\n", + "zEMU6GHAesBiYCaSLOviaF0Cg8HQi+kLCsLrWuvT2y1ET0ZrvUYp5SG57mO2Bz4LPB59/2ZRQ5P7\n", + "oM+SD8OLgYvbLYfBYOg+jJOiIeZKxOs8STJiIb28daC1/lf3imQwGAyGdmEUBAMA0XTKraniI5VS\n", + "A6O0zOnloI31wGAwGNZhjIJgSHJp6vtgJBNlehW65cANLZHIYDAYDG3BKAiGtWitHwVeShV/muLF\n", + "uW7VWi9qjVQGg8FgaAd9wUnRUBuXAn9IfN8f+FyqTo/OfbDnSX8brDpXnqEUe2ropzQvdtDx66ev\n", + "GN9XolIMPQDb9T8LrBd4zsPtlsXQe7Bd/0BgQeA5QbtlMQqCIc21wC+ADaPv6WWu5wAPtVKgWtjt\n", + "6Os2XG/9jhdQjIzTQ2rFF9bQecy4E2/I9UQlwXb9LYDDK1R7K/Cc21shj6FxbNcfDjwGKNv1Rwae\n", + "83q7ZWo2tusPBb6ELGW9BbAICX99Gngs8Jx0hlZDBWzXHwvcC6ywXX9o4DlL2ymPURAMXdBaL1ZK\n", + "+ZRItwz8Jc6N0BMZMFB9GxiZsWnzTjrPAH7QWomqYgTF/h9pngC6RUGwXf+XwC2B50ztjv57M7br\n", + "XwJMCjxneo1NP0SWgAfJq7LOYLv+esAFwOkUL9wWM912/d0Dz+lsnWQ9A9v1BwEXAT8PPKfWVOML\n", + "kPVt3kNWQm0rxgfBkEWph5UG/tJCOWqnQ40ttUkrvWcrRamWwHOmAZsguSfGAi9Hmy5AUhgPAz7f\n", + "Hfu2XX8k8ENgx+7ovzdju/4uwP9D/peaCDxnCbANsF3gOYubLVu7sF1/AHAHcBaiHDwI/C+ywNsE\n", + "4KfA68BdfVE5iNgbOBmxqtRE4Dn/BoYDnwg8Z02zBasVY0EwFKG1fkEp9RTioJjkIa11zzaVarYq\n", + "vVFt2TpBaiN6oCwB5tiu/2FUPCvwnLTTaLM5oJv77800dGwCz1kXHXkvRNKydwI/Cjzn1+kKtuuf\n", + "i2TX7Ks0et681yxBGsUoCIZSBBQrCL0h98EbdW7rddiuPwoYFJu/bdffFNgL2BZ4DZgWKR5ZbRWS\n", + "2+KIqGiE7fpjUtXmlrtZRdaHscBAYDowM/CckimWbdffFfgw8JzXou/9kfUccojV5MXAcz4s0XYw\n", + "sCsymu8PzAVmBJ7zVqn9pdoPRVKF7wSsAN4EgqzRve36HcAoZDEqgO0zjs3rged8kGo3gOJ05ADT\n", + "s0bTkan+k9HXGaVGjNFxykVf81nH2Hb9Ich/MRJJeT291H9fL7brj6CwANfPspQDgOi3rijRx/rI\n", + "b8kB7wPPBZ4zL6Ne/JvfCDzn/WhufhvgvsBzVkR1dgN2AR4JPGduom38P7wXeM7c6FzfCfgU4iMR\n", + "lFLebNfPIefXzMBzikz8tusPQyx676bljmTeCfhyVLST7frp//TV9Dluu/6GwOhUvTWB58zIkjFq\n", + "sykyNfmfwHMW2K7fLzoWeyDTFPnAc14t1T7qYwNgT+Rc/wi5ZyT/N20UBEMRSqn+wNdTxQspTqTU\n", + "41BaP6yVOipzGzzSYnG6m6uBz0YPv7OQm3dytc35tuuflHZutF3/BuArwEaJ4p/QNdU2wGnAH9M7\n", + "jRSTG5CbS5LQdv2joymTLKYBzwHjbNc/DomW2TCxfbXt+sMCz3k/sa8RwM+Qh/X6qf5W2q4/CTit\n", + "zMN1OPB7CopQktW2658YeM5fEvXvRKZzBiXqZaWUPha4JlW2NfB8Rt0hiANfmjWIuf5jiLPfvVm/\n", + "AfmvbgNmB54zKrkheuD+Bjg11Wap7fpnBJ5TybelFk4E+iE+Fb+ptbHt+scg//nGqfJbgeMDz1mY\n", + "KN4UOZYX2q7fSWHhuNdt198ZOBc4MypbbLv+5wPPeTb6PiJqe5ft+ichx3WXRN8rbdc/OfCcrGis\n", + "R4ChiHKSlSn2f4BzkOvitMRvCKJ9DEzU9TPafwGZlkkyBvExSrKUrtdnmoOBycA5tus/iCyat3li\n", + "u7Zd/0rk2ihS1mzXPwT4E3LulaLTKAiGLL6EaMlJbtBat91pphIjFw289t9DVh4N7Jva9EKnWnpJ\n", + "G0RqBXcjCa08YCqy/PJE4L8A33b9HQPPeTNR/0bgvujzGchoywPSq5U+nd6R7fp7IDfRjYDrEE99\n", + "DeyHrPb5lO364xI36zTb2q4/AUnt/SSyLHQHMvJZklQOIhYChyCLid2FWBoGIQrDfwGnAP8Gskzd\n", + "VvSbBgPvIMdpJjLHuxdikXgg1ewa4Jbo84+BHRAFI/3gT9/QQZa+/iIy9zwccVQrSeA5nbbrX4s8\n", + "cI6htIIQK7xdFJLIAvEEYjmYBlyP/E4LeXj92Xb94YHnnFtOjhrYJ3q/vtbpE9v1fwqcjYxUL0GO\n", + "51bI//g1YIzt+mNTSgJIivfNEIXgBOThfx0ySv8Nct7vgzgfj0+1HQf8E5iPKM/vI+vLHA9cZbs+\n", + "JZSEevgDBZ++3yIKzgVI1FeSrCnD6ci0zebAJxCfjmoZjxzXPPBL5By0gW8jCt3sqHwtkYL1N0RB\n", + "/R2ymOG2yHE5CLFAHAu8ahQEQxbfyijrDdML3HTTkWvUBRfsb88bPb6TzjEK+oHKL184YHL+Jmdl\n", + "u+XrJsYBhwaec0dcYLu+hzw0dkcu/AvjbUmLgu36DqIgPB54zuQq9nURMgI8LjnyBibZrj8z2s/l\n", + "tuvvVcJJbWvkXDoi8JzbKu0s8JxFtut/IqXgAPzOdv0/IiPnb5KhICAjpMGIEjAhPV1iu35HWsbA\n", + "c25ObD8ZURAeqibENBqpTYnark8FBSHiakRBOMx2/cHpB29kSv4KooSlLRYnIcrBHcBXk7/Fdv0b\n", + "gReAM23Xvz7wnJlVyFIJK3qfXUsj2/U/jiiiq4B9ktEytuv/Fhlpfx2xEnw31XxHYLfAc6bbrv8k\n", + "cny/Bnwz8Jy/2q6/DTLd9F8Zu94ceXAeEHhOvM7MNbbrT0UU4vNs15+c2FY3gedcm/hNP0EUhDvL\n", + "KMrJtkuIFPboWNWiIOSAO4HDE7/Dj67FSxEn21+m2pyOWDpuCDxn7fG2Xf8e4F1EIVsceE5oohgM\n", + "XVBKjURuSEke11qXMhv3OPR553VO9Sb407yJZwTexO8FnnNV/qYj11XlAOCfSeUA1s4D/y36mp7f\n", + "rAvb9fdGLDMzU8pBzMXIg2wsMhLKQiFhgxWVg5gM5SDm+uh9VHqD7fr7IlaNFcAJWb4UPcHLPvCc\n", + "2YgVZn3gyIwq30AsQg8lQ+aiefUfR1/PzlB08sD9Udusfmsi2t+Q6GutjspnIE6L16dDaSN/irMR\n", + "p8dTbddPOxK/nwgxTZr8747e30SsEkNL7PvXGQrAVYgvwggK/gK9mXMyfuON0fvWkY9Dkp2i97uT\n", + "hYHnLKNgURsDxknRUMz5FJ8XP22DHIbqSc9pxsSOW8ObtJ89ovdXbNcvpQC8j4zcdiTbnAoy4q2b\n", + "6Ia3CYV5/Y0zqsXOf4/WEYveaq5GQuOOQaZekhydqJNkW2BLZF2UzhL/R+xE2XAIa+A52nb9lUho\n", + "Y63hd7GD5d1ZGwPPmW27/iuIUrkLXc/n9xP13rZd/yNgVezoF8n1NjAyyyKETGGl97fGdv1/IlaL\n", + "3h7e+06WM2PgOQtt11+GTMcNo6vVJ1aWsyK+4nvFQjAKgiGBUmoshfnOmGe11vdl1Tf0GOaUKI9v\n", + "lqrE9lqJb6b/Hb3KsU2Zba/VslPb9bdDfA0ORLz0N62iWWxVqMkc3iZuRuawP2u7/g6JKI9RSCTR\n", + "YoodhOP/YgNKK2Ix2zZJzjnINMN2NbaL/4uiaIUE/0EUhB3pqiCkMwl2IscjXZZFJ/B2iW1xRtWR\n", + "ZWTqDcwps63U9f8Q0TSN7fp/iK0PtuvviPjmrCHyR1qrICilNkTmHjZDLsDke/JzOtwnzY1KqXcR\n", + "R4cFiBab9XlRT87I19dQSo1GNPz0tJOxHvR8mhrOVobB0XuAOBiWo1zmwaqdXW3X3x+4BzGVv4SM\n", + "pN9AnPEg21McxMIArTs2dRN4zoe26/8NOA6xGJwfbYqV9b8GnrM81Sz+Lz5A0qOXo2y4Ww3MoT4F\n", + "IY4+KTfNF58TaXN4VthstVNDitLKcdxvOjKmEj0tv0M953fs87E3Eul0B2JliBflOzfwnFcA+iul\n", + "5iEmwQFNEBaK569L0amUWggcqrXO8gg2FKHG2CdW4Uem9XvBlUflu7RUaiByU3lPa92ZKN8cSav8\n", + "fUQBTHKr1rrqueIsxp18/eg1azrLjSYB6NfRsY3G6Is9nDjDYxh4zundvbMotvtm5N50duA5P09t\n", + "T0faJIkfirU+zNrF1YiC4FBQECZE73/JqB//F+u14r+ImIVEOB1iu/6ZNfhwzEamp7YuU2e7RN1m\n", + "oZBnW5YVIfZ1qNWfotw51yuIph++hET0bAkcikwpTAEuCjxnSly3PzIP0a8NcnYgD6SBlSoaIhQX\n", + "V2UtVup24LBU6S7IyG+NUuodZP52awojrTSvIjeshlij9XdQKh2jXYRRDtpGfOCruQfEpmzbdn0V\n", + "dP9iPLsgjnEryI67Lzd/PCt6/5Tt+v3LJXAqQ/z7ut2ZO/Ccx23XfxUYZbt+7D8xCngl8Jwsa80s\n", + "ZBS8ke36O7cg4ybA5UgegJ0QE/XN5auvZRaiIMQRF12wXX8TCv9ls6eERpOtIMR+EXNS5YsRh8dS\n", + "To/V+CzUck21i6uR5++4wHNeKFXJRDH0PfoR5fqmtHKwDDhCa73O5JA3lCSeF04v6Z3FPRTMzBO7\n", + "S6AE8Q12PbomgYn5Xpm29yMPhu2RUK96iKMn9q6zfa38JXo/NHoly7oQeM5K4Iro60+jKINuJVJC\n", + "Yu/439uuX805A4VkWyfbrp+V/MdFnOmeCmpfFKsSRYMc2/U/DeyG3OfSjpOx5WmfVHmcuXFcFfus\n", + "5ZpqObbrb45EtswqpxyAcVI0FDMbOFxrXeT9a+heopvnEArzolvashT0wmbEapdgGpIU5XDb9R9F\n", + "YqrXQyyL8wPPeTeuGHjOMtv1T0VuqldH6W//jigNmyHOcAcBgwPPcZog20xkRLcJ8DPb9S9CRqM7\n", + "I7kDvoDE1hfdxwLPWWy7/plI7oCLbNffHXm4zUQeRtsjGRP/EXhOKSfcABkpj49i5+9G/putgHmB\n", + "5yxIN4iSF21C14V6Rtiu/yYSW15uHv4a4P8oKAedlPcvOAv4KmItfCTKKfAS8v8NR1ILHwnsl5GA\n", + "qF7ORdYaGA48HGWyfBqYgViDRwCfQR72PkDgOU9E2TvHI4m0TgeeRczb30DyH2iKcyA0ymrgWNv1\n", + "FyDK1NvIQ3tStN3LCH+9HUl29UPb9echFo8BUbtLEKfJtJ9EmgA59ifbrj8bCR3cGDlvZqdTLcPa\n", + "9NCbUMhs2GFLKvPFSAKxZl7/CxEL8pgoA+QMxD+kE3HenAHcHnjOGmNB6Dt8iGjHWSFKK4HHkcQr\n", + "OxvloLXYrr+77fqrEIejNyiE6P0WccZbabv+lFLtG+Ry5AY/BHkYfRDtR9M79QAAA3FJREFUcwYS\n", + "NdCFwHPuQR6a7wHfAR5GMhk+i9xcT6G6KIOKBJ6zFBn9r0GUmBlIWN9ziHf/5yjO/phsfy2yqt4i\n", + "xOJxF3INTI9k/Q7ZoV4xv0PC5LZCci4sQm6g08kYHdquvxy5lt4DwsSmF5EENCts1//Idv3M9LbR\n", + "egJTkEx4NvBA1joFifqLIjkeR6wcfwdeQfIFTEEcjHNU79RXkShvw95Ixs5+yOj/KuSh+ATiAHcq\n", + "xb4fxwOXRfJMQc6zlxGF6B3g4MBznmmWnBFzEUfP0xDFcCGiAG+JHKushESXIdanjRBF4l3EInAj\n", + "8vuOqWK/5yNRGaOQFNkfIhkOX6CQgwAA2/W3jkI3V0T7ejjatAFyXb2PXP/LbVnroWGi6bbzo697\n", + "IlaWk5Br93wkk+jztusP7o94Lna7eaoMZU0cVXIAped7eqGZfP2ZqmPFl+ptrVf3n19UpvVMYLRS\n", + "agBywxuEjLwWAe9qrTMXV2mUzs7OP/Xrp+6qt33Hmn5Zue3XNeZTOVoky5nqKiQkrNT883Qk3WvJ\n", + "sMLAc1bbrv9Z5AH6KWRkOB+5wRWlWo7a3Ga7/mOIomAho/GFyI30YeDREru7ELlOq07TG3jONbbr\n", + "T0Nu9KOQm+i/gFsDz3nTdv2fI2FbpdpfHnlpH4LcnHdAlIz5yLErqXgFnvOR7fo28lDYE7lu3kKO\n", + "TdZ9K52xrhTl7knnUVB6SqVeTsr4apQU6lDEbG4hCsFbROsRBE1ebjrwnNB2/XGIGf5gRBkYhPyv\n", + "7yDpjR9MtVkOnGK7/vWIgrFrVPcF4O8ZKbaXIuduWkH6KfL/JbkEsWClfWK2CDzHt10/jzhXjkGO\n", + "yzNIZEiRD00ga3ocaLv+kUh2xo8hSuVURKmIUyiXVGYCWVzKQlJD7xrJNg85b9LX8RLgF6X6SpFU\n", + "9Cpe28gaJgORqEEAbNffDLlvHIQoAndR8NEYilwjExD/nwuUiTQ0GAwGw7qC7fqjEUvKqsBzmhWd\n", + "t05gu/5pyNoifw48J9N5PForxQeeNFMMBoPBYDD0DWL/llvK1In9jt4zCoLBYDAYDH2DePo5MwrJ\n", + "dv0hFPwTnjBRDAaDwWAw9A3+hPgOHRPl25iK+FhsiuR4OARx0Lwf+J1REAwGg8Fg6AMEnvNklL78\n", + "HMRRca/E5hVINNIVwI2B56z6/3ExLRI31pXNAAAAAElFTkSuQmCC\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 6, "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "png": "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\n", - "prompt_number": 6, - "text": [ - "" - ] - } - ], - "prompt_number": 6 + "output_type": "execute_result" } ], - "metadata": {} + "source": [ + "from IPython.display import Image\n", + "Image(\"http://ipython.org/_static/IPy_header.png\")" + ] } - ] -} + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/IPython/nbformat/tests/test3.ipynb b/IPython/nbformat/tests/test3.ipynb index b661f40..4472ae6 100644 --- a/IPython/nbformat/tests/test3.ipynb +++ b/IPython/nbformat/tests/test3.ipynb @@ -1,11 +1,5 @@ { "metadata": { - "cell_tags": [ - [ - "", - null - ] - ], "name": "" }, "nbformat": 3, diff --git a/IPython/nbformat/tests/test4.ipynb b/IPython/nbformat/tests/test4.ipynb new file mode 100644 index 0000000..fa14dd2 --- /dev/null +++ b/IPython/nbformat/tests/test4.ipynb @@ -0,0 +1,308 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# nbconvert latex test" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Lorem ipsum** dolor sit amet, consectetur adipiscing elit. Nunc luctus bibendum felis dictum sodales. Ut suscipit, orci ut interdum imperdiet, purus ligula mollis *justo*, non malesuada nisl augue eget lorem. Donec bibendum, erat sit amet porttitor aliquam, urna lorem ornare libero, in vehicula diam diam ut ante. Nam non urna rhoncus, accumsan elit sit amet, mollis tellus. Vestibulum nec tellus metus. Vestibulum tempor, ligula et vehicula rhoncus, sapien turpis faucibus lorem, id dapibus turpis mauris ac orci. Sed volutpat vestibulum venenatis." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Printed Using Python" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello\n" + ] + } + ], + "source": [ + "print(\"hello\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Pyout" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "HTML\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.display import HTML\n", + "HTML(\"\"\"\n", + "\n", + "HTML\n", + "\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "console.log(\"hi\");" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%javascript\n", + "console.log(\"hi\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Image" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": [ + "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\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.display import Image\n", + "Image(\"http://ipython.org/_static/IPy_header.png\")" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/IPython/nbformat/tests/test4plus.ipynb b/IPython/nbformat/tests/test4plus.ipynb new file mode 100644 index 0000000..38859b5 --- /dev/null +++ b/IPython/nbformat/tests/test4plus.ipynb @@ -0,0 +1,349 @@ +{ + "extra": "future", + "cells": [ + { + "cell_type": "markdown", + "extra": 5, + "metadata": {}, + "source": [ + "# nbconvert latex test" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Lorem ipsum** dolor sit amet, consectetur adipiscing elit. Nunc luctus bibendum felis dictum sodales. Ut suscipit, orci ut interdum imperdiet, purus ligula mollis *justo*, non malesuada nisl augue eget lorem. Donec bibendum, erat sit amet porttitor aliquam, urna lorem ornare libero, in vehicula diam diam ut ante. Nam non urna rhoncus, accumsan elit sit amet, mollis tellus. Vestibulum nec tellus metus. Vestibulum tempor, ligula et vehicula rhoncus, sapien turpis faucibus lorem, id dapibus turpis mauris ac orci. Sed volutpat vestibulum venenatis." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Printed Using Python" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "future": "yes", + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "extra": "future", + "output_type": "stream", + "text": [ + "hello\n" + ] + } + ], + "source": [ + "print(\"hello\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Pyout" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "HTML\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.display import HTML\n", + "HTML(\"\"\"\n", + "\n", + "HTML\n", + "\"\"\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "console.log(\"hi\");" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "%%javascript\n", + "console.log(\"hi\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Image" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": [ + "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\n" + ], + "extra": "yes", + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "extra": "yes", + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.display import Image\n", + "Image(\"http://ipython.org/_static/IPy_header.png\")" + ] + }, + { + "cell_type": "future cell", + "metadata": {}, + "key": "value" + }, + { + "cell_type": "code", + "execution_count": 99, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello\n" + ] + }, + { + "output_type": "future output", + "some key": [ + "some data" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello again\n" + ] + } + ], + "source": [ + "future_output()" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 99 +} \ No newline at end of file diff --git a/IPython/nbformat/tests/test_api.py b/IPython/nbformat/tests/test_api.py new file mode 100644 index 0000000..1fb1585 --- /dev/null +++ b/IPython/nbformat/tests/test_api.py @@ -0,0 +1,37 @@ +"""Test the APIs at the top-level of nbformat""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import json + +from .base import TestsBase + +from ..reader import get_version +from IPython.nbformat import read, current_nbformat, writes + + +class TestAPI(TestsBase): + + def test_read(self): + """Can older notebooks be opened and automatically converted to the current + nbformat?""" + + # Open a version 2 notebook. + with self.fopen(u'test2.ipynb', 'r') as f: + nb = read(f, as_version=current_nbformat) + + # Check that the notebook was upgraded to the latest version automatically. + (major, minor) = get_version(nb) + self.assertEqual(major, current_nbformat) + + def test_write_downgrade_2(self): + """dowgrade a v3 notebook to v2""" + # Open a version 3 notebook. + with self.fopen(u'test3.ipynb', 'r') as f: + nb = read(f, as_version=3) + + jsons = writes(nb, version=2) + nb2 = json.loads(jsons) + (major, minor) = get_version(nb2) + self.assertEqual(major, 2) diff --git a/IPython/nbformat/tests/test_convert.py b/IPython/nbformat/tests/test_convert.py index 9eb45ad..06ca145 100644 --- a/IPython/nbformat/tests/test_convert.py +++ b/IPython/nbformat/tests/test_convert.py @@ -1,30 +1,18 @@ -""" -Contains tests class for convert.py -""" -#----------------------------------------------------------------------------- -# Copyright (C) 2013 The 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 -#----------------------------------------------------------------------------- +"""Tests for nbformat.convert""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. from .base import TestsBase -from ..convert import convert +from ..converter import convert from ..reader import read, get_version -from ..current import current_nbformat +from .. import current_nbformat -#----------------------------------------------------------------------------- -# Classes and functions -#----------------------------------------------------------------------------- class TestConvert(TestsBase): - def test_downgrade(self): + def test_downgrade_3_2(self): """Do notebook downgrades work?""" # Open a version 3 notebook and attempt to downgrade it to version 2. @@ -37,7 +25,7 @@ class TestConvert(TestsBase): self.assertEqual(major, 2) - def test_upgrade(self): + def test_upgrade_2_3(self): """Do notebook upgrades work?""" # Open a version 2 notebook and attempt to upgrade it to version 3. diff --git a/IPython/nbformat/tests/test_current.py b/IPython/nbformat/tests/test_current.py deleted file mode 100644 index 8fc99ba..0000000 --- a/IPython/nbformat/tests/test_current.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -Contains tests class for current.py -""" -#----------------------------------------------------------------------------- -# Copyright (C) 2013 The 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 .base import TestsBase - -from ..reader import get_version -from ..current import read, current_nbformat - -#----------------------------------------------------------------------------- -# Classes and functions -#----------------------------------------------------------------------------- - -class TestCurrent(TestsBase): - - def test_read(self): - """Can older notebooks be opened and automatically converted to the current - nbformat?""" - - # Open a version 2 notebook. - with self.fopen(u'test2.ipynb', u'r') as f: - nb = read(f, u'json') - - # Check that the notebook was upgraded to the latest version automatically. - (major, minor) = get_version(nb) - self.assertEqual(major, current_nbformat) diff --git a/IPython/nbformat/tests/test_sign.py b/IPython/nbformat/tests/test_sign.py index 82df8ae..2516343 100644 --- a/IPython/nbformat/tests/test_sign.py +++ b/IPython/nbformat/tests/test_sign.py @@ -1,24 +1,13 @@ """Test Notebook signing""" -#----------------------------------------------------------------------------- -# Copyright (C) 2014, The 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 -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. -from .. import sign from .base import TestsBase -from ..current import read +from IPython.nbformat import read, sign from IPython.core.getipython import get_ipython -#----------------------------------------------------------------------------- -# Classes and functions -#----------------------------------------------------------------------------- class TestNotary(TestsBase): @@ -28,7 +17,9 @@ class TestNotary(TestsBase): profile_dir=get_ipython().profile_dir ) with self.fopen(u'test3.ipynb', u'r') as f: - self.nb = read(f, u'json') + self.nb = read(f, as_version=4) + with self.fopen(u'test3.ipynb', u'r') as f: + self.nb3 = read(f, as_version=3) def test_algorithms(self): last_sig = '' @@ -80,41 +71,80 @@ class TestNotary(TestsBase): self.assertTrue(check_signature(nb)) def test_mark_cells_untrusted(self): - cells = self.nb.worksheets[0].cells + cells = self.nb.cells self.notary.mark_cells(self.nb, False) for cell in cells: + self.assertNotIn('trusted', cell) if cell.cell_type == 'code': - self.assertIn('trusted', cell) - self.assertFalse(cell.trusted) + self.assertIn('trusted', cell.metadata) + self.assertFalse(cell.metadata.trusted) else: - self.assertNotIn('trusted', cell) + self.assertNotIn('trusted', cell.metadata) def test_mark_cells_trusted(self): - cells = self.nb.worksheets[0].cells + cells = self.nb.cells self.notary.mark_cells(self.nb, True) for cell in cells: + self.assertNotIn('trusted', cell) if cell.cell_type == 'code': - self.assertIn('trusted', cell) - self.assertTrue(cell.trusted) + self.assertIn('trusted', cell.metadata) + self.assertTrue(cell.metadata.trusted) else: - self.assertNotIn('trusted', cell) + self.assertNotIn('trusted', cell.metadata) def test_check_cells(self): nb = self.nb self.notary.mark_cells(nb, True) self.assertTrue(self.notary.check_cells(nb)) - for cell in nb.worksheets[0].cells: + for cell in nb.cells: self.assertNotIn('trusted', cell) self.notary.mark_cells(nb, False) self.assertFalse(self.notary.check_cells(nb)) - for cell in nb.worksheets[0].cells: + for cell in nb.cells: self.assertNotIn('trusted', cell) def test_trust_no_output(self): nb = self.nb self.notary.mark_cells(nb, False) - for cell in nb.worksheets[0].cells: + for cell in nb.cells: if cell.cell_type == 'code': cell.outputs = [] self.assertTrue(self.notary.check_cells(nb)) + + def test_mark_cells_untrusted_v3(self): + nb = self.nb3 + cells = nb.worksheets[0].cells + self.notary.mark_cells(nb, False) + for cell in cells: + self.assertNotIn('trusted', cell) + if cell.cell_type == 'code': + self.assertIn('trusted', cell.metadata) + self.assertFalse(cell.metadata.trusted) + else: + self.assertNotIn('trusted', cell.metadata) + + def test_mark_cells_trusted_v3(self): + nb = self.nb3 + cells = nb.worksheets[0].cells + self.notary.mark_cells(nb, True) + for cell in cells: + self.assertNotIn('trusted', cell) + if cell.cell_type == 'code': + self.assertIn('trusted', cell.metadata) + self.assertTrue(cell.metadata.trusted) + else: + self.assertNotIn('trusted', cell.metadata) + + def test_check_cells_v3(self): + nb = self.nb3 + cells = nb.worksheets[0].cells + self.notary.mark_cells(nb, True) + self.assertTrue(self.notary.check_cells(nb)) + for cell in cells: + self.assertNotIn('trusted', cell) + self.notary.mark_cells(nb, False) + self.assertFalse(self.notary.check_cells(nb)) + for cell in cells: + self.assertNotIn('trusted', cell) + diff --git a/IPython/nbformat/tests/test_validator.py b/IPython/nbformat/tests/test_validator.py index 9706a96..a084282 100644 --- a/IPython/nbformat/tests/test_validator.py +++ b/IPython/nbformat/tests/test_validator.py @@ -1,73 +1,58 @@ -""" -Contains tests class for validator.py -""" -#----------------------------------------------------------------------------- -# Copyright (C) 2014 The 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. -#----------------------------------------------------------------------------- +"""Test nbformat.validator""" -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. import os from .base import TestsBase -from jsonschema import SchemaError -from ..current import read -from ..validator import schema_path, isvalid, validate, resolve_ref +from jsonschema import ValidationError +from IPython.nbformat import read +from ..validator import isvalid, validate -#----------------------------------------------------------------------------- -# Classes and functions -#----------------------------------------------------------------------------- - class TestValidator(TestsBase): - def test_schema_path(self): - """Test that the schema path exists""" - self.assertEqual(os.path.exists(schema_path), True) - def test_nb2(self): - """Test that a v2 notebook converted to v3 passes validation""" + """Test that a v2 notebook converted to current passes validation""" with self.fopen(u'test2.ipynb', u'r') as f: - nb = read(f, u'json') - self.assertEqual(validate(nb), []) + nb = read(f, as_version=4) + validate(nb) self.assertEqual(isvalid(nb), True) def test_nb3(self): """Test that a v3 notebook passes validation""" with self.fopen(u'test3.ipynb', u'r') as f: - nb = read(f, u'json') - self.assertEqual(validate(nb), []) + nb = read(f, as_version=4) + validate(nb) + self.assertEqual(isvalid(nb), True) + + def test_nb4(self): + """Test that a v4 notebook passes validation""" + with self.fopen(u'test4.ipynb', u'r') as f: + nb = read(f, as_version=4) + validate(nb) self.assertEqual(isvalid(nb), True) def test_invalid(self): """Test than an invalid notebook does not pass validation""" # this notebook has a few different errors: - # - the name is an integer, rather than a string # - one cell is missing its source - # - one cell has an invalid level + # - invalid cell type + # - invalid output_type with self.fopen(u'invalid.ipynb', u'r') as f: - nb = read(f, u'json') - self.assertEqual(len(validate(nb)), 3) + nb = read(f, as_version=4) + with self.assertRaises(ValidationError): + validate(nb) self.assertEqual(isvalid(nb), False) - def test_resolve_ref(self): - """Test that references are correctly resolved""" - # make sure it resolves the ref correctly - json = {"abc": "def", "ghi": {"$ref": "/abc"}} - resolved = resolve_ref(json) - self.assertEqual(resolved, {"abc": "def", "ghi": "def"}) - - # make sure it throws an error if the ref is not by itself - json = {"abc": "def", "ghi": {"$ref": "/abc", "foo": "bar"}} - with self.assertRaises(SchemaError): - resolved = resolve_ref(json) + def test_future(self): + """Test than a notebook from the future with extra keys passes validation""" + with self.fopen(u'test4plus.ipynb', u'r') as f: + nb = read(f, as_version=4) + with self.assertRaises(ValidationError): + validate(nb, version=4) + + self.assertEqual(isvalid(nb, version=4), False) + self.assertEqual(isvalid(nb), True) - # make sure it can handle json with no reference - json = {"abc": "def"} - resolved = resolve_ref(json) - self.assertEqual(resolved, json) diff --git a/IPython/nbformat/v2/__init__.py b/IPython/nbformat/v2/__init__.py index 04ab496..35fd430 100644 --- a/IPython/nbformat/v2/__init__.py +++ b/IPython/nbformat/v2/__init__.py @@ -42,6 +42,9 @@ from .convert import downgrade, upgrade # Code #----------------------------------------------------------------------------- +nbformat = 2 +nbformat_minor = 0 + def parse_filename(fname): """Parse a notebook filename. diff --git a/IPython/nbformat/v3/__init__.py b/IPython/nbformat/v3/__init__.py index 640ce2f..c93834b 100644 --- a/IPython/nbformat/v3/__init__.py +++ b/IPython/nbformat/v3/__init__.py @@ -1,20 +1,16 @@ """The main API for the v3 notebook format. - -Authors: - -* Brian Granger """ -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The 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. -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- +__all__ = ['NotebookNode', 'new_code_cell', 'new_text_cell', 'new_notebook', + 'new_output', 'new_worksheet', 'new_metadata', 'new_author', + 'new_heading_cell', 'nbformat', 'nbformat_minor', 'nbformat_schema', + 'reads_json', 'writes_json', 'read_json', 'write_json', + 'to_notebook_json', 'reads_py', 'writes_py', 'read_py', 'write_py', + 'to_notebook_py', 'downgrade', 'upgrade', 'parse_filename' + ] import os @@ -35,9 +31,6 @@ from .nbpy import to_notebook as to_notebook_py from .convert import downgrade, upgrade -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- def parse_filename(fname): """Parse a notebook filename. diff --git a/IPython/nbformat/v3/convert.py b/IPython/nbformat/v3/convert.py index 377a2e2..c2857bb 100644 --- a/IPython/nbformat/v3/convert.py +++ b/IPython/nbformat/v3/convert.py @@ -1,22 +1,7 @@ -"""Code for converting notebooks to and from the v2 format. +"""Code for converting notebooks to and from the v2 format.""" -Authors: - -* Brian Granger -* Min RK -* Jonathan Frederic -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The 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 -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. from .nbbase import ( new_code_cell, new_text_cell, new_worksheet, new_notebook, new_output, @@ -25,9 +10,21 @@ from .nbbase import ( from IPython.nbformat import v2 -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- +def _unbytes(obj): + """There should be no bytes objects in a notebook + + v2 stores png/jpeg as b64 ascii bytes + """ + if isinstance(obj, dict): + for k,v in obj.items(): + obj[k] = _unbytes(v) + elif isinstance(obj, list): + for i,v in enumerate(obj): + obj[i] = _unbytes(v) + elif isinstance(obj, bytes): + # only valid bytes are b64-encoded ascii + obj = obj.decode('ascii') + return obj def upgrade(nb, from_version=2, from_minor=0): """Convert a notebook to v3. @@ -47,6 +44,10 @@ def upgrade(nb, from_version=2, from_minor=0): nb.nbformat_minor = nbformat_minor nb.orig_nbformat = 2 + nb = _unbytes(nb) + for ws in nb['worksheets']: + for cell in ws['cells']: + cell.setdefault('metadata', {}) return nb elif from_version == 3: if from_minor != nbformat_minor: diff --git a/IPython/nbformat/v3/nbbase.py b/IPython/nbformat/v3/nbbase.py index f27572d..3a48ab8 100644 --- a/IPython/nbformat/v3/nbbase.py +++ b/IPython/nbformat/v3/nbbase.py @@ -22,7 +22,7 @@ from IPython.utils.py3compat import cast_unicode, unicode_type # Change this when incrementing the nbformat version nbformat = 3 nbformat_minor = 0 -nbformat_schema = 'v3.withref.json' +nbformat_schema = 'nbformat.v3.schema.json' class NotebookNode(Struct): pass @@ -53,7 +53,10 @@ def new_output(output_type, output_text=None, output_png=None, metadata = {} if not isinstance(metadata, dict): raise TypeError("metadata must be dict") - output.metadata = metadata + + + if output_type in {u'pyout', 'display_data'}: + output.metadata = metadata if output_type != 'pyerr': if output_text is not None: @@ -121,21 +124,17 @@ def new_text_cell(cell_type, source=None, rendered=None, metadata=None): cell_type = 'raw' if source is not None: cell.source = cast_unicode(source) - if rendered is not None: - cell.rendered = cast_unicode(rendered) cell.metadata = NotebookNode(metadata or {}) cell.cell_type = cell_type return cell -def new_heading_cell(source=None, rendered=None, level=1, metadata=None): +def new_heading_cell(source=None, level=1, rendered=None, metadata=None): """Create a new section cell with a given integer level.""" cell = NotebookNode() cell.cell_type = u'heading' if source is not None: cell.source = cast_unicode(source) - if rendered is not None: - cell.rendered = cast_unicode(rendered) cell.level = int(level) cell.metadata = NotebookNode(metadata or {}) return cell @@ -144,8 +143,6 @@ def new_heading_cell(source=None, rendered=None, level=1, metadata=None): def new_worksheet(name=None, cells=None, metadata=None): """Create a worksheet by name with with a list of cells.""" ws = NotebookNode() - if name is not None: - ws.name = cast_unicode(name) if cells is None: ws.cells = [] else: diff --git a/IPython/nbformat/v4/v4.withref.json b/IPython/nbformat/v3/nbformat.v3.schema.json similarity index 68% rename from IPython/nbformat/v4/v4.withref.json rename to IPython/nbformat/v3/nbformat.v3.schema.json index 4a114f9..8175853 100644 --- a/IPython/nbformat/v4/v4.withref.json +++ b/IPython/nbformat/v3/nbformat.v3.schema.json @@ -1,9 +1,9 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "description": "IPython Notebook v4.0 JSON schema.", + "description": "IPython Notebook v3.0 JSON schema.", "type": "object", "additionalProperties": false, - "required": ["metadata", "nbformat_minor", "nbformat", "cells"], + "required": ["metadata", "nbformat_minor", "nbformat", "worksheets"], "properties": { "metadata": { "description": "Notebook root-level metadata.", @@ -32,11 +32,6 @@ "signature": { "description": "Hash of the notebook.", "type": "string" - }, - "orig_nbformat": { - "description": "Original notebook format (major number) before converting the notebook between versions.", - "type": "integer", - "minimum": 1 } } }, @@ -48,31 +43,55 @@ "nbformat": { "description": "Notebook format (major number). Incremented between backwards incompatible changes to the notebook format.", "type": "integer", - "minimum": 4, - "maximum": 4 + "minimum": 3, + "maximum": 3 + }, + "orig_nbformat": { + "description": "Original notebook format (major number) before converting the notebook between versions.", + "type": "integer", + "minimum": 1 + }, + "orig_nbformat_minor": { + "description": "Original notebook format (minor number) before converting the notebook between versions.", + "type": "integer", + "minimum": 0 }, - "cells": { - "description": "Array of cells of the current notebook.", + "worksheets" : { + "description": "Array of worksheets", "type": "array", - "items": { - "type": "object", - "oneOf": [ - {"$ref": "/definitions/raw_cell"}, - {"$ref": "/definitions/markdown_cell"}, - {"$ref": "/definitions/heading_cell"}, - {"$ref": "/definitions/code_cell"} - ] - } + "items": {"$ref": "#/definitions/worksheet"} } }, "definitions": { - + "worksheet": { + "additionalProperties": false, + "required" : ["cells"], + "properties":{ + "cells": { + "description": "Array of cells of the current notebook.", + "type": "array", + "items": { + "type": "object", + "oneOf": [ + {"$ref": "#/definitions/raw_cell"}, + {"$ref": "#/definitions/markdown_cell"}, + {"$ref": "#/definitions/heading_cell"}, + {"$ref": "#/definitions/code_cell"} + ] + } + }, + "metadata": { + "type": "object", + "description": "metadata of the current worksheet" + } + } + }, "raw_cell": { "description": "Notebook raw nbconvert cell.", "type": "object", "additionalProperties": false, - "required": ["cell_type", "metadata"], + "required": ["cell_type", "source"], "properties": { "cell_type": { "description": "String identifying the type of cell.", @@ -87,11 +106,11 @@ "description": "Raw cell metadata format for nbconvert.", "type": "string" }, - "name": {"$ref": "/definitions/misc/metadata_name"}, - "tags": {"$ref": "/definitions/misc/metadata_tags"} + "name": {"$ref": "#/definitions/misc/metadata_name"}, + "tags": {"$ref": "#/definitions/misc/metadata_tags"} } }, - "source": {"$ref": "/definitions/misc/source"} + "source": {"$ref": "#/definitions/misc/source"} } }, @@ -99,22 +118,22 @@ "description": "Notebook markdown cell.", "type": "object", "additionalProperties": false, - "required": ["cell_type", "metadata"], + "required": ["cell_type", "source"], "properties": { "cell_type": { "description": "String identifying the type of cell.", - "enum": ["markdown"] + "enum": ["markdown", "html"] }, "metadata": { "description": "Cell-level metadata.", "type": "object", "properties": { - "name": {"$ref": "/definitions/misc/metadata_name"}, - "tags": {"$ref": "/definitions/misc/metadata_tags"} + "name": {"$ref": "#/definitions/misc/metadata_name"}, + "tags": {"$ref": "#/definitions/misc/metadata_tags"} }, "additionalProperties": true }, - "source": {"$ref": "/definitions/misc/source"} + "source": {"$ref": "#/definitions/misc/source"} } }, @@ -122,7 +141,7 @@ "description": "Notebook heading cell.", "type": "object", "additionalProperties": false, - "required": ["cell_type", "metadata", "source", "level"], + "required": ["cell_type", "source", "level"], "properties": { "cell_type": { "description": "String identifying the type of cell.", @@ -131,18 +150,13 @@ "metadata": { "description": "Cell-level metadata.", "type": "object", - "properties": { - "name": {"$ref": "/definitions/misc/metadata_name"}, - "tags": {"$ref": "/definitions/misc/metadata_tags"} - }, "additionalProperties": true }, - "source": {"$ref": "/definitions/misc/source"}, + "source": {"$ref": "#/definitions/misc/source"}, "level": { "description": "Level of heading cells.", "type": "integer", - "minimum": 1, - "maximum": 6 + "minimum": 1 } } }, @@ -151,42 +165,30 @@ "description": "Notebook code cell.", "type": "object", "additionalProperties": false, - "required": ["cell_type", "metadata", "source", "outputs", "prompt_number"], + "required": ["cell_type", "input", "outputs", "language"], "properties": { "cell_type": { "description": "String identifying the type of cell.", "enum": ["code"] }, + "language": { + "description": "The cell's language (always Python)", + "type": "string" + }, + "collapsed": { + "description": "Whether the cell is collapsed/expanded.", + "type": "boolean" + }, "metadata": { "description": "Cell-level metadata.", "type": "object", - "additionalProperties": true, - "properties": { - "collapsed": { - "description": "Whether the cell is collapsed/expanded.", - "type": "boolean" - }, - "autoscroll": { - "description": "Whether the cell's output is scrolled, unscrolled, or autoscrolled.", - "enum": [true, false, "auto"] - }, - "name": {"$ref": "/definitions/misc/metadata_name"}, - "tags": {"$ref": "/definitions/misc/metadata_tags"} - } + "additionalProperties": true }, - "source": {"$ref": "/definitions/misc/source"}, + "input": {"$ref": "#/definitions/misc/source"}, "outputs": { "description": "Execution, display, or stream outputs.", "type": "array", - "items": { - "type": "object", - "oneOf": [ - {"$ref": "/definitions/execute_result"}, - {"$ref": "/definitions/display_data_output"}, - {"$ref": "/definitions/stream_output"}, - {"$ref": "/definitions/error_output"} - ] - } + "items": {"$ref": "#/definitions/output"} }, "prompt_number": { "description": "The code cell's prompt number. Will be null if the cell has not been run.", @@ -195,71 +197,109 @@ } } }, - - "execute_result": { + "output": { + "type": "object", + "oneOf": [ + {"$ref": "#/definitions/pyout"}, + {"$ref": "#/definitions/display_data"}, + {"$ref": "#/definitions/stream"}, + {"$ref": "#/definitions/pyerr"} + ] + }, + "pyout": { "description": "Result of executing a code cell.", "type": "object", "additionalProperties": false, - "required": ["output_type", "metadata", "prompt_number"], + "required": ["output_type", "prompt_number"], "properties": { "output_type": { "description": "Type of cell output.", - "enum": ["execute_result"] + "enum": ["pyout"] }, - "metadata": {"$ref": "/definitions/misc/output_metadata"}, - "prompt_number": {"$ref": "/definitions/misc/prompt_number"} + "prompt_number": { + "description": "A result's prompt number.", + "type": ["integer"], + "minimum": 0 + }, + "text": {"$ref": "#/definitions/misc/multiline_string"}, + "latex": {"$ref": "#/definitions/misc/multiline_string"}, + "png": {"$ref": "#/definitions/misc/multiline_string"}, + "jpeg": {"$ref": "#/definitions/misc/multiline_string"}, + "svg": {"$ref": "#/definitions/misc/multiline_string"}, + "html": {"$ref": "#/definitions/misc/multiline_string"}, + "javascript": {"$ref": "#/definitions/misc/multiline_string"}, + "json": {"$ref": "#/definitions/misc/multiline_string"}, + "pdf": {"$ref": "#/definitions/misc/multiline_string"}, + "metadata": {"$ref": "#/definitions/misc/output_metadata"} }, - "patternProperties": {"$ref": "/definitions/misc/mimetype"} + "patternProperties": { + "^[a-zA-Z0-9]+/[a-zA-Z0-9\\-\\+\\.]+$": { + "description": "mimetype output (e.g. text/plain), represented as either an array of strings or a string.", + "$ref": "#/definitions/misc/multiline_string" + } + } }, - "display_data_output": { + "display_data": { "description": "Data displayed as a result of code cell execution.", "type": "object", "additionalProperties": false, - "required": ["output_type", "metadata"], + "required": ["output_type"], "properties": { "output_type": { "description": "Type of cell output.", "enum": ["display_data"] }, - "metadata": {"$ref": "/definitions/misc/output_metadata"} + "text": {"$ref": "#/definitions/misc/multiline_string"}, + "latex": {"$ref": "#/definitions/misc/multiline_string"}, + "png": {"$ref": "#/definitions/misc/multiline_string"}, + "jpeg": {"$ref": "#/definitions/misc/multiline_string"}, + "svg": {"$ref": "#/definitions/misc/multiline_string"}, + "html": {"$ref": "#/definitions/misc/multiline_string"}, + "javascript": {"$ref": "#/definitions/misc/multiline_string"}, + "json": {"$ref": "#/definitions/misc/multiline_string"}, + "pdf": {"$ref": "#/definitions/misc/multiline_string"}, + "metadata": {"$ref": "#/definitions/misc/output_metadata"} }, - "patternProperties": {"$ref": "/definitions/misc/mimetype"} + "patternProperties": { + "[a-zA-Z0-9]+/[a-zA-Z0-9\\-\\+\\.]+$": { + "description": "mimetype output (e.g. text/plain), represented as either an array of strings or a string.", + "$ref": "#/definitions/misc/multiline_string" + } + } }, - "stream_output": { + "stream": { "description": "Stream output from a code cell.", "type": "object", "additionalProperties": false, - "required": ["output_type", "metadata", "stream"], + "required": ["output_type", "stream", "text"], "properties": { "output_type": { "description": "Type of cell output.", "enum": ["stream"] }, - "metadata": {"$ref": "/definitions/misc/output_metadata"}, "stream": { "description": "The stream type/destination.", "type": "string" }, - "text/plain": { + "text": { "description": "The stream's text output, represented as an array of strings.", - "oneOf": {"$ref": "/definitions/misc/multiline_string"} + "$ref": "#/definitions/misc/multiline_string" } } }, - "error_output": { + "pyerr": { "description": "Output of an error that occurred during code cell execution.", "type": "object", "additionalProperties": false, - "required": ["output_type", "metadata", "ename", "evalue", "traceback"], + "required": ["output_type", "ename", "evalue", "traceback"], "properties": { "output_type": { "description": "Type of cell output.", - "enum": ["error"] + "enum": ["pyerr"] }, - "metadata": {"$ref": "/definitions/misc/output_metadata"}, "ename": { "description": "The name of the error.", "type": "string" @@ -293,7 +333,7 @@ }, "source": { "description": "Contents of the cell, represented as an array of lines.", - "oneOf": {"$ref": "/definitions/misc/multiline_string"} + "$ref": "#/definitions/misc/multiline_string" }, "prompt_number": { "description": "The code cell's prompt number. Will be null if the cell has not been run.", @@ -301,9 +341,11 @@ "minimum": 0 }, "mimetype": { - "^[a-zA-Z0-9+-]+/[a-zA-Z0-9+-]+$": { - "description": "The cell's mimetype output (e.g. text/plain), represented as either an array of strings or a string.", - "oneOf": {"$ref": "/definitions/misc/multiline_string"} + "patternProperties": { + "^[a-zA-Z0-9\\-\\+]+/[a-zA-Z0-9\\-\\+]+": { + "description": "The cell's mimetype output (e.g. text/plain), represented as either an array of strings or a string.", + "$ref": "#/definitions/misc/multiline_string" + } } }, "output_metadata": { @@ -311,13 +353,15 @@ "type": "object", "additionalProperties": true }, - "multiline_string": [ - {"type": "string"}, - { - "type": "array", - "items": {"type": "string"} - } - ] + "multiline_string": { + "oneOf" : [ + {"type": "string"}, + { + "type": "array", + "items": {"type": "string"} + } + ] + } } } } diff --git a/IPython/nbformat/v3/nbjson.py b/IPython/nbformat/v3/nbjson.py index be9ee28..d63cbba 100644 --- a/IPython/nbformat/v3/nbjson.py +++ b/IPython/nbformat/v3/nbjson.py @@ -1,34 +1,19 @@ -"""Read and write notebooks in JSON format. +"""Read and write notebooks in JSON format.""" -Authors: - -* Brian Granger -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The 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 -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. import copy import json from .nbbase import from_dict from .rwbase import ( - NotebookReader, NotebookWriter, restore_bytes, rejoin_lines, split_lines + NotebookReader, NotebookWriter, restore_bytes, rejoin_lines, split_lines, + strip_transient, ) from IPython.utils import py3compat -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- class BytesEncoder(json.JSONEncoder): """A JSON encoder that accepts b64 (and other *ascii*) bytestrings.""" @@ -43,6 +28,7 @@ class JSONReader(NotebookReader): def reads(self, s, **kwargs): nb = json.loads(s, **kwargs) nb = self.to_notebook(nb, **kwargs) + nb = strip_transient(nb) return nb def to_notebook(self, d, **kwargs): @@ -56,8 +42,10 @@ class JSONWriter(NotebookWriter): kwargs['indent'] = 1 kwargs['sort_keys'] = True kwargs['separators'] = (',',': ') + nb = copy.deepcopy(nb) + nb = strip_transient(nb) if kwargs.pop('split_lines', True): - nb = split_lines(copy.deepcopy(nb)) + nb = split_lines(nb) return py3compat.str_to_unicode(json.dumps(nb, **kwargs), 'utf-8') diff --git a/IPython/nbformat/v3/rwbase.py b/IPython/nbformat/v3/rwbase.py index e2ac3df..8b0635b 100644 --- a/IPython/nbformat/v3/rwbase.py +++ b/IPython/nbformat/v3/rwbase.py @@ -1,30 +1,13 @@ -"""Base classes and utilities for readers and writers. +"""Base classes and utilities for readers and writers.""" -Authors: - -* Brian Granger -""" - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The 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 -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. from base64 import encodestring, decodestring -import pprint from IPython.utils import py3compat from IPython.utils.py3compat import str_to_bytes, unicode_type, string_types -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- def restore_bytes(nb): """Restore bytes of image data from unicode-only formats. @@ -157,6 +140,22 @@ def base64_encode(nb): return nb +def strip_transient(nb): + """Strip transient values that shouldn't be stored in files. + + This should be called in *both* read and write. + """ + nb.pop('orig_nbformat', None) + nb.pop('orig_nbformat_minor', None) + for ws in nb['worksheets']: + for cell in ws['cells']: + cell.get('metadata', {}).pop('trusted', None) + # strip cell.trusted even though it shouldn't be used, + # since it's where the transient value used to be stored. + cell.pop('trusted', None) + return nb + + class NotebookReader(object): """A class for reading notebooks.""" diff --git a/IPython/nbformat/v3/tests/nbexamples.py b/IPython/nbformat/v3/tests/nbexamples.py index 713957d..898a032 100644 --- a/IPython/nbformat/v3/tests/nbexamples.py +++ b/IPython/nbformat/v3/tests/nbexamples.py @@ -13,12 +13,11 @@ from ..nbbase import ( png = encodestring(os.urandom(5)).decode('ascii') jpeg = encodestring(os.urandom(6)).decode('ascii') -ws = new_worksheet(name='worksheet1') +ws = new_worksheet() ws.cells.append(new_text_cell( u'html', source='Some NumPy Examples', - rendered='Some NumPy Examples' )) @@ -31,7 +30,6 @@ ws.cells.append(new_code_cell( ws.cells.append(new_text_cell( u'markdown', source='A random array', - rendered='A random array' )) ws.cells.append(new_text_cell( @@ -70,7 +68,7 @@ ws.cells.append(new_code_cell( output_png=png, output_jpeg=jpeg, output_svg=u'', - output_json=u'json data', + output_json=u'{"json": "data"}', output_javascript=u'var i=0;', prompt_number=3 ),new_output( @@ -81,7 +79,7 @@ ws.cells.append(new_code_cell( output_png=png, output_jpeg=jpeg, output_svg=u'', - output_json=u'json data', + output_json=u'{"json": "data"}', output_javascript=u'var i=0;' ),new_output( output_type=u'pyerr', @@ -104,7 +102,7 @@ md = new_metadata(name=u'My Notebook',license=u'BSD',created=u'8601_goes_here', modified=u'8601_goes_here',gistid=u'21341231',authors=authors) nb0 = new_notebook( - worksheets=[ws, new_worksheet(name='worksheet2')], + worksheets=[ws, new_worksheet()], metadata=md ) diff --git a/IPython/nbformat/v3/tests/test_json.py b/IPython/nbformat/v3/tests/test_json.py index cfbc698..cd8c76e 100644 --- a/IPython/nbformat/v3/tests/test_json.py +++ b/IPython/nbformat/v3/tests/test_json.py @@ -1,9 +1,11 @@ -import pprint +import copy +import json from base64 import decodestring from unittest import TestCase from IPython.utils.py3compat import unicode_type from ..nbjson import reads, writes +from ..nbbase import from_dict from .. import nbjson from .nbexamples import nb0 @@ -31,6 +33,35 @@ class TestJSON(formattest.NBFormatTest, TestCase): s = writes(nb0, split_lines=True) self.assertEqual(nbjson.reads(s),nb0) + def test_strip_transient(self): + """transient values aren't written to files""" + nb = copy.deepcopy(nb0) + nb.orig_nbformat = 2 + nb.orig_nbformat_minor = 3 + nb.worksheets[0].cells[0].metadata.trusted = False + nbs = nbjson.writes(nb) + + nb2 = from_dict(json.loads(nbs)) + self.assertNotIn('orig_nbformat', nb2) + self.assertNotIn('orig_nbformat_minor', nb2) + for cell in nb2.worksheets[0].cells: + self.assertNotIn('trusted', cell.metadata) + + def test_to_json(self): + """to_notebook_json doesn't strip transient""" + nb = copy.deepcopy(nb0) + nb.orig_nbformat = 2 + nb.orig_nbformat_minor = 3 + nb.worksheets[0].cells[0].metadata.trusted = False + nbs = json.dumps(nb) + nb2 = nbjson.to_notebook(json.loads(nbs)) + + nb2 = from_dict(json.loads(nbs)) + self.assertIn('orig_nbformat', nb2) + self.assertIn('orig_nbformat_minor', nb2) + cell = nb2.worksheets[0].cells[0] + self.assertIn('trusted', cell.metadata) + def test_read_png(self): """PNG output data is b64 unicode""" s = writes(nb0) diff --git a/IPython/nbformat/v3/tests/test_nbbase.py b/IPython/nbformat/v3/tests/test_nbbase.py index 351fe5d..0526657 100644 --- a/IPython/nbformat/v3/tests/test_nbbase.py +++ b/IPython/nbformat/v3/tests/test_nbbase.py @@ -41,45 +41,37 @@ class TestCell(TestCase): tc = new_text_cell(u'html') self.assertEqual(tc.cell_type, u'html') self.assertEqual(u'source' not in tc, True) - self.assertEqual(u'rendered' not in tc, True) def test_html_cell(self): - tc = new_text_cell(u'html', 'hi', 'hi') + tc = new_text_cell(u'html', 'hi') self.assertEqual(tc.source, u'hi') - self.assertEqual(tc.rendered, u'hi') def test_empty_markdown_cell(self): tc = new_text_cell(u'markdown') self.assertEqual(tc.cell_type, u'markdown') self.assertEqual(u'source' not in tc, True) - self.assertEqual(u'rendered' not in tc, True) def test_markdown_cell(self): - tc = new_text_cell(u'markdown', 'hi', 'hi') + tc = new_text_cell(u'markdown', 'hi') self.assertEqual(tc.source, u'hi') - self.assertEqual(tc.rendered, u'hi') def test_empty_raw_cell(self): tc = new_text_cell(u'raw') self.assertEqual(tc.cell_type, u'raw') self.assertEqual(u'source' not in tc, True) - self.assertEqual(u'rendered' not in tc, True) def test_raw_cell(self): - tc = new_text_cell(u'raw', 'hi', 'hi') + tc = new_text_cell(u'raw', 'hi') self.assertEqual(tc.source, u'hi') - self.assertEqual(tc.rendered, u'hi') def test_empty_heading_cell(self): tc = new_heading_cell() self.assertEqual(tc.cell_type, u'heading') self.assertEqual(u'source' not in tc, True) - self.assertEqual(u'rendered' not in tc, True) def test_heading_cell(self): - tc = new_heading_cell(u'hi', u'hi', level=2) + tc = new_heading_cell(u'hi', level=2) self.assertEqual(tc.source, u'hi') - self.assertEqual(tc.rendered, u'hi') self.assertEqual(tc.level, 2) @@ -92,9 +84,8 @@ class TestWorksheet(TestCase): def test_worksheet(self): cells = [new_code_cell(), new_text_cell(u'html')] - ws = new_worksheet(cells=cells,name=u'foo') + ws = new_worksheet(cells=cells) self.assertEqual(ws.cells,cells) - self.assertEqual(ws.name,u'foo') class TestNotebook(TestCase): diff --git a/IPython/nbformat/v3/v3.withref.json b/IPython/nbformat/v3/v3.withref.json deleted file mode 100644 index 9a711a2..0000000 --- a/IPython/nbformat/v3/v3.withref.json +++ /dev/null @@ -1,178 +0,0 @@ -{ - "description": "custom json structure with references to generate notebook schema", - "notebook":{ - "type": "object", - "description": "notebook v3.0 root schema", - "$schema": "http://json-schema.org/draft-03/schema", - "id": "#notebook", - "required": true, - "additionalProperties": false, - "properties":{ - "metadata": { - "type": "object", - "id": "metadata", - "required": true, - "description": "the metadata atribute can contain any additionnal information", - "additionalProperties": true, - "properties":{ - "name": { - "id": "name", - "description": "the title of the notebook", - "type": "string", - "id": "name", - "required": true - } - } - }, - "nbformat_minor": { - "description": "Notebook format, minor number. Incremented for slight variation of notebook format.", - "type": "integer", - "minimum": 0, - "id": "nbformat_minor", - "required": true - }, - "nbformat": { - "description": "Notebook format, major number. Incremented between backward incompatible change is introduced.", - "type": "integer", - "minimum": 3, - "id": "nbformat", - "required": true - }, - "orig_nbformat": { - "description": "Original notebook format, major number.", - "type": "integer", - "minimum": 1, - "id": "orig_nbformat", - "required": false - }, - "worksheets": { - "description": "Array of worksheet, not used by the current implementation of ipython yet", - "type": "array", - "id": "worksheets", - "required": true, - "items": {"$ref": "/worksheet"} - } - } - }, - - "worksheet": { - "additionalProperties": false, - "properties":{ - "cells": { - "type": "array", - "$schema": "http://json-schema.org/draft-03/schema", - "description": "array of cells of the current worksheet", - "id": "#cells", - "required": true, - "items": {"$ref": "/any_cell"} - - }, - "metadata": { - "type": "object", - "description": "metadata of the current worksheet", - "id": "metadata", - "required": false - } - } - }, - - "text_cell": { - "type": "object", - "description": "scheme for text cel and childrenm (level only optionnal argument for HEader cell)", - "$schema": "http://json-schema.org/draft-03/schema", - "id": "#cell", - "required": true, - "additionalProperties": false, - "properties":{ - "cell_type": { - "type": "string", - "id": "cell_type", - "required": true - }, - "level": { - "type": "integer", - "minimum": 1, - "maximum": 6, - "id": "level", - "required": false - }, - "metadata": { - "type": "object", - "id": "metadata", - "required": false - }, - "source": { - "description": "for code cell, the source code", - "type": ["array", "string"], - "id": "source", - "required": true, - "items": - { - "type": "string", - "description": "each item represent one line of the source code written, terminated by \n", - "id": "0", - "required": true - } - } - } - - }, - - "any_cell": { - "description": "Meta cell type that match any cell type", - "type": [{"$ref": "/text_cell"},{"$ref":"/code_cell"}], - "$schema": "http://json-schema.org/draft-03/schema" - }, - - "code_cell":{ - "type": "object", - "$schema": "http://json-schema.org/draft-03/schema", - "description": "Cell used to execute code", - "id": "#cell", - "required": true, - "additionalProperties": false, - "properties":{ - "cell_type": { - "type": "string", - "id": "cell_type", - "required": true - }, - "metadata": { - "type": "object", - "id": "metadata", - "required": false - }, - "collapsed": { - "type": "boolean", - "required": true - }, - "input": { - "description": "user input for text cells", - "type": ["array", "string"], - "id": "input", - "required": true, - "items": - { - "type": "string", - "id": "input", - "required": true - } - }, - "outputs": { - "description": "output for code cell, to be definied", - "required": true, - "type": "array" - }, - "prompt_number": { - "type": ["integer","null"], - "required": false, - "minimum": 0 - }, - "language": { - "type": "string", - "required": true - } - } - - } -} diff --git a/IPython/nbformat/v4/__init__.py b/IPython/nbformat/v4/__init__.py new file mode 100644 index 0000000..03a517e --- /dev/null +++ b/IPython/nbformat/v4/__init__.py @@ -0,0 +1,23 @@ +"""The main API for the v4 notebook format.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +__all__ = ['nbformat', 'nbformat_minor', 'nbformat_schema', 'new_code_cell', + 'new_markdown_cell', 'new_notebook', 'new_output', 'output_from_msg', + 'reads', 'writes', 'to_notebook', 'downgrade', 'upgrade'] + +from .nbbase import ( + nbformat, nbformat_minor, nbformat_schema, + new_code_cell, new_markdown_cell, new_notebook, + new_output, output_from_msg, +) + +from .nbjson import reads, writes, to_notebook +reads_json = reads +writes_json = writes +to_notebook_json = to_notebook + +from .convert import downgrade, upgrade + + diff --git a/IPython/nbformat/v4/convert.py b/IPython/nbformat/v4/convert.py new file mode 100644 index 0000000..c559bdf --- /dev/null +++ b/IPython/nbformat/v4/convert.py @@ -0,0 +1,252 @@ +"""Code for converting notebooks to and from v3.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import json +import re + +from .nbbase import ( + nbformat, nbformat_minor, + NotebookNode, +) + +from IPython.nbformat import v3 +from IPython.utils.log import get_logger + +def _warn_if_invalid(nb, version): + """Log validation errors, if there are any.""" + from IPython.nbformat import validate, ValidationError + try: + validate(nb, version=version) + except ValidationError as e: + get_logger().error("Notebook JSON is not valid v%i: %s", version, e) + +def upgrade(nb, from_version=3, from_minor=0): + """Convert a notebook to v4. + + Parameters + ---------- + nb : NotebookNode + The Python representation of the notebook to convert. + from_version : int + The original version of the notebook to convert. + from_minor : int + The original minor version of the notebook to convert (only relevant for v >= 3). + """ + if from_version == 3: + # Validate the notebook before conversion + _warn_if_invalid(nb, from_version) + + # Mark the original nbformat so consumers know it has been converted + orig_nbformat = nb.pop('orig_nbformat', None) + nb.metadata.orig_nbformat = orig_nbformat or 3 + + # Mark the new format + nb.nbformat = nbformat + nb.nbformat_minor = nbformat_minor + + # remove worksheet(s) + nb['cells'] = cells = [] + # In the unlikely event of multiple worksheets, + # they will be flattened + for ws in nb.pop('worksheets', []): + # upgrade each cell + for cell in ws['cells']: + cells.append(upgrade_cell(cell)) + # upgrade metadata + nb.metadata.pop('name', '') + # Validate the converted notebook before returning it + _warn_if_invalid(nb, nbformat) + return nb + elif from_version == 4: + # nothing to do + if from_minor != nbformat_minor: + nb.metadata.orig_nbformat_minor = from_minor + nb.nbformat_minor = nbformat_minor + + return nb + else: + raise ValueError('Cannot convert a notebook directly from v%s to v4. ' \ + 'Try using the IPython.nbformat.convert module.' % from_version) + +def upgrade_cell(cell): + """upgrade a cell from v3 to v4 + + heading cell: + - -> markdown heading + code cell: + - remove language metadata + - cell.input -> cell.source + - cell.prompt_number -> cell.execution_count + - update outputs + """ + cell.setdefault('metadata', NotebookNode()) + if cell.cell_type == 'code': + cell.pop('language', '') + if 'collapsed' in cell: + cell.metadata['collapsed'] = cell.pop('collapsed') + cell.source = cell.pop('input', '') + cell.execution_count = cell.pop('prompt_number', None) + cell.outputs = upgrade_outputs(cell.outputs) + elif cell.cell_type == 'heading': + cell.cell_type = 'markdown' + level = cell.pop('level', 1) + cell.source = u'{hashes} {single_line}'.format( + hashes='#' * level, + single_line = ' '.join(cell.get('source', '').splitlines()), + ) + elif cell.cell_type == 'html': + # Technically, this exists. It will never happen in practice. + cell.cell_type = 'markdown' + return cell + +def downgrade_cell(cell): + """downgrade a cell from v4 to v3 + + code cell: + - set cell.language + - cell.input <- cell.source + - cell.prompt_number <- cell.execution_count + - update outputs + markdown cell: + - single-line heading -> heading cell + """ + if cell.cell_type == 'code': + cell.language = 'python' + cell.input = cell.pop('source', '') + cell.prompt_number = cell.pop('execution_count', None) + cell.collapsed = cell.metadata.pop('collapsed', False) + cell.outputs = downgrade_outputs(cell.outputs) + elif cell.cell_type == 'markdown': + source = cell.get('source', '') + if '\n' not in source and source.startswith('#'): + prefix, text = re.match(r'(#+)\s*(.*)', source).groups() + cell.cell_type = 'heading' + cell.source = text + cell.level = len(prefix) + return cell + +_mime_map = { + "text" : "text/plain", + "html" : "text/html", + "svg" : "image/svg+xml", + "png" : "image/png", + "jpeg" : "image/jpeg", + "latex" : "text/latex", + "json" : "application/json", + "javascript" : "application/javascript", +}; + +def to_mime_key(d): + """convert dict with v3 aliases to plain mime-type keys""" + for alias, mime in _mime_map.items(): + if alias in d: + d[mime] = d.pop(alias) + return d + +def from_mime_key(d): + """convert dict with mime-type keys to v3 aliases""" + for alias, mime in _mime_map.items(): + if mime in d: + d[alias] = d.pop(mime) + return d + +def upgrade_output(output): + """upgrade a single code cell output from v3 to v4 + + - pyout -> execute_result + - pyerr -> error + - output.type -> output.data.mime/type + - mime-type keys + - stream.stream -> stream.name + """ + if output['output_type'] in {'pyout', 'display_data'}: + output.setdefault('metadata', NotebookNode()) + if output['output_type'] == 'pyout': + output['output_type'] = 'execute_result' + output['execution_count'] = output.pop('prompt_number', None) + + # move output data into data sub-dict + data = {} + for key in list(output): + if key in {'output_type', 'execution_count', 'metadata'}: + continue + data[key] = output.pop(key) + to_mime_key(data) + output['data'] = data + to_mime_key(output.metadata) + if 'application/json' in data: + data['application/json'] = json.loads(data['application/json']) + # promote ascii bytes (from v2) to unicode + for key in ('image/png', 'image/jpeg'): + if key in data and isinstance(data[key], bytes): + data[key] = data[key].decode('ascii') + elif output['output_type'] == 'pyerr': + output['output_type'] = 'error' + elif output['output_type'] == 'stream': + output['name'] = output.pop('stream') + return output + +def downgrade_output(output): + """downgrade a single code cell output to v3 from v4 + + - pyout <- execute_result + - pyerr <- error + - output.data.mime/type -> output.type + - un-mime-type keys + - stream.stream <- stream.name + """ + if output['output_type'] in {'execute_result', 'display_data'}: + if output['output_type'] == 'execute_result': + output['output_type'] = 'pyout' + output['prompt_number'] = output.pop('execution_count', None) + + # promote data dict to top-level output namespace + data = output.pop('data', {}) + if 'application/json' in data: + data['application/json'] = json.dumps(data['application/json']) + from_mime_key(data) + output.update(data) + from_mime_key(output.get('metadata', {})) + elif output['output_type'] == 'error': + output['output_type'] = 'pyerr' + elif output['output_type'] == 'stream': + output['stream'] = output.pop('name') + return output + +def upgrade_outputs(outputs): + """upgrade outputs of a code cell from v3 to v4""" + return [upgrade_output(op) for op in outputs] + +def downgrade_outputs(outputs): + """downgrade outputs of a code cell to v3 from v4""" + return [downgrade_output(op) for op in outputs] + +def downgrade(nb): + """Convert a v4 notebook to v3. + + Parameters + ---------- + nb : NotebookNode + The Python representation of the notebook to convert. + """ + if nb.nbformat != nbformat: + return nb + + # Validate the notebook before conversion + _warn_if_invalid(nb, nbformat) + + nb.nbformat = v3.nbformat + nb.nbformat_minor = v3.nbformat_minor + cells = [ downgrade_cell(cell) for cell in nb.pop('cells') ] + nb.worksheets = [v3.new_worksheet(cells=cells)] + nb.metadata.setdefault('name', '') + + # Validate the converted notebook before returning it + _warn_if_invalid(nb, v3.nbformat) + + nb.orig_nbformat = nb.metadata.pop('orig_nbformat', nbformat) + nb.orig_nbformat_minor = nb.metadata.pop('orig_nbformat_minor', nbformat_minor) + + return nb diff --git a/IPython/nbformat/v4/nbbase.py b/IPython/nbformat/v4/nbbase.py new file mode 100644 index 0000000..1827bf7 --- /dev/null +++ b/IPython/nbformat/v4/nbbase.py @@ -0,0 +1,137 @@ +"""Python API for composing notebook elements + +The Python representation of a notebook is a nested structure of +dictionary subclasses that support attribute access +(IPython.utils.ipstruct.Struct). The functions in this module are merely +helpers to build the structs in the right form. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from ..notebooknode import from_dict, NotebookNode + +# Change this when incrementing the nbformat version +nbformat = 4 +nbformat_minor = 0 +nbformat_schema = 'nbformat.v4.schema.json' + + +def validate(node, ref=None): + """validate a v4 node""" + from .. import validate + return validate(node, ref=ref, version=nbformat) + + +def new_output(output_type, data=None, **kwargs): + """Create a new output, to go in the ``cell.outputs`` list of a code cell.""" + output = NotebookNode(output_type=output_type) + + # populate defaults: + if output_type == 'stream': + output.name = u'stdout' + output.text = u'' + elif output_type in {'execute_result', 'display_data'}: + output.metadata = NotebookNode() + output.data = NotebookNode() + # load from args: + output.update(from_dict(kwargs)) + if data is not None: + output.data = from_dict(data) + # validate + validate(output, output_type) + return output + + +def output_from_msg(msg): + """Create a NotebookNode for an output from a kernel's IOPub message. + + Returns + ------- + + NotebookNode: the output as a notebook node. + + Raises + ------ + + ValueError: if the message is not an output message. + + """ + msg_type = msg['header']['msg_type'] + content = msg['content'] + + if msg_type == 'execute_result': + return new_output(output_type=msg_type, + metadata=content['metadata'], + data=content['data'], + execution_count=content['execution_count'], + ) + elif msg_type == 'stream': + return new_output(output_type=msg_type, + name=content['name'], + text=content['text'], + ) + elif msg_type == 'display_data': + return new_output(output_type=msg_type, + metadata=content['metadata'], + data=content['data'], + ) + elif msg_type == 'error': + return new_output(output_type=msg_type, + ename=content['ename'], + evalue=content['evalue'], + traceback=content['traceback'], + ) + else: + raise ValueError("Unrecognized output msg type: %r" % msg_type) + + +def new_code_cell(source='', **kwargs): + """Create a new code cell""" + cell = NotebookNode( + cell_type='code', + metadata=NotebookNode(), + execution_count=None, + source=source, + outputs=[], + ) + cell.update(from_dict(kwargs)) + + validate(cell, 'code_cell') + return cell + +def new_markdown_cell(source='', **kwargs): + """Create a new markdown cell""" + cell = NotebookNode( + cell_type='markdown', + source=source, + metadata=NotebookNode(), + ) + cell.update(from_dict(kwargs)) + + validate(cell, 'markdown_cell') + return cell + +def new_raw_cell(source='', **kwargs): + """Create a new raw cell""" + cell = NotebookNode( + cell_type='raw', + source=source, + metadata=NotebookNode(), + ) + cell.update(from_dict(kwargs)) + + validate(cell, 'raw_cell') + return cell + +def new_notebook(**kwargs): + """Create a new notebook""" + nb = NotebookNode( + nbformat=nbformat, + nbformat_minor=nbformat_minor, + metadata=NotebookNode(), + cells=[], + ) + nb.update(from_dict(kwargs)) + validate(nb) + return nb diff --git a/IPython/nbformat/v4/nbformat.v4.schema.json b/IPython/nbformat/v4/nbformat.v4.schema.json new file mode 100644 index 0000000..365a08a --- /dev/null +++ b/IPython/nbformat/v4/nbformat.v4.schema.json @@ -0,0 +1,375 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "IPython Notebook v4.0 JSON schema.", + "type": "object", + "additionalProperties": false, + "required": ["metadata", "nbformat_minor", "nbformat", "cells"], + "properties": { + "metadata": { + "description": "Notebook root-level metadata.", + "type": "object", + "additionalProperties": true, + "properties": { + "kernelspec": { + "description": "Kernel information.", + "type": "object", + "required": ["name", "display_name"], + "properties": { + "name": { + "description": "Name of the kernel specification.", + "type": "string" + }, + "display_name": { + "description": "Name to display in UI.", + "type": "string" + } + } + }, + "language_info": { + "description": "Kernel information.", + "type": "object", + "required": ["name"], + "properties": { + "name": { + "description": "The programming language which this kernel runs.", + "type": "string" + }, + "codemirror_mode": { + "description": "The codemirror mode to use for code in this language.", + "oneOf": [ + {"type": "string"}, + {"type": "object"} + ] + }, + "file_extension": { + "description": "The file extension for files in this language.", + "type": "string" + }, + "mimetype": { + "description": "The mimetype corresponding to files in this language.", + "type": "string" + }, + "pygments_lexer": { + "description": "The pygments lexer to use for code in this language.", + "type": "string" + } + } + }, + "signature": { + "description": "Hash of the notebook.", + "type": "string" + }, + "orig_nbformat": { + "description": "Original notebook format (major number) before converting the notebook between versions. This should never be written to a file.", + "type": "integer", + "minimum": 1 + } + } + }, + "nbformat_minor": { + "description": "Notebook format (minor number). Incremented for backward compatible changes to the notebook format.", + "type": "integer", + "minimum": 0 + }, + "nbformat": { + "description": "Notebook format (major number). Incremented between backwards incompatible changes to the notebook format.", + "type": "integer", + "minimum": 4, + "maximum": 4 + }, + "cells": { + "description": "Array of cells of the current notebook.", + "type": "array", + "items": {"$ref": "#/definitions/cell"} + } + }, + + "definitions": { + "cell": { + "type": "object", + "oneOf": [ + {"$ref": "#/definitions/raw_cell"}, + {"$ref": "#/definitions/markdown_cell"}, + {"$ref": "#/definitions/code_cell"} + ] + }, + + "raw_cell": { + "description": "Notebook raw nbconvert cell.", + "type": "object", + "additionalProperties": false, + "required": ["cell_type", "metadata", "source"], + "properties": { + "cell_type": { + "description": "String identifying the type of cell.", + "enum": ["raw"] + }, + "metadata": { + "description": "Cell-level metadata.", + "type": "object", + "additionalProperties": true, + "properties": { + "format": { + "description": "Raw cell metadata format for nbconvert.", + "type": "string" + }, + "name": {"$ref": "#/definitions/misc/metadata_name"}, + "tags": {"$ref": "#/definitions/misc/metadata_tags"} + } + }, + "source": {"$ref": "#/definitions/misc/source"} + } + }, + + "markdown_cell": { + "description": "Notebook markdown cell.", + "type": "object", + "additionalProperties": false, + "required": ["cell_type", "metadata", "source"], + "properties": { + "cell_type": { + "description": "String identifying the type of cell.", + "enum": ["markdown"] + }, + "metadata": { + "description": "Cell-level metadata.", + "type": "object", + "properties": { + "name": {"$ref": "#/definitions/misc/metadata_name"}, + "tags": {"$ref": "#/definitions/misc/metadata_tags"} + }, + "additionalProperties": true + }, + "source": {"$ref": "#/definitions/misc/source"} + } + }, + + "code_cell": { + "description": "Notebook code cell.", + "type": "object", + "additionalProperties": false, + "required": ["cell_type", "metadata", "source", "outputs", "execution_count"], + "properties": { + "cell_type": { + "description": "String identifying the type of cell.", + "enum": ["code"] + }, + "metadata": { + "description": "Cell-level metadata.", + "type": "object", + "additionalProperties": true, + "properties": { + "collapsed": { + "description": "Whether the cell is collapsed/expanded.", + "type": "boolean" + }, + "autoscroll": { + "description": "Whether the cell's output is scrolled, unscrolled, or autoscrolled.", + "enum": [true, false, "auto"] + }, + "name": {"$ref": "#/definitions/misc/metadata_name"}, + "tags": {"$ref": "#/definitions/misc/metadata_tags"} + } + }, + "source": {"$ref": "#/definitions/misc/source"}, + "outputs": { + "description": "Execution, display, or stream outputs.", + "type": "array", + "items": {"$ref": "#/definitions/output"} + }, + "execution_count": { + "description": "The code cell's prompt number. Will be null if the cell has not been run.", + "type": ["integer", "null"], + "minimum": 0 + } + } + }, + + "unrecognized_cell": { + "description": "Unrecognized cell from a future minor-revision to the notebook format.", + "type": "object", + "additionalProperties": true, + "required": ["cell_type", "metadata"], + "properties": { + "cell_type": { + "description": "String identifying the type of cell.", + "not" : { + "enum": ["markdown", "code", "raw"] + } + }, + "metadata": { + "description": "Cell-level metadata.", + "type": "object", + "properties": { + "name": {"$ref": "#/definitions/misc/metadata_name"}, + "tags": {"$ref": "#/definitions/misc/metadata_tags"} + }, + "additionalProperties": true + } + } + }, + + "output": { + "type": "object", + "oneOf": [ + {"$ref": "#/definitions/execute_result"}, + {"$ref": "#/definitions/display_data"}, + {"$ref": "#/definitions/stream"}, + {"$ref": "#/definitions/error"} + ] + }, + + "execute_result": { + "description": "Result of executing a code cell.", + "type": "object", + "additionalProperties": false, + "required": ["output_type", "data", "metadata", "execution_count"], + "properties": { + "output_type": { + "description": "Type of cell output.", + "enum": ["execute_result"] + }, + "execution_count": { + "description": "A result's prompt number.", + "type": ["integer", "null"], + "minimum": 0 + }, + "data": {"$ref": "#/definitions/misc/mimebundle"}, + "metadata": {"$ref": "#/definitions/misc/output_metadata"} + } + }, + + "display_data": { + "description": "Data displayed as a result of code cell execution.", + "type": "object", + "additionalProperties": false, + "required": ["output_type", "data", "metadata"], + "properties": { + "output_type": { + "description": "Type of cell output.", + "enum": ["display_data"] + }, + "data": {"$ref": "#/definitions/misc/mimebundle"}, + "metadata": {"$ref": "#/definitions/misc/output_metadata"} + } + }, + + "stream": { + "description": "Stream output from a code cell.", + "type": "object", + "additionalProperties": false, + "required": ["output_type", "name", "text"], + "properties": { + "output_type": { + "description": "Type of cell output.", + "enum": ["stream"] + }, + "name": { + "description": "The name of the stream (stdout, stderr).", + "type": "string" + }, + "text": { + "description": "The stream's text output, represented as an array of strings.", + "$ref": "#/definitions/misc/multiline_string" + } + } + }, + + "error": { + "description": "Output of an error that occurred during code cell execution.", + "type": "object", + "additionalProperties": false, + "required": ["output_type", "ename", "evalue", "traceback"], + "properties": { + "output_type": { + "description": "Type of cell output.", + "enum": ["error"] + }, + "ename": { + "description": "The name of the error.", + "type": "string" + }, + "evalue": { + "description": "The value, or message, of the error.", + "type": "string" + }, + "traceback": { + "description": "The error's traceback, represented as an array of strings.", + "type": "array", + "items": {"type": "string"} + } + } + }, + + "unrecognized_output": { + "description": "Unrecognized output from a future minor-revision to the notebook format.", + "type": "object", + "additionalProperties": true, + "required": ["output_type"], + "properties": { + "output_type": { + "description": "Type of cell output.", + "not": { + "enum": ["execute_result", "display_data", "stream", "error"] + } + } + } + }, + + "misc": { + "metadata_name": { + "description": "The cell's name. If present, must be a non-empty string.", + "type": "string", + "pattern": "^.+$" + }, + "metadata_tags": { + "description": "The cell's tags. Tags must be unique, and must not contain commas.", + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[^,]+$" + } + }, + "source": { + "description": "Contents of the cell, represented as an array of lines.", + "$ref": "#/definitions/misc/multiline_string" + }, + "execution_count": { + "description": "The code cell's prompt number. Will be null if the cell has not been run.", + "type": ["integer", "null"], + "minimum": 0 + }, + "mimebundle": { + "description": "A mime-type keyed dictionary of data", + "type": "object", + "additionalProperties": false, + "properties": { + "application/json": { + "type": "object" + } + }, + "patternProperties": { + "^(?!application/json$)[a-zA-Z0-9]+/[a-zA-Z0-9\\-\\+\\.]+$": { + "description": "mimetype output (e.g. text/plain), represented as either an array of strings or a string.", + "$ref": "#/definitions/misc/multiline_string" + } + } + }, + "output_metadata": { + "description": "Cell output metadata.", + "type": "object", + "additionalProperties": true + }, + "multiline_string": { + "oneOf" : [ + {"type": "string"}, + { + "type": "array", + "items": {"type": "string"} + } + ] + } + } + } +} diff --git a/IPython/nbformat/v4/nbjson.py b/IPython/nbformat/v4/nbjson.py new file mode 100644 index 0000000..753d49a --- /dev/null +++ b/IPython/nbformat/v4/nbjson.py @@ -0,0 +1,67 @@ +"""Read and write notebooks in JSON format.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import copy +import json + +from IPython.utils import py3compat + +from .nbbase import from_dict +from .rwbase import ( + NotebookReader, NotebookWriter, rejoin_lines, split_lines, strip_transient +) + + +class BytesEncoder(json.JSONEncoder): + """A JSON encoder that accepts b64 (and other *ascii*) bytestrings.""" + def default(self, obj): + if isinstance(obj, bytes): + return obj.decode('ascii') + return json.JSONEncoder.default(self, obj) + + +class JSONReader(NotebookReader): + + def reads(self, s, **kwargs): + """Read a JSON string into a Notebook object""" + nb = json.loads(s, **kwargs) + nb = self.to_notebook(nb, **kwargs) + return nb + + def to_notebook(self, d, **kwargs): + """Convert a disk-format notebook dict to in-memory NotebookNode + + handles multi-line values as strings, scrubbing of transient values, etc. + """ + nb = from_dict(d) + nb = rejoin_lines(nb) + nb = strip_transient(nb) + return nb + + +class JSONWriter(NotebookWriter): + + def writes(self, nb, **kwargs): + """Serialize a NotebookNode object as a JSON string""" + kwargs['cls'] = BytesEncoder + kwargs['indent'] = 1 + kwargs['sort_keys'] = True + kwargs['separators'] = (',',': ') + # don't modify in-memory dict + nb = copy.deepcopy(nb) + if kwargs.pop('split_lines', True): + nb = split_lines(nb) + nb = strip_transient(nb) + return py3compat.str_to_unicode(json.dumps(nb, **kwargs), 'utf-8') + + +_reader = JSONReader() +_writer = JSONWriter() + +reads = _reader.reads +read = _reader.read +to_notebook = _reader.to_notebook +write = _writer.write +writes = _writer.writes diff --git a/IPython/nbformat/v4/rwbase.py b/IPython/nbformat/v4/rwbase.py new file mode 100644 index 0000000..68b81e0 --- /dev/null +++ b/IPython/nbformat/v4/rwbase.py @@ -0,0 +1,95 @@ +"""Base classes and utilities for readers and writers.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from IPython.utils.py3compat import string_types, cast_unicode_py2 + + +def rejoin_lines(nb): + """rejoin multiline text into strings + + For reversing effects of ``split_lines(nb)``. + + This only rejoins lines that have been split, so if text objects were not split + they will pass through unchanged. + + Used when reading JSON files that may have been passed through split_lines. + """ + for cell in nb.cells: + if 'source' in cell and isinstance(cell.source, list): + cell.source = ''.join(cell.source) + if cell.get('cell_type', None) == 'code': + for output in cell.get('outputs', []): + output_type = output.get('output_type', '') + if output_type in {'execute_result', 'display_data'}: + for key, value in output.get('data', {}).items(): + if key != 'application/json' and isinstance(value, list): + output.data[key] = ''.join(value) + elif output_type: + if isinstance(output.get('text', ''), list): + output.text = ''.join(output.text) + return nb + + +def split_lines(nb): + """split likely multiline text into lists of strings + + For file output more friendly to line-based VCS. ``rejoin_lines(nb)`` will + reverse the effects of ``split_lines(nb)``. + + Used when writing JSON files. + """ + for cell in nb.cells: + source = cell.get('source', None) + if isinstance(source, string_types): + cell['source'] = source.splitlines(True) + + if cell.cell_type == 'code': + for output in cell.outputs: + if output.output_type in {'execute_result', 'display_data'}: + for key, value in output.data.items(): + if key != 'application/json' and isinstance(value, string_types): + output.data[key] = value.splitlines(True) + elif output.output_type == 'stream': + if isinstance(output.text, string_types): + output.text = output.text.splitlines(True) + return nb + + +def strip_transient(nb): + """Strip transient values that shouldn't be stored in files. + + This should be called in *both* read and write. + """ + nb.metadata.pop('orig_nbformat', None) + nb.metadata.pop('orig_nbformat_minor', None) + for cell in nb.cells: + cell.metadata.pop('trusted', None) + return nb + + +class NotebookReader(object): + """A class for reading notebooks.""" + + def reads(self, s, **kwargs): + """Read a notebook from a string.""" + raise NotImplementedError("loads must be implemented in a subclass") + + def read(self, fp, **kwargs): + """Read a notebook from a file like object""" + nbs = cast_unicode_py2(fp.read()) + return self.reads(nbs, **kwargs) + + +class NotebookWriter(object): + """A class for writing notebooks.""" + + def writes(self, nb, **kwargs): + """Write a notebook to a string.""" + raise NotImplementedError("loads must be implemented in a subclass") + + def write(self, nb, fp, **kwargs): + """Write a notebook to a file like object""" + nbs = cast_unicode_py2(self.writes(nb, **kwargs)) + return fp.write(nbs) diff --git a/IPython/nbformat/v4/tests/__init__.py b/IPython/nbformat/v4/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/IPython/nbformat/v4/tests/__init__.py diff --git a/IPython/nbformat/v4/tests/formattest.py b/IPython/nbformat/v4/tests/formattest.py new file mode 100644 index 0000000..853083e --- /dev/null +++ b/IPython/nbformat/v4/tests/formattest.py @@ -0,0 +1,54 @@ +# -*- coding: utf8 -*- +import io +import os +import shutil +import tempfile + +pjoin = os.path.join + +from .nbexamples import nb0 + + +def open_utf8(fname, mode): + return io.open(fname, mode=mode, encoding='utf-8') + +class NBFormatTest: + """Mixin for writing notebook format tests""" + + # override with appropriate values in subclasses + nb0_ref = None + ext = None + mod = None + + def setUp(self): + self.wd = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self.wd) + + def assertNBEquals(self, nba, nbb): + self.assertEqual(nba, nbb) + + def test_writes(self): + s = self.mod.writes(nb0) + if self.nb0_ref: + self.assertEqual(s, self.nb0_ref) + + def test_reads(self): + s = self.mod.writes(nb0) + nb = self.mod.reads(s) + + def test_roundtrip(self): + s = self.mod.writes(nb0) + self.assertNBEquals(self.mod.reads(s),nb0) + + def test_write_file(self): + with open_utf8(pjoin(self.wd, "nb0.%s" % self.ext), 'w') as f: + self.mod.write(nb0, f) + + def test_read_file(self): + with open_utf8(pjoin(self.wd, "nb0.%s" % self.ext), 'w') as f: + self.mod.write(nb0, f) + + with open_utf8(pjoin(self.wd, "nb0.%s" % self.ext), 'r') as f: + nb = self.mod.read(f) diff --git a/IPython/nbformat/v4/tests/nbexamples.py b/IPython/nbformat/v4/tests/nbexamples.py new file mode 100644 index 0000000..b55a0c4 --- /dev/null +++ b/IPython/nbformat/v4/tests/nbexamples.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- + +import os +from base64 import encodestring + +from ..nbbase import ( + new_code_cell, new_markdown_cell, new_notebook, + new_output, new_raw_cell +) + +# some random base64-encoded *text* +png = encodestring(os.urandom(5)).decode('ascii') +jpeg = encodestring(os.urandom(6)).decode('ascii') + +cells = [] +cells.append(new_markdown_cell( + source='Some NumPy Examples', +)) + + +cells.append(new_code_cell( + source='import numpy', + execution_count=1, +)) + +cells.append(new_markdown_cell( + source='A random array', +)) + +cells.append(new_raw_cell( + source='A random array', +)) + +cells.append(new_markdown_cell( + source=u'## My Heading', +)) + +cells.append(new_code_cell( + source='a = numpy.random.rand(100)', + execution_count=2, +)) +cells.append(new_code_cell( + source='a = 10\nb = 5\n', + execution_count=3, +)) +cells.append(new_code_cell( + source='a = 10\nb = 5', + execution_count=4, +)) + +cells.append(new_code_cell( + source=u'print "ünîcødé"', + execution_count=3, + outputs=[new_output( + output_type=u'execute_result', + data={ + 'text/plain': u'', + 'text/html': u'The HTML rep', + 'text/latex': u'$a$', + 'image/png': png, + 'image/jpeg': jpeg, + 'image/svg+xml': u'', + 'application/json': { + 'key': 'value' + }, + 'application/javascript': u'var i=0;' + }, + execution_count=3 + ),new_output( + output_type=u'display_data', + data={ + 'text/plain': u'', + 'text/html': u'The HTML rep', + 'text/latex': u'$a$', + 'image/png': png, + 'image/jpeg': jpeg, + 'image/svg+xml': u'', + 'application/json': { + 'key': 'value' + }, + 'application/javascript': u'var i=0;' + }, + ),new_output( + output_type=u'error', + ename=u'NameError', + evalue=u'NameError was here', + traceback=[u'frame 0', u'frame 1', u'frame 2'] + ),new_output( + output_type=u'stream', + text='foo\rbar\r\n' + ),new_output( + output_type=u'stream', + name='stderr', + text='\rfoo\rbar\n' + )] +)) + +nb0 = new_notebook(cells=cells, + metadata={ + 'language': 'python', + } +) + + diff --git a/IPython/nbformat/v4/tests/test_convert.py b/IPython/nbformat/v4/tests/test_convert.py new file mode 100644 index 0000000..04f871b --- /dev/null +++ b/IPython/nbformat/v4/tests/test_convert.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +import copy + +import nose.tools as nt + +from IPython.nbformat import validate +from .. import convert + +from . import nbexamples +from IPython.nbformat.v3.tests import nbexamples as v3examples +from IPython.nbformat import v3, v4 + +def test_upgrade_notebook(): + nb03 = copy.deepcopy(v3examples.nb0) + validate(nb03) + nb04 = convert.upgrade(nb03) + validate(nb04) + +def test_downgrade_notebook(): + nb04 = copy.deepcopy(nbexamples.nb0) + validate(nb04) + nb03 = convert.downgrade(nb04) + validate(nb03) + +def test_upgrade_heading(): + v3h = v3.new_heading_cell + v4m = v4.new_markdown_cell + for v3cell, expected in [ + ( + v3h(source='foo', level=1), + v4m(source='# foo'), + ), + ( + v3h(source='foo\nbar\nmulti-line\n', level=4), + v4m(source='#### foo bar multi-line'), + ), + ( + v3h(source=u'ünìcö∂e–cønvërsioñ', level=4), + v4m(source=u'#### ünìcö∂e–cønvërsioñ'), + ), + ]: + upgraded = convert.upgrade_cell(v3cell) + nt.assert_equal(upgraded, expected) + +def test_downgrade_heading(): + v3h = v3.new_heading_cell + v4m = v4.new_markdown_cell + v3m = lambda source: v3.new_text_cell('markdown', source) + for v4cell, expected in [ + ( + v4m(source='# foo'), + v3h(source='foo', level=1), + ), + ( + v4m(source='#foo'), + v3h(source='foo', level=1), + ), + ( + v4m(source='#\tfoo'), + v3h(source='foo', level=1), + ), + ( + v4m(source='# \t foo'), + v3h(source='foo', level=1), + ), + ( + v4m(source='# foo\nbar'), + v3m(source='# foo\nbar'), + ), + ]: + downgraded = convert.downgrade_cell(v4cell) + nt.assert_equal(downgraded, expected) diff --git a/IPython/nbformat/v4/tests/test_json.py b/IPython/nbformat/v4/tests/test_json.py new file mode 100644 index 0000000..5b89c5f --- /dev/null +++ b/IPython/nbformat/v4/tests/test_json.py @@ -0,0 +1,69 @@ +from base64 import decodestring +from unittest import TestCase + +from IPython.utils.py3compat import unicode_type +from ..nbjson import reads, writes +from .. import nbjson +from .nbexamples import nb0 + +from . import formattest + + +class TestJSON(formattest.NBFormatTest, TestCase): + + nb0_ref = None + ext = 'ipynb' + mod = nbjson + + def test_roundtrip_nosplit(self): + """Ensure that multiline blobs are still readable""" + # ensures that notebooks written prior to splitlines change + # are still readable. + s = writes(nb0, split_lines=False) + self.assertEqual(nbjson.reads(s),nb0) + + def test_roundtrip_split(self): + """Ensure that splitting multiline blocks is safe""" + # This won't differ from test_roundtrip unless the default changes + s = writes(nb0, split_lines=True) + self.assertEqual(nbjson.reads(s),nb0) + + def test_read_png(self): + """PNG output data is b64 unicode""" + s = writes(nb0) + nb1 = nbjson.reads(s) + found_png = False + for cell in nb1.cells: + if not 'outputs' in cell: + continue + for output in cell.outputs: + if not 'data' in output: + continue + if 'image/png' in output.data: + found_png = True + pngdata = output.data['image/png'] + self.assertEqual(type(pngdata), unicode_type) + # test that it is valid b64 data + b64bytes = pngdata.encode('ascii') + raw_bytes = decodestring(b64bytes) + assert found_png, "never found png output" + + def test_read_jpeg(self): + """JPEG output data is b64 unicode""" + s = writes(nb0) + nb1 = nbjson.reads(s) + found_jpeg = False + for cell in nb1.cells: + if not 'outputs' in cell: + continue + for output in cell.outputs: + if not 'data' in output: + continue + if 'image/jpeg' in output.data: + found_jpeg = True + jpegdata = output.data['image/jpeg'] + self.assertEqual(type(jpegdata), unicode_type) + # test that it is valid b64 data + b64bytes = jpegdata.encode('ascii') + raw_bytes = decodestring(b64bytes) + assert found_jpeg, "never found jpeg output" diff --git a/IPython/nbformat/v4/tests/test_nbbase.py b/IPython/nbformat/v4/tests/test_nbbase.py new file mode 100644 index 0000000..6172657 --- /dev/null +++ b/IPython/nbformat/v4/tests/test_nbbase.py @@ -0,0 +1,101 @@ +# coding: utf-8 +"""Tests for the Python API for composing notebook elements""" + +import nose.tools as nt + +from IPython.nbformat.validator import isvalid, validate, ValidationError +from ..nbbase import ( + NotebookNode, nbformat, + new_code_cell, new_markdown_cell, new_notebook, + new_output, new_raw_cell, +) + +def test_empty_notebook(): + nb = new_notebook() + nt.assert_equal(nb.cells, []) + nt.assert_equal(nb.metadata, NotebookNode()) + nt.assert_equal(nb.nbformat, nbformat) + +def test_empty_markdown_cell(): + cell = new_markdown_cell() + nt.assert_equal(cell.cell_type, 'markdown') + nt.assert_equal(cell.source, '') + +def test_markdown_cell(): + cell = new_markdown_cell(u'* Søme markdown') + nt.assert_equal(cell.source, u'* Søme markdown') + +def test_empty_raw_cell(): + cell = new_raw_cell() + nt.assert_equal(cell.cell_type, u'raw') + nt.assert_equal(cell.source, '') + +def test_raw_cell(): + cell = new_raw_cell('hi') + nt.assert_equal(cell.source, u'hi') + +def test_empty_code_cell(): + cell = new_code_cell('hi') + nt.assert_equal(cell.cell_type, 'code') + nt.assert_equal(cell.source, u'hi') + +def test_empty_display_data(): + output = new_output('display_data') + nt.assert_equal(output.output_type, 'display_data') + +def test_empty_stream(): + output = new_output('stream') + nt.assert_equal(output.output_type, 'stream') + nt.assert_equal(output.name, 'stdout') + nt.assert_equal(output.text, '') + +def test_empty_execute_result(): + output = new_output('execute_result', execution_count=1) + nt.assert_equal(output.output_type, 'execute_result') + +mimebundle = { + 'text/plain': "some text", + "application/json": { + "key": "value" + }, + "image/svg+xml": 'ABCDEF', + "application/octet-stream": 'ABC-123', + "application/vnd.foo+bar": "Some other stuff", +} + +def test_display_data(): + output = new_output('display_data', mimebundle) + for key, expected in mimebundle.items(): + nt.assert_equal(output.data[key], expected) + +def test_execute_result(): + output = new_output('execute_result', mimebundle, execution_count=10) + nt.assert_equal(output.execution_count, 10) + for key, expected in mimebundle.items(): + nt.assert_equal(output.data[key], expected) + +def test_error(): + o = new_output(output_type=u'error', ename=u'NameError', + evalue=u'Name not found', traceback=[u'frame 0', u'frame 1', u'frame 2'] + ) + nt.assert_equal(o.output_type, u'error') + nt.assert_equal(o.ename, u'NameError') + nt.assert_equal(o.evalue, u'Name not found') + nt.assert_equal(o.traceback, [u'frame 0', u'frame 1', u'frame 2']) + +def test_code_cell_with_outputs(): + cell = new_code_cell(execution_count=10, outputs=[ + new_output('display_data', mimebundle), + new_output('stream', text='hello'), + new_output('execute_result', mimebundle, execution_count=10), + ]) + nt.assert_equal(cell.execution_count, 10) + nt.assert_equal(len(cell.outputs), 3) + er = cell.outputs[-1] + nt.assert_equal(er.execution_count, 10) + nt.assert_equal(er['output_type'], 'execute_result') + +def test_stream(): + output = new_output('stream', name='stderr', text='hello there') + nt.assert_equal(output.name, 'stderr') + nt.assert_equal(output.text, 'hello there') diff --git a/IPython/nbformat/v4/tests/test_validate.py b/IPython/nbformat/v4/tests/test_validate.py new file mode 100644 index 0000000..dbef2cb --- /dev/null +++ b/IPython/nbformat/v4/tests/test_validate.py @@ -0,0 +1,105 @@ +"""Tests for nbformat validation""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import io +import os + +import nose.tools as nt + +from IPython.nbformat.validator import validate, ValidationError +from ..nbjson import reads +from ..nbbase import ( + nbformat, + new_code_cell, new_markdown_cell, new_notebook, + new_output, new_raw_cell, +) + +def validate4(obj, ref=None): + return validate(obj, ref, version=nbformat) + +def test_valid_code_cell(): + cell = new_code_cell() + validate4(cell, 'code_cell') + +def test_invalid_code_cell(): + cell = new_code_cell() + + cell['source'] = 5 + with nt.assert_raises(ValidationError): + validate4(cell, 'code_cell') + + cell = new_code_cell() + del cell['metadata'] + + with nt.assert_raises(ValidationError): + validate4(cell, 'code_cell') + + cell = new_code_cell() + del cell['source'] + + with nt.assert_raises(ValidationError): + validate4(cell, 'code_cell') + + cell = new_code_cell() + del cell['cell_type'] + + with nt.assert_raises(ValidationError): + validate4(cell, 'code_cell') + +def test_invalid_markdown_cell(): + cell = new_markdown_cell() + + cell['source'] = 5 + with nt.assert_raises(ValidationError): + validate4(cell, 'markdown_cell') + + cell = new_markdown_cell() + del cell['metadata'] + + with nt.assert_raises(ValidationError): + validate4(cell, 'markdown_cell') + + cell = new_markdown_cell() + del cell['source'] + + with nt.assert_raises(ValidationError): + validate4(cell, 'markdown_cell') + + cell = new_markdown_cell() + del cell['cell_type'] + + with nt.assert_raises(ValidationError): + validate4(cell, 'markdown_cell') + +def test_invalid_raw_cell(): + cell = new_raw_cell() + + cell['source'] = 5 + with nt.assert_raises(ValidationError): + validate4(cell, 'raw_cell') + + cell = new_raw_cell() + del cell['metadata'] + + with nt.assert_raises(ValidationError): + validate4(cell, 'raw_cell') + + cell = new_raw_cell() + del cell['source'] + + with nt.assert_raises(ValidationError): + validate4(cell, 'raw_cell') + + cell = new_raw_cell() + del cell['cell_type'] + + with nt.assert_raises(ValidationError): + validate4(cell, 'raw_cell') + +def test_sample_notebook(): + here = os.path.dirname(__file__) + with io.open(os.path.join(here, os.pardir, os.pardir, 'tests', "test4.ipynb"), encoding='utf-8') as f: + nb = reads(f.read()) + validate4(nb) diff --git a/IPython/nbformat/v4/v4-test.ipynb b/IPython/nbformat/v4/v4-test.ipynb deleted file mode 100644 index ad9d3d8..0000000 --- a/IPython/nbformat/v4/v4-test.ipynb +++ /dev/null @@ -1,155 +0,0 @@ -{ - "metadata": { - "cell_tags": ["", null], - "name": "", - "kernel_info": { - "name": "python", - "language": "python" - } - }, - "nbformat": 4, - "nbformat_minor": 0, - "cells": [ - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "nbconvert latex test" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Lorem ipsum** dolor sit amet, consectetur adipiscing elit. Nunc luctus bibendum felis dictum sodales. Ut suscipit, orci ut interdum imperdiet, purus ligula mollis *justo*, non malesuada nisl augue eget lorem. Donec bibendum, erat sit amet porttitor aliquam, urna lorem ornare libero, in vehicula diam diam ut ante. Nam non urna rhoncus, accumsan elit sit amet, mollis tellus. Vestibulum nec tellus metus. Vestibulum tempor, ligula et vehicula rhoncus, sapien turpis faucibus lorem, id dapibus turpis mauris ac orci. Sed volutpat vestibulum venenatis." - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Printed Using Python" - ] - }, - { - "cell_type": "code", - "source": [ - "print(\"hello\")" - ], - "metadata": { - "collapsed": false, - "autoscroll": false - }, - "outputs": [ - { - "output_type": "stream", - "metadata": {}, - "stream": "stdout", - "text/plain": [ - "hello\n" - ] - } - ], - "prompt_number": 1 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Pyout" - ] - }, - { - "cell_type": "code", - "source": [ - "from IPython.display import HTML\n", - "HTML(\"\"\"\n", - "\n", - "HTML\n", - "\"\"\")" - ], - "metadata": { - "collapsed": false, - "autoscroll": false - }, - "outputs": [ - { - "text/html": [ - "\n", - "\n", - "HTML\n" - ], - "metadata": {}, - "output_type": "execute_result", - "prompt_number": 3, - "text/plain": [ - "" - ] - } - ], - "prompt_number": 3 - }, - { - "cell_type": "code", - "source": [ - "%%javascript\n", - "console.log(\"hi\");" - ], - "metadata": { - "collapsed": false, - "autoscroll": false - }, - "outputs": [ - { - "text/javascript": [ - "console.log(\"hi\");" - ], - "metadata": {}, - "output_type": "display_data", - "text/plain": [ - "" - ] - } - ], - "prompt_number": 7 - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Image" - ] - }, - { - "cell_type": "code", - "source": [ - "from IPython.display import Image\n", - "Image(\"http://ipython.org/_static/IPy_header.png\")" - ], - "metadata": { - "collapsed": false, - "autoscroll": false - }, - "outputs": [ - { - "metadata": {}, - "output_type": "execute_result", - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAggAAABDCAYAAAD5/P3lAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAAH3AAAB9wBYvxo6AAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAACAASURB\nVHic7Z15uBxF1bjfugkJhCWBsCSAJGACNg4QCI3RT1lEAVE+UEBNOmwCDcjHT1wQgU+WD3dFxA1o\nCAikAZFFVlnCjizpsCUjHQjBIAkQlpCFJGS79fvjdGf69vTsc2fuza33eeaZmeqq6jM9vZw6dc4p\nBUwC+tE+fqW1fqmRDpRSHjCggS40sBxYDCxKvL8KzNBaL21EPoPB0DPIWVY/4NlE0ffzYfhgu+Qx\nGHoy/YFjaK+CcB3QkIIAHAWs3wRZsuhUSs0CXgQeBm7UWi/spn0Z+jA5yxpEfYruqnwYllRic5a1\nMaWv8U5gaT4M19Sx396IAnZLfB/SLkEMhp5O/3YL0AvoAHaKXl8HLlZK3QZcpbWe0lbJDOsaHuDU\n0e4u4JAy2wPk/C1JzrKWArOQ0fUtwH35MOysQxaDwbCO0NFuAXoh6wPjgQeUUvcqpUa0WyCDoQls\nCIwBjgfuAV7KWdY+7RWpmJxlXZezrEdylvXxdstiMKzrGAtCYxwI/EspdZbW+g/tFsbQ67kQuBHY\nFNgseh9FV6vCbUAeWBC9PgBeq2EfS6J2MQOBrRDTe5KdgAdzlvW1fBjeUUP/3UbOsoYBE6OvG7VT\nFoOhL9Af+BUwFLkZpV+DaY6V4UPkRpb1+ncT+m8nGwK/V0oN01qf025hDL2XfBi+DLycLMtZVo6u\nCsKfGnSq8/NheEpqHwOBEcDBwJnAsGhTP2ByzrJG5cPwnQb22Sy+0G4BDIa+RH+t9dmlNiqlFKIk\nJJWGi+jq5JPmq8BbJJQArfXqpkncczlbKbVQa/3rdgtiMNRCPgxXAK8Ar+Qs63LgXmDvaPPGwPeA\nH7VJvCRfbLcABkNfouwUg9ZaAwuj178BlFLvVejzgR4WFviM1npcuQpKqf6IyXIjxLS7GzAWuUnu\nXsO+fqWUellr3ZBJdq/jr9+BDn1uve07O9Rz0y6f8PtGZGgWe53oT6SBkZ/q1/nHZy47aloTRTKU\nIR+Gy3OWNR6Zxtg0Kv4KRkEwGPocxgcBiCwcsSI0F5iOhF+ilPok8C3gVGS+thK/VErdrbWuO2ys\ns/+aLZTuOKbe9krrIUCPUBB0B+PQ1P1bdKe6EzAKQgvJh+GbOct6gkJkxM45y+qXDIWMHBhjBWJe\nPgyDWvaRs6zPIVObAG/nw/DpEvUGAp8E9gGGJzbtl7Os7cvs4skqp0V0Yl8jgcOBjyMDhbmIZeWl\nfBg+UUVfReQsayhwELAnsAXi6/E28BxwTz4MP6iyn92RaSCA+/NhuCwqXx9R4MYhU0MfRTK/AjyW\nD8MFGd0ZDFVhFIQKaK3/BXxfKXUlklTq0xWafAI4Driyu2UzGLqRlygoCArYHJif2H4gcFb0+Z2c\nZW2bD8NV1XScs6yNgH8g/jsAPwCeTmzfFPgjYsnbiez71MUVdnMQcF8V4nyUs6whwB8QX4+0s2Ys\n0yPAt/NhGFbRZ/wbzgO+DaxXotqqnGX9GbigCkXhf5CBCsDngYdzljURGQhsWqLN+znL+iFwdT4M\ndYk6BkNJTJhjlWitQ2Bf4P4qqv848t8wGHor6Yd9+ruHJFkC2BI4rIa+D6egHKwmstYlGAxMQCwH\nrRjEPI5ER5S7ZvcFXsxZ1phKneUsawSi8HyH0soB0bbvAM9Ebaplt5xlnYkct1LKAYiFZhJwSQ19\nGwxrMRaEGtBar1RKfRX4JxIzXortou3PN1mE+YgJsSwaeoLHOQCqUy3QSr9eqZ6G/gq2aYVMhqrY\nOfF5FeJwvJZ8GM7JWdY/gC9HRS7wtyr7Pjrx+e6MqYC3KLbU7Qhck/h+FJIKvRRVjfSREXicU8EH\npgAvIIqLBZwGfC7avl5Uf29KkLOsTZCMq8npj9sQx89no37HIlaAODplNPBIzrJ2z4dhNVlaT0HC\nXwFmIkrAC4if2PaIz8/3KCgn385Z1pX5MJxeRd8Gw1qMglAjWutlSqnTgUcqVP0SzVYQtP5mcMXE\nSvvtUUy9YsK5QEWHy7EnTB6lOtSsFohkqEDOsgYAdqJoagkT9Z8pKAj75yzr4/kwnF2h748ho/GY\nq9J1oqiKLj4JOctKK8Yz8mH4Yrl9VcnHkXVYTsyHoZ8WJWdZNyPThbF5/3M5yzowH4alpi9+T0E5\nWA18Nx+Gf0zVeRG4KmdZ90R9bwCMRKwyX69C5h2j91uA4/JhuCSxbTYwJWdZtwNPIFbifsAFSISZ\nwVA1ZoqhDrTWjyIjjXIc3ApZDIZu4ELgY4nvt5Wody8wJ/qsgBOr6HsihfvOfCRrY7v5dYZyAECk\nGP0ISEZmZYZ55yxrB8SyEXNxhnKQ7Pt64H8TRUfmLGuXKmWeC4xPKQfJvp9CLCJlZTYYymEUhPq5\ntcL2XVsihcHQJHKWtU3Osi5GnAZj5iKWgiKitRouTxQdl7OscnPu0HV64dp8GLY7R8pyxEGxJPkw\nfBcZ9ceUSvN8IoV76upK/UZcgawcG3NKqYopfleFU+gDic/b5SzLWIwNNWFOmPqp5CG9sVJqPa11\nVZ7dBkOL2D1nWcmcBkOR8MFtgM/QdTXJZcCR+TBcXqa/SYj5egAFZ8VMX4ScZe2FRPnEXF2z9M3n\n3nwYVsrtAmK6/0z0uVR4ZXLtivvzYfhGpU7zYbgkZ1k3ACdHRQdWIQsUO3ZmkUzB3Q/xjaolLbeh\nj2MUhDrRWr+mlFpJ+eV5hyIxz4YWs98Fj/Rf8uZbozo0/ZYt7D8rf9ORK9stUw/hU9GrEnMAp1R+\ngph8GL4bzdNPiIpOorSzYtJ68FS1IYPdTLWp3hcnPm+Q3pizrA7E+TCmFn+aZN0dcpY1LB+G5e4b\ny6rM8bA49X39GmQyGMwUQ4NUGnkMrbDd0A3sdeLk4z6cN+89pTtDTWd+gyErF+7pTv5eu+XqJbyK\nTDHsmg/DJ6tsc2ni8+dzljUqXSGaevhmoqjIObFNVBzlV8kQug4W5tbQNl13WGatAv+poW+DoW6M\nBaExPgC2LrO9nHWhpSilDqI4NPMhrfXUJvS9M/DfqeJXtdY3N9p3rex50uQ9lFKT6BrTvoFCXbTX\nyZNfmnrZxHtbLVMP4xng74nvK5DzeD7wfIWRayb5MHwiZ1kzgF0oOCuemar2ZQoK8zLgr7Xup5t4\ns0n9DEl9b0RBSPeV5q0a+jYY6sYoCI1RacnZ91siRXUMAH6eKnsYicdulDOAY1NlpzWh35pRqG9R\nIuGN7uw4AfG878s8nw/DX3RDv5dScGY8NmdZP86HYXJaJzm9cHMp7/s2UHdK9BTpKaxBNbRN163k\nt9Rux05DH8FMMTTGZhW2v9sSKarjbopNk/sqpUY30qlSahCSGS/JCuD6RvqtF6UpMm/HaHTJbYaG\nmQzED/0umRVzlrUZhXwJ0HOmF5pJOlXyxzJrZbNt6rtZP8HQIzAKQp0opTZAlsItxTKtdTnv75YS\nLR7lpYqrjV0vx2EUH4fbtdZtucnpMqOrDjPy6jYii8DkRFHSYnAEhem22cBjrZKrVeTDcCldTf/p\nh345ksrEGprnF2EwNIRREOrnMxW2z2uJFLVxJcXmy2OVUo34ShydUda+EaIq7T2u0SZTY/eSdFY8\nMGdZm0efk86J6/LCQUnFp5pIkZjkcvQz8mH4YZPkMRgawigI9VNp7v7BlkhRA1rr+RQneNqC2hba\nWYtSajiS9z3JXLomaGktq/VllLIUdKqSWe0MjZMPwxlIel8Q/6Zv5CxrGIX8AJ10XU+hFtIRQ+UW\nKWoXyYyTu+Qsa79KDXKWNRpJyx5zZ9OlMhjqxCgIdaCU6g98o0K1npBCNotLM8rcOvuagCRgSXKN\n1rozq3IrCCZNfFkrfRjotWsCaJinUBODK51/tkuuPkTy/DoYOIDCfeb+fBjW4t2/lqhdcmRdbUri\nVnILXS2HZ1WRvfAcCk61K4A/dYdgBkM9GAWhPr5F6XSrIBf6Qy2SpSaidSReShV/XilV7veUIj29\noOkB2fGmXT7x7sCbOGpFf7VZx4A1m0/znG2nehMyc+0bms7NFJxzxwH7J7Y1OvWUPG9/mLOsLRvs\nr6lEaaOT0TtfBB5ITLWsJWdZg3KWdRNwTKL4wnwYzu9mMQ2GqjFhjjWilBqBpJYtx51a66UV6rST\nS+maJz52VvxRdvVilFK7UbzexGNa67Kr+bWS6X+ekPYs79HkLGt34JOI+Xyz6D2d1vfMnGUdini6\nL0C851/Oh2HD+SyaQT4MV+YsaxJyLm1Gwf9gAXBHg93/JNHHtsArOcuajCztPBDYCkkytBXg5sOw\n5QmF8mF4W86yLgK+HxXtC8zKWVaALMm8CslHsicS7RFzL8VhyAZDWzEKQg0opbYE7qd8prPVdF2h\nrSdyLfALYMNE2XFKqR/XsHbEURll62L4Wiv5PuBUqPPF6JXkLuCQbpGoPi4HfohYKGMHWD9axrlu\n8mF4Z7RuwfioaDBwaonqRemQW0U+DH+Qs6xFwHnIFNwQsv+3mMnA8dHiVwZDj8FMMVSJUuow4DkK\na7GX4gqt9cstEKlutNaL6boULMho5tBq2iul+lH8IFuCmJcNfZx8GM6hOCFVU5THfBhOQHxfylkH\n3gY+asb+6iUfhhcCewC3l5BlFbJk/P75MDwqlVTKYOgRKK1rizhSSk2h67ximo1abV5XSi2n9EIk\nz2itx5XYVqnfQcjI7DiqW2XtfeCTUbRA3ex50nWfUrqjeJEcrfcLrpj4SCN9xyilxgDPp4of0Fof\nUEXbg4B/pIqv1FrXnVNh7AmTR3V0qIwwRH1E4E28pd5+De0hZ1m/Bb4bfX0+H4Z7dMM+hgGjkDwC\nS5FpjFk9bR4/Z1mDkGmF4VHR20g4Y3oxJYOhR9EXphg6lFLlVjFbH0mZvDGwCTAayCFe0ntTOZ1y\nzDLgkEaVg1ahtX5BKfUU8OlE8ReUUjtorSstCduzch8YehSR5/6ERFG3nBvRuhE9frXUfBguA6pd\n+Mpg6DH0BQXBBro7o+Ea4Bta66e6eT/N5lK6KggKOAE4u1QDpdTGFOdNmNkLf7uh+zgYcRQEMa+3\nJe22wWBoDOOD0DhLgYla67vaLUgd3ETxglLHRXkeSnEExQ5gbQ9tNPQokis5TsqHoVlbwGDohRgF\noTECYHet9Y3tFqQetNYrKDb/DqN46eYk6emF1UhUhMFAzrImUEhDvgr4VRvFMRgMDWAUhPpYAvwf\n8Bmte31+/8uQBEdJMjMrKqW2o5A2N+YfWusePw9s6F5yltWRs6zxwKRE8RXtyEVgMBiaQ1/wQWgm\neWTe/jqtdU9Zz74htNavKaXuAw5KFB+glBqptZ6Tqj6RQlrYGDO90AfJWdY5wNeQFQwHIAmetk5U\neZFCsiCDwdALMQpCed5AphEC4NF12BHvUroqCAoJ7TwvVS+d++BdJEmPoe+xKRLnn0UeODwfhm3N\nRWAwGBqjLygIbwN/LbNdI1MGH6ReL/eWkMUmcDeSeGa7RNlRSqnzdZQoQym1C7Bzqt11NWReNKxb\nzEMU6GHAesBiYCaSLOviaF0Cg8HQi+kLCsLrWuvT2y1ET0ZrvUYp5SG57mO2Bz4LPB59/2ZRQ5P7\noM+SD8OLgYvbLYfBYOg+jJOiIeZKxOs8STJiIb28daC1/lf3imQwGAyGdmEUBAMA0XTKraniI5VS\nA6O0zOnloI31wGAwGNZhjIJgSHJp6vtgJBNlehW65cANLZHIYDAYDG3BKAiGtWitHwVeShV/muLF\nuW7VWi9qjVQGg8FgaAd9wUnRUBuXAn9IfN8f+FyqTo/OfbDnSX8brDpXnqEUe2ropzQvdtDx66ev\nGN9XolIMPQDb9T8LrBd4zsPtlsXQe7Bd/0BgQeA5QbtlMQqCIc21wC+ADaPv6WWu5wAPtVKgWtjt\n6Os2XG/9jhdQjIzTQ2rFF9bQecy4E2/I9UQlwXb9LYDDK1R7K/Cc21shj6FxbNcfDjwGKNv1Rwae\n83q7ZWo2tusPBb6ELGW9BbAICX99Gngs8Jx0hlZDBWzXHwvcC6ywXX9o4DlL2ymPURAMXdBaL1ZK\n+ZRItwz8Jc6N0BMZMFB9GxiZsWnzTjrPAH7QWomqYgTF/h9pngC6RUGwXf+XwC2B50ztjv57M7br\nXwJMCjxneo1NP0SWgAfJq7LOYLv+esAFwOkUL9wWM912/d0Dz+lsnWQ9A9v1BwEXAT8PPKfWVOML\nkPVt3kNWQm0rxgfBkEWph5UG/tJCOWqnQ40ttUkrvWcrRamWwHOmAZsguSfGAi9Hmy5AUhgPAz7f\nHfu2XX8k8ENgx+7ovzdju/4uwP9D/peaCDxnCbANsF3gOYubLVu7sF1/AHAHcBaiHDwI/C+ywNsE\n4KfA68BdfVE5iNgbOBmxqtRE4Dn/BoYDnwg8Z02zBasVY0EwFKG1fkEp9RTioJjkIa11zzaVarYq\nvVFt2TpBaiN6oCwB5tiu/2FUPCvwnLTTaLM5oJv77800dGwCz1kXHXkvRNKydwI/Cjzn1+kKtuuf\ni2TX7Ks0et681yxBGsUoCIZSBBQrCL0h98EbdW7rddiuPwoYFJu/bdffFNgL2BZ4DZgWKR5ZbRWS\n2+KIqGiE7fpjUtXmlrtZRdaHscBAYDowM/CckimWbdffFfgw8JzXou/9kfUccojV5MXAcz4s0XYw\nsCsymu8PzAVmBJ7zVqn9pdoPRVKF7wSsAN4EgqzRve36HcAoZDEqgO0zjs3rged8kGo3gOJ05ADT\ns0bTkan+k9HXGaVGjNFxykVf81nH2Hb9Ich/MRJJeT291H9fL7brj6CwANfPspQDgOi3rijRx/rI\nb8kB7wPPBZ4zL6Ne/JvfCDzn/WhufhvgvsBzVkR1dgN2AR4JPGduom38P7wXeM7c6FzfCfgU4iMR\nlFLebNfPIefXzMBzikz8tusPQyx676bljmTeCfhyVLST7frp//TV9Dluu/6GwOhUvTWB58zIkjFq\nsykyNfmfwHMW2K7fLzoWeyDTFPnAc14t1T7qYwNgT+Rc/wi5ZyT/N20UBEMRSqn+wNdTxQspTqTU\n41BaP6yVOipzGzzSYnG6m6uBz0YPv7OQm3dytc35tuuflHZutF3/BuArwEaJ4p/QNdU2wGnAH9M7\njRSTG5CbS5LQdv2joymTLKYBzwHjbNc/DomW2TCxfbXt+sMCz3k/sa8RwM+Qh/X6qf5W2q4/CTit\nzMN1OPB7CopQktW2658YeM5fEvXvRKZzBiXqZaWUPha4JlW2NfB8Rt0hiANfmjWIuf5jiLPfvVm/\nAfmvbgNmB54zKrkheuD+Bjg11Wap7fpnBJ5TybelFk4E+iE+Fb+ptbHt+scg//nGqfJbgeMDz1mY\nKN4UOZYX2q7fSWHhuNdt198ZOBc4MypbbLv+5wPPeTb6PiJqe5ft+ichx3WXRN8rbdc/OfCcrGis\nR4ChiHKSlSn2f4BzkOvitMRvCKJ9DEzU9TPafwGZlkkyBvExSrKUrtdnmoOBycA5tus/iCyat3li\nu7Zd/0rk2ihS1mzXPwT4E3LulaLTKAiGLL6EaMlJbtBat91pphIjFw289t9DVh4N7Jva9EKnWnpJ\nG0RqBXcjCa08YCqy/PJE4L8A33b9HQPPeTNR/0bgvujzGchoywPSq5U+nd6R7fp7IDfRjYDrEE99\nDeyHrPb5lO364xI36zTb2q4/AUnt/SSyLHQHMvJZklQOIhYChyCLid2FWBoGIQrDfwGnAP8Gskzd\nVvSbBgPvIMdpJjLHuxdikXgg1ewa4Jbo84+BHRAFI/3gT9/QQZa+/iIy9zwccVQrSeA5nbbrX4s8\ncI6htIIQK7xdFJLIAvEEYjmYBlyP/E4LeXj92Xb94YHnnFtOjhrYJ3q/vtbpE9v1fwqcjYxUL0GO\n51bI//g1YIzt+mNTSgJIivfNEIXgBOThfx0ySv8Nct7vgzgfj0+1HQf8E5iPKM/vI+vLHA9cZbs+\nJZSEevgDBZ++3yIKzgVI1FeSrCnD6ci0zebAJxCfjmoZjxzXPPBL5By0gW8jCt3sqHwtkYL1N0RB\n/R2ymOG2yHE5CLFAHAu8ahQEQxbfyijrDdML3HTTkWvUBRfsb88bPb6TzjEK+oHKL184YHL+Jmdl\nu+XrJsYBhwaec0dcYLu+hzw0dkcu/AvjbUmLgu36DqIgPB54zuQq9nURMgI8LjnyBibZrj8z2s/l\ntuvvVcJJbWvkXDoi8JzbKu0s8JxFtut/IqXgAPzOdv0/IiPnb5KhICAjpMGIEjAhPV1iu35HWsbA\nc25ObD8ZURAeqibENBqpTYnark8FBSHiakRBOMx2/cHpB29kSv4KooSlLRYnIcrBHcBXk7/Fdv0b\ngReAM23Xvz7wnJlVyFIJK3qfXUsj2/U/jiiiq4B9ktEytuv/Fhlpfx2xEnw31XxHYLfAc6bbrv8k\ncny/Bnwz8Jy/2q6/DTLd9F8Zu94ceXAeEHhOvM7MNbbrT0UU4vNs15+c2FY3gedcm/hNP0EUhDvL\nKMrJtkuIFPboWNWiIOSAO4HDE7/Dj67FSxEn21+m2pyOWDpuCDxn7fG2Xf8e4F1EIVsceE5oohgM\nXVBKjURuSEke11qXMhv3OPR553VO9Sb407yJZwTexO8FnnNV/qYj11XlAOCfSeUA1s4D/y36mp7f\nrAvb9fdGLDMzU8pBzMXIg2wsMhLKQiFhgxWVg5gM5SDm+uh9VHqD7fr7IlaNFcAJWb4UPcHLPvCc\n2YgVZn3gyIwq30AsQg8lQ+aiefUfR1/PzlB08sD9Udusfmsi2t+Q6GutjspnIE6L16dDaSN/irMR\np8dTbddPOxK/nwgxTZr8747e30SsEkNL7PvXGQrAVYgvwggK/gK9mXMyfuON0fvWkY9Dkp2i97uT\nhYHnLKNgURsDxknRUMz5FJ8XP22DHIbqSc9pxsSOW8ObtJ89ovdXbNcvpQC8j4zcdiTbnAoy4q2b\n6Ia3CYV5/Y0zqsXOf4/WEYveaq5GQuOOQaZekhydqJNkW2BLZF2UzhL/R+xE2XAIa+A52nb9lUho\nY63hd7GD5d1ZGwPPmW27/iuIUrkLXc/n9xP13rZd/yNgVezoF8n1NjAyyyKETGGl97fGdv1/IlaL\n3h7e+06WM2PgOQtt11+GTMcNo6vVJ1aWsyK+4nvFQjAKgiGBUmoshfnOmGe11vdl1Tf0GOaUKI9v\nlqrE9lqJb6b/Hb3KsU2Zba/VslPb9bdDfA0ORLz0N62iWWxVqMkc3iZuRuawP2u7/g6JKI9RSCTR\nYoodhOP/YgNKK2Ix2zZJzjnINMN2NbaL/4uiaIUE/0EUhB3pqiCkMwl2IscjXZZFJ/B2iW1xRtWR\nZWTqDcwps63U9f8Q0TSN7fp/iK0PtuvviPjmrCHyR1qrICilNkTmHjZDLsDke/JzOtwnzY1KqXcR\nR4cFiBab9XlRT87I19dQSo1GNPz0tJOxHvR8mhrOVobB0XuAOBiWo1zmwaqdXW3X3x+4BzGVv4SM\npN9AnPEg21McxMIArTs2dRN4zoe26/8NOA6xGJwfbYqV9b8GnrM81Sz+Lz5A0qOXo2y4Ww3MoT4F\nIY4+KTfNF58TaXN4VthstVNDitLKcdxvOjKmEj0tv0M953fs87E3Eul0B2JliBflOzfwnFcA+iul\n5iEmwQFNEBaK569L0amUWggcqrXO8gg2FKHG2CdW4Uem9XvBlUflu7RUaiByU3lPa92ZKN8cSav8\nfUQBTHKr1rrqueIsxp18/eg1azrLjSYB6NfRsY3G6Is9nDjDYxh4zundvbMotvtm5N50duA5P09t\nT0faJIkfirU+zNrF1YiC4FBQECZE73/JqB//F+u14r+ImIVEOB1iu/6ZNfhwzEamp7YuU2e7RN1m\noZBnW5YVIfZ1qNWfotw51yuIph++hET0bAkcikwpTAEuCjxnSly3PzIP0a8NcnYgD6SBlSoaIhQX\nV2UtVup24LBU6S7IyG+NUuodZP52awojrTSvIjeshlij9XdQKh2jXYRRDtpGfOCruQfEpmzbdn0V\ndP9iPLsgjnEryI67Lzd/PCt6/5Tt+v3LJXAqQ/z7ut2ZO/Ccx23XfxUYZbt+7D8xCngl8Jwsa80s\nZBS8ke36O7cg4ybA5UgegJ0QE/XN5auvZRaiIMQRF12wXX8TCv9ls6eERpOtIMR+EXNS5YsRh8dS\nTo/V+CzUck21i6uR5++4wHNeKFXJRDH0PfoR5fqmtHKwDDhCa73O5JA3lCSeF04v6Z3FPRTMzBO7\nS6AE8Q12PbomgYn5Xpm29yMPhu2RUK96iKMn9q6zfa38JXo/NHoly7oQeM5K4Iro60+jKINuJVJC\nYu/439uuX805A4VkWyfbrp+V/MdFnOmeCmpfFKsSRYMc2/U/DeyG3OfSjpOx5WmfVHmcuXFcFfus\n5ZpqObbrb45EtswqpxyAcVI0FDMbOFxrXeT9a+heopvnEArzolvashT0wmbEapdgGpIU5XDb9R9F\nYqrXQyyL8wPPeTeuGHjOMtv1T0VuqldH6W//jigNmyHOcAcBgwPPcZog20xkRLcJ8DPb9S9CRqM7\nI7kDvoDE1hfdxwLPWWy7/plI7oCLbNffHXm4zUQeRtsjGRP/EXhOKSfcABkpj49i5+9G/putgHmB\n5yxIN4iSF21C14V6Rtiu/yYSW15uHv4a4P8oKAedlPcvOAv4KmItfCTKKfAS8v8NR1ILHwnsl5GA\nqF7ORdYaGA48HGWyfBqYgViDRwCfQR72PkDgOU9E2TvHI4m0TgeeRczb30DyH2iKcyA0ymrgWNv1\nFyDK1NvIQ3tStN3LCH+9HUl29UPb9echFo8BUbtLEKfJtJ9EmgA59ifbrj8bCR3cGDlvZqdTLcPa\n9NCbUMhs2GFLKvPFSAKxZl7/CxEL8pgoA+QMxD+kE3HenAHcHnjOGmNB6Dt8iGjHWSFKK4HHkcQr\nOxvloLXYrr+77fqrEIejNyiE6P0WccZbabv+lFLtG+Ry5AY/BHkYfRDtR9M79QAAA3FJREFUcwYS\nNdCFwHPuQR6a7wHfAR5GMhk+i9xcT6G6KIOKBJ6zFBn9r0GUmBlIWN9ziHf/5yjO/phsfy2yqt4i\nxOJxF3INTI9k/Q7ZoV4xv0PC5LZCci4sQm6g08kYHdquvxy5lt4DwsSmF5EENCts1//Idv3M9LbR\negJTkEx4NvBA1joFifqLIjkeR6wcfwdeQfIFTEEcjHNU79RXkShvw95Ixs5+yOj/KuSh+ATiAHcq\nxb4fxwOXRfJMQc6zlxGF6B3g4MBznmmWnBFzEUfP0xDFcCGiAG+JHKushESXIdanjRBF4l3EInAj\n8vuOqWK/5yNRGaOQFNkfIhkOX6CQgwAA2/W3jkI3V0T7ejjatAFyXb2PXP/LbVnroWGi6bbzo697\nIlaWk5Br93wkk+jztusP7o94Lna7eaoMZU0cVXIAped7eqGZfP2ZqmPFl+ptrVf3n19UpvVMYLRS\nagBywxuEjLwWAe9qrTMXV2mUzs7OP/Xrp+6qt33Hmn5Zue3XNeZTOVoky5nqKiQkrNT883Qk3WvJ\nsMLAc1bbrv9Z5AH6KWRkOB+5wRWlWo7a3Ga7/mOIomAho/GFyI30YeDREru7ELlOq07TG3jONbbr\nT0Nu9KOQm+i/gFsDz3nTdv2fI2FbpdpfHnlpH4LcnHdAlIz5yLErqXgFnvOR7fo28lDYE7lu3kKO\nTdZ9K52xrhTl7knnUVB6SqVeTsr4apQU6lDEbG4hCsFbROsRBE1ebjrwnNB2/XGIGf5gRBkYhPyv\n7yDpjR9MtVkOnGK7/vWIgrFrVPcF4O8ZKbaXIuduWkH6KfL/JbkEsWClfWK2CDzHt10/jzhXjkGO\nyzNIZEiRD00ga3ocaLv+kUh2xo8hSuVURKmIUyiXVGYCWVzKQlJD7xrJNg85b9LX8RLgF6X6SpFU\n9Cpe28gaJgORqEEAbNffDLlvHIQoAndR8NEYilwjExD/nwuUiTQ0GAwGw7qC7fqjEUvKqsBzmhWd\nt05gu/5pyNoifw48J9N5PForxQeeNFMMBoPBYDD0DWL/llvK1In9jt4zCoLBYDAYDH2DePo5MwrJ\ndv0hFPwTnjBRDAaDwWAw9A3+hPgOHRPl25iK+FhsiuR4OARx0Lwf+J1REAwGg8Fg6AMEnvNklL78\nHMRRca/E5hVINNIVwI2B56z6/3ExLRI31pXNAAAAAElFTkSuQmCC\n", - "prompt_number": 6, - "text/plain": [ - "" - ] - } - ], - "prompt_number": 6 - } - ] -} diff --git a/IPython/nbformat/validator.py b/IPython/nbformat/validator.py index e0d486a..c3f00fc 100644 --- a/IPython/nbformat/validator.py +++ b/IPython/nbformat/validator.py @@ -1,112 +1,157 @@ +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + from __future__ import print_function import json import os +import warnings try: - from jsonschema import SchemaError - from jsonschema import Draft3Validator as Validator + from jsonschema import ValidationError + from jsonschema import Draft4Validator as Validator except ImportError as e: verbose_msg = """ - IPython depends on the jsonschema package: https://pypi.python.org/pypi/jsonschema + IPython notebook format depends on the jsonschema package: - Please install it first. - """ - raise ImportError(str(e) + verbose_msg) - -try: - import jsonpointer as jsonpointer -except ImportError as e: - verbose_msg = """ - - IPython depends on the jsonpointer package: https://pypi.python.org/pypi/jsonpointer + https://pypi.python.org/pypi/jsonschema Please install it first. """ raise ImportError(str(e) + verbose_msg) -from IPython.utils.py3compat import iteritems - +from IPython.utils.importstring import import_item -from .current import nbformat, nbformat_schema -schema_path = os.path.join( - os.path.dirname(__file__), "v%d" % nbformat, nbformat_schema) +validators = {} -def isvalid(nbjson): +def _relax_additional_properties(obj): + """relax any `additionalProperties`""" + if isinstance(obj, dict): + for key, value in obj.items(): + if key == 'additionalProperties': + value = True + else: + value = _relax_additional_properties(value) + obj[key] = value + elif isinstance(obj, list): + for i, value in enumerate(obj): + obj[i] = _relax_additional_properties(value) + return obj + +def _allow_undefined(schema): + schema['definitions']['cell']['oneOf'].append( + {"$ref": "#/definitions/unrecognized_cell"} + ) + schema['definitions']['output']['oneOf'].append( + {"$ref": "#/definitions/unrecognized_output"} + ) + return schema + +def get_validator(version=None, version_minor=None): + """Load the JSON schema into a Validator""" + if version is None: + from .. import current_nbformat + version = current_nbformat + + v = import_item("IPython.nbformat.v%s" % version) + current_minor = v.nbformat_minor + if version_minor is None: + version_minor = current_minor + + version_tuple = (version, version_minor) + + if version_tuple not in validators: + try: + v.nbformat_schema + except AttributeError: + # no validator + return None + schema_path = os.path.join(os.path.dirname(v.__file__), v.nbformat_schema) + with open(schema_path) as f: + schema_json = json.load(f) + + if current_minor < version_minor: + # notebook from the future, relax all `additionalProperties: False` requirements + schema_json = _relax_additional_properties(schema_json) + # and allow undefined cell types and outputs + schema_json = _allow_undefined(schema_json) + + validators[version_tuple] = Validator(schema_json) + return validators[version_tuple] + +def isvalid(nbjson, ref=None, version=None, version_minor=None): """Checks whether the given notebook JSON conforms to the current notebook format schema. Returns True if the JSON is valid, and False otherwise. To see the individual errors that were encountered, please use the `validate` function instead. - """ - - errors = validate(nbjson) - return errors == [] + try: + validate(nbjson, ref, version, version_minor) + except ValidationError: + return False + else: + return True -def validate(nbjson): - """Checks whether the given notebook JSON conforms to the current - notebook format schema, and returns the list of errors. +def better_validation_error(error, version, version_minor): + """Get better ValidationError on oneOf failures + oneOf errors aren't informative. + if it's a cell type or output_type error, + try validating directly based on the type for a better error message """ + key = error.schema_path[-1] + if key.endswith('Of'): + + ref = None + if isinstance(error.instance, dict): + if 'cell_type' in error.instance: + ref = error.instance['cell_type'] + "_cell" + elif 'output_type' in error.instance: + ref = error.instance['output_type'] + + if ref: + try: + validate(error.instance, + ref, + version=version, + version_minor=version_minor, + ) + except ValidationError as e: + return better_validation_error(e, version, version_minor) + except: + # if it fails for some reason, + # let the original error through + pass + + return error + + +def validate(nbjson, ref=None, version=None, version_minor=None): + """Checks whether the given notebook JSON conforms to the current + notebook format schema. - # load the schema file - with open(schema_path, 'r') as fh: - schema_json = json.load(fh) - - # resolve internal references - schema = resolve_ref(schema_json) - schema = jsonpointer.resolve_pointer(schema, '/notebook') - - # count how many errors there are - v = Validator(schema) - errors = list(v.iter_errors(nbjson)) - return errors - - -def resolve_ref(json, schema=None): - """Resolve internal references within the given JSON. This essentially - means that dictionaries of this form: - - {"$ref": "/somepointer"} - - will be replaced with the resolved reference to `/somepointer`. - This only supports local reference to the same JSON file. - + Raises ValidationError if not valid. """ + if version is None: + from .reader import get_version + (version, version_minor) = get_version(nbjson) - if not schema: - schema = json - - # if it's a list, resolve references for each item in the list - if type(json) is list: - resolved = [] - for item in json: - resolved.append(resolve_ref(item, schema=schema)) - - # if it's a dictionary, resolve references for each item in the - # dictionary - elif type(json) is dict: - resolved = {} - for key, ref in iteritems(json): - - # if the key is equal to $ref, then replace the entire - # dictionary with the resolved value - if key == '$ref': - if len(json) != 1: - raise SchemaError( - "objects containing a $ref should only have one item") - pointer = jsonpointer.resolve_pointer(schema, ref) - resolved = resolve_ref(pointer, schema=schema) + validator = get_validator(version, version_minor) - else: - resolved[key] = resolve_ref(ref, schema=schema) + if validator is None: + # no validator + warnings.warn("No schema for validating v%s notebooks" % version, UserWarning) + return - # otherwise it's a normal object, so just return it - else: - resolved = json + try: + if ref: + return validator.validate(nbjson, {'$ref' : '#/definitions/%s' % ref}) + else: + return validator.validate(nbjson) + except ValidationError as e: + raise better_validation_error(e, version, version_minor) - return resolved diff --git a/IPython/parallel/apps/baseapp.py b/IPython/parallel/apps/baseapp.py index fc06304..e538c04 100644 --- a/IPython/parallel/apps/baseapp.py +++ b/IPython/parallel/apps/baseapp.py @@ -1,32 +1,14 @@ # encoding: utf-8 """ The Base Application class for IPython.parallel apps - -Authors: - -* Brian Granger -* Min RK - """ -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The 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 logging import re import sys -from subprocess import Popen, PIPE - from IPython.config.application import catch_config_error, LevelFormatter from IPython.core import release from IPython.core.crashhandler import CrashHandler diff --git a/IPython/parallel/apps/ipclusterapp.py b/IPython/parallel/apps/ipclusterapp.py index 8777bad..f476f42 100755 --- a/IPython/parallel/apps/ipclusterapp.py +++ b/IPython/parallel/apps/ipclusterapp.py @@ -1,27 +1,8 @@ #!/usr/bin/env python # encoding: utf-8 -""" -The ipcluster application. - -Authors: - -* Brian Granger -* MinRK - -""" +"""The ipcluster application.""" from __future__ import print_function -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The 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 errno import logging import os @@ -30,9 +11,8 @@ import signal from subprocess import check_call, CalledProcessError, PIPE import zmq -from zmq.eventloop import ioloop -from IPython.config.application import Application, boolean_flag, catch_config_error +from IPython.config.application import catch_config_error from IPython.config.loader import Config from IPython.core.application import BaseIPythonApplication from IPython.core.profiledir import ProfileDir @@ -355,7 +335,7 @@ class IPClusterEngines(BaseParallelApplication): raise self.engine_launcher.on_stop(self.engines_stopped_early) if self.early_shutdown: - ioloop.DelayedCallback(self.engines_started_ok, self.early_shutdown*1000, self.loop).start() + self.loop.add_timeout(self.loop.time() + self.early_shutdown, self.engines_started_ok) def engines_stopped_early(self, r): if self.early_shutdown and not self._stopping: @@ -393,8 +373,7 @@ class IPClusterEngines(BaseParallelApplication): self.log.error("IPython cluster: stopping") self.stop_engines() # Wait a few seconds to let things shut down. - dc = ioloop.DelayedCallback(self.loop.stop, 3000, self.loop) - dc.start() + self.loop.add_timeout(self.loop.time() + 3, self.loop.stop) def sigint_handler(self, signum, frame): self.log.debug("SIGINT received, stopping launchers...") @@ -421,9 +400,8 @@ class IPClusterEngines(BaseParallelApplication): if self.daemonize: if os.name=='posix': daemonize() - - dc = ioloop.DelayedCallback(self.start_engines, 0, self.loop) - dc.start() + + self.loop.add_callback(self.start_engines) # Now write the new pid file AFTER our new forked pid is active. # self.write_pid_file() try: @@ -565,11 +543,11 @@ class IPClusterStart(IPClusterEngines): if self.daemonize: if os.name=='posix': daemonize() - - dc = ioloop.DelayedCallback(self.start_controller, 0, self.loop) - dc.start() - dc = ioloop.DelayedCallback(self.start_engines, 1000*self.delay, self.loop) - dc.start() + + def start(): + self.start_controller() + self.loop.add_timeout(self.loop.time() + self.delay, self.start_engines) + self.loop.add_callback(start) # Now write the new pid file AFTER our new forked pid is active. self.write_pid_file() try: diff --git a/IPython/parallel/apps/launcher.py b/IPython/parallel/apps/launcher.py index 8479c52..a36accc 100644 --- a/IPython/parallel/apps/launcher.py +++ b/IPython/parallel/apps/launcher.py @@ -294,8 +294,7 @@ class LocalProcessLauncher(BaseLauncher): except Exception: self.log.debug("interrupt failed") pass - self.killer = ioloop.DelayedCallback(lambda : self.signal(SIGKILL), delay*1000, self.loop) - self.killer.start() + self.killer = self.loop.add_timeout(self.loop.time() + delay, lambda : self.signal(SIGKILL)) # callbacks, etc: diff --git a/IPython/parallel/client/client.py b/IPython/parallel/client/client.py index 4aa137a..44c6d1a 100644 --- a/IPython/parallel/client/client.py +++ b/IPython/parallel/client/client.py @@ -27,7 +27,7 @@ from IPython.utils.capture import RichOutput from IPython.utils.coloransi import TermColors from IPython.utils.jsonutil import rekey, extract_dates, parse_date from IPython.utils.localinterfaces import localhost, is_local_ip -from IPython.utils.path import get_ipython_dir +from IPython.utils.path import get_ipython_dir, compress_user from IPython.utils.py3compat import cast_bytes, string_types, xrange, iteritems from IPython.utils.traitlets import (HasTraits, Integer, Instance, Unicode, Dict, List, Bool, Set, Any) @@ -47,6 +47,7 @@ from .view import DirectView, LoadBalancedView # Decorators for Client methods #-------------------------------------------------------------------------- + @decorator def spin_first(f, self, *args, **kwargs): """Call spin() to sync state prior to calling the method.""" @@ -58,6 +59,10 @@ def spin_first(f, self, *args, **kwargs): # Classes #-------------------------------------------------------------------------- +_no_connection_file_msg = """ +Failed to connect because no Controller could be found. +Please double-check your profile and ensure that a cluster is running. +""" class ExecuteReply(RichOutput): """wrapper for finished Execute results""" @@ -382,6 +387,11 @@ class Client(HasTraits): self._setup_profile_dir(self.profile, profile_dir, ipython_dir) + no_file_msg = '\n'.join([ + "You have attempted to connect to an IPython Cluster but no Controller could be found.", + "Please double-check your configuration and ensure that a cluster is running.", + ]) + if self._cd is not None: if url_file is None: if not cluster_id: @@ -389,10 +399,19 @@ class Client(HasTraits): else: client_json = 'ipcontroller-%s-client.json' % cluster_id url_file = pjoin(self._cd.security_dir, client_json) + if not os.path.exists(url_file): + msg = '\n'.join([ + "Connection file %r not found." % compress_user(url_file), + no_file_msg, + ]) + raise IOError(msg) if url_file is None: - raise ValueError( - "I can't find enough information to connect to a hub!" - " Please specify at least one of url_file or profile." + raise IOError(no_file_msg) + + if not os.path.exists(url_file): + # Connection file explicitly specified, but not found + raise IOError("Connection file %r not found. Is a controller running?" % \ + compress_user(url_file) ) with open(url_file) as f: @@ -782,7 +801,7 @@ class Client(HasTraits): # construct result: if content['status'] == 'ok': - self.results[msg_id] = serialize.unserialize_object(msg['buffers'])[0] + self.results[msg_id] = serialize.deserialize_object(msg['buffers'])[0] elif content['status'] == 'aborted': self.results[msg_id] = error.TaskAborted(msg_id) elif content['status'] == 'resubmitted': @@ -874,7 +893,7 @@ class Client(HasTraits): if msg_type == 'stream': name = content['name'] s = md[name] or '' - md[name] = s + content['data'] + md[name] = s + content['text'] elif msg_type == 'error': md.update({'error' : self._unwrap_exception(content)}) elif msg_type == 'execute_input': @@ -884,7 +903,7 @@ class Client(HasTraits): elif msg_type == 'execute_result': md['execute_result'] = content elif msg_type == 'data_message': - data, remainder = serialize.unserialize_object(msg['buffers']) + data, remainder = serialize.deserialize_object(msg['buffers']) md['data'].update(data) elif msg_type == 'status': # idle message comes after all outputs @@ -1593,7 +1612,7 @@ class Client(HasTraits): if rcontent['status'] == 'ok': if header['msg_type'] == 'apply_reply': - res,buffers = serialize.unserialize_object(buffers) + res,buffers = serialize.deserialize_object(buffers) elif header['msg_type'] == 'execute_reply': res = ExecuteReply(msg_id, rcontent, md) else: diff --git a/IPython/parallel/controller/hub.py b/IPython/parallel/controller/hub.py index 40e55cc..a20f4d9 100644 --- a/IPython/parallel/controller/hub.py +++ b/IPython/parallel/controller/hub.py @@ -16,7 +16,6 @@ import time from datetime import datetime import zmq -from zmq.eventloop import ioloop from zmq.eventloop.zmqstream import ZMQStream # internal: @@ -25,7 +24,7 @@ from IPython.utils.jsonutil import extract_dates from IPython.utils.localinterfaces import localhost from IPython.utils.py3compat import cast_bytes, unicode_type, iteritems from IPython.utils.traitlets import ( - HasTraits, Instance, Integer, Unicode, Dict, Set, Tuple, CBytes, DottedObjectName + HasTraits, Any, Instance, Integer, Unicode, Dict, Set, Tuple, DottedObjectName ) from IPython.parallel import error, util @@ -35,9 +34,6 @@ from IPython.kernel.zmq.session import SessionFactory from .heartmonitor import HeartMonitor -#----------------------------------------------------------------------------- -# Code -#----------------------------------------------------------------------------- def _passer(*args, **kwargs): return @@ -108,13 +104,13 @@ class EngineConnector(HasTraits): id (int): engine ID uuid (unicode): engine UUID pending: set of msg_ids - stallback: DelayedCallback for stalled registration + stallback: tornado timeout for stalled registration """ id = Integer(0) uuid = Unicode() pending = Set() - stallback = Instance(ioloop.DelayedCallback) + stallback = Any() _db_shortcuts = { @@ -339,13 +335,10 @@ class HubFactory(RegistrationFactory): url = util.disambiguate_url(self.client_url('task')) r.connect(url) - # convert seconds to msec - registration_timeout = 1000*self.registration_timeout - self.hub = Hub(loop=loop, session=self.session, monitor=sub, heartmonitor=self.heartmonitor, query=q, notifier=n, resubmit=r, db=self.db, engine_info=self.engine_info, client_info=self.client_info, - log=self.log, registration_timeout=registration_timeout) + log=self.log, registration_timeout=self.registration_timeout) class Hub(SessionFactory): @@ -530,7 +523,7 @@ class Hub(SessionFactory): return client_id = idents[0] try: - msg = self.session.unserialize(msg, content=True) + msg = self.session.deserialize(msg, content=True) except Exception: content = error.wrap_exception() self.log.error("Bad Query Message: %r", msg, exc_info=True) @@ -595,7 +588,7 @@ class Hub(SessionFactory): return queue_id, client_id = idents[:2] try: - msg = self.session.unserialize(msg) + msg = self.session.deserialize(msg) except Exception: self.log.error("queue::client %r sent invalid message to %r: %r", client_id, queue_id, msg, exc_info=True) return @@ -643,7 +636,7 @@ class Hub(SessionFactory): client_id, queue_id = idents[:2] try: - msg = self.session.unserialize(msg) + msg = self.session.deserialize(msg) except Exception: self.log.error("queue::engine %r sent invalid message to %r: %r", queue_id, client_id, msg, exc_info=True) @@ -697,7 +690,7 @@ class Hub(SessionFactory): client_id = idents[0] try: - msg = self.session.unserialize(msg) + msg = self.session.deserialize(msg) except Exception: self.log.error("task::client %r sent invalid task message: %r", client_id, msg, exc_info=True) @@ -747,7 +740,7 @@ class Hub(SessionFactory): """save the result of a completed task.""" client_id = idents[0] try: - msg = self.session.unserialize(msg) + msg = self.session.deserialize(msg) except Exception: self.log.error("task::invalid task result message send to %r: %r", client_id, msg, exc_info=True) @@ -801,7 +794,7 @@ class Hub(SessionFactory): def save_task_destination(self, idents, msg): try: - msg = self.session.unserialize(msg, content=True) + msg = self.session.deserialize(msg, content=True) except Exception: self.log.error("task::invalid task tracking message", exc_info=True) return @@ -838,7 +831,7 @@ class Hub(SessionFactory): """save an iopub message into the db""" # print (topics) try: - msg = self.session.unserialize(msg, content=True) + msg = self.session.deserialize(msg, content=True) except Exception: self.log.error("iopub::invalid IOPub message", exc_info=True) return @@ -862,7 +855,7 @@ class Hub(SessionFactory): if msg_type == 'stream': name = content['name'] s = '' if rec is None else rec[name] - d[name] = s + content['data'] + d[name] = s + content['text'] elif msg_type == 'error': d['error'] = content @@ -963,9 +956,11 @@ class Hub(SessionFactory): self.finish_registration(heart) else: purge = lambda : self._purge_stalled_registration(heart) - dc = ioloop.DelayedCallback(purge, self.registration_timeout, self.loop) - dc.start() - self.incoming_registrations[heart] = EngineConnector(id=eid,uuid=uuid,stallback=dc) + t = self.loop.add_timeout( + self.loop.time() + self.registration_timeout, + purge, + ) + self.incoming_registrations[heart] = EngineConnector(id=eid,uuid=uuid,stallback=t) else: self.log.error("registration::registration %i failed: %r", eid, content['evalue']) @@ -979,20 +974,15 @@ class Hub(SessionFactory): self.log.error("registration::bad engine id for unregistration: %r", ident, exc_info=True) return self.log.info("registration::unregister_engine(%r)", eid) - # print (eid) + uuid = self.keytable[eid] content=dict(id=eid, uuid=uuid) self.dead_engines.add(uuid) - # self.ids.remove(eid) - # uuid = self.keytable.pop(eid) - # - # ec = self.engines.pop(eid) - # self.hearts.pop(ec.heartbeat) - # self.by_ident.pop(ec.queue) - # self.completed.pop(eid) - handleit = lambda : self._handle_stranded_msgs(eid, uuid) - dc = ioloop.DelayedCallback(handleit, self.registration_timeout, self.loop) - dc.start() + + self.loop.add_timeout( + self.loop.time() + self.registration_timeout, + lambda : self._handle_stranded_msgs(eid, uuid), + ) ############## TODO: HANDLE IT ################ self._save_engine_state() @@ -1040,7 +1030,7 @@ class Hub(SessionFactory): return self.log.info("registration::finished registering engine %i:%s", ec.id, ec.uuid) if ec.stallback is not None: - ec.stallback.stop() + self.loop.remove_timeout(ec.stallback) eid = ec.id self.ids.add(eid) self.keytable[eid] = ec.uuid @@ -1133,8 +1123,7 @@ class Hub(SessionFactory): self.session.send(self.query, 'shutdown_reply', content={'status': 'ok'}, ident=client_id) # also notify other clients of shutdown self.session.send(self.notifier, 'shutdown_notice', content={'status': 'ok'}) - dc = ioloop.DelayedCallback(lambda : self._shutdown(), 1000, self.loop) - dc.start() + self.loop.add_timeout(self.loop.time() + 1, self._shutdown) def _shutdown(self): self.log.info("hub::hub shutting down.") diff --git a/IPython/parallel/controller/scheduler.py b/IPython/parallel/controller/scheduler.py index 1894b93..4f13a1b 100644 --- a/IPython/parallel/controller/scheduler.py +++ b/IPython/parallel/controller/scheduler.py @@ -251,7 +251,7 @@ class TaskScheduler(SessionFactory): self.log.warn("task::Invalid Message: %r",msg) return try: - msg = self.session.unserialize(msg) + msg = self.session.deserialize(msg) except ValueError: self.log.warn("task::Unauthorized message from: %r"%idents) return @@ -270,7 +270,7 @@ class TaskScheduler(SessionFactory): self.log.warn("task::Invalid Message: %r",msg) return try: - msg = self.session.unserialize(msg) + msg = self.session.deserialize(msg) except ValueError: self.log.warn("task::Unauthorized message from: %r"%idents) return @@ -321,8 +321,9 @@ class TaskScheduler(SessionFactory): # wait 5 seconds before cleaning up pending jobs, since the results might # still be incoming if self.pending[uid]: - dc = ioloop.DelayedCallback(lambda : self.handle_stranded_tasks(uid), 5000, self.loop) - dc.start() + self.loop.add_timeout(self.loop.time() + 5, + lambda : self.handle_stranded_tasks(uid), + ) else: self.completed.pop(uid) self.failed.pop(uid) @@ -374,7 +375,7 @@ class TaskScheduler(SessionFactory): self.notifier_stream.flush() try: idents, msg = self.session.feed_identities(raw_msg, copy=False) - msg = self.session.unserialize(msg, content=False, copy=False) + msg = self.session.deserialize(msg, content=False, copy=False) except Exception: self.log.error("task::Invaid task msg: %r"%raw_msg, exc_info=True) return @@ -620,7 +621,7 @@ class TaskScheduler(SessionFactory): """dispatch method for result replies""" try: idents,msg = self.session.feed_identities(raw_msg, copy=False) - msg = self.session.unserialize(msg, content=False, copy=False) + msg = self.session.deserialize(msg, content=False, copy=False) engine = idents[0] try: idx = self.targets.index(engine) diff --git a/IPython/parallel/engine/engine.py b/IPython/parallel/engine/engine.py index 326a690..32146b4 100644 --- a/IPython/parallel/engine/engine.py +++ b/IPython/parallel/engine/engine.py @@ -25,7 +25,6 @@ from IPython.parallel.controller.heartmonitor import Heart from IPython.parallel.factory import RegistrationFactory from IPython.parallel.util import disambiguate_url -from IPython.kernel.zmq.session import Message from IPython.kernel.zmq.ipkernel import IPythonKernel as Kernel from IPython.kernel.zmq.kernelapp import IPKernelApp @@ -155,12 +154,12 @@ class EngineFactory(RegistrationFactory): def complete_registration(self, msg, connect, maybe_tunnel): # print msg - self._abort_dc.stop() + self.loop.remove_timeout(self._abort_timeout) ctx = self.context loop = self.loop identity = self.bident idents,msg = self.session.feed_identities(msg) - msg = self.session.unserialize(msg) + msg = self.session.deserialize(msg) content = msg['content'] info = self.connection_info @@ -293,9 +292,10 @@ class EngineFactory(RegistrationFactory): def start(self): - dc = ioloop.DelayedCallback(self.register, 0, self.loop) - dc.start() - self._abort_dc = ioloop.DelayedCallback(self.abort, self.timeout*1000, self.loop) - self._abort_dc.start() + loop = self.loop + def _start(): + self.register() + self._abort_timeout = loop.add_timeout(loop.time() + self.timeout, self.abort) + self.loop.add_callback(_start) diff --git a/IPython/qt/base_frontend_mixin.py b/IPython/qt/base_frontend_mixin.py index d03be9b..d317a9c 100644 --- a/IPython/qt/base_frontend_mixin.py +++ b/IPython/qt/base_frontend_mixin.py @@ -136,15 +136,23 @@ class BaseFrontendMixin(object): handler = getattr(self, '_handle_' + msg_type, None) if handler: handler(msg) - - def _is_from_this_session(self, msg): - """ Returns whether a reply from the kernel originated from a request - from this frontend. - """ - session = self._kernel_client.session.session - parent = msg['parent_header'] - if not parent: - # if the message has no parent, assume it is meant for all frontends + + def from_here(self, msg): + """Return whether a message is from this session""" + session_id = self._kernel_client.session.session + return msg['parent_header'].get("session", session_id) == session_id + + def include_output(self, msg): + """Return whether we should include a given output message""" + if self._hidden: + return False + from_here = self.from_here(msg) + if msg['msg_type'] == 'execute_input': + # only echo inputs not from here + return self.include_other_output and not from_here + + if self.include_other_output: return True else: - return parent.get('session') == session + return from_here + diff --git a/IPython/qt/console/call_tip_widget.py b/IPython/qt/console/call_tip_widget.py index 378a138..653094e 100644 --- a/IPython/qt/console/call_tip_widget.py +++ b/IPython/qt/console/call_tip_widget.py @@ -1,6 +1,5 @@ # Standard library imports import re -import textwrap from unicodedata import category # System library imports @@ -37,6 +36,7 @@ class CallTipWidget(QtGui.QLabel): QtGui.QStyle.PM_ToolTipLabelFrameWidth, None, self)) self.setWindowOpacity(self.style().styleHint( QtGui.QStyle.SH_ToolTipLabel_Opacity, None, self, None) / 255.0) + self.setWordWrap(True) def eventFilter(self, obj, event): """ Reimplemented to hide on certain key presses and on text edit focus @@ -189,8 +189,9 @@ class CallTipWidget(QtGui.QLabel): horizontal = 'Left' pos = getattr(cursor_rect, '%s%s' %(vertical, horizontal)) point = text_edit.mapToGlobal(pos()) + point.setY(point.y() + padding) if vertical == 'top': - point.setY(point.y() - tip_height - padding) + point.setY(point.y() - tip_height) if horizontal == 'Left': point.setX(point.x() - tip_width - padding) @@ -244,16 +245,6 @@ class CallTipWidget(QtGui.QLabel): def _format_tooltip(self, doc): doc = re.sub(r'\033\[(\d|;)+?m', '', doc) - - # make sure a long argument list does not make - # the first row overflow the width of the actual tip body - rows = doc.split("\n") - # An object which is not a callable has '' as doc - if len(rows) == 1: - return doc - max_text_width = max(80, max([len(x) for x in rows[1:]])) - rows= textwrap.wrap(rows[0],max_text_width) + rows[1:] - doc = "\n".join(rows) return doc #------ Signal handlers ---------------------------------------------------- diff --git a/IPython/qt/console/completion_lexer.py b/IPython/qt/console/completion_lexer.py deleted file mode 100644 index 15be441..0000000 --- a/IPython/qt/console/completion_lexer.py +++ /dev/null @@ -1,79 +0,0 @@ -# System library imports -from pygments.token import Token, is_token_subtype - - -class CompletionLexer(object): - """ Uses Pygments and some auxillary information to lex code snippets for - symbol contexts. - """ - - # Maps Lexer names to a list of possible name separators - separator_map = { 'C' : [ '.', '->' ], - 'C++' : [ '.', '->', '::' ], - 'Python' : [ '.' ] } - - def __init__(self, lexer): - """ Create a CompletionLexer using the specified Pygments lexer. - """ - self.lexer = lexer - - def get_context(self, string): - """ Assuming the cursor is at the end of the specified string, get the - context (a list of names) for the symbol at cursor position. - """ - context = [] - reversed_tokens = list(self._lexer.get_tokens(string)) - reversed_tokens.reverse() - - # Pygments often tacks on a newline when none is specified in the input. - # Remove this newline. - if reversed_tokens and reversed_tokens[0][1].endswith('\n') and \ - not string.endswith('\n'): - reversed_tokens.pop(0) - - current_op = '' - for token, text in reversed_tokens: - - if is_token_subtype(token, Token.Name): - - # Handle a trailing separator, e.g 'foo.bar.' - if current_op in self._name_separators: - if not context: - context.insert(0, '') - - # Handle non-separator operators and punction. - elif current_op: - break - - context.insert(0, text) - current_op = '' - - # Pygments doesn't understand that, e.g., '->' is a single operator - # in C++. This is why we have to build up an operator from - # potentially several tokens. - elif token is Token.Operator or token is Token.Punctuation: - # Handle a trailing separator, e.g 'foo.bar.' - if current_op in self._name_separators: - if not context: - context.insert(0, '') - else: - current_op = text + current_op - - # Break on anything that is not a Operator, Punctuation, or Name. - else: - break - - return context - - def get_lexer(self, lexer): - return self._lexer - - def set_lexer(self, lexer, name_separators=None): - self._lexer = lexer - if name_separators is None: - self._name_separators = self.separator_map.get(lexer.name, ['.']) - else: - self._name_separators = list(name_separators) - - lexer = property(get_lexer, set_lexer) - diff --git a/IPython/qt/console/console_widget.py b/IPython/qt/console/console_widget.py index a988bda..f8794df 100644 --- a/IPython/qt/console/console_widget.py +++ b/IPython/qt/console/console_widget.py @@ -519,7 +519,15 @@ class ConsoleWidget(MetaQObjectHasTraits('NewBase', (LoggingConfigurable, QtGui. #--------------------------------------------------------------------------- # 'ConsoleWidget' public interface #--------------------------------------------------------------------------- - + + include_other_output = Bool(False, config=True, + help="""Whether to include output from clients + other than this one sharing the same kernel. + + Outputs are not displayed until enter is pressed. + """ + ) + def can_copy(self): """ Returns whether text can be copied to the clipboard. """ @@ -1490,6 +1498,22 @@ class ConsoleWidget(MetaQObjectHasTraits('NewBase', (LoggingConfigurable, QtGui. QtGui.qApp.sendEvent(self._page_control, new_event) return True + # vi/less -like key bindings + elif key == QtCore.Qt.Key_J: + new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, + QtCore.Qt.Key_Down, + QtCore.Qt.NoModifier) + QtGui.qApp.sendEvent(self._page_control, new_event) + return True + + # vi/less -like key bindings + elif key == QtCore.Qt.Key_K: + new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, + QtCore.Qt.Key_Up, + QtCore.Qt.NoModifier) + QtGui.qApp.sendEvent(self._page_control, new_event) + return True + return False def _on_flush_pending_stream_timer(self): diff --git a/IPython/qt/console/frontend_widget.py b/IPython/qt/console/frontend_widget.py index ef5137c..0c0ea0a 100644 --- a/IPython/qt/console/frontend_widget.py +++ b/IPython/qt/console/frontend_widget.py @@ -21,7 +21,6 @@ from IPython.qt.base_frontend_mixin import BaseFrontendMixin from IPython.utils.traitlets import Any, Bool, Instance, Unicode, DottedObjectName from .bracket_matcher import BracketMatcher from .call_tip_widget import CallTipWidget -from .completion_lexer import CompletionLexer from .history_console_widget import HistoryConsoleWidget from .pygments_highlighter import PygmentsHighlighter @@ -163,7 +162,6 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): # FrontendWidget protected variables. self._bracket_matcher = BracketMatcher(self._control) self._call_tip_widget = CallTipWidget(self._control) - self._completion_lexer = CompletionLexer(self.lexer) self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None) self._hidden = False self._highlighter = FrontendHighlighter(self, lexer=self.lexer) @@ -215,7 +213,10 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): elif self._control.hasFocus(): text = self._control.textCursor().selection().toPlainText() if text: + was_newline = text[-1] == '\n' text = self._prompt_transformer.transform_cell(text) + if not was_newline: # user doesn't need newline + text = text[:-1] QtGui.QApplication.clipboard().setText(text) else: self.log.debug("frontend widget : unknown copy target") @@ -347,25 +348,13 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): #--------------------------------------------------------------------------- def _handle_clear_output(self, msg): """Handle clear output messages.""" - if not self._hidden and self._is_from_this_session(msg): + if include_output(msg): wait = msg['content'].get('wait', True) if wait: self._pending_clearoutput = True else: self.clear_output() - def _handle_complete_reply(self, rep): - """ Handle replies for tab completion. - """ - self.log.debug("complete: %s", rep.get('content', '')) - cursor = self._get_cursor() - info = self._request_info.get('complete') - if info and info.id == rep['parent_header']['msg_id'] and \ - info.pos == cursor.position(): - text = '.'.join(self._get_context()) - cursor.movePosition(QtGui.QTextCursor.Left, n=len(text)) - self._complete_with_items(cursor, rep['content']['matches']) - def _silent_exec_callback(self, expr, callback): """Silently execute `expr` in the kernel and call `callback` with reply @@ -513,14 +502,14 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): if info and info.id == rep['parent_header']['msg_id'] and \ info.pos == cursor.position(): content = rep['content'] - if content.get('status') == 'ok': + if content.get('status') == 'ok' and content.get('found', False): self._call_tip_widget.show_inspect_data(content) def _handle_execute_result(self, msg): """ Handle display hook output. """ self.log.debug("execute_result: %s", msg.get('content', '')) - if not self._hidden and self._is_from_this_session(msg): + if self.include_output(msg): self.flush_clearoutput() text = msg['content']['data'] self._append_plain_text(text + '\n', before_prompt=True) @@ -529,16 +518,16 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): """ Handle stdout, stderr, and stdin. """ self.log.debug("stream: %s", msg.get('content', '')) - if not self._hidden and self._is_from_this_session(msg): + if self.include_output(msg): self.flush_clearoutput() - self.append_stream(msg['content']['data']) + self.append_stream(msg['content']['text']) def _handle_shutdown_reply(self, msg): """ Handle shutdown signal, only if from other console. """ self.log.info("shutdown: %s", msg.get('content', '')) restart = msg.get('content', {}).get('restart', False) - if not self._hidden and not self._is_from_this_session(msg): + if not self._hidden and not self.from_here(msg): # got shutdown reply, request came from session other than ours if restart: # someone restarted the kernel, handle it @@ -626,10 +615,11 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): if clear: self._control.clear() - self._append_plain_text(self.banner) - if self.kernel_banner: - self._append_plain_text(self.kernel_banner) - + if self._display_banner: + self._append_plain_text(self.banner) + if self.kernel_banner: + self._append_plain_text(self.kernel_banner) + # update output marker for stdout/stderr, so that startup # messages appear after banner: self._append_before_prompt_pos = self._get_cursor().position() @@ -711,10 +701,20 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): #--------------------------------------------------------------------------- # 'FrontendWidget' protected interface #--------------------------------------------------------------------------- - - def _call_tip(self): - """ Shows a call tip, if appropriate, at the current cursor location. + + def _auto_call_tip(self): + """Trigger call tip automatically on open parenthesis + + Call tips can be requested explcitly with `_call_tip`. """ + cursor = self._get_cursor() + cursor.movePosition(QtGui.QTextCursor.Left) + if cursor.document().characterAt(cursor.position()) == '(': + # trigger auto call tip on open paren + self._call_tip() + + def _call_tip(self): + """Shows a call tip, if appropriate, at the current cursor location.""" # Decide if it makes sense to show a call tip if not self.enable_calltips or not self.kernel_client.shell_channel.is_alive(): return False @@ -729,27 +729,14 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): def _complete(self): """ Performs completion at the current cursor location. """ - context = self._get_context() - if context: - # Send the completion request to the kernel - msg_id = self.kernel_client.complete( - code=self.input_buffer, - cursor_pos=self._get_input_buffer_cursor_pos(), - ) - pos = self._get_cursor().position() - info = self._CompletionRequest(msg_id, pos) - self._request_info['complete'] = info - - def _get_context(self, cursor=None): - """ Gets the context for the specified cursor (or the current cursor - if none is specified). - """ - if cursor is None: - cursor = self._get_cursor() - cursor.movePosition(QtGui.QTextCursor.StartOfBlock, - QtGui.QTextCursor.KeepAnchor) - text = cursor.selection().toPlainText() - return self._completion_lexer.get_context(text) + # Send the completion request to the kernel + msg_id = self.kernel_client.complete( + code=self.input_buffer, + cursor_pos=self._get_input_buffer_cursor_pos(), + ) + pos = self._get_cursor().position() + info = self._CompletionRequest(msg_id, pos) + self._request_info['complete'] = info def _process_execute_abort(self, msg): """ Process a reply for an aborted execution request. @@ -809,7 +796,7 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin): document = self._control.document() if position == self._get_cursor().position(): - self._call_tip() + self._auto_call_tip() #------ Trait default initializers ----------------------------------------- diff --git a/IPython/qt/console/ipython_widget.py b/IPython/qt/console/ipython_widget.py index d145330..6f3da94 100644 --- a/IPython/qt/console/ipython_widget.py +++ b/IPython/qt/console/ipython_widget.py @@ -103,7 +103,7 @@ class IPythonWidget(FrontendWidget): # IPythonWidget protected class variables. _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number']) - _payload_source_edit = 'edit_magic' + _payload_source_edit = 'edit' _payload_source_exit = 'ask_exit' _payload_source_next_input = 'set_next_input' _payload_source_page = 'page' @@ -151,8 +151,23 @@ class IPythonWidget(FrontendWidget): start = content['cursor_start'] end = content['cursor_end'] + start = max(start, 0) + end = max(end, start) + + # Move the control's cursor to the desired end point + cursor_pos = self._get_input_buffer_cursor_pos() + if end < cursor_pos: + cursor.movePosition(QtGui.QTextCursor.Left, + n=(cursor_pos - end)) + elif end > cursor_pos: + cursor.movePosition(QtGui.QTextCursor.Right, + n=(end - cursor_pos)) + # This line actually applies the move to control's cursor + self._control.setTextCursor(cursor) + offset = end - start - # Move the cursor to the start of the match and complete. + # Move the local cursor object to the start of the match and + # complete. cursor.movePosition(QtGui.QTextCursor.Left, n=offset) self._complete_with_items(cursor, matches) @@ -204,12 +219,30 @@ class IPythonWidget(FrontendWidget): items.append(cell) last_cell = cell self._set_history(items) + + def _insert_other_input(self, cursor, content): + """Insert function for input from other frontends""" + cursor.beginEditBlock() + start = cursor.position() + n = content.get('execution_count', 0) + cursor.insertText('\n') + self._insert_html(cursor, self._make_in_prompt(n)) + cursor.insertText(content['code']) + self._highlighter.rehighlightBlock(cursor.block()) + cursor.endEditBlock() + + def _handle_execute_input(self, msg): + """Handle an execute_input message""" + self.log.debug("execute_input: %s", msg.get('content', '')) + if self.include_output(msg): + self._append_custom(self._insert_other_input, msg['content'], before_prompt=True) + def _handle_execute_result(self, msg): """ Reimplemented for IPython-style "display hook". """ self.log.debug("execute_result: %s", msg.get('content', '')) - if not self._hidden and self._is_from_this_session(msg): + if self.include_output(msg): self.flush_clearoutput() content = msg['content'] prompt_number = content.get('execution_count', 0) @@ -231,7 +264,7 @@ class IPythonWidget(FrontendWidget): # For now, we don't display data from other frontends, but we # eventually will as this allows all frontends to monitor the display # data. But we need to figure out how to handle this in the GUI. - if not self._hidden and self._is_from_this_session(msg): + if self.include_output(msg): self.flush_clearoutput() data = msg['content']['data'] metadata = msg['content']['metadata'] diff --git a/IPython/qt/console/magic_helper.py b/IPython/qt/console/magic_helper.py index 7b83b7a..d599b50 100644 --- a/IPython/qt/console/magic_helper.py +++ b/IPython/qt/console/magic_helper.py @@ -116,7 +116,7 @@ class MagicHelper(QtGui.QDockWidget): instance is expected to invoke populate_magic_helper() when magic info is available. """ - if not visible or self.data != None: + if not visible or self.data is not None: return self.data = {} self.search_class.clear() @@ -178,14 +178,14 @@ class MagicHelper(QtGui.QDockWidget): """Emit pasteRequested signal with currently selected item text """ text = self._get_current_search_item(item) - if text != None: + if text is not None: self.pasteRequested.emit(text) def run_requested(self, item = None): """Emit runRequested signal with currently selected item text """ text = self._get_current_search_item(item) - if text != None: + if text is not None: self.runRequested.emit(text) def filter_magic_helper(self, regex, cls): @@ -193,9 +193,9 @@ class MagicHelper(QtGui.QDockWidget): regex and class match cls. If cls equals 'any' - any class matches. """ - if regex == "" or regex == None: + if regex == "" or regex is None: regex = '.' - if cls == None: + if cls is None: cls = 'any' self.search_list.clear() diff --git a/IPython/qt/console/mainwindow.py b/IPython/qt/console/mainwindow.py index b48a492..6a04542 100644 --- a/IPython/qt/console/mainwindow.py +++ b/IPython/qt/console/mainwindow.py @@ -144,7 +144,7 @@ class MainWindow(QtGui.QMainWindow): # of this is when 'exit' is sent in a slave tab. 'exit' will be # re-sent by this function on the master widget, which ask all slave # widgets to exit - if closing_widget==None: + if closing_widget is None: return #get a list of all slave widgets on the same kernel. @@ -205,7 +205,7 @@ class MainWindow(QtGui.QMainWindow): for slave in slave_tabs: background(slave.kernel_client.stop_channels) self.tab_widget.removeTab(self.tab_widget.indexOf(slave)) - closing_widget.execute("exit") + kernel_manager.shutdown_kernel() self.tab_widget.removeTab(current_tab) background(kernel_client.stop_channels) elif reply == 0: # close Console @@ -719,12 +719,12 @@ class MainWindow(QtGui.QMainWindow): QtCore.QTimer.singleShot(200, self.active_frontend._control.setFocus) def magic_helper_paste_requested(self, text = None): - if text != None: + if text is not None: self.active_frontend.input_buffer = text self._set_active_frontend_focus() def magic_helper_run_requested(self, text = None): - if text != None: + if text is not None: self.active_frontend.execute(text) self._set_active_frontend_focus() diff --git a/IPython/qt/console/pygments_highlighter.py b/IPython/qt/console/pygments_highlighter.py index 4044be7..35b6255 100644 --- a/IPython/qt/console/pygments_highlighter.py +++ b/IPython/qt/console/pygments_highlighter.py @@ -12,6 +12,11 @@ def get_tokens_unprocessed(self, text, stack=('root',)): """ Split ``text`` into (tokentype, text) pairs. Monkeypatched to store the final stack on the object itself. + + The `text` parameter this gets passed is only the current line, so to + highlight things like multiline strings correctly, we need to retrieve + the state from the previous line (this is done in PygmentsHighlighter, + below), and use it to continue processing the current line. """ pos = 0 tokendefs = self._tokens @@ -24,11 +29,12 @@ def get_tokens_unprocessed(self, text, stack=('root',)): for rexmatch, action, new_state in statetokens: m = rexmatch(text, pos) if m: - if type(action) is _TokenType: - yield pos, action, m.group() - else: - for item in action(self, m): - yield item + if action is not None: + if type(action) is _TokenType: + yield pos, action, m.group() + else: + for item in action(self, m): + yield item pos = m.end() if new_state is not None: # state transition diff --git a/IPython/qt/console/qtconsoleapp.py b/IPython/qt/console/qtconsoleapp.py index 2752308..aa2de06 100644 --- a/IPython/qt/console/qtconsoleapp.py +++ b/IPython/qt/console/qtconsoleapp.py @@ -50,6 +50,7 @@ if os.name == 'nt': from IPython.external.qt import QtCore, QtGui # Local imports +from IPython.config.application import boolean_flag from IPython.config.application import catch_config_error from IPython.core.application import BaseIPythonApplication from IPython.qt.console.ipython_widget import IPythonWidget @@ -91,6 +92,11 @@ qt_flags = { 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}}, "Disable rich text support."), } +qt_flags.update(boolean_flag( + 'banner', 'IPythonQtConsoleApp.display_banner', + "Display a banner upon starting the QtConsole.", + "Don't display a banner upon starting the QtConsole." +)) # and app_flags from the Console Mixin qt_flags.update(app_flags) @@ -168,6 +174,10 @@ class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp): plain = CBool(False, config=True, help="Use a plaintext widget instead of rich text (plain can't print/save).") + display_banner = CBool(True, config=True, + help="Whether to display a banner upon starting the QtConsole." + ) + def _plain_changed(self, name, old, new): kind = 'plain' if new else 'rich' self.config.ConsoleWidget.kind = kind @@ -193,8 +203,10 @@ class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp): autorestart=True, ) # start the kernel - kwargs = dict() - kwargs['extra_arguments'] = self.kernel_argv + kwargs = {} + # FIXME: remove special treatment of IPython kernels + if self.kernel_manager.ipython_kernel: + kwargs['extra_arguments'] = self.kernel_argv kernel_manager.start_kernel(**kwargs) kernel_manager.client_factory = self.kernel_client_class kernel_client = kernel_manager.client() @@ -253,6 +265,7 @@ class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp): self.widget._existing = self.existing self.widget._may_close = not self.existing self.widget._confirm_exit = self.confirm_exit + self.widget._display_banner = self.display_banner self.widget.kernel_manager = self.kernel_manager self.widget.kernel_client = self.kernel_client diff --git a/IPython/qt/console/rich_ipython_widget.py b/IPython/qt/console/rich_ipython_widget.py index 60247e7..2105019 100644 --- a/IPython/qt/console/rich_ipython_widget.py +++ b/IPython/qt/console/rich_ipython_widget.py @@ -107,7 +107,7 @@ class RichIPythonWidget(IPythonWidget): def _handle_execute_result(self, msg): """ Overridden to handle rich data types, like SVG. """ - if not self._hidden and self._is_from_this_session(msg): + if self.include_output(msg): self.flush_clearoutput() content = msg['content'] prompt_number = content.get('execution_count', 0) @@ -146,7 +146,7 @@ class RichIPythonWidget(IPythonWidget): def _handle_display_data(self, msg): """ Overridden to handle rich data types, like SVG. """ - if not self._hidden and self._is_from_this_session(msg): + if self.include_output(msg): self.flush_clearoutput() data = msg['content']['data'] metadata = msg['content']['metadata'] diff --git a/IPython/qt/console/tests/test_completion_lexer.py b/IPython/qt/console/tests/test_completion_lexer.py deleted file mode 100644 index 70c9174..0000000 --- a/IPython/qt/console/tests/test_completion_lexer.py +++ /dev/null @@ -1,47 +0,0 @@ -# Standard library imports -import unittest - -# System library imports -from pygments.lexers import CLexer, CppLexer, PythonLexer - -# Local imports -from IPython.qt.console.completion_lexer import CompletionLexer - - -class TestCompletionLexer(unittest.TestCase): - - def testPython(self): - """ Does the CompletionLexer work for Python? - """ - lexer = CompletionLexer(PythonLexer()) - - # Test simplest case. - self.assertEqual(lexer.get_context("foo.bar.baz"), - [ "foo", "bar", "baz" ]) - - # Test trailing period. - self.assertEqual(lexer.get_context("foo.bar."), [ "foo", "bar", "" ]) - - # Test with prompt present. - self.assertEqual(lexer.get_context(">>> foo.bar.baz"), - [ "foo", "bar", "baz" ]) - - # Test spacing in name. - self.assertEqual(lexer.get_context("foo.bar. baz"), [ "baz" ]) - - # Test parenthesis. - self.assertEqual(lexer.get_context("foo("), []) - - def testC(self): - """ Does the CompletionLexer work for C/C++? - """ - lexer = CompletionLexer(CLexer()) - self.assertEqual(lexer.get_context("foo.bar"), [ "foo", "bar" ]) - self.assertEqual(lexer.get_context("foo->bar"), [ "foo", "bar" ]) - - lexer = CompletionLexer(CppLexer()) - self.assertEqual(lexer.get_context("Foo::Bar"), [ "Foo", "Bar" ]) - - -if __name__ == '__main__': - unittest.main() diff --git a/IPython/qt/kernel_mixins.py b/IPython/qt/kernel_mixins.py index 9fb659a..23d432b 100644 --- a/IPython/qt/kernel_mixins.py +++ b/IPython/qt/kernel_mixins.py @@ -17,10 +17,6 @@ class ChannelQObject(SuperQObject): # Emitted when the channel is stopped. stopped = QtCore.Signal() - #--------------------------------------------------------------------------- - # Channel interface - #--------------------------------------------------------------------------- - def start(self): """ Reimplemented to emit signal. """ @@ -61,10 +57,6 @@ class QtShellChannelMixin(ChannelQObject): history_reply = QtCore.Signal(object) kernel_info_reply = QtCore.Signal(object) - #--------------------------------------------------------------------------- - # 'ShellChannel' interface - #--------------------------------------------------------------------------- - def call_handlers(self, msg): """ Reimplemented to emit signals instead of making callbacks. """ @@ -108,10 +100,6 @@ class QtIOPubChannelMixin(ChannelQObject): # Emitted when a shutdown is noticed. shutdown_reply_received = QtCore.Signal(object) - #--------------------------------------------------------------------------- - # 'IOPubChannel' interface - #--------------------------------------------------------------------------- - def call_handlers(self, msg): """ Reimplemented to emit signals instead of making callbacks. """ @@ -122,8 +110,6 @@ class QtIOPubChannelMixin(ChannelQObject): signal = getattr(self, msg_type + '_received', None) if signal: signal.emit(msg) - elif msg_type in ('stdout', 'stderr'): - self.stream_received.emit(msg) def flush(self): """ Reimplemented to ensure that signals are dispatched immediately. @@ -140,10 +126,6 @@ class QtStdInChannelMixin(ChannelQObject): # Emitted when an input request is received. input_requested = QtCore.Signal(object) - #--------------------------------------------------------------------------- - # 'StdInChannel' interface - #--------------------------------------------------------------------------- - def call_handlers(self, msg): """ Reimplemented to emit signals instead of making callbacks. """ @@ -161,14 +143,9 @@ class QtHBChannelMixin(ChannelQObject): # Emitted when the kernel has died. kernel_died = QtCore.Signal(object) - #--------------------------------------------------------------------------- - # 'HBChannel' interface - #--------------------------------------------------------------------------- - def call_handlers(self, since_last_heartbeat): """ Reimplemented to emit signals instead of making callbacks. """ - # Emit the generic signal. self.kernel_died.emit(since_last_heartbeat) diff --git a/IPython/terminal/console/interactiveshell.py b/IPython/terminal/console/interactiveshell.py index 43121f3..1daba8e 100644 --- a/IPython/terminal/console/interactiveshell.py +++ b/IPython/terminal/console/interactiveshell.py @@ -23,16 +23,16 @@ except ImportError: from IPython.core import page from IPython.core import release +from IPython.terminal.console.zmqhistory import ZMQHistoryManager from IPython.utils.warn import warn, error from IPython.utils import io from IPython.utils.py3compat import string_types, input -from IPython.utils.traitlets import List, Enum, Any, Instance, Unicode, Float +from IPython.utils.traitlets import List, Enum, Any, Instance, Unicode, Float, Bool from IPython.utils.tempdir import NamedFileInTemporaryDirectory from IPython.terminal.interactiveshell import TerminalInteractiveShell from IPython.terminal.console.completer import ZMQCompleter - class ZMQTerminalInteractiveShell(TerminalInteractiveShell): """A subclass of TerminalInteractiveShell that uses the 0MQ kernel""" _executing = False @@ -212,8 +212,37 @@ class ZMQTerminalInteractiveShell(TerminalInteractiveShell): print(frame, file=io.stderr) self.execution_count = int(content["execution_count"] + 1) - - + + include_other_output = Bool(False, config=True, + help="""Whether to include output from clients + other than this one sharing the same kernel. + + Outputs are not displayed until enter is pressed. + """ + ) + other_output_prefix = Unicode("[remote] ", config=True, + help="""Prefix to add to outputs coming from clients other than this one. + + Only relevant if include_other_output is True. + """ + ) + + def from_here(self, msg): + """Return whether a message is from this session""" + return msg['parent_header'].get("session", self.session_id) == self.session_id + + def include_output(self, msg): + """Return whether we should include a given output message""" + from_here = self.from_here(msg) + if msg['msg_type'] == 'execute_input': + # only echo inputs not from here + return self.include_other_output and not from_here + + if self.include_other_output: + return True + else: + return from_here + def handle_iopub(self, msg_id=''): """Process messages on the IOPub channel @@ -227,7 +256,7 @@ class ZMQTerminalInteractiveShell(TerminalInteractiveShell): msg_type = sub_msg['header']['msg_type'] parent = sub_msg["parent_header"] - if parent.get("session", self.session_id) == self.session_id: + if self.include_output(sub_msg): if msg_type == 'status': self._execution_state = sub_msg["content"]["execution_state"] elif msg_type == 'stream': @@ -235,13 +264,13 @@ class ZMQTerminalInteractiveShell(TerminalInteractiveShell): if self._pending_clearoutput: print("\r", file=io.stdout, end="") self._pending_clearoutput = False - print(sub_msg["content"]["data"], file=io.stdout, end="") + print(sub_msg["content"]["text"], file=io.stdout, end="") io.stdout.flush() - elif sub_msg["content"]["name"] == "stderr" : + elif sub_msg["content"]["name"] == "stderr": if self._pending_clearoutput: print("\r", file=io.stderr, end="") self._pending_clearoutput = False - print(sub_msg["content"]["data"], file=io.stderr, end="") + print(sub_msg["content"]["text"], file=io.stderr, end="") io.stderr.flush() elif msg_type == 'execute_result': @@ -249,8 +278,11 @@ class ZMQTerminalInteractiveShell(TerminalInteractiveShell): print("\r", file=io.stdout, end="") self._pending_clearoutput = False self.execution_count = int(sub_msg["content"]["execution_count"]) + if not self.from_here(sub_msg): + sys.stdout.write(self.other_output_prefix) format_dict = sub_msg["content"]["data"] self.handle_rich_data(format_dict) + # taken from DisplayHook.__call__: hook = self.displayhook hook.start_displayhook() @@ -263,10 +295,20 @@ class ZMQTerminalInteractiveShell(TerminalInteractiveShell): data = sub_msg["content"]["data"] handled = self.handle_rich_data(data) if not handled: + if not self.from_here(sub_msg): + sys.stdout.write(self.other_output_prefix) # if it was an image, we handled it by now if 'text/plain' in data: print(data['text/plain']) - + + elif msg_type == 'execute_input': + content = sub_msg['content'] + self.execution_count = content['execution_count'] + if not self.from_here(sub_msg): + sys.stdout.write(self.other_output_prefix) + sys.stdout.write(self.prompt_manager.render('in')) + sys.stdout.write(content['code']) + elif msg_type == 'clear_output': if sub_msg["content"]["wait"]: self._pending_clearoutput = True @@ -371,6 +413,8 @@ class ZMQTerminalInteractiveShell(TerminalInteractiveShell): # this should not be necessary, but KeyboardInterrupt # handling seems rather unpredictable... self.write("\nKeyboardInterrupt in interact()\n") + + self.client.shell_channel.shutdown() def _banner1_default(self): return "IPython Console {version}\n".format(version=release.version) @@ -485,7 +529,7 @@ class ZMQTerminalInteractiveShell(TerminalInteractiveShell): except KeyboardInterrupt: #double-guard against keyboardinterrupts during kbdint handling try: - self.write('\nKeyboardInterrupt\n') + self.write('\n' + self.get_exception_only()) source_raw = self.input_splitter.raw_reset() hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell) more = False @@ -526,3 +570,9 @@ class ZMQTerminalInteractiveShell(TerminalInteractiveShell): # Turn off the exit flag, so the mainloop can be restarted if desired self.exit_now = False + + def init_history(self): + """Sets up the command history. """ + self.history_manager = ZMQHistoryManager(client=self.client) + self.configurables.append(self.history_manager) + diff --git a/IPython/terminal/console/tests/test_console.py b/IPython/terminal/console/tests/test_console.py index 479a06b..09f1a51 100644 --- a/IPython/terminal/console/tests/test_console.py +++ b/IPython/terminal/console/tests/test_console.py @@ -64,13 +64,8 @@ def start_console(): "Start `ipython console` using pexpect" from IPython.external import pexpect - args = ['console', '--colors=NoColor'] - # FIXME: remove workaround for 2.6 support - if sys.version_info[:2] > (2,6): - args = ['-m', 'IPython'] + args - cmd = sys.executable - else: - cmd = 'ipython' + args = ['-m', 'IPython', 'console', '--colors=NoColor'] + cmd = sys.executable try: p = pexpect.spawn(cmd, args=args) diff --git a/IPython/terminal/console/zmqhistory.py b/IPython/terminal/console/zmqhistory.py new file mode 100644 index 0000000..9384a39 --- /dev/null +++ b/IPython/terminal/console/zmqhistory.py @@ -0,0 +1,95 @@ +""" ZMQ Kernel History accessor and manager. """ +#----------------------------------------------------------------------------- +# Copyright (C) 2010-2011 The IPython Development Team. +# +# Distributed under the terms of the BSD License. +# +# The full license is in the file COPYING.txt, distributed with this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +from IPython.core.history import HistoryAccessorBase +from IPython.utils.traitlets import Dict, List + +try: + from queue import Empty # Py 3 +except ImportError: + from Queue import Empty # Py 2 + +class ZMQHistoryManager(HistoryAccessorBase): + """History accessor and manager for ZMQ-based kernels""" + input_hist_parsed = List([""]) + output_hist = Dict() + dir_hist = List() + output_hist_reprs = Dict() + + def __init__(self, client): + """ + Class to load the command-line history from a ZMQ-based kernel, + and access the history. + + Parameters + ---------- + + client: `IPython.kernel.KernelClient` + The kernel client in order to request the history. + """ + self.client = client + + def _load_history(self, raw=True, output=False, hist_access_type='range', + **kwargs): + """ + Load the history over ZMQ from the kernel. Wraps the history + messaging with loop to wait to get history results. + """ + history = [] + if hasattr(self.client, "history"): + ## In tests, KernelClient may not have a history method + msg_id = self.client.history(raw=raw, output=output, + hist_access_type=hist_access_type, + **kwargs) + while True: + try: + reply = self.client.get_shell_msg(timeout=1) + except Empty: + break + else: + if reply['parent_header'].get('msg_id') == msg_id: + history = reply['content'].get('history', []) + break + return history + + def get_tail(self, n=10, raw=True, output=False, include_latest=False): + return self._load_history(hist_access_type='tail', n=n, raw=raw, + output=output) + + def search(self, pattern="*", raw=True, search_raw=True, + output=False, n=None, unique=False): + return self._load_history(hist_access_type='search', pattern=pattern, + raw=raw, search_raw=search_raw, + output=output, n=n, unique=unique) + + def get_range(self, session, start=1, stop=None, raw=True,output=False): + return self._load_history(hist_access_type='range', raw=raw, + output=output, start=start, stop=stop, + session=session) + + def get_range_by_str(self, rangestr, raw=True, output=False): + return self._load_history(hist_access_type='range', raw=raw, + output=output, rangestr=rangestr) + + def end_session(self): + """ + Nothing to do for ZMQ-based histories. + """ + pass + + def reset(self, new_session=True): + """ + Nothing to do for ZMQ-based histories. + """ + pass + diff --git a/IPython/terminal/embed.py b/IPython/terminal/embed.py index 045925c..b2f3408 100644 --- a/IPython/terminal/embed.py +++ b/IPython/terminal/embed.py @@ -14,6 +14,7 @@ import warnings from IPython.core import ultratb, compilerop from IPython.core.magic import Magics, magics_class, line_magic from IPython.core.interactiveshell import DummyMod +from IPython.core.interactiveshell import InteractiveShell from IPython.terminal.interactiveshell import TerminalInteractiveShell from IPython.terminal.ipapp import load_default_config @@ -192,7 +193,8 @@ class InteractiveShellEmbed(TerminalInteractiveShell): # like _ih and get_ipython() into the local namespace, but delete them # later. if local_ns is not None: - self.user_ns = local_ns + reentrant_local_ns = {k: v for (k, v) in local_ns.items() if k not in self.user_ns_hidden.keys()} + self.user_ns = reentrant_local_ns self.init_user_ns() # Compiler flags @@ -208,8 +210,8 @@ class InteractiveShellEmbed(TerminalInteractiveShell): # now, purge out the local namespace of IPython's hidden variables. if local_ns is not None: - for name in self.user_ns_hidden: - local_ns.pop(name, None) + local_ns.update({k: v for (k, v) in self.user_ns.items() if k not in self.user_ns_hidden.keys()}) + # Restore original namespace so shell can shut down when we exit. self.user_module = orig_user_module @@ -249,5 +251,28 @@ def embed(**kwargs): config = load_default_config() config.InteractiveShellEmbed = config.TerminalInteractiveShell kwargs['config'] = config + #save ps1/ps2 if defined + ps1 = None + ps2 = None + try: + ps1 = sys.ps1 + ps2 = sys.ps2 + except AttributeError: + pass + #save previous instance + saved_shell_instance = InteractiveShell._instance + if saved_shell_instance is not None: + cls = type(saved_shell_instance) + cls.clear_instance() shell = InteractiveShellEmbed.instance(**kwargs) shell(header=header, stack_depth=2, compile_flags=compile_flags) + InteractiveShellEmbed.clear_instance() + #restore previous instance + if saved_shell_instance is not None: + cls = type(saved_shell_instance) + cls.clear_instance() + for subclass in cls._walk_mro(): + subclass._instance = saved_shell_instance + if ps1 is not None: + sys.ps1 = ps1 + sys.ps2 = ps2 diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index ff02a3f..939cf90 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -468,7 +468,7 @@ class TerminalInteractiveShell(InteractiveShell): except KeyboardInterrupt: #double-guard against keyboardinterrupts during kbdint handling try: - self.write('\nKeyboardInterrupt\n') + self.write('\n' + self.get_exception_only()) source_raw = self.input_splitter.raw_reset() hlen_b4_cell = \ self._replace_rlhist_multiline(source_raw, hlen_b4_cell) diff --git a/IPython/terminal/ipapp.py b/IPython/terminal/ipapp.py index 7de0339..8d70e8f 100755 --- a/IPython/terminal/ipapp.py +++ b/IPython/terminal/ipapp.py @@ -171,8 +171,7 @@ frontend_flags['quick']=( frontend_flags['i'] = ( {'TerminalIPythonApp' : {'force_interact' : True}}, - """If running code from the command line, become interactive afterwards. - Note: can also be given simply as '-i'.""" + """If running code from the command line, become interactive afterwards.""" ) flags.update(frontend_flags) diff --git a/IPython/terminal/tests/test_embed.py b/IPython/terminal/tests/test_embed.py index 70a4508..ec51316 100644 --- a/IPython/terminal/tests/test_embed.py +++ b/IPython/terminal/tests/test_embed.py @@ -16,11 +16,13 @@ import sys import nose.tools as nt from IPython.utils.process import process_handler from IPython.utils.tempdir import NamedFileInTemporaryDirectory +from IPython.testing.decorators import skip_win32 #----------------------------------------------------------------------------- # Tests #----------------------------------------------------------------------------- + _sample_embed = b""" from __future__ import print_function import IPython @@ -55,3 +57,69 @@ def test_ipython_embed(): nt.assert_in('IPython', std) nt.assert_in('bye!', std) +@skip_win32 +def test_nest_embed(): + """test that `IPython.embed()` is nestable""" + from IPython.external import pexpect + ipy_prompt = r']:' #ansi color codes give problems matching beyond this + + + child = pexpect.spawn('%s -m IPython'%(sys.executable, )) + child.expect(ipy_prompt) + child.sendline("from __future__ import print_function") + child.expect(ipy_prompt) + child.sendline("import IPython") + child.expect(ipy_prompt) + child.sendline("ip0 = get_ipython()") + #enter first nested embed + child.sendline("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 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("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 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 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 IPython.get_ipython() is ip0 else 'false')") + assert(child.expect(['true\r\n', 'false\r\n']) == 0) + child.expect(ipy_prompt) diff --git a/IPython/terminal/tests/test_help.py b/IPython/terminal/tests/test_help.py index fd197f8..262a375 100644 --- a/IPython/terminal/tests/test_help.py +++ b/IPython/terminal/tests/test_help.py @@ -1,23 +1,11 @@ """Test help output of various IPython entry points""" -#----------------------------------------------------------------------------- -# Copyright (C) 2013 The 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 -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. import IPython.testing.tools as tt from IPython.testing.decorators import skip_without -#----------------------------------------------------------------------------- -# Tests -#----------------------------------------------------------------------------- - def test_ipython_help(): tt.help_all_output_test() @@ -37,6 +25,6 @@ def test_locate_help(): def test_locate_profile_help(): tt.help_all_output_test("locate profile") -@skip_without('IPython.nbformat.current') # Requires jsonschema to be installed +@skip_without('IPython.nbformat') # Requires jsonschema to be installed def test_trust_help(): tt.help_all_output_test("trust") diff --git a/IPython/testing/iptest.py b/IPython/testing/iptest.py index b911009..54ac454 100644 --- a/IPython/testing/iptest.py +++ b/IPython/testing/iptest.py @@ -14,19 +14,11 @@ itself from the command line. There are two ways of running this script: """ -#----------------------------------------------------------------------------- -# Copyright (C) 2009-2011 The 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. -#----------------------------------------------------------------------------- +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- from __future__ import print_function -# Stdlib import glob from io import BytesIO import os @@ -35,7 +27,6 @@ import sys from threading import Thread, Lock, Event import warnings -# Now, proceed to import nose itself import nose.plugins.builtin from nose.plugins.xunit import Xunit from nose import SkipTest @@ -43,8 +34,8 @@ from nose.core import TestProgram from nose.plugins import Plugin from nose.util import safe_str -# Our own imports from IPython.utils.process import is_cmd_found +from IPython.utils.py3compat import bytes_to_str from IPython.utils.importstring import import_item from IPython.testing.plugin.ipdoctest import IPythonDoctest from IPython.external.decorators import KnownFailure, knownfailureif @@ -141,14 +132,13 @@ have['pymongo'] = test_for('pymongo') have['pygments'] = test_for('pygments') have['qt'] = test_for('IPython.external.qt') have['sqlite3'] = test_for('sqlite3') -have['cython'] = test_for('Cython') -have['tornado'] = test_for('tornado.version_info', (3,1,0), callback=None) +have['tornado'] = test_for('tornado.version_info', (4,0), callback=None) have['jinja2'] = test_for('jinja2') have['mistune'] = test_for('mistune') have['requests'] = test_for('requests') have['sphinx'] = test_for('sphinx') have['jsonschema'] = test_for('jsonschema') -have['jsonpointer'] = test_for('jsonpointer') +have['terminado'] = test_for('terminado') have['casperjs'] = is_cmd_found('casperjs') have['phantomjs'] = is_cmd_found('phantomjs') have['slimerjs'] = is_cmd_found('slimerjs') @@ -251,9 +241,6 @@ test_sections['kernel.inprocess'].requires('zmq') # extensions: sec = test_sections['extensions'] -if not have['cython']: - sec.exclude('cythonmagic') - sec.exclude('tests.test_cythonmagic') # This is deprecated in favour of rpy2 sec.exclude('rmagic') # autoreload does some strange stuff, so move it to its own test section @@ -268,17 +255,19 @@ test_sections['qt'].requires('zmq', 'qt', 'pygments') # html: sec = test_sections['html'] -sec.requires('zmq', 'tornado', 'requests', 'sqlite3', 'jsonschema', 'jsonpointer') +sec.requires('zmq', 'tornado', 'requests', 'sqlite3', 'jsonschema') # The notebook 'static' directory contains JS, css and other # files for web serving. Occasionally projects may put a .py # file in there (MathJax ships a conf.py), so we might as # well play it safe and skip the whole thing. sec.exclude('static') -sec.exclude('fabfile') +sec.exclude('tasks') if not have['jinja2']: sec.exclude('notebookapp') if not have['pygments'] or not have['jinja2']: sec.exclude('nbconvert') +if not have['terminado']: + sec.exclude('terminal') # config: # Config files aren't really importable stand-alone @@ -286,7 +275,7 @@ test_sections['config'].exclude('profile') # nbconvert: sec = test_sections['nbconvert'] -sec.requires('pygments', 'jinja2', 'jsonschema', 'jsonpointer', 'mistune') +sec.requires('pygments', 'jinja2', 'jsonschema', 'mistune') # Exclude nbconvert directories containing config files used to test. # Executing the config files with iptest would cause an exception. sec.exclude('tests.files') @@ -296,7 +285,7 @@ if not have['tornado']: sec.exclude('nbconvert.post_processors.tests.test_serve') # nbformat: -test_sections['nbformat'].requires('jsonschema', 'jsonpointer') +test_sections['nbformat'].requires('jsonschema') #----------------------------------------------------------------------------- # Functions and classes @@ -357,8 +346,9 @@ class ExclusionPlugin(Plugin): class StreamCapturer(Thread): daemon = True # Don't hang if main thread crashes started = False - def __init__(self): + def __init__(self, echo=False): super(StreamCapturer, self).__init__() + self.echo = echo self.streams = [] self.buffer = BytesIO() self.readfd, self.writefd = os.pipe() @@ -373,6 +363,8 @@ class StreamCapturer(Thread): with self.buffer_lock: self.buffer.write(chunk) + if self.echo: + sys.stdout.write(bytes_to_str(chunk)) os.close(self.readfd) os.close(self.writefd) diff --git a/IPython/testing/iptestcontroller.py b/IPython/testing/iptestcontroller.py index 12ee610..fed1405 100644 --- a/IPython/testing/iptestcontroller.py +++ b/IPython/testing/iptestcontroller.py @@ -15,14 +15,18 @@ import argparse import json import multiprocessing.pool import os +import re +import requests import shutil import signal import sys import subprocess import time -import re -from .iptest import have, test_group_names as py_test_group_names, test_sections, StreamCapturer +from .iptest import ( + have, test_group_names as py_test_group_names, test_sections, StreamCapturer, + test_for, +) from IPython.utils.path import compress_user from IPython.utils.py3compat import bytes_to_str from IPython.utils.sysinfo import get_sys_info @@ -77,18 +81,24 @@ class TestController(object): """ pass - def launch(self, buffer_output=False): + def launch(self, buffer_output=False, capture_output=False): # print('*** ENV:', self.env) # dbg # print('*** CMD:', self.cmd) # dbg env = os.environ.copy() env.update(self.env) - output = subprocess.PIPE if buffer_output else None - stdout = subprocess.STDOUT if buffer_output else None - self.process = subprocess.Popen(self.cmd, stdout=output, - stderr=stdout, env=env) + if buffer_output: + capture_output = True + self.stdout_capturer = c = StreamCapturer(echo=not buffer_output) + c.start() + stdout = c.writefd if capture_output else None + stderr = subprocess.STDOUT if capture_output else None + self.process = subprocess.Popen(self.cmd, stdout=stdout, + stderr=stderr, env=env) def wait(self): - self.stdout, _ = self.process.communicate() + self.process.wait() + self.stdout_capturer.halt() + self.stdout = self.stdout_capturer.get_buffer() return self.process.returncode def print_extra_info(self): @@ -217,16 +227,17 @@ def all_js_groups(): class JSController(TestController): """Run CasperJS tests """ + requirements = ['zmq', 'tornado', 'jinja2', 'casperjs', 'sqlite3', - 'jsonschema', 'jsonpointer'] - display_slimer_output = False + 'jsonschema'] - def __init__(self, section, xunit=True, engine='phantomjs'): + def __init__(self, section, xunit=True, engine='phantomjs', url=None): """Create new test runner.""" TestController.__init__(self) self.engine = engine self.section = section self.xunit = xunit + self.url = url self.slimer_failure = re.compile('^FAIL.*', flags=re.MULTILINE) js_test_dir = get_js_test_dir() includes = '--includes=' + os.path.join(js_test_dir,'util.js') @@ -244,14 +255,26 @@ class JSController(TestController): if self.xunit: self.add_xunit() - # start the ipython notebook, so we get the port number - self.server_port = 0 - self._init_server() - if self.server_port: - self.cmd.append("--port=%i" % self.server_port) + # If a url was specified, use that for the testing. + if self.url: + try: + alive = requests.get(self.url).status_code == 200 + except: + alive = False + + if alive: + self.cmd.append("--url=%s" % self.url) + else: + raise Exception('Could not reach "%s".' % self.url) else: - # don't launch tests if the server didn't start - self.cmd = [sys.executable, '-c', 'raise SystemExit(1)'] + # start the ipython notebook, so we get the port number + self.server_port = 0 + self._init_server() + if self.server_port: + self.cmd.append("--port=%i" % self.server_port) + else: + # don't launch tests if the server didn't start + self.cmd = [sys.executable, '-c', 'raise SystemExit(1)'] def add_xunit(self): xunit_file = os.path.abspath(self.section.replace('/','.') + '.xunit.xml') @@ -261,8 +284,7 @@ class JSController(TestController): # If the engine is SlimerJS, we need to buffer the output because # SlimerJS does not support exit codes, so CasperJS always returns 0. if self.engine == 'slimerjs' and not buffer_output: - self.display_slimer_output = True - return super(JSController, self).launch(buffer_output=True) + return super(JSController, self).launch(capture_output=True) else: return super(JSController, self).launch(buffer_output=buffer_output) @@ -274,8 +296,6 @@ class JSController(TestController): # errors. Otherwise, just return the return code. if self.engine == 'slimerjs': stdout = bytes_to_str(self.stdout) - if self.display_slimer_output: - print(stdout) if ret != 0: # This could still happen e.g. if it's stopped by SIGINT return ret @@ -288,7 +308,8 @@ class JSController(TestController): @property def will_run(self): - return all(have[a] for a in self.requirements + [self.engine]) + should_run = all(have[a] for a in self.requirements + [self.engine]) + return should_run def _init_server(self): "Start the notebook server in a separate process" @@ -304,7 +325,15 @@ class JSController(TestController): command.append('--KernelManager.transport=ipc') self.stream_capturer = c = StreamCapturer() c.start() - self.server = subprocess.Popen(command, stdout=c.writefd, stderr=subprocess.STDOUT, cwd=self.nbdir.name) + env = os.environ.copy() + if self.engine == 'phantomjs': + env['IPYTHON_ALLOW_DRAFT_WEBSOCKETS_FOR_PHANTOMJS'] = '1' + self.server = subprocess.Popen(command, + stdout=c.writefd, + stderr=subprocess.STDOUT, + cwd=self.nbdir.name, + env=env, + ) self.server_info_file = os.path.join(self.ipydir.name, 'profile_default', 'security', 'nbserver-%i.json' % self.server.pid ) @@ -393,7 +422,7 @@ def prepare_controllers(options): js_testgroups = all_js_groups() engine = 'slimerjs' if options.slimerjs else 'phantomjs' - c_js = [JSController(name, xunit=options.xunit, engine=engine) for name in js_testgroups] + c_js = [JSController(name, xunit=options.xunit, engine=engine, url=options.url) for name in js_testgroups] c_py = [PyTestController(name, options) for name in py_testgroups] controllers = c_py + c_js @@ -501,6 +530,9 @@ def run_iptestall(options): slimerjs : bool Use slimerjs if it's installed instead of phantomjs for casperjs tests. + url : unicode + Address:port to use when running the JS tests. + xunit : bool Produce Xunit XML output. This is written to multiple foo.xunit.xml files. @@ -629,6 +661,7 @@ argparser.add_argument('--all', action='store_true', help='Include slow tests not run by default.') argparser.add_argument('--slimerjs', action='store_true', help="Use slimerjs if it's installed instead of phantomjs for casperjs tests.") +argparser.add_argument('--url', help="URL to use for the JS tests.") argparser.add_argument('-j', '--fast', nargs='?', const=None, default=1, type=int, help='Run test sections in parallel. This starts as many ' 'processes as you have cores, or you can specify a number.') diff --git a/IPython/testing/tools.py b/IPython/testing/tools.py index 5970775..cfb4c9f 100644 --- a/IPython/testing/tools.py +++ b/IPython/testing/tools.py @@ -177,7 +177,7 @@ def get_ipython_cmd(as_string=False): return ipython_cmd -def ipexec(fname, options=None): +def ipexec(fname, options=None, commands=()): """Utility to call 'ipython filename'. Starts IPython with a minimal and safe configuration to make startup as fast @@ -193,6 +193,9 @@ def ipexec(fname, options=None): options : optional, list Extra command-line flags to be passed to IPython. + commands : optional, list + Commands to send in on stdin + Returns ------- (stdout, stderr) of ipython subprocess. @@ -215,8 +218,13 @@ def ipexec(fname, options=None): full_cmd = ipython_cmd + cmdargs + [full_fname] env = os.environ.copy() env.pop('PYTHONWARNINGS', None) # Avoid extraneous warnings appearing on stderr - p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, env=env) - out, err = p.communicate() + for k, v in env.items(): + # Debug a bizarre failure we've seen on Windows: + # TypeError: environment can only contain strings + if not isinstance(v, str): + print(k, v) + p = Popen(full_cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, env=env) + out, err = p.communicate(input=py3compat.str_to_bytes('\n'.join(commands)) or None) out, err = py3compat.bytes_to_str(out), py3compat.bytes_to_str(err) # `import readline` causes 'ESC[?1034h' to be output sometimes, # so strip that out before doing comparisons @@ -226,7 +234,7 @@ def ipexec(fname, options=None): def ipexec_validate(fname, expected_out, expected_err='', - options=None): + options=None, commands=()): """Utility to call 'ipython filename' and validate output/error. This function raises an AssertionError if the validation fails. @@ -254,7 +262,7 @@ def ipexec_validate(fname, expected_out, expected_err='', import nose.tools as nt - out, err = ipexec(fname, options) + out, err = ipexec(fname, options, commands) #print 'OUT', out # dbg #print 'ERR', err # dbg # If there are any errors, we must check those befor stdout, as they may be @@ -465,3 +473,23 @@ def help_all_output_test(subcommand=''): nt.assert_in("Class parameters", out) return out, err +def assert_big_text_equal(a, b, chunk_size=80): + """assert that large strings are equal + + Zooms in on first chunk that differs, + to give better info than vanilla assertEqual for large text blobs. + """ + for i in range(0, len(a), chunk_size): + chunk_a = a[i:i + chunk_size] + chunk_b = b[i:i + chunk_size] + nt.assert_equal(chunk_a, chunk_b, "[offset: %i]\n%r != \n%r" % ( + i, chunk_a, chunk_b)) + + if len(a) > len(b): + nt.fail("Length doesn't match (%i > %i). Extra text:\n%r" % ( + len(a), len(b), a[len(b):] + )) + elif len(a) < len(b): + nt.fail("Length doesn't match (%i < %i). Extra text:\n%r" % ( + len(a), len(b), b[len(a):] + )) diff --git a/IPython/utils/_process_win32_controller.py b/IPython/utils/_process_win32_controller.py index aa508cb..555eec2 100644 --- a/IPython/utils/_process_win32_controller.py +++ b/IPython/utils/_process_win32_controller.py @@ -296,7 +296,7 @@ class Win32ShellCommandController(object): c_hstdin = None CloseHandle(c_hstdout) c_hstdout = None - if c_hstderr != None: + if c_hstderr is not None: CloseHandle(c_hstderr) c_hstderr = None @@ -403,10 +403,10 @@ class Win32ShellCommandController(object): These functions are called from different threads (but not concurrently, because of the GIL). """ - if stdout_func == None and stdin_func == None and stderr_func == None: + if stdout_func is None and stdin_func is None and stderr_func is None: return self._run_stdio() - if stderr_func != None and self.mergeout: + if stderr_func is not None and self.mergeout: raise RuntimeError("Shell command was initiated with " "merged stdin/stdout, but a separate stderr_func " "was provided to the run() method") @@ -421,7 +421,7 @@ class Win32ShellCommandController(object): threads.append(threading.Thread(target=self._stdout_thread, args=(self.hstdout, stdout_func))) if not self.mergeout: - if stderr_func == None: + if stderr_func is None: stderr_func = stdout_func threads.append(threading.Thread(target=self._stdout_thread, args=(self.hstderr, stderr_func))) @@ -541,7 +541,7 @@ class Win32ShellCommandController(object): if self.hstderr: CloseHandle(self.hstderr) self.hstderr = None - if self.piProcInfo != None: + if self.piProcInfo is not None: CloseHandle(self.piProcInfo.hProcess) CloseHandle(self.piProcInfo.hThread) self.piProcInfo = None diff --git a/IPython/utils/_sysinfo.py b/IPython/utils/_sysinfo.py index 2e58242..a80b029 100644 --- a/IPython/utils/_sysinfo.py +++ b/IPython/utils/_sysinfo.py @@ -1,2 +1,2 @@ # GENERATED BY setup.py -commit = "" +commit = u"" diff --git a/IPython/utils/io.py b/IPython/utils/io.py index 809095d..df1e39e 100644 --- a/IPython/utils/io.py +++ b/IPython/utils/io.py @@ -237,9 +237,9 @@ def atomic_writing(path, text=True, encoding='utf-8', **kwargs): """Context manager to write to a file only if the entire write is successful. This works by creating a temporary file in the same directory, and renaming - it over the old file if the context is exited without an error. If the - target file is a symlink or a hardlink, this will not be preserved: it will - be replaced by a new regular file. + it over the old file if the context is exited without an error. If other + file names are hard linked to the target file, this relationship will not be + preserved. On Windows, there is a small chink in the atomicity: the target file is deleted before renaming the temporary file over it. This appears to be @@ -260,9 +260,14 @@ def atomic_writing(path, text=True, encoding='utf-8', **kwargs): **kwargs Passed to :func:`io.open`. """ - path = os.path.realpath(path) # Dereference symlinks + # realpath doesn't work on Windows: http://bugs.python.org/issue9949 + # Luckily, we only need to resolve the file itself being a symlink, not + # any of its directories, so this will suffice: + if os.path.islink(path): + path = os.path.join(os.path.dirname(path), os.readlink(path)) + dirname, basename = os.path.split(path) - handle, tmp_path = tempfile.mkstemp(prefix=basename, dir=dirname, text=text) + handle, tmp_path = tempfile.mkstemp(prefix=basename, dir=dirname) if text: fileobj = io.open(handle, 'w', encoding=encoding, **kwargs) else: diff --git a/IPython/utils/path.py b/IPython/utils/path.py index 4fcf37d..dcd66e7 100644 --- a/IPython/utils/path.py +++ b/IPython/utils/path.py @@ -12,9 +12,9 @@ import errno import shutil import random import tempfile -import warnings -from hashlib import md5 import glob +from warnings import warn +from hashlib import md5 import IPython from IPython.testing.skipdoctest import skip_doctest @@ -271,8 +271,8 @@ def get_ipython_dir(): # import pdb; pdb.set_trace() # dbg if 'IPYTHON_DIR' in env: - warnings.warn('The environment variable IPYTHON_DIR is deprecated. ' - 'Please use IPYTHONDIR instead.') + warn('The environment variable IPYTHON_DIR is deprecated. ' + 'Please use IPYTHONDIR instead.') ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None)) if ipdir is None: # not set explicitly, use ~/.ipython @@ -285,28 +285,28 @@ def get_ipython_dir(): if _writable_dir(xdg_ipdir): cu = compress_user if os.path.exists(ipdir): - warnings.warn(('Ignoring {0} in favour of {1}. Remove {0} ' - 'to get rid of this message').format(cu(xdg_ipdir), cu(ipdir))) + warn(('Ignoring {0} in favour of {1}. Remove {0} to ' + 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir))) elif os.path.islink(xdg_ipdir): - warnings.warn(('{0} is deprecated. Move link to {1} ' - 'to get rid of this message').format(cu(xdg_ipdir), cu(ipdir))) + warn(('{0} is deprecated. Move link to {1} to ' + 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir))) else: - warnings.warn('Moving {0} to {1}'.format(cu(xdg_ipdir), cu(ipdir))) + warn('Moving {0} to {1}'.format(cu(xdg_ipdir), cu(ipdir))) shutil.move(xdg_ipdir, ipdir) ipdir = os.path.normpath(os.path.expanduser(ipdir)) if os.path.exists(ipdir) and not _writable_dir(ipdir): # ipdir exists, but is not writable - warnings.warn("IPython dir '%s' is not a writable location," - " using a temp directory."%ipdir) + warn("IPython dir '{0}' is not a writable location," + " using a temp directory.".format(ipdir)) ipdir = tempfile.mkdtemp() elif not os.path.exists(ipdir): parent = os.path.dirname(ipdir) if not _writable_dir(parent): # ipdir does not exist and parent isn't writable - warnings.warn("IPython parent '%s' is not a writable location," - " using a temp directory."%parent) + warn("IPython parent '{0}' is not a writable location," + " using a temp directory.".format(parent)) ipdir = tempfile.mkdtemp() return py3compat.cast_unicode(ipdir, fs_encoding) @@ -473,11 +473,11 @@ def check_for_old_config(ipython_dir=None): if filehash(f) == old_config_md5.get(cfg, ''): os.unlink(f) else: - warnings.warn("Found old IPython config file %r (modified by user)"%f) + warn("Found old IPython config file {!r} (modified by user)".format(f)) warned = True if warned: - warnings.warn(""" + warn(""" The IPython configuration system has changed as of 0.11, and these files will be ignored. See http://ipython.github.com/ipython-doc/dev/config for details of the new config system. diff --git a/IPython/utils/py3compat.py b/IPython/utils/py3compat.py index 47d1d12..e0ebd3d 100644 --- a/IPython/utils/py3compat.py +++ b/IPython/utils/py3compat.py @@ -101,11 +101,12 @@ if sys.version_info[0] >= 3: getcwd = os.getcwd MethodType = types.MethodType - - def execfile(fname, glob, loc=None): + + def execfile(fname, glob, loc=None, compiler=None): loc = loc if (loc is not None) else glob with open(fname, 'rb') as f: - exec(compile(f.read(), fname, 'exec'), glob, loc) + compiler = compiler or compile + exec(compiler(f.read(), fname, 'exec'), glob, loc) # Refactor print statements in doctests. _print_statement_re = re.compile(r"\bprint (?P.*)$", re.MULTILINE) @@ -185,26 +186,30 @@ else: return s.format(u='u') if sys.platform == 'win32': - def execfile(fname, glob=None, loc=None): + def execfile(fname, glob=None, loc=None, compiler=None): loc = loc if (loc is not None) else glob - # The rstrip() is necessary b/c trailing whitespace in files will - # cause an IndentationError in Python 2.6 (this was fixed in 2.7, - # but we still support 2.6). See issue 1027. - scripttext = builtin_mod.open(fname).read().rstrip() + '\n' + scripttext = builtin_mod.open(fname).read()+ '\n' # compile converts unicode filename to str assuming # ascii. Let's do the conversion before calling compile if isinstance(fname, unicode): filename = unicode_to_str(fname) else: filename = fname - exec(compile(scripttext, filename, 'exec'), glob, loc) + compiler = compiler or compile + exec(compiler(scripttext, filename, 'exec'), glob, loc) + else: - def execfile(fname, *where): + def execfile(fname, glob=None, loc=None, compiler=None): if isinstance(fname, unicode): filename = fname.encode(sys.getfilesystemencoding()) else: filename = fname - builtin_mod.execfile(filename, *where) + where = [ns for ns in [glob, loc] if ns is not None] + if compiler is None: + builtin_mod.execfile(filename, *where) + else: + scripttext = builtin_mod.open(fname).read().rstrip() + '\n' + exec(compiler(scripttext, filename, 'exec'), glob, loc) def annotate(**kwargs): diff --git a/IPython/utils/signatures.py b/IPython/utils/signatures.py index 0ab0a88..771899e 100644 --- a/IPython/utils/signatures.py +++ b/IPython/utils/signatures.py @@ -72,10 +72,13 @@ def signature(obj): raise TypeError('{0!r} is not a callable object'.format(obj)) if isinstance(obj, types.MethodType): - # In this case we skip the first parameter of the underlying - # function (usually `self` or `cls`). - sig = signature(obj.__func__) - return sig.replace(parameters=tuple(sig.parameters.values())[1:]) + if obj.__self__ is None: + # Unbound method - treat it as a function (no distinction in Py 3) + obj = obj.__func__ + else: + # Bound method: trim off the first parameter (typically self or cls) + sig = signature(obj.__func__) + return sig.replace(parameters=tuple(sig.parameters.values())[1:]) try: sig = obj.__signature__ diff --git a/IPython/utils/sysinfo.py b/IPython/utils/sysinfo.py index e3d40c0..db7f291 100644 --- a/IPython/utils/sysinfo.py +++ b/IPython/utils/sysinfo.py @@ -61,8 +61,8 @@ def pkg_commit_hash(pkg_path): cwd=pkg_path, shell=True) repo_commit, _ = proc.communicate() if repo_commit: - return 'repository', repo_commit.strip() - return '(none found)', '' + return 'repository', repo_commit.strip().decode('ascii') + return '(none found)', u'' def pkg_info(pkg_path): @@ -152,10 +152,7 @@ def num_cpus(): ncpufuncs = {'Linux':_num_cpus_unix, 'Darwin':_num_cpus_darwin, - 'Windows':_num_cpus_windows, - # On Vista, python < 2.5.2 has a bug and returns 'Microsoft' - # See http://bugs.python.org/issue1082 for details. - 'Microsoft':_num_cpus_windows, + 'Windows':_num_cpus_windows } ncpufunc = ncpufuncs.get(platform.system(), diff --git a/IPython/utils/tests/test_io.py b/IPython/utils/tests/test_io.py index a72207c..023c964 100644 --- a/IPython/utils/tests/test_io.py +++ b/IPython/utils/tests/test_io.py @@ -144,7 +144,10 @@ def test_atomic_writing(): try: os.symlink(f1, f2) have_symlink = True - except (AttributeError, NotImplementedError): + except (AttributeError, NotImplementedError, OSError): + # AttributeError: Python doesn't support it + # NotImplementedError: The system doesn't support it + # OSError: The user lacks the privilege (Windows) have_symlink = False with nt.assert_raises(CustomExc): @@ -172,4 +175,41 @@ def test_atomic_writing(): f.write(u'written from symlink') with stdlib_io.open(f1, 'r') as f: - nt.assert_equal(f.read(), u'written from symlink') \ No newline at end of file + nt.assert_equal(f.read(), u'written from symlink') + +def test_atomic_writing_newlines(): + with TemporaryDirectory() as td: + path = os.path.join(td, 'testfile') + + lf = u'a\nb\nc\n' + plat = lf.replace(u'\n', os.linesep) + crlf = lf.replace(u'\n', u'\r\n') + + # test default + with stdlib_io.open(path, 'w') as f: + f.write(lf) + with stdlib_io.open(path, 'r', newline='') as f: + read = f.read() + nt.assert_equal(read, plat) + + # test newline=LF + with stdlib_io.open(path, 'w', newline='\n') as f: + f.write(lf) + with stdlib_io.open(path, 'r', newline='') as f: + read = f.read() + nt.assert_equal(read, lf) + + # test newline=CRLF + with atomic_writing(path, newline='\r\n') as f: + f.write(lf) + with stdlib_io.open(path, 'r', newline='') as f: + read = f.read() + nt.assert_equal(read, crlf) + + # test newline=no convert + text = u'crlf\r\ncr\rlf\n' + with atomic_writing(path, newline='') as f: + f.write(text) + with stdlib_io.open(path, 'r', newline='') as f: + read = f.read() + nt.assert_equal(read, text) diff --git a/IPython/utils/tests/test_path.py b/IPython/utils/tests/test_path.py index 8d50f17..9c4f392 100644 --- a/IPython/utils/tests/test_path.py +++ b/IPython/utils/tests/test_path.py @@ -12,8 +12,14 @@ import tempfile import warnings from contextlib import contextmanager +try: # Python 3.3+ + from unittest.mock import patch +except ImportError: + from mock import patch + from os.path import join, abspath, split +from nose import SkipTest import nose.tools as nt from nose import with_setup @@ -97,10 +103,6 @@ def setup_environment(): global oldstuff, platformstuff oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd()) - if os.name == 'nt': - platformstuff = (wreg.OpenKey, wreg.QueryValueEx,) - - def teardown_environment(): """Restore things that were remembered by the setup_environment function """ @@ -114,8 +116,6 @@ def teardown_environment(): env.update(oldenv) if hasattr(sys, 'frozen'): del sys.frozen - if os.name == 'nt': - (wreg.OpenKey, wreg.QueryValueEx,) = platformstuff # Build decorator that uses the setup_environment/setup_environment with_environment = with_setup(setup_environment, teardown_environment) @@ -183,7 +183,6 @@ def test_get_home_dir_5(): os.name = 'posix' nt.assert_raises(path.HomeDirError, path.get_home_dir, True) - # Should we stub wreg fully so we can run the test on all platforms? @skip_if_not_win32 @with_environment @@ -197,19 +196,13 @@ def test_get_home_dir_8(): for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']: env.pop(key, None) - #Stub windows registry functions - def OpenKey(x, y): - class key: - def Close(self): - pass - return key() - def QueryValueEx(x, y): - return [abspath(HOME_TEST_DIR)] + class key: + def Close(self): + pass - wreg.OpenKey = OpenKey - wreg.QueryValueEx = QueryValueEx - - home_dir = path.get_home_dir() + with patch.object(wreg, 'OpenKey', return_value=key()), \ + patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]): + home_dir = path.get_home_dir() nt.assert_equal(home_dir, abspath(HOME_TEST_DIR)) @@ -471,6 +464,14 @@ def test_not_writable_ipdir(): ipdir = os.path.join(tmpdir, '.ipython') os.mkdir(ipdir) os.chmod(ipdir, 600) + try: + os.listdir(ipdir) + except OSError: + pass + else: + # I can still read an unreadable dir, + # assume I'm root and skip the test + raise SkipTest("I can't create directories that I can't list") with AssertPrints('is not a writable location', channel='stderr'): ipdir = path.get_ipython_dir() env.pop('IPYTHON_DIR', None) diff --git a/IPython/utils/tests/test_sysinfo.py b/IPython/utils/tests/test_sysinfo.py new file mode 100644 index 0000000..c4f9c3c --- /dev/null +++ b/IPython/utils/tests/test_sysinfo.py @@ -0,0 +1,17 @@ +# coding: utf-8 +"""Test suite for our sysinfo utilities.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import json +import nose.tools as nt + +from IPython.utils import sysinfo + + +def test_json_getsysinfo(): + """ + test that it is easily jsonable and don't return bytes somewhere. + """ + json.dumps(sysinfo.get_sys_info()) diff --git a/IPython/utils/tests/test_tokenutil.py b/IPython/utils/tests/test_tokenutil.py index 4d7084a..986adf8 100644 --- a/IPython/utils/tests/test_tokenutil.py +++ b/IPython/utils/tests/test_tokenutil.py @@ -4,7 +4,7 @@ import nose.tools as nt -from IPython.utils.tokenutil import token_at_cursor +from IPython.utils.tokenutil import token_at_cursor, line_at_cursor def expect_token(expected, cell, cursor_pos): token = token_at_cursor(cell, cursor_pos) @@ -18,8 +18,8 @@ def expect_token(expected, cell, cursor_pos): line_with_cursor = '%s|%s' % (line[:column], line[column:]) line nt.assert_equal(token, expected, - "Excpected %r, got %r in: %s" % ( - expected, token, line_with_cursor) + "Expected %r, got %r in: %r (pos %i)" % ( + expected, token, line_with_cursor, cursor_pos) ) def test_simple(): @@ -30,7 +30,14 @@ def test_simple(): def test_function(): cell = "foo(a=5, b='10')" expected = 'foo' - for i in (6,7,8,10,11,12): + # up to `foo(|a=` + for i in range(cell.find('a=') + 1): + expect_token("foo", cell, i) + # find foo after `=` + for i in [cell.find('=') + 1, cell.rfind('=') + 1]: + expect_token("foo", cell, i) + # in between `5,|` and `|b=` + for i in range(cell.find(','), cell.find('b=')): expect_token("foo", cell, i) def test_multiline(): @@ -39,25 +46,31 @@ def test_multiline(): 'b = hello("string", there)' ]) expected = 'hello' - start = cell.index(expected) + start = cell.index(expected) + 1 for i in range(start, start + len(expected)): expect_token(expected, cell, i) expected = 'there' - start = cell.index(expected) + start = cell.index(expected) + 1 for i in range(start, start + len(expected)): expect_token(expected, cell, i) def test_attrs(): cell = "foo(a=obj.attr.subattr)" expected = 'obj' - idx = cell.find('obj') + idx = cell.find('obj') + 1 for i in range(idx, idx + 3): expect_token(expected, cell, i) - idx = idx + 4 + idx = cell.find('.attr') + 2 expected = 'obj.attr' for i in range(idx, idx + 4): expect_token(expected, cell, i) - idx = idx + 5 + idx = cell.find('.subattr') + 2 expected = 'obj.attr.subattr' for i in range(idx, len(cell)): expect_token(expected, cell, i) + +def test_line_at_cursor(): + cell = "" + (line, offset) = line_at_cursor(cell, cursor_pos=11) + assert line == "", ("Expected '', got %r" % line) + assert offset == 0, ("Expected '', got %r" % line) diff --git a/IPython/utils/tests/test_traitlets.py b/IPython/utils/tests/test_traitlets.py index 9251ae8..1aa6bd6 100644 --- a/IPython/utils/tests/test_traitlets.py +++ b/IPython/utils/tests/test_traitlets.py @@ -16,11 +16,11 @@ import nose.tools as nt from nose import SkipTest from IPython.utils.traitlets import ( - HasTraits, MetaHasTraits, TraitType, Any, CBytes, Dict, + HasTraits, MetaHasTraits, TraitType, Any, Bool, CBytes, Dict, Int, Long, Integer, Float, Complex, Bytes, Unicode, TraitError, - Undefined, Type, This, Instance, TCPAddress, List, Tuple, + Union, Undefined, Type, This, Instance, TCPAddress, List, Tuple, ObjectName, DottedObjectName, CRegExp, link, directional_link, - EventfulList, EventfulDict + EventfulList, EventfulDict, ForwardDeclaredType, ForwardDeclaredInstance, ) from IPython.utils import py3compat from IPython.testing.decorators import skipif @@ -684,6 +684,20 @@ class TestThis(TestCase): self.assertEqual(f.t, b) self.assertRaises(TraitError, setattr, b, 't', f) + def test_this_in_container(self): + + class Tree(HasTraits): + value = Unicode() + leaves = List(This()) + + tree = Tree( + value='foo', + leaves=[Tree('bar'), Tree('buzz')] + ) + + with self.assertRaises(TraitError): + tree.leaves = [1, 2] + class TraitTestBase(TestCase): """A best testing class for basic trait types.""" @@ -746,6 +760,25 @@ class AnyTraitTest(TraitTestBase): _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j] _bad_values = [] +class UnionTrait(HasTraits): + + value = Union([Type(), Bool()]) + +class UnionTraitTest(TraitTestBase): + + obj = UnionTrait(value='IPython.utils.ipstruct.Struct') + _good_values = [int, float, True] + _bad_values = [[], (0,), 1j] + +class OrTrait(HasTraits): + + value = Bool() | Unicode() + +class OrTraitTest(TraitTestBase): + + obj = OrTrait() + _good_values = [True, False, 'ten'] + _bad_values = [[], (0,), 1j] class IntTrait(HasTraits): @@ -1306,3 +1339,103 @@ class TestEventful(TestCase): # Is the output correct? self.assertEqual(a.x, {c: c for c in 'bz'}) + +### +# Traits for Forward Declaration Tests +### +class ForwardDeclaredInstanceTrait(HasTraits): + + value = ForwardDeclaredInstance('ForwardDeclaredBar') + +class ForwardDeclaredTypeTrait(HasTraits): + + value = ForwardDeclaredType('ForwardDeclaredBar') + +class ForwardDeclaredInstanceListTrait(HasTraits): + + value = List(ForwardDeclaredInstance('ForwardDeclaredBar')) + +class ForwardDeclaredTypeListTrait(HasTraits): + + value = List(ForwardDeclaredType('ForwardDeclaredBar')) +### +# End Traits for Forward Declaration Tests +### + +### +# Classes for Forward Declaration Tests +### +class ForwardDeclaredBar(object): + pass + +class ForwardDeclaredBarSub(ForwardDeclaredBar): + pass +### +# End Classes for Forward Declaration Tests +### + +### +# Forward Declaration Tests +### +class TestForwardDeclaredInstanceTrait(TraitTestBase): + + obj = ForwardDeclaredInstanceTrait() + _default_value = None + _good_values = [None, ForwardDeclaredBar(), ForwardDeclaredBarSub()] + _bad_values = ['foo', 3, ForwardDeclaredBar, ForwardDeclaredBarSub] + +class TestForwardDeclaredTypeTrait(TraitTestBase): + + obj = ForwardDeclaredTypeTrait() + _default_value = None + _good_values = [None, ForwardDeclaredBar, ForwardDeclaredBarSub] + _bad_values = ['foo', 3, ForwardDeclaredBar(), ForwardDeclaredBarSub()] + +class TestForwardDeclaredInstanceList(TraitTestBase): + + obj = ForwardDeclaredInstanceListTrait() + + def test_klass(self): + """Test that the instance klass is properly assigned.""" + self.assertIs(self.obj.traits()['value']._trait.klass, ForwardDeclaredBar) + + _default_value = [] + _good_values = [ + [ForwardDeclaredBar(), ForwardDeclaredBarSub(), None], + [None], + [], + None, + ] + _bad_values = [ + ForwardDeclaredBar(), + [ForwardDeclaredBar(), 3], + '1', + # Note that this is the type, not an instance. + [ForwardDeclaredBar] + ] + +class TestForwardDeclaredTypeList(TraitTestBase): + + obj = ForwardDeclaredTypeListTrait() + + def test_klass(self): + """Test that the instance klass is properly assigned.""" + self.assertIs(self.obj.traits()['value']._trait.klass, ForwardDeclaredBar) + + _default_value = [] + _good_values = [ + [ForwardDeclaredBar, ForwardDeclaredBarSub, None], + [], + [None], + None, + ] + _bad_values = [ + ForwardDeclaredBar, + [ForwardDeclaredBar, 3], + '1', + # Note that this is an instance, not the type. + [ForwardDeclaredBar()] + ] +### +# End Forward Declaration Tests +### diff --git a/IPython/utils/tokenutil.py b/IPython/utils/tokenutil.py index 9a14e04..48cc82c 100644 --- a/IPython/utils/tokenutil.py +++ b/IPython/utils/tokenutil.py @@ -23,6 +23,36 @@ def generate_tokens(readline): # catch EOF error return +def line_at_cursor(cell, cursor_pos=0): + """Return the line in a cell at a given cursor position + + Used for calling line-based APIs that don't support multi-line input, yet. + + Parameters + ---------- + + cell: text + multiline block of text + cursor_pos: integer + the cursor position + + Returns + ------- + + (line, offset): (text, integer) + The line with the current cursor, and the character offset of the start of the line. + """ + offset = 0 + lines = cell.splitlines(True) + for line in lines: + next_offset = offset + len(line) + if next_offset >= cursor_pos: + break + offset = next_offset + else: + line = "" + return (line, offset) + def token_at_cursor(cell, cursor_pos=0): """Get the token at a given cursor @@ -47,7 +77,9 @@ def token_at_cursor(cell, cursor_pos=0): # token, text, start, end, line = tup start_col = tok.start[1] end_col = tok.end[1] - if offset + start_col > cursor_pos: + # allow '|foo' to find 'foo' at the beginning of a line + boundary = cursor_pos + 1 if start_col == 0 else cursor_pos + if offset + start_col >= boundary: # current token starts after the cursor, # don't consume it break diff --git a/IPython/utils/traitlets.py b/IPython/utils/traitlets.py index c81f68a..8ec27cb 100644 --- a/IPython/utils/traitlets.py +++ b/IPython/utils/traitlets.py @@ -55,7 +55,7 @@ except: from .importstring import import_item from IPython.utils import py3compat from IPython.utils import eventful -from IPython.utils.py3compat import iteritems +from IPython.utils.py3compat import iteritems, string_types from IPython.testing.skipdoctest import skip_doctest SequenceTypes = (list, tuple, set, frozenset) @@ -133,13 +133,13 @@ def parse_notifier_name(name): >>> parse_notifier_name(None) ['anytrait'] """ - if isinstance(name, str): + if isinstance(name, string_types): return [name] elif name is None: return ['anytrait'] elif isinstance(name, (list, tuple)): for n in name: - assert isinstance(n, str), "names must be strings" + assert isinstance(n, string_types), "names must be strings" return name @@ -192,14 +192,14 @@ class link(object): raise TypeError('At least two traitlets must be provided.') self.objects = {} + initial = getattr(args[0][0], args[0][1]) - for obj,attr in args: - if getattr(obj, attr) != initial: - setattr(obj, attr, initial) + for obj, attr in args: + setattr(obj, attr, initial) - callback = self._make_closure(obj,attr) + callback = self._make_closure(obj, attr) obj.on_trait_change(callback, attr) - self.objects[(obj,attr)] = callback + self.objects[(obj, attr)] = callback @contextlib.contextmanager def _busy_updating(self): @@ -218,13 +218,12 @@ class link(object): if self.updating: return with self._busy_updating(): - for obj,attr in self.objects.keys(): - if obj is not sending_obj or attr != sending_attr: - setattr(obj, attr, new) - + for obj, attr in self.objects.keys(): + setattr(obj, attr, new) + def unlink(self): for key, callback in self.objects.items(): - (obj,attr) = key + (obj, attr) = key obj.on_trait_change(callback, attr, remove=True) @skip_doctest @@ -252,8 +251,7 @@ class directional_link(object): # Update current value src_attr_value = getattr(source[0], source[1]) for obj, attr in targets: - if getattr(obj, attr) != src_attr_value: - setattr(obj, attr, src_attr_value) + setattr(obj, attr, src_attr_value) # Wire self.source[0].on_trait_change(self._update, self.source[1]) @@ -417,7 +415,11 @@ class TraitType(object): def __set__(self, obj, value): new_value = self._validate(obj, value) - old_value = self.__get__(obj) + try: + old_value = obj._trait_values[self.name] + except KeyError: + old_value = None + obj._trait_values[self.name] = new_value try: silent = bool(old_value == new_value) @@ -445,6 +447,12 @@ class TraitType(object): else: return value + def __or__(self, other): + if isinstance(other, Union): + return Union([self] + other.trait_types) + else: + return Union([self, other]) + def info(self): return self.info_text @@ -748,7 +756,16 @@ class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)): class ClassBasedTraitType(TraitType): - """A trait with error reporting for Type, Instance and This.""" + """ + A trait with error reporting and string -> type resolution for Type, + Instance and This. + """ + + def _resolve_string(self, string): + """ + Resolve a string supplied for a type into an actual object. + """ + return import_item(string) def error(self, obj, value): kind = type(value) @@ -813,7 +830,7 @@ class Type(ClassBasedTraitType): """Validates that the value is a valid object instance.""" if isinstance(value, py3compat.string_types): try: - value = import_item(value) + value = self._resolve_string(value) except ImportError: raise TraitError("The '%s' trait of %s instance must be a type, but " "%r could not be imported" % (self.name, obj, value)) @@ -842,9 +859,9 @@ class Type(ClassBasedTraitType): def _resolve_classes(self): if isinstance(self.klass, py3compat.string_types): - self.klass = import_item(self.klass) + self.klass = self._resolve_string(self.klass) if isinstance(self.default_value, py3compat.string_types): - self.default_value = import_item(self.default_value) + self.default_value = self._resolve_string(self.default_value) def get_default_value(self): return self.default_value @@ -876,7 +893,7 @@ class Instance(ClassBasedTraitType): """Construct an Instance trait. This trait allows values that are instances of a particular - class or its sublclasses. Our implementation is quite different + class or its subclasses. Our implementation is quite different from that of enthough.traits as we don't allow instances to be used for klass and we handle the ``args`` and ``kw`` arguments differently. @@ -951,7 +968,7 @@ class Instance(ClassBasedTraitType): def _resolve_classes(self): if isinstance(self.klass, py3compat.string_types): - self.klass = import_item(self.klass) + self.klass = self._resolve_string(self.klass) def get_default_value(self): """Instantiate a default value instance. @@ -967,6 +984,33 @@ class Instance(ClassBasedTraitType): return dv +class ForwardDeclaredMixin(object): + """ + Mixin for forward-declared versions of Instance and Type. + """ + def _resolve_string(self, string): + """ + Find the specified class name by looking for it in the module in which + our this_class attribute was defined. + """ + modname = self.this_class.__module__ + return import_item('.'.join([modname, string])) + + +class ForwardDeclaredType(ForwardDeclaredMixin, Type): + """ + Forward-declared version of Type. + """ + pass + + +class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance): + """ + Forward-declared version of Instance. + """ + pass + + class This(ClassBasedTraitType): """A trait for instances of the class containing this trait. @@ -990,6 +1034,52 @@ class This(ClassBasedTraitType): self.error(obj, value) +class Union(TraitType): + """A trait type representing a Union type.""" + + def __init__(self, trait_types, **metadata): + """Construct a Union trait. + + This trait allows values that are allowed by at least one of the + specified trait types. + + Parameters + ---------- + trait_types: sequence + The list of trait types of length at least 1. + + Notes + ----- + Union([Float(), Bool(), Int()]) attempts to validate the provided values + with the validation function of Float, then Bool, and finally Int. + """ + self.trait_types = trait_types + self.info_text = " or ".join([tt.info_text for tt in self.trait_types]) + self.default_value = self.trait_types[0].get_default_value() + super(Union, self).__init__(**metadata) + + def instance_init(self, obj): + for trait_type in self.trait_types: + trait_type.name = self.name + trait_type.this_class = self.this_class + if hasattr(trait_type, '_resolve_classes'): + trait_type._resolve_classes() + super(Union, self).instance_init(obj) + + def validate(self, obj, value): + for trait_type in self.trait_types: + try: + return trait_type._validate(obj, value) + except TraitError: + continue + self.error(obj, value) + + def __or__(self, other): + if isinstance(other, Union): + return Union(self.trait_types + other.trait_types) + else: + return Union(self.trait_types + [other]) + #----------------------------------------------------------------------------- # Basic TraitTypes implementations/subclasses #----------------------------------------------------------------------------- @@ -1193,7 +1283,7 @@ class ObjectName(TraitType): def validate(self, obj, value): value = self.coerce_str(obj, value) - if isinstance(value, str) and py3compat.isidentifier(value): + if isinstance(value, string_types) and py3compat.isidentifier(value): return value self.error(obj, value) @@ -1202,7 +1292,7 @@ class DottedObjectName(ObjectName): def validate(self, obj, value): value = self.coerce_str(obj, value) - if isinstance(value, str) and py3compat.isidentifier(value, dotted=True): + if isinstance(value, string_types) and py3compat.isidentifier(value, dotted=True): return value self.error(obj, value) @@ -1354,8 +1444,10 @@ class Container(Instance): return self.klass(validated) def instance_init(self, obj): - if isinstance(self._trait, Instance): - self._trait._resolve_classes() + if isinstance(self._trait, TraitType): + self._trait.this_class = self.this_class + if hasattr(self._trait, 'instance_init'): + self._trait.instance_init(obj) super(Container, self).instance_init(obj) diff --git a/docs/Makefile b/docs/Makefile index 39e29f7..f7c2e4a 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -42,6 +42,7 @@ clean: clean_api -rm -rf build/* dist/* -cd $(SRCDIR)/config/options; cat generated | xargs -r rm -f -rm -rf $(SRCDIR)/config/options/generated + -rm -f $(SRCDIR)/interactive/magics-generated.txt pdf: latex cd build/latex && make all-pdf @@ -58,8 +59,8 @@ dist: html cp -al build/html . @echo "Build finished. Final docs are in html/" -html: api autoconfig -html_noapi: clean_api autoconfig +html: api autoconfig automagic +html_noapi: clean_api autoconfig automagic html html_noapi: mkdir -p build/html build/doctrees @@ -67,6 +68,12 @@ html html_noapi: @echo @echo "Build finished. The HTML pages are in build/html." +automagic: source/interactive/magics-generated.txt + +source/interactive/magics-generated.txt: autogen_magics.py + python autogen_magics.py + @echo "Created docs for line & cell magics" + autoconfig: source/config/options/generated source/config/options/generated: diff --git a/docs/autogen_api.py b/docs/autogen_api.py index b504f80..e1fc7a6 100755 --- a/docs/autogen_api.py +++ b/docs/autogen_api.py @@ -21,10 +21,12 @@ if __name__ == '__main__': # Extensions are documented elsewhere. r'\.extensions', r'\.config\.profile', - # These should be accessed via nbformat.current - r'\.nbformat\.v\d+', + # Old nbformat versions + r'\.nbformat\.v[1-2]', # Public API for this is in kernel.zmq.eventloops r'\.kernel\.zmq\.gui', + # Magics are documented separately + r'\.core\.magics', ] # The inputhook* modules often cause problems on import, such as trying to @@ -39,14 +41,21 @@ if __name__ == '__main__': r'\.core\.magics\.deprecated', # We document this manually. r'\.utils\.py3compat', - # These are exposed by nbformat.current + # These are exposed by nbformat r'\.nbformat\.convert', r'\.nbformat\.validator', + r'\.nbformat\.notebooknode', + # Deprecated + r'\.nbformat\.current', + # Exposed by nbformat.vN + r'\.nbformat\.v[3-4]\.nbbase', # These are exposed in display r'\.core\.display', r'\.lib\.display', # This isn't actually a module - r'\.html\.fabfile', + r'\.html\.tasks', + # This is private + r'\.html\.allow76' ] # These modules import functions and classes from other places to expose @@ -54,7 +63,9 @@ if __name__ == '__main__': # non-API modules they import from should be excluded by the skip patterns # above. docwriter.names_from__all__.update({ - 'IPython.nbformat.current', + 'IPython.nbformat', + 'IPython.nbformat.v3', + 'IPython.nbformat.v4', 'IPython.display', }) diff --git a/docs/autogen_config.py b/docs/autogen_config.py index 1086cf8..854dd2b 100755 --- a/docs/autogen_config.py +++ b/docs/autogen_config.py @@ -70,11 +70,12 @@ if __name__ == '__main__': write_doc('terminal', 'Terminal IPython options', TerminalIPythonApp().classes) write_doc('kernel', 'IPython kernel options', kernel_classes, - preamble="These options can be used in :file:`ipython_notebook_config.py` " - "or in :file:`ipython_qtconsole_config.py`") + preamble="These options can be used in :file:`ipython_kernel_config.py`", + ) nbclasses = set(NotebookApp().classes) - set(kernel_classes) write_doc('notebook', 'IPython notebook options', nbclasses, - preamble="Any of the :doc:`kernel` can also be used.") + preamble="To configure the IPython kernel, see :doc:`kernel`." + ) try: from IPython.qt.console.qtconsoleapp import IPythonQtConsoleApp @@ -84,5 +85,6 @@ if __name__ == '__main__': else: qtclasses = set(IPythonQtConsoleApp().classes) - set(kernel_classes) write_doc('qtconsole', 'IPython Qt console options', qtclasses, - preamble="Any of the :doc:`kernel` can also be used.") + preamble="To configure the IPython kernel, see :doc:`kernel`." + ) diff --git a/docs/autogen_magics.py b/docs/autogen_magics.py new file mode 100644 index 0000000..7355733 --- /dev/null +++ b/docs/autogen_magics.py @@ -0,0 +1,64 @@ +from IPython.core.alias import Alias +from IPython.core.interactiveshell import InteractiveShell +from IPython.core.magic import MagicAlias +from IPython.utils.text import dedent, indent + +shell = InteractiveShell.instance() +magics = shell.magics_manager.magics + +def _strip_underline(line): + chars = set(line.strip()) + if len(chars) == 1 and ('-' in chars or '=' in chars): + return "" + else: + return line + +def format_docstring(func): + docstring = (func.__doc__ or "Undocumented").rstrip() + docstring = indent(dedent(docstring)) + # Sphinx complains if indented bits have rst headings in, so strip out + # any underlines in the docstring. + lines = [_strip_underline(l) for l in docstring.splitlines()] + return "\n".join(lines) + +output = [ +"Line magics", +"===========", +"", +] + +# Case insensitive sort by name +def sortkey(s): return s[0].lower() + +for name, func in sorted(magics['line'].items(), key=sortkey): + if isinstance(func, Alias) or isinstance(func, MagicAlias): + # Aliases are magics, but shouldn't be documented here + # Also skip aliases to other magics + continue + output.extend([".. magic:: {}".format(name), + "", + format_docstring(func), + ""]) + +output.extend([ +"Cell magics", +"===========", +"", +]) + +for name, func in sorted(magics['cell'].items(), key=sortkey): + if name == "!": + # Special case - don't encourage people to use %%! + continue + if func == magics['line'].get(name, 'QQQP'): + # Don't redocument line magics that double as cell magics + continue + if isinstance(func, MagicAlias): + continue + output.extend([".. cellmagic:: {}".format(name), + "", + format_docstring(func), + ""]) + +with open("source/interactive/magics-generated.txt", "w") as f: + f.write("\n".join(output)) \ No newline at end of file diff --git a/docs/resources/ipynb_icon_48x48.png b/docs/resources/ipynb_icon_48x48.png new file mode 100644 index 0000000000000000000000000000000000000000..0bc76abcab86710c1ccd05b6d2ccb8f48d4f2d48 GIT binary patch literal 2342 zc$@(y3EB3EP)NklC|R76K{}3juLKQI1*FoO3Uq zd?M!$z^7hbUO!WWk>Ng_V;}d=%F5~&4h{}~=JWX*oSd9CFU80>8*OZC*6?_|ugG{` z+S%FtiTM8D=;-)`tE=nh^jZpsa&vS0B0oQ0(%aiR_W1GR@uk@P`}fC-ii*ZcN=n`r z78Z`CrlyW$Wo12$ii)}+5D0h#=xYi_j(I0ZIA{qk&0~j72#?a6ZJMQf4oTRft#Jkea`e2CV z1fVl<=gu9BjEvybt5a`ss*6d+S-bjFJFQrJv(k} zY-F!3EiE7+^@afa@q;SWasyDKN&V!>lX?E8rlt{VYwNFm5gsAHP+wmk`}gmMsi`SQnL|%c4;!4utd^D* z)YR0Vt*s4pb#-EWM*~DonH>KzmOg?EDj97?1;NpV*ZJ7#|;JGhkw3 z!ugD1e~jq<%MP9kV_!<{_W`d)d8^ED|a3MS+cWapXswXmkJZU zA3MHOSPkXFCzl35WApXv*NBUYV*oTXX!H6|fNd^mvjFJ0Mn*=+$jF#=o`nx7r4C+G z=S<>WXZO+oLPA1VbD}XmZS?*1>(|*uD4d3d20T4IF^u$5gQwySYCk6zUQcHdC1wTH*e)$ zaOB+>Dw?}ceya=JeS^%G{ljf&23KNVU-sGsz8o3>Wf12Ut}K87X-eI_eT!bOod6sp;txdDz=|y3s4ug!iHI-7 z9{-Y+2cUK=AWaUyAQP_>eiK$x%Yw|xM(jCT40XREh_9|7fVO8EOp@=AS&*Q)y>nrJ z$Ae6~R@e<#dR<_?jEY9=CgRn63Xp!aVC4Y*om~W@_**a}W^(4@q5xW?dQo+_ zUf(?Lf^Clnn0U>gN?4qh0f@^Rn0OV>YYf0IwH!N6UBUK~nb;=Gz&~Bmu|;qRo9$zf zTT!QAfP@kl6Y)j~%>$$JV;+$*fU*WAUitJ@ zn0O{J@#{%vRz2*3?~eOntC<@%IV9lg<0<4d5u)r8#Q``c6f^OLWLro9sLwiBOe#$+ z4n$NVyY%Kf04knVonsym%$Hs!fwIR{>~zn@4!100cMHk(Uf5 zb3bm6uw;M}@kKCyBWP$yOAsC&2!pj3h#MErIl zezQX?q)oRL%pXC>MQk85pkx;h->4MD0344kWa4Rang*ce7@ol@4UPBs%pXhR-G|SZ z_}#vRI6AdCxm;S$#M5|JHgj3Li@zDgI;%*mvx&u4Q=wu2_%Q`sXPpV~{3Th5`sT4@ z-Q|+RHuExoekNY+Og^`}x?EDn#BX&?hrMTr+_JgFDv$xJH9DyvfarW#XFXp7$qWFt zDT&I$-auKr`zK^_0K15IRqs3;aSP=DrYlWI>_xc%dwC%Y;P0ls7#kZ~I)GF3_p;6! zaT5~hY=F0;@1W)`W?fcUkjyqacYFGw>Xk>TPA<$(%6{!j#kEZQHz!gNo02IPK>I|* z6u?<_b8N`~ju-M+vTBofp9?_EKlz$0WJsfO2@#={0H~bKB~>Q}hlNuBMKw(PX8uLE z21LsR*kL1^fxqbr6ca+c^Vhg!T?l}dI~9S5HLC3=l2O~*g`Gq^t@Ve6!R(wg0zk!Y zauh+|(Qi@lVH_I ztUVG)s?D1P#}VaMGx5}n>`o~PAcu*k$+{Q-H5GmgYgF6aGMV_TLNUyyZjq!FR+IXk z1SR_fthbGaiOc*-^zi8rHj%wE6~E?NZ*))HKwdI{eRww4S>*ww7v5l9b{iG%DkfFu zG7bsO$+~R8%_;G5SZnCYMDDT;LPUBAO6uEjDkg^uopm-b_|CyyaZ3riuq(372&pvk z0H~?*!a4CtI5|1}zO1ZFx*7o5+S*>)*w}peK>*Iq&c6x{4mJ%73kwMi z4UJqWBiD~0ukPdr{mlnGC#Fxv`F#E_>7#!7<(0Cje>M1Qu_yKa1Gzp4a{vGU M07*qoM6N<$g2_i+GXMYp literal 0 Hc$@`_. Use ``%load_ext rpy2.ipython`` to load it, and see :mod:`rpy2.ipython.rmagic` for details of how to use it. +* ``cythonmagic``used to be bundled, but is now part of `cython `_ + Use ``%load_ext Cython`` to load it. diff --git a/docs/source/config/intro.rst b/docs/source/config/intro.rst index c7ef592..2f9cd48 100644 --- a/docs/source/config/intro.rst +++ b/docs/source/config/intro.rst @@ -41,7 +41,7 @@ extend, :meth:`~IPython.config.loader.LazyConfigValue.prepend` (like extend, but at the front), add and update (which works both for dicts and sets):: - c.InteractiveShellApp.extensions.append('cythonmagic') + c.InteractiveShellApp.extensions.append('Cython') .. versionadded:: 2.0 list, dict and set methods for config values diff --git a/docs/source/development/figs/ipy_kernel_and_terminal.png b/docs/source/development/figs/ipy_kernel_and_terminal.png new file mode 100644 index 0000000000000000000000000000000000000000..f17fda69549b15a8c36a946d8874f5ad5a58a6dc GIT binary patch literal 28265 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 + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + Pythonexecution + + stdin &stdout + TerminalIPython + + JSON,ØMQ + IPythonKernel + + + + + + + + + + + + + Messages + + diff --git a/docs/source/development/figs/nbconvert.png b/docs/source/development/figs/nbconvert.png new file mode 100644 index 0000000000000000000000000000000000000000..e9864b9d10904ef6a5c4b9fb9969d3946baea0d5 GIT binary patch literal 23877 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 + + + + + + + + + image/svg+xml + + + + + + + + + + Notebook + + Preprocessors + + + Exporter + + + + + + Exportedfile + + + Postprocessors + + diff --git a/docs/source/development/figs/notebook_components.png b/docs/source/development/figs/notebook_components.png new file mode 100644 index 0000000000000000000000000000000000000000..3b010050ae71b6e4c362051e7d5649d315cc09be GIT binary patch literal 30974 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 RQt*J6hKi1InUd|>{{?6-g6IGM literal 0 Hc$@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + Browser + + + + Notebookserver + + + + Kernel + + + + Notebookfile + + + + + + User + + + + + + + + + + ØMQ + + + + + + + HTTP &Websockets + + + + + + + + + + + + + + + + diff --git a/docs/source/development/figs/other_kernels.png b/docs/source/development/figs/other_kernels.png new file mode 100644 index 0000000000000000000000000000000000000000..067e45d4d75592d9d1f260ccaa6e58c46a493ab9 GIT binary patch literal 41180 zc%0OF1yfti`!?2UaUBU;7}+Ox8m;ZUIG+%_u}r(gHwvTySqEQhu_TqL%cha z$t07UvwQZsFWKEt#cz`6C`2ewP*CX7QeY)0DCkh&Wr&0Td@~4hrvbddIf_dEKmwW< zk`V;>jBF>R;Rw9(y}zK(jbdehFA1H*)t!`WO`Kfy9gLw|U0qqsZ7dxP_3eyVY#mI~ zP6dddpeUfE!C!y4r5|Uwdf^%0zO9*_wjWR;Gl=j}Mq^2dLLIa-=-^r*&Er35x-cJU zc2pdlj?Fmh3>y%%<|5GtNa5t);d|I3GegHldz@W88OBD-p33{v@E~7}GP2t5rlfG3 z|MS}A9djQA72qQLuSJe)Tl@I~?dwY)7BcL6EBrvE{}X8IxMDPD?=9T`67>EPe`T~7 zw)bWiMv48ue}7{KcXz!vhF?-2-rJY|FD~Lz4~>vOO%)?TM*)ZAa#;#p=&$_EhljDi zWv=-G0U?QkYC=SkFfxrjmpj<;Nq&T4)V_mJlH#XU~RqSEqo{ZKSssiNV( zb!;#V%0A*X^Q-OfrVg*jP><4WCml5LV-LR4Zs$i|8znCFxfSEBKCDm=KS;7)Oqbbp2m{eeL1$<<>y5EY@xNh#PR#N4`{fruODNQ6 zU9VH#E?1f2w&6B-*gz$4FPb*yZzlX;{D2}8&f0z~!${gxDXNREt(ws+1tG~V-Cr>r z{9wfgcx{yE+4cF1`}oN33Ga0oR1^anoo9fDj|z&xTF2~a7lEtF49`7pRXWsW)|o@y z|4)2kAHxWW9Qy220mf{Is zKFHUP5GmHLWH9}y>~W%D#i%++RD&OHjbev1XKONV#Nlzxx)e}u5|u1+WYH|vKTv%S zfebQt;q1q`v`*;tDkmuI=~$?x{Rn5TVx5e372u+R4#e7*5CwNp{*QMr|yG+wzX<7$u3)| zMtMUmDY|tU-jPwk^e45Y87ww%E8jkuO2;8(-W)UMnBz#qSLodnoRY(L~D-d!I^M?2gnnzx? zZOSI%>8mKfELZ1u&ZiFi5JaF`!CfjbNFCM3WP)xP|Epmo?1rodUWH7I#sUkJg47l+ zwDFX!WYHWhUxEkDCv8D3;W<|;m}X?*9?HhYit&Vd(Bnuor~aUiLbp6aJA-nNpVXAC zSR~9?uxeK?e}fEM2MSc3EeS2?CluyZSIxT3;PO=p|QY%2#a#O*Mew;8XZcT2}iW8V`abe z5O(Yy<)VGQw4S$7mU-E?NYj?yA`_dXqPnE7!V>IMDlAo@fQnZ_9G~)fhVr7Ldcy|g z*>X{Uw;8_rM0{)*@?LhGL4gyY@Ru{G;q`}O{rLy-+&+H`fHB}{)!fm0j_x{vRF)hnLl`;deE32*$6lXneT_`OrpoHYX z+h<)I`&XD}&5N0j5Xf(ER^bD}%8L<_xS%*#7jIi3kC2&kf8E0I@Z-|LsH<0@Ulgvz z2Ha$9SJXE+y^_BFJ4VS~ybw zH%-j8g(DFh|H2UGp9S2*t4*whQiC8X2pA75M$LaZ0E7xEXuDEf|cD7v5{N z&P}}+XGtG9?yeR6WV2T@E@h{j`JSUA{Wj3f6|dY0@dE@+Mvmq8JK@ zyd{fJ!A5A0&%Jpv#XLIJV>3aJWjHVX0Fcw4+R_noTq{H3wJX7Wf2On6#mvQSEM!Dn zQwN<;UAdQUYD-heSTO|SjzT)7@o{CuU>fo0gpB=d?f_6f6@=!BbE)W!ty#T6 z2_T?=-pTvd?>9pp8450Std#O{=UHC`$1$5E80y#R@wsfkG+?o%58vDdX=qdAleMjt z@z@dGsB0lAVhz@ba+wOq1{(ENd$jo zrl7JRT9mVG3$)!e5u>nx{@N5nEH7_+U?vqvOs z;{MC?-|gB{bJR^=1V_7a*0F36+FtZ8&Fpe&F5B;M+tenr4_KN-u%OmaV)sD?SGsz- z03y`{p1?fAHlW z3T3DB*Ubf~qqh~rKh~C>*atzHVCBPoVgEiiZ@&rp$x-Cp-%ScU1;Z#xBr(6pFd8a7jA!s3X>24<-5nz@~ZQ)`!kK!_i~&Mf0oDyW8G zIxO}gJlU;;j#OAkaO%~M4Do3?p5W#!-TlzQ%WhN-ljbh?1X&jM?UXS3&w7)qe{zgb z9qZH+X7Wk0m&$ga<*K zBz!4_%Htnhne|H1$0ei5Xyv@a=`9$L{NeDyI45KhA2xz;z=|2WS7X#sh9q0`lDz*m zy7NdrW_ih86dBgz9i8p_O3EfExz`(qK$an7MEnvAT(u1qQ^}whvaT9^v^W+g`+8nA zF0J`)%x5UJ9}G9)r6hw->e|cXM~e5nvsvNw}uI-w&@;VSH|z8dzV0@TK#4 z)9pl&`_LO@#+2vMH_#-*eK8!^&NvHK;F_})vlyl&Ws1@3?R7v8MEg;vE_}+wb7o}_ zND}f?0}>S#E&Gri#|&)8uH};MZDxZ(gtANq0I#Is87AyXC`@Qli<)`&i)B&0ucsiR zbi!JiIIB}BN%ODasV#CDu?j*<^ZlB$`1x_CCjD*gkX%=P)&wO&E@x5@GF(GWl%`EZ z@s%z*WYCvcoy;p5QU3M5b&&*?BF$=b_axWrva^9<39Po{*sPsEMM<^dXWX^Ll^`Ys zrt*`fmAlid{v`=gnNJx_Y^dw|-8{KPj6(P2KLqX~d8<#KvZ213ELM3VF%^YQBtw3S zj(zZqQ#x#AUxqX zHdJ%40X?972m_v_1~mc?;8B-?%q4NZ=isE6F%?&Y$QhYA$s zes2D7;-nTqv^0nnd0UwCZ^o|e7vI`nPS2uM;1^DlpH+Y zrvmP|qMQggP$;g^83lrd$K(`nmmggovm9v)+`$%=@rxT4o+UjqwAkdit?ZhN1uQ?V z)Lqx@(N7+rVCpu|T*E25LoGrIB1Kh&+$70xBh3{> zYTSLDh^)L1g^=7j4nHF0slot&VC-4jEr^P0QzQ@upYY`L%3kp~+>NJxZ>5mD` z|CpP)g0+boI|?j11+$u$#(4^<=x}7>u*q zi|T1A#M*cOcITiSC*o{IDmU5hkS=ZLa~d|#8iTMkTP3(1P#BC-UJc=A)vF`R{s!=Z z%u%i2$&_RWZ>pg|&1A{;bgtOq@uef|MF*5svoxK79;z)KO?8%-K!_|8v+N?m7a+Y(gl~5iFGf zyZ$1ctkrb+vcZ$-1LUHx{hp(SG_NVM8+2 z>sF_VB(bSB(T`z$lw@9VVjKRWm2+ISlO-E-ll`*cY`CowhR0Z&Y`pSUwLsC8zM!M5 z$lIPW$#jBxDsna=;RY3SF*T0i{HJz}?N{?6Ng(4-P`G-3jR0|jS-~_i;J`o;FP7V6 zU6^haxe|Ri(?>7ovYG#k#3&A-aAfwxV&)Iae=^f$^VRG4HXK%ukA|; zH~SNV#XP^_oLp^EDefrB?BuFdwHCUG->L*dn#|3755aVC?-5~O9mT#UqQZ&G(hM^c z5(QuvBEg8ge-lRyUFb{&-lqME57<0Ge`FI(fx}V0K&4nI11cOY)I=@0r^4Pe_{bQZI4MAt#w!i8tWZ+<)5=}-Am0J~B0hTIQSV0H zL$F~(zD$S2GfwBm^TBx`J{P;m%jQXndGdu7Q5k+U-J*^bSsa&0VG}&D&`O`!*NyGt zO|9q~qVEnm2&d&lZcoq}`4wL5Ga2dfv3i?4R4Ocxwi4RfALMMMnL$l8H(zygC_UzJ z6v@`~*<(@}=p!ptppJDoHBp9Cb`;eu^p4Vx@If7JD>0i*+Hkr|XWNvo2?z4F29W=P z4vu80Gvk$x{QG|B`x}W#Mi;^$Gg--7s-+ny%7U?_Kj@1SX||KdbAF1!im_YAU%!!! zGYyr>|8=XX_h^A$KYKmXZzho@pR5V(>sa-~mE8z&DmJV=L<5CF_h08G^{GFsH64hv z)}k*#fq;0xoHSVkhH=CCf;wLS!y1Y4Advi1mi)1)|8tT(dJ|R0b2{v_N(BCG%T8BZ(5U zsB_k(g-;>R5ED%q%~MdI-PavUfY?*mUZ_)yM3Jibv!~iK26}*M2=18*0 z{05FfN-gB1%hy5b$m!Tl|4V6Ti@@(Q|Fu6e{AM=I)$H4o)?)41oHvR3c2n6M7p88Y zUJyBImIkCIVQg=Q{6wL$(9_=Pgq_=`uDYfZp)H^da!M{Q)ru8>6Hu*TZB%ILXm&t( zJGvtKUH7~B(st+&9333Lz!=aKUiO=y1D(|q8mgXnCC9(VuLy!Pw_28U~`LTgFBjrTkcdsom% zHUx5Gg&s=LI_p1Z_?9R9$47IRJX~VLV)pG!5&2e@LW8Q2tscBkXJPhkd>2=iBR9^_ z1~?QkF`DciVN6^(Y&vY5JnM)%-?m)wU!1?2KQ-_)e6;zI-s5~<<*1*MG>i5~jfVa*AW8J-9L zgPN&YbB;FjO#jSplF(2F*uUBZe;0cfsy+~{QmzVAoj|t*v`b|q{;lz4*)lqmwhHE6 z_F8_2oe;B^41BHxmQ-f6oQnsy(1^F0U>O&`>8aCv-5IL)!JY5RpkUnaZ)A@7&Y!{y z2MbVe{qV7NhA>(k^PA`M457+je6QP9xGi&7Bwh|G3u06KUeBJzQfRYagu|rDlq}Bh zR?XW*z^N#ose*l7m8j$nd`YEi)tvmdf5U z{|u|ASL2;88hrD|)!b}w5L3c{x$08C5tHz_^1to$LH%7)4)e@O63f>Fl>xcT>{bL| zB0zaUMB?v|zs;z}So)Mkb+hziacwd4wz0uO^+DZ>Bl14ONgKvqxl@8%xC(h1`r z%g?!}){1>H63_x8_FLlX^W`8b+Vg-&EFCRBZYa=MkGBFIVAx)EBZxb?_oglx8+X3X zr`LP{kcRlv_U!a1{Apt8mlj^(ZmHM_zJv;;7O^ea!fvXf%P&q;bW2c%QffbCSuXH8 z7AnU;2yw;C_7q*lR!4@ccO=V3&0HP%HS_C!4ku}mL^?q)r!_f_z|KKc?SwP$ytd0i z4vEFVcSsH1tr4||qkL9|w`@48g6Zo)KSdXV*H8DpxoC11x@MTnmFc)pdyV8yY$*uR zwT@LO1`sV1n@KNHK&Hn-Lad z)@{rBDv<`V{&2a7dS7H}MZGDbel4Z(1Ap*T`k4Q5?RrFA#>{I_KO_Jo_tQyiOz z4`b|4rb`BK_M7Yrdvn_T3wbBMx4t$qL;fIsgA-m%(PqLNeL)y_oAGA=M4*!Bt3NKz zpVp;G)lV{C792!mvHNME#*Yqk^KHNFo!9j2JUXYvSnod$R2Y$t0mK54ZaR%PIyXNJ z1h65z`jXed1WtJZdg-FGIYG?Osg>-VH z^W?5k95w02k00BZtP3MLY+MJ+h<9_0$9-gF{;DzTiW&ul`Rmn{p4Js7m;goNvhm}$ zF?g(b+UTP~T?IGSfdaT+9lY^To<9F#@FZm#ol}=Xi#kSS|D7gR_nh*6Hlz)uxsu>s zjiAKl60>J=XT82mG;ZXS1{*j<6rnxbt z?W#fhTmC2T9sS?(rp}MQ<{(Z!-&%^Aha>4v zwR|Yy2QjxsyT~$u&GUOg9Fyp3p1kE-SxGV|()}*J*?Qa<8<}F2B;OD&u|NXk>#8}l z^*z~!HKYG>H>79^5FTA~Hm zQ^dzu9Nc~{^!zJ%tDxGcz-7*y0KiuKNhCcOO1xl(hQo5wZ{ZPq6f~`bqvnlHJ0c zaw6bj=EJOxldEB&EISfNIb5(-nbZH2rzJLAgD2gi0rmNgJOMNlz_a7TOE8x^@XpK56J490Z+mZ@(4;k0;K+3LXn_3On~ zz*J}@RC3+V{MYo*RK1ah{tcGWBK#e`z#vweJr}rSvj!Ypvf49GgZ1LcpZ3lw$JH(p3JJs@KU-u)f!PlL-@(jui$w9kD0qj%07 zOw@o1Z)}|Xm$G6CX=hP=Yv)dk(^A+Z*pd7}z9%H@2aAeX_40I5*V2G@F*m?(?Ya^F z3ez;LLYw;YmJQIMEnt4CD)jF;TQ3dN0F&|lLqHuVk5j8AJsDGM=~IdtYey>Eh{v`~ zKGj#chwKKqxTKFb0f5v+cI!KB1gFa&QNr;ohxy4)*?YmeLFcjD`5Zxem#d3?($v?9 z*6}k7FsqOIL#=NG1E6ZDW2OIrdXKl|(yaJti11W?IpuBJ3#A5S|H(_wOU#?GGg@(R zE~%rg?$5u<&jr+Pwhi`1JD^c=L50?|MSTa>PP^)p#+y%ae5QF-KQF5-Up%EGTS-StdC>qa98hm#GAcB#-L{pryRtVNv=sZ)9%);|iqDG5 zOXpx=+>s*+7i*)COHt}4=y>Zl+1E|o706A!7;m}6sY=dXXf4%xEZg$bvbAxhe`k5& z(gD|xDJ+V_kWY5K_e+S5og{gSBDWxo(D6Ile=04V&sPum)^gNE)>uL#UL`Z;?E&o& zQleo%=hQC_!yKLvpS?vAXx~Q3dB@IJ-$wnC5XX;-b%7_!G`D;GP0)?e$4Q{{RoPk1 zcxYQ)T+gjTzXRy~lDI#Og4`dVkAyhGAE!U+Yr}*#e#D;P4-=EcOjsnC<@&wA#AFE<-8(fXKRA4#~fg zo*fJxHmb%sQWfTkF%}q2d%7*?*F+wSi5%AbsCXK4rjeI2*g0Y(B`egpwjEJ<(-Hr> z!cf;)FL<=7HIVZ$Bwd%Ws$QrwvAXiv$VQ~pZ}R1%(dn2I5tNb+mFMACTCL7yO(Lk8 zlOyZvdq+&bX4V=$_DxvERnaC3j%=s@V<7O}2TE&qXyl~9xX?7Nqu48dJEL<=!)EEf zHH=I1H-%wqR?1AbVZzbh*dTl3Y283PWKbwntRDf%@i%h`j?!NyuD*ccBSVmXzIDI2 z=oM|ObcYh-NzPW6S@_P-tfoRtX4T(TJwkkD0NVR}uNTOe@eHeev54}Xg)$XxoIX(2 z-6gfBFT2D70StT_xqW`7qGjo5F~FdRHA9Cok-tp;u}Oyw2n%1T&Npd%>_pj^`5Jy+ znWmAh&b`xe8*>amvjtSuoAr3VpKM6HUl&8fWHES2&}ZOsf(-hISg8QLtx-6&TK|~M zuUN7E!&?Gu;Gbx1rmW9ztOC>vrDb7R!Q0y7)J7!6^{b_=$8$4cc&66&xmZC-{J*RO zI6WvJ25fX~!UF0*NyFZ|ks2ENy{LS=e>r6Dv;HgGdjh&sTJ2HYcEeuUEU?VUi^|Dg zirWVQ&2a;>uQ4xbX2qAs%98ls$HS|pi@!KWkA=jJa>_4IEeG&d|vuxvdV(AhR#tc2#P-}({k(vX8Lt3ib11T(8L--K^oMddFPHi$@alaA&4 zJnS9w(0Fb7ynKPXi|b%;gbbsFXmC*zEx?e{R_p%Ewt(kqGsxLS=ZWb-8MH~ZISzwO;WYB{S)B! zZAjUc9zt_-_sjIibL(B7S`PV6zHjqvT&e9{7zGAi;hz@R zy%DW#tz-dfRCO4kfzDy^oY~&$^aFQ+zeZF|&6(XZSvHWbqWO)8Z*^tDCd*{P z%vaWPwyhn(uNHAE*D8!wU*2iXBE^%pVfTaZS|y?iLLcJ94TvAJh%`J)PkU$o4|X?F z4{TJfz~wJPNUt$FiF$7Q-wvmcU--wtYRwOc0P5HcH_>t*!3U&z>(3KcW$vEnfR^k! zH3>b&P3&lkYYfyTJKr&h@`nK7B_V)m0Ci)ORA?Y3LDGyXU*+KNU)VgqD{HII@(yRx zL}u0JJ;IAwf67z&bVvovC-NQiG7*zVjBeX&Pa51eR4xbp#DhW& zd?YH>?+6Ni&53c=eP{0-ESPXp(LZDLToCl+i6V9X4U}ZkM{Iqa9we*0R=3JZXJ*6=9sY{73E{9cT)OeAO+Bms7Kn=sx`0A}!eHdZ3kNr!h`WowFTrRD z%{d}QWbtRF_&~~%dfu3O)mub=bdt#Qd`(f&<&&_>=mBW?0bsV&Lq}~ zr+|2V<@cn1wY>|FWj=i+#4%*-eT8GE<}`EJWm5kKuA&VDL-9|^&nX9f=cK9a$M~l| zpTWJ7(W~~oIxZBU$#B!+cT&y_$Q{Y-_6SRzOS(31$z%P|K=w-ZXa819n`C!-&g=8G zcXs`z<%{rUS=D2OFzdy5kTzw$n&Vy{Jfit?=h(j%t`4PZWvqR9k!ZY@vFV}3-ebVL zwel@?VzTf>cxS&PZzr;KtC1h+h)6=wOYwkYy_#zPn47KCr^;gXBsCRtF=FmoUJY<6 z0ix+NFS-(q-|PNHWNpSVW|@rpB)~YAqr9Vda8(z15ea|#ToTJFn3g(LP4@6L?@sw< z+mF1i-EO-UQ0VHQ05(-VKsk}E##h$>P@mXY)7I@$*pOvl(OI03dG@b@BXWsh{{BLO z+eh17#=eV%+DhfT3-)WgC@*$sun5_sl%q`jw_`p8#`xx%2c$6(1%x(EKcH5&_7B5Z zo4UCDJup7{dtWj7`19xX_9;c{bVVNUlT_@kUGCsx_kRS^ShG?Lly^XT4=JhBh5cp5 z#S&_#{cxGq{)?Qg%jlv8k%SK3Bu@d(_$R*xo;u?xx}B+$q~Ai5+`7M|E>Ui&fWSXW z_GS3LkBFp;^*h7NLC)yZ=AugGo!UTxuWQd?o)fS`Cl*@#ne?l(A@n0Yy8J>F`cABY zg|oOAx<4tjm6{B%7K^p=sD}yRpEd3bJ+g-a33QGk@Ay}0k!FqW1=H|9K_2Ah^uNs{ zYDM)v>FQ;taPh6JrSK;%|HrW~X7@6E1l{DldeR#aX*gG=!{lDQ`79PmKcwE~*#E6C zZ>in!E;cDe@;TWJpcaw_hH$R6zpPQOxu#ey*f1S{R*~qse0WQIU2fgb)x@w09U55; z{_&Z?he7tO1J_s?&BvInJY-?+EwWQ66PgvhE7f+V@$ISH$x}Ld2!J>rUd6O|F@0%` z7M!QUku*?5ggkmYw3QihfS*nvuI8=1lCdAikfO2-66tOYt_8dT zAhkU?Px(RL$s-N6pLWDf3!}1hA7|QkWcTt6Ed3KsrW&U%G9&BStC`b;SF{h>PhQP= zJa>;7?yy;hlX^OgdRwXA%||FItpQ`5y~m!p1Bc@U`pw5|W51p)V758lLLbmPjIkPD zyoh<}9KX2kle{v&0GgkBE=no9@)WnC<{PT6!udGH8`A^iVhACLm{VtX&a$xlYba_V z>-d&};D@IZ^?3Q1YP!JVYpb?^c9}V)FI$+Fuj`7*e`w^3IqP^Lzg+2KVA^AisL^qL ziNT6-5y2hRk)N;1Xz5BQpkpiQ+*W#Ab*06w^>-cLD`y!wP+8kI>myQ6DJ>4FLpLu7 zZ`qU>upj=XrT#RkIZEc#CdF=OD-nEzcR9ARwY$}y$?N$^ezXx_7`O%uri^u#zl@vX zv0`{8NiRDwdR1;3STPP7fnOCI9(wM{=K&pN4>UBJsD=kWKnqUn2T>6@C{vd#{ef%JGB59fmEpP86rKl-2Y`%a53~b0W?yZY zftWplot6+51lX--y4_mSQL;_RfvH?WRMEwdb-d%gU}b`NVPD~aSrWrNrYAdlNlV(l zO36oV+p3A&b^2siZGNl!r_+4mM4BeXAa^J?ymMt4UhNE8sw|plwhdaF&tb4UqLFq% zQTIsM%}RguwR0fHFeVHInPtjbC7}?3(_c%Bz8kD2#b(lNurlDyPUg3xA2eu0h;O@V z@ES(#x$zzy6_qPkdo&(Umg&e~b~=v1qBQMA1m{urQEDkl#fdN8@E&@_b4ZgM0Z7Fa zB2dSv_2C3N;$jpPFu9N#GqcDVv}rzc6&jK~#HPWPxb!-8bGsBmw306SNW<>B@>tB^ z1Q9w=u~*_uu$h@RRVb%K8v-8o*B~ez@%+&znLuo0kU2&j_)Vq^qQY&ph+NXxJ&~<% zm)sL9j;o@YY74&(?NpYZF^m;s#yFdeM5S#V_EnHB0Lf}zz&};XM^~IEUdnF5TX925 zl)%R0%;($2IPmWoD$0c_wd=fLlUFw{p!G2GA< znPQo#ub-wb14+s>Lzw~*dh8>W!7-sSpIjIUtoB7G+rOc;3V6VQe(XmMAN~C>?a0Zg zIWD>p@VYz>&gD;NO@*q`Piv0LDDmUNWVES5^Yr`>MdAh*<(FFirW0pn%xLnDKVC_N z;g5DDU8Q{TBN(JoxhIEqi1{6y0(_jkaVGvPg`0G71jZAGNv=>7Ut%EF^RCC?;fsvE zrQB&iq=VZcKGd=-m8`@i=H{+#u7c!ZmbeX~Q2YRYVnN@B0rxJ2)n~Ai>no~ILAt`Y za@}kiM$Om&iw%A7Z6`(^ioT!*@9;_9>hmvb<8daUJYh0=WbD^rVyW)^bxd>&ESz{#-@T>USkMs`fKT_rHpg zn*?#3R4`{T``wJktQJ?D_$dCkPMFf6K(x#YZYWu%8$P#l^5AS&wt%kNMwBnK^Kurk zl4c}XSIH*wP)Uu5*wPDeXJ4fLKjQ&{#@1rez}Gd3XRXIPtFY^cgXjAYa#>{x#5$Cp zmn}m;_Pw(C+p2zbb3>Sm>{{8B20Tr#<)^L5+f;GG!BR1)%;evaeu%*YC<|Pc1j54S z9PXwkwUgYTX5A15YJkj?Q=gd}^=S3bUIKm#5;}Esa?HK#@tLf$*J^*yyKwBEauAW# zHGf0_u0Z(kpOn(69Q4oow_hB`%>rckk)N|VwI$l}XG)V& zh6Js0U7aQFX6@-oSoww3Lbo~ak+r#%a>5r+Ti<9<)PO0S#}cSbj-i4o;0B9_OC&;6-@JRID7OA zB_$fZJ!`l+%JSK{OY)_=5-~9^oRcG>b&czpfDDZU4Xym}7=2$SJ5UxZzf5FpV0-FV z_}5{}4bY{_$?&Lj(^IqKTK|(7M-4h)QG0$PbErRLCG_eN411@YGV;i zBQ$tb2Wz;F8a&-CT}0jP?H=&x-Ab8JqK3;Qt9~l@#1f&J=`4~9nJ&eoLO)4m>n57K znxkkoydZtHKO>jDlIL>jM#2e}N#slaQl^F($q^QgCJr5G8Kml`WbsSmpB6!F)5@2A zm80R2ZJXRT*_WYjW4j}>wu$)b(rd}1K@T|Z%V;LwQjxpDd@Ct{NY|cu+6N3t5inMM zotW#_kyA`Q`t}FB)E;0gVcJY|a+~!+;b$Y)c1yChUk!$fHCu=;+=y)gP?bMff$I$I zj(r+y(I8yvSoqa9Ec#c6fkeYTNM3n(Z=*B*ePfE}Uy|*Bm!;a#BkJ$FuZb^Od}S#*AW7c8y0@wRg(c3}qZnN$*r&;iIU^`sF2 zE?f?6FXbxu%9|DjH&v^aZ=Ay`0{B zQ<6emur~N5+z;BH!4ZczeJ7pEHjN4#miLkm)U*tE(aObz>W`C3kDb%x%{Axivsm-G2a&O`e>}JMqYcq7H9b|Hm_()H95srm5W2z~-53jy(|sM5x6XWEM(* zKL9^*etuV#Cd=!4-KH~Q;CIaVLdoT+S~*;9fH&~v5AI>ka1(sI5+RiXc01;a8}D8b z{31$j>SW!hVFa@-K9ep!OM1n^k4FFF{ zN+lM8*LI0=EsJ4)#}bmJgYe~#bvNH#BVMSW@R_~GsI1=e z!`{^bk1aV}<6-lIcj^kL|CYYdIB;0f2HoNos&~PZ ziVEh%x0HQ1V&^Y1rAGFBMwLrGv?_LXYqWb`AJ`ol0ma#!j`JoyTZig|E&!m=F|C?EVL-*<%jb#@;J3w{V`vQ29*-tDd1AM}Ev5mv z3PPJMXqPx+TW&Jr`rA7Ej`o5jmAY7)hLb+)UHEv>P@ z(aaH?hBWrb*8YdQHLPD5oHfCxx_u>t7ri+0P6Y$;`9F1^Mi)QLSkC?Mx4&c8 zX_`$(-+t~bZ`jlC^oIb3*nh6_+=<^GL(N8IghEObiIR(0RtK6r(4fis*4hj|Vx`vA z(P2q?acw#CJ9$Jm<9un|?n4QWY78f3=r`Z}-o&v+JGYe6OB;ZtkqvR)kKE`89#paD z%V}~VhbIMlcb;y|FB<+Y@wMBNcI0%paLtS3oT_^vZmjeS&By)MAH}2OmJ@w$v*Gel znRyj|`DJ-RZbxv#M1kYf=QNq=aXQyzRw^S=rg5NUW3@M9#P;;Q~cj*FV`3u{<7xItgC82J=WJrng91Yl;e2O^WGh6ML1O&l`h7cDsAoU zf*+b4x;HAc@M&vhh(B#$GAU7aw^Q-POoF2kC*4lSt3;nDOsW?)oY7hZS*AFJmff2- z!klMixE){k(`{6i+)U@87=MVWG>wPAL?C{KCfi;Pul5&j|LV2h1!q^@vZy72r?D1- zWn4LPK{d9SR{@u^UW|JfS=3F|Y{0<8n)~en=@>3YI zB9&Cz!G?-nSoo~rD?L^Y9H2>|pxTg8zWNgx^h%Y1zWQ=YE5 zWJu2rq}=Z8-p^pvRZkrYrL*M4BS0Y%kZ-SV2Iem(1bE`OCbreVY3c9ie5{hE5X?Fr z3LT-lZ<-sxH99)7PHJlk(lS{UoeZMkmS5=wLgI1c75a_w-tP=KVxID89IOTYSpK9{ z>B6CmLphoNB+=*~zUB|;KQ}eOoD`A6>2)2r#0{QTLu(JbSp>Vv)s>fnVbmmguIU)A zP84O~-haNaBfn0(7QDS5itu5*-nwtuDod-0oVHb7xH}T^5L$@CC)J8?(7IdYD}z@> zXyX)l!)6VX^b1)LZHyqd?p+znSRI=W5l<5wA()dpo+Iy`?a^Fnzi$;E{8-$;)b+%r5$gV8B@e{ zIx6N*)*8bQNZ&8!nhM(TJP*nDb9P|cj_a!nmiS-4j?H)M zFNoj5K&x@m+4H`e)Km?_8?91Vco?U~%gLl00`I#4QdSGy*Fy(>wTp=aNrpj|Y-x3z ze`L;ABcyU{s%7%xz4p-OM1LrM)8vjH!AwJ>=nEXc+bwdRdE^W=*WLQlxJ)&Re~tz^ zeh_vQaAocc2gJ_two3!Z^GIj-qwi+!tlAwytNk`~V`dVua5u{GvY2|cPI9{DA)F^n zIP#RSHlS)o4TxcQrcUp=8p73iMF?-(F`K*T)p^t{U7P0@J?$s)1FwNgwWO)r{)TJ! z226YEtuNI(aI2K7;A_bG3dUJ%vv3?WV`Ov(Lk2LX=kpr_^Mal{(B&{lt+c1wV#J4_=u~#OArb+y^(r{V}^~eDK{BLig z_Q(t{a?NJc8LYt?}bG0TnrAr;nU) zF=krNYeXPqsDf#G5TSz~Y^l#Coiz3^nr}I0sNTpYBr<6+)`I}O0ihzX z0``41=DRK^Tu)tFGoS2QrT;1`S)qF#ZV}B^nP|6Va|Vw@&ojNk_gF^i2}=M*;EU@$ zN$G2C*!2hj!HULJx!3%O*rUKwNb*cASJsZ@6*Aw98?SP)GtPQ& zsYtLX(FXz|E|9F4o`*$M+ZuA0^Ivm5hRCcq84ryN?!2$jjOvl>(661#gV@9wZW5<{ z&Fs-fN314Q%v-w$*TJ_UwyQGO^EQyprUEs~Z{=v7if`GG@Srfd*RaqCeLkGm86UpR zz-#+sjXw90Z!}BYLDv;PaGFPAOQyEGi0q1sq<(dSDDlJInM+o|F~4^m^VSH`Qjd zsG8%n#<7Fv?$pM)s@;=Iy`}f%a6W?~B%o7$W6UNOj-zYV3+4;|S}awYPt1uY|CXyk z%>yiT`=YtJ>K--1*of{#srj;Nq1vgTkwKokXj{^AGB8Wx*n;_!e<@|usZ>-RKqq6n zBA22J1870|1!5O=*WcPjddDXX(oN1Aqh@xu5AErIilpA>lRtI37`t=XoF{rxJdJ%Q zRtC%aIMR|~2swg?f{3rkZNc}__9x24fWGJugRIP$)w%+vWjDW?K2 zCkG@VvOzdHE$wSVPh~G2BR{Gr!;Bxpg7}5)4OR##wF|D1ma{lQmhcC_fE-y5DEA>s zhYy-9a_yi{EYU+Nmh*o$g1@IXjW@1GOT0}#t|m$?7kz2)smF8Gb5%N(p>)SgNBua^uIMKBl@|*y4!7h2Y8@kA zZSfFAQ6{6x;3P%%G(GBmu;u6y--Y-`pupw*?=cjf+J^v9bX6H|)!`&L$un6N@n}VI zi{0?1Pw!UL`K;o7_sf2&e}c1t0YV0|!qiL%`zn;psf2NT+?DB{$>in%%}eL-zM3nm zrx~V~PcEiQs??{CDkM5v6mzy17S?R~Gi}9*)P= zZH-0uYM%{N+*3$I+^5Olav&b>M@3;jQu%{nZ~ zC)(pQ0uoDiEFIDzAqXrWEwOZWNq3`2hcwcSEZq%DHz-JVcX!|Qcklgso@d@Ob7DT< zne)bCJE3jhYWTH9C?+T#_Ys^{8bK)#KOLnEDqh+HPaP>S)A-hY$4HL)x-#s3@G}U1 zl=l`6XU}ipJcw3#>u1Kq-WW-QYshr{ct&t{=R3L^_3X@MDoWNEYgyI$5jDQyB-K`Q{>Qn64}-IEnU8yv}D_Bk~OBD#&bvvhdl3DUOuaF!}rRSAm9xB+yEnNJqzxZL+eK zQmBjYZ-^zUNHgW+Y;o;LsoVE$I@0X9yQXi#P?AEjwNHDQffJjX(0)X_J9o)&$KU1-?@SNt%CE>56M|RJ!sOpK-BYuQ>1l82 zY)+6*>^EjArB5y5EWc0tAb<1Y${sgbiSy;_)wNXdXg?MI#n7Pgo)=h|l;2@+Wot7w zvcMyLPQ=_Le3JW7^K|!kYPIN=uU*i3O}2@wX};BjoS4E?5@^WqVh%DUN(@o;2{ zem>e&TtV6^h4ASx2rc>H`ja`AAJHMGOqYoIQq&Hn-3ewNqWZ4M7B>*KyPqcLhsdo1`4WgUe2rFn&&`JeGgg*h^x|CyEn?x{YYvOv-8Sw+3S5J^sNiv2m*r8WqR7z!URoteH0dnN0Z^ ztc&l*brj<$7X=(9lZ={LT*%dVBVkHPwDn{2h%V5N6eR|a&v&bDkt<~>g^_3elHY;o zh@ow>#5O_C`DZ`x${OY>mU|gWi08DAUnb5E?D+CnjQO;NHqr5zX}BI!^eJ8RLj{1*{~fU4>z&x0r2})4}^0i;!PqF*LzR;iWNtUq?p8bN_2ryk-VnCFs%Gc4) zS6?_*sKE6(Pdvexp*b1u!F*xVV~6>IlerdEJF3wWyN;T;nA8^=iskk;uQ+isK`M)L z?u!}?F-Usny}NEXl06NnuN&dmthMUw%}2)A%mr1E`pC=$KV%H*$ZqX9RvLq_eGeAZ zPQtqr^hPskB2koXIwXJno4Zn|$FHA1*x5TOgbQ+j%z&66Re;R71&2C5N_Tm*%|^A? zGs_f``n931@WXNb;-8p=_a!qKs}b=^La?u-FSjIQdXbp%BK?i_2_-ruH{z-qjP!6L z7RqClr<`j{)U3-TV|?Hv-oO=ld-&0J*0b&2U^0F(B5~G{DV$6oLP+lGn7|t1iaf?2lcsZD%LOSu;@I0v`Z!3PJ+WAGeH}^?h zRUa82)aBktsBqNKHyHgZl1h&^Re{MrG;^U`El-*fV;8u28x7qKWMi0}hYsNNIlZt#V zy4=)`X}#yRIU{nRcfTdWtkdhi-R(B|{gCrV?jAf!7@7>I7kiLY}*+d}< z^v^;^Qyy1LJk!vbWoW`2N1U5sh3Oyp$d7B2XerJQUy5tbL!<4oYUb?$J-G6+O|~t# zc1;s=LbVCM-ayjO6zcuusDTK)$U1Q6A2B#q2nDx#O(}hUenerA{Lu5d33fP6Dt_zm zHN2BN4=mG+IcSIk7HkO1r=auqm5< zF+OB0ll<0$|EdRZ>kKwGa21xzqkBm%Yz~d7L?vtF)!%vYtD%rn)s+Lc<|ZD;gxg3eSPFYb%2kQXk!;OYJ)@y#9QX2Rz%#jw6~LtY8N3PtiSsEza$iW!QkAyn`# zB`Yjw14~;tHssl$RIZ~TFrn zc}I6uT^r`$siG?y?x=zLLI+^;G5hMCkicg3{7b=%6uuWRD`DH? z_kZ6#xeUd8mk~|PTEz+p?mz?aMVSngQILOd6#44X6+rkq#~Lw2lsoaTSYLVkg*=Z` zit8}D_Ke`K*0@$LfHTF$RW!6VzG{kh?Xy7jRPaxXwJvJ~&9J1+Q>@7`x1D^R@bF0E z--amgRTh(^@vF)eiu>JJ|7XRRqPuk7`@f1uQK>G8Ag-J86;$-&&<2imTdTP?n(FZC z@0|j+vr&8ff9l_vXsN-11lJw+KR3eykv67?%6uIIgX98=W3TOsVargyK6pZ#@%tR9 zCF(>XpD=pvY#9XxB;L`7ec)AtKFbte4d6vd#|9IcPY(gb0fT!8csD4YE`nz>j%4d- zV*RKyTd^}e)+6~t)jC|K=oRY-Bb$ug*QVesQy=b~DK1~H>6Gt82wb%zNp^z%m7H|nf3yZ1=zuQ+J7^zch? zOMT~ekBdQKsN^2H1CyN5!8^t$)<~RacNGvK!Z-_HjUGry8yF@y?o5JN2)pfWA?>wpRT0do)a2-TkhIIDSn z5l}UbH!jD!R8>z=LEZeHPwNZmx|hQ>82xY|Evld_gcpnqR9`xv-x!TP!4VH|lECV1 z8LCOpVwvPJsMoO7-2B{NTH~^a<|89DvK9m)BbT5EN;6JW2L5CiYh#vh*16UpA&*823L$kzQ_zTRp(>AD_@kxfSrWMQrMK6sro4%zf84UO8R$Qn$pv6Of>4; z;@oEr*$=P%4zJUwO*y)cfE_3rh$%*iC{U^(;~8Zqwc+&CjfnQ5!zRC={1qdH_$cU00+4eI#44M5g4Y zK?X7@HJb;a^^Sx&`9EOCOepunlKJz2;h%sYE;0RgzyJ=54HDCK|02j^pdWCLiFGUpz{3JYS$kzwL(A6dkM>z|Kwd8 zhv&7M`9b00{-cKJZm0U5d2HVe%;CK+sd4gVhLv%Otbh*k1GT7ww@0g?YG2G*-o3Ac4Q|xNK>!eP^<QB|J)9!#pQeArwDo=WjWm~55O z?!OklNUfauMOWXQq_%&n?Dvxz?}&Fx8u#C`OUqn}>S@Kgl)e>Udav`@WHh`uYw&&| zW8%qjS5sH^ZHEJAPc|9l)e5`+qsbDNeR4;2U2nC)ruU!20|Pi->*-*_lh?w(wpYy^ z*t=pbqHw~yCvPEixDO1RHzU<`d#c9Vi@Q?mci&!L-YcqGP3yo@b2ko zkP$5KI{_ypFc>6?^nwinyrmCQDGE`=?~z@O~i0xy`HE3EUh~u9e#!U8OA> z==45BRThc%<*%o7I~V#jbLY}4MmmGThE_kPqOmxF5#P)xEd)gJ+f@c+;E_Udj`jtz@01FeKWd9HxhP21cGecjB8vOKGyJ zWRH^;M(L^(Y6J*7pW8KYNj}0~f0F%mz#k=Ml6Z~Sl@z?ni;up07Mog-armwfXOJoo zFYxpm{j?O$i=f$VjH_5qeJ)*E9XETi{^i%^!ibFsXRa%%oi4G%YHg_pMWVc-LMw44 z_-FflncAB%xwim-g@_2E2ciH`ySK`iuQ9n{u`X<#`u7Wuch8!H8Q8 zSOxi>fRYJ{W%Wk-FtL*@fY|?pAGM-MhPT*H#gFn~#mQ4o<$+q z^x$5l2eyL>9DXc?q8l?8%?7Fk4FR1y!ma>uV(_QN_=B`yK@Z}%Fm2O?B0BYY==Z}< zUzozbkzphJzK+LD@4@92D-;}4wpO}XvE%orddhwC!{dEyy;ZidB(SE_+) zhNN+@9XZVy-+hOzp3pe`Gb3<{tNG2>@U3}zU@j-7AF`S01&N&S>LHYJ)ZUbjp|*uL zTSCh7O~RKC2np1UxSpsyXaSbY@t+s~gH>PQ?IeFZ!`uvu?X0p7+OC~#!lt5Rl2h)A z@)U=w1ZH;QOw*#L15}uQ^VDQrRMqpDKHu$6hy`^IYRceFbGq73zyO#x!Xa2-Req-- zFTB5pl1dsD{r-M?nlTh2(hn`Jw3axb+^Ne`3mh~5gn=u$L7lY4;Y)eA7+=mid=O?~pEt3{fQZ@gP&dIgddnk(W!`MFJ4uDC_3@JmF4}fXO=zc!$m0C#r2_Q) z=Zp^rc&7l8hFMnapQ%2a>Y78wpm*c1&|e#n^i<%sOdu}$P~mp8YqEO@IWQ%D?b(KA zrlu(`gRdK@cbg}KlsSF+YiFtVA!H(Ze*$ll5{5@X&|$Q60Kkx*3)6my9RgS8Teh?q1LP}Tj!qO8 zlkMX#HnCC1mUP_poAdW&eqK*#gZLsg@-(CHT1C_}yvP^q@fG#bo4RiwmjA}OODEVuz~}YyF_O$oG!VX*_mSQziG5?T@czuH9Hm z_A_!~4bEFW7oC@dt*D{2 z%lopOt}}b|UsawME?!l9wwhbovbm%3924f{IK+oK@yL5&8wM6Su$!+!{4%`vOI$mC z@!Shem)ht%)&;^!U~g~VZ>A1Fv<|PyolB2k&EuO|s%(9;vQ<>IEN)hN=L0`C2VY96 z!w!~m^0cnkOg`aeJF#y~!A%KK-50Ez2OE7OG+2FWOyLqwrJW}Yaw?a;VI@TmKn5yf zy&pnySm1obB7U&e5gi;>V%m~Ht(pCZ+3b@(^+0CJPhFm&vC%@QUBWt&&)y=Ptiw{b zTssq0b(3U4zclafDK{TT?^omQM5ZgtHa+lfJ6Z&KvMH1{@yISO4gHI#i>*YyzUFrN zsLT`gKbPJnbg*Yu5P1|DUxr*Y<)P{pf5D_gA^&(#^H)M}IPY?BoY!h0uzkp9XdwZ- zGZzsr9wHW30ZR%xrtsyDSZ;>0OHob|*ofZAGrohla(BB*?~mvj&^Kfmmy;t~$(pQ} zSM7$ePkC8&l`BH~D%nstV|G;0+iu&dIh?+1%!!!u_$09I@LZ?VTKy?RMm!<@{N-NL z&*X(|{AFPNAM~aBFZG6;Mu@WNUfWB}Mz+Q$rAb{p7PgF|Yr_KMPjo)X{lX{GM!V6p z2{{j~NA1UL9qvYpNr(q4Ox5GMMzId}X=4))JVRn7Zg?dzc=HOm2CQ>f%VHE z*(C>d;PU&klm{sRg}c%Z$~$oC(}&&l?L{8AA1V*Dflq3B-9I`pb7dv)ZWvDKEnm;A zZx#arHeod8t@q~}YiCn@|GU^(izplXzO_dbm!ZX!9D{ep3(T?m*b0nd!r4>yBJcLj z0k0%oZfcdRONEJj!{My5x>*)axCou?-Xdp8OA;x6^qZ_t6vO0GZ?8l|nzw~FpBaip zXRo_lM%5(tktEx7Qb8WmvzksYC}+iGU#$D0R*~*a!Iy6;Qf;Zt%9XFLY zN_)(h!h`o1I9Z`B9SM%V1Y7VZTi?L7d1@9Wtio0aO{r5rYYT07Ufz6Sq`3D|kqwVoL|G%}oTew!SoJ2;=xqq*4u zbhF;pAvP7?Z}4|fr$|qIuM?ORAjqc&+q&*#H7#1qj!vxAL}Mk1ssg0fy(b{!vj3Rk ze7;EJV)P6TbFH$(t$0A7l&l>z)tHedOO1Fj_l=3E$t`FrWs4M&X&IB@ZUy_kwj66WWjr2<&43p zv9$d`i=KPBw3v*a{N&9zw8ex(WA23hM^O%2K>umjWC7D!CLfQKKsGq?3x#}whT?lz z?lX5Lo$gxwu7kIH>Ew|$OdsFSB%HF<+g9TvUWemqM$*2boR9m1XmYn3CsEIgCR|x*Pr*Jo}SY8RB$kHfj7+r0UZ}P z{=0%|eAga5H&gBFHm$iWEeA%C@=7airxdw`9FWlnJCOvpD|dH0(3MU3hdX`*4P!sW zP=g8xet$g+v%9DjR}s|es}WX-X6plTo}C2sW|K~IMWfOxiNj^^?$P}%rq?(#cXFKS zssiH}UC@ZOUh%Yf8L+?JISai##Hn$iT7N?+VsGOlDihIc&9l~*iYcLb(8UsR`x=(#O5U`AAJkB-0 zZS$c9O1LIi!N}>!@en&VrH1emEGS|vF-b&`p zApp~DzKF^M$5-iC*&}|PeJ&@B=5$NGF^SlQrv}=uktx(>8!chCk>7=cxuSN!eVwMb z2_M{qCWuEk)$e+i7dJpZ6zW~dtw}4i7qYOT0R-XIjQQuiu_!yDa>GoSLy9zMm5FHFqFd(g$s1#4~S418CRZF1c9oYrmL3$noh z$aR~L6lJL>UQ+MSq(ymgD)p2#_-LO7;J?=bp_oF7_`C2o$zr{j_)$?z5}}(PmDeEL z6+XpbQJT|9xotn;1a-BX>rs;tl#MlxWdcQ0Zy@b~_zG_GMg`7o!Cs%~X@L4%=s?Uj zY4jXG4pVd_%31?UQ&K87RB6Om1Q#i@D2P(UQfBhnTO+Ba>rc1qx)~C~ed_BDj?0>d z5;nW;zXThe+UN^*7keoCo|b1w<2DlDwBBR@3Ye9CoeMm8eFTeXPAh|PrrZ)#~o|+R~*ia|AZdDhSnissE8RoUBN4t6xhrxVYSqiep=yhbv;k4$xeoI;jB%upW0(n?Z!=h`gMGqw-oY^O;_> zBM^lN@yy+E`(JURO1WCACUx?j5o^6)s(2HUApWuUN-ZWeze|I|GORLbj}nopIJ2La zTDdDJfU&gXH0ejgHeRAK@$p*UnJ1hqFbxM*(+=a&qdu&9KmW>L+vni2$aIk^aQd<7 zlp5*@f98t`BF2QKjwVA6E})1&ZuBYTM;Ojl7kqMZdn+oT2#k*4pkm@Q$PC+d?M`yZ!ew4<8O-v719B zd??or6I^Gtu$FilaTM-6q8_04%7f1WSGlI2u!~zI)9hKtJ6qw~YU*N1hLbrL(p&|k zL4b@U2C~!aSU&e9AQB_=jj=$h)8+~UVxH1~$py%#V%<~3VHCz_ z^W6P=<3_6@n_;(2^p{v;1>~X7WW^KJY1|sN^x5uLK)qg^m}>w??j7ciI&hm^yexq2xEtUC)vr42QR5SoP2#mpr?WYNKlj{j+gGfKpq$=FkJbqpXgA_G8ojN_? zZs!#3O8L9{e}8bCm2$5oBdVZHJRQLh1Qfy|MkPuSEdEqRD;aW0Q3V=(ZBTAm--Z83iTks88BSg#9|G|M*>cg z)bgBv=IXde zAKEnh5(tw!wKrp7_IlnWP7YZqU-OX*DN(1?ORN`*c&&7*Wxla4n4c8qh3#7}-}Tlk zx$*TVxqJKF4Ua8D(10auq1oERq36v|j$D!IuwM8S#8p($kL77Hua?g`o{C^_Df`qe zw0K7DNfJC(D>_S>3a1z5m7{SNR1EnEyx_EuQ8AhRqCav<*P#I(VP$^6>7V^Ws5Y?o z80gYxoH+T7cxr}6T$CzG7K)d8dL%z`ZomL((E|B*`7%a=wpap6HCRFJ#5+C_@RaJ) za*HOQEp-LZY}v5?42p%mQ5D0&zyJyOl22mK?n34KnyO!5y0YWjc!| zQ|WNj#-?DN6Txr)ZW}C)(|?{fQ_lD{gq%9ObPQ@`4-5#tiiCE(I&fm93n1~il`yRG zH-lb7d>U8fT{o=^K6&Pzwg0E_17x_JdO-_st$LbcGj)HRVU>wN|Dn9;O$_d;a^(2C zyXU*rDR)QRCCsI*qY>jFvj0TQUk~5hmD3%6y}QZp3dGz&MPJe{9?v_Aty_x<*yYW) zdy_J5x4cFJ+(j?5-`yXH-Wu+5-*t2;_!yCntiE$lGvN1`AK{9TGM(~`Y03%SE{cSRC8uBh!gEl`ketPM<^N zi!@BC=X3;%-@Y|D^ZWKs6#wgF*ttOEp{4s_spNfH3p*E#3aGVhFPOpgIC01VEDyC$2B#wX>VdO23%XC1&jyxL3wRgrJV zs*+t_qdG0zr_C)P`IeeBCFoRbP=<~<<#3NA^Z|3jKcX) z{^wsa<;aRoKP>F=V>(8DE@HDD!F6*U)=HET8a-S@_bND&z zjZ6dxzD%r`yArh+u4I1bJYLVo5Cfh%ek^eLVLl$dth9Cf;^1QRE!-{jeDLjwmJ=$P zAKsp?$}akK8j4kbyUz;MgJ|mxnfzH!`22d`q;yz_sZ=PK&k~CAAi;!X0PMB<6$DMCj?a%TZ zQql959&%(k{?2mLp)g8t**lYXORMVAmj*ZxXq_y06n7!2c62GeH~_IZUjn>7o+6wk zyNpk86$!zW{aBTSngTB(x8+OlllSeI9DU5iY-!c7oX<`q$@2uomwCY^Lx=Ja@4Xw9 zgex|Q+V$#cdSVMs%-y9|D(GOKdJ!5zZXeZxpO*ND(4Nke~(u%k^C-~j=Rx`{r|$E{uR4l6;wD@>m+CmR zpS7Cd(bBlPM>Uo3P}Sv{V7~E9$!PM?rta>a+fv2Vis+2`V~Ho20IQ=TcPYkjT*{>9hMPI@x@7x$(52Rx!VE=UJs1ckrel&qXRqd4=p?DJI+ zO;KeS>`m;6774HTavXM+_#5WDv%oBc{&fzSAqJGtsn>r56&WXj%pV9V>wz{KoVOkE zffhn>Uz1YKxQb?_^f$Spw2z$Ed;rbAsos1RmqwI@kaj%3i_C>IvpbE=^%h}FtVJCp zOLJ^#?Nn0}eQPUgi^pzPMPSvvu^S3|jmjBe!n-dwpyyz%{49$!`INZh>HJ}flBIGZ zf<~a$WOF#b?vX9VFuWdCDESe;5`B47lp)7M;&s_p_F~6}cuG1rw3{&+qb+Rprx_W| z4|ndar~k*&Dg%EB#w>2Ew@tl)8Z~SkY(;RVVV~5%A5^<0o`Sii3XI)x9CR3p!n#@9 zwRGms!}=qLTjB8x*obS5zPANvR1AMofD;Gh?rk00b^Z!O$U}>^1#+quv;2D`{@3he zXTs(}ncV|X)^M_Vr17Ps5}4@x>2667J<9%G=Oc`0XzkHjF4$ux$Vl;gq)vIn2A)b7 z1AdHt-6ce#O3=S>`aE-=Aoj^(O&@XJ&&|PobV0?+M(0xdTKMGttorAe0msVe2$jAI zs3!gnjrJ8el@fJ34^qfC<@AP@ZQv#pe7Cnx%-HhfX3>VTFY>nh*shpW|2P?$GiI-e z--!YpV$B6rhDwuWV@%_hwwlDHE-hcPwa+a1YMi*U24mX1$PU981lJ(Zq+ZWaN++t zuopPFPG@Pbh7de?VacdQh@Pc;SdF6L^oJ(my32D?7JO zFVsI7%F}+Es}n6?+5fvYOisVk1n^g*37qhP>+1oYy)0;v%+SFaIg|H)S)~!h=2cp# z@jcJ|8{?vlO<%4m8+>ApwNndb>h#svANOi<7*f4nvt-1T;CG)c$-VWLBlr6Mgz1Kz zk}6MB=a%^z@}b8N{=29Qx5piu7SmJQunrH?{TS8bJ%}i2wUT@B$GlY3;QPeprbp}x z79FpOqLGAQyC-Ah(1%2wtP0|OS{$@AUOM3w~{SqL0M zG{foX?C#tVA2p6?VFfB+`BD?>R8-+IA-&e^L1RZuAUhYE=-4Q|o-y+D<&Gky%?Hg? zl;Yb1{xx$26OpjRsnIc!?U$B>O*S7}k}+XkOW5h&?ALb;D+FQkj#59bFOo}ihXo~N zYi=!S%?}7$43?6XM)ewARO3jq%oCJ^P~YQ3d-c!DfmqSNiiB~~P%5WHkl!u}^|wKE zzjt3D>@o4bD2C`fe#OLV-E^AwGW4Ukb&tcCW94gn_-k{h^UupWa+t}KPDf+6+{S}Q z<4uomVc_?TaiV9bcqjfzoEvs*oDg9_iynDiS@$$60vl+uqdY9C&6t&D{DHDGA@1bLS z(`Bx;#>xvDhNX3#N~U9$w*MMIa32}+AZB($Mw{4V^PLZ#@4vvvr#tHEV(#o1<=^Vv z3@dxm4WHBEhv+u@{eDM!c-JdyDAGxI?0gR!G~{Xx2Z77|z4Ik|jen9g!i(vcFVhkN zyy!ww2dK8hJ=D!A*AEE(x#8L2c%t``vBq!b#wK$lG?vKgPlDt)E9Bv07^sXp?j!;3 z4r%K67`+v_#2RGwHB5mZ4&9P8e2_Vbgeo`V8*{#D?$IUZZocn@J{e0E?ST;wS2fpF zTMZ+LE;mcvAye!ZJKE3;rfxG)nYQ>UIUQw0`#RlZ9V4cqUxwe4Q-R#%wcRf{DCX#> z_DD0R);ai2zYf?1PVQN?05g7^2f`ct)!hGn_Sk6TC=#zlVe|Dju6I#`iHP@K7|O@{cDa{nzi_U1CncJK2T?4VSd+zO`fQ?B}q3e>DR?c^$P z@NW1g-yb^?Zs8+b?PX#Ox<7C(>ZxCK`ye5fPRYwP?~#mWpY+Q-1CS5EH*CwM++~t$ zEBYfkzITbkNh_5qz!j%s8PE#KxaRaYjfkb~T38Z1`CSLu)bhLhyK3rPN8^Pbl#owq zl!rWD0`vQ*PJlINQ^=Ji5DV?nJx?QLw*}XTPmAZUQoZ!RSd$uW4`b4|?{MNy`o_0$<8(o0 z@I{6_>}z3RM>ugg?U_<^;viANI7uiawHoLqY_N&CXH#m+N5L>G6RHC68emDF@EpG^ z^YN~odt8kiWC2*S0I76#=Z!OWu(rvttpj5i>;FWgN^0P$x?47j1?+0ERGc~A(x14 z=;mD#oFP;zr!xJ0=G(Kh0`vLs21=T-Gkc5a17E;bF3Suoh5pof`HPT+xLjaqiak-dX-E!xrpIV8SxqobDDG~sE~T%6Jp0|+3) zRaooc*i<9h25UV6TuUr!v-B=_P~p_+%WQ~KjGcnR{>LZnSXqB$c#DBRZF)pk^rt|` z38p#@VQpk=y)$t|ABg&cOrIxn)c{O^4y67f6)#NAlv+sA5&dD_XWn}>o`zufND$2D zk5>TW%M^FJozVcXHqv)_~)@b?Qc|ZqW%;=tBA#zX<$W2IW zPJw>3=Z{|GAfZa1c8C<2%?^&?exsq!J?Hf#n!*HVracSYblLk$y~|Rr9bRa(23c1_naivAnGSV@ zv|=_zK)y>QUk5@QnM%U+P@f_sV|KE> zy~67+=1~H@SR4;NiT;R4O&do+G4joSS&DVzHO~ z1wQzzgzypEXp}JyF85N^V6y(6>;iPAKLbdk#rFJ7vUzib#=S%(q5E@^BzL&Ke9!A? z_zRnbCg6xs>$WVXe1MH-Y2c%6R3&Ou33F%!xZM{=s{+C^LP4_r>p+L7!um2*bIEo8 zvrOuMNF@wV!TMH>Y+b)x{g2!sPluf5YD{*=ERn%#a>zby2Au{9`T*>tGg zR-MKyd{S>9m`9yC+y**xJCbrXBX}_`2Sswd_Fp4Vby!Y)id^WzYL#+&!{swS;@;5e zcb8SIn>_g|K~4fpWZG?X49S^W@m^LtdNvmCFEYxQ`Bolk{(d92x20%QB?$_VPYCu} zdu+u>wzWZci5SAX=T@%?7KR8dMhknvV`O+MxkVwfeHJ+R5-h$N_HSC4*=xRleTSa< zXL=C_qBvqWK=T{VUw0o_^{!PEtPTZ$lLOA40)%lcQf5Zwvij(d2Z}13yY{dr84zw4 z$Rd33GvHo^K=~QI$omkJ_;v7a4T|n;JCN(3)y*h9CBjFI&Grgi~)^!JL%kcPgeJ*4^YPMT*OMxL4GTpuU`>z*WQN80)mcFi zIQbS&|61D1Ke8nZ;w`n+3@-D&78y?{;;q-E3$<}f%x5$GIU9Ov4j!x5&8qpugZ%Q< z_^0;_;)wb+OY621Q^fUYEm8iJ5{vD~JK%(|M7nUP&ClWD3Lr3gsj2ZgPqW)xkHLLp z^Rt`(j%9Mq)emgLP-yUsV*z`5detaV0BV24pwYjHx(ZXDB5sFOa2QP$6kjVU(P7yW z>vb;L6rU8YQBp@5>bOCTsy(cljlC+?qc8rEVpaAI5ci!JBT+45)dJM}<)@Oj>67$1 zv_!+*$vXQU|2^KGMtIqkP& zvazX{xA;Cx>~hyivt9d8ia+84>(uk6+96!C9bh{BOIa?DwmWscogXhCkpes#(Km#75(i&ybb#S6iRTdliRdYK=EXhtOB;X8?IO zacRVi)WMSYdundRQXp&k(q{ChE*MWas5HP(L)L` z{c|X^!0sGT8el?@!AA%1Sc7hsqckXQxhaM5$M0he`)KEkx%B}MgSSVGOCym(>?gZ= z-BXK1F!4!jD&?D)d1e>|q;rCe9h~q%_rGj{h)(OR3x>V|zf4hd2hqjP_4B6?ow~*S zqRydYL1Q{Xdsbdsr}j&xu#*5SAXdUTOoxU@@&>lgjEu;@1W@t!%7@0S?dP9m23ywN(ktzl`FwsOdrf^-(xuvIZ`^-=2Nr8 zLcD$NDij$r1xKrOGrT8MlWP((**R8hX|-X#(ZGd+v;a0!nhZfe2E>{Q8ek@;AfU1o zav59-gtSQi8Q9@cR%5Q8441@A5J#Cx-9a{}YB7`My`EXx8sHWlL&gp4fO!A4h&vz{ zzopUv^&WTEUm+>&`7wG(OHD5)a~X%{wGH^JsH-;moZrM;=_CEU%tXTH5~hb!YK~dh zrBSeKNhe2E@l&5Z?D5b?Az9g1s9a_JNS505R`t`L79)!bwbLY|K!`m`1-v~$T+?Dj8V2|oa3{7# z01%w8_c{;)PU77mrRG2!7TziAOM*L5vL527{95PC8{fNSsjJ?{oVI;!tAL!&zf)!^ zxfKoH2pimmI2Ay@Atz;ViW0=E((<~Yx~AeZt-?C z_f)T!0jF%d<3xuM> zdYhd%q8U3j%a=$gB%_vr)662Cp4qn_eCAtcBX8?k<_u=J1PFSZvkcJdkVNbA9LOaG z0)o1rRIr`9{AH6&bh?K~{{9AY^$y=sg;>siOGc4)Y_m@o!p`!>F%k4x-Hpf**%AIM zYHZ^#31~}60qsmzefs>@3@)T)I@DoQnx8v;-aS&yQmYpjFWVn_ssq;l(kR0BJ)cev zrlk;W;jkkHSC5k!CjoJ`L9>CD>eoE2+a(Ua4k{y7#6ktxOyZU|Kq9PKT^vscXB^AAEI@YfbK^3%6cFf4aYl0%0TH!p6uBk<`+-5EPu(07@EJot z-6Qei*enIb&Ja_PC-B@y)TZ9c<&mpjcLFyM4e$Ue)5XWh%<(vJWP!IqDeBkoFbht9 z9Rz@SZ!tPWDw!mSO&UJ|q;O>wEV!X+kcR-+3}mnw5PlsTCg*uBtKut(L(!>$0tlz@ z6QvN-8Vo%3Yh=K=lIencgfpSid`$#S4t3V&>4Br<;s)5ZA*)OaNcCd0ng7f>c;c@W z3LB&fLW8=*gfl;Vo=%bK70BGLgp0{z3N>fjl<`OMEbDka7M9Dy)e=RgI%}xU_gu37 z*$pUdUGiL8y*8Jp42KPzx)!}^stBgn9uqRNp?$`Qosn^=O;FH825k|p=|?ed+ZP+h zbqzCnk$WO})`)%?q9p+|V|bcUBVGg9asQ2l#*&OeZ7?3IATuM5o-(rP06;VO9o+xa z>Yc%Gcv1U$lxVB>wtDZqv#gcXLv+F>qKn?iCQ7u`yI?~EK}4ckq6CXrb@eC_MDP8d z--rK)cjo>2&fIV3InSAM=iD>TIdf+Tl1!$G&J0|{A6F$CNhH78d$}#COV2Ge`g-S9 z2Tti*oIMrNqYnE5u|5?=F?23aCq&H7q-{&oujp zeg87W&%cw>VfpG4CkhLOKco)9G3!}-wi0Tg>e8q$o0#QtLF7YL@nqsvK<$-lM4$B^ zm4_86Vg{qFgVLA#018#Wy4~dnRoE#U`A0xf~ z5yi$od|>}z&Uss6YVf*dKmG@yn5~0cDR|8!DRCPb)TaB9TCOoq(D0D;mwqSZa=^!{ z+C{0?G%4xClH7iP10e~b+=PV4ImbHlQcayXJaJr0yv7*iouLjz0zI9%HlL1P--CSl zl|u9+huDnT7oA_jwOO5o%pzE3SNwkEZ+#Dnn|hDo!qEdyQgKV+nf?64)6aSg|M+`W z*kXzb@qFdpN$H%e`)@iQRya%BlbgGIp?TT(%PqEWmwCvzEa9t7jnJ>YVJ&*8I7NGo z&RezcbE~2o*4Pz+1ZEu_XN5bT5rRF-De>K`kA7CQwhjX_B%y@me?RYL4Ruq9s88a@ zsxU)p+-1tk+r~${8ie{De?8(+r)OPF6Sz{TAet~~+aP_$%4MklPPA$JxY_jt(;q=qU12gg@Z+`^;G<6zb+~9Y!Rr@R6Z|cm}A#q4sZ1=@U8o z3k%95oAKx9Y-5`+?fX|YDKg02XlqYf>nhh}m{s9dy)uRG$~M0siam zEcJo(L5}P{4pEe-osUWrd?7@Rf#jCBA-RS}@)iN*Y)1+@^#6??abz=WiE=w2dA43o z%Lhuc+(x8=H~rq7O*MoH+uzT{U~1kCy6=bwOaOAumm;4C_J~Fa?xMmr*}N@Hb`ST( z%!%RbND`e0T9frx2Gqz8D!SC{@#bzETh6#Q0CZVh5*meO9bG8uFlyUa2z#X<9_Uqxka*`8szrf6sFQ{mb!qEA63+Sfij^6wp8zz+FKwdhm z-AsLjpI9ESm^RBjpM3<0P>vmsKJbg;2xiD-nrK4-9H(+0L+-M(UpeSt1(FEa&)Hv1 z(CIU41iuMN0X=2;euYbn)gTgM@8x9aNz3MX=*EP0gy)Wh|CX!^U_Z8IsMjE~`Z*)f z7y9=4=QAzg+H>cRp=BL ziFYGi@$}_TDs)I3EqMRJG!lG0d86}^3UXiaHG5Kd#xpQf`N8J;E~i^?jynJ$Bjfhd zOL%L7(9fM$syZmgqH^~TELNI*7>sAH^Gzi)5fIUQtC(3&NY9? zDh#wdBrNn}U#L^JMf2Kd1q>Gc{Ud@OfvxP2&yq35NPykfGez+|*o_|W57%rPW%{}rZzX>N53N_>q9G5$HDu*Nta*$C zWxg_o#go2{uh(AML3N;(+cZjSNgbqo!SO+>TYegIg!2@CI8q_QI^hci@z<0dh)i|* zW6CTU5XSW|oDBTUAoK|a0lqy27IAIKfyco4oLhY0FqhM$8Ah6k=ip&*s|H3oeJ|tW zRiV;6f56v_FX>1JNa$nCqVMu=Tj!Y*74W%!K4abCVmrfE!TSihl!>CM6O}q~!JX>oe0m#A2gxmfT?|3N4oEXK5Ts zS&Vx_)*O4(wuUDirqr?*ZDs$WzW6DOCgbQ;j2+2-O&&qifUbpQsa#`^&PA+9#oP3o zIOipWmHyzH$EJ}wru*r@`&D3+iJu1w%haNXVq6hf+0*foln_BIO1~`XgFo=J=ZiW< zj^5>cevK+eJpeBOR?NUfbgK+DLQ(PcPi~~aON4p*-o9w>Lp(g0k8N_#Zt6nDVMEPe7cLxxtD(uT#Uy--#4O5qGRE@TDenOU2q@b6N zr9gpD5b)hE!XuoxJZuV_eLORlf2Az}eW)QA?^%<#>jB%uQKmk76a7jQV z!q2BnP%G~$K$Akn0~-3bWO2t0~@@R*0x}^`X_`2P&x_ionlDFHsxtH#<}-pA#oM zs;y?#2zI~d!yt_p@lv#dA5HmQl5lpTd`*~u*$6Zx#zF*+TDCH zalU~a`pJ-CH2swErrF>j7=;9SmSlPw`;>&r(axm`slA1dORkLiwhAx9^UviLYu`Fk zC0g~PE2ypKs7~&lsC+e`8~IBP7WK8>nT7|!65ivbuykwfuVe#vAP2r_&RcA!6h2WZ zOaGpa3()}rNxtSW>ED%)&^@nlC3R(9tAFCxzyy=SqUiy@$+dk(z0z&JnTGdX6JA=< zv<7RF+-DAu*wxIu_$hmBWJunxaa7d=ZS6#B#RiN*beFfn2roleP2)T`v`|HB{+8Pz z#n*r&gGPbd$g#48i`#spC`s48?W)LN*MO24No1V6-=}$Mq&76HziZ2Qq6A$d%U+p0 z-Sm9ExkE-!_{G9BGI-l;HtFaE((P_`9khimYhC48J!0QtyjFvKeU-ob!)JJxbZazuj_SwkVTg%hJ*nBv=10%7^8wpPEX+vPuV5i zwl&1gu?uZ+gO&B?Y%xpekuExRm^*E5AZou&9ih|K!Og7QOT!GS7wXR_@<* z%=U^~5B`kEWv#OnurQpnQ7H6N=_6-q@&7tGojzT24r)rt$OG=81pGcDC+IOQ}Boy1nF|`>tmFT}qn1no4CRa{o ziCD9HrdXb>xG%eNvOUkW-Z}8F8cW-QsjL z7Rxw=ZyM}#&=@wZt!L?-8J|>4NTkE?a#@Wg`VUT-#z`{cZXe*E8_elB1z?MK*?8RA zky_do)rcQg^he?3Pu4Z3m#9^4p98wouHGD-QbTR@bjV~O=1_3zUMWD z*n|y*#h8fvJkoVH+QKUC9SF1V(9dAs#wY$IO6tds zv6_Vs!&&|m2zCi=hdt+u3W@E`C0{5MoF`2E*C^q%CBu}&FV887tfe-dt~Rzr@kQzO zpvziN;fN(~{s7sQ=+0ZE`N9RO)KfN({XQw@Q?(cLhuq2uItW3z z&7!_1K?fdRBvs>D0=OF{lp0stT=RNbMvlBhh%A z|KXnvYF70E2)s?B0u%eR<#l9clJkjpSZ*U!m|yjTN@;`Xf2aF*_DSgeV_S+wdYjrC zo}-BgX*erMOEnjqXw#VJ1ssFkBz7 z_;ZCj$_-TkHBJ$QT*&wDkJ3T->hYCCtj0eF`sO-L>j9Xx^#C(;%6 z`dU?=nbQqhc0_vmIPHrmUEbYzs_~9&j+;)-o_@GhUHZv!Xy+?HdSKRxLcQmJ`=-LF zlT&px3vZFlZcs@aQXCu#m@VTuNwDE^c!bqh@Pg$${(up7^ADVOof(uEMxh`m{0@ak zx91eOR<^;=f;anApO|4sfE{cT#p!aHGR5_lJ-s?!2AP+yk+Qst57GEbSuzkzRi5gn zi7-~KtxExQJ&ZY+L7zF3fBDq~nGdUkE(V)Eohtg-7AVT^MhQ;EJI5&@x|`N*Rf5WT z5kUzm43x|;p%%rP8@v%uFf{PZV9yux?`r6w+%?&xPWy5D=4rYZq0=P|Q+2TR+a;T= zCf?IJU+4Q-r5m?r4$hL0U|S58X2;~w zGF-q7e~F-2wE4`)&%dQAR!ki6_lf507gbS9=^PH|No(TGEigOzKF)lTay972QKdxF zs&jp$aO}PS7A0p?(yntD2@uBRa4jAuJUqWMR*6zoo5!lMKWwhYwZq&3`}^8Qh#_a0 zXCbpq@Ag1&^2|Fn*Jm_IlfM-|f1^nn5rq?7jgidfGS?erDk=^crgrU?3v|6=7+5~6 zB=r}Ry)bA+O#jW67-v5g#n%{Z4l!9%>UBb^o+iH{I2CEWdmCkYz01OIuM9I?4me}u4m+}ja*}!# z!Ao|8Gj|Mmk79{!`e4hc@4qn4D)Xqz3GN&79Gd&xc&9IERE!=cEHr@ctigB3;K*rx zL!9Z67OyMbh3xexS+giC^6wV#9uABOh~<)6*#LI&Gbb!=Ur@iVvhiqM-^$4O-~s&H zwP`&eO}?Ej3((`=9CLN}(J-pfL;sz(MIFFgGGMk;>P70QC&)fL9`ZQz+AmX)-|WsB z&5|>?EZ(vw1!oh5xLQ&(Z>aMz3iF(F67dG~^BvJ8>vS|0e1)c15=83M$8*rgi{Bfj zH^n0N`8Uak=W!#gO&z3%ZCXx=rNe3y#pL-1uv<3OcrVv-7eL@UKPJ21G_C$D`rFf=cV zQJ=kcpqx#SkjZu>DeVsOB(%%MDIf75jYhhmS{poG>FnY31ddTLpT<3@C!mDP31!!5 zOE{mKH#>-(GAHQ555HkxieZryg)XS}J+$q^MEy669=1hmU(;JE)<Vk_BKsK7b-*yi9n=3lREK36P{S$$98cE`!4mnaZ(W0wHJ z*xX?EGbF7tY|>%&80kqhy_@aP$pPQFS9z+uZUE1?x5DElW5>XiWB8z4PH(#z(C#U0 z3!)rx->!A2HdRNvK31)nV}0HZ4!C6KR!0mB%i7nGllOshv25_I=TstdeeV@p=<%HG z^?Rc-YsIT{G26)tZMenzpjLmmrj)_a%37lV-&UR`wXsG)dF%OMTWNRRq})vV1MO%s zDL=QT$6nI&9h|ofuTDPdd>oFT^KYp^y#4m9>R?U4rrwiy;whvsjrG0`smyz8g)su& z#{8VW2vy#*qvSW9K8{P@OZT@m%%O3XqWHsYr&Pf5!INyJV5BBHJ84ZOV@H7fBlPlR zvi77b4gX|et_z!h+x5B%o0t-(cp4Owk8Ou?Pq{jPs{@c? zh(}7^tu}^T~!C z#qcpcaOl_r_N_OIBrxeojy^M;9;BZtoX&=dZ$(9TOQ;8!UrNBy_X72%sMO|77gY}p z2v=Co?i27rVjAZwzv1ej(uR8(Jo!r%TS4DJ{EAvKm^wS;l=uHl$p}aHzEL<`?n{3f&LB zBHUnXu;aCJg4M$0OfbY+sm?OpgKD`yW1|wOCbN$oawXwtsc5sC(b(CeGduGCo$+H` zFbfUrU8LH!6mVNA<~`f>7~jAInLnz;EG~PzD6qLrVeTn;{JuJp`%yqEI_u)9lC zdC3*FFAHHBaO1~<#ynwi%9fZd;|2W_Q?!SWXZc8n``(Ea`mgI8RPc1O<*g6Tcw3A7 zo!!F)tf4MzC4MmHot-{@b)Hi4FHl*~OA4pIXQ`^ebq@yweU-YuowYT0rcV&cw2Q<| zY^XdUP7CXpCWW1qFrG%bR=RB{Bp=c`ox&4iYz1L*W|-xJ%@)X^W2=42fBUo7+vzD2 zw}+c8JH9->{s~3eRW*_UU)D-0dBA_lkjo4Bzlg~H4+Zu3vJMFLCd1bhQq1C6{tp~4 oo!*n;;NY + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + $LANGUAGEexecution + + + IPythonJSON, ØMQmachinery + WrapperKernel + + + + + + + + + + + + + + + $LANGUAGEexecution&JSON, ØMQmachinery + + + + + + NativeKernel + + diff --git a/docs/source/development/how_ipython_works.rst b/docs/source/development/how_ipython_works.rst new file mode 100644 index 0000000..89f9163 --- /dev/null +++ b/docs/source/development/how_ipython_works.rst @@ -0,0 +1,104 @@ +How IPython works +================= + +Terminal IPython +---------------- + +When you type ``ipython``, you get the original IPython interface, running in +the terminal. It does something like this:: + + while True: + code = input(">>> ") + exec(code) + +Of course, it's much more complex, because it has to deal with multi-line +code, tab completion using :mod:`readline`, magic commands, and so on. But the +model is like that: prompt the user for some code, and when they've entered it, +exec it in the same process. This model is often called a REPL, or +Read-Eval-Print-Loop. + +The IPython Kernel +------------------ + +All the other interfaces—the Notebook, the Qt console, ``ipython console`` in +the terminal, and third party interfaces—use the IPython Kernel. This is a +separate process which is responsible for running user code, and things like +computing possible completions. Frontends communicate with it using JSON +messages sent over `ZeroMQ `_ sockets; the protocol they use is described in +:doc:`messaging`. + +The core execution machinery for the kernel is shared with terminal IPython: + +.. image:: figs/ipy_kernel_and_terminal.png + +A kernel process can be connected to more than one frontend simultaneously. In +this case, the different frontends will have access to the same variables. + +.. TODO: Diagram illustrating this? + +This design was intended to allow easy development of different frontends based +on the same kernel, but it also made it possible to support new languages in the +same frontends, by developing kernels in those languages, and we are refining +IPython to make that more practical. + +Today, there are two ways to develop a kernel for another language. Wrapper +kernels reuse the communications machinery from IPython, and implement only the +core execution part. Native kernels implement execution and communications in +the target language: + +.. image:: figs/other_kernels.png + +Wrapper kernels are easier to write quickly for languages that have good Python +wrappers, like `octave_kernel `_, or +languages where it's impractical to implement the communications machinery, like +`bash_kernel `_. Native kernels are +likely to be better maintained by the community using them, like +`IJulia `_ or `IHaskell `_. + +.. seealso:: + + :doc:`kernels` + + :doc:`wrapperkernels` + +Notebooks +--------- + +The Notebook frontend does something extra. In addition to running your code, it +stores code and output, together with markdown notes, in an editable document +called a notebook. When you save it, this is sent from your browser to the +notebook server, which saves it on disk as a JSON file with a ``.ipynb`` +extension. + +.. image:: figs/notebook_components.png + +The notebook server, not the kernel, is responsible for saving and loading +notebooks, so you can edit notebooks even if you don't have the kernel for that +language—you just won't be able to run code. The kernel doesn't know anything +about the notebook document: it just gets sent cells of code to execute when the +user runs them. + +Exporting to other formats +`````````````````````````` + +The Nbconvert tool in IPython converts notebook files to other formats, such as +HTML, LaTeX, or reStructuredText. This conversion goes through a series of steps: + +.. image:: figs/nbconvert.png + +1. Preprocessors modify the notebook in memory. E.g. ExecutePreprocessor runs + the code in the notebook and updates the output. +2. An exporter converts the notebook to another file format. Most of the + exporters use templates for this. +3. Postprocessors work on the file produced by exporting. + +The `nbviewer `_ website uses nbconvert with the +HTML exporter. When you give it a URL, it fetches the notebook from that URL, +converts it to HTML, and serves that HTML to you. + +IPython.parallel +---------------- + +IPython also includes a parallel computing framework, ``IPython.parallel``. This +allows you to control many individual engines, which are an extended version of +the IPython kernel described above. For more details, see :doc:`/parallel/index`. diff --git a/docs/source/development/index.rst b/docs/source/development/index.rst index 5771c25..ba7dea4 100644 --- a/docs/source/development/index.rst +++ b/docs/source/development/index.rst @@ -19,6 +19,7 @@ on the IPython GitHub wiki. .. toctree:: :maxdepth: 1 + how_ipython_works messaging kernels wrapperkernels diff --git a/docs/source/development/kernels.rst b/docs/source/development/kernels.rst index 4077d3b..6336dc1 100644 --- a/docs/source/development/kernels.rst +++ b/docs/source/development/kernels.rst @@ -112,31 +112,16 @@ JSON serialised dictionary containing the following keys and values: - **display_name**: The kernel's name as it should be displayed in the UI. Unlike the kernel name used in the API, this can contain arbitrary unicode characters. -- **language**: The programming language which this kernel runs. This will be - stored in notebook metadata. This may be used by syntax highlighters to guess - how to parse code in a notebook, and frontends may eventually use it to - identify alternative kernels that can run some code. -- **codemirror_mode** (optional): The `codemirror mode `_ - to use for code in this language. This can be a string or a dictionary, as - passed to codemirror config. The string from *language* will be used if this is - not provided. - **env** (optional): A dictionary of environment variables to set for the kernel. These will be added to the current environment variables before the kernel is started. -- **help_links** (optional): A list of dictionaries, each with keys 'text' and - 'url'. These will be displayed in the help menu in the notebook UI. For example, the kernel.json file for IPython looks like this:: { "argv": ["python3", "-c", "from IPython.kernel.zmq.kernelapp import main; main()", - "-f", "{connection_file}"], - "codemirror_mode": { - "version": 3, - "name": "ipython" - }, + "-f", "{connection_file}"], "display_name": "IPython (Python 3)", - "language": "python" } To see the available kernel specs, run:: diff --git a/docs/source/development/messaging.rst b/docs/source/development/messaging.rst index f43b137..08b7618 100644 --- a/docs/source/development/messaging.rst +++ b/docs/source/development/messaging.rst @@ -355,13 +355,11 @@ Message type: ``execute_reply``:: When status is 'ok', the following extra fields are present:: { - # 'payload' will be a list of payload dicts. - # Each execution payload is a dict with string keys that may have been - # produced by the code being executed. It is retrieved by the kernel at - # the end of the execution and sent back to the front end, which can take - # action on it as needed. + # 'payload' will be a list of payload dicts, and is optional. + # payloads are considered deprecated. # The only requirement of each payload dict is that it have a 'source' key, - # which is a string classifying the payload (e.g. 'pager'). + # which is a string classifying the payload (e.g. 'page'). + 'payload' : list(dict), # Results for the user_expressions. @@ -372,24 +370,6 @@ When status is 'ok', the following extra fields are present:: ``user_variables`` is removed, use user_expressions instead. -.. admonition:: Execution payloads - - The notion of an 'execution payload' is different from a return value of a - given set of code, which normally is just displayed on the execute_result stream - through the PUB socket. The idea of a payload is to allow special types of - code, typically magics, to populate a data container in the IPython kernel - that will be shipped back to the caller via this channel. The kernel - has an API for this in the PayloadManager:: - - ip.payload_manager.write_payload(payload_dict) - - which appends a dictionary to the list of payloads. - - The payload API is not yet stabilized, - and should probably not be supported by non-Python kernels at this time. - In such cases, the payload list should always be empty. - - When status is 'error', the following extra fields are present:: { @@ -411,6 +391,72 @@ When status is 'error', the following extra fields are present:: When status is 'abort', there are for now no additional data fields. This happens when the kernel was interrupted by a signal. +Payloads +******** + +.. admonition:: Execution payloads + + Payloads are considered deprecated, though their replacement is not yet implemented. + +Payloads are a way to trigger frontend actions from the kernel. Current payloads: + +**page**: display data in a pager. + +Pager output is used for introspection, or other displayed information that's not considered output. +Pager payloads are generally displayed in a separate pane, that can be viewed alongside code, +and are not included in notebook documents. + +.. sourcecode:: python + + { + "source": "page", + # mime-bundle of data to display in the pager. + # Must include text/plain. + "data": mimebundle, + # line offset to start from + "start": int, + } + +**set_next_input**: create a new output + +used to create new cells in the notebook, +or set the next input in a console interface. +The main example being ``%load``. + +.. sourcecode:: python + + { + "source": "set_next_input", + # the text contents of the cell to create + "text": "some cell content", + } + +**edit**: open a file for editing. + +Triggered by `%edit`. Only the QtConsole currently supports edit payloads. + +.. sourcecode:: python + + { + "source": "edit", + "filename": "/path/to/file.py", # the file to edit + "line_number": int, # the line number to start with + } + +**ask_exit**: instruct the frontend to prompt the user for exit + +Allows the kernel to request exit, e.g. via ``%exit`` in IPython. +Only for console frontends. + +.. sourcecode:: python + + { + "source": "ask_exit", + # whether the kernel should be left running, only closing the client + "keepkernel": bool, + } + + .. _msging_inspection: Introspection @@ -573,6 +619,51 @@ Message type: ``history_reply``:: 'history' : list, } +.. _msging_is_complete: + +Code completeness +----------------- + +.. versionadded:: 5.0 + +When the user enters a line in a console style interface, the console must +decide whether to immediately execute the current code, or whether to show a +continuation prompt for further input. For instance, in Python ``a = 5`` would +be executed immediately, while ``for i in range(5):`` would expect further input. + +There are four possible replies: + +- *complete* code is ready to be executed +- *incomplete* code should prompt for another line +- *invalid* code will typically be sent for execution, so that the user sees the + error soonest. +- *unknown* - if the kernel is not able to determine this. The frontend should + also handle the kernel not replying promptly. It may default to sending the + code for execution, or it may implement simple fallback heuristics for whether + to execute the code (e.g. execute after a blank line). + +Frontends may have ways to override this, forcing the code to be sent for +execution or forcing a continuation prompt. + +Message type: ``is_complete_request``:: + + content = { + # The code entered so far as a multiline string + 'code' : str, + } + +Message type: ``is_complete_reply``:: + + content = { + # One of 'complete', 'incomplete', 'invalid', 'unknown' + 'status' : str, + + # If status is 'incomplete', indent should contain the characters to use + # to indent the next line. This is only a hint: frontends may ignore it + # and use their own autoindentation rules. For other statuses, this + # field does not exist. + 'indent': str, + } Connect ------- @@ -629,22 +720,53 @@ Message type: ``kernel_info_reply``:: # Implementation version number. # The version number of the kernel's implementation # (e.g. IPython.__version__ for the IPython kernel) - 'implementation_version': 'X.Y.Z', - - # Programming language in which kernel is implemented. - # Kernel included in IPython returns 'python'. - 'language': str, - - # Language version number. - # It is Python version number (e.g., '2.7.3') for the kernel - # included in IPython. - 'language_version': 'X.Y.Z', + 'implementation_version': 'X.Y.Z', + + # Information about the language of code for the kernel + 'language_info': { + # Name of the programming language in which kernel is implemented. + # Kernel included in IPython returns 'python'. + 'name': str, + + # Language version number. + # It is Python version number (e.g., '2.7.3') for the kernel + # included in IPython. + 'version': 'X.Y.Z', + + # mimetype for script files in this language + 'mimetype': str, + + # Extension without the dot, e.g. 'py' + 'file_extension': str, + + # Pygments lexer, for highlighting + # Only needed if it differs from the top level 'language' field. + 'pygments_lexer': str, + + # Codemirror mode, for for highlighting in the notebook. + # Only needed if it differs from the top level 'language' field. + 'codemirror_mode': str or dict, + + # Nbconvert exporter, if notebooks written with this kernel should + # be exported with something other than the general 'script' + # exporter. + 'nbconvert_exporter': str, + }, # A banner of information about the kernel, # which may be desplayed in console environments. 'banner' : str, + + # Optional: A list of dictionaries, each with keys 'text' and 'url'. + # These will be displayed in the help menu in the notebook UI. + 'help_links': [ + {'text': str, 'url': str} + ], } +Refer to the lists of available `Pygments lexers `_ +and `codemirror modes `_ for those fields. + .. versionchanged:: 5.0 Versions changed from lists of integers to strings. @@ -655,7 +777,16 @@ Message type: ``kernel_info_reply``:: .. versionchanged:: 5.0 - ``implementation``, ``implementation_version``, and ``banner`` keys are added. + ``language_info``, ``implementation``, ``implementation_version``, ``banner`` + and ``help_links`` keys are added. + +.. versionchanged:: 5.0 + + ``language_version`` moved to ``language_info.version`` + +.. versionchanged:: 5.0 + + ``language`` moved to ``language_info.name`` .. _msging_shutdown: @@ -711,10 +842,14 @@ Message type: ``stream``:: # The name of the stream is one of 'stdout', 'stderr' 'name' : str, - # The data is an arbitrary string to be written to that stream - 'data' : str, + # The text is an arbitrary string to be written to that stream + 'text' : str, } +.. versionchanged:: 5.0 + + 'data' key renamed to 'text' for conistency with the notebook format. + Display Data ------------ diff --git a/docs/source/development/wrapperkernels.rst b/docs/source/development/wrapperkernels.rst index a8817a0..4525150 100644 --- a/docs/source/development/wrapperkernels.rst +++ b/docs/source/development/wrapperkernels.rst @@ -34,6 +34,17 @@ following methods and attributes: interprets (e.g. Python). The 'banner' is displayed to the user in console UIs before the first prompt. All of these values are strings. + .. attribute:: language_info + + Language information for :ref:`msging_kernel_info` replies, in a dictionary. + This should contain the key ``mimetype`` with the mimetype of code in the + target language (e.g. ``'text/x-python'``), and ``file_extension`` (e.g. + ``'py'``). + It may also contain keys ``codemirror_mode`` and ``pygments_lexer`` if they + need to differ from :attr:`language`. + + Other keys may be added to this later. + .. method:: do_execute(code, silent, store_history=True, user_expressions=None, allow_stdin=False) Execute user code. @@ -71,12 +82,13 @@ Example implementation_version = '1.0' language = 'no-op' language_version = '0.1' + language_info = {'mimetype': 'text/plain'} banner = "Echo kernel - as useful as a parrot" def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False): if not silent: - stream_content = {'name': 'stdout', 'data':code} + stream_content = {'name': 'stdout', 'text': code} self.send_response(self.iopub_socket, 'stream', stream_content) return {'status': 'ok', @@ -94,7 +106,6 @@ Here's the Kernel spec ``kernel.json`` file for this:: {"argv":["python","-m","echokernel", "-f", "{connection_file}"], "display_name":"Echo", - "language":"no-op" } @@ -141,6 +152,17 @@ relevant section of the :doc:`messaging spec `. :ref:`msging_history` messages + .. method:: do_is_complete(code) + + Is code entered in a console-like interface complete and ready to execute, + or should a continuation prompt be shown? + + :param str code: The code entered so far - possibly multiple lines + + .. seealso:: + + :ref:`msging_is_complete` messages + .. method:: do_shutdown(restart) Shutdown the kernel. You only need to handle your own clean up - the kernel diff --git a/docs/source/install/install.rst b/docs/source/install/install.rst index b809471..5b21e5b 100644 --- a/docs/source/install/install.rst +++ b/docs/source/install/install.rst @@ -13,7 +13,7 @@ the quickest way to get up and running with IPython is: .. code-block:: bash - $ pip install ipython[all] + $ pip install "ipython[all]" This will download and install IPython and its main optional dependencies: @@ -237,7 +237,7 @@ optional dependencies: .. code-block:: bash - $ pip install ipython[terminal] + $ pip install "ipython[terminal]" nose @@ -255,7 +255,7 @@ Another way of getting this is to do: .. code-block:: bash - $ pip install ipython[test] + $ pip install "ipython[test]" For more installation options, see the `nose website `_. @@ -279,7 +279,7 @@ On a Unix style platform (including OS X), if you want to use .. code-block:: bash - $ pip install ipython[zmq] # will include pyzmq + $ pip install "ipython[zmq]" # will include pyzmq Security in IPython.parallel is provided by SSH tunnels. By default, Linux and OSX clients will use the shell ssh command, but on Windows, we also diff --git a/docs/source/interactive/index.rst b/docs/source/interactive/index.rst index 9a4ecdf..f238298 100644 --- a/docs/source/interactive/index.rst +++ b/docs/source/interactive/index.rst @@ -8,6 +8,7 @@ Using IPython for interactive work tutorial tips reference + magics shell qtconsole diff --git a/docs/source/interactive/magics.rst b/docs/source/interactive/magics.rst new file mode 100644 index 0000000..1a24289 --- /dev/null +++ b/docs/source/interactive/magics.rst @@ -0,0 +1,5 @@ +======================= +Built-in magic commands +======================= + +.. include:: magics-generated.txt diff --git a/docs/source/interactive/qtconsole.rst b/docs/source/interactive/qtconsole.rst index 0394569..cdaa4f4 100644 --- a/docs/source/interactive/qtconsole.rst +++ b/docs/source/interactive/qtconsole.rst @@ -4,6 +4,10 @@ A Qt Console for IPython ========================= +To start the Qt Console:: + + $> ipython qtconsole + We now have a version of IPython, using the new two-process :ref:`ZeroMQ Kernel `, running in a PyQt_ GUI. This is a very lightweight widget that largely feels like a terminal, but provides a number of enhancements only diff --git a/docs/source/interactive/reference.rst b/docs/source/interactive/reference.rst index aacbf90..64cf953 100644 --- a/docs/source/interactive/reference.rst +++ b/docs/source/interactive/reference.rst @@ -88,13 +88,13 @@ current execution block. Cell magics can in fact make arbitrary modifications to the input they receive, which need not even be valid Python code at all. They receive the whole block as a single string. -As a line magic example, the ``%cd`` magic works just like the OS command of +As a line magic example, the :magic:`cd` magic works just like the OS command of the same name:: In [8]: %cd /home/fperez -The following uses the builtin ``timeit`` in cell mode:: +The following uses the builtin :magic:`timeit` in cell mode:: In [10]: %%timeit x = range(10000) ...: min(x) @@ -104,7 +104,7 @@ The following uses the builtin ``timeit`` in cell mode:: In this case, ``x = range(10000)`` is called as the line argument, and the block with ``min(x)`` and ``max(x)`` is called as the cell body. The -``timeit`` magic receives both. +:magic:`timeit` magic receives both. If you have 'automagic' enabled (as it by default), you don't need to type in the single ``%`` explicitly for line magics; IPython will scan its internal @@ -156,6 +156,9 @@ docstrings of all currently available magic commands. .. seealso:: + :doc:`magics` + A list of the line and cell magics available in IPython by default + :ref:`defining_magics` How to define and register additional magic functions @@ -185,21 +188,19 @@ Typing ``??word`` or ``word??`` gives access to the full information, including the source code where possible. Long strings are not snipped. The following magic functions are particularly useful for gathering -information about your working environment. You can get more details by -typing ``%magic`` or querying them individually (``%function_name?``); -this is just a summary: +information about your working environment: - * **%pdoc **: Print (or run through a pager if too long) the + * :magic:`pdoc` ****: Print (or run through a pager if too long) the docstring for an object. If the given object is a class, it will print both the class and the constructor docstrings. - * **%pdef **: Print the call signature for any callable + * :magic:`pdef` ****: Print the call signature for any callable object. If the object is a class, print the constructor information. - * **%psource **: Print (or run through a pager if too long) + * :magic:`psource` ****: Print (or run through a pager if too long) the source code for an object. - * **%pfile **: Show the entire source file where an object was + * :magic:`pfile` ****: Show the entire source file where an object was defined via a pager, opening it at the line where the object definition begins. - * **%who/%whos**: These functions give information about identifiers + * :magic:`who`/:magic:`whos`: These functions give information about identifiers you have defined interactively (not things you loaded or defined in your configuration files). %who just prints a list of identifiers and %whos prints a table with some basic details about @@ -310,7 +311,7 @@ Session logging and restoring You can log all input from a session either by starting IPython with the command line switch ``--logfile=foo.py`` (see :ref:`here `) -or by activating the logging at any moment with the magic function %logstart. +or by activating the logging at any moment with the magic function :magic:`logstart`. Log files can later be reloaded by running them as scripts and IPython will attempt to 'replay' the log by executing all the lines in it, thus @@ -322,7 +323,7 @@ any code you wrote while experimenting. Log files are regular text files which you can later open in your favorite text editor to extract code or to 'clean them up' before using them to replay a session. -The `%logstart` function for activating logging in mid-session is used as +The :magic:`logstart` function for activating logging in mid-session is used as follows:: %logstart [log_name [log_mode]] @@ -341,7 +342,7 @@ one of (note that the modes are given unquoted): * [append:] well, that says it. * [rotate:] create rotating logs log_name.1~, log_name.2~, etc. -The %logoff and %logon functions allow you to temporarily stop and +The :magic:`logoff` and :magic:`logon` functions allow you to temporarily stop and resume logging to a file which had previously been started with %logstart. They will fail (with an explanation) if you try to use them before logging has been started. @@ -362,7 +363,7 @@ You can assign the result of a system command to a Python variable with the syntax ``myfiles = !ls``. This gets machine readable output from stdout (e.g. without colours), and splits on newlines. To explicitly get this sort of output without assigning to a variable, use two exclamation marks (``!!ls``) or -the ``%sx`` magic command. +the :magic:`sx` magic command. The captured list has some convenience features. ``myfiles.n`` or ``myfiles.s`` returns a string delimited by newlines or spaces, respectively. ``myfiles.p`` @@ -387,10 +388,12 @@ For simple cases, you can alternatively prepend $ to a variable name:: In [7]: !echo "A system variable: $$HOME" # Use $$ for literal $ A system variable: /home/fperez +Note that `$$` is used to represent a literal `$`. + System command aliases ---------------------- -The %alias magic function allows you to define magic functions which are in fact +The :magic:`alias` magic function allows you to define magic functions which are in fact system shell commands. These aliases can have parameters. ``%alias alias_name cmd`` defines 'alias_name' as an alias for 'cmd' @@ -409,10 +412,10 @@ replaced by a positional parameter to the call to %parts:: In [3]: parts A ERROR: Alias requires 2 arguments, 1 given. -If called with no parameters, %alias prints the table of currently +If called with no parameters, :magic:`alias` prints the table of currently defined aliases. -The %rehashx magic allows you to load your entire $PATH as +The :magic:`rehashx` magic allows you to load your entire $PATH as ipython aliases. See its docstring for further details. @@ -438,7 +441,7 @@ detailed tracebacks. Furthermore, both normal and verbose tracebacks can be colored (if your terminal supports it) which makes them much easier to parse visually. -See the magic xmode and colors functions for details. +See the magic :magic:`xmode` and :magic:`colors` functions for details. These features are basically a terminal version of Ka-Ping Yee's cgitb module, now part of the standard Python library. @@ -452,8 +455,8 @@ Input caching system IPython offers numbered prompts (In/Out) with input and output caching (also referred to as 'input history'). All input is saved and can be retrieved as variables (besides the usual arrow key recall), in -addition to the %rep magic command that brings a history entry -up for editing on the next command line. +addition to the :magic:`rep` magic command that brings a history entry +up for editing on the next command line. The following variables always exist: @@ -474,17 +477,17 @@ characters. You can also manipulate them like regular variables (they are strings), modify or exec them. You can also re-execute multiple lines of input easily by using the -magic %rerun or %macro functions. The macro system also allows you to re-execute +magic :magic:`rerun` or :magic:`macro` functions. The macro system also allows you to re-execute previous lines which include magic function calls (which require special processing). Type %macro? for more details on the macro system. -A history function %hist allows you to see any part of your input +A history function :magic:`history` allows you to see any part of your input history by printing a range of the _i variables. You can also search ('grep') through your history by typing ``%hist -g somestring``. This is handy for searching for URLs, IP addresses, etc. You can bring history entries listed by '%hist -g' up for editing -with the %recall command, or run them immediately with %rerun. +with the %recall command, or run them immediately with :magic:`rerun`. .. _output_caching: @@ -520,15 +523,15 @@ This system obviously can potentially put heavy memory demands on your system, since it prevents Python's garbage collector from removing any previously computed results. You can control how many results are kept in memory with the configuration option ``InteractiveShell.cache_size``. -If you set it to 0, output caching is disabled. You can also use the ``%reset`` -and ``%xdel`` magics to clear large items from memory. +If you set it to 0, output caching is disabled. You can also use the :magic:`reset` +and :magic:`xdel` magics to clear large items from memory. Directory history ----------------- Your history of visited directories is kept in the global list _dh, and -the magic %cd command can be used to go to any entry in that list. The -%dhist command allows you to view this history. Do ``cd -`` to +the magic :magic:`cd` command can be used to go to any entry in that list. The +:magic:`dhist` command allows you to view this history. Do ``cd -`` to conveniently view the directory history. @@ -705,7 +708,7 @@ allows you to step through code, set breakpoints, watch variables, etc. IPython makes it very easy to start any script under the control of pdb, regardless of whether you have wrapped it into a 'main()' function or not. For this, simply type ``%run -d myscript`` at an -IPython prompt. See the %run command's documentation for more details, including +IPython prompt. See the :magic:`run` command's documentation for more details, including how to control where pdb will stop execution first. For more information on the use of the pdb debugger, see :ref:`debugger-commands` @@ -722,9 +725,9 @@ while your program is at this point 'dead', all the data is still available and you can walk up and down the stack frame and understand the origin of the problem. -You can use the ``%debug`` magic after an exception has occurred to start +You can use the :magic:`debug` magic after an exception has occurred to start post-mortem debugging. IPython can also call debugger every time your code -triggers an uncaught exception. This feature can be toggled with the %pdb magic +triggers an uncaught exception. This feature can be toggled with the :magic:`pdb` magic command, or you can start IPython with the ``--pdb`` option. For a post-mortem debugger in your programs outside IPython, @@ -803,7 +806,7 @@ advantages of this are: all of these things. For users, enabling GUI event loop integration is simple. You simple use the -``%gui`` magic as follows:: +:magic:`gui` magic as follows:: %gui [GUINAME] @@ -902,7 +905,7 @@ scientific computing, all with a syntax compatible with that of the popular Matlab program. To start IPython with matplotlib support, use the ``--matplotlib`` switch. If -IPython is already running, you can run the ``%matplotlib`` magic. If no +IPython is already running, you can run the :magic:`matplotlib` magic. If no arguments are given, IPython will automatically detect your choice of matplotlib backend. You can also request a specific backend with ``%matplotlib backend``, where ``backend`` must be one of: 'tk', 'qt', 'wx', diff --git a/docs/source/interactive/tutorial.rst b/docs/source/interactive/tutorial.rst index cd96506..7fcf779 100644 --- a/docs/source/interactive/tutorial.rst +++ b/docs/source/interactive/tutorial.rst @@ -60,7 +60,7 @@ 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. -The following examples show how to call the builtin ``timeit`` magic, both in +The following examples show how to call the builtin :magic:`timeit` magic, both in line and cell mode:: In [1]: %timeit range(1000) @@ -73,19 +73,19 @@ line and cell mode:: The builtin magics include: -- Functions that work with code: ``%run``, ``%edit``, ``%save``, ``%macro``, - ``%recall``, etc. -- Functions which affect the shell: ``%colors``, ``%xmode``, ``%autoindent``, - ``%automagic``, etc. -- Other functions such as ``%reset``, ``%timeit``, ``%%file``, ``%load``, or - ``%paste``. +- Functions that work with code: :magic`run`, :magic:`edit`, :magic:`save`, :magic:`macro`, + :magic:`recall`, etc. +- Functions which affect the shell: :magic:`colors`, :magic:`xmode`, :magic:`autoindent`, + :magic:`automagic`, etc. +- Other functions such as :magic:`reset`, :magic:`timeit`, :cellmagic:`writefile`, :magic:`load`, or + :magic:`paste`. You can always call them using the ``%`` prefix, and if you're calling a line magic on a line by itself, you can omit even that:: run thescript.py -You can toggle this behavior by running the ``%automagic`` magic. Cell magics +You can toggle this behavior by running the :magic:`automagic` magic. Cell magics must always have the ``%%`` prefix. A more detailed explanation of the magic system can be obtained by calling @@ -95,12 +95,14 @@ read its docstring. To see all the available magic functions, call .. seealso:: + :doc:`magics` + `Cell magics`_ example notebook Running and Editing ------------------- -The ``%run`` magic command allows you to run any python script and load all of +The :magic:`run` magic command allows you to run any python script and load all of its data directly into the interactive namespace. Since the file is re-read from disk each time, changes you make to it are reflected immediately (unlike imported modules, which have to be specifically reloaded). IPython also @@ -110,15 +112,15 @@ includes :ref:`dreload `, a recursive reload function. for running them under the control of either Python's pdb debugger (-d) or profiler (-p). -The ``%edit`` command gives a reasonable approximation of multiline editing, +The :magic:`edit` command gives a reasonable approximation of multiline editing, by invoking your favorite editor on the spot. IPython will execute the code you type in there as if it were typed interactively. Debugging --------- -After an exception occurs, you can call ``%debug`` to jump into the Python -debugger (pdb) and examine the problem. Alternatively, if you call ``%pdb``, +After an exception occurs, you can call :magic:`debug` to jump into the Python +debugger (pdb) and examine the problem. Alternatively, if you call :magic:`pdb`, IPython will automatically start the debugger on any uncaught exception. You can print variables, see code, execute statements and even walk up and down the call stack to track down the true source of the problem. This can be an efficient @@ -163,7 +165,7 @@ You can capture the output into a Python list, e.g.: ``files = !ls``. To pass the values of Python variables or expressions to system commands, prefix them with $: ``!grep -rF $pattern ipython/*``. See :ref:`our shell section ` for more details. - + Define your own system aliases ------------------------------ @@ -171,7 +173,7 @@ It's convenient to have aliases to the system commands you use most often. This allows you to work seamlessly from inside IPython with the same commands you are used to in your system shell. IPython comes with some pre-defined aliases and a complete system for changing directories, both via a stack (see -%pushd, %popd and %dhist) and via direct %cd. The latter keeps a history of +:magic:`pushd`, :magic:`popd` and :magic:`dhist`) and via direct :magic:`cd`. The latter keeps a history of visited directories and allows you to go to any previously visited one. diff --git a/docs/source/notebook/index.rst b/docs/source/notebook/index.rst index a069111..19de406 100644 --- a/docs/source/notebook/index.rst +++ b/docs/source/notebook/index.rst @@ -6,6 +6,7 @@ The IPython notebook :maxdepth: 2 notebook + nbformat nbconvert public_server security diff --git a/docs/source/notebook/nbformat.rst b/docs/source/notebook/nbformat.rst new file mode 100644 index 0000000..b2ee31f --- /dev/null +++ b/docs/source/notebook/nbformat.rst @@ -0,0 +1,342 @@ +.. _nbformat: + +=========================== +The Jupyter Notebook Format +=========================== + +Introduction +============ + +Jupyter (né IPython) notebook files are simple JSON documents, +containing text, source code, rich media output, and metadata. +each segment of the document is stored in a cell. + +Some general points about the notebook format: + +.. note:: + + *All* metadata fields are optional. + While the type and values of some metadata are defined, + no metadata values are required to be defined. + + +Top-level structure +=================== + +At the highest level, a Jupyter notebook is a dictionary with a few keys: + +- metadata (dict) +- nbformat (int) +- nbformat_minor (int) +- cells (list) + +.. sourcecode:: python + + { + "metadata" : { + "signature": "hex-digest", # used for authenticating unsafe outputs on load + "kernel_info": { + # if kernel_info is defined, its name field is required. + "name" : "the name of the kernel" + }, + "language_info": { + # if language_info is defined, its name field is required. + "name" : "the programming language of the kernel", + "version": "the version of the language", + "codemirror_mode": "The name of the codemirror mode to use [optional]" + } + }, + "nbformat": 4, + "nbformat_minor": 0, + "cells" : [ + # list of cell dictionaries, see below + ], + } + +Some fields, such as code input and text output, are characteristically multi-line strings. +When these fields are written to disk, they **may** be written as a list of strings, +which should be joined with ``''`` when reading back into memory. +In programmatic APIs for working with notebooks (Python, Javascript), +these are always re-joined into the original multi-line string. +If you intend to work with notebook files directly, +you must allow multi-line string fields to be either a string or list of strings. + + +Cell Types +========== + +There are a few basic cell types for encapsulating code and text. +All cells have the following basic structure: + +.. sourcecode:: python + + { + "cell_type" : "name", + "metadata" : {}, + "source" : "single string or [list, of, strings]", + } + + +Markdown cells +-------------- + +Markdown cells are used for body-text, and contain markdown, +as defined in `GitHub-flavored markdown`_, and implemented in marked_. + +.. _GitHub-flavored markdown: https://help.github.com/articles/github-flavored-markdown +.. _marked: https://github.com/chjj/marked + +.. sourcecode:: python + + { + "cell_type" : "markdown", + "metadata" : {}, + "source" : ["some *markdown*"], + } + +.. versionchanged:: nbformat 4.0 + + Heading cells have been removed, in favor of simple headings in markdown. + + +Code cells +---------- + +Code cells are the primary content of Jupyter notebooks. +They contain source code in the language of the document's associated kernel, +and a list of outputs associated with executing that code. +They also have an execution_count, which must be an integer or ``null``. + +.. sourcecode:: python + + { + "cell_type" : "code", + "execution_count": 1, # integer or null + "metadata" : { + "collapsed" : True, # whether the output of the cell is collapsed + "autoscroll": False, # any of true, false or "auto" + }, + "source" : ["some code"], + "outputs": [{ + # list of output dicts (described below) + "output_type": "stream", + ... + }], + } + +.. versionchanged:: nbformat 4.0 + + ``input`` was renamed to ``source``, for consistency among cell types. + +.. versionchanged:: nbformat 4.0 + + ``prompt_number`` renamed to ``execution_count`` + +Code cell outputs +----------------- + +A code cell can have a variety of outputs (stream data or rich mime-type output). +These correspond to :ref:`messages ` produced as a result of executing the cell. + +All outputs have an ``output_type`` field, +which is a string defining what type of output it is. + + +stream output +************* + +.. sourcecode:: python + + { + "output_type" : "stream", + "name" : "stdout", # or stderr + "data" : ["multiline stream text"], + } + +.. versionchanged:: nbformat 4.0 + + The keys ``stream`` and ``text`` were changed to ``name`` and ``data`` to match + the stream message specification. + + +display_data +************ + +Rich display outputs, as created by ``display_data`` messages, +contain data keyed by mime-type. This is often called a mime-bundle, +and shows up in various locations in the notebook format and message spec. +The metadata of these messages may be keyed by mime-type as well. + + + +.. sourcecode:: python + + { + "output_type" : "display_data", + "data" : { + "text/plain" : ["multiline text data"], + "image/png": ["base64-encoded-png-data"], + "application/json": { + # JSON data is included as-is + "json": "data", + }, + }, + "metadata" : { + "image/png": { + "width": 640, + "height": 480, + }, + }, + } + + +.. versionchanged:: nbformat 4.0 + + ``application/json`` output is no longer double-serialized into a string. + +.. versionchanged:: nbformat 4.0 + + mime-types are used for keys, instead of a combination of short names (``text``) + and mime-types, and are stored in a ``data`` key, rather than the top-level. + i.e. ``output.data['image/png']`` instead of ``output.png``. + + +execute_result +************** + +Results of executing a cell (as created by ``displayhook`` in Python) +are stored in ``execute_result`` outputs. +`execute_result` outputs are identical to ``display_data``, +adding only a ``execution_count`` field, which must be an integer. + +.. sourcecode:: python + + { + "output_type" : "execute_result", + "execution_count": 42, + "data" : { + "text/plain" : ["multiline text data"], + "image/png": ["base64-encoded-png-data"], + "application/json": { + # JSON data is included as-is + "json": "data", + }, + }, + "metadata" : { + "image/png": { + "width": 640, + "height": 480, + }, + }, + } + +.. versionchanged:: nbformat 4.0 + + ``pyout`` renamed to ``execute_result`` + +.. versionchanged:: nbformat 4.0 + + ``prompt_number`` renamed to ``execution_count`` + + +error +***** + +Failed execution may show a traceback + +.. sourcecode:: python + + { + 'ename' : str, # Exception name, as a string + 'evalue' : str, # Exception value, as a string + + # The traceback will contain a list of frames, + # represented each as a string. + 'traceback' : list, + } + +.. versionchanged:: nbformat 4.0 + + ``pyerr`` renamed to ``error`` + + +.. _raw nbconvert cells: + +Raw NBConvert cells +------------------- + +A raw cell is defined as content that should be included *unmodified* in :ref:`nbconvert ` output. +For example, this cell could include raw LaTeX for nbconvert to pdf via latex, +or restructured text for use in Sphinx documentation. + +The notebook authoring environment does not render raw cells. + +The only logic in a raw cell is the `format` metadata field. +If defined, it specifies which nbconvert output format is the intended target +for the raw cell. When outputting to any other format, +the raw cell's contents will be excluded. +In the default case when this value is undefined, +a raw cell's contents will be included in any nbconvert output, +regardless of format. + +.. sourcecode:: python + + { + "cell_type" : "raw", + "metadata" : { + # the mime-type of the target nbconvert format. + # nbconvert to formats other than this will exclude this cell. + "format" : "mime/type" + }, + "source" : ["some nbformat mime-type data"] + } + +Metadata +======== + +Metadata is a place that you can put arbitrary JSONable information about +your notebook, cell, or output. Because it is a shared namespace, +any custom metadata should use a sufficiently unique namespace, +such as `metadata.kaylees_md.foo = "bar"`. + +Metadata fields officially defined for Jupyter notebooks are listed here: + +Notebook metadata +----------------- + +The following metadata keys are defined at the notebook level: + +=========== =============== ============== +Key Value Interpretation +=========== =============== ============== +kernelspec dict A :ref:`kernel specification ` +signature str A hashed :ref:`signature ` of the notebook +=========== =============== ============== + + +Cell metadata +------------- + +The following metadata keys are defined at the cell level: + +=========== =============== ============== +Key Value Interpretation +=========== =============== ============== +collapsed bool Whether the cell's output container should be collapsed +autoscroll bool or 'auto' Whether the cell's output is scrolled, unscrolled, or autoscrolled +deletable bool If False, prevent deletion of the cell +format 'mime/type' The mime-type of a :ref:`Raw NBConvert Cell ` +name str A name for the cell. Should be unique +tags list of str A list of string tags on the cell. Commas are not allowed in a tag +=========== =============== ============== + +Output metadata +--------------- + +The following metadata keys are defined for code cell outputs: + +=========== =============== ============== +Key Value Interpretation +=========== =============== ============== +isolated bool Whether the output should be isolated into an IFrame +=========== =============== ============== diff --git a/docs/source/notebook/public_server.rst b/docs/source/notebook/public_server.rst index 1745497..e8b9a32 100644 --- a/docs/source/notebook/public_server.rst +++ b/docs/source/notebook/public_server.rst @@ -95,9 +95,6 @@ commented; the minimum set you need to uncomment and edit is the following:: c = get_config() - # Kernel config - c.IPKernelApp.pylab = 'inline' # if you want plotting support always - # Notebook config c.NotebookApp.certfile = u'/absolute/path/to/your/certificate/mycert.pem' c.NotebookApp.ip = '*' @@ -110,6 +107,18 @@ You can then start the notebook and access it later by pointing your browser to ``https://your.host.com:9999`` with ``ipython notebook --profile=nbserver``. + +Firewall Setup +`````````````` + +To function correctly, the firewall on the computer running the ipython server must be +configured to allow connections from client machines on the ``c.NotebookApp.port`` +port to allow connections to the web interface. The firewall must also allow +connections from 127.0.0.1 (localhost) on ports from 49152 to 65535. +These ports are used by the server to communicate with the notebook kernels. +The kernel communication ports are chosen randomly by ZeroMQ, and may require +multiple connections per kernel, so a large range of ports must be accessible. + Running with a different URL prefix ----------------------------------- diff --git a/docs/source/parallel/magics.rst b/docs/source/parallel/magics.rst index 01ccbdf..1036d92 100644 --- a/docs/source/parallel/magics.rst +++ b/docs/source/parallel/magics.rst @@ -13,7 +13,8 @@ These magics will automatically become available when you create a Client: .. sourcecode:: ipython - In [2]: rc = parallel.Client() + In [1]: from IPython.parallel import Client + In [2]: rc = Client() The initially active View will have attributes ``targets='all', block=True``, which is a blocking view of all engines, evaluated at request time diff --git a/docs/source/whatsnew/development.rst b/docs/source/whatsnew/development.rst index 58a7e31..87b41c5 100644 --- a/docs/source/whatsnew/development.rst +++ b/docs/source/whatsnew/development.rst @@ -53,7 +53,7 @@ Other new features ``placeholder`` attribute, for displaying placeholder text before the user has typed anything. -* The %load magic can now find the source for objects in the user namespace. +* The :magic:`load` magic can now find the source for objects in the user namespace. To enable searching the namespace, use the ``-n`` option. .. sourcecode:: ipython @@ -84,6 +84,43 @@ Other new features Now, the same merge is applied to the stored output as the displayed output, improving document load time for notebooks with many small outputs. +* ``NotebookApp.webapp_settings`` is deprecated and replaced with + the more informatively named ``NotebookApp.tornado_settings``. + +* Using :magic:`timeit` prints warnings if there is atleast a 4x difference in timings + between the slowest and fastest runs, since this might meant that the multiple + runs are not independent of one another. + +* It's now possible to provide mechanisms to integrate IPython with other event + loops, in addition to the ones we already support. This lets you run GUI code + in IPython with an interactive prompt, and to embed the IPython + kernel in GUI applications. See :doc:`/config/eventloops` for details. As part + of this, the direct ``enable_*`` and ``disable_*`` functions for various GUIs + in :mod:`IPython.lib.inputhook` have been deprecated in favour of + :meth:`~.InputHookManager.enable_gui` and :meth:`~.InputHookManager.disable_gui`. + +* A ``ScrollManager`` was added to the notebook. The ``ScrollManager`` controls how the notebook document is scrolled using keyboard. Users can inherit from the ``ScrollManager`` or ``TargetScrollManager`` to customize how their notebook scrolls. The default ``ScrollManager`` is the ``SlideScrollManager``, which tries to scroll to the nearest slide or sub-slide cell. + +* The function :func:`~IPython.html.widgets.interaction.interact_manual` has been + added which behaves similarly to :func:`~IPython.html.widgets.interaction.interact`, + but adds a button to explicitly run the interacted-with function, rather than + doing it automatically for every change of the parameter widgets. This should + be useful for long-running functions. + +* The ``%cython`` magic is now part of the Cython module. Use `%load_ext Cython` with a version of Cython >= 0.21 to have access to the magic now. + +* The Notebook application now offers integrated terminals on Unix platforms, + intended for when it is used on a remote server. To enable these, install + the ``terminado`` Python package. + +* Setting the default highlighting language for nbconvert with the config option + ``NbConvertBase.default_language`` is deprecated. Nbconvert now respects + metadata stored in the :ref:`kernel spec `. + +* IPython can now be configured systemwide, with files in :file:`/etc/ipython` + or :file:`/usr/local/etc/ipython` on Unix systems, + or :file:`{%PROGRAMDATA%}\\ipython` on Windows. + .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT. @@ -143,16 +180,42 @@ Backwards incompatible changes .. DO NOT EDIT THIS LINE BEFORE RELEASE. INCOMPAT INSERTION POINT. -IFrame embedding -```````````````` +Content Security Policy +``````````````````````` + +The Content Security Policy is a web standard for adding a layer of security to +detect and mitigate certain classes of attacks, including Cross Site Scripting +(XSS) and data injection attacks. This was introduced into the notebook to +ensure that the IPython Notebook and its APIs (by default) can only be embedded +in an iframe on the same origin. + +Override ``headers['Content-Security-Policy']`` within your notebook +configuration to extend for alternate domains and security settings.:: + + c.NotebookApp.tornado_settings = { + 'headers': { + 'Content-Security-Policy': "frame-ancestors 'self'" + } + } + +Example policies:: + + Content-Security-Policy: default-src 'self' https://*.jupyter.org + +Matches embeddings on any subdomain of jupyter.org, so long as they are served +over SSL. -The IPython Notebook and its APIs by default will only be allowed to be -embedded in an iframe on the same origin. +There is a `report-uri `_ endpoint available for logging CSP violations, located at +``/api/security/csp-report``. To use it, set ``report-uri`` as part of the CSP:: -To override this, set ``headers[X-Frame-Options]`` to one of + c.NotebookApp.tornado_settings = { + 'headers': { + 'Content-Security-Policy': "frame-ancestors 'self'; report-uri /api/security/csp-report" + } + } -* DENY -* SAMEORIGIN -* ALLOW-FROM uri +It simply provides the CSP report as a warning in IPython's logs. The default +CSP sets this report-uri relative to the ``base_url`` (not shown above). -See `Mozilla's guide to X-Frame-Options `_ for more examples. +For a more thorough and accurate guide on Content Security Policies, check out +`MDN's Using Content Security Policy `_ for more examples. diff --git a/docs/source/whatsnew/pr/inputhook_extensible.rst b/docs/source/whatsnew/pr/inputhook_extensible.rst deleted file mode 100644 index 80cd40f..0000000 --- a/docs/source/whatsnew/pr/inputhook_extensible.rst +++ /dev/null @@ -1,7 +0,0 @@ -* It's now possible to provide mechanisms to integrate IPython with other event - loops, in addition to the ones we already support. This lets you run GUI code - in IPython with an interactive prompt, and to embed the IPython - kernel in GUI applications. See :doc:`/config/eventloops` for details. As part - of this, the direct ``enable_*`` and ``disable_*`` functions for various GUIs - in :mod:`IPython.lib.inputhook` have been deprecated in favour of - :meth:`~.InputHookManager.enable_gui` and :meth:`~.InputHookManager.disable_gui`. diff --git a/docs/source/whatsnew/pr/interact-manual.rst b/docs/source/whatsnew/pr/interact-manual.rst deleted file mode 100644 index 931005c..0000000 --- a/docs/source/whatsnew/pr/interact-manual.rst +++ /dev/null @@ -1,4 +0,0 @@ -- The function ``interact_manual`` has been added which behaves similarly to - ``interact``, but adds a button to explicitly run the interacted-with - function, rather than doing it automatically for every change of the - parameter widgets. This should be useful for long-running functions. diff --git a/docs/source/whatsnew/pr/paul-scroll.rst b/docs/source/whatsnew/pr/paul-scroll.rst deleted file mode 100644 index 34cb1d3..0000000 --- a/docs/source/whatsnew/pr/paul-scroll.rst +++ /dev/null @@ -1 +0,0 @@ -* A ``ScrollManager`` was added to the notebook. The ``ScrollManager`` controls how the notebook document is scrolled using keyboard. Users can inherit from the ``ScrollManager`` or ``TargetScrollManager`` to customize how their notebook scrolls. The default ``ScrollManager`` is the ``SlideScrollManager``, which tries to scroll to the nearest slide or sub-slide cell. diff --git a/docs/source/whatsnew/pr/tornado_settings.rst b/docs/source/whatsnew/pr/tornado_settings.rst deleted file mode 100644 index 769437e..0000000 --- a/docs/source/whatsnew/pr/tornado_settings.rst +++ /dev/null @@ -1,2 +0,0 @@ -- ``NotebookApp.webapp_settings`` is deprecated and replaced with - the more informatively named ``NotebookApp.tornado_settings``. \ No newline at end of file diff --git a/docs/source/whatsnew/pr/user-templates.rst b/docs/source/whatsnew/pr/user-templates.rst new file mode 100644 index 0000000..54a6927 --- /dev/null +++ b/docs/source/whatsnew/pr/user-templates.rst @@ -0,0 +1,29 @@ +* Added support for configurable user-supplied `Jinja + `_ HTML templates for the notebook. Paths to + directories containing template files can be specified via + ``NotebookApp.extra_template_paths``. User-supplied template directories + searched first by the notebook, making it possible to replace existing + templates with your own files. + + For example, to replace the notebook's built-in ``error.html`` with your own, + create a directory like ``/home/my_templates`` and put your override template + at ``/home/my_templates/error.html``. To start the notebook with your custom + error page enabled, you would run:: + + ipython notebook '--extra_template_paths=["/home/my_templates/"]' + + It's also possible to override a template while also `inheriting + `_ from that + template, by prepending ``templates/`` to the ``{% extends %}`` target of + your child template. This is useful when you only want to override a + specific block of a template. For example, to add additional CSS to the + built-in ``error.html``, you might create an override that looks like:: + + {% extends "templates/error.html" %} + + {% block stylesheet %} + {{super()}} + + {% endblock %} diff --git a/docs/source/whatsnew/pr/warning_timeit_cache.rst b/docs/source/whatsnew/pr/warning_timeit_cache.rst deleted file mode 100644 index 3f287e8..0000000 --- a/docs/source/whatsnew/pr/warning_timeit_cache.rst +++ /dev/null @@ -1,3 +0,0 @@ -Using %timeit prints warnings if there is atleast a 4x difference in timings -between the slowest and fastest runs, since this might meant that the multiple -runs are not independent of one another. \ No newline at end of file diff --git a/docs/sphinxext/magics.py b/docs/sphinxext/magics.py new file mode 100644 index 0000000..c8da2d3 --- /dev/null +++ b/docs/sphinxext/magics.py @@ -0,0 +1,42 @@ +import re +from sphinx import addnodes +from sphinx.domains.std import StandardDomain +from sphinx.roles import XRefRole + +name_re = re.compile(r"[\w_]+") + +def parse_magic(env, sig, signode): + m = name_re.match(sig) + if not m: + raise Exception("Invalid magic command: %s" % sig) + name = "%" + sig + signode += addnodes.desc_name(name, name) + return m.group(0) + +class LineMagicRole(XRefRole): + """Cross reference role displayed with a % prefix""" + prefix = "%" + + def process_link(self, env, refnode, has_explicit_title, title, target): + if not has_explicit_title: + title = self.prefix + title.lstrip("%") + target = target.lstrip("%") + return title, target + +def parse_cell_magic(env, sig, signode): + m = name_re.match(sig) + if not m: + raise ValueError("Invalid cell magic: %s" % sig) + name = "%%" + sig + signode += addnodes.desc_name(name, name) + return m.group(0) + +class CellMagicRole(LineMagicRole): + """Cross reference role displayed with a %% prefix""" + prefix = "%%" + +def setup(app): + app.add_object_type('magic', 'magic', 'pair: %s; magic command', parse_magic) + StandardDomain.roles['magic'] = LineMagicRole() + app.add_object_type('cellmagic', 'cellmagic', 'pair: %s; cell magic', parse_cell_magic) + StandardDomain.roles['cellmagic'] = CellMagicRole() diff --git a/examples/Builtin Extensions/Cython Magics.ipynb b/examples/Builtin Extensions/Cython Magics.ipynb deleted file mode 100644 index 58de011..0000000 --- a/examples/Builtin Extensions/Cython Magics.ipynb +++ /dev/null @@ -1,366 +0,0 @@ -{ - "metadata": { - "name": "Cython Magics", - "signature": "sha256:c357b93e9480d6347c6677862bf43750745cef4b30129c5bc53cb879a19d4074" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ - { - "cells": [ - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Cython Magic Functions" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Loading the extension" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "IPython has a `cythonmagic` extension that contains a number of magic functions for working with Cython code. This extension can be loaded using the `%load_ext` magic as follows:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%load_ext cythonmagic" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 1 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "The %cython_inline magic" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `%%cython_inline` magic uses `Cython.inline` to compile a Cython expression. This allows you to enter and run a function body with Cython code. Use a bare `return` statement to return values. " - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "a = 10\n", - "b = 20" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 2 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%cython_inline\n", - "return a+b" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 3, - "text": [ - "30" - ] - } - ], - "prompt_number": 3 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "The %cython_pyximport magic" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `%%cython_pyximport` magic allows you to enter arbitrary Cython code into a cell. That Cython code is written as a `.pyx` file in the current working directory and then imported using `pyximport`. You have to specify the name of the module that the Code will appear in. All symbols from the module are imported automatically by the magic function." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%cython_pyximport foo\n", - "def f(x):\n", - " return 4.0*x" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 4 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "f(10)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 5, - "text": [ - "40.0" - ] - } - ], - "prompt_number": 5 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "The %cython magic" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Probably the most important magic is the `%cython` magic. This is similar to the `%%cython_pyximport` magic, but doesn't require you to specify a module name. Instead, the `%%cython` magic manages everything using temporary files in the `~/.cython/magic` directory. All of the symbols in the Cython module are imported automatically by the magic.\n", - "\n", - "Here is a simple example of a Black-Scholes options pricing algorithm written in Cython. Please note that this example might not compile on non-POSIX systems (e.g., Windows) because of a missing `erf` symbol." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%cython\n", - "cimport cython\n", - "from libc.math cimport exp, sqrt, pow, log, erf\n", - "\n", - "@cython.cdivision(True)\n", - "cdef double std_norm_cdf_cy(double x) nogil:\n", - " return 0.5*(1+erf(x/sqrt(2.0)))\n", - "\n", - "@cython.cdivision(True)\n", - "def black_scholes_cy(double s, double k, double t, double v,\n", - " double rf, double div, double cp):\n", - " \"\"\"Price an option using the Black-Scholes model.\n", - " \n", - " s : initial stock price\n", - " k : strike price\n", - " t : expiration time\n", - " v : volatility\n", - " rf : risk-free rate\n", - " div : dividend\n", - " cp : +1/-1 for call/put\n", - " \"\"\"\n", - " cdef double d1, d2, optprice\n", - " with nogil:\n", - " d1 = (log(s/k)+(rf-div+0.5*pow(v,2))*t)/(v*sqrt(t))\n", - " d2 = d1 - v*sqrt(t)\n", - " optprice = cp*s*exp(-div*t)*std_norm_cdf_cy(cp*d1) - \\\n", - " cp*k*exp(-rf*t)*std_norm_cdf_cy(cp*d2)\n", - " return optprice" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 6 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "black_scholes_cy(100.0, 100.0, 1.0, 0.3, 0.03, 0.0, -1)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 7, - "text": [ - "10.327861752731728" - ] - } - ], - "prompt_number": 7 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For comparison, the same code is implemented here in pure python." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from math import exp, sqrt, pow, log, erf\n", - "\n", - "def std_norm_cdf_py(x):\n", - " return 0.5*(1+erf(x/sqrt(2.0)))\n", - "\n", - "def black_scholes_py(s, k, t, v, rf, div, cp):\n", - " \"\"\"Price an option using the Black-Scholes model.\n", - " \n", - " s : initial stock price\n", - " k : strike price\n", - " t : expiration time\n", - " v : volatility\n", - " rf : risk-free rate\n", - " div : dividend\n", - " cp : +1/-1 for call/put\n", - " \"\"\"\n", - " d1 = (log(s/k)+(rf-div+0.5*pow(v,2))*t)/(v*sqrt(t))\n", - " d2 = d1 - v*sqrt(t)\n", - " optprice = cp*s*exp(-div*t)*std_norm_cdf_py(cp*d1) - \\\n", - " cp*k*exp(-rf*t)*std_norm_cdf_py(cp*d2)\n", - " return optprice" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 8 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "black_scholes_py(100.0, 100.0, 1.0, 0.3, 0.03, 0.0, -1)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 9, - "text": [ - "10.327861752731728" - ] - } - ], - "prompt_number": 9 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Below we see the runtime of the two functions: the Cython version is nearly a factor of 10 faster." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%timeit black_scholes_cy(100.0, 100.0, 1.0, 0.3, 0.03, 0.0, -1)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "1000000 loops, best of 3: 319 ns per loop\n" - ] - } - ], - "prompt_number": 10 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%timeit black_scholes_py(100.0, 100.0, 1.0, 0.3, 0.03, 0.0, -1)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "100000 loops, best of 3: 2.28 \u00b5s per loop\n" - ] - } - ], - "prompt_number": 11 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "External libraries" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Cython allows you to specify additional libraries to be linked with your extension, you can do so with the `-l` flag (also spelled `--lib`). Note that this flag can be passed more than once to specify multiple libraries, such as `-lm -llib2 --lib lib3`. Here's a simple example of how to access the system math library:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%cython -lm\n", - "from libc.math cimport sin\n", - "print 'sin(1)=', sin(1)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "sin(1)= 0.841470984808\n" - ] - } - ], - "prompt_number": 12 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can similarly use the `-I/--include` flag to add include directories to the search path, and `-c/--compile-args` to add extra flags that are passed to Cython via the `extra_compile_args` of the distutils `Extension` class. Please see [the Cython docs on C library usage](http://docs.cython.org/src/tutorial/clibraries.html) for more details on the use of these flags." - ] - } - ], - "metadata": {} - } - ] -} diff --git a/examples/Builtin Extensions/Index.ipynb b/examples/Builtin Extensions/Index.ipynb deleted file mode 100644 index 606b801..0000000 --- a/examples/Builtin Extensions/Index.ipynb +++ /dev/null @@ -1,65 +0,0 @@ -{ - "metadata": { - "name": "", - "signature": "sha256:a89ddd606f68e27067a5e2301a4d19a3c149131f4f07be4483a65979e26bebe0" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ - { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Back to the main [Index](../Index.ipynb)" - ] - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Builtin Extensions" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "IPython extensions allow custom magic commands to be shipped as standalone libraries. IPython includes a few extensions that define magic commands for working with code in other languages.\n", - "\n", - "
\n", - "We are in the process of moving these builtin extensions to their parent projects (Cython, oct2py and rpy2). Once this happens this documentation will move as well.\n", - "
" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Tutorials" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "* [Cython Magics](Cython Magics.ipynb): magics for compiling and running Cython code in a cell\n", - "* [Octave Magic](Octave Magic.ipynb): magics for running Octave code in a cell\n", - "* [R Magics](R Magics.ipynb): magics for running R code in a cell" - ] - } - ], - "metadata": {} - } - ] -} \ No newline at end of file diff --git a/examples/Builtin Extensions/Octave Magic.ipynb b/examples/Builtin Extensions/Octave Magic.ipynb deleted file mode 100644 index ff642e5..0000000 --- a/examples/Builtin Extensions/Octave Magic.ipynb +++ /dev/null @@ -1,371 +0,0 @@ -{ - "metadata": { - "name": "Octave Magic" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ - { - "cells": [ - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Using Octave Inside IPython" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Installation" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `octavemagic` extension provides the ability to interact with Octave. It depends on the `oct2py` package,\n", - "which may be installed using `easy_install`.\n", - "\n", - "To enable the extension, load it as follows:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%load_ext octavemagic" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 18 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Overview" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Loading the extension enables three magic functions: `%octave`, `%octave_push`, and `%octave_pull`.\n", - "\n", - "The first is for executing one or more lines of Octave, while the latter allow moving variables between the Octave and Python workspace.\n", - "Here you see an example of how to execute a single line of Octave, and how to transfer the generated value back to Python:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x = %octave [1 2; 3 4];\n", - "x" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "pyout", - "prompt_number": 19, - "text": [ - "array([[ 1., 2.],\n", - " [ 3., 4.]])" - ] - } - ], - "prompt_number": 19 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "a = [1, 2, 3]\n", - "\n", - "%octave_push a\n", - "%octave a = a * 2;\n", - "%octave_pull a\n", - "a" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "pyout", - "prompt_number": 20, - "text": [ - "array([[2, 4, 6]])" - ] - } - ], - "prompt_number": 20 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When using the cell magic, `%%octave` (note the double `%`), multiple lines of Octave can be executed together. Unlike\n", - "with the single cell magic, no value is returned, so we use the `-i` and `-o` flags to specify input and output variables." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%octave -i x -o y\n", - "y = x + 3;" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 21 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "y" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "pyout", - "prompt_number": 22, - "text": [ - "array([[ 4., 5.],\n", - " [ 6., 7.]])" - ] - } - ], - "prompt_number": 22 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Plotting" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Plot output is automatically captured and displayed, and using the `-f` flag you may choose its format (currently, `png` and `svg` are supported)." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%octave -f svg\n", - "\n", - "p = [12 -2.5 -8 -0.1 8];\n", - "x = 0:0.01:1;\n", - "\n", - "polyout(p, 'x')\n", - "plot(x, polyval(p, x));" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "display_data", - "text": [ - "12*x^4 - 2.5*x^3 - 8*x^2 - 0.1*x^1 + 8" - ] - }, - { - "output_type": "display_data", - "svg": [ - "\n", - "\n", - "Produced by GNUPLOT 4.4 patchlevel 0 \n", - "\n", - "\n", - "\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\t\n", - "\t\n", - "\t\t6\n", - "\t\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\t6.5\n", - "\t\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\t7\n", - "\t\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\t7.5\n", - "\t\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\t8\n", - "\t\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\t8.5\n", - "\t\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\t9\n", - "\t\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\t9.5\n", - "\t\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\t0\n", - "\t\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\t0.2\n", - "\t\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\t0.4\n", - "\t\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\t0.6\n", - "\t\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\t0.8\n", - "\t\t\n", - "\t\n", - "\t\n", - "\t\n", - "\t\t1\n", - "\t\t\n", - "\t\n", - "\t\n", - "\n", - "\t\n", - "\n", - "\t\n", - "\n", - "\t\n", - "\n", - "\n", - "" - ] - } - ], - "prompt_number": 23 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The plot size is adjusted using the `-s` flag:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%octave -s 500,500\n", - "\n", - "# butterworth filter, order 2, cutoff pi/2 radians\n", - "b = [0.292893218813452 0.585786437626905 0.292893218813452];\n", - "a = [1 0 0.171572875253810];\n", - "freqz(b, a, 32);" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "display_data", - "png": "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\n" - } - ], - "prompt_number": 24 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%octave -s 600,200 -f png\n", - "\n", - "subplot(121);\n", - "[x, y] = meshgrid(0:0.1:3);\n", - "r = sin(x - 0.5).^2 + cos(y - 0.5).^2;\n", - "surf(x, y, r);\n", - "\n", - "subplot(122);\n", - "sombrero()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "display_data", - "png": "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\nUzfATHmCZdmtW7f+/fffe/fulUgk9+/fd3Z27tev39y5c03dNDNmTMnu3bt//fXXc+fOPXz4MC8v\nr2LFitWrV583b55UKjV108z8O2aL0My/o9PpBg4c2LJly8DAwJ9++qlNmzYJCQnx8fFNmjSJi4vT\n6XReXl4RERHmUbCZ/xR6vf7LL78MCAho3LjxypUrK1eurNFobt++XadOnbi4OA8Pj7Zt24aGhur1\nelO31My/YBZCM2+HZVm1Wt20aVMvLy+VSlWhQoX//e9/cXFxR48e7dmzJ38YwzAHDhy4cOHCs2fP\nvL29zXJo5qMnNja2U6dOFStWDA0Nff78+axZs+Li4jQazfTp0xmG4Q9bsmRJUlKSTCZr0aJF//79\ndTqdCdts5v0Q88a8Zgqi1Wq1Wu2mTZtcXV07d+7cvHnz7t27v+vgwMBAjUZTsEStVs+ZM8fX1zc6\nOto8KWTmo0Gn02m12nXr1qWmpn7xxRe+vr6DBw9+18Fv9gutVjtr1qy7d+9+//33crm8xJtrppCY\nhdAM9Ho9p38nT55s27Ztr1695HJ5wbHtu3izw3PMnz9/3bp1/v7+U6dO9fPzK4EmmzFT4rAsy/WL\nX3/9tWLFioMHD5bL5R8yvHtXv9i8efOmTZsyMjLCwsIGDhxYAk02U0TMQvjfZeXKlbzbS/fu3UNC\nQmQyWaFqeFeH5+tfuXJl3bp1FQqFeRRsprxw7Nix7du3c24vLVu2HDZsWGGf3vf3iwsXLkyZMkWv\n1yuVSoVCUez2mjECZiH8b6HT6ZYuXZqYmHj//v0qVaoMGjToPTM8/8r7OzyHVqsdNWqUlZXV4sWL\nzXJopmyi1+vXr19/6tSp27dve3p6duzYceTIkR8yKfJWPqRf6PX6cePGnT59ev78+WY5NDnm8ImP\nH5ZlY2Nj169fn56e7uvr6+npOXv27FJbwJPL5Tdu3NDpdGq1esiQIZMmTZowYULp/LQZM+8nNjZ2\n69at165dq1KlSq1atWbNmhUQEFA6Py2VSvft28eyrEqlcnFx6dat25IlS4osvWaKidki/GjR6XSx\nsbG820vdunWNvizxISPfgly4cGH58uUnTpwwhx6aMRWc28vWrVuTk5O/+OKLypUr9+/f37gKVNh+\nwbLs9OnTDx069Mknn6xatcosh6WP2SL8qHjN7SUgIODatWtlp1/5+flt2LBBr9ePHj26fv36AwYM\n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\n" - } - ], - "prompt_number": 25 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Future work" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "After the next release of `oct2py`, we'll add the ability to interrupt/kill the current Octave session without restarting the Python kernel." - ] - } - ], - "metadata": {} - } - ] -} \ No newline at end of file diff --git a/examples/Builtin Extensions/R Magics.ipynb b/examples/Builtin Extensions/R Magics.ipynb deleted file mode 100644 index 2fd1a4a..0000000 --- a/examples/Builtin Extensions/R Magics.ipynb +++ /dev/null @@ -1,930 +0,0 @@ -{ - "metadata": { - "name": "" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ - { - "cells": [ - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Using R Within the IPython Notebok" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Using the `rmagic` extension, users can run R code from within the IPython Notebook. This example Notebook demonstrates this capability. " - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%matplotlib inline" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 1 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Line magics" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "IPython has an `rmagic` extension that contains a some magic functions for working with R via rpy2. This extension can be loaded using the `%load_ext` magic as follows:" - ] - }, - { - "cell_type": "code", - "collapsed": true, - "input": [ - "%load_ext rmagic " - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 2 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A typical use case one imagines is having some numpy arrays, wanting to compute some statistics of interest on these\n", - " arrays and return the result back to python. Let's suppose we just want to fit a simple linear model to a scatterplot." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "X = np.array([0,1,2,3,4])\n", - "Y = np.array([3,5,4,6,7])\n", - "plt.scatter(X, Y)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 3, - "text": [ - "" - ] - }, - { - "metadata": {}, - "output_type": "display_data", - "png": "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\nH7s1AAAAAElFTkSuQmCC\n", - "text": [ - "" - ] - } - ], - "prompt_number": 3 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can accomplish this by first pushing variables to R, fitting a model and returning the results. The line magic %Rpush copies its arguments to variables of the same name in rpy2. The %R line magic evaluates the string in rpy2 and returns the results. In this case, the coefficients of a linear model." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%Rpush X Y\n", - "%R lm(Y~X)$coef" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "pyout", - "prompt_number": 3, - "text": [ - "array([ 3.2, 0.9])" - ] - } - ], - "prompt_number": 3 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can check that this is correct fairly easily:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "Xr = X - X.mean(); Yr = Y - Y.mean()\n", - "slope = (Xr*Yr).sum() / (Xr**2).sum()\n", - "intercept = Y.mean() - X.mean() * slope\n", - "(intercept, slope)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "pyout", - "prompt_number": 4, - "text": [ - "(3.2000000000000002, 0.90000000000000002)" - ] - } - ], - "prompt_number": 4 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It is also possible to return more than one value with %R." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%R resid(lm(Y~X)); coef(lm(X~Y))\n" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "pyout", - "prompt_number": 5, - "text": [ - "array([-2.5, 0.9])" - ] - } - ], - "prompt_number": 5 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "One can also easily capture the results of %R into python objects. Like R, the return value of this multiline expression (multiline in the sense that it is separated by ';') is the final value, which is \n", - "the *coef(lm(X~Y))*. To pull other variables from R, there is one more magic." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There are two more line magics, %Rpull and %Rget. Both are useful after some R code has been executed and there are variables\n", - "in the rpy2 namespace that one would like to retrieve. The main difference is that one\n", - " returns the value (%Rget), while the other pulls it to self.shell.user_ns (%Rpull). Imagine we've stored the results\n", - "of some calculation in the variable \"a\" in rpy2's namespace. By using the %R magic, we can obtain these results and\n", - "store them in b. We can also pull them directly to user_ns with %Rpull. They are both views on the same data." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "b = %R a=resid(lm(Y~X))\n", - "%Rpull a\n", - "print(a)\n", - "assert id(b.data) == id(a.data)\n", - "%R -o a" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "[-0.2 0.9 -1. 0.1 0.2]\n" - ] - } - ], - "prompt_number": 6 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "%Rpull is equivalent to calling %R with just -o\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%R d=resid(lm(Y~X)); e=coef(lm(Y~X))\n", - "%R -o d -o e\n", - "%Rpull e\n", - "print(d)\n", - "print(e)\n", - "import numpy as np\n", - "np.testing.assert_almost_equal(d, a)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "[-0.2 0.9 -1. 0.1 0.2]\n", - "[ 3.2 0.9]\n" - ] - } - ], - "prompt_number": 7 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "On the other hand %Rpush is equivalent to calling %R with just -i and no trailing code." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "A = np.arange(20)\n", - "%R -i A\n", - "%R mean(A)\n" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "pyout", - "prompt_number": 8, - "text": [ - "array([ 9.5])" - ] - } - ], - "prompt_number": 8 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The magic %Rget retrieves one variable from R." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%Rget A" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "pyout", - "prompt_number": 9, - "text": [ - "array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,\n", - " 17, 18, 19], dtype=int32)" - ] - } - ], - "prompt_number": 9 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Plotting and capturing output" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "R's console (i.e. its stdout() connection) is captured by ipython, as are any plots which are published as PNG files, as with `%matplotlib inline`. As a call to %R may produce a return value (see above) we must ask what happens to a magic like the one below. The R code specifies that something is published to the notebook. If anything is published to the notebook, that call to %R returns None." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from __future__ import print_function\n", - "v1 = %R plot(X,Y); print(summary(lm(Y~X))); vv=mean(X)*mean(Y)\n", - "print('v1 is:', v1)\n", - "v2 = %R mean(X)*mean(Y)\n", - "print('v2 is:', v2)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "display_data", - "text": [ - "\n", - "Call:\n", - "lm(formula = Y ~ X)\n", - "\n", - "Residuals:\n", - " 1 2 3 4 5 \n", - "-0.2 0.9 -1.0 0.1 0.2 \n", - "\n", - "Coefficients:\n", - " Estimate Std. Error t value Pr(>|t|) \n", - "(Intercept) 3.2000 0.6164 5.191 0.0139 *\n", - "X 0.9000 0.2517 3.576 0.0374 *\n", - "---\n", - "Signif. codes: 0 \u2018***\u2019 0.001 \u2018**\u2019 0.01 \u2018*\u2019 0.05 \u2018.\u2019 0.1 \u2018 \u2019 1 \n", - "\n", - "Residual standard error: 0.7958 on 3 degrees of freedom\n", - "Multiple R-squared: 0.81,\tAdjusted R-squared: 0.7467 \n", - "F-statistic: 12.79 on 1 and 3 DF, p-value: 0.03739 \n", - "\n" - ] - }, - { - "output_type": "display_data", - "png": "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\nUpK06OAUzDPgiIjOnTvHggULYtWqVTFjxoz41re+FevWrYshQ4bEQw899B9/vmvXrtG1a9f9nnv5\n5Zdj/fr1h3oyAEkGDBgQEyZMiEceeSSaNGkS8+bNi5tvvvmgnrhlKqgA/9spp5wSU6dOjalTp8aO\nHTti06ZN2ZMAKDBlZWWxYcOGOOOMM6Jr167RvHnzeO+996JFixbZ02qlIAI8YcKE6Nix437PNWvW\nLJo1a1bHiwD4NBg5cmSMHDkye8Z/pSACPHDgwOwJAFCnCuZNWABQnwgwACQQYABIIMAAkECAASCB\nAANAAgEGgAQCDAAJBBgAEggwACQQYABIIMAAkECAASCBAANAAgEGgAQCDAAJBBgAEggwACQQYABI\nIMAAkECAASCBAANAAgEGgAQCDAAJBBgAEggwACQQYABIIMAAkECAASCBAANAAgEGgAQCDAAJBBgA\nEggwACQQYABIIMAAkECAASCBAANAAgEGgAQCDAAJBBgAEggwACQQYABIIMAAkECAASCBAANAAgEG\ngAQCDAAJBBgAEggwACQQYABIIMAAkECAASCBAANAAgEGgAQCDAAJBBgAEggwACQQYABIIMAAkECA\nASCBAANAAgEGgAQCDAAJBBgAEggwACQQYABIIMAAkECAASCBAANAAgEGgAQCDAAJGmUPOJCdO3dG\nw4YNo6SkJHsKwEGprq6OGTNmxC9/+cs44ogj4rrrroszzzwzexYFpiCeAa9bty4GDx4cy5Ytiw0b\nNsTQoUOjXbt2cdRRR8WQIUNi9+7d2RMBam3w4MGxYMGCmDx5cowZMya+//3vx4svvpg9iwJTEAG+\n9dZb4/jjj4/Pf/7zcd9990VVVVWsXLky3nzzzdi+fXtMmjSpVteprq6Oqqqq/X5VV1dHTU3NYf6X\nAPXd66+/Hu+++248+eST0alTp+jevXvMmDEj7rvvvuxpFJiCuAX92muvxapVq+KII46IZ555JubN\nmxelpaURETFp0qQYMWJEra4zc+bMmDNnzn7PrV69Ok488cRDNRlgvyorK6N3794fO9ahQ4eorq5O\nWkShKogAd+3aNWbNmhXXXHNN9OzZM+bPnx+jR4+OiIgXXnghunTpUqvrDB06NIYOHbrfc2PHjo31\n69cfss0A+9O1a9e49957Y9OmTXHsscdGRMSiRYviww8/TF5GoSmIAE+bNi2+/vWvx8MPPxydO3eO\nG2+8MR555JH4zGc+E9u2bYvXXnsteyJArZxwwgkxdOjQaN26dcyePTu2bt0azz77bDz55JPZ0ygw\nBRHgk08+Od56661YsGBBrF69Oo4//vg4+uijo0uXLtG3b99o1KggZgLUSv/+/WP58uWxaNGiaNKk\nSZSXl+97Ngz/VjBla9CgQVx00UVx0UUXZU8B+J917949unfvnj2DAlYQ74IGgPpGgAEggQADQAIB\nBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBA\ngAEggQADQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEAgwACQQYABIIMAAk\nEGAASCDAAJBAgAEggQADQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEAgwA\nCQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQAD\nQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEAgwACQQYABIIMAAkEGAASPCJ\nAN90002xffv2jC3UY5WVlfHyyy/HwoUL46OPPsqeA3DYfSLA69ati9NOOy0WLVqUsWefDRs2RFVV\nVeoG6sbatWvj0ksvjddffz0qKiqiZcuWsX79+uxZAIfVJwL8+OOPxx133BFlZWUxYcKE2L1792Ef\nMXjw4Fi1alVERKxevTr69u0bHTt2jHbt2sWoUaNiz549h30DOT766KM47bTT4rvf/W5873vfiylT\npsTMmTNjwoQJsXfv3ux5AIdNo/0dHDhwYHz1q1+NG2+8Mc4666y4/PLL95079dRT49JLLz2kI1au\nXBk7duyIiIgpU6bEKaecErNnz46NGzfGuHHjYsqUKXHrrbf+x+s899xzsWDBgv2eW7RoURx77LGH\ndDf/uz//+c9x1VVXRe/evfcdGzRoUDz33HPx/vvvR8eOHRPXARw++w1wRESDBg2ipKQk1q9fHytX\nrtx3vHnz5od10EsvvRRr1qyJFi1axDHHHBOTJ0+OcePG1SrAPXr0iJNOOmm/5yorK/dFnsLRuHHj\nqKys/Nix6urqWLt2bTRp0iRpFcDht98Al5eXx/XXXx9f/vKXY8WKFdGmTZvDPmTJkiXRvn37OOec\nc2LTpk3RokWLiIhYsWJFnH766bW6Rtu2baNt27b7Pde6dWuvKRegLl26xMknnxy33357TJgwIaqq\nquIrX/lK9OzZM1q3bp09D+Cw+USAr7jiiqioqIj7778/vvnNb9bJiKuuuiqef/75mDRpUmzdujUa\nN24c5eXlcdttt8W0adOioqKiTnaQY9KkSXH99dfHxRdfHE2bNo0hQ4bE8OHDs2cBHFafCHCrVq3i\nj3/84wGfSR4O48ePj/Hjx0dExN///vfYtm1bRET06dMnbrzxxsN+25tcDRs2jGnTpmXPAKhTnwjw\ngw8+mLFjnw4dOkSHDh0iIuKcc85J3QIAh4vfhAUACQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIB\nBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBA\ngAEggQADQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEAgwACQQYABIIMAAk\nEGAASCDAAJBAgAEggQADQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEAgwA\nCQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQAD\nQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEBRvgnTt3xrZt27JnQFFZtmxZ\nDBw4MPr06RNlZWWxadOm7ElQbxVsgOfOnRvjxo3LngFF469//WvccMMNMX78+Hj22Wdj6NChcfnl\nl8fmzZuzp0G91Ch7QEREly5dYuPGjR87tnv37qiqqoq5c+dG//79Y+bMmf/xOlu2bInKysr9ntu6\ndWvs2bPnkOyFT6O77ror7rjjjjjzzDMjIuJrX/tavPPOO/HYY4/F6NGjk9dB/VMQAZ45c2YMGTIk\nBg0aFFdffXVERMybNy+WLl0aP/jBD6JZs2a1uk5FRUXMnz9/v+d+85vfRNu2bQ/ZZvi0+fDDD6Nd\nu3YfO1ZaWhqrV69OWgT1W0EE+Pzzz49ly5bFqFGjYty4cfHAAw9E69ato3nz5nHCCSfU+jplZWVR\nVla233Njx46N9evXH6rJ8KnTo0eP+OEPfxgzZsyIiH/dZfrOd74Tc+fOTV4G9VNBBDgiomXLljFr\n1qx44okn4oILLogePXpEw4YNs2dB0Rg2bFjMnz8/evfuHf37948FCxbExIkTo1evXtnToF4qmAD/\n2+WXXx7nnXdejBw5Mrp37549B4pGw4YN47nnnouKiorYvHlzTJw4Mc4444zsWVBvFVyAI/71utTz\nzz+fPQOK0oUXXpg9AYgC/hgSABQzAQaABAIMAAkEGAASCDAAJBBgAEggwACQQIABIIEAA0ACAQaA\nBAIMAAkEGAASCDAAJBBgAEggwACQQIABIIEAA0ACAQaABAIMAAkEGAASCDAAJBBgAEggwACQQIAB\nIIEAA0ACAQaABAIMAAkEGAASCDAAJBBgAEggwACQQIABIIEAA0ACAQaABAIMAAkEGAASCDAAJBBg\nAEggwACQQIABIIEAA0ACAQaABAIMAAkEGAASCDAAJBBgAEggwACQQIABIIEAA0ACAQaABAIMAAkE\nGAASCDAAJBBgAEggwACQQIABIIEAA0ACAQaABAIMAAkEGAASCDAAJBBgAEggwACQQIABIIEAA0AC\nAQaABAIMAAkEGAASCDAAJBBgAEggwACQQIABIEGj7AGF7qWXXor3338/jjvuuOjTp0/2HACKRME+\nA967d29s27YtdcPVV18dTz31VJSUlMRdd90Vl112WVRXV6duAqA4FESA9+zZE1OmTIkhQ4bEG2+8\nEXPmzIm2bdvGUUcdFZdeemns2rWrzjeVl5fHW2+9FQ899FAMGjQofvGLX0TLli3jpz/9aZ1vAaD4\nFMQt6JtuuinefvvtOOOMM+KKK66IRo0axdy5c6O0tDTGjh0b8+bNiyuuuOI/Xmf27Nnx9NNP7/fc\nm2++GaWlpbXetHz58rjnnns+dmz48OHx+OOP1/oaAHAgBRHg+fPnx7Jly6Jly5bRpEmT+OCDD+LL\nX/5yRERMnjw5Jk6cWKsAX3bZZXHJJZfs99yTTz4ZO3bsqPWmli1bxurVq+P888/fd+z3v/99tGzZ\nstbXAIADKYgAn3TSSbFq1ao4++yz45prrom//e1v+86tWLEiOnfuXKvrNG7cOBo3brzfcy1btoy9\ne/fWetOwYcOirKws2rVrF2effXYsWrQoRowYEZWVlbW+BgAcSEEEeNy4cdGvX7/48Y9/HP369Yv2\n7dtHRMQtt9wSjzzySCxcuLDON7Vp0ybmzZsXN910U8yaNSvatGkT69ati1atWtX5FgCKT0EE+KKL\n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\nrkJggg==\n" - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "v1 is: [ 10.]\n", - "v2 is: [ 10.]\n" - ] - } - ], - "prompt_number": 10 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "What value is returned from %R?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Some calls have no particularly interesting return value, the magic %R will not return anything in this case. The return value in rpy2 is actually NULL so %R returns None." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "v = %R plot(X,Y)\n", - "assert v == None" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "display_data", - "png": "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\nUpK06OAUzDPgiIjOnTvHggULYtWqVTFjxoz41re+FevWrYshQ4bEQw899B9/vmvXrtG1a9f9nnv5\n5Zdj/fr1h3oyAEkGDBgQEyZMiEceeSSaNGkS8+bNi5tvvvmgnrhlKqgA/9spp5wSU6dOjalTp8aO\nHTti06ZN2ZMAKDBlZWWxYcOGOOOMM6Jr167RvHnzeO+996JFixbZ02qlIAI8YcKE6Nix437PNWvW\nLJo1a1bHiwD4NBg5cmSMHDkye8Z/pSACPHDgwOwJAFCnCuZNWABQnwgwACQQYABIIMAAkECAASCB\nAANAAgEGgAQCDAAJBBgAEggwACQQYABIIMAAkECAASCBAANAAgEGgAQCDAAJBBgAEggwACQQYABI\nIMAAkECAASCBAANAAgEGgAQCDAAJBBgAEggwACQQYABIIMAAkECAASCBAANAAgEGgAQCDAAJBBgA\nEggwACQQYABIIMAAkECAASCBAANAAgEGgAQCDAAJBBgAEggwACQQYABIIMAAkECAASCBAANAAgEG\ngAQCDAAJBBgAEggwACQQYABIIMAAkECAASCBAANAAgEGgAQCDAAJBBgAEggwACQQYABIIMAAkECA\nASCBAANAAgEGgAQCDAAJBBgAEggwACQQYABIIMAAkECAASCBAANAAgEGgAQCDAAJGmUPOJCdO3dG\nw4YNo6SkJHsKwEGprq6OGTNmxC9/+cs44ogj4rrrroszzzwzexYFpiCeAa9bty4GDx4cy5Ytiw0b\nNsTQoUOjXbt2cdRRR8WQIUNi9+7d2RMBam3w4MGxYMGCmDx5cowZMya+//3vx4svvpg9iwJTEAG+\n9dZb4/jjj4/Pf/7zcd9990VVVVWsXLky3nzzzdi+fXtMmjSpVteprq6Oqqqq/X5VV1dHTU3NYf6X\nAPXd66+/Hu+++248+eST0alTp+jevXvMmDEj7rvvvuxpFJiCuAX92muvxapVq+KII46IZ555JubN\nmxelpaURETFp0qQYMWJEra4zc+bMmDNnzn7PrV69Ok488cRDNRlgvyorK6N3794fO9ahQ4eorq5O\nWkShKogAd+3aNWbNmhXXXHNN9OzZM+bPnx+jR4+OiIgXXnghunTpUqvrDB06NIYOHbrfc2PHjo31\n69cfss0A+9O1a9e49957Y9OmTXHsscdGRMSiRYviww8/TF5GoSmIAE+bNi2+/vWvx8MPPxydO3eO\nG2+8MR555JH4zGc+E9u2bYvXXnsteyJArZxwwgkxdOjQaN26dcyePTu2bt0azz77bDz55JPZ0ygw\nBRHgk08+Od56661YsGBBrF69Oo4//vg4+uijo0uXLtG3b99o1KggZgLUSv/+/WP58uWxaNGiaNKk\nSZSXl+97Ngz/VjBla9CgQVx00UVx0UUXZU8B+J917949unfvnj2DAlYQ74IGgPpGgAEggQADQAIB\nBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBA\ngAEggQADQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEAgwACQQYABIIMAAk\nEGAASCDAAJBAgAEggQADQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEAgwA\nCQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQAD\nQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEAgwACQQYABIIMAAkEGAASPCJ\nAN90002xffv2jC3UY5WVlfHyyy/HwoUL46OPPsqeA3DYfSLA69ati9NOOy0WLVqUsWefDRs2RFVV\nVeoG6sbatWvj0ksvjddffz0qKiqiZcuWsX79+uxZAIfVJwL8+OOPxx133BFlZWUxYcKE2L1792Ef\nMXjw4Fi1alVERKxevTr69u0bHTt2jHbt2sWoUaNiz549h30DOT766KM47bTT4rvf/W5873vfiylT\npsTMmTNjwoQJsXfv3ux5AIdNo/0dHDhwYHz1q1+NG2+8Mc4666y4/PLL95079dRT49JLLz2kI1au\nXBk7duyIiIgpU6bEKaecErNnz46NGzfGuHHjYsqUKXHrrbf+x+s899xzsWDBgv2eW7RoURx77LGH\ndDf/uz//+c9x1VVXRe/evfcdGzRoUDz33HPx/vvvR8eOHRPXARw++w1wRESDBg2ipKQk1q9fHytX\nrtx3vHnz5od10EsvvRRr1qyJFi1axDHHHBOTJ0+OcePG1SrAPXr0iJNOOmm/5yorK/dFnsLRuHHj\nqKys/Nix6urqWLt2bTRp0iRpFcDht98Al5eXx/XXXx9f/vKXY8WKFdGmTZvDPmTJkiXRvn37OOec\nc2LTpk3RokWLiIhYsWJFnH766bW6Rtu2baNt27b7Pde6dWuvKRegLl26xMknnxy33357TJgwIaqq\nquIrX/lK9OzZM1q3bp09D+Cw+USAr7jiiqioqIj7778/vvnNb9bJiKuuuiqef/75mDRpUmzdujUa\nN24c5eXlcdttt8W0adOioqKiTnaQY9KkSXH99dfHxRdfHE2bNo0hQ4bE8OHDs2cBHFafCHCrVq3i\nj3/84wGfSR4O48ePj/Hjx0dExN///vfYtm1bRET06dMnbrzxxsN+25tcDRs2jGnTpmXPAKhTnwjw\ngw8+mLFjnw4dOkSHDh0iIuKcc85J3QIAh4vfhAUACQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIB\nBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBA\ngAEggQADQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEAgwACQQYABIIMAAk\nEGAASCDAAJBAgAEggQADQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEAgwA\nCQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQAD\nQAIBBoAEAgwACQQYABIIMAAkEGAASCDAAJBAgAEggQADQAIBBoAEBRvgnTt3xrZt27JnQFFZtmxZ\nDBw4MPr06RNlZWWxadOm7ElQbxVsgOfOnRvjxo3LngFF469//WvccMMNMX78+Hj22Wdj6NChcfnl\nl8fmzZuzp0G91Ch7QEREly5dYuPGjR87tnv37qiqqoq5c+dG//79Y+bMmf/xOlu2bInKysr9ntu6\ndWvs2bPnkOyFT6O77ror7rjjjjjzzDMjIuJrX/tavPPOO/HYY4/F6NGjk9dB/VMQAZ45c2YMGTIk\nBg0aFFdffXVERMybNy+WLl0aP/jBD6JZs2a1uk5FRUXMnz9/v+d+85vfRNu2bQ/ZZvi0+fDDD6Nd\nu3YfO1ZaWhqrV69OWgT1W0EE+Pzzz49ly5bFqFGjYty4cfHAAw9E69ato3nz5nHCCSfU+jplZWVR\nVla233Njx46N9evXH6rJ8KnTo0eP+OEPfxgzZsyIiH/dZfrOd74Tc+fOTV4G9VNBBDgiomXLljFr\n1qx44okn4oILLogePXpEw4YNs2dB0Rg2bFjMnz8/evfuHf37948FCxbExIkTo1evXtnToF4qmAD/\n2+WXXx7nnXdejBw5Mrp37549B4pGw4YN47nnnouKiorYvHlzTJw4Mc4444zsWVBvFVyAI/71utTz\nzz+fPQOK0oUXXpg9AYgC/hgSABQzAQaABAIMAAkEGAASCDAAJBBgAEggwACQQIABIIEAA0ACAQaA\nBAIMAAkEGAASCDAAJBBgAEggwACQQIABIIEAA0ACAQaABAIMAAkEGAASCDAAJBBgAEggwACQQIAB\nIIEAA0ACAQaABAIMAAkEGAASCDAAJBBgAEggwACQQIABIIEAA0ACAQaABAIMAAkEGAASCDAAJBBg\nAEggwACQQIABIIEAA0ACAQaABAIMAAkEGAASCDAAJBBgAEggwACQQIABIIEAA0ACAQaABAIMAAkE\nGAASCDAAJBBgAEggwACQQIABIIEAA0ACAQaABAIMAAkEGAASCDAAJBBgAEggwACQQIABIIEAA0AC\nAQaABAIMAAkEGAASCDAAJBBgAEggwACQQIABIEGj7AGF7qWXXor3338/jjvuuOjTp0/2HACKRME+\nA967d29s27YtdcPVV18dTz31VJSUlMRdd90Vl112WVRXV6duAqA4FESA9+zZE1OmTIkhQ4bEG2+8\nEXPmzIm2bdvGUUcdFZdeemns2rWrzjeVl5fHW2+9FQ899FAMGjQofvGLX0TLli3jpz/9aZ1vAaD4\nFMQt6JtuuinefvvtOOOMM+KKK66IRo0axdy5c6O0tDTGjh0b8+bNiyuuuOI/Xmf27Nnx9NNP7/fc\nm2++GaWlpbXetHz58rjnnns+dmz48OHx+OOP1/oaAHAgBRHg+fPnx7Jly6Jly5bRpEmT+OCDD+LL\nX/5yRERMnjw5Jk6cWKsAX3bZZXHJJZfs99yTTz4ZO3bsqPWmli1bxurVq+P888/fd+z3v/99tGzZ\nstbXAIADKYgAn3TSSbFq1ao4++yz45prrom//e1v+86tWLEiOnfuXKvrNG7cOBo3brzfcy1btoy9\ne/fWetOwYcOirKws2rVrF2effXYsWrQoRowYEZWVlbW+BgAcSEEEeNy4cdGvX7/48Y9/HP369Yv2\n7dtHRMQtt9wSjzzySCxcuLDON7Vp0ybmzZsXN910U8yaNSvatGkT69ati1atWtX5FgCKT0EE+KKL\n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\nrkJggg==\n" - } - ], - "prompt_number": 11 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Also, if the return value of a call to %R (in line mode) has just been printed to the console, then its value is also not returned." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "v = %R print(X)\n", - "assert v == None" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "display_data", - "text": [ - "[1] 0 1 2 3 4\n" - ] - } - ], - "prompt_number": 12 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But, if the last value did not print anything to console, the value is returned:\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "v = %R print(summary(X)); X\n", - "print('v:', v)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "display_data", - "text": [ - " Min. 1st Qu. Median Mean 3rd Qu. Max. \n", - " 0 1 2 2 3 4 \n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "v: [0 1 2 3 4]\n" - ] - } - ], - "prompt_number": 13 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The return value can be suppressed by a trailing ';' or an -n argument.\n" - ] - }, - { - "cell_type": "code", - "collapsed": true, - "input": [ - "%R -n X" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 14 - }, - { - "cell_type": "code", - "collapsed": true, - "input": [ - "%R X; " - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 15 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Cell level magic" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Often, we will want to do more than a simple linear regression model. There may be several lines of R code that we want to \n", - "use before returning to python. This is the cell-level magic.\n", - "\n", - "\n", - "For the cell level magic, inputs can be passed via the -i or --inputs argument in the line. These variables are copied \n", - "from the shell namespace to R's namespace using rpy2.robjects.r.assign. It would be nice not to have to copy these into R: rnumpy ( http://bitbucket.org/njs/rnumpy/wiki/API ) has done some work to limit or at least make transparent the number of copies of an array. This seems like a natural thing to try to build on. Arrays can be output from R via the -o or --outputs argument in the line. All other arguments are sent to R's png function, which is the graphics device used to create the plots.\n", - "\n", - "We can redo the above calculations in one ipython cell. We might also want to add some output such as a summary\n", - " from R or perhaps the standard plotting diagnostics of the lm." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%R -i X,Y -o XYcoef\n", - "XYlm = lm(Y~X)\n", - "XYcoef = coef(XYlm)\n", - "print(summary(XYlm))\n", - "par(mfrow=c(2,2))\n", - "plot(XYlm)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "display_data", - "text": [ - "\n", - "Call:\n", - "lm(formula = Y ~ X)\n", - "\n", - "Residuals:\n", - " 1 2 3 4 5 \n", - "-0.2 0.9 -1.0 0.1 0.2 \n", - "\n", - "Coefficients:\n", - " Estimate Std. Error t value Pr(>|t|) \n", - "(Intercept) 3.2000 0.6164 5.191 0.0139 *\n", - "X 0.9000 0.2517 3.576 0.0374 *\n", - "---\n", - "Signif. codes: 0 \u2018***\u2019 0.001 \u2018**\u2019 0.01 \u2018*\u2019 0.05 \u2018.\u2019 0.1 \u2018 \u2019 1 \n", - "\n", - "Residual standard error: 0.7958 on 3 degrees of freedom\n", - "Multiple R-squared: 0.81,\tAdjusted R-squared: 0.7467 \n", - "F-statistic: 12.79 on 1 and 3 DF, p-value: 0.03739 \n", - "\n" - ] - }, - { - "output_type": "display_data", - "png": "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\n" - } - ], - "prompt_number": 16 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Passing data back and forth" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Currently, data is passed through RMagics.pyconverter when going from python to R and RMagics.Rconverter when \n", - "going from R to python. These currently default to numpy.ndarray. Future work will involve writing better converters, most likely involving integration with http://pandas.sourceforge.net.\n", - "\n", - "Passing ndarrays into R seems to require a copy, though once an object is returned to python, this object is NOT copied, and it is possible to change its values.\n" - ] - }, - { - "cell_type": "code", - "collapsed": true, - "input": [ - "seq1 = np.arange(10)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 17 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%R -i seq1 -o seq2\n", - "seq2 = rep(seq1, 2)\n", - "print(seq2)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "display_data", - "text": [ - " [1] 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9\n" - ] - } - ], - "prompt_number": 18 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "seq2[::2] = 0\n", - "seq2" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "pyout", - "prompt_number": 19, - "text": [ - "array([0, 1, 0, 3, 0, 5, 0, 7, 0, 9, 0, 1, 0, 3, 0, 5, 0, 7, 0, 9], dtype=int32)" - ] - } - ], - "prompt_number": 19 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%R\n", - "print(seq2)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "display_data", - "text": [ - " [1] 0 1 0 3 0 5 0 7 0 9 0 1 0 3 0 5 0 7 0 9\n" - ] - } - ], - "prompt_number": 20 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Once the array data has been passed to R, modifring its contents does not modify R's copy of the data." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "seq1[0] = 200\n", - "%R print(seq1)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "display_data", - "text": [ - " [1] 0 1 2 3 4 5 6 7 8 9\n" - ] - } - ], - "prompt_number": 21 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But, if we pass data as both input and output, then the value of \"data\" in user_ns will be overwritten and the\n", - "new array will be a view of the data in R's copy." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print(seq1)\n", - "%R -i seq1 -o seq1\n", - "print(seq1)\n", - "seq1[0] = 200\n", - "%R print(seq1)\n", - "seq1_view = %R seq1\n", - "assert(id(seq1_view.data) == id(seq1.data))" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "[200 1 2 3 4 5 6 7 8 9]\n", - "[200 1 2 3 4 5 6 7 8 9]\n" - ] - }, - { - "output_type": "display_data", - "text": [ - " [1] 200 1 2 3 4 5 6 7 8 9\n" - ] - } - ], - "prompt_number": 22 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Exception handling\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Exceptions are handled by passing back rpy2's exception and the line that triggered it." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "try:\n", - " %R -n nosuchvar\n", - "except Exception as e:\n", - " print(e)\n", - " pass" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "parsing and evaluating line \"nosuchvar\".\n", - "R error message: \"Error in eval(expr, envir, enclos) : object 'nosuchvar' not found\n", - "\"\n", - " R stdout:\"Error in eval(expr, envir, enclos) : object 'nosuchvar' not found\n", - "\"\n", - "\n" - ] - } - ], - "prompt_number": 23 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Structured arrays and data frames\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In R, data frames play an important role as they allow array-like objects of mixed type with column names (and row names). In bumpy, the closest analogy is a structured array with named fields. In future work, it would be nice to use pandas to return full-fledged DataFrames from rpy2. In the mean time, structured arrays can be passed back and forth with the -d flag to %R, %Rpull, and %Rget" - ] - }, - { - "cell_type": "code", - "collapsed": true, - "input": [ - "datapy= np.array([(1, 2.9, 'a'), (2, 3.5, 'b'), (3, 2.1, 'c')],\n", - " dtype=[('x', '" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Back to the main [Index](../Index.ipynb)" - ] - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Customization" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "IPython has rich APIs for customization. Many behaviors of the different IPython applications can be configured using command line arguments or configuration files. IPython's core syntax and command line features can also be customized through input filters, custom magic commands, etc." - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Tutorials" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Coming soon." - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Examples" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Coming soon." - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Non-notebook examples" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This directory also contains examples that are regular Python (`.py`) files." - ] - }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Back to the main [Index](../Index.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Customization" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "IPython has rich APIs for customization. Many behaviors of the different IPython applications can be configured using command line arguments or configuration files. IPython's core syntax and command line features can also be customized through input filters, custom magic commands, etc." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tutorials" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Coming soon." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Examples" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Coming soon." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Non-notebook examples" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This directory also contains examples that are regular Python (`.py`) files." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "%run ../utils/list_pyfiles.ipy" - ], - "language": "python", + "data": { + "text/html": [ + "appconfig.py
" + ], + "text/plain": [ + "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Customization/appconfig.py" + ] + }, "metadata": {}, - "outputs": [ - { - "html": [ - "appconfig.py
" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Customization/appconfig.py" - ] - } - ], - "prompt_number": 1 + "output_type": "display_data" } ], - "metadata": {} + "source": [ + "%run ../utils/list_pyfiles.ipy" + ] } - ] + ], + "metadata": { + "signature": "sha256:de8cb1aff3da9097ba3fc7afab4327fc589f2bb1c154feeeb5ed97c87e37486f" + }, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/Embedding/Index.ipynb b/examples/Embedding/Index.ipynb index 9682176..e5ecb6a 100644 --- a/examples/Embedding/Index.ipynb +++ b/examples/Embedding/Index.ipynb @@ -1,188 +1,194 @@ { - "metadata": { - "name": "", - "signature": "sha256:627cdf03b8de558c9344f9d1e8f0beeb2448e37e492d676e6db7b07d33251a2b" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Back to the main [Index](../Index.ipynb)" - ] - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Embedding IPython Into Other Applications" - ] - }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Back to the main [Index](../Index.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Embedding IPython Into Other Applications" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The architecture of IPython is built with reusable components. These components include:\n", + "\n", + "* The configuration system for processing command line arguments and configuration files\n", + "* The IPython `InteractiveShell` object that provides the core interactive features across the entire code base\n", + "* The IPython kernel, which provides the capabilities of the `InteractiveShell` object over a ZeroMQ/JSON based message protocol to various frontends\n", + "* The IPython frontends (Notebook, Qt Console, Console, Terminal)\n", + "\n", + "These components can be embedded into other applications." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tutorials" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Coming soon." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Examples" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Coming soon." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Non-notebook examples" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This directory also contains examples that are regular Python (`.py`) files." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", + "data": { + "text/html": [ + "embed_class_long.py
" + ], + "text/plain": [ + "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Embedding/embed_class_long.py" + ] + }, "metadata": {}, - "source": [ - "The architecture of IPython is built with reusable components. These components include:\n", - "\n", - "* The configuration system for processing command line arguments and configuration files\n", - "* The IPython `InteractiveShell` object that provides the core interactive features across the entire code base\n", - "* The IPython kernel, which provides the capabilities of the `InteractiveShell` object over a ZeroMQ/JSON based message protocol to various frontends\n", - "* The IPython frontends (Notebook, Qt Console, Console, Terminal)\n", - "\n", - "These components can be embedded into other applications." - ] + "output_type": "display_data" }, { - "cell_type": "heading", - "level": 2, + "data": { + "text/html": [ + "embed_class_short.py
" + ], + "text/plain": [ + "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Embedding/embed_class_short.py" + ] + }, "metadata": {}, - "source": [ - "Tutorials" - ] + "output_type": "display_data" }, { - "cell_type": "markdown", + "data": { + "text/html": [ + "embed_function.py
" + ], + "text/plain": [ + "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Embedding/embed_function.py" + ] + }, "metadata": {}, - "source": [ - "Coming soon." - ] + "output_type": "display_data" }, { - "cell_type": "heading", - "level": 2, + "data": { + "text/html": [ + "inprocess_qtconsole.py
" + ], + "text/plain": [ + "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Embedding/inprocess_qtconsole.py" + ] + }, "metadata": {}, - "source": [ - "Examples" - ] + "output_type": "display_data" }, { - "cell_type": "markdown", + "data": { + "text/html": [ + "inprocess_terminal.py
" + ], + "text/plain": [ + "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Embedding/inprocess_terminal.py" + ] + }, "metadata": {}, - "source": [ - "Coming soon." - ] + "output_type": "display_data" }, { - "cell_type": "heading", - "level": 2, + "data": { + "text/html": [ + "internal_ipkernel.py
" + ], + "text/plain": [ + "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Embedding/internal_ipkernel.py" + ] + }, "metadata": {}, - "source": [ - "Non-notebook examples" - ] + "output_type": "display_data" }, { - "cell_type": "markdown", + "data": { + "text/html": [ + "ipkernel_qtapp.py
" + ], + "text/plain": [ + "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Embedding/ipkernel_qtapp.py" + ] + }, "metadata": {}, - "source": [ - "This directory also contains examples that are regular Python (`.py`) files." - ] + "output_type": "display_data" }, { - "cell_type": "code", - "collapsed": false, - "input": [ - "%run ../utils/list_pyfiles.ipy" - ], - "language": "python", + "data": { + "text/html": [ + "ipkernel_wxapp.py
" + ], + "text/plain": [ + "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Embedding/ipkernel_wxapp.py" + ] + }, "metadata": {}, - "outputs": [ - { - "html": [ - "embed_class_long.py
" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Embedding/embed_class_long.py" - ] - }, - { - "html": [ - "embed_class_short.py
" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Embedding/embed_class_short.py" - ] - }, - { - "html": [ - "embed_function.py
" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Embedding/embed_function.py" - ] - }, - { - "html": [ - "inprocess_qtconsole.py
" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Embedding/inprocess_qtconsole.py" - ] - }, - { - "html": [ - "inprocess_terminal.py
" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Embedding/inprocess_terminal.py" - ] - }, - { - "html": [ - "internal_ipkernel.py
" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Embedding/internal_ipkernel.py" - ] - }, - { - "html": [ - "ipkernel_qtapp.py
" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Embedding/ipkernel_qtapp.py" - ] - }, - { - "html": [ - "ipkernel_wxapp.py
" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Embedding/ipkernel_wxapp.py" - ] - } - ], - "prompt_number": 1 + "output_type": "display_data" } ], - "metadata": {} + "source": [ + "%run ../utils/list_pyfiles.ipy" + ] } - ] + ], + "metadata": { + "signature": "sha256:627cdf03b8de558c9344f9d1e8f0beeb2448e37e492d676e6db7b07d33251a2b" + }, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Animations Using clear_output.ipynb b/examples/IPython Kernel/Animations Using clear_output.ipynb index c799dfb..8b59c77 100644 --- a/examples/IPython Kernel/Animations Using clear_output.ipynb +++ b/examples/IPython Kernel/Animations Using clear_output.ipynb @@ -1,194 +1,1341 @@ { - "metadata": { - "name": "" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Simple animations Using clear_output" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Sometimes you want to clear the output area in the middle of a calculation. This can be useful for doing simple animations. In terminals, there is the carriage-return (`'\\r'`) for overwriting a single line, but the notebook frontend can clear the whole output area, not just a single line.\n", - "\n", - "To clear output in the Notebook you can use the `clear_output()` function. If you are clearing the output every frame of an animation, calling `clear_output()` will create noticeable flickering. You can use `clear_output(wait=True)` to add the *clear_output* call to a queue. When data becomes available to replace the existing output, the *clear_output* will be called immediately before the new data is added. This avoids the flickering by not rendering the cleared output to the screen." - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Simple example" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here we show our progress iterating through a list:" - ] - }, - { - "cell_type": "code", - "collapsed": true, - "input": [ - "import sys\n", - "import time" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 1 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import display, clear_output\n", - "for i in range(10):\n", - " time.sleep(0.25)\n", - " clear_output(wait=True)\n", - " print(i)\n", - " sys.stdout.flush()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "9\n" - ] - } - ], - "prompt_number": 2 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "AsyncResult.wait_interactive" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The AsyncResult object has a special `wait_interactive()` method, which prints its progress interactively,\n", - "so you can watch as your parallel computation completes.\n", - "\n", - "**This example assumes you have an IPython cluster running, which you can start from the [cluster panel](/#clusters)**" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython import parallel\n", - "rc = parallel.Client()\n", - "view = rc.load_balanced_view()\n", - "\n", - "amr = view.map_async(time.sleep, [0.5]*100)\n", - "\n", - "amr.wait_interactive()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - " 100/100 tasks finished after 6 s" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "done\n" - ] - } - ], - "prompt_number": 3 - }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Simple animations Using clear_output" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Sometimes you want to clear the output area in the middle of a calculation. This can be useful for doing simple animations. In terminals, there is the carriage-return (`'\\r'`) for overwriting a single line, but the notebook frontend can clear the whole output area, not just a single line.\n", + "\n", + "To clear output in the Notebook you can use the `clear_output()` function. If you are clearing the output every frame of an animation, calling `clear_output()` will create noticeable flickering. You can use `clear_output(wait=True)` to add the *clear_output* call to a queue. When data becomes available to replace the existing output, the *clear_output* will be called immediately before the new data is added. This avoids the flickering by not rendering the cleared output to the screen." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simple example" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we show our progress iterating through a list:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import sys\n", + "import time" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Matplotlib example" + "name": "stdout", + "output_type": "stream", + "text": [ + "9\n" ] - }, + } + ], + "source": [ + "from IPython.display import display, clear_output\n", + "for i in range(10):\n", + " time.sleep(0.25)\n", + " clear_output(wait=True)\n", + " print(i)\n", + " sys.stdout.flush()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## AsyncResult.wait_interactive" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The AsyncResult object has a special `wait_interactive()` method, which prints its progress interactively,\n", + "so you can watch as your parallel computation completes.\n", + "\n", + "**This example assumes you have an IPython cluster running, which you can start from the [cluster panel](/#clusters)**" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can also use `clear_output()` to clear figures and plots." + "name": "stdout", + "output_type": "stream", + "text": [ + " 100/100 tasks finished after 6 s\n", + "done\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%matplotlib inline\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 4 - }, + } + ], + "source": [ + "from IPython import parallel\n", + "rc = parallel.Client()\n", + "view = rc.load_balanced_view()\n", + "\n", + "amr = view.map_async(time.sleep, [0.5]*100)\n", + "\n", + "amr.wait_interactive()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Matplotlib example" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also use `clear_output()` to clear figures and plots." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "from scipy.special import jn\n", - "x = np.linspace(0,5)\n", - "f, ax = plt.subplots()\n", - "ax.set_title(\"Bessel functions\")\n", - "\n", - "for n in range(1,10):\n", - " time.sleep(1)\n", - " ax.plot(x, jn(x,n))\n", - " clear_output(wait=True)\n", - " display(f)\n", - "\n", - "# close the figure at the end, so we don't get a duplicate\n", - "# of the last plot\n", - "plt.close()" - ], - "language": "python", + "data": { + "image/png": [ + "iVBORw0KGgoAAAANSUhEUgAAAloAAAGKCAYAAADUje9YAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n", + "AAALEgAACxIB0t1+/AAAIABJREFUeJzs3Xd4U+fZx/GvJMt774H3wOABmG32CitANmkzmzRpk6ZN\n", + "SdpmJzTrzWpmm9WRBVklhB32DBsMBoyx8cA23lOesiyd8/5hcEKBBIxtedyf69J1dKxxbksY/fSc\n", + "Z2hUVVURQgghhBAdTmvtAoQQQggheisJWkIIIYQQnUSClhBCCCFEJ5GgJYQQQgjRSSRoCSGEEEJ0\n", + "EglaQgghhBCdRIKWEKJH+Pjjj/nVr371k/epqanh2muvxcXFhZdffrmLKhNCiIuToCWEYOHChWi1\n", + "2raLj48PM2fOZNeuXdYurY1Go/nZ+/ztb3/D09OTsrIyFixY0AVVXZjJZCIuLo4jR45YrQYhRPcg\n", + "QUsIgUaj4dZbb0VRFBRF4cSJE8TFxTF9+nQyMzOtXd4lS01NZf78+Tg4OGBra2u1OmxtbUlLSyMx\n", + "MdFqNQghugcJWkIIVFXlx4tEeHl58dprrxEYGMjKlSutWNnlqaurw97evsuPO3HiRLZv397lxxVC\n", + "dH8StIQQF2U2m3F3d7d2GZfFGquKaTQaqxxXCNH9SdASQpynoqKCRx99FFtbW+bPn3/ObWvXrmXw\n", + "4MHY29sTGRnJe++9d87tn3zyCTExMTg6OpKcnExqaioAFouFP/3pT/j7++Pi4sL8+fOpqqpqe1xJ\n", + "SQk333wzrq6ueHh4cOedd1JZWXlJ9X788cdotVq2bdvGpEmT0Ol0QGtL07Zt286578KFC9s61be0\n", + "tKDVatm9ezdXX301zs7OxMbGsnTp0nMeYzabefHFFwkPD8fe3p5hw4axYcOG846r1Wr59NNPAQgL\n", + "CyM/P7/tOQwGA/fffz++vr44Ojoyfvx49u7d23b7pdbyyiuvEBoaipOTE9OnTycvL++SXiMhhHVI\n", + "0BJCALB48eK2zvC+vr4sWrSIV199FWdn57b7LFmyhEceeYR33nmHmpoaVq9ezdKlS3nppZcAOHr0\n", + "KAsWLOCLL76gsrKSMWPG8PnnnwPw/vvvs337dg4ePEhOTg61tbVtp9sMBgNjx45l/PjxnDp1ipyc\n", + "HOLi4pg0aRJms/lna7/zzjtRFIUJEyawdetWLBYL0NrS9L+d6H+8r9frAbjnnnt44IEHKC8v55ln\n", + "nuH222+nrKys7X633nora9as4ZtvvqGmpobHHnuMF198kdtuu+2c4yqKwu23337ecZqbm0lOTsZk\n", + "MrF3716Kior49a9/zcyZM1m+fPnP1lJeXg7A6tWree+999iwYQPFxcX4+vqyatWqn319hBBWpAoh\n", + "+rxnnnlGve2229r2q6ur1S+//FL18PBQP/nkE1VVVdVisaj9+vVTc3Nzz3lsQUGB6ubmpqqqqi5f\n", + "vlyNiIhQTSbTecd46KGH1FtuueWCx3/00UfVZ5555ryfT506Vf3mm29UVVXVjz76SL3zzjt/8veY\n", + "OHGiunXr1nP2t23bds59Fi5ceM7zaDQa9d133z3nPqNHj1ZXrVqlqqqq7ty5U3V3d1crKysv+biq\n", + "qqphYWFqXl6eqqqq+tprr6mjR48+73EffPCBGhMTc8m1vP322+q4ceMuWocQovuRFi0hBHBu3yZ3\n", + "d3fmz5/P448/3tZalZOTQ2FhIREREedMBRESEkJdXR2FhYVMmTIFHx8fIiIiuO+++1i6dCmKogBw\n", + "9913s2HDBoYMGcKjjz56ztQRO3bs4Nlnnz3nebVaLZs3byY9Pf2yfo9LmQbif40bN+6c/YiIiLbT\n", + "mjt37mT06NF4enq2+7hbt25l7ty55/38mmuu4eTJk20tVher5ewp1BtvvJHi4mJiYmJYsGAB69ev\n", + "/+lfTAhhdRK0hBAXNWDAAHJzc4HWIKHVajEYDG3TQJy9WCwWgoKCcHJyYteuXXzyySd4eHiwYMEC\n", + "brjhBgAGDhxITk4OTz75JFVVVcycOZO3334bAK1WyzvvvHPB533iiSfajn+5NBrNeaceGxoazruf\n", + "o6PjOft6vb4teLbnuBeq41L9VC3+/v6kpaXxxhtvoKoqt956Kw899NAV1yeE6DwStIQQFw0CBw8e\n", + "JDY2FmhtWQkKCmrrc3VWfX09OTk5bfuKojB58mRefPFF9uzZw7Jly6iqqkJRFBwdHbn++uv58MMP\n", + "ef/99/nnP/8JwIQJE857XqCtI317eXh4nNdZ/Pvvv7+k4HM23IwZM4Y9e/ac03H/f2k0mrZ+YRcy\n", + "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\n", + "FHu5DJWdFJVcin1FqZRLK/XgpjEILNidSJneyMy+gSZzY7BxPRmphaz/LYqQDo25p3/zajvaC4KI\n", + "TmtAq74ivgxoNHq0agM67Y0vI9qbHNNpDej1RuRyGQqlDDs7GXZKGXZ28hv2ywWbnZ0MO4UM+dVS\n", + "ip3dNftyKXKF7N9jdlJkclml/kaTC63o6Gi2bdvGyy+/DMDSpUvp0aMH7dqVrw7Kzs7mq6++Ytas\n", + "WUgkElatWoWHhwf33XffTdurLaFVdCaG4+Om0e6Ld/G+r+vV4yczs3hx8xZWjB6Fv1vl4kbpC4vZ\n", + "3/MRwn5diGuHVqYy2eIo0Rp4ZUMsfZt5MLbD3UW5NgoG3l/5LB0CuzO625M1ZKHlckVsDXqgLcEm\n", + "FFuiTof6wzeRBYegfOQZk/VjDoxqLaeen4lRo6Xj9/PMFq9OFEUWHznKnqRkfhwxDA/7qqXpsmZE\n", + "UURrECjVC5TpjJTpjZTpBMr0Rkp1RsquPV6xrTEIaPQCaoOxohSuljqDgEIuxb5CjCnlUpSyilIu\n", + "QSmXIhHhTFYpLkoZXZq6YK+QXa1nJ5OgkElRyCTY3aJUVNSzk0mwk0qQy6TIJFjl1GltcuZ4Knu3\n", + "XGTA6LY0b2MZWQ1EQUSvN6LXlb90OsPV7Zvt6/VGDBX1DXrh6r5Bb0SvFzBcW0dvxGAQkEolyOXl\n", + "wkwulyKXy5Bd2bYrF2iB7aWm9dGKioq6roO+ffty6NChq0Lr5MmT9OrV6+qXuF+/fnz33Xe3FFq1\n", + "QUlMIpGPvkrrD1+7TmQBhPo05IWITkzZso3fHxiFg92dn5QvLfwe74E96pXIAnBSypk/KJip62Pw\n", + "cLCjf/Pqx035a/832MlVjOx669HOukRjPzfufzyM1ctMJ7ZEQUDz9YdI3b1QjHuqxts3NzJ7JaHf\n", + "z+P8Wx9z9MGX6LR8EQqv2p1uFkWRjw8d4VBqKj+NGIZ7PRJZUC5OVHYyVHYyPB3uflRRqBBuGoOA\n", + "Wi+gNVS8jAJag0h6kZbfTmbS0tuBsMZO6IwiWqN4dfpTL4jorpRGAb1BRC8I6Izlx3XGin2DiEEQ\n", + "0QsieqOAKIL8ivCSSiq2ywWZXHr9S3btvkyCXHL9Mdk1pUxCeSmVIL1aD2SSK+clSCUgvcW2TCKp\n", + "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\n", + "msn/AbPOyGwnPY5DAAAAAElFTkSuQmCC\n" + ], + "text/plain": [ + "" + ] + }, "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "display_data", - "png": "iVBORw0KGgoAAAANSUhEUgAAAloAAAGKCAYAAADUje9YAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3Xd4U+fZx/GvJMt774H3wOABmG32CitANmkzmzRpk6ZN\nSdpmJzTrzWpmm9WRBVklhB32DBsMBoyx8cA23lOesiyd8/5hcEKBBIxtedyf69J1dKxxbksY/fSc\nZ2hUVVURQgghhBAdTmvtAoQQQggheisJWkIIIYQQnUSClhBCCCFEJ5GgJYQQQgjRSSRoCSGEEEJ0\nEglaQgghhBCdRIKWEKJH+Pjjj/nVr371k/epqanh2muvxcXFhZdffrmLKhNCiIuToCWEYOHChWi1\n2raLj48PM2fOZNeuXdYurY1Go/nZ+/ztb3/D09OTsrIyFixY0AVVXZjJZCIuLo4jR45YrQYhRPcg\nQUsIgUaj4dZbb0VRFBRF4cSJE8TFxTF9+nQyMzOtXd4lS01NZf78+Tg4OGBra2u1OmxtbUlLSyMx\nMdFqNQghugcJWkIIVFXlx4tEeHl58dprrxEYGMjKlSutWNnlqaurw97evsuPO3HiRLZv397lxxVC\ndH8StIQQF2U2m3F3d7d2GZfFGquKaTQaqxxXCNH9SdASQpynoqKCRx99FFtbW+bPn3/ObWvXrmXw\n4MHY29sTGRnJe++9d87tn3zyCTExMTg6OpKcnExqaioAFouFP/3pT/j7++Pi4sL8+fOpqqpqe1xJ\nSQk333wzrq6ueHh4cOedd1JZWXlJ9X788cdotVq2bdvGpEmT0Ol0QGtL07Zt286578KFC9s61be0\ntKDVatm9ezdXX301zs7OxMbGsnTp0nMeYzabefHFFwkPD8fe3p5hw4axYcOG846r1Wr59NNPAQgL\nCyM/P7/tOQwGA/fffz++vr44Ojoyfvx49u7d23b7pdbyyiuvEBoaipOTE9OnTycvL++SXiMhhHVI\n0BJCALB48eK2zvC+vr4sWrSIV199FWdn57b7LFmyhEceeYR33nmHmpoaVq9ezdKlS3nppZcAOHr0\nKAsWLOCLL76gsrKSMWPG8PnnnwPw/vvvs337dg4ePEhOTg61tbVtp9sMBgNjx45l/PjxnDp1ipyc\nHOLi4pg0aRJms/lna7/zzjtRFIUJEyawdetWLBYL0NrS9L+d6H+8r9frAbjnnnt44IEHKC8v55ln\nnuH222+nrKys7X633nora9as4ZtvvqGmpobHHnuMF198kdtuu+2c4yqKwu23337ecZqbm0lOTsZk\nMrF3716Kior49a9/zcyZM1m+fPnP1lJeXg7A6tWree+999iwYQPFxcX4+vqyatWqn319hBBWpAoh\n+rxnnnlGve2229r2q6ur1S+//FL18PBQP/nkE1VVVdVisaj9+vVTc3Nzz3lsQUGB6ubmpqqqqi5f\nvlyNiIhQTSbTecd46KGH1FtuueWCx3/00UfVZ5555ryfT506Vf3mm29UVVXVjz76SL3zzjt/8veY\nOHGiunXr1nP2t23bds59Fi5ceM7zaDQa9d133z3nPqNHj1ZXrVqlqqqq7ty5U3V3d1crKysv+biq\nqqphYWFqXl6eqqqq+tprr6mjR48+73EffPCBGhMTc8m1vP322+q4ceMuWocQovuRFi0hBHBu3yZ3\nd3fmz5/P448/3tZalZOTQ2FhIREREedMBRESEkJdXR2FhYVMmTIFHx8fIiIiuO+++1i6dCmKogBw\n9913s2HDBoYMGcKjjz56ztQRO3bs4Nlnnz3nebVaLZs3byY9Pf2yfo9LmQbif40bN+6c/YiIiLbT\nmjt37mT06NF4enq2+7hbt25l7ty55/38mmuu4eTJk20tVher5ewp1BtvvJHi4mJiYmJYsGAB69ev\n/+lfTAhhdRK0hBAXNWDAAHJzc4HWIKHVajEYDG3TQJy9WCwWgoKCcHJyYteuXXzyySd4eHiwYMEC\nbrjhBgAGDhxITk4OTz75JFVVVcycOZO3334bAK1WyzvvvHPB533iiSfajn+5NBrNeaceGxoazruf\no6PjOft6vb4teLbnuBeq41L9VC3+/v6kpaXxxhtvoKoqt956Kw899NAV1yeE6DwStIQQFw0CBw8e\nJDY2FmhtWQkKCmrrc3VWfX09OTk5bfuKojB58mRefPFF9uzZw7Jly6iqqkJRFBwdHbn++uv58MMP\nef/99/nnP/8JwIQJE857XqCtI317eXh4nNdZ/Pvvv7+k4HM23IwZM4Y9e/ac03H/f2k0mrZ+YRcy\nceLEC06TsXz5cmJiYvDx8fnZes7SarXMnj2bN998k+XLl7e9hkKI7kmClhDivKkJysvL+eCDD3j5\n5Zd56qmngNYw8dZbb/GXv/yFRYsWUVtby/Hjx5k7dy6vvPIKAIsWLWL06NHk5OTQ2NjIl19+iZ+f\n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\nTmtAq74ivgxoNHq0agM67Y0vI9qbHNNpDej1RuRyGQqlDDs7GXZKGXZ28hv2ywWbnZ0MO4UM+dVS\nip3dNftyKXKF7N9jdlJkclml/kaTC63o6Gi2bdvGyy+/DMDSpUvp0aMH7dqVrw7Kzs7mq6++Ytas\nWUgkElatWoWHhwf33XffTdurLaFVdCaG4+Om0e6Ld/G+r+vV4yczs3hx8xZWjB6Fv1vl4kbpC4vZ\n3/MRwn5diGuHVqYy2eIo0Rp4ZUMsfZt5MLbD3UW5NgoG3l/5LB0CuzO625M1ZKHlckVsDXqgLcEm\nFFuiTof6wzeRBYegfOQZk/VjDoxqLaeen4lRo6Xj9/PMFq9OFEUWHznKnqRkfhwxDA/7qqXpsmZE\nUURrECjVC5TpjJTpjZTpBMr0Rkp1RsquPV6xrTEIaPQCaoOxohSuljqDgEIuxb5CjCnlUpSyilIu\nQSmXIhHhTFYpLkoZXZq6YK+QXa1nJ5OgkElRyCTY3aJUVNSzk0mwk0qQy6TIJFjl1GltcuZ4Knu3\nXGTA6LY0b2MZWQ1EQUSvN6LXlb90OsPV7Zvt6/VGDBX1DXrh6r5Bb0SvFzBcW0dvxGAQkEolyOXl\nwkwulyKXy5Bd2bYrF2iB7aWm9dGKioq6roO+ffty6NChq0Lr5MmT9OrV6+qXuF+/fnz33Xe3FFq1\nQUlMIpGPvkrrD1+7TmQBhPo05IWITkzZso3fHxiFg92dn5QvLfwe74E96pXIAnBSypk/KJip62Pw\ncLCjf/Pqx035a/832MlVjOx669HOukRjPzfufzyM1ctMJ7ZEQUDz9YdI3b1QjHuqxts3NzJ7JaHf\nz+P8Wx9z9MGX6LR8EQqv2p1uFkWRjw8d4VBqKj+NGIZ7PRJZUC5OVHYyVHYyPB3uflRRqBBuGoOA\nWi+gNVS8jAJag0h6kZbfTmbS0tuBsMZO6IwiWqN4dfpTL4jorpRGAb1BRC8I6Izlx3XGin2DiEEQ\n0QsieqOAKIL8ivCSSiq2ywWZXHr9S3btvkyCXHL9Mdk1pUxCeSmVIL1aD2SSK+clSCUgvcW2TCKp\n2C/34ZNKK8qKc5IrxyWSCrH47760Yrv8WPn/6oqglHCTYxIqjv+7LZFIMBoEdm2IJiX+MuOe6YJn\nA8tZqSyRSq5OK5oCURQRjCIGg1AuyAwCBoMRo768NOgFDAaBgtKUSrVXbSuLi4txdv43RYaLiwuF\nhYVX90tKSvDz87vufFFRUXW7u2vKktI4Pm4qLWZMxmfYvTetM65Na85kZTNz9x4W9u972yed4ovx\npK/ZTs+9ddcB/nZ4OSqYNzCY1zdewk0lJ6IaK35OJRxi79kNfDBhhdUFJb0bGjU1rdjS/f4tFBeg\nfOODOjudLZXLabPwTS4t/J7DI56j08pPcfC7fQ7TmkIURRYcOERkRgY/jhiGm6p+xQ4yBVKJBHs7\nGfZ2Mtxv0Kznskr4/WQmE8MbMaRVzYZvMAoixmuEl0GoEGLG8mMGQcRgLC+NwvXlta+r58TybaNY\n3rYglrer1osYBf49X1FXEMtFplEoL2+2fW094brt8qk4gYp9AUTK3yMiIopcV1cURUSuPyaIIlSU\nIlCxi9JgpEN2IXqplGgfV/638RJSrhdmV4TbTfclIOHfffj3GNe9B6BcHFZslp+/UvNKvWvqSq45\ndl39a27X1/V7pZF/q/9bSrjuiOSGetxQ76r9FccfqmQ64GoLLWdn5+uEU1FRES4uLpU+X5toMnI4\nNuZlgl56HN+HBt+ynkQiYWbvnjy2Zi2/nDrDxND2N60niiIX3v2MZtMmViuCfF3B392eWf0Cmb0j\ngXkDg2nhXXn/lPySHJZums1Lw+fi6lh/Iglf4TqxdX9bgkNqRmzpNv8P4+nj2M/8FIld3XYSlkgk\nNH/jaRTeHhwZ+Tzhyz/GpU1zk/YpiiLz9h3gTHY2PwwfhqsJk1/bgH0JBXx+IMVk4RuujDqV/1Is\nJxWVOUlNvMyGlado38OfiF5BiJJ/BZgglAsyqBj1qdgRKjyQhIp6iCBUiL3yuuXi799tKs79K/6u\nNCxeqXml3rV1rzl2Xf1rHKD+rV/Ruvjv8etKkeuO/Pf49fviDfUQgezKDR5VW2iFhYWxdevWq1OF\nu3bt4p57/g0oGBoayldffUWfPn2QSqXs2LGD8PDw6nZXbXS5+Rx7aApNHx+J36T771hfJZfz+aAB\njFu1hhBvT7r4/jdGSPaWfWgzc2k64c7t1XXa+DgxrWdTZm6PY9HQFvi63vnGIwhGlmx4l36h99PG\nr+orPesKjZq6cf+EcNYsi2Qgdy+2DEf2oN+0CvtZnyFxrL2EzObGf9IDKD3dOf7Q1KsLXEyBIIq8\nv3c/F3Jz+X74UJyVNpFlSv53Jpv/ncnmg0HBNPOyrkUG1ogoipw8ksKhXZcY9IB5QtFYGyeyK1dP\nNnv27NnV6cDLy4usrCyWL1/Orl27aNasGX369GHFihUEBATg4eGBVCrlu+++Y8+ePahUKh544IFb\ntpeQkECjRo2qY8ot0RcWc+yhKTQY1JNmrzxR6fc5K5W08vLkjR27GRQcjLPy35EBo0bLiQlv0nrB\nazgGNa1Re62Vpm4q7OUyvjiYQu8gd+zvkJ1+zaEfyS5I5dlBM5HUoynDm+HsoqJJoAfrV57Cw9sR\nD+/qOXYbL5xB++1CVG/MQ9a4/n0vnVoG4tKhJaeemYm9X2OcWgbWaPuCKDL7n73EXc7n2+FDbCLL\nhBgFkW8Op7EvoYCPajH1V33GoDeybc054qKzefCJTjRuWn9naqpCRkYGQUFBd6xXZ+NoGUrLOD52\nKq6hIbR6f2q1Vpb8EHWSrZfi+XX0CJTy8sG/uMU/U3jqAmE/LagxW+sKyyIzOJJcyMKhzXFQ3Fxs\nRadEsXjdm3zw+HI8nE0f4sBayEgtZM2ySAaMakOz1lVb2SOkJaOe9yrK599E3q6TiSy0DorOxhA5\n/nWCXhqP/xMP1kibRkFg5j97SSkqYunQwThWYqGMjeqhNQgs+CeREq2Rmf0CcbaFbzA5RQVq1q2I\nwtXDgYH3tzWZg3ldpLLhHerkcIJRoyVq4ls4NvOn1ZyXq71894nQDvi6OPP+3v2IoogmPZvEb1bS\navZLNWxx3WB8mA/NvR2YsyMBnVH4z/lidQFLNszg2UHv2kTWDTRq4sr9j4ez7e9zxJ7LqvT7hPw8\n1B+/g+Lhp+u9yAJwaduCruu+JumHVcTM/5q7fY40CgLv7PqH9OJivraJLJNSoNbzxqZYlDIp8wYF\n20RWLZAcl8eKpYdp2b4Rw8Z1sIksE1HnhJagN3Dq2ZnYubnQdtFbd7XqSiKRMPe+PpzOzubP89Fc\nnPsVTR8fjYN//c3tdDskEgkvdW+KvULKwj1JVx0koXz+f+mm2XRt1Y+w4J5mtNJy8WniygMTwtm+\n9hwXz2Tesb6oLkOzaAZ2vQdh13NALVhoHdg3bUTXdV+Ttz+Ss1PnIegN1WrHIAi8vXM3OWVlfDVk\nUKVCvtioHmmFWqauj6VDI2fe6OOPQlbnbk0WhSiKHN+fwIY/TjFkTHsiegba4omZkDr1bRaNRs68\nPBfRaKT9l7OQyO5+FYmjnR1fDBrIZwcOcTwugaAp42vA0rqLTCrh7T4B5KsNLD2UenVEYXPk7xSW\nXubhXi+a2ULLpqGvKw9O6sSuDdFcOJVxy3qiwYDmi/eRBrbAbuQjtWihdaDwdCNi1efocgs4MfFN\nDGXqKr3fIAi8uWMX+RoNXw4ZhL1NZJmM6OxSXt0Qw5h2DXgionGNpvay8V/0OgMb/zhN9MkMHn2+\nG/7NPM1tUp2nzggtURQ59+ZCtJm5hH43r0ZTc/g5OzF+31mWje5Jvu0acEcUcinv9Q/iTGYpv53M\nIj4zmjWHfmDKiPnIZbYb1p1o0MiFMZM6sXvTBc5Hpf/nvCiKaH9cDFIpyolTbE+it0DuYE/Hnxeg\n9Pbg2P0vosvNr9T79EYjr2/fSYlOx5LBA1HJbdMppuJAYgEzt8UzracfQ0NqNkaWjf9SkFfGb18f\nQSaXMu7ZLrjeGLTMhkmoE0JLFEUuvvcFxefjCFv2ITL7ml0RlLpyIx0L1YwJC+WVrdvRG4012n5d\nxFEhY96gYLZezGX2ur94ov9bNHRrYm6zrAYvH2fGPBnB3q0XOXsi7bpz+jW/IqQkoHpxRo2M2tZl\npHZy2n46Hc8+nTk84jnKktJuW19nNPLqth1oDQa+GDzw6iIYGzXP3+dyWHIwlXkDg+niV7m0Zzaq\nT0JMDr99c5j2EU0Y9EBb7O6wOtxGzVEnhFbcoh/J23uc8BWLajzvmb6wmNgF3xIydxqTI8JxVihY\nePBwjfZRV/Gwl9NauY4CeX+MyvobL6u6eDVwYsyTEezfFsOZ46kA6PdsQb9vO6rX5iJR2Z5GK4NE\nIqHFW88S8PRYjox8nqIzMTetpzMaeWXrdgRg8aABKGwi1iQIYnn4hvXROXw6vHmVAh3bqDqiIHJ4\ndxxbV59lxCMd6djN3zYKXstYvdBK+Pp30tdsp9Mfi1G413zk4LhPfqLBgB64tm+JVCJhQb/72Juc\nzPqLN79Y2/iXf86sJS//FAuGtOKzAymczig2t0lWh6e3E2Of6szBnZeIWr0P3Z8/Yv/GfKSutZvb\nry7gN+l+QuZO49i4qeTuPXbdOa3BwMtbtiGTSvlkQD+byDIROoPA/F2JxOSW8umwFvg42+KRmRKt\nxsDa36KIv5jDY5O70STAdt0wB1YttFKWryX5h7+I+PMzlN41n8alJCaRtFVbaP72M1ePuSiVfDFo\nIAsOHCI6J7fG+6wrpOTG8dueL3h5xAe09nFn+r0BvL8zkbi8MnObZnW4ezny4KAGHDmSyYUBryBt\nVP8CktYUPsPupeP38zn9/GzSV28DykXWlC3bUMnlfNy/r01kmYgijYE3N19CIoEPBjXDRWWbljUl\neTklrFh6CEcnJWOf6oyTiy3wq7mwWqGVvmY7lz7+gU5/fo69b9UCPFYGURSJnrmY4CmPo/S6XsQ1\n9/Tg3V49mLJlGwUaTY33be3o9Bo+W/c2j/R+iSZe5VFzO/o681L3JszYGk9GkdbMFloXQk4mqu/n\nMGZwQyJjtEQeSDS3SVaNR7dQIlZ9Tsy8pcR8/gsvbtpS7hLQvy92NpFlEjKKtExbH0Obho68fW8A\nCrnV3nqsgtjzWaz89igRPQPpP6oNMtvnbVas8tPP3rqPCzM/o9PKT3EMNI2Ddc62/WjSsvC7RXTp\nQc2CGRgcxKvbdmAQ/hucsz7zy65F+Hs3p0+7kdcd7xXkzsOhDXl7Sxz5ar2ZrLMuxJIi1AunYzds\nLJ59ejH2qc6cOJTE8f0J5jbNqnEOCSZ0zRJOLFtDlz92ML9PL+R3EXPPxq25mFPKtA0xjGzjzVOd\nfW3hG0yIIIjs3xbDrvXR3D8hnHadbAuQLAGru7Lk7j7C2VcWEL5sIc6tgk3Sh6DVcWHW54TMmYrU\n7tbD29O6dkYmkdic46/h0IXtnEs+xpMD3r6pw+WI1t7cF+zOjC1xlOpsqzdvh6jTof50FvLQLigG\njgbA1d2esU915uThFI7ujTezhdZLmV7PtKgTnJ31LB0McOaJ6RhKbdPaNc2BxAJmbI3n5Xv8GNHa\nlqTYlKjLdKz+JZK05ALGv9CNRk1sKzktBasSWnkHIjn94hw6/rwA144hJusn8ds/cGwRiNe9XW5b\nTyaVsrB/P/YmJbM6+oLJ7LEWsgpS+WnHh0wZ/gEOSqdb1hsf5kMLbwfe2xF/01Q9NkAUBLTffIjU\nzRPFuKevO+fiZs/Ypztz5ngqB3deuus0M/WNUp2OZzdsorGzM+8NHUj48o9ReHtw9P4X0Wbnmdu8\nOsOas9lXwzd087fd9E1JVlohv355CG8fJ8ZM6oSDk22RganJOxBZ6bpWI7Tyj5zi1DMzCf1uLu4R\n7UzWjyYzh4Slv9HqvSmVqu+qUvLlkIF8cvgIURl3TptSV9EbdHy29i1Gd32SIJ/bi2CJRMKL3Zvi\nrJTzwe5EjIJNKNyI7vfvEAoLUD77xk3TSDm7qhj3dBdiz2WxZ/NFm9iqJMVaLU9v2ESwuzvv39sb\nmVRaHmvrk7dpMLAnh4c+Q0lMornNtGqMgsjSQ6lsvJBnC99QC5yNTGXVT8fpPbglvQe3QmpLX2RS\nBJ2ei3O/4vTk9yr9Hqv4jxREnSfqiem0/2oWHt07mrSvmHlLafLI8Cr5fgW5uzP/vnuZtm07GcUl\nJrTOcvl97xd4ODdkUPi4StWXSSW82ccfrUHgk33J1+VFrO/otqzGcOoo9lNnIVEoblnP0VnJQ09F\nkJqYz4615xFtgvW2FGq0PLV+I228vZjVu+d1vkISiYRmr0yi2WtPcvT+F7h8MMqMllovGoPA+zsT\niL+sZvHw5rbwDSbEYBDYtuYsR/ckMO6ZLrRs62Nuk+o8pXHJHB7+LCUX4um+8+dKv8/ihVbRmRhO\njH+dtoun49W7s0n7yj9+hrx9xwmeOqHK7+3l78f49u2YsmUrGkP1kthaK8cv7eFYzG6eGzyzSoHw\nFDIpM/sFkVGk5atr8iLWZwxH96Hf+Bf2b8xH4nTnuHD2DgrGPBFBXnYJm/93BsE2FXtT8tVqnli3\ngfBGjZje455bfk99xw6hw9L3OPn0DNLXbK9lK62bfLWe1zfG4qiQMX9QME5KW/gGU1FUoGblt0dQ\nl+l5dHI3PBvc2lXDxt0jiiKpv63n8Ijn8B03lLBfF/4nGsHtsGihVXwhjshHX6X1h6/ToP89Ju1L\nFASiZ3xKi3cmVzu6/BOhHQh0d+fd3XvqjWjILcrguy1zeXH4PJzsq+6HoZJLeX9gMNFZpfwceesk\nyvUBY8xZtD9/jurV95F6VT5kiVIl54GJnSgr0bF+5SmMBpvYupa8MjWT1m2gh18TXu/e9Y4PA549\nO1WEf/iK+CW/1pvf8t2QnK/h5XUxdG7qwmu9/LCzTV+ZjOS4PFYsPUyLtg0Z8UgoSls8MpOiyy/i\n5NPvkPjdn3RevQT/SQ9UObK+xf4aSmKTOD52Gq3mTMFnaB+T95f2xyakMjmNHxhQ7TYkEglz+vQi\nqaCQH6JO1aB1lonBqOfz9e8wJOJRWvp2qHY7V/Ii7k8s4M9TWTVoofUgpCWhWfweyufeRBbQrMrv\nt1PIGDU+DET4e/kJ9Hrbik6AnNJSJq5dR/+gQKZ26VzpC6RzSDBd139LxpodnH/rY4R6NkpdFU5l\nFPPaxlge6+jD+LBGtvQuJkIURY7ujWfjn6cZ+lB7OvcKsn3WJibvQCQH+z6OqlEDum3+HueWQdVq\nxyKFVlliKsfHvkyL6c/RaFR/k/enLyoh9oNvCJk77aaOx1VBJZfzxeCBLD9zhn8Sk2rIQstk1YFv\nsFc4MLzz43fdlpu9HR8ObsaGC7lsiK5fEfeFy7moF76D4pFnkbfvVO125HIpwx/ugMrBjtU/R6LT\n1m9xkFlSwuN/r2dYi+a8ENGpyjclVSNvuvz9FerkdKImvoWhpNREllovOy9dZt7ORKbfF8CAFp7m\nNqfOotUYWPfbSWLOZvHo813xC7Z91qZE0OmJmbeU05Pfo83HbxHy/lRkqur7G1qc0FKnZHJszBSC\np03Cd+yQWukz7pMf8erbrcZCRjR0cuSzgQOYsfsf4vLza6RNS+NUwiH2nt3I5CFzkEpq5mvk5ahg\nweBm/BaVyc5Ll2ukTUtHLC1Bs3A6dn2HY9ej3123J5VJGfJge9y9HPjrx2No6mlg2PTiYib+vZ4x\nrVvxbHhYtduROzsStmwhSh8vjox4HnVK/V1ZfC2iKLIiKpOfj2fw0dBmhDZ2NrdJdZa87PJUOg6O\nCsY90wUXN1syeVNSGpfM4RHPUhwdR/edP+N9X9e7btOihJYmI4djY14i4NlxNB0/8s5vqAFKYpNI\n+3MzLaY/V6PtdvBpyGvduvLCpi0UaupWypn8khyWbprNi8Pex9WxZnNMNnZRMn9wMN8eSeNgUkGN\ntm1piHodmsWzkYV0wG7YQzXWrkQqof+oNjT2c+eP749SVlK3vn93IqWwiAl/r+fR9m15omPoXbcn\ntZPTZuGb+I4byuFhz5B//EwNWGm96I0Ci/YmczCpgM9GtCDA3XbjNxXRJ9NZ+e0ROvcqT6Ujt6XS\nMRmiKJKyYl25w/vYqju83w7Z7NmzZ9dIS3dJQkICiU/MoOmjIwl47uFa6VMURc68NAffh4aYZEVj\nKy8v0oqL+f3seYY0b1YnUk8IgpFFa16lS8u+9G433CR9uNnb0aGRM/N2JdLc04FGLnVvibgoCGiX\nLgCFEuVTdz9lfSMSiYSA5p6UFmvZs+kizVo3rBdOs0kFhUxat4GnwkJ5tF3bGmtXIpHgFt4Wx+Z+\nnH52FkofL5xbV92Xztop0hh4d1s8Egm8PyAI53rwnTIHBoPArg3RnDmeyv0TwglsYYuqb0q0uZc5\nPfk98vYeI2zZRzTod+uVydeSkZFBUNCd/bYsSh43un8ggS88Wmv95ew4iDo5Hf9b5DOsCV7tVj7s\nuOhQ3UjTs+bQj4gi3N/tSZP208LbgXf7BjJ/dyLns+qWb4woiuh++wahMB/Vc28ikZomkbFEIuGe\nfs1p26kJK789QkFe3U4xE5+fz8S165kcEc64tm1M0keDfvcQ8b/Pif3wO2IWfINYj/KcphZqmLIu\nhlYNHJjZLxCVnS0BtykozFez8pvDlBZrGf9CNxo0unOYFxvVJ3vrPg7eNwGnFgF021R9h/fbYVFC\nq9krk2qtL0Gr48K7i2k1ZypShZ3J+pFLpSwa0I9/EpNZc+GiyfqpDc4nH2f7yVW8OHwuUhOJg2tp\n38iJ13v7MWt7PHF5apP3V1voN63CePYE9tNm3zYgaU3RuVcgEb0CWfndEbLSi0zenzm4kJvHpLUb\neLlLBA+EtDJpX86tgum26TvyD53k5NMzMJTVne/mrTiVUcwr62MZ274BT9sSQ5uM+AvZrFh6iFYd\nGleEbjDdvam+Yygp5eyrC4h+9zNCv5tLi3eeN5kWsCihVZskfleez7AmHN3uhKtKyZIhA1l06DDH\n060zVlRRWT5LNrzL80Nm4+FUe8PYnZu68lL3Jryz9RIpBZpa69dU6A/sRL9tDao35iNxrD0H4tAu\nftw7LIRVPx0nOa5u5fM7nZXF0+s38naP7oxq1bJW+lR4uRPx52fInRw4OmoymvTsWunXHGy5mHd1\nZeHgVl7mNqdOIhgF9m2NYfva84x8tCOdegTYQjeYkPxjZzjQdyKiIHDPzl9w71L98ESVoV4KLU1m\nDglfVT6fYU0Q7O7Own59eWXrdhLyrcvJWxAFvtw4k55thtAhsFut998ryJ2J4Y15a/Ml0gqt17Hb\ncPYEuhVfo3p9PlKP2ve5aNnWhxEPh7J+5SkunrZOwX8jR9PSmbxpC3Pv7c2gZsG12rdUqaDt4nfw\nGdmXQ0OfpjAqulb7NzWCKPL90TRWnsrk42HNbSsLTURpsZa/fjpOZloh41/ohq+/u7lNqrMIOj0x\nH3xD1BNv02rWi7T7dDpy5+oFKK8K9VJoVSefYU3QrWkTpnbtzPMbN3NZbT3TDRuO/opaW8KYHjW7\nMrMqDGrpySMdfXhjU6xVii1j4iW0X32AaspMZE0CzGZH0yAPxjzRid2bLhB1yLrjvO1NSuaVbdtZ\nNKAfvQP8zWKDRCIh6IXHaD3/VY4/9ioZ63aaxY6aRqM38v7OBKKzS/lsREv83FTmNqlOkpJwmV+/\nPEiTAHcemNgJB6e6t/DHUii5mMDhoU9TfC6We3b+QsMhvWut73ontAoiUO6fYgAAIABJREFUz1Y7\nn2FNcH9IKwY3D+bFzdaREzEm7TQbjy3npeHzkcvM6y8wtJXXVbGVXmQ9YkvIzkCzaAbKSS8ja9XO\n3ObQoJELDz/ThRMHk9i/LcYqU8xsi4vnnV3/sGTwILr4+prbHBoO7kXEH4u5OGcJlz7+wSo/0yvk\nlep5dWMsDnYyPhjcDFfbysIaRxTKo7yv//0kA+9vxz39miOV2qYKTYEoCCR9/ydHRk+m6fhR5WEb\nGtRuwNd6JbREQSD6nbvLZ1gTTOkcga+zM2/v3I1gwRfkorJ8Pl//Nk8PfAdv10bmNgeoEFuhPry+\n0TrEllhciPqj6diNeBh5RA9zm3MVVw8HHn6uK4mX8ti25pxVJaNedzGGufsO8O2wIYT6VD4npKlx\naduCbpu+J2fXYU4+M8MqI8nH5ZUxZd1FegS48VovPxS2nIU1jrpMx98roog9l81jk7sR2MLm92Yq\n1CmZHB83jfTV2+m64VuaPj7KLL5v9epXlPbnJiRy2V3lM6wJJBIJ8+7rQ25ZGZ8ePmJWW26FIAp8\ntWkWXVv2p1PzPuY25zqGhliH2BI1atQfz0DeuSeK/rUTgLcqODgqeOjJCIoLNaz97SR6neXnR1x5\n9hyLjxzlp5HDCPG2vBuUsoEnnVcvwc7FmUNDnqb0kvVMzx5OKuStzXE828WXh0N9bM7YJiA1MZ9f\nlxzEzcOBcU93tkV5NxGiKJKyfC0HBz2Bxz1hdFm3FMegpmazp94ILX1RCbHzayafYU2gkMn4YvBA\ndiYk8se58+Y25z+sPfwTGl0p43q9YG5TbsrQEC8eDi2fRsywQLEl6nVoPnsPaZMAFGNqL2xJVVEo\n5YweH4ZSKeevH4+hLtOZ26Rb8mPUSX48eYpfRo4g2N1yHYZlKiVtF71FwDNjOTzyebI27TG3SbdF\nFEX+OJXFZwdSeH9AEL2CLPeztVYEQeTw7jjW/RZF3xGtuXdoK2S2KO8mQZ2WReQjr5Cy7G86r/qC\n4JcnIJWbd/q73vyn4z79qTyfYWjN5DOsCdxUKpYOHcyXxyLZl5xibnOuci75GFtP/MmU4R+Y3S/r\ndgwL8WJcBx9etzCxJQpGtF9/BEoVyiemWvzIgEwuZfCD7Wjs58bKb49SVGBZCzVEUWTJ0eP8L/oi\ny0aNoKmrdQRwbPrYSMKXf0z0u4uJmf81otHyRgy1BoEP/0lib0I+n49sQasG5nOpqKuUFGlY9dNx\nEmNzGf9Cd4JbNTC3SXUSURRJ/X0DBwdMwr1zB7pu/A7nkNpdiXwr6oXQMlU+w5rA39WVzwb25+2d\nu7iQa/74RvklOSxZP4PJQ9/Dw9nyLwjDQrwY276hxYgtURTR/vwFYnEhqsnTkcisI3q2RCqhz5BW\ntA33ZeW3R8jNLjG3SUD55/nRwUPsTEhg2agR+Dg5mdukKuHWsTXdtv5AQeQ5jj/yKrrLheY26So5\npTpe2RADwCfDWuDtaPrgufWNhJhcfv3yEE0C3Hnoqc44u9pWb5oCTUYOkY+9RtIPf9H5r88JnjYR\nqZ3lLOKo80JLFEUuzPqMoJfGo/Su2QTINUXHRj7M6NmDyZs2k1livhucUTDwxfr/t3ff0VGVWwOH\nf1PTe68kpBJqQq/SiyBeUewodkUBC2C9NhSxIWDlKkVFsYECSu+9JrQQEhKSkE56nT7n+yPKd71S\nQkgyM8n7rJU1M+HknE3KzJ637P0yw7pNoEtY8xdybSo3xflwRxc/Zq1Lp7DassmW/pdlmDPTsG+h\nqu9NrefAcAaMiOanrw6RnV5i0VhMZjNv7NxNYkERS2++CS9H21zPYuftSY8fP8K1YxT7Rz1I5QnL\nd4hILqph2uo0bgj34PnB7bAT01hNymQys3NDKhtXnWTcHV3pNyxS7CpsBpIkkffjOvYNn4x7Qkf6\nrl9slT1IrSflaybFm/c2ez/DpjA6MoK86mqm/LGBb28Zj5MFXqR/3vMFCrmSCX0fbvFrX6+b4nyQ\ngJl/pPP+2Ej8XVq+Ho1+/UqMh3bj+O95yBxsdwomLj4QF3d71q44Rr9hkXTrHdriMehNJp7fso1y\njZYl48da5O+hKcmVSmJefRK3bh04ctczxL76JEF3jLVILBtSS1l8OJ+ZN4TSK8TNIjG0ZpXlGn7/\n4Tj2jirum9ofRzFS2Cy0hcUkz3gXbUFx/RuZTtGWDumyWvXbGJNGR8or8+nw9rPN2s+wqTzYrStd\n/Hx5dtMWjC3crDYpYw+7Tv3BU+Napo9hcxgf58PELr7M/KPlR7YMuzdj2LASh+fnInN1b9FrN4eQ\ncE/ueqw3iXuz2fZ7SouWf6jV63n8j/WYJYlF48bYfJL13/zHD6XXqk/IWPgtyc+/j1nXcpsPTGaJ\nz/bn8tOJIj4cFyWSrGaQdqqQ5Z/tJ7qTHxMmJYgkqxlIkkT+LxvYN2wybt060Hf9V1adZEErT7Qy\nP12Oa9cYvG/oZelQGkQmk/HKoPpaS2/v3ttiRQ9Lqgr4Yv0bTBs/Bzcn65xebaj/TrZaqoK8MXE/\n+h++xGHWXOTe1r+uraE8vJy4+4k+lF6o4ddvE9Fpm7/Abmmdhsmr1xLq6sq8kcOxs/BuoebgEtOe\nvuu/QldUwqEJT6EtKG72a1Zpjby0IYO8Si0Lx0eLSu9NzGAwsWV1Mjs3pDLhvgR6DgxHJqYKm5wm\np4Cj987g3Kff0X3FPCJnPGQTgyitNtGqy84je8kvxL7ecv0Mm4JSLmfeyOEcLyziP4lJzX49o8nA\n/NUvMK7XJGKD45v9ei1hfJwPd3fzY8YfZ8kobd4ddKYzJ9B9NQ/752YjD2r5KbbmZu+gYsL93XHz\ncOD7RQeoLKtrtmvlVlVx76+rGdQulNduGIjCCsqwNBeVqzPxS97BZ2R/9o96kOKt+5vtWlnlGqat\nSSXSy4E3R0bgbNf6kldLupBfxfJP96PRGLjvqX4EhNj+iLa1MRuNZC36gX2jHsSjd1f6bVqKW5eW\naSDfFBSvv/7665YOAiAzM5OAgKarPn5i2lv4jx+G74j+TXbOlqJWKBga3o45u/chl8no7Nd8oyTf\nbJ+HyWRk8vBZVl+G4FpEeTvi66zinW1ZdPB1wte56YfwTVnpaOe9it2TL6GM6dTk57cWcrmM8Bgf\nJDNsWHmKwFD3Ji+0mFpSykNr/mByty48nBDfqn4XL0cmk+HZpxtuXWM59cwctPkX8OwXj0zZdFP3\n+7IreHNLFg/2CGRiFz/kbeD72lLMZonDuzLZvOY0A0ZE0X94JEqVbS67sGZVp9JIvP95NDmFJCyb\ni9/oQcispGNBQUEB7du3v+px1hFtE7uwZS+16dmEP3anpUNpNB8nJxaPH8dXScdYm5rWLNc4kLqF\no+m7eGLsG63yhW1QuAfPD2nHG1syOZTTtNvqzYW5aD94GbsHpqPs2DpGAq9EJpOR0K8doyZ04rfl\nSZxOym+ycx/NL+Chtb8zs18f7uncehPWy/Hsl0C/LV9Tdz6fA+MebZJq8mZJ4tvEAj7dl8vske0Z\nHmXbSwKsTWW5hp++OkRmWgn3TulLXHxgq3wOtSRTnZbU2Z9x5M5nCH1gAj1/WWjR6u7Xo9WNaJm0\nOhInzaLjO8/hFNmuCSKzHFc7OwaEhjBry3ZCXV1p34TVsAvKzjPvtxnMmDAPf/fgJjuvtQl0taOL\nvzNztmfh5agi3PP6R2LMZSVo5sxEfcu9qPoPa4IobYeHtxPtY7zZ9GsydbV6QsM9r+sFZltmFs9v\n3cZ7w4cxNDysyeK0NQoHe/xvHgZmiRNTZ6P2csOlY1SjvreVWiOzt2aSV6lnzphIsR6rCUmSRHJS\nPmu/T6JT92BG/KsjDo7Wv0bI1pTsOszRe2agdHIg4ev38Ozd1SoT2YaOaLW6yfrMT7/DtWMk3kN6\nWzqUJhHh4cHnN47msT/W4ahW0zc46LrPqTdo+Wj1LCYOeJwI/7gmiNK6xfo68e6Nkby8IYMavYnx\ncT6NPpdUXYX23RdQDR2HasiNTRil7fD2c+GeKX1ZvTyRtSuOMWZiF1Tqa58yWZVyhvkHDvH52DF0\n9m09mwgaSyaTETp5Ah59unL8sdco3XGIuPdmoXJteJHW1OJa3tqaxaD27jzYIxCFWJDdZDR1ejb/\nmkxpSS0TH+qJb4BtdCiwJfrSCs688THl+5OIe2cGPsP7WTqkJtGqpg7rzueT/dVPxL4x3dKhNKmO\nvj7MHzWSmZu3cLyw6LrPt3Tr+wR7t2dEN+uuLdaUwjwc+HBcFKtOFbM8saBROzql2ho0776AIr4P\n6pvuaIYobYejk5qJD/VCqVbww5cHr6ltjyRJLE46xmdHjvL1v8aLJOt/uMRG0HfDYpTuLuwbdj/l\nR05e9WskSeL3lBJe2XiOx3oH8UivIJFkNaHMtBK+XrgXVw8HJk3pK5KsJvZXyYY9g+9F5e5K/x3L\nW02SBa1s6vDk9LfwHzcE31EDmigq6xHo4kKkpyczNm+lf0gw3o6OjTrP1mOr2H9mMzNv/QiVsm3V\neHG2U3JDe3eWHikgp1JH92CXBg9HS5paNO++iCK6I+q7H7XKYeyWJpfLiIzzRac1sWHlSbz9nPHw\nunKhVrMk8cG+A2w+l8mym8cTbCN9C1uaXKXEd3h/HEICOPHkG5j1Bjx6dkZ2iZ2YWoOJebvPc/B8\nJXPGRNDJ37baFFkzg97EjnVnOLw7kxsndqFr71DkVrIQu7WoScvixJTXKd11hG6fv0nw3eNsomQD\ntMHF8MVb9lGTmkn4E3dbOpRmM6hdKC8P7M9jf6wju+LaF3en5Z3gxz2fMWPChziobbdy+fXwcFDx\nwdhI0kvqeH9nNkbz1Ue2JK0GzfsvIw+LQn3vEyLJ+i8ymYxeg8K56a5ubFx1ir1bzmK+zPdUZzTy\nwpZtHCsq4pt/jcfPuW3+Dl4LvzGD6LdpKaW7DnN44vR/1NzKrdQybU0aMmDBzTEEi156TaYwr5Jv\nP92Hps7A/dP6ExrhZemQWhVjdS1n3vyEg7dMwWdEf/puWIxbfAdLh9UsWkWiZdLqSHnlIzq89Qxy\nu9Y9SjM6MoKnevbgobW/U1Dd8L6I5TXFzF/9PI+NfpVAz7DmC9AGONspmTMmkhqdiTe3nENnvHzV\nc0mnRfvhv5EHhGB3/1MiybqMkHBPJj3Zj9ysclYuO0Jdzd+LxZZrNDy09g8MZjNLxo/D3V4kBA1l\nH+hLz58X4jWwB/tGTKZg9RYkSWJPZgXPrD3LzXE+zLyhHfaiX2GTMBpM7NqYxsplR+k7JIJxd3TF\n3sE2RlhswV/ThLsH3oWhrJIBO5YT9sjtVtUEuqnJpJYqP34VW7duJSEhoVFfm/HRMiqPp5Cw7N0m\njsp6LT12nF9On+HbW8bj6XDlnXRGk4HZPzxO57De3Nb/0RaK0PoZzRIf7MymuFbPmyMjcPqfBd2S\nXo/2o1eRubpj99hMZDbamqglmU1m9m5N53RSPuPu7EpQOw+yKip4/I/1jIpoz/TevUQtp+tQkZjM\nyafnUOzhzfobb2fmhHiifRq3jED4p9yscjauOomPnwvDxsfhZIGeqa1Z1ak0Ul7+CJNWR9ycZ3Hv\nbtvlXBITExk27Oo7z60qhZR0WmR21/ZOV5NTSNZ/fqDfpqXNFJV1eqBbV6p1eh5Zu45lN4/Dxe7y\nTwjfbPsQZ3tXJvSzvWbRzUkplzFrcDu+OJDH02vSeHNkewJc67+PktGAduGbyBydsHtUJFkNJVfI\nGTgymqBQd1Z/l0RAV2/mFR1jep+e3BbXOqcFWpIpNprVz7xM9Pq13LHwbRzdpiDdMVaMtF4nvc7I\n7k1ppJ0qYthNHYju5G/pkFoVfXkV6e99SeHabUS98CjBd41Dpmg7z6lWNdas/eI9pGtspnzmtQW0\ne/h2HEKarqq8rZjaqwcJAf48sW4DGoPhksdsP7GaU9mHeXLcm8hlVvXjtgpymYwpfYO5Kc6bp9em\ncaKgGsloRPvJ26BQYvfEi23qCaGptI/1xW+UH0eOZPOAKZKb2kdaOiSbdziniid/O0PXdh48vGgW\nvX6cz/nFv3DkrmfQ5BRaOjyblXW2hGUL96LXmZg8vb9IspqQZDKRs3w1ewbdDZLEgN0rCLn35jb3\nnGpVr7xSVTn6X5Y1+PjibQeoSk4n/Ml7mi8oKyaTyXhxQD9CXF2Zun7TP5Kt9IJTfL9zIc/d8gGO\ndi4WitI2jI/zYdbgdry1NYvfv/wOjAbsn3oJWStsatzcJEnik0NH+OL0ce57oi+hfu58++l+LuRX\nWTo0m6Q3mvniQC7z95znxSFhTEoIQC6T4dopmj7rvsKzXwL7Rj1A9tKV1/xGtS3TagxsWHmSTb+e\nYsTNcYy5rTMOjq17jW9LqkhMZv/YR8j7aT09Vswjbu4M1B5tc5dxo9doZWdns2TJEuRyOWq1mqlT\np+Ls/PdtxSUlJXz++eeYzWaMRiMTJ06kS5culzzf1q1biY+KoO71aahvvhvVoFFXvL5Zp2fP4HuJ\nnT0d3+G218+wKRnNZl7ZtoO86mo+HzsGZ7WaitpSXv5mEpOHz6Rn1BBLh2gTJLOJjP98xluyrvTp\nFMYjfUNELaJrpDeZeGX7TrIrKvn0xlEXy5CkHMtn2+8pDBodQ+cerbcTQVPLLtfwzvYsglztmT4g\nBFf7Syf+NWezOfXsHGQKOZ0+fBGniNbX4LwpnT1dxNY1p4mM82PQqGjUotF2k6nLyiVtziLKDx0n\n5pUpBNw6qtVObTd0jVaj62i99957PPfcc4wePRovLy9WrVpF795/r8a+bds2+vfvz8SJE+nRowef\nffYZw4cPv+T5MjMzCQwLR9m5O7rP5qJoH4Pc5/JDuOc+XY5ZZyDquYcaE36rIpfJGBoexqkLxSxO\nOs7QdiEsXD2DntGDGRk/0dLh2QTJbEa3ZAEuxTmMfOge1p6tZFdmBb1D3VCLujkNUq7R8Pgf67FT\nKPhkzCjc/mtnoY+/C+1jfdnxewoFOZWEtPcUDXiv4K8CpO/vOs/d3fx5sGcA9lf4fqm93Am640ZM\ndRpOTJsNgFtC3CXrbrVltdU6Nqw6RUpSPmMmdiG+TygKsVuzSehKykh763NS/j0fv9ED6fLZ67h1\n7dBqkyxo5jpaeXl5BAQE4PFn7724uDgKC/+5RmDcuHHExdW3eNHr9Xh6Xr2xqTwwFLspL6L95G3M\nhbmXPEaTU0jWFyvoMLt1VYC/HnKZjFcHDaB7gD8TVnwDSldu6/+YpcOyCZIkof/mU8x557F/7i3c\nXJ14e3QEfs5qnl6TRkGV7uonaeOyKyq5a9VvxPv7MW/UCBxU/9wO7+3rzL1P9kVtr+TrhXvJTCu+\nxJmESq2R1zdnsiG1lHnjohgd49WgFyuZQkG7h2+n7/qvKN1xiAPjHqUq+WwLRGz9zGaJE4dz+Prj\nvbh5OHDftP6EhItG203BWFtH+odL2DPoHmQKOQN3ryDi6ckoHa+/r2xrccXx0oqKChYsWPCPz3fs\n2BFX17/PtSqVSkwmE4pLLHK7cOECixYt4rHHGvbCr+yUgHTr/Wg+eAXH1xcic/77tVJenU+7hybi\n2O76+/61JjKZjF6uxewynuOgoSfFtRpRFPIqJElCv/xzTOdScXjhXWT29U8OSrmMqf1DWHO6mKfX\npvHy0DC6BIh1bpdyNL+AZzZuZmrvnky8ys5ClVrJ8PFxRMX5sXHVScKivBl8Y6yYuvlTYl4VH+w8\nz5BID14ZFoaqEaOpju2C6PHTAnK/X8uRO57Gb+xgomY+jNq76ZrS25KCnAq2rjmNXCHn1sk98Ats\nm+uEmprZYCT3+7VkzFuKZ794+q7/SrwmX8YVn93c3d157bXX/vH5/Px8Vq5c+bfPGQyGSyZZGRkZ\nrFixgilTpuDl1fDKuqqhYzEX5KJd8Cb2z7+DTFn/Drlo3U5q0rLo+vkbDT5XW3GuMIXvdizg87v+\nw6acaib9tpol48cR7CqeWC5FMpvQLVmAOTcLh+fnInP8Z1I6Ps6HIDc7Zm/N4qGegYyOEdWh/yJJ\nEj8mn+aTw0d4b/gw+oU0fO1Vu0gv7p82gB3rzrBs4V7G3NqZkPZtd4TBYDKz7GgB29PLmXFDKAlB\n1/c3K5PJCLlnPH43Dibjw8XsvuEe2k+dRLsHb7OZ9ibXq65Gx66NaWSmlTBodDRx3QJb9TRWS5Ek\niaI/dpD2ziIcAn1J+OY93LrGWjosq9aoNVouLi6sWbOGhIQEHBwcSElJoaSkhF69ev3tuCNHjrBh\nwwamT5/+jxGw/3WpXoeKTvEYj+7DdPo4ioS+mGrqSLxvJp3nv4RTeMi1ht2qVdaW8fZPT/DA8FnE\nhXYnIcAfhUzGqzt2MSg0FA8HUYn7v0lGI7pF7yOVFuMwc84lk6y/BLra0TfUjU/25VJYrSM+0KXN\nF93UGo28vnM32zKz+Wr82EY1hlYq5UR28MXDy5ENK09SVaElOMwTRRtbE5dToeWVjRkYzfD26AjC\nPZtuykXhYIfP0L74juhPzje/kbHgaxxCA3BsH9Jqkw6zyUzSgfOsXXGMwFB3xt8dT0CIe6v9/7ak\nsv3HOP7Yq5TtPUqHN6YROfNh7P19LB2WxTR0jVajdx1mZWWxePFiFAoFarWaadOm4ezsTGJiInq9\nnj59+vDggw8SEhKC/M8FmXK5nH//+9+XPN/lKsNLWg2a2c+g7DuE9H15mDRaOn/0UmNCbrWMJgNz\nfnqS6KCu3Dnoyb/9269nUpl/4BD/GXcjMd5iNAZAMujRfjIHTAbsp72KTN2w6s9VWiNvb8tCIYdZ\nN7TDvY225cirqmb6xk2Eubnx5pAbcLzEeqxrpanTs21tCoV5lYy5rTOBoa1/msssSaw5XcJ3SYXc\nl+DPuA7ezZ4MFG/dz5nXF2If6Efsm9Nwibn6i4Qtyc0qY+uaFOwdVQy7qQPefmK6vymU7T9GxoJl\n1GWcJ+r5RwmYMFJstKDhuw5togWPuayY2pemkH6qlo5rf2iztTguRZIkFm+eS3FlPs/fOh/5JSqY\nrz+bzpw9+/hs7OhGjTy0JpJOi3b+62DviP2TL16ckm4oo1li2ZF8tqWXM2twO7oFtq0n8n05ubyw\nZRsPJXTjvi6dmzwxSDtVyJY1p+mUEES/4VEoW+mOsPPlWubtOY8ceGZgKCHuLTfibDYYyfl6FRkf\nfY3/+KFEznwYtadbi12/OdRUadm5IZXczHIGj4khurO/GMG6TpIkUbrzEBnzl6ErLCF86iSCJo5p\nM1PPDWGTLXguy8WDtAw5UdFKFOWFIBKti9Yd+Y60vGO8fvfiSyZZAGOiIrFXqXjij/UsGD2S7gFt\nr4o+gKSpRfPBv5H7+GH3yIxGVSdWymU83CuI+CAX5u7IYmSUF/d1D0DZyuttSZLEV0nHWH7iFB+M\nHE6voMBmuU50J3+CwzzY/Ntpln+6jzG3dcYvyLaTgP9mNEv8fKKIlScvcF/3AMZ18G7xaWi5Skm7\nh28nYMIo0j9YzJ6Bd9P+6fsJnTzB5hr7moxmEvdnc2jnObr0DOGBpzuKjRXXSTKbubBpDxnzl2Gq\n0xIx/X78bx6GXBRvbjSbGNE69/G3lO1LpOuzt6L/5lMc3liI3LPtzgv/5VDadpZueZfZ9y7F2/Xq\nydP+nFxmbtl6zQuXWwOppgrN+y8jD4vE7v6pTTLsXaEx8P7ObGr0Jl4cEoZ/K21AW6PX8/K2HRTV\n1jJ/1Aj8/6cwcXOQJImU4wVs/+MMMZ396T880uardp8tqWPe7vN4OCiZ3j8UPxfr+P9Up57jzGsL\n0eYWEvHsg/iPH2r1L6qSWeLMiQL2bknHw9uRIeM64OktdlhfD8lkonDtNjIWfINcqaD905PxGzNI\nTBFeQauZOqzNzOXA2Efou2ExjqGB6P/4CePebTi8+tHFrfhtUUZBMnN/mcaLEz+hvX/Dm/UeLShg\n+oZNzOrXl/Ex0c0YofUwV5ajffcFFJ0SUN/1aJNOKZgliV9PFfPD8SKe7BfM4Pata23RufJypq3f\nRI+gAF4a0B91C/co09Tp2bv5LGnJRfQdGknXnsHIbWyxvM5oZnlSIRtTS3mkdxDDIz2sblpLkiRK\ndx0m46Ol6IpKaT/9PgJvHW11I1ySJHEutZg9m86iUMoZNCqa0Aix9vR6mA1GClZtJGPht6g9XIl4\nejLew/pa3e+oNWoViZYkSRy5fTreQ/oQPuXui5/TLZmPVJSP/Yy3GryQuTUprizg1e8e4KERL9Aj\navA1f316WRlT1m1gdEQET/fp1ap30JnLitG88zyqvoNR3TKp2Z480krqmLMtiy4BzkzpE3TFKt62\nYsu5TF7fuYtn+vTm1g6W3b5dXFDNtj9S0NTpGTYuzmZKQZwqrGHe7vOEezrwVL9gPGxgA0XZ/mNk\nfLSUuswcwp+8l6A7x6Kwt/zzbE5mGbs3pqHTGRk4IoqIDr4iGbgOxupa8n5eT9bn3+PQLoiIZybj\n2S9BfE+vQatItPJ+3kDWohX03bD4b0PZktmE7ov3kKorsX/mTWRq6xiCbwl1umpe++5hBncez9ie\njW+mXa7R8PTGzTir1bw3fChOrfB7aL5QgOadWaiG34R67O3Nfr06vYlP9+dy5kItLw0NI8LLsdmv\n2RxMZjMLDx3h97SzLBg9kk6+1jFNL0kSaclF7FyXin+wKzeMicXNwzpHtev0JpYcyWdvViVP9g1m\nQLi7pUO6ZhVHT5Ex/2uqTqUR/sTdhNx7MwrHli8TU5RfxZ5NaZQW19J/eCQdugYib+VrIptTdeo5\ncpauouC3zXgO6E7Y43fh0aOzpcOySTafaOlLK9hzwz10//YD3OL/OTUmmUzoPpuDpNNh//Rr17x7\nzBYZTQbeXfk0AR4hPDD8+et+56E3mZizey9JhUV8euOoVlXY1Jx/Hs3cF1CPvxPV8PEteu2t6WV8\ncSCPe+L9uTmu+bfsN6Xcqipe2LIdO6WC90cMw9PB+hIZg8HE4d20Xun0AAAgAElEQVSZJO7NJr5P\nKL1uCEelto4pLkmSOJhTxSf7cogPdOHR3kG42Pji7KqTaWQsWEb5wROEPXYHoZMnoGyBjhNlJbXs\n3XyW3Kxy+gxuT5eeIaIvYSOZDUYurN/F+WUrqc3IIfje8YTcezP2AdbxJspW2XyidWLabFTurnR4\n8/L9DCWjEe3HbwFgP/UVZFa+gPN6SJLEl5vepqz6AjMnzEMhb5r/qyRJfH8qmUVHk5g3cjg9Am1/\nR6IpPQXt/NdR3/EQqoEjLRJDXqWOd7Zn4emo5OkBoXg6WvcbAUmSWJN2lvf27ufhhG7c37WL1U8p\nV1Vo2LUhjbzscm4YHUNMF8tu6c8q17DoQB4XavRM6RdM9+us7m5tqlPPcW7BN5TsPES7B24l9MHb\nmqUsRHWllv3b0jmbXESPAWEk9GtnNYm0rdEWFpO7fA05y1fjGB5M6AO34jfmBqtbe2erbDrRKt19\nhJPPvM2And+hdLry9ItkNKCd/wYyOzvsprzUqC37tmDtoW/YnbyON+5ZjIO66d9N7s/JZdaWbUxr\nQL86a2Y8tAvt0oXYPzoDZXwfi8ZiMJn5NrGQ9amlTErwZ2ysNwornPKo0Gp5c9ce0svKeG/4MGJt\nrLBtblYZ29amoFIrGTQ6mqB2LbshoVJr5JujBezKrOCeeD/GdfBp1eU+as/lcO7jbyn6Ywc+Q/sQ\nfO/4+rU917k7raSwmsN7sshIuUCXnsH0HBRu8ztNLUGSJMr3J5G9dCWlu44QcMtwQidPwCU2wtKh\ntTo2m2iZNDr2DrmX2NlP4zuif4O+VtLr0X70KjIXd+wen4nsMvWkbNWB1C18s/VDZk9ahpeLX7Nd\nJ6uigifXbaR/SDCz+vdFaUPbeiVJwvDHTxg2rcb+2TdRhEVaOqSLsso1fLw3F63RxPT+oUT7WM/a\nrf25eby8bTsj2rfn2T69sLPRUWGzWSI5MY8D2zNwdXeg9+D2tIv0atYRLoPJzJrTJfxwvIjB7T2Y\nlOCPq71tfv8aw1BRRf6qTeQuX4OpTkPQ3TcRdMeN2Pt5N/gckiRxPqOUw7uzKC6sJr5vKF17hYgE\nqxHqzudTuHor+T9vQEIi9IFbCbptNEoXUfaiudhsopX2ziLqzuXQ7cu3runrJb0O7QevIPP2xe7h\n51pN7Y+z+Sd5b+XTvDTxE8KvoYxDY1XpdMzYtAWzBB+OHI6bFew2uhrJaET39ceYM1KxnzHbKmus\nSZLE5rNlLD6cz8BwdyZ3D8DZgmt3dEYjCw4eZn16Bm8NvYH+Ia2jd6jZZObMiUIO7jyHSiWn9+AI\nIjv4ImvCESZJkjh4vopFB/MIdLXjsd5BhHq03V6ikiRReSyF3OVrKPx9O5794gm+5yZ8hvS57AyD\nyWjmzMkCjuzJwmyS6DEgjA7dAlttJ4Dmoi0opnDtNgp+20Jddh7+44YQ8K8RePTpalNrQ22VTSZa\nUQ5uHLptKv23fXNN74r+Imk19UUpA0Owe2C6zSdbxZX5vLr8AR4Z9QoJkQNb7LpGs5kP9h1g1/nz\nfHbjaMLcrXfHlFRXi3bhbFAqsH/yZWQO1jNadClVWiNLj+Sz/3wlj/QKYmhEy9dUSistZdaWbbRz\nc+ONwYNwt299SYJklkhPucCBHRkYDWZ639Ce2C7+112DK6tMwxcH8yiu1fNY72B6hbSudVjXy1hT\nS8HqreQuX4OuqISgu8YRfOc4HEL8AdBqDJw4nEPivmw8fZzoMSCc8Gjb2jBiafqScgp/307B6i3U\npGTgO3oQATcPx3NAd7H2qoXZZKKl//cXBN1xIyH3/avR55E0dWjeexFFWCTq+56y2T/gWm01r333\nIMO6TWBM97ssEsPKlDPMP3CI2UNuYHBYO4vEcCXmkiK0H7yCokMX1PdOsan1eSkXalm4NwcXOwVP\n9QshtAV63ZklieUnTrLoaBLP9e3NLbExNvv30VCSJJGdXsqB7RlUV2rpdUN7OsYHorzGOmcVGgPf\nJhayK7OCe+P9GdvBu1Wvw2oK1afTyVm+hoJfN+EYG0lteAfSFX6E9Iyix4Bw/AJFktpQhspqitbv\npOC3LVQmnsZnWF8C/jUc78G9kduJaVZLsc1E6/X/0Pu3z657JEqqq0Uz93kUMZ1Q3/2Yzb2Y6A1a\n5v4ynRCfCB4YPsuisSQWFPL8lm30DwlmZv++OKmsY/ec6Vwq2o9eQzX2dlSjbrG5nzGAySyx5nQx\n3yUVMraDN3d188e+maZOimpqeWnbdjRGI3OHDSHUrfX0D2yo3KxyDu7IoLiwmh4DwujSM+SqffEq\nNAZWnSpm3ZkShkZ6cm9821qHdT2MBhMZKRdIPpRF5Z7DBNfkIDudjL2vJ74j+uMzoj/u3Tva1Buk\nliKZTFSdOkvpniOU7j5CxZFTeA/qif+/huMzvB9KR+sru9IW2WSiFe3kgXNMeJOcT6qtRvPOLBSd\nuqO+4yGbeSE2mgx8+OsMHO2ceXLsm5dtFN2SavR65u7Zx+H8AuYMG2zxptTGI3vQLp6P/cPPouze\nz6KxNIXSWgNfHMwltbiOR3sF0S/MrclKKxjNZr4/eYovjiZxT+eOPNY9waY2OTSHovwqDu7I4HxG\nGVEd/eiYEEhQu79P4RbX6vnlxAW2pJdxQ3sPbu/i22p7WTYlSZLIy67gdFIeaaeK8A10pWN8IFEd\n/VDbKZFMJiqPpXBh816KN+1FW1SCz9A++I4YgNfgXqhcm7+PpjWSJIm6zFxKdx+hdPdhyvYmovZy\nx2tgT7wG9sBzQPc2+72xZjaZaF2uqXRjSdVVaObMQBHfB/XEB6w+2TKZjSxc8xJmycT08XNRKqxj\n9Ogv2zKzeGPnbm6KjmJqrx4tvkNNkiQM63/BsH4V9s++gSK8dfVqPJpXxZJD+RjNEnfH+zMgzP26\nykEkFhQye9duPOzteXnQACI8WlcfxutVU6Xl9LF8khPzMRpMdEwIwjvKm3XZVezOqmBklBe3dfbF\ny8m6/g6tUUVZHaeT8klOykOpkBOXEESHrgG4ul955EWTW0jxln1c2LyX8oPHcevWAd8R/XHv1QWX\nDhFW0fqnuegulP6ZWNV/SCYTXgN71CdXA7pjH+hr6RCFqxCJ1p/MleVoP/w38oBg7B5+FpnKOuez\nzZKZz/94jSpNOTNumYdKaZ1xlmk0vLFzN1kVlcwdPoQO3te+aaExJJMJ3TefYE5Lxv65t5B7t84n\nIUmSOJRTxXdJhdQaTNzdzZ/B7T2uKeEqrdMw78BB9ubkMKtfX8ZERlj9mwxLkiSJY2eK2bwjE1N+\nJQ7uDvTtF0q3+EDs7EWSdTlajYG0U4UkJ+ZTVlJLbBd/OsYH4Rfk2qjfN2OdhrLdR7iwZR+Viaep\nPXcep4hQ3LrE4to1FrcusbjERdjcmiRJktDmFVGdkkF1SgY1p9OpSj6L7kIZnv3iLyZXTpGh4u/U\nxohE679IOi3aL95DqirH4enXkblY1/oUSZL4atMc8suyeOG2hdiprHv+XZIkfk87y7v79jOpS2ce\niu/WrNNR5rJitJ++g8zOHvupLyNzaP11YSRJIjG/mu+TCimtM3JXNz+GRXpecQG2yWzm59MpfHL4\nCDdFR/Fkzx44t8Ielk0praSOFccKSS6sZUInH26M8aIos4zkxDzOZ5TRPtaHjvFBtIv0Ev31qB+5\nyjpbQtbZEs5nlBEW6UVcQhDhUd5N3h7HpNFRnZJO1fFUKo+nUHUildrMHJwj2+HaJRa3rrG4donB\nOTrcIj0YL8VQVUPNmXNUn06n+kwGNSnnqE7JQGFvh3NcBC4dInDpEIlLhwicO7T/Ww9fwfaIROt/\nSGYz+p+XYjy0C4cZbyEPsI66QZIk8e32eaTlneDlOz5rlqrvzaWguoaXt++gzmBg7rAhzVIGwnjs\nILovP0Q18l+obrrT5kt2NMaJgmqWJxVSUKXnzq5+jIj2RP0/ZQpOXrjA7J17UCsVvDpoANFetlXd\nvSWZzBJH86r4LbmY7HItt3X2ZUys9z82ItTV6kk9UUByYh7VVTrCorwJjfAktL0XLm7W8cLe3PQ6\nI+fPldUnV2klGAwmwiK9aBflTXi0d4sXFjVpdFSfPkvl8TMXE7C6zFzk9nbY+/tg5+998fa/79v7\n+6D28WhUYiNJEsbqWvQl5ehLytEVl9Xf//NWV1p/X5NbiKGsCufoMFziInHuEIFLXAQusRGovay3\nRI7QeCLRugzDzg3of1yM3VMvo4zr1uzXu5ofd39GUsYeXrnzC5ztbW+7s1mSWHEqmU8PH+Wpnt25\ns1PHJlnILRmN9Ynx/u3YT3kRRazoLp9cVMP3SYVklWu5vYsfo2O80Bj1zD94iG2Z2TzXtzfjo6PE\n9MNl5FZq2ZhWxpazZXg7qRgb683QSI9/JK2XUl5SS3ZGKefTSzl/rgxHJzWhEZ6ERHgR2t6z1VQy\nl8wSRflVF0etivKrCAx1p12kN+FR3nj7O1vd75ckSRjKq9AVlaAtKL7kra6wBH1ZBSpXF2QqRf0b\nNrms/v8ik9cXtJX/eSuTIZPJQSbDWFOfYMlUSuy8PVB7e6D28UTt7fG3x3beHtgF+OIYGiB2UbYh\nItG6AuPpY+g+eRv1HQ+jumFUi1zzUn47sJTdyX/w2l1f4upo2wuVM8sreGHrdpxUSmb173dd/fLM\nJUVoP3kbmZML9o/PsrqpXktLLa7lu6RCjhdUUW0spleIMy8OSsCtFRYevV61ehO7zpWz8WwZBVU6\nhkd6MiLakzCPxk/PS2aJ4sLq+sTrXBl5WeW4ezoQGuFFaIQXwWEeVy0bYQ0kSaK6UktJYTXFRTVc\nyK/ifEYpjs52hEV5ERblTXCYJyp160gczEYjhrJKJJMZyWwGs4QkmUGSkMwSmM3/f4sEZgmlsxNq\nL3ermZoUrItItK7CnH8ezYf/RtlrUP2OxBaeklp/dAUbjv7Aa3d/haez9bWMaQyj2cwPp07zn6OJ\n9A0JZlqvngS5ulzbOY7uQ7f4I1RjJ6Iac1ubnCq8EqPZzB9n01l0NBFXtQs9fGM4nq/DXiVnWKQn\nQyM88HVuHaMrjWWWJE4W1rAprYx92ZV0DXBmVLQXPUNcm6XIqMlkpjC3kvPnyjifUUphbiVung54\neDnh4e2Ep7cj7l71tw5OaouMCOm0BooLaygpqq5PrAqrKSmqQalS4O3njI+/Cz7+LoS097zqTkFB\nEOqJRKsBpOpKNB+9hszNs37kxK5l3rVsP/EbK/d9yWt3fYWPm2VrUjWHWr2eZcdPsPzEKcbHRPNY\n93g8Ha785C0ZDehXfInx6D7sn3wJRVRcC0VrGwwmE2vTzrLoaBL+zk5M6dmdXoGByGQyJEkiuaiW\nrell7M6sINzTgWGRngwIc7NoP8WWVlitY2t6OZvSSrFTyhkV7cWwSA/cHVp256DBYKKsuJbykr8+\n6igvraWsuBYADy9HPLzrkzAPL0fcPB1QqZQo1XKUSgVKlRylSoFSKb9iUmY2mdFqjWjrDGjq9Gg1\nBjR1BrR1BrR/Pq6sqB+x0moMePnWJ1R/JVbe/i44OrXtpFwQrodItBpIMujRfTkPc2Eu9s++idzd\ns1mvt+f0er7bsYBX71xEgKf1tbVpSiV1dSw6msjvaenc17Uz93XtcsnK8uYLBWg/fguZpzf2jzyH\nzNn21qo1F73JxG9nUvky8Rihbq480aM7PQIvn5zrTWYO51SxNb2MpPwauge5MCzSkx7BLqius8+f\ntanVmziWX01iXv1Hrd7EwHB3RkV7EeXtYJVriTR1BipKaykrqatPwkrrqCrXYDCYMBpMGA3mP29N\nmMwSSuX/J18qlQKFSo5Bb0JbZ0CvN2Fnp8TBUYW9owp7RzUODn/dV+HgoMLZ1R5vf2fcPRybtLG2\nIAgi0bomkiRh+G05hp0bsX9uNoqQpqlO/78OpW1n8eZ3eOX2zwjxiWyWa1ij85WVfHzoCIfy8nm8\nRwK3dYhF9eeCUeOhXeiWfYxq/F0220qnOeiMRladSeWrxGNEenrwePcE4gP8r+kc1TojuzMr2Jpe\nxvkKHfGBLnT0c6KjnxPhng7XVQzVEoxmidQLtSTmV3M0r5rMMg0dfJ3oHuRCQpAr4Z72TVZR3xqY\nzRImownDxeTLjNFoQqVWYO+gwt5eJZInQbAgkWg1gmHfNvTLP0d91yMoB4xo0hf9/Wc2s3TLu7xw\n28e09+/QZOe1JaeLi/nowCFyqqqY1rULgw9sQjqdhP1Tr6Bo37qqvDeW1mjk59MpLEk6TgdvLx7v\n0Z0uftdfnLWoWs+xgmqSi2pJLqqhtNZArI8THf2diPNzooOPE45WtujZLEnkVepI+nPU6nhBDf4u\nahKCXOge5EJHP2fsmqk3pCAIwtWIRKuRTNnp6BZ9gMzTG7sHn0buef2Vz9cfXcHag9/w/G0LaOfb\nthMKSZLYs3Et85NTwdGZBwYOYkRsTIu387E2OZVVrElL48fkFLr6+fJ49wQ6+jbfJokqrZHTF2pJ\nLqrldFENZ0s0BLraXRzxivZxxNtRhb2q+ZOvWr2JnAotuZU6ciu15FTqyK3Qkl+lw91BRdcAZ7oH\nuxAf6NLi660EQRAuRyRa10EyGjCsWYFhy9rrGt2SJIkVuz7hyNkdvDjxY3zcApshWtthzstGt2wh\nkqYO1f1T2S6356fkFJKLi7kxKpIJsTHE+Xi3menDKp2OjRnnWJ2aRlZFBWMiI7mtQywx11Eao7EM\nJjPppRpO/znilVGqobTOgFIuw9NRhZejCk9HFZ4OKryclH/e1j/2cKhPkg1mCb1RwmA2ozdJGEz/\nf2swSRfvl2uM5FTWJ1Y5FVo0BjPBbnYEu9kT4m538X6wmx0OLZDoCYIgNIZItJrA/49ueWH34DPX\nNLplNBn4z4a3yC/LYtat822+Ttb1kLQa9L99h2HHetQTJqEadtPfivrlV1fz25k0fj2TipNaxYTY\nGMZFR111p6ItMphM7M3JZXVqGntzcukXHMT4mGgGhIagtrJCh5IkUas3UVpnoKzOSJnGQGmtof62\nzkBZnYHSOiPlGgMyQK2Qo1LILt7+7b5cjlpZf+vuoCTYzY4QN3uC3e3wdlS1meRaEITWQyRaTaR+\ndOsHDFvWoL7zYZQDR171RUGrr+Oj1c8jl8uZftNc7NWtL2FoCEmSMCXuR/ftZyiiO6K+61HkHpcf\nrTFLEofz81mVksqOrGz6BgcxoUMs/UKCm7WXYnOTJImUkhJWp55l3dl0Qt1cGR8TzeiICNzs7Swd\nniAIgtAIItFqYqbsDHT/eR+Zx5VHt6rqynn3l+mE+ETyyKiXUMjb5toj84UCdN9+hrkwD7vJU1F2\njL+mr6/W6ViXnsGvZ1IprKnh5phoRkdGEO3picIGki6t0cipC8UcyS9gXXo6GoORm2OiuSk6inbu\notK9IAiCrROJVjOQjEYMa1dg2LwG9R0Poxz099Gtoopc3vl5Kv1iRzJxwONtcjpEMugxrPsF/fqV\nqMfciurG25Cprq8oYnpZGavOpLI9M5tSjYbOvj508/ejm78/Xf18cbWz/KhQSV0dSYVFJBUUklhY\nyNnSMiI9PYj392dE+3DiA/xbVekBQRCEtk4kWs3IlJ2B7ssPkLl5YvfANOTefmQWpvDeqme4pe9D\njIyfaOkQW5xkNGI8uBP9b98h9w/CbtIU5L5NX/W+XKPheNEFkgqLOF5YxKniYgKcnenm70e8vx9d\n/f0Id3dv1qTGLElklJWTVFh4Mbmq0Gnp6udHQoA/8f7+dPb1weESxVkFQRCE1kEkWs1MMhox/P4j\n+g0rqY6O4lPTUcbf/BK9oodaOrQWJWk1GHasx7B+JXJff1Tj7kTRpUeLjeYZzWbSSks5VlhU/1FU\nRLVOTydfH3wcHXG1s8PVzg43+z9v//xwvfihRqVQ1C/8Nhgo1Wgo12j/vNVQptFQqtFSrtFQ+ufj\ngpoa3Ozsiff3Iz7AnwR/fyI8PcSIlSAIQhvS0ESrbS4gagIypRL1v+4hMcSRnJ8/Y2aFC+r1uzEp\ng1C0j7F0eM3OXFGGYdNvGLavQxnXFfvpr1rk/62Uy4nz8SHOx4e7O3cCoLiujuQLxZRpNFTp9FTq\ntJwrr6BKp6NSq6VSp6+/r9NRrdNhp1RiNJtRymR4Ojrg6eCAp719/a2DAwHOznTy8cbDwQEvBwf8\nnJ1a5Y5IQRAEoemJRKuRzGYTvx5Ywrbjv/HCc5/j6hKEYecGtAveQO4fjGr8XSjiurW6dVrmghz0\n637BeGg3qr5DcHx9IXI/66oP5uPoyOCwhvWR/GskSyGTiak+QRAEocmJRKsRSquL+PT3fyOTyZh9\n71I8XepbpKhH3YJq2E0Y929D9/UnyOwdUN90J4ru/ZDZwE65KzGdPY3+958wn01GNfwmnN5fgszV\n3dJhXTeZTIaz+voW6wuCIAjC5YhE6xodStvO4k1zGNPjbsb3ug+5/O9FJmVKJaqBI1H2H44pcT/6\ntT8g/bQE9bg7UPYfikxpO6MmUnUlxhOHMWz9HamiDNWYW7Gf8gIyO3tLhyYIgiAINkEkWg2kM2j4\ndvt8TmTuZ8aEeUQFdr7i8TK5HGWP/ii698OUchzDmh/Q/7wERddeKDp3R9kxAZmLawtF3zCSJGHO\nzsB07CDGYwcx52WjiItHNfIWlD0H/K2auyAIgiAIVycSrQbIvnCWj9e+RDvfaOZO/g5HO5cGf61M\nJkMZ1w1lXDfMBbkYTx7FuHcruq8+Qh4QXJ90de6OPCrOIqNdkqYOU3IixmOHMB07CPYOKLv1Rn3r\n/ShiO193DSxBEARBaMtEonUFkiSxMfFHVu77kklDnmFgx7HXtbhdHhCMOiAYRt6MZDRgPnsa48mj\n6L7/D+bCXBSxXVB07oGyc3dk/kHNspBe0mkxXyjAdCoR07GDmDLOoIiMQ9GtF+pxtyP3D27yawqC\nIAhCWyUSrcuoqivni/VvUFFbyux7l+HvEdKk55cpVSg6dEXRoSvc/mD9eqjkJEwnj6L5/UeQy5EH\nhiBzdkPm4orMxe3Pj/r7OP/5OWdXZMr6H6Ok1yGVFWMuK0EqLa6/X3oBqazkz/vFoNci8/JF0aEr\nqpH/wr5jPDJ7UapAEARBEJqDSLQu4VT2IT774zUGxI3h2X+9j1LR/FN6Mhc3VH0Go+ozGEmSkApy\nMBcXIlVXIVVXIlVXYs5KR6qpgprK//98TRWo7UChBK0GmYcXMi9f5J7eyDx9kIeEI+/aq/6+lw+4\nuLW6khOCIAiCYK1EovVf6nTVrNr3FXtTNvLEja/TJayPReKQyWTIAkORB4Ze9VjJbAZNHZLRUD/C\nZeNlJARBEAShNRGJFqDVa9iY9CO/H/qWhIiBvDt5Ba6OHpYOq0Fkcjk4OSPGqARBEATB+rTpRMtg\n1LPtxK/8tn8JMcHdeP3urwjyCrd0WIIgCIIgtBJtMtEymY3sSV7PL3sXEewdwazbFhDuF2vpsARB\nEARBaGXaVKJllswcSt3KT3u+wNXRgyfHzSY2ON7SYQmCIAiC0Eq1iURLkiSOZ+7jh92fIUfG/cNm\n0CWsj9h9JwiCIAhCs2rViVZVXTlJ5/aw/cRvVGsquWPgFHpGDREJVjNITU0lJibG0mEIjSR+frZL\n/Oxsm/j5tX6tKtGSJImcknSOpu8mKWM3OSXn6BzWi5Hxt9MnZvg/GkALTSctLU08Wdgw8fOzXeJn\nZ9vEz6/1a3SilZ2dzZIlS5DL5ajVaqZOnYqzs/Mlj921axdLly7liy++wM7OrtHBXorBqOd0zlGO\npu8iKWM3yGR0jxjEbf0fpUNId1RK0atPEARBEATLaHSitWjRImbOnImHhwenT59m6dKlTJ069R/H\nZWVlkZeXR1hYGJIkNTpQSZLQG7XU6Wqo09WQlneCxIzdnMo+TIhPBN0jBjLrtgUEe7UXU4OCIAiC\nIFiFRiVaeXl5BAQE4OFRX9QzLi6O77777h/H1dTUsHbtWqZMmcJbb7111QRoyea51Olq0OhqqdPX\n/P99XQ0afS1yuQJHtRMOds60842mZ9RgHhn1ss0UFxUEQRAEoW25YqJVUVHBggUL/vH5jh074urq\n+vcTKZWYTCYUivp1UGazma+//pp77rnn4ueuNqIV5BWOg9oJRztnHO2cL953+PNxS/QcFARBEARB\naCoyqRHzefn5+axcufJvU4UvvfQSc+bMufi4rKyMefPm4ebmBsCZM2eIiYnhwQcfxNvb+x/n3Lp1\na2PiFwRBEARBsIhhw4Zd9ZhGTR0GBgZSWFhIeXk5Hh4epKSkEBgY+LdjPD09eeutty4+fuONN5g+\nffplF8M3JFhBEARBEARb0ujF8I888gjz5s1DoVCgVquZNm0aAImJiej1evr06dNkQQqCIAiCINii\nRk0dCoIgCIIgCFcnt3QAgiAIgiAIrZVItARBEARBEJqJSLQEQRAEQRCaiVX0OlyzZg2HDx8GICEh\ngVtuucXCEQnXwmg08v3335OSksI777xj6XCEa7Bu3Tr27NmDUqkkICCARx55BKXSKp4WhAb4+uuv\nSU1NRaVS0atXL8aOHWvpkIRroNPpmDNnDpGRkUyaNMnS4QjXYNq0aXh5eV18PHXqVDw9PS95rMWf\nUVNSUsjMzGT27NkAfP7555w8eZLOnTtbODKhoVasWEGnTp1ISUmxdCjCNaipqeH8+fO8/fbbyGQy\nli9fzqFDh+jXr5+lQxMaQKvV0rFjR+6//36gvoTOwIED/1FMWrBey5cvZ8iQIeTk5Fg6FOEaOTk5\n8dprrzXoWItPHSYlJf2thtawYcNITEy0YETCtZo0aRIJCQmWDkO4Rs7Ozjz++OMXW2PpdDp8fHws\nHJXQUPb29vTo0QOAurq6i58TbMP69euJj4/H19fX0qEIjWAwGJg9ezazZs1iw4YNVzzW4iNa1dXV\nuLi4XHzs6upKZWWlBSMShLbn119/xdHRkaioKEuHIlyjZQL/KLAAAAHbSURBVMuWsXv3bu655x7U\narWlwxEaICUlhaqqKsaMGUNycrKlwxEa4Z133kGlUmEwGJg7dy6xsbGEhYVd8liLj2i5uLhQVVV1\n8XFVVZUY+haEFmI2m/nqq69QqVTcddddlg5HaITJkyfz+eefc/ToUbKysiwdjtAAx44dIysri/ff\nf58ff/yRI0eOsGbNGkuHJVwDlUp18bZnz55kZ2df9liLj2glJCSwcePGi2uytm3bRv/+/S0clSC0\nflqtlk8//ZSBAwfSq1cvS4cjXKOMjAzKysro2bMnarUaNze3v71pFazXf7+pOX36NEePHmX8+PEW\njEi4Frm5uRw7doxx48ZhNBpJSkrivvvuu+zxFk+0YmNjSU1N5ZVXXgHqEy+xEF4Qmt+2bdtIT0+n\npqaG9evXAzBkyBAGDRpk4ciEhvD392fNmjX8/vvvAMTExNClSxcLRyU0xl/rJAXb4O/vT15eHq+8\n8gpyuZyRI0cSFBR02eNFCx5BEARBEIRmYvE1WoIgCIIgCK2VSLQEQRAEQRCaiUi0BEEQBEEQmolI\ntARBEARBEJqJSLQEQRAEQRCaiUi0BEEQBEEQmolItARBEARBEJqJSLQEQRAEQRCaiUi0BEEQBEEQ\nmsn/AbPOyGwnPY5DAAAAAElFTkSuQmCC\n", - "text": [ - "" - ] - } - ], - "prompt_number": 5 + "output_type": "display_data" } ], - "metadata": {} + "source": [ + "from scipy.special import jn\n", + "x = np.linspace(0,5)\n", + "f, ax = plt.subplots()\n", + "ax.set_title(\"Bessel functions\")\n", + "\n", + "for n in range(1,10):\n", + " time.sleep(1)\n", + " ax.plot(x, jn(x,n))\n", + " clear_output(wait=True)\n", + " display(f)\n", + "\n", + "# close the figure at the end, so we don't get a duplicate\n", + "# of the last plot\n", + "plt.close()" + ] } - ] + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Background Jobs.ipynb b/examples/IPython Kernel/Background Jobs.ipynb index 165f1cd..2d8b7f5 100644 --- a/examples/IPython Kernel/Background Jobs.ipynb +++ b/examples/IPython Kernel/Background Jobs.ipynb @@ -1,406 +1,386 @@ { - "metadata": { - "name": "BackgroundJobs" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Simple interactive bacgkround jobs with IPython\n", - "\n", - "We start by loading the `backgroundjobs` library and defining a few trivial functions to illustrate things with." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.lib import backgroundjobs as bg\n", - "\n", - "import sys\n", - "import time\n", - "\n", - "def sleepfunc(interval=2, *a, **kw):\n", - " args = dict(interval=interval,\n", - " args=a,\n", - " kwargs=kw)\n", - " time.sleep(interval)\n", - " return args\n", - "\n", - "def diefunc(interval=2, *a, **kw):\n", - " time.sleep(interval)\n", - " raise Exception(\"Dead job with interval %s\" % interval)\n", - "\n", - "def printfunc(interval=1, reps=5):\n", - " for n in range(reps):\n", - " time.sleep(interval)\n", - " print 'In the background...', n\n", - " sys.stdout.flush()\n", - " print 'All done!'\n", - " sys.stdout.flush()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 1 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, we can create a job manager (called simply `jobs`) and use it to submit new jobs.\n", - "\n", - "Run the cell below, it will show when the jobs start. Wait a few seconds until you see the 'all done' completion message:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "jobs = bg.BackgroundJobManager()\n", - "\n", - "# Start a few jobs, the first one will have ID # 0\n", - "jobs.new(sleepfunc, 4)\n", - "jobs.new(sleepfunc, kw={'reps':2})\n", - "jobs.new('printfunc(1,3)')" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Starting job # 0 in a separate thread.\n", - "Starting job # 2 in a separate thread.\n", - "Starting job # 3 in a separate thread.\n" - ] - }, - { - "output_type": "pyout", - "prompt_number": 10, - "text": [ - "" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "In the background... 0\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "In the background... 1\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "In the background... 2\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "All done!\n" - ] - } - ], - "prompt_number": 10 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can check the status of your jobs at any time:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "jobs.status()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Completed jobs:\n", - "0 : \n", - "2 : \n", - "3 : printfunc(1,3)\n", - "\n" - ] - } - ], - "prompt_number": 11 - }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Simple interactive bacgkround jobs with IPython\n", + "\n", + "We start by loading the `backgroundjobs` library and defining a few trivial functions to illustrate things with." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from IPython.lib import backgroundjobs as bg\n", + "\n", + "import sys\n", + "import time\n", + "\n", + "def sleepfunc(interval=2, *a, **kw):\n", + " args = dict(interval=interval,\n", + " args=a,\n", + " kwargs=kw)\n", + " time.sleep(interval)\n", + " return args\n", + "\n", + "def diefunc(interval=2, *a, **kw):\n", + " time.sleep(interval)\n", + " raise Exception(\"Dead job with interval %s\" % interval)\n", + "\n", + "def printfunc(interval=1, reps=5):\n", + " for n in range(reps):\n", + " time.sleep(interval)\n", + " print 'In the background...', n\n", + " sys.stdout.flush()\n", + " print 'All done!'\n", + " sys.stdout.flush()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we can create a job manager (called simply `jobs`) and use it to submit new jobs.\n", + "\n", + "Run the cell below, it will show when the jobs start. Wait a few seconds until you see the 'all done' completion message:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For any completed job, you can get its result easily:" + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting job # 0 in a separate thread.\n", + "Starting job # 2 in a separate thread.\n", + "Starting job # 3 in a separate thread.\n" ] }, { - "cell_type": "code", - "collapsed": false, - "input": [ - "jobs[0].result" - ], - "language": "python", + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 10, "metadata": {}, - "outputs": [ - { - "output_type": "pyout", - "prompt_number": 12, - "text": [ - "{'args': (), 'interval': 4, 'kwargs': {}}" - ] - } - ], - "prompt_number": 12 + "output_type": "execute_result" }, { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Errors and tracebacks" + "name": "stdout", + "output_type": "stream", + "text": [ + "In the background... 0\n", + "In the background... 1\n", + "In the background... 2\n", + "All done!\n" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The jobs manager tries to help you with debugging:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "# This makes a couple of jobs which will die. Let's keep a reference to\n", - "# them for easier traceback reporting later\n", - "diejob1 = jobs.new(diefunc, 1)\n", - "diejob2 = jobs.new(diefunc, 2)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Starting job # 4 in a separate thread.\n", - "Starting job # 5 in a separate thread.\n" - ] - } - ], - "prompt_number": 13 - }, + } + ], + "source": [ + "jobs = bg.BackgroundJobManager()\n", + "\n", + "# Start a few jobs, the first one will have ID # 0\n", + "jobs.new(sleepfunc, 4)\n", + "jobs.new(sleepfunc, kw={'reps':2})\n", + "jobs.new('printfunc(1,3)')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can check the status of your jobs at any time:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can get the traceback of any dead job. Run the line\n", - "below again interactively until it prints a traceback (check the status\n", - "of the job):\n" + "name": "stdout", + "output_type": "stream", + "text": [ + "Completed jobs:\n", + "0 : \n", + "2 : \n", + "3 : printfunc(1,3)\n", + "\n" ] - }, + } + ], + "source": [ + "jobs.status()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "For any completed job, you can get its result easily:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "print \"Status of diejob1:\", diejob1.status\n", - "diejob1.traceback() # jobs.traceback(4) would also work here, with the job number" - ], - "language": "python", + "data": { + "text/plain": [ + "{'args': (), 'interval': 4, 'kwargs': {}}" + ] + }, + "execution_count": 12, "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Status of diejob1: Dead (Exception), call jobs.traceback() for details\n", - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n", - "\u001b[1;31mException\u001b[0m Traceback (most recent call last)\n", - "\u001b[1;32m/home/fperez/usr/opt/virtualenv/ipython-0.13.2/lib/python2.7/site-packages/IPython/lib/backgroundjobs.pyc\u001b[0m in \u001b[0;36mcall\u001b[1;34m(self)\u001b[0m\n", - "\u001b[0;32m 482\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 483\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mcall\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m--> 484\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\n", - "\u001b[1;32m\u001b[0m in \u001b[0;36mdiefunc\u001b[1;34m(interval, *a, **kw)\u001b[0m\n", - "\u001b[0;32m 13\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mdiefunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minterval\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m*\u001b[0m\u001b[0ma\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkw\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 14\u001b[0m \u001b[0mtime\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minterval\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m---> 15\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Dead job with interval %s\"\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0minterval\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[0;32m 16\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 17\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mprintfunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minterval\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mreps\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m5\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\n", - "\u001b[1;31mException\u001b[0m: Dead job with interval 1\n" - ] - } - ], - "prompt_number": 14 - }, + "output_type": "execute_result" + } + ], + "source": [ + "jobs[0].result" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Errors and tracebacks" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The jobs manager tries to help you with debugging:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This will print all tracebacks for all dead jobs:" + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting job # 4 in a separate thread.\n", + "Starting job # 5 in a separate thread.\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "jobs.traceback()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Traceback for: >\n", - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n", - "\u001b[1;31mException\u001b[0m Traceback (most recent call last)\n", - "\u001b[1;32m/home/fperez/usr/opt/virtualenv/ipython-0.13.2/lib/python2.7/site-packages/IPython/lib/backgroundjobs.pyc\u001b[0m in \u001b[0;36mcall\u001b[1;34m(self)\u001b[0m\n", - "\u001b[0;32m 482\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 483\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mcall\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m--> 484\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\n", - "\u001b[1;32m\u001b[0m in \u001b[0;36mdiefunc\u001b[1;34m(interval, *a, **kw)\u001b[0m\n", - "\u001b[0;32m 13\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mdiefunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minterval\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m*\u001b[0m\u001b[0ma\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkw\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 14\u001b[0m \u001b[0mtime\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minterval\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m---> 15\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Dead job with interval %s\"\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0minterval\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[0;32m 16\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 17\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mprintfunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minterval\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mreps\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m5\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\n", - "\u001b[1;31mException\u001b[0m: Dead job with interval 1\n", - "\n", - "Traceback for: >\n", - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n", - "\u001b[1;31mException\u001b[0m Traceback (most recent call last)\n", - "\u001b[1;32m/home/fperez/usr/opt/virtualenv/ipython-0.13.2/lib/python2.7/site-packages/IPython/lib/backgroundjobs.pyc\u001b[0m in \u001b[0;36mcall\u001b[1;34m(self)\u001b[0m\n", - "\u001b[0;32m 482\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 483\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mcall\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m--> 484\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\n", - "\u001b[1;32m\u001b[0m in \u001b[0;36mdiefunc\u001b[1;34m(interval, *a, **kw)\u001b[0m\n", - "\u001b[0;32m 13\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mdiefunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minterval\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m*\u001b[0m\u001b[0ma\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkw\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 14\u001b[0m \u001b[0mtime\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minterval\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;32m---> 15\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Dead job with interval %s\"\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0minterval\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[0;32m 16\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0;32m 17\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mprintfunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minterval\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mreps\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m5\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\n", - "\u001b[1;31mException\u001b[0m: Dead job with interval 2\n", - "\n" - ] - } - ], - "prompt_number": 15 - }, + } + ], + "source": [ + "# This makes a couple of jobs which will die. Let's keep a reference to\n", + "# them for easier traceback reporting later\n", + "diejob1 = jobs.new(diefunc, 1)\n", + "diejob2 = jobs.new(diefunc, 2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can get the traceback of any dead job. Run the line\n", + "below again interactively until it prints a traceback (check the status\n", + "of the job):\n" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The job manager can be flushed of all completed jobs at any time:" + "name": "stdout", + "output_type": "stream", + "text": [ + "Status of diejob1: Dead (Exception), call jobs.traceback() for details\n", + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n", + "\u001b[1;31mException\u001b[0m Traceback (most recent call last)\n", + "\u001b[1;32m/home/fperez/usr/opt/virtualenv/ipython-0.13.2/lib/python2.7/site-packages/IPython/lib/backgroundjobs.pyc\u001b[0m in \u001b[0;36mcall\u001b[1;34m(self)\u001b[0m\n", + "\u001b[0;32m 482\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 483\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mcall\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m--> 484\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\n", + "\u001b[1;32m\u001b[0m in \u001b[0;36mdiefunc\u001b[1;34m(interval, *a, **kw)\u001b[0m\n", + "\u001b[0;32m 13\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mdiefunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minterval\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m*\u001b[0m\u001b[0ma\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkw\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 14\u001b[0m \u001b[0mtime\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minterval\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m---> 15\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Dead job with interval %s\"\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0minterval\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 16\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 17\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mprintfunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minterval\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mreps\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m5\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\n", + "\u001b[1;31mException\u001b[0m: Dead job with interval 1\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "jobs.flush()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Flushing 3 Completed jobs.\n", - "Flushing 2 Dead jobs.\n" - ] - } - ], - "prompt_number": 16 - }, + } + ], + "source": [ + "print \"Status of diejob1:\", diejob1.status\n", + "diejob1.traceback() # jobs.traceback(4) would also work here, with the job number" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This will print all tracebacks for all dead jobs:" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "After that, the status is simply empty:" + "name": "stdout", + "output_type": "stream", + "text": [ + "Traceback for: >\n", + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n", + "\u001b[1;31mException\u001b[0m Traceback (most recent call last)\n", + "\u001b[1;32m/home/fperez/usr/opt/virtualenv/ipython-0.13.2/lib/python2.7/site-packages/IPython/lib/backgroundjobs.pyc\u001b[0m in \u001b[0;36mcall\u001b[1;34m(self)\u001b[0m\n", + "\u001b[0;32m 482\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 483\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mcall\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m--> 484\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\n", + "\u001b[1;32m\u001b[0m in \u001b[0;36mdiefunc\u001b[1;34m(interval, *a, **kw)\u001b[0m\n", + "\u001b[0;32m 13\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mdiefunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minterval\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m*\u001b[0m\u001b[0ma\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkw\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 14\u001b[0m \u001b[0mtime\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minterval\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m---> 15\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Dead job with interval %s\"\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0minterval\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 16\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 17\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mprintfunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minterval\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mreps\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m5\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\n", + "\u001b[1;31mException\u001b[0m: Dead job with interval 1\n", + "\n", + "Traceback for: >\n", + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n", + "\u001b[1;31mException\u001b[0m Traceback (most recent call last)\n", + "\u001b[1;32m/home/fperez/usr/opt/virtualenv/ipython-0.13.2/lib/python2.7/site-packages/IPython/lib/backgroundjobs.pyc\u001b[0m in \u001b[0;36mcall\u001b[1;34m(self)\u001b[0m\n", + "\u001b[0;32m 482\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 483\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mcall\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m--> 484\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\n", + "\u001b[1;32m\u001b[0m in \u001b[0;36mdiefunc\u001b[1;34m(interval, *a, **kw)\u001b[0m\n", + "\u001b[0;32m 13\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mdiefunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minterval\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m*\u001b[0m\u001b[0ma\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkw\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 14\u001b[0m \u001b[0mtime\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msleep\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minterval\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m---> 15\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Dead job with interval %s\"\u001b[0m \u001b[1;33m%\u001b[0m \u001b[0minterval\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 16\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0;32m 17\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mprintfunc\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minterval\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mreps\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m5\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\n", + "\u001b[1;31mException\u001b[0m: Dead job with interval 2\n", + "\n" ] - }, - { - "cell_type": "code", - "collapsed": true, - "input": [ - "jobs.status()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 17 - }, + } + ], + "source": [ + "jobs.traceback()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The job manager can be flushed of all completed jobs at any time:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Jobs have a `.join` method that lets you wait on their thread for completion:" + "name": "stdout", + "output_type": "stream", + "text": [ + "Flushing 3 Completed jobs.\n", + "Flushing 2 Dead jobs.\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "j = jobs.new(sleepfunc, 2)\n", - "j.join?" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Starting job # 0 in a separate thread.\n" - ] - } - ], - "prompt_number": 18 - }, + } + ], + "source": [ + "jobs.flush()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After that, the status is simply empty:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "jobs.status()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Jobs have a `.join` method that lets you wait on their thread for completion:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Exercise\n", - "\n", - "1. Start a new job that calls `sleepfunc` with a 5-second wait\n", - "2. Print a short message that indicates you are waiting (note: you'll need to flush stdout to see that print output appear).\n", - "3. Wait on the job and then print its result." + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting job # 0 in a separate thread.\n" ] } ], - "metadata": {} + "source": [ + "j = jobs.new(sleepfunc, 2)\n", + "j.join?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exercise\n", + "\n", + "1. Start a new job that calls `sleepfunc` with a 5-second wait\n", + "2. Print a short message that indicates you are waiting (note: you'll need to flush stdout to see that print output appear).\n", + "3. Wait on the job and then print its result." + ] } - ] + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Beyond Plain Python.ipynb b/examples/IPython Kernel/Beyond Plain Python.ipynb index de7126c..da3e5e0 100644 --- a/examples/IPython Kernel/Beyond Plain Python.ipynb +++ b/examples/IPython Kernel/Beyond Plain Python.ipynb @@ -1,1613 +1,1807 @@ { - "metadata": { - "name": "", - "signature": "sha256:31071a05d0ecd75ed72fe3f0de0ad447a6f85cffe382c26efa5e68db1fee54ee" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ - { - "cell_type": "heading", - "level": 1, - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "IPython: beyond plain Python" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When executing code in IPython, all valid Python syntax works as-is, but IPython provides a number of features designed to make the interactive experience more fluid and efficient." - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "First things first: running code, getting help" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In the notebook, to run a cell of code, hit `Shift-Enter`. This executes the cell and puts the cursor in the next cell below, or makes a new one if you are at the end. Alternately, you can use:\n", - " \n", - "- `Alt-Enter` to force the creation of a new cell unconditionally (useful when inserting new content in the middle of an existing notebook).\n", - "- `Control-Enter` executes the cell and keeps the cursor in the same cell, useful for quick experimentation of snippets that you don't need to keep permanently." + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# IPython: beyond plain Python" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When executing code in IPython, all valid Python syntax works as-is, but IPython provides a number of features designed to make the interactive experience more fluid and efficient." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## First things first: running code, getting help" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the notebook, to run a cell of code, hit `Shift-Enter`. This executes the cell and puts the cursor in the next cell below, or makes a new one if you are at the end. Alternately, you can use:\n", + " \n", + "- `Alt-Enter` to force the creation of a new cell unconditionally (useful when inserting new content in the middle of an existing notebook).\n", + "- `Control-Enter` executes the cell and keeps the cursor in the same cell, useful for quick experimentation of snippets that you don't need to keep permanently." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hi\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print \"Hi\"" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Hi\n" - ] - } - ], - "prompt_number": 1 - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } + } + ], + "source": [ + "print \"Hi\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Getting help:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "?" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "Typing `object_name?` will print all sorts of details about any object, including docstrings, function definition lines (for call arguments) and constructor details for classes." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import collections\n", + "collections.namedtuple?" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "collections.Counter??" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "*int*?" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "An IPython quick reference card:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%quickref" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Tab completion" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Tab completion, especially for attributes, is a convenient way to explore the structure of any object you\u2019re dealing with. Simply type `object_name.` to view the object\u2019s attributes. Besides Python objects and keywords, tab completion also works on file and directory names." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "collections." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## The interactive workflow: input, output, history" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "12" + ] }, - "source": [ - "Getting help:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "?" - ], - "language": "python", + "execution_count": 7, "metadata": {}, - "outputs": [], - "prompt_number": 2 - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } + "output_type": "execute_result" + } + ], + "source": [ + "2+10" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "22" + ] }, - "source": [ - "Typing `object_name?` will print all sorts of details about any object, including docstrings, function definition lines (for call arguments) and constructor details for classes." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import collections\n", - "collections.namedtuple?" - ], - "language": "python", + "execution_count": 8, "metadata": {}, - "outputs": [], - "prompt_number": 3 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "collections.Counter??" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 4 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "*int*?" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 5 - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } + "output_type": "execute_result" + } + ], + "source": [ + "_+10" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "You can suppress the storage and rendering of output if you append `;` to the last cell (this comes in handy when plotting with matplotlib, for example):" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "10+20;" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "22" + ] }, - "source": [ - "An IPython quick reference card:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%quickref" - ], - "language": "python", + "execution_count": 10, "metadata": {}, - "outputs": [], - "prompt_number": 6 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": { - "slideshow": { - "slide_type": "slide" - } + "output_type": "execute_result" + } + ], + "source": [ + "_" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "The output is stored in `_N` and `Out[N]` variables:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] }, - "source": [ - "Tab completion" - ] - }, - { - "cell_type": "markdown", + "execution_count": 11, "metadata": {}, - "source": [ - "Tab completion, especially for attributes, is a convenient way to explore the structure of any object you\u2019re dealing with. Simply type `object_name.` to view the object\u2019s attributes. Besides Python objects and keywords, tab completion also works on file and directory names." + "output_type": "execute_result" + } + ], + "source": [ + "_10 == Out[10]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "And the last three have shorthands for convenience:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "last output: True\n", + "next one : 22\n", + "and next : 22\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "collections." - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 8 - }, + } + ], + "source": [ + "print 'last output:', _\n", + "print 'next one :', __\n", + "print 'and next :', ___" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false, + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [ { - "cell_type": "heading", - "level": 2, - "metadata": { - "slideshow": { - "slide_type": "slide" - } + "data": { + "text/plain": [ + "u'_10 == Out[10]'" + ] }, - "source": [ - "The interactive workflow: input, output, history" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "2+10" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 7, - "text": [ - "12" - ] - } - ], - "prompt_number": 7 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "_+10" - ], - "language": "python", + "execution_count": 13, "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 8, - "text": [ - "22" - ] - } - ], - "prompt_number": 8 - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } + "output_type": "execute_result" + } + ], + "source": [ + "In[11]" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "u'In[11]'" + ] }, - "source": [ - "You can suppress the storage and rendering of output if you append `;` to the last cell (this comes in handy when plotting with matplotlib, for example):" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "10+20;" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 9 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "_" - ], - "language": "python", + "execution_count": 14, "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 10, - "text": [ - "22" - ] - } - ], - "prompt_number": 10 - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } + "output_type": "execute_result" + } + ], + "source": [ + "_i" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "u'In[11]'" + ] }, - "source": [ - "The output is stored in `_N` and `Out[N]` variables:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "_10 == Out[10]" - ], - "language": "python", + "execution_count": 15, "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 11, - "text": [ - "True" - ] - } - ], - "prompt_number": 11 - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "And the last three have shorthands for convenience:" + "output_type": "execute_result" + } + ], + "source": [ + "_ii" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false, + "slideshow": { + "slide_type": "subslide" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "last input: _ii\n", + "next one : _i\n", + "and next : In[11]\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print 'last output:', _\n", - "print 'next one :', __\n", - "print 'and next :', ___" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "last output: True\n", - "next one : 22\n", - "and next : 22\n" - ] - } - ], - "prompt_number": 12 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "In[11]" - ], - "language": "python", - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 13, - "text": [ - "u'_10 == Out[10]'" - ] - } - ], - "prompt_number": 13 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "_i" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 14, - "text": [ - "u'In[11]'" - ] - } - ], - "prompt_number": 14 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "_ii" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 15, - "text": [ - "u'In[11]'" - ] - } - ], - "prompt_number": 15 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print 'last input:', _i\n", - "print 'next one :', _ii\n", - "print 'and next :', _iii" - ], - "language": "python", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "last input: _ii\n", - "next one : _i\n", - "and next : In[11]\n" - ] - } - ], - "prompt_number": 16 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%history -n 1-5" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - " 1: print \"Hi\"\n", - " 2: ?\n", - " 3:\n", - "import collections\n", - "collections.namedtuple?\n", - " 4: collections.Counter??\n", - " 5: *int*?\n" - ] - } - ], - "prompt_number": 17 - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "**Exercise**\n", - "\n", - "Write the last 10 lines of history to a file named `log.py`." + } + ], + "source": [ + "print 'last input:', _i\n", + "print 'next one :', _ii\n", + "print 'and next :', _iii" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 1: print \"Hi\"\n", + " 2: ?\n", + " 3:\n", + "import collections\n", + "collections.namedtuple?\n", + " 4: collections.Counter??\n", + " 5: *int*?\n" ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "Accessing the underlying operating system" + } + ], + "source": [ + "%history -n 1-5" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "**Exercise**\n", + "\n", + "Write the last 10 lines of history to a file named `log.py`." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Accessing the underlying operating system" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/home/fperez/ipython/tutorial/notebooks\r\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "!pwd" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "/home/fperez/ipython/tutorial/notebooks\r\n" - ] - } - ], - "prompt_number": 18 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "files = !ls\n", - "print \"My current directory's files:\"\n", - "print files" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "My current directory's files:\n", - "['BackgroundJobs.ipynb', 'Custom Display Logic.ipynb', 'Customizing IPython - Condensed.ipynb', 'Customizing IPython - Config.ipynb', 'Customizing IPython - Extensions.ipynb', 'Customizing IPython - Magics.ipynb', 'data', 'figs', 'flare.json', 'Index.ipynb', 'Interactive Widgets.ipynb', 'IPython - beyond plain Python.ipynb', 'kernel-embedding', 'Markdown Cells.ipynb', 'myscript.py', 'nbconvert_arch.png', 'NbConvert from command line.ipynb', 'NbConvert Python library.ipynb', 'Notebook and javascript extension.ipynb', 'Notebook Basics.ipynb', 'Overview of IPython.parallel.ipynb', 'parallel', 'Rich Display System.ipynb', 'Running a Secure Public Notebook.ipynb', 'Running Code.ipynb', 'Sample.ipynb', 'soln', 'Terminal usage.ipynb', 'text_analysis.py', 'Typesetting Math Using MathJax.ipynb']\n" - ] - } - ], - "prompt_number": 19 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "!echo $files" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "[BackgroundJobs.ipynb, Custom Display Logic.ipynb, Customizing IPython - Condensed.ipynb, Customizing IPython - Config.ipynb, Customizing IPython - Extensions.ipynb, Customizing IPython - Magics.ipynb, data, figs, flare.json, Index.ipynb, Interactive Widgets.ipynb, IPython - beyond plain Python.ipynb, kernel-embedding, Markdown Cells.ipynb, myscript.py, nbconvert_arch.png, NbConvert from command line.ipynb, NbConvert Python library.ipynb, Notebook and javascript extension.ipynb, Notebook Basics.ipynb, Overview of IPython.parallel.ipynb, parallel, Rich Display System.ipynb, Running a Secure Public Notebook.ipynb, Running Code.ipynb, Sample.ipynb, soln, Terminal usage.ipynb, text_analysis.py, Typesetting Math Using MathJax.ipynb]\r\n" - ] - } - ], - "prompt_number": 20 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "!echo {files[0].upper()}" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "BACKGROUNDJOBS.IPYNB\r\n" - ] - } - ], - "prompt_number": 21 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that all this is available even in multiline blocks:" + } + ], + "source": [ + "!pwd" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "My current directory's files:\n", + "['BackgroundJobs.ipynb', 'Custom Display Logic.ipynb', 'Customizing IPython - Condensed.ipynb', 'Customizing IPython - Config.ipynb', 'Customizing IPython - Extensions.ipynb', 'Customizing IPython - Magics.ipynb', 'data', 'figs', 'flare.json', 'Index.ipynb', 'Interactive Widgets.ipynb', 'IPython - beyond plain Python.ipynb', 'kernel-embedding', 'Markdown Cells.ipynb', 'myscript.py', 'nbconvert_arch.png', 'NbConvert from command line.ipynb', 'NbConvert Python library.ipynb', 'Notebook and javascript extension.ipynb', 'Notebook Basics.ipynb', 'Overview of IPython.parallel.ipynb', 'parallel', 'Rich Display System.ipynb', 'Running a Secure Public Notebook.ipynb', 'Running Code.ipynb', 'Sample.ipynb', 'soln', 'Terminal usage.ipynb', 'text_analysis.py', 'Typesetting Math Using MathJax.ipynb']\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import os\n", - "for i,f in enumerate(files):\n", - " if f.endswith('ipynb'):\n", - " !echo {\"%02d\" % i} - \"{os.path.splitext(f)[0]}\"\n", - " else:\n", - " print '--'" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "00 - BackgroundJobs\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "01 - Custom Display Logic\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "02 - Customizing IPython - Condensed\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "03 - Customizing IPython - Config\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "04 - Customizing IPython - Extensions\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "05 - Customizing IPython - Magics\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "--\n", - "--\n", - "--\n", - "09 - Index\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "10 - Interactive Widgets\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "11 - IPython - beyond plain Python\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "--\n", - "13 - Markdown Cells\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "--\n", - "--\n", - "16 - NbConvert from command line\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "17 - NbConvert Python library\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "18 - Notebook and javascript extension\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "19 - Notebook Basics\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "20 - Overview of IPython.parallel\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "--\n", - "22 - Rich Display System\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "23 - Running a Secure Public Notebook\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "24 - Running Code\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "25 - Sample\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "--\n", - "27 - Terminal usage\r\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "--\n", - "29 - Typesetting Math Using MathJax\r\n" - ] - } - ], - "prompt_number": 27 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Beyond Python: magic functions" + } + ], + "source": [ + "files = !ls\n", + "print \"My current directory's files:\"\n", + "print files" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[BackgroundJobs.ipynb, Custom Display Logic.ipynb, Customizing IPython - Condensed.ipynb, Customizing IPython - Config.ipynb, Customizing IPython - Extensions.ipynb, Customizing IPython - Magics.ipynb, data, figs, flare.json, Index.ipynb, Interactive Widgets.ipynb, IPython - beyond plain Python.ipynb, kernel-embedding, Markdown Cells.ipynb, myscript.py, nbconvert_arch.png, NbConvert from command line.ipynb, NbConvert Python library.ipynb, Notebook and javascript extension.ipynb, Notebook Basics.ipynb, Overview of IPython.parallel.ipynb, parallel, Rich Display System.ipynb, Running a Secure Public Notebook.ipynb, Running Code.ipynb, Sample.ipynb, soln, Terminal usage.ipynb, text_analysis.py, Typesetting Math Using MathJax.ipynb]\r\n" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The IPyhton 'magic' functions are a set of commands, invoked by prepending one or two `%` signs to their name, that live in a namespace separate from your normal Python variables and provide a more command-like interface. They take flags with `--` and arguments without quotes, parentheses or commas. The motivation behind this system is two-fold:\n", - " \n", - "- To provide an orthogonal namespace for controlling IPython itself and exposing other system-oriented functionality.\n", - "\n", - "- To expose a calling mode that requires minimal verbosity and typing while working interactively. Thus the inspiration taken from the classic Unix shell style for commands." + } + ], + "source": [ + "!echo $files" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BACKGROUNDJOBS.IPYNB\r\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%magic" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 28 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Line vs cell magics:" + } + ], + "source": [ + "!echo {files[0].upper()}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that all this is available even in multiline blocks:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "00 - BackgroundJobs\r\n", + "01 - Custom Display Logic\r\n", + "02 - Customizing IPython - Condensed\r\n", + "03 - Customizing IPython - Config\r\n", + "04 - Customizing IPython - Extensions\r\n", + "05 - Customizing IPython - Magics\r\n", + "--\n", + "--\n", + "--\n", + "09 - Index\r\n", + "10 - Interactive Widgets\r\n", + "11 - IPython - beyond plain Python\r\n", + "--\n", + "13 - Markdown Cells\r\n", + "--\n", + "--\n", + "16 - NbConvert from command line\r\n", + "17 - NbConvert Python library\r\n", + "18 - Notebook and javascript extension\r\n", + "19 - Notebook Basics\r\n", + "20 - Overview of IPython.parallel\r\n", + "--\n", + "22 - Rich Display System\r\n", + "23 - Running a Secure Public Notebook\r\n", + "24 - Running Code\r\n", + "25 - Sample\r\n", + "--\n", + "27 - Terminal usage\r\n", + "--\n", + "29 - Typesetting Math Using MathJax\r\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%timeit range(10)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "10000000 loops, best of 3: 190 ns per loop\n" - ] - } - ], - "prompt_number": 29 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit\n", - "range(10)\n", - "range(100)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "1000000 loops, best of 3: 888 ns per loop\n" - ] - } - ], - "prompt_number": 30 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Line magics can be used even inside code blocks:" + } + ], + "source": [ + "import os\n", + "for i,f in enumerate(files):\n", + " if f.endswith('ipynb'):\n", + " !echo {\"%02d\" % i} - \"{os.path.splitext(f)[0]}\"\n", + " else:\n", + " print '--'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Beyond Python: magic functions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The IPyhton 'magic' functions are a set of commands, invoked by prepending one or two `%` signs to their name, that live in a namespace separate from your normal Python variables and provide a more command-like interface. They take flags with `--` and arguments without quotes, parentheses or commas. The motivation behind this system is two-fold:\n", + " \n", + "- To provide an orthogonal namespace for controlling IPython itself and exposing other system-oriented functionality.\n", + "\n", + "- To expose a calling mode that requires minimal verbosity and typing while working interactively. Thus the inspiration taken from the classic Unix shell style for commands." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%magic" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Line vs cell magics:" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "10000000 loops, best of 3: 190 ns per loop\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "for i in range(5):\n", - " size = i*100\n", - " print 'size:',size, \n", - " %timeit range(size)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "size: 010000000 loops, best of 3: 129 ns per loop" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - " size: 1001000000 loops, best of 3: 649 ns per loop" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - " size: 2001000000 loops, best of 3: 1.09 \u00b5s per loop" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - " size: 3001000000 loops, best of 3: 1.74 \u00b5s per loop" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - " size: 400100000 loops, best of 3: 2.72 \u00b5s per loop" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "\n", - "\n" - ] - } - ], - "prompt_number": 31 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Magics can do anything they want with their input, so it doesn't have to be valid Python:" + } + ], + "source": [ + "%timeit range(10)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1000000 loops, best of 3: 888 ns per loop\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%bash\n", - "echo \"My shell is:\" $SHELL\n", - "echo \"My memory status is:\"\n", - "free" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "My shell is: /bin/bash\n", - "My memory status is:\n", - " total used free shared buffers cached\n", - "Mem: 7870888 6389328 1481560 0 662860 2505172\n", - "-/+ buffers/cache: 3221296 4649592\n", - "Swap: 3905532 4852 3900680\n" - ] - } - ], - "prompt_number": 32 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Another interesting cell magic: create any file you want locally from the notebook:" + } + ], + "source": [ + "%%timeit\n", + "range(10)\n", + "range(100)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Line magics can be used even inside code blocks:" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "size: 010000000 loops, best of 3: 129 ns per loop\n", + " size: 1001000000 loops, best of 3: 649 ns per loop\n", + " size: 2001000000 loops, best of 3: 1.09 \u00b5s per loop\n", + " size: 3001000000 loops, best of 3: 1.74 \u00b5s per loop\n", + " size: 400100000 loops, best of 3: 2.72 \u00b5s per loop\n", + "\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%writefile test.txt\n", - "This is a test file!\n", - "It can contain anything I want...\n", - "\n", + } + ], + "source": [ + "for i in range(5):\n", + " size = i*100\n", + " print 'size:',size, \n", + " %timeit range(size)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Magics can do anything they want with their input, so it doesn't have to be valid Python:" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "My shell is: /bin/bash\n", + "My memory status is:\n", + " total used free shared buffers cached\n", + "Mem: 7870888 6389328 1481560 0 662860 2505172\n", + "-/+ buffers/cache: 3221296 4649592\n", + "Swap: 3905532 4852 3900680\n" + ] + } + ], + "source": [ + "%%bash\n", + "echo \"My shell is:\" $SHELL\n", + "echo \"My memory status is:\"\n", + "free" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Another interesting cell magic: create any file you want locally from the notebook:" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing test.txt\n" + ] + } + ], + "source": [ + "%%writefile test.txt\n", + "This is a test file!\n", + "It can contain anything I want...\n", + "\n", + "And more..." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This is a test file!\r\n", + "It can contain anything I want...\r\n", + "\r\n", "And more..." - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Writing test.txt\n" - ] - } - ], - "prompt_number": 33 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "!cat test.txt" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "This is a test file!\r\n", - "It can contain anything I want...\r\n", - "\r\n", - "And more..." - ] - } - ], - "prompt_number": 34 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's see what other magics are currently defined in the system:" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%lsmagic" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "json": [ - "{\"cell\": {\"prun\": \"ExecutionMagics\", \"file\": \"Other\", \"!\": \"OSMagics\", \"capture\": \"ExecutionMagics\", \"timeit\": \"ExecutionMagics\", \"script\": \"ScriptMagics\", \"pypy\": \"Other\", \"system\": \"OSMagics\", \"perl\": \"Other\", \"HTML\": \"Other\", \"bash\": \"Other\", \"python\": \"Other\", \"SVG\": \"Other\", \"javascript\": \"DisplayMagics\", \"writefile\": \"OSMagics\", \"ruby\": \"Other\", \"python3\": \"Other\", \"python2\": \"Other\", \"latex\": \"DisplayMagics\", \"sx\": \"OSMagics\", \"svg\": \"DisplayMagics\", \"html\": \"DisplayMagics\", \"sh\": \"Other\", \"time\": \"ExecutionMagics\", \"debug\": \"ExecutionMagics\"}, \"line\": {\"psource\": \"NamespaceMagics\", \"logstart\": \"LoggingMagics\", \"popd\": \"OSMagics\", \"loadpy\": \"CodeMagics\", \"install_ext\": \"ExtensionMagics\", \"colors\": \"BasicMagics\", \"who_ls\": \"NamespaceMagics\", \"lf\": \"Other\", \"install_profiles\": \"DeprecatedMagics\", \"clk\": \"Other\", \"ll\": \"Other\", \"pprint\": \"BasicMagics\", \"lk\": \"Other\", \"ls\": \"Other\", \"save\": \"CodeMagics\", \"tb\": \"ExecutionMagics\", \"lx\": \"Other\", \"dl\": \"Other\", \"pylab\": \"PylabMagics\", \"dd\": \"Other\", \"quickref\": \"BasicMagics\", \"dx\": \"Other\", \"d\": \"Other\", \"magic\": \"BasicMagics\", \"dhist\": \"OSMagics\", \"edit\": \"KernelMagics\", \"logstop\": \"LoggingMagics\", \"gui\": \"BasicMagics\", \"alias_magic\": \"BasicMagics\", \"debug\": \"ExecutionMagics\", \"page\": \"BasicMagics\", \"logstate\": \"LoggingMagics\", \"ed\": \"Other\", \"pushd\": \"OSMagics\", \"timeit\": \"ExecutionMagics\", \"rehashx\": \"OSMagics\", \"hist\": \"Other\", \"qtconsole\": \"KernelMagics\", \"rm\": \"Other\", \"dirs\": \"OSMagics\", \"run\": \"ExecutionMagics\", \"reset_selective\": \"NamespaceMagics\", \"rep\": \"Other\", \"pinfo2\": \"NamespaceMagics\", \"matplotlib\": \"PylabMagics\", \"automagic\": \"AutoMagics\", \"doctest_mode\": \"KernelMagics\", \"logoff\": \"LoggingMagics\", \"reload_ext\": \"ExtensionMagics\", \"pdb\": \"ExecutionMagics\", \"load\": \"CodeMagics\", \"lsmagic\": \"BasicMagics\", \"cl\": \"Other\", \"autosave\": \"KernelMagics\", \"cd\": \"OSMagics\", \"pastebin\": \"CodeMagics\", \"prun\": \"ExecutionMagics\", \"cp\": \"Other\", \"autocall\": \"AutoMagics\", \"bookmark\": \"OSMagics\", \"connect_info\": \"KernelMagics\", \"mkdir\": \"Other\", \"system\": \"OSMagics\", \"whos\": \"NamespaceMagics\", \"rmdir\": \"Other\", \"unload_ext\": \"ExtensionMagics\", \"store\": \"StoreMagics\", \"more\": \"KernelMagics\", \"pdef\": \"NamespaceMagics\", \"precision\": \"BasicMagics\", \"pinfo\": \"NamespaceMagics\", \"pwd\": \"OSMagics\", \"psearch\": \"NamespaceMagics\", \"reset\": \"NamespaceMagics\", \"recall\": \"HistoryMagics\", \"xdel\": \"NamespaceMagics\", \"xmode\": \"BasicMagics\", \"cat\": \"Other\", \"mv\": \"Other\", \"rerun\": \"HistoryMagics\", \"logon\": \"LoggingMagics\", \"history\": \"HistoryMagics\", \"pycat\": \"OSMagics\", \"unalias\": \"OSMagics\", \"install_default_config\": \"DeprecatedMagics\", \"env\": \"OSMagics\", \"load_ext\": \"ExtensionMagics\", \"config\": \"ConfigMagics\", \"killbgscripts\": \"ScriptMagics\", \"profile\": \"BasicMagics\", \"pfile\": \"NamespaceMagics\", \"less\": \"KernelMagics\", \"who\": \"NamespaceMagics\", \"notebook\": \"BasicMagics\", \"man\": \"KernelMagics\", \"sx\": \"OSMagics\", \"macro\": \"ExecutionMagics\", \"clear\": \"KernelMagics\", \"alias\": \"OSMagics\", \"time\": \"ExecutionMagics\", \"sc\": \"OSMagics\", \"ldir\": \"Other\", \"pdoc\": \"NamespaceMagics\"}}" - ], - "metadata": {}, - "output_type": "pyout", - "prompt_number": 35, - "text": [ - "Available line magics:\n", - "%alias %alias_magic %autocall %automagic %autosave %bookmark %cat %cd %cl %clear %clk %colors %config %connect_info %cp %d %dd %debug %dhist %dirs %dl %doctest_mode %dx %ed %edit %env %gui %hist %history %install_default_config %install_ext %install_profiles %killbgscripts %ldir %less %lf %lk %ll %load %load_ext %loadpy %logoff %logon %logstart %logstate %logstop %ls %lsmagic %lx %macro %magic %man %matplotlib %mkdir %more %mv %notebook %page %pastebin %pdb %pdef %pdoc %pfile %pinfo %pinfo2 %popd %pprint %precision %profile %prun %psearch %psource %pushd %pwd %pycat %pylab %qtconsole %quickref %recall %rehashx %reload_ext %rep %rerun %reset %reset_selective %rm %rmdir %run %save %sc %store %sx %system %tb %time %timeit %unalias %unload_ext %who %who_ls %whos %xdel %xmode\n", - "\n", - "Available cell magics:\n", - "%%! %%HTML %%SVG %%bash %%capture %%debug %%file %%html %%javascript %%latex %%perl %%prun %%pypy %%python %%python2 %%python3 %%ruby %%script %%sh %%svg %%sx %%system %%time %%timeit %%writefile\n", - "\n", - "Automagic is ON, % prefix IS NOT needed for line magics." - ] - } - ], - "prompt_number": 35 - }, - { - "cell_type": "heading", - "level": 2, + } + ], + "source": [ + "!cat test.txt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's see what other magics are currently defined in the system:" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "application/json": { + "cell": { + "!": "OSMagics", + "HTML": "Other", + "SVG": "Other", + "bash": "Other", + "capture": "ExecutionMagics", + "debug": "ExecutionMagics", + "file": "Other", + "html": "DisplayMagics", + "javascript": "DisplayMagics", + "latex": "DisplayMagics", + "perl": "Other", + "prun": "ExecutionMagics", + "pypy": "Other", + "python": "Other", + "python2": "Other", + "python3": "Other", + "ruby": "Other", + "script": "ScriptMagics", + "sh": "Other", + "svg": "DisplayMagics", + "sx": "OSMagics", + "system": "OSMagics", + "time": "ExecutionMagics", + "timeit": "ExecutionMagics", + "writefile": "OSMagics" + }, + "line": { + "alias": "OSMagics", + "alias_magic": "BasicMagics", + "autocall": "AutoMagics", + "automagic": "AutoMagics", + "autosave": "KernelMagics", + "bookmark": "OSMagics", + "cat": "Other", + "cd": "OSMagics", + "cl": "Other", + "clear": "KernelMagics", + "clk": "Other", + "colors": "BasicMagics", + "config": "ConfigMagics", + "connect_info": "KernelMagics", + "cp": "Other", + "d": "Other", + "dd": "Other", + "debug": "ExecutionMagics", + "dhist": "OSMagics", + "dirs": "OSMagics", + "dl": "Other", + "doctest_mode": "KernelMagics", + "dx": "Other", + "ed": "Other", + "edit": "KernelMagics", + "env": "OSMagics", + "gui": "BasicMagics", + "hist": "Other", + "history": "HistoryMagics", + "install_default_config": "DeprecatedMagics", + "install_ext": "ExtensionMagics", + "install_profiles": "DeprecatedMagics", + "killbgscripts": "ScriptMagics", + "ldir": "Other", + "less": "KernelMagics", + "lf": "Other", + "lk": "Other", + "ll": "Other", + "load": "CodeMagics", + "load_ext": "ExtensionMagics", + "loadpy": "CodeMagics", + "logoff": "LoggingMagics", + "logon": "LoggingMagics", + "logstart": "LoggingMagics", + "logstate": "LoggingMagics", + "logstop": "LoggingMagics", + "ls": "Other", + "lsmagic": "BasicMagics", + "lx": "Other", + "macro": "ExecutionMagics", + "magic": "BasicMagics", + "man": "KernelMagics", + "matplotlib": "PylabMagics", + "mkdir": "Other", + "more": "KernelMagics", + "mv": "Other", + "notebook": "BasicMagics", + "page": "BasicMagics", + "pastebin": "CodeMagics", + "pdb": "ExecutionMagics", + "pdef": "NamespaceMagics", + "pdoc": "NamespaceMagics", + "pfile": "NamespaceMagics", + "pinfo": "NamespaceMagics", + "pinfo2": "NamespaceMagics", + "popd": "OSMagics", + "pprint": "BasicMagics", + "precision": "BasicMagics", + "profile": "BasicMagics", + "prun": "ExecutionMagics", + "psearch": "NamespaceMagics", + "psource": "NamespaceMagics", + "pushd": "OSMagics", + "pwd": "OSMagics", + "pycat": "OSMagics", + "pylab": "PylabMagics", + "qtconsole": "KernelMagics", + "quickref": "BasicMagics", + "recall": "HistoryMagics", + "rehashx": "OSMagics", + "reload_ext": "ExtensionMagics", + "rep": "Other", + "rerun": "HistoryMagics", + "reset": "NamespaceMagics", + "reset_selective": "NamespaceMagics", + "rm": "Other", + "rmdir": "Other", + "run": "ExecutionMagics", + "save": "CodeMagics", + "sc": "OSMagics", + "store": "StoreMagics", + "sx": "OSMagics", + "system": "OSMagics", + "tb": "ExecutionMagics", + "time": "ExecutionMagics", + "timeit": "ExecutionMagics", + "unalias": "OSMagics", + "unload_ext": "ExtensionMagics", + "who": "NamespaceMagics", + "who_ls": "NamespaceMagics", + "whos": "NamespaceMagics", + "xdel": "NamespaceMagics", + "xmode": "BasicMagics" + } + }, + "text/plain": [ + "Available line magics:\n", + "%alias %alias_magic %autocall %automagic %autosave %bookmark %cat %cd %cl %clear %clk %colors %config %connect_info %cp %d %dd %debug %dhist %dirs %dl %doctest_mode %dx %ed %edit %env %gui %hist %history %install_default_config %install_ext %install_profiles %killbgscripts %ldir %less %lf %lk %ll %load %load_ext %loadpy %logoff %logon %logstart %logstate %logstop %ls %lsmagic %lx %macro %magic %man %matplotlib %mkdir %more %mv %notebook %page %pastebin %pdb %pdef %pdoc %pfile %pinfo %pinfo2 %popd %pprint %precision %profile %prun %psearch %psource %pushd %pwd %pycat %pylab %qtconsole %quickref %recall %rehashx %reload_ext %rep %rerun %reset %reset_selective %rm %rmdir %run %save %sc %store %sx %system %tb %time %timeit %unalias %unload_ext %who %who_ls %whos %xdel %xmode\n", + "\n", + "Available cell magics:\n", + "%%! %%HTML %%SVG %%bash %%capture %%debug %%file %%html %%javascript %%latex %%perl %%prun %%pypy %%python %%python2 %%python3 %%ruby %%script %%sh %%svg %%sx %%system %%time %%timeit %%writefile\n", + "\n", + "Automagic is ON, % prefix IS NOT needed for line magics." + ] + }, + "execution_count": 35, "metadata": {}, - "source": [ - "Running normal Python code: execution and errors" + "output_type": "execute_result" + } + ], + "source": [ + "%lsmagic" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Running normal Python code: execution and errors" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Not only can you input normal Python code, you can even paste straight from a Python or IPython shell session:" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "1\n", + "2\n", + "3\n", + "5\n", + "8\n" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Not only can you input normal Python code, you can even paste straight from a Python or IPython shell session:" + } + ], + "source": [ + ">>> # Fibonacci series:\n", + "... # the sum of two elements defines the next\n", + "... a, b = 0, 1\n", + ">>> while b < 10:\n", + "... print b\n", + "... a, b = b, a+b" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0 1 2 3 4 5 6 7 8 9\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - ">>> # Fibonacci series:\n", - "... # the sum of two elements defines the next\n", - "... a, b = 0, 1\n", - ">>> while b < 10:\n", - "... print b\n", - "... a, b = b, a+b" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "1\n", - "1\n", - "2\n", - "3\n", - "5\n", - "8\n" - ] - } - ], - "prompt_number": 36 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "In [1]: for i in range(10):\n", - " ...: print i,\n", - " ...: " - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "0 1 2 3 4 5 6 7 8 9\n" - ] - } - ], - "prompt_number": 37 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And when your code produces errors, you can control how they are displayed with the `%xmode` magic:" + } + ], + "source": [ + "In [1]: for i in range(10):\n", + " ...: print i,\n", + " ...: " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And when your code produces errors, you can control how they are displayed with the `%xmode` magic:" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing mod.py\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%writefile mod.py\n", - "\n", - "def f(x):\n", - " return 1.0/(x-1)\n", - "\n", - "def g(y):\n", - " return f(y+1)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Writing mod.py\n" - ] - } - ], - "prompt_number": 38 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now let's call the function `g` with an argument that would produce an error:" + } + ], + "source": [ + "%%writefile mod.py\n", + "\n", + "def f(x):\n", + " return 1.0/(x-1)\n", + "\n", + "def g(y):\n", + " return f(y+1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's call the function `g` with an argument that would produce an error:" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "ename": "ZeroDivisionError", + "evalue": "float division by zero", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mmod\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mmod\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32m/home/fperez/ipython/tutorial/notebooks/mod.py\u001b[0m in \u001b[0;36mg\u001b[1;34m(y)\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 6\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m+\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32m/home/fperez/ipython/tutorial/notebooks/mod.py\u001b[0m in \u001b[0;36mf\u001b[1;34m(x)\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[1;36m1.0\u001b[0m\u001b[1;33m/\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mZeroDivisionError\u001b[0m: float division by zero" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import mod\n", - "mod.g(0)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "ename": "ZeroDivisionError", - "evalue": "float division by zero", - "output_type": "pyerr", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;32mimport\u001b[0m \u001b[0mmod\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mmod\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[1;32m/home/fperez/ipython/tutorial/notebooks/mod.py\u001b[0m in \u001b[0;36mg\u001b[1;34m(y)\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 6\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m+\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[1;32m/home/fperez/ipython/tutorial/notebooks/mod.py\u001b[0m in \u001b[0;36mf\u001b[1;34m(x)\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[1;36m1.0\u001b[0m\u001b[1;33m/\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;31mZeroDivisionError\u001b[0m: float division by zero" - ] - } - ], - "prompt_number": 39 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%xmode plain\n", - "mod.g(0)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Exception reporting mode: Plain\n" - ] - }, - { - "ename": "ZeroDivisionError", - "evalue": "float division by zero", - "output_type": "pyerr", - "traceback": [ - "Traceback \u001b[1;36m(most recent call last)\u001b[0m:\n", - " File \u001b[0;32m\"\"\u001b[0m, line \u001b[0;32m2\u001b[0m, in \u001b[0;35m\u001b[0m\n mod.g(0)\n", - " File \u001b[0;32m\"mod.py\"\u001b[0m, line \u001b[0;32m6\u001b[0m, in \u001b[0;35mg\u001b[0m\n return f(y+1)\n", - "\u001b[1;36m File \u001b[1;32m\"mod.py\"\u001b[1;36m, line \u001b[1;32m3\u001b[1;36m, in \u001b[1;35mf\u001b[1;36m\u001b[0m\n\u001b[1;33m return 1.0/(x-1)\u001b[0m\n", - "\u001b[1;31mZeroDivisionError\u001b[0m\u001b[1;31m:\u001b[0m float division by zero\n" - ] - } - ], - "prompt_number": 40 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%xmode verbose\n", - "mod.g(0)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Exception reporting mode: Verbose\n" - ] - }, - { - "ename": "ZeroDivisionError", - "evalue": "float division by zero", - "output_type": "pyerr", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0mget_ipython\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mmagic\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34mu'xmode verbose'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mmod\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m \u001b[1;36mglobal\u001b[0m \u001b[0;36mmod.g\u001b[0m \u001b[1;34m= \u001b[0m\n", - "\u001b[1;32m/home/fperez/ipython/tutorial/notebooks/mod.py\u001b[0m in \u001b[0;36mg\u001b[1;34m(y=0)\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 6\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m+\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m \u001b[1;36mglobal\u001b[0m \u001b[0;36mf\u001b[0m \u001b[1;34m= \u001b[0m\u001b[1;34m\n \u001b[0m\u001b[0;36my\u001b[0m \u001b[1;34m= 0\u001b[0m\n", - "\u001b[1;32m/home/fperez/ipython/tutorial/notebooks/mod.py\u001b[0m in \u001b[0;36mf\u001b[1;34m(x=1)\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[1;36m1.0\u001b[0m\u001b[1;33m/\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m \u001b[0;36mx\u001b[0m \u001b[1;34m= 1\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;31mZeroDivisionError\u001b[0m: float division by zero" - ] - } - ], - "prompt_number": 41 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The default `%xmode` is \"context\", which shows additional context but not all local variables. Let's restore that one for the rest of our session." + } + ], + "source": [ + "import mod\n", + "mod.g(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Exception reporting mode: Plain\n" ] }, { - "cell_type": "code", - "collapsed": false, - "input": [ - "%xmode context" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Exception reporting mode: Context\n" - ] - } - ], - "prompt_number": 42 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Running code in other languages with special `%%` magics" + "ename": "ZeroDivisionError", + "evalue": "float division by zero", + "output_type": "error", + "traceback": [ + "Traceback \u001b[1;36m(most recent call last)\u001b[0m:\n", + " File \u001b[0;32m\"\"\u001b[0m, line \u001b[0;32m2\u001b[0m, in \u001b[0;35m\u001b[0m\n mod.g(0)\n", + " File \u001b[0;32m\"mod.py\"\u001b[0m, line \u001b[0;32m6\u001b[0m, in \u001b[0;35mg\u001b[0m\n return f(y+1)\n", + "\u001b[1;36m File \u001b[1;32m\"mod.py\"\u001b[1;36m, line \u001b[1;32m3\u001b[1;36m, in \u001b[1;35mf\u001b[1;36m\u001b[0m\n\u001b[1;33m return 1.0/(x-1)\u001b[0m\n", + "\u001b[1;31mZeroDivisionError\u001b[0m\u001b[1;31m:\u001b[0m float division by zero\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%perl\n", - "@months = (\"July\", \"August\", \"September\");\n", - "print $months[0];" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "July" - ] - } - ], - "prompt_number": 43 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%ruby\n", - "name = \"world\"\n", - "puts \"Hello #{name.capitalize}!\"" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Hello World!\n" - ] - } - ], - "prompt_number": 44 - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Exercise" + } + ], + "source": [ + "%xmode plain\n", + "mod.g(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Exception reporting mode: Verbose\n" ] }, { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Write a cell that executes in Bash and prints your current working directory as well as the date.\n", - "\n", - "Apologies to Windows users who may not have Bash available, not sure how to obtain the equivalent result with `cmd.exe` or Powershell." + "ename": "ZeroDivisionError", + "evalue": "float division by zero", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[0mget_ipython\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mmagic\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34mu'xmode verbose'\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[0mmod\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m \u001b[1;36mglobal\u001b[0m \u001b[0;36mmod.g\u001b[0m \u001b[1;34m= \u001b[0m\n", + "\u001b[1;32m/home/fperez/ipython/tutorial/notebooks/mod.py\u001b[0m in \u001b[0;36mg\u001b[1;34m(y=0)\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 6\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m+\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m \u001b[1;36mglobal\u001b[0m \u001b[0;36mf\u001b[0m \u001b[1;34m= \u001b[0m\u001b[1;34m\n \u001b[0m\u001b[0;36my\u001b[0m \u001b[1;34m= 0\u001b[0m\n", + "\u001b[1;32m/home/fperez/ipython/tutorial/notebooks/mod.py\u001b[0m in \u001b[0;36mf\u001b[1;34m(x=1)\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[1;36m1.0\u001b[0m\u001b[1;33m/\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m \u001b[0;36mx\u001b[0m \u001b[1;34m= 1\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mZeroDivisionError\u001b[0m: float division by zero" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%load soln/bash-script" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Raw Input in the notebook" + } + ], + "source": [ + "%xmode verbose\n", + "mod.g(0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The default `%xmode` is \"context\", which shows additional context but not all local variables. Let's restore that one for the rest of our session." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Exception reporting mode: Context\n" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Since 1.0 the IPython notebook web application support `raw_input` which for example allow us to invoke the `%debug` magic in the notebook:" + } + ], + "source": [ + "%xmode context" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Running code in other languages with special `%%` magics" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "July" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "mod.g(0)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "ename": "ZeroDivisionError", - "evalue": "float division by zero", - "output_type": "pyerr", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mmod\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[1;32m/home/fperez/ipython/tutorial/notebooks/mod.py\u001b[0m in \u001b[0;36mg\u001b[1;34m(y)\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 6\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m+\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[1;32m/home/fperez/ipython/tutorial/notebooks/mod.py\u001b[0m in \u001b[0;36mf\u001b[1;34m(x)\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[1;36m1.0\u001b[0m\u001b[1;33m/\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;31mZeroDivisionError\u001b[0m: float division by zero" - ] - } - ], - "prompt_number": 45 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%debug" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "> \u001b[0;32m/Users/bussonniermatthias/ipython-in-depth/notebooks/mod.py\u001b[0m(3)\u001b[0;36mf\u001b[0;34m()\u001b[0m\n", - "\u001b[0;32m 2 \u001b[0;31m\u001b[0;32mdef\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[0;32m----> 3 \u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0;36m1.0\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[0;32m 4 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0m\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "stream": "stdout", - "text": [ - "ipdb> x\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "1\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "stream": "stdout", - "text": [ - "ipdb> up\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "> \u001b[0;32m/Users/bussonniermatthias/ipython-in-depth/notebooks/mod.py\u001b[0m(6)\u001b[0;36mg\u001b[0;34m()\u001b[0m\n", - "\u001b[0;32m 4 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[0;32m 5 \u001b[0;31m\u001b[0;32mdef\u001b[0m \u001b[0mg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[0;32m----> 6 \u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m+\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0m\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "stream": "stdout", - "text": [ - "ipdb> y\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "0\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "stream": "stdout", - "text": [ - "ipdb> up\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "> \u001b[0;32m\u001b[0m(1)\u001b[0;36m\u001b[0;34m()\u001b[0m\n", - "\u001b[0;32m----> 1 \u001b[0;31m\u001b[0mmod\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0m\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "stream": "stdout", - "text": [ - "ipdb> exit\n" - ] - } - ], - "prompt_number": 38 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Don't foget to exit your debugging session. Raw input can of course be use to ask for user input:" + } + ], + "source": [ + "%%perl\n", + "@months = (\"July\", \"August\", \"September\");\n", + "print $months[0];" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello World!\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "enjoy = raw_input('Are you enjoying this tutorial ?')\n", - "print 'enjoy is :', enjoy" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "stream": "stdout", - "text": [ - "Are you enjoying this tutorial ?Yes !\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "enjoy is : Yes !\n" - ] - } - ], - "prompt_number": 39 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Plotting in the notebook" + } + ], + "source": [ + "%%ruby\n", + "name = \"world\"\n", + "puts \"Hello #{name.capitalize}!\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exercise" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Write a cell that executes in Bash and prints your current working directory as well as the date.\n", + "\n", + "Apologies to Windows users who may not have Bash available, not sure how to obtain the equivalent result with `cmd.exe` or Powershell." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%load soln/bash-script" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Raw Input in the notebook" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since 1.0 the IPython notebook web application support `raw_input` which for example allow us to invoke the `%debug` magic in the notebook:" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "ename": "ZeroDivisionError", + "evalue": "float division by zero", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mmod\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32m/home/fperez/ipython/tutorial/notebooks/mod.py\u001b[0m in \u001b[0;36mg\u001b[1;34m(y)\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 6\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m+\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32m/home/fperez/ipython/tutorial/notebooks/mod.py\u001b[0m in \u001b[0;36mf\u001b[1;34m(x)\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 3\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[1;36m1.0\u001b[0m\u001b[1;33m/\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mg\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mZeroDivisionError\u001b[0m: float division by zero" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This magic configures matplotlib to render its figures inline:" + } + ], + "source": [ + "mod.g(0)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "> \u001b[0;32m/Users/bussonniermatthias/ipython-in-depth/notebooks/mod.py\u001b[0m(3)\u001b[0;36mf\u001b[0;34m()\u001b[0m\n", + "\u001b[0;32m 2 \u001b[0;31m\u001b[0;32mdef\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m----> 3 \u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0;36m1.0\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 4 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\n", + "ipdb> x\n", + "1\n", + "ipdb> up\n", + "> \u001b[0;32m/Users/bussonniermatthias/ipython-in-depth/notebooks/mod.py\u001b[0m(6)\u001b[0;36mg\u001b[0;34m()\u001b[0m\n", + "\u001b[0;32m 4 \u001b[0;31m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m 5 \u001b[0;31m\u001b[0;32mdef\u001b[0m \u001b[0mg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[0;32m----> 6 \u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m+\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\n", + "ipdb> y\n", + "0\n", + "ipdb> up\n", + "> \u001b[0;32m\u001b[0m(1)\u001b[0;36m\u001b[0;34m()\u001b[0m\n", + "\u001b[0;32m----> 1 \u001b[0;31m\u001b[0mmod\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mg\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0m\n", + "ipdb> exit\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%matplotlib inline" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 46 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import numpy as np\n", - "import matplotlib.pyplot as plt" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 47 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x = np.linspace(0, 2*np.pi, 300)\n", - "y = np.sin(x**2)\n", - "plt.plot(x, y)\n", - "plt.title(\"A little chirp\")\n", - "fig = plt.gcf() # let's keep the figure object around for later..." - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "display_data", - "png": "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\n", - "text": [ - "" - ] - } - ], - "prompt_number": 48 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "The IPython kernel/client model" + } + ], + "source": [ + "%debug" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Don't foget to exit your debugging session. Raw input can of course be use to ask for user input:" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Are you enjoying this tutorial ?Yes !\n", + "enjoy is : Yes !\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%connect_info" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "{\n", - " \"stdin_port\": 50023, \n", - " \"ip\": \"127.0.0.1\", \n", - " \"control_port\": 50024, \n", - " \"hb_port\": 50025, \n", - " \"signature_scheme\": \"hmac-sha256\", \n", - " \"key\": \"b54b8859-d64d-48bb-814a-909f9beb3316\", \n", - " \"shell_port\": 50021, \n", - " \"transport\": \"tcp\", \n", - " \"iopub_port\": 50022\n", - "}\n", - "\n", - "Paste the above JSON into a file, and connect with:\n", - " $> ipython --existing \n", - "or, if you are local, you can connect with just:\n", - " $> ipython --existing kernel-30f00f4a-230c-4e64-bea5-0e5f6a52cb40.json \n", - "or even just:\n", - " $> ipython --existing \n", - "if this is the most recent IPython session you have started.\n" - ] - } - ], - "prompt_number": 43 - }, - { - "cell_type": "markdown", + } + ], + "source": [ + "enjoy = raw_input('Are you enjoying this tutorial ?')\n", + "print 'enjoy is :', enjoy" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plotting in the notebook" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This magic configures matplotlib to render its figures inline:" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": [ + "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\n" + ], + "text/plain": [ + "" + ] + }, "metadata": {}, - "source": [ - "We can connect automatically a Qt Console to the currently running kernel with the `%qtconsole` magic, or by typing `ipython console --existing ` in any terminal:" + "output_type": "display_data" + } + ], + "source": [ + "x = np.linspace(0, 2*np.pi, 300)\n", + "y = np.sin(x**2)\n", + "plt.plot(x, y)\n", + "plt.title(\"A little chirp\")\n", + "fig = plt.gcf() # let's keep the figure object around for later..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The IPython kernel/client model" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"stdin_port\": 50023, \n", + " \"ip\": \"127.0.0.1\", \n", + " \"control_port\": 50024, \n", + " \"hb_port\": 50025, \n", + " \"signature_scheme\": \"hmac-sha256\", \n", + " \"key\": \"b54b8859-d64d-48bb-814a-909f9beb3316\", \n", + " \"shell_port\": 50021, \n", + " \"transport\": \"tcp\", \n", + " \"iopub_port\": 50022\n", + "}\n", + "\n", + "Paste the above JSON into a file, and connect with:\n", + " $> ipython --existing \n", + "or, if you are local, you can connect with just:\n", + " $> ipython --existing kernel-30f00f4a-230c-4e64-bea5-0e5f6a52cb40.json \n", + "or even just:\n", + " $> ipython --existing \n", + "if this is the most recent IPython session you have started.\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%qtconsole" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 83 } ], - "metadata": {} + "source": [ + "%connect_info" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can connect automatically a Qt Console to the currently running kernel with the `%qtconsole` magic, or by typing `ipython console --existing ` in any terminal:" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%qtconsole" + ] } - ] + ], + "metadata": { + "signature": "sha256:31071a05d0ecd75ed72fe3f0de0ad447a6f85cffe382c26efa5e68db1fee54ee" + }, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Capturing Output.ipynb b/examples/IPython Kernel/Capturing Output.ipynb index 6365a89..2cc7970 100644 --- a/examples/IPython Kernel/Capturing Output.ipynb +++ b/examples/IPython Kernel/Capturing Output.ipynb @@ -1,332 +1,489 @@ { - "metadata": { - "name": "", - "signature": "sha256:df6354daf203e842bc040989d149760382d8ceec769160e4efe8cde9dfcb9107" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Capturing Output With %%capture" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "IPython has a [cell magic](Cell Magics.ipynb), `%%capture`, which captures the stdout/stderr of a cell. With this magic you can discard these streams or store them in a variable." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from __future__ import print_function\n", - "import sys" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 9 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "By default, `%%capture` discards these streams. This is a simple way to suppress unwanted output." - ] - }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Capturing Output With %%capture" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "IPython has a [cell magic](Cell Magics.ipynb), `%%capture`, which captures the stdout/stderr of a cell. With this magic you can discard these streams or store them in a variable." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from __future__ import print_function\n", + "import sys" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "By default, `%%capture` discards these streams. This is a simple way to suppress unwanted output." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%capture\n", + "print('hi, stdout')\n", + "print('hi, stderr', file=sys.stderr)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you specify a name, then stdout/stderr will be stored in an object in your namespace." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%capture captured\n", + "print('hi, stdout')\n", + "print('hi, stderr', file=sys.stderr)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%capture\n", - "print('hi, stdout')\n", - "print('hi, stderr', file=sys.stderr)" - ], - "language": "python", + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 12, "metadata": {}, - "outputs": [], - "prompt_number": 10 - }, + "output_type": "execute_result" + } + ], + "source": [ + "captured" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Calling the object writes the output to stdout/stderr as appropriate." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If you specify a name, then stdout/stderr will be stored in an object in your namespace." + "name": "stdout", + "output_type": "stream", + "text": [ + "hi, stdout\n" ] }, { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%capture captured\n", - "print('hi, stdout')\n", - "print('hi, stderr', file=sys.stderr)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 11 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "captured" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 12, - "text": [ - "" - ] - } - ], - "prompt_number": 12 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Calling the object writes the output to stdout/stderr as appropriate." + "name": "stderr", + "output_type": "stream", + "text": [ + "hi, stderr\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "captured()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "hi, stdout\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "hi, stderr\n" - ] - } - ], - "prompt_number": 13 - }, + } + ], + "source": [ + "captured()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "captured.stdout" - ], - "language": "python", + "data": { + "text/plain": [ + "'hi, stdout\\n'" + ] + }, + "execution_count": 14, "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 14, - "text": [ - "'hi, stdout\\n'" - ] - } - ], - "prompt_number": 14 - }, + "output_type": "execute_result" + } + ], + "source": [ + "captured.stdout" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "captured.stderr" - ], - "language": "python", + "data": { + "text/plain": [ + "'hi, stderr\\n'" + ] + }, + "execution_count": 15, "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 15, - "text": [ - "'hi, stderr\\n'" - ] - } - ], - "prompt_number": 15 - }, + "output_type": "execute_result" + } + ], + "source": [ + "captured.stderr" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`%%capture` grabs all output types, not just stdout/stderr, so you can do plots and use IPython's display system inside `%%capture`" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%capture wontshutup\n", + "\n", + "print(\"setting up X\")\n", + "x = np.linspace(0,5,1000)\n", + "print(\"step 2: constructing y-data\")\n", + "y = np.sin(x)\n", + "print(\"step 3: display info about y\")\n", + "plt.plot(x,y)\n", + "print(\"okay, I'm done now\")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`%%capture` grabs all output types, not just stdout/stderr, so you can do plots and use IPython's display system inside `%%capture`" + "name": "stdout", + "output_type": "stream", + "text": [ + "setting up X\n", + "step 2: constructing y-data\n", + "step 3: display info about y\n", + "okay, I'm done now\n" ] }, { - "cell_type": "code", - "collapsed": false, - "input": [ - "%matplotlib inline\n", - "import matplotlib.pyplot as plt\n", - "import numpy as np" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 16 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%capture wontshutup\n", - "\n", - "print(\"setting up X\")\n", - "x = np.linspace(0,5,1000)\n", - "print(\"step 2: constructing y-data\")\n", - "y = np.sin(x)\n", - "print(\"step 3: display info about y\")\n", - "plt.plot(x,y)\n", - "print(\"okay, I'm done now\")" - ], - "language": "python", + "data": { + "image/png": [ + "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\n", + "4uLiuPw8ay2tjujz8/Np2LAhK1asIDw8nNatW5Oamhq07Z1Vq1ZRuXJlbr/9djZu3Gg7jjW7du1i\n", + "165dtGjRgl9++YVWrVqxcOHCoP3v4tChQ1SsWJFjx47Rrl07nnvuOdq1a2c7ljUvvPACGzZs4MCB\n", + "AyxevNh2HGvq1q3Lhg0bqF69+nmftTqi14aq0yUkJFCtWjXbMayrWbMmLVq0AKBy5crExsbyfVlu\n", + "Q3SYihUrAnDkyBHy8/OL9BfbrXJyckhLS+OOO+7QabcUvhDmTFYLfUEbqnJzcy0mEqfZsWMHWVlZ\n", + "tGnTxnYUa44fP06LFi0ICwujQ4cONGrUyHYka+6//34mT55MuXLWu87WhYSE0LlzZ+Lj45k+ffo5\n", + "n7X63dKGKjmXX375hZtvvpkpU6ZQuXJl23GsKVeuHJ9//jk5OTl88sknQXsUwpIlS7jsssuIi4vT\n", + "aB5IT08nKyuLpUuX8ve//51Vq1YV+qzVQh8eHk52dvbJj7Ozs4mIiLCYSJzi6NGj3HTTTdx66630\n", + "7NnTdhxHqFKlCt27d2f9+vW2o1ixevVqFi9eTN26denXrx8fffQRt99+u+1Y1tSqVQuAP/3pT9x4\n", + "441kZmYW+qzVQv/HDVVHjhxh7ty5JCUl2YwkDuD1ehk8eDCNGjVi5MiRtuNYtWfPHvbt2wfA4cOH\n", + "+eCDD4iLi7Ocyo6JEyeSnZ3Nt99+y5w5c+jYsSOzZs2yHcuKQ4cOceDAAQAOHjzI8uXLz7laz2qh\n", + "14aq0/VNJLb3AAAAlUlEQVTr14+2bduydetWIiMjmTFjhu1IVqSnpzN79mxWrlxJXFwccXFxLFu2\n", + "zHYsK3bu3EnHjh1p0aIFbdq0oUePHnTq1Ml2LEcI5tZvXl4eCQkJJ/+7uP7667nmmmsKfV4bpkRE\n", + "XE5T1yIiLqdCLyLicir0IiIup0IvIuJyKvQiIi6nQi8i4nIq9CIiLqdCLyLicv8PoaUAhzXYTeQA\n", + "AAAASUVORK5CYII=\n" + ], + "text/plain": [ + "" + ] + }, "metadata": {}, - "outputs": [], - "prompt_number": 17 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "wontshutup()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "setting up X\n", - "step 2: constructing y-data\n", - "step 3: display info about y\n", - "okay, I'm done now\n" - ] - }, - { - "metadata": {}, - "output_type": "display_data", - "png": "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\nEfGbE9cVJiSYYxQ8HliyxByYdvCgafFceaW53aphQ3OoWlFOzNy7F/7v/8xr3TpYs8b8vm1bc2TD\no49CvXolz33OQt+lSxd27dp11j+fOHEiPXr0OO8XDwkJKXkyEREHu+giuPZa80pJgZwcU6AzMuCV\nV8yoPzcXqlUzxyzUqAEXXGA+1+uF/fvNaZp79ph/1qCBebVqZVb8tGwJF17oo7DeUkpMTPRu2LCh\nwD9bs2aNt2vXric/njhxonfSpEkFPluvXj0voJdeeumlVzFe9erVO2+d9knrxltIcyo+Pp5t27ax\nY8cOateuzdy5c0lNTS3w2e1OXL8kIuICJZ6MXbBgAZGRkWRkZNC9e3euu+46AL7//nu6d+8OQGho\nKCkpKXTt2pVGjRrRp08fYmNjfZNcRESKxDFHIIiIiH9Y3xmrDVWnDBo0iLCwMJoGyiEbfpKdnU2H\nDh1o3LgxTZo04aWXXrIdyZpff/2VNm3a0KJFCxo1asQjjzxiO5J1+fn5xMXFFWlBiJtFRUXRrFkz\n4uLiuPw8ay2tjujz8/Np2LAhK1asIDw8nNatW5Oamhq07Z1Vq1ZRuXJlbr/9djZu3Gg7jjW7du1i\n165dtGjRgl9++YVWrVqxcOHCoP3v4tChQ1SsWJFjx47Rrl07nnvuOdq1a2c7ljUvvPACGzZs4MCB\nAyxevNh2HGvq1q3Lhg0bqF69+nmftTqi14aq0yUkJFCtWjXbMayrWbMmLVq0AKBy5crExsbyfVlu\nQ3SYihUrAnDkyBHy8/OL9BfbrXJyckhLS+OOO+7QabcUvhDmTFYLfUEbqnJzcy0mEqfZsWMHWVlZ\ntGnTxnYUa44fP06LFi0ICwujQ4cONGrUyHYka+6//34mT55MuXLWu87WhYSE0LlzZ+Lj45k+ffo5\nn7X63dKGKjmXX375hZtvvpkpU6ZQuXJl23GsKVeuHJ9//jk5OTl88sknQXsUwpIlS7jsssuIi4vT\naB5IT08nKyuLpUuX8ve//51Vq1YV+qzVQh8eHk52dvbJj7Ozs4mIiLCYSJzi6NGj3HTTTdx66630\n7NnTdhxHqFKlCt27d2f9+vW2o1ixevVqFi9eTN26denXrx8fffQRt99+u+1Y1tSqVQuAP/3pT9x4\n441kZmYW+qzVQv/HDVVHjhxh7ty5JCUl2YwkDuD1ehk8eDCNGjVi5MiRtuNYtWfPHvbt2wfA4cOH\n+eCDD4iLi7Ocyo6JEyeSnZ3Nt99+y5w5c+jYsSOzZs2yHcuKQ4cOceDAAQAOHjzI8uXLz7laz2qh\n14aq0/VNJLb3AAAAlUlEQVTr14+2bduydetWIiMjmTFjhu1IVqSnpzN79mxWrlxJXFwccXFxLFu2\nzHYsK3bu3EnHjh1p0aIFbdq0oUePHnTq1Ml2LEcI5tZvXl4eCQkJJ/+7uP7667nmmmsKfV4bpkRE\nXE5T1yIiLqdCLyLicir0IiIup0IvIuJyKvQiIi6nQi8i4nIq9CIiLqdCLyLicv8PoaUAhzXYTeQA\nAAAASUVORK5CYII=\n", - "text": [ - "" - ] - } - ], - "prompt_number": 18 - }, + "output_type": "display_data" + } + ], + "source": [ + "wontshutup()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And you can selectively disable capturing stdout, stderr or rich display, by passing `--no-stdout`, `--no-stderr` and `--no-display`" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And you can selectively disable capturing stdout, stderr or rich display, by passing `--no-stdout`, `--no-stderr` and `--no-display`" + "name": "stderr", + "output_type": "stream", + "text": [ + "hello, stderr\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%capture cap --no-stderr\n", - "print('hi, stdout')\n", - "print(\"hello, stderr\", file=sys.stderr)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "hello, stderr\n" - ] - } - ], - "prompt_number": 19 - }, + } + ], + "source": [ + "%%capture cap --no-stderr\n", + "print('hi, stdout')\n", + "print(\"hello, stderr\", file=sys.stderr)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "cap.stdout" - ], - "language": "python", + "data": { + "text/plain": [ + "'hi, stdout\\n'" + ] + }, + "execution_count": 20, "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 20, - "text": [ - "'hi, stdout\\n'" - ] - } - ], - "prompt_number": 20 - }, + "output_type": "execute_result" + } + ], + "source": [ + "cap.stdout" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "cap.stderr" - ], - "language": "python", + "data": { + "text/plain": [ + "''" + ] + }, + "execution_count": 21, "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 21, - "text": [ - "''" - ] - } - ], - "prompt_number": 21 - }, + "output_type": "execute_result" + } + ], + "source": [ + "cap.stderr" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "cap.outputs" - ], - "language": "python", + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 22, "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 22, - "text": [ - "[]" - ] - } - ], - "prompt_number": 22 + "output_type": "execute_result" } ], - "metadata": {} + "source": [ + "cap.outputs" + ] } - ] + ], + "metadata": { + "signature": "sha256:df6354daf203e842bc040989d149760382d8ceec769160e4efe8cde9dfcb9107" + }, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Cell Magics.ipynb b/examples/IPython Kernel/Cell Magics.ipynb index bfa6705..f7dc4a2 100644 --- a/examples/IPython Kernel/Cell Magics.ipynb +++ b/examples/IPython Kernel/Cell Magics.ipynb @@ -1,593 +1,699 @@ { - "metadata": { - "name": "" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ - { - "cells": [ - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Cell Magics in IPython" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "IPython has a system of commands we call 'magics' that provide effectively a mini command language that is orthogonal to the syntax of Python and is extensible by the user with new commands. Magics are meant to be typed interactively, so they use command-line conventions, such as using whitespace for separating arguments, dashes for options and other conventions typical of a command-line environment.\n", - "\n", - "Magics come in two kinds:\n", - "\n", - "* Line magics: these are commands prepended by one `%` character and whose arguments only extend to the end of the current line.\n", - "* Cell magics: these use *two* percent characters as a marker (`%%`), and they receive as argument *both* the current line where they are declared and the whole body of the cell. Note that cell magics can *only* be used as the first line in a cell, and as a general principle they can't be 'stacked' (i.e. you can only use one cell magic per cell). A few of them, because of how they operate, can be stacked, but that is something you will discover on a case by case basis.\n", - "\n", - "The `%lsmagic` magic is used to list all available magics, and it will show both line and cell magics currently defined:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%lsmagic" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "json": [ - "{\"cell\": {\"prun\": \"ExecutionMagics\", \"file\": \"Other\", \"!\": \"OSMagics\", \"capture\": \"ExecutionMagics\", \"timeit\": \"ExecutionMagics\", \"script\": \"ScriptMagics\", \"ruby\": \"Other\", \"system\": \"OSMagics\", \"perl\": \"Other\", \"HTML\": \"Other\", \"bash\": \"Other\", \"python\": \"Other\", \"SVG\": \"Other\", \"javascript\": \"DisplayMagics\", \"writefile\": \"OSMagics\", \"pypy\": \"Other\", \"python3\": \"Other\", \"latex\": \"DisplayMagics\", \"sx\": \"OSMagics\", \"svg\": \"DisplayMagics\", \"html\": \"DisplayMagics\", \"sh\": \"Other\", \"time\": \"ExecutionMagics\", \"debug\": \"ExecutionMagics\"}, \"line\": {\"psource\": \"NamespaceMagics\", \"logstart\": \"LoggingMagics\", \"popd\": \"OSMagics\", \"loadpy\": \"CodeMagics\", \"install_ext\": \"ExtensionMagics\", \"colors\": \"BasicMagics\", \"who_ls\": \"NamespaceMagics\", \"install_profiles\": \"DeprecatedMagics\", \"pprint\": \"BasicMagics\", \"save\": \"CodeMagics\", \"tb\": \"ExecutionMagics\", \"pylab\": \"PylabMagics\", \"killbgscripts\": \"ScriptMagics\", \"quickref\": \"BasicMagics\", \"magic\": \"BasicMagics\", \"dhist\": \"OSMagics\", \"edit\": \"KernelMagics\", \"logstop\": \"LoggingMagics\", \"gui\": \"BasicMagics\", \"alias_magic\": \"BasicMagics\", \"debug\": \"ExecutionMagics\", \"page\": \"BasicMagics\", \"logstate\": \"LoggingMagics\", \"ed\": \"Other\", \"pushd\": \"OSMagics\", \"timeit\": \"ExecutionMagics\", \"rehashx\": \"OSMagics\", \"hist\": \"Other\", \"qtconsole\": \"KernelMagics\", \"dirs\": \"OSMagics\", \"run\": \"ExecutionMagics\", \"reset_selective\": \"NamespaceMagics\", \"pinfo2\": \"NamespaceMagics\", \"matplotlib\": \"PylabMagics\", \"automagic\": \"AutoMagics\", \"doctest_mode\": \"KernelMagics\", \"logoff\": \"LoggingMagics\", \"reload_ext\": \"ExtensionMagics\", \"pdb\": \"ExecutionMagics\", \"load\": \"CodeMagics\", \"lsmagic\": \"BasicMagics\", \"autosave\": \"KernelMagics\", \"cd\": \"OSMagics\", \"pastebin\": \"CodeMagics\", \"prun\": \"ExecutionMagics\", \"autocall\": \"AutoMagics\", \"bookmark\": \"OSMagics\", \"connect_info\": \"KernelMagics\", \"system\": \"OSMagics\", \"whos\": \"NamespaceMagics\", \"toc\": \"TimerMagics\", \"unload_ext\": \"ExtensionMagics\", \"store\": \"StoreMagics\", \"more\": \"KernelMagics\", \"gist\": \"Other\", \"pdef\": \"NamespaceMagics\", \"precision\": \"BasicMagics\", \"pinfo\": \"NamespaceMagics\", \"pwd\": \"OSMagics\", \"psearch\": \"NamespaceMagics\", \"reset\": \"NamespaceMagics\", \"recall\": \"HistoryMagics\", \"xdel\": \"NamespaceMagics\", \"xmode\": \"BasicMagics\", \"rerun\": \"HistoryMagics\", \"logon\": \"LoggingMagics\", \"history\": \"HistoryMagics\", \"pycat\": \"OSMagics\", \"unalias\": \"OSMagics\", \"install_default_config\": \"DeprecatedMagics\", \"env\": \"OSMagics\", \"load_ext\": \"ExtensionMagics\", \"config\": \"ConfigMagics\", \"tic\": \"TimerMagics\", \"profile\": \"BasicMagics\", \"pfile\": \"NamespaceMagics\", \"less\": \"KernelMagics\", \"who\": \"NamespaceMagics\", \"notebook\": \"BasicMagics\", \"man\": \"KernelMagics\", \"sx\": \"OSMagics\", \"macro\": \"ExecutionMagics\", \"clear\": \"KernelMagics\", \"alias\": \"OSMagics\", \"time\": \"ExecutionMagics\", \"sc\": \"OSMagics\", \"rep\": \"Other\", \"pdoc\": \"NamespaceMagics\"}}" - ], - "metadata": {}, - "output_type": "pyout", - "prompt_number": 1, - "text": [ - "Available line magics:\n", - "%alias %alias_magic %autocall %automagic %autosave %bookmark %cd %clear %colors %config %connect_info %debug %dhist %dirs %doctest_mode %ed %edit %env %gist %gui %hist %history %install_default_config %install_ext %install_profiles %killbgscripts %less %load %load_ext %loadpy %logoff %logon %logstart %logstate %logstop %lsmagic %macro %magic %man %matplotlib %more %notebook %page %pastebin %pdb %pdef %pdoc %pfile %pinfo %pinfo2 %popd %pprint %precision %profile %prun %psearch %psource %pushd %pwd %pycat %pylab %qtconsole %quickref %recall %rehashx %reload_ext %rep %rerun %reset %reset_selective %run %save %sc %store %sx %system %tb %tic %time %timeit %toc %unalias %unload_ext %who %who_ls %whos %xdel %xmode\n", - "\n", - "Available cell magics:\n", - "%%! %%HTML %%SVG %%bash %%capture %%debug %%file %%html %%javascript %%latex %%perl %%prun %%pypy %%python %%python3 %%ruby %%script %%sh %%svg %%sx %%system %%time %%timeit %%writefile\n", - "\n", - "Automagic is ON, % prefix IS NOT needed for line magics." - ] - } - ], - "prompt_number": 1 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Since in the introductory section we already covered the most frequently used line magics, we will focus here on the cell magics, which offer a great amount of power.\n", - "\n", - "Let's load matplotlib and numpy so we can use numerics/plotting at will later on." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%matplotlib inline\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 2 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "\n", - "Some simple cell magics" + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Cell Magics in IPython" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "IPython has a system of commands we call 'magics' that provide effectively a mini command language that is orthogonal to the syntax of Python and is extensible by the user with new commands. Magics are meant to be typed interactively, so they use command-line conventions, such as using whitespace for separating arguments, dashes for options and other conventions typical of a command-line environment.\n", + "\n", + "Magics come in two kinds:\n", + "\n", + "* Line magics: these are commands prepended by one `%` character and whose arguments only extend to the end of the current line.\n", + "* Cell magics: these use *two* percent characters as a marker (`%%`), and they receive as argument *both* the current line where they are declared and the whole body of the cell. Note that cell magics can *only* be used as the first line in a cell, and as a general principle they can't be 'stacked' (i.e. you can only use one cell magic per cell). A few of them, because of how they operate, can be stacked, but that is something you will discover on a case by case basis.\n", + "\n", + "The `%lsmagic` magic is used to list all available magics, and it will show both line and cell magics currently defined:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "application/json": { + "cell": { + "!": "OSMagics", + "HTML": "Other", + "SVG": "Other", + "bash": "Other", + "capture": "ExecutionMagics", + "debug": "ExecutionMagics", + "file": "Other", + "html": "DisplayMagics", + "javascript": "DisplayMagics", + "latex": "DisplayMagics", + "perl": "Other", + "prun": "ExecutionMagics", + "pypy": "Other", + "python": "Other", + "python3": "Other", + "ruby": "Other", + "script": "ScriptMagics", + "sh": "Other", + "svg": "DisplayMagics", + "sx": "OSMagics", + "system": "OSMagics", + "time": "ExecutionMagics", + "timeit": "ExecutionMagics", + "writefile": "OSMagics" + }, + "line": { + "alias": "OSMagics", + "alias_magic": "BasicMagics", + "autocall": "AutoMagics", + "automagic": "AutoMagics", + "autosave": "KernelMagics", + "bookmark": "OSMagics", + "cd": "OSMagics", + "clear": "KernelMagics", + "colors": "BasicMagics", + "config": "ConfigMagics", + "connect_info": "KernelMagics", + "debug": "ExecutionMagics", + "dhist": "OSMagics", + "dirs": "OSMagics", + "doctest_mode": "KernelMagics", + "ed": "Other", + "edit": "KernelMagics", + "env": "OSMagics", + "gist": "Other", + "gui": "BasicMagics", + "hist": "Other", + "history": "HistoryMagics", + "install_default_config": "DeprecatedMagics", + "install_ext": "ExtensionMagics", + "install_profiles": "DeprecatedMagics", + "killbgscripts": "ScriptMagics", + "less": "KernelMagics", + "load": "CodeMagics", + "load_ext": "ExtensionMagics", + "loadpy": "CodeMagics", + "logoff": "LoggingMagics", + "logon": "LoggingMagics", + "logstart": "LoggingMagics", + "logstate": "LoggingMagics", + "logstop": "LoggingMagics", + "lsmagic": "BasicMagics", + "macro": "ExecutionMagics", + "magic": "BasicMagics", + "man": "KernelMagics", + "matplotlib": "PylabMagics", + "more": "KernelMagics", + "notebook": "BasicMagics", + "page": "BasicMagics", + "pastebin": "CodeMagics", + "pdb": "ExecutionMagics", + "pdef": "NamespaceMagics", + "pdoc": "NamespaceMagics", + "pfile": "NamespaceMagics", + "pinfo": "NamespaceMagics", + "pinfo2": "NamespaceMagics", + "popd": "OSMagics", + "pprint": "BasicMagics", + "precision": "BasicMagics", + "profile": "BasicMagics", + "prun": "ExecutionMagics", + "psearch": "NamespaceMagics", + "psource": "NamespaceMagics", + "pushd": "OSMagics", + "pwd": "OSMagics", + "pycat": "OSMagics", + "pylab": "PylabMagics", + "qtconsole": "KernelMagics", + "quickref": "BasicMagics", + "recall": "HistoryMagics", + "rehashx": "OSMagics", + "reload_ext": "ExtensionMagics", + "rep": "Other", + "rerun": "HistoryMagics", + "reset": "NamespaceMagics", + "reset_selective": "NamespaceMagics", + "run": "ExecutionMagics", + "save": "CodeMagics", + "sc": "OSMagics", + "store": "StoreMagics", + "sx": "OSMagics", + "system": "OSMagics", + "tb": "ExecutionMagics", + "tic": "TimerMagics", + "time": "ExecutionMagics", + "timeit": "ExecutionMagics", + "toc": "TimerMagics", + "unalias": "OSMagics", + "unload_ext": "ExtensionMagics", + "who": "NamespaceMagics", + "who_ls": "NamespaceMagics", + "whos": "NamespaceMagics", + "xdel": "NamespaceMagics", + "xmode": "BasicMagics" + } + }, + "text/plain": [ + "Available line magics:\n", + "%alias %alias_magic %autocall %automagic %autosave %bookmark %cd %clear %colors %config %connect_info %debug %dhist %dirs %doctest_mode %ed %edit %env %gist %gui %hist %history %install_default_config %install_ext %install_profiles %killbgscripts %less %load %load_ext %loadpy %logoff %logon %logstart %logstate %logstop %lsmagic %macro %magic %man %matplotlib %more %notebook %page %pastebin %pdb %pdef %pdoc %pfile %pinfo %pinfo2 %popd %pprint %precision %profile %prun %psearch %psource %pushd %pwd %pycat %pylab %qtconsole %quickref %recall %rehashx %reload_ext %rep %rerun %reset %reset_selective %run %save %sc %store %sx %system %tb %tic %time %timeit %toc %unalias %unload_ext %who %who_ls %whos %xdel %xmode\n", + "\n", + "Available cell magics:\n", + "%%! %%HTML %%SVG %%bash %%capture %%debug %%file %%html %%javascript %%latex %%perl %%prun %%pypy %%python %%python3 %%ruby %%script %%sh %%svg %%sx %%system %%time %%timeit %%writefile\n", + "\n", + "Automagic is ON, % prefix IS NOT needed for line magics." + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%lsmagic" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since in the introductory section we already covered the most frequently used line magics, we will focus here on the cell magics, which offer a great amount of power.\n", + "\n", + "Let's load matplotlib and numpy so we can use numerics/plotting at will later on." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Some simple cell magics" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Timing the execution of code; the 'timeit' magic exists both in line and cell form:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100 loops, best of 3: 6.05 ms per loop\n" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Timing the execution of code; the 'timeit' magic exists both in line and cell form:" + } + ], + "source": [ + "%timeit np.linalg.eigvals(np.random.rand(100,100))" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100 loops, best of 3: 6.07 ms per loop\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%timeit np.linalg.eigvals(np.random.rand(100,100))" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "100 loops, best of 3: 6.05 ms per loop\n" - ] - } - ], - "prompt_number": 3 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%timeit a = np.random.rand(100, 100)\n", - "np.linalg.eigvals(a)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "100 loops, best of 3: 6.07 ms per loop\n" - ] - } - ], - "prompt_number": 4 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `%%capture` magic can be used to capture the stdout/err of any block of python code, either to discard it (if it's noise to you) or to store it in a variable for later use:" + } + ], + "source": [ + "%%timeit a = np.random.rand(100, 100)\n", + "np.linalg.eigvals(a)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `%%capture` magic can be used to capture the stdout/err of any block of python code, either to discard it (if it's noise to you) or to store it in a variable for later use:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%capture capt\n", + "from __future__ import print_function\n", + "import sys\n", + "print('Hello stdout')\n", + "print('and stderr', file=sys.stderr)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "('Hello stdout\\n', 'and stderr\\n')" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "capt.stdout, capt.stderr" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello stdout\n" ] }, { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%capture capt\n", - "from __future__ import print_function\n", - "import sys\n", - "print('Hello stdout')\n", - "print('and stderr', file=sys.stderr)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 5 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "capt.stdout, capt.stderr" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 6, - "text": [ - "('Hello stdout\\n', 'and stderr\\n')" - ] - } - ], - "prompt_number": 6 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "capt.show()" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Hello stdout\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "and stderr\n" - ] - } - ], - "prompt_number": 7 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `%%writefile` magic is a very useful tool that writes the cell contents as a named file:" + "name": "stderr", + "output_type": "stream", + "text": [ + "and stderr\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%writefile foo.py\n", - "print('Hello world')" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Writing foo.py\n" - ] - } - ], - "prompt_number": 8 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%run foo" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Hello world\n" - ] - } - ], - "prompt_number": 9 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "\n", - "Magics for running code under other interpreters" + } + ], + "source": [ + "capt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `%%writefile` magic is a very useful tool that writes the cell contents as a named file:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing foo.py\n" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "IPython has a `%%script` cell magic, which lets you run a cell in\n", - "a subprocess of any interpreter on your system, such as: bash, ruby, perl, zsh, R, etc.\n", - "\n", - "It can even be a script of your own, which expects input on stdin." + } + ], + "source": [ + "%%writefile foo.py\n", + "print('Hello world')" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello world\n" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To use it, simply pass a path or shell command to the program you want to run on the `%%script` line,\n", - "and the rest of the cell will be run by that script, and stdout/err from the subprocess are captured and displayed." + } + ], + "source": [ + "%run foo" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Magics for running code under other interpreters" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "IPython has a `%%script` cell magic, which lets you run a cell in\n", + "a subprocess of any interpreter on your system, such as: bash, ruby, perl, zsh, R, etc.\n", + "\n", + "It can even be a script of your own, which expects input on stdin." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To use it, simply pass a path or shell command to the program you want to run on the `%%script` line,\n", + "and the rest of the cell will be run by that script, and stdout/err from the subprocess are captured and displayed." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello from Python 2.7.2 (default, Oct 11 2012, 20:14:37) \n", + "[GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)]\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%script python\n", - "import sys\n", - "print 'hello from Python %s' % sys.version" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "hello from Python 2.7.2 (default, Oct 11 2012, 20:14:37) \n", - "[GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)]\n" - ] - } - ], - "prompt_number": 10 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%script python3\n", - "import sys\n", - "print('hello from Python: %s' % sys.version)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "hello from Python: 3.3.1 (v3.3.1:d9893d13c628, Apr 6 2013, 11:07:11) \n", - "[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)]\n" - ] - } - ], - "prompt_number": 11 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "IPython also creates aliases for a few common interpreters, such as bash, ruby, perl, etc.\n", - "\n", - "These are all equivalent to `%%script `" + } + ], + "source": [ + "%%script python\n", + "import sys\n", + "print 'hello from Python %s' % sys.version" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello from Python: 3.3.1 (v3.3.1:d9893d13c628, Apr 6 2013, 11:07:11) \n", + "[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)]\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%ruby\n", - "puts \"Hello from Ruby #{RUBY_VERSION}\"" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Hello from Ruby 1.9.3\n" - ] - } - ], - "prompt_number": 12 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%bash\n", - "echo \"hello from $BASH\"" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "hello from /usr/local/bin/bash\n" - ] - } - ], - "prompt_number": 13 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Exercise: write your own script that numbers input lines" + } + ], + "source": [ + "%%script python3\n", + "import sys\n", + "print('hello from Python: %s' % sys.version)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "IPython also creates aliases for a few common interpreters, such as bash, ruby, perl, etc.\n", + "\n", + "These are all equivalent to `%%script `" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Hello from Ruby 1.9.3\n" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Write a file, called `lnum.py`, such that the following cell works as shown (hint: don't forget about the executable bit!): " + } + ], + "source": [ + "%%ruby\n", + "puts \"Hello from Ruby #{RUBY_VERSION}\"" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello from /usr/local/bin/bash\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%script ./lnum.py\n", - "my first line\n", - "my second\n", - "more" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "0: my first line\n", - "1: my second\n", - "2: more\n", - "---- END ----\n" - ] - } - ], - "prompt_number": 29 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Capturing output" + } + ], + "source": [ + "%%bash\n", + "echo \"hello from $BASH\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exercise: write your own script that numbers input lines" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Write a file, called `lnum.py`, such that the following cell works as shown (hint: don't forget about the executable bit!): " + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0: my first line\n", + "1: my second\n", + "2: more\n", + "---- END ----\n" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can also capture stdout/err from these subprocesses into Python variables, instead of letting them go directly to stdout/err" + } + ], + "source": [ + "%%script ./lnum.py\n", + "my first line\n", + "my second\n", + "more" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Capturing output" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also capture stdout/err from these subprocesses into Python variables, instead of letting them go directly to stdout/err" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hi, stdout\n" ] }, { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%bash\n", - "echo \"hi, stdout\"\n", - "echo \"hello, stderr\" >&2\n" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "hi, stdout\n" - ] - }, - { - "output_type": "stream", - "stream": "stderr", - "text": [ - "hello, stderr\n" - ] - } - ], - "prompt_number": 30 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%bash --out output --err error\n", - "echo \"hi, stdout\"\n", - "echo \"hello, stderr\" >&2" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 31 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print(error)\n", - "print(output)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "hello, stderr\n", - "\n", - "hi, stdout\n", - "\n" - ] - } - ], - "prompt_number": 32 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Background Scripts" + "name": "stderr", + "output_type": "stream", + "text": [ + "hello, stderr\n" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "These scripts can be run in the background, by adding the `--bg` flag.\n", + } + ], + "source": [ + "%%bash\n", + "echo \"hi, stdout\"\n", + "echo \"hello, stderr\" >&2\n" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%%bash --out output --err error\n", + "echo \"hi, stdout\"\n", + "echo \"hello, stderr\" >&2" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "hello, stderr\n", "\n", - "When you do this, output is discarded unless you use the `--out/err`\n", - "flags to store output as above." + "hi, stdout\n", + "\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%ruby --bg --out ruby_lines\n", - "for n in 1...10\n", - " sleep 1\n", - " puts \"line #{n}\"\n", - " STDOUT.flush\n", - "end" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Starting job # 0 in a separate thread.\n" - ] - } - ], - "prompt_number": 33 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When you do store output of a background thread, these are the stdout/err *pipes*,\n", - "rather than the text of the output." + } + ], + "source": [ + "print(error)\n", + "print(output)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Background Scripts" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "These scripts can be run in the background, by adding the `--bg` flag.\n", + "\n", + "When you do this, output is discarded unless you use the `--out/err`\n", + "flags to store output as above." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Starting job # 0 in a separate thread.\n" + ] + } + ], + "source": [ + "%%ruby --bg --out ruby_lines\n", + "for n in 1...10\n", + " sleep 1\n", + " puts \"line #{n}\"\n", + " STDOUT.flush\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When you do store output of a background thread, these are the stdout/err *pipes*,\n", + "rather than the text of the output." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "', mode 'rb' at 0x112cd55d0>" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ruby_lines" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "line 1\n", + "line 2\n", + "line 3\n", + "line 4\n", + "line 5\n", + "line 6\n", + "line 7\n", + "line 8\n", + "line 9\n", + "\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "ruby_lines" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 34, - "text": [ - "', mode 'rb' at 0x112cd55d0>" - ] - } - ], - "prompt_number": 34 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print(ruby_lines.read())" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "line 1\n", - "line 2\n", - "line 3\n", - "line 4\n", - "line 5\n", - "line 6\n", - "line 7\n", - "line 8\n", - "line 9\n", - "\n" - ] - } - ], - "prompt_number": 35 } ], - "metadata": {} + "source": [ + "print(ruby_lines.read())" + ] } - ] + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Custom Display Logic.ipynb b/examples/IPython Kernel/Custom Display Logic.ipynb index 2e23ac4..5b32c8b 100644 --- a/examples/IPython Kernel/Custom Display Logic.ipynb +++ b/examples/IPython Kernel/Custom Display Logic.ipynb @@ -1,787 +1,1325 @@ { - "metadata": { - "name": "", - "signature": "sha256:86c779d5798c4a68bda7e71c8ef320cb7ba9d7e3d0f1bc4b828ee65f617a5ae3" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Custom Display Logic" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Overview" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As described in the [Rich Output](Rich Output.ipynb) tutorial, the IPython display system can display rich representations of objects in the following formats:\n", + "\n", + "* JavaScript\n", + "* HTML\n", + "* PNG\n", + "* JPEG\n", + "* SVG\n", + "* LaTeX\n", + "* PDF\n", + "\n", + "This Notebook shows how you can add custom display logic to your own classes, so that they can be displayed using these rich representations. There are two ways of accomplishing this:\n", + "\n", + "1. Implementing special display methods such as `_repr_html_` when you define your class.\n", + "2. Registering a display function for a particular existing class.\n", + "\n", + "This Notebook describes and illustrates both approaches." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Import the IPython display functions." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from IPython.display import (\n", + " display, display_html, display_png, display_svg\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Parts of this notebook need the matplotlib inline backend:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Special display methods" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The main idea of the first approach is that you have to implement special display methods when you define your class, one for each representation you want to use. Here is a list of the names of the special methods and the values they must return:\n", + "\n", + "* `_repr_html_`: return raw HTML as a string\n", + "* `_repr_json_`: return raw JSON as a string\n", + "* `_repr_jpeg_`: return raw JPEG data\n", + "* `_repr_png_`: return raw PNG data\n", + "* `_repr_svg_`: return raw SVG data as a string\n", + "* `_repr_latex_`: return LaTeX commands in a string surrounded by \"$\"." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As an illustration, we build a class that holds data generated by sampling a Gaussian distribution with given mean and standard deviation. Here is the definition of the `Gaussian` class, which has a custom PNG and LaTeX representation." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from IPython.core.pylabtools import print_figure\n", + "from IPython.display import Image, SVG, Math\n", + "\n", + "class Gaussian(object):\n", + " \"\"\"A simple object holding data sampled from a Gaussian distribution.\n", + " \"\"\"\n", + " def __init__(self, mean=0.0, std=1, size=1000):\n", + " self.data = np.random.normal(mean, std, size)\n", + " self.mean = mean\n", + " self.std = std\n", + " self.size = size\n", + " # For caching plots that may be expensive to compute\n", + " self._png_data = None\n", + " \n", + " def _figure_data(self, format):\n", + " fig, ax = plt.subplots()\n", + " ax.hist(self.data, bins=50)\n", + " ax.set_title(self._repr_latex_())\n", + " ax.set_xlim(-10.0,10.0)\n", + " data = print_figure(fig, format)\n", + " # We MUST close the figure, otherwise IPython's display machinery\n", + " # will pick it up and send it as output, resulting in a double display\n", + " plt.close(fig)\n", + " return data\n", + " \n", + " def _repr_png_(self):\n", + " if self._png_data is None:\n", + " self._png_data = self._figure_data('png')\n", + " return self._png_data\n", + " \n", + " def _repr_latex_(self):\n", + " return r'$\\mathcal{N}(\\mu=%.2g, \\sigma=%.2g),\\ N=%d$' % (self.mean,\n", + " self.std, self.size)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create an instance of the Gaussian distribution and return it to display the default representation:" + ] + }, { - "cells": [ + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "heading", - "level": 1, + "data": { + "image/png": [ + "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\n" + ], + "text/latex": [ + "$\\mathcal{N}(\\mu=2, \\sigma=1),\\ N=1000$" + ], + "text/plain": [ + "<__main__.Gaussian at 0x106e7ae10>" + ] + }, + "execution_count": 4, "metadata": {}, - "source": [ - "Custom Display Logic" - ] - }, + "output_type": "execute_result" + } + ], + "source": [ + "x = Gaussian(2.0, 1.0)\n", + "x" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also pass the object to the `display` function to display the default representation:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "heading", - "level": 2, + "data": { + "image/png": [ + "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\n" + ], + "text/latex": [ + "$\\mathcal{N}(\\mu=2, \\sigma=1),\\ N=1000$" + ], + "text/plain": [ + "<__main__.Gaussian at 0x106e7ae10>" + ] + }, "metadata": {}, - "source": [ - "Overview" - ] - }, + "output_type": "display_data" + } + ], + "source": [ + "display(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Use `display_png` to view the PNG representation:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", + "data": { + "image/png": [ + "iVBORw0KGgoAAAANSUhEUgAAAXIAAAENCAYAAAASUO4dAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n", + "AAALEgAACxIB0t1+/AAAElNJREFUeJzt3X+wXGV9x/H3JpefITfhCr1JSyCIRH5UJbQgrVhXChas\n", + "DXE6onbaBoownRG0tVUSOg63vxQY+8tRoVOQuTIOSq3QQGtNiGyr1SK0IfxqTBNNB2zuhUJoLogk\n", + "kO0fz9ncvZu9956z9+yefXbfr5mdPefZs7tPbnY/++z3POcsSJIkSZIkSZIkSZIkSZIkSZIkSZJ6\n", + "1olFd6DNlgJHFt0JKYt5RXdAXe8sYAtwEyHEzym2O233DPCxojshZVEqugPqGjcD/wrc3tB+E/BV\n", + "4EFgHXBNm57/1wij4bOBu4Avtel5Gp0B/Drw+3VtZwGnAl/oUB+m06xvq4HTgP3AD5n8/8raLqkH\n", + "fQf424a204GLkuU3AVe36blfV/fYxwC76UwJ5yOED6nbmtyWJcTfTPjweQoYSNqGCR9G9wI/n1Pf\n", + "FgH/Xrf+HeA1GduPaaEv6nKWVgQwH7gPOA84vK69DHwjWX5X3XLeTmeynPG/wHbgZ9r0XPX+HPj7\n", + "aW57hvABk8YDwD8B24BfTdrGCSH+HuDbOfXtF4An6ta3EP7PsrS/vYW+qMsNzL6J+sDpwCbCV/mL\n", + "CKNLgCOAl5Pls4BPZHzc1wJXzHD7vxHC6h+ZHPmXCCWW7RmfK+tz1kxXXtxC+DBJ0495wD7g08BH\n", + "gS8n7QuAl3Ls23HA83XrzwMnA89lbFePMcgFoS59O6EU8H5CkB8G7K3b5kigWrc+H/hn4Nxk/Vbg\n", + "k0wNvu8T6uqz2Qc8liz/MvAQ8PA0264A/gQ4FvhZoAL8A6HGn+U5a6rTtO9OniuNMwl9fowwkj4T\n", + "+I8mjz3Xvi0Gfly3vhc4KtkuS7t6jKUVQXhzvwysB94B/AQh3B+o22Z+w31+DvjvZLmUrLc6iq5Z\n", + "DFxK2MHXzBAhsH+TUCLYlGx78zTbpzHdiPwl4NCUj/FG4BHCDsXPEer9rwe+N4d+NevbREPbEYRR\n", + "d9Z29RhH5FoE/ChZniDUeq8CngU+W7fdKw33uxD4erK8Eni0yWNnKSWUgLXAB4AXgBOY/KCo+WDS\n", + "p9oo87C6vrfynDD9iHwR6UOvfkB0C+ED7Qngr3Lu2w7Ct5Ca1xBG/s+nbD8maZfUI44izET5babO\n", + "ZDgT+D8OroePMvVr+YPATyfLHweuBFbNoT8fItSklxC+DbwtaT+ZyaC8kTAtEEJd/1NzeL6aS2k+\n", + "a+Uq4Bfr1uv7Ue8QwjeEejcT6v55920BUz8wtxC+PWVtV49p/Lqs/lEGvkUoCdxb174LeAOwmamj\n", + "tyFCMPyAUJ9eRxjxLSSURBYBOwl14KzOJdTnrwR+D7ic8OEwQZjbvr3u8k7CTr8zCDX5/S08X81V\n", + "hNLMm5J/w2Ymd+5eCfw1k99E6vtRcxZh1H08oQw1kbR/n1DG+FbOfXshuawi/P/dSxjF78vYrj61\n", + "GPgK8J+Er4xvJryxNxKmXG1ItlFchjNsu5iwkxFCwPxp/t1p6lDgrR16rprDCTsti+6HlKtR4LeS\n", + "5QHC6OtGJuf+XgNcX0C/1Fm/QyjDfJrOzPMGeC+d/+Z4KWFnZdH9kHKziOZfl7cyOaJbkqyrt5WY\n", + "eWddL1gGXFx0J6S8nUGo/91GqJn+DaFWurtum1LDuiSpQ9LMIx8gzGT4XHL9ImGaWL0q00/jkiS1\n", + "UZp55E8llweT9a8QZiyMEUoqY4RDqp9uvONJJ51U3bFjRz49laT+sYP05/pJNSIfA55k8nDl84HH\n", + "gXuANUnbGuDug3qyYwfVatVLDpfrrruu8D700sW/p3/Pbr4AJ6UNcUh/ZOfVwBcJU7B2AJcR9uDf\n", + "SZjzuxO4JMsTS5LykTbItxAOfmh0fo59kSS1wJNmRaJcLhfdhZ7i3zNf/j2L1e6feqsm9R5JUkql\n", + "Ugky5LMjckmKnEEuSZEzyCUpcga5JEXOIJekyBnkUocMDg5RKpUYHBwquivqMU4/lDokTCmrAiV8\n", + "X2gmTj+UpD5jkEsdN0CpVLLMotxYWpE6pL60Mnn6fsssOpilFUnqMwa5JEXOIJe6iFMU1Qpr5FKH\n", + "pKmRO0VRYI1ckvqOQS5JkTPIJSlyBrkkRc4gl6TIGeSSFDmDXGoT54SrU5xHLrVJ45xw55ErLeeR\n", + "S1KfMcglKXIGuSRFziCXpMgNpNxuJ7AHeBXYB5wNDAFfBk5Ibr8EeD73HkqSZpR2RF4FysBKQogD\n", + "rAU2AiuATcm6JKnDspRWGqfCrAJGk+VRYHUuPZIkZZJlRH4f8BBwRdI2DIwny+PJuiSpw9LWyN8C\n", + "7AKOJZRTtjbcXmXyCAdJUgelDfJdyfUzwF2EOvk4sAQYA5YCTze748jIyIHlcrlMuVxuradSTxqg\n", + "VCqxcOHR7NnzXNGdUUEqlQqVSqXl+6c5BPRIYD4wASwANgB/CJwPPAvcQNjRuZiDd3h6iL76VtpD\n", + "9KfbxvdO/8p6iH6aEfkwYRRe2/6LhDB/CLgTuJzJ6YeSpA7zpFlSmzgiV6s8aZYk9RmDXJIiZ5BL\n", + "UuQMcqmL+StDSsOdnVKb5LGz052f/cmdnZLUZwxySYqcQS5JkTPIJSlyBrkkRc4gl6TIGeSSFDmD\n", + "XJIiZ5BLUuQMcmmO2nMY/UDt6D5pVh6iL83RdIfRz/UQ/cZr30v9w0P0JanPGOSSFDmDXJIiZ5BL\n", + "UuQMckmKnEEuSZEzyCUpcga5JEXOIJekyBnkkhQ5g1ySImeQS1LkDHJJilzaIJ8PbAbuSdaHgI3A\n", + "NmADsDj/rkm9wlPSqr3SBvmHgSeYPPfmWkKQrwA2JeuSmnqFybeOlL80QX4c8E7gFibPj7sKGE2W\n", + "R4HV+XdNkpRGmiD/C+CjwP66tmFgPFkeT9YlSQUYmOX2dwFPE+rj5Wm2qTLD98aRkZEDy+VymXJ5\n", + "uoeRpP5UqVSoVCot33+2PTCfAH6DUOQ7HBgEvgqcRQj2MWApcD9wSpP7+1Nv6nlpfuqt+TX4U29q\n", + "Ju+fersWWAacCLwP+AYh2NcDa5Jt1gB3Z+2oJCkfWeeR14YE1wMXEKYfnpesS5IK0O7JrZZW1POK\n", + "KK0MDg4xMbEbgIULj2bPnufa+C9Up2UtrRjk0hwVEeST23HQbYpf3jVySVKXm236oaSOmO0w/trt\n", + "hwD7OtQnxcIgl7pC7TD+6cK8/vaZtlM/srQiSZEzyCUpcga5JEXOIJekyBnkkhQ5g1ySImeQS1Lk\n", + "DHJJipxBLkmRM8glKXIGuSRFziCXpMgZ5JIUOYNckiJnkEtS5AxySYqcQS5JkTPIpdyEn2MbHBwq\n", + "uiPqM/7Um5Sb8HNsExP+DJs6yxG5JEXOIJekyBnkUvSszfc7a+RS9KzN9ztH5JIUudmC/HDgAeBh\n", + "4Angk0n7ELAR2AZsABa3q4OSpJnNFuQ/Bt4OnAG8MVk+F1hLCPIVwKZkXZJUgDSllR8l14cC84Hd\n", + "wCpgNGkfBVbn3zVJUhppgnweobQyDtwPPA4MJ+sk18Nt6Z0kaVZpZq3sJ5RWFgFfJ5RX6lWTS1Mj\n", + "IyMHlsvlMuVyOWsfpcINDg4xMbGbhQuPZs+e54rujnpMpVKhUqm0fP+s85U+DrwEfAAoA2PAUsJI\n", + "/ZQm21er1WkzXopGqVQijFdKNL6m62+r36ZZ+9RrZrgt7fXUx/D91hvCayd9Ps9WWjmGyRkpRwAX\n", + "AJuB9cCapH0NcHemXkqScjNbaWUpYWfmvORyO2GWymbgTuByYCdwSfu6KEmaSbsPBbO0op7QrLRS\n", + "q5sH3VBaOQR4xTp+D8haWjHIpRSaBflMQV1UjdxaeW/Iu0YuSepyBrkkRc4gl6TIGeSSFDmDXJIi\n", + "Z5BLuRuozTqQOsIgl3IXfrFH6hSDXJIiZ5BLUuQMckmKnEEuSZEzyCUpcga5JEXOIJekyBnkkhQ5\n", + "g1ySImeQS1LkDHJJipxBLkmRM8glKXIDRXdAiounqFX3cUQuZVI7Ra2nqVX3MMglKXIGuSRFziCX\n", + "ZjA4OGRNXF3PIJdmMDGxG+vh6nYGuSRFLk2QLwPuBx4HHgM+lLQPARuBbcAGYHE7OihJmlmaIN8H\n", + "/C5wOnAO8EHgVGAtIchXAJuSdUldolbfHxwcKrorarNW9uLcDXwmubwNGAeWABXglIZtq9Wq9UXF\n", + "K+zorBLeKvXXNGnLet2+x6hWq1P67vswLskO9tT5nLVGvhxYCTwADBNCnOR6OONjSZJykOUQ/aOA\n", + "vwM+DEw03DbtoW4jIyMHlsvlMuVyOVMHJanXVSoVKpVKy/dPO3Q/BLgX+Brwl0nbVqAMjAFLCTtE\n", + "La2op1haURHaUVopAbcCTzAZ4gDrgTXJ8hpC7VyS1GFpEv9c4F+AR5gcAqwDvgvcCRwP7AQuAZ5v\n", + "uK8jckXNEbmKkHVE3u5jjw1yRc0gVxHaPWtFktRlDHJJipxBLkmRM8ilJjx9rWJikEtNePpaxcQg\n", + "l6TIGeSSFDmDXKpjbVwxMsilOtbGFSODXJIiZ5BLPWfA8lCfMcilnvMKlof6i0EuSZEzyCUpcga5\n", + "JEXOIJekyBnkkhQ5g1ySImeQS1LkDHJJipxBLkmRM8ilPlM7w+Pg4FDRXVFOBorugKTOqp3hcWLC\n", + "87H0CkfkkhQ5g1ySImeQS1LkDHJJipxBLkmRSxPknwfGgUfr2oaAjcA2YAOwOP+uSZLSSBPktwEX\n", + "NrStJQT5CmBTsi4pKuEn4ZxTHr80Qf5NYHdD2ypgNFkeBVbn2SlJnVD7SbhqMrdcsWq1Rj5MKLeQ\n", + "XA/n0x1JUlZ57Oys4i+9SlJhWj1EfxxYAowBS4Gnp9twZGTkwHK5XKZcLrf4lJJaE2rhCxcezZ49\n", + "zxXdGTVRqVSoVCot3z/tyRaWA/cAb0jWbwSeBW4g7OhcTPMdntVq1cG64lEqlQhfMGe7JsU23fcY\n", + "1Wq1yb+RA7epO4T/o9T5nKq0cgfwbeD1wJPAZcD1wAWE6YfnJeuSpAK0+/RnjsjVlQYHh5iY2H1Q\n", + "ucERubpB1hG5p7FVX/JUruolHqIvSZEzyCUpcga5+kbtJ86S+mMfGujjf3tvM8jVN2p18f49fq12\n", + "SL56jUEuSZEzyCUpcga5el6tNq6ZDHg624g5j1w9b7I2bphPL9TPnVcfJ0fkkhQ5g1x9zpKC4mdp\n", + "RX3OkoLi54hckiJnkEtS5AxySYqcQS5JkTPIJSlyBrkkRc4glwBP8aqYGeQS4CleFTODXJIiZ5BL\n", + "UuQMckmKnEEuSZHzpFmKyq5du3jxxRc57LDDWLZsWdHdkbqCI3JFY//+/Rx//AmsXHkhy5efyNjY\n", + "2JTba78E5Clp5yJMwyyVDm167d+2OzkiVzSq1SqvvvoqL7ywnQULjmfv3r1Tbq/9EpCnpJ2L2jTM\n", + "UtNr/7bdyRG5JEVurkF+IbAV+C/gmrl3R5KU1VyCfD7wGUKYnwa8Hzg1j07pYJVKpeguRGRqnVd5\n", + "mvrTeLX9Es3arKd3zlyC/GxgO7AT2Ad8Cbg4hz6pCYM8i1qddx8edp+32k/j7QYm90vAdQe11dbV\n", + "fnMJ8p8CnqxbfyppkyR10FyC3KGOClBlcPBXePnlZ5g3z331EoQ5Ra06Bxgh1MgB1gH7gRvqttkO\n", + "nDSH55CkfrQDeF0nnmggebLlwKHAw7izU5KicxHwPcLIe13BfZEkSZIE8B7gceBV4MyG29YRDh7a\n", + "Cryjw/3qBSOE2UGbk8uFM26t6XggW752Ao8QXpPfLbYr0fk8MA48Wtc2BGwEtgEbgMUF9ItTgBXA\n", + "/UwN8tMIdfRDCHX17XiKgKyuAz5SdCciN5/w2ltOeC26b2fufkAIH2X3VmAlU4P8RuBjyfI1wPWz\n", + "PUg7gnQr4ZOk0cXAHYSjNHYS3kxnt+H5e52HKs6NB7K1h6/L1nwTaDxyahUwmiyPAqtne5BOjoh/\n", + "klAWqPEAotZcDWwBbqWgr1yR80C2/FWB+4CHgCsK7ksvGCaUW0iuh2e7Q6unsd0ILGnSfi1wT4bH\n", + "8aCig033t/0D4Cbgj5L1Pwb+DLi8Q/3qFb7m8vcWYBdwLOH1u5Uw0tTcVUnxmm01yC9o4T4/BOp/\n", + "0uW4pE1Tpf3b3kK2D00Fja/DZUz9pqjsdiXXzwB3EcpXBnnrxgmDuTFgKfD0bHdod2mlvm62Hngf\n", + "4eChE4GTcQ93Vkvrlt/N1B0kSuchwmtvOeG1+F7Ca1OtORJYmCwvIMxG83U5N+uBNcnyGuDuIjrx\n", + "bkIN8iXCJ8rX6m67lrCjaSvwS53vWvS+QJjmtYXwnztr7UxNeSBbfk4kzPx5GHgM/55Z3QH8D7CX\n", + "kJuXEWYA3UfB0w8lSZIkSZIkSZIkSZIkSZIkSZIktcn/A4eK9UXawRDUAAAAAElFTkSuQmCC\n" + ] + }, "metadata": {}, - "source": [ - "As described in the [Rich Output](Rich Output.ipynb) tutorial, the IPython display system can display rich representations of objects in the following formats:\n", - "\n", - "* JavaScript\n", - "* HTML\n", - "* PNG\n", - "* JPEG\n", - "* SVG\n", - "* LaTeX\n", - "* PDF\n", - "\n", - "This Notebook shows how you can add custom display logic to your own classes, so that they can be displayed using these rich representations. There are two ways of accomplishing this:\n", - "\n", - "1. Implementing special display methods such as `_repr_html_` when you define your class.\n", - "2. Registering a display function for a particular existing class.\n", - "\n", - "This Notebook describes and illustrates both approaches." - ] - }, + "output_type": "display_data" + } + ], + "source": [ + "display_png(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "It is important to note a subtle different between display and display_png. The former computes all representations of the object, and lets the notebook UI decide which to display. The later only computes the PNG representation.\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create a new Gaussian with different parameters:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", + "data": { + "image/png": [ + "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\n", + "TkSuQmCC\n" + ], + "text/latex": [ + "$\\mathcal{N}(\\mu=0, \\sigma=2),\\ N=2000$" + ], + "text/plain": [ + "<__main__.Gaussian at 0x106e9ce90>" + ] + }, + "execution_count": 7, "metadata": {}, - "source": [ - "Import the IPython display functions." - ] - }, + "output_type": "execute_result" + } + ], + "source": [ + "x2 = Gaussian(0, 2, 2000)\n", + "x2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can then compare the two Gaussians by displaying their histograms:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import (\n", - " display, display_html, display_png, display_svg\n", - ")" - ], - "language": "python", + "data": { + "image/png": [ + "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\n" + ] + }, "metadata": {}, - "outputs": [], - "prompt_number": 1 + "output_type": "display_data" }, { - "cell_type": "markdown", + "data": { + "image/png": [ + "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\n", + "TkSuQmCC\n" + ] + }, "metadata": {}, - "source": [ - "Parts of this notebook need the matplotlib inline backend:" - ] - }, + "output_type": "display_data" + } + ], + "source": [ + "display_png(x)\n", + "display_png(x2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that like `print`, you can call any of the `display` functions multiple times in a cell." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Adding IPython display support to existing objects" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When you are directly writing your own classes, you can adapt them for display in IPython by following the above approach. But in practice, you often need to work with existing classes that you can't easily modify. We now illustrate how to add rich output capabilities to existing objects. We will use the NumPy polynomials and change their default representation to be a formatted LaTeX expression." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, consider how a NumPy polynomial object renders by default:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "%matplotlib inline\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt" - ], - "language": "python", + "data": { + "text/plain": [ + "Polynomial([ 1., 2., 3.], [-10., 10.], [-1., 1.])" + ] + }, + "execution_count": 9, "metadata": {}, - "outputs": [], - "prompt_number": 2 - }, + "output_type": "execute_result" + } + ], + "source": [ + "p = np.polynomial.Polynomial([1,2,3], [-10, 10])\n", + "p" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, define a function that pretty-prints a polynomial as a LaTeX string:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "def poly_to_latex(p):\n", + " terms = ['%.2g' % p.coef[0]]\n", + " if len(p) > 1:\n", + " term = 'x'\n", + " c = p.coef[1]\n", + " if c!=1:\n", + " term = ('%.2g ' % c) + term\n", + " terms.append(term)\n", + " if len(p) > 2:\n", + " for i in range(2, len(p)):\n", + " term = 'x^%d' % i\n", + " c = p.coef[i]\n", + " if c!=1:\n", + " term = ('%.2g ' % c) + term\n", + " terms.append(term)\n", + " px = '$P(x)=%s$' % '+'.join(terms)\n", + " dom = r', $x \\in [%.2g,\\ %.2g]$' % tuple(p.domain)\n", + " return px+dom" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This produces, on our polynomial ``p``, the following:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "heading", - "level": 2, + "data": { + "text/plain": [ + "'$P(x)=1+2 x+3 x^2$, $x \\\\in [-10,\\\\ 10]$'" + ] + }, + "execution_count": 11, "metadata": {}, - "source": [ - "Special display methods" - ] - }, + "output_type": "execute_result" + } + ], + "source": [ + "poly_to_latex(p)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can render this string using the `Latex` class:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", + "data": { + "text/latex": [ + "$P(x)=1+2 x+3 x^2$, $x \\in [-10,\\ 10]$" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 12, "metadata": {}, - "source": [ - "The main idea of the first approach is that you have to implement special display methods when you define your class, one for each representation you want to use. Here is a list of the names of the special methods and the values they must return:\n", - "\n", - "* `_repr_html_`: return raw HTML as a string\n", - "* `_repr_json_`: return raw JSON as a string\n", - "* `_repr_jpeg_`: return raw JPEG data\n", - "* `_repr_png_`: return raw PNG data\n", - "* `_repr_svg_`: return raw SVG data as a string\n", - "* `_repr_latex_`: return LaTeX commands in a string surrounded by \"$\"." - ] - }, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.display import Latex\n", + "Latex(poly_to_latex(p))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, you can configure IPython to do this automatically by registering the `Polynomial` class and the `plot_to_latex` function with an IPython display formatter. Let's look at the default formatters provided by IPython:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As an illustration, we build a class that holds data generated by sampling a Gaussian distribution with given mean and standard deviation. Here is the definition of the `Gaussian` class, which has a custom PNG and LaTeX representation." + "name": "stdout", + "output_type": "stream", + "text": [ + " text/plain : PlainTextFormatter\n", + " image/jpeg : JPEGFormatter\n", + " text/html : HTMLFormatter\n", + " image/svg+xml : SVGFormatter\n", + " image/png : PNGFormatter\n", + " application/javascript : JavascriptFormatter\n", + " text/markdown : MarkdownFormatter\n", + " text/latex : LatexFormatter\n", + " application/json : JSONFormatter\n", + " application/pdf : PDFFormatter\n" ] - }, + } + ], + "source": [ + "ip = get_ipython()\n", + "for mime, formatter in ip.display_formatter.formatters.items():\n", + " print '%24s : %s' % (mime, formatter.__class__.__name__)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `formatters` attribute is a dictionary keyed by MIME types. To define a custom LaTeX display function, you want a handle on the `text/latex` formatter:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "ip = get_ipython()\n", + "latex_f = ip.display_formatter.formatters['text/latex']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The formatter object has a couple of methods for registering custom display functions for existing types." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.core.pylabtools import print_figure\n", - "from IPython.display import Image, SVG, Math\n", + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on method for_type in module IPython.core.formatters:\n", "\n", - "class Gaussian(object):\n", - " \"\"\"A simple object holding data sampled from a Gaussian distribution.\n", - " \"\"\"\n", - " def __init__(self, mean=0.0, std=1, size=1000):\n", - " self.data = np.random.normal(mean, std, size)\n", - " self.mean = mean\n", - " self.std = std\n", - " self.size = size\n", - " # For caching plots that may be expensive to compute\n", - " self._png_data = None\n", - " \n", - " def _figure_data(self, format):\n", - " fig, ax = plt.subplots()\n", - " ax.hist(self.data, bins=50)\n", - " ax.set_title(self._repr_latex_())\n", - " ax.set_xlim(-10.0,10.0)\n", - " data = print_figure(fig, format)\n", - " # We MUST close the figure, otherwise IPython's display machinery\n", - " # will pick it up and send it as output, resulting in a double display\n", - " plt.close(fig)\n", - " return data\n", + "for_type(self, typ, func=None) method of IPython.core.formatters.LatexFormatter instance\n", + " Add a format function for a given type.\n", + " \n", + " Parameters\n", + " -----------\n", + " typ : type or '__module__.__name__' string for a type\n", + " The class of the object that will be formatted using `func`.\n", + " func : callable\n", + " A callable for computing the format data.\n", + " `func` will be called with the object to be formatted,\n", + " and will return the raw data in this formatter's format.\n", + " Subclasses may use a different call signature for the\n", + " `func` argument.\n", " \n", - " def _repr_png_(self):\n", - " if self._png_data is None:\n", - " self._png_data = self._figure_data('png')\n", - " return self._png_data\n", + " If `func` is None or not specified, there will be no change,\n", + " only returning the current value.\n", " \n", - " def _repr_latex_(self):\n", - " return r'$\\mathcal{N}(\\mu=%.2g, \\sigma=%.2g),\\ N=%d$' % (self.mean,\n", - " self.std, self.size)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 3 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Create an instance of the Gaussian distribution and return it to display the default representation:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x = Gaussian(2.0, 1.0)\n", - "x" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "latex": [ - "$\\mathcal{N}(\\mu=2, \\sigma=1),\\ N=1000$" - ], - "metadata": {}, - "output_type": "pyout", - "png": "iVBORw0KGgoAAAANSUhEUgAAAXIAAAENCAYAAAASUO4dAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAElNJREFUeJzt3X+wXGV9x/H3JpefITfhCr1JSyCIRH5UJbQgrVhXChas\nDXE6onbaBoownRG0tVUSOg63vxQY+8tRoVOQuTIOSq3QQGtNiGyr1SK0IfxqTBNNB2zuhUJoLogk\nkO0fz9ncvZu9956z9+yefXbfr5mdPefZs7tPbnY/++z3POcsSJIkSZIkSZIkSZIkSZIkSZIkSZJ6\n1olFd6DNlgJHFt0JKYt5RXdAXe8sYAtwEyHEzym2O233DPCxojshZVEqugPqGjcD/wrc3tB+E/BV\n4EFgHXBNm57/1wij4bOBu4Avtel5Gp0B/Drw+3VtZwGnAl/oUB+m06xvq4HTgP3AD5n8/8raLqkH\nfQf424a204GLkuU3AVe36blfV/fYxwC76UwJ5yOED6nbmtyWJcTfTPjweQoYSNqGCR9G9wI/n1Pf\nFgH/Xrf+HeA1GduPaaEv6nKWVgQwH7gPOA84vK69DHwjWX5X3XLeTmeynPG/wHbgZ9r0XPX+HPj7\naW57hvABk8YDwD8B24BfTdrGCSH+HuDbOfXtF4An6ta3EP7PsrS/vYW+qMsNzL6J+sDpwCbCV/mL\nCKNLgCOAl5Pls4BPZHzc1wJXzHD7vxHC6h+ZHPmXCCWW7RmfK+tz1kxXXtxC+DBJ0495wD7g08BH\ngS8n7QuAl3Ls23HA83XrzwMnA89lbFePMcgFoS59O6EU8H5CkB8G7K3b5kigWrc+H/hn4Nxk/Vbg\nk0wNvu8T6uqz2Qc8liz/MvAQ8PA0264A/gQ4FvhZoAL8A6HGn+U5a6rTtO9OniuNMwl9fowwkj4T\n+I8mjz3Xvi0Gfly3vhc4KtkuS7t6jKUVQXhzvwysB94B/AQh3B+o22Z+w31+DvjvZLmUrLc6iq5Z\nDFxK2MHXzBAhsH+TUCLYlGx78zTbpzHdiPwl4NCUj/FG4BHCDsXPEer9rwe+N4d+NevbREPbEYRR\nd9Z29RhH5FoE/ChZniDUeq8CngU+W7fdKw33uxD4erK8Eni0yWNnKSWUgLXAB4AXgBOY/KCo+WDS\np9oo87C6vrfynDD9iHwR6UOvfkB0C+ED7Qngr3Lu2w7Ct5Ca1xBG/s+nbD8maZfUI44izET5babO\nZDgT+D8OroePMvVr+YPATyfLHweuBFbNoT8fItSklxC+DbwtaT+ZyaC8kTAtEEJd/1NzeL6aS2k+\na+Uq4Bfr1uv7Ue8QwjeEejcT6v55920BUz8wtxC+PWVtV49p/Lqs/lEGvkUoCdxb174LeAOwmamj\ntyFCMPyAUJ9eRxjxLSSURBYBOwl14KzOJdTnrwR+D7ic8OEwQZjbvr3u8k7CTr8zCDX5/S08X81V\nhNLMm5J/w2Ymd+5eCfw1k99E6vtRcxZh1H08oQw1kbR/n1DG+FbOfXshuawi/P/dSxjF78vYrj61\nGPgK8J+Er4xvJryxNxKmXG1ItlFchjNsu5iwkxFCwPxp/t1p6lDgrR16rprDCTsti+6HlKtR4LeS\n5QHC6OtGJuf+XgNcX0C/1Fm/QyjDfJrOzPMGeC+d/+Z4KWFnZdH9kHKziOZfl7cyOaJbkqyrt5WY\neWddL1gGXFx0J6S8nUGo/91GqJn+DaFWurtum1LDuiSpQ9LMIx8gzGT4XHL9ImGaWL0q00/jkiS1\nUZp55E8llweT9a8QZiyMEUoqY4RDqp9uvONJJ51U3bFjRz49laT+sYP05/pJNSIfA55k8nDl84HH\ngXuANUnbGuDug3qyYwfVatVLDpfrrruu8D700sW/p3/Pbr4AJ6UNcUh/ZOfVwBcJU7B2AJcR9uDf\nSZjzuxO4JMsTS5LykTbItxAOfmh0fo59kSS1wJNmRaJcLhfdhZ7i3zNf/j2L1e6feqsm9R5JUkql\nUgky5LMjckmKnEEuSZEzyCUpcga5JEXOIJekyBnkUocMDg5RKpUYHBwquivqMU4/lDokTCmrAiV8\nX2gmTj+UpD5jkEsdN0CpVLLMotxYWpE6pL60Mnn6fsssOpilFUnqMwa5JEXOIJe6iFMU1Qpr5FKH\npKmRO0VRYI1ckvqOQS5JkTPIJSlyBrkkRc4gl6TIGeSSFDmDXGoT54SrU5xHLrVJ45xw55ErLeeR\nS1KfMcglKXIGuSRFziCXpMgNpNxuJ7AHeBXYB5wNDAFfBk5Ibr8EeD73HkqSZpR2RF4FysBKQogD\nrAU2AiuATcm6JKnDspRWGqfCrAJGk+VRYHUuPZIkZZJlRH4f8BBwRdI2DIwny+PJuiSpw9LWyN8C\n7AKOJZRTtjbcXmXyCAdJUgelDfJdyfUzwF2EOvk4sAQYA5YCTze748jIyIHlcrlMuVxuradSTxqg\nVCqxcOHR7NnzXNGdUUEqlQqVSqXl+6c5BPRIYD4wASwANgB/CJwPPAvcQNjRuZiDd3h6iL76VtpD\n9KfbxvdO/8p6iH6aEfkwYRRe2/6LhDB/CLgTuJzJ6YeSpA7zpFlSmzgiV6s8aZYk9RmDXJIiZ5BL\nUuQMcqmL+StDSsOdnVKb5LGz052f/cmdnZLUZwxySYqcQS5JkTPIJSlyBrkkRc4gl6TIGeSSFDmD\nXJIiZ5BLUuQMcmmO2nMY/UDt6D5pVh6iL83RdIfRz/UQ/cZr30v9w0P0JanPGOSSFDmDXJIiZ5BL\nUuQMckmKnEEuSZEzyCUpcga5JEXOIJekyBnkkhQ5g1ySImeQS1LkDHJJilzaIJ8PbAbuSdaHgI3A\nNmADsDj/rkm9wlPSqr3SBvmHgSeYPPfmWkKQrwA2JeuSmnqFybeOlL80QX4c8E7gFibPj7sKGE2W\nR4HV+XdNkpRGmiD/C+CjwP66tmFgPFkeT9YlSQUYmOX2dwFPE+rj5Wm2qTLD98aRkZEDy+VymXJ5\nuoeRpP5UqVSoVCot33+2PTCfAH6DUOQ7HBgEvgqcRQj2MWApcD9wSpP7+1Nv6nlpfuqt+TX4U29q\nJu+fersWWAacCLwP+AYh2NcDa5Jt1gB3Z+2oJCkfWeeR14YE1wMXEKYfnpesS5IK0O7JrZZW1POK\nKK0MDg4xMbEbgIULj2bPnufa+C9Up2UtrRjk0hwVEeST23HQbYpf3jVySVKXm236oaSOmO0w/trt\nhwD7OtQnxcIgl7pC7TD+6cK8/vaZtlM/srQiSZEzyCUpcga5JEXOIJekyBnkkhQ5g1ySImeQS1Lk\nDHJJipxBLkmRM8glKXIGuSRFziCXpMgZ5JIUOYNckiJnkEtS5AxySYqcQS5JkTPIpdyEn2MbHBwq\nuiPqM/7Um5Sb8HNsExP+DJs6yxG5JEXOIJekyBnkUvSszfc7a+RS9KzN9ztH5JIUudmC/HDgAeBh\n4Angk0n7ELAR2AZsABa3q4OSpJnNFuQ/Bt4OnAG8MVk+F1hLCPIVwKZkXZJUgDSllR8l14cC84Hd\nwCpgNGkfBVbn3zVJUhppgnweobQyDtwPPA4MJ+sk18Nt6Z0kaVZpZq3sJ5RWFgFfJ5RX6lWTS1Mj\nIyMHlsvlMuVyOWsfpcINDg4xMbGbhQuPZs+e54rujnpMpVKhUqm0fP+s85U+DrwEfAAoA2PAUsJI\n/ZQm21er1WkzXopGqVQijFdKNL6m62+r36ZZ+9RrZrgt7fXUx/D91hvCayd9Ps9WWjmGyRkpRwAX\nAJuB9cCapH0NcHemXkqScjNbaWUpYWfmvORyO2GWymbgTuByYCdwSfu6KEmaSbsPBbO0op7QrLRS\nq5sH3VBaOQR4xTp+D8haWjHIpRSaBflMQV1UjdxaeW/Iu0YuSepyBrkkRc4gl6TIGeSSFDmDXJIi\nZ5BLuRuozTqQOsIgl3IXfrFH6hSDXJIiZ5BLUuQMckmKnEEuSZEzyCUpcga5JEXOIJekyBnkkhQ5\ng1ySImeQS1LkDHJJipxBLkmRM8glKXIDRXdAiounqFX3cUQuZVI7Ra2nqVX3MMglKXIGuSRFziCX\nZjA4OGRNXF3PIJdmMDGxG+vh6nYGuSRFLk2QLwPuBx4HHgM+lLQPARuBbcAGYHE7OihJmlmaIN8H\n/C5wOnAO8EHgVGAtIchXAJuSdUldolbfHxwcKrorarNW9uLcDXwmubwNGAeWABXglIZtq9Wq9UXF\nK+zorBLeKvXXNGnLet2+x6hWq1P67vswLskO9tT5nLVGvhxYCTwADBNCnOR6OONjSZJykOUQ/aOA\nvwM+DEw03DbtoW4jIyMHlsvlMuVyOVMHJanXVSoVKpVKy/dPO3Q/BLgX+Brwl0nbVqAMjAFLCTtE\nLa2op1haURHaUVopAbcCTzAZ4gDrgTXJ8hpC7VyS1GFpEv9c4F+AR5gcAqwDvgvcCRwP7AQuAZ5v\nuK8jckXNEbmKkHVE3u5jjw1yRc0gVxHaPWtFktRlDHJJipxBLkmRM8ilJjx9rWJikEtNePpaxcQg\nl6TIGeSSFDmDXKpjbVwxMsilOtbGFSODXJIiZ5BLPWfA8lCfMcilnvMKlof6i0EuSZEzyCUpcga5\nJEXOIJekyBnkkhQ5g1ySImeQS1LkDHJJipxBLkmRM8ilPlM7w+Pg4FDRXVFOBorugKTOqp3hcWLC\n87H0CkfkkhQ5g1ySImeQS1LkDHJJipxBLkmRSxPknwfGgUfr2oaAjcA2YAOwOP+uSZLSSBPktwEX\nNrStJQT5CmBTsi4pKuEn4ZxTHr80Qf5NYHdD2ypgNFkeBVbn2SlJnVD7SbhqMrdcsWq1Rj5MKLeQ\nXA/n0x1JUlZ57Oys4i+9SlJhWj1EfxxYAowBS4Gnp9twZGTkwHK5XKZcLrf4lJJaE2rhCxcezZ49\nzxXdGTVRqVSoVCot3z/tyRaWA/cAb0jWbwSeBW4g7OhcTPMdntVq1cG64lEqlQhfMGe7JsU23fcY\n1Wq1yb+RA7epO4T/o9T5nKq0cgfwbeD1wJPAZcD1wAWE6YfnJeuSpAK0+/RnjsjVlQYHh5iY2H1Q\nucERubpB1hG5p7FVX/JUruolHqIvSZEzyCUpcga5+kbtJ86S+mMfGujjf3tvM8jVN2p18f49fq12\nSL56jUEuSZEzyCUpcga5el6tNq6ZDHg624g5j1w9b7I2bphPL9TPnVcfJ0fkkhQ5g1x9zpKC4mdp\nRX3OkoLi54hckiJnkEtS5AxySYqcQS5JkTPIJSlyBrkkRc4glwBP8aqYGeQS4CleFTODXJIiZ5BL\nUuQMckmKnEEuSZHzpFmKyq5du3jxxRc57LDDWLZsWdHdkbqCI3JFY//+/Rx//AmsXHkhy5efyNjY\n2JTba78E5Clp5yJMwyyVDm167d+2OzkiVzSq1SqvvvoqL7ywnQULjmfv3r1Tbq/9EpCnpJ2L2jTM\nUtNr/7bdyRG5JEVurkF+IbAV+C/gmrl3R5KU1VyCfD7wGUKYnwa8Hzg1j07pYJVKpeguRGRqnVd5\nmvrTeLX9Es3arKd3zlyC/GxgO7AT2Ad8Cbg4hz6pCYM8i1qddx8edp+32k/j7QYm90vAdQe11dbV\nfnMJ8p8CnqxbfyppkyR10FyC3KGOClBlcPBXePnlZ5g3z331EoQ5Ra06Bxgh1MgB1gH7gRvqttkO\nnDSH55CkfrQDeF0nnmggebLlwKHAw7izU5KicxHwPcLIe13BfZEkSZIE8B7gceBV4MyG29YRDh7a\nCryjw/3qBSOE2UGbk8uFM26t6XggW752Ao8QXpPfLbYr0fk8MA48Wtc2BGwEtgEbgMUF9ItTgBXA\n/UwN8tMIdfRDCHX17XiKgKyuAz5SdCciN5/w2ltOeC26b2fufkAIH2X3VmAlU4P8RuBjyfI1wPWz\nPUg7gnQr4ZOk0cXAHYSjNHYS3kxnt+H5e52HKs6NB7K1h6/L1nwTaDxyahUwmiyPAqtne5BOjoh/\nklAWqPEAotZcDWwBbqWgr1yR80C2/FWB+4CHgCsK7ksvGCaUW0iuh2e7Q6unsd0ILGnSfi1wT4bH\n8aCig033t/0D4Cbgj5L1Pwb+DLi8Q/3qFb7m8vcWYBdwLOH1u5Uw0tTcVUnxmm01yC9o4T4/BOp/\n0uW4pE1Tpf3b3kK2D00Fja/DZUz9pqjsdiXXzwB3EcpXBnnrxgmDuTFgKfD0bHdod2mlvm62Hngf\n4eChE4GTcQ93Vkvrlt/N1B0kSuchwmtvOeG1+F7Ca1OtORJYmCwvIMxG83U5N+uBNcnyGuDuIjrx\nbkIN8iXCJ8rX6m67lrCjaSvwS53vWvS+QJjmtYXwnztr7UxNeSBbfk4kzPx5GHgM/55Z3QH8D7CX\nkJuXEWYA3UfB0w8lSZIkSZIkSZIkSZIkSZIkSZIktcn/A4eK9UXawRDUAAAAAElFTkSuQmCC\n", - "prompt_number": 4, - "text": [ - "<__main__.Gaussian at 0x106e7ae10>" - ] - } - ], - "prompt_number": 4 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can also pass the object to the `display` function to display the default representation:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "display(x)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "latex": [ - "$\\mathcal{N}(\\mu=2, \\sigma=1),\\ N=1000$" - ], - "metadata": {}, - "output_type": "display_data", - "png": "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\n", - "text": [ - "<__main__.Gaussian at 0x106e7ae10>" - ] - } - ], - "prompt_number": 5 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Use `display_png` to view the PNG representation:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "display_png(x)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "display_data", - "png": "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\n" - } - ], - "prompt_number": 6 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "
\n", - "It is important to note a subtle different between display and display_png. The former computes all representations of the object, and lets the notebook UI decide which to display. The later only computes the PNG representation.\n", - "
" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Create a new Gaussian with different parameters:" + " Returns\n", + " -------\n", + " oldfunc : callable\n", + " The currently registered callable.\n", + " If you are registering a new formatter,\n", + " this will be the previous value (to enable restoring later).\n", + "\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x2 = Gaussian(0, 2, 2000)\n", - "x2" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "latex": [ - "$\\mathcal{N}(\\mu=0, \\sigma=2),\\ N=2000$" - ], - "metadata": {}, - "output_type": "pyout", - "png": "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\nTkSuQmCC\n", - "prompt_number": 7, - "text": [ - "<__main__.Gaussian at 0x106e9ce90>" - ] - } - ], - "prompt_number": 7 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can then compare the two Gaussians by displaying their histograms:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "display_png(x)\n", - "display_png(x2)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "display_data", - "png": "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\n" - }, - { - "metadata": {}, - "output_type": "display_data", - "png": "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\nTkSuQmCC\n" - } - ], - "prompt_number": 8 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that like `print`, you can call any of the `display` functions multiple times in a cell." - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Adding IPython display support to existing objects" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When you are directly writing your own classes, you can adapt them for display in IPython by following the above approach. But in practice, you often need to work with existing classes that you can't easily modify. We now illustrate how to add rich output capabilities to existing objects. We will use the NumPy polynomials and change their default representation to be a formatted LaTeX expression." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First, consider how a NumPy polynomial object renders by default:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "p = np.polynomial.Polynomial([1,2,3], [-10, 10])\n", - "p" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 9, - "text": [ - "Polynomial([ 1., 2., 3.], [-10., 10.], [-1., 1.])" - ] - } - ], - "prompt_number": 9 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, define a function that pretty-prints a polynomial as a LaTeX string:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "def poly_to_latex(p):\n", - " terms = ['%.2g' % p.coef[0]]\n", - " if len(p) > 1:\n", - " term = 'x'\n", - " c = p.coef[1]\n", - " if c!=1:\n", - " term = ('%.2g ' % c) + term\n", - " terms.append(term)\n", - " if len(p) > 2:\n", - " for i in range(2, len(p)):\n", - " term = 'x^%d' % i\n", - " c = p.coef[i]\n", - " if c!=1:\n", - " term = ('%.2g ' % c) + term\n", - " terms.append(term)\n", - " px = '$P(x)=%s$' % '+'.join(terms)\n", - " dom = r', $x \\in [%.2g,\\ %.2g]$' % tuple(p.domain)\n", - " return px+dom" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 10 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This produces, on our polynomial ``p``, the following:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "poly_to_latex(p)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 11, - "text": [ - "'$P(x)=1+2 x+3 x^2$, $x \\\\in [-10,\\\\ 10]$'" - ] - } - ], - "prompt_number": 11 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can render this string using the `Latex` class:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import Latex\n", - "Latex(poly_to_latex(p))" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "latex": [ - "$P(x)=1+2 x+3 x^2$, $x \\in [-10,\\ 10]$" - ], - "metadata": {}, - "output_type": "pyout", - "prompt_number": 12, - "text": [ - "" - ] - } - ], - "prompt_number": 12 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "However, you can configure IPython to do this automatically by registering the `Polynomial` class and the `plot_to_latex` function with an IPython display formatter. Let's look at the default formatters provided by IPython:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "ip = get_ipython()\n", - "for mime, formatter in ip.display_formatter.formatters.items():\n", - " print '%24s : %s' % (mime, formatter.__class__.__name__)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - " text/plain : PlainTextFormatter\n", - " image/jpeg : JPEGFormatter\n", - " text/html : HTMLFormatter\n", - " image/svg+xml : SVGFormatter\n", - " image/png : PNGFormatter\n", - " application/javascript : JavascriptFormatter\n", - " text/markdown : MarkdownFormatter\n", - " text/latex : LatexFormatter\n", - " application/json : JSONFormatter\n", - " application/pdf : PDFFormatter\n" - ] - } - ], - "prompt_number": 13 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `formatters` attribute is a dictionary keyed by MIME types. To define a custom LaTeX display function, you want a handle on the `text/latex` formatter:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "ip = get_ipython()\n", - "latex_f = ip.display_formatter.formatters['text/latex']" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 14 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The formatter object has a couple of methods for registering custom display functions for existing types." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "help(latex_f.for_type)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Help on method for_type in module IPython.core.formatters:\n", - "\n", - "for_type(self, typ, func=None) method of IPython.core.formatters.LatexFormatter instance\n", - " Add a format function for a given type.\n", - " \n", - " Parameters\n", - " -----------\n", - " typ : type or '__module__.__name__' string for a type\n", - " The class of the object that will be formatted using `func`.\n", - " func : callable\n", - " A callable for computing the format data.\n", - " `func` will be called with the object to be formatted,\n", - " and will return the raw data in this formatter's format.\n", - " Subclasses may use a different call signature for the\n", - " `func` argument.\n", - " \n", - " If `func` is None or not specified, there will be no change,\n", - " only returning the current value.\n", - " \n", - " Returns\n", - " -------\n", - " oldfunc : callable\n", - " The currently registered callable.\n", - " If you are registering a new formatter,\n", - " this will be the previous value (to enable restoring later).\n", - "\n" - ] - } - ], - "prompt_number": 15 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "help(latex_f.for_type_by_name)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Help on method for_type_by_name in module IPython.core.formatters:\n", - "\n", - "for_type_by_name(self, type_module, type_name, func=None) method of IPython.core.formatters.LatexFormatter instance\n", - " Add a format function for a type specified by the full dotted\n", - " module and name of the type, rather than the type of the object.\n", - " \n", - " Parameters\n", - " ----------\n", - " type_module : str\n", - " The full dotted name of the module the type is defined in, like\n", - " ``numpy``.\n", - " type_name : str\n", - " The name of the type (the class name), like ``dtype``\n", - " func : callable\n", - " A callable for computing the format data.\n", - " `func` will be called with the object to be formatted,\n", - " and will return the raw data in this formatter's format.\n", - " Subclasses may use a different call signature for the\n", - " `func` argument.\n", - " \n", - " If `func` is None or unspecified, there will be no change,\n", - " only returning the current value.\n", - " \n", - " Returns\n", - " -------\n", - " oldfunc : callable\n", - " The currently registered callable.\n", - " If you are registering a new formatter,\n", - " this will be the previous value (to enable restoring later).\n", - "\n" - ] - } - ], - "prompt_number": 16 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In this case, we will use `for_type_by_name` to register `poly_to_latex` as the display function for the `Polynomial` type:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "latex_f.for_type_by_name('numpy.polynomial.polynomial',\n", - " 'Polynomial', poly_to_latex)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 18 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Once the custom display function has been registered, all NumPy `Polynomial` instances will be represented by their LaTeX form instead:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "p" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "latex": [ - "$P(x)=1+2 x+3 x^2$, $x \\in [-10,\\ 10]$" - ], - "metadata": {}, - "output_type": "pyout", - "prompt_number": 19, - "text": [ - "Polynomial([ 1., 2., 3.], [-10., 10.], [-1., 1.])" - ] - } - ], - "prompt_number": 19 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "p2 = np.polynomial.Polynomial([-20, 71, -15, 1])\n", - "p2" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "latex": [ - "$P(x)=-20+71 x+-15 x^2+x^3$, $x \\in [-1,\\ 1]$" - ], - "metadata": {}, - "output_type": "pyout", - "prompt_number": 20, - "text": [ - "Polynomial([-20., 71., -15., 1.], [-1., 1.], [-1., 1.])" - ] - } - ], - "prompt_number": 20 - }, + } + ], + "source": [ + "help(latex_f.for_type)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "More complex display with `_ipython_display_`" + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on method for_type_by_name in module IPython.core.formatters:\n", + "\n", + "for_type_by_name(self, type_module, type_name, func=None) method of IPython.core.formatters.LatexFormatter instance\n", + " Add a format function for a type specified by the full dotted\n", + " module and name of the type, rather than the type of the object.\n", + " \n", + " Parameters\n", + " ----------\n", + " type_module : str\n", + " The full dotted name of the module the type is defined in, like\n", + " ``numpy``.\n", + " type_name : str\n", + " The name of the type (the class name), like ``dtype``\n", + " func : callable\n", + " A callable for computing the format data.\n", + " `func` will be called with the object to be formatted,\n", + " and will return the raw data in this formatter's format.\n", + " Subclasses may use a different call signature for the\n", + " `func` argument.\n", + " \n", + " If `func` is None or unspecified, there will be no change,\n", + " only returning the current value.\n", + " \n", + " Returns\n", + " -------\n", + " oldfunc : callable\n", + " The currently registered callable.\n", + " If you are registering a new formatter,\n", + " this will be the previous value (to enable restoring later).\n", + "\n" ] - }, + } + ], + "source": [ + "help(latex_f.for_type_by_name)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this case, we will use `for_type_by_name` to register `poly_to_latex` as the display function for the `Polynomial` type:" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "latex_f.for_type_by_name('numpy.polynomial.polynomial',\n", + " 'Polynomial', poly_to_latex)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once the custom display function has been registered, all NumPy `Polynomial` instances will be represented by their LaTeX form instead:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", + "data": { + "text/latex": [ + "$P(x)=1+2 x+3 x^2$, $x \\in [-10,\\ 10]$" + ], + "text/plain": [ + "Polynomial([ 1., 2., 3.], [-10., 10.], [-1., 1.])" + ] + }, + "execution_count": 19, "metadata": {}, - "source": [ - "Rich output special methods and functions can only display one object or MIME type at a time. Sometimes this is not enough if you want to display multiple objects or MIME types at once. An example of this would be to use an HTML representation to put some HTML elements in the DOM and then use a JavaScript representation to add events to those elements.\n", - "\n", - "**IPython 2.0** recognizes another display method, `_ipython_display_`, which allows your objects to take complete control of displaying themselves. If this method is defined, IPython will call it, and make no effort to display the object using the above described `_repr_*_` methods for custom display functions. It's a way for you to say \"Back off, IPython, I can display this myself.\" Most importantly, your `_ipython_display_` method can make multiple calls to the top-level `display` functions to accomplish its goals.\n", - "\n", - "Here is an object that uses `display_html` and `display_javascript` to make a plot using the [Flot](http://www.flotcharts.org/) JavaScript plotting library:" - ] - }, + "output_type": "execute_result" + } + ], + "source": [ + "p" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "import json\n", - "import uuid\n", - "from IPython.display import display_javascript, display_html, display\n", - "\n", - "class FlotPlot(object):\n", - " def __init__(self, x, y):\n", - " self.x = x\n", - " self.y = y\n", - " self.uuid = str(uuid.uuid4())\n", - " \n", - " def _ipython_display_(self):\n", - " json_data = json.dumps(zip(self.x, self.y))\n", - " display_html('
'.format(self.uuid),\n", - " raw=True\n", - " )\n", - " display_javascript(\"\"\"\n", - " require([\"//cdnjs.cloudflare.com/ajax/libs/flot/0.8.2/jquery.flot.min.js\"], function() {\n", - " var line = JSON.parse(\"%s\");\n", - " console.log(line);\n", - " $.plot(\"#%s\", [line]);\n", - " });\n", - " \"\"\" % (json_data, self.uuid), raw=True)\n" - ], - "language": "python", + "data": { + "text/latex": [ + "$P(x)=-20+71 x+-15 x^2+x^3$, $x \\in [-1,\\ 1]$" + ], + "text/plain": [ + "Polynomial([-20., 71., -15., 1.], [-1., 1.], [-1., 1.])" + ] + }, + "execution_count": 20, "metadata": {}, - "outputs": [], - "prompt_number": 21 - }, + "output_type": "execute_result" + } + ], + "source": [ + "p2 = np.polynomial.Polynomial([-20, 71, -15, 1])\n", + "p2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## More complex display with `_ipython_display_`" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Rich output special methods and functions can only display one object or MIME type at a time. Sometimes this is not enough if you want to display multiple objects or MIME types at once. An example of this would be to use an HTML representation to put some HTML elements in the DOM and then use a JavaScript representation to add events to those elements.\n", + "\n", + "**IPython 2.0** recognizes another display method, `_ipython_display_`, which allows your objects to take complete control of displaying themselves. If this method is defined, IPython will call it, and make no effort to display the object using the above described `_repr_*_` methods for custom display functions. It's a way for you to say \"Back off, IPython, I can display this myself.\" Most importantly, your `_ipython_display_` method can make multiple calls to the top-level `display` functions to accomplish its goals.\n", + "\n", + "Here is an object that uses `display_html` and `display_javascript` to make a plot using the [Flot](http://www.flotcharts.org/) JavaScript plotting library:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import json\n", + "import uuid\n", + "from IPython.display import display_javascript, display_html, display\n", + "\n", + "class FlotPlot(object):\n", + " def __init__(self, x, y):\n", + " self.x = x\n", + " self.y = y\n", + " self.uuid = str(uuid.uuid4())\n", + " \n", + " def _ipython_display_(self):\n", + " json_data = json.dumps(zip(self.x, self.y))\n", + " display_html('
'.format(self.uuid),\n", + " raw=True\n", + " )\n", + " display_javascript(\"\"\"\n", + " require([\"//cdnjs.cloudflare.com/ajax/libs/flot/0.8.2/jquery.flot.min.js\"], function() {\n", + " var line = JSON.parse(\"%s\");\n", + " console.log(line);\n", + " $.plot(\"#%s\", [line]);\n", + " });\n", + " \"\"\" % (json_data, self.uuid), raw=True)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "import numpy as np\n", - "x = np.linspace(0,10)\n", - "y = np.sin(x)\n", - "FlotPlot(x, np.sin(x))" - ], - "language": "python", + "data": { + "text/html": [ + "
" + ] + }, "metadata": {}, - "outputs": [ - { - "html": [ - "
" - ], - "metadata": {}, - "output_type": "display_data" - }, - { - "javascript": [ - "\n", - " require([\"//cdnjs.cloudflare.com/ajax/libs/flot/0.8.2/jquery.flot.min.js\"], function() {\n", - " var line = JSON.parse(\"[[0.0, 0.0], [0.20408163265306123, 0.20266793654820095], [0.40816326530612246, 0.39692414892492234], [0.61224489795918369, 0.57470604121617908], [0.81632653061224492, 0.72863478346935029], [1.0204081632653061, 0.85232156971961837], [1.2244897959183674, 0.94063278511248671], [1.4285714285714286, 0.98990307637212394], [1.6326530612244898, 0.99808748213471832], [1.8367346938775511, 0.96484630898376322], [2.0408163265306123, 0.89155923041100371], [2.2448979591836737, 0.7812680235262639], [2.4489795918367347, 0.63855032022660208], [2.6530612244897958, 0.46932961277720098], [2.8571428571428572, 0.28062939951435684], [3.0612244897959187, 0.080281674842813497], [3.2653061224489797, -0.12339813736217871], [3.4693877551020407, -0.32195631507261868], [3.6734693877551021, -0.50715170948451438], [3.8775510204081636, -0.67129779355193209], [4.0816326530612246, -0.80758169096833643], [4.2857142857142856, -0.91034694431078278], [4.4897959183673475, -0.97532828606704558], [4.6938775510204085, -0.99982866838408957], [4.8979591836734695, -0.98283120392563061], [5.1020408163265305, -0.92504137173820289], [5.3061224489795915, -0.82885773637304272], [5.5102040816326534, -0.69827239556539955], [5.7142857142857144, -0.53870528838615628], [5.9183673469387754, -0.35677924089893803], [6.1224489795918373, -0.16004508604325057], [6.3265306122448983, 0.043331733368683463], [6.5306122448979593, 0.24491007101197931], [6.7346938775510203, 0.43632342647181932], [6.9387755102040813, 0.6096271964908323], [7.1428571428571432, 0.75762841539272019], [7.3469387755102042, 0.87418429881973347], [7.5510204081632653, 0.95445719973875187], [7.7551020408163271, 0.99511539477766364], [7.9591836734693882, 0.99447136726361685], [8.1632653061224492, 0.95255184753146038], [8.3673469387755102, 0.87109670348232071], [8.5714285714285712, 0.75348672743963763], [8.7755102040816322, 0.60460331650615429], [8.979591836734695, 0.43062587038273736], [9.183673469387756, 0.23877531564403087], [9.387755102040817, 0.037014401485062368], [9.591836734693878, -0.16628279384875641], [9.795918367346939, -0.36267842882654883], [10.0, -0.54402111088936989]]\");\n", - " console.log(line);\n", - " $.plot(\"#e75b8189-92cb-4cbb-9996-bb8ad5ff1b4e\", [line]);\n", - " });\n", - " " - ], - "metadata": {}, - "output_type": "display_data" - } - ], - "prompt_number": 22 + "output_type": "display_data" }, { - "cell_type": "code", - "collapsed": false, - "input": [], - "language": "python", + "data": { + "application/javascript": [ + "\n", + " require([\"//cdnjs.cloudflare.com/ajax/libs/flot/0.8.2/jquery.flot.min.js\"], function() {\n", + " var line = JSON.parse(\"[[0.0, 0.0], [0.20408163265306123, 0.20266793654820095], [0.40816326530612246, 0.39692414892492234], [0.61224489795918369, 0.57470604121617908], [0.81632653061224492, 0.72863478346935029], [1.0204081632653061, 0.85232156971961837], [1.2244897959183674, 0.94063278511248671], [1.4285714285714286, 0.98990307637212394], [1.6326530612244898, 0.99808748213471832], [1.8367346938775511, 0.96484630898376322], [2.0408163265306123, 0.89155923041100371], [2.2448979591836737, 0.7812680235262639], [2.4489795918367347, 0.63855032022660208], [2.6530612244897958, 0.46932961277720098], [2.8571428571428572, 0.28062939951435684], [3.0612244897959187, 0.080281674842813497], [3.2653061224489797, -0.12339813736217871], [3.4693877551020407, -0.32195631507261868], [3.6734693877551021, -0.50715170948451438], [3.8775510204081636, -0.67129779355193209], [4.0816326530612246, -0.80758169096833643], [4.2857142857142856, -0.91034694431078278], [4.4897959183673475, -0.97532828606704558], [4.6938775510204085, -0.99982866838408957], [4.8979591836734695, -0.98283120392563061], [5.1020408163265305, -0.92504137173820289], [5.3061224489795915, -0.82885773637304272], [5.5102040816326534, -0.69827239556539955], [5.7142857142857144, -0.53870528838615628], [5.9183673469387754, -0.35677924089893803], [6.1224489795918373, -0.16004508604325057], [6.3265306122448983, 0.043331733368683463], [6.5306122448979593, 0.24491007101197931], [6.7346938775510203, 0.43632342647181932], [6.9387755102040813, 0.6096271964908323], [7.1428571428571432, 0.75762841539272019], [7.3469387755102042, 0.87418429881973347], [7.5510204081632653, 0.95445719973875187], [7.7551020408163271, 0.99511539477766364], [7.9591836734693882, 0.99447136726361685], [8.1632653061224492, 0.95255184753146038], [8.3673469387755102, 0.87109670348232071], [8.5714285714285712, 0.75348672743963763], [8.7755102040816322, 0.60460331650615429], [8.979591836734695, 0.43062587038273736], [9.183673469387756, 0.23877531564403087], [9.387755102040817, 0.037014401485062368], [9.591836734693878, -0.16628279384875641], [9.795918367346939, -0.36267842882654883], [10.0, -0.54402111088936989]]\");\n", + " console.log(line);\n", + " $.plot(\"#e75b8189-92cb-4cbb-9996-bb8ad5ff1b4e\", [line]);\n", + " });\n", + " " + ] + }, "metadata": {}, - "outputs": [] + "output_type": "display_data" } ], - "metadata": {} + "source": [ + "import numpy as np\n", + "x = np.linspace(0,10)\n", + "y = np.sin(x)\n", + "FlotPlot(x, np.sin(x))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [] } - ] + ], + "metadata": { + "signature": "sha256:86c779d5798c4a68bda7e71c8ef320cb7ba9d7e3d0f1bc4b828ee65f617a5ae3" + }, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Index.ipynb b/examples/IPython Kernel/Index.ipynb index 5627e30..9418e0e 100644 --- a/examples/IPython Kernel/Index.ipynb +++ b/examples/IPython Kernel/Index.ipynb @@ -1,172 +1,168 @@ { - "metadata": { - "name": "", - "signature": "sha256:ee769d05a7e195e4b8546ef9a866ef03e59bff2f0fcba499d168c06b516aa79a" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Back to the main [Index](../Index.ipynb)" - ] - }, - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "IPython Kernel" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "IPython provides extensions to the Python programming language that make working interactively convenient and efficient. These extensions are implemented in the IPython Kernel and are available in all of the IPython Frontends (Notebook, Terminal, Console and Qt Console) when running this kernel." - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Tutorials" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "* [Cell Magics](Cell Magics.ipynb)\n", - "* [Script Magics](Script Magics.ipynb)\n", - "* [Rich Output](Rich Output.ipynb)\n", - "* [Custom Display Logic](Custom Display Logic.ipynb)\n", - "* [Plotting in the Notebook](Plotting in the Notebook.ipynb)\n", - "* [Capturing Output](Capturing Output.ipynb)" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Examples" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "* [Background Jobs](Background Jobs.ipynb)\n", - "* [Trapezoid Rule](Trapezoid Rule.ipynb)\n", - "* [SymPy](SymPy.ipynb)\n", - "* [Raw Input in the Notebook](Raw Input in the Notebook.ipynb)" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Non-notebook examples" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This directory also contains examples that are regular Python (`.py`) files." - ] - }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Back to the main [Index](../Index.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# IPython Kernel" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "IPython provides extensions to the Python programming language that make working interactively convenient and efficient. These extensions are implemented in the IPython Kernel and are available in all of the IPython Frontends (Notebook, Terminal, Console and Qt Console) when running this kernel." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tutorials" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* [Cell Magics](Cell Magics.ipynb)\n", + "* [Script Magics](Script Magics.ipynb)\n", + "* [Rich Output](Rich Output.ipynb)\n", + "* [Custom Display Logic](Custom Display Logic.ipynb)\n", + "* [Plotting in the Notebook](Plotting in the Notebook.ipynb)\n", + "* [Capturing Output](Capturing Output.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Examples" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* [Background Jobs](Background Jobs.ipynb)\n", + "* [Trapezoid Rule](Trapezoid Rule.ipynb)\n", + "* [SymPy](SymPy.ipynb)\n", + "* [Raw Input in the Notebook](Raw Input in the Notebook.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Non-notebook examples" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This directory also contains examples that are regular Python (`.py`) files." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "%run ../utils/list_pyfiles.ipy" - ], - "language": "python", + "data": { + "text/html": [ + "example-demo.py
" + ], + "text/plain": [ + "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/IPython Kernel/example-demo.py" + ] + }, "metadata": {}, - "outputs": [ - { - "html": [ - "example-demo.py
" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/IPython Kernel/example-demo.py" - ] - }, - { - "html": [ - "ipython-get-history.py
" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/IPython Kernel/ipython-get-history.py" - ] - } - ], - "prompt_number": 1 + "output_type": "display_data" }, { - "cell_type": "markdown", + "data": { + "text/html": [ + "ipython-get-history.py
" + ], + "text/plain": [ + "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/IPython Kernel/ipython-get-history.py" + ] + }, "metadata": {}, - "source": [ - "There are also a set of examples that show how to integrate IPython with different GUI event loops:" - ] - }, + "output_type": "display_data" + } + ], + "source": [ + "%run ../utils/list_pyfiles.ipy" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There are also a set of examples that show how to integrate IPython with different GUI event loops:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "%run ../utils/list_subdirs.ipy" - ], - "language": "python", + "data": { + "text/html": [ + "gui/
\n", + "  gui-glut.py
\n", + "  gui-gtk.py
\n", + "  gui-gtk3.py
\n", + "  gui-pyglet.py
\n", + "  gui-qt.py
\n", + "  gui-tk.py
\n", + "  gui-wx.py
" + ], + "text/plain": [ + "gui/\n", + " gui-glut.py\n", + " gui-gtk.py\n", + " gui-gtk3.py\n", + " gui-pyglet.py\n", + " gui-qt.py\n", + " gui-tk.py\n", + " gui-wx.py" + ] + }, "metadata": {}, - "outputs": [ - { - "html": [ - "gui/
\n", - "  gui-glut.py
\n", - "  gui-gtk.py
\n", - "  gui-gtk3.py
\n", - "  gui-pyglet.py
\n", - "  gui-qt.py
\n", - "  gui-tk.py
\n", - "  gui-wx.py
" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "gui/\n", - " gui-glut.py\n", - " gui-gtk.py\n", - " gui-gtk3.py\n", - " gui-pyglet.py\n", - " gui-qt.py\n", - " gui-tk.py\n", - " gui-wx.py" - ] - } - ], - "prompt_number": 2 + "output_type": "display_data" } ], - "metadata": {} + "source": [ + "%run ../utils/list_subdirs.ipy" + ] } - ] + ], + "metadata": { + "signature": "sha256:ee769d05a7e195e4b8546ef9a866ef03e59bff2f0fcba499d168c06b516aa79a" + }, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Old Custom Display Logic.ipynb b/examples/IPython Kernel/Old Custom Display Logic.ipynb index 051f1e4..b147242 100644 --- a/examples/IPython Kernel/Old Custom Display Logic.ipynb +++ b/examples/IPython Kernel/Old Custom Display Logic.ipynb @@ -1,944 +1,1483 @@ { - "metadata": { - "name": "" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ - { - "cells": [ - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Defining Custom Display Logic for Your Own Objects" - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Overview" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In Python, objects can declare their textual representation using the `__repr__` method. IPython expands on this idea and allows objects to declare other, richer representations including:\n", - "\n", - "* HTML\n", - "* JSON\n", - "* PNG\n", - "* JPEG\n", - "* SVG\n", - "* LaTeX\n", - "\n", - "This Notebook shows how you can add custom display logic to your own classes, so that they can be displayed using these rich representations. There are two ways of accomplishing this:\n", - "\n", - "1. Implementing special display methods such as `_repr_html_`.\n", - "2. Registering a display function for a particular type.\n", - "\n", - "In this Notebook we show how both approaches work." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Before we get started, we will import the various display functions for displaying the different formats we will create." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import display\n", - "from IPython.display import (\n", - " display_html, display_jpeg, display_png,\n", - " display_javascript, display_svg, display_latex\n", - ")" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 1 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Implementing special display methods" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The main idea of the first approach is that you have to implement special display methods, one for each representation you want to use. Here is a list of the names of the special methods and the values they must return:\n", - "\n", - "* `_repr_html_`: return raw HTML as a string\n", - "* `_repr_json_`: return raw JSON as a string\n", - "* `_repr_jpeg_`: return raw JPEG data\n", - "* `_repr_png_`: return raw PNG data\n", - "* `_repr_svg_`: return raw SVG data as a string\n", - "* `_repr_latex_`: return LaTeX commands in a string surrounded by \"$\"." - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Model Citizen: pandas" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A prominent example of a package that has IPython-aware rich representations of its objects is [pandas](http://pandas.pydata.org/).\n", - "\n", - "A pandas DataFrame has a rich HTML table representation,\n", - "using `_repr_html_`.\n" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import io\n", - "import pandas" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 2 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%writefile data.csv\n", - "Date,Open,High,Low,Close,Volume,Adj Close\n", - "2012-06-01,569.16,590.00,548.50,584.00,14077000,581.50\n", - "2012-05-01,584.90,596.76,522.18,577.73,18827900,575.26\n", - "2012-04-02,601.83,644.00,555.00,583.98,28759100,581.48\n", - "2012-03-01,548.17,621.45,516.22,599.55,26486000,596.99\n", - "2012-02-01,458.41,547.61,453.98,542.44,22001000,540.12\n", - "2012-01-03,409.40,458.24,409.00,456.48,12949100,454.53\n" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "Writing data.csv\n" - ] - } - ], - "prompt_number": 3 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "df = pandas.read_csv(\"data.csv\")\n", - "pandas.set_option('display.notebook_repr_html', False)\n", - "df" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 4, - "text": [ - " Date Open High Low Close Volume Adj Close\n", - "0 2012-06-01 569.16 590.00 548.50 584.00 14077000 581.50\n", - "1 2012-05-01 584.90 596.76 522.18 577.73 18827900 575.26\n", - "2 2012-04-02 601.83 644.00 555.00 583.98 28759100 581.48\n", - "3 2012-03-01 548.17 621.45 516.22 599.55 26486000 596.99\n", - "4 2012-02-01 458.41 547.61 453.98 542.44 22001000 540.12\n", - "5 2012-01-03 409.40 458.24 409.00 456.48 12949100 454.53" - ] - } - ], - "prompt_number": 4 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "rich HTML can be activated via `pandas.set_option`." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "pandas.set_option('display.notebook_repr_html', True)\n", - "df" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "html": [ - "
\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
DateOpenHighLowCloseVolumeAdj Close
0 2012-06-01 569.16 590.00 548.50 584.00 14077000 581.50
1 2012-05-01 584.90 596.76 522.18 577.73 18827900 575.26
2 2012-04-02 601.83 644.00 555.00 583.98 28759100 581.48
3 2012-03-01 548.17 621.45 516.22 599.55 26486000 596.99
4 2012-02-01 458.41 547.61 453.98 542.44 22001000 540.12
5 2012-01-03 409.40 458.24 409.00 456.48 12949100 454.53
\n", - "
" - ], - "metadata": {}, - "output_type": "pyout", - "prompt_number": 5, - "text": [ - " Date Open High Low Close Volume Adj Close\n", - "0 2012-06-01 569.16 590.00 548.50 584.00 14077000 581.50\n", - "1 2012-05-01 584.90 596.76 522.18 577.73 18827900 575.26\n", - "2 2012-04-02 601.83 644.00 555.00 583.98 28759100 581.48\n", - "3 2012-03-01 548.17 621.45 516.22 599.55 26486000 596.99\n", - "4 2012-02-01 458.41 547.61 453.98 542.44 22001000 540.12\n", - "5 2012-01-03 409.40 458.24 409.00 456.48 12949100 454.53" - ] - } - ], - "prompt_number": 5 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "lines = df._repr_html_().splitlines()\n", - "print \"\\n\".join(lines[:20])" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "
\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n" - ] - } - ], - "prompt_number": 6 - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Exercise" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Write a simple `Circle` Python class. Don't even worry about properties such as radius, position, colors, etc. To help you out use the following representations (remember to wrap them in Python strings):\n", - "\n", - "For HTML:\n", - "\n", - " ○\n", - "\n", - "For SVG:\n", - "\n", - " \n", - " \n", - " \n", - "\n", - "For LaTeX (wrap with `$` and use a raw Python string):\n", - "\n", - " \\bigcirc\n", - "\n", - "After you write the class, create an instance and then use `display_html`, `display_svg` and `display_latex` to display those representations.\n", - "\n", - "Tips : you can slightly tweek the representation to know from which `_repr_*_` method it came from. \n", - "For example in my solution the svg representation is blue, and the HTML one show \"`HTML`\" between brackets." - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Solution" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here is my simple `MyCircle` class:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%load soln/mycircle.py" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 8 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now create an instance and use the display methods:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "c = MyCircle()" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 11 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "display_html(c)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "html": [ - "○ (html)" - ], - "metadata": {}, - "output_type": "display_data" - } - ], - "prompt_number": 12 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "display_svg(c)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "display_data", - "svg": [ - "\n", - " \n", - " " - ] - } - ], - "prompt_number": 13 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "display_latex(c)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "latex": [ - "$\\bigcirc \\LaTeX$" - ], - "metadata": {}, - "output_type": "display_data" - } - ], - "prompt_number": 14 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "display_javascript(c)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "javascript": [ - "alert('I am a circle!');" - ], - "metadata": {}, - "output_type": "display_data" - } - ], - "prompt_number": 15 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Adding IPython display support to existing objects" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "When you are directly writing your own classes, you can adapt them for display in IPython by following the above example. But in practice, we often need to work with existing code we can't modify. We now illustrate how to add these kinds of extended display capabilities to existing objects. To continue with our example above, we will add a PNG representation to our `Circle` class using Matplotlib." - ] - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Model citizen: sympy" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[SymPy](http://sympy.org) is another model citizen that defines rich representations of its object.\n", - "Unlike pandas above, sympy registers display formatters via IPython's display formatter API, rather than declaring `_repr_mime_` methods." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from sympy import Rational, pi, exp, I, symbols\n", - "x, y, z = symbols(\"x y z\")" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 16 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "r = Rational(3,2)*pi + exp(I*x) / (x**2 + y)\n", - "r" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 17, - "text": [ - "3*pi/2 + exp(I*x)/(x**2 + y)" - ] - } - ], - "prompt_number": 17 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "SymPy provides an `init_printing` function that sets up advanced $\\LaTeX$\n", - "representations of its objects." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from sympy.interactive.printing import init_printing\n", - "init_printing()\n", - "r" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "latex": [ - "$$\\frac{3}{2} \\pi + \\frac{e^{\\mathbf{\\imath} x}}{x^{2} + y}$$" - ], - "metadata": {}, - "output_type": "pyout", - "png": "iVBORw0KGgoAAAANSUhEUgAAAFAAAAAlCAYAAADV/m7fAAAABHNCSVQICAgIfAhkiAAAA9xJREFU\naIHt2l2IVVUUwPHfzDQ4hc1UFpaVTvoiKNoHaTCmU/lQaERR9mGUZBSkUVEQvcR9CSKIoCgoom5F\nBX2T+RD6EBQVZI1BBoVSFA0JUkNiiX1MD+uc5szkOPfjnHvvyP3DwF4z96y1Zp+91l577Uubuuho\ntgMNYikG8TU+wwosxs9YjTvwWy2Kj8mMV2IeZiTGythem78txwjmYitOxHs4G7vwthonbyL7cHMy\nvga/4/g8FLcAx+FVnIVesXDeRScW1KM4uwIH8V0yHkV3PYpbjFnifzsDt2J3Il+Mg9hTq+LJcuAr\nYnk/VKviJtKJe3AIv2I2Hi3SWJZzcR8O4LGijBbM0yKynsCbOKkZTtyGzzGzAbaWGJ9K6mEh/hC5\nfD1uETmvcC7AXpFkU0dGcXUDbJfRn5Oua7EjJ10VkYbwXyLnDSfyfPyJnY10Jge+FZtCSofYNAqr\nd9PQ2YHnsBn/iEJzrditsvTiGVyBnkl0jmIVPszb2QoYEnnvLvyCY7El8akQqnkzHaLoHMKnuEpU\n9d/jbjwldr6D+Fi8iEooo5ToOaq5CZdl5NfRlYy31KG3LL8c2HCq2f1ezIxPSJ79W1T5p+Tp1HSi\n1vJhPT5JxgvFRE7FC+JQP5G5WCbCfyIbRTnV8oxO8TORnTg/Ga/BN3XYLqsshKfysSk/6QqsZjNJ\nuzZfJHKvWEU9xpcQeTOVj53YJHZeeKRAX8YZrZZ7sc1Y2A6LyVudl1M1sgbviIlbjvMaYTQ7gcvF\nIbwkJmjlJM8sxbMZeQg/qO1l5MkCXJ+M9+DMRhqfiYcz8jrRDzy9AbbL8iljZhjrX76POTnorJgl\novBNm4u9Ikmua4Dtx3FqjvouxAM56quIDhHCaaJeJCbwnEY7Uid9eLDZTsBLCmxCFsgm0UnvVt2m\ntjlPJzaKnWy63djdIC6H9olO9OIqni3l5cRaMYFEadKfl+KcGMAG0S2/EbfjLdF+q4fSJL/vElee\nz4vTEpwsczrKlh6rxP3BVpHUL8VpdTqWJ73i2FjGB7hTtO/3i4qhCK4UTZPsYroIP6YfSMN0Pr70\n/xZ+n5zuTHOgR1QKh0TJNWJ86VUps0X7LZuiVuCjjLxfXKj1JfJuMYEH8GQiT9c7I0QIpWfxviN9\nsEJKR/jbdXgjI+8Sl29o/umhGi4XJ6V+sUEMiVW0oWC7c4zdG/cn8n9XHV2HeaBVGRD16SxxXbBM\n1KmvidCqh0GRVw/HXvFNjW7xAofxcp32jjrur/Bz20St2aZCFuEnkSoG8JWxdhmmVwg3g04xefNw\nifjCwUhTPWrTpk2bFuJflVvSLV1580UAAAAASUVORK5CYII=\n", - "prompt_number": 18, - "text": [ - " \u2148\u22c5x \n", - "3\u22c5\u03c0 \u212f \n", - "\u2500\u2500\u2500 + \u2500\u2500\u2500\u2500\u2500\u2500\n", - " 2 2 \n", - " x + y" - ] - } - ], - "prompt_number": 18 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To add a display method to an existing class, we must use IPython's display formatter API. Here we show all of the available formatters:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "ip = get_ipython()\n", - "for mime, formatter in ip.display_formatter.formatters.items():\n", - " print '%24s : %s' % (mime, formatter.__class__.__name__)\n" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - " text/html : HTMLFormatter\n", - " image/jpeg : JPEGFormatter\n", - " image/svg+xml : SVGFormatter\n", - " image/png : PNGFormatter\n", - " application/javascript : JavascriptFormatter\n", - " text/latex : LatexFormatter\n", - " application/json : JSONFormatter\n", - " text/plain : PlainTextFormatter\n" - ] - } - ], - "prompt_number": 6 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's grab the PNG formatter:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "png_f = ip.display_formatter.formatters['image/png']" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 20 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We will use the `for_type` method to register our display function." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "png_f.for_type?" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 21 - }, + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Defining Custom Display Logic for Your Own Objects" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Overview" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In Python, objects can declare their textual representation using the `__repr__` method. IPython expands on this idea and allows objects to declare other, richer representations including:\n", + "\n", + "* HTML\n", + "* JSON\n", + "* PNG\n", + "* JPEG\n", + "* SVG\n", + "* LaTeX\n", + "\n", + "This Notebook shows how you can add custom display logic to your own classes, so that they can be displayed using these rich representations. There are two ways of accomplishing this:\n", + "\n", + "1. Implementing special display methods such as `_repr_html_`.\n", + "2. Registering a display function for a particular type.\n", + "\n", + "In this Notebook we show how both approaches work." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Before we get started, we will import the various display functions for displaying the different formats we will create." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from IPython.display import display\n", + "from IPython.display import (\n", + " display_html, display_jpeg, display_png,\n", + " display_javascript, display_svg, display_latex\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Implementing special display methods" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The main idea of the first approach is that you have to implement special display methods, one for each representation you want to use. Here is a list of the names of the special methods and the values they must return:\n", + "\n", + "* `_repr_html_`: return raw HTML as a string\n", + "* `_repr_json_`: return raw JSON as a string\n", + "* `_repr_jpeg_`: return raw JPEG data\n", + "* `_repr_png_`: return raw PNG data\n", + "* `_repr_svg_`: return raw SVG data as a string\n", + "* `_repr_latex_`: return LaTeX commands in a string surrounded by \"$\"." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Model Citizen: pandas" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A prominent example of a package that has IPython-aware rich representations of its objects is [pandas](http://pandas.pydata.org/).\n", + "\n", + "A pandas DataFrame has a rich HTML table representation,\n", + "using `_repr_html_`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import io\n", + "import pandas" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As the docstring describes, we need to define a function the takes the object as a parameter and returns the raw PNG data." + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing data.csv\n" ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%matplotlib inline\n", - "import matplotlib.pyplot as plt" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 22 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "class AnotherCircle(object):\n", - " def __init__(self, radius=1, center=(0,0), color='r'):\n", - " self.radius = radius\n", - " self.center = center\n", - " self.color = color\n", - " \n", - " def __repr__(self):\n", - " return \"<%s Circle with r=%s at %s>\" % (\n", - " self.color,\n", - " self.radius,\n", - " self.center,\n", - " )\n", - " \n", - "c = AnotherCircle()\n", - "c" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 23, - "text": [ - "" - ] - } - ], - "prompt_number": 23 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.core.pylabtools import print_figure\n", - "\n", - "def png_circle(circle):\n", - " \"\"\"Render AnotherCircle to png data using matplotlib\"\"\"\n", - " fig, ax = plt.subplots()\n", - " patch = plt.Circle(circle.center,\n", - " radius=circle.radius,\n", - " fc=circle.color,\n", - " )\n", - " ax.add_patch(patch)\n", - " plt.axis('scaled')\n", - " data = print_figure(fig, 'png')\n", - " # We MUST close the figure, otherwise IPython's display machinery\n", - " # will pick it up and send it as output, resulting in a double display\n", - " plt.close(fig)\n", - " return data" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 24 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "c = AnotherCircle()\n", - "print repr(png_circle(c)[:10])" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "'\\x89PNG\\r\\n\\x1a\\n\\x00\\x00'\n" - ] - } - ], - "prompt_number": 27 - }, + } + ], + "source": [ + "%%writefile data.csv\n", + "Date,Open,High,Low,Close,Volume,Adj Close\n", + "2012-06-01,569.16,590.00,548.50,584.00,14077000,581.50\n", + "2012-05-01,584.90,596.76,522.18,577.73,18827900,575.26\n", + "2012-04-02,601.83,644.00,555.00,583.98,28759100,581.48\n", + "2012-03-01,548.17,621.45,516.22,599.55,26486000,596.99\n", + "2012-02-01,458.41,547.61,453.98,542.44,22001000,540.12\n", + "2012-01-03,409.40,458.24,409.00,456.48,12949100,454.53\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", + "data": { + "text/plain": [ + " Date Open High Low Close Volume Adj Close\n", + "0 2012-06-01 569.16 590.00 548.50 584.00 14077000 581.50\n", + "1 2012-05-01 584.90 596.76 522.18 577.73 18827900 575.26\n", + "2 2012-04-02 601.83 644.00 555.00 583.98 28759100 581.48\n", + "3 2012-03-01 548.17 621.45 516.22 599.55 26486000 596.99\n", + "4 2012-02-01 458.41 547.61 453.98 542.44 22001000 540.12\n", + "5 2012-01-03 409.40 458.24 409.00 456.48 12949100 454.53" + ] + }, + "execution_count": 4, "metadata": {}, - "source": [ - "Now we register the display function for the type:" - ] - }, + "output_type": "execute_result" + } + ], + "source": [ + "df = pandas.read_csv(\"data.csv\")\n", + "pandas.set_option('display.notebook_repr_html', False)\n", + "df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "rich HTML can be activated via `pandas.set_option`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "png_f.for_type(AnotherCircle, png_circle)" - ], - "language": "python", + "data": { + "text/html": [ + "
\n", + "
DateOpenHighLowCloseVolumeAdj Close
0 2012-06-01 569.16 590.00
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
DateOpenHighLowCloseVolumeAdj Close
0 2012-06-01 569.16 590.00 548.50 584.00 14077000 581.50
1 2012-05-01 584.90 596.76 522.18 577.73 18827900 575.26
2 2012-04-02 601.83 644.00 555.00 583.98 28759100 581.48
3 2012-03-01 548.17 621.45 516.22 599.55 26486000 596.99
4 2012-02-01 458.41 547.61 453.98 542.44 22001000 540.12
5 2012-01-03 409.40 458.24 409.00 456.48 12949100 454.53
\n", + "
" + ], + "text/plain": [ + " Date Open High Low Close Volume Adj Close\n", + "0 2012-06-01 569.16 590.00 548.50 584.00 14077000 581.50\n", + "1 2012-05-01 584.90 596.76 522.18 577.73 18827900 575.26\n", + "2 2012-04-02 601.83 644.00 555.00 583.98 28759100 581.48\n", + "3 2012-03-01 548.17 621.45 516.22 599.55 26486000 596.99\n", + "4 2012-02-01 458.41 547.61 453.98 542.44 22001000 540.12\n", + "5 2012-01-03 409.40 458.24 409.00 456.48 12949100 454.53" + ] + }, + "execution_count": 5, "metadata": {}, - "outputs": [], - "prompt_number": 28 - }, + "output_type": "execute_result" + } + ], + "source": [ + "pandas.set_option('display.notebook_repr_html', True)\n", + "df" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now all `Circle` instances have PNG representations!" + "name": "stdout", + "output_type": "stream", + "text": [ + "
\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n" ] - }, + } + ], + "source": [ + "lines = df._repr_html_().splitlines()\n", + "print \"\\n\".join(lines[:20])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exercise" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Write a simple `Circle` Python class. Don't even worry about properties such as radius, position, colors, etc. To help you out use the following representations (remember to wrap them in Python strings):\n", + "\n", + "For HTML:\n", + "\n", + " ○\n", + "\n", + "For SVG:\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "For LaTeX (wrap with `$` and use a raw Python string):\n", + "\n", + " \\bigcirc\n", + "\n", + "After you write the class, create an instance and then use `display_html`, `display_svg` and `display_latex` to display those representations.\n", + "\n", + "Tips : you can slightly tweek the representation to know from which `_repr_*_` method it came from. \n", + "For example in my solution the svg representation is blue, and the HTML one show \"`HTML`\" between brackets." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Solution" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here is my simple `MyCircle` class:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%load soln/mycircle.py" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now create an instance and use the display methods:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "c = MyCircle()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "c2 = AnotherCircle(radius=2, center=(1,0), color='g')\n", - "c2" - ], - "language": "python", + "data": { + "text/html": [ + "○ (html)" + ] + }, "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "png": "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\nTkSuQmCC\n", - "prompt_number": 29, - "text": [ - "" - ] - } - ], - "prompt_number": 29 - }, + "output_type": "display_data" + } + ], + "source": [ + "display_html(c)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "display_png(c2)" - ], - "language": "python", + "data": { + "image/svg+xml": [ + "\n", + " \n", + " " + ] + }, "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "display_data", - "png": "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\nTkSuQmCC\n" - } - ], - "prompt_number": 30 - }, + "output_type": "display_data" + } + ], + "source": [ + "display_svg(c)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "heading", - "level": 2, + "data": { + "text/latex": [ + "$\\bigcirc \\LaTeX$" + ] + }, "metadata": {}, - "source": [ - "return the object" - ] - }, + "output_type": "display_data" + } + ], + "source": [ + "display_latex(c)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "# for demonstration purpose, I do the same with a circle that has no _repr_javascript method\n", - "class MyNoJSCircle(MyCircle):\n", - " \n", - " def _repr_javascript_(self):\n", - " return\n", - "\n", - "cNoJS = MyNoJSCircle()" - ], - "language": "python", + "data": { + "application/javascript": [ + "alert('I am a circle!');" + ] + }, "metadata": {}, - "outputs": [] - }, + "output_type": "display_data" + } + ], + "source": [ + "display_javascript(c)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Adding IPython display support to existing objects" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When you are directly writing your own classes, you can adapt them for display in IPython by following the above example. But in practice, we often need to work with existing code we can't modify. We now illustrate how to add these kinds of extended display capabilities to existing objects. To continue with our example above, we will add a PNG representation to our `Circle` class using Matplotlib." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Model citizen: sympy" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[SymPy](http://sympy.org) is another model citizen that defines rich representations of its object.\n", + "Unlike pandas above, sympy registers display formatters via IPython's display formatter API, rather than declaring `_repr_mime_` methods." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from sympy import Rational, pi, exp, I, symbols\n", + "x, y, z = symbols(\"x y z\")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", + "data": { + "text/plain": [ + "3*pi/2 + exp(I*x)/(x**2 + y)" + ] + }, + "execution_count": 17, "metadata": {}, - "source": [ - "Of course you can now still return the object, and this will use compute all the representations, store them in the notebook and show you the appropriate one." - ] - }, + "output_type": "execute_result" + } + ], + "source": [ + "r = Rational(3,2)*pi + exp(I*x) / (x**2 + y)\n", + "r" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "SymPy provides an `init_printing` function that sets up advanced $\\LaTeX$\n", + "representations of its objects." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "cNoJS" - ], - "language": "python", + "data": { + "image/png": [ + "iVBORw0KGgoAAAANSUhEUgAAAFAAAAAlCAYAAADV/m7fAAAABHNCSVQICAgIfAhkiAAAA9xJREFU\n", + "aIHt2l2IVVUUwPHfzDQ4hc1UFpaVTvoiKNoHaTCmU/lQaERR9mGUZBSkUVEQvcR9CSKIoCgoom5F\n", + "BX2T+RD6EBQVZI1BBoVSFA0JUkNiiX1MD+uc5szkOPfjnHvvyP3DwF4z96y1Zp+91l577Uubuuho\n", + "tgMNYikG8TU+wwosxs9YjTvwWy2Kj8mMV2IeZiTGythem78txwjmYitOxHs4G7vwthonbyL7cHMy\n", + "vga/4/g8FLcAx+FVnIVesXDeRScW1KM4uwIH8V0yHkV3PYpbjFnifzsDt2J3Il+Mg9hTq+LJcuAr\n", + "Ynk/VKviJtKJe3AIv2I2Hi3SWJZzcR8O4LGijBbM0yKynsCbOKkZTtyGzzGzAbaWGJ9K6mEh/hC5\n", + "fD1uETmvcC7AXpFkU0dGcXUDbJfRn5Oua7EjJ10VkYbwXyLnDSfyfPyJnY10Jge+FZtCSofYNAqr\n", + "d9PQ2YHnsBn/iEJzrditsvTiGVyBnkl0jmIVPszb2QoYEnnvLvyCY7El8akQqnkzHaLoHMKnuEpU\n", + "9d/jbjwldr6D+Fi8iEooo5ToOaq5CZdl5NfRlYy31KG3LL8c2HCq2f1ezIxPSJ79W1T5p+Tp1HSi\n", + "1vJhPT5JxgvFRE7FC+JQP5G5WCbCfyIbRTnV8oxO8TORnTg/Ga/BN3XYLqsshKfysSk/6QqsZjNJ\n", + "uzZfJHKvWEU9xpcQeTOVj53YJHZeeKRAX8YZrZZ7sc1Y2A6LyVudl1M1sgbviIlbjvMaYTQ7gcvF\n", + "IbwkJmjlJM8sxbMZeQg/qO1l5MkCXJ+M9+DMRhqfiYcz8jrRDzy9AbbL8iljZhjrX76POTnorJgl\n", + "ovBNm4u9Ikmua4Dtx3FqjvouxAM56quIDhHCaaJeJCbwnEY7Uid9eLDZTsBLCmxCFsgm0UnvVt2m\n", + "tjlPJzaKnWy63djdIC6H9olO9OIqni3l5cRaMYFEadKfl+KcGMAG0S2/EbfjLdF+q4fSJL/vElee\n", + "z4vTEpwsczrKlh6rxP3BVpHUL8VpdTqWJ73i2FjGB7hTtO/3i4qhCK4UTZPsYroIP6YfSMN0Pr70\n", + "/xZ+n5zuTHOgR1QKh0TJNWJ86VUps0X7LZuiVuCjjLxfXKj1JfJuMYEH8GQiT9c7I0QIpWfxviN9\n", + "sEJKR/jbdXgjI+8Sl29o/umhGi4XJ6V+sUEMiVW0oWC7c4zdG/cn8n9XHV2HeaBVGRD16SxxXbBM\n", + "1KmvidCqh0GRVw/HXvFNjW7xAofxcp32jjrur/Bz20St2aZCFuEnkSoG8JWxdhmmVwg3g04xefNw\n", + "ifjCwUhTPWrTpk2bFuJflVvSLV1580UAAAAASUVORK5CYII=\n" + ], + "text/latex": [ + "$$\\frac{3}{2} \\pi + \\frac{e^{\\mathbf{\\imath} x}}{x^{2} + y}$$" + ], + "text/plain": [ + " \u2148\u22c5x \n", + "3\u22c5\u03c0 \u212f \n", + "\u2500\u2500\u2500 + \u2500\u2500\u2500\u2500\u2500\u2500\n", + " 2 2 \n", + " x + y" + ] + }, + "execution_count": 18, "metadata": {}, - "outputs": [] - }, + "output_type": "execute_result" + } + ], + "source": [ + "from sympy.interactive.printing import init_printing\n", + "init_printing()\n", + "r" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To add a display method to an existing class, we must use IPython's display formatter API. Here we show all of the available formatters:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Or just use `display(object)` if you are in a middle of a loop" + "name": "stdout", + "output_type": "stream", + "text": [ + " text/html : HTMLFormatter\n", + " image/jpeg : JPEGFormatter\n", + " image/svg+xml : SVGFormatter\n", + " image/png : PNGFormatter\n", + " application/javascript : JavascriptFormatter\n", + " text/latex : LatexFormatter\n", + " application/json : JSONFormatter\n", + " text/plain : PlainTextFormatter\n" ] - }, + } + ], + "source": [ + "ip = get_ipython()\n", + "for mime, formatter in ip.display_formatter.formatters.items():\n", + " print '%24s : %s' % (mime, formatter.__class__.__name__)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's grab the PNG formatter:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "png_f = ip.display_formatter.formatters['image/png']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will use the `for_type` method to register our display function." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "png_f.for_type?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As the docstring describes, we need to define a function the takes the object as a parameter and returns the raw PNG data." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "for i in range(3):\n", - " display(cNoJS)" - ], - "language": "python", + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 23, "metadata": {}, - "outputs": [] - }, + "output_type": "execute_result" + } + ], + "source": [ + "class AnotherCircle(object):\n", + " def __init__(self, radius=1, center=(0,0), color='r'):\n", + " self.radius = radius\n", + " self.center = center\n", + " self.color = color\n", + " \n", + " def __repr__(self):\n", + " return \"<%s Circle with r=%s at %s>\" % (\n", + " self.color,\n", + " self.radius,\n", + " self.center,\n", + " )\n", + " \n", + "c = AnotherCircle()\n", + "c" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "from IPython.core.pylabtools import print_figure\n", + "\n", + "def png_circle(circle):\n", + " \"\"\"Render AnotherCircle to png data using matplotlib\"\"\"\n", + " fig, ax = plt.subplots()\n", + " patch = plt.Circle(circle.center,\n", + " radius=circle.radius,\n", + " fc=circle.color,\n", + " )\n", + " ax.add_patch(patch)\n", + " plt.axis('scaled')\n", + " data = print_figure(fig, 'png')\n", + " # We MUST close the figure, otherwise IPython's display machinery\n", + " # will pick it up and send it as output, resulting in a double display\n", + " plt.close(fig)\n", + " return data" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Advantage of using `display()` versus `display_*()` is that all representation will be stored in the notebook document and notebook file, they are then availlable for other frontends or post-processing tool like `nbconvert`." + "name": "stdout", + "output_type": "stream", + "text": [ + "'\\x89PNG\\r\\n\\x1a\\n\\x00\\x00'\n" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's compare `display()` vs `display_html()` for our circle in the Notebook Web-app and we'll see later the difference in nbconvert." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print \"I should see a nice html circle in web-app, but\"\n", - "print \"nothing if the format I'm viewing the notebook in\"\n", - "print \"does not support html\"\n", - "display_html(cNoJS)" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "print \"Whatever the format I will see a representation\"\n", - "print \"of my circle\"\n", - "display(cNoJS)" - ], - "language": "python", - "metadata": {}, - "outputs": [] - }, + } + ], + "source": [ + "c = AnotherCircle()\n", + "print repr(png_circle(c)[:10])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we register the display function for the type:" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "png_f.for_type(AnotherCircle, png_circle)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now all `Circle` instances have PNG representations!" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "print \"Same if I return the object\"\n", - "cNoJS" - ], - "language": "python", + "data": { + "image/png": [ + "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\n", + "TkSuQmCC\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 29, "metadata": {}, - "outputs": [] - }, + "output_type": "execute_result" + } + ], + "source": [ + "c2 = AnotherCircle(radius=2, center=(1,0), color='g')\n", + "c2" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "print \"But not if I print it\"\n", - "print cNoJS" - ], - "language": "python", + "data": { + "image/png": [ + "iVBORw0KGgoAAAANSUhEUgAAAY4AAAF8CAYAAADYXlxuAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n", + "AAALEgAACxIB0t1+/AAAIABJREFUeJzt3Xlc1HXiP/DXDDAgwwwzwyngAQhoItGotR1rB5ZbqXmV\n", + "9e1a92ttaK7Z9NvWyqx2q83VvliQbrrZYamlVLqtdx5pbYUIiniAeAJyODOcg1zz+8PV1UAcmOP9\n", + "mZnX8/Ho8Yjh8/m8X34iXn6u90dmtVqtICIispFcdAAiInIvLA4iIuoWFgcREXULi4OIiLqFxUFE\n", + "RN3C4iAiom7xdcRGFi5ciOLiYvj6+kKtVuOJJ55ATExMh+VKS0uxaNEi1NXVQa1WIz09HVFRUY6I\n", + "QERELiJzxHMcOTk50Ov1kMvl2LZtG7Zt24bXXnutw3KzZs3ClClTkJKSgtzcXKxevRpvvPGGvcMT\n", + "EZELOeRU1bBhwyCXn99UbGwsTCZTh2WqqqpgsViQkpICANDr9aiurobZbHZEBCIichGHX+PYsmUL\n", + "9Hp9h8+NRiNUKtVlnwUHB8NoNDo6AhEROZFDi2PTpk0oKirCQw891Plg8o7Dtba2OjICERE5mUMu\n", + "jgPA2rVr8eOPP2LOnDkICAjo8H2tVtvhtJTZbIZOp+t0e9u2bUN7e7uj4hER0SX8/PwwYsSIHq1r\n", + "d3G0t7fjgw8+QGVlZYfSMJlMkMlk0Gg0CA8Ph1KpRF5eHlJTU5GTkwO1Wo3Q0NArbrezU15StG7d\n", + "OowZM0Z0DJu4U1bA/rxNrU0wNhlRe64WpiYTjE1GVDZW4qj5KIpNxThVdwoVDRUwNok/ZRrkF4QI\n", + "ZQR6K3sjQZuAeG08eit7Q9dLB12ADsH+wdD4a6D2V9s9ljv9HDCrc+Tm5vZ4XbuLo7q6Gps3b0Zk\n", + "ZCRmz5598fPp06dj06ZNAIBp06YBAAwGAxYvXoxly5ZBrVbDYDDYOzwRAKC5rRmVjZWobqxGlaUK\n", + "1Y3V2F+9H3vO7EGRqQjmc9K/CaO+pR715nocNR/FrtJdHb4f4BOA2OBYpISnYGjEUPQO6o3QXqEI\n", + "DwxHWGAYghRBAlKTN7K7OMLDw7Fq1apOvzdgwIDLvo6Ojsaf//xne4ckL3ehJKoaq1DRUIEiUxE2\n", + "n9iMvIo81LfUi47nNE1tTThoPIiDxoNYdei//8/5yn2RpEvCHX3vwHUR16G3sjfLhJzKYdc4iJzB\n", + "z88PjS2NKKsvw+m60yg8W4gNxzZ4fEl0R2t7Kw5UH8CB6gMXP7tQJnf2uxPDew9HH1UfRAVFQder\n", + "82uKRN3B4iDJuVAUpXWlOBh8EAuyFyCvMg9t1jbR0dxGZ2XST90P9yXchxt63wBlrBJGi5FFQj3C\n", + "4nCAxMRE0RFsJtWs5fXlOFF7Avur9mP14dXIrchlUTjYidoTeGfPOxe/jt0Ti/EJ43FT9E3oH9wf\n", + "fVR94OfjJzBh56T6M9sZd8pqD4dMOeIMW7dudZu7qqj7WttacaruFI7XHMf2U9ux8uBKVFmqRMfy\n", + "WgE+ARgVOwoTEicgXhOPvuq+vD7i4XJzc5GWltajdXnEQS5jabXgeM1xFJuKkX0kGxuPbURTW5Po\n", + "WITzF96/Lv4aXxd/DRlk0Efo8ejgRzE4dDDiguOg7aUVHZEkhMVBTtXa3ooTtSdw+OxhfHLgE2w+\n", + "sRntVj7YKWVWWLGnYg/2VOwBAAzUDUR6ajqui7gOcZo4BPoFCk5IorE4yClO155GsbkYXxV9hc8P\n", + "fc4jCzd2yHgIM7+dCRlkuCXmFvxuyO8wKGQQYoNjJXlNhJyPxUEOY24y44jxCLae3Ip/7PuHJJ7G\n", + "JsexworvTn+H705/Bz+5H+4bcB8mD5qMQbpBiFLxvTrehMVBdjtecxz7q/Zj4Z6FyK3o+TQG5D5a\n", + "2luw+shqrD6yGhGBEZg1fBZuib4F8Zp4+Pv6i45HTsbioB5paG5AsbkYW45vQebeTNScqxEdiQSp\n", + "aKzAn3b8CT4yH0xKmoSHr3kYA3UDERrY+Tx05P5YHNQtZxrOoLC6EEvzl2LD8Q2i45CEtFnbsOrQ\n", + "Kqw6tAoJmgQYrjdgaMRQxGniIJPJRMcjB2JxkE1O151GTnkOXvv+NRyvPS46DklckbkIT216CkF+\n", + "QTAMN2BU7CgkaBPgI/cRHY0cgMVBXTpmPobdpbvx+g+vo6KxQnQccjP1LfV49ftX8eaPb2Ja6jSM\n", + "TxyPJG0SFL4K0dHIDiwO6qDd2o5iUzG+PfEt/vrjX1HbXCs6Erm55rZmZOzJwDu57+CxwY/h0cGP\n", + "IkmXxGdC3BSLgy6yWq04YjqCtUVrkbEnA5ZWi+hI5GHare34sOBDfFjwIe4bcB+m66djcMhg9PLr\n", + "JToadQOLgwAAJeYSbDy2Ea//8DoaWxtFxyEvcGGKk4cHPYwnU5/EQN1APlDoJlgcXu503WnsPLUT\n", + "c3bNganJJDoOeaFPD36KlYdW4mn905g8cDKSdEm8C0vi5KIDkBiVDZVYV7wOd39xN57e8jRLg4Rq\n", + "s7Zh4Z6FuGPlHcjMzUSJuUR0JOoCi8PL1DfXY+epnRj/1Xg8/q/HUVpfKjoS0UVNbU2Yu3su7lh5\n", + "B5YfWI4z9WdER6JO8FSVl7BarSg8W4h3ct7BF0e+EB2HqEu1zbX4w9Y/IEmXhPm3zUdqeCqUCqXo\n", + "WPQfPOLwAqV1pViybwnuXHUnS4PcymHjYYzJHoPZO2fj0NlDkOh757wOi8OD1TXXYfvJ7RibPRZ/\n", + "2vEnTm1Obmt54XKMXDUSHxV8hPL6ctFxvB6Lw0MVVhfi2W+fxYSvJuBYzTHRcYjs1tjaiGe3PYtx\n", + "X47D7tO70djC28ZFYXF4GKPFiFUHV+Guz+/CmiNrRMchcrgiUxHGZI/BG/9+A8drjouO45VYHB6k\n", + "sLoQT216Cumb0/kQH3m89/a+hzFrxmDnqZ08+nAxFocHuPQoY8uJLaLjELlMaX0pxn05jkcfLsbi\n", + "cHM8yiDi0YersTjcVM25Gqw5vIZHGUT/ceHo468//hWnak+JjuPRWBxu6HjNccz5bg6e2PgEjzKI\n", + "fiEzNxOT105GfmU+n/twEhaHG2lrb0POmRxM+GoClhcuFx2HSLIOGQ/h7i/uxpoja1BzrkZ0HI/D\n", + "4nATxiYjPi38FPesvocXAYls0NTWhCc3PomXd73M/2ccjMXhBkrMJXhu23N45ttn0NreKjoOkVv5\n", + "5MAnmPDVBOScyUFbe5voOB6BxSFh7dZ2/Fz+M8auGYuvir4SHYfIbR2vOY57V9+LVYdWoaaJp67s\n", + "xeKQqIbmBnxd9DVGrxmNsoYy0XGI3F5Lewue3vI03vrxLc53ZSeHTKteW1uLF198Ec8//zxiYmI6\n", + "XSYrKwv5+flQKv87NbLBYLji8t6ssrESH+z7APN+mic6CpHHWZy/GAVnC/D27W9jgHaA6Dhuye7i\n", + "WLt2LdatW4f6+voul5PJZJg4cSJGjRpl75Ae7XjNcbz03Uv4V8m/REch8li7Tu/CuOxx+Ojej6CP\n", + "0PNVtd1k96mqsWPHYsmSJdDpdFddlvdUd21/1X7c/9X9LA0iFyhrKMOYNWOwvmQ9LK0W0XHcikuv\n", + "cWRnZ2PmzJl45ZVXUFBQ4MqhJa2lrQXbTm7D6DWjcbTmqOg4RF6jqa0Jj3zzCJbkL8FZy1nRcdyG\n", + "y14dO3XqVCgUCgBAQUEBFixYgKysLAQGBroqgiQ1tjTim5JvkL4pHe3WdtFxiLzSK7tfwTHzMfzx\n", + "V39Eb2Vv0XEkz2VHHBdKAwCSk5Oh0WhQWVnpquElqaapBh8XfIzfb/w9S4NIsI8OfATDtwbOc2UD\n", + "hxbHpdcwTCYTzGbzxa9zc3PR3n7+l2NhYSEsFguioqIcObxbqW6sxsLchXjhuxdERyGi/9hwbAP+\n", + "d/3/4qiJp4y7Yvepqg0bNmDHjh0wm83IyMhAXFwcpk+fjhUrVgAApk2bBgDYvn07li5dCoVCAZVK\n", + "BYPBcNlRiDcpry/H6z+8js8OfiY6ChH9Qk5FDh5c+yA+Hv0xBoUMEh1HkmRWid7qtHXrVuj1etEx\n", + "HO5k7UkYthmw9cRW0VGIqAthvcKwcuxKXBdxnegoTpGbm4u0tLQercsnx13oqOkofvuv37I0iNxA\n", + "laUK92Xfh+9Lv+ejBL/A4nCRYlMxHvnmEeRV5omOQkQ2qm+px6SvJmF36W6WxyVYHC5QZCzCw/98\n", + "GIeNh0VHIaJuamprwv1f34+dp3fy7sf/YHE4WZGxCP+z7n9QZCoSHYWIeuhc2zlMXjsZO07t4JEH\n", + "WBxOdaE0+DQ4kftrbmvGQ+sews7TO72+PFgcTlJsKsaj/3qUpUHkQZrbmvHg2gex6/Qury4PFocT\n", + "lJhL8Pg3j+OI8YjoKETkYBdOW/1Q9oPoKMKwOBzsZO1JpG9Kx0HjQdFRiMhJmtqa8NC6h5BX4Z13\n", + "SbI4HOhM/Rm8uPNF/HzmZ9FRiMjJ6prrMHntZBw8631/SWRxOMhZy1nM+2kevin5RnQUInKRKksV\n", + "HvvnY143txWLwwFqz9ViSf4SfFjwoegoRORiR2uOIn1TOk7WnhQdxWVYHHZqam3C6sOr+X5wIi+W\n", + "U5GDP+34E8obykVHcQkWhx3a2tuw4dgGPLf9OdFRiEiwDcc2YN6/53nFmwRZHHb4sfxHPLnxSdEx\n", + "iEgiPjrwET4t/NTj32HO4uihg2cP4tFvHkVre6voKEQkIa/sfgXbTmzz6AcEWRw9cKr2FJ7a+BRM\n", + "TSbRUYhIgqZumIq9FXtFx3AaFkc3nbWcxes/vI791ftFRyEiiWpqa8Jj3zyGYlOx6ChOweLohqbW\n", + "Jqw4uAKfH/5cdBQikriyhjI8u+1ZlNd73p1WLI5u2H5yO17e9bLoGETkJnad3oV397yLmqYa0VEc\n", + "isVho/zKfPzvhv8VHYOI3Mzi/MX4Z8k/0dbeJjqKw7A4bHCy9iSe3PCkx99iR0TO8ey3z2Jvpedc\n", + "LGdxXEVdcx0yczNRZOYb/IioZ1raW/DkxidxvOa46CgOweK4im9PfIul+5aKjkFEbu54zXG8/fPb\n", + "qDnn/tc7WBxd2F+1H9M3Txcdg4g8xPLC5dh0fJPbPxzI4riC0rpSzNg8A42tjaKjEJEH+cOWPyC/\n", + "Kl90DLuwODrR0NyAJflLsK96n+goRORhzrWdQ/qmdJyqPSU6So+xODrxQ9kPeCf3HdExiMhDHTYe\n", + "xt/z/47GFvc8o8Hi+IViUzHSN6eLjkFEHu69ve8h50yO6Bg9wuK4RH1zPd7d865XzKdPROKlb0p3\n", + "y1t0WRyX+KH0B3xS+InoGETkJcobyrEkf4nbnbJicfzHUdNRTNsyTXQMIvIyi/IWud0pKxYHzj8d\n", + "/s6ed3iKioiEcLdTViwO8BQVEYnlbqesvL44SswlmL6FT4cTkViL8hYhtyJXdAybOKQ4amtrMWPG\n", + "DJw+ffqKy5SWluKll17CzJkzMWfOHJSVlTliaLs0tzbjs8LPeIqKiCTB8K0BZfXifzdejd3FsXbt\n", + "WhgMBlRXV3e53Pz58/HAAw9g4cKFGD9+PDIzM+0d2m4F1QXI2JMhOgYREQCgyFyEjcc2Sn4uK7uL\n", + "Y+zYsViyZAl0Ot0Vl6mqqoLFYkFKSgoAQK/Xo7q6Gmaz2d7he+ys5Szm7p6Ldmu7sAxERL8057s5\n", + "OGQ8JDpGl1xyjcNoNEKlUl32WXBwMIxGoyuG79Tu0t3YXbpb2PhERJ1pbG1EVm4WGpobREe5Ipdd\n", + "HJfLOw7V2trqquEvU2IugWGbQcjYRERX89nBzyT9xkCXFIdWq+1wWspsNnd5estZWtpasPLgSl4Q\n", + "JyJJM2wzoLy+XHSMTjm0OC69oGMymS6WRXh4OJRKJfLy8gAAOTk5UKvVCA0NdeTwNimoLsDbOW+7\n", + "fFwiou4oMhVhy/EtomN0ytfeDWzYsAE7duyA2WxGRkYG4uLiMH36dKxYsQIAMG3a+Wk8DAYDFi9e\n", + "jGXLlkGtVsNgcP2porrmOmTkZPCCOBG5hTm75uCm6JsQr40XHeUyMqtE7/vaunUr9Hq9Q7f5Q+kP\n", + "uHfNvQ7dJhGRM829aS5mDJ0BucyxVxZyc3ORlpbWo3W95slxU5MJf/nhL6JjEBF1y1s/voXDZw+L\n", + "jnEZrymOvRV78UPZD6JjEBF1S1NbE1YeWomWthbRUS7yiuKoaKjAS9+9JDoGEVGPvLf3PRw8e1B0\n", + "jIu8ojh+LP9R8k9iEhFdSZu1De/nvw9Li0V0FABeUByn607jhZ0viI5BRGSXzw5+hgNnD4iOAcAL\n", + "iuPfZf92i9kmiYiu5t0970piKhKPLo7y+nK8uvtV0TGIiBxi3dF1OGwUf4eVRxfH3oq9KK0vFR2D\n", + "iMhhPj7wMc61nhOawWOLo7qxms9tEJHHWV64HEdMR4Rm8Nji2Fe1j3dSEZHHabe2Y82RNWhtFzO7\n", + "OOChxWFuMmPeT/NExyAicorFeYtRZCoSNr5HFkfh2UL8VP6T6BhERE7R3NaMDSUbhL1i1uOKo6G5\n", + "AVm5WaJjEBE51ds5b+Oo+aiQsT2uOIpMRVh/bL3oGERETtXQ0oCcMzlCxva44th8fLPoCERELrHg\n", + "pwWoaqxy+bgeVRzHa44jc2+m6BhERC5xtOYoDp11/d2jHlUc+yr3oa65TnQMIiKXWV643OUPBHpM\n", + "cZiaTMjYkyE6BhGRS2UfyUaxudilY3pMcRw2HkZeZZ7oGERELtVmbcOu07tcOqZHFEdreyuyj2SL\n", + "jkFEJMT/5fwfTteddtl4HlEcx8zH8PGBj0XHICISorKx0qUXyT2iOArPFqK5rVl0DCIiYVYeWonm\n", + "Vtf8HnT74mhsacQH+z8QHYOISKh1xetwrPaYS8Zy++I4aj7q8gtDRERS09Le4rLTVW5fHHkVebBC\n", + "zERfRERSsnTfUtQ31zt9HLcuDqPFiPf2vic6BhGRJOwu3Y1jNc4/XeXWxVFiLsFhk/j37xIRScXe\n", + "ir1OH8Oti2NXKa9tEBFd6r2978FoMTp1DLctjoqGCizJXyI6BhGRpBwxHUFJTYlTx3Db4jhZexLl\n", + "DeWiYxARSc6B6gNO3b7bFse+qn2iIxARSdLHBR+j7pzzZgp3y+KoPVfLKUaIiK5gb+VenKo75bTt\n", + "u2VxnKw9if1V+0XHICKSrCJTkdO27ZbF4cwdQkTkCdYcWeO0uavcrjia25rxxeEvRMcgIpK0zcc3\n", + "O+10la8jNlJaWopFixahrq4OarUa6enpiIqK6rBcVlYW8vPzoVQqL35mMBgQExNj81inak/h25Pf\n", + "OiI2EZHHOtd2DidqTyBeG+/wbTukOObPn48pU6YgJSUFubm5yMzMxBtvvNFhOZlMhokTJ2LUqFE9\n", + "Hut47XFOoU5EZIOdp3fijn53OHy7dp+qqqqqgsViQUpKCgBAr9ejuroaZrO50+WtVvsmJHTF4/RE\n", + "RJ7g66KvcdZy1uHbtfuIw2g0QqVSXfZZcHAwjEYjNBpNh+Wzs7Oxfv16aLVaTJo0CcnJyTaPVXOu\n", + "Bl8e+dLeyEREXuFE7QmU15cjpFeIQ7frkFNVcnnHA5fW1tYOn02dOhUKhQIAUFBQgAULFiArKwuB\n", + "gYE2jVNeX46DxoP2hSUi8iKn604jOcz2v6Dbwu5TVVqttsNpKbPZDJ1O12HZC6UBAMnJydBoNKis\n", + "rLR5LFe+jJ2IyBN8X/q9w7dpd3GEh4dDqVQiLy8PAJCTkwO1Wo3Q0FCYTKbLSiU3Nxft7e0AgMLC\n", + "Qlgslk7vvrqS3Ipce+MSEXmVr4u/xtlGx17ncMipKoPBgMWLF2PZsmVQq9UwGAwAgBUrVgAApk2b\n", + "BgDYvn07li5dCoVCAZVKBYPBcNlRSFdqmmrwZRGvbxARdcepulMoayhDSKDjrnPIrPbe5uQkW7du\n", + "hV6vv/j1wbMHcfOnNwtMRETknj4d/Snujrv7ss9yc3ORlpbWo+25zZPjpXWloiMQEbmlH0p/cOj2\n", + "3KY4SszOfTEJEZGn2npiK+qaHTfNulsUR2tbKzaf2Cw6BhGRWzpiOoKqxiqHbc8tiqOysRI5Z3JE\n", + "xyAicktt1jZUNFY4bHtuURxVlirUnKsRHYOIyG1VNtj+zNzVuEVxVDY67g9MROSNfj7zs8O25RbF\n", + "ccx8THQEIiK3tu3kNoddIJd8cbS2tWLLiS2iYxARubUjRsddIJd8cVRZqnhhnIjITm3WNoed9pd8\n", + "cZiaTDCf6/zdHkREZDtHvZtD8sVhbDKKjkBE5BFK6x0zA4fki8PUZBIdgYjII+SUO+a0v+SLg+/g\n", + "ICJyjP3V+x3yTJzki4MXxomIHONYzTHUNHl4cZibzNhftV90DCIij9Dc1gzTOftP/0u6OGrO1eBE\n", + "7QnRMYiIPIYjrhtLujjM58xoaW8RHYOIyGN4fHFwYkMiIsc6WXfS7m1Iujgc+eIRIiICjpqO2r0N\n", + "SRdHfUu96AhERB7lqPkoWtta7dqGpIujrK5MdAQiIo9S3lCO2uZau7Yh6eI4arb/kIqIiP6roqEC\n", + "ja2Ndm1D0sVRbCoWHYGIyKNYWi1obPHg4jjTeEZ0BCIij2Pv9WNJF0dFg+Nerk5EROc1NDfYtb6k\n", + "i+Nc2znREYiIPE5DiwcXBxEROZ69fylncRAReZnmtma71mdxEBF5maa2JrvWZ3EQEXkZc5PZrvVZ\n", + "HEREXsbYZLRrfRYHEZGXsXdqdRYHEZGXOWs5a9f6LA4iIi9ztsm+4vB1RIjS0lIsWrQIdXV1UKvV\n", + "SE9PR1RUVI+XIyIi5znXKoHnOObPn48HHngACxcuxPjx45GZmWnXckRE5Dzt1na71re7OKqqqmCx\n", + "WJCSkgIA0Ov1qK6uhtls7tFyRETkXG3WNrvWt7s4jEYjVCrVZZ8FBwfDaDT2aDkiInIu4UccACCX\n", + "d9xMa2vHVxPauhwRETmPFVa71re7OLRabYfTTWazGTqdrkfLERGRc8ll9v3qt7s4wsPDoVQqkZeX\n", + "BwDIycmBWq1GaGgoTCbTxbLoajkiInIde4vDIbfjGgwGLF68GMuWLYNarYbBYAAArFixAgAwbdq0\n", + "LpcjIiLXkdt5zCCzWq32nexykq1bt2LkrpGiYxAReRx9hB7zEuYhLS2tR+vzyXEiIi+j9FPatT6L\n", + "g4jIy4QEhNi1PouDiMjLhPRicRARUTfoAux7DILFQUTkZYIDgu1an8VBRORlgv1ZHERE1A0BPgF2\n", + "rc/iICLyMn4+fnatz+IgIvIyHn3EoVKorr4QERF1S5AiyK71JV0cEYERoiMQEXkcj35yPCqI7yMn\n", + "InI0jz7iSNQmio5ARORR1Ao1evn2smsbki6OOE2c6AhERB6ld1Bvzz5VFRYYJjoCEZFHiQqKglqh\n", + "tmsbki4Oe8/DERHR5RK0CZDJZHZtQ9rF4cfiICJypHhNvN3bkHRxaAO0oiMQEXmUSGWk3duQdHEE\n", + "+wfbfRGHiIj+y94p1QGJF4c2QMs7q4iIHMgRZ3IkXRyBfoHQh+tFxyAi8ggqhcruKdUBiRcHAKRG\n", + "pIqOQETkEeI18Z5/xAEA4YHhoiMQEXkEfYQegX6Bdm9H8sXhiAs5REQEpIY75gwOi4OIyEs4ajYO\n", + "yReHNkCL6KBo0TGIiNye1xRHaGAoboq+SXQMIiK3FugbiLBeXlIcAHBrn1tFRyAicmspYSnec8QB\n", + "gKeqiIjsNLL/SAT42veu8QvcojgilHyFLBGRPQaFDHLYttyiOEJ7hfKog4jIDo78C7h7FAcvkBMR\n", + "9ZgjL4wDblIcAC+QExH1lCMvjANuVBx9VH1ERyAickuj4kY57MI4APjas3JpaSkWLVqEuro6qNVq\n", + "pKenIyoqqtNls7KykJ+fD6Xyv+/XMBgMiImJsWmsaFU0/OR+aGlvsScyEZHXGRI6xKHbs6s45s+f\n", + "jylTpiAlJQW5ubnIzMzEG2+80emyMpkMEydOxKhRo3o0VlRQFIZHDsf3Zd/bE5mIyKvIZXLEqGz7\n", + "C7rN2+zpilVVVbBYLEhJSQEA6PV6VFdXw2w2X3Edq9Xa0+EQ4BuAcYnjerw+EZE3Sg5NRlRQ52eC\n", + "eqrHRxxGoxEqleqyz4KDg2E0GqHRaDpdJzs7G+vXr4dWq8WkSZOQnJzcrTEH6gb2NC4RkVeamDgR\n", + "QYogh26zy+LIyMhASUlJh89DQkLw0EMPQS7veMDS2tra6bamTp0KhUIBACgoKMCCBQuQlZWFwEDb\n", + "54aPCoqCwkeB5rZmm9chIvJmyWHd+wu6LbosjmeeeeaK36usrOxwWspsNkOn63wa9AulAQDJycnQ\n", + "aDSorKxE//79bQ4bFRSF6yOvx67SXTavQ0TkreQyOWKCHHt9A7DjGkd4eDiUSiXy8vIAADk5OVCr\n", + "1QgNDQUAmEymy4olNzcX7e3tAIDCwkJYLJYr3oF1JQG+ARifOL6nkYmIvMq1Ydc6/PoGYOddVQaD\n", + "AYsXL8ayZcugVqthMBgufm/FihUAgGnTpgEAtm/fjqVLl0KhUEClUsFgMFx2FGKrRF2iPZGJiLzG\n", + "xKSJUCqUV1+wm+wqjujoaPz5z3/u9HsXCuOCZ5991p6hLuqn7odg/2DUnKtxyPaIiDyVo14V+0tu\n", + "8+T4BdFB0ZiYOFF0DCIiSdMF6NBP3c8p23a74pDJZBgV27OHCImIvMX9SfcjWuWcWcXdrjgAIDY4\n", + "Fn5yP9ExiIgka2T/kU7btlsWRx91H9ze93bRMYiIJEnho0D/4P5O275bFoe/jz/uT7pfdAwiIkka\n", + "2W8k+gQ5b0ZxtywOgLflEhFdyf1J90Ph2/3HHWzltsXRV90XKWEpomMQEUmKDDIkaBOcOobbFkew\n", + "fzCevPZJ0TGIiCTltr63OfX6BuDGxQGcf7hFBpnoGEREkjEleQoC/WyfPLYn3Lo4+gf3xx197xAd\n", + "g4hIEvx9/JEUkuT0cdy6OAL9AvF48uOiYxARScL4hPGIVcc6fRy3Lg4AGBgyEP4+/qJjEBEJNylp\n", + "Enx97JqC0CZuXxz9g/tjUuIk0TGIiIQK9g/GAO0Al4zl9sXhK/fFhKQJomMQEQk1JXkK+qr7umQs\n", + "ty8OAEjQJCCkV4joGEREwtwVe5fLxvKI4ohRx2DW0FmiYxARCTEkdAgSta6bTcMjigMARvQZAbnM\n", + "Y/44RESFVaFVAAAXf0lEQVQ2mzVsFnS9dC4bz2N+0w7QDsDYAWNFxyAicimVQoWUcNdOv+QxxRHg\n", + "G8BnOojI60xLnYbYYOc/u3EpjykOABikG4T+6v6iYxARucxdsXdBJnPt1EseVRzhynA8d/1zomMQ\n", + "EbnEHX3vEPKKCY8qDgAYFjmMT5ITkVf4fervofRTunxcjyuOeE08fn/t70XHICJyquigaCSHJgsZ\n", + "2+OKw0fugwlJE3hrLhF5tJdvfhm9g3oLGdsjf7smaZPw8DUPi45BROQUIb1CcEPvG4SN75HF4e/r\n", + "j8cGPyY6BhGRU7x444sum5eqMx5ZHAAwUDcQo+NGi45BRORQKoUKv475tdAMHlscSoUS6fp00TGI\n", + "iBzqueHPIV4TLzSDxxYHAFwTcg1ujrpZdAwiIofw9/HHnbF3io7h2cUR7B+M527gA4FE5BmmXzcd\n", + "CZoE0TE8uzgAICUsBTdH86iDiNxbgE8AJiVNgo/cR3QUzy8ObYAWL9z4gugYRER2mf2r2UjSJYmO\n", + "AcALigM4/5KTMQPGiI5BRNQjaoUa98bf6/LJDK/EK4ojSBGEmUNnQgZp7HQiou547ZbXEKeJEx3j\n", + "IruLo7a2FjNmzMDp06e7XK60tBQvvfQSZs6ciTlz5qCsrMzeobvlmpBr8Lshv3PpmERE9opURuL2\n", + "vreLjnEZu4pj7dq1MBgMqK6uvuqy8+fPxwMPPICFCxdi/PjxyMzMtGfobgvwDcDjQx6Hn9zPpeMS\n", + "EdnjjV+/gT7qPqJjXMau4hg7diyWLFkCna7rd91WVVXBYrEgJeX86w31ej2qq6thNpvtGb7bBuoG\n", + "YtawWS4dk4iopwZoBuBX0b8SHaMDl1zjMBqNUKlUl30WHBwMo9HoiuEv8pX7YlLSJAT7B7t0XCKi\n", + "nnjr1rcQqYwUHaMD366+mZGRgZKSkg6fh4SEYO7cud0aSC7v2FGtra3d2oYjDNAOwFu3voWnNj3l\n", + "8rGJiGw1dsBYDI0cKjpGp7osjmeeecYhg2i12g6npcxm81VPcTnLbX1vw3Xh12Fv5V4h4xMRdUXh\n", + "o8Bzw5+D2l8tOkqnHHaqymq1Xva1yWS6WBbh4eFQKpXIy8sDAOTk5ECtViM0NNRRw3dLeGA43rz1\n", + "TSFjExFdzcs3vYxrQq8RHeOK7CqODRs2YPbs2TCbzcjIyEBWVtbF761YsQKfffbZxa8NBgPWrFmD\n", + "mTNn4uuvv4bBYLBnaLulhKXwFbNEJDl9VH0wJn6MpN9iKrP+8lBBIrZu3Qq9Xu/UMYpMRRi5aiTq\n", + "muucOg4Rka2+uO8LpPVLc/o4ubm5SEvr2TjSrTQXSNAm4K1b3xIdg4gIADA6fjSGRw4XHeOqvLo4\n", + "AOCOvndgWMQw0TGIyMsF+ATgj9f/UbIXxC/l9cURrgzH327/G58oJyKh/nbb3zA4dLDoGDbx+uIA\n", + "gOTQZPzl138RHYOIvNSImBEYFTtKMrPfXg2LA4CP3Of8wzYR0nzYhog8V4BPAP7y678gNFDM4wk9\n", + "weL4jwhlBObfPp+nrIjIpdzpFNUFLI5LJIcm4/Vfvy46BhF5iVv73OpWp6guYHFcwkfugzEDxvAu\n", + "KyJyugCfAPz5lj+71SmqC1gcvxChjMDfbv8bAnwCREchIg/29h1vu90pqgtYHJ1ICUvBOyPfER2D\n", + "iDzUA0kPuOUpqgtYHJ2QyWS4q/9deGzwY6KjEJGHiQ6KxvM3PA9tgFZ0lB5jcVyB2l+NWcNmITY4\n", + "VnQUIvIQPjIf/OPufyBW496/V1gcXegX3A/vj3qft+gSkUO8detb0Ec4d/JWV2BxXEVqeCrevv1t\n", + "0TGIyM2NjhuN+xLug6+8y/fnuQUWx1X4yH1wT9w9mJgwUXQUInJT4YHhePnmlxHSK0R0FIdgcdhA\n", + "20uLF258AXHBcaKjEJGb8ZX74sO7P8QA7QDRURyGxWGjWE0sPrjnAwT5BYmOQkRuJGtkFob3lv47\n", + "NrqDxdENKWEpWHbPMsjgnvdeE5FrzRo2C3fH3Q0fuY/oKA7F4uimX0f/mm8NJKKrSuuXhievfRJB\n", + "Cs87S8Hi6CaFrwITkybi8cGPi45CRBLVT90P826dhwhlhOgoTsHi6AFtgBbPXf8cJ0Mkog6Ufkp8\n", + "dM9Hbv+QX1dYHD0UrYpG5p2ZCOsVJjoKEUmEDDIsu3sZUsJTREdxKhaHHRJ1iVg5diVUCpXoKEQk\n", + "AZl3ZmJEzAjRMZyOxWGn6yKuw2ejP4PCRyE6ChEJ9MrNr2Bs/FgofD3/dwGLwwFujL4RH979IW/T\n", + "JfJS01Kn4bHBj0GpUIqO4hIsDgeQy+RI65eGrDuzREchIhe7P+l+zBw2E5oAjegoLsPicBA/Hz+M\n", + "iR+D1255TXQUInKRETEj8PJNLyMs0LtukmFxOJBSocQjgx/B09c9LToKETnZ4JDByEjLQLQqWnQU\n", + "l2NxOJjGX4MZ+hl8QJDIgw3QDMCye5ahf3B/0VGEYHE4QZgyDH/61Z/w8DUPi45CRA4WGxyLT0d/\n", + "6lGz3XYXi8NJIpQReOnGlzB54GTRUYjIQfqq+2LFmBVI0CWIjiIUi8OJIpQRmHvTXJYHkQfop+6H\n", + "VWNXIVGXKDqKcCwOJ4sMisQrN7/C01ZEbiwuOA6rxq5Cki5JdBRJYHG4wIXTVrxgTuR+EjQJWDF2\n", + "BY80LmH3W9Nra2vx4osv4vnnn0dMTMwVl8vKykJ+fj6Uyv8+WWkwGLpcx5NEKCPwwq9egEqhQube\n", + "TNFxiMgGqeGpeH/U+159IbwzdhXH2rVrsW7dOtTX1191WZlMhokTJ2LUqFH2DOnWwpRhePb6Z8+/\n", + "uH73y6LjEFEX7ux3J/52+9/QV91XdBTJsetU1dixY7FkyRLodDqblrdarfYM5xE0/hpMGTIF7935\n", + "Hue2IpKoh695GBlpGSyNK7D7VFV3ZGdnY/369dBqtZg0aRKSk5NdObxkKBVKTEycCI2/Br9d/1s0\n", + "tzWLjkRE/zFr2Cykp6YjNDBUdBTJ6rI4MjIyUFJS0uHzkJAQzJ07t1sDTZ06FQrF+emGCwoKsGDB\n", + "AmRlZSEwMLBb2/EUfj5+uCv2LmSPy8aDax9EfcvVT/cRkXO9OeJNPDjwQQQHBIuOImldFsczzzzj\n", + "sIEulAYAJCcnQ6PRoLKyEv3793fYGO5GLpPjpuibsHbCWkxeOxlVlirRkYi8klwmx6K7FuHeuHsR\n", + "6Oedf5ntDofdjvvL6xcmkwlms/ni17m5uWhvbwcAFBYWwmKxICoqylHDu7XUiFSsm7gOQyOGio5C\n", + "5HXUCjW+HPclxg0Yx9KwkV3XODZs2IAdO3bAbDYjIyMDcXFxmD59OgBgxYoVAIBp06YBALZv346l\n", + "S5dCoVBApVLBYDBcdhTi7RJ1iVh2zzIs+GkBPjrwkeg4RF4hSZeEpb9ZisGhg0VHcSsyq0Rvddq6\n", + "dSv0er3oGC5najJhzeE1eH7H87BCkv9piDzCuAHjMPfmuegX3E90FCFyc3ORlpbWo3X55LjEaAO0\n", + "eGzwY/j8vs8R5BckOg6RR5pz4xzMu22e15aGvVgcEqTwVSCtXxr+OemfiA2OFR2HyGP4+/jjk3s/\n", + "we9Tf8/bbe3A4pCwlLAUrL5vNSYkTBAdhcjtxQbH4ptJ3+CeuHt4EdxOLA6Ji9XEYt7t8/Bu2rvw\n", + "k/uJjkPklh4f/Diyx2VDH6GHTMYZG+zl0ifHqWd0ATo8OOhBDAwZiCc2PoHjNcdFRyJyCwE+AXh3\n", + "5Lu4s/+dUPurRcfxGDzicBM+ch8MjRyK7HHZeHTwo6LjEEneQN1ArL9/PSYkTmBpOBiLw830D+6P\n", + "1255De+Peh/+Pv6i4xBJ0lPXPoVVY1fh2vBreWrKCXiqyg0F+wdjYuJEJGgT8Ny257CnYo/oSESS\n", + "oPHX4L0738MtMbcgSMHb2Z2FRxxuSiaT4drwa/HJvZ/grVvf4oVz8noPDnwQGx/YiN/E/Yal4WQs\n", + "DjcXGRSJ3w35HTbevxH6CO970p5I46/B8tHL8ddb/4oEbYLoOF6BxeEBfOQ+SI1IxfJ7l/Pog7zK\n", + "5IGTsfH+jbgn7h5eAHchFocH4dEHeQuNvwafjv4Ub936FhJ0PMpwNRaHh7lw9PHp6E+RdWcWVAqV\n", + "6EhEDiODDDP0M7DpgU24O+5uHmUIwruqPFSEMgIPDXoIQyOGYtn+Zfh7/t9FRyKyy/DI4Xh9xOtI\n", + "Dk1GgG+A6DhejUccHi5Rl4iXb34Z6yetR2pYqug4RN2mDdDiH7/5Bz4d/SmGRQ5jaUgAi8ML9PLt\n", + "hRuibsDKsSux+K7FUCt4eE/SJ5fJMXPoTGx6YBPGJ47nbLYSwlNVXiRcGY4HBj6A68KvwxeHv0DG\n", + "ngy0treKjkXUwV3978L/u/7/ITk0Gf6+nCFBanjE4YUSdAn44w1/xLeTv8XvhvwOMnBKBpKG4ZHD\n", + "sXbCWrw/6n0MjRzK0pAoFoeX8pX7IjksGX/59V+w+YHNGBM/RnQk8mIDNAPw+djP8dmYz3BLzC28\n", + "W0riWBxeLsA3APpIPTLvzMQ3E7/BjVE3io5EXiQiMAL/+M0/sHbiWozsPxIhvUJERyIb8BoHAQBU\n", + "ChVujL4Ry0cvR35lPub/NB/fl30vOhZ5qN7K3phz0xzcFH0T+qr7io5D3cTioMtoA7S4re9tSA1P\n", + "xcGzB5G1Nwv/KvmX6FjkIQZoBmDOTXOgj9AjWhUtOg71EIuDOqUJ0ODG6BuREpaCg8aD+LjgY3x2\n", + "8DO0W9tFRyM3dF34dZj9q9lICUtBuDJcdByyE4uDuqRUKDEschiGhA7B1JSpWH14NZbsW4JzbedE\n", + "RyM3kNY3DX8Y9gcMDhkMXS+d6DjkICwOsom/rz9SwlNwTeg1eGTwI8g5k4N5P83DydqToqORxAT5\n", + "BeGp1Kfwm7jfIFGbyHdjeCAWB3WLr9wXibpEJOoSkdYvDYeMh7Bs3zKsO7oOVlhFxyOBrgm5Bobh\n", + "Blwbfi1ig2P5ylYPxuKgHotQRiBCGYHre18Pg8mAHSd3IGNPBoxNRtHRyEX85H54aNBDmDxwMpJ0\n", + "STwd5SVYHGS3Xr69MCRsCIaEDcF9CfehyFSE1YdXI7soG81tzaLjkRPc0PsGPHHtExgcOhjxwfHw\n", + "9eGvEm/C/9rkUH3UfdBH3QcjYkZg1vBZOHz2MD7Y/wF2nNrBU1luLj44HtP00zA0YijiNHG8duHF\n", + "WBzkFL4+vkjQJiBBm4A7+t2BYzXHkF+Zj8V5i1FQXSA6HtkorFcYpl47Fbf2uRXxwfEICeST3cTi\n", + "IBcI9AvE4NDBGBw6GPfE3YOTdSdxxHgEqw6uwo7TOzhDr8QM1A3Eb5N/i2vDr0VfdV/0DuotOhJJ\n", + "DIuDXEoToIEmQIOUsBSMjh+NU7WncKzmGNaXrMeXRV+itrlWdESv4yv3xa0xt2LyoMlI1CWir6ov\n", + "NAEa0bFIwlgcJEyAbwASdAlI0CVgZP+ReHb4szhZexKHjIfw5ZEv8fOZn9HS3iI6pkdK0CZgXMI4\n", + "DIschv7q/uij7sM365HNWBwOcPjwYSQlJYmOYROpZpXL5BcvrN8cczP+Z9D/oLyhHMVVxTjeeBxf\n", + "FX2FnDM5PK3VQ/GaeIxPGI9hkcMQo4qBT6MPkvpK7+egM1L9me2MO2W1h13FsXDhQhQXF8PX1xdq\n", + "tRpPPPEEYmJiOl22tLQUixYtQl1dHdRqNdLT0xEVFWXP8JJx5MgRt/lhcZesvfx6IU4ThwPfHcCT\n", + "Y57EI9c8gvKGcpTWleJk7UlsO7kN/y77N8obykVHlRyVQgV9hB4j+41EgjYBMaoYRCojL3vGYt26\n", + "dW5THO7yMwu4V1Z72FUcN998M2bMmAG5XI5t27bh/fffx2uvvdbpsvPnz8eUKVOQkpKC3NxcZGZm\n", + "4o033rBnePIigX6BiNfEI14TDwB4+JqHUdVYhbOWszjTeAaldaXYfmo7/l32b5TVlwlO6zqXlkS8\n", + "Nh6RykiE9gpFeGA4FD4K0fHIQ9lVHMOGDbv477GxscjOzu50uaqqKlgsFqSkpAAA9Ho9Fi9eDLPZ\n", + "DI2GF+Go+2QyGcKV4QhXhmMQBgE4XybVlmoYm4wwWUwwnjOioqECuWdykVeVhxJzCSytFsHJu89H\n", + "5oO+6r4YHDIYw3sPR191X+h66aD110IboEVYYBhLglzKYdc4tmzZAr1e3+n3jEYjVCrVZZ8FBwfD\n", + "aDSyOMhhZDIZwgLDEBYYdtnnU4ZMQUNzA0znTDA3mWE+Z0Zdcx0aWhpwpv4MSmpKUGQqQnl9Oc40\n", + "nEFja6PLMvvKfRERGIFIZSRiNbFI0Jw/taRSqBCkCEKwfzC0AVqoFWpoA7Sc/4kkocviyMjIQElJ\n", + "SYfPQ0JCMHfu3Itfb9q0CUVFRXj11VevuC25vONbaltbeaGTXEOpUEKpUCJG1fk1uLb2NtQ216Kx\n", + "pRGWVgssrRa0tLWgub0ZzW3/+eeSfz/Xdg6t7a1oa29DO9rR3t6OpqYmKAOV8JX7Qi6Tw9/HHwof\n", + "xfl/5Ir//vslXwf6BSLQNxAqfxX8ffxdvFeIekZmtVrtmgdi7dq1+PHHHzF79mwEBXU+BUFlZSXm\n", + "zJmDv//97xc/e+KJJ/Dmm28iNDS003V27tyJlhbeiklE5Ax+fn4YMWJEj9bt8amq9vZ2fPDBBxdL\n", + "ISDg8nvATSYTZDIZNBoNwsPDoVQqkZeXh9TUVOTk5ECtVl+xNAD0+A9ERETO1eMjjsrKSsyYMQOR\n", + "kZGXnYZ6+umnER8fj/feew8AMG3aNADnb8ddvHgxamtrPe52XCIib2L3qSoiIvIuHa9YExERdYHF\n", + "QURE3SKpuapqa2vx4osv4vnnn7/i1CWA+OlLujN+VlYW8vPzoVQqL35mMBi6/PO5Kp/o/didDCL2\n", + "Y2ds+RmVwn4FbMsqlf1q6/RFUti3tmaVwr5dtGgRDh06BJlMBj8/Pzz++ONITk7usFy396tVIr7+\n", + "+mvr1KlTrQ8++KD11KlTXS77zDPPWPPz861Wq9W6Z88e6+zZs10RsUfjZ2VlWTds2OCqaFar1fZ8\n", + "ovdjdzKI2I+/ZOvPqBT2q61ZpbBfrVar9eeff7a2tbVZrVar9dtvv7XOmTOn0+WksG9tzSqFfbtv\n", + "376LWffs2WOdNWtWp8t1d79K5lTV2LFjsWTJEuh0Xb/svrPpS6qrq2E2m10Rs0fjW114/4Gt+UTv\n", + "x55kcOV+7IwtP6NS2K+A7f8/AeL3K3B++qILd2fGxsbCZDJ1WEYq+9aWrBeI3rdDhgyBXC6H1WpF\n", + "WVkZYmNjOyzTk/0qqVNVthA9fUlPxs/Ozsb69euh1WoxadKkTg8VXZ1P9H7sSQZX7seeksJ+7S6p\n", + "7dcrTV8kxX3b1VRLgDT27aFDh7BgwQJoNBrMnj27w/d7sl9dVhy2Tl9iC2dPX9JV1oceeqhb40+d\n", + "OhUKxfkJ6AoKCrBgwQJkZWUhMDDQYXl/ydZ8UpgGxtYMIvZjT0lhv9pKavv1atMXSWnfXi2rVPbt\n", + "wIEDsWTJEuTl5eHVV1/FwoULOyzT3f3qsuJ45plnHLIdrVbb4RDKbDbbdEhuq66yVlZWdmv8Cz84\n", + "AJCcnAyNRoPKykr079/fIVl/ydb944r9eDXdyeDq/dhTUtiv3SGl/Xph+qLOZqIApLVvr5YVkNa+\n", + "BYDU1FScPXsW9fX1l00P1ZP9KplrHJf65XlBk8l08Q926fQlAGyavsSRrjb+pVkBIDc3F+3t7QCA\n", + "wsJCWCwWp94F0lU+Ke3H7mQFXL8fr+bSn1Gp7ddfulJWQBr7tb29HUuXLkVBQQHmzJlz2S81qe1b\n", + "W7MC4vdtfX09cnJyLv73//777xEaGoqgoCC796tknhzfsGEDduzYgZMnTyIyMhJxcXGYPn06AEhu\n", + "+pKuxv9l1rfffhvFxcVQKBRQqVR47LHHkJCQICSf1PZjd7KK2I+/dKWfUSnuV1uzSmG/Xmn6ounT\n", + "p2PTpk2X5RW9b7uTVfS+ra+vR0ZGBsrKyuDv7w+dTocpU6YgJibG7p9ZyRQHERG5B0meqiIiIuli\n", + "cRARUbewOIiIqFtYHERE1C0sDiIi6hYWBxERdQuLg4iIuoXFQURE3fL/AR8SQcFGS9m7AAAAAElF\n", + "TkSuQmCC\n" + ] + }, "metadata": {}, - "outputs": [] + "output_type": "display_data" } ], - "metadata": {} + "source": [ + "display_png(c2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## return the object" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# for demonstration purpose, I do the same with a circle that has no _repr_javascript method\n", + "class MyNoJSCircle(MyCircle):\n", + " \n", + " def _repr_javascript_(self):\n", + " return\n", + "\n", + "cNoJS = MyNoJSCircle()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Of course you can now still return the object, and this will use compute all the representations, store them in the notebook and show you the appropriate one." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "cNoJS" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Or just use `display(object)` if you are in a middle of a loop" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "for i in range(3):\n", + " display(cNoJS)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Advantage of using `display()` versus `display_*()` is that all representation will be stored in the notebook document and notebook file, they are then availlable for other frontends or post-processing tool like `nbconvert`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's compare `display()` vs `display_html()` for our circle in the Notebook Web-app and we'll see later the difference in nbconvert." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print \"I should see a nice html circle in web-app, but\"\n", + "print \"nothing if the format I'm viewing the notebook in\"\n", + "print \"does not support html\"\n", + "display_html(cNoJS)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print \"Whatever the format I will see a representation\"\n", + "print \"of my circle\"\n", + "display(cNoJS)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print \"Same if I return the object\"\n", + "cNoJS" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "print \"But not if I print it\"\n", + "print cNoJS" + ] } - ] + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Plotting in the Notebook.ipynb b/examples/IPython Kernel/Plotting in the Notebook.ipynb index 941f4b3..00e713b 100644 --- a/examples/IPython Kernel/Plotting in the Notebook.ipynb +++ b/examples/IPython Kernel/Plotting in the Notebook.ipynb @@ -1,226 +1,740 @@ { - "metadata": { - "name": "", - "signature": "sha256:74dbf5caa25c937be70dfe2ab509783a01f4a2044850d7044e729300a8c3644d" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Plotting with Matplotlib" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "IPython works with the [Matplotlib](http://matplotlib.org/) plotting library, which integrates Matplotlib with IPython's display system and event loop handling." - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "matplotlib mode" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To make plots using Matplotlib, you must first enable IPython's matplotlib mode.\n", - "\n", - "To do this, run the `%matplotlib` magic command to enable plotting in the current Notebook.\n", - "\n", - "This magic takes an optional argument that specifies which Matplotlib backend should be used. Most of the time, in the Notebook, you will want to use the `inline` backend, which will embed plots inside the Notebook:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%matplotlib inline" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 1 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can also use Matplotlib GUI backends in the Notebook, such as the Qt backend (`%matplotlib qt`). This will use Matplotlib's interactive Qt UI in a floating window to the side of your browser. Of course, this only works if your browser is running on the same system as the Notebook Server. You can always call the `display` function to paste figures into the Notebook document." - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Making a simple plot" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "With matplotlib enabled, plotting should just work." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 2 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "x = np.linspace(0, 3*np.pi, 500)\n", - "plt.plot(x, np.sin(x**2))\n", - "plt.title('A simple chirp');" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "display_data", - "png": "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\n", - "text": [ - "" - ] - } - ], - "prompt_number": 3 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "These images can be resized by dragging the handle in the lower right corner. Double clicking will return them to their original size." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "One thing to be aware of is that by default, the `Figure` object is cleared at the end of each cell, so you will need to issue all plotting commands for a single figure in a single cell." - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Loading Matplotlib demos with %load" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "IPython's `%load` magic can be used to load any Matplotlib demo by its URL:" - ] - }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Plotting with Matplotlib" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "IPython works with the [Matplotlib](http://matplotlib.org/) plotting library, which integrates Matplotlib with IPython's display system and event loop handling." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## matplotlib mode" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To make plots using Matplotlib, you must first enable IPython's matplotlib mode.\n", + "\n", + "To do this, run the `%matplotlib` magic command to enable plotting in the current Notebook.\n", + "\n", + "This magic takes an optional argument that specifies which Matplotlib backend should be used. Most of the time, in the Notebook, you will want to use the `inline` backend, which will embed plots inside the Notebook:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also use Matplotlib GUI backends in the Notebook, such as the Qt backend (`%matplotlib qt`). This will use Matplotlib's interactive Qt UI in a floating window to the side of your browser. Of course, this only works if your browser is running on the same system as the Notebook Server. You can always call the `display` function to paste figures into the Notebook document." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Making a simple plot" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With matplotlib enabled, plotting should just work." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "%load http://matplotlib.org/mpl_examples/showcase/integral_demo.py" - ], - "language": "python", + "data": { + "image/png": [ + "iVBORw0KGgoAAAANSUhEUgAAAX0AAAEKCAYAAAD+XoUoAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n", + "AAALEgAACxIB0t1+/AAAIABJREFUeJztfXt0VdWd/+fmwSvhkQTyDiAEFkRUUKwtLRqrSEFNtb6w\n", + "Vqlay2pL22lndVZX5zejrpkqTKdLnWFq0dVWmLFIO6NCfeCzqbSW0iKiFSqPiuYBIZAH5EEeN+f3\n", + "x+7OPTk5j/0859xkf9bKgiRnP+7NPZ/9OZ/vd393wrIsCwYGBgYGowIZUU/AwMDAwCA8GNI3MDAw\n", + "GEUwpG9gYGAwimBI38DAwGAUwZC+gYGBwSiCIX0DAwODUQRD+gZpjyeffBLLly/X0vcXv/hF/NM/\n", + "/ZPSPu+77z7cfvvtnr9fsGAB3njjDaVjGhhQGNI3iBzV1dXIz89Hb2+vUPvbbrsNL730kuJZESQS\n", + "CSQSCeV9+uHPf/4zLr30UqVjGhhQGNI3iBRHjx7F7t27UVhYiO3bt0c9HVeo3r8o018ymVQ4E4PR\n", + "CEP6BpFi8+bNuPLKK3H77bdj06ZNvtc+8cQTmD17NiZNmoRZs2bh5z//+eDPly5dOnhdRkYGHn30\n", + "UcyZMweTJk3CP//zP+PIkSP4xCc+gSlTpmDVqlXo6+sDANTW1qK8vBwPPvggpk2bhnPOOWewXzc8\n", + "99xzWLhwIfLy8vDJT34S7777rue17733HpYtW4aCggIUFxfjwQcfBECUfm9vL1avXo1JkyZhwYIF\n", + "2LNnz2C7mTNn4vXXXwdArKAbb7wRt99+OyZPnownnnhi8GerVq3CpEmTcNFFF+Gdd94JeKcNDAgM\n", + "6RtEis2bN+OWW27BzTffjJdeegknTpxwva6zsxPf/OY3sWPHDpw+fRq///3vsXDhQs9+X375Zezd\n", + "uxe7du3C+vXrcc8992DLli346KOP8O6772LLli2D1zY1NeHUqVNobGzEpk2b8OUvfxmHDh0a1ufe\n", + "vXtx99134/HHH0dLSwvWrFmDmpoaV1vqzJkzuPLKK7Fy5UocO3YMhw8fxhVXXAGAKP3t27fj1ltv\n", + "RXt7O2pqarB27drBtk77Z/v27bjpppvQ3t6O2267bfBnN998M1pbW/H5z38e1113Hfr7+33eaQMD\n", + "AkP6BpHht7/9LRoaGlBTU4M5c+agqqrKV2VnZGTg3XffRXd3N4qKilBVVeV57T/8wz8gNzcXVVVV\n", + "OO+887BixQrMnDkTkyZNwooVK7B3794h1//Lv/wLsrOzcemll+Lqq6/G1q1bB39HSfixxx7DmjVr\n", + "cPHFFyORSOCOO+7A2LFjsWvXrmHjP/fccygtLcW3vvUtjBkzBrm5ufjYxz42+PulS5fiM5/5DBKJ\n", + "BL7whS9g3759nq9lyZIlqKmpAQCMGzcOALB48WJ87nOfQ2ZmJr797W/j7NmzrvMwMHDCkL5BZNi0\n", + "aROuuuoqTJw4EQBw0003eVo8OTk52Lp1K3784x+jtLQU11xzDd5//33PvouKigb/P378+CHfjxs3\n", + "Dh0dHYPf5+XlYfz48YPfz5gxA8eOHRvW54cffogf/vCHyMvLG/yqr693vbaurg6zZs1imt+ECRNw\n", + "9uxZDAwMuF5bXl7u+7NEIoHy8nLXeRgYOGFI3yASdHd34xe/+AVef/11lJSUoKSkBD/84Q+xb98+\n", + "T3/6qquuwssvv4zjx49j3rx5uOeee4TGdtonra2t6OrqGvz+ww8/RGlp6bB206dPxz/+4z+itbV1\n", + "8KujowO33HKL67V//etfmcbnnS9AFhWKgYEB1NfXu87ZwMAJQ/oGkeDZZ59FVlYWDhw4gH379mHf\n", + "vn04cOAAli5dis2bNw+7/sSJE9i2bRs6OzuRnZ2NnJwcZGZmMo9nz5hxy56599570dfXh507d+L5\n", + "55/HTTfdNHgtvf6ee+7Bj3/8Y+zevRuWZaGzsxPPP//8kKcGimuuuQbHjh3DI488gp6eHpw5cwa7\n", + "d+/2HJ8Xe/bswTPPPIP+/n48/PDDGDduHD7+8Y9L92sw8mFI3yASbN68GXfddRfKy8tRWFiIwsJC\n", + "FBUVYe3atfj5z38+zOoYGBjAQw89hLKyMhQUFGDnzp149NFHAQzPpXdTxs7f278vLi5GXl4eSktL\n", + "cfvtt2Pjxo2YO3fusGsvuugiPP7441i7di3y8/MxZ84c1wUKAHJzc/HKK6/gV7/6FUpKSjB37lzU\n", + "1ta6ju81Z79rP/vZz2Lr1q3Iz8/Hk08+iaeffpprETQYvUjIHqJy11134fnnn0dhYaFn+to3vvEN\n", + "vPjii5gwYQKeeOIJLFq0SGZIAwNlqK2txe233z7ELok77r//fhw+fBj//d//HfVUDNIQ0kr/zjvv\n", + "xI4dOzx//8ILL+Dw4cM4dOgQHnvsMXzlK1+RHdLAYFTDHHZnIANp0l+6dCny8vI8f799+3asXr0a\n", + "AHDJJZegra0NTU1NssMaGCiD6jILuqGjNITB6EGW7gEaGhpQUVEx+H15eTnq6+uHpKwZGESF6upq\n", + "fPTRR1FPgwv33ntv1FMwSGOEEsh1Po4alWJgYGAQDbQr/bKysiFBsvr6epSVlQ27rrKyEkeOHNE9\n", + "HQMDA4MRhdmzZ+Pw4cPM12tX+jU1NYNpbbt27cKUKVNcrZ0jR44M5kTH6esHP7CwdKmF3t7Uz779\n", + "bQu33aZvzHvvvVfra2ppsZCba6Gjw0JJiYXDh8N/X6++2sL69RZmzND3Xhw6ZKG01ML06eT/qub+\n", + "9NMWxo61cPnl8n0lkxYmTbKQk2Ohs1P8vfi3f7MAWPjRj8TmsWcPab96NX/b1lbS9stf5m8LWJg6\n", + "la/N1q0WgHvxl7+wt7nhBjIW6/UbN5Lrd+xgu/5737NQWanuM8bzxSuWpUn/1ltvxZIlS/D++++j\n", + "oqICP/3pT7Fx40Zs3LgRALBy5UrMmjULlZWVWLNmDX70ox/JDhkampuBdeuAn/4UyM5O/fz++4HX\n", + "XgN8CizGGq+9BnzqU0BODnDZZcDOneHPYd8+4IYbgNZW4NQpPWPs3Qtccgkwfz7wl7+o6/fwYeDa\n", + "a4H9+9X0lZ8PFBUBjY3i/TQ0AMXFpD/R9oDY36K+nvz79tt87Wh9uNxcvnYHDpB/ed6vMWPIv6dP\n", + "s11Pr2O9vrkZqKsDPCppxArS9o69WqEXNmzYIDtMJPj+94FVq4DKyqE/z80FvvY14OGHgZ/8JJq5\n", + "yeDNNwF6RsenPgX89rfAF78Y3vgtLUB7O3DOOcDChYScr7xS/Th1dcD06eT/f/kLcM01avo9coQs\n", + "li+9RF5Lfr54X42NZI69vUBT0/DPGitOngQ+8QnApTgoExoagKoqMdKvqwMqKoC2Nr527e1AVhaZ\n", + "Ow8OHgQyMlILFQuam8m/778PXHxx8PVnzgz9l6X/nh7yb9xzVMyOXA+cOgU88QTw//6f++/vvht4\n", + "+mng7Fn1Y1dXV6vv1IZ33iFkCwAXXcSv0GSxfz8hmIwMYO5cQqJekHkvKBnNnUuIQhWOHCHkXF4u\n", + "p84BQnhTpwKFhYBHVelB+L0Xzc3AhReS1yyChgbg/PP5CRggYy5YQEicB21t5O/T08N3H7W2AuXl\n", + "1VzvfVMTeY9bW9muF1H6iQSQDolghvQ98MQT5BG+uNj99yUlwAUXAC+/rH5snaRvWcRaoaRPrY8w\n", + "H0s/+giYMYP8f/p0/xtFlvSnTwdKS4Hjx4W7GYbGRqCsjJC1CEnaQUm/qEiO9E+eBObM4SdeisZG\n", + "8nkWUfrHjpHPEa/Sb2sD8vL438fOTmDRIn7SP+cc0pYFZ86QRYJH6U+fLv95CAOG9F0wMAA8+iix\n", + "cPxw883AL34RzpxUge6Lo4vZ5MnApEkpXzYM2G2XigpxdRqEjz4i/RcVpV63Cpw6BRQUANOmqSN9\n", + "FqXvh+Zm8vQhSvrNzalFg/csljNnyHs8MEBUOyva2oApU/jfx44O8pTF+lqTSWLDzZjBTvqnT5Mx\n", + "eJT+9OlAdzfb9VHCkL4L3niDBDkvucT/us99DnjuOeLHpgsOHSJ2h32rxLx5qeBYGKC2CxCs9GVQ\n", + "X09uXFlCtcOyCIEUFKhV+jJztCxCOrNnEyK0BKo0dHQQAp4yhd0CsbedOJEICJ5Fp72djDd1aspz\n", + "Zx2vsBCwVcP2RVcXMHYsmR+P0i8rY1P6lkUWsLIyQ/ppiy1bgNtuG0qMbiguJjfaH/8YzrxU4PBh\n", + "Mmc7KisBj9LvWhAG6VsWIdTCQjbrhBVnzpBMkLFj+cnKDXbSF30aoeQ3eTKZFyux2XHmDElQmDqV\n", + "3+Lp6CBtJ0/ms3ja2kibvDy+dh0d5OmAlfS7u4EJE4iQ41H6ZWVsSr+nh3wmcnIM6aclenuB//s/\n", + "krXDgupq4G8Vc9MChw8PzxCZMQP48MPw5kBtF4DcWA0NYurUD6dPA+PGpW5GyyJkIQtq7QBq7Z2C\n", + "AvHUVTqnRIIoZ15vHSCkP3EiyUQSIf2JE8nYPEqf2jsTJvCRZWcnWSRZCby7Gxg/no/0eZT+2bPk\n", + "szZ+vCH9tMQrrxC7g3rOQUg30qeZJ3aETfrHj5NAOEBulDFj2L1TVtjJOZFQp/aptQOos3cKCghp\n", + "sgYNnejoIHEZgN9isfcxcSIfMVLQpwTesSnpjx/Prtrp4s1j74iQPo/SN6Sf5ti2jWwaYsXSpcCu\n", + "XXwBrCjxwQfAzJlDfzZzZnikPzBAiG7atNTPpk2Tt0mcsJM+IGefOPulefkq5n36NCE+WdKnG5x4\n", + "1TYFVfoipC9r70yYwE7gPT0kt3/yZD5PX6fS7+42pJ+2GBgggdlrr2VvM2UKCYymi69vz5yhCFPp\n", + "t7WRm4/ukATCIf38fP4AZVC/kyfLP6FQslVF+rzEC6TUc26uPOnzLDhdXWQ8HtLv6OBvw+vpWxb5\n", + "u5aWGqU/4rF3L3lM5t0V+fGPpwfp9/YSlU2tFYrSUmJ98KbqieDECaK67QiD9EW9bifsO3AnTVJD\n", + "+rm5pK+olH53N1mEs7IIMbKSqXP83Fy+BYPaLrykn5vLT/o84/T1kX/z89lejyH9NMZzz4lt1b/o\n", + "ImDPHvXzUY3GRpJx5DxKNTOT+NNhnG3T3DzU2gHUplRSUK+cQhXpnz5NFC1A1LkM6Q8MEJLIySFE\n", + "1tEhnm4po/Tp0wZAiFFU6fOSHrVFeAK5nZ0p0medJ6+909NDsqDGjmWzbc+eJf0b0k9D/OpXfNYO\n", + "RbqQvj1V0omSErKzUjeiVPqiG5fsOH06RZAy6hwgBDRhAilHkZUln24JkH95lbq9vYy9I0L6YSl9\n", + "HntHhPSN0k9DNDaSXPUlS/jbVlWRNEQZAggDdXVks5IbSkvDIX03pT9tmnql39pK8r8pVCl9uyqm\n", + "6ly0hIW9L0B8EbErfR4ytLen8+Al/d5esuN17Fh+0rMrZF5Pn8aEqBXjB97snbNnDemPCrz8MrBs\n", + "2dASyqzIziYFp8IuXMYLe6qkEyUl8sXDWNDcTKwkO/LziVeuEu3tKRsGELM93HDmTCo9MjOT3Ogi\n", + "6pz2ZS8rLBrMdZK+SMqlKOl3dpK2iUQ4Sr+zk8wRYG/HS/o9PYTEs7LIgp5MBvdvSD8N8eqrcuV9\n", + "08HiaWryLyAXhtJvaxteiph3RyYL7N47oEfpA3IWj7Mv0RiBrNJ32js87anyBsIhfaqqAfZ2op5+\n", + "IsGm9o3ST0NYFjlYRIb0Fy4kJYvjjOPHvWt9h0n6U6YM/Vlenpp0Sjva21OKHFAbyFVB1MBQW4X2\n", + "FYW9Q1MnaXsepd/VRdoA4ZA+JWSeuVJPn9WusY9hSH+EYv9+8qE45xzxPqqq1JykpBNNTd6kr6Kk\n", + "AAvCIn2dSt++mMikbcbF07erZ157x95WhvRZ2zlJn8feESX9oFr/JnsnDfHqq8AVV8j1QUlfdQ0Z\n", + "lTh+3NveUVFSgAXprvR12ju5udGQPiVFIFzSp21l7B1WpU/LfbBUxLWT/rhxRumPSMj6+QDxqXNy\n", + "+I5wCxt+Sn+kkb5T6avYSEX7VWXvOAO5vIXHKOy+ejqRPh2XJ3uHl5CBlAXFm40DGHtnRKKvjxwM\n", + "/ulPy/cVZ4snmUyVGnZDWKTf2jqc9HNzyY2l8lwCZ/YOTa+UhdPekfX0naTPS9jAcE8+HUjfsuQ9\n", + "fVblblf6fX3BKba8nj7N3hk3Ts/xqaox6kl/zx5ScMyZRiiCqirgvffk+9GBU6cICXqlpBYUkLRJ\n", + "3ccmuil9mZLAbjh7lpAKvXGBFJnJvD57jRoK3tIDdtjJGhAnfTtpi6RsimTE2NvSsXlIv6+PpLxm\n", + "ZorbO7zKPZEgn/+g3H7RQC5dVOKOUU/6b7wBXHqpmr7irPT9rB2A3Aw5OWp2rXphYCB1WpITKi0e\n", + "au3YD8GhOfUipErR2UlIICsr9TNRogZSWSX2vkTsASfpyyh9XrUqqvTtY7KSN8BPyAB5GuBpI0L6\n", + "48eTeyjoyWNgINzzqN0w6kl/505SHlkF4k76XkFcCt0WT0cHISU7aVKoJH1nEJdCppIlMFyZA2Jl\n", + "C+z9UeID5JS+PftGlvR5yoSrIH1q07AkQYjYO/RkK0Af6bMq/Z07yRkcUWJUk/7AAPC736kj/blz\n", + "yRm0cYRfjj6FbtJ3s3YoZAnZDqfvTiHr69tz0ilklL6zP9EnEVmlbyfusJS+vV1GBptKBlK7ZQE+\n", + "pU9Jn2Wh4LWQ6KKSlUUq1fotXn19Yrv+VWJUk/577xEvO0gBs6K4mNxwOi0SUcRB6fuRvmzxMjuc\n", + "vjuF7MLitGMAeaWvwt6x++rpaO8AhDRZ/fm42TuUyBMJQvx+ar+/35B+pNi5U52fD5A/emUlOZIw\n", + "bhhNSt9en8UOXUpflPTdPH1ZpU/tGR7f2N4+K4soVdazFdxIn8WmcZL+2LH8OfSsbexKX4T0gxZB\n", + "u3oPepLo63O3N8PEqCd9VdYORWUlOXw8bnAraexE1KSv6pxcL9JX4enbiQoQ89C9+hMh/WSSEDQl\n", + "NVr4jDcDhxI3wKf27W0zM9ltGueYIrtlWZ8OnJ5+0Px49wLY1XuQr2+UfoSwLJK5M1pI33kurRt0\n", + "k75bjj5Fuip91faOqDVjz1TiyYax90HBQ/r2IDLP2HbLhaedSMqm09PXYe9Q9R606BmlHyE++IAQ\n", + "/6xZavuNK+k7DxVxQxhK317j3g6VpK/L01cdyHXaOyI7Op2EDfBn4LiRPmt7GcVuPydZVzE0gN/e\n", + "4V1YeOwdo/QjBLV27ApJBQzpeyOsQK4upa8jkCtr77iRPosP7deHqL0DsKdR2okYELd3RDx9HnuH\n", + "ZV52Ig/a/GWUfoTQ4ecDhvT9EHUgN25KX6W9YwevvaPK0+cZ206sADuBi9o7PHEAGXvHKP0YQxfp\n", + "l5YSclNR50UV+vtJkNSLcCmiJn3dgVwZVQ54B3JVZu+I2Dt20gXkPX2eJwV7uijP2CqUvoiVpDNl\n", + "EzDZO7FFUxPJZlmwQH3fGRnAjBnAhx+q71sUra2kLEFmpv91Ok6wsiMspe/l6cuociCegVwn6QJq\n", + "PP2wlb4o6etI2eQhcYDP3jFKPyLs3Al88pPBJCiKmTPjRfqnTrEVlJs8mSwQus4EaGsbWvnSjjDs\n", + "HR2kL2vv2MlW5LxdFZ6+m70jGshlTaOUUfp0PJGxWEjcTvosBdp47B2j9COCLmuHYsYM4OhRff3z\n", + "gsXPB8jNlJmprya4V3kEIJxArizpewVyu7r4F0pKJHbVR8mWpy8Vnr5TdfMqfadi591kRdvp2JFr\n", + "WUNJnFfpZ2cHb1Qz2TtpAN2kP3NmepI+oLbEsRPOQ0PsUK30ddk7ToKl5YF5zwJwW0AyMoK38bv1\n", + "I0P6liVH+nblzTN2WJ4+HYdm6fFm47D8PUz2Tsxx+jRw8CCweLG+MeLm6fOQvk5f33k8oB0qA7n2\n", + "k6Ts0GHvAIR0eQ/PcAvAAvJ+PMBH+v39ZLGxW528pG8nb55dsiLZO319fFaNc3FhtXfsm61U2jtG\n", + "6UeAN98khG//IKhG3JT+yZPxUPodHf6k39GhJp7gZ+/IWFdepD9uHH+/Ti+cQtaPp/PhIW07+fLO\n", + "QVSxy7TjsWpExhHx9E32Toyh29oB0jeQC+gjfcvytl0AciOMGSOnxCnC9PQBsWPy3MhWpC9nOQNA\n", + "/FASkTm4lVPQ5emL+PNhkL7T3jFKP2ZQeVKWF4qKCHHG5ZDkOHj69NQpv4wpVcFcnZ6+KnvHi/RF\n", + "grDOp1YVpM/TPiyln0wSKyojI9WG194RIXFee8d4+jHC2bPA3r3AJz6hd5yMDGD69Pio/TiQvp+1\n", + "Q6EqmKvT03f654CYvaNK6XvZM1EpfVFPn1eBs47lHIeXxOnBKKzzMp5+zPDHPwLz53tbDCoRp7TN\n", + "OJC+XxCXQkUwl9pIcQ/k+nn6PErfqWSBcD19VUqfN3+edSwRpa/T3jFKP2SE4edTTJ8O1NWFM1YQ\n", + "0on0ZZV+Tw+5qdxuLF2kP9KUPstZrxRhevr2zB2ArXZ/GKTPY+8YpR8ywiT98nKgoSGcsYJw6hSQ\n", + "n892rU57J+gJS4Wn7xcs5jnZyQ1hBHJFlL4O0o+jp2/P3KHzDCJk3aRPTxkz2TsxRDIJ/P73wKc+\n", + "Fc54ZWVAfX04YwXBr469E+mu9L2sHYDEWnhTIu0II5ArovRVB3JZc+YHBoYr1zA9fRYCd74/rHYN\n", + "a55+MkmSE+jmL5O9EyO88w5QUhJ8epQqxEXp9/SQD6ZbANIN6U76XkFcCpW1cihU5+nLlFCg85Hx\n", + "9Hk3Somc2iWrwFnbyI4TFMh1KneTvRMjhGntAIT046D029sJkbMeFpOXR4quqUZYgVw/ewcQJ/2B\n", + "geElByiiztNXrfR5fHm3sUXahkX6vNk4LGUVnE86I17p79ixA/PmzcOcOXOwfv36Yb+vra3F5MmT\n", + "sWjRIixatAj/+q//KjukEMIm/bjYO37ljN0QpaefmytX7x7wt3cAcdKnZRMyXO4YkWMOVebp6/D0\n", + "WUsWi8YD3FI9ebN3dC0UPKRvt4Lo9XH39KWGTyaTWLt2LV599VWUlZXh4osvRk1NDebPnz/kussu\n", + "uwzbt2+XmqgMLIuQ/r//e3hj5ucTxRZEQroRF9JnUfo5OUBzs9w4Oknfzc8HxJS+szqlaF+6Arky\n", + "Sp/3YBOAnYzDeDrgLaDm7N/vSSLtlf7u3btRWVmJmTNnIjs7G6tWrcK2bduGXWfpKtDOiMOHyYdl\n", + "xozwxkwk4uHr+9Wwd8PkyaSN6j8ZK+nLKn1dnr5XEBcQD+SqsIrciDcsT19mwXG2ZbVq7IRJd3cn\n", + "k95tRJU+ayDX2X9QVc44KH0p0m9oaEBFRcXg9+Xl5WhwsFwikcCbb76JCy64ACtXrsT+/ftlhhRC\n", + "2NYORRwsHl6lP2aMuho4drDsyFVB+ro8fa8gLqA2Tz+d7B2VSp8l/dJJsAD/SVUsKZg8gVw3eyfu\n", + "efpSa06CITp44YUXoq6uDhMmTMCLL76I6667DgcPHnS99r777hv8f3V1Naqrq2WmN4g33oiG9OOg\n", + "9GkglwdTppBgrkpbyq+WPsWECfH19P2U/rhx/MFvVUpfRyCXR+k7x2bd2OWWVcPr6dN2fX3u7yVt\n", + "w0PKzvo+IkqfJ9tHBLW1taitrRVuLzV8WVkZ6mzbTuvq6lBeXj7kmok2ebdixQp89atfRUtLC/Jd\n", + "dgvZSV8ldu4EvvMdLV37Ig4ZPLxKH1B7ihUFq70j+4ThR85AfOyds2fdbbexY8lTESt0KX0Ri4a2\n", + "ZU33lA3KsrTjHYf3yUC2fxE4BfH999/P1V7K3lm8eDEOHTqEo0ePore3F1u3bkVNTc2Qa5qamgY9\n", + "/d27d8OyLFfC14XGRkJ8jthyKEhHewcgpK/qQBOKsOwdPxsG0BfIFbF3VHn6UeXpu9k7LIodcK+9\n", + "EwfS530ycLN3dCt9WUgNn5WVhQ0bNmD58uVIJpO4++67MX/+fGzcuBEAsGbNGvzv//4vHn30UWRl\n", + "ZWHChAl46qmnlEycFTt3kl24bql2ulFeDrz2Wvjj2tHWBpSW8rXRQfphBXK7u/3PDhg/Xr2nH2Vp\n", + "5ajtHdG6PW5kzGLvuC0yqklf5vqgQG7ae/oAsWxWrFgx5Gdr1qwZ/P/XvvY1fO1rX5MdRhhRBXGB\n", + "+Ng7PNk7gNrzailYPH1VpK9L6YcRyI2i4Jrz7yKzOUunvePM3mFpJ+K586RgilwftdIf8TtyoyR9\n", + "Y++kEKbS10H6XmUTALV5+mEXXPMKxsoofRF7R9TTZylwplu520k8HZT+iCb91lbggw+ACy+MZvyi\n", + "IqClhe0m0AWR7B2Vh5RTsHj6sqWPgWhIP53z9GWIW8bT5y1f4NaGjqcyMOtG4smk974V2ZhBFBjR\n", + "pP+b3wBLlkS3smZmAsXFwLFj0YwPxCN7x7LYyjDEOZAbpPRV5umHae/IKn2RtpZFiJQnYAqIB3J5\n", + "xnGOkUj4q3de+8gofc349a+Byy+Pdg5RWzxxsHfOnvU+2MSO8eNTVUFF4ZdlA8RL6Xt5+mEGcnXY\n", + "O6zkbd/mE5eUTd4xeDdnGaWvGa+/Hj3pl5QAx49HN74I6au2d1jrDyUS8hZPVJ6+SGllXUo/O5ss\n", + "nCyLpyzp83rsMu2iyN6hbbzU+6irvRNnNDeT4wqj8vMpSkqis3f6+giB8O6sVW3v8BSdk92glS6B\n", + "XD9Pn1WlJ5Ok5LNTOSYS4nXtAb7NWSKevohip+OFvTkL4Ld3jNKPCLW1JGsn6je4uDg6pd/eTtI1\n", + "WWvpU0Sl9AH5UgzpFMiVVfqUdN3+vjKkT9XqwIB/WzflrUux03Zhb84KasO7OcsofY2Ig7UDRBvI\n", + "FcncAfQo/aAgLoVsMFdXINdLmQPq8/RlNlZRyJB+IiGuvFnPrRVpF0dP3yj9GOHXvwY+/emoZxGt\n", + "py/i5wPqA7m89o5OpS9ixQDqlb7fcYm8St8NMqRP27N486JK34tY/Up6q0jZpNk1rCmYQWMYTz8m\n", + "aGwknv7550c9k2jtHVHSj9LeUUH6ftk7IqdcAf6kT290nqwj3UqfdXHzIn3d5O32dJGZGbybVSSQ\n", + "a1fWtIKm19/KjZR57B0/pU9TVek5AFFhRJL+r38NXHZZNPV2nIgykCuj9EdqIFcH6ScSasonAHxK\n", + "X5e9A7Azh3HZAAAgAElEQVRn4biRd5DF4Wbv0DFVWi8ibdzsF7/cex77iC4QvDE21YgBLarHa6/F\n", + "w9oBgMJC4ORJudxzUYjU3QHSN5CbTLrnjtshY+8ELSY8/fodlyiTY08RBum7KW/alpeIgeDMHxXZ\n", + "O0FtdNo7cfDzgRFI+pYFvPQSsHx51DMhyM4mavvkyfDHFlX6EyYQwvB71OZBWPYOVeN+SkqH0gf4\n", + "grn0dCa/2jssx1XGQel7KfagejhuY6omcJE2spuzeNI7o8KII/0//5ncgJWVUc8khah8fdHsnURC\n", + "rcUTdG6tHTKkH2TtAHpJnycA69yRSpGRQYhDdEcs73xEiRvwV/q8ih0QJ3CegmtB4+hU+s4FIiqM\n", + "ONKnKj9q38yOqHx9UaUPqLV4wvL0g4K4QGq3Ku9TTBDp8ywmfgodYLd43AqeUahQ+kHt/cibl4jp\n", + "mLztVMcB3IhZVcqmUfqasGNHfKwdiqiUvgzpq0zbDMveYVH6iYTaFEsKHqXv5edTsAZzo7Z3ZDx9\n", + "UXuHN3uHNxvHjZj9Ark8m7OM0teAzk7gD3+ITxCXIqoNWrKkr8reCYv0gzZmUeggfZ4+/TZ6AexK\n", + "PyiQy1riOGxPX7W9o3KHrc7NWUbpa0BtLXDRRcF128NGVBu0RLN3gOjsHZnsHRalD4gXSFMVyA2y\n", + "d1QofZnyyHQOujx9UXtHVfYOTwpm0BhupO+1+csofQ146SXgM5+JehbDYZR+fOwdQCyYy0L6KjZV\n", + "8fTlF8iVKZpG24sqfRZPP67ZO7Kbs/w2fxmlrwFx9POB6JS+aPYOMHIDuYD48YZ+pM+zqUqlpy9j\n", + "7wwMeKvPdMre0b05S5UdZJS+Yhw+TEjqgguinslwpKvSH4mBXIBf6VsWmzpX6emHYe9Q0nbLdJP1\n", + "9EU2Z8VlR67b9bx2kNv1RukrxrZtQE1NPEovOBGF0k8mCXmKxjeitHdElT5PIJeH9P1KGFOotHdk\n", + "M28A9pRLv/YiZRhY2vrZO7ztwiB9v+Csm3r3ut4ofcXYtg347GejnoU7Jk4kJNzREd6Yp0+TcUUX\n", + "wSgDuTL2Dmsgl7dkgp8yB9TVzAH4DiaXUfqypK+ynAJtp1rpi3j0up4MjNJXiJMngX37gCuuiHom\n", + "7kgkws/Vl8ncAaKzd8IgfV6lz0L6qvP0ZQO5oqWRKXQrfRF7RzR7h9ej5z1EhfXJIA5llYERQvrP\n", + "PQdceWXwjRklioqAEyfCG0/GzweI0o/C3pFN2WQJ5OoifR57JygoLLsjV4W9E9Rex+YslSUVRNro\n", + "DPyagmsKsW0bcN11Uc/CH4WF4ZK+TOYOoE7pW1b8lH7c7R0e0o/S3kmXzVm6ArNe13vtAzBKXxE6\n", + "O8nRiCtXRj0TfxQWAk1N4Y0nq/RVkX5vLzk0gvXDLkP6ugK5qu0dlYFcXfZOFJuzwiq4xrtrNiiQ\n", + "y7oQGaWvCL/6FbBkCVBQEPVM/DFa7R2eCptAijyDDuV2Q5RKn8feUeXp67Z3wt6cJZL142cl0RLW\n", + "uguuuV1vlL5GPPUUsGpV1LMIRtj2TlyUPo+1A5BsI5HaOEC0gVxee8evP9bdtFHbO6Keflj2TjKZ\n", + "2iHL2kZniqdR+grQ1kaORoy7nw9EY+/IZO+oUvq8pA+IWzxRB3JV2jsydXNY+4jK01dt7/BYL0Ft\n", + "VJVtMEpfE555hlTUlCG3sJBu9k5USh8QJ31WT38k2TsytXe8SJu2j6LgmsqUTS9lrXpHLuvmLKP0\n", + "FWDLFuDWW6OeBRvSLXtn7Fjiq7MSmRdESV8kbTOd7B3dgdwos3dYNmeFkbIp8kTBs8PWb05G6WtA\n", + "XR3wpz8B11wT9UzYkG7ZO4mEGosnbHsnXbJ3dOfp67Z3kkkSKM3M5G8rQsaWRcaULYYm0kbV5iyj\n", + "9CXxs5+RAC6LhxsH5OcTu8Tv8VUlZEkfGLmkH7W9E5c8fS97hrb3mwMlR69ibaoLrnmNFzXp82zO\n", + "MkpfAskk8JOfAF/6UtQzYUdmJkkrPXkynPFUkb5svaA4kn7U9o7KMgxRpWzqaOtn76gicEDvISr0\n", + "eq/aO0bpC+K114CpU4ELL4x6JnwI0+KRzd4B0k/pd3WlTxkG3QXXdG/O8iseJuLN03a6VXtQGxUF\n", + "2kztHQ14/PH0UvkUYQZz09neES2vHLW9E7anH2UgV6atiL0jktsvmrLpFsjlyd4xSl8xPviAlF24\n", + "7baoZ8KPsNI2BwYIWU+aJNdPuin9dLF3VHr6Udk7QfGAoNTLsOwdVSmbKjZnGaUviIceIipfltCi\n", + "QFj2TkcHIU5ZVZGbGx3p60zZjDqQG0aevorsHb85yOT4x9ne0bk5Ky5KPwZTYMepU8D//A/w5z9H\n", + "PRMxhGXvqLB2gPRS+v395MuLxOwQUfp+JA1EV3BNRz182j4oA0e1vRNExryHqUeVveOn9HNz3fsJ\n", + "E2ml9B99lJyOVVoa9UzEEJa9MxpJn5Zg8DvSkEKE9IOeIFTW3glzR66Mpy8ayBU5fMUvDqDSEhIp\n", + "uJZuVTZjMAU2tLUBjzwC7NwZ9UzEEZa9oyJzB4iW9Fta+NqwWjuAvnr6PT1kE1HQwhOn7B2v16XT\n", + "0083e8cvkOuVvWN25CrAD35ADj6fNy/qmYhjNNo7vKWVAXGlz0r6OgK5WVmE7L3IwdlfOtg7okpf\n", + "h70jkr2j296xLL4nA6P0OXD0KPDjHwN790Y9EzmERfqydXcoVCl9Xh9TN+mPG0euZ1HlABvp037P\n", + "ng1Wcyo8fcvy7yczk2RxJZPupRIAvdk7ovaOyEYrWhLC+bf0UtaqNmf195P31m2XsFH6kvjmN4Fv\n", + "fQuYPj3qmciB2juWpXecOCn9sPL0eUg/K4vcrKwlMXhIn9WLl/X0aa14L0JPJIItnqg8fT8F7ufP\n", + "u801keCvdSPi6fOQuKm9I4mtW4H33we+852oZyKPCRPIB1dFyWI/qCL9KFM2eUmfdTcuBY/Fw5K9\n", + "A7AHc1Uofb8cfQqZDVYsxB129o7fIuNFsqo8fd5FxSh9QRw9Cnz968CTT7LddOmAMCyedFf6Inn6\n", + "PEof4Dudi9feYelPBekH3RMsufa6PH2RdE8RewfQT/p+efejUunv2LED8+bNw5w5c7B+/XrXa77x\n", + "jW9gzpw5uOCCC7CX0ZhvbweuvRb43veAiy6SnWV8UFSkP4NHZfZOuhRc4yV96uuzQKW9E+TFA/JB\n", + "WNZ+ZIKxOhYMkR25tF0USp+3zMOIUPrJZBJr167Fjh07sH//fmzZsgUHDhwYcs0LL7yAw4cP49Ch\n", + "Q3jsscfwla98JbDf1lZSJ7+6mvj5IwlG6QcjDNLntXdYSJ/F3qHBPy8vnvajQunLePqUuLziT2EX\n", + "XAuKIaggfd6a/V7K3StQPCKU/u7du1FZWYmZM2ciOzsbq1atwrZt24Zcs337dqxevRoAcMkll6Ct\n", + "rQ1NPlL3j38EPvlJYPFi4OGH2bIr0glhkH5csnfojc+yS9YOUU+fV+lHYe+wkHV2NiGfgQHva/yK\n", + "rVHI2DsZGf5ZLjoyf6JW+rzZOCL9p73Sb2hoQEVFxeD35eXlaGhoCLymvr7etb+ampSl89BD/moo\n", + "XRGWvaOS9EWzjURUPiC3I5cVPEo/KNuGgsXeYQkKJxLBhK0ikOsXjA1qL7o5yyu3Paid33i8JMu7\n", + "SIjYO3FW+lJTSDDKcMvBGl7tEon7cOedwOHDQG1tNaqrq2WmF0sUFgJ/+YveMVSR/pgxRPGxkp4T\n", + "YZN+Otg7LEqf9tXT4/2adNs7QIr03f6GovEASnxuFKAje4fXflFB4rqrbNbW1qK2tla4vRTpl5WV\n", + "oa6ubvD7uro6lJeX+15TX1+PsrIy1/62bbtPZjppgcJC4De/0TuGKtIHUmmbUZA+6+YpQCyQy2Lv\n", + "WBZ7yiarvcO6gIhaMxQy9g5tL6r0RdpFnbIpkncfhdKvrh4qiO+//36u9lL2zuLFi3Ho0CEcPXoU\n", + "vb292Lp1K2pqaoZcU1NTg82bNwMAdu3ahSlTpqCoqEhm2LRGYSHQ3Kyvf8sinr6K7B1AztcXJf3M\n", + "THLjsJYqBvQpfXpjZzDcKarsHSDYmmFN2VSh9L3aigRyRQKyQDikryrvPu719KXWnaysLGzYsAHL\n", + "ly9HMpnE3Xffjfnz52Pjxo0AgDVr1mDlypV44YUXUFlZiZycHPzsZz9TMvF0he5AbmcnuVlVfbhk\n", + "0jZFSR9IqX3WJ4yuLiAvj71/VtJntXYAPfaOF1gCuarsHTf4KXYaDHUrAeFH3iInZwFqSV9F3v2I\n", + "r72zYsUKrFixYsjP1qxZM+T7DRs2yA4zYqCb9FVl7lBEofSBFOnn57Ndr8veYVXmrH2qIn3WQK4u\n", + "e8ePhO1tnX8T0VRPUaXvtmDzZuPQOkYDA0Of+PwWCbMj12AQ+fmEmFnrvvBCpZ8PyJG+SIVNCt5g\n", + "ri57h0fps9g7qjz9qO0d0cwflr0BvOOpVO5u19P6Pk4iF8kOioPSN6QfMjIzgYIC4ORJPf3HifRV\n", + "KH1W6CrDoNre4anjE2d7J0jpe6n2IFsIILaQW7swArlepOzWxtTeMWCGTotntJK+yOYsHUo/bvZO\n", + "lErfjVhZFgseAhdpIzqGk8hHbe0dA36kE+nLVNoMW+nr2Jylw95RRfo6d+TS9rKevhOitlDUpO9G\n", + "5Lybs4zSH8XQTfqq0jWB6LJ3cnL4Km3qDOSqzt5h6U9WpdP5xNHTF1H6qrN3eAK5Xm14N2cZpT+K\n", + "oZP0R1r2DiviEsgNy9OPOpCrw9On7XTbO5mZqdO27PBT4m5EzruoGKU/ipFO9o4s6fMelUgRF9Ln\n", + "KUERpr0jW3BtYMA/cEnbq7Z3ZDx9Vdk7Xqdt+SlxtzHMyVkGzBgtpN/RER7p856cFaW9E1Yg18/e\n", + "oSTqV+YiKG8+6EwAXvKmY+pW+l5tgjx9VntnRFfZNBDDaCH90WjvpEuefpDiZmmvw9MPI5Dr1UbV\n", + "9XGvsmlIPwLorL8TJ9IPU+nrJH2VO3JV5unL7MiVPXkrqL2fpx91yiZtw7rZivf6uNfeMaQfAdIp\n", + "eyc3N9raOyywrPTK3gmr4JqfvaOb9EV25ALhZO8A/J4+z/VuC4RbGYeoEIMpjD6MluwdWaXPmrJJ\n", + "b0YeFZXuefqsgVzRlEva3q8AmsjmrDgpfV57R2ZzFlX5cTgJ0JB+BMjNJSljPHnorIiTvSObp8+q\n", + "9Hk3ZgF6yjCEWU9ftuBaXJW+SAA4rECuzOasuPj5gCH9SJBI6PH1LUvP5qy4e/q81g6gpwxDmLV3\n", + "4mDvqA7IAmKxgCgDuaybs4Jed5gwpB8Rpk1Tb/F0dxPPUOSUKy/InJMblqcvQvojwd7RqdRl23uR\n", + "t6inH1Yg18/TZ7V3vHL6jdIf5dDh67e18R0kwoLsbPJhZbFC7LCseJN+3AO5umvvhGHvhOnp8z4d\n", + "8Kpx3s1ZrAtEFDCkHxF0kH5rq1o/n0LE4jl7NrVgiICH9Hk3ZgHRlmFQUXtHRZ6+DOnLFE6LQ/aO\n", + "qkAu6+Yso/QN0kbpA2JpmzJ+PhCO0u/pIWl0fkhneyeuKZvpuDmL58mAKn27JWqUvsGIV/pxJ/1E\n", + "IphYAb7aO8beSUFHwTXd2TuqNmdlZJAv+2EwRukbpJXSFyF9GT8f4MvTFyF9gM3i4dmRS31sv6cH\n", + "VSmbsnn6cVX6YQVyeWrp0Ot5C7TZ+zdK38Ao/QDw5OnznppFwUr6rEo/kQi2eFSmbMbd3hlJBdd4\n", + "NmcBwxcJo/QNjNIPAK+9wxvIBdQGXimCLJ6RYO/IlGWOi9LXuTnLrX+j9A2M0g8AvUG8ygDYodve\n", + "4SH9oIVEVe2dKPP0qVoXKcssk/XDS/q8GT88JE7nxJrXb5S+AaZNIztyg7JHeKBT6fNm78gqfYBd\n", + "7YuSvg6lH2TvhFlaWZe9I1OWWea4RJ5ArmWpJXFee8cofYNhGDuWkGJbm7o+dSl9kcPRZZU+oJ/0\n", + "dSj9IHsn7OMSddg7rG2j9PT7+8mxiF5VLXk3UInYO06lb0jfQHn9HdXF1iii8PQBdtIX2ZwFxNve\n", + "CSJ9lnnJHqIiWkrBb2wdnr7bAiOy81fV5ixg+CJhCq4ZAFDv67e2xieQq0rps6Rtppu9I0v6AwMk\n", + "BzyItHXZO6xlmUU9fScZJ5MkfuCl2nkJXKSNSNkGo/QNhkE16Y9WpZ8u9g7dpcmi+PxIn6ZrBtVm\n", + "p0rdrVieTBkHlgXD7ymBt8qmagL3auMXbOXZnAUYpW/gAaP0/cGaqx8n0vd7eqBEy3KQhp8fz/q0\n", + "kJHhfXSfbtJX6emHRfoiSp91kTBK3wCAWtJPJgnRTpqkpj874q70RTdnBdk7PMrc3qcsWQP+1oyK\n", + "flhJnzeTxt5WlacvUo45iPR5A7myi4RR+gYA1JJ+ezshfB1ncIqkbIadvaMjkMtTd4fCz94RIWtR\n", + "a4ZCVq2rbpuuSp/X3nH2b5S+AQC1pK/LzwfEUjZHgqfPa+0AwfYOa38ZGSTl0E1p88zLyyaKq6fv\n", + "NmaYnr4ue8cofQMAaklfl58PRJu9E+XmLFHS97J3eIq3Ad7B3DDtndGm9FVtznKrvWOUvkHaKP0o\n", + "PX2dKZs6lL4qe4f2JUv6XuQrS/osO3J5SyMA0ZO+rs1ZRukbAEgvpd/RwXdObphKX1cgV4e9EwXp\n", + "R2HvyByMHgbpiwRyZTZnGaVvAADIzycBWJaiYkHQqfSzssgHluV4QQoVSp8nZVNHIDdqe0dGpVPI\n", + "2Dteef4sm7NEd/O6PSHoyN5R5ekbpW/AhYwMoKAAOHlSvi+dSh/gz+BJB08/CnuHZ5467R0WtU7z\n", + "/J0KV7fSjyqQG+TpyywSRukbDEKVxaNT6QN8vr5lEdKPe/ZO2IHc7m7+3b1ufYWVvUPbO0lYZnNW\n", + "WKpdh6fPY++4Vdk0St8AgDrS11Vhk4InbbO3l6jEIGIIAgvpW1a8lL7fQnL2bHyUfhikH5ann5VF\n", + "NifyHEQuWyo56Hq3evpG6RsAUKv0dds7rKSvwtoB2Ei/p4fcTJmZ/P2zkD6PBw/42zuqlH5Ynj4g\n", + "Tvqinr4I6ScS7jtggxaKMDdnGaVvMAhV5ZV1K30e0lcRxAXYSF9U5QPR5OmHTfpR2jsiSl9kcxbg\n", + "TrIqC66Z2jsGymCUvjdY8vRlSD/u9o6K7J0o7R0nSdKS0H6KV6T2jls73WUYLMv/tZjaOwaeSBdP\n", + "nyd7J12Ufti1d1QGcmXtHRYiBcRJn3ra9uNAKakGna3La++4tdO9OYuezOX1WozSN/CEKtJvaSHp\n", + "n7oQhdJnydMX3ZgFpLe9E3X2ThCBJRLD1T5rfn9YpK/zzFuj9A08oYL0LSucPP24evoiG7OAYKXf\n", + "3a02kJuO2TtuAVnWpwRnW9YjGlWQftBYvGUVnNk4vE8SRukbDEIF6Z8+TchENkXSDzwpm2Fm7+gM\n", + "5HZ38y9efn2OlOwdFsXu1palXZSBXJ68e7+gL+C+SBilbwBADemfOkVKOuhEXJW+zFhBSl/EOkqH\n", + "7J2BAXHiBvjiATzqG1Br7/Ckhg4MkC+v1F+3sgqjTum3tLRg2bJlmDt3Lq666iq0tbW5Xjdz5kyc\n", + "f/75WLRoET72sY8JT3SkIjeXZAGwVJP0gm4/H+D39FWQPlWpyaT3NTKkn51NbnSnt0vR1cVvHam0\n", + "d3Rl71DSZj22USSbxm1sUU8/jOydoCAz7z6AEVl7Z926dVi2bBkOHjyIK664AuvWrXO9LpFIoLa2\n", + "Fnv37sXu3buFJzpSkUjI5+rHTemfOaPm2MZEIljtyywwiUSwHcNL+mHYO7LZO/RgdRbIKP0oPX1e\n", + "Ug5S4m7XB9k7I07pb9++HatXrwYArF69Gs8++6zntRZPTd5RCFmL59SpcJQ+a8rm6dPqzuoNIv3O\n", + "Trn4gZ/FI6L00yF7p7dXzZMCb1tWT9+N9FUrfd7sGqP0ATQ1NaGoqAgAUFRUhKamJtfrEokErrzy\n", + "SixevBiPP/646HAjGrKkHzd7R5XSB9hIX8ZKCiJ9Xk8/HbJ3VNlDLG15Pf3MzNTGJ57xVNk7qq6P\n", + "s9L3XXuWLVuG48ePD/v597///SHfJxIJJDzMsN/97ncoKSlBc3Mzli1bhnnz5mHp0qWu1953332D\n", + "/6+urkZ1dXXA9EcGVCh93fbOpElEwbPg9GmySKhAUK6+LOn72TGiSj/u2Tthkj6v0gdSBEuDqr29\n", + "wZ+nMEjcqdx5AsUqlX5tbS1qa2uF2/tO45VXXvH8XVFREY4fP47i4mIcO3YMhYWFrteVlJQAAKZN\n", + "m4brr78eu3fvZiL90QQVSv+cc9TNxw2TJ5MDX1gQtr3zt4+YEFTbO5SoLWt4UDCq7J2oSF/E06ft\n", + "+vpS71VPT/CTbNhKn2UfgK4duU5BfP/993O1F7Z3ampqsGnTJgDApk2bcN111w27pqurC2f+5gl0\n", + "dnbi5ZdfxnnnnSc65IhFOij9yZNJfR8WpJu9ozKQm5lJvpy+NCCWvaMjZVMF6bMQmKzSt4+nw97h\n", + "Ccy6efRRKX1ZCJP+d7/7XbzyyiuYO3cuXn/9dXz3u98FADQ2NuLqq68GABw/fhxLly7FwoULcckl\n", + "l+Caa67BVVddpWbmIwjp4OmPH08+9G4phE6otHd0Zu8ARE2qVPq0T7eFRMTecXu/0yV7R8TTdxtT\n", + "B+nLlmJmOeCdJ68/TAivPfn5+Xj11VeH/by0tBTPP/88AGDWrFl4++23xWc3SlBYCHjEwZkQRvZO\n", + "IpGyeKZN879Wtb3jt4dBd/aO6IlcsideAeqyd9xIW0bp69yRC7jbQizn+eq0dzIzUxu4MjKCX4vb\n", + "oqJzxzwPzI7cGKCkBDh2TLx9S4t+ewdg9/XTyd5RHcgFvDN4osre0WHviJA+q9IfOzYce8c5ht97\n", + "Qg9qoeqdV+mzvvYwYEg/BpAl/TCUPsBO+mHaOzpTNkWLuam0d9I5e8ep2FktDudCFRbp84xhlL6B\n", + "FAoKiDr22tTjh2SSkKzOWvoULKTf10e+RIugOREV6ff3ky+RG9XN3rGs0Ze9I+rpO1+3yEldrCmY\n", + "dN8oL+kbpW8ghYwMoKhITO23tRErReSMWF5MmRKcwUOtHZa6LiyIKk+fVu8UeR1u9k5/P/k782Rw\n", + "jER7R6SyJ0vg2a12vx/JZmSQL7oJjHdh4VX6rO9ZGDCkHxOUloqR/smT4Vg7AJvSV2ntANEFckX9\n", + "fMB9IeG1drz6AfieGKLO3nGSNwvpiyp9exvWej10frykz6L0jb1j4AtRX7+5mWT/hAFW0lcVxAUI\n", + "ofvV/JFN2dRF+k51LXIKl9fcZJW+bPZOTw/7JitR0udN2XS2YSF9e8BYtdI39o5BIERJ/8SJeJG+\n", + "yswdwL/mj2XptXdESd/N3uHN3PGaGyUSVpvIzSLiWTTcTs5iTRkVVfoigVwRpW9vo1rpm0CuQSBK\n", + "S4HGRv52zc3BefOqEIXSnzjRu+ZPTw+5uWR2OvopfZUnconYO25z4yFs2odzLrJPCqxPLU6fXae9\n", + "I6L0nSRulL5BqEgXpR8UyFXt6U+a5K30VZzQ5ZUdFFd7h5f03RYgnj7GjRtKpv39qZz1IMgofd4A\n", + "sIjSty8UOpW+ZcWryqYh/ZhAxtOPk9IP095RQfo5Oe6BYhnS12nvREH69vY8JSCc1hBrW6fSZwk8\n", + "i8QBZOwdHqVP6+6oymiThSH9mKCkRMzeiZunr9re8SvpLJu5A3iTvoyn76b0RewdGmi015bnfWKg\n", + "pG0/x4gne8dJ+jIlIHQGcp1Kn2WssJR+nPx8wJB+bCCashmm0p8yJfyUzXRU+uPGDbeMROwdt+Mc\n", + "eZV+RoZYLRsK51MLj9IX9fRFArnOhYJlLN1KnyczKEwY0o8Jpk0DWlvdS/L64cSJ0WvvqDiA3Y/0\n", + "RQO5OTnDvXgRewcY7uvzkC6FzMLhfGrhWbzclD5L27CUPm8g1/lkwFpPP05BXMCQfmyQmUnI2+Wg\n", + "Ml+M9Dx9SvpuxyyrUvpu+wBklL7bhjIRewcYTtgqDmuX8fTDsHfCUvoy9g6P0jf2joEneC2egQFS\n", + "bG3qVH1zsiOK7J2sLHJzumXYxNXeccsIErF3gOFKP2rSlwnkhrkjV7e9w1N7xyh9A0/wZvC0thKC\n", + "DesDNW5cqnCYF1QrfcA7bTOugVwv0ldh74jYTs4+wlT6op4+JdeBgeBTrYChqt2yolf6JpBrwARe\n", + "0g/TzwdIYDGo6JqOU7y8NmjpVvoynr6T9FXaO7KpnzLZO7yBXFmlT8k4KN3RrtppcbugIoRG6RtE\n", + "Dt60zTD9fIr8fGIpeaGlBcjLUzumVzBXJek7YwaqPX1V9o7IvJzEzZO940b6YaZsss7V3oZ1UdO5\n", + "I5cqfcsySt/AB7ylGMJW+gBR8S0t3r9vbVV/ipeXvaMie4eqSGd9GdWevqjSd/P0ZZU+Tx8y9o6o\n", + "p8+rwJ1tRBcXlUrfXrrZKH0DT1RUAPX17Nc3NZE6/GGioMBb6Q8MEOtH9YEuXvaOqvRQN4tHxtN3\n", + "s3dEn0pU2DsyTwvUS6dWRVj2jl21sx68Ym+jY3HhUfpAKm3T5OkbeGL6dOCjj9ivb2wEysr0zccN\n", + "fqR/+jQhNpkCaG7wsndOnyYZRbJwI33VSl806KzD3uFdOOztwwrkUjJmDYA7CTxqpQ+k0jaNvWPg\n", + "CUr6bjnpbmhsJJZQmPAjfR3WDuBdikFVplBurjvpiwZy3Tx9UaWvw97hXTjsG7TCDuSyvl5Rpa/L\n", + "0wdSpG/sHQNP0GMPg3LhKeJG+jqCuIC30m9vV2fvODdonTkjng7qpfTjQvoySp8nkCvj6dN2PGWc\n", + "eT19ndk7QGqxNErfwBc8Fk8cSV+X0veyd1SQ/sSJ7qQvuslMpafvtJ5E7B1nTX0RpS9i7zjr9ogo\n", + "fVZ7R0Tpy9o7LLuEz541St8gADyk39AQL9LXZe94BXJVkb6bfSRD+irtHSfpiyp9macFp9JntXcm\n", + "TBDbFGYfjzXrKWylz/IEQl+HUfoGvpg+HairC76uu5uQQViHolPEyd5RqfSd/cuQPj14xF4SOS6k\n", + "T2Ak9qMAAA6KSURBVHPHeZSnXbHz2Dt2a8qy2LNY7O10Kn0ZT59lXtTeMUrfwBcVFWxK/9gxovLD\n", + "PphhJNo7TqVvWcTuESX9RIIQgt3iidLesdtNdNHg+dw47R0epU/HpU8IGQyMY2/HqvRp0HRgQDw1\n", + "lIf0WeZF3zeTsmngC1alH4WfD8TH3kkmCTHIbs6i/dsXla4ucpPKpJ46M4JEUzZVKH17H7IpnzyL\n", + "F1XslsU3rlPps5B+IpGya0TsHZb3ldfesXv6hvQNPMHq6UdJ+i0t7mmluuwdN6Xf0UFIlEU5svRv\n", + "X1RkrB0K50IlqvRzc4cGmUWVPu1DdnMXz/iZmUSB9/SIkz7PfGkMQUTps5I+T4CZLpYi77lOGNKP\n", + "GaZPBz78MPi6qEh/7Fjy4XerQa/L3nEr6ayymqdT6asgfftCRe0iVfZO2Erf3p538aIEzvNUJqL0\n", + "AX7S51X6zv0KLPZOT48hfYMAlJeTmjp+5YuB6Egf8LZ4dNk7+fmkbzva2tTsxgX0KX1K+j09xCoS\n", + "sYucpC8Sa7D3IWsP8ZI+9ed5FhtnLIB1vjSOwkr69lRWlsWU9k93GQf9PY3SN2BCVhYwYwbwwQf+\n", + "19XXR0f6XpU2ddk7eXnDLSWVTxU6lL7d3pGpBuokfZG52eMLsvZQZydfe6q+ecbNzk7V0ecpVEcX\n", + "C1bSp6m1lsVGzPS1sD59UE9f9CwFXTCkH0NUVgKHD/tf89e/ArNmhTMfJ7yUvi57Z9w44g/bs2FU\n", + "jqVD6dvtnahJX1bpOxcNXnuHV+nT7CceggX47R26SPT2sj2J0dfCSuJ2pS9SYVUXDOnHEKykP3t2\n", + "OPNxYupU4OTJoT+zLH2kD5B+7SWdVVpJupQ+7VN2dy8l3P5+QmgynnzYnr6I0qfturr4FilKyqxz\n", + "pKmsrGPY58RC4sbTN2BGEOmfOUM+2GGXVaYoLh5+gHtHB8mkUZFC6Qanr69a6dsPfFcRJLbbO+3t\n", + "4uWm7YRLM5Z492ZE6emLKH3aTkTp85A+78LCOyfj6Rswo7ISOHTI+/fU2gl7YxaFG+nrru3vVPoq\n", + "4wc6FhS7vSMTdLYTrmgROLsnL2oPyXj6sqTPm7JJF0fWufEqfV5P35C+QSCClP6RI9H5+YD7Wb7H\n", + "j5PFQBdoMJdCpdJ3BopVWEd2e0dG6dOTvXp6xG0i+8LR3s6/AMl6+iL2Dn1CEAnk6lL69kAur6dv\n", + "SN/AFzNmkGJqziP8KKL084FoSF+npz9+/NBAsYoFxW7vyKaXTp5M+hIlfUpWAwNi1hVdNOjRfzxB\n", + "SVGlz5spA6QWGF2kTz36zk5j7xgoxpgxpAbPkSPuv49a6Udh70ydSg6Cp1CdHmpfVFT0rUrpA6Rt\n", + "a6s46WdkpIquiZJ+R0fK2uGxFWWUfnc3/47cri52e2fsWJJzf+YM2xiJBHkfW1v5ArkmZdOACeef\n", + "D7zzjvvvRqPSLyoiCwtFc7PaCqN20lfxFEGJGpBX+nl5cqQPpCwaEXuHKn2RWkcynn5XF998ee2d\n", + "RIK0OXWKL0OIlfTtnr5J2TQIxMKFwNtvu/8uaqVfUJAiAYpjx/SSfnHxUNI/fpwsPqrgVPqypD9t\n", + "Wmovg6zSV0H6NENJROnTBUNkv0FuLpm3qL3Ds2Da7R3WgDcv6U+YQD4fxtM3UA4v0u/uJn5/lKSf\n", + "kTG8RtCHH5JYhC7YLaX+fnKjFhaq658WkgMIwcraO/a9DHFQ+nRDnYzS583cAVKZUbykTy0lngXT\n", + "bu+wLk6U9HlKRLS0GE/fQAO8SH//fmDu3OgPZZg5cyjpHz0aHuk3NxMykSl97IS9tIQKpU9J37II\n", + "6avw9GXmRUlfVOmfOUPG57XU6ILFe54xXSx02ju0Da+9w0r6tMyDIX0DJlRUkCCQM2D6zjvAeedF\n", + "Myc7Zs4kRA+QrJC6uvBI/9gxtdYOQMisuZncoH194oeiU9CAZ1cXIS9Ze6etjRTiE326kVH6+fmp\n", + "8adO5W/b0kIWwGnT+No1NpLPlkjKpk5759gxtveQPj0a0jdgQiJB1P6+fUN//vbbJMgbNeykf/w4\n", + "uQl0frDz8lIbY3QEjSsqSBG7+npS6VTFxjeacSRbEZWqZZkMKfokI6L0s7LI3/fgQX7Sp3NvbuZr\n", + "W1BAYldTprD/Leh+C157p6GBL27w4YdsC1hBAVko+/rYTxsLA4b0Y4zFi4E//GHoz958E1iyJJr5\n", + "2DF7NiEBgGQTzZypd7xEIhVH0KH0KyrI00pdHSF9FZg6ldz0x4+rIX1Zpd/SIqb0ATLue+/xqXVg\n", + "qNLnJf2//pVvrsXF5ACijAx2+zMnh2yEZP08TZjAR/qNjfxprrohTPq//OUvce655yIzMxNvvfWW\n", + "53U7duzAvHnzMGfOHKxfv150uFGJyy8HXn899X1nJ/H0L7ooujlR2FNK3303HMtp7lyy0Bw8SHYt\n", + "qwQ9prKujiwAKjB1Kvl7TZkid1wetZ5OnBBX+gUFhNzGjBFLH6SkL6L0T54k9hBPPEKU9I8c4csw\n", + "Ki4mT6ysT47FxWThZCH9yZPJhjZVIkIVhEn/vPPOwzPPPINLL73U85pkMom1a9dix44d2L9/P7Zs\n", + "2YIDBw6IDjlqUFtbCwBYuhTYsye1yec3vyGEHwd/cM4corg7OojldMEFesah7wVASP/99wmRVlWp\n", + "HYceSK+S9CsqgN/9Tv6mp7WY6utrpZT+7t3kCU1EdRYWkvddxNM/cYJkHfEE3vPziRfuFQuxfy7s\n", + "c+zu5ost0SdUVtI/5xzyLwvpZ2SQ911nrEsEwqQ/b948zJ071/ea3bt3o7KyEjNnzkR2djZWrVqF\n", + "bdu2iQ45akA/0Lm5RO0/8wz5+dNPA9dfH9287MjKAubPB/buJaS/cKGecZykf/AgcOCAetLPzycl\n", + "Bt57Tx3pn38+8OKLQFmZXD9z5pDXfeZMrfCGtGnT5Db1FRYS1cpr71CBwpvfT1+nl9J3I/0xY0g7\n", + "ns8GJXEdpA+MMNJnQUNDAypsd1B5eTkaGhp0Djni8MUvAo88QlT1M88AN98c9YxSWLkS2LCBqNAw\n", + "LKfFi4GXXybvhep9CokE8PGPA7/8JXDJJWr6PP98EiSUtaLGjyeEO2aM+EHw9DWJLhrUmhGNTdTX\n", + "811P53nZZXztiouJGGEFL+nTz106k77vA9eyZctw3JkzCOCBBx7AtddeG9h5Ik7RizTF9dcD//mf\n", + "wIIFwJe/LK8aVeK224B584A1a/g37Yhg8WJiE1x3nZ59CjffTIJ0F16opj+aZfX1r8v3lZkJXHyx\n", + "eHuawii66Wz1arJwiMRuPvqI2HI8GD8e2LoVuOEGvnbl5XxznDWLvDesm95mzybZT6xPLsXF0ZZM\n", + "cYUlierqamvPnj2uv/v9739vLV++fPD7Bx54wFq3bp3rtbNnz7YAmC/zZb7Ml/ni+Jo9ezYXZyvZ\n", + "02jZT6y2YfHixTh06BCOHj2K0tJSbN26FVu2bHG99nDQ+YAGBgYGBtIQ9vSfeeYZVFRUYNeuXbj6\n", + "6quxYsUKAEBjYyOuvvpqAEBWVhY2bNiA5cuXo6qqCrfccgvm8xhuBgYGBgZKkbC8ZLqBgYGBwYhD\n", + "5DtyzeYtgrq6Olx++eU499xzsWDBAvzHf/xH1FOKHMlkEosWLWJKGhjJaGtrw4033oj58+ejqqoK\n", + "u3btinpKkeHBBx/Eueeei/POOw+f//zn0dPTE/WUQsNdd92FoqIinGeLVLe0tGDZsmWYO3currrq\n", + "KrS1tQX2Eynpm81bKWRnZ+Ohhx7Ce++9h127duG//uu/Ru17QfHII4+gqqpq1GeBffOb38TKlStx\n", + "4MABvPPOO6PWIj169Cgef/xxvPXWW3j33XeRTCbx1FNPRT2t0HDnnXdix44dQ362bt06LFu2DAcP\n", + "HsQVV1yBdevWBfYTKembzVspFBcXY+Hfdjjl5uZi/vz5aGxsjHhW0aG+vh4vvPACvvSlL3kmCowG\n", + "tLe3Y+fOnbjrrrsAkDjZZJni/GmMSZMmITs7G11dXejv70dXVxfK4pTDrBlLly5FniPndvv27Vi9\n", + "ejUAYPXq1Xj22WcD+4mU9M3mLXccPXoUe/fuxSWqdgmlIb71rW/hBz/4ATJEdyONEHzwwQeYNm0a\n", + "7rzzTlx44YW455570GU/smwUIT8/H3//93+P6dOno7S0FFOmTMGVV14Z9bQiRVNTE4r+VpCpqKgI\n", + "Tfbj5TwQ6R012h/b3dDR0YEbb7wRjzzyCHJli7qnKZ577jkUFhZi0aJFo1rlA0B/fz/eeustfPWr\n", + "X8Vbb72FnJwcpkf4kYgjR47g4YcfxtGjR9HY2IiOjg48+eSTUU8rNkgkEkycGinpl5WVoa6ubvD7\n", + "uro6lMetJF2I6Ovrww033IAvfOELuO6666KeTmR48803sX37dpxzzjm49dZb8frrr+OOO+6IelqR\n", + "oLy8HOXl5bj4b9txb7zxRt+qtiMZf/rTn7BkyRIUFBQgKysLn/vc5/Dmm29GPa1IUVRUNFg14dix\n", + "YyhkqMgXKenbN2/19vZi69atqKmpiXJKkcGyLNx9992oqqrC3/3d30U9nUjxwAMPoK6uDh988AGe\n", + "euopfPrTn8bmzZujnlYkKC4uRkVFBQ7+7fCCV199Feeee27Es4oG8+bNw65du9Dd3Q3LsvDqq6+i\n", + "SnXlvTRDTU0NNm3aBADYtGkTm1jk2r+rAS+88II1d+5ca/bs2dYDDzwQ9XQiw86dO61EImFdcMEF\n", + "1sKFC62FCxdaL774YtTTihy1tbXWtddeG/U0IsXbb79tLV682Dr//POt66+/3mpra4t6SpFh/fr1\n", + "VlVVlbVgwQLrjjvusHp7e6OeUmhYtWqVVVJSYmVnZ1vl5eXWT3/6U+vUqVPWFVdcYc2ZM8datmyZ\n", + "1draGtiP2ZxlYGBgMIowulMjDAwMDEYZDOkbGBgYjCIY0jcwMDAYRTCkb2BgYDCKYEjfwMDAYBTB\n", + "kL6BgYHBKIIhfQMDA4NRBEP6BgYGBqMI/x+3ghdT1LKwsgAAAABJRU5ErkJggg==\n" + ], + "text/plain": [ + "" + ] + }, "metadata": {}, - "outputs": [], - "prompt_number": 4 - }, + "output_type": "display_data" + } + ], + "source": [ + "x = np.linspace(0, 3*np.pi, 500)\n", + "plt.plot(x, np.sin(x**2))\n", + "plt.title('A simple chirp');" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "These images can be resized by dragging the handle in the lower right corner. Double clicking will return them to their original size." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "One thing to be aware of is that by default, the `Figure` object is cleared at the end of each cell, so you will need to issue all plotting commands for a single figure in a single cell." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Loading Matplotlib demos with %load" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "IPython's `%load` magic can be used to load any Matplotlib demo by its URL:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "%load http://matplotlib.org/mpl_examples/showcase/integral_demo.py" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "\"\"\"\n", - "Plot demonstrating the integral as the area under a curve.\n", - "\n", - "Although this is a simple example, it demonstrates some important tweaks:\n", - "\n", - " * A simple line plot with custom color and line width.\n", - " * A shaded region created using a Polygon patch.\n", - " * A text label with mathtext rendering.\n", - " * figtext calls to label the x- and y-axes.\n", - " * Use of axis spines to hide the top and right spines.\n", - " * Custom tick placement and labels.\n", - "\"\"\"\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "from matplotlib.patches import Polygon\n", - "\n", - "\n", - "def func(x):\n", - " return (x - 3) * (x - 5) * (x - 7) + 85\n", - "\n", - "\n", - "a, b = 2, 9 # integral limits\n", - "x = np.linspace(0, 10)\n", - "y = func(x)\n", - "\n", - "fig, ax = plt.subplots()\n", - "plt.plot(x, y, 'r', linewidth=2)\n", - "plt.ylim(ymin=0)\n", - "\n", - "# Make the shaded region\n", - "ix = np.linspace(a, b)\n", - "iy = func(ix)\n", - "verts = [(a, 0)] + list(zip(ix, iy)) + [(b, 0)]\n", - "poly = Polygon(verts, facecolor='0.9', edgecolor='0.5')\n", - "ax.add_patch(poly)\n", - "\n", - "plt.text(0.5 * (a + b), 30, r\"$\\int_a^b f(x)\\mathrm{d}x$\",\n", - " horizontalalignment='center', fontsize=20)\n", - "\n", - "plt.figtext(0.9, 0.05, '$x$')\n", - "plt.figtext(0.1, 0.9, '$y$')\n", - "\n", - "ax.spines['right'].set_visible(False)\n", - "ax.spines['top'].set_visible(False)\n", - "ax.xaxis.set_ticks_position('bottom')\n", - "\n", - "ax.set_xticks((a, b))\n", - "ax.set_xticklabels(('$a$', '$b$'))\n", - "ax.set_yticks([])\n", - "\n", - "plt.show()\n" - ], - "language": "python", + "data": { + "image/png": [ + "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\n", + "iISh/weZPyRnS1m/IAAAAABJRU5ErkJggg==\n" + ], + "text/plain": [ + "" + ] + }, "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "display_data", - "png": "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\niISh/weZPyRnS1m/IAAAAABJRU5ErkJggg==\n", - "text": [ - "" - ] - } - ], - "prompt_number": 5 + "output_type": "display_data" } ], - "metadata": {} + "source": [ + "\"\"\"\n", + "Plot demonstrating the integral as the area under a curve.\n", + "\n", + "Although this is a simple example, it demonstrates some important tweaks:\n", + "\n", + " * A simple line plot with custom color and line width.\n", + " * A shaded region created using a Polygon patch.\n", + " * A text label with mathtext rendering.\n", + " * figtext calls to label the x- and y-axes.\n", + " * Use of axis spines to hide the top and right spines.\n", + " * Custom tick placement and labels.\n", + "\"\"\"\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from matplotlib.patches import Polygon\n", + "\n", + "\n", + "def func(x):\n", + " return (x - 3) * (x - 5) * (x - 7) + 85\n", + "\n", + "\n", + "a, b = 2, 9 # integral limits\n", + "x = np.linspace(0, 10)\n", + "y = func(x)\n", + "\n", + "fig, ax = plt.subplots()\n", + "plt.plot(x, y, 'r', linewidth=2)\n", + "plt.ylim(ymin=0)\n", + "\n", + "# Make the shaded region\n", + "ix = np.linspace(a, b)\n", + "iy = func(ix)\n", + "verts = [(a, 0)] + list(zip(ix, iy)) + [(b, 0)]\n", + "poly = Polygon(verts, facecolor='0.9', edgecolor='0.5')\n", + "ax.add_patch(poly)\n", + "\n", + "plt.text(0.5 * (a + b), 30, r\"$\\int_a^b f(x)\\mathrm{d}x$\",\n", + " horizontalalignment='center', fontsize=20)\n", + "\n", + "plt.figtext(0.9, 0.05, '$x$')\n", + "plt.figtext(0.1, 0.9, '$y$')\n", + "\n", + "ax.spines['right'].set_visible(False)\n", + "ax.spines['top'].set_visible(False)\n", + "ax.xaxis.set_ticks_position('bottom')\n", + "\n", + "ax.set_xticks((a, b))\n", + "ax.set_xticklabels(('$a$', '$b$'))\n", + "ax.set_yticks([])\n", + "\n", + "plt.show()\n" + ] } - ] + ], + "metadata": { + "signature": "sha256:74dbf5caa25c937be70dfe2ab509783a01f4a2044850d7044e729300a8c3644d" + }, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Raw Input in the Notebook.ipynb b/examples/IPython Kernel/Raw Input in the Notebook.ipynb index 660d2ba..f9887b2 100644 --- a/examples/IPython Kernel/Raw Input in the Notebook.ipynb +++ b/examples/IPython Kernel/Raw Input in the Notebook.ipynb @@ -1,191 +1,158 @@ { - "metadata": { - "name": "", - "signature": "sha256:ac5c21534f3dd013c78d4d201527f3ed4dea5b6fad4116b8d23c67ba107e48c3" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Using `raw_input` and `%debug` in the Notebook" - ] - }, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Using `raw_input` and `%debug` in the Notebook" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The Notebook has added support for `raw_input` and `%debug`, as of 1.0." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Python 3 compat\n", + "import sys\n", + "if sys.version_info[0] >= 3:\n", + " raw_input = input" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The Notebook has added support for `raw_input` and `%debug`, as of 1.0." + "name": "stdout", + "output_type": "stream", + "text": [ + "What is your name? Sir Robin\n" ] }, { - "cell_type": "code", - "collapsed": false, - "input": [ - "# Python 3 compat\n", - "import sys\n", - "if sys.version_info[0] >= 3:\n", - " raw_input = input" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 1 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "name = raw_input(\"What is your name? \")\n", - "name" - ], - "language": "python", + "data": { + "text/plain": [ + "'Sir Robin'" + ] + }, + "execution_count": 2, "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "What is your name? Sir Robin\n" - ] - }, - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 2, - "text": [ - "'Sir Robin'" - ] - } - ], - "prompt_number": 2 - }, + "output_type": "execute_result" + } + ], + "source": [ + "name = raw_input(\"What is your name? \")\n", + "name" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Python 2-only**: the eval input works as well (`input` is just `eval(raw_input(prompt))`)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**Python 2-only**: the eval input works as well (`input` is just `eval(raw_input(prompt))`)" + "name": "stdout", + "output_type": "stream", + "text": [ + "How many fingers? 4\n" ] }, { - "cell_type": "code", - "collapsed": false, - "input": [ - "fingers = input(\"How many fingers? \")\n", - "fingers, type(fingers)" - ], - "language": "python", + "data": { + "text/plain": [ + "(4, int)" + ] + }, + "execution_count": 3, "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "How many fingers? 4\n" - ] - }, - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 3, - "text": [ - "(4, int)" - ] - } - ], - "prompt_number": 3 - }, + "output_type": "execute_result" + } + ], + "source": [ + "fingers = input(\"How many fingers? \")\n", + "fingers, type(fingers)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "def div(x, y):\n", - " return x/y\n", - "\n", - "div(1,0)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "ename": "ZeroDivisionError", - "evalue": "integer division or modulo by zero", - "output_type": "pyerr", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", - "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mx\u001b[0m\u001b[1;33m/\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m \u001b[0mdiv\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[1;32m\u001b[0m in \u001b[0;36mdiv\u001b[1;34m(x, y)\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mdiv\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0my\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mx\u001b[0m\u001b[1;33m/\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 3\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[0mdiv\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[1;31mZeroDivisionError\u001b[0m: integer division or modulo by zero" - ] - } - ], - "prompt_number": 4 - }, + "ename": "ZeroDivisionError", + "evalue": "integer division or modulo by zero", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mZeroDivisionError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mx\u001b[0m\u001b[1;33m/\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m \u001b[0mdiv\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[1;32m\u001b[0m in \u001b[0;36mdiv\u001b[1;34m(x, y)\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mdiv\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0my\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 2\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mx\u001b[0m\u001b[1;33m/\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 3\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[0mdiv\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mZeroDivisionError\u001b[0m: integer division or modulo by zero" + ] + } + ], + "source": [ + "def div(x, y):\n", + " return x/y\n", + "\n", + "div(1,0)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": false + }, + "outputs": [ { - "cell_type": "code", - "collapsed": false, - "input": [ - "%debug" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "> \u001b[1;32m\u001b[0m(2)\u001b[0;36mdiv\u001b[1;34m()\u001b[0m\n", - "\u001b[1;32m 1 \u001b[1;33m\u001b[1;32mdef\u001b[0m \u001b[0mdiv\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0my\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1;32m----> 2 \u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mx\u001b[0m\u001b[1;33m/\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\u001b[1;32m 3 \u001b[1;33m\u001b[1;33m\u001b[0m\u001b[0m\n", - "\u001b[0m\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "ipdb> x\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "1\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "ipdb> y\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "0\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "ipdb> exit\n" - ] - } - ], - "prompt_number": 5 + "name": "stdout", + "output_type": "stream", + "text": [ + "> \u001b[1;32m\u001b[0m(2)\u001b[0;36mdiv\u001b[1;34m()\u001b[0m\n", + "\u001b[1;32m 1 \u001b[1;33m\u001b[1;32mdef\u001b[0m \u001b[0mdiv\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0my\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1;32m----> 2 \u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mx\u001b[0m\u001b[1;33m/\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\u001b[1;32m 3 \u001b[1;33m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[0m\n", + "ipdb> x\n", + "1\n", + "ipdb> y\n", + "0\n", + "ipdb> exit\n" + ] } ], - "metadata": {} + "source": [ + "%debug" + ] } - ] + ], + "metadata": { + "signature": "sha256:ac5c21534f3dd013c78d4d201527f3ed4dea5b6fad4116b8d23c67ba107e48c3" + }, + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Rich Output.ipynb b/examples/IPython Kernel/Rich Output.ipynb index 83fff95..c3e8f0b 100644 --- a/examples/IPython Kernel/Rich Output.ipynb +++ b/examples/IPython Kernel/Rich Output.ipynb @@ -1,1368 +1,3059 @@ { - "metadata": { - "name": "", - "signature": "sha256:cf83dc9e6288480ac94c44a5983b4ee421f0ade792a9fac64bc00719263386c0" - }, - "nbformat": 3, - "nbformat_minor": 0, - "worksheets": [ + "cells": [ { - "cells": [ - { - "cell_type": "heading", - "level": 1, - "metadata": {}, - "source": [ - "Rich Output" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In Python, objects can declare their textual representation using the `__repr__` method. IPython expands on this idea and allows objects to declare other, rich representations including:\n", - "\n", - "* HTML\n", - "* JSON\n", - "* PNG\n", - "* JPEG\n", - "* SVG\n", - "* LaTeX\n", - "\n", - "A single object can declare some or all of these representations; all are handled by IPython's *display system*. This Notebook shows how you can use this display system to incorporate a broad range of content into your Notebooks." - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Basic display imports" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `display` function is a general purpose tool for displaying different representations of objects. Think of it as `print` for these rich representations." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import display" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 1 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A few points:\n", - "\n", - "* Calling `display` on an object will send **all** possible representations to the Notebook.\n", - "* These representations are stored in the Notebook document.\n", - "* In general the Notebook will use the richest available representation.\n", - "\n", - "If you want to display a particular representation, there are specific functions for that:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import (\n", - " display_pretty, display_html, display_jpeg,\n", - " display_png, display_json, display_latex, display_svg\n", - ")" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 2 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Images" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To work with images (JPEG, PNG) use the `Image` class." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import Image" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 3 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "i = Image(filename='../images/ipython_logo.png')" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 4 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Returning an `Image` object from an expression will automatically display it:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "i" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "png": "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\n", - "prompt_number": 5, - "text": [ - "" - ] - } - ], - "prompt_number": 5 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Or you can pass an object with a rich representation to `display`:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "display(i)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "display_data", - "png": "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\n", - "text": [ - "" - ] - } - ], - "prompt_number": 6 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "An image can also be displayed from raw data or a URL." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "Image(url='http://python.org/images/python-logo.gif')" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "html": [ - "" - ], - "metadata": {}, - "output_type": "pyout", - "prompt_number": 7, - "text": [ - "" - ] - } - ], - "prompt_number": 7 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "SVG images are also supported out of the box." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import SVG\n", - "SVG(filename='../images/python_logo.svg')" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "metadata": {}, - "output_type": "pyout", - "prompt_number": 8, - "svg": [ - "\n", - " \n", - " \n", - " \n", - " image/svg+xml\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "" - ], - "text": [ - "" - ] - } - ], - "prompt_number": 8 - }, - { - "cell_type": "heading", - "level": 3, - "metadata": {}, - "source": [ - "Embedded vs non-embedded Images" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "By default, image data is embedded in the notebook document so that the images can be viewed offline. However it is also possible to tell the `Image` class to only store a *link* to the image. Let's see how this works using a webcam at Berkeley." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import Image\n", - "img_url = 'http://www.lawrencehallofscience.org/static/scienceview/scienceview.berkeley.edu/html/view/view_assets/images/newview.jpg'\n", - "\n", - "# by default Image data are embedded\n", - "Embed = Image(img_url)\n", - "\n", - "# if kwarg `url` is given, the embedding is assumed to be false\n", - "SoftLinked = Image(url=img_url)\n", - "\n", - "# In each case, embed can be specified explicitly with the `embed` kwarg\n", - "# ForceEmbed = Image(url=img_url, embed=True)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 9 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here is the embedded version. Note that this image was pulled from the webcam when this code cell was originally run and stored in the Notebook. Unless we rerun this cell, this is not todays image." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "Embed" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "jpeg": "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\nAFT+//9fBAAAZgQAAGYEAAAJBAAABwQAAAoEAAAKBAAADAQAABAEAAAEBAAA//8AAAAAAADABQAA\nKAIAAFQBAACAAAAAbwQAAKkBAAAJAQAAYwAAAAAAAAAAAAAAAwAAAAMAAAACAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAANAAAACMlrWmAABAAJAAkAIAqY\nB2QAZAASABIAEgASABIAEgASABIAEgASABIAEgASABIAEgASABIAEgDu/wAAEgDu/wAAEgDu/wAA\nEgDu/+7/7v8AAAAAAAASABIAEgAQAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAACAAAQAAAAIAAgACAAIAAAAAAAAAAAAAAAAAAAAAAAAAigABAAAABAAIAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAoAEAAAAAEAAIAAEAAQCAAuABAAAAAAAAAAAAAAgAgAEAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAoAAQAAAAAAAABF1Yvq/udUJVCJsJVgaq7+\nSUkqAN4CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAEAAgAEAAAAUjk4\nAAIABwAEAAAAMDEwMAEQAwABAAAAIAoAAAIQAwABAAAAmAcAAAAAAAAAjScAJAEAAMCpHQDbAAAA\nQBEAAEARAAAGAAMBAwABAAAABgAAABoBBQABAAAAPA0AABsBBQABAAAARA0AACgBAwABAAAAAgAA\nAAECBAABAAAA9BMAAAICBAABAAAA2wkAAAAAAAC0AAAAAQAAALQAAAABAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/9j/2wCEAAkGBggGBQkIBwgK\nCQkLDRYPDQwMDRwTFRAWIR0jIiEcIB8kKTQsJCcxJx4fLT0tMTY3Ojo6Iio/RD44QjM3OTYBCQkJ\nDAoMFAwMFA8KCgoPGhoKChoaTxoaGhoaT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09P\nT09PT//AABEIAHgAoAMBIQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsB\nAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKCxAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEG\nE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVW\n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\n", - "metadata": {}, - "output_type": "pyout", - "prompt_number": 10, - "text": [ - "" - ] - } - ], - "prompt_number": 10 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here is today's image from same webcam at Berkeley, (refreshed every minutes, if you reload the notebook), visible only with an active internet connection, that should be different from the previous one. Notebooks saved with this kind of image will be smaller and always reflect the current version of the source, but the image won't display offline." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "SoftLinked" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "html": [ - "" - ], - "metadata": {}, - "output_type": "pyout", - "prompt_number": 11, - "text": [ - "" - ] - } - ], - "prompt_number": 11 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Of course, if you re-run this Notebook, the two images will be the same again." - ] - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "HTML" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Python objects can declare HTML representations that will be displayed in the Notebook. If you have some HTML you want to display, simply use the `HTML` class." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import HTML" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 12 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "s = \"\"\"
DateOpenHighLowCloseVolumeAdj Close
0 2012-06-01 569.16 590.00
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
Header 1Header 2
row 1, cell 1row 1, cell 2
row 2, cell 1row 2, cell 2
\"\"\"" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 13 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "h = HTML(s)" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 14 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "display(h)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
Header 1Header 2
row 1, cell 1row 1, cell 2
row 2, cell 1row 2, cell 2
" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "" - ] - } - ], - "prompt_number": 15 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can also use the `%%html` cell magic to accomplish the same thing." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%html\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
Header 1Header 2
row 1, cell 1row 1, cell 2
row 2, cell 1row 2, cell 2
" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
Header 1Header 2
row 1, cell 1row 1, cell 2
row 2, cell 1row 2, cell 2
" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "" - ] - } - ], - "prompt_number": 16 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "JavaScript" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The Notebook also enables objects to declare a JavaScript representation. At first, this may seem odd as output is inherently visual and JavaScript is a programming language. However, this opens the door for rich output that leverages the full power of JavaScript and associated libraries such as [d3.js](http://d3js.org) for output." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import Javascript" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 17 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Pass a string of JavaScript source code to the `JavaScript` object and then display it." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "js = Javascript('alert(\"hi\")');" - ], - "language": "python", - "metadata": {}, - "outputs": [], - "prompt_number": 18 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "display(js)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "javascript": [ - "alert(\"hi\")" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "" - ] - } - ], - "prompt_number": 19 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The same thing can be accomplished using the `%%javascript` cell magic:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%javascript\n", - "\n", - "alert(\"hi\");" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "javascript": [ - "\n", - "alert(\"hi\");" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "" - ] - } - ], - "prompt_number": 20 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here is a more complicated example that loads `d3.js` from a CDN, uses the `%%html` magic to load CSS styles onto the page and then runs ones of the `d3.js` examples." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "Javascript(\n", - " \"\"\"$.getScript('//cdnjs.cloudflare.com/ajax/libs/d3/3.2.2/d3.v3.min.js')\"\"\"\n", - ")" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "javascript": [ - "$.getScript('//cdnjs.cloudflare.com/ajax/libs/d3/3.2.2/d3.v3.min.js')" - ], - "metadata": {}, - "output_type": "pyout", - "prompt_number": 21, - "text": [ - "" - ] - } - ], - "prompt_number": 21 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%html\n", - "" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "html": [ - "" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "" - ] - } - ], - "prompt_number": 22 - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%javascript\n", - "\n", - "// element is the jQuery element we will append to\n", - "var e = element.get(0);\n", - " \n", - "var diameter = 600,\n", - " format = d3.format(\",d\");\n", - "\n", - "var pack = d3.layout.pack()\n", - " .size([diameter - 4, diameter - 4])\n", - " .value(function(d) { return d.size; });\n", - "\n", - "var svg = d3.select(e).append(\"svg\")\n", - " .attr(\"width\", diameter)\n", - " .attr(\"height\", diameter)\n", - " .append(\"g\")\n", - " .attr(\"transform\", \"translate(2,2)\");\n", - "\n", - "d3.json(\"data/flare.json\", function(error, root) {\n", - " var node = svg.datum(root).selectAll(\".node\")\n", - " .data(pack.nodes)\n", - " .enter().append(\"g\")\n", - " .attr(\"class\", function(d) { return d.children ? \"node\" : \"leaf node\"; })\n", - " .attr(\"transform\", function(d) { return \"translate(\" + d.x + \",\" + d.y + \")\"; });\n", - "\n", - " node.append(\"title\")\n", - " .text(function(d) { return d.name + (d.children ? \"\" : \": \" + format(d.size)); });\n", - "\n", - " node.append(\"circle\")\n", - " .attr(\"r\", function(d) { return d.r; });\n", - "\n", - " node.filter(function(d) { return !d.children; }).append(\"text\")\n", - " .attr(\"dy\", \".3em\")\n", - " .style(\"text-anchor\", \"middle\")\n", - " .text(function(d) { return d.name.substring(0, d.r / 3); });\n", - "});\n", - "\n", - "d3.select(self.frameElement).style(\"height\", diameter + \"px\");" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "javascript": [ - "\n", - "// element is the jQuery element we will append to\n", - "var e = element.get(0);\n", - " \n", - "var diameter = 600,\n", - " format = d3.format(\",d\");\n", - "\n", - "var pack = d3.layout.pack()\n", - " .size([diameter - 4, diameter - 4])\n", - " .value(function(d) { return d.size; });\n", - "\n", - "var svg = d3.select(e).append(\"svg\")\n", - " .attr(\"width\", diameter)\n", - " .attr(\"height\", diameter)\n", - " .append(\"g\")\n", - " .attr(\"transform\", \"translate(2,2)\");\n", - "\n", - "d3.json(\"data/flare.json\", function(error, root) {\n", - " var node = svg.datum(root).selectAll(\".node\")\n", - " .data(pack.nodes)\n", - " .enter().append(\"g\")\n", - " .attr(\"class\", function(d) { return d.children ? \"node\" : \"leaf node\"; })\n", - " .attr(\"transform\", function(d) { return \"translate(\" + d.x + \",\" + d.y + \")\"; });\n", - "\n", - " node.append(\"title\")\n", - " .text(function(d) { return d.name + (d.children ? \"\" : \": \" + format(d.size)); });\n", - "\n", - " node.append(\"circle\")\n", - " .attr(\"r\", function(d) { return d.r; });\n", - "\n", - " node.filter(function(d) { return !d.children; }).append(\"text\")\n", - " .attr(\"dy\", \".3em\")\n", - " .style(\"text-anchor\", \"middle\")\n", - " .text(function(d) { return d.name.substring(0, d.r / 3); });\n", - "});\n", - "\n", - "d3.select(self.frameElement).style(\"height\", diameter + \"px\");" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "" - ] - } - ], - "prompt_number": 23 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "LaTeX" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The IPython display system also has builtin support for the display of mathematical expressions typeset in LaTeX, which is rendered in the browser using [MathJax](http://mathjax.org)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can pass raw LaTeX test as a string to the `Math` object:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import Math\n", - "Math(r'F(k) = \\int_{-\\infty}^{\\infty} f(x) e^{2\\pi i k} dx')" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "latex": [ - "$$F(k) = \\int_{-\\infty}^{\\infty} f(x) e^{2\\pi i k} dx$$" - ], - "metadata": {}, - "output_type": "pyout", - "prompt_number": 24, - "text": [ - "" - ] - } - ], - "prompt_number": 24 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "With the `Latex` class, you have to include the delimiters yourself. This allows you to use other LaTeX modes such as `eqnarray`:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import Latex\n", - "Latex(r\"\"\"\\begin{eqnarray}\n", - "\\nabla \\times \\vec{\\mathbf{B}} -\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{E}}}{\\partial t} & = \\frac{4\\pi}{c}\\vec{\\mathbf{j}} \\\\\n", - "\\nabla \\cdot \\vec{\\mathbf{E}} & = 4 \\pi \\rho \\\\\n", - "\\nabla \\times \\vec{\\mathbf{E}}\\, +\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{B}}}{\\partial t} & = \\vec{\\mathbf{0}} \\\\\n", - "\\nabla \\cdot \\vec{\\mathbf{B}} & = 0 \n", - "\\end{eqnarray}\"\"\")" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "latex": [ - "\\begin{eqnarray}\n", - "\\nabla \\times \\vec{\\mathbf{B}} -\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{E}}}{\\partial t} & = \\frac{4\\pi}{c}\\vec{\\mathbf{j}} \\\\\n", - "\\nabla \\cdot \\vec{\\mathbf{E}} & = 4 \\pi \\rho \\\\\n", - "\\nabla \\times \\vec{\\mathbf{E}}\\, +\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{B}}}{\\partial t} & = \\vec{\\mathbf{0}} \\\\\n", - "\\nabla \\cdot \\vec{\\mathbf{B}} & = 0 \n", - "\\end{eqnarray}" - ], - "metadata": {}, - "output_type": "pyout", - "prompt_number": 25, - "text": [ - "" - ] - } - ], - "prompt_number": 25 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Or you can enter LaTeX directly with the `%%latex` cell magic:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "%%latex\n", - "\\begin{align}\n", - "\\nabla \\times \\vec{\\mathbf{B}} -\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{E}}}{\\partial t} & = \\frac{4\\pi}{c}\\vec{\\mathbf{j}} \\\\\n", - "\\nabla \\cdot \\vec{\\mathbf{E}} & = 4 \\pi \\rho \\\\\n", - "\\nabla \\times \\vec{\\mathbf{E}}\\, +\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{B}}}{\\partial t} & = \\vec{\\mathbf{0}} \\\\\n", - "\\nabla \\cdot \\vec{\\mathbf{B}} & = 0\n", - "\\end{align}" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "latex": [ - "\\begin{align}\n", - "\\nabla \\times \\vec{\\mathbf{B}} -\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{E}}}{\\partial t} & = \\frac{4\\pi}{c}\\vec{\\mathbf{j}} \\\\\n", - "\\nabla \\cdot \\vec{\\mathbf{E}} & = 4 \\pi \\rho \\\\\n", - "\\nabla \\times \\vec{\\mathbf{E}}\\, +\\, \\frac1c\\, \\frac{\\partial\\vec{\\mathbf{B}}}{\\partial t} & = \\vec{\\mathbf{0}} \\\\\n", - "\\nabla \\cdot \\vec{\\mathbf{B}} & = 0\n", - "\\end{align}" - ], - "metadata": {}, - "output_type": "display_data", - "text": [ - "" - ] - } - ], - "prompt_number": 26 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Audio" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "IPython makes it easy to work with sounds interactively. The `Audio` display class allows you to create an audio control that is embedded in the Notebook. The interface is analogous to the interface of the `Image` display class. All audio formats supported by the browser can be used. Note that no single format is presently supported in all browsers." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import Audio\n", - "Audio(url=\"http://www.nch.com.au/acm/8k16bitpcm.wav\")" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "html": [ - "\n", - " \n", - " " - ], - "metadata": {}, - "output_type": "pyout", - "prompt_number": 27, - "text": [ - "" - ] - } - ], - "prompt_number": 27 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "A NumPy array can be auralized automatically. The `Audio` class normalizes and encodes the data and embeds the resulting audio in the Notebook.\n", - "\n", - "For instance, when two sine waves with almost the same frequency are superimposed a phenomena known as [beats](https://en.wikipedia.org/wiki/Beat_%28acoustics%29) occur. This can be auralised as follows:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "import numpy as np\n", - "max_time = 3\n", - "f1 = 220.0\n", - "f2 = 224.0\n", - "rate = 8000.0\n", - "L = 3\n", - "times = np.linspace(0,L,rate*L)\n", - "signal = np.sin(2*np.pi*f1*times) + np.sin(2*np.pi*f2*times)\n", - "\n", - "Audio(data=signal, rate=rate)" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "html": [ - "\n", - " \n", - " " - ], - "metadata": {}, - "output_type": "pyout", - "prompt_number": 28, - "text": [ - "" - ] - } - ], - "prompt_number": 28 - }, - { - "cell_type": "heading", - "level": 2, - "metadata": {}, - "source": [ - "Video" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "More exotic objects can also be displayed, as long as their representation supports the IPython display protocol. For example, videos hosted externally on YouTube are easy to load:" - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import YouTubeVideo\n", - "YouTubeVideo('sjfsUzECqK0')" - ], - "language": "python", - "metadata": {}, - "outputs": [ - { - "html": [ - "\n", - " \n", - " " - ], - "metadata": {}, - "output_type": "pyout", - "prompt_number": 29, - "text": [ - "" - ] - } - ], - "prompt_number": 29 - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Using the nascent video capabilities of modern browsers, you may also be able to display local\n", - "videos. At the moment this doesn't work very well in all browsers, so it may or may not work for you;\n", - "we will continue testing this and looking for ways to make it more robust. \n", - "\n", - "The following cell loads a local file called `animation.m4v`, encodes the raw video as base64 for http\n", - "transport, and uses the HTML5 video tag to load it. On Chrome 15 it works correctly, displaying a control bar at the bottom with a play/pause button and a location slider." - ] - }, - { - "cell_type": "code", - "collapsed": false, - "input": [ - "from IPython.display import HTML\n", - "from base64 import b64encode\n", - "video = open(\"../images/animation.m4v\", \"rb\").read()\n", - "video_encoded = b64encode(video).decode('ascii')\n", - "video_tag = '