##// END OF EJS Templates
Fixing InteractiveShell creation
Brian Granger -
Show More
@@ -1,41 +1,47 b''
1 1 """Minimal script to reproduce our nasty reference counting bug.
2 2
3 3 The problem is related to https://bugs.launchpad.net/ipython/+bug/269966
4 4
5 5 The original fix for that appeared to work, but John D. Hunter found a
6 6 matplotlib example which, when run twice in a row, would break. The problem
7 7 were references held by open figures to internals of Tkinter.
8 8
9 9 This code reproduces the problem that John saw, without matplotlib.
10 10
11 11 This script is meant to be called by other parts of the test suite that call it
12 12 via %run as if it were executed interactively by the user. As of 2009-04-13,
13 13 test_magic.py calls it.
14 14 """
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Module imports
18 18 #-----------------------------------------------------------------------------
19 19 import sys
20 20
21 21 from IPython.core import ipapi
22 22
23 23 #-----------------------------------------------------------------------------
24 24 # Globals
25 25 #-----------------------------------------------------------------------------
26 ip = ipapi.get()
27 26
28 if not '_refbug_cache' in ip.user_ns:
29 ip.user_ns['_refbug_cache'] = []
27 # This needs to be here because nose and other test runners will import
28 # this module. Importing this module has potential side effects that we
29 # want to prevent.
30 if __name__ == '__main__':
30 31
32 ip = ipapi.get()
31 33
32 aglobal = 'Hello'
33 def f():
34 return aglobal
34 if not '_refbug_cache' in ip.user_ns:
35 ip.user_ns['_refbug_cache'] = []
35 36
36 cache = ip.user_ns['_refbug_cache']
37 cache.append(f)
38 37
39 def call_f():
40 for func in cache:
41 print 'lowercased:',func().lower()
38 aglobal = 'Hello'
39 def f():
40 return aglobal
41
42 cache = ip.user_ns['_refbug_cache']
43 cache.append(f)
44
45 def call_f():
46 for func in cache:
47 print 'lowercased:',func().lower()
@@ -1,635 +1,635 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Sphinx directive to support embedded IPython code.
3 3
4 4 This directive allows pasting of entire interactive IPython sessions, prompts
5 5 and all, and their code will actually get re-executed at doc build time, with
6 6 all prompts renumbered sequentially.
7 7
8 8 To enable this directive, simply list it in your Sphinx ``conf.py`` file
9 9 (making sure the directory where you placed it is visible to sphinx, as is
10 10 needed for all Sphinx directives).
11 11
12 12 By default this directive assumes that your prompts are unchanged IPython ones,
13 13 but this can be customized. For example, the following code in your Sphinx
14 14 config file will configure this directive for the following input/output
15 15 prompts ``Yade [1]:`` and ``-> [1]:``::
16 16
17 17 import ipython_directive as id
18 18 id.rgxin =re.compile(r'(?:In |Yade )\[(\d+)\]:\s?(.*)\s*')
19 19 id.rgxout=re.compile(r'(?:Out| -> )\[(\d+)\]:\s?(.*)\s*')
20 20 id.fmtin ='Yade [%d]:'
21 21 id.fmtout=' -> [%d]:'
22 22
23 23 from IPython import Config
24 24 id.CONFIG = Config(
25 25 prompt_in1="Yade [\#]:",
26 26 prompt_in2=" .\D..",
27 27 prompt_out=" -> [\#]:"
28 28 )
29 29 id.reconfig_shell()
30 30
31 31 import ipython_console_highlighting as ich
32 32 ich.IPythonConsoleLexer.input_prompt=
33 33 re.compile("(Yade \[[0-9]+\]: )|( \.\.\.+:)")
34 34 ich.IPythonConsoleLexer.output_prompt=
35 35 re.compile("(( -> )|(Out)\[[0-9]+\]: )|( \.\.\.+:)")
36 36 ich.IPythonConsoleLexer.continue_prompt=re.compile(" \.\.\.+:")
37 37
38 38
39 39 ToDo
40 40 ----
41 41
42 42 - Turn the ad-hoc test() function into a real test suite.
43 43 - Break up ipython-specific functionality from matplotlib stuff into better
44 44 separated code.
45 45 - Make sure %bookmarks used internally are removed on exit.
46 46
47 47
48 48 Authors
49 49 -------
50 50
51 51 - John D Hunter: orignal author.
52 52 - Fernando Perez: refactoring, documentation, cleanups, port to 0.11.
53 53 - VΓ‘clavΕ milauer <eudoxos-AT-arcig.cz>: Prompt generalizations.
54 54 """
55 55
56 56 #-----------------------------------------------------------------------------
57 57 # Imports
58 58 #-----------------------------------------------------------------------------
59 59
60 60 # Stdlib
61 61 import cStringIO
62 62 import os
63 63 import re
64 64 import sys
65 65
66 66 # To keep compatibility with various python versions
67 67 try:
68 68 from hashlib import md5
69 69 except ImportError:
70 70 from md5 import md5
71 71
72 72 # Third-party
73 73 import matplotlib
74 74 import sphinx
75 75 from docutils.parsers.rst import directives
76 76
77 77 matplotlib.use('Agg')
78 78
79 79 # Our own
80 80 from IPython import Config, InteractiveShell
81 81 from IPython.utils.io import Term
82 82
83 83 #-----------------------------------------------------------------------------
84 84 # Globals
85 85 #-----------------------------------------------------------------------------
86 86
87 87 sphinx_version = sphinx.__version__.split(".")
88 88 # The split is necessary for sphinx beta versions where the string is
89 89 # '6b1'
90 90 sphinx_version = tuple([int(re.split('[a-z]', x)[0])
91 91 for x in sphinx_version[:2]])
92 92
93 93 COMMENT, INPUT, OUTPUT = range(3)
94 94 CONFIG = Config()
95 95 rgxin = re.compile('In \[(\d+)\]:\s?(.*)\s*')
96 96 rgxout = re.compile('Out\[(\d+)\]:\s?(.*)\s*')
97 97 fmtin = 'In [%d]:'
98 98 fmtout = 'Out[%d]:'
99 99
100 100 #-----------------------------------------------------------------------------
101 101 # Functions and class declarations
102 102 #-----------------------------------------------------------------------------
103 103 def block_parser(part):
104 104 """
105 105 part is a string of ipython text, comprised of at most one
106 106 input, one ouput, comments, and blank lines. The block parser
107 107 parses the text into a list of::
108 108
109 109 blocks = [ (TOKEN0, data0), (TOKEN1, data1), ...]
110 110
111 111 where TOKEN is one of [COMMENT | INPUT | OUTPUT ] and
112 112 data is, depending on the type of token::
113 113
114 114 COMMENT : the comment string
115 115
116 116 INPUT: the (DECORATOR, INPUT_LINE, REST) where
117 117 DECORATOR: the input decorator (or None)
118 118 INPUT_LINE: the input as string (possibly multi-line)
119 119 REST : any stdout generated by the input line (not OUTPUT)
120 120
121 121
122 122 OUTPUT: the output string, possibly multi-line
123 123 """
124 124
125 125 block = []
126 126 lines = part.split('\n')
127 127 N = len(lines)
128 128 i = 0
129 129 decorator = None
130 130 while 1:
131 131
132 132 if i==N:
133 133 # nothing left to parse -- the last line
134 134 break
135 135
136 136 line = lines[i]
137 137 i += 1
138 138 line_stripped = line.strip()
139 139 if line_stripped.startswith('#'):
140 140 block.append((COMMENT, line))
141 141 continue
142 142
143 143 if line_stripped.startswith('@'):
144 144 # we're assuming at most one decorator -- may need to
145 145 # rethink
146 146 decorator = line_stripped
147 147 continue
148 148
149 149 # does this look like an input line?
150 150 matchin = rgxin.match(line)
151 151 if matchin:
152 152 lineno, inputline = int(matchin.group(1)), matchin.group(2)
153 153
154 154 # the ....: continuation string
155 155 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
156 156 Nc = len(continuation)
157 157 # input lines can continue on for more than one line, if
158 158 # we have a '\' line continuation char or a function call
159 159 # echo line 'print'. The input line can only be
160 160 # terminated by the end of the block or an output line, so
161 161 # we parse out the rest of the input line if it is
162 162 # multiline as well as any echo text
163 163
164 164 rest = []
165 165 while i<N:
166 166
167 167 # look ahead; if the next line is blank, or a comment, or
168 168 # an output line, we're done
169 169
170 170 nextline = lines[i]
171 171 matchout = rgxout.match(nextline)
172 172 #print "nextline=%s, continuation=%s, starts=%s"%(nextline, continuation, nextline.startswith(continuation))
173 173 if matchout or nextline.startswith('#'):
174 174 break
175 175 elif nextline.startswith(continuation):
176 176 inputline += '\n' + nextline[Nc:]
177 177 else:
178 178 rest.append(nextline)
179 179 i+= 1
180 180
181 181 block.append((INPUT, (decorator, inputline, '\n'.join(rest))))
182 182 continue
183 183
184 184 # if it looks like an output line grab all the text to the end
185 185 # of the block
186 186 matchout = rgxout.match(line)
187 187 if matchout:
188 188 lineno, output = int(matchout.group(1)), matchout.group(2)
189 189 if i<N-1:
190 190 output = '\n'.join([output] + lines[i:])
191 191
192 192 block.append((OUTPUT, output))
193 193 break
194 194
195 195 return block
196 196
197 197
198 198 class EmbeddedSphinxShell(object):
199 199 """An embedded IPython instance to run inside Sphinx"""
200 200
201 201 def __init__(self):
202 202
203 203 self.cout = cStringIO.StringIO()
204 204 Term.cout = self.cout
205 205 Term.cerr = self.cout
206 206
207 207 # For debugging, so we can see normal output, use this:
208 208 # from IPython.utils.io import Tee
209 209 #Term.cout = Tee(self.cout, channel='stdout') # dbg
210 210 #Term.cerr = Tee(self.cout, channel='stderr') # dbg
211 211
212 212 # Create config object for IPython
213 213 config = Config()
214 214 config.Global.display_banner = False
215 215 config.Global.exec_lines = ['import numpy as np',
216 216 'from pylab import *'
217 217 ]
218 218 config.InteractiveShell.autocall = False
219 219 config.InteractiveShell.autoindent = False
220 220 config.InteractiveShell.colors = 'NoColor'
221 221
222 222 # Create and initialize ipython, but don't start its mainloop
223 IP = InteractiveShell(parent=None, config=config)
223 IP = InteractiveShell.instance(config=config)
224 224
225 225 # Store a few parts of IPython we'll need.
226 226 self.IP = IP
227 227 self.user_ns = self.IP.user_ns
228 228 self.user_global_ns = self.IP.user_global_ns
229 229
230 230 self.input = ''
231 231 self.output = ''
232 232
233 233 self.is_verbatim = False
234 234 self.is_doctest = False
235 235 self.is_suppress = False
236 236
237 237 # on the first call to the savefig decorator, we'll import
238 238 # pyplot as plt so we can make a call to the plt.gcf().savefig
239 239 self._pyplot_imported = False
240 240
241 241 # we need bookmark the current dir first so we can save
242 242 # relative to it
243 243 self.process_input_line('bookmark ipy_basedir')
244 244 self.cout.seek(0)
245 245 self.cout.truncate(0)
246 246
247 247 def process_input_line(self, line):
248 248 """process the input, capturing stdout"""
249 249 #print "input='%s'"%self.input
250 250 stdout = sys.stdout
251 251 try:
252 252 sys.stdout = self.cout
253 253 self.IP.push_line(line)
254 254 finally:
255 255 sys.stdout = stdout
256 256
257 257 # Callbacks for each type of token
258 258 def process_input(self, data, input_prompt, lineno):
259 259 """Process data block for INPUT token."""
260 260 decorator, input, rest = data
261 261 image_file = None
262 262 #print 'INPUT:', data # dbg
263 263 is_verbatim = decorator=='@verbatim' or self.is_verbatim
264 264 is_doctest = decorator=='@doctest' or self.is_doctest
265 265 is_suppress = decorator=='@suppress' or self.is_suppress
266 266 is_savefig = decorator is not None and \
267 267 decorator.startswith('@savefig')
268 268
269 269 input_lines = input.split('\n')
270 270
271 271 continuation = ' %s:'%''.join(['.']*(len(str(lineno))+2))
272 272 Nc = len(continuation)
273 273
274 274 if is_savefig:
275 275 saveargs = decorator.split(' ')
276 276 filename = saveargs[1]
277 277 outfile = os.path.join('_static/%s'%filename)
278 278 # build out an image directive like
279 279 # .. image:: somefile.png
280 280 # :width 4in
281 281 #
282 282 # from an input like
283 283 # savefig somefile.png width=4in
284 284 imagerows = ['.. image:: %s'%outfile]
285 285
286 286 for kwarg in saveargs[2:]:
287 287 arg, val = kwarg.split('=')
288 288 arg = arg.strip()
289 289 val = val.strip()
290 290 imagerows.append(' :%s: %s'%(arg, val))
291 291
292 292 image_file = outfile
293 293 image_directive = '\n'.join(imagerows)
294 294
295 295 # TODO: can we get "rest" from ipython
296 296 #self.process_input_line('\n'.join(input_lines))
297 297
298 298 ret = []
299 299 is_semicolon = False
300 300
301 301 for i, line in enumerate(input_lines):
302 302 if line.endswith(';'):
303 303 is_semicolon = True
304 304
305 305 if i==0:
306 306 # process the first input line
307 307 if is_verbatim:
308 308 self.process_input_line('')
309 309 else:
310 310 # only submit the line in non-verbatim mode
311 311 self.process_input_line(line)
312 312 formatted_line = '%s %s'%(input_prompt, line)
313 313 else:
314 314 # process a continuation line
315 315 if not is_verbatim:
316 316 self.process_input_line(line)
317 317
318 318 formatted_line = '%s %s'%(continuation, line)
319 319
320 320 if not is_suppress:
321 321 ret.append(formatted_line)
322 322
323 323 if not is_suppress:
324 324 if len(rest.strip()):
325 325 if is_verbatim:
326 326 # the "rest" is the standard output of the
327 327 # input, which needs to be added in
328 328 # verbatim mode
329 329 ret.append(rest)
330 330
331 331 self.cout.seek(0)
332 332 output = self.cout.read()
333 333 if not is_suppress and not is_semicolon:
334 334 ret.append(output)
335 335
336 336 self.cout.truncate(0)
337 337 return ret, input_lines, output, is_doctest, image_file
338 338 #print 'OUTPUT', output # dbg
339 339
340 340 def process_output(self, data, output_prompt,
341 341 input_lines, output, is_doctest, image_file):
342 342 """Process data block for OUTPUT token."""
343 343 if is_doctest:
344 344 submitted = data.strip()
345 345 found = output
346 346 if found is not None:
347 347 found = found.strip()
348 348
349 349 # XXX - fperez: in 0.11, 'output' never comes with the prompt
350 350 # in it, just the actual output text. So I think all this code
351 351 # can be nuked...
352 352 ## ind = found.find(output_prompt)
353 353 ## if ind<0:
354 354 ## e='output prompt="%s" does not match out line=%s' % \
355 355 ## (output_prompt, found)
356 356 ## raise RuntimeError(e)
357 357 ## found = found[len(output_prompt):].strip()
358 358
359 359 if found!=submitted:
360 360 e = ('doctest failure for input_lines="%s" with '
361 361 'found_output="%s" and submitted output="%s"' %
362 362 (input_lines, found, submitted) )
363 363 raise RuntimeError(e)
364 364 #print 'doctest PASSED for input_lines="%s" with found_output="%s" and submitted output="%s"'%(input_lines, found, submitted)
365 365
366 366 def process_comment(self, data):
367 367 """Process data block for COMMENT token."""
368 368 if not self.is_suppress:
369 369 return [data]
370 370
371 371 def process_block(self, block):
372 372 """
373 373 process block from the block_parser and return a list of processed lines
374 374 """
375 375
376 376 ret = []
377 377 output = None
378 378 input_lines = None
379 379
380 380 m = rgxin.match(str(self.IP.outputcache.prompt1).strip())
381 381 lineno = int(m.group(1))
382 382
383 383 input_prompt = fmtin%lineno
384 384 output_prompt = fmtout%lineno
385 385 image_file = None
386 386 image_directive = None
387 387 # XXX - This needs a second refactor. There's too much state being
388 388 # held globally, which makes for a very awkward interface and large,
389 389 # hard to test functions. I've already broken this up at least into
390 390 # three separate processors to isolate the logic better, but this only
391 391 # serves to highlight the coupling. Next we need to clean it up...
392 392 for token, data in block:
393 393 if token==COMMENT:
394 394 out_data = self.process_comment(data)
395 395 elif token==INPUT:
396 396 out_data, input_lines, output, is_doctest, image_file= \
397 397 self.process_input(data, input_prompt, lineno)
398 398 elif token==OUTPUT:
399 399 out_data = \
400 400 self.process_output(data, output_prompt,
401 401 input_lines, output, is_doctest,
402 402 image_file)
403 403 if out_data:
404 404 ret.extend(out_data)
405 405
406 406 if image_file is not None:
407 407 self.ensure_pyplot()
408 408 command = 'plt.gcf().savefig("%s")'%image_file
409 409 print 'SAVEFIG', command # dbg
410 410 self.process_input_line('bookmark ipy_thisdir')
411 411 self.process_input_line('cd -b ipy_basedir')
412 412 self.process_input_line(command)
413 413 self.process_input_line('cd -b ipy_thisdir')
414 414 self.cout.seek(0)
415 415 self.cout.truncate(0)
416 416 return ret, image_directive
417 417
418 418 def ensure_pyplot(self):
419 419 if self._pyplot_imported:
420 420 return
421 421 self.process_input_line('import matplotlib.pyplot as plt')
422 422
423 423 # A global instance used below. XXX: not sure why this can't be created inside
424 424 # ipython_directive itself.
425 425 shell = EmbeddedSphinxShell()
426 426
427 427 def reconfig_shell():
428 428 """Called after setting module-level variables to re-instantiate
429 429 with the set values (since shell is instantiated first at import-time
430 430 when module variables have default values)"""
431 431 global shell
432 432 shell = EmbeddedSphinxShell()
433 433
434 434
435 435 def ipython_directive(name, arguments, options, content, lineno,
436 436 content_offset, block_text, state, state_machine,
437 437 ):
438 438
439 439 debug = ipython_directive.DEBUG
440 440 shell.is_suppress = options.has_key('suppress')
441 441 shell.is_doctest = options.has_key('doctest')
442 442 shell.is_verbatim = options.has_key('verbatim')
443 443
444 444 #print 'ipy', shell.is_suppress, options
445 445 parts = '\n'.join(content).split('\n\n')
446 446 lines = ['.. sourcecode:: ipython', '']
447 447
448 448 figures = []
449 449 for part in parts:
450 450 block = block_parser(part)
451 451
452 452 if len(block):
453 453 rows, figure = shell.process_block(block)
454 454 for row in rows:
455 455 lines.extend([' %s'%line for line in row.split('\n')])
456 456
457 457 if figure is not None:
458 458 figures.append(figure)
459 459
460 460 for figure in figures:
461 461 lines.append('')
462 462 lines.extend(figure.split('\n'))
463 463 lines.append('')
464 464
465 465 #print lines
466 466 if len(lines)>2:
467 467 if debug:
468 468 print '\n'.join(lines)
469 469 else:
470 470 #print 'INSERTING %d lines'%len(lines)
471 471 state_machine.insert_input(
472 472 lines, state_machine.input_lines.source(0))
473 473
474 474 return []
475 475
476 476 ipython_directive.DEBUG = False
477 477 ipython_directive.DEBUG = True # dbg
478 478
479 479 # Enable as a proper Sphinx directive
480 480 def setup(app):
481 481 setup.app = app
482 482 options = {'suppress': directives.flag,
483 483 'doctest': directives.flag,
484 484 'verbatim': directives.flag,
485 485 }
486 486
487 487 app.add_directive('ipython', ipython_directive, True, (0, 2, 0), **options)
488 488
489 489
490 490 # Simple smoke test, needs to be converted to a proper automatic test.
491 491 def test():
492 492
493 493 examples = [
494 494 r"""
495 495 In [9]: pwd
496 496 Out[9]: '/home/jdhunter/py4science/book'
497 497
498 498 In [10]: cd bookdata/
499 499 /home/jdhunter/py4science/book/bookdata
500 500
501 501 In [2]: from pylab import *
502 502
503 503 In [2]: ion()
504 504
505 505 In [3]: im = imread('stinkbug.png')
506 506
507 507 @savefig mystinkbug.png width=4in
508 508 In [4]: imshow(im)
509 509 Out[4]: <matplotlib.image.AxesImage object at 0x39ea850>
510 510
511 511 """,
512 512 r"""
513 513
514 514 In [1]: x = 'hello world'
515 515
516 516 # string methods can be
517 517 # used to alter the string
518 518 @doctest
519 519 In [2]: x.upper()
520 520 Out[2]: 'HELLO WORLD'
521 521
522 522 @verbatim
523 523 In [3]: x.st<TAB>
524 524 x.startswith x.strip
525 525 """,
526 526 r"""
527 527
528 528 In [130]: url = 'http://ichart.finance.yahoo.com/table.csv?s=CROX\
529 529 .....: &d=9&e=22&f=2009&g=d&a=1&br=8&c=2006&ignore=.csv'
530 530
531 531 In [131]: print url.split('&')
532 532 ['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']
533 533
534 534 In [60]: import urllib
535 535
536 536 """,
537 537 r"""\
538 538
539 539 In [133]: import numpy.random
540 540
541 541 @suppress
542 542 In [134]: numpy.random.seed(2358)
543 543
544 544 @doctest
545 545 In [135]: np.random.rand(10,2)
546 546 Out[135]:
547 547 array([[ 0.64524308, 0.59943846],
548 548 [ 0.47102322, 0.8715456 ],
549 549 [ 0.29370834, 0.74776844],
550 550 [ 0.99539577, 0.1313423 ],
551 551 [ 0.16250302, 0.21103583],
552 552 [ 0.81626524, 0.1312433 ],
553 553 [ 0.67338089, 0.72302393],
554 554 [ 0.7566368 , 0.07033696],
555 555 [ 0.22591016, 0.77731835],
556 556 [ 0.0072729 , 0.34273127]])
557 557
558 558 """,
559 559
560 560 r"""
561 561 In [106]: print x
562 562 jdh
563 563
564 564 In [109]: for i in range(10):
565 565 .....: print i
566 566 .....:
567 567 .....:
568 568 0
569 569 1
570 570 2
571 571 3
572 572 4
573 573 5
574 574 6
575 575 7
576 576 8
577 577 9
578 578 """,
579 579
580 580 r"""
581 581
582 582 In [144]: from pylab import *
583 583
584 584 In [145]: ion()
585 585
586 586 # use a semicolon to suppress the output
587 587 @savefig test_hist.png width=4in
588 588 In [151]: hist(np.random.randn(10000), 100);
589 589
590 590
591 591 @savefig test_plot.png width=4in
592 592 In [151]: plot(np.random.randn(10000), 'o');
593 593 """,
594 594
595 595 r"""
596 596 # use a semicolon to suppress the output
597 597 In [151]: plt.clf()
598 598
599 599 @savefig plot_simple.png width=4in
600 600 In [151]: plot([1,2,3])
601 601
602 602 @savefig hist_simple.png width=4in
603 603 In [151]: hist(np.random.randn(10000), 100);
604 604
605 605 """,
606 606 r"""
607 607 # update the current fig
608 608 In [151]: ylabel('number')
609 609
610 610 In [152]: title('normal distribution')
611 611
612 612
613 613 @savefig hist_with_text.png
614 614 In [153]: grid(True)
615 615
616 616 """,
617 617 ]
618 618
619 619 #ipython_directive.DEBUG = True # dbg
620 620 #options = dict(suppress=True) # dbg
621 621 options = dict()
622 622 for example in examples:
623 623 content = example.split('\n')
624 624 ipython_directive('debug', arguments=None, options=options,
625 625 content=content, lineno=0,
626 626 content_offset=None, block_text=None,
627 627 state=None, state_machine=None,
628 628 )
629 629
630 630 # Run test suite as a script
631 631 if __name__=='__main__':
632 632 if not os.path.isdir('_static'):
633 633 os.mkdir('_static')
634 634 test()
635 635 print 'All OK? Check figures in _static/'
General Comments 0
You need to be logged in to leave comments. Login now