##// END OF EJS Templates
Merging in Brian's branch (also pulls Gael's work)....
Fernando Perez -
r1720:a2890974 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,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 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,431 +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
25 try:
26 import _ast
27 except ImportError:
28 # Python 2.4 hackish workaround.
29 class bunch: pass
30 _ast = bunch()
31 _ast.PyCF_ONLY_AST = 1024
32
33
34
35 try:
36 import uuid
37 except ImportError:
38 # Python 2.4 hackish workaround.
39 class UUID:
40 def __init__(self,bytes):
41 version = 4
42 int = long(('%02x'*16) % tuple(map(ord, bytes)), 16)
43 # Set the variant to RFC 4122.
44 int &= ~(0xc000 << 48L)
45 int |= 0x8000 << 48L
46 # Set the version number.
47 int &= ~(0xf000 << 64L)
48 int |= version << 76L
49 self.__dict__['int'] = int
50
51 def __cmp__(self, other):
52 if isinstance(other, UUID):
53 return cmp(self.int, other.int)
54 return NotImplemented
55
56 def __hash__(self):
57 return hash(self.int)
58
59 def __int__(self):
60 return self.int
61
62 def __repr__(self):
63 return 'UUID(%r)' % str(self)
64
65 def __setattr__(self, name, value):
66 raise TypeError('UUID objects are immutable')
67
68 def __str__(self):
69 hex = '%032x' % self.int
70 return '%s-%s-%s-%s-%s' % (
71 hex[:8], hex[8:12], hex[12:16], hex[16:20], hex[20:])
72
73 def get_bytes(self):
74 bytes = ''
75 for shift in range(0, 128, 8):
76 bytes = chr((self.int >> shift) & 0xff) + bytes
77 return bytes
78
79 bytes = property(get_bytes)
80
81
82 def _u4():
83 "Fake random uuid"
84
85 import random
86 bytes = [chr(random.randrange(256)) for i in range(16)]
87 return UUID(bytes)
88
89 class bunch: pass
90 uuid = bunch()
91 uuid.uuid4 = _u4
92 del _u4
93
24 import codeop
25 from IPython.external import guid
94 26
95 27
96 28 from IPython.frontend.zopeinterface import (
97 29 Interface,
98 30 Attribute,
99 implements,
100 classProvides
101 31 )
102 32 from IPython.kernel.core.history import FrontEndHistory
103 33 from IPython.kernel.core.util import Bunch
104 34
105 35 ##############################################################################
106 36 # TEMPORARY!!! fake configuration, while we decide whether to use tconfig or
107 37 # not
108 38
109 39 rc = Bunch()
110 40 rc.prompt_in1 = r'In [$number]: '
111 41 rc.prompt_in2 = r'...'
112 42 rc.prompt_out = r'Out [$number]: '
113 43
114 44 ##############################################################################
115 45 # Interface definitions
116 46 ##############################################################################
117 47
118 48 class IFrontEndFactory(Interface):
119 49 """Factory interface for frontends."""
120 50
121 51 def __call__(engine=None, history=None):
122 52 """
123 53 Parameters:
124 54 interpreter : IPython.kernel.engineservice.IEngineCore
125 55 """
126 56
127 57 pass
128 58
129 59
130 60 class IFrontEnd(Interface):
131 61 """Interface for frontends. All methods return t.i.d.Deferred"""
132 62
133 63 Attribute("input_prompt_template", "string.Template instance\
134 64 substituteable with execute result.")
135 65 Attribute("output_prompt_template", "string.Template instance\
136 66 substituteable with execute result.")
137 67 Attribute("continuation_prompt_template", "string.Template instance\
138 68 substituteable with execute result.")
139 69
140 70 def update_cell_prompt(result, blockID=None):
141 71 """Subclass may override to update the input prompt for a block.
142 72
143 73 In asynchronous frontends, this method will be called as a
144 74 twisted.internet.defer.Deferred's callback/errback.
145 75 Implementations should thus return result when finished.
146 76
147 77 Result is a result dict in case of success, and a
148 78 twisted.python.util.failure.Failure in case of an error
149 79 """
150 80
151 81 pass
152 82
153 83 def render_result(result):
154 84 """Render the result of an execute call. Implementors may choose the
155 85 method of rendering.
156 86 For example, a notebook-style frontend might render a Chaco plot
157 87 inline.
158 88
159 89 Parameters:
160 90 result : dict (result of IEngineBase.execute )
161 91 blockID = result['blockID']
162 92
163 93 Result:
164 94 Output of frontend rendering
165 95 """
166 96
167 97 pass
168 98
169 99 def render_error(failure):
170 100 """Subclasses must override to render the failure.
171 101
172 102 In asynchronous frontend, since this method will be called as a
173 103 twisted.internet.defer.Deferred's callback. Implementations
174 104 should thus return result when finished.
175 105
176 106 blockID = failure.blockID
177 107 """
178 108
179 109 pass
180 110
181 111 def input_prompt(number=''):
182 112 """Returns the input prompt by subsituting into
183 113 self.input_prompt_template
184 114 """
185 115 pass
186 116
187 117 def output_prompt(number=''):
188 118 """Returns the output prompt by subsituting into
189 119 self.output_prompt_template
190 120 """
191 121
192 122 pass
193 123
194 124 def continuation_prompt():
195 125 """Returns the continuation prompt by subsituting into
196 126 self.continuation_prompt_template
197 127 """
198 128
199 129 pass
200 130
201 131 def is_complete(block):
202 132 """Returns True if block is complete, False otherwise."""
203 133
204 134 pass
205 135
206 def compile_ast(block):
207 """Compiles block to an _ast.AST"""
208
209 pass
210
136
211 137 def get_history_previous(current_block):
212 138 """Returns the block previous in the history. Saves currentBlock if
213 139 the history_cursor is currently at the end of the input history"""
214 140 pass
215 141
216 142 def get_history_next():
217 143 """Returns the next block in the history."""
218 144
219 145 pass
220 146
221 147 def complete(self, line):
222 148 """Returns the list of possible completions, and the completed
223 149 line.
224 150
225 151 The input argument is the full line to be completed. This method
226 152 returns both the line completed as much as possible, and the list
227 153 of further possible completions (full words).
228 154 """
229 155 pass
230 156
231 157
232 158 ##############################################################################
233 159 # Base class for all the frontends.
234 160 ##############################################################################
235 161
236 162 class FrontEndBase(object):
237 163 """
238 164 FrontEndBase manages the state tasks for a CLI frontend:
239 165 - Input and output history management
240 166 - Input/continuation and output prompt generation
241 167
242 168 Some issues (due to possibly unavailable engine):
243 169 - How do we get the current cell number for the engine?
244 170 - How do we handle completions?
245 171 """
246 172
247 173 history_cursor = 0
248 174
249 175 input_prompt_template = string.Template(rc.prompt_in1)
250 176 output_prompt_template = string.Template(rc.prompt_out)
251 177 continuation_prompt_template = string.Template(rc.prompt_in2)
252 178
253 179 def __init__(self, shell=None, history=None):
254 180 self.shell = shell
255 181 if history is None:
256 182 self.history = FrontEndHistory(input_cache=[''])
257 183 else:
258 184 self.history = history
259 185
260 186
261 187 def input_prompt(self, number=''):
262 188 """Returns the current input prompt
263 189
264 190 It would be great to use ipython1.core.prompts.Prompt1 here
265 191 """
266 192 return self.input_prompt_template.safe_substitute({'number':number})
267 193
268 194
269 195 def continuation_prompt(self):
270 196 """Returns the current continuation prompt"""
271 197
272 198 return self.continuation_prompt_template.safe_substitute()
273 199
274 200 def output_prompt(self, number=''):
275 201 """Returns the output prompt for result"""
276 202
277 203 return self.output_prompt_template.safe_substitute({'number':number})
278 204
279 205
280 206 def is_complete(self, block):
281 207 """Determine if block is complete.
282 208
283 209 Parameters
284 210 block : string
285 211
286 212 Result
287 213 True if block can be sent to the engine without compile errors.
288 214 False otherwise.
289 215 """
290 216
291 217 try:
292 ast = self.compile_ast(block)
218 is_complete = codeop.compile_command(block.rstrip() + '\n\n',
219 "<string>", "exec")
293 220 except:
294 221 return False
295 222
296 223 lines = block.split('\n')
297 return (len(lines)==1 or str(lines[-1])=='')
298
299
300 def compile_ast(self, block):
301 """Compile block to an AST
302
303 Parameters:
304 block : str
305
306 Result:
307 AST
308
309 Throws:
310 Exception if block cannot be compiled
311 """
312
313 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])==''))
314 226
315 227
316 228 def execute(self, block, blockID=None):
317 229 """Execute the block and return the result.
318 230
319 231 Parameters:
320 232 block : {str, AST}
321 233 blockID : any
322 234 Caller may provide an ID to identify this block.
323 235 result['blockID'] := blockID
324 236
325 237 Result:
326 238 Deferred result of self.interpreter.execute
327 239 """
328 240
329 241 if(not self.is_complete(block)):
330 242 raise Exception("Block is not compilable")
331 243
332 244 if(blockID == None):
333 blockID = uuid.uuid4() #random UUID
245 blockID = guid.generate()
334 246
335 247 try:
336 248 result = self.shell.execute(block)
337 249 except Exception,e:
338 250 e = self._add_block_id_for_failure(e, blockID=blockID)
339 251 e = self.update_cell_prompt(e, blockID=blockID)
340 252 e = self.render_error(e)
341 253 else:
342 254 result = self._add_block_id_for_result(result, blockID=blockID)
343 255 result = self.update_cell_prompt(result, blockID=blockID)
344 256 result = self.render_result(result)
345 257
346 258 return result
347 259
348 260
349 261 def _add_block_id_for_result(self, result, blockID):
350 262 """Add the blockID to result or failure. Unfortunatley, we have to
351 263 treat failures differently than result dicts.
352 264 """
353 265
354 266 result['blockID'] = blockID
355 267
356 268 return result
357 269
358 270 def _add_block_id_for_failure(self, failure, blockID):
359 271 """_add_block_id_for_failure"""
360 272 failure.blockID = blockID
361 273 return failure
362 274
363 275
364 276 def _add_history(self, result, block=None):
365 277 """Add block to the history"""
366 278
367 279 assert(block != None)
368 280 self.history.add_items([block])
369 281 self.history_cursor += 1
370 282
371 283 return result
372 284
373 285
374 286 def get_history_previous(self, current_block):
375 287 """ Returns previous history string and decrement history cursor.
376 288 """
377 289 command = self.history.get_history_item(self.history_cursor - 1)
378 290
379 291 if command is not None:
380 292 if(self.history_cursor+1 == len(self.history.input_cache)):
381 293 self.history.input_cache[self.history_cursor] = current_block
382 294 self.history_cursor -= 1
383 295 return command
384 296
385 297
386 298 def get_history_next(self):
387 299 """ Returns next history string and increment history cursor.
388 300 """
389 301 command = self.history.get_history_item(self.history_cursor+1)
390 302
391 303 if command is not None:
392 304 self.history_cursor += 1
393 305 return command
394 306
395 307 ###
396 308 # Subclasses probably want to override these methods...
397 309 ###
398 310
399 311 def update_cell_prompt(self, result, blockID=None):
400 312 """Subclass may override to update the input prompt for a block.
401 313
402 314 This method only really makes sens in asyncrhonous frontend.
403 315 Since this method will be called as a
404 316 twisted.internet.defer.Deferred's callback, implementations should
405 317 return result when finished.
406 318 """
407 319
408 320 raise NotImplementedError
409 321
410 322
411 323 def render_result(self, result):
412 324 """Subclasses must override to render result.
413 325
414 326 In asynchronous frontends, this method will be called as a
415 327 twisted.internet.defer.Deferred's callback. Implementations
416 328 should thus return result when finished.
417 329 """
418 330
419 331 raise NotImplementedError
420 332
421 333
422 334 def render_error(self, failure):
423 335 """Subclasses must override to render the failure.
424 336
425 337 In asynchronous frontends, this method will be called as a
426 338 twisted.internet.defer.Deferred's callback. Implementations
427 339 should thus return result when finished.
428 340 """
429 341
430 342 raise NotImplementedError
431 343
@@ -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,30 +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 19 try:
23 20 from zope.interface import Interface, Attribute, implements, classProvides
24 21 except ImportError:
25 22 #zope.interface is not available
26 23 Interface = object
27 24 def Attribute(name, doc): pass
28 25 def implements(interface): pass
29 26 def classProvides(interface): pass
30 27
@@ -1,68 +1,68 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 16 import os
17 17 from cStringIO import StringIO
18 18
19 from IPython.testing import decorators as testdec
19 # FIXME:
20 import nose
21 import sys
22 if sys.platform == 'win32':
23 raise nose.SkipTest("These tests are not reliable under windows")
20 24
21 # FIXME
22 @testdec.skip("This doesn't work under Windows")
23 25 def test_redirector():
24 26 """ Checks that the redirector can be used to do synchronous capture.
25 27 """
26 28 from IPython.kernel.core.fd_redirector import FDRedirector
27 29 r = FDRedirector()
28 30 out = StringIO()
29 31 try:
30 32 r.start()
31 33 for i in range(10):
32 34 os.system('echo %ic' % i)
33 35 print >>out, r.getvalue(),
34 36 print >>out, i
35 37 except:
36 38 r.stop()
37 39 raise
38 40 r.stop()
39 41 result1 = out.getvalue()
40 42 result2 = "".join("%ic\n%i\n" %(i, i) for i in range(10))
41 43 assert result1 == result2
42 44
43 # FIXME
44 @testdec.skip("This doesn't work under Windows")
45 45 def test_redirector_output_trap():
46 46 """ This test check not only that the redirector_output_trap does
47 47 trap the output, but also that it does it in a gready way, that
48 48 is by calling the callback ASAP.
49 49 """
50 50 from IPython.kernel.core.redirector_output_trap import RedirectorOutputTrap
51 51 out = StringIO()
52 52 trap = RedirectorOutputTrap(out.write, out.write)
53 53 try:
54 54 trap.set()
55 55 for i in range(10):
56 56 os.system('echo %ic' % i)
57 57 print "%ip" % i
58 58 print >>out, i
59 59 except:
60 60 trap.unset()
61 61 raise
62 62 trap.unset()
63 63 result1 = out.getvalue()
64 64 result2 = "".join("%ic\n%ip\n%i\n" %(i, i, i) for i in range(10))
65 65 assert result1 == result2
66 66
67 67
68 68
@@ -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,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 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now