##// END OF EJS Templates
Python 3 compatibility for os.getcwdu()
Thomas Kluyver -
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,381 +1,382 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for IPython.
4 4
5 5 All top-level applications should use the classes in this module for
6 6 handling configuration and creating configurables.
7 7
8 8 The job of an :class:`Application` is to create the master configuration
9 9 object and then create the configurable objects, passing the config to them.
10 10
11 11 Authors:
12 12
13 13 * Brian Granger
14 14 * Fernando Perez
15 15 * Min RK
16 16
17 17 """
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Copyright (C) 2008 The IPython Development Team
21 21 #
22 22 # Distributed under the terms of the BSD License. The full license is in
23 23 # the file COPYING, distributed as part of this software.
24 24 #-----------------------------------------------------------------------------
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Imports
28 28 #-----------------------------------------------------------------------------
29 29
30 30 import atexit
31 31 import errno
32 32 import glob
33 33 import logging
34 34 import os
35 35 import shutil
36 36 import sys
37 37
38 38 from IPython.config.application import Application, catch_config_error
39 39 from IPython.config.loader import ConfigFileNotFound
40 40 from IPython.core import release, crashhandler
41 41 from IPython.core.profiledir import ProfileDir, ProfileDirError
42 42 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
43 from IPython.utils import py3compat
43 44 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance
44 45
45 46 #-----------------------------------------------------------------------------
46 47 # Classes and functions
47 48 #-----------------------------------------------------------------------------
48 49
49 50
50 51 #-----------------------------------------------------------------------------
51 52 # Base Application Class
52 53 #-----------------------------------------------------------------------------
53 54
54 55 # aliases and flags
55 56
56 57 base_aliases = {
57 58 'profile-dir' : 'ProfileDir.location',
58 59 'profile' : 'BaseIPythonApplication.profile',
59 60 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
60 61 'log-level' : 'Application.log_level',
61 62 'config' : 'BaseIPythonApplication.extra_config_file',
62 63 }
63 64
64 65 base_flags = dict(
65 66 debug = ({'Application' : {'log_level' : logging.DEBUG}},
66 67 "set log level to logging.DEBUG (maximize logging output)"),
67 68 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
68 69 "set log level to logging.CRITICAL (minimize logging output)"),
69 70 init = ({'BaseIPythonApplication' : {
70 71 'copy_config_files' : True,
71 72 'auto_create' : True}
72 73 }, """Initialize profile with default config files. This is equivalent
73 74 to running `ipython profile create <profile>` prior to startup.
74 75 """)
75 76 )
76 77
77 78
78 79 class BaseIPythonApplication(Application):
79 80
80 81 name = Unicode(u'ipython')
81 82 description = Unicode(u'IPython: an enhanced interactive Python shell.')
82 83 version = Unicode(release.version)
83 84
84 85 aliases = Dict(base_aliases)
85 86 flags = Dict(base_flags)
86 87 classes = List([ProfileDir])
87 88
88 89 # Track whether the config_file has changed,
89 90 # because some logic happens only if we aren't using the default.
90 91 config_file_specified = Set()
91 92
92 93 config_file_name = Unicode()
93 94 def _config_file_name_default(self):
94 95 return self.name.replace('-','_') + u'_config.py'
95 96 def _config_file_name_changed(self, name, old, new):
96 97 if new != old:
97 98 self.config_file_specified.add(new)
98 99
99 100 # The directory that contains IPython's builtin profiles.
100 101 builtin_profile_dir = Unicode(
101 102 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
102 103 )
103 104
104 105 config_file_paths = List(Unicode)
105 106 def _config_file_paths_default(self):
106 return [os.getcwdu()]
107 return [py3compat.getcwd()]
107 108
108 109 extra_config_file = Unicode(config=True,
109 110 help="""Path to an extra config file to load.
110 111
111 112 If specified, load this config file in addition to any other IPython config.
112 113 """)
113 114 def _extra_config_file_changed(self, name, old, new):
114 115 try:
115 116 self.config_files.remove(old)
116 117 except ValueError:
117 118 pass
118 119 self.config_file_specified.add(new)
119 120 self.config_files.append(new)
120 121
121 122 profile = Unicode(u'default', config=True,
122 123 help="""The IPython profile to use."""
123 124 )
124 125
125 126 def _profile_changed(self, name, old, new):
126 127 self.builtin_profile_dir = os.path.join(
127 128 get_ipython_package_dir(), u'config', u'profile', new
128 129 )
129 130
130 131 ipython_dir = Unicode(config=True,
131 132 help="""
132 133 The name of the IPython directory. This directory is used for logging
133 134 configuration (through profiles), history storage, etc. The default
134 135 is usually $HOME/.ipython. This options can also be specified through
135 136 the environment variable IPYTHONDIR.
136 137 """
137 138 )
138 139 def _ipython_dir_default(self):
139 140 d = get_ipython_dir()
140 141 self._ipython_dir_changed('ipython_dir', d, d)
141 142 return d
142 143
143 144 _in_init_profile_dir = False
144 145 profile_dir = Instance(ProfileDir)
145 146 def _profile_dir_default(self):
146 147 # avoid recursion
147 148 if self._in_init_profile_dir:
148 149 return
149 150 # profile_dir requested early, force initialization
150 151 self.init_profile_dir()
151 152 return self.profile_dir
152 153
153 154 overwrite = Bool(False, config=True,
154 155 help="""Whether to overwrite existing config files when copying""")
155 156 auto_create = Bool(False, config=True,
156 157 help="""Whether to create profile dir if it doesn't exist""")
157 158
158 159 config_files = List(Unicode)
159 160 def _config_files_default(self):
160 161 return [self.config_file_name]
161 162
162 163 copy_config_files = Bool(False, config=True,
163 164 help="""Whether to install the default config files into the profile dir.
164 165 If a new profile is being created, and IPython contains config files for that
165 166 profile, then they will be staged into the new directory. Otherwise,
166 167 default config files will be automatically generated.
167 168 """)
168 169
169 170 verbose_crash = Bool(False, config=True,
170 171 help="""Create a massive crash report when IPython encounters what may be an
171 172 internal error. The default is to append a short message to the
172 173 usual traceback""")
173 174
174 175 # The class to use as the crash handler.
175 176 crash_handler_class = Type(crashhandler.CrashHandler)
176 177
177 178 @catch_config_error
178 179 def __init__(self, **kwargs):
179 180 super(BaseIPythonApplication, self).__init__(**kwargs)
180 181 # ensure current working directory exists
181 182 try:
182 directory = os.getcwdu()
183 directory = py3compat.getcwd()
183 184 except:
184 185 # raise exception
185 186 self.log.error("Current working directory doesn't exist.")
186 187 raise
187 188
188 189 #-------------------------------------------------------------------------
189 190 # Various stages of Application creation
190 191 #-------------------------------------------------------------------------
191 192
192 193 def init_crash_handler(self):
193 194 """Create a crash handler, typically setting sys.excepthook to it."""
194 195 self.crash_handler = self.crash_handler_class(self)
195 196 sys.excepthook = self.excepthook
196 197 def unset_crashhandler():
197 198 sys.excepthook = sys.__excepthook__
198 199 atexit.register(unset_crashhandler)
199 200
200 201 def excepthook(self, etype, evalue, tb):
201 202 """this is sys.excepthook after init_crashhandler
202 203
203 204 set self.verbose_crash=True to use our full crashhandler, instead of
204 205 a regular traceback with a short message (crash_handler_lite)
205 206 """
206 207
207 208 if self.verbose_crash:
208 209 return self.crash_handler(etype, evalue, tb)
209 210 else:
210 211 return crashhandler.crash_handler_lite(etype, evalue, tb)
211 212
212 213 def _ipython_dir_changed(self, name, old, new):
213 214 if old in sys.path:
214 215 sys.path.remove(old)
215 216 sys.path.append(os.path.abspath(new))
216 217 if not os.path.isdir(new):
217 218 os.makedirs(new, mode=0o777)
218 219 readme = os.path.join(new, 'README')
219 220 readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README')
220 221 if not os.path.exists(readme) and os.path.exists(readme_src):
221 222 shutil.copy(readme_src, readme)
222 223 for d in ('extensions', 'nbextensions'):
223 224 path = os.path.join(new, d)
224 225 if not os.path.exists(path):
225 226 try:
226 227 os.mkdir(path)
227 228 except OSError as e:
228 229 if e.errno != errno.EEXIST:
229 230 self.log.error("couldn't create path %s: %s", path, e)
230 231 self.log.debug("IPYTHONDIR set to: %s" % new)
231 232
232 233 def load_config_file(self, suppress_errors=True):
233 234 """Load the config file.
234 235
235 236 By default, errors in loading config are handled, and a warning
236 237 printed on screen. For testing, the suppress_errors option is set
237 238 to False, so errors will make tests fail.
238 239 """
239 240 self.log.debug("Searching path %s for config files", self.config_file_paths)
240 241 base_config = 'ipython_config.py'
241 242 self.log.debug("Attempting to load config file: %s" %
242 243 base_config)
243 244 try:
244 245 Application.load_config_file(
245 246 self,
246 247 base_config,
247 248 path=self.config_file_paths
248 249 )
249 250 except ConfigFileNotFound:
250 251 # ignore errors loading parent
251 252 self.log.debug("Config file %s not found", base_config)
252 253 pass
253 254
254 255 for config_file_name in self.config_files:
255 256 if not config_file_name or config_file_name == base_config:
256 257 continue
257 258 self.log.debug("Attempting to load config file: %s" %
258 259 self.config_file_name)
259 260 try:
260 261 Application.load_config_file(
261 262 self,
262 263 config_file_name,
263 264 path=self.config_file_paths
264 265 )
265 266 except ConfigFileNotFound:
266 267 # Only warn if the default config file was NOT being used.
267 268 if config_file_name in self.config_file_specified:
268 269 msg = self.log.warn
269 270 else:
270 271 msg = self.log.debug
271 272 msg("Config file not found, skipping: %s", config_file_name)
272 273 except:
273 274 # For testing purposes.
274 275 if not suppress_errors:
275 276 raise
276 277 self.log.warn("Error loading config file: %s" %
277 278 self.config_file_name, exc_info=True)
278 279
279 280 def init_profile_dir(self):
280 281 """initialize the profile dir"""
281 282 self._in_init_profile_dir = True
282 283 if self.profile_dir is not None:
283 284 # already ran
284 285 return
285 286 if 'ProfileDir.location' not in self.config:
286 287 # location not specified, find by profile name
287 288 try:
288 289 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
289 290 except ProfileDirError:
290 291 # not found, maybe create it (always create default profile)
291 292 if self.auto_create or self.profile == 'default':
292 293 try:
293 294 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
294 295 except ProfileDirError:
295 296 self.log.fatal("Could not create profile: %r"%self.profile)
296 297 self.exit(1)
297 298 else:
298 299 self.log.info("Created profile dir: %r"%p.location)
299 300 else:
300 301 self.log.fatal("Profile %r not found."%self.profile)
301 302 self.exit(1)
302 303 else:
303 304 self.log.info("Using existing profile dir: %r"%p.location)
304 305 else:
305 306 location = self.config.ProfileDir.location
306 307 # location is fully specified
307 308 try:
308 309 p = ProfileDir.find_profile_dir(location, self.config)
309 310 except ProfileDirError:
310 311 # not found, maybe create it
311 312 if self.auto_create:
312 313 try:
313 314 p = ProfileDir.create_profile_dir(location, self.config)
314 315 except ProfileDirError:
315 316 self.log.fatal("Could not create profile directory: %r"%location)
316 317 self.exit(1)
317 318 else:
318 319 self.log.info("Creating new profile dir: %r"%location)
319 320 else:
320 321 self.log.fatal("Profile directory %r not found."%location)
321 322 self.exit(1)
322 323 else:
323 324 self.log.info("Using existing profile dir: %r"%location)
324 325
325 326 self.profile_dir = p
326 327 self.config_file_paths.append(p.location)
327 328 self._in_init_profile_dir = False
328 329
329 330 def init_config_files(self):
330 331 """[optionally] copy default config files into profile dir."""
331 332 # copy config files
332 333 path = self.builtin_profile_dir
333 334 if self.copy_config_files:
334 335 src = self.profile
335 336
336 337 cfg = self.config_file_name
337 338 if path and os.path.exists(os.path.join(path, cfg)):
338 339 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
339 340 cfg, src, self.profile_dir.location, self.overwrite)
340 341 )
341 342 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
342 343 else:
343 344 self.stage_default_config_file()
344 345 else:
345 346 # Still stage *bundled* config files, but not generated ones
346 347 # This is necessary for `ipython profile=sympy` to load the profile
347 348 # on the first go
348 349 files = glob.glob(os.path.join(path, '*.py'))
349 350 for fullpath in files:
350 351 cfg = os.path.basename(fullpath)
351 352 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
352 353 # file was copied
353 354 self.log.warn("Staging bundled %s from %s into %r"%(
354 355 cfg, self.profile, self.profile_dir.location)
355 356 )
356 357
357 358
358 359 def stage_default_config_file(self):
359 360 """auto generate default config file, and stage it into the profile."""
360 361 s = self.generate_config_file()
361 362 fname = os.path.join(self.profile_dir.location, self.config_file_name)
362 363 if self.overwrite or not os.path.exists(fname):
363 364 self.log.warn("Generating default config file: %r"%(fname))
364 365 with open(fname, 'w') as f:
365 366 f.write(s)
366 367
367 368 @catch_config_error
368 369 def initialize(self, argv=None):
369 370 # don't hook up crash handler before parsing command-line
370 371 self.parse_command_line(argv)
371 372 self.init_crash_handler()
372 373 if self.subapp is not None:
373 374 # stop here if subapp is taking over
374 375 return
375 376 cl_config = self.config
376 377 self.init_profile_dir()
377 378 self.init_config_files()
378 379 self.load_config_file()
379 380 # enforce cl-opts override configfile opts:
380 381 self.update_config(cl_config)
381 382
@@ -1,216 +1,216 b''
1 1 # encoding: utf-8
2 2 """sys.excepthook for IPython itself, leaves a detailed report on disk.
3 3
4 4 Authors:
5 5
6 6 * Fernando Perez
7 7 * Brian E. Granger
8 8 """
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
12 12 # Copyright (C) 2008-2011 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is in
15 15 # the file COPYING, distributed as part of this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21 from __future__ import print_function
22 22
23 23 import os
24 24 import sys
25 25 import traceback
26 26 from pprint import pformat
27 27
28 28 from IPython.core import ultratb
29 29 from IPython.core.release import author_email
30 30 from IPython.utils.sysinfo import sys_info
31 from IPython.utils.py3compat import input
31 from IPython.utils.py3compat import input, getcwd
32 32
33 33 #-----------------------------------------------------------------------------
34 34 # Code
35 35 #-----------------------------------------------------------------------------
36 36
37 37 # Template for the user message.
38 38 _default_message_template = """\
39 39 Oops, {app_name} crashed. We do our best to make it stable, but...
40 40
41 41 A crash report was automatically generated with the following information:
42 42 - A verbatim copy of the crash traceback.
43 43 - A copy of your input history during this session.
44 44 - Data on your current {app_name} configuration.
45 45
46 46 It was left in the file named:
47 47 \t'{crash_report_fname}'
48 48 If you can email this file to the developers, the information in it will help
49 49 them in understanding and correcting the problem.
50 50
51 51 You can mail it to: {contact_name} at {contact_email}
52 52 with the subject '{app_name} Crash Report'.
53 53
54 54 If you want to do it now, the following command will work (under Unix):
55 55 mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname}
56 56
57 57 To ensure accurate tracking of this issue, please file a report about it at:
58 58 {bug_tracker}
59 59 """
60 60
61 61 _lite_message_template = """
62 62 If you suspect this is an IPython bug, please report it at:
63 63 https://github.com/ipython/ipython/issues
64 64 or send an email to the mailing list at {email}
65 65
66 66 You can print a more detailed traceback right now with "%tb", or use "%debug"
67 67 to interactively debug it.
68 68
69 69 Extra-detailed tracebacks for bug-reporting purposes can be enabled via:
70 70 {config}Application.verbose_crash=True
71 71 """
72 72
73 73
74 74 class CrashHandler(object):
75 75 """Customizable crash handlers for IPython applications.
76 76
77 77 Instances of this class provide a :meth:`__call__` method which can be
78 78 used as a ``sys.excepthook``. The :meth:`__call__` signature is::
79 79
80 80 def __call__(self, etype, evalue, etb)
81 81 """
82 82
83 83 message_template = _default_message_template
84 84 section_sep = '\n\n'+'*'*75+'\n\n'
85 85
86 86 def __init__(self, app, contact_name=None, contact_email=None,
87 87 bug_tracker=None, show_crash_traceback=True, call_pdb=False):
88 88 """Create a new crash handler
89 89
90 90 Parameters
91 91 ----------
92 92 app : Application
93 93 A running :class:`Application` instance, which will be queried at
94 94 crash time for internal information.
95 95
96 96 contact_name : str
97 97 A string with the name of the person to contact.
98 98
99 99 contact_email : str
100 100 A string with the email address of the contact.
101 101
102 102 bug_tracker : str
103 103 A string with the URL for your project's bug tracker.
104 104
105 105 show_crash_traceback : bool
106 106 If false, don't print the crash traceback on stderr, only generate
107 107 the on-disk report
108 108
109 109 Non-argument instance attributes:
110 110
111 111 These instances contain some non-argument attributes which allow for
112 112 further customization of the crash handler's behavior. Please see the
113 113 source for further details.
114 114 """
115 115 self.crash_report_fname = "Crash_report_%s.txt" % app.name
116 116 self.app = app
117 117 self.call_pdb = call_pdb
118 118 #self.call_pdb = True # dbg
119 119 self.show_crash_traceback = show_crash_traceback
120 120 self.info = dict(app_name = app.name,
121 121 contact_name = contact_name,
122 122 contact_email = contact_email,
123 123 bug_tracker = bug_tracker,
124 124 crash_report_fname = self.crash_report_fname)
125 125
126 126
127 127 def __call__(self, etype, evalue, etb):
128 128 """Handle an exception, call for compatible with sys.excepthook"""
129 129
130 130 # do not allow the crash handler to be called twice without reinstalling it
131 131 # this prevents unlikely errors in the crash handling from entering an
132 132 # infinite loop.
133 133 sys.excepthook = sys.__excepthook__
134 134
135 135 # Report tracebacks shouldn't use color in general (safer for users)
136 136 color_scheme = 'NoColor'
137 137
138 138 # Use this ONLY for developer debugging (keep commented out for release)
139 139 #color_scheme = 'Linux' # dbg
140 140 try:
141 141 rptdir = self.app.ipython_dir
142 142 except:
143 rptdir = os.getcwdu()
143 rptdir = getcwd()
144 144 if rptdir is None or not os.path.isdir(rptdir):
145 rptdir = os.getcwdu()
145 rptdir = getcwd()
146 146 report_name = os.path.join(rptdir,self.crash_report_fname)
147 147 # write the report filename into the instance dict so it can get
148 148 # properly expanded out in the user message template
149 149 self.crash_report_fname = report_name
150 150 self.info['crash_report_fname'] = report_name
151 151 TBhandler = ultratb.VerboseTB(
152 152 color_scheme=color_scheme,
153 153 long_header=1,
154 154 call_pdb=self.call_pdb,
155 155 )
156 156 if self.call_pdb:
157 157 TBhandler(etype,evalue,etb)
158 158 return
159 159 else:
160 160 traceback = TBhandler.text(etype,evalue,etb,context=31)
161 161
162 162 # print traceback to screen
163 163 if self.show_crash_traceback:
164 164 print(traceback, file=sys.stderr)
165 165
166 166 # and generate a complete report on disk
167 167 try:
168 168 report = open(report_name,'w')
169 169 except:
170 170 print('Could not create crash report on disk.', file=sys.stderr)
171 171 return
172 172
173 173 # Inform user on stderr of what happened
174 174 print('\n'+'*'*70+'\n', file=sys.stderr)
175 175 print(self.message_template.format(**self.info), file=sys.stderr)
176 176
177 177 # Construct report on disk
178 178 report.write(self.make_report(traceback))
179 179 report.close()
180 180 input("Hit <Enter> to quit (your terminal may close):")
181 181
182 182 def make_report(self,traceback):
183 183 """Return a string containing a crash report."""
184 184
185 185 sec_sep = self.section_sep
186 186
187 187 report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
188 188 rpt_add = report.append
189 189 rpt_add(sys_info())
190 190
191 191 try:
192 192 config = pformat(self.app.config)
193 193 rpt_add(sec_sep)
194 194 rpt_add('Application name: %s\n\n' % self.app_name)
195 195 rpt_add('Current user configuration structure:\n\n')
196 196 rpt_add(config)
197 197 except:
198 198 pass
199 199 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
200 200
201 201 return ''.join(report)
202 202
203 203
204 204 def crash_handler_lite(etype, evalue, tb):
205 205 """a light excepthook, adding a small message to the usual traceback"""
206 206 traceback.print_exception(etype, evalue, tb)
207 207
208 208 from IPython.core.interactiveshell import InteractiveShell
209 209 if InteractiveShell.initialized():
210 210 # we are in a Shell environment, give %magic example
211 211 config = "%config "
212 212 else:
213 213 # we are not in a shell, show generic config
214 214 config = "c."
215 215 print(_lite_message_template.format(email=author_email, config=config), file=sys.stderr)
216 216
@@ -1,807 +1,808 b''
1 1 """ History related magics and functionality """
2 2 #-----------------------------------------------------------------------------
3 3 # Copyright (C) 2010-2011 The IPython Development Team.
4 4 #
5 5 # Distributed under the terms of the BSD License.
6 6 #
7 7 # The full license is in the file COPYING.txt, distributed with this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13 from __future__ import print_function
14 14
15 15 # Stdlib imports
16 16 import atexit
17 17 import datetime
18 18 import os
19 19 import re
20 20 try:
21 21 import sqlite3
22 22 except ImportError:
23 23 try:
24 24 from pysqlite2 import dbapi2 as sqlite3
25 25 except ImportError:
26 26 sqlite3 = None
27 27 import threading
28 28
29 29 # Our own packages
30 30 from IPython.config.configurable import Configurable
31 31 from IPython.external.decorator import decorator
32 32 from IPython.utils.path import locate_profile
33 from IPython.utils import py3compat
33 34 from IPython.utils.traitlets import (
34 35 Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
35 36 )
36 37 from IPython.utils.warn import warn
37 38
38 39 #-----------------------------------------------------------------------------
39 40 # Classes and functions
40 41 #-----------------------------------------------------------------------------
41 42
42 43 class DummyDB(object):
43 44 """Dummy DB that will act as a black hole for history.
44 45
45 46 Only used in the absence of sqlite"""
46 47 def execute(*args, **kwargs):
47 48 return []
48 49
49 50 def commit(self, *args, **kwargs):
50 51 pass
51 52
52 53 def __enter__(self, *args, **kwargs):
53 54 pass
54 55
55 56 def __exit__(self, *args, **kwargs):
56 57 pass
57 58
58 59
59 60 @decorator
60 61 def needs_sqlite(f, self, *a, **kw):
61 62 """return an empty list in the absence of sqlite"""
62 63 if sqlite3 is None or not self.enabled:
63 64 return []
64 65 else:
65 66 return f(self, *a, **kw)
66 67
67 68
68 69 if sqlite3 is not None:
69 70 DatabaseError = sqlite3.DatabaseError
70 71 else:
71 72 class DatabaseError(Exception):
72 73 "Dummy exception when sqlite could not be imported. Should never occur."
73 74
74 75 @decorator
75 76 def catch_corrupt_db(f, self, *a, **kw):
76 77 """A decorator which wraps HistoryAccessor method calls to catch errors from
77 78 a corrupt SQLite database, move the old database out of the way, and create
78 79 a new one.
79 80 """
80 81 try:
81 82 return f(self, *a, **kw)
82 83 except DatabaseError:
83 84 if os.path.isfile(self.hist_file):
84 85 # Try to move the file out of the way
85 86 base,ext = os.path.splitext(self.hist_file)
86 87 newpath = base + '-corrupt' + ext
87 88 os.rename(self.hist_file, newpath)
88 89 self.init_db()
89 90 print("ERROR! History file wasn't a valid SQLite database.",
90 91 "It was moved to %s" % newpath, "and a new file created.")
91 92 return []
92 93
93 94 else:
94 95 # The hist_file is probably :memory: or something else.
95 96 raise
96 97
97 98
98 99
99 100 class HistoryAccessor(Configurable):
100 101 """Access the history database without adding to it.
101 102
102 103 This is intended for use by standalone history tools. IPython shells use
103 104 HistoryManager, below, which is a subclass of this."""
104 105
105 106 # String holding the path to the history file
106 107 hist_file = Unicode(config=True,
107 108 help="""Path to file to use for SQLite history database.
108 109
109 110 By default, IPython will put the history database in the IPython
110 111 profile directory. If you would rather share one history among
111 112 profiles, you can set this value in each, so that they are consistent.
112 113
113 114 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
114 115 mounts. If you see IPython hanging, try setting this to something on a
115 116 local disk, e.g::
116 117
117 118 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
118 119
119 120 """)
120 121
121 122 enabled = Bool(True, config=True,
122 123 help="""enable the SQLite history
123 124
124 125 set enabled=False to disable the SQLite history,
125 126 in which case there will be no stored history, no SQLite connection,
126 127 and no background saving thread. This may be necessary in some
127 128 threaded environments where IPython is embedded.
128 129 """
129 130 )
130 131
131 132 connection_options = Dict(config=True,
132 133 help="""Options for configuring the SQLite connection
133 134
134 135 These options are passed as keyword args to sqlite3.connect
135 136 when establishing database conenctions.
136 137 """
137 138 )
138 139
139 140 # The SQLite database
140 141 db = Any()
141 142 def _db_changed(self, name, old, new):
142 143 """validate the db, since it can be an Instance of two different types"""
143 144 connection_types = (DummyDB,)
144 145 if sqlite3 is not None:
145 146 connection_types = (DummyDB, sqlite3.Connection)
146 147 if not isinstance(new, connection_types):
147 148 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
148 149 (self.__class__.__name__, new)
149 150 raise TraitError(msg)
150 151
151 152 def __init__(self, profile='default', hist_file=u'', **traits):
152 153 """Create a new history accessor.
153 154
154 155 Parameters
155 156 ----------
156 157 profile : str
157 158 The name of the profile from which to open history.
158 159 hist_file : str
159 160 Path to an SQLite history database stored by IPython. If specified,
160 161 hist_file overrides profile.
161 162 config :
162 163 Config object. hist_file can also be set through this.
163 164 """
164 165 # We need a pointer back to the shell for various tasks.
165 166 super(HistoryAccessor, self).__init__(**traits)
166 167 # defer setting hist_file from kwarg until after init,
167 168 # otherwise the default kwarg value would clobber any value
168 169 # set by config
169 170 if hist_file:
170 171 self.hist_file = hist_file
171 172
172 173 if self.hist_file == u'':
173 174 # No one has set the hist_file, yet.
174 175 self.hist_file = self._get_hist_file_name(profile)
175 176
176 177 if sqlite3 is None and self.enabled:
177 178 warn("IPython History requires SQLite, your history will not be saved")
178 179 self.enabled = False
179 180
180 181 self.init_db()
181 182
182 183 def _get_hist_file_name(self, profile='default'):
183 184 """Find the history file for the given profile name.
184 185
185 186 This is overridden by the HistoryManager subclass, to use the shell's
186 187 active profile.
187 188
188 189 Parameters
189 190 ----------
190 191 profile : str
191 192 The name of a profile which has a history file.
192 193 """
193 194 return os.path.join(locate_profile(profile), 'history.sqlite')
194 195
195 196 @catch_corrupt_db
196 197 def init_db(self):
197 198 """Connect to the database, and create tables if necessary."""
198 199 if not self.enabled:
199 200 self.db = DummyDB()
200 201 return
201 202
202 203 # use detect_types so that timestamps return datetime objects
203 204 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
204 205 kwargs.update(self.connection_options)
205 206 self.db = sqlite3.connect(self.hist_file, **kwargs)
206 207 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
207 208 primary key autoincrement, start timestamp,
208 209 end timestamp, num_cmds integer, remark text)""")
209 210 self.db.execute("""CREATE TABLE IF NOT EXISTS history
210 211 (session integer, line integer, source text, source_raw text,
211 212 PRIMARY KEY (session, line))""")
212 213 # Output history is optional, but ensure the table's there so it can be
213 214 # enabled later.
214 215 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
215 216 (session integer, line integer, output text,
216 217 PRIMARY KEY (session, line))""")
217 218 self.db.commit()
218 219
219 220 def writeout_cache(self):
220 221 """Overridden by HistoryManager to dump the cache before certain
221 222 database lookups."""
222 223 pass
223 224
224 225 ## -------------------------------
225 226 ## Methods for retrieving history:
226 227 ## -------------------------------
227 228 def _run_sql(self, sql, params, raw=True, output=False):
228 229 """Prepares and runs an SQL query for the history database.
229 230
230 231 Parameters
231 232 ----------
232 233 sql : str
233 234 Any filtering expressions to go after SELECT ... FROM ...
234 235 params : tuple
235 236 Parameters passed to the SQL query (to replace "?")
236 237 raw, output : bool
237 238 See :meth:`get_range`
238 239
239 240 Returns
240 241 -------
241 242 Tuples as :meth:`get_range`
242 243 """
243 244 toget = 'source_raw' if raw else 'source'
244 245 sqlfrom = "history"
245 246 if output:
246 247 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
247 248 toget = "history.%s, output_history.output" % toget
248 249 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
249 250 (toget, sqlfrom) + sql, params)
250 251 if output: # Regroup into 3-tuples, and parse JSON
251 252 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
252 253 return cur
253 254
254 255 @needs_sqlite
255 256 @catch_corrupt_db
256 257 def get_session_info(self, session=0):
257 258 """get info about a session
258 259
259 260 Parameters
260 261 ----------
261 262
262 263 session : int
263 264 Session number to retrieve. The current session is 0, and negative
264 265 numbers count back from current session, so -1 is previous session.
265 266
266 267 Returns
267 268 -------
268 269
269 270 (session_id [int], start [datetime], end [datetime], num_cmds [int],
270 271 remark [unicode])
271 272
272 273 Sessions that are running or did not exit cleanly will have `end=None`
273 274 and `num_cmds=None`.
274 275
275 276 """
276 277
277 278 if session <= 0:
278 279 session += self.session_number
279 280
280 281 query = "SELECT * from sessions where session == ?"
281 282 return self.db.execute(query, (session,)).fetchone()
282 283
283 284 @catch_corrupt_db
284 285 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
285 286 """Get the last n lines from the history database.
286 287
287 288 Parameters
288 289 ----------
289 290 n : int
290 291 The number of lines to get
291 292 raw, output : bool
292 293 See :meth:`get_range`
293 294 include_latest : bool
294 295 If False (default), n+1 lines are fetched, and the latest one
295 296 is discarded. This is intended to be used where the function
296 297 is called by a user command, which it should not return.
297 298
298 299 Returns
299 300 -------
300 301 Tuples as :meth:`get_range`
301 302 """
302 303 self.writeout_cache()
303 304 if not include_latest:
304 305 n += 1
305 306 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
306 307 (n,), raw=raw, output=output)
307 308 if not include_latest:
308 309 return reversed(list(cur)[1:])
309 310 return reversed(list(cur))
310 311
311 312 @catch_corrupt_db
312 313 def search(self, pattern="*", raw=True, search_raw=True,
313 314 output=False, n=None, unique=False):
314 315 """Search the database using unix glob-style matching (wildcards
315 316 * and ?).
316 317
317 318 Parameters
318 319 ----------
319 320 pattern : str
320 321 The wildcarded pattern to match when searching
321 322 search_raw : bool
322 323 If True, search the raw input, otherwise, the parsed input
323 324 raw, output : bool
324 325 See :meth:`get_range`
325 326 n : None or int
326 327 If an integer is given, it defines the limit of
327 328 returned entries.
328 329 unique : bool
329 330 When it is true, return only unique entries.
330 331
331 332 Returns
332 333 -------
333 334 Tuples as :meth:`get_range`
334 335 """
335 336 tosearch = "source_raw" if search_raw else "source"
336 337 if output:
337 338 tosearch = "history." + tosearch
338 339 self.writeout_cache()
339 340 sqlform = "WHERE %s GLOB ?" % tosearch
340 341 params = (pattern,)
341 342 if unique:
342 343 sqlform += ' GROUP BY {0}'.format(tosearch)
343 344 if n is not None:
344 345 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
345 346 params += (n,)
346 347 elif unique:
347 348 sqlform += " ORDER BY session, line"
348 349 cur = self._run_sql(sqlform, params, raw=raw, output=output)
349 350 if n is not None:
350 351 return reversed(list(cur))
351 352 return cur
352 353
353 354 @catch_corrupt_db
354 355 def get_range(self, session, start=1, stop=None, raw=True,output=False):
355 356 """Retrieve input by session.
356 357
357 358 Parameters
358 359 ----------
359 360 session : int
360 361 Session number to retrieve.
361 362 start : int
362 363 First line to retrieve.
363 364 stop : int
364 365 End of line range (excluded from output itself). If None, retrieve
365 366 to the end of the session.
366 367 raw : bool
367 368 If True, return untranslated input
368 369 output : bool
369 370 If True, attempt to include output. This will be 'real' Python
370 371 objects for the current session, or text reprs from previous
371 372 sessions if db_log_output was enabled at the time. Where no output
372 373 is found, None is used.
373 374
374 375 Returns
375 376 -------
376 377 An iterator over the desired lines. Each line is a 3-tuple, either
377 378 (session, line, input) if output is False, or
378 379 (session, line, (input, output)) if output is True.
379 380 """
380 381 if stop:
381 382 lineclause = "line >= ? AND line < ?"
382 383 params = (session, start, stop)
383 384 else:
384 385 lineclause = "line>=?"
385 386 params = (session, start)
386 387
387 388 return self._run_sql("WHERE session==? AND %s" % lineclause,
388 389 params, raw=raw, output=output)
389 390
390 391 def get_range_by_str(self, rangestr, raw=True, output=False):
391 392 """Get lines of history from a string of ranges, as used by magic
392 393 commands %hist, %save, %macro, etc.
393 394
394 395 Parameters
395 396 ----------
396 397 rangestr : str
397 398 A string specifying ranges, e.g. "5 ~2/1-4". See
398 399 :func:`magic_history` for full details.
399 400 raw, output : bool
400 401 As :meth:`get_range`
401 402
402 403 Returns
403 404 -------
404 405 Tuples as :meth:`get_range`
405 406 """
406 407 for sess, s, e in extract_hist_ranges(rangestr):
407 408 for line in self.get_range(sess, s, e, raw=raw, output=output):
408 409 yield line
409 410
410 411
411 412 class HistoryManager(HistoryAccessor):
412 413 """A class to organize all history-related functionality in one place.
413 414 """
414 415 # Public interface
415 416
416 417 # An instance of the IPython shell we are attached to
417 418 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
418 419 # Lists to hold processed and raw history. These start with a blank entry
419 420 # so that we can index them starting from 1
420 421 input_hist_parsed = List([""])
421 422 input_hist_raw = List([""])
422 423 # A list of directories visited during session
423 424 dir_hist = List()
424 425 def _dir_hist_default(self):
425 426 try:
426 return [os.getcwdu()]
427 return [py3compat.getcwd()]
427 428 except OSError:
428 429 return []
429 430
430 431 # A dict of output history, keyed with ints from the shell's
431 432 # execution count.
432 433 output_hist = Dict()
433 434 # The text/plain repr of outputs.
434 435 output_hist_reprs = Dict()
435 436
436 437 # The number of the current session in the history database
437 438 session_number = Integer()
438 439 # Should we log output to the database? (default no)
439 440 db_log_output = Bool(False, config=True)
440 441 # Write to database every x commands (higher values save disk access & power)
441 442 # Values of 1 or less effectively disable caching.
442 443 db_cache_size = Integer(0, config=True)
443 444 # The input and output caches
444 445 db_input_cache = List()
445 446 db_output_cache = List()
446 447
447 448 # History saving in separate thread
448 449 save_thread = Instance('IPython.core.history.HistorySavingThread')
449 450 try: # Event is a function returning an instance of _Event...
450 451 save_flag = Instance(threading._Event)
451 452 except AttributeError: # ...until Python 3.3, when it's a class.
452 453 save_flag = Instance(threading.Event)
453 454
454 455 # Private interface
455 456 # Variables used to store the three last inputs from the user. On each new
456 457 # history update, we populate the user's namespace with these, shifted as
457 458 # necessary.
458 459 _i00 = Unicode(u'')
459 460 _i = Unicode(u'')
460 461 _ii = Unicode(u'')
461 462 _iii = Unicode(u'')
462 463
463 464 # A regex matching all forms of the exit command, so that we don't store
464 465 # them in the history (it's annoying to rewind the first entry and land on
465 466 # an exit call).
466 467 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
467 468
468 469 def __init__(self, shell=None, config=None, **traits):
469 470 """Create a new history manager associated with a shell instance.
470 471 """
471 472 # We need a pointer back to the shell for various tasks.
472 473 super(HistoryManager, self).__init__(shell=shell, config=config,
473 474 **traits)
474 475 self.save_flag = threading.Event()
475 476 self.db_input_cache_lock = threading.Lock()
476 477 self.db_output_cache_lock = threading.Lock()
477 478 if self.enabled and self.hist_file != ':memory:':
478 479 self.save_thread = HistorySavingThread(self)
479 480 self.save_thread.start()
480 481
481 482 self.new_session()
482 483
483 484 def _get_hist_file_name(self, profile=None):
484 485 """Get default history file name based on the Shell's profile.
485 486
486 487 The profile parameter is ignored, but must exist for compatibility with
487 488 the parent class."""
488 489 profile_dir = self.shell.profile_dir.location
489 490 return os.path.join(profile_dir, 'history.sqlite')
490 491
491 492 @needs_sqlite
492 493 def new_session(self, conn=None):
493 494 """Get a new session number."""
494 495 if conn is None:
495 496 conn = self.db
496 497
497 498 with conn:
498 499 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
499 500 NULL, "") """, (datetime.datetime.now(),))
500 501 self.session_number = cur.lastrowid
501 502
502 503 def end_session(self):
503 504 """Close the database session, filling in the end time and line count."""
504 505 self.writeout_cache()
505 506 with self.db:
506 507 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
507 508 session==?""", (datetime.datetime.now(),
508 509 len(self.input_hist_parsed)-1, self.session_number))
509 510 self.session_number = 0
510 511
511 512 def name_session(self, name):
512 513 """Give the current session a name in the history database."""
513 514 with self.db:
514 515 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
515 516 (name, self.session_number))
516 517
517 518 def reset(self, new_session=True):
518 519 """Clear the session history, releasing all object references, and
519 520 optionally open a new session."""
520 521 self.output_hist.clear()
521 522 # The directory history can't be completely empty
522 self.dir_hist[:] = [os.getcwdu()]
523 self.dir_hist[:] = [py3compat.getcwd()]
523 524
524 525 if new_session:
525 526 if self.session_number:
526 527 self.end_session()
527 528 self.input_hist_parsed[:] = [""]
528 529 self.input_hist_raw[:] = [""]
529 530 self.new_session()
530 531
531 532 # ------------------------------
532 533 # Methods for retrieving history
533 534 # ------------------------------
534 535 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
535 536 """Get input and output history from the current session. Called by
536 537 get_range, and takes similar parameters."""
537 538 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
538 539
539 540 n = len(input_hist)
540 541 if start < 0:
541 542 start += n
542 543 if not stop or (stop > n):
543 544 stop = n
544 545 elif stop < 0:
545 546 stop += n
546 547
547 548 for i in range(start, stop):
548 549 if output:
549 550 line = (input_hist[i], self.output_hist_reprs.get(i))
550 551 else:
551 552 line = input_hist[i]
552 553 yield (0, i, line)
553 554
554 555 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
555 556 """Retrieve input by session.
556 557
557 558 Parameters
558 559 ----------
559 560 session : int
560 561 Session number to retrieve. The current session is 0, and negative
561 562 numbers count back from current session, so -1 is previous session.
562 563 start : int
563 564 First line to retrieve.
564 565 stop : int
565 566 End of line range (excluded from output itself). If None, retrieve
566 567 to the end of the session.
567 568 raw : bool
568 569 If True, return untranslated input
569 570 output : bool
570 571 If True, attempt to include output. This will be 'real' Python
571 572 objects for the current session, or text reprs from previous
572 573 sessions if db_log_output was enabled at the time. Where no output
573 574 is found, None is used.
574 575
575 576 Returns
576 577 -------
577 578 An iterator over the desired lines. Each line is a 3-tuple, either
578 579 (session, line, input) if output is False, or
579 580 (session, line, (input, output)) if output is True.
580 581 """
581 582 if session <= 0:
582 583 session += self.session_number
583 584 if session==self.session_number: # Current session
584 585 return self._get_range_session(start, stop, raw, output)
585 586 return super(HistoryManager, self).get_range(session, start, stop, raw,
586 587 output)
587 588
588 589 ## ----------------------------
589 590 ## Methods for storing history:
590 591 ## ----------------------------
591 592 def store_inputs(self, line_num, source, source_raw=None):
592 593 """Store source and raw input in history and create input cache
593 594 variables _i*.
594 595
595 596 Parameters
596 597 ----------
597 598 line_num : int
598 599 The prompt number of this input.
599 600
600 601 source : str
601 602 Python input.
602 603
603 604 source_raw : str, optional
604 605 If given, this is the raw input without any IPython transformations
605 606 applied to it. If not given, ``source`` is used.
606 607 """
607 608 if source_raw is None:
608 609 source_raw = source
609 610 source = source.rstrip('\n')
610 611 source_raw = source_raw.rstrip('\n')
611 612
612 613 # do not store exit/quit commands
613 614 if self._exit_re.match(source_raw.strip()):
614 615 return
615 616
616 617 self.input_hist_parsed.append(source)
617 618 self.input_hist_raw.append(source_raw)
618 619
619 620 with self.db_input_cache_lock:
620 621 self.db_input_cache.append((line_num, source, source_raw))
621 622 # Trigger to flush cache and write to DB.
622 623 if len(self.db_input_cache) >= self.db_cache_size:
623 624 self.save_flag.set()
624 625
625 626 # update the auto _i variables
626 627 self._iii = self._ii
627 628 self._ii = self._i
628 629 self._i = self._i00
629 630 self._i00 = source_raw
630 631
631 632 # hackish access to user namespace to create _i1,_i2... dynamically
632 633 new_i = '_i%s' % line_num
633 634 to_main = {'_i': self._i,
634 635 '_ii': self._ii,
635 636 '_iii': self._iii,
636 637 new_i : self._i00 }
637 638
638 639 if self.shell is not None:
639 640 self.shell.push(to_main, interactive=False)
640 641
641 642 def store_output(self, line_num):
642 643 """If database output logging is enabled, this saves all the
643 644 outputs from the indicated prompt number to the database. It's
644 645 called by run_cell after code has been executed.
645 646
646 647 Parameters
647 648 ----------
648 649 line_num : int
649 650 The line number from which to save outputs
650 651 """
651 652 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
652 653 return
653 654 output = self.output_hist_reprs[line_num]
654 655
655 656 with self.db_output_cache_lock:
656 657 self.db_output_cache.append((line_num, output))
657 658 if self.db_cache_size <= 1:
658 659 self.save_flag.set()
659 660
660 661 def _writeout_input_cache(self, conn):
661 662 with conn:
662 663 for line in self.db_input_cache:
663 664 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
664 665 (self.session_number,)+line)
665 666
666 667 def _writeout_output_cache(self, conn):
667 668 with conn:
668 669 for line in self.db_output_cache:
669 670 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
670 671 (self.session_number,)+line)
671 672
672 673 @needs_sqlite
673 674 def writeout_cache(self, conn=None):
674 675 """Write any entries in the cache to the database."""
675 676 if conn is None:
676 677 conn = self.db
677 678
678 679 with self.db_input_cache_lock:
679 680 try:
680 681 self._writeout_input_cache(conn)
681 682 except sqlite3.IntegrityError:
682 683 self.new_session(conn)
683 684 print("ERROR! Session/line number was not unique in",
684 685 "database. History logging moved to new session",
685 686 self.session_number)
686 687 try:
687 688 # Try writing to the new session. If this fails, don't
688 689 # recurse
689 690 self._writeout_input_cache(conn)
690 691 except sqlite3.IntegrityError:
691 692 pass
692 693 finally:
693 694 self.db_input_cache = []
694 695
695 696 with self.db_output_cache_lock:
696 697 try:
697 698 self._writeout_output_cache(conn)
698 699 except sqlite3.IntegrityError:
699 700 print("!! Session/line number for output was not unique",
700 701 "in database. Output will not be stored.")
701 702 finally:
702 703 self.db_output_cache = []
703 704
704 705
705 706 class HistorySavingThread(threading.Thread):
706 707 """This thread takes care of writing history to the database, so that
707 708 the UI isn't held up while that happens.
708 709
709 710 It waits for the HistoryManager's save_flag to be set, then writes out
710 711 the history cache. The main thread is responsible for setting the flag when
711 712 the cache size reaches a defined threshold."""
712 713 daemon = True
713 714 stop_now = False
714 715 enabled = True
715 716 def __init__(self, history_manager):
716 717 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
717 718 self.history_manager = history_manager
718 719 self.enabled = history_manager.enabled
719 720 atexit.register(self.stop)
720 721
721 722 @needs_sqlite
722 723 def run(self):
723 724 # We need a separate db connection per thread:
724 725 try:
725 726 self.db = sqlite3.connect(self.history_manager.hist_file,
726 727 **self.history_manager.connection_options
727 728 )
728 729 while True:
729 730 self.history_manager.save_flag.wait()
730 731 if self.stop_now:
731 732 return
732 733 self.history_manager.save_flag.clear()
733 734 self.history_manager.writeout_cache(self.db)
734 735 except Exception as e:
735 736 print(("The history saving thread hit an unexpected error (%s)."
736 737 "History will not be written to the database.") % repr(e))
737 738
738 739 def stop(self):
739 740 """This can be called from the main thread to safely stop this thread.
740 741
741 742 Note that it does not attempt to write out remaining history before
742 743 exiting. That should be done by calling the HistoryManager's
743 744 end_session method."""
744 745 self.stop_now = True
745 746 self.history_manager.save_flag.set()
746 747 self.join()
747 748
748 749
749 750 # To match, e.g. ~5/8-~2/3
750 751 range_re = re.compile(r"""
751 752 ((?P<startsess>~?\d+)/)?
752 753 (?P<start>\d+)?
753 754 ((?P<sep>[\-:])
754 755 ((?P<endsess>~?\d+)/)?
755 756 (?P<end>\d+))?
756 757 $""", re.VERBOSE)
757 758
758 759
759 760 def extract_hist_ranges(ranges_str):
760 761 """Turn a string of history ranges into 3-tuples of (session, start, stop).
761 762
762 763 Examples
763 764 --------
764 765 list(extract_input_ranges("~8/5-~7/4 2"))
765 766 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
766 767 """
767 768 for range_str in ranges_str.split():
768 769 rmatch = range_re.match(range_str)
769 770 if not rmatch:
770 771 continue
771 772 start = rmatch.group("start")
772 773 if start:
773 774 start = int(start)
774 775 end = rmatch.group("end")
775 776 # If no end specified, get (a, a + 1)
776 777 end = int(end) if end else start + 1
777 778 else: # start not specified
778 779 if not rmatch.group('startsess'): # no startsess
779 780 continue
780 781 start = 1
781 782 end = None # provide the entire session hist
782 783
783 784 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
784 785 end += 1
785 786 startsess = rmatch.group("startsess") or "0"
786 787 endsess = rmatch.group("endsess") or startsess
787 788 startsess = int(startsess.replace("~","-"))
788 789 endsess = int(endsess.replace("~","-"))
789 790 assert endsess >= startsess, "start session must be earlier than end session"
790 791
791 792 if endsess == startsess:
792 793 yield (startsess, start, end)
793 794 continue
794 795 # Multiple sessions in one range:
795 796 yield (startsess, start, None)
796 797 for sess in range(startsess+1, endsess):
797 798 yield (sess, 1, None)
798 799 yield (endsess, 1, end)
799 800
800 801
801 802 def _format_lineno(session, line):
802 803 """Helper function to format line numbers properly."""
803 804 if session == 0:
804 805 return str(line)
805 806 return "%s#%s" % (session, line)
806 807
807 808
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,740 +1,741 b''
1 1 """Implementation of magic functions for interaction with the OS.
2 2
3 3 Note: this module is named 'osm' instead of 'os' to avoid a collision with the
4 4 builtin.
5 5 """
6 6 from __future__ import print_function
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (c) 2012 The IPython Development Team.
9 9 #
10 10 # Distributed under the terms of the Modified BSD License.
11 11 #
12 12 # The full license is in the file COPYING.txt, distributed with this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 # Stdlib
20 20 import io
21 21 import os
22 22 import re
23 23 import sys
24 24 from pprint import pformat
25 25
26 26 # Our own packages
27 27 from IPython.core import magic_arguments
28 28 from IPython.core import oinspect
29 29 from IPython.core import page
30 30 from IPython.core.alias import AliasError, Alias
31 31 from IPython.core.error import UsageError
32 32 from IPython.core.magic import (
33 33 Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic
34 34 )
35 35 from IPython.testing.skipdoctest import skip_doctest
36 36 from IPython.utils.openpy import source_to_unicode
37 37 from IPython.utils.path import unquote_filename
38 38 from IPython.utils.process import abbrev_cwd
39 from IPython.utils import py3compat
39 40 from IPython.utils.py3compat import unicode_type
40 41 from IPython.utils.terminal import set_term_title
41 42
42 43 #-----------------------------------------------------------------------------
43 44 # Magic implementation classes
44 45 #-----------------------------------------------------------------------------
45 46 @magics_class
46 47 class OSMagics(Magics):
47 48 """Magics to interact with the underlying OS (shell-type functionality).
48 49 """
49 50
50 51 @skip_doctest
51 52 @line_magic
52 53 def alias(self, parameter_s=''):
53 54 """Define an alias for a system command.
54 55
55 56 '%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd'
56 57
57 58 Then, typing 'alias_name params' will execute the system command 'cmd
58 59 params' (from your underlying operating system).
59 60
60 61 Aliases have lower precedence than magic functions and Python normal
61 62 variables, so if 'foo' is both a Python variable and an alias, the
62 63 alias can not be executed until 'del foo' removes the Python variable.
63 64
64 65 You can use the %l specifier in an alias definition to represent the
65 66 whole line when the alias is called. For example::
66 67
67 68 In [2]: alias bracket echo "Input in brackets: <%l>"
68 69 In [3]: bracket hello world
69 70 Input in brackets: <hello world>
70 71
71 72 You can also define aliases with parameters using %s specifiers (one
72 73 per parameter)::
73 74
74 75 In [1]: alias parts echo first %s second %s
75 76 In [2]: %parts A B
76 77 first A second B
77 78 In [3]: %parts A
78 79 Incorrect number of arguments: 2 expected.
79 80 parts is an alias to: 'echo first %s second %s'
80 81
81 82 Note that %l and %s are mutually exclusive. You can only use one or
82 83 the other in your aliases.
83 84
84 85 Aliases expand Python variables just like system calls using ! or !!
85 86 do: all expressions prefixed with '$' get expanded. For details of
86 87 the semantic rules, see PEP-215:
87 88 http://www.python.org/peps/pep-0215.html. This is the library used by
88 89 IPython for variable expansion. If you want to access a true shell
89 90 variable, an extra $ is necessary to prevent its expansion by
90 91 IPython::
91 92
92 93 In [6]: alias show echo
93 94 In [7]: PATH='A Python string'
94 95 In [8]: show $PATH
95 96 A Python string
96 97 In [9]: show $$PATH
97 98 /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:...
98 99
99 100 You can use the alias facility to acess all of $PATH. See the %rehash
100 101 and %rehashx functions, which automatically create aliases for the
101 102 contents of your $PATH.
102 103
103 104 If called with no parameters, %alias prints the current alias table."""
104 105
105 106 par = parameter_s.strip()
106 107 if not par:
107 108 aliases = sorted(self.shell.alias_manager.aliases)
108 109 # stored = self.shell.db.get('stored_aliases', {} )
109 110 # for k, v in stored:
110 111 # atab.append(k, v[0])
111 112
112 113 print("Total number of aliases:", len(aliases))
113 114 sys.stdout.flush()
114 115 return aliases
115 116
116 117 # Now try to define a new one
117 118 try:
118 119 alias,cmd = par.split(None, 1)
119 120 except TypeError:
120 121 print(oinspect.getdoc(self.alias))
121 122 return
122 123
123 124 try:
124 125 self.shell.alias_manager.define_alias(alias, cmd)
125 126 except AliasError as e:
126 127 print(e)
127 128 # end magic_alias
128 129
129 130 @line_magic
130 131 def unalias(self, parameter_s=''):
131 132 """Remove an alias"""
132 133
133 134 aname = parameter_s.strip()
134 135 try:
135 136 self.shell.alias_manager.undefine_alias(aname)
136 137 except ValueError as e:
137 138 print(e)
138 139 return
139 140
140 141 stored = self.shell.db.get('stored_aliases', {} )
141 142 if aname in stored:
142 143 print("Removing %stored alias",aname)
143 144 del stored[aname]
144 145 self.shell.db['stored_aliases'] = stored
145 146
146 147 @line_magic
147 148 def rehashx(self, parameter_s=''):
148 149 """Update the alias table with all executable files in $PATH.
149 150
150 151 This version explicitly checks that every entry in $PATH is a file
151 152 with execute access (os.X_OK), so it is much slower than %rehash.
152 153
153 154 Under Windows, it checks executability as a match against a
154 155 '|'-separated string of extensions, stored in the IPython config
155 156 variable win_exec_ext. This defaults to 'exe|com|bat'.
156 157
157 158 This function also resets the root module cache of module completer,
158 159 used on slow filesystems.
159 160 """
160 161 from IPython.core.alias import InvalidAliasError
161 162
162 163 # for the benefit of module completer in ipy_completers.py
163 164 del self.shell.db['rootmodules_cache']
164 165
165 166 path = [os.path.abspath(os.path.expanduser(p)) for p in
166 167 os.environ.get('PATH','').split(os.pathsep)]
167 168 path = filter(os.path.isdir,path)
168 169
169 170 syscmdlist = []
170 171 # Now define isexec in a cross platform manner.
171 172 if os.name == 'posix':
172 173 isexec = lambda fname:os.path.isfile(fname) and \
173 174 os.access(fname,os.X_OK)
174 175 else:
175 176 try:
176 177 winext = os.environ['pathext'].replace(';','|').replace('.','')
177 178 except KeyError:
178 179 winext = 'exe|com|bat|py'
179 180 if 'py' not in winext:
180 181 winext += '|py'
181 182 execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
182 183 isexec = lambda fname:os.path.isfile(fname) and execre.match(fname)
183 savedir = os.getcwdu()
184 savedir = py3compat.getcwd()
184 185
185 186 # Now walk the paths looking for executables to alias.
186 187 try:
187 188 # write the whole loop for posix/Windows so we don't have an if in
188 189 # the innermost part
189 190 if os.name == 'posix':
190 191 for pdir in path:
191 192 os.chdir(pdir)
192 193 for ff in os.listdir(pdir):
193 194 if isexec(ff):
194 195 try:
195 196 # Removes dots from the name since ipython
196 197 # will assume names with dots to be python.
197 198 if not self.shell.alias_manager.is_alias(ff):
198 199 self.shell.alias_manager.define_alias(
199 200 ff.replace('.',''), ff)
200 201 except InvalidAliasError:
201 202 pass
202 203 else:
203 204 syscmdlist.append(ff)
204 205 else:
205 206 no_alias = Alias.blacklist
206 207 for pdir in path:
207 208 os.chdir(pdir)
208 209 for ff in os.listdir(pdir):
209 210 base, ext = os.path.splitext(ff)
210 211 if isexec(ff) and base.lower() not in no_alias:
211 212 if ext.lower() == '.exe':
212 213 ff = base
213 214 try:
214 215 # Removes dots from the name since ipython
215 216 # will assume names with dots to be python.
216 217 self.shell.alias_manager.define_alias(
217 218 base.lower().replace('.',''), ff)
218 219 except InvalidAliasError:
219 220 pass
220 221 syscmdlist.append(ff)
221 222 self.shell.db['syscmdlist'] = syscmdlist
222 223 finally:
223 224 os.chdir(savedir)
224 225
225 226 @skip_doctest
226 227 @line_magic
227 228 def pwd(self, parameter_s=''):
228 229 """Return the current working directory path.
229 230
230 231 Examples
231 232 --------
232 233 ::
233 234
234 235 In [9]: pwd
235 236 Out[9]: '/home/tsuser/sprint/ipython'
236 237 """
237 return os.getcwdu()
238 return py3compat.getcwd()
238 239
239 240 @skip_doctest
240 241 @line_magic
241 242 def cd(self, parameter_s=''):
242 243 """Change the current working directory.
243 244
244 245 This command automatically maintains an internal list of directories
245 246 you visit during your IPython session, in the variable _dh. The
246 247 command %dhist shows this history nicely formatted. You can also
247 248 do 'cd -<tab>' to see directory history conveniently.
248 249
249 250 Usage:
250 251
251 252 cd 'dir': changes to directory 'dir'.
252 253
253 254 cd -: changes to the last visited directory.
254 255
255 256 cd -<n>: changes to the n-th directory in the directory history.
256 257
257 258 cd --foo: change to directory that matches 'foo' in history
258 259
259 260 cd -b <bookmark_name>: jump to a bookmark set by %bookmark
260 261 (note: cd <bookmark_name> is enough if there is no
261 262 directory <bookmark_name>, but a bookmark with the name exists.)
262 263 'cd -b <tab>' allows you to tab-complete bookmark names.
263 264
264 265 Options:
265 266
266 267 -q: quiet. Do not print the working directory after the cd command is
267 268 executed. By default IPython's cd command does print this directory,
268 269 since the default prompts do not display path information.
269 270
270 271 Note that !cd doesn't work for this purpose because the shell where
271 272 !command runs is immediately discarded after executing 'command'.
272 273
273 274 Examples
274 275 --------
275 276 ::
276 277
277 278 In [10]: cd parent/child
278 279 /home/tsuser/parent/child
279 280 """
280 281
281 oldcwd = os.getcwdu()
282 oldcwd = py3compat.getcwd()
282 283 numcd = re.match(r'(-)(\d+)$',parameter_s)
283 284 # jump in directory history by number
284 285 if numcd:
285 286 nn = int(numcd.group(2))
286 287 try:
287 288 ps = self.shell.user_ns['_dh'][nn]
288 289 except IndexError:
289 290 print('The requested directory does not exist in history.')
290 291 return
291 292 else:
292 293 opts = {}
293 294 elif parameter_s.startswith('--'):
294 295 ps = None
295 296 fallback = None
296 297 pat = parameter_s[2:]
297 298 dh = self.shell.user_ns['_dh']
298 299 # first search only by basename (last component)
299 300 for ent in reversed(dh):
300 301 if pat in os.path.basename(ent) and os.path.isdir(ent):
301 302 ps = ent
302 303 break
303 304
304 305 if fallback is None and pat in ent and os.path.isdir(ent):
305 306 fallback = ent
306 307
307 308 # if we have no last part match, pick the first full path match
308 309 if ps is None:
309 310 ps = fallback
310 311
311 312 if ps is None:
312 313 print("No matching entry in directory history")
313 314 return
314 315 else:
315 316 opts = {}
316 317
317 318
318 319 else:
319 320 #turn all non-space-escaping backslashes to slashes,
320 321 # for c:\windows\directory\names\
321 322 parameter_s = re.sub(r'\\(?! )','/', parameter_s)
322 323 opts,ps = self.parse_options(parameter_s,'qb',mode='string')
323 324 # jump to previous
324 325 if ps == '-':
325 326 try:
326 327 ps = self.shell.user_ns['_dh'][-2]
327 328 except IndexError:
328 329 raise UsageError('%cd -: No previous directory to change to.')
329 330 # jump to bookmark if needed
330 331 else:
331 332 if not os.path.isdir(ps) or 'b' in opts:
332 333 bkms = self.shell.db.get('bookmarks', {})
333 334
334 335 if ps in bkms:
335 336 target = bkms[ps]
336 337 print('(bookmark:%s) -> %s' % (ps, target))
337 338 ps = target
338 339 else:
339 340 if 'b' in opts:
340 341 raise UsageError("Bookmark '%s' not found. "
341 342 "Use '%%bookmark -l' to see your bookmarks." % ps)
342 343
343 344 # strip extra quotes on Windows, because os.chdir doesn't like them
344 345 ps = unquote_filename(ps)
345 346 # at this point ps should point to the target dir
346 347 if ps:
347 348 try:
348 349 os.chdir(os.path.expanduser(ps))
349 350 if hasattr(self.shell, 'term_title') and self.shell.term_title:
350 351 set_term_title('IPython: ' + abbrev_cwd())
351 352 except OSError:
352 353 print(sys.exc_info()[1])
353 354 else:
354 cwd = os.getcwdu()
355 cwd = py3compat.getcwd()
355 356 dhist = self.shell.user_ns['_dh']
356 357 if oldcwd != cwd:
357 358 dhist.append(cwd)
358 359 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
359 360
360 361 else:
361 362 os.chdir(self.shell.home_dir)
362 363 if hasattr(self.shell, 'term_title') and self.shell.term_title:
363 364 set_term_title('IPython: ' + '~')
364 cwd = os.getcwdu()
365 cwd = py3compat.getcwd()
365 366 dhist = self.shell.user_ns['_dh']
366 367
367 368 if oldcwd != cwd:
368 369 dhist.append(cwd)
369 370 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
370 371 if not 'q' in opts and self.shell.user_ns['_dh']:
371 372 print(self.shell.user_ns['_dh'][-1])
372 373
373 374
374 375 @line_magic
375 376 def env(self, parameter_s=''):
376 377 """List environment variables."""
377 378
378 379 return dict(os.environ)
379 380
380 381 @line_magic
381 382 def pushd(self, parameter_s=''):
382 383 """Place the current dir on stack and change directory.
383 384
384 385 Usage:\\
385 386 %pushd ['dirname']
386 387 """
387 388
388 389 dir_s = self.shell.dir_stack
389 390 tgt = os.path.expanduser(unquote_filename(parameter_s))
390 cwd = os.getcwdu().replace(self.shell.home_dir,'~')
391 cwd = py3compat.getcwd().replace(self.shell.home_dir,'~')
391 392 if tgt:
392 393 self.cd(parameter_s)
393 394 dir_s.insert(0,cwd)
394 395 return self.shell.magic('dirs')
395 396
396 397 @line_magic
397 398 def popd(self, parameter_s=''):
398 399 """Change to directory popped off the top of the stack.
399 400 """
400 401 if not self.shell.dir_stack:
401 402 raise UsageError("%popd on empty stack")
402 403 top = self.shell.dir_stack.pop(0)
403 404 self.cd(top)
404 405 print("popd ->",top)
405 406
406 407 @line_magic
407 408 def dirs(self, parameter_s=''):
408 409 """Return the current directory stack."""
409 410
410 411 return self.shell.dir_stack
411 412
412 413 @line_magic
413 414 def dhist(self, parameter_s=''):
414 415 """Print your history of visited directories.
415 416
416 417 %dhist -> print full history\\
417 418 %dhist n -> print last n entries only\\
418 419 %dhist n1 n2 -> print entries between n1 and n2 (n2 not included)\\
419 420
420 421 This history is automatically maintained by the %cd command, and
421 422 always available as the global list variable _dh. You can use %cd -<n>
422 423 to go to directory number <n>.
423 424
424 425 Note that most of time, you should view directory history by entering
425 426 cd -<TAB>.
426 427
427 428 """
428 429
429 430 dh = self.shell.user_ns['_dh']
430 431 if parameter_s:
431 432 try:
432 433 args = map(int,parameter_s.split())
433 434 except:
434 435 self.arg_err(self.dhist)
435 436 return
436 437 if len(args) == 1:
437 438 ini,fin = max(len(dh)-(args[0]),0),len(dh)
438 439 elif len(args) == 2:
439 440 ini,fin = args
440 441 fin = min(fin, len(dh))
441 442 else:
442 443 self.arg_err(self.dhist)
443 444 return
444 445 else:
445 446 ini,fin = 0,len(dh)
446 447 print('Directory history (kept in _dh)')
447 448 for i in range(ini, fin):
448 449 print("%d: %s" % (i, dh[i]))
449 450
450 451 @skip_doctest
451 452 @line_magic
452 453 def sc(self, parameter_s=''):
453 454 """Shell capture - run shell command and capture output (DEPRECATED use !).
454 455
455 456 DEPRECATED. Suboptimal, retained for backwards compatibility.
456 457
457 458 You should use the form 'var = !command' instead. Example:
458 459
459 460 "%sc -l myfiles = ls ~" should now be written as
460 461
461 462 "myfiles = !ls ~"
462 463
463 464 myfiles.s, myfiles.l and myfiles.n still apply as documented
464 465 below.
465 466
466 467 --
467 468 %sc [options] varname=command
468 469
469 470 IPython will run the given command using commands.getoutput(), and
470 471 will then update the user's interactive namespace with a variable
471 472 called varname, containing the value of the call. Your command can
472 473 contain shell wildcards, pipes, etc.
473 474
474 475 The '=' sign in the syntax is mandatory, and the variable name you
475 476 supply must follow Python's standard conventions for valid names.
476 477
477 478 (A special format without variable name exists for internal use)
478 479
479 480 Options:
480 481
481 482 -l: list output. Split the output on newlines into a list before
482 483 assigning it to the given variable. By default the output is stored
483 484 as a single string.
484 485
485 486 -v: verbose. Print the contents of the variable.
486 487
487 488 In most cases you should not need to split as a list, because the
488 489 returned value is a special type of string which can automatically
489 490 provide its contents either as a list (split on newlines) or as a
490 491 space-separated string. These are convenient, respectively, either
491 492 for sequential processing or to be passed to a shell command.
492 493
493 494 For example::
494 495
495 496 # Capture into variable a
496 497 In [1]: sc a=ls *py
497 498
498 499 # a is a string with embedded newlines
499 500 In [2]: a
500 501 Out[2]: 'setup.py\\nwin32_manual_post_install.py'
501 502
502 503 # which can be seen as a list:
503 504 In [3]: a.l
504 505 Out[3]: ['setup.py', 'win32_manual_post_install.py']
505 506
506 507 # or as a whitespace-separated string:
507 508 In [4]: a.s
508 509 Out[4]: 'setup.py win32_manual_post_install.py'
509 510
510 511 # a.s is useful to pass as a single command line:
511 512 In [5]: !wc -l $a.s
512 513 146 setup.py
513 514 130 win32_manual_post_install.py
514 515 276 total
515 516
516 517 # while the list form is useful to loop over:
517 518 In [6]: for f in a.l:
518 519 ...: !wc -l $f
519 520 ...:
520 521 146 setup.py
521 522 130 win32_manual_post_install.py
522 523
523 524 Similarly, the lists returned by the -l option are also special, in
524 525 the sense that you can equally invoke the .s attribute on them to
525 526 automatically get a whitespace-separated string from their contents::
526 527
527 528 In [7]: sc -l b=ls *py
528 529
529 530 In [8]: b
530 531 Out[8]: ['setup.py', 'win32_manual_post_install.py']
531 532
532 533 In [9]: b.s
533 534 Out[9]: 'setup.py win32_manual_post_install.py'
534 535
535 536 In summary, both the lists and strings used for output capture have
536 537 the following special attributes::
537 538
538 539 .l (or .list) : value as list.
539 540 .n (or .nlstr): value as newline-separated string.
540 541 .s (or .spstr): value as space-separated string.
541 542 """
542 543
543 544 opts,args = self.parse_options(parameter_s, 'lv')
544 545 # Try to get a variable name and command to run
545 546 try:
546 547 # the variable name must be obtained from the parse_options
547 548 # output, which uses shlex.split to strip options out.
548 549 var,_ = args.split('=', 1)
549 550 var = var.strip()
550 551 # But the command has to be extracted from the original input
551 552 # parameter_s, not on what parse_options returns, to avoid the
552 553 # quote stripping which shlex.split performs on it.
553 554 _,cmd = parameter_s.split('=', 1)
554 555 except ValueError:
555 556 var,cmd = '',''
556 557 # If all looks ok, proceed
557 558 split = 'l' in opts
558 559 out = self.shell.getoutput(cmd, split=split)
559 560 if 'v' in opts:
560 561 print('%s ==\n%s' % (var, pformat(out)))
561 562 if var:
562 563 self.shell.user_ns.update({var:out})
563 564 else:
564 565 return out
565 566
566 567 @line_cell_magic
567 568 def sx(self, line='', cell=None):
568 569 """Shell execute - run shell command and capture output (!! is short-hand).
569 570
570 571 %sx command
571 572
572 573 IPython will run the given command using commands.getoutput(), and
573 574 return the result formatted as a list (split on '\\n'). Since the
574 575 output is _returned_, it will be stored in ipython's regular output
575 576 cache Out[N] and in the '_N' automatic variables.
576 577
577 578 Notes:
578 579
579 580 1) If an input line begins with '!!', then %sx is automatically
580 581 invoked. That is, while::
581 582
582 583 !ls
583 584
584 585 causes ipython to simply issue system('ls'), typing::
585 586
586 587 !!ls
587 588
588 589 is a shorthand equivalent to::
589 590
590 591 %sx ls
591 592
592 593 2) %sx differs from %sc in that %sx automatically splits into a list,
593 594 like '%sc -l'. The reason for this is to make it as easy as possible
594 595 to process line-oriented shell output via further python commands.
595 596 %sc is meant to provide much finer control, but requires more
596 597 typing.
597 598
598 599 3) Just like %sc -l, this is a list with special attributes:
599 600 ::
600 601
601 602 .l (or .list) : value as list.
602 603 .n (or .nlstr): value as newline-separated string.
603 604 .s (or .spstr): value as whitespace-separated string.
604 605
605 606 This is very useful when trying to use such lists as arguments to
606 607 system commands."""
607 608
608 609 if cell is None:
609 610 # line magic
610 611 return self.shell.getoutput(line)
611 612 else:
612 613 opts,args = self.parse_options(line, '', 'out=')
613 614 output = self.shell.getoutput(cell)
614 615 out_name = opts.get('out', opts.get('o'))
615 616 if out_name:
616 617 self.shell.user_ns[out_name] = output
617 618 else:
618 619 return output
619 620
620 621 system = line_cell_magic('system')(sx)
621 622 bang = cell_magic('!')(sx)
622 623
623 624 @line_magic
624 625 def bookmark(self, parameter_s=''):
625 626 """Manage IPython's bookmark system.
626 627
627 628 %bookmark <name> - set bookmark to current dir
628 629 %bookmark <name> <dir> - set bookmark to <dir>
629 630 %bookmark -l - list all bookmarks
630 631 %bookmark -d <name> - remove bookmark
631 632 %bookmark -r - remove all bookmarks
632 633
633 634 You can later on access a bookmarked folder with::
634 635
635 636 %cd -b <name>
636 637
637 638 or simply '%cd <name>' if there is no directory called <name> AND
638 639 there is such a bookmark defined.
639 640
640 641 Your bookmarks persist through IPython sessions, but they are
641 642 associated with each profile."""
642 643
643 644 opts,args = self.parse_options(parameter_s,'drl',mode='list')
644 645 if len(args) > 2:
645 646 raise UsageError("%bookmark: too many arguments")
646 647
647 648 bkms = self.shell.db.get('bookmarks',{})
648 649
649 650 if 'd' in opts:
650 651 try:
651 652 todel = args[0]
652 653 except IndexError:
653 654 raise UsageError(
654 655 "%bookmark -d: must provide a bookmark to delete")
655 656 else:
656 657 try:
657 658 del bkms[todel]
658 659 except KeyError:
659 660 raise UsageError(
660 661 "%%bookmark -d: Can't delete bookmark '%s'" % todel)
661 662
662 663 elif 'r' in opts:
663 664 bkms = {}
664 665 elif 'l' in opts:
665 666 bks = bkms.keys()
666 667 bks.sort()
667 668 if bks:
668 669 size = max(map(len, bks))
669 670 else:
670 671 size = 0
671 672 fmt = '%-'+str(size)+'s -> %s'
672 673 print('Current bookmarks:')
673 674 for bk in bks:
674 675 print(fmt % (bk, bkms[bk]))
675 676 else:
676 677 if not args:
677 678 raise UsageError("%bookmark: You must specify the bookmark name")
678 679 elif len(args)==1:
679 bkms[args[0]] = os.getcwdu()
680 bkms[args[0]] = py3compat.getcwd()
680 681 elif len(args)==2:
681 682 bkms[args[0]] = args[1]
682 683 self.shell.db['bookmarks'] = bkms
683 684
684 685 @line_magic
685 686 def pycat(self, parameter_s=''):
686 687 """Show a syntax-highlighted file through a pager.
687 688
688 689 This magic is similar to the cat utility, but it will assume the file
689 690 to be Python source and will show it with syntax highlighting.
690 691
691 692 This magic command can either take a local filename, an url,
692 693 an history range (see %history) or a macro as argument ::
693 694
694 695 %pycat myscript.py
695 696 %pycat 7-27
696 697 %pycat myMacro
697 698 %pycat http://www.example.com/myscript.py
698 699 """
699 700 if not parameter_s:
700 701 raise UsageError('Missing filename, URL, input history range, '
701 702 'or macro.')
702 703
703 704 try :
704 705 cont = self.shell.find_user_code(parameter_s, skip_encoding_cookie=False)
705 706 except (ValueError, IOError):
706 707 print("Error: no such file, variable, URL, history range or macro")
707 708 return
708 709
709 710 page.page(self.shell.pycolorize(source_to_unicode(cont)))
710 711
711 712 @magic_arguments.magic_arguments()
712 713 @magic_arguments.argument(
713 714 '-a', '--append', action='store_true', default=False,
714 715 help='Append contents of the cell to an existing file. '
715 716 'The file will be created if it does not exist.'
716 717 )
717 718 @magic_arguments.argument(
718 719 'filename', type=unicode_type,
719 720 help='file to write'
720 721 )
721 722 @cell_magic
722 723 def writefile(self, line, cell):
723 724 """Write the contents of the cell to a file.
724 725
725 726 The file will be overwritten unless the -a (--append) flag is specified.
726 727 """
727 728 args = magic_arguments.parse_argstring(self.writefile, line)
728 729 filename = os.path.expanduser(unquote_filename(args.filename))
729 730
730 731 if os.path.exists(filename):
731 732 if args.append:
732 733 print("Appending to %s" % filename)
733 734 else:
734 735 print("Overwriting %s" % filename)
735 736 else:
736 737 print("Writing %s" % filename)
737 738
738 739 mode = 'a' if args.append else 'w'
739 740 with io.open(filename, mode, encoding='utf-8') as f:
740 741 f.write(cell)
@@ -1,314 +1,315 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for managing IPython profiles.
4 4
5 5 To be invoked as the `ipython profile` subcommand.
6 6
7 7 Authors:
8 8
9 9 * Min RK
10 10
11 11 """
12 12 from __future__ import print_function
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Copyright (C) 2008 The IPython Development Team
16 16 #
17 17 # Distributed under the terms of the BSD License. The full license is in
18 18 # the file COPYING, distributed as part of this software.
19 19 #-----------------------------------------------------------------------------
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Imports
23 23 #-----------------------------------------------------------------------------
24 24
25 25 import os
26 26
27 27 from IPython.config.application import Application
28 28 from IPython.core.application import (
29 29 BaseIPythonApplication, base_flags
30 30 )
31 31 from IPython.core.profiledir import ProfileDir
32 32 from IPython.utils.importstring import import_item
33 33 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
34 from IPython.utils import py3compat
34 35 from IPython.utils.traitlets import Unicode, Bool, Dict
35 36
36 37 #-----------------------------------------------------------------------------
37 38 # Constants
38 39 #-----------------------------------------------------------------------------
39 40
40 41 create_help = """Create an IPython profile by name
41 42
42 43 Create an ipython profile directory by its name or
43 44 profile directory path. Profile directories contain
44 45 configuration, log and security related files and are named
45 46 using the convention 'profile_<name>'. By default they are
46 47 located in your ipython directory. Once created, you will
47 48 can edit the configuration files in the profile
48 49 directory to configure IPython. Most users will create a
49 50 profile directory by name,
50 51 `ipython profile create myprofile`, which will put the directory
51 52 in `<ipython_dir>/profile_myprofile`.
52 53 """
53 54 list_help = """List available IPython profiles
54 55
55 56 List all available profiles, by profile location, that can
56 57 be found in the current working directly or in the ipython
57 58 directory. Profile directories are named using the convention
58 59 'profile_<profile>'.
59 60 """
60 61 profile_help = """Manage IPython profiles
61 62
62 63 Profile directories contain
63 64 configuration, log and security related files and are named
64 65 using the convention 'profile_<name>'. By default they are
65 66 located in your ipython directory. You can create profiles
66 67 with `ipython profile create <name>`, or see the profiles you
67 68 already have with `ipython profile list`
68 69
69 70 To get started configuring IPython, simply do:
70 71
71 72 $> ipython profile create
72 73
73 74 and IPython will create the default profile in <ipython_dir>/profile_default,
74 75 where you can edit ipython_config.py to start configuring IPython.
75 76
76 77 """
77 78
78 79 _list_examples = "ipython profile list # list all profiles"
79 80
80 81 _create_examples = """
81 82 ipython profile create foo # create profile foo w/ default config files
82 83 ipython profile create foo --reset # restage default config files over current
83 84 ipython profile create foo --parallel # also stage parallel config files
84 85 """
85 86
86 87 _main_examples = """
87 88 ipython profile create -h # show the help string for the create subcommand
88 89 ipython profile list -h # show the help string for the list subcommand
89 90
90 91 ipython locate profile foo # print the path to the directory for profile 'foo'
91 92 """
92 93
93 94 #-----------------------------------------------------------------------------
94 95 # Profile Application Class (for `ipython profile` subcommand)
95 96 #-----------------------------------------------------------------------------
96 97
97 98
98 99 def list_profiles_in(path):
99 100 """list profiles in a given root directory"""
100 101 files = os.listdir(path)
101 102 profiles = []
102 103 for f in files:
103 104 try:
104 105 full_path = os.path.join(path, f)
105 106 except UnicodeError:
106 107 continue
107 108 if os.path.isdir(full_path) and f.startswith('profile_'):
108 109 profiles.append(f.split('_',1)[-1])
109 110 return profiles
110 111
111 112
112 113 def list_bundled_profiles():
113 114 """list profiles that are bundled with IPython."""
114 115 path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
115 116 files = os.listdir(path)
116 117 profiles = []
117 118 for profile in files:
118 119 full_path = os.path.join(path, profile)
119 120 if os.path.isdir(full_path) and profile != "__pycache__":
120 121 profiles.append(profile)
121 122 return profiles
122 123
123 124
124 125 class ProfileLocate(BaseIPythonApplication):
125 126 description = """print the path to an IPython profile dir"""
126 127
127 128 def parse_command_line(self, argv=None):
128 129 super(ProfileLocate, self).parse_command_line(argv)
129 130 if self.extra_args:
130 131 self.profile = self.extra_args[0]
131 132
132 133 def start(self):
133 134 print(self.profile_dir.location)
134 135
135 136
136 137 class ProfileList(Application):
137 138 name = u'ipython-profile'
138 139 description = list_help
139 140 examples = _list_examples
140 141
141 142 aliases = Dict({
142 143 'ipython-dir' : 'ProfileList.ipython_dir',
143 144 'log-level' : 'Application.log_level',
144 145 })
145 146 flags = Dict(dict(
146 147 debug = ({'Application' : {'log_level' : 0}},
147 148 "Set Application.log_level to 0, maximizing log output."
148 149 )
149 150 ))
150 151
151 152 ipython_dir = Unicode(get_ipython_dir(), config=True,
152 153 help="""
153 154 The name of the IPython directory. This directory is used for logging
154 155 configuration (through profiles), history storage, etc. The default
155 156 is usually $HOME/.ipython. This options can also be specified through
156 157 the environment variable IPYTHONDIR.
157 158 """
158 159 )
159 160
160 161
161 162 def _print_profiles(self, profiles):
162 163 """print list of profiles, indented."""
163 164 for profile in profiles:
164 165 print(' %s' % profile)
165 166
166 167 def list_profile_dirs(self):
167 168 profiles = list_bundled_profiles()
168 169 if profiles:
169 170 print()
170 171 print("Available profiles in IPython:")
171 172 self._print_profiles(profiles)
172 173 print()
173 174 print(" The first request for a bundled profile will copy it")
174 175 print(" into your IPython directory (%s)," % self.ipython_dir)
175 176 print(" where you can customize it.")
176 177
177 178 profiles = list_profiles_in(self.ipython_dir)
178 179 if profiles:
179 180 print()
180 181 print("Available profiles in %s:" % self.ipython_dir)
181 182 self._print_profiles(profiles)
182 183
183 profiles = list_profiles_in(os.getcwdu())
184 profiles = list_profiles_in(py3compat.getcwd())
184 185 if profiles:
185 186 print()
186 print("Available profiles in current directory (%s):" % os.getcwdu())
187 print("Available profiles in current directory (%s):" % py3compat.getcwd())
187 188 self._print_profiles(profiles)
188 189
189 190 print()
190 191 print("To use any of the above profiles, start IPython with:")
191 192 print(" ipython --profile=<name>")
192 193 print()
193 194
194 195 def start(self):
195 196 self.list_profile_dirs()
196 197
197 198
198 199 create_flags = {}
199 200 create_flags.update(base_flags)
200 201 # don't include '--init' flag, which implies running profile create in other apps
201 202 create_flags.pop('init')
202 203 create_flags['reset'] = ({'ProfileCreate': {'overwrite' : True}},
203 204 "reset config files in this profile to the defaults.")
204 205 create_flags['parallel'] = ({'ProfileCreate': {'parallel' : True}},
205 206 "Include the config files for parallel "
206 207 "computing apps (ipengine, ipcontroller, etc.)")
207 208
208 209
209 210 class ProfileCreate(BaseIPythonApplication):
210 211 name = u'ipython-profile'
211 212 description = create_help
212 213 examples = _create_examples
213 214 auto_create = Bool(True, config=False)
214 215 def _log_format_default(self):
215 216 return "[%(name)s] %(message)s"
216 217
217 218 def _copy_config_files_default(self):
218 219 return True
219 220
220 221 parallel = Bool(False, config=True,
221 222 help="whether to include parallel computing config files")
222 223 def _parallel_changed(self, name, old, new):
223 224 parallel_files = [ 'ipcontroller_config.py',
224 225 'ipengine_config.py',
225 226 'ipcluster_config.py'
226 227 ]
227 228 if new:
228 229 for cf in parallel_files:
229 230 self.config_files.append(cf)
230 231 else:
231 232 for cf in parallel_files:
232 233 if cf in self.config_files:
233 234 self.config_files.remove(cf)
234 235
235 236 def parse_command_line(self, argv):
236 237 super(ProfileCreate, self).parse_command_line(argv)
237 238 # accept positional arg as profile name
238 239 if self.extra_args:
239 240 self.profile = self.extra_args[0]
240 241
241 242 flags = Dict(create_flags)
242 243
243 244 classes = [ProfileDir]
244 245
245 246 def _import_app(self, app_path):
246 247 """import an app class"""
247 248 app = None
248 249 name = app_path.rsplit('.', 1)[-1]
249 250 try:
250 251 app = import_item(app_path)
251 except ImportError as e:
252 except ImportError:
252 253 self.log.info("Couldn't import %s, config file will be excluded", name)
253 254 except Exception:
254 255 self.log.warn('Unexpected error importing %s', name, exc_info=True)
255 256 return app
256 257
257 258 def init_config_files(self):
258 259 super(ProfileCreate, self).init_config_files()
259 260 # use local imports, since these classes may import from here
260 261 from IPython.terminal.ipapp import TerminalIPythonApp
261 262 apps = [TerminalIPythonApp]
262 263 for app_path in (
263 264 'IPython.qt.console.qtconsoleapp.IPythonQtConsoleApp',
264 265 'IPython.html.notebookapp.NotebookApp',
265 266 'IPython.nbconvert.nbconvertapp.NbConvertApp',
266 267 ):
267 268 app = self._import_app(app_path)
268 269 if app is not None:
269 270 apps.append(app)
270 271 if self.parallel:
271 272 from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
272 273 from IPython.parallel.apps.ipengineapp import IPEngineApp
273 274 from IPython.parallel.apps.ipclusterapp import IPClusterStart
274 275 from IPython.parallel.apps.iploggerapp import IPLoggerApp
275 276 apps.extend([
276 277 IPControllerApp,
277 278 IPEngineApp,
278 279 IPClusterStart,
279 280 IPLoggerApp,
280 281 ])
281 282 for App in apps:
282 283 app = App()
283 284 app.config.update(self.config)
284 285 app.log = self.log
285 286 app.overwrite = self.overwrite
286 287 app.copy_config_files=True
287 288 app.profile = self.profile
288 289 app.init_profile_dir()
289 290 app.init_config_files()
290 291
291 292 def stage_default_config_file(self):
292 293 pass
293 294
294 295
295 296 class ProfileApp(Application):
296 297 name = u'ipython-profile'
297 298 description = profile_help
298 299 examples = _main_examples
299 300
300 301 subcommands = Dict(dict(
301 302 create = (ProfileCreate, ProfileCreate.description.splitlines()[0]),
302 303 list = (ProfileList, ProfileList.description.splitlines()[0]),
303 304 locate = (ProfileLocate, ProfileLocate.description.splitlines()[0]),
304 305 ))
305 306
306 307 def start(self):
307 308 if self.subapp is None:
308 309 print("No subcommand specified. Must specify one of: %s"%(self.subcommands.keys()))
309 310 print()
310 311 self.print_description()
311 312 self.print_subcommands()
312 313 self.exit(1)
313 314 else:
314 315 return self.subapp.start()
@@ -1,273 +1,274 b''
1 1 # encoding: utf-8
2 2 """
3 3 An object for managing IPython profile directories.
4 4
5 5 Authors:
6 6
7 7 * Brian Granger
8 8 * Fernando Perez
9 9 * Min RK
10 10
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Copyright (C) 2011 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-----------------------------------------------------------------------------
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Imports
22 22 #-----------------------------------------------------------------------------
23 23
24 24 import os
25 25 import shutil
26 26 import errno
27 27
28 28 from IPython.config.configurable import LoggingConfigurable
29 29 from IPython.utils.path import get_ipython_package_dir, expand_path
30 from IPython.utils import py3compat
30 31 from IPython.utils.traitlets import Unicode, Bool
31 32
32 33 #-----------------------------------------------------------------------------
33 34 # Classes and functions
34 35 #-----------------------------------------------------------------------------
35 36
36 37
37 38 #-----------------------------------------------------------------------------
38 39 # Module errors
39 40 #-----------------------------------------------------------------------------
40 41
41 42 class ProfileDirError(Exception):
42 43 pass
43 44
44 45
45 46 #-----------------------------------------------------------------------------
46 47 # Class for managing profile directories
47 48 #-----------------------------------------------------------------------------
48 49
49 50 class ProfileDir(LoggingConfigurable):
50 51 """An object to manage the profile directory and its resources.
51 52
52 53 The profile directory is used by all IPython applications, to manage
53 54 configuration, logging and security.
54 55
55 56 This object knows how to find, create and manage these directories. This
56 57 should be used by any code that wants to handle profiles.
57 58 """
58 59
59 60 security_dir_name = Unicode('security')
60 61 log_dir_name = Unicode('log')
61 62 startup_dir_name = Unicode('startup')
62 63 pid_dir_name = Unicode('pid')
63 64 static_dir_name = Unicode('static')
64 65 security_dir = Unicode(u'')
65 66 log_dir = Unicode(u'')
66 67 startup_dir = Unicode(u'')
67 68 pid_dir = Unicode(u'')
68 69 static_dir = Unicode(u'')
69 70
70 71 location = Unicode(u'', config=True,
71 72 help="""Set the profile location directly. This overrides the logic used by the
72 73 `profile` option.""",
73 74 )
74 75
75 76 _location_isset = Bool(False) # flag for detecting multiply set location
76 77
77 78 def _location_changed(self, name, old, new):
78 79 if self._location_isset:
79 80 raise RuntimeError("Cannot set profile location more than once.")
80 81 self._location_isset = True
81 82 if not os.path.isdir(new):
82 83 os.makedirs(new)
83 84
84 85 # ensure config files exist:
85 86 self.security_dir = os.path.join(new, self.security_dir_name)
86 87 self.log_dir = os.path.join(new, self.log_dir_name)
87 88 self.startup_dir = os.path.join(new, self.startup_dir_name)
88 89 self.pid_dir = os.path.join(new, self.pid_dir_name)
89 90 self.static_dir = os.path.join(new, self.static_dir_name)
90 91 self.check_dirs()
91 92
92 93 def _log_dir_changed(self, name, old, new):
93 94 self.check_log_dir()
94 95
95 96 def _mkdir(self, path, mode=None):
96 97 """ensure a directory exists at a given path
97 98
98 99 This is a version of os.mkdir, with the following differences:
99 100
100 101 - returns True if it created the directory, False otherwise
101 102 - ignores EEXIST, protecting against race conditions where
102 103 the dir may have been created in between the check and
103 104 the creation
104 105 - sets permissions if requested and the dir already exists
105 106 """
106 107 if os.path.exists(path):
107 108 if mode and os.stat(path).st_mode != mode:
108 109 try:
109 110 os.chmod(path, mode)
110 111 except OSError:
111 112 self.log.warn(
112 113 "Could not set permissions on %s",
113 114 path
114 115 )
115 116 return False
116 117 try:
117 118 if mode:
118 119 os.mkdir(path, mode)
119 120 else:
120 121 os.mkdir(path)
121 122 except OSError as e:
122 123 if e.errno == errno.EEXIST:
123 124 return False
124 125 else:
125 126 raise
126 127
127 128 return True
128 129
129 130 def check_log_dir(self):
130 131 self._mkdir(self.log_dir)
131 132
132 133 def _startup_dir_changed(self, name, old, new):
133 134 self.check_startup_dir()
134 135
135 136 def check_startup_dir(self):
136 137 self._mkdir(self.startup_dir)
137 138
138 139 readme = os.path.join(self.startup_dir, 'README')
139 140 src = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'README_STARTUP')
140 141
141 142 if not os.path.exists(src):
142 143 self.log.warn("Could not copy README_STARTUP to startup dir. Source file %s does not exist.", src)
143 144
144 145 if os.path.exists(src) and not os.path.exists(readme):
145 146 shutil.copy(src, readme)
146 147
147 148 def _security_dir_changed(self, name, old, new):
148 149 self.check_security_dir()
149 150
150 151 def check_security_dir(self):
151 152 self._mkdir(self.security_dir, 0o40700)
152 153
153 154 def _pid_dir_changed(self, name, old, new):
154 155 self.check_pid_dir()
155 156
156 157 def check_pid_dir(self):
157 158 self._mkdir(self.pid_dir, 0o40700)
158 159
159 160 def _static_dir_changed(self, name, old, new):
160 161 self.check_startup_dir()
161 162
162 163 def check_static_dir(self):
163 164 self._mkdir(self.static_dir)
164 165 custom = os.path.join(self.static_dir, 'custom')
165 166 self._mkdir(custom)
166 167 from IPython.html import DEFAULT_STATIC_FILES_PATH
167 168 for fname in ('custom.js', 'custom.css'):
168 169 src = os.path.join(DEFAULT_STATIC_FILES_PATH, 'custom', fname)
169 170 dest = os.path.join(custom, fname)
170 171 if not os.path.exists(src):
171 172 self.log.warn("Could not copy default file to static dir. Source file %s does not exist.", src)
172 173 continue
173 174 if not os.path.exists(dest):
174 175 shutil.copy(src, dest)
175 176
176 177 def check_dirs(self):
177 178 self.check_security_dir()
178 179 self.check_log_dir()
179 180 self.check_pid_dir()
180 181 self.check_startup_dir()
181 182 self.check_static_dir()
182 183
183 184 def copy_config_file(self, config_file, path=None, overwrite=False):
184 185 """Copy a default config file into the active profile directory.
185 186
186 187 Default configuration files are kept in :mod:`IPython.config.default`.
187 188 This function moves these from that location to the working profile
188 189 directory.
189 190 """
190 191 dst = os.path.join(self.location, config_file)
191 192 if os.path.isfile(dst) and not overwrite:
192 193 return False
193 194 if path is None:
194 195 path = os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
195 196 src = os.path.join(path, config_file)
196 197 shutil.copy(src, dst)
197 198 return True
198 199
199 200 @classmethod
200 201 def create_profile_dir(cls, profile_dir, config=None):
201 202 """Create a new profile directory given a full path.
202 203
203 204 Parameters
204 205 ----------
205 206 profile_dir : str
206 207 The full path to the profile directory. If it does exist, it will
207 208 be used. If not, it will be created.
208 209 """
209 210 return cls(location=profile_dir, config=config)
210 211
211 212 @classmethod
212 213 def create_profile_dir_by_name(cls, path, name=u'default', config=None):
213 214 """Create a profile dir by profile name and path.
214 215
215 216 Parameters
216 217 ----------
217 218 path : unicode
218 219 The path (directory) to put the profile directory in.
219 220 name : unicode
220 221 The name of the profile. The name of the profile directory will
221 222 be "profile_<profile>".
222 223 """
223 224 if not os.path.isdir(path):
224 225 raise ProfileDirError('Directory not found: %s' % path)
225 226 profile_dir = os.path.join(path, u'profile_' + name)
226 227 return cls(location=profile_dir, config=config)
227 228
228 229 @classmethod
229 230 def find_profile_dir_by_name(cls, ipython_dir, name=u'default', config=None):
230 231 """Find an existing profile dir by profile name, return its ProfileDir.
231 232
232 233 This searches through a sequence of paths for a profile dir. If it
233 234 is not found, a :class:`ProfileDirError` exception will be raised.
234 235
235 236 The search path algorithm is:
236 1. ``os.getcwdu()``
237 1. ``py3compat.getcwd()``
237 238 2. ``ipython_dir``
238 239
239 240 Parameters
240 241 ----------
241 242 ipython_dir : unicode or str
242 243 The IPython directory to use.
243 244 name : unicode or str
244 245 The name of the profile. The name of the profile directory
245 246 will be "profile_<profile>".
246 247 """
247 248 dirname = u'profile_' + name
248 paths = [os.getcwdu(), ipython_dir]
249 paths = [py3compat.getcwd(), ipython_dir]
249 250 for p in paths:
250 251 profile_dir = os.path.join(p, dirname)
251 252 if os.path.isdir(profile_dir):
252 253 return cls(location=profile_dir, config=config)
253 254 else:
254 255 raise ProfileDirError('Profile directory not found in paths: %s' % dirname)
255 256
256 257 @classmethod
257 258 def find_profile_dir(cls, profile_dir, config=None):
258 259 """Find/create a profile dir and return its ProfileDir.
259 260
260 261 This will create the profile directory if it doesn't exist.
261 262
262 263 Parameters
263 264 ----------
264 265 profile_dir : unicode or str
265 266 The path of the profile directory. This is expanded using
266 267 :func:`IPython.utils.genutils.expand_path`.
267 268 """
268 269 profile_dir = expand_path(profile_dir)
269 270 if not os.path.isdir(profile_dir):
270 271 raise ProfileDirError('Profile directory not found: %s' % profile_dir)
271 272 return cls(location=profile_dir, config=config)
272 273
273 274
@@ -1,439 +1,439 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Classes for handling input/output prompts.
3 3
4 4 Authors:
5 5
6 6 * Fernando Perez
7 7 * Brian Granger
8 8 * Thomas Kluyver
9 9 """
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Copyright (C) 2008-2011 The IPython Development Team
13 13 # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 import os
24 24 import re
25 25 import socket
26 26 import sys
27 27 import time
28 28
29 29 from string import Formatter
30 30
31 31 from IPython.config.configurable import Configurable
32 32 from IPython.core import release
33 33 from IPython.utils import coloransi, py3compat
34 34 from IPython.utils.traitlets import (Unicode, Instance, Dict, Bool, Int)
35 35
36 36 #-----------------------------------------------------------------------------
37 37 # Color schemes for prompts
38 38 #-----------------------------------------------------------------------------
39 39
40 40 InputColors = coloransi.InputTermColors # just a shorthand
41 41 Colors = coloransi.TermColors # just a shorthand
42 42
43 43 color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors())
44 44
45 45 PColNoColors = coloransi.ColorScheme(
46 46 'NoColor',
47 47 in_prompt = InputColors.NoColor, # Input prompt
48 48 in_number = InputColors.NoColor, # Input prompt number
49 49 in_prompt2 = InputColors.NoColor, # Continuation prompt
50 50 in_normal = InputColors.NoColor, # color off (usu. Colors.Normal)
51 51
52 52 out_prompt = Colors.NoColor, # Output prompt
53 53 out_number = Colors.NoColor, # Output prompt number
54 54
55 55 normal = Colors.NoColor # color off (usu. Colors.Normal)
56 56 )
57 57
58 58 # make some schemes as instances so we can copy them for modification easily:
59 59 PColLinux = coloransi.ColorScheme(
60 60 'Linux',
61 61 in_prompt = InputColors.Green,
62 62 in_number = InputColors.LightGreen,
63 63 in_prompt2 = InputColors.Green,
64 64 in_normal = InputColors.Normal, # color off (usu. Colors.Normal)
65 65
66 66 out_prompt = Colors.Red,
67 67 out_number = Colors.LightRed,
68 68
69 69 normal = Colors.Normal
70 70 )
71 71
72 72 # Slightly modified Linux for light backgrounds
73 73 PColLightBG = PColLinux.copy('LightBG')
74 74
75 75 PColLightBG.colors.update(
76 76 in_prompt = InputColors.Blue,
77 77 in_number = InputColors.LightBlue,
78 78 in_prompt2 = InputColors.Blue
79 79 )
80 80
81 81 #-----------------------------------------------------------------------------
82 82 # Utilities
83 83 #-----------------------------------------------------------------------------
84 84
85 85 class LazyEvaluate(object):
86 86 """This is used for formatting strings with values that need to be updated
87 87 at that time, such as the current time or working directory."""
88 88 def __init__(self, func, *args, **kwargs):
89 89 self.func = func
90 90 self.args = args
91 91 self.kwargs = kwargs
92 92
93 93 def __call__(self, **kwargs):
94 94 self.kwargs.update(kwargs)
95 95 return self.func(*self.args, **self.kwargs)
96 96
97 97 def __str__(self):
98 98 return str(self())
99 99
100 100 def __unicode__(self):
101 101 return py3compat.unicode_type(self())
102 102
103 103 def __format__(self, format_spec):
104 104 return format(self(), format_spec)
105 105
106 106 def multiple_replace(dict, text):
107 107 """ Replace in 'text' all occurences of any key in the given
108 108 dictionary by its corresponding value. Returns the new string."""
109 109
110 110 # Function by Xavier Defrang, originally found at:
111 111 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
112 112
113 113 # Create a regular expression from the dictionary keys
114 114 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
115 115 # For each match, look-up corresponding value in dictionary
116 116 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
117 117
118 118 #-----------------------------------------------------------------------------
119 119 # Special characters that can be used in prompt templates, mainly bash-like
120 120 #-----------------------------------------------------------------------------
121 121
122 122 # If $HOME isn't defined (Windows), make it an absurd string so that it can
123 123 # never be expanded out into '~'. Basically anything which can never be a
124 124 # reasonable directory name will do, we just want the $HOME -> '~' operation
125 125 # to become a no-op. We pre-compute $HOME here so it's not done on every
126 126 # prompt call.
127 127
128 128 # FIXME:
129 129
130 130 # - This should be turned into a class which does proper namespace management,
131 131 # since the prompt specials need to be evaluated in a certain namespace.
132 132 # Currently it's just globals, which need to be managed manually by code
133 133 # below.
134 134
135 135 # - I also need to split up the color schemes from the prompt specials
136 136 # somehow. I don't have a clean design for that quite yet.
137 137
138 138 HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~"))
139 139
140 140 # This is needed on FreeBSD, and maybe other systems which symlink /home to
141 141 # /usr/home, but retain the $HOME variable as pointing to /home
142 142 HOME = os.path.realpath(HOME)
143 143
144 144 # We precompute a few more strings here for the prompt_specials, which are
145 145 # fixed once ipython starts. This reduces the runtime overhead of computing
146 146 # prompt strings.
147 147 USER = py3compat.str_to_unicode(os.environ.get("USER",''))
148 148 HOSTNAME = py3compat.str_to_unicode(socket.gethostname())
149 149 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
150 150 ROOT_SYMBOL = "#" if (os.name=='nt' or os.getuid()==0) else "$"
151 151
152 152 prompt_abbreviations = {
153 153 # Prompt/history count
154 154 '%n' : '{color.number}' '{count}' '{color.prompt}',
155 155 r'\#': '{color.number}' '{count}' '{color.prompt}',
156 156 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
157 157 # can get numbers displayed in whatever color they want.
158 158 r'\N': '{count}',
159 159
160 160 # Prompt/history count, with the actual digits replaced by dots. Used
161 161 # mainly in continuation prompts (prompt_in2)
162 162 r'\D': '{dots}',
163 163
164 164 # Current time
165 165 r'\T' : '{time}',
166 166 # Current working directory
167 167 r'\w': '{cwd}',
168 168 # Basename of current working directory.
169 169 # (use os.sep to make this portable across OSes)
170 170 r'\W' : '{cwd_last}',
171 171 # These X<N> are an extension to the normal bash prompts. They return
172 172 # N terms of the path, after replacing $HOME with '~'
173 173 r'\X0': '{cwd_x[0]}',
174 174 r'\X1': '{cwd_x[1]}',
175 175 r'\X2': '{cwd_x[2]}',
176 176 r'\X3': '{cwd_x[3]}',
177 177 r'\X4': '{cwd_x[4]}',
178 178 r'\X5': '{cwd_x[5]}',
179 179 # Y<N> are similar to X<N>, but they show '~' if it's the directory
180 180 # N+1 in the list. Somewhat like %cN in tcsh.
181 181 r'\Y0': '{cwd_y[0]}',
182 182 r'\Y1': '{cwd_y[1]}',
183 183 r'\Y2': '{cwd_y[2]}',
184 184 r'\Y3': '{cwd_y[3]}',
185 185 r'\Y4': '{cwd_y[4]}',
186 186 r'\Y5': '{cwd_y[5]}',
187 187 # Hostname up to first .
188 188 r'\h': HOSTNAME_SHORT,
189 189 # Full hostname
190 190 r'\H': HOSTNAME,
191 191 # Username of current user
192 192 r'\u': USER,
193 193 # Escaped '\'
194 194 '\\\\': '\\',
195 195 # Newline
196 196 r'\n': '\n',
197 197 # Carriage return
198 198 r'\r': '\r',
199 199 # Release version
200 200 r'\v': release.version,
201 201 # Root symbol ($ or #)
202 202 r'\$': ROOT_SYMBOL,
203 203 }
204 204
205 205 #-----------------------------------------------------------------------------
206 206 # More utilities
207 207 #-----------------------------------------------------------------------------
208 208
209 209 def cwd_filt(depth):
210 210 """Return the last depth elements of the current working directory.
211 211
212 212 $HOME is always replaced with '~'.
213 213 If depth==0, the full path is returned."""
214 214
215 cwd = os.getcwdu().replace(HOME,"~")
215 cwd = py3compat.getcwd().replace(HOME,"~")
216 216 out = os.sep.join(cwd.split(os.sep)[-depth:])
217 217 return out or os.sep
218 218
219 219 def cwd_filt2(depth):
220 220 """Return the last depth elements of the current working directory.
221 221
222 222 $HOME is always replaced with '~'.
223 223 If depth==0, the full path is returned."""
224 224
225 full_cwd = os.getcwdu()
225 full_cwd = py3compat.getcwd()
226 226 cwd = full_cwd.replace(HOME,"~").split(os.sep)
227 227 if '~' in cwd and len(cwd) == depth+1:
228 228 depth += 1
229 229 drivepart = ''
230 230 if sys.platform == 'win32' and len(cwd) > depth:
231 231 drivepart = os.path.splitdrive(full_cwd)[0]
232 232 out = drivepart + '/'.join(cwd[-depth:])
233 233
234 234 return out or os.sep
235 235
236 236 #-----------------------------------------------------------------------------
237 237 # Prompt classes
238 238 #-----------------------------------------------------------------------------
239 239
240 240 lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"),
241 'cwd': LazyEvaluate(os.getcwdu),
242 'cwd_last': LazyEvaluate(lambda: os.getcwdu().split(os.sep)[-1]),
243 'cwd_x': [LazyEvaluate(lambda: os.getcwdu().replace(HOME,"~"))] +\
241 'cwd': LazyEvaluate(py3compat.getcwd),
242 'cwd_last': LazyEvaluate(lambda: py3compat.getcwd().split(os.sep)[-1]),
243 'cwd_x': [LazyEvaluate(lambda: py3compat.getcwd().replace(HOME,"~"))] +\
244 244 [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
245 245 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
246 246 }
247 247
248 248 def _lenlastline(s):
249 249 """Get the length of the last line. More intelligent than
250 250 len(s.splitlines()[-1]).
251 251 """
252 252 if not s or s.endswith(('\n', '\r')):
253 253 return 0
254 254 return len(s.splitlines()[-1])
255 255
256 256
257 257 class UserNSFormatter(Formatter):
258 258 """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution"""
259 259 def __init__(self, shell):
260 260 self.shell = shell
261 261
262 262 def get_value(self, key, args, kwargs):
263 263 # try regular formatting first:
264 264 try:
265 265 return Formatter.get_value(self, key, args, kwargs)
266 266 except Exception:
267 267 pass
268 268 # next, look in user_ns and builtins:
269 269 for container in (self.shell.user_ns, __builtins__):
270 270 if key in container:
271 271 return container[key]
272 272 # nothing found, put error message in its place
273 273 return "<ERROR: '%s' not found>" % key
274 274
275 275
276 276 class PromptManager(Configurable):
277 277 """This is the primary interface for producing IPython's prompts."""
278 278 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
279 279
280 280 color_scheme_table = Instance(coloransi.ColorSchemeTable)
281 281 color_scheme = Unicode('Linux', config=True)
282 282 def _color_scheme_changed(self, name, new_value):
283 283 self.color_scheme_table.set_active_scheme(new_value)
284 284 for pname in ['in', 'in2', 'out', 'rewrite']:
285 285 # We need to recalculate the number of invisible characters
286 286 self.update_prompt(pname)
287 287
288 288 lazy_evaluate_fields = Dict(help="""
289 289 This maps field names used in the prompt templates to functions which
290 290 will be called when the prompt is rendered. This allows us to include
291 291 things like the current time in the prompts. Functions are only called
292 292 if they are used in the prompt.
293 293 """)
294 294 def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy()
295 295
296 296 in_template = Unicode('In [\\#]: ', config=True,
297 297 help="Input prompt. '\\#' will be transformed to the prompt number")
298 298 in2_template = Unicode(' .\\D.: ', config=True,
299 299 help="Continuation prompt.")
300 300 out_template = Unicode('Out[\\#]: ', config=True,
301 301 help="Output prompt. '\\#' will be transformed to the prompt number")
302 302
303 303 justify = Bool(True, config=True, help="""
304 304 If True (default), each prompt will be right-aligned with the
305 305 preceding one.
306 306 """)
307 307
308 308 # We actually store the expanded templates here:
309 309 templates = Dict()
310 310
311 311 # The number of characters in the last prompt rendered, not including
312 312 # colour characters.
313 313 width = Int()
314 314 txtwidth = Int() # Not including right-justification
315 315
316 316 # The number of characters in each prompt which don't contribute to width
317 317 invisible_chars = Dict()
318 318 def _invisible_chars_default(self):
319 319 return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0}
320 320
321 321 def __init__(self, shell, **kwargs):
322 322 super(PromptManager, self).__init__(shell=shell, **kwargs)
323 323
324 324 # Prepare colour scheme table
325 325 self.color_scheme_table = coloransi.ColorSchemeTable([PColNoColors,
326 326 PColLinux, PColLightBG], self.color_scheme)
327 327
328 328 self._formatter = UserNSFormatter(shell)
329 329 # Prepare templates & numbers of invisible characters
330 330 self.update_prompt('in', self.in_template)
331 331 self.update_prompt('in2', self.in2_template)
332 332 self.update_prompt('out', self.out_template)
333 333 self.update_prompt('rewrite')
334 334 self.on_trait_change(self._update_prompt_trait, ['in_template',
335 335 'in2_template', 'out_template'])
336 336
337 337 def update_prompt(self, name, new_template=None):
338 338 """This is called when a prompt template is updated. It processes
339 339 abbreviations used in the prompt template (like \#) and calculates how
340 340 many invisible characters (ANSI colour escapes) the resulting prompt
341 341 contains.
342 342
343 343 It is also called for each prompt on changing the colour scheme. In both
344 344 cases, traitlets should take care of calling this automatically.
345 345 """
346 346 if new_template is not None:
347 347 self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
348 348 # We count invisible characters (colour escapes) on the last line of the
349 349 # prompt, to calculate the width for lining up subsequent prompts.
350 350 invis_chars = _lenlastline(self._render(name, color=True)) - \
351 351 _lenlastline(self._render(name, color=False))
352 352 self.invisible_chars[name] = invis_chars
353 353
354 354 def _update_prompt_trait(self, traitname, new_template):
355 355 name = traitname[:-9] # Cut off '_template'
356 356 self.update_prompt(name, new_template)
357 357
358 358 def _render(self, name, color=True, **kwargs):
359 359 """Render but don't justify, or update the width or txtwidth attributes.
360 360 """
361 361 if name == 'rewrite':
362 362 return self._render_rewrite(color=color)
363 363
364 364 if color:
365 365 scheme = self.color_scheme_table.active_colors
366 366 if name=='out':
367 367 colors = color_lists['normal']
368 368 colors.number, colors.prompt, colors.normal = \
369 369 scheme.out_number, scheme.out_prompt, scheme.normal
370 370 else:
371 371 colors = color_lists['inp']
372 372 colors.number, colors.prompt, colors.normal = \
373 373 scheme.in_number, scheme.in_prompt, scheme.in_normal
374 374 if name=='in2':
375 375 colors.prompt = scheme.in_prompt2
376 376 else:
377 377 # No color
378 378 colors = color_lists['nocolor']
379 379 colors.number, colors.prompt, colors.normal = '', '', ''
380 380
381 381 count = self.shell.execution_count # Shorthand
382 382 # Build the dictionary to be passed to string formatting
383 383 fmtargs = dict(color=colors, count=count,
384 384 dots="."*len(str(count)),
385 385 width=self.width, txtwidth=self.txtwidth )
386 386 fmtargs.update(self.lazy_evaluate_fields)
387 387 fmtargs.update(kwargs)
388 388
389 389 # Prepare the prompt
390 390 prompt = colors.prompt + self.templates[name] + colors.normal
391 391
392 392 # Fill in required fields
393 393 return self._formatter.format(prompt, **fmtargs)
394 394
395 395 def _render_rewrite(self, color=True):
396 396 """Render the ---> rewrite prompt."""
397 397 if color:
398 398 scheme = self.color_scheme_table.active_colors
399 399 # We need a non-input version of these escapes
400 400 color_prompt = scheme.in_prompt.replace("\001","").replace("\002","")
401 401 color_normal = scheme.normal
402 402 else:
403 403 color_prompt, color_normal = '', ''
404 404
405 405 return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal
406 406
407 407 def render(self, name, color=True, just=None, **kwargs):
408 408 """
409 409 Render the selected prompt.
410 410
411 411 Parameters
412 412 ----------
413 413 name : str
414 414 Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
415 415 color : bool
416 416 If True (default), include ANSI escape sequences for a coloured prompt.
417 417 just : bool
418 418 If True, justify the prompt to the width of the last prompt. The
419 419 default is stored in self.justify.
420 420 **kwargs :
421 421 Additional arguments will be passed to the string formatting operation,
422 422 so they can override the values that would otherwise fill in the
423 423 template.
424 424
425 425 Returns
426 426 -------
427 427 A string containing the rendered prompt.
428 428 """
429 429 res = self._render(name, color=color, **kwargs)
430 430
431 431 # Handle justification of prompt
432 432 invis_chars = self.invisible_chars[name] if color else 0
433 433 self.txtwidth = _lenlastline(res) - invis_chars
434 434 just = self.justify if (just is None) else just
435 435 # If the prompt spans more than one line, don't try to justify it:
436 436 if just and name != 'in' and ('\n' not in res) and ('\r' not in res):
437 437 res = res.rjust(self.width + invis_chars)
438 438 self.width = _lenlastline(res) - invis_chars
439 439 return res
@@ -1,50 +1,50 b''
1 1 # coding: utf-8
2 2 """Tests for IPython.core.application"""
3 3
4 4 import os
5 5 import tempfile
6 6
7 7 from IPython.core.application import BaseIPythonApplication
8 8 from IPython.testing import decorators as dec
9 9 from IPython.utils import py3compat
10 10
11 11 @dec.onlyif_unicode_paths
12 12 def test_unicode_cwd():
13 13 """Check that IPython starts with non-ascii characters in the path."""
14 14 wd = tempfile.mkdtemp(suffix=u"€")
15 15
16 old_wd = os.getcwdu()
16 old_wd = py3compat.getcwd()
17 17 os.chdir(wd)
18 #raise Exception(repr(os.getcwdu()))
18 #raise Exception(repr(py3compat.getcwd()))
19 19 try:
20 20 app = BaseIPythonApplication()
21 21 # The lines below are copied from Application.initialize()
22 22 app.init_profile_dir()
23 23 app.init_config_files()
24 24 app.load_config_file(suppress_errors=False)
25 25 finally:
26 26 os.chdir(old_wd)
27 27
28 28 @dec.onlyif_unicode_paths
29 29 def test_unicode_ipdir():
30 30 """Check that IPython starts with non-ascii characters in the IP dir."""
31 31 ipdir = tempfile.mkdtemp(suffix=u"€")
32 32
33 33 # Create the config file, so it tries to load it.
34 34 with open(os.path.join(ipdir, 'ipython_config.py'), "w") as f:
35 35 pass
36 36
37 37 old_ipdir1 = os.environ.pop("IPYTHONDIR", None)
38 38 old_ipdir2 = os.environ.pop("IPYTHON_DIR", None)
39 39 os.environ["IPYTHONDIR"] = py3compat.unicode_to_str(ipdir, "utf-8")
40 40 try:
41 41 app = BaseIPythonApplication()
42 42 # The lines below are copied from Application.initialize()
43 43 app.init_profile_dir()
44 44 app.init_config_files()
45 45 app.load_config_file(suppress_errors=False)
46 46 finally:
47 47 if old_ipdir1:
48 48 os.environ["IPYTHONDIR"] = old_ipdir1
49 49 if old_ipdir2:
50 50 os.environ["IPYTHONDIR"] = old_ipdir2
@@ -1,393 +1,394 b''
1 1 """Tests for the IPython tab-completion machinery.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Module imports
5 5 #-----------------------------------------------------------------------------
6 6
7 7 # stdlib
8 8 import os
9 9 import sys
10 10 import unittest
11 11
12 12 # third party
13 13 import nose.tools as nt
14 14
15 15 # our own packages
16 16 from IPython.config.loader import Config
17 17 from IPython.core import completer
18 18 from IPython.external.decorators import knownfailureif
19 19 from IPython.utils.tempdir import TemporaryDirectory
20 20 from IPython.utils.generics import complete_object
21 from IPython.utils import py3compat
21 22 from IPython.utils.py3compat import string_types, unicode_type
22 23
23 24 #-----------------------------------------------------------------------------
24 25 # Test functions
25 26 #-----------------------------------------------------------------------------
26 27 def test_protect_filename():
27 28 pairs = [ ('abc','abc'),
28 29 (' abc',r'\ abc'),
29 30 ('a bc',r'a\ bc'),
30 31 ('a bc',r'a\ \ bc'),
31 32 (' bc',r'\ \ bc'),
32 33 ]
33 34 # On posix, we also protect parens and other special characters
34 35 if sys.platform != 'win32':
35 36 pairs.extend( [('a(bc',r'a\(bc'),
36 37 ('a)bc',r'a\)bc'),
37 38 ('a( )bc',r'a\(\ \)bc'),
38 39 ('a[1]bc', r'a\[1\]bc'),
39 40 ('a{1}bc', r'a\{1\}bc'),
40 41 ('a#bc', r'a\#bc'),
41 42 ('a?bc', r'a\?bc'),
42 43 ('a=bc', r'a\=bc'),
43 44 ('a\\bc', r'a\\bc'),
44 45 ('a|bc', r'a\|bc'),
45 46 ('a;bc', r'a\;bc'),
46 47 ('a:bc', r'a\:bc'),
47 48 ("a'bc", r"a\'bc"),
48 49 ('a*bc', r'a\*bc'),
49 50 ('a"bc', r'a\"bc'),
50 51 ('a^bc', r'a\^bc'),
51 52 ('a&bc', r'a\&bc'),
52 53 ] )
53 54 # run the actual tests
54 55 for s1, s2 in pairs:
55 56 s1p = completer.protect_filename(s1)
56 57 nt.assert_equal(s1p, s2)
57 58
58 59
59 60 def check_line_split(splitter, test_specs):
60 61 for part1, part2, split in test_specs:
61 62 cursor_pos = len(part1)
62 63 line = part1+part2
63 64 out = splitter.split_line(line, cursor_pos)
64 65 nt.assert_equal(out, split)
65 66
66 67
67 68 def test_line_split():
68 69 """Basic line splitter test with default specs."""
69 70 sp = completer.CompletionSplitter()
70 71 # The format of the test specs is: part1, part2, expected answer. Parts 1
71 72 # and 2 are joined into the 'line' sent to the splitter, as if the cursor
72 73 # was at the end of part1. So an empty part2 represents someone hitting
73 74 # tab at the end of the line, the most common case.
74 75 t = [('run some/scrip', '', 'some/scrip'),
75 76 ('run scripts/er', 'ror.py foo', 'scripts/er'),
76 77 ('echo $HOM', '', 'HOM'),
77 78 ('print sys.pa', '', 'sys.pa'),
78 79 ('print(sys.pa', '', 'sys.pa'),
79 80 ("execfile('scripts/er", '', 'scripts/er'),
80 81 ('a[x.', '', 'x.'),
81 82 ('a[x.', 'y', 'x.'),
82 83 ('cd "some_file/', '', 'some_file/'),
83 84 ]
84 85 check_line_split(sp, t)
85 86 # Ensure splitting works OK with unicode by re-running the tests with
86 87 # all inputs turned into unicode
87 88 check_line_split(sp, [ map(unicode_type, p) for p in t] )
88 89
89 90
90 91 def test_custom_completion_error():
91 92 """Test that errors from custom attribute completers are silenced."""
92 93 ip = get_ipython()
93 94 class A(object): pass
94 95 ip.user_ns['a'] = A()
95 96
96 97 @complete_object.when_type(A)
97 98 def complete_A(a, existing_completions):
98 99 raise TypeError("this should be silenced")
99 100
100 101 ip.complete("a.")
101 102
102 103
103 104 def test_unicode_completions():
104 105 ip = get_ipython()
105 106 # Some strings that trigger different types of completion. Check them both
106 107 # in str and unicode forms
107 108 s = ['ru', '%ru', 'cd /', 'floa', 'float(x)/']
108 109 for t in s + list(map(unicode_type, s)):
109 110 # We don't need to check exact completion values (they may change
110 111 # depending on the state of the namespace, but at least no exceptions
111 112 # should be thrown and the return value should be a pair of text, list
112 113 # values.
113 114 text, matches = ip.complete(t)
114 115 nt.assert_true(isinstance(text, string_types))
115 116 nt.assert_true(isinstance(matches, list))
116 117
117 118
118 119 class CompletionSplitterTestCase(unittest.TestCase):
119 120 def setUp(self):
120 121 self.sp = completer.CompletionSplitter()
121 122
122 123 def test_delim_setting(self):
123 124 self.sp.delims = ' '
124 125 nt.assert_equal(self.sp.delims, ' ')
125 126 nt.assert_equal(self.sp._delim_expr, '[\ ]')
126 127
127 128 def test_spaces(self):
128 129 """Test with only spaces as split chars."""
129 130 self.sp.delims = ' '
130 131 t = [('foo', '', 'foo'),
131 132 ('run foo', '', 'foo'),
132 133 ('run foo', 'bar', 'foo'),
133 134 ]
134 135 check_line_split(self.sp, t)
135 136
136 137
137 138 def test_has_open_quotes1():
138 139 for s in ["'", "'''", "'hi' '"]:
139 140 nt.assert_equal(completer.has_open_quotes(s), "'")
140 141
141 142
142 143 def test_has_open_quotes2():
143 144 for s in ['"', '"""', '"hi" "']:
144 145 nt.assert_equal(completer.has_open_quotes(s), '"')
145 146
146 147
147 148 def test_has_open_quotes3():
148 149 for s in ["''", "''' '''", "'hi' 'ipython'"]:
149 150 nt.assert_false(completer.has_open_quotes(s))
150 151
151 152
152 153 def test_has_open_quotes4():
153 154 for s in ['""', '""" """', '"hi" "ipython"']:
154 155 nt.assert_false(completer.has_open_quotes(s))
155 156
156 157
157 158 @knownfailureif(sys.platform == 'win32', "abspath completions fail on Windows")
158 159 def test_abspath_file_completions():
159 160 ip = get_ipython()
160 161 with TemporaryDirectory() as tmpdir:
161 162 prefix = os.path.join(tmpdir, 'foo')
162 163 suffixes = ['1', '2']
163 164 names = [prefix+s for s in suffixes]
164 165 for n in names:
165 166 open(n, 'w').close()
166 167
167 168 # Check simple completion
168 169 c = ip.complete(prefix)[1]
169 170 nt.assert_equal(c, names)
170 171
171 172 # Now check with a function call
172 173 cmd = 'a = f("%s' % prefix
173 174 c = ip.complete(prefix, cmd)[1]
174 175 comp = [prefix+s for s in suffixes]
175 176 nt.assert_equal(c, comp)
176 177
177 178
178 179 def test_local_file_completions():
179 180 ip = get_ipython()
180 cwd = os.getcwdu()
181 cwd = py3compat.getcwd()
181 182 try:
182 183 with TemporaryDirectory() as tmpdir:
183 184 os.chdir(tmpdir)
184 185 prefix = './foo'
185 186 suffixes = ['1', '2']
186 187 names = [prefix+s for s in suffixes]
187 188 for n in names:
188 189 open(n, 'w').close()
189 190
190 191 # Check simple completion
191 192 c = ip.complete(prefix)[1]
192 193 nt.assert_equal(c, names)
193 194
194 195 # Now check with a function call
195 196 cmd = 'a = f("%s' % prefix
196 197 c = ip.complete(prefix, cmd)[1]
197 198 comp = [prefix+s for s in suffixes]
198 199 nt.assert_equal(c, comp)
199 200 finally:
200 201 # prevent failures from making chdir stick
201 202 os.chdir(cwd)
202 203
203 204
204 205 def test_greedy_completions():
205 206 ip = get_ipython()
206 207 greedy_original = ip.Completer.greedy
207 208 try:
208 209 ip.Completer.greedy = False
209 210 ip.ex('a=list(range(5))')
210 211 _,c = ip.complete('.',line='a[0].')
211 212 nt.assert_false('a[0].real' in c,
212 213 "Shouldn't have completed on a[0]: %s"%c)
213 214 ip.Completer.greedy = True
214 215 _,c = ip.complete('.',line='a[0].')
215 216 nt.assert_true('a[0].real' in c, "Should have completed on a[0]: %s"%c)
216 217 finally:
217 218 ip.Completer.greedy = greedy_original
218 219
219 220
220 221 def test_omit__names():
221 222 # also happens to test IPCompleter as a configurable
222 223 ip = get_ipython()
223 224 ip._hidden_attr = 1
224 225 c = ip.Completer
225 226 ip.ex('ip=get_ipython()')
226 227 cfg = Config()
227 228 cfg.IPCompleter.omit__names = 0
228 229 c.update_config(cfg)
229 230 s,matches = c.complete('ip.')
230 231 nt.assert_in('ip.__str__', matches)
231 232 nt.assert_in('ip._hidden_attr', matches)
232 233 cfg.IPCompleter.omit__names = 1
233 234 c.update_config(cfg)
234 235 s,matches = c.complete('ip.')
235 236 nt.assert_not_in('ip.__str__', matches)
236 237 nt.assert_in('ip._hidden_attr', matches)
237 238 cfg.IPCompleter.omit__names = 2
238 239 c.update_config(cfg)
239 240 s,matches = c.complete('ip.')
240 241 nt.assert_not_in('ip.__str__', matches)
241 242 nt.assert_not_in('ip._hidden_attr', matches)
242 243 del ip._hidden_attr
243 244
244 245
245 246 def test_limit_to__all__False_ok():
246 247 ip = get_ipython()
247 248 c = ip.Completer
248 249 ip.ex('class D: x=24')
249 250 ip.ex('d=D()')
250 251 cfg = Config()
251 252 cfg.IPCompleter.limit_to__all__ = False
252 253 c.update_config(cfg)
253 254 s, matches = c.complete('d.')
254 255 nt.assert_in('d.x', matches)
255 256
256 257
257 258 def test_limit_to__all__True_ok():
258 259 ip = get_ipython()
259 260 c = ip.Completer
260 261 ip.ex('class D: x=24')
261 262 ip.ex('d=D()')
262 263 ip.ex("d.__all__=['z']")
263 264 cfg = Config()
264 265 cfg.IPCompleter.limit_to__all__ = True
265 266 c.update_config(cfg)
266 267 s, matches = c.complete('d.')
267 268 nt.assert_in('d.z', matches)
268 269 nt.assert_not_in('d.x', matches)
269 270
270 271
271 272 def test_get__all__entries_ok():
272 273 class A(object):
273 274 __all__ = ['x', 1]
274 275 words = completer.get__all__entries(A())
275 276 nt.assert_equal(words, ['x'])
276 277
277 278
278 279 def test_get__all__entries_no__all__ok():
279 280 class A(object):
280 281 pass
281 282 words = completer.get__all__entries(A())
282 283 nt.assert_equal(words, [])
283 284
284 285
285 286 def test_func_kw_completions():
286 287 ip = get_ipython()
287 288 c = ip.Completer
288 289 ip.ex('def myfunc(a=1,b=2): return a+b')
289 290 s, matches = c.complete(None, 'myfunc(1,b')
290 291 nt.assert_in('b=', matches)
291 292 # Simulate completing with cursor right after b (pos==10):
292 293 s, matches = c.complete(None, 'myfunc(1,b)', 10)
293 294 nt.assert_in('b=', matches)
294 295 s, matches = c.complete(None, 'myfunc(a="escaped\\")string",b')
295 296 nt.assert_in('b=', matches)
296 297 #builtin function
297 298 s, matches = c.complete(None, 'min(k, k')
298 299 nt.assert_in('key=', matches)
299 300
300 301
301 302 def test_default_arguments_from_docstring():
302 303 doc = min.__doc__
303 304 ip = get_ipython()
304 305 c = ip.Completer
305 306 kwd = c._default_arguments_from_docstring(
306 307 'min(iterable[, key=func]) -> value')
307 308 nt.assert_equal(kwd, ['key'])
308 309 #with cython type etc
309 310 kwd = c._default_arguments_from_docstring(
310 311 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n')
311 312 nt.assert_equal(kwd, ['ncall', 'resume', 'nsplit'])
312 313 #white spaces
313 314 kwd = c._default_arguments_from_docstring(
314 315 '\n Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n')
315 316 nt.assert_equal(kwd, ['ncall', 'resume', 'nsplit'])
316 317
317 318 def test_line_magics():
318 319 ip = get_ipython()
319 320 c = ip.Completer
320 321 s, matches = c.complete(None, 'lsmag')
321 322 nt.assert_in('%lsmagic', matches)
322 323 s, matches = c.complete(None, '%lsmag')
323 324 nt.assert_in('%lsmagic', matches)
324 325
325 326
326 327 def test_cell_magics():
327 328 from IPython.core.magic import register_cell_magic
328 329
329 330 @register_cell_magic
330 331 def _foo_cellm(line, cell):
331 332 pass
332 333
333 334 ip = get_ipython()
334 335 c = ip.Completer
335 336
336 337 s, matches = c.complete(None, '_foo_ce')
337 338 nt.assert_in('%%_foo_cellm', matches)
338 339 s, matches = c.complete(None, '%%_foo_ce')
339 340 nt.assert_in('%%_foo_cellm', matches)
340 341
341 342
342 343 def test_line_cell_magics():
343 344 from IPython.core.magic import register_line_cell_magic
344 345
345 346 @register_line_cell_magic
346 347 def _bar_cellm(line, cell):
347 348 pass
348 349
349 350 ip = get_ipython()
350 351 c = ip.Completer
351 352
352 353 # The policy here is trickier, see comments in completion code. The
353 354 # returned values depend on whether the user passes %% or not explicitly,
354 355 # and this will show a difference if the same name is both a line and cell
355 356 # magic.
356 357 s, matches = c.complete(None, '_bar_ce')
357 358 nt.assert_in('%_bar_cellm', matches)
358 359 nt.assert_in('%%_bar_cellm', matches)
359 360 s, matches = c.complete(None, '%_bar_ce')
360 361 nt.assert_in('%_bar_cellm', matches)
361 362 nt.assert_in('%%_bar_cellm', matches)
362 363 s, matches = c.complete(None, '%%_bar_ce')
363 364 nt.assert_not_in('%_bar_cellm', matches)
364 365 nt.assert_in('%%_bar_cellm', matches)
365 366
366 367
367 368 def test_magic_completion_order():
368 369
369 370 ip = get_ipython()
370 371 c = ip.Completer
371 372
372 373 # Test ordering of magics and non-magics with the same name
373 374 # We want the non-magic first
374 375
375 376 # Before importing matplotlib, there should only be one option:
376 377
377 378 text, matches = c.complete('mat')
378 379 nt.assert_equal(matches, ["%matplotlib"])
379 380
380 381
381 382 ip.run_cell("matplotlib = 1") # introduce name into namespace
382 383
383 384 # After the import, there should be two options, ordered like this:
384 385 text, matches = c.complete('mat')
385 386 nt.assert_equal(matches, ["matplotlib", "%matplotlib"])
386 387
387 388
388 389 ip.run_cell("timeit = 1") # define a user variable called 'timeit'
389 390
390 391 # Order of user variable and line and cell magics with same name:
391 392 text, matches = c.complete('timeit')
392 393 nt.assert_equal(matches, ["timeit", "%timeit","%%timeit"])
393 394
@@ -1,120 +1,121 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tests for completerlib.
3 3
4 4 """
5 5 from __future__ import absolute_import
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Imports
9 9 #-----------------------------------------------------------------------------
10 10
11 11 import os
12 12 import shutil
13 13 import sys
14 14 import tempfile
15 15 import unittest
16 16 from os.path import join
17 17
18 18 from IPython.core.completerlib import magic_run_completer, module_completion
19 from IPython.utils import py3compat
19 20 from IPython.utils.tempdir import TemporaryDirectory
20 21 from IPython.testing.decorators import onlyif_unicode_paths
21 22
22 23
23 24 class MockEvent(object):
24 25 def __init__(self, line):
25 26 self.line = line
26 27
27 28 #-----------------------------------------------------------------------------
28 29 # Test functions begin
29 30 #-----------------------------------------------------------------------------
30 31 class Test_magic_run_completer(unittest.TestCase):
31 32 def setUp(self):
32 33 self.BASETESTDIR = tempfile.mkdtemp()
33 34 for fil in [u"aao.py", u"a.py", u"b.py"]:
34 35 with open(join(self.BASETESTDIR, fil), "w") as sfile:
35 36 sfile.write("pass\n")
36 self.oldpath = os.getcwdu()
37 self.oldpath = py3compat.getcwd()
37 38 os.chdir(self.BASETESTDIR)
38 39
39 40 def tearDown(self):
40 41 os.chdir(self.oldpath)
41 42 shutil.rmtree(self.BASETESTDIR)
42 43
43 44 def test_1(self):
44 45 """Test magic_run_completer, should match two alterntives
45 46 """
46 47 event = MockEvent(u"%run a")
47 48 mockself = None
48 49 match = set(magic_run_completer(mockself, event))
49 50 self.assertEqual(match, set([u"a.py", u"aao.py"]))
50 51
51 52 def test_2(self):
52 53 """Test magic_run_completer, should match one alterntive
53 54 """
54 55 event = MockEvent(u"%run aa")
55 56 mockself = None
56 57 match = set(magic_run_completer(mockself, event))
57 58 self.assertEqual(match, set([u"aao.py"]))
58 59
59 60 def test_3(self):
60 61 """Test magic_run_completer with unterminated " """
61 62 event = MockEvent(u'%run "a')
62 63 mockself = None
63 64 match = set(magic_run_completer(mockself, event))
64 65 self.assertEqual(match, set([u"a.py", u"aao.py"]))
65 66
66 67 def test_import_invalid_module(self):
67 68 """Testing of issue https://github.com/ipython/ipython/issues/1107"""
68 69 invalid_module_names = set(['foo-bar', 'foo:bar', '10foo'])
69 70 valid_module_names = set(['foobar'])
70 71 with TemporaryDirectory() as tmpdir:
71 72 sys.path.insert( 0, tmpdir )
72 73 for name in invalid_module_names | valid_module_names:
73 74 filename = os.path.join(tmpdir, name + '.py')
74 75 open(filename, 'w').close()
75 76
76 77 s = set( module_completion('import foo') )
77 78 intersection = s.intersection(invalid_module_names)
78 79 self.assertFalse(intersection, intersection)
79 80
80 81 assert valid_module_names.issubset(s), valid_module_names.intersection(s)
81 82
82 83 class Test_magic_run_completer_nonascii(unittest.TestCase):
83 84 @onlyif_unicode_paths
84 85 def setUp(self):
85 86 self.BASETESTDIR = tempfile.mkdtemp()
86 87 for fil in [u"aaΓΈ.py", u"a.py", u"b.py"]:
87 88 with open(join(self.BASETESTDIR, fil), "w") as sfile:
88 89 sfile.write("pass\n")
89 self.oldpath = os.getcwdu()
90 self.oldpath = py3compat.getcwd()
90 91 os.chdir(self.BASETESTDIR)
91 92
92 93 def tearDown(self):
93 94 os.chdir(self.oldpath)
94 95 shutil.rmtree(self.BASETESTDIR)
95 96
96 97 @onlyif_unicode_paths
97 98 def test_1(self):
98 99 """Test magic_run_completer, should match two alterntives
99 100 """
100 101 event = MockEvent(u"%run a")
101 102 mockself = None
102 103 match = set(magic_run_completer(mockself, event))
103 104 self.assertEqual(match, set([u"a.py", u"aaΓΈ.py"]))
104 105
105 106 @onlyif_unicode_paths
106 107 def test_2(self):
107 108 """Test magic_run_completer, should match one alterntive
108 109 """
109 110 event = MockEvent(u"%run aa")
110 111 mockself = None
111 112 match = set(magic_run_completer(mockself, event))
112 113 self.assertEqual(match, set([u"aaΓΈ.py"]))
113 114
114 115 @onlyif_unicode_paths
115 116 def test_3(self):
116 117 """Test magic_run_completer with unterminated " """
117 118 event = MockEvent(u'%run "a')
118 119 mockself = None
119 120 match = set(magic_run_completer(mockself, event))
120 121 self.assertEqual(match, set([u"a.py", u"aaΓΈ.py"]))
@@ -1,676 +1,677 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tests for the key interactiveshell module.
3 3
4 4 Historically the main classes in interactiveshell have been under-tested. This
5 5 module should grow as many single-method tests as possible to trap many of the
6 6 recurring bugs we seem to encounter with high-level interaction.
7 7
8 8 Authors
9 9 -------
10 10 * Fernando Perez
11 11 """
12 12 #-----------------------------------------------------------------------------
13 13 # Copyright (C) 2011 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22 # stdlib
23 23 import ast
24 24 import os
25 25 import signal
26 26 import shutil
27 27 import sys
28 28 import tempfile
29 29 import unittest
30 30 from os.path import join
31 31
32 32 # third-party
33 33 import nose.tools as nt
34 34
35 35 # Our own
36 36 from IPython.testing.decorators import skipif, skip_win32, onlyif_unicode_paths
37 37 from IPython.testing import tools as tt
38 38 from IPython.utils import io
39 from IPython.utils import py3compat
39 40 from IPython.utils.py3compat import unicode_type, PY3
40 41
41 42 if PY3:
42 43 from io import StringIO
43 44 else:
44 45 from StringIO import StringIO
45 46
46 47 #-----------------------------------------------------------------------------
47 48 # Globals
48 49 #-----------------------------------------------------------------------------
49 50 # This is used by every single test, no point repeating it ad nauseam
50 51 ip = get_ipython()
51 52
52 53 #-----------------------------------------------------------------------------
53 54 # Tests
54 55 #-----------------------------------------------------------------------------
55 56
56 57 class InteractiveShellTestCase(unittest.TestCase):
57 58 def test_naked_string_cells(self):
58 59 """Test that cells with only naked strings are fully executed"""
59 60 # First, single-line inputs
60 61 ip.run_cell('"a"\n')
61 62 self.assertEqual(ip.user_ns['_'], 'a')
62 63 # And also multi-line cells
63 64 ip.run_cell('"""a\nb"""\n')
64 65 self.assertEqual(ip.user_ns['_'], 'a\nb')
65 66
66 67 def test_run_empty_cell(self):
67 68 """Just make sure we don't get a horrible error with a blank
68 69 cell of input. Yes, I did overlook that."""
69 70 old_xc = ip.execution_count
70 71 ip.run_cell('')
71 72 self.assertEqual(ip.execution_count, old_xc)
72 73
73 74 def test_run_cell_multiline(self):
74 75 """Multi-block, multi-line cells must execute correctly.
75 76 """
76 77 src = '\n'.join(["x=1",
77 78 "y=2",
78 79 "if 1:",
79 80 " x += 1",
80 81 " y += 1",])
81 82 ip.run_cell(src)
82 83 self.assertEqual(ip.user_ns['x'], 2)
83 84 self.assertEqual(ip.user_ns['y'], 3)
84 85
85 86 def test_multiline_string_cells(self):
86 87 "Code sprinkled with multiline strings should execute (GH-306)"
87 88 ip.run_cell('tmp=0')
88 89 self.assertEqual(ip.user_ns['tmp'], 0)
89 90 ip.run_cell('tmp=1;"""a\nb"""\n')
90 91 self.assertEqual(ip.user_ns['tmp'], 1)
91 92
92 93 def test_dont_cache_with_semicolon(self):
93 94 "Ending a line with semicolon should not cache the returned object (GH-307)"
94 95 oldlen = len(ip.user_ns['Out'])
95 96 a = ip.run_cell('1;', store_history=True)
96 97 newlen = len(ip.user_ns['Out'])
97 98 self.assertEqual(oldlen, newlen)
98 99 #also test the default caching behavior
99 100 ip.run_cell('1', store_history=True)
100 101 newlen = len(ip.user_ns['Out'])
101 102 self.assertEqual(oldlen+1, newlen)
102 103
103 104 def test_In_variable(self):
104 105 "Verify that In variable grows with user input (GH-284)"
105 106 oldlen = len(ip.user_ns['In'])
106 107 ip.run_cell('1;', store_history=True)
107 108 newlen = len(ip.user_ns['In'])
108 109 self.assertEqual(oldlen+1, newlen)
109 110 self.assertEqual(ip.user_ns['In'][-1],'1;')
110 111
111 112 def test_magic_names_in_string(self):
112 113 ip.run_cell('a = """\n%exit\n"""')
113 114 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
114 115
115 116 def test_trailing_newline(self):
116 117 """test that running !(command) does not raise a SyntaxError"""
117 118 ip.run_cell('!(true)\n', False)
118 119 ip.run_cell('!(true)\n\n\n', False)
119 120
120 121 def test_gh_597(self):
121 122 """Pretty-printing lists of objects with non-ascii reprs may cause
122 123 problems."""
123 124 class Spam(object):
124 125 def __repr__(self):
125 126 return "\xe9"*50
126 127 import IPython.core.formatters
127 128 f = IPython.core.formatters.PlainTextFormatter()
128 129 f([Spam(),Spam()])
129 130
130 131
131 132 def test_future_flags(self):
132 133 """Check that future flags are used for parsing code (gh-777)"""
133 134 ip.run_cell('from __future__ import print_function')
134 135 try:
135 136 ip.run_cell('prfunc_return_val = print(1,2, sep=" ")')
136 137 assert 'prfunc_return_val' in ip.user_ns
137 138 finally:
138 139 # Reset compiler flags so we don't mess up other tests.
139 140 ip.compile.reset_compiler_flags()
140 141
141 142 def test_future_unicode(self):
142 143 """Check that unicode_literals is imported from __future__ (gh #786)"""
143 144 try:
144 145 ip.run_cell(u'byte_str = "a"')
145 146 assert isinstance(ip.user_ns['byte_str'], str) # string literals are byte strings by default
146 147 ip.run_cell('from __future__ import unicode_literals')
147 148 ip.run_cell(u'unicode_str = "a"')
148 149 assert isinstance(ip.user_ns['unicode_str'], unicode_type) # strings literals are now unicode
149 150 finally:
150 151 # Reset compiler flags so we don't mess up other tests.
151 152 ip.compile.reset_compiler_flags()
152 153
153 154 def test_can_pickle(self):
154 155 "Can we pickle objects defined interactively (GH-29)"
155 156 ip = get_ipython()
156 157 ip.reset()
157 158 ip.run_cell(("class Mylist(list):\n"
158 159 " def __init__(self,x=[]):\n"
159 160 " list.__init__(self,x)"))
160 161 ip.run_cell("w=Mylist([1,2,3])")
161 162
162 163 from pickle import dumps
163 164
164 165 # We need to swap in our main module - this is only necessary
165 166 # inside the test framework, because IPython puts the interactive module
166 167 # in place (but the test framework undoes this).
167 168 _main = sys.modules['__main__']
168 169 sys.modules['__main__'] = ip.user_module
169 170 try:
170 171 res = dumps(ip.user_ns["w"])
171 172 finally:
172 173 sys.modules['__main__'] = _main
173 174 self.assertTrue(isinstance(res, bytes))
174 175
175 176 def test_global_ns(self):
176 177 "Code in functions must be able to access variables outside them."
177 178 ip = get_ipython()
178 179 ip.run_cell("a = 10")
179 180 ip.run_cell(("def f(x):\n"
180 181 " return x + a"))
181 182 ip.run_cell("b = f(12)")
182 183 self.assertEqual(ip.user_ns["b"], 22)
183 184
184 185 def test_bad_custom_tb(self):
185 186 """Check that InteractiveShell is protected from bad custom exception handlers"""
186 187 from IPython.utils import io
187 188 save_stderr = io.stderr
188 189 try:
189 190 # capture stderr
190 191 io.stderr = StringIO()
191 192 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
192 193 self.assertEqual(ip.custom_exceptions, (IOError,))
193 194 ip.run_cell(u'raise IOError("foo")')
194 195 self.assertEqual(ip.custom_exceptions, ())
195 196 self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue())
196 197 finally:
197 198 io.stderr = save_stderr
198 199
199 200 def test_bad_custom_tb_return(self):
200 201 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
201 202 from IPython.utils import io
202 203 save_stderr = io.stderr
203 204 try:
204 205 # capture stderr
205 206 io.stderr = StringIO()
206 207 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
207 208 self.assertEqual(ip.custom_exceptions, (NameError,))
208 209 ip.run_cell(u'a=abracadabra')
209 210 self.assertEqual(ip.custom_exceptions, ())
210 211 self.assertTrue("Custom TB Handler failed" in io.stderr.getvalue())
211 212 finally:
212 213 io.stderr = save_stderr
213 214
214 215 def test_drop_by_id(self):
215 216 myvars = {"a":object(), "b":object(), "c": object()}
216 217 ip.push(myvars, interactive=False)
217 218 for name in myvars:
218 219 assert name in ip.user_ns, name
219 220 assert name in ip.user_ns_hidden, name
220 221 ip.user_ns['b'] = 12
221 222 ip.drop_by_id(myvars)
222 223 for name in ["a", "c"]:
223 224 assert name not in ip.user_ns, name
224 225 assert name not in ip.user_ns_hidden, name
225 226 assert ip.user_ns['b'] == 12
226 227 ip.reset()
227 228
228 229 def test_var_expand(self):
229 230 ip.user_ns['f'] = u'Ca\xf1o'
230 231 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
231 232 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
232 233 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
233 234 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
234 235
235 236 ip.user_ns['f'] = b'Ca\xc3\xb1o'
236 237 # This should not raise any exception:
237 238 ip.var_expand(u'echo $f')
238 239
239 240 def test_var_expand_local(self):
240 241 """Test local variable expansion in !system and %magic calls"""
241 242 # !system
242 243 ip.run_cell('def test():\n'
243 244 ' lvar = "ttt"\n'
244 245 ' ret = !echo {lvar}\n'
245 246 ' return ret[0]\n')
246 247 res = ip.user_ns['test']()
247 248 nt.assert_in('ttt', res)
248 249
249 250 # %magic
250 251 ip.run_cell('def makemacro():\n'
251 252 ' macroname = "macro_var_expand_locals"\n'
252 253 ' %macro {macroname} codestr\n')
253 254 ip.user_ns['codestr'] = "str(12)"
254 255 ip.run_cell('makemacro()')
255 256 nt.assert_in('macro_var_expand_locals', ip.user_ns)
256 257
257 258 def test_var_expand_self(self):
258 259 """Test variable expansion with the name 'self', which was failing.
259 260
260 261 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
261 262 """
262 263 ip.run_cell('class cTest:\n'
263 264 ' classvar="see me"\n'
264 265 ' def test(self):\n'
265 266 ' res = !echo Variable: {self.classvar}\n'
266 267 ' return res[0]\n')
267 268 nt.assert_in('see me', ip.user_ns['cTest']().test())
268 269
269 270 def test_bad_var_expand(self):
270 271 """var_expand on invalid formats shouldn't raise"""
271 272 # SyntaxError
272 273 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
273 274 # NameError
274 275 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
275 276 # ZeroDivisionError
276 277 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
277 278
278 279 def test_silent_nopostexec(self):
279 280 """run_cell(silent=True) doesn't invoke post-exec funcs"""
280 281 d = dict(called=False)
281 282 def set_called():
282 283 d['called'] = True
283 284
284 285 ip.register_post_execute(set_called)
285 286 ip.run_cell("1", silent=True)
286 287 self.assertFalse(d['called'])
287 288 # double-check that non-silent exec did what we expected
288 289 # silent to avoid
289 290 ip.run_cell("1")
290 291 self.assertTrue(d['called'])
291 292 # remove post-exec
292 293 ip._post_execute.pop(set_called)
293 294
294 295 def test_silent_noadvance(self):
295 296 """run_cell(silent=True) doesn't advance execution_count"""
296 297 ec = ip.execution_count
297 298 # silent should force store_history=False
298 299 ip.run_cell("1", store_history=True, silent=True)
299 300
300 301 self.assertEqual(ec, ip.execution_count)
301 302 # double-check that non-silent exec did what we expected
302 303 # silent to avoid
303 304 ip.run_cell("1", store_history=True)
304 305 self.assertEqual(ec+1, ip.execution_count)
305 306
306 307 def test_silent_nodisplayhook(self):
307 308 """run_cell(silent=True) doesn't trigger displayhook"""
308 309 d = dict(called=False)
309 310
310 311 trap = ip.display_trap
311 312 save_hook = trap.hook
312 313
313 314 def failing_hook(*args, **kwargs):
314 315 d['called'] = True
315 316
316 317 try:
317 318 trap.hook = failing_hook
318 319 ip.run_cell("1", silent=True)
319 320 self.assertFalse(d['called'])
320 321 # double-check that non-silent exec did what we expected
321 322 # silent to avoid
322 323 ip.run_cell("1")
323 324 self.assertTrue(d['called'])
324 325 finally:
325 326 trap.hook = save_hook
326 327
327 328 @skipif(sys.version_info[0] >= 3, "softspace removed in py3")
328 329 def test_print_softspace(self):
329 330 """Verify that softspace is handled correctly when executing multiple
330 331 statements.
331 332
332 333 In [1]: print 1; print 2
333 334 1
334 335 2
335 336
336 337 In [2]: print 1,; print 2
337 338 1 2
338 339 """
339 340
340 341 def test_ofind_line_magic(self):
341 342 from IPython.core.magic import register_line_magic
342 343
343 344 @register_line_magic
344 345 def lmagic(line):
345 346 "A line magic"
346 347
347 348 # Get info on line magic
348 349 lfind = ip._ofind('lmagic')
349 350 info = dict(found=True, isalias=False, ismagic=True,
350 351 namespace = 'IPython internal', obj= lmagic.__wrapped__,
351 352 parent = None)
352 353 nt.assert_equal(lfind, info)
353 354
354 355 def test_ofind_cell_magic(self):
355 356 from IPython.core.magic import register_cell_magic
356 357
357 358 @register_cell_magic
358 359 def cmagic(line, cell):
359 360 "A cell magic"
360 361
361 362 # Get info on cell magic
362 363 find = ip._ofind('cmagic')
363 364 info = dict(found=True, isalias=False, ismagic=True,
364 365 namespace = 'IPython internal', obj= cmagic.__wrapped__,
365 366 parent = None)
366 367 nt.assert_equal(find, info)
367 368
368 369 def test_custom_exception(self):
369 370 called = []
370 371 def my_handler(shell, etype, value, tb, tb_offset=None):
371 372 called.append(etype)
372 373 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
373 374
374 375 ip.set_custom_exc((ValueError,), my_handler)
375 376 try:
376 377 ip.run_cell("raise ValueError('test')")
377 378 # Check that this was called, and only once.
378 379 self.assertEqual(called, [ValueError])
379 380 finally:
380 381 # Reset the custom exception hook
381 382 ip.set_custom_exc((), None)
382 383
383 384 @skipif(sys.version_info[0] >= 3, "no differences with __future__ in py3")
384 385 def test_future_environment(self):
385 386 "Can we run code with & without the shell's __future__ imports?"
386 387 ip.run_cell("from __future__ import division")
387 388 ip.run_cell("a = 1/2", shell_futures=True)
388 389 self.assertEqual(ip.user_ns['a'], 0.5)
389 390 ip.run_cell("b = 1/2", shell_futures=False)
390 391 self.assertEqual(ip.user_ns['b'], 0)
391 392
392 393 ip.compile.reset_compiler_flags()
393 394 # This shouldn't leak to the shell's compiler
394 395 ip.run_cell("from __future__ import division \nc=1/2", shell_futures=False)
395 396 self.assertEqual(ip.user_ns['c'], 0.5)
396 397 ip.run_cell("d = 1/2", shell_futures=True)
397 398 self.assertEqual(ip.user_ns['d'], 0)
398 399
399 400
400 401 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
401 402
402 403 @onlyif_unicode_paths
403 404 def setUp(self):
404 405 self.BASETESTDIR = tempfile.mkdtemp()
405 406 self.TESTDIR = join(self.BASETESTDIR, u"Γ₯Àâ")
406 407 os.mkdir(self.TESTDIR)
407 408 with open(join(self.TESTDIR, u"Γ₯Àâtestscript.py"), "w") as sfile:
408 409 sfile.write("pass\n")
409 self.oldpath = os.getcwdu()
410 self.oldpath = py3compat.getcwd()
410 411 os.chdir(self.TESTDIR)
411 412 self.fname = u"Γ₯Àâtestscript.py"
412 413
413 414 def tearDown(self):
414 415 os.chdir(self.oldpath)
415 416 shutil.rmtree(self.BASETESTDIR)
416 417
417 418 @onlyif_unicode_paths
418 419 def test_1(self):
419 420 """Test safe_execfile with non-ascii path
420 421 """
421 422 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
422 423
423 424 class ExitCodeChecks(tt.TempFileMixin):
424 425 def test_exit_code_ok(self):
425 426 self.system('exit 0')
426 427 self.assertEqual(ip.user_ns['_exit_code'], 0)
427 428
428 429 def test_exit_code_error(self):
429 430 self.system('exit 1')
430 431 self.assertEqual(ip.user_ns['_exit_code'], 1)
431 432
432 433 @skipif(not hasattr(signal, 'SIGALRM'))
433 434 def test_exit_code_signal(self):
434 435 self.mktmp("import signal, time\n"
435 436 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
436 437 "time.sleep(1)\n")
437 438 self.system("%s %s" % (sys.executable, self.fname))
438 439 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
439 440
440 441 class TestSystemRaw(unittest.TestCase, ExitCodeChecks):
441 442 system = ip.system_raw
442 443
443 444 @onlyif_unicode_paths
444 445 def test_1(self):
445 446 """Test system_raw with non-ascii cmd
446 447 """
447 448 cmd = u'''python -c "'Γ₯Àâ'" '''
448 449 ip.system_raw(cmd)
449 450
450 451 # TODO: Exit codes are currently ignored on Windows.
451 452 class TestSystemPipedExitCode(unittest.TestCase, ExitCodeChecks):
452 453 system = ip.system_piped
453 454
454 455 @skip_win32
455 456 def test_exit_code_ok(self):
456 457 ExitCodeChecks.test_exit_code_ok(self)
457 458
458 459 @skip_win32
459 460 def test_exit_code_error(self):
460 461 ExitCodeChecks.test_exit_code_error(self)
461 462
462 463 @skip_win32
463 464 def test_exit_code_signal(self):
464 465 ExitCodeChecks.test_exit_code_signal(self)
465 466
466 467 class TestModules(unittest.TestCase, tt.TempFileMixin):
467 468 def test_extraneous_loads(self):
468 469 """Test we're not loading modules on startup that we shouldn't.
469 470 """
470 471 self.mktmp("import sys\n"
471 472 "print('numpy' in sys.modules)\n"
472 473 "print('IPython.parallel' in sys.modules)\n"
473 474 "print('IPython.kernel.zmq' in sys.modules)\n"
474 475 )
475 476 out = "False\nFalse\nFalse\n"
476 477 tt.ipexec_validate(self.fname, out)
477 478
478 479 class Negator(ast.NodeTransformer):
479 480 """Negates all number literals in an AST."""
480 481 def visit_Num(self, node):
481 482 node.n = -node.n
482 483 return node
483 484
484 485 class TestAstTransform(unittest.TestCase):
485 486 def setUp(self):
486 487 self.negator = Negator()
487 488 ip.ast_transformers.append(self.negator)
488 489
489 490 def tearDown(self):
490 491 ip.ast_transformers.remove(self.negator)
491 492
492 493 def test_run_cell(self):
493 494 with tt.AssertPrints('-34'):
494 495 ip.run_cell('print (12 + 22)')
495 496
496 497 # A named reference to a number shouldn't be transformed.
497 498 ip.user_ns['n'] = 55
498 499 with tt.AssertNotPrints('-55'):
499 500 ip.run_cell('print (n)')
500 501
501 502 def test_timeit(self):
502 503 called = set()
503 504 def f(x):
504 505 called.add(x)
505 506 ip.push({'f':f})
506 507
507 508 with tt.AssertPrints("best of "):
508 509 ip.run_line_magic("timeit", "-n1 f(1)")
509 510 self.assertEqual(called, set([-1]))
510 511 called.clear()
511 512
512 513 with tt.AssertPrints("best of "):
513 514 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
514 515 self.assertEqual(called, set([-2, -3]))
515 516
516 517 def test_time(self):
517 518 called = []
518 519 def f(x):
519 520 called.append(x)
520 521 ip.push({'f':f})
521 522
522 523 # Test with an expression
523 524 with tt.AssertPrints("Wall time: "):
524 525 ip.run_line_magic("time", "f(5+9)")
525 526 self.assertEqual(called, [-14])
526 527 called[:] = []
527 528
528 529 # Test with a statement (different code path)
529 530 with tt.AssertPrints("Wall time: "):
530 531 ip.run_line_magic("time", "a = f(-3 + -2)")
531 532 self.assertEqual(called, [5])
532 533
533 534 def test_macro(self):
534 535 ip.push({'a':10})
535 536 # The AST transformation makes this do a+=-1
536 537 ip.define_macro("amacro", "a+=1\nprint(a)")
537 538
538 539 with tt.AssertPrints("9"):
539 540 ip.run_cell("amacro")
540 541 with tt.AssertPrints("8"):
541 542 ip.run_cell("amacro")
542 543
543 544 class IntegerWrapper(ast.NodeTransformer):
544 545 """Wraps all integers in a call to Integer()"""
545 546 def visit_Num(self, node):
546 547 if isinstance(node.n, int):
547 548 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
548 549 args=[node], keywords=[])
549 550 return node
550 551
551 552 class TestAstTransform2(unittest.TestCase):
552 553 def setUp(self):
553 554 self.intwrapper = IntegerWrapper()
554 555 ip.ast_transformers.append(self.intwrapper)
555 556
556 557 self.calls = []
557 558 def Integer(*args):
558 559 self.calls.append(args)
559 560 return args
560 561 ip.push({"Integer": Integer})
561 562
562 563 def tearDown(self):
563 564 ip.ast_transformers.remove(self.intwrapper)
564 565 del ip.user_ns['Integer']
565 566
566 567 def test_run_cell(self):
567 568 ip.run_cell("n = 2")
568 569 self.assertEqual(self.calls, [(2,)])
569 570
570 571 # This shouldn't throw an error
571 572 ip.run_cell("o = 2.0")
572 573 self.assertEqual(ip.user_ns['o'], 2.0)
573 574
574 575 def test_timeit(self):
575 576 called = set()
576 577 def f(x):
577 578 called.add(x)
578 579 ip.push({'f':f})
579 580
580 581 with tt.AssertPrints("best of "):
581 582 ip.run_line_magic("timeit", "-n1 f(1)")
582 583 self.assertEqual(called, set([(1,)]))
583 584 called.clear()
584 585
585 586 with tt.AssertPrints("best of "):
586 587 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
587 588 self.assertEqual(called, set([(2,), (3,)]))
588 589
589 590 class ErrorTransformer(ast.NodeTransformer):
590 591 """Throws an error when it sees a number."""
591 592 def visit_Num(self):
592 593 raise ValueError("test")
593 594
594 595 class TestAstTransformError(unittest.TestCase):
595 596 def test_unregistering(self):
596 597 err_transformer = ErrorTransformer()
597 598 ip.ast_transformers.append(err_transformer)
598 599
599 600 with tt.AssertPrints("unregister", channel='stderr'):
600 601 ip.run_cell("1 + 2")
601 602
602 603 # This should have been removed.
603 604 nt.assert_not_in(err_transformer, ip.ast_transformers)
604 605
605 606 def test__IPYTHON__():
606 607 # This shouldn't raise a NameError, that's all
607 608 __IPYTHON__
608 609
609 610
610 611 class DummyRepr(object):
611 612 def __repr__(self):
612 613 return "DummyRepr"
613 614
614 615 def _repr_html_(self):
615 616 return "<b>dummy</b>"
616 617
617 618 def _repr_javascript_(self):
618 619 return "console.log('hi');", {'key': 'value'}
619 620
620 621
621 622 def test_user_variables():
622 623 # enable all formatters
623 624 ip.display_formatter.active_types = ip.display_formatter.format_types
624 625
625 626 ip.user_ns['dummy'] = d = DummyRepr()
626 627 keys = set(['dummy', 'doesnotexist'])
627 628 r = ip.user_variables(keys)
628 629
629 630 nt.assert_equal(keys, set(r.keys()))
630 631 dummy = r['dummy']
631 632 nt.assert_equal(set(['status', 'data', 'metadata']), set(dummy.keys()))
632 633 nt.assert_equal(dummy['status'], 'ok')
633 634 data = dummy['data']
634 635 metadata = dummy['metadata']
635 636 nt.assert_equal(data.get('text/html'), d._repr_html_())
636 637 js, jsmd = d._repr_javascript_()
637 638 nt.assert_equal(data.get('application/javascript'), js)
638 639 nt.assert_equal(metadata.get('application/javascript'), jsmd)
639 640
640 641 dne = r['doesnotexist']
641 642 nt.assert_equal(dne['status'], 'error')
642 643 nt.assert_equal(dne['ename'], 'KeyError')
643 644
644 645 # back to text only
645 646 ip.display_formatter.active_types = ['text/plain']
646 647
647 648 def test_user_expression():
648 649 # enable all formatters
649 650 ip.display_formatter.active_types = ip.display_formatter.format_types
650 651 query = {
651 652 'a' : '1 + 2',
652 653 'b' : '1/0',
653 654 }
654 655 r = ip.user_expressions(query)
655 656 import pprint
656 657 pprint.pprint(r)
657 658 nt.assert_equal(r.keys(), query.keys())
658 659 a = r['a']
659 660 nt.assert_equal(set(['status', 'data', 'metadata']), set(a.keys()))
660 661 nt.assert_equal(a['status'], 'ok')
661 662 data = a['data']
662 663 metadata = a['metadata']
663 664 nt.assert_equal(data.get('text/plain'), '3')
664 665
665 666 b = r['b']
666 667 nt.assert_equal(b['status'], 'error')
667 668 nt.assert_equal(b['ename'], 'ZeroDivisionError')
668 669
669 670 # back to text only
670 671 ip.display_formatter.active_types = ['text/plain']
671 672
672 673
673 674
674 675
675 676
676 677
@@ -1,944 +1,944 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tests for various magic functions.
3 3
4 4 Needs to be run by nose (to make ipython session available).
5 5 """
6 6 from __future__ import absolute_import
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Imports
10 10 #-----------------------------------------------------------------------------
11 11
12 12 import io
13 13 import os
14 14 import sys
15 15 from unittest import TestCase
16 16
17 17 try:
18 18 from importlib import invalidate_caches # Required from Python 3.3
19 19 except ImportError:
20 20 def invalidate_caches():
21 21 pass
22 22
23 23 import nose.tools as nt
24 24
25 25 from IPython.core import magic
26 26 from IPython.core.magic import (Magics, magics_class, line_magic,
27 27 cell_magic, line_cell_magic,
28 28 register_line_magic, register_cell_magic,
29 29 register_line_cell_magic)
30 30 from IPython.core.magics import execution, script, code
31 31 from IPython.nbformat.v3.tests.nbexamples import nb0
32 32 from IPython.nbformat import current
33 33 from IPython.testing import decorators as dec
34 34 from IPython.testing import tools as tt
35 35 from IPython.utils import py3compat
36 36 from IPython.utils.io import capture_output
37 37 from IPython.utils.tempdir import TemporaryDirectory
38 38 from IPython.utils.process import find_cmd
39 39
40 40 if py3compat.PY3:
41 41 from io import StringIO
42 42 else:
43 43 from StringIO import StringIO
44 44
45 45 #-----------------------------------------------------------------------------
46 46 # Test functions begin
47 47 #-----------------------------------------------------------------------------
48 48
49 49 @magic.magics_class
50 50 class DummyMagics(magic.Magics): pass
51 51
52 52 def test_extract_code_ranges():
53 53 instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :"
54 54 expected = [(0, 1),
55 55 (2, 3),
56 56 (4, 6),
57 57 (6, 9),
58 58 (9, 14),
59 59 (16, None),
60 60 (None, 9),
61 61 (9, None),
62 62 (None, 13),
63 63 (None, None)]
64 64 actual = list(code.extract_code_ranges(instr))
65 65 nt.assert_equal(actual, expected)
66 66
67 67 def test_extract_symbols():
68 68 source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n"""
69 69 symbols_args = ["a", "b", "A", "A,b", "A,a", "z"]
70 70 expected = [([], ['a']),
71 71 (["def b():\n return 42\n"], []),
72 72 (["class A: pass\n"], []),
73 73 (["class A: pass\n", "def b():\n return 42\n"], []),
74 74 (["class A: pass\n"], ['a']),
75 75 ([], ['z'])]
76 76 for symbols, exp in zip(symbols_args, expected):
77 77 nt.assert_equal(code.extract_symbols(source, symbols), exp)
78 78
79 79
80 80 def test_extract_symbols_raises_exception_with_non_python_code():
81 81 source = ("=begin A Ruby program :)=end\n"
82 82 "def hello\n"
83 83 "puts 'Hello world'\n"
84 84 "end")
85 85 with nt.assert_raises(SyntaxError):
86 86 code.extract_symbols(source, "hello")
87 87
88 88 def test_config():
89 89 """ test that config magic does not raise
90 90 can happen if Configurable init is moved too early into
91 91 Magics.__init__ as then a Config object will be registerd as a
92 92 magic.
93 93 """
94 94 ## should not raise.
95 95 _ip.magic('config')
96 96
97 97 def test_rehashx():
98 98 # clear up everything
99 99 _ip = get_ipython()
100 100 _ip.alias_manager.clear_aliases()
101 101 del _ip.db['syscmdlist']
102 102
103 103 _ip.magic('rehashx')
104 104 # Practically ALL ipython development systems will have more than 10 aliases
105 105
106 106 nt.assert_true(len(_ip.alias_manager.aliases) > 10)
107 107 for name, cmd in _ip.alias_manager.aliases:
108 108 # we must strip dots from alias names
109 109 nt.assert_not_in('.', name)
110 110
111 111 # rehashx must fill up syscmdlist
112 112 scoms = _ip.db['syscmdlist']
113 113 nt.assert_true(len(scoms) > 10)
114 114
115 115
116 116 def test_magic_parse_options():
117 117 """Test that we don't mangle paths when parsing magic options."""
118 118 ip = get_ipython()
119 119 path = 'c:\\x'
120 120 m = DummyMagics(ip)
121 121 opts = m.parse_options('-f %s' % path,'f:')[0]
122 122 # argv splitting is os-dependent
123 123 if os.name == 'posix':
124 124 expected = 'c:x'
125 125 else:
126 126 expected = path
127 127 nt.assert_equal(opts['f'], expected)
128 128
129 129 def test_magic_parse_long_options():
130 130 """Magic.parse_options can handle --foo=bar long options"""
131 131 ip = get_ipython()
132 132 m = DummyMagics(ip)
133 133 opts, _ = m.parse_options('--foo --bar=bubble', 'a', 'foo', 'bar=')
134 134 nt.assert_in('foo', opts)
135 135 nt.assert_in('bar', opts)
136 136 nt.assert_equal(opts['bar'], "bubble")
137 137
138 138
139 139 @dec.skip_without('sqlite3')
140 140 def doctest_hist_f():
141 141 """Test %hist -f with temporary filename.
142 142
143 143 In [9]: import tempfile
144 144
145 145 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
146 146
147 147 In [11]: %hist -nl -f $tfile 3
148 148
149 149 In [13]: import os; os.unlink(tfile)
150 150 """
151 151
152 152
153 153 @dec.skip_without('sqlite3')
154 154 def doctest_hist_r():
155 155 """Test %hist -r
156 156
157 157 XXX - This test is not recording the output correctly. For some reason, in
158 158 testing mode the raw history isn't getting populated. No idea why.
159 159 Disabling the output checking for now, though at least we do run it.
160 160
161 161 In [1]: 'hist' in _ip.lsmagic()
162 162 Out[1]: True
163 163
164 164 In [2]: x=1
165 165
166 166 In [3]: %hist -rl 2
167 167 x=1 # random
168 168 %hist -r 2
169 169 """
170 170
171 171
172 172 @dec.skip_without('sqlite3')
173 173 def doctest_hist_op():
174 174 """Test %hist -op
175 175
176 176 In [1]: class b(float):
177 177 ...: pass
178 178 ...:
179 179
180 180 In [2]: class s(object):
181 181 ...: def __str__(self):
182 182 ...: return 's'
183 183 ...:
184 184
185 185 In [3]:
186 186
187 187 In [4]: class r(b):
188 188 ...: def __repr__(self):
189 189 ...: return 'r'
190 190 ...:
191 191
192 192 In [5]: class sr(s,r): pass
193 193 ...:
194 194
195 195 In [6]:
196 196
197 197 In [7]: bb=b()
198 198
199 199 In [8]: ss=s()
200 200
201 201 In [9]: rr=r()
202 202
203 203 In [10]: ssrr=sr()
204 204
205 205 In [11]: 4.5
206 206 Out[11]: 4.5
207 207
208 208 In [12]: str(ss)
209 209 Out[12]: 's'
210 210
211 211 In [13]:
212 212
213 213 In [14]: %hist -op
214 214 >>> class b:
215 215 ... pass
216 216 ...
217 217 >>> class s(b):
218 218 ... def __str__(self):
219 219 ... return 's'
220 220 ...
221 221 >>>
222 222 >>> class r(b):
223 223 ... def __repr__(self):
224 224 ... return 'r'
225 225 ...
226 226 >>> class sr(s,r): pass
227 227 >>>
228 228 >>> bb=b()
229 229 >>> ss=s()
230 230 >>> rr=r()
231 231 >>> ssrr=sr()
232 232 >>> 4.5
233 233 4.5
234 234 >>> str(ss)
235 235 's'
236 236 >>>
237 237 """
238 238
239 239
240 240 @dec.skip_without('sqlite3')
241 241 def test_macro():
242 242 ip = get_ipython()
243 243 ip.history_manager.reset() # Clear any existing history.
244 244 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
245 245 for i, cmd in enumerate(cmds, start=1):
246 246 ip.history_manager.store_inputs(i, cmd)
247 247 ip.magic("macro test 1-3")
248 248 nt.assert_equal(ip.user_ns["test"].value, "\n".join(cmds)+"\n")
249 249
250 250 # List macros
251 251 nt.assert_in("test", ip.magic("macro"))
252 252
253 253
254 254 @dec.skip_without('sqlite3')
255 255 def test_macro_run():
256 256 """Test that we can run a multi-line macro successfully."""
257 257 ip = get_ipython()
258 258 ip.history_manager.reset()
259 259 cmds = ["a=10", "a+=1", py3compat.doctest_refactor_print("print a"),
260 260 "%macro test 2-3"]
261 261 for cmd in cmds:
262 262 ip.run_cell(cmd, store_history=True)
263 263 nt.assert_equal(ip.user_ns["test"].value,
264 264 py3compat.doctest_refactor_print("a+=1\nprint a\n"))
265 265 with tt.AssertPrints("12"):
266 266 ip.run_cell("test")
267 267 with tt.AssertPrints("13"):
268 268 ip.run_cell("test")
269 269
270 270
271 271 def test_magic_magic():
272 272 """Test %magic"""
273 273 ip = get_ipython()
274 274 with capture_output() as captured:
275 275 ip.magic("magic")
276 276
277 277 stdout = captured.stdout
278 278 nt.assert_in('%magic', stdout)
279 279 nt.assert_in('IPython', stdout)
280 280 nt.assert_in('Available', stdout)
281 281
282 282
283 283 @dec.skipif_not_numpy
284 284 def test_numpy_reset_array_undec():
285 285 "Test '%reset array' functionality"
286 286 _ip.ex('import numpy as np')
287 287 _ip.ex('a = np.empty(2)')
288 288 nt.assert_in('a', _ip.user_ns)
289 289 _ip.magic('reset -f array')
290 290 nt.assert_not_in('a', _ip.user_ns)
291 291
292 292 def test_reset_out():
293 293 "Test '%reset out' magic"
294 294 _ip.run_cell("parrot = 'dead'", store_history=True)
295 295 # test '%reset -f out', make an Out prompt
296 296 _ip.run_cell("parrot", store_history=True)
297 297 nt.assert_true('dead' in [_ip.user_ns[x] for x in ('_','__','___')])
298 298 _ip.magic('reset -f out')
299 299 nt.assert_false('dead' in [_ip.user_ns[x] for x in ('_','__','___')])
300 300 nt.assert_equal(len(_ip.user_ns['Out']), 0)
301 301
302 302 def test_reset_in():
303 303 "Test '%reset in' magic"
304 304 # test '%reset -f in'
305 305 _ip.run_cell("parrot", store_history=True)
306 306 nt.assert_true('parrot' in [_ip.user_ns[x] for x in ('_i','_ii','_iii')])
307 307 _ip.magic('%reset -f in')
308 308 nt.assert_false('parrot' in [_ip.user_ns[x] for x in ('_i','_ii','_iii')])
309 309 nt.assert_equal(len(set(_ip.user_ns['In'])), 1)
310 310
311 311 def test_reset_dhist():
312 312 "Test '%reset dhist' magic"
313 313 _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing
314 314 _ip.magic('cd ' + os.path.dirname(nt.__file__))
315 315 _ip.magic('cd -')
316 316 nt.assert_true(len(_ip.user_ns['_dh']) > 0)
317 317 _ip.magic('reset -f dhist')
318 318 nt.assert_equal(len(_ip.user_ns['_dh']), 0)
319 319 _ip.run_cell("_dh = [d for d in tmp]") #restore
320 320
321 321 def test_reset_in_length():
322 322 "Test that '%reset in' preserves In[] length"
323 323 _ip.run_cell("print 'foo'")
324 324 _ip.run_cell("reset -f in")
325 325 nt.assert_equal(len(_ip.user_ns['In']), _ip.displayhook.prompt_count+1)
326 326
327 327 def test_tb_syntaxerror():
328 328 """test %tb after a SyntaxError"""
329 329 ip = get_ipython()
330 330 ip.run_cell("for")
331 331
332 332 # trap and validate stdout
333 333 save_stdout = sys.stdout
334 334 try:
335 335 sys.stdout = StringIO()
336 336 ip.run_cell("%tb")
337 337 out = sys.stdout.getvalue()
338 338 finally:
339 339 sys.stdout = save_stdout
340 340 # trim output, and only check the last line
341 341 last_line = out.rstrip().splitlines()[-1].strip()
342 342 nt.assert_equal(last_line, "SyntaxError: invalid syntax")
343 343
344 344
345 345 def test_time():
346 346 ip = get_ipython()
347 347
348 348 with tt.AssertPrints("Wall time: "):
349 349 ip.run_cell("%time None")
350 350
351 351 ip.run_cell("def f(kmjy):\n"
352 352 " %time print (2*kmjy)")
353 353
354 354 with tt.AssertPrints("Wall time: "):
355 355 with tt.AssertPrints("hihi", suppress=False):
356 356 ip.run_cell("f('hi')")
357 357
358 358
359 359 @dec.skip_win32
360 360 def test_time2():
361 361 ip = get_ipython()
362 362
363 363 with tt.AssertPrints("CPU times: user "):
364 364 ip.run_cell("%time None")
365 365
366 366 def test_time3():
367 367 """Erroneous magic function calls, issue gh-3334"""
368 368 ip = get_ipython()
369 369 ip.user_ns.pop('run', None)
370 370
371 371 with tt.AssertNotPrints("not found", channel='stderr'):
372 372 ip.run_cell("%%time\n"
373 373 "run = 0\n"
374 374 "run += 1")
375 375
376 376 def test_doctest_mode():
377 377 "Toggle doctest_mode twice, it should be a no-op and run without error"
378 378 _ip.magic('doctest_mode')
379 379 _ip.magic('doctest_mode')
380 380
381 381
382 382 def test_parse_options():
383 383 """Tests for basic options parsing in magics."""
384 384 # These are only the most minimal of tests, more should be added later. At
385 385 # the very least we check that basic text/unicode calls work OK.
386 386 m = DummyMagics(_ip)
387 387 nt.assert_equal(m.parse_options('foo', '')[1], 'foo')
388 388 nt.assert_equal(m.parse_options(u'foo', '')[1], u'foo')
389 389
390 390
391 391 def test_dirops():
392 392 """Test various directory handling operations."""
393 # curpath = lambda :os.path.splitdrive(os.getcwdu())[1].replace('\\','/')
394 curpath = os.getcwdu
395 startdir = os.getcwdu()
393 # curpath = lambda :os.path.splitdrive(py3compat.getcwd())[1].replace('\\','/')
394 curpath = py3compat.getcwd
395 startdir = py3compat.getcwd()
396 396 ipdir = os.path.realpath(_ip.ipython_dir)
397 397 try:
398 398 _ip.magic('cd "%s"' % ipdir)
399 399 nt.assert_equal(curpath(), ipdir)
400 400 _ip.magic('cd -')
401 401 nt.assert_equal(curpath(), startdir)
402 402 _ip.magic('pushd "%s"' % ipdir)
403 403 nt.assert_equal(curpath(), ipdir)
404 404 _ip.magic('popd')
405 405 nt.assert_equal(curpath(), startdir)
406 406 finally:
407 407 os.chdir(startdir)
408 408
409 409
410 410 def test_xmode():
411 411 # Calling xmode three times should be a no-op
412 412 xmode = _ip.InteractiveTB.mode
413 413 for i in range(3):
414 414 _ip.magic("xmode")
415 415 nt.assert_equal(_ip.InteractiveTB.mode, xmode)
416 416
417 417 def test_reset_hard():
418 418 monitor = []
419 419 class A(object):
420 420 def __del__(self):
421 421 monitor.append(1)
422 422 def __repr__(self):
423 423 return "<A instance>"
424 424
425 425 _ip.user_ns["a"] = A()
426 426 _ip.run_cell("a")
427 427
428 428 nt.assert_equal(monitor, [])
429 429 _ip.magic("reset -f")
430 430 nt.assert_equal(monitor, [1])
431 431
432 432 class TestXdel(tt.TempFileMixin):
433 433 def test_xdel(self):
434 434 """Test that references from %run are cleared by xdel."""
435 435 src = ("class A(object):\n"
436 436 " monitor = []\n"
437 437 " def __del__(self):\n"
438 438 " self.monitor.append(1)\n"
439 439 "a = A()\n")
440 440 self.mktmp(src)
441 441 # %run creates some hidden references...
442 442 _ip.magic("run %s" % self.fname)
443 443 # ... as does the displayhook.
444 444 _ip.run_cell("a")
445 445
446 446 monitor = _ip.user_ns["A"].monitor
447 447 nt.assert_equal(monitor, [])
448 448
449 449 _ip.magic("xdel a")
450 450
451 451 # Check that a's __del__ method has been called.
452 452 nt.assert_equal(monitor, [1])
453 453
454 454 def doctest_who():
455 455 """doctest for %who
456 456
457 457 In [1]: %reset -f
458 458
459 459 In [2]: alpha = 123
460 460
461 461 In [3]: beta = 'beta'
462 462
463 463 In [4]: %who int
464 464 alpha
465 465
466 466 In [5]: %who str
467 467 beta
468 468
469 469 In [6]: %whos
470 470 Variable Type Data/Info
471 471 ----------------------------
472 472 alpha int 123
473 473 beta str beta
474 474
475 475 In [7]: %who_ls
476 476 Out[7]: ['alpha', 'beta']
477 477 """
478 478
479 479 def test_whos():
480 480 """Check that whos is protected against objects where repr() fails."""
481 481 class A(object):
482 482 def __repr__(self):
483 483 raise Exception()
484 484 _ip.user_ns['a'] = A()
485 485 _ip.magic("whos")
486 486
487 487 @py3compat.u_format
488 488 def doctest_precision():
489 489 """doctest for %precision
490 490
491 491 In [1]: f = get_ipython().display_formatter.formatters['text/plain']
492 492
493 493 In [2]: %precision 5
494 494 Out[2]: {u}'%.5f'
495 495
496 496 In [3]: f.float_format
497 497 Out[3]: {u}'%.5f'
498 498
499 499 In [4]: %precision %e
500 500 Out[4]: {u}'%e'
501 501
502 502 In [5]: f(3.1415927)
503 503 Out[5]: {u}'3.141593e+00'
504 504 """
505 505
506 506 def test_psearch():
507 507 with tt.AssertPrints("dict.fromkeys"):
508 508 _ip.run_cell("dict.fr*?")
509 509
510 510 def test_timeit_shlex():
511 511 """test shlex issues with timeit (#1109)"""
512 512 _ip.ex("def f(*a,**kw): pass")
513 513 _ip.magic('timeit -n1 "this is a bug".count(" ")')
514 514 _ip.magic('timeit -r1 -n1 f(" ", 1)')
515 515 _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")')
516 516 _ip.magic('timeit -r1 -n1 ("a " + "b")')
517 517 _ip.magic('timeit -r1 -n1 f("a " + "b")')
518 518 _ip.magic('timeit -r1 -n1 f("a " + "b ")')
519 519
520 520
521 521 def test_timeit_arguments():
522 522 "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
523 523 _ip.magic("timeit ('#')")
524 524
525 525
526 526 def test_timeit_special_syntax():
527 527 "Test %%timeit with IPython special syntax"
528 528 @register_line_magic
529 529 def lmagic(line):
530 530 ip = get_ipython()
531 531 ip.user_ns['lmagic_out'] = line
532 532
533 533 # line mode test
534 534 _ip.run_line_magic('timeit', '-n1 -r1 %lmagic my line')
535 535 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line')
536 536 # cell mode test
537 537 _ip.run_cell_magic('timeit', '-n1 -r1', '%lmagic my line2')
538 538 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2')
539 539
540 540 def test_timeit_return():
541 541 """
542 542 test wether timeit -o return object
543 543 """
544 544
545 545 res = _ip.run_line_magic('timeit','-n10 -r10 -o 1')
546 546 assert(res is not None)
547 547
548 548 def test_timeit_quiet():
549 549 """
550 550 test quiet option of timeit magic
551 551 """
552 552 with tt.AssertNotPrints("loops"):
553 553 _ip.run_cell("%timeit -n1 -r1 -q 1")
554 554
555 555 @dec.skipif(execution.profile is None)
556 556 def test_prun_special_syntax():
557 557 "Test %%prun with IPython special syntax"
558 558 @register_line_magic
559 559 def lmagic(line):
560 560 ip = get_ipython()
561 561 ip.user_ns['lmagic_out'] = line
562 562
563 563 # line mode test
564 564 _ip.run_line_magic('prun', '-q %lmagic my line')
565 565 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line')
566 566 # cell mode test
567 567 _ip.run_cell_magic('prun', '-q', '%lmagic my line2')
568 568 nt.assert_equal(_ip.user_ns['lmagic_out'], 'my line2')
569 569
570 570 @dec.skipif(execution.profile is None)
571 571 def test_prun_quotes():
572 572 "Test that prun does not clobber string escapes (GH #1302)"
573 573 _ip.magic(r"prun -q x = '\t'")
574 574 nt.assert_equal(_ip.user_ns['x'], '\t')
575 575
576 576 def test_extension():
577 577 tmpdir = TemporaryDirectory()
578 578 orig_ipython_dir = _ip.ipython_dir
579 579 try:
580 580 _ip.ipython_dir = tmpdir.name
581 581 nt.assert_raises(ImportError, _ip.magic, "load_ext daft_extension")
582 582 url = os.path.join(os.path.dirname(__file__), "daft_extension.py")
583 583 _ip.magic("install_ext %s" % url)
584 584 _ip.user_ns.pop('arq', None)
585 585 invalidate_caches() # Clear import caches
586 586 _ip.magic("load_ext daft_extension")
587 587 nt.assert_equal(_ip.user_ns['arq'], 185)
588 588 _ip.magic("unload_ext daft_extension")
589 589 assert 'arq' not in _ip.user_ns
590 590 finally:
591 591 _ip.ipython_dir = orig_ipython_dir
592 592 tmpdir.cleanup()
593 593
594 594 def test_notebook_export_json():
595 595 with TemporaryDirectory() as td:
596 596 outfile = os.path.join(td, "nb.ipynb")
597 597 _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'"))
598 598 _ip.magic("notebook -e %s" % outfile)
599 599
600 600 def test_notebook_export_py():
601 601 with TemporaryDirectory() as td:
602 602 outfile = os.path.join(td, "nb.py")
603 603 _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'"))
604 604 _ip.magic("notebook -e %s" % outfile)
605 605
606 606 def test_notebook_reformat_py():
607 607 with TemporaryDirectory() as td:
608 608 infile = os.path.join(td, "nb.ipynb")
609 609 with io.open(infile, 'w', encoding='utf-8') as f:
610 610 current.write(nb0, f, 'json')
611 611
612 612 _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'"))
613 613 _ip.magic("notebook -f py %s" % infile)
614 614
615 615 def test_notebook_reformat_json():
616 616 with TemporaryDirectory() as td:
617 617 infile = os.path.join(td, "nb.py")
618 618 with io.open(infile, 'w', encoding='utf-8') as f:
619 619 current.write(nb0, f, 'py')
620 620
621 621 _ip.ex(py3compat.u_format(u"u = {u}'hΓ©llo'"))
622 622 _ip.magic("notebook -f ipynb %s" % infile)
623 623 _ip.magic("notebook -f json %s" % infile)
624 624
625 625 def test_env():
626 626 env = _ip.magic("env")
627 627 assert isinstance(env, dict), type(env)
628 628
629 629
630 630 class CellMagicTestCase(TestCase):
631 631
632 632 def check_ident(self, magic):
633 633 # Manually called, we get the result
634 634 out = _ip.run_cell_magic(magic, 'a', 'b')
635 635 nt.assert_equal(out, ('a','b'))
636 636 # Via run_cell, it goes into the user's namespace via displayhook
637 637 _ip.run_cell('%%' + magic +' c\nd')
638 638 nt.assert_equal(_ip.user_ns['_'], ('c','d'))
639 639
640 640 def test_cell_magic_func_deco(self):
641 641 "Cell magic using simple decorator"
642 642 @register_cell_magic
643 643 def cellm(line, cell):
644 644 return line, cell
645 645
646 646 self.check_ident('cellm')
647 647
648 648 def test_cell_magic_reg(self):
649 649 "Cell magic manually registered"
650 650 def cellm(line, cell):
651 651 return line, cell
652 652
653 653 _ip.register_magic_function(cellm, 'cell', 'cellm2')
654 654 self.check_ident('cellm2')
655 655
656 656 def test_cell_magic_class(self):
657 657 "Cell magics declared via a class"
658 658 @magics_class
659 659 class MyMagics(Magics):
660 660
661 661 @cell_magic
662 662 def cellm3(self, line, cell):
663 663 return line, cell
664 664
665 665 _ip.register_magics(MyMagics)
666 666 self.check_ident('cellm3')
667 667
668 668 def test_cell_magic_class2(self):
669 669 "Cell magics declared via a class, #2"
670 670 @magics_class
671 671 class MyMagics2(Magics):
672 672
673 673 @cell_magic('cellm4')
674 674 def cellm33(self, line, cell):
675 675 return line, cell
676 676
677 677 _ip.register_magics(MyMagics2)
678 678 self.check_ident('cellm4')
679 679 # Check that nothing is registered as 'cellm33'
680 680 c33 = _ip.find_cell_magic('cellm33')
681 681 nt.assert_equal(c33, None)
682 682
683 683 def test_file():
684 684 """Basic %%file"""
685 685 ip = get_ipython()
686 686 with TemporaryDirectory() as td:
687 687 fname = os.path.join(td, 'file1')
688 688 ip.run_cell_magic("file", fname, u'\n'.join([
689 689 'line1',
690 690 'line2',
691 691 ]))
692 692 with open(fname) as f:
693 693 s = f.read()
694 694 nt.assert_in('line1\n', s)
695 695 nt.assert_in('line2', s)
696 696
697 697 def test_file_var_expand():
698 698 """%%file $filename"""
699 699 ip = get_ipython()
700 700 with TemporaryDirectory() as td:
701 701 fname = os.path.join(td, 'file1')
702 702 ip.user_ns['filename'] = fname
703 703 ip.run_cell_magic("file", '$filename', u'\n'.join([
704 704 'line1',
705 705 'line2',
706 706 ]))
707 707 with open(fname) as f:
708 708 s = f.read()
709 709 nt.assert_in('line1\n', s)
710 710 nt.assert_in('line2', s)
711 711
712 712 def test_file_unicode():
713 713 """%%file with unicode cell"""
714 714 ip = get_ipython()
715 715 with TemporaryDirectory() as td:
716 716 fname = os.path.join(td, 'file1')
717 717 ip.run_cell_magic("file", fname, u'\n'.join([
718 718 u'linΓ©1',
719 719 u'linΓ©2',
720 720 ]))
721 721 with io.open(fname, encoding='utf-8') as f:
722 722 s = f.read()
723 723 nt.assert_in(u'linΓ©1\n', s)
724 724 nt.assert_in(u'linΓ©2', s)
725 725
726 726 def test_file_amend():
727 727 """%%file -a amends files"""
728 728 ip = get_ipython()
729 729 with TemporaryDirectory() as td:
730 730 fname = os.path.join(td, 'file2')
731 731 ip.run_cell_magic("file", fname, u'\n'.join([
732 732 'line1',
733 733 'line2',
734 734 ]))
735 735 ip.run_cell_magic("file", "-a %s" % fname, u'\n'.join([
736 736 'line3',
737 737 'line4',
738 738 ]))
739 739 with open(fname) as f:
740 740 s = f.read()
741 741 nt.assert_in('line1\n', s)
742 742 nt.assert_in('line3\n', s)
743 743
744 744
745 745 def test_script_config():
746 746 ip = get_ipython()
747 747 ip.config.ScriptMagics.script_magics = ['whoda']
748 748 sm = script.ScriptMagics(shell=ip)
749 749 nt.assert_in('whoda', sm.magics['cell'])
750 750
751 751 @dec.skip_win32
752 752 def test_script_out():
753 753 ip = get_ipython()
754 754 ip.run_cell_magic("script", "--out output sh", "echo 'hi'")
755 755 nt.assert_equal(ip.user_ns['output'], 'hi\n')
756 756
757 757 @dec.skip_win32
758 758 def test_script_err():
759 759 ip = get_ipython()
760 760 ip.run_cell_magic("script", "--err error sh", "echo 'hello' >&2")
761 761 nt.assert_equal(ip.user_ns['error'], 'hello\n')
762 762
763 763 @dec.skip_win32
764 764 def test_script_out_err():
765 765 ip = get_ipython()
766 766 ip.run_cell_magic("script", "--out output --err error sh", "echo 'hi'\necho 'hello' >&2")
767 767 nt.assert_equal(ip.user_ns['output'], 'hi\n')
768 768 nt.assert_equal(ip.user_ns['error'], 'hello\n')
769 769
770 770 @dec.skip_win32
771 771 def test_script_bg_out():
772 772 ip = get_ipython()
773 773 ip.run_cell_magic("script", "--bg --out output sh", "echo 'hi'")
774 774 nt.assert_equal(ip.user_ns['output'].read(), b'hi\n')
775 775
776 776 @dec.skip_win32
777 777 def test_script_bg_err():
778 778 ip = get_ipython()
779 779 ip.run_cell_magic("script", "--bg --err error sh", "echo 'hello' >&2")
780 780 nt.assert_equal(ip.user_ns['error'].read(), b'hello\n')
781 781
782 782 @dec.skip_win32
783 783 def test_script_bg_out_err():
784 784 ip = get_ipython()
785 785 ip.run_cell_magic("script", "--bg --out output --err error sh", "echo 'hi'\necho 'hello' >&2")
786 786 nt.assert_equal(ip.user_ns['output'].read(), b'hi\n')
787 787 nt.assert_equal(ip.user_ns['error'].read(), b'hello\n')
788 788
789 789 def test_script_defaults():
790 790 ip = get_ipython()
791 791 for cmd in ['sh', 'bash', 'perl', 'ruby']:
792 792 try:
793 793 find_cmd(cmd)
794 794 except Exception:
795 795 pass
796 796 else:
797 797 nt.assert_in(cmd, ip.magics_manager.magics['cell'])
798 798
799 799
800 800 @magics_class
801 801 class FooFoo(Magics):
802 802 """class with both %foo and %%foo magics"""
803 803 @line_magic('foo')
804 804 def line_foo(self, line):
805 805 "I am line foo"
806 806 pass
807 807
808 808 @cell_magic("foo")
809 809 def cell_foo(self, line, cell):
810 810 "I am cell foo, not line foo"
811 811 pass
812 812
813 813 def test_line_cell_info():
814 814 """%%foo and %foo magics are distinguishable to inspect"""
815 815 ip = get_ipython()
816 816 ip.magics_manager.register(FooFoo)
817 817 oinfo = ip.object_inspect('foo')
818 818 nt.assert_true(oinfo['found'])
819 819 nt.assert_true(oinfo['ismagic'])
820 820
821 821 oinfo = ip.object_inspect('%%foo')
822 822 nt.assert_true(oinfo['found'])
823 823 nt.assert_true(oinfo['ismagic'])
824 824 nt.assert_equal(oinfo['docstring'], FooFoo.cell_foo.__doc__)
825 825
826 826 oinfo = ip.object_inspect('%foo')
827 827 nt.assert_true(oinfo['found'])
828 828 nt.assert_true(oinfo['ismagic'])
829 829 nt.assert_equal(oinfo['docstring'], FooFoo.line_foo.__doc__)
830 830
831 831 def test_multiple_magics():
832 832 ip = get_ipython()
833 833 foo1 = FooFoo(ip)
834 834 foo2 = FooFoo(ip)
835 835 mm = ip.magics_manager
836 836 mm.register(foo1)
837 837 nt.assert_true(mm.magics['line']['foo'].__self__ is foo1)
838 838 mm.register(foo2)
839 839 nt.assert_true(mm.magics['line']['foo'].__self__ is foo2)
840 840
841 841 def test_alias_magic():
842 842 """Test %alias_magic."""
843 843 ip = get_ipython()
844 844 mm = ip.magics_manager
845 845
846 846 # Basic operation: both cell and line magics are created, if possible.
847 847 ip.run_line_magic('alias_magic', 'timeit_alias timeit')
848 848 nt.assert_in('timeit_alias', mm.magics['line'])
849 849 nt.assert_in('timeit_alias', mm.magics['cell'])
850 850
851 851 # --cell is specified, line magic not created.
852 852 ip.run_line_magic('alias_magic', '--cell timeit_cell_alias timeit')
853 853 nt.assert_not_in('timeit_cell_alias', mm.magics['line'])
854 854 nt.assert_in('timeit_cell_alias', mm.magics['cell'])
855 855
856 856 # Test that line alias is created successfully.
857 857 ip.run_line_magic('alias_magic', '--line env_alias env')
858 858 nt.assert_equal(ip.run_line_magic('env', ''),
859 859 ip.run_line_magic('env_alias', ''))
860 860
861 861 def test_save():
862 862 """Test %save."""
863 863 ip = get_ipython()
864 864 ip.history_manager.reset() # Clear any existing history.
865 865 cmds = [u"a=1", u"def b():\n return a**2", u"print(a, b())"]
866 866 for i, cmd in enumerate(cmds, start=1):
867 867 ip.history_manager.store_inputs(i, cmd)
868 868 with TemporaryDirectory() as tmpdir:
869 869 file = os.path.join(tmpdir, "testsave.py")
870 870 ip.run_line_magic("save", "%s 1-10" % file)
871 871 with open(file) as f:
872 872 content = f.read()
873 873 nt.assert_equal(content.count(cmds[0]), 1)
874 874 nt.assert_in('coding: utf-8', content)
875 875 ip.run_line_magic("save", "-a %s 1-10" % file)
876 876 with open(file) as f:
877 877 content = f.read()
878 878 nt.assert_equal(content.count(cmds[0]), 2)
879 879 nt.assert_in('coding: utf-8', content)
880 880
881 881
882 882 def test_store():
883 883 """Test %store."""
884 884 ip = get_ipython()
885 885 ip.run_line_magic('load_ext', 'storemagic')
886 886
887 887 # make sure the storage is empty
888 888 ip.run_line_magic('store', '-z')
889 889 ip.user_ns['var'] = 42
890 890 ip.run_line_magic('store', 'var')
891 891 ip.user_ns['var'] = 39
892 892 ip.run_line_magic('store', '-r')
893 893 nt.assert_equal(ip.user_ns['var'], 42)
894 894
895 895 ip.run_line_magic('store', '-d var')
896 896 ip.user_ns['var'] = 39
897 897 ip.run_line_magic('store' , '-r')
898 898 nt.assert_equal(ip.user_ns['var'], 39)
899 899
900 900
901 901 def _run_edit_test(arg_s, exp_filename=None,
902 902 exp_lineno=-1,
903 903 exp_contents=None,
904 904 exp_is_temp=None):
905 905 ip = get_ipython()
906 906 M = code.CodeMagics(ip)
907 907 last_call = ['','']
908 908 opts,args = M.parse_options(arg_s,'prxn:')
909 909 filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call)
910 910
911 911 if exp_filename is not None:
912 912 nt.assert_equal(exp_filename, filename)
913 913 if exp_contents is not None:
914 914 with io.open(filename, 'r') as f:
915 915 contents = f.read()
916 916 nt.assert_equal(exp_contents, contents)
917 917 if exp_lineno != -1:
918 918 nt.assert_equal(exp_lineno, lineno)
919 919 if exp_is_temp is not None:
920 920 nt.assert_equal(exp_is_temp, is_temp)
921 921
922 922
923 923 def test_edit_interactive():
924 924 """%edit on interactively defined objects"""
925 925 ip = get_ipython()
926 926 n = ip.execution_count
927 927 ip.run_cell(u"def foo(): return 1", store_history=True)
928 928
929 929 try:
930 930 _run_edit_test("foo")
931 931 except code.InteractivelyDefined as e:
932 932 nt.assert_equal(e.index, n)
933 933 else:
934 934 raise AssertionError("Should have raised InteractivelyDefined")
935 935
936 936
937 937 def test_edit_cell():
938 938 """%edit [cell id]"""
939 939 ip = get_ipython()
940 940
941 941 ip.run_cell(u"def foo(): return 1", store_history=True)
942 942
943 943 # test
944 944 _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True)
@@ -1,111 +1,112 b''
1 1 # -*- coding: utf-8
2 2 """Tests for prompt generation."""
3 3
4 4 import unittest
5 5
6 6 import os
7 7
8 8 from IPython.testing import tools as tt, decorators as dec
9 9 from IPython.core.prompts import PromptManager, LazyEvaluate
10 10 from IPython.testing.globalipapp import get_ipython
11 11 from IPython.utils.tempdir import TemporaryDirectory
12 from IPython.utils import py3compat
12 13 from IPython.utils.py3compat import unicode_type
13 14
14 15 ip = get_ipython()
15 16
16 17
17 18 class PromptTests(unittest.TestCase):
18 19 def setUp(self):
19 20 self.pm = PromptManager(shell=ip, config=ip.config)
20 21
21 22 def test_multiline_prompt(self):
22 23 self.pm.in_template = "[In]\n>>>"
23 24 self.pm.render('in')
24 25 self.assertEqual(self.pm.width, 3)
25 26 self.assertEqual(self.pm.txtwidth, 3)
26 27
27 28 self.pm.in_template = '[In]\n'
28 29 self.pm.render('in')
29 30 self.assertEqual(self.pm.width, 0)
30 31 self.assertEqual(self.pm.txtwidth, 0)
31 32
32 33 def test_translate_abbreviations(self):
33 34 def do_translate(template):
34 35 self.pm.in_template = template
35 36 return self.pm.templates['in']
36 37
37 38 pairs = [(r'%n>', '{color.number}{count}{color.prompt}>'),
38 39 (r'\T', '{time}'),
39 40 (r'\n', '\n')
40 41 ]
41 42
42 43 tt.check_pairs(do_translate, pairs)
43 44
44 45 def test_user_ns(self):
45 46 self.pm.color_scheme = 'NoColor'
46 47 ip.ex("foo='bar'")
47 48 self.pm.in_template = "In [{foo}]"
48 49 prompt = self.pm.render('in')
49 50 self.assertEqual(prompt, u'In [bar]')
50 51
51 52 def test_builtins(self):
52 53 self.pm.color_scheme = 'NoColor'
53 54 self.pm.in_template = "In [{int}]"
54 55 prompt = self.pm.render('in')
55 56 self.assertEqual(prompt, u"In [%r]" % int)
56 57
57 58 def test_undefined(self):
58 59 self.pm.color_scheme = 'NoColor'
59 60 self.pm.in_template = "In [{foo_dne}]"
60 61 prompt = self.pm.render('in')
61 62 self.assertEqual(prompt, u"In [<ERROR: 'foo_dne' not found>]")
62 63
63 64 def test_render(self):
64 65 self.pm.in_template = r'\#>'
65 66 self.assertEqual(self.pm.render('in',color=False), '%d>' % ip.execution_count)
66 67
67 68 @dec.onlyif_unicode_paths
68 69 def test_render_unicode_cwd(self):
69 save = os.getcwdu()
70 save = py3compat.getcwd()
70 71 with TemporaryDirectory(u'ΓΌnicΓΈdΓ©') as td:
71 72 os.chdir(td)
72 73 self.pm.in_template = r'\w [\#]'
73 74 p = self.pm.render('in', color=False)
74 self.assertEqual(p, u"%s [%i]" % (os.getcwdu(), ip.execution_count))
75 self.assertEqual(p, u"%s [%i]" % (py3compat.getcwd(), ip.execution_count))
75 76 os.chdir(save)
76 77
77 78 def test_lazy_eval_unicode(self):
78 79 u = u'ΓΌnicΓΈdΓ©'
79 80 lz = LazyEvaluate(lambda : u)
80 81 # str(lz) would fail
81 82 self.assertEqual(unicode_type(lz), u)
82 83 self.assertEqual(format(lz), u)
83 84
84 85 def test_lazy_eval_nonascii_bytes(self):
85 86 u = u'ΓΌnicΓΈdΓ©'
86 87 b = u.encode('utf8')
87 88 lz = LazyEvaluate(lambda : b)
88 89 # unicode(lz) would fail
89 90 self.assertEqual(str(lz), str(b))
90 91 self.assertEqual(format(lz), str(b))
91 92
92 93 def test_lazy_eval_float(self):
93 94 f = 0.503
94 95 lz = LazyEvaluate(lambda : f)
95 96
96 97 self.assertEqual(str(lz), str(f))
97 98 self.assertEqual(unicode_type(lz), unicode_type(f))
98 99 self.assertEqual(format(lz), str(f))
99 100 self.assertEqual(format(lz, '.1'), '0.5')
100 101
101 102 @dec.skip_win32
102 103 def test_cwd_x(self):
103 104 self.pm.in_template = r"\X0"
104 save = os.getcwdu()
105 save = py3compat.getcwd()
105 106 os.chdir(os.path.expanduser('~'))
106 107 p = self.pm.render('in', color=False)
107 108 try:
108 109 self.assertEqual(p, '~')
109 110 finally:
110 111 os.chdir(save)
111 112
@@ -1,458 +1,458 b''
1 1 # encoding: utf-8
2 2 """Tests for code execution (%run and related), which is particularly tricky.
3 3
4 4 Because of how %run manages namespaces, and the fact that we are trying here to
5 5 verify subtle object deletion and reference counting issues, the %run tests
6 6 will be kept in this separate file. This makes it easier to aggregate in one
7 7 place the tricks needed to handle it; most other magics are much easier to test
8 8 and we do so in a common test_magic file.
9 9 """
10 10 from __future__ import absolute_import
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15
16 16 import functools
17 17 import os
18 18 from os.path import join as pjoin
19 19 import random
20 20 import sys
21 21 import tempfile
22 22 import textwrap
23 23 import unittest
24 24
25 25 import nose.tools as nt
26 26 from nose import SkipTest
27 27
28 28 from IPython.testing import decorators as dec
29 29 from IPython.testing import tools as tt
30 30 from IPython.utils import py3compat
31 31 from IPython.utils.tempdir import TemporaryDirectory
32 32 from IPython.core import debugger
33 33
34 34 #-----------------------------------------------------------------------------
35 35 # Test functions begin
36 36 #-----------------------------------------------------------------------------
37 37
38 38 def doctest_refbug():
39 39 """Very nasty problem with references held by multiple runs of a script.
40 40 See: https://github.com/ipython/ipython/issues/141
41 41
42 42 In [1]: _ip.clear_main_mod_cache()
43 43 # random
44 44
45 45 In [2]: %run refbug
46 46
47 47 In [3]: call_f()
48 48 lowercased: hello
49 49
50 50 In [4]: %run refbug
51 51
52 52 In [5]: call_f()
53 53 lowercased: hello
54 54 lowercased: hello
55 55 """
56 56
57 57
58 58 def doctest_run_builtins():
59 59 r"""Check that %run doesn't damage __builtins__.
60 60
61 61 In [1]: import tempfile
62 62
63 63 In [2]: bid1 = id(__builtins__)
64 64
65 65 In [3]: fname = tempfile.mkstemp('.py')[1]
66 66
67 67 In [3]: f = open(fname,'w')
68 68
69 69 In [4]: dummy= f.write('pass\n')
70 70
71 71 In [5]: f.flush()
72 72
73 73 In [6]: t1 = type(__builtins__)
74 74
75 75 In [7]: %run $fname
76 76
77 77 In [7]: f.close()
78 78
79 79 In [8]: bid2 = id(__builtins__)
80 80
81 81 In [9]: t2 = type(__builtins__)
82 82
83 83 In [10]: t1 == t2
84 84 Out[10]: True
85 85
86 86 In [10]: bid1 == bid2
87 87 Out[10]: True
88 88
89 89 In [12]: try:
90 90 ....: os.unlink(fname)
91 91 ....: except:
92 92 ....: pass
93 93 ....:
94 94 """
95 95
96 96
97 97 def doctest_run_option_parser():
98 98 r"""Test option parser in %run.
99 99
100 100 In [1]: %run print_argv.py
101 101 []
102 102
103 103 In [2]: %run print_argv.py print*.py
104 104 ['print_argv.py']
105 105
106 106 In [3]: %run -G print_argv.py print*.py
107 107 ['print*.py']
108 108
109 109 """
110 110
111 111
112 112 @dec.skip_win32
113 113 def doctest_run_option_parser_for_posix():
114 114 r"""Test option parser in %run (Linux/OSX specific).
115 115
116 116 You need double quote to escape glob in POSIX systems:
117 117
118 118 In [1]: %run print_argv.py print\\*.py
119 119 ['print*.py']
120 120
121 121 You can't use quote to escape glob in POSIX systems:
122 122
123 123 In [2]: %run print_argv.py 'print*.py'
124 124 ['print_argv.py']
125 125
126 126 """
127 127
128 128
129 129 @dec.skip_if_not_win32
130 130 def doctest_run_option_parser_for_windows():
131 131 r"""Test option parser in %run (Windows specific).
132 132
133 133 In Windows, you can't escape ``*` `by backslash:
134 134
135 135 In [1]: %run print_argv.py print\\*.py
136 136 ['print\\*.py']
137 137
138 138 You can use quote to escape glob:
139 139
140 140 In [2]: %run print_argv.py 'print*.py'
141 141 ['print*.py']
142 142
143 143 """
144 144
145 145
146 146 @py3compat.doctest_refactor_print
147 147 def doctest_reset_del():
148 148 """Test that resetting doesn't cause errors in __del__ methods.
149 149
150 150 In [2]: class A(object):
151 151 ...: def __del__(self):
152 152 ...: print str("Hi")
153 153 ...:
154 154
155 155 In [3]: a = A()
156 156
157 157 In [4]: get_ipython().reset()
158 158 Hi
159 159
160 160 In [5]: 1+1
161 161 Out[5]: 2
162 162 """
163 163
164 164 # For some tests, it will be handy to organize them in a class with a common
165 165 # setup that makes a temp file
166 166
167 167 class TestMagicRunPass(tt.TempFileMixin):
168 168
169 169 def setup(self):
170 170 """Make a valid python temp file."""
171 171 self.mktmp('pass\n')
172 172
173 173 def run_tmpfile(self):
174 174 _ip = get_ipython()
175 175 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
176 176 # See below and ticket https://bugs.launchpad.net/bugs/366353
177 177 _ip.magic('run %s' % self.fname)
178 178
179 179 def run_tmpfile_p(self):
180 180 _ip = get_ipython()
181 181 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
182 182 # See below and ticket https://bugs.launchpad.net/bugs/366353
183 183 _ip.magic('run -p %s' % self.fname)
184 184
185 185 def test_builtins_id(self):
186 186 """Check that %run doesn't damage __builtins__ """
187 187 _ip = get_ipython()
188 188 # Test that the id of __builtins__ is not modified by %run
189 189 bid1 = id(_ip.user_ns['__builtins__'])
190 190 self.run_tmpfile()
191 191 bid2 = id(_ip.user_ns['__builtins__'])
192 192 nt.assert_equal(bid1, bid2)
193 193
194 194 def test_builtins_type(self):
195 195 """Check that the type of __builtins__ doesn't change with %run.
196 196
197 197 However, the above could pass if __builtins__ was already modified to
198 198 be a dict (it should be a module) by a previous use of %run. So we
199 199 also check explicitly that it really is a module:
200 200 """
201 201 _ip = get_ipython()
202 202 self.run_tmpfile()
203 203 nt.assert_equal(type(_ip.user_ns['__builtins__']),type(sys))
204 204
205 205 def test_prompts(self):
206 206 """Test that prompts correctly generate after %run"""
207 207 self.run_tmpfile()
208 208 _ip = get_ipython()
209 209 p2 = _ip.prompt_manager.render('in2').strip()
210 210 nt.assert_equal(p2[:3], '...')
211 211
212 212 def test_run_profile( self ):
213 213 """Test that the option -p, which invokes the profiler, do not
214 214 crash by invoking execfile"""
215 215 _ip = get_ipython()
216 216 self.run_tmpfile_p()
217 217
218 218
219 219 class TestMagicRunSimple(tt.TempFileMixin):
220 220
221 221 def test_simpledef(self):
222 222 """Test that simple class definitions work."""
223 223 src = ("class foo: pass\n"
224 224 "def f(): return foo()")
225 225 self.mktmp(src)
226 226 _ip.magic('run %s' % self.fname)
227 227 _ip.run_cell('t = isinstance(f(), foo)')
228 228 nt.assert_true(_ip.user_ns['t'])
229 229
230 230 def test_obj_del(self):
231 231 """Test that object's __del__ methods are called on exit."""
232 232 if sys.platform == 'win32':
233 233 try:
234 234 import win32api
235 235 except ImportError:
236 236 raise SkipTest("Test requires pywin32")
237 237 src = ("class A(object):\n"
238 238 " def __del__(self):\n"
239 239 " print 'object A deleted'\n"
240 240 "a = A()\n")
241 241 self.mktmp(py3compat.doctest_refactor_print(src))
242 242 if dec.module_not_available('sqlite3'):
243 243 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
244 244 else:
245 245 err = None
246 246 tt.ipexec_validate(self.fname, 'object A deleted', err)
247 247
248 248 def test_aggressive_namespace_cleanup(self):
249 249 """Test that namespace cleanup is not too aggressive GH-238
250 250
251 251 Returning from another run magic deletes the namespace"""
252 252 # see ticket https://github.com/ipython/ipython/issues/238
253 253 class secondtmp(tt.TempFileMixin): pass
254 254 empty = secondtmp()
255 255 empty.mktmp('')
256 256 # On Windows, the filename will have \users in it, so we need to use the
257 257 # repr so that the \u becomes \\u.
258 258 src = ("ip = get_ipython()\n"
259 259 "for i in range(5):\n"
260 260 " try:\n"
261 261 " ip.magic(%r)\n"
262 262 " except NameError as e:\n"
263 263 " print(i)\n"
264 264 " break\n" % ('run ' + empty.fname))
265 265 self.mktmp(src)
266 266 _ip.magic('run %s' % self.fname)
267 267 _ip.run_cell('ip == get_ipython()')
268 268 nt.assert_equal(_ip.user_ns['i'], 4)
269 269
270 270 def test_run_second(self):
271 271 """Test that running a second file doesn't clobber the first, gh-3547
272 272 """
273 273 self.mktmp("avar = 1\n"
274 274 "def afunc():\n"
275 275 " return avar\n")
276 276
277 277 empty = tt.TempFileMixin()
278 278 empty.mktmp("")
279 279
280 280 _ip.magic('run %s' % self.fname)
281 281 _ip.magic('run %s' % empty.fname)
282 282 nt.assert_equal(_ip.user_ns['afunc'](), 1)
283 283
284 284 @dec.skip_win32
285 285 def test_tclass(self):
286 286 mydir = os.path.dirname(__file__)
287 287 tc = os.path.join(mydir, 'tclass')
288 288 src = ("%%run '%s' C-first\n"
289 289 "%%run '%s' C-second\n"
290 290 "%%run '%s' C-third\n") % (tc, tc, tc)
291 291 self.mktmp(src, '.ipy')
292 292 out = """\
293 293 ARGV 1-: ['C-first']
294 294 ARGV 1-: ['C-second']
295 295 tclass.py: deleting object: C-first
296 296 ARGV 1-: ['C-third']
297 297 tclass.py: deleting object: C-second
298 298 tclass.py: deleting object: C-third
299 299 """
300 300 if dec.module_not_available('sqlite3'):
301 301 err = 'WARNING: IPython History requires SQLite, your history will not be saved\n'
302 302 else:
303 303 err = None
304 304 tt.ipexec_validate(self.fname, out, err)
305 305
306 306 def test_run_i_after_reset(self):
307 307 """Check that %run -i still works after %reset (gh-693)"""
308 308 src = "yy = zz\n"
309 309 self.mktmp(src)
310 310 _ip.run_cell("zz = 23")
311 311 _ip.magic('run -i %s' % self.fname)
312 312 nt.assert_equal(_ip.user_ns['yy'], 23)
313 313 _ip.magic('reset -f')
314 314 _ip.run_cell("zz = 23")
315 315 _ip.magic('run -i %s' % self.fname)
316 316 nt.assert_equal(_ip.user_ns['yy'], 23)
317 317
318 318 def test_unicode(self):
319 319 """Check that files in odd encodings are accepted."""
320 320 mydir = os.path.dirname(__file__)
321 321 na = os.path.join(mydir, 'nonascii.py')
322 322 _ip.magic('run "%s"' % na)
323 323 nt.assert_equal(_ip.user_ns['u'], u'ΠŽΡ‚β„–Π€')
324 324
325 325 def test_run_py_file_attribute(self):
326 326 """Test handling of `__file__` attribute in `%run <file>.py`."""
327 327 src = "t = __file__\n"
328 328 self.mktmp(src)
329 329 _missing = object()
330 330 file1 = _ip.user_ns.get('__file__', _missing)
331 331 _ip.magic('run %s' % self.fname)
332 332 file2 = _ip.user_ns.get('__file__', _missing)
333 333
334 334 # Check that __file__ was equal to the filename in the script's
335 335 # namespace.
336 336 nt.assert_equal(_ip.user_ns['t'], self.fname)
337 337
338 338 # Check that __file__ was not leaked back into user_ns.
339 339 nt.assert_equal(file1, file2)
340 340
341 341 def test_run_ipy_file_attribute(self):
342 342 """Test handling of `__file__` attribute in `%run <file.ipy>`."""
343 343 src = "t = __file__\n"
344 344 self.mktmp(src, ext='.ipy')
345 345 _missing = object()
346 346 file1 = _ip.user_ns.get('__file__', _missing)
347 347 _ip.magic('run %s' % self.fname)
348 348 file2 = _ip.user_ns.get('__file__', _missing)
349 349
350 350 # Check that __file__ was equal to the filename in the script's
351 351 # namespace.
352 352 nt.assert_equal(_ip.user_ns['t'], self.fname)
353 353
354 354 # Check that __file__ was not leaked back into user_ns.
355 355 nt.assert_equal(file1, file2)
356 356
357 357 def test_run_formatting(self):
358 358 """ Test that %run -t -N<N> does not raise a TypeError for N > 1."""
359 359 src = "pass"
360 360 self.mktmp(src)
361 361 _ip.magic('run -t -N 1 %s' % self.fname)
362 362 _ip.magic('run -t -N 10 %s' % self.fname)
363 363
364 364 def test_ignore_sys_exit(self):
365 365 """Test the -e option to ignore sys.exit()"""
366 366 src = "import sys; sys.exit(1)"
367 367 self.mktmp(src)
368 368 with tt.AssertPrints('SystemExit'):
369 369 _ip.magic('run %s' % self.fname)
370 370
371 371 with tt.AssertNotPrints('SystemExit'):
372 372 _ip.magic('run -e %s' % self.fname)
373 373
374 374
375 375
376 376 class TestMagicRunWithPackage(unittest.TestCase):
377 377
378 378 def writefile(self, name, content):
379 379 path = os.path.join(self.tempdir.name, name)
380 380 d = os.path.dirname(path)
381 381 if not os.path.isdir(d):
382 382 os.makedirs(d)
383 383 with open(path, 'w') as f:
384 384 f.write(textwrap.dedent(content))
385 385
386 386 def setUp(self):
387 387 self.package = package = 'tmp{0}'.format(repr(random.random())[2:])
388 388 """Temporary valid python package name."""
389 389
390 390 self.value = int(random.random() * 10000)
391 391
392 392 self.tempdir = TemporaryDirectory()
393 self.__orig_cwd = os.getcwdu()
393 self.__orig_cwd = py3compat.getcwd()
394 394 sys.path.insert(0, self.tempdir.name)
395 395
396 396 self.writefile(os.path.join(package, '__init__.py'), '')
397 397 self.writefile(os.path.join(package, 'sub.py'), """
398 398 x = {0!r}
399 399 """.format(self.value))
400 400 self.writefile(os.path.join(package, 'relative.py'), """
401 401 from .sub import x
402 402 """)
403 403 self.writefile(os.path.join(package, 'absolute.py'), """
404 404 from {0}.sub import x
405 405 """.format(package))
406 406
407 407 def tearDown(self):
408 408 os.chdir(self.__orig_cwd)
409 409 sys.path[:] = [p for p in sys.path if p != self.tempdir.name]
410 410 self.tempdir.cleanup()
411 411
412 412 def check_run_submodule(self, submodule, opts=''):
413 413 _ip.user_ns.pop('x', None)
414 414 _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts))
415 415 self.assertEqual(_ip.user_ns['x'], self.value,
416 416 'Variable `x` is not loaded from module `{0}`.'
417 417 .format(submodule))
418 418
419 419 def test_run_submodule_with_absolute_import(self):
420 420 self.check_run_submodule('absolute')
421 421
422 422 def test_run_submodule_with_relative_import(self):
423 423 """Run submodule that has a relative import statement (#2727)."""
424 424 self.check_run_submodule('relative')
425 425
426 426 def test_prun_submodule_with_absolute_import(self):
427 427 self.check_run_submodule('absolute', '-p')
428 428
429 429 def test_prun_submodule_with_relative_import(self):
430 430 self.check_run_submodule('relative', '-p')
431 431
432 432 def with_fake_debugger(func):
433 433 @functools.wraps(func)
434 434 def wrapper(*args, **kwds):
435 435 with tt.monkeypatch(debugger.Pdb, 'run', staticmethod(eval)):
436 436 return func(*args, **kwds)
437 437 return wrapper
438 438
439 439 @with_fake_debugger
440 440 def test_debug_run_submodule_with_absolute_import(self):
441 441 self.check_run_submodule('absolute', '-d')
442 442
443 443 @with_fake_debugger
444 444 def test_debug_run_submodule_with_relative_import(self):
445 445 self.check_run_submodule('relative', '-d')
446 446
447 447 def test_run__name__():
448 448 with TemporaryDirectory() as td:
449 449 path = pjoin(td, 'foo.py')
450 450 with open(path, 'w') as f:
451 451 f.write("q = __name__")
452 452
453 453 _ip.user_ns.pop('q', None)
454 454 _ip.magic('run {}'.format(path))
455 455 nt.assert_equal(_ip.user_ns.pop('q'), '__main__')
456 456
457 457 _ip.magic('run -n {}'.format(path))
458 458 nt.assert_equal(_ip.user_ns.pop('q'), 'foo')
@@ -1,172 +1,171 b''
1 1 """Manage IPython.parallel clusters in the notebook.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 import os
20
21 19 from tornado import web
22 20 from zmq.eventloop import ioloop
23 21
24 22 from IPython.config.configurable import LoggingConfigurable
25 23 from IPython.utils.traitlets import Dict, Instance, CFloat
26 24 from IPython.parallel.apps.ipclusterapp import IPClusterStart
27 25 from IPython.core.profileapp import list_profiles_in
28 26 from IPython.core.profiledir import ProfileDir
27 from IPython.utils import py3compat
29 28 from IPython.utils.path import get_ipython_dir
30 29
31 30
32 31 #-----------------------------------------------------------------------------
33 32 # Classes
34 33 #-----------------------------------------------------------------------------
35 34
36 35
37 36 class DummyIPClusterStart(IPClusterStart):
38 37 """Dummy subclass to skip init steps that conflict with global app.
39 38
40 39 Instantiating and initializing this class should result in fully configured
41 40 launchers, but no other side effects or state.
42 41 """
43 42
44 43 def init_signal(self):
45 44 pass
46 45 def reinit_logging(self):
47 46 pass
48 47
49 48
50 49 class ClusterManager(LoggingConfigurable):
51 50
52 51 profiles = Dict()
53 52
54 53 delay = CFloat(1., config=True,
55 54 help="delay (in s) between starting the controller and the engines")
56 55
57 56 loop = Instance('zmq.eventloop.ioloop.IOLoop')
58 57 def _loop_default(self):
59 58 from zmq.eventloop.ioloop import IOLoop
60 59 return IOLoop.instance()
61 60
62 61 def build_launchers(self, profile_dir):
63 62 starter = DummyIPClusterStart(log=self.log)
64 63 starter.initialize(['--profile-dir', profile_dir])
65 64 cl = starter.controller_launcher
66 65 esl = starter.engine_launcher
67 66 n = starter.n
68 67 return cl, esl, n
69 68
70 69 def get_profile_dir(self, name, path):
71 70 p = ProfileDir.find_profile_dir_by_name(path,name=name)
72 71 return p.location
73 72
74 73 def update_profiles(self):
75 74 """List all profiles in the ipython_dir and cwd.
76 75 """
77 for path in [get_ipython_dir(), os.getcwdu()]:
76 for path in [get_ipython_dir(), py3compat.getcwd()]:
78 77 for profile in list_profiles_in(path):
79 78 pd = self.get_profile_dir(profile, path)
80 79 if profile not in self.profiles:
81 80 self.log.debug("Adding cluster profile '%s'" % profile)
82 81 self.profiles[profile] = {
83 82 'profile': profile,
84 83 'profile_dir': pd,
85 84 'status': 'stopped'
86 85 }
87 86
88 87 def list_profiles(self):
89 88 self.update_profiles()
90 89 # sorted list, but ensure that 'default' always comes first
91 90 default_first = lambda name: name if name != 'default' else ''
92 91 result = [self.profile_info(p) for p in sorted(self.profiles, key=default_first)]
93 92 return result
94 93
95 94 def check_profile(self, profile):
96 95 if profile not in self.profiles:
97 96 raise web.HTTPError(404, u'profile not found')
98 97
99 98 def profile_info(self, profile):
100 99 self.check_profile(profile)
101 100 result = {}
102 101 data = self.profiles.get(profile)
103 102 result['profile'] = profile
104 103 result['profile_dir'] = data['profile_dir']
105 104 result['status'] = data['status']
106 105 if 'n' in data:
107 106 result['n'] = data['n']
108 107 return result
109 108
110 109 def start_cluster(self, profile, n=None):
111 110 """Start a cluster for a given profile."""
112 111 self.check_profile(profile)
113 112 data = self.profiles[profile]
114 113 if data['status'] == 'running':
115 114 raise web.HTTPError(409, u'cluster already running')
116 115 cl, esl, default_n = self.build_launchers(data['profile_dir'])
117 116 n = n if n is not None else default_n
118 117 def clean_data():
119 118 data.pop('controller_launcher',None)
120 119 data.pop('engine_set_launcher',None)
121 120 data.pop('n',None)
122 121 data['status'] = 'stopped'
123 122 def engines_stopped(r):
124 123 self.log.debug('Engines stopped')
125 124 if cl.running:
126 125 cl.stop()
127 126 clean_data()
128 127 esl.on_stop(engines_stopped)
129 128 def controller_stopped(r):
130 129 self.log.debug('Controller stopped')
131 130 if esl.running:
132 131 esl.stop()
133 132 clean_data()
134 133 cl.on_stop(controller_stopped)
135 134
136 135 dc = ioloop.DelayedCallback(lambda: cl.start(), 0, self.loop)
137 136 dc.start()
138 137 dc = ioloop.DelayedCallback(lambda: esl.start(n), 1000*self.delay, self.loop)
139 138 dc.start()
140 139
141 140 self.log.debug('Cluster started')
142 141 data['controller_launcher'] = cl
143 142 data['engine_set_launcher'] = esl
144 143 data['n'] = n
145 144 data['status'] = 'running'
146 145 return self.profile_info(profile)
147 146
148 147 def stop_cluster(self, profile):
149 148 """Stop a cluster for a given profile."""
150 149 self.check_profile(profile)
151 150 data = self.profiles[profile]
152 151 if data['status'] == 'stopped':
153 152 raise web.HTTPError(409, u'cluster not running')
154 153 data = self.profiles[profile]
155 154 cl = data['controller_launcher']
156 155 esl = data['engine_set_launcher']
157 156 if cl.running:
158 157 cl.stop()
159 158 if esl.running:
160 159 esl.stop()
161 160 # Return a temp info dict, the real one is updated in the on_stop
162 161 # logic above.
163 162 result = {
164 163 'profile': data['profile'],
165 164 'profile_dir': data['profile_dir'],
166 165 'status': 'stopped'
167 166 }
168 167 return result
169 168
170 169 def stop_all_clusters(self):
171 170 for p in self.profiles.keys():
172 171 self.stop_cluster(p)
@@ -1,173 +1,174 b''
1 1 """A base class notebook manager.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 * Zach Sailer
7 7 """
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2011 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-----------------------------------------------------------------------------
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 19
20 20 import os
21 21
22 22 from IPython.config.configurable import LoggingConfigurable
23 23 from IPython.nbformat import current
24 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
24 from IPython.utils import py3compat
25 from IPython.utils.traitlets import Unicode, TraitError
25 26
26 27 #-----------------------------------------------------------------------------
27 28 # Classes
28 29 #-----------------------------------------------------------------------------
29 30
30 31 class NotebookManager(LoggingConfigurable):
31 32
32 33 # Todo:
33 34 # The notebook_dir attribute is used to mean a couple of different things:
34 35 # 1. Where the notebooks are stored if FileNotebookManager is used.
35 36 # 2. The cwd of the kernel for a project.
36 37 # Right now we use this attribute in a number of different places and
37 38 # we are going to have to disentangle all of this.
38 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
39 notebook_dir = Unicode(py3compat.getcwd(), config=True, help="""
39 40 The directory to use for notebooks.
40 41 """)
41 42
42 43 filename_ext = Unicode(u'.ipynb')
43 44
44 45 def path_exists(self, path):
45 46 """Does the API-style path (directory) actually exist?
46 47
47 48 Override this method in subclasses.
48 49
49 50 Parameters
50 51 ----------
51 52 path : string
52 53 The
53 54
54 55 Returns
55 56 -------
56 57 exists : bool
57 58 Whether the path does indeed exist.
58 59 """
59 60 raise NotImplementedError
60 61
61 62 def _notebook_dir_changed(self, name, old, new):
62 63 """Do a bit of validation of the notebook dir."""
63 64 if not os.path.isabs(new):
64 65 # If we receive a non-absolute path, make it absolute.
65 66 self.notebook_dir = os.path.abspath(new)
66 67 return
67 68 if os.path.exists(new) and not os.path.isdir(new):
68 69 raise TraitError("notebook dir %r is not a directory" % new)
69 70 if not os.path.exists(new):
70 71 self.log.info("Creating notebook dir %s", new)
71 72 try:
72 73 os.mkdir(new)
73 74 except:
74 75 raise TraitError("Couldn't create notebook dir %r" % new)
75 76
76 77 # Main notebook API
77 78
78 79 def increment_filename(self, basename, path=''):
79 80 """Increment a notebook filename without the .ipynb to make it unique.
80 81
81 82 Parameters
82 83 ----------
83 84 basename : unicode
84 85 The name of a notebook without the ``.ipynb`` file extension.
85 86 path : unicode
86 87 The URL path of the notebooks directory
87 88 """
88 89 return basename
89 90
90 91 def list_notebooks(self, path=''):
91 92 """Return a list of notebook dicts without content.
92 93
93 94 This returns a list of dicts, each of the form::
94 95
95 96 dict(notebook_id=notebook,name=name)
96 97
97 98 This list of dicts should be sorted by name::
98 99
99 100 data = sorted(data, key=lambda item: item['name'])
100 101 """
101 102 raise NotImplementedError('must be implemented in a subclass')
102 103
103 104 def get_notebook_model(self, name, path='', content=True):
104 105 """Get the notebook model with or without content."""
105 106 raise NotImplementedError('must be implemented in a subclass')
106 107
107 108 def save_notebook_model(self, model, name, path=''):
108 109 """Save the notebook model and return the model with no content."""
109 110 raise NotImplementedError('must be implemented in a subclass')
110 111
111 112 def update_notebook_model(self, model, name, path=''):
112 113 """Update the notebook model and return the model with no content."""
113 114 raise NotImplementedError('must be implemented in a subclass')
114 115
115 116 def delete_notebook_model(self, name, path=''):
116 117 """Delete notebook by name and path."""
117 118 raise NotImplementedError('must be implemented in a subclass')
118 119
119 120 def create_notebook_model(self, model=None, path=''):
120 121 """Create a new notebook and return its model with no content."""
121 122 path = path.strip('/')
122 123 if model is None:
123 124 model = {}
124 125 if 'content' not in model:
125 126 metadata = current.new_metadata(name=u'')
126 127 model['content'] = current.new_notebook(metadata=metadata)
127 128 if 'name' not in model:
128 129 model['name'] = self.increment_filename('Untitled', path)
129 130
130 131 model['path'] = path
131 132 model = self.save_notebook_model(model, model['name'], model['path'])
132 133 return model
133 134
134 135 def copy_notebook(self, from_name, to_name=None, path=''):
135 136 """Copy an existing notebook and return its new model.
136 137
137 138 If to_name not specified, increment `from_name-Copy#.ipynb`.
138 139 """
139 140 path = path.strip('/')
140 141 model = self.get_notebook_model(from_name, path)
141 142 if not to_name:
142 143 base = os.path.splitext(from_name)[0] + '-Copy'
143 144 to_name = self.increment_filename(base, path)
144 145 model['name'] = to_name
145 146 model = self.save_notebook_model(model, to_name, path)
146 147 return model
147 148
148 149 # Checkpoint-related
149 150
150 151 def create_checkpoint(self, name, path=''):
151 152 """Create a checkpoint of the current state of a notebook
152 153
153 154 Returns a checkpoint_id for the new checkpoint.
154 155 """
155 156 raise NotImplementedError("must be implemented in a subclass")
156 157
157 158 def list_checkpoints(self, name, path=''):
158 159 """Return a list of checkpoints for a given notebook"""
159 160 return []
160 161
161 162 def restore_checkpoint(self, checkpoint_id, name, path=''):
162 163 """Restore a notebook from one of its checkpoints"""
163 164 raise NotImplementedError("must be implemented in a subclass")
164 165
165 166 def delete_checkpoint(self, checkpoint_id, name, path=''):
166 167 """delete a checkpoint for a notebook"""
167 168 raise NotImplementedError("must be implemented in a subclass")
168 169
169 170 def log_info(self):
170 171 self.log.info(self.info_string())
171 172
172 173 def info_string(self):
173 174 return "Serving notebooks"
@@ -1,275 +1,276 b''
1 1 # encoding: utf-8
2 2 """
3 3 The Base Application class for IPython.parallel apps
4 4
5 5 Authors:
6 6
7 7 * Brian Granger
8 8 * Min RK
9 9
10 10 """
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Copyright (C) 2008-2011 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 import os
24 24 import logging
25 25 import re
26 26 import sys
27 27
28 28 from subprocess import Popen, PIPE
29 29
30 30 from IPython.config.application import catch_config_error, LevelFormatter
31 31 from IPython.core import release
32 32 from IPython.core.crashhandler import CrashHandler
33 33 from IPython.core.application import (
34 34 BaseIPythonApplication,
35 35 base_aliases as base_ip_aliases,
36 36 base_flags as base_ip_flags
37 37 )
38 38 from IPython.utils.path import expand_path
39 from IPython.utils import py3compat
39 40 from IPython.utils.py3compat import unicode_type
40 41
41 42 from IPython.utils.traitlets import Unicode, Bool, Instance, Dict
42 43
43 44 #-----------------------------------------------------------------------------
44 45 # Module errors
45 46 #-----------------------------------------------------------------------------
46 47
47 48 class PIDFileError(Exception):
48 49 pass
49 50
50 51
51 52 #-----------------------------------------------------------------------------
52 53 # Crash handler for this application
53 54 #-----------------------------------------------------------------------------
54 55
55 56 class ParallelCrashHandler(CrashHandler):
56 57 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
57 58
58 59 def __init__(self, app):
59 60 contact_name = release.authors['Min'][0]
60 61 contact_email = release.author_email
61 62 bug_tracker = 'https://github.com/ipython/ipython/issues'
62 63 super(ParallelCrashHandler,self).__init__(
63 64 app, contact_name, contact_email, bug_tracker
64 65 )
65 66
66 67
67 68 #-----------------------------------------------------------------------------
68 69 # Main application
69 70 #-----------------------------------------------------------------------------
70 71 base_aliases = {}
71 72 base_aliases.update(base_ip_aliases)
72 73 base_aliases.update({
73 74 'work-dir' : 'BaseParallelApplication.work_dir',
74 75 'log-to-file' : 'BaseParallelApplication.log_to_file',
75 76 'clean-logs' : 'BaseParallelApplication.clean_logs',
76 77 'log-url' : 'BaseParallelApplication.log_url',
77 78 'cluster-id' : 'BaseParallelApplication.cluster_id',
78 79 })
79 80
80 81 base_flags = {
81 82 'log-to-file' : (
82 83 {'BaseParallelApplication' : {'log_to_file' : True}},
83 84 "send log output to a file"
84 85 )
85 86 }
86 87 base_flags.update(base_ip_flags)
87 88
88 89 class BaseParallelApplication(BaseIPythonApplication):
89 90 """The base Application for IPython.parallel apps
90 91
91 92 Principle extensions to BaseIPyythonApplication:
92 93
93 94 * work_dir
94 95 * remote logging via pyzmq
95 96 * IOLoop instance
96 97 """
97 98
98 99 crash_handler_class = ParallelCrashHandler
99 100
100 101 def _log_level_default(self):
101 102 # temporarily override default_log_level to INFO
102 103 return logging.INFO
103 104
104 105 def _log_format_default(self):
105 106 """override default log format to include time"""
106 107 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
107 108
108 work_dir = Unicode(os.getcwdu(), config=True,
109 work_dir = Unicode(py3compat.getcwd(), config=True,
109 110 help='Set the working dir for the process.'
110 111 )
111 112 def _work_dir_changed(self, name, old, new):
112 113 self.work_dir = unicode_type(expand_path(new))
113 114
114 115 log_to_file = Bool(config=True,
115 116 help="whether to log to a file")
116 117
117 118 clean_logs = Bool(False, config=True,
118 119 help="whether to cleanup old logfiles before starting")
119 120
120 121 log_url = Unicode('', config=True,
121 122 help="The ZMQ URL of the iplogger to aggregate logging.")
122 123
123 124 cluster_id = Unicode('', config=True,
124 125 help="""String id to add to runtime files, to prevent name collisions when
125 126 using multiple clusters with a single profile simultaneously.
126 127
127 128 When set, files will be named like: 'ipcontroller-<cluster_id>-engine.json'
128 129
129 130 Since this is text inserted into filenames, typical recommendations apply:
130 131 Simple character strings are ideal, and spaces are not recommended (but should
131 132 generally work).
132 133 """
133 134 )
134 135 def _cluster_id_changed(self, name, old, new):
135 136 self.name = self.__class__.name
136 137 if new:
137 138 self.name += '-%s'%new
138 139
139 140 def _config_files_default(self):
140 141 return ['ipcontroller_config.py', 'ipengine_config.py', 'ipcluster_config.py']
141 142
142 143 loop = Instance('zmq.eventloop.ioloop.IOLoop')
143 144 def _loop_default(self):
144 145 from zmq.eventloop.ioloop import IOLoop
145 146 return IOLoop.instance()
146 147
147 148 aliases = Dict(base_aliases)
148 149 flags = Dict(base_flags)
149 150
150 151 @catch_config_error
151 152 def initialize(self, argv=None):
152 153 """initialize the app"""
153 154 super(BaseParallelApplication, self).initialize(argv)
154 155 self.to_work_dir()
155 156 self.reinit_logging()
156 157
157 158 def to_work_dir(self):
158 159 wd = self.work_dir
159 if unicode_type(wd) != os.getcwdu():
160 if unicode_type(wd) != py3compat.getcwd():
160 161 os.chdir(wd)
161 162 self.log.info("Changing to working dir: %s" % wd)
162 163 # This is the working dir by now.
163 164 sys.path.insert(0, '')
164 165
165 166 def reinit_logging(self):
166 167 # Remove old log files
167 168 log_dir = self.profile_dir.log_dir
168 169 if self.clean_logs:
169 170 for f in os.listdir(log_dir):
170 171 if re.match(r'%s-\d+\.(log|err|out)' % self.name, f):
171 172 try:
172 173 os.remove(os.path.join(log_dir, f))
173 174 except (OSError, IOError):
174 175 # probably just conflict from sibling process
175 176 # already removing it
176 177 pass
177 178 if self.log_to_file:
178 179 # Start logging to the new log file
179 180 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
180 181 logfile = os.path.join(log_dir, log_filename)
181 182 open_log_file = open(logfile, 'w')
182 183 else:
183 184 open_log_file = None
184 185 if open_log_file is not None:
185 186 while self.log.handlers:
186 187 self.log.removeHandler(self.log.handlers[0])
187 188 self._log_handler = logging.StreamHandler(open_log_file)
188 189 self.log.addHandler(self._log_handler)
189 190 else:
190 191 self._log_handler = self.log.handlers[0]
191 192 # Add timestamps to log format:
192 193 self._log_formatter = LevelFormatter(self.log_format,
193 194 datefmt=self.log_datefmt)
194 195 self._log_handler.setFormatter(self._log_formatter)
195 196 # do not propagate log messages to root logger
196 197 # ipcluster app will sometimes print duplicate messages during shutdown
197 198 # if this is 1 (default):
198 199 self.log.propagate = False
199 200
200 201 def write_pid_file(self, overwrite=False):
201 202 """Create a .pid file in the pid_dir with my pid.
202 203
203 204 This must be called after pre_construct, which sets `self.pid_dir`.
204 205 This raises :exc:`PIDFileError` if the pid file exists already.
205 206 """
206 207 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
207 208 if os.path.isfile(pid_file):
208 209 pid = self.get_pid_from_file()
209 210 if not overwrite:
210 211 raise PIDFileError(
211 212 'The pid file [%s] already exists. \nThis could mean that this '
212 213 'server is already running with [pid=%s].' % (pid_file, pid)
213 214 )
214 215 with open(pid_file, 'w') as f:
215 216 self.log.info("Creating pid file: %s" % pid_file)
216 217 f.write(repr(os.getpid())+'\n')
217 218
218 219 def remove_pid_file(self):
219 220 """Remove the pid file.
220 221
221 222 This should be called at shutdown by registering a callback with
222 223 :func:`reactor.addSystemEventTrigger`. This needs to return
223 224 ``None``.
224 225 """
225 226 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
226 227 if os.path.isfile(pid_file):
227 228 try:
228 229 self.log.info("Removing pid file: %s" % pid_file)
229 230 os.remove(pid_file)
230 231 except:
231 232 self.log.warn("Error removing the pid file: %s" % pid_file)
232 233
233 234 def get_pid_from_file(self):
234 235 """Get the pid from the pid file.
235 236
236 237 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
237 238 """
238 239 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
239 240 if os.path.isfile(pid_file):
240 241 with open(pid_file, 'r') as f:
241 242 s = f.read().strip()
242 243 try:
243 244 pid = int(s)
244 245 except:
245 246 raise PIDFileError("invalid pid file: %s (contents: %r)"%(pid_file, s))
246 247 return pid
247 248 else:
248 249 raise PIDFileError('pid file not found: %s' % pid_file)
249 250
250 251 def check_pid(self, pid):
251 252 if os.name == 'nt':
252 253 try:
253 254 import ctypes
254 255 # returns 0 if no such process (of ours) exists
255 256 # positive int otherwise
256 257 p = ctypes.windll.kernel32.OpenProcess(1,0,pid)
257 258 except Exception:
258 259 self.log.warn(
259 260 "Could not determine whether pid %i is running via `OpenProcess`. "
260 261 " Making the likely assumption that it is."%pid
261 262 )
262 263 return True
263 264 return bool(p)
264 265 else:
265 266 try:
266 267 p = Popen(['ps','x'], stdout=PIPE, stderr=PIPE)
267 268 output,_ = p.communicate()
268 269 except OSError:
269 270 self.log.warn(
270 271 "Could not determine whether pid %i is running via `ps x`. "
271 272 " Making the likely assumption that it is."%pid
272 273 )
273 274 return True
274 275 pids = list(map(int, re.findall(r'^\W*\d+', output, re.MULTILINE)))
275 276 return pid in pids
@@ -1,764 +1,764 b''
1 1 """Nose Plugin that supports IPython doctests.
2 2
3 3 Limitations:
4 4
5 5 - When generating examples for use as doctests, make sure that you have
6 6 pretty-printing OFF. This can be done either by setting the
7 7 ``PlainTextFormatter.pprint`` option in your configuration file to False, or
8 8 by interactively disabling it with %Pprint. This is required so that IPython
9 9 output matches that of normal Python, which is used by doctest for internal
10 10 execution.
11 11
12 12 - Do not rely on specific prompt numbers for results (such as using
13 13 '_34==True', for example). For IPython tests run via an external process the
14 14 prompt numbers may be different, and IPython tests run as normal python code
15 15 won't even have these special _NN variables set at all.
16 16 """
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Module imports
20 20
21 21 # From the standard library
22 22 import doctest
23 23 import inspect
24 24 import logging
25 25 import os
26 26 import re
27 27 import sys
28 28 import traceback
29 29 import unittest
30 30
31 31 from inspect import getmodule
32 32
33 33 # We are overriding the default doctest runner, so we need to import a few
34 34 # things from doctest directly
35 35 from doctest import (REPORTING_FLAGS, REPORT_ONLY_FIRST_FAILURE,
36 36 _unittest_reportflags, DocTestRunner,
37 37 _extract_future_flags, pdb, _OutputRedirectingPdb,
38 38 _exception_traceback,
39 39 linecache)
40 40
41 41 # Third-party modules
42 42 import nose.core
43 43
44 44 from nose.plugins import doctests, Plugin
45 45 from nose.util import anyp, getpackage, test_address, resolve_name, tolist
46 46
47 47 # Our own imports
48 from IPython.utils.py3compat import builtin_mod, PY3
48 from IPython.utils.py3compat import builtin_mod, PY3, getcwd
49 49
50 50 if PY3:
51 51 from io import StringIO
52 52 else:
53 53 from StringIO import StringIO
54 54
55 55 #-----------------------------------------------------------------------------
56 56 # Module globals and other constants
57 57 #-----------------------------------------------------------------------------
58 58
59 59 log = logging.getLogger(__name__)
60 60
61 61
62 62 #-----------------------------------------------------------------------------
63 63 # Classes and functions
64 64 #-----------------------------------------------------------------------------
65 65
66 66 def is_extension_module(filename):
67 67 """Return whether the given filename is an extension module.
68 68
69 69 This simply checks that the extension is either .so or .pyd.
70 70 """
71 71 return os.path.splitext(filename)[1].lower() in ('.so','.pyd')
72 72
73 73
74 74 class DocTestSkip(object):
75 75 """Object wrapper for doctests to be skipped."""
76 76
77 77 ds_skip = """Doctest to skip.
78 78 >>> 1 #doctest: +SKIP
79 79 """
80 80
81 81 def __init__(self,obj):
82 82 self.obj = obj
83 83
84 84 def __getattribute__(self,key):
85 85 if key == '__doc__':
86 86 return DocTestSkip.ds_skip
87 87 else:
88 88 return getattr(object.__getattribute__(self,'obj'),key)
89 89
90 90 # Modified version of the one in the stdlib, that fixes a python bug (doctests
91 91 # not found in extension modules, http://bugs.python.org/issue3158)
92 92 class DocTestFinder(doctest.DocTestFinder):
93 93
94 94 def _from_module(self, module, object):
95 95 """
96 96 Return true if the given object is defined in the given
97 97 module.
98 98 """
99 99 if module is None:
100 100 return True
101 101 elif inspect.isfunction(object):
102 102 return module.__dict__ is object.__globals__
103 103 elif inspect.isbuiltin(object):
104 104 return module.__name__ == object.__module__
105 105 elif inspect.isclass(object):
106 106 return module.__name__ == object.__module__
107 107 elif inspect.ismethod(object):
108 108 # This one may be a bug in cython that fails to correctly set the
109 109 # __module__ attribute of methods, but since the same error is easy
110 110 # to make by extension code writers, having this safety in place
111 111 # isn't such a bad idea
112 112 return module.__name__ == object.__self__.__class__.__module__
113 113 elif inspect.getmodule(object) is not None:
114 114 return module is inspect.getmodule(object)
115 115 elif hasattr(object, '__module__'):
116 116 return module.__name__ == object.__module__
117 117 elif isinstance(object, property):
118 118 return True # [XX] no way not be sure.
119 119 else:
120 120 raise ValueError("object must be a class or function, got %r" % object)
121 121
122 122 def _find(self, tests, obj, name, module, source_lines, globs, seen):
123 123 """
124 124 Find tests for the given object and any contained objects, and
125 125 add them to `tests`.
126 126 """
127 127 #print '_find for:', obj, name, module # dbg
128 128 if hasattr(obj,"skip_doctest"):
129 129 #print 'SKIPPING DOCTEST FOR:',obj # dbg
130 130 obj = DocTestSkip(obj)
131 131
132 132 doctest.DocTestFinder._find(self,tests, obj, name, module,
133 133 source_lines, globs, seen)
134 134
135 135 # Below we re-run pieces of the above method with manual modifications,
136 136 # because the original code is buggy and fails to correctly identify
137 137 # doctests in extension modules.
138 138
139 139 # Local shorthands
140 140 from inspect import isroutine, isclass, ismodule
141 141
142 142 # Look for tests in a module's contained objects.
143 143 if inspect.ismodule(obj) and self._recurse:
144 144 for valname, val in obj.__dict__.items():
145 145 valname1 = '%s.%s' % (name, valname)
146 146 if ( (isroutine(val) or isclass(val))
147 147 and self._from_module(module, val) ):
148 148
149 149 self._find(tests, val, valname1, module, source_lines,
150 150 globs, seen)
151 151
152 152 # Look for tests in a class's contained objects.
153 153 if inspect.isclass(obj) and self._recurse:
154 154 #print 'RECURSE into class:',obj # dbg
155 155 for valname, val in obj.__dict__.items():
156 156 # Special handling for staticmethod/classmethod.
157 157 if isinstance(val, staticmethod):
158 158 val = getattr(obj, valname)
159 159 if isinstance(val, classmethod):
160 160 val = getattr(obj, valname).__func__
161 161
162 162 # Recurse to methods, properties, and nested classes.
163 163 if ((inspect.isfunction(val) or inspect.isclass(val) or
164 164 inspect.ismethod(val) or
165 165 isinstance(val, property)) and
166 166 self._from_module(module, val)):
167 167 valname = '%s.%s' % (name, valname)
168 168 self._find(tests, val, valname, module, source_lines,
169 169 globs, seen)
170 170
171 171
172 172 class IPDoctestOutputChecker(doctest.OutputChecker):
173 173 """Second-chance checker with support for random tests.
174 174
175 175 If the default comparison doesn't pass, this checker looks in the expected
176 176 output string for flags that tell us to ignore the output.
177 177 """
178 178
179 179 random_re = re.compile(r'#\s*random\s+')
180 180
181 181 def check_output(self, want, got, optionflags):
182 182 """Check output, accepting special markers embedded in the output.
183 183
184 184 If the output didn't pass the default validation but the special string
185 185 '#random' is included, we accept it."""
186 186
187 187 # Let the original tester verify first, in case people have valid tests
188 188 # that happen to have a comment saying '#random' embedded in.
189 189 ret = doctest.OutputChecker.check_output(self, want, got,
190 190 optionflags)
191 191 if not ret and self.random_re.search(want):
192 192 #print >> sys.stderr, 'RANDOM OK:',want # dbg
193 193 return True
194 194
195 195 return ret
196 196
197 197
198 198 class DocTestCase(doctests.DocTestCase):
199 199 """Proxy for DocTestCase: provides an address() method that
200 200 returns the correct address for the doctest case. Otherwise
201 201 acts as a proxy to the test case. To provide hints for address(),
202 202 an obj may also be passed -- this will be used as the test object
203 203 for purposes of determining the test address, if it is provided.
204 204 """
205 205
206 206 # Note: this method was taken from numpy's nosetester module.
207 207
208 208 # Subclass nose.plugins.doctests.DocTestCase to work around a bug in
209 209 # its constructor that blocks non-default arguments from being passed
210 210 # down into doctest.DocTestCase
211 211
212 212 def __init__(self, test, optionflags=0, setUp=None, tearDown=None,
213 213 checker=None, obj=None, result_var='_'):
214 214 self._result_var = result_var
215 215 doctests.DocTestCase.__init__(self, test,
216 216 optionflags=optionflags,
217 217 setUp=setUp, tearDown=tearDown,
218 218 checker=checker)
219 219 # Now we must actually copy the original constructor from the stdlib
220 220 # doctest class, because we can't call it directly and a bug in nose
221 221 # means it never gets passed the right arguments.
222 222
223 223 self._dt_optionflags = optionflags
224 224 self._dt_checker = checker
225 225 self._dt_test = test
226 226 self._dt_test_globs_ori = test.globs
227 227 self._dt_setUp = setUp
228 228 self._dt_tearDown = tearDown
229 229
230 230 # XXX - store this runner once in the object!
231 231 runner = IPDocTestRunner(optionflags=optionflags,
232 232 checker=checker, verbose=False)
233 233 self._dt_runner = runner
234 234
235 235
236 236 # Each doctest should remember the directory it was loaded from, so
237 237 # things like %run work without too many contortions
238 238 self._ori_dir = os.path.dirname(test.filename)
239 239
240 240 # Modified runTest from the default stdlib
241 241 def runTest(self):
242 242 test = self._dt_test
243 243 runner = self._dt_runner
244 244
245 245 old = sys.stdout
246 246 new = StringIO()
247 247 optionflags = self._dt_optionflags
248 248
249 249 if not (optionflags & REPORTING_FLAGS):
250 250 # The option flags don't include any reporting flags,
251 251 # so add the default reporting flags
252 252 optionflags |= _unittest_reportflags
253 253
254 254 try:
255 255 # Save our current directory and switch out to the one where the
256 256 # test was originally created, in case another doctest did a
257 257 # directory change. We'll restore this in the finally clause.
258 curdir = os.getcwdu()
258 curdir = getcwd()
259 259 #print 'runTest in dir:', self._ori_dir # dbg
260 260 os.chdir(self._ori_dir)
261 261
262 262 runner.DIVIDER = "-"*70
263 263 failures, tries = runner.run(test,out=new.write,
264 264 clear_globs=False)
265 265 finally:
266 266 sys.stdout = old
267 267 os.chdir(curdir)
268 268
269 269 if failures:
270 270 raise self.failureException(self.format_failure(new.getvalue()))
271 271
272 272 def setUp(self):
273 273 """Modified test setup that syncs with ipython namespace"""
274 274 #print "setUp test", self._dt_test.examples # dbg
275 275 if isinstance(self._dt_test.examples[0], IPExample):
276 276 # for IPython examples *only*, we swap the globals with the ipython
277 277 # namespace, after updating it with the globals (which doctest
278 278 # fills with the necessary info from the module being tested).
279 279 self.user_ns_orig = {}
280 280 self.user_ns_orig.update(_ip.user_ns)
281 281 _ip.user_ns.update(self._dt_test.globs)
282 282 # We must remove the _ key in the namespace, so that Python's
283 283 # doctest code sets it naturally
284 284 _ip.user_ns.pop('_', None)
285 285 _ip.user_ns['__builtins__'] = builtin_mod
286 286 self._dt_test.globs = _ip.user_ns
287 287
288 288 super(DocTestCase, self).setUp()
289 289
290 290 def tearDown(self):
291 291
292 292 # Undo the test.globs reassignment we made, so that the parent class
293 293 # teardown doesn't destroy the ipython namespace
294 294 if isinstance(self._dt_test.examples[0], IPExample):
295 295 self._dt_test.globs = self._dt_test_globs_ori
296 296 _ip.user_ns.clear()
297 297 _ip.user_ns.update(self.user_ns_orig)
298 298
299 299 # XXX - fperez: I am not sure if this is truly a bug in nose 0.11, but
300 300 # it does look like one to me: its tearDown method tries to run
301 301 #
302 302 # delattr(builtin_mod, self._result_var)
303 303 #
304 304 # without checking that the attribute really is there; it implicitly
305 305 # assumes it should have been set via displayhook. But if the
306 306 # displayhook was never called, this doesn't necessarily happen. I
307 307 # haven't been able to find a little self-contained example outside of
308 308 # ipython that would show the problem so I can report it to the nose
309 309 # team, but it does happen a lot in our code.
310 310 #
311 311 # So here, we just protect as narrowly as possible by trapping an
312 312 # attribute error whose message would be the name of self._result_var,
313 313 # and letting any other error propagate.
314 314 try:
315 315 super(DocTestCase, self).tearDown()
316 316 except AttributeError as exc:
317 317 if exc.args[0] != self._result_var:
318 318 raise
319 319
320 320
321 321 # A simple subclassing of the original with a different class name, so we can
322 322 # distinguish and treat differently IPython examples from pure python ones.
323 323 class IPExample(doctest.Example): pass
324 324
325 325
326 326 class IPExternalExample(doctest.Example):
327 327 """Doctest examples to be run in an external process."""
328 328
329 329 def __init__(self, source, want, exc_msg=None, lineno=0, indent=0,
330 330 options=None):
331 331 # Parent constructor
332 332 doctest.Example.__init__(self,source,want,exc_msg,lineno,indent,options)
333 333
334 334 # An EXTRA newline is needed to prevent pexpect hangs
335 335 self.source += '\n'
336 336
337 337
338 338 class IPDocTestParser(doctest.DocTestParser):
339 339 """
340 340 A class used to parse strings containing doctest examples.
341 341
342 342 Note: This is a version modified to properly recognize IPython input and
343 343 convert any IPython examples into valid Python ones.
344 344 """
345 345 # This regular expression is used to find doctest examples in a
346 346 # string. It defines three groups: `source` is the source code
347 347 # (including leading indentation and prompts); `indent` is the
348 348 # indentation of the first (PS1) line of the source code; and
349 349 # `want` is the expected output (including leading indentation).
350 350
351 351 # Classic Python prompts or default IPython ones
352 352 _PS1_PY = r'>>>'
353 353 _PS2_PY = r'\.\.\.'
354 354
355 355 _PS1_IP = r'In\ \[\d+\]:'
356 356 _PS2_IP = r'\ \ \ \.\.\.+:'
357 357
358 358 _RE_TPL = r'''
359 359 # Source consists of a PS1 line followed by zero or more PS2 lines.
360 360 (?P<source>
361 361 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
362 362 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
363 363 \n? # a newline
364 364 # Want consists of any non-blank lines that do not start with PS1.
365 365 (?P<want> (?:(?![ ]*$) # Not a blank line
366 366 (?![ ]*%s) # Not a line starting with PS1
367 367 (?![ ]*%s) # Not a line starting with PS2
368 368 .*$\n? # But any other line
369 369 )*)
370 370 '''
371 371
372 372 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
373 373 re.MULTILINE | re.VERBOSE)
374 374
375 375 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
376 376 re.MULTILINE | re.VERBOSE)
377 377
378 378 # Mark a test as being fully random. In this case, we simply append the
379 379 # random marker ('#random') to each individual example's output. This way
380 380 # we don't need to modify any other code.
381 381 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
382 382
383 383 # Mark tests to be executed in an external process - currently unsupported.
384 384 _EXTERNAL_IP = re.compile(r'#\s*ipdoctest:\s*EXTERNAL')
385 385
386 386 def ip2py(self,source):
387 387 """Convert input IPython source into valid Python."""
388 388 block = _ip.input_transformer_manager.transform_cell(source)
389 389 if len(block.splitlines()) == 1:
390 390 return _ip.prefilter(block)
391 391 else:
392 392 return block
393 393
394 394 def parse(self, string, name='<string>'):
395 395 """
396 396 Divide the given string into examples and intervening text,
397 397 and return them as a list of alternating Examples and strings.
398 398 Line numbers for the Examples are 0-based. The optional
399 399 argument `name` is a name identifying this string, and is only
400 400 used for error messages.
401 401 """
402 402
403 403 #print 'Parse string:\n',string # dbg
404 404
405 405 string = string.expandtabs()
406 406 # If all lines begin with the same indentation, then strip it.
407 407 min_indent = self._min_indent(string)
408 408 if min_indent > 0:
409 409 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
410 410
411 411 output = []
412 412 charno, lineno = 0, 0
413 413
414 414 # We make 'all random' tests by adding the '# random' mark to every
415 415 # block of output in the test.
416 416 if self._RANDOM_TEST.search(string):
417 417 random_marker = '\n# random'
418 418 else:
419 419 random_marker = ''
420 420
421 421 # Whether to convert the input from ipython to python syntax
422 422 ip2py = False
423 423 # Find all doctest examples in the string. First, try them as Python
424 424 # examples, then as IPython ones
425 425 terms = list(self._EXAMPLE_RE_PY.finditer(string))
426 426 if terms:
427 427 # Normal Python example
428 428 #print '-'*70 # dbg
429 429 #print 'PyExample, Source:\n',string # dbg
430 430 #print '-'*70 # dbg
431 431 Example = doctest.Example
432 432 else:
433 433 # It's an ipython example. Note that IPExamples are run
434 434 # in-process, so their syntax must be turned into valid python.
435 435 # IPExternalExamples are run out-of-process (via pexpect) so they
436 436 # don't need any filtering (a real ipython will be executing them).
437 437 terms = list(self._EXAMPLE_RE_IP.finditer(string))
438 438 if self._EXTERNAL_IP.search(string):
439 439 #print '-'*70 # dbg
440 440 #print 'IPExternalExample, Source:\n',string # dbg
441 441 #print '-'*70 # dbg
442 442 Example = IPExternalExample
443 443 else:
444 444 #print '-'*70 # dbg
445 445 #print 'IPExample, Source:\n',string # dbg
446 446 #print '-'*70 # dbg
447 447 Example = IPExample
448 448 ip2py = True
449 449
450 450 for m in terms:
451 451 # Add the pre-example text to `output`.
452 452 output.append(string[charno:m.start()])
453 453 # Update lineno (lines before this example)
454 454 lineno += string.count('\n', charno, m.start())
455 455 # Extract info from the regexp match.
456 456 (source, options, want, exc_msg) = \
457 457 self._parse_example(m, name, lineno,ip2py)
458 458
459 459 # Append the random-output marker (it defaults to empty in most
460 460 # cases, it's only non-empty for 'all-random' tests):
461 461 want += random_marker
462 462
463 463 if Example is IPExternalExample:
464 464 options[doctest.NORMALIZE_WHITESPACE] = True
465 465 want += '\n'
466 466
467 467 # Create an Example, and add it to the list.
468 468 if not self._IS_BLANK_OR_COMMENT(source):
469 469 output.append(Example(source, want, exc_msg,
470 470 lineno=lineno,
471 471 indent=min_indent+len(m.group('indent')),
472 472 options=options))
473 473 # Update lineno (lines inside this example)
474 474 lineno += string.count('\n', m.start(), m.end())
475 475 # Update charno.
476 476 charno = m.end()
477 477 # Add any remaining post-example text to `output`.
478 478 output.append(string[charno:])
479 479 return output
480 480
481 481 def _parse_example(self, m, name, lineno,ip2py=False):
482 482 """
483 483 Given a regular expression match from `_EXAMPLE_RE` (`m`),
484 484 return a pair `(source, want)`, where `source` is the matched
485 485 example's source code (with prompts and indentation stripped);
486 486 and `want` is the example's expected output (with indentation
487 487 stripped).
488 488
489 489 `name` is the string's name, and `lineno` is the line number
490 490 where the example starts; both are used for error messages.
491 491
492 492 Optional:
493 493 `ip2py`: if true, filter the input via IPython to convert the syntax
494 494 into valid python.
495 495 """
496 496
497 497 # Get the example's indentation level.
498 498 indent = len(m.group('indent'))
499 499
500 500 # Divide source into lines; check that they're properly
501 501 # indented; and then strip their indentation & prompts.
502 502 source_lines = m.group('source').split('\n')
503 503
504 504 # We're using variable-length input prompts
505 505 ps1 = m.group('ps1')
506 506 ps2 = m.group('ps2')
507 507 ps1_len = len(ps1)
508 508
509 509 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
510 510 if ps2:
511 511 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
512 512
513 513 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
514 514
515 515 if ip2py:
516 516 # Convert source input from IPython into valid Python syntax
517 517 source = self.ip2py(source)
518 518
519 519 # Divide want into lines; check that it's properly indented; and
520 520 # then strip the indentation. Spaces before the last newline should
521 521 # be preserved, so plain rstrip() isn't good enough.
522 522 want = m.group('want')
523 523 want_lines = want.split('\n')
524 524 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
525 525 del want_lines[-1] # forget final newline & spaces after it
526 526 self._check_prefix(want_lines, ' '*indent, name,
527 527 lineno + len(source_lines))
528 528
529 529 # Remove ipython output prompt that might be present in the first line
530 530 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
531 531
532 532 want = '\n'.join([wl[indent:] for wl in want_lines])
533 533
534 534 # If `want` contains a traceback message, then extract it.
535 535 m = self._EXCEPTION_RE.match(want)
536 536 if m:
537 537 exc_msg = m.group('msg')
538 538 else:
539 539 exc_msg = None
540 540
541 541 # Extract options from the source.
542 542 options = self._find_options(source, name, lineno)
543 543
544 544 return source, options, want, exc_msg
545 545
546 546 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
547 547 """
548 548 Given the lines of a source string (including prompts and
549 549 leading indentation), check to make sure that every prompt is
550 550 followed by a space character. If any line is not followed by
551 551 a space character, then raise ValueError.
552 552
553 553 Note: IPython-modified version which takes the input prompt length as a
554 554 parameter, so that prompts of variable length can be dealt with.
555 555 """
556 556 space_idx = indent+ps1_len
557 557 min_len = space_idx+1
558 558 for i, line in enumerate(lines):
559 559 if len(line) >= min_len and line[space_idx] != ' ':
560 560 raise ValueError('line %r of the docstring for %s '
561 561 'lacks blank after %s: %r' %
562 562 (lineno+i+1, name,
563 563 line[indent:space_idx], line))
564 564
565 565
566 566 SKIP = doctest.register_optionflag('SKIP')
567 567
568 568
569 569 class IPDocTestRunner(doctest.DocTestRunner,object):
570 570 """Test runner that synchronizes the IPython namespace with test globals.
571 571 """
572 572
573 573 def run(self, test, compileflags=None, out=None, clear_globs=True):
574 574
575 575 # Hack: ipython needs access to the execution context of the example,
576 576 # so that it can propagate user variables loaded by %run into
577 577 # test.globs. We put them here into our modified %run as a function
578 578 # attribute. Our new %run will then only make the namespace update
579 579 # when called (rather than unconconditionally updating test.globs here
580 580 # for all examples, most of which won't be calling %run anyway).
581 581 #_ip._ipdoctest_test_globs = test.globs
582 582 #_ip._ipdoctest_test_filename = test.filename
583 583
584 584 test.globs.update(_ip.user_ns)
585 585
586 586 return super(IPDocTestRunner,self).run(test,
587 587 compileflags,out,clear_globs)
588 588
589 589
590 590 class DocFileCase(doctest.DocFileCase):
591 591 """Overrides to provide filename
592 592 """
593 593 def address(self):
594 594 return (self._dt_test.filename, None, None)
595 595
596 596
597 597 class ExtensionDoctest(doctests.Doctest):
598 598 """Nose Plugin that supports doctests in extension modules.
599 599 """
600 600 name = 'extdoctest' # call nosetests with --with-extdoctest
601 601 enabled = True
602 602
603 603 def options(self, parser, env=os.environ):
604 604 Plugin.options(self, parser, env)
605 605 parser.add_option('--doctest-tests', action='store_true',
606 606 dest='doctest_tests',
607 607 default=env.get('NOSE_DOCTEST_TESTS',True),
608 608 help="Also look for doctests in test modules. "
609 609 "Note that classes, methods and functions should "
610 610 "have either doctests or non-doctest tests, "
611 611 "not both. [NOSE_DOCTEST_TESTS]")
612 612 parser.add_option('--doctest-extension', action="append",
613 613 dest="doctestExtension",
614 614 help="Also look for doctests in files with "
615 615 "this extension [NOSE_DOCTEST_EXTENSION]")
616 616 # Set the default as a list, if given in env; otherwise
617 617 # an additional value set on the command line will cause
618 618 # an error.
619 619 env_setting = env.get('NOSE_DOCTEST_EXTENSION')
620 620 if env_setting is not None:
621 621 parser.set_defaults(doctestExtension=tolist(env_setting))
622 622
623 623
624 624 def configure(self, options, config):
625 625 Plugin.configure(self, options, config)
626 626 # Pull standard doctest plugin out of config; we will do doctesting
627 627 config.plugins.plugins = [p for p in config.plugins.plugins
628 628 if p.name != 'doctest']
629 629 self.doctest_tests = options.doctest_tests
630 630 self.extension = tolist(options.doctestExtension)
631 631
632 632 self.parser = doctest.DocTestParser()
633 633 self.finder = DocTestFinder()
634 634 self.checker = IPDoctestOutputChecker()
635 635 self.globs = None
636 636 self.extraglobs = None
637 637
638 638
639 639 def loadTestsFromExtensionModule(self,filename):
640 640 bpath,mod = os.path.split(filename)
641 641 modname = os.path.splitext(mod)[0]
642 642 try:
643 643 sys.path.append(bpath)
644 644 module = __import__(modname)
645 645 tests = list(self.loadTestsFromModule(module))
646 646 finally:
647 647 sys.path.pop()
648 648 return tests
649 649
650 650 # NOTE: the method below is almost a copy of the original one in nose, with
651 651 # a few modifications to control output checking.
652 652
653 653 def loadTestsFromModule(self, module):
654 654 #print '*** ipdoctest - lTM',module # dbg
655 655
656 656 if not self.matches(module.__name__):
657 657 log.debug("Doctest doesn't want module %s", module)
658 658 return
659 659
660 660 tests = self.finder.find(module,globs=self.globs,
661 661 extraglobs=self.extraglobs)
662 662 if not tests:
663 663 return
664 664
665 665 # always use whitespace and ellipsis options
666 666 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
667 667
668 668 tests.sort()
669 669 module_file = module.__file__
670 670 if module_file[-4:] in ('.pyc', '.pyo'):
671 671 module_file = module_file[:-1]
672 672 for test in tests:
673 673 if not test.examples:
674 674 continue
675 675 if not test.filename:
676 676 test.filename = module_file
677 677
678 678 yield DocTestCase(test,
679 679 optionflags=optionflags,
680 680 checker=self.checker)
681 681
682 682
683 683 def loadTestsFromFile(self, filename):
684 684 #print "ipdoctest - from file", filename # dbg
685 685 if is_extension_module(filename):
686 686 for t in self.loadTestsFromExtensionModule(filename):
687 687 yield t
688 688 else:
689 689 if self.extension and anyp(filename.endswith, self.extension):
690 690 name = os.path.basename(filename)
691 691 dh = open(filename)
692 692 try:
693 693 doc = dh.read()
694 694 finally:
695 695 dh.close()
696 696 test = self.parser.get_doctest(
697 697 doc, globs={'__file__': filename}, name=name,
698 698 filename=filename, lineno=0)
699 699 if test.examples:
700 700 #print 'FileCase:',test.examples # dbg
701 701 yield DocFileCase(test)
702 702 else:
703 703 yield False # no tests to load
704 704
705 705
706 706 class IPythonDoctest(ExtensionDoctest):
707 707 """Nose Plugin that supports doctests in extension modules.
708 708 """
709 709 name = 'ipdoctest' # call nosetests with --with-ipdoctest
710 710 enabled = True
711 711
712 712 def makeTest(self, obj, parent):
713 713 """Look for doctests in the given object, which will be a
714 714 function, method or class.
715 715 """
716 716 #print 'Plugin analyzing:', obj, parent # dbg
717 717 # always use whitespace and ellipsis options
718 718 optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
719 719
720 720 doctests = self.finder.find(obj, module=getmodule(parent))
721 721 if doctests:
722 722 for test in doctests:
723 723 if len(test.examples) == 0:
724 724 continue
725 725
726 726 yield DocTestCase(test, obj=obj,
727 727 optionflags=optionflags,
728 728 checker=self.checker)
729 729
730 730 def options(self, parser, env=os.environ):
731 731 #print "Options for nose plugin:", self.name # dbg
732 732 Plugin.options(self, parser, env)
733 733 parser.add_option('--ipdoctest-tests', action='store_true',
734 734 dest='ipdoctest_tests',
735 735 default=env.get('NOSE_IPDOCTEST_TESTS',True),
736 736 help="Also look for doctests in test modules. "
737 737 "Note that classes, methods and functions should "
738 738 "have either doctests or non-doctest tests, "
739 739 "not both. [NOSE_IPDOCTEST_TESTS]")
740 740 parser.add_option('--ipdoctest-extension', action="append",
741 741 dest="ipdoctest_extension",
742 742 help="Also look for doctests in files with "
743 743 "this extension [NOSE_IPDOCTEST_EXTENSION]")
744 744 # Set the default as a list, if given in env; otherwise
745 745 # an additional value set on the command line will cause
746 746 # an error.
747 747 env_setting = env.get('NOSE_IPDOCTEST_EXTENSION')
748 748 if env_setting is not None:
749 749 parser.set_defaults(ipdoctest_extension=tolist(env_setting))
750 750
751 751 def configure(self, options, config):
752 752 #print "Configuring nose plugin:", self.name # dbg
753 753 Plugin.configure(self, options, config)
754 754 # Pull standard doctest plugin out of config; we will do doctesting
755 755 config.plugins.plugins = [p for p in config.plugins.plugins
756 756 if p.name != 'doctest']
757 757 self.doctest_tests = options.ipdoctest_tests
758 758 self.extension = tolist(options.ipdoctest_extension)
759 759
760 760 self.parser = IPDocTestParser()
761 761 self.finder = DocTestFinder(parser=self.parser)
762 762 self.checker = IPDoctestOutputChecker()
763 763 self.globs = None
764 764 self.extraglobs = None
@@ -1,187 +1,187 b''
1 1 """Windows-specific implementation of process utilities.
2 2
3 3 This file is only meant to be imported by process.py, not by end-users.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2010-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # stdlib
19 19 import os
20 20 import sys
21 21 import ctypes
22 22
23 23 from ctypes import c_int, POINTER
24 24 from ctypes.wintypes import LPCWSTR, HLOCAL
25 25 from subprocess import STDOUT
26 26
27 27 # our own imports
28 28 from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
29 29 from . import py3compat
30 30 from .encoding import DEFAULT_ENCODING
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # Function definitions
34 34 #-----------------------------------------------------------------------------
35 35
36 36 class AvoidUNCPath(object):
37 37 """A context manager to protect command execution from UNC paths.
38 38
39 39 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
40 40 This context manager temporarily changes directory to the 'C:' drive on
41 41 entering, and restores the original working directory on exit.
42 42
43 43 The context manager returns the starting working directory *if* it made a
44 44 change and None otherwise, so that users can apply the necessary adjustment
45 45 to their system calls in the event of a change.
46 46
47 47 Examples
48 48 --------
49 49 ::
50 50 cmd = 'dir'
51 51 with AvoidUNCPath() as path:
52 52 if path is not None:
53 53 cmd = '"pushd %s &&"%s' % (path, cmd)
54 54 os.system(cmd)
55 55 """
56 56 def __enter__(self):
57 self.path = os.getcwdu()
57 self.path = py3compat.getcwd()
58 58 self.is_unc_path = self.path.startswith(r"\\")
59 59 if self.is_unc_path:
60 60 # change to c drive (as cmd.exe cannot handle UNC addresses)
61 61 os.chdir("C:")
62 62 return self.path
63 63 else:
64 64 # We return None to signal that there was no change in the working
65 65 # directory
66 66 return None
67 67
68 68 def __exit__(self, exc_type, exc_value, traceback):
69 69 if self.is_unc_path:
70 70 os.chdir(self.path)
71 71
72 72
73 73 def _find_cmd(cmd):
74 74 """Find the full path to a .bat or .exe using the win32api module."""
75 75 try:
76 76 from win32api import SearchPath
77 77 except ImportError:
78 78 raise ImportError('you need to have pywin32 installed for this to work')
79 79 else:
80 80 PATH = os.environ['PATH']
81 81 extensions = ['.exe', '.com', '.bat', '.py']
82 82 path = None
83 83 for ext in extensions:
84 84 try:
85 85 path = SearchPath(PATH, cmd, ext)[0]
86 86 except:
87 87 pass
88 88 if path is None:
89 89 raise OSError("command %r not found" % cmd)
90 90 else:
91 91 return path
92 92
93 93
94 94 def _system_body(p):
95 95 """Callback for _system."""
96 96 enc = DEFAULT_ENCODING
97 97 for line in read_no_interrupt(p.stdout).splitlines():
98 98 line = line.decode(enc, 'replace')
99 99 print(line, file=sys.stdout)
100 100 for line in read_no_interrupt(p.stderr).splitlines():
101 101 line = line.decode(enc, 'replace')
102 102 print(line, file=sys.stderr)
103 103
104 104 # Wait to finish for returncode
105 105 return p.wait()
106 106
107 107
108 108 def system(cmd):
109 109 """Win32 version of os.system() that works with network shares.
110 110
111 111 Note that this implementation returns None, as meant for use in IPython.
112 112
113 113 Parameters
114 114 ----------
115 115 cmd : str
116 116 A command to be executed in the system shell.
117 117
118 118 Returns
119 119 -------
120 120 None : we explicitly do NOT return the subprocess status code, as this
121 121 utility is meant to be used extensively in IPython, where any return value
122 122 would trigger :func:`sys.displayhook` calls.
123 123 """
124 124 # The controller provides interactivity with both
125 125 # stdin and stdout
126 126 #import _process_win32_controller
127 127 #_process_win32_controller.system(cmd)
128 128
129 129 with AvoidUNCPath() as path:
130 130 if path is not None:
131 131 cmd = '"pushd %s &&"%s' % (path, cmd)
132 132 return process_handler(cmd, _system_body)
133 133
134 134 def getoutput(cmd):
135 135 """Return standard output of executing cmd in a shell.
136 136
137 137 Accepts the same arguments as os.system().
138 138
139 139 Parameters
140 140 ----------
141 141 cmd : str
142 142 A command to be executed in the system shell.
143 143
144 144 Returns
145 145 -------
146 146 stdout : str
147 147 """
148 148
149 149 with AvoidUNCPath() as path:
150 150 if path is not None:
151 151 cmd = '"pushd %s &&"%s' % (path, cmd)
152 152 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
153 153
154 154 if out is None:
155 155 out = b''
156 156 return py3compat.bytes_to_str(out)
157 157
158 158 try:
159 159 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
160 160 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
161 161 CommandLineToArgvW.restype = POINTER(LPCWSTR)
162 162 LocalFree = ctypes.windll.kernel32.LocalFree
163 163 LocalFree.res_type = HLOCAL
164 164 LocalFree.arg_types = [HLOCAL]
165 165
166 166 def arg_split(commandline, posix=False, strict=True):
167 167 """Split a command line's arguments in a shell-like manner.
168 168
169 169 This is a special version for windows that use a ctypes call to CommandLineToArgvW
170 170 to do the argv splitting. The posix paramter is ignored.
171 171
172 172 If strict=False, process_common.arg_split(...strict=False) is used instead.
173 173 """
174 174 #CommandLineToArgvW returns path to executable if called with empty string.
175 175 if commandline.strip() == "":
176 176 return []
177 177 if not strict:
178 178 # not really a cl-arg, fallback on _process_common
179 179 return py_arg_split(commandline, posix=posix, strict=strict)
180 180 argvn = c_int()
181 181 result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
182 182 result_array_type = LPCWSTR * argvn.value
183 183 result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))]
184 184 retval = LocalFree(result_pointer)
185 185 return result
186 186 except AttributeError:
187 187 arg_split = py_arg_split
@@ -1,577 +1,577 b''
1 1 """Windows-specific implementation of process utilities with direct WinAPI.
2 2
3 3 This file is meant to be used by process.py
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2010-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 from __future__ import print_function
14 14
15 15 # stdlib
16 16 import os, sys, threading
17 17 import ctypes, msvcrt
18 18
19 19 # local imports
20 from .py3compat import unicode_type
20 from . import py3compat
21 21
22 22 # Win32 API types needed for the API calls
23 23 from ctypes import POINTER
24 24 from ctypes.wintypes import HANDLE, HLOCAL, LPVOID, WORD, DWORD, BOOL, \
25 25 ULONG, LPCWSTR
26 26 LPDWORD = POINTER(DWORD)
27 27 LPHANDLE = POINTER(HANDLE)
28 28 ULONG_PTR = POINTER(ULONG)
29 29 class SECURITY_ATTRIBUTES(ctypes.Structure):
30 30 _fields_ = [("nLength", DWORD),
31 31 ("lpSecurityDescriptor", LPVOID),
32 32 ("bInheritHandle", BOOL)]
33 33 LPSECURITY_ATTRIBUTES = POINTER(SECURITY_ATTRIBUTES)
34 34 class STARTUPINFO(ctypes.Structure):
35 35 _fields_ = [("cb", DWORD),
36 36 ("lpReserved", LPCWSTR),
37 37 ("lpDesktop", LPCWSTR),
38 38 ("lpTitle", LPCWSTR),
39 39 ("dwX", DWORD),
40 40 ("dwY", DWORD),
41 41 ("dwXSize", DWORD),
42 42 ("dwYSize", DWORD),
43 43 ("dwXCountChars", DWORD),
44 44 ("dwYCountChars", DWORD),
45 45 ("dwFillAttribute", DWORD),
46 46 ("dwFlags", DWORD),
47 47 ("wShowWindow", WORD),
48 48 ("cbReserved2", WORD),
49 49 ("lpReserved2", LPVOID),
50 50 ("hStdInput", HANDLE),
51 51 ("hStdOutput", HANDLE),
52 52 ("hStdError", HANDLE)]
53 53 LPSTARTUPINFO = POINTER(STARTUPINFO)
54 54 class PROCESS_INFORMATION(ctypes.Structure):
55 55 _fields_ = [("hProcess", HANDLE),
56 56 ("hThread", HANDLE),
57 57 ("dwProcessId", DWORD),
58 58 ("dwThreadId", DWORD)]
59 59 LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION)
60 60
61 61 # Win32 API constants needed
62 62 ERROR_HANDLE_EOF = 38
63 63 ERROR_BROKEN_PIPE = 109
64 64 ERROR_NO_DATA = 232
65 65 HANDLE_FLAG_INHERIT = 0x0001
66 66 STARTF_USESTDHANDLES = 0x0100
67 67 CREATE_SUSPENDED = 0x0004
68 68 CREATE_NEW_CONSOLE = 0x0010
69 69 CREATE_NO_WINDOW = 0x08000000
70 70 STILL_ACTIVE = 259
71 71 WAIT_TIMEOUT = 0x0102
72 72 WAIT_FAILED = 0xFFFFFFFF
73 73 INFINITE = 0xFFFFFFFF
74 74 DUPLICATE_SAME_ACCESS = 0x00000002
75 75 ENABLE_ECHO_INPUT = 0x0004
76 76 ENABLE_LINE_INPUT = 0x0002
77 77 ENABLE_PROCESSED_INPUT = 0x0001
78 78
79 79 # Win32 API functions needed
80 80 GetLastError = ctypes.windll.kernel32.GetLastError
81 81 GetLastError.argtypes = []
82 82 GetLastError.restype = DWORD
83 83
84 84 CreateFile = ctypes.windll.kernel32.CreateFileW
85 85 CreateFile.argtypes = [LPCWSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE]
86 86 CreateFile.restype = HANDLE
87 87
88 88 CreatePipe = ctypes.windll.kernel32.CreatePipe
89 89 CreatePipe.argtypes = [POINTER(HANDLE), POINTER(HANDLE),
90 90 LPSECURITY_ATTRIBUTES, DWORD]
91 91 CreatePipe.restype = BOOL
92 92
93 93 CreateProcess = ctypes.windll.kernel32.CreateProcessW
94 94 CreateProcess.argtypes = [LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES,
95 95 LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFO,
96 96 LPPROCESS_INFORMATION]
97 97 CreateProcess.restype = BOOL
98 98
99 99 GetExitCodeProcess = ctypes.windll.kernel32.GetExitCodeProcess
100 100 GetExitCodeProcess.argtypes = [HANDLE, LPDWORD]
101 101 GetExitCodeProcess.restype = BOOL
102 102
103 103 GetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess
104 104 GetCurrentProcess.argtypes = []
105 105 GetCurrentProcess.restype = HANDLE
106 106
107 107 ResumeThread = ctypes.windll.kernel32.ResumeThread
108 108 ResumeThread.argtypes = [HANDLE]
109 109 ResumeThread.restype = DWORD
110 110
111 111 ReadFile = ctypes.windll.kernel32.ReadFile
112 112 ReadFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID]
113 113 ReadFile.restype = BOOL
114 114
115 115 WriteFile = ctypes.windll.kernel32.WriteFile
116 116 WriteFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPVOID]
117 117 WriteFile.restype = BOOL
118 118
119 119 GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode
120 120 GetConsoleMode.argtypes = [HANDLE, LPDWORD]
121 121 GetConsoleMode.restype = BOOL
122 122
123 123 SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode
124 124 SetConsoleMode.argtypes = [HANDLE, DWORD]
125 125 SetConsoleMode.restype = BOOL
126 126
127 127 FlushConsoleInputBuffer = ctypes.windll.kernel32.FlushConsoleInputBuffer
128 128 FlushConsoleInputBuffer.argtypes = [HANDLE]
129 129 FlushConsoleInputBuffer.restype = BOOL
130 130
131 131 WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject
132 132 WaitForSingleObject.argtypes = [HANDLE, DWORD]
133 133 WaitForSingleObject.restype = DWORD
134 134
135 135 DuplicateHandle = ctypes.windll.kernel32.DuplicateHandle
136 136 DuplicateHandle.argtypes = [HANDLE, HANDLE, HANDLE, LPHANDLE,
137 137 DWORD, BOOL, DWORD]
138 138 DuplicateHandle.restype = BOOL
139 139
140 140 SetHandleInformation = ctypes.windll.kernel32.SetHandleInformation
141 141 SetHandleInformation.argtypes = [HANDLE, DWORD, DWORD]
142 142 SetHandleInformation.restype = BOOL
143 143
144 144 CloseHandle = ctypes.windll.kernel32.CloseHandle
145 145 CloseHandle.argtypes = [HANDLE]
146 146 CloseHandle.restype = BOOL
147 147
148 148 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
149 149 CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(ctypes.c_int)]
150 150 CommandLineToArgvW.restype = POINTER(LPCWSTR)
151 151
152 152 LocalFree = ctypes.windll.kernel32.LocalFree
153 153 LocalFree.argtypes = [HLOCAL]
154 154 LocalFree.restype = HLOCAL
155 155
156 156 class AvoidUNCPath(object):
157 157 """A context manager to protect command execution from UNC paths.
158 158
159 159 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
160 160 This context manager temporarily changes directory to the 'C:' drive on
161 161 entering, and restores the original working directory on exit.
162 162
163 163 The context manager returns the starting working directory *if* it made a
164 164 change and None otherwise, so that users can apply the necessary adjustment
165 165 to their system calls in the event of a change.
166 166
167 167 Examples
168 168 --------
169 169 ::
170 170 cmd = 'dir'
171 171 with AvoidUNCPath() as path:
172 172 if path is not None:
173 173 cmd = '"pushd %s &&"%s' % (path, cmd)
174 174 os.system(cmd)
175 175 """
176 176 def __enter__(self):
177 self.path = os.getcwdu()
177 self.path = py3compat.getcwd()
178 178 self.is_unc_path = self.path.startswith(r"\\")
179 179 if self.is_unc_path:
180 180 # change to c drive (as cmd.exe cannot handle UNC addresses)
181 181 os.chdir("C:")
182 182 return self.path
183 183 else:
184 184 # We return None to signal that there was no change in the working
185 185 # directory
186 186 return None
187 187
188 188 def __exit__(self, exc_type, exc_value, traceback):
189 189 if self.is_unc_path:
190 190 os.chdir(self.path)
191 191
192 192
193 193 class Win32ShellCommandController(object):
194 194 """Runs a shell command in a 'with' context.
195 195
196 196 This implementation is Win32-specific.
197 197
198 198 Example:
199 199 # Runs the command interactively with default console stdin/stdout
200 200 with ShellCommandController('python -i') as scc:
201 201 scc.run()
202 202
203 203 # Runs the command using the provided functions for stdin/stdout
204 204 def my_stdout_func(s):
205 205 # print or save the string 's'
206 206 write_to_stdout(s)
207 207 def my_stdin_func():
208 208 # If input is available, return it as a string.
209 209 if input_available():
210 210 return get_input()
211 211 # If no input available, return None after a short delay to
212 212 # keep from blocking.
213 213 else:
214 214 time.sleep(0.01)
215 215 return None
216 216
217 217 with ShellCommandController('python -i') as scc:
218 218 scc.run(my_stdout_func, my_stdin_func)
219 219 """
220 220
221 221 def __init__(self, cmd, mergeout = True):
222 222 """Initializes the shell command controller.
223 223
224 224 The cmd is the program to execute, and mergeout is
225 225 whether to blend stdout and stderr into one output
226 226 in stdout. Merging them together in this fashion more
227 227 reliably keeps stdout and stderr in the correct order
228 228 especially for interactive shell usage.
229 229 """
230 230 self.cmd = cmd
231 231 self.mergeout = mergeout
232 232
233 233 def __enter__(self):
234 234 cmd = self.cmd
235 235 mergeout = self.mergeout
236 236
237 237 self.hstdout, self.hstdin, self.hstderr = None, None, None
238 238 self.piProcInfo = None
239 239 try:
240 240 p_hstdout, c_hstdout, p_hstderr, \
241 241 c_hstderr, p_hstdin, c_hstdin = [None]*6
242 242
243 243 # SECURITY_ATTRIBUTES with inherit handle set to True
244 244 saAttr = SECURITY_ATTRIBUTES()
245 245 saAttr.nLength = ctypes.sizeof(saAttr)
246 246 saAttr.bInheritHandle = True
247 247 saAttr.lpSecurityDescriptor = None
248 248
249 249 def create_pipe(uninherit):
250 250 """Creates a Windows pipe, which consists of two handles.
251 251
252 252 The 'uninherit' parameter controls which handle is not
253 253 inherited by the child process.
254 254 """
255 255 handles = HANDLE(), HANDLE()
256 256 if not CreatePipe(ctypes.byref(handles[0]),
257 257 ctypes.byref(handles[1]), ctypes.byref(saAttr), 0):
258 258 raise ctypes.WinError()
259 259 if not SetHandleInformation(handles[uninherit],
260 260 HANDLE_FLAG_INHERIT, 0):
261 261 raise ctypes.WinError()
262 262 return handles[0].value, handles[1].value
263 263
264 264 p_hstdout, c_hstdout = create_pipe(uninherit=0)
265 265 # 'mergeout' signals that stdout and stderr should be merged.
266 266 # We do that by using one pipe for both of them.
267 267 if mergeout:
268 268 c_hstderr = HANDLE()
269 269 if not DuplicateHandle(GetCurrentProcess(), c_hstdout,
270 270 GetCurrentProcess(), ctypes.byref(c_hstderr),
271 271 0, True, DUPLICATE_SAME_ACCESS):
272 272 raise ctypes.WinError()
273 273 else:
274 274 p_hstderr, c_hstderr = create_pipe(uninherit=0)
275 275 c_hstdin, p_hstdin = create_pipe(uninherit=1)
276 276
277 277 # Create the process object
278 278 piProcInfo = PROCESS_INFORMATION()
279 279 siStartInfo = STARTUPINFO()
280 280 siStartInfo.cb = ctypes.sizeof(siStartInfo)
281 281 siStartInfo.hStdInput = c_hstdin
282 282 siStartInfo.hStdOutput = c_hstdout
283 283 siStartInfo.hStdError = c_hstderr
284 284 siStartInfo.dwFlags = STARTF_USESTDHANDLES
285 285 dwCreationFlags = CREATE_SUSPENDED | CREATE_NO_WINDOW # | CREATE_NEW_CONSOLE
286 286
287 287 if not CreateProcess(None,
288 288 u"cmd.exe /c " + cmd,
289 289 None, None, True, dwCreationFlags,
290 290 None, None, ctypes.byref(siStartInfo),
291 291 ctypes.byref(piProcInfo)):
292 292 raise ctypes.WinError()
293 293
294 294 # Close this process's versions of the child handles
295 295 CloseHandle(c_hstdin)
296 296 c_hstdin = None
297 297 CloseHandle(c_hstdout)
298 298 c_hstdout = None
299 299 if c_hstderr != None:
300 300 CloseHandle(c_hstderr)
301 301 c_hstderr = None
302 302
303 303 # Transfer ownership of the parent handles to the object
304 304 self.hstdin = p_hstdin
305 305 p_hstdin = None
306 306 self.hstdout = p_hstdout
307 307 p_hstdout = None
308 308 if not mergeout:
309 309 self.hstderr = p_hstderr
310 310 p_hstderr = None
311 311 self.piProcInfo = piProcInfo
312 312
313 313 finally:
314 314 if p_hstdin:
315 315 CloseHandle(p_hstdin)
316 316 if c_hstdin:
317 317 CloseHandle(c_hstdin)
318 318 if p_hstdout:
319 319 CloseHandle(p_hstdout)
320 320 if c_hstdout:
321 321 CloseHandle(c_hstdout)
322 322 if p_hstderr:
323 323 CloseHandle(p_hstderr)
324 324 if c_hstderr:
325 325 CloseHandle(c_hstderr)
326 326
327 327 return self
328 328
329 329 def _stdin_thread(self, handle, hprocess, func, stdout_func):
330 330 exitCode = DWORD()
331 331 bytesWritten = DWORD(0)
332 332 while True:
333 333 #print("stdin thread loop start")
334 334 # Get the input string (may be bytes or unicode)
335 335 data = func()
336 336
337 337 # None signals to poll whether the process has exited
338 338 if data is None:
339 339 #print("checking for process completion")
340 340 if not GetExitCodeProcess(hprocess, ctypes.byref(exitCode)):
341 341 raise ctypes.WinError()
342 342 if exitCode.value != STILL_ACTIVE:
343 343 return
344 344 # TESTING: Does zero-sized writefile help?
345 345 if not WriteFile(handle, "", 0,
346 346 ctypes.byref(bytesWritten), None):
347 347 raise ctypes.WinError()
348 348 continue
349 349 #print("\nGot str %s\n" % repr(data), file=sys.stderr)
350 350
351 351 # Encode the string to the console encoding
352 352 if isinstance(data, unicode): #FIXME: Python3
353 353 data = data.encode('utf_8')
354 354
355 355 # What we have now must be a string of bytes
356 356 if not isinstance(data, str): #FIXME: Python3
357 357 raise RuntimeError("internal stdin function string error")
358 358
359 359 # An empty string signals EOF
360 360 if len(data) == 0:
361 361 return
362 362
363 363 # In a windows console, sometimes the input is echoed,
364 364 # but sometimes not. How do we determine when to do this?
365 365 stdout_func(data)
366 366 # WriteFile may not accept all the data at once.
367 367 # Loop until everything is processed
368 368 while len(data) != 0:
369 369 #print("Calling writefile")
370 370 if not WriteFile(handle, data, len(data),
371 371 ctypes.byref(bytesWritten), None):
372 372 # This occurs at exit
373 373 if GetLastError() == ERROR_NO_DATA:
374 374 return
375 375 raise ctypes.WinError()
376 376 #print("Called writefile")
377 377 data = data[bytesWritten.value:]
378 378
379 379 def _stdout_thread(self, handle, func):
380 380 # Allocate the output buffer
381 381 data = ctypes.create_string_buffer(4096)
382 382 while True:
383 383 bytesRead = DWORD(0)
384 384 if not ReadFile(handle, data, 4096,
385 385 ctypes.byref(bytesRead), None):
386 386 le = GetLastError()
387 387 if le == ERROR_BROKEN_PIPE:
388 388 return
389 389 else:
390 390 raise ctypes.WinError()
391 391 # FIXME: Python3
392 392 s = data.value[0:bytesRead.value]
393 393 #print("\nv: %s" % repr(s), file=sys.stderr)
394 394 func(s.decode('utf_8', 'replace'))
395 395
396 396 def run(self, stdout_func = None, stdin_func = None, stderr_func = None):
397 397 """Runs the process, using the provided functions for I/O.
398 398
399 399 The function stdin_func should return strings whenever a
400 400 character or characters become available.
401 401 The functions stdout_func and stderr_func are called whenever
402 402 something is printed to stdout or stderr, respectively.
403 403 These functions are called from different threads (but not
404 404 concurrently, because of the GIL).
405 405 """
406 406 if stdout_func == None and stdin_func == None and stderr_func == None:
407 407 return self._run_stdio()
408 408
409 409 if stderr_func != None and self.mergeout:
410 410 raise RuntimeError("Shell command was initiated with "
411 411 "merged stdin/stdout, but a separate stderr_func "
412 412 "was provided to the run() method")
413 413
414 414 # Create a thread for each input/output handle
415 415 stdin_thread = None
416 416 threads = []
417 417 if stdin_func:
418 418 stdin_thread = threading.Thread(target=self._stdin_thread,
419 419 args=(self.hstdin, self.piProcInfo.hProcess,
420 420 stdin_func, stdout_func))
421 421 threads.append(threading.Thread(target=self._stdout_thread,
422 422 args=(self.hstdout, stdout_func)))
423 423 if not self.mergeout:
424 424 if stderr_func == None:
425 425 stderr_func = stdout_func
426 426 threads.append(threading.Thread(target=self._stdout_thread,
427 427 args=(self.hstderr, stderr_func)))
428 428 # Start the I/O threads and the process
429 429 if ResumeThread(self.piProcInfo.hThread) == 0xFFFFFFFF:
430 430 raise ctypes.WinError()
431 431 if stdin_thread is not None:
432 432 stdin_thread.start()
433 433 for thread in threads:
434 434 thread.start()
435 435 # Wait for the process to complete
436 436 if WaitForSingleObject(self.piProcInfo.hProcess, INFINITE) == \
437 437 WAIT_FAILED:
438 438 raise ctypes.WinError()
439 439 # Wait for the I/O threads to complete
440 440 for thread in threads:
441 441 thread.join()
442 442
443 443 # Wait for the stdin thread to complete
444 444 if stdin_thread is not None:
445 445 stdin_thread.join()
446 446
447 447 def _stdin_raw_nonblock(self):
448 448 """Use the raw Win32 handle of sys.stdin to do non-blocking reads"""
449 449 # WARNING: This is experimental, and produces inconsistent results.
450 450 # It's possible for the handle not to be appropriate for use
451 451 # with WaitForSingleObject, among other things.
452 452 handle = msvcrt.get_osfhandle(sys.stdin.fileno())
453 453 result = WaitForSingleObject(handle, 100)
454 454 if result == WAIT_FAILED:
455 455 raise ctypes.WinError()
456 456 elif result == WAIT_TIMEOUT:
457 457 print(".", end='')
458 458 return None
459 459 else:
460 460 data = ctypes.create_string_buffer(256)
461 461 bytesRead = DWORD(0)
462 462 print('?', end='')
463 463
464 464 if not ReadFile(handle, data, 256,
465 465 ctypes.byref(bytesRead), None):
466 466 raise ctypes.WinError()
467 467 # This ensures the non-blocking works with an actual console
468 468 # Not checking the error, so the processing will still work with
469 469 # other handle types
470 470 FlushConsoleInputBuffer(handle)
471 471
472 472 data = data.value
473 473 data = data.replace('\r\n', '\n')
474 474 data = data.replace('\r', '\n')
475 475 print(repr(data) + " ", end='')
476 476 return data
477 477
478 478 def _stdin_raw_block(self):
479 479 """Use a blocking stdin read"""
480 480 # The big problem with the blocking read is that it doesn't
481 481 # exit when it's supposed to in all contexts. An extra
482 482 # key-press may be required to trigger the exit.
483 483 try:
484 484 data = sys.stdin.read(1)
485 485 data = data.replace('\r', '\n')
486 486 return data
487 487 except WindowsError as we:
488 488 if we.winerror == ERROR_NO_DATA:
489 489 # This error occurs when the pipe is closed
490 490 return None
491 491 else:
492 492 # Otherwise let the error propagate
493 493 raise we
494 494
495 495 def _stdout_raw(self, s):
496 496 """Writes the string to stdout"""
497 497 print(s, end='', file=sys.stdout)
498 498 sys.stdout.flush()
499 499
500 500 def _stderr_raw(self, s):
501 501 """Writes the string to stdout"""
502 502 print(s, end='', file=sys.stderr)
503 503 sys.stderr.flush()
504 504
505 505 def _run_stdio(self):
506 506 """Runs the process using the system standard I/O.
507 507
508 508 IMPORTANT: stdin needs to be asynchronous, so the Python
509 509 sys.stdin object is not used. Instead,
510 510 msvcrt.kbhit/getwch are used asynchronously.
511 511 """
512 512 # Disable Line and Echo mode
513 513 #lpMode = DWORD()
514 514 #handle = msvcrt.get_osfhandle(sys.stdin.fileno())
515 515 #if GetConsoleMode(handle, ctypes.byref(lpMode)):
516 516 # set_console_mode = True
517 517 # if not SetConsoleMode(handle, lpMode.value &
518 518 # ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)):
519 519 # raise ctypes.WinError()
520 520
521 521 if self.mergeout:
522 522 return self.run(stdout_func = self._stdout_raw,
523 523 stdin_func = self._stdin_raw_block)
524 524 else:
525 525 return self.run(stdout_func = self._stdout_raw,
526 526 stdin_func = self._stdin_raw_block,
527 527 stderr_func = self._stderr_raw)
528 528
529 529 # Restore the previous console mode
530 530 #if set_console_mode:
531 531 # if not SetConsoleMode(handle, lpMode.value):
532 532 # raise ctypes.WinError()
533 533
534 534 def __exit__(self, exc_type, exc_value, traceback):
535 535 if self.hstdin:
536 536 CloseHandle(self.hstdin)
537 537 self.hstdin = None
538 538 if self.hstdout:
539 539 CloseHandle(self.hstdout)
540 540 self.hstdout = None
541 541 if self.hstderr:
542 542 CloseHandle(self.hstderr)
543 543 self.hstderr = None
544 544 if self.piProcInfo != None:
545 545 CloseHandle(self.piProcInfo.hProcess)
546 546 CloseHandle(self.piProcInfo.hThread)
547 547 self.piProcInfo = None
548 548
549 549
550 550 def system(cmd):
551 551 """Win32 version of os.system() that works with network shares.
552 552
553 553 Note that this implementation returns None, as meant for use in IPython.
554 554
555 555 Parameters
556 556 ----------
557 557 cmd : str
558 558 A command to be executed in the system shell.
559 559
560 560 Returns
561 561 -------
562 562 None : we explicitly do NOT return the subprocess status code, as this
563 563 utility is meant to be used extensively in IPython, where any return value
564 564 would trigger :func:`sys.displayhook` calls.
565 565 """
566 566 with AvoidUNCPath() as path:
567 567 if path is not None:
568 568 cmd = '"pushd %s &&"%s' % (path, cmd)
569 569 with Win32ShellCommandController(cmd) as scc:
570 570 scc.run()
571 571
572 572
573 573 if __name__ == "__main__":
574 574 print("Test starting!")
575 575 #system("cmd")
576 576 system("python -i")
577 577 print("Test finished!")
@@ -1,574 +1,574 b''
1 1 # encoding: utf-8
2 2 """
3 3 Utilities for path handling.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2008-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 import os
18 18 import sys
19 19 import errno
20 20 import shutil
21 21 import random
22 22 import tempfile
23 23 import warnings
24 24 from hashlib import md5
25 25 import glob
26 26
27 27 import IPython
28 28 from IPython.testing.skipdoctest import skip_doctest
29 29 from IPython.utils.process import system
30 30 from IPython.utils.importstring import import_item
31 31 from IPython.utils import py3compat
32 32 #-----------------------------------------------------------------------------
33 33 # Code
34 34 #-----------------------------------------------------------------------------
35 35
36 36 fs_encoding = sys.getfilesystemencoding()
37 37
38 38 def _get_long_path_name(path):
39 39 """Dummy no-op."""
40 40 return path
41 41
42 42 def _writable_dir(path):
43 43 """Whether `path` is a directory, to which the user has write access."""
44 44 return os.path.isdir(path) and os.access(path, os.W_OK)
45 45
46 46 if sys.platform == 'win32':
47 47 @skip_doctest
48 48 def _get_long_path_name(path):
49 49 """Get a long path name (expand ~) on Windows using ctypes.
50 50
51 51 Examples
52 52 --------
53 53
54 54 >>> get_long_path_name('c:\\docume~1')
55 55 u'c:\\\\Documents and Settings'
56 56
57 57 """
58 58 try:
59 59 import ctypes
60 60 except ImportError:
61 61 raise ImportError('you need to have ctypes installed for this to work')
62 62 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
63 63 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
64 64 ctypes.c_uint ]
65 65
66 66 buf = ctypes.create_unicode_buffer(260)
67 67 rv = _GetLongPathName(path, buf, 260)
68 68 if rv == 0 or rv > 260:
69 69 return path
70 70 else:
71 71 return buf.value
72 72
73 73
74 74 def get_long_path_name(path):
75 75 """Expand a path into its long form.
76 76
77 77 On Windows this expands any ~ in the paths. On other platforms, it is
78 78 a null operation.
79 79 """
80 80 return _get_long_path_name(path)
81 81
82 82
83 83 def unquote_filename(name, win32=(sys.platform=='win32')):
84 84 """ On Windows, remove leading and trailing quotes from filenames.
85 85 """
86 86 if win32:
87 87 if name.startswith(("'", '"')) and name.endswith(("'", '"')):
88 88 name = name[1:-1]
89 89 return name
90 90
91 91 def compress_user(path):
92 92 """Reverse of :func:`os.path.expanduser`
93 93 """
94 94 home = os.path.expanduser('~')
95 95 if path.startswith(home):
96 96 path = "~" + path[len(home):]
97 97 return path
98 98
99 99 def get_py_filename(name, force_win32=None):
100 100 """Return a valid python filename in the current directory.
101 101
102 102 If the given name is not a file, it adds '.py' and searches again.
103 103 Raises IOError with an informative message if the file isn't found.
104 104
105 105 On Windows, apply Windows semantics to the filename. In particular, remove
106 106 any quoting that has been applied to it. This option can be forced for
107 107 testing purposes.
108 108 """
109 109
110 110 name = os.path.expanduser(name)
111 111 if force_win32 is None:
112 112 win32 = (sys.platform == 'win32')
113 113 else:
114 114 win32 = force_win32
115 115 name = unquote_filename(name, win32=win32)
116 116 if not os.path.isfile(name) and not name.endswith('.py'):
117 117 name += '.py'
118 118 if os.path.isfile(name):
119 119 return name
120 120 else:
121 121 raise IOError('File `%r` not found.' % name)
122 122
123 123
124 124 def filefind(filename, path_dirs=None):
125 125 """Find a file by looking through a sequence of paths.
126 126
127 127 This iterates through a sequence of paths looking for a file and returns
128 128 the full, absolute path of the first occurence of the file. If no set of
129 129 path dirs is given, the filename is tested as is, after running through
130 130 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
131 131
132 132 filefind('myfile.txt')
133 133
134 134 will find the file in the current working dir, but::
135 135
136 136 filefind('~/myfile.txt')
137 137
138 138 Will find the file in the users home directory. This function does not
139 139 automatically try any paths, such as the cwd or the user's home directory.
140 140
141 141 Parameters
142 142 ----------
143 143 filename : str
144 144 The filename to look for.
145 145 path_dirs : str, None or sequence of str
146 146 The sequence of paths to look for the file in. If None, the filename
147 147 need to be absolute or be in the cwd. If a string, the string is
148 148 put into a sequence and the searched. If a sequence, walk through
149 149 each element and join with ``filename``, calling :func:`expandvars`
150 150 and :func:`expanduser` before testing for existence.
151 151
152 152 Returns
153 153 -------
154 154 Raises :exc:`IOError` or returns absolute path to file.
155 155 """
156 156
157 157 # If paths are quoted, abspath gets confused, strip them...
158 158 filename = filename.strip('"').strip("'")
159 159 # If the input is an absolute path, just check it exists
160 160 if os.path.isabs(filename) and os.path.isfile(filename):
161 161 return filename
162 162
163 163 if path_dirs is None:
164 164 path_dirs = ("",)
165 165 elif isinstance(path_dirs, py3compat.string_types):
166 166 path_dirs = (path_dirs,)
167 167
168 168 for path in path_dirs:
169 if path == '.': path = os.getcwdu()
169 if path == '.': path = py3compat.getcwd()
170 170 testname = expand_path(os.path.join(path, filename))
171 171 if os.path.isfile(testname):
172 172 return os.path.abspath(testname)
173 173
174 174 raise IOError("File %r does not exist in any of the search paths: %r" %
175 175 (filename, path_dirs) )
176 176
177 177
178 178 class HomeDirError(Exception):
179 179 pass
180 180
181 181
182 182 def get_home_dir(require_writable=False):
183 183 """Return the 'home' directory, as a unicode string.
184 184
185 185 Uses os.path.expanduser('~'), and checks for writability.
186 186
187 187 See stdlib docs for how this is determined.
188 188 $HOME is first priority on *ALL* platforms.
189 189
190 190 Parameters
191 191 ----------
192 192
193 193 require_writable : bool [default: False]
194 194 if True:
195 195 guarantees the return value is a writable directory, otherwise
196 196 raises HomeDirError
197 197 if False:
198 198 The path is resolved, but it is not guaranteed to exist or be writable.
199 199 """
200 200
201 201 homedir = os.path.expanduser('~')
202 202 # Next line will make things work even when /home/ is a symlink to
203 203 # /usr/home as it is on FreeBSD, for example
204 204 homedir = os.path.realpath(homedir)
205 205
206 206 if not _writable_dir(homedir) and os.name == 'nt':
207 207 # expanduser failed, use the registry to get the 'My Documents' folder.
208 208 try:
209 209 try:
210 210 import winreg as wreg # Py 3
211 211 except ImportError:
212 212 import _winreg as wreg # Py 2
213 213 key = wreg.OpenKey(
214 214 wreg.HKEY_CURRENT_USER,
215 215 "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
216 216 )
217 217 homedir = wreg.QueryValueEx(key,'Personal')[0]
218 218 key.Close()
219 219 except:
220 220 pass
221 221
222 222 if (not require_writable) or _writable_dir(homedir):
223 223 return py3compat.cast_unicode(homedir, fs_encoding)
224 224 else:
225 225 raise HomeDirError('%s is not a writable dir, '
226 226 'set $HOME environment variable to override' % homedir)
227 227
228 228 def get_xdg_dir():
229 229 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
230 230
231 231 This is only for non-OS X posix (Linux,Unix,etc.) systems.
232 232 """
233 233
234 234 env = os.environ
235 235
236 236 if os.name == 'posix' and sys.platform != 'darwin':
237 237 # Linux, Unix, AIX, etc.
238 238 # use ~/.config if empty OR not set
239 239 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
240 240 if xdg and _writable_dir(xdg):
241 241 return py3compat.cast_unicode(xdg, fs_encoding)
242 242
243 243 return None
244 244
245 245
246 246 def get_xdg_cache_dir():
247 247 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
248 248
249 249 This is only for non-OS X posix (Linux,Unix,etc.) systems.
250 250 """
251 251
252 252 env = os.environ
253 253
254 254 if os.name == 'posix' and sys.platform != 'darwin':
255 255 # Linux, Unix, AIX, etc.
256 256 # use ~/.cache if empty OR not set
257 257 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
258 258 if xdg and _writable_dir(xdg):
259 259 return py3compat.cast_unicode(xdg, fs_encoding)
260 260
261 261 return None
262 262
263 263
264 264 def get_ipython_dir():
265 265 """Get the IPython directory for this platform and user.
266 266
267 267 This uses the logic in `get_home_dir` to find the home directory
268 268 and then adds .ipython to the end of the path.
269 269 """
270 270
271 271 env = os.environ
272 272 pjoin = os.path.join
273 273
274 274
275 275 ipdir_def = '.ipython'
276 276 xdg_def = 'ipython'
277 277
278 278 home_dir = get_home_dir()
279 279 xdg_dir = get_xdg_dir()
280 280
281 281 # import pdb; pdb.set_trace() # dbg
282 282 if 'IPYTHON_DIR' in env:
283 283 warnings.warn('The environment variable IPYTHON_DIR is deprecated. '
284 284 'Please use IPYTHONDIR instead.')
285 285 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
286 286 if ipdir is None:
287 287 # not set explicitly, use XDG_CONFIG_HOME or HOME
288 288 home_ipdir = pjoin(home_dir, ipdir_def)
289 289 if xdg_dir:
290 290 # use XDG, as long as the user isn't already
291 291 # using $HOME/.ipython and *not* XDG/ipython
292 292
293 293 xdg_ipdir = pjoin(xdg_dir, xdg_def)
294 294
295 295 if _writable_dir(xdg_ipdir) or not _writable_dir(home_ipdir):
296 296 ipdir = xdg_ipdir
297 297
298 298 if ipdir is None:
299 299 # not using XDG
300 300 ipdir = home_ipdir
301 301
302 302 ipdir = os.path.normpath(os.path.expanduser(ipdir))
303 303
304 304 if os.path.exists(ipdir) and not _writable_dir(ipdir):
305 305 # ipdir exists, but is not writable
306 306 warnings.warn("IPython dir '%s' is not a writable location,"
307 307 " using a temp directory."%ipdir)
308 308 ipdir = tempfile.mkdtemp()
309 309 elif not os.path.exists(ipdir):
310 310 parent = os.path.dirname(ipdir)
311 311 if not _writable_dir(parent):
312 312 # ipdir does not exist and parent isn't writable
313 313 warnings.warn("IPython parent '%s' is not a writable location,"
314 314 " using a temp directory."%parent)
315 315 ipdir = tempfile.mkdtemp()
316 316
317 317 return py3compat.cast_unicode(ipdir, fs_encoding)
318 318
319 319
320 320 def get_ipython_cache_dir():
321 321 """Get the cache directory it is created if it does not exist."""
322 322 xdgdir = get_xdg_cache_dir()
323 323 if xdgdir is None:
324 324 return get_ipython_dir()
325 325 ipdir = os.path.join(xdgdir, "ipython")
326 326 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
327 327 os.makedirs(ipdir)
328 328 elif not _writable_dir(xdgdir):
329 329 return get_ipython_dir()
330 330
331 331 return py3compat.cast_unicode(ipdir, fs_encoding)
332 332
333 333
334 334 def get_ipython_package_dir():
335 335 """Get the base directory where IPython itself is installed."""
336 336 ipdir = os.path.dirname(IPython.__file__)
337 337 return py3compat.cast_unicode(ipdir, fs_encoding)
338 338
339 339
340 340 def get_ipython_module_path(module_str):
341 341 """Find the path to an IPython module in this version of IPython.
342 342
343 343 This will always find the version of the module that is in this importable
344 344 IPython package. This will always return the path to the ``.py``
345 345 version of the module.
346 346 """
347 347 if module_str == 'IPython':
348 348 return os.path.join(get_ipython_package_dir(), '__init__.py')
349 349 mod = import_item(module_str)
350 350 the_path = mod.__file__.replace('.pyc', '.py')
351 351 the_path = the_path.replace('.pyo', '.py')
352 352 return py3compat.cast_unicode(the_path, fs_encoding)
353 353
354 354 def locate_profile(profile='default'):
355 355 """Find the path to the folder associated with a given profile.
356 356
357 357 I.e. find $IPYTHONDIR/profile_whatever.
358 358 """
359 359 from IPython.core.profiledir import ProfileDir, ProfileDirError
360 360 try:
361 361 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
362 362 except ProfileDirError:
363 363 # IOError makes more sense when people are expecting a path
364 364 raise IOError("Couldn't find profile %r" % profile)
365 365 return pd.location
366 366
367 367 def expand_path(s):
368 368 """Expand $VARS and ~names in a string, like a shell
369 369
370 370 :Examples:
371 371
372 372 In [2]: os.environ['FOO']='test'
373 373
374 374 In [3]: expand_path('variable FOO is $FOO')
375 375 Out[3]: 'variable FOO is test'
376 376 """
377 377 # This is a pretty subtle hack. When expand user is given a UNC path
378 378 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
379 379 # the $ to get (\\server\share\%username%). I think it considered $
380 380 # alone an empty var. But, we need the $ to remains there (it indicates
381 381 # a hidden share).
382 382 if os.name=='nt':
383 383 s = s.replace('$\\', 'IPYTHON_TEMP')
384 384 s = os.path.expandvars(os.path.expanduser(s))
385 385 if os.name=='nt':
386 386 s = s.replace('IPYTHON_TEMP', '$\\')
387 387 return s
388 388
389 389
390 390 def unescape_glob(string):
391 391 """Unescape glob pattern in `string`."""
392 392 def unescape(s):
393 393 for pattern in '*[]!?':
394 394 s = s.replace(r'\{0}'.format(pattern), pattern)
395 395 return s
396 396 return '\\'.join(map(unescape, string.split('\\\\')))
397 397
398 398
399 399 def shellglob(args):
400 400 """
401 401 Do glob expansion for each element in `args` and return a flattened list.
402 402
403 403 Unmatched glob pattern will remain as-is in the returned list.
404 404
405 405 """
406 406 expanded = []
407 407 # Do not unescape backslash in Windows as it is interpreted as
408 408 # path separator:
409 409 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
410 410 for a in args:
411 411 expanded.extend(glob.glob(a) or [unescape(a)])
412 412 return expanded
413 413
414 414
415 415 def target_outdated(target,deps):
416 416 """Determine whether a target is out of date.
417 417
418 418 target_outdated(target,deps) -> 1/0
419 419
420 420 deps: list of filenames which MUST exist.
421 421 target: single filename which may or may not exist.
422 422
423 423 If target doesn't exist or is older than any file listed in deps, return
424 424 true, otherwise return false.
425 425 """
426 426 try:
427 427 target_time = os.path.getmtime(target)
428 428 except os.error:
429 429 return 1
430 430 for dep in deps:
431 431 dep_time = os.path.getmtime(dep)
432 432 if dep_time > target_time:
433 433 #print "For target",target,"Dep failed:",dep # dbg
434 434 #print "times (dep,tar):",dep_time,target_time # dbg
435 435 return 1
436 436 return 0
437 437
438 438
439 439 def target_update(target,deps,cmd):
440 440 """Update a target with a given command given a list of dependencies.
441 441
442 442 target_update(target,deps,cmd) -> runs cmd if target is outdated.
443 443
444 444 This is just a wrapper around target_outdated() which calls the given
445 445 command if target is outdated."""
446 446
447 447 if target_outdated(target,deps):
448 448 system(cmd)
449 449
450 450 def filehash(path):
451 451 """Make an MD5 hash of a file, ignoring any differences in line
452 452 ending characters."""
453 453 with open(path, "rU") as f:
454 454 return md5(py3compat.str_to_bytes(f.read())).hexdigest()
455 455
456 456 # If the config is unmodified from the default, we'll just delete it.
457 457 # These are consistent for 0.10.x, thankfully. We're not going to worry about
458 458 # older versions.
459 459 old_config_md5 = {'ipy_user_conf.py': 'fc108bedff4b9a00f91fa0a5999140d3',
460 460 'ipythonrc': '12a68954f3403eea2eec09dc8fe5a9b5'}
461 461
462 462 def check_for_old_config(ipython_dir=None):
463 463 """Check for old config files, and present a warning if they exist.
464 464
465 465 A link to the docs of the new config is included in the message.
466 466
467 467 This should mitigate confusion with the transition to the new
468 468 config system in 0.11.
469 469 """
470 470 if ipython_dir is None:
471 471 ipython_dir = get_ipython_dir()
472 472
473 473 old_configs = ['ipy_user_conf.py', 'ipythonrc', 'ipython_config.py']
474 474 warned = False
475 475 for cfg in old_configs:
476 476 f = os.path.join(ipython_dir, cfg)
477 477 if os.path.exists(f):
478 478 if filehash(f) == old_config_md5.get(cfg, ''):
479 479 os.unlink(f)
480 480 else:
481 481 warnings.warn("Found old IPython config file %r (modified by user)"%f)
482 482 warned = True
483 483
484 484 if warned:
485 485 warnings.warn("""
486 486 The IPython configuration system has changed as of 0.11, and these files will
487 487 be ignored. See http://ipython.github.com/ipython-doc/dev/config for details
488 488 of the new config system.
489 489 To start configuring IPython, do `ipython profile create`, and edit
490 490 `ipython_config.py` in <ipython_dir>/profile_default.
491 491 If you need to leave the old config files in place for an older version of
492 492 IPython and want to suppress this warning message, set
493 493 `c.InteractiveShellApp.ignore_old_config=True` in the new config.""")
494 494
495 495 def get_security_file(filename, profile='default'):
496 496 """Return the absolute path of a security file given by filename and profile
497 497
498 498 This allows users and developers to find security files without
499 499 knowledge of the IPython directory structure. The search path
500 500 will be ['.', profile.security_dir]
501 501
502 502 Parameters
503 503 ----------
504 504
505 505 filename : str
506 506 The file to be found. If it is passed as an absolute path, it will
507 507 simply be returned.
508 508 profile : str [default: 'default']
509 509 The name of the profile to search. Leaving this unspecified
510 510 The file to be found. If it is passed as an absolute path, fname will
511 511 simply be returned.
512 512
513 513 Returns
514 514 -------
515 515 Raises :exc:`IOError` if file not found or returns absolute path to file.
516 516 """
517 517 # import here, because profiledir also imports from utils.path
518 518 from IPython.core.profiledir import ProfileDir
519 519 try:
520 520 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
521 521 except Exception:
522 522 # will raise ProfileDirError if no such profile
523 523 raise IOError("Profile %r not found")
524 524 return filefind(filename, ['.', pd.security_dir])
525 525
526 526
527 527 ENOLINK = 1998
528 528
529 529 def link(src, dst):
530 530 """Hard links ``src`` to ``dst``, returning 0 or errno.
531 531
532 532 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
533 533 supported by the operating system.
534 534 """
535 535
536 536 if not hasattr(os, "link"):
537 537 return ENOLINK
538 538 link_errno = 0
539 539 try:
540 540 os.link(src, dst)
541 541 except OSError as e:
542 542 link_errno = e.errno
543 543 return link_errno
544 544
545 545
546 546 def link_or_copy(src, dst):
547 547 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
548 548
549 549 Attempts to maintain the semantics of ``shutil.copy``.
550 550
551 551 Because ``os.link`` does not overwrite files, a unique temporary file
552 552 will be used if the target already exists, then that file will be moved
553 553 into place.
554 554 """
555 555
556 556 if os.path.isdir(dst):
557 557 dst = os.path.join(dst, os.path.basename(src))
558 558
559 559 link_errno = link(src, dst)
560 560 if link_errno == errno.EEXIST:
561 561 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
562 562 try:
563 563 link_or_copy(src, new_dst)
564 564 except:
565 565 try:
566 566 os.remove(new_dst)
567 567 except OSError:
568 568 pass
569 569 raise
570 570 os.rename(new_dst, dst)
571 571 elif link_errno != 0:
572 572 # Either link isn't supported, or the filesystem doesn't support
573 573 # linking, or 'src' and 'dst' are on different filesystems.
574 574 shutil.copy(src, dst)
@@ -1,122 +1,123 b''
1 1 # encoding: utf-8
2 2 """
3 3 Utilities for working with external processes.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2008-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # Stdlib
19 19 import os
20 20 import sys
21 21 import shlex
22 22
23 23 # Our own
24 24 if sys.platform == 'win32':
25 25 from ._process_win32 import _find_cmd, system, getoutput, AvoidUNCPath, arg_split
26 26 else:
27 27 from ._process_posix import _find_cmd, system, getoutput, arg_split
28 28
29 29
30 30 from ._process_common import getoutputerror, get_output_error_code
31 from . import py3compat
31 32
32 33 #-----------------------------------------------------------------------------
33 34 # Code
34 35 #-----------------------------------------------------------------------------
35 36
36 37
37 38 class FindCmdError(Exception):
38 39 pass
39 40
40 41
41 42 def find_cmd(cmd):
42 43 """Find absolute path to executable cmd in a cross platform manner.
43 44
44 45 This function tries to determine the full path to a command line program
45 46 using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the
46 47 time it will use the version that is first on the users `PATH`.
47 48
48 49 Warning, don't use this to find IPython command line programs as there
49 50 is a risk you will find the wrong one. Instead find those using the
50 51 following code and looking for the application itself::
51 52
52 53 from IPython.utils.path import get_ipython_module_path
53 54 from IPython.utils.process import pycmd2argv
54 55 argv = pycmd2argv(get_ipython_module_path('IPython.terminal.ipapp'))
55 56
56 57 Parameters
57 58 ----------
58 59 cmd : str
59 60 The command line program to look for.
60 61 """
61 62 try:
62 63 path = _find_cmd(cmd).rstrip()
63 64 except OSError:
64 65 raise FindCmdError('command could not be found: %s' % cmd)
65 66 # which returns empty if not found
66 67 if path == '':
67 68 raise FindCmdError('command could not be found: %s' % cmd)
68 69 return os.path.abspath(path)
69 70
70 71
71 72 def is_cmd_found(cmd):
72 73 """Check whether executable `cmd` exists or not and return a bool."""
73 74 try:
74 75 find_cmd(cmd)
75 76 return True
76 77 except FindCmdError:
77 78 return False
78 79
79 80
80 81 def pycmd2argv(cmd):
81 82 r"""Take the path of a python command and return a list (argv-style).
82 83
83 84 This only works on Python based command line programs and will find the
84 85 location of the ``python`` executable using ``sys.executable`` to make
85 86 sure the right version is used.
86 87
87 88 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
88 89 .com or .bat, and [, cmd] otherwise.
89 90
90 91 Parameters
91 92 ----------
92 93 cmd : string
93 94 The path of the command.
94 95
95 96 Returns
96 97 -------
97 98 argv-style list.
98 99 """
99 100 ext = os.path.splitext(cmd)[1]
100 101 if ext in ['.exe', '.com', '.bat']:
101 102 return [cmd]
102 103 else:
103 104 return [sys.executable, cmd]
104 105
105 106
106 107 def abbrev_cwd():
107 108 """ Return abbreviated version of cwd, e.g. d:mydir """
108 cwd = os.getcwdu().replace('\\','/')
109 cwd = py3compat.getcwd().replace('\\','/')
109 110 drivepart = ''
110 111 tail = cwd
111 112 if sys.platform == 'win32':
112 113 if len(cwd) < 4:
113 114 return cwd
114 115 drivepart,tail = os.path.splitdrive(cwd)
115 116
116 117
117 118 parts = tail.split('/')
118 119 if len(parts) > 2:
119 120 tail = '/'.join(parts[-2:])
120 121
121 122 return (drivepart + (
122 123 cwd == '/' and '/' or tail))
@@ -1,239 +1,242 b''
1 1 # coding: utf-8
2 2 """Compatibility tricks for Python 3. Mainly to do with unicode."""
3 3 import functools
4 import os
4 5 import sys
5 6 import re
6 7 import types
7 8
8 9 from .encoding import DEFAULT_ENCODING
9 10
10 11 orig_open = open
11 12
12 13 def no_code(x, encoding=None):
13 14 return x
14 15
15 16 def decode(s, encoding=None):
16 17 encoding = encoding or DEFAULT_ENCODING
17 18 return s.decode(encoding, "replace")
18 19
19 20 def encode(u, encoding=None):
20 21 encoding = encoding or DEFAULT_ENCODING
21 22 return u.encode(encoding, "replace")
22 23
23 24
24 25 def cast_unicode(s, encoding=None):
25 26 if isinstance(s, bytes):
26 27 return decode(s, encoding)
27 28 return s
28 29
29 30 def cast_bytes(s, encoding=None):
30 31 if not isinstance(s, bytes):
31 32 return encode(s, encoding)
32 33 return s
33 34
34 35 def _modify_str_or_docstring(str_change_func):
35 36 @functools.wraps(str_change_func)
36 37 def wrapper(func_or_str):
37 38 if isinstance(func_or_str, string_types):
38 39 func = None
39 40 doc = func_or_str
40 41 else:
41 42 func = func_or_str
42 43 doc = func.__doc__
43 44
44 45 doc = str_change_func(doc)
45 46
46 47 if func:
47 48 func.__doc__ = doc
48 49 return func
49 50 return doc
50 51 return wrapper
51 52
52 53 def safe_unicode(e):
53 54 """unicode(e) with various fallbacks. Used for exceptions, which may not be
54 55 safe to call unicode() on.
55 56 """
56 57 try:
57 58 return unicode_type(e)
58 59 except UnicodeError:
59 60 pass
60 61
61 62 try:
62 63 return str_to_unicode(str(e))
63 64 except UnicodeError:
64 65 pass
65 66
66 67 try:
67 68 return str_to_unicode(repr(e))
68 69 except UnicodeError:
69 70 pass
70 71
71 72 return u'Unrecoverably corrupt evalue'
72 73
73 74 if sys.version_info[0] >= 3:
74 75 PY3 = True
75 76
76 77 input = input
77 78 builtin_mod_name = "builtins"
78 79 import builtins as builtin_mod
79 80
80 81 str_to_unicode = no_code
81 82 unicode_to_str = no_code
82 83 str_to_bytes = encode
83 84 bytes_to_str = decode
84 85 cast_bytes_py2 = no_code
85 86
86 87 string_types = (str,)
87 88 unicode_type = str
88 89
89 90 def isidentifier(s, dotted=False):
90 91 if dotted:
91 92 return all(isidentifier(a) for a in s.split("."))
92 93 return s.isidentifier()
93 94
94 95 open = orig_open
95 96 xrange = range
96 97 def iteritems(d): return iter(d.items())
97 98 def itervalues(d): return iter(d.values())
99 getcwd = os.getcwd
98 100
99 101 MethodType = types.MethodType
100 102
101 103 def execfile(fname, glob, loc=None):
102 104 loc = loc if (loc is not None) else glob
103 105 with open(fname, 'rb') as f:
104 106 exec(compile(f.read(), fname, 'exec'), glob, loc)
105 107
106 108 # Refactor print statements in doctests.
107 109 _print_statement_re = re.compile(r"\bprint (?P<expr>.*)$", re.MULTILINE)
108 110 def _print_statement_sub(match):
109 111 expr = match.groups('expr')
110 112 return "print(%s)" % expr
111 113
112 114 @_modify_str_or_docstring
113 115 def doctest_refactor_print(doc):
114 116 """Refactor 'print x' statements in a doctest to print(x) style. 2to3
115 117 unfortunately doesn't pick up on our doctests.
116 118
117 119 Can accept a string or a function, so it can be used as a decorator."""
118 120 return _print_statement_re.sub(_print_statement_sub, doc)
119 121
120 122 # Abstract u'abc' syntax:
121 123 @_modify_str_or_docstring
122 124 def u_format(s):
123 125 """"{u}'abc'" --> "'abc'" (Python 3)
124 126
125 127 Accepts a string or a function, so it can be used as a decorator."""
126 128 return s.format(u='')
127 129
128 130 else:
129 131 PY3 = False
130 132
131 133 input = raw_input
132 134 builtin_mod_name = "__builtin__"
133 135 import __builtin__ as builtin_mod
134 136
135 137 str_to_unicode = decode
136 138 unicode_to_str = encode
137 139 str_to_bytes = no_code
138 140 bytes_to_str = no_code
139 141 cast_bytes_py2 = cast_bytes
140 142
141 143 string_types = (str, unicode)
142 144 unicode_type = unicode
143 145
144 146 import re
145 147 _name_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$")
146 148 def isidentifier(s, dotted=False):
147 149 if dotted:
148 150 return all(isidentifier(a) for a in s.split("."))
149 151 return bool(_name_re.match(s))
150 152
151 153 class open(object):
152 154 """Wrapper providing key part of Python 3 open() interface."""
153 155 def __init__(self, fname, mode="r", encoding="utf-8"):
154 156 self.f = orig_open(fname, mode)
155 157 self.enc = encoding
156 158
157 159 def write(self, s):
158 160 return self.f.write(s.encode(self.enc))
159 161
160 162 def read(self, size=-1):
161 163 return self.f.read(size).decode(self.enc)
162 164
163 165 def close(self):
164 166 return self.f.close()
165 167
166 168 def __enter__(self):
167 169 return self
168 170
169 171 def __exit__(self, etype, value, traceback):
170 172 self.f.close()
171 173
172 174 xrange = xrange
173 175 def iteritems(d): return d.iteritems()
174 176 def itervalues(d): return d.itervalues()
177 getcwd = os.getcwdu
175 178
176 179 def MethodType(func, instance):
177 180 return types.MethodType(func, instance, type(instance))
178 181
179 182 # don't override system execfile on 2.x:
180 183 execfile = execfile
181 184
182 185 def doctest_refactor_print(func_or_str):
183 186 return func_or_str
184 187
185 188
186 189 # Abstract u'abc' syntax:
187 190 @_modify_str_or_docstring
188 191 def u_format(s):
189 192 """"{u}'abc'" --> "u'abc'" (Python 2)
190 193
191 194 Accepts a string or a function, so it can be used as a decorator."""
192 195 return s.format(u='u')
193 196
194 197 if sys.platform == 'win32':
195 198 def execfile(fname, glob=None, loc=None):
196 199 loc = loc if (loc is not None) else glob
197 200 # The rstrip() is necessary b/c trailing whitespace in files will
198 201 # cause an IndentationError in Python 2.6 (this was fixed in 2.7,
199 202 # but we still support 2.6). See issue 1027.
200 203 scripttext = builtin_mod.open(fname).read().rstrip() + '\n'
201 204 # compile converts unicode filename to str assuming
202 205 # ascii. Let's do the conversion before calling compile
203 206 if isinstance(fname, unicode):
204 207 filename = unicode_to_str(fname)
205 208 else:
206 209 filename = fname
207 210 exec(compile(scripttext, filename, 'exec'), glob, loc)
208 211 else:
209 212 def execfile(fname, *where):
210 213 if isinstance(fname, unicode):
211 214 filename = fname.encode(sys.getfilesystemencoding())
212 215 else:
213 216 filename = fname
214 217 builtin_mod.execfile(filename, *where)
215 218
216 219 # Parts below taken from six:
217 220 # Copyright (c) 2010-2013 Benjamin Peterson
218 221 #
219 222 # Permission is hereby granted, free of charge, to any person obtaining a copy
220 223 # of this software and associated documentation files (the "Software"), to deal
221 224 # in the Software without restriction, including without limitation the rights
222 225 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
223 226 # copies of the Software, and to permit persons to whom the Software is
224 227 # furnished to do so, subject to the following conditions:
225 228 #
226 229 # The above copyright notice and this permission notice shall be included in all
227 230 # copies or substantial portions of the Software.
228 231 #
229 232 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
230 233 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
231 234 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
232 235 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
233 236 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
234 237 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
235 238 # SOFTWARE.
236 239
237 240 def with_metaclass(meta, *bases):
238 241 """Create a base class with a metaclass."""
239 242 return meta("NewBase", bases, {})
@@ -1,162 +1,164 b''
1 1 # encoding: utf-8
2 2 """
3 3 Utilities for working with terminals.
4 4
5 5 Authors:
6 6
7 7 * Brian E. Granger
8 8 * Fernando Perez
9 9 * Alexander Belchenko (e-mail: bialix AT ukr.net)
10 10 """
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Copyright (C) 2008-2011 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 import os
24 24 import struct
25 25 import sys
26 26 import warnings
27 27
28 from . import py3compat
29
28 30 #-----------------------------------------------------------------------------
29 31 # Code
30 32 #-----------------------------------------------------------------------------
31 33
32 34 # This variable is part of the expected API of the module:
33 35 ignore_termtitle = True
34 36
35 37
36 38 def _term_clear():
37 39 pass
38 40
39 41
40 42 if os.name == 'posix':
41 43 def _term_clear():
42 44 os.system('clear')
43 45
44 46
45 47 if sys.platform == 'win32':
46 48 def _term_clear():
47 49 os.system('cls')
48 50
49 51
50 52 def term_clear():
51 53 _term_clear()
52 54
53 55
54 56 def toggle_set_term_title(val):
55 57 """Control whether set_term_title is active or not.
56 58
57 59 set_term_title() allows writing to the console titlebar. In embedded
58 60 widgets this can cause problems, so this call can be used to toggle it on
59 61 or off as needed.
60 62
61 63 The default state of the module is for the function to be disabled.
62 64
63 65 Parameters
64 66 ----------
65 67 val : bool
66 68 If True, set_term_title() actually writes to the terminal (using the
67 69 appropriate platform-specific module). If False, it is a no-op.
68 70 """
69 71 global ignore_termtitle
70 72 ignore_termtitle = not(val)
71 73
72 74
73 75 def _set_term_title(*args,**kw):
74 76 """Dummy no-op."""
75 77 pass
76 78
77 79
78 80 def _set_term_title_xterm(title):
79 81 """ Change virtual terminal title in xterm-workalikes """
80 82 sys.stdout.write('\033]0;%s\007' % title)
81 83
82 84 if os.name == 'posix':
83 85 TERM = os.environ.get('TERM','')
84 86 if TERM.startswith('xterm'):
85 87 _set_term_title = _set_term_title_xterm
86 88
87 89
88 90 if sys.platform == 'win32':
89 91 try:
90 92 import ctypes
91 93
92 94 SetConsoleTitleW = ctypes.windll.kernel32.SetConsoleTitleW
93 95 SetConsoleTitleW.argtypes = [ctypes.c_wchar_p]
94 96
95 97 def _set_term_title(title):
96 98 """Set terminal title using ctypes to access the Win32 APIs."""
97 99 SetConsoleTitleW(title)
98 100 except ImportError:
99 101 def _set_term_title(title):
100 102 """Set terminal title using the 'title' command."""
101 103 global ignore_termtitle
102 104
103 105 try:
104 106 # Cannot be on network share when issuing system commands
105 curr = os.getcwdu()
107 curr = py3compat.getcwd()
106 108 os.chdir("C:")
107 109 ret = os.system("title " + title)
108 110 finally:
109 111 os.chdir(curr)
110 112 if ret:
111 113 # non-zero return code signals error, don't try again
112 114 ignore_termtitle = True
113 115
114 116
115 117 def set_term_title(title):
116 118 """Set terminal title using the necessary platform-dependent calls."""
117 119 if ignore_termtitle:
118 120 return
119 121 _set_term_title(title)
120 122
121 123
122 124 def freeze_term_title():
123 125 warnings.warn("This function is deprecated, use toggle_set_term_title()")
124 126 global ignore_termtitle
125 127 ignore_termtitle = True
126 128
127 129
128 130 def get_terminal_size(defaultx=80, defaulty=25):
129 131 return defaultx, defaulty
130 132
131 133
132 134 if sys.platform == 'win32':
133 135 def get_terminal_size(defaultx=80, defaulty=25):
134 136 """Return size of current terminal console.
135 137
136 138 This function try to determine actual size of current working
137 139 console window and return tuple (sizex, sizey) if success,
138 140 or default size (defaultx, defaulty) otherwise.
139 141
140 142 Dependencies: ctypes should be installed.
141 143
142 144 Author: Alexander Belchenko (e-mail: bialix AT ukr.net)
143 145 """
144 146 try:
145 147 import ctypes
146 148 except ImportError:
147 149 return defaultx, defaulty
148 150
149 151 h = ctypes.windll.kernel32.GetStdHandle(-11)
150 152 csbi = ctypes.create_string_buffer(22)
151 153 res = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
152 154
153 155 if res:
154 156 (bufx, bufy, curx, cury, wattr,
155 157 left, top, right, bottom, maxx, maxy) = struct.unpack(
156 158 "hhhhHhhhhhh", csbi.raw)
157 159 sizex = right - left + 1
158 160 sizey = bottom - top + 1
159 161 return (sizex, sizey)
160 162 else:
161 163 return (defaultx, defaulty)
162 164
@@ -1,643 +1,643 b''
1 1 # encoding: utf-8
2 2 """Tests for IPython.utils.path.py"""
3 3
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (C) 2008-2011 The IPython Development Team
6 6 #
7 7 # Distributed under the terms of the BSD License. The full license is in
8 8 # the file COPYING, distributed as part of this software.
9 9 #-----------------------------------------------------------------------------
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Imports
13 13 #-----------------------------------------------------------------------------
14 14
15 15 from __future__ import with_statement
16 16
17 17 import os
18 18 import shutil
19 19 import sys
20 20 import tempfile
21 21 from contextlib import contextmanager
22 22
23 23 from os.path import join, abspath, split
24 24
25 25 import nose.tools as nt
26 26
27 27 from nose import with_setup
28 28
29 29 import IPython
30 30 from IPython.testing import decorators as dec
31 31 from IPython.testing.decorators import (skip_if_not_win32, skip_win32,
32 32 onlyif_unicode_paths,)
33 33 from IPython.testing.tools import make_tempfile, AssertPrints
34 34 from IPython.utils import path
35 35 from IPython.utils import py3compat
36 36 from IPython.utils.tempdir import TemporaryDirectory
37 37
38 38 # Platform-dependent imports
39 39 try:
40 40 import winreg as wreg # Py 3
41 41 except ImportError:
42 42 try:
43 43 import _winreg as wreg # Py 2
44 44 except ImportError:
45 45 #Fake _winreg module on none windows platforms
46 46 import types
47 47 wr_name = "winreg" if py3compat.PY3 else "_winreg"
48 48 sys.modules[wr_name] = types.ModuleType(wr_name)
49 49 try:
50 50 import winreg as wreg
51 51 except ImportError:
52 52 import _winreg as wreg
53 53 #Add entries that needs to be stubbed by the testing code
54 54 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
55 55
56 56 try:
57 57 reload
58 58 except NameError: # Python 3
59 59 from imp import reload
60 60
61 61 #-----------------------------------------------------------------------------
62 62 # Globals
63 63 #-----------------------------------------------------------------------------
64 64 env = os.environ
65 65 TEST_FILE_PATH = split(abspath(__file__))[0]
66 66 TMP_TEST_DIR = tempfile.mkdtemp()
67 67 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
68 68 XDG_TEST_DIR = join(HOME_TEST_DIR, "xdg_test_dir")
69 69 XDG_CACHE_DIR = join(HOME_TEST_DIR, "xdg_cache_dir")
70 70 IP_TEST_DIR = join(HOME_TEST_DIR,'.ipython')
71 71 #
72 72 # Setup/teardown functions/decorators
73 73 #
74 74
75 75 def setup():
76 76 """Setup testenvironment for the module:
77 77
78 78 - Adds dummy home dir tree
79 79 """
80 80 # Do not mask exceptions here. In particular, catching WindowsError is a
81 81 # problem because that exception is only defined on Windows...
82 82 os.makedirs(IP_TEST_DIR)
83 83 os.makedirs(os.path.join(XDG_TEST_DIR, 'ipython'))
84 84 os.makedirs(os.path.join(XDG_CACHE_DIR, 'ipython'))
85 85
86 86
87 87 def teardown():
88 88 """Teardown testenvironment for the module:
89 89
90 90 - Remove dummy home dir tree
91 91 """
92 92 # Note: we remove the parent test dir, which is the root of all test
93 93 # subdirs we may have created. Use shutil instead of os.removedirs, so
94 94 # that non-empty directories are all recursively removed.
95 95 shutil.rmtree(TMP_TEST_DIR)
96 96
97 97
98 98 def setup_environment():
99 99 """Setup testenvironment for some functions that are tested
100 100 in this module. In particular this functions stores attributes
101 101 and other things that we need to stub in some test functions.
102 102 This needs to be done on a function level and not module level because
103 103 each testfunction needs a pristine environment.
104 104 """
105 105 global oldstuff, platformstuff
106 106 oldstuff = (env.copy(), os.name, sys.platform, path.get_home_dir, IPython.__file__, os.getcwd())
107 107
108 108 if os.name == 'nt':
109 109 platformstuff = (wreg.OpenKey, wreg.QueryValueEx,)
110 110
111 111
112 112 def teardown_environment():
113 113 """Restore things that were remembered by the setup_environment function
114 114 """
115 115 (oldenv, os.name, sys.platform, path.get_home_dir, IPython.__file__, old_wd) = oldstuff
116 116 os.chdir(old_wd)
117 117 reload(path)
118 118
119 119 for key in list(env):
120 120 if key not in oldenv:
121 121 del env[key]
122 122 env.update(oldenv)
123 123 if hasattr(sys, 'frozen'):
124 124 del sys.frozen
125 125 if os.name == 'nt':
126 126 (wreg.OpenKey, wreg.QueryValueEx,) = platformstuff
127 127
128 128 # Build decorator that uses the setup_environment/setup_environment
129 129 with_environment = with_setup(setup_environment, teardown_environment)
130 130
131 131 @skip_if_not_win32
132 132 @with_environment
133 133 def test_get_home_dir_1():
134 134 """Testcase for py2exe logic, un-compressed lib
135 135 """
136 136 unfrozen = path.get_home_dir()
137 137 sys.frozen = True
138 138
139 139 #fake filename for IPython.__init__
140 140 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
141 141
142 142 home_dir = path.get_home_dir()
143 143 nt.assert_equal(home_dir, unfrozen)
144 144
145 145
146 146 @skip_if_not_win32
147 147 @with_environment
148 148 def test_get_home_dir_2():
149 149 """Testcase for py2exe logic, compressed lib
150 150 """
151 151 unfrozen = path.get_home_dir()
152 152 sys.frozen = True
153 153 #fake filename for IPython.__init__
154 154 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")).lower()
155 155
156 156 home_dir = path.get_home_dir(True)
157 157 nt.assert_equal(home_dir, unfrozen)
158 158
159 159
160 160 @with_environment
161 161 def test_get_home_dir_3():
162 162 """get_home_dir() uses $HOME if set"""
163 163 env["HOME"] = HOME_TEST_DIR
164 164 home_dir = path.get_home_dir(True)
165 165 # get_home_dir expands symlinks
166 166 nt.assert_equal(home_dir, os.path.realpath(env["HOME"]))
167 167
168 168
169 169 @with_environment
170 170 def test_get_home_dir_4():
171 171 """get_home_dir() still works if $HOME is not set"""
172 172
173 173 if 'HOME' in env: del env['HOME']
174 174 # this should still succeed, but we don't care what the answer is
175 175 home = path.get_home_dir(False)
176 176
177 177 @with_environment
178 178 def test_get_home_dir_5():
179 179 """raise HomeDirError if $HOME is specified, but not a writable dir"""
180 180 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
181 181 # set os.name = posix, to prevent My Documents fallback on Windows
182 182 os.name = 'posix'
183 183 nt.assert_raises(path.HomeDirError, path.get_home_dir, True)
184 184
185 185
186 186 # Should we stub wreg fully so we can run the test on all platforms?
187 187 @skip_if_not_win32
188 188 @with_environment
189 189 def test_get_home_dir_8():
190 190 """Using registry hack for 'My Documents', os=='nt'
191 191
192 192 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
193 193 """
194 194 os.name = 'nt'
195 195 # Remove from stub environment all keys that may be set
196 196 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
197 197 env.pop(key, None)
198 198
199 199 #Stub windows registry functions
200 200 def OpenKey(x, y):
201 201 class key:
202 202 def Close(self):
203 203 pass
204 204 return key()
205 205 def QueryValueEx(x, y):
206 206 return [abspath(HOME_TEST_DIR)]
207 207
208 208 wreg.OpenKey = OpenKey
209 209 wreg.QueryValueEx = QueryValueEx
210 210
211 211 home_dir = path.get_home_dir()
212 212 nt.assert_equal(home_dir, abspath(HOME_TEST_DIR))
213 213
214 214
215 215 @with_environment
216 216 def test_get_ipython_dir_1():
217 217 """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions."""
218 218 env_ipdir = os.path.join("someplace", ".ipython")
219 219 path._writable_dir = lambda path: True
220 220 env['IPYTHONDIR'] = env_ipdir
221 221 ipdir = path.get_ipython_dir()
222 222 nt.assert_equal(ipdir, env_ipdir)
223 223
224 224
225 225 @with_environment
226 226 def test_get_ipython_dir_2():
227 227 """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions."""
228 228 path.get_home_dir = lambda : "someplace"
229 229 path.get_xdg_dir = lambda : None
230 230 path._writable_dir = lambda path: True
231 231 os.name = "posix"
232 232 env.pop('IPYTHON_DIR', None)
233 233 env.pop('IPYTHONDIR', None)
234 234 env.pop('XDG_CONFIG_HOME', None)
235 235 ipdir = path.get_ipython_dir()
236 236 nt.assert_equal(ipdir, os.path.join("someplace", ".ipython"))
237 237
238 238 @with_environment
239 239 def test_get_ipython_dir_3():
240 240 """test_get_ipython_dir_3, use XDG if defined, and .ipython doesn't exist."""
241 241 path.get_home_dir = lambda : "someplace"
242 242 path._writable_dir = lambda path: True
243 243 os.name = "posix"
244 244 env.pop('IPYTHON_DIR', None)
245 245 env.pop('IPYTHONDIR', None)
246 246 env['XDG_CONFIG_HOME'] = XDG_TEST_DIR
247 247 ipdir = path.get_ipython_dir()
248 248 if sys.platform == "darwin":
249 249 expected = os.path.join("someplace", ".ipython")
250 250 else:
251 251 expected = os.path.join(XDG_TEST_DIR, "ipython")
252 252 nt.assert_equal(ipdir, expected)
253 253
254 254 @with_environment
255 255 def test_get_ipython_dir_4():
256 256 """test_get_ipython_dir_4, use XDG if both exist."""
257 257 path.get_home_dir = lambda : HOME_TEST_DIR
258 258 os.name = "posix"
259 259 env.pop('IPYTHON_DIR', None)
260 260 env.pop('IPYTHONDIR', None)
261 261 env['XDG_CONFIG_HOME'] = XDG_TEST_DIR
262 262 ipdir = path.get_ipython_dir()
263 263 if sys.platform == "darwin":
264 264 expected = os.path.join(HOME_TEST_DIR, ".ipython")
265 265 else:
266 266 expected = os.path.join(XDG_TEST_DIR, "ipython")
267 267 nt.assert_equal(ipdir, expected)
268 268
269 269 @with_environment
270 270 def test_get_ipython_dir_5():
271 271 """test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist."""
272 272 path.get_home_dir = lambda : HOME_TEST_DIR
273 273 os.name = "posix"
274 274 env.pop('IPYTHON_DIR', None)
275 275 env.pop('IPYTHONDIR', None)
276 276 env['XDG_CONFIG_HOME'] = XDG_TEST_DIR
277 277 os.rmdir(os.path.join(XDG_TEST_DIR, 'ipython'))
278 278 ipdir = path.get_ipython_dir()
279 279 nt.assert_equal(ipdir, IP_TEST_DIR)
280 280
281 281 @with_environment
282 282 def test_get_ipython_dir_6():
283 283 """test_get_ipython_dir_6, use XDG if defined and neither exist."""
284 284 xdg = os.path.join(HOME_TEST_DIR, 'somexdg')
285 285 os.mkdir(xdg)
286 286 shutil.rmtree(os.path.join(HOME_TEST_DIR, '.ipython'))
287 287 path.get_home_dir = lambda : HOME_TEST_DIR
288 288 path.get_xdg_dir = lambda : xdg
289 289 os.name = "posix"
290 290 env.pop('IPYTHON_DIR', None)
291 291 env.pop('IPYTHONDIR', None)
292 292 env.pop('XDG_CONFIG_HOME', None)
293 293 xdg_ipdir = os.path.join(xdg, "ipython")
294 294 ipdir = path.get_ipython_dir()
295 295 nt.assert_equal(ipdir, xdg_ipdir)
296 296
297 297 @with_environment
298 298 def test_get_ipython_dir_7():
299 299 """test_get_ipython_dir_7, test home directory expansion on IPYTHONDIR"""
300 300 path._writable_dir = lambda path: True
301 301 home_dir = os.path.normpath(os.path.expanduser('~'))
302 302 env['IPYTHONDIR'] = os.path.join('~', 'somewhere')
303 303 ipdir = path.get_ipython_dir()
304 304 nt.assert_equal(ipdir, os.path.join(home_dir, 'somewhere'))
305 305
306 306 @skip_win32
307 307 @with_environment
308 308 def test_get_ipython_dir_8():
309 309 """test_get_ipython_dir_8, test / home directory"""
310 310 old = path._writable_dir, path.get_xdg_dir
311 311 try:
312 312 path._writable_dir = lambda path: bool(path)
313 313 path.get_xdg_dir = lambda: None
314 314 env.pop('IPYTHON_DIR', None)
315 315 env.pop('IPYTHONDIR', None)
316 316 env['HOME'] = '/'
317 317 nt.assert_equal(path.get_ipython_dir(), '/.ipython')
318 318 finally:
319 319 path._writable_dir, path.get_xdg_dir = old
320 320
321 321 @with_environment
322 322 def test_get_xdg_dir_0():
323 323 """test_get_xdg_dir_0, check xdg_dir"""
324 324 reload(path)
325 325 path._writable_dir = lambda path: True
326 326 path.get_home_dir = lambda : 'somewhere'
327 327 os.name = "posix"
328 328 sys.platform = "linux2"
329 329 env.pop('IPYTHON_DIR', None)
330 330 env.pop('IPYTHONDIR', None)
331 331 env.pop('XDG_CONFIG_HOME', None)
332 332
333 333 nt.assert_equal(path.get_xdg_dir(), os.path.join('somewhere', '.config'))
334 334
335 335
336 336 @with_environment
337 337 def test_get_xdg_dir_1():
338 338 """test_get_xdg_dir_1, check nonexistant xdg_dir"""
339 339 reload(path)
340 340 path.get_home_dir = lambda : HOME_TEST_DIR
341 341 os.name = "posix"
342 342 sys.platform = "linux2"
343 343 env.pop('IPYTHON_DIR', None)
344 344 env.pop('IPYTHONDIR', None)
345 345 env.pop('XDG_CONFIG_HOME', None)
346 346 nt.assert_equal(path.get_xdg_dir(), None)
347 347
348 348 @with_environment
349 349 def test_get_xdg_dir_2():
350 350 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
351 351 reload(path)
352 352 path.get_home_dir = lambda : HOME_TEST_DIR
353 353 os.name = "posix"
354 354 sys.platform = "linux2"
355 355 env.pop('IPYTHON_DIR', None)
356 356 env.pop('IPYTHONDIR', None)
357 357 env.pop('XDG_CONFIG_HOME', None)
358 358 cfgdir=os.path.join(path.get_home_dir(), '.config')
359 359 if not os.path.exists(cfgdir):
360 360 os.makedirs(cfgdir)
361 361
362 362 nt.assert_equal(path.get_xdg_dir(), cfgdir)
363 363
364 364 @with_environment
365 365 def test_get_xdg_dir_3():
366 366 """test_get_xdg_dir_3, check xdg_dir not used on OS X"""
367 367 reload(path)
368 368 path.get_home_dir = lambda : HOME_TEST_DIR
369 369 os.name = "posix"
370 370 sys.platform = "darwin"
371 371 env.pop('IPYTHON_DIR', None)
372 372 env.pop('IPYTHONDIR', None)
373 373 env.pop('XDG_CONFIG_HOME', None)
374 374 cfgdir=os.path.join(path.get_home_dir(), '.config')
375 375 if not os.path.exists(cfgdir):
376 376 os.makedirs(cfgdir)
377 377
378 378 nt.assert_equal(path.get_xdg_dir(), None)
379 379
380 380 def test_filefind():
381 381 """Various tests for filefind"""
382 382 f = tempfile.NamedTemporaryFile()
383 383 # print 'fname:',f.name
384 384 alt_dirs = path.get_ipython_dir()
385 385 t = path.filefind(f.name, alt_dirs)
386 386 # print 'found:',t
387 387
388 388 @with_environment
389 389 def test_get_ipython_cache_dir():
390 390 os.environ["HOME"] = HOME_TEST_DIR
391 391 if os.name == 'posix' and sys.platform != 'darwin':
392 392 # test default
393 393 os.makedirs(os.path.join(HOME_TEST_DIR, ".cache"))
394 394 os.environ.pop("XDG_CACHE_HOME", None)
395 395 ipdir = path.get_ipython_cache_dir()
396 396 nt.assert_equal(os.path.join(HOME_TEST_DIR, ".cache", "ipython"),
397 397 ipdir)
398 398 nt.assert_true(os.path.isdir(ipdir))
399 399
400 400 # test env override
401 401 os.environ["XDG_CACHE_HOME"] = XDG_CACHE_DIR
402 402 ipdir = path.get_ipython_cache_dir()
403 403 nt.assert_true(os.path.isdir(ipdir))
404 404 nt.assert_equal(ipdir, os.path.join(XDG_CACHE_DIR, "ipython"))
405 405 else:
406 406 nt.assert_equal(path.get_ipython_cache_dir(),
407 407 path.get_ipython_dir())
408 408
409 409 def test_get_ipython_package_dir():
410 410 ipdir = path.get_ipython_package_dir()
411 411 nt.assert_true(os.path.isdir(ipdir))
412 412
413 413
414 414 def test_get_ipython_module_path():
415 415 ipapp_path = path.get_ipython_module_path('IPython.terminal.ipapp')
416 416 nt.assert_true(os.path.isfile(ipapp_path))
417 417
418 418
419 419 @dec.skip_if_not_win32
420 420 def test_get_long_path_name_win32():
421 421 with TemporaryDirectory() as tmpdir:
422 422
423 423 # Make a long path.
424 424 long_path = os.path.join(tmpdir, u'this is my long path name')
425 425 os.makedirs(long_path)
426 426
427 427 # Test to see if the short path evaluates correctly.
428 428 short_path = os.path.join(tmpdir, u'THISIS~1')
429 429 evaluated_path = path.get_long_path_name(short_path)
430 430 nt.assert_equal(evaluated_path.lower(), long_path.lower())
431 431
432 432
433 433 @dec.skip_win32
434 434 def test_get_long_path_name():
435 435 p = path.get_long_path_name('/usr/local')
436 436 nt.assert_equal(p,'/usr/local')
437 437
438 438 @dec.skip_win32 # can't create not-user-writable dir on win
439 439 @with_environment
440 440 def test_not_writable_ipdir():
441 441 tmpdir = tempfile.mkdtemp()
442 442 os.name = "posix"
443 443 env.pop('IPYTHON_DIR', None)
444 444 env.pop('IPYTHONDIR', None)
445 445 env.pop('XDG_CONFIG_HOME', None)
446 446 env['HOME'] = tmpdir
447 447 ipdir = os.path.join(tmpdir, '.ipython')
448 448 os.mkdir(ipdir)
449 449 os.chmod(ipdir, 600)
450 450 with AssertPrints('is not a writable location', channel='stderr'):
451 451 ipdir = path.get_ipython_dir()
452 452 env.pop('IPYTHON_DIR', None)
453 453
454 454 def test_unquote_filename():
455 455 for win32 in (True, False):
456 456 nt.assert_equal(path.unquote_filename('foo.py', win32=win32), 'foo.py')
457 457 nt.assert_equal(path.unquote_filename('foo bar.py', win32=win32), 'foo bar.py')
458 458 nt.assert_equal(path.unquote_filename('"foo.py"', win32=True), 'foo.py')
459 459 nt.assert_equal(path.unquote_filename('"foo bar.py"', win32=True), 'foo bar.py')
460 460 nt.assert_equal(path.unquote_filename("'foo.py'", win32=True), 'foo.py')
461 461 nt.assert_equal(path.unquote_filename("'foo bar.py'", win32=True), 'foo bar.py')
462 462 nt.assert_equal(path.unquote_filename('"foo.py"', win32=False), '"foo.py"')
463 463 nt.assert_equal(path.unquote_filename('"foo bar.py"', win32=False), '"foo bar.py"')
464 464 nt.assert_equal(path.unquote_filename("'foo.py'", win32=False), "'foo.py'")
465 465 nt.assert_equal(path.unquote_filename("'foo bar.py'", win32=False), "'foo bar.py'")
466 466
467 467 @with_environment
468 468 def test_get_py_filename():
469 469 os.chdir(TMP_TEST_DIR)
470 470 for win32 in (True, False):
471 471 with make_tempfile('foo.py'):
472 472 nt.assert_equal(path.get_py_filename('foo.py', force_win32=win32), 'foo.py')
473 473 nt.assert_equal(path.get_py_filename('foo', force_win32=win32), 'foo.py')
474 474 with make_tempfile('foo'):
475 475 nt.assert_equal(path.get_py_filename('foo', force_win32=win32), 'foo')
476 476 nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32)
477 477 nt.assert_raises(IOError, path.get_py_filename, 'foo', force_win32=win32)
478 478 nt.assert_raises(IOError, path.get_py_filename, 'foo.py', force_win32=win32)
479 479 true_fn = 'foo with spaces.py'
480 480 with make_tempfile(true_fn):
481 481 nt.assert_equal(path.get_py_filename('foo with spaces', force_win32=win32), true_fn)
482 482 nt.assert_equal(path.get_py_filename('foo with spaces.py', force_win32=win32), true_fn)
483 483 if win32:
484 484 nt.assert_equal(path.get_py_filename('"foo with spaces.py"', force_win32=True), true_fn)
485 485 nt.assert_equal(path.get_py_filename("'foo with spaces.py'", force_win32=True), true_fn)
486 486 else:
487 487 nt.assert_raises(IOError, path.get_py_filename, '"foo with spaces.py"', force_win32=False)
488 488 nt.assert_raises(IOError, path.get_py_filename, "'foo with spaces.py'", force_win32=False)
489 489
490 490 @onlyif_unicode_paths
491 491 def test_unicode_in_filename():
492 492 """When a file doesn't exist, the exception raised should be safe to call
493 493 str() on - i.e. in Python 2 it must only have ASCII characters.
494 494
495 495 https://github.com/ipython/ipython/issues/875
496 496 """
497 497 try:
498 498 # these calls should not throw unicode encode exceptions
499 499 path.get_py_filename(u'fooéè.py', force_win32=False)
500 500 except IOError as ex:
501 501 str(ex)
502 502
503 503
504 504 class TestShellGlob(object):
505 505
506 506 @classmethod
507 507 def setUpClass(cls):
508 508 cls.filenames_start_with_a = ['a0', 'a1', 'a2']
509 509 cls.filenames_end_with_b = ['0b', '1b', '2b']
510 510 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
511 511 cls.tempdir = TemporaryDirectory()
512 512 td = cls.tempdir.name
513 513
514 514 with cls.in_tempdir():
515 515 # Create empty files
516 516 for fname in cls.filenames:
517 517 open(os.path.join(td, fname), 'w').close()
518 518
519 519 @classmethod
520 520 def tearDownClass(cls):
521 521 cls.tempdir.cleanup()
522 522
523 523 @classmethod
524 524 @contextmanager
525 525 def in_tempdir(cls):
526 save = os.getcwdu()
526 save = py3compat.getcwd()
527 527 try:
528 528 os.chdir(cls.tempdir.name)
529 529 yield
530 530 finally:
531 531 os.chdir(save)
532 532
533 533 def check_match(self, patterns, matches):
534 534 with self.in_tempdir():
535 535 # glob returns unordered list. that's why sorted is required.
536 536 nt.assert_equals(sorted(path.shellglob(patterns)),
537 537 sorted(matches))
538 538
539 539 def common_cases(self):
540 540 return [
541 541 (['*'], self.filenames),
542 542 (['a*'], self.filenames_start_with_a),
543 543 (['*c'], ['*c']),
544 544 (['*', 'a*', '*b', '*c'], self.filenames
545 545 + self.filenames_start_with_a
546 546 + self.filenames_end_with_b
547 547 + ['*c']),
548 548 (['a[012]'], self.filenames_start_with_a),
549 549 ]
550 550
551 551 @skip_win32
552 552 def test_match_posix(self):
553 553 for (patterns, matches) in self.common_cases() + [
554 554 ([r'\*'], ['*']),
555 555 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
556 556 ([r'a\[012]'], ['a[012]']),
557 557 ]:
558 558 yield (self.check_match, patterns, matches)
559 559
560 560 @skip_if_not_win32
561 561 def test_match_windows(self):
562 562 for (patterns, matches) in self.common_cases() + [
563 563 # In windows, backslash is interpreted as path
564 564 # separator. Therefore, you can't escape glob
565 565 # using it.
566 566 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
567 567 ([r'a\[012]'], [r'a\[012]']),
568 568 ]:
569 569 yield (self.check_match, patterns, matches)
570 570
571 571
572 572 def test_unescape_glob():
573 573 nt.assert_equals(path.unescape_glob(r'\*\[\!\]\?'), '*[!]?')
574 574 nt.assert_equals(path.unescape_glob(r'\\*'), r'\*')
575 575 nt.assert_equals(path.unescape_glob(r'\\\*'), r'\*')
576 576 nt.assert_equals(path.unescape_glob(r'\\a'), r'\a')
577 577 nt.assert_equals(path.unescape_glob(r'\a'), r'\a')
578 578
579 579
580 580 class TestLinkOrCopy(object):
581 581 def setUp(self):
582 582 self.tempdir = TemporaryDirectory()
583 583 self.src = self.dst("src")
584 584 with open(self.src, "w") as f:
585 585 f.write("Hello, world!")
586 586
587 587 def tearDown(self):
588 588 self.tempdir.cleanup()
589 589
590 590 def dst(self, *args):
591 591 return os.path.join(self.tempdir.name, *args)
592 592
593 593 def assert_inode_not_equal(self, a, b):
594 594 nt.assert_not_equals(os.stat(a).st_ino, os.stat(b).st_ino,
595 595 "%r and %r do reference the same indoes" %(a, b))
596 596
597 597 def assert_inode_equal(self, a, b):
598 598 nt.assert_equals(os.stat(a).st_ino, os.stat(b).st_ino,
599 599 "%r and %r do not reference the same indoes" %(a, b))
600 600
601 601 def assert_content_equal(self, a, b):
602 602 with open(a) as a_f:
603 603 with open(b) as b_f:
604 604 nt.assert_equals(a_f.read(), b_f.read())
605 605
606 606 @skip_win32
607 607 def test_link_successful(self):
608 608 dst = self.dst("target")
609 609 path.link_or_copy(self.src, dst)
610 610 self.assert_inode_equal(self.src, dst)
611 611
612 612 @skip_win32
613 613 def test_link_into_dir(self):
614 614 dst = self.dst("some_dir")
615 615 os.mkdir(dst)
616 616 path.link_or_copy(self.src, dst)
617 617 expected_dst = self.dst("some_dir", os.path.basename(self.src))
618 618 self.assert_inode_equal(self.src, expected_dst)
619 619
620 620 @skip_win32
621 621 def test_target_exists(self):
622 622 dst = self.dst("target")
623 623 open(dst, "w").close()
624 624 path.link_or_copy(self.src, dst)
625 625 self.assert_inode_equal(self.src, dst)
626 626
627 627 @skip_win32
628 628 def test_no_link(self):
629 629 real_link = os.link
630 630 try:
631 631 del os.link
632 632 dst = self.dst("target")
633 633 path.link_or_copy(self.src, dst)
634 634 self.assert_content_equal(self.src, dst)
635 635 self.assert_inode_not_equal(self.src, dst)
636 636 finally:
637 637 os.link = real_link
638 638
639 639 @skip_if_not_win32
640 640 def test_windows(self):
641 641 dst = self.dst("target")
642 642 path.link_or_copy(self.src, dst)
643 643 self.assert_content_equal(self.src, dst)
General Comments 0
You need to be logged in to leave comments. Login now