Show More
@@ -69,7 +69,6 b' An example usage of the directive is:' | |||||
69 |
|
69 | |||
70 | In [3]: print(y) |
|
70 | In [3]: print(y) | |
71 |
|
71 | |||
72 |
|
||||
73 | See http://matplotlib.org/sampledoc/ipython_directive.html for more additional |
|
72 | See http://matplotlib.org/sampledoc/ipython_directive.html for more additional | |
74 | documentation. |
|
73 | documentation. | |
75 |
|
74 | |||
@@ -133,6 +132,28 b' COMMENT, INPUT, OUTPUT = range(3)' | |||||
133 | #----------------------------------------------------------------------------- |
|
132 | #----------------------------------------------------------------------------- | |
134 | # Functions and class declarations |
|
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 | def block_parser(part, rgxin, rgxout, fmtin, fmtout): |
|
157 | def block_parser(part, rgxin, rgxout, fmtin, fmtout): | |
137 | """ |
|
158 | """ | |
138 | part is a string of ipython text, comprised of at most one |
|
159 | part is a string of ipython text, comprised of at most one | |
@@ -339,7 +360,8 b' class EmbeddedSphinxShell(object):' | |||||
339 | image_directive = None |
|
360 | image_directive = None | |
340 | #print 'INPUT:', data # dbg |
|
361 | #print 'INPUT:', data # dbg | |
341 | is_verbatim = decorator=='@verbatim' or self.is_verbatim |
|
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 | is_suppress = decorator=='@suppress' or self.is_suppress |
|
365 | is_suppress = decorator=='@suppress' or self.is_suppress | |
344 | is_savefig = decorator is not None and \ |
|
366 | is_savefig = decorator is not None and \ | |
345 | decorator.startswith('@savefig') |
|
367 | decorator.startswith('@savefig') | |
@@ -396,38 +418,38 b' class EmbeddedSphinxShell(object):' | |||||
396 | ret.append('') |
|
418 | ret.append('') | |
397 |
|
419 | |||
398 | self.cout.truncate(0) |
|
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 | image_directive) |
|
422 | image_directive) | |
401 | #print 'OUTPUT', output # dbg |
|
423 | #print 'OUTPUT', output # dbg | |
402 |
|
424 | |||
403 | def process_output(self, data, output_prompt, |
|
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 | """Process data block for OUTPUT token.""" |
|
427 | """Process data block for OUTPUT token.""" | |
406 | if is_doctest: |
|
428 | if is_doctest and output is not None: | |
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) |
|
|||
417 |
|
429 | |||
418 |
|
|
430 | found = output | |
419 | if ind<0: |
|
431 | found = found.strip() | |
420 | e='output prompt="%s" does not match out line=%s' % \ |
|
432 | submitted = data.strip() | |
421 | (output_prompt, found) |
|
|||
422 | raise RuntimeError(e) |
|
|||
423 | found = found[len(output_prompt):].strip() |
|
|||
424 |
|
433 | |||
425 | if found!=submitted: |
|
434 | # Make sure the output contains the output prompt. | |
426 | e = ('doctest failure for input_lines="%s" with ' |
|
435 | ind = found.find(output_prompt) | |
427 | 'found_output="%s" and submitted output="%s"' % |
|
436 | if ind < 0: | |
428 | (input_lines, found, submitted) ) |
|
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 | raise RuntimeError(e) |
|
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 | def process_comment(self, data): |
|
454 | def process_comment(self, data): | |
433 | """Process data fPblock for COMMENT token.""" |
|
455 | """Process data fPblock for COMMENT token.""" | |
@@ -448,7 +470,6 b' class EmbeddedSphinxShell(object):' | |||||
448 | self.process_input_line('bookmark -d ipy_thisdir', store_history=False) |
|
470 | self.process_input_line('bookmark -d ipy_thisdir', store_history=False) | |
449 | self.clear_cout() |
|
471 | self.clear_cout() | |
450 |
|
472 | |||
451 |
|
||||
452 | def process_block(self, block): |
|
473 | def process_block(self, block): | |
453 | """ |
|
474 | """ | |
454 | process block from the block_parser and return a list of processed lines |
|
475 | process block from the block_parser and return a list of processed lines | |
@@ -467,14 +488,14 b' class EmbeddedSphinxShell(object):' | |||||
467 | if token==COMMENT: |
|
488 | if token==COMMENT: | |
468 | out_data = self.process_comment(data) |
|
489 | out_data = self.process_comment(data) | |
469 | elif token==INPUT: |
|
490 | elif token==INPUT: | |
470 |
(out_data, input_lines, output, is_doctest, |
|
491 | (out_data, input_lines, output, is_doctest, decorator, | |
471 | image_directive) = \ |
|
492 | image_file, image_directive) = \ | |
472 | self.process_input(data, input_prompt, lineno) |
|
493 | self.process_input(data, input_prompt, lineno) | |
473 | elif token==OUTPUT: |
|
494 | elif token==OUTPUT: | |
474 | out_data = \ |
|
495 | out_data = \ | |
475 | self.process_output(data, output_prompt, |
|
496 | self.process_output(data, output_prompt, | |
476 | input_lines, output, is_doctest, |
|
497 | input_lines, output, is_doctest, | |
477 | image_file) |
|
498 | decorator, image_file) | |
478 | if out_data: |
|
499 | if out_data: | |
479 | ret.extend(out_data) |
|
500 | ret.extend(out_data) | |
480 |
|
501 | |||
@@ -489,10 +510,11 b' class EmbeddedSphinxShell(object):' | |||||
489 | return |
|
510 | return | |
490 | self.process_input_line('import matplotlib.pyplot as plt', |
|
511 | self.process_input_line('import matplotlib.pyplot as plt', | |
491 | store_history=False) |
|
512 | store_history=False) | |
|
513 | self._pyplot_imported = True | |||
492 |
|
514 | |||
493 | def process_pure_python(self, content): |
|
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 | This runs it line by line in the InteractiveShell, prepends |
|
519 | This runs it line by line in the InteractiveShell, prepends | |
498 | prompts as needed capturing stderr and stdout, then returns |
|
520 | prompts as needed capturing stderr and stdout, then returns | |
@@ -568,6 +590,57 b' class EmbeddedSphinxShell(object):' | |||||
568 |
|
590 | |||
569 | return output |
|
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 | class IPythonDirective(Directive): |
|
644 | class IPythonDirective(Directive): | |
572 |
|
645 | |||
573 | has_content = True |
|
646 | has_content = True | |
@@ -847,6 +920,33 b" In [152]: title('normal distribution')" | |||||
847 | @savefig hist_with_text.png |
|
920 | @savefig hist_with_text.png | |
848 | In [153]: grid(True) |
|
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 | # skip local-file depending first example: |
|
952 | # skip local-file depending first example: |
General Comments 0
You need to be logged in to leave comments.
Login now