##// END OF EJS Templates
Format code
gousaiyang -
Show More

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

@@ -1,490 +1,490 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
12 12 # Copyright (c) IPython Development Team.
13 13 # Distributed under the terms of the Modified BSD License.
14 14
15 15 import atexit
16 16 from copy import deepcopy
17 17 import glob
18 18 import logging
19 19 import os
20 20 import shutil
21 21 import sys
22 22
23 23 from pathlib import Path
24 24
25 25 from traitlets.config.application import Application, catch_config_error
26 26 from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader
27 27 from IPython.core import release, crashhandler
28 28 from IPython.core.profiledir import ProfileDir, ProfileDirError
29 29 from IPython.paths import get_ipython_dir, get_ipython_package_dir
30 30 from IPython.utils.path import ensure_dir_exists
31 31 from traitlets import (
32 32 List, Unicode, Type, Bool, Set, Instance, Undefined,
33 33 default, observe,
34 34 )
35 35
36 36 if os.name == "nt":
37 37 programdata = os.environ.get("PROGRAMDATA", None)
38 38 if programdata is not None:
39 39 SYSTEM_CONFIG_DIRS = [str(Path(programdata) / "ipython")]
40 40 else: # PROGRAMDATA is not defined by default on XP.
41 41 SYSTEM_CONFIG_DIRS = []
42 42 else:
43 43 SYSTEM_CONFIG_DIRS = [
44 44 "/usr/local/etc/ipython",
45 45 "/etc/ipython",
46 46 ]
47 47
48 48
49 49 ENV_CONFIG_DIRS = []
50 50 _env_config_dir = os.path.join(sys.prefix, 'etc', 'ipython')
51 51 if _env_config_dir not in SYSTEM_CONFIG_DIRS:
52 52 # only add ENV_CONFIG if sys.prefix is not already included
53 53 ENV_CONFIG_DIRS.append(_env_config_dir)
54 54
55 55
56 56 _envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS')
57 57 if _envvar in {None, ''}:
58 58 IPYTHON_SUPPRESS_CONFIG_ERRORS = None
59 59 else:
60 60 if _envvar.lower() in {'1','true'}:
61 61 IPYTHON_SUPPRESS_CONFIG_ERRORS = True
62 62 elif _envvar.lower() in {'0','false'} :
63 63 IPYTHON_SUPPRESS_CONFIG_ERRORS = False
64 64 else:
65 65 sys.exit("Unsupported value for environment variable: 'IPYTHON_SUPPRESS_CONFIG_ERRORS' is set to '%s' which is none of {'0', '1', 'false', 'true', ''}."% _envvar )
66 66
67 67 # aliases and flags
68 68
69 69 base_aliases = {}
70 70 if isinstance(Application.aliases, dict):
71 71 # traitlets 5
72 72 base_aliases.update(Application.aliases)
73 73 base_aliases.update(
74 74 {
75 75 "profile-dir": "ProfileDir.location",
76 76 "profile": "BaseIPythonApplication.profile",
77 77 "ipython-dir": "BaseIPythonApplication.ipython_dir",
78 78 "log-level": "Application.log_level",
79 79 "config": "BaseIPythonApplication.extra_config_file",
80 80 }
81 81 )
82 82
83 83 base_flags = dict()
84 84 if isinstance(Application.flags, dict):
85 85 # traitlets 5
86 86 base_flags.update(Application.flags)
87 87 base_flags.update(
88 88 dict(
89 89 debug=(
90 90 {"Application": {"log_level": logging.DEBUG}},
91 91 "set log level to logging.DEBUG (maximize logging output)",
92 92 ),
93 93 quiet=(
94 94 {"Application": {"log_level": logging.CRITICAL}},
95 95 "set log level to logging.CRITICAL (minimize logging output)",
96 96 ),
97 97 init=(
98 98 {
99 99 "BaseIPythonApplication": {
100 100 "copy_config_files": True,
101 101 "auto_create": True,
102 102 }
103 103 },
104 104 """Initialize profile with default config files. This is equivalent
105 105 to running `ipython profile create <profile>` prior to startup.
106 106 """,
107 107 ),
108 108 )
109 109 )
110 110
111 111
112 112 class ProfileAwareConfigLoader(PyFileConfigLoader):
113 113 """A Python file config loader that is aware of IPython profiles."""
114 114 def load_subconfig(self, fname, path=None, profile=None):
115 115 if profile is not None:
116 116 try:
117 117 profile_dir = ProfileDir.find_profile_dir_by_name(
118 118 get_ipython_dir(),
119 119 profile,
120 120 )
121 121 except ProfileDirError:
122 122 return
123 123 path = profile_dir.location
124 124 return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path)
125 125
126 126 class BaseIPythonApplication(Application):
127 127
128 128 name = u'ipython'
129 129 description = Unicode(u'IPython: an enhanced interactive Python shell.')
130 130 version = Unicode(release.version)
131 131
132 132 aliases = base_aliases
133 133 flags = base_flags
134 134 classes = List([ProfileDir])
135 135
136 136 # enable `load_subconfig('cfg.py', profile='name')`
137 137 python_config_loader_class = ProfileAwareConfigLoader
138 138
139 139 # Track whether the config_file has changed,
140 140 # because some logic happens only if we aren't using the default.
141 141 config_file_specified = Set()
142 142
143 143 config_file_name = Unicode()
144 144 @default('config_file_name')
145 145 def _config_file_name_default(self):
146 146 return self.name.replace('-','_') + u'_config.py'
147 147 @observe('config_file_name')
148 148 def _config_file_name_changed(self, change):
149 149 if change['new'] != change['old']:
150 150 self.config_file_specified.add(change['new'])
151 151
152 152 # The directory that contains IPython's builtin profiles.
153 153 builtin_profile_dir = Unicode(
154 154 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
155 155 )
156 156
157 157 config_file_paths = List(Unicode())
158 158 @default('config_file_paths')
159 159 def _config_file_paths_default(self):
160 160 return []
161 161
162 162 extra_config_file = Unicode(
163 163 help="""Path to an extra config file to load.
164 164
165 165 If specified, load this config file in addition to any other IPython config.
166 166 """).tag(config=True)
167 167 @observe('extra_config_file')
168 168 def _extra_config_file_changed(self, change):
169 169 old = change['old']
170 170 new = change['new']
171 171 try:
172 172 self.config_files.remove(old)
173 173 except ValueError:
174 174 pass
175 175 self.config_file_specified.add(new)
176 176 self.config_files.append(new)
177 177
178 178 profile = Unicode(u'default',
179 179 help="""The IPython profile to use."""
180 180 ).tag(config=True)
181 181
182 182 @observe('profile')
183 183 def _profile_changed(self, change):
184 184 self.builtin_profile_dir = os.path.join(
185 185 get_ipython_package_dir(), u'config', u'profile', change['new']
186 186 )
187 187
188 188 add_ipython_dir_to_sys_path = Bool(
189 189 False,
190 190 """Should the IPython profile directory be added to sys path ?
191 191
192 192 This option was non-existing before IPython 8.0, and ipython_dir was added to
193 193 sys path to allow import of extensions present there. This was historical
194 194 baggage from when pip did not exist. This now default to false,
195 195 but can be set to true for legacy reasons.
196 196 """,
197 197 ).tag(config=True)
198 198
199 199 ipython_dir = Unicode(
200 200 help="""
201 201 The name of the IPython directory. This directory is used for logging
202 202 configuration (through profiles), history storage, etc. The default
203 203 is usually $HOME/.ipython. This option can also be specified through
204 204 the environment variable IPYTHONDIR.
205 205 """
206 206 ).tag(config=True)
207 207 @default('ipython_dir')
208 208 def _ipython_dir_default(self):
209 209 d = get_ipython_dir()
210 210 self._ipython_dir_changed({
211 211 'name': 'ipython_dir',
212 212 'old': d,
213 213 'new': d,
214 214 })
215 215 return d
216 216
217 217 _in_init_profile_dir = False
218 218 profile_dir = Instance(ProfileDir, allow_none=True)
219 219 @default('profile_dir')
220 220 def _profile_dir_default(self):
221 221 # avoid recursion
222 222 if self._in_init_profile_dir:
223 223 return
224 224 # profile_dir requested early, force initialization
225 225 self.init_profile_dir()
226 226 return self.profile_dir
227 227
228 228 overwrite = Bool(False,
229 229 help="""Whether to overwrite existing config files when copying"""
230 230 ).tag(config=True)
231 231 auto_create = Bool(False,
232 232 help="""Whether to create profile dir if it doesn't exist"""
233 233 ).tag(config=True)
234 234
235 235 config_files = List(Unicode())
236 236 @default('config_files')
237 237 def _config_files_default(self):
238 238 return [self.config_file_name]
239 239
240 240 copy_config_files = Bool(False,
241 241 help="""Whether to install the default config files into the profile dir.
242 242 If a new profile is being created, and IPython contains config files for that
243 243 profile, then they will be staged into the new directory. Otherwise,
244 244 default config files will be automatically generated.
245 245 """).tag(config=True)
246 246
247 247 verbose_crash = Bool(False,
248 248 help="""Create a massive crash report when IPython encounters what may be an
249 249 internal error. The default is to append a short message to the
250 250 usual traceback""").tag(config=True)
251 251
252 252 # The class to use as the crash handler.
253 253 crash_handler_class = Type(crashhandler.CrashHandler)
254 254
255 255 @catch_config_error
256 256 def __init__(self, **kwargs):
257 257 super(BaseIPythonApplication, self).__init__(**kwargs)
258 258 # ensure current working directory exists
259 259 try:
260 260 os.getcwd()
261 261 except:
262 262 # exit if cwd doesn't exist
263 263 self.log.error("Current working directory doesn't exist.")
264 264 self.exit(1)
265 265
266 266 #-------------------------------------------------------------------------
267 267 # Various stages of Application creation
268 268 #-------------------------------------------------------------------------
269 269
270 270 def init_crash_handler(self):
271 271 """Create a crash handler, typically setting sys.excepthook to it."""
272 272 self.crash_handler = self.crash_handler_class(self)
273 273 sys.excepthook = self.excepthook
274 274 def unset_crashhandler():
275 275 sys.excepthook = sys.__excepthook__
276 276 atexit.register(unset_crashhandler)
277 277
278 278 def excepthook(self, etype, evalue, tb):
279 279 """this is sys.excepthook after init_crashhandler
280 280
281 281 set self.verbose_crash=True to use our full crashhandler, instead of
282 282 a regular traceback with a short message (crash_handler_lite)
283 283 """
284 284
285 285 if self.verbose_crash:
286 286 return self.crash_handler(etype, evalue, tb)
287 287 else:
288 288 return crashhandler.crash_handler_lite(etype, evalue, tb)
289 289
290 290 @observe('ipython_dir')
291 291 def _ipython_dir_changed(self, change):
292 292 old = change['old']
293 293 new = change['new']
294 294 if old is not Undefined:
295 295 str_old = os.path.abspath(old)
296 296 if str_old in sys.path:
297 297 sys.path.remove(str_old)
298 298 if self.add_ipython_dir_to_sys_path:
299 299 str_path = os.path.abspath(new)
300 300 sys.path.append(str_path)
301 301 ensure_dir_exists(new)
302 302 readme = os.path.join(new, "README")
303 303 readme_src = os.path.join(
304 304 get_ipython_package_dir(), "config", "profile", "README"
305 305 )
306 306 if not os.path.exists(readme) and os.path.exists(readme_src):
307 307 shutil.copy(readme_src, readme)
308 308 for d in ("extensions", "nbextensions"):
309 309 path = os.path.join(new, d)
310 310 try:
311 311 ensure_dir_exists(path)
312 312 except OSError as e:
313 313 # this will not be EEXIST
314 314 self.log.error("couldn't create path %s: %s", path, e)
315 315 self.log.debug("IPYTHONDIR set to: %s" % new)
316 316
317 317 def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS):
318 318 """Load the config file.
319 319
320 320 By default, errors in loading config are handled, and a warning
321 321 printed on screen. For testing, the suppress_errors option is set
322 322 to False, so errors will make tests fail.
323 323
324 324 `suppress_errors` default value is to be `None` in which case the
325 325 behavior default to the one of `traitlets.Application`.
326 326
327 327 The default value can be set :
328 328 - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive).
329 329 - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive).
330 330 - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset.
331 331
332 332 Any other value are invalid, and will make IPython exit with a non-zero return code.
333 333 """
334 334
335 335
336 336 self.log.debug("Searching path %s for config files", self.config_file_paths)
337 337 base_config = 'ipython_config.py'
338 338 self.log.debug("Attempting to load config file: %s" %
339 339 base_config)
340 340 try:
341 341 if suppress_errors is not None:
342 342 old_value = Application.raise_config_file_errors
343 343 Application.raise_config_file_errors = not suppress_errors;
344 344 Application.load_config_file(
345 345 self,
346 346 base_config,
347 347 path=self.config_file_paths
348 348 )
349 349 except ConfigFileNotFound:
350 350 # ignore errors loading parent
351 351 self.log.debug("Config file %s not found", base_config)
352 352 pass
353 353 if suppress_errors is not None:
354 354 Application.raise_config_file_errors = old_value
355 355
356 356 for config_file_name in self.config_files:
357 357 if not config_file_name or config_file_name == base_config:
358 358 continue
359 359 self.log.debug("Attempting to load config file: %s" %
360 360 self.config_file_name)
361 361 try:
362 362 Application.load_config_file(
363 363 self,
364 364 config_file_name,
365 365 path=self.config_file_paths
366 366 )
367 367 except ConfigFileNotFound:
368 368 # Only warn if the default config file was NOT being used.
369 369 if config_file_name in self.config_file_specified:
370 370 msg = self.log.warning
371 371 else:
372 372 msg = self.log.debug
373 373 msg("Config file not found, skipping: %s", config_file_name)
374 374 except Exception:
375 375 # For testing purposes.
376 376 if not suppress_errors:
377 377 raise
378 378 self.log.warning("Error loading config file: %s" %
379 379 self.config_file_name, exc_info=True)
380 380
381 381 def init_profile_dir(self):
382 382 """initialize the profile dir"""
383 383 self._in_init_profile_dir = True
384 384 if self.profile_dir is not None:
385 385 # already ran
386 386 return
387 387 if 'ProfileDir.location' not in self.config:
388 388 # location not specified, find by profile name
389 389 try:
390 390 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
391 391 except ProfileDirError:
392 392 # not found, maybe create it (always create default profile)
393 393 if self.auto_create or self.profile == 'default':
394 394 try:
395 395 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
396 396 except ProfileDirError:
397 397 self.log.fatal("Could not create profile: %r"%self.profile)
398 398 self.exit(1)
399 399 else:
400 400 self.log.info("Created profile dir: %r"%p.location)
401 401 else:
402 402 self.log.fatal("Profile %r not found."%self.profile)
403 403 self.exit(1)
404 404 else:
405 405 self.log.debug(f"Using existing profile dir: {p.location!r}")
406 406 else:
407 407 location = self.config.ProfileDir.location
408 408 # location is fully specified
409 409 try:
410 410 p = ProfileDir.find_profile_dir(location, self.config)
411 411 except ProfileDirError:
412 412 # not found, maybe create it
413 413 if self.auto_create:
414 414 try:
415 415 p = ProfileDir.create_profile_dir(location, self.config)
416 416 except ProfileDirError:
417 417 self.log.fatal("Could not create profile directory: %r"%location)
418 418 self.exit(1)
419 419 else:
420 420 self.log.debug("Creating new profile dir: %r"%location)
421 421 else:
422 422 self.log.fatal("Profile directory %r not found."%location)
423 423 self.exit(1)
424 424 else:
425 425 self.log.debug(f"Using existing profile dir: {p.location!r}")
426 426 # if profile_dir is specified explicitly, set profile name
427 427 dir_name = os.path.basename(p.location)
428 428 if dir_name.startswith('profile_'):
429 429 self.profile = dir_name[8:]
430 430
431 431 self.profile_dir = p
432 432 self.config_file_paths.append(p.location)
433 433 self._in_init_profile_dir = False
434 434
435 435 def init_config_files(self):
436 436 """[optionally] copy default config files into profile dir."""
437 437 self.config_file_paths.extend(ENV_CONFIG_DIRS)
438 438 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
439 439 # copy config files
440 440 path = Path(self.builtin_profile_dir)
441 441 if self.copy_config_files:
442 442 src = self.profile
443 443
444 444 cfg = self.config_file_name
445 445 if path and (path / cfg).exists():
446 446 self.log.warning(
447 447 "Staging %r from %s into %r [overwrite=%s]"
448 448 % (cfg, src, self.profile_dir.location, self.overwrite)
449 449 )
450 450 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
451 451 else:
452 452 self.stage_default_config_file()
453 453 else:
454 454 # Still stage *bundled* config files, but not generated ones
455 455 # This is necessary for `ipython profile=sympy` to load the profile
456 456 # on the first go
457 457 files = path.glob("*.py")
458 458 for fullpath in files:
459 459 cfg = fullpath.name
460 460 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
461 461 # file was copied
462 462 self.log.warning("Staging bundled %s from %s into %r"%(
463 463 cfg, self.profile, self.profile_dir.location)
464 464 )
465 465
466 466
467 467 def stage_default_config_file(self):
468 468 """auto generate default config file, and stage it into the profile."""
469 469 s = self.generate_config_file()
470 470 config_file = Path(self.profile_dir.location) / self.config_file_name
471 471 if self.overwrite or not config_file.exists():
472 472 self.log.warning("Generating default config file: %r" % (config_file))
473 config_file.write_text(s, encoding='utf-8')
473 config_file.write_text(s, encoding="utf-8")
474 474
475 475 @catch_config_error
476 476 def initialize(self, argv=None):
477 477 # don't hook up crash handler before parsing command-line
478 478 self.parse_command_line(argv)
479 479 self.init_crash_handler()
480 480 if self.subapp is not None:
481 481 # stop here if subapp is taking over
482 482 return
483 483 # save a copy of CLI config to re-load after config files
484 484 # so that it has highest priority
485 485 cl_config = deepcopy(self.config)
486 486 self.init_profile_dir()
487 487 self.init_config_files()
488 488 self.load_config_file()
489 489 # enforce cl-opts override configfile opts:
490 490 self.update_config(cl_config)
@@ -1,237 +1,237 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
22 22 import os
23 23 import sys
24 24 import traceback
25 25 from pprint import pformat
26 26 from pathlib import Path
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 31 from IPython.utils.py3compat import input
32 32
33 33 from IPython.core.release import __version__ as version
34 34
35 35 from typing import Optional
36 36
37 37 #-----------------------------------------------------------------------------
38 38 # Code
39 39 #-----------------------------------------------------------------------------
40 40
41 41 # Template for the user message.
42 42 _default_message_template = """\
43 43 Oops, {app_name} crashed. We do our best to make it stable, but...
44 44
45 45 A crash report was automatically generated with the following information:
46 46 - A verbatim copy of the crash traceback.
47 47 - A copy of your input history during this session.
48 48 - Data on your current {app_name} configuration.
49 49
50 50 It was left in the file named:
51 51 \t'{crash_report_fname}'
52 52 If you can email this file to the developers, the information in it will help
53 53 them in understanding and correcting the problem.
54 54
55 55 You can mail it to: {contact_name} at {contact_email}
56 56 with the subject '{app_name} Crash Report'.
57 57
58 58 If you want to do it now, the following command will work (under Unix):
59 59 mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname}
60 60
61 61 In your email, please also include information about:
62 62 - The operating system under which the crash happened: Linux, macOS, Windows,
63 63 other, and which exact version (for example: Ubuntu 16.04.3, macOS 10.13.2,
64 64 Windows 10 Pro), and whether it is 32-bit or 64-bit;
65 65 - How {app_name} was installed: using pip or conda, from GitHub, as part of
66 66 a Docker container, or other, providing more detail if possible;
67 67 - How to reproduce the crash: what exact sequence of instructions can one
68 68 input to get the same crash? Ideally, find a minimal yet complete sequence
69 69 of instructions that yields the crash.
70 70
71 71 To ensure accurate tracking of this issue, please file a report about it at:
72 72 {bug_tracker}
73 73 """
74 74
75 75 _lite_message_template = """
76 76 If you suspect this is an IPython {version} bug, please report it at:
77 77 https://github.com/ipython/ipython/issues
78 78 or send an email to the mailing list at {email}
79 79
80 80 You can print a more detailed traceback right now with "%tb", or use "%debug"
81 81 to interactively debug it.
82 82
83 83 Extra-detailed tracebacks for bug-reporting purposes can be enabled via:
84 84 {config}Application.verbose_crash=True
85 85 """
86 86
87 87
88 88 class CrashHandler(object):
89 89 """Customizable crash handlers for IPython applications.
90 90
91 91 Instances of this class provide a :meth:`__call__` method which can be
92 92 used as a ``sys.excepthook``. The :meth:`__call__` signature is::
93 93
94 94 def __call__(self, etype, evalue, etb)
95 95 """
96 96
97 97 message_template = _default_message_template
98 98 section_sep = '\n\n'+'*'*75+'\n\n'
99 99
100 100 def __init__(
101 101 self,
102 102 app,
103 103 contact_name: Optional[str] = None,
104 104 contact_email: Optional[str] = None,
105 105 bug_tracker: Optional[str] = None,
106 106 show_crash_traceback: bool = True,
107 107 call_pdb: bool = False,
108 108 ):
109 109 """Create a new crash handler
110 110
111 111 Parameters
112 112 ----------
113 113 app : Application
114 114 A running :class:`Application` instance, which will be queried at
115 115 crash time for internal information.
116 116 contact_name : str
117 117 A string with the name of the person to contact.
118 118 contact_email : str
119 119 A string with the email address of the contact.
120 120 bug_tracker : str
121 121 A string with the URL for your project's bug tracker.
122 122 show_crash_traceback : bool
123 123 If false, don't print the crash traceback on stderr, only generate
124 124 the on-disk report
125 125 call_pdb
126 126 Whether to call pdb on crash
127 127
128 128 Attributes
129 129 ----------
130 130 These instances contain some non-argument attributes which allow for
131 131 further customization of the crash handler's behavior. Please see the
132 132 source for further details.
133 133
134 134 """
135 135 self.crash_report_fname = "Crash_report_%s.txt" % app.name
136 136 self.app = app
137 137 self.call_pdb = call_pdb
138 138 #self.call_pdb = True # dbg
139 139 self.show_crash_traceback = show_crash_traceback
140 140 self.info = dict(app_name = app.name,
141 141 contact_name = contact_name,
142 142 contact_email = contact_email,
143 143 bug_tracker = bug_tracker,
144 144 crash_report_fname = self.crash_report_fname)
145 145
146 146
147 147 def __call__(self, etype, evalue, etb):
148 148 """Handle an exception, call for compatible with sys.excepthook"""
149 149
150 150 # do not allow the crash handler to be called twice without reinstalling it
151 151 # this prevents unlikely errors in the crash handling from entering an
152 152 # infinite loop.
153 153 sys.excepthook = sys.__excepthook__
154 154
155 155 # Report tracebacks shouldn't use color in general (safer for users)
156 156 color_scheme = 'NoColor'
157 157
158 158 # Use this ONLY for developer debugging (keep commented out for release)
159 159 #color_scheme = 'Linux' # dbg
160 160 try:
161 161 rptdir = self.app.ipython_dir
162 162 except:
163 163 rptdir = Path.cwd()
164 164 if rptdir is None or not Path.is_dir(rptdir):
165 165 rptdir = Path.cwd()
166 166 report_name = rptdir / self.crash_report_fname
167 167 # write the report filename into the instance dict so it can get
168 168 # properly expanded out in the user message template
169 169 self.crash_report_fname = report_name
170 170 self.info['crash_report_fname'] = report_name
171 171 TBhandler = ultratb.VerboseTB(
172 172 color_scheme=color_scheme,
173 173 long_header=1,
174 174 call_pdb=self.call_pdb,
175 175 )
176 176 if self.call_pdb:
177 177 TBhandler(etype,evalue,etb)
178 178 return
179 179 else:
180 180 traceback = TBhandler.text(etype,evalue,etb,context=31)
181 181
182 182 # print traceback to screen
183 183 if self.show_crash_traceback:
184 184 print(traceback, file=sys.stderr)
185 185
186 186 # and generate a complete report on disk
187 187 try:
188 report = open(report_name, 'w', encoding='utf-8')
188 report = open(report_name, "w", encoding="utf-8")
189 189 except:
190 190 print('Could not create crash report on disk.', file=sys.stderr)
191 191 return
192 192
193 193 with report:
194 194 # Inform user on stderr of what happened
195 195 print('\n'+'*'*70+'\n', file=sys.stderr)
196 196 print(self.message_template.format(**self.info), file=sys.stderr)
197 197
198 198 # Construct report on disk
199 199 report.write(self.make_report(traceback))
200 200
201 201 input("Hit <Enter> to quit (your terminal may close):")
202 202
203 203 def make_report(self,traceback):
204 204 """Return a string containing a crash report."""
205 205
206 206 sec_sep = self.section_sep
207 207
208 208 report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
209 209 rpt_add = report.append
210 210 rpt_add(sys_info())
211 211
212 212 try:
213 213 config = pformat(self.app.config)
214 214 rpt_add(sec_sep)
215 215 rpt_add('Application name: %s\n\n' % self.app_name)
216 216 rpt_add('Current user configuration structure:\n\n')
217 217 rpt_add(config)
218 218 except:
219 219 pass
220 220 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
221 221
222 222 return ''.join(report)
223 223
224 224
225 225 def crash_handler_lite(etype, evalue, tb):
226 226 """a light excepthook, adding a small message to the usual traceback"""
227 227 traceback.print_exception(etype, evalue, tb)
228 228
229 229 from IPython.core.interactiveshell import InteractiveShell
230 230 if InteractiveShell.initialized():
231 231 # we are in a Shell environment, give %magic example
232 232 config = "%config "
233 233 else:
234 234 # we are not in a shell, show generic config
235 235 config = "c."
236 236 print(_lite_message_template.format(email=author_email, config=config, version=version), file=sys.stderr)
237 237
@@ -1,1274 +1,1277 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Top-level display functions for displaying object in different formats."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7
8 8 from binascii import b2a_base64, hexlify
9 9 import html
10 10 import json
11 11 import mimetypes
12 12 import os
13 13 import struct
14 14 import warnings
15 15 from copy import deepcopy
16 16 from os.path import splitext
17 17 from pathlib import Path, PurePath
18 18
19 19 from IPython.utils.py3compat import cast_unicode
20 20 from IPython.testing.skipdoctest import skip_doctest
21 21 from . import display_functions
22 22
23 23
24 24 __all__ = ['display_pretty', 'display_html', 'display_markdown',
25 25 'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json',
26 26 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject',
27 27 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'ProgressBar', 'JSON',
28 28 'GeoJSON', 'Javascript', 'Image', 'set_matplotlib_formats',
29 29 'set_matplotlib_close',
30 30 'Video']
31 31
32 32 _deprecated_names = ["display", "clear_output", "publish_display_data", "update_display", "DisplayHandle"]
33 33
34 34 __all__ = __all__ + _deprecated_names
35 35
36 36
37 37 # ----- warn to import from IPython.display -----
38 38
39 39 from warnings import warn
40 40
41 41
42 42 def __getattr__(name):
43 43 if name in _deprecated_names:
44 44 warn(f"Importing {name} from IPython.core.display is deprecated since IPython 7.14, please import from IPython display", DeprecationWarning, stacklevel=2)
45 45 return getattr(display_functions, name)
46 46
47 47 if name in globals().keys():
48 48 return globals()[name]
49 49 else:
50 50 raise AttributeError(f"module {__name__} has no attribute {name}")
51 51
52 52
53 53 #-----------------------------------------------------------------------------
54 54 # utility functions
55 55 #-----------------------------------------------------------------------------
56 56
57 57 def _safe_exists(path):
58 58 """Check path, but don't let exceptions raise"""
59 59 try:
60 60 return os.path.exists(path)
61 61 except Exception:
62 62 return False
63 63
64 64
65 65 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
66 66 """internal implementation of all display_foo methods
67 67
68 68 Parameters
69 69 ----------
70 70 mimetype : str
71 71 The mimetype to be published (e.g. 'image/png')
72 72 *objs : object
73 73 The Python objects to display, or if raw=True raw text data to
74 74 display.
75 75 raw : bool
76 76 Are the data objects raw data or Python objects that need to be
77 77 formatted before display? [default: False]
78 78 metadata : dict (optional)
79 79 Metadata to be associated with the specific mimetype output.
80 80 """
81 81 if metadata:
82 82 metadata = {mimetype: metadata}
83 83 if raw:
84 84 # turn list of pngdata into list of { 'image/png': pngdata }
85 85 objs = [ {mimetype: obj} for obj in objs ]
86 86 display_functions.display(*objs, raw=raw, metadata=metadata, include=[mimetype])
87 87
88 88 #-----------------------------------------------------------------------------
89 89 # Main functions
90 90 #-----------------------------------------------------------------------------
91 91
92 92
93 93 def display_pretty(*objs, **kwargs):
94 94 """Display the pretty (default) representation of an object.
95 95
96 96 Parameters
97 97 ----------
98 98 *objs : object
99 99 The Python objects to display, or if raw=True raw text data to
100 100 display.
101 101 raw : bool
102 102 Are the data objects raw data or Python objects that need to be
103 103 formatted before display? [default: False]
104 104 metadata : dict (optional)
105 105 Metadata to be associated with the specific mimetype output.
106 106 """
107 107 _display_mimetype('text/plain', objs, **kwargs)
108 108
109 109
110 110 def display_html(*objs, **kwargs):
111 111 """Display the HTML representation of an object.
112 112
113 113 Note: If raw=False and the object does not have a HTML
114 114 representation, no HTML will be shown.
115 115
116 116 Parameters
117 117 ----------
118 118 *objs : object
119 119 The Python objects to display, or if raw=True raw HTML data to
120 120 display.
121 121 raw : bool
122 122 Are the data objects raw data or Python objects that need to be
123 123 formatted before display? [default: False]
124 124 metadata : dict (optional)
125 125 Metadata to be associated with the specific mimetype output.
126 126 """
127 127 _display_mimetype('text/html', objs, **kwargs)
128 128
129 129
130 130 def display_markdown(*objs, **kwargs):
131 131 """Displays the Markdown representation of an object.
132 132
133 133 Parameters
134 134 ----------
135 135 *objs : object
136 136 The Python objects to display, or if raw=True raw markdown data to
137 137 display.
138 138 raw : bool
139 139 Are the data objects raw data or Python objects that need to be
140 140 formatted before display? [default: False]
141 141 metadata : dict (optional)
142 142 Metadata to be associated with the specific mimetype output.
143 143 """
144 144
145 145 _display_mimetype('text/markdown', objs, **kwargs)
146 146
147 147
148 148 def display_svg(*objs, **kwargs):
149 149 """Display the SVG representation of an object.
150 150
151 151 Parameters
152 152 ----------
153 153 *objs : object
154 154 The Python objects to display, or if raw=True raw svg data to
155 155 display.
156 156 raw : bool
157 157 Are the data objects raw data or Python objects that need to be
158 158 formatted before display? [default: False]
159 159 metadata : dict (optional)
160 160 Metadata to be associated with the specific mimetype output.
161 161 """
162 162 _display_mimetype('image/svg+xml', objs, **kwargs)
163 163
164 164
165 165 def display_png(*objs, **kwargs):
166 166 """Display the PNG representation of an object.
167 167
168 168 Parameters
169 169 ----------
170 170 *objs : object
171 171 The Python objects to display, or if raw=True raw png data to
172 172 display.
173 173 raw : bool
174 174 Are the data objects raw data or Python objects that need to be
175 175 formatted before display? [default: False]
176 176 metadata : dict (optional)
177 177 Metadata to be associated with the specific mimetype output.
178 178 """
179 179 _display_mimetype('image/png', objs, **kwargs)
180 180
181 181
182 182 def display_jpeg(*objs, **kwargs):
183 183 """Display the JPEG representation of an object.
184 184
185 185 Parameters
186 186 ----------
187 187 *objs : object
188 188 The Python objects to display, or if raw=True raw JPEG data to
189 189 display.
190 190 raw : bool
191 191 Are the data objects raw data or Python objects that need to be
192 192 formatted before display? [default: False]
193 193 metadata : dict (optional)
194 194 Metadata to be associated with the specific mimetype output.
195 195 """
196 196 _display_mimetype('image/jpeg', objs, **kwargs)
197 197
198 198
199 199 def display_latex(*objs, **kwargs):
200 200 """Display the LaTeX representation of an object.
201 201
202 202 Parameters
203 203 ----------
204 204 *objs : object
205 205 The Python objects to display, or if raw=True raw latex data to
206 206 display.
207 207 raw : bool
208 208 Are the data objects raw data or Python objects that need to be
209 209 formatted before display? [default: False]
210 210 metadata : dict (optional)
211 211 Metadata to be associated with the specific mimetype output.
212 212 """
213 213 _display_mimetype('text/latex', objs, **kwargs)
214 214
215 215
216 216 def display_json(*objs, **kwargs):
217 217 """Display the JSON representation of an object.
218 218
219 219 Note that not many frontends support displaying JSON.
220 220
221 221 Parameters
222 222 ----------
223 223 *objs : object
224 224 The Python objects to display, or if raw=True raw json data to
225 225 display.
226 226 raw : bool
227 227 Are the data objects raw data or Python objects that need to be
228 228 formatted before display? [default: False]
229 229 metadata : dict (optional)
230 230 Metadata to be associated with the specific mimetype output.
231 231 """
232 232 _display_mimetype('application/json', objs, **kwargs)
233 233
234 234
235 235 def display_javascript(*objs, **kwargs):
236 236 """Display the Javascript representation of an object.
237 237
238 238 Parameters
239 239 ----------
240 240 *objs : object
241 241 The Python objects to display, or if raw=True raw javascript data to
242 242 display.
243 243 raw : bool
244 244 Are the data objects raw data or Python objects that need to be
245 245 formatted before display? [default: False]
246 246 metadata : dict (optional)
247 247 Metadata to be associated with the specific mimetype output.
248 248 """
249 249 _display_mimetype('application/javascript', objs, **kwargs)
250 250
251 251
252 252 def display_pdf(*objs, **kwargs):
253 253 """Display the PDF representation of an object.
254 254
255 255 Parameters
256 256 ----------
257 257 *objs : object
258 258 The Python objects to display, or if raw=True raw javascript data to
259 259 display.
260 260 raw : bool
261 261 Are the data objects raw data or Python objects that need to be
262 262 formatted before display? [default: False]
263 263 metadata : dict (optional)
264 264 Metadata to be associated with the specific mimetype output.
265 265 """
266 266 _display_mimetype('application/pdf', objs, **kwargs)
267 267
268 268
269 269 #-----------------------------------------------------------------------------
270 270 # Smart classes
271 271 #-----------------------------------------------------------------------------
272 272
273 273
274 274 class DisplayObject(object):
275 275 """An object that wraps data to be displayed."""
276 276
277 277 _read_flags = 'r'
278 278 _show_mem_addr = False
279 279 metadata = None
280 280
281 281 def __init__(self, data=None, url=None, filename=None, metadata=None):
282 282 """Create a display object given raw data.
283 283
284 284 When this object is returned by an expression or passed to the
285 285 display function, it will result in the data being displayed
286 286 in the frontend. The MIME type of the data should match the
287 287 subclasses used, so the Png subclass should be used for 'image/png'
288 288 data. If the data is a URL, the data will first be downloaded
289 289 and then displayed. If
290 290
291 291 Parameters
292 292 ----------
293 293 data : unicode, str or bytes
294 294 The raw data or a URL or file to load the data from
295 295 url : unicode
296 296 A URL to download the data from.
297 297 filename : unicode
298 298 Path to a local file to load the data from.
299 299 metadata : dict
300 300 Dict of metadata associated to be the object when displayed
301 301 """
302 302 if isinstance(data, (Path, PurePath)):
303 303 data = str(data)
304 304
305 305 if data is not None and isinstance(data, str):
306 306 if data.startswith('http') and url is None:
307 307 url = data
308 308 filename = None
309 309 data = None
310 310 elif _safe_exists(data) and filename is None:
311 311 url = None
312 312 filename = data
313 313 data = None
314 314
315 315 self.url = url
316 316 self.filename = filename
317 317 # because of @data.setter methods in
318 318 # subclasses ensure url and filename are set
319 319 # before assigning to self.data
320 320 self.data = data
321 321
322 322 if metadata is not None:
323 323 self.metadata = metadata
324 324 elif self.metadata is None:
325 325 self.metadata = {}
326 326
327 327 self.reload()
328 328 self._check_data()
329 329
330 330 def __repr__(self):
331 331 if not self._show_mem_addr:
332 332 cls = self.__class__
333 333 r = "<%s.%s object>" % (cls.__module__, cls.__name__)
334 334 else:
335 335 r = super(DisplayObject, self).__repr__()
336 336 return r
337 337
338 338 def _check_data(self):
339 339 """Override in subclasses if there's something to check."""
340 340 pass
341 341
342 342 def _data_and_metadata(self):
343 343 """shortcut for returning metadata with shape information, if defined"""
344 344 if self.metadata:
345 345 return self.data, deepcopy(self.metadata)
346 346 else:
347 347 return self.data
348 348
349 349 def reload(self):
350 350 """Reload the raw data from file or URL."""
351 351 if self.filename is not None:
352 encoding = None if 'b' in self._read_flags else 'utf-8'
352 encoding = None if "b" in self._read_flags else "utf-8"
353 353 with open(self.filename, self._read_flags, encoding=encoding) as f:
354 354 self.data = f.read()
355 355 elif self.url is not None:
356 356 # Deferred import
357 357 from urllib.request import urlopen
358 358 response = urlopen(self.url)
359 359 data = response.read()
360 360 # extract encoding from header, if there is one:
361 361 encoding = None
362 362 if 'content-type' in response.headers:
363 363 for sub in response.headers['content-type'].split(';'):
364 364 sub = sub.strip()
365 365 if sub.startswith('charset'):
366 366 encoding = sub.split('=')[-1].strip()
367 367 break
368 368 if 'content-encoding' in response.headers:
369 369 # TODO: do deflate?
370 370 if 'gzip' in response.headers['content-encoding']:
371 371 import gzip
372 372 from io import BytesIO
373
373 374 # assume utf-8 if encoding is not specified
374 with gzip.open(BytesIO(data), 'rt', encoding=encoding or 'utf-8') as fp:
375 with gzip.open(
376 BytesIO(data), "rt", encoding=encoding or "utf-8"
377 ) as fp:
375 378 encoding = None
376 379 data = fp.read()
377 380
378 381 # decode data, if an encoding was specified
379 382 # We only touch self.data once since
380 383 # subclasses such as SVG have @data.setter methods
381 384 # that transform self.data into ... well svg.
382 385 if encoding:
383 386 self.data = data.decode(encoding, 'replace')
384 387 else:
385 388 self.data = data
386 389
387 390
388 391 class TextDisplayObject(DisplayObject):
389 392 """Validate that display data is text"""
390 393 def _check_data(self):
391 394 if self.data is not None and not isinstance(self.data, str):
392 395 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
393 396
394 397 class Pretty(TextDisplayObject):
395 398
396 399 def _repr_pretty_(self, pp, cycle):
397 400 return pp.text(self.data)
398 401
399 402
400 403 class HTML(TextDisplayObject):
401 404
402 405 def __init__(self, data=None, url=None, filename=None, metadata=None):
403 406 def warn():
404 407 if not data:
405 408 return False
406 409
407 410 #
408 411 # Avoid calling lower() on the entire data, because it could be a
409 412 # long string and we're only interested in its beginning and end.
410 413 #
411 414 prefix = data[:10].lower()
412 415 suffix = data[-10:].lower()
413 416 return prefix.startswith("<iframe ") and suffix.endswith("</iframe>")
414 417
415 418 if warn():
416 419 warnings.warn("Consider using IPython.display.IFrame instead")
417 420 super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata)
418 421
419 422 def _repr_html_(self):
420 423 return self._data_and_metadata()
421 424
422 425 def __html__(self):
423 426 """
424 427 This method exists to inform other HTML-using modules (e.g. Markupsafe,
425 428 htmltag, etc) that this object is HTML and does not need things like
426 429 special characters (<>&) escaped.
427 430 """
428 431 return self._repr_html_()
429 432
430 433
431 434 class Markdown(TextDisplayObject):
432 435
433 436 def _repr_markdown_(self):
434 437 return self._data_and_metadata()
435 438
436 439
437 440 class Math(TextDisplayObject):
438 441
439 442 def _repr_latex_(self):
440 443 s = r"$\displaystyle %s$" % self.data.strip('$')
441 444 if self.metadata:
442 445 return s, deepcopy(self.metadata)
443 446 else:
444 447 return s
445 448
446 449
447 450 class Latex(TextDisplayObject):
448 451
449 452 def _repr_latex_(self):
450 453 return self._data_and_metadata()
451 454
452 455
453 456 class SVG(DisplayObject):
454 457 """Embed an SVG into the display.
455 458
456 459 Note if you just want to view a svg image via a URL use `:class:Image` with
457 460 a url=URL keyword argument.
458 461 """
459 462
460 463 _read_flags = 'rb'
461 464 # wrap data in a property, which extracts the <svg> tag, discarding
462 465 # document headers
463 466 _data = None
464 467
465 468 @property
466 469 def data(self):
467 470 return self._data
468 471
469 472 @data.setter
470 473 def data(self, svg):
471 474 if svg is None:
472 475 self._data = None
473 476 return
474 477 # parse into dom object
475 478 from xml.dom import minidom
476 479 x = minidom.parseString(svg)
477 480 # get svg tag (should be 1)
478 481 found_svg = x.getElementsByTagName('svg')
479 482 if found_svg:
480 483 svg = found_svg[0].toxml()
481 484 else:
482 485 # fallback on the input, trust the user
483 486 # but this is probably an error.
484 487 pass
485 488 svg = cast_unicode(svg)
486 489 self._data = svg
487 490
488 491 def _repr_svg_(self):
489 492 return self._data_and_metadata()
490 493
491 494 class ProgressBar(DisplayObject):
492 495 """Progressbar supports displaying a progressbar like element
493 496 """
494 497 def __init__(self, total):
495 498 """Creates a new progressbar
496 499
497 500 Parameters
498 501 ----------
499 502 total : int
500 503 maximum size of the progressbar
501 504 """
502 505 self.total = total
503 506 self._progress = 0
504 507 self.html_width = '60ex'
505 508 self.text_width = 60
506 509 self._display_id = hexlify(os.urandom(8)).decode('ascii')
507 510
508 511 def __repr__(self):
509 512 fraction = self.progress / self.total
510 513 filled = '=' * int(fraction * self.text_width)
511 514 rest = ' ' * (self.text_width - len(filled))
512 515 return '[{}{}] {}/{}'.format(
513 516 filled, rest,
514 517 self.progress, self.total,
515 518 )
516 519
517 520 def _repr_html_(self):
518 521 return "<progress style='width:{}' max='{}' value='{}'></progress>".format(
519 522 self.html_width, self.total, self.progress)
520 523
521 524 def display(self):
522 525 display_functions.display(self, display_id=self._display_id)
523 526
524 527 def update(self):
525 528 display_functions.display(self, display_id=self._display_id, update=True)
526 529
527 530 @property
528 531 def progress(self):
529 532 return self._progress
530 533
531 534 @progress.setter
532 535 def progress(self, value):
533 536 self._progress = value
534 537 self.update()
535 538
536 539 def __iter__(self):
537 540 self.display()
538 541 self._progress = -1 # First iteration is 0
539 542 return self
540 543
541 544 def __next__(self):
542 545 """Returns current value and increments display by one."""
543 546 self.progress += 1
544 547 if self.progress < self.total:
545 548 return self.progress
546 549 else:
547 550 raise StopIteration()
548 551
549 552 class JSON(DisplayObject):
550 553 """JSON expects a JSON-able dict or list
551 554
552 555 not an already-serialized JSON string.
553 556
554 557 Scalar types (None, number, string) are not allowed, only dict or list containers.
555 558 """
556 559 # wrap data in a property, which warns about passing already-serialized JSON
557 560 _data = None
558 561 def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, root='root', **kwargs):
559 562 """Create a JSON display object given raw data.
560 563
561 564 Parameters
562 565 ----------
563 566 data : dict or list
564 567 JSON data to display. Not an already-serialized JSON string.
565 568 Scalar types (None, number, string) are not allowed, only dict
566 569 or list containers.
567 570 url : unicode
568 571 A URL to download the data from.
569 572 filename : unicode
570 573 Path to a local file to load the data from.
571 574 expanded : boolean
572 575 Metadata to control whether a JSON display component is expanded.
573 576 metadata : dict
574 577 Specify extra metadata to attach to the json display object.
575 578 root : str
576 579 The name of the root element of the JSON tree
577 580 """
578 581 self.metadata = {
579 582 'expanded': expanded,
580 583 'root': root,
581 584 }
582 585 if metadata:
583 586 self.metadata.update(metadata)
584 587 if kwargs:
585 588 self.metadata.update(kwargs)
586 589 super(JSON, self).__init__(data=data, url=url, filename=filename)
587 590
588 591 def _check_data(self):
589 592 if self.data is not None and not isinstance(self.data, (dict, list)):
590 593 raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data))
591 594
592 595 @property
593 596 def data(self):
594 597 return self._data
595 598
596 599 @data.setter
597 600 def data(self, data):
598 601 if isinstance(data, (Path, PurePath)):
599 602 data = str(data)
600 603
601 604 if isinstance(data, str):
602 605 if self.filename is None and self.url is None:
603 606 warnings.warn("JSON expects JSONable dict or list, not JSON strings")
604 607 data = json.loads(data)
605 608 self._data = data
606 609
607 610 def _data_and_metadata(self):
608 611 return self.data, self.metadata
609 612
610 613 def _repr_json_(self):
611 614 return self._data_and_metadata()
612 615
613 616 _css_t = """var link = document.createElement("link");
614 617 link.ref = "stylesheet";
615 618 link.type = "text/css";
616 619 link.href = "%s";
617 620 document.head.appendChild(link);
618 621 """
619 622
620 623 _lib_t1 = """new Promise(function(resolve, reject) {
621 624 var script = document.createElement("script");
622 625 script.onload = resolve;
623 626 script.onerror = reject;
624 627 script.src = "%s";
625 628 document.head.appendChild(script);
626 629 }).then(() => {
627 630 """
628 631
629 632 _lib_t2 = """
630 633 });"""
631 634
632 635 class GeoJSON(JSON):
633 636 """GeoJSON expects JSON-able dict
634 637
635 638 not an already-serialized JSON string.
636 639
637 640 Scalar types (None, number, string) are not allowed, only dict containers.
638 641 """
639 642
640 643 def __init__(self, *args, **kwargs):
641 644 """Create a GeoJSON display object given raw data.
642 645
643 646 Parameters
644 647 ----------
645 648 data : dict or list
646 649 VegaLite data. Not an already-serialized JSON string.
647 650 Scalar types (None, number, string) are not allowed, only dict
648 651 or list containers.
649 652 url_template : string
650 653 Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template
651 654 layer_options : dict
652 655 Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options
653 656 url : unicode
654 657 A URL to download the data from.
655 658 filename : unicode
656 659 Path to a local file to load the data from.
657 660 metadata : dict
658 661 Specify extra metadata to attach to the json display object.
659 662
660 663 Examples
661 664 --------
662 665 The following will display an interactive map of Mars with a point of
663 666 interest on frontend that do support GeoJSON display.
664 667
665 668 >>> from IPython.display import GeoJSON
666 669
667 670 >>> GeoJSON(data={
668 671 ... "type": "Feature",
669 672 ... "geometry": {
670 673 ... "type": "Point",
671 674 ... "coordinates": [-81.327, 296.038]
672 675 ... }
673 676 ... },
674 677 ... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
675 678 ... layer_options={
676 679 ... "basemap_id": "celestia_mars-shaded-16k_global",
677 680 ... "attribution" : "Celestia/praesepe",
678 681 ... "minZoom" : 0,
679 682 ... "maxZoom" : 18,
680 683 ... })
681 684 <IPython.core.display.GeoJSON object>
682 685
683 686 In the terminal IPython, you will only see the text representation of
684 687 the GeoJSON object.
685 688
686 689 """
687 690
688 691 super(GeoJSON, self).__init__(*args, **kwargs)
689 692
690 693
691 694 def _ipython_display_(self):
692 695 bundle = {
693 696 'application/geo+json': self.data,
694 697 'text/plain': '<IPython.display.GeoJSON object>'
695 698 }
696 699 metadata = {
697 700 'application/geo+json': self.metadata
698 701 }
699 702 display_functions.display(bundle, metadata=metadata, raw=True)
700 703
701 704 class Javascript(TextDisplayObject):
702 705
703 706 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
704 707 """Create a Javascript display object given raw data.
705 708
706 709 When this object is returned by an expression or passed to the
707 710 display function, it will result in the data being displayed
708 711 in the frontend. If the data is a URL, the data will first be
709 712 downloaded and then displayed.
710 713
711 714 In the Notebook, the containing element will be available as `element`,
712 715 and jQuery will be available. Content appended to `element` will be
713 716 visible in the output area.
714 717
715 718 Parameters
716 719 ----------
717 720 data : unicode, str or bytes
718 721 The Javascript source code or a URL to download it from.
719 722 url : unicode
720 723 A URL to download the data from.
721 724 filename : unicode
722 725 Path to a local file to load the data from.
723 726 lib : list or str
724 727 A sequence of Javascript library URLs to load asynchronously before
725 728 running the source code. The full URLs of the libraries should
726 729 be given. A single Javascript library URL can also be given as a
727 730 string.
728 731 css : list or str
729 732 A sequence of css files to load before running the source code.
730 733 The full URLs of the css files should be given. A single css URL
731 734 can also be given as a string.
732 735 """
733 736 if isinstance(lib, str):
734 737 lib = [lib]
735 738 elif lib is None:
736 739 lib = []
737 740 if isinstance(css, str):
738 741 css = [css]
739 742 elif css is None:
740 743 css = []
741 744 if not isinstance(lib, (list,tuple)):
742 745 raise TypeError('expected sequence, got: %r' % lib)
743 746 if not isinstance(css, (list,tuple)):
744 747 raise TypeError('expected sequence, got: %r' % css)
745 748 self.lib = lib
746 749 self.css = css
747 750 super(Javascript, self).__init__(data=data, url=url, filename=filename)
748 751
749 752 def _repr_javascript_(self):
750 753 r = ''
751 754 for c in self.css:
752 755 r += _css_t % c
753 756 for l in self.lib:
754 757 r += _lib_t1 % l
755 758 r += self.data
756 759 r += _lib_t2*len(self.lib)
757 760 return r
758 761
759 762 # constants for identifying png/jpeg data
760 763 _PNG = b'\x89PNG\r\n\x1a\n'
761 764 _JPEG = b'\xff\xd8'
762 765
763 766 def _pngxy(data):
764 767 """read the (width, height) from a PNG header"""
765 768 ihdr = data.index(b'IHDR')
766 769 # next 8 bytes are width/height
767 770 return struct.unpack('>ii', data[ihdr+4:ihdr+12])
768 771
769 772 def _jpegxy(data):
770 773 """read the (width, height) from a JPEG header"""
771 774 # adapted from http://www.64lines.com/jpeg-width-height
772 775
773 776 idx = 4
774 777 while True:
775 778 block_size = struct.unpack('>H', data[idx:idx+2])[0]
776 779 idx = idx + block_size
777 780 if data[idx:idx+2] == b'\xFF\xC0':
778 781 # found Start of Frame
779 782 iSOF = idx
780 783 break
781 784 else:
782 785 # read another block
783 786 idx += 2
784 787
785 788 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
786 789 return w, h
787 790
788 791 def _gifxy(data):
789 792 """read the (width, height) from a GIF header"""
790 793 return struct.unpack('<HH', data[6:10])
791 794
792 795
793 796 class Image(DisplayObject):
794 797
795 798 _read_flags = 'rb'
796 799 _FMT_JPEG = u'jpeg'
797 800 _FMT_PNG = u'png'
798 801 _FMT_GIF = u'gif'
799 802 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF]
800 803 _MIMETYPES = {
801 804 _FMT_PNG: 'image/png',
802 805 _FMT_JPEG: 'image/jpeg',
803 806 _FMT_GIF: 'image/gif',
804 807 }
805 808
806 809 def __init__(
807 810 self,
808 811 data=None,
809 812 url=None,
810 813 filename=None,
811 814 format=None,
812 815 embed=None,
813 816 width=None,
814 817 height=None,
815 818 retina=False,
816 819 unconfined=False,
817 820 metadata=None,
818 821 alt=None,
819 822 ):
820 823 """Create a PNG/JPEG/GIF image object given raw data.
821 824
822 825 When this object is returned by an input cell or passed to the
823 826 display function, it will result in the image being displayed
824 827 in the frontend.
825 828
826 829 Parameters
827 830 ----------
828 831 data : unicode, str or bytes
829 832 The raw image data or a URL or filename to load the data from.
830 833 This always results in embedded image data.
831 834
832 835 url : unicode
833 836 A URL to download the data from. If you specify `url=`,
834 837 the image data will not be embedded unless you also specify `embed=True`.
835 838
836 839 filename : unicode
837 840 Path to a local file to load the data from.
838 841 Images from a file are always embedded.
839 842
840 843 format : unicode
841 844 The format of the image data (png/jpeg/jpg/gif). If a filename or URL is given
842 845 for format will be inferred from the filename extension.
843 846
844 847 embed : bool
845 848 Should the image data be embedded using a data URI (True) or be
846 849 loaded using an <img> tag. Set this to True if you want the image
847 850 to be viewable later with no internet connection in the notebook.
848 851
849 852 Default is `True`, unless the keyword argument `url` is set, then
850 853 default value is `False`.
851 854
852 855 Note that QtConsole is not able to display images if `embed` is set to `False`
853 856
854 857 width : int
855 858 Width in pixels to which to constrain the image in html
856 859
857 860 height : int
858 861 Height in pixels to which to constrain the image in html
859 862
860 863 retina : bool
861 864 Automatically set the width and height to half of the measured
862 865 width and height.
863 866 This only works for embedded images because it reads the width/height
864 867 from image data.
865 868 For non-embedded images, you can just set the desired display width
866 869 and height directly.
867 870
868 871 unconfined : bool
869 872 Set unconfined=True to disable max-width confinement of the image.
870 873
871 874 metadata : dict
872 875 Specify extra metadata to attach to the image.
873 876
874 877 alt : unicode
875 878 Alternative text for the image, for use by screen readers.
876 879
877 880 Examples
878 881 --------
879 882 embedded image data, works in qtconsole and notebook
880 883 when passed positionally, the first arg can be any of raw image data,
881 884 a URL, or a filename from which to load image data.
882 885 The result is always embedding image data for inline images.
883 886
884 887 >>> Image('http://www.google.fr/images/srpr/logo3w.png')
885 888 <IPython.core.display.Image object>
886 889
887 890 >>> Image('/path/to/image.jpg')
888 891 <IPython.core.display.Image object>
889 892
890 893 >>> Image(b'RAW_PNG_DATA...')
891 894 <IPython.core.display.Image object>
892 895
893 896 Specifying Image(url=...) does not embed the image data,
894 897 it only generates ``<img>`` tag with a link to the source.
895 898 This will not work in the qtconsole or offline.
896 899
897 900 >>> Image(url='http://www.google.fr/images/srpr/logo3w.png')
898 901 <IPython.core.display.Image object>
899 902
900 903 """
901 904 if isinstance(data, (Path, PurePath)):
902 905 data = str(data)
903 906
904 907 if filename is not None:
905 908 ext = self._find_ext(filename)
906 909 elif url is not None:
907 910 ext = self._find_ext(url)
908 911 elif data is None:
909 912 raise ValueError("No image data found. Expecting filename, url, or data.")
910 913 elif isinstance(data, str) and (
911 914 data.startswith('http') or _safe_exists(data)
912 915 ):
913 916 ext = self._find_ext(data)
914 917 else:
915 918 ext = None
916 919
917 920 if format is None:
918 921 if ext is not None:
919 922 if ext == u'jpg' or ext == u'jpeg':
920 923 format = self._FMT_JPEG
921 924 elif ext == u'png':
922 925 format = self._FMT_PNG
923 926 elif ext == u'gif':
924 927 format = self._FMT_GIF
925 928 else:
926 929 format = ext.lower()
927 930 elif isinstance(data, bytes):
928 931 # infer image type from image data header,
929 932 # only if format has not been specified.
930 933 if data[:2] == _JPEG:
931 934 format = self._FMT_JPEG
932 935
933 936 # failed to detect format, default png
934 937 if format is None:
935 938 format = self._FMT_PNG
936 939
937 940 if format.lower() == 'jpg':
938 941 # jpg->jpeg
939 942 format = self._FMT_JPEG
940 943
941 944 self.format = format.lower()
942 945 self.embed = embed if embed is not None else (url is None)
943 946
944 947 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
945 948 raise ValueError("Cannot embed the '%s' image format" % (self.format))
946 949 if self.embed:
947 950 self._mimetype = self._MIMETYPES.get(self.format)
948 951
949 952 self.width = width
950 953 self.height = height
951 954 self.retina = retina
952 955 self.unconfined = unconfined
953 956 self.alt = alt
954 957 super(Image, self).__init__(data=data, url=url, filename=filename,
955 958 metadata=metadata)
956 959
957 960 if self.width is None and self.metadata.get('width', {}):
958 961 self.width = metadata['width']
959 962
960 963 if self.height is None and self.metadata.get('height', {}):
961 964 self.height = metadata['height']
962 965
963 966 if self.alt is None and self.metadata.get("alt", {}):
964 967 self.alt = metadata["alt"]
965 968
966 969 if retina:
967 970 self._retina_shape()
968 971
969 972
970 973 def _retina_shape(self):
971 974 """load pixel-doubled width and height from image data"""
972 975 if not self.embed:
973 976 return
974 977 if self.format == self._FMT_PNG:
975 978 w, h = _pngxy(self.data)
976 979 elif self.format == self._FMT_JPEG:
977 980 w, h = _jpegxy(self.data)
978 981 elif self.format == self._FMT_GIF:
979 982 w, h = _gifxy(self.data)
980 983 else:
981 984 # retina only supports png
982 985 return
983 986 self.width = w // 2
984 987 self.height = h // 2
985 988
986 989 def reload(self):
987 990 """Reload the raw data from file or URL."""
988 991 if self.embed:
989 992 super(Image,self).reload()
990 993 if self.retina:
991 994 self._retina_shape()
992 995
993 996 def _repr_html_(self):
994 997 if not self.embed:
995 998 width = height = klass = alt = ""
996 999 if self.width:
997 1000 width = ' width="%d"' % self.width
998 1001 if self.height:
999 1002 height = ' height="%d"' % self.height
1000 1003 if self.unconfined:
1001 1004 klass = ' class="unconfined"'
1002 1005 if self.alt:
1003 1006 alt = ' alt="%s"' % html.escape(self.alt)
1004 1007 return '<img src="{url}"{width}{height}{klass}{alt}/>'.format(
1005 1008 url=self.url,
1006 1009 width=width,
1007 1010 height=height,
1008 1011 klass=klass,
1009 1012 alt=alt,
1010 1013 )
1011 1014
1012 1015 def _repr_mimebundle_(self, include=None, exclude=None):
1013 1016 """Return the image as a mimebundle
1014 1017
1015 1018 Any new mimetype support should be implemented here.
1016 1019 """
1017 1020 if self.embed:
1018 1021 mimetype = self._mimetype
1019 1022 data, metadata = self._data_and_metadata(always_both=True)
1020 1023 if metadata:
1021 1024 metadata = {mimetype: metadata}
1022 1025 return {mimetype: data}, metadata
1023 1026 else:
1024 1027 return {'text/html': self._repr_html_()}
1025 1028
1026 1029 def _data_and_metadata(self, always_both=False):
1027 1030 """shortcut for returning metadata with shape information, if defined"""
1028 1031 try:
1029 1032 b64_data = b2a_base64(self.data).decode('ascii')
1030 1033 except TypeError as e:
1031 1034 raise FileNotFoundError(
1032 1035 "No such file or directory: '%s'" % (self.data)) from e
1033 1036 md = {}
1034 1037 if self.metadata:
1035 1038 md.update(self.metadata)
1036 1039 if self.width:
1037 1040 md['width'] = self.width
1038 1041 if self.height:
1039 1042 md['height'] = self.height
1040 1043 if self.unconfined:
1041 1044 md['unconfined'] = self.unconfined
1042 1045 if self.alt:
1043 1046 md["alt"] = self.alt
1044 1047 if md or always_both:
1045 1048 return b64_data, md
1046 1049 else:
1047 1050 return b64_data
1048 1051
1049 1052 def _repr_png_(self):
1050 1053 if self.embed and self.format == self._FMT_PNG:
1051 1054 return self._data_and_metadata()
1052 1055
1053 1056 def _repr_jpeg_(self):
1054 1057 if self.embed and self.format == self._FMT_JPEG:
1055 1058 return self._data_and_metadata()
1056 1059
1057 1060 def _find_ext(self, s):
1058 1061 base, ext = splitext(s)
1059 1062
1060 1063 if not ext:
1061 1064 return base
1062 1065
1063 1066 # `splitext` includes leading period, so we skip it
1064 1067 return ext[1:].lower()
1065 1068
1066 1069
1067 1070 class Video(DisplayObject):
1068 1071
1069 1072 def __init__(self, data=None, url=None, filename=None, embed=False,
1070 1073 mimetype=None, width=None, height=None, html_attributes="controls"):
1071 1074 """Create a video object given raw data or an URL.
1072 1075
1073 1076 When this object is returned by an input cell or passed to the
1074 1077 display function, it will result in the video being displayed
1075 1078 in the frontend.
1076 1079
1077 1080 Parameters
1078 1081 ----------
1079 1082 data : unicode, str or bytes
1080 1083 The raw video data or a URL or filename to load the data from.
1081 1084 Raw data will require passing ``embed=True``.
1082 1085
1083 1086 url : unicode
1084 1087 A URL for the video. If you specify ``url=``,
1085 1088 the image data will not be embedded.
1086 1089
1087 1090 filename : unicode
1088 1091 Path to a local file containing the video.
1089 1092 Will be interpreted as a local URL unless ``embed=True``.
1090 1093
1091 1094 embed : bool
1092 1095 Should the video be embedded using a data URI (True) or be
1093 1096 loaded using a <video> tag (False).
1094 1097
1095 1098 Since videos are large, embedding them should be avoided, if possible.
1096 1099 You must confirm embedding as your intention by passing ``embed=True``.
1097 1100
1098 1101 Local files can be displayed with URLs without embedding the content, via::
1099 1102
1100 1103 Video('./video.mp4')
1101 1104
1102 1105 mimetype : unicode
1103 1106 Specify the mimetype for embedded videos.
1104 1107 Default will be guessed from file extension, if available.
1105 1108
1106 1109 width : int
1107 1110 Width in pixels to which to constrain the video in HTML.
1108 1111 If not supplied, defaults to the width of the video.
1109 1112
1110 1113 height : int
1111 1114 Height in pixels to which to constrain the video in html.
1112 1115 If not supplied, defaults to the height of the video.
1113 1116
1114 1117 html_attributes : str
1115 1118 Attributes for the HTML ``<video>`` block.
1116 1119 Default: ``"controls"`` to get video controls.
1117 1120 Other examples: ``"controls muted"`` for muted video with controls,
1118 1121 ``"loop autoplay"`` for looping autoplaying video without controls.
1119 1122
1120 1123 Examples
1121 1124 --------
1122 1125 ::
1123 1126
1124 1127 Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4')
1125 1128 Video('path/to/video.mp4')
1126 1129 Video('path/to/video.mp4', embed=True)
1127 1130 Video('path/to/video.mp4', embed=True, html_attributes="controls muted autoplay")
1128 1131 Video(b'raw-videodata', embed=True)
1129 1132 """
1130 1133 if isinstance(data, (Path, PurePath)):
1131 1134 data = str(data)
1132 1135
1133 1136 if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')):
1134 1137 url = data
1135 1138 data = None
1136 1139 elif data is not None and os.path.exists(data):
1137 1140 filename = data
1138 1141 data = None
1139 1142
1140 1143 if data and not embed:
1141 1144 msg = ''.join([
1142 1145 "To embed videos, you must pass embed=True ",
1143 1146 "(this may make your notebook files huge)\n",
1144 1147 "Consider passing Video(url='...')",
1145 1148 ])
1146 1149 raise ValueError(msg)
1147 1150
1148 1151 self.mimetype = mimetype
1149 1152 self.embed = embed
1150 1153 self.width = width
1151 1154 self.height = height
1152 1155 self.html_attributes = html_attributes
1153 1156 super(Video, self).__init__(data=data, url=url, filename=filename)
1154 1157
1155 1158 def _repr_html_(self):
1156 1159 width = height = ''
1157 1160 if self.width:
1158 1161 width = ' width="%d"' % self.width
1159 1162 if self.height:
1160 1163 height = ' height="%d"' % self.height
1161 1164
1162 1165 # External URLs and potentially local files are not embedded into the
1163 1166 # notebook output.
1164 1167 if not self.embed:
1165 1168 url = self.url if self.url is not None else self.filename
1166 1169 output = """<video src="{0}" {1} {2} {3}>
1167 1170 Your browser does not support the <code>video</code> element.
1168 1171 </video>""".format(url, self.html_attributes, width, height)
1169 1172 return output
1170 1173
1171 1174 # Embedded videos are base64-encoded.
1172 1175 mimetype = self.mimetype
1173 1176 if self.filename is not None:
1174 1177 if not mimetype:
1175 1178 mimetype, _ = mimetypes.guess_type(self.filename)
1176 1179
1177 1180 with open(self.filename, 'rb') as f:
1178 1181 video = f.read()
1179 1182 else:
1180 1183 video = self.data
1181 1184 if isinstance(video, str):
1182 1185 # unicode input is already b64-encoded
1183 1186 b64_video = video
1184 1187 else:
1185 1188 b64_video = b2a_base64(video).decode('ascii').rstrip()
1186 1189
1187 1190 output = """<video {0} {1} {2}>
1188 1191 <source src="data:{3};base64,{4}" type="{3}">
1189 1192 Your browser does not support the video tag.
1190 1193 </video>""".format(self.html_attributes, width, height, mimetype, b64_video)
1191 1194 return output
1192 1195
1193 1196 def reload(self):
1194 1197 # TODO
1195 1198 pass
1196 1199
1197 1200
1198 1201 @skip_doctest
1199 1202 def set_matplotlib_formats(*formats, **kwargs):
1200 1203 """
1201 1204 .. deprecated:: 7.23
1202 1205
1203 1206 use `matplotlib_inline.backend_inline.set_matplotlib_formats()`
1204 1207
1205 1208 Select figure formats for the inline backend. Optionally pass quality for JPEG.
1206 1209
1207 1210 For example, this enables PNG and JPEG output with a JPEG quality of 90%::
1208 1211
1209 1212 In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
1210 1213
1211 1214 To set this in your config files use the following::
1212 1215
1213 1216 c.InlineBackend.figure_formats = {'png', 'jpeg'}
1214 1217 c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
1215 1218
1216 1219 Parameters
1217 1220 ----------
1218 1221 *formats : strs
1219 1222 One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
1220 1223 **kwargs
1221 1224 Keyword args will be relayed to ``figure.canvas.print_figure``.
1222 1225 """
1223 1226 warnings.warn(
1224 1227 "`set_matplotlib_formats` is deprecated since IPython 7.23, directly "
1225 1228 "use `matplotlib_inline.backend_inline.set_matplotlib_formats()`",
1226 1229 DeprecationWarning,
1227 1230 stacklevel=2,
1228 1231 )
1229 1232
1230 1233 from matplotlib_inline.backend_inline import (
1231 1234 set_matplotlib_formats as set_matplotlib_formats_orig,
1232 1235 )
1233 1236
1234 1237 set_matplotlib_formats_orig(*formats, **kwargs)
1235 1238
1236 1239 @skip_doctest
1237 1240 def set_matplotlib_close(close=True):
1238 1241 """
1239 1242 .. deprecated:: 7.23
1240 1243
1241 1244 use `matplotlib_inline.backend_inline.set_matplotlib_close()`
1242 1245
1243 1246 Set whether the inline backend closes all figures automatically or not.
1244 1247
1245 1248 By default, the inline backend used in the IPython Notebook will close all
1246 1249 matplotlib figures automatically after each cell is run. This means that
1247 1250 plots in different cells won't interfere. Sometimes, you may want to make
1248 1251 a plot in one cell and then refine it in later cells. This can be accomplished
1249 1252 by::
1250 1253
1251 1254 In [1]: set_matplotlib_close(False)
1252 1255
1253 1256 To set this in your config files use the following::
1254 1257
1255 1258 c.InlineBackend.close_figures = False
1256 1259
1257 1260 Parameters
1258 1261 ----------
1259 1262 close : bool
1260 1263 Should all matplotlib figures be automatically closed after each cell is
1261 1264 run?
1262 1265 """
1263 1266 warnings.warn(
1264 1267 "`set_matplotlib_close` is deprecated since IPython 7.23, directly "
1265 1268 "use `matplotlib_inline.backend_inline.set_matplotlib_close()`",
1266 1269 DeprecationWarning,
1267 1270 stacklevel=2,
1268 1271 )
1269 1272
1270 1273 from matplotlib_inline.backend_inline import (
1271 1274 set_matplotlib_close as set_matplotlib_close_orig,
1272 1275 )
1273 1276
1274 1277 set_matplotlib_close_orig(close)
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,755 +1,755 b''
1 1 """Implementation of code management magic functions.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (c) 2012 The IPython Development Team.
5 5 #
6 6 # Distributed under the terms of the Modified BSD License.
7 7 #
8 8 # The full license is in the file COPYING.txt, distributed with this software.
9 9 #-----------------------------------------------------------------------------
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Imports
13 13 #-----------------------------------------------------------------------------
14 14
15 15 # Stdlib
16 16 import inspect
17 17 import io
18 18 import os
19 19 import re
20 20 import sys
21 21 import ast
22 22 from itertools import chain
23 23 from urllib.request import Request, urlopen
24 24 from urllib.parse import urlencode
25 25 from pathlib import Path
26 26
27 27 # Our own packages
28 28 from IPython.core.error import TryNext, StdinNotImplementedError, UsageError
29 29 from IPython.core.macro import Macro
30 30 from IPython.core.magic import Magics, magics_class, line_magic
31 31 from IPython.core.oinspect import find_file, find_source_lines
32 32 from IPython.core.release import version
33 33 from IPython.testing.skipdoctest import skip_doctest
34 34 from IPython.utils.contexts import preserve_keys
35 35 from IPython.utils.path import get_py_filename
36 36 from warnings import warn
37 37 from logging import error
38 38 from IPython.utils.text import get_text_list
39 39
40 40 #-----------------------------------------------------------------------------
41 41 # Magic implementation classes
42 42 #-----------------------------------------------------------------------------
43 43
44 44 # Used for exception handling in magic_edit
45 45 class MacroToEdit(ValueError): pass
46 46
47 47 ipython_input_pat = re.compile(r"<ipython\-input\-(\d+)-[a-z\d]+>$")
48 48
49 49 # To match, e.g. 8-10 1:5 :10 3-
50 50 range_re = re.compile(r"""
51 51 (?P<start>\d+)?
52 52 ((?P<sep>[\-:])
53 53 (?P<end>\d+)?)?
54 54 $""", re.VERBOSE)
55 55
56 56
57 57 def extract_code_ranges(ranges_str):
58 58 """Turn a string of range for %%load into 2-tuples of (start, stop)
59 59 ready to use as a slice of the content split by lines.
60 60
61 61 Examples
62 62 --------
63 63 list(extract_input_ranges("5-10 2"))
64 64 [(4, 10), (1, 2)]
65 65 """
66 66 for range_str in ranges_str.split():
67 67 rmatch = range_re.match(range_str)
68 68 if not rmatch:
69 69 continue
70 70 sep = rmatch.group("sep")
71 71 start = rmatch.group("start")
72 72 end = rmatch.group("end")
73 73
74 74 if sep == '-':
75 75 start = int(start) - 1 if start else None
76 76 end = int(end) if end else None
77 77 elif sep == ':':
78 78 start = int(start) - 1 if start else None
79 79 end = int(end) - 1 if end else None
80 80 else:
81 81 end = int(start)
82 82 start = int(start) - 1
83 83 yield (start, end)
84 84
85 85
86 86 def extract_symbols(code, symbols):
87 87 """
88 88 Return a tuple (blocks, not_found)
89 89 where ``blocks`` is a list of code fragments
90 90 for each symbol parsed from code, and ``not_found`` are
91 91 symbols not found in the code.
92 92
93 93 For example::
94 94
95 95 In [1]: code = '''a = 10
96 96 ...: def b(): return 42
97 97 ...: class A: pass'''
98 98
99 99 In [2]: extract_symbols(code, 'A,b,z')
100 100 Out[2]: (['class A: pass\\n', 'def b(): return 42\\n'], ['z'])
101 101 """
102 102 symbols = symbols.split(',')
103 103
104 104 # this will raise SyntaxError if code isn't valid Python
105 105 py_code = ast.parse(code)
106 106
107 107 marks = [(getattr(s, 'name', None), s.lineno) for s in py_code.body]
108 108 code = code.split('\n')
109 109
110 110 symbols_lines = {}
111 111
112 112 # we already know the start_lineno of each symbol (marks).
113 113 # To find each end_lineno, we traverse in reverse order until each
114 114 # non-blank line
115 115 end = len(code)
116 116 for name, start in reversed(marks):
117 117 while not code[end - 1].strip():
118 118 end -= 1
119 119 if name:
120 120 symbols_lines[name] = (start - 1, end)
121 121 end = start - 1
122 122
123 123 # Now symbols_lines is a map
124 124 # {'symbol_name': (start_lineno, end_lineno), ...}
125 125
126 126 # fill a list with chunks of codes for each requested symbol
127 127 blocks = []
128 128 not_found = []
129 129 for symbol in symbols:
130 130 if symbol in symbols_lines:
131 131 start, end = symbols_lines[symbol]
132 132 blocks.append('\n'.join(code[start:end]) + '\n')
133 133 else:
134 134 not_found.append(symbol)
135 135
136 136 return blocks, not_found
137 137
138 138 def strip_initial_indent(lines):
139 139 """For %load, strip indent from lines until finding an unindented line.
140 140
141 141 https://github.com/ipython/ipython/issues/9775
142 142 """
143 143 indent_re = re.compile(r'\s+')
144 144
145 145 it = iter(lines)
146 146 first_line = next(it)
147 147 indent_match = indent_re.match(first_line)
148 148
149 149 if indent_match:
150 150 # First line was indented
151 151 indent = indent_match.group()
152 152 yield first_line[len(indent):]
153 153
154 154 for line in it:
155 155 if line.startswith(indent):
156 156 yield line[len(indent):]
157 157 else:
158 158 # Less indented than the first line - stop dedenting
159 159 yield line
160 160 break
161 161 else:
162 162 yield first_line
163 163
164 164 # Pass the remaining lines through without dedenting
165 165 for line in it:
166 166 yield line
167 167
168 168
169 169 class InteractivelyDefined(Exception):
170 170 """Exception for interactively defined variable in magic_edit"""
171 171 def __init__(self, index):
172 172 self.index = index
173 173
174 174
175 175 @magics_class
176 176 class CodeMagics(Magics):
177 177 """Magics related to code management (loading, saving, editing, ...)."""
178 178
179 179 def __init__(self, *args, **kwargs):
180 180 self._knowntemps = set()
181 181 super(CodeMagics, self).__init__(*args, **kwargs)
182 182
183 183 @line_magic
184 184 def save(self, parameter_s=''):
185 185 """Save a set of lines or a macro to a given filename.
186 186
187 187 Usage:\\
188 188 %save [options] filename [history]
189 189
190 190 Options:
191 191
192 192 -r: use 'raw' input. By default, the 'processed' history is used,
193 193 so that magics are loaded in their transformed version to valid
194 194 Python. If this option is given, the raw input as typed as the
195 195 command line is used instead.
196 196
197 197 -f: force overwrite. If file exists, %save will prompt for overwrite
198 198 unless -f is given.
199 199
200 200 -a: append to the file instead of overwriting it.
201 201
202 202 The history argument uses the same syntax as %history for input ranges,
203 203 then saves the lines to the filename you specify.
204 204
205 205 If no ranges are specified, saves history of the current session up to
206 206 this point.
207 207
208 208 It adds a '.py' extension to the file if you don't do so yourself, and
209 209 it asks for confirmation before overwriting existing files.
210 210
211 211 If `-r` option is used, the default extension is `.ipy`.
212 212 """
213 213
214 214 opts,args = self.parse_options(parameter_s,'fra',mode='list')
215 215 if not args:
216 216 raise UsageError('Missing filename.')
217 217 raw = 'r' in opts
218 218 force = 'f' in opts
219 219 append = 'a' in opts
220 220 mode = 'a' if append else 'w'
221 221 ext = '.ipy' if raw else '.py'
222 222 fname, codefrom = args[0], " ".join(args[1:])
223 223 if not fname.endswith(('.py','.ipy')):
224 224 fname += ext
225 225 fname = os.path.expanduser(fname)
226 226 file_exists = os.path.isfile(fname)
227 227 if file_exists and not force and not append:
228 228 try:
229 229 overwrite = self.shell.ask_yes_no('File `%s` exists. Overwrite (y/[N])? ' % fname, default='n')
230 230 except StdinNotImplementedError:
231 231 print("File `%s` exists. Use `%%save -f %s` to force overwrite" % (fname, parameter_s))
232 232 return
233 233 if not overwrite :
234 234 print('Operation cancelled.')
235 235 return
236 236 try:
237 237 cmds = self.shell.find_user_code(codefrom,raw)
238 238 except (TypeError, ValueError) as e:
239 239 print(e.args[0])
240 240 return
241 241 with io.open(fname, mode, encoding="utf-8") as f:
242 242 if not file_exists or not append:
243 243 f.write("# coding: utf-8\n")
244 244 f.write(cmds)
245 245 # make sure we end on a newline
246 246 if not cmds.endswith('\n'):
247 247 f.write('\n')
248 248 print('The following commands were written to file `%s`:' % fname)
249 249 print(cmds)
250 250
251 251 @line_magic
252 252 def pastebin(self, parameter_s=''):
253 253 """Upload code to dpaste.com, returning the URL.
254 254
255 255 Usage:\\
256 256 %pastebin [-d "Custom description"][-e 24] 1-7
257 257
258 258 The argument can be an input history range, a filename, or the name of a
259 259 string or macro.
260 260
261 261 If no arguments are given, uploads the history of this session up to
262 262 this point.
263 263
264 264 Options:
265 265
266 266 -d: Pass a custom description. The default will say
267 267 "Pasted from IPython".
268 268 -e: Pass number of days for the link to be expired.
269 269 The default will be 7 days.
270 270 """
271 271 opts, args = self.parse_options(parameter_s, "d:e:")
272 272
273 273 try:
274 274 code = self.shell.find_user_code(args)
275 275 except (ValueError, TypeError) as e:
276 276 print(e.args[0])
277 277 return
278 278
279 279 expiry_days = 7
280 280 try:
281 281 expiry_days = int(opts.get("e", 7))
282 282 except ValueError as e:
283 283 print(e.args[0].capitalize())
284 284 return
285 285 if expiry_days < 1 or expiry_days > 365:
286 286 print("Expiry days should be in range of 1 to 365")
287 287 return
288 288
289 289 post_data = urlencode(
290 290 {
291 291 "title": opts.get("d", "Pasted from IPython"),
292 292 "syntax": "python",
293 293 "content": code,
294 294 "expiry_days": expiry_days,
295 295 }
296 296 ).encode("utf-8")
297 297
298 298 request = Request(
299 299 "https://dpaste.com/api/v2/",
300 300 headers={"User-Agent": "IPython v{}".format(version)},
301 301 )
302 302 response = urlopen(request, post_data)
303 303 return response.headers.get('Location')
304 304
305 305 @line_magic
306 306 def loadpy(self, arg_s):
307 307 """Alias of `%load`
308 308
309 309 `%loadpy` has gained some flexibility and dropped the requirement of a `.py`
310 310 extension. So it has been renamed simply into %load. You can look at
311 311 `%load`'s docstring for more info.
312 312 """
313 313 self.load(arg_s)
314 314
315 315 @line_magic
316 316 def load(self, arg_s):
317 317 """Load code into the current frontend.
318 318
319 319 Usage:\\
320 320 %load [options] source
321 321
322 322 where source can be a filename, URL, input history range, macro, or
323 323 element in the user namespace
324 324
325 325 If no arguments are given, loads the history of this session up to this
326 326 point.
327 327
328 328 Options:
329 329
330 330 -r <lines>: Specify lines or ranges of lines to load from the source.
331 331 Ranges could be specified as x-y (x..y) or in python-style x:y
332 332 (x..(y-1)). Both limits x and y can be left blank (meaning the
333 333 beginning and end of the file, respectively).
334 334
335 335 -s <symbols>: Specify function or classes to load from python source.
336 336
337 337 -y : Don't ask confirmation for loading source above 200 000 characters.
338 338
339 339 -n : Include the user's namespace when searching for source code.
340 340
341 341 This magic command can either take a local filename, a URL, an history
342 342 range (see %history) or a macro as argument, it will prompt for
343 343 confirmation before loading source with more than 200 000 characters, unless
344 344 -y flag is passed or if the frontend does not support raw_input::
345 345
346 346 %load
347 347 %load myscript.py
348 348 %load 7-27
349 349 %load myMacro
350 350 %load http://www.example.com/myscript.py
351 351 %load -r 5-10 myscript.py
352 352 %load -r 10-20,30,40: foo.py
353 353 %load -s MyClass,wonder_function myscript.py
354 354 %load -n MyClass
355 355 %load -n my_module.wonder_function
356 356 """
357 357 opts,args = self.parse_options(arg_s,'yns:r:')
358 358 search_ns = 'n' in opts
359 359 contents = self.shell.find_user_code(args, search_ns=search_ns)
360 360
361 361 if 's' in opts:
362 362 try:
363 363 blocks, not_found = extract_symbols(contents, opts['s'])
364 364 except SyntaxError:
365 365 # non python code
366 366 error("Unable to parse the input as valid Python code")
367 367 return
368 368
369 369 if len(not_found) == 1:
370 370 warn('The symbol `%s` was not found' % not_found[0])
371 371 elif len(not_found) > 1:
372 372 warn('The symbols %s were not found' % get_text_list(not_found,
373 373 wrap_item_with='`')
374 374 )
375 375
376 376 contents = '\n'.join(blocks)
377 377
378 378 if 'r' in opts:
379 379 ranges = opts['r'].replace(',', ' ')
380 380 lines = contents.split('\n')
381 381 slices = extract_code_ranges(ranges)
382 382 contents = [lines[slice(*slc)] for slc in slices]
383 383 contents = '\n'.join(strip_initial_indent(chain.from_iterable(contents)))
384 384
385 385 l = len(contents)
386 386
387 387 # 200 000 is ~ 2500 full 80 character lines
388 388 # so in average, more than 5000 lines
389 389 if l > 200000 and 'y' not in opts:
390 390 try:
391 391 ans = self.shell.ask_yes_no(("The text you're trying to load seems pretty big"\
392 392 " (%d characters). Continue (y/[N]) ?" % l), default='n' )
393 393 except StdinNotImplementedError:
394 394 #assume yes if raw input not implemented
395 395 ans = True
396 396
397 397 if ans is False :
398 398 print('Operation cancelled.')
399 399 return
400 400
401 401 contents = "# %load {}\n".format(arg_s) + contents
402 402
403 403 self.shell.set_next_input(contents, replace=True)
404 404
405 405 @staticmethod
406 406 def _find_edit_target(shell, args, opts, last_call):
407 407 """Utility method used by magic_edit to find what to edit."""
408 408
409 409 def make_filename(arg):
410 410 "Make a filename from the given args"
411 411 try:
412 412 filename = get_py_filename(arg)
413 413 except IOError:
414 414 # If it ends with .py but doesn't already exist, assume we want
415 415 # a new file.
416 416 if arg.endswith('.py'):
417 417 filename = arg
418 418 else:
419 419 filename = None
420 420 return filename
421 421
422 422 # Set a few locals from the options for convenience:
423 423 opts_prev = 'p' in opts
424 424 opts_raw = 'r' in opts
425 425
426 426 # custom exceptions
427 427 class DataIsObject(Exception): pass
428 428
429 429 # Default line number value
430 430 lineno = opts.get('n',None)
431 431
432 432 if opts_prev:
433 433 args = '_%s' % last_call[0]
434 434 if args not in shell.user_ns:
435 435 args = last_call[1]
436 436
437 437 # by default this is done with temp files, except when the given
438 438 # arg is a filename
439 439 use_temp = True
440 440
441 441 data = ''
442 442
443 443 # First, see if the arguments should be a filename.
444 444 filename = make_filename(args)
445 445 if filename:
446 446 use_temp = False
447 447 elif args:
448 448 # Mode where user specifies ranges of lines, like in %macro.
449 449 data = shell.extract_input_lines(args, opts_raw)
450 450 if not data:
451 451 try:
452 452 # Load the parameter given as a variable. If not a string,
453 453 # process it as an object instead (below)
454 454
455 455 #print '*** args',args,'type',type(args) # dbg
456 456 data = eval(args, shell.user_ns)
457 457 if not isinstance(data, str):
458 458 raise DataIsObject
459 459
460 460 except (NameError,SyntaxError):
461 461 # given argument is not a variable, try as a filename
462 462 filename = make_filename(args)
463 463 if filename is None:
464 464 warn("Argument given (%s) can't be found as a variable "
465 465 "or as a filename." % args)
466 466 return (None, None, None)
467 467 use_temp = False
468 468
469 469 except DataIsObject as e:
470 470 # macros have a special edit function
471 471 if isinstance(data, Macro):
472 472 raise MacroToEdit(data) from e
473 473
474 474 # For objects, try to edit the file where they are defined
475 475 filename = find_file(data)
476 476 if filename:
477 477 if 'fakemodule' in filename.lower() and \
478 478 inspect.isclass(data):
479 479 # class created by %edit? Try to find source
480 480 # by looking for method definitions instead, the
481 481 # __module__ in those classes is FakeModule.
482 482 attrs = [getattr(data, aname) for aname in dir(data)]
483 483 for attr in attrs:
484 484 if not inspect.ismethod(attr):
485 485 continue
486 486 filename = find_file(attr)
487 487 if filename and \
488 488 'fakemodule' not in filename.lower():
489 489 # change the attribute to be the edit
490 490 # target instead
491 491 data = attr
492 492 break
493 493
494 494 m = ipython_input_pat.match(os.path.basename(filename))
495 495 if m:
496 496 raise InteractivelyDefined(int(m.groups()[0])) from e
497 497
498 498 datafile = 1
499 499 if filename is None:
500 500 filename = make_filename(args)
501 501 datafile = 1
502 502 if filename is not None:
503 503 # only warn about this if we get a real name
504 504 warn('Could not find file where `%s` is defined.\n'
505 505 'Opening a file named `%s`' % (args, filename))
506 506 # Now, make sure we can actually read the source (if it was
507 507 # in a temp file it's gone by now).
508 508 if datafile:
509 509 if lineno is None:
510 510 lineno = find_source_lines(data)
511 511 if lineno is None:
512 512 filename = make_filename(args)
513 513 if filename is None:
514 514 warn('The file where `%s` was defined '
515 515 'cannot be read or found.' % data)
516 516 return (None, None, None)
517 517 use_temp = False
518 518
519 519 if use_temp:
520 520 filename = shell.mktempfile(data)
521 521 print('IPython will make a temporary file named:',filename)
522 522
523 523 # use last_call to remember the state of the previous call, but don't
524 524 # let it be clobbered by successive '-p' calls.
525 525 try:
526 526 last_call[0] = shell.displayhook.prompt_count
527 527 if not opts_prev:
528 528 last_call[1] = args
529 529 except:
530 530 pass
531 531
532 532
533 533 return filename, lineno, use_temp
534 534
535 535 def _edit_macro(self,mname,macro):
536 536 """open an editor with the macro data in a file"""
537 537 filename = self.shell.mktempfile(macro.value)
538 538 self.shell.hooks.editor(filename)
539 539
540 540 # and make a new macro object, to replace the old one
541 mvalue = Path(filename).read_text(encoding='utf-8')
541 mvalue = Path(filename).read_text(encoding="utf-8")
542 542 self.shell.user_ns[mname] = Macro(mvalue)
543 543
544 544 @skip_doctest
545 545 @line_magic
546 546 def edit(self, parameter_s='',last_call=['','']):
547 547 """Bring up an editor and execute the resulting code.
548 548
549 549 Usage:
550 550 %edit [options] [args]
551 551
552 552 %edit runs IPython's editor hook. The default version of this hook is
553 553 set to call the editor specified by your $EDITOR environment variable.
554 554 If this isn't found, it will default to vi under Linux/Unix and to
555 555 notepad under Windows. See the end of this docstring for how to change
556 556 the editor hook.
557 557
558 558 You can also set the value of this editor via the
559 559 ``TerminalInteractiveShell.editor`` option in your configuration file.
560 560 This is useful if you wish to use a different editor from your typical
561 561 default with IPython (and for Windows users who typically don't set
562 562 environment variables).
563 563
564 564 This command allows you to conveniently edit multi-line code right in
565 565 your IPython session.
566 566
567 567 If called without arguments, %edit opens up an empty editor with a
568 568 temporary file and will execute the contents of this file when you
569 569 close it (don't forget to save it!).
570 570
571 571
572 572 Options:
573 573
574 574 -n <number>: open the editor at a specified line number. By default,
575 575 the IPython editor hook uses the unix syntax 'editor +N filename', but
576 576 you can configure this by providing your own modified hook if your
577 577 favorite editor supports line-number specifications with a different
578 578 syntax.
579 579
580 580 -p: this will call the editor with the same data as the previous time
581 581 it was used, regardless of how long ago (in your current session) it
582 582 was.
583 583
584 584 -r: use 'raw' input. This option only applies to input taken from the
585 585 user's history. By default, the 'processed' history is used, so that
586 586 magics are loaded in their transformed version to valid Python. If
587 587 this option is given, the raw input as typed as the command line is
588 588 used instead. When you exit the editor, it will be executed by
589 589 IPython's own processor.
590 590
591 591 -x: do not execute the edited code immediately upon exit. This is
592 592 mainly useful if you are editing programs which need to be called with
593 593 command line arguments, which you can then do using %run.
594 594
595 595
596 596 Arguments:
597 597
598 598 If arguments are given, the following possibilities exist:
599 599
600 600 - If the argument is a filename, IPython will load that into the
601 601 editor. It will execute its contents with execfile() when you exit,
602 602 loading any code in the file into your interactive namespace.
603 603
604 604 - The arguments are ranges of input history, e.g. "7 ~1/4-6".
605 605 The syntax is the same as in the %history magic.
606 606
607 607 - If the argument is a string variable, its contents are loaded
608 608 into the editor. You can thus edit any string which contains
609 609 python code (including the result of previous edits).
610 610
611 611 - If the argument is the name of an object (other than a string),
612 612 IPython will try to locate the file where it was defined and open the
613 613 editor at the point where it is defined. You can use `%edit function`
614 614 to load an editor exactly at the point where 'function' is defined,
615 615 edit it and have the file be executed automatically.
616 616
617 617 - If the object is a macro (see %macro for details), this opens up your
618 618 specified editor with a temporary file containing the macro's data.
619 619 Upon exit, the macro is reloaded with the contents of the file.
620 620
621 621 Note: opening at an exact line is only supported under Unix, and some
622 622 editors (like kedit and gedit up to Gnome 2.8) do not understand the
623 623 '+NUMBER' parameter necessary for this feature. Good editors like
624 624 (X)Emacs, vi, jed, pico and joe all do.
625 625
626 626 After executing your code, %edit will return as output the code you
627 627 typed in the editor (except when it was an existing file). This way
628 628 you can reload the code in further invocations of %edit as a variable,
629 629 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
630 630 the output.
631 631
632 632 Note that %edit is also available through the alias %ed.
633 633
634 634 This is an example of creating a simple function inside the editor and
635 635 then modifying it. First, start up the editor::
636 636
637 637 In [1]: edit
638 638 Editing... done. Executing edited code...
639 639 Out[1]: 'def foo():\\n print "foo() was defined in an editing
640 640 session"\\n'
641 641
642 642 We can then call the function foo()::
643 643
644 644 In [2]: foo()
645 645 foo() was defined in an editing session
646 646
647 647 Now we edit foo. IPython automatically loads the editor with the
648 648 (temporary) file where foo() was previously defined::
649 649
650 650 In [3]: edit foo
651 651 Editing... done. Executing edited code...
652 652
653 653 And if we call foo() again we get the modified version::
654 654
655 655 In [4]: foo()
656 656 foo() has now been changed!
657 657
658 658 Here is an example of how to edit a code snippet successive
659 659 times. First we call the editor::
660 660
661 661 In [5]: edit
662 662 Editing... done. Executing edited code...
663 663 hello
664 664 Out[5]: "print 'hello'\\n"
665 665
666 666 Now we call it again with the previous output (stored in _)::
667 667
668 668 In [6]: edit _
669 669 Editing... done. Executing edited code...
670 670 hello world
671 671 Out[6]: "print 'hello world'\\n"
672 672
673 673 Now we call it with the output #8 (stored in _8, also as Out[8])::
674 674
675 675 In [7]: edit _8
676 676 Editing... done. Executing edited code...
677 677 hello again
678 678 Out[7]: "print 'hello again'\\n"
679 679
680 680
681 681 Changing the default editor hook:
682 682
683 683 If you wish to write your own editor hook, you can put it in a
684 684 configuration file which you load at startup time. The default hook
685 685 is defined in the IPython.core.hooks module, and you can use that as a
686 686 starting example for further modifications. That file also has
687 687 general instructions on how to set a new hook for use once you've
688 688 defined it."""
689 689 opts,args = self.parse_options(parameter_s,'prxn:')
690 690
691 691 try:
692 692 filename, lineno, is_temp = self._find_edit_target(self.shell,
693 693 args, opts, last_call)
694 694 except MacroToEdit as e:
695 695 self._edit_macro(args, e.args[0])
696 696 return
697 697 except InteractivelyDefined as e:
698 698 print("Editing In[%i]" % e.index)
699 699 args = str(e.index)
700 700 filename, lineno, is_temp = self._find_edit_target(self.shell,
701 701 args, opts, last_call)
702 702 if filename is None:
703 703 # nothing was found, warnings have already been issued,
704 704 # just give up.
705 705 return
706 706
707 707 if is_temp:
708 708 self._knowntemps.add(filename)
709 709 elif (filename in self._knowntemps):
710 710 is_temp = True
711 711
712 712
713 713 # do actual editing here
714 714 print('Editing...', end=' ')
715 715 sys.stdout.flush()
716 716 filepath = Path(filename)
717 717 try:
718 718 # Quote filenames that may have spaces in them when opening
719 719 # the editor
720 720 quoted = filename = str(filepath.absolute())
721 721 if " " in quoted:
722 722 quoted = "'%s'" % quoted
723 723 self.shell.hooks.editor(quoted, lineno)
724 724 except TryNext:
725 725 warn('Could not open editor')
726 726 return
727 727
728 728 # XXX TODO: should this be generalized for all string vars?
729 729 # For now, this is special-cased to blocks created by cpaste
730 730 if args.strip() == "pasted_block":
731 self.shell.user_ns["pasted_block"] = filepath.read_text(encoding='utf-8')
731 self.shell.user_ns["pasted_block"] = filepath.read_text(encoding="utf-8")
732 732
733 733 if 'x' in opts: # -x prevents actual execution
734 734 print()
735 735 else:
736 736 print('done. Executing edited code...')
737 737 with preserve_keys(self.shell.user_ns, '__file__'):
738 738 if not is_temp:
739 self.shell.user_ns['__file__'] = filename
740 if 'r' in opts: # Untranslated IPython code
741 source = filepath.read_text(encoding='utf-8')
739 self.shell.user_ns["__file__"] = filename
740 if "r" in opts: # Untranslated IPython code
741 source = filepath.read_text(encoding="utf-8")
742 742 self.shell.run_cell(source, store_history=False)
743 743 else:
744 744 self.shell.safe_execfile(filename, self.shell.user_ns,
745 745 self.shell.user_ns)
746 746
747 747 if is_temp:
748 748 try:
749 return filepath.read_text(encoding='utf-8')
749 return filepath.read_text(encoding="utf-8")
750 750 except IOError as msg:
751 751 if Path(msg.filename) == filepath:
752 752 warn('File not found. Did you forget to save?')
753 753 return
754 754 else:
755 755 self.shell.showtraceback()
@@ -1,1510 +1,1510 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Implementation of execution-related magic functions."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7
8 8 import ast
9 9 import bdb
10 10 import builtins as builtin_mod
11 11 import cProfile as profile
12 12 import gc
13 13 import itertools
14 14 import math
15 15 import os
16 16 import pstats
17 17 import re
18 18 import shlex
19 19 import sys
20 20 import time
21 21 import timeit
22 22 from ast import Module
23 23 from io import StringIO
24 24 from logging import error
25 25 from pathlib import Path
26 26 from pdb import Restart
27 27 from warnings import warn
28 28
29 29 from IPython.core import magic_arguments, oinspect, page
30 30 from IPython.core.error import UsageError
31 31 from IPython.core.macro import Macro
32 32 from IPython.core.magic import (
33 33 Magics,
34 34 cell_magic,
35 35 line_cell_magic,
36 36 line_magic,
37 37 magics_class,
38 38 needs_local_scope,
39 39 no_var_expand,
40 40 on_off,
41 41 )
42 42 from IPython.testing.skipdoctest import skip_doctest
43 43 from IPython.utils.capture import capture_output
44 44 from IPython.utils.contexts import preserve_keys
45 45 from IPython.utils.ipstruct import Struct
46 46 from IPython.utils.module_paths import find_mod
47 47 from IPython.utils.path import get_py_filename, shellglob
48 48 from IPython.utils.timing import clock, clock2
49 49
50 50 #-----------------------------------------------------------------------------
51 51 # Magic implementation classes
52 52 #-----------------------------------------------------------------------------
53 53
54 54
55 55 class TimeitResult(object):
56 56 """
57 57 Object returned by the timeit magic with info about the run.
58 58
59 59 Contains the following attributes :
60 60
61 61 loops: (int) number of loops done per measurement
62 62 repeat: (int) number of times the measurement has been repeated
63 63 best: (float) best execution time / number
64 64 all_runs: (list of float) execution time of each run (in s)
65 65 compile_time: (float) time of statement compilation (s)
66 66
67 67 """
68 68 def __init__(self, loops, repeat, best, worst, all_runs, compile_time, precision):
69 69 self.loops = loops
70 70 self.repeat = repeat
71 71 self.best = best
72 72 self.worst = worst
73 73 self.all_runs = all_runs
74 74 self.compile_time = compile_time
75 75 self._precision = precision
76 76 self.timings = [ dt / self.loops for dt in all_runs]
77 77
78 78 @property
79 79 def average(self):
80 80 return math.fsum(self.timings) / len(self.timings)
81 81
82 82 @property
83 83 def stdev(self):
84 84 mean = self.average
85 85 return (math.fsum([(x - mean) ** 2 for x in self.timings]) / len(self.timings)) ** 0.5
86 86
87 87 def __str__(self):
88 88 pm = '+-'
89 89 if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
90 90 try:
91 91 u'\xb1'.encode(sys.stdout.encoding)
92 92 pm = u'\xb1'
93 93 except:
94 94 pass
95 95 return "{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops:,} loop{loop_plural} each)".format(
96 96 pm=pm,
97 97 runs=self.repeat,
98 98 loops=self.loops,
99 99 loop_plural="" if self.loops == 1 else "s",
100 100 run_plural="" if self.repeat == 1 else "s",
101 101 mean=_format_time(self.average, self._precision),
102 102 std=_format_time(self.stdev, self._precision),
103 103 )
104 104
105 105 def _repr_pretty_(self, p , cycle):
106 106 unic = self.__str__()
107 107 p.text(u'<TimeitResult : '+unic+u'>')
108 108
109 109
110 110 class TimeitTemplateFiller(ast.NodeTransformer):
111 111 """Fill in the AST template for timing execution.
112 112
113 113 This is quite closely tied to the template definition, which is in
114 114 :meth:`ExecutionMagics.timeit`.
115 115 """
116 116 def __init__(self, ast_setup, ast_stmt):
117 117 self.ast_setup = ast_setup
118 118 self.ast_stmt = ast_stmt
119 119
120 120 def visit_FunctionDef(self, node):
121 121 "Fill in the setup statement"
122 122 self.generic_visit(node)
123 123 if node.name == "inner":
124 124 node.body[:1] = self.ast_setup.body
125 125
126 126 return node
127 127
128 128 def visit_For(self, node):
129 129 "Fill in the statement to be timed"
130 130 if getattr(getattr(node.body[0], 'value', None), 'id', None) == 'stmt':
131 131 node.body = self.ast_stmt.body
132 132 return node
133 133
134 134
135 135 class Timer(timeit.Timer):
136 136 """Timer class that explicitly uses self.inner
137 137
138 138 which is an undocumented implementation detail of CPython,
139 139 not shared by PyPy.
140 140 """
141 141 # Timer.timeit copied from CPython 3.4.2
142 142 def timeit(self, number=timeit.default_number):
143 143 """Time 'number' executions of the main statement.
144 144
145 145 To be precise, this executes the setup statement once, and
146 146 then returns the time it takes to execute the main statement
147 147 a number of times, as a float measured in seconds. The
148 148 argument is the number of times through the loop, defaulting
149 149 to one million. The main statement, the setup statement and
150 150 the timer function to be used are passed to the constructor.
151 151 """
152 152 it = itertools.repeat(None, number)
153 153 gcold = gc.isenabled()
154 154 gc.disable()
155 155 try:
156 156 timing = self.inner(it, self.timer)
157 157 finally:
158 158 if gcold:
159 159 gc.enable()
160 160 return timing
161 161
162 162
163 163 @magics_class
164 164 class ExecutionMagics(Magics):
165 165 """Magics related to code execution, debugging, profiling, etc.
166 166
167 167 """
168 168
169 169 def __init__(self, shell):
170 170 super(ExecutionMagics, self).__init__(shell)
171 171 # Default execution function used to actually run user code.
172 172 self.default_runner = None
173 173
174 174 @skip_doctest
175 175 @no_var_expand
176 176 @line_cell_magic
177 177 def prun(self, parameter_s='', cell=None):
178 178
179 179 """Run a statement through the python code profiler.
180 180
181 181 Usage, in line mode:
182 182 %prun [options] statement
183 183
184 184 Usage, in cell mode:
185 185 %%prun [options] [statement]
186 186 code...
187 187 code...
188 188
189 189 In cell mode, the additional code lines are appended to the (possibly
190 190 empty) statement in the first line. Cell mode allows you to easily
191 191 profile multiline blocks without having to put them in a separate
192 192 function.
193 193
194 194 The given statement (which doesn't require quote marks) is run via the
195 195 python profiler in a manner similar to the profile.run() function.
196 196 Namespaces are internally managed to work correctly; profile.run
197 197 cannot be used in IPython because it makes certain assumptions about
198 198 namespaces which do not hold under IPython.
199 199
200 200 Options:
201 201
202 202 -l <limit>
203 203 you can place restrictions on what or how much of the
204 204 profile gets printed. The limit value can be:
205 205
206 206 * A string: only information for function names containing this string
207 207 is printed.
208 208
209 209 * An integer: only these many lines are printed.
210 210
211 211 * A float (between 0 and 1): this fraction of the report is printed
212 212 (for example, use a limit of 0.4 to see the topmost 40% only).
213 213
214 214 You can combine several limits with repeated use of the option. For
215 215 example, ``-l __init__ -l 5`` will print only the topmost 5 lines of
216 216 information about class constructors.
217 217
218 218 -r
219 219 return the pstats.Stats object generated by the profiling. This
220 220 object has all the information about the profile in it, and you can
221 221 later use it for further analysis or in other functions.
222 222
223 223 -s <key>
224 224 sort profile by given key. You can provide more than one key
225 225 by using the option several times: '-s key1 -s key2 -s key3...'. The
226 226 default sorting key is 'time'.
227 227
228 228 The following is copied verbatim from the profile documentation
229 229 referenced below:
230 230
231 231 When more than one key is provided, additional keys are used as
232 232 secondary criteria when the there is equality in all keys selected
233 233 before them.
234 234
235 235 Abbreviations can be used for any key names, as long as the
236 236 abbreviation is unambiguous. The following are the keys currently
237 237 defined:
238 238
239 239 ============ =====================
240 240 Valid Arg Meaning
241 241 ============ =====================
242 242 "calls" call count
243 243 "cumulative" cumulative time
244 244 "file" file name
245 245 "module" file name
246 246 "pcalls" primitive call count
247 247 "line" line number
248 248 "name" function name
249 249 "nfl" name/file/line
250 250 "stdname" standard name
251 251 "time" internal time
252 252 ============ =====================
253 253
254 254 Note that all sorts on statistics are in descending order (placing
255 255 most time consuming items first), where as name, file, and line number
256 256 searches are in ascending order (i.e., alphabetical). The subtle
257 257 distinction between "nfl" and "stdname" is that the standard name is a
258 258 sort of the name as printed, which means that the embedded line
259 259 numbers get compared in an odd way. For example, lines 3, 20, and 40
260 260 would (if the file names were the same) appear in the string order
261 261 "20" "3" and "40". In contrast, "nfl" does a numeric compare of the
262 262 line numbers. In fact, sort_stats("nfl") is the same as
263 263 sort_stats("name", "file", "line").
264 264
265 265 -T <filename>
266 266 save profile results as shown on screen to a text
267 267 file. The profile is still shown on screen.
268 268
269 269 -D <filename>
270 270 save (via dump_stats) profile statistics to given
271 271 filename. This data is in a format understood by the pstats module, and
272 272 is generated by a call to the dump_stats() method of profile
273 273 objects. The profile is still shown on screen.
274 274
275 275 -q
276 276 suppress output to the pager. Best used with -T and/or -D above.
277 277
278 278 If you want to run complete programs under the profiler's control, use
279 279 ``%run -p [prof_opts] filename.py [args to program]`` where prof_opts
280 280 contains profiler specific options as described here.
281 281
282 282 You can read the complete documentation for the profile module with::
283 283
284 284 In [1]: import profile; profile.help()
285 285
286 286 .. versionchanged:: 7.3
287 287 User variables are no longer expanded,
288 288 the magic line is always left unmodified.
289 289
290 290 """
291 291 opts, arg_str = self.parse_options(parameter_s, 'D:l:rs:T:q',
292 292 list_all=True, posix=False)
293 293 if cell is not None:
294 294 arg_str += '\n' + cell
295 295 arg_str = self.shell.transform_cell(arg_str)
296 296 return self._run_with_profiler(arg_str, opts, self.shell.user_ns)
297 297
298 298 def _run_with_profiler(self, code, opts, namespace):
299 299 """
300 300 Run `code` with profiler. Used by ``%prun`` and ``%run -p``.
301 301
302 302 Parameters
303 303 ----------
304 304 code : str
305 305 Code to be executed.
306 306 opts : Struct
307 307 Options parsed by `self.parse_options`.
308 308 namespace : dict
309 309 A dictionary for Python namespace (e.g., `self.shell.user_ns`).
310 310
311 311 """
312 312
313 313 # Fill default values for unspecified options:
314 314 opts.merge(Struct(D=[''], l=[], s=['time'], T=['']))
315 315
316 316 prof = profile.Profile()
317 317 try:
318 318 prof = prof.runctx(code, namespace, namespace)
319 319 sys_exit = ''
320 320 except SystemExit:
321 321 sys_exit = """*** SystemExit exception caught in code being profiled."""
322 322
323 323 stats = pstats.Stats(prof).strip_dirs().sort_stats(*opts.s)
324 324
325 325 lims = opts.l
326 326 if lims:
327 327 lims = [] # rebuild lims with ints/floats/strings
328 328 for lim in opts.l:
329 329 try:
330 330 lims.append(int(lim))
331 331 except ValueError:
332 332 try:
333 333 lims.append(float(lim))
334 334 except ValueError:
335 335 lims.append(lim)
336 336
337 337 # Trap output.
338 338 stdout_trap = StringIO()
339 339 stats_stream = stats.stream
340 340 try:
341 341 stats.stream = stdout_trap
342 342 stats.print_stats(*lims)
343 343 finally:
344 344 stats.stream = stats_stream
345 345
346 346 output = stdout_trap.getvalue()
347 347 output = output.rstrip()
348 348
349 349 if 'q' not in opts:
350 350 page.page(output)
351 351 print(sys_exit, end=' ')
352 352
353 353 dump_file = opts.D[0]
354 354 text_file = opts.T[0]
355 355 if dump_file:
356 356 prof.dump_stats(dump_file)
357 357 print(
358 358 f"\n*** Profile stats marshalled to file {repr(dump_file)}.{sys_exit}"
359 359 )
360 360 if text_file:
361 361 pfile = Path(text_file)
362 362 pfile.touch(exist_ok=True)
363 pfile.write_text(output, encoding='utf-8')
363 pfile.write_text(output, encoding="utf-8")
364 364
365 365 print(
366 366 f"\n*** Profile printout saved to text file {repr(text_file)}.{sys_exit}"
367 367 )
368 368
369 369 if 'r' in opts:
370 370 return stats
371 371
372 372 return None
373 373
374 374 @line_magic
375 375 def pdb(self, parameter_s=''):
376 376 """Control the automatic calling of the pdb interactive debugger.
377 377
378 378 Call as '%pdb on', '%pdb 1', '%pdb off' or '%pdb 0'. If called without
379 379 argument it works as a toggle.
380 380
381 381 When an exception is triggered, IPython can optionally call the
382 382 interactive pdb debugger after the traceback printout. %pdb toggles
383 383 this feature on and off.
384 384
385 385 The initial state of this feature is set in your configuration
386 386 file (the option is ``InteractiveShell.pdb``).
387 387
388 388 If you want to just activate the debugger AFTER an exception has fired,
389 389 without having to type '%pdb on' and rerunning your code, you can use
390 390 the %debug magic."""
391 391
392 392 par = parameter_s.strip().lower()
393 393
394 394 if par:
395 395 try:
396 396 new_pdb = {'off':0,'0':0,'on':1,'1':1}[par]
397 397 except KeyError:
398 398 print ('Incorrect argument. Use on/1, off/0, '
399 399 'or nothing for a toggle.')
400 400 return
401 401 else:
402 402 # toggle
403 403 new_pdb = not self.shell.call_pdb
404 404
405 405 # set on the shell
406 406 self.shell.call_pdb = new_pdb
407 407 print('Automatic pdb calling has been turned',on_off(new_pdb))
408 408
409 409 @magic_arguments.magic_arguments()
410 410 @magic_arguments.argument('--breakpoint', '-b', metavar='FILE:LINE',
411 411 help="""
412 412 Set break point at LINE in FILE.
413 413 """
414 414 )
415 415 @magic_arguments.argument('statement', nargs='*',
416 416 help="""
417 417 Code to run in debugger.
418 418 You can omit this in cell magic mode.
419 419 """
420 420 )
421 421 @no_var_expand
422 422 @line_cell_magic
423 423 def debug(self, line='', cell=None):
424 424 """Activate the interactive debugger.
425 425
426 426 This magic command support two ways of activating debugger.
427 427 One is to activate debugger before executing code. This way, you
428 428 can set a break point, to step through the code from the point.
429 429 You can use this mode by giving statements to execute and optionally
430 430 a breakpoint.
431 431
432 432 The other one is to activate debugger in post-mortem mode. You can
433 433 activate this mode simply running %debug without any argument.
434 434 If an exception has just occurred, this lets you inspect its stack
435 435 frames interactively. Note that this will always work only on the last
436 436 traceback that occurred, so you must call this quickly after an
437 437 exception that you wish to inspect has fired, because if another one
438 438 occurs, it clobbers the previous one.
439 439
440 440 If you want IPython to automatically do this on every exception, see
441 441 the %pdb magic for more details.
442 442
443 443 .. versionchanged:: 7.3
444 444 When running code, user variables are no longer expanded,
445 445 the magic line is always left unmodified.
446 446
447 447 """
448 448 args = magic_arguments.parse_argstring(self.debug, line)
449 449
450 450 if not (args.breakpoint or args.statement or cell):
451 451 self._debug_post_mortem()
452 452 elif not (args.breakpoint or cell):
453 453 # If there is no breakpoints, the line is just code to execute
454 454 self._debug_exec(line, None)
455 455 else:
456 456 # Here we try to reconstruct the code from the output of
457 457 # parse_argstring. This might not work if the code has spaces
458 458 # For example this fails for `print("a b")`
459 459 code = "\n".join(args.statement)
460 460 if cell:
461 461 code += "\n" + cell
462 462 self._debug_exec(code, args.breakpoint)
463 463
464 464 def _debug_post_mortem(self):
465 465 self.shell.debugger(force=True)
466 466
467 467 def _debug_exec(self, code, breakpoint):
468 468 if breakpoint:
469 469 (filename, bp_line) = breakpoint.rsplit(':', 1)
470 470 bp_line = int(bp_line)
471 471 else:
472 472 (filename, bp_line) = (None, None)
473 473 self._run_with_debugger(code, self.shell.user_ns, filename, bp_line)
474 474
475 475 @line_magic
476 476 def tb(self, s):
477 477 """Print the last traceback.
478 478
479 479 Optionally, specify an exception reporting mode, tuning the
480 480 verbosity of the traceback. By default the currently-active exception
481 481 mode is used. See %xmode for changing exception reporting modes.
482 482
483 483 Valid modes: Plain, Context, Verbose, and Minimal.
484 484 """
485 485 interactive_tb = self.shell.InteractiveTB
486 486 if s:
487 487 # Switch exception reporting mode for this one call.
488 488 # Ensure it is switched back.
489 489 def xmode_switch_err(name):
490 490 warn('Error changing %s exception modes.\n%s' %
491 491 (name,sys.exc_info()[1]))
492 492
493 493 new_mode = s.strip().capitalize()
494 494 original_mode = interactive_tb.mode
495 495 try:
496 496 try:
497 497 interactive_tb.set_mode(mode=new_mode)
498 498 except Exception:
499 499 xmode_switch_err('user')
500 500 else:
501 501 self.shell.showtraceback()
502 502 finally:
503 503 interactive_tb.set_mode(mode=original_mode)
504 504 else:
505 505 self.shell.showtraceback()
506 506
507 507 @skip_doctest
508 508 @line_magic
509 509 def run(self, parameter_s='', runner=None,
510 510 file_finder=get_py_filename):
511 511 """Run the named file inside IPython as a program.
512 512
513 513 Usage::
514 514
515 515 %run [-n -i -e -G]
516 516 [( -t [-N<N>] | -d [-b<N>] | -p [profile options] )]
517 517 ( -m mod | filename ) [args]
518 518
519 519 The filename argument should be either a pure Python script (with
520 520 extension ``.py``), or a file with custom IPython syntax (such as
521 521 magics). If the latter, the file can be either a script with ``.ipy``
522 522 extension, or a Jupyter notebook with ``.ipynb`` extension. When running
523 523 a Jupyter notebook, the output from print statements and other
524 524 displayed objects will appear in the terminal (even matplotlib figures
525 525 will open, if a terminal-compliant backend is being used). Note that,
526 526 at the system command line, the ``jupyter run`` command offers similar
527 527 functionality for executing notebooks (albeit currently with some
528 528 differences in supported options).
529 529
530 530 Parameters after the filename are passed as command-line arguments to
531 531 the program (put in sys.argv). Then, control returns to IPython's
532 532 prompt.
533 533
534 534 This is similar to running at a system prompt ``python file args``,
535 535 but with the advantage of giving you IPython's tracebacks, and of
536 536 loading all variables into your interactive namespace for further use
537 537 (unless -p is used, see below).
538 538
539 539 The file is executed in a namespace initially consisting only of
540 540 ``__name__=='__main__'`` and sys.argv constructed as indicated. It thus
541 541 sees its environment as if it were being run as a stand-alone program
542 542 (except for sharing global objects such as previously imported
543 543 modules). But after execution, the IPython interactive namespace gets
544 544 updated with all variables defined in the program (except for __name__
545 545 and sys.argv). This allows for very convenient loading of code for
546 546 interactive work, while giving each program a 'clean sheet' to run in.
547 547
548 548 Arguments are expanded using shell-like glob match. Patterns
549 549 '*', '?', '[seq]' and '[!seq]' can be used. Additionally,
550 550 tilde '~' will be expanded into user's home directory. Unlike
551 551 real shells, quotation does not suppress expansions. Use
552 552 *two* back slashes (e.g. ``\\\\*``) to suppress expansions.
553 553 To completely disable these expansions, you can use -G flag.
554 554
555 555 On Windows systems, the use of single quotes `'` when specifying
556 556 a file is not supported. Use double quotes `"`.
557 557
558 558 Options:
559 559
560 560 -n
561 561 __name__ is NOT set to '__main__', but to the running file's name
562 562 without extension (as python does under import). This allows running
563 563 scripts and reloading the definitions in them without calling code
564 564 protected by an ``if __name__ == "__main__"`` clause.
565 565
566 566 -i
567 567 run the file in IPython's namespace instead of an empty one. This
568 568 is useful if you are experimenting with code written in a text editor
569 569 which depends on variables defined interactively.
570 570
571 571 -e
572 572 ignore sys.exit() calls or SystemExit exceptions in the script
573 573 being run. This is particularly useful if IPython is being used to
574 574 run unittests, which always exit with a sys.exit() call. In such
575 575 cases you are interested in the output of the test results, not in
576 576 seeing a traceback of the unittest module.
577 577
578 578 -t
579 579 print timing information at the end of the run. IPython will give
580 580 you an estimated CPU time consumption for your script, which under
581 581 Unix uses the resource module to avoid the wraparound problems of
582 582 time.clock(). Under Unix, an estimate of time spent on system tasks
583 583 is also given (for Windows platforms this is reported as 0.0).
584 584
585 585 If -t is given, an additional ``-N<N>`` option can be given, where <N>
586 586 must be an integer indicating how many times you want the script to
587 587 run. The final timing report will include total and per run results.
588 588
589 589 For example (testing the script uniq_stable.py)::
590 590
591 591 In [1]: run -t uniq_stable
592 592
593 593 IPython CPU timings (estimated):
594 594 User : 0.19597 s.
595 595 System: 0.0 s.
596 596
597 597 In [2]: run -t -N5 uniq_stable
598 598
599 599 IPython CPU timings (estimated):
600 600 Total runs performed: 5
601 601 Times : Total Per run
602 602 User : 0.910862 s, 0.1821724 s.
603 603 System: 0.0 s, 0.0 s.
604 604
605 605 -d
606 606 run your program under the control of pdb, the Python debugger.
607 607 This allows you to execute your program step by step, watch variables,
608 608 etc. Internally, what IPython does is similar to calling::
609 609
610 610 pdb.run('execfile("YOURFILENAME")')
611 611
612 612 with a breakpoint set on line 1 of your file. You can change the line
613 613 number for this automatic breakpoint to be <N> by using the -bN option
614 614 (where N must be an integer). For example::
615 615
616 616 %run -d -b40 myscript
617 617
618 618 will set the first breakpoint at line 40 in myscript.py. Note that
619 619 the first breakpoint must be set on a line which actually does
620 620 something (not a comment or docstring) for it to stop execution.
621 621
622 622 Or you can specify a breakpoint in a different file::
623 623
624 624 %run -d -b myotherfile.py:20 myscript
625 625
626 626 When the pdb debugger starts, you will see a (Pdb) prompt. You must
627 627 first enter 'c' (without quotes) to start execution up to the first
628 628 breakpoint.
629 629
630 630 Entering 'help' gives information about the use of the debugger. You
631 631 can easily see pdb's full documentation with "import pdb;pdb.help()"
632 632 at a prompt.
633 633
634 634 -p
635 635 run program under the control of the Python profiler module (which
636 636 prints a detailed report of execution times, function calls, etc).
637 637
638 638 You can pass other options after -p which affect the behavior of the
639 639 profiler itself. See the docs for %prun for details.
640 640
641 641 In this mode, the program's variables do NOT propagate back to the
642 642 IPython interactive namespace (because they remain in the namespace
643 643 where the profiler executes them).
644 644
645 645 Internally this triggers a call to %prun, see its documentation for
646 646 details on the options available specifically for profiling.
647 647
648 648 There is one special usage for which the text above doesn't apply:
649 649 if the filename ends with .ipy[nb], the file is run as ipython script,
650 650 just as if the commands were written on IPython prompt.
651 651
652 652 -m
653 653 specify module name to load instead of script path. Similar to
654 654 the -m option for the python interpreter. Use this option last if you
655 655 want to combine with other %run options. Unlike the python interpreter
656 656 only source modules are allowed no .pyc or .pyo files.
657 657 For example::
658 658
659 659 %run -m example
660 660
661 661 will run the example module.
662 662
663 663 -G
664 664 disable shell-like glob expansion of arguments.
665 665
666 666 """
667 667
668 668 # Logic to handle issue #3664
669 669 # Add '--' after '-m <module_name>' to ignore additional args passed to a module.
670 670 if '-m' in parameter_s and '--' not in parameter_s:
671 671 argv = shlex.split(parameter_s, posix=(os.name == 'posix'))
672 672 for idx, arg in enumerate(argv):
673 673 if arg and arg.startswith('-') and arg != '-':
674 674 if arg == '-m':
675 675 argv.insert(idx + 2, '--')
676 676 break
677 677 else:
678 678 # Positional arg, break
679 679 break
680 680 parameter_s = ' '.join(shlex.quote(arg) for arg in argv)
681 681
682 682 # get arguments and set sys.argv for program to be run.
683 683 opts, arg_lst = self.parse_options(parameter_s,
684 684 'nidtN:b:pD:l:rs:T:em:G',
685 685 mode='list', list_all=1)
686 686 if "m" in opts:
687 687 modulename = opts["m"][0]
688 688 modpath = find_mod(modulename)
689 689 if modpath is None:
690 690 msg = '%r is not a valid modulename on sys.path'%modulename
691 691 raise Exception(msg)
692 692 arg_lst = [modpath] + arg_lst
693 693 try:
694 694 fpath = None # initialize to make sure fpath is in scope later
695 695 fpath = arg_lst[0]
696 696 filename = file_finder(fpath)
697 697 except IndexError as e:
698 698 msg = 'you must provide at least a filename.'
699 699 raise Exception(msg) from e
700 700 except IOError as e:
701 701 try:
702 702 msg = str(e)
703 703 except UnicodeError:
704 704 msg = e.message
705 705 if os.name == 'nt' and re.match(r"^'.*'$",fpath):
706 706 warn('For Windows, use double quotes to wrap a filename: %run "mypath\\myfile.py"')
707 707 raise Exception(msg) from e
708 708 except TypeError:
709 709 if fpath in sys.meta_path:
710 710 filename = ""
711 711 else:
712 712 raise
713 713
714 714 if filename.lower().endswith(('.ipy', '.ipynb')):
715 715 with preserve_keys(self.shell.user_ns, '__file__'):
716 716 self.shell.user_ns['__file__'] = filename
717 717 self.shell.safe_execfile_ipy(filename, raise_exceptions=True)
718 718 return
719 719
720 720 # Control the response to exit() calls made by the script being run
721 721 exit_ignore = 'e' in opts
722 722
723 723 # Make sure that the running script gets a proper sys.argv as if it
724 724 # were run from a system shell.
725 725 save_argv = sys.argv # save it for later restoring
726 726
727 727 if 'G' in opts:
728 728 args = arg_lst[1:]
729 729 else:
730 730 # tilde and glob expansion
731 731 args = shellglob(map(os.path.expanduser, arg_lst[1:]))
732 732
733 733 sys.argv = [filename] + args # put in the proper filename
734 734
735 735 if 'n' in opts:
736 736 name = Path(filename).stem
737 737 else:
738 738 name = '__main__'
739 739
740 740 if 'i' in opts:
741 741 # Run in user's interactive namespace
742 742 prog_ns = self.shell.user_ns
743 743 __name__save = self.shell.user_ns['__name__']
744 744 prog_ns['__name__'] = name
745 745 main_mod = self.shell.user_module
746 746
747 747 # Since '%run foo' emulates 'python foo.py' at the cmd line, we must
748 748 # set the __file__ global in the script's namespace
749 749 # TK: Is this necessary in interactive mode?
750 750 prog_ns['__file__'] = filename
751 751 else:
752 752 # Run in a fresh, empty namespace
753 753
754 754 # The shell MUST hold a reference to prog_ns so after %run
755 755 # exits, the python deletion mechanism doesn't zero it out
756 756 # (leaving dangling references). See interactiveshell for details
757 757 main_mod = self.shell.new_main_mod(filename, name)
758 758 prog_ns = main_mod.__dict__
759 759
760 760 # pickle fix. See interactiveshell for an explanation. But we need to
761 761 # make sure that, if we overwrite __main__, we replace it at the end
762 762 main_mod_name = prog_ns['__name__']
763 763
764 764 if main_mod_name == '__main__':
765 765 restore_main = sys.modules['__main__']
766 766 else:
767 767 restore_main = False
768 768
769 769 # This needs to be undone at the end to prevent holding references to
770 770 # every single object ever created.
771 771 sys.modules[main_mod_name] = main_mod
772 772
773 773 if 'p' in opts or 'd' in opts:
774 774 if 'm' in opts:
775 775 code = 'run_module(modulename, prog_ns)'
776 776 code_ns = {
777 777 'run_module': self.shell.safe_run_module,
778 778 'prog_ns': prog_ns,
779 779 'modulename': modulename,
780 780 }
781 781 else:
782 782 if 'd' in opts:
783 783 # allow exceptions to raise in debug mode
784 784 code = 'execfile(filename, prog_ns, raise_exceptions=True)'
785 785 else:
786 786 code = 'execfile(filename, prog_ns)'
787 787 code_ns = {
788 788 'execfile': self.shell.safe_execfile,
789 789 'prog_ns': prog_ns,
790 790 'filename': get_py_filename(filename),
791 791 }
792 792
793 793 try:
794 794 stats = None
795 795 if 'p' in opts:
796 796 stats = self._run_with_profiler(code, opts, code_ns)
797 797 else:
798 798 if 'd' in opts:
799 799 bp_file, bp_line = parse_breakpoint(
800 800 opts.get('b', ['1'])[0], filename)
801 801 self._run_with_debugger(
802 802 code, code_ns, filename, bp_line, bp_file)
803 803 else:
804 804 if 'm' in opts:
805 805 def run():
806 806 self.shell.safe_run_module(modulename, prog_ns)
807 807 else:
808 808 if runner is None:
809 809 runner = self.default_runner
810 810 if runner is None:
811 811 runner = self.shell.safe_execfile
812 812
813 813 def run():
814 814 runner(filename, prog_ns, prog_ns,
815 815 exit_ignore=exit_ignore)
816 816
817 817 if 't' in opts:
818 818 # timed execution
819 819 try:
820 820 nruns = int(opts['N'][0])
821 821 if nruns < 1:
822 822 error('Number of runs must be >=1')
823 823 return
824 824 except (KeyError):
825 825 nruns = 1
826 826 self._run_with_timing(run, nruns)
827 827 else:
828 828 # regular execution
829 829 run()
830 830
831 831 if 'i' in opts:
832 832 self.shell.user_ns['__name__'] = __name__save
833 833 else:
834 834 # update IPython interactive namespace
835 835
836 836 # Some forms of read errors on the file may mean the
837 837 # __name__ key was never set; using pop we don't have to
838 838 # worry about a possible KeyError.
839 839 prog_ns.pop('__name__', None)
840 840
841 841 with preserve_keys(self.shell.user_ns, '__file__'):
842 842 self.shell.user_ns.update(prog_ns)
843 843 finally:
844 844 # It's a bit of a mystery why, but __builtins__ can change from
845 845 # being a module to becoming a dict missing some key data after
846 846 # %run. As best I can see, this is NOT something IPython is doing
847 847 # at all, and similar problems have been reported before:
848 848 # http://coding.derkeiler.com/Archive/Python/comp.lang.python/2004-10/0188.html
849 849 # Since this seems to be done by the interpreter itself, the best
850 850 # we can do is to at least restore __builtins__ for the user on
851 851 # exit.
852 852 self.shell.user_ns['__builtins__'] = builtin_mod
853 853
854 854 # Ensure key global structures are restored
855 855 sys.argv = save_argv
856 856 if restore_main:
857 857 sys.modules['__main__'] = restore_main
858 858 if '__mp_main__' in sys.modules:
859 859 sys.modules['__mp_main__'] = restore_main
860 860 else:
861 861 # Remove from sys.modules the reference to main_mod we'd
862 862 # added. Otherwise it will trap references to objects
863 863 # contained therein.
864 864 del sys.modules[main_mod_name]
865 865
866 866 return stats
867 867
868 868 def _run_with_debugger(self, code, code_ns, filename=None,
869 869 bp_line=None, bp_file=None):
870 870 """
871 871 Run `code` in debugger with a break point.
872 872
873 873 Parameters
874 874 ----------
875 875 code : str
876 876 Code to execute.
877 877 code_ns : dict
878 878 A namespace in which `code` is executed.
879 879 filename : str
880 880 `code` is ran as if it is in `filename`.
881 881 bp_line : int, optional
882 882 Line number of the break point.
883 883 bp_file : str, optional
884 884 Path to the file in which break point is specified.
885 885 `filename` is used if not given.
886 886
887 887 Raises
888 888 ------
889 889 UsageError
890 890 If the break point given by `bp_line` is not valid.
891 891
892 892 """
893 893 deb = self.shell.InteractiveTB.pdb
894 894 if not deb:
895 895 self.shell.InteractiveTB.pdb = self.shell.InteractiveTB.debugger_cls()
896 896 deb = self.shell.InteractiveTB.pdb
897 897
898 898 # deb.checkline() fails if deb.curframe exists but is None; it can
899 899 # handle it not existing. https://github.com/ipython/ipython/issues/10028
900 900 if hasattr(deb, 'curframe'):
901 901 del deb.curframe
902 902
903 903 # reset Breakpoint state, which is moronically kept
904 904 # in a class
905 905 bdb.Breakpoint.next = 1
906 906 bdb.Breakpoint.bplist = {}
907 907 bdb.Breakpoint.bpbynumber = [None]
908 908 deb.clear_all_breaks()
909 909 if bp_line is not None:
910 910 # Set an initial breakpoint to stop execution
911 911 maxtries = 10
912 912 bp_file = bp_file or filename
913 913 checkline = deb.checkline(bp_file, bp_line)
914 914 if not checkline:
915 915 for bp in range(bp_line + 1, bp_line + maxtries + 1):
916 916 if deb.checkline(bp_file, bp):
917 917 break
918 918 else:
919 919 msg = ("\nI failed to find a valid line to set "
920 920 "a breakpoint\n"
921 921 "after trying up to line: %s.\n"
922 922 "Please set a valid breakpoint manually "
923 923 "with the -b option." % bp)
924 924 raise UsageError(msg)
925 925 # if we find a good linenumber, set the breakpoint
926 926 deb.do_break('%s:%s' % (bp_file, bp_line))
927 927
928 928 if filename:
929 929 # Mimic Pdb._runscript(...)
930 930 deb._wait_for_mainpyfile = True
931 931 deb.mainpyfile = deb.canonic(filename)
932 932
933 933 # Start file run
934 934 print("NOTE: Enter 'c' at the %s prompt to continue execution." % deb.prompt)
935 935 try:
936 936 if filename:
937 937 # save filename so it can be used by methods on the deb object
938 938 deb._exec_filename = filename
939 939 while True:
940 940 try:
941 941 trace = sys.gettrace()
942 942 deb.run(code, code_ns)
943 943 except Restart:
944 944 print("Restarting")
945 945 if filename:
946 946 deb._wait_for_mainpyfile = True
947 947 deb.mainpyfile = deb.canonic(filename)
948 948 continue
949 949 else:
950 950 break
951 951 finally:
952 952 sys.settrace(trace)
953 953
954 954
955 955 except:
956 956 etype, value, tb = sys.exc_info()
957 957 # Skip three frames in the traceback: the %run one,
958 958 # one inside bdb.py, and the command-line typed by the
959 959 # user (run by exec in pdb itself).
960 960 self.shell.InteractiveTB(etype, value, tb, tb_offset=3)
961 961
962 962 @staticmethod
963 963 def _run_with_timing(run, nruns):
964 964 """
965 965 Run function `run` and print timing information.
966 966
967 967 Parameters
968 968 ----------
969 969 run : callable
970 970 Any callable object which takes no argument.
971 971 nruns : int
972 972 Number of times to execute `run`.
973 973
974 974 """
975 975 twall0 = time.perf_counter()
976 976 if nruns == 1:
977 977 t0 = clock2()
978 978 run()
979 979 t1 = clock2()
980 980 t_usr = t1[0] - t0[0]
981 981 t_sys = t1[1] - t0[1]
982 982 print("\nIPython CPU timings (estimated):")
983 983 print(" User : %10.2f s." % t_usr)
984 984 print(" System : %10.2f s." % t_sys)
985 985 else:
986 986 runs = range(nruns)
987 987 t0 = clock2()
988 988 for nr in runs:
989 989 run()
990 990 t1 = clock2()
991 991 t_usr = t1[0] - t0[0]
992 992 t_sys = t1[1] - t0[1]
993 993 print("\nIPython CPU timings (estimated):")
994 994 print("Total runs performed:", nruns)
995 995 print(" Times : %10s %10s" % ('Total', 'Per run'))
996 996 print(" User : %10.2f s, %10.2f s." % (t_usr, t_usr / nruns))
997 997 print(" System : %10.2f s, %10.2f s." % (t_sys, t_sys / nruns))
998 998 twall1 = time.perf_counter()
999 999 print("Wall time: %10.2f s." % (twall1 - twall0))
1000 1000
1001 1001 @skip_doctest
1002 1002 @no_var_expand
1003 1003 @line_cell_magic
1004 1004 @needs_local_scope
1005 1005 def timeit(self, line='', cell=None, local_ns=None):
1006 1006 """Time execution of a Python statement or expression
1007 1007
1008 1008 Usage, in line mode:
1009 1009 %timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] statement
1010 1010 or in cell mode:
1011 1011 %%timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] setup_code
1012 1012 code
1013 1013 code...
1014 1014
1015 1015 Time execution of a Python statement or expression using the timeit
1016 1016 module. This function can be used both as a line and cell magic:
1017 1017
1018 1018 - In line mode you can time a single-line statement (though multiple
1019 1019 ones can be chained with using semicolons).
1020 1020
1021 1021 - In cell mode, the statement in the first line is used as setup code
1022 1022 (executed but not timed) and the body of the cell is timed. The cell
1023 1023 body has access to any variables created in the setup code.
1024 1024
1025 1025 Options:
1026 1026 -n<N>: execute the given statement <N> times in a loop. If <N> is not
1027 1027 provided, <N> is determined so as to get sufficient accuracy.
1028 1028
1029 1029 -r<R>: number of repeats <R>, each consisting of <N> loops, and take the
1030 1030 best result.
1031 1031 Default: 7
1032 1032
1033 1033 -t: use time.time to measure the time, which is the default on Unix.
1034 1034 This function measures wall time.
1035 1035
1036 1036 -c: use time.clock to measure the time, which is the default on
1037 1037 Windows and measures wall time. On Unix, resource.getrusage is used
1038 1038 instead and returns the CPU user time.
1039 1039
1040 1040 -p<P>: use a precision of <P> digits to display the timing result.
1041 1041 Default: 3
1042 1042
1043 1043 -q: Quiet, do not print result.
1044 1044
1045 1045 -o: return a TimeitResult that can be stored in a variable to inspect
1046 1046 the result in more details.
1047 1047
1048 1048 .. versionchanged:: 7.3
1049 1049 User variables are no longer expanded,
1050 1050 the magic line is always left unmodified.
1051 1051
1052 1052 Examples
1053 1053 --------
1054 1054 ::
1055 1055
1056 1056 In [1]: %timeit pass
1057 1057 8.26 ns ± 0.12 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)
1058 1058
1059 1059 In [2]: u = None
1060 1060
1061 1061 In [3]: %timeit u is None
1062 1062 29.9 ns ± 0.643 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
1063 1063
1064 1064 In [4]: %timeit -r 4 u == None
1065 1065
1066 1066 In [5]: import time
1067 1067
1068 1068 In [6]: %timeit -n1 time.sleep(2)
1069 1069
1070 1070 The times reported by %timeit will be slightly higher than those
1071 1071 reported by the timeit.py script when variables are accessed. This is
1072 1072 due to the fact that %timeit executes the statement in the namespace
1073 1073 of the shell, compared with timeit.py, which uses a single setup
1074 1074 statement to import function or create variables. Generally, the bias
1075 1075 does not matter as long as results from timeit.py are not mixed with
1076 1076 those from %timeit."""
1077 1077
1078 1078 opts, stmt = self.parse_options(
1079 1079 line, "n:r:tcp:qo", posix=False, strict=False, preserve_non_opts=True
1080 1080 )
1081 1081 if stmt == "" and cell is None:
1082 1082 return
1083 1083
1084 1084 timefunc = timeit.default_timer
1085 1085 number = int(getattr(opts, "n", 0))
1086 1086 default_repeat = 7 if timeit.default_repeat < 7 else timeit.default_repeat
1087 1087 repeat = int(getattr(opts, "r", default_repeat))
1088 1088 precision = int(getattr(opts, "p", 3))
1089 1089 quiet = 'q' in opts
1090 1090 return_result = 'o' in opts
1091 1091 if hasattr(opts, "t"):
1092 1092 timefunc = time.time
1093 1093 if hasattr(opts, "c"):
1094 1094 timefunc = clock
1095 1095
1096 1096 timer = Timer(timer=timefunc)
1097 1097 # this code has tight coupling to the inner workings of timeit.Timer,
1098 1098 # but is there a better way to achieve that the code stmt has access
1099 1099 # to the shell namespace?
1100 1100 transform = self.shell.transform_cell
1101 1101
1102 1102 if cell is None:
1103 1103 # called as line magic
1104 1104 ast_setup = self.shell.compile.ast_parse("pass")
1105 1105 ast_stmt = self.shell.compile.ast_parse(transform(stmt))
1106 1106 else:
1107 1107 ast_setup = self.shell.compile.ast_parse(transform(stmt))
1108 1108 ast_stmt = self.shell.compile.ast_parse(transform(cell))
1109 1109
1110 1110 ast_setup = self.shell.transform_ast(ast_setup)
1111 1111 ast_stmt = self.shell.transform_ast(ast_stmt)
1112 1112
1113 1113 # Check that these compile to valid Python code *outside* the timer func
1114 1114 # Invalid code may become valid when put inside the function & loop,
1115 1115 # which messes up error messages.
1116 1116 # https://github.com/ipython/ipython/issues/10636
1117 1117 self.shell.compile(ast_setup, "<magic-timeit-setup>", "exec")
1118 1118 self.shell.compile(ast_stmt, "<magic-timeit-stmt>", "exec")
1119 1119
1120 1120 # This codestring is taken from timeit.template - we fill it in as an
1121 1121 # AST, so that we can apply our AST transformations to the user code
1122 1122 # without affecting the timing code.
1123 1123 timeit_ast_template = ast.parse('def inner(_it, _timer):\n'
1124 1124 ' setup\n'
1125 1125 ' _t0 = _timer()\n'
1126 1126 ' for _i in _it:\n'
1127 1127 ' stmt\n'
1128 1128 ' _t1 = _timer()\n'
1129 1129 ' return _t1 - _t0\n')
1130 1130
1131 1131 timeit_ast = TimeitTemplateFiller(ast_setup, ast_stmt).visit(timeit_ast_template)
1132 1132 timeit_ast = ast.fix_missing_locations(timeit_ast)
1133 1133
1134 1134 # Track compilation time so it can be reported if too long
1135 1135 # Minimum time above which compilation time will be reported
1136 1136 tc_min = 0.1
1137 1137
1138 1138 t0 = clock()
1139 1139 code = self.shell.compile(timeit_ast, "<magic-timeit>", "exec")
1140 1140 tc = clock()-t0
1141 1141
1142 1142 ns = {}
1143 1143 glob = self.shell.user_ns
1144 1144 # handles global vars with same name as local vars. We store them in conflict_globs.
1145 1145 conflict_globs = {}
1146 1146 if local_ns and cell is None:
1147 1147 for var_name, var_val in glob.items():
1148 1148 if var_name in local_ns:
1149 1149 conflict_globs[var_name] = var_val
1150 1150 glob.update(local_ns)
1151 1151
1152 1152 exec(code, glob, ns)
1153 1153 timer.inner = ns["inner"]
1154 1154
1155 1155 # This is used to check if there is a huge difference between the
1156 1156 # best and worst timings.
1157 1157 # Issue: https://github.com/ipython/ipython/issues/6471
1158 1158 if number == 0:
1159 1159 # determine number so that 0.2 <= total time < 2.0
1160 1160 for index in range(0, 10):
1161 1161 number = 10 ** index
1162 1162 time_number = timer.timeit(number)
1163 1163 if time_number >= 0.2:
1164 1164 break
1165 1165
1166 1166 all_runs = timer.repeat(repeat, number)
1167 1167 best = min(all_runs) / number
1168 1168 worst = max(all_runs) / number
1169 1169 timeit_result = TimeitResult(number, repeat, best, worst, all_runs, tc, precision)
1170 1170
1171 1171 # Restore global vars from conflict_globs
1172 1172 if conflict_globs:
1173 1173 glob.update(conflict_globs)
1174 1174
1175 1175 if not quiet :
1176 1176 # Check best timing is greater than zero to avoid a
1177 1177 # ZeroDivisionError.
1178 1178 # In cases where the slowest timing is lesser than a microsecond
1179 1179 # we assume that it does not really matter if the fastest
1180 1180 # timing is 4 times faster than the slowest timing or not.
1181 1181 if worst > 4 * best and best > 0 and worst > 1e-6:
1182 1182 print("The slowest run took %0.2f times longer than the "
1183 1183 "fastest. This could mean that an intermediate result "
1184 1184 "is being cached." % (worst / best))
1185 1185
1186 1186 print( timeit_result )
1187 1187
1188 1188 if tc > tc_min:
1189 1189 print("Compiler time: %.2f s" % tc)
1190 1190 if return_result:
1191 1191 return timeit_result
1192 1192
1193 1193 @skip_doctest
1194 1194 @no_var_expand
1195 1195 @needs_local_scope
1196 1196 @line_cell_magic
1197 1197 def time(self,line='', cell=None, local_ns=None):
1198 1198 """Time execution of a Python statement or expression.
1199 1199
1200 1200 The CPU and wall clock times are printed, and the value of the
1201 1201 expression (if any) is returned. Note that under Win32, system time
1202 1202 is always reported as 0, since it can not be measured.
1203 1203
1204 1204 This function can be used both as a line and cell magic:
1205 1205
1206 1206 - In line mode you can time a single-line statement (though multiple
1207 1207 ones can be chained with using semicolons).
1208 1208
1209 1209 - In cell mode, you can time the cell body (a directly
1210 1210 following statement raises an error).
1211 1211
1212 1212 This function provides very basic timing functionality. Use the timeit
1213 1213 magic for more control over the measurement.
1214 1214
1215 1215 .. versionchanged:: 7.3
1216 1216 User variables are no longer expanded,
1217 1217 the magic line is always left unmodified.
1218 1218
1219 1219 Examples
1220 1220 --------
1221 1221 ::
1222 1222
1223 1223 In [1]: %time 2**128
1224 1224 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1225 1225 Wall time: 0.00
1226 1226 Out[1]: 340282366920938463463374607431768211456L
1227 1227
1228 1228 In [2]: n = 1000000
1229 1229
1230 1230 In [3]: %time sum(range(n))
1231 1231 CPU times: user 1.20 s, sys: 0.05 s, total: 1.25 s
1232 1232 Wall time: 1.37
1233 1233 Out[3]: 499999500000L
1234 1234
1235 1235 In [4]: %time print 'hello world'
1236 1236 hello world
1237 1237 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1238 1238 Wall time: 0.00
1239 1239
1240 1240 .. note::
1241 1241 The time needed by Python to compile the given expression will be
1242 1242 reported if it is more than 0.1s.
1243 1243
1244 1244 In the example below, the actual exponentiation is done by Python
1245 1245 at compilation time, so while the expression can take a noticeable
1246 1246 amount of time to compute, that time is purely due to the
1247 1247 compilation::
1248 1248
1249 1249 In [5]: %time 3**9999;
1250 1250 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1251 1251 Wall time: 0.00 s
1252 1252
1253 1253 In [6]: %time 3**999999;
1254 1254 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1255 1255 Wall time: 0.00 s
1256 1256 Compiler : 0.78 s
1257 1257 """
1258 1258 # fail immediately if the given expression can't be compiled
1259 1259
1260 1260 if line and cell:
1261 1261 raise UsageError("Can't use statement directly after '%%time'!")
1262 1262
1263 1263 if cell:
1264 1264 expr = self.shell.transform_cell(cell)
1265 1265 else:
1266 1266 expr = self.shell.transform_cell(line)
1267 1267
1268 1268 # Minimum time above which parse time will be reported
1269 1269 tp_min = 0.1
1270 1270
1271 1271 t0 = clock()
1272 1272 expr_ast = self.shell.compile.ast_parse(expr)
1273 1273 tp = clock()-t0
1274 1274
1275 1275 # Apply AST transformations
1276 1276 expr_ast = self.shell.transform_ast(expr_ast)
1277 1277
1278 1278 # Minimum time above which compilation time will be reported
1279 1279 tc_min = 0.1
1280 1280
1281 1281 expr_val=None
1282 1282 if len(expr_ast.body)==1 and isinstance(expr_ast.body[0], ast.Expr):
1283 1283 mode = 'eval'
1284 1284 source = '<timed eval>'
1285 1285 expr_ast = ast.Expression(expr_ast.body[0].value)
1286 1286 else:
1287 1287 mode = 'exec'
1288 1288 source = '<timed exec>'
1289 1289 # multi-line %%time case
1290 1290 if len(expr_ast.body) > 1 and isinstance(expr_ast.body[-1], ast.Expr):
1291 1291 expr_val= expr_ast.body[-1]
1292 1292 expr_ast = expr_ast.body[:-1]
1293 1293 expr_ast = Module(expr_ast, [])
1294 1294 expr_val = ast.Expression(expr_val.value)
1295 1295
1296 1296 t0 = clock()
1297 1297 code = self.shell.compile(expr_ast, source, mode)
1298 1298 tc = clock()-t0
1299 1299
1300 1300 # skew measurement as little as possible
1301 1301 glob = self.shell.user_ns
1302 1302 wtime = time.time
1303 1303 # time execution
1304 1304 wall_st = wtime()
1305 1305 if mode=='eval':
1306 1306 st = clock2()
1307 1307 try:
1308 1308 out = eval(code, glob, local_ns)
1309 1309 except:
1310 1310 self.shell.showtraceback()
1311 1311 return
1312 1312 end = clock2()
1313 1313 else:
1314 1314 st = clock2()
1315 1315 try:
1316 1316 exec(code, glob, local_ns)
1317 1317 out=None
1318 1318 # multi-line %%time case
1319 1319 if expr_val is not None:
1320 1320 code_2 = self.shell.compile(expr_val, source, 'eval')
1321 1321 out = eval(code_2, glob, local_ns)
1322 1322 except:
1323 1323 self.shell.showtraceback()
1324 1324 return
1325 1325 end = clock2()
1326 1326
1327 1327 wall_end = wtime()
1328 1328 # Compute actual times and report
1329 1329 wall_time = wall_end - wall_st
1330 1330 cpu_user = end[0] - st[0]
1331 1331 cpu_sys = end[1] - st[1]
1332 1332 cpu_tot = cpu_user + cpu_sys
1333 1333 # On windows cpu_sys is always zero, so only total is displayed
1334 1334 if sys.platform != "win32":
1335 1335 print(
1336 1336 f"CPU times: user {_format_time(cpu_user)}, sys: {_format_time(cpu_sys)}, total: {_format_time(cpu_tot)}"
1337 1337 )
1338 1338 else:
1339 1339 print(f"CPU times: total: {_format_time(cpu_tot)}")
1340 1340 print(f"Wall time: {_format_time(wall_time)}")
1341 1341 if tc > tc_min:
1342 1342 print(f"Compiler : {_format_time(tc)}")
1343 1343 if tp > tp_min:
1344 1344 print(f"Parser : {_format_time(tp)}")
1345 1345 return out
1346 1346
1347 1347 @skip_doctest
1348 1348 @line_magic
1349 1349 def macro(self, parameter_s=''):
1350 1350 """Define a macro for future re-execution. It accepts ranges of history,
1351 1351 filenames or string objects.
1352 1352
1353 1353 Usage:\\
1354 1354 %macro [options] name n1-n2 n3-n4 ... n5 .. n6 ...
1355 1355
1356 1356 Options:
1357 1357
1358 1358 -r: use 'raw' input. By default, the 'processed' history is used,
1359 1359 so that magics are loaded in their transformed version to valid
1360 1360 Python. If this option is given, the raw input as typed at the
1361 1361 command line is used instead.
1362 1362
1363 1363 -q: quiet macro definition. By default, a tag line is printed
1364 1364 to indicate the macro has been created, and then the contents of
1365 1365 the macro are printed. If this option is given, then no printout
1366 1366 is produced once the macro is created.
1367 1367
1368 1368 This will define a global variable called `name` which is a string
1369 1369 made of joining the slices and lines you specify (n1,n2,... numbers
1370 1370 above) from your input history into a single string. This variable
1371 1371 acts like an automatic function which re-executes those lines as if
1372 1372 you had typed them. You just type 'name' at the prompt and the code
1373 1373 executes.
1374 1374
1375 1375 The syntax for indicating input ranges is described in %history.
1376 1376
1377 1377 Note: as a 'hidden' feature, you can also use traditional python slice
1378 1378 notation, where N:M means numbers N through M-1.
1379 1379
1380 1380 For example, if your history contains (print using %hist -n )::
1381 1381
1382 1382 44: x=1
1383 1383 45: y=3
1384 1384 46: z=x+y
1385 1385 47: print x
1386 1386 48: a=5
1387 1387 49: print 'x',x,'y',y
1388 1388
1389 1389 you can create a macro with lines 44 through 47 (included) and line 49
1390 1390 called my_macro with::
1391 1391
1392 1392 In [55]: %macro my_macro 44-47 49
1393 1393
1394 1394 Now, typing `my_macro` (without quotes) will re-execute all this code
1395 1395 in one pass.
1396 1396
1397 1397 You don't need to give the line-numbers in order, and any given line
1398 1398 number can appear multiple times. You can assemble macros with any
1399 1399 lines from your input history in any order.
1400 1400
1401 1401 The macro is a simple object which holds its value in an attribute,
1402 1402 but IPython's display system checks for macros and executes them as
1403 1403 code instead of printing them when you type their name.
1404 1404
1405 1405 You can view a macro's contents by explicitly printing it with::
1406 1406
1407 1407 print macro_name
1408 1408
1409 1409 """
1410 1410 opts,args = self.parse_options(parameter_s,'rq',mode='list')
1411 1411 if not args: # List existing macros
1412 1412 return sorted(k for k,v in self.shell.user_ns.items() if isinstance(v, Macro))
1413 1413 if len(args) == 1:
1414 1414 raise UsageError(
1415 1415 "%macro insufficient args; usage '%macro name n1-n2 n3-4...")
1416 1416 name, codefrom = args[0], " ".join(args[1:])
1417 1417
1418 1418 #print 'rng',ranges # dbg
1419 1419 try:
1420 1420 lines = self.shell.find_user_code(codefrom, 'r' in opts)
1421 1421 except (ValueError, TypeError) as e:
1422 1422 print(e.args[0])
1423 1423 return
1424 1424 macro = Macro(lines)
1425 1425 self.shell.define_macro(name, macro)
1426 1426 if not ( 'q' in opts) :
1427 1427 print('Macro `%s` created. To execute, type its name (without quotes).' % name)
1428 1428 print('=== Macro contents: ===')
1429 1429 print(macro, end=' ')
1430 1430
1431 1431 @magic_arguments.magic_arguments()
1432 1432 @magic_arguments.argument('output', type=str, default='', nargs='?',
1433 1433 help="""The name of the variable in which to store output.
1434 1434 This is a utils.io.CapturedIO object with stdout/err attributes
1435 1435 for the text of the captured output.
1436 1436
1437 1437 CapturedOutput also has a show() method for displaying the output,
1438 1438 and __call__ as well, so you can use that to quickly display the
1439 1439 output.
1440 1440
1441 1441 If unspecified, captured output is discarded.
1442 1442 """
1443 1443 )
1444 1444 @magic_arguments.argument('--no-stderr', action="store_true",
1445 1445 help="""Don't capture stderr."""
1446 1446 )
1447 1447 @magic_arguments.argument('--no-stdout', action="store_true",
1448 1448 help="""Don't capture stdout."""
1449 1449 )
1450 1450 @magic_arguments.argument('--no-display', action="store_true",
1451 1451 help="""Don't capture IPython's rich display."""
1452 1452 )
1453 1453 @cell_magic
1454 1454 def capture(self, line, cell):
1455 1455 """run the cell, capturing stdout, stderr, and IPython's rich display() calls."""
1456 1456 args = magic_arguments.parse_argstring(self.capture, line)
1457 1457 out = not args.no_stdout
1458 1458 err = not args.no_stderr
1459 1459 disp = not args.no_display
1460 1460 with capture_output(out, err, disp) as io:
1461 1461 self.shell.run_cell(cell)
1462 1462 if args.output:
1463 1463 self.shell.user_ns[args.output] = io
1464 1464
1465 1465 def parse_breakpoint(text, current_file):
1466 1466 '''Returns (file, line) for file:line and (current_file, line) for line'''
1467 1467 colon = text.find(':')
1468 1468 if colon == -1:
1469 1469 return current_file, int(text)
1470 1470 else:
1471 1471 return text[:colon], int(text[colon+1:])
1472 1472
1473 1473 def _format_time(timespan, precision=3):
1474 1474 """Formats the timespan in a human readable form"""
1475 1475
1476 1476 if timespan >= 60.0:
1477 1477 # we have more than a minute, format that in a human readable form
1478 1478 # Idea from http://snipplr.com/view/5713/
1479 1479 parts = [("d", 60*60*24),("h", 60*60),("min", 60), ("s", 1)]
1480 1480 time = []
1481 1481 leftover = timespan
1482 1482 for suffix, length in parts:
1483 1483 value = int(leftover / length)
1484 1484 if value > 0:
1485 1485 leftover = leftover % length
1486 1486 time.append(u'%s%s' % (str(value), suffix))
1487 1487 if leftover < 1:
1488 1488 break
1489 1489 return " ".join(time)
1490 1490
1491 1491
1492 1492 # Unfortunately the unicode 'micro' symbol can cause problems in
1493 1493 # certain terminals.
1494 1494 # See bug: https://bugs.launchpad.net/ipython/+bug/348466
1495 1495 # Try to prevent crashes by being more secure than it needs to
1496 1496 # E.g. eclipse is able to print a µ, but has no sys.stdout.encoding set.
1497 1497 units = [u"s", u"ms",u'us',"ns"] # the save value
1498 1498 if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
1499 1499 try:
1500 1500 u'\xb5'.encode(sys.stdout.encoding)
1501 1501 units = [u"s", u"ms",u'\xb5s',"ns"]
1502 1502 except:
1503 1503 pass
1504 1504 scaling = [1, 1e3, 1e6, 1e9]
1505 1505
1506 1506 if timespan > 0.0:
1507 1507 order = min(-int(math.floor(math.log10(timespan)) // 3), 3)
1508 1508 else:
1509 1509 order = 3
1510 1510 return u"%.*g %s" % (precision, timespan * scaling[order], units[order])
@@ -1,112 +1,112 b''
1 1 """Implementation of packaging-related magic functions.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (c) 2018 The IPython Development Team.
5 5 #
6 6 # Distributed under the terms of the Modified BSD License.
7 7 #
8 8 # The full license is in the file COPYING.txt, distributed with this software.
9 9 #-----------------------------------------------------------------------------
10 10
11 11 import re
12 12 import shlex
13 13 import sys
14 14 from pathlib import Path
15 15
16 16 from IPython.core.magic import Magics, magics_class, line_magic
17 17
18 18
19 19 def _is_conda_environment():
20 20 """Return True if the current Python executable is in a conda env"""
21 21 # TODO: does this need to change on windows?
22 22 return Path(sys.prefix, "conda-meta", "history").exists()
23 23
24 24
25 25 def _get_conda_executable():
26 26 """Find the path to the conda executable"""
27 27 # Check if there is a conda executable in the same directory as the Python executable.
28 28 # This is the case within conda's root environment.
29 29 conda = Path(sys.executable).parent / "conda"
30 30 if conda.is_file():
31 31 return str(conda)
32 32
33 33 # Otherwise, attempt to extract the executable from conda history.
34 34 # This applies in any conda environment.
35 history = Path(sys.prefix, "conda-meta", "history").read_text(encoding='utf-8')
35 history = Path(sys.prefix, "conda-meta", "history").read_text(encoding="utf-8")
36 36 match = re.search(
37 37 r"^#\s*cmd:\s*(?P<command>.*conda)\s[create|install]",
38 38 history,
39 39 flags=re.MULTILINE,
40 40 )
41 41 if match:
42 42 return match.groupdict()["command"]
43 43
44 44 # Fallback: assume conda is available on the system path.
45 45 return "conda"
46 46
47 47
48 48 CONDA_COMMANDS_REQUIRING_PREFIX = {
49 49 'install', 'list', 'remove', 'uninstall', 'update', 'upgrade',
50 50 }
51 51 CONDA_COMMANDS_REQUIRING_YES = {
52 52 'install', 'remove', 'uninstall', 'update', 'upgrade',
53 53 }
54 54 CONDA_ENV_FLAGS = {'-p', '--prefix', '-n', '--name'}
55 55 CONDA_YES_FLAGS = {'-y', '--y'}
56 56
57 57
58 58 @magics_class
59 59 class PackagingMagics(Magics):
60 60 """Magics related to packaging & installation"""
61 61
62 62 @line_magic
63 63 def pip(self, line):
64 64 """Run the pip package manager within the current kernel.
65 65
66 66 Usage:
67 67 %pip install [pkgs]
68 68 """
69 69 python = sys.executable
70 70 if sys.platform == "win32":
71 71 python = '"' + python + '"'
72 72 else:
73 73 python = shlex.quote(python)
74 74
75 75 self.shell.system(" ".join([python, "-m", "pip", line]))
76 76
77 77 print("Note: you may need to restart the kernel to use updated packages.")
78 78
79 79 @line_magic
80 80 def conda(self, line):
81 81 """Run the conda package manager within the current kernel.
82 82
83 83 Usage:
84 84 %conda install [pkgs]
85 85 """
86 86 if not _is_conda_environment():
87 87 raise ValueError("The python kernel does not appear to be a conda environment. "
88 88 "Please use ``%pip install`` instead.")
89 89
90 90 conda = _get_conda_executable()
91 91 args = shlex.split(line)
92 92 command = args[0] if len(args) > 0 else ""
93 93 args = args[1:] if len(args) > 1 else [""]
94 94
95 95 extra_args = []
96 96
97 97 # When the subprocess does not allow us to respond "yes" during the installation,
98 98 # we need to insert --yes in the argument list for some commands
99 99 stdin_disabled = getattr(self.shell, 'kernel', None) is not None
100 100 needs_yes = command in CONDA_COMMANDS_REQUIRING_YES
101 101 has_yes = set(args).intersection(CONDA_YES_FLAGS)
102 102 if stdin_disabled and needs_yes and not has_yes:
103 103 extra_args.append("--yes")
104 104
105 105 # Add --prefix to point conda installation to the current environment
106 106 needs_prefix = command in CONDA_COMMANDS_REQUIRING_PREFIX
107 107 has_prefix = set(args).intersection(CONDA_ENV_FLAGS)
108 108 if needs_prefix and not has_prefix:
109 109 extra_args.extend(["--prefix", sys.prefix])
110 110
111 111 self.shell.system(' '.join([conda, command] + extra_args + args))
112 112 print("\nNote: you may need to restart the kernel to use updated packages.")
@@ -1,345 +1,348 b''
1 1 # encoding: utf-8
2 2 """
3 3 Paging capabilities for IPython.core
4 4
5 5 Notes
6 6 -----
7 7
8 8 For now this uses IPython hooks, so it can't be in IPython.utils. If we can get
9 9 rid of that dependency, we could move it there.
10 10 -----
11 11 """
12 12
13 13 # Copyright (c) IPython Development Team.
14 14 # Distributed under the terms of the Modified BSD License.
15 15
16 16
17 17 import os
18 18 import io
19 19 import re
20 20 import sys
21 21 import tempfile
22 22 import subprocess
23 23
24 24 from io import UnsupportedOperation
25 25 from pathlib import Path
26 26
27 27 from IPython import get_ipython
28 28 from IPython.display import display
29 29 from IPython.core.error import TryNext
30 30 from IPython.utils.data import chop
31 31 from IPython.utils.process import system
32 32 from IPython.utils.terminal import get_terminal_size
33 33 from IPython.utils import py3compat
34 34
35 35
36 36 def display_page(strng, start=0, screen_lines=25):
37 37 """Just display, no paging. screen_lines is ignored."""
38 38 if isinstance(strng, dict):
39 39 data = strng
40 40 else:
41 41 if start:
42 42 strng = u'\n'.join(strng.splitlines()[start:])
43 43 data = { 'text/plain': strng }
44 44 display(data, raw=True)
45 45
46 46
47 47 def as_hook(page_func):
48 48 """Wrap a pager func to strip the `self` arg
49 49
50 50 so it can be called as a hook.
51 51 """
52 52 return lambda self, *args, **kwargs: page_func(*args, **kwargs)
53 53
54 54
55 55 esc_re = re.compile(r"(\x1b[^m]+m)")
56 56
57 57 def page_dumb(strng, start=0, screen_lines=25):
58 58 """Very dumb 'pager' in Python, for when nothing else works.
59 59
60 60 Only moves forward, same interface as page(), except for pager_cmd and
61 61 mode.
62 62 """
63 63 if isinstance(strng, dict):
64 64 strng = strng.get('text/plain', '')
65 65 out_ln = strng.splitlines()[start:]
66 66 screens = chop(out_ln,screen_lines-1)
67 67 if len(screens) == 1:
68 68 print(os.linesep.join(screens[0]))
69 69 else:
70 70 last_escape = ""
71 71 for scr in screens[0:-1]:
72 72 hunk = os.linesep.join(scr)
73 73 print(last_escape + hunk)
74 74 if not page_more():
75 75 return
76 76 esc_list = esc_re.findall(hunk)
77 77 if len(esc_list) > 0:
78 78 last_escape = esc_list[-1]
79 79 print(last_escape + os.linesep.join(screens[-1]))
80 80
81 81 def _detect_screen_size(screen_lines_def):
82 82 """Attempt to work out the number of lines on the screen.
83 83
84 84 This is called by page(). It can raise an error (e.g. when run in the
85 85 test suite), so it's separated out so it can easily be called in a try block.
86 86 """
87 87 TERM = os.environ.get('TERM',None)
88 88 if not((TERM=='xterm' or TERM=='xterm-color') and sys.platform != 'sunos5'):
89 89 # curses causes problems on many terminals other than xterm, and
90 90 # some termios calls lock up on Sun OS5.
91 91 return screen_lines_def
92 92
93 93 try:
94 94 import termios
95 95 import curses
96 96 except ImportError:
97 97 return screen_lines_def
98 98
99 99 # There is a bug in curses, where *sometimes* it fails to properly
100 100 # initialize, and then after the endwin() call is made, the
101 101 # terminal is left in an unusable state. Rather than trying to
102 102 # check every time for this (by requesting and comparing termios
103 103 # flags each time), we just save the initial terminal state and
104 104 # unconditionally reset it every time. It's cheaper than making
105 105 # the checks.
106 106 try:
107 107 term_flags = termios.tcgetattr(sys.stdout)
108 108 except termios.error as err:
109 109 # can fail on Linux 2.6, pager_page will catch the TypeError
110 110 raise TypeError('termios error: {0}'.format(err)) from err
111 111
112 112 try:
113 113 scr = curses.initscr()
114 114 except AttributeError:
115 115 # Curses on Solaris may not be complete, so we can't use it there
116 116 return screen_lines_def
117 117
118 118 screen_lines_real,screen_cols = scr.getmaxyx()
119 119 curses.endwin()
120 120
121 121 # Restore terminal state in case endwin() didn't.
122 122 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
123 123 # Now we have what we needed: the screen size in rows/columns
124 124 return screen_lines_real
125 125 #print '***Screen size:',screen_lines_real,'lines x',\
126 126 #screen_cols,'columns.' # dbg
127 127
128 128 def pager_page(strng, start=0, screen_lines=0, pager_cmd=None):
129 129 """Display a string, piping through a pager after a certain length.
130 130
131 131 strng can be a mime-bundle dict, supplying multiple representations,
132 132 keyed by mime-type.
133 133
134 134 The screen_lines parameter specifies the number of *usable* lines of your
135 135 terminal screen (total lines minus lines you need to reserve to show other
136 136 information).
137 137
138 138 If you set screen_lines to a number <=0, page() will try to auto-determine
139 139 your screen size and will only use up to (screen_size+screen_lines) for
140 140 printing, paging after that. That is, if you want auto-detection but need
141 141 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
142 142 auto-detection without any lines reserved simply use screen_lines = 0.
143 143
144 144 If a string won't fit in the allowed lines, it is sent through the
145 145 specified pager command. If none given, look for PAGER in the environment,
146 146 and ultimately default to less.
147 147
148 148 If no system pager works, the string is sent through a 'dumb pager'
149 149 written in python, very simplistic.
150 150 """
151 151
152 152 # for compatibility with mime-bundle form:
153 153 if isinstance(strng, dict):
154 154 strng = strng['text/plain']
155 155
156 156 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
157 157 TERM = os.environ.get('TERM','dumb')
158 158 if TERM in ['dumb','emacs'] and os.name != 'nt':
159 159 print(strng)
160 160 return
161 161 # chop off the topmost part of the string we don't want to see
162 162 str_lines = strng.splitlines()[start:]
163 163 str_toprint = os.linesep.join(str_lines)
164 164 num_newlines = len(str_lines)
165 165 len_str = len(str_toprint)
166 166
167 167 # Dumb heuristics to guesstimate number of on-screen lines the string
168 168 # takes. Very basic, but good enough for docstrings in reasonable
169 169 # terminals. If someone later feels like refining it, it's not hard.
170 170 numlines = max(num_newlines,int(len_str/80)+1)
171 171
172 172 screen_lines_def = get_terminal_size()[1]
173 173
174 174 # auto-determine screen size
175 175 if screen_lines <= 0:
176 176 try:
177 177 screen_lines += _detect_screen_size(screen_lines_def)
178 178 except (TypeError, UnsupportedOperation):
179 179 print(str_toprint)
180 180 return
181 181
182 182 #print 'numlines',numlines,'screenlines',screen_lines # dbg
183 183 if numlines <= screen_lines :
184 184 #print '*** normal print' # dbg
185 185 print(str_toprint)
186 186 else:
187 187 # Try to open pager and default to internal one if that fails.
188 188 # All failure modes are tagged as 'retval=1', to match the return
189 189 # value of a failed system command. If any intermediate attempt
190 190 # sets retval to 1, at the end we resort to our own page_dumb() pager.
191 191 pager_cmd = get_pager_cmd(pager_cmd)
192 192 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
193 193 if os.name == 'nt':
194 194 if pager_cmd.startswith('type'):
195 195 # The default WinXP 'type' command is failing on complex strings.
196 196 retval = 1
197 197 else:
198 198 fd, tmpname = tempfile.mkstemp('.txt')
199 199 tmppath = Path(tmpname)
200 200 try:
201 201 os.close(fd)
202 with tmppath.open("wt", encoding='utf-8') as tmpfile:
202 with tmppath.open("wt", encoding="utf-8") as tmpfile:
203 203 tmpfile.write(strng)
204 204 cmd = "%s < %s" % (pager_cmd, tmppath)
205 205 # tmpfile needs to be closed for windows
206 206 if os.system(cmd):
207 207 retval = 1
208 208 else:
209 209 retval = None
210 210 finally:
211 211 Path.unlink(tmppath)
212 212 else:
213 213 try:
214 214 retval = None
215 215 # Emulate os.popen, but redirect stderr
216 proc = subprocess.Popen(pager_cmd,
217 shell=True,
218 stdin=subprocess.PIPE,
219 stderr=subprocess.DEVNULL
220 )
221 pager = os._wrap_close(io.TextIOWrapper(proc.stdin, encoding='utf-8'), proc)
216 proc = subprocess.Popen(
217 pager_cmd,
218 shell=True,
219 stdin=subprocess.PIPE,
220 stderr=subprocess.DEVNULL,
221 )
222 pager = os._wrap_close(
223 io.TextIOWrapper(proc.stdin, encoding="utf-8"), proc
224 )
222 225 try:
223 226 pager_encoding = pager.encoding or sys.stdout.encoding
224 227 pager.write(strng)
225 228 finally:
226 229 retval = pager.close()
227 230 except IOError as msg: # broken pipe when user quits
228 231 if msg.args == (32, 'Broken pipe'):
229 232 retval = None
230 233 else:
231 234 retval = 1
232 235 except OSError:
233 236 # Other strange problems, sometimes seen in Win2k/cygwin
234 237 retval = 1
235 238 if retval is not None:
236 239 page_dumb(strng,screen_lines=screen_lines)
237 240
238 241
239 242 def page(data, start=0, screen_lines=0, pager_cmd=None):
240 243 """Display content in a pager, piping through a pager after a certain length.
241 244
242 245 data can be a mime-bundle dict, supplying multiple representations,
243 246 keyed by mime-type, or text.
244 247
245 248 Pager is dispatched via the `show_in_pager` IPython hook.
246 249 If no hook is registered, `pager_page` will be used.
247 250 """
248 251 # Some routines may auto-compute start offsets incorrectly and pass a
249 252 # negative value. Offset to 0 for robustness.
250 253 start = max(0, start)
251 254
252 255 # first, try the hook
253 256 ip = get_ipython()
254 257 if ip:
255 258 try:
256 259 ip.hooks.show_in_pager(data, start=start, screen_lines=screen_lines)
257 260 return
258 261 except TryNext:
259 262 pass
260 263
261 264 # fallback on default pager
262 265 return pager_page(data, start, screen_lines, pager_cmd)
263 266
264 267
265 268 def page_file(fname, start=0, pager_cmd=None):
266 269 """Page a file, using an optional pager command and starting line.
267 270 """
268 271
269 272 pager_cmd = get_pager_cmd(pager_cmd)
270 273 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
271 274
272 275 try:
273 276 if os.environ['TERM'] in ['emacs','dumb']:
274 277 raise EnvironmentError
275 278 system(pager_cmd + ' ' + fname)
276 279 except:
277 280 try:
278 281 if start > 0:
279 282 start -= 1
280 page(open(fname, encoding='utf-8').read(),start)
283 page(open(fname, encoding="utf-8").read(), start)
281 284 except:
282 285 print('Unable to show file',repr(fname))
283 286
284 287
285 288 def get_pager_cmd(pager_cmd=None):
286 289 """Return a pager command.
287 290
288 291 Makes some attempts at finding an OS-correct one.
289 292 """
290 293 if os.name == 'posix':
291 294 default_pager_cmd = 'less -R' # -R for color control sequences
292 295 elif os.name in ['nt','dos']:
293 296 default_pager_cmd = 'type'
294 297
295 298 if pager_cmd is None:
296 299 try:
297 300 pager_cmd = os.environ['PAGER']
298 301 except:
299 302 pager_cmd = default_pager_cmd
300 303
301 304 if pager_cmd == 'less' and '-r' not in os.environ.get('LESS', '').lower():
302 305 pager_cmd += ' -R'
303 306
304 307 return pager_cmd
305 308
306 309
307 310 def get_pager_start(pager, start):
308 311 """Return the string for paging files with an offset.
309 312
310 313 This is the '+N' argument which less and more (under Unix) accept.
311 314 """
312 315
313 316 if pager in ['less','more']:
314 317 if start:
315 318 start_string = '+' + str(start)
316 319 else:
317 320 start_string = ''
318 321 else:
319 322 start_string = ''
320 323 return start_string
321 324
322 325
323 326 # (X)emacs on win32 doesn't like to be bypassed with msvcrt.getch()
324 327 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
325 328 import msvcrt
326 329 def page_more():
327 330 """ Smart pausing between pages
328 331
329 332 @return: True if need print more lines, False if quit
330 333 """
331 334 sys.stdout.write('---Return to continue, q to quit--- ')
332 335 ans = msvcrt.getwch()
333 336 if ans in ("q", "Q"):
334 337 result = False
335 338 else:
336 339 result = True
337 340 sys.stdout.write("\b"*37 + " "*37 + "\b"*37)
338 341 return result
339 342 else:
340 343 def page_more():
341 344 ans = py3compat.input('---Return to continue, q to quit--- ')
342 345 if ans.lower().startswith('q'):
343 346 return False
344 347 else:
345 348 return True
@@ -1,70 +1,70 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 traitlets import Unicode
8 8
9 9 from IPython.core.application import BaseIPythonApplication
10 10 from IPython.testing import decorators as dec
11 11 from IPython.utils.tempdir import TemporaryDirectory
12 12
13 13
14 14 @dec.onlyif_unicode_paths
15 15 def test_unicode_cwd():
16 16 """Check that IPython starts with non-ascii characters in the path."""
17 17 wd = tempfile.mkdtemp(suffix=u"€")
18 18
19 19 old_wd = os.getcwd()
20 20 os.chdir(wd)
21 21 #raise Exception(repr(os.getcwd()))
22 22 try:
23 23 app = BaseIPythonApplication()
24 24 # The lines below are copied from Application.initialize()
25 25 app.init_profile_dir()
26 26 app.init_config_files()
27 27 app.load_config_file(suppress_errors=False)
28 28 finally:
29 29 os.chdir(old_wd)
30 30
31 31 @dec.onlyif_unicode_paths
32 32 def test_unicode_ipdir():
33 33 """Check that IPython starts with non-ascii characters in the IP dir."""
34 34 ipdir = tempfile.mkdtemp(suffix=u"€")
35 35
36 36 # Create the config file, so it tries to load it.
37 with open(os.path.join(ipdir, 'ipython_config.py'), "w", encoding='utf-8') as f:
37 with open(os.path.join(ipdir, "ipython_config.py"), "w", encoding="utf-8") as f:
38 38 pass
39 39
40 40 old_ipdir1 = os.environ.pop("IPYTHONDIR", None)
41 41 old_ipdir2 = os.environ.pop("IPYTHON_DIR", None)
42 42 os.environ["IPYTHONDIR"] = ipdir
43 43 try:
44 44 app = BaseIPythonApplication()
45 45 # The lines below are copied from Application.initialize()
46 46 app.init_profile_dir()
47 47 app.init_config_files()
48 48 app.load_config_file(suppress_errors=False)
49 49 finally:
50 50 if old_ipdir1:
51 51 os.environ["IPYTHONDIR"] = old_ipdir1
52 52 if old_ipdir2:
53 53 os.environ["IPYTHONDIR"] = old_ipdir2
54 54
55 55 def test_cli_priority():
56 56 with TemporaryDirectory() as td:
57 57
58 58 class TestApp(BaseIPythonApplication):
59 59 test = Unicode().tag(config=True)
60 60
61 61 # Create the config file, so it tries to load it.
62 with open(os.path.join(td, 'ipython_config.py'), "w", encoding='utf-8') as f:
62 with open(os.path.join(td, "ipython_config.py"), "w", encoding="utf-8") as f:
63 63 f.write("c.TestApp.test = 'config file'")
64 64
65 65 app = TestApp()
66 66 app.initialize(["--profile-dir", td])
67 67 assert app.test == "config file"
68 68 app = TestApp()
69 69 app.initialize(["--profile-dir", td, "--TestApp.test=cli"])
70 70 assert app.test == "cli"
@@ -1,1264 +1,1264 b''
1 1 # encoding: utf-8
2 2 """Tests for the IPython tab-completion machinery."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 import os
8 8 import pytest
9 9 import sys
10 10 import textwrap
11 11 import unittest
12 12
13 13 from contextlib import contextmanager
14 14
15 15 from traitlets.config.loader import Config
16 16 from IPython import get_ipython
17 17 from IPython.core import completer
18 18 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
19 19 from IPython.utils.generics import complete_object
20 20 from IPython.testing import decorators as dec
21 21
22 22 from IPython.core.completer import (
23 23 Completion,
24 24 provisionalcompleter,
25 25 match_dict_keys,
26 26 _deduplicate_completions,
27 27 )
28 28
29 29 # -----------------------------------------------------------------------------
30 30 # Test functions
31 31 # -----------------------------------------------------------------------------
32 32
33 33 def recompute_unicode_ranges():
34 34 """
35 35 utility to recompute the largest unicode range without any characters
36 36
37 37 use to recompute the gap in the global _UNICODE_RANGES of completer.py
38 38 """
39 39 import itertools
40 40 import unicodedata
41 41 valid = []
42 42 for c in range(0,0x10FFFF + 1):
43 43 try:
44 44 unicodedata.name(chr(c))
45 45 except ValueError:
46 46 continue
47 47 valid.append(c)
48 48
49 49 def ranges(i):
50 50 for a, b in itertools.groupby(enumerate(i), lambda pair: pair[1] - pair[0]):
51 51 b = list(b)
52 52 yield b[0][1], b[-1][1]
53 53
54 54 rg = list(ranges(valid))
55 55 lens = []
56 56 gap_lens = []
57 57 pstart, pstop = 0,0
58 58 for start, stop in rg:
59 59 lens.append(stop-start)
60 60 gap_lens.append((start - pstop, hex(pstop), hex(start), f'{round((start - pstop)/0xe01f0*100)}%'))
61 61 pstart, pstop = start, stop
62 62
63 63 return sorted(gap_lens)[-1]
64 64
65 65
66 66
67 67 def test_unicode_range():
68 68 """
69 69 Test that the ranges we test for unicode names give the same number of
70 70 results than testing the full length.
71 71 """
72 72 from IPython.core.completer import _unicode_name_compute, _UNICODE_RANGES
73 73
74 74 expected_list = _unicode_name_compute([(0, 0x110000)])
75 75 test = _unicode_name_compute(_UNICODE_RANGES)
76 76 len_exp = len(expected_list)
77 77 len_test = len(test)
78 78
79 79 # do not inline the len() or on error pytest will try to print the 130 000 +
80 80 # elements.
81 81 message = None
82 82 if len_exp != len_test or len_exp > 131808:
83 83 size, start, stop, prct = recompute_unicode_ranges()
84 84 message = f"""_UNICODE_RANGES likely wrong and need updating. This is
85 85 likely due to a new release of Python. We've find that the biggest gap
86 86 in unicode characters has reduces in size to be {size} characters
87 87 ({prct}), from {start}, to {stop}. In completer.py likely update to
88 88
89 89 _UNICODE_RANGES = [(32, {start}), ({stop}, 0xe01f0)]
90 90
91 91 And update the assertion below to use
92 92
93 93 len_exp <= {len_exp}
94 94 """
95 95 assert len_exp == len_test, message
96 96
97 97 # fail if new unicode symbols have been added.
98 98 assert len_exp <= 138552, message
99 99
100 100
101 101 @contextmanager
102 102 def greedy_completion():
103 103 ip = get_ipython()
104 104 greedy_original = ip.Completer.greedy
105 105 try:
106 106 ip.Completer.greedy = True
107 107 yield
108 108 finally:
109 109 ip.Completer.greedy = greedy_original
110 110
111 111
112 112 def test_protect_filename():
113 113 if sys.platform == "win32":
114 114 pairs = [
115 115 ("abc", "abc"),
116 116 (" abc", '" abc"'),
117 117 ("a bc", '"a bc"'),
118 118 ("a bc", '"a bc"'),
119 119 (" bc", '" bc"'),
120 120 ]
121 121 else:
122 122 pairs = [
123 123 ("abc", "abc"),
124 124 (" abc", r"\ abc"),
125 125 ("a bc", r"a\ bc"),
126 126 ("a bc", r"a\ \ bc"),
127 127 (" bc", r"\ \ bc"),
128 128 # On posix, we also protect parens and other special characters.
129 129 ("a(bc", r"a\(bc"),
130 130 ("a)bc", r"a\)bc"),
131 131 ("a( )bc", r"a\(\ \)bc"),
132 132 ("a[1]bc", r"a\[1\]bc"),
133 133 ("a{1}bc", r"a\{1\}bc"),
134 134 ("a#bc", r"a\#bc"),
135 135 ("a?bc", r"a\?bc"),
136 136 ("a=bc", r"a\=bc"),
137 137 ("a\\bc", r"a\\bc"),
138 138 ("a|bc", r"a\|bc"),
139 139 ("a;bc", r"a\;bc"),
140 140 ("a:bc", r"a\:bc"),
141 141 ("a'bc", r"a\'bc"),
142 142 ("a*bc", r"a\*bc"),
143 143 ('a"bc', r"a\"bc"),
144 144 ("a^bc", r"a\^bc"),
145 145 ("a&bc", r"a\&bc"),
146 146 ]
147 147 # run the actual tests
148 148 for s1, s2 in pairs:
149 149 s1p = completer.protect_filename(s1)
150 150 assert s1p == s2
151 151
152 152
153 153 def check_line_split(splitter, test_specs):
154 154 for part1, part2, split in test_specs:
155 155 cursor_pos = len(part1)
156 156 line = part1 + part2
157 157 out = splitter.split_line(line, cursor_pos)
158 158 assert out == split
159 159
160 160
161 161 def test_line_split():
162 162 """Basic line splitter test with default specs."""
163 163 sp = completer.CompletionSplitter()
164 164 # The format of the test specs is: part1, part2, expected answer. Parts 1
165 165 # and 2 are joined into the 'line' sent to the splitter, as if the cursor
166 166 # was at the end of part1. So an empty part2 represents someone hitting
167 167 # tab at the end of the line, the most common case.
168 168 t = [
169 169 ("run some/scrip", "", "some/scrip"),
170 170 ("run scripts/er", "ror.py foo", "scripts/er"),
171 171 ("echo $HOM", "", "HOM"),
172 172 ("print sys.pa", "", "sys.pa"),
173 173 ("print(sys.pa", "", "sys.pa"),
174 174 ("execfile('scripts/er", "", "scripts/er"),
175 175 ("a[x.", "", "x."),
176 176 ("a[x.", "y", "x."),
177 177 ('cd "some_file/', "", "some_file/"),
178 178 ]
179 179 check_line_split(sp, t)
180 180 # Ensure splitting works OK with unicode by re-running the tests with
181 181 # all inputs turned into unicode
182 182 check_line_split(sp, [map(str, p) for p in t])
183 183
184 184
185 185 class NamedInstanceClass:
186 186 instances = {}
187 187
188 188 def __init__(self, name):
189 189 self.instances[name] = self
190 190
191 191 @classmethod
192 192 def _ipython_key_completions_(cls):
193 193 return cls.instances.keys()
194 194
195 195
196 196 class KeyCompletable:
197 197 def __init__(self, things=()):
198 198 self.things = things
199 199
200 200 def _ipython_key_completions_(self):
201 201 return list(self.things)
202 202
203 203
204 204 class TestCompleter(unittest.TestCase):
205 205 def setUp(self):
206 206 """
207 207 We want to silence all PendingDeprecationWarning when testing the completer
208 208 """
209 209 self._assertwarns = self.assertWarns(PendingDeprecationWarning)
210 210 self._assertwarns.__enter__()
211 211
212 212 def tearDown(self):
213 213 try:
214 214 self._assertwarns.__exit__(None, None, None)
215 215 except AssertionError:
216 216 pass
217 217
218 218 def test_custom_completion_error(self):
219 219 """Test that errors from custom attribute completers are silenced."""
220 220 ip = get_ipython()
221 221
222 222 class A:
223 223 pass
224 224
225 225 ip.user_ns["x"] = A()
226 226
227 227 @complete_object.register(A)
228 228 def complete_A(a, existing_completions):
229 229 raise TypeError("this should be silenced")
230 230
231 231 ip.complete("x.")
232 232
233 233 def test_custom_completion_ordering(self):
234 234 """Test that errors from custom attribute completers are silenced."""
235 235 ip = get_ipython()
236 236
237 237 _, matches = ip.complete('in')
238 238 assert matches.index('input') < matches.index('int')
239 239
240 240 def complete_example(a):
241 241 return ['example2', 'example1']
242 242
243 243 ip.Completer.custom_completers.add_re('ex*', complete_example)
244 244 _, matches = ip.complete('ex')
245 245 assert matches.index('example2') < matches.index('example1')
246 246
247 247 def test_unicode_completions(self):
248 248 ip = get_ipython()
249 249 # Some strings that trigger different types of completion. Check them both
250 250 # in str and unicode forms
251 251 s = ["ru", "%ru", "cd /", "floa", "float(x)/"]
252 252 for t in s + list(map(str, s)):
253 253 # We don't need to check exact completion values (they may change
254 254 # depending on the state of the namespace, but at least no exceptions
255 255 # should be thrown and the return value should be a pair of text, list
256 256 # values.
257 257 text, matches = ip.complete(t)
258 258 self.assertIsInstance(text, str)
259 259 self.assertIsInstance(matches, list)
260 260
261 261 def test_latex_completions(self):
262 262 from IPython.core.latex_symbols import latex_symbols
263 263 import random
264 264
265 265 ip = get_ipython()
266 266 # Test some random unicode symbols
267 267 keys = random.sample(sorted(latex_symbols), 10)
268 268 for k in keys:
269 269 text, matches = ip.complete(k)
270 270 self.assertEqual(text, k)
271 271 self.assertEqual(matches, [latex_symbols[k]])
272 272 # Test a more complex line
273 273 text, matches = ip.complete("print(\\alpha")
274 274 self.assertEqual(text, "\\alpha")
275 275 self.assertEqual(matches[0], latex_symbols["\\alpha"])
276 276 # Test multiple matching latex symbols
277 277 text, matches = ip.complete("\\al")
278 278 self.assertIn("\\alpha", matches)
279 279 self.assertIn("\\aleph", matches)
280 280
281 281 def test_latex_no_results(self):
282 282 """
283 283 forward latex should really return nothing in either field if nothing is found.
284 284 """
285 285 ip = get_ipython()
286 286 text, matches = ip.Completer.latex_matches("\\really_i_should_match_nothing")
287 287 self.assertEqual(text, "")
288 288 self.assertEqual(matches, ())
289 289
290 290 def test_back_latex_completion(self):
291 291 ip = get_ipython()
292 292
293 293 # do not return more than 1 matches for \beta, only the latex one.
294 294 name, matches = ip.complete("\\β")
295 295 self.assertEqual(matches, ["\\beta"])
296 296
297 297 def test_back_unicode_completion(self):
298 298 ip = get_ipython()
299 299
300 300 name, matches = ip.complete("\\Ⅴ")
301 301 self.assertEqual(matches, ("\\ROMAN NUMERAL FIVE",))
302 302
303 303 def test_forward_unicode_completion(self):
304 304 ip = get_ipython()
305 305
306 306 name, matches = ip.complete("\\ROMAN NUMERAL FIVE")
307 307 self.assertEqual(matches, ["Ⅴ"]) # This is not a V
308 308 self.assertEqual(matches, ["\u2164"]) # same as above but explicit.
309 309
310 310 def test_delim_setting(self):
311 311 sp = completer.CompletionSplitter()
312 312 sp.delims = " "
313 313 self.assertEqual(sp.delims, " ")
314 314 self.assertEqual(sp._delim_expr, r"[\ ]")
315 315
316 316 def test_spaces(self):
317 317 """Test with only spaces as split chars."""
318 318 sp = completer.CompletionSplitter()
319 319 sp.delims = " "
320 320 t = [("foo", "", "foo"), ("run foo", "", "foo"), ("run foo", "bar", "foo")]
321 321 check_line_split(sp, t)
322 322
323 323 def test_has_open_quotes1(self):
324 324 for s in ["'", "'''", "'hi' '"]:
325 325 self.assertEqual(completer.has_open_quotes(s), "'")
326 326
327 327 def test_has_open_quotes2(self):
328 328 for s in ['"', '"""', '"hi" "']:
329 329 self.assertEqual(completer.has_open_quotes(s), '"')
330 330
331 331 def test_has_open_quotes3(self):
332 332 for s in ["''", "''' '''", "'hi' 'ipython'"]:
333 333 self.assertFalse(completer.has_open_quotes(s))
334 334
335 335 def test_has_open_quotes4(self):
336 336 for s in ['""', '""" """', '"hi" "ipython"']:
337 337 self.assertFalse(completer.has_open_quotes(s))
338 338
339 339 @pytest.mark.xfail(
340 340 sys.platform == "win32", reason="abspath completions fail on Windows"
341 341 )
342 342 def test_abspath_file_completions(self):
343 343 ip = get_ipython()
344 344 with TemporaryDirectory() as tmpdir:
345 345 prefix = os.path.join(tmpdir, "foo")
346 346 suffixes = ["1", "2"]
347 347 names = [prefix + s for s in suffixes]
348 348 for n in names:
349 open(n, "w", encoding='utf-8').close()
349 open(n, "w", encoding="utf-8").close()
350 350
351 351 # Check simple completion
352 352 c = ip.complete(prefix)[1]
353 353 self.assertEqual(c, names)
354 354
355 355 # Now check with a function call
356 356 cmd = 'a = f("%s' % prefix
357 357 c = ip.complete(prefix, cmd)[1]
358 358 comp = [prefix + s for s in suffixes]
359 359 self.assertEqual(c, comp)
360 360
361 361 def test_local_file_completions(self):
362 362 ip = get_ipython()
363 363 with TemporaryWorkingDirectory():
364 364 prefix = "./foo"
365 365 suffixes = ["1", "2"]
366 366 names = [prefix + s for s in suffixes]
367 367 for n in names:
368 open(n, "w", encoding='utf-8').close()
368 open(n, "w", encoding="utf-8").close()
369 369
370 370 # Check simple completion
371 371 c = ip.complete(prefix)[1]
372 372 self.assertEqual(c, names)
373 373
374 374 # Now check with a function call
375 375 cmd = 'a = f("%s' % prefix
376 376 c = ip.complete(prefix, cmd)[1]
377 377 comp = {prefix + s for s in suffixes}
378 378 self.assertTrue(comp.issubset(set(c)))
379 379
380 380 def test_quoted_file_completions(self):
381 381 ip = get_ipython()
382 382 with TemporaryWorkingDirectory():
383 383 name = "foo'bar"
384 open(name, "w", encoding='utf-8').close()
384 open(name, "w", encoding="utf-8").close()
385 385
386 386 # Don't escape Windows
387 387 escaped = name if sys.platform == "win32" else "foo\\'bar"
388 388
389 389 # Single quote matches embedded single quote
390 390 text = "open('foo"
391 391 c = ip.Completer._complete(
392 392 cursor_line=0, cursor_pos=len(text), full_text=text
393 393 )[1]
394 394 self.assertEqual(c, [escaped])
395 395
396 396 # Double quote requires no escape
397 397 text = 'open("foo'
398 398 c = ip.Completer._complete(
399 399 cursor_line=0, cursor_pos=len(text), full_text=text
400 400 )[1]
401 401 self.assertEqual(c, [name])
402 402
403 403 # No quote requires an escape
404 404 text = "%ls foo"
405 405 c = ip.Completer._complete(
406 406 cursor_line=0, cursor_pos=len(text), full_text=text
407 407 )[1]
408 408 self.assertEqual(c, [escaped])
409 409
410 410 def test_all_completions_dups(self):
411 411 """
412 412 Make sure the output of `IPCompleter.all_completions` does not have
413 413 duplicated prefixes.
414 414 """
415 415 ip = get_ipython()
416 416 c = ip.Completer
417 417 ip.ex("class TestClass():\n\ta=1\n\ta1=2")
418 418 for jedi_status in [True, False]:
419 419 with provisionalcompleter():
420 420 ip.Completer.use_jedi = jedi_status
421 421 matches = c.all_completions("TestCl")
422 422 assert matches == ["TestClass"], (jedi_status, matches)
423 423 matches = c.all_completions("TestClass.")
424 424 assert len(matches) > 2, (jedi_status, matches)
425 425 matches = c.all_completions("TestClass.a")
426 426 assert matches == ['TestClass.a', 'TestClass.a1'], jedi_status
427 427
428 428 def test_jedi(self):
429 429 """
430 430 A couple of issue we had with Jedi
431 431 """
432 432 ip = get_ipython()
433 433
434 434 def _test_complete(reason, s, comp, start=None, end=None):
435 435 l = len(s)
436 436 start = start if start is not None else l
437 437 end = end if end is not None else l
438 438 with provisionalcompleter():
439 439 ip.Completer.use_jedi = True
440 440 completions = set(ip.Completer.completions(s, l))
441 441 ip.Completer.use_jedi = False
442 442 assert Completion(start, end, comp) in completions, reason
443 443
444 444 def _test_not_complete(reason, s, comp):
445 445 l = len(s)
446 446 with provisionalcompleter():
447 447 ip.Completer.use_jedi = True
448 448 completions = set(ip.Completer.completions(s, l))
449 449 ip.Completer.use_jedi = False
450 450 assert Completion(l, l, comp) not in completions, reason
451 451
452 452 import jedi
453 453
454 454 jedi_version = tuple(int(i) for i in jedi.__version__.split(".")[:3])
455 455 if jedi_version > (0, 10):
456 456 _test_complete("jedi >0.9 should complete and not crash", "a=1;a.", "real")
457 457 _test_complete("can infer first argument", 'a=(1,"foo");a[0].', "real")
458 458 _test_complete("can infer second argument", 'a=(1,"foo");a[1].', "capitalize")
459 459 _test_complete("cover duplicate completions", "im", "import", 0, 2)
460 460
461 461 _test_not_complete("does not mix types", 'a=(1,"foo");a[0].', "capitalize")
462 462
463 463 def test_completion_have_signature(self):
464 464 """
465 465 Lets make sure jedi is capable of pulling out the signature of the function we are completing.
466 466 """
467 467 ip = get_ipython()
468 468 with provisionalcompleter():
469 469 ip.Completer.use_jedi = True
470 470 completions = ip.Completer.completions("ope", 3)
471 471 c = next(completions) # should be `open`
472 472 ip.Completer.use_jedi = False
473 473 assert "file" in c.signature, "Signature of function was not found by completer"
474 474 assert (
475 475 "encoding" in c.signature
476 476 ), "Signature of function was not found by completer"
477 477
478 478 @pytest.mark.xfail(reason="Known failure on jedi<=0.18.0")
479 479 def test_deduplicate_completions(self):
480 480 """
481 481 Test that completions are correctly deduplicated (even if ranges are not the same)
482 482 """
483 483 ip = get_ipython()
484 484 ip.ex(
485 485 textwrap.dedent(
486 486 """
487 487 class Z:
488 488 zoo = 1
489 489 """
490 490 )
491 491 )
492 492 with provisionalcompleter():
493 493 ip.Completer.use_jedi = True
494 494 l = list(
495 495 _deduplicate_completions("Z.z", ip.Completer.completions("Z.z", 3))
496 496 )
497 497 ip.Completer.use_jedi = False
498 498
499 499 assert len(l) == 1, "Completions (Z.z<tab>) correctly deduplicate: %s " % l
500 500 assert l[0].text == "zoo" # and not `it.accumulate`
501 501
502 502 def test_greedy_completions(self):
503 503 """
504 504 Test the capability of the Greedy completer.
505 505
506 506 Most of the test here does not really show off the greedy completer, for proof
507 507 each of the text below now pass with Jedi. The greedy completer is capable of more.
508 508
509 509 See the :any:`test_dict_key_completion_contexts`
510 510
511 511 """
512 512 ip = get_ipython()
513 513 ip.ex("a=list(range(5))")
514 514 _, c = ip.complete(".", line="a[0].")
515 515 self.assertFalse(".real" in c, "Shouldn't have completed on a[0]: %s" % c)
516 516
517 517 def _(line, cursor_pos, expect, message, completion):
518 518 with greedy_completion(), provisionalcompleter():
519 519 ip.Completer.use_jedi = False
520 520 _, c = ip.complete(".", line=line, cursor_pos=cursor_pos)
521 521 self.assertIn(expect, c, message % c)
522 522
523 523 ip.Completer.use_jedi = True
524 524 with provisionalcompleter():
525 525 completions = ip.Completer.completions(line, cursor_pos)
526 526 self.assertIn(completion, completions)
527 527
528 528 with provisionalcompleter():
529 529 _(
530 530 "a[0].",
531 531 5,
532 532 "a[0].real",
533 533 "Should have completed on a[0].: %s",
534 534 Completion(5, 5, "real"),
535 535 )
536 536 _(
537 537 "a[0].r",
538 538 6,
539 539 "a[0].real",
540 540 "Should have completed on a[0].r: %s",
541 541 Completion(5, 6, "real"),
542 542 )
543 543
544 544 _(
545 545 "a[0].from_",
546 546 10,
547 547 "a[0].from_bytes",
548 548 "Should have completed on a[0].from_: %s",
549 549 Completion(5, 10, "from_bytes"),
550 550 )
551 551
552 552 def test_omit__names(self):
553 553 # also happens to test IPCompleter as a configurable
554 554 ip = get_ipython()
555 555 ip._hidden_attr = 1
556 556 ip._x = {}
557 557 c = ip.Completer
558 558 ip.ex("ip=get_ipython()")
559 559 cfg = Config()
560 560 cfg.IPCompleter.omit__names = 0
561 561 c.update_config(cfg)
562 562 with provisionalcompleter():
563 563 c.use_jedi = False
564 564 s, matches = c.complete("ip.")
565 565 self.assertIn("ip.__str__", matches)
566 566 self.assertIn("ip._hidden_attr", matches)
567 567
568 568 # c.use_jedi = True
569 569 # completions = set(c.completions('ip.', 3))
570 570 # self.assertIn(Completion(3, 3, '__str__'), completions)
571 571 # self.assertIn(Completion(3,3, "_hidden_attr"), completions)
572 572
573 573 cfg = Config()
574 574 cfg.IPCompleter.omit__names = 1
575 575 c.update_config(cfg)
576 576 with provisionalcompleter():
577 577 c.use_jedi = False
578 578 s, matches = c.complete("ip.")
579 579 self.assertNotIn("ip.__str__", matches)
580 580 # self.assertIn('ip._hidden_attr', matches)
581 581
582 582 # c.use_jedi = True
583 583 # completions = set(c.completions('ip.', 3))
584 584 # self.assertNotIn(Completion(3,3,'__str__'), completions)
585 585 # self.assertIn(Completion(3,3, "_hidden_attr"), completions)
586 586
587 587 cfg = Config()
588 588 cfg.IPCompleter.omit__names = 2
589 589 c.update_config(cfg)
590 590 with provisionalcompleter():
591 591 c.use_jedi = False
592 592 s, matches = c.complete("ip.")
593 593 self.assertNotIn("ip.__str__", matches)
594 594 self.assertNotIn("ip._hidden_attr", matches)
595 595
596 596 # c.use_jedi = True
597 597 # completions = set(c.completions('ip.', 3))
598 598 # self.assertNotIn(Completion(3,3,'__str__'), completions)
599 599 # self.assertNotIn(Completion(3,3, "_hidden_attr"), completions)
600 600
601 601 with provisionalcompleter():
602 602 c.use_jedi = False
603 603 s, matches = c.complete("ip._x.")
604 604 self.assertIn("ip._x.keys", matches)
605 605
606 606 # c.use_jedi = True
607 607 # completions = set(c.completions('ip._x.', 6))
608 608 # self.assertIn(Completion(6,6, "keys"), completions)
609 609
610 610 del ip._hidden_attr
611 611 del ip._x
612 612
613 613 def test_limit_to__all__False_ok(self):
614 614 """
615 615 Limit to all is deprecated, once we remove it this test can go away.
616 616 """
617 617 ip = get_ipython()
618 618 c = ip.Completer
619 619 c.use_jedi = False
620 620 ip.ex("class D: x=24")
621 621 ip.ex("d=D()")
622 622 cfg = Config()
623 623 cfg.IPCompleter.limit_to__all__ = False
624 624 c.update_config(cfg)
625 625 s, matches = c.complete("d.")
626 626 self.assertIn("d.x", matches)
627 627
628 628 def test_get__all__entries_ok(self):
629 629 class A:
630 630 __all__ = ["x", 1]
631 631
632 632 words = completer.get__all__entries(A())
633 633 self.assertEqual(words, ["x"])
634 634
635 635 def test_get__all__entries_no__all__ok(self):
636 636 class A:
637 637 pass
638 638
639 639 words = completer.get__all__entries(A())
640 640 self.assertEqual(words, [])
641 641
642 642 def test_func_kw_completions(self):
643 643 ip = get_ipython()
644 644 c = ip.Completer
645 645 c.use_jedi = False
646 646 ip.ex("def myfunc(a=1,b=2): return a+b")
647 647 s, matches = c.complete(None, "myfunc(1,b")
648 648 self.assertIn("b=", matches)
649 649 # Simulate completing with cursor right after b (pos==10):
650 650 s, matches = c.complete(None, "myfunc(1,b)", 10)
651 651 self.assertIn("b=", matches)
652 652 s, matches = c.complete(None, 'myfunc(a="escaped\\")string",b')
653 653 self.assertIn("b=", matches)
654 654 # builtin function
655 655 s, matches = c.complete(None, "min(k, k")
656 656 self.assertIn("key=", matches)
657 657
658 658 def test_default_arguments_from_docstring(self):
659 659 ip = get_ipython()
660 660 c = ip.Completer
661 661 kwd = c._default_arguments_from_docstring("min(iterable[, key=func]) -> value")
662 662 self.assertEqual(kwd, ["key"])
663 663 # with cython type etc
664 664 kwd = c._default_arguments_from_docstring(
665 665 "Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n"
666 666 )
667 667 self.assertEqual(kwd, ["ncall", "resume", "nsplit"])
668 668 # white spaces
669 669 kwd = c._default_arguments_from_docstring(
670 670 "\n Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n"
671 671 )
672 672 self.assertEqual(kwd, ["ncall", "resume", "nsplit"])
673 673
674 674 def test_line_magics(self):
675 675 ip = get_ipython()
676 676 c = ip.Completer
677 677 s, matches = c.complete(None, "lsmag")
678 678 self.assertIn("%lsmagic", matches)
679 679 s, matches = c.complete(None, "%lsmag")
680 680 self.assertIn("%lsmagic", matches)
681 681
682 682 def test_cell_magics(self):
683 683 from IPython.core.magic import register_cell_magic
684 684
685 685 @register_cell_magic
686 686 def _foo_cellm(line, cell):
687 687 pass
688 688
689 689 ip = get_ipython()
690 690 c = ip.Completer
691 691
692 692 s, matches = c.complete(None, "_foo_ce")
693 693 self.assertIn("%%_foo_cellm", matches)
694 694 s, matches = c.complete(None, "%%_foo_ce")
695 695 self.assertIn("%%_foo_cellm", matches)
696 696
697 697 def test_line_cell_magics(self):
698 698 from IPython.core.magic import register_line_cell_magic
699 699
700 700 @register_line_cell_magic
701 701 def _bar_cellm(line, cell):
702 702 pass
703 703
704 704 ip = get_ipython()
705 705 c = ip.Completer
706 706
707 707 # The policy here is trickier, see comments in completion code. The
708 708 # returned values depend on whether the user passes %% or not explicitly,
709 709 # and this will show a difference if the same name is both a line and cell
710 710 # magic.
711 711 s, matches = c.complete(None, "_bar_ce")
712 712 self.assertIn("%_bar_cellm", matches)
713 713 self.assertIn("%%_bar_cellm", matches)
714 714 s, matches = c.complete(None, "%_bar_ce")
715 715 self.assertIn("%_bar_cellm", matches)
716 716 self.assertIn("%%_bar_cellm", matches)
717 717 s, matches = c.complete(None, "%%_bar_ce")
718 718 self.assertNotIn("%_bar_cellm", matches)
719 719 self.assertIn("%%_bar_cellm", matches)
720 720
721 721 def test_magic_completion_order(self):
722 722 ip = get_ipython()
723 723 c = ip.Completer
724 724
725 725 # Test ordering of line and cell magics.
726 726 text, matches = c.complete("timeit")
727 727 self.assertEqual(matches, ["%timeit", "%%timeit"])
728 728
729 729 def test_magic_completion_shadowing(self):
730 730 ip = get_ipython()
731 731 c = ip.Completer
732 732 c.use_jedi = False
733 733
734 734 # Before importing matplotlib, %matplotlib magic should be the only option.
735 735 text, matches = c.complete("mat")
736 736 self.assertEqual(matches, ["%matplotlib"])
737 737
738 738 # The newly introduced name should shadow the magic.
739 739 ip.run_cell("matplotlib = 1")
740 740 text, matches = c.complete("mat")
741 741 self.assertEqual(matches, ["matplotlib"])
742 742
743 743 # After removing matplotlib from namespace, the magic should again be
744 744 # the only option.
745 745 del ip.user_ns["matplotlib"]
746 746 text, matches = c.complete("mat")
747 747 self.assertEqual(matches, ["%matplotlib"])
748 748
749 749 def test_magic_completion_shadowing_explicit(self):
750 750 """
751 751 If the user try to complete a shadowed magic, and explicit % start should
752 752 still return the completions.
753 753 """
754 754 ip = get_ipython()
755 755 c = ip.Completer
756 756
757 757 # Before importing matplotlib, %matplotlib magic should be the only option.
758 758 text, matches = c.complete("%mat")
759 759 self.assertEqual(matches, ["%matplotlib"])
760 760
761 761 ip.run_cell("matplotlib = 1")
762 762
763 763 # After removing matplotlib from namespace, the magic should still be
764 764 # the only option.
765 765 text, matches = c.complete("%mat")
766 766 self.assertEqual(matches, ["%matplotlib"])
767 767
768 768 def test_magic_config(self):
769 769 ip = get_ipython()
770 770 c = ip.Completer
771 771
772 772 s, matches = c.complete(None, "conf")
773 773 self.assertIn("%config", matches)
774 774 s, matches = c.complete(None, "conf")
775 775 self.assertNotIn("AliasManager", matches)
776 776 s, matches = c.complete(None, "config ")
777 777 self.assertIn("AliasManager", matches)
778 778 s, matches = c.complete(None, "%config ")
779 779 self.assertIn("AliasManager", matches)
780 780 s, matches = c.complete(None, "config Ali")
781 781 self.assertListEqual(["AliasManager"], matches)
782 782 s, matches = c.complete(None, "%config Ali")
783 783 self.assertListEqual(["AliasManager"], matches)
784 784 s, matches = c.complete(None, "config AliasManager")
785 785 self.assertListEqual(["AliasManager"], matches)
786 786 s, matches = c.complete(None, "%config AliasManager")
787 787 self.assertListEqual(["AliasManager"], matches)
788 788 s, matches = c.complete(None, "config AliasManager.")
789 789 self.assertIn("AliasManager.default_aliases", matches)
790 790 s, matches = c.complete(None, "%config AliasManager.")
791 791 self.assertIn("AliasManager.default_aliases", matches)
792 792 s, matches = c.complete(None, "config AliasManager.de")
793 793 self.assertListEqual(["AliasManager.default_aliases"], matches)
794 794 s, matches = c.complete(None, "config AliasManager.de")
795 795 self.assertListEqual(["AliasManager.default_aliases"], matches)
796 796
797 797 def test_magic_color(self):
798 798 ip = get_ipython()
799 799 c = ip.Completer
800 800
801 801 s, matches = c.complete(None, "colo")
802 802 self.assertIn("%colors", matches)
803 803 s, matches = c.complete(None, "colo")
804 804 self.assertNotIn("NoColor", matches)
805 805 s, matches = c.complete(None, "%colors") # No trailing space
806 806 self.assertNotIn("NoColor", matches)
807 807 s, matches = c.complete(None, "colors ")
808 808 self.assertIn("NoColor", matches)
809 809 s, matches = c.complete(None, "%colors ")
810 810 self.assertIn("NoColor", matches)
811 811 s, matches = c.complete(None, "colors NoCo")
812 812 self.assertListEqual(["NoColor"], matches)
813 813 s, matches = c.complete(None, "%colors NoCo")
814 814 self.assertListEqual(["NoColor"], matches)
815 815
816 816 def test_match_dict_keys(self):
817 817 """
818 818 Test that match_dict_keys works on a couple of use case does return what
819 819 expected, and does not crash
820 820 """
821 821 delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?"
822 822
823 823 keys = ["foo", b"far"]
824 824 assert match_dict_keys(keys, "b'", delims=delims) == ("'", 2, ["far"])
825 825 assert match_dict_keys(keys, "b'f", delims=delims) == ("'", 2, ["far"])
826 826 assert match_dict_keys(keys, 'b"', delims=delims) == ('"', 2, ["far"])
827 827 assert match_dict_keys(keys, 'b"f', delims=delims) == ('"', 2, ["far"])
828 828
829 829 assert match_dict_keys(keys, "'", delims=delims) == ("'", 1, ["foo"])
830 830 assert match_dict_keys(keys, "'f", delims=delims) == ("'", 1, ["foo"])
831 831 assert match_dict_keys(keys, '"', delims=delims) == ('"', 1, ["foo"])
832 832 assert match_dict_keys(keys, '"f', delims=delims) == ('"', 1, ["foo"])
833 833
834 834 match_dict_keys
835 835
836 836 def test_match_dict_keys_tuple(self):
837 837 """
838 838 Test that match_dict_keys called with extra prefix works on a couple of use case,
839 839 does return what expected, and does not crash.
840 840 """
841 841 delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?"
842 842
843 843 keys = [("foo", "bar"), ("foo", "oof"), ("foo", b"bar"), ('other', 'test')]
844 844
845 845 # Completion on first key == "foo"
846 846 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["bar", "oof"])
847 847 assert match_dict_keys(keys, "\"", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["bar", "oof"])
848 848 assert match_dict_keys(keys, "'o", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["oof"])
849 849 assert match_dict_keys(keys, "\"o", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["oof"])
850 850 assert match_dict_keys(keys, "b'", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
851 851 assert match_dict_keys(keys, "b\"", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
852 852 assert match_dict_keys(keys, "b'b", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
853 853 assert match_dict_keys(keys, "b\"b", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
854 854
855 855 # No Completion
856 856 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("no_foo",)) == ("'", 1, [])
857 857 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("fo",)) == ("'", 1, [])
858 858
859 859 keys = [('foo1', 'foo2', 'foo3', 'foo4'), ('foo1', 'foo2', 'bar', 'foo4')]
860 860 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1',)) == ("'", 1, ["foo2", "foo2"])
861 861 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2')) == ("'", 1, ["foo3"])
862 862 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3')) == ("'", 1, ["foo4"])
863 863 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3', 'foo4')) == ("'", 1, [])
864 864
865 865 def test_dict_key_completion_string(self):
866 866 """Test dictionary key completion for string keys"""
867 867 ip = get_ipython()
868 868 complete = ip.Completer.complete
869 869
870 870 ip.user_ns["d"] = {"abc": None}
871 871
872 872 # check completion at different stages
873 873 _, matches = complete(line_buffer="d[")
874 874 self.assertIn("'abc'", matches)
875 875 self.assertNotIn("'abc']", matches)
876 876
877 877 _, matches = complete(line_buffer="d['")
878 878 self.assertIn("abc", matches)
879 879 self.assertNotIn("abc']", matches)
880 880
881 881 _, matches = complete(line_buffer="d['a")
882 882 self.assertIn("abc", matches)
883 883 self.assertNotIn("abc']", matches)
884 884
885 885 # check use of different quoting
886 886 _, matches = complete(line_buffer='d["')
887 887 self.assertIn("abc", matches)
888 888 self.assertNotIn('abc"]', matches)
889 889
890 890 _, matches = complete(line_buffer='d["a')
891 891 self.assertIn("abc", matches)
892 892 self.assertNotIn('abc"]', matches)
893 893
894 894 # check sensitivity to following context
895 895 _, matches = complete(line_buffer="d[]", cursor_pos=2)
896 896 self.assertIn("'abc'", matches)
897 897
898 898 _, matches = complete(line_buffer="d['']", cursor_pos=3)
899 899 self.assertIn("abc", matches)
900 900 self.assertNotIn("abc'", matches)
901 901 self.assertNotIn("abc']", matches)
902 902
903 903 # check multiple solutions are correctly returned and that noise is not
904 904 ip.user_ns["d"] = {
905 905 "abc": None,
906 906 "abd": None,
907 907 "bad": None,
908 908 object(): None,
909 909 5: None,
910 910 ("abe", None): None,
911 911 (None, "abf"): None
912 912 }
913 913
914 914 _, matches = complete(line_buffer="d['a")
915 915 self.assertIn("abc", matches)
916 916 self.assertIn("abd", matches)
917 917 self.assertNotIn("bad", matches)
918 918 self.assertNotIn("abe", matches)
919 919 self.assertNotIn("abf", matches)
920 920 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
921 921
922 922 # check escaping and whitespace
923 923 ip.user_ns["d"] = {"a\nb": None, "a'b": None, 'a"b': None, "a word": None}
924 924 _, matches = complete(line_buffer="d['a")
925 925 self.assertIn("a\\nb", matches)
926 926 self.assertIn("a\\'b", matches)
927 927 self.assertIn('a"b', matches)
928 928 self.assertIn("a word", matches)
929 929 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
930 930
931 931 # - can complete on non-initial word of the string
932 932 _, matches = complete(line_buffer="d['a w")
933 933 self.assertIn("word", matches)
934 934
935 935 # - understands quote escaping
936 936 _, matches = complete(line_buffer="d['a\\'")
937 937 self.assertIn("b", matches)
938 938
939 939 # - default quoting should work like repr
940 940 _, matches = complete(line_buffer="d[")
941 941 self.assertIn('"a\'b"', matches)
942 942
943 943 # - when opening quote with ", possible to match with unescaped apostrophe
944 944 _, matches = complete(line_buffer="d[\"a'")
945 945 self.assertIn("b", matches)
946 946
947 947 # need to not split at delims that readline won't split at
948 948 if "-" not in ip.Completer.splitter.delims:
949 949 ip.user_ns["d"] = {"before-after": None}
950 950 _, matches = complete(line_buffer="d['before-af")
951 951 self.assertIn("before-after", matches)
952 952
953 953 # check completion on tuple-of-string keys at different stage - on first key
954 954 ip.user_ns["d"] = {('foo', 'bar'): None}
955 955 _, matches = complete(line_buffer="d[")
956 956 self.assertIn("'foo'", matches)
957 957 self.assertNotIn("'foo']", matches)
958 958 self.assertNotIn("'bar'", matches)
959 959 self.assertNotIn("foo", matches)
960 960 self.assertNotIn("bar", matches)
961 961
962 962 # - match the prefix
963 963 _, matches = complete(line_buffer="d['f")
964 964 self.assertIn("foo", matches)
965 965 self.assertNotIn("foo']", matches)
966 966 self.assertNotIn('foo"]', matches)
967 967 _, matches = complete(line_buffer="d['foo")
968 968 self.assertIn("foo", matches)
969 969
970 970 # - can complete on second key
971 971 _, matches = complete(line_buffer="d['foo', ")
972 972 self.assertIn("'bar'", matches)
973 973 _, matches = complete(line_buffer="d['foo', 'b")
974 974 self.assertIn("bar", matches)
975 975 self.assertNotIn("foo", matches)
976 976
977 977 # - does not propose missing keys
978 978 _, matches = complete(line_buffer="d['foo', 'f")
979 979 self.assertNotIn("bar", matches)
980 980 self.assertNotIn("foo", matches)
981 981
982 982 # check sensitivity to following context
983 983 _, matches = complete(line_buffer="d['foo',]", cursor_pos=8)
984 984 self.assertIn("'bar'", matches)
985 985 self.assertNotIn("bar", matches)
986 986 self.assertNotIn("'foo'", matches)
987 987 self.assertNotIn("foo", matches)
988 988
989 989 _, matches = complete(line_buffer="d['']", cursor_pos=3)
990 990 self.assertIn("foo", matches)
991 991 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
992 992
993 993 _, matches = complete(line_buffer='d[""]', cursor_pos=3)
994 994 self.assertIn("foo", matches)
995 995 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
996 996
997 997 _, matches = complete(line_buffer='d["foo","]', cursor_pos=9)
998 998 self.assertIn("bar", matches)
999 999 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
1000 1000
1001 1001 _, matches = complete(line_buffer='d["foo",]', cursor_pos=8)
1002 1002 self.assertIn("'bar'", matches)
1003 1003 self.assertNotIn("bar", matches)
1004 1004
1005 1005 # Can complete with longer tuple keys
1006 1006 ip.user_ns["d"] = {('foo', 'bar', 'foobar'): None}
1007 1007
1008 1008 # - can complete second key
1009 1009 _, matches = complete(line_buffer="d['foo', 'b")
1010 1010 self.assertIn("bar", matches)
1011 1011 self.assertNotIn("foo", matches)
1012 1012 self.assertNotIn("foobar", matches)
1013 1013
1014 1014 # - can complete third key
1015 1015 _, matches = complete(line_buffer="d['foo', 'bar', 'fo")
1016 1016 self.assertIn("foobar", matches)
1017 1017 self.assertNotIn("foo", matches)
1018 1018 self.assertNotIn("bar", matches)
1019 1019
1020 1020 def test_dict_key_completion_contexts(self):
1021 1021 """Test expression contexts in which dict key completion occurs"""
1022 1022 ip = get_ipython()
1023 1023 complete = ip.Completer.complete
1024 1024 d = {"abc": None}
1025 1025 ip.user_ns["d"] = d
1026 1026
1027 1027 class C:
1028 1028 data = d
1029 1029
1030 1030 ip.user_ns["C"] = C
1031 1031 ip.user_ns["get"] = lambda: d
1032 1032
1033 1033 def assert_no_completion(**kwargs):
1034 1034 _, matches = complete(**kwargs)
1035 1035 self.assertNotIn("abc", matches)
1036 1036 self.assertNotIn("abc'", matches)
1037 1037 self.assertNotIn("abc']", matches)
1038 1038 self.assertNotIn("'abc'", matches)
1039 1039 self.assertNotIn("'abc']", matches)
1040 1040
1041 1041 def assert_completion(**kwargs):
1042 1042 _, matches = complete(**kwargs)
1043 1043 self.assertIn("'abc'", matches)
1044 1044 self.assertNotIn("'abc']", matches)
1045 1045
1046 1046 # no completion after string closed, even if reopened
1047 1047 assert_no_completion(line_buffer="d['a'")
1048 1048 assert_no_completion(line_buffer='d["a"')
1049 1049 assert_no_completion(line_buffer="d['a' + ")
1050 1050 assert_no_completion(line_buffer="d['a' + '")
1051 1051
1052 1052 # completion in non-trivial expressions
1053 1053 assert_completion(line_buffer="+ d[")
1054 1054 assert_completion(line_buffer="(d[")
1055 1055 assert_completion(line_buffer="C.data[")
1056 1056
1057 1057 # greedy flag
1058 1058 def assert_completion(**kwargs):
1059 1059 _, matches = complete(**kwargs)
1060 1060 self.assertIn("get()['abc']", matches)
1061 1061
1062 1062 assert_no_completion(line_buffer="get()[")
1063 1063 with greedy_completion():
1064 1064 assert_completion(line_buffer="get()[")
1065 1065 assert_completion(line_buffer="get()['")
1066 1066 assert_completion(line_buffer="get()['a")
1067 1067 assert_completion(line_buffer="get()['ab")
1068 1068 assert_completion(line_buffer="get()['abc")
1069 1069
1070 1070 def test_dict_key_completion_bytes(self):
1071 1071 """Test handling of bytes in dict key completion"""
1072 1072 ip = get_ipython()
1073 1073 complete = ip.Completer.complete
1074 1074
1075 1075 ip.user_ns["d"] = {"abc": None, b"abd": None}
1076 1076
1077 1077 _, matches = complete(line_buffer="d[")
1078 1078 self.assertIn("'abc'", matches)
1079 1079 self.assertIn("b'abd'", matches)
1080 1080
1081 1081 if False: # not currently implemented
1082 1082 _, matches = complete(line_buffer="d[b")
1083 1083 self.assertIn("b'abd'", matches)
1084 1084 self.assertNotIn("b'abc'", matches)
1085 1085
1086 1086 _, matches = complete(line_buffer="d[b'")
1087 1087 self.assertIn("abd", matches)
1088 1088 self.assertNotIn("abc", matches)
1089 1089
1090 1090 _, matches = complete(line_buffer="d[B'")
1091 1091 self.assertIn("abd", matches)
1092 1092 self.assertNotIn("abc", matches)
1093 1093
1094 1094 _, matches = complete(line_buffer="d['")
1095 1095 self.assertIn("abc", matches)
1096 1096 self.assertNotIn("abd", matches)
1097 1097
1098 1098 def test_dict_key_completion_unicode_py3(self):
1099 1099 """Test handling of unicode in dict key completion"""
1100 1100 ip = get_ipython()
1101 1101 complete = ip.Completer.complete
1102 1102
1103 1103 ip.user_ns["d"] = {"a\u05d0": None}
1104 1104
1105 1105 # query using escape
1106 1106 if sys.platform != "win32":
1107 1107 # Known failure on Windows
1108 1108 _, matches = complete(line_buffer="d['a\\u05d0")
1109 1109 self.assertIn("u05d0", matches) # tokenized after \\
1110 1110
1111 1111 # query using character
1112 1112 _, matches = complete(line_buffer="d['a\u05d0")
1113 1113 self.assertIn("a\u05d0", matches)
1114 1114
1115 1115 with greedy_completion():
1116 1116 # query using escape
1117 1117 _, matches = complete(line_buffer="d['a\\u05d0")
1118 1118 self.assertIn("d['a\\u05d0']", matches) # tokenized after \\
1119 1119
1120 1120 # query using character
1121 1121 _, matches = complete(line_buffer="d['a\u05d0")
1122 1122 self.assertIn("d['a\u05d0']", matches)
1123 1123
1124 1124 @dec.skip_without("numpy")
1125 1125 def test_struct_array_key_completion(self):
1126 1126 """Test dict key completion applies to numpy struct arrays"""
1127 1127 import numpy
1128 1128
1129 1129 ip = get_ipython()
1130 1130 complete = ip.Completer.complete
1131 1131 ip.user_ns["d"] = numpy.array([], dtype=[("hello", "f"), ("world", "f")])
1132 1132 _, matches = complete(line_buffer="d['")
1133 1133 self.assertIn("hello", matches)
1134 1134 self.assertIn("world", matches)
1135 1135 # complete on the numpy struct itself
1136 1136 dt = numpy.dtype(
1137 1137 [("my_head", [("my_dt", ">u4"), ("my_df", ">u4")]), ("my_data", ">f4", 5)]
1138 1138 )
1139 1139 x = numpy.zeros(2, dtype=dt)
1140 1140 ip.user_ns["d"] = x[1]
1141 1141 _, matches = complete(line_buffer="d['")
1142 1142 self.assertIn("my_head", matches)
1143 1143 self.assertIn("my_data", matches)
1144 1144 # complete on a nested level
1145 1145 with greedy_completion():
1146 1146 ip.user_ns["d"] = numpy.zeros(2, dtype=dt)
1147 1147 _, matches = complete(line_buffer="d[1]['my_head']['")
1148 1148 self.assertTrue(any(["my_dt" in m for m in matches]))
1149 1149 self.assertTrue(any(["my_df" in m for m in matches]))
1150 1150
1151 1151 @dec.skip_without("pandas")
1152 1152 def test_dataframe_key_completion(self):
1153 1153 """Test dict key completion applies to pandas DataFrames"""
1154 1154 import pandas
1155 1155
1156 1156 ip = get_ipython()
1157 1157 complete = ip.Completer.complete
1158 1158 ip.user_ns["d"] = pandas.DataFrame({"hello": [1], "world": [2]})
1159 1159 _, matches = complete(line_buffer="d['")
1160 1160 self.assertIn("hello", matches)
1161 1161 self.assertIn("world", matches)
1162 1162
1163 1163 def test_dict_key_completion_invalids(self):
1164 1164 """Smoke test cases dict key completion can't handle"""
1165 1165 ip = get_ipython()
1166 1166 complete = ip.Completer.complete
1167 1167
1168 1168 ip.user_ns["no_getitem"] = None
1169 1169 ip.user_ns["no_keys"] = []
1170 1170 ip.user_ns["cant_call_keys"] = dict
1171 1171 ip.user_ns["empty"] = {}
1172 1172 ip.user_ns["d"] = {"abc": 5}
1173 1173
1174 1174 _, matches = complete(line_buffer="no_getitem['")
1175 1175 _, matches = complete(line_buffer="no_keys['")
1176 1176 _, matches = complete(line_buffer="cant_call_keys['")
1177 1177 _, matches = complete(line_buffer="empty['")
1178 1178 _, matches = complete(line_buffer="name_error['")
1179 1179 _, matches = complete(line_buffer="d['\\") # incomplete escape
1180 1180
1181 1181 def test_object_key_completion(self):
1182 1182 ip = get_ipython()
1183 1183 ip.user_ns["key_completable"] = KeyCompletable(["qwerty", "qwick"])
1184 1184
1185 1185 _, matches = ip.Completer.complete(line_buffer="key_completable['qw")
1186 1186 self.assertIn("qwerty", matches)
1187 1187 self.assertIn("qwick", matches)
1188 1188
1189 1189 def test_class_key_completion(self):
1190 1190 ip = get_ipython()
1191 1191 NamedInstanceClass("qwerty")
1192 1192 NamedInstanceClass("qwick")
1193 1193 ip.user_ns["named_instance_class"] = NamedInstanceClass
1194 1194
1195 1195 _, matches = ip.Completer.complete(line_buffer="named_instance_class['qw")
1196 1196 self.assertIn("qwerty", matches)
1197 1197 self.assertIn("qwick", matches)
1198 1198
1199 1199 def test_tryimport(self):
1200 1200 """
1201 1201 Test that try-import don't crash on trailing dot, and import modules before
1202 1202 """
1203 1203 from IPython.core.completerlib import try_import
1204 1204
1205 1205 assert try_import("IPython.")
1206 1206
1207 1207 def test_aimport_module_completer(self):
1208 1208 ip = get_ipython()
1209 1209 _, matches = ip.complete("i", "%aimport i")
1210 1210 self.assertIn("io", matches)
1211 1211 self.assertNotIn("int", matches)
1212 1212
1213 1213 def test_nested_import_module_completer(self):
1214 1214 ip = get_ipython()
1215 1215 _, matches = ip.complete(None, "import IPython.co", 17)
1216 1216 self.assertIn("IPython.core", matches)
1217 1217 self.assertNotIn("import IPython.core", matches)
1218 1218 self.assertNotIn("IPython.display", matches)
1219 1219
1220 1220 def test_import_module_completer(self):
1221 1221 ip = get_ipython()
1222 1222 _, matches = ip.complete("i", "import i")
1223 1223 self.assertIn("io", matches)
1224 1224 self.assertNotIn("int", matches)
1225 1225
1226 1226 def test_from_module_completer(self):
1227 1227 ip = get_ipython()
1228 1228 _, matches = ip.complete("B", "from io import B", 16)
1229 1229 self.assertIn("BytesIO", matches)
1230 1230 self.assertNotIn("BaseException", matches)
1231 1231
1232 1232 def test_snake_case_completion(self):
1233 1233 ip = get_ipython()
1234 1234 ip.Completer.use_jedi = False
1235 1235 ip.user_ns["some_three"] = 3
1236 1236 ip.user_ns["some_four"] = 4
1237 1237 _, matches = ip.complete("s_", "print(s_f")
1238 1238 self.assertIn("some_three", matches)
1239 1239 self.assertIn("some_four", matches)
1240 1240
1241 1241 def test_mix_terms(self):
1242 1242 ip = get_ipython()
1243 1243 from textwrap import dedent
1244 1244
1245 1245 ip.Completer.use_jedi = False
1246 1246 ip.ex(
1247 1247 dedent(
1248 1248 """
1249 1249 class Test:
1250 1250 def meth(self, meth_arg1):
1251 1251 print("meth")
1252 1252
1253 1253 def meth_1(self, meth1_arg1, meth1_arg2):
1254 1254 print("meth1")
1255 1255
1256 1256 def meth_2(self, meth2_arg1, meth2_arg2):
1257 1257 print("meth2")
1258 1258 test = Test()
1259 1259 """
1260 1260 )
1261 1261 )
1262 1262 _, matches = ip.complete(None, "test.meth(")
1263 1263 self.assertIn("meth_arg1=", matches)
1264 1264 self.assertNotIn("meth2_arg1=", matches)
@@ -1,192 +1,192 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tests for completerlib.
3 3
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Imports
8 8 #-----------------------------------------------------------------------------
9 9
10 10 import os
11 11 import shutil
12 12 import sys
13 13 import tempfile
14 14 import unittest
15 15 from os.path import join
16 16
17 17 from IPython.core.completerlib import magic_run_completer, module_completion, try_import
18 18 from IPython.utils.tempdir import TemporaryDirectory
19 19 from IPython.testing.decorators import onlyif_unicode_paths
20 20
21 21
22 22 class MockEvent(object):
23 23 def __init__(self, line):
24 24 self.line = line
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Test functions begin
28 28 #-----------------------------------------------------------------------------
29 29 class Test_magic_run_completer(unittest.TestCase):
30 30 files = [u"aao.py", u"a.py", u"b.py", u"aao.txt"]
31 31 dirs = [u"adir/", "bdir/"]
32 32
33 33 def setUp(self):
34 34 self.BASETESTDIR = tempfile.mkdtemp()
35 35 for fil in self.files:
36 with open(join(self.BASETESTDIR, fil), "w", encoding='utf-8') as sfile:
36 with open(join(self.BASETESTDIR, fil), "w", encoding="utf-8") as sfile:
37 37 sfile.write("pass\n")
38 38 for d in self.dirs:
39 39 os.mkdir(join(self.BASETESTDIR, d))
40 40
41 41 self.oldpath = os.getcwd()
42 42 os.chdir(self.BASETESTDIR)
43 43
44 44 def tearDown(self):
45 45 os.chdir(self.oldpath)
46 46 shutil.rmtree(self.BASETESTDIR)
47 47
48 48 def test_1(self):
49 49 """Test magic_run_completer, should match two alternatives
50 50 """
51 51 event = MockEvent(u"%run a")
52 52 mockself = None
53 53 match = set(magic_run_completer(mockself, event))
54 54 self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
55 55
56 56 def test_2(self):
57 57 """Test magic_run_completer, should match one alternative
58 58 """
59 59 event = MockEvent(u"%run aa")
60 60 mockself = None
61 61 match = set(magic_run_completer(mockself, event))
62 62 self.assertEqual(match, {u"aao.py"})
63 63
64 64 def test_3(self):
65 65 """Test magic_run_completer with unterminated " """
66 66 event = MockEvent(u'%run "a')
67 67 mockself = None
68 68 match = set(magic_run_completer(mockself, event))
69 69 self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
70 70
71 71 def test_completion_more_args(self):
72 72 event = MockEvent(u'%run a.py ')
73 73 match = set(magic_run_completer(None, event))
74 74 self.assertEqual(match, set(self.files + self.dirs))
75 75
76 76 def test_completion_in_dir(self):
77 77 # Github issue #3459
78 78 event = MockEvent(u'%run a.py {}'.format(join(self.BASETESTDIR, 'a')))
79 79 print(repr(event.line))
80 80 match = set(magic_run_completer(None, event))
81 81 # We specifically use replace here rather than normpath, because
82 82 # at one point there were duplicates 'adir' and 'adir/', and normpath
83 83 # would hide the failure for that.
84 84 self.assertEqual(match, {join(self.BASETESTDIR, f).replace('\\','/')
85 85 for f in (u'a.py', u'aao.py', u'aao.txt', u'adir/')})
86 86
87 87 class Test_magic_run_completer_nonascii(unittest.TestCase):
88 88 @onlyif_unicode_paths
89 89 def setUp(self):
90 90 self.BASETESTDIR = tempfile.mkdtemp()
91 91 for fil in [u"aaø.py", u"a.py", u"b.py"]:
92 with open(join(self.BASETESTDIR, fil), "w", encoding='utf-8') as sfile:
92 with open(join(self.BASETESTDIR, fil), "w", encoding="utf-8") as sfile:
93 93 sfile.write("pass\n")
94 94 self.oldpath = os.getcwd()
95 95 os.chdir(self.BASETESTDIR)
96 96
97 97 def tearDown(self):
98 98 os.chdir(self.oldpath)
99 99 shutil.rmtree(self.BASETESTDIR)
100 100
101 101 @onlyif_unicode_paths
102 102 def test_1(self):
103 103 """Test magic_run_completer, should match two alternatives
104 104 """
105 105 event = MockEvent(u"%run a")
106 106 mockself = None
107 107 match = set(magic_run_completer(mockself, event))
108 108 self.assertEqual(match, {u"a.py", u"aaø.py"})
109 109
110 110 @onlyif_unicode_paths
111 111 def test_2(self):
112 112 """Test magic_run_completer, should match one alternative
113 113 """
114 114 event = MockEvent(u"%run aa")
115 115 mockself = None
116 116 match = set(magic_run_completer(mockself, event))
117 117 self.assertEqual(match, {u"aaø.py"})
118 118
119 119 @onlyif_unicode_paths
120 120 def test_3(self):
121 121 """Test magic_run_completer with unterminated " """
122 122 event = MockEvent(u'%run "a')
123 123 mockself = None
124 124 match = set(magic_run_completer(mockself, event))
125 125 self.assertEqual(match, {u"a.py", u"aaø.py"})
126 126
127 127 # module_completer:
128 128
129 129 def test_import_invalid_module():
130 130 """Testing of issue https://github.com/ipython/ipython/issues/1107"""
131 131 invalid_module_names = {'foo-bar', 'foo:bar', '10foo'}
132 132 valid_module_names = {'foobar'}
133 133 with TemporaryDirectory() as tmpdir:
134 134 sys.path.insert( 0, tmpdir )
135 135 for name in invalid_module_names | valid_module_names:
136 filename = os.path.join(tmpdir, name + '.py')
137 open(filename, 'w', encoding='utf-8').close()
136 filename = os.path.join(tmpdir, name + ".py")
137 open(filename, "w", encoding="utf-8").close()
138 138
139 139 s = set( module_completion('import foo') )
140 140 intersection = s.intersection(invalid_module_names)
141 141 assert intersection == set()
142 142
143 143 assert valid_module_names.issubset(s), valid_module_names.intersection(s)
144 144
145 145
146 146 def test_bad_module_all():
147 147 """Test module with invalid __all__
148 148
149 149 https://github.com/ipython/ipython/issues/9678
150 150 """
151 151 testsdir = os.path.dirname(__file__)
152 152 sys.path.insert(0, testsdir)
153 153 try:
154 154 results = module_completion("from bad_all import ")
155 155 assert "puppies" in results
156 156 for r in results:
157 157 assert isinstance(r, str)
158 158
159 159 # bad_all doesn't contain submodules, but this completion
160 160 # should finish without raising an exception:
161 161 results = module_completion("import bad_all.")
162 162 assert results == []
163 163 finally:
164 164 sys.path.remove(testsdir)
165 165
166 166
167 167 def test_module_without_init():
168 168 """
169 169 Test module without __init__.py.
170 170
171 171 https://github.com/ipython/ipython/issues/11226
172 172 """
173 173 fake_module_name = "foo"
174 174 with TemporaryDirectory() as tmpdir:
175 175 sys.path.insert(0, tmpdir)
176 176 try:
177 177 os.makedirs(os.path.join(tmpdir, fake_module_name))
178 178 s = try_import(mod=fake_module_name)
179 179 assert s == []
180 180 finally:
181 181 sys.path.remove(tmpdir)
182 182
183 183
184 184 def test_valid_exported_submodules():
185 185 """
186 186 Test checking exported (__all__) objects are submodules
187 187 """
188 188 results = module_completion("import os.pa")
189 189 # ensure we get a valid submodule:
190 190 assert "os.path" in results
191 191 # ensure we don't get objects that aren't submodules:
192 192 assert "os.pathconf" not in results
@@ -1,94 +1,94 b''
1 1 import os.path
2 2
3 3 import IPython.testing.tools as tt
4 4 from IPython.utils.syspathcontext import prepended_to_syspath
5 5 from IPython.utils.tempdir import TemporaryDirectory
6 6
7 7 ext1_content = """
8 8 def load_ipython_extension(ip):
9 9 print("Running ext1 load")
10 10
11 11 def unload_ipython_extension(ip):
12 12 print("Running ext1 unload")
13 13 """
14 14
15 15 ext2_content = """
16 16 def load_ipython_extension(ip):
17 17 print("Running ext2 load")
18 18 """
19 19
20 20 ext3_content = """
21 21 def load_ipython_extension(ip):
22 22 ip2 = get_ipython()
23 23 print(ip is ip2)
24 24 """
25 25
26 26 def test_extension_loading():
27 27 em = get_ipython().extension_manager
28 28 with TemporaryDirectory() as td:
29 ext1 = os.path.join(td, 'ext1.py')
30 with open(ext1, 'w', encoding='utf-8') as f:
29 ext1 = os.path.join(td, "ext1.py")
30 with open(ext1, "w", encoding="utf-8") as f:
31 31 f.write(ext1_content)
32
33 ext2 = os.path.join(td, 'ext2.py')
34 with open(ext2, 'w', encoding='utf-8') as f:
32
33 ext2 = os.path.join(td, "ext2.py")
34 with open(ext2, "w", encoding="utf-8") as f:
35 35 f.write(ext2_content)
36 36
37 37 with prepended_to_syspath(td):
38 38 assert 'ext1' not in em.loaded
39 39 assert 'ext2' not in em.loaded
40 40
41 41 # Load extension
42 42 with tt.AssertPrints("Running ext1 load"):
43 43 assert em.load_extension('ext1') is None
44 44 assert 'ext1' in em.loaded
45 45
46 46 # Should refuse to load it again
47 47 with tt.AssertNotPrints("Running ext1 load"):
48 48 assert em.load_extension('ext1') == 'already loaded'
49 49
50 50 # Reload
51 51 with tt.AssertPrints("Running ext1 unload"):
52 52 with tt.AssertPrints("Running ext1 load", suppress=False):
53 53 em.reload_extension('ext1')
54 54
55 55 # Unload
56 56 with tt.AssertPrints("Running ext1 unload"):
57 57 assert em.unload_extension('ext1') is None
58 58
59 59 # Can't unload again
60 60 with tt.AssertNotPrints("Running ext1 unload"):
61 61 assert em.unload_extension('ext1') == 'not loaded'
62 62 assert em.unload_extension('ext2') == 'not loaded'
63 63
64 64 # Load extension 2
65 65 with tt.AssertPrints("Running ext2 load"):
66 66 assert em.load_extension('ext2') is None
67 67
68 68 # Can't unload this
69 69 assert em.unload_extension('ext2') == 'no unload function'
70 70
71 71 # But can reload it
72 72 with tt.AssertPrints("Running ext2 load"):
73 73 em.reload_extension('ext2')
74 74
75 75
76 76 def test_extension_builtins():
77 77 em = get_ipython().extension_manager
78 78 with TemporaryDirectory() as td:
79 ext3 = os.path.join(td, 'ext3.py')
80 with open(ext3, 'w', encoding='utf-8') as f:
79 ext3 = os.path.join(td, "ext3.py")
80 with open(ext3, "w", encoding="utf-8") as f:
81 81 f.write(ext3_content)
82 82
83 83 assert 'ext3' not in em.loaded
84 84
85 85 with prepended_to_syspath(td):
86 86 # Load extension
87 87 with tt.AssertPrints("True"):
88 88 assert em.load_extension('ext3') is None
89 89 assert 'ext3' in em.loaded
90 90
91 91
92 92 def test_non_extension():
93 93 em = get_ipython().extension_manager
94 94 assert em.load_extension("sys") == "no load function"
@@ -1,1098 +1,1100 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
9 9 # Copyright (c) IPython Development Team.
10 10 # Distributed under the terms of the Modified BSD License.
11 11
12 12 import asyncio
13 13 import ast
14 14 import os
15 15 import signal
16 16 import shutil
17 17 import sys
18 18 import tempfile
19 19 import unittest
20 20 from unittest import mock
21 21
22 22 from os.path import join
23 23
24 24 from IPython.core.error import InputRejected
25 25 from IPython.core.inputtransformer import InputTransformer
26 26 from IPython.core import interactiveshell
27 27 from IPython.testing.decorators import (
28 28 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
29 29 )
30 30 from IPython.testing import tools as tt
31 31 from IPython.utils.process import find_cmd
32 32
33 33 #-----------------------------------------------------------------------------
34 34 # Globals
35 35 #-----------------------------------------------------------------------------
36 36 # This is used by every single test, no point repeating it ad nauseam
37 37
38 38 #-----------------------------------------------------------------------------
39 39 # Tests
40 40 #-----------------------------------------------------------------------------
41 41
42 42 class DerivedInterrupt(KeyboardInterrupt):
43 43 pass
44 44
45 45 class InteractiveShellTestCase(unittest.TestCase):
46 46 def test_naked_string_cells(self):
47 47 """Test that cells with only naked strings are fully executed"""
48 48 # First, single-line inputs
49 49 ip.run_cell('"a"\n')
50 50 self.assertEqual(ip.user_ns['_'], 'a')
51 51 # And also multi-line cells
52 52 ip.run_cell('"""a\nb"""\n')
53 53 self.assertEqual(ip.user_ns['_'], 'a\nb')
54 54
55 55 def test_run_empty_cell(self):
56 56 """Just make sure we don't get a horrible error with a blank
57 57 cell of input. Yes, I did overlook that."""
58 58 old_xc = ip.execution_count
59 59 res = ip.run_cell('')
60 60 self.assertEqual(ip.execution_count, old_xc)
61 61 self.assertEqual(res.execution_count, None)
62 62
63 63 def test_run_cell_multiline(self):
64 64 """Multi-block, multi-line cells must execute correctly.
65 65 """
66 66 src = '\n'.join(["x=1",
67 67 "y=2",
68 68 "if 1:",
69 69 " x += 1",
70 70 " y += 1",])
71 71 res = ip.run_cell(src)
72 72 self.assertEqual(ip.user_ns['x'], 2)
73 73 self.assertEqual(ip.user_ns['y'], 3)
74 74 self.assertEqual(res.success, True)
75 75 self.assertEqual(res.result, None)
76 76
77 77 def test_multiline_string_cells(self):
78 78 "Code sprinkled with multiline strings should execute (GH-306)"
79 79 ip.run_cell('tmp=0')
80 80 self.assertEqual(ip.user_ns['tmp'], 0)
81 81 res = ip.run_cell('tmp=1;"""a\nb"""\n')
82 82 self.assertEqual(ip.user_ns['tmp'], 1)
83 83 self.assertEqual(res.success, True)
84 84 self.assertEqual(res.result, "a\nb")
85 85
86 86 def test_dont_cache_with_semicolon(self):
87 87 "Ending a line with semicolon should not cache the returned object (GH-307)"
88 88 oldlen = len(ip.user_ns['Out'])
89 89 for cell in ['1;', '1;1;']:
90 90 res = ip.run_cell(cell, store_history=True)
91 91 newlen = len(ip.user_ns['Out'])
92 92 self.assertEqual(oldlen, newlen)
93 93 self.assertIsNone(res.result)
94 94 i = 0
95 95 #also test the default caching behavior
96 96 for cell in ['1', '1;1']:
97 97 ip.run_cell(cell, store_history=True)
98 98 newlen = len(ip.user_ns['Out'])
99 99 i += 1
100 100 self.assertEqual(oldlen+i, newlen)
101 101
102 102 def test_syntax_error(self):
103 103 res = ip.run_cell("raise = 3")
104 104 self.assertIsInstance(res.error_before_exec, SyntaxError)
105 105
106 106 def test_In_variable(self):
107 107 "Verify that In variable grows with user input (GH-284)"
108 108 oldlen = len(ip.user_ns['In'])
109 109 ip.run_cell('1;', store_history=True)
110 110 newlen = len(ip.user_ns['In'])
111 111 self.assertEqual(oldlen+1, newlen)
112 112 self.assertEqual(ip.user_ns['In'][-1],'1;')
113 113
114 114 def test_magic_names_in_string(self):
115 115 ip.run_cell('a = """\n%exit\n"""')
116 116 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
117 117
118 118 def test_trailing_newline(self):
119 119 """test that running !(command) does not raise a SyntaxError"""
120 120 ip.run_cell('!(true)\n', False)
121 121 ip.run_cell('!(true)\n\n\n', False)
122 122
123 123 def test_gh_597(self):
124 124 """Pretty-printing lists of objects with non-ascii reprs may cause
125 125 problems."""
126 126 class Spam(object):
127 127 def __repr__(self):
128 128 return "\xe9"*50
129 129 import IPython.core.formatters
130 130 f = IPython.core.formatters.PlainTextFormatter()
131 131 f([Spam(),Spam()])
132 132
133 133
134 134 def test_future_flags(self):
135 135 """Check that future flags are used for parsing code (gh-777)"""
136 136 ip.run_cell('from __future__ import barry_as_FLUFL')
137 137 try:
138 138 ip.run_cell('prfunc_return_val = 1 <> 2')
139 139 assert 'prfunc_return_val' in ip.user_ns
140 140 finally:
141 141 # Reset compiler flags so we don't mess up other tests.
142 142 ip.compile.reset_compiler_flags()
143 143
144 144 def test_can_pickle(self):
145 145 "Can we pickle objects defined interactively (GH-29)"
146 146 ip = get_ipython()
147 147 ip.reset()
148 148 ip.run_cell(("class Mylist(list):\n"
149 149 " def __init__(self,x=[]):\n"
150 150 " list.__init__(self,x)"))
151 151 ip.run_cell("w=Mylist([1,2,3])")
152 152
153 153 from pickle import dumps
154 154
155 155 # We need to swap in our main module - this is only necessary
156 156 # inside the test framework, because IPython puts the interactive module
157 157 # in place (but the test framework undoes this).
158 158 _main = sys.modules['__main__']
159 159 sys.modules['__main__'] = ip.user_module
160 160 try:
161 161 res = dumps(ip.user_ns["w"])
162 162 finally:
163 163 sys.modules['__main__'] = _main
164 164 self.assertTrue(isinstance(res, bytes))
165 165
166 166 def test_global_ns(self):
167 167 "Code in functions must be able to access variables outside them."
168 168 ip = get_ipython()
169 169 ip.run_cell("a = 10")
170 170 ip.run_cell(("def f(x):\n"
171 171 " return x + a"))
172 172 ip.run_cell("b = f(12)")
173 173 self.assertEqual(ip.user_ns["b"], 22)
174 174
175 175 def test_bad_custom_tb(self):
176 176 """Check that InteractiveShell is protected from bad custom exception handlers"""
177 177 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
178 178 self.assertEqual(ip.custom_exceptions, (IOError,))
179 179 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
180 180 ip.run_cell(u'raise IOError("foo")')
181 181 self.assertEqual(ip.custom_exceptions, ())
182 182
183 183 def test_bad_custom_tb_return(self):
184 184 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
185 185 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
186 186 self.assertEqual(ip.custom_exceptions, (NameError,))
187 187 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
188 188 ip.run_cell(u'a=abracadabra')
189 189 self.assertEqual(ip.custom_exceptions, ())
190 190
191 191 def test_drop_by_id(self):
192 192 myvars = {"a":object(), "b":object(), "c": object()}
193 193 ip.push(myvars, interactive=False)
194 194 for name in myvars:
195 195 assert name in ip.user_ns, name
196 196 assert name in ip.user_ns_hidden, name
197 197 ip.user_ns['b'] = 12
198 198 ip.drop_by_id(myvars)
199 199 for name in ["a", "c"]:
200 200 assert name not in ip.user_ns, name
201 201 assert name not in ip.user_ns_hidden, name
202 202 assert ip.user_ns['b'] == 12
203 203 ip.reset()
204 204
205 205 def test_var_expand(self):
206 206 ip.user_ns['f'] = u'Ca\xf1o'
207 207 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
208 208 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
209 209 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
210 210 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
211 211
212 212 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
213 213
214 214 ip.user_ns['f'] = b'Ca\xc3\xb1o'
215 215 # This should not raise any exception:
216 216 ip.var_expand(u'echo $f')
217 217
218 218 def test_var_expand_local(self):
219 219 """Test local variable expansion in !system and %magic calls"""
220 220 # !system
221 221 ip.run_cell(
222 222 "def test():\n"
223 223 ' lvar = "ttt"\n'
224 224 " ret = !echo {lvar}\n"
225 225 " return ret[0]\n"
226 226 )
227 227 res = ip.user_ns["test"]()
228 228 self.assertIn("ttt", res)
229 229
230 230 # %magic
231 231 ip.run_cell(
232 232 "def makemacro():\n"
233 233 ' macroname = "macro_var_expand_locals"\n'
234 234 " %macro {macroname} codestr\n"
235 235 )
236 236 ip.user_ns["codestr"] = "str(12)"
237 237 ip.run_cell("makemacro()")
238 238 self.assertIn("macro_var_expand_locals", ip.user_ns)
239 239
240 240 def test_var_expand_self(self):
241 241 """Test variable expansion with the name 'self', which was failing.
242 242
243 243 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
244 244 """
245 245 ip.run_cell(
246 246 "class cTest:\n"
247 247 ' classvar="see me"\n'
248 248 " def test(self):\n"
249 249 " res = !echo Variable: {self.classvar}\n"
250 250 " return res[0]\n"
251 251 )
252 252 self.assertIn("see me", ip.user_ns["cTest"]().test())
253 253
254 254 def test_bad_var_expand(self):
255 255 """var_expand on invalid formats shouldn't raise"""
256 256 # SyntaxError
257 257 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
258 258 # NameError
259 259 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
260 260 # ZeroDivisionError
261 261 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
262 262
263 263 def test_silent_postexec(self):
264 264 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
265 265 pre_explicit = mock.Mock()
266 266 pre_always = mock.Mock()
267 267 post_explicit = mock.Mock()
268 268 post_always = mock.Mock()
269 269 all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
270 270
271 271 ip.events.register('pre_run_cell', pre_explicit)
272 272 ip.events.register('pre_execute', pre_always)
273 273 ip.events.register('post_run_cell', post_explicit)
274 274 ip.events.register('post_execute', post_always)
275 275
276 276 try:
277 277 ip.run_cell("1", silent=True)
278 278 assert pre_always.called
279 279 assert not pre_explicit.called
280 280 assert post_always.called
281 281 assert not post_explicit.called
282 282 # double-check that non-silent exec did what we expected
283 283 # silent to avoid
284 284 ip.run_cell("1")
285 285 assert pre_explicit.called
286 286 assert post_explicit.called
287 287 info, = pre_explicit.call_args[0]
288 288 result, = post_explicit.call_args[0]
289 289 self.assertEqual(info, result.info)
290 290 # check that post hooks are always called
291 291 [m.reset_mock() for m in all_mocks]
292 292 ip.run_cell("syntax error")
293 293 assert pre_always.called
294 294 assert pre_explicit.called
295 295 assert post_always.called
296 296 assert post_explicit.called
297 297 info, = pre_explicit.call_args[0]
298 298 result, = post_explicit.call_args[0]
299 299 self.assertEqual(info, result.info)
300 300 finally:
301 301 # remove post-exec
302 302 ip.events.unregister('pre_run_cell', pre_explicit)
303 303 ip.events.unregister('pre_execute', pre_always)
304 304 ip.events.unregister('post_run_cell', post_explicit)
305 305 ip.events.unregister('post_execute', post_always)
306 306
307 307 def test_silent_noadvance(self):
308 308 """run_cell(silent=True) doesn't advance execution_count"""
309 309 ec = ip.execution_count
310 310 # silent should force store_history=False
311 311 ip.run_cell("1", store_history=True, silent=True)
312 312
313 313 self.assertEqual(ec, ip.execution_count)
314 314 # double-check that non-silent exec did what we expected
315 315 # silent to avoid
316 316 ip.run_cell("1", store_history=True)
317 317 self.assertEqual(ec+1, ip.execution_count)
318 318
319 319 def test_silent_nodisplayhook(self):
320 320 """run_cell(silent=True) doesn't trigger displayhook"""
321 321 d = dict(called=False)
322 322
323 323 trap = ip.display_trap
324 324 save_hook = trap.hook
325 325
326 326 def failing_hook(*args, **kwargs):
327 327 d['called'] = True
328 328
329 329 try:
330 330 trap.hook = failing_hook
331 331 res = ip.run_cell("1", silent=True)
332 332 self.assertFalse(d['called'])
333 333 self.assertIsNone(res.result)
334 334 # double-check that non-silent exec did what we expected
335 335 # silent to avoid
336 336 ip.run_cell("1")
337 337 self.assertTrue(d['called'])
338 338 finally:
339 339 trap.hook = save_hook
340 340
341 341 def test_ofind_line_magic(self):
342 342 from IPython.core.magic import register_line_magic
343 343
344 344 @register_line_magic
345 345 def lmagic(line):
346 346 "A line magic"
347 347
348 348 # Get info on line magic
349 349 lfind = ip._ofind("lmagic")
350 350 info = dict(
351 351 found=True,
352 352 isalias=False,
353 353 ismagic=True,
354 354 namespace="IPython internal",
355 355 obj=lmagic,
356 356 parent=None,
357 357 )
358 358 self.assertEqual(lfind, info)
359 359
360 360 def test_ofind_cell_magic(self):
361 361 from IPython.core.magic import register_cell_magic
362 362
363 363 @register_cell_magic
364 364 def cmagic(line, cell):
365 365 "A cell magic"
366 366
367 367 # Get info on cell magic
368 368 find = ip._ofind("cmagic")
369 369 info = dict(
370 370 found=True,
371 371 isalias=False,
372 372 ismagic=True,
373 373 namespace="IPython internal",
374 374 obj=cmagic,
375 375 parent=None,
376 376 )
377 377 self.assertEqual(find, info)
378 378
379 379 def test_ofind_property_with_error(self):
380 380 class A(object):
381 381 @property
382 382 def foo(self):
383 383 raise NotImplementedError()
384 384 a = A()
385 385
386 386 found = ip._ofind('a.foo', [('locals', locals())])
387 387 info = dict(found=True, isalias=False, ismagic=False,
388 388 namespace='locals', obj=A.foo, parent=a)
389 389 self.assertEqual(found, info)
390 390
391 391 def test_ofind_multiple_attribute_lookups(self):
392 392 class A(object):
393 393 @property
394 394 def foo(self):
395 395 raise NotImplementedError()
396 396
397 397 a = A()
398 398 a.a = A()
399 399 a.a.a = A()
400 400
401 401 found = ip._ofind('a.a.a.foo', [('locals', locals())])
402 402 info = dict(found=True, isalias=False, ismagic=False,
403 403 namespace='locals', obj=A.foo, parent=a.a.a)
404 404 self.assertEqual(found, info)
405 405
406 406 def test_ofind_slotted_attributes(self):
407 407 class A(object):
408 408 __slots__ = ['foo']
409 409 def __init__(self):
410 410 self.foo = 'bar'
411 411
412 412 a = A()
413 413 found = ip._ofind('a.foo', [('locals', locals())])
414 414 info = dict(found=True, isalias=False, ismagic=False,
415 415 namespace='locals', obj=a.foo, parent=a)
416 416 self.assertEqual(found, info)
417 417
418 418 found = ip._ofind('a.bar', [('locals', locals())])
419 419 info = dict(found=False, isalias=False, ismagic=False,
420 420 namespace=None, obj=None, parent=a)
421 421 self.assertEqual(found, info)
422 422
423 423 def test_ofind_prefers_property_to_instance_level_attribute(self):
424 424 class A(object):
425 425 @property
426 426 def foo(self):
427 427 return 'bar'
428 428 a = A()
429 429 a.__dict__["foo"] = "baz"
430 430 self.assertEqual(a.foo, "bar")
431 431 found = ip._ofind("a.foo", [("locals", locals())])
432 432 self.assertIs(found["obj"], A.foo)
433 433
434 434 def test_custom_syntaxerror_exception(self):
435 435 called = []
436 436 def my_handler(shell, etype, value, tb, tb_offset=None):
437 437 called.append(etype)
438 438 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
439 439
440 440 ip.set_custom_exc((SyntaxError,), my_handler)
441 441 try:
442 442 ip.run_cell("1f")
443 443 # Check that this was called, and only once.
444 444 self.assertEqual(called, [SyntaxError])
445 445 finally:
446 446 # Reset the custom exception hook
447 447 ip.set_custom_exc((), None)
448 448
449 449 def test_custom_exception(self):
450 450 called = []
451 451 def my_handler(shell, etype, value, tb, tb_offset=None):
452 452 called.append(etype)
453 453 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
454 454
455 455 ip.set_custom_exc((ValueError,), my_handler)
456 456 try:
457 457 res = ip.run_cell("raise ValueError('test')")
458 458 # Check that this was called, and only once.
459 459 self.assertEqual(called, [ValueError])
460 460 # Check that the error is on the result object
461 461 self.assertIsInstance(res.error_in_exec, ValueError)
462 462 finally:
463 463 # Reset the custom exception hook
464 464 ip.set_custom_exc((), None)
465 465
466 466 @mock.patch("builtins.print")
467 467 def test_showtraceback_with_surrogates(self, mocked_print):
468 468 values = []
469 469
470 470 def mock_print_func(value, sep=" ", end="\n", file=sys.stdout, flush=False):
471 471 values.append(value)
472 472 if value == chr(0xD8FF):
473 473 raise UnicodeEncodeError("utf-8", chr(0xD8FF), 0, 1, "")
474 474
475 475 # mock builtins.print
476 476 mocked_print.side_effect = mock_print_func
477 477
478 478 # ip._showtraceback() is replaced in globalipapp.py.
479 479 # Call original method to test.
480 480 interactiveshell.InteractiveShell._showtraceback(ip, None, None, chr(0xD8FF))
481 481
482 482 self.assertEqual(mocked_print.call_count, 2)
483 483 self.assertEqual(values, [chr(0xD8FF), "\\ud8ff"])
484 484
485 485 def test_mktempfile(self):
486 486 filename = ip.mktempfile()
487 487 # Check that we can open the file again on Windows
488 with open(filename, 'w', encoding='utf-8') as f:
489 f.write('abc')
488 with open(filename, "w", encoding="utf-8") as f:
489 f.write("abc")
490 490
491 filename = ip.mktempfile(data='blah')
492 with open(filename, 'r', encoding='utf-8') as f:
493 self.assertEqual(f.read(), 'blah')
491 filename = ip.mktempfile(data="blah")
492 with open(filename, "r", encoding="utf-8") as f:
493 self.assertEqual(f.read(), "blah")
494 494
495 495 def test_new_main_mod(self):
496 496 # Smoketest to check that this accepts a unicode module name
497 497 name = u'jiefmw'
498 498 mod = ip.new_main_mod(u'%s.py' % name, name)
499 499 self.assertEqual(mod.__name__, name)
500 500
501 501 def test_get_exception_only(self):
502 502 try:
503 503 raise KeyboardInterrupt
504 504 except KeyboardInterrupt:
505 505 msg = ip.get_exception_only()
506 506 self.assertEqual(msg, 'KeyboardInterrupt\n')
507 507
508 508 try:
509 509 raise DerivedInterrupt("foo")
510 510 except KeyboardInterrupt:
511 511 msg = ip.get_exception_only()
512 512 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
513 513
514 514 def test_inspect_text(self):
515 515 ip.run_cell('a = 5')
516 516 text = ip.object_inspect_text('a')
517 517 self.assertIsInstance(text, str)
518 518
519 519 def test_last_execution_result(self):
520 520 """ Check that last execution result gets set correctly (GH-10702) """
521 521 result = ip.run_cell('a = 5; a')
522 522 self.assertTrue(ip.last_execution_succeeded)
523 523 self.assertEqual(ip.last_execution_result.result, 5)
524 524
525 525 result = ip.run_cell('a = x_invalid_id_x')
526 526 self.assertFalse(ip.last_execution_succeeded)
527 527 self.assertFalse(ip.last_execution_result.success)
528 528 self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
529 529
530 530 def test_reset_aliasing(self):
531 531 """ Check that standard posix aliases work after %reset. """
532 532 if os.name != 'posix':
533 533 return
534 534
535 535 ip.reset()
536 536 for cmd in ('clear', 'more', 'less', 'man'):
537 537 res = ip.run_cell('%' + cmd)
538 538 self.assertEqual(res.success, True)
539 539
540 540
541 541 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
542 542
543 543 @onlyif_unicode_paths
544 544 def setUp(self):
545 545 self.BASETESTDIR = tempfile.mkdtemp()
546 546 self.TESTDIR = join(self.BASETESTDIR, u"åäö")
547 547 os.mkdir(self.TESTDIR)
548 with open(join(self.TESTDIR, u"åäötestscript.py"), "w", encoding='utf-8') as sfile:
548 with open(
549 join(self.TESTDIR, u"åäötestscript.py"), "w", encoding="utf-8"
550 ) as sfile:
549 551 sfile.write("pass\n")
550 552 self.oldpath = os.getcwd()
551 553 os.chdir(self.TESTDIR)
552 554 self.fname = u"åäötestscript.py"
553 555
554 556 def tearDown(self):
555 557 os.chdir(self.oldpath)
556 558 shutil.rmtree(self.BASETESTDIR)
557 559
558 560 @onlyif_unicode_paths
559 561 def test_1(self):
560 562 """Test safe_execfile with non-ascii path
561 563 """
562 564 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
563 565
564 566 class ExitCodeChecks(tt.TempFileMixin):
565 567
566 568 def setUp(self):
567 569 self.system = ip.system_raw
568 570
569 571 def test_exit_code_ok(self):
570 572 self.system('exit 0')
571 573 self.assertEqual(ip.user_ns['_exit_code'], 0)
572 574
573 575 def test_exit_code_error(self):
574 576 self.system('exit 1')
575 577 self.assertEqual(ip.user_ns['_exit_code'], 1)
576 578
577 579 @skipif(not hasattr(signal, 'SIGALRM'))
578 580 def test_exit_code_signal(self):
579 581 self.mktmp("import signal, time\n"
580 582 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
581 583 "time.sleep(1)\n")
582 584 self.system("%s %s" % (sys.executable, self.fname))
583 585 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
584 586
585 587 @onlyif_cmds_exist("csh")
586 588 def test_exit_code_signal_csh(self):
587 589 SHELL = os.environ.get('SHELL', None)
588 590 os.environ['SHELL'] = find_cmd("csh")
589 591 try:
590 592 self.test_exit_code_signal()
591 593 finally:
592 594 if SHELL is not None:
593 595 os.environ['SHELL'] = SHELL
594 596 else:
595 597 del os.environ['SHELL']
596 598
597 599
598 600 class TestSystemRaw(ExitCodeChecks):
599 601
600 602 def setUp(self):
601 603 super().setUp()
602 604 self.system = ip.system_raw
603 605
604 606 @onlyif_unicode_paths
605 607 def test_1(self):
606 608 """Test system_raw with non-ascii cmd
607 609 """
608 610 cmd = u'''python -c "'åäö'" '''
609 611 ip.system_raw(cmd)
610 612
611 613 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
612 614 @mock.patch('os.system', side_effect=KeyboardInterrupt)
613 615 def test_control_c(self, *mocks):
614 616 try:
615 617 self.system("sleep 1 # wont happen")
616 618 except KeyboardInterrupt:
617 619 self.fail(
618 620 "system call should intercept "
619 621 "keyboard interrupt from subprocess.call"
620 622 )
621 623 self.assertEqual(ip.user_ns["_exit_code"], -signal.SIGINT)
622 624
623 625 def test_magic_warnings(self):
624 626 for magic_cmd in ("ls", "pip", "conda", "cd"):
625 627 with self.assertWarnsRegex(Warning, "You executed the system command"):
626 628 ip.system_raw(magic_cmd)
627 629
628 630 # TODO: Exit codes are currently ignored on Windows.
629 631 class TestSystemPipedExitCode(ExitCodeChecks):
630 632
631 633 def setUp(self):
632 634 super().setUp()
633 635 self.system = ip.system_piped
634 636
635 637 @skip_win32
636 638 def test_exit_code_ok(self):
637 639 ExitCodeChecks.test_exit_code_ok(self)
638 640
639 641 @skip_win32
640 642 def test_exit_code_error(self):
641 643 ExitCodeChecks.test_exit_code_error(self)
642 644
643 645 @skip_win32
644 646 def test_exit_code_signal(self):
645 647 ExitCodeChecks.test_exit_code_signal(self)
646 648
647 649 class TestModules(tt.TempFileMixin):
648 650 def test_extraneous_loads(self):
649 651 """Test we're not loading modules on startup that we shouldn't.
650 652 """
651 653 self.mktmp("import sys\n"
652 654 "print('numpy' in sys.modules)\n"
653 655 "print('ipyparallel' in sys.modules)\n"
654 656 "print('ipykernel' in sys.modules)\n"
655 657 )
656 658 out = "False\nFalse\nFalse\n"
657 659 tt.ipexec_validate(self.fname, out)
658 660
659 661 class Negator(ast.NodeTransformer):
660 662 """Negates all number literals in an AST."""
661 663
662 664 # for python 3.7 and earlier
663 665 def visit_Num(self, node):
664 666 node.n = -node.n
665 667 return node
666 668
667 669 # for python 3.8+
668 670 def visit_Constant(self, node):
669 671 if isinstance(node.value, int):
670 672 return self.visit_Num(node)
671 673 return node
672 674
673 675 class TestAstTransform(unittest.TestCase):
674 676 def setUp(self):
675 677 self.negator = Negator()
676 678 ip.ast_transformers.append(self.negator)
677 679
678 680 def tearDown(self):
679 681 ip.ast_transformers.remove(self.negator)
680 682
681 683 def test_run_cell(self):
682 684 with tt.AssertPrints('-34'):
683 685 ip.run_cell('print (12 + 22)')
684 686
685 687 # A named reference to a number shouldn't be transformed.
686 688 ip.user_ns['n'] = 55
687 689 with tt.AssertNotPrints('-55'):
688 690 ip.run_cell('print (n)')
689 691
690 692 def test_timeit(self):
691 693 called = set()
692 694 def f(x):
693 695 called.add(x)
694 696 ip.push({'f':f})
695 697
696 698 with tt.AssertPrints("std. dev. of"):
697 699 ip.run_line_magic("timeit", "-n1 f(1)")
698 700 self.assertEqual(called, {-1})
699 701 called.clear()
700 702
701 703 with tt.AssertPrints("std. dev. of"):
702 704 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
703 705 self.assertEqual(called, {-2, -3})
704 706
705 707 def test_time(self):
706 708 called = []
707 709 def f(x):
708 710 called.append(x)
709 711 ip.push({'f':f})
710 712
711 713 # Test with an expression
712 714 with tt.AssertPrints("Wall time: "):
713 715 ip.run_line_magic("time", "f(5+9)")
714 716 self.assertEqual(called, [-14])
715 717 called[:] = []
716 718
717 719 # Test with a statement (different code path)
718 720 with tt.AssertPrints("Wall time: "):
719 721 ip.run_line_magic("time", "a = f(-3 + -2)")
720 722 self.assertEqual(called, [5])
721 723
722 724 def test_macro(self):
723 725 ip.push({'a':10})
724 726 # The AST transformation makes this do a+=-1
725 727 ip.define_macro("amacro", "a+=1\nprint(a)")
726 728
727 729 with tt.AssertPrints("9"):
728 730 ip.run_cell("amacro")
729 731 with tt.AssertPrints("8"):
730 732 ip.run_cell("amacro")
731 733
732 734 class TestMiscTransform(unittest.TestCase):
733 735
734 736
735 737 def test_transform_only_once(self):
736 738 cleanup = 0
737 739 line_t = 0
738 740 def count_cleanup(lines):
739 741 nonlocal cleanup
740 742 cleanup += 1
741 743 return lines
742 744
743 745 def count_line_t(lines):
744 746 nonlocal line_t
745 747 line_t += 1
746 748 return lines
747 749
748 750 ip.input_transformer_manager.cleanup_transforms.append(count_cleanup)
749 751 ip.input_transformer_manager.line_transforms.append(count_line_t)
750 752
751 753 ip.run_cell('1')
752 754
753 755 assert cleanup == 1
754 756 assert line_t == 1
755 757
756 758 class IntegerWrapper(ast.NodeTransformer):
757 759 """Wraps all integers in a call to Integer()"""
758 760
759 761 # for Python 3.7 and earlier
760 762
761 763 # for Python 3.7 and earlier
762 764 def visit_Num(self, node):
763 765 if isinstance(node.n, int):
764 766 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
765 767 args=[node], keywords=[])
766 768 return node
767 769
768 770 # For Python 3.8+
769 771 def visit_Constant(self, node):
770 772 if isinstance(node.value, int):
771 773 return self.visit_Num(node)
772 774 return node
773 775
774 776
775 777 class TestAstTransform2(unittest.TestCase):
776 778 def setUp(self):
777 779 self.intwrapper = IntegerWrapper()
778 780 ip.ast_transformers.append(self.intwrapper)
779 781
780 782 self.calls = []
781 783 def Integer(*args):
782 784 self.calls.append(args)
783 785 return args
784 786 ip.push({"Integer": Integer})
785 787
786 788 def tearDown(self):
787 789 ip.ast_transformers.remove(self.intwrapper)
788 790 del ip.user_ns['Integer']
789 791
790 792 def test_run_cell(self):
791 793 ip.run_cell("n = 2")
792 794 self.assertEqual(self.calls, [(2,)])
793 795
794 796 # This shouldn't throw an error
795 797 ip.run_cell("o = 2.0")
796 798 self.assertEqual(ip.user_ns['o'], 2.0)
797 799
798 800 def test_timeit(self):
799 801 called = set()
800 802 def f(x):
801 803 called.add(x)
802 804 ip.push({'f':f})
803 805
804 806 with tt.AssertPrints("std. dev. of"):
805 807 ip.run_line_magic("timeit", "-n1 f(1)")
806 808 self.assertEqual(called, {(1,)})
807 809 called.clear()
808 810
809 811 with tt.AssertPrints("std. dev. of"):
810 812 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
811 813 self.assertEqual(called, {(2,), (3,)})
812 814
813 815 class ErrorTransformer(ast.NodeTransformer):
814 816 """Throws an error when it sees a number."""
815 817
816 818 # for Python 3.7 and earlier
817 819 def visit_Num(self, node):
818 820 raise ValueError("test")
819 821
820 822 # for Python 3.8+
821 823 def visit_Constant(self, node):
822 824 if isinstance(node.value, int):
823 825 return self.visit_Num(node)
824 826 return node
825 827
826 828
827 829 class TestAstTransformError(unittest.TestCase):
828 830 def test_unregistering(self):
829 831 err_transformer = ErrorTransformer()
830 832 ip.ast_transformers.append(err_transformer)
831 833
832 834 with self.assertWarnsRegex(UserWarning, "It will be unregistered"):
833 835 ip.run_cell("1 + 2")
834 836
835 837 # This should have been removed.
836 838 self.assertNotIn(err_transformer, ip.ast_transformers)
837 839
838 840
839 841 class StringRejector(ast.NodeTransformer):
840 842 """Throws an InputRejected when it sees a string literal.
841 843
842 844 Used to verify that NodeTransformers can signal that a piece of code should
843 845 not be executed by throwing an InputRejected.
844 846 """
845 847
846 848 #for python 3.7 and earlier
847 849 def visit_Str(self, node):
848 850 raise InputRejected("test")
849 851
850 852 # 3.8 only
851 853 def visit_Constant(self, node):
852 854 if isinstance(node.value, str):
853 855 raise InputRejected("test")
854 856 return node
855 857
856 858
857 859 class TestAstTransformInputRejection(unittest.TestCase):
858 860
859 861 def setUp(self):
860 862 self.transformer = StringRejector()
861 863 ip.ast_transformers.append(self.transformer)
862 864
863 865 def tearDown(self):
864 866 ip.ast_transformers.remove(self.transformer)
865 867
866 868 def test_input_rejection(self):
867 869 """Check that NodeTransformers can reject input."""
868 870
869 871 expect_exception_tb = tt.AssertPrints("InputRejected: test")
870 872 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
871 873
872 874 # Run the same check twice to verify that the transformer is not
873 875 # disabled after raising.
874 876 with expect_exception_tb, expect_no_cell_output:
875 877 ip.run_cell("'unsafe'")
876 878
877 879 with expect_exception_tb, expect_no_cell_output:
878 880 res = ip.run_cell("'unsafe'")
879 881
880 882 self.assertIsInstance(res.error_before_exec, InputRejected)
881 883
882 884 def test__IPYTHON__():
883 885 # This shouldn't raise a NameError, that's all
884 886 __IPYTHON__
885 887
886 888
887 889 class DummyRepr(object):
888 890 def __repr__(self):
889 891 return "DummyRepr"
890 892
891 893 def _repr_html_(self):
892 894 return "<b>dummy</b>"
893 895
894 896 def _repr_javascript_(self):
895 897 return "console.log('hi');", {'key': 'value'}
896 898
897 899
898 900 def test_user_variables():
899 901 # enable all formatters
900 902 ip.display_formatter.active_types = ip.display_formatter.format_types
901 903
902 904 ip.user_ns['dummy'] = d = DummyRepr()
903 905 keys = {'dummy', 'doesnotexist'}
904 906 r = ip.user_expressions({ key:key for key in keys})
905 907
906 908 assert keys == set(r.keys())
907 909 dummy = r["dummy"]
908 910 assert {"status", "data", "metadata"} == set(dummy.keys())
909 911 assert dummy["status"] == "ok"
910 912 data = dummy["data"]
911 913 metadata = dummy["metadata"]
912 914 assert data.get("text/html") == d._repr_html_()
913 915 js, jsmd = d._repr_javascript_()
914 916 assert data.get("application/javascript") == js
915 917 assert metadata.get("application/javascript") == jsmd
916 918
917 919 dne = r["doesnotexist"]
918 920 assert dne["status"] == "error"
919 921 assert dne["ename"] == "NameError"
920 922
921 923 # back to text only
922 924 ip.display_formatter.active_types = ['text/plain']
923 925
924 926 def test_user_expression():
925 927 # enable all formatters
926 928 ip.display_formatter.active_types = ip.display_formatter.format_types
927 929 query = {
928 930 'a' : '1 + 2',
929 931 'b' : '1/0',
930 932 }
931 933 r = ip.user_expressions(query)
932 934 import pprint
933 935 pprint.pprint(r)
934 936 assert set(r.keys()) == set(query.keys())
935 937 a = r["a"]
936 938 assert {"status", "data", "metadata"} == set(a.keys())
937 939 assert a["status"] == "ok"
938 940 data = a["data"]
939 941 metadata = a["metadata"]
940 942 assert data.get("text/plain") == "3"
941 943
942 944 b = r["b"]
943 945 assert b["status"] == "error"
944 946 assert b["ename"] == "ZeroDivisionError"
945 947
946 948 # back to text only
947 949 ip.display_formatter.active_types = ['text/plain']
948 950
949 951
950 952 class TestSyntaxErrorTransformer(unittest.TestCase):
951 953 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
952 954
953 955 @staticmethod
954 956 def transformer(lines):
955 957 for line in lines:
956 958 pos = line.find('syntaxerror')
957 959 if pos >= 0:
958 960 e = SyntaxError('input contains "syntaxerror"')
959 961 e.text = line
960 962 e.offset = pos + 1
961 963 raise e
962 964 return lines
963 965
964 966 def setUp(self):
965 967 ip.input_transformers_post.append(self.transformer)
966 968
967 969 def tearDown(self):
968 970 ip.input_transformers_post.remove(self.transformer)
969 971
970 972 def test_syntaxerror_input_transformer(self):
971 973 with tt.AssertPrints('1234'):
972 974 ip.run_cell('1234')
973 975 with tt.AssertPrints('SyntaxError: invalid syntax'):
974 976 ip.run_cell('1 2 3') # plain python syntax error
975 977 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
976 978 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
977 979 with tt.AssertPrints('3456'):
978 980 ip.run_cell('3456')
979 981
980 982
981 983 class TestWarningSuppression(unittest.TestCase):
982 984 def test_warning_suppression(self):
983 985 ip.run_cell("import warnings")
984 986 try:
985 987 with self.assertWarnsRegex(UserWarning, "asdf"):
986 988 ip.run_cell("warnings.warn('asdf')")
987 989 # Here's the real test -- if we run that again, we should get the
988 990 # warning again. Traditionally, each warning was only issued once per
989 991 # IPython session (approximately), even if the user typed in new and
990 992 # different code that should have also triggered the warning, leading
991 993 # to much confusion.
992 994 with self.assertWarnsRegex(UserWarning, "asdf"):
993 995 ip.run_cell("warnings.warn('asdf')")
994 996 finally:
995 997 ip.run_cell("del warnings")
996 998
997 999
998 1000 def test_deprecation_warning(self):
999 1001 ip.run_cell("""
1000 1002 import warnings
1001 1003 def wrn():
1002 1004 warnings.warn(
1003 1005 "I AM A WARNING",
1004 1006 DeprecationWarning
1005 1007 )
1006 1008 """)
1007 1009 try:
1008 1010 with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"):
1009 1011 ip.run_cell("wrn()")
1010 1012 finally:
1011 1013 ip.run_cell("del warnings")
1012 1014 ip.run_cell("del wrn")
1013 1015
1014 1016
1015 1017 class TestImportNoDeprecate(tt.TempFileMixin):
1016 1018
1017 1019 def setUp(self):
1018 1020 """Make a valid python temp file."""
1019 1021 self.mktmp("""
1020 1022 import warnings
1021 1023 def wrn():
1022 1024 warnings.warn(
1023 1025 "I AM A WARNING",
1024 1026 DeprecationWarning
1025 1027 )
1026 1028 """)
1027 1029 super().setUp()
1028 1030
1029 1031 def test_no_dep(self):
1030 1032 """
1031 1033 No deprecation warning should be raised from imported functions
1032 1034 """
1033 1035 ip.run_cell("from {} import wrn".format(self.fname))
1034 1036
1035 1037 with tt.AssertNotPrints("I AM A WARNING"):
1036 1038 ip.run_cell("wrn()")
1037 1039 ip.run_cell("del wrn")
1038 1040
1039 1041
1040 1042 def test_custom_exc_count():
1041 1043 hook = mock.Mock(return_value=None)
1042 1044 ip.set_custom_exc((SyntaxError,), hook)
1043 1045 before = ip.execution_count
1044 1046 ip.run_cell("def foo()", store_history=True)
1045 1047 # restore default excepthook
1046 1048 ip.set_custom_exc((), None)
1047 1049 assert hook.call_count == 1
1048 1050 assert ip.execution_count == before + 1
1049 1051
1050 1052
1051 1053 def test_run_cell_async():
1052 1054 ip.run_cell("import asyncio")
1053 1055 coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5")
1054 1056 assert asyncio.iscoroutine(coro)
1055 1057 loop = asyncio.new_event_loop()
1056 1058 result = loop.run_until_complete(coro)
1057 1059 assert isinstance(result, interactiveshell.ExecutionResult)
1058 1060 assert result.result == 5
1059 1061
1060 1062
1061 1063 def test_run_cell_await():
1062 1064 ip.run_cell("import asyncio")
1063 1065 result = ip.run_cell("await asyncio.sleep(0.01); 10")
1064 1066 assert ip.user_ns["_"] == 10
1065 1067
1066 1068
1067 1069 def test_run_cell_asyncio_run():
1068 1070 ip.run_cell("import asyncio")
1069 1071 result = ip.run_cell("await asyncio.sleep(0.01); 1")
1070 1072 assert ip.user_ns["_"] == 1
1071 1073 result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2")
1072 1074 assert ip.user_ns["_"] == 2
1073 1075 result = ip.run_cell("await asyncio.sleep(0.01); 3")
1074 1076 assert ip.user_ns["_"] == 3
1075 1077
1076 1078
1077 1079 def test_should_run_async():
1078 1080 assert not ip.should_run_async("a = 5")
1079 1081 assert ip.should_run_async("await x")
1080 1082 assert ip.should_run_async("import asyncio; await asyncio.sleep(1)")
1081 1083
1082 1084
1083 1085 def test_set_custom_completer():
1084 1086 num_completers = len(ip.Completer.matchers)
1085 1087
1086 1088 def foo(*args, **kwargs):
1087 1089 return "I'm a completer!"
1088 1090
1089 1091 ip.set_custom_completer(foo, 0)
1090 1092
1091 1093 # check that we've really added a new completer
1092 1094 assert len(ip.Completer.matchers) == num_completers + 1
1093 1095
1094 1096 # check that the first completer is the function we defined
1095 1097 assert ip.Completer.matchers[0]() == "I'm a completer!"
1096 1098
1097 1099 # clean up
1098 1100 ip.Completer.custom_matchers.pop()
@@ -1,1366 +1,1408 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tests for various magic functions."""
3 3
4 4 import asyncio
5 5 import gc
6 6 import io
7 7 import os
8 8 import re
9 9 import shlex
10 10 import sys
11 11 import warnings
12 12 from importlib import invalidate_caches
13 13 from io import StringIO
14 14 from pathlib import Path
15 15 from textwrap import dedent
16 16 from unittest import TestCase, mock
17 17
18 18 import pytest
19 19
20 20 from IPython import get_ipython
21 21 from IPython.core import magic
22 22 from IPython.core.error import UsageError
23 23 from IPython.core.magic import (
24 24 Magics,
25 25 cell_magic,
26 26 line_magic,
27 27 magics_class,
28 28 register_cell_magic,
29 29 register_line_magic,
30 30 )
31 31 from IPython.core.magics import code, execution, logging, osm, script
32 32 from IPython.testing import decorators as dec
33 33 from IPython.testing import tools as tt
34 34 from IPython.utils.io import capture_output
35 35 from IPython.utils.process import find_cmd
36 36 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
37 37
38 38 from .test_debugger import PdbTestInput
39 39
40 40
41 41 @magic.magics_class
42 42 class DummyMagics(magic.Magics): pass
43 43
44 44 def test_extract_code_ranges():
45 45 instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :"
46 46 expected = [
47 47 (0, 1),
48 48 (2, 3),
49 49 (4, 6),
50 50 (6, 9),
51 51 (9, 14),
52 52 (16, None),
53 53 (None, 9),
54 54 (9, None),
55 55 (None, 13),
56 56 (None, None),
57 57 ]
58 58 actual = list(code.extract_code_ranges(instr))
59 59 assert actual == expected
60 60
61 61 def test_extract_symbols():
62 62 source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n"""
63 63 symbols_args = ["a", "b", "A", "A,b", "A,a", "z"]
64 64 expected = [([], ['a']),
65 65 (["def b():\n return 42\n"], []),
66 66 (["class A: pass\n"], []),
67 67 (["class A: pass\n", "def b():\n return 42\n"], []),
68 68 (["class A: pass\n"], ['a']),
69 69 ([], ['z'])]
70 70 for symbols, exp in zip(symbols_args, expected):
71 71 assert code.extract_symbols(source, symbols) == exp
72 72
73 73
74 74 def test_extract_symbols_raises_exception_with_non_python_code():
75 75 source = ("=begin A Ruby program :)=end\n"
76 76 "def hello\n"
77 77 "puts 'Hello world'\n"
78 78 "end")
79 79 with pytest.raises(SyntaxError):
80 80 code.extract_symbols(source, "hello")
81 81
82 82
83 83 def test_magic_not_found():
84 84 # magic not found raises UsageError
85 85 with pytest.raises(UsageError):
86 86 _ip.magic('doesntexist')
87 87
88 88 # ensure result isn't success when a magic isn't found
89 89 result = _ip.run_cell('%doesntexist')
90 90 assert isinstance(result.error_in_exec, UsageError)
91 91
92 92
93 93 def test_cell_magic_not_found():
94 94 # magic not found raises UsageError
95 95 with pytest.raises(UsageError):
96 96 _ip.run_cell_magic('doesntexist', 'line', 'cell')
97 97
98 98 # ensure result isn't success when a magic isn't found
99 99 result = _ip.run_cell('%%doesntexist')
100 100 assert isinstance(result.error_in_exec, UsageError)
101 101
102 102
103 103 def test_magic_error_status():
104 104 def fail(shell):
105 105 1/0
106 106 _ip.register_magic_function(fail)
107 107 result = _ip.run_cell('%fail')
108 108 assert isinstance(result.error_in_exec, ZeroDivisionError)
109 109
110 110
111 111 def test_config():
112 112 """ test that config magic does not raise
113 113 can happen if Configurable init is moved too early into
114 114 Magics.__init__ as then a Config object will be registered as a
115 115 magic.
116 116 """
117 117 ## should not raise.
118 118 _ip.magic('config')
119 119
120 120 def test_config_available_configs():
121 121 """ test that config magic prints available configs in unique and
122 122 sorted order. """
123 123 with capture_output() as captured:
124 124 _ip.magic('config')
125 125
126 126 stdout = captured.stdout
127 127 config_classes = stdout.strip().split('\n')[1:]
128 128 assert config_classes == sorted(set(config_classes))
129 129
130 130 def test_config_print_class():
131 131 """ test that config with a classname prints the class's options. """
132 132 with capture_output() as captured:
133 133 _ip.magic('config TerminalInteractiveShell')
134 134
135 135 stdout = captured.stdout
136 136 assert re.match(
137 137 "TerminalInteractiveShell.* options", stdout.splitlines()[0]
138 138 ), f"{stdout}\n\n1st line of stdout not like 'TerminalInteractiveShell.* options'"
139 139
140 140
141 141 def test_rehashx():
142 142 # clear up everything
143 143 _ip.alias_manager.clear_aliases()
144 144 del _ip.db['syscmdlist']
145 145
146 146 _ip.magic('rehashx')
147 147 # Practically ALL ipython development systems will have more than 10 aliases
148 148
149 149 assert len(_ip.alias_manager.aliases) > 10
150 150 for name, cmd in _ip.alias_manager.aliases:
151 151 # we must strip dots from alias names
152 152 assert "." not in name
153 153
154 154 # rehashx must fill up syscmdlist
155 155 scoms = _ip.db['syscmdlist']
156 156 assert len(scoms) > 10
157 157
158 158
159 159 def test_magic_parse_options():
160 160 """Test that we don't mangle paths when parsing magic options."""
161 161 ip = get_ipython()
162 162 path = 'c:\\x'
163 163 m = DummyMagics(ip)
164 164 opts = m.parse_options('-f %s' % path,'f:')[0]
165 165 # argv splitting is os-dependent
166 166 if os.name == 'posix':
167 167 expected = 'c:x'
168 168 else:
169 169 expected = path
170 170 assert opts["f"] == expected
171 171
172 172
173 173 def test_magic_parse_long_options():
174 174 """Magic.parse_options can handle --foo=bar long options"""
175 175 ip = get_ipython()
176 176 m = DummyMagics(ip)
177 177 opts, _ = m.parse_options("--foo --bar=bubble", "a", "foo", "bar=")
178 178 assert "foo" in opts
179 179 assert "bar" in opts
180 180 assert opts["bar"] == "bubble"
181 181
182 182
183 183 def doctest_hist_f():
184 184 """Test %hist -f with temporary filename.
185 185
186 186 In [9]: import tempfile
187 187
188 188 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
189 189
190 190 In [11]: %hist -nl -f $tfile 3
191 191
192 192 In [13]: import os; os.unlink(tfile)
193 193 """
194 194
195 195
196 196 def doctest_hist_op():
197 197 """Test %hist -op
198 198
199 199 In [1]: class b(float):
200 200 ...: pass
201 201 ...:
202 202
203 203 In [2]: class s(object):
204 204 ...: def __str__(self):
205 205 ...: return 's'
206 206 ...:
207 207
208 208 In [3]:
209 209
210 210 In [4]: class r(b):
211 211 ...: def __repr__(self):
212 212 ...: return 'r'
213 213 ...:
214 214
215 215 In [5]: class sr(s,r): pass
216 216 ...:
217 217
218 218 In [6]:
219 219
220 220 In [7]: bb=b()
221 221
222 222 In [8]: ss=s()
223 223
224 224 In [9]: rr=r()
225 225
226 226 In [10]: ssrr=sr()
227 227
228 228 In [11]: 4.5
229 229 Out[11]: 4.5
230 230
231 231 In [12]: str(ss)
232 232 Out[12]: 's'
233 233
234 234 In [13]:
235 235
236 236 In [14]: %hist -op
237 237 >>> class b:
238 238 ... pass
239 239 ...
240 240 >>> class s(b):
241 241 ... def __str__(self):
242 242 ... return 's'
243 243 ...
244 244 >>>
245 245 >>> class r(b):
246 246 ... def __repr__(self):
247 247 ... return 'r'
248 248 ...
249 249 >>> class sr(s,r): pass
250 250 >>>
251 251 >>> bb=b()
252 252 >>> ss=s()
253 253 >>> rr=r()
254 254 >>> ssrr=sr()
255 255 >>> 4.5
256 256 4.5
257 257 >>> str(ss)
258 258 's'
259 259 >>>
260 260 """
261 261
262 262 def test_hist_pof():
263 263 ip = get_ipython()
264 264 ip.run_cell("1+2", store_history=True)
265 265 #raise Exception(ip.history_manager.session_number)
266 266 #raise Exception(list(ip.history_manager._get_range_session()))
267 267 with TemporaryDirectory() as td:
268 268 tf = os.path.join(td, 'hist.py')
269 269 ip.run_line_magic('history', '-pof %s' % tf)
270 270 assert os.path.isfile(tf)
271 271
272 272
273 273 def test_macro():
274 274 ip = get_ipython()
275 275 ip.history_manager.reset() # Clear any existing history.
276 276 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
277 277 for i, cmd in enumerate(cmds, start=1):
278 278 ip.history_manager.store_inputs(i, cmd)
279 279 ip.magic("macro test 1-3")
280 280 assert ip.user_ns["test"].value == "\n".join(cmds) + "\n"
281 281
282 282 # List macros
283 283 assert "test" in ip.magic("macro")
284 284
285 285
286 286 def test_macro_run():
287 287 """Test that we can run a multi-line macro successfully."""
288 288 ip = get_ipython()
289 289 ip.history_manager.reset()
290 290 cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"]
291 291 for cmd in cmds:
292 292 ip.run_cell(cmd, store_history=True)
293 293 assert ip.user_ns["test"].value == "a+=1\nprint(a)\n"
294 294 with tt.AssertPrints("12"):
295 295 ip.run_cell("test")
296 296 with tt.AssertPrints("13"):
297 297 ip.run_cell("test")
298 298
299 299
300 300 def test_magic_magic():
301 301 """Test %magic"""
302 302 ip = get_ipython()
303 303 with capture_output() as captured:
304 304 ip.magic("magic")
305 305
306 306 stdout = captured.stdout
307 307 assert "%magic" in stdout
308 308 assert "IPython" in stdout
309 309 assert "Available" in stdout
310 310
311 311
312 312 @dec.skipif_not_numpy
313 313 def test_numpy_reset_array_undec():
314 314 "Test '%reset array' functionality"
315 315 _ip.ex("import numpy as np")
316 316 _ip.ex("a = np.empty(2)")
317 317 assert "a" in _ip.user_ns
318 318 _ip.magic("reset -f array")
319 319 assert "a" not in _ip.user_ns
320 320
321 321
322 322 def test_reset_out():
323 323 "Test '%reset out' magic"
324 324 _ip.run_cell("parrot = 'dead'", store_history=True)
325 325 # test '%reset -f out', make an Out prompt
326 326 _ip.run_cell("parrot", store_history=True)
327 327 assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")]
328 328 _ip.magic("reset -f out")
329 329 assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")]
330 330 assert len(_ip.user_ns["Out"]) == 0
331 331
332 332
333 333 def test_reset_in():
334 334 "Test '%reset in' magic"
335 335 # test '%reset -f in'
336 336 _ip.run_cell("parrot", store_history=True)
337 337 assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
338 338 _ip.magic("%reset -f in")
339 339 assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
340 340 assert len(set(_ip.user_ns["In"])) == 1
341 341
342 342
343 343 def test_reset_dhist():
344 344 "Test '%reset dhist' magic"
345 345 _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing
346 346 _ip.magic("cd " + os.path.dirname(pytest.__file__))
347 347 _ip.magic("cd -")
348 348 assert len(_ip.user_ns["_dh"]) > 0
349 349 _ip.magic("reset -f dhist")
350 350 assert len(_ip.user_ns["_dh"]) == 0
351 351 _ip.run_cell("_dh = [d for d in tmp]") # restore
352 352
353 353
354 354 def test_reset_in_length():
355 355 "Test that '%reset in' preserves In[] length"
356 356 _ip.run_cell("print 'foo'")
357 357 _ip.run_cell("reset -f in")
358 358 assert len(_ip.user_ns["In"]) == _ip.displayhook.prompt_count + 1
359 359
360 360
361 361 class TestResetErrors(TestCase):
362 362
363 363 def test_reset_redefine(self):
364 364
365 365 @magics_class
366 366 class KernelMagics(Magics):
367 367 @line_magic
368 368 def less(self, shell): pass
369 369
370 370 _ip.register_magics(KernelMagics)
371 371
372 372 with self.assertLogs() as cm:
373 373 # hack, we want to just capture logs, but assertLogs fails if not
374 374 # logs get produce.
375 375 # so log one things we ignore.
376 376 import logging as log_mod
377 377 log = log_mod.getLogger()
378 378 log.info('Nothing')
379 379 # end hack.
380 380 _ip.run_cell("reset -f")
381 381
382 382 assert len(cm.output) == 1
383 383 for out in cm.output:
384 384 assert "Invalid alias" not in out
385 385
386 386 def test_tb_syntaxerror():
387 387 """test %tb after a SyntaxError"""
388 388 ip = get_ipython()
389 389 ip.run_cell("for")
390 390
391 391 # trap and validate stdout
392 392 save_stdout = sys.stdout
393 393 try:
394 394 sys.stdout = StringIO()
395 395 ip.run_cell("%tb")
396 396 out = sys.stdout.getvalue()
397 397 finally:
398 398 sys.stdout = save_stdout
399 399 # trim output, and only check the last line
400 400 last_line = out.rstrip().splitlines()[-1].strip()
401 401 assert last_line == "SyntaxError: invalid syntax"
402 402
403 403
404 404 def test_time():
405 405 ip = get_ipython()
406 406
407 407 with tt.AssertPrints("Wall time: "):
408 408 ip.run_cell("%time None")
409 409
410 410 ip.run_cell("def f(kmjy):\n"
411 411 " %time print (2*kmjy)")
412 412
413 413 with tt.AssertPrints("Wall time: "):
414 414 with tt.AssertPrints("hihi", suppress=False):
415 415 ip.run_cell("f('hi')")
416 416
417 417 def test_time_last_not_expression():
418 418 ip.run_cell("%%time\n"
419 419 "var_1 = 1\n"
420 420 "var_2 = 2\n")
421 421 assert ip.user_ns['var_1'] == 1
422 422 del ip.user_ns['var_1']
423 423 assert ip.user_ns['var_2'] == 2
424 424 del ip.user_ns['var_2']
425 425
426 426
427 427 @dec.skip_win32
428 428 def test_time2():
429 429 ip = get_ipython()
430 430
431 431 with tt.AssertPrints("CPU times: user "):
432 432 ip.run_cell("%time None")
433 433
434 434 def test_time3():
435 435 """Erroneous magic function calls, issue gh-3334"""
436 436 ip = get_ipython()
437 437 ip.user_ns.pop('run', None)
438 438
439 439 with tt.AssertNotPrints("not found", channel='stderr'):
440 440 ip.run_cell("%%time\n"
441 441 "run = 0\n"
442 442 "run += 1")
443 443
444 444 def test_multiline_time():
445 445 """Make sure last statement from time return a value."""
446 446 ip = get_ipython()
447 447 ip.user_ns.pop('run', None)
448 448
449 449 ip.run_cell(dedent("""\
450 450 %%time
451 451 a = "ho"
452 452 b = "hey"
453 453 a+b
454 454 """
455 455 )
456 456 )
457 457 assert ip.user_ns_hidden["_"] == "hohey"
458 458
459 459
460 460 def test_time_local_ns():
461 461 """
462 462 Test that local_ns is actually global_ns when running a cell magic
463 463 """
464 464 ip = get_ipython()
465 465 ip.run_cell("%%time\n" "myvar = 1")
466 466 assert ip.user_ns["myvar"] == 1
467 467 del ip.user_ns["myvar"]
468 468
469 469
470 470 def test_doctest_mode():
471 471 "Toggle doctest_mode twice, it should be a no-op and run without error"
472 472 _ip.magic('doctest_mode')
473 473 _ip.magic('doctest_mode')
474 474
475 475
476 476 def test_parse_options():
477 477 """Tests for basic options parsing in magics."""
478 478 # These are only the most minimal of tests, more should be added later. At
479 479 # the very least we check that basic text/unicode calls work OK.
480 480 m = DummyMagics(_ip)
481 481 assert m.parse_options("foo", "")[1] == "foo"
482 482 assert m.parse_options("foo", "")[1] == "foo"
483 483
484 484
485 485 def test_parse_options_preserve_non_option_string():
486 486 """Test to assert preservation of non-option part of magic-block, while parsing magic options."""
487 487 m = DummyMagics(_ip)
488 488 opts, stmt = m.parse_options(
489 489 " -n1 -r 13 _ = 314 + foo", "n:r:", preserve_non_opts=True
490 490 )
491 491 assert opts == {"n": "1", "r": "13"}
492 492 assert stmt == "_ = 314 + foo"
493 493
494 494
495 495 def test_run_magic_preserve_code_block():
496 496 """Test to assert preservation of non-option part of magic-block, while running magic."""
497 497 _ip.user_ns["spaces"] = []
498 498 _ip.magic("timeit -n1 -r1 spaces.append([s.count(' ') for s in ['document']])")
499 499 assert _ip.user_ns["spaces"] == [[0]]
500 500
501 501
502 502 def test_dirops():
503 503 """Test various directory handling operations."""
504 504 # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/')
505 505 curpath = os.getcwd
506 506 startdir = os.getcwd()
507 507 ipdir = os.path.realpath(_ip.ipython_dir)
508 508 try:
509 509 _ip.magic('cd "%s"' % ipdir)
510 510 assert curpath() == ipdir
511 511 _ip.magic('cd -')
512 512 assert curpath() == startdir
513 513 _ip.magic('pushd "%s"' % ipdir)
514 514 assert curpath() == ipdir
515 515 _ip.magic('popd')
516 516 assert curpath() == startdir
517 517 finally:
518 518 os.chdir(startdir)
519 519
520 520
521 521 def test_cd_force_quiet():
522 522 """Test OSMagics.cd_force_quiet option"""
523 523 _ip.config.OSMagics.cd_force_quiet = True
524 524 osmagics = osm.OSMagics(shell=_ip)
525 525
526 526 startdir = os.getcwd()
527 527 ipdir = os.path.realpath(_ip.ipython_dir)
528 528
529 529 try:
530 530 with tt.AssertNotPrints(ipdir):
531 531 osmagics.cd('"%s"' % ipdir)
532 532 with tt.AssertNotPrints(startdir):
533 533 osmagics.cd('-')
534 534 finally:
535 535 os.chdir(startdir)
536 536
537 537
538 538 def test_xmode():
539 539 # Calling xmode three times should be a no-op
540 540 xmode = _ip.InteractiveTB.mode
541 541 for i in range(4):
542 542 _ip.magic("xmode")
543 543 assert _ip.InteractiveTB.mode == xmode
544 544
545 545 def test_reset_hard():
546 546 monitor = []
547 547 class A(object):
548 548 def __del__(self):
549 549 monitor.append(1)
550 550 def __repr__(self):
551 551 return "<A instance>"
552 552
553 553 _ip.user_ns["a"] = A()
554 554 _ip.run_cell("a")
555 555
556 556 assert monitor == []
557 557 _ip.magic("reset -f")
558 558 assert monitor == [1]
559 559
560 560 class TestXdel(tt.TempFileMixin):
561 561 def test_xdel(self):
562 562 """Test that references from %run are cleared by xdel."""
563 563 src = ("class A(object):\n"
564 564 " monitor = []\n"
565 565 " def __del__(self):\n"
566 566 " self.monitor.append(1)\n"
567 567 "a = A()\n")
568 568 self.mktmp(src)
569 569 # %run creates some hidden references...
570 570 _ip.magic("run %s" % self.fname)
571 571 # ... as does the displayhook.
572 572 _ip.run_cell("a")
573 573
574 574 monitor = _ip.user_ns["A"].monitor
575 575 assert monitor == []
576 576
577 577 _ip.magic("xdel a")
578 578
579 579 # Check that a's __del__ method has been called.
580 580 gc.collect(0)
581 581 assert monitor == [1]
582 582
583 583 def doctest_who():
584 584 """doctest for %who
585 585
586 586 In [1]: %reset -sf
587 587
588 588 In [2]: alpha = 123
589 589
590 590 In [3]: beta = 'beta'
591 591
592 592 In [4]: %who int
593 593 alpha
594 594
595 595 In [5]: %who str
596 596 beta
597 597
598 598 In [6]: %whos
599 599 Variable Type Data/Info
600 600 ----------------------------
601 601 alpha int 123
602 602 beta str beta
603 603
604 604 In [7]: %who_ls
605 605 Out[7]: ['alpha', 'beta']
606 606 """
607 607
608 608 def test_whos():
609 609 """Check that whos is protected against objects where repr() fails."""
610 610 class A(object):
611 611 def __repr__(self):
612 612 raise Exception()
613 613 _ip.user_ns['a'] = A()
614 614 _ip.magic("whos")
615 615
616 616 def doctest_precision():
617 617 """doctest for %precision
618 618
619 619 In [1]: f = get_ipython().display_formatter.formatters['text/plain']
620 620
621 621 In [2]: %precision 5
622 622 Out[2]: '%.5f'
623 623
624 624 In [3]: f.float_format
625 625 Out[3]: '%.5f'
626 626
627 627 In [4]: %precision %e
628 628 Out[4]: '%e'
629 629
630 630 In [5]: f(3.1415927)
631 631 Out[5]: '3.141593e+00'
632 632 """
633 633
634 634 def test_debug_magic():
635 635 """Test debugging a small code with %debug
636 636
637 637 In [1]: with PdbTestInput(['c']):
638 638 ...: %debug print("a b") #doctest: +ELLIPSIS
639 639 ...:
640 640 ...
641 641 ipdb> c
642 642 a b
643 643 In [2]:
644 644 """
645 645
646 646 def test_psearch():
647 647 with tt.AssertPrints("dict.fromkeys"):
648 648 _ip.run_cell("dict.fr*?")
649 649 with tt.AssertPrints("π.is_integer"):
650 650 _ip.run_cell("π = 3.14;\nπ.is_integ*?")
651 651
652 652 def test_timeit_shlex():
653 653 """test shlex issues with timeit (#1109)"""
654 654 _ip.ex("def f(*a,**kw): pass")
655 655 _ip.magic('timeit -n1 "this is a bug".count(" ")')
656 656 _ip.magic('timeit -r1 -n1 f(" ", 1)')
657 657 _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")')
658 658 _ip.magic('timeit -r1 -n1 ("a " + "b")')
659 659 _ip.magic('timeit -r1 -n1 f("a " + "b")')
660 660 _ip.magic('timeit -r1 -n1 f("a " + "b ")')
661 661
662 662
663 663 def test_timeit_special_syntax():
664 664 "Test %%timeit with IPython special syntax"
665 665 @register_line_magic
666 666 def lmagic(line):
667 667 ip = get_ipython()
668 668 ip.user_ns['lmagic_out'] = line
669 669
670 670 # line mode test
671 671 _ip.run_line_magic("timeit", "-n1 -r1 %lmagic my line")
672 672 assert _ip.user_ns["lmagic_out"] == "my line"
673 673 # cell mode test
674 674 _ip.run_cell_magic("timeit", "-n1 -r1", "%lmagic my line2")
675 675 assert _ip.user_ns["lmagic_out"] == "my line2"
676 676
677 677
678 678 def test_timeit_return():
679 679 """
680 680 test whether timeit -o return object
681 681 """
682 682
683 683 res = _ip.run_line_magic('timeit','-n10 -r10 -o 1')
684 684 assert(res is not None)
685 685
686 686 def test_timeit_quiet():
687 687 """
688 688 test quiet option of timeit magic
689 689 """
690 690 with tt.AssertNotPrints("loops"):
691 691 _ip.run_cell("%timeit -n1 -r1 -q 1")
692 692
693 693 def test_timeit_return_quiet():
694 694 with tt.AssertNotPrints("loops"):
695 695 res = _ip.run_line_magic('timeit', '-n1 -r1 -q -o 1')
696 696 assert (res is not None)
697 697
698 698 def test_timeit_invalid_return():
699 699 with pytest.raises(SyntaxError):
700 700 _ip.run_line_magic('timeit', 'return')
701 701
702 702 @dec.skipif(execution.profile is None)
703 703 def test_prun_special_syntax():
704 704 "Test %%prun with IPython special syntax"
705 705 @register_line_magic
706 706 def lmagic(line):
707 707 ip = get_ipython()
708 708 ip.user_ns['lmagic_out'] = line
709 709
710 710 # line mode test
711 711 _ip.run_line_magic("prun", "-q %lmagic my line")
712 712 assert _ip.user_ns["lmagic_out"] == "my line"
713 713 # cell mode test
714 714 _ip.run_cell_magic("prun", "-q", "%lmagic my line2")
715 715 assert _ip.user_ns["lmagic_out"] == "my line2"
716 716
717 717
718 718 @dec.skipif(execution.profile is None)
719 719 def test_prun_quotes():
720 720 "Test that prun does not clobber string escapes (GH #1302)"
721 721 _ip.magic(r"prun -q x = '\t'")
722 722 assert _ip.user_ns["x"] == "\t"
723 723
724 724
725 725 def test_extension():
726 726 # Debugging information for failures of this test
727 727 print('sys.path:')
728 728 for p in sys.path:
729 729 print(' ', p)
730 730 print('CWD', os.getcwd())
731 731
732 732 pytest.raises(ImportError, _ip.magic, "load_ext daft_extension")
733 733 daft_path = os.path.join(os.path.dirname(__file__), "daft_extension")
734 734 sys.path.insert(0, daft_path)
735 735 try:
736 736 _ip.user_ns.pop('arq', None)
737 737 invalidate_caches() # Clear import caches
738 738 _ip.magic("load_ext daft_extension")
739 739 assert _ip.user_ns["arq"] == 185
740 740 _ip.magic("unload_ext daft_extension")
741 741 assert 'arq' not in _ip.user_ns
742 742 finally:
743 743 sys.path.remove(daft_path)
744 744
745 745
746 746 def test_notebook_export_json():
747 747 pytest.importorskip("nbformat")
748 748 _ip = get_ipython()
749 749 _ip.history_manager.reset() # Clear any existing history.
750 750 cmds = ["a=1", "def b():\n return a**2", "print('noël, été', b())"]
751 751 for i, cmd in enumerate(cmds, start=1):
752 752 _ip.history_manager.store_inputs(i, cmd)
753 753 with TemporaryDirectory() as td:
754 754 outfile = os.path.join(td, "nb.ipynb")
755 755 _ip.magic("notebook %s" % outfile)
756 756
757 757
758 758 class TestEnv(TestCase):
759 759
760 760 def test_env(self):
761 761 env = _ip.magic("env")
762 762 self.assertTrue(isinstance(env, dict))
763 763
764 764 def test_env_secret(self):
765 765 env = _ip.magic("env")
766 766 hidden = "<hidden>"
767 767 with mock.patch.dict(
768 768 os.environ,
769 769 {
770 770 "API_KEY": "abc123",
771 771 "SECRET_THING": "ssshhh",
772 772 "JUPYTER_TOKEN": "",
773 773 "VAR": "abc"
774 774 }
775 775 ):
776 776 env = _ip.magic("env")
777 777 assert env["API_KEY"] == hidden
778 778 assert env["SECRET_THING"] == hidden
779 779 assert env["JUPYTER_TOKEN"] == hidden
780 780 assert env["VAR"] == "abc"
781 781
782 782 def test_env_get_set_simple(self):
783 783 env = _ip.magic("env var val1")
784 784 self.assertEqual(env, None)
785 785 self.assertEqual(os.environ['var'], 'val1')
786 786 self.assertEqual(_ip.magic("env var"), 'val1')
787 787 env = _ip.magic("env var=val2")
788 788 self.assertEqual(env, None)
789 789 self.assertEqual(os.environ['var'], 'val2')
790 790
791 791 def test_env_get_set_complex(self):
792 792 env = _ip.magic("env var 'val1 '' 'val2")
793 793 self.assertEqual(env, None)
794 794 self.assertEqual(os.environ['var'], "'val1 '' 'val2")
795 795 self.assertEqual(_ip.magic("env var"), "'val1 '' 'val2")
796 796 env = _ip.magic('env var=val2 val3="val4')
797 797 self.assertEqual(env, None)
798 798 self.assertEqual(os.environ['var'], 'val2 val3="val4')
799 799
800 800 def test_env_set_bad_input(self):
801 801 self.assertRaises(UsageError, lambda: _ip.magic("set_env var"))
802 802
803 803 def test_env_set_whitespace(self):
804 804 self.assertRaises(UsageError, lambda: _ip.magic("env var A=B"))
805 805
806 806
807 807 class CellMagicTestCase(TestCase):
808 808
809 809 def check_ident(self, magic):
810 810 # Manually called, we get the result
811 811 out = _ip.run_cell_magic(magic, "a", "b")
812 812 assert out == ("a", "b")
813 813 # Via run_cell, it goes into the user's namespace via displayhook
814 814 _ip.run_cell("%%" + magic + " c\nd\n")
815 815 assert _ip.user_ns["_"] == ("c", "d\n")
816 816
817 817 def test_cell_magic_func_deco(self):
818 818 "Cell magic using simple decorator"
819 819 @register_cell_magic
820 820 def cellm(line, cell):
821 821 return line, cell
822 822
823 823 self.check_ident('cellm')
824 824
825 825 def test_cell_magic_reg(self):
826 826 "Cell magic manually registered"
827 827 def cellm(line, cell):
828 828 return line, cell
829 829
830 830 _ip.register_magic_function(cellm, 'cell', 'cellm2')
831 831 self.check_ident('cellm2')
832 832
833 833 def test_cell_magic_class(self):
834 834 "Cell magics declared via a class"
835 835 @magics_class
836 836 class MyMagics(Magics):
837 837
838 838 @cell_magic
839 839 def cellm3(self, line, cell):
840 840 return line, cell
841 841
842 842 _ip.register_magics(MyMagics)
843 843 self.check_ident('cellm3')
844 844
845 845 def test_cell_magic_class2(self):
846 846 "Cell magics declared via a class, #2"
847 847 @magics_class
848 848 class MyMagics2(Magics):
849 849
850 850 @cell_magic('cellm4')
851 851 def cellm33(self, line, cell):
852 852 return line, cell
853 853
854 854 _ip.register_magics(MyMagics2)
855 855 self.check_ident('cellm4')
856 856 # Check that nothing is registered as 'cellm33'
857 857 c33 = _ip.find_cell_magic('cellm33')
858 858 assert c33 == None
859 859
860 860 def test_file():
861 861 """Basic %%writefile"""
862 862 ip = get_ipython()
863 863 with TemporaryDirectory() as td:
864 fname = os.path.join(td, 'file1')
865 ip.run_cell_magic("writefile", fname, u'\n'.join([
866 'line1',
867 'line2',
868 ]))
869 s = Path(fname).read_text(encoding='utf-8')
864 fname = os.path.join(td, "file1")
865 ip.run_cell_magic(
866 "writefile",
867 fname,
868 "\n".join(
869 [
870 "line1",
871 "line2",
872 ]
873 ),
874 )
875 s = Path(fname).read_text(encoding="utf-8")
870 876 assert "line1\n" in s
871 877 assert "line2" in s
872 878
873 879
874 880 @dec.skip_win32
875 881 def test_file_single_quote():
876 882 """Basic %%writefile with embedded single quotes"""
877 883 ip = get_ipython()
878 884 with TemporaryDirectory() as td:
879 fname = os.path.join(td, '\'file1\'')
880 ip.run_cell_magic("writefile", fname, u'\n'.join([
881 'line1',
882 'line2',
883 ]))
884 s = Path(fname).read_text(encoding='utf-8')
885 fname = os.path.join(td, "'file1'")
886 ip.run_cell_magic(
887 "writefile",
888 fname,
889 "\n".join(
890 [
891 "line1",
892 "line2",
893 ]
894 ),
895 )
896 s = Path(fname).read_text(encoding="utf-8")
885 897 assert "line1\n" in s
886 898 assert "line2" in s
887 899
888 900
889 901 @dec.skip_win32
890 902 def test_file_double_quote():
891 903 """Basic %%writefile with embedded double quotes"""
892 904 ip = get_ipython()
893 905 with TemporaryDirectory() as td:
894 906 fname = os.path.join(td, '"file1"')
895 ip.run_cell_magic("writefile", fname, u'\n'.join([
896 'line1',
897 'line2',
898 ]))
899 s = Path(fname).read_text(encoding='utf-8')
907 ip.run_cell_magic(
908 "writefile",
909 fname,
910 "\n".join(
911 [
912 "line1",
913 "line2",
914 ]
915 ),
916 )
917 s = Path(fname).read_text(encoding="utf-8")
900 918 assert "line1\n" in s
901 919 assert "line2" in s
902 920
903 921
904 922 def test_file_var_expand():
905 923 """%%writefile $filename"""
906 924 ip = get_ipython()
907 925 with TemporaryDirectory() as td:
908 fname = os.path.join(td, 'file1')
909 ip.user_ns['filename'] = fname
910 ip.run_cell_magic("writefile", '$filename', u'\n'.join([
911 'line1',
912 'line2',
913 ]))
914 s = Path(fname).read_text(encoding='utf-8')
926 fname = os.path.join(td, "file1")
927 ip.user_ns["filename"] = fname
928 ip.run_cell_magic(
929 "writefile",
930 "$filename",
931 "\n".join(
932 [
933 "line1",
934 "line2",
935 ]
936 ),
937 )
938 s = Path(fname).read_text(encoding="utf-8")
915 939 assert "line1\n" in s
916 940 assert "line2" in s
917 941
918 942
919 943 def test_file_unicode():
920 944 """%%writefile with unicode cell"""
921 945 ip = get_ipython()
922 946 with TemporaryDirectory() as td:
923 947 fname = os.path.join(td, 'file1')
924 948 ip.run_cell_magic("writefile", fname, u'\n'.join([
925 949 u'liné1',
926 950 u'liné2',
927 951 ]))
928 952 with io.open(fname, encoding='utf-8') as f:
929 953 s = f.read()
930 954 assert "liné1\n" in s
931 955 assert "liné2" in s
932 956
933 957
934 958 def test_file_amend():
935 959 """%%writefile -a amends files"""
936 960 ip = get_ipython()
937 961 with TemporaryDirectory() as td:
938 fname = os.path.join(td, 'file2')
939 ip.run_cell_magic("writefile", fname, u'\n'.join([
940 'line1',
941 'line2',
942 ]))
943 ip.run_cell_magic("writefile", "-a %s" % fname, u'\n'.join([
944 'line3',
945 'line4',
946 ]))
947 s = Path(fname).read_text(encoding='utf-8')
962 fname = os.path.join(td, "file2")
963 ip.run_cell_magic(
964 "writefile",
965 fname,
966 "\n".join(
967 [
968 "line1",
969 "line2",
970 ]
971 ),
972 )
973 ip.run_cell_magic(
974 "writefile",
975 "-a %s" % fname,
976 "\n".join(
977 [
978 "line3",
979 "line4",
980 ]
981 ),
982 )
983 s = Path(fname).read_text(encoding="utf-8")
948 984 assert "line1\n" in s
949 985 assert "line3\n" in s
950 986
951 987
952 988 def test_file_spaces():
953 989 """%%file with spaces in filename"""
954 990 ip = get_ipython()
955 991 with TemporaryWorkingDirectory() as td:
956 992 fname = "file name"
957 ip.run_cell_magic("file", '"%s"'%fname, u'\n'.join([
958 'line1',
959 'line2',
960 ]))
961 s = Path(fname).read_text(encoding='utf-8')
993 ip.run_cell_magic(
994 "file",
995 '"%s"' % fname,
996 "\n".join(
997 [
998 "line1",
999 "line2",
1000 ]
1001 ),
1002 )
1003 s = Path(fname).read_text(encoding="utf-8")
962 1004 assert "line1\n" in s
963 1005 assert "line2" in s
964 1006
965 1007
966 1008 def test_script_config():
967 1009 ip = get_ipython()
968 1010 ip.config.ScriptMagics.script_magics = ['whoda']
969 1011 sm = script.ScriptMagics(shell=ip)
970 1012 assert "whoda" in sm.magics["cell"]
971 1013
972 1014
973 1015 def test_script_out():
974 1016 ip = get_ipython()
975 1017 ip.run_cell_magic("script", f"--out output {sys.executable}", "print('hi')")
976 1018 assert ip.user_ns["output"].strip() == "hi"
977 1019
978 1020
979 1021 def test_script_err():
980 1022 ip = get_ipython()
981 1023 ip.run_cell_magic(
982 1024 "script",
983 1025 f"--err error {sys.executable}",
984 1026 "import sys; print('hello', file=sys.stderr)",
985 1027 )
986 1028 assert ip.user_ns["error"].strip() == "hello"
987 1029
988 1030
989 1031 def test_script_out_err():
990 1032
991 1033 ip = get_ipython()
992 1034 ip.run_cell_magic(
993 1035 "script",
994 1036 f"--out output --err error {sys.executable}",
995 1037 "\n".join(
996 1038 [
997 1039 "import sys",
998 1040 "print('hi')",
999 1041 "print('hello', file=sys.stderr)",
1000 1042 ]
1001 1043 ),
1002 1044 )
1003 1045 assert ip.user_ns["output"].strip() == "hi"
1004 1046 assert ip.user_ns["error"].strip() == "hello"
1005 1047
1006 1048
1007 1049 async def test_script_bg_out():
1008 1050 ip = get_ipython()
1009 1051 ip.run_cell_magic("script", f"--bg --out output {sys.executable}", "print('hi')")
1010 1052 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1011 1053 assert ip.user_ns["output"].at_eof()
1012 1054
1013 1055
1014 1056 async def test_script_bg_err():
1015 1057 ip = get_ipython()
1016 1058 ip.run_cell_magic(
1017 1059 "script",
1018 1060 f"--bg --err error {sys.executable}",
1019 1061 "import sys; print('hello', file=sys.stderr)",
1020 1062 )
1021 1063 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1022 1064 assert ip.user_ns["error"].at_eof()
1023 1065
1024 1066
1025 1067 async def test_script_bg_out_err():
1026 1068 ip = get_ipython()
1027 1069 ip.run_cell_magic(
1028 1070 "script",
1029 1071 f"--bg --out output --err error {sys.executable}",
1030 1072 "\n".join(
1031 1073 [
1032 1074 "import sys",
1033 1075 "print('hi')",
1034 1076 "print('hello', file=sys.stderr)",
1035 1077 ]
1036 1078 ),
1037 1079 )
1038 1080 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1039 1081 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1040 1082 assert ip.user_ns["output"].at_eof()
1041 1083 assert ip.user_ns["error"].at_eof()
1042 1084
1043 1085
1044 1086 async def test_script_bg_proc():
1045 1087 ip = get_ipython()
1046 1088 ip.run_cell_magic(
1047 1089 "script",
1048 1090 f"--bg --out output --proc p {sys.executable}",
1049 1091 "\n".join(
1050 1092 [
1051 1093 "import sys",
1052 1094 "print('hi')",
1053 1095 "print('hello', file=sys.stderr)",
1054 1096 ]
1055 1097 ),
1056 1098 )
1057 1099 p = ip.user_ns["p"]
1058 1100 await p.wait()
1059 1101 assert p.returncode == 0
1060 1102 assert (await p.stdout.read()).strip() == b"hi"
1061 1103 # not captured, so empty
1062 1104 assert (await p.stderr.read()) == b""
1063 1105 assert p.stdout.at_eof()
1064 1106 assert p.stderr.at_eof()
1065 1107
1066 1108
1067 1109 def test_script_defaults():
1068 1110 ip = get_ipython()
1069 1111 for cmd in ['sh', 'bash', 'perl', 'ruby']:
1070 1112 try:
1071 1113 find_cmd(cmd)
1072 1114 except Exception:
1073 1115 pass
1074 1116 else:
1075 1117 assert cmd in ip.magics_manager.magics["cell"]
1076 1118
1077 1119
1078 1120 @magics_class
1079 1121 class FooFoo(Magics):
1080 1122 """class with both %foo and %%foo magics"""
1081 1123 @line_magic('foo')
1082 1124 def line_foo(self, line):
1083 1125 "I am line foo"
1084 1126 pass
1085 1127
1086 1128 @cell_magic("foo")
1087 1129 def cell_foo(self, line, cell):
1088 1130 "I am cell foo, not line foo"
1089 1131 pass
1090 1132
1091 1133 def test_line_cell_info():
1092 1134 """%%foo and %foo magics are distinguishable to inspect"""
1093 1135 ip = get_ipython()
1094 1136 ip.magics_manager.register(FooFoo)
1095 1137 oinfo = ip.object_inspect("foo")
1096 1138 assert oinfo["found"] is True
1097 1139 assert oinfo["ismagic"] is True
1098 1140
1099 1141 oinfo = ip.object_inspect("%%foo")
1100 1142 assert oinfo["found"] is True
1101 1143 assert oinfo["ismagic"] is True
1102 1144 assert oinfo["docstring"] == FooFoo.cell_foo.__doc__
1103 1145
1104 1146 oinfo = ip.object_inspect("%foo")
1105 1147 assert oinfo["found"] is True
1106 1148 assert oinfo["ismagic"] is True
1107 1149 assert oinfo["docstring"] == FooFoo.line_foo.__doc__
1108 1150
1109 1151
1110 1152 def test_multiple_magics():
1111 1153 ip = get_ipython()
1112 1154 foo1 = FooFoo(ip)
1113 1155 foo2 = FooFoo(ip)
1114 1156 mm = ip.magics_manager
1115 1157 mm.register(foo1)
1116 1158 assert mm.magics["line"]["foo"].__self__ is foo1
1117 1159 mm.register(foo2)
1118 1160 assert mm.magics["line"]["foo"].__self__ is foo2
1119 1161
1120 1162
1121 1163 def test_alias_magic():
1122 1164 """Test %alias_magic."""
1123 1165 ip = get_ipython()
1124 1166 mm = ip.magics_manager
1125 1167
1126 1168 # Basic operation: both cell and line magics are created, if possible.
1127 1169 ip.run_line_magic("alias_magic", "timeit_alias timeit")
1128 1170 assert "timeit_alias" in mm.magics["line"]
1129 1171 assert "timeit_alias" in mm.magics["cell"]
1130 1172
1131 1173 # --cell is specified, line magic not created.
1132 1174 ip.run_line_magic("alias_magic", "--cell timeit_cell_alias timeit")
1133 1175 assert "timeit_cell_alias" not in mm.magics["line"]
1134 1176 assert "timeit_cell_alias" in mm.magics["cell"]
1135 1177
1136 1178 # Test that line alias is created successfully.
1137 1179 ip.run_line_magic("alias_magic", "--line env_alias env")
1138 1180 assert ip.run_line_magic("env", "") == ip.run_line_magic("env_alias", "")
1139 1181
1140 1182 # Test that line alias with parameters passed in is created successfully.
1141 1183 ip.run_line_magic(
1142 1184 "alias_magic", "--line history_alias history --params " + shlex.quote("3")
1143 1185 )
1144 1186 assert "history_alias" in mm.magics["line"]
1145 1187
1146 1188
1147 1189 def test_save():
1148 1190 """Test %save."""
1149 1191 ip = get_ipython()
1150 1192 ip.history_manager.reset() # Clear any existing history.
1151 1193 cmds = ["a=1", "def b():\n return a**2", "print(a, b())"]
1152 1194 for i, cmd in enumerate(cmds, start=1):
1153 1195 ip.history_manager.store_inputs(i, cmd)
1154 1196 with TemporaryDirectory() as tmpdir:
1155 1197 file = os.path.join(tmpdir, "testsave.py")
1156 1198 ip.run_line_magic("save", "%s 1-10" % file)
1157 content = Path(file).read_text(encoding='utf-8')
1199 content = Path(file).read_text(encoding="utf-8")
1158 1200 assert content.count(cmds[0]) == 1
1159 1201 assert "coding: utf-8" in content
1160 1202 ip.run_line_magic("save", "-a %s 1-10" % file)
1161 content = Path(file).read_text(encoding='utf-8')
1203 content = Path(file).read_text(encoding="utf-8")
1162 1204 assert content.count(cmds[0]) == 2
1163 1205 assert "coding: utf-8" in content
1164 1206
1165 1207
1166 1208 def test_save_with_no_args():
1167 1209 ip = get_ipython()
1168 1210 ip.history_manager.reset() # Clear any existing history.
1169 1211 cmds = ["a=1", "def b():\n return a**2", "print(a, b())", "%save"]
1170 1212 for i, cmd in enumerate(cmds, start=1):
1171 1213 ip.history_manager.store_inputs(i, cmd)
1172 1214
1173 1215 with TemporaryDirectory() as tmpdir:
1174 1216 path = os.path.join(tmpdir, "testsave.py")
1175 1217 ip.run_line_magic("save", path)
1176 content = Path(path).read_text(encoding='utf-8')
1218 content = Path(path).read_text(encoding="utf-8")
1177 1219 expected_content = dedent(
1178 1220 """\
1179 1221 # coding: utf-8
1180 1222 a=1
1181 1223 def b():
1182 1224 return a**2
1183 1225 print(a, b())
1184 1226 """
1185 1227 )
1186 1228 assert content == expected_content
1187 1229
1188 1230
1189 1231 def test_store():
1190 1232 """Test %store."""
1191 1233 ip = get_ipython()
1192 1234 ip.run_line_magic('load_ext', 'storemagic')
1193 1235
1194 1236 # make sure the storage is empty
1195 1237 ip.run_line_magic("store", "-z")
1196 1238 ip.user_ns["var"] = 42
1197 1239 ip.run_line_magic("store", "var")
1198 1240 ip.user_ns["var"] = 39
1199 1241 ip.run_line_magic("store", "-r")
1200 1242 assert ip.user_ns["var"] == 42
1201 1243
1202 1244 ip.run_line_magic("store", "-d var")
1203 1245 ip.user_ns["var"] = 39
1204 1246 ip.run_line_magic("store", "-r")
1205 1247 assert ip.user_ns["var"] == 39
1206 1248
1207 1249
1208 1250 def _run_edit_test(arg_s, exp_filename=None,
1209 1251 exp_lineno=-1,
1210 1252 exp_contents=None,
1211 1253 exp_is_temp=None):
1212 1254 ip = get_ipython()
1213 1255 M = code.CodeMagics(ip)
1214 1256 last_call = ['','']
1215 1257 opts,args = M.parse_options(arg_s,'prxn:')
1216 1258 filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call)
1217 1259
1218 1260 if exp_filename is not None:
1219 1261 assert exp_filename == filename
1220 1262 if exp_contents is not None:
1221 1263 with io.open(filename, 'r', encoding='utf-8') as f:
1222 1264 contents = f.read()
1223 1265 assert exp_contents == contents
1224 1266 if exp_lineno != -1:
1225 1267 assert exp_lineno == lineno
1226 1268 if exp_is_temp is not None:
1227 1269 assert exp_is_temp == is_temp
1228 1270
1229 1271
1230 1272 def test_edit_interactive():
1231 1273 """%edit on interactively defined objects"""
1232 1274 ip = get_ipython()
1233 1275 n = ip.execution_count
1234 1276 ip.run_cell("def foo(): return 1", store_history=True)
1235 1277
1236 1278 with pytest.raises(code.InteractivelyDefined) as e:
1237 1279 _run_edit_test("foo")
1238 1280 assert e.value.index == n
1239 1281
1240 1282
1241 1283 def test_edit_cell():
1242 1284 """%edit [cell id]"""
1243 1285 ip = get_ipython()
1244 1286
1245 1287 ip.run_cell("def foo(): return 1", store_history=True)
1246 1288
1247 1289 # test
1248 1290 _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True)
1249 1291
1250 1292 def test_edit_fname():
1251 1293 """%edit file"""
1252 1294 # test
1253 1295 _run_edit_test("test file.py", exp_filename="test file.py")
1254 1296
1255 1297 def test_bookmark():
1256 1298 ip = get_ipython()
1257 1299 ip.run_line_magic('bookmark', 'bmname')
1258 1300 with tt.AssertPrints('bmname'):
1259 1301 ip.run_line_magic('bookmark', '-l')
1260 1302 ip.run_line_magic('bookmark', '-d bmname')
1261 1303
1262 1304 def test_ls_magic():
1263 1305 ip = get_ipython()
1264 1306 json_formatter = ip.display_formatter.formatters['application/json']
1265 1307 json_formatter.enabled = True
1266 1308 lsmagic = ip.magic('lsmagic')
1267 1309 with warnings.catch_warnings(record=True) as w:
1268 1310 j = json_formatter(lsmagic)
1269 1311 assert sorted(j) == ["cell", "line"]
1270 1312 assert w == [] # no warnings
1271 1313
1272 1314
1273 1315 def test_strip_initial_indent():
1274 1316 def sii(s):
1275 1317 lines = s.splitlines()
1276 1318 return '\n'.join(code.strip_initial_indent(lines))
1277 1319
1278 1320 assert sii(" a = 1\nb = 2") == "a = 1\nb = 2"
1279 1321 assert sii(" a\n b\nc") == "a\n b\nc"
1280 1322 assert sii("a\n b") == "a\n b"
1281 1323
1282 1324 def test_logging_magic_quiet_from_arg():
1283 1325 _ip.config.LoggingMagics.quiet = False
1284 1326 lm = logging.LoggingMagics(shell=_ip)
1285 1327 with TemporaryDirectory() as td:
1286 1328 try:
1287 1329 with tt.AssertNotPrints(re.compile("Activating.*")):
1288 1330 lm.logstart('-q {}'.format(
1289 1331 os.path.join(td, "quiet_from_arg.log")))
1290 1332 finally:
1291 1333 _ip.logger.logstop()
1292 1334
1293 1335 def test_logging_magic_quiet_from_config():
1294 1336 _ip.config.LoggingMagics.quiet = True
1295 1337 lm = logging.LoggingMagics(shell=_ip)
1296 1338 with TemporaryDirectory() as td:
1297 1339 try:
1298 1340 with tt.AssertNotPrints(re.compile("Activating.*")):
1299 1341 lm.logstart(os.path.join(td, "quiet_from_config.log"))
1300 1342 finally:
1301 1343 _ip.logger.logstop()
1302 1344
1303 1345
1304 1346 def test_logging_magic_not_quiet():
1305 1347 _ip.config.LoggingMagics.quiet = False
1306 1348 lm = logging.LoggingMagics(shell=_ip)
1307 1349 with TemporaryDirectory() as td:
1308 1350 try:
1309 1351 with tt.AssertPrints(re.compile("Activating.*")):
1310 1352 lm.logstart(os.path.join(td, "not_quiet.log"))
1311 1353 finally:
1312 1354 _ip.logger.logstop()
1313 1355
1314 1356
1315 1357 def test_time_no_var_expand():
1316 1358 _ip.user_ns['a'] = 5
1317 1359 _ip.user_ns['b'] = []
1318 1360 _ip.magic('time b.append("{a}")')
1319 1361 assert _ip.user_ns['b'] == ['{a}']
1320 1362
1321 1363
1322 1364 # this is slow, put at the end for local testing.
1323 1365 def test_timeit_arguments():
1324 1366 "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
1325 1367 _ip.magic("timeit -n1 -r1 a=('#')")
1326 1368
1327 1369
1328 1370 TEST_MODULE = """
1329 1371 print('Loaded my_tmp')
1330 1372 if __name__ == "__main__":
1331 1373 print('I just ran a script')
1332 1374 """
1333 1375
1334 1376
1335 1377 def test_run_module_from_import_hook():
1336 1378 "Test that a module can be loaded via an import hook"
1337 1379 with TemporaryDirectory() as tmpdir:
1338 fullpath = os.path.join(tmpdir, 'my_tmp.py')
1339 Path(fullpath).write_text(TEST_MODULE, encoding='utf-8')
1380 fullpath = os.path.join(tmpdir, "my_tmp.py")
1381 Path(fullpath).write_text(TEST_MODULE, encoding="utf-8")
1340 1382
1341 1383 import importlib.abc
1342 1384 import importlib.util
1343 1385
1344 1386 class MyTempImporter(importlib.abc.MetaPathFinder, importlib.abc.SourceLoader):
1345 1387 def find_spec(self, fullname, path, target=None):
1346 1388 if fullname == "my_tmp":
1347 1389 return importlib.util.spec_from_loader(fullname, self)
1348 1390
1349 1391 def get_filename(self, fullname):
1350 1392 assert fullname == "my_tmp"
1351 1393 return fullpath
1352 1394
1353 1395 def get_data(self, path):
1354 1396 assert Path(path).samefile(fullpath)
1355 return Path(fullpath).read_text(encoding='utf-8')
1397 return Path(fullpath).read_text(encoding="utf-8")
1356 1398
1357 1399 sys.meta_path.insert(0, MyTempImporter())
1358 1400
1359 1401 with capture_output() as captured:
1360 1402 _ip.magic("run -m my_tmp")
1361 1403 _ip.run_cell("import my_tmp")
1362 1404
1363 1405 output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n"
1364 1406 assert output == captured.stdout
1365 1407
1366 1408 sys.meta_path.pop(0)
@@ -1,156 +1,156 b''
1 1 # coding: utf-8
2 2 """Tests for profile-related functions.
3 3
4 4 Currently only the startup-dir functionality is tested, but more tests should
5 5 be added for:
6 6
7 7 * ipython profile create
8 8 * ipython profile list
9 9 * ipython profile create --parallel
10 10 * security dir permissions
11 11
12 12 Authors
13 13 -------
14 14
15 15 * MinRK
16 16
17 17 """
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 import shutil
24 24 import sys
25 25 import tempfile
26 26
27 27 from pathlib import Path
28 28 from unittest import TestCase
29 29
30 30 from IPython.core.profileapp import list_profiles_in, list_bundled_profiles
31 31 from IPython.core.profiledir import ProfileDir
32 32
33 33 from IPython.testing import decorators as dec
34 34 from IPython.testing import tools as tt
35 35 from IPython.utils.process import getoutput
36 36 from IPython.utils.tempdir import TemporaryDirectory
37 37
38 38 #-----------------------------------------------------------------------------
39 39 # Globals
40 40 #-----------------------------------------------------------------------------
41 41 TMP_TEST_DIR = Path(tempfile.mkdtemp())
42 42 HOME_TEST_DIR = TMP_TEST_DIR / "home_test_dir"
43 43 IP_TEST_DIR = HOME_TEST_DIR / ".ipython"
44 44
45 45 #
46 46 # Setup/teardown functions/decorators
47 47 #
48 48
49 49 def setup_module():
50 50 """Setup test environment for the module:
51 51
52 52 - Adds dummy home dir tree
53 53 """
54 54 # Do not mask exceptions here. In particular, catching WindowsError is a
55 55 # problem because that exception is only defined on Windows...
56 56 (Path.cwd() / IP_TEST_DIR).mkdir(parents=True)
57 57
58 58
59 59 def teardown_module():
60 60 """Teardown test environment for the module:
61 61
62 62 - Remove dummy home dir tree
63 63 """
64 64 # Note: we remove the parent test dir, which is the root of all test
65 65 # subdirs we may have created. Use shutil instead of os.removedirs, so
66 66 # that non-empty directories are all recursively removed.
67 67 shutil.rmtree(TMP_TEST_DIR)
68 68
69 69
70 70 #-----------------------------------------------------------------------------
71 71 # Test functions
72 72 #-----------------------------------------------------------------------------
73 73 class ProfileStartupTest(TestCase):
74 74 def setUp(self):
75 75 # create profile dir
76 76 self.pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, "test")
77 77 self.options = ["--ipython-dir", IP_TEST_DIR, "--profile", "test"]
78 78 self.fname = TMP_TEST_DIR / "test.py"
79 79
80 80 def tearDown(self):
81 81 # We must remove this profile right away so its presence doesn't
82 82 # confuse other tests.
83 83 shutil.rmtree(self.pd.location)
84 84
85 85 def init(self, startup_file, startup, test):
86 86 # write startup python file
87 with open(Path(self.pd.startup_dir) / startup_file, "w", encoding='utf-8') as f:
87 with open(Path(self.pd.startup_dir) / startup_file, "w", encoding="utf-8") as f:
88 88 f.write(startup)
89 89 # write simple test file, to check that the startup file was run
90 with open(self.fname, 'w', encoding='utf-8') as f:
90 with open(self.fname, "w", encoding="utf-8") as f:
91 91 f.write(test)
92 92
93 93 def validate(self, output):
94 94 tt.ipexec_validate(self.fname, output, "", options=self.options)
95 95
96 96 def test_startup_py(self):
97 97 self.init('00-start.py', 'zzz=123\n', 'print(zzz)\n')
98 98 self.validate('123')
99 99
100 100 def test_startup_ipy(self):
101 101 self.init('00-start.ipy', '%xmode plain\n', '')
102 102 self.validate('Exception reporting mode: Plain')
103 103
104 104
105 105 def test_list_profiles_in():
106 106 # No need to remove these directories and files, as they will get nuked in
107 107 # the module-level teardown.
108 108 td = Path(tempfile.mkdtemp(dir=TMP_TEST_DIR))
109 109 for name in ("profile_foo", "profile_hello", "not_a_profile"):
110 110 Path(td / name).mkdir(parents=True)
111 111 if dec.unicode_paths:
112 112 Path(td / u"profile_ünicode").mkdir(parents=True)
113 113
114 with open(td / "profile_file", "w", encoding='utf-8') as f:
114 with open(td / "profile_file", "w", encoding="utf-8") as f:
115 115 f.write("I am not a profile directory")
116 116 profiles = list_profiles_in(td)
117 117
118 118 # unicode normalization can turn u'ünicode' into u'u\0308nicode',
119 119 # so only check for *nicode, and that creating a ProfileDir from the
120 120 # name remains valid
121 121 found_unicode = False
122 122 for p in list(profiles):
123 123 if p.endswith('nicode'):
124 124 pd = ProfileDir.find_profile_dir_by_name(td, p)
125 125 profiles.remove(p)
126 126 found_unicode = True
127 127 break
128 128 if dec.unicode_paths:
129 129 assert found_unicode is True
130 130 assert set(profiles) == {"foo", "hello"}
131 131
132 132
133 133 def test_list_bundled_profiles():
134 134 # This variable will need to be updated when a new profile gets bundled
135 135 bundled = sorted(list_bundled_profiles())
136 136 assert bundled == []
137 137
138 138
139 139 def test_profile_create_ipython_dir():
140 140 """ipython profile create respects --ipython-dir"""
141 141 with TemporaryDirectory() as td:
142 142 getoutput(
143 143 [
144 144 sys.executable,
145 145 "-m",
146 146 "IPython",
147 147 "profile",
148 148 "create",
149 149 "foo",
150 150 "--ipython-dir=%s" % td,
151 151 ]
152 152 )
153 153 profile_dir = Path(td) / "profile_foo"
154 154 assert Path(profile_dir).exists()
155 155 ipython_config = profile_dir / "ipython_config.py"
156 156 assert Path(ipython_config).exists()
@@ -1,612 +1,620 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 Note that any test using `run -i` should make sure to do a `reset` afterwards,
11 11 as otherwise it may influence later tests.
12 12 """
13 13
14 14 # Copyright (c) IPython Development Team.
15 15 # Distributed under the terms of the Modified BSD License.
16 16
17 17
18 18
19 19 import functools
20 20 import os
21 21 import platform
22 22 from os.path import join as pjoin
23 23 import random
24 24 import string
25 25 import sys
26 26 import textwrap
27 27 import unittest
28 28 from unittest.mock import patch
29 29
30 30 import pytest
31 31
32 32 from IPython.testing import decorators as dec
33 33 from IPython.testing import tools as tt
34 34 from IPython.utils.io import capture_output
35 35 from IPython.utils.tempdir import TemporaryDirectory
36 36 from IPython.core import debugger
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', encoding='utf-8')
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 doctest_run_option_parser_for_posix.__skip_doctest__ = sys.platform == "win32"
130 130
131 131
132 132 @dec.skip_if_not_win32
133 133 def doctest_run_option_parser_for_windows():
134 134 r"""Test option parser in %run (Windows specific).
135 135
136 136 In Windows, you can't escape ``*` `by backslash:
137 137
138 138 In [1]: %run print_argv.py print\\*.py
139 139 ['print\\\\*.py']
140 140
141 141 You can use quote to escape glob:
142 142
143 143 In [2]: %run print_argv.py 'print*.py'
144 144 ["'print*.py'"]
145 145
146 146 """
147 147
148 148
149 149 doctest_run_option_parser_for_windows.__skip_doctest__ = sys.platform != "win32"
150 150
151 151
152 152 def doctest_reset_del():
153 153 """Test that resetting doesn't cause errors in __del__ methods.
154 154
155 155 In [2]: class A(object):
156 156 ...: def __del__(self):
157 157 ...: print(str("Hi"))
158 158 ...:
159 159
160 160 In [3]: a = A()
161 161
162 162 In [4]: get_ipython().reset(); import gc; x = gc.collect(0)
163 163 Hi
164 164
165 165 In [5]: 1+1
166 166 Out[5]: 2
167 167 """
168 168
169 169 # For some tests, it will be handy to organize them in a class with a common
170 170 # setup that makes a temp file
171 171
172 172 class TestMagicRunPass(tt.TempFileMixin):
173 173
174 174 def setUp(self):
175 175 content = "a = [1,2,3]\nb = 1"
176 176 self.mktmp(content)
177 177
178 178 def run_tmpfile(self):
179 179 _ip = get_ipython()
180 180 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
181 181 # See below and ticket https://bugs.launchpad.net/bugs/366353
182 182 _ip.magic('run %s' % self.fname)
183 183
184 184 def run_tmpfile_p(self):
185 185 _ip = get_ipython()
186 186 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
187 187 # See below and ticket https://bugs.launchpad.net/bugs/366353
188 188 _ip.magic('run -p %s' % self.fname)
189 189
190 190 def test_builtins_id(self):
191 191 """Check that %run doesn't damage __builtins__ """
192 192 _ip = get_ipython()
193 193 # Test that the id of __builtins__ is not modified by %run
194 194 bid1 = id(_ip.user_ns['__builtins__'])
195 195 self.run_tmpfile()
196 196 bid2 = id(_ip.user_ns['__builtins__'])
197 197 assert bid1 == bid2
198 198
199 199 def test_builtins_type(self):
200 200 """Check that the type of __builtins__ doesn't change with %run.
201 201
202 202 However, the above could pass if __builtins__ was already modified to
203 203 be a dict (it should be a module) by a previous use of %run. So we
204 204 also check explicitly that it really is a module:
205 205 """
206 206 _ip = get_ipython()
207 207 self.run_tmpfile()
208 208 assert type(_ip.user_ns["__builtins__"]) == type(sys)
209 209
210 210 def test_run_profile(self):
211 211 """Test that the option -p, which invokes the profiler, do not
212 212 crash by invoking execfile"""
213 213 self.run_tmpfile_p()
214 214
215 215 def test_run_debug_twice(self):
216 216 # https://github.com/ipython/ipython/issues/10028
217 217 _ip = get_ipython()
218 218 with tt.fake_input(['c']):
219 219 _ip.magic('run -d %s' % self.fname)
220 220 with tt.fake_input(['c']):
221 221 _ip.magic('run -d %s' % self.fname)
222 222
223 223 def test_run_debug_twice_with_breakpoint(self):
224 224 """Make a valid python temp file."""
225 225 _ip = get_ipython()
226 226 with tt.fake_input(['b 2', 'c', 'c']):
227 227 _ip.magic('run -d %s' % self.fname)
228 228
229 229 with tt.fake_input(['c']):
230 230 with tt.AssertNotPrints('KeyError'):
231 231 _ip.magic('run -d %s' % self.fname)
232 232
233 233
234 234 class TestMagicRunSimple(tt.TempFileMixin):
235 235
236 236 def test_simpledef(self):
237 237 """Test that simple class definitions work."""
238 238 src = ("class foo: pass\n"
239 239 "def f(): return foo()")
240 240 self.mktmp(src)
241 241 _ip.magic("run %s" % self.fname)
242 242 _ip.run_cell("t = isinstance(f(), foo)")
243 243 assert _ip.user_ns["t"] is True
244 244
245 245 @pytest.mark.xfail(
246 246 platform.python_implementation() == "PyPy",
247 247 reason="expecting __del__ call on exit is unreliable and doesn't happen on PyPy",
248 248 )
249 249 def test_obj_del(self):
250 250 """Test that object's __del__ methods are called on exit."""
251 251 src = ("class A(object):\n"
252 252 " def __del__(self):\n"
253 253 " print('object A deleted')\n"
254 254 "a = A()\n")
255 255 self.mktmp(src)
256 256 err = None
257 257 tt.ipexec_validate(self.fname, 'object A deleted', err)
258 258
259 259 def test_aggressive_namespace_cleanup(self):
260 260 """Test that namespace cleanup is not too aggressive GH-238
261 261
262 262 Returning from another run magic deletes the namespace"""
263 263 # see ticket https://github.com/ipython/ipython/issues/238
264 264
265 265 with tt.TempFileMixin() as empty:
266 266 empty.mktmp("")
267 267 # On Windows, the filename will have \users in it, so we need to use the
268 268 # repr so that the \u becomes \\u.
269 269 src = (
270 270 "ip = get_ipython()\n"
271 271 "for i in range(5):\n"
272 272 " try:\n"
273 273 " ip.magic(%r)\n"
274 274 " except NameError as e:\n"
275 275 " print(i)\n"
276 276 " break\n" % ("run " + empty.fname)
277 277 )
278 278 self.mktmp(src)
279 279 _ip.magic("run %s" % self.fname)
280 280 _ip.run_cell("ip == get_ipython()")
281 281 assert _ip.user_ns["i"] == 4
282 282
283 283 def test_run_second(self):
284 284 """Test that running a second file doesn't clobber the first, gh-3547"""
285 285 self.mktmp("avar = 1\n" "def afunc():\n" " return avar\n")
286 286
287 287 with tt.TempFileMixin() as empty:
288 288 empty.mktmp("")
289 289
290 290 _ip.magic("run %s" % self.fname)
291 291 _ip.magic("run %s" % empty.fname)
292 292 assert _ip.user_ns["afunc"]() == 1
293 293
294 294 def test_tclass(self):
295 295 mydir = os.path.dirname(__file__)
296 296 tc = os.path.join(mydir, "tclass")
297 297 src = f"""\
298 298 import gc
299 299 %run "{tc}" C-first
300 300 gc.collect(0)
301 301 %run "{tc}" C-second
302 302 gc.collect(0)
303 303 %run "{tc}" C-third
304 304 gc.collect(0)
305 305 %reset -f
306 306 """
307 307 self.mktmp(src, ".ipy")
308 308 out = """\
309 309 ARGV 1-: ['C-first']
310 310 ARGV 1-: ['C-second']
311 311 tclass.py: deleting object: C-first
312 312 ARGV 1-: ['C-third']
313 313 tclass.py: deleting object: C-second
314 314 tclass.py: deleting object: C-third
315 315 """
316 316 err = None
317 317 tt.ipexec_validate(self.fname, out, err)
318 318
319 319 def test_run_i_after_reset(self):
320 320 """Check that %run -i still works after %reset (gh-693)"""
321 321 src = "yy = zz\n"
322 322 self.mktmp(src)
323 323 _ip.run_cell("zz = 23")
324 324 try:
325 325 _ip.magic("run -i %s" % self.fname)
326 326 assert _ip.user_ns["yy"] == 23
327 327 finally:
328 328 _ip.magic('reset -f')
329 329
330 330 _ip.run_cell("zz = 23")
331 331 try:
332 332 _ip.magic("run -i %s" % self.fname)
333 333 assert _ip.user_ns["yy"] == 23
334 334 finally:
335 335 _ip.magic('reset -f')
336 336
337 337 def test_unicode(self):
338 338 """Check that files in odd encodings are accepted."""
339 339 mydir = os.path.dirname(__file__)
340 340 na = os.path.join(mydir, 'nonascii.py')
341 341 _ip.magic('run "%s"' % na)
342 342 assert _ip.user_ns["u"] == "Ўт№Ф"
343 343
344 344 def test_run_py_file_attribute(self):
345 345 """Test handling of `__file__` attribute in `%run <file>.py`."""
346 346 src = "t = __file__\n"
347 347 self.mktmp(src)
348 348 _missing = object()
349 349 file1 = _ip.user_ns.get('__file__', _missing)
350 350 _ip.magic('run %s' % self.fname)
351 351 file2 = _ip.user_ns.get('__file__', _missing)
352 352
353 353 # Check that __file__ was equal to the filename in the script's
354 354 # namespace.
355 355 assert _ip.user_ns["t"] == self.fname
356 356
357 357 # Check that __file__ was not leaked back into user_ns.
358 358 assert file1 == file2
359 359
360 360 def test_run_ipy_file_attribute(self):
361 361 """Test handling of `__file__` attribute in `%run <file.ipy>`."""
362 362 src = "t = __file__\n"
363 363 self.mktmp(src, ext='.ipy')
364 364 _missing = object()
365 365 file1 = _ip.user_ns.get('__file__', _missing)
366 366 _ip.magic('run %s' % self.fname)
367 367 file2 = _ip.user_ns.get('__file__', _missing)
368 368
369 369 # Check that __file__ was equal to the filename in the script's
370 370 # namespace.
371 371 assert _ip.user_ns["t"] == self.fname
372 372
373 373 # Check that __file__ was not leaked back into user_ns.
374 374 assert file1 == file2
375 375
376 376 def test_run_formatting(self):
377 377 """ Test that %run -t -N<N> does not raise a TypeError for N > 1."""
378 378 src = "pass"
379 379 self.mktmp(src)
380 380 _ip.magic('run -t -N 1 %s' % self.fname)
381 381 _ip.magic('run -t -N 10 %s' % self.fname)
382 382
383 383 def test_ignore_sys_exit(self):
384 384 """Test the -e option to ignore sys.exit()"""
385 385 src = "import sys; sys.exit(1)"
386 386 self.mktmp(src)
387 387 with tt.AssertPrints('SystemExit'):
388 388 _ip.magic('run %s' % self.fname)
389 389
390 390 with tt.AssertNotPrints('SystemExit'):
391 391 _ip.magic('run -e %s' % self.fname)
392 392
393 393 def test_run_nb(self):
394 394 """Test %run notebook.ipynb"""
395 395 pytest.importorskip("nbformat")
396 396 from nbformat import v4, writes
397 397 nb = v4.new_notebook(
398 398 cells=[
399 399 v4.new_markdown_cell("The Ultimate Question of Everything"),
400 400 v4.new_code_cell("answer=42")
401 401 ]
402 402 )
403 403 src = writes(nb, version=4)
404 404 self.mktmp(src, ext='.ipynb')
405 405
406 406 _ip.magic("run %s" % self.fname)
407 407
408 408 assert _ip.user_ns["answer"] == 42
409 409
410 410 def test_run_nb_error(self):
411 411 """Test %run notebook.ipynb error"""
412 412 pytest.importorskip("nbformat")
413 413 from nbformat import v4, writes
414 414 # %run when a file name isn't provided
415 415 pytest.raises(Exception, _ip.magic, "run")
416 416
417 417 # %run when a file doesn't exist
418 418 pytest.raises(Exception, _ip.magic, "run foobar.ipynb")
419 419
420 420 # %run on a notebook with an error
421 421 nb = v4.new_notebook(
422 422 cells=[
423 423 v4.new_code_cell("0/0")
424 424 ]
425 425 )
426 426 src = writes(nb, version=4)
427 427 self.mktmp(src, ext='.ipynb')
428 428 pytest.raises(Exception, _ip.magic, "run %s" % self.fname)
429 429
430 430 def test_file_options(self):
431 431 src = ('import sys\n'
432 432 'a = " ".join(sys.argv[1:])\n')
433 433 self.mktmp(src)
434 434 test_opts = "-x 3 --verbose"
435 435 _ip.run_line_magic("run", "{0} {1}".format(self.fname, test_opts))
436 436 assert _ip.user_ns["a"] == test_opts
437 437
438 438
439 439 class TestMagicRunWithPackage(unittest.TestCase):
440 440
441 441 def writefile(self, name, content):
442 442 path = os.path.join(self.tempdir.name, name)
443 443 d = os.path.dirname(path)
444 444 if not os.path.isdir(d):
445 445 os.makedirs(d)
446 with open(path, 'w', encoding='utf-8') as f:
446 with open(path, "w", encoding="utf-8") as f:
447 447 f.write(textwrap.dedent(content))
448 448
449 449 def setUp(self):
450 450 self.package = package = 'tmp{0}'.format(''.join([random.choice(string.ascii_letters) for i in range(10)]))
451 451 """Temporary (probably) valid python package name."""
452 452
453 453 self.value = int(random.random() * 10000)
454 454
455 455 self.tempdir = TemporaryDirectory()
456 456 self.__orig_cwd = os.getcwd()
457 457 sys.path.insert(0, self.tempdir.name)
458 458
459 459 self.writefile(os.path.join(package, '__init__.py'), '')
460 460 self.writefile(os.path.join(package, 'sub.py'), """
461 461 x = {0!r}
462 462 """.format(self.value))
463 463 self.writefile(os.path.join(package, 'relative.py'), """
464 464 from .sub import x
465 465 """)
466 466 self.writefile(os.path.join(package, 'absolute.py'), """
467 467 from {0}.sub import x
468 468 """.format(package))
469 469 self.writefile(os.path.join(package, 'args.py'), """
470 470 import sys
471 471 a = " ".join(sys.argv[1:])
472 472 """.format(package))
473 473
474 474 def tearDown(self):
475 475 os.chdir(self.__orig_cwd)
476 476 sys.path[:] = [p for p in sys.path if p != self.tempdir.name]
477 477 self.tempdir.cleanup()
478 478
479 479 def check_run_submodule(self, submodule, opts=''):
480 480 _ip.user_ns.pop('x', None)
481 481 _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts))
482 482 self.assertEqual(_ip.user_ns['x'], self.value,
483 483 'Variable `x` is not loaded from module `{0}`.'
484 484 .format(submodule))
485 485
486 486 def test_run_submodule_with_absolute_import(self):
487 487 self.check_run_submodule('absolute')
488 488
489 489 def test_run_submodule_with_relative_import(self):
490 490 """Run submodule that has a relative import statement (#2727)."""
491 491 self.check_run_submodule('relative')
492 492
493 493 def test_prun_submodule_with_absolute_import(self):
494 494 self.check_run_submodule('absolute', '-p')
495 495
496 496 def test_prun_submodule_with_relative_import(self):
497 497 self.check_run_submodule('relative', '-p')
498 498
499 499 def with_fake_debugger(func):
500 500 @functools.wraps(func)
501 501 def wrapper(*args, **kwds):
502 502 with patch.object(debugger.Pdb, 'run', staticmethod(eval)):
503 503 return func(*args, **kwds)
504 504 return wrapper
505 505
506 506 @with_fake_debugger
507 507 def test_debug_run_submodule_with_absolute_import(self):
508 508 self.check_run_submodule('absolute', '-d')
509 509
510 510 @with_fake_debugger
511 511 def test_debug_run_submodule_with_relative_import(self):
512 512 self.check_run_submodule('relative', '-d')
513 513
514 514 def test_module_options(self):
515 515 _ip.user_ns.pop("a", None)
516 516 test_opts = "-x abc -m test"
517 517 _ip.run_line_magic("run", "-m {0}.args {1}".format(self.package, test_opts))
518 518 assert _ip.user_ns["a"] == test_opts
519 519
520 520 def test_module_options_with_separator(self):
521 521 _ip.user_ns.pop("a", None)
522 522 test_opts = "-x abc -m test"
523 523 _ip.run_line_magic("run", "-m {0}.args -- {1}".format(self.package, test_opts))
524 524 assert _ip.user_ns["a"] == test_opts
525 525
526 526
527 527 def test_run__name__():
528 528 with TemporaryDirectory() as td:
529 path = pjoin(td, 'foo.py')
530 with open(path, 'w', encoding='utf-8') as f:
529 path = pjoin(td, "foo.py")
530 with open(path, "w", encoding="utf-8") as f:
531 531 f.write("q = __name__")
532 532
533 533 _ip.user_ns.pop("q", None)
534 534 _ip.magic("run {}".format(path))
535 535 assert _ip.user_ns.pop("q") == "__main__"
536 536
537 537 _ip.magic("run -n {}".format(path))
538 538 assert _ip.user_ns.pop("q") == "foo"
539 539
540 540 try:
541 541 _ip.magic("run -i -n {}".format(path))
542 542 assert _ip.user_ns.pop("q") == "foo"
543 543 finally:
544 544 _ip.magic('reset -f')
545 545
546 546
547 547 def test_run_tb():
548 548 """Test traceback offset in %run"""
549 549 with TemporaryDirectory() as td:
550 path = pjoin(td, 'foo.py')
551 with open(path, 'w', encoding='utf-8') as f:
552 f.write('\n'.join([
553 "def foo():",
554 " return bar()",
555 "def bar():",
556 " raise RuntimeError('hello!')",
557 "foo()",
558 ]))
550 path = pjoin(td, "foo.py")
551 with open(path, "w", encoding="utf-8") as f:
552 f.write(
553 "\n".join(
554 [
555 "def foo():",
556 " return bar()",
557 "def bar():",
558 " raise RuntimeError('hello!')",
559 "foo()",
560 ]
561 )
562 )
559 563 with capture_output() as io:
560 564 _ip.magic('run {}'.format(path))
561 565 out = io.stdout
562 566 assert "execfile" not in out
563 567 assert "RuntimeError" in out
564 568 assert out.count("---->") == 3
565 569 del ip.user_ns['bar']
566 570 del ip.user_ns['foo']
567 571
568 572
569 573 def test_multiprocessing_run():
570 574 """Set we can run mutiprocesgin without messing up up main namespace
571 575
572 576 Note that import `nose.tools as nt` mdify the value s
573 577 sys.module['__mp_main__'] so we need to temporarily set it to None to test
574 578 the issue.
575 579 """
576 580 with TemporaryDirectory() as td:
577 581 mpm = sys.modules.get('__mp_main__')
578 582 sys.modules['__mp_main__'] = None
579 583 try:
580 path = pjoin(td, 'test.py')
581 with open(path, 'w', encoding='utf-8') as f:
584 path = pjoin(td, "test.py")
585 with open(path, "w", encoding="utf-8") as f:
582 586 f.write("import multiprocessing\nprint('hoy')")
583 587 with capture_output() as io:
584 588 _ip.run_line_magic('run', path)
585 589 _ip.run_cell("i_m_undefined")
586 590 out = io.stdout
587 591 assert "hoy" in out
588 592 assert "AttributeError" not in out
589 593 assert "NameError" in out
590 594 assert out.count("---->") == 1
591 595 except:
592 596 raise
593 597 finally:
594 598 sys.modules['__mp_main__'] = mpm
595 599
596 600
597 601 def test_script_tb():
598 602 """Test traceback offset in `ipython script.py`"""
599 603 with TemporaryDirectory() as td:
600 path = pjoin(td, 'foo.py')
601 with open(path, 'w', encoding='utf-8') as f:
602 f.write('\n'.join([
603 "def foo():",
604 " return bar()",
605 "def bar():",
606 " raise RuntimeError('hello!')",
607 "foo()",
608 ]))
604 path = pjoin(td, "foo.py")
605 with open(path, "w", encoding="utf-8") as f:
606 f.write(
607 "\n".join(
608 [
609 "def foo():",
610 " return bar()",
611 "def bar():",
612 " raise RuntimeError('hello!')",
613 "foo()",
614 ]
615 )
616 )
609 617 out, err = tt.ipexec(path)
610 618 assert "execfile" not in out
611 619 assert "RuntimeError" in out
612 620 assert out.count("---->") == 3
@@ -1,410 +1,410 b''
1 1 # encoding: utf-8
2 2 """Tests for IPython.core.ultratb
3 3 """
4 4 import io
5 5 import logging
6 6 import platform
7 7 import re
8 8 import sys
9 9 import os.path
10 10 from textwrap import dedent
11 11 import traceback
12 12 import unittest
13 13
14 14 from IPython.core.ultratb import ColorTB, VerboseTB
15 15
16 16
17 17 from IPython.testing import tools as tt
18 18 from IPython.testing.decorators import onlyif_unicode_paths
19 19 from IPython.utils.syspathcontext import prepended_to_syspath
20 20 from IPython.utils.tempdir import TemporaryDirectory
21 21
22 22 file_1 = """1
23 23 2
24 24 3
25 25 def f():
26 26 1/0
27 27 """
28 28
29 29 file_2 = """def f():
30 30 1/0
31 31 """
32 32
33 33
34 34 def recursionlimit(frames):
35 35 """
36 36 decorator to set the recursion limit temporarily
37 37 """
38 38
39 39 def inner(test_function):
40 40 def wrapper(*args, **kwargs):
41 41 rl = sys.getrecursionlimit()
42 42 sys.setrecursionlimit(frames)
43 43 try:
44 44 return test_function(*args, **kwargs)
45 45 finally:
46 46 sys.setrecursionlimit(rl)
47 47
48 48 return wrapper
49 49
50 50 return inner
51 51
52 52
53 53 class ChangedPyFileTest(unittest.TestCase):
54 54 def test_changing_py_file(self):
55 55 """Traceback produced if the line where the error occurred is missing?
56 56
57 57 https://github.com/ipython/ipython/issues/1456
58 58 """
59 59 with TemporaryDirectory() as td:
60 60 fname = os.path.join(td, "foo.py")
61 with open(fname, "w", encoding='utf-8') as f:
61 with open(fname, "w", encoding="utf-8") as f:
62 62 f.write(file_1)
63 63
64 64 with prepended_to_syspath(td):
65 65 ip.run_cell("import foo")
66 66
67 67 with tt.AssertPrints("ZeroDivisionError"):
68 68 ip.run_cell("foo.f()")
69 69
70 70 # Make the file shorter, so the line of the error is missing.
71 with open(fname, "w", encoding='utf-8') as f:
71 with open(fname, "w", encoding="utf-8") as f:
72 72 f.write(file_2)
73 73
74 74 # For some reason, this was failing on the *second* call after
75 75 # changing the file, so we call f() twice.
76 76 with tt.AssertNotPrints("Internal Python error", channel='stderr'):
77 77 with tt.AssertPrints("ZeroDivisionError"):
78 78 ip.run_cell("foo.f()")
79 79 with tt.AssertPrints("ZeroDivisionError"):
80 80 ip.run_cell("foo.f()")
81 81
82 82 iso_8859_5_file = u'''# coding: iso-8859-5
83 83
84 84 def fail():
85 85 """дбИЖ"""
86 86 1/0 # дбИЖ
87 87 '''
88 88
89 89 class NonAsciiTest(unittest.TestCase):
90 90 @onlyif_unicode_paths
91 91 def test_nonascii_path(self):
92 92 # Non-ascii directory name as well.
93 93 with TemporaryDirectory(suffix=u'é') as td:
94 94 fname = os.path.join(td, u"fooé.py")
95 with open(fname, "w", encoding='utf-8') as f:
95 with open(fname, "w", encoding="utf-8") as f:
96 96 f.write(file_1)
97 97
98 98 with prepended_to_syspath(td):
99 99 ip.run_cell("import foo")
100 100
101 101 with tt.AssertPrints("ZeroDivisionError"):
102 102 ip.run_cell("foo.f()")
103 103
104 104 def test_iso8859_5(self):
105 105 with TemporaryDirectory() as td:
106 106 fname = os.path.join(td, 'dfghjkl.py')
107 107
108 108 with io.open(fname, 'w', encoding='iso-8859-5') as f:
109 109 f.write(iso_8859_5_file)
110 110
111 111 with prepended_to_syspath(td):
112 112 ip.run_cell("from dfghjkl import fail")
113 113
114 114 with tt.AssertPrints("ZeroDivisionError"):
115 115 with tt.AssertPrints(u'дбИЖ', suppress=False):
116 116 ip.run_cell('fail()')
117 117
118 118 def test_nonascii_msg(self):
119 119 cell = u"raise Exception('é')"
120 120 expected = u"Exception('é')"
121 121 ip.run_cell("%xmode plain")
122 122 with tt.AssertPrints(expected):
123 123 ip.run_cell(cell)
124 124
125 125 ip.run_cell("%xmode verbose")
126 126 with tt.AssertPrints(expected):
127 127 ip.run_cell(cell)
128 128
129 129 ip.run_cell("%xmode context")
130 130 with tt.AssertPrints(expected):
131 131 ip.run_cell(cell)
132 132
133 133 ip.run_cell("%xmode minimal")
134 134 with tt.AssertPrints(u"Exception: é"):
135 135 ip.run_cell(cell)
136 136
137 137 # Put this back into Context mode for later tests.
138 138 ip.run_cell("%xmode context")
139 139
140 140 class NestedGenExprTestCase(unittest.TestCase):
141 141 """
142 142 Regression test for the following issues:
143 143 https://github.com/ipython/ipython/issues/8293
144 144 https://github.com/ipython/ipython/issues/8205
145 145 """
146 146 def test_nested_genexpr(self):
147 147 code = dedent(
148 148 """\
149 149 class SpecificException(Exception):
150 150 pass
151 151
152 152 def foo(x):
153 153 raise SpecificException("Success!")
154 154
155 155 sum(sum(foo(x) for _ in [0]) for x in [0])
156 156 """
157 157 )
158 158 with tt.AssertPrints('SpecificException: Success!', suppress=False):
159 159 ip.run_cell(code)
160 160
161 161
162 162 indentationerror_file = """if True:
163 163 zoon()
164 164 """
165 165
166 166 class IndentationErrorTest(unittest.TestCase):
167 167 def test_indentationerror_shows_line(self):
168 168 # See issue gh-2398
169 169 with tt.AssertPrints("IndentationError"):
170 170 with tt.AssertPrints("zoon()", suppress=False):
171 171 ip.run_cell(indentationerror_file)
172 172
173 173 with TemporaryDirectory() as td:
174 174 fname = os.path.join(td, "foo.py")
175 with open(fname, "w", encoding='utf-8') as f:
175 with open(fname, "w", encoding="utf-8") as f:
176 176 f.write(indentationerror_file)
177 177
178 178 with tt.AssertPrints("IndentationError"):
179 179 with tt.AssertPrints("zoon()", suppress=False):
180 180 ip.magic('run %s' % fname)
181 181
182 182 se_file_1 = """1
183 183 2
184 184 7/
185 185 """
186 186
187 187 se_file_2 = """7/
188 188 """
189 189
190 190 class SyntaxErrorTest(unittest.TestCase):
191 191
192 192 def test_syntaxerror_no_stacktrace_at_compile_time(self):
193 193 syntax_error_at_compile_time = """
194 194 def foo():
195 195 ..
196 196 """
197 197 with tt.AssertPrints("SyntaxError"):
198 198 ip.run_cell(syntax_error_at_compile_time)
199 199
200 200 with tt.AssertNotPrints("foo()"):
201 201 ip.run_cell(syntax_error_at_compile_time)
202 202
203 203 def test_syntaxerror_stacktrace_when_running_compiled_code(self):
204 204 syntax_error_at_runtime = """
205 205 def foo():
206 206 eval("..")
207 207
208 208 def bar():
209 209 foo()
210 210
211 211 bar()
212 212 """
213 213 with tt.AssertPrints("SyntaxError"):
214 214 ip.run_cell(syntax_error_at_runtime)
215 215 # Assert syntax error during runtime generate stacktrace
216 216 with tt.AssertPrints(["foo()", "bar()"]):
217 217 ip.run_cell(syntax_error_at_runtime)
218 218 del ip.user_ns['bar']
219 219 del ip.user_ns['foo']
220 220
221 221 def test_changing_py_file(self):
222 222 with TemporaryDirectory() as td:
223 223 fname = os.path.join(td, "foo.py")
224 with open(fname, 'w', encoding='utf-8') as f:
224 with open(fname, "w", encoding="utf-8") as f:
225 225 f.write(se_file_1)
226 226
227 227 with tt.AssertPrints(["7/", "SyntaxError"]):
228 228 ip.magic("run " + fname)
229 229
230 230 # Modify the file
231 with open(fname, 'w', encoding='utf-8') as f:
231 with open(fname, "w", encoding="utf-8") as f:
232 232 f.write(se_file_2)
233 233
234 234 # The SyntaxError should point to the correct line
235 235 with tt.AssertPrints(["7/", "SyntaxError"]):
236 236 ip.magic("run " + fname)
237 237
238 238 def test_non_syntaxerror(self):
239 239 # SyntaxTB may be called with an error other than a SyntaxError
240 240 # See e.g. gh-4361
241 241 try:
242 242 raise ValueError('QWERTY')
243 243 except ValueError:
244 244 with tt.AssertPrints('QWERTY'):
245 245 ip.showsyntaxerror()
246 246
247 247 import sys
248 248
249 249 if sys.version_info < (3, 9) and platform.python_implementation() != "PyPy":
250 250 """
251 251 New 3.9 Pgen Parser does not raise Memory error, except on failed malloc.
252 252 """
253 253 class MemoryErrorTest(unittest.TestCase):
254 254 def test_memoryerror(self):
255 255 memoryerror_code = "(" * 200 + ")" * 200
256 256 with tt.AssertPrints("MemoryError"):
257 257 ip.run_cell(memoryerror_code)
258 258
259 259
260 260 class Python3ChainedExceptionsTest(unittest.TestCase):
261 261 DIRECT_CAUSE_ERROR_CODE = """
262 262 try:
263 263 x = 1 + 2
264 264 print(not_defined_here)
265 265 except Exception as e:
266 266 x += 55
267 267 x - 1
268 268 y = {}
269 269 raise KeyError('uh') from e
270 270 """
271 271
272 272 EXCEPTION_DURING_HANDLING_CODE = """
273 273 try:
274 274 x = 1 + 2
275 275 print(not_defined_here)
276 276 except Exception as e:
277 277 x += 55
278 278 x - 1
279 279 y = {}
280 280 raise KeyError('uh')
281 281 """
282 282
283 283 SUPPRESS_CHAINING_CODE = """
284 284 try:
285 285 1/0
286 286 except Exception:
287 287 raise ValueError("Yikes") from None
288 288 """
289 289
290 290 def test_direct_cause_error(self):
291 291 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
292 292 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
293 293
294 294 def test_exception_during_handling_error(self):
295 295 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
296 296 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
297 297
298 298 def test_suppress_exception_chaining(self):
299 299 with tt.AssertNotPrints("ZeroDivisionError"), \
300 300 tt.AssertPrints("ValueError", suppress=False):
301 301 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
302 302
303 303 def test_plain_direct_cause_error(self):
304 304 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
305 305 ip.run_cell("%xmode Plain")
306 306 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
307 307 ip.run_cell("%xmode Verbose")
308 308
309 309 def test_plain_exception_during_handling_error(self):
310 310 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
311 311 ip.run_cell("%xmode Plain")
312 312 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
313 313 ip.run_cell("%xmode Verbose")
314 314
315 315 def test_plain_suppress_exception_chaining(self):
316 316 with tt.AssertNotPrints("ZeroDivisionError"), \
317 317 tt.AssertPrints("ValueError", suppress=False):
318 318 ip.run_cell("%xmode Plain")
319 319 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
320 320 ip.run_cell("%xmode Verbose")
321 321
322 322
323 323 class RecursionTest(unittest.TestCase):
324 324 DEFINITIONS = """
325 325 def non_recurs():
326 326 1/0
327 327
328 328 def r1():
329 329 r1()
330 330
331 331 def r3a():
332 332 r3b()
333 333
334 334 def r3b():
335 335 r3c()
336 336
337 337 def r3c():
338 338 r3a()
339 339
340 340 def r3o1():
341 341 r3a()
342 342
343 343 def r3o2():
344 344 r3o1()
345 345 """
346 346 def setUp(self):
347 347 ip.run_cell(self.DEFINITIONS)
348 348
349 349 def test_no_recursion(self):
350 350 with tt.AssertNotPrints("skipping similar frames"):
351 351 ip.run_cell("non_recurs()")
352 352
353 353 @recursionlimit(200)
354 354 def test_recursion_one_frame(self):
355 355 with tt.AssertPrints(re.compile(
356 356 r"\[\.\.\. skipping similar frames: r1 at line 5 \(\d{2,3} times\)\]")
357 357 ):
358 358 ip.run_cell("r1()")
359 359
360 360 @recursionlimit(160)
361 361 def test_recursion_three_frames(self):
362 362 with tt.AssertPrints("[... skipping similar frames: "), \
363 363 tt.AssertPrints(re.compile(r"r3a at line 8 \(\d{2} times\)"), suppress=False), \
364 364 tt.AssertPrints(re.compile(r"r3b at line 11 \(\d{2} times\)"), suppress=False), \
365 365 tt.AssertPrints(re.compile(r"r3c at line 14 \(\d{2} times\)"), suppress=False):
366 366 ip.run_cell("r3o2()")
367 367
368 368
369 369 #----------------------------------------------------------------------------
370 370
371 371 # module testing (minimal)
372 372 def test_handlers():
373 373 def spam(c, d_e):
374 374 (d, e) = d_e
375 375 x = c + d
376 376 y = c * d
377 377 foo(x, y)
378 378
379 379 def foo(a, b, bar=1):
380 380 eggs(a, b + bar)
381 381
382 382 def eggs(f, g, z=globals()):
383 383 h = f + g
384 384 i = f - g
385 385 return h / i
386 386
387 387 buff = io.StringIO()
388 388
389 389 buff.write('')
390 390 buff.write('*** Before ***')
391 391 try:
392 392 buff.write(spam(1, (2, 3)))
393 393 except:
394 394 traceback.print_exc(file=buff)
395 395
396 396 handler = ColorTB(ostream=buff)
397 397 buff.write('*** ColorTB ***')
398 398 try:
399 399 buff.write(spam(1, (2, 3)))
400 400 except:
401 401 handler(*sys.exc_info())
402 402 buff.write('')
403 403
404 404 handler = VerboseTB(ostream=buff)
405 405 buff.write('*** VerboseTB ***')
406 406 try:
407 407 buff.write(spam(1, (2, 3)))
408 408 except:
409 409 handler(*sys.exc_info())
410 410 buff.write('')
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now