Show More
@@ -69,7 +69,6 b' An example usage of the directive is:' | |||
|
69 | 69 | |
|
70 | 70 | In [3]: print(y) |
|
71 | 71 | |
|
72 | ||
|
73 | 72 | See http://matplotlib.org/sampledoc/ipython_directive.html for more additional |
|
74 | 73 | documentation. |
|
75 | 74 | |
@@ -133,6 +132,28 b' COMMENT, INPUT, OUTPUT = range(3)' | |||
|
133 | 132 | #----------------------------------------------------------------------------- |
|
134 | 133 | # Functions and class declarations |
|
135 | 134 | #----------------------------------------------------------------------------- |
|
135 | def str_to_array(s): | |
|
136 | """ | |
|
137 | Simplistic converter of strings from repr to float NumPy arrays. | |
|
138 | ||
|
139 | """ | |
|
140 | import numpy as np | |
|
141 | ||
|
142 | # Handle infs (assumes default printoptions for NumPy) | |
|
143 | s = s.replace(u'inf', u'np.inf') | |
|
144 | s = s.replace(u'nan', u'np.nan') | |
|
145 | ||
|
146 | if s.startswith(u'array'): | |
|
147 | # Remove array( and ) | |
|
148 | s = s[6:-1] | |
|
149 | ||
|
150 | if s.startswith(u'['): | |
|
151 | a = np.array(eval(s), dtype=float) | |
|
152 | else: | |
|
153 | # Assume its a regular float. Force 1D so we can index into it. | |
|
154 | a = np.atleast_1d(float(s)) | |
|
155 | return a | |
|
156 | ||
|
136 | 157 | def block_parser(part, rgxin, rgxout, fmtin, fmtout): |
|
137 | 158 | """ |
|
138 | 159 | part is a string of ipython text, comprised of at most one |
@@ -339,7 +360,8 b' class EmbeddedSphinxShell(object):' | |||
|
339 | 360 | image_directive = None |
|
340 | 361 | #print 'INPUT:', data # dbg |
|
341 | 362 | is_verbatim = decorator=='@verbatim' or self.is_verbatim |
|
342 |
is_doctest = decorator |
|
|
363 | is_doctest = (decorator is not None and \ | |
|
364 | decorator.startswith('@doctest')) or self.is_doctest | |
|
343 | 365 | is_suppress = decorator=='@suppress' or self.is_suppress |
|
344 | 366 | is_savefig = decorator is not None and \ |
|
345 | 367 | decorator.startswith('@savefig') |
@@ -396,38 +418,38 b' class EmbeddedSphinxShell(object):' | |||
|
396 | 418 | ret.append('') |
|
397 | 419 | |
|
398 | 420 | self.cout.truncate(0) |
|
399 | return (ret, input_lines, output, is_doctest, image_file, | |
|
421 | return (ret, input_lines, output, is_doctest, decorator, image_file, | |
|
400 | 422 | image_directive) |
|
401 | 423 | #print 'OUTPUT', output # dbg |
|
402 | 424 | |
|
403 | 425 | def process_output(self, data, output_prompt, |
|
404 | input_lines, output, is_doctest, image_file): | |
|
426 | input_lines, output, is_doctest, decorator, image_file): | |
|
405 | 427 | """Process data block for OUTPUT token.""" |
|
406 | if is_doctest: | |
|
407 | submitted = data.strip() | |
|
408 | found = output | |
|
409 | if found is not None: | |
|
410 | found = found.strip() | |
|
411 | ||
|
412 | # XXX - fperez: in 0.11, 'output' never comes with the prompt | |
|
413 | # in it, just the actual output text. So I think all this code | |
|
414 | # can be nuked... | |
|
415 | ||
|
416 | # the above comment does not appear to be accurate... (minrk) | |
|
428 | if is_doctest and output is not None: | |
|
417 | 429 | |
|
418 |
|
|
|
419 | if ind<0: | |
|
420 | e='output prompt="%s" does not match out line=%s' % \ | |
|
421 | (output_prompt, found) | |
|
422 | raise RuntimeError(e) | |
|
423 | found = found[len(output_prompt):].strip() | |
|
430 | found = output | |
|
431 | found = found.strip() | |
|
432 | submitted = data.strip() | |
|
424 | 433 | |
|
425 | if found!=submitted: | |
|
426 | e = ('doctest failure for input_lines="%s" with ' | |
|
427 | 'found_output="%s" and submitted output="%s"' % | |
|
428 | (input_lines, found, submitted) ) | |
|
434 | # Make sure the output contains the output prompt. | |
|
435 | ind = found.find(output_prompt) | |
|
436 | if ind < 0: | |
|
437 | e = ('output prompt="{0}" does ' | |
|
438 | 'not match out line={1}'.format(output_prompt, found)) | |
|
439 | raise RuntimeError(e) | |
|
440 | found = found[len(output_prompt):].strip() | |
|
441 | ||
|
442 | # Handle the actual doctest comparison. | |
|
443 | if decorator.strip() == '@doctest': | |
|
444 | # Standard doctest | |
|
445 | if found != submitted: | |
|
446 | e = ('doctest failure for input_lines="{0}" with ' | |
|
447 | 'found_output="{1}" and submitted ' | |
|
448 | 'output="{2}"'.format(input_lines, found, submitted) ) | |
|
429 | 449 | raise RuntimeError(e) |
|
430 | #print 'doctest PASSED for input_lines="%s" with found_output="%s" and submitted output="%s"'%(input_lines, found, submitted) | |
|
450 | else: | |
|
451 | self.specialized_doctest(decorator, input_lines, | |
|
452 | found, submitted) | |
|
431 | 453 | |
|
432 | 454 | def process_comment(self, data): |
|
433 | 455 | """Process data fPblock for COMMENT token.""" |
@@ -448,7 +470,6 b' class EmbeddedSphinxShell(object):' | |||
|
448 | 470 | self.process_input_line('bookmark -d ipy_thisdir', store_history=False) |
|
449 | 471 | self.clear_cout() |
|
450 | 472 | |
|
451 | ||
|
452 | 473 | def process_block(self, block): |
|
453 | 474 | """ |
|
454 | 475 | process block from the block_parser and return a list of processed lines |
@@ -467,14 +488,14 b' class EmbeddedSphinxShell(object):' | |||
|
467 | 488 | if token==COMMENT: |
|
468 | 489 | out_data = self.process_comment(data) |
|
469 | 490 | elif token==INPUT: |
|
470 |
(out_data, input_lines, output, is_doctest, |
|
|
471 | image_directive) = \ | |
|
491 | (out_data, input_lines, output, is_doctest, decorator, | |
|
492 | image_file, image_directive) = \ | |
|
472 | 493 | self.process_input(data, input_prompt, lineno) |
|
473 | 494 | elif token==OUTPUT: |
|
474 | 495 | out_data = \ |
|
475 | 496 | self.process_output(data, output_prompt, |
|
476 | 497 | input_lines, output, is_doctest, |
|
477 | image_file) | |
|
498 | decorator, image_file) | |
|
478 | 499 | if out_data: |
|
479 | 500 | ret.extend(out_data) |
|
480 | 501 | |
@@ -489,10 +510,11 b' class EmbeddedSphinxShell(object):' | |||
|
489 | 510 | return |
|
490 | 511 | self.process_input_line('import matplotlib.pyplot as plt', |
|
491 | 512 | store_history=False) |
|
513 | self._pyplot_imported = True | |
|
492 | 514 | |
|
493 | 515 | def process_pure_python(self, content): |
|
494 | 516 | """ |
|
495 | content is a list of strings. it is unedited directive conent | |
|
517 | content is a list of strings. it is unedited directive content | |
|
496 | 518 | |
|
497 | 519 | This runs it line by line in the InteractiveShell, prepends |
|
498 | 520 | prompts as needed capturing stderr and stdout, then returns |
@@ -568,6 +590,57 b' class EmbeddedSphinxShell(object):' | |||
|
568 | 590 | |
|
569 | 591 | return output |
|
570 | 592 | |
|
593 | def specialized_doctest(self, decorator, input_lines, found, submitted): | |
|
594 | """ | |
|
595 | Perform a specialized doctest. | |
|
596 | ||
|
597 | """ | |
|
598 | # Requires NumPy | |
|
599 | import numpy as np | |
|
600 | ||
|
601 | valid_types = set(['float']) | |
|
602 | ||
|
603 | args = decorator.split() | |
|
604 | ||
|
605 | doctest_type = args[1] | |
|
606 | if doctest_type not in valid_types: | |
|
607 | e = "Invalid option to @doctest: {0}".format(doctest_type) | |
|
608 | raise Exception(e) | |
|
609 | ||
|
610 | if len(args) == 2: | |
|
611 | rtol = 1e-05 | |
|
612 | atol = 1e-08 | |
|
613 | else: | |
|
614 | # Both must be specified if any are specified. | |
|
615 | try: | |
|
616 | rtol = float(args[2]) | |
|
617 | atol = float(args[3]) | |
|
618 | except IndexError: | |
|
619 | e = ("Both `rtol` and `atol` must be specified " | |
|
620 | "if either are specified: {0}".format(args)) | |
|
621 | raise IndexError(e) | |
|
622 | ||
|
623 | try: | |
|
624 | submitted = str_to_array(submitted) | |
|
625 | found = str_to_array(found) | |
|
626 | except: | |
|
627 | # For example, if the array is huge and there are ellipsis in it. | |
|
628 | error = True | |
|
629 | else: | |
|
630 | found_isnan = np.isnan(found) | |
|
631 | submitted_isnan = np.isnan(submitted) | |
|
632 | error = not np.allclose(found_isnan, submitted_isnan) | |
|
633 | error |= not np.allclose(found[~found_isnan], | |
|
634 | submitted[~submitted_isnan], | |
|
635 | rtol=rtol, atol=atol) | |
|
636 | ||
|
637 | if error: | |
|
638 | e = ('doctest float comparison failure for input_lines="{0}" with ' | |
|
639 | 'found_output="{1}" and submitted ' | |
|
640 | 'output="{2}"'.format(input_lines, found, submitted) ) | |
|
641 | raise RuntimeError(e) | |
|
642 | ||
|
643 | ||
|
571 | 644 | class IPythonDirective(Directive): |
|
572 | 645 | |
|
573 | 646 | has_content = True |
@@ -847,6 +920,33 b" In [152]: title('normal distribution')" | |||
|
847 | 920 | @savefig hist_with_text.png |
|
848 | 921 | In [153]: grid(True) |
|
849 | 922 | |
|
923 | @doctest float | |
|
924 | In [154]: 0.1 + 0.2 | |
|
925 | Out[154]: 0.3 | |
|
926 | ||
|
927 | @doctest float | |
|
928 | In [155]: np.arange(16).reshape(4,4) | |
|
929 | Out[155]: | |
|
930 | array([[ 0, 1, 2, 3], | |
|
931 | [ 4, 5, 6, 7], | |
|
932 | [ 8, 9, 10, 11], | |
|
933 | [12, 13, 14, 15]]) | |
|
934 | ||
|
935 | In [1]: x = np.arange(16, dtype=float).reshape(4,4) | |
|
936 | ||
|
937 | In [2]: x[0,0] = np.inf | |
|
938 | ||
|
939 | In [3]: x[0,1] = np.nan | |
|
940 | ||
|
941 | @doctest float | |
|
942 | In [4]: x | |
|
943 | Out[4]: | |
|
944 | array([[ inf, nan, 2., 3.], | |
|
945 | [ 4., 5., 6., 7.], | |
|
946 | [ 8., 9., 10., 11.], | |
|
947 | [ 12., 13., 14., 15.]]) | |
|
948 | ||
|
949 | ||
|
850 | 950 | """, |
|
851 | 951 | ] |
|
852 | 952 | # skip local-file depending first example: |
General Comments 0
You need to be logged in to leave comments.
Login now