##// END OF EJS Templates
try ruff to find unused imports
Matthias Bussonnier -
Show More
@@ -1,156 +1,155 b''
1 1 """
2 2 IPython: tools for interactive and parallel computing in Python.
3 3
4 4 https://ipython.org
5 5 """
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (c) 2008-2011, IPython Development Team.
8 8 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
9 9 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
10 10 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
11 11 #
12 12 # Distributed under the terms of the Modified BSD License.
13 13 #
14 14 # The full license is in the file COPYING.txt, distributed with this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 import os
22 21 import sys
23 22
24 23 #-----------------------------------------------------------------------------
25 24 # Setup everything
26 25 #-----------------------------------------------------------------------------
27 26
28 27 # Don't forget to also update setup.py when this changes!
29 28 if sys.version_info < (3, 8):
30 29 raise ImportError(
31 30 """
32 31 IPython 8+ supports Python 3.8 and above, following NEP 29.
33 32 When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
34 33 Python 3.3 and 3.4 were supported up to IPython 6.x.
35 34 Python 3.5 was supported with IPython 7.0 to 7.9.
36 35 Python 3.6 was supported with IPython up to 7.16.
37 36 Python 3.7 was still supported with the 7.x branch.
38 37
39 38 See IPython `README.rst` file for more information:
40 39
41 40 https://github.com/ipython/ipython/blob/main/README.rst
42 41
43 42 """
44 43 )
45 44
46 45 #-----------------------------------------------------------------------------
47 46 # Setup the top level names
48 47 #-----------------------------------------------------------------------------
49 48
50 49 from .core.getipython import get_ipython
51 50 from .core import release
52 51 from .core.application import Application
53 52 from .terminal.embed import embed
54 53
55 54 from .core.interactiveshell import InteractiveShell
56 55 from .utils.sysinfo import sys_info
57 56 from .utils.frame import extract_module_locals
58 57
59 58 # Release data
60 59 __author__ = '%s <%s>' % (release.author, release.author_email)
61 60 __license__ = release.license
62 61 __version__ = release.version
63 62 version_info = release.version_info
64 63 # list of CVEs that should have been patched in this release.
65 64 # this is informational and should not be relied upon.
66 65 __patched_cves__ = {"CVE-2022-21699"}
67 66
68 67
69 68 def embed_kernel(module=None, local_ns=None, **kwargs):
70 69 """Embed and start an IPython kernel in a given scope.
71 70
72 71 If you don't want the kernel to initialize the namespace
73 72 from the scope of the surrounding function,
74 73 and/or you want to load full IPython configuration,
75 74 you probably want `IPython.start_kernel()` instead.
76 75
77 76 Parameters
78 77 ----------
79 78 module : types.ModuleType, optional
80 79 The module to load into IPython globals (default: caller)
81 80 local_ns : dict, optional
82 81 The namespace to load into IPython user namespace (default: caller)
83 82 **kwargs : various, optional
84 83 Further keyword args are relayed to the IPKernelApp constructor,
85 84 allowing configuration of the Kernel. Will only have an effect
86 85 on the first embed_kernel call for a given process.
87 86 """
88 87
89 88 (caller_module, caller_locals) = extract_module_locals(1)
90 89 if module is None:
91 90 module = caller_module
92 91 if local_ns is None:
93 92 local_ns = caller_locals
94 93
95 94 # Only import .zmq when we really need it
96 95 from ipykernel.embed import embed_kernel as real_embed_kernel
97 96 real_embed_kernel(module=module, local_ns=local_ns, **kwargs)
98 97
99 98 def start_ipython(argv=None, **kwargs):
100 99 """Launch a normal IPython instance (as opposed to embedded)
101 100
102 101 `IPython.embed()` puts a shell in a particular calling scope,
103 102 such as a function or method for debugging purposes,
104 103 which is often not desirable.
105 104
106 105 `start_ipython()` does full, regular IPython initialization,
107 106 including loading startup files, configuration, etc.
108 107 much of which is skipped by `embed()`.
109 108
110 109 This is a public API method, and will survive implementation changes.
111 110
112 111 Parameters
113 112 ----------
114 113 argv : list or None, optional
115 114 If unspecified or None, IPython will parse command-line options from sys.argv.
116 115 To prevent any command-line parsing, pass an empty list: `argv=[]`.
117 116 user_ns : dict, optional
118 117 specify this dictionary to initialize the IPython user namespace with particular values.
119 118 **kwargs : various, optional
120 119 Any other kwargs will be passed to the Application constructor,
121 120 such as `config`.
122 121 """
123 122 from IPython.terminal.ipapp import launch_new_instance
124 123 return launch_new_instance(argv=argv, **kwargs)
125 124
126 125 def start_kernel(argv=None, **kwargs):
127 126 """Launch a normal IPython kernel instance (as opposed to embedded)
128 127
129 128 `IPython.embed_kernel()` puts a shell in a particular calling scope,
130 129 such as a function or method for debugging purposes,
131 130 which is often not desirable.
132 131
133 132 `start_kernel()` does full, regular IPython initialization,
134 133 including loading startup files, configuration, etc.
135 134 much of which is skipped by `embed()`.
136 135
137 136 Parameters
138 137 ----------
139 138 argv : list or None, optional
140 139 If unspecified or None, IPython will parse command-line options from sys.argv.
141 140 To prevent any command-line parsing, pass an empty list: `argv=[]`.
142 141 user_ns : dict, optional
143 142 specify this dictionary to initialize the IPython user namespace with particular values.
144 143 **kwargs : various, optional
145 144 Any other kwargs will be passed to the Application constructor,
146 145 such as `config`.
147 146 """
148 147 import warnings
149 148
150 149 warnings.warn(
151 150 "start_kernel is deprecated since IPython 8.0, use from `ipykernel.kernelapp.launch_new_instance`",
152 151 DeprecationWarning,
153 152 stacklevel=2,
154 153 )
155 154 from ipykernel.kernelapp import launch_new_instance
156 155 return launch_new_instance(argv=argv, **kwargs)
@@ -1,490 +1,489 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 import glob
18 17 import logging
19 18 import os
20 19 import shutil
21 20 import sys
22 21
23 22 from pathlib import Path
24 23
25 24 from traitlets.config.application import Application, catch_config_error
26 25 from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader
27 26 from IPython.core import release, crashhandler
28 27 from IPython.core.profiledir import ProfileDir, ProfileDirError
29 28 from IPython.paths import get_ipython_dir, get_ipython_package_dir
30 29 from IPython.utils.path import ensure_dir_exists
31 30 from traitlets import (
32 31 List, Unicode, Type, Bool, Set, Instance, Undefined,
33 32 default, observe,
34 33 )
35 34
36 35 if os.name == "nt":
37 36 programdata = os.environ.get("PROGRAMDATA", None)
38 37 if programdata is not None:
39 38 SYSTEM_CONFIG_DIRS = [str(Path(programdata) / "ipython")]
40 39 else: # PROGRAMDATA is not defined by default on XP.
41 40 SYSTEM_CONFIG_DIRS = []
42 41 else:
43 42 SYSTEM_CONFIG_DIRS = [
44 43 "/usr/local/etc/ipython",
45 44 "/etc/ipython",
46 45 ]
47 46
48 47
49 48 ENV_CONFIG_DIRS = []
50 49 _env_config_dir = os.path.join(sys.prefix, 'etc', 'ipython')
51 50 if _env_config_dir not in SYSTEM_CONFIG_DIRS:
52 51 # only add ENV_CONFIG if sys.prefix is not already included
53 52 ENV_CONFIG_DIRS.append(_env_config_dir)
54 53
55 54
56 55 _envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS')
57 56 if _envvar in {None, ''}:
58 57 IPYTHON_SUPPRESS_CONFIG_ERRORS = None
59 58 else:
60 59 if _envvar.lower() in {'1','true'}:
61 60 IPYTHON_SUPPRESS_CONFIG_ERRORS = True
62 61 elif _envvar.lower() in {'0','false'} :
63 62 IPYTHON_SUPPRESS_CONFIG_ERRORS = False
64 63 else:
65 64 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 65
67 66 # aliases and flags
68 67
69 68 base_aliases = {}
70 69 if isinstance(Application.aliases, dict):
71 70 # traitlets 5
72 71 base_aliases.update(Application.aliases)
73 72 base_aliases.update(
74 73 {
75 74 "profile-dir": "ProfileDir.location",
76 75 "profile": "BaseIPythonApplication.profile",
77 76 "ipython-dir": "BaseIPythonApplication.ipython_dir",
78 77 "log-level": "Application.log_level",
79 78 "config": "BaseIPythonApplication.extra_config_file",
80 79 }
81 80 )
82 81
83 82 base_flags = dict()
84 83 if isinstance(Application.flags, dict):
85 84 # traitlets 5
86 85 base_flags.update(Application.flags)
87 86 base_flags.update(
88 87 dict(
89 88 debug=(
90 89 {"Application": {"log_level": logging.DEBUG}},
91 90 "set log level to logging.DEBUG (maximize logging output)",
92 91 ),
93 92 quiet=(
94 93 {"Application": {"log_level": logging.CRITICAL}},
95 94 "set log level to logging.CRITICAL (minimize logging output)",
96 95 ),
97 96 init=(
98 97 {
99 98 "BaseIPythonApplication": {
100 99 "copy_config_files": True,
101 100 "auto_create": True,
102 101 }
103 102 },
104 103 """Initialize profile with default config files. This is equivalent
105 104 to running `ipython profile create <profile>` prior to startup.
106 105 """,
107 106 ),
108 107 )
109 108 )
110 109
111 110
112 111 class ProfileAwareConfigLoader(PyFileConfigLoader):
113 112 """A Python file config loader that is aware of IPython profiles."""
114 113 def load_subconfig(self, fname, path=None, profile=None):
115 114 if profile is not None:
116 115 try:
117 116 profile_dir = ProfileDir.find_profile_dir_by_name(
118 117 get_ipython_dir(),
119 118 profile,
120 119 )
121 120 except ProfileDirError:
122 121 return
123 122 path = profile_dir.location
124 123 return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path)
125 124
126 125 class BaseIPythonApplication(Application):
127 126
128 127 name = u'ipython'
129 128 description = Unicode(u'IPython: an enhanced interactive Python shell.')
130 129 version = Unicode(release.version)
131 130
132 131 aliases = base_aliases
133 132 flags = base_flags
134 133 classes = List([ProfileDir])
135 134
136 135 # enable `load_subconfig('cfg.py', profile='name')`
137 136 python_config_loader_class = ProfileAwareConfigLoader
138 137
139 138 # Track whether the config_file has changed,
140 139 # because some logic happens only if we aren't using the default.
141 140 config_file_specified = Set()
142 141
143 142 config_file_name = Unicode()
144 143 @default('config_file_name')
145 144 def _config_file_name_default(self):
146 145 return self.name.replace('-','_') + u'_config.py'
147 146 @observe('config_file_name')
148 147 def _config_file_name_changed(self, change):
149 148 if change['new'] != change['old']:
150 149 self.config_file_specified.add(change['new'])
151 150
152 151 # The directory that contains IPython's builtin profiles.
153 152 builtin_profile_dir = Unicode(
154 153 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
155 154 )
156 155
157 156 config_file_paths = List(Unicode())
158 157 @default('config_file_paths')
159 158 def _config_file_paths_default(self):
160 159 return []
161 160
162 161 extra_config_file = Unicode(
163 162 help="""Path to an extra config file to load.
164 163
165 164 If specified, load this config file in addition to any other IPython config.
166 165 """).tag(config=True)
167 166 @observe('extra_config_file')
168 167 def _extra_config_file_changed(self, change):
169 168 old = change['old']
170 169 new = change['new']
171 170 try:
172 171 self.config_files.remove(old)
173 172 except ValueError:
174 173 pass
175 174 self.config_file_specified.add(new)
176 175 self.config_files.append(new)
177 176
178 177 profile = Unicode(u'default',
179 178 help="""The IPython profile to use."""
180 179 ).tag(config=True)
181 180
182 181 @observe('profile')
183 182 def _profile_changed(self, change):
184 183 self.builtin_profile_dir = os.path.join(
185 184 get_ipython_package_dir(), u'config', u'profile', change['new']
186 185 )
187 186
188 187 add_ipython_dir_to_sys_path = Bool(
189 188 False,
190 189 """Should the IPython profile directory be added to sys path ?
191 190
192 191 This option was non-existing before IPython 8.0, and ipython_dir was added to
193 192 sys path to allow import of extensions present there. This was historical
194 193 baggage from when pip did not exist. This now default to false,
195 194 but can be set to true for legacy reasons.
196 195 """,
197 196 ).tag(config=True)
198 197
199 198 ipython_dir = Unicode(
200 199 help="""
201 200 The name of the IPython directory. This directory is used for logging
202 201 configuration (through profiles), history storage, etc. The default
203 202 is usually $HOME/.ipython. This option can also be specified through
204 203 the environment variable IPYTHONDIR.
205 204 """
206 205 ).tag(config=True)
207 206 @default('ipython_dir')
208 207 def _ipython_dir_default(self):
209 208 d = get_ipython_dir()
210 209 self._ipython_dir_changed({
211 210 'name': 'ipython_dir',
212 211 'old': d,
213 212 'new': d,
214 213 })
215 214 return d
216 215
217 216 _in_init_profile_dir = False
218 217 profile_dir = Instance(ProfileDir, allow_none=True)
219 218 @default('profile_dir')
220 219 def _profile_dir_default(self):
221 220 # avoid recursion
222 221 if self._in_init_profile_dir:
223 222 return
224 223 # profile_dir requested early, force initialization
225 224 self.init_profile_dir()
226 225 return self.profile_dir
227 226
228 227 overwrite = Bool(False,
229 228 help="""Whether to overwrite existing config files when copying"""
230 229 ).tag(config=True)
231 230 auto_create = Bool(False,
232 231 help="""Whether to create profile dir if it doesn't exist"""
233 232 ).tag(config=True)
234 233
235 234 config_files = List(Unicode())
236 235 @default('config_files')
237 236 def _config_files_default(self):
238 237 return [self.config_file_name]
239 238
240 239 copy_config_files = Bool(False,
241 240 help="""Whether to install the default config files into the profile dir.
242 241 If a new profile is being created, and IPython contains config files for that
243 242 profile, then they will be staged into the new directory. Otherwise,
244 243 default config files will be automatically generated.
245 244 """).tag(config=True)
246 245
247 246 verbose_crash = Bool(False,
248 247 help="""Create a massive crash report when IPython encounters what may be an
249 248 internal error. The default is to append a short message to the
250 249 usual traceback""").tag(config=True)
251 250
252 251 # The class to use as the crash handler.
253 252 crash_handler_class = Type(crashhandler.CrashHandler)
254 253
255 254 @catch_config_error
256 255 def __init__(self, **kwargs):
257 256 super(BaseIPythonApplication, self).__init__(**kwargs)
258 257 # ensure current working directory exists
259 258 try:
260 259 os.getcwd()
261 260 except:
262 261 # exit if cwd doesn't exist
263 262 self.log.error("Current working directory doesn't exist.")
264 263 self.exit(1)
265 264
266 265 #-------------------------------------------------------------------------
267 266 # Various stages of Application creation
268 267 #-------------------------------------------------------------------------
269 268
270 269 def init_crash_handler(self):
271 270 """Create a crash handler, typically setting sys.excepthook to it."""
272 271 self.crash_handler = self.crash_handler_class(self)
273 272 sys.excepthook = self.excepthook
274 273 def unset_crashhandler():
275 274 sys.excepthook = sys.__excepthook__
276 275 atexit.register(unset_crashhandler)
277 276
278 277 def excepthook(self, etype, evalue, tb):
279 278 """this is sys.excepthook after init_crashhandler
280 279
281 280 set self.verbose_crash=True to use our full crashhandler, instead of
282 281 a regular traceback with a short message (crash_handler_lite)
283 282 """
284 283
285 284 if self.verbose_crash:
286 285 return self.crash_handler(etype, evalue, tb)
287 286 else:
288 287 return crashhandler.crash_handler_lite(etype, evalue, tb)
289 288
290 289 @observe('ipython_dir')
291 290 def _ipython_dir_changed(self, change):
292 291 old = change['old']
293 292 new = change['new']
294 293 if old is not Undefined:
295 294 str_old = os.path.abspath(old)
296 295 if str_old in sys.path:
297 296 sys.path.remove(str_old)
298 297 if self.add_ipython_dir_to_sys_path:
299 298 str_path = os.path.abspath(new)
300 299 sys.path.append(str_path)
301 300 ensure_dir_exists(new)
302 301 readme = os.path.join(new, "README")
303 302 readme_src = os.path.join(
304 303 get_ipython_package_dir(), "config", "profile", "README"
305 304 )
306 305 if not os.path.exists(readme) and os.path.exists(readme_src):
307 306 shutil.copy(readme_src, readme)
308 307 for d in ("extensions", "nbextensions"):
309 308 path = os.path.join(new, d)
310 309 try:
311 310 ensure_dir_exists(path)
312 311 except OSError as e:
313 312 # this will not be EEXIST
314 313 self.log.error("couldn't create path %s: %s", path, e)
315 314 self.log.debug("IPYTHONDIR set to: %s" % new)
316 315
317 316 def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS):
318 317 """Load the config file.
319 318
320 319 By default, errors in loading config are handled, and a warning
321 320 printed on screen. For testing, the suppress_errors option is set
322 321 to False, so errors will make tests fail.
323 322
324 323 `suppress_errors` default value is to be `None` in which case the
325 324 behavior default to the one of `traitlets.Application`.
326 325
327 326 The default value can be set :
328 327 - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive).
329 328 - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive).
330 329 - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset.
331 330
332 331 Any other value are invalid, and will make IPython exit with a non-zero return code.
333 332 """
334 333
335 334
336 335 self.log.debug("Searching path %s for config files", self.config_file_paths)
337 336 base_config = 'ipython_config.py'
338 337 self.log.debug("Attempting to load config file: %s" %
339 338 base_config)
340 339 try:
341 340 if suppress_errors is not None:
342 341 old_value = Application.raise_config_file_errors
343 342 Application.raise_config_file_errors = not suppress_errors;
344 343 Application.load_config_file(
345 344 self,
346 345 base_config,
347 346 path=self.config_file_paths
348 347 )
349 348 except ConfigFileNotFound:
350 349 # ignore errors loading parent
351 350 self.log.debug("Config file %s not found", base_config)
352 351 pass
353 352 if suppress_errors is not None:
354 353 Application.raise_config_file_errors = old_value
355 354
356 355 for config_file_name in self.config_files:
357 356 if not config_file_name or config_file_name == base_config:
358 357 continue
359 358 self.log.debug("Attempting to load config file: %s" %
360 359 self.config_file_name)
361 360 try:
362 361 Application.load_config_file(
363 362 self,
364 363 config_file_name,
365 364 path=self.config_file_paths
366 365 )
367 366 except ConfigFileNotFound:
368 367 # Only warn if the default config file was NOT being used.
369 368 if config_file_name in self.config_file_specified:
370 369 msg = self.log.warning
371 370 else:
372 371 msg = self.log.debug
373 372 msg("Config file not found, skipping: %s", config_file_name)
374 373 except Exception:
375 374 # For testing purposes.
376 375 if not suppress_errors:
377 376 raise
378 377 self.log.warning("Error loading config file: %s" %
379 378 self.config_file_name, exc_info=True)
380 379
381 380 def init_profile_dir(self):
382 381 """initialize the profile dir"""
383 382 self._in_init_profile_dir = True
384 383 if self.profile_dir is not None:
385 384 # already ran
386 385 return
387 386 if 'ProfileDir.location' not in self.config:
388 387 # location not specified, find by profile name
389 388 try:
390 389 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
391 390 except ProfileDirError:
392 391 # not found, maybe create it (always create default profile)
393 392 if self.auto_create or self.profile == 'default':
394 393 try:
395 394 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
396 395 except ProfileDirError:
397 396 self.log.fatal("Could not create profile: %r"%self.profile)
398 397 self.exit(1)
399 398 else:
400 399 self.log.info("Created profile dir: %r"%p.location)
401 400 else:
402 401 self.log.fatal("Profile %r not found."%self.profile)
403 402 self.exit(1)
404 403 else:
405 404 self.log.debug(f"Using existing profile dir: {p.location!r}")
406 405 else:
407 406 location = self.config.ProfileDir.location
408 407 # location is fully specified
409 408 try:
410 409 p = ProfileDir.find_profile_dir(location, self.config)
411 410 except ProfileDirError:
412 411 # not found, maybe create it
413 412 if self.auto_create:
414 413 try:
415 414 p = ProfileDir.create_profile_dir(location, self.config)
416 415 except ProfileDirError:
417 416 self.log.fatal("Could not create profile directory: %r"%location)
418 417 self.exit(1)
419 418 else:
420 419 self.log.debug("Creating new profile dir: %r"%location)
421 420 else:
422 421 self.log.fatal("Profile directory %r not found."%location)
423 422 self.exit(1)
424 423 else:
425 424 self.log.debug(f"Using existing profile dir: {p.location!r}")
426 425 # if profile_dir is specified explicitly, set profile name
427 426 dir_name = os.path.basename(p.location)
428 427 if dir_name.startswith('profile_'):
429 428 self.profile = dir_name[8:]
430 429
431 430 self.profile_dir = p
432 431 self.config_file_paths.append(p.location)
433 432 self._in_init_profile_dir = False
434 433
435 434 def init_config_files(self):
436 435 """[optionally] copy default config files into profile dir."""
437 436 self.config_file_paths.extend(ENV_CONFIG_DIRS)
438 437 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
439 438 # copy config files
440 439 path = Path(self.builtin_profile_dir)
441 440 if self.copy_config_files:
442 441 src = self.profile
443 442
444 443 cfg = self.config_file_name
445 444 if path and (path / cfg).exists():
446 445 self.log.warning(
447 446 "Staging %r from %s into %r [overwrite=%s]"
448 447 % (cfg, src, self.profile_dir.location, self.overwrite)
449 448 )
450 449 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
451 450 else:
452 451 self.stage_default_config_file()
453 452 else:
454 453 # Still stage *bundled* config files, but not generated ones
455 454 # This is necessary for `ipython profile=sympy` to load the profile
456 455 # on the first go
457 456 files = path.glob("*.py")
458 457 for fullpath in files:
459 458 cfg = fullpath.name
460 459 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
461 460 # file was copied
462 461 self.log.warning("Staging bundled %s from %s into %r"%(
463 462 cfg, self.profile, self.profile_dir.location)
464 463 )
465 464
466 465
467 466 def stage_default_config_file(self):
468 467 """auto generate default config file, and stage it into the profile."""
469 468 s = self.generate_config_file()
470 469 config_file = Path(self.profile_dir.location) / self.config_file_name
471 470 if self.overwrite or not config_file.exists():
472 471 self.log.warning("Generating default config file: %r" % (config_file))
473 472 config_file.write_text(s, encoding="utf-8")
474 473
475 474 @catch_config_error
476 475 def initialize(self, argv=None):
477 476 # don't hook up crash handler before parsing command-line
478 477 self.parse_command_line(argv)
479 478 self.init_crash_handler()
480 479 if self.subapp is not None:
481 480 # stop here if subapp is taking over
482 481 return
483 482 # save a copy of CLI config to re-load after config files
484 483 # so that it has highest priority
485 484 cl_config = deepcopy(self.config)
486 485 self.init_profile_dir()
487 486 self.init_config_files()
488 487 self.load_config_file()
489 488 # enforce cl-opts override configfile opts:
490 489 self.update_config(cl_config)
@@ -1,237 +1,236 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 import os
23 22 import sys
24 23 import traceback
25 24 from pprint import pformat
26 25 from pathlib import Path
27 26
28 27 from IPython.core import ultratb
29 28 from IPython.core.release import author_email
30 29 from IPython.utils.sysinfo import sys_info
31 30 from IPython.utils.py3compat import input
32 31
33 32 from IPython.core.release import __version__ as version
34 33
35 34 from typing import Optional
36 35
37 36 #-----------------------------------------------------------------------------
38 37 # Code
39 38 #-----------------------------------------------------------------------------
40 39
41 40 # Template for the user message.
42 41 _default_message_template = """\
43 42 Oops, {app_name} crashed. We do our best to make it stable, but...
44 43
45 44 A crash report was automatically generated with the following information:
46 45 - A verbatim copy of the crash traceback.
47 46 - A copy of your input history during this session.
48 47 - Data on your current {app_name} configuration.
49 48
50 49 It was left in the file named:
51 50 \t'{crash_report_fname}'
52 51 If you can email this file to the developers, the information in it will help
53 52 them in understanding and correcting the problem.
54 53
55 54 You can mail it to: {contact_name} at {contact_email}
56 55 with the subject '{app_name} Crash Report'.
57 56
58 57 If you want to do it now, the following command will work (under Unix):
59 58 mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname}
60 59
61 60 In your email, please also include information about:
62 61 - The operating system under which the crash happened: Linux, macOS, Windows,
63 62 other, and which exact version (for example: Ubuntu 16.04.3, macOS 10.13.2,
64 63 Windows 10 Pro), and whether it is 32-bit or 64-bit;
65 64 - How {app_name} was installed: using pip or conda, from GitHub, as part of
66 65 a Docker container, or other, providing more detail if possible;
67 66 - How to reproduce the crash: what exact sequence of instructions can one
68 67 input to get the same crash? Ideally, find a minimal yet complete sequence
69 68 of instructions that yields the crash.
70 69
71 70 To ensure accurate tracking of this issue, please file a report about it at:
72 71 {bug_tracker}
73 72 """
74 73
75 74 _lite_message_template = """
76 75 If you suspect this is an IPython {version} bug, please report it at:
77 76 https://github.com/ipython/ipython/issues
78 77 or send an email to the mailing list at {email}
79 78
80 79 You can print a more detailed traceback right now with "%tb", or use "%debug"
81 80 to interactively debug it.
82 81
83 82 Extra-detailed tracebacks for bug-reporting purposes can be enabled via:
84 83 {config}Application.verbose_crash=True
85 84 """
86 85
87 86
88 87 class CrashHandler(object):
89 88 """Customizable crash handlers for IPython applications.
90 89
91 90 Instances of this class provide a :meth:`__call__` method which can be
92 91 used as a ``sys.excepthook``. The :meth:`__call__` signature is::
93 92
94 93 def __call__(self, etype, evalue, etb)
95 94 """
96 95
97 96 message_template = _default_message_template
98 97 section_sep = '\n\n'+'*'*75+'\n\n'
99 98
100 99 def __init__(
101 100 self,
102 101 app,
103 102 contact_name: Optional[str] = None,
104 103 contact_email: Optional[str] = None,
105 104 bug_tracker: Optional[str] = None,
106 105 show_crash_traceback: bool = True,
107 106 call_pdb: bool = False,
108 107 ):
109 108 """Create a new crash handler
110 109
111 110 Parameters
112 111 ----------
113 112 app : Application
114 113 A running :class:`Application` instance, which will be queried at
115 114 crash time for internal information.
116 115 contact_name : str
117 116 A string with the name of the person to contact.
118 117 contact_email : str
119 118 A string with the email address of the contact.
120 119 bug_tracker : str
121 120 A string with the URL for your project's bug tracker.
122 121 show_crash_traceback : bool
123 122 If false, don't print the crash traceback on stderr, only generate
124 123 the on-disk report
125 124 call_pdb
126 125 Whether to call pdb on crash
127 126
128 127 Attributes
129 128 ----------
130 129 These instances contain some non-argument attributes which allow for
131 130 further customization of the crash handler's behavior. Please see the
132 131 source for further details.
133 132
134 133 """
135 134 self.crash_report_fname = "Crash_report_%s.txt" % app.name
136 135 self.app = app
137 136 self.call_pdb = call_pdb
138 137 #self.call_pdb = True # dbg
139 138 self.show_crash_traceback = show_crash_traceback
140 139 self.info = dict(app_name = app.name,
141 140 contact_name = contact_name,
142 141 contact_email = contact_email,
143 142 bug_tracker = bug_tracker,
144 143 crash_report_fname = self.crash_report_fname)
145 144
146 145
147 146 def __call__(self, etype, evalue, etb):
148 147 """Handle an exception, call for compatible with sys.excepthook"""
149 148
150 149 # do not allow the crash handler to be called twice without reinstalling it
151 150 # this prevents unlikely errors in the crash handling from entering an
152 151 # infinite loop.
153 152 sys.excepthook = sys.__excepthook__
154 153
155 154 # Report tracebacks shouldn't use color in general (safer for users)
156 155 color_scheme = 'NoColor'
157 156
158 157 # Use this ONLY for developer debugging (keep commented out for release)
159 158 #color_scheme = 'Linux' # dbg
160 159 try:
161 160 rptdir = self.app.ipython_dir
162 161 except:
163 162 rptdir = Path.cwd()
164 163 if rptdir is None or not Path.is_dir(rptdir):
165 164 rptdir = Path.cwd()
166 165 report_name = rptdir / self.crash_report_fname
167 166 # write the report filename into the instance dict so it can get
168 167 # properly expanded out in the user message template
169 168 self.crash_report_fname = report_name
170 169 self.info['crash_report_fname'] = report_name
171 170 TBhandler = ultratb.VerboseTB(
172 171 color_scheme=color_scheme,
173 172 long_header=1,
174 173 call_pdb=self.call_pdb,
175 174 )
176 175 if self.call_pdb:
177 176 TBhandler(etype,evalue,etb)
178 177 return
179 178 else:
180 179 traceback = TBhandler.text(etype,evalue,etb,context=31)
181 180
182 181 # print traceback to screen
183 182 if self.show_crash_traceback:
184 183 print(traceback, file=sys.stderr)
185 184
186 185 # and generate a complete report on disk
187 186 try:
188 187 report = open(report_name, "w", encoding="utf-8")
189 188 except:
190 189 print('Could not create crash report on disk.', file=sys.stderr)
191 190 return
192 191
193 192 with report:
194 193 # Inform user on stderr of what happened
195 194 print('\n'+'*'*70+'\n', file=sys.stderr)
196 195 print(self.message_template.format(**self.info), file=sys.stderr)
197 196
198 197 # Construct report on disk
199 198 report.write(self.make_report(traceback))
200 199
201 200 input("Hit <Enter> to quit (your terminal may close):")
202 201
203 202 def make_report(self,traceback):
204 203 """Return a string containing a crash report."""
205 204
206 205 sec_sep = self.section_sep
207 206
208 207 report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
209 208 rpt_add = report.append
210 209 rpt_add(sys_info())
211 210
212 211 try:
213 212 config = pformat(self.app.config)
214 213 rpt_add(sec_sep)
215 214 rpt_add('Application name: %s\n\n' % self.app_name)
216 215 rpt_add('Current user configuration structure:\n\n')
217 216 rpt_add(config)
218 217 except:
219 218 pass
220 219 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
221 220
222 221 return ''.join(report)
223 222
224 223
225 224 def crash_handler_lite(etype, evalue, tb):
226 225 """a light excepthook, adding a small message to the usual traceback"""
227 226 traceback.print_exception(etype, evalue, tb)
228 227
229 228 from IPython.core.interactiveshell import InteractiveShell
230 229 if InteractiveShell.initialized():
231 230 # we are in a Shell environment, give %magic example
232 231 config = "%config "
233 232 else:
234 233 # we are not in a shell, show generic config
235 234 config = "c."
236 235 print(_lite_message_template.format(email=author_email, config=config, version=version), file=sys.stderr)
237 236
@@ -1,999 +1,998 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 Pdb debugger class.
4 4
5 5
6 6 This is an extension to PDB which adds a number of new features.
7 7 Note that there is also the `IPython.terminal.debugger` class which provides UI
8 8 improvements.
9 9
10 10 We also strongly recommend to use this via the `ipdb` package, which provides
11 11 extra configuration options.
12 12
13 13 Among other things, this subclass of PDB:
14 14 - supports many IPython magics like pdef/psource
15 15 - hide frames in tracebacks based on `__tracebackhide__`
16 16 - allows to skip frames based on `__debuggerskip__`
17 17
18 18 The skipping and hiding frames are configurable via the `skip_predicates`
19 19 command.
20 20
21 21 By default, frames from readonly files will be hidden, frames containing
22 22 ``__tracebackhide__=True`` will be hidden.
23 23
24 24 Frames containing ``__debuggerskip__`` will be stepped over, frames who's parent
25 25 frames value of ``__debuggerskip__`` is ``True`` will be skipped.
26 26
27 27 >>> def helpers_helper():
28 28 ... pass
29 29 ...
30 30 ... def helper_1():
31 31 ... print("don't step in me")
32 32 ... helpers_helpers() # will be stepped over unless breakpoint set.
33 33 ...
34 34 ...
35 35 ... def helper_2():
36 36 ... print("in me neither")
37 37 ...
38 38
39 39 One can define a decorator that wraps a function between the two helpers:
40 40
41 41 >>> def pdb_skipped_decorator(function):
42 42 ...
43 43 ...
44 44 ... def wrapped_fn(*args, **kwargs):
45 45 ... __debuggerskip__ = True
46 46 ... helper_1()
47 47 ... __debuggerskip__ = False
48 48 ... result = function(*args, **kwargs)
49 49 ... __debuggerskip__ = True
50 50 ... helper_2()
51 51 ... # setting __debuggerskip__ to False again is not necessary
52 52 ... return result
53 53 ...
54 54 ... return wrapped_fn
55 55
56 56 When decorating a function, ipdb will directly step into ``bar()`` by
57 57 default:
58 58
59 59 >>> @foo_decorator
60 60 ... def bar(x, y):
61 61 ... return x * y
62 62
63 63
64 64 You can toggle the behavior with
65 65
66 66 ipdb> skip_predicates debuggerskip false
67 67
68 68 or configure it in your ``.pdbrc``
69 69
70 70
71 71
72 72 License
73 73 -------
74 74
75 75 Modified from the standard pdb.Pdb class to avoid including readline, so that
76 76 the command line completion of other programs which include this isn't
77 77 damaged.
78 78
79 79 In the future, this class will be expanded with improvements over the standard
80 80 pdb.
81 81
82 82 The original code in this file is mainly lifted out of cmd.py in Python 2.2,
83 83 with minor changes. Licensing should therefore be under the standard Python
84 84 terms. For details on the PSF (Python Software Foundation) standard license,
85 85 see:
86 86
87 87 https://docs.python.org/2/license.html
88 88
89 89
90 90 All the changes since then are under the same license as IPython.
91 91
92 92 """
93 93
94 94 #*****************************************************************************
95 95 #
96 96 # This file is licensed under the PSF license.
97 97 #
98 98 # Copyright (C) 2001 Python Software Foundation, www.python.org
99 99 # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
100 100 #
101 101 #
102 102 #*****************************************************************************
103 103
104 104 import inspect
105 105 import linecache
106 106 import sys
107 import warnings
108 107 import re
109 108 import os
110 109
111 110 from IPython import get_ipython
112 111 from IPython.utils import PyColorize
113 112 from IPython.utils import coloransi, py3compat
114 113 from IPython.core.excolors import exception_colors
115 114
116 115 # skip module docstests
117 116 __skip_doctest__ = True
118 117
119 118 prompt = 'ipdb> '
120 119
121 120 # We have to check this directly from sys.argv, config struct not yet available
122 121 from pdb import Pdb as OldPdb
123 122
124 123 # Allow the set_trace code to operate outside of an ipython instance, even if
125 124 # it does so with some limitations. The rest of this support is implemented in
126 125 # the Tracer constructor.
127 126
128 127 DEBUGGERSKIP = "__debuggerskip__"
129 128
130 129
131 130 def make_arrow(pad):
132 131 """generate the leading arrow in front of traceback or debugger"""
133 132 if pad >= 2:
134 133 return '-'*(pad-2) + '> '
135 134 elif pad == 1:
136 135 return '>'
137 136 return ''
138 137
139 138
140 139 def BdbQuit_excepthook(et, ev, tb, excepthook=None):
141 140 """Exception hook which handles `BdbQuit` exceptions.
142 141
143 142 All other exceptions are processed using the `excepthook`
144 143 parameter.
145 144 """
146 145 raise ValueError(
147 146 "`BdbQuit_excepthook` is deprecated since version 5.1",
148 147 )
149 148
150 149
151 150 def BdbQuit_IPython_excepthook(self, et, ev, tb, tb_offset=None):
152 151 raise ValueError(
153 152 "`BdbQuit_IPython_excepthook` is deprecated since version 5.1",
154 153 DeprecationWarning, stacklevel=2)
155 154
156 155
157 156 RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+')
158 157
159 158
160 159 def strip_indentation(multiline_string):
161 160 return RGX_EXTRA_INDENT.sub('', multiline_string)
162 161
163 162
164 163 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
165 164 """Make new_fn have old_fn's doc string. This is particularly useful
166 165 for the ``do_...`` commands that hook into the help system.
167 166 Adapted from from a comp.lang.python posting
168 167 by Duncan Booth."""
169 168 def wrapper(*args, **kw):
170 169 return new_fn(*args, **kw)
171 170 if old_fn.__doc__:
172 171 wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text
173 172 return wrapper
174 173
175 174
176 175 class Pdb(OldPdb):
177 176 """Modified Pdb class, does not load readline.
178 177
179 178 for a standalone version that uses prompt_toolkit, see
180 179 `IPython.terminal.debugger.TerminalPdb` and
181 180 `IPython.terminal.debugger.set_trace()`
182 181
183 182
184 183 This debugger can hide and skip frames that are tagged according to some predicates.
185 184 See the `skip_predicates` commands.
186 185
187 186 """
188 187
189 188 default_predicates = {
190 189 "tbhide": True,
191 190 "readonly": False,
192 191 "ipython_internal": True,
193 192 "debuggerskip": True,
194 193 }
195 194
196 195 def __init__(self, completekey=None, stdin=None, stdout=None, context=5, **kwargs):
197 196 """Create a new IPython debugger.
198 197
199 198 Parameters
200 199 ----------
201 200 completekey : default None
202 201 Passed to pdb.Pdb.
203 202 stdin : default None
204 203 Passed to pdb.Pdb.
205 204 stdout : default None
206 205 Passed to pdb.Pdb.
207 206 context : int
208 207 Number of lines of source code context to show when
209 208 displaying stacktrace information.
210 209 **kwargs
211 210 Passed to pdb.Pdb.
212 211
213 212 Notes
214 213 -----
215 214 The possibilities are python version dependent, see the python
216 215 docs for more info.
217 216 """
218 217
219 218 # Parent constructor:
220 219 try:
221 220 self.context = int(context)
222 221 if self.context <= 0:
223 222 raise ValueError("Context must be a positive integer")
224 223 except (TypeError, ValueError) as e:
225 224 raise ValueError("Context must be a positive integer") from e
226 225
227 226 # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`.
228 227 OldPdb.__init__(self, completekey, stdin, stdout, **kwargs)
229 228
230 229 # IPython changes...
231 230 self.shell = get_ipython()
232 231
233 232 if self.shell is None:
234 233 save_main = sys.modules['__main__']
235 234 # No IPython instance running, we must create one
236 235 from IPython.terminal.interactiveshell import \
237 236 TerminalInteractiveShell
238 237 self.shell = TerminalInteractiveShell.instance()
239 238 # needed by any code which calls __import__("__main__") after
240 239 # the debugger was entered. See also #9941.
241 240 sys.modules["__main__"] = save_main
242 241
243 242
244 243 color_scheme = self.shell.colors
245 244
246 245 self.aliases = {}
247 246
248 247 # Create color table: we copy the default one from the traceback
249 248 # module and add a few attributes needed for debugging
250 249 self.color_scheme_table = exception_colors()
251 250
252 251 # shorthands
253 252 C = coloransi.TermColors
254 253 cst = self.color_scheme_table
255 254
256 255 cst['NoColor'].colors.prompt = C.NoColor
257 256 cst['NoColor'].colors.breakpoint_enabled = C.NoColor
258 257 cst['NoColor'].colors.breakpoint_disabled = C.NoColor
259 258
260 259 cst['Linux'].colors.prompt = C.Green
261 260 cst['Linux'].colors.breakpoint_enabled = C.LightRed
262 261 cst['Linux'].colors.breakpoint_disabled = C.Red
263 262
264 263 cst['LightBG'].colors.prompt = C.Blue
265 264 cst['LightBG'].colors.breakpoint_enabled = C.LightRed
266 265 cst['LightBG'].colors.breakpoint_disabled = C.Red
267 266
268 267 cst['Neutral'].colors.prompt = C.Blue
269 268 cst['Neutral'].colors.breakpoint_enabled = C.LightRed
270 269 cst['Neutral'].colors.breakpoint_disabled = C.Red
271 270
272 271 # Add a python parser so we can syntax highlight source while
273 272 # debugging.
274 273 self.parser = PyColorize.Parser(style=color_scheme)
275 274 self.set_colors(color_scheme)
276 275
277 276 # Set the prompt - the default prompt is '(Pdb)'
278 277 self.prompt = prompt
279 278 self.skip_hidden = True
280 279 self.report_skipped = True
281 280
282 281 # list of predicates we use to skip frames
283 282 self._predicates = self.default_predicates
284 283
285 284 #
286 285 def set_colors(self, scheme):
287 286 """Shorthand access to the color table scheme selector method."""
288 287 self.color_scheme_table.set_active_scheme(scheme)
289 288 self.parser.style = scheme
290 289
291 290 def set_trace(self, frame=None):
292 291 if frame is None:
293 292 frame = sys._getframe().f_back
294 293 self.initial_frame = frame
295 294 return super().set_trace(frame)
296 295
297 296 def _hidden_predicate(self, frame):
298 297 """
299 298 Given a frame return whether it it should be hidden or not by IPython.
300 299 """
301 300
302 301 if self._predicates["readonly"]:
303 302 fname = frame.f_code.co_filename
304 303 # we need to check for file existence and interactively define
305 304 # function would otherwise appear as RO.
306 305 if os.path.isfile(fname) and not os.access(fname, os.W_OK):
307 306 return True
308 307
309 308 if self._predicates["tbhide"]:
310 309 if frame in (self.curframe, getattr(self, "initial_frame", None)):
311 310 return False
312 311 frame_locals = self._get_frame_locals(frame)
313 312 if "__tracebackhide__" not in frame_locals:
314 313 return False
315 314 return frame_locals["__tracebackhide__"]
316 315 return False
317 316
318 317 def hidden_frames(self, stack):
319 318 """
320 319 Given an index in the stack return whether it should be skipped.
321 320
322 321 This is used in up/down and where to skip frames.
323 322 """
324 323 # The f_locals dictionary is updated from the actual frame
325 324 # locals whenever the .f_locals accessor is called, so we
326 325 # avoid calling it here to preserve self.curframe_locals.
327 326 # Furthermore, there is no good reason to hide the current frame.
328 327 ip_hide = [self._hidden_predicate(s[0]) for s in stack]
329 328 ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"]
330 329 if ip_start and self._predicates["ipython_internal"]:
331 330 ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)]
332 331 return ip_hide
333 332
334 333 def interaction(self, frame, traceback):
335 334 try:
336 335 OldPdb.interaction(self, frame, traceback)
337 336 except KeyboardInterrupt:
338 337 self.stdout.write("\n" + self.shell.get_exception_only())
339 338
340 339 def precmd(self, line):
341 340 """Perform useful escapes on the command before it is executed."""
342 341
343 342 if line.endswith("??"):
344 343 line = "pinfo2 " + line[:-2]
345 344 elif line.endswith("?"):
346 345 line = "pinfo " + line[:-1]
347 346
348 347 line = super().precmd(line)
349 348
350 349 return line
351 350
352 351 def new_do_frame(self, arg):
353 352 OldPdb.do_frame(self, arg)
354 353
355 354 def new_do_quit(self, arg):
356 355
357 356 if hasattr(self, 'old_all_completions'):
358 357 self.shell.Completer.all_completions = self.old_all_completions
359 358
360 359 return OldPdb.do_quit(self, arg)
361 360
362 361 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
363 362
364 363 def new_do_restart(self, arg):
365 364 """Restart command. In the context of ipython this is exactly the same
366 365 thing as 'quit'."""
367 366 self.msg("Restart doesn't make sense here. Using 'quit' instead.")
368 367 return self.do_quit(arg)
369 368
370 369 def print_stack_trace(self, context=None):
371 370 Colors = self.color_scheme_table.active_colors
372 371 ColorsNormal = Colors.Normal
373 372 if context is None:
374 373 context = self.context
375 374 try:
376 375 context = int(context)
377 376 if context <= 0:
378 377 raise ValueError("Context must be a positive integer")
379 378 except (TypeError, ValueError) as e:
380 379 raise ValueError("Context must be a positive integer") from e
381 380 try:
382 381 skipped = 0
383 382 for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack):
384 383 if hidden and self.skip_hidden:
385 384 skipped += 1
386 385 continue
387 386 if skipped:
388 387 print(
389 388 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
390 389 )
391 390 skipped = 0
392 391 self.print_stack_entry(frame_lineno, context=context)
393 392 if skipped:
394 393 print(
395 394 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
396 395 )
397 396 except KeyboardInterrupt:
398 397 pass
399 398
400 399 def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ',
401 400 context=None):
402 401 if context is None:
403 402 context = self.context
404 403 try:
405 404 context = int(context)
406 405 if context <= 0:
407 406 raise ValueError("Context must be a positive integer")
408 407 except (TypeError, ValueError) as e:
409 408 raise ValueError("Context must be a positive integer") from e
410 409 print(self.format_stack_entry(frame_lineno, '', context), file=self.stdout)
411 410
412 411 # vds: >>
413 412 frame, lineno = frame_lineno
414 413 filename = frame.f_code.co_filename
415 414 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
416 415 # vds: <<
417 416
418 417 def _get_frame_locals(self, frame):
419 418 """ "
420 419 Accessing f_local of current frame reset the namespace, so we want to avoid
421 420 that or the following can happen
422 421
423 422 ipdb> foo
424 423 "old"
425 424 ipdb> foo = "new"
426 425 ipdb> foo
427 426 "new"
428 427 ipdb> where
429 428 ipdb> foo
430 429 "old"
431 430
432 431 So if frame is self.current_frame we instead return self.curframe_locals
433 432
434 433 """
435 434 if frame is self.curframe:
436 435 return self.curframe_locals
437 436 else:
438 437 return frame.f_locals
439 438
440 439 def format_stack_entry(self, frame_lineno, lprefix=': ', context=None):
441 440 if context is None:
442 441 context = self.context
443 442 try:
444 443 context = int(context)
445 444 if context <= 0:
446 445 print("Context must be a positive integer", file=self.stdout)
447 446 except (TypeError, ValueError):
448 447 print("Context must be a positive integer", file=self.stdout)
449 448
450 449 import reprlib
451 450
452 451 ret = []
453 452
454 453 Colors = self.color_scheme_table.active_colors
455 454 ColorsNormal = Colors.Normal
456 455 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
457 456 tpl_call = "%s%%s%s%%s%s" % (Colors.vName, Colors.valEm, ColorsNormal)
458 457 tpl_line = "%%s%s%%s %s%%s" % (Colors.lineno, ColorsNormal)
459 458 tpl_line_em = "%%s%s%%s %s%%s%s" % (Colors.linenoEm, Colors.line, ColorsNormal)
460 459
461 460 frame, lineno = frame_lineno
462 461
463 462 return_value = ''
464 463 loc_frame = self._get_frame_locals(frame)
465 464 if "__return__" in loc_frame:
466 465 rv = loc_frame["__return__"]
467 466 # return_value += '->'
468 467 return_value += reprlib.repr(rv) + "\n"
469 468 ret.append(return_value)
470 469
471 470 #s = filename + '(' + `lineno` + ')'
472 471 filename = self.canonic(frame.f_code.co_filename)
473 472 link = tpl_link % py3compat.cast_unicode(filename)
474 473
475 474 if frame.f_code.co_name:
476 475 func = frame.f_code.co_name
477 476 else:
478 477 func = "<lambda>"
479 478
480 479 call = ""
481 480 if func != "?":
482 481 if "__args__" in loc_frame:
483 482 args = reprlib.repr(loc_frame["__args__"])
484 483 else:
485 484 args = '()'
486 485 call = tpl_call % (func, args)
487 486
488 487 # The level info should be generated in the same format pdb uses, to
489 488 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
490 489 if frame is self.curframe:
491 490 ret.append('> ')
492 491 else:
493 492 ret.append(" ")
494 493 ret.append("%s(%s)%s\n" % (link, lineno, call))
495 494
496 495 start = lineno - 1 - context//2
497 496 lines = linecache.getlines(filename)
498 497 start = min(start, len(lines) - context)
499 498 start = max(start, 0)
500 499 lines = lines[start : start + context]
501 500
502 501 for i, line in enumerate(lines):
503 502 show_arrow = start + 1 + i == lineno
504 503 linetpl = (frame is self.curframe or show_arrow) and tpl_line_em or tpl_line
505 504 ret.append(
506 505 self.__format_line(
507 506 linetpl, filename, start + 1 + i, line, arrow=show_arrow
508 507 )
509 508 )
510 509 return "".join(ret)
511 510
512 511 def __format_line(self, tpl_line, filename, lineno, line, arrow=False):
513 512 bp_mark = ""
514 513 bp_mark_color = ""
515 514
516 515 new_line, err = self.parser.format2(line, 'str')
517 516 if not err:
518 517 line = new_line
519 518
520 519 bp = None
521 520 if lineno in self.get_file_breaks(filename):
522 521 bps = self.get_breaks(filename, lineno)
523 522 bp = bps[-1]
524 523
525 524 if bp:
526 525 Colors = self.color_scheme_table.active_colors
527 526 bp_mark = str(bp.number)
528 527 bp_mark_color = Colors.breakpoint_enabled
529 528 if not bp.enabled:
530 529 bp_mark_color = Colors.breakpoint_disabled
531 530
532 531 numbers_width = 7
533 532 if arrow:
534 533 # This is the line with the error
535 534 pad = numbers_width - len(str(lineno)) - len(bp_mark)
536 535 num = '%s%s' % (make_arrow(pad), str(lineno))
537 536 else:
538 537 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
539 538
540 539 return tpl_line % (bp_mark_color + bp_mark, num, line)
541 540
542 541 def print_list_lines(self, filename, first, last):
543 542 """The printing (as opposed to the parsing part of a 'list'
544 543 command."""
545 544 try:
546 545 Colors = self.color_scheme_table.active_colors
547 546 ColorsNormal = Colors.Normal
548 547 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
549 548 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
550 549 src = []
551 550 if filename == "<string>" and hasattr(self, "_exec_filename"):
552 551 filename = self._exec_filename
553 552
554 553 for lineno in range(first, last+1):
555 554 line = linecache.getline(filename, lineno)
556 555 if not line:
557 556 break
558 557
559 558 if lineno == self.curframe.f_lineno:
560 559 line = self.__format_line(
561 560 tpl_line_em, filename, lineno, line, arrow=True
562 561 )
563 562 else:
564 563 line = self.__format_line(
565 564 tpl_line, filename, lineno, line, arrow=False
566 565 )
567 566
568 567 src.append(line)
569 568 self.lineno = lineno
570 569
571 570 print(''.join(src), file=self.stdout)
572 571
573 572 except KeyboardInterrupt:
574 573 pass
575 574
576 575 def do_skip_predicates(self, args):
577 576 """
578 577 Turn on/off individual predicates as to whether a frame should be hidden/skip.
579 578
580 579 The global option to skip (or not) hidden frames is set with skip_hidden
581 580
582 581 To change the value of a predicate
583 582
584 583 skip_predicates key [true|false]
585 584
586 585 Call without arguments to see the current values.
587 586
588 587 To permanently change the value of an option add the corresponding
589 588 command to your ``~/.pdbrc`` file. If you are programmatically using the
590 589 Pdb instance you can also change the ``default_predicates`` class
591 590 attribute.
592 591 """
593 592 if not args.strip():
594 593 print("current predicates:")
595 594 for (p, v) in self._predicates.items():
596 595 print(" ", p, ":", v)
597 596 return
598 597 type_value = args.strip().split(" ")
599 598 if len(type_value) != 2:
600 599 print(
601 600 f"Usage: skip_predicates <type> <value>, with <type> one of {set(self._predicates.keys())}"
602 601 )
603 602 return
604 603
605 604 type_, value = type_value
606 605 if type_ not in self._predicates:
607 606 print(f"{type_!r} not in {set(self._predicates.keys())}")
608 607 return
609 608 if value.lower() not in ("true", "yes", "1", "no", "false", "0"):
610 609 print(
611 610 f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')"
612 611 )
613 612 return
614 613
615 614 self._predicates[type_] = value.lower() in ("true", "yes", "1")
616 615 if not any(self._predicates.values()):
617 616 print(
618 617 "Warning, all predicates set to False, skip_hidden may not have any effects."
619 618 )
620 619
621 620 def do_skip_hidden(self, arg):
622 621 """
623 622 Change whether or not we should skip frames with the
624 623 __tracebackhide__ attribute.
625 624 """
626 625 if not arg.strip():
627 626 print(
628 627 f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change."
629 628 )
630 629 elif arg.strip().lower() in ("true", "yes"):
631 630 self.skip_hidden = True
632 631 elif arg.strip().lower() in ("false", "no"):
633 632 self.skip_hidden = False
634 633 if not any(self._predicates.values()):
635 634 print(
636 635 "Warning, all predicates set to False, skip_hidden may not have any effects."
637 636 )
638 637
639 638 def do_list(self, arg):
640 639 """Print lines of code from the current stack frame
641 640 """
642 641 self.lastcmd = 'list'
643 642 last = None
644 643 if arg:
645 644 try:
646 645 x = eval(arg, {}, {})
647 646 if type(x) == type(()):
648 647 first, last = x
649 648 first = int(first)
650 649 last = int(last)
651 650 if last < first:
652 651 # Assume it's a count
653 652 last = first + last
654 653 else:
655 654 first = max(1, int(x) - 5)
656 655 except:
657 656 print('*** Error in argument:', repr(arg), file=self.stdout)
658 657 return
659 658 elif self.lineno is None:
660 659 first = max(1, self.curframe.f_lineno - 5)
661 660 else:
662 661 first = self.lineno + 1
663 662 if last is None:
664 663 last = first + 10
665 664 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
666 665
667 666 # vds: >>
668 667 lineno = first
669 668 filename = self.curframe.f_code.co_filename
670 669 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
671 670 # vds: <<
672 671
673 672 do_l = do_list
674 673
675 674 def getsourcelines(self, obj):
676 675 lines, lineno = inspect.findsource(obj)
677 676 if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj):
678 677 # must be a module frame: do not try to cut a block out of it
679 678 return lines, 1
680 679 elif inspect.ismodule(obj):
681 680 return lines, 1
682 681 return inspect.getblock(lines[lineno:]), lineno+1
683 682
684 683 def do_longlist(self, arg):
685 684 """Print lines of code from the current stack frame.
686 685
687 686 Shows more lines than 'list' does.
688 687 """
689 688 self.lastcmd = 'longlist'
690 689 try:
691 690 lines, lineno = self.getsourcelines(self.curframe)
692 691 except OSError as err:
693 692 self.error(err)
694 693 return
695 694 last = lineno + len(lines)
696 695 self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
697 696 do_ll = do_longlist
698 697
699 698 def do_debug(self, arg):
700 699 """debug code
701 700 Enter a recursive debugger that steps through the code
702 701 argument (which is an arbitrary expression or statement to be
703 702 executed in the current environment).
704 703 """
705 704 trace_function = sys.gettrace()
706 705 sys.settrace(None)
707 706 globals = self.curframe.f_globals
708 707 locals = self.curframe_locals
709 708 p = self.__class__(completekey=self.completekey,
710 709 stdin=self.stdin, stdout=self.stdout)
711 710 p.use_rawinput = self.use_rawinput
712 711 p.prompt = "(%s) " % self.prompt.strip()
713 712 self.message("ENTERING RECURSIVE DEBUGGER")
714 713 sys.call_tracing(p.run, (arg, globals, locals))
715 714 self.message("LEAVING RECURSIVE DEBUGGER")
716 715 sys.settrace(trace_function)
717 716 self.lastcmd = p.lastcmd
718 717
719 718 def do_pdef(self, arg):
720 719 """Print the call signature for any callable object.
721 720
722 721 The debugger interface to %pdef"""
723 722 namespaces = [
724 723 ("Locals", self.curframe_locals),
725 724 ("Globals", self.curframe.f_globals),
726 725 ]
727 726 self.shell.find_line_magic("pdef")(arg, namespaces=namespaces)
728 727
729 728 def do_pdoc(self, arg):
730 729 """Print the docstring for an object.
731 730
732 731 The debugger interface to %pdoc."""
733 732 namespaces = [
734 733 ("Locals", self.curframe_locals),
735 734 ("Globals", self.curframe.f_globals),
736 735 ]
737 736 self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces)
738 737
739 738 def do_pfile(self, arg):
740 739 """Print (or run through pager) the file where an object is defined.
741 740
742 741 The debugger interface to %pfile.
743 742 """
744 743 namespaces = [
745 744 ("Locals", self.curframe_locals),
746 745 ("Globals", self.curframe.f_globals),
747 746 ]
748 747 self.shell.find_line_magic("pfile")(arg, namespaces=namespaces)
749 748
750 749 def do_pinfo(self, arg):
751 750 """Provide detailed information about an object.
752 751
753 752 The debugger interface to %pinfo, i.e., obj?."""
754 753 namespaces = [
755 754 ("Locals", self.curframe_locals),
756 755 ("Globals", self.curframe.f_globals),
757 756 ]
758 757 self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces)
759 758
760 759 def do_pinfo2(self, arg):
761 760 """Provide extra detailed information about an object.
762 761
763 762 The debugger interface to %pinfo2, i.e., obj??."""
764 763 namespaces = [
765 764 ("Locals", self.curframe_locals),
766 765 ("Globals", self.curframe.f_globals),
767 766 ]
768 767 self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces)
769 768
770 769 def do_psource(self, arg):
771 770 """Print (or run through pager) the source code for an object."""
772 771 namespaces = [
773 772 ("Locals", self.curframe_locals),
774 773 ("Globals", self.curframe.f_globals),
775 774 ]
776 775 self.shell.find_line_magic("psource")(arg, namespaces=namespaces)
777 776
778 777 def do_where(self, arg):
779 778 """w(here)
780 779 Print a stack trace, with the most recent frame at the bottom.
781 780 An arrow indicates the "current frame", which determines the
782 781 context of most commands. 'bt' is an alias for this command.
783 782
784 783 Take a number as argument as an (optional) number of context line to
785 784 print"""
786 785 if arg:
787 786 try:
788 787 context = int(arg)
789 788 except ValueError as err:
790 789 self.error(err)
791 790 return
792 791 self.print_stack_trace(context)
793 792 else:
794 793 self.print_stack_trace()
795 794
796 795 do_w = do_where
797 796
798 797 def break_anywhere(self, frame):
799 798 """
800 799 _stop_in_decorator_internals is overly restrictive, as we may still want
801 800 to trace function calls, so we need to also update break_anywhere so
802 801 that is we don't `stop_here`, because of debugger skip, we may still
803 802 stop at any point inside the function
804 803
805 804 """
806 805
807 806 sup = super().break_anywhere(frame)
808 807 if sup:
809 808 return sup
810 809 if self._predicates["debuggerskip"]:
811 810 if DEBUGGERSKIP in frame.f_code.co_varnames:
812 811 return True
813 812 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
814 813 return True
815 814 return False
816 815
817 816 def _is_in_decorator_internal_and_should_skip(self, frame):
818 817 """
819 818 Utility to tell us whether we are in a decorator internal and should stop.
820 819
821 820 """
822 821
823 822 # if we are disabled don't skip
824 823 if not self._predicates["debuggerskip"]:
825 824 return False
826 825
827 826 # if frame is tagged, skip by default.
828 827 if DEBUGGERSKIP in frame.f_code.co_varnames:
829 828 return True
830 829
831 830 # if one of the parent frame value set to True skip as well.
832 831
833 832 cframe = frame
834 833 while getattr(cframe, "f_back", None):
835 834 cframe = cframe.f_back
836 835 if self._get_frame_locals(cframe).get(DEBUGGERSKIP):
837 836 return True
838 837
839 838 return False
840 839
841 840 def stop_here(self, frame):
842 841
843 842 if self._is_in_decorator_internal_and_should_skip(frame) is True:
844 843 return False
845 844
846 845 hidden = False
847 846 if self.skip_hidden:
848 847 hidden = self._hidden_predicate(frame)
849 848 if hidden:
850 849 if self.report_skipped:
851 850 Colors = self.color_scheme_table.active_colors
852 851 ColorsNormal = Colors.Normal
853 852 print(
854 853 f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n"
855 854 )
856 855 return super().stop_here(frame)
857 856
858 857 def do_up(self, arg):
859 858 """u(p) [count]
860 859 Move the current frame count (default one) levels up in the
861 860 stack trace (to an older frame).
862 861
863 862 Will skip hidden frames.
864 863 """
865 864 # modified version of upstream that skips
866 865 # frames with __tracebackhide__
867 866 if self.curindex == 0:
868 867 self.error("Oldest frame")
869 868 return
870 869 try:
871 870 count = int(arg or 1)
872 871 except ValueError:
873 872 self.error("Invalid frame count (%s)" % arg)
874 873 return
875 874 skipped = 0
876 875 if count < 0:
877 876 _newframe = 0
878 877 else:
879 878 counter = 0
880 879 hidden_frames = self.hidden_frames(self.stack)
881 880 for i in range(self.curindex - 1, -1, -1):
882 881 if hidden_frames[i] and self.skip_hidden:
883 882 skipped += 1
884 883 continue
885 884 counter += 1
886 885 if counter >= count:
887 886 break
888 887 else:
889 888 # if no break occurred.
890 889 self.error(
891 890 "all frames above hidden, use `skip_hidden False` to get get into those."
892 891 )
893 892 return
894 893
895 894 Colors = self.color_scheme_table.active_colors
896 895 ColorsNormal = Colors.Normal
897 896 _newframe = i
898 897 self._select_frame(_newframe)
899 898 if skipped:
900 899 print(
901 900 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
902 901 )
903 902
904 903 def do_down(self, arg):
905 904 """d(own) [count]
906 905 Move the current frame count (default one) levels down in the
907 906 stack trace (to a newer frame).
908 907
909 908 Will skip hidden frames.
910 909 """
911 910 if self.curindex + 1 == len(self.stack):
912 911 self.error("Newest frame")
913 912 return
914 913 try:
915 914 count = int(arg or 1)
916 915 except ValueError:
917 916 self.error("Invalid frame count (%s)" % arg)
918 917 return
919 918 if count < 0:
920 919 _newframe = len(self.stack) - 1
921 920 else:
922 921 counter = 0
923 922 skipped = 0
924 923 hidden_frames = self.hidden_frames(self.stack)
925 924 for i in range(self.curindex + 1, len(self.stack)):
926 925 if hidden_frames[i] and self.skip_hidden:
927 926 skipped += 1
928 927 continue
929 928 counter += 1
930 929 if counter >= count:
931 930 break
932 931 else:
933 932 self.error(
934 933 "all frames below hidden, use `skip_hidden False` to get get into those."
935 934 )
936 935 return
937 936
938 937 Colors = self.color_scheme_table.active_colors
939 938 ColorsNormal = Colors.Normal
940 939 if skipped:
941 940 print(
942 941 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
943 942 )
944 943 _newframe = i
945 944
946 945 self._select_frame(_newframe)
947 946
948 947 do_d = do_down
949 948 do_u = do_up
950 949
951 950 def do_context(self, context):
952 951 """context number_of_lines
953 952 Set the number of lines of source code to show when displaying
954 953 stacktrace information.
955 954 """
956 955 try:
957 956 new_context = int(context)
958 957 if new_context <= 0:
959 958 raise ValueError()
960 959 self.context = new_context
961 960 except ValueError:
962 961 self.error("The 'context' command requires a positive integer argument.")
963 962
964 963
965 964 class InterruptiblePdb(Pdb):
966 965 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
967 966
968 967 def cmdloop(self, intro=None):
969 968 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
970 969 try:
971 970 return OldPdb.cmdloop(self, intro=intro)
972 971 except KeyboardInterrupt:
973 972 self.stop_here = lambda frame: False
974 973 self.do_quit("")
975 974 sys.settrace(None)
976 975 self.quitting = False
977 976 raise
978 977
979 978 def _cmdloop(self):
980 979 while True:
981 980 try:
982 981 # keyboard interrupts allow for an easy way to cancel
983 982 # the current command, so allow them during interactive input
984 983 self.allow_kbdint = True
985 984 self.cmdloop()
986 985 self.allow_kbdint = False
987 986 break
988 987 except KeyboardInterrupt:
989 988 self.message('--KeyboardInterrupt--')
990 989 raise
991 990
992 991
993 992 def set_trace(frame=None):
994 993 """
995 994 Start debugging from `frame`.
996 995
997 996 If frame is not specified, debugging starts from caller's frame.
998 997 """
999 998 Pdb().set_trace(frame or sys._getframe().f_back)
@@ -1,166 +1,165 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 Color schemes for exception handling code in IPython.
4 4 """
5 5
6 6 import os
7 import warnings
8 7
9 8 #*****************************************************************************
10 9 # Copyright (C) 2005-2006 Fernando Perez <fperez@colorado.edu>
11 10 #
12 11 # Distributed under the terms of the BSD License. The full license is in
13 12 # the file COPYING, distributed as part of this software.
14 13 #*****************************************************************************
15 14
16 15 from IPython.utils.coloransi import ColorSchemeTable, TermColors, ColorScheme
17 16
18 17 def exception_colors():
19 18 """Return a color table with fields for exception reporting.
20 19
21 20 The table is an instance of ColorSchemeTable with schemes added for
22 21 'Neutral', 'Linux', 'LightBG' and 'NoColor' and fields for exception handling filled
23 22 in.
24 23
25 24 Examples:
26 25
27 26 >>> ec = exception_colors()
28 27 >>> ec.active_scheme_name
29 28 ''
30 29 >>> print(ec.active_colors)
31 30 None
32 31
33 32 Now we activate a color scheme:
34 33 >>> ec.set_active_scheme('NoColor')
35 34 >>> ec.active_scheme_name
36 35 'NoColor'
37 36 >>> sorted(ec.active_colors.keys())
38 37 ['Normal', 'caret', 'em', 'excName', 'filename', 'filenameEm', 'line',
39 38 'lineno', 'linenoEm', 'name', 'nameEm', 'normalEm', 'topline', 'vName',
40 39 'val', 'valEm']
41 40 """
42 41
43 42 ex_colors = ColorSchemeTable()
44 43
45 44 # Populate it with color schemes
46 45 C = TermColors # shorthand and local lookup
47 46 ex_colors.add_scheme(ColorScheme(
48 47 'NoColor',
49 48 # The color to be used for the top line
50 49 topline = C.NoColor,
51 50
52 51 # The colors to be used in the traceback
53 52 filename = C.NoColor,
54 53 lineno = C.NoColor,
55 54 name = C.NoColor,
56 55 vName = C.NoColor,
57 56 val = C.NoColor,
58 57 em = C.NoColor,
59 58
60 59 # Emphasized colors for the last frame of the traceback
61 60 normalEm = C.NoColor,
62 61 filenameEm = C.NoColor,
63 62 linenoEm = C.NoColor,
64 63 nameEm = C.NoColor,
65 64 valEm = C.NoColor,
66 65
67 66 # Colors for printing the exception
68 67 excName = C.NoColor,
69 68 line = C.NoColor,
70 69 caret = C.NoColor,
71 70 Normal = C.NoColor
72 71 ))
73 72
74 73 # make some schemes as instances so we can copy them for modification easily
75 74 ex_colors.add_scheme(ColorScheme(
76 75 'Linux',
77 76 # The color to be used for the top line
78 77 topline = C.LightRed,
79 78
80 79 # The colors to be used in the traceback
81 80 filename = C.Green,
82 81 lineno = C.Green,
83 82 name = C.Purple,
84 83 vName = C.Cyan,
85 84 val = C.Green,
86 85 em = C.LightCyan,
87 86
88 87 # Emphasized colors for the last frame of the traceback
89 88 normalEm = C.LightCyan,
90 89 filenameEm = C.LightGreen,
91 90 linenoEm = C.LightGreen,
92 91 nameEm = C.LightPurple,
93 92 valEm = C.LightBlue,
94 93
95 94 # Colors for printing the exception
96 95 excName = C.LightRed,
97 96 line = C.Yellow,
98 97 caret = C.White,
99 98 Normal = C.Normal
100 99 ))
101 100
102 101 # For light backgrounds, swap dark/light colors
103 102 ex_colors.add_scheme(ColorScheme(
104 103 'LightBG',
105 104 # The color to be used for the top line
106 105 topline = C.Red,
107 106
108 107 # The colors to be used in the traceback
109 108 filename = C.LightGreen,
110 109 lineno = C.LightGreen,
111 110 name = C.LightPurple,
112 111 vName = C.Cyan,
113 112 val = C.LightGreen,
114 113 em = C.Cyan,
115 114
116 115 # Emphasized colors for the last frame of the traceback
117 116 normalEm = C.Cyan,
118 117 filenameEm = C.Green,
119 118 linenoEm = C.Green,
120 119 nameEm = C.Purple,
121 120 valEm = C.Blue,
122 121
123 122 # Colors for printing the exception
124 123 excName = C.Red,
125 124 #line = C.Brown, # brown often is displayed as yellow
126 125 line = C.Red,
127 126 caret = C.Normal,
128 127 Normal = C.Normal,
129 128 ))
130 129
131 130 ex_colors.add_scheme(ColorScheme(
132 131 'Neutral',
133 132 # The color to be used for the top line
134 133 topline = C.Red,
135 134
136 135 # The colors to be used in the traceback
137 136 filename = C.LightGreen,
138 137 lineno = C.LightGreen,
139 138 name = C.LightPurple,
140 139 vName = C.Cyan,
141 140 val = C.LightGreen,
142 141 em = C.Cyan,
143 142
144 143 # Emphasized colors for the last frame of the traceback
145 144 normalEm = C.Cyan,
146 145 filenameEm = C.Green,
147 146 linenoEm = C.Green,
148 147 nameEm = C.Purple,
149 148 valEm = C.Blue,
150 149
151 150 # Colors for printing the exception
152 151 excName = C.Red,
153 152 #line = C.Brown, # brown often is displayed as yellow
154 153 line = C.Red,
155 154 caret = C.Normal,
156 155 Normal = C.Normal,
157 156 ))
158 157
159 158 # Hack: the 'neutral' colours are not very visible on a dark background on
160 159 # Windows. Since Windows command prompts have a dark background by default, and
161 160 # relatively few users are likely to alter that, we will use the 'Linux' colours,
162 161 # designed for a dark background, as the default on Windows.
163 162 if os.name == "nt":
164 163 ex_colors.add_scheme(ex_colors['Linux'].copy('Neutral'))
165 164
166 165 return ex_colors
@@ -1,1027 +1,1026 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Display formatters.
3 3
4 4 Inheritance diagram:
5 5
6 6 .. inheritance-diagram:: IPython.core.formatters
7 7 :parts: 3
8 8 """
9 9
10 10 # Copyright (c) IPython Development Team.
11 11 # Distributed under the terms of the Modified BSD License.
12 12
13 13 import abc
14 import json
15 14 import sys
16 15 import traceback
17 16 import warnings
18 17 from io import StringIO
19 18
20 19 from decorator import decorator
21 20
22 21 from traitlets.config.configurable import Configurable
23 22 from .getipython import get_ipython
24 23 from ..utils.sentinel import Sentinel
25 24 from ..utils.dir2 import get_real_method
26 25 from ..lib import pretty
27 26 from traitlets import (
28 27 Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
29 28 ForwardDeclaredInstance,
30 29 default, observe,
31 30 )
32 31
33 32
34 33 class DisplayFormatter(Configurable):
35 34
36 35 active_types = List(Unicode(),
37 36 help="""List of currently active mime-types to display.
38 37 You can use this to set a white-list for formats to display.
39 38
40 39 Most users will not need to change this value.
41 40 """).tag(config=True)
42 41
43 42 @default('active_types')
44 43 def _active_types_default(self):
45 44 return self.format_types
46 45
47 46 @observe('active_types')
48 47 def _active_types_changed(self, change):
49 48 for key, formatter in self.formatters.items():
50 49 if key in change['new']:
51 50 formatter.enabled = True
52 51 else:
53 52 formatter.enabled = False
54 53
55 54 ipython_display_formatter = ForwardDeclaredInstance('FormatterABC')
56 55 @default('ipython_display_formatter')
57 56 def _default_formatter(self):
58 57 return IPythonDisplayFormatter(parent=self)
59 58
60 59 mimebundle_formatter = ForwardDeclaredInstance('FormatterABC')
61 60 @default('mimebundle_formatter')
62 61 def _default_mime_formatter(self):
63 62 return MimeBundleFormatter(parent=self)
64 63
65 64 # A dict of formatter whose keys are format types (MIME types) and whose
66 65 # values are subclasses of BaseFormatter.
67 66 formatters = Dict()
68 67 @default('formatters')
69 68 def _formatters_default(self):
70 69 """Activate the default formatters."""
71 70 formatter_classes = [
72 71 PlainTextFormatter,
73 72 HTMLFormatter,
74 73 MarkdownFormatter,
75 74 SVGFormatter,
76 75 PNGFormatter,
77 76 PDFFormatter,
78 77 JPEGFormatter,
79 78 LatexFormatter,
80 79 JSONFormatter,
81 80 JavascriptFormatter
82 81 ]
83 82 d = {}
84 83 for cls in formatter_classes:
85 84 f = cls(parent=self)
86 85 d[f.format_type] = f
87 86 return d
88 87
89 88 def format(self, obj, include=None, exclude=None):
90 89 """Return a format data dict for an object.
91 90
92 91 By default all format types will be computed.
93 92
94 93 The following MIME types are usually implemented:
95 94
96 95 * text/plain
97 96 * text/html
98 97 * text/markdown
99 98 * text/latex
100 99 * application/json
101 100 * application/javascript
102 101 * application/pdf
103 102 * image/png
104 103 * image/jpeg
105 104 * image/svg+xml
106 105
107 106 Parameters
108 107 ----------
109 108 obj : object
110 109 The Python object whose format data will be computed.
111 110 include : list, tuple or set; optional
112 111 A list of format type strings (MIME types) to include in the
113 112 format data dict. If this is set *only* the format types included
114 113 in this list will be computed.
115 114 exclude : list, tuple or set; optional
116 115 A list of format type string (MIME types) to exclude in the format
117 116 data dict. If this is set all format types will be computed,
118 117 except for those included in this argument.
119 118 Mimetypes present in exclude will take precedence over the ones in include
120 119
121 120 Returns
122 121 -------
123 122 (format_dict, metadata_dict) : tuple of two dicts
124 123 format_dict is a dictionary of key/value pairs, one of each format that was
125 124 generated for the object. The keys are the format types, which
126 125 will usually be MIME type strings and the values and JSON'able
127 126 data structure containing the raw data for the representation in
128 127 that format.
129 128
130 129 metadata_dict is a dictionary of metadata about each mime-type output.
131 130 Its keys will be a strict subset of the keys in format_dict.
132 131
133 132 Notes
134 133 -----
135 134 If an object implement `_repr_mimebundle_` as well as various
136 135 `_repr_*_`, the data returned by `_repr_mimebundle_` will take
137 136 precedence and the corresponding `_repr_*_` for this mimetype will
138 137 not be called.
139 138
140 139 """
141 140 format_dict = {}
142 141 md_dict = {}
143 142
144 143 if self.ipython_display_formatter(obj):
145 144 # object handled itself, don't proceed
146 145 return {}, {}
147 146
148 147 format_dict, md_dict = self.mimebundle_formatter(obj, include=include, exclude=exclude)
149 148
150 149 if format_dict or md_dict:
151 150 if include:
152 151 format_dict = {k:v for k,v in format_dict.items() if k in include}
153 152 md_dict = {k:v for k,v in md_dict.items() if k in include}
154 153 if exclude:
155 154 format_dict = {k:v for k,v in format_dict.items() if k not in exclude}
156 155 md_dict = {k:v for k,v in md_dict.items() if k not in exclude}
157 156
158 157 for format_type, formatter in self.formatters.items():
159 158 if format_type in format_dict:
160 159 # already got it from mimebundle, maybe don't render again.
161 160 # exception: manually registered per-mime renderer
162 161 # check priority:
163 162 # 1. user-registered per-mime formatter
164 163 # 2. mime-bundle (user-registered or repr method)
165 164 # 3. default per-mime formatter (e.g. repr method)
166 165 try:
167 166 formatter.lookup(obj)
168 167 except KeyError:
169 168 # no special formatter, use mime-bundle-provided value
170 169 continue
171 170 if include and format_type not in include:
172 171 continue
173 172 if exclude and format_type in exclude:
174 173 continue
175 174
176 175 md = None
177 176 try:
178 177 data = formatter(obj)
179 178 except:
180 179 # FIXME: log the exception
181 180 raise
182 181
183 182 # formatters can return raw data or (data, metadata)
184 183 if isinstance(data, tuple) and len(data) == 2:
185 184 data, md = data
186 185
187 186 if data is not None:
188 187 format_dict[format_type] = data
189 188 if md is not None:
190 189 md_dict[format_type] = md
191 190 return format_dict, md_dict
192 191
193 192 @property
194 193 def format_types(self):
195 194 """Return the format types (MIME types) of the active formatters."""
196 195 return list(self.formatters.keys())
197 196
198 197
199 198 #-----------------------------------------------------------------------------
200 199 # Formatters for specific format types (text, html, svg, etc.)
201 200 #-----------------------------------------------------------------------------
202 201
203 202
204 203 def _safe_repr(obj):
205 204 """Try to return a repr of an object
206 205
207 206 always returns a string, at least.
208 207 """
209 208 try:
210 209 return repr(obj)
211 210 except Exception as e:
212 211 return "un-repr-able object (%r)" % e
213 212
214 213
215 214 class FormatterWarning(UserWarning):
216 215 """Warning class for errors in formatters"""
217 216
218 217 @decorator
219 218 def catch_format_error(method, self, *args, **kwargs):
220 219 """show traceback on failed format call"""
221 220 try:
222 221 r = method(self, *args, **kwargs)
223 222 except NotImplementedError:
224 223 # don't warn on NotImplementedErrors
225 224 return self._check_return(None, args[0])
226 225 except Exception:
227 226 exc_info = sys.exc_info()
228 227 ip = get_ipython()
229 228 if ip is not None:
230 229 ip.showtraceback(exc_info)
231 230 else:
232 231 traceback.print_exception(*exc_info)
233 232 return self._check_return(None, args[0])
234 233 return self._check_return(r, args[0])
235 234
236 235
237 236 class FormatterABC(metaclass=abc.ABCMeta):
238 237 """ Abstract base class for Formatters.
239 238
240 239 A formatter is a callable class that is responsible for computing the
241 240 raw format data for a particular format type (MIME type). For example,
242 241 an HTML formatter would have a format type of `text/html` and would return
243 242 the HTML representation of the object when called.
244 243 """
245 244
246 245 # The format type of the data returned, usually a MIME type.
247 246 format_type = 'text/plain'
248 247
249 248 # Is the formatter enabled...
250 249 enabled = True
251 250
252 251 @abc.abstractmethod
253 252 def __call__(self, obj):
254 253 """Return a JSON'able representation of the object.
255 254
256 255 If the object cannot be formatted by this formatter,
257 256 warn and return None.
258 257 """
259 258 return repr(obj)
260 259
261 260
262 261 def _mod_name_key(typ):
263 262 """Return a (__module__, __name__) tuple for a type.
264 263
265 264 Used as key in Formatter.deferred_printers.
266 265 """
267 266 module = getattr(typ, '__module__', None)
268 267 name = getattr(typ, '__name__', None)
269 268 return (module, name)
270 269
271 270
272 271 def _get_type(obj):
273 272 """Return the type of an instance (old and new-style)"""
274 273 return getattr(obj, '__class__', None) or type(obj)
275 274
276 275
277 276 _raise_key_error = Sentinel('_raise_key_error', __name__,
278 277 """
279 278 Special value to raise a KeyError
280 279
281 280 Raise KeyError in `BaseFormatter.pop` if passed as the default value to `pop`
282 281 """)
283 282
284 283
285 284 class BaseFormatter(Configurable):
286 285 """A base formatter class that is configurable.
287 286
288 287 This formatter should usually be used as the base class of all formatters.
289 288 It is a traited :class:`Configurable` class and includes an extensible
290 289 API for users to determine how their objects are formatted. The following
291 290 logic is used to find a function to format an given object.
292 291
293 292 1. The object is introspected to see if it has a method with the name
294 293 :attr:`print_method`. If is does, that object is passed to that method
295 294 for formatting.
296 295 2. If no print method is found, three internal dictionaries are consulted
297 296 to find print method: :attr:`singleton_printers`, :attr:`type_printers`
298 297 and :attr:`deferred_printers`.
299 298
300 299 Users should use these dictionaries to register functions that will be
301 300 used to compute the format data for their objects (if those objects don't
302 301 have the special print methods). The easiest way of using these
303 302 dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name`
304 303 methods.
305 304
306 305 If no function/callable is found to compute the format data, ``None`` is
307 306 returned and this format type is not used.
308 307 """
309 308
310 309 format_type = Unicode('text/plain')
311 310 _return_type = str
312 311
313 312 enabled = Bool(True).tag(config=True)
314 313
315 314 print_method = ObjectName('__repr__')
316 315
317 316 # The singleton printers.
318 317 # Maps the IDs of the builtin singleton objects to the format functions.
319 318 singleton_printers = Dict().tag(config=True)
320 319
321 320 # The type-specific printers.
322 321 # Map type objects to the format functions.
323 322 type_printers = Dict().tag(config=True)
324 323
325 324 # The deferred-import type-specific printers.
326 325 # Map (modulename, classname) pairs to the format functions.
327 326 deferred_printers = Dict().tag(config=True)
328 327
329 328 @catch_format_error
330 329 def __call__(self, obj):
331 330 """Compute the format for an object."""
332 331 if self.enabled:
333 332 # lookup registered printer
334 333 try:
335 334 printer = self.lookup(obj)
336 335 except KeyError:
337 336 pass
338 337 else:
339 338 return printer(obj)
340 339 # Finally look for special method names
341 340 method = get_real_method(obj, self.print_method)
342 341 if method is not None:
343 342 return method()
344 343 return None
345 344 else:
346 345 return None
347 346
348 347 def __contains__(self, typ):
349 348 """map in to lookup_by_type"""
350 349 try:
351 350 self.lookup_by_type(typ)
352 351 except KeyError:
353 352 return False
354 353 else:
355 354 return True
356 355
357 356 def _check_return(self, r, obj):
358 357 """Check that a return value is appropriate
359 358
360 359 Return the value if so, None otherwise, warning if invalid.
361 360 """
362 361 if r is None or isinstance(r, self._return_type) or \
363 362 (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)):
364 363 return r
365 364 else:
366 365 warnings.warn(
367 366 "%s formatter returned invalid type %s (expected %s) for object: %s" % \
368 367 (self.format_type, type(r), self._return_type, _safe_repr(obj)),
369 368 FormatterWarning
370 369 )
371 370
372 371 def lookup(self, obj):
373 372 """Look up the formatter for a given instance.
374 373
375 374 Parameters
376 375 ----------
377 376 obj : object instance
378 377
379 378 Returns
380 379 -------
381 380 f : callable
382 381 The registered formatting callable for the type.
383 382
384 383 Raises
385 384 ------
386 385 KeyError if the type has not been registered.
387 386 """
388 387 # look for singleton first
389 388 obj_id = id(obj)
390 389 if obj_id in self.singleton_printers:
391 390 return self.singleton_printers[obj_id]
392 391 # then lookup by type
393 392 return self.lookup_by_type(_get_type(obj))
394 393
395 394 def lookup_by_type(self, typ):
396 395 """Look up the registered formatter for a type.
397 396
398 397 Parameters
399 398 ----------
400 399 typ : type or '__module__.__name__' string for a type
401 400
402 401 Returns
403 402 -------
404 403 f : callable
405 404 The registered formatting callable for the type.
406 405
407 406 Raises
408 407 ------
409 408 KeyError if the type has not been registered.
410 409 """
411 410 if isinstance(typ, str):
412 411 typ_key = tuple(typ.rsplit('.',1))
413 412 if typ_key not in self.deferred_printers:
414 413 # We may have it cached in the type map. We will have to
415 414 # iterate over all of the types to check.
416 415 for cls in self.type_printers:
417 416 if _mod_name_key(cls) == typ_key:
418 417 return self.type_printers[cls]
419 418 else:
420 419 return self.deferred_printers[typ_key]
421 420 else:
422 421 for cls in pretty._get_mro(typ):
423 422 if cls in self.type_printers or self._in_deferred_types(cls):
424 423 return self.type_printers[cls]
425 424
426 425 # If we have reached here, the lookup failed.
427 426 raise KeyError("No registered printer for {0!r}".format(typ))
428 427
429 428 def for_type(self, typ, func=None):
430 429 """Add a format function for a given type.
431 430
432 431 Parameters
433 432 ----------
434 433 typ : type or '__module__.__name__' string for a type
435 434 The class of the object that will be formatted using `func`.
436 435
437 436 func : callable
438 437 A callable for computing the format data.
439 438 `func` will be called with the object to be formatted,
440 439 and will return the raw data in this formatter's format.
441 440 Subclasses may use a different call signature for the
442 441 `func` argument.
443 442
444 443 If `func` is None or not specified, there will be no change,
445 444 only returning the current value.
446 445
447 446 Returns
448 447 -------
449 448 oldfunc : callable
450 449 The currently registered callable.
451 450 If you are registering a new formatter,
452 451 this will be the previous value (to enable restoring later).
453 452 """
454 453 # if string given, interpret as 'pkg.module.class_name'
455 454 if isinstance(typ, str):
456 455 type_module, type_name = typ.rsplit('.', 1)
457 456 return self.for_type_by_name(type_module, type_name, func)
458 457
459 458 try:
460 459 oldfunc = self.lookup_by_type(typ)
461 460 except KeyError:
462 461 oldfunc = None
463 462
464 463 if func is not None:
465 464 self.type_printers[typ] = func
466 465
467 466 return oldfunc
468 467
469 468 def for_type_by_name(self, type_module, type_name, func=None):
470 469 """Add a format function for a type specified by the full dotted
471 470 module and name of the type, rather than the type of the object.
472 471
473 472 Parameters
474 473 ----------
475 474 type_module : str
476 475 The full dotted name of the module the type is defined in, like
477 476 ``numpy``.
478 477
479 478 type_name : str
480 479 The name of the type (the class name), like ``dtype``
481 480
482 481 func : callable
483 482 A callable for computing the format data.
484 483 `func` will be called with the object to be formatted,
485 484 and will return the raw data in this formatter's format.
486 485 Subclasses may use a different call signature for the
487 486 `func` argument.
488 487
489 488 If `func` is None or unspecified, there will be no change,
490 489 only returning the current value.
491 490
492 491 Returns
493 492 -------
494 493 oldfunc : callable
495 494 The currently registered callable.
496 495 If you are registering a new formatter,
497 496 this will be the previous value (to enable restoring later).
498 497 """
499 498 key = (type_module, type_name)
500 499
501 500 try:
502 501 oldfunc = self.lookup_by_type("%s.%s" % key)
503 502 except KeyError:
504 503 oldfunc = None
505 504
506 505 if func is not None:
507 506 self.deferred_printers[key] = func
508 507 return oldfunc
509 508
510 509 def pop(self, typ, default=_raise_key_error):
511 510 """Pop a formatter for the given type.
512 511
513 512 Parameters
514 513 ----------
515 514 typ : type or '__module__.__name__' string for a type
516 515 default : object
517 516 value to be returned if no formatter is registered for typ.
518 517
519 518 Returns
520 519 -------
521 520 obj : object
522 521 The last registered object for the type.
523 522
524 523 Raises
525 524 ------
526 525 KeyError if the type is not registered and default is not specified.
527 526 """
528 527
529 528 if isinstance(typ, str):
530 529 typ_key = tuple(typ.rsplit('.',1))
531 530 if typ_key not in self.deferred_printers:
532 531 # We may have it cached in the type map. We will have to
533 532 # iterate over all of the types to check.
534 533 for cls in self.type_printers:
535 534 if _mod_name_key(cls) == typ_key:
536 535 old = self.type_printers.pop(cls)
537 536 break
538 537 else:
539 538 old = default
540 539 else:
541 540 old = self.deferred_printers.pop(typ_key)
542 541 else:
543 542 if typ in self.type_printers:
544 543 old = self.type_printers.pop(typ)
545 544 else:
546 545 old = self.deferred_printers.pop(_mod_name_key(typ), default)
547 546 if old is _raise_key_error:
548 547 raise KeyError("No registered value for {0!r}".format(typ))
549 548 return old
550 549
551 550 def _in_deferred_types(self, cls):
552 551 """
553 552 Check if the given class is specified in the deferred type registry.
554 553
555 554 Successful matches will be moved to the regular type registry for future use.
556 555 """
557 556 mod = getattr(cls, '__module__', None)
558 557 name = getattr(cls, '__name__', None)
559 558 key = (mod, name)
560 559 if key in self.deferred_printers:
561 560 # Move the printer over to the regular registry.
562 561 printer = self.deferred_printers.pop(key)
563 562 self.type_printers[cls] = printer
564 563 return True
565 564 return False
566 565
567 566
568 567 class PlainTextFormatter(BaseFormatter):
569 568 """The default pretty-printer.
570 569
571 570 This uses :mod:`IPython.lib.pretty` to compute the format data of
572 571 the object. If the object cannot be pretty printed, :func:`repr` is used.
573 572 See the documentation of :mod:`IPython.lib.pretty` for details on
574 573 how to write pretty printers. Here is a simple example::
575 574
576 575 def dtype_pprinter(obj, p, cycle):
577 576 if cycle:
578 577 return p.text('dtype(...)')
579 578 if hasattr(obj, 'fields'):
580 579 if obj.fields is None:
581 580 p.text(repr(obj))
582 581 else:
583 582 p.begin_group(7, 'dtype([')
584 583 for i, field in enumerate(obj.descr):
585 584 if i > 0:
586 585 p.text(',')
587 586 p.breakable()
588 587 p.pretty(field)
589 588 p.end_group(7, '])')
590 589 """
591 590
592 591 # The format type of data returned.
593 592 format_type = Unicode('text/plain')
594 593
595 594 # This subclass ignores this attribute as it always need to return
596 595 # something.
597 596 enabled = Bool(True).tag(config=False)
598 597
599 598 max_seq_length = Integer(pretty.MAX_SEQ_LENGTH,
600 599 help="""Truncate large collections (lists, dicts, tuples, sets) to this size.
601 600
602 601 Set to 0 to disable truncation.
603 602 """
604 603 ).tag(config=True)
605 604
606 605 # Look for a _repr_pretty_ methods to use for pretty printing.
607 606 print_method = ObjectName('_repr_pretty_')
608 607
609 608 # Whether to pretty-print or not.
610 609 pprint = Bool(True).tag(config=True)
611 610
612 611 # Whether to be verbose or not.
613 612 verbose = Bool(False).tag(config=True)
614 613
615 614 # The maximum width.
616 615 max_width = Integer(79).tag(config=True)
617 616
618 617 # The newline character.
619 618 newline = Unicode('\n').tag(config=True)
620 619
621 620 # format-string for pprinting floats
622 621 float_format = Unicode('%r')
623 622 # setter for float precision, either int or direct format-string
624 623 float_precision = CUnicode('').tag(config=True)
625 624
626 625 @observe('float_precision')
627 626 def _float_precision_changed(self, change):
628 627 """float_precision changed, set float_format accordingly.
629 628
630 629 float_precision can be set by int or str.
631 630 This will set float_format, after interpreting input.
632 631 If numpy has been imported, numpy print precision will also be set.
633 632
634 633 integer `n` sets format to '%.nf', otherwise, format set directly.
635 634
636 635 An empty string returns to defaults (repr for float, 8 for numpy).
637 636
638 637 This parameter can be set via the '%precision' magic.
639 638 """
640 639 new = change['new']
641 640 if '%' in new:
642 641 # got explicit format string
643 642 fmt = new
644 643 try:
645 644 fmt%3.14159
646 645 except Exception as e:
647 646 raise ValueError("Precision must be int or format string, not %r"%new) from e
648 647 elif new:
649 648 # otherwise, should be an int
650 649 try:
651 650 i = int(new)
652 651 assert i >= 0
653 652 except ValueError as e:
654 653 raise ValueError("Precision must be int or format string, not %r"%new) from e
655 654 except AssertionError as e:
656 655 raise ValueError("int precision must be non-negative, not %r"%i) from e
657 656
658 657 fmt = '%%.%if'%i
659 658 if 'numpy' in sys.modules:
660 659 # set numpy precision if it has been imported
661 660 import numpy
662 661 numpy.set_printoptions(precision=i)
663 662 else:
664 663 # default back to repr
665 664 fmt = '%r'
666 665 if 'numpy' in sys.modules:
667 666 import numpy
668 667 # numpy default is 8
669 668 numpy.set_printoptions(precision=8)
670 669 self.float_format = fmt
671 670
672 671 # Use the default pretty printers from IPython.lib.pretty.
673 672 @default('singleton_printers')
674 673 def _singleton_printers_default(self):
675 674 return pretty._singleton_pprinters.copy()
676 675
677 676 @default('type_printers')
678 677 def _type_printers_default(self):
679 678 d = pretty._type_pprinters.copy()
680 679 d[float] = lambda obj,p,cycle: p.text(self.float_format%obj)
681 680 # if NumPy is used, set precision for its float64 type
682 681 if "numpy" in sys.modules:
683 682 import numpy
684 683
685 684 d[numpy.float64] = lambda obj, p, cycle: p.text(self.float_format % obj)
686 685 return d
687 686
688 687 @default('deferred_printers')
689 688 def _deferred_printers_default(self):
690 689 return pretty._deferred_type_pprinters.copy()
691 690
692 691 #### FormatterABC interface ####
693 692
694 693 @catch_format_error
695 694 def __call__(self, obj):
696 695 """Compute the pretty representation of the object."""
697 696 if not self.pprint:
698 697 return repr(obj)
699 698 else:
700 699 stream = StringIO()
701 700 printer = pretty.RepresentationPrinter(stream, self.verbose,
702 701 self.max_width, self.newline,
703 702 max_seq_length=self.max_seq_length,
704 703 singleton_pprinters=self.singleton_printers,
705 704 type_pprinters=self.type_printers,
706 705 deferred_pprinters=self.deferred_printers)
707 706 printer.pretty(obj)
708 707 printer.flush()
709 708 return stream.getvalue()
710 709
711 710
712 711 class HTMLFormatter(BaseFormatter):
713 712 """An HTML formatter.
714 713
715 714 To define the callables that compute the HTML representation of your
716 715 objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
717 716 or :meth:`for_type_by_name` methods to register functions that handle
718 717 this.
719 718
720 719 The return value of this formatter should be a valid HTML snippet that
721 720 could be injected into an existing DOM. It should *not* include the
722 721 ```<html>`` or ```<body>`` tags.
723 722 """
724 723 format_type = Unicode('text/html')
725 724
726 725 print_method = ObjectName('_repr_html_')
727 726
728 727
729 728 class MarkdownFormatter(BaseFormatter):
730 729 """A Markdown formatter.
731 730
732 731 To define the callables that compute the Markdown representation of your
733 732 objects, define a :meth:`_repr_markdown_` method or use the :meth:`for_type`
734 733 or :meth:`for_type_by_name` methods to register functions that handle
735 734 this.
736 735
737 736 The return value of this formatter should be a valid Markdown.
738 737 """
739 738 format_type = Unicode('text/markdown')
740 739
741 740 print_method = ObjectName('_repr_markdown_')
742 741
743 742 class SVGFormatter(BaseFormatter):
744 743 """An SVG formatter.
745 744
746 745 To define the callables that compute the SVG representation of your
747 746 objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
748 747 or :meth:`for_type_by_name` methods to register functions that handle
749 748 this.
750 749
751 750 The return value of this formatter should be valid SVG enclosed in
752 751 ```<svg>``` tags, that could be injected into an existing DOM. It should
753 752 *not* include the ```<html>`` or ```<body>`` tags.
754 753 """
755 754 format_type = Unicode('image/svg+xml')
756 755
757 756 print_method = ObjectName('_repr_svg_')
758 757
759 758
760 759 class PNGFormatter(BaseFormatter):
761 760 """A PNG formatter.
762 761
763 762 To define the callables that compute the PNG representation of your
764 763 objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
765 764 or :meth:`for_type_by_name` methods to register functions that handle
766 765 this.
767 766
768 767 The return value of this formatter should be raw PNG data, *not*
769 768 base64 encoded.
770 769 """
771 770 format_type = Unicode('image/png')
772 771
773 772 print_method = ObjectName('_repr_png_')
774 773
775 774 _return_type = (bytes, str)
776 775
777 776
778 777 class JPEGFormatter(BaseFormatter):
779 778 """A JPEG formatter.
780 779
781 780 To define the callables that compute the JPEG representation of your
782 781 objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type`
783 782 or :meth:`for_type_by_name` methods to register functions that handle
784 783 this.
785 784
786 785 The return value of this formatter should be raw JPEG data, *not*
787 786 base64 encoded.
788 787 """
789 788 format_type = Unicode('image/jpeg')
790 789
791 790 print_method = ObjectName('_repr_jpeg_')
792 791
793 792 _return_type = (bytes, str)
794 793
795 794
796 795 class LatexFormatter(BaseFormatter):
797 796 """A LaTeX formatter.
798 797
799 798 To define the callables that compute the LaTeX representation of your
800 799 objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
801 800 or :meth:`for_type_by_name` methods to register functions that handle
802 801 this.
803 802
804 803 The return value of this formatter should be a valid LaTeX equation,
805 804 enclosed in either ```$```, ```$$``` or another LaTeX equation
806 805 environment.
807 806 """
808 807 format_type = Unicode('text/latex')
809 808
810 809 print_method = ObjectName('_repr_latex_')
811 810
812 811
813 812 class JSONFormatter(BaseFormatter):
814 813 """A JSON string formatter.
815 814
816 815 To define the callables that compute the JSONable representation of
817 816 your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
818 817 or :meth:`for_type_by_name` methods to register functions that handle
819 818 this.
820 819
821 820 The return value of this formatter should be a JSONable list or dict.
822 821 JSON scalars (None, number, string) are not allowed, only dict or list containers.
823 822 """
824 823 format_type = Unicode('application/json')
825 824 _return_type = (list, dict)
826 825
827 826 print_method = ObjectName('_repr_json_')
828 827
829 828 def _check_return(self, r, obj):
830 829 """Check that a return value is appropriate
831 830
832 831 Return the value if so, None otherwise, warning if invalid.
833 832 """
834 833 if r is None:
835 834 return
836 835 md = None
837 836 if isinstance(r, tuple):
838 837 # unpack data, metadata tuple for type checking on first element
839 838 r, md = r
840 839
841 840 assert not isinstance(
842 841 r, str
843 842 ), "JSON-as-string has been deprecated since IPython < 3"
844 843
845 844 if md is not None:
846 845 # put the tuple back together
847 846 r = (r, md)
848 847 return super(JSONFormatter, self)._check_return(r, obj)
849 848
850 849
851 850 class JavascriptFormatter(BaseFormatter):
852 851 """A Javascript formatter.
853 852
854 853 To define the callables that compute the Javascript representation of
855 854 your objects, define a :meth:`_repr_javascript_` method or use the
856 855 :meth:`for_type` or :meth:`for_type_by_name` methods to register functions
857 856 that handle this.
858 857
859 858 The return value of this formatter should be valid Javascript code and
860 859 should *not* be enclosed in ```<script>``` tags.
861 860 """
862 861 format_type = Unicode('application/javascript')
863 862
864 863 print_method = ObjectName('_repr_javascript_')
865 864
866 865
867 866 class PDFFormatter(BaseFormatter):
868 867 """A PDF formatter.
869 868
870 869 To define the callables that compute the PDF representation of your
871 870 objects, define a :meth:`_repr_pdf_` method or use the :meth:`for_type`
872 871 or :meth:`for_type_by_name` methods to register functions that handle
873 872 this.
874 873
875 874 The return value of this formatter should be raw PDF data, *not*
876 875 base64 encoded.
877 876 """
878 877 format_type = Unicode('application/pdf')
879 878
880 879 print_method = ObjectName('_repr_pdf_')
881 880
882 881 _return_type = (bytes, str)
883 882
884 883 class IPythonDisplayFormatter(BaseFormatter):
885 884 """An escape-hatch Formatter for objects that know how to display themselves.
886 885
887 886 To define the callables that compute the representation of your
888 887 objects, define a :meth:`_ipython_display_` method or use the :meth:`for_type`
889 888 or :meth:`for_type_by_name` methods to register functions that handle
890 889 this. Unlike mime-type displays, this method should not return anything,
891 890 instead calling any appropriate display methods itself.
892 891
893 892 This display formatter has highest priority.
894 893 If it fires, no other display formatter will be called.
895 894
896 895 Prior to IPython 6.1, `_ipython_display_` was the only way to display custom mime-types
897 896 without registering a new Formatter.
898 897
899 898 IPython 6.1 introduces `_repr_mimebundle_` for displaying custom mime-types,
900 899 so `_ipython_display_` should only be used for objects that require unusual
901 900 display patterns, such as multiple display calls.
902 901 """
903 902 print_method = ObjectName('_ipython_display_')
904 903 _return_type = (type(None), bool)
905 904
906 905 @catch_format_error
907 906 def __call__(self, obj):
908 907 """Compute the format for an object."""
909 908 if self.enabled:
910 909 # lookup registered printer
911 910 try:
912 911 printer = self.lookup(obj)
913 912 except KeyError:
914 913 pass
915 914 else:
916 915 printer(obj)
917 916 return True
918 917 # Finally look for special method names
919 918 method = get_real_method(obj, self.print_method)
920 919 if method is not None:
921 920 method()
922 921 return True
923 922
924 923
925 924 class MimeBundleFormatter(BaseFormatter):
926 925 """A Formatter for arbitrary mime-types.
927 926
928 927 Unlike other `_repr_<mimetype>_` methods,
929 928 `_repr_mimebundle_` should return mime-bundle data,
930 929 either the mime-keyed `data` dictionary or the tuple `(data, metadata)`.
931 930 Any mime-type is valid.
932 931
933 932 To define the callables that compute the mime-bundle representation of your
934 933 objects, define a :meth:`_repr_mimebundle_` method or use the :meth:`for_type`
935 934 or :meth:`for_type_by_name` methods to register functions that handle
936 935 this.
937 936
938 937 .. versionadded:: 6.1
939 938 """
940 939 print_method = ObjectName('_repr_mimebundle_')
941 940 _return_type = dict
942 941
943 942 def _check_return(self, r, obj):
944 943 r = super(MimeBundleFormatter, self)._check_return(r, obj)
945 944 # always return (data, metadata):
946 945 if r is None:
947 946 return {}, {}
948 947 if not isinstance(r, tuple):
949 948 return r, {}
950 949 return r
951 950
952 951 @catch_format_error
953 952 def __call__(self, obj, include=None, exclude=None):
954 953 """Compute the format for an object.
955 954
956 955 Identical to parent's method but we pass extra parameters to the method.
957 956
958 957 Unlike other _repr_*_ `_repr_mimebundle_` should allow extra kwargs, in
959 958 particular `include` and `exclude`.
960 959 """
961 960 if self.enabled:
962 961 # lookup registered printer
963 962 try:
964 963 printer = self.lookup(obj)
965 964 except KeyError:
966 965 pass
967 966 else:
968 967 return printer(obj)
969 968 # Finally look for special method names
970 969 method = get_real_method(obj, self.print_method)
971 970
972 971 if method is not None:
973 972 return method(include=include, exclude=exclude)
974 973 return None
975 974 else:
976 975 return None
977 976
978 977
979 978 FormatterABC.register(BaseFormatter)
980 979 FormatterABC.register(PlainTextFormatter)
981 980 FormatterABC.register(HTMLFormatter)
982 981 FormatterABC.register(MarkdownFormatter)
983 982 FormatterABC.register(SVGFormatter)
984 983 FormatterABC.register(PNGFormatter)
985 984 FormatterABC.register(PDFFormatter)
986 985 FormatterABC.register(JPEGFormatter)
987 986 FormatterABC.register(LatexFormatter)
988 987 FormatterABC.register(JSONFormatter)
989 988 FormatterABC.register(JavascriptFormatter)
990 989 FormatterABC.register(IPythonDisplayFormatter)
991 990 FormatterABC.register(MimeBundleFormatter)
992 991
993 992
994 993 def format_display_data(obj, include=None, exclude=None):
995 994 """Return a format data dict for an object.
996 995
997 996 By default all format types will be computed.
998 997
999 998 Parameters
1000 999 ----------
1001 1000 obj : object
1002 1001 The Python object whose format data will be computed.
1003 1002
1004 1003 Returns
1005 1004 -------
1006 1005 format_dict : dict
1007 1006 A dictionary of key/value pairs, one or each format that was
1008 1007 generated for the object. The keys are the format types, which
1009 1008 will usually be MIME type strings and the values and JSON'able
1010 1009 data structure containing the raw data for the representation in
1011 1010 that format.
1012 1011 include : list or tuple, optional
1013 1012 A list of format type strings (MIME types) to include in the
1014 1013 format data dict. If this is set *only* the format types included
1015 1014 in this list will be computed.
1016 1015 exclude : list or tuple, optional
1017 1016 A list of format type string (MIME types) to exclude in the format
1018 1017 data dict. If this is set all format types will be computed,
1019 1018 except for those included in this argument.
1020 1019 """
1021 1020 from .interactiveshell import InteractiveShell
1022 1021
1023 1022 return InteractiveShell.instance().display_formatter.format(
1024 1023 obj,
1025 1024 include,
1026 1025 exclude
1027 1026 )
@@ -1,788 +1,787 b''
1 1 """Input transformer machinery to support IPython special syntax.
2 2
3 3 This includes the machinery to recognise and transform ``%magic`` commands,
4 4 ``!system`` commands, ``help?`` querying, prompt stripping, and so forth.
5 5
6 6 Added: IPython 7.0. Replaces inputsplitter and inputtransformer which were
7 7 deprecated in 7.0.
8 8 """
9 9
10 10 # Copyright (c) IPython Development Team.
11 11 # Distributed under the terms of the Modified BSD License.
12 12
13 13 import ast
14 import sys
15 14 from codeop import CommandCompiler, Compile
16 15 import re
17 16 import tokenize
18 17 from typing import List, Tuple, Optional, Any
19 18 import warnings
20 19
21 20 _indent_re = re.compile(r'^[ \t]+')
22 21
23 22 def leading_empty_lines(lines):
24 23 """Remove leading empty lines
25 24
26 25 If the leading lines are empty or contain only whitespace, they will be
27 26 removed.
28 27 """
29 28 if not lines:
30 29 return lines
31 30 for i, line in enumerate(lines):
32 31 if line and not line.isspace():
33 32 return lines[i:]
34 33 return lines
35 34
36 35 def leading_indent(lines):
37 36 """Remove leading indentation.
38 37
39 38 If the first line starts with a spaces or tabs, the same whitespace will be
40 39 removed from each following line in the cell.
41 40 """
42 41 if not lines:
43 42 return lines
44 43 m = _indent_re.match(lines[0])
45 44 if not m:
46 45 return lines
47 46 space = m.group(0)
48 47 n = len(space)
49 48 return [l[n:] if l.startswith(space) else l
50 49 for l in lines]
51 50
52 51 class PromptStripper:
53 52 """Remove matching input prompts from a block of input.
54 53
55 54 Parameters
56 55 ----------
57 56 prompt_re : regular expression
58 57 A regular expression matching any input prompt (including continuation,
59 58 e.g. ``...``)
60 59 initial_re : regular expression, optional
61 60 A regular expression matching only the initial prompt, but not continuation.
62 61 If no initial expression is given, prompt_re will be used everywhere.
63 62 Used mainly for plain Python prompts (``>>>``), where the continuation prompt
64 63 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
65 64
66 65 Notes
67 66 -----
68 67
69 68 If initial_re and prompt_re differ,
70 69 only initial_re will be tested against the first line.
71 70 If any prompt is found on the first two lines,
72 71 prompts will be stripped from the rest of the block.
73 72 """
74 73 def __init__(self, prompt_re, initial_re=None):
75 74 self.prompt_re = prompt_re
76 75 self.initial_re = initial_re or prompt_re
77 76
78 77 def _strip(self, lines):
79 78 return [self.prompt_re.sub('', l, count=1) for l in lines]
80 79
81 80 def __call__(self, lines):
82 81 if not lines:
83 82 return lines
84 83 if self.initial_re.match(lines[0]) or \
85 84 (len(lines) > 1 and self.prompt_re.match(lines[1])):
86 85 return self._strip(lines)
87 86 return lines
88 87
89 88 classic_prompt = PromptStripper(
90 89 prompt_re=re.compile(r'^(>>>|\.\.\.)( |$)'),
91 90 initial_re=re.compile(r'^>>>( |$)')
92 91 )
93 92
94 93 ipython_prompt = PromptStripper(
95 94 re.compile(
96 95 r"""
97 96 ^( # Match from the beginning of a line, either:
98 97
99 98 # 1. First-line prompt:
100 99 ((\[nav\]|\[ins\])?\ )? # Vi editing mode prompt, if it's there
101 100 In\ # The 'In' of the prompt, with a space
102 101 \[\d+\]: # Command index, as displayed in the prompt
103 102 \ # With a mandatory trailing space
104 103
105 104 | # ... or ...
106 105
107 106 # 2. The three dots of the multiline prompt
108 107 \s* # All leading whitespace characters
109 108 \.{3,}: # The three (or more) dots
110 109 \ ? # With an optional trailing space
111 110
112 111 )
113 112 """,
114 113 re.VERBOSE,
115 114 )
116 115 )
117 116
118 117
119 118 def cell_magic(lines):
120 119 if not lines or not lines[0].startswith('%%'):
121 120 return lines
122 121 if re.match(r'%%\w+\?', lines[0]):
123 122 # This case will be handled by help_end
124 123 return lines
125 124 magic_name, _, first_line = lines[0][2:].rstrip().partition(' ')
126 125 body = ''.join(lines[1:])
127 126 return ['get_ipython().run_cell_magic(%r, %r, %r)\n'
128 127 % (magic_name, first_line, body)]
129 128
130 129
131 130 def _find_assign_op(token_line) -> Optional[int]:
132 131 """Get the index of the first assignment in the line ('=' not inside brackets)
133 132
134 133 Note: We don't try to support multiple special assignment (a = b = %foo)
135 134 """
136 135 paren_level = 0
137 136 for i, ti in enumerate(token_line):
138 137 s = ti.string
139 138 if s == '=' and paren_level == 0:
140 139 return i
141 140 if s in {'(','[','{'}:
142 141 paren_level += 1
143 142 elif s in {')', ']', '}'}:
144 143 if paren_level > 0:
145 144 paren_level -= 1
146 145 return None
147 146
148 147 def find_end_of_continued_line(lines, start_line: int):
149 148 """Find the last line of a line explicitly extended using backslashes.
150 149
151 150 Uses 0-indexed line numbers.
152 151 """
153 152 end_line = start_line
154 153 while lines[end_line].endswith('\\\n'):
155 154 end_line += 1
156 155 if end_line >= len(lines):
157 156 break
158 157 return end_line
159 158
160 159 def assemble_continued_line(lines, start: Tuple[int, int], end_line: int):
161 160 r"""Assemble a single line from multiple continued line pieces
162 161
163 162 Continued lines are lines ending in ``\``, and the line following the last
164 163 ``\`` in the block.
165 164
166 165 For example, this code continues over multiple lines::
167 166
168 167 if (assign_ix is not None) \
169 168 and (len(line) >= assign_ix + 2) \
170 169 and (line[assign_ix+1].string == '%') \
171 170 and (line[assign_ix+2].type == tokenize.NAME):
172 171
173 172 This statement contains four continued line pieces.
174 173 Assembling these pieces into a single line would give::
175 174
176 175 if (assign_ix is not None) and (len(line) >= assign_ix + 2) and (line[...
177 176
178 177 This uses 0-indexed line numbers. *start* is (lineno, colno).
179 178
180 179 Used to allow ``%magic`` and ``!system`` commands to be continued over
181 180 multiple lines.
182 181 """
183 182 parts = [lines[start[0]][start[1]:]] + lines[start[0]+1:end_line+1]
184 183 return ' '.join([p.rstrip()[:-1] for p in parts[:-1]] # Strip backslash+newline
185 184 + [parts[-1].rstrip()]) # Strip newline from last line
186 185
187 186 class TokenTransformBase:
188 187 """Base class for transformations which examine tokens.
189 188
190 189 Special syntax should not be transformed when it occurs inside strings or
191 190 comments. This is hard to reliably avoid with regexes. The solution is to
192 191 tokenise the code as Python, and recognise the special syntax in the tokens.
193 192
194 193 IPython's special syntax is not valid Python syntax, so tokenising may go
195 194 wrong after the special syntax starts. These classes therefore find and
196 195 transform *one* instance of special syntax at a time into regular Python
197 196 syntax. After each transformation, tokens are regenerated to find the next
198 197 piece of special syntax.
199 198
200 199 Subclasses need to implement one class method (find)
201 200 and one regular method (transform).
202 201
203 202 The priority attribute can select which transformation to apply if multiple
204 203 transformers match in the same place. Lower numbers have higher priority.
205 204 This allows "%magic?" to be turned into a help call rather than a magic call.
206 205 """
207 206 # Lower numbers -> higher priority (for matches in the same location)
208 207 priority = 10
209 208
210 209 def sortby(self):
211 210 return self.start_line, self.start_col, self.priority
212 211
213 212 def __init__(self, start):
214 213 self.start_line = start[0] - 1 # Shift from 1-index to 0-index
215 214 self.start_col = start[1]
216 215
217 216 @classmethod
218 217 def find(cls, tokens_by_line):
219 218 """Find one instance of special syntax in the provided tokens.
220 219
221 220 Tokens are grouped into logical lines for convenience,
222 221 so it is easy to e.g. look at the first token of each line.
223 222 *tokens_by_line* is a list of lists of tokenize.TokenInfo objects.
224 223
225 224 This should return an instance of its class, pointing to the start
226 225 position it has found, or None if it found no match.
227 226 """
228 227 raise NotImplementedError
229 228
230 229 def transform(self, lines: List[str]):
231 230 """Transform one instance of special syntax found by ``find()``
232 231
233 232 Takes a list of strings representing physical lines,
234 233 returns a similar list of transformed lines.
235 234 """
236 235 raise NotImplementedError
237 236
238 237 class MagicAssign(TokenTransformBase):
239 238 """Transformer for assignments from magics (a = %foo)"""
240 239 @classmethod
241 240 def find(cls, tokens_by_line):
242 241 """Find the first magic assignment (a = %foo) in the cell.
243 242 """
244 243 for line in tokens_by_line:
245 244 assign_ix = _find_assign_op(line)
246 245 if (assign_ix is not None) \
247 246 and (len(line) >= assign_ix + 2) \
248 247 and (line[assign_ix+1].string == '%') \
249 248 and (line[assign_ix+2].type == tokenize.NAME):
250 249 return cls(line[assign_ix+1].start)
251 250
252 251 def transform(self, lines: List[str]):
253 252 """Transform a magic assignment found by the ``find()`` classmethod.
254 253 """
255 254 start_line, start_col = self.start_line, self.start_col
256 255 lhs = lines[start_line][:start_col]
257 256 end_line = find_end_of_continued_line(lines, start_line)
258 257 rhs = assemble_continued_line(lines, (start_line, start_col), end_line)
259 258 assert rhs.startswith('%'), rhs
260 259 magic_name, _, args = rhs[1:].partition(' ')
261 260
262 261 lines_before = lines[:start_line]
263 262 call = "get_ipython().run_line_magic({!r}, {!r})".format(magic_name, args)
264 263 new_line = lhs + call + '\n'
265 264 lines_after = lines[end_line+1:]
266 265
267 266 return lines_before + [new_line] + lines_after
268 267
269 268
270 269 class SystemAssign(TokenTransformBase):
271 270 """Transformer for assignments from system commands (a = !foo)"""
272 271 @classmethod
273 272 def find(cls, tokens_by_line):
274 273 """Find the first system assignment (a = !foo) in the cell.
275 274 """
276 275 for line in tokens_by_line:
277 276 assign_ix = _find_assign_op(line)
278 277 if (assign_ix is not None) \
279 278 and not line[assign_ix].line.strip().startswith('=') \
280 279 and (len(line) >= assign_ix + 2) \
281 280 and (line[assign_ix + 1].type == tokenize.ERRORTOKEN):
282 281 ix = assign_ix + 1
283 282
284 283 while ix < len(line) and line[ix].type == tokenize.ERRORTOKEN:
285 284 if line[ix].string == '!':
286 285 return cls(line[ix].start)
287 286 elif not line[ix].string.isspace():
288 287 break
289 288 ix += 1
290 289
291 290 def transform(self, lines: List[str]):
292 291 """Transform a system assignment found by the ``find()`` classmethod.
293 292 """
294 293 start_line, start_col = self.start_line, self.start_col
295 294
296 295 lhs = lines[start_line][:start_col]
297 296 end_line = find_end_of_continued_line(lines, start_line)
298 297 rhs = assemble_continued_line(lines, (start_line, start_col), end_line)
299 298 assert rhs.startswith('!'), rhs
300 299 cmd = rhs[1:]
301 300
302 301 lines_before = lines[:start_line]
303 302 call = "get_ipython().getoutput({!r})".format(cmd)
304 303 new_line = lhs + call + '\n'
305 304 lines_after = lines[end_line + 1:]
306 305
307 306 return lines_before + [new_line] + lines_after
308 307
309 308 # The escape sequences that define the syntax transformations IPython will
310 309 # apply to user input. These can NOT be just changed here: many regular
311 310 # expressions and other parts of the code may use their hardcoded values, and
312 311 # for all intents and purposes they constitute the 'IPython syntax', so they
313 312 # should be considered fixed.
314 313
315 314 ESC_SHELL = '!' # Send line to underlying system shell
316 315 ESC_SH_CAP = '!!' # Send line to system shell and capture output
317 316 ESC_HELP = '?' # Find information about object
318 317 ESC_HELP2 = '??' # Find extra-detailed information about object
319 318 ESC_MAGIC = '%' # Call magic function
320 319 ESC_MAGIC2 = '%%' # Call cell-magic function
321 320 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
322 321 ESC_QUOTE2 = ';' # Quote all args as a single string, call
323 322 ESC_PAREN = '/' # Call first argument with rest of line as arguments
324 323
325 324 ESCAPE_SINGLES = {'!', '?', '%', ',', ';', '/'}
326 325 ESCAPE_DOUBLES = {'!!', '??'} # %% (cell magic) is handled separately
327 326
328 327 def _make_help_call(target, esc):
329 328 """Prepares a pinfo(2)/psearch call from a target name and the escape
330 329 (i.e. ? or ??)"""
331 330 method = 'pinfo2' if esc == '??' \
332 331 else 'psearch' if '*' in target \
333 332 else 'pinfo'
334 333 arg = " ".join([method, target])
335 334 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
336 335 t_magic_name, _, t_magic_arg_s = arg.partition(' ')
337 336 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
338 337 return "get_ipython().run_line_magic(%r, %r)" % (t_magic_name, t_magic_arg_s)
339 338
340 339
341 340 def _tr_help(content):
342 341 """Translate lines escaped with: ?
343 342
344 343 A naked help line should fire the intro help screen (shell.show_usage())
345 344 """
346 345 if not content:
347 346 return 'get_ipython().show_usage()'
348 347
349 348 return _make_help_call(content, '?')
350 349
351 350 def _tr_help2(content):
352 351 """Translate lines escaped with: ??
353 352
354 353 A naked help line should fire the intro help screen (shell.show_usage())
355 354 """
356 355 if not content:
357 356 return 'get_ipython().show_usage()'
358 357
359 358 return _make_help_call(content, '??')
360 359
361 360 def _tr_magic(content):
362 361 "Translate lines escaped with a percent sign: %"
363 362 name, _, args = content.partition(' ')
364 363 return 'get_ipython().run_line_magic(%r, %r)' % (name, args)
365 364
366 365 def _tr_quote(content):
367 366 "Translate lines escaped with a comma: ,"
368 367 name, _, args = content.partition(' ')
369 368 return '%s("%s")' % (name, '", "'.join(args.split()) )
370 369
371 370 def _tr_quote2(content):
372 371 "Translate lines escaped with a semicolon: ;"
373 372 name, _, args = content.partition(' ')
374 373 return '%s("%s")' % (name, args)
375 374
376 375 def _tr_paren(content):
377 376 "Translate lines escaped with a slash: /"
378 377 name, _, args = content.partition(' ')
379 378 return '%s(%s)' % (name, ", ".join(args.split()))
380 379
381 380 tr = { ESC_SHELL : 'get_ipython().system({!r})'.format,
382 381 ESC_SH_CAP : 'get_ipython().getoutput({!r})'.format,
383 382 ESC_HELP : _tr_help,
384 383 ESC_HELP2 : _tr_help2,
385 384 ESC_MAGIC : _tr_magic,
386 385 ESC_QUOTE : _tr_quote,
387 386 ESC_QUOTE2 : _tr_quote2,
388 387 ESC_PAREN : _tr_paren }
389 388
390 389 class EscapedCommand(TokenTransformBase):
391 390 """Transformer for escaped commands like %foo, !foo, or /foo"""
392 391 @classmethod
393 392 def find(cls, tokens_by_line):
394 393 """Find the first escaped command (%foo, !foo, etc.) in the cell.
395 394 """
396 395 for line in tokens_by_line:
397 396 if not line:
398 397 continue
399 398 ix = 0
400 399 ll = len(line)
401 400 while ll > ix and line[ix].type in {tokenize.INDENT, tokenize.DEDENT}:
402 401 ix += 1
403 402 if ix >= ll:
404 403 continue
405 404 if line[ix].string in ESCAPE_SINGLES:
406 405 return cls(line[ix].start)
407 406
408 407 def transform(self, lines):
409 408 """Transform an escaped line found by the ``find()`` classmethod.
410 409 """
411 410 start_line, start_col = self.start_line, self.start_col
412 411
413 412 indent = lines[start_line][:start_col]
414 413 end_line = find_end_of_continued_line(lines, start_line)
415 414 line = assemble_continued_line(lines, (start_line, start_col), end_line)
416 415
417 416 if len(line) > 1 and line[:2] in ESCAPE_DOUBLES:
418 417 escape, content = line[:2], line[2:]
419 418 else:
420 419 escape, content = line[:1], line[1:]
421 420
422 421 if escape in tr:
423 422 call = tr[escape](content)
424 423 else:
425 424 call = ''
426 425
427 426 lines_before = lines[:start_line]
428 427 new_line = indent + call + '\n'
429 428 lines_after = lines[end_line + 1:]
430 429
431 430 return lines_before + [new_line] + lines_after
432 431
433 432 _help_end_re = re.compile(r"""(%{0,2}
434 433 (?!\d)[\w*]+ # Variable name
435 434 (\.(?!\d)[\w*]+)* # .etc.etc
436 435 )
437 436 (\?\??)$ # ? or ??
438 437 """,
439 438 re.VERBOSE)
440 439
441 440 class HelpEnd(TokenTransformBase):
442 441 """Transformer for help syntax: obj? and obj??"""
443 442 # This needs to be higher priority (lower number) than EscapedCommand so
444 443 # that inspecting magics (%foo?) works.
445 444 priority = 5
446 445
447 446 def __init__(self, start, q_locn):
448 447 super().__init__(start)
449 448 self.q_line = q_locn[0] - 1 # Shift from 1-indexed to 0-indexed
450 449 self.q_col = q_locn[1]
451 450
452 451 @classmethod
453 452 def find(cls, tokens_by_line):
454 453 """Find the first help command (foo?) in the cell.
455 454 """
456 455 for line in tokens_by_line:
457 456 # Last token is NEWLINE; look at last but one
458 457 if len(line) > 2 and line[-2].string == '?':
459 458 # Find the first token that's not INDENT/DEDENT
460 459 ix = 0
461 460 while line[ix].type in {tokenize.INDENT, tokenize.DEDENT}:
462 461 ix += 1
463 462 return cls(line[ix].start, line[-2].start)
464 463
465 464 def transform(self, lines):
466 465 """Transform a help command found by the ``find()`` classmethod.
467 466 """
468 467 piece = ''.join(lines[self.start_line:self.q_line+1])
469 468 indent, content = piece[:self.start_col], piece[self.start_col:]
470 469 lines_before = lines[:self.start_line]
471 470 lines_after = lines[self.q_line + 1:]
472 471
473 472 m = _help_end_re.search(content)
474 473 if not m:
475 474 raise SyntaxError(content)
476 475 assert m is not None, content
477 476 target = m.group(1)
478 477 esc = m.group(3)
479 478
480 479
481 480 call = _make_help_call(target, esc)
482 481 new_line = indent + call + '\n'
483 482
484 483 return lines_before + [new_line] + lines_after
485 484
486 485 def make_tokens_by_line(lines:List[str]):
487 486 """Tokenize a series of lines and group tokens by line.
488 487
489 488 The tokens for a multiline Python string or expression are grouped as one
490 489 line. All lines except the last lines should keep their line ending ('\\n',
491 490 '\\r\\n') for this to properly work. Use `.splitlines(keeplineending=True)`
492 491 for example when passing block of text to this function.
493 492
494 493 """
495 494 # NL tokens are used inside multiline expressions, but also after blank
496 495 # lines or comments. This is intentional - see https://bugs.python.org/issue17061
497 496 # We want to group the former case together but split the latter, so we
498 497 # track parentheses level, similar to the internals of tokenize.
499 498
500 499 # reexported from token on 3.7+
501 500 NEWLINE, NL = tokenize.NEWLINE, tokenize.NL # type: ignore
502 501 tokens_by_line: List[List[Any]] = [[]]
503 502 if len(lines) > 1 and not lines[0].endswith(("\n", "\r", "\r\n", "\x0b", "\x0c")):
504 503 warnings.warn(
505 504 "`make_tokens_by_line` received a list of lines which do not have lineending markers ('\\n', '\\r', '\\r\\n', '\\x0b', '\\x0c'), behavior will be unspecified",
506 505 stacklevel=2,
507 506 )
508 507 parenlev = 0
509 508 try:
510 509 for token in tokenize.generate_tokens(iter(lines).__next__):
511 510 tokens_by_line[-1].append(token)
512 511 if (token.type == NEWLINE) \
513 512 or ((token.type == NL) and (parenlev <= 0)):
514 513 tokens_by_line.append([])
515 514 elif token.string in {'(', '[', '{'}:
516 515 parenlev += 1
517 516 elif token.string in {')', ']', '}'}:
518 517 if parenlev > 0:
519 518 parenlev -= 1
520 519 except tokenize.TokenError:
521 520 # Input ended in a multiline string or expression. That's OK for us.
522 521 pass
523 522
524 523
525 524 if not tokens_by_line[-1]:
526 525 tokens_by_line.pop()
527 526
528 527
529 528 return tokens_by_line
530 529
531 530
532 531 def has_sunken_brackets(tokens: List[tokenize.TokenInfo]):
533 532 """Check if the depth of brackets in the list of tokens drops below 0"""
534 533 parenlev = 0
535 534 for token in tokens:
536 535 if token.string in {"(", "[", "{"}:
537 536 parenlev += 1
538 537 elif token.string in {")", "]", "}"}:
539 538 parenlev -= 1
540 539 if parenlev < 0:
541 540 return True
542 541 return False
543 542
544 543
545 544 def show_linewise_tokens(s: str):
546 545 """For investigation and debugging"""
547 546 if not s.endswith('\n'):
548 547 s += '\n'
549 548 lines = s.splitlines(keepends=True)
550 549 for line in make_tokens_by_line(lines):
551 550 print("Line -------")
552 551 for tokinfo in line:
553 552 print(" ", tokinfo)
554 553
555 554 # Arbitrary limit to prevent getting stuck in infinite loops
556 555 TRANSFORM_LOOP_LIMIT = 500
557 556
558 557 class TransformerManager:
559 558 """Applies various transformations to a cell or code block.
560 559
561 560 The key methods for external use are ``transform_cell()``
562 561 and ``check_complete()``.
563 562 """
564 563 def __init__(self):
565 564 self.cleanup_transforms = [
566 565 leading_empty_lines,
567 566 leading_indent,
568 567 classic_prompt,
569 568 ipython_prompt,
570 569 ]
571 570 self.line_transforms = [
572 571 cell_magic,
573 572 ]
574 573 self.token_transformers = [
575 574 MagicAssign,
576 575 SystemAssign,
577 576 EscapedCommand,
578 577 HelpEnd,
579 578 ]
580 579
581 580 def do_one_token_transform(self, lines):
582 581 """Find and run the transform earliest in the code.
583 582
584 583 Returns (changed, lines).
585 584
586 585 This method is called repeatedly until changed is False, indicating
587 586 that all available transformations are complete.
588 587
589 588 The tokens following IPython special syntax might not be valid, so
590 589 the transformed code is retokenised every time to identify the next
591 590 piece of special syntax. Hopefully long code cells are mostly valid
592 591 Python, not using lots of IPython special syntax, so this shouldn't be
593 592 a performance issue.
594 593 """
595 594 tokens_by_line = make_tokens_by_line(lines)
596 595 candidates = []
597 596 for transformer_cls in self.token_transformers:
598 597 transformer = transformer_cls.find(tokens_by_line)
599 598 if transformer:
600 599 candidates.append(transformer)
601 600
602 601 if not candidates:
603 602 # Nothing to transform
604 603 return False, lines
605 604 ordered_transformers = sorted(candidates, key=TokenTransformBase.sortby)
606 605 for transformer in ordered_transformers:
607 606 try:
608 607 return True, transformer.transform(lines)
609 608 except SyntaxError:
610 609 pass
611 610 return False, lines
612 611
613 612 def do_token_transforms(self, lines):
614 613 for _ in range(TRANSFORM_LOOP_LIMIT):
615 614 changed, lines = self.do_one_token_transform(lines)
616 615 if not changed:
617 616 return lines
618 617
619 618 raise RuntimeError("Input transformation still changing after "
620 619 "%d iterations. Aborting." % TRANSFORM_LOOP_LIMIT)
621 620
622 621 def transform_cell(self, cell: str) -> str:
623 622 """Transforms a cell of input code"""
624 623 if not cell.endswith('\n'):
625 624 cell += '\n' # Ensure the cell has a trailing newline
626 625 lines = cell.splitlines(keepends=True)
627 626 for transform in self.cleanup_transforms + self.line_transforms:
628 627 lines = transform(lines)
629 628
630 629 lines = self.do_token_transforms(lines)
631 630 return ''.join(lines)
632 631
633 632 def check_complete(self, cell: str):
634 633 """Return whether a block of code is ready to execute, or should be continued
635 634
636 635 Parameters
637 636 ----------
638 637 cell : string
639 638 Python input code, which can be multiline.
640 639
641 640 Returns
642 641 -------
643 642 status : str
644 643 One of 'complete', 'incomplete', or 'invalid' if source is not a
645 644 prefix of valid code.
646 645 indent_spaces : int or None
647 646 The number of spaces by which to indent the next line of code. If
648 647 status is not 'incomplete', this is None.
649 648 """
650 649 # Remember if the lines ends in a new line.
651 650 ends_with_newline = False
652 651 for character in reversed(cell):
653 652 if character == '\n':
654 653 ends_with_newline = True
655 654 break
656 655 elif character.strip():
657 656 break
658 657 else:
659 658 continue
660 659
661 660 if not ends_with_newline:
662 661 # Append an newline for consistent tokenization
663 662 # See https://bugs.python.org/issue33899
664 663 cell += '\n'
665 664
666 665 lines = cell.splitlines(keepends=True)
667 666
668 667 if not lines:
669 668 return 'complete', None
670 669
671 670 if lines[-1].endswith('\\'):
672 671 # Explicit backslash continuation
673 672 return 'incomplete', find_last_indent(lines)
674 673
675 674 try:
676 675 for transform in self.cleanup_transforms:
677 676 if not getattr(transform, 'has_side_effects', False):
678 677 lines = transform(lines)
679 678 except SyntaxError:
680 679 return 'invalid', None
681 680
682 681 if lines[0].startswith('%%'):
683 682 # Special case for cell magics - completion marked by blank line
684 683 if lines[-1].strip():
685 684 return 'incomplete', find_last_indent(lines)
686 685 else:
687 686 return 'complete', None
688 687
689 688 try:
690 689 for transform in self.line_transforms:
691 690 if not getattr(transform, 'has_side_effects', False):
692 691 lines = transform(lines)
693 692 lines = self.do_token_transforms(lines)
694 693 except SyntaxError:
695 694 return 'invalid', None
696 695
697 696 tokens_by_line = make_tokens_by_line(lines)
698 697
699 698 # Bail if we got one line and there are more closing parentheses than
700 699 # the opening ones
701 700 if (
702 701 len(lines) == 1
703 702 and tokens_by_line
704 703 and has_sunken_brackets(tokens_by_line[0])
705 704 ):
706 705 return "invalid", None
707 706
708 707 if not tokens_by_line:
709 708 return 'incomplete', find_last_indent(lines)
710 709
711 710 if tokens_by_line[-1][-1].type != tokenize.ENDMARKER:
712 711 # We're in a multiline string or expression
713 712 return 'incomplete', find_last_indent(lines)
714 713
715 714 newline_types = {tokenize.NEWLINE, tokenize.COMMENT, tokenize.ENDMARKER} # type: ignore
716 715
717 716 # Pop the last line which only contains DEDENTs and ENDMARKER
718 717 last_token_line = None
719 718 if {t.type for t in tokens_by_line[-1]} in [
720 719 {tokenize.DEDENT, tokenize.ENDMARKER},
721 720 {tokenize.ENDMARKER}
722 721 ] and len(tokens_by_line) > 1:
723 722 last_token_line = tokens_by_line.pop()
724 723
725 724 while tokens_by_line[-1] and tokens_by_line[-1][-1].type in newline_types:
726 725 tokens_by_line[-1].pop()
727 726
728 727 if not tokens_by_line[-1]:
729 728 return 'incomplete', find_last_indent(lines)
730 729
731 730 if tokens_by_line[-1][-1].string == ':':
732 731 # The last line starts a block (e.g. 'if foo:')
733 732 ix = 0
734 733 while tokens_by_line[-1][ix].type in {tokenize.INDENT, tokenize.DEDENT}:
735 734 ix += 1
736 735
737 736 indent = tokens_by_line[-1][ix].start[1]
738 737 return 'incomplete', indent + 4
739 738
740 739 if tokens_by_line[-1][0].line.endswith('\\'):
741 740 return 'incomplete', None
742 741
743 742 # At this point, our checks think the code is complete (or invalid).
744 743 # We'll use codeop.compile_command to check this with the real parser
745 744 try:
746 745 with warnings.catch_warnings():
747 746 warnings.simplefilter('error', SyntaxWarning)
748 747 res = compile_command(''.join(lines), symbol='exec')
749 748 except (SyntaxError, OverflowError, ValueError, TypeError,
750 749 MemoryError, SyntaxWarning):
751 750 return 'invalid', None
752 751 else:
753 752 if res is None:
754 753 return 'incomplete', find_last_indent(lines)
755 754
756 755 if last_token_line and last_token_line[0].type == tokenize.DEDENT:
757 756 if ends_with_newline:
758 757 return 'complete', None
759 758 return 'incomplete', find_last_indent(lines)
760 759
761 760 # If there's a blank line at the end, assume we're ready to execute
762 761 if not lines[-1].strip():
763 762 return 'complete', None
764 763
765 764 return 'complete', None
766 765
767 766
768 767 def find_last_indent(lines):
769 768 m = _indent_re.match(lines[-1])
770 769 if not m:
771 770 return 0
772 771 return len(m.group(0).replace('\t', ' '*4))
773 772
774 773
775 774 class MaybeAsyncCompile(Compile):
776 775 def __init__(self, extra_flags=0):
777 776 super().__init__()
778 777 self.flags |= extra_flags
779 778
780 779
781 780 class MaybeAsyncCommandCompiler(CommandCompiler):
782 781 def __init__(self, extra_flags=0):
783 782 self.compiler = MaybeAsyncCompile(extra_flags=extra_flags)
784 783
785 784
786 785 _extra_flags = ast.PyCF_ALLOW_TOP_LEVEL_AWAIT
787 786
788 787 compile_command = MaybeAsyncCommandCompiler(extra_flags=_extra_flags)
@@ -1,659 +1,658 b''
1 1 """Implementation of basic magic functions."""
2 2
3 3
4 import argparse
5 4 from logging import error
6 5 import io
7 6 import os
8 7 from pprint import pformat
9 8 import sys
10 9 from warnings import warn
11 10
12 11 from traitlets.utils.importstring import import_item
13 12 from IPython.core import magic_arguments, page
14 13 from IPython.core.error import UsageError
15 14 from IPython.core.magic import Magics, magics_class, line_magic, magic_escapes
16 15 from IPython.utils.text import format_screen, dedent, indent
17 16 from IPython.testing.skipdoctest import skip_doctest
18 17 from IPython.utils.ipstruct import Struct
19 18
20 19
21 20 class MagicsDisplay(object):
22 21 def __init__(self, magics_manager, ignore=None):
23 22 self.ignore = ignore if ignore else []
24 23 self.magics_manager = magics_manager
25 24
26 25 def _lsmagic(self):
27 26 """The main implementation of the %lsmagic"""
28 27 mesc = magic_escapes['line']
29 28 cesc = magic_escapes['cell']
30 29 mman = self.magics_manager
31 30 magics = mman.lsmagic()
32 31 out = ['Available line magics:',
33 32 mesc + (' '+mesc).join(sorted([m for m,v in magics['line'].items() if (v not in self.ignore)])),
34 33 '',
35 34 'Available cell magics:',
36 35 cesc + (' '+cesc).join(sorted([m for m,v in magics['cell'].items() if (v not in self.ignore)])),
37 36 '',
38 37 mman.auto_status()]
39 38 return '\n'.join(out)
40 39
41 40 def _repr_pretty_(self, p, cycle):
42 41 p.text(self._lsmagic())
43 42
44 43 def __str__(self):
45 44 return self._lsmagic()
46 45
47 46 def _jsonable(self):
48 47 """turn magics dict into jsonable dict of the same structure
49 48
50 49 replaces object instances with their class names as strings
51 50 """
52 51 magic_dict = {}
53 52 mman = self.magics_manager
54 53 magics = mman.lsmagic()
55 54 for key, subdict in magics.items():
56 55 d = {}
57 56 magic_dict[key] = d
58 57 for name, obj in subdict.items():
59 58 try:
60 59 classname = obj.__self__.__class__.__name__
61 60 except AttributeError:
62 61 classname = 'Other'
63 62
64 63 d[name] = classname
65 64 return magic_dict
66 65
67 66 def _repr_json_(self):
68 67 return self._jsonable()
69 68
70 69
71 70 @magics_class
72 71 class BasicMagics(Magics):
73 72 """Magics that provide central IPython functionality.
74 73
75 74 These are various magics that don't fit into specific categories but that
76 75 are all part of the base 'IPython experience'."""
77 76
78 77 @skip_doctest
79 78 @magic_arguments.magic_arguments()
80 79 @magic_arguments.argument(
81 80 '-l', '--line', action='store_true',
82 81 help="""Create a line magic alias."""
83 82 )
84 83 @magic_arguments.argument(
85 84 '-c', '--cell', action='store_true',
86 85 help="""Create a cell magic alias."""
87 86 )
88 87 @magic_arguments.argument(
89 88 'name',
90 89 help="""Name of the magic to be created."""
91 90 )
92 91 @magic_arguments.argument(
93 92 'target',
94 93 help="""Name of the existing line or cell magic."""
95 94 )
96 95 @magic_arguments.argument(
97 96 '-p', '--params', default=None,
98 97 help="""Parameters passed to the magic function."""
99 98 )
100 99 @line_magic
101 100 def alias_magic(self, line=''):
102 101 """Create an alias for an existing line or cell magic.
103 102
104 103 Examples
105 104 --------
106 105 ::
107 106
108 107 In [1]: %alias_magic t timeit
109 108 Created `%t` as an alias for `%timeit`.
110 109 Created `%%t` as an alias for `%%timeit`.
111 110
112 111 In [2]: %t -n1 pass
113 112 1 loops, best of 3: 954 ns per loop
114 113
115 114 In [3]: %%t -n1
116 115 ...: pass
117 116 ...:
118 117 1 loops, best of 3: 954 ns per loop
119 118
120 119 In [4]: %alias_magic --cell whereami pwd
121 120 UsageError: Cell magic function `%%pwd` not found.
122 121 In [5]: %alias_magic --line whereami pwd
123 122 Created `%whereami` as an alias for `%pwd`.
124 123
125 124 In [6]: %whereami
126 125 Out[6]: u'/home/testuser'
127 126
128 127 In [7]: %alias_magic h history "-p -l 30" --line
129 128 Created `%h` as an alias for `%history -l 30`.
130 129 """
131 130
132 131 args = magic_arguments.parse_argstring(self.alias_magic, line)
133 132 shell = self.shell
134 133 mman = self.shell.magics_manager
135 134 escs = ''.join(magic_escapes.values())
136 135
137 136 target = args.target.lstrip(escs)
138 137 name = args.name.lstrip(escs)
139 138
140 139 params = args.params
141 140 if (params and
142 141 ((params.startswith('"') and params.endswith('"'))
143 142 or (params.startswith("'") and params.endswith("'")))):
144 143 params = params[1:-1]
145 144
146 145 # Find the requested magics.
147 146 m_line = shell.find_magic(target, 'line')
148 147 m_cell = shell.find_magic(target, 'cell')
149 148 if args.line and m_line is None:
150 149 raise UsageError('Line magic function `%s%s` not found.' %
151 150 (magic_escapes['line'], target))
152 151 if args.cell and m_cell is None:
153 152 raise UsageError('Cell magic function `%s%s` not found.' %
154 153 (magic_escapes['cell'], target))
155 154
156 155 # If --line and --cell are not specified, default to the ones
157 156 # that are available.
158 157 if not args.line and not args.cell:
159 158 if not m_line and not m_cell:
160 159 raise UsageError(
161 160 'No line or cell magic with name `%s` found.' % target
162 161 )
163 162 args.line = bool(m_line)
164 163 args.cell = bool(m_cell)
165 164
166 165 params_str = "" if params is None else " " + params
167 166
168 167 if args.line:
169 168 mman.register_alias(name, target, 'line', params)
170 169 print('Created `%s%s` as an alias for `%s%s%s`.' % (
171 170 magic_escapes['line'], name,
172 171 magic_escapes['line'], target, params_str))
173 172
174 173 if args.cell:
175 174 mman.register_alias(name, target, 'cell', params)
176 175 print('Created `%s%s` as an alias for `%s%s%s`.' % (
177 176 magic_escapes['cell'], name,
178 177 magic_escapes['cell'], target, params_str))
179 178
180 179 @line_magic
181 180 def lsmagic(self, parameter_s=''):
182 181 """List currently available magic functions."""
183 182 return MagicsDisplay(self.shell.magics_manager, ignore=[])
184 183
185 184 def _magic_docs(self, brief=False, rest=False):
186 185 """Return docstrings from magic functions."""
187 186 mman = self.shell.magics_manager
188 187 docs = mman.lsmagic_docs(brief, missing='No documentation')
189 188
190 189 if rest:
191 190 format_string = '**%s%s**::\n\n%s\n\n'
192 191 else:
193 192 format_string = '%s%s:\n%s\n'
194 193
195 194 return ''.join(
196 195 [format_string % (magic_escapes['line'], fname,
197 196 indent(dedent(fndoc)))
198 197 for fname, fndoc in sorted(docs['line'].items())]
199 198 +
200 199 [format_string % (magic_escapes['cell'], fname,
201 200 indent(dedent(fndoc)))
202 201 for fname, fndoc in sorted(docs['cell'].items())]
203 202 )
204 203
205 204 @line_magic
206 205 def magic(self, parameter_s=''):
207 206 """Print information about the magic function system.
208 207
209 208 Supported formats: -latex, -brief, -rest
210 209 """
211 210
212 211 mode = ''
213 212 try:
214 213 mode = parameter_s.split()[0][1:]
215 214 except IndexError:
216 215 pass
217 216
218 217 brief = (mode == 'brief')
219 218 rest = (mode == 'rest')
220 219 magic_docs = self._magic_docs(brief, rest)
221 220
222 221 if mode == 'latex':
223 222 print(self.format_latex(magic_docs))
224 223 return
225 224 else:
226 225 magic_docs = format_screen(magic_docs)
227 226
228 227 out = ["""
229 228 IPython's 'magic' functions
230 229 ===========================
231 230
232 231 The magic function system provides a series of functions which allow you to
233 232 control the behavior of IPython itself, plus a lot of system-type
234 233 features. There are two kinds of magics, line-oriented and cell-oriented.
235 234
236 235 Line magics are prefixed with the % character and work much like OS
237 236 command-line calls: they get as an argument the rest of the line, where
238 237 arguments are passed without parentheses or quotes. For example, this will
239 238 time the given statement::
240 239
241 240 %timeit range(1000)
242 241
243 242 Cell magics are prefixed with a double %%, and they are functions that get as
244 243 an argument not only the rest of the line, but also the lines below it in a
245 244 separate argument. These magics are called with two arguments: the rest of the
246 245 call line and the body of the cell, consisting of the lines below the first.
247 246 For example::
248 247
249 248 %%timeit x = numpy.random.randn((100, 100))
250 249 numpy.linalg.svd(x)
251 250
252 251 will time the execution of the numpy svd routine, running the assignment of x
253 252 as part of the setup phase, which is not timed.
254 253
255 254 In a line-oriented client (the terminal or Qt console IPython), starting a new
256 255 input with %% will automatically enter cell mode, and IPython will continue
257 256 reading input until a blank line is given. In the notebook, simply type the
258 257 whole cell as one entity, but keep in mind that the %% escape can only be at
259 258 the very start of the cell.
260 259
261 260 NOTE: If you have 'automagic' enabled (via the command line option or with the
262 261 %automagic function), you don't need to type in the % explicitly for line
263 262 magics; cell magics always require an explicit '%%' escape. By default,
264 263 IPython ships with automagic on, so you should only rarely need the % escape.
265 264
266 265 Example: typing '%cd mydir' (without the quotes) changes your working directory
267 266 to 'mydir', if it exists.
268 267
269 268 For a list of the available magic functions, use %lsmagic. For a description
270 269 of any of them, type %magic_name?, e.g. '%cd?'.
271 270
272 271 Currently the magic system has the following functions:""",
273 272 magic_docs,
274 273 "Summary of magic functions (from %slsmagic):" % magic_escapes['line'],
275 274 str(self.lsmagic()),
276 275 ]
277 276 page.page('\n'.join(out))
278 277
279 278
280 279 @line_magic
281 280 def page(self, parameter_s=''):
282 281 """Pretty print the object and display it through a pager.
283 282
284 283 %page [options] OBJECT
285 284
286 285 If no object is given, use _ (last output).
287 286
288 287 Options:
289 288
290 289 -r: page str(object), don't pretty-print it."""
291 290
292 291 # After a function contributed by Olivier Aubert, slightly modified.
293 292
294 293 # Process options/args
295 294 opts, args = self.parse_options(parameter_s, 'r')
296 295 raw = 'r' in opts
297 296
298 297 oname = args and args or '_'
299 298 info = self.shell._ofind(oname)
300 299 if info['found']:
301 300 txt = (raw and str or pformat)( info['obj'] )
302 301 page.page(txt)
303 302 else:
304 303 print('Object `%s` not found' % oname)
305 304
306 305 @line_magic
307 306 def pprint(self, parameter_s=''):
308 307 """Toggle pretty printing on/off."""
309 308 ptformatter = self.shell.display_formatter.formatters['text/plain']
310 309 ptformatter.pprint = bool(1 - ptformatter.pprint)
311 310 print('Pretty printing has been turned',
312 311 ['OFF','ON'][ptformatter.pprint])
313 312
314 313 @line_magic
315 314 def colors(self, parameter_s=''):
316 315 """Switch color scheme for prompts, info system and exception handlers.
317 316
318 317 Currently implemented schemes: NoColor, Linux, LightBG.
319 318
320 319 Color scheme names are not case-sensitive.
321 320
322 321 Examples
323 322 --------
324 323 To get a plain black and white terminal::
325 324
326 325 %colors nocolor
327 326 """
328 327 def color_switch_err(name):
329 328 warn('Error changing %s color schemes.\n%s' %
330 329 (name, sys.exc_info()[1]), stacklevel=2)
331 330
332 331
333 332 new_scheme = parameter_s.strip()
334 333 if not new_scheme:
335 334 raise UsageError(
336 335 "%colors: you must specify a color scheme. See '%colors?'")
337 336 # local shortcut
338 337 shell = self.shell
339 338
340 339 # Set shell colour scheme
341 340 try:
342 341 shell.colors = new_scheme
343 342 shell.refresh_style()
344 343 except:
345 344 color_switch_err('shell')
346 345
347 346 # Set exception colors
348 347 try:
349 348 shell.InteractiveTB.set_colors(scheme = new_scheme)
350 349 shell.SyntaxTB.set_colors(scheme = new_scheme)
351 350 except:
352 351 color_switch_err('exception')
353 352
354 353 # Set info (for 'object?') colors
355 354 if shell.color_info:
356 355 try:
357 356 shell.inspector.set_active_scheme(new_scheme)
358 357 except:
359 358 color_switch_err('object inspector')
360 359 else:
361 360 shell.inspector.set_active_scheme('NoColor')
362 361
363 362 @line_magic
364 363 def xmode(self, parameter_s=''):
365 364 """Switch modes for the exception handlers.
366 365
367 366 Valid modes: Plain, Context, Verbose, and Minimal.
368 367
369 368 If called without arguments, acts as a toggle.
370 369
371 370 When in verbose mode the value `--show` (and `--hide`)
372 371 will respectively show (or hide) frames with ``__tracebackhide__ =
373 372 True`` value set.
374 373 """
375 374
376 375 def xmode_switch_err(name):
377 376 warn('Error changing %s exception modes.\n%s' %
378 377 (name,sys.exc_info()[1]))
379 378
380 379 shell = self.shell
381 380 if parameter_s.strip() == "--show":
382 381 shell.InteractiveTB.skip_hidden = False
383 382 return
384 383 if parameter_s.strip() == "--hide":
385 384 shell.InteractiveTB.skip_hidden = True
386 385 return
387 386
388 387 new_mode = parameter_s.strip().capitalize()
389 388 try:
390 389 shell.InteractiveTB.set_mode(mode=new_mode)
391 390 print('Exception reporting mode:',shell.InteractiveTB.mode)
392 391 except:
393 392 xmode_switch_err('user')
394 393
395 394 @line_magic
396 395 def quickref(self, arg):
397 396 """ Show a quick reference sheet """
398 397 from IPython.core.usage import quick_reference
399 398 qr = quick_reference + self._magic_docs(brief=True)
400 399 page.page(qr)
401 400
402 401 @line_magic
403 402 def doctest_mode(self, parameter_s=''):
404 403 """Toggle doctest mode on and off.
405 404
406 405 This mode is intended to make IPython behave as much as possible like a
407 406 plain Python shell, from the perspective of how its prompts, exceptions
408 407 and output look. This makes it easy to copy and paste parts of a
409 408 session into doctests. It does so by:
410 409
411 410 - Changing the prompts to the classic ``>>>`` ones.
412 411 - Changing the exception reporting mode to 'Plain'.
413 412 - Disabling pretty-printing of output.
414 413
415 414 Note that IPython also supports the pasting of code snippets that have
416 415 leading '>>>' and '...' prompts in them. This means that you can paste
417 416 doctests from files or docstrings (even if they have leading
418 417 whitespace), and the code will execute correctly. You can then use
419 418 '%history -t' to see the translated history; this will give you the
420 419 input after removal of all the leading prompts and whitespace, which
421 420 can be pasted back into an editor.
422 421
423 422 With these features, you can switch into this mode easily whenever you
424 423 need to do testing and changes to doctests, without having to leave
425 424 your existing IPython session.
426 425 """
427 426
428 427 # Shorthands
429 428 shell = self.shell
430 429 meta = shell.meta
431 430 disp_formatter = self.shell.display_formatter
432 431 ptformatter = disp_formatter.formatters['text/plain']
433 432 # dstore is a data store kept in the instance metadata bag to track any
434 433 # changes we make, so we can undo them later.
435 434 dstore = meta.setdefault('doctest_mode',Struct())
436 435 save_dstore = dstore.setdefault
437 436
438 437 # save a few values we'll need to recover later
439 438 mode = save_dstore('mode',False)
440 439 save_dstore('rc_pprint',ptformatter.pprint)
441 440 save_dstore('xmode',shell.InteractiveTB.mode)
442 441 save_dstore('rc_separate_out',shell.separate_out)
443 442 save_dstore('rc_separate_out2',shell.separate_out2)
444 443 save_dstore('rc_separate_in',shell.separate_in)
445 444 save_dstore('rc_active_types',disp_formatter.active_types)
446 445
447 446 if not mode:
448 447 # turn on
449 448
450 449 # Prompt separators like plain python
451 450 shell.separate_in = ''
452 451 shell.separate_out = ''
453 452 shell.separate_out2 = ''
454 453
455 454
456 455 ptformatter.pprint = False
457 456 disp_formatter.active_types = ['text/plain']
458 457
459 458 shell.magic('xmode Plain')
460 459 else:
461 460 # turn off
462 461 shell.separate_in = dstore.rc_separate_in
463 462
464 463 shell.separate_out = dstore.rc_separate_out
465 464 shell.separate_out2 = dstore.rc_separate_out2
466 465
467 466 ptformatter.pprint = dstore.rc_pprint
468 467 disp_formatter.active_types = dstore.rc_active_types
469 468
470 469 shell.magic('xmode ' + dstore.xmode)
471 470
472 471 # mode here is the state before we switch; switch_doctest_mode takes
473 472 # the mode we're switching to.
474 473 shell.switch_doctest_mode(not mode)
475 474
476 475 # Store new mode and inform
477 476 dstore.mode = bool(not mode)
478 477 mode_label = ['OFF','ON'][dstore.mode]
479 478 print('Doctest mode is:', mode_label)
480 479
481 480 @line_magic
482 481 def gui(self, parameter_s=''):
483 482 """Enable or disable IPython GUI event loop integration.
484 483
485 484 %gui [GUINAME]
486 485
487 486 This magic replaces IPython's threaded shells that were activated
488 487 using the (pylab/wthread/etc.) command line flags. GUI toolkits
489 488 can now be enabled at runtime and keyboard
490 489 interrupts should work without any problems. The following toolkits
491 490 are supported: wxPython, PyQt4, PyGTK, Tk and Cocoa (OSX)::
492 491
493 492 %gui wx # enable wxPython event loop integration
494 493 %gui qt4|qt # enable PyQt4 event loop integration
495 494 %gui qt5 # enable PyQt5 event loop integration
496 495 %gui gtk # enable PyGTK event loop integration
497 496 %gui gtk3 # enable Gtk3 event loop integration
498 497 %gui gtk4 # enable Gtk4 event loop integration
499 498 %gui tk # enable Tk event loop integration
500 499 %gui osx # enable Cocoa event loop integration
501 500 # (requires %matplotlib 1.1)
502 501 %gui # disable all event loop integration
503 502
504 503 WARNING: after any of these has been called you can simply create
505 504 an application object, but DO NOT start the event loop yourself, as
506 505 we have already handled that.
507 506 """
508 507 opts, arg = self.parse_options(parameter_s, '')
509 508 if arg=='': arg = None
510 509 try:
511 510 return self.shell.enable_gui(arg)
512 511 except Exception as e:
513 512 # print simple error message, rather than traceback if we can't
514 513 # hook up the GUI
515 514 error(str(e))
516 515
517 516 @skip_doctest
518 517 @line_magic
519 518 def precision(self, s=''):
520 519 """Set floating point precision for pretty printing.
521 520
522 521 Can set either integer precision or a format string.
523 522
524 523 If numpy has been imported and precision is an int,
525 524 numpy display precision will also be set, via ``numpy.set_printoptions``.
526 525
527 526 If no argument is given, defaults will be restored.
528 527
529 528 Examples
530 529 --------
531 530 ::
532 531
533 532 In [1]: from math import pi
534 533
535 534 In [2]: %precision 3
536 535 Out[2]: u'%.3f'
537 536
538 537 In [3]: pi
539 538 Out[3]: 3.142
540 539
541 540 In [4]: %precision %i
542 541 Out[4]: u'%i'
543 542
544 543 In [5]: pi
545 544 Out[5]: 3
546 545
547 546 In [6]: %precision %e
548 547 Out[6]: u'%e'
549 548
550 549 In [7]: pi**10
551 550 Out[7]: 9.364805e+04
552 551
553 552 In [8]: %precision
554 553 Out[8]: u'%r'
555 554
556 555 In [9]: pi**10
557 556 Out[9]: 93648.047476082982
558 557 """
559 558 ptformatter = self.shell.display_formatter.formatters['text/plain']
560 559 ptformatter.float_precision = s
561 560 return ptformatter.float_format
562 561
563 562 @magic_arguments.magic_arguments()
564 563 @magic_arguments.argument(
565 564 'filename', type=str,
566 565 help='Notebook name or filename'
567 566 )
568 567 @line_magic
569 568 def notebook(self, s):
570 569 """Export and convert IPython notebooks.
571 570
572 571 This function can export the current IPython history to a notebook file.
573 572 For example, to export the history to "foo.ipynb" do "%notebook foo.ipynb".
574 573 """
575 574 args = magic_arguments.parse_argstring(self.notebook, s)
576 575 outfname = os.path.expanduser(args.filename)
577 576
578 577 from nbformat import write, v4
579 578
580 579 cells = []
581 580 hist = list(self.shell.history_manager.get_range())
582 581 if(len(hist)<=1):
583 582 raise ValueError('History is empty, cannot export')
584 583 for session, execution_count, source in hist[:-1]:
585 584 cells.append(v4.new_code_cell(
586 585 execution_count=execution_count,
587 586 source=source
588 587 ))
589 588 nb = v4.new_notebook(cells=cells)
590 589 with io.open(outfname, "w", encoding="utf-8") as f:
591 590 write(nb, f, version=4)
592 591
593 592 @magics_class
594 593 class AsyncMagics(BasicMagics):
595 594
596 595 @line_magic
597 596 def autoawait(self, parameter_s):
598 597 """
599 598 Allow to change the status of the autoawait option.
600 599
601 600 This allow you to set a specific asynchronous code runner.
602 601
603 602 If no value is passed, print the currently used asynchronous integration
604 603 and whether it is activated.
605 604
606 605 It can take a number of value evaluated in the following order:
607 606
608 607 - False/false/off deactivate autoawait integration
609 608 - True/true/on activate autoawait integration using configured default
610 609 loop
611 610 - asyncio/curio/trio activate autoawait integration and use integration
612 611 with said library.
613 612
614 613 - `sync` turn on the pseudo-sync integration (mostly used for
615 614 `IPython.embed()` which does not run IPython with a real eventloop and
616 615 deactivate running asynchronous code. Turning on Asynchronous code with
617 616 the pseudo sync loop is undefined behavior and may lead IPython to crash.
618 617
619 618 If the passed parameter does not match any of the above and is a python
620 619 identifier, get said object from user namespace and set it as the
621 620 runner, and activate autoawait.
622 621
623 622 If the object is a fully qualified object name, attempt to import it and
624 623 set it as the runner, and activate autoawait.
625 624
626 625 The exact behavior of autoawait is experimental and subject to change
627 626 across version of IPython and Python.
628 627 """
629 628
630 629 param = parameter_s.strip()
631 630 d = {True: "on", False: "off"}
632 631
633 632 if not param:
634 633 print("IPython autoawait is `{}`, and set to use `{}`".format(
635 634 d[self.shell.autoawait],
636 635 self.shell.loop_runner
637 636 ))
638 637 return None
639 638
640 639 if param.lower() in ('false', 'off'):
641 640 self.shell.autoawait = False
642 641 return None
643 642 if param.lower() in ('true', 'on'):
644 643 self.shell.autoawait = True
645 644 return None
646 645
647 646 if param in self.shell.loop_runner_map:
648 647 self.shell.loop_runner, self.shell.autoawait = self.shell.loop_runner_map[param]
649 648 return None
650 649
651 650 if param in self.shell.user_ns :
652 651 self.shell.loop_runner = self.shell.user_ns[param]
653 652 self.shell.autoawait = True
654 653 return None
655 654
656 655 runner = import_item(param)
657 656
658 657 self.shell.loop_runner = runner
659 658 self.shell.autoawait = True
@@ -1,1055 +1,1054 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tools for inspecting Python objects.
3 3
4 4 Uses syntax highlighting for presenting the various information elements.
5 5
6 6 Similar in spirit to the inspect module, but all calls take a name argument to
7 7 reference the name under which an object is being read.
8 8 """
9 9
10 10 # Copyright (c) IPython Development Team.
11 11 # Distributed under the terms of the Modified BSD License.
12 12
13 13 __all__ = ['Inspector','InspectColors']
14 14
15 15 # stdlib modules
16 16 import ast
17 17 import inspect
18 18 from inspect import signature
19 19 import linecache
20 20 import warnings
21 21 import os
22 22 from textwrap import dedent
23 23 import types
24 24 import io as stdlib_io
25 25
26 26 from typing import Union
27 27
28 28 # IPython's own
29 29 from IPython.core import page
30 30 from IPython.lib.pretty import pretty
31 31 from IPython.testing.skipdoctest import skip_doctest
32 32 from IPython.utils import PyColorize
33 33 from IPython.utils import openpy
34 from IPython.utils import py3compat
35 34 from IPython.utils.dir2 import safe_hasattr
36 35 from IPython.utils.path import compress_user
37 36 from IPython.utils.text import indent
38 37 from IPython.utils.wildcard import list_namespace
39 38 from IPython.utils.wildcard import typestr2type
40 39 from IPython.utils.coloransi import TermColors, ColorScheme, ColorSchemeTable
41 40 from IPython.utils.py3compat import cast_unicode
42 41 from IPython.utils.colorable import Colorable
43 42 from IPython.utils.decorators import undoc
44 43
45 44 from pygments import highlight
46 45 from pygments.lexers import PythonLexer
47 46 from pygments.formatters import HtmlFormatter
48 47
49 48 def pylight(code):
50 49 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
51 50
52 51 # builtin docstrings to ignore
53 52 _func_call_docstring = types.FunctionType.__call__.__doc__
54 53 _object_init_docstring = object.__init__.__doc__
55 54 _builtin_type_docstrings = {
56 55 inspect.getdoc(t) for t in (types.ModuleType, types.MethodType,
57 56 types.FunctionType, property)
58 57 }
59 58
60 59 _builtin_func_type = type(all)
61 60 _builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions
62 61 #****************************************************************************
63 62 # Builtin color schemes
64 63
65 64 Colors = TermColors # just a shorthand
66 65
67 66 InspectColors = PyColorize.ANSICodeColors
68 67
69 68 #****************************************************************************
70 69 # Auxiliary functions and objects
71 70
72 71 # See the messaging spec for the definition of all these fields. This list
73 72 # effectively defines the order of display
74 73 info_fields = ['type_name', 'base_class', 'string_form', 'namespace',
75 74 'length', 'file', 'definition', 'docstring', 'source',
76 75 'init_definition', 'class_docstring', 'init_docstring',
77 76 'call_def', 'call_docstring',
78 77 # These won't be printed but will be used to determine how to
79 78 # format the object
80 79 'ismagic', 'isalias', 'isclass', 'found', 'name'
81 80 ]
82 81
83 82
84 83 def object_info(**kw):
85 84 """Make an object info dict with all fields present."""
86 85 infodict = {k:None for k in info_fields}
87 86 infodict.update(kw)
88 87 return infodict
89 88
90 89
91 90 def get_encoding(obj):
92 91 """Get encoding for python source file defining obj
93 92
94 93 Returns None if obj is not defined in a sourcefile.
95 94 """
96 95 ofile = find_file(obj)
97 96 # run contents of file through pager starting at line where the object
98 97 # is defined, as long as the file isn't binary and is actually on the
99 98 # filesystem.
100 99 if ofile is None:
101 100 return None
102 101 elif ofile.endswith(('.so', '.dll', '.pyd')):
103 102 return None
104 103 elif not os.path.isfile(ofile):
105 104 return None
106 105 else:
107 106 # Print only text files, not extension binaries. Note that
108 107 # getsourcelines returns lineno with 1-offset and page() uses
109 108 # 0-offset, so we must adjust.
110 109 with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2
111 110 encoding, lines = openpy.detect_encoding(buffer.readline)
112 111 return encoding
113 112
114 113 def getdoc(obj) -> Union[str,None]:
115 114 """Stable wrapper around inspect.getdoc.
116 115
117 116 This can't crash because of attribute problems.
118 117
119 118 It also attempts to call a getdoc() method on the given object. This
120 119 allows objects which provide their docstrings via non-standard mechanisms
121 120 (like Pyro proxies) to still be inspected by ipython's ? system.
122 121 """
123 122 # Allow objects to offer customized documentation via a getdoc method:
124 123 try:
125 124 ds = obj.getdoc()
126 125 except Exception:
127 126 pass
128 127 else:
129 128 if isinstance(ds, str):
130 129 return inspect.cleandoc(ds)
131 130 docstr = inspect.getdoc(obj)
132 131 return docstr
133 132
134 133
135 134 def getsource(obj, oname='') -> Union[str,None]:
136 135 """Wrapper around inspect.getsource.
137 136
138 137 This can be modified by other projects to provide customized source
139 138 extraction.
140 139
141 140 Parameters
142 141 ----------
143 142 obj : object
144 143 an object whose source code we will attempt to extract
145 144 oname : str
146 145 (optional) a name under which the object is known
147 146
148 147 Returns
149 148 -------
150 149 src : unicode or None
151 150
152 151 """
153 152
154 153 if isinstance(obj, property):
155 154 sources = []
156 155 for attrname in ['fget', 'fset', 'fdel']:
157 156 fn = getattr(obj, attrname)
158 157 if fn is not None:
159 158 encoding = get_encoding(fn)
160 159 oname_prefix = ('%s.' % oname) if oname else ''
161 160 sources.append(''.join(('# ', oname_prefix, attrname)))
162 161 if inspect.isfunction(fn):
163 162 sources.append(dedent(getsource(fn)))
164 163 else:
165 164 # Default str/repr only prints function name,
166 165 # pretty.pretty prints module name too.
167 166 sources.append(
168 167 '%s%s = %s\n' % (oname_prefix, attrname, pretty(fn))
169 168 )
170 169 if sources:
171 170 return '\n'.join(sources)
172 171 else:
173 172 return None
174 173
175 174 else:
176 175 # Get source for non-property objects.
177 176
178 177 obj = _get_wrapped(obj)
179 178
180 179 try:
181 180 src = inspect.getsource(obj)
182 181 except TypeError:
183 182 # The object itself provided no meaningful source, try looking for
184 183 # its class definition instead.
185 184 try:
186 185 src = inspect.getsource(obj.__class__)
187 186 except (OSError, TypeError):
188 187 return None
189 188 except OSError:
190 189 return None
191 190
192 191 return src
193 192
194 193
195 194 def is_simple_callable(obj):
196 195 """True if obj is a function ()"""
197 196 return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
198 197 isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))
199 198
200 199 @undoc
201 200 def getargspec(obj):
202 201 """Wrapper around :func:`inspect.getfullargspec`
203 202
204 203 In addition to functions and methods, this can also handle objects with a
205 204 ``__call__`` attribute.
206 205
207 206 DEPRECATED: Deprecated since 7.10. Do not use, will be removed.
208 207 """
209 208
210 209 warnings.warn('`getargspec` function is deprecated as of IPython 7.10'
211 210 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
212 211
213 212 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
214 213 obj = obj.__call__
215 214
216 215 return inspect.getfullargspec(obj)
217 216
218 217 @undoc
219 218 def format_argspec(argspec):
220 219 """Format argspect, convenience wrapper around inspect's.
221 220
222 221 This takes a dict instead of ordered arguments and calls
223 222 inspect.format_argspec with the arguments in the necessary order.
224 223
225 224 DEPRECATED (since 7.10): Do not use; will be removed in future versions.
226 225 """
227 226
228 227 warnings.warn('`format_argspec` function is deprecated as of IPython 7.10'
229 228 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
230 229
231 230
232 231 return inspect.formatargspec(argspec['args'], argspec['varargs'],
233 232 argspec['varkw'], argspec['defaults'])
234 233
235 234 @undoc
236 235 def call_tip(oinfo, format_call=True):
237 236 """DEPRECATED since 6.0. Extract call tip data from an oinfo dict."""
238 237 warnings.warn(
239 238 "`call_tip` function is deprecated as of IPython 6.0"
240 239 "and will be removed in future versions.",
241 240 DeprecationWarning,
242 241 stacklevel=2,
243 242 )
244 243 # Get call definition
245 244 argspec = oinfo.get('argspec')
246 245 if argspec is None:
247 246 call_line = None
248 247 else:
249 248 # Callable objects will have 'self' as their first argument, prune
250 249 # it out if it's there for clarity (since users do *not* pass an
251 250 # extra first argument explicitly).
252 251 try:
253 252 has_self = argspec['args'][0] == 'self'
254 253 except (KeyError, IndexError):
255 254 pass
256 255 else:
257 256 if has_self:
258 257 argspec['args'] = argspec['args'][1:]
259 258
260 259 call_line = oinfo['name']+format_argspec(argspec)
261 260
262 261 # Now get docstring.
263 262 # The priority is: call docstring, constructor docstring, main one.
264 263 doc = oinfo.get('call_docstring')
265 264 if doc is None:
266 265 doc = oinfo.get('init_docstring')
267 266 if doc is None:
268 267 doc = oinfo.get('docstring','')
269 268
270 269 return call_line, doc
271 270
272 271
273 272 def _get_wrapped(obj):
274 273 """Get the original object if wrapped in one or more @decorators
275 274
276 275 Some objects automatically construct similar objects on any unrecognised
277 276 attribute access (e.g. unittest.mock.call). To protect against infinite loops,
278 277 this will arbitrarily cut off after 100 levels of obj.__wrapped__
279 278 attribute access. --TK, Jan 2016
280 279 """
281 280 orig_obj = obj
282 281 i = 0
283 282 while safe_hasattr(obj, '__wrapped__'):
284 283 obj = obj.__wrapped__
285 284 i += 1
286 285 if i > 100:
287 286 # __wrapped__ is probably a lie, so return the thing we started with
288 287 return orig_obj
289 288 return obj
290 289
291 290 def find_file(obj) -> str:
292 291 """Find the absolute path to the file where an object was defined.
293 292
294 293 This is essentially a robust wrapper around `inspect.getabsfile`.
295 294
296 295 Returns None if no file can be found.
297 296
298 297 Parameters
299 298 ----------
300 299 obj : any Python object
301 300
302 301 Returns
303 302 -------
304 303 fname : str
305 304 The absolute path to the file where the object was defined.
306 305 """
307 306 obj = _get_wrapped(obj)
308 307
309 308 fname = None
310 309 try:
311 310 fname = inspect.getabsfile(obj)
312 311 except TypeError:
313 312 # For an instance, the file that matters is where its class was
314 313 # declared.
315 314 try:
316 315 fname = inspect.getabsfile(obj.__class__)
317 316 except (OSError, TypeError):
318 317 # Can happen for builtins
319 318 pass
320 319 except OSError:
321 320 pass
322 321
323 322 return cast_unicode(fname)
324 323
325 324
326 325 def find_source_lines(obj):
327 326 """Find the line number in a file where an object was defined.
328 327
329 328 This is essentially a robust wrapper around `inspect.getsourcelines`.
330 329
331 330 Returns None if no file can be found.
332 331
333 332 Parameters
334 333 ----------
335 334 obj : any Python object
336 335
337 336 Returns
338 337 -------
339 338 lineno : int
340 339 The line number where the object definition starts.
341 340 """
342 341 obj = _get_wrapped(obj)
343 342
344 343 try:
345 344 lineno = inspect.getsourcelines(obj)[1]
346 345 except TypeError:
347 346 # For instances, try the class object like getsource() does
348 347 try:
349 348 lineno = inspect.getsourcelines(obj.__class__)[1]
350 349 except (OSError, TypeError):
351 350 return None
352 351 except OSError:
353 352 return None
354 353
355 354 return lineno
356 355
357 356 class Inspector(Colorable):
358 357
359 358 def __init__(self, color_table=InspectColors,
360 359 code_color_table=PyColorize.ANSICodeColors,
361 360 scheme=None,
362 361 str_detail_level=0,
363 362 parent=None, config=None):
364 363 super(Inspector, self).__init__(parent=parent, config=config)
365 364 self.color_table = color_table
366 365 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
367 366 self.format = self.parser.format
368 367 self.str_detail_level = str_detail_level
369 368 self.set_active_scheme(scheme)
370 369
371 370 def _getdef(self,obj,oname='') -> Union[str,None]:
372 371 """Return the call signature for any callable object.
373 372
374 373 If any exception is generated, None is returned instead and the
375 374 exception is suppressed."""
376 375 try:
377 376 return _render_signature(signature(obj), oname)
378 377 except:
379 378 return None
380 379
381 380 def __head(self,h) -> str:
382 381 """Return a header string with proper colors."""
383 382 return '%s%s%s' % (self.color_table.active_colors.header,h,
384 383 self.color_table.active_colors.normal)
385 384
386 385 def set_active_scheme(self, scheme):
387 386 if scheme is not None:
388 387 self.color_table.set_active_scheme(scheme)
389 388 self.parser.color_table.set_active_scheme(scheme)
390 389
391 390 def noinfo(self, msg, oname):
392 391 """Generic message when no information is found."""
393 392 print('No %s found' % msg, end=' ')
394 393 if oname:
395 394 print('for %s' % oname)
396 395 else:
397 396 print()
398 397
399 398 def pdef(self, obj, oname=''):
400 399 """Print the call signature for any callable object.
401 400
402 401 If the object is a class, print the constructor information."""
403 402
404 403 if not callable(obj):
405 404 print('Object is not callable.')
406 405 return
407 406
408 407 header = ''
409 408
410 409 if inspect.isclass(obj):
411 410 header = self.__head('Class constructor information:\n')
412 411
413 412
414 413 output = self._getdef(obj,oname)
415 414 if output is None:
416 415 self.noinfo('definition header',oname)
417 416 else:
418 417 print(header,self.format(output), end=' ')
419 418
420 419 # In Python 3, all classes are new-style, so they all have __init__.
421 420 @skip_doctest
422 421 def pdoc(self, obj, oname='', formatter=None):
423 422 """Print the docstring for any object.
424 423
425 424 Optional:
426 425 -formatter: a function to run the docstring through for specially
427 426 formatted docstrings.
428 427
429 428 Examples
430 429 --------
431 430 In [1]: class NoInit:
432 431 ...: pass
433 432
434 433 In [2]: class NoDoc:
435 434 ...: def __init__(self):
436 435 ...: pass
437 436
438 437 In [3]: %pdoc NoDoc
439 438 No documentation found for NoDoc
440 439
441 440 In [4]: %pdoc NoInit
442 441 No documentation found for NoInit
443 442
444 443 In [5]: obj = NoInit()
445 444
446 445 In [6]: %pdoc obj
447 446 No documentation found for obj
448 447
449 448 In [5]: obj2 = NoDoc()
450 449
451 450 In [6]: %pdoc obj2
452 451 No documentation found for obj2
453 452 """
454 453
455 454 head = self.__head # For convenience
456 455 lines = []
457 456 ds = getdoc(obj)
458 457 if formatter:
459 458 ds = formatter(ds).get('plain/text', ds)
460 459 if ds:
461 460 lines.append(head("Class docstring:"))
462 461 lines.append(indent(ds))
463 462 if inspect.isclass(obj) and hasattr(obj, '__init__'):
464 463 init_ds = getdoc(obj.__init__)
465 464 if init_ds is not None:
466 465 lines.append(head("Init docstring:"))
467 466 lines.append(indent(init_ds))
468 467 elif hasattr(obj,'__call__'):
469 468 call_ds = getdoc(obj.__call__)
470 469 if call_ds:
471 470 lines.append(head("Call docstring:"))
472 471 lines.append(indent(call_ds))
473 472
474 473 if not lines:
475 474 self.noinfo('documentation',oname)
476 475 else:
477 476 page.page('\n'.join(lines))
478 477
479 478 def psource(self, obj, oname=''):
480 479 """Print the source code for an object."""
481 480
482 481 # Flush the source cache because inspect can return out-of-date source
483 482 linecache.checkcache()
484 483 try:
485 484 src = getsource(obj, oname=oname)
486 485 except Exception:
487 486 src = None
488 487
489 488 if src is None:
490 489 self.noinfo('source', oname)
491 490 else:
492 491 page.page(self.format(src))
493 492
494 493 def pfile(self, obj, oname=''):
495 494 """Show the whole file where an object was defined."""
496 495
497 496 lineno = find_source_lines(obj)
498 497 if lineno is None:
499 498 self.noinfo('file', oname)
500 499 return
501 500
502 501 ofile = find_file(obj)
503 502 # run contents of file through pager starting at line where the object
504 503 # is defined, as long as the file isn't binary and is actually on the
505 504 # filesystem.
506 505 if ofile.endswith(('.so', '.dll', '.pyd')):
507 506 print('File %r is binary, not printing.' % ofile)
508 507 elif not os.path.isfile(ofile):
509 508 print('File %r does not exist, not printing.' % ofile)
510 509 else:
511 510 # Print only text files, not extension binaries. Note that
512 511 # getsourcelines returns lineno with 1-offset and page() uses
513 512 # 0-offset, so we must adjust.
514 513 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
515 514
516 515
517 516 def _mime_format(self, text:str, formatter=None) -> dict:
518 517 """Return a mime bundle representation of the input text.
519 518
520 519 - if `formatter` is None, the returned mime bundle has
521 520 a ``text/plain`` field, with the input text.
522 521 a ``text/html`` field with a ``<pre>`` tag containing the input text.
523 522
524 523 - if ``formatter`` is not None, it must be a callable transforming the
525 524 input text into a mime bundle. Default values for ``text/plain`` and
526 525 ``text/html`` representations are the ones described above.
527 526
528 527 Note:
529 528
530 529 Formatters returning strings are supported but this behavior is deprecated.
531 530
532 531 """
533 532 defaults = {
534 533 'text/plain': text,
535 534 'text/html': '<pre>' + text + '</pre>'
536 535 }
537 536
538 537 if formatter is None:
539 538 return defaults
540 539 else:
541 540 formatted = formatter(text)
542 541
543 542 if not isinstance(formatted, dict):
544 543 # Handle the deprecated behavior of a formatter returning
545 544 # a string instead of a mime bundle.
546 545 return {
547 546 'text/plain': formatted,
548 547 'text/html': '<pre>' + formatted + '</pre>'
549 548 }
550 549
551 550 else:
552 551 return dict(defaults, **formatted)
553 552
554 553
555 554 def format_mime(self, bundle):
556 555
557 556 text_plain = bundle['text/plain']
558 557
559 558 text = ''
560 559 heads, bodies = list(zip(*text_plain))
561 560 _len = max(len(h) for h in heads)
562 561
563 562 for head, body in zip(heads, bodies):
564 563 body = body.strip('\n')
565 564 delim = '\n' if '\n' in body else ' '
566 565 text += self.__head(head+':') + (_len - len(head))*' ' +delim + body +'\n'
567 566
568 567 bundle['text/plain'] = text
569 568 return bundle
570 569
571 570 def _get_info(
572 571 self, obj, oname="", formatter=None, info=None, detail_level=0, omit_sections=()
573 572 ):
574 573 """Retrieve an info dict and format it.
575 574
576 575 Parameters
577 576 ----------
578 577 obj : any
579 578 Object to inspect and return info from
580 579 oname : str (default: ''):
581 580 Name of the variable pointing to `obj`.
582 581 formatter : callable
583 582 info
584 583 already computed information
585 584 detail_level : integer
586 585 Granularity of detail level, if set to 1, give more information.
587 586 omit_sections : container[str]
588 587 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`)
589 588 """
590 589
591 590 info = self.info(obj, oname=oname, info=info, detail_level=detail_level)
592 591
593 592 _mime = {
594 593 'text/plain': [],
595 594 'text/html': '',
596 595 }
597 596
598 597 def append_field(bundle, title:str, key:str, formatter=None):
599 598 if title in omit_sections or key in omit_sections:
600 599 return
601 600 field = info[key]
602 601 if field is not None:
603 602 formatted_field = self._mime_format(field, formatter)
604 603 bundle['text/plain'].append((title, formatted_field['text/plain']))
605 604 bundle['text/html'] += '<h1>' + title + '</h1>\n' + formatted_field['text/html'] + '\n'
606 605
607 606 def code_formatter(text):
608 607 return {
609 608 'text/plain': self.format(text),
610 609 'text/html': pylight(text)
611 610 }
612 611
613 612 if info['isalias']:
614 613 append_field(_mime, 'Repr', 'string_form')
615 614
616 615 elif info['ismagic']:
617 616 if detail_level > 0:
618 617 append_field(_mime, 'Source', 'source', code_formatter)
619 618 else:
620 619 append_field(_mime, 'Docstring', 'docstring', formatter)
621 620 append_field(_mime, 'File', 'file')
622 621
623 622 elif info['isclass'] or is_simple_callable(obj):
624 623 # Functions, methods, classes
625 624 append_field(_mime, 'Signature', 'definition', code_formatter)
626 625 append_field(_mime, 'Init signature', 'init_definition', code_formatter)
627 626 append_field(_mime, 'Docstring', 'docstring', formatter)
628 627 if detail_level > 0 and info['source']:
629 628 append_field(_mime, 'Source', 'source', code_formatter)
630 629 else:
631 630 append_field(_mime, 'Init docstring', 'init_docstring', formatter)
632 631
633 632 append_field(_mime, 'File', 'file')
634 633 append_field(_mime, 'Type', 'type_name')
635 634 append_field(_mime, 'Subclasses', 'subclasses')
636 635
637 636 else:
638 637 # General Python objects
639 638 append_field(_mime, 'Signature', 'definition', code_formatter)
640 639 append_field(_mime, 'Call signature', 'call_def', code_formatter)
641 640 append_field(_mime, 'Type', 'type_name')
642 641 append_field(_mime, 'String form', 'string_form')
643 642
644 643 # Namespace
645 644 if info['namespace'] != 'Interactive':
646 645 append_field(_mime, 'Namespace', 'namespace')
647 646
648 647 append_field(_mime, 'Length', 'length')
649 648 append_field(_mime, 'File', 'file')
650 649
651 650 # Source or docstring, depending on detail level and whether
652 651 # source found.
653 652 if detail_level > 0 and info['source']:
654 653 append_field(_mime, 'Source', 'source', code_formatter)
655 654 else:
656 655 append_field(_mime, 'Docstring', 'docstring', formatter)
657 656
658 657 append_field(_mime, 'Class docstring', 'class_docstring', formatter)
659 658 append_field(_mime, 'Init docstring', 'init_docstring', formatter)
660 659 append_field(_mime, 'Call docstring', 'call_docstring', formatter)
661 660
662 661
663 662 return self.format_mime(_mime)
664 663
665 664 def pinfo(
666 665 self,
667 666 obj,
668 667 oname="",
669 668 formatter=None,
670 669 info=None,
671 670 detail_level=0,
672 671 enable_html_pager=True,
673 672 omit_sections=(),
674 673 ):
675 674 """Show detailed information about an object.
676 675
677 676 Optional arguments:
678 677
679 678 - oname: name of the variable pointing to the object.
680 679
681 680 - formatter: callable (optional)
682 681 A special formatter for docstrings.
683 682
684 683 The formatter is a callable that takes a string as an input
685 684 and returns either a formatted string or a mime type bundle
686 685 in the form of a dictionary.
687 686
688 687 Although the support of custom formatter returning a string
689 688 instead of a mime type bundle is deprecated.
690 689
691 690 - info: a structure with some information fields which may have been
692 691 precomputed already.
693 692
694 693 - detail_level: if set to 1, more information is given.
695 694
696 695 - omit_sections: set of section keys and titles to omit
697 696 """
698 697 info = self._get_info(
699 698 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
700 699 )
701 700 if not enable_html_pager:
702 701 del info['text/html']
703 702 page.page(info)
704 703
705 704 def _info(self, obj, oname="", info=None, detail_level=0):
706 705 """
707 706 Inspector.info() was likely improperly marked as deprecated
708 707 while only a parameter was deprecated. We "un-deprecate" it.
709 708 """
710 709
711 710 warnings.warn(
712 711 "The `Inspector.info()` method has been un-deprecated as of 8.0 "
713 712 "and the `formatter=` keyword removed. `Inspector._info` is now "
714 713 "an alias, and you can just call `.info()` directly.",
715 714 DeprecationWarning,
716 715 stacklevel=2,
717 716 )
718 717 return self.info(obj, oname=oname, info=info, detail_level=detail_level)
719 718
720 719 def info(self, obj, oname="", info=None, detail_level=0) -> dict:
721 720 """Compute a dict with detailed information about an object.
722 721
723 722 Parameters
724 723 ----------
725 724 obj : any
726 725 An object to find information about
727 726 oname : str (default: '')
728 727 Name of the variable pointing to `obj`.
729 728 info : (default: None)
730 729 A struct (dict like with attr access) with some information fields
731 730 which may have been precomputed already.
732 731 detail_level : int (default:0)
733 732 If set to 1, more information is given.
734 733
735 734 Returns
736 735 -------
737 736 An object info dict with known fields from `info_fields`. Keys are
738 737 strings, values are string or None.
739 738 """
740 739
741 740 if info is None:
742 741 ismagic = False
743 742 isalias = False
744 743 ospace = ''
745 744 else:
746 745 ismagic = info.ismagic
747 746 isalias = info.isalias
748 747 ospace = info.namespace
749 748
750 749 # Get docstring, special-casing aliases:
751 750 if isalias:
752 751 if not callable(obj):
753 752 try:
754 753 ds = "Alias to the system command:\n %s" % obj[1]
755 754 except:
756 755 ds = "Alias: " + str(obj)
757 756 else:
758 757 ds = "Alias to " + str(obj)
759 758 if obj.__doc__:
760 759 ds += "\nDocstring:\n" + obj.__doc__
761 760 else:
762 761 ds = getdoc(obj)
763 762 if ds is None:
764 763 ds = '<no docstring>'
765 764
766 765 # store output in a dict, we initialize it here and fill it as we go
767 766 out = dict(name=oname, found=True, isalias=isalias, ismagic=ismagic, subclasses=None)
768 767
769 768 string_max = 200 # max size of strings to show (snipped if longer)
770 769 shalf = int((string_max - 5) / 2)
771 770
772 771 if ismagic:
773 772 out['type_name'] = 'Magic function'
774 773 elif isalias:
775 774 out['type_name'] = 'System alias'
776 775 else:
777 776 out['type_name'] = type(obj).__name__
778 777
779 778 try:
780 779 bclass = obj.__class__
781 780 out['base_class'] = str(bclass)
782 781 except:
783 782 pass
784 783
785 784 # String form, but snip if too long in ? form (full in ??)
786 785 if detail_level >= self.str_detail_level:
787 786 try:
788 787 ostr = str(obj)
789 788 str_head = 'string_form'
790 789 if not detail_level and len(ostr)>string_max:
791 790 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
792 791 ostr = ("\n" + " " * len(str_head.expandtabs())).\
793 792 join(q.strip() for q in ostr.split("\n"))
794 793 out[str_head] = ostr
795 794 except:
796 795 pass
797 796
798 797 if ospace:
799 798 out['namespace'] = ospace
800 799
801 800 # Length (for strings and lists)
802 801 try:
803 802 out['length'] = str(len(obj))
804 803 except Exception:
805 804 pass
806 805
807 806 # Filename where object was defined
808 807 binary_file = False
809 808 fname = find_file(obj)
810 809 if fname is None:
811 810 # if anything goes wrong, we don't want to show source, so it's as
812 811 # if the file was binary
813 812 binary_file = True
814 813 else:
815 814 if fname.endswith(('.so', '.dll', '.pyd')):
816 815 binary_file = True
817 816 elif fname.endswith('<string>'):
818 817 fname = 'Dynamically generated function. No source code available.'
819 818 out['file'] = compress_user(fname)
820 819
821 820 # Original source code for a callable, class or property.
822 821 if detail_level:
823 822 # Flush the source cache because inspect can return out-of-date
824 823 # source
825 824 linecache.checkcache()
826 825 try:
827 826 if isinstance(obj, property) or not binary_file:
828 827 src = getsource(obj, oname)
829 828 if src is not None:
830 829 src = src.rstrip()
831 830 out['source'] = src
832 831
833 832 except Exception:
834 833 pass
835 834
836 835 # Add docstring only if no source is to be shown (avoid repetitions).
837 836 if ds and not self._source_contains_docstring(out.get('source'), ds):
838 837 out['docstring'] = ds
839 838
840 839 # Constructor docstring for classes
841 840 if inspect.isclass(obj):
842 841 out['isclass'] = True
843 842
844 843 # get the init signature:
845 844 try:
846 845 init_def = self._getdef(obj, oname)
847 846 except AttributeError:
848 847 init_def = None
849 848
850 849 # get the __init__ docstring
851 850 try:
852 851 obj_init = obj.__init__
853 852 except AttributeError:
854 853 init_ds = None
855 854 else:
856 855 if init_def is None:
857 856 # Get signature from init if top-level sig failed.
858 857 # Can happen for built-in types (list, etc.).
859 858 try:
860 859 init_def = self._getdef(obj_init, oname)
861 860 except AttributeError:
862 861 pass
863 862 init_ds = getdoc(obj_init)
864 863 # Skip Python's auto-generated docstrings
865 864 if init_ds == _object_init_docstring:
866 865 init_ds = None
867 866
868 867 if init_def:
869 868 out['init_definition'] = init_def
870 869
871 870 if init_ds:
872 871 out['init_docstring'] = init_ds
873 872
874 873 names = [sub.__name__ for sub in type.__subclasses__(obj)]
875 874 if len(names) < 10:
876 875 all_names = ', '.join(names)
877 876 else:
878 877 all_names = ', '.join(names[:10]+['...'])
879 878 out['subclasses'] = all_names
880 879 # and class docstring for instances:
881 880 else:
882 881 # reconstruct the function definition and print it:
883 882 defln = self._getdef(obj, oname)
884 883 if defln:
885 884 out['definition'] = defln
886 885
887 886 # First, check whether the instance docstring is identical to the
888 887 # class one, and print it separately if they don't coincide. In
889 888 # most cases they will, but it's nice to print all the info for
890 889 # objects which use instance-customized docstrings.
891 890 if ds:
892 891 try:
893 892 cls = getattr(obj,'__class__')
894 893 except:
895 894 class_ds = None
896 895 else:
897 896 class_ds = getdoc(cls)
898 897 # Skip Python's auto-generated docstrings
899 898 if class_ds in _builtin_type_docstrings:
900 899 class_ds = None
901 900 if class_ds and ds != class_ds:
902 901 out['class_docstring'] = class_ds
903 902
904 903 # Next, try to show constructor docstrings
905 904 try:
906 905 init_ds = getdoc(obj.__init__)
907 906 # Skip Python's auto-generated docstrings
908 907 if init_ds == _object_init_docstring:
909 908 init_ds = None
910 909 except AttributeError:
911 910 init_ds = None
912 911 if init_ds:
913 912 out['init_docstring'] = init_ds
914 913
915 914 # Call form docstring for callable instances
916 915 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
917 916 call_def = self._getdef(obj.__call__, oname)
918 917 if call_def and (call_def != out.get('definition')):
919 918 # it may never be the case that call def and definition differ,
920 919 # but don't include the same signature twice
921 920 out['call_def'] = call_def
922 921 call_ds = getdoc(obj.__call__)
923 922 # Skip Python's auto-generated docstrings
924 923 if call_ds == _func_call_docstring:
925 924 call_ds = None
926 925 if call_ds:
927 926 out['call_docstring'] = call_ds
928 927
929 928 return object_info(**out)
930 929
931 930 @staticmethod
932 931 def _source_contains_docstring(src, doc):
933 932 """
934 933 Check whether the source *src* contains the docstring *doc*.
935 934
936 935 This is is helper function to skip displaying the docstring if the
937 936 source already contains it, avoiding repetition of information.
938 937 """
939 938 try:
940 939 def_node, = ast.parse(dedent(src)).body
941 940 return ast.get_docstring(def_node) == doc
942 941 except Exception:
943 942 # The source can become invalid or even non-existent (because it
944 943 # is re-fetched from the source file) so the above code fail in
945 944 # arbitrary ways.
946 945 return False
947 946
948 947 def psearch(self,pattern,ns_table,ns_search=[],
949 948 ignore_case=False,show_all=False, *, list_types=False):
950 949 """Search namespaces with wildcards for objects.
951 950
952 951 Arguments:
953 952
954 953 - pattern: string containing shell-like wildcards to use in namespace
955 954 searches and optionally a type specification to narrow the search to
956 955 objects of that type.
957 956
958 957 - ns_table: dict of name->namespaces for search.
959 958
960 959 Optional arguments:
961 960
962 961 - ns_search: list of namespace names to include in search.
963 962
964 963 - ignore_case(False): make the search case-insensitive.
965 964
966 965 - show_all(False): show all names, including those starting with
967 966 underscores.
968 967
969 968 - list_types(False): list all available object types for object matching.
970 969 """
971 970 #print 'ps pattern:<%r>' % pattern # dbg
972 971
973 972 # defaults
974 973 type_pattern = 'all'
975 974 filter = ''
976 975
977 976 # list all object types
978 977 if list_types:
979 978 page.page('\n'.join(sorted(typestr2type)))
980 979 return
981 980
982 981 cmds = pattern.split()
983 982 len_cmds = len(cmds)
984 983 if len_cmds == 1:
985 984 # Only filter pattern given
986 985 filter = cmds[0]
987 986 elif len_cmds == 2:
988 987 # Both filter and type specified
989 988 filter,type_pattern = cmds
990 989 else:
991 990 raise ValueError('invalid argument string for psearch: <%s>' %
992 991 pattern)
993 992
994 993 # filter search namespaces
995 994 for name in ns_search:
996 995 if name not in ns_table:
997 996 raise ValueError('invalid namespace <%s>. Valid names: %s' %
998 997 (name,ns_table.keys()))
999 998
1000 999 #print 'type_pattern:',type_pattern # dbg
1001 1000 search_result, namespaces_seen = set(), set()
1002 1001 for ns_name in ns_search:
1003 1002 ns = ns_table[ns_name]
1004 1003 # Normally, locals and globals are the same, so we just check one.
1005 1004 if id(ns) in namespaces_seen:
1006 1005 continue
1007 1006 namespaces_seen.add(id(ns))
1008 1007 tmp_res = list_namespace(ns, type_pattern, filter,
1009 1008 ignore_case=ignore_case, show_all=show_all)
1010 1009 search_result.update(tmp_res)
1011 1010
1012 1011 page.page('\n'.join(sorted(search_result)))
1013 1012
1014 1013
1015 1014 def _render_signature(obj_signature, obj_name) -> str:
1016 1015 """
1017 1016 This was mostly taken from inspect.Signature.__str__.
1018 1017 Look there for the comments.
1019 1018 The only change is to add linebreaks when this gets too long.
1020 1019 """
1021 1020 result = []
1022 1021 pos_only = False
1023 1022 kw_only = True
1024 1023 for param in obj_signature.parameters.values():
1025 1024 if param.kind == inspect._POSITIONAL_ONLY:
1026 1025 pos_only = True
1027 1026 elif pos_only:
1028 1027 result.append('/')
1029 1028 pos_only = False
1030 1029
1031 1030 if param.kind == inspect._VAR_POSITIONAL:
1032 1031 kw_only = False
1033 1032 elif param.kind == inspect._KEYWORD_ONLY and kw_only:
1034 1033 result.append('*')
1035 1034 kw_only = False
1036 1035
1037 1036 result.append(str(param))
1038 1037
1039 1038 if pos_only:
1040 1039 result.append('/')
1041 1040
1042 1041 # add up name, parameters, braces (2), and commas
1043 1042 if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
1044 1043 # This doesn’t fit behind β€œSignature: ” in an inspect window.
1045 1044 rendered = '{}(\n{})'.format(obj_name, ''.join(
1046 1045 ' {},\n'.format(r) for r in result)
1047 1046 )
1048 1047 else:
1049 1048 rendered = '{}({})'.format(obj_name, ', '.join(result))
1050 1049
1051 1050 if obj_signature.return_annotation is not inspect._empty:
1052 1051 anno = inspect.formatannotation(obj_signature.return_annotation)
1053 1052 rendered += ' -> {}'.format(anno)
1054 1053
1055 1054 return rendered
@@ -1,452 +1,451 b''
1 1 # encoding: utf-8
2 2 """
3 3 A mixin for :class:`~IPython.core.application.Application` classes that
4 4 launch InteractiveShell instances, load extensions, etc.
5 5 """
6 6
7 7 # Copyright (c) IPython Development Team.
8 8 # Distributed under the terms of the Modified BSD License.
9 9
10 10 import glob
11 11 from itertools import chain
12 12 import os
13 13 import sys
14 14
15 15 from traitlets.config.application import boolean_flag
16 16 from traitlets.config.configurable import Configurable
17 17 from traitlets.config.loader import Config
18 18 from IPython.core.application import SYSTEM_CONFIG_DIRS, ENV_CONFIG_DIRS
19 19 from IPython.core import pylabtools
20 20 from IPython.utils.contexts import preserve_keys
21 21 from IPython.utils.path import filefind
22 import traitlets
23 22 from traitlets import (
24 23 Unicode, Instance, List, Bool, CaselessStrEnum, observe,
25 24 DottedObjectName,
26 25 )
27 26 from IPython.terminal import pt_inputhooks
28 27
29 28 #-----------------------------------------------------------------------------
30 29 # Aliases and Flags
31 30 #-----------------------------------------------------------------------------
32 31
33 32 gui_keys = tuple(sorted(pt_inputhooks.backends) + sorted(pt_inputhooks.aliases))
34 33
35 34 backend_keys = sorted(pylabtools.backends.keys())
36 35 backend_keys.insert(0, 'auto')
37 36
38 37 shell_flags = {}
39 38
40 39 addflag = lambda *args: shell_flags.update(boolean_flag(*args))
41 40 addflag('autoindent', 'InteractiveShell.autoindent',
42 41 'Turn on autoindenting.', 'Turn off autoindenting.'
43 42 )
44 43 addflag('automagic', 'InteractiveShell.automagic',
45 44 """Turn on the auto calling of magic commands. Type %%magic at the
46 45 IPython prompt for more information.""",
47 46 'Turn off the auto calling of magic commands.'
48 47 )
49 48 addflag('pdb', 'InteractiveShell.pdb',
50 49 "Enable auto calling the pdb debugger after every exception.",
51 50 "Disable auto calling the pdb debugger after every exception."
52 51 )
53 52 addflag('pprint', 'PlainTextFormatter.pprint',
54 53 "Enable auto pretty printing of results.",
55 54 "Disable auto pretty printing of results."
56 55 )
57 56 addflag('color-info', 'InteractiveShell.color_info',
58 57 """IPython can display information about objects via a set of functions,
59 58 and optionally can use colors for this, syntax highlighting
60 59 source code and various other elements. This is on by default, but can cause
61 60 problems with some pagers. If you see such problems, you can disable the
62 61 colours.""",
63 62 "Disable using colors for info related things."
64 63 )
65 64 addflag('ignore-cwd', 'InteractiveShellApp.ignore_cwd',
66 65 "Exclude the current working directory from sys.path",
67 66 "Include the current working directory in sys.path",
68 67 )
69 68 nosep_config = Config()
70 69 nosep_config.InteractiveShell.separate_in = ''
71 70 nosep_config.InteractiveShell.separate_out = ''
72 71 nosep_config.InteractiveShell.separate_out2 = ''
73 72
74 73 shell_flags['nosep']=(nosep_config, "Eliminate all spacing between prompts.")
75 74 shell_flags['pylab'] = (
76 75 {'InteractiveShellApp' : {'pylab' : 'auto'}},
77 76 """Pre-load matplotlib and numpy for interactive use with
78 77 the default matplotlib backend."""
79 78 )
80 79 shell_flags['matplotlib'] = (
81 80 {'InteractiveShellApp' : {'matplotlib' : 'auto'}},
82 81 """Configure matplotlib for interactive use with
83 82 the default matplotlib backend."""
84 83 )
85 84
86 85 # it's possible we don't want short aliases for *all* of these:
87 86 shell_aliases = dict(
88 87 autocall='InteractiveShell.autocall',
89 88 colors='InteractiveShell.colors',
90 89 logfile='InteractiveShell.logfile',
91 90 logappend='InteractiveShell.logappend',
92 91 c='InteractiveShellApp.code_to_run',
93 92 m='InteractiveShellApp.module_to_run',
94 93 ext="InteractiveShellApp.extra_extensions",
95 94 gui='InteractiveShellApp.gui',
96 95 pylab='InteractiveShellApp.pylab',
97 96 matplotlib='InteractiveShellApp.matplotlib',
98 97 )
99 98 shell_aliases['cache-size'] = 'InteractiveShell.cache_size'
100 99
101 100 #-----------------------------------------------------------------------------
102 101 # Main classes and functions
103 102 #-----------------------------------------------------------------------------
104 103
105 104 class InteractiveShellApp(Configurable):
106 105 """A Mixin for applications that start InteractiveShell instances.
107 106
108 107 Provides configurables for loading extensions and executing files
109 108 as part of configuring a Shell environment.
110 109
111 110 The following methods should be called by the :meth:`initialize` method
112 111 of the subclass:
113 112
114 113 - :meth:`init_path`
115 114 - :meth:`init_shell` (to be implemented by the subclass)
116 115 - :meth:`init_gui_pylab`
117 116 - :meth:`init_extensions`
118 117 - :meth:`init_code`
119 118 """
120 119 extensions = List(Unicode(),
121 120 help="A list of dotted module names of IPython extensions to load."
122 121 ).tag(config=True)
123 122
124 123 extra_extensions = List(
125 124 DottedObjectName(),
126 125 help="""
127 126 Dotted module name(s) of one or more IPython extensions to load.
128 127
129 128 For specifying extra extensions to load on the command-line.
130 129
131 130 .. versionadded:: 7.10
132 131 """,
133 132 ).tag(config=True)
134 133
135 134 reraise_ipython_extension_failures = Bool(False,
136 135 help="Reraise exceptions encountered loading IPython extensions?",
137 136 ).tag(config=True)
138 137
139 138 # Extensions that are always loaded (not configurable)
140 139 default_extensions = List(Unicode(), [u'storemagic']).tag(config=False)
141 140
142 141 hide_initial_ns = Bool(True,
143 142 help="""Should variables loaded at startup (by startup files, exec_lines, etc.)
144 143 be hidden from tools like %who?"""
145 144 ).tag(config=True)
146 145
147 146 exec_files = List(Unicode(),
148 147 help="""List of files to run at IPython startup."""
149 148 ).tag(config=True)
150 149 exec_PYTHONSTARTUP = Bool(True,
151 150 help="""Run the file referenced by the PYTHONSTARTUP environment
152 151 variable at IPython startup."""
153 152 ).tag(config=True)
154 153 file_to_run = Unicode('',
155 154 help="""A file to be run""").tag(config=True)
156 155
157 156 exec_lines = List(Unicode(),
158 157 help="""lines of code to run at IPython startup."""
159 158 ).tag(config=True)
160 159 code_to_run = Unicode('',
161 160 help="Execute the given command string."
162 161 ).tag(config=True)
163 162 module_to_run = Unicode('',
164 163 help="Run the module as a script."
165 164 ).tag(config=True)
166 165 gui = CaselessStrEnum(gui_keys, allow_none=True,
167 166 help="Enable GUI event loop integration with any of {0}.".format(gui_keys)
168 167 ).tag(config=True)
169 168 matplotlib = CaselessStrEnum(backend_keys, allow_none=True,
170 169 help="""Configure matplotlib for interactive use with
171 170 the default matplotlib backend."""
172 171 ).tag(config=True)
173 172 pylab = CaselessStrEnum(backend_keys, allow_none=True,
174 173 help="""Pre-load matplotlib and numpy for interactive use,
175 174 selecting a particular matplotlib backend and loop integration.
176 175 """
177 176 ).tag(config=True)
178 177 pylab_import_all = Bool(True,
179 178 help="""If true, IPython will populate the user namespace with numpy, pylab, etc.
180 179 and an ``import *`` is done from numpy and pylab, when using pylab mode.
181 180
182 181 When False, pylab mode should not import any names into the user namespace.
183 182 """
184 183 ).tag(config=True)
185 184 ignore_cwd = Bool(
186 185 False,
187 186 help="""If True, IPython will not add the current working directory to sys.path.
188 187 When False, the current working directory is added to sys.path, allowing imports
189 188 of modules defined in the current directory."""
190 189 ).tag(config=True)
191 190 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
192 191 allow_none=True)
193 192 # whether interact-loop should start
194 193 interact = Bool(True)
195 194
196 195 user_ns = Instance(dict, args=None, allow_none=True)
197 196 @observe('user_ns')
198 197 def _user_ns_changed(self, change):
199 198 if self.shell is not None:
200 199 self.shell.user_ns = change['new']
201 200 self.shell.init_user_ns()
202 201
203 202 def init_path(self):
204 203 """Add current working directory, '', to sys.path
205 204
206 205 Unlike Python's default, we insert before the first `site-packages`
207 206 or `dist-packages` directory,
208 207 so that it is after the standard library.
209 208
210 209 .. versionchanged:: 7.2
211 210 Try to insert after the standard library, instead of first.
212 211 .. versionchanged:: 8.0
213 212 Allow optionally not including the current directory in sys.path
214 213 """
215 214 if '' in sys.path or self.ignore_cwd:
216 215 return
217 216 for idx, path in enumerate(sys.path):
218 217 parent, last_part = os.path.split(path)
219 218 if last_part in {'site-packages', 'dist-packages'}:
220 219 break
221 220 else:
222 221 # no site-packages or dist-packages found (?!)
223 222 # back to original behavior of inserting at the front
224 223 idx = 0
225 224 sys.path.insert(idx, '')
226 225
227 226 def init_shell(self):
228 227 raise NotImplementedError("Override in subclasses")
229 228
230 229 def init_gui_pylab(self):
231 230 """Enable GUI event loop integration, taking pylab into account."""
232 231 enable = False
233 232 shell = self.shell
234 233 if self.pylab:
235 234 enable = lambda key: shell.enable_pylab(key, import_all=self.pylab_import_all)
236 235 key = self.pylab
237 236 elif self.matplotlib:
238 237 enable = shell.enable_matplotlib
239 238 key = self.matplotlib
240 239 elif self.gui:
241 240 enable = shell.enable_gui
242 241 key = self.gui
243 242
244 243 if not enable:
245 244 return
246 245
247 246 try:
248 247 r = enable(key)
249 248 except ImportError:
250 249 self.log.warning("Eventloop or matplotlib integration failed. Is matplotlib installed?")
251 250 self.shell.showtraceback()
252 251 return
253 252 except Exception:
254 253 self.log.warning("GUI event loop or pylab initialization failed")
255 254 self.shell.showtraceback()
256 255 return
257 256
258 257 if isinstance(r, tuple):
259 258 gui, backend = r[:2]
260 259 self.log.info("Enabling GUI event loop integration, "
261 260 "eventloop=%s, matplotlib=%s", gui, backend)
262 261 if key == "auto":
263 262 print("Using matplotlib backend: %s" % backend)
264 263 else:
265 264 gui = r
266 265 self.log.info("Enabling GUI event loop integration, "
267 266 "eventloop=%s", gui)
268 267
269 268 def init_extensions(self):
270 269 """Load all IPython extensions in IPythonApp.extensions.
271 270
272 271 This uses the :meth:`ExtensionManager.load_extensions` to load all
273 272 the extensions listed in ``self.extensions``.
274 273 """
275 274 try:
276 275 self.log.debug("Loading IPython extensions...")
277 276 extensions = (
278 277 self.default_extensions + self.extensions + self.extra_extensions
279 278 )
280 279 for ext in extensions:
281 280 try:
282 281 self.log.info("Loading IPython extension: %s" % ext)
283 282 self.shell.extension_manager.load_extension(ext)
284 283 except:
285 284 if self.reraise_ipython_extension_failures:
286 285 raise
287 286 msg = ("Error in loading extension: {ext}\n"
288 287 "Check your config files in {location}".format(
289 288 ext=ext,
290 289 location=self.profile_dir.location
291 290 ))
292 291 self.log.warning(msg, exc_info=True)
293 292 except:
294 293 if self.reraise_ipython_extension_failures:
295 294 raise
296 295 self.log.warning("Unknown error in loading extensions:", exc_info=True)
297 296
298 297 def init_code(self):
299 298 """run the pre-flight code, specified via exec_lines"""
300 299 self._run_startup_files()
301 300 self._run_exec_lines()
302 301 self._run_exec_files()
303 302
304 303 # Hide variables defined here from %who etc.
305 304 if self.hide_initial_ns:
306 305 self.shell.user_ns_hidden.update(self.shell.user_ns)
307 306
308 307 # command-line execution (ipython -i script.py, ipython -m module)
309 308 # should *not* be excluded from %whos
310 309 self._run_cmd_line_code()
311 310 self._run_module()
312 311
313 312 # flush output, so itwon't be attached to the first cell
314 313 sys.stdout.flush()
315 314 sys.stderr.flush()
316 315 self.shell._sys_modules_keys = set(sys.modules.keys())
317 316
318 317 def _run_exec_lines(self):
319 318 """Run lines of code in IPythonApp.exec_lines in the user's namespace."""
320 319 if not self.exec_lines:
321 320 return
322 321 try:
323 322 self.log.debug("Running code from IPythonApp.exec_lines...")
324 323 for line in self.exec_lines:
325 324 try:
326 325 self.log.info("Running code in user namespace: %s" %
327 326 line)
328 327 self.shell.run_cell(line, store_history=False)
329 328 except:
330 329 self.log.warning("Error in executing line in user "
331 330 "namespace: %s" % line)
332 331 self.shell.showtraceback()
333 332 except:
334 333 self.log.warning("Unknown error in handling IPythonApp.exec_lines:")
335 334 self.shell.showtraceback()
336 335
337 336 def _exec_file(self, fname, shell_futures=False):
338 337 try:
339 338 full_filename = filefind(fname, [u'.', self.ipython_dir])
340 339 except IOError:
341 340 self.log.warning("File not found: %r"%fname)
342 341 return
343 342 # Make sure that the running script gets a proper sys.argv as if it
344 343 # were run from a system shell.
345 344 save_argv = sys.argv
346 345 sys.argv = [full_filename] + self.extra_args[1:]
347 346 try:
348 347 if os.path.isfile(full_filename):
349 348 self.log.info("Running file in user namespace: %s" %
350 349 full_filename)
351 350 # Ensure that __file__ is always defined to match Python
352 351 # behavior.
353 352 with preserve_keys(self.shell.user_ns, '__file__'):
354 353 self.shell.user_ns['__file__'] = fname
355 354 if full_filename.endswith('.ipy') or full_filename.endswith('.ipynb'):
356 355 self.shell.safe_execfile_ipy(full_filename,
357 356 shell_futures=shell_futures)
358 357 else:
359 358 # default to python, even without extension
360 359 self.shell.safe_execfile(full_filename,
361 360 self.shell.user_ns,
362 361 shell_futures=shell_futures,
363 362 raise_exceptions=True)
364 363 finally:
365 364 sys.argv = save_argv
366 365
367 366 def _run_startup_files(self):
368 367 """Run files from profile startup directory"""
369 368 startup_dirs = [self.profile_dir.startup_dir] + [
370 369 os.path.join(p, 'startup') for p in chain(ENV_CONFIG_DIRS, SYSTEM_CONFIG_DIRS)
371 370 ]
372 371 startup_files = []
373 372
374 373 if self.exec_PYTHONSTARTUP and os.environ.get('PYTHONSTARTUP', False) and \
375 374 not (self.file_to_run or self.code_to_run or self.module_to_run):
376 375 python_startup = os.environ['PYTHONSTARTUP']
377 376 self.log.debug("Running PYTHONSTARTUP file %s...", python_startup)
378 377 try:
379 378 self._exec_file(python_startup)
380 379 except:
381 380 self.log.warning("Unknown error in handling PYTHONSTARTUP file %s:", python_startup)
382 381 self.shell.showtraceback()
383 382 for startup_dir in startup_dirs[::-1]:
384 383 startup_files += glob.glob(os.path.join(startup_dir, '*.py'))
385 384 startup_files += glob.glob(os.path.join(startup_dir, '*.ipy'))
386 385 if not startup_files:
387 386 return
388 387
389 388 self.log.debug("Running startup files from %s...", startup_dir)
390 389 try:
391 390 for fname in sorted(startup_files):
392 391 self._exec_file(fname)
393 392 except:
394 393 self.log.warning("Unknown error in handling startup files:")
395 394 self.shell.showtraceback()
396 395
397 396 def _run_exec_files(self):
398 397 """Run files from IPythonApp.exec_files"""
399 398 if not self.exec_files:
400 399 return
401 400
402 401 self.log.debug("Running files in IPythonApp.exec_files...")
403 402 try:
404 403 for fname in self.exec_files:
405 404 self._exec_file(fname)
406 405 except:
407 406 self.log.warning("Unknown error in handling IPythonApp.exec_files:")
408 407 self.shell.showtraceback()
409 408
410 409 def _run_cmd_line_code(self):
411 410 """Run code or file specified at the command-line"""
412 411 if self.code_to_run:
413 412 line = self.code_to_run
414 413 try:
415 414 self.log.info("Running code given at command line (c=): %s" %
416 415 line)
417 416 self.shell.run_cell(line, store_history=False)
418 417 except:
419 418 self.log.warning("Error in executing line in user namespace: %s" %
420 419 line)
421 420 self.shell.showtraceback()
422 421 if not self.interact:
423 422 self.exit(1)
424 423
425 424 # Like Python itself, ignore the second if the first of these is present
426 425 elif self.file_to_run:
427 426 fname = self.file_to_run
428 427 if os.path.isdir(fname):
429 428 fname = os.path.join(fname, "__main__.py")
430 429 if not os.path.exists(fname):
431 430 self.log.warning("File '%s' doesn't exist", fname)
432 431 if not self.interact:
433 432 self.exit(2)
434 433 try:
435 434 self._exec_file(fname, shell_futures=True)
436 435 except:
437 436 self.shell.showtraceback(tb_offset=4)
438 437 if not self.interact:
439 438 self.exit(1)
440 439
441 440 def _run_module(self):
442 441 """Run module specified at the command-line."""
443 442 if self.module_to_run:
444 443 # Make sure that the module gets a proper sys.argv as if it were
445 444 # run using `python -m`.
446 445 save_argv = sys.argv
447 446 sys.argv = [sys.executable] + self.extra_args
448 447 try:
449 448 self.shell.safe_run_module(self.module_to_run,
450 449 self.shell.user_ns)
451 450 finally:
452 451 sys.argv = save_argv
@@ -1,71 +1,68 b''
1 1 # coding: utf-8
2 2 """Tests for the compilerop module.
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (C) 2010-2011 The IPython Development Team.
6 6 #
7 7 # Distributed under the terms of the BSD License.
8 8 #
9 9 # The full license is in the file COPYING.txt, distributed with this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15
16 16 # Stdlib imports
17 17 import linecache
18 18 import sys
19 19
20 # Third-party imports
21 import pytest
22
23 20 # Our own imports
24 21 from IPython.core import compilerop
25 22
26 23 #-----------------------------------------------------------------------------
27 24 # Test functions
28 25 #-----------------------------------------------------------------------------
29 26
30 27 def test_code_name():
31 28 code = 'x=1'
32 29 name = compilerop.code_name(code)
33 30 assert name.startswith("<ipython-input-0")
34 31
35 32
36 33 def test_code_name2():
37 34 code = 'x=1'
38 35 name = compilerop.code_name(code, 9)
39 36 assert name.startswith("<ipython-input-9")
40 37
41 38
42 39 def test_cache():
43 40 """Test the compiler correctly compiles and caches inputs
44 41 """
45 42 cp = compilerop.CachingCompiler()
46 43 ncache = len(linecache.cache)
47 44 cp.cache('x=1')
48 45 assert len(linecache.cache) > ncache
49 46
50 47 def test_proper_default_encoding():
51 48 # Check we're in a proper Python 2 environment (some imports, such
52 49 # as GTK, can change the default encoding, which can hide bugs.)
53 50 assert sys.getdefaultencoding() == "utf-8"
54 51
55 52 def test_cache_unicode():
56 53 cp = compilerop.CachingCompiler()
57 54 ncache = len(linecache.cache)
58 55 cp.cache(u"t = 'žćčőđ'")
59 56 assert len(linecache.cache) > ncache
60 57
61 58 def test_compiler_check_cache():
62 59 """Test the compiler properly manages the cache.
63 60 """
64 61 # Rather simple-minded tests that just exercise the API
65 62 cp = compilerop.CachingCompiler()
66 63 cp.cache('x=1', 99)
67 64 # Ensure now that after clearing the cache, our entries survive
68 65 linecache.checkcache()
69 66 assert any(
70 67 k.startswith("<ipython-input-99") for k in linecache.cache
71 68 ), "Entry for input-99 missing from linecache"
@@ -1,576 +1,575 b''
1 1 """Tests for debugging machinery.
2 2 """
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 import bdb
8 7 import builtins
9 8 import os
10 9 import sys
11 10 import platform
12 11
13 12 from tempfile import NamedTemporaryFile
14 13 from textwrap import dedent
15 14 from unittest.mock import patch
16 15
17 16 from IPython.core import debugger
18 17 from IPython.testing import IPYTHON_TESTING_TIMEOUT_SCALE
19 18 from IPython.testing.decorators import skip_win32
20 19 import pytest
21 20
22 21 #-----------------------------------------------------------------------------
23 22 # Helper classes, from CPython's Pdb test suite
24 23 #-----------------------------------------------------------------------------
25 24
26 25 class _FakeInput(object):
27 26 """
28 27 A fake input stream for pdb's interactive debugger. Whenever a
29 28 line is read, print it (to simulate the user typing it), and then
30 29 return it. The set of lines to return is specified in the
31 30 constructor; they should not have trailing newlines.
32 31 """
33 32 def __init__(self, lines):
34 33 self.lines = iter(lines)
35 34
36 35 def readline(self):
37 36 line = next(self.lines)
38 37 print(line)
39 38 return line+'\n'
40 39
41 40 class PdbTestInput(object):
42 41 """Context manager that makes testing Pdb in doctests easier."""
43 42
44 43 def __init__(self, input):
45 44 self.input = input
46 45
47 46 def __enter__(self):
48 47 self.real_stdin = sys.stdin
49 48 sys.stdin = _FakeInput(self.input)
50 49
51 50 def __exit__(self, *exc):
52 51 sys.stdin = self.real_stdin
53 52
54 53 #-----------------------------------------------------------------------------
55 54 # Tests
56 55 #-----------------------------------------------------------------------------
57 56
58 57 def test_ipdb_magics():
59 58 '''Test calling some IPython magics from ipdb.
60 59
61 60 First, set up some test functions and classes which we can inspect.
62 61
63 62 >>> class ExampleClass(object):
64 63 ... """Docstring for ExampleClass."""
65 64 ... def __init__(self):
66 65 ... """Docstring for ExampleClass.__init__"""
67 66 ... pass
68 67 ... def __str__(self):
69 68 ... return "ExampleClass()"
70 69
71 70 >>> def example_function(x, y, z="hello"):
72 71 ... """Docstring for example_function."""
73 72 ... pass
74 73
75 74 >>> old_trace = sys.gettrace()
76 75
77 76 Create a function which triggers ipdb.
78 77
79 78 >>> def trigger_ipdb():
80 79 ... a = ExampleClass()
81 80 ... debugger.Pdb().set_trace()
82 81
83 82 >>> with PdbTestInput([
84 83 ... 'pdef example_function',
85 84 ... 'pdoc ExampleClass',
86 85 ... 'up',
87 86 ... 'down',
88 87 ... 'list',
89 88 ... 'pinfo a',
90 89 ... 'll',
91 90 ... 'continue',
92 91 ... ]):
93 92 ... trigger_ipdb()
94 93 --Return--
95 94 None
96 95 > <doctest ...>(3)trigger_ipdb()
97 96 1 def trigger_ipdb():
98 97 2 a = ExampleClass()
99 98 ----> 3 debugger.Pdb().set_trace()
100 99 <BLANKLINE>
101 100 ipdb> pdef example_function
102 101 example_function(x, y, z='hello')
103 102 ipdb> pdoc ExampleClass
104 103 Class docstring:
105 104 Docstring for ExampleClass.
106 105 Init docstring:
107 106 Docstring for ExampleClass.__init__
108 107 ipdb> up
109 108 > <doctest ...>(11)<module>()
110 109 7 'pinfo a',
111 110 8 'll',
112 111 9 'continue',
113 112 10 ]):
114 113 ---> 11 trigger_ipdb()
115 114 <BLANKLINE>
116 115 ipdb> down
117 116 None
118 117 > <doctest ...>(3)trigger_ipdb()
119 118 1 def trigger_ipdb():
120 119 2 a = ExampleClass()
121 120 ----> 3 debugger.Pdb().set_trace()
122 121 <BLANKLINE>
123 122 ipdb> list
124 123 1 def trigger_ipdb():
125 124 2 a = ExampleClass()
126 125 ----> 3 debugger.Pdb().set_trace()
127 126 <BLANKLINE>
128 127 ipdb> pinfo a
129 128 Type: ExampleClass
130 129 String form: ExampleClass()
131 130 Namespace: Local...
132 131 Docstring: Docstring for ExampleClass.
133 132 Init docstring: Docstring for ExampleClass.__init__
134 133 ipdb> ll
135 134 1 def trigger_ipdb():
136 135 2 a = ExampleClass()
137 136 ----> 3 debugger.Pdb().set_trace()
138 137 <BLANKLINE>
139 138 ipdb> continue
140 139
141 140 Restore previous trace function, e.g. for coverage.py
142 141
143 142 >>> sys.settrace(old_trace)
144 143 '''
145 144
146 145 def test_ipdb_magics2():
147 146 '''Test ipdb with a very short function.
148 147
149 148 >>> old_trace = sys.gettrace()
150 149
151 150 >>> def bar():
152 151 ... pass
153 152
154 153 Run ipdb.
155 154
156 155 >>> with PdbTestInput([
157 156 ... 'continue',
158 157 ... ]):
159 158 ... debugger.Pdb().runcall(bar)
160 159 > <doctest ...>(2)bar()
161 160 1 def bar():
162 161 ----> 2 pass
163 162 <BLANKLINE>
164 163 ipdb> continue
165 164
166 165 Restore previous trace function, e.g. for coverage.py
167 166
168 167 >>> sys.settrace(old_trace)
169 168 '''
170 169
171 170 def can_quit():
172 171 '''Test that quit work in ipydb
173 172
174 173 >>> old_trace = sys.gettrace()
175 174
176 175 >>> def bar():
177 176 ... pass
178 177
179 178 >>> with PdbTestInput([
180 179 ... 'quit',
181 180 ... ]):
182 181 ... debugger.Pdb().runcall(bar)
183 182 > <doctest ...>(2)bar()
184 183 1 def bar():
185 184 ----> 2 pass
186 185 <BLANKLINE>
187 186 ipdb> quit
188 187
189 188 Restore previous trace function, e.g. for coverage.py
190 189
191 190 >>> sys.settrace(old_trace)
192 191 '''
193 192
194 193
195 194 def can_exit():
196 195 '''Test that quit work in ipydb
197 196
198 197 >>> old_trace = sys.gettrace()
199 198
200 199 >>> def bar():
201 200 ... pass
202 201
203 202 >>> with PdbTestInput([
204 203 ... 'exit',
205 204 ... ]):
206 205 ... debugger.Pdb().runcall(bar)
207 206 > <doctest ...>(2)bar()
208 207 1 def bar():
209 208 ----> 2 pass
210 209 <BLANKLINE>
211 210 ipdb> exit
212 211
213 212 Restore previous trace function, e.g. for coverage.py
214 213
215 214 >>> sys.settrace(old_trace)
216 215 '''
217 216
218 217
219 218 def test_interruptible_core_debugger():
220 219 """The debugger can be interrupted.
221 220
222 221 The presumption is there is some mechanism that causes a KeyboardInterrupt
223 222 (this is implemented in ipykernel). We want to ensure the
224 223 KeyboardInterrupt cause debugging to cease.
225 224 """
226 225 def raising_input(msg="", called=[0]):
227 226 called[0] += 1
228 227 assert called[0] == 1, "input() should only be called once!"
229 228 raise KeyboardInterrupt()
230 229
231 230 tracer_orig = sys.gettrace()
232 231 try:
233 232 with patch.object(builtins, "input", raising_input):
234 233 debugger.InterruptiblePdb().set_trace()
235 234 # The way this test will fail is by set_trace() never exiting,
236 235 # resulting in a timeout by the test runner. The alternative
237 236 # implementation would involve a subprocess, but that adds issues
238 237 # with interrupting subprocesses that are rather complex, so it's
239 238 # simpler just to do it this way.
240 239 finally:
241 240 # restore the original trace function
242 241 sys.settrace(tracer_orig)
243 242
244 243
245 244 @skip_win32
246 245 def test_xmode_skip():
247 246 """that xmode skip frames
248 247
249 248 Not as a doctest as pytest does not run doctests.
250 249 """
251 250 import pexpect
252 251 env = os.environ.copy()
253 252 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
254 253
255 254 child = pexpect.spawn(
256 255 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
257 256 )
258 257 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
259 258
260 259 child.expect("IPython")
261 260 child.expect("\n")
262 261 child.expect_exact("In [1]")
263 262
264 263 block = dedent(
265 264 """
266 265 def f():
267 266 __tracebackhide__ = True
268 267 g()
269 268
270 269 def g():
271 270 raise ValueError
272 271
273 272 f()
274 273 """
275 274 )
276 275
277 276 for line in block.splitlines():
278 277 child.sendline(line)
279 278 child.expect_exact(line)
280 279 child.expect_exact("skipping")
281 280
282 281 block = dedent(
283 282 """
284 283 def f():
285 284 __tracebackhide__ = True
286 285 g()
287 286
288 287 def g():
289 288 from IPython.core.debugger import set_trace
290 289 set_trace()
291 290
292 291 f()
293 292 """
294 293 )
295 294
296 295 for line in block.splitlines():
297 296 child.sendline(line)
298 297 child.expect_exact(line)
299 298
300 299 child.expect("ipdb>")
301 300 child.sendline("w")
302 301 child.expect("hidden")
303 302 child.expect("ipdb>")
304 303 child.sendline("skip_hidden false")
305 304 child.sendline("w")
306 305 child.expect("__traceba")
307 306 child.expect("ipdb>")
308 307
309 308 child.close()
310 309
311 310
312 311 skip_decorators_blocks = (
313 312 """
314 313 def helpers_helper():
315 314 pass # should not stop here except breakpoint
316 315 """,
317 316 """
318 317 def helper_1():
319 318 helpers_helper() # should not stop here
320 319 """,
321 320 """
322 321 def helper_2():
323 322 pass # should not stop here
324 323 """,
325 324 """
326 325 def pdb_skipped_decorator2(function):
327 326 def wrapped_fn(*args, **kwargs):
328 327 __debuggerskip__ = True
329 328 helper_2()
330 329 __debuggerskip__ = False
331 330 result = function(*args, **kwargs)
332 331 __debuggerskip__ = True
333 332 helper_2()
334 333 return result
335 334 return wrapped_fn
336 335 """,
337 336 """
338 337 def pdb_skipped_decorator(function):
339 338 def wrapped_fn(*args, **kwargs):
340 339 __debuggerskip__ = True
341 340 helper_1()
342 341 __debuggerskip__ = False
343 342 result = function(*args, **kwargs)
344 343 __debuggerskip__ = True
345 344 helper_2()
346 345 return result
347 346 return wrapped_fn
348 347 """,
349 348 """
350 349 @pdb_skipped_decorator
351 350 @pdb_skipped_decorator2
352 351 def bar(x, y):
353 352 return x * y
354 353 """,
355 354 """import IPython.terminal.debugger as ipdb""",
356 355 """
357 356 def f():
358 357 ipdb.set_trace()
359 358 bar(3, 4)
360 359 """,
361 360 """
362 361 f()
363 362 """,
364 363 )
365 364
366 365
367 366 def _decorator_skip_setup():
368 367 import pexpect
369 368
370 369 env = os.environ.copy()
371 370 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
372 371 env["PROMPT_TOOLKIT_NO_CPR"] = "1"
373 372
374 373 child = pexpect.spawn(
375 374 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
376 375 )
377 376 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
378 377
379 378 child.expect("IPython")
380 379 child.expect("\n")
381 380
382 381 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
383 382 child.str_last_chars = 500
384 383
385 384 dedented_blocks = [dedent(b).strip() for b in skip_decorators_blocks]
386 385 in_prompt_number = 1
387 386 for cblock in dedented_blocks:
388 387 child.expect_exact(f"In [{in_prompt_number}]:")
389 388 in_prompt_number += 1
390 389 for line in cblock.splitlines():
391 390 child.sendline(line)
392 391 child.expect_exact(line)
393 392 child.sendline("")
394 393 return child
395 394
396 395
397 396 @pytest.mark.skip(reason="recently fail for unknown reason on CI")
398 397 @skip_win32
399 398 def test_decorator_skip():
400 399 """test that decorator frames can be skipped."""
401 400
402 401 child = _decorator_skip_setup()
403 402
404 403 child.expect_exact("ipython-input-8")
405 404 child.expect_exact("3 bar(3, 4)")
406 405 child.expect("ipdb>")
407 406
408 407 child.expect("ipdb>")
409 408 child.sendline("step")
410 409 child.expect_exact("step")
411 410 child.expect_exact("--Call--")
412 411 child.expect_exact("ipython-input-6")
413 412
414 413 child.expect_exact("1 @pdb_skipped_decorator")
415 414
416 415 child.sendline("s")
417 416 child.expect_exact("return x * y")
418 417
419 418 child.close()
420 419
421 420
422 421 @pytest.mark.skip(reason="recently fail for unknown reason on CI")
423 422 @pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy")
424 423 @skip_win32
425 424 def test_decorator_skip_disabled():
426 425 """test that decorator frame skipping can be disabled"""
427 426
428 427 child = _decorator_skip_setup()
429 428
430 429 child.expect_exact("3 bar(3, 4)")
431 430
432 431 for input_, expected in [
433 432 ("skip_predicates debuggerskip False", ""),
434 433 ("skip_predicates", "debuggerskip : False"),
435 434 ("step", "---> 2 def wrapped_fn"),
436 435 ("step", "----> 3 __debuggerskip__"),
437 436 ("step", "----> 4 helper_1()"),
438 437 ("step", "---> 1 def helper_1():"),
439 438 ("next", "----> 2 helpers_helper()"),
440 439 ("next", "--Return--"),
441 440 ("next", "----> 5 __debuggerskip__ = False"),
442 441 ]:
443 442 child.expect("ipdb>")
444 443 child.sendline(input_)
445 444 child.expect_exact(input_)
446 445 child.expect_exact(expected)
447 446
448 447 child.close()
449 448
450 449
451 450 @pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy")
452 451 @skip_win32
453 452 def test_decorator_skip_with_breakpoint():
454 453 """test that decorator frame skipping can be disabled"""
455 454
456 455 import pexpect
457 456
458 457 env = os.environ.copy()
459 458 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
460 459 env["PROMPT_TOOLKIT_NO_CPR"] = "1"
461 460
462 461 child = pexpect.spawn(
463 462 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
464 463 )
465 464 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
466 465 child.str_last_chars = 500
467 466
468 467 child.expect("IPython")
469 468 child.expect("\n")
470 469
471 470 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
472 471
473 472 ### we need a filename, so we need to exec the full block with a filename
474 473 with NamedTemporaryFile(suffix=".py", dir=".", delete=True) as tf:
475 474
476 475 name = tf.name[:-3].split("/")[-1]
477 476 tf.write("\n".join([dedent(x) for x in skip_decorators_blocks[:-1]]).encode())
478 477 tf.flush()
479 478 codeblock = f"from {name} import f"
480 479
481 480 dedented_blocks = [
482 481 codeblock,
483 482 "f()",
484 483 ]
485 484
486 485 in_prompt_number = 1
487 486 for cblock in dedented_blocks:
488 487 child.expect_exact(f"In [{in_prompt_number}]:")
489 488 in_prompt_number += 1
490 489 for line in cblock.splitlines():
491 490 child.sendline(line)
492 491 child.expect_exact(line)
493 492 child.sendline("")
494 493
495 494 # as the filename does not exists, we'll rely on the filename prompt
496 495 child.expect_exact("47 bar(3, 4)")
497 496
498 497 for input_, expected in [
499 498 (f"b {name}.py:3", ""),
500 499 ("step", "1---> 3 pass # should not stop here except"),
501 500 ("step", "---> 38 @pdb_skipped_decorator"),
502 501 ("continue", ""),
503 502 ]:
504 503 child.expect("ipdb>")
505 504 child.sendline(input_)
506 505 child.expect_exact(input_)
507 506 child.expect_exact(expected)
508 507
509 508 child.close()
510 509
511 510
512 511 @skip_win32
513 512 def test_where_erase_value():
514 513 """Test that `where` does not access f_locals and erase values."""
515 514 import pexpect
516 515
517 516 env = os.environ.copy()
518 517 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
519 518
520 519 child = pexpect.spawn(
521 520 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
522 521 )
523 522 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
524 523
525 524 child.expect("IPython")
526 525 child.expect("\n")
527 526 child.expect_exact("In [1]")
528 527
529 528 block = dedent(
530 529 """
531 530 def simple_f():
532 531 myvar = 1
533 532 print(myvar)
534 533 1/0
535 534 print(myvar)
536 535 simple_f() """
537 536 )
538 537
539 538 for line in block.splitlines():
540 539 child.sendline(line)
541 540 child.expect_exact(line)
542 541 child.expect_exact("ZeroDivisionError")
543 542 child.expect_exact("In [2]:")
544 543
545 544 child.sendline("%debug")
546 545
547 546 ##
548 547 child.expect("ipdb>")
549 548
550 549 child.sendline("myvar")
551 550 child.expect("1")
552 551
553 552 ##
554 553 child.expect("ipdb>")
555 554
556 555 child.sendline("myvar = 2")
557 556
558 557 ##
559 558 child.expect_exact("ipdb>")
560 559
561 560 child.sendline("myvar")
562 561
563 562 child.expect_exact("2")
564 563
565 564 ##
566 565 child.expect("ipdb>")
567 566 child.sendline("where")
568 567
569 568 ##
570 569 child.expect("ipdb>")
571 570 child.sendline("myvar")
572 571
573 572 child.expect_exact("2")
574 573 child.expect("ipdb>")
575 574
576 575 child.close()
@@ -1,532 +1,531 b''
1 1 """Tests for the Formatters."""
2 2
3 import warnings
4 3 from math import pi
5 4
6 5 try:
7 6 import numpy
8 7 except:
9 8 numpy = None
10 9 import pytest
11 10
12 11 from IPython import get_ipython
13 12 from traitlets.config import Config
14 13 from IPython.core.formatters import (
15 14 PlainTextFormatter, HTMLFormatter, PDFFormatter, _mod_name_key,
16 15 DisplayFormatter, JSONFormatter,
17 16 )
18 17 from IPython.utils.io import capture_output
19 18
20 19 class A(object):
21 20 def __repr__(self):
22 21 return 'A()'
23 22
24 23 class B(A):
25 24 def __repr__(self):
26 25 return 'B()'
27 26
28 27 class C:
29 28 pass
30 29
31 30 class BadRepr(object):
32 31 def __repr__(self):
33 32 raise ValueError("bad repr")
34 33
35 34 class BadPretty(object):
36 35 _repr_pretty_ = None
37 36
38 37 class GoodPretty(object):
39 38 def _repr_pretty_(self, pp, cycle):
40 39 pp.text('foo')
41 40
42 41 def __repr__(self):
43 42 return 'GoodPretty()'
44 43
45 44 def foo_printer(obj, pp, cycle):
46 45 pp.text('foo')
47 46
48 47 def test_pretty():
49 48 f = PlainTextFormatter()
50 49 f.for_type(A, foo_printer)
51 50 assert f(A()) == "foo"
52 51 assert f(B()) == "B()"
53 52 assert f(GoodPretty()) == "foo"
54 53 # Just don't raise an exception for the following:
55 54 f(BadPretty())
56 55
57 56 f.pprint = False
58 57 assert f(A()) == "A()"
59 58 assert f(B()) == "B()"
60 59 assert f(GoodPretty()) == "GoodPretty()"
61 60
62 61
63 62 def test_deferred():
64 63 f = PlainTextFormatter()
65 64
66 65 def test_precision():
67 66 """test various values for float_precision."""
68 67 f = PlainTextFormatter()
69 68 assert f(pi) == repr(pi)
70 69 f.float_precision = 0
71 70 if numpy:
72 71 po = numpy.get_printoptions()
73 72 assert po["precision"] == 0
74 73 assert f(pi) == "3"
75 74 f.float_precision = 2
76 75 if numpy:
77 76 po = numpy.get_printoptions()
78 77 assert po["precision"] == 2
79 78 assert f(pi) == "3.14"
80 79 f.float_precision = "%g"
81 80 if numpy:
82 81 po = numpy.get_printoptions()
83 82 assert po["precision"] == 2
84 83 assert f(pi) == "3.14159"
85 84 f.float_precision = "%e"
86 85 assert f(pi) == "3.141593e+00"
87 86 f.float_precision = ""
88 87 if numpy:
89 88 po = numpy.get_printoptions()
90 89 assert po["precision"] == 8
91 90 assert f(pi) == repr(pi)
92 91
93 92
94 93 def test_bad_precision():
95 94 """test various invalid values for float_precision."""
96 95 f = PlainTextFormatter()
97 96 def set_fp(p):
98 97 f.float_precision = p
99 98
100 99 pytest.raises(ValueError, set_fp, "%")
101 100 pytest.raises(ValueError, set_fp, "%.3f%i")
102 101 pytest.raises(ValueError, set_fp, "foo")
103 102 pytest.raises(ValueError, set_fp, -1)
104 103
105 104 def test_for_type():
106 105 f = PlainTextFormatter()
107 106
108 107 # initial return, None
109 108 assert f.for_type(C, foo_printer) is None
110 109 # no func queries
111 110 assert f.for_type(C) is foo_printer
112 111 # shouldn't change anything
113 112 assert f.for_type(C) is foo_printer
114 113 # None should do the same
115 114 assert f.for_type(C, None) is foo_printer
116 115 assert f.for_type(C, None) is foo_printer
117 116
118 117 def test_for_type_string():
119 118 f = PlainTextFormatter()
120 119
121 120 type_str = '%s.%s' % (C.__module__, 'C')
122 121
123 122 # initial return, None
124 123 assert f.for_type(type_str, foo_printer) is None
125 124 # no func queries
126 125 assert f.for_type(type_str) is foo_printer
127 126 assert _mod_name_key(C) in f.deferred_printers
128 127 assert f.for_type(C) is foo_printer
129 128 assert _mod_name_key(C) not in f.deferred_printers
130 129 assert C in f.type_printers
131 130
132 131 def test_for_type_by_name():
133 132 f = PlainTextFormatter()
134 133
135 134 mod = C.__module__
136 135
137 136 # initial return, None
138 137 assert f.for_type_by_name(mod, "C", foo_printer) is None
139 138 # no func queries
140 139 assert f.for_type_by_name(mod, "C") is foo_printer
141 140 # shouldn't change anything
142 141 assert f.for_type_by_name(mod, "C") is foo_printer
143 142 # None should do the same
144 143 assert f.for_type_by_name(mod, "C", None) is foo_printer
145 144 assert f.for_type_by_name(mod, "C", None) is foo_printer
146 145
147 146
148 147 def test_lookup():
149 148 f = PlainTextFormatter()
150 149
151 150 f.for_type(C, foo_printer)
152 151 assert f.lookup(C()) is foo_printer
153 152 with pytest.raises(KeyError):
154 153 f.lookup(A())
155 154
156 155 def test_lookup_string():
157 156 f = PlainTextFormatter()
158 157 type_str = '%s.%s' % (C.__module__, 'C')
159 158
160 159 f.for_type(type_str, foo_printer)
161 160 assert f.lookup(C()) is foo_printer
162 161 # should move from deferred to imported dict
163 162 assert _mod_name_key(C) not in f.deferred_printers
164 163 assert C in f.type_printers
165 164
166 165 def test_lookup_by_type():
167 166 f = PlainTextFormatter()
168 167 f.for_type(C, foo_printer)
169 168 assert f.lookup_by_type(C) is foo_printer
170 169 with pytest.raises(KeyError):
171 170 f.lookup_by_type(A)
172 171
173 172 def test_lookup_by_type_string():
174 173 f = PlainTextFormatter()
175 174 type_str = '%s.%s' % (C.__module__, 'C')
176 175 f.for_type(type_str, foo_printer)
177 176
178 177 # verify insertion
179 178 assert _mod_name_key(C) in f.deferred_printers
180 179 assert C not in f.type_printers
181 180
182 181 assert f.lookup_by_type(type_str) is foo_printer
183 182 # lookup by string doesn't cause import
184 183 assert _mod_name_key(C) in f.deferred_printers
185 184 assert C not in f.type_printers
186 185
187 186 assert f.lookup_by_type(C) is foo_printer
188 187 # should move from deferred to imported dict
189 188 assert _mod_name_key(C) not in f.deferred_printers
190 189 assert C in f.type_printers
191 190
192 191 def test_in_formatter():
193 192 f = PlainTextFormatter()
194 193 f.for_type(C, foo_printer)
195 194 type_str = '%s.%s' % (C.__module__, 'C')
196 195 assert C in f
197 196 assert type_str in f
198 197
199 198 def test_string_in_formatter():
200 199 f = PlainTextFormatter()
201 200 type_str = '%s.%s' % (C.__module__, 'C')
202 201 f.for_type(type_str, foo_printer)
203 202 assert type_str in f
204 203 assert C in f
205 204
206 205 def test_pop():
207 206 f = PlainTextFormatter()
208 207 f.for_type(C, foo_printer)
209 208 assert f.lookup_by_type(C) is foo_printer
210 209 assert f.pop(C, None) is foo_printer
211 210 f.for_type(C, foo_printer)
212 211 assert f.pop(C) is foo_printer
213 212 with pytest.raises(KeyError):
214 213 f.lookup_by_type(C)
215 214 with pytest.raises(KeyError):
216 215 f.pop(C)
217 216 with pytest.raises(KeyError):
218 217 f.pop(A)
219 218 assert f.pop(A, None) is None
220 219
221 220 def test_pop_string():
222 221 f = PlainTextFormatter()
223 222 type_str = '%s.%s' % (C.__module__, 'C')
224 223
225 224 with pytest.raises(KeyError):
226 225 f.pop(type_str)
227 226
228 227 f.for_type(type_str, foo_printer)
229 228 f.pop(type_str)
230 229 with pytest.raises(KeyError):
231 230 f.lookup_by_type(C)
232 231 with pytest.raises(KeyError):
233 232 f.pop(type_str)
234 233
235 234 f.for_type(C, foo_printer)
236 235 assert f.pop(type_str, None) is foo_printer
237 236 with pytest.raises(KeyError):
238 237 f.lookup_by_type(C)
239 238 with pytest.raises(KeyError):
240 239 f.pop(type_str)
241 240 assert f.pop(type_str, None) is None
242 241
243 242
244 243 def test_error_method():
245 244 f = HTMLFormatter()
246 245 class BadHTML(object):
247 246 def _repr_html_(self):
248 247 raise ValueError("Bad HTML")
249 248 bad = BadHTML()
250 249 with capture_output() as captured:
251 250 result = f(bad)
252 251 assert result is None
253 252 assert "Traceback" in captured.stdout
254 253 assert "Bad HTML" in captured.stdout
255 254 assert "_repr_html_" in captured.stdout
256 255
257 256 def test_nowarn_notimplemented():
258 257 f = HTMLFormatter()
259 258 class HTMLNotImplemented(object):
260 259 def _repr_html_(self):
261 260 raise NotImplementedError
262 261 h = HTMLNotImplemented()
263 262 with capture_output() as captured:
264 263 result = f(h)
265 264 assert result is None
266 265 assert "" == captured.stderr
267 266 assert "" == captured.stdout
268 267
269 268
270 269 def test_warn_error_for_type():
271 270 f = HTMLFormatter()
272 271 f.for_type(int, lambda i: name_error)
273 272 with capture_output() as captured:
274 273 result = f(5)
275 274 assert result is None
276 275 assert "Traceback" in captured.stdout
277 276 assert "NameError" in captured.stdout
278 277 assert "name_error" in captured.stdout
279 278
280 279 def test_error_pretty_method():
281 280 f = PlainTextFormatter()
282 281 class BadPretty(object):
283 282 def _repr_pretty_(self):
284 283 return "hello"
285 284 bad = BadPretty()
286 285 with capture_output() as captured:
287 286 result = f(bad)
288 287 assert result is None
289 288 assert "Traceback" in captured.stdout
290 289 assert "_repr_pretty_" in captured.stdout
291 290 assert "given" in captured.stdout
292 291 assert "argument" in captured.stdout
293 292
294 293
295 294 def test_bad_repr_traceback():
296 295 f = PlainTextFormatter()
297 296 bad = BadRepr()
298 297 with capture_output() as captured:
299 298 result = f(bad)
300 299 # catches error, returns None
301 300 assert result is None
302 301 assert "Traceback" in captured.stdout
303 302 assert "__repr__" in captured.stdout
304 303 assert "ValueError" in captured.stdout
305 304
306 305
307 306 class MakePDF(object):
308 307 def _repr_pdf_(self):
309 308 return 'PDF'
310 309
311 310 def test_pdf_formatter():
312 311 pdf = MakePDF()
313 312 f = PDFFormatter()
314 313 assert f(pdf) == "PDF"
315 314
316 315
317 316 def test_print_method_bound():
318 317 f = HTMLFormatter()
319 318 class MyHTML(object):
320 319 def _repr_html_(self):
321 320 return "hello"
322 321 with capture_output() as captured:
323 322 result = f(MyHTML)
324 323 assert result is None
325 324 assert "FormatterWarning" not in captured.stderr
326 325
327 326 with capture_output() as captured:
328 327 result = f(MyHTML())
329 328 assert result == "hello"
330 329 assert captured.stderr == ""
331 330
332 331
333 332 def test_print_method_weird():
334 333
335 334 class TextMagicHat(object):
336 335 def __getattr__(self, key):
337 336 return key
338 337
339 338 f = HTMLFormatter()
340 339
341 340 text_hat = TextMagicHat()
342 341 assert text_hat._repr_html_ == "_repr_html_"
343 342 with capture_output() as captured:
344 343 result = f(text_hat)
345 344
346 345 assert result is None
347 346 assert "FormatterWarning" not in captured.stderr
348 347
349 348 class CallableMagicHat(object):
350 349 def __getattr__(self, key):
351 350 return lambda : key
352 351
353 352 call_hat = CallableMagicHat()
354 353 with capture_output() as captured:
355 354 result = f(call_hat)
356 355
357 356 assert result is None
358 357
359 358 class BadReprArgs(object):
360 359 def _repr_html_(self, extra, args):
361 360 return "html"
362 361
363 362 bad = BadReprArgs()
364 363 with capture_output() as captured:
365 364 result = f(bad)
366 365
367 366 assert result is None
368 367 assert "FormatterWarning" not in captured.stderr
369 368
370 369
371 370 def test_format_config():
372 371 """config objects don't pretend to support fancy reprs with lazy attrs"""
373 372 f = HTMLFormatter()
374 373 cfg = Config()
375 374 with capture_output() as captured:
376 375 result = f(cfg)
377 376 assert result is None
378 377 assert captured.stderr == ""
379 378
380 379 with capture_output() as captured:
381 380 result = f(Config)
382 381 assert result is None
383 382 assert captured.stderr == ""
384 383
385 384
386 385 def test_pretty_max_seq_length():
387 386 f = PlainTextFormatter(max_seq_length=1)
388 387 lis = list(range(3))
389 388 text = f(lis)
390 389 assert text == "[0, ...]"
391 390 f.max_seq_length = 0
392 391 text = f(lis)
393 392 assert text == "[0, 1, 2]"
394 393 text = f(list(range(1024)))
395 394 lines = text.splitlines()
396 395 assert len(lines) == 1024
397 396
398 397
399 398 def test_ipython_display_formatter():
400 399 """Objects with _ipython_display_ defined bypass other formatters"""
401 400 f = get_ipython().display_formatter
402 401 catcher = []
403 402 class SelfDisplaying(object):
404 403 def _ipython_display_(self):
405 404 catcher.append(self)
406 405
407 406 class NotSelfDisplaying(object):
408 407 def __repr__(self):
409 408 return "NotSelfDisplaying"
410 409
411 410 def _ipython_display_(self):
412 411 raise NotImplementedError
413 412
414 413 save_enabled = f.ipython_display_formatter.enabled
415 414 f.ipython_display_formatter.enabled = True
416 415
417 416 yes = SelfDisplaying()
418 417 no = NotSelfDisplaying()
419 418
420 419 d, md = f.format(no)
421 420 assert d == {"text/plain": repr(no)}
422 421 assert md == {}
423 422 assert catcher == []
424 423
425 424 d, md = f.format(yes)
426 425 assert d == {}
427 426 assert md == {}
428 427 assert catcher == [yes]
429 428
430 429 f.ipython_display_formatter.enabled = save_enabled
431 430
432 431
433 432 def test_repr_mime():
434 433 class HasReprMime(object):
435 434 def _repr_mimebundle_(self, include=None, exclude=None):
436 435 return {
437 436 'application/json+test.v2': {
438 437 'x': 'y'
439 438 },
440 439 'plain/text' : '<HasReprMime>',
441 440 'image/png' : 'i-overwrite'
442 441 }
443 442
444 443 def _repr_png_(self):
445 444 return 'should-be-overwritten'
446 445 def _repr_html_(self):
447 446 return '<b>hi!</b>'
448 447
449 448 f = get_ipython().display_formatter
450 449 html_f = f.formatters['text/html']
451 450 save_enabled = html_f.enabled
452 451 html_f.enabled = True
453 452 obj = HasReprMime()
454 453 d, md = f.format(obj)
455 454 html_f.enabled = save_enabled
456 455
457 456 assert sorted(d) == [
458 457 "application/json+test.v2",
459 458 "image/png",
460 459 "plain/text",
461 460 "text/html",
462 461 "text/plain",
463 462 ]
464 463 assert md == {}
465 464
466 465 d, md = f.format(obj, include={"image/png"})
467 466 assert list(d.keys()) == [
468 467 "image/png"
469 468 ], "Include should filter out even things from repr_mimebundle"
470 469
471 470 assert d["image/png"] == "i-overwrite", "_repr_mimebundle_ take precedence"
472 471
473 472
474 473 def test_pass_correct_include_exclude():
475 474 class Tester(object):
476 475
477 476 def __init__(self, include=None, exclude=None):
478 477 self.include = include
479 478 self.exclude = exclude
480 479
481 480 def _repr_mimebundle_(self, include, exclude, **kwargs):
482 481 if include and (include != self.include):
483 482 raise ValueError('include got modified: display() may be broken.')
484 483 if exclude and (exclude != self.exclude):
485 484 raise ValueError('exclude got modified: display() may be broken.')
486 485
487 486 return None
488 487
489 488 include = {'a', 'b', 'c'}
490 489 exclude = {'c', 'e' , 'f'}
491 490
492 491 f = get_ipython().display_formatter
493 492 f.format(Tester(include=include, exclude=exclude), include=include, exclude=exclude)
494 493 f.format(Tester(exclude=exclude), exclude=exclude)
495 494 f.format(Tester(include=include), include=include)
496 495
497 496
498 497 def test_repr_mime_meta():
499 498 class HasReprMimeMeta(object):
500 499 def _repr_mimebundle_(self, include=None, exclude=None):
501 500 data = {
502 501 'image/png': 'base64-image-data',
503 502 }
504 503 metadata = {
505 504 'image/png': {
506 505 'width': 5,
507 506 'height': 10,
508 507 }
509 508 }
510 509 return (data, metadata)
511 510
512 511 f = get_ipython().display_formatter
513 512 obj = HasReprMimeMeta()
514 513 d, md = f.format(obj)
515 514 assert sorted(d) == ["image/png", "text/plain"]
516 515 assert md == {
517 516 "image/png": {
518 517 "width": 5,
519 518 "height": 10,
520 519 }
521 520 }
522 521
523 522
524 523 def test_repr_mime_failure():
525 524 class BadReprMime(object):
526 525 def _repr_mimebundle_(self, include=None, exclude=None):
527 526 raise RuntimeError
528 527
529 528 f = get_ipython().display_formatter
530 529 obj = BadReprMime()
531 530 d, md = f.format(obj)
532 531 assert "text/plain" in d
@@ -1,307 +1,306 b''
1 1 # coding: utf-8
2 2 """Tests for the IPython tab-completion machinery.
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 # Module imports
6 6 #-----------------------------------------------------------------------------
7 7
8 8 # stdlib
9 9 import io
10 import sqlite3
11 10 import sys
12 11 import tempfile
13 12 from datetime import datetime
14 13 from pathlib import Path
15 14
16 15 from tempfile import TemporaryDirectory
17 16 # our own packages
18 17 from traitlets.config.loader import Config
19 18
20 19 from IPython.core.history import HistoryAccessor, HistoryManager, extract_hist_ranges
21 20
22 21
23 22 def test_proper_default_encoding():
24 23 assert sys.getdefaultencoding() == "utf-8"
25 24
26 25 def test_history():
27 26 ip = get_ipython()
28 27 with TemporaryDirectory() as tmpdir:
29 28 tmp_path = Path(tmpdir)
30 29 hist_manager_ori = ip.history_manager
31 30 hist_file = tmp_path / "history.sqlite"
32 31 try:
33 32 ip.history_manager = HistoryManager(shell=ip, hist_file=hist_file)
34 33 hist = ["a=1", "def f():\n test = 1\n return test", "b='β‚¬Γ†ΒΎΓ·ΓŸ'"]
35 34 for i, h in enumerate(hist, start=1):
36 35 ip.history_manager.store_inputs(i, h)
37 36
38 37 ip.history_manager.db_log_output = True
39 38 # Doesn't match the input, but we'll just check it's stored.
40 39 ip.history_manager.output_hist_reprs[3] = "spam"
41 40 ip.history_manager.store_output(3)
42 41
43 42 assert ip.history_manager.input_hist_raw == [""] + hist
44 43
45 44 # Detailed tests for _get_range_session
46 45 grs = ip.history_manager._get_range_session
47 46 assert list(grs(start=2, stop=-1)) == list(zip([0], [2], hist[1:-1]))
48 47 assert list(grs(start=-2)) == list(zip([0, 0], [2, 3], hist[-2:]))
49 48 assert list(grs(output=True)) == list(
50 49 zip([0, 0, 0], [1, 2, 3], zip(hist, [None, None, "spam"]))
51 50 )
52 51
53 52 # Check whether specifying a range beyond the end of the current
54 53 # session results in an error (gh-804)
55 54 ip.run_line_magic("hist", "2-500")
56 55
57 56 # Check that we can write non-ascii characters to a file
58 57 ip.run_line_magic("hist", "-f %s" % (tmp_path / "test1"))
59 58 ip.run_line_magic("hist", "-pf %s" % (tmp_path / "test2"))
60 59 ip.run_line_magic("hist", "-nf %s" % (tmp_path / "test3"))
61 60 ip.run_line_magic("save", "%s 1-10" % (tmp_path / "test4"))
62 61
63 62 # New session
64 63 ip.history_manager.reset()
65 64 newcmds = ["z=5", "class X(object):\n pass", "k='p'", "z=5"]
66 65 for i, cmd in enumerate(newcmds, start=1):
67 66 ip.history_manager.store_inputs(i, cmd)
68 67 gothist = ip.history_manager.get_range(start=1, stop=4)
69 68 assert list(gothist) == list(zip([0, 0, 0], [1, 2, 3], newcmds))
70 69 # Previous session:
71 70 gothist = ip.history_manager.get_range(-1, 1, 4)
72 71 assert list(gothist) == list(zip([1, 1, 1], [1, 2, 3], hist))
73 72
74 73 newhist = [(2, i, c) for (i, c) in enumerate(newcmds, 1)]
75 74
76 75 # Check get_hist_tail
77 76 gothist = ip.history_manager.get_tail(5, output=True,
78 77 include_latest=True)
79 78 expected = [(1, 3, (hist[-1], "spam"))] \
80 79 + [(s, n, (c, None)) for (s, n, c) in newhist]
81 80 assert list(gothist) == expected
82 81
83 82 gothist = ip.history_manager.get_tail(2)
84 83 expected = newhist[-3:-1]
85 84 assert list(gothist) == expected
86 85
87 86 # Check get_hist_search
88 87
89 88 gothist = ip.history_manager.search("*test*")
90 89 assert list(gothist) == [(1, 2, hist[1])]
91 90
92 91 gothist = ip.history_manager.search("*=*")
93 92 assert list(gothist) == [
94 93 (1, 1, hist[0]),
95 94 (1, 2, hist[1]),
96 95 (1, 3, hist[2]),
97 96 newhist[0],
98 97 newhist[2],
99 98 newhist[3],
100 99 ]
101 100
102 101 gothist = ip.history_manager.search("*=*", n=4)
103 102 assert list(gothist) == [
104 103 (1, 3, hist[2]),
105 104 newhist[0],
106 105 newhist[2],
107 106 newhist[3],
108 107 ]
109 108
110 109 gothist = ip.history_manager.search("*=*", unique=True)
111 110 assert list(gothist) == [
112 111 (1, 1, hist[0]),
113 112 (1, 2, hist[1]),
114 113 (1, 3, hist[2]),
115 114 newhist[2],
116 115 newhist[3],
117 116 ]
118 117
119 118 gothist = ip.history_manager.search("*=*", unique=True, n=3)
120 119 assert list(gothist) == [(1, 3, hist[2]), newhist[2], newhist[3]]
121 120
122 121 gothist = ip.history_manager.search("b*", output=True)
123 122 assert list(gothist) == [(1, 3, (hist[2], "spam"))]
124 123
125 124 # Cross testing: check that magic %save can get previous session.
126 125 testfilename = (tmp_path / "test.py").resolve()
127 126 ip.run_line_magic("save", str(testfilename) + " ~1/1-3")
128 127 with io.open(testfilename, encoding="utf-8") as testfile:
129 128 assert testfile.read() == "# coding: utf-8\n" + "\n".join(hist) + "\n"
130 129
131 130 # Duplicate line numbers - check that it doesn't crash, and
132 131 # gets a new session
133 132 ip.history_manager.store_inputs(1, "rogue")
134 133 ip.history_manager.writeout_cache()
135 134 assert ip.history_manager.session_number == 3
136 135
137 136 # Check that session and line values are not just max values
138 137 sessid, lineno, entry = newhist[-1]
139 138 assert lineno > 1
140 139 ip.history_manager.reset()
141 140 lineno = 1
142 141 ip.history_manager.store_inputs(lineno, entry)
143 142 gothist = ip.history_manager.search("*=*", unique=True)
144 143 hist = list(gothist)[-1]
145 144 assert sessid < hist[0]
146 145 assert hist[1:] == (lineno, entry)
147 146 finally:
148 147 # Ensure saving thread is shut down before we try to clean up the files
149 148 ip.history_manager.save_thread.stop()
150 149 # Forcibly close database rather than relying on garbage collection
151 150 ip.history_manager.db.close()
152 151 # Restore history manager
153 152 ip.history_manager = hist_manager_ori
154 153
155 154
156 155 def test_extract_hist_ranges():
157 156 instr = "1 2/3 ~4/5-6 ~4/7-~4/9 ~9/2-~7/5 ~10/"
158 157 expected = [(0, 1, 2), # 0 == current session
159 158 (2, 3, 4),
160 159 (-4, 5, 7),
161 160 (-4, 7, 10),
162 161 (-9, 2, None), # None == to end
163 162 (-8, 1, None),
164 163 (-7, 1, 6),
165 164 (-10, 1, None)]
166 165 actual = list(extract_hist_ranges(instr))
167 166 assert actual == expected
168 167
169 168
170 169 def test_extract_hist_ranges_empty_str():
171 170 instr = ""
172 171 expected = [(0, 1, None)] # 0 == current session, None == to end
173 172 actual = list(extract_hist_ranges(instr))
174 173 assert actual == expected
175 174
176 175
177 176 def test_magic_rerun():
178 177 """Simple test for %rerun (no args -> rerun last line)"""
179 178 ip = get_ipython()
180 179 ip.run_cell("a = 10", store_history=True)
181 180 ip.run_cell("a += 1", store_history=True)
182 181 assert ip.user_ns["a"] == 11
183 182 ip.run_cell("%rerun", store_history=True)
184 183 assert ip.user_ns["a"] == 12
185 184
186 185 def test_timestamp_type():
187 186 ip = get_ipython()
188 187 info = ip.history_manager.get_session_info()
189 188 assert isinstance(info[1], datetime)
190 189
191 190 def test_hist_file_config():
192 191 cfg = Config()
193 192 tfile = tempfile.NamedTemporaryFile(delete=False)
194 193 cfg.HistoryManager.hist_file = Path(tfile.name)
195 194 try:
196 195 hm = HistoryManager(shell=get_ipython(), config=cfg)
197 196 assert hm.hist_file == cfg.HistoryManager.hist_file
198 197 finally:
199 198 try:
200 199 Path(tfile.name).unlink()
201 200 except OSError:
202 201 # same catch as in testing.tools.TempFileMixin
203 202 # On Windows, even though we close the file, we still can't
204 203 # delete it. I have no clue why
205 204 pass
206 205
207 206 def test_histmanager_disabled():
208 207 """Ensure that disabling the history manager doesn't create a database."""
209 208 cfg = Config()
210 209 cfg.HistoryAccessor.enabled = False
211 210
212 211 ip = get_ipython()
213 212 with TemporaryDirectory() as tmpdir:
214 213 hist_manager_ori = ip.history_manager
215 214 hist_file = Path(tmpdir) / "history.sqlite"
216 215 cfg.HistoryManager.hist_file = hist_file
217 216 try:
218 217 ip.history_manager = HistoryManager(shell=ip, config=cfg)
219 218 hist = ["a=1", "def f():\n test = 1\n return test", "b='β‚¬Γ†ΒΎΓ·ΓŸ'"]
220 219 for i, h in enumerate(hist, start=1):
221 220 ip.history_manager.store_inputs(i, h)
222 221 assert ip.history_manager.input_hist_raw == [""] + hist
223 222 ip.history_manager.reset()
224 223 ip.history_manager.end_session()
225 224 finally:
226 225 ip.history_manager = hist_manager_ori
227 226
228 227 # hist_file should not be created
229 228 assert hist_file.exists() is False
230 229
231 230
232 231 def test_get_tail_session_awareness():
233 232 """Test .get_tail() is:
234 233 - session specific in HistoryManager
235 234 - session agnostic in HistoryAccessor
236 235 same for .get_last_session_id()
237 236 """
238 237 ip = get_ipython()
239 238 with TemporaryDirectory() as tmpdir:
240 239 tmp_path = Path(tmpdir)
241 240 hist_file = tmp_path / "history.sqlite"
242 241 get_source = lambda x: x[2]
243 242 hm1 = None
244 243 hm2 = None
245 244 ha = None
246 245 try:
247 246 # hm1 creates a new session and adds history entries,
248 247 # ha catches up
249 248 hm1 = HistoryManager(shell=ip, hist_file=hist_file)
250 249 hm1_last_sid = hm1.get_last_session_id
251 250 ha = HistoryAccessor(hist_file=hist_file)
252 251 ha_last_sid = ha.get_last_session_id
253 252
254 253 hist1 = ["a=1", "b=1", "c=1"]
255 254 for i, h in enumerate(hist1 + [""], start=1):
256 255 hm1.store_inputs(i, h)
257 256 assert list(map(get_source, hm1.get_tail())) == hist1
258 257 assert list(map(get_source, ha.get_tail())) == hist1
259 258 sid1 = hm1_last_sid()
260 259 assert sid1 is not None
261 260 assert ha_last_sid() == sid1
262 261
263 262 # hm2 creates a new session and adds entries,
264 263 # ha catches up
265 264 hm2 = HistoryManager(shell=ip, hist_file=hist_file)
266 265 hm2_last_sid = hm2.get_last_session_id
267 266
268 267 hist2 = ["a=2", "b=2", "c=2"]
269 268 for i, h in enumerate(hist2 + [""], start=1):
270 269 hm2.store_inputs(i, h)
271 270 tail = hm2.get_tail(n=3)
272 271 assert list(map(get_source, tail)) == hist2
273 272 tail = ha.get_tail(n=3)
274 273 assert list(map(get_source, tail)) == hist2
275 274 sid2 = hm2_last_sid()
276 275 assert sid2 is not None
277 276 assert ha_last_sid() == sid2
278 277 assert sid2 != sid1
279 278
280 279 # but hm1 still maintains its point of reference
281 280 # and adding more entries to it doesn't change others
282 281 # immediate perspective
283 282 assert hm1_last_sid() == sid1
284 283 tail = hm1.get_tail(n=3)
285 284 assert list(map(get_source, tail)) == hist1
286 285
287 286 hist3 = ["a=3", "b=3", "c=3"]
288 287 for i, h in enumerate(hist3 + [""], start=5):
289 288 hm1.store_inputs(i, h)
290 289 tail = hm1.get_tail(n=7)
291 290 assert list(map(get_source, tail)) == hist1 + [""] + hist3
292 291 tail = hm2.get_tail(n=3)
293 292 assert list(map(get_source, tail)) == hist2
294 293 tail = ha.get_tail(n=3)
295 294 assert list(map(get_source, tail)) == hist2
296 295 assert hm1_last_sid() == sid1
297 296 assert hm2_last_sid() == sid2
298 297 assert ha_last_sid() == sid2
299 298 finally:
300 299 if hm1:
301 300 hm1.save_thread.stop()
302 301 hm1.db.close()
303 302 if hm2:
304 303 hm2.save_thread.stop()
305 304 hm2.db.close()
306 305 if ha:
307 306 ha.db.close()
@@ -1,168 +1,167 b''
1 1 """Tests for the line-based transformers in IPython.core.inputtransformer2
2 2
3 3 Line-based transformers are the simpler ones; token-based transformers are
4 4 more complex. See test_inputtransformer2 for tests for token-based transformers.
5 5 """
6 import pytest
7 6
8 7 from IPython.core import inputtransformer2 as ipt2
9 8
10 9 CELL_MAGIC = ("""\
11 10 %%foo arg
12 11 body 1
13 12 body 2
14 13 """, """\
15 14 get_ipython().run_cell_magic('foo', 'arg', 'body 1\\nbody 2\\n')
16 15 """)
17 16
18 17 def test_cell_magic():
19 18 for sample, expected in [CELL_MAGIC]:
20 19 assert ipt2.cell_magic(sample.splitlines(keepends=True)) == expected.splitlines(
21 20 keepends=True
22 21 )
23 22
24 23 CLASSIC_PROMPT = ("""\
25 24 >>> for a in range(5):
26 25 ... print(a)
27 26 """, """\
28 27 for a in range(5):
29 28 print(a)
30 29 """)
31 30
32 31 CLASSIC_PROMPT_L2 = ("""\
33 32 for a in range(5):
34 33 ... print(a)
35 34 ... print(a ** 2)
36 35 """, """\
37 36 for a in range(5):
38 37 print(a)
39 38 print(a ** 2)
40 39 """)
41 40
42 41 def test_classic_prompt():
43 42 for sample, expected in [CLASSIC_PROMPT, CLASSIC_PROMPT_L2]:
44 43 assert ipt2.classic_prompt(
45 44 sample.splitlines(keepends=True)
46 45 ) == expected.splitlines(keepends=True)
47 46
48 47 IPYTHON_PROMPT = ("""\
49 48 In [1]: for a in range(5):
50 49 ...: print(a)
51 50 """, """\
52 51 for a in range(5):
53 52 print(a)
54 53 """)
55 54
56 55 IPYTHON_PROMPT_L2 = ("""\
57 56 for a in range(5):
58 57 ...: print(a)
59 58 ...: print(a ** 2)
60 59 """, """\
61 60 for a in range(5):
62 61 print(a)
63 62 print(a ** 2)
64 63 """)
65 64
66 65
67 66 IPYTHON_PROMPT_VI_INS = (
68 67 """\
69 68 [ins] In [11]: def a():
70 69 ...: 123
71 70 ...:
72 71 ...: 123
73 72 """,
74 73 """\
75 74 def a():
76 75 123
77 76
78 77 123
79 78 """,
80 79 )
81 80
82 81 IPYTHON_PROMPT_VI_NAV = (
83 82 """\
84 83 [nav] In [11]: def a():
85 84 ...: 123
86 85 ...:
87 86 ...: 123
88 87 """,
89 88 """\
90 89 def a():
91 90 123
92 91
93 92 123
94 93 """,
95 94 )
96 95
97 96
98 97 def test_ipython_prompt():
99 98 for sample, expected in [
100 99 IPYTHON_PROMPT,
101 100 IPYTHON_PROMPT_L2,
102 101 IPYTHON_PROMPT_VI_INS,
103 102 IPYTHON_PROMPT_VI_NAV,
104 103 ]:
105 104 assert ipt2.ipython_prompt(
106 105 sample.splitlines(keepends=True)
107 106 ) == expected.splitlines(keepends=True)
108 107
109 108
110 109 INDENT_SPACES = ("""\
111 110 if True:
112 111 a = 3
113 112 """, """\
114 113 if True:
115 114 a = 3
116 115 """)
117 116
118 117 INDENT_TABS = ("""\
119 118 \tif True:
120 119 \t\tb = 4
121 120 """, """\
122 121 if True:
123 122 \tb = 4
124 123 """)
125 124
126 125 def test_leading_indent():
127 126 for sample, expected in [INDENT_SPACES, INDENT_TABS]:
128 127 assert ipt2.leading_indent(
129 128 sample.splitlines(keepends=True)
130 129 ) == expected.splitlines(keepends=True)
131 130
132 131 LEADING_EMPTY_LINES = ("""\
133 132 \t
134 133
135 134 if True:
136 135 a = 3
137 136
138 137 b = 4
139 138 """, """\
140 139 if True:
141 140 a = 3
142 141
143 142 b = 4
144 143 """)
145 144
146 145 ONLY_EMPTY_LINES = ("""\
147 146 \t
148 147
149 148 """, """\
150 149 \t
151 150
152 151 """)
153 152
154 153 def test_leading_empty_lines():
155 154 for sample, expected in [LEADING_EMPTY_LINES, ONLY_EMPTY_LINES]:
156 155 assert ipt2.leading_empty_lines(
157 156 sample.splitlines(keepends=True)
158 157 ) == expected.splitlines(keepends=True)
159 158
160 159 CRLF_MAGIC = ([
161 160 "%%ls\r\n"
162 161 ], [
163 162 "get_ipython().run_cell_magic('ls', '', '')\n"
164 163 ])
165 164
166 165 def test_crlf_magic():
167 166 for sample, expected in [CRLF_MAGIC]:
168 167 assert ipt2.cell_magic(sample) == expected
@@ -1,250 +1,248 b''
1 1 """Tests for the key interactiveshell module, where the main ipython class is defined.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Module imports
5 5 #-----------------------------------------------------------------------------
6 6
7 # third party
8 import pytest
9 7
10 8 # our own packages
11 9
12 10 #-----------------------------------------------------------------------------
13 11 # Test functions
14 12 #-----------------------------------------------------------------------------
15 13
16 14 def test_reset():
17 15 """reset must clear most namespaces."""
18 16
19 17 # Check that reset runs without error
20 18 ip.reset()
21 19
22 20 # Once we've reset it (to clear of any junk that might have been there from
23 21 # other tests, we can count how many variables are in the user's namespace
24 22 nvars_user_ns = len(ip.user_ns)
25 23 nvars_hidden = len(ip.user_ns_hidden)
26 24
27 25 # Now add a few variables to user_ns, and check that reset clears them
28 26 ip.user_ns['x'] = 1
29 27 ip.user_ns['y'] = 1
30 28 ip.reset()
31 29
32 30 # Finally, check that all namespaces have only as many variables as we
33 31 # expect to find in them:
34 32 assert len(ip.user_ns) == nvars_user_ns
35 33 assert len(ip.user_ns_hidden) == nvars_hidden
36 34
37 35
38 36 # Tests for reporting of exceptions in various modes, handling of SystemExit,
39 37 # and %tb functionality. This is really a mix of testing ultraTB and interactiveshell.
40 38
41 39 def doctest_tb_plain():
42 40 """
43 41 In [18]: xmode plain
44 42 Exception reporting mode: Plain
45 43
46 44 In [19]: run simpleerr.py
47 45 Traceback (most recent call last):
48 46 File ...:...
49 47 bar(mode)
50 48 File ...:... in bar
51 49 div0()
52 50 File ...:... in div0
53 51 x/y
54 52 ZeroDivisionError: ...
55 53 """
56 54
57 55
58 56 def doctest_tb_context():
59 57 """
60 58 In [3]: xmode context
61 59 Exception reporting mode: Context
62 60
63 61 In [4]: run simpleerr.py
64 62 ---------------------------------------------------------------------------
65 63 ZeroDivisionError Traceback (most recent call last)
66 64 <BLANKLINE>
67 65 ...
68 66 30 except IndexError:
69 67 31 mode = 'div'
70 68 ---> 33 bar(mode)
71 69 <BLANKLINE>
72 70 ... in bar(mode)
73 71 15 "bar"
74 72 16 if mode=='div':
75 73 ---> 17 div0()
76 74 18 elif mode=='exit':
77 75 19 try:
78 76 <BLANKLINE>
79 77 ... in div0()
80 78 6 x = 1
81 79 7 y = 0
82 80 ----> 8 x/y
83 81 <BLANKLINE>
84 82 ZeroDivisionError: ..."""
85 83
86 84
87 85 def doctest_tb_verbose():
88 86 """
89 87 In [5]: xmode verbose
90 88 Exception reporting mode: Verbose
91 89
92 90 In [6]: run simpleerr.py
93 91 ---------------------------------------------------------------------------
94 92 ZeroDivisionError Traceback (most recent call last)
95 93 <BLANKLINE>
96 94 ...
97 95 30 except IndexError:
98 96 31 mode = 'div'
99 97 ---> 33 bar(mode)
100 98 mode = 'div'
101 99 <BLANKLINE>
102 100 ... in bar(mode='div')
103 101 15 "bar"
104 102 16 if mode=='div':
105 103 ---> 17 div0()
106 104 18 elif mode=='exit':
107 105 19 try:
108 106 <BLANKLINE>
109 107 ... in div0()
110 108 6 x = 1
111 109 7 y = 0
112 110 ----> 8 x/y
113 111 x = 1
114 112 y = 0
115 113 <BLANKLINE>
116 114 ZeroDivisionError: ...
117 115 """
118 116
119 117
120 118 def doctest_tb_sysexit():
121 119 """
122 120 In [17]: %xmode plain
123 121 Exception reporting mode: Plain
124 122
125 123 In [18]: %run simpleerr.py exit
126 124 An exception has occurred, use %tb to see the full traceback.
127 125 SystemExit: (1, 'Mode = exit')
128 126
129 127 In [19]: %run simpleerr.py exit 2
130 128 An exception has occurred, use %tb to see the full traceback.
131 129 SystemExit: (2, 'Mode = exit')
132 130
133 131 In [20]: %tb
134 132 Traceback (most recent call last):
135 133 File ...:... in execfile
136 134 exec(compiler(f.read(), fname, "exec"), glob, loc)
137 135 File ...:...
138 136 bar(mode)
139 137 File ...:... in bar
140 138 sysexit(stat, mode)
141 139 File ...:... in sysexit
142 140 raise SystemExit(stat, f"Mode = {mode}")
143 141 SystemExit: (2, 'Mode = exit')
144 142
145 143 In [21]: %xmode context
146 144 Exception reporting mode: Context
147 145
148 146 In [22]: %tb
149 147 ---------------------------------------------------------------------------
150 148 SystemExit Traceback (most recent call last)
151 149 File ..., in execfile(fname, glob, loc, compiler)
152 150 ... with open(fname, "rb") as f:
153 151 ... compiler = compiler or compile
154 152 ---> ... exec(compiler(f.read(), fname, "exec"), glob, loc)
155 153 ...
156 154 30 except IndexError:
157 155 31 mode = 'div'
158 156 ---> 33 bar(mode)
159 157 <BLANKLINE>
160 158 ...bar(mode)
161 159 21 except:
162 160 22 stat = 1
163 161 ---> 23 sysexit(stat, mode)
164 162 24 else:
165 163 25 raise ValueError('Unknown mode')
166 164 <BLANKLINE>
167 165 ...sysexit(stat, mode)
168 166 10 def sysexit(stat, mode):
169 167 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
170 168 <BLANKLINE>
171 169 SystemExit: (2, 'Mode = exit')
172 170 """
173 171
174 172
175 173 def doctest_tb_sysexit_verbose():
176 174 """
177 175 In [18]: %run simpleerr.py exit
178 176 An exception has occurred, use %tb to see the full traceback.
179 177 SystemExit: (1, 'Mode = exit')
180 178
181 179 In [19]: %run simpleerr.py exit 2
182 180 An exception has occurred, use %tb to see the full traceback.
183 181 SystemExit: (2, 'Mode = exit')
184 182
185 183 In [23]: %xmode verbose
186 184 Exception reporting mode: Verbose
187 185
188 186 In [24]: %tb
189 187 ---------------------------------------------------------------------------
190 188 SystemExit Traceback (most recent call last)
191 189 <BLANKLINE>
192 190 ...
193 191 30 except IndexError:
194 192 31 mode = 'div'
195 193 ---> 33 bar(mode)
196 194 mode = 'exit'
197 195 <BLANKLINE>
198 196 ... in bar(mode='exit')
199 197 ... except:
200 198 ... stat = 1
201 199 ---> ... sysexit(stat, mode)
202 200 mode = 'exit'
203 201 stat = 2
204 202 ... else:
205 203 ... raise ValueError('Unknown mode')
206 204 <BLANKLINE>
207 205 ... in sysexit(stat=2, mode='exit')
208 206 10 def sysexit(stat, mode):
209 207 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
210 208 stat = 2
211 209 <BLANKLINE>
212 210 SystemExit: (2, 'Mode = exit')
213 211 """
214 212
215 213
216 214 def test_run_cell():
217 215 import textwrap
218 216
219 217 ip.run_cell("a = 10\na+=1")
220 218 ip.run_cell("assert a == 11\nassert 1")
221 219
222 220 assert ip.user_ns["a"] == 11
223 221 complex = textwrap.dedent(
224 222 """
225 223 if 1:
226 224 print "hello"
227 225 if 1:
228 226 print "world"
229 227
230 228 if 2:
231 229 print "foo"
232 230
233 231 if 3:
234 232 print "bar"
235 233
236 234 if 4:
237 235 print "bar"
238 236
239 237 """
240 238 )
241 239 # Simply verifies that this kind of input is run
242 240 ip.run_cell(complex)
243 241
244 242
245 243 def test_db():
246 244 """Test the internal database used for variable persistence."""
247 245 ip.db["__unittest_"] = 12
248 246 assert ip.db["__unittest_"] == 12
249 247 del ip.db["__unittest_"]
250 248 assert "__unittest_" not in ip.db
@@ -1,1452 +1,1451 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tests for various magic functions."""
3 3
4 import asyncio
5 4 import gc
6 5 import io
7 6 import os
8 7 import re
9 8 import shlex
10 9 import sys
11 10 import warnings
12 11 from importlib import invalidate_caches
13 12 from io import StringIO
14 13 from pathlib import Path
15 14 from textwrap import dedent
16 15 from unittest import TestCase, mock
17 16
18 17 import pytest
19 18
20 19 from IPython import get_ipython
21 20 from IPython.core import magic
22 21 from IPython.core.error import UsageError
23 22 from IPython.core.magic import (
24 23 Magics,
25 24 cell_magic,
26 25 line_magic,
27 26 magics_class,
28 27 register_cell_magic,
29 28 register_line_magic,
30 29 )
31 30 from IPython.core.magics import code, execution, logging, osm, script
32 31 from IPython.testing import decorators as dec
33 32 from IPython.testing import tools as tt
34 33 from IPython.utils.io import capture_output
35 34 from IPython.utils.process import find_cmd
36 35 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
37 36 from IPython.utils.syspathcontext import prepended_to_syspath
38 37
39 38 from .test_debugger import PdbTestInput
40 39
41 40 from tempfile import NamedTemporaryFile
42 41
43 42 @magic.magics_class
44 43 class DummyMagics(magic.Magics): pass
45 44
46 45 def test_extract_code_ranges():
47 46 instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :"
48 47 expected = [
49 48 (0, 1),
50 49 (2, 3),
51 50 (4, 6),
52 51 (6, 9),
53 52 (9, 14),
54 53 (16, None),
55 54 (None, 9),
56 55 (9, None),
57 56 (None, 13),
58 57 (None, None),
59 58 ]
60 59 actual = list(code.extract_code_ranges(instr))
61 60 assert actual == expected
62 61
63 62 def test_extract_symbols():
64 63 source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n"""
65 64 symbols_args = ["a", "b", "A", "A,b", "A,a", "z"]
66 65 expected = [([], ['a']),
67 66 (["def b():\n return 42\n"], []),
68 67 (["class A: pass\n"], []),
69 68 (["class A: pass\n", "def b():\n return 42\n"], []),
70 69 (["class A: pass\n"], ['a']),
71 70 ([], ['z'])]
72 71 for symbols, exp in zip(symbols_args, expected):
73 72 assert code.extract_symbols(source, symbols) == exp
74 73
75 74
76 75 def test_extract_symbols_raises_exception_with_non_python_code():
77 76 source = ("=begin A Ruby program :)=end\n"
78 77 "def hello\n"
79 78 "puts 'Hello world'\n"
80 79 "end")
81 80 with pytest.raises(SyntaxError):
82 81 code.extract_symbols(source, "hello")
83 82
84 83
85 84 def test_magic_not_found():
86 85 # magic not found raises UsageError
87 86 with pytest.raises(UsageError):
88 87 _ip.magic('doesntexist')
89 88
90 89 # ensure result isn't success when a magic isn't found
91 90 result = _ip.run_cell('%doesntexist')
92 91 assert isinstance(result.error_in_exec, UsageError)
93 92
94 93
95 94 def test_cell_magic_not_found():
96 95 # magic not found raises UsageError
97 96 with pytest.raises(UsageError):
98 97 _ip.run_cell_magic('doesntexist', 'line', 'cell')
99 98
100 99 # ensure result isn't success when a magic isn't found
101 100 result = _ip.run_cell('%%doesntexist')
102 101 assert isinstance(result.error_in_exec, UsageError)
103 102
104 103
105 104 def test_magic_error_status():
106 105 def fail(shell):
107 106 1/0
108 107 _ip.register_magic_function(fail)
109 108 result = _ip.run_cell('%fail')
110 109 assert isinstance(result.error_in_exec, ZeroDivisionError)
111 110
112 111
113 112 def test_config():
114 113 """ test that config magic does not raise
115 114 can happen if Configurable init is moved too early into
116 115 Magics.__init__ as then a Config object will be registered as a
117 116 magic.
118 117 """
119 118 ## should not raise.
120 119 _ip.magic('config')
121 120
122 121 def test_config_available_configs():
123 122 """ test that config magic prints available configs in unique and
124 123 sorted order. """
125 124 with capture_output() as captured:
126 125 _ip.magic('config')
127 126
128 127 stdout = captured.stdout
129 128 config_classes = stdout.strip().split('\n')[1:]
130 129 assert config_classes == sorted(set(config_classes))
131 130
132 131 def test_config_print_class():
133 132 """ test that config with a classname prints the class's options. """
134 133 with capture_output() as captured:
135 134 _ip.magic('config TerminalInteractiveShell')
136 135
137 136 stdout = captured.stdout
138 137 assert re.match(
139 138 "TerminalInteractiveShell.* options", stdout.splitlines()[0]
140 139 ), f"{stdout}\n\n1st line of stdout not like 'TerminalInteractiveShell.* options'"
141 140
142 141
143 142 def test_rehashx():
144 143 # clear up everything
145 144 _ip.alias_manager.clear_aliases()
146 145 del _ip.db['syscmdlist']
147 146
148 147 _ip.magic('rehashx')
149 148 # Practically ALL ipython development systems will have more than 10 aliases
150 149
151 150 assert len(_ip.alias_manager.aliases) > 10
152 151 for name, cmd in _ip.alias_manager.aliases:
153 152 # we must strip dots from alias names
154 153 assert "." not in name
155 154
156 155 # rehashx must fill up syscmdlist
157 156 scoms = _ip.db['syscmdlist']
158 157 assert len(scoms) > 10
159 158
160 159
161 160 def test_magic_parse_options():
162 161 """Test that we don't mangle paths when parsing magic options."""
163 162 ip = get_ipython()
164 163 path = 'c:\\x'
165 164 m = DummyMagics(ip)
166 165 opts = m.parse_options('-f %s' % path,'f:')[0]
167 166 # argv splitting is os-dependent
168 167 if os.name == 'posix':
169 168 expected = 'c:x'
170 169 else:
171 170 expected = path
172 171 assert opts["f"] == expected
173 172
174 173
175 174 def test_magic_parse_long_options():
176 175 """Magic.parse_options can handle --foo=bar long options"""
177 176 ip = get_ipython()
178 177 m = DummyMagics(ip)
179 178 opts, _ = m.parse_options("--foo --bar=bubble", "a", "foo", "bar=")
180 179 assert "foo" in opts
181 180 assert "bar" in opts
182 181 assert opts["bar"] == "bubble"
183 182
184 183
185 184 def doctest_hist_f():
186 185 """Test %hist -f with temporary filename.
187 186
188 187 In [9]: import tempfile
189 188
190 189 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
191 190
192 191 In [11]: %hist -nl -f $tfile 3
193 192
194 193 In [13]: import os; os.unlink(tfile)
195 194 """
196 195
197 196
198 197 def doctest_hist_op():
199 198 """Test %hist -op
200 199
201 200 In [1]: class b(float):
202 201 ...: pass
203 202 ...:
204 203
205 204 In [2]: class s(object):
206 205 ...: def __str__(self):
207 206 ...: return 's'
208 207 ...:
209 208
210 209 In [3]:
211 210
212 211 In [4]: class r(b):
213 212 ...: def __repr__(self):
214 213 ...: return 'r'
215 214 ...:
216 215
217 216 In [5]: class sr(s,r): pass
218 217 ...:
219 218
220 219 In [6]:
221 220
222 221 In [7]: bb=b()
223 222
224 223 In [8]: ss=s()
225 224
226 225 In [9]: rr=r()
227 226
228 227 In [10]: ssrr=sr()
229 228
230 229 In [11]: 4.5
231 230 Out[11]: 4.5
232 231
233 232 In [12]: str(ss)
234 233 Out[12]: 's'
235 234
236 235 In [13]:
237 236
238 237 In [14]: %hist -op
239 238 >>> class b:
240 239 ... pass
241 240 ...
242 241 >>> class s(b):
243 242 ... def __str__(self):
244 243 ... return 's'
245 244 ...
246 245 >>>
247 246 >>> class r(b):
248 247 ... def __repr__(self):
249 248 ... return 'r'
250 249 ...
251 250 >>> class sr(s,r): pass
252 251 >>>
253 252 >>> bb=b()
254 253 >>> ss=s()
255 254 >>> rr=r()
256 255 >>> ssrr=sr()
257 256 >>> 4.5
258 257 4.5
259 258 >>> str(ss)
260 259 's'
261 260 >>>
262 261 """
263 262
264 263 def test_hist_pof():
265 264 ip = get_ipython()
266 265 ip.run_cell("1+2", store_history=True)
267 266 #raise Exception(ip.history_manager.session_number)
268 267 #raise Exception(list(ip.history_manager._get_range_session()))
269 268 with TemporaryDirectory() as td:
270 269 tf = os.path.join(td, 'hist.py')
271 270 ip.run_line_magic('history', '-pof %s' % tf)
272 271 assert os.path.isfile(tf)
273 272
274 273
275 274 def test_macro():
276 275 ip = get_ipython()
277 276 ip.history_manager.reset() # Clear any existing history.
278 277 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
279 278 for i, cmd in enumerate(cmds, start=1):
280 279 ip.history_manager.store_inputs(i, cmd)
281 280 ip.magic("macro test 1-3")
282 281 assert ip.user_ns["test"].value == "\n".join(cmds) + "\n"
283 282
284 283 # List macros
285 284 assert "test" in ip.magic("macro")
286 285
287 286
288 287 def test_macro_run():
289 288 """Test that we can run a multi-line macro successfully."""
290 289 ip = get_ipython()
291 290 ip.history_manager.reset()
292 291 cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"]
293 292 for cmd in cmds:
294 293 ip.run_cell(cmd, store_history=True)
295 294 assert ip.user_ns["test"].value == "a+=1\nprint(a)\n"
296 295 with tt.AssertPrints("12"):
297 296 ip.run_cell("test")
298 297 with tt.AssertPrints("13"):
299 298 ip.run_cell("test")
300 299
301 300
302 301 def test_magic_magic():
303 302 """Test %magic"""
304 303 ip = get_ipython()
305 304 with capture_output() as captured:
306 305 ip.magic("magic")
307 306
308 307 stdout = captured.stdout
309 308 assert "%magic" in stdout
310 309 assert "IPython" in stdout
311 310 assert "Available" in stdout
312 311
313 312
314 313 @dec.skipif_not_numpy
315 314 def test_numpy_reset_array_undec():
316 315 "Test '%reset array' functionality"
317 316 _ip.ex("import numpy as np")
318 317 _ip.ex("a = np.empty(2)")
319 318 assert "a" in _ip.user_ns
320 319 _ip.magic("reset -f array")
321 320 assert "a" not in _ip.user_ns
322 321
323 322
324 323 def test_reset_out():
325 324 "Test '%reset out' magic"
326 325 _ip.run_cell("parrot = 'dead'", store_history=True)
327 326 # test '%reset -f out', make an Out prompt
328 327 _ip.run_cell("parrot", store_history=True)
329 328 assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")]
330 329 _ip.magic("reset -f out")
331 330 assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")]
332 331 assert len(_ip.user_ns["Out"]) == 0
333 332
334 333
335 334 def test_reset_in():
336 335 "Test '%reset in' magic"
337 336 # test '%reset -f in'
338 337 _ip.run_cell("parrot", store_history=True)
339 338 assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
340 339 _ip.magic("%reset -f in")
341 340 assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
342 341 assert len(set(_ip.user_ns["In"])) == 1
343 342
344 343
345 344 def test_reset_dhist():
346 345 "Test '%reset dhist' magic"
347 346 _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing
348 347 _ip.magic("cd " + os.path.dirname(pytest.__file__))
349 348 _ip.magic("cd -")
350 349 assert len(_ip.user_ns["_dh"]) > 0
351 350 _ip.magic("reset -f dhist")
352 351 assert len(_ip.user_ns["_dh"]) == 0
353 352 _ip.run_cell("_dh = [d for d in tmp]") # restore
354 353
355 354
356 355 def test_reset_in_length():
357 356 "Test that '%reset in' preserves In[] length"
358 357 _ip.run_cell("print 'foo'")
359 358 _ip.run_cell("reset -f in")
360 359 assert len(_ip.user_ns["In"]) == _ip.displayhook.prompt_count + 1
361 360
362 361
363 362 class TestResetErrors(TestCase):
364 363
365 364 def test_reset_redefine(self):
366 365
367 366 @magics_class
368 367 class KernelMagics(Magics):
369 368 @line_magic
370 369 def less(self, shell): pass
371 370
372 371 _ip.register_magics(KernelMagics)
373 372
374 373 with self.assertLogs() as cm:
375 374 # hack, we want to just capture logs, but assertLogs fails if not
376 375 # logs get produce.
377 376 # so log one things we ignore.
378 377 import logging as log_mod
379 378 log = log_mod.getLogger()
380 379 log.info('Nothing')
381 380 # end hack.
382 381 _ip.run_cell("reset -f")
383 382
384 383 assert len(cm.output) == 1
385 384 for out in cm.output:
386 385 assert "Invalid alias" not in out
387 386
388 387 def test_tb_syntaxerror():
389 388 """test %tb after a SyntaxError"""
390 389 ip = get_ipython()
391 390 ip.run_cell("for")
392 391
393 392 # trap and validate stdout
394 393 save_stdout = sys.stdout
395 394 try:
396 395 sys.stdout = StringIO()
397 396 ip.run_cell("%tb")
398 397 out = sys.stdout.getvalue()
399 398 finally:
400 399 sys.stdout = save_stdout
401 400 # trim output, and only check the last line
402 401 last_line = out.rstrip().splitlines()[-1].strip()
403 402 assert last_line == "SyntaxError: invalid syntax"
404 403
405 404
406 405 def test_time():
407 406 ip = get_ipython()
408 407
409 408 with tt.AssertPrints("Wall time: "):
410 409 ip.run_cell("%time None")
411 410
412 411 ip.run_cell("def f(kmjy):\n"
413 412 " %time print (2*kmjy)")
414 413
415 414 with tt.AssertPrints("Wall time: "):
416 415 with tt.AssertPrints("hihi", suppress=False):
417 416 ip.run_cell("f('hi')")
418 417
419 418 def test_time_last_not_expression():
420 419 ip.run_cell("%%time\n"
421 420 "var_1 = 1\n"
422 421 "var_2 = 2\n")
423 422 assert ip.user_ns['var_1'] == 1
424 423 del ip.user_ns['var_1']
425 424 assert ip.user_ns['var_2'] == 2
426 425 del ip.user_ns['var_2']
427 426
428 427
429 428 @dec.skip_win32
430 429 def test_time2():
431 430 ip = get_ipython()
432 431
433 432 with tt.AssertPrints("CPU times: user "):
434 433 ip.run_cell("%time None")
435 434
436 435 def test_time3():
437 436 """Erroneous magic function calls, issue gh-3334"""
438 437 ip = get_ipython()
439 438 ip.user_ns.pop('run', None)
440 439
441 440 with tt.AssertNotPrints("not found", channel='stderr'):
442 441 ip.run_cell("%%time\n"
443 442 "run = 0\n"
444 443 "run += 1")
445 444
446 445 def test_multiline_time():
447 446 """Make sure last statement from time return a value."""
448 447 ip = get_ipython()
449 448 ip.user_ns.pop('run', None)
450 449
451 450 ip.run_cell(
452 451 dedent(
453 452 """\
454 453 %%time
455 454 a = "ho"
456 455 b = "hey"
457 456 a+b
458 457 """
459 458 )
460 459 )
461 460 assert ip.user_ns_hidden["_"] == "hohey"
462 461
463 462
464 463 def test_time_local_ns():
465 464 """
466 465 Test that local_ns is actually global_ns when running a cell magic
467 466 """
468 467 ip = get_ipython()
469 468 ip.run_cell("%%time\n" "myvar = 1")
470 469 assert ip.user_ns["myvar"] == 1
471 470 del ip.user_ns["myvar"]
472 471
473 472
474 473 def test_doctest_mode():
475 474 "Toggle doctest_mode twice, it should be a no-op and run without error"
476 475 _ip.magic('doctest_mode')
477 476 _ip.magic('doctest_mode')
478 477
479 478
480 479 def test_parse_options():
481 480 """Tests for basic options parsing in magics."""
482 481 # These are only the most minimal of tests, more should be added later. At
483 482 # the very least we check that basic text/unicode calls work OK.
484 483 m = DummyMagics(_ip)
485 484 assert m.parse_options("foo", "")[1] == "foo"
486 485 assert m.parse_options("foo", "")[1] == "foo"
487 486
488 487
489 488 def test_parse_options_preserve_non_option_string():
490 489 """Test to assert preservation of non-option part of magic-block, while parsing magic options."""
491 490 m = DummyMagics(_ip)
492 491 opts, stmt = m.parse_options(
493 492 " -n1 -r 13 _ = 314 + foo", "n:r:", preserve_non_opts=True
494 493 )
495 494 assert opts == {"n": "1", "r": "13"}
496 495 assert stmt == "_ = 314 + foo"
497 496
498 497
499 498 def test_run_magic_preserve_code_block():
500 499 """Test to assert preservation of non-option part of magic-block, while running magic."""
501 500 _ip.user_ns["spaces"] = []
502 501 _ip.magic("timeit -n1 -r1 spaces.append([s.count(' ') for s in ['document']])")
503 502 assert _ip.user_ns["spaces"] == [[0]]
504 503
505 504
506 505 def test_dirops():
507 506 """Test various directory handling operations."""
508 507 # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/')
509 508 curpath = os.getcwd
510 509 startdir = os.getcwd()
511 510 ipdir = os.path.realpath(_ip.ipython_dir)
512 511 try:
513 512 _ip.magic('cd "%s"' % ipdir)
514 513 assert curpath() == ipdir
515 514 _ip.magic('cd -')
516 515 assert curpath() == startdir
517 516 _ip.magic('pushd "%s"' % ipdir)
518 517 assert curpath() == ipdir
519 518 _ip.magic('popd')
520 519 assert curpath() == startdir
521 520 finally:
522 521 os.chdir(startdir)
523 522
524 523
525 524 def test_cd_force_quiet():
526 525 """Test OSMagics.cd_force_quiet option"""
527 526 _ip.config.OSMagics.cd_force_quiet = True
528 527 osmagics = osm.OSMagics(shell=_ip)
529 528
530 529 startdir = os.getcwd()
531 530 ipdir = os.path.realpath(_ip.ipython_dir)
532 531
533 532 try:
534 533 with tt.AssertNotPrints(ipdir):
535 534 osmagics.cd('"%s"' % ipdir)
536 535 with tt.AssertNotPrints(startdir):
537 536 osmagics.cd('-')
538 537 finally:
539 538 os.chdir(startdir)
540 539
541 540
542 541 def test_xmode():
543 542 # Calling xmode three times should be a no-op
544 543 xmode = _ip.InteractiveTB.mode
545 544 for i in range(4):
546 545 _ip.magic("xmode")
547 546 assert _ip.InteractiveTB.mode == xmode
548 547
549 548 def test_reset_hard():
550 549 monitor = []
551 550 class A(object):
552 551 def __del__(self):
553 552 monitor.append(1)
554 553 def __repr__(self):
555 554 return "<A instance>"
556 555
557 556 _ip.user_ns["a"] = A()
558 557 _ip.run_cell("a")
559 558
560 559 assert monitor == []
561 560 _ip.magic("reset -f")
562 561 assert monitor == [1]
563 562
564 563 class TestXdel(tt.TempFileMixin):
565 564 def test_xdel(self):
566 565 """Test that references from %run are cleared by xdel."""
567 566 src = ("class A(object):\n"
568 567 " monitor = []\n"
569 568 " def __del__(self):\n"
570 569 " self.monitor.append(1)\n"
571 570 "a = A()\n")
572 571 self.mktmp(src)
573 572 # %run creates some hidden references...
574 573 _ip.magic("run %s" % self.fname)
575 574 # ... as does the displayhook.
576 575 _ip.run_cell("a")
577 576
578 577 monitor = _ip.user_ns["A"].monitor
579 578 assert monitor == []
580 579
581 580 _ip.magic("xdel a")
582 581
583 582 # Check that a's __del__ method has been called.
584 583 gc.collect(0)
585 584 assert monitor == [1]
586 585
587 586 def doctest_who():
588 587 """doctest for %who
589 588
590 589 In [1]: %reset -sf
591 590
592 591 In [2]: alpha = 123
593 592
594 593 In [3]: beta = 'beta'
595 594
596 595 In [4]: %who int
597 596 alpha
598 597
599 598 In [5]: %who str
600 599 beta
601 600
602 601 In [6]: %whos
603 602 Variable Type Data/Info
604 603 ----------------------------
605 604 alpha int 123
606 605 beta str beta
607 606
608 607 In [7]: %who_ls
609 608 Out[7]: ['alpha', 'beta']
610 609 """
611 610
612 611 def test_whos():
613 612 """Check that whos is protected against objects where repr() fails."""
614 613 class A(object):
615 614 def __repr__(self):
616 615 raise Exception()
617 616 _ip.user_ns['a'] = A()
618 617 _ip.magic("whos")
619 618
620 619 def doctest_precision():
621 620 """doctest for %precision
622 621
623 622 In [1]: f = get_ipython().display_formatter.formatters['text/plain']
624 623
625 624 In [2]: %precision 5
626 625 Out[2]: '%.5f'
627 626
628 627 In [3]: f.float_format
629 628 Out[3]: '%.5f'
630 629
631 630 In [4]: %precision %e
632 631 Out[4]: '%e'
633 632
634 633 In [5]: f(3.1415927)
635 634 Out[5]: '3.141593e+00'
636 635 """
637 636
638 637 def test_debug_magic():
639 638 """Test debugging a small code with %debug
640 639
641 640 In [1]: with PdbTestInput(['c']):
642 641 ...: %debug print("a b") #doctest: +ELLIPSIS
643 642 ...:
644 643 ...
645 644 ipdb> c
646 645 a b
647 646 In [2]:
648 647 """
649 648
650 649 def test_psearch():
651 650 with tt.AssertPrints("dict.fromkeys"):
652 651 _ip.run_cell("dict.fr*?")
653 652 with tt.AssertPrints("Ο€.is_integer"):
654 653 _ip.run_cell("Ο€ = 3.14;\nΟ€.is_integ*?")
655 654
656 655 def test_timeit_shlex():
657 656 """test shlex issues with timeit (#1109)"""
658 657 _ip.ex("def f(*a,**kw): pass")
659 658 _ip.magic('timeit -n1 "this is a bug".count(" ")')
660 659 _ip.magic('timeit -r1 -n1 f(" ", 1)')
661 660 _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")')
662 661 _ip.magic('timeit -r1 -n1 ("a " + "b")')
663 662 _ip.magic('timeit -r1 -n1 f("a " + "b")')
664 663 _ip.magic('timeit -r1 -n1 f("a " + "b ")')
665 664
666 665
667 666 def test_timeit_special_syntax():
668 667 "Test %%timeit with IPython special syntax"
669 668 @register_line_magic
670 669 def lmagic(line):
671 670 ip = get_ipython()
672 671 ip.user_ns['lmagic_out'] = line
673 672
674 673 # line mode test
675 674 _ip.run_line_magic("timeit", "-n1 -r1 %lmagic my line")
676 675 assert _ip.user_ns["lmagic_out"] == "my line"
677 676 # cell mode test
678 677 _ip.run_cell_magic("timeit", "-n1 -r1", "%lmagic my line2")
679 678 assert _ip.user_ns["lmagic_out"] == "my line2"
680 679
681 680
682 681 def test_timeit_return():
683 682 """
684 683 test whether timeit -o return object
685 684 """
686 685
687 686 res = _ip.run_line_magic('timeit','-n10 -r10 -o 1')
688 687 assert(res is not None)
689 688
690 689 def test_timeit_quiet():
691 690 """
692 691 test quiet option of timeit magic
693 692 """
694 693 with tt.AssertNotPrints("loops"):
695 694 _ip.run_cell("%timeit -n1 -r1 -q 1")
696 695
697 696 def test_timeit_return_quiet():
698 697 with tt.AssertNotPrints("loops"):
699 698 res = _ip.run_line_magic('timeit', '-n1 -r1 -q -o 1')
700 699 assert (res is not None)
701 700
702 701 def test_timeit_invalid_return():
703 702 with pytest.raises(SyntaxError):
704 703 _ip.run_line_magic('timeit', 'return')
705 704
706 705 @dec.skipif(execution.profile is None)
707 706 def test_prun_special_syntax():
708 707 "Test %%prun with IPython special syntax"
709 708 @register_line_magic
710 709 def lmagic(line):
711 710 ip = get_ipython()
712 711 ip.user_ns['lmagic_out'] = line
713 712
714 713 # line mode test
715 714 _ip.run_line_magic("prun", "-q %lmagic my line")
716 715 assert _ip.user_ns["lmagic_out"] == "my line"
717 716 # cell mode test
718 717 _ip.run_cell_magic("prun", "-q", "%lmagic my line2")
719 718 assert _ip.user_ns["lmagic_out"] == "my line2"
720 719
721 720
722 721 @dec.skipif(execution.profile is None)
723 722 def test_prun_quotes():
724 723 "Test that prun does not clobber string escapes (GH #1302)"
725 724 _ip.magic(r"prun -q x = '\t'")
726 725 assert _ip.user_ns["x"] == "\t"
727 726
728 727
729 728 def test_extension():
730 729 # Debugging information for failures of this test
731 730 print('sys.path:')
732 731 for p in sys.path:
733 732 print(' ', p)
734 733 print('CWD', os.getcwd())
735 734
736 735 pytest.raises(ImportError, _ip.magic, "load_ext daft_extension")
737 736 daft_path = os.path.join(os.path.dirname(__file__), "daft_extension")
738 737 sys.path.insert(0, daft_path)
739 738 try:
740 739 _ip.user_ns.pop('arq', None)
741 740 invalidate_caches() # Clear import caches
742 741 _ip.magic("load_ext daft_extension")
743 742 assert _ip.user_ns["arq"] == 185
744 743 _ip.magic("unload_ext daft_extension")
745 744 assert 'arq' not in _ip.user_ns
746 745 finally:
747 746 sys.path.remove(daft_path)
748 747
749 748
750 749 def test_notebook_export_json():
751 750 pytest.importorskip("nbformat")
752 751 _ip = get_ipython()
753 752 _ip.history_manager.reset() # Clear any existing history.
754 753 cmds = ["a=1", "def b():\n return a**2", "print('noΓ«l, Γ©tΓ©', b())"]
755 754 for i, cmd in enumerate(cmds, start=1):
756 755 _ip.history_manager.store_inputs(i, cmd)
757 756 with TemporaryDirectory() as td:
758 757 outfile = os.path.join(td, "nb.ipynb")
759 758 _ip.magic("notebook %s" % outfile)
760 759
761 760
762 761 class TestEnv(TestCase):
763 762
764 763 def test_env(self):
765 764 env = _ip.magic("env")
766 765 self.assertTrue(isinstance(env, dict))
767 766
768 767 def test_env_secret(self):
769 768 env = _ip.magic("env")
770 769 hidden = "<hidden>"
771 770 with mock.patch.dict(
772 771 os.environ,
773 772 {
774 773 "API_KEY": "abc123",
775 774 "SECRET_THING": "ssshhh",
776 775 "JUPYTER_TOKEN": "",
777 776 "VAR": "abc"
778 777 }
779 778 ):
780 779 env = _ip.magic("env")
781 780 assert env["API_KEY"] == hidden
782 781 assert env["SECRET_THING"] == hidden
783 782 assert env["JUPYTER_TOKEN"] == hidden
784 783 assert env["VAR"] == "abc"
785 784
786 785 def test_env_get_set_simple(self):
787 786 env = _ip.magic("env var val1")
788 787 self.assertEqual(env, None)
789 788 self.assertEqual(os.environ['var'], 'val1')
790 789 self.assertEqual(_ip.magic("env var"), 'val1')
791 790 env = _ip.magic("env var=val2")
792 791 self.assertEqual(env, None)
793 792 self.assertEqual(os.environ['var'], 'val2')
794 793
795 794 def test_env_get_set_complex(self):
796 795 env = _ip.magic("env var 'val1 '' 'val2")
797 796 self.assertEqual(env, None)
798 797 self.assertEqual(os.environ['var'], "'val1 '' 'val2")
799 798 self.assertEqual(_ip.magic("env var"), "'val1 '' 'val2")
800 799 env = _ip.magic('env var=val2 val3="val4')
801 800 self.assertEqual(env, None)
802 801 self.assertEqual(os.environ['var'], 'val2 val3="val4')
803 802
804 803 def test_env_set_bad_input(self):
805 804 self.assertRaises(UsageError, lambda: _ip.magic("set_env var"))
806 805
807 806 def test_env_set_whitespace(self):
808 807 self.assertRaises(UsageError, lambda: _ip.magic("env var A=B"))
809 808
810 809
811 810 class CellMagicTestCase(TestCase):
812 811
813 812 def check_ident(self, magic):
814 813 # Manually called, we get the result
815 814 out = _ip.run_cell_magic(magic, "a", "b")
816 815 assert out == ("a", "b")
817 816 # Via run_cell, it goes into the user's namespace via displayhook
818 817 _ip.run_cell("%%" + magic + " c\nd\n")
819 818 assert _ip.user_ns["_"] == ("c", "d\n")
820 819
821 820 def test_cell_magic_func_deco(self):
822 821 "Cell magic using simple decorator"
823 822 @register_cell_magic
824 823 def cellm(line, cell):
825 824 return line, cell
826 825
827 826 self.check_ident('cellm')
828 827
829 828 def test_cell_magic_reg(self):
830 829 "Cell magic manually registered"
831 830 def cellm(line, cell):
832 831 return line, cell
833 832
834 833 _ip.register_magic_function(cellm, 'cell', 'cellm2')
835 834 self.check_ident('cellm2')
836 835
837 836 def test_cell_magic_class(self):
838 837 "Cell magics declared via a class"
839 838 @magics_class
840 839 class MyMagics(Magics):
841 840
842 841 @cell_magic
843 842 def cellm3(self, line, cell):
844 843 return line, cell
845 844
846 845 _ip.register_magics(MyMagics)
847 846 self.check_ident('cellm3')
848 847
849 848 def test_cell_magic_class2(self):
850 849 "Cell magics declared via a class, #2"
851 850 @magics_class
852 851 class MyMagics2(Magics):
853 852
854 853 @cell_magic('cellm4')
855 854 def cellm33(self, line, cell):
856 855 return line, cell
857 856
858 857 _ip.register_magics(MyMagics2)
859 858 self.check_ident('cellm4')
860 859 # Check that nothing is registered as 'cellm33'
861 860 c33 = _ip.find_cell_magic('cellm33')
862 861 assert c33 == None
863 862
864 863 def test_file():
865 864 """Basic %%writefile"""
866 865 ip = get_ipython()
867 866 with TemporaryDirectory() as td:
868 867 fname = os.path.join(td, "file1")
869 868 ip.run_cell_magic(
870 869 "writefile",
871 870 fname,
872 871 "\n".join(
873 872 [
874 873 "line1",
875 874 "line2",
876 875 ]
877 876 ),
878 877 )
879 878 s = Path(fname).read_text(encoding="utf-8")
880 879 assert "line1\n" in s
881 880 assert "line2" in s
882 881
883 882
884 883 @dec.skip_win32
885 884 def test_file_single_quote():
886 885 """Basic %%writefile with embedded single quotes"""
887 886 ip = get_ipython()
888 887 with TemporaryDirectory() as td:
889 888 fname = os.path.join(td, "'file1'")
890 889 ip.run_cell_magic(
891 890 "writefile",
892 891 fname,
893 892 "\n".join(
894 893 [
895 894 "line1",
896 895 "line2",
897 896 ]
898 897 ),
899 898 )
900 899 s = Path(fname).read_text(encoding="utf-8")
901 900 assert "line1\n" in s
902 901 assert "line2" in s
903 902
904 903
905 904 @dec.skip_win32
906 905 def test_file_double_quote():
907 906 """Basic %%writefile with embedded double quotes"""
908 907 ip = get_ipython()
909 908 with TemporaryDirectory() as td:
910 909 fname = os.path.join(td, '"file1"')
911 910 ip.run_cell_magic(
912 911 "writefile",
913 912 fname,
914 913 "\n".join(
915 914 [
916 915 "line1",
917 916 "line2",
918 917 ]
919 918 ),
920 919 )
921 920 s = Path(fname).read_text(encoding="utf-8")
922 921 assert "line1\n" in s
923 922 assert "line2" in s
924 923
925 924
926 925 def test_file_var_expand():
927 926 """%%writefile $filename"""
928 927 ip = get_ipython()
929 928 with TemporaryDirectory() as td:
930 929 fname = os.path.join(td, "file1")
931 930 ip.user_ns["filename"] = fname
932 931 ip.run_cell_magic(
933 932 "writefile",
934 933 "$filename",
935 934 "\n".join(
936 935 [
937 936 "line1",
938 937 "line2",
939 938 ]
940 939 ),
941 940 )
942 941 s = Path(fname).read_text(encoding="utf-8")
943 942 assert "line1\n" in s
944 943 assert "line2" in s
945 944
946 945
947 946 def test_file_unicode():
948 947 """%%writefile with unicode cell"""
949 948 ip = get_ipython()
950 949 with TemporaryDirectory() as td:
951 950 fname = os.path.join(td, 'file1')
952 951 ip.run_cell_magic("writefile", fname, u'\n'.join([
953 952 u'linΓ©1',
954 953 u'linΓ©2',
955 954 ]))
956 955 with io.open(fname, encoding='utf-8') as f:
957 956 s = f.read()
958 957 assert "linΓ©1\n" in s
959 958 assert "linΓ©2" in s
960 959
961 960
962 961 def test_file_amend():
963 962 """%%writefile -a amends files"""
964 963 ip = get_ipython()
965 964 with TemporaryDirectory() as td:
966 965 fname = os.path.join(td, "file2")
967 966 ip.run_cell_magic(
968 967 "writefile",
969 968 fname,
970 969 "\n".join(
971 970 [
972 971 "line1",
973 972 "line2",
974 973 ]
975 974 ),
976 975 )
977 976 ip.run_cell_magic(
978 977 "writefile",
979 978 "-a %s" % fname,
980 979 "\n".join(
981 980 [
982 981 "line3",
983 982 "line4",
984 983 ]
985 984 ),
986 985 )
987 986 s = Path(fname).read_text(encoding="utf-8")
988 987 assert "line1\n" in s
989 988 assert "line3\n" in s
990 989
991 990
992 991 def test_file_spaces():
993 992 """%%file with spaces in filename"""
994 993 ip = get_ipython()
995 994 with TemporaryWorkingDirectory() as td:
996 995 fname = "file name"
997 996 ip.run_cell_magic(
998 997 "file",
999 998 '"%s"' % fname,
1000 999 "\n".join(
1001 1000 [
1002 1001 "line1",
1003 1002 "line2",
1004 1003 ]
1005 1004 ),
1006 1005 )
1007 1006 s = Path(fname).read_text(encoding="utf-8")
1008 1007 assert "line1\n" in s
1009 1008 assert "line2" in s
1010 1009
1011 1010
1012 1011 def test_script_config():
1013 1012 ip = get_ipython()
1014 1013 ip.config.ScriptMagics.script_magics = ['whoda']
1015 1014 sm = script.ScriptMagics(shell=ip)
1016 1015 assert "whoda" in sm.magics["cell"]
1017 1016
1018 1017
1019 1018 def test_script_out():
1020 1019 ip = get_ipython()
1021 1020 ip.run_cell_magic("script", f"--out output {sys.executable}", "print('hi')")
1022 1021 assert ip.user_ns["output"].strip() == "hi"
1023 1022
1024 1023
1025 1024 def test_script_err():
1026 1025 ip = get_ipython()
1027 1026 ip.run_cell_magic(
1028 1027 "script",
1029 1028 f"--err error {sys.executable}",
1030 1029 "import sys; print('hello', file=sys.stderr)",
1031 1030 )
1032 1031 assert ip.user_ns["error"].strip() == "hello"
1033 1032
1034 1033
1035 1034 def test_script_out_err():
1036 1035
1037 1036 ip = get_ipython()
1038 1037 ip.run_cell_magic(
1039 1038 "script",
1040 1039 f"--out output --err error {sys.executable}",
1041 1040 "\n".join(
1042 1041 [
1043 1042 "import sys",
1044 1043 "print('hi')",
1045 1044 "print('hello', file=sys.stderr)",
1046 1045 ]
1047 1046 ),
1048 1047 )
1049 1048 assert ip.user_ns["output"].strip() == "hi"
1050 1049 assert ip.user_ns["error"].strip() == "hello"
1051 1050
1052 1051
1053 1052 async def test_script_bg_out():
1054 1053 ip = get_ipython()
1055 1054 ip.run_cell_magic("script", f"--bg --out output {sys.executable}", "print('hi')")
1056 1055 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1057 1056 assert ip.user_ns["output"].at_eof()
1058 1057
1059 1058
1060 1059 async def test_script_bg_err():
1061 1060 ip = get_ipython()
1062 1061 ip.run_cell_magic(
1063 1062 "script",
1064 1063 f"--bg --err error {sys.executable}",
1065 1064 "import sys; print('hello', file=sys.stderr)",
1066 1065 )
1067 1066 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1068 1067 assert ip.user_ns["error"].at_eof()
1069 1068
1070 1069
1071 1070 async def test_script_bg_out_err():
1072 1071 ip = get_ipython()
1073 1072 ip.run_cell_magic(
1074 1073 "script",
1075 1074 f"--bg --out output --err error {sys.executable}",
1076 1075 "\n".join(
1077 1076 [
1078 1077 "import sys",
1079 1078 "print('hi')",
1080 1079 "print('hello', file=sys.stderr)",
1081 1080 ]
1082 1081 ),
1083 1082 )
1084 1083 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1085 1084 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1086 1085 assert ip.user_ns["output"].at_eof()
1087 1086 assert ip.user_ns["error"].at_eof()
1088 1087
1089 1088
1090 1089 async def test_script_bg_proc():
1091 1090 ip = get_ipython()
1092 1091 ip.run_cell_magic(
1093 1092 "script",
1094 1093 f"--bg --out output --proc p {sys.executable}",
1095 1094 "\n".join(
1096 1095 [
1097 1096 "import sys",
1098 1097 "print('hi')",
1099 1098 "print('hello', file=sys.stderr)",
1100 1099 ]
1101 1100 ),
1102 1101 )
1103 1102 p = ip.user_ns["p"]
1104 1103 await p.wait()
1105 1104 assert p.returncode == 0
1106 1105 assert (await p.stdout.read()).strip() == b"hi"
1107 1106 # not captured, so empty
1108 1107 assert (await p.stderr.read()) == b""
1109 1108 assert p.stdout.at_eof()
1110 1109 assert p.stderr.at_eof()
1111 1110
1112 1111
1113 1112 def test_script_defaults():
1114 1113 ip = get_ipython()
1115 1114 for cmd in ['sh', 'bash', 'perl', 'ruby']:
1116 1115 try:
1117 1116 find_cmd(cmd)
1118 1117 except Exception:
1119 1118 pass
1120 1119 else:
1121 1120 assert cmd in ip.magics_manager.magics["cell"]
1122 1121
1123 1122
1124 1123 @magics_class
1125 1124 class FooFoo(Magics):
1126 1125 """class with both %foo and %%foo magics"""
1127 1126 @line_magic('foo')
1128 1127 def line_foo(self, line):
1129 1128 "I am line foo"
1130 1129 pass
1131 1130
1132 1131 @cell_magic("foo")
1133 1132 def cell_foo(self, line, cell):
1134 1133 "I am cell foo, not line foo"
1135 1134 pass
1136 1135
1137 1136 def test_line_cell_info():
1138 1137 """%%foo and %foo magics are distinguishable to inspect"""
1139 1138 ip = get_ipython()
1140 1139 ip.magics_manager.register(FooFoo)
1141 1140 oinfo = ip.object_inspect("foo")
1142 1141 assert oinfo["found"] is True
1143 1142 assert oinfo["ismagic"] is True
1144 1143
1145 1144 oinfo = ip.object_inspect("%%foo")
1146 1145 assert oinfo["found"] is True
1147 1146 assert oinfo["ismagic"] is True
1148 1147 assert oinfo["docstring"] == FooFoo.cell_foo.__doc__
1149 1148
1150 1149 oinfo = ip.object_inspect("%foo")
1151 1150 assert oinfo["found"] is True
1152 1151 assert oinfo["ismagic"] is True
1153 1152 assert oinfo["docstring"] == FooFoo.line_foo.__doc__
1154 1153
1155 1154
1156 1155 def test_multiple_magics():
1157 1156 ip = get_ipython()
1158 1157 foo1 = FooFoo(ip)
1159 1158 foo2 = FooFoo(ip)
1160 1159 mm = ip.magics_manager
1161 1160 mm.register(foo1)
1162 1161 assert mm.magics["line"]["foo"].__self__ is foo1
1163 1162 mm.register(foo2)
1164 1163 assert mm.magics["line"]["foo"].__self__ is foo2
1165 1164
1166 1165
1167 1166 def test_alias_magic():
1168 1167 """Test %alias_magic."""
1169 1168 ip = get_ipython()
1170 1169 mm = ip.magics_manager
1171 1170
1172 1171 # Basic operation: both cell and line magics are created, if possible.
1173 1172 ip.run_line_magic("alias_magic", "timeit_alias timeit")
1174 1173 assert "timeit_alias" in mm.magics["line"]
1175 1174 assert "timeit_alias" in mm.magics["cell"]
1176 1175
1177 1176 # --cell is specified, line magic not created.
1178 1177 ip.run_line_magic("alias_magic", "--cell timeit_cell_alias timeit")
1179 1178 assert "timeit_cell_alias" not in mm.magics["line"]
1180 1179 assert "timeit_cell_alias" in mm.magics["cell"]
1181 1180
1182 1181 # Test that line alias is created successfully.
1183 1182 ip.run_line_magic("alias_magic", "--line env_alias env")
1184 1183 assert ip.run_line_magic("env", "") == ip.run_line_magic("env_alias", "")
1185 1184
1186 1185 # Test that line alias with parameters passed in is created successfully.
1187 1186 ip.run_line_magic(
1188 1187 "alias_magic", "--line history_alias history --params " + shlex.quote("3")
1189 1188 )
1190 1189 assert "history_alias" in mm.magics["line"]
1191 1190
1192 1191
1193 1192 def test_save():
1194 1193 """Test %save."""
1195 1194 ip = get_ipython()
1196 1195 ip.history_manager.reset() # Clear any existing history.
1197 1196 cmds = ["a=1", "def b():\n return a**2", "print(a, b())"]
1198 1197 for i, cmd in enumerate(cmds, start=1):
1199 1198 ip.history_manager.store_inputs(i, cmd)
1200 1199 with TemporaryDirectory() as tmpdir:
1201 1200 file = os.path.join(tmpdir, "testsave.py")
1202 1201 ip.run_line_magic("save", "%s 1-10" % file)
1203 1202 content = Path(file).read_text(encoding="utf-8")
1204 1203 assert content.count(cmds[0]) == 1
1205 1204 assert "coding: utf-8" in content
1206 1205 ip.run_line_magic("save", "-a %s 1-10" % file)
1207 1206 content = Path(file).read_text(encoding="utf-8")
1208 1207 assert content.count(cmds[0]) == 2
1209 1208 assert "coding: utf-8" in content
1210 1209
1211 1210
1212 1211 def test_save_with_no_args():
1213 1212 ip = get_ipython()
1214 1213 ip.history_manager.reset() # Clear any existing history.
1215 1214 cmds = ["a=1", "def b():\n return a**2", "print(a, b())", "%save"]
1216 1215 for i, cmd in enumerate(cmds, start=1):
1217 1216 ip.history_manager.store_inputs(i, cmd)
1218 1217
1219 1218 with TemporaryDirectory() as tmpdir:
1220 1219 path = os.path.join(tmpdir, "testsave.py")
1221 1220 ip.run_line_magic("save", path)
1222 1221 content = Path(path).read_text(encoding="utf-8")
1223 1222 expected_content = dedent(
1224 1223 """\
1225 1224 # coding: utf-8
1226 1225 a=1
1227 1226 def b():
1228 1227 return a**2
1229 1228 print(a, b())
1230 1229 """
1231 1230 )
1232 1231 assert content == expected_content
1233 1232
1234 1233
1235 1234 def test_store():
1236 1235 """Test %store."""
1237 1236 ip = get_ipython()
1238 1237 ip.run_line_magic('load_ext', 'storemagic')
1239 1238
1240 1239 # make sure the storage is empty
1241 1240 ip.run_line_magic("store", "-z")
1242 1241 ip.user_ns["var"] = 42
1243 1242 ip.run_line_magic("store", "var")
1244 1243 ip.user_ns["var"] = 39
1245 1244 ip.run_line_magic("store", "-r")
1246 1245 assert ip.user_ns["var"] == 42
1247 1246
1248 1247 ip.run_line_magic("store", "-d var")
1249 1248 ip.user_ns["var"] = 39
1250 1249 ip.run_line_magic("store", "-r")
1251 1250 assert ip.user_ns["var"] == 39
1252 1251
1253 1252
1254 1253 def _run_edit_test(arg_s, exp_filename=None,
1255 1254 exp_lineno=-1,
1256 1255 exp_contents=None,
1257 1256 exp_is_temp=None):
1258 1257 ip = get_ipython()
1259 1258 M = code.CodeMagics(ip)
1260 1259 last_call = ['','']
1261 1260 opts,args = M.parse_options(arg_s,'prxn:')
1262 1261 filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call)
1263 1262
1264 1263 if exp_filename is not None:
1265 1264 assert exp_filename == filename
1266 1265 if exp_contents is not None:
1267 1266 with io.open(filename, 'r', encoding='utf-8') as f:
1268 1267 contents = f.read()
1269 1268 assert exp_contents == contents
1270 1269 if exp_lineno != -1:
1271 1270 assert exp_lineno == lineno
1272 1271 if exp_is_temp is not None:
1273 1272 assert exp_is_temp == is_temp
1274 1273
1275 1274
1276 1275 def test_edit_interactive():
1277 1276 """%edit on interactively defined objects"""
1278 1277 ip = get_ipython()
1279 1278 n = ip.execution_count
1280 1279 ip.run_cell("def foo(): return 1", store_history=True)
1281 1280
1282 1281 with pytest.raises(code.InteractivelyDefined) as e:
1283 1282 _run_edit_test("foo")
1284 1283 assert e.value.index == n
1285 1284
1286 1285
1287 1286 def test_edit_cell():
1288 1287 """%edit [cell id]"""
1289 1288 ip = get_ipython()
1290 1289
1291 1290 ip.run_cell("def foo(): return 1", store_history=True)
1292 1291
1293 1292 # test
1294 1293 _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True)
1295 1294
1296 1295 def test_edit_fname():
1297 1296 """%edit file"""
1298 1297 # test
1299 1298 _run_edit_test("test file.py", exp_filename="test file.py")
1300 1299
1301 1300 def test_bookmark():
1302 1301 ip = get_ipython()
1303 1302 ip.run_line_magic('bookmark', 'bmname')
1304 1303 with tt.AssertPrints('bmname'):
1305 1304 ip.run_line_magic('bookmark', '-l')
1306 1305 ip.run_line_magic('bookmark', '-d bmname')
1307 1306
1308 1307 def test_ls_magic():
1309 1308 ip = get_ipython()
1310 1309 json_formatter = ip.display_formatter.formatters['application/json']
1311 1310 json_formatter.enabled = True
1312 1311 lsmagic = ip.magic('lsmagic')
1313 1312 with warnings.catch_warnings(record=True) as w:
1314 1313 j = json_formatter(lsmagic)
1315 1314 assert sorted(j) == ["cell", "line"]
1316 1315 assert w == [] # no warnings
1317 1316
1318 1317
1319 1318 def test_strip_initial_indent():
1320 1319 def sii(s):
1321 1320 lines = s.splitlines()
1322 1321 return '\n'.join(code.strip_initial_indent(lines))
1323 1322
1324 1323 assert sii(" a = 1\nb = 2") == "a = 1\nb = 2"
1325 1324 assert sii(" a\n b\nc") == "a\n b\nc"
1326 1325 assert sii("a\n b") == "a\n b"
1327 1326
1328 1327 def test_logging_magic_quiet_from_arg():
1329 1328 _ip.config.LoggingMagics.quiet = False
1330 1329 lm = logging.LoggingMagics(shell=_ip)
1331 1330 with TemporaryDirectory() as td:
1332 1331 try:
1333 1332 with tt.AssertNotPrints(re.compile("Activating.*")):
1334 1333 lm.logstart('-q {}'.format(
1335 1334 os.path.join(td, "quiet_from_arg.log")))
1336 1335 finally:
1337 1336 _ip.logger.logstop()
1338 1337
1339 1338 def test_logging_magic_quiet_from_config():
1340 1339 _ip.config.LoggingMagics.quiet = True
1341 1340 lm = logging.LoggingMagics(shell=_ip)
1342 1341 with TemporaryDirectory() as td:
1343 1342 try:
1344 1343 with tt.AssertNotPrints(re.compile("Activating.*")):
1345 1344 lm.logstart(os.path.join(td, "quiet_from_config.log"))
1346 1345 finally:
1347 1346 _ip.logger.logstop()
1348 1347
1349 1348
1350 1349 def test_logging_magic_not_quiet():
1351 1350 _ip.config.LoggingMagics.quiet = False
1352 1351 lm = logging.LoggingMagics(shell=_ip)
1353 1352 with TemporaryDirectory() as td:
1354 1353 try:
1355 1354 with tt.AssertPrints(re.compile("Activating.*")):
1356 1355 lm.logstart(os.path.join(td, "not_quiet.log"))
1357 1356 finally:
1358 1357 _ip.logger.logstop()
1359 1358
1360 1359
1361 1360 def test_time_no_var_expand():
1362 1361 _ip.user_ns['a'] = 5
1363 1362 _ip.user_ns['b'] = []
1364 1363 _ip.magic('time b.append("{a}")')
1365 1364 assert _ip.user_ns['b'] == ['{a}']
1366 1365
1367 1366
1368 1367 # this is slow, put at the end for local testing.
1369 1368 def test_timeit_arguments():
1370 1369 "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
1371 1370 _ip.magic("timeit -n1 -r1 a=('#')")
1372 1371
1373 1372
1374 1373 MINIMAL_LAZY_MAGIC = """
1375 1374 from IPython.core.magic import (
1376 1375 Magics,
1377 1376 magics_class,
1378 1377 line_magic,
1379 1378 cell_magic,
1380 1379 )
1381 1380
1382 1381
1383 1382 @magics_class
1384 1383 class LazyMagics(Magics):
1385 1384 @line_magic
1386 1385 def lazy_line(self, line):
1387 1386 print("Lazy Line")
1388 1387
1389 1388 @cell_magic
1390 1389 def lazy_cell(self, line, cell):
1391 1390 print("Lazy Cell")
1392 1391
1393 1392
1394 1393 def load_ipython_extension(ipython):
1395 1394 ipython.register_magics(LazyMagics)
1396 1395 """
1397 1396
1398 1397
1399 1398 def test_lazy_magics():
1400 1399 with pytest.raises(UsageError):
1401 1400 ip.run_line_magic("lazy_line", "")
1402 1401
1403 1402 startdir = os.getcwd()
1404 1403
1405 1404 with TemporaryDirectory() as tmpdir:
1406 1405 with prepended_to_syspath(tmpdir):
1407 1406 ptempdir = Path(tmpdir)
1408 1407 tf = ptempdir / "lazy_magic_module.py"
1409 1408 tf.write_text(MINIMAL_LAZY_MAGIC)
1410 1409 ip.magics_manager.register_lazy("lazy_line", Path(tf.name).name[:-3])
1411 1410 with tt.AssertPrints("Lazy Line"):
1412 1411 ip.run_line_magic("lazy_line", "")
1413 1412
1414 1413
1415 1414 TEST_MODULE = """
1416 1415 print('Loaded my_tmp')
1417 1416 if __name__ == "__main__":
1418 1417 print('I just ran a script')
1419 1418 """
1420 1419
1421 1420 def test_run_module_from_import_hook():
1422 1421 "Test that a module can be loaded via an import hook"
1423 1422 with TemporaryDirectory() as tmpdir:
1424 1423 fullpath = os.path.join(tmpdir, "my_tmp.py")
1425 1424 Path(fullpath).write_text(TEST_MODULE, encoding="utf-8")
1426 1425
1427 1426 import importlib.abc
1428 1427 import importlib.util
1429 1428
1430 1429 class MyTempImporter(importlib.abc.MetaPathFinder, importlib.abc.SourceLoader):
1431 1430 def find_spec(self, fullname, path, target=None):
1432 1431 if fullname == "my_tmp":
1433 1432 return importlib.util.spec_from_loader(fullname, self)
1434 1433
1435 1434 def get_filename(self, fullname):
1436 1435 assert fullname == "my_tmp"
1437 1436 return fullpath
1438 1437
1439 1438 def get_data(self, path):
1440 1439 assert Path(path).samefile(fullpath)
1441 1440 return Path(fullpath).read_text(encoding="utf-8")
1442 1441
1443 1442 sys.meta_path.insert(0, MyTempImporter())
1444 1443
1445 1444 with capture_output() as captured:
1446 1445 _ip.magic("run -m my_tmp")
1447 1446 _ip.run_cell("import my_tmp")
1448 1447
1449 1448 output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n"
1450 1449 assert output == captured.stdout
1451 1450
1452 1451 sys.meta_path.pop(0)
@@ -1,201 +1,200 b''
1 1 import errno
2 2 import os
3 3 import shutil
4 import sys
5 4 import tempfile
6 5 import warnings
7 6 from unittest.mock import patch
8 7
9 8 from tempfile import TemporaryDirectory
10 9 from testpath import assert_isdir, assert_isfile, modified_env
11 10
12 11 from IPython import paths
13 12 from IPython.testing.decorators import skip_win32
14 13
15 14 TMP_TEST_DIR = os.path.realpath(tempfile.mkdtemp())
16 15 HOME_TEST_DIR = os.path.join(TMP_TEST_DIR, "home_test_dir")
17 16 XDG_TEST_DIR = os.path.join(HOME_TEST_DIR, "xdg_test_dir")
18 17 XDG_CACHE_DIR = os.path.join(HOME_TEST_DIR, "xdg_cache_dir")
19 18 IP_TEST_DIR = os.path.join(HOME_TEST_DIR,'.ipython')
20 19
21 20 def setup_module():
22 21 """Setup testenvironment for the module:
23 22
24 23 - Adds dummy home dir tree
25 24 """
26 25 # Do not mask exceptions here. In particular, catching WindowsError is a
27 26 # problem because that exception is only defined on Windows...
28 27 os.makedirs(IP_TEST_DIR)
29 28 os.makedirs(os.path.join(XDG_TEST_DIR, 'ipython'))
30 29 os.makedirs(os.path.join(XDG_CACHE_DIR, 'ipython'))
31 30
32 31
33 32 def teardown_module():
34 33 """Teardown testenvironment for the module:
35 34
36 35 - Remove dummy home dir tree
37 36 """
38 37 # Note: we remove the parent test dir, which is the root of all test
39 38 # subdirs we may have created. Use shutil instead of os.removedirs, so
40 39 # that non-empty directories are all recursively removed.
41 40 shutil.rmtree(TMP_TEST_DIR)
42 41
43 42 def patch_get_home_dir(dirpath):
44 43 return patch.object(paths, 'get_home_dir', return_value=dirpath)
45 44
46 45
47 46 def test_get_ipython_dir_1():
48 47 """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions."""
49 48 env_ipdir = os.path.join("someplace", ".ipython")
50 49 with patch.object(paths, '_writable_dir', return_value=True), \
51 50 modified_env({'IPYTHONDIR': env_ipdir}):
52 51 ipdir = paths.get_ipython_dir()
53 52
54 53 assert ipdir == env_ipdir
55 54
56 55 def test_get_ipython_dir_2():
57 56 """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions."""
58 57 with patch_get_home_dir('someplace'), \
59 58 patch.object(paths, 'get_xdg_dir', return_value=None), \
60 59 patch.object(paths, '_writable_dir', return_value=True), \
61 60 patch('os.name', "posix"), \
62 61 modified_env({'IPYTHON_DIR': None,
63 62 'IPYTHONDIR': None,
64 63 'XDG_CONFIG_HOME': None
65 64 }):
66 65 ipdir = paths.get_ipython_dir()
67 66
68 67 assert ipdir == os.path.join("someplace", ".ipython")
69 68
70 69 def test_get_ipython_dir_3():
71 70 """test_get_ipython_dir_3, use XDG if defined and exists, and .ipython doesn't exist."""
72 71 tmphome = TemporaryDirectory()
73 72 try:
74 73 with patch_get_home_dir(tmphome.name), \
75 74 patch('os.name', 'posix'), \
76 75 modified_env({
77 76 'IPYTHON_DIR': None,
78 77 'IPYTHONDIR': None,
79 78 'XDG_CONFIG_HOME': XDG_TEST_DIR,
80 79 }), warnings.catch_warnings(record=True) as w:
81 80 ipdir = paths.get_ipython_dir()
82 81
83 82 assert ipdir == os.path.join(tmphome.name, XDG_TEST_DIR, "ipython")
84 83 assert len(w) == 0
85 84 finally:
86 85 tmphome.cleanup()
87 86
88 87 def test_get_ipython_dir_4():
89 88 """test_get_ipython_dir_4, warn if XDG and home both exist."""
90 89 with patch_get_home_dir(HOME_TEST_DIR), \
91 90 patch('os.name', 'posix'):
92 91 try:
93 92 os.mkdir(os.path.join(XDG_TEST_DIR, 'ipython'))
94 93 except OSError as e:
95 94 if e.errno != errno.EEXIST:
96 95 raise
97 96
98 97
99 98 with modified_env({
100 99 'IPYTHON_DIR': None,
101 100 'IPYTHONDIR': None,
102 101 'XDG_CONFIG_HOME': XDG_TEST_DIR,
103 102 }), warnings.catch_warnings(record=True) as w:
104 103 ipdir = paths.get_ipython_dir()
105 104
106 105 assert len(w) == 1
107 106 assert "Ignoring" in str(w[0])
108 107
109 108
110 109 def test_get_ipython_dir_5():
111 110 """test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist."""
112 111 with patch_get_home_dir(HOME_TEST_DIR), \
113 112 patch('os.name', 'posix'):
114 113 try:
115 114 os.rmdir(os.path.join(XDG_TEST_DIR, 'ipython'))
116 115 except OSError as e:
117 116 if e.errno != errno.ENOENT:
118 117 raise
119 118
120 119 with modified_env({
121 120 'IPYTHON_DIR': None,
122 121 'IPYTHONDIR': None,
123 122 'XDG_CONFIG_HOME': XDG_TEST_DIR,
124 123 }):
125 124 ipdir = paths.get_ipython_dir()
126 125
127 126 assert ipdir == IP_TEST_DIR
128 127
129 128 def test_get_ipython_dir_6():
130 129 """test_get_ipython_dir_6, use home over XDG if defined and neither exist."""
131 130 xdg = os.path.join(HOME_TEST_DIR, 'somexdg')
132 131 os.mkdir(xdg)
133 132 shutil.rmtree(os.path.join(HOME_TEST_DIR, '.ipython'))
134 133 print(paths._writable_dir)
135 134 with patch_get_home_dir(HOME_TEST_DIR), \
136 135 patch.object(paths, 'get_xdg_dir', return_value=xdg), \
137 136 patch('os.name', 'posix'), \
138 137 modified_env({
139 138 'IPYTHON_DIR': None,
140 139 'IPYTHONDIR': None,
141 140 'XDG_CONFIG_HOME': None,
142 141 }), warnings.catch_warnings(record=True) as w:
143 142 ipdir = paths.get_ipython_dir()
144 143
145 144 assert ipdir == os.path.join(HOME_TEST_DIR, ".ipython")
146 145 assert len(w) == 0
147 146
148 147 def test_get_ipython_dir_7():
149 148 """test_get_ipython_dir_7, test home directory expansion on IPYTHONDIR"""
150 149 home_dir = os.path.normpath(os.path.expanduser('~'))
151 150 with modified_env({'IPYTHONDIR': os.path.join('~', 'somewhere')}), \
152 151 patch.object(paths, '_writable_dir', return_value=True):
153 152 ipdir = paths.get_ipython_dir()
154 153 assert ipdir == os.path.join(home_dir, "somewhere")
155 154
156 155
157 156 @skip_win32
158 157 def test_get_ipython_dir_8():
159 158 """test_get_ipython_dir_8, test / home directory"""
160 159 if not os.access("/", os.W_OK):
161 160 # test only when HOME directory actually writable
162 161 return
163 162
164 163 with patch.object(paths, "_writable_dir", lambda path: bool(path)), patch.object(
165 164 paths, "get_xdg_dir", return_value=None
166 165 ), modified_env(
167 166 {
168 167 "IPYTHON_DIR": None,
169 168 "IPYTHONDIR": None,
170 169 "HOME": "/",
171 170 }
172 171 ):
173 172 assert paths.get_ipython_dir() == "/.ipython"
174 173
175 174
176 175 def test_get_ipython_cache_dir():
177 176 with modified_env({'HOME': HOME_TEST_DIR}):
178 177 if os.name == "posix":
179 178 # test default
180 179 os.makedirs(os.path.join(HOME_TEST_DIR, ".cache"))
181 180 with modified_env({'XDG_CACHE_HOME': None}):
182 181 ipdir = paths.get_ipython_cache_dir()
183 182 assert os.path.join(HOME_TEST_DIR, ".cache", "ipython") == ipdir
184 183 assert_isdir(ipdir)
185 184
186 185 # test env override
187 186 with modified_env({"XDG_CACHE_HOME": XDG_CACHE_DIR}):
188 187 ipdir = paths.get_ipython_cache_dir()
189 188 assert_isdir(ipdir)
190 189 assert ipdir == os.path.join(XDG_CACHE_DIR, "ipython")
191 190 else:
192 191 assert paths.get_ipython_cache_dir() == paths.get_ipython_dir()
193 192
194 193 def test_get_ipython_package_dir():
195 194 ipdir = paths.get_ipython_package_dir()
196 195 assert_isdir(ipdir)
197 196
198 197
199 198 def test_get_ipython_module_path():
200 199 ipapp_path = paths.get_ipython_module_path('IPython.terminal.ipapp')
201 200 assert_isfile(ipapp_path)
@@ -1,274 +1,271 b''
1 1 """Tests for pylab tools module.
2 2 """
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 a2b_base64
9 9 from io import BytesIO
10 10
11 11 import pytest
12 12
13 13 matplotlib = pytest.importorskip("matplotlib")
14 14 matplotlib.use('Agg')
15 15 from matplotlib.figure import Figure
16 16
17 17 from matplotlib import pyplot as plt
18 18 from matplotlib_inline import backend_inline
19 19 import numpy as np
20 20
21 21 from IPython.core.getipython import get_ipython
22 22 from IPython.core.interactiveshell import InteractiveShell
23 23 from IPython.core.display import _PNG, _JPEG
24 24 from .. import pylabtools as pt
25 25
26 26 from IPython.testing import decorators as dec
27 27
28 28
29 29 def test_figure_to_svg():
30 30 # simple empty-figure test
31 31 fig = plt.figure()
32 32 assert pt.print_figure(fig, "svg") is None
33 33
34 34 plt.close('all')
35 35
36 36 # simple check for at least svg-looking output
37 37 fig = plt.figure()
38 38 ax = fig.add_subplot(1,1,1)
39 39 ax.plot([1,2,3])
40 40 plt.draw()
41 41 svg = pt.print_figure(fig, "svg")[:100].lower()
42 42 assert "doctype svg" in svg
43 43
44 44
45 45 def _check_pil_jpeg_bytes():
46 46 """Skip if PIL can't write JPEGs to BytesIO objects"""
47 47 # PIL's JPEG plugin can't write to BytesIO objects
48 48 # Pillow fixes this
49 49 from PIL import Image
50 50 buf = BytesIO()
51 51 img = Image.new("RGB", (4,4))
52 52 try:
53 53 img.save(buf, 'jpeg')
54 54 except Exception as e:
55 55 ename = e.__class__.__name__
56 56 raise pytest.skip("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e)) from e
57 57
58 58 @dec.skip_without("PIL.Image")
59 59 def test_figure_to_jpeg():
60 60 _check_pil_jpeg_bytes()
61 61 # simple check for at least jpeg-looking output
62 62 fig = plt.figure()
63 63 ax = fig.add_subplot(1,1,1)
64 64 ax.plot([1,2,3])
65 65 plt.draw()
66 66 jpeg = pt.print_figure(fig, 'jpeg', pil_kwargs={'optimize': 50})[:100].lower()
67 67 assert jpeg.startswith(_JPEG)
68 68
69 69 def test_retina_figure():
70 70 # simple empty-figure test
71 71 fig = plt.figure()
72 72 assert pt.retina_figure(fig) == None
73 73 plt.close('all')
74 74
75 75 fig = plt.figure()
76 76 ax = fig.add_subplot(1,1,1)
77 77 ax.plot([1,2,3])
78 78 plt.draw()
79 79 png, md = pt.retina_figure(fig)
80 80 assert png.startswith(_PNG)
81 81 assert "width" in md
82 82 assert "height" in md
83 83
84 84
85 85 _fmt_mime_map = {
86 86 'png': 'image/png',
87 87 'jpeg': 'image/jpeg',
88 88 'pdf': 'application/pdf',
89 89 'retina': 'image/png',
90 90 'svg': 'image/svg+xml',
91 91 }
92 92
93 93 def test_select_figure_formats_str():
94 94 ip = get_ipython()
95 95 for fmt, active_mime in _fmt_mime_map.items():
96 96 pt.select_figure_formats(ip, fmt)
97 97 for mime, f in ip.display_formatter.formatters.items():
98 98 if mime == active_mime:
99 99 assert Figure in f
100 100 else:
101 101 assert Figure not in f
102 102
103 103 def test_select_figure_formats_kwargs():
104 104 ip = get_ipython()
105 105 kwargs = dict(bbox_inches="tight")
106 106 pt.select_figure_formats(ip, "png", **kwargs)
107 107 formatter = ip.display_formatter.formatters["image/png"]
108 108 f = formatter.lookup_by_type(Figure)
109 109 cell = f.keywords
110 110 expected = kwargs
111 111 expected["base64"] = True
112 112 expected["fmt"] = "png"
113 113 assert cell == expected
114 114
115 115 # check that the formatter doesn't raise
116 116 fig = plt.figure()
117 117 ax = fig.add_subplot(1,1,1)
118 118 ax.plot([1,2,3])
119 119 plt.draw()
120 120 formatter.enabled = True
121 121 png = formatter(fig)
122 122 assert isinstance(png, str)
123 123 png_bytes = a2b_base64(png)
124 124 assert png_bytes.startswith(_PNG)
125 125
126 126 def test_select_figure_formats_set():
127 127 ip = get_ipython()
128 128 for fmts in [
129 129 {'png', 'svg'},
130 130 ['png'],
131 131 ('jpeg', 'pdf', 'retina'),
132 132 {'svg'},
133 133 ]:
134 134 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
135 135 pt.select_figure_formats(ip, fmts)
136 136 for mime, f in ip.display_formatter.formatters.items():
137 137 if mime in active_mimes:
138 138 assert Figure in f
139 139 else:
140 140 assert Figure not in f
141 141
142 142 def test_select_figure_formats_bad():
143 143 ip = get_ipython()
144 144 with pytest.raises(ValueError):
145 145 pt.select_figure_formats(ip, 'foo')
146 146 with pytest.raises(ValueError):
147 147 pt.select_figure_formats(ip, {'png', 'foo'})
148 148 with pytest.raises(ValueError):
149 149 pt.select_figure_formats(ip, ['retina', 'pdf', 'bar', 'bad'])
150 150
151 151 def test_import_pylab():
152 152 ns = {}
153 153 pt.import_pylab(ns, import_all=False)
154 154 assert "plt" in ns
155 155 assert ns["np"] == np
156 156
157 157
158 from traitlets.config import Config
159
160
161 158 class TestPylabSwitch(object):
162 159 class Shell(InteractiveShell):
163 160 def init_history(self):
164 161 """Sets up the command history, and starts regular autosaves."""
165 162 self.config.HistoryManager.hist_file = ":memory:"
166 163 super().init_history()
167 164
168 165 def enable_gui(self, gui):
169 166 pass
170 167
171 168 def setup(self):
172 169 import matplotlib
173 170 def act_mpl(backend):
174 171 matplotlib.rcParams['backend'] = backend
175 172
176 173 # Save rcParams since they get modified
177 174 self._saved_rcParams = matplotlib.rcParams
178 175 self._saved_rcParamsOrig = matplotlib.rcParamsOrig
179 176 matplotlib.rcParams = dict(backend='Qt4Agg')
180 177 matplotlib.rcParamsOrig = dict(backend='Qt4Agg')
181 178
182 179 # Mock out functions
183 180 self._save_am = pt.activate_matplotlib
184 181 pt.activate_matplotlib = act_mpl
185 182 self._save_ip = pt.import_pylab
186 183 pt.import_pylab = lambda *a,**kw:None
187 184 self._save_cis = backend_inline.configure_inline_support
188 185 backend_inline.configure_inline_support = lambda *a, **kw: None
189 186
190 187 def teardown(self):
191 188 pt.activate_matplotlib = self._save_am
192 189 pt.import_pylab = self._save_ip
193 190 backend_inline.configure_inline_support = self._save_cis
194 191 import matplotlib
195 192 matplotlib.rcParams = self._saved_rcParams
196 193 matplotlib.rcParamsOrig = self._saved_rcParamsOrig
197 194
198 195 def test_qt(self):
199 196
200 197 s = self.Shell()
201 198 gui, backend = s.enable_matplotlib(None)
202 199 assert gui == "qt"
203 200 assert s.pylab_gui_select == "qt"
204 201
205 202 gui, backend = s.enable_matplotlib("inline")
206 203 assert gui == "inline"
207 204 assert s.pylab_gui_select == "qt"
208 205
209 206 gui, backend = s.enable_matplotlib("qt")
210 207 assert gui == "qt"
211 208 assert s.pylab_gui_select == "qt"
212 209
213 210 gui, backend = s.enable_matplotlib("inline")
214 211 assert gui == "inline"
215 212 assert s.pylab_gui_select == "qt"
216 213
217 214 gui, backend = s.enable_matplotlib()
218 215 assert gui == "qt"
219 216 assert s.pylab_gui_select == "qt"
220 217
221 218 def test_inline(self):
222 219 s = self.Shell()
223 220 gui, backend = s.enable_matplotlib("inline")
224 221 assert gui == "inline"
225 222 assert s.pylab_gui_select == None
226 223
227 224 gui, backend = s.enable_matplotlib("inline")
228 225 assert gui == "inline"
229 226 assert s.pylab_gui_select == None
230 227
231 228 gui, backend = s.enable_matplotlib("qt")
232 229 assert gui == "qt"
233 230 assert s.pylab_gui_select == "qt"
234 231
235 232 def test_inline_twice(self):
236 233 "Using '%matplotlib inline' twice should not reset formatters"
237 234
238 235 ip = self.Shell()
239 236 gui, backend = ip.enable_matplotlib("inline")
240 237 assert gui == "inline"
241 238
242 239 fmts = {'png'}
243 240 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
244 241 pt.select_figure_formats(ip, fmts)
245 242
246 243 gui, backend = ip.enable_matplotlib("inline")
247 244 assert gui == "inline"
248 245
249 246 for mime, f in ip.display_formatter.formatters.items():
250 247 if mime in active_mimes:
251 248 assert Figure in f
252 249 else:
253 250 assert Figure not in f
254 251
255 252 def test_qt_gtk(self):
256 253 s = self.Shell()
257 254 gui, backend = s.enable_matplotlib("qt")
258 255 assert gui == "qt"
259 256 assert s.pylab_gui_select == "qt"
260 257
261 258 gui, backend = s.enable_matplotlib("gtk")
262 259 assert gui == "qt"
263 260 assert s.pylab_gui_select == "qt"
264 261
265 262
266 263 def test_no_gui_backends():
267 264 for k in ['agg', 'svg', 'pdf', 'ps']:
268 265 assert k not in pt.backend2gui
269 266
270 267
271 268 def test_figure_no_canvas():
272 269 fig = Figure()
273 270 fig.canvas = None
274 271 pt.print_figure(fig)
@@ -1,409 +1,408 b''
1 1 # encoding: utf-8
2 2 """Tests for IPython.core.ultratb
3 3 """
4 4 import io
5 import logging
6 5 import os.path
7 6 import platform
8 7 import re
9 8 import sys
10 9 import traceback
11 10 import unittest
12 11 from textwrap import dedent
13 12
14 13 from tempfile import TemporaryDirectory
15 14
16 15 from IPython.core.ultratb import ColorTB, VerboseTB
17 16 from IPython.testing import tools as tt
18 17 from IPython.testing.decorators import onlyif_unicode_paths
19 18 from IPython.utils.syspathcontext import prepended_to_syspath
20 19
21 20 file_1 = """1
22 21 2
23 22 3
24 23 def f():
25 24 1/0
26 25 """
27 26
28 27 file_2 = """def f():
29 28 1/0
30 29 """
31 30
32 31
33 32 def recursionlimit(frames):
34 33 """
35 34 decorator to set the recursion limit temporarily
36 35 """
37 36
38 37 def inner(test_function):
39 38 def wrapper(*args, **kwargs):
40 39 rl = sys.getrecursionlimit()
41 40 sys.setrecursionlimit(frames)
42 41 try:
43 42 return test_function(*args, **kwargs)
44 43 finally:
45 44 sys.setrecursionlimit(rl)
46 45
47 46 return wrapper
48 47
49 48 return inner
50 49
51 50
52 51 class ChangedPyFileTest(unittest.TestCase):
53 52 def test_changing_py_file(self):
54 53 """Traceback produced if the line where the error occurred is missing?
55 54
56 55 https://github.com/ipython/ipython/issues/1456
57 56 """
58 57 with TemporaryDirectory() as td:
59 58 fname = os.path.join(td, "foo.py")
60 59 with open(fname, "w", encoding="utf-8") as f:
61 60 f.write(file_1)
62 61
63 62 with prepended_to_syspath(td):
64 63 ip.run_cell("import foo")
65 64
66 65 with tt.AssertPrints("ZeroDivisionError"):
67 66 ip.run_cell("foo.f()")
68 67
69 68 # Make the file shorter, so the line of the error is missing.
70 69 with open(fname, "w", encoding="utf-8") as f:
71 70 f.write(file_2)
72 71
73 72 # For some reason, this was failing on the *second* call after
74 73 # changing the file, so we call f() twice.
75 74 with tt.AssertNotPrints("Internal Python error", channel='stderr'):
76 75 with tt.AssertPrints("ZeroDivisionError"):
77 76 ip.run_cell("foo.f()")
78 77 with tt.AssertPrints("ZeroDivisionError"):
79 78 ip.run_cell("foo.f()")
80 79
81 80 iso_8859_5_file = u'''# coding: iso-8859-5
82 81
83 82 def fail():
84 83 """Π΄Π±Π˜Π–"""
85 84 1/0 # Π΄Π±Π˜Π–
86 85 '''
87 86
88 87 class NonAsciiTest(unittest.TestCase):
89 88 @onlyif_unicode_paths
90 89 def test_nonascii_path(self):
91 90 # Non-ascii directory name as well.
92 91 with TemporaryDirectory(suffix=u'Γ©') as td:
93 92 fname = os.path.join(td, u"fooΓ©.py")
94 93 with open(fname, "w", encoding="utf-8") as f:
95 94 f.write(file_1)
96 95
97 96 with prepended_to_syspath(td):
98 97 ip.run_cell("import foo")
99 98
100 99 with tt.AssertPrints("ZeroDivisionError"):
101 100 ip.run_cell("foo.f()")
102 101
103 102 def test_iso8859_5(self):
104 103 with TemporaryDirectory() as td:
105 104 fname = os.path.join(td, 'dfghjkl.py')
106 105
107 106 with io.open(fname, 'w', encoding='iso-8859-5') as f:
108 107 f.write(iso_8859_5_file)
109 108
110 109 with prepended_to_syspath(td):
111 110 ip.run_cell("from dfghjkl import fail")
112 111
113 112 with tt.AssertPrints("ZeroDivisionError"):
114 113 with tt.AssertPrints(u'Π΄Π±Π˜Π–', suppress=False):
115 114 ip.run_cell('fail()')
116 115
117 116 def test_nonascii_msg(self):
118 117 cell = u"raise Exception('Γ©')"
119 118 expected = u"Exception('Γ©')"
120 119 ip.run_cell("%xmode plain")
121 120 with tt.AssertPrints(expected):
122 121 ip.run_cell(cell)
123 122
124 123 ip.run_cell("%xmode verbose")
125 124 with tt.AssertPrints(expected):
126 125 ip.run_cell(cell)
127 126
128 127 ip.run_cell("%xmode context")
129 128 with tt.AssertPrints(expected):
130 129 ip.run_cell(cell)
131 130
132 131 ip.run_cell("%xmode minimal")
133 132 with tt.AssertPrints(u"Exception: Γ©"):
134 133 ip.run_cell(cell)
135 134
136 135 # Put this back into Context mode for later tests.
137 136 ip.run_cell("%xmode context")
138 137
139 138 class NestedGenExprTestCase(unittest.TestCase):
140 139 """
141 140 Regression test for the following issues:
142 141 https://github.com/ipython/ipython/issues/8293
143 142 https://github.com/ipython/ipython/issues/8205
144 143 """
145 144 def test_nested_genexpr(self):
146 145 code = dedent(
147 146 """\
148 147 class SpecificException(Exception):
149 148 pass
150 149
151 150 def foo(x):
152 151 raise SpecificException("Success!")
153 152
154 153 sum(sum(foo(x) for _ in [0]) for x in [0])
155 154 """
156 155 )
157 156 with tt.AssertPrints('SpecificException: Success!', suppress=False):
158 157 ip.run_cell(code)
159 158
160 159
161 160 indentationerror_file = """if True:
162 161 zoon()
163 162 """
164 163
165 164 class IndentationErrorTest(unittest.TestCase):
166 165 def test_indentationerror_shows_line(self):
167 166 # See issue gh-2398
168 167 with tt.AssertPrints("IndentationError"):
169 168 with tt.AssertPrints("zoon()", suppress=False):
170 169 ip.run_cell(indentationerror_file)
171 170
172 171 with TemporaryDirectory() as td:
173 172 fname = os.path.join(td, "foo.py")
174 173 with open(fname, "w", encoding="utf-8") as f:
175 174 f.write(indentationerror_file)
176 175
177 176 with tt.AssertPrints("IndentationError"):
178 177 with tt.AssertPrints("zoon()", suppress=False):
179 178 ip.magic('run %s' % fname)
180 179
181 180 se_file_1 = """1
182 181 2
183 182 7/
184 183 """
185 184
186 185 se_file_2 = """7/
187 186 """
188 187
189 188 class SyntaxErrorTest(unittest.TestCase):
190 189
191 190 def test_syntaxerror_no_stacktrace_at_compile_time(self):
192 191 syntax_error_at_compile_time = """
193 192 def foo():
194 193 ..
195 194 """
196 195 with tt.AssertPrints("SyntaxError"):
197 196 ip.run_cell(syntax_error_at_compile_time)
198 197
199 198 with tt.AssertNotPrints("foo()"):
200 199 ip.run_cell(syntax_error_at_compile_time)
201 200
202 201 def test_syntaxerror_stacktrace_when_running_compiled_code(self):
203 202 syntax_error_at_runtime = """
204 203 def foo():
205 204 eval("..")
206 205
207 206 def bar():
208 207 foo()
209 208
210 209 bar()
211 210 """
212 211 with tt.AssertPrints("SyntaxError"):
213 212 ip.run_cell(syntax_error_at_runtime)
214 213 # Assert syntax error during runtime generate stacktrace
215 214 with tt.AssertPrints(["foo()", "bar()"]):
216 215 ip.run_cell(syntax_error_at_runtime)
217 216 del ip.user_ns['bar']
218 217 del ip.user_ns['foo']
219 218
220 219 def test_changing_py_file(self):
221 220 with TemporaryDirectory() as td:
222 221 fname = os.path.join(td, "foo.py")
223 222 with open(fname, "w", encoding="utf-8") as f:
224 223 f.write(se_file_1)
225 224
226 225 with tt.AssertPrints(["7/", "SyntaxError"]):
227 226 ip.magic("run " + fname)
228 227
229 228 # Modify the file
230 229 with open(fname, "w", encoding="utf-8") as f:
231 230 f.write(se_file_2)
232 231
233 232 # The SyntaxError should point to the correct line
234 233 with tt.AssertPrints(["7/", "SyntaxError"]):
235 234 ip.magic("run " + fname)
236 235
237 236 def test_non_syntaxerror(self):
238 237 # SyntaxTB may be called with an error other than a SyntaxError
239 238 # See e.g. gh-4361
240 239 try:
241 240 raise ValueError('QWERTY')
242 241 except ValueError:
243 242 with tt.AssertPrints('QWERTY'):
244 243 ip.showsyntaxerror()
245 244
246 245 import sys
247 246
248 247 if sys.version_info < (3, 9) and platform.python_implementation() != "PyPy":
249 248 """
250 249 New 3.9 Pgen Parser does not raise Memory error, except on failed malloc.
251 250 """
252 251 class MemoryErrorTest(unittest.TestCase):
253 252 def test_memoryerror(self):
254 253 memoryerror_code = "(" * 200 + ")" * 200
255 254 with tt.AssertPrints("MemoryError"):
256 255 ip.run_cell(memoryerror_code)
257 256
258 257
259 258 class Python3ChainedExceptionsTest(unittest.TestCase):
260 259 DIRECT_CAUSE_ERROR_CODE = """
261 260 try:
262 261 x = 1 + 2
263 262 print(not_defined_here)
264 263 except Exception as e:
265 264 x += 55
266 265 x - 1
267 266 y = {}
268 267 raise KeyError('uh') from e
269 268 """
270 269
271 270 EXCEPTION_DURING_HANDLING_CODE = """
272 271 try:
273 272 x = 1 + 2
274 273 print(not_defined_here)
275 274 except Exception as e:
276 275 x += 55
277 276 x - 1
278 277 y = {}
279 278 raise KeyError('uh')
280 279 """
281 280
282 281 SUPPRESS_CHAINING_CODE = """
283 282 try:
284 283 1/0
285 284 except Exception:
286 285 raise ValueError("Yikes") from None
287 286 """
288 287
289 288 def test_direct_cause_error(self):
290 289 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
291 290 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
292 291
293 292 def test_exception_during_handling_error(self):
294 293 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
295 294 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
296 295
297 296 def test_suppress_exception_chaining(self):
298 297 with tt.AssertNotPrints("ZeroDivisionError"), \
299 298 tt.AssertPrints("ValueError", suppress=False):
300 299 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
301 300
302 301 def test_plain_direct_cause_error(self):
303 302 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
304 303 ip.run_cell("%xmode Plain")
305 304 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
306 305 ip.run_cell("%xmode Verbose")
307 306
308 307 def test_plain_exception_during_handling_error(self):
309 308 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
310 309 ip.run_cell("%xmode Plain")
311 310 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
312 311 ip.run_cell("%xmode Verbose")
313 312
314 313 def test_plain_suppress_exception_chaining(self):
315 314 with tt.AssertNotPrints("ZeroDivisionError"), \
316 315 tt.AssertPrints("ValueError", suppress=False):
317 316 ip.run_cell("%xmode Plain")
318 317 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
319 318 ip.run_cell("%xmode Verbose")
320 319
321 320
322 321 class RecursionTest(unittest.TestCase):
323 322 DEFINITIONS = """
324 323 def non_recurs():
325 324 1/0
326 325
327 326 def r1():
328 327 r1()
329 328
330 329 def r3a():
331 330 r3b()
332 331
333 332 def r3b():
334 333 r3c()
335 334
336 335 def r3c():
337 336 r3a()
338 337
339 338 def r3o1():
340 339 r3a()
341 340
342 341 def r3o2():
343 342 r3o1()
344 343 """
345 344 def setUp(self):
346 345 ip.run_cell(self.DEFINITIONS)
347 346
348 347 def test_no_recursion(self):
349 348 with tt.AssertNotPrints("skipping similar frames"):
350 349 ip.run_cell("non_recurs()")
351 350
352 351 @recursionlimit(200)
353 352 def test_recursion_one_frame(self):
354 353 with tt.AssertPrints(re.compile(
355 354 r"\[\.\.\. skipping similar frames: r1 at line 5 \(\d{2,3} times\)\]")
356 355 ):
357 356 ip.run_cell("r1()")
358 357
359 358 @recursionlimit(160)
360 359 def test_recursion_three_frames(self):
361 360 with tt.AssertPrints("[... skipping similar frames: "), \
362 361 tt.AssertPrints(re.compile(r"r3a at line 8 \(\d{2} times\)"), suppress=False), \
363 362 tt.AssertPrints(re.compile(r"r3b at line 11 \(\d{2} times\)"), suppress=False), \
364 363 tt.AssertPrints(re.compile(r"r3c at line 14 \(\d{2} times\)"), suppress=False):
365 364 ip.run_cell("r3o2()")
366 365
367 366
368 367 #----------------------------------------------------------------------------
369 368
370 369 # module testing (minimal)
371 370 def test_handlers():
372 371 def spam(c, d_e):
373 372 (d, e) = d_e
374 373 x = c + d
375 374 y = c * d
376 375 foo(x, y)
377 376
378 377 def foo(a, b, bar=1):
379 378 eggs(a, b + bar)
380 379
381 380 def eggs(f, g, z=globals()):
382 381 h = f + g
383 382 i = f - g
384 383 return h / i
385 384
386 385 buff = io.StringIO()
387 386
388 387 buff.write('')
389 388 buff.write('*** Before ***')
390 389 try:
391 390 buff.write(spam(1, (2, 3)))
392 391 except:
393 392 traceback.print_exc(file=buff)
394 393
395 394 handler = ColorTB(ostream=buff)
396 395 buff.write('*** ColorTB ***')
397 396 try:
398 397 buff.write(spam(1, (2, 3)))
399 398 except:
400 399 handler(*sys.exc_info())
401 400 buff.write('')
402 401
403 402 handler = VerboseTB(ostream=buff)
404 403 buff.write('*** VerboseTB ***')
405 404 try:
406 405 buff.write(spam(1, (2, 3)))
407 406 except:
408 407 handler(*sys.exc_info())
409 408 buff.write('')
@@ -1,258 +1,258 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tools for handling LaTeX."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from io import BytesIO, open
8 8 import os
9 9 import tempfile
10 10 import shutil
11 11 import subprocess
12 12 from base64 import encodebytes
13 13 import textwrap
14 14
15 from pathlib import Path, PurePath
15 from pathlib import Path
16 16
17 17 from IPython.utils.process import find_cmd, FindCmdError
18 18 from traitlets.config import get_config
19 19 from traitlets.config.configurable import SingletonConfigurable
20 20 from traitlets import List, Bool, Unicode
21 21 from IPython.utils.py3compat import cast_unicode
22 22
23 23
24 24 class LaTeXTool(SingletonConfigurable):
25 25 """An object to store configuration of the LaTeX tool."""
26 26 def _config_default(self):
27 27 return get_config()
28 28
29 29 backends = List(
30 30 Unicode(), ["matplotlib", "dvipng"],
31 31 help="Preferred backend to draw LaTeX math equations. "
32 32 "Backends in the list are checked one by one and the first "
33 33 "usable one is used. Note that `matplotlib` backend "
34 34 "is usable only for inline style equations. To draw "
35 35 "display style equations, `dvipng` backend must be specified. ",
36 36 # It is a List instead of Enum, to make configuration more
37 37 # flexible. For example, to use matplotlib mainly but dvipng
38 38 # for display style, the default ["matplotlib", "dvipng"] can
39 39 # be used. To NOT use dvipng so that other repr such as
40 40 # unicode pretty printing is used, you can use ["matplotlib"].
41 41 ).tag(config=True)
42 42
43 43 use_breqn = Bool(
44 44 True,
45 45 help="Use breqn.sty to automatically break long equations. "
46 46 "This configuration takes effect only for dvipng backend.",
47 47 ).tag(config=True)
48 48
49 49 packages = List(
50 50 ['amsmath', 'amsthm', 'amssymb', 'bm'],
51 51 help="A list of packages to use for dvipng backend. "
52 52 "'breqn' will be automatically appended when use_breqn=True.",
53 53 ).tag(config=True)
54 54
55 55 preamble = Unicode(
56 56 help="Additional preamble to use when generating LaTeX source "
57 57 "for dvipng backend.",
58 58 ).tag(config=True)
59 59
60 60
61 61 def latex_to_png(s, encode=False, backend=None, wrap=False, color='Black',
62 62 scale=1.0):
63 63 """Render a LaTeX string to PNG.
64 64
65 65 Parameters
66 66 ----------
67 67 s : str
68 68 The raw string containing valid inline LaTeX.
69 69 encode : bool, optional
70 70 Should the PNG data base64 encoded to make it JSON'able.
71 71 backend : {matplotlib, dvipng}
72 72 Backend for producing PNG data.
73 73 wrap : bool
74 74 If true, Automatically wrap `s` as a LaTeX equation.
75 75 color : string
76 76 Foreground color name among dvipsnames, e.g. 'Maroon' or on hex RGB
77 77 format, e.g. '#AA20FA'.
78 78 scale : float
79 79 Scale factor for the resulting PNG.
80 80 None is returned when the backend cannot be used.
81 81
82 82 """
83 83 s = cast_unicode(s)
84 84 allowed_backends = LaTeXTool.instance().backends
85 85 if backend is None:
86 86 backend = allowed_backends[0]
87 87 if backend not in allowed_backends:
88 88 return None
89 89 if backend == 'matplotlib':
90 90 f = latex_to_png_mpl
91 91 elif backend == 'dvipng':
92 92 f = latex_to_png_dvipng
93 93 if color.startswith('#'):
94 94 # Convert hex RGB color to LaTeX RGB color.
95 95 if len(color) == 7:
96 96 try:
97 97 color = "RGB {}".format(" ".join([str(int(x, 16)) for x in
98 98 textwrap.wrap(color[1:], 2)]))
99 99 except ValueError as e:
100 100 raise ValueError('Invalid color specification {}.'.format(color)) from e
101 101 else:
102 102 raise ValueError('Invalid color specification {}.'.format(color))
103 103 else:
104 104 raise ValueError('No such backend {0}'.format(backend))
105 105 bin_data = f(s, wrap, color, scale)
106 106 if encode and bin_data:
107 107 bin_data = encodebytes(bin_data)
108 108 return bin_data
109 109
110 110
111 111 def latex_to_png_mpl(s, wrap, color='Black', scale=1.0):
112 112 try:
113 113 from matplotlib import figure, font_manager, mathtext
114 114 from matplotlib.backends import backend_agg
115 115 from pyparsing import ParseFatalException
116 116 except ImportError:
117 117 return None
118 118
119 119 # mpl mathtext doesn't support display math, force inline
120 120 s = s.replace('$$', '$')
121 121 if wrap:
122 122 s = u'${0}$'.format(s)
123 123
124 124 try:
125 125 prop = font_manager.FontProperties(size=12)
126 126 dpi = 120 * scale
127 127 buffer = BytesIO()
128 128
129 129 # Adapted from mathtext.math_to_image
130 130 parser = mathtext.MathTextParser("path")
131 131 width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
132 132 fig = figure.Figure(figsize=(width / 72, height / 72))
133 133 fig.text(0, depth / height, s, fontproperties=prop, color=color)
134 134 backend_agg.FigureCanvasAgg(fig)
135 135 fig.savefig(buffer, dpi=dpi, format="png", transparent=True)
136 136 return buffer.getvalue()
137 137 except (ValueError, RuntimeError, ParseFatalException):
138 138 return None
139 139
140 140
141 141 def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0):
142 142 try:
143 143 find_cmd('latex')
144 144 find_cmd('dvipng')
145 145 except FindCmdError:
146 146 return None
147 147
148 148 startupinfo = None
149 149 if os.name == "nt":
150 150 # prevent popup-windows
151 151 startupinfo = subprocess.STARTUPINFO()
152 152 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
153 153
154 154 try:
155 155 workdir = Path(tempfile.mkdtemp())
156 156 tmpfile = "tmp.tex"
157 157 dvifile = "tmp.dvi"
158 158 outfile = "tmp.png"
159 159
160 160 with workdir.joinpath(tmpfile).open("w", encoding="utf8") as f:
161 161 f.writelines(genelatex(s, wrap))
162 162
163 163 with open(os.devnull, 'wb') as devnull:
164 164 subprocess.check_call(
165 165 ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile],
166 166 cwd=workdir,
167 167 stdout=devnull,
168 168 stderr=devnull,
169 169 startupinfo=startupinfo,
170 170 )
171 171
172 172 resolution = round(150*scale)
173 173 subprocess.check_call(
174 174 [
175 175 "dvipng",
176 176 "-T",
177 177 "tight",
178 178 "-D",
179 179 str(resolution),
180 180 "-z",
181 181 "9",
182 182 "-bg",
183 183 "Transparent",
184 184 "-o",
185 185 outfile,
186 186 dvifile,
187 187 "-fg",
188 188 color,
189 189 ],
190 190 cwd=workdir,
191 191 stdout=devnull,
192 192 stderr=devnull,
193 193 startupinfo=startupinfo,
194 194 )
195 195
196 196 with workdir.joinpath(outfile).open("rb") as f:
197 197 return f.read()
198 198 except subprocess.CalledProcessError:
199 199 return None
200 200 finally:
201 201 shutil.rmtree(workdir)
202 202
203 203
204 204 def kpsewhich(filename):
205 205 """Invoke kpsewhich command with an argument `filename`."""
206 206 try:
207 207 find_cmd("kpsewhich")
208 208 proc = subprocess.Popen(
209 209 ["kpsewhich", filename],
210 210 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
211 211 (stdout, stderr) = proc.communicate()
212 212 return stdout.strip().decode('utf8', 'replace')
213 213 except FindCmdError:
214 214 pass
215 215
216 216
217 217 def genelatex(body, wrap):
218 218 """Generate LaTeX document for dvipng backend."""
219 219 lt = LaTeXTool.instance()
220 220 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
221 221 yield r'\documentclass{article}'
222 222 packages = lt.packages
223 223 if breqn:
224 224 packages = packages + ['breqn']
225 225 for pack in packages:
226 226 yield r'\usepackage{{{0}}}'.format(pack)
227 227 yield r'\pagestyle{empty}'
228 228 if lt.preamble:
229 229 yield lt.preamble
230 230 yield r'\begin{document}'
231 231 if breqn:
232 232 yield r'\begin{dmath*}'
233 233 yield body
234 234 yield r'\end{dmath*}'
235 235 elif wrap:
236 236 yield u'$${0}$$'.format(body)
237 237 else:
238 238 yield body
239 239 yield u'\\end{document}'
240 240
241 241
242 242 _data_uri_template_png = u"""<img src="data:image/png;base64,%s" alt=%s />"""
243 243
244 244 def latex_to_html(s, alt='image'):
245 245 """Render LaTeX to HTML with embedded PNG data using data URIs.
246 246
247 247 Parameters
248 248 ----------
249 249 s : str
250 250 The raw string containing valid inline LateX.
251 251 alt : str
252 252 The alt text to use for the HTML.
253 253 """
254 254 base64_data = latex_to_png(s, encode=True).decode('ascii')
255 255 if base64_data:
256 256 return _data_uri_template_png % (base64_data, alt)
257 257
258 258
@@ -1,126 +1,125 b''
1 1 """Find files and directories which IPython uses.
2 2 """
3 3 import os.path
4 import shutil
5 4 import tempfile
6 5 from warnings import warn
7 6
8 7 import IPython
9 8 from IPython.utils.importstring import import_item
10 9 from IPython.utils.path import (
11 10 get_home_dir,
12 11 get_xdg_dir,
13 12 get_xdg_cache_dir,
14 13 compress_user,
15 14 _writable_dir,
16 15 ensure_dir_exists,
17 16 )
18 17
19 18
20 19 def get_ipython_dir() -> str:
21 20 """Get the IPython directory for this platform and user.
22 21
23 22 This uses the logic in `get_home_dir` to find the home directory
24 23 and then adds .ipython to the end of the path.
25 24 """
26 25
27 26 env = os.environ
28 27 pjoin = os.path.join
29 28
30 29
31 30 ipdir_def = '.ipython'
32 31
33 32 home_dir = get_home_dir()
34 33 xdg_dir = get_xdg_dir()
35 34
36 35 if 'IPYTHON_DIR' in env:
37 36 warn('The environment variable IPYTHON_DIR is deprecated since IPython 3.0. '
38 37 'Please use IPYTHONDIR instead.', DeprecationWarning)
39 38 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
40 39 if ipdir is None:
41 40 # not set explicitly, use ~/.ipython
42 41 ipdir = pjoin(home_dir, ipdir_def)
43 42 if xdg_dir:
44 43 # Several IPython versions (up to 1.x) defaulted to .config/ipython
45 44 # on Linux. We have decided to go back to using .ipython everywhere
46 45 xdg_ipdir = pjoin(xdg_dir, 'ipython')
47 46
48 47 if _writable_dir(xdg_ipdir):
49 48 cu = compress_user
50 49 if os.path.exists(ipdir):
51 50 warn(('Ignoring {0} in favour of {1}. Remove {0} to '
52 51 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
53 52 elif os.path.islink(xdg_ipdir):
54 53 warn(('{0} is deprecated. Move link to {1} to '
55 54 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
56 55 else:
57 56 ipdir = xdg_ipdir
58 57
59 58 ipdir = os.path.normpath(os.path.expanduser(ipdir))
60 59
61 60 if os.path.exists(ipdir) and not _writable_dir(ipdir):
62 61 # ipdir exists, but is not writable
63 62 warn("IPython dir '{0}' is not a writable location,"
64 63 " using a temp directory.".format(ipdir))
65 64 ipdir = tempfile.mkdtemp()
66 65 elif not os.path.exists(ipdir):
67 66 parent = os.path.dirname(ipdir)
68 67 if not _writable_dir(parent):
69 68 # ipdir does not exist and parent isn't writable
70 69 warn("IPython parent '{0}' is not a writable location,"
71 70 " using a temp directory.".format(parent))
72 71 ipdir = tempfile.mkdtemp()
73 72 else:
74 73 os.makedirs(ipdir, exist_ok=True)
75 74 assert isinstance(ipdir, str), "all path manipulation should be str(unicode), but are not."
76 75 return ipdir
77 76
78 77
79 78 def get_ipython_cache_dir() -> str:
80 79 """Get the cache directory it is created if it does not exist."""
81 80 xdgdir = get_xdg_cache_dir()
82 81 if xdgdir is None:
83 82 return get_ipython_dir()
84 83 ipdir = os.path.join(xdgdir, "ipython")
85 84 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
86 85 ensure_dir_exists(ipdir)
87 86 elif not _writable_dir(xdgdir):
88 87 return get_ipython_dir()
89 88
90 89 return ipdir
91 90
92 91
93 92 def get_ipython_package_dir() -> str:
94 93 """Get the base directory where IPython itself is installed."""
95 94 ipdir = os.path.dirname(IPython.__file__)
96 95 assert isinstance(ipdir, str)
97 96 return ipdir
98 97
99 98
100 99 def get_ipython_module_path(module_str):
101 100 """Find the path to an IPython module in this version of IPython.
102 101
103 102 This will always find the version of the module that is in this importable
104 103 IPython package. This will always return the path to the ``.py``
105 104 version of the module.
106 105 """
107 106 if module_str == 'IPython':
108 107 return os.path.join(get_ipython_package_dir(), '__init__.py')
109 108 mod = import_item(module_str)
110 109 the_path = mod.__file__.replace('.pyc', '.py')
111 110 the_path = the_path.replace('.pyo', '.py')
112 111 return the_path
113 112
114 113
115 114 def locate_profile(profile='default'):
116 115 """Find the path to the folder associated with a given profile.
117 116
118 117 I.e. find $IPYTHONDIR/profile_whatever.
119 118 """
120 119 from IPython.core.profiledir import ProfileDir, ProfileDirError
121 120 try:
122 121 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
123 122 except ProfileDirError as e:
124 123 # IOError makes more sense when people are expecting a path
125 124 raise IOError("Couldn't find profile %r" % profile) from e
126 125 return pd.location
@@ -1,202 +1,201 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Decorators for labeling test objects.
3 3
4 4 Decorators that merely return a modified version of the original function
5 5 object are straightforward. Decorators that return a new function object need
6 6 to use nose.tools.make_decorator(original_function)(decorator) in returning the
7 7 decorator, in order to preserve metadata such as function name, setup and
8 8 teardown functions and so on - see nose.tools for more information.
9 9
10 10 This module provides a set of useful decorators meant to be ready to use in
11 11 your own tests. See the bottom of the file for the ready-made ones, and if you
12 12 find yourself writing a new one that may be of generic use, add it here.
13 13
14 14 Included decorators:
15 15
16 16
17 17 Lightweight testing that remains unittest-compatible.
18 18
19 19 - An @as_unittest decorator can be used to tag any normal parameter-less
20 20 function as a unittest TestCase. Then, both nose and normal unittest will
21 21 recognize it as such. This will make it easier to migrate away from Nose if
22 22 we ever need/want to while maintaining very lightweight tests.
23 23
24 24 NOTE: This file contains IPython-specific decorators. Using the machinery in
25 25 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
26 26 available, OR use equivalent code in IPython.external._decorators, which
27 27 we've copied verbatim from numpy.
28 28
29 29 """
30 30
31 31 # Copyright (c) IPython Development Team.
32 32 # Distributed under the terms of the Modified BSD License.
33 33
34 34 import os
35 35 import shutil
36 36 import sys
37 37 import tempfile
38 38 import unittest
39 import warnings
40 39 from importlib import import_module
41 40
42 41 from decorator import decorator
43 42
44 43 # Expose the unittest-driven decorators
45 44 from .ipunittest import ipdoctest, ipdocstring
46 45
47 46 #-----------------------------------------------------------------------------
48 47 # Classes and functions
49 48 #-----------------------------------------------------------------------------
50 49
51 50 # Simple example of the basic idea
52 51 def as_unittest(func):
53 52 """Decorator to make a simple function into a normal test via unittest."""
54 53 class Tester(unittest.TestCase):
55 54 def test(self):
56 55 func()
57 56
58 57 Tester.__name__ = func.__name__
59 58
60 59 return Tester
61 60
62 61 # Utility functions
63 62
64 63
65 64 def skipif(skip_condition, msg=None):
66 65 """Make function raise SkipTest exception if skip_condition is true
67 66
68 67 Parameters
69 68 ----------
70 69
71 70 skip_condition : bool or callable
72 71 Flag to determine whether to skip test. If the condition is a
73 72 callable, it is used at runtime to dynamically make the decision. This
74 73 is useful for tests that may require costly imports, to delay the cost
75 74 until the test suite is actually executed.
76 75 msg : string
77 76 Message to give on raising a SkipTest exception.
78 77
79 78 Returns
80 79 -------
81 80 decorator : function
82 81 Decorator, which, when applied to a function, causes SkipTest
83 82 to be raised when the skip_condition was True, and the function
84 83 to be called normally otherwise.
85 84 """
86 85 if msg is None:
87 86 msg = "Test skipped due to test condition."
88 87
89 88 import pytest
90 89
91 90 assert isinstance(skip_condition, bool)
92 91 return pytest.mark.skipif(skip_condition, reason=msg)
93 92
94 93
95 94 # A version with the condition set to true, common case just to attach a message
96 95 # to a skip decorator
97 96 def skip(msg=None):
98 97 """Decorator factory - mark a test function for skipping from test suite.
99 98
100 99 Parameters
101 100 ----------
102 101 msg : string
103 102 Optional message to be added.
104 103
105 104 Returns
106 105 -------
107 106 decorator : function
108 107 Decorator, which, when applied to a function, causes SkipTest
109 108 to be raised, with the optional message added.
110 109 """
111 110 if msg and not isinstance(msg, str):
112 111 raise ValueError('invalid object passed to `@skip` decorator, did you '
113 112 'meant `@skip()` with brackets ?')
114 113 return skipif(True, msg)
115 114
116 115
117 116 def onlyif(condition, msg):
118 117 """The reverse from skipif, see skipif for details."""
119 118
120 119 return skipif(not condition, msg)
121 120
122 121 #-----------------------------------------------------------------------------
123 122 # Utility functions for decorators
124 123 def module_not_available(module):
125 124 """Can module be imported? Returns true if module does NOT import.
126 125
127 126 This is used to make a decorator to skip tests that require module to be
128 127 available, but delay the 'import numpy' to test execution time.
129 128 """
130 129 try:
131 130 mod = import_module(module)
132 131 mod_not_avail = False
133 132 except ImportError:
134 133 mod_not_avail = True
135 134
136 135 return mod_not_avail
137 136
138 137
139 138 #-----------------------------------------------------------------------------
140 139 # Decorators for public use
141 140
142 141 # Decorators to skip certain tests on specific platforms.
143 142 skip_win32 = skipif(sys.platform == 'win32',
144 143 "This test does not run under Windows")
145 144 skip_linux = skipif(sys.platform.startswith('linux'),
146 145 "This test does not run under Linux")
147 146 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
148 147
149 148
150 149 # Decorators to skip tests if not on specific platforms.
151 150 skip_if_not_win32 = skipif(sys.platform != 'win32',
152 151 "This test only runs under Windows")
153 152 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
154 153 "This test only runs under Linux")
155 154
156 155 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
157 156 os.environ.get('DISPLAY', '') == '')
158 157 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
159 158
160 159 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
161 160
162 161 # Other skip decorators
163 162
164 163 # generic skip without module
165 164 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
166 165
167 166 skipif_not_numpy = skip_without('numpy')
168 167
169 168 skipif_not_matplotlib = skip_without('matplotlib')
170 169
171 170 # A null 'decorator', useful to make more readable code that needs to pick
172 171 # between different decorators based on OS or other conditions
173 172 null_deco = lambda f: f
174 173
175 174 # Some tests only run where we can use unicode paths. Note that we can't just
176 175 # check os.path.supports_unicode_filenames, which is always False on Linux.
177 176 try:
178 177 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
179 178 except UnicodeEncodeError:
180 179 unicode_paths = False
181 180 else:
182 181 unicode_paths = True
183 182 f.close()
184 183
185 184 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
186 185 "where we can use unicode in filenames."))
187 186
188 187
189 188 def onlyif_cmds_exist(*commands):
190 189 """
191 190 Decorator to skip test when at least one of `commands` is not found.
192 191 """
193 192 assert (
194 193 os.environ.get("IPTEST_WORKING_DIR", None) is None
195 194 ), "iptest deprecated since IPython 8.0"
196 195 for cmd in commands:
197 196 reason = f"This test runs only if command '{cmd}' is installed"
198 197 if not shutil.which(cmd):
199 198 import pytest
200 199
201 200 return pytest.mark.skip(reason=reason)
202 201 return null_deco
@@ -1,115 +1,114 b''
1 1 """Global IPython app to support test running.
2 2
3 3 We must start our own ipython object and heavily muck with it so that all the
4 4 modifications IPython makes to system behavior don't send the doctest machinery
5 5 into a fit. This code should be considered a gross hack, but it gets the job
6 6 done.
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 builtins as builtin_mod
13 13 import sys
14 14 import types
15 import warnings
16 15
17 16 from pathlib import Path
18 17
19 18 from . import tools
20 19
21 20 from IPython.core import page
22 21 from IPython.utils import io
23 22 from IPython.terminal.interactiveshell import TerminalInteractiveShell
24 23
25 24
26 25 def get_ipython():
27 26 # This will get replaced by the real thing once we start IPython below
28 27 return start_ipython()
29 28
30 29
31 30 # A couple of methods to override those in the running IPython to interact
32 31 # better with doctest (doctest captures on raw stdout, so we need to direct
33 32 # various types of output there otherwise it will miss them).
34 33
35 34 def xsys(self, cmd):
36 35 """Replace the default system call with a capturing one for doctest.
37 36 """
38 37 # We use getoutput, but we need to strip it because pexpect captures
39 38 # the trailing newline differently from commands.getoutput
40 39 print(self.getoutput(cmd, split=False, depth=1).rstrip(), end='', file=sys.stdout)
41 40 sys.stdout.flush()
42 41
43 42
44 43 def _showtraceback(self, etype, evalue, stb):
45 44 """Print the traceback purely on stdout for doctest to capture it.
46 45 """
47 46 print(self.InteractiveTB.stb2text(stb), file=sys.stdout)
48 47
49 48
50 49 def start_ipython():
51 50 """Start a global IPython shell, which we need for IPython-specific syntax.
52 51 """
53 52 global get_ipython
54 53
55 54 # This function should only ever run once!
56 55 if hasattr(start_ipython, 'already_called'):
57 56 return
58 57 start_ipython.already_called = True
59 58
60 59 # Store certain global objects that IPython modifies
61 60 _displayhook = sys.displayhook
62 61 _excepthook = sys.excepthook
63 62 _main = sys.modules.get('__main__')
64 63
65 64 # Create custom argv and namespaces for our IPython to be test-friendly
66 65 config = tools.default_config()
67 66 config.TerminalInteractiveShell.simple_prompt = True
68 67
69 68 # Create and initialize our test-friendly IPython instance.
70 69 shell = TerminalInteractiveShell.instance(config=config,
71 70 )
72 71
73 72 # A few more tweaks needed for playing nicely with doctests...
74 73
75 74 # remove history file
76 75 shell.tempfiles.append(Path(config.HistoryManager.hist_file))
77 76
78 77 # These traps are normally only active for interactive use, set them
79 78 # permanently since we'll be mocking interactive sessions.
80 79 shell.builtin_trap.activate()
81 80
82 81 # Modify the IPython system call with one that uses getoutput, so that we
83 82 # can capture subcommands and print them to Python's stdout, otherwise the
84 83 # doctest machinery would miss them.
85 84 shell.system = types.MethodType(xsys, shell)
86 85
87 86 shell._showtraceback = types.MethodType(_showtraceback, shell)
88 87
89 88 # IPython is ready, now clean up some global state...
90 89
91 90 # Deactivate the various python system hooks added by ipython for
92 91 # interactive convenience so we don't confuse the doctest system
93 92 sys.modules['__main__'] = _main
94 93 sys.displayhook = _displayhook
95 94 sys.excepthook = _excepthook
96 95
97 96 # So that ipython magics and aliases can be doctested (they work by making
98 97 # a call into a global _ip object). Also make the top-level get_ipython
99 98 # now return this without recursively calling here again.
100 99 _ip = shell
101 100 get_ipython = _ip.get_ipython
102 101 builtin_mod._ip = _ip
103 102 builtin_mod.ip = _ip
104 103 builtin_mod.get_ipython = get_ipython
105 104
106 105 # Override paging, so we don't require user interaction during the tests.
107 106 def nopage(strng, start=0, screen_lines=0, pager_cmd=None):
108 107 if isinstance(strng, dict):
109 108 strng = strng.get('text/plain', '')
110 109 print(strng)
111 110
112 111 page.orig_page = page.pager_page
113 112 page.pager_page = nopage
114 113
115 114 return _ip
@@ -1,300 +1,299 b''
1 1 """Nose Plugin that supports IPython doctests.
2 2
3 3 Limitations:
4 4
5 5 - When generating examples for use as doctests, make sure that you have
6 6 pretty-printing OFF. This can be done either by setting the
7 7 ``PlainTextFormatter.pprint`` option in your configuration file to False, or
8 8 by interactively disabling it with %Pprint. This is required so that IPython
9 9 output matches that of normal Python, which is used by doctest for internal
10 10 execution.
11 11
12 12 - Do not rely on specific prompt numbers for results (such as using
13 13 '_34==True', for example). For IPython tests run via an external process the
14 14 prompt numbers may be different, and IPython tests run as normal python code
15 15 won't even have these special _NN variables set at all.
16 16 """
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Module imports
20 20
21 21 # From the standard library
22 22 import doctest
23 23 import logging
24 import os
25 24 import re
26 25
27 26 from testpath import modified_env
28 27
29 28 #-----------------------------------------------------------------------------
30 29 # Module globals and other constants
31 30 #-----------------------------------------------------------------------------
32 31
33 32 log = logging.getLogger(__name__)
34 33
35 34
36 35 #-----------------------------------------------------------------------------
37 36 # Classes and functions
38 37 #-----------------------------------------------------------------------------
39 38
40 39
41 40 class DocTestFinder(doctest.DocTestFinder):
42 41 def _get_test(self, obj, name, module, globs, source_lines):
43 42 test = super()._get_test(obj, name, module, globs, source_lines)
44 43
45 44 if bool(getattr(obj, "__skip_doctest__", False)) and test is not None:
46 45 for example in test.examples:
47 46 example.options[doctest.SKIP] = True
48 47
49 48 return test
50 49
51 50
52 51 class IPDoctestOutputChecker(doctest.OutputChecker):
53 52 """Second-chance checker with support for random tests.
54 53
55 54 If the default comparison doesn't pass, this checker looks in the expected
56 55 output string for flags that tell us to ignore the output.
57 56 """
58 57
59 58 random_re = re.compile(r'#\s*random\s+')
60 59
61 60 def check_output(self, want, got, optionflags):
62 61 """Check output, accepting special markers embedded in the output.
63 62
64 63 If the output didn't pass the default validation but the special string
65 64 '#random' is included, we accept it."""
66 65
67 66 # Let the original tester verify first, in case people have valid tests
68 67 # that happen to have a comment saying '#random' embedded in.
69 68 ret = doctest.OutputChecker.check_output(self, want, got,
70 69 optionflags)
71 70 if not ret and self.random_re.search(want):
72 71 #print >> sys.stderr, 'RANDOM OK:',want # dbg
73 72 return True
74 73
75 74 return ret
76 75
77 76
78 77 # A simple subclassing of the original with a different class name, so we can
79 78 # distinguish and treat differently IPython examples from pure python ones.
80 79 class IPExample(doctest.Example): pass
81 80
82 81
83 82 class IPDocTestParser(doctest.DocTestParser):
84 83 """
85 84 A class used to parse strings containing doctest examples.
86 85
87 86 Note: This is a version modified to properly recognize IPython input and
88 87 convert any IPython examples into valid Python ones.
89 88 """
90 89 # This regular expression is used to find doctest examples in a
91 90 # string. It defines three groups: `source` is the source code
92 91 # (including leading indentation and prompts); `indent` is the
93 92 # indentation of the first (PS1) line of the source code; and
94 93 # `want` is the expected output (including leading indentation).
95 94
96 95 # Classic Python prompts or default IPython ones
97 96 _PS1_PY = r'>>>'
98 97 _PS2_PY = r'\.\.\.'
99 98
100 99 _PS1_IP = r'In\ \[\d+\]:'
101 100 _PS2_IP = r'\ \ \ \.\.\.+:'
102 101
103 102 _RE_TPL = r'''
104 103 # Source consists of a PS1 line followed by zero or more PS2 lines.
105 104 (?P<source>
106 105 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
107 106 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
108 107 \n? # a newline
109 108 # Want consists of any non-blank lines that do not start with PS1.
110 109 (?P<want> (?:(?![ ]*$) # Not a blank line
111 110 (?![ ]*%s) # Not a line starting with PS1
112 111 (?![ ]*%s) # Not a line starting with PS2
113 112 .*$\n? # But any other line
114 113 )*)
115 114 '''
116 115
117 116 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
118 117 re.MULTILINE | re.VERBOSE)
119 118
120 119 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
121 120 re.MULTILINE | re.VERBOSE)
122 121
123 122 # Mark a test as being fully random. In this case, we simply append the
124 123 # random marker ('#random') to each individual example's output. This way
125 124 # we don't need to modify any other code.
126 125 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
127 126
128 127 def ip2py(self,source):
129 128 """Convert input IPython source into valid Python."""
130 129 block = _ip.input_transformer_manager.transform_cell(source)
131 130 if len(block.splitlines()) == 1:
132 131 return _ip.prefilter(block)
133 132 else:
134 133 return block
135 134
136 135 def parse(self, string, name='<string>'):
137 136 """
138 137 Divide the given string into examples and intervening text,
139 138 and return them as a list of alternating Examples and strings.
140 139 Line numbers for the Examples are 0-based. The optional
141 140 argument `name` is a name identifying this string, and is only
142 141 used for error messages.
143 142 """
144 143
145 144 #print 'Parse string:\n',string # dbg
146 145
147 146 string = string.expandtabs()
148 147 # If all lines begin with the same indentation, then strip it.
149 148 min_indent = self._min_indent(string)
150 149 if min_indent > 0:
151 150 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
152 151
153 152 output = []
154 153 charno, lineno = 0, 0
155 154
156 155 # We make 'all random' tests by adding the '# random' mark to every
157 156 # block of output in the test.
158 157 if self._RANDOM_TEST.search(string):
159 158 random_marker = '\n# random'
160 159 else:
161 160 random_marker = ''
162 161
163 162 # Whether to convert the input from ipython to python syntax
164 163 ip2py = False
165 164 # Find all doctest examples in the string. First, try them as Python
166 165 # examples, then as IPython ones
167 166 terms = list(self._EXAMPLE_RE_PY.finditer(string))
168 167 if terms:
169 168 # Normal Python example
170 169 Example = doctest.Example
171 170 else:
172 171 # It's an ipython example.
173 172 terms = list(self._EXAMPLE_RE_IP.finditer(string))
174 173 Example = IPExample
175 174 ip2py = True
176 175
177 176 for m in terms:
178 177 # Add the pre-example text to `output`.
179 178 output.append(string[charno:m.start()])
180 179 # Update lineno (lines before this example)
181 180 lineno += string.count('\n', charno, m.start())
182 181 # Extract info from the regexp match.
183 182 (source, options, want, exc_msg) = \
184 183 self._parse_example(m, name, lineno,ip2py)
185 184
186 185 # Append the random-output marker (it defaults to empty in most
187 186 # cases, it's only non-empty for 'all-random' tests):
188 187 want += random_marker
189 188
190 189 # Create an Example, and add it to the list.
191 190 if not self._IS_BLANK_OR_COMMENT(source):
192 191 output.append(Example(source, want, exc_msg,
193 192 lineno=lineno,
194 193 indent=min_indent+len(m.group('indent')),
195 194 options=options))
196 195 # Update lineno (lines inside this example)
197 196 lineno += string.count('\n', m.start(), m.end())
198 197 # Update charno.
199 198 charno = m.end()
200 199 # Add any remaining post-example text to `output`.
201 200 output.append(string[charno:])
202 201 return output
203 202
204 203 def _parse_example(self, m, name, lineno,ip2py=False):
205 204 """
206 205 Given a regular expression match from `_EXAMPLE_RE` (`m`),
207 206 return a pair `(source, want)`, where `source` is the matched
208 207 example's source code (with prompts and indentation stripped);
209 208 and `want` is the example's expected output (with indentation
210 209 stripped).
211 210
212 211 `name` is the string's name, and `lineno` is the line number
213 212 where the example starts; both are used for error messages.
214 213
215 214 Optional:
216 215 `ip2py`: if true, filter the input via IPython to convert the syntax
217 216 into valid python.
218 217 """
219 218
220 219 # Get the example's indentation level.
221 220 indent = len(m.group('indent'))
222 221
223 222 # Divide source into lines; check that they're properly
224 223 # indented; and then strip their indentation & prompts.
225 224 source_lines = m.group('source').split('\n')
226 225
227 226 # We're using variable-length input prompts
228 227 ps1 = m.group('ps1')
229 228 ps2 = m.group('ps2')
230 229 ps1_len = len(ps1)
231 230
232 231 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
233 232 if ps2:
234 233 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
235 234
236 235 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
237 236
238 237 if ip2py:
239 238 # Convert source input from IPython into valid Python syntax
240 239 source = self.ip2py(source)
241 240
242 241 # Divide want into lines; check that it's properly indented; and
243 242 # then strip the indentation. Spaces before the last newline should
244 243 # be preserved, so plain rstrip() isn't good enough.
245 244 want = m.group('want')
246 245 want_lines = want.split('\n')
247 246 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
248 247 del want_lines[-1] # forget final newline & spaces after it
249 248 self._check_prefix(want_lines, ' '*indent, name,
250 249 lineno + len(source_lines))
251 250
252 251 # Remove ipython output prompt that might be present in the first line
253 252 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
254 253
255 254 want = '\n'.join([wl[indent:] for wl in want_lines])
256 255
257 256 # If `want` contains a traceback message, then extract it.
258 257 m = self._EXCEPTION_RE.match(want)
259 258 if m:
260 259 exc_msg = m.group('msg')
261 260 else:
262 261 exc_msg = None
263 262
264 263 # Extract options from the source.
265 264 options = self._find_options(source, name, lineno)
266 265
267 266 return source, options, want, exc_msg
268 267
269 268 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
270 269 """
271 270 Given the lines of a source string (including prompts and
272 271 leading indentation), check to make sure that every prompt is
273 272 followed by a space character. If any line is not followed by
274 273 a space character, then raise ValueError.
275 274
276 275 Note: IPython-modified version which takes the input prompt length as a
277 276 parameter, so that prompts of variable length can be dealt with.
278 277 """
279 278 space_idx = indent+ps1_len
280 279 min_len = space_idx+1
281 280 for i, line in enumerate(lines):
282 281 if len(line) >= min_len and line[space_idx] != ' ':
283 282 raise ValueError('line %r of the docstring for %s '
284 283 'lacks blank after %s: %r' %
285 284 (lineno+i+1, name,
286 285 line[indent:space_idx], line))
287 286
288 287
289 288 SKIP = doctest.register_optionflag('SKIP')
290 289
291 290
292 291 class IPDocTestRunner(doctest.DocTestRunner,object):
293 292 """Test runner that synchronizes the IPython namespace with test globals.
294 293 """
295 294
296 295 def run(self, test, compileflags=None, out=None, clear_globs=True):
297 296 # Override terminal size to standardise traceback format
298 297 with modified_env({'COLUMNS': '80', 'LINES': '24'}):
299 298 return super(IPDocTestRunner,self).run(test,
300 299 compileflags,out,clear_globs)
@@ -1,67 +1,66 b''
1 1 """
2 2 Test that CVEs stay fixed.
3 3 """
4 4
5 5 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
6 6 from pathlib import Path
7 7 import random
8 8 import sys
9 9 import os
10 10 import string
11 11 import subprocess
12 import time
13 12
14 13
15 14 def test_cve_2022_21699():
16 15 """
17 16 Here we test CVE-2022-21699.
18 17
19 18 We create a temporary directory, cd into it.
20 19 Make a profile file that should not be executed and start IPython in a subprocess,
21 20 checking for the value.
22 21
23 22
24 23
25 24 """
26 25
27 26 dangerous_profile_dir = Path("profile_default")
28 27
29 28 dangerous_startup_dir = dangerous_profile_dir / "startup"
30 29 dangerous_expected = "CVE-2022-21699-" + "".join(
31 30 [random.choice(string.ascii_letters) for i in range(10)]
32 31 )
33 32
34 33 with TemporaryWorkingDirectory() as t:
35 34 dangerous_startup_dir.mkdir(parents=True)
36 35 (dangerous_startup_dir / "foo.py").write_text(
37 36 f'print("{dangerous_expected}")', encoding="utf-8"
38 37 )
39 38 # 1 sec to make sure FS is flushed.
40 39 # time.sleep(1)
41 40 cmd = [sys.executable, "-m", "IPython"]
42 41 env = os.environ.copy()
43 42 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
44 43
45 44 # First we fake old behavior, making sure the profile is/was actually dangerous
46 45 p_dangerous = subprocess.Popen(
47 46 cmd + [f"--profile-dir={dangerous_profile_dir}"],
48 47 env=env,
49 48 stdin=subprocess.PIPE,
50 49 stdout=subprocess.PIPE,
51 50 stderr=subprocess.PIPE,
52 51 )
53 52 out_dangerous, err_dangerouns = p_dangerous.communicate(b"exit\r")
54 53 assert dangerous_expected in out_dangerous.decode()
55 54
56 55 # Now that we know it _would_ have been dangerous, we test it's not loaded
57 56 p = subprocess.Popen(
58 57 cmd,
59 58 env=env,
60 59 stdin=subprocess.PIPE,
61 60 stdout=subprocess.PIPE,
62 61 stderr=subprocess.PIPE,
63 62 )
64 63 out, err = p.communicate(b"exit\r")
65 64 assert b"IPython" in out
66 65 assert dangerous_expected not in out.decode()
67 66 assert err == b""
@@ -1,157 +1,156 b''
1 1 # encoding: utf-8
2 2 """
3 3 IO related utilities.
4 4 """
5 5
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9
10 10
11 11 import atexit
12 12 import os
13 13 import sys
14 14 import tempfile
15 import warnings
16 15 from pathlib import Path
17 16 from warnings import warn
18 17
19 18 from IPython.utils.decorators import undoc
20 19 from .capture import CapturedIO, capture_output
21 20
22 21 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
23 22 devnull = open(os.devnull, "w", encoding="utf-8")
24 23 atexit.register(devnull.close)
25 24
26 25
27 26 class Tee(object):
28 27 """A class to duplicate an output stream to stdout/err.
29 28
30 29 This works in a manner very similar to the Unix 'tee' command.
31 30
32 31 When the object is closed or deleted, it closes the original file given to
33 32 it for duplication.
34 33 """
35 34 # Inspired by:
36 35 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
37 36
38 37 def __init__(self, file_or_name, mode="w", channel='stdout'):
39 38 """Construct a new Tee object.
40 39
41 40 Parameters
42 41 ----------
43 42 file_or_name : filename or open filehandle (writable)
44 43 File that will be duplicated
45 44 mode : optional, valid mode for open().
46 45 If a filename was give, open with this mode.
47 46 channel : str, one of ['stdout', 'stderr']
48 47 """
49 48 if channel not in ['stdout', 'stderr']:
50 49 raise ValueError('Invalid channel spec %s' % channel)
51 50
52 51 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
53 52 self.file = file_or_name
54 53 else:
55 54 encoding = None if "b" in mode else "utf-8"
56 55 self.file = open(file_or_name, mode, encoding=encoding)
57 56 self.channel = channel
58 57 self.ostream = getattr(sys, channel)
59 58 setattr(sys, channel, self)
60 59 self._closed = False
61 60
62 61 def close(self):
63 62 """Close the file and restore the channel."""
64 63 self.flush()
65 64 setattr(sys, self.channel, self.ostream)
66 65 self.file.close()
67 66 self._closed = True
68 67
69 68 def write(self, data):
70 69 """Write data to both channels."""
71 70 self.file.write(data)
72 71 self.ostream.write(data)
73 72 self.ostream.flush()
74 73
75 74 def flush(self):
76 75 """Flush both channels."""
77 76 self.file.flush()
78 77 self.ostream.flush()
79 78
80 79 def __del__(self):
81 80 if not self._closed:
82 81 self.close()
83 82
84 83
85 84 def ask_yes_no(prompt, default=None, interrupt=None):
86 85 """Asks a question and returns a boolean (y/n) answer.
87 86
88 87 If default is given (one of 'y','n'), it is used if the user input is
89 88 empty. If interrupt is given (one of 'y','n'), it is used if the user
90 89 presses Ctrl-C. Otherwise the question is repeated until an answer is
91 90 given.
92 91
93 92 An EOF is treated as the default answer. If there is no default, an
94 93 exception is raised to prevent infinite loops.
95 94
96 95 Valid answers are: y/yes/n/no (match is not case sensitive)."""
97 96
98 97 answers = {'y':True,'n':False,'yes':True,'no':False}
99 98 ans = None
100 99 while ans not in answers.keys():
101 100 try:
102 101 ans = input(prompt+' ').lower()
103 102 if not ans: # response was an empty string
104 103 ans = default
105 104 except KeyboardInterrupt:
106 105 if interrupt:
107 106 ans = interrupt
108 107 print("\r")
109 108 except EOFError:
110 109 if default in answers.keys():
111 110 ans = default
112 111 print()
113 112 else:
114 113 raise
115 114
116 115 return answers[ans]
117 116
118 117
119 118 def temp_pyfile(src, ext='.py'):
120 119 """Make a temporary python file, return filename and filehandle.
121 120
122 121 Parameters
123 122 ----------
124 123 src : string or list of strings (no need for ending newlines if list)
125 124 Source code to be written to the file.
126 125 ext : optional, string
127 126 Extension for the generated file.
128 127
129 128 Returns
130 129 -------
131 130 (filename, open filehandle)
132 131 It is the caller's responsibility to close the open file and unlink it.
133 132 """
134 133 fname = tempfile.mkstemp(ext)[1]
135 134 with open(Path(fname), "w", encoding="utf-8") as f:
136 135 f.write(src)
137 136 f.flush()
138 137 return fname
139 138
140 139
141 140 @undoc
142 141 def raw_print(*args, **kw):
143 142 """DEPRECATED: Raw print to sys.__stdout__, otherwise identical interface to print()."""
144 143 warn("IPython.utils.io.raw_print has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
145 144
146 145 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
147 146 file=sys.__stdout__)
148 147 sys.__stdout__.flush()
149 148
150 149 @undoc
151 150 def raw_print_err(*args, **kw):
152 151 """DEPRECATED: Raw print to sys.__stderr__, otherwise identical interface to print()."""
153 152 warn("IPython.utils.io.raw_print_err has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
154 153
155 154 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
156 155 file=sys.__stderr__)
157 156 sys.__stderr__.flush()
@@ -1,71 +1,70 b''
1 1 """Utility functions for finding modules
2 2
3 3 Utility functions for finding modules on sys.path.
4 4
5 5 """
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (c) 2011, the IPython Development Team.
8 8 #
9 9 # Distributed under the terms of the Modified BSD License.
10 10 #
11 11 # The full license is in the file COPYING.txt, distributed with this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 # Stdlib imports
19 19 import importlib
20 import os
21 20 import sys
22 21
23 22 # Third-party imports
24 23
25 24 # Our own imports
26 25
27 26
28 27 #-----------------------------------------------------------------------------
29 28 # Globals and constants
30 29 #-----------------------------------------------------------------------------
31 30
32 31 #-----------------------------------------------------------------------------
33 32 # Local utilities
34 33 #-----------------------------------------------------------------------------
35 34
36 35 #-----------------------------------------------------------------------------
37 36 # Classes and functions
38 37 #-----------------------------------------------------------------------------
39 38
40 39 def find_mod(module_name):
41 40 """
42 41 Find module `module_name` on sys.path, and return the path to module `module_name`.
43 42
44 43 - If `module_name` refers to a module directory, then return path to __init__ file.
45 44 - If `module_name` is a directory without an __init__file, return None.
46 45 - If module is missing or does not have a `.py` or `.pyw` extension, return None.
47 46 - Note that we are not interested in running bytecode.
48 47 - Otherwise, return the fill path of the module.
49 48
50 49 Parameters
51 50 ----------
52 51 module_name : str
53 52
54 53 Returns
55 54 -------
56 55 module_path : str
57 56 Path to module `module_name`, its __init__.py, or None,
58 57 depending on above conditions.
59 58 """
60 59 spec = importlib.util.find_spec(module_name)
61 60 module_path = spec.origin
62 61 if module_path is None:
63 62 if spec.loader in sys.meta_path:
64 63 return spec.loader
65 64 return None
66 65 else:
67 66 split_path = module_path.split(".")
68 67 if split_path[-1] in ["py", "pyw"]:
69 68 return module_path
70 69 else:
71 70 return None
@@ -1,393 +1,391 b''
1 1 # encoding: utf-8
2 2 """
3 3 Utilities for path handling.
4 4 """
5 5
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9 import os
10 10 import sys
11 11 import errno
12 12 import shutil
13 13 import random
14 14 import glob
15 from warnings import warn
16 15
17 16 from IPython.utils.process import system
18 from IPython.utils.decorators import undoc
19 17
20 18 #-----------------------------------------------------------------------------
21 19 # Code
22 20 #-----------------------------------------------------------------------------
23 21 fs_encoding = sys.getfilesystemencoding()
24 22
25 23 def _writable_dir(path):
26 24 """Whether `path` is a directory, to which the user has write access."""
27 25 return os.path.isdir(path) and os.access(path, os.W_OK)
28 26
29 27 if sys.platform == 'win32':
30 28 def _get_long_path_name(path):
31 29 """Get a long path name (expand ~) on Windows using ctypes.
32 30
33 31 Examples
34 32 --------
35 33
36 34 >>> get_long_path_name('c:\\\\docume~1')
37 35 'c:\\\\Documents and Settings'
38 36
39 37 """
40 38 try:
41 39 import ctypes
42 40 except ImportError as e:
43 41 raise ImportError('you need to have ctypes installed for this to work') from e
44 42 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
45 43 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
46 44 ctypes.c_uint ]
47 45
48 46 buf = ctypes.create_unicode_buffer(260)
49 47 rv = _GetLongPathName(path, buf, 260)
50 48 if rv == 0 or rv > 260:
51 49 return path
52 50 else:
53 51 return buf.value
54 52 else:
55 53 def _get_long_path_name(path):
56 54 """Dummy no-op."""
57 55 return path
58 56
59 57
60 58
61 59 def get_long_path_name(path):
62 60 """Expand a path into its long form.
63 61
64 62 On Windows this expands any ~ in the paths. On other platforms, it is
65 63 a null operation.
66 64 """
67 65 return _get_long_path_name(path)
68 66
69 67
70 68 def compress_user(path):
71 69 """Reverse of :func:`os.path.expanduser`
72 70 """
73 71 home = os.path.expanduser('~')
74 72 if path.startswith(home):
75 73 path = "~" + path[len(home):]
76 74 return path
77 75
78 76 def get_py_filename(name):
79 77 """Return a valid python filename in the current directory.
80 78
81 79 If the given name is not a file, it adds '.py' and searches again.
82 80 Raises IOError with an informative message if the file isn't found.
83 81 """
84 82
85 83 name = os.path.expanduser(name)
86 84 if os.path.isfile(name):
87 85 return name
88 86 if not name.endswith(".py"):
89 87 py_name = name + ".py"
90 88 if os.path.isfile(py_name):
91 89 return py_name
92 90 raise IOError("File `%r` not found." % name)
93 91
94 92
95 93 def filefind(filename: str, path_dirs=None) -> str:
96 94 """Find a file by looking through a sequence of paths.
97 95
98 96 This iterates through a sequence of paths looking for a file and returns
99 97 the full, absolute path of the first occurrence of the file. If no set of
100 98 path dirs is given, the filename is tested as is, after running through
101 99 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
102 100
103 101 filefind('myfile.txt')
104 102
105 103 will find the file in the current working dir, but::
106 104
107 105 filefind('~/myfile.txt')
108 106
109 107 Will find the file in the users home directory. This function does not
110 108 automatically try any paths, such as the cwd or the user's home directory.
111 109
112 110 Parameters
113 111 ----------
114 112 filename : str
115 113 The filename to look for.
116 114 path_dirs : str, None or sequence of str
117 115 The sequence of paths to look for the file in. If None, the filename
118 116 need to be absolute or be in the cwd. If a string, the string is
119 117 put into a sequence and the searched. If a sequence, walk through
120 118 each element and join with ``filename``, calling :func:`expandvars`
121 119 and :func:`expanduser` before testing for existence.
122 120
123 121 Returns
124 122 -------
125 123 path : str
126 124 returns absolute path to file.
127 125
128 126 Raises
129 127 ------
130 128 IOError
131 129 """
132 130
133 131 # If paths are quoted, abspath gets confused, strip them...
134 132 filename = filename.strip('"').strip("'")
135 133 # If the input is an absolute path, just check it exists
136 134 if os.path.isabs(filename) and os.path.isfile(filename):
137 135 return filename
138 136
139 137 if path_dirs is None:
140 138 path_dirs = ("",)
141 139 elif isinstance(path_dirs, str):
142 140 path_dirs = (path_dirs,)
143 141
144 142 for path in path_dirs:
145 143 if path == '.': path = os.getcwd()
146 144 testname = expand_path(os.path.join(path, filename))
147 145 if os.path.isfile(testname):
148 146 return os.path.abspath(testname)
149 147
150 148 raise IOError("File %r does not exist in any of the search paths: %r" %
151 149 (filename, path_dirs) )
152 150
153 151
154 152 class HomeDirError(Exception):
155 153 pass
156 154
157 155
158 156 def get_home_dir(require_writable=False) -> str:
159 157 """Return the 'home' directory, as a unicode string.
160 158
161 159 Uses os.path.expanduser('~'), and checks for writability.
162 160
163 161 See stdlib docs for how this is determined.
164 162 For Python <3.8, $HOME is first priority on *ALL* platforms.
165 163 For Python >=3.8 on Windows, %HOME% is no longer considered.
166 164
167 165 Parameters
168 166 ----------
169 167 require_writable : bool [default: False]
170 168 if True:
171 169 guarantees the return value is a writable directory, otherwise
172 170 raises HomeDirError
173 171 if False:
174 172 The path is resolved, but it is not guaranteed to exist or be writable.
175 173 """
176 174
177 175 homedir = os.path.expanduser('~')
178 176 # Next line will make things work even when /home/ is a symlink to
179 177 # /usr/home as it is on FreeBSD, for example
180 178 homedir = os.path.realpath(homedir)
181 179
182 180 if not _writable_dir(homedir) and os.name == 'nt':
183 181 # expanduser failed, use the registry to get the 'My Documents' folder.
184 182 try:
185 183 import winreg as wreg
186 184 with wreg.OpenKey(
187 185 wreg.HKEY_CURRENT_USER,
188 186 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
189 187 ) as key:
190 188 homedir = wreg.QueryValueEx(key,'Personal')[0]
191 189 except:
192 190 pass
193 191
194 192 if (not require_writable) or _writable_dir(homedir):
195 193 assert isinstance(homedir, str), "Homedir should be unicode not bytes"
196 194 return homedir
197 195 else:
198 196 raise HomeDirError('%s is not a writable dir, '
199 197 'set $HOME environment variable to override' % homedir)
200 198
201 199 def get_xdg_dir():
202 200 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
203 201
204 202 This is only for non-OS X posix (Linux,Unix,etc.) systems.
205 203 """
206 204
207 205 env = os.environ
208 206
209 207 if os.name == "posix":
210 208 # Linux, Unix, AIX, etc.
211 209 # use ~/.config if empty OR not set
212 210 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
213 211 if xdg and _writable_dir(xdg):
214 212 assert isinstance(xdg, str)
215 213 return xdg
216 214
217 215 return None
218 216
219 217
220 218 def get_xdg_cache_dir():
221 219 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
222 220
223 221 This is only for non-OS X posix (Linux,Unix,etc.) systems.
224 222 """
225 223
226 224 env = os.environ
227 225
228 226 if os.name == "posix":
229 227 # Linux, Unix, AIX, etc.
230 228 # use ~/.cache if empty OR not set
231 229 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
232 230 if xdg and _writable_dir(xdg):
233 231 assert isinstance(xdg, str)
234 232 return xdg
235 233
236 234 return None
237 235
238 236
239 237 def expand_path(s):
240 238 """Expand $VARS and ~names in a string, like a shell
241 239
242 240 :Examples:
243 241
244 242 In [2]: os.environ['FOO']='test'
245 243
246 244 In [3]: expand_path('variable FOO is $FOO')
247 245 Out[3]: 'variable FOO is test'
248 246 """
249 247 # This is a pretty subtle hack. When expand user is given a UNC path
250 248 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
251 249 # the $ to get (\\server\share\%username%). I think it considered $
252 250 # alone an empty var. But, we need the $ to remains there (it indicates
253 251 # a hidden share).
254 252 if os.name=='nt':
255 253 s = s.replace('$\\', 'IPYTHON_TEMP')
256 254 s = os.path.expandvars(os.path.expanduser(s))
257 255 if os.name=='nt':
258 256 s = s.replace('IPYTHON_TEMP', '$\\')
259 257 return s
260 258
261 259
262 260 def unescape_glob(string):
263 261 """Unescape glob pattern in `string`."""
264 262 def unescape(s):
265 263 for pattern in '*[]!?':
266 264 s = s.replace(r'\{0}'.format(pattern), pattern)
267 265 return s
268 266 return '\\'.join(map(unescape, string.split('\\\\')))
269 267
270 268
271 269 def shellglob(args):
272 270 """
273 271 Do glob expansion for each element in `args` and return a flattened list.
274 272
275 273 Unmatched glob pattern will remain as-is in the returned list.
276 274
277 275 """
278 276 expanded = []
279 277 # Do not unescape backslash in Windows as it is interpreted as
280 278 # path separator:
281 279 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
282 280 for a in args:
283 281 expanded.extend(glob.glob(a) or [unescape(a)])
284 282 return expanded
285 283
286 284
287 285 def target_outdated(target,deps):
288 286 """Determine whether a target is out of date.
289 287
290 288 target_outdated(target,deps) -> 1/0
291 289
292 290 deps: list of filenames which MUST exist.
293 291 target: single filename which may or may not exist.
294 292
295 293 If target doesn't exist or is older than any file listed in deps, return
296 294 true, otherwise return false.
297 295 """
298 296 try:
299 297 target_time = os.path.getmtime(target)
300 298 except os.error:
301 299 return 1
302 300 for dep in deps:
303 301 dep_time = os.path.getmtime(dep)
304 302 if dep_time > target_time:
305 303 #print "For target",target,"Dep failed:",dep # dbg
306 304 #print "times (dep,tar):",dep_time,target_time # dbg
307 305 return 1
308 306 return 0
309 307
310 308
311 309 def target_update(target,deps,cmd):
312 310 """Update a target with a given command given a list of dependencies.
313 311
314 312 target_update(target,deps,cmd) -> runs cmd if target is outdated.
315 313
316 314 This is just a wrapper around target_outdated() which calls the given
317 315 command if target is outdated."""
318 316
319 317 if target_outdated(target,deps):
320 318 system(cmd)
321 319
322 320
323 321 ENOLINK = 1998
324 322
325 323 def link(src, dst):
326 324 """Hard links ``src`` to ``dst``, returning 0 or errno.
327 325
328 326 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
329 327 supported by the operating system.
330 328 """
331 329
332 330 if not hasattr(os, "link"):
333 331 return ENOLINK
334 332 link_errno = 0
335 333 try:
336 334 os.link(src, dst)
337 335 except OSError as e:
338 336 link_errno = e.errno
339 337 return link_errno
340 338
341 339
342 340 def link_or_copy(src, dst):
343 341 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
344 342
345 343 Attempts to maintain the semantics of ``shutil.copy``.
346 344
347 345 Because ``os.link`` does not overwrite files, a unique temporary file
348 346 will be used if the target already exists, then that file will be moved
349 347 into place.
350 348 """
351 349
352 350 if os.path.isdir(dst):
353 351 dst = os.path.join(dst, os.path.basename(src))
354 352
355 353 link_errno = link(src, dst)
356 354 if link_errno == errno.EEXIST:
357 355 if os.stat(src).st_ino == os.stat(dst).st_ino:
358 356 # dst is already a hard link to the correct file, so we don't need
359 357 # to do anything else. If we try to link and rename the file
360 358 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
361 359 return
362 360
363 361 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
364 362 try:
365 363 link_or_copy(src, new_dst)
366 364 except:
367 365 try:
368 366 os.remove(new_dst)
369 367 except OSError:
370 368 pass
371 369 raise
372 370 os.rename(new_dst, dst)
373 371 elif link_errno != 0:
374 372 # Either link isn't supported, or the filesystem doesn't support
375 373 # linking, or 'src' and 'dst' are on different filesystems.
376 374 shutil.copy(src, dst)
377 375
378 376 def ensure_dir_exists(path, mode=0o755):
379 377 """ensure that a directory exists
380 378
381 379 If it doesn't exist, try to create it and protect against a race condition
382 380 if another process is doing the same.
383 381
384 382 The default permissions are 755, which differ from os.makedirs default of 777.
385 383 """
386 384 if not os.path.exists(path):
387 385 try:
388 386 os.makedirs(path, mode=mode)
389 387 except OSError as e:
390 388 if e.errno != errno.EEXIST:
391 389 raise
392 390 elif not os.path.isdir(path):
393 391 raise IOError("%r exists but is not a directory" % path)
@@ -1,61 +1,60 b''
1 1 # encoding: utf-8
2 2 """Tests for io.py"""
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 sys
9 9 from io import StringIO
10 10
11 from subprocess import Popen, PIPE
12 11 import unittest
13 12
14 13 from IPython.utils.io import Tee, capture_output
15 14
16 15
17 16 def test_tee_simple():
18 17 "Very simple check with stdout only"
19 18 chan = StringIO()
20 19 text = 'Hello'
21 20 tee = Tee(chan, channel='stdout')
22 21 print(text, file=chan)
23 22 assert chan.getvalue() == text + "\n"
24 23
25 24
26 25 class TeeTestCase(unittest.TestCase):
27 26
28 27 def tchan(self, channel):
29 28 trap = StringIO()
30 29 chan = StringIO()
31 30 text = 'Hello'
32 31
33 32 std_ori = getattr(sys, channel)
34 33 setattr(sys, channel, trap)
35 34
36 35 tee = Tee(chan, channel=channel)
37 36
38 37 print(text, end='', file=chan)
39 38 trap_val = trap.getvalue()
40 39 self.assertEqual(chan.getvalue(), text)
41 40
42 41 tee.close()
43 42
44 43 setattr(sys, channel, std_ori)
45 44 assert getattr(sys, channel) == std_ori
46 45
47 46 def test(self):
48 47 for chan in ['stdout', 'stderr']:
49 48 self.tchan(chan)
50 49
51 50 class TestIOStream(unittest.TestCase):
52 51
53 52 def test_capture_output(self):
54 53 """capture_output() context works"""
55 54
56 55 with capture_output() as io:
57 56 print("hi, stdout")
58 57 print("hi, stderr", file=sys.stderr)
59 58
60 59 self.assertEqual(io.stdout, "hi, stdout\n")
61 60 self.assertEqual(io.stderr, "hi, stderr\n")
@@ -1,109 +1,107 b''
1 1 # encoding: utf-8
2 2 """Tests for IPython.utils.module_paths.py"""
3 3
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (C) 2008-2011 The IPython Development Team
6 6 #
7 7 # Distributed under the terms of the BSD License. The full license is in
8 8 # the file COPYING, distributed as part of this software.
9 9 #-----------------------------------------------------------------------------
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Imports
13 13 #-----------------------------------------------------------------------------
14 14
15 15 import shutil
16 16 import sys
17 17 import tempfile
18 18
19 19 from pathlib import Path
20 20
21 from IPython.testing.tools import make_tempfile
22
23 21 import IPython.utils.module_paths as mp
24 22
25 23 TEST_FILE_PATH = Path(__file__).resolve().parent
26 24
27 25 TMP_TEST_DIR = Path(tempfile.mkdtemp(suffix="with.dot"))
28 26 #
29 27 # Setup/teardown functions/decorators
30 28 #
31 29
32 30 old_syspath = sys.path
33 31
34 32 def make_empty_file(fname):
35 33 open(fname, "w", encoding="utf-8").close()
36 34
37 35
38 36 def setup_module():
39 37 """Setup testenvironment for the module:
40 38
41 39 """
42 40 # Do not mask exceptions here. In particular, catching WindowsError is a
43 41 # problem because that exception is only defined on Windows...
44 42 Path(TMP_TEST_DIR / "xmod").mkdir(parents=True)
45 43 Path(TMP_TEST_DIR / "nomod").mkdir(parents=True)
46 44 make_empty_file(TMP_TEST_DIR / "xmod/__init__.py")
47 45 make_empty_file(TMP_TEST_DIR / "xmod/sub.py")
48 46 make_empty_file(TMP_TEST_DIR / "pack.py")
49 47 make_empty_file(TMP_TEST_DIR / "packpyc.pyc")
50 48 sys.path = [str(TMP_TEST_DIR)]
51 49
52 50 def teardown_module():
53 51 """Teardown testenvironment for the module:
54 52
55 53 - Remove tempdir
56 54 - restore sys.path
57 55 """
58 56 # Note: we remove the parent test dir, which is the root of all test
59 57 # subdirs we may have created. Use shutil instead of os.removedirs, so
60 58 # that non-empty directories are all recursively removed.
61 59 shutil.rmtree(TMP_TEST_DIR)
62 60 sys.path = old_syspath
63 61
64 62 def test_tempdir():
65 63 """
66 64 Ensure the test are done with a temporary file that have a dot somewhere.
67 65 """
68 66 assert "." in str(TMP_TEST_DIR)
69 67
70 68
71 69 def test_find_mod_1():
72 70 """
73 71 Search for a directory's file path.
74 72 Expected output: a path to that directory's __init__.py file.
75 73 """
76 74 modpath = TMP_TEST_DIR / "xmod" / "__init__.py"
77 75 assert Path(mp.find_mod("xmod")) == modpath
78 76
79 77 def test_find_mod_2():
80 78 """
81 79 Search for a directory's file path.
82 80 Expected output: a path to that directory's __init__.py file.
83 81 TODO: Confirm why this is a duplicate test.
84 82 """
85 83 modpath = TMP_TEST_DIR / "xmod" / "__init__.py"
86 84 assert Path(mp.find_mod("xmod")) == modpath
87 85
88 86 def test_find_mod_3():
89 87 """
90 88 Search for a directory + a filename without its .py extension
91 89 Expected output: full path with .py extension.
92 90 """
93 91 modpath = TMP_TEST_DIR / "xmod" / "sub.py"
94 92 assert Path(mp.find_mod("xmod.sub")) == modpath
95 93
96 94 def test_find_mod_4():
97 95 """
98 96 Search for a filename without its .py extension
99 97 Expected output: full path with .py extension
100 98 """
101 99 modpath = TMP_TEST_DIR / "pack.py"
102 100 assert Path(mp.find_mod("pack")) == modpath
103 101
104 102 def test_find_mod_5():
105 103 """
106 104 Search for a filename with a .pyc extension
107 105 Expected output: TODO: do we exclude or include .pyc files?
108 106 """
109 107 assert mp.find_mod("packpyc") == None
@@ -1,208 +1,207 b''
1 1 # encoding: utf-8
2 2 """Tests for IPython.utils.text"""
3 3
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (C) 2011 The IPython Development Team
6 6 #
7 7 # Distributed under the terms of the BSD License. The full license is in
8 8 # the file COPYING, distributed as part of this software.
9 9 #-----------------------------------------------------------------------------
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Imports
13 13 #-----------------------------------------------------------------------------
14 14
15 15 import os
16 16 import math
17 17 import random
18 import sys
19 18
20 19 from pathlib import Path
21 20
22 21 import pytest
23 22
24 23 from IPython.utils import text
25 24
26 25 #-----------------------------------------------------------------------------
27 26 # Globals
28 27 #-----------------------------------------------------------------------------
29 28
30 29 def test_columnize():
31 30 """Basic columnize tests."""
32 31 size = 5
33 32 items = [l*size for l in 'abcd']
34 33
35 34 out = text.columnize(items, displaywidth=80)
36 35 assert out == "aaaaa bbbbb ccccc ddddd\n"
37 36 out = text.columnize(items, displaywidth=25)
38 37 assert out == "aaaaa ccccc\nbbbbb ddddd\n"
39 38 out = text.columnize(items, displaywidth=12)
40 39 assert out == "aaaaa ccccc\nbbbbb ddddd\n"
41 40 out = text.columnize(items, displaywidth=10)
42 41 assert out == "aaaaa\nbbbbb\nccccc\nddddd\n"
43 42
44 43 out = text.columnize(items, row_first=True, displaywidth=80)
45 44 assert out == "aaaaa bbbbb ccccc ddddd\n"
46 45 out = text.columnize(items, row_first=True, displaywidth=25)
47 46 assert out == "aaaaa bbbbb\nccccc ddddd\n"
48 47 out = text.columnize(items, row_first=True, displaywidth=12)
49 48 assert out == "aaaaa bbbbb\nccccc ddddd\n"
50 49 out = text.columnize(items, row_first=True, displaywidth=10)
51 50 assert out == "aaaaa\nbbbbb\nccccc\nddddd\n"
52 51
53 52 out = text.columnize(items, displaywidth=40, spread=True)
54 53 assert out == "aaaaa bbbbb ccccc ddddd\n"
55 54 out = text.columnize(items, displaywidth=20, spread=True)
56 55 assert out == "aaaaa ccccc\nbbbbb ddddd\n"
57 56 out = text.columnize(items, displaywidth=12, spread=True)
58 57 assert out == "aaaaa ccccc\nbbbbb ddddd\n"
59 58 out = text.columnize(items, displaywidth=10, spread=True)
60 59 assert out == "aaaaa\nbbbbb\nccccc\nddddd\n"
61 60
62 61
63 62 def test_columnize_random():
64 63 """Test with random input to hopefully catch edge case """
65 64 for row_first in [True, False]:
66 65 for nitems in [random.randint(2,70) for i in range(2,20)]:
67 66 displaywidth = random.randint(20,200)
68 67 rand_len = [random.randint(2,displaywidth) for i in range(nitems)]
69 68 items = ['x'*l for l in rand_len]
70 69 out = text.columnize(items, row_first=row_first, displaywidth=displaywidth)
71 70 longer_line = max([len(x) for x in out.split('\n')])
72 71 longer_element = max(rand_len)
73 72 assert longer_line <= displaywidth, (
74 73 f"Columnize displayed something lager than displaywidth : {longer_line}\n"
75 74 f"longer element : {longer_element}\n"
76 75 f"displaywidth : {displaywidth}\n"
77 76 f"number of element : {nitems}\n"
78 77 f"size of each element : {rand_len}\n"
79 78 f"row_first={row_first}\n"
80 79 )
81 80
82 81
83 82 @pytest.mark.parametrize("row_first", [True, False])
84 83 def test_columnize_medium(row_first):
85 84 """Test with inputs than shouldn't be wider than 80"""
86 85 size = 40
87 86 items = [l*size for l in 'abc']
88 87 out = text.columnize(items, row_first=row_first, displaywidth=80)
89 88 assert out == "\n".join(items + [""]), "row_first={0}".format(row_first)
90 89
91 90
92 91 @pytest.mark.parametrize("row_first", [True, False])
93 92 def test_columnize_long(row_first):
94 93 """Test columnize with inputs longer than the display window"""
95 94 size = 11
96 95 items = [l*size for l in 'abc']
97 96 out = text.columnize(items, row_first=row_first, displaywidth=size - 1)
98 97 assert out == "\n".join(items + [""]), "row_first={0}".format(row_first)
99 98
100 99
101 100 def eval_formatter_check(f):
102 101 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os, u=u"cafΓ©", b="cafΓ©")
103 102 s = f.format("{n} {n//4} {stuff.split()[0]}", **ns)
104 103 assert s == "12 3 hello"
105 104 s = f.format(" ".join(["{n//%i}" % i for i in range(1, 8)]), **ns)
106 105 assert s == "12 6 4 3 2 2 1"
107 106 s = f.format("{[n//i for i in range(1,8)]}", **ns)
108 107 assert s == "[12, 6, 4, 3, 2, 2, 1]"
109 108 s = f.format("{stuff!s}", **ns)
110 109 assert s == ns["stuff"]
111 110 s = f.format("{stuff!r}", **ns)
112 111 assert s == repr(ns["stuff"])
113 112
114 113 # Check with unicode:
115 114 s = f.format("{u}", **ns)
116 115 assert s == ns["u"]
117 116 # This decodes in a platform dependent manner, but it shouldn't error out
118 117 s = f.format("{b}", **ns)
119 118
120 119 pytest.raises(NameError, f.format, "{dne}", **ns)
121 120
122 121
123 122 def eval_formatter_slicing_check(f):
124 123 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
125 124 s = f.format(" {stuff.split()[:]} ", **ns)
126 125 assert s == " ['hello', 'there'] "
127 126 s = f.format(" {stuff.split()[::-1]} ", **ns)
128 127 assert s == " ['there', 'hello'] "
129 128 s = f.format("{stuff[::2]}", **ns)
130 129 assert s == ns["stuff"][::2]
131 130
132 131 pytest.raises(SyntaxError, f.format, "{n:x}", **ns)
133 132
134 133 def eval_formatter_no_slicing_check(f):
135 134 ns = dict(n=12, pi=math.pi, stuff="hello there", os=os)
136 135
137 136 s = f.format("{n:x} {pi**2:+f}", **ns)
138 137 assert s == "c +9.869604"
139 138
140 139 s = f.format("{stuff[slice(1,4)]}", **ns)
141 140 assert s == "ell"
142 141
143 142 s = f.format("{a[:]}", a=[1, 2])
144 143 assert s == "[1, 2]"
145 144
146 145 def test_eval_formatter():
147 146 f = text.EvalFormatter()
148 147 eval_formatter_check(f)
149 148 eval_formatter_no_slicing_check(f)
150 149
151 150 def test_full_eval_formatter():
152 151 f = text.FullEvalFormatter()
153 152 eval_formatter_check(f)
154 153 eval_formatter_slicing_check(f)
155 154
156 155 def test_dollar_formatter():
157 156 f = text.DollarFormatter()
158 157 eval_formatter_check(f)
159 158 eval_formatter_slicing_check(f)
160 159
161 160 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
162 161 s = f.format("$n", **ns)
163 162 assert s == "12"
164 163 s = f.format("$n.real", **ns)
165 164 assert s == "12"
166 165 s = f.format("$n/{stuff[:5]}", **ns)
167 166 assert s == "12/hello"
168 167 s = f.format("$n $$HOME", **ns)
169 168 assert s == "12 $HOME"
170 169 s = f.format("${foo}", foo="HOME")
171 170 assert s == "$HOME"
172 171
173 172
174 173 def test_strip_email():
175 174 src = """\
176 175 >> >>> def f(x):
177 176 >> ... return x+1
178 177 >> ...
179 178 >> >>> zz = f(2.5)"""
180 179 cln = """\
181 180 >>> def f(x):
182 181 ... return x+1
183 182 ...
184 183 >>> zz = f(2.5)"""
185 184 assert text.strip_email_quotes(src) == cln
186 185
187 186
188 187 def test_strip_email2():
189 188 src = "> > > list()"
190 189 cln = "list()"
191 190 assert text.strip_email_quotes(src) == cln
192 191
193 192
194 193 def test_LSString():
195 194 lss = text.LSString("abc\ndef")
196 195 assert lss.l == ["abc", "def"]
197 196 assert lss.s == "abc def"
198 197 lss = text.LSString(os.getcwd())
199 198 assert isinstance(lss.p[0], Path)
200 199
201 200
202 201 def test_SList():
203 202 sl = text.SList(["a 11", "b 1", "a 2"])
204 203 assert sl.n == "a 11\nb 1\na 2"
205 204 assert sl.s == "a 11 b 1 a 2"
206 205 assert sl.grep(lambda x: x.startswith("a")) == text.SList(["a 11", "a 2"])
207 206 assert sl.fields(0) == text.SList(["a", "b", "a"])
208 207 assert sl.sort(field=1, nums=True) == text.SList(["b 1", "a 2", "a 11"])
General Comments 0
You need to be logged in to leave comments. Login now