ipython_directive.py
1276 lines
| 44.1 KiB
| text/x-python
|
PythonLexer
|
r2439 | # -*- coding: utf-8 -*- | ||
|
r13901 | """ | ||
Sphinx directive to support embedded IPython code. | ||||
|
r2439 | |||
|
r24591 | IPython provides an extension for `Sphinx <http://www.sphinx-doc.org/>`_ to | ||
highlight and run code. | ||||
|
r2439 | This directive allows pasting of entire interactive IPython sessions, prompts | ||
and all, and their code will actually get re-executed at doc build time, with | ||||
|
r4432 | all prompts renumbered sequentially. It also allows you to input code as a pure | ||
python input by giving the argument python to the directive. The output looks | ||||
like an interactive ipython section. | ||||
|
r2439 | |||
|
r24591 | Here is an example of how the IPython directive can | ||
**run** python code, at build time. | ||||
.. ipython:: | ||||
In [1]: 1+1 | ||||
In [1]: import datetime | ||||
|
r27687 | ...: datetime.date.fromisoformat('2022-02-22') | ||
|
r24591 | |||
It supports IPython construct that plain | ||||
Python does not understand (like magics): | ||||
.. ipython:: | ||||
In [0]: import time | ||||
|
r27687 | In [0]: %pdoc time.sleep | ||
|
r24591 | |||
This will also support top-level async when using IPython 7.0+ | ||||
.. ipython:: | ||||
In [2]: import asyncio | ||||
...: print('before') | ||||
...: await asyncio.sleep(1) | ||||
...: print('after') | ||||
The namespace will persist across multiple code chucks, Let's define a variable: | ||||
.. ipython:: | ||||
In [0]: who = "World" | ||||
And now say hello: | ||||
.. ipython:: | ||||
In [0]: print('Hello,', who) | ||||
If the current section raises an exception, you can add the ``:okexcept:`` flag | ||||
to the current block, otherwise the build will fail. | ||||
.. ipython:: | ||||
:okexcept: | ||||
In [1]: 1/0 | ||||
IPython Sphinx directive module | ||||
=============================== | ||||
|
r2439 | To enable this directive, simply list it in your Sphinx ``conf.py`` file | ||
(making sure the directory where you placed it is visible to sphinx, as is | ||||
|
r13658 | needed for all Sphinx directives). For example, to enable syntax highlighting | ||
and the IPython directive:: | ||||
extensions = ['IPython.sphinxext.ipython_console_highlighting', | ||||
'IPython.sphinxext.ipython_directive'] | ||||
|
r2439 | |||
|
r13660 | The IPython directive outputs code-blocks with the language 'ipython'. So | ||
if you do not have the syntax highlighting extension enabled as well, then | ||||
all rendered code-blocks will be uncolored. By default this directive assumes | ||||
that your prompts are unchanged IPython ones, but this can be customized. | ||||
The configurable options that can be placed in conf.py are: | ||||
|
r4432 | |||
ipython_savefig_dir: | ||||
The directory in which to save the figures. This is relative to the | ||||
Sphinx source directory. The default is `html_static_path`. | ||||
ipython_rgxin: | ||||
The compiled regular expression to denote the start of IPython input | ||||
|
r24896 | lines. The default is ``re.compile('In \\[(\\d+)\\]:\\s?(.*)\\s*')``. You | ||
|
r4432 | shouldn't need to change this. | ||
|
r24714 | ipython_warning_is_error: [default to True] | ||
Fail the build if something unexpected happen, for example if a block raise | ||||
an exception but does not have the `:okexcept:` flag. The exact behavior of | ||||
what is considered strict, may change between the sphinx directive version. | ||||
|
r4432 | ipython_rgxout: | ||
The compiled regular expression to denote the start of IPython output | ||||
|
r24896 | lines. The default is ``re.compile('Out\\[(\\d+)\\]:\\s?(.*)\\s*')``. You | ||
|
r4432 | shouldn't need to change this. | ||
ipython_promptin: | ||||
The string to represent the IPython input prompt in the generated ReST. | ||||
|
r24591 | The default is ``'In [%d]:'``. This expects that the line numbers are used | ||
|
r4432 | in the prompt. | ||
ipython_promptout: | ||||
The string to represent the IPython prompt in the generated ReST. The | ||||
|
r24591 | default is ``'Out [%d]:'``. This expects that the line numbers are used | ||
|
r4432 | in the prompt. | ||
|
r13658 | ipython_mplbackend: | ||
The string which specifies if the embedded Sphinx shell should import | ||||
|
r13801 | Matplotlib and set the backend. The value specifies a backend that is | ||
|
r13810 | passed to `matplotlib.use()` before any lines in `ipython_execlines` are | ||
|
r13873 | executed. If not specified in conf.py, then the default value of 'agg' is | ||
used. To use the IPython directive without matplotlib as a dependency, set | ||||
the value to `None`. It may end up that matplotlib is still imported | ||||
if the user specifies so in `ipython_execlines` or makes use of the | ||||
@savefig pseudo decorator. | ||||
|
r13659 | ipython_execlines: | ||
|
r13873 | A list of strings to be exec'd in the embedded Sphinx shell. Typical | ||
|
r13801 | usage is to make certain packages always available. Set this to an empty | ||
list if you wish to have no imports always available. If specified in | ||||
|
r24591 | ``conf.py`` as `None`, then it has the effect of making no imports available. | ||
|
r13873 | If omitted from conf.py altogether, then the default value of | ||
['import numpy as np', 'import matplotlib.pyplot as plt'] is used. | ||||
|
r13902 | ipython_holdcount | ||
When the @suppress pseudo-decorator is used, the execution count can be | ||||
incremented or not. The default behavior is to hold the execution count, | ||||
corresponding to a value of `True`. Set this to `False` to increment | ||||
the execution count after each suppressed command. | ||||
|
r13658 | |||
As an example, to use the IPython directive when `matplotlib` is not available, | ||||
|
r13876 | one sets the backend to `None`:: | ||
|
r13658 | |||
|
r13876 | ipython_mplbackend = None | ||
|
r13658 | |||
|
r13660 | An example usage of the directive is: | ||
.. code-block:: rst | ||||
.. ipython:: | ||||
In [1]: x = 1 | ||||
In [2]: y = x**2 | ||||
In [3]: print(y) | ||||
|
r13873 | See http://matplotlib.org/sampledoc/ipython_directive.html for additional | ||
|
r13660 | documentation. | ||
|
r2439 | |||
|
r14936 | Pseudo-Decorators | ||
================= | ||||
|
r16646 | Note: Only one decorator is supported per input. If more than one decorator | ||
is specified, then only the last one is used. | ||||
|
r14936 | In addition to the Pseudo-Decorators/options described at the above link, | ||
|
r16646 | several enhancements have been made. The directive will emit a message to the | ||
console at build-time if code-execution resulted in an exception or warning. | ||||
You can suppress these on a per-block basis by specifying the :okexcept: | ||||
or :okwarning: options: | ||||
|
r14936 | |||
.. code-block:: rst | ||||
.. ipython:: | ||||
:okexcept: | ||||
:okwarning: | ||||
In [1]: 1/0 | ||||
In [2]: # raise warning. | ||||
|
r24009 | To Do | ||
|
r24591 | ===== | ||
|
r2439 | |||
- Turn the ad-hoc test() function into a real test suite. | ||||
- Break up ipython-specific functionality from matplotlib stuff into better | ||||
separated code. | ||||
""" | ||||
|
r24591 | # Authors | ||
# ======= | ||||
# | ||||
# - John D Hunter: original author. | ||||
# - Fernando Perez: refactoring, documentation, cleanups, port to 0.11. | ||||
# - VáclavŠmilauer <eudoxos-AT-arcig.cz>: Prompt generalizations. | ||||
# - Skipper Seabold, refactoring, cleanups, pure python addition | ||||
|
r2439 | #----------------------------------------------------------------------------- | ||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
# Stdlib | ||||
|
r19719 | import atexit | ||
|
r23221 | import errno | ||
|
r2439 | import os | ||
|
r25034 | import pathlib | ||
|
r2439 | import re | ||
import sys | ||||
|
r4432 | import tempfile | ||
|
r6136 | import ast | ||
|
r14779 | import warnings | ||
|
r19719 | import shutil | ||
|
r23090 | from io import StringIO | ||
|
r2439 | |||
# Third-party | ||||
from docutils.parsers.rst import directives | ||||
|
r23778 | from docutils.parsers.rst import Directive | ||
|
r26606 | from sphinx.util import logging | ||
|
r2439 | |||
# Our own | ||||
|
r21295 | from traitlets.config import Config | ||
from IPython import InteractiveShell | ||||
|
r4432 | from IPython.core.profiledir import ProfileDir | ||
|
r2439 | |||
|
r25167 | use_matplotlib = False | ||
|
r24591 | try: | ||
import matplotlib | ||||
|
r25167 | use_matplotlib = True | ||
|
r24591 | except Exception: | ||
pass | ||||
|
r2439 | #----------------------------------------------------------------------------- | ||
# Globals | ||||
#----------------------------------------------------------------------------- | ||||
|
r4432 | # for tokenizing blocks | ||
|
r2439 | COMMENT, INPUT, OUTPUT = range(3) | ||
|
r27539 | PSEUDO_DECORATORS = ["suppress", "verbatim", "savefig", "doctest"] | ||
|
r2439 | #----------------------------------------------------------------------------- | ||
# Functions and class declarations | ||||
#----------------------------------------------------------------------------- | ||||
|
r13680 | |||
|
r4432 | def block_parser(part, rgxin, rgxout, fmtin, fmtout): | ||
|
r2439 | """ | ||
part is a string of ipython text, comprised of at most one | ||||
|
r21437 | input, one output, comments, and blank lines. The block parser | ||
|
r2439 | parses the text into a list of:: | ||
blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...] | ||||
where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and | ||||
data is, depending on the type of token:: | ||||
COMMENT : the comment string | ||||
INPUT: the (DECORATOR, INPUT_LINE, REST) where | ||||
DECORATOR: the input decorator (or None) | ||||
INPUT_LINE: the input as string (possibly multi-line) | ||||
REST : any stdout generated by the input line (not OUTPUT) | ||||
OUTPUT: the output string, possibly multi-line | ||||
|
r13658 | """ | ||
|
r2439 | block = [] | ||
lines = part.split('\n') | ||||
N = len(lines) | ||||
i = 0 | ||||
decorator = None | ||||
while 1: | ||||
if i==N: | ||||
# nothing left to parse -- the last line | ||||
break | ||||
line = lines[i] | ||||
i += 1 | ||||
line_stripped = line.strip() | ||||
if line_stripped.startswith('#'): | ||||
block.append((COMMENT, line)) | ||||
continue | ||||
|
r27539 | if any( | ||
|
r27540 | line_stripped.startswith("@" + pseudo_decorator) | ||
for pseudo_decorator in PSEUDO_DECORATORS | ||||
): | ||||
|
r27539 | if decorator: | ||
|
r27540 | raise RuntimeError( | ||
"Applying multiple pseudo-decorators on one line is not supported" | ||||
) | ||||
|
r27539 | else: | ||
decorator = line_stripped | ||||
continue | ||||
|
r2439 | |||
# does this look like an input line? | ||||
matchin = rgxin.match(line) | ||||
if matchin: | ||||
lineno, inputline = int(matchin.group(1)), matchin.group(2) | ||||
# the ....: continuation string | ||||
continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2)) | ||||
Nc = len(continuation) | ||||
# input lines can continue on for more than one line, if | ||||
# we have a '\' line continuation char or a function call | ||||
# echo line 'print'. The input line can only be | ||||
# terminated by the end of the block or an output line, so | ||||
# we parse out the rest of the input line if it is | ||||
# multiline as well as any echo text | ||||
rest = [] | ||||
while i<N: | ||||
# look ahead; if the next line is blank, or a comment, or | ||||
# an output line, we're done | ||||
nextline = lines[i] | ||||
matchout = rgxout.match(nextline) | ||||
|
r28756 | # print("nextline=%s, continuation=%s, starts=%s"%(nextline, continuation, nextline.startswith(continuation))) | ||
|
r2439 | if matchout or nextline.startswith('#'): | ||
break | ||||
elif nextline.startswith(continuation): | ||||
|
r14812 | # The default ipython_rgx* treat the space following the colon as optional. | ||
# However, If the space is there we must consume it or code | ||||
# employing the cython_magic extension will fail to execute. | ||||
# | ||||
# This works with the default ipython_rgx* patterns, | ||||
# If you modify them, YMMV. | ||||
|
r14213 | nextline = nextline[Nc:] | ||
if nextline and nextline[0] == ' ': | ||||
nextline = nextline[1:] | ||||
inputline += '\n' + nextline | ||||
|
r2439 | else: | ||
rest.append(nextline) | ||||
i+= 1 | ||||
block.append((INPUT, (decorator, inputline, '\n'.join(rest)))) | ||||
continue | ||||
# if it looks like an output line grab all the text to the end | ||||
# of the block | ||||
matchout = rgxout.match(line) | ||||
if matchout: | ||||
lineno, output = int(matchout.group(1)), matchout.group(2) | ||||
if i<N-1: | ||||
output = '\n'.join([output] + lines[i:]) | ||||
block.append((OUTPUT, output)) | ||||
break | ||||
return block | ||||
|
r13843 | |||
|
r2439 | class EmbeddedSphinxShell(object): | ||
"""An embedded IPython instance to run inside Sphinx""" | ||||
|
r14799 | def __init__(self, exec_lines=None): | ||
|
r2439 | |||
|
r13366 | self.cout = StringIO() | ||
|
r2439 | |||
|
r13659 | if exec_lines is None: | ||
|
r13801 | exec_lines = [] | ||
|
r2439 | |||
# Create config object for IPython | ||||
config = Config() | ||||
|
r21728 | config.HistoryManager.hist_file = ':memory:' | ||
|
r2439 | config.InteractiveShell.autocall = False | ||
config.InteractiveShell.autoindent = False | ||||
config.InteractiveShell.colors = 'NoColor' | ||||
|
r4432 | # create a profile so instance history isn't saved | ||
tmp_profile_dir = tempfile.mkdtemp(prefix='profile_') | ||||
profname = 'auto_profile_sphinx_build' | ||||
pdir = os.path.join(tmp_profile_dir,profname) | ||||
profile = ProfileDir.create_profile_dir(pdir) | ||||
|
r13674 | # Create and initialize global ipython, but don't start its mainloop. | ||
|
r23804 | # This will persist across different EmbeddedSphinxShell instances. | ||
|
r21328 | IP = InteractiveShell.instance(config=config, profile_dir=profile) | ||
|
r21437 | atexit.register(self.cleanup) | ||
|
r13674 | |||
|
r2439 | # Store a few parts of IPython we'll need. | ||
|
r2499 | self.IP = IP | ||
|
r2439 | self.user_ns = self.IP.user_ns | ||
self.user_global_ns = self.IP.user_global_ns | ||||
|
r4432 | |||
|
r2439 | self.input = '' | ||
self.output = '' | ||||
|
r19719 | self.tmp_profile_dir = tmp_profile_dir | ||
|
r2439 | |||
self.is_verbatim = False | ||||
self.is_doctest = False | ||||
self.is_suppress = False | ||||
|
r13863 | # Optionally, provide more detailed information to shell. | ||
|
r14799 | # this is assigned by the SetUp method of IPythonDirective | ||
# to point at itself. | ||||
# | ||||
# So, you can access handy things at self.directive.state | ||||
|
r13863 | self.directive = None | ||
|
r2439 | # on the first call to the savefig decorator, we'll import | ||
# pyplot as plt so we can make a call to the plt.gcf().savefig | ||||
self._pyplot_imported = False | ||||
|
r13674 | # Prepopulate the namespace. | ||
for line in exec_lines: | ||||
self.process_input_line(line, store_history=False) | ||||
|
r19719 | def cleanup(self): | ||
shutil.rmtree(self.tmp_profile_dir, ignore_errors=True) | ||||
|
r4432 | def clear_cout(self): | ||
|
r2439 | self.cout.seek(0) | ||
self.cout.truncate(0) | ||||
|
r24710 | def process_input_line(self, line, store_history): | ||
return self.process_input_lines([line], store_history=store_history) | ||||
def process_input_lines(self, lines, store_history=True): | ||||
|
r2439 | """process the input, capturing stdout""" | ||
stdout = sys.stdout | ||||
|
r24710 | source_raw = '\n'.join(lines) | ||
|
r2439 | try: | ||
sys.stdout = self.cout | ||||
|
r24710 | self.IP.run_cell(source_raw, store_history=store_history) | ||
|
r2439 | finally: | ||
sys.stdout = stdout | ||||
|
r4432 | def process_image(self, decorator): | ||
""" | ||||
# build out an image directive like | ||||
# .. image:: somefile.png | ||||
# :width 4in | ||||
# | ||||
# from an input like | ||||
# savefig somefile.png width=4in | ||||
""" | ||||
savefig_dir = self.savefig_dir | ||||
source_dir = self.source_dir | ||||
saveargs = decorator.split(' ') | ||||
filename = saveargs[1] | ||||
|
r25034 | # insert relative path to image file in source | ||
# as absolute path for Sphinx | ||||
# sphinx expects a posix path, even on Windows | ||||
|
r25768 | path = pathlib.Path(savefig_dir, filename) | ||
outfile = '/' + path.relative_to(source_dir).as_posix() | ||||
|
r4432 | |||
|
r25034 | imagerows = ['.. image:: %s' % outfile] | ||
|
r4432 | |||
for kwarg in saveargs[2:]: | ||||
arg, val = kwarg.split('=') | ||||
arg = arg.strip() | ||||
val = val.strip() | ||||
imagerows.append(' :%s: %s'%(arg, val)) | ||||
image_file = os.path.basename(outfile) # only return file name | ||||
image_directive = '\n'.join(imagerows) | ||||
return image_file, image_directive | ||||
|
r2439 | # Callbacks for each type of token | ||
def process_input(self, data, input_prompt, lineno): | ||||
|
r13863 | """ | ||
Process data block for INPUT token. | ||||
""" | ||||
|
r2439 | decorator, input, rest = data | ||
image_file = None | ||||
|
r4432 | image_directive = None | ||
|
r13863 | |||
|
r2439 | is_verbatim = decorator=='@verbatim' or self.is_verbatim | ||
|
r13680 | is_doctest = (decorator is not None and \ | ||
decorator.startswith('@doctest')) or self.is_doctest | ||||
|
r2439 | is_suppress = decorator=='@suppress' or self.is_suppress | ||
|
r14779 | is_okexcept = decorator=='@okexcept' or self.is_okexcept | ||
is_okwarning = decorator=='@okwarning' or self.is_okwarning | ||||
|
r2439 | is_savefig = decorator is not None and \ | ||
decorator.startswith('@savefig') | ||||
input_lines = input.split('\n') | ||||
|
r6135 | if len(input_lines) > 1: | ||
if input_lines[-1] != "": | ||||
input_lines.append('') # make sure there's a blank line | ||||
# so splitter buffer gets reset | ||||
|
r2439 | |||
continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2)) | ||||
if is_savefig: | ||||
|
r4432 | image_file, image_directive = self.process_image(decorator) | ||
|
r2439 | |||
ret = [] | ||||
is_semicolon = False | ||||
|
r13902 | # Hold the execution count, if requested to do so. | ||
if is_suppress and self.hold_count: | ||||
store_history = False | ||||
else: | ||||
store_history = True | ||||
|
r14779 | # Note: catch_warnings is not thread safe | ||
with warnings.catch_warnings(record=True) as ws: | ||||
|
r24710 | if input_lines[0].endswith(';'): | ||
is_semicolon = True | ||||
#for i, line in enumerate(input_lines): | ||||
# process the first input line | ||||
if is_verbatim: | ||||
self.process_input_lines(['']) | ||||
self.IP.execution_count += 1 # increment it anyway | ||||
else: | ||||
# only submit the line in non-verbatim mode | ||||
self.process_input_lines(input_lines, store_history=store_history) | ||||
|
r14779 | |||
|
r24710 | if not is_suppress: | ||
for i, line in enumerate(input_lines): | ||||
|
r14779 | if i == 0: | ||
formatted_line = '%s %s'%(input_prompt, line) | ||||
|
r2439 | else: | ||
|
r14779 | formatted_line = '%s %s'%(continuation, line) | ||
|
r24710 | ret.append(formatted_line) | ||
|
r2439 | |||
|
r6135 | if not is_suppress and len(rest.strip()) and is_verbatim: | ||
|
r16648 | # The "rest" is the standard output of the input. This needs to be | ||
# added when in verbatim mode. If there is no "rest", then we don't | ||||
# add it, as the new line will be added by the processed output. | ||||
|
r6135 | ret.append(rest) | ||
|
r2439 | |||
|
r16646 | # Fetch the processed output. (This is not the submitted output.) | ||
|
r2439 | self.cout.seek(0) | ||
|
r16646 | processed_output = self.cout.read() | ||
|
r2439 | if not is_suppress and not is_semicolon: | ||
|
r16646 | # | ||
# In IPythonDirective.run, the elements of `ret` are eventually | ||||
# combined such that '' entries correspond to newlines. So if | ||||
# `processed_output` is equal to '', then the adding it to `ret` | ||||
# ensures that there is a blank line between consecutive inputs | ||||
# that have no outputs, as in: | ||||
# | ||||
# In [1]: x = 4 | ||||
# | ||||
# In [2]: x = 5 | ||||
# | ||||
# When there is processed output, it has a '\n' at the tail end. So | ||||
# adding the output to `ret` will provide the necessary spacing | ||||
# between consecutive input/output blocks, as in: | ||||
# | ||||
# In [1]: x | ||||
# Out[1]: 5 | ||||
# | ||||
# In [2]: x | ||||
# Out[2]: 5 | ||||
# | ||||
# When there is stdout from the input, it also has a '\n' at the | ||||
# tail end, and so this ensures proper spacing as well. E.g.: | ||||
# | ||||
|
r28756 | # In [1]: print(x) | ||
|
r16646 | # 5 | ||
# | ||||
# In [2]: x = 5 | ||||
# | ||||
# When in verbatim mode, `processed_output` is empty (because | ||||
# nothing was passed to IP. Sometimes the submitted code block has | ||||
# an Out[] portion and sometimes it does not. When it does not, we | ||||
# need to ensure proper spacing, so we have to add '' to `ret`. | ||||
# However, if there is an Out[] in the submitted code, then we do | ||||
# not want to add a newline as `process_output` has stuff to add. | ||||
# The difficulty is that `process_input` doesn't know if | ||||
# `process_output` will be called---so it doesn't know if there is | ||||
# Out[] in the code block. The requires that we include a hack in | ||||
# `process_block`. See the comments there. | ||||
# | ||||
ret.append(processed_output) | ||||
elif is_semicolon: | ||||
# Make sure there is a newline after the semicolon. | ||||
|
r6135 | ret.append('') | ||
|
r2439 | |||
|
r14779 | # context information | ||
|
r14781 | filename = "Unknown" | ||
lineno = 0 | ||||
|
r14799 | if self.directive.state: | ||
filename = self.directive.state.document.current_source | ||||
lineno = self.directive.state.document.current_line | ||||
|
r26626 | |||
|
r26606 | # Use sphinx logger for warnings | ||
logger = logging.getLogger(__name__) | ||||
|
r14779 | |||
# output any exceptions raised during execution to stdout | ||||
# unless :okexcept: has been specified. | ||||
|
r26626 | if not is_okexcept and ( | ||
("Traceback" in processed_output) or ("SyntaxError" in processed_output) | ||||
): | ||||
s = "\n>>>" + ("-" * 73) + "\n" | ||||
|
r26606 | s += "Exception in %s at block ending on line %s\n" % (filename, lineno) | ||
|
r14779 | s += "Specify :okexcept: as an option in the ipython:: block to suppress this message\n" | ||
|
r26606 | s += processed_output + "\n" | ||
s += "<<<" + ("-" * 73) | ||||
logger.warning(s) | ||||
|
r24714 | if self.warning_is_error: | ||
|
r28329 | raise RuntimeError( | ||
"Unexpected exception in `{}` line {}".format(filename, lineno) | ||||
) | ||||
|
r14779 | |||
# output any warning raised during execution to stdout | ||||
# unless :okwarning: has been specified. | ||||
if not is_okwarning: | ||||
for w in ws: | ||||
|
r26626 | s = "\n>>>" + ("-" * 73) + "\n" | ||
|
r26606 | s += "Warning in %s at block ending on line %s\n" % (filename, lineno) | ||
|
r14779 | s += "Specify :okwarning: as an option in the ipython:: block to suppress this message\n" | ||
|
r26626 | s += ("-" * 76) + "\n" | ||
s += warnings.formatwarning( | ||||
w.message, w.category, w.filename, w.lineno, w.line | ||||
) | ||||
s += "<<<" + ("-" * 73) | ||||
|
r26606 | logger.warning(s) | ||
|
r24747 | if self.warning_is_error: | ||
|
r28329 | raise RuntimeError( | ||
"Unexpected warning in `{}` line {}".format(filename, lineno) | ||||
) | ||||
|
r14779 | |||
|
r25192 | self.clear_cout() | ||
|
r16646 | return (ret, input_lines, processed_output, | ||
is_doctest, decorator, image_file, image_directive) | ||||
|
r13863 | |||
|
r2439 | |||
|
r16646 | def process_output(self, data, output_prompt, input_lines, output, | ||
is_doctest, decorator, image_file): | ||||
|
r13863 | """ | ||
Process data block for OUTPUT token. | ||||
""" | ||||
|
r16646 | # Recall: `data` is the submitted output, and `output` is the processed | ||
# output from `input_lines`. | ||||
|
r13863 | TAB = ' ' * 4 | ||
|
r13680 | if is_doctest and output is not None: | ||
|
r4432 | |||
|
r16646 | found = output # This is the processed output | ||
|
r13680 | found = found.strip() | ||
submitted = data.strip() | ||||
|
r2439 | |||
|
r13863 | if self.directive is None: | ||
source = 'Unavailable' | ||||
content = 'Unavailable' | ||||
else: | ||||
source = self.directive.state.document.current_source | ||||
content = self.directive.content | ||||
# Add tabs and join into a single string. | ||||
content = '\n'.join([TAB + line for line in content]) | ||||
|
r13680 | # Make sure the output contains the output prompt. | ||
ind = found.find(output_prompt) | ||||
if ind < 0: | ||||
|
r13863 | e = ('output does not contain output prompt\n\n' | ||
'Document source: {0}\n\n' | ||||
'Raw content: \n{1}\n\n' | ||||
'Input line(s):\n{TAB}{2}\n\n' | ||||
'Output line(s):\n{TAB}{3}\n\n') | ||||
e = e.format(source, content, '\n'.join(input_lines), | ||||
repr(found), TAB=TAB) | ||||
|
r13680 | raise RuntimeError(e) | ||
found = found[len(output_prompt):].strip() | ||||
# Handle the actual doctest comparison. | ||||
if decorator.strip() == '@doctest': | ||||
# Standard doctest | ||||
if found != submitted: | ||||
|
r13863 | e = ('doctest failure\n\n' | ||
'Document source: {0}\n\n' | ||||
'Raw content: \n{1}\n\n' | ||||
'On input line(s):\n{TAB}{2}\n\n' | ||||
'we found output:\n{TAB}{3}\n\n' | ||||
'instead of the expected:\n{TAB}{4}\n\n') | ||||
e = e.format(source, content, '\n'.join(input_lines), | ||||
repr(found), repr(submitted), TAB=TAB) | ||||
|
r2439 | raise RuntimeError(e) | ||
|
r13680 | else: | ||
|
r13843 | self.custom_doctest(decorator, input_lines, found, submitted) | ||
|
r2439 | |||
|
r16646 | # When in verbatim mode, this holds additional submitted output | ||
# to be written in the final Sphinx output. | ||||
|
r16644 | # https://github.com/ipython/ipython/issues/5776 | ||
|
r16646 | out_data = [] | ||
|
r16644 | is_verbatim = decorator=='@verbatim' or self.is_verbatim | ||
|
r16646 | if is_verbatim and data.strip(): | ||
# Note that `ret` in `process_block` has '' as its last element if | ||||
# the code block was in verbatim mode. So if there is no submitted | ||||
# output, then we will have proper spacing only if we do not add | ||||
# an additional '' to `out_data`. This is why we condition on | ||||
# `and data.strip()`. | ||||
# The submitted output has no output prompt. If we want the | ||||
# prompt and the code to appear, we need to join them now | ||||
# instead of adding them separately---as this would create an | ||||
# undesired newline. How we do this ultimately depends on the | ||||
# format of the output regex. I'll do what works for the default | ||||
# prompt for now, and we might have to adjust if it doesn't work | ||||
# in other cases. Finally, the submitted output does not have | ||||
# a trailing newline, so we must add it manually. | ||||
out_data.append("{0} {1}\n".format(output_prompt, data)) | ||||
|
r16644 | |||
return out_data | ||||
|
r2439 | def process_comment(self, data): | ||
|
r4432 | """Process data fPblock for COMMENT token.""" | ||
|
r2439 | if not self.is_suppress: | ||
return [data] | ||||
|
r4432 | def save_image(self, image_file): | ||
""" | ||||
Saves the image file to disk. | ||||
""" | ||||
self.ensure_pyplot() | ||||
command = 'plt.gcf().savefig("%s")'%image_file | ||||
|
r28756 | # print('SAVEFIG', command) # dbg | ||
|
r4432 | self.process_input_line('bookmark ipy_thisdir', store_history=False) | ||
self.process_input_line('cd -b ipy_savedir', store_history=False) | ||||
self.process_input_line(command, store_history=False) | ||||
self.process_input_line('cd -b ipy_thisdir', store_history=False) | ||||
self.process_input_line('bookmark -d ipy_thisdir', store_history=False) | ||||
self.clear_cout() | ||||
|
r2439 | def process_block(self, block): | ||
""" | ||||
process block from the block_parser and return a list of processed lines | ||||
""" | ||||
ret = [] | ||||
output = None | ||||
input_lines = None | ||||
|
r4432 | lineno = self.IP.execution_count | ||
|
r2439 | |||
|
r13873 | input_prompt = self.promptin % lineno | ||
output_prompt = self.promptout % lineno | ||||
|
r2439 | image_file = None | ||
image_directive = None | ||||
|
r4432 | |||
|
r21437 | found_input = False | ||
|
r2439 | for token, data in block: | ||
|
r13873 | if token == COMMENT: | ||
|
r2439 | out_data = self.process_comment(data) | ||
|
r13873 | elif token == INPUT: | ||
|
r21437 | found_input = True | ||
|
r16646 | (out_data, input_lines, output, is_doctest, | ||
decorator, image_file, image_directive) = \ | ||||
|
r2439 | self.process_input(data, input_prompt, lineno) | ||
|
r13873 | elif token == OUTPUT: | ||
|
r21437 | if not found_input: | ||
TAB = ' ' * 4 | ||||
linenumber = 0 | ||||
source = 'Unavailable' | ||||
content = 'Unavailable' | ||||
if self.directive: | ||||
linenumber = self.directive.state.document.current_line | ||||
source = self.directive.state.document.current_source | ||||
content = self.directive.content | ||||
# Add tabs and join into a single string. | ||||
content = '\n'.join([TAB + line for line in content]) | ||||
e = ('\n\nInvalid block: Block contains an output prompt ' | ||||
'without an input prompt.\n\n' | ||||
'Document source: {0}\n\n' | ||||
'Content begins at line {1}: \n\n{2}\n\n' | ||||
'Problematic block within content: \n\n{TAB}{3}\n\n') | ||||
e = e.format(source, linenumber, content, block, TAB=TAB) | ||||
# Write, rather than include in exception, since Sphinx | ||||
# will truncate tracebacks. | ||||
sys.stdout.write(e) | ||||
raise RuntimeError('An invalid block was detected.') | ||||
|
r2439 | out_data = \ | ||
|
r16646 | self.process_output(data, output_prompt, input_lines, | ||
output, is_doctest, decorator, | ||||
image_file) | ||||
if out_data: | ||||
# Then there was user submitted output in verbatim mode. | ||||
# We need to remove the last element of `ret` that was | ||||
# added in `process_input`, as it is '' and would introduce | ||||
# an undesirable newline. | ||||
assert(ret[-1] == '') | ||||
del ret[-1] | ||||
|
r2439 | if out_data: | ||
ret.extend(out_data) | ||||
|
r4432 | # save the image files | ||
|
r2439 | if image_file is not None: | ||
|
r4432 | self.save_image(image_file) | ||
|
r2439 | return ret, image_directive | ||
def ensure_pyplot(self): | ||||
|
r13873 | """ | ||
Ensures that pyplot has been imported into the embedded IPython shell. | ||||
Also, makes sure to set the backend appropriately if not set already. | ||||
""" | ||||
# We are here if the @figure pseudo decorator was used. Thus, it's | ||||
# possible that we could be here even if python_mplbackend were set to | ||||
# `None`. That's also strange and perhaps worthy of raising an | ||||
# exception, but for now, we just set the backend to 'agg'. | ||||
if not self._pyplot_imported: | ||||
if 'matplotlib.backends' not in sys.modules: | ||||
# Then ipython_matplotlib was set to None but there was a | ||||
# call to the @figure decorator (and ipython_execlines did | ||||
# not set a backend). | ||||
#raise Exception("No backend was set, but @figure was used!") | ||||
import matplotlib | ||||
matplotlib.use('agg') | ||||
# Always import pyplot into embedded shell. | ||||
self.process_input_line('import matplotlib.pyplot as plt', | ||||
store_history=False) | ||||
self._pyplot_imported = True | ||||
|
r2439 | |||
|
r4432 | def process_pure_python(self, content): | ||
""" | ||||
|
r13680 | content is a list of strings. it is unedited directive content | ||
|
r4432 | |||
This runs it line by line in the InteractiveShell, prepends | ||||
prompts as needed capturing stderr and stdout, then returns | ||||
the content as a list as if it were ipython code | ||||
""" | ||||
output = [] | ||||
savefig = False # keep up with this to clear figure | ||||
multiline = False # to handle line continuation | ||||
|
r6134 | multiline_start = None | ||
|
r4432 | fmtin = self.promptin | ||
|
r6134 | ct = 0 | ||
|
r4432 | for lineno, line in enumerate(content): | ||
line_stripped = line.strip() | ||||
if not len(line): | ||||
|
r6134 | output.append(line) | ||
|
r4432 | continue | ||
|
r27685 | # handle pseudo-decorators, whilst ensuring real python decorators are treated as input | ||
if any( | ||||
line_stripped.startswith("@" + pseudo_decorator) | ||||
for pseudo_decorator in PSEUDO_DECORATORS | ||||
): | ||||
|
r4432 | output.extend([line]) | ||
if 'savefig' in line: | ||||
savefig = True # and need to clear figure | ||||
continue | ||||
# handle comments | ||||
if line_stripped.startswith('#'): | ||||
output.extend([line]) | ||||
continue | ||||
|
r6134 | # deal with lines checking for multiline | ||
continuation = u' %s:'% ''.join(['.']*(len(str(ct))+2)) | ||||
if not multiline: | ||||
modified = u"%s %s" % (fmtin % ct, line_stripped) | ||||
output.append(modified) | ||||
ct += 1 | ||||
try: | ||||
ast.parse(line_stripped) | ||||
output.append(u'') | ||||
except Exception: # on a multiline | ||||
|
r4432 | multiline = True | ||
|
r6134 | multiline_start = lineno | ||
else: # still on a multiline | ||||
modified = u'%s %s' % (continuation, line) | ||||
output.append(modified) | ||||
|
r10597 | |||
# if the next line is indented, it should be part of multiline | ||||
if len(content) > lineno + 1: | ||||
nextline = content[lineno + 1] | ||||
if len(nextline) - len(nextline.lstrip()) > 3: | ||||
continue | ||||
|
r6134 | try: | ||
mod = ast.parse( | ||||
'\n'.join(content[multiline_start:lineno+1])) | ||||
if isinstance(mod.body[0], ast.FunctionDef): | ||||
# check to see if we have the whole function | ||||
for element in mod.body[0].body: | ||||
if isinstance(element, ast.Return): | ||||
multiline = False | ||||
else: | ||||
output.append(u'') | ||||
multiline = False | ||||
except Exception: | ||||
pass | ||||
if savefig: # clear figure if plotted | ||||
|
r4432 | self.ensure_pyplot() | ||
self.process_input_line('plt.clf()', store_history=False) | ||||
self.clear_cout() | ||||
savefig = False | ||||
return output | ||||
|
r13843 | def custom_doctest(self, decorator, input_lines, found, submitted): | ||
|
r13680 | """ | ||
Perform a specialized doctest. | ||||
""" | ||||
|
r13843 | from .custom_doctests import doctests | ||
|
r13680 | |||
args = decorator.split() | ||||
doctest_type = args[1] | ||||
|
r13843 | if doctest_type in doctests: | ||
doctests[doctest_type](self, args, input_lines, found, submitted) | ||||
else: | ||||
|
r13680 | e = "Invalid option to @doctest: {0}".format(doctest_type) | ||
raise Exception(e) | ||||
|
r13150 | class IPythonDirective(Directive): | ||
|
r4432 | |||
has_content = True | ||||
required_arguments = 0 | ||||
optional_arguments = 4 # python, suppress, verbatim, doctest | ||||
final_argumuent_whitespace = True | ||||
option_spec = { 'python': directives.unchanged, | ||||
'suppress' : directives.flag, | ||||
'verbatim' : directives.flag, | ||||
'doctest' : directives.flag, | ||||
|
r14779 | 'okexcept': directives.flag, | ||
'okwarning': directives.flag | ||||
|
r4432 | } | ||
|
r2439 | |||
|
r13150 | shell = None | ||
|
r2439 | |||
|
r10596 | seen_docs = set() | ||
|
r4432 | def get_config_options(self): | ||
# contains sphinx configuration variables | ||||
config = self.state.document.settings.env.config | ||||
# get config variables to set figure output directory | ||||
savefig_dir = config.ipython_savefig_dir | ||||
|
r23221 | source_dir = self.state.document.settings.env.srcdir | ||
savefig_dir = os.path.join(source_dir, savefig_dir) | ||||
|
r4432 | |||
# get regex and prompt stuff | ||||
|
r13658 | rgxin = config.ipython_rgxin | ||
rgxout = config.ipython_rgxout | ||||
|
r24714 | warning_is_error= config.ipython_warning_is_error | ||
|
r13658 | promptin = config.ipython_promptin | ||
promptout = config.ipython_promptout | ||||
mplbackend = config.ipython_mplbackend | ||||
|
r13659 | exec_lines = config.ipython_execlines | ||
|
r13902 | hold_count = config.ipython_holdcount | ||
|
r4432 | |||
|
r13658 | return (savefig_dir, source_dir, rgxin, rgxout, | ||
|
r24714 | promptin, promptout, mplbackend, exec_lines, hold_count, warning_is_error) | ||
|
r4432 | |||
def setup(self): | ||||
|
r13658 | # Get configuration values. | ||
|
r13902 | (savefig_dir, source_dir, rgxin, rgxout, promptin, promptout, | ||
|
r24714 | mplbackend, exec_lines, hold_count, warning_is_error) = self.get_config_options() | ||
|
r13658 | |||
|
r23221 | try: | ||
os.makedirs(savefig_dir) | ||||
except OSError as e: | ||||
if e.errno != errno.EEXIST: | ||||
raise | ||||
|
r13150 | if self.shell is None: | ||
|
r13873 | # We will be here many times. However, when the | ||
# EmbeddedSphinxShell is created, its interactive shell member | ||||
# is the same for each instance. | ||||
|
r25167 | if mplbackend and 'matplotlib.backends' not in sys.modules and use_matplotlib: | ||
|
r13658 | import matplotlib | ||
matplotlib.use(mplbackend) | ||||
|
r13674 | # Must be called after (potentially) importing matplotlib and | ||
# setting its backend since exec_lines might import pylab. | ||||
|
r14799 | self.shell = EmbeddedSphinxShell(exec_lines) | ||
|
r13674 | |||
|
r13863 | # Store IPython directive to enable better error messages | ||
self.shell.directive = self | ||||
|
r6137 | # reset the execution count if we haven't processed this doc | ||
#NOTE: this may be borked if there are multiple seen_doc tmp files | ||||
#check time stamp? | ||||
|
r10596 | if not self.state.document.current_source in self.seen_docs: | ||
|
r13658 | self.shell.IP.history_manager.reset() | ||
self.shell.IP.execution_count = 1 | ||||
self.seen_docs.add(self.state.document.current_source) | ||||
|
r4432 | |||
# and attach to shell so we don't have to pass them around | ||||
self.shell.rgxin = rgxin | ||||
self.shell.rgxout = rgxout | ||||
self.shell.promptin = promptin | ||||
self.shell.promptout = promptout | ||||
self.shell.savefig_dir = savefig_dir | ||||
self.shell.source_dir = source_dir | ||||
|
r13902 | self.shell.hold_count = hold_count | ||
|
r24714 | self.shell.warning_is_error = warning_is_error | ||
|
r2439 | |||
|
r4432 | # setup bookmark for saving figures directory | ||
|
r27870 | self.shell.process_input_line( | ||
'bookmark ipy_savedir "%s"' % savefig_dir, store_history=False | ||||
) | ||||
|
r4432 | self.shell.clear_cout() | ||
|
r2439 | |||
|
r4432 | return rgxin, rgxout, promptin, promptout | ||
|
r2439 | |||
|
r4432 | def teardown(self): | ||
# delete last bookmark | ||||
self.shell.process_input_line('bookmark -d ipy_savedir', | ||||
store_history=False) | ||||
self.shell.clear_cout() | ||||
|
r2439 | |||
|
r4432 | def run(self): | ||
debug = False | ||||
|
r2439 | |||
|
r4432 | #TODO, any reason block_parser can't be a method of embeddable shell | ||
# then we wouldn't have to carry these around | ||||
rgxin, rgxout, promptin, promptout = self.setup() | ||||
|
r2439 | |||
|
r4432 | options = self.options | ||
self.shell.is_suppress = 'suppress' in options | ||||
self.shell.is_doctest = 'doctest' in options | ||||
self.shell.is_verbatim = 'verbatim' in options | ||||
|
r14779 | self.shell.is_okexcept = 'okexcept' in options | ||
self.shell.is_okwarning = 'okwarning' in options | ||||
|
r2439 | |||
|
r4432 | # handle pure python code | ||
if 'python' in self.arguments: | ||||
content = self.content | ||||
self.content = self.shell.process_pure_python(content) | ||||
|
r21437 | # parts consists of all text within the ipython-block. | ||
# Each part is an input/output block. | ||||
|
r4432 | parts = '\n'.join(self.content).split('\n\n') | ||
|
r13863 | lines = ['.. code-block:: ipython', ''] | ||
|
r4432 | figures = [] | ||
|
r26606 | # Use sphinx logger for warnings | ||
logger = logging.getLogger(__name__) | ||||
|
r4432 | for part in parts: | ||
block = block_parser(part, rgxin, rgxout, promptin, promptout) | ||||
if len(block): | ||||
rows, figure = self.shell.process_block(block) | ||||
for row in rows: | ||||
|
r16646 | lines.extend([' {0}'.format(line) | ||
for line in row.split('\n')]) | ||||
|
r4432 | |||
if figure is not None: | ||||
figures.append(figure) | ||||
|
r24742 | else: | ||
message = 'Code input with no code at {}, line {}'\ | ||||
.format( | ||||
self.state.document.current_source, | ||||
self.state.document.current_line) | ||||
if self.shell.warning_is_error: | ||||
raise RuntimeError(message) | ||||
else: | ||||
|
r26606 | logger.warning(message) | ||
|
r4432 | |||
for figure in figures: | ||||
lines.append('') | ||||
lines.extend(figure.split('\n')) | ||||
lines.append('') | ||||
|
r16646 | if len(lines) > 2: | ||
|
r4432 | if debug: | ||
|
r13348 | print('\n'.join(lines)) | ||
|
r13658 | else: | ||
|
r13863 | # This has to do with input, not output. But if we comment | ||
# these lines out, then no IPython code will appear in the | ||||
# final output. | ||||
|
r4432 | self.state_machine.insert_input( | ||
lines, self.state_machine.input_lines.source(0)) | ||||
# cleanup | ||||
self.teardown() | ||||
|
r13873 | return [] | ||
|
r2439 | |||
# Enable as a proper Sphinx directive | ||||
def setup(app): | ||||
setup.app = app | ||||
|
r13150 | app.add_directive('ipython', IPythonDirective) | ||
|
r23221 | app.add_config_value('ipython_savefig_dir', 'savefig', 'env') | ||
|
r24714 | app.add_config_value('ipython_warning_is_error', True, 'env') | ||
|
r4432 | app.add_config_value('ipython_rgxin', | ||
|
r24896 | re.compile(r'In \[(\d+)\]:\s?(.*)\s*'), 'env') | ||
|
r4432 | app.add_config_value('ipython_rgxout', | ||
|
r24896 | re.compile(r'Out\[(\d+)\]:\s?(.*)\s*'), 'env') | ||
|
r13658 | app.add_config_value('ipython_promptin', 'In [%d]:', 'env') | ||
app.add_config_value('ipython_promptout', 'Out[%d]:', 'env') | ||||
|
r13902 | |||
|
r13873 | # We could just let matplotlib pick whatever is specified as the default | ||
# backend in the matplotlibrc file, but this would cause issues if the | ||||
# backend didn't work in headless environments. For this reason, 'agg' | ||||
# is a good default backend choice. | ||||
app.add_config_value('ipython_mplbackend', 'agg', 'env') | ||||
|
r13902 | |||
|
r13873 | # If the user sets this config value to `None`, then EmbeddedSphinxShell's | ||
# __init__ method will treat it as []. | ||||
|
r24591 | execlines = ['import numpy as np'] | ||
|
r25167 | if use_matplotlib: | ||
|
r24591 | execlines.append('import matplotlib.pyplot as plt') | ||
|
r13873 | app.add_config_value('ipython_execlines', execlines, 'env') | ||
|
r2439 | |||
|
r13902 | app.add_config_value('ipython_holdcount', True, 'env') | ||
|
r21874 | metadata = {'parallel_read_safe': True, 'parallel_write_safe': True} | ||
return metadata | ||||
|
r2439 | # Simple smoke test, needs to be converted to a proper automatic test. | ||
def test(): | ||||
examples = [ | ||||
r""" | ||||
In [9]: pwd | ||||
Out[9]: '/home/jdhunter/py4science/book' | ||||
In [10]: cd bookdata/ | ||||
/home/jdhunter/py4science/book/bookdata | ||||
In [2]: from pylab import * | ||||
In [2]: ion() | ||||
In [3]: im = imread('stinkbug.png') | ||||
@savefig mystinkbug.png width=4in | ||||
In [4]: imshow(im) | ||||
Out[4]: <matplotlib.image.AxesImage object at 0x39ea850> | ||||
|
r4432 | |||
|
r2439 | """, | ||
r""" | ||||
In [1]: x = 'hello world' | ||||
# string methods can be | ||||
# used to alter the string | ||||
@doctest | ||||
In [2]: x.upper() | ||||
Out[2]: 'HELLO WORLD' | ||||
@verbatim | ||||
In [3]: x.st<TAB> | ||||
x.startswith x.strip | ||||
""", | ||||
r""" | ||||
In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\ | ||||
.....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv' | ||||
In [131]: print url.split('&') | ||||
['http://ichart.finance.yahoo.com/table.csv?s=CROX', 'd=9', 'e=22', 'f=2009', 'g=d', 'a=1', 'b=8', 'c=2006', 'ignore=.csv'] | ||||
In [60]: import urllib | ||||
""", | ||||
r"""\ | ||||
In [133]: import numpy.random | ||||
@suppress | ||||
In [134]: numpy.random.seed(2358) | ||||
@doctest | ||||
|
r4432 | In [135]: numpy.random.rand(10,2) | ||
|
r2439 | Out[135]: | ||
array([[ 0.64524308, 0.59943846], | ||||
[ 0.47102322, 0.8715456 ], | ||||
[ 0.29370834, 0.74776844], | ||||
[ 0.99539577, 0.1313423 ], | ||||
[ 0.16250302, 0.21103583], | ||||
[ 0.81626524, 0.1312433 ], | ||||
[ 0.67338089, 0.72302393], | ||||
[ 0.7566368 , 0.07033696], | ||||
[ 0.22591016, 0.77731835], | ||||
[ 0.0072729 , 0.34273127]]) | ||||
""", | ||||
r""" | ||||
In [106]: print x | ||||
jdh | ||||
In [109]: for i in range(10): | ||||
.....: print i | ||||
.....: | ||||
.....: | ||||
0 | ||||
1 | ||||
2 | ||||
3 | ||||
4 | ||||
5 | ||||
6 | ||||
7 | ||||
8 | ||||
9 | ||||
""", | ||||
r""" | ||||
In [144]: from pylab import * | ||||
In [145]: ion() | ||||
# use a semicolon to suppress the output | ||||
@savefig test_hist.png width=4in | ||||
In [151]: hist(np.random.randn(10000), 100); | ||||
@savefig test_plot.png width=4in | ||||
In [151]: plot(np.random.randn(10000), 'o'); | ||||
""", | ||||
r""" | ||||
# use a semicolon to suppress the output | ||||
In [151]: plt.clf() | ||||
@savefig plot_simple.png width=4in | ||||
In [151]: plot([1,2,3]) | ||||
@savefig hist_simple.png width=4in | ||||
In [151]: hist(np.random.randn(10000), 100); | ||||
""", | ||||
r""" | ||||
# update the current fig | ||||
In [151]: ylabel('number') | ||||
In [152]: title('normal distribution') | ||||
@savefig hist_with_text.png | ||||
In [153]: grid(True) | ||||
|
r13680 | @doctest float | ||
In [154]: 0.1 + 0.2 | ||||
Out[154]: 0.3 | ||||
@doctest float | ||||
In [155]: np.arange(16).reshape(4,4) | ||||
Out[155]: | ||||
array([[ 0, 1, 2, 3], | ||||
[ 4, 5, 6, 7], | ||||
[ 8, 9, 10, 11], | ||||
[12, 13, 14, 15]]) | ||||
In [1]: x = np.arange(16, dtype=float).reshape(4,4) | ||||
In [2]: x[0,0] = np.inf | ||||
In [3]: x[0,1] = np.nan | ||||
@doctest float | ||||
In [4]: x | ||||
Out[4]: | ||||
array([[ inf, nan, 2., 3.], | ||||
[ 4., 5., 6., 7.], | ||||
[ 8., 9., 10., 11.], | ||||
[ 12., 13., 14., 15.]]) | ||||
|
r2439 | """, | ||
] | ||||
|
r4432 | # skip local-file depending first example: | ||
examples = examples[1:] | ||||
|
r2439 | |||
#ipython_directive.DEBUG = True # dbg | ||||
#options = dict(suppress=True) # dbg | ||||
|
r23231 | options = {} | ||
|
r2439 | for example in examples: | ||
content = example.split('\n') | ||||
|
r13150 | IPythonDirective('debug', arguments=None, options=options, | ||
|
r2439 | content=content, lineno=0, | ||
content_offset=None, block_text=None, | ||||
state=None, state_machine=None, | ||||
) | ||||
# Run test suite as a script | ||||
if __name__=='__main__': | ||||
if not os.path.isdir('_static'): | ||||
os.mkdir('_static') | ||||
test() | ||||
|
r13348 | print('All OK? Check figures in _static/') | ||