##// END OF EJS Templates
update references for IPython.html
MinRK -
Show More
@@ -1,306 +1,306 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 An application for managing IPython profiles.
3 An application for managing IPython profiles.
4
4
5 To be invoked as the `ipython profile` subcommand.
5 To be invoked as the `ipython profile` subcommand.
6
6
7 Authors:
7 Authors:
8
8
9 * Min RK
9 * Min RK
10
10
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2008-2011 The IPython Development Team
14 # Copyright (C) 2008-2011 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 import os
24 import os
25
25
26 from IPython.config.application import Application
26 from IPython.config.application import Application
27 from IPython.core.application import (
27 from IPython.core.application import (
28 BaseIPythonApplication, base_flags
28 BaseIPythonApplication, base_flags
29 )
29 )
30 from IPython.core.profiledir import ProfileDir
30 from IPython.core.profiledir import ProfileDir
31 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
31 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
32 from IPython.utils.traitlets import Unicode, Bool, Dict
32 from IPython.utils.traitlets import Unicode, Bool, Dict
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Constants
35 # Constants
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38 create_help = """Create an IPython profile by name
38 create_help = """Create an IPython profile by name
39
39
40 Create an ipython profile directory by its name or
40 Create an ipython profile directory by its name or
41 profile directory path. Profile directories contain
41 profile directory path. Profile directories contain
42 configuration, log and security related files and are named
42 configuration, log and security related files and are named
43 using the convention 'profile_<name>'. By default they are
43 using the convention 'profile_<name>'. By default they are
44 located in your ipython directory. Once created, you will
44 located in your ipython directory. Once created, you will
45 can edit the configuration files in the profile
45 can edit the configuration files in the profile
46 directory to configure IPython. Most users will create a
46 directory to configure IPython. Most users will create a
47 profile directory by name,
47 profile directory by name,
48 `ipython profile create myprofile`, which will put the directory
48 `ipython profile create myprofile`, which will put the directory
49 in `<ipython_dir>/profile_myprofile`.
49 in `<ipython_dir>/profile_myprofile`.
50 """
50 """
51 list_help = """List available IPython profiles
51 list_help = """List available IPython profiles
52
52
53 List all available profiles, by profile location, that can
53 List all available profiles, by profile location, that can
54 be found in the current working directly or in the ipython
54 be found in the current working directly or in the ipython
55 directory. Profile directories are named using the convention
55 directory. Profile directories are named using the convention
56 'profile_<profile>'.
56 'profile_<profile>'.
57 """
57 """
58 profile_help = """Manage IPython profiles
58 profile_help = """Manage IPython profiles
59
59
60 Profile directories contain
60 Profile directories contain
61 configuration, log and security related files and are named
61 configuration, log and security related files and are named
62 using the convention 'profile_<name>'. By default they are
62 using the convention 'profile_<name>'. By default they are
63 located in your ipython directory. You can create profiles
63 located in your ipython directory. You can create profiles
64 with `ipython profile create <name>`, or see the profiles you
64 with `ipython profile create <name>`, or see the profiles you
65 already have with `ipython profile list`
65 already have with `ipython profile list`
66
66
67 To get started configuring IPython, simply do:
67 To get started configuring IPython, simply do:
68
68
69 $> ipython profile create
69 $> ipython profile create
70
70
71 and IPython will create the default profile in <ipython_dir>/profile_default,
71 and IPython will create the default profile in <ipython_dir>/profile_default,
72 where you can edit ipython_config.py to start configuring IPython.
72 where you can edit ipython_config.py to start configuring IPython.
73
73
74 """
74 """
75
75
76 _list_examples = "ipython profile list # list all profiles"
76 _list_examples = "ipython profile list # list all profiles"
77
77
78 _create_examples = """
78 _create_examples = """
79 ipython profile create foo # create profile foo w/ default config files
79 ipython profile create foo # create profile foo w/ default config files
80 ipython profile create foo --reset # restage default config files over current
80 ipython profile create foo --reset # restage default config files over current
81 ipython profile create foo --parallel # also stage parallel config files
81 ipython profile create foo --parallel # also stage parallel config files
82 """
82 """
83
83
84 _main_examples = """
84 _main_examples = """
85 ipython profile create -h # show the help string for the create subcommand
85 ipython profile create -h # show the help string for the create subcommand
86 ipython profile list -h # show the help string for the list subcommand
86 ipython profile list -h # show the help string for the list subcommand
87
87
88 ipython locate profile foo # print the path to the directory for profile 'foo'
88 ipython locate profile foo # print the path to the directory for profile 'foo'
89 """
89 """
90
90
91 #-----------------------------------------------------------------------------
91 #-----------------------------------------------------------------------------
92 # Profile Application Class (for `ipython profile` subcommand)
92 # Profile Application Class (for `ipython profile` subcommand)
93 #-----------------------------------------------------------------------------
93 #-----------------------------------------------------------------------------
94
94
95
95
96 def list_profiles_in(path):
96 def list_profiles_in(path):
97 """list profiles in a given root directory"""
97 """list profiles in a given root directory"""
98 files = os.listdir(path)
98 files = os.listdir(path)
99 profiles = []
99 profiles = []
100 for f in files:
100 for f in files:
101 full_path = os.path.join(path, f)
101 full_path = os.path.join(path, f)
102 if os.path.isdir(full_path) and f.startswith('profile_'):
102 if os.path.isdir(full_path) and f.startswith('profile_'):
103 profiles.append(f.split('_',1)[-1])
103 profiles.append(f.split('_',1)[-1])
104 return profiles
104 return profiles
105
105
106
106
107 def list_bundled_profiles():
107 def list_bundled_profiles():
108 """list profiles that are bundled with IPython."""
108 """list profiles that are bundled with IPython."""
109 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
109 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
110 files = os.listdir(path)
110 files = os.listdir(path)
111 profiles = []
111 profiles = []
112 for profile in files:
112 for profile in files:
113 full_path = os.path.join(path, profile)
113 full_path = os.path.join(path, profile)
114 if os.path.isdir(full_path) and profile != "__pycache__":
114 if os.path.isdir(full_path) and profile != "__pycache__":
115 profiles.append(profile)
115 profiles.append(profile)
116 return profiles
116 return profiles
117
117
118
118
119 class ProfileLocate(BaseIPythonApplication):
119 class ProfileLocate(BaseIPythonApplication):
120 description = """print the path an IPython profile dir"""
120 description = """print the path an IPython profile dir"""
121
121
122 def parse_command_line(self, argv=None):
122 def parse_command_line(self, argv=None):
123 super(ProfileLocate, self).parse_command_line(argv)
123 super(ProfileLocate, self).parse_command_line(argv)
124 if self.extra_args:
124 if self.extra_args:
125 self.profile = self.extra_args[0]
125 self.profile = self.extra_args[0]
126
126
127 def start(self):
127 def start(self):
128 print self.profile_dir.location
128 print self.profile_dir.location
129
129
130
130
131 class ProfileList(Application):
131 class ProfileList(Application):
132 name = u'ipython-profile'
132 name = u'ipython-profile'
133 description = list_help
133 description = list_help
134 examples = _list_examples
134 examples = _list_examples
135
135
136 aliases = Dict({
136 aliases = Dict({
137 'ipython-dir' : 'ProfileList.ipython_dir',
137 'ipython-dir' : 'ProfileList.ipython_dir',
138 'log-level' : 'Application.log_level',
138 'log-level' : 'Application.log_level',
139 })
139 })
140 flags = Dict(dict(
140 flags = Dict(dict(
141 debug = ({'Application' : {'log_level' : 0}},
141 debug = ({'Application' : {'log_level' : 0}},
142 "Set Application.log_level to 0, maximizing log output."
142 "Set Application.log_level to 0, maximizing log output."
143 )
143 )
144 ))
144 ))
145
145
146 ipython_dir = Unicode(get_ipython_dir(), config=True,
146 ipython_dir = Unicode(get_ipython_dir(), config=True,
147 help="""
147 help="""
148 The name of the IPython directory. This directory is used for logging
148 The name of the IPython directory. This directory is used for logging
149 configuration (through profiles), history storage, etc. The default
149 configuration (through profiles), history storage, etc. The default
150 is usually $HOME/.ipython. This options can also be specified through
150 is usually $HOME/.ipython. This options can also be specified through
151 the environment variable IPYTHONDIR.
151 the environment variable IPYTHONDIR.
152 """
152 """
153 )
153 )
154
154
155
155
156 def _print_profiles(self, profiles):
156 def _print_profiles(self, profiles):
157 """print list of profiles, indented."""
157 """print list of profiles, indented."""
158 for profile in profiles:
158 for profile in profiles:
159 print ' %s' % profile
159 print ' %s' % profile
160
160
161 def list_profile_dirs(self):
161 def list_profile_dirs(self):
162 profiles = list_bundled_profiles()
162 profiles = list_bundled_profiles()
163 if profiles:
163 if profiles:
164 print
164 print
165 print "Available profiles in IPython:"
165 print "Available profiles in IPython:"
166 self._print_profiles(profiles)
166 self._print_profiles(profiles)
167 print
167 print
168 print " The first request for a bundled profile will copy it"
168 print " The first request for a bundled profile will copy it"
169 print " into your IPython directory (%s)," % self.ipython_dir
169 print " into your IPython directory (%s)," % self.ipython_dir
170 print " where you can customize it."
170 print " where you can customize it."
171
171
172 profiles = list_profiles_in(self.ipython_dir)
172 profiles = list_profiles_in(self.ipython_dir)
173 if profiles:
173 if profiles:
174 print
174 print
175 print "Available profiles in %s:" % self.ipython_dir
175 print "Available profiles in %s:" % self.ipython_dir
176 self._print_profiles(profiles)
176 self._print_profiles(profiles)
177
177
178 profiles = list_profiles_in(os.getcwdu())
178 profiles = list_profiles_in(os.getcwdu())
179 if profiles:
179 if profiles:
180 print
180 print
181 print "Available profiles in current directory (%s):" % os.getcwdu()
181 print "Available profiles in current directory (%s):" % os.getcwdu()
182 self._print_profiles(profiles)
182 self._print_profiles(profiles)
183
183
184 print
184 print
185 print "To use any of the above profiles, start IPython with:"
185 print "To use any of the above profiles, start IPython with:"
186 print " ipython --profile=<name>"
186 print " ipython --profile=<name>"
187 print
187 print
188
188
189 def start(self):
189 def start(self):
190 self.list_profile_dirs()
190 self.list_profile_dirs()
191
191
192
192
193 create_flags = {}
193 create_flags = {}
194 create_flags.update(base_flags)
194 create_flags.update(base_flags)
195 # don't include '--init' flag, which implies running profile create in other apps
195 # don't include '--init' flag, which implies running profile create in other apps
196 create_flags.pop('init')
196 create_flags.pop('init')
197 create_flags['reset'] = ({'ProfileCreate': {'overwrite' : True}},
197 create_flags['reset'] = ({'ProfileCreate': {'overwrite' : True}},
198 "reset config files in this profile to the defaults.")
198 "reset config files in this profile to the defaults.")
199 create_flags['parallel'] = ({'ProfileCreate': {'parallel' : True}},
199 create_flags['parallel'] = ({'ProfileCreate': {'parallel' : True}},
200 "Include the config files for parallel "
200 "Include the config files for parallel "
201 "computing apps (ipengine, ipcontroller, etc.)")
201 "computing apps (ipengine, ipcontroller, etc.)")
202
202
203
203
204 class ProfileCreate(BaseIPythonApplication):
204 class ProfileCreate(BaseIPythonApplication):
205 name = u'ipython-profile'
205 name = u'ipython-profile'
206 description = create_help
206 description = create_help
207 examples = _create_examples
207 examples = _create_examples
208 auto_create = Bool(True, config=False)
208 auto_create = Bool(True, config=False)
209
209
210 def _copy_config_files_default(self):
210 def _copy_config_files_default(self):
211 return True
211 return True
212
212
213 parallel = Bool(False, config=True,
213 parallel = Bool(False, config=True,
214 help="whether to include parallel computing config files")
214 help="whether to include parallel computing config files")
215 def _parallel_changed(self, name, old, new):
215 def _parallel_changed(self, name, old, new):
216 parallel_files = [ 'ipcontroller_config.py',
216 parallel_files = [ 'ipcontroller_config.py',
217 'ipengine_config.py',
217 'ipengine_config.py',
218 'ipcluster_config.py'
218 'ipcluster_config.py'
219 ]
219 ]
220 if new:
220 if new:
221 for cf in parallel_files:
221 for cf in parallel_files:
222 self.config_files.append(cf)
222 self.config_files.append(cf)
223 else:
223 else:
224 for cf in parallel_files:
224 for cf in parallel_files:
225 if cf in self.config_files:
225 if cf in self.config_files:
226 self.config_files.remove(cf)
226 self.config_files.remove(cf)
227
227
228 def parse_command_line(self, argv):
228 def parse_command_line(self, argv):
229 super(ProfileCreate, self).parse_command_line(argv)
229 super(ProfileCreate, self).parse_command_line(argv)
230 # accept positional arg as profile name
230 # accept positional arg as profile name
231 if self.extra_args:
231 if self.extra_args:
232 self.profile = self.extra_args[0]
232 self.profile = self.extra_args[0]
233
233
234 flags = Dict(create_flags)
234 flags = Dict(create_flags)
235
235
236 classes = [ProfileDir]
236 classes = [ProfileDir]
237
237
238 def init_config_files(self):
238 def init_config_files(self):
239 super(ProfileCreate, self).init_config_files()
239 super(ProfileCreate, self).init_config_files()
240 # use local imports, since these classes may import from here
240 # use local imports, since these classes may import from here
241 from IPython.terminal.ipapp import TerminalIPythonApp
241 from IPython.terminal.ipapp import TerminalIPythonApp
242 apps = [TerminalIPythonApp]
242 apps = [TerminalIPythonApp]
243 try:
243 try:
244 from IPython.qt.console.qtconsoleapp import IPythonQtConsoleApp
244 from IPython.qt.console.qtconsoleapp import IPythonQtConsoleApp
245 except Exception:
245 except Exception:
246 # this should be ImportError, but under weird circumstances
246 # this should be ImportError, but under weird circumstances
247 # this might be an AttributeError, or possibly others
247 # this might be an AttributeError, or possibly others
248 # in any case, nothing should cause the profile creation to crash.
248 # in any case, nothing should cause the profile creation to crash.
249 pass
249 pass
250 else:
250 else:
251 apps.append(IPythonQtConsoleApp)
251 apps.append(IPythonQtConsoleApp)
252 try:
252 try:
253 from IPython.html.notebook.notebookapp import NotebookApp
253 from IPython.html.notebookapp import NotebookApp
254 except ImportError:
254 except ImportError:
255 pass
255 pass
256 except Exception:
256 except Exception:
257 self.log.debug('Unexpected error when importing NotebookApp',
257 self.log.debug('Unexpected error when importing NotebookApp',
258 exc_info=True
258 exc_info=True
259 )
259 )
260 else:
260 else:
261 apps.append(NotebookApp)
261 apps.append(NotebookApp)
262 if self.parallel:
262 if self.parallel:
263 from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
263 from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
264 from IPython.parallel.apps.ipengineapp import IPEngineApp
264 from IPython.parallel.apps.ipengineapp import IPEngineApp
265 from IPython.parallel.apps.ipclusterapp import IPClusterStart
265 from IPython.parallel.apps.ipclusterapp import IPClusterStart
266 from IPython.parallel.apps.iploggerapp import IPLoggerApp
266 from IPython.parallel.apps.iploggerapp import IPLoggerApp
267 apps.extend([
267 apps.extend([
268 IPControllerApp,
268 IPControllerApp,
269 IPEngineApp,
269 IPEngineApp,
270 IPClusterStart,
270 IPClusterStart,
271 IPLoggerApp,
271 IPLoggerApp,
272 ])
272 ])
273 for App in apps:
273 for App in apps:
274 app = App()
274 app = App()
275 app.config.update(self.config)
275 app.config.update(self.config)
276 app.log = self.log
276 app.log = self.log
277 app.overwrite = self.overwrite
277 app.overwrite = self.overwrite
278 app.copy_config_files=True
278 app.copy_config_files=True
279 app.profile = self.profile
279 app.profile = self.profile
280 app.init_profile_dir()
280 app.init_profile_dir()
281 app.init_config_files()
281 app.init_config_files()
282
282
283 def stage_default_config_file(self):
283 def stage_default_config_file(self):
284 pass
284 pass
285
285
286
286
287 class ProfileApp(Application):
287 class ProfileApp(Application):
288 name = u'ipython-profile'
288 name = u'ipython-profile'
289 description = profile_help
289 description = profile_help
290 examples = _main_examples
290 examples = _main_examples
291
291
292 subcommands = Dict(dict(
292 subcommands = Dict(dict(
293 create = (ProfileCreate, ProfileCreate.description.splitlines()[0]),
293 create = (ProfileCreate, ProfileCreate.description.splitlines()[0]),
294 list = (ProfileList, ProfileList.description.splitlines()[0]),
294 list = (ProfileList, ProfileList.description.splitlines()[0]),
295 ))
295 ))
296
296
297 def start(self):
297 def start(self):
298 if self.subapp is None:
298 if self.subapp is None:
299 print "No subcommand specified. Must specify one of: %s"%(self.subcommands.keys())
299 print "No subcommand specified. Must specify one of: %s"%(self.subcommands.keys())
300 print
300 print
301 self.print_description()
301 self.print_description()
302 self.print_subcommands()
302 self.print_subcommands()
303 self.exit(1)
303 self.exit(1)
304 else:
304 else:
305 return self.subapp.start()
305 return self.subapp.start()
306
306
@@ -1,38 +1,38 b''
1 #-----------------------------------------------------------------------------
1 #-----------------------------------------------------------------------------
2 # Copyright (C) 2010-2011 The IPython Development Team.
2 # Copyright (C) 2010-2011 The IPython Development Team.
3 #
3 #
4 # Distributed under the terms of the BSD License.
4 # Distributed under the terms of the BSD License.
5 #
5 #
6 # The full license is in the file COPYING.txt, distributed with this software.
6 # The full license is in the file COPYING.txt, distributed with this software.
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 import os
8 import os
9
9
10 import nose.tools as nt
10 import nose.tools as nt
11
11
12 from IPython.core import display
12 from IPython.core import display
13 from IPython.utils import path as ipath
13 from IPython.utils import path as ipath
14
14
15 def test_image_size():
15 def test_image_size():
16 """Simple test for display.Image(args, width=x,height=y)"""
16 """Simple test for display.Image(args, width=x,height=y)"""
17 thisurl = 'http://www.google.fr/images/srpr/logo3w.png'
17 thisurl = 'http://www.google.fr/images/srpr/logo3w.png'
18 img = display.Image(url=thisurl, width=200, height=200)
18 img = display.Image(url=thisurl, width=200, height=200)
19 nt.assert_equal(u'<img src="%s" width="200" height="200"/>' % (thisurl), img._repr_html_())
19 nt.assert_equal(u'<img src="%s" width="200" height="200"/>' % (thisurl), img._repr_html_())
20 img = display.Image(url=thisurl, width=200)
20 img = display.Image(url=thisurl, width=200)
21 nt.assert_equal(u'<img src="%s" width="200"/>' % (thisurl), img._repr_html_())
21 nt.assert_equal(u'<img src="%s" width="200"/>' % (thisurl), img._repr_html_())
22 img = display.Image(url=thisurl)
22 img = display.Image(url=thisurl)
23 nt.assert_equal(u'<img src="%s"/>' % (thisurl), img._repr_html_())
23 nt.assert_equal(u'<img src="%s"/>' % (thisurl), img._repr_html_())
24
24
25 def test_image_filename_defaults():
25 def test_image_filename_defaults():
26 '''test format constraint, and validity of jpeg and png'''
26 '''test format constraint, and validity of jpeg and png'''
27 tpath = ipath.get_ipython_package_dir()
27 tpath = ipath.get_ipython_package_dir()
28 nt.assert_raises(ValueError, display.Image, filename=os.path.join(tpath, 'testing/tests/badformat.gif'),
28 nt.assert_raises(ValueError, display.Image, filename=os.path.join(tpath, 'testing/tests/badformat.gif'),
29 embed=True)
29 embed=True)
30 nt.assert_raises(ValueError, display.Image)
30 nt.assert_raises(ValueError, display.Image)
31 nt.assert_raises(ValueError, display.Image, data='this is not an image', format='badformat', embed=True)
31 nt.assert_raises(ValueError, display.Image, data='this is not an image', format='badformat', embed=True)
32 imgfile = os.path.join(tpath, 'html/notebook/static/base/images/ipynblogo.png')
32 imgfile = os.path.join(tpath, 'html/static/base/images/ipynblogo.png')
33 img = display.Image(filename=imgfile)
33 img = display.Image(filename=imgfile)
34 nt.assert_equal('png', img.format)
34 nt.assert_equal('png', img.format)
35 nt.assert_is_not_none(img._repr_png_())
35 nt.assert_is_not_none(img._repr_png_())
36 img = display.Image(filename=os.path.join(tpath, 'testing/tests/logo.jpg'), embed=False)
36 img = display.Image(filename=os.path.join(tpath, 'testing/tests/logo.jpg'), embed=False)
37 nt.assert_equal('jpeg', img.format)
37 nt.assert_equal('jpeg', img.format)
38 nt.assert_is_none(img._repr_jpeg_())
38 nt.assert_is_none(img._repr_jpeg_())
@@ -1,73 +1,73 b''
1 # IPython Notebook development
1 # IPython Notebook development
2
2
3 ## Development dependencies
3 ## Development dependencies
4
4
5 Developers of the IPython Notebook will need to install the following tools:
5 Developers of the IPython Notebook will need to install the following tools:
6
6
7 * fabric
7 * fabric
8 * node.js
8 * node.js
9 * less (`npm install -g less`)
9 * less (`npm install -g less`)
10 * bower (`npm install -g bower`)
10 * bower (`npm install -g bower`)
11
11
12 ## Components
12 ## Components
13
13
14 We are moving to a model where our JavaScript dependencies are managed using
14 We are moving to a model where our JavaScript dependencies are managed using
15 [bower](http://bower.io/). These packages are installed in `static/components`
15 [bower](http://bower.io/). These packages are installed in `static/components`
16 and commited into our git repo. Our dependencies are described in the file
16 and commited into our git repo. Our dependencies are described in the file
17 `static/bower.json`. To update our bower packages, run `fab components` in this
17 `static/bower.json`. To update our bower packages, run `fab components` in this
18 directory.
18 directory.
19
19
20 Because CodeMirror does not use proper semantic versioning for its GitHub tags,
20 Because CodeMirror does not use proper semantic versioning for its GitHub tags,
21 we maintain our own fork of CodeMirror that is used with bower. This fork should
21 we maintain our own fork of CodeMirror that is used with bower. This fork should
22 track the upstream CodeMirror exactly; the only difference is that we are adding
22 track the upstream CodeMirror exactly; the only difference is that we are adding
23 semantic versioned tags to our repo.
23 semantic versioned tags to our repo.
24
24
25 ## less
25 ## less
26
26
27 If you edit our `.less` files you will need to run the less compiler to build
27 If you edit our `.less` files you will need to run the less compiler to build
28 our minified css files. This can be done by running `fab css` from this directory.
28 our minified css files. This can be done by running `fab css` from this directory.
29
29
30 ## JavaScript Documentation
30 ## JavaScript Documentation
31
31
32
32
33 How to Build/ view the doc for JavaScript. JavaScript documentation should follow a
33 How to Build/ view the doc for JavaScript. JavaScript documentation should follow a
34 style close to JSDoc one, so you should be able to build them with your favorite
34 style close to JSDoc one, so you should be able to build them with your favorite
35 documentation builder. Still the documentation comment are mainly written to be read
35 documentation builder. Still the documentation comment are mainly written to be read
36 with YUI doc. You can either build a static version, or start a YUIdoc server that
36 with YUI doc. You can either build a static version, or start a YUIdoc server that
37 will live update the doc at every page request.
37 will live update the doc at every page request.
38
38
39
39
40
40
41 To do so, you will need to install YUIdoc.
41 To do so, you will need to install YUIdoc.
42
42
43 ### Install NodeJS
43 ### Install NodeJS
44
44
45 Node is a browser less javascript interpreter. To install it please refer to
45 Node is a browser less javascript interpreter. To install it please refer to
46 the documentation for your platform. Install also NPM (node package manager) if
46 the documentation for your platform. Install also NPM (node package manager) if
47 it does not come bundled with it.
47 it does not come bundled with it.
48
48
49 ### Get YUIdoc
49 ### Get YUIdoc
50
50
51 npm does by default install package in `./node_modules` instead of doing a
51 npm does by default install package in `./node_modules` instead of doing a
52 system wide install. I'll leave you to yuidoc docs if you want to make a system
52 system wide install. I'll leave you to yuidoc docs if you want to make a system
53 wide install.
53 wide install.
54
54
55 First, cd into js directory :
55 First, cd into js directory :
56 ```bash
56 ```bash
57 cd IPython/html/notebook/static/js/
57 cd IPython/html/static/js/
58 # install yuidoc
58 # install yuidoc
59 npm install yuidocjs
59 npm install yuidocjs
60 ```
60 ```
61
61
62
62
63 ### Run YUIdoc server
63 ### Run YUIdoc server
64
64
65 From IPython/html/notebook/static/js/
65 From IPython/html/static/js/
66 ```bash
66 ```bash
67 # run yuidoc for install dir
67 # run yuidoc for install dir
68 ./node_modules/yuidocjs/lib/cli.js --server .
68 ./node_modules/yuidocjs/lib/cli.js --server .
69 ```
69 ```
70
70
71 Follow the instruction and the documentation should be available on localhost:3000
71 Follow the instruction and the documentation should be available on localhost:3000
72
72
73 Omitting `--server` will build a static version in the `out` folder by default.
73 Omitting `--server` will build a static version in the `out` folder by default.
@@ -1,753 +1,753 b''
1 # coding: utf-8
1 # coding: utf-8
2 """A tornado based IPython notebook server.
2 """A tornado based IPython notebook server.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 # stdlib
19 # stdlib
20 import errno
20 import errno
21 import logging
21 import logging
22 import os
22 import os
23 import random
23 import random
24 import select
24 import select
25 import signal
25 import signal
26 import socket
26 import socket
27 import sys
27 import sys
28 import threading
28 import threading
29 import time
29 import time
30 import uuid
30 import uuid
31 import webbrowser
31 import webbrowser
32
32
33
33
34 # Third party
34 # Third party
35 # check for pyzmq 2.1.11
35 # check for pyzmq 2.1.11
36 from IPython.utils.zmqrelated import check_for_zmq
36 from IPython.utils.zmqrelated import check_for_zmq
37 check_for_zmq('2.1.11', 'IPython.html.notebook')
37 check_for_zmq('2.1.11', 'IPython.html')
38
38
39 import zmq
39 import zmq
40 from jinja2 import Environment, FileSystemLoader
40 from jinja2 import Environment, FileSystemLoader
41
41
42 # Install the pyzmq ioloop. This has to be done before anything else from
42 # Install the pyzmq ioloop. This has to be done before anything else from
43 # tornado is imported.
43 # tornado is imported.
44 from zmq.eventloop import ioloop
44 from zmq.eventloop import ioloop
45 ioloop.install()
45 ioloop.install()
46
46
47 # check for tornado 2.1.0
47 # check for tornado 2.1.0
48 msg = "The IPython Notebook requires tornado >= 2.1.0"
48 msg = "The IPython Notebook requires tornado >= 2.1.0"
49 try:
49 try:
50 import tornado
50 import tornado
51 except ImportError:
51 except ImportError:
52 raise ImportError(msg)
52 raise ImportError(msg)
53 try:
53 try:
54 version_info = tornado.version_info
54 version_info = tornado.version_info
55 except AttributeError:
55 except AttributeError:
56 raise ImportError(msg + ", but you have < 1.1.0")
56 raise ImportError(msg + ", but you have < 1.1.0")
57 if version_info < (2,1,0):
57 if version_info < (2,1,0):
58 raise ImportError(msg + ", but you have %s" % tornado.version)
58 raise ImportError(msg + ", but you have %s" % tornado.version)
59
59
60 from tornado import httpserver
60 from tornado import httpserver
61 from tornado import web
61 from tornado import web
62
62
63 # Our own libraries
63 # Our own libraries
64 from IPython.html.notebook import DEFAULT_STATIC_FILES_PATH
64 from IPython.html import DEFAULT_STATIC_FILES_PATH
65
65
66 from .services.kernels.kernelmanager import MappingKernelManager
66 from .services.kernels.kernelmanager import MappingKernelManager
67 from .services.notebooks.nbmanager import NotebookManager
67 from .services.notebooks.nbmanager import NotebookManager
68 from .services.notebooks.filenbmanager import FileNotebookManager
68 from .services.notebooks.filenbmanager import FileNotebookManager
69 from .services.clusters.clustermanager import ClusterManager
69 from .services.clusters.clustermanager import ClusterManager
70
70
71 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
71 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
72
72
73 from IPython.config.application import catch_config_error, boolean_flag
73 from IPython.config.application import catch_config_error, boolean_flag
74 from IPython.core.application import BaseIPythonApplication
74 from IPython.core.application import BaseIPythonApplication
75 from IPython.consoleapp import IPythonConsoleApp
75 from IPython.consoleapp import IPythonConsoleApp
76 from IPython.kernel import swallow_argv
76 from IPython.kernel import swallow_argv
77 from IPython.kernel.zmq.session import default_secure
77 from IPython.kernel.zmq.session import default_secure
78 from IPython.kernel.zmq.kernelapp import (
78 from IPython.kernel.zmq.kernelapp import (
79 kernel_flags,
79 kernel_flags,
80 kernel_aliases,
80 kernel_aliases,
81 )
81 )
82 from IPython.utils.importstring import import_item
82 from IPython.utils.importstring import import_item
83 from IPython.utils.localinterfaces import LOCALHOST
83 from IPython.utils.localinterfaces import LOCALHOST
84 from IPython.utils import submodule
84 from IPython.utils import submodule
85 from IPython.utils.traitlets import (
85 from IPython.utils.traitlets import (
86 Dict, Unicode, Integer, List, Bool, Bytes,
86 Dict, Unicode, Integer, List, Bool, Bytes,
87 DottedObjectName
87 DottedObjectName
88 )
88 )
89 from IPython.utils import py3compat
89 from IPython.utils import py3compat
90 from IPython.utils.path import filefind
90 from IPython.utils.path import filefind
91
91
92 from .utils import url_path_join
92 from .utils import url_path_join
93
93
94 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
95 # Module globals
95 # Module globals
96 #-----------------------------------------------------------------------------
96 #-----------------------------------------------------------------------------
97
97
98 _examples = """
98 _examples = """
99 ipython notebook # start the notebook
99 ipython notebook # start the notebook
100 ipython notebook --profile=sympy # use the sympy profile
100 ipython notebook --profile=sympy # use the sympy profile
101 ipython notebook --pylab=inline # pylab in inline plotting mode
101 ipython notebook --pylab=inline # pylab in inline plotting mode
102 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
102 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
103 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
103 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
104 """
104 """
105
105
106 #-----------------------------------------------------------------------------
106 #-----------------------------------------------------------------------------
107 # Helper functions
107 # Helper functions
108 #-----------------------------------------------------------------------------
108 #-----------------------------------------------------------------------------
109
109
110 def random_ports(port, n):
110 def random_ports(port, n):
111 """Generate a list of n random ports near the given port.
111 """Generate a list of n random ports near the given port.
112
112
113 The first 5 ports will be sequential, and the remaining n-5 will be
113 The first 5 ports will be sequential, and the remaining n-5 will be
114 randomly selected in the range [port-2*n, port+2*n].
114 randomly selected in the range [port-2*n, port+2*n].
115 """
115 """
116 for i in range(min(5, n)):
116 for i in range(min(5, n)):
117 yield port + i
117 yield port + i
118 for i in range(n-5):
118 for i in range(n-5):
119 yield port + random.randint(-2*n, 2*n)
119 yield port + random.randint(-2*n, 2*n)
120
120
121 def load_handlers(name):
121 def load_handlers(name):
122 """Load the (URL pattern, handler) tuples for each component."""
122 """Load the (URL pattern, handler) tuples for each component."""
123 name = 'IPython.html.notebook.' + name
123 name = 'IPython.html.' + name
124 mod = __import__(name, fromlist=['default_handlers'])
124 mod = __import__(name, fromlist=['default_handlers'])
125 return mod.default_handlers
125 return mod.default_handlers
126
126
127 #-----------------------------------------------------------------------------
127 #-----------------------------------------------------------------------------
128 # The Tornado web application
128 # The Tornado web application
129 #-----------------------------------------------------------------------------
129 #-----------------------------------------------------------------------------
130
130
131 class NotebookWebApplication(web.Application):
131 class NotebookWebApplication(web.Application):
132
132
133 def __init__(self, ipython_app, kernel_manager, notebook_manager,
133 def __init__(self, ipython_app, kernel_manager, notebook_manager,
134 cluster_manager, log,
134 cluster_manager, log,
135 base_project_url, settings_overrides):
135 base_project_url, settings_overrides):
136
136
137 settings = self.init_settings(
137 settings = self.init_settings(
138 ipython_app, kernel_manager, notebook_manager, cluster_manager,
138 ipython_app, kernel_manager, notebook_manager, cluster_manager,
139 log, base_project_url, settings_overrides)
139 log, base_project_url, settings_overrides)
140 handlers = self.init_handlers(settings)
140 handlers = self.init_handlers(settings)
141
141
142 super(NotebookWebApplication, self).__init__(handlers, **settings)
142 super(NotebookWebApplication, self).__init__(handlers, **settings)
143
143
144 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
144 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
145 cluster_manager, log,
145 cluster_manager, log,
146 base_project_url, settings_overrides):
146 base_project_url, settings_overrides):
147 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
147 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
148 # base_project_url will always be unicode, which will in turn
148 # base_project_url will always be unicode, which will in turn
149 # make the patterns unicode, and ultimately result in unicode
149 # make the patterns unicode, and ultimately result in unicode
150 # keys in kwargs to handler._execute(**kwargs) in tornado.
150 # keys in kwargs to handler._execute(**kwargs) in tornado.
151 # This enforces that base_project_url be ascii in that situation.
151 # This enforces that base_project_url be ascii in that situation.
152 #
152 #
153 # Note that the URLs these patterns check against are escaped,
153 # Note that the URLs these patterns check against are escaped,
154 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
154 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
155 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
155 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
156 template_path = os.path.join(os.path.dirname(__file__), "templates")
156 template_path = os.path.join(os.path.dirname(__file__), "templates")
157 settings = dict(
157 settings = dict(
158 # basics
158 # basics
159 base_project_url=base_project_url,
159 base_project_url=base_project_url,
160 base_kernel_url=ipython_app.base_kernel_url,
160 base_kernel_url=ipython_app.base_kernel_url,
161 template_path=template_path,
161 template_path=template_path,
162 static_path=ipython_app.static_file_path,
162 static_path=ipython_app.static_file_path,
163 static_handler_class = FileFindHandler,
163 static_handler_class = FileFindHandler,
164 static_url_prefix = url_path_join(base_project_url,'/static/'),
164 static_url_prefix = url_path_join(base_project_url,'/static/'),
165
165
166 # authentication
166 # authentication
167 cookie_secret=ipython_app.cookie_secret,
167 cookie_secret=ipython_app.cookie_secret,
168 login_url=url_path_join(base_project_url,'/login'),
168 login_url=url_path_join(base_project_url,'/login'),
169 read_only=ipython_app.read_only,
169 read_only=ipython_app.read_only,
170 password=ipython_app.password,
170 password=ipython_app.password,
171
171
172 # managers
172 # managers
173 kernel_manager=kernel_manager,
173 kernel_manager=kernel_manager,
174 notebook_manager=notebook_manager,
174 notebook_manager=notebook_manager,
175 cluster_manager=cluster_manager,
175 cluster_manager=cluster_manager,
176
176
177 # IPython stuff
177 # IPython stuff
178 mathjax_url=ipython_app.mathjax_url,
178 mathjax_url=ipython_app.mathjax_url,
179 max_msg_size=ipython_app.max_msg_size,
179 max_msg_size=ipython_app.max_msg_size,
180 config=ipython_app.config,
180 config=ipython_app.config,
181 use_less=ipython_app.use_less,
181 use_less=ipython_app.use_less,
182 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
182 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
183 )
183 )
184
184
185 # allow custom overrides for the tornado web app.
185 # allow custom overrides for the tornado web app.
186 settings.update(settings_overrides)
186 settings.update(settings_overrides)
187 return settings
187 return settings
188
188
189 def init_handlers(self, settings):
189 def init_handlers(self, settings):
190 # Load the (URL pattern, handler) tuples for each component.
190 # Load the (URL pattern, handler) tuples for each component.
191 handlers = []
191 handlers = []
192 handlers.extend(load_handlers('base.handlers'))
192 handlers.extend(load_handlers('base.handlers'))
193 handlers.extend(load_handlers('tree.handlers'))
193 handlers.extend(load_handlers('tree.handlers'))
194 handlers.extend(load_handlers('auth.login'))
194 handlers.extend(load_handlers('auth.login'))
195 handlers.extend(load_handlers('auth.logout'))
195 handlers.extend(load_handlers('auth.logout'))
196 handlers.extend(load_handlers('notebook.handlers'))
196 handlers.extend(load_handlers('notebook.handlers'))
197 handlers.extend(load_handlers('services.kernels.handlers'))
197 handlers.extend(load_handlers('services.kernels.handlers'))
198 handlers.extend(load_handlers('services.notebooks.handlers'))
198 handlers.extend(load_handlers('services.notebooks.handlers'))
199 handlers.extend(load_handlers('services.clusters.handlers'))
199 handlers.extend(load_handlers('services.clusters.handlers'))
200 handlers.extend([
200 handlers.extend([
201 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
201 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
202 ])
202 ])
203 # prepend base_project_url onto the patterns that we match
203 # prepend base_project_url onto the patterns that we match
204 new_handlers = []
204 new_handlers = []
205 for handler in handlers:
205 for handler in handlers:
206 pattern = url_path_join(settings['base_project_url'], handler[0])
206 pattern = url_path_join(settings['base_project_url'], handler[0])
207 new_handler = tuple([pattern] + list(handler[1:]))
207 new_handler = tuple([pattern] + list(handler[1:]))
208 new_handlers.append(new_handler)
208 new_handlers.append(new_handler)
209 return new_handlers
209 return new_handlers
210
210
211
211
212
212
213 #-----------------------------------------------------------------------------
213 #-----------------------------------------------------------------------------
214 # Aliases and Flags
214 # Aliases and Flags
215 #-----------------------------------------------------------------------------
215 #-----------------------------------------------------------------------------
216
216
217 flags = dict(kernel_flags)
217 flags = dict(kernel_flags)
218 flags['no-browser']=(
218 flags['no-browser']=(
219 {'NotebookApp' : {'open_browser' : False}},
219 {'NotebookApp' : {'open_browser' : False}},
220 "Don't open the notebook in a browser after startup."
220 "Don't open the notebook in a browser after startup."
221 )
221 )
222 flags['no-mathjax']=(
222 flags['no-mathjax']=(
223 {'NotebookApp' : {'enable_mathjax' : False}},
223 {'NotebookApp' : {'enable_mathjax' : False}},
224 """Disable MathJax
224 """Disable MathJax
225
225
226 MathJax is the javascript library IPython uses to render math/LaTeX. It is
226 MathJax is the javascript library IPython uses to render math/LaTeX. It is
227 very large, so you may want to disable it if you have a slow internet
227 very large, so you may want to disable it if you have a slow internet
228 connection, or for offline use of the notebook.
228 connection, or for offline use of the notebook.
229
229
230 When disabled, equations etc. will appear as their untransformed TeX source.
230 When disabled, equations etc. will appear as their untransformed TeX source.
231 """
231 """
232 )
232 )
233 flags['read-only'] = (
233 flags['read-only'] = (
234 {'NotebookApp' : {'read_only' : True}},
234 {'NotebookApp' : {'read_only' : True}},
235 """Allow read-only access to notebooks.
235 """Allow read-only access to notebooks.
236
236
237 When using a password to protect the notebook server, this flag
237 When using a password to protect the notebook server, this flag
238 allows unauthenticated clients to view the notebook list, and
238 allows unauthenticated clients to view the notebook list, and
239 individual notebooks, but not edit them, start kernels, or run
239 individual notebooks, but not edit them, start kernels, or run
240 code.
240 code.
241
241
242 If no password is set, the server will be entirely read-only.
242 If no password is set, the server will be entirely read-only.
243 """
243 """
244 )
244 )
245
245
246 # Add notebook manager flags
246 # Add notebook manager flags
247 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
247 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
248 'Auto-save a .py script everytime the .ipynb notebook is saved',
248 'Auto-save a .py script everytime the .ipynb notebook is saved',
249 'Do not auto-save .py scripts for every notebook'))
249 'Do not auto-save .py scripts for every notebook'))
250
250
251 # the flags that are specific to the frontend
251 # the flags that are specific to the frontend
252 # these must be scrubbed before being passed to the kernel,
252 # these must be scrubbed before being passed to the kernel,
253 # or it will raise an error on unrecognized flags
253 # or it will raise an error on unrecognized flags
254 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
254 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
255
255
256 aliases = dict(kernel_aliases)
256 aliases = dict(kernel_aliases)
257
257
258 aliases.update({
258 aliases.update({
259 'ip': 'NotebookApp.ip',
259 'ip': 'NotebookApp.ip',
260 'port': 'NotebookApp.port',
260 'port': 'NotebookApp.port',
261 'port-retries': 'NotebookApp.port_retries',
261 'port-retries': 'NotebookApp.port_retries',
262 'transport': 'KernelManager.transport',
262 'transport': 'KernelManager.transport',
263 'keyfile': 'NotebookApp.keyfile',
263 'keyfile': 'NotebookApp.keyfile',
264 'certfile': 'NotebookApp.certfile',
264 'certfile': 'NotebookApp.certfile',
265 'notebook-dir': 'NotebookManager.notebook_dir',
265 'notebook-dir': 'NotebookManager.notebook_dir',
266 'browser': 'NotebookApp.browser',
266 'browser': 'NotebookApp.browser',
267 })
267 })
268
268
269 # remove ipkernel flags that are singletons, and don't make sense in
269 # remove ipkernel flags that are singletons, and don't make sense in
270 # multi-kernel evironment:
270 # multi-kernel evironment:
271 aliases.pop('f', None)
271 aliases.pop('f', None)
272
272
273 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
273 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
274 u'notebook-dir']
274 u'notebook-dir']
275
275
276 #-----------------------------------------------------------------------------
276 #-----------------------------------------------------------------------------
277 # NotebookApp
277 # NotebookApp
278 #-----------------------------------------------------------------------------
278 #-----------------------------------------------------------------------------
279
279
280 class NotebookApp(BaseIPythonApplication):
280 class NotebookApp(BaseIPythonApplication):
281
281
282 name = 'ipython-notebook'
282 name = 'ipython-notebook'
283 default_config_file_name='ipython_notebook_config.py'
283 default_config_file_name='ipython_notebook_config.py'
284
284
285 description = """
285 description = """
286 The IPython HTML Notebook.
286 The IPython HTML Notebook.
287
287
288 This launches a Tornado based HTML Notebook Server that serves up an
288 This launches a Tornado based HTML Notebook Server that serves up an
289 HTML5/Javascript Notebook client.
289 HTML5/Javascript Notebook client.
290 """
290 """
291 examples = _examples
291 examples = _examples
292
292
293 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
293 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
294 FileNotebookManager]
294 FileNotebookManager]
295 flags = Dict(flags)
295 flags = Dict(flags)
296 aliases = Dict(aliases)
296 aliases = Dict(aliases)
297
297
298 kernel_argv = List(Unicode)
298 kernel_argv = List(Unicode)
299
299
300 max_msg_size = Integer(65536, config=True, help="""
300 max_msg_size = Integer(65536, config=True, help="""
301 The max raw message size accepted from the browser
301 The max raw message size accepted from the browser
302 over a WebSocket connection.
302 over a WebSocket connection.
303 """)
303 """)
304
304
305 def _log_level_default(self):
305 def _log_level_default(self):
306 return logging.INFO
306 return logging.INFO
307
307
308 def _log_format_default(self):
308 def _log_format_default(self):
309 """override default log format to include time"""
309 """override default log format to include time"""
310 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
310 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
311
311
312 # create requested profiles by default, if they don't exist:
312 # create requested profiles by default, if they don't exist:
313 auto_create = Bool(True)
313 auto_create = Bool(True)
314
314
315 # file to be opened in the notebook server
315 # file to be opened in the notebook server
316 file_to_run = Unicode('')
316 file_to_run = Unicode('')
317
317
318 # Network related information.
318 # Network related information.
319
319
320 ip = Unicode(LOCALHOST, config=True,
320 ip = Unicode(LOCALHOST, config=True,
321 help="The IP address the notebook server will listen on."
321 help="The IP address the notebook server will listen on."
322 )
322 )
323
323
324 def _ip_changed(self, name, old, new):
324 def _ip_changed(self, name, old, new):
325 if new == u'*': self.ip = u''
325 if new == u'*': self.ip = u''
326
326
327 port = Integer(8888, config=True,
327 port = Integer(8888, config=True,
328 help="The port the notebook server will listen on."
328 help="The port the notebook server will listen on."
329 )
329 )
330 port_retries = Integer(50, config=True,
330 port_retries = Integer(50, config=True,
331 help="The number of additional ports to try if the specified port is not available."
331 help="The number of additional ports to try if the specified port is not available."
332 )
332 )
333
333
334 certfile = Unicode(u'', config=True,
334 certfile = Unicode(u'', config=True,
335 help="""The full path to an SSL/TLS certificate file."""
335 help="""The full path to an SSL/TLS certificate file."""
336 )
336 )
337
337
338 keyfile = Unicode(u'', config=True,
338 keyfile = Unicode(u'', config=True,
339 help="""The full path to a private key file for usage with SSL/TLS."""
339 help="""The full path to a private key file for usage with SSL/TLS."""
340 )
340 )
341
341
342 cookie_secret = Bytes(b'', config=True,
342 cookie_secret = Bytes(b'', config=True,
343 help="""The random bytes used to secure cookies.
343 help="""The random bytes used to secure cookies.
344 By default this is a new random number every time you start the Notebook.
344 By default this is a new random number every time you start the Notebook.
345 Set it to a value in a config file to enable logins to persist across server sessions.
345 Set it to a value in a config file to enable logins to persist across server sessions.
346
346
347 Note: Cookie secrets should be kept private, do not share config files with
347 Note: Cookie secrets should be kept private, do not share config files with
348 cookie_secret stored in plaintext (you can read the value from a file).
348 cookie_secret stored in plaintext (you can read the value from a file).
349 """
349 """
350 )
350 )
351 def _cookie_secret_default(self):
351 def _cookie_secret_default(self):
352 return os.urandom(1024)
352 return os.urandom(1024)
353
353
354 password = Unicode(u'', config=True,
354 password = Unicode(u'', config=True,
355 help="""Hashed password to use for web authentication.
355 help="""Hashed password to use for web authentication.
356
356
357 To generate, type in a python/IPython shell:
357 To generate, type in a python/IPython shell:
358
358
359 from IPython.lib import passwd; passwd()
359 from IPython.lib import passwd; passwd()
360
360
361 The string should be of the form type:salt:hashed-password.
361 The string should be of the form type:salt:hashed-password.
362 """
362 """
363 )
363 )
364
364
365 open_browser = Bool(True, config=True,
365 open_browser = Bool(True, config=True,
366 help="""Whether to open in a browser after starting.
366 help="""Whether to open in a browser after starting.
367 The specific browser used is platform dependent and
367 The specific browser used is platform dependent and
368 determined by the python standard library `webbrowser`
368 determined by the python standard library `webbrowser`
369 module, unless it is overridden using the --browser
369 module, unless it is overridden using the --browser
370 (NotebookApp.browser) configuration option.
370 (NotebookApp.browser) configuration option.
371 """)
371 """)
372
372
373 browser = Unicode(u'', config=True,
373 browser = Unicode(u'', config=True,
374 help="""Specify what command to use to invoke a web
374 help="""Specify what command to use to invoke a web
375 browser when opening the notebook. If not specified, the
375 browser when opening the notebook. If not specified, the
376 default browser will be determined by the `webbrowser`
376 default browser will be determined by the `webbrowser`
377 standard library module, which allows setting of the
377 standard library module, which allows setting of the
378 BROWSER environment variable to override it.
378 BROWSER environment variable to override it.
379 """)
379 """)
380
380
381 read_only = Bool(False, config=True,
381 read_only = Bool(False, config=True,
382 help="Whether to prevent editing/execution of notebooks."
382 help="Whether to prevent editing/execution of notebooks."
383 )
383 )
384
384
385 use_less = Bool(False, config=True,
385 use_less = Bool(False, config=True,
386 help="""Wether to use Browser Side less-css parsing
386 help="""Wether to use Browser Side less-css parsing
387 instead of compiled css version in templates that allows
387 instead of compiled css version in templates that allows
388 it. This is mainly convenient when working on the less
388 it. This is mainly convenient when working on the less
389 file to avoid a build step, or if user want to overwrite
389 file to avoid a build step, or if user want to overwrite
390 some of the less variables without having to recompile
390 some of the less variables without having to recompile
391 everything.
391 everything.
392
392
393 You will need to install the less.js component in the static directory
393 You will need to install the less.js component in the static directory
394 either in the source tree or in your profile folder.
394 either in the source tree or in your profile folder.
395 """)
395 """)
396
396
397 webapp_settings = Dict(config=True,
397 webapp_settings = Dict(config=True,
398 help="Supply overrides for the tornado.web.Application that the "
398 help="Supply overrides for the tornado.web.Application that the "
399 "IPython notebook uses.")
399 "IPython notebook uses.")
400
400
401 enable_mathjax = Bool(True, config=True,
401 enable_mathjax = Bool(True, config=True,
402 help="""Whether to enable MathJax for typesetting math/TeX
402 help="""Whether to enable MathJax for typesetting math/TeX
403
403
404 MathJax is the javascript library IPython uses to render math/LaTeX. It is
404 MathJax is the javascript library IPython uses to render math/LaTeX. It is
405 very large, so you may want to disable it if you have a slow internet
405 very large, so you may want to disable it if you have a slow internet
406 connection, or for offline use of the notebook.
406 connection, or for offline use of the notebook.
407
407
408 When disabled, equations etc. will appear as their untransformed TeX source.
408 When disabled, equations etc. will appear as their untransformed TeX source.
409 """
409 """
410 )
410 )
411 def _enable_mathjax_changed(self, name, old, new):
411 def _enable_mathjax_changed(self, name, old, new):
412 """set mathjax url to empty if mathjax is disabled"""
412 """set mathjax url to empty if mathjax is disabled"""
413 if not new:
413 if not new:
414 self.mathjax_url = u''
414 self.mathjax_url = u''
415
415
416 base_project_url = Unicode('/', config=True,
416 base_project_url = Unicode('/', config=True,
417 help='''The base URL for the notebook server.
417 help='''The base URL for the notebook server.
418
418
419 Leading and trailing slashes can be omitted,
419 Leading and trailing slashes can be omitted,
420 and will automatically be added.
420 and will automatically be added.
421 ''')
421 ''')
422 def _base_project_url_changed(self, name, old, new):
422 def _base_project_url_changed(self, name, old, new):
423 if not new.startswith('/'):
423 if not new.startswith('/'):
424 self.base_project_url = '/'+new
424 self.base_project_url = '/'+new
425 elif not new.endswith('/'):
425 elif not new.endswith('/'):
426 self.base_project_url = new+'/'
426 self.base_project_url = new+'/'
427
427
428 base_kernel_url = Unicode('/', config=True,
428 base_kernel_url = Unicode('/', config=True,
429 help='''The base URL for the kernel server
429 help='''The base URL for the kernel server
430
430
431 Leading and trailing slashes can be omitted,
431 Leading and trailing slashes can be omitted,
432 and will automatically be added.
432 and will automatically be added.
433 ''')
433 ''')
434 def _base_kernel_url_changed(self, name, old, new):
434 def _base_kernel_url_changed(self, name, old, new):
435 if not new.startswith('/'):
435 if not new.startswith('/'):
436 self.base_kernel_url = '/'+new
436 self.base_kernel_url = '/'+new
437 elif not new.endswith('/'):
437 elif not new.endswith('/'):
438 self.base_kernel_url = new+'/'
438 self.base_kernel_url = new+'/'
439
439
440 websocket_url = Unicode("", config=True,
440 websocket_url = Unicode("", config=True,
441 help="""The base URL for the websocket server,
441 help="""The base URL for the websocket server,
442 if it differs from the HTTP server (hint: it almost certainly doesn't).
442 if it differs from the HTTP server (hint: it almost certainly doesn't).
443
443
444 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
444 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
445 """
445 """
446 )
446 )
447
447
448 extra_static_paths = List(Unicode, config=True,
448 extra_static_paths = List(Unicode, config=True,
449 help="""Extra paths to search for serving static files.
449 help="""Extra paths to search for serving static files.
450
450
451 This allows adding javascript/css to be available from the notebook server machine,
451 This allows adding javascript/css to be available from the notebook server machine,
452 or overriding individual files in the IPython"""
452 or overriding individual files in the IPython"""
453 )
453 )
454 def _extra_static_paths_default(self):
454 def _extra_static_paths_default(self):
455 return [os.path.join(self.profile_dir.location, 'static')]
455 return [os.path.join(self.profile_dir.location, 'static')]
456
456
457 @property
457 @property
458 def static_file_path(self):
458 def static_file_path(self):
459 """return extra paths + the default location"""
459 """return extra paths + the default location"""
460 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
460 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
461
461
462 mathjax_url = Unicode("", config=True,
462 mathjax_url = Unicode("", config=True,
463 help="""The url for MathJax.js."""
463 help="""The url for MathJax.js."""
464 )
464 )
465 def _mathjax_url_default(self):
465 def _mathjax_url_default(self):
466 if not self.enable_mathjax:
466 if not self.enable_mathjax:
467 return u''
467 return u''
468 static_url_prefix = self.webapp_settings.get("static_url_prefix",
468 static_url_prefix = self.webapp_settings.get("static_url_prefix",
469 "/static/")
469 "/static/")
470 try:
470 try:
471 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
471 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
472 except IOError:
472 except IOError:
473 if self.certfile:
473 if self.certfile:
474 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
474 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
475 base = u"https://c328740.ssl.cf1.rackcdn.com"
475 base = u"https://c328740.ssl.cf1.rackcdn.com"
476 else:
476 else:
477 base = u"http://cdn.mathjax.org"
477 base = u"http://cdn.mathjax.org"
478
478
479 url = base + u"/mathjax/latest/MathJax.js"
479 url = base + u"/mathjax/latest/MathJax.js"
480 self.log.info("Using MathJax from CDN: %s", url)
480 self.log.info("Using MathJax from CDN: %s", url)
481 return url
481 return url
482 else:
482 else:
483 self.log.info("Using local MathJax from %s" % mathjax)
483 self.log.info("Using local MathJax from %s" % mathjax)
484 return static_url_prefix+u"mathjax/MathJax.js"
484 return static_url_prefix+u"mathjax/MathJax.js"
485
485
486 def _mathjax_url_changed(self, name, old, new):
486 def _mathjax_url_changed(self, name, old, new):
487 if new and not self.enable_mathjax:
487 if new and not self.enable_mathjax:
488 # enable_mathjax=False overrides mathjax_url
488 # enable_mathjax=False overrides mathjax_url
489 self.mathjax_url = u''
489 self.mathjax_url = u''
490 else:
490 else:
491 self.log.info("Using MathJax: %s", new)
491 self.log.info("Using MathJax: %s", new)
492
492
493 notebook_manager_class = DottedObjectName('IPython.html.notebook.services.notebooks.filenbmanager.FileNotebookManager',
493 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
494 config=True,
494 config=True,
495 help='The notebook manager class to use.')
495 help='The notebook manager class to use.')
496
496
497 trust_xheaders = Bool(False, config=True,
497 trust_xheaders = Bool(False, config=True,
498 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
498 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
499 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
499 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
500 )
500 )
501
501
502 def parse_command_line(self, argv=None):
502 def parse_command_line(self, argv=None):
503 super(NotebookApp, self).parse_command_line(argv)
503 super(NotebookApp, self).parse_command_line(argv)
504 if argv is None:
504 if argv is None:
505 argv = sys.argv[1:]
505 argv = sys.argv[1:]
506
506
507 # Scrub frontend-specific flags
507 # Scrub frontend-specific flags
508 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
508 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
509 # Kernel should inherit default config file from frontend
509 # Kernel should inherit default config file from frontend
510 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
510 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
511
511
512 if self.extra_args:
512 if self.extra_args:
513 f = os.path.abspath(self.extra_args[0])
513 f = os.path.abspath(self.extra_args[0])
514 if os.path.isdir(f):
514 if os.path.isdir(f):
515 nbdir = f
515 nbdir = f
516 else:
516 else:
517 self.file_to_run = f
517 self.file_to_run = f
518 nbdir = os.path.dirname(f)
518 nbdir = os.path.dirname(f)
519 self.config.NotebookManager.notebook_dir = nbdir
519 self.config.NotebookManager.notebook_dir = nbdir
520
520
521 def init_configurables(self):
521 def init_configurables(self):
522 # force Session default to be secure
522 # force Session default to be secure
523 default_secure(self.config)
523 default_secure(self.config)
524 self.kernel_manager = MappingKernelManager(
524 self.kernel_manager = MappingKernelManager(
525 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
525 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
526 connection_dir = self.profile_dir.security_dir,
526 connection_dir = self.profile_dir.security_dir,
527 )
527 )
528 kls = import_item(self.notebook_manager_class)
528 kls = import_item(self.notebook_manager_class)
529 self.notebook_manager = kls(config=self.config, log=self.log)
529 self.notebook_manager = kls(config=self.config, log=self.log)
530 self.notebook_manager.load_notebook_names()
530 self.notebook_manager.load_notebook_names()
531 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
531 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
532 self.cluster_manager.update_profiles()
532 self.cluster_manager.update_profiles()
533
533
534 def init_logging(self):
534 def init_logging(self):
535 # This prevents double log messages because tornado use a root logger that
535 # This prevents double log messages because tornado use a root logger that
536 # self.log is a child of. The logging module dipatches log messages to a log
536 # self.log is a child of. The logging module dipatches log messages to a log
537 # and all of its ancenstors until propagate is set to False.
537 # and all of its ancenstors until propagate is set to False.
538 self.log.propagate = False
538 self.log.propagate = False
539
539
540 # hook up tornado 3's loggers to our app handlers
540 # hook up tornado 3's loggers to our app handlers
541 for name in ('access', 'application', 'general'):
541 for name in ('access', 'application', 'general'):
542 logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
542 logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
543
543
544 def init_webapp(self):
544 def init_webapp(self):
545 """initialize tornado webapp and httpserver"""
545 """initialize tornado webapp and httpserver"""
546 self.web_app = NotebookWebApplication(
546 self.web_app = NotebookWebApplication(
547 self, self.kernel_manager, self.notebook_manager,
547 self, self.kernel_manager, self.notebook_manager,
548 self.cluster_manager, self.log,
548 self.cluster_manager, self.log,
549 self.base_project_url, self.webapp_settings
549 self.base_project_url, self.webapp_settings
550 )
550 )
551 if self.certfile:
551 if self.certfile:
552 ssl_options = dict(certfile=self.certfile)
552 ssl_options = dict(certfile=self.certfile)
553 if self.keyfile:
553 if self.keyfile:
554 ssl_options['keyfile'] = self.keyfile
554 ssl_options['keyfile'] = self.keyfile
555 else:
555 else:
556 ssl_options = None
556 ssl_options = None
557 self.web_app.password = self.password
557 self.web_app.password = self.password
558 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
558 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
559 xheaders=self.trust_xheaders)
559 xheaders=self.trust_xheaders)
560 if not self.ip:
560 if not self.ip:
561 warning = "WARNING: The notebook server is listening on all IP addresses"
561 warning = "WARNING: The notebook server is listening on all IP addresses"
562 if ssl_options is None:
562 if ssl_options is None:
563 self.log.critical(warning + " and not using encryption. This "
563 self.log.critical(warning + " and not using encryption. This "
564 "is not recommended.")
564 "is not recommended.")
565 if not self.password and not self.read_only:
565 if not self.password and not self.read_only:
566 self.log.critical(warning + " and not using authentication. "
566 self.log.critical(warning + " and not using authentication. "
567 "This is highly insecure and not recommended.")
567 "This is highly insecure and not recommended.")
568 success = None
568 success = None
569 for port in random_ports(self.port, self.port_retries+1):
569 for port in random_ports(self.port, self.port_retries+1):
570 try:
570 try:
571 self.http_server.listen(port, self.ip)
571 self.http_server.listen(port, self.ip)
572 except socket.error as e:
572 except socket.error as e:
573 # XXX: remove the e.errno == -9 block when we require
573 # XXX: remove the e.errno == -9 block when we require
574 # tornado >= 3.0
574 # tornado >= 3.0
575 if e.errno == -9 and tornado.version_info[0] < 3:
575 if e.errno == -9 and tornado.version_info[0] < 3:
576 # The flags passed to socket.getaddrinfo from
576 # The flags passed to socket.getaddrinfo from
577 # tornado.netutils.bind_sockets can cause "gaierror:
577 # tornado.netutils.bind_sockets can cause "gaierror:
578 # [Errno -9] Address family for hostname not supported"
578 # [Errno -9] Address family for hostname not supported"
579 # when the interface is not associated, for example.
579 # when the interface is not associated, for example.
580 # Changing the flags to exclude socket.AI_ADDRCONFIG does
580 # Changing the flags to exclude socket.AI_ADDRCONFIG does
581 # not cause this error, but the only way to do this is to
581 # not cause this error, but the only way to do this is to
582 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
582 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
583 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
583 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
584 self.log.warn('Monkeypatching socket to fix tornado bug')
584 self.log.warn('Monkeypatching socket to fix tornado bug')
585 del(socket.AI_ADDRCONFIG)
585 del(socket.AI_ADDRCONFIG)
586 try:
586 try:
587 # retry the tornado call without AI_ADDRCONFIG flags
587 # retry the tornado call without AI_ADDRCONFIG flags
588 self.http_server.listen(port, self.ip)
588 self.http_server.listen(port, self.ip)
589 except socket.error as e2:
589 except socket.error as e2:
590 e = e2
590 e = e2
591 else:
591 else:
592 self.port = port
592 self.port = port
593 success = True
593 success = True
594 break
594 break
595 # restore the monekypatch
595 # restore the monekypatch
596 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
596 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
597 if e.errno != errno.EADDRINUSE:
597 if e.errno != errno.EADDRINUSE:
598 raise
598 raise
599 self.log.info('The port %i is already in use, trying another random port.' % port)
599 self.log.info('The port %i is already in use, trying another random port.' % port)
600 else:
600 else:
601 self.port = port
601 self.port = port
602 success = True
602 success = True
603 break
603 break
604 if not success:
604 if not success:
605 self.log.critical('ERROR: the notebook server could not be started because '
605 self.log.critical('ERROR: the notebook server could not be started because '
606 'no available port could be found.')
606 'no available port could be found.')
607 self.exit(1)
607 self.exit(1)
608
608
609 def init_signal(self):
609 def init_signal(self):
610 if not sys.platform.startswith('win'):
610 if not sys.platform.startswith('win'):
611 signal.signal(signal.SIGINT, self._handle_sigint)
611 signal.signal(signal.SIGINT, self._handle_sigint)
612 signal.signal(signal.SIGTERM, self._signal_stop)
612 signal.signal(signal.SIGTERM, self._signal_stop)
613 if hasattr(signal, 'SIGUSR1'):
613 if hasattr(signal, 'SIGUSR1'):
614 # Windows doesn't support SIGUSR1
614 # Windows doesn't support SIGUSR1
615 signal.signal(signal.SIGUSR1, self._signal_info)
615 signal.signal(signal.SIGUSR1, self._signal_info)
616 if hasattr(signal, 'SIGINFO'):
616 if hasattr(signal, 'SIGINFO'):
617 # only on BSD-based systems
617 # only on BSD-based systems
618 signal.signal(signal.SIGINFO, self._signal_info)
618 signal.signal(signal.SIGINFO, self._signal_info)
619
619
620 def _handle_sigint(self, sig, frame):
620 def _handle_sigint(self, sig, frame):
621 """SIGINT handler spawns confirmation dialog"""
621 """SIGINT handler spawns confirmation dialog"""
622 # register more forceful signal handler for ^C^C case
622 # register more forceful signal handler for ^C^C case
623 signal.signal(signal.SIGINT, self._signal_stop)
623 signal.signal(signal.SIGINT, self._signal_stop)
624 # request confirmation dialog in bg thread, to avoid
624 # request confirmation dialog in bg thread, to avoid
625 # blocking the App
625 # blocking the App
626 thread = threading.Thread(target=self._confirm_exit)
626 thread = threading.Thread(target=self._confirm_exit)
627 thread.daemon = True
627 thread.daemon = True
628 thread.start()
628 thread.start()
629
629
630 def _restore_sigint_handler(self):
630 def _restore_sigint_handler(self):
631 """callback for restoring original SIGINT handler"""
631 """callback for restoring original SIGINT handler"""
632 signal.signal(signal.SIGINT, self._handle_sigint)
632 signal.signal(signal.SIGINT, self._handle_sigint)
633
633
634 def _confirm_exit(self):
634 def _confirm_exit(self):
635 """confirm shutdown on ^C
635 """confirm shutdown on ^C
636
636
637 A second ^C, or answering 'y' within 5s will cause shutdown,
637 A second ^C, or answering 'y' within 5s will cause shutdown,
638 otherwise original SIGINT handler will be restored.
638 otherwise original SIGINT handler will be restored.
639
639
640 This doesn't work on Windows.
640 This doesn't work on Windows.
641 """
641 """
642 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
642 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
643 time.sleep(0.1)
643 time.sleep(0.1)
644 info = self.log.info
644 info = self.log.info
645 info('interrupted')
645 info('interrupted')
646 print self.notebook_info()
646 print self.notebook_info()
647 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
647 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
648 sys.stdout.flush()
648 sys.stdout.flush()
649 r,w,x = select.select([sys.stdin], [], [], 5)
649 r,w,x = select.select([sys.stdin], [], [], 5)
650 if r:
650 if r:
651 line = sys.stdin.readline()
651 line = sys.stdin.readline()
652 if line.lower().startswith('y'):
652 if line.lower().startswith('y'):
653 self.log.critical("Shutdown confirmed")
653 self.log.critical("Shutdown confirmed")
654 ioloop.IOLoop.instance().stop()
654 ioloop.IOLoop.instance().stop()
655 return
655 return
656 else:
656 else:
657 print "No answer for 5s:",
657 print "No answer for 5s:",
658 print "resuming operation..."
658 print "resuming operation..."
659 # no answer, or answer is no:
659 # no answer, or answer is no:
660 # set it back to original SIGINT handler
660 # set it back to original SIGINT handler
661 # use IOLoop.add_callback because signal.signal must be called
661 # use IOLoop.add_callback because signal.signal must be called
662 # from main thread
662 # from main thread
663 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
663 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
664
664
665 def _signal_stop(self, sig, frame):
665 def _signal_stop(self, sig, frame):
666 self.log.critical("received signal %s, stopping", sig)
666 self.log.critical("received signal %s, stopping", sig)
667 ioloop.IOLoop.instance().stop()
667 ioloop.IOLoop.instance().stop()
668
668
669 def _signal_info(self, sig, frame):
669 def _signal_info(self, sig, frame):
670 print self.notebook_info()
670 print self.notebook_info()
671
671
672 def init_components(self):
672 def init_components(self):
673 """Check the components submodule, and warn if it's unclean"""
673 """Check the components submodule, and warn if it's unclean"""
674 status = submodule.check_submodule_status()
674 status = submodule.check_submodule_status()
675 if status == 'missing':
675 if status == 'missing':
676 self.log.warn("components submodule missing, running `git submodule update`")
676 self.log.warn("components submodule missing, running `git submodule update`")
677 submodule.update_submodules(submodule.ipython_parent())
677 submodule.update_submodules(submodule.ipython_parent())
678 elif status == 'unclean':
678 elif status == 'unclean':
679 self.log.warn("components submodule unclean, you may see 404s on static/components")
679 self.log.warn("components submodule unclean, you may see 404s on static/components")
680 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
680 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
681
681
682
682
683 @catch_config_error
683 @catch_config_error
684 def initialize(self, argv=None):
684 def initialize(self, argv=None):
685 self.init_logging()
685 self.init_logging()
686 super(NotebookApp, self).initialize(argv)
686 super(NotebookApp, self).initialize(argv)
687 self.init_configurables()
687 self.init_configurables()
688 self.init_components()
688 self.init_components()
689 self.init_webapp()
689 self.init_webapp()
690 self.init_signal()
690 self.init_signal()
691
691
692 def cleanup_kernels(self):
692 def cleanup_kernels(self):
693 """Shutdown all kernels.
693 """Shutdown all kernels.
694
694
695 The kernels will shutdown themselves when this process no longer exists,
695 The kernels will shutdown themselves when this process no longer exists,
696 but explicit shutdown allows the KernelManagers to cleanup the connection files.
696 but explicit shutdown allows the KernelManagers to cleanup the connection files.
697 """
697 """
698 self.log.info('Shutting down kernels')
698 self.log.info('Shutting down kernels')
699 self.kernel_manager.shutdown_all()
699 self.kernel_manager.shutdown_all()
700
700
701 def notebook_info(self):
701 def notebook_info(self):
702 "Return the current working directory and the server url information"
702 "Return the current working directory and the server url information"
703 mgr_info = self.notebook_manager.info_string() + "\n"
703 mgr_info = self.notebook_manager.info_string() + "\n"
704 return mgr_info +"The IPython Notebook is running at: %s" % self._url
704 return mgr_info +"The IPython Notebook is running at: %s" % self._url
705
705
706 def start(self):
706 def start(self):
707 """ Start the IPython Notebook server app, after initialization
707 """ Start the IPython Notebook server app, after initialization
708
708
709 This method takes no arguments so all configuration and initialization
709 This method takes no arguments so all configuration and initialization
710 must be done prior to calling this method."""
710 must be done prior to calling this method."""
711 ip = self.ip if self.ip else '[all ip addresses on your system]'
711 ip = self.ip if self.ip else '[all ip addresses on your system]'
712 proto = 'https' if self.certfile else 'http'
712 proto = 'https' if self.certfile else 'http'
713 info = self.log.info
713 info = self.log.info
714 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
714 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
715 self.base_project_url)
715 self.base_project_url)
716 for line in self.notebook_info().split("\n"):
716 for line in self.notebook_info().split("\n"):
717 info(line)
717 info(line)
718 info("Use Control-C to stop this server and shut down all kernels.")
718 info("Use Control-C to stop this server and shut down all kernels.")
719
719
720 if self.open_browser or self.file_to_run:
720 if self.open_browser or self.file_to_run:
721 ip = self.ip or LOCALHOST
721 ip = self.ip or LOCALHOST
722 try:
722 try:
723 browser = webbrowser.get(self.browser or None)
723 browser = webbrowser.get(self.browser or None)
724 except webbrowser.Error as e:
724 except webbrowser.Error as e:
725 self.log.warn('No web browser found: %s.' % e)
725 self.log.warn('No web browser found: %s.' % e)
726 browser = None
726 browser = None
727
727
728 if self.file_to_run:
728 if self.file_to_run:
729 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
729 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
730 url = self.notebook_manager.rev_mapping.get(name, '')
730 url = self.notebook_manager.rev_mapping.get(name, '')
731 else:
731 else:
732 url = ''
732 url = ''
733 if browser:
733 if browser:
734 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
734 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
735 self.port, self.base_project_url, url), new=2)
735 self.port, self.base_project_url, url), new=2)
736 threading.Thread(target=b).start()
736 threading.Thread(target=b).start()
737 try:
737 try:
738 ioloop.IOLoop.instance().start()
738 ioloop.IOLoop.instance().start()
739 except KeyboardInterrupt:
739 except KeyboardInterrupt:
740 info("Interrupted...")
740 info("Interrupted...")
741 finally:
741 finally:
742 self.cleanup_kernels()
742 self.cleanup_kernels()
743
743
744
744
745 #-----------------------------------------------------------------------------
745 #-----------------------------------------------------------------------------
746 # Main entry point
746 # Main entry point
747 #-----------------------------------------------------------------------------
747 #-----------------------------------------------------------------------------
748
748
749 def launch_new_instance():
749 def launch_new_instance():
750 app = NotebookApp.instance()
750 app = NotebookApp.instance()
751 app.initialize()
751 app.initialize()
752 app.start()
752 app.start()
753
753
@@ -1,395 +1,395 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 The :class:`~IPython.core.application.Application` object for the command
4 The :class:`~IPython.core.application.Application` object for the command
5 line :command:`ipython` program.
5 line :command:`ipython` program.
6
6
7 Authors
7 Authors
8 -------
8 -------
9
9
10 * Brian Granger
10 * Brian Granger
11 * Fernando Perez
11 * Fernando Perez
12 * Min Ragan-Kelley
12 * Min Ragan-Kelley
13 """
13 """
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Copyright (C) 2008-2011 The IPython Development Team
16 # Copyright (C) 2008-2011 The IPython Development Team
17 #
17 #
18 # Distributed under the terms of the BSD License. The full license is in
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
19 # the file COPYING, distributed as part of this software.
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Imports
23 # Imports
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 from __future__ import absolute_import
26 from __future__ import absolute_import
27
27
28 import logging
28 import logging
29 import os
29 import os
30 import sys
30 import sys
31
31
32 from IPython.config.loader import (
32 from IPython.config.loader import (
33 Config, PyFileConfigLoader, ConfigFileNotFound
33 Config, PyFileConfigLoader, ConfigFileNotFound
34 )
34 )
35 from IPython.config.application import boolean_flag, catch_config_error
35 from IPython.config.application import boolean_flag, catch_config_error
36 from IPython.core import release
36 from IPython.core import release
37 from IPython.core import usage
37 from IPython.core import usage
38 from IPython.core.completer import IPCompleter
38 from IPython.core.completer import IPCompleter
39 from IPython.core.crashhandler import CrashHandler
39 from IPython.core.crashhandler import CrashHandler
40 from IPython.core.formatters import PlainTextFormatter
40 from IPython.core.formatters import PlainTextFormatter
41 from IPython.core.history import HistoryManager
41 from IPython.core.history import HistoryManager
42 from IPython.core.prompts import PromptManager
42 from IPython.core.prompts import PromptManager
43 from IPython.core.application import (
43 from IPython.core.application import (
44 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
44 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
45 )
45 )
46 from IPython.core.magics import ScriptMagics
46 from IPython.core.magics import ScriptMagics
47 from IPython.core.shellapp import (
47 from IPython.core.shellapp import (
48 InteractiveShellApp, shell_flags, shell_aliases
48 InteractiveShellApp, shell_flags, shell_aliases
49 )
49 )
50 from IPython.terminal.interactiveshell import TerminalInteractiveShell
50 from IPython.terminal.interactiveshell import TerminalInteractiveShell
51 from IPython.utils import warn
51 from IPython.utils import warn
52 from IPython.utils.path import get_ipython_dir, check_for_old_config
52 from IPython.utils.path import get_ipython_dir, check_for_old_config
53 from IPython.utils.traitlets import (
53 from IPython.utils.traitlets import (
54 Bool, List, Dict, CaselessStrEnum
54 Bool, List, Dict, CaselessStrEnum
55 )
55 )
56
56
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58 # Globals, utilities and helpers
58 # Globals, utilities and helpers
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60
60
61 #: The default config file name for this application.
61 #: The default config file name for this application.
62 default_config_file_name = u'ipython_config.py'
62 default_config_file_name = u'ipython_config.py'
63
63
64 _examples = """
64 _examples = """
65 ipython --pylab # start in pylab mode
65 ipython --pylab # start in pylab mode
66 ipython --pylab=qt # start in pylab mode with the qt4 backend
66 ipython --pylab=qt # start in pylab mode with the qt4 backend
67 ipython --log-level=DEBUG # set logging to DEBUG
67 ipython --log-level=DEBUG # set logging to DEBUG
68 ipython --profile=foo # start with profile foo
68 ipython --profile=foo # start with profile foo
69
69
70 ipython qtconsole # start the qtconsole GUI application
70 ipython qtconsole # start the qtconsole GUI application
71 ipython help qtconsole # show the help for the qtconsole subcmd
71 ipython help qtconsole # show the help for the qtconsole subcmd
72
72
73 ipython console # start the terminal-based console application
73 ipython console # start the terminal-based console application
74 ipython help console # show the help for the console subcmd
74 ipython help console # show the help for the console subcmd
75
75
76 ipython notebook # start the IPython notebook
76 ipython notebook # start the IPython notebook
77 ipython help notebook # show the help for the notebook subcmd
77 ipython help notebook # show the help for the notebook subcmd
78
78
79 ipython profile create foo # create profile foo w/ default config files
79 ipython profile create foo # create profile foo w/ default config files
80 ipython help profile # show the help for the profile subcmd
80 ipython help profile # show the help for the profile subcmd
81
81
82 ipython locate # print the path to the IPython directory
82 ipython locate # print the path to the IPython directory
83 ipython locate profile foo # print the path to the directory for profile `foo`
83 ipython locate profile foo # print the path to the directory for profile `foo`
84 """
84 """
85
85
86 #-----------------------------------------------------------------------------
86 #-----------------------------------------------------------------------------
87 # Crash handler for this application
87 # Crash handler for this application
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89
89
90 class IPAppCrashHandler(CrashHandler):
90 class IPAppCrashHandler(CrashHandler):
91 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
91 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
92
92
93 def __init__(self, app):
93 def __init__(self, app):
94 contact_name = release.author
94 contact_name = release.author
95 contact_email = release.author_email
95 contact_email = release.author_email
96 bug_tracker = 'https://github.com/ipython/ipython/issues'
96 bug_tracker = 'https://github.com/ipython/ipython/issues'
97 super(IPAppCrashHandler,self).__init__(
97 super(IPAppCrashHandler,self).__init__(
98 app, contact_name, contact_email, bug_tracker
98 app, contact_name, contact_email, bug_tracker
99 )
99 )
100
100
101 def make_report(self,traceback):
101 def make_report(self,traceback):
102 """Return a string containing a crash report."""
102 """Return a string containing a crash report."""
103
103
104 sec_sep = self.section_sep
104 sec_sep = self.section_sep
105 # Start with parent report
105 # Start with parent report
106 report = [super(IPAppCrashHandler, self).make_report(traceback)]
106 report = [super(IPAppCrashHandler, self).make_report(traceback)]
107 # Add interactive-specific info we may have
107 # Add interactive-specific info we may have
108 rpt_add = report.append
108 rpt_add = report.append
109 try:
109 try:
110 rpt_add(sec_sep+"History of session input:")
110 rpt_add(sec_sep+"History of session input:")
111 for line in self.app.shell.user_ns['_ih']:
111 for line in self.app.shell.user_ns['_ih']:
112 rpt_add(line)
112 rpt_add(line)
113 rpt_add('\n*** Last line of input (may not be in above history):\n')
113 rpt_add('\n*** Last line of input (may not be in above history):\n')
114 rpt_add(self.app.shell._last_input_line+'\n')
114 rpt_add(self.app.shell._last_input_line+'\n')
115 except:
115 except:
116 pass
116 pass
117
117
118 return ''.join(report)
118 return ''.join(report)
119
119
120 #-----------------------------------------------------------------------------
120 #-----------------------------------------------------------------------------
121 # Aliases and Flags
121 # Aliases and Flags
122 #-----------------------------------------------------------------------------
122 #-----------------------------------------------------------------------------
123 flags = dict(base_flags)
123 flags = dict(base_flags)
124 flags.update(shell_flags)
124 flags.update(shell_flags)
125 frontend_flags = {}
125 frontend_flags = {}
126 addflag = lambda *args: frontend_flags.update(boolean_flag(*args))
126 addflag = lambda *args: frontend_flags.update(boolean_flag(*args))
127 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
127 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
128 'Turn on auto editing of files with syntax errors.',
128 'Turn on auto editing of files with syntax errors.',
129 'Turn off auto editing of files with syntax errors.'
129 'Turn off auto editing of files with syntax errors.'
130 )
130 )
131 addflag('banner', 'TerminalIPythonApp.display_banner',
131 addflag('banner', 'TerminalIPythonApp.display_banner',
132 "Display a banner upon starting IPython.",
132 "Display a banner upon starting IPython.",
133 "Don't display a banner upon starting IPython."
133 "Don't display a banner upon starting IPython."
134 )
134 )
135 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
135 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
136 """Set to confirm when you try to exit IPython with an EOF (Control-D
136 """Set to confirm when you try to exit IPython with an EOF (Control-D
137 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
137 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
138 you can force a direct exit without any confirmation.""",
138 you can force a direct exit without any confirmation.""",
139 "Don't prompt the user when exiting."
139 "Don't prompt the user when exiting."
140 )
140 )
141 addflag('term-title', 'TerminalInteractiveShell.term_title',
141 addflag('term-title', 'TerminalInteractiveShell.term_title',
142 "Enable auto setting the terminal title.",
142 "Enable auto setting the terminal title.",
143 "Disable auto setting the terminal title."
143 "Disable auto setting the terminal title."
144 )
144 )
145 classic_config = Config()
145 classic_config = Config()
146 classic_config.InteractiveShell.cache_size = 0
146 classic_config.InteractiveShell.cache_size = 0
147 classic_config.PlainTextFormatter.pprint = False
147 classic_config.PlainTextFormatter.pprint = False
148 classic_config.PromptManager.in_template = '>>> '
148 classic_config.PromptManager.in_template = '>>> '
149 classic_config.PromptManager.in2_template = '... '
149 classic_config.PromptManager.in2_template = '... '
150 classic_config.PromptManager.out_template = ''
150 classic_config.PromptManager.out_template = ''
151 classic_config.InteractiveShell.separate_in = ''
151 classic_config.InteractiveShell.separate_in = ''
152 classic_config.InteractiveShell.separate_out = ''
152 classic_config.InteractiveShell.separate_out = ''
153 classic_config.InteractiveShell.separate_out2 = ''
153 classic_config.InteractiveShell.separate_out2 = ''
154 classic_config.InteractiveShell.colors = 'NoColor'
154 classic_config.InteractiveShell.colors = 'NoColor'
155 classic_config.InteractiveShell.xmode = 'Plain'
155 classic_config.InteractiveShell.xmode = 'Plain'
156
156
157 frontend_flags['classic']=(
157 frontend_flags['classic']=(
158 classic_config,
158 classic_config,
159 "Gives IPython a similar feel to the classic Python prompt."
159 "Gives IPython a similar feel to the classic Python prompt."
160 )
160 )
161 # # log doesn't make so much sense this way anymore
161 # # log doesn't make so much sense this way anymore
162 # paa('--log','-l',
162 # paa('--log','-l',
163 # action='store_true', dest='InteractiveShell.logstart',
163 # action='store_true', dest='InteractiveShell.logstart',
164 # help="Start logging to the default log file (./ipython_log.py).")
164 # help="Start logging to the default log file (./ipython_log.py).")
165 #
165 #
166 # # quick is harder to implement
166 # # quick is harder to implement
167 frontend_flags['quick']=(
167 frontend_flags['quick']=(
168 {'TerminalIPythonApp' : {'quick' : True}},
168 {'TerminalIPythonApp' : {'quick' : True}},
169 "Enable quick startup with no config files."
169 "Enable quick startup with no config files."
170 )
170 )
171
171
172 frontend_flags['i'] = (
172 frontend_flags['i'] = (
173 {'TerminalIPythonApp' : {'force_interact' : True}},
173 {'TerminalIPythonApp' : {'force_interact' : True}},
174 """If running code from the command line, become interactive afterwards.
174 """If running code from the command line, become interactive afterwards.
175 Note: can also be given simply as '-i.'"""
175 Note: can also be given simply as '-i.'"""
176 )
176 )
177 flags.update(frontend_flags)
177 flags.update(frontend_flags)
178
178
179 aliases = dict(base_aliases)
179 aliases = dict(base_aliases)
180 aliases.update(shell_aliases)
180 aliases.update(shell_aliases)
181
181
182 #-----------------------------------------------------------------------------
182 #-----------------------------------------------------------------------------
183 # Main classes and functions
183 # Main classes and functions
184 #-----------------------------------------------------------------------------
184 #-----------------------------------------------------------------------------
185
185
186
186
187 class LocateIPythonApp(BaseIPythonApplication):
187 class LocateIPythonApp(BaseIPythonApplication):
188 description = """print the path to the IPython dir"""
188 description = """print the path to the IPython dir"""
189 subcommands = Dict(dict(
189 subcommands = Dict(dict(
190 profile=('IPython.core.profileapp.ProfileLocate',
190 profile=('IPython.core.profileapp.ProfileLocate',
191 "print the path to an IPython profile directory",
191 "print the path to an IPython profile directory",
192 ),
192 ),
193 ))
193 ))
194 def start(self):
194 def start(self):
195 if self.subapp is not None:
195 if self.subapp is not None:
196 return self.subapp.start()
196 return self.subapp.start()
197 else:
197 else:
198 print self.ipython_dir
198 print self.ipython_dir
199
199
200
200
201 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
201 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
202 name = u'ipython'
202 name = u'ipython'
203 description = usage.cl_usage
203 description = usage.cl_usage
204 default_config_file_name = default_config_file_name
204 default_config_file_name = default_config_file_name
205 crash_handler_class = IPAppCrashHandler
205 crash_handler_class = IPAppCrashHandler
206 examples = _examples
206 examples = _examples
207
207
208 flags = Dict(flags)
208 flags = Dict(flags)
209 aliases = Dict(aliases)
209 aliases = Dict(aliases)
210 classes = List()
210 classes = List()
211 def _classes_default(self):
211 def _classes_default(self):
212 """This has to be in a method, for TerminalIPythonApp to be available."""
212 """This has to be in a method, for TerminalIPythonApp to be available."""
213 return [
213 return [
214 InteractiveShellApp, # ShellApp comes before TerminalApp, because
214 InteractiveShellApp, # ShellApp comes before TerminalApp, because
215 self.__class__, # it will also affect subclasses (e.g. QtConsole)
215 self.__class__, # it will also affect subclasses (e.g. QtConsole)
216 TerminalInteractiveShell,
216 TerminalInteractiveShell,
217 PromptManager,
217 PromptManager,
218 HistoryManager,
218 HistoryManager,
219 ProfileDir,
219 ProfileDir,
220 PlainTextFormatter,
220 PlainTextFormatter,
221 IPCompleter,
221 IPCompleter,
222 ScriptMagics,
222 ScriptMagics,
223 ]
223 ]
224
224
225 subcommands = Dict(dict(
225 subcommands = Dict(dict(
226 qtconsole=('IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp',
226 qtconsole=('IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp',
227 """Launch the IPython Qt Console."""
227 """Launch the IPython Qt Console."""
228 ),
228 ),
229 notebook=('IPython.html.notebook.notebookapp.NotebookApp',
229 notebook=('IPython.html.notebookapp.NotebookApp',
230 """Launch the IPython HTML Notebook Server."""
230 """Launch the IPython HTML Notebook Server."""
231 ),
231 ),
232 profile = ("IPython.core.profileapp.ProfileApp",
232 profile = ("IPython.core.profileapp.ProfileApp",
233 "Create and manage IPython profiles."
233 "Create and manage IPython profiles."
234 ),
234 ),
235 kernel = ("IPython.kernel.zmq.kernelapp.IPKernelApp",
235 kernel = ("IPython.kernel.zmq.kernelapp.IPKernelApp",
236 "Start a kernel without an attached frontend."
236 "Start a kernel without an attached frontend."
237 ),
237 ),
238 console=('IPython.terminal.console.app.ZMQTerminalIPythonApp',
238 console=('IPython.terminal.console.app.ZMQTerminalIPythonApp',
239 """Launch the IPython terminal-based Console."""
239 """Launch the IPython terminal-based Console."""
240 ),
240 ),
241 locate=('IPython.terminal.ipapp.LocateIPythonApp',
241 locate=('IPython.terminal.ipapp.LocateIPythonApp',
242 LocateIPythonApp.description
242 LocateIPythonApp.description
243 ),
243 ),
244 history=('IPython.core.historyapp.HistoryApp',
244 history=('IPython.core.historyapp.HistoryApp',
245 "Manage the IPython history database."
245 "Manage the IPython history database."
246 ),
246 ),
247 ))
247 ))
248
248
249 # *do* autocreate requested profile, but don't create the config file.
249 # *do* autocreate requested profile, but don't create the config file.
250 auto_create=Bool(True)
250 auto_create=Bool(True)
251 # configurables
251 # configurables
252 ignore_old_config=Bool(False, config=True,
252 ignore_old_config=Bool(False, config=True,
253 help="Suppress warning messages about legacy config files"
253 help="Suppress warning messages about legacy config files"
254 )
254 )
255 quick = Bool(False, config=True,
255 quick = Bool(False, config=True,
256 help="""Start IPython quickly by skipping the loading of config files."""
256 help="""Start IPython quickly by skipping the loading of config files."""
257 )
257 )
258 def _quick_changed(self, name, old, new):
258 def _quick_changed(self, name, old, new):
259 if new:
259 if new:
260 self.load_config_file = lambda *a, **kw: None
260 self.load_config_file = lambda *a, **kw: None
261 self.ignore_old_config=True
261 self.ignore_old_config=True
262
262
263 display_banner = Bool(True, config=True,
263 display_banner = Bool(True, config=True,
264 help="Whether to display a banner upon starting IPython."
264 help="Whether to display a banner upon starting IPython."
265 )
265 )
266
266
267 # if there is code of files to run from the cmd line, don't interact
267 # if there is code of files to run from the cmd line, don't interact
268 # unless the --i flag (App.force_interact) is true.
268 # unless the --i flag (App.force_interact) is true.
269 force_interact = Bool(False, config=True,
269 force_interact = Bool(False, config=True,
270 help="""If a command or file is given via the command-line,
270 help="""If a command or file is given via the command-line,
271 e.g. 'ipython foo.py"""
271 e.g. 'ipython foo.py"""
272 )
272 )
273 def _force_interact_changed(self, name, old, new):
273 def _force_interact_changed(self, name, old, new):
274 if new:
274 if new:
275 self.interact = True
275 self.interact = True
276
276
277 def _file_to_run_changed(self, name, old, new):
277 def _file_to_run_changed(self, name, old, new):
278 if new:
278 if new:
279 self.something_to_run = True
279 self.something_to_run = True
280 if new and not self.force_interact:
280 if new and not self.force_interact:
281 self.interact = False
281 self.interact = False
282 _code_to_run_changed = _file_to_run_changed
282 _code_to_run_changed = _file_to_run_changed
283 _module_to_run_changed = _file_to_run_changed
283 _module_to_run_changed = _file_to_run_changed
284
284
285 # internal, not-configurable
285 # internal, not-configurable
286 interact=Bool(True)
286 interact=Bool(True)
287 something_to_run=Bool(False)
287 something_to_run=Bool(False)
288
288
289 def parse_command_line(self, argv=None):
289 def parse_command_line(self, argv=None):
290 """override to allow old '-pylab' flag with deprecation warning"""
290 """override to allow old '-pylab' flag with deprecation warning"""
291
291
292 argv = sys.argv[1:] if argv is None else argv
292 argv = sys.argv[1:] if argv is None else argv
293
293
294 if '-pylab' in argv:
294 if '-pylab' in argv:
295 # deprecated `-pylab` given,
295 # deprecated `-pylab` given,
296 # warn and transform into current syntax
296 # warn and transform into current syntax
297 argv = argv[:] # copy, don't clobber
297 argv = argv[:] # copy, don't clobber
298 idx = argv.index('-pylab')
298 idx = argv.index('-pylab')
299 warn.warn("`-pylab` flag has been deprecated.\n"
299 warn.warn("`-pylab` flag has been deprecated.\n"
300 " Use `--pylab` instead, or `--pylab=foo` to specify a backend.")
300 " Use `--pylab` instead, or `--pylab=foo` to specify a backend.")
301 sub = '--pylab'
301 sub = '--pylab'
302 if len(argv) > idx+1:
302 if len(argv) > idx+1:
303 # check for gui arg, as in '-pylab qt'
303 # check for gui arg, as in '-pylab qt'
304 gui = argv[idx+1]
304 gui = argv[idx+1]
305 if gui in ('wx', 'qt', 'qt4', 'gtk', 'auto'):
305 if gui in ('wx', 'qt', 'qt4', 'gtk', 'auto'):
306 sub = '--pylab='+gui
306 sub = '--pylab='+gui
307 argv.pop(idx+1)
307 argv.pop(idx+1)
308 argv[idx] = sub
308 argv[idx] = sub
309
309
310 return super(TerminalIPythonApp, self).parse_command_line(argv)
310 return super(TerminalIPythonApp, self).parse_command_line(argv)
311
311
312 @catch_config_error
312 @catch_config_error
313 def initialize(self, argv=None):
313 def initialize(self, argv=None):
314 """Do actions after construct, but before starting the app."""
314 """Do actions after construct, but before starting the app."""
315 super(TerminalIPythonApp, self).initialize(argv)
315 super(TerminalIPythonApp, self).initialize(argv)
316 if self.subapp is not None:
316 if self.subapp is not None:
317 # don't bother initializing further, starting subapp
317 # don't bother initializing further, starting subapp
318 return
318 return
319 if not self.ignore_old_config:
319 if not self.ignore_old_config:
320 check_for_old_config(self.ipython_dir)
320 check_for_old_config(self.ipython_dir)
321 # print self.extra_args
321 # print self.extra_args
322 if self.extra_args and not self.something_to_run:
322 if self.extra_args and not self.something_to_run:
323 self.file_to_run = self.extra_args[0]
323 self.file_to_run = self.extra_args[0]
324 self.init_path()
324 self.init_path()
325 # create the shell
325 # create the shell
326 self.init_shell()
326 self.init_shell()
327 # and draw the banner
327 # and draw the banner
328 self.init_banner()
328 self.init_banner()
329 # Now a variety of things that happen after the banner is printed.
329 # Now a variety of things that happen after the banner is printed.
330 self.init_gui_pylab()
330 self.init_gui_pylab()
331 self.init_extensions()
331 self.init_extensions()
332 self.init_code()
332 self.init_code()
333
333
334 def init_shell(self):
334 def init_shell(self):
335 """initialize the InteractiveShell instance"""
335 """initialize the InteractiveShell instance"""
336 # Create an InteractiveShell instance.
336 # Create an InteractiveShell instance.
337 # shell.display_banner should always be False for the terminal
337 # shell.display_banner should always be False for the terminal
338 # based app, because we call shell.show_banner() by hand below
338 # based app, because we call shell.show_banner() by hand below
339 # so the banner shows *before* all extension loading stuff.
339 # so the banner shows *before* all extension loading stuff.
340 self.shell = TerminalInteractiveShell.instance(config=self.config,
340 self.shell = TerminalInteractiveShell.instance(config=self.config,
341 display_banner=False, profile_dir=self.profile_dir,
341 display_banner=False, profile_dir=self.profile_dir,
342 ipython_dir=self.ipython_dir)
342 ipython_dir=self.ipython_dir)
343 self.shell.configurables.append(self)
343 self.shell.configurables.append(self)
344
344
345 def init_banner(self):
345 def init_banner(self):
346 """optionally display the banner"""
346 """optionally display the banner"""
347 if self.display_banner and self.interact:
347 if self.display_banner and self.interact:
348 self.shell.show_banner()
348 self.shell.show_banner()
349 # Make sure there is a space below the banner.
349 # Make sure there is a space below the banner.
350 if self.log_level <= logging.INFO: print
350 if self.log_level <= logging.INFO: print
351
351
352 def _pylab_changed(self, name, old, new):
352 def _pylab_changed(self, name, old, new):
353 """Replace --pylab='inline' with --pylab='auto'"""
353 """Replace --pylab='inline' with --pylab='auto'"""
354 if new == 'inline':
354 if new == 'inline':
355 warn.warn("'inline' not available as pylab backend, "
355 warn.warn("'inline' not available as pylab backend, "
356 "using 'auto' instead.")
356 "using 'auto' instead.")
357 self.pylab = 'auto'
357 self.pylab = 'auto'
358
358
359 def start(self):
359 def start(self):
360 if self.subapp is not None:
360 if self.subapp is not None:
361 return self.subapp.start()
361 return self.subapp.start()
362 # perform any prexec steps:
362 # perform any prexec steps:
363 if self.interact:
363 if self.interact:
364 self.log.debug("Starting IPython's mainloop...")
364 self.log.debug("Starting IPython's mainloop...")
365 self.shell.mainloop()
365 self.shell.mainloop()
366 else:
366 else:
367 self.log.debug("IPython not interactive...")
367 self.log.debug("IPython not interactive...")
368
368
369
369
370 def load_default_config(ipython_dir=None):
370 def load_default_config(ipython_dir=None):
371 """Load the default config file from the default ipython_dir.
371 """Load the default config file from the default ipython_dir.
372
372
373 This is useful for embedded shells.
373 This is useful for embedded shells.
374 """
374 """
375 if ipython_dir is None:
375 if ipython_dir is None:
376 ipython_dir = get_ipython_dir()
376 ipython_dir = get_ipython_dir()
377 profile_dir = os.path.join(ipython_dir, 'profile_default')
377 profile_dir = os.path.join(ipython_dir, 'profile_default')
378 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
378 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
379 try:
379 try:
380 config = cl.load_config()
380 config = cl.load_config()
381 except ConfigFileNotFound:
381 except ConfigFileNotFound:
382 # no config found
382 # no config found
383 config = Config()
383 config = Config()
384 return config
384 return config
385
385
386
386
387 def launch_new_instance():
387 def launch_new_instance():
388 """Create and run a full blown IPython instance"""
388 """Create and run a full blown IPython instance"""
389 app = TerminalIPythonApp.instance()
389 app = TerminalIPythonApp.instance()
390 app.initialize()
390 app.initialize()
391 app.start()
391 app.start()
392
392
393
393
394 if __name__ == '__main__':
394 if __name__ == '__main__':
395 launch_new_instance()
395 launch_new_instance()
@@ -1,610 +1,610 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """IPython Test Suite Runner.
2 """IPython Test Suite Runner.
3
3
4 This module provides a main entry point to a user script to test IPython
4 This module provides a main entry point to a user script to test IPython
5 itself from the command line. There are two ways of running this script:
5 itself from the command line. There are two ways of running this script:
6
6
7 1. With the syntax `iptest all`. This runs our entire test suite by
7 1. With the syntax `iptest all`. This runs our entire test suite by
8 calling this script (with different arguments) recursively. This
8 calling this script (with different arguments) recursively. This
9 causes modules and package to be tested in different processes, using nose
9 causes modules and package to be tested in different processes, using nose
10 or trial where appropriate.
10 or trial where appropriate.
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 the script simply calls nose, but with special command line flags and
12 the script simply calls nose, but with special command line flags and
13 plugins loaded.
13 plugins loaded.
14
14
15 """
15 """
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Copyright (C) 2009-2011 The IPython Development Team
18 # Copyright (C) 2009-2011 The IPython Development Team
19 #
19 #
20 # Distributed under the terms of the BSD License. The full license is in
20 # Distributed under the terms of the BSD License. The full license is in
21 # the file COPYING, distributed as part of this software.
21 # the file COPYING, distributed as part of this software.
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Imports
25 # Imports
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 from __future__ import print_function
27 from __future__ import print_function
28
28
29 # Stdlib
29 # Stdlib
30 import glob
30 import glob
31 import os
31 import os
32 import os.path as path
32 import os.path as path
33 import signal
33 import signal
34 import sys
34 import sys
35 import subprocess
35 import subprocess
36 import tempfile
36 import tempfile
37 import time
37 import time
38 import warnings
38 import warnings
39
39
40 # Note: monkeypatch!
40 # Note: monkeypatch!
41 # We need to monkeypatch a small problem in nose itself first, before importing
41 # We need to monkeypatch a small problem in nose itself first, before importing
42 # it for actual use. This should get into nose upstream, but its release cycle
42 # it for actual use. This should get into nose upstream, but its release cycle
43 # is slow and we need it for our parametric tests to work correctly.
43 # is slow and we need it for our parametric tests to work correctly.
44 from IPython.testing import nosepatch
44 from IPython.testing import nosepatch
45
45
46 # Monkeypatch extra assert methods into nose.tools if they're not already there.
46 # Monkeypatch extra assert methods into nose.tools if they're not already there.
47 # This can be dropped once we no longer test on Python 2.6
47 # This can be dropped once we no longer test on Python 2.6
48 from IPython.testing import nose_assert_methods
48 from IPython.testing import nose_assert_methods
49
49
50 # Now, proceed to import nose itself
50 # Now, proceed to import nose itself
51 import nose.plugins.builtin
51 import nose.plugins.builtin
52 from nose.plugins.xunit import Xunit
52 from nose.plugins.xunit import Xunit
53 from nose import SkipTest
53 from nose import SkipTest
54 from nose.core import TestProgram
54 from nose.core import TestProgram
55
55
56 # Our own imports
56 # Our own imports
57 from IPython.utils import py3compat
57 from IPython.utils import py3compat
58 from IPython.utils.importstring import import_item
58 from IPython.utils.importstring import import_item
59 from IPython.utils.path import get_ipython_module_path, get_ipython_package_dir
59 from IPython.utils.path import get_ipython_module_path, get_ipython_package_dir
60 from IPython.utils.process import find_cmd, pycmd2argv
60 from IPython.utils.process import find_cmd, pycmd2argv
61 from IPython.utils.sysinfo import sys_info
61 from IPython.utils.sysinfo import sys_info
62 from IPython.utils.tempdir import TemporaryDirectory
62 from IPython.utils.tempdir import TemporaryDirectory
63 from IPython.utils.warn import warn
63 from IPython.utils.warn import warn
64
64
65 from IPython.testing import globalipapp
65 from IPython.testing import globalipapp
66 from IPython.testing.plugin.ipdoctest import IPythonDoctest
66 from IPython.testing.plugin.ipdoctest import IPythonDoctest
67 from IPython.external.decorators import KnownFailure, knownfailureif
67 from IPython.external.decorators import KnownFailure, knownfailureif
68
68
69 pjoin = path.join
69 pjoin = path.join
70
70
71
71
72 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
73 # Globals
73 # Globals
74 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
75
75
76
76
77 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
78 # Warnings control
78 # Warnings control
79 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
80
80
81 # Twisted generates annoying warnings with Python 2.6, as will do other code
81 # Twisted generates annoying warnings with Python 2.6, as will do other code
82 # that imports 'sets' as of today
82 # that imports 'sets' as of today
83 warnings.filterwarnings('ignore', 'the sets module is deprecated',
83 warnings.filterwarnings('ignore', 'the sets module is deprecated',
84 DeprecationWarning )
84 DeprecationWarning )
85
85
86 # This one also comes from Twisted
86 # This one also comes from Twisted
87 warnings.filterwarnings('ignore', 'the sha module is deprecated',
87 warnings.filterwarnings('ignore', 'the sha module is deprecated',
88 DeprecationWarning)
88 DeprecationWarning)
89
89
90 # Wx on Fedora11 spits these out
90 # Wx on Fedora11 spits these out
91 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
91 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
92 UserWarning)
92 UserWarning)
93
93
94 # ------------------------------------------------------------------------------
94 # ------------------------------------------------------------------------------
95 # Monkeypatch Xunit to count known failures as skipped.
95 # Monkeypatch Xunit to count known failures as skipped.
96 # ------------------------------------------------------------------------------
96 # ------------------------------------------------------------------------------
97 def monkeypatch_xunit():
97 def monkeypatch_xunit():
98 try:
98 try:
99 knownfailureif(True)(lambda: None)()
99 knownfailureif(True)(lambda: None)()
100 except Exception as e:
100 except Exception as e:
101 KnownFailureTest = type(e)
101 KnownFailureTest = type(e)
102
102
103 def addError(self, test, err, capt=None):
103 def addError(self, test, err, capt=None):
104 if issubclass(err[0], KnownFailureTest):
104 if issubclass(err[0], KnownFailureTest):
105 err = (SkipTest,) + err[1:]
105 err = (SkipTest,) + err[1:]
106 return self.orig_addError(test, err, capt)
106 return self.orig_addError(test, err, capt)
107
107
108 Xunit.orig_addError = Xunit.addError
108 Xunit.orig_addError = Xunit.addError
109 Xunit.addError = addError
109 Xunit.addError = addError
110
110
111 #-----------------------------------------------------------------------------
111 #-----------------------------------------------------------------------------
112 # Logic for skipping doctests
112 # Logic for skipping doctests
113 #-----------------------------------------------------------------------------
113 #-----------------------------------------------------------------------------
114 def extract_version(mod):
114 def extract_version(mod):
115 return mod.__version__
115 return mod.__version__
116
116
117 def test_for(item, min_version=None, callback=extract_version):
117 def test_for(item, min_version=None, callback=extract_version):
118 """Test to see if item is importable, and optionally check against a minimum
118 """Test to see if item is importable, and optionally check against a minimum
119 version.
119 version.
120
120
121 If min_version is given, the default behavior is to check against the
121 If min_version is given, the default behavior is to check against the
122 `__version__` attribute of the item, but specifying `callback` allows you to
122 `__version__` attribute of the item, but specifying `callback` allows you to
123 extract the value you are interested in. e.g::
123 extract the value you are interested in. e.g::
124
124
125 In [1]: import sys
125 In [1]: import sys
126
126
127 In [2]: from IPython.testing.iptest import test_for
127 In [2]: from IPython.testing.iptest import test_for
128
128
129 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
129 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
130 Out[3]: True
130 Out[3]: True
131
131
132 """
132 """
133 try:
133 try:
134 check = import_item(item)
134 check = import_item(item)
135 except (ImportError, RuntimeError):
135 except (ImportError, RuntimeError):
136 # GTK reports Runtime error if it can't be initialized even if it's
136 # GTK reports Runtime error if it can't be initialized even if it's
137 # importable.
137 # importable.
138 return False
138 return False
139 else:
139 else:
140 if min_version:
140 if min_version:
141 if callback:
141 if callback:
142 # extra processing step to get version to compare
142 # extra processing step to get version to compare
143 check = callback(check)
143 check = callback(check)
144
144
145 return check >= min_version
145 return check >= min_version
146 else:
146 else:
147 return True
147 return True
148
148
149 # Global dict where we can store information on what we have and what we don't
149 # Global dict where we can store information on what we have and what we don't
150 # have available at test run time
150 # have available at test run time
151 have = {}
151 have = {}
152
152
153 have['curses'] = test_for('_curses')
153 have['curses'] = test_for('_curses')
154 have['matplotlib'] = test_for('matplotlib')
154 have['matplotlib'] = test_for('matplotlib')
155 have['numpy'] = test_for('numpy')
155 have['numpy'] = test_for('numpy')
156 have['pexpect'] = test_for('IPython.external.pexpect')
156 have['pexpect'] = test_for('IPython.external.pexpect')
157 have['pymongo'] = test_for('pymongo')
157 have['pymongo'] = test_for('pymongo')
158 have['pygments'] = test_for('pygments')
158 have['pygments'] = test_for('pygments')
159 have['qt'] = test_for('IPython.external.qt')
159 have['qt'] = test_for('IPython.external.qt')
160 have['rpy2'] = test_for('rpy2')
160 have['rpy2'] = test_for('rpy2')
161 have['sqlite3'] = test_for('sqlite3')
161 have['sqlite3'] = test_for('sqlite3')
162 have['cython'] = test_for('Cython')
162 have['cython'] = test_for('Cython')
163 have['oct2py'] = test_for('oct2py')
163 have['oct2py'] = test_for('oct2py')
164 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
164 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
165 have['jinja2'] = test_for('jinja2')
165 have['jinja2'] = test_for('jinja2')
166 have['wx'] = test_for('wx')
166 have['wx'] = test_for('wx')
167 have['wx.aui'] = test_for('wx.aui')
167 have['wx.aui'] = test_for('wx.aui')
168 have['azure'] = test_for('azure')
168 have['azure'] = test_for('azure')
169
169
170 min_zmq = (2,1,11)
170 min_zmq = (2,1,11)
171
171
172 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
172 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
173
173
174 #-----------------------------------------------------------------------------
174 #-----------------------------------------------------------------------------
175 # Functions and classes
175 # Functions and classes
176 #-----------------------------------------------------------------------------
176 #-----------------------------------------------------------------------------
177
177
178 def report():
178 def report():
179 """Return a string with a summary report of test-related variables."""
179 """Return a string with a summary report of test-related variables."""
180
180
181 out = [ sys_info(), '\n']
181 out = [ sys_info(), '\n']
182
182
183 avail = []
183 avail = []
184 not_avail = []
184 not_avail = []
185
185
186 for k, is_avail in have.items():
186 for k, is_avail in have.items():
187 if is_avail:
187 if is_avail:
188 avail.append(k)
188 avail.append(k)
189 else:
189 else:
190 not_avail.append(k)
190 not_avail.append(k)
191
191
192 if avail:
192 if avail:
193 out.append('\nTools and libraries available at test time:\n')
193 out.append('\nTools and libraries available at test time:\n')
194 avail.sort()
194 avail.sort()
195 out.append(' ' + ' '.join(avail)+'\n')
195 out.append(' ' + ' '.join(avail)+'\n')
196
196
197 if not_avail:
197 if not_avail:
198 out.append('\nTools and libraries NOT available at test time:\n')
198 out.append('\nTools and libraries NOT available at test time:\n')
199 not_avail.sort()
199 not_avail.sort()
200 out.append(' ' + ' '.join(not_avail)+'\n')
200 out.append(' ' + ' '.join(not_avail)+'\n')
201
201
202 return ''.join(out)
202 return ''.join(out)
203
203
204
204
205 def make_exclude():
205 def make_exclude():
206 """Make patterns of modules and packages to exclude from testing.
206 """Make patterns of modules and packages to exclude from testing.
207
207
208 For the IPythonDoctest plugin, we need to exclude certain patterns that
208 For the IPythonDoctest plugin, we need to exclude certain patterns that
209 cause testing problems. We should strive to minimize the number of
209 cause testing problems. We should strive to minimize the number of
210 skipped modules, since this means untested code.
210 skipped modules, since this means untested code.
211
211
212 These modules and packages will NOT get scanned by nose at all for tests.
212 These modules and packages will NOT get scanned by nose at all for tests.
213 """
213 """
214 # Simple utility to make IPython paths more readably, we need a lot of
214 # Simple utility to make IPython paths more readably, we need a lot of
215 # these below
215 # these below
216 ipjoin = lambda *paths: pjoin('IPython', *paths)
216 ipjoin = lambda *paths: pjoin('IPython', *paths)
217
217
218 exclusions = [ipjoin('external'),
218 exclusions = [ipjoin('external'),
219 ipjoin('quarantine'),
219 ipjoin('quarantine'),
220 ipjoin('deathrow'),
220 ipjoin('deathrow'),
221 # This guy is probably attic material
221 # This guy is probably attic material
222 ipjoin('testing', 'mkdoctests'),
222 ipjoin('testing', 'mkdoctests'),
223 # Testing inputhook will need a lot of thought, to figure out
223 # Testing inputhook will need a lot of thought, to figure out
224 # how to have tests that don't lock up with the gui event
224 # how to have tests that don't lock up with the gui event
225 # loops in the picture
225 # loops in the picture
226 ipjoin('lib', 'inputhook'),
226 ipjoin('lib', 'inputhook'),
227 # Config files aren't really importable stand-alone
227 # Config files aren't really importable stand-alone
228 ipjoin('config', 'profile'),
228 ipjoin('config', 'profile'),
229 # The notebook 'static' directory contains JS, css and other
229 # The notebook 'static' directory contains JS, css and other
230 # files for web serving. Occasionally projects may put a .py
230 # files for web serving. Occasionally projects may put a .py
231 # file in there (MathJax ships a conf.py), so we might as
231 # file in there (MathJax ships a conf.py), so we might as
232 # well play it safe and skip the whole thing.
232 # well play it safe and skip the whole thing.
233 ipjoin('html', 'notebook', 'static'),
233 ipjoin('html', 'static'),
234 ipjoin('html', 'notebook', 'fabfile'),
234 ipjoin('html', 'fabfile'),
235 ]
235 ]
236 if not have['sqlite3']:
236 if not have['sqlite3']:
237 exclusions.append(ipjoin('core', 'tests', 'test_history'))
237 exclusions.append(ipjoin('core', 'tests', 'test_history'))
238 exclusions.append(ipjoin('core', 'history'))
238 exclusions.append(ipjoin('core', 'history'))
239 if not have['wx']:
239 if not have['wx']:
240 exclusions.append(ipjoin('lib', 'inputhookwx'))
240 exclusions.append(ipjoin('lib', 'inputhookwx'))
241
241
242 if 'IPython.kernel.inprocess' not in sys.argv:
242 if 'IPython.kernel.inprocess' not in sys.argv:
243 exclusions.append(ipjoin('kernel', 'inprocess'))
243 exclusions.append(ipjoin('kernel', 'inprocess'))
244
244
245 # FIXME: temporarily disable autoreload tests, as they can produce
245 # FIXME: temporarily disable autoreload tests, as they can produce
246 # spurious failures in subsequent tests (cythonmagic).
246 # spurious failures in subsequent tests (cythonmagic).
247 exclusions.append(ipjoin('extensions', 'autoreload'))
247 exclusions.append(ipjoin('extensions', 'autoreload'))
248 exclusions.append(ipjoin('extensions', 'tests', 'test_autoreload'))
248 exclusions.append(ipjoin('extensions', 'tests', 'test_autoreload'))
249
249
250 # We do this unconditionally, so that the test suite doesn't import
250 # We do this unconditionally, so that the test suite doesn't import
251 # gtk, changing the default encoding and masking some unicode bugs.
251 # gtk, changing the default encoding and masking some unicode bugs.
252 exclusions.append(ipjoin('lib', 'inputhookgtk'))
252 exclusions.append(ipjoin('lib', 'inputhookgtk'))
253 exclusions.append(ipjoin('kernel', 'zmq', 'gui', 'gtkembed'))
253 exclusions.append(ipjoin('kernel', 'zmq', 'gui', 'gtkembed'))
254
254
255 # These have to be skipped on win32 because the use echo, rm, cd, etc.
255 # These have to be skipped on win32 because the use echo, rm, cd, etc.
256 # See ticket https://github.com/ipython/ipython/issues/87
256 # See ticket https://github.com/ipython/ipython/issues/87
257 if sys.platform == 'win32':
257 if sys.platform == 'win32':
258 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
258 exclusions.append(ipjoin('testing', 'plugin', 'test_exampleip'))
259 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
259 exclusions.append(ipjoin('testing', 'plugin', 'dtexample'))
260
260
261 if not have['pexpect']:
261 if not have['pexpect']:
262 exclusions.extend([ipjoin('lib', 'irunner'),
262 exclusions.extend([ipjoin('lib', 'irunner'),
263 ipjoin('lib', 'tests', 'test_irunner'),
263 ipjoin('lib', 'tests', 'test_irunner'),
264 ipjoin('terminal', 'console'),
264 ipjoin('terminal', 'console'),
265 ])
265 ])
266
266
267 if not have['zmq']:
267 if not have['zmq']:
268 exclusions.append(ipjoin('kernel'))
268 exclusions.append(ipjoin('kernel'))
269 exclusions.append(ipjoin('qt'))
269 exclusions.append(ipjoin('qt'))
270 exclusions.append(ipjoin('html'))
270 exclusions.append(ipjoin('html'))
271 exclusions.append(ipjoin('consoleapp.py'))
271 exclusions.append(ipjoin('consoleapp.py'))
272 exclusions.append(ipjoin('terminal', 'console'))
272 exclusions.append(ipjoin('terminal', 'console'))
273 exclusions.append(ipjoin('parallel'))
273 exclusions.append(ipjoin('parallel'))
274 elif not have['qt'] or not have['pygments']:
274 elif not have['qt'] or not have['pygments']:
275 exclusions.append(ipjoin('qt'))
275 exclusions.append(ipjoin('qt'))
276
276
277 if not have['pymongo']:
277 if not have['pymongo']:
278 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
278 exclusions.append(ipjoin('parallel', 'controller', 'mongodb'))
279 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
279 exclusions.append(ipjoin('parallel', 'tests', 'test_mongodb'))
280
280
281 if not have['matplotlib']:
281 if not have['matplotlib']:
282 exclusions.extend([ipjoin('core', 'pylabtools'),
282 exclusions.extend([ipjoin('core', 'pylabtools'),
283 ipjoin('core', 'tests', 'test_pylabtools'),
283 ipjoin('core', 'tests', 'test_pylabtools'),
284 ipjoin('kernel', 'zmq', 'pylab'),
284 ipjoin('kernel', 'zmq', 'pylab'),
285 ])
285 ])
286
286
287 if not have['cython']:
287 if not have['cython']:
288 exclusions.extend([ipjoin('extensions', 'cythonmagic')])
288 exclusions.extend([ipjoin('extensions', 'cythonmagic')])
289 exclusions.extend([ipjoin('extensions', 'tests', 'test_cythonmagic')])
289 exclusions.extend([ipjoin('extensions', 'tests', 'test_cythonmagic')])
290
290
291 if not have['oct2py']:
291 if not have['oct2py']:
292 exclusions.extend([ipjoin('extensions', 'octavemagic')])
292 exclusions.extend([ipjoin('extensions', 'octavemagic')])
293 exclusions.extend([ipjoin('extensions', 'tests', 'test_octavemagic')])
293 exclusions.extend([ipjoin('extensions', 'tests', 'test_octavemagic')])
294
294
295 if not have['tornado']:
295 if not have['tornado']:
296 exclusions.append(ipjoin('html'))
296 exclusions.append(ipjoin('html'))
297
297
298 if not have['jinja2']:
298 if not have['jinja2']:
299 exclusions.append(ipjoin('html', 'notebook', 'notebookapp'))
299 exclusions.append(ipjoin('html', 'notebookapp'))
300
300
301 if not have['rpy2'] or not have['numpy']:
301 if not have['rpy2'] or not have['numpy']:
302 exclusions.append(ipjoin('extensions', 'rmagic'))
302 exclusions.append(ipjoin('extensions', 'rmagic'))
303 exclusions.append(ipjoin('extensions', 'tests', 'test_rmagic'))
303 exclusions.append(ipjoin('extensions', 'tests', 'test_rmagic'))
304
304
305 if not have['azure']:
305 if not have['azure']:
306 exclusions.append(ipjoin('html', 'notebook', 'services', 'notebooks', 'azurenbmanager'))
306 exclusions.append(ipjoin('html', 'services', 'notebooks', 'azurenbmanager'))
307
307
308 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
308 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
309 if sys.platform == 'win32':
309 if sys.platform == 'win32':
310 exclusions = [s.replace('\\','\\\\') for s in exclusions]
310 exclusions = [s.replace('\\','\\\\') for s in exclusions]
311
311
312 # check for any exclusions that don't seem to exist:
312 # check for any exclusions that don't seem to exist:
313 parent, _ = os.path.split(get_ipython_package_dir())
313 parent, _ = os.path.split(get_ipython_package_dir())
314 for exclusion in exclusions:
314 for exclusion in exclusions:
315 if exclusion.endswith(('deathrow', 'quarantine')):
315 if exclusion.endswith(('deathrow', 'quarantine')):
316 # ignore deathrow/quarantine, which exist in dev, but not install
316 # ignore deathrow/quarantine, which exist in dev, but not install
317 continue
317 continue
318 fullpath = pjoin(parent, exclusion)
318 fullpath = pjoin(parent, exclusion)
319 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
319 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
320 warn("Excluding nonexistent file: %r" % exclusion)
320 warn("Excluding nonexistent file: %r" % exclusion)
321
321
322 return exclusions
322 return exclusions
323
323
324
324
325 class IPTester(object):
325 class IPTester(object):
326 """Call that calls iptest or trial in a subprocess.
326 """Call that calls iptest or trial in a subprocess.
327 """
327 """
328 #: string, name of test runner that will be called
328 #: string, name of test runner that will be called
329 runner = None
329 runner = None
330 #: list, parameters for test runner
330 #: list, parameters for test runner
331 params = None
331 params = None
332 #: list, arguments of system call to be made to call test runner
332 #: list, arguments of system call to be made to call test runner
333 call_args = None
333 call_args = None
334 #: list, subprocesses we start (for cleanup)
334 #: list, subprocesses we start (for cleanup)
335 processes = None
335 processes = None
336 #: str, coverage xml output file
336 #: str, coverage xml output file
337 coverage_xml = None
337 coverage_xml = None
338
338
339 def __init__(self, runner='iptest', params=None):
339 def __init__(self, runner='iptest', params=None):
340 """Create new test runner."""
340 """Create new test runner."""
341 p = os.path
341 p = os.path
342 if runner == 'iptest':
342 if runner == 'iptest':
343 iptest_app = get_ipython_module_path('IPython.testing.iptest')
343 iptest_app = get_ipython_module_path('IPython.testing.iptest')
344 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
344 self.runner = pycmd2argv(iptest_app) + sys.argv[1:]
345 else:
345 else:
346 raise Exception('Not a valid test runner: %s' % repr(runner))
346 raise Exception('Not a valid test runner: %s' % repr(runner))
347 if params is None:
347 if params is None:
348 params = []
348 params = []
349 if isinstance(params, str):
349 if isinstance(params, str):
350 params = [params]
350 params = [params]
351 self.params = params
351 self.params = params
352
352
353 # Assemble call
353 # Assemble call
354 self.call_args = self.runner+self.params
354 self.call_args = self.runner+self.params
355
355
356 # Find the section we're testing (IPython.foo)
356 # Find the section we're testing (IPython.foo)
357 for sect in self.params:
357 for sect in self.params:
358 if sect.startswith('IPython'): break
358 if sect.startswith('IPython'): break
359 else:
359 else:
360 raise ValueError("Section not found", self.params)
360 raise ValueError("Section not found", self.params)
361
361
362 if '--with-xunit' in self.call_args:
362 if '--with-xunit' in self.call_args:
363
363
364 self.call_args.append('--xunit-file')
364 self.call_args.append('--xunit-file')
365 # FIXME: when Windows uses subprocess.call, these extra quotes are unnecessary:
365 # FIXME: when Windows uses subprocess.call, these extra quotes are unnecessary:
366 xunit_file = path.abspath(sect+'.xunit.xml')
366 xunit_file = path.abspath(sect+'.xunit.xml')
367 if sys.platform == 'win32':
367 if sys.platform == 'win32':
368 xunit_file = '"%s"' % xunit_file
368 xunit_file = '"%s"' % xunit_file
369 self.call_args.append(xunit_file)
369 self.call_args.append(xunit_file)
370
370
371 if '--with-xml-coverage' in self.call_args:
371 if '--with-xml-coverage' in self.call_args:
372 self.coverage_xml = path.abspath(sect+".coverage.xml")
372 self.coverage_xml = path.abspath(sect+".coverage.xml")
373 self.call_args.remove('--with-xml-coverage')
373 self.call_args.remove('--with-xml-coverage')
374 self.call_args = ["coverage", "run", "--source="+sect] + self.call_args[1:]
374 self.call_args = ["coverage", "run", "--source="+sect] + self.call_args[1:]
375
375
376 # Store anything we start to clean up on deletion
376 # Store anything we start to clean up on deletion
377 self.processes = []
377 self.processes = []
378
378
379 def _run_cmd(self):
379 def _run_cmd(self):
380 with TemporaryDirectory() as IPYTHONDIR:
380 with TemporaryDirectory() as IPYTHONDIR:
381 env = os.environ.copy()
381 env = os.environ.copy()
382 env['IPYTHONDIR'] = IPYTHONDIR
382 env['IPYTHONDIR'] = IPYTHONDIR
383 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
383 # print >> sys.stderr, '*** CMD:', ' '.join(self.call_args) # dbg
384 subp = subprocess.Popen(self.call_args, env=env)
384 subp = subprocess.Popen(self.call_args, env=env)
385 self.processes.append(subp)
385 self.processes.append(subp)
386 # If this fails, the process will be left in self.processes and
386 # If this fails, the process will be left in self.processes and
387 # cleaned up later, but if the wait call succeeds, then we can
387 # cleaned up later, but if the wait call succeeds, then we can
388 # clear the stored process.
388 # clear the stored process.
389 retcode = subp.wait()
389 retcode = subp.wait()
390 self.processes.pop()
390 self.processes.pop()
391 return retcode
391 return retcode
392
392
393 def run(self):
393 def run(self):
394 """Run the stored commands"""
394 """Run the stored commands"""
395 try:
395 try:
396 retcode = self._run_cmd()
396 retcode = self._run_cmd()
397 except KeyboardInterrupt:
397 except KeyboardInterrupt:
398 return -signal.SIGINT
398 return -signal.SIGINT
399 except:
399 except:
400 import traceback
400 import traceback
401 traceback.print_exc()
401 traceback.print_exc()
402 return 1 # signal failure
402 return 1 # signal failure
403
403
404 if self.coverage_xml:
404 if self.coverage_xml:
405 subprocess.call(["coverage", "xml", "-o", self.coverage_xml])
405 subprocess.call(["coverage", "xml", "-o", self.coverage_xml])
406 return retcode
406 return retcode
407
407
408 def __del__(self):
408 def __del__(self):
409 """Cleanup on exit by killing any leftover processes."""
409 """Cleanup on exit by killing any leftover processes."""
410 for subp in self.processes:
410 for subp in self.processes:
411 if subp.poll() is not None:
411 if subp.poll() is not None:
412 continue # process is already dead
412 continue # process is already dead
413
413
414 try:
414 try:
415 print('Cleaning up stale PID: %d' % subp.pid)
415 print('Cleaning up stale PID: %d' % subp.pid)
416 subp.kill()
416 subp.kill()
417 except: # (OSError, WindowsError) ?
417 except: # (OSError, WindowsError) ?
418 # This is just a best effort, if we fail or the process was
418 # This is just a best effort, if we fail or the process was
419 # really gone, ignore it.
419 # really gone, ignore it.
420 pass
420 pass
421 else:
421 else:
422 for i in range(10):
422 for i in range(10):
423 if subp.poll() is None:
423 if subp.poll() is None:
424 time.sleep(0.1)
424 time.sleep(0.1)
425 else:
425 else:
426 break
426 break
427
427
428 if subp.poll() is None:
428 if subp.poll() is None:
429 # The process did not die...
429 # The process did not die...
430 print('... failed. Manual cleanup may be required.')
430 print('... failed. Manual cleanup may be required.')
431
431
432
432
433 def make_runners(inc_slow=False):
433 def make_runners(inc_slow=False):
434 """Define the top-level packages that need to be tested.
434 """Define the top-level packages that need to be tested.
435 """
435 """
436
436
437 # Packages to be tested via nose, that only depend on the stdlib
437 # Packages to be tested via nose, that only depend on the stdlib
438 nose_pkg_names = ['config', 'core', 'extensions', 'lib', 'terminal',
438 nose_pkg_names = ['config', 'core', 'extensions', 'lib', 'terminal',
439 'testing', 'utils', 'nbformat' ]
439 'testing', 'utils', 'nbformat' ]
440
440
441 if have['qt']:
441 if have['qt']:
442 nose_pkg_names.append('qt')
442 nose_pkg_names.append('qt')
443
443
444 if have['tornado']:
444 if have['tornado']:
445 nose_pkg_names.append('html')
445 nose_pkg_names.append('html')
446
446
447 if have['zmq']:
447 if have['zmq']:
448 nose_pkg_names.append('kernel')
448 nose_pkg_names.append('kernel')
449 nose_pkg_names.append('kernel.inprocess')
449 nose_pkg_names.append('kernel.inprocess')
450 if inc_slow:
450 if inc_slow:
451 nose_pkg_names.append('parallel')
451 nose_pkg_names.append('parallel')
452
452
453 # For debugging this code, only load quick stuff
453 # For debugging this code, only load quick stuff
454 #nose_pkg_names = ['core', 'extensions'] # dbg
454 #nose_pkg_names = ['core', 'extensions'] # dbg
455
455
456 # Make fully qualified package names prepending 'IPython.' to our name lists
456 # Make fully qualified package names prepending 'IPython.' to our name lists
457 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
457 nose_packages = ['IPython.%s' % m for m in nose_pkg_names ]
458
458
459 # Make runners
459 # Make runners
460 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
460 runners = [ (v, IPTester('iptest', params=v)) for v in nose_packages ]
461
461
462 return runners
462 return runners
463
463
464
464
465 def run_iptest():
465 def run_iptest():
466 """Run the IPython test suite using nose.
466 """Run the IPython test suite using nose.
467
467
468 This function is called when this script is **not** called with the form
468 This function is called when this script is **not** called with the form
469 `iptest all`. It simply calls nose with appropriate command line flags
469 `iptest all`. It simply calls nose with appropriate command line flags
470 and accepts all of the standard nose arguments.
470 and accepts all of the standard nose arguments.
471 """
471 """
472 # Apply our monkeypatch to Xunit
472 # Apply our monkeypatch to Xunit
473 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
473 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
474 monkeypatch_xunit()
474 monkeypatch_xunit()
475
475
476 warnings.filterwarnings('ignore',
476 warnings.filterwarnings('ignore',
477 'This will be removed soon. Use IPython.testing.util instead')
477 'This will be removed soon. Use IPython.testing.util instead')
478
478
479 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
479 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
480
480
481 '--with-ipdoctest',
481 '--with-ipdoctest',
482 '--ipdoctest-tests','--ipdoctest-extension=txt',
482 '--ipdoctest-tests','--ipdoctest-extension=txt',
483
483
484 # We add --exe because of setuptools' imbecility (it
484 # We add --exe because of setuptools' imbecility (it
485 # blindly does chmod +x on ALL files). Nose does the
485 # blindly does chmod +x on ALL files). Nose does the
486 # right thing and it tries to avoid executables,
486 # right thing and it tries to avoid executables,
487 # setuptools unfortunately forces our hand here. This
487 # setuptools unfortunately forces our hand here. This
488 # has been discussed on the distutils list and the
488 # has been discussed on the distutils list and the
489 # setuptools devs refuse to fix this problem!
489 # setuptools devs refuse to fix this problem!
490 '--exe',
490 '--exe',
491 ]
491 ]
492 if '-a' not in argv and '-A' not in argv:
492 if '-a' not in argv and '-A' not in argv:
493 argv = argv + ['-a', '!crash']
493 argv = argv + ['-a', '!crash']
494
494
495 if nose.__version__ >= '0.11':
495 if nose.__version__ >= '0.11':
496 # I don't fully understand why we need this one, but depending on what
496 # I don't fully understand why we need this one, but depending on what
497 # directory the test suite is run from, if we don't give it, 0 tests
497 # directory the test suite is run from, if we don't give it, 0 tests
498 # get run. Specifically, if the test suite is run from the source dir
498 # get run. Specifically, if the test suite is run from the source dir
499 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
499 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
500 # even if the same call done in this directory works fine). It appears
500 # even if the same call done in this directory works fine). It appears
501 # that if the requested package is in the current dir, nose bails early
501 # that if the requested package is in the current dir, nose bails early
502 # by default. Since it's otherwise harmless, leave it in by default
502 # by default. Since it's otherwise harmless, leave it in by default
503 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
503 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
504 argv.append('--traverse-namespace')
504 argv.append('--traverse-namespace')
505
505
506 # use our plugin for doctesting. It will remove the standard doctest plugin
506 # use our plugin for doctesting. It will remove the standard doctest plugin
507 # if it finds it enabled
507 # if it finds it enabled
508 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
508 plugins = [IPythonDoctest(make_exclude()), KnownFailure()]
509
509
510 # We need a global ipython running in this process, but the special
510 # We need a global ipython running in this process, but the special
511 # in-process group spawns its own IPython kernels, so for *that* group we
511 # in-process group spawns its own IPython kernels, so for *that* group we
512 # must avoid also opening the global one (otherwise there's a conflict of
512 # must avoid also opening the global one (otherwise there's a conflict of
513 # singletons). Ultimately the solution to this problem is to refactor our
513 # singletons). Ultimately the solution to this problem is to refactor our
514 # assumptions about what needs to be a singleton and what doesn't (app
514 # assumptions about what needs to be a singleton and what doesn't (app
515 # objects should, individual shells shouldn't). But for now, this
515 # objects should, individual shells shouldn't). But for now, this
516 # workaround allows the test suite for the inprocess module to complete.
516 # workaround allows the test suite for the inprocess module to complete.
517 if not 'IPython.kernel.inprocess' in sys.argv:
517 if not 'IPython.kernel.inprocess' in sys.argv:
518 globalipapp.start_ipython()
518 globalipapp.start_ipython()
519
519
520 # Now nose can run
520 # Now nose can run
521 TestProgram(argv=argv, addplugins=plugins)
521 TestProgram(argv=argv, addplugins=plugins)
522
522
523
523
524 def run_iptestall(inc_slow=False):
524 def run_iptestall(inc_slow=False):
525 """Run the entire IPython test suite by calling nose and trial.
525 """Run the entire IPython test suite by calling nose and trial.
526
526
527 This function constructs :class:`IPTester` instances for all IPython
527 This function constructs :class:`IPTester` instances for all IPython
528 modules and package and then runs each of them. This causes the modules
528 modules and package and then runs each of them. This causes the modules
529 and packages of IPython to be tested each in their own subprocess using
529 and packages of IPython to be tested each in their own subprocess using
530 nose.
530 nose.
531
531
532 Parameters
532 Parameters
533 ----------
533 ----------
534
534
535 inc_slow : bool, optional
535 inc_slow : bool, optional
536 Include slow tests, like IPython.parallel. By default, these tests aren't
536 Include slow tests, like IPython.parallel. By default, these tests aren't
537 run.
537 run.
538 """
538 """
539
539
540 runners = make_runners(inc_slow=inc_slow)
540 runners = make_runners(inc_slow=inc_slow)
541
541
542 # Run the test runners in a temporary dir so we can nuke it when finished
542 # Run the test runners in a temporary dir so we can nuke it when finished
543 # to clean up any junk files left over by accident. This also makes it
543 # to clean up any junk files left over by accident. This also makes it
544 # robust against being run in non-writeable directories by mistake, as the
544 # robust against being run in non-writeable directories by mistake, as the
545 # temp dir will always be user-writeable.
545 # temp dir will always be user-writeable.
546 curdir = os.getcwdu()
546 curdir = os.getcwdu()
547 testdir = tempfile.gettempdir()
547 testdir = tempfile.gettempdir()
548 os.chdir(testdir)
548 os.chdir(testdir)
549
549
550 # Run all test runners, tracking execution time
550 # Run all test runners, tracking execution time
551 failed = []
551 failed = []
552 t_start = time.time()
552 t_start = time.time()
553 try:
553 try:
554 for (name, runner) in runners:
554 for (name, runner) in runners:
555 print('*'*70)
555 print('*'*70)
556 print('IPython test group:',name)
556 print('IPython test group:',name)
557 res = runner.run()
557 res = runner.run()
558 if res:
558 if res:
559 failed.append( (name, runner) )
559 failed.append( (name, runner) )
560 if res == -signal.SIGINT:
560 if res == -signal.SIGINT:
561 print("Interrupted")
561 print("Interrupted")
562 break
562 break
563 finally:
563 finally:
564 os.chdir(curdir)
564 os.chdir(curdir)
565 t_end = time.time()
565 t_end = time.time()
566 t_tests = t_end - t_start
566 t_tests = t_end - t_start
567 nrunners = len(runners)
567 nrunners = len(runners)
568 nfail = len(failed)
568 nfail = len(failed)
569 # summarize results
569 # summarize results
570 print()
570 print()
571 print('*'*70)
571 print('*'*70)
572 print('Test suite completed for system with the following information:')
572 print('Test suite completed for system with the following information:')
573 print(report())
573 print(report())
574 print('Ran %s test groups in %.3fs' % (nrunners, t_tests))
574 print('Ran %s test groups in %.3fs' % (nrunners, t_tests))
575 print()
575 print()
576 print('Status:')
576 print('Status:')
577 if not failed:
577 if not failed:
578 print('OK')
578 print('OK')
579 else:
579 else:
580 # If anything went wrong, point out what command to rerun manually to
580 # If anything went wrong, point out what command to rerun manually to
581 # see the actual errors and individual summary
581 # see the actual errors and individual summary
582 print('ERROR - %s out of %s test groups failed.' % (nfail, nrunners))
582 print('ERROR - %s out of %s test groups failed.' % (nfail, nrunners))
583 for name, failed_runner in failed:
583 for name, failed_runner in failed:
584 print('-'*40)
584 print('-'*40)
585 print('Runner failed:',name)
585 print('Runner failed:',name)
586 print('You may wish to rerun this one individually, with:')
586 print('You may wish to rerun this one individually, with:')
587 failed_call_args = [py3compat.cast_unicode(x) for x in failed_runner.call_args]
587 failed_call_args = [py3compat.cast_unicode(x) for x in failed_runner.call_args]
588 print(u' '.join(failed_call_args))
588 print(u' '.join(failed_call_args))
589 print()
589 print()
590 # Ensure that our exit code indicates failure
590 # Ensure that our exit code indicates failure
591 sys.exit(1)
591 sys.exit(1)
592
592
593
593
594 def main():
594 def main():
595 for arg in sys.argv[1:]:
595 for arg in sys.argv[1:]:
596 if arg.startswith('IPython'):
596 if arg.startswith('IPython'):
597 # This is in-process
597 # This is in-process
598 run_iptest()
598 run_iptest()
599 else:
599 else:
600 if "--all" in sys.argv:
600 if "--all" in sys.argv:
601 sys.argv.remove("--all")
601 sys.argv.remove("--all")
602 inc_slow = True
602 inc_slow = True
603 else:
603 else:
604 inc_slow = False
604 inc_slow = False
605 # This starts subprocesses
605 # This starts subprocesses
606 run_iptestall(inc_slow=inc_slow)
606 run_iptestall(inc_slow=inc_slow)
607
607
608
608
609 if __name__ == '__main__':
609 if __name__ == '__main__':
610 main()
610 main()
@@ -1,92 +1,92 b''
1 """utilities for checking submodule status"""
1 """utilities for checking submodule status"""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
4 # Copyright (C) 2013 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 import os
14 import os
15 import subprocess
15 import subprocess
16 import sys
16 import sys
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Globals
19 # Globals
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 pjoin = os.path.join
22 pjoin = os.path.join
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Code
25 # Code
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 def ipython_parent():
28 def ipython_parent():
29 """return IPython's parent (i.e. root if run from git)"""
29 """return IPython's parent (i.e. root if run from git)"""
30 from IPython.utils.path import get_ipython_package_dir
30 from IPython.utils.path import get_ipython_package_dir
31 return os.path.abspath(os.path.dirname(get_ipython_package_dir()))
31 return os.path.abspath(os.path.dirname(get_ipython_package_dir()))
32
32
33 def ipython_submodules(root):
33 def ipython_submodules(root):
34 """return IPython submodules relative to root"""
34 """return IPython submodules relative to root"""
35 return [
35 return [
36 pjoin(root, 'IPython', 'html', 'notebook', 'static', 'components'),
36 pjoin(root, 'IPython', 'html', 'static', 'components'),
37 ]
37 ]
38
38
39 def is_repo(d):
39 def is_repo(d):
40 """is d a git repo?"""
40 """is d a git repo?"""
41 return os.path.exists(pjoin(d, '.git'))
41 return os.path.exists(pjoin(d, '.git'))
42
42
43 def check_submodule_status(root=None):
43 def check_submodule_status(root=None):
44 """check submodule status
44 """check submodule status
45
45
46 Has three return values:
46 Has three return values:
47
47
48 'missing' - submodules are absent
48 'missing' - submodules are absent
49 'unclean' - submodules have unstaged changes
49 'unclean' - submodules have unstaged changes
50 'clean' - all submodules are up to date
50 'clean' - all submodules are up to date
51 """
51 """
52
52
53 if hasattr(sys, "frozen"):
53 if hasattr(sys, "frozen"):
54 # frozen via py2exe or similar, don't bother
54 # frozen via py2exe or similar, don't bother
55 return 'clean'
55 return 'clean'
56
56
57 if not root:
57 if not root:
58 root = ipython_parent()
58 root = ipython_parent()
59
59
60 if not is_repo(root):
60 if not is_repo(root):
61 # not in git, assume clean
61 # not in git, assume clean
62 return 'clean'
62 return 'clean'
63
63
64 submodules = ipython_submodules(root)
64 submodules = ipython_submodules(root)
65
65
66 for submodule in submodules:
66 for submodule in submodules:
67 if not os.path.exists(submodule):
67 if not os.path.exists(submodule):
68 return 'missing'
68 return 'missing'
69
69
70 # check with git submodule status
70 # check with git submodule status
71 proc = subprocess.Popen('git submodule status',
71 proc = subprocess.Popen('git submodule status',
72 stdout=subprocess.PIPE,
72 stdout=subprocess.PIPE,
73 stderr=subprocess.PIPE,
73 stderr=subprocess.PIPE,
74 shell=True,
74 shell=True,
75 cwd=root,
75 cwd=root,
76 )
76 )
77 status, _ = proc.communicate()
77 status, _ = proc.communicate()
78 status = status.decode("ascii")
78 status = status.decode("ascii")
79
79
80 for line in status.splitlines():
80 for line in status.splitlines():
81 if status.startswith('-'):
81 if status.startswith('-'):
82 return 'missing'
82 return 'missing'
83 elif status.startswith('+'):
83 elif status.startswith('+'):
84 return 'unclean'
84 return 'unclean'
85
85
86 return 'clean'
86 return 'clean'
87
87
88 def update_submodules(repo_dir):
88 def update_submodules(repo_dir):
89 """update submodules in a repo"""
89 """update submodules in a repo"""
90 subprocess.check_call("git submodule init", cwd=repo_dir, shell=True)
90 subprocess.check_call("git submodule init", cwd=repo_dir, shell=True)
91 subprocess.check_call("git submodule update --recursive", cwd=repo_dir, shell=True)
91 subprocess.check_call("git submodule update --recursive", cwd=repo_dir, shell=True)
92
92
@@ -1,39 +1,39 b''
1 include README.rst
1 include README.rst
2 include COPYING.txt
2 include COPYING.txt
3 include setupbase.py
3 include setupbase.py
4 include setupegg.py
4 include setupegg.py
5
5
6 graft setupext
6 graft setupext
7
7
8 graft scripts
8 graft scripts
9
9
10 # Load main dir but exclude things we don't want in the distro
10 # Load main dir but exclude things we don't want in the distro
11 graft IPython
11 graft IPython
12 prune IPython/deathrow
12 prune IPython/deathrow
13 prune IPython/external/js
13 prune IPython/external/js
14 prune IPython/html/notebook/static/mathjax
14 prune IPython/html/static/mathjax
15
15
16 # Include some specific files and data resources we need
16 # Include some specific files and data resources we need
17 include IPython/.git_commit_info.ini
17 include IPython/.git_commit_info.ini
18 include IPython/qt/console/resources/icon/IPythonConsole.svg
18 include IPython/qt/console/resources/icon/IPythonConsole.svg
19
19
20 # Documentation
20 # Documentation
21 graft docs
21 graft docs
22 exclude docs/\#*
22 exclude docs/\#*
23 exclude docs/man/*.1.gz
23 exclude docs/man/*.1.gz
24
24
25 # Examples
25 # Examples
26 graft examples
26 graft examples
27
27
28 # docs subdirs we want to skip
28 # docs subdirs we want to skip
29 prune docs/attic
29 prune docs/attic
30 prune docs/build
30 prune docs/build
31 prune docs/gh-pages
31 prune docs/gh-pages
32 prune docs/dist
32 prune docs/dist
33
33
34 # Patterns to exclude from any directory
34 # Patterns to exclude from any directory
35 global-exclude *~
35 global-exclude *~
36 global-exclude *.flc
36 global-exclude *.flc
37 global-exclude *.pyc
37 global-exclude *.pyc
38 global-exclude *.pyo
38 global-exclude *.pyo
39 global-exclude .dircopy.log
39 global-exclude .dircopy.log
@@ -1,482 +1,482 b''
1 .. _htmlnotebook:
1 .. _htmlnotebook:
2
2
3 =========================
3 =========================
4 An HTML Notebook IPython
4 An HTML Notebook IPython
5 =========================
5 =========================
6
6
7 .. seealso::
7 .. seealso::
8
8
9 :ref:`Installation requirements <installnotebook>` for the Notebook.
9 :ref:`Installation requirements <installnotebook>` for the Notebook.
10
10
11 The IPython Notebook consists of two related components:
11 The IPython Notebook consists of two related components:
12
12
13 * An JSON based Notebook document format for recording and distributing
13 * An JSON based Notebook document format for recording and distributing
14 Python code and rich text.
14 Python code and rich text.
15 * A web-based user interface for authoring and running notebook documents.
15 * A web-based user interface for authoring and running notebook documents.
16
16
17 The Notebook can be used by starting the Notebook server with the
17 The Notebook can be used by starting the Notebook server with the
18 command::
18 command::
19
19
20 $ ipython notebook
20 $ ipython notebook
21
21
22 Note that by default, the notebook doesn't load pylab, it's just a normal
22 Note that by default, the notebook doesn't load pylab, it's just a normal
23 IPython session like any other. If you want pylab support, you must use::
23 IPython session like any other. If you want pylab support, you must use::
24
24
25 $ ipython notebook --pylab
25 $ ipython notebook --pylab
26
26
27 which will behave similar to the terminal and Qt console versions, using your
27 which will behave similar to the terminal and Qt console versions, using your
28 default matplotlib backend and providing floating interactive plot windows. If
28 default matplotlib backend and providing floating interactive plot windows. If
29 you want inline figures, you must manually select the ``inline`` backend::
29 you want inline figures, you must manually select the ``inline`` backend::
30
30
31 $ ipython notebook --pylab inline
31 $ ipython notebook --pylab inline
32
32
33 This server uses the same ZeroMQ-based two process kernel architecture as
33 This server uses the same ZeroMQ-based two process kernel architecture as
34 the QT Console as well Tornado for serving HTTP/S requests. Some of the main
34 the QT Console as well Tornado for serving HTTP/S requests. Some of the main
35 features of the Notebook include:
35 features of the Notebook include:
36
36
37 * Display rich data (png/html/latex/svg) in the browser as a result of
37 * Display rich data (png/html/latex/svg) in the browser as a result of
38 computations.
38 computations.
39 * Compose text cells using HTML and Markdown.
39 * Compose text cells using HTML and Markdown.
40 * Import and export notebook documents in range of formats (.ipynb, .py).
40 * Import and export notebook documents in range of formats (.ipynb, .py).
41 * In browser syntax highlighting, tab completion and autoindentation.
41 * In browser syntax highlighting, tab completion and autoindentation.
42 * Inline matplotlib plots that can be stored in Notebook documents and opened
42 * Inline matplotlib plots that can be stored in Notebook documents and opened
43 later.
43 later.
44
44
45 See :ref:`our installation documentation <install_index>` for directions on
45 See :ref:`our installation documentation <install_index>` for directions on
46 how to install the notebook and its dependencies.
46 how to install the notebook and its dependencies.
47
47
48 .. note::
48 .. note::
49
49
50 You can start more than one notebook server at the same time, if you want to
50 You can start more than one notebook server at the same time, if you want to
51 work on notebooks in different directories. By default the first notebook
51 work on notebooks in different directories. By default the first notebook
52 server starts in port 8888, later notebooks search for random ports near
52 server starts in port 8888, later notebooks search for random ports near
53 that one. You can also manually specify the port with the ``--port``
53 that one. You can also manually specify the port with the ``--port``
54 option.
54 option.
55
55
56
56
57 Basic Usage
57 Basic Usage
58 ===========
58 ===========
59
59
60 The landing page of the notebook server application, which we call the IPython
60 The landing page of the notebook server application, which we call the IPython
61 Notebook *dashboard*, shows the notebooks currently available in the directory
61 Notebook *dashboard*, shows the notebooks currently available in the directory
62 in which the application was started, and allows you to create new notebooks.
62 in which the application was started, and allows you to create new notebooks.
63
63
64 A notebook is a combination of two things:
64 A notebook is a combination of two things:
65
65
66 1. An interactive session connected to an IPython kernel, controlled by a web
66 1. An interactive session connected to an IPython kernel, controlled by a web
67 application that can send input to the console and display many types of
67 application that can send input to the console and display many types of
68 output (text, graphics, mathematics and more). This is the same kernel used
68 output (text, graphics, mathematics and more). This is the same kernel used
69 by the :ref:`Qt console <qtconsole>`, but in this case the web console sends
69 by the :ref:`Qt console <qtconsole>`, but in this case the web console sends
70 input in persistent cells that you can edit in-place instead of the
70 input in persistent cells that you can edit in-place instead of the
71 vertically scrolling terminal style used by the Qt console.
71 vertically scrolling terminal style used by the Qt console.
72
72
73 2. A document that can save the inputs and outputs of the session as well as
73 2. A document that can save the inputs and outputs of the session as well as
74 additional text that accompanies the code but is not meant for execution.
74 additional text that accompanies the code but is not meant for execution.
75 In this way, notebook files serve as a complete computational record of a
75 In this way, notebook files serve as a complete computational record of a
76 session including explanatory text and mathematics, code and resulting
76 session including explanatory text and mathematics, code and resulting
77 figures. These documents are internally JSON files and are saved with the
77 figures. These documents are internally JSON files and are saved with the
78 ``.ipynb`` extension.
78 ``.ipynb`` extension.
79
79
80 If you have ever used the Mathematica or Sage notebooks (the latter is also
80 If you have ever used the Mathematica or Sage notebooks (the latter is also
81 web-based__) you should feel right at home. If you have not, you should be
81 web-based__) you should feel right at home. If you have not, you should be
82 able to learn how to use it in just a few minutes.
82 able to learn how to use it in just a few minutes.
83
83
84 .. __: http://sagenb.org
84 .. __: http://sagenb.org
85
85
86
86
87 Creating and editing notebooks
87 Creating and editing notebooks
88 ------------------------------
88 ------------------------------
89
89
90 You can create new notebooks from the dashboard with the ``New Notebook``
90 You can create new notebooks from the dashboard with the ``New Notebook``
91 button or open existing ones by clicking on their name. Once in a notebook,
91 button or open existing ones by clicking on their name. Once in a notebook,
92 your browser tab will reflect the name of that notebook (prefixed with "IPy:").
92 your browser tab will reflect the name of that notebook (prefixed with "IPy:").
93 The URL for that notebook is not meant to be human-readable and is *not*
93 The URL for that notebook is not meant to be human-readable and is *not*
94 persistent across invocations of the notebook server.
94 persistent across invocations of the notebook server.
95
95
96 You can also drag and drop into the area listing files any python file: it
96 You can also drag and drop into the area listing files any python file: it
97 will be imported into a notebook with the same name (but ``.ipynb`` extension)
97 will be imported into a notebook with the same name (but ``.ipynb`` extension)
98 located in the directory where the notebook server was started. This notebook
98 located in the directory where the notebook server was started. This notebook
99 will consist of a single cell with all the code in the file, which you can
99 will consist of a single cell with all the code in the file, which you can
100 later manually partition into individual cells for gradual execution, add text
100 later manually partition into individual cells for gradual execution, add text
101 and graphics, etc.
101 and graphics, etc.
102
102
103
103
104 Workflow and limitations
104 Workflow and limitations
105 ------------------------
105 ------------------------
106
106
107 The normal workflow in a notebook is quite similar to a normal IPython session,
107 The normal workflow in a notebook is quite similar to a normal IPython session,
108 with the difference that you can edit a cell in-place multiple times until you
108 with the difference that you can edit a cell in-place multiple times until you
109 obtain the desired results rather than having to rerun separate scripts with
109 obtain the desired results rather than having to rerun separate scripts with
110 the ``%run`` magic (though magics also work in the notebook). Typically
110 the ``%run`` magic (though magics also work in the notebook). Typically
111 you'll work on a problem in pieces, organizing related pieces into cells and
111 you'll work on a problem in pieces, organizing related pieces into cells and
112 moving forward as previous parts work correctly. This is much more convenient
112 moving forward as previous parts work correctly. This is much more convenient
113 for interactive exploration than breaking up a computation into scripts that
113 for interactive exploration than breaking up a computation into scripts that
114 must be executed together, especially if parts of them take a long time to run
114 must be executed together, especially if parts of them take a long time to run
115 (In the traditional terminal-based IPython, you can use tricks with namespaces
115 (In the traditional terminal-based IPython, you can use tricks with namespaces
116 and ``%run -i`` to achieve this capability, but we think the notebook is a more
116 and ``%run -i`` to achieve this capability, but we think the notebook is a more
117 natural solution for that kind of problem).
117 natural solution for that kind of problem).
118
118
119 The only significant limitation the notebook currently has, compared to the qt
119 The only significant limitation the notebook currently has, compared to the qt
120 console, is that it can not run any code that expects input from the kernel
120 console, is that it can not run any code that expects input from the kernel
121 (such as scripts that call :func:`raw_input`). Very importantly, this means
121 (such as scripts that call :func:`raw_input`). Very importantly, this means
122 that the ``%debug`` magic does *not* work in the notebook! We intend to
122 that the ``%debug`` magic does *not* work in the notebook! We intend to
123 correct this limitation, but in the meantime, there is a way to debug problems
123 correct this limitation, but in the meantime, there is a way to debug problems
124 in the notebook: you can attach a Qt console to your existing notebook kernel,
124 in the notebook: you can attach a Qt console to your existing notebook kernel,
125 and run ``%debug`` from the Qt console. If your notebook is running on a local
125 and run ``%debug`` from the Qt console. If your notebook is running on a local
126 computer (i.e. if you are accessing it via your localhost address at
126 computer (i.e. if you are accessing it via your localhost address at
127 127.0.0.1), you can just type ``%qtconsole`` in the notebook and a Qt console
127 127.0.0.1), you can just type ``%qtconsole`` in the notebook and a Qt console
128 will open up connected to that same kernel.
128 will open up connected to that same kernel.
129
129
130 In general, the notebook server prints the full details of how to connect to
130 In general, the notebook server prints the full details of how to connect to
131 each kernel at the terminal, with lines like::
131 each kernel at the terminal, with lines like::
132
132
133 [IPKernelApp] To connect another client to this kernel, use:
133 [IPKernelApp] To connect another client to this kernel, use:
134 [IPKernelApp] --existing kernel-3bb93edd-6b5a-455c-99c8-3b658f45dde5.json
134 [IPKernelApp] --existing kernel-3bb93edd-6b5a-455c-99c8-3b658f45dde5.json
135
135
136 This is the name of a JSON file that contains all the port and validation
136 This is the name of a JSON file that contains all the port and validation
137 information necessary to connect to the kernel. You can manually start a
137 information necessary to connect to the kernel. You can manually start a
138 qt console with::
138 qt console with::
139
139
140 ipython qtconsole --existing kernel-3bb93edd-6b5a-455c-99c8-3b658f45dde5.json
140 ipython qtconsole --existing kernel-3bb93edd-6b5a-455c-99c8-3b658f45dde5.json
141
141
142 and if you only have a single kernel running, simply typing::
142 and if you only have a single kernel running, simply typing::
143
143
144 ipython qtconsole --existing
144 ipython qtconsole --existing
145
145
146 will automatically find it (it will always find the most recently started
146 will automatically find it (it will always find the most recently started
147 kernel if there is more than one). You can also request this connection data
147 kernel if there is more than one). You can also request this connection data
148 by typing ``%connect_info``; this will print the same file information as well
148 by typing ``%connect_info``; this will print the same file information as well
149 as the content of the JSON data structure it contains.
149 as the content of the JSON data structure it contains.
150
150
151
151
152 Text input
152 Text input
153 ----------
153 ----------
154
154
155 In addition to code cells and the output they produce (such as figures), you
155 In addition to code cells and the output they produce (such as figures), you
156 can also type text not meant for execution. To type text, change the type of a
156 can also type text not meant for execution. To type text, change the type of a
157 cell from ``Code`` to ``Markdown`` by using the button or the :kbd:`Ctrl-m m`
157 cell from ``Code`` to ``Markdown`` by using the button or the :kbd:`Ctrl-m m`
158 keybinding (see below). You can then type any text in Markdown_ syntax, as
158 keybinding (see below). You can then type any text in Markdown_ syntax, as
159 well as mathematical expressions if you use ``$...$`` for inline math or
159 well as mathematical expressions if you use ``$...$`` for inline math or
160 ``$$...$$`` for displayed math.
160 ``$$...$$`` for displayed math.
161
161
162
162
163 Exporting a notebook and importing existing scripts
163 Exporting a notebook and importing existing scripts
164 ---------------------------------------------------
164 ---------------------------------------------------
165
165
166 If you want to provide others with a static HTML or PDF view of your notebook,
166 If you want to provide others with a static HTML or PDF view of your notebook,
167 use the ``Print`` button. This opens a static view of the document, which you
167 use the ``Print`` button. This opens a static view of the document, which you
168 can print to PDF using your operating system's facilities, or save to a file
168 can print to PDF using your operating system's facilities, or save to a file
169 with your web browser's 'Save' option (note that typically, this will create
169 with your web browser's 'Save' option (note that typically, this will create
170 both an html file *and* a directory called `notebook_name_files` next to it
170 both an html file *and* a directory called `notebook_name_files` next to it
171 that contains all the necessary style information, so if you intend to share
171 that contains all the necessary style information, so if you intend to share
172 this, you must send the directory along with the main html file).
172 this, you must send the directory along with the main html file).
173
173
174 The `Download` button lets you save a notebook file to the Download area
174 The `Download` button lets you save a notebook file to the Download area
175 configured by your web browser (particularly useful if you are running the
175 configured by your web browser (particularly useful if you are running the
176 notebook server on a remote host and need a file locally). The notebook is
176 notebook server on a remote host and need a file locally). The notebook is
177 saved by default with the ``.ipynb`` extension and the files contain JSON data
177 saved by default with the ``.ipynb`` extension and the files contain JSON data
178 that is not meant for human editing or consumption. But you can always export
178 that is not meant for human editing or consumption. But you can always export
179 the input part of a notebook to a plain python script by choosing Python format
179 the input part of a notebook to a plain python script by choosing Python format
180 in the `Download` drop list. This removes all output and saves the text cells
180 in the `Download` drop list. This removes all output and saves the text cells
181 in comment areas. See ref:`below <notebook_format>` for more details on the
181 in comment areas. See ref:`below <notebook_format>` for more details on the
182 notebook format.
182 notebook format.
183
183
184 The notebook can also *import* ``.py`` files as notebooks, by dragging and
184 The notebook can also *import* ``.py`` files as notebooks, by dragging and
185 dropping the file into the notebook dashboard file list area. By default, the
185 dropping the file into the notebook dashboard file list area. By default, the
186 entire contents of the file will be loaded into a single code cell. But if
186 entire contents of the file will be loaded into a single code cell. But if
187 prior to import, you manually add the ``# <nbformat>2</nbformat>`` marker at
187 prior to import, you manually add the ``# <nbformat>2</nbformat>`` marker at
188 the start and then add separators for text/code cells, you can get a cleaner
188 the start and then add separators for text/code cells, you can get a cleaner
189 import with the file broken into individual cells.
189 import with the file broken into individual cells.
190
190
191 .. warning::
191 .. warning::
192
192
193 While in simple cases you can roundtrip a notebook to Python, edit the
193 While in simple cases you can roundtrip a notebook to Python, edit the
194 python file and import it back without loss of main content, this is in
194 python file and import it back without loss of main content, this is in
195 general *not guaranteed to work at all*. First, there is extra metadata
195 general *not guaranteed to work at all*. First, there is extra metadata
196 saved in the notebook that may not be saved to the ``.py`` format. And as
196 saved in the notebook that may not be saved to the ``.py`` format. And as
197 the notebook format evolves in complexity, there will be attributes of the
197 the notebook format evolves in complexity, there will be attributes of the
198 notebook that will not survive a roundtrip through the Python form. You
198 notebook that will not survive a roundtrip through the Python form. You
199 should think of the Python format as a way to output a script version of a
199 should think of the Python format as a way to output a script version of a
200 notebook and the import capabilities as a way to load existing code to get a
200 notebook and the import capabilities as a way to load existing code to get a
201 notebook started. But the Python version is *not* an alternate notebook
201 notebook started. But the Python version is *not* an alternate notebook
202 format.
202 format.
203
203
204
204
205 Importing or executing a notebook as a normal Python file
205 Importing or executing a notebook as a normal Python file
206 ---------------------------------------------------------
206 ---------------------------------------------------------
207
207
208 The native format of the notebook, a file with a ``.ipynb`` extension, is a
208 The native format of the notebook, a file with a ``.ipynb`` extension, is a
209 JSON container of all the input and output of the notebook, and therefore not
209 JSON container of all the input and output of the notebook, and therefore not
210 valid Python by itself. This means that by default, you can not import a
210 valid Python by itself. This means that by default, you can not import a
211 notebook or execute it as a normal python script. But if you want use
211 notebook or execute it as a normal python script. But if you want use
212 notebooks as regular Python files, you can start the notebook server with::
212 notebooks as regular Python files, you can start the notebook server with::
213
213
214 ipython notebook --script
214 ipython notebook --script
215
215
216 or you can set this option permanently in your configuration file with::
216 or you can set this option permanently in your configuration file with::
217
217
218 c.NotebookManager.save_script=True
218 c.NotebookManager.save_script=True
219
219
220 This will instruct the notebook server to save the ``.py`` export of each
220 This will instruct the notebook server to save the ``.py`` export of each
221 notebook adjacent to the ``.ipynb`` at every save. These files can be
221 notebook adjacent to the ``.ipynb`` at every save. These files can be
222 ``%run``, imported from regular IPython sessions or other notebooks, or
222 ``%run``, imported from regular IPython sessions or other notebooks, or
223 executed at the command-line as normal Python files. Since we export the raw
223 executed at the command-line as normal Python files. Since we export the raw
224 code you have typed, for these files to be importable from other code you will
224 code you have typed, for these files to be importable from other code you will
225 have to avoid using syntax such as ``%magics`` and other IPython-specific
225 have to avoid using syntax such as ``%magics`` and other IPython-specific
226 extensions to the language.
226 extensions to the language.
227
227
228 In regular practice, the standard way to differentiate importable code from the
228 In regular practice, the standard way to differentiate importable code from the
229 'executable' part of a script is to put at the bottom::
229 'executable' part of a script is to put at the bottom::
230
230
231 if __name__ == '__main__':
231 if __name__ == '__main__':
232 # rest of the code...
232 # rest of the code...
233
233
234 Since all cells in the notebook are run as top-level code, you'll need to
234 Since all cells in the notebook are run as top-level code, you'll need to
235 similarly protect *all* cells that you do not want executed when other scripts
235 similarly protect *all* cells that you do not want executed when other scripts
236 try to import your notebook. A convenient shortand for this is to define early
236 try to import your notebook. A convenient shortand for this is to define early
237 on::
237 on::
238
238
239 script = __name__ == '__main__'
239 script = __name__ == '__main__'
240
240
241 and then on any cell that you need to protect, use::
241 and then on any cell that you need to protect, use::
242
242
243 if script:
243 if script:
244 # rest of the cell...
244 # rest of the cell...
245
245
246 Configuration
246 Configuration
247 -------------
247 -------------
248
248
249 The IPython notebook server can be run with a variety of command line arguments.
249 The IPython notebook server can be run with a variety of command line arguments.
250 To see a list of available options enter:
250 To see a list of available options enter:
251
251
252 $ ipython notebook --help
252 $ ipython notebook --help
253
253
254 Defaults for these options can also be set by creating a file named
254 Defaults for these options can also be set by creating a file named
255 ipython_notebook_config.py in your IPython profile folder. The profile folder is
255 ipython_notebook_config.py in your IPython profile folder. The profile folder is
256 a subfolder of your IPython directory (`ipython locate` will show you where that
256 a subfolder of your IPython directory (`ipython locate` will show you where that
257 is). To create default configuration files (with lots of info on available
257 is). To create default configuration files (with lots of info on available
258 options) use:
258 options) use:
259
259
260 $ ipython profile create
260 $ ipython profile create
261
261
262 .. seealso:
262 .. seealso:
263
263
264 :ref:`config_overview`, in particular :ref:`Profiles`.
264 :ref:`config_overview`, in particular :ref:`Profiles`.
265
265
266
266
267 Keyboard use
267 Keyboard use
268 ------------
268 ------------
269
269
270 All actions in the notebook can be achieved with the mouse, but we have also
270 All actions in the notebook can be achieved with the mouse, but we have also
271 added keyboard shortcuts for the most common ones, so that productive use of
271 added keyboard shortcuts for the most common ones, so that productive use of
272 the notebook can be achieved with minimal mouse intervention. The main
272 the notebook can be achieved with minimal mouse intervention. The main
273 key bindings you need to remember are:
273 key bindings you need to remember are:
274
274
275 * :kbd:`Shift-Enter`: execute the current cell (similar to the Qt console),
275 * :kbd:`Shift-Enter`: execute the current cell (similar to the Qt console),
276 show output (if any) and jump to the next cell below. If :kbd:`Shift-Enter`
276 show output (if any) and jump to the next cell below. If :kbd:`Shift-Enter`
277 was invoked on the last input line, a new code cell will also be created. Note
277 was invoked on the last input line, a new code cell will also be created. Note
278 that in the notebook, simply using :kbd:`Enter` *never* forces execution,
278 that in the notebook, simply using :kbd:`Enter` *never* forces execution,
279 it simply inserts a new line in the current cell. Therefore, in the notebook
279 it simply inserts a new line in the current cell. Therefore, in the notebook
280 you must always use :kbd:`Shift-Enter` to get execution (or use the mouse and
280 you must always use :kbd:`Shift-Enter` to get execution (or use the mouse and
281 click on the ``Run Selected`` button).
281 click on the ``Run Selected`` button).
282
282
283 * :kbd:`Alt-Enter`: this combination is similar to the previous one, with the
283 * :kbd:`Alt-Enter`: this combination is similar to the previous one, with the
284 exception that, if the next cell below is not empty, a new code cell will be
284 exception that, if the next cell below is not empty, a new code cell will be
285 added to the notebook, even if the cell execution happens not in the last cell.
285 added to the notebook, even if the cell execution happens not in the last cell.
286 In this regard, :kbd:`Alt-Enter`: is simply a shortcut for the :kbd:`Shift-Enter`,
286 In this regard, :kbd:`Alt-Enter`: is simply a shortcut for the :kbd:`Shift-Enter`,
287 :kbd:`Ctrl-m a` sequence.
287 :kbd:`Ctrl-m a` sequence.
288
288
289 * :kbd:`Ctrl-Enter`: execute the current cell in "terminal mode", where any
289 * :kbd:`Ctrl-Enter`: execute the current cell in "terminal mode", where any
290 output is shown but the cursor stays in the current cell, whose input
290 output is shown but the cursor stays in the current cell, whose input
291 area is flushed empty. This is convenient to do quick in-place experiments
291 area is flushed empty. This is convenient to do quick in-place experiments
292 or query things like filesystem content without creating additional cells you
292 or query things like filesystem content without creating additional cells you
293 may not want saved in your notebook.
293 may not want saved in your notebook.
294
294
295 * :kbd:`Ctrl-m`: this is the prefix for all other keybindings, which consist
295 * :kbd:`Ctrl-m`: this is the prefix for all other keybindings, which consist
296 of an additional single letter. Type :kbd:`Ctrl-m h` (that is, the sole
296 of an additional single letter. Type :kbd:`Ctrl-m h` (that is, the sole
297 letter :kbd:`h` after :kbd:`Ctrl-m`) and IPython will show you the remaining
297 letter :kbd:`h` after :kbd:`Ctrl-m`) and IPython will show you the remaining
298 available keybindings.
298 available keybindings.
299
299
300
300
301 .. _notebook_security:
301 .. _notebook_security:
302
302
303 Security
303 Security
304 ========
304 ========
305
305
306 You can protect your notebook server with a simple single-password by
306 You can protect your notebook server with a simple single-password by
307 setting the :attr:`NotebookApp.password` configurable. You can prepare a
307 setting the :attr:`NotebookApp.password` configurable. You can prepare a
308 hashed password using the function :func:`IPython.lib.security.passwd`:
308 hashed password using the function :func:`IPython.lib.security.passwd`:
309
309
310 .. sourcecode:: ipython
310 .. sourcecode:: ipython
311
311
312 In [1]: from IPython.lib import passwd
312 In [1]: from IPython.lib import passwd
313 In [2]: passwd()
313 In [2]: passwd()
314 Enter password:
314 Enter password:
315 Verify password:
315 Verify password:
316 Out[2]: 'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed'
316 Out[2]: 'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed'
317
317
318 .. note::
318 .. note::
319
319
320 :func:`~IPython.lib.security.passwd` can also take the password as a string
320 :func:`~IPython.lib.security.passwd` can also take the password as a string
321 argument. **Do not** pass it as an argument inside an IPython session, as it
321 argument. **Do not** pass it as an argument inside an IPython session, as it
322 will be saved in your input history.
322 will be saved in your input history.
323
323
324 You can then add this to your :file:`ipython_notebook_config.py`, e.g.::
324 You can then add this to your :file:`ipython_notebook_config.py`, e.g.::
325
325
326 # Password to use for web authentication
326 # Password to use for web authentication
327 c.NotebookApp.password = u'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed'
327 c.NotebookApp.password = u'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed'
328
328
329 When using a password, it is a good idea to also use SSL, so that your password
329 When using a password, it is a good idea to also use SSL, so that your password
330 is not sent unencrypted by your browser. You can start the notebook to
330 is not sent unencrypted by your browser. You can start the notebook to
331 communicate via a secure protocol mode using a self-signed certificate by
331 communicate via a secure protocol mode using a self-signed certificate by
332 typing::
332 typing::
333
333
334 $ ipython notebook --certfile=mycert.pem
334 $ ipython notebook --certfile=mycert.pem
335
335
336 .. note::
336 .. note::
337
337
338 A self-signed certificate can be generated with openssl. For example, the
338 A self-signed certificate can be generated with openssl. For example, the
339 following command will create a certificate valid for 365 days with both
339 following command will create a certificate valid for 365 days with both
340 the key and certificate data written to the same file::
340 the key and certificate data written to the same file::
341
341
342 $ openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout mycert.pem -out mycert.pem
342 $ openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout mycert.pem -out mycert.pem
343
343
344 Your browser will warn you of a dangerous certificate because it is
344 Your browser will warn you of a dangerous certificate because it is
345 self-signed. If you want to have a fully compliant certificate that will not
345 self-signed. If you want to have a fully compliant certificate that will not
346 raise warnings, it is possible (but rather involved) to obtain one for free,
346 raise warnings, it is possible (but rather involved) to obtain one for free,
347 `as explained in detailed in this tutorial`__.
347 `as explained in detailed in this tutorial`__.
348
348
349 .. __: http://arstechnica.com/security/news/2009/12/how-to-get-set-with-a-secure-sertificate-for-free.ars
349 .. __: http://arstechnica.com/security/news/2009/12/how-to-get-set-with-a-secure-sertificate-for-free.ars
350
350
351 Keep in mind that when you enable SSL support, you'll need to access the
351 Keep in mind that when you enable SSL support, you'll need to access the
352 notebook server over ``https://``, not over plain ``http://``. The startup
352 notebook server over ``https://``, not over plain ``http://``. The startup
353 message from the server prints this, but it's easy to overlook and think the
353 message from the server prints this, but it's easy to overlook and think the
354 server is for some reason non-responsive.
354 server is for some reason non-responsive.
355
355
356 Quick how to's
356 Quick how to's
357 ==============
357 ==============
358
358
359 Running a public notebook server
359 Running a public notebook server
360 --------------------------------
360 --------------------------------
361
361
362 If you want to access your notebook server remotely with just a web browser,
362 If you want to access your notebook server remotely with just a web browser,
363 here is a quick set of instructions. Start by creating a certificate file and
363 here is a quick set of instructions. Start by creating a certificate file and
364 a hashed password as explained above. Then, create a custom profile for the
364 a hashed password as explained above. Then, create a custom profile for the
365 notebook. At the command line, type::
365 notebook. At the command line, type::
366
366
367 ipython profile create nbserver
367 ipython profile create nbserver
368
368
369 In the profile directory, edit the file ``ipython_notebook_config.py``. By
369 In the profile directory, edit the file ``ipython_notebook_config.py``. By
370 default the file has all fields commented, the minimum set you need to
370 default the file has all fields commented, the minimum set you need to
371 uncomment and edit is here::
371 uncomment and edit is here::
372
372
373 c = get_config()
373 c = get_config()
374
374
375 # Kernel config
375 # Kernel config
376 c.IPKernelApp.pylab = 'inline' # if you want plotting support always
376 c.IPKernelApp.pylab = 'inline' # if you want plotting support always
377
377
378 # Notebook config
378 # Notebook config
379 c.NotebookApp.certfile = u'/absolute/path/to/your/certificate/mycert.pem'
379 c.NotebookApp.certfile = u'/absolute/path/to/your/certificate/mycert.pem'
380 c.NotebookApp.ip = '*'
380 c.NotebookApp.ip = '*'
381 c.NotebookApp.open_browser = False
381 c.NotebookApp.open_browser = False
382 c.NotebookApp.password = u'sha1:bcd259ccf...your hashed password here'
382 c.NotebookApp.password = u'sha1:bcd259ccf...your hashed password here'
383 # It's a good idea to put it on a known, fixed port
383 # It's a good idea to put it on a known, fixed port
384 c.NotebookApp.port = 9999
384 c.NotebookApp.port = 9999
385
385
386 You can then start the notebook and access it later by pointing your browser to
386 You can then start the notebook and access it later by pointing your browser to
387 ``https://your.host.com:9999`` with ``ipython notebook --profile=nbserver``.
387 ``https://your.host.com:9999`` with ``ipython notebook --profile=nbserver``.
388
388
389 Running with a different URL prefix
389 Running with a different URL prefix
390 -----------------------------------
390 -----------------------------------
391
391
392 The notebook dashboard (i.e. the default landing page with an overview
392 The notebook dashboard (i.e. the default landing page with an overview
393 of all your notebooks) typically lives at a URL path of
393 of all your notebooks) typically lives at a URL path of
394 "http://localhost:8888/". If you want to have it, and the rest of the
394 "http://localhost:8888/". If you want to have it, and the rest of the
395 notebook, live under a sub-directory,
395 notebook, live under a sub-directory,
396 e.g. "http://localhost:8888/ipython/", you can do so with
396 e.g. "http://localhost:8888/ipython/", you can do so with
397 configuration options like these (see above for instructions about
397 configuration options like these (see above for instructions about
398 modifying ``ipython_notebook_config.py``)::
398 modifying ``ipython_notebook_config.py``)::
399
399
400 c.NotebookApp.base_project_url = '/ipython/'
400 c.NotebookApp.base_project_url = '/ipython/'
401 c.NotebookApp.base_kernel_url = '/ipython/'
401 c.NotebookApp.base_kernel_url = '/ipython/'
402 c.NotebookApp.webapp_settings = {'static_url_prefix':'/ipython/static/'}
402 c.NotebookApp.webapp_settings = {'static_url_prefix':'/ipython/static/'}
403
403
404 Using a different notebook store
404 Using a different notebook store
405 --------------------------------
405 --------------------------------
406
406
407 By default the notebook server stores notebooks as files in the working
407 By default the notebook server stores notebooks as files in the working
408 directory of the notebook server, also known as the ``notebook_dir``. This
408 directory of the notebook server, also known as the ``notebook_dir``. This
409 logic is implemented in the :class:`FileNotebookManager` class. However, the
409 logic is implemented in the :class:`FileNotebookManager` class. However, the
410 server can be configured to use a different notebook manager class, which can
410 server can be configured to use a different notebook manager class, which can
411 store the notebooks in a different format. Currently, we ship a
411 store the notebooks in a different format. Currently, we ship a
412 :class:`AzureNotebookManager` class that stores notebooks in Azure blob
412 :class:`AzureNotebookManager` class that stores notebooks in Azure blob
413 storage. This can be used by adding the following lines to your
413 storage. This can be used by adding the following lines to your
414 ``ipython_notebook_config.py`` file::
414 ``ipython_notebook_config.py`` file::
415
415
416 c.NotebookApp.notebook_manager_class = 'IPython.frontend.html.notebook.azurenbmanager.AzureNotebookManager'
416 c.NotebookApp.notebook_manager_class = 'IPython.html.services.notebooks.azurenbmanager.AzureNotebookManager'
417 c.AzureNotebookManager.account_name = u'paste_your_account_name_here'
417 c.AzureNotebookManager.account_name = u'paste_your_account_name_here'
418 c.AzureNotebookManager.account_key = u'paste_your_account_key_here'
418 c.AzureNotebookManager.account_key = u'paste_your_account_key_here'
419 c.AzureNotebookManager.container = u'notebooks'
419 c.AzureNotebookManager.container = u'notebooks'
420
420
421 In addition to providing your Azure Blob Storage account name and key, you will
421 In addition to providing your Azure Blob Storage account name and key, you will
422 have to provide a container name; you can use multiple containers to organize
422 have to provide a container name; you can use multiple containers to organize
423 your Notebooks.
423 your Notebooks.
424
424
425 .. _notebook_format:
425 .. _notebook_format:
426
426
427 The notebook format
427 The notebook format
428 ===================
428 ===================
429
429
430 The notebooks themselves are JSON files with an ``ipynb`` extension, formatted
430 The notebooks themselves are JSON files with an ``ipynb`` extension, formatted
431 as legibly as possible with minimal extra indentation and cell content broken
431 as legibly as possible with minimal extra indentation and cell content broken
432 across lines to make them reasonably friendly to use in version-control
432 across lines to make them reasonably friendly to use in version-control
433 workflows. You should be very careful if you ever edit manually this JSON
433 workflows. You should be very careful if you ever edit manually this JSON
434 data, as it is extremely easy to corrupt its internal structure and make the
434 data, as it is extremely easy to corrupt its internal structure and make the
435 file impossible to load. In general, you should consider the notebook as a
435 file impossible to load. In general, you should consider the notebook as a
436 file meant only to be edited by IPython itself, not for hand-editing.
436 file meant only to be edited by IPython itself, not for hand-editing.
437
437
438 .. note::
438 .. note::
439
439
440 Binary data such as figures are directly saved in the JSON file. This
440 Binary data such as figures are directly saved in the JSON file. This
441 provides convenient single-file portability but means the files can be
441 provides convenient single-file portability but means the files can be
442 large and diffs of binary data aren't very meaningful. Since the binary
442 large and diffs of binary data aren't very meaningful. Since the binary
443 blobs are encoded in a single line they only affect one line of the diff
443 blobs are encoded in a single line they only affect one line of the diff
444 output, but they are typically very long lines. You can use the
444 output, but they are typically very long lines. You can use the
445 'ClearAll' button to remove all output from a notebook prior to
445 'ClearAll' button to remove all output from a notebook prior to
446 committing it to version control, if this is a concern.
446 committing it to version control, if this is a concern.
447
447
448 The notebook server can also generate a pure-python version of your notebook,
448 The notebook server can also generate a pure-python version of your notebook,
449 by clicking on the 'Download' button and selecting ``py`` as the format. This
449 by clicking on the 'Download' button and selecting ``py`` as the format. This
450 file will contain all the code cells from your notebook verbatim, and all text
450 file will contain all the code cells from your notebook verbatim, and all text
451 cells prepended with a comment marker. The separation between code and text
451 cells prepended with a comment marker. The separation between code and text
452 cells is indicated with special comments and there is a header indicating the
452 cells is indicated with special comments and there is a header indicating the
453 format version. All output is stripped out when exporting to python.
453 format version. All output is stripped out when exporting to python.
454
454
455 Here is an example of a simple notebook with one text cell and one code input
455 Here is an example of a simple notebook with one text cell and one code input
456 cell, when exported to python format::
456 cell, when exported to python format::
457
457
458 # <nbformat>2</nbformat>
458 # <nbformat>2</nbformat>
459
459
460 # <markdowncell>
460 # <markdowncell>
461
461
462 # A text cell
462 # A text cell
463
463
464 # <codecell>
464 # <codecell>
465
465
466 print "hello IPython"
466 print "hello IPython"
467
467
468
468
469 Known issues
469 Known issues
470 ============
470 ============
471
471
472 When behind a proxy, especially if your system or browser is set to autodetect
472 When behind a proxy, especially if your system or browser is set to autodetect
473 the proxy, the html notebook might fail to connect to the server's websockets,
473 the proxy, the html notebook might fail to connect to the server's websockets,
474 and present you with a warning at startup. In this case, you need to configure
474 and present you with a warning at startup. In this case, you need to configure
475 your system not to use the proxy for the server's address.
475 your system not to use the proxy for the server's address.
476
476
477 In Firefox, for example, go to the Preferences panel, Advanced section,
477 In Firefox, for example, go to the Preferences panel, Advanced section,
478 Network tab, click 'Settings...', and add the address of the notebook server
478 Network tab, click 'Settings...', and add the address of the notebook server
479 to the 'No proxy for' field.
479 to the 'No proxy for' field.
480
480
481
481
482 .. _Markdown: http://daringfireball.net/projects/markdown/basics
482 .. _Markdown: http://daringfireball.net/projects/markdown/basics
@@ -1,468 +1,468 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 This module defines the things that are used in setup.py for building IPython
3 This module defines the things that are used in setup.py for building IPython
4
4
5 This includes:
5 This includes:
6
6
7 * The basic arguments to setup
7 * The basic arguments to setup
8 * Functions for finding things like packages, package data, etc.
8 * Functions for finding things like packages, package data, etc.
9 * A function for checking dependencies.
9 * A function for checking dependencies.
10 """
10 """
11 from __future__ import print_function
11 from __future__ import print_function
12
12
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14 # Copyright (C) 2008 The IPython Development Team
14 # Copyright (C) 2008 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19
19
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-------------------------------------------------------------------------------
22 #-------------------------------------------------------------------------------
23 import os
23 import os
24 import sys
24 import sys
25
25
26 try:
26 try:
27 from configparser import ConfigParser
27 from configparser import ConfigParser
28 except:
28 except:
29 from ConfigParser import ConfigParser
29 from ConfigParser import ConfigParser
30 from distutils.command.build_py import build_py
30 from distutils.command.build_py import build_py
31 from distutils.cmd import Command
31 from distutils.cmd import Command
32 from glob import glob
32 from glob import glob
33
33
34 from setupext import install_data_ext
34 from setupext import install_data_ext
35
35
36 #-------------------------------------------------------------------------------
36 #-------------------------------------------------------------------------------
37 # Useful globals and utility functions
37 # Useful globals and utility functions
38 #-------------------------------------------------------------------------------
38 #-------------------------------------------------------------------------------
39
39
40 # A few handy globals
40 # A few handy globals
41 isfile = os.path.isfile
41 isfile = os.path.isfile
42 pjoin = os.path.join
42 pjoin = os.path.join
43 repo_root = os.path.dirname(os.path.abspath(__file__))
43 repo_root = os.path.dirname(os.path.abspath(__file__))
44
44
45 def oscmd(s):
45 def oscmd(s):
46 print(">", s)
46 print(">", s)
47 os.system(s)
47 os.system(s)
48
48
49 # Py3 compatibility hacks, without assuming IPython itself is installed with
49 # Py3 compatibility hacks, without assuming IPython itself is installed with
50 # the full py3compat machinery.
50 # the full py3compat machinery.
51
51
52 try:
52 try:
53 execfile
53 execfile
54 except NameError:
54 except NameError:
55 def execfile(fname, globs, locs=None):
55 def execfile(fname, globs, locs=None):
56 locs = locs or globs
56 locs = locs or globs
57 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
57 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
58
58
59 # A little utility we'll need below, since glob() does NOT allow you to do
59 # A little utility we'll need below, since glob() does NOT allow you to do
60 # exclusion on multiple endings!
60 # exclusion on multiple endings!
61 def file_doesnt_endwith(test,endings):
61 def file_doesnt_endwith(test,endings):
62 """Return true if test is a file and its name does NOT end with any
62 """Return true if test is a file and its name does NOT end with any
63 of the strings listed in endings."""
63 of the strings listed in endings."""
64 if not isfile(test):
64 if not isfile(test):
65 return False
65 return False
66 for e in endings:
66 for e in endings:
67 if test.endswith(e):
67 if test.endswith(e):
68 return False
68 return False
69 return True
69 return True
70
70
71 #---------------------------------------------------------------------------
71 #---------------------------------------------------------------------------
72 # Basic project information
72 # Basic project information
73 #---------------------------------------------------------------------------
73 #---------------------------------------------------------------------------
74
74
75 # release.py contains version, authors, license, url, keywords, etc.
75 # release.py contains version, authors, license, url, keywords, etc.
76 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
76 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
77
77
78 # Create a dict with the basic information
78 # Create a dict with the basic information
79 # This dict is eventually passed to setup after additional keys are added.
79 # This dict is eventually passed to setup after additional keys are added.
80 setup_args = dict(
80 setup_args = dict(
81 name = name,
81 name = name,
82 version = version,
82 version = version,
83 description = description,
83 description = description,
84 long_description = long_description,
84 long_description = long_description,
85 author = author,
85 author = author,
86 author_email = author_email,
86 author_email = author_email,
87 url = url,
87 url = url,
88 download_url = download_url,
88 download_url = download_url,
89 license = license,
89 license = license,
90 platforms = platforms,
90 platforms = platforms,
91 keywords = keywords,
91 keywords = keywords,
92 classifiers = classifiers,
92 classifiers = classifiers,
93 cmdclass = {'install_data': install_data_ext},
93 cmdclass = {'install_data': install_data_ext},
94 )
94 )
95
95
96
96
97 #---------------------------------------------------------------------------
97 #---------------------------------------------------------------------------
98 # Find packages
98 # Find packages
99 #---------------------------------------------------------------------------
99 #---------------------------------------------------------------------------
100
100
101 def find_packages():
101 def find_packages():
102 """
102 """
103 Find all of IPython's packages.
103 Find all of IPython's packages.
104 """
104 """
105 excludes = ['deathrow', 'quarantine']
105 excludes = ['deathrow', 'quarantine']
106 packages = []
106 packages = []
107 for dir,subdirs,files in os.walk('IPython'):
107 for dir,subdirs,files in os.walk('IPython'):
108 package = dir.replace(os.path.sep, '.')
108 package = dir.replace(os.path.sep, '.')
109 if any(package.startswith('IPython.'+exc) for exc in excludes):
109 if any(package.startswith('IPython.'+exc) for exc in excludes):
110 # package is to be excluded (e.g. deathrow)
110 # package is to be excluded (e.g. deathrow)
111 continue
111 continue
112 if '__init__.py' not in files:
112 if '__init__.py' not in files:
113 # not a package
113 # not a package
114 continue
114 continue
115 packages.append(package)
115 packages.append(package)
116 return packages
116 return packages
117
117
118 #---------------------------------------------------------------------------
118 #---------------------------------------------------------------------------
119 # Find package data
119 # Find package data
120 #---------------------------------------------------------------------------
120 #---------------------------------------------------------------------------
121
121
122 def find_package_data():
122 def find_package_data():
123 """
123 """
124 Find IPython's package_data.
124 Find IPython's package_data.
125 """
125 """
126 # This is not enough for these things to appear in an sdist.
126 # This is not enough for these things to appear in an sdist.
127 # We need to muck with the MANIFEST to get this to work
127 # We need to muck with the MANIFEST to get this to work
128
128
129 # exclude static things that we don't ship (e.g. mathjax)
129 # exclude static things that we don't ship (e.g. mathjax)
130 excludes = ['mathjax']
130 excludes = ['mathjax']
131
131
132 # add 'static/' prefix to exclusions, and tuplify for use in startswith
132 # add 'static/' prefix to exclusions, and tuplify for use in startswith
133 excludes = tuple([os.path.join('static', ex) for ex in excludes])
133 excludes = tuple([os.path.join('static', ex) for ex in excludes])
134
134
135 # walk notebook resources:
135 # walk notebook resources:
136 cwd = os.getcwd()
136 cwd = os.getcwd()
137 os.chdir(os.path.join('IPython', 'html', 'notebook'))
137 os.chdir(os.path.join('IPython', 'html'))
138 static_walk = list(os.walk('static'))
138 static_walk = list(os.walk('static'))
139 os.chdir(cwd)
139 os.chdir(cwd)
140 static_data = []
140 static_data = []
141 for parent, dirs, files in static_walk:
141 for parent, dirs, files in static_walk:
142 if parent.startswith(excludes):
142 if parent.startswith(excludes):
143 continue
143 continue
144 for f in files:
144 for f in files:
145 static_data.append(os.path.join(parent, f))
145 static_data.append(os.path.join(parent, f))
146
146
147 package_data = {
147 package_data = {
148 'IPython.config.profile' : ['README*', '*/*.py'],
148 'IPython.config.profile' : ['README*', '*/*.py'],
149 'IPython.testing' : ['*.txt'],
149 'IPython.testing' : ['*.txt'],
150 'IPython.testing.plugin' : ['*.txt'],
150 'IPython.testing.plugin' : ['*.txt'],
151 'IPython.html.notebook' : ['templates/*'] + static_data,
151 'IPython.html' : ['templates/*'] + static_data,
152 'IPython.qt.console' : ['resources/icon/*.svg'],
152 'IPython.qt.console' : ['resources/icon/*.svg'],
153 }
153 }
154 return package_data
154 return package_data
155
155
156
156
157 #---------------------------------------------------------------------------
157 #---------------------------------------------------------------------------
158 # Find data files
158 # Find data files
159 #---------------------------------------------------------------------------
159 #---------------------------------------------------------------------------
160
160
161 def make_dir_struct(tag,base,out_base):
161 def make_dir_struct(tag,base,out_base):
162 """Make the directory structure of all files below a starting dir.
162 """Make the directory structure of all files below a starting dir.
163
163
164 This is just a convenience routine to help build a nested directory
164 This is just a convenience routine to help build a nested directory
165 hierarchy because distutils is too stupid to do this by itself.
165 hierarchy because distutils is too stupid to do this by itself.
166
166
167 XXX - this needs a proper docstring!
167 XXX - this needs a proper docstring!
168 """
168 """
169
169
170 # we'll use these a lot below
170 # we'll use these a lot below
171 lbase = len(base)
171 lbase = len(base)
172 pathsep = os.path.sep
172 pathsep = os.path.sep
173 lpathsep = len(pathsep)
173 lpathsep = len(pathsep)
174
174
175 out = []
175 out = []
176 for (dirpath,dirnames,filenames) in os.walk(base):
176 for (dirpath,dirnames,filenames) in os.walk(base):
177 # we need to strip out the dirpath from the base to map it to the
177 # we need to strip out the dirpath from the base to map it to the
178 # output (installation) path. This requires possibly stripping the
178 # output (installation) path. This requires possibly stripping the
179 # path separator, because otherwise pjoin will not work correctly
179 # path separator, because otherwise pjoin will not work correctly
180 # (pjoin('foo/','/bar') returns '/bar').
180 # (pjoin('foo/','/bar') returns '/bar').
181
181
182 dp_eff = dirpath[lbase:]
182 dp_eff = dirpath[lbase:]
183 if dp_eff.startswith(pathsep):
183 if dp_eff.startswith(pathsep):
184 dp_eff = dp_eff[lpathsep:]
184 dp_eff = dp_eff[lpathsep:]
185 # The output path must be anchored at the out_base marker
185 # The output path must be anchored at the out_base marker
186 out_path = pjoin(out_base,dp_eff)
186 out_path = pjoin(out_base,dp_eff)
187 # Now we can generate the final filenames. Since os.walk only produces
187 # Now we can generate the final filenames. Since os.walk only produces
188 # filenames, we must join back with the dirpath to get full valid file
188 # filenames, we must join back with the dirpath to get full valid file
189 # paths:
189 # paths:
190 pfiles = [pjoin(dirpath,f) for f in filenames]
190 pfiles = [pjoin(dirpath,f) for f in filenames]
191 # Finally, generate the entry we need, which is a pari of (output
191 # Finally, generate the entry we need, which is a pari of (output
192 # path, files) for use as a data_files parameter in install_data.
192 # path, files) for use as a data_files parameter in install_data.
193 out.append((out_path, pfiles))
193 out.append((out_path, pfiles))
194
194
195 return out
195 return out
196
196
197
197
198 def find_data_files():
198 def find_data_files():
199 """
199 """
200 Find IPython's data_files.
200 Find IPython's data_files.
201
201
202 Most of these are docs.
202 Most of these are docs.
203 """
203 """
204
204
205 docdirbase = pjoin('share', 'doc', 'ipython')
205 docdirbase = pjoin('share', 'doc', 'ipython')
206 manpagebase = pjoin('share', 'man', 'man1')
206 manpagebase = pjoin('share', 'man', 'man1')
207
207
208 # Simple file lists can be made by hand
208 # Simple file lists can be made by hand
209 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
209 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
210 if not manpages:
210 if not manpages:
211 # When running from a source tree, the manpages aren't gzipped
211 # When running from a source tree, the manpages aren't gzipped
212 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
212 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
213
213
214 igridhelpfiles = [f for f in glob(pjoin('IPython','extensions','igrid_help.*')) if isfile(f)]
214 igridhelpfiles = [f for f in glob(pjoin('IPython','extensions','igrid_help.*')) if isfile(f)]
215
215
216 # For nested structures, use the utility above
216 # For nested structures, use the utility above
217 example_files = make_dir_struct(
217 example_files = make_dir_struct(
218 'data',
218 'data',
219 pjoin('docs','examples'),
219 pjoin('docs','examples'),
220 pjoin(docdirbase,'examples')
220 pjoin(docdirbase,'examples')
221 )
221 )
222 manual_files = make_dir_struct(
222 manual_files = make_dir_struct(
223 'data',
223 'data',
224 pjoin('docs','html'),
224 pjoin('docs','html'),
225 pjoin(docdirbase,'manual')
225 pjoin(docdirbase,'manual')
226 )
226 )
227
227
228 # And assemble the entire output list
228 # And assemble the entire output list
229 data_files = [ (manpagebase, manpages),
229 data_files = [ (manpagebase, manpages),
230 (pjoin(docdirbase, 'extensions'), igridhelpfiles),
230 (pjoin(docdirbase, 'extensions'), igridhelpfiles),
231 ] + manual_files + example_files
231 ] + manual_files + example_files
232
232
233 return data_files
233 return data_files
234
234
235
235
236 def make_man_update_target(manpage):
236 def make_man_update_target(manpage):
237 """Return a target_update-compliant tuple for the given manpage.
237 """Return a target_update-compliant tuple for the given manpage.
238
238
239 Parameters
239 Parameters
240 ----------
240 ----------
241 manpage : string
241 manpage : string
242 Name of the manpage, must include the section number (trailing number).
242 Name of the manpage, must include the section number (trailing number).
243
243
244 Example
244 Example
245 -------
245 -------
246
246
247 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
247 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
248 ('docs/man/ipython.1.gz',
248 ('docs/man/ipython.1.gz',
249 ['docs/man/ipython.1'],
249 ['docs/man/ipython.1'],
250 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
250 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
251 """
251 """
252 man_dir = pjoin('docs', 'man')
252 man_dir = pjoin('docs', 'man')
253 manpage_gz = manpage + '.gz'
253 manpage_gz = manpage + '.gz'
254 manpath = pjoin(man_dir, manpage)
254 manpath = pjoin(man_dir, manpage)
255 manpath_gz = pjoin(man_dir, manpage_gz)
255 manpath_gz = pjoin(man_dir, manpage_gz)
256 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
256 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
257 locals() )
257 locals() )
258 return (manpath_gz, [manpath], gz_cmd)
258 return (manpath_gz, [manpath], gz_cmd)
259
259
260 # The two functions below are copied from IPython.utils.path, so we don't need
260 # The two functions below are copied from IPython.utils.path, so we don't need
261 # to import IPython during setup, which fails on Python 3.
261 # to import IPython during setup, which fails on Python 3.
262
262
263 def target_outdated(target,deps):
263 def target_outdated(target,deps):
264 """Determine whether a target is out of date.
264 """Determine whether a target is out of date.
265
265
266 target_outdated(target,deps) -> 1/0
266 target_outdated(target,deps) -> 1/0
267
267
268 deps: list of filenames which MUST exist.
268 deps: list of filenames which MUST exist.
269 target: single filename which may or may not exist.
269 target: single filename which may or may not exist.
270
270
271 If target doesn't exist or is older than any file listed in deps, return
271 If target doesn't exist or is older than any file listed in deps, return
272 true, otherwise return false.
272 true, otherwise return false.
273 """
273 """
274 try:
274 try:
275 target_time = os.path.getmtime(target)
275 target_time = os.path.getmtime(target)
276 except os.error:
276 except os.error:
277 return 1
277 return 1
278 for dep in deps:
278 for dep in deps:
279 dep_time = os.path.getmtime(dep)
279 dep_time = os.path.getmtime(dep)
280 if dep_time > target_time:
280 if dep_time > target_time:
281 #print "For target",target,"Dep failed:",dep # dbg
281 #print "For target",target,"Dep failed:",dep # dbg
282 #print "times (dep,tar):",dep_time,target_time # dbg
282 #print "times (dep,tar):",dep_time,target_time # dbg
283 return 1
283 return 1
284 return 0
284 return 0
285
285
286
286
287 def target_update(target,deps,cmd):
287 def target_update(target,deps,cmd):
288 """Update a target with a given command given a list of dependencies.
288 """Update a target with a given command given a list of dependencies.
289
289
290 target_update(target,deps,cmd) -> runs cmd if target is outdated.
290 target_update(target,deps,cmd) -> runs cmd if target is outdated.
291
291
292 This is just a wrapper around target_outdated() which calls the given
292 This is just a wrapper around target_outdated() which calls the given
293 command if target is outdated."""
293 command if target is outdated."""
294
294
295 if target_outdated(target,deps):
295 if target_outdated(target,deps):
296 os.system(cmd)
296 os.system(cmd)
297
297
298 #---------------------------------------------------------------------------
298 #---------------------------------------------------------------------------
299 # Find scripts
299 # Find scripts
300 #---------------------------------------------------------------------------
300 #---------------------------------------------------------------------------
301
301
302 def find_scripts(entry_points=False, suffix=''):
302 def find_scripts(entry_points=False, suffix=''):
303 """Find IPython's scripts.
303 """Find IPython's scripts.
304
304
305 if entry_points is True:
305 if entry_points is True:
306 return setuptools entry_point-style definitions
306 return setuptools entry_point-style definitions
307 else:
307 else:
308 return file paths of plain scripts [default]
308 return file paths of plain scripts [default]
309
309
310 suffix is appended to script names if entry_points is True, so that the
310 suffix is appended to script names if entry_points is True, so that the
311 Python 3 scripts get named "ipython3" etc.
311 Python 3 scripts get named "ipython3" etc.
312 """
312 """
313 if entry_points:
313 if entry_points:
314 console_scripts = [s % suffix for s in [
314 console_scripts = [s % suffix for s in [
315 'ipython%s = IPython.terminal.ipapp:launch_new_instance',
315 'ipython%s = IPython.terminal.ipapp:launch_new_instance',
316 'pycolor%s = IPython.utils.PyColorize:main',
316 'pycolor%s = IPython.utils.PyColorize:main',
317 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
317 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
318 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
318 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
319 'iplogger%s = IPython.parallel.apps.iploggerapp:launch_new_instance',
319 'iplogger%s = IPython.parallel.apps.iploggerapp:launch_new_instance',
320 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
320 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
321 'iptest%s = IPython.testing.iptest:main',
321 'iptest%s = IPython.testing.iptest:main',
322 'irunner%s = IPython.lib.irunner:main'
322 'irunner%s = IPython.lib.irunner:main'
323 ]]
323 ]]
324 gui_scripts = []
324 gui_scripts = []
325 scripts = dict(console_scripts=console_scripts, gui_scripts=gui_scripts)
325 scripts = dict(console_scripts=console_scripts, gui_scripts=gui_scripts)
326 else:
326 else:
327 parallel_scripts = pjoin('IPython','parallel','scripts')
327 parallel_scripts = pjoin('IPython','parallel','scripts')
328 main_scripts = pjoin('IPython','scripts')
328 main_scripts = pjoin('IPython','scripts')
329 scripts = [
329 scripts = [
330 pjoin(parallel_scripts, 'ipengine'),
330 pjoin(parallel_scripts, 'ipengine'),
331 pjoin(parallel_scripts, 'ipcontroller'),
331 pjoin(parallel_scripts, 'ipcontroller'),
332 pjoin(parallel_scripts, 'ipcluster'),
332 pjoin(parallel_scripts, 'ipcluster'),
333 pjoin(parallel_scripts, 'iplogger'),
333 pjoin(parallel_scripts, 'iplogger'),
334 pjoin(main_scripts, 'ipython'),
334 pjoin(main_scripts, 'ipython'),
335 pjoin(main_scripts, 'pycolor'),
335 pjoin(main_scripts, 'pycolor'),
336 pjoin(main_scripts, 'irunner'),
336 pjoin(main_scripts, 'irunner'),
337 pjoin(main_scripts, 'iptest')
337 pjoin(main_scripts, 'iptest')
338 ]
338 ]
339 return scripts
339 return scripts
340
340
341 #---------------------------------------------------------------------------
341 #---------------------------------------------------------------------------
342 # Verify all dependencies
342 # Verify all dependencies
343 #---------------------------------------------------------------------------
343 #---------------------------------------------------------------------------
344
344
345 def check_for_dependencies():
345 def check_for_dependencies():
346 """Check for IPython's dependencies.
346 """Check for IPython's dependencies.
347
347
348 This function should NOT be called if running under setuptools!
348 This function should NOT be called if running under setuptools!
349 """
349 """
350 from setupext.setupext import (
350 from setupext.setupext import (
351 print_line, print_raw, print_status,
351 print_line, print_raw, print_status,
352 check_for_sphinx, check_for_pygments,
352 check_for_sphinx, check_for_pygments,
353 check_for_nose, check_for_pexpect,
353 check_for_nose, check_for_pexpect,
354 check_for_pyzmq, check_for_readline
354 check_for_pyzmq, check_for_readline
355 )
355 )
356 print_line()
356 print_line()
357 print_raw("BUILDING IPYTHON")
357 print_raw("BUILDING IPYTHON")
358 print_status('python', sys.version)
358 print_status('python', sys.version)
359 print_status('platform', sys.platform)
359 print_status('platform', sys.platform)
360 if sys.platform == 'win32':
360 if sys.platform == 'win32':
361 print_status('Windows version', sys.getwindowsversion())
361 print_status('Windows version', sys.getwindowsversion())
362
362
363 print_raw("")
363 print_raw("")
364 print_raw("OPTIONAL DEPENDENCIES")
364 print_raw("OPTIONAL DEPENDENCIES")
365
365
366 check_for_sphinx()
366 check_for_sphinx()
367 check_for_pygments()
367 check_for_pygments()
368 check_for_nose()
368 check_for_nose()
369 check_for_pexpect()
369 check_for_pexpect()
370 check_for_pyzmq()
370 check_for_pyzmq()
371 check_for_readline()
371 check_for_readline()
372
372
373 #---------------------------------------------------------------------------
373 #---------------------------------------------------------------------------
374 # VCS related
374 # VCS related
375 #---------------------------------------------------------------------------
375 #---------------------------------------------------------------------------
376
376
377 # utils.submodule has checks for submodule status
377 # utils.submodule has checks for submodule status
378 execfile(pjoin('IPython','utils','submodule.py'), globals())
378 execfile(pjoin('IPython','utils','submodule.py'), globals())
379
379
380 class UpdateSubmodules(Command):
380 class UpdateSubmodules(Command):
381 """Update git submodules
381 """Update git submodules
382
382
383 IPython's external javascript dependencies live in a separate repo.
383 IPython's external javascript dependencies live in a separate repo.
384 """
384 """
385 description = "Update git submodules"
385 description = "Update git submodules"
386 user_options = []
386 user_options = []
387
387
388 def initialize_options(self):
388 def initialize_options(self):
389 pass
389 pass
390
390
391 def finalize_options(self):
391 def finalize_options(self):
392 pass
392 pass
393
393
394 def run(self):
394 def run(self):
395 failure = False
395 failure = False
396 try:
396 try:
397 self.spawn('git submodule init'.split())
397 self.spawn('git submodule init'.split())
398 self.spawn('git submodule update --recursive'.split())
398 self.spawn('git submodule update --recursive'.split())
399 except Exception as e:
399 except Exception as e:
400 failure = e
400 failure = e
401 print(e)
401 print(e)
402
402
403 if not check_submodule_status(repo_root) == 'clean':
403 if not check_submodule_status(repo_root) == 'clean':
404 print("submodules could not be checked out")
404 print("submodules could not be checked out")
405 sys.exit(1)
405 sys.exit(1)
406
406
407
407
408 def git_prebuild(pkg_dir, build_cmd=build_py):
408 def git_prebuild(pkg_dir, build_cmd=build_py):
409 """Return extended build or sdist command class for recording commit
409 """Return extended build or sdist command class for recording commit
410
410
411 records git commit in IPython.utils._sysinfo.commit
411 records git commit in IPython.utils._sysinfo.commit
412
412
413 for use in IPython.utils.sysinfo.sys_info() calls after installation.
413 for use in IPython.utils.sysinfo.sys_info() calls after installation.
414
414
415 Also ensures that submodules exist prior to running
415 Also ensures that submodules exist prior to running
416 """
416 """
417
417
418 class MyBuildPy(build_cmd):
418 class MyBuildPy(build_cmd):
419 ''' Subclass to write commit data into installation tree '''
419 ''' Subclass to write commit data into installation tree '''
420 def run(self):
420 def run(self):
421 build_cmd.run(self)
421 build_cmd.run(self)
422 # this one will only fire for build commands
422 # this one will only fire for build commands
423 if hasattr(self, 'build_lib'):
423 if hasattr(self, 'build_lib'):
424 self._record_commit(self.build_lib)
424 self._record_commit(self.build_lib)
425
425
426 def make_release_tree(self, base_dir, files):
426 def make_release_tree(self, base_dir, files):
427 # this one will fire for sdist
427 # this one will fire for sdist
428 build_cmd.make_release_tree(self, base_dir, files)
428 build_cmd.make_release_tree(self, base_dir, files)
429 self._record_commit(base_dir)
429 self._record_commit(base_dir)
430
430
431 def _record_commit(self, base_dir):
431 def _record_commit(self, base_dir):
432 import subprocess
432 import subprocess
433 proc = subprocess.Popen('git rev-parse --short HEAD',
433 proc = subprocess.Popen('git rev-parse --short HEAD',
434 stdout=subprocess.PIPE,
434 stdout=subprocess.PIPE,
435 stderr=subprocess.PIPE,
435 stderr=subprocess.PIPE,
436 shell=True)
436 shell=True)
437 repo_commit, _ = proc.communicate()
437 repo_commit, _ = proc.communicate()
438 repo_commit = repo_commit.strip().decode("ascii")
438 repo_commit = repo_commit.strip().decode("ascii")
439
439
440 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
440 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
441 if os.path.isfile(out_pth) and not repo_commit:
441 if os.path.isfile(out_pth) and not repo_commit:
442 # nothing to write, don't clobber
442 # nothing to write, don't clobber
443 return
443 return
444
444
445 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
445 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
446
446
447 # remove to avoid overwriting original via hard link
447 # remove to avoid overwriting original via hard link
448 try:
448 try:
449 os.remove(out_pth)
449 os.remove(out_pth)
450 except (IOError, OSError):
450 except (IOError, OSError):
451 pass
451 pass
452 with open(out_pth, 'w') as out_file:
452 with open(out_pth, 'w') as out_file:
453 out_file.writelines([
453 out_file.writelines([
454 '# GENERATED BY setup.py\n',
454 '# GENERATED BY setup.py\n',
455 'commit = "%s"\n' % repo_commit,
455 'commit = "%s"\n' % repo_commit,
456 ])
456 ])
457 return require_submodules(MyBuildPy)
457 return require_submodules(MyBuildPy)
458
458
459
459
460 def require_submodules(command):
460 def require_submodules(command):
461 """decorator for instructing a command to check for submodules before running"""
461 """decorator for instructing a command to check for submodules before running"""
462 class DecoratedCommand(command):
462 class DecoratedCommand(command):
463 def run(self):
463 def run(self):
464 if not check_submodule_status(repo_root) == 'clean':
464 if not check_submodule_status(repo_root) == 'clean':
465 print("submodules missing! Run `setup.py submodule` and try again")
465 print("submodules missing! Run `setup.py submodule` and try again")
466 sys.exit(1)
466 sys.exit(1)
467 command.run(self)
467 command.run(self)
468 return DecoratedCommand
468 return DecoratedCommand
General Comments 0
You need to be logged in to leave comments. Login now