##// END OF EJS Templates
FIX: Typing annotations...
farisachugthai -
Show More
@@ -1,155 +1,155
1 1 """
2 2 Handlers for IPythonDirective's @doctest pseudo-decorator.
3 3
4 4 The Sphinx extension that provides support for embedded IPython code provides
5 5 a pseudo-decorator @doctest, which treats the input/output block as a
6 6 doctest, raising a RuntimeError during doc generation if the actual output
7 7 (after running the input) does not match the expected output.
8 8
9 9 An example usage is:
10 10
11 11 .. code-block:: rst
12 12
13 13 .. ipython::
14 14
15 15 In [1]: x = 1
16 16
17 17 @doctest
18 18 In [2]: x + 2
19 19 Out[3]: 3
20 20
21 21 One can also provide arguments to the decorator. The first argument should be
22 22 the name of a custom handler. The specification of any other arguments is
23 23 determined by the handler. For example,
24 24
25 25 .. code-block:: rst
26 26
27 27 .. ipython::
28 28
29 29 @doctest float
30 30 In [154]: 0.1 + 0.2
31 31 Out[154]: 0.3
32 32
33 33 allows the actual output ``0.30000000000000004`` to match the expected output
34 34 due to a comparison with `np.allclose`.
35 35
36 36 This module contains handlers for the @doctest pseudo-decorator. Handlers
37 37 should have the following function signature::
38 38
39 39 handler(sphinx_shell, args, input_lines, found, submitted)
40 40
41 41 where `sphinx_shell` is the embedded Sphinx shell, `args` contains the list
42 42 of arguments that follow: '@doctest handler_name', `input_lines` contains
43 43 a list of the lines relevant to the current doctest, `found` is a string
44 44 containing the output from the IPython shell, and `submitted` is a string
45 45 containing the expected output from the IPython shell.
46 46
47 47 Handlers must be registered in the `doctests` dict at the end of this module.
48 48
49 49 """
50 50
51 51 def str_to_array(s):
52 52 """
53 53 Simplistic converter of strings from repr to float NumPy arrays.
54 54
55 55 If the repr representation has ellipsis in it, then this will fail.
56 56
57 57 Parameters
58 58 ----------
59 59 s : str
60 60 The repr version of a NumPy array.
61 61
62 62 Examples
63 63 --------
64 64 >>> s = "array([ 0.3, inf, nan])"
65 65 >>> a = str_to_array(s)
66 66
67 67 """
68 68 import numpy as np
69 69
70 70 # Need to make sure eval() knows about inf and nan.
71 71 # This also assumes default printoptions for NumPy.
72 72 from numpy import inf, nan
73 73
74 74 if s.startswith(u'array'):
75 75 # Remove array( and )
76 76 s = s[6:-1]
77 77
78 78 if s.startswith(u'['):
79 79 a = np.array(eval(s), dtype=float)
80 80 else:
81 81 # Assume its a regular float. Force 1D so we can index into it.
82 82 a = np.atleast_1d(float(s))
83 83 return a
84 84
85 85 def float_doctest(sphinx_shell, args, input_lines, found, submitted):
86 86 """
87 87 Doctest which allow the submitted output to vary slightly from the input.
88 88
89 89 Here is how it might appear in an rst file:
90 90
91 91 .. code-block:: rst
92 92
93 93 .. ipython::
94 94
95 95 @doctest float
96 96 In [1]: 0.1 + 0.2
97 97 Out[1]: 0.3
98 98
99 99 """
100 100 import numpy as np
101 101
102 102 if len(args) == 2:
103 103 rtol = 1e-05
104 104 atol = 1e-08
105 105 else:
106 106 # Both must be specified if any are specified.
107 107 try:
108 108 rtol = float(args[2])
109 109 atol = float(args[3])
110 except IndexError as e:
110 except IndexError:
111 111 e = ("Both `rtol` and `atol` must be specified "
112 112 "if either are specified: {0}".format(args))
113 113 raise IndexError(e) from e
114 114
115 115 try:
116 116 submitted = str_to_array(submitted)
117 117 found = str_to_array(found)
118 118 except:
119 119 # For example, if the array is huge and there are ellipsis in it.
120 120 error = True
121 121 else:
122 122 found_isnan = np.isnan(found)
123 123 submitted_isnan = np.isnan(submitted)
124 124 error = not np.allclose(found_isnan, submitted_isnan)
125 125 error |= not np.allclose(found[~found_isnan],
126 126 submitted[~submitted_isnan],
127 127 rtol=rtol, atol=atol)
128 128
129 129 TAB = ' ' * 4
130 130 directive = sphinx_shell.directive
131 131 if directive is None:
132 132 source = 'Unavailable'
133 133 content = 'Unavailable'
134 134 else:
135 135 source = directive.state.document.current_source
136 136 # Add tabs and make into a single string.
137 137 content = '\n'.join([TAB + line for line in directive.content])
138 138
139 139 if error:
140 140
141 141 e = ('doctest float comparison failure\n\n'
142 142 'Document source: {0}\n\n'
143 143 'Raw content: \n{1}\n\n'
144 144 'On input line(s):\n{TAB}{2}\n\n'
145 145 'we found output:\n{TAB}{3}\n\n'
146 146 'instead of the expected:\n{TAB}{4}\n\n')
147 147 e = e.format(source, content, '\n'.join(input_lines), repr(found),
148 148 repr(submitted), TAB=TAB)
149 149 raise RuntimeError(e)
150 150
151 151 # dict of allowable doctest handlers. The key represents the first argument
152 152 # that must be given to @doctest in order to activate the handler.
153 153 doctests = {
154 154 'float': float_doctest,
155 155 }
@@ -1,1276 +1,1277
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 Sphinx directive to support embedded IPython code.
4 4
5 5 IPython provides an extension for `Sphinx <http://www.sphinx-doc.org/>`_ to
6 6 highlight and run code.
7 7
8 8 This directive allows pasting of entire interactive IPython sessions, prompts
9 9 and all, and their code will actually get re-executed at doc build time, with
10 10 all prompts renumbered sequentially. It also allows you to input code as a pure
11 11 python input by giving the argument python to the directive. The output looks
12 12 like an interactive ipython section.
13 13
14 14 Here is an example of how the IPython directive can
15 15 **run** python code, at build time.
16 16
17 17 .. ipython::
18 18
19 19 In [1]: 1+1
20 20
21 21 In [1]: import datetime
22 22 ...: datetime.date.fromisoformat('2022-02-22')
23 23
24 24 It supports IPython construct that plain
25 25 Python does not understand (like magics):
26 26
27 27 .. ipython::
28 28
29 29 In [0]: import time
30 30
31 31 In [0]: %pdoc time.sleep
32 32
33 33 This will also support top-level async when using IPython 7.0+
34 34
35 35 .. ipython::
36 36
37 37 In [2]: import asyncio
38 38 ...: print('before')
39 39 ...: await asyncio.sleep(1)
40 40 ...: print('after')
41 41
42 42
43 43 The namespace will persist across multiple code chucks, Let's define a variable:
44 44
45 45 .. ipython::
46 46
47 47 In [0]: who = "World"
48 48
49 49 And now say hello:
50 50
51 51 .. ipython::
52 52
53 53 In [0]: print('Hello,', who)
54 54
55 55 If the current section raises an exception, you can add the ``:okexcept:`` flag
56 56 to the current block, otherwise the build will fail.
57 57
58 58 .. ipython::
59 59 :okexcept:
60 60
61 61 In [1]: 1/0
62 62
63 63 IPython Sphinx directive module
64 64 ===============================
65 65
66 66 To enable this directive, simply list it in your Sphinx ``conf.py`` file
67 67 (making sure the directory where you placed it is visible to sphinx, as is
68 68 needed for all Sphinx directives). For example, to enable syntax highlighting
69 69 and the IPython directive::
70 70
71 71 extensions = ['IPython.sphinxext.ipython_console_highlighting',
72 72 'IPython.sphinxext.ipython_directive']
73 73
74 74 The IPython directive outputs code-blocks with the language 'ipython'. So
75 75 if you do not have the syntax highlighting extension enabled as well, then
76 76 all rendered code-blocks will be uncolored. By default this directive assumes
77 77 that your prompts are unchanged IPython ones, but this can be customized.
78 78 The configurable options that can be placed in conf.py are:
79 79
80 80 ipython_savefig_dir:
81 81 The directory in which to save the figures. This is relative to the
82 82 Sphinx source directory. The default is `html_static_path`.
83 83 ipython_rgxin:
84 84 The compiled regular expression to denote the start of IPython input
85 85 lines. The default is ``re.compile('In \\[(\\d+)\\]:\\s?(.*)\\s*')``. You
86 86 shouldn't need to change this.
87 87 ipython_warning_is_error: [default to True]
88 88 Fail the build if something unexpected happen, for example if a block raise
89 89 an exception but does not have the `:okexcept:` flag. The exact behavior of
90 90 what is considered strict, may change between the sphinx directive version.
91 91 ipython_rgxout:
92 92 The compiled regular expression to denote the start of IPython output
93 93 lines. The default is ``re.compile('Out\\[(\\d+)\\]:\\s?(.*)\\s*')``. You
94 94 shouldn't need to change this.
95 95 ipython_promptin:
96 96 The string to represent the IPython input prompt in the generated ReST.
97 97 The default is ``'In [%d]:'``. This expects that the line numbers are used
98 98 in the prompt.
99 99 ipython_promptout:
100 100 The string to represent the IPython prompt in the generated ReST. The
101 101 default is ``'Out [%d]:'``. This expects that the line numbers are used
102 102 in the prompt.
103 103 ipython_mplbackend:
104 104 The string which specifies if the embedded Sphinx shell should import
105 105 Matplotlib and set the backend. The value specifies a backend that is
106 106 passed to `matplotlib.use()` before any lines in `ipython_execlines` are
107 107 executed. If not specified in conf.py, then the default value of 'agg' is
108 108 used. To use the IPython directive without matplotlib as a dependency, set
109 109 the value to `None`. It may end up that matplotlib is still imported
110 110 if the user specifies so in `ipython_execlines` or makes use of the
111 111 @savefig pseudo decorator.
112 112 ipython_execlines:
113 113 A list of strings to be exec'd in the embedded Sphinx shell. Typical
114 114 usage is to make certain packages always available. Set this to an empty
115 115 list if you wish to have no imports always available. If specified in
116 116 ``conf.py`` as `None`, then it has the effect of making no imports available.
117 117 If omitted from conf.py altogether, then the default value of
118 118 ['import numpy as np', 'import matplotlib.pyplot as plt'] is used.
119 119 ipython_holdcount
120 120 When the @suppress pseudo-decorator is used, the execution count can be
121 121 incremented or not. The default behavior is to hold the execution count,
122 122 corresponding to a value of `True`. Set this to `False` to increment
123 123 the execution count after each suppressed command.
124 124
125 125 As an example, to use the IPython directive when `matplotlib` is not available,
126 126 one sets the backend to `None`::
127 127
128 128 ipython_mplbackend = None
129 129
130 130 An example usage of the directive is:
131 131
132 132 .. code-block:: rst
133 133
134 134 .. ipython::
135 135
136 136 In [1]: x = 1
137 137
138 138 In [2]: y = x**2
139 139
140 140 In [3]: print(y)
141 141
142 142 See http://matplotlib.org/sampledoc/ipython_directive.html for additional
143 143 documentation.
144 144
145 145 Pseudo-Decorators
146 146 =================
147 147
148 148 Note: Only one decorator is supported per input. If more than one decorator
149 149 is specified, then only the last one is used.
150 150
151 151 In addition to the Pseudo-Decorators/options described at the above link,
152 152 several enhancements have been made. The directive will emit a message to the
153 153 console at build-time if code-execution resulted in an exception or warning.
154 154 You can suppress these on a per-block basis by specifying the :okexcept:
155 155 or :okwarning: options:
156 156
157 157 .. code-block:: rst
158 158
159 159 .. ipython::
160 160 :okexcept:
161 161 :okwarning:
162 162
163 163 In [1]: 1/0
164 164 In [2]: # raise warning.
165 165
166 166 To Do
167 167 =====
168 168
169 169 - Turn the ad-hoc test() function into a real test suite.
170 170 - Break up ipython-specific functionality from matplotlib stuff into better
171 171 separated code.
172 172
173 173 """
174 174
175 175 # Authors
176 176 # =======
177 #
177 #
178 178 # - John D Hunter: original author.
179 179 # - Fernando Perez: refactoring, documentation, cleanups, port to 0.11.
180 180 # - VΓ‘clavΕ milauer <eudoxos-AT-arcig.cz>: Prompt generalizations.
181 181 # - Skipper Seabold, refactoring, cleanups, pure python addition
182 182
183 183 #-----------------------------------------------------------------------------
184 184 # Imports
185 185 #-----------------------------------------------------------------------------
186 186
187 187 # Stdlib
188 188 import atexit
189 189 import errno
190 190 import os
191 191 import pathlib
192 192 import re
193 193 import sys
194 194 import tempfile
195 195 import ast
196 196 import warnings
197 197 import shutil
198 198 from io import StringIO
199 from typing import Any, Dict, Set
199 200
200 201 # Third-party
201 202 from docutils.parsers.rst import directives
202 203 from docutils.parsers.rst import Directive
203 204 from sphinx.util import logging
204 205
205 206 # Our own
206 207 from traitlets.config import Config
207 208 from IPython import InteractiveShell
208 209 from IPython.core.profiledir import ProfileDir
209 210
210 211 use_matplotlib = False
211 212 try:
212 213 import matplotlib
213 214 use_matplotlib = True
214 215 except Exception:
215 216 pass
216 217
217 218 #-----------------------------------------------------------------------------
218 219 # Globals
219 220 #-----------------------------------------------------------------------------
220 221 # for tokenizing blocks
221 222 COMMENT, INPUT, OUTPUT = range(3)
222 223
223 224 PSEUDO_DECORATORS = ["suppress", "verbatim", "savefig", "doctest"]
224 225
225 226 #-----------------------------------------------------------------------------
226 227 # Functions and class declarations
227 228 #-----------------------------------------------------------------------------
228 229
229 230 def block_parser(part, rgxin, rgxout, fmtin, fmtout):
230 231 """
231 232 part is a string of ipython text, comprised of at most one
232 233 input, one output, comments, and blank lines. The block parser
233 234 parses the text into a list of::
234 235
235 236 blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...]
236 237
237 238 where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and
238 239 data is, depending on the type of token::
239 240
240 241 COMMENT : the comment string
241 242
242 243 INPUT: the (DECORATOR, INPUT_LINE, REST) where
243 244 DECORATOR: the input decorator (or None)
244 245 INPUT_LINE: the input as string (possibly multi-line)
245 246 REST : any stdout generated by the input line (not OUTPUT)
246 247
247 248 OUTPUT: the output string, possibly multi-line
248 249
249 250 """
250 251 block = []
251 252 lines = part.split('\n')
252 253 N = len(lines)
253 254 i = 0
254 255 decorator = None
255 256 while 1:
256 257
257 258 if i==N:
258 259 # nothing left to parse -- the last line
259 260 break
260 261
261 262 line = lines[i]
262 263 i += 1
263 264 line_stripped = line.strip()
264 265 if line_stripped.startswith('#'):
265 266 block.append((COMMENT, line))
266 267 continue
267 268
268 269 if any(
269 270 line_stripped.startswith("@" + pseudo_decorator)
270 271 for pseudo_decorator in PSEUDO_DECORATORS
271 272 ):
272 273 if decorator:
273 274 raise RuntimeError(
274 275 "Applying multiple pseudo-decorators on one line is not supported"
275 276 )
276 277 else:
277 278 decorator = line_stripped
278 279 continue
279 280
280 281 # does this look like an input line?
281 282 matchin = rgxin.match(line)
282 283 if matchin:
283 284 lineno, inputline = int(matchin.group(1)), matchin.group(2)
284 285
285 286 # the ....: continuation string
286 287 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
287 288 Nc = len(continuation)
288 289 # input lines can continue on for more than one line, if
289 290 # we have a '\' line continuation char or a function call
290 291 # echo line 'print'. The input line can only be
291 292 # terminated by the end of the block or an output line, so
292 293 # we parse out the rest of the input line if it is
293 294 # multiline as well as any echo text
294 295
295 296 rest = []
296 297 while i<N:
297 298
298 299 # look ahead; if the next line is blank, or a comment, or
299 300 # an output line, we're done
300 301
301 302 nextline = lines[i]
302 303 matchout = rgxout.match(nextline)
303 304 # print("nextline=%s, continuation=%s, starts=%s"%(nextline, continuation, nextline.startswith(continuation)))
304 305 if matchout or nextline.startswith('#'):
305 306 break
306 307 elif nextline.startswith(continuation):
307 308 # The default ipython_rgx* treat the space following the colon as optional.
308 309 # However, If the space is there we must consume it or code
309 310 # employing the cython_magic extension will fail to execute.
310 311 #
311 312 # This works with the default ipython_rgx* patterns,
312 313 # If you modify them, YMMV.
313 314 nextline = nextline[Nc:]
314 315 if nextline and nextline[0] == ' ':
315 316 nextline = nextline[1:]
316 317
317 318 inputline += '\n' + nextline
318 319 else:
319 320 rest.append(nextline)
320 321 i+= 1
321 322
322 323 block.append((INPUT, (decorator, inputline, '\n'.join(rest))))
323 324 continue
324 325
325 326 # if it looks like an output line grab all the text to the end
326 327 # of the block
327 328 matchout = rgxout.match(line)
328 329 if matchout:
329 330 lineno, output = int(matchout.group(1)), matchout.group(2)
330 331 if i<N-1:
331 332 output = '\n'.join([output] + lines[i:])
332 333
333 334 block.append((OUTPUT, output))
334 335 break
335 336
336 337 return block
337 338
338 339
339 340 class EmbeddedSphinxShell(object):
340 341 """An embedded IPython instance to run inside Sphinx"""
341 342
342 343 def __init__(self, exec_lines=None):
343 344
344 345 self.cout = StringIO()
345 346
346 347 if exec_lines is None:
347 348 exec_lines = []
348 349
349 350 # Create config object for IPython
350 351 config = Config()
351 352 config.HistoryManager.hist_file = ':memory:'
352 353 config.InteractiveShell.autocall = False
353 354 config.InteractiveShell.autoindent = False
354 355 config.InteractiveShell.colors = 'NoColor'
355 356
356 357 # create a profile so instance history isn't saved
357 358 tmp_profile_dir = tempfile.mkdtemp(prefix='profile_')
358 359 profname = 'auto_profile_sphinx_build'
359 360 pdir = os.path.join(tmp_profile_dir,profname)
360 361 profile = ProfileDir.create_profile_dir(pdir)
361 362
362 363 # Create and initialize global ipython, but don't start its mainloop.
363 364 # This will persist across different EmbeddedSphinxShell instances.
364 365 IP = InteractiveShell.instance(config=config, profile_dir=profile)
365 366 atexit.register(self.cleanup)
366 367
367 368 # Store a few parts of IPython we'll need.
368 369 self.IP = IP
369 370 self.user_ns = self.IP.user_ns
370 371 self.user_global_ns = self.IP.user_global_ns
371 372
372 373 self.input = ''
373 374 self.output = ''
374 375 self.tmp_profile_dir = tmp_profile_dir
375 376
376 377 self.is_verbatim = False
377 378 self.is_doctest = False
378 379 self.is_suppress = False
379 380
380 381 # Optionally, provide more detailed information to shell.
381 382 # this is assigned by the SetUp method of IPythonDirective
382 383 # to point at itself.
383 384 #
384 385 # So, you can access handy things at self.directive.state
385 386 self.directive = None
386 387
387 388 # on the first call to the savefig decorator, we'll import
388 389 # pyplot as plt so we can make a call to the plt.gcf().savefig
389 390 self._pyplot_imported = False
390 391
391 392 # Prepopulate the namespace.
392 393 for line in exec_lines:
393 394 self.process_input_line(line, store_history=False)
394 395
395 396 def cleanup(self):
396 397 shutil.rmtree(self.tmp_profile_dir, ignore_errors=True)
397 398
398 399 def clear_cout(self):
399 400 self.cout.seek(0)
400 401 self.cout.truncate(0)
401 402
402 403 def process_input_line(self, line, store_history):
403 404 return self.process_input_lines([line], store_history=store_history)
404 405
405 406 def process_input_lines(self, lines, store_history=True):
406 407 """process the input, capturing stdout"""
407 408 stdout = sys.stdout
408 409 source_raw = '\n'.join(lines)
409 410 try:
410 411 sys.stdout = self.cout
411 412 self.IP.run_cell(source_raw, store_history=store_history)
412 413 finally:
413 414 sys.stdout = stdout
414 415
415 416 def process_image(self, decorator):
416 417 """
417 418 # build out an image directive like
418 419 # .. image:: somefile.png
419 420 # :width 4in
420 421 #
421 422 # from an input like
422 423 # savefig somefile.png width=4in
423 424 """
424 425 savefig_dir = self.savefig_dir
425 426 source_dir = self.source_dir
426 427 saveargs = decorator.split(' ')
427 428 filename = saveargs[1]
428 # insert relative path to image file in source
429 # insert relative path to image file in source
429 430 # as absolute path for Sphinx
430 431 # sphinx expects a posix path, even on Windows
431 432 path = pathlib.Path(savefig_dir, filename)
432 433 outfile = '/' + path.relative_to(source_dir).as_posix()
433 434
434 435 imagerows = ['.. image:: %s' % outfile]
435 436
436 437 for kwarg in saveargs[2:]:
437 438 arg, val = kwarg.split('=')
438 439 arg = arg.strip()
439 440 val = val.strip()
440 441 imagerows.append(' :%s: %s'%(arg, val))
441 442
442 443 image_file = os.path.basename(outfile) # only return file name
443 444 image_directive = '\n'.join(imagerows)
444 445 return image_file, image_directive
445 446
446 447 # Callbacks for each type of token
447 448 def process_input(self, data, input_prompt, lineno):
448 449 """
449 450 Process data block for INPUT token.
450 451
451 452 """
452 453 decorator, input, rest = data
453 454 image_file = None
454 455 image_directive = None
455 456
456 457 is_verbatim = decorator=='@verbatim' or self.is_verbatim
457 458 is_doctest = (decorator is not None and \
458 459 decorator.startswith('@doctest')) or self.is_doctest
459 460 is_suppress = decorator=='@suppress' or self.is_suppress
460 461 is_okexcept = decorator=='@okexcept' or self.is_okexcept
461 462 is_okwarning = decorator=='@okwarning' or self.is_okwarning
462 463 is_savefig = decorator is not None and \
463 464 decorator.startswith('@savefig')
464 465
465 466 input_lines = input.split('\n')
466 467 if len(input_lines) > 1:
467 468 if input_lines[-1] != "":
468 469 input_lines.append('') # make sure there's a blank line
469 470 # so splitter buffer gets reset
470 471
471 472 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
472 473
473 474 if is_savefig:
474 475 image_file, image_directive = self.process_image(decorator)
475 476
476 477 ret = []
477 478 is_semicolon = False
478 479
479 480 # Hold the execution count, if requested to do so.
480 481 if is_suppress and self.hold_count:
481 482 store_history = False
482 483 else:
483 484 store_history = True
484 485
485 486 # Note: catch_warnings is not thread safe
486 487 with warnings.catch_warnings(record=True) as ws:
487 488 if input_lines[0].endswith(';'):
488 489 is_semicolon = True
489 490 #for i, line in enumerate(input_lines):
490 491
491 492 # process the first input line
492 493 if is_verbatim:
493 494 self.process_input_lines([''])
494 495 self.IP.execution_count += 1 # increment it anyway
495 496 else:
496 497 # only submit the line in non-verbatim mode
497 498 self.process_input_lines(input_lines, store_history=store_history)
498 499
499 500 if not is_suppress:
500 501 for i, line in enumerate(input_lines):
501 502 if i == 0:
502 503 formatted_line = '%s %s'%(input_prompt, line)
503 504 else:
504 505 formatted_line = '%s %s'%(continuation, line)
505 506 ret.append(formatted_line)
506 507
507 508 if not is_suppress and len(rest.strip()) and is_verbatim:
508 509 # The "rest" is the standard output of the input. This needs to be
509 510 # added when in verbatim mode. If there is no "rest", then we don't
510 511 # add it, as the new line will be added by the processed output.
511 512 ret.append(rest)
512 513
513 514 # Fetch the processed output. (This is not the submitted output.)
514 515 self.cout.seek(0)
515 516 processed_output = self.cout.read()
516 517 if not is_suppress and not is_semicolon:
517 518 #
518 519 # In IPythonDirective.run, the elements of `ret` are eventually
519 520 # combined such that '' entries correspond to newlines. So if
520 521 # `processed_output` is equal to '', then the adding it to `ret`
521 522 # ensures that there is a blank line between consecutive inputs
522 523 # that have no outputs, as in:
523 524 #
524 525 # In [1]: x = 4
525 526 #
526 527 # In [2]: x = 5
527 528 #
528 529 # When there is processed output, it has a '\n' at the tail end. So
529 530 # adding the output to `ret` will provide the necessary spacing
530 531 # between consecutive input/output blocks, as in:
531 532 #
532 533 # In [1]: x
533 534 # Out[1]: 5
534 535 #
535 536 # In [2]: x
536 537 # Out[2]: 5
537 538 #
538 539 # When there is stdout from the input, it also has a '\n' at the
539 540 # tail end, and so this ensures proper spacing as well. E.g.:
540 541 #
541 542 # In [1]: print(x)
542 543 # 5
543 544 #
544 545 # In [2]: x = 5
545 546 #
546 547 # When in verbatim mode, `processed_output` is empty (because
547 548 # nothing was passed to IP. Sometimes the submitted code block has
548 549 # an Out[] portion and sometimes it does not. When it does not, we
549 550 # need to ensure proper spacing, so we have to add '' to `ret`.
550 551 # However, if there is an Out[] in the submitted code, then we do
551 552 # not want to add a newline as `process_output` has stuff to add.
552 553 # The difficulty is that `process_input` doesn't know if
553 554 # `process_output` will be called---so it doesn't know if there is
554 555 # Out[] in the code block. The requires that we include a hack in
555 556 # `process_block`. See the comments there.
556 557 #
557 558 ret.append(processed_output)
558 559 elif is_semicolon:
559 560 # Make sure there is a newline after the semicolon.
560 561 ret.append('')
561 562
562 563 # context information
563 564 filename = "Unknown"
564 565 lineno = 0
565 566 if self.directive.state:
566 567 filename = self.directive.state.document.current_source
567 568 lineno = self.directive.state.document.current_line
568 569
569 570 # Use sphinx logger for warnings
570 571 logger = logging.getLogger(__name__)
571 572
572 573 # output any exceptions raised during execution to stdout
573 574 # unless :okexcept: has been specified.
574 575 if not is_okexcept and (
575 576 ("Traceback" in processed_output) or ("SyntaxError" in processed_output)
576 577 ):
577 578 s = "\n>>>" + ("-" * 73) + "\n"
578 579 s += "Exception in %s at block ending on line %s\n" % (filename, lineno)
579 580 s += "Specify :okexcept: as an option in the ipython:: block to suppress this message\n"
580 581 s += processed_output + "\n"
581 582 s += "<<<" + ("-" * 73)
582 583 logger.warning(s)
583 584 if self.warning_is_error:
584 585 raise RuntimeError(
585 586 "Unexpected exception in `{}` line {}".format(filename, lineno)
586 587 )
587 588
588 589 # output any warning raised during execution to stdout
589 590 # unless :okwarning: has been specified.
590 591 if not is_okwarning:
591 592 for w in ws:
592 593 s = "\n>>>" + ("-" * 73) + "\n"
593 594 s += "Warning in %s at block ending on line %s\n" % (filename, lineno)
594 595 s += "Specify :okwarning: as an option in the ipython:: block to suppress this message\n"
595 596 s += ("-" * 76) + "\n"
596 597 s += warnings.formatwarning(
597 598 w.message, w.category, w.filename, w.lineno, w.line
598 599 )
599 600 s += "<<<" + ("-" * 73)
600 601 logger.warning(s)
601 602 if self.warning_is_error:
602 603 raise RuntimeError(
603 604 "Unexpected warning in `{}` line {}".format(filename, lineno)
604 605 )
605 606
606 607 self.clear_cout()
607 608 return (ret, input_lines, processed_output,
608 609 is_doctest, decorator, image_file, image_directive)
609 610
610 611
611 612 def process_output(self, data, output_prompt, input_lines, output,
612 613 is_doctest, decorator, image_file):
613 614 """
614 615 Process data block for OUTPUT token.
615 616
616 617 """
617 618 # Recall: `data` is the submitted output, and `output` is the processed
618 619 # output from `input_lines`.
619 620
620 621 TAB = ' ' * 4
621 622
622 623 if is_doctest and output is not None:
623 624
624 625 found = output # This is the processed output
625 626 found = found.strip()
626 627 submitted = data.strip()
627 628
628 629 if self.directive is None:
629 630 source = 'Unavailable'
630 631 content = 'Unavailable'
631 632 else:
632 633 source = self.directive.state.document.current_source
633 634 content = self.directive.content
634 635 # Add tabs and join into a single string.
635 636 content = '\n'.join([TAB + line for line in content])
636 637
637 638 # Make sure the output contains the output prompt.
638 639 ind = found.find(output_prompt)
639 640 if ind < 0:
640 641 e = ('output does not contain output prompt\n\n'
641 642 'Document source: {0}\n\n'
642 643 'Raw content: \n{1}\n\n'
643 644 'Input line(s):\n{TAB}{2}\n\n'
644 645 'Output line(s):\n{TAB}{3}\n\n')
645 646 e = e.format(source, content, '\n'.join(input_lines),
646 647 repr(found), TAB=TAB)
647 648 raise RuntimeError(e)
648 649 found = found[len(output_prompt):].strip()
649 650
650 651 # Handle the actual doctest comparison.
651 652 if decorator.strip() == '@doctest':
652 653 # Standard doctest
653 654 if found != submitted:
654 655 e = ('doctest failure\n\n'
655 656 'Document source: {0}\n\n'
656 657 'Raw content: \n{1}\n\n'
657 658 'On input line(s):\n{TAB}{2}\n\n'
658 659 'we found output:\n{TAB}{3}\n\n'
659 660 'instead of the expected:\n{TAB}{4}\n\n')
660 661 e = e.format(source, content, '\n'.join(input_lines),
661 662 repr(found), repr(submitted), TAB=TAB)
662 663 raise RuntimeError(e)
663 664 else:
664 665 self.custom_doctest(decorator, input_lines, found, submitted)
665 666
666 667 # When in verbatim mode, this holds additional submitted output
667 668 # to be written in the final Sphinx output.
668 669 # https://github.com/ipython/ipython/issues/5776
669 670 out_data = []
670 671
671 672 is_verbatim = decorator=='@verbatim' or self.is_verbatim
672 673 if is_verbatim and data.strip():
673 674 # Note that `ret` in `process_block` has '' as its last element if
674 675 # the code block was in verbatim mode. So if there is no submitted
675 676 # output, then we will have proper spacing only if we do not add
676 677 # an additional '' to `out_data`. This is why we condition on
677 678 # `and data.strip()`.
678 679
679 680 # The submitted output has no output prompt. If we want the
680 681 # prompt and the code to appear, we need to join them now
681 682 # instead of adding them separately---as this would create an
682 683 # undesired newline. How we do this ultimately depends on the
683 684 # format of the output regex. I'll do what works for the default
684 685 # prompt for now, and we might have to adjust if it doesn't work
685 686 # in other cases. Finally, the submitted output does not have
686 687 # a trailing newline, so we must add it manually.
687 688 out_data.append("{0} {1}\n".format(output_prompt, data))
688 689
689 690 return out_data
690 691
691 692 def process_comment(self, data):
692 693 """Process data fPblock for COMMENT token."""
693 694 if not self.is_suppress:
694 695 return [data]
695 696
696 697 def save_image(self, image_file):
697 698 """
698 699 Saves the image file to disk.
699 700 """
700 701 self.ensure_pyplot()
701 702 command = 'plt.gcf().savefig("%s")'%image_file
702 703 # print('SAVEFIG', command) # dbg
703 704 self.process_input_line('bookmark ipy_thisdir', store_history=False)
704 705 self.process_input_line('cd -b ipy_savedir', store_history=False)
705 706 self.process_input_line(command, store_history=False)
706 707 self.process_input_line('cd -b ipy_thisdir', store_history=False)
707 708 self.process_input_line('bookmark -d ipy_thisdir', store_history=False)
708 709 self.clear_cout()
709 710
710 711 def process_block(self, block):
711 712 """
712 713 process block from the block_parser and return a list of processed lines
713 714 """
714 715 ret = []
715 716 output = None
716 717 input_lines = None
717 718 lineno = self.IP.execution_count
718 719
719 720 input_prompt = self.promptin % lineno
720 721 output_prompt = self.promptout % lineno
721 722 image_file = None
722 723 image_directive = None
723 724
724 725 found_input = False
725 726 for token, data in block:
726 727 if token == COMMENT:
727 728 out_data = self.process_comment(data)
728 729 elif token == INPUT:
729 730 found_input = True
730 731 (out_data, input_lines, output, is_doctest,
731 732 decorator, image_file, image_directive) = \
732 733 self.process_input(data, input_prompt, lineno)
733 734 elif token == OUTPUT:
734 735 if not found_input:
735 736
736 737 TAB = ' ' * 4
737 738 linenumber = 0
738 739 source = 'Unavailable'
739 740 content = 'Unavailable'
740 741 if self.directive:
741 742 linenumber = self.directive.state.document.current_line
742 743 source = self.directive.state.document.current_source
743 744 content = self.directive.content
744 745 # Add tabs and join into a single string.
745 746 content = '\n'.join([TAB + line for line in content])
746 747
747 748 e = ('\n\nInvalid block: Block contains an output prompt '
748 749 'without an input prompt.\n\n'
749 750 'Document source: {0}\n\n'
750 751 'Content begins at line {1}: \n\n{2}\n\n'
751 752 'Problematic block within content: \n\n{TAB}{3}\n\n')
752 753 e = e.format(source, linenumber, content, block, TAB=TAB)
753 754
754 755 # Write, rather than include in exception, since Sphinx
755 756 # will truncate tracebacks.
756 757 sys.stdout.write(e)
757 758 raise RuntimeError('An invalid block was detected.')
758 759 out_data = \
759 760 self.process_output(data, output_prompt, input_lines,
760 761 output, is_doctest, decorator,
761 762 image_file)
762 763 if out_data:
763 764 # Then there was user submitted output in verbatim mode.
764 765 # We need to remove the last element of `ret` that was
765 766 # added in `process_input`, as it is '' and would introduce
766 767 # an undesirable newline.
767 768 assert(ret[-1] == '')
768 769 del ret[-1]
769 770
770 771 if out_data:
771 772 ret.extend(out_data)
772 773
773 774 # save the image files
774 775 if image_file is not None:
775 776 self.save_image(image_file)
776 777
777 778 return ret, image_directive
778 779
779 780 def ensure_pyplot(self):
780 781 """
781 782 Ensures that pyplot has been imported into the embedded IPython shell.
782 783
783 784 Also, makes sure to set the backend appropriately if not set already.
784 785
785 786 """
786 787 # We are here if the @figure pseudo decorator was used. Thus, it's
787 788 # possible that we could be here even if python_mplbackend were set to
788 789 # `None`. That's also strange and perhaps worthy of raising an
789 790 # exception, but for now, we just set the backend to 'agg'.
790 791
791 792 if not self._pyplot_imported:
792 793 if 'matplotlib.backends' not in sys.modules:
793 794 # Then ipython_matplotlib was set to None but there was a
794 795 # call to the @figure decorator (and ipython_execlines did
795 796 # not set a backend).
796 797 #raise Exception("No backend was set, but @figure was used!")
797 798 import matplotlib
798 799 matplotlib.use('agg')
799 800
800 801 # Always import pyplot into embedded shell.
801 802 self.process_input_line('import matplotlib.pyplot as plt',
802 803 store_history=False)
803 804 self._pyplot_imported = True
804 805
805 806 def process_pure_python(self, content):
806 807 """
807 808 content is a list of strings. it is unedited directive content
808 809
809 810 This runs it line by line in the InteractiveShell, prepends
810 811 prompts as needed capturing stderr and stdout, then returns
811 812 the content as a list as if it were ipython code
812 813 """
813 814 output = []
814 815 savefig = False # keep up with this to clear figure
815 816 multiline = False # to handle line continuation
816 817 multiline_start = None
817 818 fmtin = self.promptin
818 819
819 820 ct = 0
820 821
821 822 for lineno, line in enumerate(content):
822 823
823 824 line_stripped = line.strip()
824 825 if not len(line):
825 826 output.append(line)
826 827 continue
827 828
828 829 # handle pseudo-decorators, whilst ensuring real python decorators are treated as input
829 830 if any(
830 831 line_stripped.startswith("@" + pseudo_decorator)
831 832 for pseudo_decorator in PSEUDO_DECORATORS
832 833 ):
833 834 output.extend([line])
834 835 if 'savefig' in line:
835 836 savefig = True # and need to clear figure
836 837 continue
837 838
838 839 # handle comments
839 840 if line_stripped.startswith('#'):
840 841 output.extend([line])
841 842 continue
842 843
843 844 # deal with lines checking for multiline
844 845 continuation = u' %s:'% ''.join(['.']*(len(str(ct))+2))
845 846 if not multiline:
846 847 modified = u"%s %s" % (fmtin % ct, line_stripped)
847 848 output.append(modified)
848 849 ct += 1
849 850 try:
850 851 ast.parse(line_stripped)
851 852 output.append(u'')
852 853 except Exception: # on a multiline
853 854 multiline = True
854 855 multiline_start = lineno
855 856 else: # still on a multiline
856 857 modified = u'%s %s' % (continuation, line)
857 858 output.append(modified)
858 859
859 860 # if the next line is indented, it should be part of multiline
860 861 if len(content) > lineno + 1:
861 862 nextline = content[lineno + 1]
862 863 if len(nextline) - len(nextline.lstrip()) > 3:
863 864 continue
864 865 try:
865 866 mod = ast.parse(
866 867 '\n'.join(content[multiline_start:lineno+1]))
867 868 if isinstance(mod.body[0], ast.FunctionDef):
868 869 # check to see if we have the whole function
869 870 for element in mod.body[0].body:
870 871 if isinstance(element, ast.Return):
871 872 multiline = False
872 873 else:
873 874 output.append(u'')
874 875 multiline = False
875 876 except Exception:
876 877 pass
877 878
878 879 if savefig: # clear figure if plotted
879 880 self.ensure_pyplot()
880 881 self.process_input_line('plt.clf()', store_history=False)
881 882 self.clear_cout()
882 883 savefig = False
883 884
884 885 return output
885 886
886 887 def custom_doctest(self, decorator, input_lines, found, submitted):
887 888 """
888 889 Perform a specialized doctest.
889 890
890 891 """
891 892 from .custom_doctests import doctests
892 893
893 894 args = decorator.split()
894 895 doctest_type = args[1]
895 896 if doctest_type in doctests:
896 897 doctests[doctest_type](self, args, input_lines, found, submitted)
897 898 else:
898 899 e = "Invalid option to @doctest: {0}".format(doctest_type)
899 900 raise Exception(e)
900 901
901 902
902 903 class IPythonDirective(Directive):
903 904
904 has_content = True
905 required_arguments = 0
906 optional_arguments = 4 # python, suppress, verbatim, doctest
907 final_argumuent_whitespace = True
908 option_spec = { 'python': directives.unchanged,
905 has_content: bool = True
906 required_arguments: int = 0
907 optional_arguments: int = 4 # python, suppress, verbatim, doctest
908 final_argumuent_whitespace: bool = True
909 option_spec: Dict[str, Any] = { 'python': directives.unchanged,
909 910 'suppress' : directives.flag,
910 911 'verbatim' : directives.flag,
911 912 'doctest' : directives.flag,
912 913 'okexcept': directives.flag,
913 914 'okwarning': directives.flag
914 915 }
915 916
916 917 shell = None
917 918
918 seen_docs = set()
919 seen_docs: Set = set()
919 920
920 921 def get_config_options(self):
921 922 # contains sphinx configuration variables
922 923 config = self.state.document.settings.env.config
923 924
924 925 # get config variables to set figure output directory
925 926 savefig_dir = config.ipython_savefig_dir
926 927 source_dir = self.state.document.settings.env.srcdir
927 928 savefig_dir = os.path.join(source_dir, savefig_dir)
928 929
929 930 # get regex and prompt stuff
930 931 rgxin = config.ipython_rgxin
931 932 rgxout = config.ipython_rgxout
932 933 warning_is_error= config.ipython_warning_is_error
933 934 promptin = config.ipython_promptin
934 935 promptout = config.ipython_promptout
935 936 mplbackend = config.ipython_mplbackend
936 937 exec_lines = config.ipython_execlines
937 938 hold_count = config.ipython_holdcount
938 939
939 940 return (savefig_dir, source_dir, rgxin, rgxout,
940 941 promptin, promptout, mplbackend, exec_lines, hold_count, warning_is_error)
941 942
942 943 def setup(self):
943 944 # Get configuration values.
944 945 (savefig_dir, source_dir, rgxin, rgxout, promptin, promptout,
945 946 mplbackend, exec_lines, hold_count, warning_is_error) = self.get_config_options()
946 947
947 948 try:
948 949 os.makedirs(savefig_dir)
949 950 except OSError as e:
950 951 if e.errno != errno.EEXIST:
951 952 raise
952 953
953 954 if self.shell is None:
954 955 # We will be here many times. However, when the
955 956 # EmbeddedSphinxShell is created, its interactive shell member
956 957 # is the same for each instance.
957 958
958 959 if mplbackend and 'matplotlib.backends' not in sys.modules and use_matplotlib:
959 960 import matplotlib
960 961 matplotlib.use(mplbackend)
961 962
962 963 # Must be called after (potentially) importing matplotlib and
963 964 # setting its backend since exec_lines might import pylab.
964 965 self.shell = EmbeddedSphinxShell(exec_lines)
965 966
966 967 # Store IPython directive to enable better error messages
967 968 self.shell.directive = self
968 969
969 970 # reset the execution count if we haven't processed this doc
970 971 #NOTE: this may be borked if there are multiple seen_doc tmp files
971 972 #check time stamp?
972 973 if not self.state.document.current_source in self.seen_docs:
973 974 self.shell.IP.history_manager.reset()
974 975 self.shell.IP.execution_count = 1
975 976 self.seen_docs.add(self.state.document.current_source)
976 977
977 978 # and attach to shell so we don't have to pass them around
978 979 self.shell.rgxin = rgxin
979 980 self.shell.rgxout = rgxout
980 981 self.shell.promptin = promptin
981 982 self.shell.promptout = promptout
982 983 self.shell.savefig_dir = savefig_dir
983 984 self.shell.source_dir = source_dir
984 985 self.shell.hold_count = hold_count
985 986 self.shell.warning_is_error = warning_is_error
986 987
987 988 # setup bookmark for saving figures directory
988 989 self.shell.process_input_line(
989 990 'bookmark ipy_savedir "%s"' % savefig_dir, store_history=False
990 991 )
991 992 self.shell.clear_cout()
992 993
993 994 return rgxin, rgxout, promptin, promptout
994 995
995 996 def teardown(self):
996 997 # delete last bookmark
997 998 self.shell.process_input_line('bookmark -d ipy_savedir',
998 999 store_history=False)
999 1000 self.shell.clear_cout()
1000 1001
1001 1002 def run(self):
1002 1003 debug = False
1003 1004
1004 1005 #TODO, any reason block_parser can't be a method of embeddable shell
1005 1006 # then we wouldn't have to carry these around
1006 1007 rgxin, rgxout, promptin, promptout = self.setup()
1007 1008
1008 1009 options = self.options
1009 1010 self.shell.is_suppress = 'suppress' in options
1010 1011 self.shell.is_doctest = 'doctest' in options
1011 1012 self.shell.is_verbatim = 'verbatim' in options
1012 1013 self.shell.is_okexcept = 'okexcept' in options
1013 1014 self.shell.is_okwarning = 'okwarning' in options
1014 1015
1015 1016 # handle pure python code
1016 1017 if 'python' in self.arguments:
1017 1018 content = self.content
1018 1019 self.content = self.shell.process_pure_python(content)
1019 1020
1020 1021 # parts consists of all text within the ipython-block.
1021 1022 # Each part is an input/output block.
1022 1023 parts = '\n'.join(self.content).split('\n\n')
1023 1024
1024 1025 lines = ['.. code-block:: ipython', '']
1025 1026 figures = []
1026 1027
1027 1028 # Use sphinx logger for warnings
1028 1029 logger = logging.getLogger(__name__)
1029 1030
1030 1031 for part in parts:
1031 1032 block = block_parser(part, rgxin, rgxout, promptin, promptout)
1032 1033 if len(block):
1033 1034 rows, figure = self.shell.process_block(block)
1034 1035 for row in rows:
1035 1036 lines.extend([' {0}'.format(line)
1036 1037 for line in row.split('\n')])
1037 1038
1038 1039 if figure is not None:
1039 1040 figures.append(figure)
1040 1041 else:
1041 1042 message = 'Code input with no code at {}, line {}'\
1042 1043 .format(
1043 1044 self.state.document.current_source,
1044 1045 self.state.document.current_line)
1045 1046 if self.shell.warning_is_error:
1046 1047 raise RuntimeError(message)
1047 1048 else:
1048 1049 logger.warning(message)
1049 1050
1050 1051 for figure in figures:
1051 1052 lines.append('')
1052 1053 lines.extend(figure.split('\n'))
1053 1054 lines.append('')
1054 1055
1055 1056 if len(lines) > 2:
1056 1057 if debug:
1057 1058 print('\n'.join(lines))
1058 1059 else:
1059 1060 # This has to do with input, not output. But if we comment
1060 1061 # these lines out, then no IPython code will appear in the
1061 1062 # final output.
1062 1063 self.state_machine.insert_input(
1063 1064 lines, self.state_machine.input_lines.source(0))
1064 1065
1065 1066 # cleanup
1066 1067 self.teardown()
1067 1068
1068 1069 return []
1069 1070
1070 1071 # Enable as a proper Sphinx directive
1071 1072 def setup(app):
1072 1073 setup.app = app
1073 1074
1074 1075 app.add_directive('ipython', IPythonDirective)
1075 1076 app.add_config_value('ipython_savefig_dir', 'savefig', 'env')
1076 1077 app.add_config_value('ipython_warning_is_error', True, 'env')
1077 1078 app.add_config_value('ipython_rgxin',
1078 1079 re.compile(r'In \[(\d+)\]:\s?(.*)\s*'), 'env')
1079 1080 app.add_config_value('ipython_rgxout',
1080 1081 re.compile(r'Out\[(\d+)\]:\s?(.*)\s*'), 'env')
1081 1082 app.add_config_value('ipython_promptin', 'In [%d]:', 'env')
1082 1083 app.add_config_value('ipython_promptout', 'Out[%d]:', 'env')
1083 1084
1084 1085 # We could just let matplotlib pick whatever is specified as the default
1085 1086 # backend in the matplotlibrc file, but this would cause issues if the
1086 1087 # backend didn't work in headless environments. For this reason, 'agg'
1087 1088 # is a good default backend choice.
1088 1089 app.add_config_value('ipython_mplbackend', 'agg', 'env')
1089 1090
1090 1091 # If the user sets this config value to `None`, then EmbeddedSphinxShell's
1091 1092 # __init__ method will treat it as [].
1092 1093 execlines = ['import numpy as np']
1093 1094 if use_matplotlib:
1094 1095 execlines.append('import matplotlib.pyplot as plt')
1095 1096 app.add_config_value('ipython_execlines', execlines, 'env')
1096 1097
1097 1098 app.add_config_value('ipython_holdcount', True, 'env')
1098 1099
1099 1100 metadata = {'parallel_read_safe': True, 'parallel_write_safe': True}
1100 1101 return metadata
1101 1102
1102 1103 # Simple smoke test, needs to be converted to a proper automatic test.
1103 1104 def test():
1104 1105
1105 1106 examples = [
1106 1107 r"""
1107 1108 In [9]: pwd
1108 1109 Out[9]: '/home/jdhunter/py4science/book'
1109 1110
1110 1111 In [10]: cd bookdata/
1111 1112 /home/jdhunter/py4science/book/bookdata
1112 1113
1113 1114 In [2]: from pylab import *
1114 1115
1115 1116 In [2]: ion()
1116 1117
1117 1118 In [3]: im = imread('stinkbug.png')
1118 1119
1119 1120 @savefig mystinkbug.png width=4in
1120 1121 In [4]: imshow(im)
1121 1122 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
1122 1123
1123 1124 """,
1124 1125 r"""
1125 1126
1126 1127 In [1]: x = 'hello world'
1127 1128
1128 1129 # string methods can be
1129 1130 # used to alter the string
1130 1131 @doctest
1131 1132 In [2]: x.upper()
1132 1133 Out[2]: 'HELLO WORLD'
1133 1134
1134 1135 @verbatim
1135 1136 In [3]: x.st<TAB>
1136 1137 x.startswith x.strip
1137 1138 """,
1138 1139 r"""
1139 1140
1140 1141 In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\
1141 1142 .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv'
1142 1143
1143 1144 In [131]: print url.split('&')
1144 1145 ['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']
1145 1146
1146 1147 In [60]: import urllib
1147 1148
1148 1149 """,
1149 1150 r"""\
1150 1151
1151 1152 In [133]: import numpy.random
1152 1153
1153 1154 @suppress
1154 1155 In [134]: numpy.random.seed(2358)
1155 1156
1156 1157 @doctest
1157 1158 In [135]: numpy.random.rand(10,2)
1158 1159 Out[135]:
1159 1160 array([[ 0.64524308, 0.59943846],
1160 1161 [ 0.47102322, 0.8715456 ],
1161 1162 [ 0.29370834, 0.74776844],
1162 1163 [ 0.99539577, 0.1313423 ],
1163 1164 [ 0.16250302, 0.21103583],
1164 1165 [ 0.81626524, 0.1312433 ],
1165 1166 [ 0.67338089, 0.72302393],
1166 1167 [ 0.7566368 , 0.07033696],
1167 1168 [ 0.22591016, 0.77731835],
1168 1169 [ 0.0072729 , 0.34273127]])
1169 1170
1170 1171 """,
1171 1172
1172 1173 r"""
1173 1174 In [106]: print x
1174 1175 jdh
1175 1176
1176 1177 In [109]: for i in range(10):
1177 1178 .....: print i
1178 1179 .....:
1179 1180 .....:
1180 1181 0
1181 1182 1
1182 1183 2
1183 1184 3
1184 1185 4
1185 1186 5
1186 1187 6
1187 1188 7
1188 1189 8
1189 1190 9
1190 1191 """,
1191 1192
1192 1193 r"""
1193 1194
1194 1195 In [144]: from pylab import *
1195 1196
1196 1197 In [145]: ion()
1197 1198
1198 1199 # use a semicolon to suppress the output
1199 1200 @savefig test_hist.png width=4in
1200 1201 In [151]: hist(np.random.randn(10000), 100);
1201 1202
1202 1203
1203 1204 @savefig test_plot.png width=4in
1204 1205 In [151]: plot(np.random.randn(10000), 'o');
1205 1206 """,
1206 1207
1207 1208 r"""
1208 1209 # use a semicolon to suppress the output
1209 1210 In [151]: plt.clf()
1210 1211
1211 1212 @savefig plot_simple.png width=4in
1212 1213 In [151]: plot([1,2,3])
1213 1214
1214 1215 @savefig hist_simple.png width=4in
1215 1216 In [151]: hist(np.random.randn(10000), 100);
1216 1217
1217 1218 """,
1218 1219 r"""
1219 1220 # update the current fig
1220 1221 In [151]: ylabel('number')
1221 1222
1222 1223 In [152]: title('normal distribution')
1223 1224
1224 1225
1225 1226 @savefig hist_with_text.png
1226 1227 In [153]: grid(True)
1227 1228
1228 1229 @doctest float
1229 1230 In [154]: 0.1 + 0.2
1230 1231 Out[154]: 0.3
1231 1232
1232 1233 @doctest float
1233 1234 In [155]: np.arange(16).reshape(4,4)
1234 1235 Out[155]:
1235 1236 array([[ 0, 1, 2, 3],
1236 1237 [ 4, 5, 6, 7],
1237 1238 [ 8, 9, 10, 11],
1238 1239 [12, 13, 14, 15]])
1239 1240
1240 1241 In [1]: x = np.arange(16, dtype=float).reshape(4,4)
1241 1242
1242 1243 In [2]: x[0,0] = np.inf
1243 1244
1244 1245 In [3]: x[0,1] = np.nan
1245 1246
1246 1247 @doctest float
1247 1248 In [4]: x
1248 1249 Out[4]:
1249 1250 array([[ inf, nan, 2., 3.],
1250 1251 [ 4., 5., 6., 7.],
1251 1252 [ 8., 9., 10., 11.],
1252 1253 [ 12., 13., 14., 15.]])
1253 1254
1254 1255
1255 1256 """,
1256 1257 ]
1257 1258 # skip local-file depending first example:
1258 1259 examples = examples[1:]
1259 1260
1260 1261 #ipython_directive.DEBUG = True # dbg
1261 1262 #options = dict(suppress=True) # dbg
1262 1263 options = {}
1263 1264 for example in examples:
1264 1265 content = example.split('\n')
1265 1266 IPythonDirective('debug', arguments=None, options=options,
1266 1267 content=content, lineno=0,
1267 1268 content_offset=None, block_text=None,
1268 1269 state=None, state_machine=None,
1269 1270 )
1270 1271
1271 1272 # Run test suite as a script
1272 1273 if __name__=='__main__':
1273 1274 if not os.path.isdir('_static'):
1274 1275 os.mkdir('_static')
1275 1276 test()
1276 1277 print('All OK? Check figures in _static/')
General Comments 0
You need to be logged in to leave comments. Login now