##// END OF EJS Templates
Merging everyones bug fixes into trunk....
Brian Granger -
r1723:df49db56 merge
parent child Browse files
Show More
@@ -0,0 +1,155 b''
1 # encoding: utf-8
2
3 """This file contains unittests for the frontendbase module."""
4
5 __docformat__ = "restructuredtext en"
6
7 #---------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #---------------------------------------------------------------------------
13
14 #---------------------------------------------------------------------------
15 # Imports
16 #---------------------------------------------------------------------------
17
18 import unittest
19
20 try:
21 from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase
22 from IPython.frontend import frontendbase
23 from IPython.kernel.engineservice import EngineService
24 except ImportError:
25 import nose
26 raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap")
27
28 from IPython.testing.decorators import skip
29
30 class FrontEndCallbackChecker(AsyncFrontEndBase):
31 """FrontEndBase subclass for checking callbacks"""
32 def __init__(self, engine=None, history=None):
33 super(FrontEndCallbackChecker, self).__init__(engine=engine,
34 history=history)
35 self.updateCalled = False
36 self.renderResultCalled = False
37 self.renderErrorCalled = False
38
39 def update_cell_prompt(self, result, blockID=None):
40 self.updateCalled = True
41 return result
42
43 def render_result(self, result):
44 self.renderResultCalled = True
45 return result
46
47
48 def render_error(self, failure):
49 self.renderErrorCalled = True
50 return failure
51
52
53
54
55 class TestAsyncFrontendBase(unittest.TestCase):
56 def setUp(self):
57 """Setup the EngineService and FrontEndBase"""
58
59 self.fb = FrontEndCallbackChecker(engine=EngineService())
60
61 def test_implements_IFrontEnd(self):
62 assert(frontendbase.IFrontEnd.implementedBy(
63 AsyncFrontEndBase))
64
65 def test_is_complete_returns_False_for_incomplete_block(self):
66 """"""
67
68 block = """def test(a):"""
69
70 assert(self.fb.is_complete(block) == False)
71
72 def test_is_complete_returns_True_for_complete_block(self):
73 """"""
74
75 block = """def test(a): pass"""
76
77 assert(self.fb.is_complete(block))
78
79 block = """a=3"""
80
81 assert(self.fb.is_complete(block))
82
83 def test_blockID_added_to_result(self):
84 block = """3+3"""
85
86 d = self.fb.execute(block, blockID='TEST_ID')
87
88 d.addCallback(self.checkBlockID, expected='TEST_ID')
89
90 def test_blockID_added_to_failure(self):
91 block = "raise Exception()"
92
93 d = self.fb.execute(block,blockID='TEST_ID')
94 d.addErrback(self.checkFailureID, expected='TEST_ID')
95
96 def checkBlockID(self, result, expected=""):
97 assert(result['blockID'] == expected)
98
99
100 def checkFailureID(self, failure, expected=""):
101 assert(failure.blockID == expected)
102
103
104 def test_callbacks_added_to_execute(self):
105 """test that
106 update_cell_prompt
107 render_result
108
109 are added to execute request
110 """
111
112 d = self.fb.execute("10+10")
113 d.addCallback(self.checkCallbacks)
114
115 def checkCallbacks(self, result):
116 assert(self.fb.updateCalled)
117 assert(self.fb.renderResultCalled)
118
119 @skip("This test fails and lead to an unhandled error in a Deferred.")
120 def test_error_callback_added_to_execute(self):
121 """test that render_error called on execution error"""
122
123 d = self.fb.execute("raise Exception()")
124 d.addCallback(self.checkRenderError)
125
126 def checkRenderError(self, result):
127 assert(self.fb.renderErrorCalled)
128
129 def test_history_returns_expected_block(self):
130 """Make sure history browsing doesn't fail"""
131
132 blocks = ["a=1","a=2","a=3"]
133 for b in blocks:
134 d = self.fb.execute(b)
135
136 # d is now the deferred for the last executed block
137 d.addCallback(self.historyTests, blocks)
138
139
140 def historyTests(self, result, blocks):
141 """historyTests"""
142
143 assert(len(blocks) >= 3)
144 assert(self.fb.get_history_previous("") == blocks[-2])
145 assert(self.fb.get_history_previous("") == blocks[-3])
146 assert(self.fb.get_history_next() == blocks[-2])
147
148
149 def test_history_returns_none_at_startup(self):
150 """test_history_returns_none_at_startup"""
151
152 assert(self.fb.get_history_previous("")==None)
153 assert(self.fb.get_history_next()==None)
154
155
@@ -0,0 +1,161 b''
1 """Tests for the decorators we've created for IPython.
2 """
3
4 # Module imports
5 # Std lib
6 import inspect
7 import sys
8
9 # Third party
10 import nose.tools as nt
11
12 # Our own
13 from IPython.testing import decorators as dec
14
15
16 #-----------------------------------------------------------------------------
17 # Utilities
18
19 # Note: copied from OInspect, kept here so the testing stuff doesn't create
20 # circular dependencies and is easier to reuse.
21 def getargspec(obj):
22 """Get the names and default values of a function's arguments.
23
24 A tuple of four things is returned: (args, varargs, varkw, defaults).
25 'args' is a list of the argument names (it may contain nested lists).
26 'varargs' and 'varkw' are the names of the * and ** arguments or None.
27 'defaults' is an n-tuple of the default values of the last n arguments.
28
29 Modified version of inspect.getargspec from the Python Standard
30 Library."""
31
32 if inspect.isfunction(obj):
33 func_obj = obj
34 elif inspect.ismethod(obj):
35 func_obj = obj.im_func
36 else:
37 raise TypeError, 'arg is not a Python function'
38 args, varargs, varkw = inspect.getargs(func_obj.func_code)
39 return args, varargs, varkw, func_obj.func_defaults
40
41 #-----------------------------------------------------------------------------
42 # Testing functions
43
44 @dec.skip
45 def test_deliberately_broken():
46 """A deliberately broken test - we want to skip this one."""
47 1/0
48
49 @dec.skip('foo')
50 def test_deliberately_broken2():
51 """Another deliberately broken test - we want to skip this one."""
52 1/0
53
54
55 # Verify that we can correctly skip the doctest for a function at will, but
56 # that the docstring itself is NOT destroyed by the decorator.
57 @dec.skip_doctest
58 def doctest_bad(x,y=1,**k):
59 """A function whose doctest we need to skip.
60
61 >>> 1+1
62 3
63 """
64 print 'x:',x
65 print 'y:',y
66 print 'k:',k
67
68
69 def call_doctest_bad():
70 """Check that we can still call the decorated functions.
71
72 >>> doctest_bad(3,y=4)
73 x: 3
74 y: 4
75 k: {}
76 """
77 pass
78
79
80 def test_skip_dt_decorator():
81 """Doctest-skipping decorator should preserve the docstring.
82 """
83 # Careful: 'check' must be a *verbatim* copy of the doctest_bad docstring!
84 check = """A function whose doctest we need to skip.
85
86 >>> 1+1
87 3
88 """
89 # Fetch the docstring from doctest_bad after decoration.
90 val = doctest_bad.__doc__
91
92 assert check==val,"doctest_bad docstrings don't match"
93
94 # Doctest skipping should work for class methods too
95 class foo(object):
96 """Foo
97
98 Example:
99
100 >>> 1+1
101 2
102 """
103
104 @dec.skip_doctest
105 def __init__(self,x):
106 """Make a foo.
107
108 Example:
109
110 >>> f = foo(3)
111 junk
112 """
113 print 'Making a foo.'
114 self.x = x
115
116 @dec.skip_doctest
117 def bar(self,y):
118 """Example:
119
120 >>> f = foo(3)
121 >>> f.bar(0)
122 boom!
123 >>> 1/0
124 bam!
125 """
126 return 1/y
127
128 def baz(self,y):
129 """Example:
130
131 >>> f = foo(3)
132 Making a foo.
133 >>> f.baz(3)
134 True
135 """
136 return self.x==y
137
138
139
140 def test_skip_dt_decorator2():
141 """Doctest-skipping decorator should preserve function signature.
142 """
143 # Hardcoded correct answer
144 dtargs = (['x', 'y'], None, 'k', (1,))
145 # Introspect out the value
146 dtargsr = getargspec(doctest_bad)
147 assert dtargsr==dtargs, \
148 "Incorrectly reconstructed args for doctest_bad: %s" % (dtargsr,)
149
150
151 @dec.skip_linux
152 def test_linux():
153 nt.assert_not_equals(sys.platform,'linux2',"This test can't run under linux")
154
155 @dec.skip_win32
156 def test_win32():
157 nt.assert_not_equals(sys.platform,'win32',"This test can't run under windows")
158
159 @dec.skip_osx
160 def test_osx():
161 nt.assert_not_equals(sys.platform,'darwin',"This test can't run under osx")
@@ -0,0 +1,20 b''
1 #!/usr/bin/env python
2 """Call the compile script to check that all code we ship compiles correctly.
3 """
4
5 import os
6 import sys
7
8
9 vstr = '.'.join(map(str,sys.version_info[:2]))
10
11 stat = os.system('python %s/lib/python%s/compileall.py .' % (sys.prefix,vstr))
12
13 print
14 if stat:
15 print '*** THERE WAS AN ERROR! ***'
16 print 'See messages above for the actual file that produced it.'
17 else:
18 print 'OK'
19
20 sys.exit(stat)
@@ -1,97 +1,121 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Release data for the IPython project."""
3 3
4 4 #*****************************************************************************
5 5 # Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
6 6 #
7 7 # Copyright (c) 2001 Janko Hauser <jhauser@zscout.de> and Nathaniel Gray
8 8 # <n8gray@caltech.edu>
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #*****************************************************************************
13 13
14 14 # Name of the package for release purposes. This is the name which labels
15 15 # the tarballs and RPMs made by distutils, so it's best to lowercase it.
16 16 name = 'ipython'
17 17
18 18 # For versions with substrings (like 0.6.16.svn), use an extra . to separate
19 19 # the new substring. We have to avoid using either dashes or underscores,
20 20 # because bdist_rpm does not accept dashes (an RPM) convention, and
21 21 # bdist_deb does not accept underscores (a Debian convention).
22 22
23 23 development = False # change this to False to do a release
24 version_base = '0.9'
24 version_base = '0.9.1'
25 25 branch = 'ipython'
26 26 revision = '1143'
27 27
28 28 if development:
29 29 if branch == 'ipython':
30 30 version = '%s.bzr.r%s' % (version_base, revision)
31 31 else:
32 32 version = '%s.bzr.r%s.%s' % (version_base, revision, branch)
33 33 else:
34 34 version = version_base
35 35
36 36
37 description = "Tools for interactive development in Python."
37 description = "An interactive computing environment for Python"
38 38
39 39 long_description = \
40 40 """
41 IPython provides a replacement for the interactive Python interpreter with
42 extra functionality.
41 The goal of IPython is to create a comprehensive environment for
42 interactive and exploratory computing. To support this goal, IPython
43 has two main components:
43 44
44 Main features:
45 * An enhanced interactive Python shell.
45 46
46 * Comprehensive object introspection.
47 * An architecture for interactive parallel computing.
47 48
48 * Input history, persistent across sessions.
49 The enhanced interactive Python shell has the following main features:
49 50
50 * Caching of output results during a session with automatically generated
51 references.
51 * Comprehensive object introspection.
52 52
53 * Readline based name completion.
53 * Input history, persistent across sessions.
54 54
55 * Extensible system of 'magic' commands for controlling the environment and
56 performing many tasks related either to IPython or the operating system.
55 * Caching of output results during a session with automatically generated
56 references.
57 57
58 * Configuration system with easy switching between different setups (simpler
59 than changing $PYTHONSTARTUP environment variables every time).
58 * Readline based name completion.
60 59
61 * Session logging and reloading.
60 * Extensible system of 'magic' commands for controlling the environment and
61 performing many tasks related either to IPython or the operating system.
62 62
63 * Extensible syntax processing for special purpose situations.
63 * Configuration system with easy switching between different setups (simpler
64 than changing $PYTHONSTARTUP environment variables every time).
64 65
65 * Access to the system shell with user-extensible alias system.
66 * Session logging and reloading.
66 67
67 * Easily embeddable in other Python programs.
68 * Extensible syntax processing for special purpose situations.
68 69
69 * Integrated access to the pdb debugger and the Python profiler.
70 * Access to the system shell with user-extensible alias system.
70 71
71 The latest development version is always available at the IPython subversion
72 repository_.
72 * Easily embeddable in other Python programs and wxPython GUIs.
73 73
74 .. _repository: http://ipython.scipy.org/svn/ipython/ipython/trunk#egg=ipython-dev
75 """
74 * Integrated access to the pdb debugger and the Python profiler.
75
76 The parallel computing architecture has the following main features:
77
78 * Quickly parallelize Python code from an interactive Python/IPython session.
79
80 * A flexible and dynamic process model that be deployed on anything from
81 multicore workstations to supercomputers.
82
83 * An architecture that supports many different styles of parallelism, from
84 message passing to task farming.
85
86 * Both blocking and fully asynchronous interfaces.
87
88 * High level APIs that enable many things to be parallelized in a few lines
89 of code.
90
91 * Share live parallel jobs with other users securely.
92
93 * Dynamically load balanced task farming system.
94
95 * Robust error handling in parallel code.
96
97 The latest development version is always available from IPython's `Launchpad
98 site <http://launchpad.net/ipython>`_.
99 """
76 100
77 101 license = 'BSD'
78 102
79 103 authors = {'Fernando' : ('Fernando Perez','fperez@colorado.edu'),
80 104 'Janko' : ('Janko Hauser','jhauser@zscout.de'),
81 105 'Nathan' : ('Nathaniel Gray','n8gray@caltech.edu'),
82 106 'Ville' : ('Ville Vainio','vivainio@gmail.com'),
83 107 'Brian' : ('Brian E Granger', 'ellisonbg@gmail.com'),
84 108 'Min' : ('Min Ragan-Kelley', 'benjaminrk@gmail.com')
85 109 }
86 110
87 111 author = 'The IPython Development Team'
88 112
89 113 author_email = 'ipython-dev@scipy.org'
90 114
91 115 url = 'http://ipython.scipy.org'
92 116
93 117 download_url = 'http://ipython.scipy.org/dist'
94 118
95 119 platforms = ['Linux','Mac OSX','Windows XP/2000/NT','Windows 95/98/ME']
96 120
97 121 keywords = ['Interactive','Interpreter','Shell','Parallel','Distributed']
@@ -1,106 +1,102 b''
1 1 # encoding: utf-8
2 2
3 3 """This is the official entry point to IPython's configuration system. """
4 4
5 5 __docformat__ = "restructuredtext en"
6 6
7 7 #-------------------------------------------------------------------------------
8 8 # Copyright (C) 2008 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-------------------------------------------------------------------------------
13 13
14 14 #-------------------------------------------------------------------------------
15 15 # Imports
16 16 #-------------------------------------------------------------------------------
17 17
18 18 import os
19 19 from os.path import join as pjoin
20 20
21 21 from IPython.genutils import get_home_dir, get_ipython_dir
22 22 from IPython.external.configobj import ConfigObj
23 23
24 # Traitlets config imports
25 from IPython.config import traitlets
26 from IPython.config.config import *
27 from traitlets import *
28 24
29 25 class ConfigObjManager(object):
30 26
31 27 def __init__(self, configObj, filename):
32 28 self.current = configObj
33 29 self.current.indent_type = ' '
34 30 self.filename = filename
35 31 # self.write_default_config_file()
36 32
37 33 def get_config_obj(self):
38 34 return self.current
39 35
40 36 def update_config_obj(self, newConfig):
41 37 self.current.merge(newConfig)
42 38
43 39 def update_config_obj_from_file(self, filename):
44 40 newConfig = ConfigObj(filename, file_error=False)
45 41 self.current.merge(newConfig)
46 42
47 43 def update_config_obj_from_default_file(self, ipythondir=None):
48 44 fname = self.resolve_file_path(self.filename, ipythondir)
49 45 self.update_config_obj_from_file(fname)
50 46
51 47 def write_config_obj_to_file(self, filename):
52 48 f = open(filename, 'w')
53 49 self.current.write(f)
54 50 f.close()
55 51
56 52 def write_default_config_file(self):
57 53 ipdir = get_ipython_dir()
58 54 fname = pjoin(ipdir, self.filename)
59 55 if not os.path.isfile(fname):
60 56 print "Writing the configuration file to: " + fname
61 57 self.write_config_obj_to_file(fname)
62 58
63 59 def _import(self, key):
64 60 package = '.'.join(key.split('.')[0:-1])
65 61 obj = key.split('.')[-1]
66 62 execString = 'from %s import %s' % (package, obj)
67 63 exec execString
68 64 exec 'temp = %s' % obj
69 65 return temp
70 66
71 67 def resolve_file_path(self, filename, ipythondir = None):
72 68 """Resolve filenames into absolute paths.
73 69
74 70 This function looks in the following directories in order:
75 71
76 72 1. In the current working directory or by absolute path with ~ expanded
77 73 2. In ipythondir if that is set
78 74 3. In the IPYTHONDIR environment variable if it exists
79 75 4. In the ~/.ipython directory
80 76
81 77 Note: The IPYTHONDIR is also used by the trunk version of IPython so
82 78 changing it will also affect it was well.
83 79 """
84 80
85 81 # In cwd or by absolute path with ~ expanded
86 82 trythis = os.path.expanduser(filename)
87 83 if os.path.isfile(trythis):
88 84 return trythis
89 85
90 86 # In ipythondir if it is set
91 87 if ipythondir is not None:
92 88 trythis = pjoin(ipythondir, filename)
93 89 if os.path.isfile(trythis):
94 90 return trythis
95 91
96 92 trythis = pjoin(get_ipython_dir(), filename)
97 93 if os.path.isfile(trythis):
98 94 return trythis
99 95
100 96 return None
101 97
102 98
103 99
104 100
105 101
106 102
@@ -1,76 +1,76 b''
1 1 """
2 2 Base front end class for all async frontends.
3 3 """
4 4 __docformat__ = "restructuredtext en"
5 5
6 6 #-------------------------------------------------------------------------------
7 7 # Copyright (C) 2008 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-------------------------------------------------------------------------------
12 12
13 13
14 14 #-------------------------------------------------------------------------------
15 15 # Imports
16 16 #-------------------------------------------------------------------------------
17 import uuid
17 from IPython.external import guid
18 18
19 19
20 20 from zope.interface import Interface, Attribute, implements, classProvides
21 21 from twisted.python.failure import Failure
22 22 from IPython.frontend.frontendbase import FrontEndBase, IFrontEnd, IFrontEndFactory
23 23 from IPython.kernel.core.history import FrontEndHistory
24 24 from IPython.kernel.engineservice import IEngineCore
25 25
26 26
27 27 class AsyncFrontEndBase(FrontEndBase):
28 28 """
29 29 Overrides FrontEndBase to wrap execute in a deferred result.
30 30 All callbacks are made as callbacks on the deferred result.
31 31 """
32 32
33 33 implements(IFrontEnd)
34 34 classProvides(IFrontEndFactory)
35 35
36 36 def __init__(self, engine=None, history=None):
37 37 assert(engine==None or IEngineCore.providedBy(engine))
38 38 self.engine = IEngineCore(engine)
39 39 if history is None:
40 40 self.history = FrontEndHistory(input_cache=[''])
41 41 else:
42 42 self.history = history
43 43
44 44
45 45 def execute(self, block, blockID=None):
46 46 """Execute the block and return the deferred result.
47 47
48 48 Parameters:
49 49 block : {str, AST}
50 50 blockID : any
51 51 Caller may provide an ID to identify this block.
52 52 result['blockID'] := blockID
53 53
54 54 Result:
55 55 Deferred result of self.interpreter.execute
56 56 """
57 57
58 58 if(not self.is_complete(block)):
59 59 return Failure(Exception("Block is not compilable"))
60 60
61 61 if(blockID == None):
62 blockID = uuid.uuid4() #random UUID
62 blockID = guid.generate()
63 63
64 64 d = self.engine.execute(block)
65 65 d.addCallback(self._add_history, block=block)
66 66 d.addCallbacks(self._add_block_id_for_result,
67 67 errback=self._add_block_id_for_failure,
68 68 callbackArgs=(blockID,),
69 69 errbackArgs=(blockID,))
70 70 d.addBoth(self.update_cell_prompt, blockID=blockID)
71 71 d.addCallbacks(self.render_result,
72 72 errback=self.render_error)
73 73
74 74 return d
75 75
76 76
@@ -1,560 +1,560 b''
1 1 # encoding: utf-8
2 2 # -*- test-case-name: IPython.frontend.cocoa.tests.test_cocoa_frontend -*-
3 3
4 4 """PyObjC classes to provide a Cocoa frontend to the
5 5 IPython.kernel.engineservice.IEngineBase.
6 6
7 7 To add an IPython interpreter to a cocoa app, instantiate an
8 8 IPythonCocoaController in a XIB and connect its textView outlet to an
9 9 NSTextView instance in your UI. That's it.
10 10
11 11 Author: Barry Wark
12 12 """
13 13
14 14 __docformat__ = "restructuredtext en"
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Copyright (C) 2008 The IPython Development Team
18 18 #
19 19 # Distributed under the terms of the BSD License. The full license is in
20 20 # the file COPYING, distributed as part of this software.
21 21 #-----------------------------------------------------------------------------
22 22
23 23 #-----------------------------------------------------------------------------
24 24 # Imports
25 25 #-----------------------------------------------------------------------------
26 26
27 27 import sys
28 28 import objc
29 import uuid
29 from IPython.external import guid
30 30
31 31 from Foundation import NSObject, NSMutableArray, NSMutableDictionary,\
32 32 NSLog, NSNotificationCenter, NSMakeRange,\
33 33 NSLocalizedString, NSIntersectionRange,\
34 34 NSString, NSAutoreleasePool
35 35
36 36 from AppKit import NSApplicationWillTerminateNotification, NSBeep,\
37 37 NSTextView, NSRulerView, NSVerticalRuler
38 38
39 39 from pprint import saferepr
40 40
41 41 import IPython
42 42 from IPython.kernel.engineservice import ThreadedEngineService
43 43 from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase
44 44
45 45 from twisted.internet.threads import blockingCallFromThread
46 46 from twisted.python.failure import Failure
47 47
48 48 #-----------------------------------------------------------------------------
49 49 # Classes to implement the Cocoa frontend
50 50 #-----------------------------------------------------------------------------
51 51
52 52 # TODO:
53 53 # 1. use MultiEngineClient and out-of-process engine rather than
54 54 # ThreadedEngineService?
55 55 # 2. integrate Xgrid launching of engines
56 56
57 57 class AutoreleasePoolWrappedThreadedEngineService(ThreadedEngineService):
58 58 """Wrap all blocks in an NSAutoreleasePool"""
59 59
60 60 def wrapped_execute(self, msg, lines):
61 61 """wrapped_execute"""
62 62 try:
63 63 p = NSAutoreleasePool.alloc().init()
64 64 result = super(AutoreleasePoolWrappedThreadedEngineService,
65 65 self).wrapped_execute(msg, lines)
66 66 finally:
67 67 p.drain()
68 68
69 69 return result
70 70
71 71
72 72
73 73 class Cell(NSObject):
74 74 """
75 75 Representation of the prompts, input and output of a cell in the
76 76 frontend
77 77 """
78 78
79 79 blockNumber = objc.ivar().unsigned_long()
80 80 blockID = objc.ivar()
81 81 inputBlock = objc.ivar()
82 82 output = objc.ivar()
83 83
84 84
85 85
86 86 class CellBlock(object):
87 87 """
88 88 Storage for information about text ranges relating to a single cell
89 89 """
90 90
91 91
92 92 def __init__(self, inputPromptRange, inputRange=None, outputPromptRange=None,
93 93 outputRange=None):
94 94 super(CellBlock, self).__init__()
95 95 self.inputPromptRange = inputPromptRange
96 96 self.inputRange = inputRange
97 97 self.outputPromptRange = outputPromptRange
98 98 self.outputRange = outputRange
99 99
100 100 def update_ranges_for_insertion(self, text, textRange):
101 101 """Update ranges for text insertion at textRange"""
102 102
103 103 for r in [self.inputPromptRange,self.inputRange,
104 104 self.outputPromptRange, self.outputRange]:
105 105 if(r == None):
106 106 continue
107 107 intersection = NSIntersectionRange(r,textRange)
108 108 if(intersection.length == 0): #ranges don't intersect
109 109 if r.location >= textRange.location:
110 110 r.location += len(text)
111 111 else: #ranges intersect
112 112 if(r.location > textRange.location):
113 113 offset = len(text) - intersection.length
114 114 r.length -= offset
115 115 r.location += offset
116 116 elif(r.location == textRange.location):
117 117 r.length += len(text) - intersection.length
118 118 else:
119 119 r.length -= intersection.length
120 120
121 121
122 122 def update_ranges_for_deletion(self, textRange):
123 123 """Update ranges for text deletion at textRange"""
124 124
125 125 for r in [self.inputPromptRange,self.inputRange,
126 126 self.outputPromptRange, self.outputRange]:
127 127 if(r==None):
128 128 continue
129 129 intersection = NSIntersectionRange(r, textRange)
130 130 if(intersection.length == 0): #ranges don't intersect
131 131 if r.location >= textRange.location:
132 132 r.location -= textRange.length
133 133 else: #ranges intersect
134 134 if(r.location > textRange.location):
135 135 offset = intersection.length
136 136 r.length -= offset
137 137 r.location += offset
138 138 elif(r.location == textRange.location):
139 139 r.length += intersection.length
140 140 else:
141 141 r.length -= intersection.length
142 142
143 143 def __repr__(self):
144 144 return 'CellBlock('+ str((self.inputPromptRange,
145 145 self.inputRange,
146 146 self.outputPromptRange,
147 147 self.outputRange)) + ')'
148 148
149 149
150 150
151 151
152 152 class IPythonCocoaController(NSObject, AsyncFrontEndBase):
153 153 userNS = objc.ivar() #mirror of engine.user_ns (key=>str(value))
154 154 waitingForEngine = objc.ivar().bool()
155 155 textView = objc.IBOutlet()
156 156
157 157 def init(self):
158 158 self = super(IPythonCocoaController, self).init()
159 159 AsyncFrontEndBase.__init__(self,
160 160 engine=AutoreleasePoolWrappedThreadedEngineService())
161 161 if(self != None):
162 162 self._common_init()
163 163
164 164 return self
165 165
166 166 def _common_init(self):
167 167 """_common_init"""
168 168
169 169 self.userNS = NSMutableDictionary.dictionary()
170 170 self.waitingForEngine = False
171 171
172 172 self.lines = {}
173 173 self.tabSpaces = 4
174 174 self.tabUsesSpaces = True
175 175 self.currentBlockID = self.next_block_ID()
176 176 self.blockRanges = {} # blockID=>CellBlock
177 177
178 178
179 179 def awakeFromNib(self):
180 180 """awakeFromNib"""
181 181
182 182 self._common_init()
183 183
184 184 # Start the IPython engine
185 185 self.engine.startService()
186 186 NSLog('IPython engine started')
187 187
188 188 # Register for app termination
189 189 nc = NSNotificationCenter.defaultCenter()
190 190 nc.addObserver_selector_name_object_(
191 191 self,
192 192 'appWillTerminate:',
193 193 NSApplicationWillTerminateNotification,
194 194 None)
195 195
196 196 self.textView.setDelegate_(self)
197 197 self.textView.enclosingScrollView().setHasVerticalRuler_(True)
198 198 r = NSRulerView.alloc().initWithScrollView_orientation_(
199 199 self.textView.enclosingScrollView(),
200 200 NSVerticalRuler)
201 201 self.verticalRulerView = r
202 202 self.verticalRulerView.setClientView_(self.textView)
203 203 self._start_cli_banner()
204 204 self.start_new_block()
205 205
206 206
207 207 def appWillTerminate_(self, notification):
208 208 """appWillTerminate"""
209 209
210 210 self.engine.stopService()
211 211
212 212
213 213 def complete(self, token):
214 214 """Complete token in engine's user_ns
215 215
216 216 Parameters
217 217 ----------
218 218 token : string
219 219
220 220 Result
221 221 ------
222 222 Deferred result of
223 223 IPython.kernel.engineservice.IEngineBase.complete
224 224 """
225 225
226 226 return self.engine.complete(token)
227 227
228 228
229 229 def execute(self, block, blockID=None):
230 230 self.waitingForEngine = True
231 231 self.willChangeValueForKey_('commandHistory')
232 232 d = super(IPythonCocoaController, self).execute(block,
233 233 blockID)
234 234 d.addBoth(self._engine_done)
235 235 d.addCallback(self._update_user_ns)
236 236
237 237 return d
238 238
239 239
240 240 def push_(self, namespace):
241 241 """Push dictionary of key=>values to python namespace"""
242 242
243 243 self.waitingForEngine = True
244 244 self.willChangeValueForKey_('commandHistory')
245 245 d = self.engine.push(namespace)
246 246 d.addBoth(self._engine_done)
247 247 d.addCallback(self._update_user_ns)
248 248
249 249
250 250 def pull_(self, keys):
251 251 """Pull keys from python namespace"""
252 252
253 253 self.waitingForEngine = True
254 254 result = blockingCallFromThread(self.engine.pull, keys)
255 255 self.waitingForEngine = False
256 256
257 257 @objc.signature('v@:@I')
258 258 def executeFileAtPath_encoding_(self, path, encoding):
259 259 """Execute file at path in an empty namespace. Update the engine
260 260 user_ns with the resulting locals."""
261 261
262 262 lines,err = NSString.stringWithContentsOfFile_encoding_error_(
263 263 path,
264 264 encoding,
265 265 None)
266 266 self.engine.execute(lines)
267 267
268 268
269 269 def _engine_done(self, x):
270 270 self.waitingForEngine = False
271 271 self.didChangeValueForKey_('commandHistory')
272 272 return x
273 273
274 274 def _update_user_ns(self, result):
275 275 """Update self.userNS from self.engine's namespace"""
276 276 d = self.engine.keys()
277 277 d.addCallback(self._get_engine_namespace_values_for_keys)
278 278
279 279 return result
280 280
281 281
282 282 def _get_engine_namespace_values_for_keys(self, keys):
283 283 d = self.engine.pull(keys)
284 284 d.addCallback(self._store_engine_namespace_values, keys=keys)
285 285
286 286
287 287 def _store_engine_namespace_values(self, values, keys=[]):
288 288 assert(len(values) == len(keys))
289 289 self.willChangeValueForKey_('userNS')
290 290 for (k,v) in zip(keys,values):
291 291 self.userNS[k] = saferepr(v)
292 292 self.didChangeValueForKey_('userNS')
293 293
294 294
295 295 def update_cell_prompt(self, result, blockID=None):
296 296 print self.blockRanges
297 297 if(isinstance(result, Failure)):
298 298 prompt = self.input_prompt()
299 299
300 300 else:
301 301 prompt = self.input_prompt(number=result['number'])
302 302
303 303 r = self.blockRanges[blockID].inputPromptRange
304 304 self.insert_text(prompt,
305 305 textRange=r,
306 306 scrollToVisible=False
307 307 )
308 308
309 309 return result
310 310
311 311
312 312 def render_result(self, result):
313 313 blockID = result['blockID']
314 314 inputRange = self.blockRanges[blockID].inputRange
315 315 del self.blockRanges[blockID]
316 316
317 317 #print inputRange,self.current_block_range()
318 318 self.insert_text('\n' +
319 319 self.output_prompt(number=result['number']) +
320 320 result.get('display',{}).get('pprint','') +
321 321 '\n\n',
322 322 textRange=NSMakeRange(inputRange.location+inputRange.length,
323 323 0))
324 324 return result
325 325
326 326
327 327 def render_error(self, failure):
328 328 print failure
329 329 blockID = failure.blockID
330 330 inputRange = self.blockRanges[blockID].inputRange
331 331 self.insert_text('\n' +
332 332 self.output_prompt() +
333 333 '\n' +
334 334 failure.getErrorMessage() +
335 335 '\n\n',
336 336 textRange=NSMakeRange(inputRange.location +
337 337 inputRange.length,
338 338 0))
339 339 self.start_new_block()
340 340 return failure
341 341
342 342
343 343 def _start_cli_banner(self):
344 344 """Print banner"""
345 345
346 346 banner = """IPython1 %s -- An enhanced Interactive Python.""" % \
347 347 IPython.__version__
348 348
349 349 self.insert_text(banner + '\n\n')
350 350
351 351
352 352 def start_new_block(self):
353 353 """"""
354 354
355 355 self.currentBlockID = self.next_block_ID()
356 356 self.blockRanges[self.currentBlockID] = self.new_cell_block()
357 357 self.insert_text(self.input_prompt(),
358 358 textRange=self.current_block_range().inputPromptRange)
359 359
360 360
361 361
362 362 def next_block_ID(self):
363 363
364 return uuid.uuid4()
364 return guid.generate()
365 365
366 366 def new_cell_block(self):
367 367 """A new CellBlock at the end of self.textView.textStorage()"""
368 368
369 369 return CellBlock(NSMakeRange(self.textView.textStorage().length(),
370 370 0), #len(self.input_prompt())),
371 371 NSMakeRange(self.textView.textStorage().length(),# + len(self.input_prompt()),
372 372 0))
373 373
374 374
375 375 def current_block_range(self):
376 376 return self.blockRanges.get(self.currentBlockID,
377 377 self.new_cell_block())
378 378
379 379 def current_block(self):
380 380 """The current block's text"""
381 381
382 382 return self.text_for_range(self.current_block_range().inputRange)
383 383
384 384 def text_for_range(self, textRange):
385 385 """text_for_range"""
386 386
387 387 ts = self.textView.textStorage()
388 388 return ts.string().substringWithRange_(textRange)
389 389
390 390 def current_line(self):
391 391 block = self.text_for_range(self.current_block_range().inputRange)
392 392 block = block.split('\n')
393 393 return block[-1]
394 394
395 395
396 396 def insert_text(self, string=None, textRange=None, scrollToVisible=True):
397 397 """Insert text into textView at textRange, updating blockRanges
398 398 as necessary
399 399 """
400 400 if(textRange == None):
401 401 #range for end of text
402 402 textRange = NSMakeRange(self.textView.textStorage().length(), 0)
403 403
404 404
405 405 self.textView.replaceCharactersInRange_withString_(
406 406 textRange, string)
407 407
408 408 for r in self.blockRanges.itervalues():
409 409 r.update_ranges_for_insertion(string, textRange)
410 410
411 411 self.textView.setSelectedRange_(textRange)
412 412 if(scrollToVisible):
413 413 self.textView.scrollRangeToVisible_(textRange)
414 414
415 415
416 416
417 417 def replace_current_block_with_string(self, textView, string):
418 418 textView.replaceCharactersInRange_withString_(
419 419 self.current_block_range().inputRange,
420 420 string)
421 421 self.current_block_range().inputRange.length = len(string)
422 422 r = NSMakeRange(textView.textStorage().length(), 0)
423 423 textView.scrollRangeToVisible_(r)
424 424 textView.setSelectedRange_(r)
425 425
426 426
427 427 def current_indent_string(self):
428 428 """returns string for indent or None if no indent"""
429 429
430 430 return self._indent_for_block(self.current_block())
431 431
432 432
433 433 def _indent_for_block(self, block):
434 434 lines = block.split('\n')
435 435 if(len(lines) > 1):
436 436 currentIndent = len(lines[-1]) - len(lines[-1].lstrip())
437 437 if(currentIndent == 0):
438 438 currentIndent = self.tabSpaces
439 439
440 440 if(self.tabUsesSpaces):
441 441 result = ' ' * currentIndent
442 442 else:
443 443 result = '\t' * (currentIndent/self.tabSpaces)
444 444 else:
445 445 result = None
446 446
447 447 return result
448 448
449 449
450 450 # NSTextView delegate methods...
451 451 def textView_doCommandBySelector_(self, textView, selector):
452 452 assert(textView == self.textView)
453 453 NSLog("textView_doCommandBySelector_: "+selector)
454 454
455 455
456 456 if(selector == 'insertNewline:'):
457 457 indent = self.current_indent_string()
458 458 if(indent):
459 459 line = indent + self.current_line()
460 460 else:
461 461 line = self.current_line()
462 462
463 463 if(self.is_complete(self.current_block())):
464 464 self.execute(self.current_block(),
465 465 blockID=self.currentBlockID)
466 466 self.start_new_block()
467 467
468 468 return True
469 469
470 470 return False
471 471
472 472 elif(selector == 'moveUp:'):
473 473 prevBlock = self.get_history_previous(self.current_block())
474 474 if(prevBlock != None):
475 475 self.replace_current_block_with_string(textView, prevBlock)
476 476 else:
477 477 NSBeep()
478 478 return True
479 479
480 480 elif(selector == 'moveDown:'):
481 481 nextBlock = self.get_history_next()
482 482 if(nextBlock != None):
483 483 self.replace_current_block_with_string(textView, nextBlock)
484 484 else:
485 485 NSBeep()
486 486 return True
487 487
488 488 elif(selector == 'moveToBeginningOfParagraph:'):
489 489 textView.setSelectedRange_(NSMakeRange(
490 490 self.current_block_range().inputRange.location,
491 491 0))
492 492 return True
493 493 elif(selector == 'moveToEndOfParagraph:'):
494 494 textView.setSelectedRange_(NSMakeRange(
495 495 self.current_block_range().inputRange.location + \
496 496 self.current_block_range().inputRange.length, 0))
497 497 return True
498 498 elif(selector == 'deleteToEndOfParagraph:'):
499 499 if(textView.selectedRange().location <= \
500 500 self.current_block_range().location):
501 501 raise NotImplemented()
502 502
503 503 return False # don't actually handle the delete
504 504
505 505 elif(selector == 'insertTab:'):
506 506 if(len(self.current_line().strip()) == 0): #only white space
507 507 return False
508 508 else:
509 509 self.textView.complete_(self)
510 510 return True
511 511
512 512 elif(selector == 'deleteBackward:'):
513 513 #if we're at the beginning of the current block, ignore
514 514 if(textView.selectedRange().location == \
515 515 self.current_block_range().inputRange.location):
516 516 return True
517 517 else:
518 518 for r in self.blockRanges.itervalues():
519 519 deleteRange = textView.selectedRange
520 520 if(deleteRange.length == 0):
521 521 deleteRange.location -= 1
522 522 deleteRange.length = 1
523 523 r.update_ranges_for_deletion(deleteRange)
524 524 return False
525 525 return False
526 526
527 527
528 528 def textView_shouldChangeTextInRanges_replacementStrings_(self,
529 529 textView, ranges, replacementStrings):
530 530 """
531 531 Delegate method for NSTextView.
532 532
533 533 Refuse change text in ranges not at end, but make those changes at
534 534 end.
535 535 """
536 536
537 537 assert(len(ranges) == len(replacementStrings))
538 538 allow = True
539 539 for r,s in zip(ranges, replacementStrings):
540 540 r = r.rangeValue()
541 541 if(textView.textStorage().length() > 0 and
542 542 r.location < self.current_block_range().inputRange.location):
543 543 self.insert_text(s)
544 544 allow = False
545 545
546 546 return allow
547 547
548 548 def textView_completions_forPartialWordRange_indexOfSelectedItem_(self,
549 549 textView, words, charRange, index):
550 550 try:
551 551 ts = textView.textStorage()
552 552 token = ts.string().substringWithRange_(charRange)
553 553 completions = blockingCallFromThread(self.complete, token)
554 554 except:
555 555 completions = objc.nil
556 556 NSBeep()
557 557
558 558 return (completions,0)
559 559
560 560
@@ -1,362 +1,343 b''
1 1 # encoding: utf-8
2 2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
3 3 """
4 4 frontendbase provides an interface and base class for GUI frontends for
5 5 IPython.kernel/IPython.kernel.core.
6 6
7 7 Frontend implementations will likely want to subclass FrontEndBase.
8 8
9 9 Author: Barry Wark
10 10 """
11 11 __docformat__ = "restructuredtext en"
12 12
13 13 #-------------------------------------------------------------------------------
14 14 # Copyright (C) 2008 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-------------------------------------------------------------------------------
19 19
20 20 #-------------------------------------------------------------------------------
21 21 # Imports
22 22 #-------------------------------------------------------------------------------
23 23 import string
24 import uuid
25 import _ast
24 import codeop
25 from IPython.external import guid
26
26 27
27 28 from IPython.frontend.zopeinterface import (
28 29 Interface,
29 30 Attribute,
30 implements,
31 classProvides
32 31 )
33 32 from IPython.kernel.core.history import FrontEndHistory
34 33 from IPython.kernel.core.util import Bunch
35 34
36 35 ##############################################################################
37 36 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
38 37 # not
39 38
40 39 rc = Bunch()
41 40 rc.prompt_in1 = r'In [$number]: '
42 41 rc.prompt_in2 = r'...'
43 42 rc.prompt_out = r'Out [$number]: '
44 43
45 44 ##############################################################################
46 45 # Interface definitions
47 46 ##############################################################################
48 47
49 48 class IFrontEndFactory(Interface):
50 49 """Factory interface for frontends."""
51 50
52 51 def __call__(engine=None, history=None):
53 52 """
54 53 Parameters:
55 54 interpreter : IPython.kernel.engineservice.IEngineCore
56 55 """
57 56
58 57 pass
59 58
60 59
61 60 class IFrontEnd(Interface):
62 61 """Interface for frontends. All methods return t.i.d.Deferred"""
63 62
64 63 Attribute("input_prompt_template", "string.Template instance\
65 64 substituteable with execute result.")
66 65 Attribute("output_prompt_template", "string.Template instance\
67 66 substituteable with execute result.")
68 67 Attribute("continuation_prompt_template", "string.Template instance\
69 68 substituteable with execute result.")
70 69
71 70 def update_cell_prompt(result, blockID=None):
72 71 """Subclass may override to update the input prompt for a block.
73 72
74 73 In asynchronous frontends, this method will be called as a
75 74 twisted.internet.defer.Deferred's callback/errback.
76 75 Implementations should thus return result when finished.
77 76
78 77 Result is a result dict in case of success, and a
79 78 twisted.python.util.failure.Failure in case of an error
80 79 """
81 80
82 81 pass
83 82
84 83 def render_result(result):
85 84 """Render the result of an execute call. Implementors may choose the
86 85 method of rendering.
87 86 For example, a notebook-style frontend might render a Chaco plot
88 87 inline.
89 88
90 89 Parameters:
91 90 result : dict (result of IEngineBase.execute )
92 91 blockID = result['blockID']
93 92
94 93 Result:
95 94 Output of frontend rendering
96 95 """
97 96
98 97 pass
99 98
100 99 def render_error(failure):
101 100 """Subclasses must override to render the failure.
102 101
103 102 In asynchronous frontend, since this method will be called as a
104 103 twisted.internet.defer.Deferred's callback. Implementations
105 104 should thus return result when finished.
106 105
107 106 blockID = failure.blockID
108 107 """
109 108
110 109 pass
111 110
112 111 def input_prompt(number=''):
113 112 """Returns the input prompt by subsituting into
114 113 self.input_prompt_template
115 114 """
116 115 pass
117 116
118 117 def output_prompt(number=''):
119 118 """Returns the output prompt by subsituting into
120 119 self.output_prompt_template
121 120 """
122 121
123 122 pass
124 123
125 124 def continuation_prompt():
126 125 """Returns the continuation prompt by subsituting into
127 126 self.continuation_prompt_template
128 127 """
129 128
130 129 pass
131 130
132 131 def is_complete(block):
133 132 """Returns True if block is complete, False otherwise."""
134 133
135 134 pass
136 135
137 def compile_ast(block):
138 """Compiles block to an _ast.AST"""
139
140 pass
141
136
142 137 def get_history_previous(current_block):
143 138 """Returns the block previous in the history. Saves currentBlock if
144 139 the history_cursor is currently at the end of the input history"""
145 140 pass
146 141
147 142 def get_history_next():
148 143 """Returns the next block in the history."""
149 144
150 145 pass
151 146
152 147 def complete(self, line):
153 148 """Returns the list of possible completions, and the completed
154 149 line.
155 150
156 151 The input argument is the full line to be completed. This method
157 152 returns both the line completed as much as possible, and the list
158 153 of further possible completions (full words).
159 154 """
160 155 pass
161 156
162 157
163 158 ##############################################################################
164 159 # Base class for all the frontends.
165 160 ##############################################################################
166 161
167 162 class FrontEndBase(object):
168 163 """
169 164 FrontEndBase manages the state tasks for a CLI frontend:
170 165 - Input and output history management
171 166 - Input/continuation and output prompt generation
172 167
173 168 Some issues (due to possibly unavailable engine):
174 169 - How do we get the current cell number for the engine?
175 170 - How do we handle completions?
176 171 """
177 172
178 173 history_cursor = 0
179 174
180 175 input_prompt_template = string.Template(rc.prompt_in1)
181 176 output_prompt_template = string.Template(rc.prompt_out)
182 177 continuation_prompt_template = string.Template(rc.prompt_in2)
183 178
184 179 def __init__(self, shell=None, history=None):
185 180 self.shell = shell
186 181 if history is None:
187 182 self.history = FrontEndHistory(input_cache=[''])
188 183 else:
189 184 self.history = history
190 185
191 186
192 187 def input_prompt(self, number=''):
193 188 """Returns the current input prompt
194 189
195 190 It would be great to use ipython1.core.prompts.Prompt1 here
196 191 """
197 192 return self.input_prompt_template.safe_substitute({'number':number})
198 193
199 194
200 195 def continuation_prompt(self):
201 196 """Returns the current continuation prompt"""
202 197
203 198 return self.continuation_prompt_template.safe_substitute()
204 199
205 200 def output_prompt(self, number=''):
206 201 """Returns the output prompt for result"""
207 202
208 203 return self.output_prompt_template.safe_substitute({'number':number})
209 204
210 205
211 206 def is_complete(self, block):
212 207 """Determine if block is complete.
213 208
214 209 Parameters
215 210 block : string
216 211
217 212 Result
218 213 True if block can be sent to the engine without compile errors.
219 214 False otherwise.
220 215 """
221 216
222 217 try:
223 ast = self.compile_ast(block)
218 is_complete = codeop.compile_command(block.rstrip() + '\n\n',
219 "<string>", "exec")
224 220 except:
225 221 return False
226 222
227 223 lines = block.split('\n')
228 return (len(lines)==1 or str(lines[-1])=='')
229
230
231 def compile_ast(self, block):
232 """Compile block to an AST
233
234 Parameters:
235 block : str
236
237 Result:
238 AST
239
240 Throws:
241 Exception if block cannot be compiled
242 """
243
244 return compile(block, "<string>", "exec", _ast.PyCF_ONLY_AST)
224 return ((is_complete is not None)
225 and (len(lines)==1 or str(lines[-1])==''))
245 226
246 227
247 228 def execute(self, block, blockID=None):
248 229 """Execute the block and return the result.
249 230
250 231 Parameters:
251 232 block : {str, AST}
252 233 blockID : any
253 234 Caller may provide an ID to identify this block.
254 235 result['blockID'] := blockID
255 236
256 237 Result:
257 238 Deferred result of self.interpreter.execute
258 239 """
259 240
260 241 if(not self.is_complete(block)):
261 242 raise Exception("Block is not compilable")
262 243
263 244 if(blockID == None):
264 blockID = uuid.uuid4() #random UUID
245 blockID = guid.generate()
265 246
266 247 try:
267 248 result = self.shell.execute(block)
268 249 except Exception,e:
269 250 e = self._add_block_id_for_failure(e, blockID=blockID)
270 251 e = self.update_cell_prompt(e, blockID=blockID)
271 252 e = self.render_error(e)
272 253 else:
273 254 result = self._add_block_id_for_result(result, blockID=blockID)
274 255 result = self.update_cell_prompt(result, blockID=blockID)
275 256 result = self.render_result(result)
276 257
277 258 return result
278 259
279 260
280 261 def _add_block_id_for_result(self, result, blockID):
281 262 """Add the blockID to result or failure. Unfortunatley, we have to
282 263 treat failures differently than result dicts.
283 264 """
284 265
285 266 result['blockID'] = blockID
286 267
287 268 return result
288 269
289 270 def _add_block_id_for_failure(self, failure, blockID):
290 271 """_add_block_id_for_failure"""
291 272 failure.blockID = blockID
292 273 return failure
293 274
294 275
295 276 def _add_history(self, result, block=None):
296 277 """Add block to the history"""
297 278
298 279 assert(block != None)
299 280 self.history.add_items([block])
300 281 self.history_cursor += 1
301 282
302 283 return result
303 284
304 285
305 286 def get_history_previous(self, current_block):
306 287 """ Returns previous history string and decrement history cursor.
307 288 """
308 289 command = self.history.get_history_item(self.history_cursor - 1)
309 290
310 291 if command is not None:
311 292 if(self.history_cursor+1 == len(self.history.input_cache)):
312 293 self.history.input_cache[self.history_cursor] = current_block
313 294 self.history_cursor -= 1
314 295 return command
315 296
316 297
317 298 def get_history_next(self):
318 299 """ Returns next history string and increment history cursor.
319 300 """
320 301 command = self.history.get_history_item(self.history_cursor+1)
321 302
322 303 if command is not None:
323 304 self.history_cursor += 1
324 305 return command
325 306
326 307 ###
327 308 # Subclasses probably want to override these methods...
328 309 ###
329 310
330 311 def update_cell_prompt(self, result, blockID=None):
331 312 """Subclass may override to update the input prompt for a block.
332 313
333 314 This method only really makes sens in asyncrhonous frontend.
334 315 Since this method will be called as a
335 316 twisted.internet.defer.Deferred's callback, implementations should
336 317 return result when finished.
337 318 """
338 319
339 320 raise NotImplementedError
340 321
341 322
342 323 def render_result(self, result):
343 324 """Subclasses must override to render result.
344 325
345 326 In asynchronous frontends, this method will be called as a
346 327 twisted.internet.defer.Deferred's callback. Implementations
347 328 should thus return result when finished.
348 329 """
349 330
350 331 raise NotImplementedError
351 332
352 333
353 334 def render_error(self, failure):
354 335 """Subclasses must override to render the failure.
355 336
356 337 In asynchronous frontends, this method will be called as a
357 338 twisted.internet.defer.Deferred's callback. Implementations
358 339 should thus return result when finished.
359 340 """
360 341
361 342 raise NotImplementedError
362 343
@@ -1,320 +1,333 b''
1 1 """
2 2 Base front end class for all line-oriented frontends, rather than
3 3 block-oriented.
4 4
5 5 Currently this focuses on synchronous frontends.
6 6 """
7 7 __docformat__ = "restructuredtext en"
8 8
9 9 #-------------------------------------------------------------------------------
10 10 # Copyright (C) 2008 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-------------------------------------------------------------------------------
15 15
16 16 #-------------------------------------------------------------------------------
17 17 # Imports
18 18 #-------------------------------------------------------------------------------
19 19 import re
20 20
21 21 import IPython
22 22 import sys
23 23 import codeop
24 24 import traceback
25 25
26 26 from frontendbase import FrontEndBase
27 27 from IPython.kernel.core.interpreter import Interpreter
28 28
29 29 def common_prefix(strings):
30 30 """ Given a list of strings, return the common prefix between all
31 31 these strings.
32 32 """
33 33 ref = strings[0]
34 34 prefix = ''
35 35 for size in range(len(ref)):
36 36 test_prefix = ref[:size+1]
37 37 for string in strings[1:]:
38 38 if not string.startswith(test_prefix):
39 39 return prefix
40 40 prefix = test_prefix
41 41
42 42 return prefix
43 43
44 44 #-------------------------------------------------------------------------------
45 45 # Base class for the line-oriented front ends
46 46 #-------------------------------------------------------------------------------
47 47 class LineFrontEndBase(FrontEndBase):
48 48 """ Concrete implementation of the FrontEndBase class. This is meant
49 49 to be the base class behind all the frontend that are line-oriented,
50 50 rather than block-oriented.
51 51 """
52 52
53 53 # We need to keep the prompt number, to be able to increment
54 54 # it when there is an exception.
55 55 prompt_number = 1
56 56
57 57 # We keep a reference to the last result: it helps testing and
58 58 # programatic control of the frontend.
59 59 last_result = dict(number=0)
60 60
61 61 # The input buffer being edited
62 62 input_buffer = ''
63 63
64 64 # Set to true for debug output
65 65 debug = False
66 66
67 67 # A banner to print at startup
68 68 banner = None
69 69
70 70 #--------------------------------------------------------------------------
71 71 # FrontEndBase interface
72 72 #--------------------------------------------------------------------------
73 73
74 74 def __init__(self, shell=None, history=None, banner=None, *args, **kwargs):
75 75 if shell is None:
76 76 shell = Interpreter()
77 77 FrontEndBase.__init__(self, shell=shell, history=history)
78 78
79 79 if banner is not None:
80 80 self.banner = banner
81 81
82 82 def start(self):
83 83 """ Put the frontend in a state where it is ready for user
84 84 interaction.
85 85 """
86 86 if self.banner is not None:
87 87 self.write(self.banner, refresh=False)
88 88
89 89 self.new_prompt(self.input_prompt_template.substitute(number=1))
90 90
91 91
92 92 def complete(self, line):
93 93 """Complete line in engine's user_ns
94 94
95 95 Parameters
96 96 ----------
97 97 line : string
98 98
99 99 Result
100 100 ------
101 101 The replacement for the line and the list of possible completions.
102 102 """
103 103 completions = self.shell.complete(line)
104 104 complete_sep = re.compile('[\s\{\}\[\]\(\)\=]')
105 105 if completions:
106 106 prefix = common_prefix(completions)
107 107 residual = complete_sep.split(line)[:-1]
108 108 line = line[:-len(residual)] + prefix
109 109 return line, completions
110 110
111 111
112 112 def render_result(self, result):
113 113 """ Frontend-specific rendering of the result of a calculation
114 114 that has been sent to an engine.
115 115 """
116 116 if 'stdout' in result and result['stdout']:
117 117 self.write('\n' + result['stdout'])
118 118 if 'display' in result and result['display']:
119 119 self.write("%s%s\n" % (
120 120 self.output_prompt_template.substitute(
121 121 number=result['number']),
122 122 result['display']['pprint']
123 123 ) )
124 124
125 125
126 126 def render_error(self, failure):
127 127 """ Frontend-specific rendering of error.
128 128 """
129 129 self.write('\n\n'+str(failure)+'\n\n')
130 130 return failure
131 131
132 132
133 133 def is_complete(self, string):
134 134 """ Check if a string forms a complete, executable set of
135 135 commands.
136 136
137 137 For the line-oriented frontend, multi-line code is not executed
138 138 as soon as it is complete: the users has to enter two line
139 139 returns.
140 140 """
141 141 if string in ('', '\n'):
142 142 # Prefiltering, eg through ipython0, may return an empty
143 143 # string although some operations have been accomplished. We
144 144 # thus want to consider an empty string as a complete
145 145 # statement.
146 146 return True
147 147 elif ( len(self.input_buffer.split('\n'))>2
148 148 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)):
149 149 return False
150 150 else:
151 151 self.capture_output()
152 152 try:
153 153 # Add line returns here, to make sure that the statement is
154 154 # complete.
155 155 is_complete = codeop.compile_command(string.rstrip() + '\n\n',
156 156 "<string>", "exec")
157 157 self.release_output()
158 158 except Exception, e:
159 159 # XXX: Hack: return True so that the
160 160 # code gets executed and the error captured.
161 161 is_complete = True
162 162 return is_complete
163 163
164 164
165 165 def write(self, string, refresh=True):
166 166 """ Write some characters to the display.
167 167
168 168 Subclass should overide this method.
169 169
170 170 The refresh keyword argument is used in frontends with an
171 171 event loop, to choose whether the write should trigget an UI
172 172 refresh, and thus be syncrhonous, or not.
173 173 """
174 174 print >>sys.__stderr__, string
175 175
176 176
177 177 def execute(self, python_string, raw_string=None):
178 178 """ Stores the raw_string in the history, and sends the
179 179 python string to the interpreter.
180 180 """
181 181 if raw_string is None:
182 182 raw_string = python_string
183 183 # Create a false result, in case there is an exception
184 184 self.last_result = dict(number=self.prompt_number)
185
186 ## try:
187 ## self.history.input_cache[-1] = raw_string.rstrip()
188 ## result = self.shell.execute(python_string)
189 ## self.last_result = result
190 ## self.render_result(result)
191 ## except:
192 ## self.show_traceback()
193 ## finally:
194 ## self.after_execute()
195
185 196 try:
186 self.history.input_cache[-1] = raw_string.rstrip()
187 result = self.shell.execute(python_string)
188 self.last_result = result
189 self.render_result(result)
190 except:
191 self.show_traceback()
197 try:
198 self.history.input_cache[-1] = raw_string.rstrip()
199 result = self.shell.execute(python_string)
200 self.last_result = result
201 self.render_result(result)
202 except:
203 self.show_traceback()
192 204 finally:
193 205 self.after_execute()
194 206
207
195 208 #--------------------------------------------------------------------------
196 209 # LineFrontEndBase interface
197 210 #--------------------------------------------------------------------------
198 211
199 212 def prefilter_input(self, string):
200 213 """ Prefilter the input to turn it in valid python.
201 214 """
202 215 string = string.replace('\r\n', '\n')
203 216 string = string.replace('\t', 4*' ')
204 217 # Clean the trailing whitespace
205 218 string = '\n'.join(l.rstrip() for l in string.split('\n'))
206 219 return string
207 220
208 221
209 222 def after_execute(self):
210 223 """ All the operations required after an execution to put the
211 224 terminal back in a shape where it is usable.
212 225 """
213 226 self.prompt_number += 1
214 227 self.new_prompt(self.input_prompt_template.substitute(
215 228 number=(self.last_result['number'] + 1)))
216 229 # Start a new empty history entry
217 230 self._add_history(None, '')
218 231 self.history_cursor = len(self.history.input_cache) - 1
219 232
220 233
221 234 def complete_current_input(self):
222 235 """ Do code completion on current line.
223 236 """
224 237 if self.debug:
225 238 print >>sys.__stdout__, "complete_current_input",
226 239 line = self.input_buffer
227 240 new_line, completions = self.complete(line)
228 241 if len(completions)>1:
229 242 self.write_completion(completions, new_line=new_line)
230 243 elif not line == new_line:
231 244 self.input_buffer = new_line
232 245 if self.debug:
233 246 print >>sys.__stdout__, 'line', line
234 247 print >>sys.__stdout__, 'new_line', new_line
235 248 print >>sys.__stdout__, completions
236 249
237 250
238 251 def get_line_width(self):
239 252 """ Return the width of the line in characters.
240 253 """
241 254 return 80
242 255
243 256
244 257 def write_completion(self, possibilities, new_line=None):
245 258 """ Write the list of possible completions.
246 259
247 260 new_line is the completed input line that should be displayed
248 261 after the completion are writen. If None, the input_buffer
249 262 before the completion is used.
250 263 """
251 264 if new_line is None:
252 265 new_line = self.input_buffer
253 266
254 267 self.write('\n')
255 268 max_len = len(max(possibilities, key=len)) + 1
256 269
257 270 # Now we check how much symbol we can put on a line...
258 271 chars_per_line = self.get_line_width()
259 272 symbols_per_line = max(1, chars_per_line/max_len)
260 273
261 274 pos = 1
262 275 buf = []
263 276 for symbol in possibilities:
264 277 if pos < symbols_per_line:
265 278 buf.append(symbol.ljust(max_len))
266 279 pos += 1
267 280 else:
268 281 buf.append(symbol.rstrip() + '\n')
269 282 pos = 1
270 283 self.write(''.join(buf))
271 284 self.new_prompt(self.input_prompt_template.substitute(
272 285 number=self.last_result['number'] + 1))
273 286 self.input_buffer = new_line
274 287
275 288
276 289 def new_prompt(self, prompt):
277 290 """ Prints a prompt and starts a new editing buffer.
278 291
279 292 Subclasses should use this method to make sure that the
280 293 terminal is put in a state favorable for a new line
281 294 input.
282 295 """
283 296 self.input_buffer = ''
284 297 self.write(prompt)
285 298
286 299
287 300 #--------------------------------------------------------------------------
288 301 # Private API
289 302 #--------------------------------------------------------------------------
290 303
291 304 def _on_enter(self):
292 305 """ Called when the return key is pressed in a line editing
293 306 buffer.
294 307 """
295 308 current_buffer = self.input_buffer
296 309 cleaned_buffer = self.prefilter_input(current_buffer)
297 310 if self.is_complete(cleaned_buffer):
298 311 self.execute(cleaned_buffer, raw_string=current_buffer)
299 312 else:
300 313 self.input_buffer += self._get_indent_string(
301 314 current_buffer[:-1])
302 315 if len(current_buffer.split('\n')) == 2:
303 316 self.input_buffer += '\t\t'
304 317 if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'):
305 318 self.input_buffer += '\t'
306 319
307 320
308 321 def _get_indent_string(self, string):
309 322 """ Return the string of whitespace that prefixes a line. Used to
310 323 add the right amount of indendation when creating a new line.
311 324 """
312 325 string = string.replace('\t', ' '*4)
313 326 string = string.split('\n')[-1]
314 327 indent_chars = len(string) - len(string.lstrip())
315 328 indent_string = '\t'*(indent_chars // 4) + \
316 329 ' '*(indent_chars % 4)
317 330
318 331 return indent_string
319 332
320 333
@@ -1,230 +1,246 b''
1 1 """
2 2 Frontend class that uses IPython0 to prefilter the inputs.
3 3
4 4 Using the IPython0 mechanism gives us access to the magics.
5 5
6 6 This is a transitory class, used here to do the transition between
7 7 ipython0 and ipython1. This class is meant to be short-lived as more
8 8 functionnality is abstracted out of ipython0 in reusable functions and
9 9 is added on the interpreter. This class can be a used to guide this
10 10 refactoring.
11 11 """
12 12 __docformat__ = "restructuredtext en"
13 13
14 14 #-------------------------------------------------------------------------------
15 15 # Copyright (C) 2008 The IPython Development Team
16 16 #
17 17 # Distributed under the terms of the BSD License. The full license is in
18 18 # the file COPYING, distributed as part of this software.
19 19 #-------------------------------------------------------------------------------
20 20
21 21 #-------------------------------------------------------------------------------
22 22 # Imports
23 23 #-------------------------------------------------------------------------------
24 24 import sys
25 25
26 26 from linefrontendbase import LineFrontEndBase, common_prefix
27 27 from frontendbase import FrontEndBase
28 28
29 29 from IPython.ipmaker import make_IPython
30 30 from IPython.ipapi import IPApi
31 31 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
32 32
33 33 from IPython.kernel.core.sync_traceback_trap import SyncTracebackTrap
34 34
35 35 from IPython.genutils import Term
36 36 import pydoc
37 37 import os
38 38 import sys
39 39
40 40
41 41 def mk_system_call(system_call_function, command):
42 42 """ given a os.system replacement, and a leading string command,
43 43 returns a function that will execute the command with the given
44 44 argument string.
45 45 """
46 46 def my_system_call(args):
47 47 system_call_function("%s %s" % (command, args))
48 48 return my_system_call
49 49
50 50 #-------------------------------------------------------------------------------
51 51 # Frontend class using ipython0 to do the prefiltering.
52 52 #-------------------------------------------------------------------------------
53 53 class PrefilterFrontEnd(LineFrontEndBase):
54 54 """ Class that uses ipython0 to do prefilter the input, do the
55 55 completion and the magics.
56 56
57 57 The core trick is to use an ipython0 instance to prefilter the
58 58 input, and share the namespace between the interpreter instance used
59 59 to execute the statements and the ipython0 used for code
60 60 completion...
61 61 """
62 62
63 63 debug = False
64 64
65 65 def __init__(self, ipython0=None, *args, **kwargs):
66 66 """ Parameters:
67 67 -----------
68 68
69 69 ipython0: an optional ipython0 instance to use for command
70 70 prefiltering and completion.
71 71 """
72 72 LineFrontEndBase.__init__(self, *args, **kwargs)
73 73 self.shell.output_trap = RedirectorOutputTrap(
74 74 out_callback=self.write,
75 75 err_callback=self.write,
76 76 )
77 77 self.shell.traceback_trap = SyncTracebackTrap(
78 78 formatters=self.shell.traceback_trap.formatters,
79 79 )
80 80
81 81 # Start the ipython0 instance:
82 82 self.save_output_hooks()
83 83 if ipython0 is None:
84 84 # Instanciate an IPython0 interpreter to be able to use the
85 85 # prefiltering.
86 86 # XXX: argv=[] is a bit bold.
87 87 ipython0 = make_IPython(argv=[],
88 88 user_ns=self.shell.user_ns,
89 89 user_global_ns=self.shell.user_global_ns)
90 90 self.ipython0 = ipython0
91 91 # Set the pager:
92 92 self.ipython0.set_hook('show_in_pager',
93 93 lambda s, string: self.write("\n" + string))
94 94 self.ipython0.write = self.write
95 95 self._ip = _ip = IPApi(self.ipython0)
96 96 # Make sure the raw system call doesn't get called, as we don't
97 97 # have a stdin accessible.
98 98 self._ip.system = self.system_call
99 99 # XXX: Muck around with magics so that they work better
100 100 # in our environment
101 101 self.ipython0.magic_ls = mk_system_call(self.system_call,
102 102 'ls -CF')
103 103 # And now clean up the mess created by ipython0
104 104 self.release_output()
105 105
106 106
107 107 if not 'banner' in kwargs and self.banner is None:
108 108 self.banner = self.ipython0.BANNER + """
109 109 This is the wx frontend, by Gael Varoquaux. This is EXPERIMENTAL code."""
110 110
111 111 self.start()
112 112
113 113 #--------------------------------------------------------------------------
114 114 # FrontEndBase interface
115 115 #--------------------------------------------------------------------------
116 116
117 117 def show_traceback(self):
118 118 """ Use ipython0 to capture the last traceback and display it.
119 119 """
120 120 self.capture_output()
121 121 self.ipython0.showtraceback(tb_offset=-1)
122 122 self.release_output()
123 123
124 124
125 125 def execute(self, python_string, raw_string=None):
126 126 if self.debug:
127 127 print 'Executing Python code:', repr(python_string)
128 128 self.capture_output()
129 129 LineFrontEndBase.execute(self, python_string,
130 130 raw_string=raw_string)
131 131 self.release_output()
132 132
133 133
134 134 def save_output_hooks(self):
135 135 """ Store all the output hooks we can think of, to be able to
136 136 restore them.
137 137
138 138 We need to do this early, as starting the ipython0 instance will
139 139 screw ouput hooks.
140 140 """
141 141 self.__old_cout_write = Term.cout.write
142 142 self.__old_cerr_write = Term.cerr.write
143 143 self.__old_stdout = sys.stdout
144 144 self.__old_stderr= sys.stderr
145 145 self.__old_help_output = pydoc.help.output
146 146 self.__old_display_hook = sys.displayhook
147 147
148 148
149 149 def capture_output(self):
150 150 """ Capture all the output mechanisms we can think of.
151 151 """
152 152 self.save_output_hooks()
153 153 Term.cout.write = self.write
154 154 Term.cerr.write = self.write
155 155 sys.stdout = Term.cout
156 156 sys.stderr = Term.cerr
157 157 pydoc.help.output = self.shell.output_trap.out
158 158
159 159
160 160 def release_output(self):
161 161 """ Release all the different captures we have made.
162 162 """
163 163 Term.cout.write = self.__old_cout_write
164 164 Term.cerr.write = self.__old_cerr_write
165 165 sys.stdout = self.__old_stdout
166 166 sys.stderr = self.__old_stderr
167 167 pydoc.help.output = self.__old_help_output
168 168 sys.displayhook = self.__old_display_hook
169 169
170 170
171 171 def complete(self, line):
172 172 # FIXME: This should be factored out in the linefrontendbase
173 173 # method.
174 174 word = line.split('\n')[-1].split(' ')[-1]
175 175 completions = self.ipython0.complete(word)
176 176 # FIXME: The proper sort should be done in the complete method.
177 177 key = lambda x: x.replace('_', '')
178 178 completions.sort(key=key)
179 179 if completions:
180 180 prefix = common_prefix(completions)
181 181 line = line[:-len(word)] + prefix
182 182 return line, completions
183 183
184 184
185 185 #--------------------------------------------------------------------------
186 186 # LineFrontEndBase interface
187 187 #--------------------------------------------------------------------------
188 188
189 189 def prefilter_input(self, input_string):
190 190 """ Using IPython0 to prefilter the commands to turn them
191 191 in executable statements that are valid Python strings.
192 192 """
193 193 input_string = LineFrontEndBase.prefilter_input(self, input_string)
194 194 filtered_lines = []
195 195 # The IPython0 prefilters sometime produce output. We need to
196 196 # capture it.
197 197 self.capture_output()
198 198 self.last_result = dict(number=self.prompt_number)
199
200 ## try:
201 ## for line in input_string.split('\n'):
202 ## filtered_lines.append(
203 ## self.ipython0.prefilter(line, False).rstrip())
204 ## except:
205 ## # XXX: probably not the right thing to do.
206 ## self.ipython0.showsyntaxerror()
207 ## self.after_execute()
208 ## finally:
209 ## self.release_output()
210
211
199 212 try:
200 for line in input_string.split('\n'):
201 filtered_lines.append(
202 self.ipython0.prefilter(line, False).rstrip())
203 except:
204 # XXX: probably not the right thing to do.
205 self.ipython0.showsyntaxerror()
206 self.after_execute()
213 try:
214 for line in input_string.split('\n'):
215 filtered_lines.append(
216 self.ipython0.prefilter(line, False).rstrip())
217 except:
218 # XXX: probably not the right thing to do.
219 self.ipython0.showsyntaxerror()
220 self.after_execute()
207 221 finally:
208 222 self.release_output()
209 223
224
225
210 226 # Clean up the trailing whitespace, to avoid indentation errors
211 227 filtered_string = '\n'.join(filtered_lines)
212 228 return filtered_string
213 229
214 230
215 231 #--------------------------------------------------------------------------
216 232 # PrefilterFrontEnd interface
217 233 #--------------------------------------------------------------------------
218 234
219 235 def system_call(self, command_string):
220 236 """ Allows for frontend to define their own system call, to be
221 237 able capture output and redirect input.
222 238 """
223 239 return os.system(command_string)
224 240
225 241
226 242 def do_exit(self):
227 243 """ Exit the shell, cleanup and save the history.
228 244 """
229 245 self.ipython0.atexit_operations()
230 246
@@ -1,155 +1,32 b''
1 1 # encoding: utf-8
2
3 """This file contains unittests for the frontendbase module."""
2 """
3 Test the basic functionality of frontendbase.
4 """
4 5
5 6 __docformat__ = "restructuredtext en"
6 7
7 #---------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #---------------------------------------------------------------------------
13
14 #---------------------------------------------------------------------------
15 # Imports
16 #---------------------------------------------------------------------------
17
18 import unittest
19
20 try:
21 from IPython.frontend.asyncfrontendbase import AsyncFrontEndBase
22 from IPython.frontend import frontendbase
23 from IPython.kernel.engineservice import EngineService
24 except ImportError:
25 import nose
26 raise nose.SkipTest("This test requires zope.interface, Twisted and Foolscap")
27
28 from IPython.testing.decorators import skip
29
30 class FrontEndCallbackChecker(AsyncFrontEndBase):
31 """FrontEndBase subclass for checking callbacks"""
32 def __init__(self, engine=None, history=None):
33 super(FrontEndCallbackChecker, self).__init__(engine=engine,
34 history=history)
35 self.updateCalled = False
36 self.renderResultCalled = False
37 self.renderErrorCalled = False
38
39 def update_cell_prompt(self, result, blockID=None):
40 self.updateCalled = True
41 return result
42
43 def render_result(self, result):
44 self.renderResultCalled = True
45 return result
46
47
48 def render_error(self, failure):
49 self.renderErrorCalled = True
50 return failure
51
52
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2008 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is
12 # in the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 from IPython.frontend.frontendbase import FrontEndBase
16
17 def test_iscomplete():
18 """ Check that is_complete works.
19 """
20 f = FrontEndBase()
21 assert f.is_complete('(a + a)')
22 assert not f.is_complete('(a + a')
23 assert f.is_complete('1')
24 assert not f.is_complete('1 + ')
25 assert not f.is_complete('1 + \n\n')
26 assert f.is_complete('if True:\n print 1\n')
27 assert not f.is_complete('if True:\n print 1')
28 assert f.is_complete('def f():\n print 1\n')
29
30 if __name__ == '__main__':
31 test_iscomplete()
53 32
54
55 class TestAsyncFrontendBase(unittest.TestCase):
56 def setUp(self):
57 """Setup the EngineService and FrontEndBase"""
58
59 self.fb = FrontEndCallbackChecker(engine=EngineService())
60
61 def test_implements_IFrontEnd(self):
62 assert(frontendbase.IFrontEnd.implementedBy(
63 AsyncFrontEndBase))
64
65 def test_is_complete_returns_False_for_incomplete_block(self):
66 """"""
67
68 block = """def test(a):"""
69
70 assert(self.fb.is_complete(block) == False)
71
72 def test_is_complete_returns_True_for_complete_block(self):
73 """"""
74
75 block = """def test(a): pass"""
76
77 assert(self.fb.is_complete(block))
78
79 block = """a=3"""
80
81 assert(self.fb.is_complete(block))
82
83 def test_blockID_added_to_result(self):
84 block = """3+3"""
85
86 d = self.fb.execute(block, blockID='TEST_ID')
87
88 d.addCallback(self.checkBlockID, expected='TEST_ID')
89
90 def test_blockID_added_to_failure(self):
91 block = "raise Exception()"
92
93 d = self.fb.execute(block,blockID='TEST_ID')
94 d.addErrback(self.checkFailureID, expected='TEST_ID')
95
96 def checkBlockID(self, result, expected=""):
97 assert(result['blockID'] == expected)
98
99
100 def checkFailureID(self, failure, expected=""):
101 assert(failure.blockID == expected)
102
103
104 def test_callbacks_added_to_execute(self):
105 """test that
106 update_cell_prompt
107 render_result
108
109 are added to execute request
110 """
111
112 d = self.fb.execute("10+10")
113 d.addCallback(self.checkCallbacks)
114
115 def checkCallbacks(self, result):
116 assert(self.fb.updateCalled)
117 assert(self.fb.renderResultCalled)
118
119 @skip("This test fails and lead to an unhandled error in a Deferred.")
120 def test_error_callback_added_to_execute(self):
121 """test that render_error called on execution error"""
122
123 d = self.fb.execute("raise Exception()")
124 d.addCallback(self.checkRenderError)
125
126 def checkRenderError(self, result):
127 assert(self.fb.renderErrorCalled)
128
129 def test_history_returns_expected_block(self):
130 """Make sure history browsing doesn't fail"""
131
132 blocks = ["a=1","a=2","a=3"]
133 for b in blocks:
134 d = self.fb.execute(b)
135
136 # d is now the deferred for the last executed block
137 d.addCallback(self.historyTests, blocks)
138
139
140 def historyTests(self, result, blocks):
141 """historyTests"""
142
143 assert(len(blocks) >= 3)
144 assert(self.fb.get_history_previous("") == blocks[-2])
145 assert(self.fb.get_history_previous("") == blocks[-3])
146 assert(self.fb.get_history_next() == blocks[-2])
147
148
149 def test_history_returns_none_at_startup(self):
150 """test_history_returns_none_at_startup"""
151
152 assert(self.fb.get_history_previous("")==None)
153 assert(self.fb.get_history_next()==None)
154
155
@@ -1,34 +1,27 b''
1 1 # encoding: utf-8
2 2 # -*- test-case-name: IPython.frontend.tests.test_frontendbase -*-
3 3 """
4 4 zope.interface mock. If zope is installed, this module provides a zope
5 5 interface classes, if not it provides mocks for them.
6 6
7 7 Classes provided:
8 8 Interface, Attribute, implements, classProvides
9 9 """
10 10 __docformat__ = "restructuredtext en"
11 11
12 12 #-------------------------------------------------------------------------------
13 13 # Copyright (C) 2008 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-------------------------------------------------------------------------------
18 18
19 #-------------------------------------------------------------------------------
20 # Imports
21 #-------------------------------------------------------------------------------
22 import string
23 import uuid
24 import _ast
25
26 19 try:
27 20 from zope.interface import Interface, Attribute, implements, classProvides
28 21 except ImportError:
29 22 #zope.interface is not available
30 23 Interface = object
31 24 def Attribute(name, doc): pass
32 25 def implements(interface): pass
33 26 def classProvides(interface): pass
34 27
@@ -1,143 +1,141 b''
1 1 # encoding: utf-8
2 2 # -*- test-case-name: IPython.kernel.test.test_contexts -*-
3 3 """Context managers for IPython.
4 4
5 5 Python 2.5 introduced the `with` statement, which is based on the context
6 6 manager protocol. This module offers a few context managers for common cases,
7 7 which can also be useful as templates for writing new, application-specific
8 8 managers.
9 9 """
10 10
11 from __future__ import with_statement
12
13 11 __docformat__ = "restructuredtext en"
14 12
15 13 #-------------------------------------------------------------------------------
16 14 # Copyright (C) 2008 The IPython Development Team
17 15 #
18 16 # Distributed under the terms of the BSD License. The full license is in
19 17 # the file COPYING, distributed as part of this software.
20 18 #-------------------------------------------------------------------------------
21 19
22 20 #-------------------------------------------------------------------------------
23 21 # Imports
24 22 #-------------------------------------------------------------------------------
25 23
26 24 import linecache
27 25 import sys
28 26
29 27 from twisted.internet.error import ConnectionRefusedError
30 28
31 29 from IPython.ultraTB import _fixed_getinnerframes, findsource
32 30 from IPython import ipapi
33 31
34 32 from IPython.kernel import error
35 33
36 34 #---------------------------------------------------------------------------
37 35 # Utility functions needed by all context managers.
38 36 #---------------------------------------------------------------------------
39 37
40 38 def remote():
41 39 """Raises a special exception meant to be caught by context managers.
42 40 """
43 41 m = 'Special exception to stop local execution of parallel code.'
44 42 raise error.StopLocalExecution(m)
45 43
46 44
47 45 def strip_whitespace(source,require_remote=True):
48 46 """strip leading whitespace from input source.
49 47
50 48 :Parameters:
51 49
52 50 """
53 51 remote_mark = 'remote()'
54 52 # Expand tabs to avoid any confusion.
55 53 wsource = [l.expandtabs(4) for l in source]
56 54 # Detect the indentation level
57 55 done = False
58 56 for line in wsource:
59 57 if line.isspace():
60 58 continue
61 59 for col,char in enumerate(line):
62 60 if char != ' ':
63 61 done = True
64 62 break
65 63 if done:
66 64 break
67 65 # Now we know how much leading space there is in the code. Next, we
68 66 # extract up to the first line that has less indentation.
69 67 # WARNINGS: we skip comments that may be misindented, but we do NOT yet
70 68 # detect triple quoted strings that may have flush left text.
71 69 for lno,line in enumerate(wsource):
72 70 lead = line[:col]
73 71 if lead.isspace():
74 72 continue
75 73 else:
76 74 if not lead.lstrip().startswith('#'):
77 75 break
78 76 # The real 'with' source is up to lno
79 77 src_lines = [l[col:] for l in wsource[:lno+1]]
80 78
81 79 # Finally, check that the source's first non-comment line begins with the
82 80 # special call 'remote()'
83 81 if require_remote:
84 82 for nline,line in enumerate(src_lines):
85 83 if line.isspace() or line.startswith('#'):
86 84 continue
87 85 if line.startswith(remote_mark):
88 86 break
89 87 else:
90 88 raise ValueError('%s call missing at the start of code' %
91 89 remote_mark)
92 90 out_lines = src_lines[nline+1:]
93 91 else:
94 92 # If the user specified that the remote() call wasn't mandatory
95 93 out_lines = src_lines
96 94
97 95 # src = ''.join(out_lines) # dbg
98 96 #print 'SRC:\n<<<<<<<>>>>>>>\n%s<<<<<>>>>>>' % src # dbg
99 97 return ''.join(out_lines)
100 98
101 99 class RemoteContextBase(object):
102 100 def __init__(self):
103 101 self.ip = ipapi.get()
104 102
105 103 def _findsource_file(self,f):
106 104 linecache.checkcache()
107 105 s = findsource(f.f_code)
108 106 lnum = f.f_lineno
109 107 wsource = s[0][f.f_lineno:]
110 108 return strip_whitespace(wsource)
111 109
112 110 def _findsource_ipython(self,f):
113 111 from IPython import ipapi
114 112 self.ip = ipapi.get()
115 113 buf = self.ip.IP.input_hist_raw[-1].splitlines()[1:]
116 114 wsource = [l+'\n' for l in buf ]
117 115
118 116 return strip_whitespace(wsource)
119 117
120 118 def findsource(self,frame):
121 119 local_ns = frame.f_locals
122 120 global_ns = frame.f_globals
123 121 if frame.f_code.co_filename == '<ipython console>':
124 122 src = self._findsource_ipython(frame)
125 123 else:
126 124 src = self._findsource_file(frame)
127 125 return src
128 126
129 127 def __enter__(self):
130 128 raise NotImplementedError
131 129
132 130 def __exit__ (self, etype, value, tb):
133 131 if issubclass(etype,error.StopLocalExecution):
134 132 return True
135 133
136 134 class RemoteMultiEngine(RemoteContextBase):
137 135 def __init__(self,mec):
138 136 self.mec = mec
139 137 RemoteContextBase.__init__(self)
140 138
141 139 def __enter__(self):
142 140 src = self.findsource(sys._getframe(1))
143 141 return self.mec.execute(src)
@@ -1,68 +1,70 b''
1 1 # encoding: utf-8
2 2 """
3 3 Test the output capture at the OS level, using file descriptors.
4 4 """
5 5
6 6 __docformat__ = "restructuredtext en"
7 7
8 8 #-------------------------------------------------------------------------------
9 9 # Copyright (C) 2008 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is
12 12 # in the file COPYING, distributed as part of this software.
13 13 #-------------------------------------------------------------------------------
14 14
15 15
16 # Stdlib imports
16 17 import os
17 18 from cStringIO import StringIO
18 19
19 from IPython.testing import decorators as testdec
20 # Our own imports
21 from IPython.testing import decorators as dec
20 22
21 # FIXME
22 @testdec.skip("This doesn't work under Windows")
23 #-----------------------------------------------------------------------------
24 # Test functions
25
26 @dec.skip_win32
23 27 def test_redirector():
24 28 """ Checks that the redirector can be used to do synchronous capture.
25 29 """
26 30 from IPython.kernel.core.fd_redirector import FDRedirector
27 31 r = FDRedirector()
28 32 out = StringIO()
29 33 try:
30 34 r.start()
31 35 for i in range(10):
32 36 os.system('echo %ic' % i)
33 37 print >>out, r.getvalue(),
34 38 print >>out, i
35 39 except:
36 40 r.stop()
37 41 raise
38 42 r.stop()
39 43 result1 = out.getvalue()
40 44 result2 = "".join("%ic\n%i\n" %(i, i) for i in range(10))
41 45 assert result1 == result2
42 46
43 # FIXME
44 @testdec.skip("This doesn't work under Windows")
47
48 @dec.skip_win32
45 49 def test_redirector_output_trap():
46 50 """ This test check not only that the redirector_output_trap does
47 51 trap the output, but also that it does it in a gready way, that
48 52 is by calling the callback ASAP.
49 53 """
50 54 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
51 55 out = StringIO()
52 56 trap = RedirectorOutputTrap(out.write, out.write)
53 57 try:
54 58 trap.set()
55 59 for i in range(10):
56 60 os.system('echo %ic' % i)
57 61 print "%ip" % i
58 62 print >>out, i
59 63 except:
60 64 trap.unset()
61 65 raise
62 66 trap.unset()
63 67 result1 = out.getvalue()
64 68 result2 = "".join("%ic\n%ip\n%i\n" %(i, i, i) for i in range(10))
65 69 assert result1 == result2
66
67 70
68
@@ -1,41 +1,43 b''
1 from __future__ import with_statement
1 #from __future__ import with_statement
2
3 # XXX This file is currently disabled to preserve 2.4 compatibility.
2 4
3 5 #def test_simple():
4 6 if 0:
5 7
6 8 # XXX - for now, we need a running cluster to be started separately. The
7 9 # daemon work is almost finished, and will make much of this unnecessary.
8 10 from IPython.kernel import client
9 11 mec = client.MultiEngineClient(('127.0.0.1',10105))
10 12
11 13 try:
12 14 mec.get_ids()
13 15 except ConnectionRefusedError:
14 16 import os, time
15 17 os.system('ipcluster -n 2 &')
16 18 time.sleep(2)
17 19 mec = client.MultiEngineClient(('127.0.0.1',10105))
18 20
19 21 mec.block = False
20 22
21 23 import itertools
22 24 c = itertools.count()
23 25
24 26 parallel = RemoteMultiEngine(mec)
25 27
26 28 mec.pushAll()
27 29
28 with parallel as pr:
29 # A comment
30 remote() # this means the code below only runs remotely
31 print 'Hello remote world'
32 x = range(10)
33 # Comments are OK
34 # Even misindented.
35 y = x+1
30 ## with parallel as pr:
31 ## # A comment
32 ## remote() # this means the code below only runs remotely
33 ## print 'Hello remote world'
34 ## x = range(10)
35 ## # Comments are OK
36 ## # Even misindented.
37 ## y = x+1
36 38
37 39
38 with pfor('i',sequence) as pr:
39 print x[i]
40 ## with pfor('i',sequence) as pr:
41 ## print x[i]
40 42
41 43 print pr.x + pr.y
@@ -1,147 +1,160 b''
1 1 """Decorators for labeling test objects.
2 2
3 3 Decorators that merely return a modified version of the original
4 4 function object are straightforward. Decorators that return a new
5 5 function object need to use
6 6 nose.tools.make_decorator(original_function)(decorator) in returning
7 7 the decorator, in order to preserve metadata such as function name,
8 8 setup and teardown functions and so on - see nose.tools for more
9 9 information.
10 10
11 This module provides a set of useful decorators meant to be ready to use in
12 your own tests. See the bottom of the file for the ready-made ones, and if you
13 find yourself writing a new one that may be of generic use, add it here.
14
11 15 NOTE: This file contains IPython-specific decorators and imports the
12 16 numpy.testing.decorators file, which we've copied verbatim. Any of our own
13 17 code will be added at the bottom if we end up extending this.
14 18 """
15 19
16 20 # Stdlib imports
17 21 import inspect
22 import sys
18 23
19 24 # Third-party imports
20 25
21 26 # This is Michele Simionato's decorator module, also kept verbatim.
22 27 from decorator_msim import decorator, update_wrapper
23 28
24 29 # Grab the numpy-specific decorators which we keep in a file that we
25 30 # occasionally update from upstream: decorators_numpy.py is an IDENTICAL copy
26 31 # of numpy.testing.decorators.
27 32 from decorators_numpy import *
28 33
29 34 ##############################################################################
30 35 # Local code begins
31 36
32 37 # Utility functions
33 38
34 39 def apply_wrapper(wrapper,func):
35 40 """Apply a wrapper to a function for decoration.
36 41
37 42 This mixes Michele Simionato's decorator tool with nose's make_decorator,
38 43 to apply a wrapper in a decorator so that all nose attributes, as well as
39 44 function signature and other properties, survive the decoration cleanly.
40 45 This will ensure that wrapped functions can still be well introspected via
41 46 IPython, for example.
42 47 """
43 48 import nose.tools
44 49
45 50 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
46 51
47 52
48 53 def make_label_dec(label,ds=None):
49 54 """Factory function to create a decorator that applies one or more labels.
50 55
51 56 :Parameters:
52 57 label : string or sequence
53 58 One or more labels that will be applied by the decorator to the functions
54 59 it decorates. Labels are attributes of the decorated function with their
55 60 value set to True.
56 61
57 62 :Keywords:
58 63 ds : string
59 64 An optional docstring for the resulting decorator. If not given, a
60 65 default docstring is auto-generated.
61 66
62 67 :Returns:
63 68 A decorator.
64 69
65 70 :Examples:
66 71
67 72 A simple labeling decorator:
68 73 >>> slow = make_label_dec('slow')
69 74 >>> print slow.__doc__
70 75 Labels a test as 'slow'.
71 76
72 77 And one that uses multiple labels and a custom docstring:
73 78 >>> rare = make_label_dec(['slow','hard'],
74 79 ... "Mix labels 'slow' and 'hard' for rare tests.")
75 80 >>> print rare.__doc__
76 81 Mix labels 'slow' and 'hard' for rare tests.
77 82
78 83 Now, let's test using this one:
79 84 >>> @rare
80 85 ... def f(): pass
81 86 ...
82 87 >>>
83 88 >>> f.slow
84 89 True
85 90 >>> f.hard
86 91 True
87 92 """
88 93
89 94 if isinstance(label,basestring):
90 95 labels = [label]
91 96 else:
92 97 labels = label
93 98
94 99 # Validate that the given label(s) are OK for use in setattr() by doing a
95 100 # dry run on a dummy function.
96 101 tmp = lambda : None
97 102 for label in labels:
98 103 setattr(tmp,label,True)
99 104
100 105 # This is the actual decorator we'll return
101 106 def decor(f):
102 107 for label in labels:
103 108 setattr(f,label,True)
104 109 return f
105 110
106 111 # Apply the user's docstring, or autogenerate a basic one
107 112 if ds is None:
108 113 ds = "Labels a test as %r." % label
109 114 decor.__doc__ = ds
110 115
111 116 return decor
112 117
113 118 #-----------------------------------------------------------------------------
114 119 # Decorators for public use
115 120
116 121 skip_doctest = make_label_dec('skip_doctest',
117 122 """Decorator - mark a function or method for skipping its doctest.
118 123
119 124 This decorator allows you to mark a function whose docstring you wish to
120 125 omit from testing, while preserving the docstring for introspection, help,
121 126 etc.""")
122 127
123 128 def skip(msg=''):
124 129 """Decorator - mark a test function for skipping from test suite.
125 130
131 This function *is* already a decorator, it is not a factory like
132 make_label_dec or some of those in decorators_numpy.
133
126 134 :Parameters:
127 135
128 136 func : function
129 137 Test function to be skipped
130 138
131 139 msg : string
132 140 Optional message to be added.
133 141 """
134 142
135 143 import nose
136 144
137 145 def inner(func):
138 146
139 147 def wrapper(*a,**k):
140 148 if msg: out = '\n'+msg
141 149 else: out = ''
142 150 raise nose.SkipTest("Skipping test for function: %s%s" %
143 151 (func.__name__,out))
144 152
145 153 return apply_wrapper(wrapper,func)
146 154
147 155 return inner
156
157 # Decorators to skip certain tests on specific platforms.
158 skip_win32 = skipif(sys.platform=='win32',"This test does not run under Windows")
159 skip_linux = skipif(sys.platform=='linux2',"This test does not run under Linux")
160 skip_osx = skipif(sys.platform=='darwin',"This test does not run under OSX")
@@ -1,185 +1,51 b''
1 """Some simple tests for the plugin while running scripts.
2 """
1 3 # Module imports
2 4 # Std lib
3 5 import inspect
4 6
5 # Third party
6
7 7 # Our own
8 8 from IPython.testing import decorators as dec
9 9
10 10 #-----------------------------------------------------------------------------
11 # Utilities
12
13 # Note: copied from OInspect, kept here so the testing stuff doesn't create
14 # circular dependencies and is easier to reuse.
15 def getargspec(obj):
16 """Get the names and default values of a function's arguments.
17
18 A tuple of four things is returned: (args, varargs, varkw, defaults).
19 'args' is a list of the argument names (it may contain nested lists).
20 'varargs' and 'varkw' are the names of the * and ** arguments or None.
21 'defaults' is an n-tuple of the default values of the last n arguments.
22
23 Modified version of inspect.getargspec from the Python Standard
24 Library."""
25
26 if inspect.isfunction(obj):
27 func_obj = obj
28 elif inspect.ismethod(obj):
29 func_obj = obj.im_func
30 else:
31 raise TypeError, 'arg is not a Python function'
32 args, varargs, varkw = inspect.getargs(func_obj.func_code)
33 return args, varargs, varkw, func_obj.func_defaults
34
35 #-----------------------------------------------------------------------------
36 11 # Testing functions
37 12
38 13 def test_trivial():
39 14 """A trivial passing test."""
40 15 pass
41 16
42
43 @dec.skip
44 def test_deliberately_broken():
45 """A deliberately broken test - we want to skip this one."""
46 1/0
47
48 @dec.skip('foo')
49 def test_deliberately_broken2():
50 """Another deliberately broken test - we want to skip this one."""
51 1/0
52
53
54 # Verify that we can correctly skip the doctest for a function at will, but
55 # that the docstring itself is NOT destroyed by the decorator.
56 @dec.skip_doctest
57 def doctest_bad(x,y=1,**k):
58 """A function whose doctest we need to skip.
59
60 >>> 1+1
61 3
62 """
63 print 'x:',x
64 print 'y:',y
65 print 'k:',k
66
67
68 def call_doctest_bad():
69 """Check that we can still call the decorated functions.
70
71 >>> doctest_bad(3,y=4)
72 x: 3
73 y: 4
74 k: {}
75 """
76 pass
77
78
79 # Doctest skipping should work for class methods too
80 class foo(object):
81 """Foo
82
83 Example:
84
85 >>> 1+1
86 2
87 """
88
89 @dec.skip_doctest
90 def __init__(self,x):
91 """Make a foo.
92
93 Example:
94
95 >>> f = foo(3)
96 junk
97 """
98 print 'Making a foo.'
99 self.x = x
100
101 @dec.skip_doctest
102 def bar(self,y):
103 """Example:
104
105 >>> f = foo(3)
106 >>> f.bar(0)
107 boom!
108 >>> 1/0
109 bam!
110 """
111 return 1/y
112
113 def baz(self,y):
114 """Example:
115
116 >>> f = foo(3)
117 Making a foo.
118 >>> f.baz(3)
119 True
120 """
121 return self.x==y
122
123
124 def test_skip_dt_decorator():
125 """Doctest-skipping decorator should preserve the docstring.
126 """
127 # Careful: 'check' must be a *verbatim* copy of the doctest_bad docstring!
128 check = """A function whose doctest we need to skip.
129
130 >>> 1+1
131 3
132 """
133 # Fetch the docstring from doctest_bad after decoration.
134 val = doctest_bad.__doc__
135
136 assert check==val,"doctest_bad docstrings don't match"
137
138
139 def test_skip_dt_decorator2():
140 """Doctest-skipping decorator should preserve function signature.
141 """
142 # Hardcoded correct answer
143 dtargs = (['x', 'y'], None, 'k', (1,))
144 # Introspect out the value
145 dtargsr = getargspec(doctest_bad)
146 assert dtargsr==dtargs, \
147 "Incorrectly reconstructed args for doctest_bad: %s" % (dtargsr,)
148
149
150 17 def doctest_run():
151 18 """Test running a trivial script.
152 19
153 20 In [13]: run simplevars.py
154 21 x is: 1
155 22 """
156 23
157 #@dec.skip_doctest
158 24 def doctest_runvars():
159 25 """Test that variables defined in scripts get loaded correcly via %run.
160 26
161 27 In [13]: run simplevars.py
162 28 x is: 1
163 29
164 30 In [14]: x
165 31 Out[14]: 1
166 32 """
167 33
168 34 def doctest_ivars():
169 35 """Test that variables defined interactively are picked up.
170 36 In [5]: zz=1
171 37
172 38 In [6]: zz
173 39 Out[6]: 1
174 40 """
175 41
176 42 @dec.skip_doctest
177 43 def doctest_refs():
178 44 """DocTest reference holding issues when running scripts.
179 45
180 46 In [32]: run show_refs.py
181 47 c referrers: [<type 'dict'>]
182 48
183 49 In [33]: map(type,gc.get_referrers(c))
184 50 Out[33]: [<type 'dict'>]
185 51 """
@@ -1,11 +1,11 b''
1 ===============
2 IPython1 README
3 ===============
4
5 .. contents::
1 ==============
2 IPython README
3 ==============
6 4
7 5 Overview
8 6 ========
9 7
10 Welcome to IPython. New users should consult our documentation, which can be found
11 in the docs/source subdirectory.
8 Welcome to IPython. Our documentation can be found in the docs/source
9 subdirectory. We also have ``.html`` and ``.pdf`` versions of this
10 documentation available on the IPython `website <http://ipython.scipy.org>`_.
11
@@ -1,120 +1,123 b''
1 1 """Example showing how to merge multiple remote data streams.
2 2 """
3 3 # Slightly modified version of:
4 4 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/511509
5 5
6 6 import heapq
7 7 from IPython.kernel.error import CompositeError
8 8
9 9 def mergesort(list_of_lists, key=None):
10 10 """ Perform an N-way merge operation on sorted lists.
11 11
12 12 @param list_of_lists: (really iterable of iterable) of sorted elements
13 13 (either by naturally or by C{key})
14 14 @param key: specify sort key function (like C{sort()}, C{sorted()})
15 15
16 16 Yields tuples of the form C{(item, iterator)}, where the iterator is the
17 17 built-in list iterator or something you pass in, if you pre-generate the
18 18 iterators.
19 19
20 20 This is a stable merge; complexity O(N lg N)
21 21
22 22 Examples::
23 23
24 24 >>> print list(mergesort([[1,2,3,4],
25 25 ... [2,3.25,3.75,4.5,6,7],
26 26 ... [2.625,3.625,6.625,9]]))
27 27 [1, 2, 2, 2.625, 3, 3.25, 3.625, 3.75, 4, 4.5, 6, 6.625, 7, 9]
28 28
29 29 # note stability
30 30 >>> print list(mergesort([[1,2,3,4],
31 31 ... [2,3.25,3.75,4.5,6,7],
32 32 ... [2.625,3.625,6.625,9]],
33 33 ... key=int))
34 34 [1, 2, 2, 2.625, 3, 3.25, 3.75, 3.625, 4, 4.5, 6, 6.625, 7, 9]
35 35
36 36
37 37 >>> print list(mergesort([[4, 3, 2, 1],
38 38 ... [7, 6, 4.5, 3.75, 3.25, 2],
39 39 ... [9, 6.625, 3.625, 2.625]],
40 40 ... key=lambda x: -x))
41 41 [9, 7, 6.625, 6, 4.5, 4, 3.75, 3.625, 3.25, 3, 2.625, 2, 2, 1]
42 42 """
43 43
44 44 heap = []
45 45 for i, itr in enumerate(iter(pl) for pl in list_of_lists):
46 46 try:
47 47 item = itr.next()
48 toadd = (key(item), i, item, itr) if key else (item, i, itr)
48 if key:
49 toadd = (key(item), i, item, itr)
50 else:
51 toadd = (item, i, itr)
49 52 heap.append(toadd)
50 53 except StopIteration:
51 54 pass
52 55 heapq.heapify(heap)
53 56
54 57 if key:
55 58 while heap:
56 59 _, idx, item, itr = heap[0]
57 60 yield item
58 61 try:
59 62 item = itr.next()
60 63 heapq.heapreplace(heap, (key(item), idx, item, itr) )
61 64 except StopIteration:
62 65 heapq.heappop(heap)
63 66
64 67 else:
65 68 while heap:
66 69 item, idx, itr = heap[0]
67 70 yield item
68 71 try:
69 72 heapq.heapreplace(heap, (itr.next(), idx, itr))
70 73 except StopIteration:
71 74 heapq.heappop(heap)
72 75
73 76
74 77 def remote_iterator(rc,engine,name):
75 78 """Return an iterator on an object living on a remote engine.
76 79 """
77 80 # Check that the object exists on the engine and pin a reference to it
78 81 iter_name = '_%s_rmt_iter_' % name
79 82 rc.execute('%s = iter(%s)' % (iter_name,name), targets=engine)
80 83 tpl = '_tmp = %s.next()' % iter_name
81 84 while True:
82 85 try:
83 86 rc.execute(tpl, targets=engine)
84 87 result = rc.pull('_tmp', targets=engine)[0]
85 88 # This causes the StopIteration exception to be raised.
86 89 except CompositeError, e:
87 90 e.raise_exception()
88 91 else:
89 92 yield result
90 93
91 94 # Main, interactive testing
92 95 if __name__ == '__main__':
93 96
94 97 from IPython.kernel import client
95 98 ipc = client.MultiEngineClient()
96 99 print 'Engine IDs:',ipc.get_ids()
97 100
98 101 # Make a set of 'sorted datasets'
99 102 a0 = range(5,20)
100 103 a1 = range(10)
101 104 a2 = range(15,25)
102 105
103 106 # Now, imagine these had been created in the remote engines by some long
104 107 # computation. In this simple example, we just send them over into the
105 108 # remote engines. They will all be called 'a' in each engine.
106 109 ipc.push(dict(a=a0), targets=0)
107 110 ipc.push(dict(a=a1), targets=1)
108 111 ipc.push(dict(a=a2), targets=2)
109 112
110 113 # And we now make a local object which represents the remote iterator
111 114 aa0 = remote_iterator(ipc,0,'a')
112 115 aa1 = remote_iterator(ipc,1,'a')
113 116 aa2 = remote_iterator(ipc,2,'a')
114 117
115 118 # Let's merge them, both locally and remotely:
116 119 print 'Merge the local datasets:'
117 120 print list(mergesort([a0,a1,a2]))
118 121
119 122 print 'Locally merge the remote sets:'
120 123 print list(mergesort([aa0,aa1,aa2]))
@@ -1,233 +1,233 b''
1 1 .. _overview:
2 2
3 3 ============
4 4 Introduction
5 5 ============
6 6
7 7 Overview
8 8 ========
9 9
10 10 One of Python's most useful features is its interactive interpreter.
11 11 This system allows very fast testing of ideas without the overhead of
12 12 creating test files as is typical in most programming languages.
13 13 However, the interpreter supplied with the standard Python distribution
14 14 is somewhat limited for extended interactive use.
15 15
16 16 The goal of IPython is to create a comprehensive environment for
17 interactive and exploratory computing. To support, this goal, IPython
17 interactive and exploratory computing. To support this goal, IPython
18 18 has two main components:
19 19
20 20 * An enhanced interactive Python shell.
21 21 * An architecture for interactive parallel computing.
22 22
23 23 All of IPython is open source (released under the revised BSD license).
24 24
25 25 Enhanced interactive Python shell
26 26 =================================
27 27
28 28 IPython's interactive shell (:command:`ipython`), has the following goals,
29 29 amongst others:
30 30
31 31 1. Provide an interactive shell superior to Python's default. IPython
32 32 has many features for object introspection, system shell access,
33 33 and its own special command system for adding functionality when
34 34 working interactively. It tries to be a very efficient environment
35 35 both for Python code development and for exploration of problems
36 36 using Python objects (in situations like data analysis).
37 37
38 38 2. Serve as an embeddable, ready to use interpreter for your own
39 39 programs. IPython can be started with a single call from inside
40 40 another program, providing access to the current namespace. This
41 41 can be very useful both for debugging purposes and for situations
42 42 where a blend of batch-processing and interactive exploration are
43 43 needed. New in the 0.9 version of IPython is a reusable wxPython
44 44 based IPython widget.
45 45
46 46 3. Offer a flexible framework which can be used as the base
47 47 environment for other systems with Python as the underlying
48 48 language. Specifically scientific environments like Mathematica,
49 49 IDL and Matlab inspired its design, but similar ideas can be
50 50 useful in many fields.
51 51
52 52 4. Allow interactive testing of threaded graphical toolkits. IPython
53 53 has support for interactive, non-blocking control of GTK, Qt and
54 54 WX applications via special threading flags. The normal Python
55 55 shell can only do this for Tkinter applications.
56 56
57 57 Main features of the interactive shell
58 58 --------------------------------------
59 59
60 60 * Dynamic object introspection. One can access docstrings, function
61 61 definition prototypes, source code, source files and other details
62 62 of any object accessible to the interpreter with a single
63 63 keystroke (:samp:`?`, and using :samp:`??` provides additional detail).
64 64
65 65 * Searching through modules and namespaces with :samp:`*` wildcards, both
66 66 when using the :samp:`?` system and via the :samp:`%psearch` command.
67 67
68 68 * Completion in the local namespace, by typing :kbd:`TAB` at the prompt.
69 69 This works for keywords, modules, methods, variables and files in the
70 70 current directory. This is supported via the readline library, and
71 71 full access to configuring readline's behavior is provided.
72 72 Custom completers can be implemented easily for different purposes
73 73 (system commands, magic arguments etc.)
74 74
75 75 * Numbered input/output prompts with command history (persistent
76 76 across sessions and tied to each profile), full searching in this
77 77 history and caching of all input and output.
78 78
79 79 * User-extensible 'magic' commands. A set of commands prefixed with
80 80 :samp:`%` is available for controlling IPython itself and provides
81 81 directory control, namespace information and many aliases to
82 82 common system shell commands.
83 83
84 84 * Alias facility for defining your own system aliases.
85 85
86 86 * Complete system shell access. Lines starting with :samp:`!` are passed
87 87 directly to the system shell, and using :samp:`!!` or :samp:`var = !cmd`
88 88 captures shell output into python variables for further use.
89 89
90 90 * Background execution of Python commands in a separate thread.
91 91 IPython has an internal job manager called jobs, and a
92 92 convenience backgrounding magic function called :samp:`%bg`.
93 93
94 94 * The ability to expand python variables when calling the system
95 95 shell. In a shell command, any python variable prefixed with :samp:`$` is
96 96 expanded. A double :samp:`$$` allows passing a literal :samp:`$` to the shell (for
97 97 access to shell and environment variables like :envvar:`PATH`).
98 98
99 99 * Filesystem navigation, via a magic :samp:`%cd` command, along with a
100 100 persistent bookmark system (using :samp:`%bookmark`) for fast access to
101 101 frequently visited directories.
102 102
103 103 * A lightweight persistence framework via the :samp:`%store` command, which
104 104 allows you to save arbitrary Python variables. These get restored
105 105 automatically when your session restarts.
106 106
107 107 * Automatic indentation (optional) of code as you type (through the
108 108 readline library).
109 109
110 110 * Macro system for quickly re-executing multiple lines of previous
111 111 input with a single name. Macros can be stored persistently via
112 112 :samp:`%store` and edited via :samp:`%edit`.
113 113
114 114 * Session logging (you can then later use these logs as code in your
115 115 programs). Logs can optionally timestamp all input, and also store
116 116 session output (marked as comments, so the log remains valid
117 117 Python source code).
118 118
119 119 * Session restoring: logs can be replayed to restore a previous
120 120 session to the state where you left it.
121 121
122 122 * Verbose and colored exception traceback printouts. Easier to parse
123 123 visually, and in verbose mode they produce a lot of useful
124 124 debugging information (basically a terminal version of the cgitb
125 125 module).
126 126
127 127 * Auto-parentheses: callable objects can be executed without
128 128 parentheses: :samp:`sin 3` is automatically converted to :samp:`sin(3)`.
129 129
130 130 * Auto-quoting: using :samp:`,`, or :samp:`;` as the first character forces
131 131 auto-quoting of the rest of the line: :samp:`,my_function a b` becomes
132 132 automatically :samp:`my_function("a","b")`, while :samp:`;my_function a b`
133 133 becomes :samp:`my_function("a b")`.
134 134
135 135 * Extensible input syntax. You can define filters that pre-process
136 136 user input to simplify input in special situations. This allows
137 137 for example pasting multi-line code fragments which start with
138 138 :samp:`>>>` or :samp:`...` such as those from other python sessions or the
139 139 standard Python documentation.
140 140
141 141 * Flexible configuration system. It uses a configuration file which
142 142 allows permanent setting of all command-line options, module
143 143 loading, code and file execution. The system allows recursive file
144 144 inclusion, so you can have a base file with defaults and layers
145 145 which load other customizations for particular projects.
146 146
147 147 * Embeddable. You can call IPython as a python shell inside your own
148 148 python programs. This can be used both for debugging code or for
149 149 providing interactive abilities to your programs with knowledge
150 150 about the local namespaces (very useful in debugging and data
151 151 analysis situations).
152 152
153 153 * Easy debugger access. You can set IPython to call up an enhanced
154 154 version of the Python debugger (pdb) every time there is an
155 155 uncaught exception. This drops you inside the code which triggered
156 156 the exception with all the data live and it is possible to
157 157 navigate the stack to rapidly isolate the source of a bug. The
158 158 :samp:`%run` magic command (with the :samp:`-d` option) can run any script under
159 159 pdb's control, automatically setting initial breakpoints for you.
160 160 This version of pdb has IPython-specific improvements, including
161 161 tab-completion and traceback coloring support. For even easier
162 162 debugger access, try :samp:`%debug` after seeing an exception. winpdb is
163 163 also supported, see ipy_winpdb extension.
164 164
165 165 * Profiler support. You can run single statements (similar to
166 166 :samp:`profile.run()`) or complete programs under the profiler's control.
167 167 While this is possible with standard cProfile or profile modules,
168 168 IPython wraps this functionality with magic commands (see :samp:`%prun`
169 169 and :samp:`%run -p`) convenient for rapid interactive work.
170 170
171 171 * Doctest support. The special :samp:`%doctest_mode` command toggles a mode
172 172 that allows you to paste existing doctests (with leading :samp:`>>>`
173 173 prompts and whitespace) and uses doctest-compatible prompts and
174 174 output, so you can use IPython sessions as doctest code.
175 175
176 176 Interactive parallel computing
177 177 ==============================
178 178
179 179 Increasingly, parallel computer hardware, such as multicore CPUs, clusters and supercomputers, is becoming ubiquitous. Over the last 3 years, we have developed an
180 180 architecture within IPython that allows such hardware to be used quickly and easily
181 181 from Python. Moreover, this architecture is designed to support interactive and
182 182 collaborative parallel computing.
183 183
184 184 The main features of this system are:
185 185
186 186 * Quickly parallelize Python code from an interactive Python/IPython session.
187 187
188 188 * A flexible and dynamic process model that be deployed on anything from
189 189 multicore workstations to supercomputers.
190 190
191 191 * An architecture that supports many different styles of parallelism, from
192 192 message passing to task farming. And all of these styles can be handled
193 193 interactively.
194 194
195 195 * Both blocking and fully asynchronous interfaces.
196 196
197 197 * High level APIs that enable many things to be parallelized in a few lines
198 198 of code.
199 199
200 200 * Write parallel code that will run unchanged on everything from multicore
201 201 workstations to supercomputers.
202 202
203 203 * Full integration with Message Passing libraries (MPI).
204 204
205 205 * Capabilities based security model with full encryption of network connections.
206 206
207 207 * Share live parallel jobs with other users securely. We call this collaborative
208 208 parallel computing.
209 209
210 210 * Dynamically load balanced task farming system.
211 211
212 212 * Robust error handling. Python exceptions raised in parallel execution are
213 213 gathered and presented to the top-level code.
214 214
215 215 For more information, see our :ref:`overview <parallel_index>` of using IPython for
216 216 parallel computing.
217 217
218 218 Portability and Python requirements
219 219 -----------------------------------
220 220
221 221 As of the 0.9 release, IPython requires Python 2.4 or greater. We have
222 222 not begun to test IPython on Python 2.6 or 3.0, but we expect it will
223 223 work with some minor changes.
224 224
225 225 IPython is known to work on the following operating systems:
226 226
227 227 * Linux
228 228 * AIX
229 229 * Most other Unix-like OSs (Solaris, BSD, etc.)
230 230 * Mac OS X
231 231 * Windows (CygWin, XP, Vista, etc.)
232 232
233 233 See :ref:`here <install_index>` for instructions on how to install IPython. No newline at end of file
1 NO CONTENT: file renamed from IPython/config/config.py to sandbox/config.py
1 NO CONTENT: file renamed from IPython/config/tests/sample_config.py to sandbox/sample_config.py
1 NO CONTENT: file renamed from IPython/config/tests/test_config.py to sandbox/test_config.py
1 NO CONTENT: file renamed from IPython/config/traitlets.py to sandbox/traitlets.py
@@ -1,31 +1,31 b''
1 1 #!/bin/sh
2 2
3 3 # release test
4 4
5 5 ipdir=$PWD/..
6 6
7 7 cd $ipdir
8 8
9 9 # Clean up build/dist directories
10 10 rm -rf $ipdir/build/*
11 11 rm -rf $ipdir/dist/*
12 12
13 13 # build source distros
14 14 cd $ipdir
15 15 ./setup.py sdist --formats=gztar
16 16
17 17 # Build rpms
18 #python2.4 ./setup.py bdist_rpm --binary-only --release=py24 --python=/usr/bin/python2.4
19 #python2.5 ./setup.py bdist_rpm --binary-only --release=py25 --python=/usr/bin/python2.5
18 python2.4 ./setup.py bdist_rpm --binary-only --release=py24 --python=/usr/bin/python2.4
19 python2.5 ./setup.py bdist_rpm --binary-only --release=py25 --python=/usr/bin/python2.5
20 20
21 21 # Build eggs
22 22 python2.4 ./setup_bdist_egg.py
23 23 python2.5 ./setup_bdist_egg.py
24 24
25 25 # Call the windows build separately, so that the extra Windows scripts don't
26 26 # get pulled into Unix builds (setup.py has code which checks for
27 27 # bdist_wininst)
28 28 ./setup.py bdist_wininst --install-script=ipython_win_post_install.py
29 29
30 30 # Change name so retarded Vista runs the installer correctly
31 31 rename 's/win32/win32-setup/' $ipdir/dist/*.exe
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now