##// END OF EJS Templates
Revert "Ruffisation"
M Bussonnier -
Show More
@@ -1,492 +1,492
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 An application for IPython.
3 An application for IPython.
4
4
5 All top-level applications should use the classes in this module for
5 All top-level applications should use the classes in this module for
6 handling configuration and creating configurables.
6 handling configuration and creating configurables.
7
7
8 The job of an :class:`Application` is to create the master configuration
8 The job of an :class:`Application` is to create the master configuration
9 object and then create the configurable objects, passing the config to them.
9 object and then create the configurable objects, passing the config to them.
10 """
10 """
11
11
12 # Copyright (c) IPython Development Team.
12 # Copyright (c) IPython Development Team.
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14
14
15 import atexit
15 import atexit
16 from copy import deepcopy
16 from copy import deepcopy
17 import logging
17 import logging
18 import os
18 import os
19 import shutil
19 import shutil
20 import sys
20 import sys
21
21
22 from pathlib import Path
22 from pathlib import Path
23
23
24 from traitlets.config.application import Application, catch_config_error
24 from traitlets.config.application import Application, catch_config_error
25 from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader
25 from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader
26 from IPython.core import release, crashhandler
26 from IPython.core import release, crashhandler
27 from IPython.core.profiledir import ProfileDir, ProfileDirError
27 from IPython.core.profiledir import ProfileDir, ProfileDirError
28 from IPython.paths import get_ipython_dir, get_ipython_package_dir
28 from IPython.paths import get_ipython_dir, get_ipython_package_dir
29 from IPython.utils.path import ensure_dir_exists
29 from IPython.utils.path import ensure_dir_exists
30 from traitlets import (
30 from traitlets import (
31 List, Unicode, Type, Bool, Set, Instance, Undefined,
31 List, Unicode, Type, Bool, Set, Instance, Undefined,
32 default, observe,
32 default, observe,
33 )
33 )
34
34
35 if os.name == "nt":
35 if os.name == "nt":
36 programdata = os.environ.get("PROGRAMDATA", None)
36 programdata = os.environ.get("PROGRAMDATA", None)
37 if programdata is not None:
37 if programdata is not None:
38 SYSTEM_CONFIG_DIRS = [str(Path(programdata) / "ipython")]
38 SYSTEM_CONFIG_DIRS = [str(Path(programdata) / "ipython")]
39 else: # PROGRAMDATA is not defined by default on XP.
39 else: # PROGRAMDATA is not defined by default on XP.
40 SYSTEM_CONFIG_DIRS = []
40 SYSTEM_CONFIG_DIRS = []
41 else:
41 else:
42 SYSTEM_CONFIG_DIRS = [
42 SYSTEM_CONFIG_DIRS = [
43 "/usr/local/etc/ipython",
43 "/usr/local/etc/ipython",
44 "/etc/ipython",
44 "/etc/ipython",
45 ]
45 ]
46
46
47
47
48 ENV_CONFIG_DIRS = []
48 ENV_CONFIG_DIRS = []
49 _env_config_dir = os.path.join(sys.prefix, 'etc', 'ipython')
49 _env_config_dir = os.path.join(sys.prefix, 'etc', 'ipython')
50 if _env_config_dir not in SYSTEM_CONFIG_DIRS:
50 if _env_config_dir not in SYSTEM_CONFIG_DIRS:
51 # only add ENV_CONFIG if sys.prefix is not already included
51 # only add ENV_CONFIG if sys.prefix is not already included
52 ENV_CONFIG_DIRS.append(_env_config_dir)
52 ENV_CONFIG_DIRS.append(_env_config_dir)
53
53
54
54
55 _envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS')
55 _envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS')
56 if _envvar in {None, ''}:
56 if _envvar in {None, ''}:
57 IPYTHON_SUPPRESS_CONFIG_ERRORS = None
57 IPYTHON_SUPPRESS_CONFIG_ERRORS = None
58 else:
58 else:
59 if _envvar.lower() in {'1','true'}:
59 if _envvar.lower() in {'1','true'}:
60 IPYTHON_SUPPRESS_CONFIG_ERRORS = True
60 IPYTHON_SUPPRESS_CONFIG_ERRORS = True
61 elif _envvar.lower() in {'0','false'} :
61 elif _envvar.lower() in {'0','false'} :
62 IPYTHON_SUPPRESS_CONFIG_ERRORS = False
62 IPYTHON_SUPPRESS_CONFIG_ERRORS = False
63 else:
63 else:
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 )
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 )
65
65
66 # aliases and flags
66 # aliases and flags
67
67
68 base_aliases = {}
68 base_aliases = {}
69 if isinstance(Application.aliases, dict):
69 if isinstance(Application.aliases, dict):
70 # traitlets 5
70 # traitlets 5
71 base_aliases.update(Application.aliases)
71 base_aliases.update(Application.aliases)
72 base_aliases.update(
72 base_aliases.update(
73 {
73 {
74 "profile-dir": "ProfileDir.location",
74 "profile-dir": "ProfileDir.location",
75 "profile": "BaseIPythonApplication.profile",
75 "profile": "BaseIPythonApplication.profile",
76 "ipython-dir": "BaseIPythonApplication.ipython_dir",
76 "ipython-dir": "BaseIPythonApplication.ipython_dir",
77 "log-level": "Application.log_level",
77 "log-level": "Application.log_level",
78 "config": "BaseIPythonApplication.extra_config_file",
78 "config": "BaseIPythonApplication.extra_config_file",
79 }
79 }
80 )
80 )
81
81
82 base_flags = dict()
82 base_flags = dict()
83 if isinstance(Application.flags, dict):
83 if isinstance(Application.flags, dict):
84 # traitlets 5
84 # traitlets 5
85 base_flags.update(Application.flags)
85 base_flags.update(Application.flags)
86 base_flags.update(
86 base_flags.update(
87 dict(
87 dict(
88 debug=(
88 debug=(
89 {"Application": {"log_level": logging.DEBUG}},
89 {"Application": {"log_level": logging.DEBUG}},
90 "set log level to logging.DEBUG (maximize logging output)",
90 "set log level to logging.DEBUG (maximize logging output)",
91 ),
91 ),
92 quiet=(
92 quiet=(
93 {"Application": {"log_level": logging.CRITICAL}},
93 {"Application": {"log_level": logging.CRITICAL}},
94 "set log level to logging.CRITICAL (minimize logging output)",
94 "set log level to logging.CRITICAL (minimize logging output)",
95 ),
95 ),
96 init=(
96 init=(
97 {
97 {
98 "BaseIPythonApplication": {
98 "BaseIPythonApplication": {
99 "copy_config_files": True,
99 "copy_config_files": True,
100 "auto_create": True,
100 "auto_create": True,
101 }
101 }
102 },
102 },
103 """Initialize profile with default config files. This is equivalent
103 """Initialize profile with default config files. This is equivalent
104 to running `ipython profile create <profile>` prior to startup.
104 to running `ipython profile create <profile>` prior to startup.
105 """,
105 """,
106 ),
106 ),
107 )
107 )
108 )
108 )
109
109
110
110
111 class ProfileAwareConfigLoader(PyFileConfigLoader):
111 class ProfileAwareConfigLoader(PyFileConfigLoader):
112 """A Python file config loader that is aware of IPython profiles."""
112 """A Python file config loader that is aware of IPython profiles."""
113 def load_subconfig(self, fname, path=None, profile=None):
113 def load_subconfig(self, fname, path=None, profile=None):
114 if profile is not None:
114 if profile is not None:
115 try:
115 try:
116 profile_dir = ProfileDir.find_profile_dir_by_name(
116 profile_dir = ProfileDir.find_profile_dir_by_name(
117 get_ipython_dir(),
117 get_ipython_dir(),
118 profile,
118 profile,
119 )
119 )
120 except ProfileDirError:
120 except ProfileDirError:
121 return
121 return
122 path = profile_dir.location
122 path = profile_dir.location
123 return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path)
123 return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path)
124
124
125 class BaseIPythonApplication(Application):
125 class BaseIPythonApplication(Application):
126 name = "ipython"
126 name = "ipython"
127 description = "IPython: an enhanced interactive Python shell."
127 description = "IPython: an enhanced interactive Python shell."
128 version = Unicode(release.version)
128 version = Unicode(release.version)
129
129
130 aliases = base_aliases
130 aliases = base_aliases
131 flags = base_flags
131 flags = base_flags
132 classes = List([ProfileDir])
132 classes = List([ProfileDir])
133
133
134 # enable `load_subconfig('cfg.py', profile='name')`
134 # enable `load_subconfig('cfg.py', profile='name')`
135 python_config_loader_class = ProfileAwareConfigLoader
135 python_config_loader_class = ProfileAwareConfigLoader
136
136
137 # Track whether the config_file has changed,
137 # Track whether the config_file has changed,
138 # because some logic happens only if we aren't using the default.
138 # because some logic happens only if we aren't using the default.
139 config_file_specified = Set()
139 config_file_specified = Set()
140
140
141 config_file_name = Unicode()
141 config_file_name = Unicode()
142 @default('config_file_name')
142 @default('config_file_name')
143 def _config_file_name_default(self):
143 def _config_file_name_default(self):
144 return self.name.replace('-','_') + u'_config.py'
144 return self.name.replace('-','_') + u'_config.py'
145 @observe('config_file_name')
145 @observe('config_file_name')
146 def _config_file_name_changed(self, change):
146 def _config_file_name_changed(self, change):
147 if change['new'] != change['old']:
147 if change['new'] != change['old']:
148 self.config_file_specified.add(change['new'])
148 self.config_file_specified.add(change['new'])
149
149
150 # The directory that contains IPython's builtin profiles.
150 # The directory that contains IPython's builtin profiles.
151 builtin_profile_dir = Unicode(
151 builtin_profile_dir = Unicode(
152 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
152 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
153 )
153 )
154
154
155 config_file_paths = List(Unicode())
155 config_file_paths = List(Unicode())
156 @default('config_file_paths')
156 @default('config_file_paths')
157 def _config_file_paths_default(self):
157 def _config_file_paths_default(self):
158 return []
158 return []
159
159
160 extra_config_file = Unicode(
160 extra_config_file = Unicode(
161 help="""Path to an extra config file to load.
161 help="""Path to an extra config file to load.
162
162
163 If specified, load this config file in addition to any other IPython config.
163 If specified, load this config file in addition to any other IPython config.
164 """).tag(config=True)
164 """).tag(config=True)
165 @observe('extra_config_file')
165 @observe('extra_config_file')
166 def _extra_config_file_changed(self, change):
166 def _extra_config_file_changed(self, change):
167 old = change['old']
167 old = change['old']
168 new = change['new']
168 new = change['new']
169 try:
169 try:
170 self.config_files.remove(old)
170 self.config_files.remove(old)
171 except ValueError:
171 except ValueError:
172 pass
172 pass
173 self.config_file_specified.add(new)
173 self.config_file_specified.add(new)
174 self.config_files.append(new)
174 self.config_files.append(new)
175
175
176 profile = Unicode(u'default',
176 profile = Unicode(u'default',
177 help="""The IPython profile to use."""
177 help="""The IPython profile to use."""
178 ).tag(config=True)
178 ).tag(config=True)
179
179
180 @observe('profile')
180 @observe('profile')
181 def _profile_changed(self, change):
181 def _profile_changed(self, change):
182 self.builtin_profile_dir = os.path.join(
182 self.builtin_profile_dir = os.path.join(
183 get_ipython_package_dir(), u'config', u'profile', change['new']
183 get_ipython_package_dir(), u'config', u'profile', change['new']
184 )
184 )
185
185
186 add_ipython_dir_to_sys_path = Bool(
186 add_ipython_dir_to_sys_path = Bool(
187 False,
187 False,
188 """Should the IPython profile directory be added to sys path ?
188 """Should the IPython profile directory be added to sys path ?
189
189
190 This option was non-existing before IPython 8.0, and ipython_dir was added to
190 This option was non-existing before IPython 8.0, and ipython_dir was added to
191 sys path to allow import of extensions present there. This was historical
191 sys path to allow import of extensions present there. This was historical
192 baggage from when pip did not exist. This now default to false,
192 baggage from when pip did not exist. This now default to false,
193 but can be set to true for legacy reasons.
193 but can be set to true for legacy reasons.
194 """,
194 """,
195 ).tag(config=True)
195 ).tag(config=True)
196
196
197 ipython_dir = Unicode(
197 ipython_dir = Unicode(
198 help="""
198 help="""
199 The name of the IPython directory. This directory is used for logging
199 The name of the IPython directory. This directory is used for logging
200 configuration (through profiles), history storage, etc. The default
200 configuration (through profiles), history storage, etc. The default
201 is usually $HOME/.ipython. This option can also be specified through
201 is usually $HOME/.ipython. This option can also be specified through
202 the environment variable IPYTHONDIR.
202 the environment variable IPYTHONDIR.
203 """
203 """
204 ).tag(config=True)
204 ).tag(config=True)
205 @default('ipython_dir')
205 @default('ipython_dir')
206 def _ipython_dir_default(self):
206 def _ipython_dir_default(self):
207 d = get_ipython_dir()
207 d = get_ipython_dir()
208 self._ipython_dir_changed({
208 self._ipython_dir_changed({
209 'name': 'ipython_dir',
209 'name': 'ipython_dir',
210 'old': d,
210 'old': d,
211 'new': d,
211 'new': d,
212 })
212 })
213 return d
213 return d
214
214
215 _in_init_profile_dir = False
215 _in_init_profile_dir = False
216
216
217 profile_dir = Instance(ProfileDir, allow_none=True)
217 profile_dir = Instance(ProfileDir, allow_none=True)
218
218
219 @default('profile_dir')
219 @default('profile_dir')
220 def _profile_dir_default(self):
220 def _profile_dir_default(self):
221 # avoid recursion
221 # avoid recursion
222 if self._in_init_profile_dir:
222 if self._in_init_profile_dir:
223 return
223 return
224 # profile_dir requested early, force initialization
224 # profile_dir requested early, force initialization
225 self.init_profile_dir()
225 self.init_profile_dir()
226 return self.profile_dir
226 return self.profile_dir
227
227
228 overwrite = Bool(False,
228 overwrite = Bool(False,
229 help="""Whether to overwrite existing config files when copying"""
229 help="""Whether to overwrite existing config files when copying"""
230 ).tag(config=True)
230 ).tag(config=True)
231
231
232 auto_create = Bool(False,
232 auto_create = Bool(False,
233 help="""Whether to create profile dir if it doesn't exist"""
233 help="""Whether to create profile dir if it doesn't exist"""
234 ).tag(config=True)
234 ).tag(config=True)
235
235
236 config_files = List(Unicode())
236 config_files = List(Unicode())
237
237
238 @default('config_files')
238 @default('config_files')
239 def _config_files_default(self):
239 def _config_files_default(self):
240 return [self.config_file_name]
240 return [self.config_file_name]
241
241
242 copy_config_files = Bool(False,
242 copy_config_files = Bool(False,
243 help="""Whether to install the default config files into the profile dir.
243 help="""Whether to install the default config files into the profile dir.
244 If a new profile is being created, and IPython contains config files for that
244 If a new profile is being created, and IPython contains config files for that
245 profile, then they will be staged into the new directory. Otherwise,
245 profile, then they will be staged into the new directory. Otherwise,
246 default config files will be automatically generated.
246 default config files will be automatically generated.
247 """).tag(config=True)
247 """).tag(config=True)
248
248
249 verbose_crash = Bool(False,
249 verbose_crash = Bool(False,
250 help="""Create a massive crash report when IPython encounters what may be an
250 help="""Create a massive crash report when IPython encounters what may be an
251 internal error. The default is to append a short message to the
251 internal error. The default is to append a short message to the
252 usual traceback""").tag(config=True)
252 usual traceback""").tag(config=True)
253
253
254 # The class to use as the crash handler.
254 # The class to use as the crash handler.
255 crash_handler_class = Type(crashhandler.CrashHandler)
255 crash_handler_class = Type(crashhandler.CrashHandler)
256
256
257 @catch_config_error
257 @catch_config_error
258 def __init__(self, **kwargs):
258 def __init__(self, **kwargs):
259 super(BaseIPythonApplication, self).__init__(**kwargs)
259 super(BaseIPythonApplication, self).__init__(**kwargs)
260 # ensure current working directory exists
260 # ensure current working directory exists
261 try:
261 try:
262 os.getcwd()
262 os.getcwd()
263 except Exception:
263 except:
264 # exit if cwd doesn't exist
264 # exit if cwd doesn't exist
265 self.log.error("Current working directory doesn't exist.")
265 self.log.error("Current working directory doesn't exist.")
266 self.exit(1)
266 self.exit(1)
267
267
268 #-------------------------------------------------------------------------
268 #-------------------------------------------------------------------------
269 # Various stages of Application creation
269 # Various stages of Application creation
270 #-------------------------------------------------------------------------
270 #-------------------------------------------------------------------------
271
271
272 def init_crash_handler(self):
272 def init_crash_handler(self):
273 """Create a crash handler, typically setting sys.excepthook to it."""
273 """Create a crash handler, typically setting sys.excepthook to it."""
274 self.crash_handler = self.crash_handler_class(self)
274 self.crash_handler = self.crash_handler_class(self)
275 sys.excepthook = self.excepthook
275 sys.excepthook = self.excepthook
276 def unset_crashhandler():
276 def unset_crashhandler():
277 sys.excepthook = sys.__excepthook__
277 sys.excepthook = sys.__excepthook__
278 atexit.register(unset_crashhandler)
278 atexit.register(unset_crashhandler)
279
279
280 def excepthook(self, etype, evalue, tb):
280 def excepthook(self, etype, evalue, tb):
281 """this is sys.excepthook after init_crashhandler
281 """this is sys.excepthook after init_crashhandler
282
282
283 set self.verbose_crash=True to use our full crashhandler, instead of
283 set self.verbose_crash=True to use our full crashhandler, instead of
284 a regular traceback with a short message (crash_handler_lite)
284 a regular traceback with a short message (crash_handler_lite)
285 """
285 """
286
286
287 if self.verbose_crash:
287 if self.verbose_crash:
288 return self.crash_handler(etype, evalue, tb)
288 return self.crash_handler(etype, evalue, tb)
289 else:
289 else:
290 return crashhandler.crash_handler_lite(etype, evalue, tb)
290 return crashhandler.crash_handler_lite(etype, evalue, tb)
291
291
292 @observe('ipython_dir')
292 @observe('ipython_dir')
293 def _ipython_dir_changed(self, change):
293 def _ipython_dir_changed(self, change):
294 old = change['old']
294 old = change['old']
295 new = change['new']
295 new = change['new']
296 if old is not Undefined:
296 if old is not Undefined:
297 str_old = os.path.abspath(old)
297 str_old = os.path.abspath(old)
298 if str_old in sys.path:
298 if str_old in sys.path:
299 sys.path.remove(str_old)
299 sys.path.remove(str_old)
300 if self.add_ipython_dir_to_sys_path:
300 if self.add_ipython_dir_to_sys_path:
301 str_path = os.path.abspath(new)
301 str_path = os.path.abspath(new)
302 sys.path.append(str_path)
302 sys.path.append(str_path)
303 ensure_dir_exists(new)
303 ensure_dir_exists(new)
304 readme = os.path.join(new, "README")
304 readme = os.path.join(new, "README")
305 readme_src = os.path.join(
305 readme_src = os.path.join(
306 get_ipython_package_dir(), "config", "profile", "README"
306 get_ipython_package_dir(), "config", "profile", "README"
307 )
307 )
308 if not os.path.exists(readme) and os.path.exists(readme_src):
308 if not os.path.exists(readme) and os.path.exists(readme_src):
309 shutil.copy(readme_src, readme)
309 shutil.copy(readme_src, readme)
310 for d in ("extensions", "nbextensions"):
310 for d in ("extensions", "nbextensions"):
311 path = os.path.join(new, d)
311 path = os.path.join(new, d)
312 try:
312 try:
313 ensure_dir_exists(path)
313 ensure_dir_exists(path)
314 except OSError as e:
314 except OSError as e:
315 # this will not be EEXIST
315 # this will not be EEXIST
316 self.log.error("couldn't create path %s: %s", path, e)
316 self.log.error("couldn't create path %s: %s", path, e)
317 self.log.debug("IPYTHONDIR set to: %s", new)
317 self.log.debug("IPYTHONDIR set to: %s", new)
318
318
319 def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS):
319 def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS):
320 """Load the config file.
320 """Load the config file.
321
321
322 By default, errors in loading config are handled, and a warning
322 By default, errors in loading config are handled, and a warning
323 printed on screen. For testing, the suppress_errors option is set
323 printed on screen. For testing, the suppress_errors option is set
324 to False, so errors will make tests fail.
324 to False, so errors will make tests fail.
325
325
326 `suppress_errors` default value is to be `None` in which case the
326 `suppress_errors` default value is to be `None` in which case the
327 behavior default to the one of `traitlets.Application`.
327 behavior default to the one of `traitlets.Application`.
328
328
329 The default value can be set :
329 The default value can be set :
330 - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive).
330 - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive).
331 - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive).
331 - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive).
332 - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset.
332 - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset.
333
333
334 Any other value are invalid, and will make IPython exit with a non-zero return code.
334 Any other value are invalid, and will make IPython exit with a non-zero return code.
335 """
335 """
336
336
337
337
338 self.log.debug("Searching path %s for config files", self.config_file_paths)
338 self.log.debug("Searching path %s for config files", self.config_file_paths)
339 base_config = 'ipython_config.py'
339 base_config = 'ipython_config.py'
340 self.log.debug("Attempting to load config file: %s" %
340 self.log.debug("Attempting to load config file: %s" %
341 base_config)
341 base_config)
342 try:
342 try:
343 if suppress_errors is not None:
343 if suppress_errors is not None:
344 old_value = Application.raise_config_file_errors
344 old_value = Application.raise_config_file_errors
345 Application.raise_config_file_errors = not suppress_errors
345 Application.raise_config_file_errors = not suppress_errors;
346 Application.load_config_file(
346 Application.load_config_file(
347 self,
347 self,
348 base_config,
348 base_config,
349 path=self.config_file_paths
349 path=self.config_file_paths
350 )
350 )
351 except ConfigFileNotFound:
351 except ConfigFileNotFound:
352 # ignore errors loading parent
352 # ignore errors loading parent
353 self.log.debug("Config file %s not found", base_config)
353 self.log.debug("Config file %s not found", base_config)
354 pass
354 pass
355 if suppress_errors is not None:
355 if suppress_errors is not None:
356 Application.raise_config_file_errors = old_value
356 Application.raise_config_file_errors = old_value
357
357
358 for config_file_name in self.config_files:
358 for config_file_name in self.config_files:
359 if not config_file_name or config_file_name == base_config:
359 if not config_file_name or config_file_name == base_config:
360 continue
360 continue
361 self.log.debug("Attempting to load config file: %s" %
361 self.log.debug("Attempting to load config file: %s" %
362 self.config_file_name)
362 self.config_file_name)
363 try:
363 try:
364 Application.load_config_file(
364 Application.load_config_file(
365 self,
365 self,
366 config_file_name,
366 config_file_name,
367 path=self.config_file_paths
367 path=self.config_file_paths
368 )
368 )
369 except ConfigFileNotFound:
369 except ConfigFileNotFound:
370 # Only warn if the default config file was NOT being used.
370 # Only warn if the default config file was NOT being used.
371 if config_file_name in self.config_file_specified:
371 if config_file_name in self.config_file_specified:
372 msg = self.log.warning
372 msg = self.log.warning
373 else:
373 else:
374 msg = self.log.debug
374 msg = self.log.debug
375 msg("Config file not found, skipping: %s", config_file_name)
375 msg("Config file not found, skipping: %s", config_file_name)
376 except Exception:
376 except Exception:
377 # For testing purposes.
377 # For testing purposes.
378 if not suppress_errors:
378 if not suppress_errors:
379 raise
379 raise
380 self.log.warning("Error loading config file: %s" %
380 self.log.warning("Error loading config file: %s" %
381 self.config_file_name, exc_info=True)
381 self.config_file_name, exc_info=True)
382
382
383 def init_profile_dir(self):
383 def init_profile_dir(self):
384 """initialize the profile dir"""
384 """initialize the profile dir"""
385 self._in_init_profile_dir = True
385 self._in_init_profile_dir = True
386 if self.profile_dir is not None:
386 if self.profile_dir is not None:
387 # already ran
387 # already ran
388 return
388 return
389 if 'ProfileDir.location' not in self.config:
389 if 'ProfileDir.location' not in self.config:
390 # location not specified, find by profile name
390 # location not specified, find by profile name
391 try:
391 try:
392 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
392 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
393 except ProfileDirError:
393 except ProfileDirError:
394 # not found, maybe create it (always create default profile)
394 # not found, maybe create it (always create default profile)
395 if self.auto_create or self.profile == 'default':
395 if self.auto_create or self.profile == 'default':
396 try:
396 try:
397 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
397 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
398 except ProfileDirError:
398 except ProfileDirError:
399 self.log.fatal("Could not create profile: %r"%self.profile)
399 self.log.fatal("Could not create profile: %r"%self.profile)
400 self.exit(1)
400 self.exit(1)
401 else:
401 else:
402 self.log.info("Created profile dir: %r"%p.location)
402 self.log.info("Created profile dir: %r"%p.location)
403 else:
403 else:
404 self.log.fatal("Profile %r not found."%self.profile)
404 self.log.fatal("Profile %r not found."%self.profile)
405 self.exit(1)
405 self.exit(1)
406 else:
406 else:
407 self.log.debug("Using existing profile dir: %r", p.location)
407 self.log.debug("Using existing profile dir: %r", p.location)
408 else:
408 else:
409 location = self.config.ProfileDir.location
409 location = self.config.ProfileDir.location
410 # location is fully specified
410 # location is fully specified
411 try:
411 try:
412 p = ProfileDir.find_profile_dir(location, self.config)
412 p = ProfileDir.find_profile_dir(location, self.config)
413 except ProfileDirError:
413 except ProfileDirError:
414 # not found, maybe create it
414 # not found, maybe create it
415 if self.auto_create:
415 if self.auto_create:
416 try:
416 try:
417 p = ProfileDir.create_profile_dir(location, self.config)
417 p = ProfileDir.create_profile_dir(location, self.config)
418 except ProfileDirError:
418 except ProfileDirError:
419 self.log.fatal("Could not create profile directory: %r"%location)
419 self.log.fatal("Could not create profile directory: %r"%location)
420 self.exit(1)
420 self.exit(1)
421 else:
421 else:
422 self.log.debug("Creating new profile dir: %r"%location)
422 self.log.debug("Creating new profile dir: %r"%location)
423 else:
423 else:
424 self.log.fatal("Profile directory %r not found."%location)
424 self.log.fatal("Profile directory %r not found."%location)
425 self.exit(1)
425 self.exit(1)
426 else:
426 else:
427 self.log.debug("Using existing profile dir: %r", p.location)
427 self.log.debug("Using existing profile dir: %r", p.location)
428 # if profile_dir is specified explicitly, set profile name
428 # if profile_dir is specified explicitly, set profile name
429 dir_name = os.path.basename(p.location)
429 dir_name = os.path.basename(p.location)
430 if dir_name.startswith('profile_'):
430 if dir_name.startswith('profile_'):
431 self.profile = dir_name[8:]
431 self.profile = dir_name[8:]
432
432
433 self.profile_dir = p
433 self.profile_dir = p
434 self.config_file_paths.append(p.location)
434 self.config_file_paths.append(p.location)
435 self._in_init_profile_dir = False
435 self._in_init_profile_dir = False
436
436
437 def init_config_files(self):
437 def init_config_files(self):
438 """[optionally] copy default config files into profile dir."""
438 """[optionally] copy default config files into profile dir."""
439 self.config_file_paths.extend(ENV_CONFIG_DIRS)
439 self.config_file_paths.extend(ENV_CONFIG_DIRS)
440 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
440 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
441 # copy config files
441 # copy config files
442 path = Path(self.builtin_profile_dir)
442 path = Path(self.builtin_profile_dir)
443 if self.copy_config_files:
443 if self.copy_config_files:
444 src = self.profile
444 src = self.profile
445
445
446 cfg = self.config_file_name
446 cfg = self.config_file_name
447 if path and (path / cfg).exists():
447 if path and (path / cfg).exists():
448 self.log.warning(
448 self.log.warning(
449 "Staging %r from %s into %r [overwrite=%s]"
449 "Staging %r from %s into %r [overwrite=%s]"
450 % (cfg, src, self.profile_dir.location, self.overwrite)
450 % (cfg, src, self.profile_dir.location, self.overwrite)
451 )
451 )
452 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
452 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
453 else:
453 else:
454 self.stage_default_config_file()
454 self.stage_default_config_file()
455 else:
455 else:
456 # Still stage *bundled* config files, but not generated ones
456 # Still stage *bundled* config files, but not generated ones
457 # This is necessary for `ipython profile=sympy` to load the profile
457 # This is necessary for `ipython profile=sympy` to load the profile
458 # on the first go
458 # on the first go
459 files = path.glob("*.py")
459 files = path.glob("*.py")
460 for fullpath in files:
460 for fullpath in files:
461 cfg = fullpath.name
461 cfg = fullpath.name
462 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
462 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
463 # file was copied
463 # file was copied
464 self.log.warning("Staging bundled %s from %s into %r"%(
464 self.log.warning("Staging bundled %s from %s into %r"%(
465 cfg, self.profile, self.profile_dir.location)
465 cfg, self.profile, self.profile_dir.location)
466 )
466 )
467
467
468
468
469 def stage_default_config_file(self):
469 def stage_default_config_file(self):
470 """auto generate default config file, and stage it into the profile."""
470 """auto generate default config file, and stage it into the profile."""
471 s = self.generate_config_file()
471 s = self.generate_config_file()
472 config_file = Path(self.profile_dir.location) / self.config_file_name
472 config_file = Path(self.profile_dir.location) / self.config_file_name
473 if self.overwrite or not config_file.exists():
473 if self.overwrite or not config_file.exists():
474 self.log.warning("Generating default config file: %r", (config_file))
474 self.log.warning("Generating default config file: %r", (config_file))
475 config_file.write_text(s, encoding="utf-8")
475 config_file.write_text(s, encoding="utf-8")
476
476
477 @catch_config_error
477 @catch_config_error
478 def initialize(self, argv=None):
478 def initialize(self, argv=None):
479 # don't hook up crash handler before parsing command-line
479 # don't hook up crash handler before parsing command-line
480 self.parse_command_line(argv)
480 self.parse_command_line(argv)
481 self.init_crash_handler()
481 self.init_crash_handler()
482 if self.subapp is not None:
482 if self.subapp is not None:
483 # stop here if subapp is taking over
483 # stop here if subapp is taking over
484 return
484 return
485 # save a copy of CLI config to re-load after config files
485 # save a copy of CLI config to re-load after config files
486 # so that it has highest priority
486 # so that it has highest priority
487 cl_config = deepcopy(self.config)
487 cl_config = deepcopy(self.config)
488 self.init_profile_dir()
488 self.init_profile_dir()
489 self.init_config_files()
489 self.init_config_files()
490 self.load_config_file()
490 self.load_config_file()
491 # enforce cl-opts override configfile opts:
491 # enforce cl-opts override configfile opts:
492 self.update_config(cl_config)
492 self.update_config(cl_config)
@@ -1,89 +1,86
1 """
1 """
2 A context manager for managing things injected into :mod:`builtins`.
2 A context manager for managing things injected into :mod:`builtins`.
3 """
3 """
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6 import builtins as builtin_mod
6 import builtins as builtin_mod
7
7
8 from traitlets.config.configurable import Configurable
8 from traitlets.config.configurable import Configurable
9
9
10 from traitlets import Instance
10 from traitlets import Instance
11
11
12
12
13 class __BuiltinUndefined:
13 class __BuiltinUndefined(object): pass
14 pass
15 BuiltinUndefined = __BuiltinUndefined()
14 BuiltinUndefined = __BuiltinUndefined()
16
15
17
16 class __HideBuiltin(object): pass
18 class __HideBuiltin:
19 pass
20 HideBuiltin = __HideBuiltin()
17 HideBuiltin = __HideBuiltin()
21
18
22
19
23 class BuiltinTrap(Configurable):
20 class BuiltinTrap(Configurable):
24
21
25 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
22 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
26 allow_none=True)
23 allow_none=True)
27
24
28 def __init__(self, shell=None):
25 def __init__(self, shell=None):
29 super(BuiltinTrap, self).__init__(shell=shell, config=None)
26 super(BuiltinTrap, self).__init__(shell=shell, config=None)
30 self._orig_builtins = {}
27 self._orig_builtins = {}
31 # We define this to track if a single BuiltinTrap is nested.
28 # We define this to track if a single BuiltinTrap is nested.
32 # Only turn off the trap when the outermost call to __exit__ is made.
29 # Only turn off the trap when the outermost call to __exit__ is made.
33 self._nested_level = 0
30 self._nested_level = 0
34 self.shell = shell
31 self.shell = shell
35 # builtins we always add - if set to HideBuiltin, they will just
32 # builtins we always add - if set to HideBuiltin, they will just
36 # be removed instead of being replaced by something else
33 # be removed instead of being replaced by something else
37 self.auto_builtins = {'exit': HideBuiltin,
34 self.auto_builtins = {'exit': HideBuiltin,
38 'quit': HideBuiltin,
35 'quit': HideBuiltin,
39 'get_ipython': self.shell.get_ipython,
36 'get_ipython': self.shell.get_ipython,
40 }
37 }
41
38
42 def __enter__(self):
39 def __enter__(self):
43 if self._nested_level == 0:
40 if self._nested_level == 0:
44 self.activate()
41 self.activate()
45 self._nested_level += 1
42 self._nested_level += 1
46 # I return self, so callers can use add_builtin in a with clause.
43 # I return self, so callers can use add_builtin in a with clause.
47 return self
44 return self
48
45
49 def __exit__(self, type, value, traceback):
46 def __exit__(self, type, value, traceback):
50 if self._nested_level == 1:
47 if self._nested_level == 1:
51 self.deactivate()
48 self.deactivate()
52 self._nested_level -= 1
49 self._nested_level -= 1
53 # Returning False will cause exceptions to propagate
50 # Returning False will cause exceptions to propagate
54 return False
51 return False
55
52
56 def add_builtin(self, key, value):
53 def add_builtin(self, key, value):
57 """Add a builtin and save the original."""
54 """Add a builtin and save the original."""
58 bdict = builtin_mod.__dict__
55 bdict = builtin_mod.__dict__
59 orig = bdict.get(key, BuiltinUndefined)
56 orig = bdict.get(key, BuiltinUndefined)
60 if value is HideBuiltin:
57 if value is HideBuiltin:
61 if orig is not BuiltinUndefined: #same as 'key in bdict'
58 if orig is not BuiltinUndefined: #same as 'key in bdict'
62 self._orig_builtins[key] = orig
59 self._orig_builtins[key] = orig
63 del bdict[key]
60 del bdict[key]
64 else:
61 else:
65 self._orig_builtins[key] = orig
62 self._orig_builtins[key] = orig
66 bdict[key] = value
63 bdict[key] = value
67
64
68 def remove_builtin(self, key, orig):
65 def remove_builtin(self, key, orig):
69 """Remove an added builtin and re-set the original."""
66 """Remove an added builtin and re-set the original."""
70 if orig is BuiltinUndefined:
67 if orig is BuiltinUndefined:
71 del builtin_mod.__dict__[key]
68 del builtin_mod.__dict__[key]
72 else:
69 else:
73 builtin_mod.__dict__[key] = orig
70 builtin_mod.__dict__[key] = orig
74
71
75 def activate(self):
72 def activate(self):
76 """Store ipython references in the __builtin__ namespace."""
73 """Store ipython references in the __builtin__ namespace."""
77
74
78 add_builtin = self.add_builtin
75 add_builtin = self.add_builtin
79 for name, func in self.auto_builtins.items():
76 for name, func in self.auto_builtins.items():
80 add_builtin(name, func)
77 add_builtin(name, func)
81
78
82 def deactivate(self):
79 def deactivate(self):
83 """Remove any builtins which might have been added by add_builtins, or
80 """Remove any builtins which might have been added by add_builtins, or
84 restore overwritten ones to their previous values."""
81 restore overwritten ones to their previous values."""
85 remove_builtin = self.remove_builtin
82 remove_builtin = self.remove_builtin
86 for key, val in self._orig_builtins.items():
83 for key, val in self._orig_builtins.items():
87 remove_builtin(key, val)
84 remove_builtin(key, val)
88 self._orig_builtins.clear()
85 self._orig_builtins.clear()
89 self._builtins_added = False
86 self._builtins_added = False
@@ -1,213 +1,214
1 """Compiler tools with improved interactive support.
1 """Compiler tools with improved interactive support.
2
2
3 Provides compilation machinery similar to codeop, but with caching support so
3 Provides compilation machinery similar to codeop, but with caching support so
4 we can provide interactive tracebacks.
4 we can provide interactive tracebacks.
5
5
6 Authors
6 Authors
7 -------
7 -------
8 * Robert Kern
8 * Robert Kern
9 * Fernando Perez
9 * Fernando Perez
10 * Thomas Kluyver
10 * Thomas Kluyver
11 """
11 """
12
12
13 # Note: though it might be more natural to name this module 'compiler', that
13 # Note: though it might be more natural to name this module 'compiler', that
14 # name is in the stdlib and name collisions with the stdlib tend to produce
14 # name is in the stdlib and name collisions with the stdlib tend to produce
15 # weird problems (often with third-party tools).
15 # weird problems (often with third-party tools).
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Copyright (C) 2010-2011 The IPython Development Team.
18 # Copyright (C) 2010-2011 The IPython Development Team.
19 #
19 #
20 # Distributed under the terms of the BSD License.
20 # Distributed under the terms of the BSD License.
21 #
21 #
22 # The full license is in the file COPYING.txt, distributed with this software.
22 # The full license is in the file COPYING.txt, distributed with this software.
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Imports
26 # Imports
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28
28
29 # Stdlib imports
29 # Stdlib imports
30 import __future__
30 import __future__
31 from ast import PyCF_ONLY_AST
31 from ast import PyCF_ONLY_AST
32 import codeop
32 import codeop
33 import functools
33 import functools
34 import hashlib
34 import hashlib
35 import linecache
35 import linecache
36 import operator
36 import operator
37 import time
37 from contextlib import contextmanager
38 from contextlib import contextmanager
38
39
39 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
40 # Constants
41 # Constants
41 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
42
43
43 # Roughly equal to PyCF_MASK | PyCF_MASK_OBSOLETE as defined in pythonrun.h,
44 # Roughly equal to PyCF_MASK | PyCF_MASK_OBSOLETE as defined in pythonrun.h,
44 # this is used as a bitmask to extract future-related code flags.
45 # this is used as a bitmask to extract future-related code flags.
45 PyCF_MASK = functools.reduce(operator.or_,
46 PyCF_MASK = functools.reduce(operator.or_,
46 (getattr(__future__, fname).compiler_flag
47 (getattr(__future__, fname).compiler_flag
47 for fname in __future__.all_feature_names))
48 for fname in __future__.all_feature_names))
48
49
49 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
50 # Local utilities
51 # Local utilities
51 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
52
53
53 def code_name(code, number=0):
54 def code_name(code, number=0):
54 """ Compute a (probably) unique name for code for caching.
55 """ Compute a (probably) unique name for code for caching.
55
56
56 This now expects code to be unicode.
57 This now expects code to be unicode.
57 """
58 """
58 hash_digest = hashlib.sha1(code.encode("utf-8")).hexdigest()
59 hash_digest = hashlib.sha1(code.encode("utf-8")).hexdigest()
59 # Include the number and 12 characters of the hash in the name. It's
60 # Include the number and 12 characters of the hash in the name. It's
60 # pretty much impossible that in a single session we'll have collisions
61 # pretty much impossible that in a single session we'll have collisions
61 # even with truncated hashes, and the full one makes tracebacks too long
62 # even with truncated hashes, and the full one makes tracebacks too long
62 return '<ipython-input-{0}-{1}>'.format(number, hash_digest[:12])
63 return '<ipython-input-{0}-{1}>'.format(number, hash_digest[:12])
63
64
64 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
65 # Classes and functions
66 # Classes and functions
66 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
67
68
68 class CachingCompiler(codeop.Compile):
69 class CachingCompiler(codeop.Compile):
69 """A compiler that caches code compiled from interactive statements.
70 """A compiler that caches code compiled from interactive statements.
70 """
71 """
71
72
72 def __init__(self):
73 def __init__(self):
73 codeop.Compile.__init__(self)
74 codeop.Compile.__init__(self)
74
75
75 # Caching a dictionary { filename: execution_count } for nicely
76 # Caching a dictionary { filename: execution_count } for nicely
76 # rendered tracebacks. The filename corresponds to the filename
77 # rendered tracebacks. The filename corresponds to the filename
77 # argument used for the builtins.compile function.
78 # argument used for the builtins.compile function.
78 self._filename_map = {}
79 self._filename_map = {}
79
80
80 def ast_parse(self, source, filename='<unknown>', symbol='exec'):
81 def ast_parse(self, source, filename='<unknown>', symbol='exec'):
81 """Parse code to an AST with the current compiler flags active.
82 """Parse code to an AST with the current compiler flags active.
82
83
83 Arguments are exactly the same as ast.parse (in the standard library),
84 Arguments are exactly the same as ast.parse (in the standard library),
84 and are passed to the built-in compile function."""
85 and are passed to the built-in compile function."""
85 return compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1)
86 return compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1)
86
87
87 def reset_compiler_flags(self):
88 def reset_compiler_flags(self):
88 """Reset compiler flags to default state."""
89 """Reset compiler flags to default state."""
89 # This value is copied from codeop.Compile.__init__, so if that ever
90 # This value is copied from codeop.Compile.__init__, so if that ever
90 # changes, it will need to be updated.
91 # changes, it will need to be updated.
91 self.flags = codeop.PyCF_DONT_IMPLY_DEDENT
92 self.flags = codeop.PyCF_DONT_IMPLY_DEDENT
92
93
93 @property
94 @property
94 def compiler_flags(self):
95 def compiler_flags(self):
95 """Flags currently active in the compilation process.
96 """Flags currently active in the compilation process.
96 """
97 """
97 return self.flags
98 return self.flags
98
99
99 def get_code_name(self, raw_code, transformed_code, number):
100 def get_code_name(self, raw_code, transformed_code, number):
100 """Compute filename given the code, and the cell number.
101 """Compute filename given the code, and the cell number.
101
102
102 Parameters
103 Parameters
103 ----------
104 ----------
104 raw_code : str
105 raw_code : str
105 The raw cell code.
106 The raw cell code.
106 transformed_code : str
107 transformed_code : str
107 The executable Python source code to cache and compile.
108 The executable Python source code to cache and compile.
108 number : int
109 number : int
109 A number which forms part of the code's name. Used for the execution
110 A number which forms part of the code's name. Used for the execution
110 counter.
111 counter.
111
112
112 Returns
113 Returns
113 -------
114 -------
114 The computed filename.
115 The computed filename.
115 """
116 """
116 return code_name(transformed_code, number)
117 return code_name(transformed_code, number)
117
118
118 def format_code_name(self, name):
119 def format_code_name(self, name):
119 """Return a user-friendly label and name for a code block.
120 """Return a user-friendly label and name for a code block.
120
121
121 Parameters
122 Parameters
122 ----------
123 ----------
123 name : str
124 name : str
124 The name for the code block returned from get_code_name
125 The name for the code block returned from get_code_name
125
126
126 Returns
127 Returns
127 -------
128 -------
128 A (label, name) pair that can be used in tracebacks, or None if the default formatting should be used.
129 A (label, name) pair that can be used in tracebacks, or None if the default formatting should be used.
129 """
130 """
130 if name in self._filename_map:
131 if name in self._filename_map:
131 return "Cell", "In[%s]" % self._filename_map[name]
132 return "Cell", "In[%s]" % self._filename_map[name]
132
133
133 def cache(self, transformed_code, number=0, raw_code=None):
134 def cache(self, transformed_code, number=0, raw_code=None):
134 """Make a name for a block of code, and cache the code.
135 """Make a name for a block of code, and cache the code.
135
136
136 Parameters
137 Parameters
137 ----------
138 ----------
138 transformed_code : str
139 transformed_code : str
139 The executable Python source code to cache and compile.
140 The executable Python source code to cache and compile.
140 number : int
141 number : int
141 A number which forms part of the code's name. Used for the execution
142 A number which forms part of the code's name. Used for the execution
142 counter.
143 counter.
143 raw_code : str
144 raw_code : str
144 The raw code before transformation, if None, set to `transformed_code`.
145 The raw code before transformation, if None, set to `transformed_code`.
145
146
146 Returns
147 Returns
147 -------
148 -------
148 The name of the cached code (as a string). Pass this as the filename
149 The name of the cached code (as a string). Pass this as the filename
149 argument to compilation, so that tracebacks are correctly hooked up.
150 argument to compilation, so that tracebacks are correctly hooked up.
150 """
151 """
151 if raw_code is None:
152 if raw_code is None:
152 raw_code = transformed_code
153 raw_code = transformed_code
153
154
154 name = self.get_code_name(raw_code, transformed_code, number)
155 name = self.get_code_name(raw_code, transformed_code, number)
155
156
156 # Save the execution count
157 # Save the execution count
157 self._filename_map[name] = number
158 self._filename_map[name] = number
158
159
159 # Since Python 2.5, setting mtime to `None` means the lines will
160 # Since Python 2.5, setting mtime to `None` means the lines will
160 # never be removed by `linecache.checkcache`. This means all the
161 # never be removed by `linecache.checkcache`. This means all the
161 # monkeypatching has *never* been necessary, since this code was
162 # monkeypatching has *never* been necessary, since this code was
162 # only added in 2010, at which point IPython had already stopped
163 # only added in 2010, at which point IPython had already stopped
163 # supporting Python 2.4.
164 # supporting Python 2.4.
164 #
165 #
165 # Note that `linecache.clearcache` and `linecache.updatecache` may
166 # Note that `linecache.clearcache` and `linecache.updatecache` may
166 # still remove our code from the cache, but those show explicit
167 # still remove our code from the cache, but those show explicit
167 # intent, and we should not try to interfere. Normally the former
168 # intent, and we should not try to interfere. Normally the former
168 # is never called except when out of memory, and the latter is only
169 # is never called except when out of memory, and the latter is only
169 # called for lines *not* in the cache.
170 # called for lines *not* in the cache.
170 entry = (
171 entry = (
171 len(transformed_code),
172 len(transformed_code),
172 None,
173 None,
173 [line + "\n" for line in transformed_code.splitlines()],
174 [line + "\n" for line in transformed_code.splitlines()],
174 name,
175 name,
175 )
176 )
176 linecache.cache[name] = entry
177 linecache.cache[name] = entry
177 return name
178 return name
178
179
179 @contextmanager
180 @contextmanager
180 def extra_flags(self, flags):
181 def extra_flags(self, flags):
181 ## bits that we'll set to 1
182 ## bits that we'll set to 1
182 turn_on_bits = ~self.flags & flags
183 turn_on_bits = ~self.flags & flags
183
184
184
185
185 self.flags = self.flags | flags
186 self.flags = self.flags | flags
186 try:
187 try:
187 yield
188 yield
188 finally:
189 finally:
189 # turn off only the bits we turned on so that something like
190 # turn off only the bits we turned on so that something like
190 # __future__ that set flags stays.
191 # __future__ that set flags stays.
191 self.flags &= ~turn_on_bits
192 self.flags &= ~turn_on_bits
192
193
193
194
194 def check_linecache_ipython(*args):
195 def check_linecache_ipython(*args):
195 """Deprecated since IPython 8.6. Call linecache.checkcache() directly.
196 """Deprecated since IPython 8.6. Call linecache.checkcache() directly.
196
197
197 It was already not necessary to call this function directly. If no
198 It was already not necessary to call this function directly. If no
198 CachingCompiler had been created, this function would fail badly. If
199 CachingCompiler had been created, this function would fail badly. If
199 an instance had been created, this function would've been monkeypatched
200 an instance had been created, this function would've been monkeypatched
200 into place.
201 into place.
201
202
202 As of IPython 8.6, the monkeypatching has gone away entirely. But there
203 As of IPython 8.6, the monkeypatching has gone away entirely. But there
203 were still internal callers of this function, so maybe external callers
204 were still internal callers of this function, so maybe external callers
204 also existed?
205 also existed?
205 """
206 """
206 import warnings
207 import warnings
207
208
208 warnings.warn(
209 warnings.warn(
209 "Deprecated Since IPython 8.6, Just call linecache.checkcache() directly.",
210 "Deprecated Since IPython 8.6, Just call linecache.checkcache() directly.",
210 DeprecationWarning,
211 DeprecationWarning,
211 stacklevel=2,
212 stacklevel=2,
212 )
213 )
213 linecache.checkcache()
214 linecache.checkcache()
@@ -1,3409 +1,3410
1 """Completion for IPython.
1 """Completion for IPython.
2
2
3 This module started as fork of the rlcompleter module in the Python standard
3 This module started as fork of the rlcompleter module in the Python standard
4 library. The original enhancements made to rlcompleter have been sent
4 library. The original enhancements made to rlcompleter have been sent
5 upstream and were accepted as of Python 2.3,
5 upstream and were accepted as of Python 2.3,
6
6
7 This module now support a wide variety of completion mechanism both available
7 This module now support a wide variety of completion mechanism both available
8 for normal classic Python code, as well as completer for IPython specific
8 for normal classic Python code, as well as completer for IPython specific
9 Syntax like magics.
9 Syntax like magics.
10
10
11 Latex and Unicode completion
11 Latex and Unicode completion
12 ============================
12 ============================
13
13
14 IPython and compatible frontends not only can complete your code, but can help
14 IPython and compatible frontends not only can complete your code, but can help
15 you to input a wide range of characters. In particular we allow you to insert
15 you to input a wide range of characters. In particular we allow you to insert
16 a unicode character using the tab completion mechanism.
16 a unicode character using the tab completion mechanism.
17
17
18 Forward latex/unicode completion
18 Forward latex/unicode completion
19 --------------------------------
19 --------------------------------
20
20
21 Forward completion allows you to easily type a unicode character using its latex
21 Forward completion allows you to easily type a unicode character using its latex
22 name, or unicode long description. To do so type a backslash follow by the
22 name, or unicode long description. To do so type a backslash follow by the
23 relevant name and press tab:
23 relevant name and press tab:
24
24
25
25
26 Using latex completion:
26 Using latex completion:
27
27
28 .. code::
28 .. code::
29
29
30 \\alpha<tab>
30 \\alpha<tab>
31 α
31 α
32
32
33 or using unicode completion:
33 or using unicode completion:
34
34
35
35
36 .. code::
36 .. code::
37
37
38 \\GREEK SMALL LETTER ALPHA<tab>
38 \\GREEK SMALL LETTER ALPHA<tab>
39 α
39 α
40
40
41
41
42 Only valid Python identifiers will complete. Combining characters (like arrow or
42 Only valid Python identifiers will complete. Combining characters (like arrow or
43 dots) are also available, unlike latex they need to be put after the their
43 dots) are also available, unlike latex they need to be put after the their
44 counterpart that is to say, ``F\\\\vec<tab>`` is correct, not ``\\\\vec<tab>F``.
44 counterpart that is to say, ``F\\\\vec<tab>`` is correct, not ``\\\\vec<tab>F``.
45
45
46 Some browsers are known to display combining characters incorrectly.
46 Some browsers are known to display combining characters incorrectly.
47
47
48 Backward latex completion
48 Backward latex completion
49 -------------------------
49 -------------------------
50
50
51 It is sometime challenging to know how to type a character, if you are using
51 It is sometime challenging to know how to type a character, if you are using
52 IPython, or any compatible frontend you can prepend backslash to the character
52 IPython, or any compatible frontend you can prepend backslash to the character
53 and press :kbd:`Tab` to expand it to its latex form.
53 and press :kbd:`Tab` to expand it to its latex form.
54
54
55 .. code::
55 .. code::
56
56
57 \\α<tab>
57 \\α<tab>
58 \\alpha
58 \\alpha
59
59
60
60
61 Both forward and backward completions can be deactivated by setting the
61 Both forward and backward completions can be deactivated by setting the
62 :std:configtrait:`Completer.backslash_combining_completions` option to
62 :std:configtrait:`Completer.backslash_combining_completions` option to
63 ``False``.
63 ``False``.
64
64
65
65
66 Experimental
66 Experimental
67 ============
67 ============
68
68
69 Starting with IPython 6.0, this module can make use of the Jedi library to
69 Starting with IPython 6.0, this module can make use of the Jedi library to
70 generate completions both using static analysis of the code, and dynamically
70 generate completions both using static analysis of the code, and dynamically
71 inspecting multiple namespaces. Jedi is an autocompletion and static analysis
71 inspecting multiple namespaces. Jedi is an autocompletion and static analysis
72 for Python. The APIs attached to this new mechanism is unstable and will
72 for Python. The APIs attached to this new mechanism is unstable and will
73 raise unless use in an :any:`provisionalcompleter` context manager.
73 raise unless use in an :any:`provisionalcompleter` context manager.
74
74
75 You will find that the following are experimental:
75 You will find that the following are experimental:
76
76
77 - :any:`provisionalcompleter`
77 - :any:`provisionalcompleter`
78 - :any:`IPCompleter.completions`
78 - :any:`IPCompleter.completions`
79 - :any:`Completion`
79 - :any:`Completion`
80 - :any:`rectify_completions`
80 - :any:`rectify_completions`
81
81
82 .. note::
82 .. note::
83
83
84 better name for :any:`rectify_completions` ?
84 better name for :any:`rectify_completions` ?
85
85
86 We welcome any feedback on these new API, and we also encourage you to try this
86 We welcome any feedback on these new API, and we also encourage you to try this
87 module in debug mode (start IPython with ``--Completer.debug=True``) in order
87 module in debug mode (start IPython with ``--Completer.debug=True``) in order
88 to have extra logging information if :any:`jedi` is crashing, or if current
88 to have extra logging information if :any:`jedi` is crashing, or if current
89 IPython completer pending deprecations are returning results not yet handled
89 IPython completer pending deprecations are returning results not yet handled
90 by :any:`jedi`
90 by :any:`jedi`
91
91
92 Using Jedi for tab completion allow snippets like the following to work without
92 Using Jedi for tab completion allow snippets like the following to work without
93 having to execute any code:
93 having to execute any code:
94
94
95 >>> myvar = ['hello', 42]
95 >>> myvar = ['hello', 42]
96 ... myvar[1].bi<tab>
96 ... myvar[1].bi<tab>
97
97
98 Tab completion will be able to infer that ``myvar[1]`` is a real number without
98 Tab completion will be able to infer that ``myvar[1]`` is a real number without
99 executing almost any code unlike the deprecated :any:`IPCompleter.greedy`
99 executing almost any code unlike the deprecated :any:`IPCompleter.greedy`
100 option.
100 option.
101
101
102 Be sure to update :any:`jedi` to the latest stable version or to try the
102 Be sure to update :any:`jedi` to the latest stable version or to try the
103 current development version to get better completions.
103 current development version to get better completions.
104
104
105 Matchers
105 Matchers
106 ========
106 ========
107
107
108 All completions routines are implemented using unified *Matchers* API.
108 All completions routines are implemented using unified *Matchers* API.
109 The matchers API is provisional and subject to change without notice.
109 The matchers API is provisional and subject to change without notice.
110
110
111 The built-in matchers include:
111 The built-in matchers include:
112
112
113 - :any:`IPCompleter.dict_key_matcher`: dictionary key completions,
113 - :any:`IPCompleter.dict_key_matcher`: dictionary key completions,
114 - :any:`IPCompleter.magic_matcher`: completions for magics,
114 - :any:`IPCompleter.magic_matcher`: completions for magics,
115 - :any:`IPCompleter.unicode_name_matcher`,
115 - :any:`IPCompleter.unicode_name_matcher`,
116 :any:`IPCompleter.fwd_unicode_matcher`
116 :any:`IPCompleter.fwd_unicode_matcher`
117 and :any:`IPCompleter.latex_name_matcher`: see `Forward latex/unicode completion`_,
117 and :any:`IPCompleter.latex_name_matcher`: see `Forward latex/unicode completion`_,
118 - :any:`back_unicode_name_matcher` and :any:`back_latex_name_matcher`: see `Backward latex completion`_,
118 - :any:`back_unicode_name_matcher` and :any:`back_latex_name_matcher`: see `Backward latex completion`_,
119 - :any:`IPCompleter.file_matcher`: paths to files and directories,
119 - :any:`IPCompleter.file_matcher`: paths to files and directories,
120 - :any:`IPCompleter.python_func_kw_matcher` - function keywords,
120 - :any:`IPCompleter.python_func_kw_matcher` - function keywords,
121 - :any:`IPCompleter.python_matches` - globals and attributes (v1 API),
121 - :any:`IPCompleter.python_matches` - globals and attributes (v1 API),
122 - ``IPCompleter.jedi_matcher`` - static analysis with Jedi,
122 - ``IPCompleter.jedi_matcher`` - static analysis with Jedi,
123 - :any:`IPCompleter.custom_completer_matcher` - pluggable completer with a default
123 - :any:`IPCompleter.custom_completer_matcher` - pluggable completer with a default
124 implementation in :any:`InteractiveShell` which uses IPython hooks system
124 implementation in :any:`InteractiveShell` which uses IPython hooks system
125 (`complete_command`) with string dispatch (including regular expressions).
125 (`complete_command`) with string dispatch (including regular expressions).
126 Differently to other matchers, ``custom_completer_matcher`` will not suppress
126 Differently to other matchers, ``custom_completer_matcher`` will not suppress
127 Jedi results to match behaviour in earlier IPython versions.
127 Jedi results to match behaviour in earlier IPython versions.
128
128
129 Custom matchers can be added by appending to ``IPCompleter.custom_matchers`` list.
129 Custom matchers can be added by appending to ``IPCompleter.custom_matchers`` list.
130
130
131 Matcher API
131 Matcher API
132 -----------
132 -----------
133
133
134 Simplifying some details, the ``Matcher`` interface can described as
134 Simplifying some details, the ``Matcher`` interface can described as
135
135
136 .. code-block::
136 .. code-block::
137
137
138 MatcherAPIv1 = Callable[[str], list[str]]
138 MatcherAPIv1 = Callable[[str], list[str]]
139 MatcherAPIv2 = Callable[[CompletionContext], SimpleMatcherResult]
139 MatcherAPIv2 = Callable[[CompletionContext], SimpleMatcherResult]
140
140
141 Matcher = MatcherAPIv1 | MatcherAPIv2
141 Matcher = MatcherAPIv1 | MatcherAPIv2
142
142
143 The ``MatcherAPIv1`` reflects the matcher API as available prior to IPython 8.6.0
143 The ``MatcherAPIv1`` reflects the matcher API as available prior to IPython 8.6.0
144 and remains supported as a simplest way for generating completions. This is also
144 and remains supported as a simplest way for generating completions. This is also
145 currently the only API supported by the IPython hooks system `complete_command`.
145 currently the only API supported by the IPython hooks system `complete_command`.
146
146
147 To distinguish between matcher versions ``matcher_api_version`` attribute is used.
147 To distinguish between matcher versions ``matcher_api_version`` attribute is used.
148 More precisely, the API allows to omit ``matcher_api_version`` for v1 Matchers,
148 More precisely, the API allows to omit ``matcher_api_version`` for v1 Matchers,
149 and requires a literal ``2`` for v2 Matchers.
149 and requires a literal ``2`` for v2 Matchers.
150
150
151 Once the API stabilises future versions may relax the requirement for specifying
151 Once the API stabilises future versions may relax the requirement for specifying
152 ``matcher_api_version`` by switching to :any:`functools.singledispatch`, therefore
152 ``matcher_api_version`` by switching to :any:`functools.singledispatch`, therefore
153 please do not rely on the presence of ``matcher_api_version`` for any purposes.
153 please do not rely on the presence of ``matcher_api_version`` for any purposes.
154
154
155 Suppression of competing matchers
155 Suppression of competing matchers
156 ---------------------------------
156 ---------------------------------
157
157
158 By default results from all matchers are combined, in the order determined by
158 By default results from all matchers are combined, in the order determined by
159 their priority. Matchers can request to suppress results from subsequent
159 their priority. Matchers can request to suppress results from subsequent
160 matchers by setting ``suppress`` to ``True`` in the ``MatcherResult``.
160 matchers by setting ``suppress`` to ``True`` in the ``MatcherResult``.
161
161
162 When multiple matchers simultaneously request suppression, the results from of
162 When multiple matchers simultaneously request suppression, the results from of
163 the matcher with higher priority will be returned.
163 the matcher with higher priority will be returned.
164
164
165 Sometimes it is desirable to suppress most but not all other matchers;
165 Sometimes it is desirable to suppress most but not all other matchers;
166 this can be achieved by adding a set of identifiers of matchers which
166 this can be achieved by adding a set of identifiers of matchers which
167 should not be suppressed to ``MatcherResult`` under ``do_not_suppress`` key.
167 should not be suppressed to ``MatcherResult`` under ``do_not_suppress`` key.
168
168
169 The suppression behaviour can is user-configurable via
169 The suppression behaviour can is user-configurable via
170 :std:configtrait:`IPCompleter.suppress_competing_matchers`.
170 :std:configtrait:`IPCompleter.suppress_competing_matchers`.
171 """
171 """
172
172
173
173
174 # Copyright (c) IPython Development Team.
174 # Copyright (c) IPython Development Team.
175 # Distributed under the terms of the Modified BSD License.
175 # Distributed under the terms of the Modified BSD License.
176 #
176 #
177 # Some of this code originated from rlcompleter in the Python standard library
177 # Some of this code originated from rlcompleter in the Python standard library
178 # Copyright (C) 2001 Python Software Foundation, www.python.org
178 # Copyright (C) 2001 Python Software Foundation, www.python.org
179
179
180 from __future__ import annotations
180 from __future__ import annotations
181 import builtins as builtin_mod
181 import builtins as builtin_mod
182 import enum
182 import enum
183 import glob
183 import glob
184 import inspect
184 import inspect
185 import itertools
185 import itertools
186 import keyword
186 import keyword
187 import ast
187 import ast
188 import os
188 import os
189 import re
189 import re
190 import string
190 import string
191 import sys
191 import sys
192 import tokenize
192 import tokenize
193 import time
193 import time
194 import unicodedata
194 import unicodedata
195 import uuid
195 import uuid
196 import warnings
196 import warnings
197 from ast import literal_eval
197 from ast import literal_eval
198 from collections import defaultdict
198 from collections import defaultdict
199 from contextlib import contextmanager
199 from contextlib import contextmanager
200 from dataclasses import dataclass
200 from dataclasses import dataclass
201 from functools import cached_property, partial
201 from functools import cached_property, partial
202 from types import SimpleNamespace
202 from types import SimpleNamespace
203 from typing import (
203 from typing import (
204 Iterable,
204 Iterable,
205 Iterator,
205 Iterator,
206 List,
206 List,
207 Tuple,
207 Tuple,
208 Union,
208 Union,
209 Any,
209 Any,
210 Sequence,
210 Sequence,
211 Dict,
211 Dict,
212 Optional,
212 Optional,
213 TYPE_CHECKING,
213 TYPE_CHECKING,
214 Set,
214 Set,
215 Sized,
215 Sized,
216 TypeVar,
216 TypeVar,
217 Literal,
217 Literal,
218 )
218 )
219
219
220 from IPython.core.guarded_eval import guarded_eval, EvaluationContext
220 from IPython.core.guarded_eval import guarded_eval, EvaluationContext
221 from IPython.core.error import TryNext
221 from IPython.core.error import TryNext
222 from IPython.core.inputtransformer2 import ESC_MAGIC
222 from IPython.core.inputtransformer2 import ESC_MAGIC
223 from IPython.core.latex_symbols import latex_symbols, reverse_latex_symbol
223 from IPython.core.latex_symbols import latex_symbols, reverse_latex_symbol
224 from IPython.core.oinspect import InspectColors
224 from IPython.core.oinspect import InspectColors
225 from IPython.testing.skipdoctest import skip_doctest
225 from IPython.testing.skipdoctest import skip_doctest
226 from IPython.utils import generics
226 from IPython.utils import generics
227 from IPython.utils.decorators import sphinx_options
227 from IPython.utils.decorators import sphinx_options
228 from IPython.utils.dir2 import dir2, get_real_method
228 from IPython.utils.dir2 import dir2, get_real_method
229 from IPython.utils.docs import GENERATING_DOCUMENTATION
229 from IPython.utils.path import ensure_dir_exists
230 from IPython.utils.path import ensure_dir_exists
230 from IPython.utils.process import arg_split
231 from IPython.utils.process import arg_split
231 from traitlets import (
232 from traitlets import (
232 Bool,
233 Bool,
233 Enum,
234 Enum,
234 Int,
235 Int,
235 List as ListTrait,
236 List as ListTrait,
236 Unicode,
237 Unicode,
237 Dict as DictTrait,
238 Dict as DictTrait,
238 Union as UnionTrait,
239 Union as UnionTrait,
239 observe,
240 observe,
240 )
241 )
241 from traitlets.config.configurable import Configurable
242 from traitlets.config.configurable import Configurable
242
243
243 import __main__
244 import __main__
244
245
245 from typing import cast
246 from typing import cast
246
247
247 if sys.version_info < (3, 12):
248 if sys.version_info < (3, 12):
248 from typing_extensions import TypedDict, NotRequired, Protocol, TypeAlias, TypeGuard
249 from typing_extensions import TypedDict, NotRequired, Protocol, TypeAlias, TypeGuard
249 else:
250 else:
250 from typing import TypedDict, NotRequired, Protocol, TypeAlias, TypeGuard
251 from typing import TypedDict, NotRequired, Protocol, TypeAlias, TypeGuard
251
252
252
253
253 # skip module docstests
254 # skip module docstests
254 __skip_doctest__ = True
255 __skip_doctest__ = True
255
256
256
257
257 try:
258 try:
258 import jedi
259 import jedi
259 jedi.settings.case_insensitive_completion = False
260 jedi.settings.case_insensitive_completion = False
260 import jedi.api.helpers
261 import jedi.api.helpers
261 import jedi.api.classes
262 import jedi.api.classes
262 JEDI_INSTALLED = True
263 JEDI_INSTALLED = True
263 except ImportError:
264 except ImportError:
264 JEDI_INSTALLED = False
265 JEDI_INSTALLED = False
265
266
266
267
267 # -----------------------------------------------------------------------------
268 # -----------------------------------------------------------------------------
268 # Globals
269 # Globals
269 #-----------------------------------------------------------------------------
270 #-----------------------------------------------------------------------------
270
271
271 # ranges where we have most of the valid unicode names. We could be more finer
272 # ranges where we have most of the valid unicode names. We could be more finer
272 # grained but is it worth it for performance While unicode have character in the
273 # grained but is it worth it for performance While unicode have character in the
273 # range 0, 0x110000, we seem to have name for about 10% of those. (131808 as I
274 # range 0, 0x110000, we seem to have name for about 10% of those. (131808 as I
274 # write this). With below range we cover them all, with a density of ~67%
275 # write this). With below range we cover them all, with a density of ~67%
275 # biggest next gap we consider only adds up about 1% density and there are 600
276 # biggest next gap we consider only adds up about 1% density and there are 600
276 # gaps that would need hard coding.
277 # gaps that would need hard coding.
277 _UNICODE_RANGES = [(32, 0x323B0), (0xE0001, 0xE01F0)]
278 _UNICODE_RANGES = [(32, 0x323B0), (0xE0001, 0xE01F0)]
278
279
279 # Public API
280 # Public API
280 __all__ = ["Completer", "IPCompleter"]
281 __all__ = ["Completer", "IPCompleter"]
281
282
282 if sys.platform == 'win32':
283 if sys.platform == 'win32':
283 PROTECTABLES = ' '
284 PROTECTABLES = ' '
284 else:
285 else:
285 PROTECTABLES = ' ()[]{}?=\\|;:\'#*"^&'
286 PROTECTABLES = ' ()[]{}?=\\|;:\'#*"^&'
286
287
287 # Protect against returning an enormous number of completions which the frontend
288 # Protect against returning an enormous number of completions which the frontend
288 # may have trouble processing.
289 # may have trouble processing.
289 MATCHES_LIMIT = 500
290 MATCHES_LIMIT = 500
290
291
291 # Completion type reported when no type can be inferred.
292 # Completion type reported when no type can be inferred.
292 _UNKNOWN_TYPE = "<unknown>"
293 _UNKNOWN_TYPE = "<unknown>"
293
294
294 # sentinel value to signal lack of a match
295 # sentinel value to signal lack of a match
295 not_found = object()
296 not_found = object()
296
297
297 class ProvisionalCompleterWarning(FutureWarning):
298 class ProvisionalCompleterWarning(FutureWarning):
298 """
299 """
299 Exception raise by an experimental feature in this module.
300 Exception raise by an experimental feature in this module.
300
301
301 Wrap code in :any:`provisionalcompleter` context manager if you
302 Wrap code in :any:`provisionalcompleter` context manager if you
302 are certain you want to use an unstable feature.
303 are certain you want to use an unstable feature.
303 """
304 """
304 pass
305 pass
305
306
306 warnings.filterwarnings('error', category=ProvisionalCompleterWarning)
307 warnings.filterwarnings('error', category=ProvisionalCompleterWarning)
307
308
308
309
309 @skip_doctest
310 @skip_doctest
310 @contextmanager
311 @contextmanager
311 def provisionalcompleter(action='ignore'):
312 def provisionalcompleter(action='ignore'):
312 """
313 """
313 This context manager has to be used in any place where unstable completer
314 This context manager has to be used in any place where unstable completer
314 behavior and API may be called.
315 behavior and API may be called.
315
316
316 >>> with provisionalcompleter():
317 >>> with provisionalcompleter():
317 ... completer.do_experimental_things() # works
318 ... completer.do_experimental_things() # works
318
319
319 >>> completer.do_experimental_things() # raises.
320 >>> completer.do_experimental_things() # raises.
320
321
321 .. note::
322 .. note::
322
323
323 Unstable
324 Unstable
324
325
325 By using this context manager you agree that the API in use may change
326 By using this context manager you agree that the API in use may change
326 without warning, and that you won't complain if they do so.
327 without warning, and that you won't complain if they do so.
327
328
328 You also understand that, if the API is not to your liking, you should report
329 You also understand that, if the API is not to your liking, you should report
329 a bug to explain your use case upstream.
330 a bug to explain your use case upstream.
330
331
331 We'll be happy to get your feedback, feature requests, and improvements on
332 We'll be happy to get your feedback, feature requests, and improvements on
332 any of the unstable APIs!
333 any of the unstable APIs!
333 """
334 """
334 with warnings.catch_warnings():
335 with warnings.catch_warnings():
335 warnings.filterwarnings(action, category=ProvisionalCompleterWarning)
336 warnings.filterwarnings(action, category=ProvisionalCompleterWarning)
336 yield
337 yield
337
338
338
339
339 def has_open_quotes(s: str) -> Union[str, bool]:
340 def has_open_quotes(s: str) -> Union[str, bool]:
340 """Return whether a string has open quotes.
341 """Return whether a string has open quotes.
341
342
342 This simply counts whether the number of quote characters of either type in
343 This simply counts whether the number of quote characters of either type in
343 the string is odd.
344 the string is odd.
344
345
345 Returns
346 Returns
346 -------
347 -------
347 If there is an open quote, the quote character is returned. Else, return
348 If there is an open quote, the quote character is returned. Else, return
348 False.
349 False.
349 """
350 """
350 # We check " first, then ', so complex cases with nested quotes will get
351 # We check " first, then ', so complex cases with nested quotes will get
351 # the " to take precedence.
352 # the " to take precedence.
352 if s.count('"') % 2:
353 if s.count('"') % 2:
353 return '"'
354 return '"'
354 elif s.count("'") % 2:
355 elif s.count("'") % 2:
355 return "'"
356 return "'"
356 else:
357 else:
357 return False
358 return False
358
359
359
360
360 def protect_filename(s: str, protectables: str = PROTECTABLES) -> str:
361 def protect_filename(s: str, protectables: str = PROTECTABLES) -> str:
361 """Escape a string to protect certain characters."""
362 """Escape a string to protect certain characters."""
362 if set(s) & set(protectables):
363 if set(s) & set(protectables):
363 if sys.platform == "win32":
364 if sys.platform == "win32":
364 return '"' + s + '"'
365 return '"' + s + '"'
365 else:
366 else:
366 return "".join(("\\" + c if c in protectables else c) for c in s)
367 return "".join(("\\" + c if c in protectables else c) for c in s)
367 else:
368 else:
368 return s
369 return s
369
370
370
371
371 def expand_user(path:str) -> Tuple[str, bool, str]:
372 def expand_user(path:str) -> Tuple[str, bool, str]:
372 """Expand ``~``-style usernames in strings.
373 """Expand ``~``-style usernames in strings.
373
374
374 This is similar to :func:`os.path.expanduser`, but it computes and returns
375 This is similar to :func:`os.path.expanduser`, but it computes and returns
375 extra information that will be useful if the input was being used in
376 extra information that will be useful if the input was being used in
376 computing completions, and you wish to return the completions with the
377 computing completions, and you wish to return the completions with the
377 original '~' instead of its expanded value.
378 original '~' instead of its expanded value.
378
379
379 Parameters
380 Parameters
380 ----------
381 ----------
381 path : str
382 path : str
382 String to be expanded. If no ~ is present, the output is the same as the
383 String to be expanded. If no ~ is present, the output is the same as the
383 input.
384 input.
384
385
385 Returns
386 Returns
386 -------
387 -------
387 newpath : str
388 newpath : str
388 Result of ~ expansion in the input path.
389 Result of ~ expansion in the input path.
389 tilde_expand : bool
390 tilde_expand : bool
390 Whether any expansion was performed or not.
391 Whether any expansion was performed or not.
391 tilde_val : str
392 tilde_val : str
392 The value that ~ was replaced with.
393 The value that ~ was replaced with.
393 """
394 """
394 # Default values
395 # Default values
395 tilde_expand = False
396 tilde_expand = False
396 tilde_val = ''
397 tilde_val = ''
397 newpath = path
398 newpath = path
398
399
399 if path.startswith('~'):
400 if path.startswith('~'):
400 tilde_expand = True
401 tilde_expand = True
401 rest = len(path)-1
402 rest = len(path)-1
402 newpath = os.path.expanduser(path)
403 newpath = os.path.expanduser(path)
403 if rest:
404 if rest:
404 tilde_val = newpath[:-rest]
405 tilde_val = newpath[:-rest]
405 else:
406 else:
406 tilde_val = newpath
407 tilde_val = newpath
407
408
408 return newpath, tilde_expand, tilde_val
409 return newpath, tilde_expand, tilde_val
409
410
410
411
411 def compress_user(path:str, tilde_expand:bool, tilde_val:str) -> str:
412 def compress_user(path:str, tilde_expand:bool, tilde_val:str) -> str:
412 """Does the opposite of expand_user, with its outputs.
413 """Does the opposite of expand_user, with its outputs.
413 """
414 """
414 if tilde_expand:
415 if tilde_expand:
415 return path.replace(tilde_val, '~')
416 return path.replace(tilde_val, '~')
416 else:
417 else:
417 return path
418 return path
418
419
419
420
420 def completions_sorting_key(word):
421 def completions_sorting_key(word):
421 """key for sorting completions
422 """key for sorting completions
422
423
423 This does several things:
424 This does several things:
424
425
425 - Demote any completions starting with underscores to the end
426 - Demote any completions starting with underscores to the end
426 - Insert any %magic and %%cellmagic completions in the alphabetical order
427 - Insert any %magic and %%cellmagic completions in the alphabetical order
427 by their name
428 by their name
428 """
429 """
429 prio1, prio2 = 0, 0
430 prio1, prio2 = 0, 0
430
431
431 if word.startswith('__'):
432 if word.startswith('__'):
432 prio1 = 2
433 prio1 = 2
433 elif word.startswith('_'):
434 elif word.startswith('_'):
434 prio1 = 1
435 prio1 = 1
435
436
436 if word.endswith('='):
437 if word.endswith('='):
437 prio1 = -1
438 prio1 = -1
438
439
439 if word.startswith('%%'):
440 if word.startswith('%%'):
440 # If there's another % in there, this is something else, so leave it alone
441 # If there's another % in there, this is something else, so leave it alone
441 if "%" not in word[2:]:
442 if "%" not in word[2:]:
442 word = word[2:]
443 word = word[2:]
443 prio2 = 2
444 prio2 = 2
444 elif word.startswith('%'):
445 elif word.startswith('%'):
445 if "%" not in word[1:]:
446 if "%" not in word[1:]:
446 word = word[1:]
447 word = word[1:]
447 prio2 = 1
448 prio2 = 1
448
449
449 return prio1, word, prio2
450 return prio1, word, prio2
450
451
451
452
452 class _FakeJediCompletion:
453 class _FakeJediCompletion:
453 """
454 """
454 This is a workaround to communicate to the UI that Jedi has crashed and to
455 This is a workaround to communicate to the UI that Jedi has crashed and to
455 report a bug. Will be used only id :any:`IPCompleter.debug` is set to true.
456 report a bug. Will be used only id :any:`IPCompleter.debug` is set to true.
456
457
457 Added in IPython 6.0 so should likely be removed for 7.0
458 Added in IPython 6.0 so should likely be removed for 7.0
458
459
459 """
460 """
460
461
461 def __init__(self, name):
462 def __init__(self, name):
462
463
463 self.name = name
464 self.name = name
464 self.complete = name
465 self.complete = name
465 self.type = 'crashed'
466 self.type = 'crashed'
466 self.name_with_symbols = name
467 self.name_with_symbols = name
467 self.signature = ""
468 self.signature = ""
468 self._origin = "fake"
469 self._origin = "fake"
469 self.text = "crashed"
470 self.text = "crashed"
470
471
471 def __repr__(self):
472 def __repr__(self):
472 return '<Fake completion object jedi has crashed>'
473 return '<Fake completion object jedi has crashed>'
473
474
474
475
475 _JediCompletionLike = Union["jedi.api.Completion", _FakeJediCompletion]
476 _JediCompletionLike = Union["jedi.api.Completion", _FakeJediCompletion]
476
477
477
478
478 class Completion:
479 class Completion:
479 """
480 """
480 Completion object used and returned by IPython completers.
481 Completion object used and returned by IPython completers.
481
482
482 .. warning::
483 .. warning::
483
484
484 Unstable
485 Unstable
485
486
486 This function is unstable, API may change without warning.
487 This function is unstable, API may change without warning.
487 It will also raise unless use in proper context manager.
488 It will also raise unless use in proper context manager.
488
489
489 This act as a middle ground :any:`Completion` object between the
490 This act as a middle ground :any:`Completion` object between the
490 :any:`jedi.api.classes.Completion` object and the Prompt Toolkit completion
491 :any:`jedi.api.classes.Completion` object and the Prompt Toolkit completion
491 object. While Jedi need a lot of information about evaluator and how the
492 object. While Jedi need a lot of information about evaluator and how the
492 code should be ran/inspected, PromptToolkit (and other frontend) mostly
493 code should be ran/inspected, PromptToolkit (and other frontend) mostly
493 need user facing information.
494 need user facing information.
494
495
495 - Which range should be replaced replaced by what.
496 - Which range should be replaced replaced by what.
496 - Some metadata (like completion type), or meta information to displayed to
497 - Some metadata (like completion type), or meta information to displayed to
497 the use user.
498 the use user.
498
499
499 For debugging purpose we can also store the origin of the completion (``jedi``,
500 For debugging purpose we can also store the origin of the completion (``jedi``,
500 ``IPython.python_matches``, ``IPython.magics_matches``...).
501 ``IPython.python_matches``, ``IPython.magics_matches``...).
501 """
502 """
502
503
503 __slots__ = ['start', 'end', 'text', 'type', 'signature', '_origin']
504 __slots__ = ['start', 'end', 'text', 'type', 'signature', '_origin']
504
505
505 def __init__(
506 def __init__(
506 self,
507 self,
507 start: int,
508 start: int,
508 end: int,
509 end: int,
509 text: str,
510 text: str,
510 *,
511 *,
511 type: Optional[str] = None,
512 type: Optional[str] = None,
512 _origin="",
513 _origin="",
513 signature="",
514 signature="",
514 ) -> None:
515 ) -> None:
515 warnings.warn(
516 warnings.warn(
516 "``Completion`` is a provisional API (as of IPython 6.0). "
517 "``Completion`` is a provisional API (as of IPython 6.0). "
517 "It may change without warnings. "
518 "It may change without warnings. "
518 "Use in corresponding context manager.",
519 "Use in corresponding context manager.",
519 category=ProvisionalCompleterWarning,
520 category=ProvisionalCompleterWarning,
520 stacklevel=2,
521 stacklevel=2,
521 )
522 )
522
523
523 self.start = start
524 self.start = start
524 self.end = end
525 self.end = end
525 self.text = text
526 self.text = text
526 self.type = type
527 self.type = type
527 self.signature = signature
528 self.signature = signature
528 self._origin = _origin
529 self._origin = _origin
529
530
530 def __repr__(self):
531 def __repr__(self):
531 return '<Completion start=%s end=%s text=%r type=%r, signature=%r,>' % \
532 return '<Completion start=%s end=%s text=%r type=%r, signature=%r,>' % \
532 (self.start, self.end, self.text, self.type or '?', self.signature or '?')
533 (self.start, self.end, self.text, self.type or '?', self.signature or '?')
533
534
534 def __eq__(self, other) -> bool:
535 def __eq__(self, other) -> bool:
535 """
536 """
536 Equality and hash do not hash the type (as some completer may not be
537 Equality and hash do not hash the type (as some completer may not be
537 able to infer the type), but are use to (partially) de-duplicate
538 able to infer the type), but are use to (partially) de-duplicate
538 completion.
539 completion.
539
540
540 Completely de-duplicating completion is a bit tricker that just
541 Completely de-duplicating completion is a bit tricker that just
541 comparing as it depends on surrounding text, which Completions are not
542 comparing as it depends on surrounding text, which Completions are not
542 aware of.
543 aware of.
543 """
544 """
544 return self.start == other.start and \
545 return self.start == other.start and \
545 self.end == other.end and \
546 self.end == other.end and \
546 self.text == other.text
547 self.text == other.text
547
548
548 def __hash__(self):
549 def __hash__(self):
549 return hash((self.start, self.end, self.text))
550 return hash((self.start, self.end, self.text))
550
551
551
552
552 class SimpleCompletion:
553 class SimpleCompletion:
553 """Completion item to be included in the dictionary returned by new-style Matcher (API v2).
554 """Completion item to be included in the dictionary returned by new-style Matcher (API v2).
554
555
555 .. warning::
556 .. warning::
556
557
557 Provisional
558 Provisional
558
559
559 This class is used to describe the currently supported attributes of
560 This class is used to describe the currently supported attributes of
560 simple completion items, and any additional implementation details
561 simple completion items, and any additional implementation details
561 should not be relied on. Additional attributes may be included in
562 should not be relied on. Additional attributes may be included in
562 future versions, and meaning of text disambiguated from the current
563 future versions, and meaning of text disambiguated from the current
563 dual meaning of "text to insert" and "text to used as a label".
564 dual meaning of "text to insert" and "text to used as a label".
564 """
565 """
565
566
566 __slots__ = ["text", "type"]
567 __slots__ = ["text", "type"]
567
568
568 def __init__(self, text: str, *, type: Optional[str] = None):
569 def __init__(self, text: str, *, type: Optional[str] = None):
569 self.text = text
570 self.text = text
570 self.type = type
571 self.type = type
571
572
572 def __repr__(self):
573 def __repr__(self):
573 return f"<SimpleCompletion text={self.text!r} type={self.type!r}>"
574 return f"<SimpleCompletion text={self.text!r} type={self.type!r}>"
574
575
575
576
576 class _MatcherResultBase(TypedDict):
577 class _MatcherResultBase(TypedDict):
577 """Definition of dictionary to be returned by new-style Matcher (API v2)."""
578 """Definition of dictionary to be returned by new-style Matcher (API v2)."""
578
579
579 #: Suffix of the provided ``CompletionContext.token``, if not given defaults to full token.
580 #: Suffix of the provided ``CompletionContext.token``, if not given defaults to full token.
580 matched_fragment: NotRequired[str]
581 matched_fragment: NotRequired[str]
581
582
582 #: Whether to suppress results from all other matchers (True), some
583 #: Whether to suppress results from all other matchers (True), some
583 #: matchers (set of identifiers) or none (False); default is False.
584 #: matchers (set of identifiers) or none (False); default is False.
584 suppress: NotRequired[Union[bool, Set[str]]]
585 suppress: NotRequired[Union[bool, Set[str]]]
585
586
586 #: Identifiers of matchers which should NOT be suppressed when this matcher
587 #: Identifiers of matchers which should NOT be suppressed when this matcher
587 #: requests to suppress all other matchers; defaults to an empty set.
588 #: requests to suppress all other matchers; defaults to an empty set.
588 do_not_suppress: NotRequired[Set[str]]
589 do_not_suppress: NotRequired[Set[str]]
589
590
590 #: Are completions already ordered and should be left as-is? default is False.
591 #: Are completions already ordered and should be left as-is? default is False.
591 ordered: NotRequired[bool]
592 ordered: NotRequired[bool]
592
593
593
594
594 @sphinx_options(show_inherited_members=True, exclude_inherited_from=["dict"])
595 @sphinx_options(show_inherited_members=True, exclude_inherited_from=["dict"])
595 class SimpleMatcherResult(_MatcherResultBase, TypedDict):
596 class SimpleMatcherResult(_MatcherResultBase, TypedDict):
596 """Result of new-style completion matcher."""
597 """Result of new-style completion matcher."""
597
598
598 # note: TypedDict is added again to the inheritance chain
599 # note: TypedDict is added again to the inheritance chain
599 # in order to get __orig_bases__ for documentation
600 # in order to get __orig_bases__ for documentation
600
601
601 #: List of candidate completions
602 #: List of candidate completions
602 completions: Sequence[SimpleCompletion] | Iterator[SimpleCompletion]
603 completions: Sequence[SimpleCompletion] | Iterator[SimpleCompletion]
603
604
604
605
605 class _JediMatcherResult(_MatcherResultBase):
606 class _JediMatcherResult(_MatcherResultBase):
606 """Matching result returned by Jedi (will be processed differently)"""
607 """Matching result returned by Jedi (will be processed differently)"""
607
608
608 #: list of candidate completions
609 #: list of candidate completions
609 completions: Iterator[_JediCompletionLike]
610 completions: Iterator[_JediCompletionLike]
610
611
611
612
612 AnyMatcherCompletion = Union[_JediCompletionLike, SimpleCompletion]
613 AnyMatcherCompletion = Union[_JediCompletionLike, SimpleCompletion]
613 AnyCompletion = TypeVar("AnyCompletion", AnyMatcherCompletion, Completion)
614 AnyCompletion = TypeVar("AnyCompletion", AnyMatcherCompletion, Completion)
614
615
615
616
616 @dataclass
617 @dataclass
617 class CompletionContext:
618 class CompletionContext:
618 """Completion context provided as an argument to matchers in the Matcher API v2."""
619 """Completion context provided as an argument to matchers in the Matcher API v2."""
619
620
620 # rationale: many legacy matchers relied on completer state (`self.text_until_cursor`)
621 # rationale: many legacy matchers relied on completer state (`self.text_until_cursor`)
621 # which was not explicitly visible as an argument of the matcher, making any refactor
622 # which was not explicitly visible as an argument of the matcher, making any refactor
622 # prone to errors; by explicitly passing `cursor_position` we can decouple the matchers
623 # prone to errors; by explicitly passing `cursor_position` we can decouple the matchers
623 # from the completer, and make substituting them in sub-classes easier.
624 # from the completer, and make substituting them in sub-classes easier.
624
625
625 #: Relevant fragment of code directly preceding the cursor.
626 #: Relevant fragment of code directly preceding the cursor.
626 #: The extraction of token is implemented via splitter heuristic
627 #: The extraction of token is implemented via splitter heuristic
627 #: (following readline behaviour for legacy reasons), which is user configurable
628 #: (following readline behaviour for legacy reasons), which is user configurable
628 #: (by switching the greedy mode).
629 #: (by switching the greedy mode).
629 token: str
630 token: str
630
631
631 #: The full available content of the editor or buffer
632 #: The full available content of the editor or buffer
632 full_text: str
633 full_text: str
633
634
634 #: Cursor position in the line (the same for ``full_text`` and ``text``).
635 #: Cursor position in the line (the same for ``full_text`` and ``text``).
635 cursor_position: int
636 cursor_position: int
636
637
637 #: Cursor line in ``full_text``.
638 #: Cursor line in ``full_text``.
638 cursor_line: int
639 cursor_line: int
639
640
640 #: The maximum number of completions that will be used downstream.
641 #: The maximum number of completions that will be used downstream.
641 #: Matchers can use this information to abort early.
642 #: Matchers can use this information to abort early.
642 #: The built-in Jedi matcher is currently excepted from this limit.
643 #: The built-in Jedi matcher is currently excepted from this limit.
643 # If not given, return all possible completions.
644 # If not given, return all possible completions.
644 limit: Optional[int]
645 limit: Optional[int]
645
646
646 @cached_property
647 @cached_property
647 def text_until_cursor(self) -> str:
648 def text_until_cursor(self) -> str:
648 return self.line_with_cursor[: self.cursor_position]
649 return self.line_with_cursor[: self.cursor_position]
649
650
650 @cached_property
651 @cached_property
651 def line_with_cursor(self) -> str:
652 def line_with_cursor(self) -> str:
652 return self.full_text.split("\n")[self.cursor_line]
653 return self.full_text.split("\n")[self.cursor_line]
653
654
654
655
655 #: Matcher results for API v2.
656 #: Matcher results for API v2.
656 MatcherResult = Union[SimpleMatcherResult, _JediMatcherResult]
657 MatcherResult = Union[SimpleMatcherResult, _JediMatcherResult]
657
658
658
659
659 class _MatcherAPIv1Base(Protocol):
660 class _MatcherAPIv1Base(Protocol):
660 def __call__(self, text: str) -> List[str]:
661 def __call__(self, text: str) -> List[str]:
661 """Call signature."""
662 """Call signature."""
662 ...
663 ...
663
664
664 #: Used to construct the default matcher identifier
665 #: Used to construct the default matcher identifier
665 __qualname__: str
666 __qualname__: str
666
667
667
668
668 class _MatcherAPIv1Total(_MatcherAPIv1Base, Protocol):
669 class _MatcherAPIv1Total(_MatcherAPIv1Base, Protocol):
669 #: API version
670 #: API version
670 matcher_api_version: Optional[Literal[1]]
671 matcher_api_version: Optional[Literal[1]]
671
672
672 def __call__(self, text: str) -> List[str]:
673 def __call__(self, text: str) -> List[str]:
673 """Call signature."""
674 """Call signature."""
674 ...
675 ...
675
676
676
677
677 #: Protocol describing Matcher API v1.
678 #: Protocol describing Matcher API v1.
678 MatcherAPIv1: TypeAlias = Union[_MatcherAPIv1Base, _MatcherAPIv1Total]
679 MatcherAPIv1: TypeAlias = Union[_MatcherAPIv1Base, _MatcherAPIv1Total]
679
680
680
681
681 class MatcherAPIv2(Protocol):
682 class MatcherAPIv2(Protocol):
682 """Protocol describing Matcher API v2."""
683 """Protocol describing Matcher API v2."""
683
684
684 #: API version
685 #: API version
685 matcher_api_version: Literal[2] = 2
686 matcher_api_version: Literal[2] = 2
686
687
687 def __call__(self, context: CompletionContext) -> MatcherResult:
688 def __call__(self, context: CompletionContext) -> MatcherResult:
688 """Call signature."""
689 """Call signature."""
689 ...
690 ...
690
691
691 #: Used to construct the default matcher identifier
692 #: Used to construct the default matcher identifier
692 __qualname__: str
693 __qualname__: str
693
694
694
695
695 Matcher: TypeAlias = Union[MatcherAPIv1, MatcherAPIv2]
696 Matcher: TypeAlias = Union[MatcherAPIv1, MatcherAPIv2]
696
697
697
698
698 def _is_matcher_v1(matcher: Matcher) -> TypeGuard[MatcherAPIv1]:
699 def _is_matcher_v1(matcher: Matcher) -> TypeGuard[MatcherAPIv1]:
699 api_version = _get_matcher_api_version(matcher)
700 api_version = _get_matcher_api_version(matcher)
700 return api_version == 1
701 return api_version == 1
701
702
702
703
703 def _is_matcher_v2(matcher: Matcher) -> TypeGuard[MatcherAPIv2]:
704 def _is_matcher_v2(matcher: Matcher) -> TypeGuard[MatcherAPIv2]:
704 api_version = _get_matcher_api_version(matcher)
705 api_version = _get_matcher_api_version(matcher)
705 return api_version == 2
706 return api_version == 2
706
707
707
708
708 def _is_sizable(value: Any) -> TypeGuard[Sized]:
709 def _is_sizable(value: Any) -> TypeGuard[Sized]:
709 """Determines whether objects is sizable"""
710 """Determines whether objects is sizable"""
710 return hasattr(value, "__len__")
711 return hasattr(value, "__len__")
711
712
712
713
713 def _is_iterator(value: Any) -> TypeGuard[Iterator]:
714 def _is_iterator(value: Any) -> TypeGuard[Iterator]:
714 """Determines whether objects is sizable"""
715 """Determines whether objects is sizable"""
715 return hasattr(value, "__next__")
716 return hasattr(value, "__next__")
716
717
717
718
718 def has_any_completions(result: MatcherResult) -> bool:
719 def has_any_completions(result: MatcherResult) -> bool:
719 """Check if any result includes any completions."""
720 """Check if any result includes any completions."""
720 completions = result["completions"]
721 completions = result["completions"]
721 if _is_sizable(completions):
722 if _is_sizable(completions):
722 return len(completions) != 0
723 return len(completions) != 0
723 if _is_iterator(completions):
724 if _is_iterator(completions):
724 try:
725 try:
725 old_iterator = completions
726 old_iterator = completions
726 first = next(old_iterator)
727 first = next(old_iterator)
727 result["completions"] = cast(
728 result["completions"] = cast(
728 Iterator[SimpleCompletion],
729 Iterator[SimpleCompletion],
729 itertools.chain([first], old_iterator),
730 itertools.chain([first], old_iterator),
730 )
731 )
731 return True
732 return True
732 except StopIteration:
733 except StopIteration:
733 return False
734 return False
734 raise ValueError(
735 raise ValueError(
735 "Completions returned by matcher need to be an Iterator or a Sizable"
736 "Completions returned by matcher need to be an Iterator or a Sizable"
736 )
737 )
737
738
738
739
739 def completion_matcher(
740 def completion_matcher(
740 *,
741 *,
741 priority: Optional[float] = None,
742 priority: Optional[float] = None,
742 identifier: Optional[str] = None,
743 identifier: Optional[str] = None,
743 api_version: int = 1,
744 api_version: int = 1,
744 ) -> Callable[[Matcher], Matcher]:
745 ) -> Callable[[Matcher], Matcher]:
745 """Adds attributes describing the matcher.
746 """Adds attributes describing the matcher.
746
747
747 Parameters
748 Parameters
748 ----------
749 ----------
749 priority : Optional[float]
750 priority : Optional[float]
750 The priority of the matcher, determines the order of execution of matchers.
751 The priority of the matcher, determines the order of execution of matchers.
751 Higher priority means that the matcher will be executed first. Defaults to 0.
752 Higher priority means that the matcher will be executed first. Defaults to 0.
752 identifier : Optional[str]
753 identifier : Optional[str]
753 identifier of the matcher allowing users to modify the behaviour via traitlets,
754 identifier of the matcher allowing users to modify the behaviour via traitlets,
754 and also used to for debugging (will be passed as ``origin`` with the completions).
755 and also used to for debugging (will be passed as ``origin`` with the completions).
755
756
756 Defaults to matcher function's ``__qualname__`` (for example,
757 Defaults to matcher function's ``__qualname__`` (for example,
757 ``IPCompleter.file_matcher`` for the built-in matched defined
758 ``IPCompleter.file_matcher`` for the built-in matched defined
758 as a ``file_matcher`` method of the ``IPCompleter`` class).
759 as a ``file_matcher`` method of the ``IPCompleter`` class).
759 api_version: Optional[int]
760 api_version: Optional[int]
760 version of the Matcher API used by this matcher.
761 version of the Matcher API used by this matcher.
761 Currently supported values are 1 and 2.
762 Currently supported values are 1 and 2.
762 Defaults to 1.
763 Defaults to 1.
763 """
764 """
764
765
765 def wrapper(func: Matcher):
766 def wrapper(func: Matcher):
766 func.matcher_priority = priority or 0 # type: ignore
767 func.matcher_priority = priority or 0 # type: ignore
767 func.matcher_identifier = identifier or func.__qualname__ # type: ignore
768 func.matcher_identifier = identifier or func.__qualname__ # type: ignore
768 func.matcher_api_version = api_version # type: ignore
769 func.matcher_api_version = api_version # type: ignore
769 if TYPE_CHECKING:
770 if TYPE_CHECKING:
770 if api_version == 1:
771 if api_version == 1:
771 func = cast(MatcherAPIv1, func)
772 func = cast(MatcherAPIv1, func)
772 elif api_version == 2:
773 elif api_version == 2:
773 func = cast(MatcherAPIv2, func)
774 func = cast(MatcherAPIv2, func)
774 return func
775 return func
775
776
776 return wrapper
777 return wrapper
777
778
778
779
779 def _get_matcher_priority(matcher: Matcher):
780 def _get_matcher_priority(matcher: Matcher):
780 return getattr(matcher, "matcher_priority", 0)
781 return getattr(matcher, "matcher_priority", 0)
781
782
782
783
783 def _get_matcher_id(matcher: Matcher):
784 def _get_matcher_id(matcher: Matcher):
784 return getattr(matcher, "matcher_identifier", matcher.__qualname__)
785 return getattr(matcher, "matcher_identifier", matcher.__qualname__)
785
786
786
787
787 def _get_matcher_api_version(matcher):
788 def _get_matcher_api_version(matcher):
788 return getattr(matcher, "matcher_api_version", 1)
789 return getattr(matcher, "matcher_api_version", 1)
789
790
790
791
791 context_matcher = partial(completion_matcher, api_version=2)
792 context_matcher = partial(completion_matcher, api_version=2)
792
793
793
794
794 _IC = Iterable[Completion]
795 _IC = Iterable[Completion]
795
796
796
797
797 def _deduplicate_completions(text: str, completions: _IC)-> _IC:
798 def _deduplicate_completions(text: str, completions: _IC)-> _IC:
798 """
799 """
799 Deduplicate a set of completions.
800 Deduplicate a set of completions.
800
801
801 .. warning::
802 .. warning::
802
803
803 Unstable
804 Unstable
804
805
805 This function is unstable, API may change without warning.
806 This function is unstable, API may change without warning.
806
807
807 Parameters
808 Parameters
808 ----------
809 ----------
809 text : str
810 text : str
810 text that should be completed.
811 text that should be completed.
811 completions : Iterator[Completion]
812 completions : Iterator[Completion]
812 iterator over the completions to deduplicate
813 iterator over the completions to deduplicate
813
814
814 Yields
815 Yields
815 ------
816 ------
816 `Completions` objects
817 `Completions` objects
817 Completions coming from multiple sources, may be different but end up having
818 Completions coming from multiple sources, may be different but end up having
818 the same effect when applied to ``text``. If this is the case, this will
819 the same effect when applied to ``text``. If this is the case, this will
819 consider completions as equal and only emit the first encountered.
820 consider completions as equal and only emit the first encountered.
820 Not folded in `completions()` yet for debugging purpose, and to detect when
821 Not folded in `completions()` yet for debugging purpose, and to detect when
821 the IPython completer does return things that Jedi does not, but should be
822 the IPython completer does return things that Jedi does not, but should be
822 at some point.
823 at some point.
823 """
824 """
824 completions = list(completions)
825 completions = list(completions)
825 if not completions:
826 if not completions:
826 return
827 return
827
828
828 new_start = min(c.start for c in completions)
829 new_start = min(c.start for c in completions)
829 new_end = max(c.end for c in completions)
830 new_end = max(c.end for c in completions)
830
831
831 seen = set()
832 seen = set()
832 for c in completions:
833 for c in completions:
833 new_text = text[new_start:c.start] + c.text + text[c.end:new_end]
834 new_text = text[new_start:c.start] + c.text + text[c.end:new_end]
834 if new_text not in seen:
835 if new_text not in seen:
835 yield c
836 yield c
836 seen.add(new_text)
837 seen.add(new_text)
837
838
838
839
839 def rectify_completions(text: str, completions: _IC, *, _debug: bool = False) -> _IC:
840 def rectify_completions(text: str, completions: _IC, *, _debug: bool = False) -> _IC:
840 """
841 """
841 Rectify a set of completions to all have the same ``start`` and ``end``
842 Rectify a set of completions to all have the same ``start`` and ``end``
842
843
843 .. warning::
844 .. warning::
844
845
845 Unstable
846 Unstable
846
847
847 This function is unstable, API may change without warning.
848 This function is unstable, API may change without warning.
848 It will also raise unless use in proper context manager.
849 It will also raise unless use in proper context manager.
849
850
850 Parameters
851 Parameters
851 ----------
852 ----------
852 text : str
853 text : str
853 text that should be completed.
854 text that should be completed.
854 completions : Iterator[Completion]
855 completions : Iterator[Completion]
855 iterator over the completions to rectify
856 iterator over the completions to rectify
856 _debug : bool
857 _debug : bool
857 Log failed completion
858 Log failed completion
858
859
859 Notes
860 Notes
860 -----
861 -----
861 :any:`jedi.api.classes.Completion` s returned by Jedi may not have the same start and end, though
862 :any:`jedi.api.classes.Completion` s returned by Jedi may not have the same start and end, though
862 the Jupyter Protocol requires them to behave like so. This will readjust
863 the Jupyter Protocol requires them to behave like so. This will readjust
863 the completion to have the same ``start`` and ``end`` by padding both
864 the completion to have the same ``start`` and ``end`` by padding both
864 extremities with surrounding text.
865 extremities with surrounding text.
865
866
866 During stabilisation should support a ``_debug`` option to log which
867 During stabilisation should support a ``_debug`` option to log which
867 completion are return by the IPython completer and not found in Jedi in
868 completion are return by the IPython completer and not found in Jedi in
868 order to make upstream bug report.
869 order to make upstream bug report.
869 """
870 """
870 warnings.warn("`rectify_completions` is a provisional API (as of IPython 6.0). "
871 warnings.warn("`rectify_completions` is a provisional API (as of IPython 6.0). "
871 "It may change without warnings. "
872 "It may change without warnings. "
872 "Use in corresponding context manager.",
873 "Use in corresponding context manager.",
873 category=ProvisionalCompleterWarning, stacklevel=2)
874 category=ProvisionalCompleterWarning, stacklevel=2)
874
875
875 completions = list(completions)
876 completions = list(completions)
876 if not completions:
877 if not completions:
877 return
878 return
878 starts = (c.start for c in completions)
879 starts = (c.start for c in completions)
879 ends = (c.end for c in completions)
880 ends = (c.end for c in completions)
880
881
881 new_start = min(starts)
882 new_start = min(starts)
882 new_end = max(ends)
883 new_end = max(ends)
883
884
884 seen_jedi = set()
885 seen_jedi = set()
885 seen_python_matches = set()
886 seen_python_matches = set()
886 for c in completions:
887 for c in completions:
887 new_text = text[new_start:c.start] + c.text + text[c.end:new_end]
888 new_text = text[new_start:c.start] + c.text + text[c.end:new_end]
888 if c._origin == 'jedi':
889 if c._origin == 'jedi':
889 seen_jedi.add(new_text)
890 seen_jedi.add(new_text)
890 elif c._origin == "IPCompleter.python_matcher":
891 elif c._origin == "IPCompleter.python_matcher":
891 seen_python_matches.add(new_text)
892 seen_python_matches.add(new_text)
892 yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin, signature=c.signature)
893 yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin, signature=c.signature)
893 diff = seen_python_matches.difference(seen_jedi)
894 diff = seen_python_matches.difference(seen_jedi)
894 if diff and _debug:
895 if diff and _debug:
895 print('IPython.python matches have extras:', diff)
896 print('IPython.python matches have extras:', diff)
896
897
897
898
898 if sys.platform == 'win32':
899 if sys.platform == 'win32':
899 DELIMS = ' \t\n`!@#$^&*()=+[{]}|;\'",<>?'
900 DELIMS = ' \t\n`!@#$^&*()=+[{]}|;\'",<>?'
900 else:
901 else:
901 DELIMS = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?'
902 DELIMS = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?'
902
903
903 GREEDY_DELIMS = ' =\r\n'
904 GREEDY_DELIMS = ' =\r\n'
904
905
905
906
906 class CompletionSplitter(object):
907 class CompletionSplitter(object):
907 """An object to split an input line in a manner similar to readline.
908 """An object to split an input line in a manner similar to readline.
908
909
909 By having our own implementation, we can expose readline-like completion in
910 By having our own implementation, we can expose readline-like completion in
910 a uniform manner to all frontends. This object only needs to be given the
911 a uniform manner to all frontends. This object only needs to be given the
911 line of text to be split and the cursor position on said line, and it
912 line of text to be split and the cursor position on said line, and it
912 returns the 'word' to be completed on at the cursor after splitting the
913 returns the 'word' to be completed on at the cursor after splitting the
913 entire line.
914 entire line.
914
915
915 What characters are used as splitting delimiters can be controlled by
916 What characters are used as splitting delimiters can be controlled by
916 setting the ``delims`` attribute (this is a property that internally
917 setting the ``delims`` attribute (this is a property that internally
917 automatically builds the necessary regular expression)"""
918 automatically builds the necessary regular expression)"""
918
919
919 # Private interface
920 # Private interface
920
921
921 # A string of delimiter characters. The default value makes sense for
922 # A string of delimiter characters. The default value makes sense for
922 # IPython's most typical usage patterns.
923 # IPython's most typical usage patterns.
923 _delims = DELIMS
924 _delims = DELIMS
924
925
925 # The expression (a normal string) to be compiled into a regular expression
926 # The expression (a normal string) to be compiled into a regular expression
926 # for actual splitting. We store it as an attribute mostly for ease of
927 # for actual splitting. We store it as an attribute mostly for ease of
927 # debugging, since this type of code can be so tricky to debug.
928 # debugging, since this type of code can be so tricky to debug.
928 _delim_expr = None
929 _delim_expr = None
929
930
930 # The regular expression that does the actual splitting
931 # The regular expression that does the actual splitting
931 _delim_re = None
932 _delim_re = None
932
933
933 def __init__(self, delims=None):
934 def __init__(self, delims=None):
934 delims = CompletionSplitter._delims if delims is None else delims
935 delims = CompletionSplitter._delims if delims is None else delims
935 self.delims = delims
936 self.delims = delims
936
937
937 @property
938 @property
938 def delims(self):
939 def delims(self):
939 """Return the string of delimiter characters."""
940 """Return the string of delimiter characters."""
940 return self._delims
941 return self._delims
941
942
942 @delims.setter
943 @delims.setter
943 def delims(self, delims):
944 def delims(self, delims):
944 """Set the delimiters for line splitting."""
945 """Set the delimiters for line splitting."""
945 expr = '[' + ''.join('\\'+ c for c in delims) + ']'
946 expr = '[' + ''.join('\\'+ c for c in delims) + ']'
946 self._delim_re = re.compile(expr)
947 self._delim_re = re.compile(expr)
947 self._delims = delims
948 self._delims = delims
948 self._delim_expr = expr
949 self._delim_expr = expr
949
950
950 def split_line(self, line, cursor_pos=None):
951 def split_line(self, line, cursor_pos=None):
951 """Split a line of text with a cursor at the given position.
952 """Split a line of text with a cursor at the given position.
952 """
953 """
953 cut_line = line if cursor_pos is None else line[:cursor_pos]
954 cut_line = line if cursor_pos is None else line[:cursor_pos]
954 return self._delim_re.split(cut_line)[-1]
955 return self._delim_re.split(cut_line)[-1]
955
956
956
957
957
958
958 class Completer(Configurable):
959 class Completer(Configurable):
959
960
960 greedy = Bool(
961 greedy = Bool(
961 False,
962 False,
962 help="""Activate greedy completion.
963 help="""Activate greedy completion.
963
964
964 .. deprecated:: 8.8
965 .. deprecated:: 8.8
965 Use :std:configtrait:`Completer.evaluation` and :std:configtrait:`Completer.auto_close_dict_keys` instead.
966 Use :std:configtrait:`Completer.evaluation` and :std:configtrait:`Completer.auto_close_dict_keys` instead.
966
967
967 When enabled in IPython 8.8 or newer, changes configuration as follows:
968 When enabled in IPython 8.8 or newer, changes configuration as follows:
968
969
969 - ``Completer.evaluation = 'unsafe'``
970 - ``Completer.evaluation = 'unsafe'``
970 - ``Completer.auto_close_dict_keys = True``
971 - ``Completer.auto_close_dict_keys = True``
971 """,
972 """,
972 ).tag(config=True)
973 ).tag(config=True)
973
974
974 evaluation = Enum(
975 evaluation = Enum(
975 ("forbidden", "minimal", "limited", "unsafe", "dangerous"),
976 ("forbidden", "minimal", "limited", "unsafe", "dangerous"),
976 default_value="limited",
977 default_value="limited",
977 help="""Policy for code evaluation under completion.
978 help="""Policy for code evaluation under completion.
978
979
979 Successive options allow to enable more eager evaluation for better
980 Successive options allow to enable more eager evaluation for better
980 completion suggestions, including for nested dictionaries, nested lists,
981 completion suggestions, including for nested dictionaries, nested lists,
981 or even results of function calls.
982 or even results of function calls.
982 Setting ``unsafe`` or higher can lead to evaluation of arbitrary user
983 Setting ``unsafe`` or higher can lead to evaluation of arbitrary user
983 code on :kbd:`Tab` with potentially unwanted or dangerous side effects.
984 code on :kbd:`Tab` with potentially unwanted or dangerous side effects.
984
985
985 Allowed values are:
986 Allowed values are:
986
987
987 - ``forbidden``: no evaluation of code is permitted,
988 - ``forbidden``: no evaluation of code is permitted,
988 - ``minimal``: evaluation of literals and access to built-in namespace;
989 - ``minimal``: evaluation of literals and access to built-in namespace;
989 no item/attribute evaluationm no access to locals/globals,
990 no item/attribute evaluationm no access to locals/globals,
990 no evaluation of any operations or comparisons.
991 no evaluation of any operations or comparisons.
991 - ``limited``: access to all namespaces, evaluation of hard-coded methods
992 - ``limited``: access to all namespaces, evaluation of hard-coded methods
992 (for example: :any:`dict.keys`, :any:`object.__getattr__`,
993 (for example: :any:`dict.keys`, :any:`object.__getattr__`,
993 :any:`object.__getitem__`) on allow-listed objects (for example:
994 :any:`object.__getitem__`) on allow-listed objects (for example:
994 :any:`dict`, :any:`list`, :any:`tuple`, ``pandas.Series``),
995 :any:`dict`, :any:`list`, :any:`tuple`, ``pandas.Series``),
995 - ``unsafe``: evaluation of all methods and function calls but not of
996 - ``unsafe``: evaluation of all methods and function calls but not of
996 syntax with side-effects like `del x`,
997 syntax with side-effects like `del x`,
997 - ``dangerous``: completely arbitrary evaluation.
998 - ``dangerous``: completely arbitrary evaluation.
998 """,
999 """,
999 ).tag(config=True)
1000 ).tag(config=True)
1000
1001
1001 use_jedi = Bool(default_value=JEDI_INSTALLED,
1002 use_jedi = Bool(default_value=JEDI_INSTALLED,
1002 help="Experimental: Use Jedi to generate autocompletions. "
1003 help="Experimental: Use Jedi to generate autocompletions. "
1003 "Default to True if jedi is installed.").tag(config=True)
1004 "Default to True if jedi is installed.").tag(config=True)
1004
1005
1005 jedi_compute_type_timeout = Int(default_value=400,
1006 jedi_compute_type_timeout = Int(default_value=400,
1006 help="""Experimental: restrict time (in milliseconds) during which Jedi can compute types.
1007 help="""Experimental: restrict time (in milliseconds) during which Jedi can compute types.
1007 Set to 0 to stop computing types. Non-zero value lower than 100ms may hurt
1008 Set to 0 to stop computing types. Non-zero value lower than 100ms may hurt
1008 performance by preventing jedi to build its cache.
1009 performance by preventing jedi to build its cache.
1009 """).tag(config=True)
1010 """).tag(config=True)
1010
1011
1011 debug = Bool(default_value=False,
1012 debug = Bool(default_value=False,
1012 help='Enable debug for the Completer. Mostly print extra '
1013 help='Enable debug for the Completer. Mostly print extra '
1013 'information for experimental jedi integration.')\
1014 'information for experimental jedi integration.')\
1014 .tag(config=True)
1015 .tag(config=True)
1015
1016
1016 backslash_combining_completions = Bool(True,
1017 backslash_combining_completions = Bool(True,
1017 help="Enable unicode completions, e.g. \\alpha<tab> . "
1018 help="Enable unicode completions, e.g. \\alpha<tab> . "
1018 "Includes completion of latex commands, unicode names, and expanding "
1019 "Includes completion of latex commands, unicode names, and expanding "
1019 "unicode characters back to latex commands.").tag(config=True)
1020 "unicode characters back to latex commands.").tag(config=True)
1020
1021
1021 auto_close_dict_keys = Bool(
1022 auto_close_dict_keys = Bool(
1022 False,
1023 False,
1023 help="""
1024 help="""
1024 Enable auto-closing dictionary keys.
1025 Enable auto-closing dictionary keys.
1025
1026
1026 When enabled string keys will be suffixed with a final quote
1027 When enabled string keys will be suffixed with a final quote
1027 (matching the opening quote), tuple keys will also receive a
1028 (matching the opening quote), tuple keys will also receive a
1028 separating comma if needed, and keys which are final will
1029 separating comma if needed, and keys which are final will
1029 receive a closing bracket (``]``).
1030 receive a closing bracket (``]``).
1030 """,
1031 """,
1031 ).tag(config=True)
1032 ).tag(config=True)
1032
1033
1033 def __init__(self, namespace=None, global_namespace=None, **kwargs):
1034 def __init__(self, namespace=None, global_namespace=None, **kwargs):
1034 """Create a new completer for the command line.
1035 """Create a new completer for the command line.
1035
1036
1036 Completer(namespace=ns, global_namespace=ns2) -> completer instance.
1037 Completer(namespace=ns, global_namespace=ns2) -> completer instance.
1037
1038
1038 If unspecified, the default namespace where completions are performed
1039 If unspecified, the default namespace where completions are performed
1039 is __main__ (technically, __main__.__dict__). Namespaces should be
1040 is __main__ (technically, __main__.__dict__). Namespaces should be
1040 given as dictionaries.
1041 given as dictionaries.
1041
1042
1042 An optional second namespace can be given. This allows the completer
1043 An optional second namespace can be given. This allows the completer
1043 to handle cases where both the local and global scopes need to be
1044 to handle cases where both the local and global scopes need to be
1044 distinguished.
1045 distinguished.
1045 """
1046 """
1046
1047
1047 # Don't bind to namespace quite yet, but flag whether the user wants a
1048 # Don't bind to namespace quite yet, but flag whether the user wants a
1048 # specific namespace or to use __main__.__dict__. This will allow us
1049 # specific namespace or to use __main__.__dict__. This will allow us
1049 # to bind to __main__.__dict__ at completion time, not now.
1050 # to bind to __main__.__dict__ at completion time, not now.
1050 if namespace is None:
1051 if namespace is None:
1051 self.use_main_ns = True
1052 self.use_main_ns = True
1052 else:
1053 else:
1053 self.use_main_ns = False
1054 self.use_main_ns = False
1054 self.namespace = namespace
1055 self.namespace = namespace
1055
1056
1056 # The global namespace, if given, can be bound directly
1057 # The global namespace, if given, can be bound directly
1057 if global_namespace is None:
1058 if global_namespace is None:
1058 self.global_namespace = {}
1059 self.global_namespace = {}
1059 else:
1060 else:
1060 self.global_namespace = global_namespace
1061 self.global_namespace = global_namespace
1061
1062
1062 self.custom_matchers = []
1063 self.custom_matchers = []
1063
1064
1064 super(Completer, self).__init__(**kwargs)
1065 super(Completer, self).__init__(**kwargs)
1065
1066
1066 def complete(self, text, state):
1067 def complete(self, text, state):
1067 """Return the next possible completion for 'text'.
1068 """Return the next possible completion for 'text'.
1068
1069
1069 This is called successively with state == 0, 1, 2, ... until it
1070 This is called successively with state == 0, 1, 2, ... until it
1070 returns None. The completion should begin with 'text'.
1071 returns None. The completion should begin with 'text'.
1071
1072
1072 """
1073 """
1073 if self.use_main_ns:
1074 if self.use_main_ns:
1074 self.namespace = __main__.__dict__
1075 self.namespace = __main__.__dict__
1075
1076
1076 if state == 0:
1077 if state == 0:
1077 if "." in text:
1078 if "." in text:
1078 self.matches = self.attr_matches(text)
1079 self.matches = self.attr_matches(text)
1079 else:
1080 else:
1080 self.matches = self.global_matches(text)
1081 self.matches = self.global_matches(text)
1081 try:
1082 try:
1082 return self.matches[state]
1083 return self.matches[state]
1083 except IndexError:
1084 except IndexError:
1084 return None
1085 return None
1085
1086
1086 def global_matches(self, text):
1087 def global_matches(self, text):
1087 """Compute matches when text is a simple name.
1088 """Compute matches when text is a simple name.
1088
1089
1089 Return a list of all keywords, built-in functions and names currently
1090 Return a list of all keywords, built-in functions and names currently
1090 defined in self.namespace or self.global_namespace that match.
1091 defined in self.namespace or self.global_namespace that match.
1091
1092
1092 """
1093 """
1093 matches = []
1094 matches = []
1094 match_append = matches.append
1095 match_append = matches.append
1095 n = len(text)
1096 n = len(text)
1096 for lst in [
1097 for lst in [
1097 keyword.kwlist,
1098 keyword.kwlist,
1098 builtin_mod.__dict__.keys(),
1099 builtin_mod.__dict__.keys(),
1099 list(self.namespace.keys()),
1100 list(self.namespace.keys()),
1100 list(self.global_namespace.keys()),
1101 list(self.global_namespace.keys()),
1101 ]:
1102 ]:
1102 for word in lst:
1103 for word in lst:
1103 if word[:n] == text and word != "__builtins__":
1104 if word[:n] == text and word != "__builtins__":
1104 match_append(word)
1105 match_append(word)
1105
1106
1106 snake_case_re = re.compile(r"[^_]+(_[^_]+)+?\Z")
1107 snake_case_re = re.compile(r"[^_]+(_[^_]+)+?\Z")
1107 for lst in [list(self.namespace.keys()), list(self.global_namespace.keys())]:
1108 for lst in [list(self.namespace.keys()), list(self.global_namespace.keys())]:
1108 shortened = {
1109 shortened = {
1109 "_".join([sub[0] for sub in word.split("_")]): word
1110 "_".join([sub[0] for sub in word.split("_")]): word
1110 for word in lst
1111 for word in lst
1111 if snake_case_re.match(word)
1112 if snake_case_re.match(word)
1112 }
1113 }
1113 for word in shortened.keys():
1114 for word in shortened.keys():
1114 if word[:n] == text and word != "__builtins__":
1115 if word[:n] == text and word != "__builtins__":
1115 match_append(shortened[word])
1116 match_append(shortened[word])
1116 return matches
1117 return matches
1117
1118
1118 def attr_matches(self, text):
1119 def attr_matches(self, text):
1119 """Compute matches when text contains a dot.
1120 """Compute matches when text contains a dot.
1120
1121
1121 Assuming the text is of the form NAME.NAME....[NAME], and is
1122 Assuming the text is of the form NAME.NAME....[NAME], and is
1122 evaluatable in self.namespace or self.global_namespace, it will be
1123 evaluatable in self.namespace or self.global_namespace, it will be
1123 evaluated and its attributes (as revealed by dir()) are used as
1124 evaluated and its attributes (as revealed by dir()) are used as
1124 possible completions. (For class instances, class members are
1125 possible completions. (For class instances, class members are
1125 also considered.)
1126 also considered.)
1126
1127
1127 WARNING: this can still invoke arbitrary C code, if an object
1128 WARNING: this can still invoke arbitrary C code, if an object
1128 with a __getattr__ hook is evaluated.
1129 with a __getattr__ hook is evaluated.
1129
1130
1130 """
1131 """
1131 return self._attr_matches(text)[0]
1132 return self._attr_matches(text)[0]
1132
1133
1133 # we simple attribute matching with normal identifiers.
1134 # we simple attribute matching with normal identifiers.
1134 _ATTR_MATCH_RE = re.compile(r"(.+)\.(\w*)$")
1135 _ATTR_MATCH_RE = re.compile(r"(.+)\.(\w*)$")
1135
1136
1136 def _attr_matches(
1137 def _attr_matches(
1137 self, text: str, include_prefix: bool = True
1138 self, text: str, include_prefix: bool = True
1138 ) -> Tuple[Sequence[str], str]:
1139 ) -> Tuple[Sequence[str], str]:
1139 m2 = self._ATTR_MATCH_RE.match(self.line_buffer)
1140 m2 = self._ATTR_MATCH_RE.match(self.line_buffer)
1140 if not m2:
1141 if not m2:
1141 return [], ""
1142 return [], ""
1142 expr, attr = m2.group(1, 2)
1143 expr, attr = m2.group(1, 2)
1143
1144
1144 obj = self._evaluate_expr(expr)
1145 obj = self._evaluate_expr(expr)
1145
1146
1146 if obj is not_found:
1147 if obj is not_found:
1147 return [], ""
1148 return [], ""
1148
1149
1149 if self.limit_to__all__ and hasattr(obj, '__all__'):
1150 if self.limit_to__all__ and hasattr(obj, '__all__'):
1150 words = get__all__entries(obj)
1151 words = get__all__entries(obj)
1151 else:
1152 else:
1152 words = dir2(obj)
1153 words = dir2(obj)
1153
1154
1154 try:
1155 try:
1155 words = generics.complete_object(obj, words)
1156 words = generics.complete_object(obj, words)
1156 except TryNext:
1157 except TryNext:
1157 pass
1158 pass
1158 except AssertionError:
1159 except AssertionError:
1159 raise
1160 raise
1160 except Exception:
1161 except Exception:
1161 # Silence errors from completion function
1162 # Silence errors from completion function
1162 pass
1163 pass
1163 # Build match list to return
1164 # Build match list to return
1164 n = len(attr)
1165 n = len(attr)
1165
1166
1166 # Note: ideally we would just return words here and the prefix
1167 # Note: ideally we would just return words here and the prefix
1167 # reconciliator would know that we intend to append to rather than
1168 # reconciliator would know that we intend to append to rather than
1168 # replace the input text; this requires refactoring to return range
1169 # replace the input text; this requires refactoring to return range
1169 # which ought to be replaced (as does jedi).
1170 # which ought to be replaced (as does jedi).
1170 if include_prefix:
1171 if include_prefix:
1171 tokens = _parse_tokens(expr)
1172 tokens = _parse_tokens(expr)
1172 rev_tokens = reversed(tokens)
1173 rev_tokens = reversed(tokens)
1173 skip_over = {tokenize.ENDMARKER, tokenize.NEWLINE}
1174 skip_over = {tokenize.ENDMARKER, tokenize.NEWLINE}
1174 name_turn = True
1175 name_turn = True
1175
1176
1176 parts = []
1177 parts = []
1177 for token in rev_tokens:
1178 for token in rev_tokens:
1178 if token.type in skip_over:
1179 if token.type in skip_over:
1179 continue
1180 continue
1180 if token.type == tokenize.NAME and name_turn:
1181 if token.type == tokenize.NAME and name_turn:
1181 parts.append(token.string)
1182 parts.append(token.string)
1182 name_turn = False
1183 name_turn = False
1183 elif (
1184 elif (
1184 token.type == tokenize.OP and token.string == "." and not name_turn
1185 token.type == tokenize.OP and token.string == "." and not name_turn
1185 ):
1186 ):
1186 parts.append(token.string)
1187 parts.append(token.string)
1187 name_turn = True
1188 name_turn = True
1188 else:
1189 else:
1189 # short-circuit if not empty nor name token
1190 # short-circuit if not empty nor name token
1190 break
1191 break
1191
1192
1192 prefix_after_space = "".join(reversed(parts))
1193 prefix_after_space = "".join(reversed(parts))
1193 else:
1194 else:
1194 prefix_after_space = ""
1195 prefix_after_space = ""
1195
1196
1196 return (
1197 return (
1197 ["%s.%s" % (prefix_after_space, w) for w in words if w[:n] == attr],
1198 ["%s.%s" % (prefix_after_space, w) for w in words if w[:n] == attr],
1198 "." + attr,
1199 "." + attr,
1199 )
1200 )
1200
1201
1201 def _trim_expr(self, code: str) -> str:
1202 def _trim_expr(self, code: str) -> str:
1202 """
1203 """
1203 Trim the code until it is a valid expression and not a tuple;
1204 Trim the code until it is a valid expression and not a tuple;
1204
1205
1205 return the trimmed expression for guarded_eval.
1206 return the trimmed expression for guarded_eval.
1206 """
1207 """
1207 while code:
1208 while code:
1208 code = code[1:]
1209 code = code[1:]
1209 try:
1210 try:
1210 res = ast.parse(code)
1211 res = ast.parse(code)
1211 except SyntaxError:
1212 except SyntaxError:
1212 continue
1213 continue
1213
1214
1214 assert res is not None
1215 assert res is not None
1215 if len(res.body) != 1:
1216 if len(res.body) != 1:
1216 continue
1217 continue
1217 expr = res.body[0].value
1218 expr = res.body[0].value
1218 if isinstance(expr, ast.Tuple) and not code[-1] == ")":
1219 if isinstance(expr, ast.Tuple) and not code[-1] == ")":
1219 # we skip implicit tuple, like when trimming `fun(a,b`<completion>
1220 # we skip implicit tuple, like when trimming `fun(a,b`<completion>
1220 # as `a,b` would be a tuple, and we actually expect to get only `b`
1221 # as `a,b` would be a tuple, and we actually expect to get only `b`
1221 continue
1222 continue
1222 return code
1223 return code
1223 return ""
1224 return ""
1224
1225
1225 def _evaluate_expr(self, expr):
1226 def _evaluate_expr(self, expr):
1226 obj = not_found
1227 obj = not_found
1227 done = False
1228 done = False
1228 while not done and expr:
1229 while not done and expr:
1229 try:
1230 try:
1230 obj = guarded_eval(
1231 obj = guarded_eval(
1231 expr,
1232 expr,
1232 EvaluationContext(
1233 EvaluationContext(
1233 globals=self.global_namespace,
1234 globals=self.global_namespace,
1234 locals=self.namespace,
1235 locals=self.namespace,
1235 evaluation=self.evaluation,
1236 evaluation=self.evaluation,
1236 ),
1237 ),
1237 )
1238 )
1238 done = True
1239 done = True
1239 except Exception as e:
1240 except Exception as e:
1240 if self.debug:
1241 if self.debug:
1241 print("Evaluation exception", e)
1242 print("Evaluation exception", e)
1242 # trim the expression to remove any invalid prefix
1243 # trim the expression to remove any invalid prefix
1243 # e.g. user starts `(d[`, so we get `expr = '(d'`,
1244 # e.g. user starts `(d[`, so we get `expr = '(d'`,
1244 # where parenthesis is not closed.
1245 # where parenthesis is not closed.
1245 # TODO: make this faster by reusing parts of the computation?
1246 # TODO: make this faster by reusing parts of the computation?
1246 expr = self._trim_expr(expr)
1247 expr = self._trim_expr(expr)
1247 return obj
1248 return obj
1248
1249
1249 def get__all__entries(obj):
1250 def get__all__entries(obj):
1250 """returns the strings in the __all__ attribute"""
1251 """returns the strings in the __all__ attribute"""
1251 try:
1252 try:
1252 words = getattr(obj, '__all__')
1253 words = getattr(obj, '__all__')
1253 except Exception:
1254 except Exception:
1254 return []
1255 return []
1255
1256
1256 return [w for w in words if isinstance(w, str)]
1257 return [w for w in words if isinstance(w, str)]
1257
1258
1258
1259
1259 class _DictKeyState(enum.Flag):
1260 class _DictKeyState(enum.Flag):
1260 """Represent state of the key match in context of other possible matches.
1261 """Represent state of the key match in context of other possible matches.
1261
1262
1262 - given `d1 = {'a': 1}` completion on `d1['<tab>` will yield `{'a': END_OF_ITEM}` as there is no tuple.
1263 - given `d1 = {'a': 1}` completion on `d1['<tab>` will yield `{'a': END_OF_ITEM}` as there is no tuple.
1263 - given `d2 = {('a', 'b'): 1}`: `d2['a', '<tab>` will yield `{'b': END_OF_TUPLE}` as there is no tuple members to add beyond `'b'`.
1264 - given `d2 = {('a', 'b'): 1}`: `d2['a', '<tab>` will yield `{'b': END_OF_TUPLE}` as there is no tuple members to add beyond `'b'`.
1264 - given `d3 = {('a', 'b'): 1}`: `d3['<tab>` will yield `{'a': IN_TUPLE}` as `'a'` can be added.
1265 - given `d3 = {('a', 'b'): 1}`: `d3['<tab>` will yield `{'a': IN_TUPLE}` as `'a'` can be added.
1265 - given `d4 = {'a': 1, ('a', 'b'): 2}`: `d4['<tab>` will yield `{'a': END_OF_ITEM & END_OF_TUPLE}`
1266 - given `d4 = {'a': 1, ('a', 'b'): 2}`: `d4['<tab>` will yield `{'a': END_OF_ITEM & END_OF_TUPLE}`
1266 """
1267 """
1267
1268
1268 BASELINE = 0
1269 BASELINE = 0
1269 END_OF_ITEM = enum.auto()
1270 END_OF_ITEM = enum.auto()
1270 END_OF_TUPLE = enum.auto()
1271 END_OF_TUPLE = enum.auto()
1271 IN_TUPLE = enum.auto()
1272 IN_TUPLE = enum.auto()
1272
1273
1273
1274
1274 def _parse_tokens(c):
1275 def _parse_tokens(c):
1275 """Parse tokens even if there is an error."""
1276 """Parse tokens even if there is an error."""
1276 tokens = []
1277 tokens = []
1277 token_generator = tokenize.generate_tokens(iter(c.splitlines()).__next__)
1278 token_generator = tokenize.generate_tokens(iter(c.splitlines()).__next__)
1278 while True:
1279 while True:
1279 try:
1280 try:
1280 tokens.append(next(token_generator))
1281 tokens.append(next(token_generator))
1281 except tokenize.TokenError:
1282 except tokenize.TokenError:
1282 return tokens
1283 return tokens
1283 except StopIteration:
1284 except StopIteration:
1284 return tokens
1285 return tokens
1285
1286
1286
1287
1287 def _match_number_in_dict_key_prefix(prefix: str) -> Union[str, None]:
1288 def _match_number_in_dict_key_prefix(prefix: str) -> Union[str, None]:
1288 """Match any valid Python numeric literal in a prefix of dictionary keys.
1289 """Match any valid Python numeric literal in a prefix of dictionary keys.
1289
1290
1290 References:
1291 References:
1291 - https://docs.python.org/3/reference/lexical_analysis.html#numeric-literals
1292 - https://docs.python.org/3/reference/lexical_analysis.html#numeric-literals
1292 - https://docs.python.org/3/library/tokenize.html
1293 - https://docs.python.org/3/library/tokenize.html
1293 """
1294 """
1294 if prefix[-1].isspace():
1295 if prefix[-1].isspace():
1295 # if user typed a space we do not have anything to complete
1296 # if user typed a space we do not have anything to complete
1296 # even if there was a valid number token before
1297 # even if there was a valid number token before
1297 return None
1298 return None
1298 tokens = _parse_tokens(prefix)
1299 tokens = _parse_tokens(prefix)
1299 rev_tokens = reversed(tokens)
1300 rev_tokens = reversed(tokens)
1300 skip_over = {tokenize.ENDMARKER, tokenize.NEWLINE}
1301 skip_over = {tokenize.ENDMARKER, tokenize.NEWLINE}
1301 number = None
1302 number = None
1302 for token in rev_tokens:
1303 for token in rev_tokens:
1303 if token.type in skip_over:
1304 if token.type in skip_over:
1304 continue
1305 continue
1305 if number is None:
1306 if number is None:
1306 if token.type == tokenize.NUMBER:
1307 if token.type == tokenize.NUMBER:
1307 number = token.string
1308 number = token.string
1308 continue
1309 continue
1309 else:
1310 else:
1310 # we did not match a number
1311 # we did not match a number
1311 return None
1312 return None
1312 if token.type == tokenize.OP:
1313 if token.type == tokenize.OP:
1313 if token.string == ",":
1314 if token.string == ",":
1314 break
1315 break
1315 if token.string in {"+", "-"}:
1316 if token.string in {"+", "-"}:
1316 number = token.string + number
1317 number = token.string + number
1317 else:
1318 else:
1318 return None
1319 return None
1319 return number
1320 return number
1320
1321
1321
1322
1322 _INT_FORMATS = {
1323 _INT_FORMATS = {
1323 "0b": bin,
1324 "0b": bin,
1324 "0o": oct,
1325 "0o": oct,
1325 "0x": hex,
1326 "0x": hex,
1326 }
1327 }
1327
1328
1328
1329
1329 def match_dict_keys(
1330 def match_dict_keys(
1330 keys: List[Union[str, bytes, Tuple[Union[str, bytes], ...]]],
1331 keys: List[Union[str, bytes, Tuple[Union[str, bytes], ...]]],
1331 prefix: str,
1332 prefix: str,
1332 delims: str,
1333 delims: str,
1333 extra_prefix: Optional[Tuple[Union[str, bytes], ...]] = None,
1334 extra_prefix: Optional[Tuple[Union[str, bytes], ...]] = None,
1334 ) -> Tuple[str, int, Dict[str, _DictKeyState]]:
1335 ) -> Tuple[str, int, Dict[str, _DictKeyState]]:
1335 """Used by dict_key_matches, matching the prefix to a list of keys
1336 """Used by dict_key_matches, matching the prefix to a list of keys
1336
1337
1337 Parameters
1338 Parameters
1338 ----------
1339 ----------
1339 keys
1340 keys
1340 list of keys in dictionary currently being completed.
1341 list of keys in dictionary currently being completed.
1341 prefix
1342 prefix
1342 Part of the text already typed by the user. E.g. `mydict[b'fo`
1343 Part of the text already typed by the user. E.g. `mydict[b'fo`
1343 delims
1344 delims
1344 String of delimiters to consider when finding the current key.
1345 String of delimiters to consider when finding the current key.
1345 extra_prefix : optional
1346 extra_prefix : optional
1346 Part of the text already typed in multi-key index cases. E.g. for
1347 Part of the text already typed in multi-key index cases. E.g. for
1347 `mydict['foo', "bar", 'b`, this would be `('foo', 'bar')`.
1348 `mydict['foo', "bar", 'b`, this would be `('foo', 'bar')`.
1348
1349
1349 Returns
1350 Returns
1350 -------
1351 -------
1351 A tuple of three elements: ``quote``, ``token_start``, ``matched``, with
1352 A tuple of three elements: ``quote``, ``token_start``, ``matched``, with
1352 ``quote`` being the quote that need to be used to close current string.
1353 ``quote`` being the quote that need to be used to close current string.
1353 ``token_start`` the position where the replacement should start occurring,
1354 ``token_start`` the position where the replacement should start occurring,
1354 ``matches`` a dictionary of replacement/completion keys on keys and values
1355 ``matches`` a dictionary of replacement/completion keys on keys and values
1355 indicating whether the state.
1356 indicating whether the state.
1356 """
1357 """
1357 prefix_tuple = extra_prefix if extra_prefix else ()
1358 prefix_tuple = extra_prefix if extra_prefix else ()
1358
1359
1359 prefix_tuple_size = sum(
1360 prefix_tuple_size = sum(
1360 [
1361 [
1361 # for pandas, do not count slices as taking space
1362 # for pandas, do not count slices as taking space
1362 not isinstance(k, slice)
1363 not isinstance(k, slice)
1363 for k in prefix_tuple
1364 for k in prefix_tuple
1364 ]
1365 ]
1365 )
1366 )
1366 text_serializable_types = (str, bytes, int, float, slice)
1367 text_serializable_types = (str, bytes, int, float, slice)
1367
1368
1368 def filter_prefix_tuple(key):
1369 def filter_prefix_tuple(key):
1369 # Reject too short keys
1370 # Reject too short keys
1370 if len(key) <= prefix_tuple_size:
1371 if len(key) <= prefix_tuple_size:
1371 return False
1372 return False
1372 # Reject keys which cannot be serialised to text
1373 # Reject keys which cannot be serialised to text
1373 for k in key:
1374 for k in key:
1374 if not isinstance(k, text_serializable_types):
1375 if not isinstance(k, text_serializable_types):
1375 return False
1376 return False
1376 # Reject keys that do not match the prefix
1377 # Reject keys that do not match the prefix
1377 for k, pt in zip(key, prefix_tuple):
1378 for k, pt in zip(key, prefix_tuple):
1378 if k != pt and not isinstance(pt, slice):
1379 if k != pt and not isinstance(pt, slice):
1379 return False
1380 return False
1380 # All checks passed!
1381 # All checks passed!
1381 return True
1382 return True
1382
1383
1383 filtered_key_is_final: Dict[Union[str, bytes, int, float], _DictKeyState] = (
1384 filtered_key_is_final: Dict[Union[str, bytes, int, float], _DictKeyState] = (
1384 defaultdict(lambda: _DictKeyState.BASELINE)
1385 defaultdict(lambda: _DictKeyState.BASELINE)
1385 )
1386 )
1386
1387
1387 for k in keys:
1388 for k in keys:
1388 # If at least one of the matches is not final, mark as undetermined.
1389 # If at least one of the matches is not final, mark as undetermined.
1389 # This can happen with `d = {111: 'b', (111, 222): 'a'}` where
1390 # This can happen with `d = {111: 'b', (111, 222): 'a'}` where
1390 # `111` appears final on first match but is not final on the second.
1391 # `111` appears final on first match but is not final on the second.
1391
1392
1392 if isinstance(k, tuple):
1393 if isinstance(k, tuple):
1393 if filter_prefix_tuple(k):
1394 if filter_prefix_tuple(k):
1394 key_fragment = k[prefix_tuple_size]
1395 key_fragment = k[prefix_tuple_size]
1395 filtered_key_is_final[key_fragment] |= (
1396 filtered_key_is_final[key_fragment] |= (
1396 _DictKeyState.END_OF_TUPLE
1397 _DictKeyState.END_OF_TUPLE
1397 if len(k) == prefix_tuple_size + 1
1398 if len(k) == prefix_tuple_size + 1
1398 else _DictKeyState.IN_TUPLE
1399 else _DictKeyState.IN_TUPLE
1399 )
1400 )
1400 elif prefix_tuple_size > 0:
1401 elif prefix_tuple_size > 0:
1401 # we are completing a tuple but this key is not a tuple,
1402 # we are completing a tuple but this key is not a tuple,
1402 # so we should ignore it
1403 # so we should ignore it
1403 pass
1404 pass
1404 else:
1405 else:
1405 if isinstance(k, text_serializable_types):
1406 if isinstance(k, text_serializable_types):
1406 filtered_key_is_final[k] |= _DictKeyState.END_OF_ITEM
1407 filtered_key_is_final[k] |= _DictKeyState.END_OF_ITEM
1407
1408
1408 filtered_keys = filtered_key_is_final.keys()
1409 filtered_keys = filtered_key_is_final.keys()
1409
1410
1410 if not prefix:
1411 if not prefix:
1411 return "", 0, {repr(k): v for k, v in filtered_key_is_final.items()}
1412 return "", 0, {repr(k): v for k, v in filtered_key_is_final.items()}
1412
1413
1413 quote_match = re.search("(?:\"|')", prefix)
1414 quote_match = re.search("(?:\"|')", prefix)
1414 is_user_prefix_numeric = False
1415 is_user_prefix_numeric = False
1415
1416
1416 if quote_match:
1417 if quote_match:
1417 quote = quote_match.group()
1418 quote = quote_match.group()
1418 valid_prefix = prefix + quote
1419 valid_prefix = prefix + quote
1419 try:
1420 try:
1420 prefix_str = literal_eval(valid_prefix)
1421 prefix_str = literal_eval(valid_prefix)
1421 except Exception:
1422 except Exception:
1422 return "", 0, {}
1423 return "", 0, {}
1423 else:
1424 else:
1424 # If it does not look like a string, let's assume
1425 # If it does not look like a string, let's assume
1425 # we are dealing with a number or variable.
1426 # we are dealing with a number or variable.
1426 number_match = _match_number_in_dict_key_prefix(prefix)
1427 number_match = _match_number_in_dict_key_prefix(prefix)
1427
1428
1428 # We do not want the key matcher to suggest variable names so we yield:
1429 # We do not want the key matcher to suggest variable names so we yield:
1429 if number_match is None:
1430 if number_match is None:
1430 # The alternative would be to assume that user forgort the quote
1431 # The alternative would be to assume that user forgort the quote
1431 # and if the substring matches, suggest adding it at the start.
1432 # and if the substring matches, suggest adding it at the start.
1432 return "", 0, {}
1433 return "", 0, {}
1433
1434
1434 prefix_str = number_match
1435 prefix_str = number_match
1435 is_user_prefix_numeric = True
1436 is_user_prefix_numeric = True
1436 quote = ""
1437 quote = ""
1437
1438
1438 pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$'
1439 pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$'
1439 token_match = re.search(pattern, prefix, re.UNICODE)
1440 token_match = re.search(pattern, prefix, re.UNICODE)
1440 assert token_match is not None # silence mypy
1441 assert token_match is not None # silence mypy
1441 token_start = token_match.start()
1442 token_start = token_match.start()
1442 token_prefix = token_match.group()
1443 token_prefix = token_match.group()
1443
1444
1444 matched: Dict[str, _DictKeyState] = {}
1445 matched: Dict[str, _DictKeyState] = {}
1445
1446
1446 str_key: Union[str, bytes]
1447 str_key: Union[str, bytes]
1447
1448
1448 for key in filtered_keys:
1449 for key in filtered_keys:
1449 if isinstance(key, (int, float)):
1450 if isinstance(key, (int, float)):
1450 # User typed a number but this key is not a number.
1451 # User typed a number but this key is not a number.
1451 if not is_user_prefix_numeric:
1452 if not is_user_prefix_numeric:
1452 continue
1453 continue
1453 str_key = str(key)
1454 str_key = str(key)
1454 if isinstance(key, int):
1455 if isinstance(key, int):
1455 int_base = prefix_str[:2].lower()
1456 int_base = prefix_str[:2].lower()
1456 # if user typed integer using binary/oct/hex notation:
1457 # if user typed integer using binary/oct/hex notation:
1457 if int_base in _INT_FORMATS:
1458 if int_base in _INT_FORMATS:
1458 int_format = _INT_FORMATS[int_base]
1459 int_format = _INT_FORMATS[int_base]
1459 str_key = int_format(key)
1460 str_key = int_format(key)
1460 else:
1461 else:
1461 # User typed a string but this key is a number.
1462 # User typed a string but this key is a number.
1462 if is_user_prefix_numeric:
1463 if is_user_prefix_numeric:
1463 continue
1464 continue
1464 str_key = key
1465 str_key = key
1465 try:
1466 try:
1466 if not str_key.startswith(prefix_str):
1467 if not str_key.startswith(prefix_str):
1467 continue
1468 continue
1468 except (AttributeError, TypeError, UnicodeError):
1469 except (AttributeError, TypeError, UnicodeError):
1469 # Python 3+ TypeError on b'a'.startswith('a') or vice-versa
1470 # Python 3+ TypeError on b'a'.startswith('a') or vice-versa
1470 continue
1471 continue
1471
1472
1472 # reformat remainder of key to begin with prefix
1473 # reformat remainder of key to begin with prefix
1473 rem = str_key[len(prefix_str) :]
1474 rem = str_key[len(prefix_str) :]
1474 # force repr wrapped in '
1475 # force repr wrapped in '
1475 rem_repr = repr(rem + '"') if isinstance(rem, str) else repr(rem + b'"')
1476 rem_repr = repr(rem + '"') if isinstance(rem, str) else repr(rem + b'"')
1476 rem_repr = rem_repr[1 + rem_repr.index("'"):-2]
1477 rem_repr = rem_repr[1 + rem_repr.index("'"):-2]
1477 if quote == '"':
1478 if quote == '"':
1478 # The entered prefix is quoted with ",
1479 # The entered prefix is quoted with ",
1479 # but the match is quoted with '.
1480 # but the match is quoted with '.
1480 # A contained " hence needs escaping for comparison:
1481 # A contained " hence needs escaping for comparison:
1481 rem_repr = rem_repr.replace('"', '\\"')
1482 rem_repr = rem_repr.replace('"', '\\"')
1482
1483
1483 # then reinsert prefix from start of token
1484 # then reinsert prefix from start of token
1484 match = "%s%s" % (token_prefix, rem_repr)
1485 match = "%s%s" % (token_prefix, rem_repr)
1485
1486
1486 matched[match] = filtered_key_is_final[key]
1487 matched[match] = filtered_key_is_final[key]
1487 return quote, token_start, matched
1488 return quote, token_start, matched
1488
1489
1489
1490
1490 def cursor_to_position(text:str, line:int, column:int)->int:
1491 def cursor_to_position(text:str, line:int, column:int)->int:
1491 """
1492 """
1492 Convert the (line,column) position of the cursor in text to an offset in a
1493 Convert the (line,column) position of the cursor in text to an offset in a
1493 string.
1494 string.
1494
1495
1495 Parameters
1496 Parameters
1496 ----------
1497 ----------
1497 text : str
1498 text : str
1498 The text in which to calculate the cursor offset
1499 The text in which to calculate the cursor offset
1499 line : int
1500 line : int
1500 Line of the cursor; 0-indexed
1501 Line of the cursor; 0-indexed
1501 column : int
1502 column : int
1502 Column of the cursor 0-indexed
1503 Column of the cursor 0-indexed
1503
1504
1504 Returns
1505 Returns
1505 -------
1506 -------
1506 Position of the cursor in ``text``, 0-indexed.
1507 Position of the cursor in ``text``, 0-indexed.
1507
1508
1508 See Also
1509 See Also
1509 --------
1510 --------
1510 position_to_cursor : reciprocal of this function
1511 position_to_cursor : reciprocal of this function
1511
1512
1512 """
1513 """
1513 lines = text.split('\n')
1514 lines = text.split('\n')
1514 assert line <= len(lines), '{} <= {}'.format(str(line), str(len(lines)))
1515 assert line <= len(lines), '{} <= {}'.format(str(line), str(len(lines)))
1515
1516
1516 return sum(len(line) + 1 for line in lines[:line]) + column
1517 return sum(len(line) + 1 for line in lines[:line]) + column
1517
1518
1518 def position_to_cursor(text:str, offset:int)->Tuple[int, int]:
1519 def position_to_cursor(text:str, offset:int)->Tuple[int, int]:
1519 """
1520 """
1520 Convert the position of the cursor in text (0 indexed) to a line
1521 Convert the position of the cursor in text (0 indexed) to a line
1521 number(0-indexed) and a column number (0-indexed) pair
1522 number(0-indexed) and a column number (0-indexed) pair
1522
1523
1523 Position should be a valid position in ``text``.
1524 Position should be a valid position in ``text``.
1524
1525
1525 Parameters
1526 Parameters
1526 ----------
1527 ----------
1527 text : str
1528 text : str
1528 The text in which to calculate the cursor offset
1529 The text in which to calculate the cursor offset
1529 offset : int
1530 offset : int
1530 Position of the cursor in ``text``, 0-indexed.
1531 Position of the cursor in ``text``, 0-indexed.
1531
1532
1532 Returns
1533 Returns
1533 -------
1534 -------
1534 (line, column) : (int, int)
1535 (line, column) : (int, int)
1535 Line of the cursor; 0-indexed, column of the cursor 0-indexed
1536 Line of the cursor; 0-indexed, column of the cursor 0-indexed
1536
1537
1537 See Also
1538 See Also
1538 --------
1539 --------
1539 cursor_to_position : reciprocal of this function
1540 cursor_to_position : reciprocal of this function
1540
1541
1541 """
1542 """
1542
1543
1543 assert 0 <= offset <= len(text) , "0 <= %s <= %s" % (offset , len(text))
1544 assert 0 <= offset <= len(text) , "0 <= %s <= %s" % (offset , len(text))
1544
1545
1545 before = text[:offset]
1546 before = text[:offset]
1546 blines = before.split('\n') # ! splitnes trim trailing \n
1547 blines = before.split('\n') # ! splitnes trim trailing \n
1547 line = before.count('\n')
1548 line = before.count('\n')
1548 col = len(blines[-1])
1549 col = len(blines[-1])
1549 return line, col
1550 return line, col
1550
1551
1551
1552
1552 def _safe_isinstance(obj, module, class_name, *attrs):
1553 def _safe_isinstance(obj, module, class_name, *attrs):
1553 """Checks if obj is an instance of module.class_name if loaded
1554 """Checks if obj is an instance of module.class_name if loaded
1554 """
1555 """
1555 if module in sys.modules:
1556 if module in sys.modules:
1556 m = sys.modules[module]
1557 m = sys.modules[module]
1557 for attr in [class_name, *attrs]:
1558 for attr in [class_name, *attrs]:
1558 m = getattr(m, attr)
1559 m = getattr(m, attr)
1559 return isinstance(obj, m)
1560 return isinstance(obj, m)
1560
1561
1561
1562
1562 @context_matcher()
1563 @context_matcher()
1563 def back_unicode_name_matcher(context: CompletionContext):
1564 def back_unicode_name_matcher(context: CompletionContext):
1564 """Match Unicode characters back to Unicode name
1565 """Match Unicode characters back to Unicode name
1565
1566
1566 Same as :any:`back_unicode_name_matches`, but adopted to new Matcher API.
1567 Same as :any:`back_unicode_name_matches`, but adopted to new Matcher API.
1567 """
1568 """
1568 fragment, matches = back_unicode_name_matches(context.text_until_cursor)
1569 fragment, matches = back_unicode_name_matches(context.text_until_cursor)
1569 return _convert_matcher_v1_result_to_v2(
1570 return _convert_matcher_v1_result_to_v2(
1570 matches, type="unicode", fragment=fragment, suppress_if_matches=True
1571 matches, type="unicode", fragment=fragment, suppress_if_matches=True
1571 )
1572 )
1572
1573
1573
1574
1574 def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]:
1575 def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]:
1575 """Match Unicode characters back to Unicode name
1576 """Match Unicode characters back to Unicode name
1576
1577
1577 This does ``☃`` -> ``\\snowman``
1578 This does ``☃`` -> ``\\snowman``
1578
1579
1579 Note that snowman is not a valid python3 combining character but will be expanded.
1580 Note that snowman is not a valid python3 combining character but will be expanded.
1580 Though it will not recombine back to the snowman character by the completion machinery.
1581 Though it will not recombine back to the snowman character by the completion machinery.
1581
1582
1582 This will not either back-complete standard sequences like \\n, \\b ...
1583 This will not either back-complete standard sequences like \\n, \\b ...
1583
1584
1584 .. deprecated:: 8.6
1585 .. deprecated:: 8.6
1585 You can use :meth:`back_unicode_name_matcher` instead.
1586 You can use :meth:`back_unicode_name_matcher` instead.
1586
1587
1587 Returns
1588 Returns
1588 =======
1589 =======
1589
1590
1590 Return a tuple with two elements:
1591 Return a tuple with two elements:
1591
1592
1592 - The Unicode character that was matched (preceded with a backslash), or
1593 - The Unicode character that was matched (preceded with a backslash), or
1593 empty string,
1594 empty string,
1594 - a sequence (of 1), name for the match Unicode character, preceded by
1595 - a sequence (of 1), name for the match Unicode character, preceded by
1595 backslash, or empty if no match.
1596 backslash, or empty if no match.
1596 """
1597 """
1597 if len(text)<2:
1598 if len(text)<2:
1598 return '', ()
1599 return '', ()
1599 maybe_slash = text[-2]
1600 maybe_slash = text[-2]
1600 if maybe_slash != '\\':
1601 if maybe_slash != '\\':
1601 return '', ()
1602 return '', ()
1602
1603
1603 char = text[-1]
1604 char = text[-1]
1604 # no expand on quote for completion in strings.
1605 # no expand on quote for completion in strings.
1605 # nor backcomplete standard ascii keys
1606 # nor backcomplete standard ascii keys
1606 if char in string.ascii_letters or char in ('"',"'"):
1607 if char in string.ascii_letters or char in ('"',"'"):
1607 return '', ()
1608 return '', ()
1608 try :
1609 try :
1609 unic = unicodedata.name(char)
1610 unic = unicodedata.name(char)
1610 return '\\'+char,('\\'+unic,)
1611 return '\\'+char,('\\'+unic,)
1611 except KeyError:
1612 except KeyError:
1612 pass
1613 pass
1613 return '', ()
1614 return '', ()
1614
1615
1615
1616
1616 @context_matcher()
1617 @context_matcher()
1617 def back_latex_name_matcher(context: CompletionContext):
1618 def back_latex_name_matcher(context: CompletionContext):
1618 """Match latex characters back to unicode name
1619 """Match latex characters back to unicode name
1619
1620
1620 Same as :any:`back_latex_name_matches`, but adopted to new Matcher API.
1621 Same as :any:`back_latex_name_matches`, but adopted to new Matcher API.
1621 """
1622 """
1622 fragment, matches = back_latex_name_matches(context.text_until_cursor)
1623 fragment, matches = back_latex_name_matches(context.text_until_cursor)
1623 return _convert_matcher_v1_result_to_v2(
1624 return _convert_matcher_v1_result_to_v2(
1624 matches, type="latex", fragment=fragment, suppress_if_matches=True
1625 matches, type="latex", fragment=fragment, suppress_if_matches=True
1625 )
1626 )
1626
1627
1627
1628
1628 def back_latex_name_matches(text: str) -> Tuple[str, Sequence[str]]:
1629 def back_latex_name_matches(text: str) -> Tuple[str, Sequence[str]]:
1629 """Match latex characters back to unicode name
1630 """Match latex characters back to unicode name
1630
1631
1631 This does ``\\ℵ`` -> ``\\aleph``
1632 This does ``\\ℵ`` -> ``\\aleph``
1632
1633
1633 .. deprecated:: 8.6
1634 .. deprecated:: 8.6
1634 You can use :meth:`back_latex_name_matcher` instead.
1635 You can use :meth:`back_latex_name_matcher` instead.
1635 """
1636 """
1636 if len(text)<2:
1637 if len(text)<2:
1637 return '', ()
1638 return '', ()
1638 maybe_slash = text[-2]
1639 maybe_slash = text[-2]
1639 if maybe_slash != '\\':
1640 if maybe_slash != '\\':
1640 return '', ()
1641 return '', ()
1641
1642
1642
1643
1643 char = text[-1]
1644 char = text[-1]
1644 # no expand on quote for completion in strings.
1645 # no expand on quote for completion in strings.
1645 # nor backcomplete standard ascii keys
1646 # nor backcomplete standard ascii keys
1646 if char in string.ascii_letters or char in ('"',"'"):
1647 if char in string.ascii_letters or char in ('"',"'"):
1647 return '', ()
1648 return '', ()
1648 try :
1649 try :
1649 latex = reverse_latex_symbol[char]
1650 latex = reverse_latex_symbol[char]
1650 # '\\' replace the \ as well
1651 # '\\' replace the \ as well
1651 return '\\'+char,[latex]
1652 return '\\'+char,[latex]
1652 except KeyError:
1653 except KeyError:
1653 pass
1654 pass
1654 return '', ()
1655 return '', ()
1655
1656
1656
1657
1657 def _formatparamchildren(parameter) -> str:
1658 def _formatparamchildren(parameter) -> str:
1658 """
1659 """
1659 Get parameter name and value from Jedi Private API
1660 Get parameter name and value from Jedi Private API
1660
1661
1661 Jedi does not expose a simple way to get `param=value` from its API.
1662 Jedi does not expose a simple way to get `param=value` from its API.
1662
1663
1663 Parameters
1664 Parameters
1664 ----------
1665 ----------
1665 parameter
1666 parameter
1666 Jedi's function `Param`
1667 Jedi's function `Param`
1667
1668
1668 Returns
1669 Returns
1669 -------
1670 -------
1670 A string like 'a', 'b=1', '*args', '**kwargs'
1671 A string like 'a', 'b=1', '*args', '**kwargs'
1671
1672
1672 """
1673 """
1673 description = parameter.description
1674 description = parameter.description
1674 if not description.startswith('param '):
1675 if not description.startswith('param '):
1675 raise ValueError('Jedi function parameter description have change format.'
1676 raise ValueError('Jedi function parameter description have change format.'
1676 'Expected "param ...", found %r".' % description)
1677 'Expected "param ...", found %r".' % description)
1677 return description[6:]
1678 return description[6:]
1678
1679
1679 def _make_signature(completion)-> str:
1680 def _make_signature(completion)-> str:
1680 """
1681 """
1681 Make the signature from a jedi completion
1682 Make the signature from a jedi completion
1682
1683
1683 Parameters
1684 Parameters
1684 ----------
1685 ----------
1685 completion : jedi.Completion
1686 completion : jedi.Completion
1686 object does not complete a function type
1687 object does not complete a function type
1687
1688
1688 Returns
1689 Returns
1689 -------
1690 -------
1690 a string consisting of the function signature, with the parenthesis but
1691 a string consisting of the function signature, with the parenthesis but
1691 without the function name. example:
1692 without the function name. example:
1692 `(a, *args, b=1, **kwargs)`
1693 `(a, *args, b=1, **kwargs)`
1693
1694
1694 """
1695 """
1695
1696
1696 # it looks like this might work on jedi 0.17
1697 # it looks like this might work on jedi 0.17
1697 if hasattr(completion, 'get_signatures'):
1698 if hasattr(completion, 'get_signatures'):
1698 signatures = completion.get_signatures()
1699 signatures = completion.get_signatures()
1699 if not signatures:
1700 if not signatures:
1700 return '(?)'
1701 return '(?)'
1701
1702
1702 c0 = completion.get_signatures()[0]
1703 c0 = completion.get_signatures()[0]
1703 return '('+c0.to_string().split('(', maxsplit=1)[1]
1704 return '('+c0.to_string().split('(', maxsplit=1)[1]
1704
1705
1705 return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for signature in completion.get_signatures()
1706 return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for signature in completion.get_signatures()
1706 for p in signature.defined_names()) if f])
1707 for p in signature.defined_names()) if f])
1707
1708
1708
1709
1709 _CompleteResult = Dict[str, MatcherResult]
1710 _CompleteResult = Dict[str, MatcherResult]
1710
1711
1711
1712
1712 DICT_MATCHER_REGEX = re.compile(
1713 DICT_MATCHER_REGEX = re.compile(
1713 r"""(?x)
1714 r"""(?x)
1714 ( # match dict-referring - or any get item object - expression
1715 ( # match dict-referring - or any get item object - expression
1715 .+
1716 .+
1716 )
1717 )
1717 \[ # open bracket
1718 \[ # open bracket
1718 \s* # and optional whitespace
1719 \s* # and optional whitespace
1719 # Capture any number of serializable objects (e.g. "a", "b", 'c')
1720 # Capture any number of serializable objects (e.g. "a", "b", 'c')
1720 # and slices
1721 # and slices
1721 ((?:(?:
1722 ((?:(?:
1722 (?: # closed string
1723 (?: # closed string
1723 [uUbB]? # string prefix (r not handled)
1724 [uUbB]? # string prefix (r not handled)
1724 (?:
1725 (?:
1725 '(?:[^']|(?<!\\)\\')*'
1726 '(?:[^']|(?<!\\)\\')*'
1726 |
1727 |
1727 "(?:[^"]|(?<!\\)\\")*"
1728 "(?:[^"]|(?<!\\)\\")*"
1728 )
1729 )
1729 )
1730 )
1730 |
1731 |
1731 # capture integers and slices
1732 # capture integers and slices
1732 (?:[-+]?\d+)?(?::(?:[-+]?\d+)?){0,2}
1733 (?:[-+]?\d+)?(?::(?:[-+]?\d+)?){0,2}
1733 |
1734 |
1734 # integer in bin/hex/oct notation
1735 # integer in bin/hex/oct notation
1735 0[bBxXoO]_?(?:\w|\d)+
1736 0[bBxXoO]_?(?:\w|\d)+
1736 )
1737 )
1737 \s*,\s*
1738 \s*,\s*
1738 )*)
1739 )*)
1739 ((?:
1740 ((?:
1740 (?: # unclosed string
1741 (?: # unclosed string
1741 [uUbB]? # string prefix (r not handled)
1742 [uUbB]? # string prefix (r not handled)
1742 (?:
1743 (?:
1743 '(?:[^']|(?<!\\)\\')*
1744 '(?:[^']|(?<!\\)\\')*
1744 |
1745 |
1745 "(?:[^"]|(?<!\\)\\")*
1746 "(?:[^"]|(?<!\\)\\")*
1746 )
1747 )
1747 )
1748 )
1748 |
1749 |
1749 # unfinished integer
1750 # unfinished integer
1750 (?:[-+]?\d+)
1751 (?:[-+]?\d+)
1751 |
1752 |
1752 # integer in bin/hex/oct notation
1753 # integer in bin/hex/oct notation
1753 0[bBxXoO]_?(?:\w|\d)+
1754 0[bBxXoO]_?(?:\w|\d)+
1754 )
1755 )
1755 )?
1756 )?
1756 $
1757 $
1757 """
1758 """
1758 )
1759 )
1759
1760
1760
1761
1761 def _convert_matcher_v1_result_to_v2(
1762 def _convert_matcher_v1_result_to_v2(
1762 matches: Sequence[str],
1763 matches: Sequence[str],
1763 type: str,
1764 type: str,
1764 fragment: Optional[str] = None,
1765 fragment: Optional[str] = None,
1765 suppress_if_matches: bool = False,
1766 suppress_if_matches: bool = False,
1766 ) -> SimpleMatcherResult:
1767 ) -> SimpleMatcherResult:
1767 """Utility to help with transition"""
1768 """Utility to help with transition"""
1768 result = {
1769 result = {
1769 "completions": [SimpleCompletion(text=match, type=type) for match in matches],
1770 "completions": [SimpleCompletion(text=match, type=type) for match in matches],
1770 "suppress": (True if matches else False) if suppress_if_matches else False,
1771 "suppress": (True if matches else False) if suppress_if_matches else False,
1771 }
1772 }
1772 if fragment is not None:
1773 if fragment is not None:
1773 result["matched_fragment"] = fragment
1774 result["matched_fragment"] = fragment
1774 return cast(SimpleMatcherResult, result)
1775 return cast(SimpleMatcherResult, result)
1775
1776
1776
1777
1777 class IPCompleter(Completer):
1778 class IPCompleter(Completer):
1778 """Extension of the completer class with IPython-specific features"""
1779 """Extension of the completer class with IPython-specific features"""
1779
1780
1780 @observe('greedy')
1781 @observe('greedy')
1781 def _greedy_changed(self, change):
1782 def _greedy_changed(self, change):
1782 """update the splitter and readline delims when greedy is changed"""
1783 """update the splitter and readline delims when greedy is changed"""
1783 if change["new"]:
1784 if change["new"]:
1784 self.evaluation = "unsafe"
1785 self.evaluation = "unsafe"
1785 self.auto_close_dict_keys = True
1786 self.auto_close_dict_keys = True
1786 self.splitter.delims = GREEDY_DELIMS
1787 self.splitter.delims = GREEDY_DELIMS
1787 else:
1788 else:
1788 self.evaluation = "limited"
1789 self.evaluation = "limited"
1789 self.auto_close_dict_keys = False
1790 self.auto_close_dict_keys = False
1790 self.splitter.delims = DELIMS
1791 self.splitter.delims = DELIMS
1791
1792
1792 dict_keys_only = Bool(
1793 dict_keys_only = Bool(
1793 False,
1794 False,
1794 help="""
1795 help="""
1795 Whether to show dict key matches only.
1796 Whether to show dict key matches only.
1796
1797
1797 (disables all matchers except for `IPCompleter.dict_key_matcher`).
1798 (disables all matchers except for `IPCompleter.dict_key_matcher`).
1798 """,
1799 """,
1799 )
1800 )
1800
1801
1801 suppress_competing_matchers = UnionTrait(
1802 suppress_competing_matchers = UnionTrait(
1802 [Bool(allow_none=True), DictTrait(Bool(None, allow_none=True))],
1803 [Bool(allow_none=True), DictTrait(Bool(None, allow_none=True))],
1803 default_value=None,
1804 default_value=None,
1804 help="""
1805 help="""
1805 Whether to suppress completions from other *Matchers*.
1806 Whether to suppress completions from other *Matchers*.
1806
1807
1807 When set to ``None`` (default) the matchers will attempt to auto-detect
1808 When set to ``None`` (default) the matchers will attempt to auto-detect
1808 whether suppression of other matchers is desirable. For example, at
1809 whether suppression of other matchers is desirable. For example, at
1809 the beginning of a line followed by `%` we expect a magic completion
1810 the beginning of a line followed by `%` we expect a magic completion
1810 to be the only applicable option, and after ``my_dict['`` we usually
1811 to be the only applicable option, and after ``my_dict['`` we usually
1811 expect a completion with an existing dictionary key.
1812 expect a completion with an existing dictionary key.
1812
1813
1813 If you want to disable this heuristic and see completions from all matchers,
1814 If you want to disable this heuristic and see completions from all matchers,
1814 set ``IPCompleter.suppress_competing_matchers = False``.
1815 set ``IPCompleter.suppress_competing_matchers = False``.
1815 To disable the heuristic for specific matchers provide a dictionary mapping:
1816 To disable the heuristic for specific matchers provide a dictionary mapping:
1816 ``IPCompleter.suppress_competing_matchers = {'IPCompleter.dict_key_matcher': False}``.
1817 ``IPCompleter.suppress_competing_matchers = {'IPCompleter.dict_key_matcher': False}``.
1817
1818
1818 Set ``IPCompleter.suppress_competing_matchers = True`` to limit
1819 Set ``IPCompleter.suppress_competing_matchers = True`` to limit
1819 completions to the set of matchers with the highest priority;
1820 completions to the set of matchers with the highest priority;
1820 this is equivalent to ``IPCompleter.merge_completions`` and
1821 this is equivalent to ``IPCompleter.merge_completions`` and
1821 can be beneficial for performance, but will sometimes omit relevant
1822 can be beneficial for performance, but will sometimes omit relevant
1822 candidates from matchers further down the priority list.
1823 candidates from matchers further down the priority list.
1823 """,
1824 """,
1824 ).tag(config=True)
1825 ).tag(config=True)
1825
1826
1826 merge_completions = Bool(
1827 merge_completions = Bool(
1827 True,
1828 True,
1828 help="""Whether to merge completion results into a single list
1829 help="""Whether to merge completion results into a single list
1829
1830
1830 If False, only the completion results from the first non-empty
1831 If False, only the completion results from the first non-empty
1831 completer will be returned.
1832 completer will be returned.
1832
1833
1833 As of version 8.6.0, setting the value to ``False`` is an alias for:
1834 As of version 8.6.0, setting the value to ``False`` is an alias for:
1834 ``IPCompleter.suppress_competing_matchers = True.``.
1835 ``IPCompleter.suppress_competing_matchers = True.``.
1835 """,
1836 """,
1836 ).tag(config=True)
1837 ).tag(config=True)
1837
1838
1838 disable_matchers = ListTrait(
1839 disable_matchers = ListTrait(
1839 Unicode(),
1840 Unicode(),
1840 help="""List of matchers to disable.
1841 help="""List of matchers to disable.
1841
1842
1842 The list should contain matcher identifiers (see :any:`completion_matcher`).
1843 The list should contain matcher identifiers (see :any:`completion_matcher`).
1843 """,
1844 """,
1844 ).tag(config=True)
1845 ).tag(config=True)
1845
1846
1846 omit__names = Enum(
1847 omit__names = Enum(
1847 (0, 1, 2),
1848 (0, 1, 2),
1848 default_value=2,
1849 default_value=2,
1849 help="""Instruct the completer to omit private method names
1850 help="""Instruct the completer to omit private method names
1850
1851
1851 Specifically, when completing on ``object.<tab>``.
1852 Specifically, when completing on ``object.<tab>``.
1852
1853
1853 When 2 [default]: all names that start with '_' will be excluded.
1854 When 2 [default]: all names that start with '_' will be excluded.
1854
1855
1855 When 1: all 'magic' names (``__foo__``) will be excluded.
1856 When 1: all 'magic' names (``__foo__``) will be excluded.
1856
1857
1857 When 0: nothing will be excluded.
1858 When 0: nothing will be excluded.
1858 """
1859 """
1859 ).tag(config=True)
1860 ).tag(config=True)
1860 limit_to__all__ = Bool(False,
1861 limit_to__all__ = Bool(False,
1861 help="""
1862 help="""
1862 DEPRECATED as of version 5.0.
1863 DEPRECATED as of version 5.0.
1863
1864
1864 Instruct the completer to use __all__ for the completion
1865 Instruct the completer to use __all__ for the completion
1865
1866
1866 Specifically, when completing on ``object.<tab>``.
1867 Specifically, when completing on ``object.<tab>``.
1867
1868
1868 When True: only those names in obj.__all__ will be included.
1869 When True: only those names in obj.__all__ will be included.
1869
1870
1870 When False [default]: the __all__ attribute is ignored
1871 When False [default]: the __all__ attribute is ignored
1871 """,
1872 """,
1872 ).tag(config=True)
1873 ).tag(config=True)
1873
1874
1874 profile_completions = Bool(
1875 profile_completions = Bool(
1875 default_value=False,
1876 default_value=False,
1876 help="If True, emit profiling data for completion subsystem using cProfile."
1877 help="If True, emit profiling data for completion subsystem using cProfile."
1877 ).tag(config=True)
1878 ).tag(config=True)
1878
1879
1879 profiler_output_dir = Unicode(
1880 profiler_output_dir = Unicode(
1880 default_value=".completion_profiles",
1881 default_value=".completion_profiles",
1881 help="Template for path at which to output profile data for completions."
1882 help="Template for path at which to output profile data for completions."
1882 ).tag(config=True)
1883 ).tag(config=True)
1883
1884
1884 @observe('limit_to__all__')
1885 @observe('limit_to__all__')
1885 def _limit_to_all_changed(self, change):
1886 def _limit_to_all_changed(self, change):
1886 warnings.warn('`IPython.core.IPCompleter.limit_to__all__` configuration '
1887 warnings.warn('`IPython.core.IPCompleter.limit_to__all__` configuration '
1887 'value has been deprecated since IPython 5.0, will be made to have '
1888 'value has been deprecated since IPython 5.0, will be made to have '
1888 'no effects and then removed in future version of IPython.',
1889 'no effects and then removed in future version of IPython.',
1889 UserWarning)
1890 UserWarning)
1890
1891
1891 def __init__(
1892 def __init__(
1892 self, shell=None, namespace=None, global_namespace=None, config=None, **kwargs
1893 self, shell=None, namespace=None, global_namespace=None, config=None, **kwargs
1893 ):
1894 ):
1894 """IPCompleter() -> completer
1895 """IPCompleter() -> completer
1895
1896
1896 Return a completer object.
1897 Return a completer object.
1897
1898
1898 Parameters
1899 Parameters
1899 ----------
1900 ----------
1900 shell
1901 shell
1901 a pointer to the ipython shell itself. This is needed
1902 a pointer to the ipython shell itself. This is needed
1902 because this completer knows about magic functions, and those can
1903 because this completer knows about magic functions, and those can
1903 only be accessed via the ipython instance.
1904 only be accessed via the ipython instance.
1904 namespace : dict, optional
1905 namespace : dict, optional
1905 an optional dict where completions are performed.
1906 an optional dict where completions are performed.
1906 global_namespace : dict, optional
1907 global_namespace : dict, optional
1907 secondary optional dict for completions, to
1908 secondary optional dict for completions, to
1908 handle cases (such as IPython embedded inside functions) where
1909 handle cases (such as IPython embedded inside functions) where
1909 both Python scopes are visible.
1910 both Python scopes are visible.
1910 config : Config
1911 config : Config
1911 traitlet's config object
1912 traitlet's config object
1912 **kwargs
1913 **kwargs
1913 passed to super class unmodified.
1914 passed to super class unmodified.
1914 """
1915 """
1915
1916
1916 self.magic_escape = ESC_MAGIC
1917 self.magic_escape = ESC_MAGIC
1917 self.splitter = CompletionSplitter()
1918 self.splitter = CompletionSplitter()
1918
1919
1919 # _greedy_changed() depends on splitter and readline being defined:
1920 # _greedy_changed() depends on splitter and readline being defined:
1920 super().__init__(
1921 super().__init__(
1921 namespace=namespace,
1922 namespace=namespace,
1922 global_namespace=global_namespace,
1923 global_namespace=global_namespace,
1923 config=config,
1924 config=config,
1924 **kwargs,
1925 **kwargs,
1925 )
1926 )
1926
1927
1927 # List where completion matches will be stored
1928 # List where completion matches will be stored
1928 self.matches = []
1929 self.matches = []
1929 self.shell = shell
1930 self.shell = shell
1930 # Regexp to split filenames with spaces in them
1931 # Regexp to split filenames with spaces in them
1931 self.space_name_re = re.compile(r'([^\\] )')
1932 self.space_name_re = re.compile(r'([^\\] )')
1932 # Hold a local ref. to glob.glob for speed
1933 # Hold a local ref. to glob.glob for speed
1933 self.glob = glob.glob
1934 self.glob = glob.glob
1934
1935
1935 # Determine if we are running on 'dumb' terminals, like (X)Emacs
1936 # Determine if we are running on 'dumb' terminals, like (X)Emacs
1936 # buffers, to avoid completion problems.
1937 # buffers, to avoid completion problems.
1937 term = os.environ.get('TERM','xterm')
1938 term = os.environ.get('TERM','xterm')
1938 self.dumb_terminal = term in ['dumb','emacs']
1939 self.dumb_terminal = term in ['dumb','emacs']
1939
1940
1940 # Special handling of backslashes needed in win32 platforms
1941 # Special handling of backslashes needed in win32 platforms
1941 if sys.platform == "win32":
1942 if sys.platform == "win32":
1942 self.clean_glob = self._clean_glob_win32
1943 self.clean_glob = self._clean_glob_win32
1943 else:
1944 else:
1944 self.clean_glob = self._clean_glob
1945 self.clean_glob = self._clean_glob
1945
1946
1946 #regexp to parse docstring for function signature
1947 #regexp to parse docstring for function signature
1947 self.docstring_sig_re = re.compile(r'^[\w|\s.]+\(([^)]*)\).*')
1948 self.docstring_sig_re = re.compile(r'^[\w|\s.]+\(([^)]*)\).*')
1948 self.docstring_kwd_re = re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)')
1949 self.docstring_kwd_re = re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)')
1949 #use this if positional argument name is also needed
1950 #use this if positional argument name is also needed
1950 #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)')
1951 #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)')
1951
1952
1952 self.magic_arg_matchers = [
1953 self.magic_arg_matchers = [
1953 self.magic_config_matcher,
1954 self.magic_config_matcher,
1954 self.magic_color_matcher,
1955 self.magic_color_matcher,
1955 ]
1956 ]
1956
1957
1957 # This is set externally by InteractiveShell
1958 # This is set externally by InteractiveShell
1958 self.custom_completers = None
1959 self.custom_completers = None
1959
1960
1960 # This is a list of names of unicode characters that can be completed
1961 # This is a list of names of unicode characters that can be completed
1961 # into their corresponding unicode value. The list is large, so we
1962 # into their corresponding unicode value. The list is large, so we
1962 # lazily initialize it on first use. Consuming code should access this
1963 # lazily initialize it on first use. Consuming code should access this
1963 # attribute through the `@unicode_names` property.
1964 # attribute through the `@unicode_names` property.
1964 self._unicode_names = None
1965 self._unicode_names = None
1965
1966
1966 self._backslash_combining_matchers = [
1967 self._backslash_combining_matchers = [
1967 self.latex_name_matcher,
1968 self.latex_name_matcher,
1968 self.unicode_name_matcher,
1969 self.unicode_name_matcher,
1969 back_latex_name_matcher,
1970 back_latex_name_matcher,
1970 back_unicode_name_matcher,
1971 back_unicode_name_matcher,
1971 self.fwd_unicode_matcher,
1972 self.fwd_unicode_matcher,
1972 ]
1973 ]
1973
1974
1974 if not self.backslash_combining_completions:
1975 if not self.backslash_combining_completions:
1975 for matcher in self._backslash_combining_matchers:
1976 for matcher in self._backslash_combining_matchers:
1976 self.disable_matchers.append(_get_matcher_id(matcher))
1977 self.disable_matchers.append(_get_matcher_id(matcher))
1977
1978
1978 if not self.merge_completions:
1979 if not self.merge_completions:
1979 self.suppress_competing_matchers = True
1980 self.suppress_competing_matchers = True
1980
1981
1981 @property
1982 @property
1982 def matchers(self) -> List[Matcher]:
1983 def matchers(self) -> List[Matcher]:
1983 """All active matcher routines for completion"""
1984 """All active matcher routines for completion"""
1984 if self.dict_keys_only:
1985 if self.dict_keys_only:
1985 return [self.dict_key_matcher]
1986 return [self.dict_key_matcher]
1986
1987
1987 if self.use_jedi:
1988 if self.use_jedi:
1988 return [
1989 return [
1989 *self.custom_matchers,
1990 *self.custom_matchers,
1990 *self._backslash_combining_matchers,
1991 *self._backslash_combining_matchers,
1991 *self.magic_arg_matchers,
1992 *self.magic_arg_matchers,
1992 self.custom_completer_matcher,
1993 self.custom_completer_matcher,
1993 self.magic_matcher,
1994 self.magic_matcher,
1994 self._jedi_matcher,
1995 self._jedi_matcher,
1995 self.dict_key_matcher,
1996 self.dict_key_matcher,
1996 self.file_matcher,
1997 self.file_matcher,
1997 ]
1998 ]
1998 else:
1999 else:
1999 return [
2000 return [
2000 *self.custom_matchers,
2001 *self.custom_matchers,
2001 *self._backslash_combining_matchers,
2002 *self._backslash_combining_matchers,
2002 *self.magic_arg_matchers,
2003 *self.magic_arg_matchers,
2003 self.custom_completer_matcher,
2004 self.custom_completer_matcher,
2004 self.dict_key_matcher,
2005 self.dict_key_matcher,
2005 self.magic_matcher,
2006 self.magic_matcher,
2006 self.python_matcher,
2007 self.python_matcher,
2007 self.file_matcher,
2008 self.file_matcher,
2008 self.python_func_kw_matcher,
2009 self.python_func_kw_matcher,
2009 ]
2010 ]
2010
2011
2011 def all_completions(self, text:str) -> List[str]:
2012 def all_completions(self, text:str) -> List[str]:
2012 """
2013 """
2013 Wrapper around the completion methods for the benefit of emacs.
2014 Wrapper around the completion methods for the benefit of emacs.
2014 """
2015 """
2015 prefix = text.rpartition('.')[0]
2016 prefix = text.rpartition('.')[0]
2016 with provisionalcompleter():
2017 with provisionalcompleter():
2017 return ['.'.join([prefix, c.text]) if prefix and self.use_jedi else c.text
2018 return ['.'.join([prefix, c.text]) if prefix and self.use_jedi else c.text
2018 for c in self.completions(text, len(text))]
2019 for c in self.completions(text, len(text))]
2019
2020
2020 return self.complete(text)[1]
2021 return self.complete(text)[1]
2021
2022
2022 def _clean_glob(self, text:str):
2023 def _clean_glob(self, text:str):
2023 return self.glob("%s*" % text)
2024 return self.glob("%s*" % text)
2024
2025
2025 def _clean_glob_win32(self, text:str):
2026 def _clean_glob_win32(self, text:str):
2026 return [f.replace("\\","/")
2027 return [f.replace("\\","/")
2027 for f in self.glob("%s*" % text)]
2028 for f in self.glob("%s*" % text)]
2028
2029
2029 @context_matcher()
2030 @context_matcher()
2030 def file_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2031 def file_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2031 """Same as :any:`file_matches`, but adopted to new Matcher API."""
2032 """Same as :any:`file_matches`, but adopted to new Matcher API."""
2032 matches = self.file_matches(context.token)
2033 matches = self.file_matches(context.token)
2033 # TODO: add a heuristic for suppressing (e.g. if it has OS-specific delimiter,
2034 # TODO: add a heuristic for suppressing (e.g. if it has OS-specific delimiter,
2034 # starts with `/home/`, `C:\`, etc)
2035 # starts with `/home/`, `C:\`, etc)
2035 return _convert_matcher_v1_result_to_v2(matches, type="path")
2036 return _convert_matcher_v1_result_to_v2(matches, type="path")
2036
2037
2037 def file_matches(self, text: str) -> List[str]:
2038 def file_matches(self, text: str) -> List[str]:
2038 """Match filenames, expanding ~USER type strings.
2039 """Match filenames, expanding ~USER type strings.
2039
2040
2040 Most of the seemingly convoluted logic in this completer is an
2041 Most of the seemingly convoluted logic in this completer is an
2041 attempt to handle filenames with spaces in them. And yet it's not
2042 attempt to handle filenames with spaces in them. And yet it's not
2042 quite perfect, because Python's readline doesn't expose all of the
2043 quite perfect, because Python's readline doesn't expose all of the
2043 GNU readline details needed for this to be done correctly.
2044 GNU readline details needed for this to be done correctly.
2044
2045
2045 For a filename with a space in it, the printed completions will be
2046 For a filename with a space in it, the printed completions will be
2046 only the parts after what's already been typed (instead of the
2047 only the parts after what's already been typed (instead of the
2047 full completions, as is normally done). I don't think with the
2048 full completions, as is normally done). I don't think with the
2048 current (as of Python 2.3) Python readline it's possible to do
2049 current (as of Python 2.3) Python readline it's possible to do
2049 better.
2050 better.
2050
2051
2051 .. deprecated:: 8.6
2052 .. deprecated:: 8.6
2052 You can use :meth:`file_matcher` instead.
2053 You can use :meth:`file_matcher` instead.
2053 """
2054 """
2054
2055
2055 # chars that require escaping with backslash - i.e. chars
2056 # chars that require escaping with backslash - i.e. chars
2056 # that readline treats incorrectly as delimiters, but we
2057 # that readline treats incorrectly as delimiters, but we
2057 # don't want to treat as delimiters in filename matching
2058 # don't want to treat as delimiters in filename matching
2058 # when escaped with backslash
2059 # when escaped with backslash
2059 if text.startswith('!'):
2060 if text.startswith('!'):
2060 text = text[1:]
2061 text = text[1:]
2061 text_prefix = u'!'
2062 text_prefix = u'!'
2062 else:
2063 else:
2063 text_prefix = u''
2064 text_prefix = u''
2064
2065
2065 text_until_cursor = self.text_until_cursor
2066 text_until_cursor = self.text_until_cursor
2066 # track strings with open quotes
2067 # track strings with open quotes
2067 open_quotes = has_open_quotes(text_until_cursor)
2068 open_quotes = has_open_quotes(text_until_cursor)
2068
2069
2069 if '(' in text_until_cursor or '[' in text_until_cursor:
2070 if '(' in text_until_cursor or '[' in text_until_cursor:
2070 lsplit = text
2071 lsplit = text
2071 else:
2072 else:
2072 try:
2073 try:
2073 # arg_split ~ shlex.split, but with unicode bugs fixed by us
2074 # arg_split ~ shlex.split, but with unicode bugs fixed by us
2074 lsplit = arg_split(text_until_cursor)[-1]
2075 lsplit = arg_split(text_until_cursor)[-1]
2075 except ValueError:
2076 except ValueError:
2076 # typically an unmatched ", or backslash without escaped char.
2077 # typically an unmatched ", or backslash without escaped char.
2077 if open_quotes:
2078 if open_quotes:
2078 lsplit = text_until_cursor.split(open_quotes)[-1]
2079 lsplit = text_until_cursor.split(open_quotes)[-1]
2079 else:
2080 else:
2080 return []
2081 return []
2081 except IndexError:
2082 except IndexError:
2082 # tab pressed on empty line
2083 # tab pressed on empty line
2083 lsplit = ""
2084 lsplit = ""
2084
2085
2085 if not open_quotes and lsplit != protect_filename(lsplit):
2086 if not open_quotes and lsplit != protect_filename(lsplit):
2086 # if protectables are found, do matching on the whole escaped name
2087 # if protectables are found, do matching on the whole escaped name
2087 has_protectables = True
2088 has_protectables = True
2088 text0,text = text,lsplit
2089 text0,text = text,lsplit
2089 else:
2090 else:
2090 has_protectables = False
2091 has_protectables = False
2091 text = os.path.expanduser(text)
2092 text = os.path.expanduser(text)
2092
2093
2093 if text == "":
2094 if text == "":
2094 return [text_prefix + protect_filename(f) for f in self.glob("*")]
2095 return [text_prefix + protect_filename(f) for f in self.glob("*")]
2095
2096
2096 # Compute the matches from the filesystem
2097 # Compute the matches from the filesystem
2097 if sys.platform == 'win32':
2098 if sys.platform == 'win32':
2098 m0 = self.clean_glob(text)
2099 m0 = self.clean_glob(text)
2099 else:
2100 else:
2100 m0 = self.clean_glob(text.replace('\\', ''))
2101 m0 = self.clean_glob(text.replace('\\', ''))
2101
2102
2102 if has_protectables:
2103 if has_protectables:
2103 # If we had protectables, we need to revert our changes to the
2104 # If we had protectables, we need to revert our changes to the
2104 # beginning of filename so that we don't double-write the part
2105 # beginning of filename so that we don't double-write the part
2105 # of the filename we have so far
2106 # of the filename we have so far
2106 len_lsplit = len(lsplit)
2107 len_lsplit = len(lsplit)
2107 matches = [text_prefix + text0 +
2108 matches = [text_prefix + text0 +
2108 protect_filename(f[len_lsplit:]) for f in m0]
2109 protect_filename(f[len_lsplit:]) for f in m0]
2109 else:
2110 else:
2110 if open_quotes:
2111 if open_quotes:
2111 # if we have a string with an open quote, we don't need to
2112 # if we have a string with an open quote, we don't need to
2112 # protect the names beyond the quote (and we _shouldn't_, as
2113 # protect the names beyond the quote (and we _shouldn't_, as
2113 # it would cause bugs when the filesystem call is made).
2114 # it would cause bugs when the filesystem call is made).
2114 matches = m0 if sys.platform == "win32" else\
2115 matches = m0 if sys.platform == "win32" else\
2115 [protect_filename(f, open_quotes) for f in m0]
2116 [protect_filename(f, open_quotes) for f in m0]
2116 else:
2117 else:
2117 matches = [text_prefix +
2118 matches = [text_prefix +
2118 protect_filename(f) for f in m0]
2119 protect_filename(f) for f in m0]
2119
2120
2120 # Mark directories in input list by appending '/' to their names.
2121 # Mark directories in input list by appending '/' to their names.
2121 return [x+'/' if os.path.isdir(x) else x for x in matches]
2122 return [x+'/' if os.path.isdir(x) else x for x in matches]
2122
2123
2123 @context_matcher()
2124 @context_matcher()
2124 def magic_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2125 def magic_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2125 """Match magics."""
2126 """Match magics."""
2126 text = context.token
2127 text = context.token
2127 matches = self.magic_matches(text)
2128 matches = self.magic_matches(text)
2128 result = _convert_matcher_v1_result_to_v2(matches, type="magic")
2129 result = _convert_matcher_v1_result_to_v2(matches, type="magic")
2129 is_magic_prefix = len(text) > 0 and text[0] == "%"
2130 is_magic_prefix = len(text) > 0 and text[0] == "%"
2130 result["suppress"] = is_magic_prefix and bool(result["completions"])
2131 result["suppress"] = is_magic_prefix and bool(result["completions"])
2131 return result
2132 return result
2132
2133
2133 def magic_matches(self, text: str) -> List[str]:
2134 def magic_matches(self, text: str) -> List[str]:
2134 """Match magics.
2135 """Match magics.
2135
2136
2136 .. deprecated:: 8.6
2137 .. deprecated:: 8.6
2137 You can use :meth:`magic_matcher` instead.
2138 You can use :meth:`magic_matcher` instead.
2138 """
2139 """
2139 # Get all shell magics now rather than statically, so magics loaded at
2140 # Get all shell magics now rather than statically, so magics loaded at
2140 # runtime show up too.
2141 # runtime show up too.
2141 lsm = self.shell.magics_manager.lsmagic()
2142 lsm = self.shell.magics_manager.lsmagic()
2142 line_magics = lsm['line']
2143 line_magics = lsm['line']
2143 cell_magics = lsm['cell']
2144 cell_magics = lsm['cell']
2144 pre = self.magic_escape
2145 pre = self.magic_escape
2145 pre2 = pre+pre
2146 pre2 = pre+pre
2146
2147
2147 explicit_magic = text.startswith(pre)
2148 explicit_magic = text.startswith(pre)
2148
2149
2149 # Completion logic:
2150 # Completion logic:
2150 # - user gives %%: only do cell magics
2151 # - user gives %%: only do cell magics
2151 # - user gives %: do both line and cell magics
2152 # - user gives %: do both line and cell magics
2152 # - no prefix: do both
2153 # - no prefix: do both
2153 # In other words, line magics are skipped if the user gives %% explicitly
2154 # In other words, line magics are skipped if the user gives %% explicitly
2154 #
2155 #
2155 # We also exclude magics that match any currently visible names:
2156 # We also exclude magics that match any currently visible names:
2156 # https://github.com/ipython/ipython/issues/4877, unless the user has
2157 # https://github.com/ipython/ipython/issues/4877, unless the user has
2157 # typed a %:
2158 # typed a %:
2158 # https://github.com/ipython/ipython/issues/10754
2159 # https://github.com/ipython/ipython/issues/10754
2159 bare_text = text.lstrip(pre)
2160 bare_text = text.lstrip(pre)
2160 global_matches = self.global_matches(bare_text)
2161 global_matches = self.global_matches(bare_text)
2161 if not explicit_magic:
2162 if not explicit_magic:
2162 def matches(magic):
2163 def matches(magic):
2163 """
2164 """
2164 Filter magics, in particular remove magics that match
2165 Filter magics, in particular remove magics that match
2165 a name present in global namespace.
2166 a name present in global namespace.
2166 """
2167 """
2167 return ( magic.startswith(bare_text) and
2168 return ( magic.startswith(bare_text) and
2168 magic not in global_matches )
2169 magic not in global_matches )
2169 else:
2170 else:
2170 def matches(magic):
2171 def matches(magic):
2171 return magic.startswith(bare_text)
2172 return magic.startswith(bare_text)
2172
2173
2173 comp = [ pre2+m for m in cell_magics if matches(m)]
2174 comp = [ pre2+m for m in cell_magics if matches(m)]
2174 if not text.startswith(pre2):
2175 if not text.startswith(pre2):
2175 comp += [ pre+m for m in line_magics if matches(m)]
2176 comp += [ pre+m for m in line_magics if matches(m)]
2176
2177
2177 return comp
2178 return comp
2178
2179
2179 @context_matcher()
2180 @context_matcher()
2180 def magic_config_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2181 def magic_config_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2181 """Match class names and attributes for %config magic."""
2182 """Match class names and attributes for %config magic."""
2182 # NOTE: uses `line_buffer` equivalent for compatibility
2183 # NOTE: uses `line_buffer` equivalent for compatibility
2183 matches = self.magic_config_matches(context.line_with_cursor)
2184 matches = self.magic_config_matches(context.line_with_cursor)
2184 return _convert_matcher_v1_result_to_v2(matches, type="param")
2185 return _convert_matcher_v1_result_to_v2(matches, type="param")
2185
2186
2186 def magic_config_matches(self, text: str) -> List[str]:
2187 def magic_config_matches(self, text: str) -> List[str]:
2187 """Match class names and attributes for %config magic.
2188 """Match class names and attributes for %config magic.
2188
2189
2189 .. deprecated:: 8.6
2190 .. deprecated:: 8.6
2190 You can use :meth:`magic_config_matcher` instead.
2191 You can use :meth:`magic_config_matcher` instead.
2191 """
2192 """
2192 texts = text.strip().split()
2193 texts = text.strip().split()
2193
2194
2194 if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'):
2195 if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'):
2195 # get all configuration classes
2196 # get all configuration classes
2196 classes = sorted(set([ c for c in self.shell.configurables
2197 classes = sorted(set([ c for c in self.shell.configurables
2197 if c.__class__.class_traits(config=True)
2198 if c.__class__.class_traits(config=True)
2198 ]), key=lambda x: x.__class__.__name__)
2199 ]), key=lambda x: x.__class__.__name__)
2199 classnames = [ c.__class__.__name__ for c in classes ]
2200 classnames = [ c.__class__.__name__ for c in classes ]
2200
2201
2201 # return all classnames if config or %config is given
2202 # return all classnames if config or %config is given
2202 if len(texts) == 1:
2203 if len(texts) == 1:
2203 return classnames
2204 return classnames
2204
2205
2205 # match classname
2206 # match classname
2206 classname_texts = texts[1].split('.')
2207 classname_texts = texts[1].split('.')
2207 classname = classname_texts[0]
2208 classname = classname_texts[0]
2208 classname_matches = [ c for c in classnames
2209 classname_matches = [ c for c in classnames
2209 if c.startswith(classname) ]
2210 if c.startswith(classname) ]
2210
2211
2211 # return matched classes or the matched class with attributes
2212 # return matched classes or the matched class with attributes
2212 if texts[1].find('.') < 0:
2213 if texts[1].find('.') < 0:
2213 return classname_matches
2214 return classname_matches
2214 elif len(classname_matches) == 1 and \
2215 elif len(classname_matches) == 1 and \
2215 classname_matches[0] == classname:
2216 classname_matches[0] == classname:
2216 cls = classes[classnames.index(classname)].__class__
2217 cls = classes[classnames.index(classname)].__class__
2217 help = cls.class_get_help()
2218 help = cls.class_get_help()
2218 # strip leading '--' from cl-args:
2219 # strip leading '--' from cl-args:
2219 help = re.sub(re.compile(r'^--', re.MULTILINE), '', help)
2220 help = re.sub(re.compile(r'^--', re.MULTILINE), '', help)
2220 return [ attr.split('=')[0]
2221 return [ attr.split('=')[0]
2221 for attr in help.strip().splitlines()
2222 for attr in help.strip().splitlines()
2222 if attr.startswith(texts[1]) ]
2223 if attr.startswith(texts[1]) ]
2223 return []
2224 return []
2224
2225
2225 @context_matcher()
2226 @context_matcher()
2226 def magic_color_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2227 def magic_color_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2227 """Match color schemes for %colors magic."""
2228 """Match color schemes for %colors magic."""
2228 # NOTE: uses `line_buffer` equivalent for compatibility
2229 # NOTE: uses `line_buffer` equivalent for compatibility
2229 matches = self.magic_color_matches(context.line_with_cursor)
2230 matches = self.magic_color_matches(context.line_with_cursor)
2230 return _convert_matcher_v1_result_to_v2(matches, type="param")
2231 return _convert_matcher_v1_result_to_v2(matches, type="param")
2231
2232
2232 def magic_color_matches(self, text: str) -> List[str]:
2233 def magic_color_matches(self, text: str) -> List[str]:
2233 """Match color schemes for %colors magic.
2234 """Match color schemes for %colors magic.
2234
2235
2235 .. deprecated:: 8.6
2236 .. deprecated:: 8.6
2236 You can use :meth:`magic_color_matcher` instead.
2237 You can use :meth:`magic_color_matcher` instead.
2237 """
2238 """
2238 texts = text.split()
2239 texts = text.split()
2239 if text.endswith(' '):
2240 if text.endswith(' '):
2240 # .split() strips off the trailing whitespace. Add '' back
2241 # .split() strips off the trailing whitespace. Add '' back
2241 # so that: '%colors ' -> ['%colors', '']
2242 # so that: '%colors ' -> ['%colors', '']
2242 texts.append('')
2243 texts.append('')
2243
2244
2244 if len(texts) == 2 and (texts[0] == 'colors' or texts[0] == '%colors'):
2245 if len(texts) == 2 and (texts[0] == 'colors' or texts[0] == '%colors'):
2245 prefix = texts[1]
2246 prefix = texts[1]
2246 return [ color for color in InspectColors.keys()
2247 return [ color for color in InspectColors.keys()
2247 if color.startswith(prefix) ]
2248 if color.startswith(prefix) ]
2248 return []
2249 return []
2249
2250
2250 @context_matcher(identifier="IPCompleter.jedi_matcher")
2251 @context_matcher(identifier="IPCompleter.jedi_matcher")
2251 def _jedi_matcher(self, context: CompletionContext) -> _JediMatcherResult:
2252 def _jedi_matcher(self, context: CompletionContext) -> _JediMatcherResult:
2252 matches = self._jedi_matches(
2253 matches = self._jedi_matches(
2253 cursor_column=context.cursor_position,
2254 cursor_column=context.cursor_position,
2254 cursor_line=context.cursor_line,
2255 cursor_line=context.cursor_line,
2255 text=context.full_text,
2256 text=context.full_text,
2256 )
2257 )
2257 return {
2258 return {
2258 "completions": matches,
2259 "completions": matches,
2259 # static analysis should not suppress other matchers
2260 # static analysis should not suppress other matchers
2260 "suppress": False,
2261 "suppress": False,
2261 }
2262 }
2262
2263
2263 def _jedi_matches(
2264 def _jedi_matches(
2264 self, cursor_column: int, cursor_line: int, text: str
2265 self, cursor_column: int, cursor_line: int, text: str
2265 ) -> Iterator[_JediCompletionLike]:
2266 ) -> Iterator[_JediCompletionLike]:
2266 """
2267 """
2267 Return a list of :any:`jedi.api.Completion`\\s object from a ``text`` and
2268 Return a list of :any:`jedi.api.Completion`\\s object from a ``text`` and
2268 cursor position.
2269 cursor position.
2269
2270
2270 Parameters
2271 Parameters
2271 ----------
2272 ----------
2272 cursor_column : int
2273 cursor_column : int
2273 column position of the cursor in ``text``, 0-indexed.
2274 column position of the cursor in ``text``, 0-indexed.
2274 cursor_line : int
2275 cursor_line : int
2275 line position of the cursor in ``text``, 0-indexed
2276 line position of the cursor in ``text``, 0-indexed
2276 text : str
2277 text : str
2277 text to complete
2278 text to complete
2278
2279
2279 Notes
2280 Notes
2280 -----
2281 -----
2281 If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion`
2282 If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion`
2282 object containing a string with the Jedi debug information attached.
2283 object containing a string with the Jedi debug information attached.
2283
2284
2284 .. deprecated:: 8.6
2285 .. deprecated:: 8.6
2285 You can use :meth:`_jedi_matcher` instead.
2286 You can use :meth:`_jedi_matcher` instead.
2286 """
2287 """
2287 namespaces = [self.namespace]
2288 namespaces = [self.namespace]
2288 if self.global_namespace is not None:
2289 if self.global_namespace is not None:
2289 namespaces.append(self.global_namespace)
2290 namespaces.append(self.global_namespace)
2290
2291
2291 completion_filter = lambda x:x
2292 completion_filter = lambda x:x
2292 offset = cursor_to_position(text, cursor_line, cursor_column)
2293 offset = cursor_to_position(text, cursor_line, cursor_column)
2293 # filter output if we are completing for object members
2294 # filter output if we are completing for object members
2294 if offset:
2295 if offset:
2295 pre = text[offset-1]
2296 pre = text[offset-1]
2296 if pre == '.':
2297 if pre == '.':
2297 if self.omit__names == 2:
2298 if self.omit__names == 2:
2298 completion_filter = lambda c:not c.name.startswith('_')
2299 completion_filter = lambda c:not c.name.startswith('_')
2299 elif self.omit__names == 1:
2300 elif self.omit__names == 1:
2300 completion_filter = lambda c:not (c.name.startswith('__') and c.name.endswith('__'))
2301 completion_filter = lambda c:not (c.name.startswith('__') and c.name.endswith('__'))
2301 elif self.omit__names == 0:
2302 elif self.omit__names == 0:
2302 completion_filter = lambda x:x
2303 completion_filter = lambda x:x
2303 else:
2304 else:
2304 raise ValueError("Don't understand self.omit__names == {}".format(self.omit__names))
2305 raise ValueError("Don't understand self.omit__names == {}".format(self.omit__names))
2305
2306
2306 interpreter = jedi.Interpreter(text[:offset], namespaces)
2307 interpreter = jedi.Interpreter(text[:offset], namespaces)
2307 try_jedi = True
2308 try_jedi = True
2308
2309
2309 try:
2310 try:
2310 # find the first token in the current tree -- if it is a ' or " then we are in a string
2311 # find the first token in the current tree -- if it is a ' or " then we are in a string
2311 completing_string = False
2312 completing_string = False
2312 try:
2313 try:
2313 first_child = next(c for c in interpreter._get_module().tree_node.children if hasattr(c, 'value'))
2314 first_child = next(c for c in interpreter._get_module().tree_node.children if hasattr(c, 'value'))
2314 except StopIteration:
2315 except StopIteration:
2315 pass
2316 pass
2316 else:
2317 else:
2317 # note the value may be ', ", or it may also be ''' or """, or
2318 # note the value may be ', ", or it may also be ''' or """, or
2318 # in some cases, """what/you/typed..., but all of these are
2319 # in some cases, """what/you/typed..., but all of these are
2319 # strings.
2320 # strings.
2320 completing_string = len(first_child.value) > 0 and first_child.value[0] in {"'", '"'}
2321 completing_string = len(first_child.value) > 0 and first_child.value[0] in {"'", '"'}
2321
2322
2322 # if we are in a string jedi is likely not the right candidate for
2323 # if we are in a string jedi is likely not the right candidate for
2323 # now. Skip it.
2324 # now. Skip it.
2324 try_jedi = not completing_string
2325 try_jedi = not completing_string
2325 except Exception as e:
2326 except Exception as e:
2326 # many of things can go wrong, we are using private API just don't crash.
2327 # many of things can go wrong, we are using private API just don't crash.
2327 if self.debug:
2328 if self.debug:
2328 print("Error detecting if completing a non-finished string :", e, '|')
2329 print("Error detecting if completing a non-finished string :", e, '|')
2329
2330
2330 if not try_jedi:
2331 if not try_jedi:
2331 return iter([])
2332 return iter([])
2332 try:
2333 try:
2333 return filter(completion_filter, interpreter.complete(column=cursor_column, line=cursor_line + 1))
2334 return filter(completion_filter, interpreter.complete(column=cursor_column, line=cursor_line + 1))
2334 except Exception as e:
2335 except Exception as e:
2335 if self.debug:
2336 if self.debug:
2336 return iter(
2337 return iter(
2337 [
2338 [
2338 _FakeJediCompletion(
2339 _FakeJediCompletion(
2339 'Oops Jedi has crashed, please report a bug with the following:\n"""\n%s\ns"""'
2340 'Oops Jedi has crashed, please report a bug with the following:\n"""\n%s\ns"""'
2340 % (e)
2341 % (e)
2341 )
2342 )
2342 ]
2343 ]
2343 )
2344 )
2344 else:
2345 else:
2345 return iter([])
2346 return iter([])
2346
2347
2347 @context_matcher()
2348 @context_matcher()
2348 def python_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2349 def python_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2349 """Match attributes or global python names"""
2350 """Match attributes or global python names"""
2350 text = context.line_with_cursor
2351 text = context.line_with_cursor
2351 if "." in text:
2352 if "." in text:
2352 try:
2353 try:
2353 matches, fragment = self._attr_matches(text, include_prefix=False)
2354 matches, fragment = self._attr_matches(text, include_prefix=False)
2354 if text.endswith(".") and self.omit__names:
2355 if text.endswith(".") and self.omit__names:
2355 if self.omit__names == 1:
2356 if self.omit__names == 1:
2356 # true if txt is _not_ a __ name, false otherwise:
2357 # true if txt is _not_ a __ name, false otherwise:
2357 no__name = lambda txt: re.match(r".*\.__.*?__", txt) is None
2358 no__name = lambda txt: re.match(r".*\.__.*?__", txt) is None
2358 else:
2359 else:
2359 # true if txt is _not_ a _ name, false otherwise:
2360 # true if txt is _not_ a _ name, false otherwise:
2360 no__name = (
2361 no__name = (
2361 lambda txt: re.match(r"\._.*?", txt[txt.rindex(".") :])
2362 lambda txt: re.match(r"\._.*?", txt[txt.rindex(".") :])
2362 is None
2363 is None
2363 )
2364 )
2364 matches = filter(no__name, matches)
2365 matches = filter(no__name, matches)
2365 return _convert_matcher_v1_result_to_v2(
2366 return _convert_matcher_v1_result_to_v2(
2366 matches, type="attribute", fragment=fragment
2367 matches, type="attribute", fragment=fragment
2367 )
2368 )
2368 except NameError:
2369 except NameError:
2369 # catches <undefined attributes>.<tab>
2370 # catches <undefined attributes>.<tab>
2370 matches = []
2371 matches = []
2371 return _convert_matcher_v1_result_to_v2(matches, type="attribute")
2372 return _convert_matcher_v1_result_to_v2(matches, type="attribute")
2372 else:
2373 else:
2373 matches = self.global_matches(context.token)
2374 matches = self.global_matches(context.token)
2374 # TODO: maybe distinguish between functions, modules and just "variables"
2375 # TODO: maybe distinguish between functions, modules and just "variables"
2375 return _convert_matcher_v1_result_to_v2(matches, type="variable")
2376 return _convert_matcher_v1_result_to_v2(matches, type="variable")
2376
2377
2377 @completion_matcher(api_version=1)
2378 @completion_matcher(api_version=1)
2378 def python_matches(self, text: str) -> Iterable[str]:
2379 def python_matches(self, text: str) -> Iterable[str]:
2379 """Match attributes or global python names.
2380 """Match attributes or global python names.
2380
2381
2381 .. deprecated:: 8.27
2382 .. deprecated:: 8.27
2382 You can use :meth:`python_matcher` instead."""
2383 You can use :meth:`python_matcher` instead."""
2383 if "." in text:
2384 if "." in text:
2384 try:
2385 try:
2385 matches = self.attr_matches(text)
2386 matches = self.attr_matches(text)
2386 if text.endswith('.') and self.omit__names:
2387 if text.endswith('.') and self.omit__names:
2387 if self.omit__names == 1:
2388 if self.omit__names == 1:
2388 # true if txt is _not_ a __ name, false otherwise:
2389 # true if txt is _not_ a __ name, false otherwise:
2389 no__name = (lambda txt:
2390 no__name = (lambda txt:
2390 re.match(r'.*\.__.*?__',txt) is None)
2391 re.match(r'.*\.__.*?__',txt) is None)
2391 else:
2392 else:
2392 # true if txt is _not_ a _ name, false otherwise:
2393 # true if txt is _not_ a _ name, false otherwise:
2393 no__name = (lambda txt:
2394 no__name = (lambda txt:
2394 re.match(r'\._.*?',txt[txt.rindex('.'):]) is None)
2395 re.match(r'\._.*?',txt[txt.rindex('.'):]) is None)
2395 matches = filter(no__name, matches)
2396 matches = filter(no__name, matches)
2396 except NameError:
2397 except NameError:
2397 # catches <undefined attributes>.<tab>
2398 # catches <undefined attributes>.<tab>
2398 matches = []
2399 matches = []
2399 else:
2400 else:
2400 matches = self.global_matches(text)
2401 matches = self.global_matches(text)
2401 return matches
2402 return matches
2402
2403
2403 def _default_arguments_from_docstring(self, doc):
2404 def _default_arguments_from_docstring(self, doc):
2404 """Parse the first line of docstring for call signature.
2405 """Parse the first line of docstring for call signature.
2405
2406
2406 Docstring should be of the form 'min(iterable[, key=func])\n'.
2407 Docstring should be of the form 'min(iterable[, key=func])\n'.
2407 It can also parse cython docstring of the form
2408 It can also parse cython docstring of the form
2408 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)'.
2409 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)'.
2409 """
2410 """
2410 if doc is None:
2411 if doc is None:
2411 return []
2412 return []
2412
2413
2413 #care only the firstline
2414 #care only the firstline
2414 line = doc.lstrip().splitlines()[0]
2415 line = doc.lstrip().splitlines()[0]
2415
2416
2416 #p = re.compile(r'^[\w|\s.]+\(([^)]*)\).*')
2417 #p = re.compile(r'^[\w|\s.]+\(([^)]*)\).*')
2417 #'min(iterable[, key=func])\n' -> 'iterable[, key=func]'
2418 #'min(iterable[, key=func])\n' -> 'iterable[, key=func]'
2418 sig = self.docstring_sig_re.search(line)
2419 sig = self.docstring_sig_re.search(line)
2419 if sig is None:
2420 if sig is None:
2420 return []
2421 return []
2421 # iterable[, key=func]' -> ['iterable[' ,' key=func]']
2422 # iterable[, key=func]' -> ['iterable[' ,' key=func]']
2422 sig = sig.groups()[0].split(',')
2423 sig = sig.groups()[0].split(',')
2423 ret = []
2424 ret = []
2424 for s in sig:
2425 for s in sig:
2425 #re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)')
2426 #re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)')
2426 ret += self.docstring_kwd_re.findall(s)
2427 ret += self.docstring_kwd_re.findall(s)
2427 return ret
2428 return ret
2428
2429
2429 def _default_arguments(self, obj):
2430 def _default_arguments(self, obj):
2430 """Return the list of default arguments of obj if it is callable,
2431 """Return the list of default arguments of obj if it is callable,
2431 or empty list otherwise."""
2432 or empty list otherwise."""
2432 call_obj = obj
2433 call_obj = obj
2433 ret = []
2434 ret = []
2434 if inspect.isbuiltin(obj):
2435 if inspect.isbuiltin(obj):
2435 pass
2436 pass
2436 elif not (inspect.isfunction(obj) or inspect.ismethod(obj)):
2437 elif not (inspect.isfunction(obj) or inspect.ismethod(obj)):
2437 if inspect.isclass(obj):
2438 if inspect.isclass(obj):
2438 #for cython embedsignature=True the constructor docstring
2439 #for cython embedsignature=True the constructor docstring
2439 #belongs to the object itself not __init__
2440 #belongs to the object itself not __init__
2440 ret += self._default_arguments_from_docstring(
2441 ret += self._default_arguments_from_docstring(
2441 getattr(obj, '__doc__', ''))
2442 getattr(obj, '__doc__', ''))
2442 # for classes, check for __init__,__new__
2443 # for classes, check for __init__,__new__
2443 call_obj = (getattr(obj, '__init__', None) or
2444 call_obj = (getattr(obj, '__init__', None) or
2444 getattr(obj, '__new__', None))
2445 getattr(obj, '__new__', None))
2445 # for all others, check if they are __call__able
2446 # for all others, check if they are __call__able
2446 elif hasattr(obj, '__call__'):
2447 elif hasattr(obj, '__call__'):
2447 call_obj = obj.__call__
2448 call_obj = obj.__call__
2448 ret += self._default_arguments_from_docstring(
2449 ret += self._default_arguments_from_docstring(
2449 getattr(call_obj, '__doc__', ''))
2450 getattr(call_obj, '__doc__', ''))
2450
2451
2451 _keeps = (inspect.Parameter.KEYWORD_ONLY,
2452 _keeps = (inspect.Parameter.KEYWORD_ONLY,
2452 inspect.Parameter.POSITIONAL_OR_KEYWORD)
2453 inspect.Parameter.POSITIONAL_OR_KEYWORD)
2453
2454
2454 try:
2455 try:
2455 sig = inspect.signature(obj)
2456 sig = inspect.signature(obj)
2456 ret.extend(k for k, v in sig.parameters.items() if
2457 ret.extend(k for k, v in sig.parameters.items() if
2457 v.kind in _keeps)
2458 v.kind in _keeps)
2458 except ValueError:
2459 except ValueError:
2459 pass
2460 pass
2460
2461
2461 return list(set(ret))
2462 return list(set(ret))
2462
2463
2463 @context_matcher()
2464 @context_matcher()
2464 def python_func_kw_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2465 def python_func_kw_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2465 """Match named parameters (kwargs) of the last open function."""
2466 """Match named parameters (kwargs) of the last open function."""
2466 matches = self.python_func_kw_matches(context.token)
2467 matches = self.python_func_kw_matches(context.token)
2467 return _convert_matcher_v1_result_to_v2(matches, type="param")
2468 return _convert_matcher_v1_result_to_v2(matches, type="param")
2468
2469
2469 def python_func_kw_matches(self, text):
2470 def python_func_kw_matches(self, text):
2470 """Match named parameters (kwargs) of the last open function.
2471 """Match named parameters (kwargs) of the last open function.
2471
2472
2472 .. deprecated:: 8.6
2473 .. deprecated:: 8.6
2473 You can use :meth:`python_func_kw_matcher` instead.
2474 You can use :meth:`python_func_kw_matcher` instead.
2474 """
2475 """
2475
2476
2476 if "." in text: # a parameter cannot be dotted
2477 if "." in text: # a parameter cannot be dotted
2477 return []
2478 return []
2478 try: regexp = self.__funcParamsRegex
2479 try: regexp = self.__funcParamsRegex
2479 except AttributeError:
2480 except AttributeError:
2480 regexp = self.__funcParamsRegex = re.compile(r'''
2481 regexp = self.__funcParamsRegex = re.compile(r'''
2481 '.*?(?<!\\)' | # single quoted strings or
2482 '.*?(?<!\\)' | # single quoted strings or
2482 ".*?(?<!\\)" | # double quoted strings or
2483 ".*?(?<!\\)" | # double quoted strings or
2483 \w+ | # identifier
2484 \w+ | # identifier
2484 \S # other characters
2485 \S # other characters
2485 ''', re.VERBOSE | re.DOTALL)
2486 ''', re.VERBOSE | re.DOTALL)
2486 # 1. find the nearest identifier that comes before an unclosed
2487 # 1. find the nearest identifier that comes before an unclosed
2487 # parenthesis before the cursor
2488 # parenthesis before the cursor
2488 # e.g. for "foo (1+bar(x), pa<cursor>,a=1)", the candidate is "foo"
2489 # e.g. for "foo (1+bar(x), pa<cursor>,a=1)", the candidate is "foo"
2489 tokens = regexp.findall(self.text_until_cursor)
2490 tokens = regexp.findall(self.text_until_cursor)
2490 iterTokens = reversed(tokens)
2491 iterTokens = reversed(tokens)
2491 openPar = 0
2492 openPar = 0
2492
2493
2493 for token in iterTokens:
2494 for token in iterTokens:
2494 if token == ')':
2495 if token == ')':
2495 openPar -= 1
2496 openPar -= 1
2496 elif token == '(':
2497 elif token == '(':
2497 openPar += 1
2498 openPar += 1
2498 if openPar > 0:
2499 if openPar > 0:
2499 # found the last unclosed parenthesis
2500 # found the last unclosed parenthesis
2500 break
2501 break
2501 else:
2502 else:
2502 return []
2503 return []
2503 # 2. Concatenate dotted names ("foo.bar" for "foo.bar(x, pa" )
2504 # 2. Concatenate dotted names ("foo.bar" for "foo.bar(x, pa" )
2504 ids = []
2505 ids = []
2505 isId = re.compile(r'\w+$').match
2506 isId = re.compile(r'\w+$').match
2506
2507
2507 while True:
2508 while True:
2508 try:
2509 try:
2509 ids.append(next(iterTokens))
2510 ids.append(next(iterTokens))
2510 if not isId(ids[-1]):
2511 if not isId(ids[-1]):
2511 ids.pop()
2512 ids.pop()
2512 break
2513 break
2513 if not next(iterTokens) == '.':
2514 if not next(iterTokens) == '.':
2514 break
2515 break
2515 except StopIteration:
2516 except StopIteration:
2516 break
2517 break
2517
2518
2518 # Find all named arguments already assigned to, as to avoid suggesting
2519 # Find all named arguments already assigned to, as to avoid suggesting
2519 # them again
2520 # them again
2520 usedNamedArgs = set()
2521 usedNamedArgs = set()
2521 par_level = -1
2522 par_level = -1
2522 for token, next_token in zip(tokens, tokens[1:]):
2523 for token, next_token in zip(tokens, tokens[1:]):
2523 if token == '(':
2524 if token == '(':
2524 par_level += 1
2525 par_level += 1
2525 elif token == ')':
2526 elif token == ')':
2526 par_level -= 1
2527 par_level -= 1
2527
2528
2528 if par_level != 0:
2529 if par_level != 0:
2529 continue
2530 continue
2530
2531
2531 if next_token != '=':
2532 if next_token != '=':
2532 continue
2533 continue
2533
2534
2534 usedNamedArgs.add(token)
2535 usedNamedArgs.add(token)
2535
2536
2536 argMatches = []
2537 argMatches = []
2537 try:
2538 try:
2538 callableObj = '.'.join(ids[::-1])
2539 callableObj = '.'.join(ids[::-1])
2539 namedArgs = self._default_arguments(eval(callableObj,
2540 namedArgs = self._default_arguments(eval(callableObj,
2540 self.namespace))
2541 self.namespace))
2541
2542
2542 # Remove used named arguments from the list, no need to show twice
2543 # Remove used named arguments from the list, no need to show twice
2543 for namedArg in set(namedArgs) - usedNamedArgs:
2544 for namedArg in set(namedArgs) - usedNamedArgs:
2544 if namedArg.startswith(text):
2545 if namedArg.startswith(text):
2545 argMatches.append("%s=" %namedArg)
2546 argMatches.append("%s=" %namedArg)
2546 except:
2547 except:
2547 pass
2548 pass
2548
2549
2549 return argMatches
2550 return argMatches
2550
2551
2551 @staticmethod
2552 @staticmethod
2552 def _get_keys(obj: Any) -> List[Any]:
2553 def _get_keys(obj: Any) -> List[Any]:
2553 # Objects can define their own completions by defining an
2554 # Objects can define their own completions by defining an
2554 # _ipy_key_completions_() method.
2555 # _ipy_key_completions_() method.
2555 method = get_real_method(obj, '_ipython_key_completions_')
2556 method = get_real_method(obj, '_ipython_key_completions_')
2556 if method is not None:
2557 if method is not None:
2557 return method()
2558 return method()
2558
2559
2559 # Special case some common in-memory dict-like types
2560 # Special case some common in-memory dict-like types
2560 if isinstance(obj, dict) or _safe_isinstance(obj, "pandas", "DataFrame"):
2561 if isinstance(obj, dict) or _safe_isinstance(obj, "pandas", "DataFrame"):
2561 try:
2562 try:
2562 return list(obj.keys())
2563 return list(obj.keys())
2563 except Exception:
2564 except Exception:
2564 return []
2565 return []
2565 elif _safe_isinstance(obj, "pandas", "core", "indexing", "_LocIndexer"):
2566 elif _safe_isinstance(obj, "pandas", "core", "indexing", "_LocIndexer"):
2566 try:
2567 try:
2567 return list(obj.obj.keys())
2568 return list(obj.obj.keys())
2568 except Exception:
2569 except Exception:
2569 return []
2570 return []
2570 elif _safe_isinstance(obj, 'numpy', 'ndarray') or\
2571 elif _safe_isinstance(obj, 'numpy', 'ndarray') or\
2571 _safe_isinstance(obj, 'numpy', 'void'):
2572 _safe_isinstance(obj, 'numpy', 'void'):
2572 return obj.dtype.names or []
2573 return obj.dtype.names or []
2573 return []
2574 return []
2574
2575
2575 @context_matcher()
2576 @context_matcher()
2576 def dict_key_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2577 def dict_key_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2577 """Match string keys in a dictionary, after e.g. ``foo[``."""
2578 """Match string keys in a dictionary, after e.g. ``foo[``."""
2578 matches = self.dict_key_matches(context.token)
2579 matches = self.dict_key_matches(context.token)
2579 return _convert_matcher_v1_result_to_v2(
2580 return _convert_matcher_v1_result_to_v2(
2580 matches, type="dict key", suppress_if_matches=True
2581 matches, type="dict key", suppress_if_matches=True
2581 )
2582 )
2582
2583
2583 def dict_key_matches(self, text: str) -> List[str]:
2584 def dict_key_matches(self, text: str) -> List[str]:
2584 """Match string keys in a dictionary, after e.g. ``foo[``.
2585 """Match string keys in a dictionary, after e.g. ``foo[``.
2585
2586
2586 .. deprecated:: 8.6
2587 .. deprecated:: 8.6
2587 You can use :meth:`dict_key_matcher` instead.
2588 You can use :meth:`dict_key_matcher` instead.
2588 """
2589 """
2589
2590
2590 # Short-circuit on closed dictionary (regular expression would
2591 # Short-circuit on closed dictionary (regular expression would
2591 # not match anyway, but would take quite a while).
2592 # not match anyway, but would take quite a while).
2592 if self.text_until_cursor.strip().endswith("]"):
2593 if self.text_until_cursor.strip().endswith("]"):
2593 return []
2594 return []
2594
2595
2595 match = DICT_MATCHER_REGEX.search(self.text_until_cursor)
2596 match = DICT_MATCHER_REGEX.search(self.text_until_cursor)
2596
2597
2597 if match is None:
2598 if match is None:
2598 return []
2599 return []
2599
2600
2600 expr, prior_tuple_keys, key_prefix = match.groups()
2601 expr, prior_tuple_keys, key_prefix = match.groups()
2601
2602
2602 obj = self._evaluate_expr(expr)
2603 obj = self._evaluate_expr(expr)
2603
2604
2604 if obj is not_found:
2605 if obj is not_found:
2605 return []
2606 return []
2606
2607
2607 keys = self._get_keys(obj)
2608 keys = self._get_keys(obj)
2608 if not keys:
2609 if not keys:
2609 return keys
2610 return keys
2610
2611
2611 tuple_prefix = guarded_eval(
2612 tuple_prefix = guarded_eval(
2612 prior_tuple_keys,
2613 prior_tuple_keys,
2613 EvaluationContext(
2614 EvaluationContext(
2614 globals=self.global_namespace,
2615 globals=self.global_namespace,
2615 locals=self.namespace,
2616 locals=self.namespace,
2616 evaluation=self.evaluation, # type: ignore
2617 evaluation=self.evaluation, # type: ignore
2617 in_subscript=True,
2618 in_subscript=True,
2618 ),
2619 ),
2619 )
2620 )
2620
2621
2621 closing_quote, token_offset, matches = match_dict_keys(
2622 closing_quote, token_offset, matches = match_dict_keys(
2622 keys, key_prefix, self.splitter.delims, extra_prefix=tuple_prefix
2623 keys, key_prefix, self.splitter.delims, extra_prefix=tuple_prefix
2623 )
2624 )
2624 if not matches:
2625 if not matches:
2625 return []
2626 return []
2626
2627
2627 # get the cursor position of
2628 # get the cursor position of
2628 # - the text being completed
2629 # - the text being completed
2629 # - the start of the key text
2630 # - the start of the key text
2630 # - the start of the completion
2631 # - the start of the completion
2631 text_start = len(self.text_until_cursor) - len(text)
2632 text_start = len(self.text_until_cursor) - len(text)
2632 if key_prefix:
2633 if key_prefix:
2633 key_start = match.start(3)
2634 key_start = match.start(3)
2634 completion_start = key_start + token_offset
2635 completion_start = key_start + token_offset
2635 else:
2636 else:
2636 key_start = completion_start = match.end()
2637 key_start = completion_start = match.end()
2637
2638
2638 # grab the leading prefix, to make sure all completions start with `text`
2639 # grab the leading prefix, to make sure all completions start with `text`
2639 if text_start > key_start:
2640 if text_start > key_start:
2640 leading = ''
2641 leading = ''
2641 else:
2642 else:
2642 leading = text[text_start:completion_start]
2643 leading = text[text_start:completion_start]
2643
2644
2644 # append closing quote and bracket as appropriate
2645 # append closing quote and bracket as appropriate
2645 # this is *not* appropriate if the opening quote or bracket is outside
2646 # this is *not* appropriate if the opening quote or bracket is outside
2646 # the text given to this method, e.g. `d["""a\nt
2647 # the text given to this method, e.g. `d["""a\nt
2647 can_close_quote = False
2648 can_close_quote = False
2648 can_close_bracket = False
2649 can_close_bracket = False
2649
2650
2650 continuation = self.line_buffer[len(self.text_until_cursor) :].strip()
2651 continuation = self.line_buffer[len(self.text_until_cursor) :].strip()
2651
2652
2652 if continuation.startswith(closing_quote):
2653 if continuation.startswith(closing_quote):
2653 # do not close if already closed, e.g. `d['a<tab>'`
2654 # do not close if already closed, e.g. `d['a<tab>'`
2654 continuation = continuation[len(closing_quote) :]
2655 continuation = continuation[len(closing_quote) :]
2655 else:
2656 else:
2656 can_close_quote = True
2657 can_close_quote = True
2657
2658
2658 continuation = continuation.strip()
2659 continuation = continuation.strip()
2659
2660
2660 # e.g. `pandas.DataFrame` has different tuple indexer behaviour,
2661 # e.g. `pandas.DataFrame` has different tuple indexer behaviour,
2661 # handling it is out of scope, so let's avoid appending suffixes.
2662 # handling it is out of scope, so let's avoid appending suffixes.
2662 has_known_tuple_handling = isinstance(obj, dict)
2663 has_known_tuple_handling = isinstance(obj, dict)
2663
2664
2664 can_close_bracket = (
2665 can_close_bracket = (
2665 not continuation.startswith("]") and self.auto_close_dict_keys
2666 not continuation.startswith("]") and self.auto_close_dict_keys
2666 )
2667 )
2667 can_close_tuple_item = (
2668 can_close_tuple_item = (
2668 not continuation.startswith(",")
2669 not continuation.startswith(",")
2669 and has_known_tuple_handling
2670 and has_known_tuple_handling
2670 and self.auto_close_dict_keys
2671 and self.auto_close_dict_keys
2671 )
2672 )
2672 can_close_quote = can_close_quote and self.auto_close_dict_keys
2673 can_close_quote = can_close_quote and self.auto_close_dict_keys
2673
2674
2674 # fast path if closing quote should be appended but not suffix is allowed
2675 # fast path if closing quote should be appended but not suffix is allowed
2675 if not can_close_quote and not can_close_bracket and closing_quote:
2676 if not can_close_quote and not can_close_bracket and closing_quote:
2676 return [leading + k for k in matches]
2677 return [leading + k for k in matches]
2677
2678
2678 results = []
2679 results = []
2679
2680
2680 end_of_tuple_or_item = _DictKeyState.END_OF_TUPLE | _DictKeyState.END_OF_ITEM
2681 end_of_tuple_or_item = _DictKeyState.END_OF_TUPLE | _DictKeyState.END_OF_ITEM
2681
2682
2682 for k, state_flag in matches.items():
2683 for k, state_flag in matches.items():
2683 result = leading + k
2684 result = leading + k
2684 if can_close_quote and closing_quote:
2685 if can_close_quote and closing_quote:
2685 result += closing_quote
2686 result += closing_quote
2686
2687
2687 if state_flag == end_of_tuple_or_item:
2688 if state_flag == end_of_tuple_or_item:
2688 # We do not know which suffix to add,
2689 # We do not know which suffix to add,
2689 # e.g. both tuple item and string
2690 # e.g. both tuple item and string
2690 # match this item.
2691 # match this item.
2691 pass
2692 pass
2692
2693
2693 if state_flag in end_of_tuple_or_item and can_close_bracket:
2694 if state_flag in end_of_tuple_or_item and can_close_bracket:
2694 result += "]"
2695 result += "]"
2695 if state_flag == _DictKeyState.IN_TUPLE and can_close_tuple_item:
2696 if state_flag == _DictKeyState.IN_TUPLE and can_close_tuple_item:
2696 result += ", "
2697 result += ", "
2697 results.append(result)
2698 results.append(result)
2698 return results
2699 return results
2699
2700
2700 @context_matcher()
2701 @context_matcher()
2701 def unicode_name_matcher(self, context: CompletionContext):
2702 def unicode_name_matcher(self, context: CompletionContext):
2702 """Same as :any:`unicode_name_matches`, but adopted to new Matcher API."""
2703 """Same as :any:`unicode_name_matches`, but adopted to new Matcher API."""
2703 fragment, matches = self.unicode_name_matches(context.text_until_cursor)
2704 fragment, matches = self.unicode_name_matches(context.text_until_cursor)
2704 return _convert_matcher_v1_result_to_v2(
2705 return _convert_matcher_v1_result_to_v2(
2705 matches, type="unicode", fragment=fragment, suppress_if_matches=True
2706 matches, type="unicode", fragment=fragment, suppress_if_matches=True
2706 )
2707 )
2707
2708
2708 @staticmethod
2709 @staticmethod
2709 def unicode_name_matches(text: str) -> Tuple[str, List[str]]:
2710 def unicode_name_matches(text: str) -> Tuple[str, List[str]]:
2710 """Match Latex-like syntax for unicode characters base
2711 """Match Latex-like syntax for unicode characters base
2711 on the name of the character.
2712 on the name of the character.
2712
2713
2713 This does ``\\GREEK SMALL LETTER ETA`` -> ``η``
2714 This does ``\\GREEK SMALL LETTER ETA`` -> ``η``
2714
2715
2715 Works only on valid python 3 identifier, or on combining characters that
2716 Works only on valid python 3 identifier, or on combining characters that
2716 will combine to form a valid identifier.
2717 will combine to form a valid identifier.
2717 """
2718 """
2718 slashpos = text.rfind('\\')
2719 slashpos = text.rfind('\\')
2719 if slashpos > -1:
2720 if slashpos > -1:
2720 s = text[slashpos+1:]
2721 s = text[slashpos+1:]
2721 try :
2722 try :
2722 unic = unicodedata.lookup(s)
2723 unic = unicodedata.lookup(s)
2723 # allow combining chars
2724 # allow combining chars
2724 if ('a'+unic).isidentifier():
2725 if ('a'+unic).isidentifier():
2725 return '\\'+s,[unic]
2726 return '\\'+s,[unic]
2726 except KeyError:
2727 except KeyError:
2727 pass
2728 pass
2728 return '', []
2729 return '', []
2729
2730
2730 @context_matcher()
2731 @context_matcher()
2731 def latex_name_matcher(self, context: CompletionContext):
2732 def latex_name_matcher(self, context: CompletionContext):
2732 """Match Latex syntax for unicode characters.
2733 """Match Latex syntax for unicode characters.
2733
2734
2734 This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α``
2735 This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α``
2735 """
2736 """
2736 fragment, matches = self.latex_matches(context.text_until_cursor)
2737 fragment, matches = self.latex_matches(context.text_until_cursor)
2737 return _convert_matcher_v1_result_to_v2(
2738 return _convert_matcher_v1_result_to_v2(
2738 matches, type="latex", fragment=fragment, suppress_if_matches=True
2739 matches, type="latex", fragment=fragment, suppress_if_matches=True
2739 )
2740 )
2740
2741
2741 def latex_matches(self, text: str) -> Tuple[str, Sequence[str]]:
2742 def latex_matches(self, text: str) -> Tuple[str, Sequence[str]]:
2742 """Match Latex syntax for unicode characters.
2743 """Match Latex syntax for unicode characters.
2743
2744
2744 This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α``
2745 This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α``
2745
2746
2746 .. deprecated:: 8.6
2747 .. deprecated:: 8.6
2747 You can use :meth:`latex_name_matcher` instead.
2748 You can use :meth:`latex_name_matcher` instead.
2748 """
2749 """
2749 slashpos = text.rfind('\\')
2750 slashpos = text.rfind('\\')
2750 if slashpos > -1:
2751 if slashpos > -1:
2751 s = text[slashpos:]
2752 s = text[slashpos:]
2752 if s in latex_symbols:
2753 if s in latex_symbols:
2753 # Try to complete a full latex symbol to unicode
2754 # Try to complete a full latex symbol to unicode
2754 # \\alpha -> α
2755 # \\alpha -> α
2755 return s, [latex_symbols[s]]
2756 return s, [latex_symbols[s]]
2756 else:
2757 else:
2757 # If a user has partially typed a latex symbol, give them
2758 # If a user has partially typed a latex symbol, give them
2758 # a full list of options \al -> [\aleph, \alpha]
2759 # a full list of options \al -> [\aleph, \alpha]
2759 matches = [k for k in latex_symbols if k.startswith(s)]
2760 matches = [k for k in latex_symbols if k.startswith(s)]
2760 if matches:
2761 if matches:
2761 return s, matches
2762 return s, matches
2762 return '', ()
2763 return '', ()
2763
2764
2764 @context_matcher()
2765 @context_matcher()
2765 def custom_completer_matcher(self, context):
2766 def custom_completer_matcher(self, context):
2766 """Dispatch custom completer.
2767 """Dispatch custom completer.
2767
2768
2768 If a match is found, suppresses all other matchers except for Jedi.
2769 If a match is found, suppresses all other matchers except for Jedi.
2769 """
2770 """
2770 matches = self.dispatch_custom_completer(context.token) or []
2771 matches = self.dispatch_custom_completer(context.token) or []
2771 result = _convert_matcher_v1_result_to_v2(
2772 result = _convert_matcher_v1_result_to_v2(
2772 matches, type=_UNKNOWN_TYPE, suppress_if_matches=True
2773 matches, type=_UNKNOWN_TYPE, suppress_if_matches=True
2773 )
2774 )
2774 result["ordered"] = True
2775 result["ordered"] = True
2775 result["do_not_suppress"] = {_get_matcher_id(self._jedi_matcher)}
2776 result["do_not_suppress"] = {_get_matcher_id(self._jedi_matcher)}
2776 return result
2777 return result
2777
2778
2778 def dispatch_custom_completer(self, text):
2779 def dispatch_custom_completer(self, text):
2779 """
2780 """
2780 .. deprecated:: 8.6
2781 .. deprecated:: 8.6
2781 You can use :meth:`custom_completer_matcher` instead.
2782 You can use :meth:`custom_completer_matcher` instead.
2782 """
2783 """
2783 if not self.custom_completers:
2784 if not self.custom_completers:
2784 return
2785 return
2785
2786
2786 line = self.line_buffer
2787 line = self.line_buffer
2787 if not line.strip():
2788 if not line.strip():
2788 return None
2789 return None
2789
2790
2790 # Create a little structure to pass all the relevant information about
2791 # Create a little structure to pass all the relevant information about
2791 # the current completion to any custom completer.
2792 # the current completion to any custom completer.
2792 event = SimpleNamespace()
2793 event = SimpleNamespace()
2793 event.line = line
2794 event.line = line
2794 event.symbol = text
2795 event.symbol = text
2795 cmd = line.split(None,1)[0]
2796 cmd = line.split(None,1)[0]
2796 event.command = cmd
2797 event.command = cmd
2797 event.text_until_cursor = self.text_until_cursor
2798 event.text_until_cursor = self.text_until_cursor
2798
2799
2799 # for foo etc, try also to find completer for %foo
2800 # for foo etc, try also to find completer for %foo
2800 if not cmd.startswith(self.magic_escape):
2801 if not cmd.startswith(self.magic_escape):
2801 try_magic = self.custom_completers.s_matches(
2802 try_magic = self.custom_completers.s_matches(
2802 self.magic_escape + cmd)
2803 self.magic_escape + cmd)
2803 else:
2804 else:
2804 try_magic = []
2805 try_magic = []
2805
2806
2806 for c in itertools.chain(self.custom_completers.s_matches(cmd),
2807 for c in itertools.chain(self.custom_completers.s_matches(cmd),
2807 try_magic,
2808 try_magic,
2808 self.custom_completers.flat_matches(self.text_until_cursor)):
2809 self.custom_completers.flat_matches(self.text_until_cursor)):
2809 try:
2810 try:
2810 res = c(event)
2811 res = c(event)
2811 if res:
2812 if res:
2812 # first, try case sensitive match
2813 # first, try case sensitive match
2813 withcase = [r for r in res if r.startswith(text)]
2814 withcase = [r for r in res if r.startswith(text)]
2814 if withcase:
2815 if withcase:
2815 return withcase
2816 return withcase
2816 # if none, then case insensitive ones are ok too
2817 # if none, then case insensitive ones are ok too
2817 text_low = text.lower()
2818 text_low = text.lower()
2818 return [r for r in res if r.lower().startswith(text_low)]
2819 return [r for r in res if r.lower().startswith(text_low)]
2819 except TryNext:
2820 except TryNext:
2820 pass
2821 pass
2821 except KeyboardInterrupt:
2822 except KeyboardInterrupt:
2822 """
2823 """
2823 If custom completer take too long,
2824 If custom completer take too long,
2824 let keyboard interrupt abort and return nothing.
2825 let keyboard interrupt abort and return nothing.
2825 """
2826 """
2826 break
2827 break
2827
2828
2828 return None
2829 return None
2829
2830
2830 def completions(self, text: str, offset: int)->Iterator[Completion]:
2831 def completions(self, text: str, offset: int)->Iterator[Completion]:
2831 """
2832 """
2832 Returns an iterator over the possible completions
2833 Returns an iterator over the possible completions
2833
2834
2834 .. warning::
2835 .. warning::
2835
2836
2836 Unstable
2837 Unstable
2837
2838
2838 This function is unstable, API may change without warning.
2839 This function is unstable, API may change without warning.
2839 It will also raise unless use in proper context manager.
2840 It will also raise unless use in proper context manager.
2840
2841
2841 Parameters
2842 Parameters
2842 ----------
2843 ----------
2843 text : str
2844 text : str
2844 Full text of the current input, multi line string.
2845 Full text of the current input, multi line string.
2845 offset : int
2846 offset : int
2846 Integer representing the position of the cursor in ``text``. Offset
2847 Integer representing the position of the cursor in ``text``. Offset
2847 is 0-based indexed.
2848 is 0-based indexed.
2848
2849
2849 Yields
2850 Yields
2850 ------
2851 ------
2851 Completion
2852 Completion
2852
2853
2853 Notes
2854 Notes
2854 -----
2855 -----
2855 The cursor on a text can either be seen as being "in between"
2856 The cursor on a text can either be seen as being "in between"
2856 characters or "On" a character depending on the interface visible to
2857 characters or "On" a character depending on the interface visible to
2857 the user. For consistency the cursor being on "in between" characters X
2858 the user. For consistency the cursor being on "in between" characters X
2858 and Y is equivalent to the cursor being "on" character Y, that is to say
2859 and Y is equivalent to the cursor being "on" character Y, that is to say
2859 the character the cursor is on is considered as being after the cursor.
2860 the character the cursor is on is considered as being after the cursor.
2860
2861
2861 Combining characters may span more that one position in the
2862 Combining characters may span more that one position in the
2862 text.
2863 text.
2863
2864
2864 .. note::
2865 .. note::
2865
2866
2866 If ``IPCompleter.debug`` is :any:`True` will yield a ``--jedi/ipython--``
2867 If ``IPCompleter.debug`` is :any:`True` will yield a ``--jedi/ipython--``
2867 fake Completion token to distinguish completion returned by Jedi
2868 fake Completion token to distinguish completion returned by Jedi
2868 and usual IPython completion.
2869 and usual IPython completion.
2869
2870
2870 .. note::
2871 .. note::
2871
2872
2872 Completions are not completely deduplicated yet. If identical
2873 Completions are not completely deduplicated yet. If identical
2873 completions are coming from different sources this function does not
2874 completions are coming from different sources this function does not
2874 ensure that each completion object will only be present once.
2875 ensure that each completion object will only be present once.
2875 """
2876 """
2876 warnings.warn("_complete is a provisional API (as of IPython 6.0). "
2877 warnings.warn("_complete is a provisional API (as of IPython 6.0). "
2877 "It may change without warnings. "
2878 "It may change without warnings. "
2878 "Use in corresponding context manager.",
2879 "Use in corresponding context manager.",
2879 category=ProvisionalCompleterWarning, stacklevel=2)
2880 category=ProvisionalCompleterWarning, stacklevel=2)
2880
2881
2881 seen = set()
2882 seen = set()
2882 profiler:Optional[cProfile.Profile]
2883 profiler:Optional[cProfile.Profile]
2883 try:
2884 try:
2884 if self.profile_completions:
2885 if self.profile_completions:
2885 import cProfile
2886 import cProfile
2886 profiler = cProfile.Profile()
2887 profiler = cProfile.Profile()
2887 profiler.enable()
2888 profiler.enable()
2888 else:
2889 else:
2889 profiler = None
2890 profiler = None
2890
2891
2891 for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000):
2892 for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000):
2892 if c and (c in seen):
2893 if c and (c in seen):
2893 continue
2894 continue
2894 yield c
2895 yield c
2895 seen.add(c)
2896 seen.add(c)
2896 except KeyboardInterrupt:
2897 except KeyboardInterrupt:
2897 """if completions take too long and users send keyboard interrupt,
2898 """if completions take too long and users send keyboard interrupt,
2898 do not crash and return ASAP. """
2899 do not crash and return ASAP. """
2899 pass
2900 pass
2900 finally:
2901 finally:
2901 if profiler is not None:
2902 if profiler is not None:
2902 profiler.disable()
2903 profiler.disable()
2903 ensure_dir_exists(self.profiler_output_dir)
2904 ensure_dir_exists(self.profiler_output_dir)
2904 output_path = os.path.join(self.profiler_output_dir, str(uuid.uuid4()))
2905 output_path = os.path.join(self.profiler_output_dir, str(uuid.uuid4()))
2905 print("Writing profiler output to", output_path)
2906 print("Writing profiler output to", output_path)
2906 profiler.dump_stats(output_path)
2907 profiler.dump_stats(output_path)
2907
2908
2908 def _completions(self, full_text: str, offset: int, *, _timeout) -> Iterator[Completion]:
2909 def _completions(self, full_text: str, offset: int, *, _timeout) -> Iterator[Completion]:
2909 """
2910 """
2910 Core completion module.Same signature as :any:`completions`, with the
2911 Core completion module.Same signature as :any:`completions`, with the
2911 extra `timeout` parameter (in seconds).
2912 extra `timeout` parameter (in seconds).
2912
2913
2913 Computing jedi's completion ``.type`` can be quite expensive (it is a
2914 Computing jedi's completion ``.type`` can be quite expensive (it is a
2914 lazy property) and can require some warm-up, more warm up than just
2915 lazy property) and can require some warm-up, more warm up than just
2915 computing the ``name`` of a completion. The warm-up can be :
2916 computing the ``name`` of a completion. The warm-up can be :
2916
2917
2917 - Long warm-up the first time a module is encountered after
2918 - Long warm-up the first time a module is encountered after
2918 install/update: actually build parse/inference tree.
2919 install/update: actually build parse/inference tree.
2919
2920
2920 - first time the module is encountered in a session: load tree from
2921 - first time the module is encountered in a session: load tree from
2921 disk.
2922 disk.
2922
2923
2923 We don't want to block completions for tens of seconds so we give the
2924 We don't want to block completions for tens of seconds so we give the
2924 completer a "budget" of ``_timeout`` seconds per invocation to compute
2925 completer a "budget" of ``_timeout`` seconds per invocation to compute
2925 completions types, the completions that have not yet been computed will
2926 completions types, the completions that have not yet been computed will
2926 be marked as "unknown" an will have a chance to be computed next round
2927 be marked as "unknown" an will have a chance to be computed next round
2927 are things get cached.
2928 are things get cached.
2928
2929
2929 Keep in mind that Jedi is not the only thing treating the completion so
2930 Keep in mind that Jedi is not the only thing treating the completion so
2930 keep the timeout short-ish as if we take more than 0.3 second we still
2931 keep the timeout short-ish as if we take more than 0.3 second we still
2931 have lots of processing to do.
2932 have lots of processing to do.
2932
2933
2933 """
2934 """
2934 deadline = time.monotonic() + _timeout
2935 deadline = time.monotonic() + _timeout
2935
2936
2936 before = full_text[:offset]
2937 before = full_text[:offset]
2937 cursor_line, cursor_column = position_to_cursor(full_text, offset)
2938 cursor_line, cursor_column = position_to_cursor(full_text, offset)
2938
2939
2939 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
2940 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
2940
2941
2941 def is_non_jedi_result(
2942 def is_non_jedi_result(
2942 result: MatcherResult, identifier: str
2943 result: MatcherResult, identifier: str
2943 ) -> TypeGuard[SimpleMatcherResult]:
2944 ) -> TypeGuard[SimpleMatcherResult]:
2944 return identifier != jedi_matcher_id
2945 return identifier != jedi_matcher_id
2945
2946
2946 results = self._complete(
2947 results = self._complete(
2947 full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column
2948 full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column
2948 )
2949 )
2949
2950
2950 non_jedi_results: Dict[str, SimpleMatcherResult] = {
2951 non_jedi_results: Dict[str, SimpleMatcherResult] = {
2951 identifier: result
2952 identifier: result
2952 for identifier, result in results.items()
2953 for identifier, result in results.items()
2953 if is_non_jedi_result(result, identifier)
2954 if is_non_jedi_result(result, identifier)
2954 }
2955 }
2955
2956
2956 jedi_matches = (
2957 jedi_matches = (
2957 cast(_JediMatcherResult, results[jedi_matcher_id])["completions"]
2958 cast(_JediMatcherResult, results[jedi_matcher_id])["completions"]
2958 if jedi_matcher_id in results
2959 if jedi_matcher_id in results
2959 else ()
2960 else ()
2960 )
2961 )
2961
2962
2962 iter_jm = iter(jedi_matches)
2963 iter_jm = iter(jedi_matches)
2963 if _timeout:
2964 if _timeout:
2964 for jm in iter_jm:
2965 for jm in iter_jm:
2965 try:
2966 try:
2966 type_ = jm.type
2967 type_ = jm.type
2967 except Exception:
2968 except Exception:
2968 if self.debug:
2969 if self.debug:
2969 print("Error in Jedi getting type of ", jm)
2970 print("Error in Jedi getting type of ", jm)
2970 type_ = None
2971 type_ = None
2971 delta = len(jm.name_with_symbols) - len(jm.complete)
2972 delta = len(jm.name_with_symbols) - len(jm.complete)
2972 if type_ == 'function':
2973 if type_ == 'function':
2973 signature = _make_signature(jm)
2974 signature = _make_signature(jm)
2974 else:
2975 else:
2975 signature = ''
2976 signature = ''
2976 yield Completion(start=offset - delta,
2977 yield Completion(start=offset - delta,
2977 end=offset,
2978 end=offset,
2978 text=jm.name_with_symbols,
2979 text=jm.name_with_symbols,
2979 type=type_,
2980 type=type_,
2980 signature=signature,
2981 signature=signature,
2981 _origin='jedi')
2982 _origin='jedi')
2982
2983
2983 if time.monotonic() > deadline:
2984 if time.monotonic() > deadline:
2984 break
2985 break
2985
2986
2986 for jm in iter_jm:
2987 for jm in iter_jm:
2987 delta = len(jm.name_with_symbols) - len(jm.complete)
2988 delta = len(jm.name_with_symbols) - len(jm.complete)
2988 yield Completion(
2989 yield Completion(
2989 start=offset - delta,
2990 start=offset - delta,
2990 end=offset,
2991 end=offset,
2991 text=jm.name_with_symbols,
2992 text=jm.name_with_symbols,
2992 type=_UNKNOWN_TYPE, # don't compute type for speed
2993 type=_UNKNOWN_TYPE, # don't compute type for speed
2993 _origin="jedi",
2994 _origin="jedi",
2994 signature="",
2995 signature="",
2995 )
2996 )
2996
2997
2997 # TODO:
2998 # TODO:
2998 # Suppress this, right now just for debug.
2999 # Suppress this, right now just for debug.
2999 if jedi_matches and non_jedi_results and self.debug:
3000 if jedi_matches and non_jedi_results and self.debug:
3000 some_start_offset = before.rfind(
3001 some_start_offset = before.rfind(
3001 next(iter(non_jedi_results.values()))["matched_fragment"]
3002 next(iter(non_jedi_results.values()))["matched_fragment"]
3002 )
3003 )
3003 yield Completion(
3004 yield Completion(
3004 start=some_start_offset,
3005 start=some_start_offset,
3005 end=offset,
3006 end=offset,
3006 text="--jedi/ipython--",
3007 text="--jedi/ipython--",
3007 _origin="debug",
3008 _origin="debug",
3008 type="none",
3009 type="none",
3009 signature="",
3010 signature="",
3010 )
3011 )
3011
3012
3012 ordered: List[Completion] = []
3013 ordered: List[Completion] = []
3013 sortable: List[Completion] = []
3014 sortable: List[Completion] = []
3014
3015
3015 for origin, result in non_jedi_results.items():
3016 for origin, result in non_jedi_results.items():
3016 matched_text = result["matched_fragment"]
3017 matched_text = result["matched_fragment"]
3017 start_offset = before.rfind(matched_text)
3018 start_offset = before.rfind(matched_text)
3018 is_ordered = result.get("ordered", False)
3019 is_ordered = result.get("ordered", False)
3019 container = ordered if is_ordered else sortable
3020 container = ordered if is_ordered else sortable
3020
3021
3021 # I'm unsure if this is always true, so let's assert and see if it
3022 # I'm unsure if this is always true, so let's assert and see if it
3022 # crash
3023 # crash
3023 assert before.endswith(matched_text)
3024 assert before.endswith(matched_text)
3024
3025
3025 for simple_completion in result["completions"]:
3026 for simple_completion in result["completions"]:
3026 completion = Completion(
3027 completion = Completion(
3027 start=start_offset,
3028 start=start_offset,
3028 end=offset,
3029 end=offset,
3029 text=simple_completion.text,
3030 text=simple_completion.text,
3030 _origin=origin,
3031 _origin=origin,
3031 signature="",
3032 signature="",
3032 type=simple_completion.type or _UNKNOWN_TYPE,
3033 type=simple_completion.type or _UNKNOWN_TYPE,
3033 )
3034 )
3034 container.append(completion)
3035 container.append(completion)
3035
3036
3036 yield from list(self._deduplicate(ordered + self._sort(sortable)))[
3037 yield from list(self._deduplicate(ordered + self._sort(sortable)))[
3037 :MATCHES_LIMIT
3038 :MATCHES_LIMIT
3038 ]
3039 ]
3039
3040
3040 def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]:
3041 def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]:
3041 """Find completions for the given text and line context.
3042 """Find completions for the given text and line context.
3042
3043
3043 Note that both the text and the line_buffer are optional, but at least
3044 Note that both the text and the line_buffer are optional, but at least
3044 one of them must be given.
3045 one of them must be given.
3045
3046
3046 Parameters
3047 Parameters
3047 ----------
3048 ----------
3048 text : string, optional
3049 text : string, optional
3049 Text to perform the completion on. If not given, the line buffer
3050 Text to perform the completion on. If not given, the line buffer
3050 is split using the instance's CompletionSplitter object.
3051 is split using the instance's CompletionSplitter object.
3051 line_buffer : string, optional
3052 line_buffer : string, optional
3052 If not given, the completer attempts to obtain the current line
3053 If not given, the completer attempts to obtain the current line
3053 buffer via readline. This keyword allows clients which are
3054 buffer via readline. This keyword allows clients which are
3054 requesting for text completions in non-readline contexts to inform
3055 requesting for text completions in non-readline contexts to inform
3055 the completer of the entire text.
3056 the completer of the entire text.
3056 cursor_pos : int, optional
3057 cursor_pos : int, optional
3057 Index of the cursor in the full line buffer. Should be provided by
3058 Index of the cursor in the full line buffer. Should be provided by
3058 remote frontends where kernel has no access to frontend state.
3059 remote frontends where kernel has no access to frontend state.
3059
3060
3060 Returns
3061 Returns
3061 -------
3062 -------
3062 Tuple of two items:
3063 Tuple of two items:
3063 text : str
3064 text : str
3064 Text that was actually used in the completion.
3065 Text that was actually used in the completion.
3065 matches : list
3066 matches : list
3066 A list of completion matches.
3067 A list of completion matches.
3067
3068
3068 Notes
3069 Notes
3069 -----
3070 -----
3070 This API is likely to be deprecated and replaced by
3071 This API is likely to be deprecated and replaced by
3071 :any:`IPCompleter.completions` in the future.
3072 :any:`IPCompleter.completions` in the future.
3072
3073
3073 """
3074 """
3074 warnings.warn('`Completer.complete` is pending deprecation since '
3075 warnings.warn('`Completer.complete` is pending deprecation since '
3075 'IPython 6.0 and will be replaced by `Completer.completions`.',
3076 'IPython 6.0 and will be replaced by `Completer.completions`.',
3076 PendingDeprecationWarning)
3077 PendingDeprecationWarning)
3077 # potential todo, FOLD the 3rd throw away argument of _complete
3078 # potential todo, FOLD the 3rd throw away argument of _complete
3078 # into the first 2 one.
3079 # into the first 2 one.
3079 # TODO: Q: does the above refer to jedi completions (i.e. 0-indexed?)
3080 # TODO: Q: does the above refer to jedi completions (i.e. 0-indexed?)
3080 # TODO: should we deprecate now, or does it stay?
3081 # TODO: should we deprecate now, or does it stay?
3081
3082
3082 results = self._complete(
3083 results = self._complete(
3083 line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0
3084 line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0
3084 )
3085 )
3085
3086
3086 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
3087 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
3087
3088
3088 return self._arrange_and_extract(
3089 return self._arrange_and_extract(
3089 results,
3090 results,
3090 # TODO: can we confirm that excluding Jedi here was a deliberate choice in previous version?
3091 # TODO: can we confirm that excluding Jedi here was a deliberate choice in previous version?
3091 skip_matchers={jedi_matcher_id},
3092 skip_matchers={jedi_matcher_id},
3092 # this API does not support different start/end positions (fragments of token).
3093 # this API does not support different start/end positions (fragments of token).
3093 abort_if_offset_changes=True,
3094 abort_if_offset_changes=True,
3094 )
3095 )
3095
3096
3096 def _arrange_and_extract(
3097 def _arrange_and_extract(
3097 self,
3098 self,
3098 results: Dict[str, MatcherResult],
3099 results: Dict[str, MatcherResult],
3099 skip_matchers: Set[str],
3100 skip_matchers: Set[str],
3100 abort_if_offset_changes: bool,
3101 abort_if_offset_changes: bool,
3101 ):
3102 ):
3102 sortable: List[AnyMatcherCompletion] = []
3103 sortable: List[AnyMatcherCompletion] = []
3103 ordered: List[AnyMatcherCompletion] = []
3104 ordered: List[AnyMatcherCompletion] = []
3104 most_recent_fragment = None
3105 most_recent_fragment = None
3105 for identifier, result in results.items():
3106 for identifier, result in results.items():
3106 if identifier in skip_matchers:
3107 if identifier in skip_matchers:
3107 continue
3108 continue
3108 if not result["completions"]:
3109 if not result["completions"]:
3109 continue
3110 continue
3110 if not most_recent_fragment:
3111 if not most_recent_fragment:
3111 most_recent_fragment = result["matched_fragment"]
3112 most_recent_fragment = result["matched_fragment"]
3112 if (
3113 if (
3113 abort_if_offset_changes
3114 abort_if_offset_changes
3114 and result["matched_fragment"] != most_recent_fragment
3115 and result["matched_fragment"] != most_recent_fragment
3115 ):
3116 ):
3116 break
3117 break
3117 if result.get("ordered", False):
3118 if result.get("ordered", False):
3118 ordered.extend(result["completions"])
3119 ordered.extend(result["completions"])
3119 else:
3120 else:
3120 sortable.extend(result["completions"])
3121 sortable.extend(result["completions"])
3121
3122
3122 if not most_recent_fragment:
3123 if not most_recent_fragment:
3123 most_recent_fragment = "" # to satisfy typechecker (and just in case)
3124 most_recent_fragment = "" # to satisfy typechecker (and just in case)
3124
3125
3125 return most_recent_fragment, [
3126 return most_recent_fragment, [
3126 m.text for m in self._deduplicate(ordered + self._sort(sortable))
3127 m.text for m in self._deduplicate(ordered + self._sort(sortable))
3127 ]
3128 ]
3128
3129
3129 def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None,
3130 def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None,
3130 full_text=None) -> _CompleteResult:
3131 full_text=None) -> _CompleteResult:
3131 """
3132 """
3132 Like complete but can also returns raw jedi completions as well as the
3133 Like complete but can also returns raw jedi completions as well as the
3133 origin of the completion text. This could (and should) be made much
3134 origin of the completion text. This could (and should) be made much
3134 cleaner but that will be simpler once we drop the old (and stateful)
3135 cleaner but that will be simpler once we drop the old (and stateful)
3135 :any:`complete` API.
3136 :any:`complete` API.
3136
3137
3137 With current provisional API, cursor_pos act both (depending on the
3138 With current provisional API, cursor_pos act both (depending on the
3138 caller) as the offset in the ``text`` or ``line_buffer``, or as the
3139 caller) as the offset in the ``text`` or ``line_buffer``, or as the
3139 ``column`` when passing multiline strings this could/should be renamed
3140 ``column`` when passing multiline strings this could/should be renamed
3140 but would add extra noise.
3141 but would add extra noise.
3141
3142
3142 Parameters
3143 Parameters
3143 ----------
3144 ----------
3144 cursor_line
3145 cursor_line
3145 Index of the line the cursor is on. 0 indexed.
3146 Index of the line the cursor is on. 0 indexed.
3146 cursor_pos
3147 cursor_pos
3147 Position of the cursor in the current line/line_buffer/text. 0
3148 Position of the cursor in the current line/line_buffer/text. 0
3148 indexed.
3149 indexed.
3149 line_buffer : optional, str
3150 line_buffer : optional, str
3150 The current line the cursor is in, this is mostly due to legacy
3151 The current line the cursor is in, this is mostly due to legacy
3151 reason that readline could only give a us the single current line.
3152 reason that readline could only give a us the single current line.
3152 Prefer `full_text`.
3153 Prefer `full_text`.
3153 text : str
3154 text : str
3154 The current "token" the cursor is in, mostly also for historical
3155 The current "token" the cursor is in, mostly also for historical
3155 reasons. as the completer would trigger only after the current line
3156 reasons. as the completer would trigger only after the current line
3156 was parsed.
3157 was parsed.
3157 full_text : str
3158 full_text : str
3158 Full text of the current cell.
3159 Full text of the current cell.
3159
3160
3160 Returns
3161 Returns
3161 -------
3162 -------
3162 An ordered dictionary where keys are identifiers of completion
3163 An ordered dictionary where keys are identifiers of completion
3163 matchers and values are ``MatcherResult``s.
3164 matchers and values are ``MatcherResult``s.
3164 """
3165 """
3165
3166
3166 # if the cursor position isn't given, the only sane assumption we can
3167 # if the cursor position isn't given, the only sane assumption we can
3167 # make is that it's at the end of the line (the common case)
3168 # make is that it's at the end of the line (the common case)
3168 if cursor_pos is None:
3169 if cursor_pos is None:
3169 cursor_pos = len(line_buffer) if text is None else len(text)
3170 cursor_pos = len(line_buffer) if text is None else len(text)
3170
3171
3171 if self.use_main_ns:
3172 if self.use_main_ns:
3172 self.namespace = __main__.__dict__
3173 self.namespace = __main__.__dict__
3173
3174
3174 # if text is either None or an empty string, rely on the line buffer
3175 # if text is either None or an empty string, rely on the line buffer
3175 if (not line_buffer) and full_text:
3176 if (not line_buffer) and full_text:
3176 line_buffer = full_text.split('\n')[cursor_line]
3177 line_buffer = full_text.split('\n')[cursor_line]
3177 if not text: # issue #11508: check line_buffer before calling split_line
3178 if not text: # issue #11508: check line_buffer before calling split_line
3178 text = (
3179 text = (
3179 self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else ""
3180 self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else ""
3180 )
3181 )
3181
3182
3182 # If no line buffer is given, assume the input text is all there was
3183 # If no line buffer is given, assume the input text is all there was
3183 if line_buffer is None:
3184 if line_buffer is None:
3184 line_buffer = text
3185 line_buffer = text
3185
3186
3186 # deprecated - do not use `line_buffer` in new code.
3187 # deprecated - do not use `line_buffer` in new code.
3187 self.line_buffer = line_buffer
3188 self.line_buffer = line_buffer
3188 self.text_until_cursor = self.line_buffer[:cursor_pos]
3189 self.text_until_cursor = self.line_buffer[:cursor_pos]
3189
3190
3190 if not full_text:
3191 if not full_text:
3191 full_text = line_buffer
3192 full_text = line_buffer
3192
3193
3193 context = CompletionContext(
3194 context = CompletionContext(
3194 full_text=full_text,
3195 full_text=full_text,
3195 cursor_position=cursor_pos,
3196 cursor_position=cursor_pos,
3196 cursor_line=cursor_line,
3197 cursor_line=cursor_line,
3197 token=text,
3198 token=text,
3198 limit=MATCHES_LIMIT,
3199 limit=MATCHES_LIMIT,
3199 )
3200 )
3200
3201
3201 # Start with a clean slate of completions
3202 # Start with a clean slate of completions
3202 results: Dict[str, MatcherResult] = {}
3203 results: Dict[str, MatcherResult] = {}
3203
3204
3204 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
3205 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
3205
3206
3206 suppressed_matchers: Set[str] = set()
3207 suppressed_matchers: Set[str] = set()
3207
3208
3208 matchers = {
3209 matchers = {
3209 _get_matcher_id(matcher): matcher
3210 _get_matcher_id(matcher): matcher
3210 for matcher in sorted(
3211 for matcher in sorted(
3211 self.matchers, key=_get_matcher_priority, reverse=True
3212 self.matchers, key=_get_matcher_priority, reverse=True
3212 )
3213 )
3213 }
3214 }
3214
3215
3215 for matcher_id, matcher in matchers.items():
3216 for matcher_id, matcher in matchers.items():
3216 matcher_id = _get_matcher_id(matcher)
3217 matcher_id = _get_matcher_id(matcher)
3217
3218
3218 if matcher_id in self.disable_matchers:
3219 if matcher_id in self.disable_matchers:
3219 continue
3220 continue
3220
3221
3221 if matcher_id in results:
3222 if matcher_id in results:
3222 warnings.warn(f"Duplicate matcher ID: {matcher_id}.")
3223 warnings.warn(f"Duplicate matcher ID: {matcher_id}.")
3223
3224
3224 if matcher_id in suppressed_matchers:
3225 if matcher_id in suppressed_matchers:
3225 continue
3226 continue
3226
3227
3227 result: MatcherResult
3228 result: MatcherResult
3228 try:
3229 try:
3229 if _is_matcher_v1(matcher):
3230 if _is_matcher_v1(matcher):
3230 result = _convert_matcher_v1_result_to_v2(
3231 result = _convert_matcher_v1_result_to_v2(
3231 matcher(text), type=_UNKNOWN_TYPE
3232 matcher(text), type=_UNKNOWN_TYPE
3232 )
3233 )
3233 elif _is_matcher_v2(matcher):
3234 elif _is_matcher_v2(matcher):
3234 result = matcher(context)
3235 result = matcher(context)
3235 else:
3236 else:
3236 api_version = _get_matcher_api_version(matcher)
3237 api_version = _get_matcher_api_version(matcher)
3237 raise ValueError(f"Unsupported API version {api_version}")
3238 raise ValueError(f"Unsupported API version {api_version}")
3238 except BaseException:
3239 except BaseException:
3239 # Show the ugly traceback if the matcher causes an
3240 # Show the ugly traceback if the matcher causes an
3240 # exception, but do NOT crash the kernel!
3241 # exception, but do NOT crash the kernel!
3241 sys.excepthook(*sys.exc_info())
3242 sys.excepthook(*sys.exc_info())
3242 continue
3243 continue
3243
3244
3244 # set default value for matched fragment if suffix was not selected.
3245 # set default value for matched fragment if suffix was not selected.
3245 result["matched_fragment"] = result.get("matched_fragment", context.token)
3246 result["matched_fragment"] = result.get("matched_fragment", context.token)
3246
3247
3247 if not suppressed_matchers:
3248 if not suppressed_matchers:
3248 suppression_recommended: Union[bool, Set[str]] = result.get(
3249 suppression_recommended: Union[bool, Set[str]] = result.get(
3249 "suppress", False
3250 "suppress", False
3250 )
3251 )
3251
3252
3252 suppression_config = (
3253 suppression_config = (
3253 self.suppress_competing_matchers.get(matcher_id, None)
3254 self.suppress_competing_matchers.get(matcher_id, None)
3254 if isinstance(self.suppress_competing_matchers, dict)
3255 if isinstance(self.suppress_competing_matchers, dict)
3255 else self.suppress_competing_matchers
3256 else self.suppress_competing_matchers
3256 )
3257 )
3257 should_suppress = (
3258 should_suppress = (
3258 (suppression_config is True)
3259 (suppression_config is True)
3259 or (suppression_recommended and (suppression_config is not False))
3260 or (suppression_recommended and (suppression_config is not False))
3260 ) and has_any_completions(result)
3261 ) and has_any_completions(result)
3261
3262
3262 if should_suppress:
3263 if should_suppress:
3263 suppression_exceptions: Set[str] = result.get(
3264 suppression_exceptions: Set[str] = result.get(
3264 "do_not_suppress", set()
3265 "do_not_suppress", set()
3265 )
3266 )
3266 if isinstance(suppression_recommended, Iterable):
3267 if isinstance(suppression_recommended, Iterable):
3267 to_suppress = set(suppression_recommended)
3268 to_suppress = set(suppression_recommended)
3268 else:
3269 else:
3269 to_suppress = set(matchers)
3270 to_suppress = set(matchers)
3270 suppressed_matchers = to_suppress - suppression_exceptions
3271 suppressed_matchers = to_suppress - suppression_exceptions
3271
3272
3272 new_results = {}
3273 new_results = {}
3273 for previous_matcher_id, previous_result in results.items():
3274 for previous_matcher_id, previous_result in results.items():
3274 if previous_matcher_id not in suppressed_matchers:
3275 if previous_matcher_id not in suppressed_matchers:
3275 new_results[previous_matcher_id] = previous_result
3276 new_results[previous_matcher_id] = previous_result
3276 results = new_results
3277 results = new_results
3277
3278
3278 results[matcher_id] = result
3279 results[matcher_id] = result
3279
3280
3280 _, matches = self._arrange_and_extract(
3281 _, matches = self._arrange_and_extract(
3281 results,
3282 results,
3282 # TODO Jedi completions non included in legacy stateful API; was this deliberate or omission?
3283 # TODO Jedi completions non included in legacy stateful API; was this deliberate or omission?
3283 # if it was omission, we can remove the filtering step, otherwise remove this comment.
3284 # if it was omission, we can remove the filtering step, otherwise remove this comment.
3284 skip_matchers={jedi_matcher_id},
3285 skip_matchers={jedi_matcher_id},
3285 abort_if_offset_changes=False,
3286 abort_if_offset_changes=False,
3286 )
3287 )
3287
3288
3288 # populate legacy stateful API
3289 # populate legacy stateful API
3289 self.matches = matches
3290 self.matches = matches
3290
3291
3291 return results
3292 return results
3292
3293
3293 @staticmethod
3294 @staticmethod
3294 def _deduplicate(
3295 def _deduplicate(
3295 matches: Sequence[AnyCompletion],
3296 matches: Sequence[AnyCompletion],
3296 ) -> Iterable[AnyCompletion]:
3297 ) -> Iterable[AnyCompletion]:
3297 filtered_matches: Dict[str, AnyCompletion] = {}
3298 filtered_matches: Dict[str, AnyCompletion] = {}
3298 for match in matches:
3299 for match in matches:
3299 text = match.text
3300 text = match.text
3300 if (
3301 if (
3301 text not in filtered_matches
3302 text not in filtered_matches
3302 or filtered_matches[text].type == _UNKNOWN_TYPE
3303 or filtered_matches[text].type == _UNKNOWN_TYPE
3303 ):
3304 ):
3304 filtered_matches[text] = match
3305 filtered_matches[text] = match
3305
3306
3306 return filtered_matches.values()
3307 return filtered_matches.values()
3307
3308
3308 @staticmethod
3309 @staticmethod
3309 def _sort(matches: Sequence[AnyCompletion]):
3310 def _sort(matches: Sequence[AnyCompletion]):
3310 return sorted(matches, key=lambda x: completions_sorting_key(x.text))
3311 return sorted(matches, key=lambda x: completions_sorting_key(x.text))
3311
3312
3312 @context_matcher()
3313 @context_matcher()
3313 def fwd_unicode_matcher(self, context: CompletionContext):
3314 def fwd_unicode_matcher(self, context: CompletionContext):
3314 """Same as :any:`fwd_unicode_match`, but adopted to new Matcher API."""
3315 """Same as :any:`fwd_unicode_match`, but adopted to new Matcher API."""
3315 # TODO: use `context.limit` to terminate early once we matched the maximum
3316 # TODO: use `context.limit` to terminate early once we matched the maximum
3316 # number that will be used downstream; can be added as an optional to
3317 # number that will be used downstream; can be added as an optional to
3317 # `fwd_unicode_match(text: str, limit: int = None)` or we could re-implement here.
3318 # `fwd_unicode_match(text: str, limit: int = None)` or we could re-implement here.
3318 fragment, matches = self.fwd_unicode_match(context.text_until_cursor)
3319 fragment, matches = self.fwd_unicode_match(context.text_until_cursor)
3319 return _convert_matcher_v1_result_to_v2(
3320 return _convert_matcher_v1_result_to_v2(
3320 matches, type="unicode", fragment=fragment, suppress_if_matches=True
3321 matches, type="unicode", fragment=fragment, suppress_if_matches=True
3321 )
3322 )
3322
3323
3323 def fwd_unicode_match(self, text: str) -> Tuple[str, Sequence[str]]:
3324 def fwd_unicode_match(self, text: str) -> Tuple[str, Sequence[str]]:
3324 """
3325 """
3325 Forward match a string starting with a backslash with a list of
3326 Forward match a string starting with a backslash with a list of
3326 potential Unicode completions.
3327 potential Unicode completions.
3327
3328
3328 Will compute list of Unicode character names on first call and cache it.
3329 Will compute list of Unicode character names on first call and cache it.
3329
3330
3330 .. deprecated:: 8.6
3331 .. deprecated:: 8.6
3331 You can use :meth:`fwd_unicode_matcher` instead.
3332 You can use :meth:`fwd_unicode_matcher` instead.
3332
3333
3333 Returns
3334 Returns
3334 -------
3335 -------
3335 At tuple with:
3336 At tuple with:
3336 - matched text (empty if no matches)
3337 - matched text (empty if no matches)
3337 - list of potential completions, empty tuple otherwise)
3338 - list of potential completions, empty tuple otherwise)
3338 """
3339 """
3339 # TODO: self.unicode_names is here a list we traverse each time with ~100k elements.
3340 # TODO: self.unicode_names is here a list we traverse each time with ~100k elements.
3340 # We could do a faster match using a Trie.
3341 # We could do a faster match using a Trie.
3341
3342
3342 # Using pygtrie the following seem to work:
3343 # Using pygtrie the following seem to work:
3343
3344
3344 # s = PrefixSet()
3345 # s = PrefixSet()
3345
3346
3346 # for c in range(0,0x10FFFF + 1):
3347 # for c in range(0,0x10FFFF + 1):
3347 # try:
3348 # try:
3348 # s.add(unicodedata.name(chr(c)))
3349 # s.add(unicodedata.name(chr(c)))
3349 # except ValueError:
3350 # except ValueError:
3350 # pass
3351 # pass
3351 # [''.join(k) for k in s.iter(prefix)]
3352 # [''.join(k) for k in s.iter(prefix)]
3352
3353
3353 # But need to be timed and adds an extra dependency.
3354 # But need to be timed and adds an extra dependency.
3354
3355
3355 slashpos = text.rfind('\\')
3356 slashpos = text.rfind('\\')
3356 # if text starts with slash
3357 # if text starts with slash
3357 if slashpos > -1:
3358 if slashpos > -1:
3358 # PERF: It's important that we don't access self._unicode_names
3359 # PERF: It's important that we don't access self._unicode_names
3359 # until we're inside this if-block. _unicode_names is lazily
3360 # until we're inside this if-block. _unicode_names is lazily
3360 # initialized, and it takes a user-noticeable amount of time to
3361 # initialized, and it takes a user-noticeable amount of time to
3361 # initialize it, so we don't want to initialize it unless we're
3362 # initialize it, so we don't want to initialize it unless we're
3362 # actually going to use it.
3363 # actually going to use it.
3363 s = text[slashpos + 1 :]
3364 s = text[slashpos + 1 :]
3364 sup = s.upper()
3365 sup = s.upper()
3365 candidates = [x for x in self.unicode_names if x.startswith(sup)]
3366 candidates = [x for x in self.unicode_names if x.startswith(sup)]
3366 if candidates:
3367 if candidates:
3367 return s, candidates
3368 return s, candidates
3368 candidates = [x for x in self.unicode_names if sup in x]
3369 candidates = [x for x in self.unicode_names if sup in x]
3369 if candidates:
3370 if candidates:
3370 return s, candidates
3371 return s, candidates
3371 splitsup = sup.split(" ")
3372 splitsup = sup.split(" ")
3372 candidates = [
3373 candidates = [
3373 x for x in self.unicode_names if all(u in x for u in splitsup)
3374 x for x in self.unicode_names if all(u in x for u in splitsup)
3374 ]
3375 ]
3375 if candidates:
3376 if candidates:
3376 return s, candidates
3377 return s, candidates
3377
3378
3378 return "", ()
3379 return "", ()
3379
3380
3380 # if text does not start with slash
3381 # if text does not start with slash
3381 else:
3382 else:
3382 return '', ()
3383 return '', ()
3383
3384
3384 @property
3385 @property
3385 def unicode_names(self) -> List[str]:
3386 def unicode_names(self) -> List[str]:
3386 """List of names of unicode code points that can be completed.
3387 """List of names of unicode code points that can be completed.
3387
3388
3388 The list is lazily initialized on first access.
3389 The list is lazily initialized on first access.
3389 """
3390 """
3390 if self._unicode_names is None:
3391 if self._unicode_names is None:
3391 names = []
3392 names = []
3392 for c in range(0,0x10FFFF + 1):
3393 for c in range(0,0x10FFFF + 1):
3393 try:
3394 try:
3394 names.append(unicodedata.name(chr(c)))
3395 names.append(unicodedata.name(chr(c)))
3395 except ValueError:
3396 except ValueError:
3396 pass
3397 pass
3397 self._unicode_names = _unicode_name_compute(_UNICODE_RANGES)
3398 self._unicode_names = _unicode_name_compute(_UNICODE_RANGES)
3398
3399
3399 return self._unicode_names
3400 return self._unicode_names
3400
3401
3401 def _unicode_name_compute(ranges:List[Tuple[int,int]]) -> List[str]:
3402 def _unicode_name_compute(ranges:List[Tuple[int,int]]) -> List[str]:
3402 names = []
3403 names = []
3403 for start,stop in ranges:
3404 for start,stop in ranges:
3404 for c in range(start, stop) :
3405 for c in range(start, stop) :
3405 try:
3406 try:
3406 names.append(unicodedata.name(chr(c)))
3407 names.append(unicodedata.name(chr(c)))
3407 except ValueError:
3408 except ValueError:
3408 pass
3409 pass
3409 return names
3410 return names
@@ -1,1131 +1,1136
1 """
1 """
2 Pdb debugger class.
2 Pdb debugger class.
3
3
4
4
5 This is an extension to PDB which adds a number of new features.
5 This is an extension to PDB which adds a number of new features.
6 Note that there is also the `IPython.terminal.debugger` class which provides UI
6 Note that there is also the `IPython.terminal.debugger` class which provides UI
7 improvements.
7 improvements.
8
8
9 We also strongly recommend to use this via the `ipdb` package, which provides
9 We also strongly recommend to use this via the `ipdb` package, which provides
10 extra configuration options.
10 extra configuration options.
11
11
12 Among other things, this subclass of PDB:
12 Among other things, this subclass of PDB:
13 - supports many IPython magics like pdef/psource
13 - supports many IPython magics like pdef/psource
14 - hide frames in tracebacks based on `__tracebackhide__`
14 - hide frames in tracebacks based on `__tracebackhide__`
15 - allows to skip frames based on `__debuggerskip__`
15 - allows to skip frames based on `__debuggerskip__`
16
16
17
17
18 Global Configuration
18 Global Configuration
19 --------------------
19 --------------------
20
20
21 The IPython debugger will by read the global ``~/.pdbrc`` file.
21 The IPython debugger will by read the global ``~/.pdbrc`` file.
22 That is to say you can list all commands supported by ipdb in your `~/.pdbrc`
22 That is to say you can list all commands supported by ipdb in your `~/.pdbrc`
23 configuration file, to globally configure pdb.
23 configuration file, to globally configure pdb.
24
24
25 Example::
25 Example::
26
26
27 # ~/.pdbrc
27 # ~/.pdbrc
28 skip_predicates debuggerskip false
28 skip_predicates debuggerskip false
29 skip_hidden false
29 skip_hidden false
30 context 25
30 context 25
31
31
32 Features
32 Features
33 --------
33 --------
34
34
35 The IPython debugger can hide and skip frames when printing or moving through
35 The IPython debugger can hide and skip frames when printing or moving through
36 the stack. This can have a performance impact, so can be configures.
36 the stack. This can have a performance impact, so can be configures.
37
37
38 The skipping and hiding frames are configurable via the `skip_predicates`
38 The skipping and hiding frames are configurable via the `skip_predicates`
39 command.
39 command.
40
40
41 By default, frames from readonly files will be hidden, frames containing
41 By default, frames from readonly files will be hidden, frames containing
42 ``__tracebackhide__ = True`` will be hidden.
42 ``__tracebackhide__ = True`` will be hidden.
43
43
44 Frames containing ``__debuggerskip__`` will be stepped over, frames whose parent
44 Frames containing ``__debuggerskip__`` will be stepped over, frames whose parent
45 frames value of ``__debuggerskip__`` is ``True`` will also be skipped.
45 frames value of ``__debuggerskip__`` is ``True`` will also be skipped.
46
46
47 >>> def helpers_helper():
47 >>> def helpers_helper():
48 ... pass
48 ... pass
49 ...
49 ...
50 ... def helper_1():
50 ... def helper_1():
51 ... print("don't step in me")
51 ... print("don't step in me")
52 ... helpers_helpers() # will be stepped over unless breakpoint set.
52 ... helpers_helpers() # will be stepped over unless breakpoint set.
53 ...
53 ...
54 ...
54 ...
55 ... def helper_2():
55 ... def helper_2():
56 ... print("in me neither")
56 ... print("in me neither")
57 ...
57 ...
58
58
59 One can define a decorator that wraps a function between the two helpers:
59 One can define a decorator that wraps a function between the two helpers:
60
60
61 >>> def pdb_skipped_decorator(function):
61 >>> def pdb_skipped_decorator(function):
62 ...
62 ...
63 ...
63 ...
64 ... def wrapped_fn(*args, **kwargs):
64 ... def wrapped_fn(*args, **kwargs):
65 ... __debuggerskip__ = True
65 ... __debuggerskip__ = True
66 ... helper_1()
66 ... helper_1()
67 ... __debuggerskip__ = False
67 ... __debuggerskip__ = False
68 ... result = function(*args, **kwargs)
68 ... result = function(*args, **kwargs)
69 ... __debuggerskip__ = True
69 ... __debuggerskip__ = True
70 ... helper_2()
70 ... helper_2()
71 ... # setting __debuggerskip__ to False again is not necessary
71 ... # setting __debuggerskip__ to False again is not necessary
72 ... return result
72 ... return result
73 ...
73 ...
74 ... return wrapped_fn
74 ... return wrapped_fn
75
75
76 When decorating a function, ipdb will directly step into ``bar()`` by
76 When decorating a function, ipdb will directly step into ``bar()`` by
77 default:
77 default:
78
78
79 >>> @foo_decorator
79 >>> @foo_decorator
80 ... def bar(x, y):
80 ... def bar(x, y):
81 ... return x * y
81 ... return x * y
82
82
83
83
84 You can toggle the behavior with
84 You can toggle the behavior with
85
85
86 ipdb> skip_predicates debuggerskip false
86 ipdb> skip_predicates debuggerskip false
87
87
88 or configure it in your ``.pdbrc``
88 or configure it in your ``.pdbrc``
89
89
90
90
91
91
92 License
92 License
93 -------
93 -------
94
94
95 Modified from the standard pdb.Pdb class to avoid including readline, so that
95 Modified from the standard pdb.Pdb class to avoid including readline, so that
96 the command line completion of other programs which include this isn't
96 the command line completion of other programs which include this isn't
97 damaged.
97 damaged.
98
98
99 In the future, this class will be expanded with improvements over the standard
99 In the future, this class will be expanded with improvements over the standard
100 pdb.
100 pdb.
101
101
102 The original code in this file is mainly lifted out of cmd.py in Python 2.2,
102 The original code in this file is mainly lifted out of cmd.py in Python 2.2,
103 with minor changes. Licensing should therefore be under the standard Python
103 with minor changes. Licensing should therefore be under the standard Python
104 terms. For details on the PSF (Python Software Foundation) standard license,
104 terms. For details on the PSF (Python Software Foundation) standard license,
105 see:
105 see:
106
106
107 https://docs.python.org/2/license.html
107 https://docs.python.org/2/license.html
108
108
109
109
110 All the changes since then are under the same license as IPython.
110 All the changes since then are under the same license as IPython.
111
111
112 """
112 """
113
113
114 #*****************************************************************************
114 #*****************************************************************************
115 #
115 #
116 # This file is licensed under the PSF license.
116 # This file is licensed under the PSF license.
117 #
117 #
118 # Copyright (C) 2001 Python Software Foundation, www.python.org
118 # Copyright (C) 2001 Python Software Foundation, www.python.org
119 # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
119 # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
120 #
120 #
121 #
121 #
122 #*****************************************************************************
122 #*****************************************************************************
123
123
124 from __future__ import annotations
124 from __future__ import annotations
125
125
126 import inspect
126 import inspect
127 import linecache
127 import linecache
128 import os
128 import os
129 import re
129 import re
130 import sys
130 import sys
131 from contextlib import contextmanager
131 from contextlib import contextmanager
132 from functools import lru_cache
132 from functools import lru_cache
133
133
134 from IPython import get_ipython
134 from IPython import get_ipython
135 from IPython.core.excolors import exception_colors
135 from IPython.core.excolors import exception_colors
136 from IPython.utils import PyColorize, py3compat
136 from IPython.utils import PyColorize, coloransi, py3compat
137
137
138 from typing import TYPE_CHECKING
138 from typing import TYPE_CHECKING
139
139
140 if TYPE_CHECKING:
140 if TYPE_CHECKING:
141 # otherwise circular import
141 # otherwise circular import
142 from IPython.core.interactiveshell import InteractiveShell
142 from IPython.core.interactiveshell import InteractiveShell
143
143
144 # skip module docstests
144 # skip module docstests
145 __skip_doctest__ = True
145 __skip_doctest__ = True
146
146
147 prompt = 'ipdb> '
147 prompt = 'ipdb> '
148
148
149 # We have to check this directly from sys.argv, config struct not yet available
149 # We have to check this directly from sys.argv, config struct not yet available
150 from pdb import Pdb as OldPdb
150 from pdb import Pdb as OldPdb
151
151
152 # Allow the set_trace code to operate outside of an ipython instance, even if
152 # Allow the set_trace code to operate outside of an ipython instance, even if
153 # it does so with some limitations. The rest of this support is implemented in
153 # it does so with some limitations. The rest of this support is implemented in
154 # the Tracer constructor.
154 # the Tracer constructor.
155
155
156 DEBUGGERSKIP = "__debuggerskip__"
156 DEBUGGERSKIP = "__debuggerskip__"
157
157
158
158
159 # this has been implemented in Pdb in Python 3.13 (https://github.com/python/cpython/pull/106676
159 # this has been implemented in Pdb in Python 3.13 (https://github.com/python/cpython/pull/106676
160 # on lower python versions, we backported the feature.
160 # on lower python versions, we backported the feature.
161 CHAIN_EXCEPTIONS = sys.version_info < (3, 13)
161 CHAIN_EXCEPTIONS = sys.version_info < (3, 13)
162
162
163
163
164 def make_arrow(pad):
164 def make_arrow(pad):
165 """generate the leading arrow in front of traceback or debugger"""
165 """generate the leading arrow in front of traceback or debugger"""
166 if pad >= 2:
166 if pad >= 2:
167 return '-'*(pad-2) + '> '
167 return '-'*(pad-2) + '> '
168 elif pad == 1:
168 elif pad == 1:
169 return '>'
169 return '>'
170 return ''
170 return ''
171
171
172
172
173 def BdbQuit_excepthook(et, ev, tb, excepthook=None):
173 def BdbQuit_excepthook(et, ev, tb, excepthook=None):
174 """Exception hook which handles `BdbQuit` exceptions.
174 """Exception hook which handles `BdbQuit` exceptions.
175
175
176 All other exceptions are processed using the `excepthook`
176 All other exceptions are processed using the `excepthook`
177 parameter.
177 parameter.
178 """
178 """
179 raise ValueError(
179 raise ValueError(
180 "`BdbQuit_excepthook` is deprecated since version 5.1. It is still around only because it is still imported by ipdb.",
180 "`BdbQuit_excepthook` is deprecated since version 5.1. It is still around only because it is still imported by ipdb.",
181 )
181 )
182
182
183
183
184 RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+')
184 RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+')
185
185
186
186
187 def strip_indentation(multiline_string):
187 def strip_indentation(multiline_string):
188 return RGX_EXTRA_INDENT.sub('', multiline_string)
188 return RGX_EXTRA_INDENT.sub('', multiline_string)
189
189
190
190
191 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
191 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
192 """Make new_fn have old_fn's doc string. This is particularly useful
192 """Make new_fn have old_fn's doc string. This is particularly useful
193 for the ``do_...`` commands that hook into the help system.
193 for the ``do_...`` commands that hook into the help system.
194 Adapted from from a comp.lang.python posting
194 Adapted from from a comp.lang.python posting
195 by Duncan Booth."""
195 by Duncan Booth."""
196 def wrapper(*args, **kw):
196 def wrapper(*args, **kw):
197 return new_fn(*args, **kw)
197 return new_fn(*args, **kw)
198 if old_fn.__doc__:
198 if old_fn.__doc__:
199 wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text
199 wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text
200 return wrapper
200 return wrapper
201
201
202
202
203 class Pdb(OldPdb):
203 class Pdb(OldPdb):
204 """Modified Pdb class, does not load readline.
204 """Modified Pdb class, does not load readline.
205
205
206 for a standalone version that uses prompt_toolkit, see
206 for a standalone version that uses prompt_toolkit, see
207 `IPython.terminal.debugger.TerminalPdb` and
207 `IPython.terminal.debugger.TerminalPdb` and
208 `IPython.terminal.debugger.set_trace()`
208 `IPython.terminal.debugger.set_trace()`
209
209
210
210
211 This debugger can hide and skip frames that are tagged according to some predicates.
211 This debugger can hide and skip frames that are tagged according to some predicates.
212 See the `skip_predicates` commands.
212 See the `skip_predicates` commands.
213
213
214 """
214 """
215
215
216 shell: InteractiveShell
216 shell: InteractiveShell
217
217
218 if CHAIN_EXCEPTIONS:
218 if CHAIN_EXCEPTIONS:
219 MAX_CHAINED_EXCEPTION_DEPTH = 999
219 MAX_CHAINED_EXCEPTION_DEPTH = 999
220
220
221 default_predicates = {
221 default_predicates = {
222 "tbhide": True,
222 "tbhide": True,
223 "readonly": False,
223 "readonly": False,
224 "ipython_internal": True,
224 "ipython_internal": True,
225 "debuggerskip": True,
225 "debuggerskip": True,
226 }
226 }
227
227
228 def __init__(self, completekey=None, stdin=None, stdout=None, context=5, **kwargs):
228 def __init__(self, completekey=None, stdin=None, stdout=None, context=5, **kwargs):
229 """Create a new IPython debugger.
229 """Create a new IPython debugger.
230
230
231 Parameters
231 Parameters
232 ----------
232 ----------
233 completekey : default None
233 completekey : default None
234 Passed to pdb.Pdb.
234 Passed to pdb.Pdb.
235 stdin : default None
235 stdin : default None
236 Passed to pdb.Pdb.
236 Passed to pdb.Pdb.
237 stdout : default None
237 stdout : default None
238 Passed to pdb.Pdb.
238 Passed to pdb.Pdb.
239 context : int
239 context : int
240 Number of lines of source code context to show when
240 Number of lines of source code context to show when
241 displaying stacktrace information.
241 displaying stacktrace information.
242 **kwargs
242 **kwargs
243 Passed to pdb.Pdb.
243 Passed to pdb.Pdb.
244
244
245 Notes
245 Notes
246 -----
246 -----
247 The possibilities are python version dependent, see the python
247 The possibilities are python version dependent, see the python
248 docs for more info.
248 docs for more info.
249 """
249 """
250
250
251 # Parent constructor:
251 # Parent constructor:
252 try:
252 try:
253 self.context = int(context)
253 self.context = int(context)
254 if self.context <= 0:
254 if self.context <= 0:
255 raise ValueError("Context must be a positive integer")
255 raise ValueError("Context must be a positive integer")
256 except (TypeError, ValueError) as e:
256 except (TypeError, ValueError) as e:
257 raise ValueError("Context must be a positive integer") from e
257 raise ValueError("Context must be a positive integer") from e
258
258
259 # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`.
259 # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`.
260 OldPdb.__init__(self, completekey, stdin, stdout, **kwargs)
260 OldPdb.__init__(self, completekey, stdin, stdout, **kwargs)
261
261
262 # IPython changes...
262 # IPython changes...
263 self.shell = get_ipython()
263 self.shell = get_ipython()
264
264
265 if self.shell is None:
265 if self.shell is None:
266 save_main = sys.modules['__main__']
266 save_main = sys.modules['__main__']
267 # No IPython instance running, we must create one
267 # No IPython instance running, we must create one
268 from IPython.terminal.interactiveshell import \
268 from IPython.terminal.interactiveshell import \
269 TerminalInteractiveShell
269 TerminalInteractiveShell
270 self.shell = TerminalInteractiveShell.instance()
270 self.shell = TerminalInteractiveShell.instance()
271 # needed by any code which calls __import__("__main__") after
271 # needed by any code which calls __import__("__main__") after
272 # the debugger was entered. See also #9941.
272 # the debugger was entered. See also #9941.
273 sys.modules["__main__"] = save_main
273 sys.modules["__main__"] = save_main
274
274
275
275
276 color_scheme = self.shell.colors
276 color_scheme = self.shell.colors
277
277
278 self.aliases = {}
278 self.aliases = {}
279
279
280 # Create color table: we copy the default one from the traceback
280 # Create color table: we copy the default one from the traceback
281 # module and add a few attributes needed for debugging
281 # module and add a few attributes needed for debugging
282 self.color_scheme_table = exception_colors()
282 self.color_scheme_table = exception_colors()
283
283
284 # shorthands
285 C = coloransi.TermColors
286 cst = self.color_scheme_table
287
288
284 # Add a python parser so we can syntax highlight source while
289 # Add a python parser so we can syntax highlight source while
285 # debugging.
290 # debugging.
286 self.parser = PyColorize.Parser(style=color_scheme)
291 self.parser = PyColorize.Parser(style=color_scheme)
287 self.set_colors(color_scheme)
292 self.set_colors(color_scheme)
288
293
289 # Set the prompt - the default prompt is '(Pdb)'
294 # Set the prompt - the default prompt is '(Pdb)'
290 self.prompt = prompt
295 self.prompt = prompt
291 self.skip_hidden = True
296 self.skip_hidden = True
292 self.report_skipped = True
297 self.report_skipped = True
293
298
294 # list of predicates we use to skip frames
299 # list of predicates we use to skip frames
295 self._predicates = self.default_predicates
300 self._predicates = self.default_predicates
296
301
297 if CHAIN_EXCEPTIONS:
302 if CHAIN_EXCEPTIONS:
298 self._chained_exceptions = tuple()
303 self._chained_exceptions = tuple()
299 self._chained_exception_index = 0
304 self._chained_exception_index = 0
300
305
301 #
306 #
302 def set_colors(self, scheme):
307 def set_colors(self, scheme):
303 """Shorthand access to the color table scheme selector method."""
308 """Shorthand access to the color table scheme selector method."""
304 self.color_scheme_table.set_active_scheme(scheme)
309 self.color_scheme_table.set_active_scheme(scheme)
305 self.parser.style = scheme
310 self.parser.style = scheme
306
311
307 def set_trace(self, frame=None):
312 def set_trace(self, frame=None):
308 if frame is None:
313 if frame is None:
309 frame = sys._getframe().f_back
314 frame = sys._getframe().f_back
310 self.initial_frame = frame
315 self.initial_frame = frame
311 return super().set_trace(frame)
316 return super().set_trace(frame)
312
317
313 def _hidden_predicate(self, frame):
318 def _hidden_predicate(self, frame):
314 """
319 """
315 Given a frame return whether it it should be hidden or not by IPython.
320 Given a frame return whether it it should be hidden or not by IPython.
316 """
321 """
317
322
318 if self._predicates["readonly"]:
323 if self._predicates["readonly"]:
319 fname = frame.f_code.co_filename
324 fname = frame.f_code.co_filename
320 # we need to check for file existence and interactively define
325 # we need to check for file existence and interactively define
321 # function would otherwise appear as RO.
326 # function would otherwise appear as RO.
322 if os.path.isfile(fname) and not os.access(fname, os.W_OK):
327 if os.path.isfile(fname) and not os.access(fname, os.W_OK):
323 return True
328 return True
324
329
325 if self._predicates["tbhide"]:
330 if self._predicates["tbhide"]:
326 if frame in (self.curframe, getattr(self, "initial_frame", None)):
331 if frame in (self.curframe, getattr(self, "initial_frame", None)):
327 return False
332 return False
328 frame_locals = self._get_frame_locals(frame)
333 frame_locals = self._get_frame_locals(frame)
329 if "__tracebackhide__" not in frame_locals:
334 if "__tracebackhide__" not in frame_locals:
330 return False
335 return False
331 return frame_locals["__tracebackhide__"]
336 return frame_locals["__tracebackhide__"]
332 return False
337 return False
333
338
334 def hidden_frames(self, stack):
339 def hidden_frames(self, stack):
335 """
340 """
336 Given an index in the stack return whether it should be skipped.
341 Given an index in the stack return whether it should be skipped.
337
342
338 This is used in up/down and where to skip frames.
343 This is used in up/down and where to skip frames.
339 """
344 """
340 # The f_locals dictionary is updated from the actual frame
345 # The f_locals dictionary is updated from the actual frame
341 # locals whenever the .f_locals accessor is called, so we
346 # locals whenever the .f_locals accessor is called, so we
342 # avoid calling it here to preserve self.curframe_locals.
347 # avoid calling it here to preserve self.curframe_locals.
343 # Furthermore, there is no good reason to hide the current frame.
348 # Furthermore, there is no good reason to hide the current frame.
344 ip_hide = [self._hidden_predicate(s[0]) for s in stack]
349 ip_hide = [self._hidden_predicate(s[0]) for s in stack]
345 ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"]
350 ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"]
346 if ip_start and self._predicates["ipython_internal"]:
351 if ip_start and self._predicates["ipython_internal"]:
347 ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)]
352 ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)]
348 return ip_hide
353 return ip_hide
349
354
350 if CHAIN_EXCEPTIONS:
355 if CHAIN_EXCEPTIONS:
351
356
352 def _get_tb_and_exceptions(self, tb_or_exc):
357 def _get_tb_and_exceptions(self, tb_or_exc):
353 """
358 """
354 Given a tracecack or an exception, return a tuple of chained exceptions
359 Given a tracecack or an exception, return a tuple of chained exceptions
355 and current traceback to inspect.
360 and current traceback to inspect.
356 This will deal with selecting the right ``__cause__`` or ``__context__``
361 This will deal with selecting the right ``__cause__`` or ``__context__``
357 as well as handling cycles, and return a flattened list of exceptions we
362 as well as handling cycles, and return a flattened list of exceptions we
358 can jump to with do_exceptions.
363 can jump to with do_exceptions.
359 """
364 """
360 _exceptions = []
365 _exceptions = []
361 if isinstance(tb_or_exc, BaseException):
366 if isinstance(tb_or_exc, BaseException):
362 traceback, current = tb_or_exc.__traceback__, tb_or_exc
367 traceback, current = tb_or_exc.__traceback__, tb_or_exc
363
368
364 while current is not None:
369 while current is not None:
365 if current in _exceptions:
370 if current in _exceptions:
366 break
371 break
367 _exceptions.append(current)
372 _exceptions.append(current)
368 if current.__cause__ is not None:
373 if current.__cause__ is not None:
369 current = current.__cause__
374 current = current.__cause__
370 elif (
375 elif (
371 current.__context__ is not None
376 current.__context__ is not None
372 and not current.__suppress_context__
377 and not current.__suppress_context__
373 ):
378 ):
374 current = current.__context__
379 current = current.__context__
375
380
376 if len(_exceptions) >= self.MAX_CHAINED_EXCEPTION_DEPTH:
381 if len(_exceptions) >= self.MAX_CHAINED_EXCEPTION_DEPTH:
377 self.message(
382 self.message(
378 f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}"
383 f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}"
379 " chained exceptions found, not all exceptions"
384 " chained exceptions found, not all exceptions"
380 "will be browsable with `exceptions`."
385 "will be browsable with `exceptions`."
381 )
386 )
382 break
387 break
383 else:
388 else:
384 traceback = tb_or_exc
389 traceback = tb_or_exc
385 return tuple(reversed(_exceptions)), traceback
390 return tuple(reversed(_exceptions)), traceback
386
391
387 @contextmanager
392 @contextmanager
388 def _hold_exceptions(self, exceptions):
393 def _hold_exceptions(self, exceptions):
389 """
394 """
390 Context manager to ensure proper cleaning of exceptions references
395 Context manager to ensure proper cleaning of exceptions references
391 When given a chained exception instead of a traceback,
396 When given a chained exception instead of a traceback,
392 pdb may hold references to many objects which may leak memory.
397 pdb may hold references to many objects which may leak memory.
393 We use this context manager to make sure everything is properly cleaned
398 We use this context manager to make sure everything is properly cleaned
394 """
399 """
395 try:
400 try:
396 self._chained_exceptions = exceptions
401 self._chained_exceptions = exceptions
397 self._chained_exception_index = len(exceptions) - 1
402 self._chained_exception_index = len(exceptions) - 1
398 yield
403 yield
399 finally:
404 finally:
400 # we can't put those in forget as otherwise they would
405 # we can't put those in forget as otherwise they would
401 # be cleared on exception change
406 # be cleared on exception change
402 self._chained_exceptions = tuple()
407 self._chained_exceptions = tuple()
403 self._chained_exception_index = 0
408 self._chained_exception_index = 0
404
409
405 def do_exceptions(self, arg):
410 def do_exceptions(self, arg):
406 """exceptions [number]
411 """exceptions [number]
407 List or change current exception in an exception chain.
412 List or change current exception in an exception chain.
408 Without arguments, list all the current exception in the exception
413 Without arguments, list all the current exception in the exception
409 chain. Exceptions will be numbered, with the current exception indicated
414 chain. Exceptions will be numbered, with the current exception indicated
410 with an arrow.
415 with an arrow.
411 If given an integer as argument, switch to the exception at that index.
416 If given an integer as argument, switch to the exception at that index.
412 """
417 """
413 if not self._chained_exceptions:
418 if not self._chained_exceptions:
414 self.message(
419 self.message(
415 "Did not find chained exceptions. To move between"
420 "Did not find chained exceptions. To move between"
416 " exceptions, pdb/post_mortem must be given an exception"
421 " exceptions, pdb/post_mortem must be given an exception"
417 " object rather than a traceback."
422 " object rather than a traceback."
418 )
423 )
419 return
424 return
420 if not arg:
425 if not arg:
421 for ix, exc in enumerate(self._chained_exceptions):
426 for ix, exc in enumerate(self._chained_exceptions):
422 prompt = ">" if ix == self._chained_exception_index else " "
427 prompt = ">" if ix == self._chained_exception_index else " "
423 rep = repr(exc)
428 rep = repr(exc)
424 if len(rep) > 80:
429 if len(rep) > 80:
425 rep = rep[:77] + "..."
430 rep = rep[:77] + "..."
426 indicator = (
431 indicator = (
427 " -"
432 " -"
428 if self._chained_exceptions[ix].__traceback__ is None
433 if self._chained_exceptions[ix].__traceback__ is None
429 else f"{ix:>3}"
434 else f"{ix:>3}"
430 )
435 )
431 self.message(f"{prompt} {indicator} {rep}")
436 self.message(f"{prompt} {indicator} {rep}")
432 else:
437 else:
433 try:
438 try:
434 number = int(arg)
439 number = int(arg)
435 except ValueError:
440 except ValueError:
436 self.error("Argument must be an integer")
441 self.error("Argument must be an integer")
437 return
442 return
438 if 0 <= number < len(self._chained_exceptions):
443 if 0 <= number < len(self._chained_exceptions):
439 if self._chained_exceptions[number].__traceback__ is None:
444 if self._chained_exceptions[number].__traceback__ is None:
440 self.error(
445 self.error(
441 "This exception does not have a traceback, cannot jump to it"
446 "This exception does not have a traceback, cannot jump to it"
442 )
447 )
443 return
448 return
444
449
445 self._chained_exception_index = number
450 self._chained_exception_index = number
446 self.setup(None, self._chained_exceptions[number].__traceback__)
451 self.setup(None, self._chained_exceptions[number].__traceback__)
447 self.print_stack_entry(self.stack[self.curindex])
452 self.print_stack_entry(self.stack[self.curindex])
448 else:
453 else:
449 self.error("No exception with that number")
454 self.error("No exception with that number")
450
455
451 def interaction(self, frame, tb_or_exc):
456 def interaction(self, frame, tb_or_exc):
452 try:
457 try:
453 if CHAIN_EXCEPTIONS:
458 if CHAIN_EXCEPTIONS:
454 # this context manager is part of interaction in 3.13
459 # this context manager is part of interaction in 3.13
455 _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc)
460 _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc)
456 if isinstance(tb_or_exc, BaseException):
461 if isinstance(tb_or_exc, BaseException):
457 assert tb is not None, "main exception must have a traceback"
462 assert tb is not None, "main exception must have a traceback"
458 with self._hold_exceptions(_chained_exceptions):
463 with self._hold_exceptions(_chained_exceptions):
459 OldPdb.interaction(self, frame, tb)
464 OldPdb.interaction(self, frame, tb)
460 else:
465 else:
461 OldPdb.interaction(self, frame, tb_or_exc)
466 OldPdb.interaction(self, frame, tb_or_exc)
462
467
463 except KeyboardInterrupt:
468 except KeyboardInterrupt:
464 self.stdout.write("\n" + self.shell.get_exception_only())
469 self.stdout.write("\n" + self.shell.get_exception_only())
465
470
466 def precmd(self, line):
471 def precmd(self, line):
467 """Perform useful escapes on the command before it is executed."""
472 """Perform useful escapes on the command before it is executed."""
468
473
469 if line.endswith("??"):
474 if line.endswith("??"):
470 line = "pinfo2 " + line[:-2]
475 line = "pinfo2 " + line[:-2]
471 elif line.endswith("?"):
476 elif line.endswith("?"):
472 line = "pinfo " + line[:-1]
477 line = "pinfo " + line[:-1]
473
478
474 line = super().precmd(line)
479 line = super().precmd(line)
475
480
476 return line
481 return line
477
482
478 def new_do_quit(self, arg):
483 def new_do_quit(self, arg):
479 return OldPdb.do_quit(self, arg)
484 return OldPdb.do_quit(self, arg)
480
485
481 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
486 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
482
487
483 def print_stack_trace(self, context=None):
488 def print_stack_trace(self, context=None):
484 Colors = self.color_scheme_table.active_colors
489 Colors = self.color_scheme_table.active_colors
485 ColorsNormal = Colors.Normal
490 ColorsNormal = Colors.Normal
486 if context is None:
491 if context is None:
487 context = self.context
492 context = self.context
488 try:
493 try:
489 context = int(context)
494 context = int(context)
490 if context <= 0:
495 if context <= 0:
491 raise ValueError("Context must be a positive integer")
496 raise ValueError("Context must be a positive integer")
492 except (TypeError, ValueError) as e:
497 except (TypeError, ValueError) as e:
493 raise ValueError("Context must be a positive integer") from e
498 raise ValueError("Context must be a positive integer") from e
494 try:
499 try:
495 skipped = 0
500 skipped = 0
496 for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack):
501 for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack):
497 if hidden and self.skip_hidden:
502 if hidden and self.skip_hidden:
498 skipped += 1
503 skipped += 1
499 continue
504 continue
500 if skipped:
505 if skipped:
501 print(
506 print(
502 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
507 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
503 )
508 )
504 skipped = 0
509 skipped = 0
505 self.print_stack_entry(frame_lineno, context=context)
510 self.print_stack_entry(frame_lineno, context=context)
506 if skipped:
511 if skipped:
507 print(
512 print(
508 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
513 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
509 )
514 )
510 except KeyboardInterrupt:
515 except KeyboardInterrupt:
511 pass
516 pass
512
517
513 def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ',
518 def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ',
514 context=None):
519 context=None):
515 if context is None:
520 if context is None:
516 context = self.context
521 context = self.context
517 try:
522 try:
518 context = int(context)
523 context = int(context)
519 if context <= 0:
524 if context <= 0:
520 raise ValueError("Context must be a positive integer")
525 raise ValueError("Context must be a positive integer")
521 except (TypeError, ValueError) as e:
526 except (TypeError, ValueError) as e:
522 raise ValueError("Context must be a positive integer") from e
527 raise ValueError("Context must be a positive integer") from e
523 print(self.format_stack_entry(frame_lineno, '', context), file=self.stdout)
528 print(self.format_stack_entry(frame_lineno, '', context), file=self.stdout)
524
529
525 # vds: >>
530 # vds: >>
526 frame, lineno = frame_lineno
531 frame, lineno = frame_lineno
527 filename = frame.f_code.co_filename
532 filename = frame.f_code.co_filename
528 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
533 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
529 # vds: <<
534 # vds: <<
530
535
531 def _get_frame_locals(self, frame):
536 def _get_frame_locals(self, frame):
532 """ "
537 """ "
533 Accessing f_local of current frame reset the namespace, so we want to avoid
538 Accessing f_local of current frame reset the namespace, so we want to avoid
534 that or the following can happen
539 that or the following can happen
535
540
536 ipdb> foo
541 ipdb> foo
537 "old"
542 "old"
538 ipdb> foo = "new"
543 ipdb> foo = "new"
539 ipdb> foo
544 ipdb> foo
540 "new"
545 "new"
541 ipdb> where
546 ipdb> where
542 ipdb> foo
547 ipdb> foo
543 "old"
548 "old"
544
549
545 So if frame is self.current_frame we instead return self.curframe_locals
550 So if frame is self.current_frame we instead return self.curframe_locals
546
551
547 """
552 """
548 if frame is getattr(self, "curframe", None):
553 if frame is getattr(self, "curframe", None):
549 return self.curframe_locals
554 return self.curframe_locals
550 else:
555 else:
551 return frame.f_locals
556 return frame.f_locals
552
557
553 def format_stack_entry(self, frame_lineno, lprefix=': ', context=None):
558 def format_stack_entry(self, frame_lineno, lprefix=': ', context=None):
554 if context is None:
559 if context is None:
555 context = self.context
560 context = self.context
556 try:
561 try:
557 context = int(context)
562 context = int(context)
558 if context <= 0:
563 if context <= 0:
559 print("Context must be a positive integer", file=self.stdout)
564 print("Context must be a positive integer", file=self.stdout)
560 except (TypeError, ValueError):
565 except (TypeError, ValueError):
561 print("Context must be a positive integer", file=self.stdout)
566 print("Context must be a positive integer", file=self.stdout)
562
567
563 import reprlib
568 import reprlib
564
569
565 ret = []
570 ret = []
566
571
567 Colors = self.color_scheme_table.active_colors
572 Colors = self.color_scheme_table.active_colors
568 ColorsNormal = Colors.Normal
573 ColorsNormal = Colors.Normal
569 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
574 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
570 tpl_call = "%s%%s%s%%s%s" % (Colors.vName, Colors.valEm, ColorsNormal)
575 tpl_call = "%s%%s%s%%s%s" % (Colors.vName, Colors.valEm, ColorsNormal)
571 tpl_line = "%%s%s%%s %s%%s" % (Colors.lineno, ColorsNormal)
576 tpl_line = "%%s%s%%s %s%%s" % (Colors.lineno, ColorsNormal)
572 tpl_line_em = "%%s%s%%s %s%%s%s" % (Colors.linenoEm, Colors.line, ColorsNormal)
577 tpl_line_em = "%%s%s%%s %s%%s%s" % (Colors.linenoEm, Colors.line, ColorsNormal)
573
578
574 frame, lineno = frame_lineno
579 frame, lineno = frame_lineno
575
580
576 return_value = ''
581 return_value = ''
577 loc_frame = self._get_frame_locals(frame)
582 loc_frame = self._get_frame_locals(frame)
578 if "__return__" in loc_frame:
583 if "__return__" in loc_frame:
579 rv = loc_frame["__return__"]
584 rv = loc_frame["__return__"]
580 # return_value += '->'
585 # return_value += '->'
581 return_value += reprlib.repr(rv) + "\n"
586 return_value += reprlib.repr(rv) + "\n"
582 ret.append(return_value)
587 ret.append(return_value)
583
588
584 #s = filename + '(' + `lineno` + ')'
589 #s = filename + '(' + `lineno` + ')'
585 filename = self.canonic(frame.f_code.co_filename)
590 filename = self.canonic(frame.f_code.co_filename)
586 link = tpl_link % py3compat.cast_unicode(filename)
591 link = tpl_link % py3compat.cast_unicode(filename)
587
592
588 if frame.f_code.co_name:
593 if frame.f_code.co_name:
589 func = frame.f_code.co_name
594 func = frame.f_code.co_name
590 else:
595 else:
591 func = "<lambda>"
596 func = "<lambda>"
592
597
593 call = ""
598 call = ""
594 if func != "?":
599 if func != "?":
595 if "__args__" in loc_frame:
600 if "__args__" in loc_frame:
596 args = reprlib.repr(loc_frame["__args__"])
601 args = reprlib.repr(loc_frame["__args__"])
597 else:
602 else:
598 args = '()'
603 args = '()'
599 call = tpl_call % (func, args)
604 call = tpl_call % (func, args)
600
605
601 # The level info should be generated in the same format pdb uses, to
606 # The level info should be generated in the same format pdb uses, to
602 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
607 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
603 if frame is self.curframe:
608 if frame is self.curframe:
604 ret.append('> ')
609 ret.append('> ')
605 else:
610 else:
606 ret.append(" ")
611 ret.append(" ")
607 ret.append("%s(%s)%s\n" % (link, lineno, call))
612 ret.append("%s(%s)%s\n" % (link, lineno, call))
608
613
609 start = lineno - 1 - context//2
614 start = lineno - 1 - context//2
610 lines = linecache.getlines(filename)
615 lines = linecache.getlines(filename)
611 start = min(start, len(lines) - context)
616 start = min(start, len(lines) - context)
612 start = max(start, 0)
617 start = max(start, 0)
613 lines = lines[start : start + context]
618 lines = lines[start : start + context]
614
619
615 for i, line in enumerate(lines):
620 for i, line in enumerate(lines):
616 show_arrow = start + 1 + i == lineno
621 show_arrow = start + 1 + i == lineno
617 linetpl = (frame is self.curframe or show_arrow) and tpl_line_em or tpl_line
622 linetpl = (frame is self.curframe or show_arrow) and tpl_line_em or tpl_line
618 ret.append(
623 ret.append(
619 self.__format_line(
624 self.__format_line(
620 linetpl, filename, start + 1 + i, line, arrow=show_arrow
625 linetpl, filename, start + 1 + i, line, arrow=show_arrow
621 )
626 )
622 )
627 )
623 return "".join(ret)
628 return "".join(ret)
624
629
625 def __format_line(self, tpl_line, filename, lineno, line, arrow=False):
630 def __format_line(self, tpl_line, filename, lineno, line, arrow=False):
626 bp_mark = ""
631 bp_mark = ""
627 bp_mark_color = ""
632 bp_mark_color = ""
628
633
629 new_line, err = self.parser.format2(line, 'str')
634 new_line, err = self.parser.format2(line, 'str')
630 if not err:
635 if not err:
631 line = new_line
636 line = new_line
632
637
633 bp = None
638 bp = None
634 if lineno in self.get_file_breaks(filename):
639 if lineno in self.get_file_breaks(filename):
635 bps = self.get_breaks(filename, lineno)
640 bps = self.get_breaks(filename, lineno)
636 bp = bps[-1]
641 bp = bps[-1]
637
642
638 if bp:
643 if bp:
639 Colors = self.color_scheme_table.active_colors
644 Colors = self.color_scheme_table.active_colors
640 bp_mark = str(bp.number)
645 bp_mark = str(bp.number)
641 bp_mark_color = Colors.breakpoint_enabled
646 bp_mark_color = Colors.breakpoint_enabled
642 if not bp.enabled:
647 if not bp.enabled:
643 bp_mark_color = Colors.breakpoint_disabled
648 bp_mark_color = Colors.breakpoint_disabled
644
649
645 numbers_width = 7
650 numbers_width = 7
646 if arrow:
651 if arrow:
647 # This is the line with the error
652 # This is the line with the error
648 pad = numbers_width - len(str(lineno)) - len(bp_mark)
653 pad = numbers_width - len(str(lineno)) - len(bp_mark)
649 num = '%s%s' % (make_arrow(pad), str(lineno))
654 num = '%s%s' % (make_arrow(pad), str(lineno))
650 else:
655 else:
651 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
656 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
652
657
653 return tpl_line % (bp_mark_color + bp_mark, num, line)
658 return tpl_line % (bp_mark_color + bp_mark, num, line)
654
659
655 def print_list_lines(self, filename, first, last):
660 def print_list_lines(self, filename, first, last):
656 """The printing (as opposed to the parsing part of a 'list'
661 """The printing (as opposed to the parsing part of a 'list'
657 command."""
662 command."""
658 try:
663 try:
659 Colors = self.color_scheme_table.active_colors
664 Colors = self.color_scheme_table.active_colors
660 ColorsNormal = Colors.Normal
665 ColorsNormal = Colors.Normal
661 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
666 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
662 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
667 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
663 src = []
668 src = []
664 if filename == "<string>" and hasattr(self, "_exec_filename"):
669 if filename == "<string>" and hasattr(self, "_exec_filename"):
665 filename = self._exec_filename
670 filename = self._exec_filename
666
671
667 for lineno in range(first, last+1):
672 for lineno in range(first, last+1):
668 line = linecache.getline(filename, lineno)
673 line = linecache.getline(filename, lineno)
669 if not line:
674 if not line:
670 break
675 break
671
676
672 if lineno == self.curframe.f_lineno:
677 if lineno == self.curframe.f_lineno:
673 line = self.__format_line(
678 line = self.__format_line(
674 tpl_line_em, filename, lineno, line, arrow=True
679 tpl_line_em, filename, lineno, line, arrow=True
675 )
680 )
676 else:
681 else:
677 line = self.__format_line(
682 line = self.__format_line(
678 tpl_line, filename, lineno, line, arrow=False
683 tpl_line, filename, lineno, line, arrow=False
679 )
684 )
680
685
681 src.append(line)
686 src.append(line)
682 self.lineno = lineno
687 self.lineno = lineno
683
688
684 print(''.join(src), file=self.stdout)
689 print(''.join(src), file=self.stdout)
685
690
686 except KeyboardInterrupt:
691 except KeyboardInterrupt:
687 pass
692 pass
688
693
689 def do_skip_predicates(self, args):
694 def do_skip_predicates(self, args):
690 """
695 """
691 Turn on/off individual predicates as to whether a frame should be hidden/skip.
696 Turn on/off individual predicates as to whether a frame should be hidden/skip.
692
697
693 The global option to skip (or not) hidden frames is set with skip_hidden
698 The global option to skip (or not) hidden frames is set with skip_hidden
694
699
695 To change the value of a predicate
700 To change the value of a predicate
696
701
697 skip_predicates key [true|false]
702 skip_predicates key [true|false]
698
703
699 Call without arguments to see the current values.
704 Call without arguments to see the current values.
700
705
701 To permanently change the value of an option add the corresponding
706 To permanently change the value of an option add the corresponding
702 command to your ``~/.pdbrc`` file. If you are programmatically using the
707 command to your ``~/.pdbrc`` file. If you are programmatically using the
703 Pdb instance you can also change the ``default_predicates`` class
708 Pdb instance you can also change the ``default_predicates`` class
704 attribute.
709 attribute.
705 """
710 """
706 if not args.strip():
711 if not args.strip():
707 print("current predicates:")
712 print("current predicates:")
708 for p, v in self._predicates.items():
713 for p, v in self._predicates.items():
709 print(" ", p, ":", v)
714 print(" ", p, ":", v)
710 return
715 return
711 type_value = args.strip().split(" ")
716 type_value = args.strip().split(" ")
712 if len(type_value) != 2:
717 if len(type_value) != 2:
713 print(
718 print(
714 f"Usage: skip_predicates <type> <value>, with <type> one of {set(self._predicates.keys())}"
719 f"Usage: skip_predicates <type> <value>, with <type> one of {set(self._predicates.keys())}"
715 )
720 )
716 return
721 return
717
722
718 type_, value = type_value
723 type_, value = type_value
719 if type_ not in self._predicates:
724 if type_ not in self._predicates:
720 print(f"{type_!r} not in {set(self._predicates.keys())}")
725 print(f"{type_!r} not in {set(self._predicates.keys())}")
721 return
726 return
722 if value.lower() not in ("true", "yes", "1", "no", "false", "0"):
727 if value.lower() not in ("true", "yes", "1", "no", "false", "0"):
723 print(
728 print(
724 f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')"
729 f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')"
725 )
730 )
726 return
731 return
727
732
728 self._predicates[type_] = value.lower() in ("true", "yes", "1")
733 self._predicates[type_] = value.lower() in ("true", "yes", "1")
729 if not any(self._predicates.values()):
734 if not any(self._predicates.values()):
730 print(
735 print(
731 "Warning, all predicates set to False, skip_hidden may not have any effects."
736 "Warning, all predicates set to False, skip_hidden may not have any effects."
732 )
737 )
733
738
734 def do_skip_hidden(self, arg):
739 def do_skip_hidden(self, arg):
735 """
740 """
736 Change whether or not we should skip frames with the
741 Change whether or not we should skip frames with the
737 __tracebackhide__ attribute.
742 __tracebackhide__ attribute.
738 """
743 """
739 if not arg.strip():
744 if not arg.strip():
740 print(
745 print(
741 f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change."
746 f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change."
742 )
747 )
743 elif arg.strip().lower() in ("true", "yes"):
748 elif arg.strip().lower() in ("true", "yes"):
744 self.skip_hidden = True
749 self.skip_hidden = True
745 elif arg.strip().lower() in ("false", "no"):
750 elif arg.strip().lower() in ("false", "no"):
746 self.skip_hidden = False
751 self.skip_hidden = False
747 if not any(self._predicates.values()):
752 if not any(self._predicates.values()):
748 print(
753 print(
749 "Warning, all predicates set to False, skip_hidden may not have any effects."
754 "Warning, all predicates set to False, skip_hidden may not have any effects."
750 )
755 )
751
756
752 def do_list(self, arg):
757 def do_list(self, arg):
753 """Print lines of code from the current stack frame
758 """Print lines of code from the current stack frame
754 """
759 """
755 self.lastcmd = 'list'
760 self.lastcmd = 'list'
756 last = None
761 last = None
757 if arg and arg != ".":
762 if arg and arg != ".":
758 try:
763 try:
759 x = eval(arg, {}, {})
764 x = eval(arg, {}, {})
760 if type(x) == type(()):
765 if type(x) == type(()):
761 first, last = x
766 first, last = x
762 first = int(first)
767 first = int(first)
763 last = int(last)
768 last = int(last)
764 if last < first:
769 if last < first:
765 # Assume it's a count
770 # Assume it's a count
766 last = first + last
771 last = first + last
767 else:
772 else:
768 first = max(1, int(x) - 5)
773 first = max(1, int(x) - 5)
769 except:
774 except:
770 print('*** Error in argument:', repr(arg), file=self.stdout)
775 print('*** Error in argument:', repr(arg), file=self.stdout)
771 return
776 return
772 elif self.lineno is None or arg == ".":
777 elif self.lineno is None or arg == ".":
773 first = max(1, self.curframe.f_lineno - 5)
778 first = max(1, self.curframe.f_lineno - 5)
774 else:
779 else:
775 first = self.lineno + 1
780 first = self.lineno + 1
776 if last is None:
781 if last is None:
777 last = first + 10
782 last = first + 10
778 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
783 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
779
784
780 # vds: >>
785 # vds: >>
781 lineno = first
786 lineno = first
782 filename = self.curframe.f_code.co_filename
787 filename = self.curframe.f_code.co_filename
783 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
788 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
784 # vds: <<
789 # vds: <<
785
790
786 do_l = do_list
791 do_l = do_list
787
792
788 def getsourcelines(self, obj):
793 def getsourcelines(self, obj):
789 lines, lineno = inspect.findsource(obj)
794 lines, lineno = inspect.findsource(obj)
790 if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj):
795 if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj):
791 # must be a module frame: do not try to cut a block out of it
796 # must be a module frame: do not try to cut a block out of it
792 return lines, 1
797 return lines, 1
793 elif inspect.ismodule(obj):
798 elif inspect.ismodule(obj):
794 return lines, 1
799 return lines, 1
795 return inspect.getblock(lines[lineno:]), lineno+1
800 return inspect.getblock(lines[lineno:]), lineno+1
796
801
797 def do_longlist(self, arg):
802 def do_longlist(self, arg):
798 """Print lines of code from the current stack frame.
803 """Print lines of code from the current stack frame.
799
804
800 Shows more lines than 'list' does.
805 Shows more lines than 'list' does.
801 """
806 """
802 self.lastcmd = 'longlist'
807 self.lastcmd = 'longlist'
803 try:
808 try:
804 lines, lineno = self.getsourcelines(self.curframe)
809 lines, lineno = self.getsourcelines(self.curframe)
805 except OSError as err:
810 except OSError as err:
806 self.error(err)
811 self.error(err)
807 return
812 return
808 last = lineno + len(lines)
813 last = lineno + len(lines)
809 self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
814 self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
810 do_ll = do_longlist
815 do_ll = do_longlist
811
816
812 def do_debug(self, arg):
817 def do_debug(self, arg):
813 """debug code
818 """debug code
814 Enter a recursive debugger that steps through the code
819 Enter a recursive debugger that steps through the code
815 argument (which is an arbitrary expression or statement to be
820 argument (which is an arbitrary expression or statement to be
816 executed in the current environment).
821 executed in the current environment).
817 """
822 """
818 trace_function = sys.gettrace()
823 trace_function = sys.gettrace()
819 sys.settrace(None)
824 sys.settrace(None)
820 globals = self.curframe.f_globals
825 globals = self.curframe.f_globals
821 locals = self.curframe_locals
826 locals = self.curframe_locals
822 p = self.__class__(completekey=self.completekey,
827 p = self.__class__(completekey=self.completekey,
823 stdin=self.stdin, stdout=self.stdout)
828 stdin=self.stdin, stdout=self.stdout)
824 p.use_rawinput = self.use_rawinput
829 p.use_rawinput = self.use_rawinput
825 p.prompt = "(%s) " % self.prompt.strip()
830 p.prompt = "(%s) " % self.prompt.strip()
826 self.message("ENTERING RECURSIVE DEBUGGER")
831 self.message("ENTERING RECURSIVE DEBUGGER")
827 sys.call_tracing(p.run, (arg, globals, locals))
832 sys.call_tracing(p.run, (arg, globals, locals))
828 self.message("LEAVING RECURSIVE DEBUGGER")
833 self.message("LEAVING RECURSIVE DEBUGGER")
829 sys.settrace(trace_function)
834 sys.settrace(trace_function)
830 self.lastcmd = p.lastcmd
835 self.lastcmd = p.lastcmd
831
836
832 def do_pdef(self, arg):
837 def do_pdef(self, arg):
833 """Print the call signature for any callable object.
838 """Print the call signature for any callable object.
834
839
835 The debugger interface to %pdef"""
840 The debugger interface to %pdef"""
836 namespaces = [
841 namespaces = [
837 ("Locals", self.curframe_locals),
842 ("Locals", self.curframe_locals),
838 ("Globals", self.curframe.f_globals),
843 ("Globals", self.curframe.f_globals),
839 ]
844 ]
840 self.shell.find_line_magic("pdef")(arg, namespaces=namespaces)
845 self.shell.find_line_magic("pdef")(arg, namespaces=namespaces)
841
846
842 def do_pdoc(self, arg):
847 def do_pdoc(self, arg):
843 """Print the docstring for an object.
848 """Print the docstring for an object.
844
849
845 The debugger interface to %pdoc."""
850 The debugger interface to %pdoc."""
846 namespaces = [
851 namespaces = [
847 ("Locals", self.curframe_locals),
852 ("Locals", self.curframe_locals),
848 ("Globals", self.curframe.f_globals),
853 ("Globals", self.curframe.f_globals),
849 ]
854 ]
850 self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces)
855 self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces)
851
856
852 def do_pfile(self, arg):
857 def do_pfile(self, arg):
853 """Print (or run through pager) the file where an object is defined.
858 """Print (or run through pager) the file where an object is defined.
854
859
855 The debugger interface to %pfile.
860 The debugger interface to %pfile.
856 """
861 """
857 namespaces = [
862 namespaces = [
858 ("Locals", self.curframe_locals),
863 ("Locals", self.curframe_locals),
859 ("Globals", self.curframe.f_globals),
864 ("Globals", self.curframe.f_globals),
860 ]
865 ]
861 self.shell.find_line_magic("pfile")(arg, namespaces=namespaces)
866 self.shell.find_line_magic("pfile")(arg, namespaces=namespaces)
862
867
863 def do_pinfo(self, arg):
868 def do_pinfo(self, arg):
864 """Provide detailed information about an object.
869 """Provide detailed information about an object.
865
870
866 The debugger interface to %pinfo, i.e., obj?."""
871 The debugger interface to %pinfo, i.e., obj?."""
867 namespaces = [
872 namespaces = [
868 ("Locals", self.curframe_locals),
873 ("Locals", self.curframe_locals),
869 ("Globals", self.curframe.f_globals),
874 ("Globals", self.curframe.f_globals),
870 ]
875 ]
871 self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces)
876 self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces)
872
877
873 def do_pinfo2(self, arg):
878 def do_pinfo2(self, arg):
874 """Provide extra detailed information about an object.
879 """Provide extra detailed information about an object.
875
880
876 The debugger interface to %pinfo2, i.e., obj??."""
881 The debugger interface to %pinfo2, i.e., obj??."""
877 namespaces = [
882 namespaces = [
878 ("Locals", self.curframe_locals),
883 ("Locals", self.curframe_locals),
879 ("Globals", self.curframe.f_globals),
884 ("Globals", self.curframe.f_globals),
880 ]
885 ]
881 self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces)
886 self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces)
882
887
883 def do_psource(self, arg):
888 def do_psource(self, arg):
884 """Print (or run through pager) the source code for an object."""
889 """Print (or run through pager) the source code for an object."""
885 namespaces = [
890 namespaces = [
886 ("Locals", self.curframe_locals),
891 ("Locals", self.curframe_locals),
887 ("Globals", self.curframe.f_globals),
892 ("Globals", self.curframe.f_globals),
888 ]
893 ]
889 self.shell.find_line_magic("psource")(arg, namespaces=namespaces)
894 self.shell.find_line_magic("psource")(arg, namespaces=namespaces)
890
895
891 def do_where(self, arg):
896 def do_where(self, arg):
892 """w(here)
897 """w(here)
893 Print a stack trace, with the most recent frame at the bottom.
898 Print a stack trace, with the most recent frame at the bottom.
894 An arrow indicates the "current frame", which determines the
899 An arrow indicates the "current frame", which determines the
895 context of most commands. 'bt' is an alias for this command.
900 context of most commands. 'bt' is an alias for this command.
896
901
897 Take a number as argument as an (optional) number of context line to
902 Take a number as argument as an (optional) number of context line to
898 print"""
903 print"""
899 if arg:
904 if arg:
900 try:
905 try:
901 context = int(arg)
906 context = int(arg)
902 except ValueError as err:
907 except ValueError as err:
903 self.error(err)
908 self.error(err)
904 return
909 return
905 self.print_stack_trace(context)
910 self.print_stack_trace(context)
906 else:
911 else:
907 self.print_stack_trace()
912 self.print_stack_trace()
908
913
909 do_w = do_where
914 do_w = do_where
910
915
911 def break_anywhere(self, frame):
916 def break_anywhere(self, frame):
912 """
917 """
913 _stop_in_decorator_internals is overly restrictive, as we may still want
918 _stop_in_decorator_internals is overly restrictive, as we may still want
914 to trace function calls, so we need to also update break_anywhere so
919 to trace function calls, so we need to also update break_anywhere so
915 that is we don't `stop_here`, because of debugger skip, we may still
920 that is we don't `stop_here`, because of debugger skip, we may still
916 stop at any point inside the function
921 stop at any point inside the function
917
922
918 """
923 """
919
924
920 sup = super().break_anywhere(frame)
925 sup = super().break_anywhere(frame)
921 if sup:
926 if sup:
922 return sup
927 return sup
923 if self._predicates["debuggerskip"]:
928 if self._predicates["debuggerskip"]:
924 if DEBUGGERSKIP in frame.f_code.co_varnames:
929 if DEBUGGERSKIP in frame.f_code.co_varnames:
925 return True
930 return True
926 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
931 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
927 return True
932 return True
928 return False
933 return False
929
934
930 def _is_in_decorator_internal_and_should_skip(self, frame):
935 def _is_in_decorator_internal_and_should_skip(self, frame):
931 """
936 """
932 Utility to tell us whether we are in a decorator internal and should stop.
937 Utility to tell us whether we are in a decorator internal and should stop.
933
938
934 """
939 """
935 # if we are disabled don't skip
940 # if we are disabled don't skip
936 if not self._predicates["debuggerskip"]:
941 if not self._predicates["debuggerskip"]:
937 return False
942 return False
938
943
939 return self._cachable_skip(frame)
944 return self._cachable_skip(frame)
940
945
941 @lru_cache(1024)
946 @lru_cache(1024)
942 def _cached_one_parent_frame_debuggerskip(self, frame):
947 def _cached_one_parent_frame_debuggerskip(self, frame):
943 """
948 """
944 Cache looking up for DEBUGGERSKIP on parent frame.
949 Cache looking up for DEBUGGERSKIP on parent frame.
945
950
946 This should speedup walking through deep frame when one of the highest
951 This should speedup walking through deep frame when one of the highest
947 one does have a debugger skip.
952 one does have a debugger skip.
948
953
949 This is likely to introduce fake positive though.
954 This is likely to introduce fake positive though.
950 """
955 """
951 while getattr(frame, "f_back", None):
956 while getattr(frame, "f_back", None):
952 frame = frame.f_back
957 frame = frame.f_back
953 if self._get_frame_locals(frame).get(DEBUGGERSKIP):
958 if self._get_frame_locals(frame).get(DEBUGGERSKIP):
954 return True
959 return True
955 return None
960 return None
956
961
957 @lru_cache(1024)
962 @lru_cache(1024)
958 def _cachable_skip(self, frame):
963 def _cachable_skip(self, frame):
959 # if frame is tagged, skip by default.
964 # if frame is tagged, skip by default.
960 if DEBUGGERSKIP in frame.f_code.co_varnames:
965 if DEBUGGERSKIP in frame.f_code.co_varnames:
961 return True
966 return True
962
967
963 # if one of the parent frame value set to True skip as well.
968 # if one of the parent frame value set to True skip as well.
964 if self._cached_one_parent_frame_debuggerskip(frame):
969 if self._cached_one_parent_frame_debuggerskip(frame):
965 return True
970 return True
966
971
967 return False
972 return False
968
973
969 def stop_here(self, frame):
974 def stop_here(self, frame):
970 if self._is_in_decorator_internal_and_should_skip(frame) is True:
975 if self._is_in_decorator_internal_and_should_skip(frame) is True:
971 return False
976 return False
972
977
973 hidden = False
978 hidden = False
974 if self.skip_hidden:
979 if self.skip_hidden:
975 hidden = self._hidden_predicate(frame)
980 hidden = self._hidden_predicate(frame)
976 if hidden:
981 if hidden:
977 if self.report_skipped:
982 if self.report_skipped:
978 Colors = self.color_scheme_table.active_colors
983 Colors = self.color_scheme_table.active_colors
979 ColorsNormal = Colors.Normal
984 ColorsNormal = Colors.Normal
980 print(
985 print(
981 f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n"
986 f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n"
982 )
987 )
983 return super().stop_here(frame)
988 return super().stop_here(frame)
984
989
985 def do_up(self, arg):
990 def do_up(self, arg):
986 """u(p) [count]
991 """u(p) [count]
987 Move the current frame count (default one) levels up in the
992 Move the current frame count (default one) levels up in the
988 stack trace (to an older frame).
993 stack trace (to an older frame).
989
994
990 Will skip hidden frames.
995 Will skip hidden frames.
991 """
996 """
992 # modified version of upstream that skips
997 # modified version of upstream that skips
993 # frames with __tracebackhide__
998 # frames with __tracebackhide__
994 if self.curindex == 0:
999 if self.curindex == 0:
995 self.error("Oldest frame")
1000 self.error("Oldest frame")
996 return
1001 return
997 try:
1002 try:
998 count = int(arg or 1)
1003 count = int(arg or 1)
999 except ValueError:
1004 except ValueError:
1000 self.error("Invalid frame count (%s)" % arg)
1005 self.error("Invalid frame count (%s)" % arg)
1001 return
1006 return
1002 skipped = 0
1007 skipped = 0
1003 if count < 0:
1008 if count < 0:
1004 _newframe = 0
1009 _newframe = 0
1005 else:
1010 else:
1006 counter = 0
1011 counter = 0
1007 hidden_frames = self.hidden_frames(self.stack)
1012 hidden_frames = self.hidden_frames(self.stack)
1008 for i in range(self.curindex - 1, -1, -1):
1013 for i in range(self.curindex - 1, -1, -1):
1009 if hidden_frames[i] and self.skip_hidden:
1014 if hidden_frames[i] and self.skip_hidden:
1010 skipped += 1
1015 skipped += 1
1011 continue
1016 continue
1012 counter += 1
1017 counter += 1
1013 if counter >= count:
1018 if counter >= count:
1014 break
1019 break
1015 else:
1020 else:
1016 # if no break occurred.
1021 # if no break occurred.
1017 self.error(
1022 self.error(
1018 "all frames above hidden, use `skip_hidden False` to get get into those."
1023 "all frames above hidden, use `skip_hidden False` to get get into those."
1019 )
1024 )
1020 return
1025 return
1021
1026
1022 Colors = self.color_scheme_table.active_colors
1027 Colors = self.color_scheme_table.active_colors
1023 ColorsNormal = Colors.Normal
1028 ColorsNormal = Colors.Normal
1024 _newframe = i
1029 _newframe = i
1025 self._select_frame(_newframe)
1030 self._select_frame(_newframe)
1026 if skipped:
1031 if skipped:
1027 print(
1032 print(
1028 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
1033 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
1029 )
1034 )
1030
1035
1031 def do_down(self, arg):
1036 def do_down(self, arg):
1032 """d(own) [count]
1037 """d(own) [count]
1033 Move the current frame count (default one) levels down in the
1038 Move the current frame count (default one) levels down in the
1034 stack trace (to a newer frame).
1039 stack trace (to a newer frame).
1035
1040
1036 Will skip hidden frames.
1041 Will skip hidden frames.
1037 """
1042 """
1038 if self.curindex + 1 == len(self.stack):
1043 if self.curindex + 1 == len(self.stack):
1039 self.error("Newest frame")
1044 self.error("Newest frame")
1040 return
1045 return
1041 try:
1046 try:
1042 count = int(arg or 1)
1047 count = int(arg or 1)
1043 except ValueError:
1048 except ValueError:
1044 self.error("Invalid frame count (%s)" % arg)
1049 self.error("Invalid frame count (%s)" % arg)
1045 return
1050 return
1046 if count < 0:
1051 if count < 0:
1047 _newframe = len(self.stack) - 1
1052 _newframe = len(self.stack) - 1
1048 else:
1053 else:
1049 counter = 0
1054 counter = 0
1050 skipped = 0
1055 skipped = 0
1051 hidden_frames = self.hidden_frames(self.stack)
1056 hidden_frames = self.hidden_frames(self.stack)
1052 for i in range(self.curindex + 1, len(self.stack)):
1057 for i in range(self.curindex + 1, len(self.stack)):
1053 if hidden_frames[i] and self.skip_hidden:
1058 if hidden_frames[i] and self.skip_hidden:
1054 skipped += 1
1059 skipped += 1
1055 continue
1060 continue
1056 counter += 1
1061 counter += 1
1057 if counter >= count:
1062 if counter >= count:
1058 break
1063 break
1059 else:
1064 else:
1060 self.error(
1065 self.error(
1061 "all frames below hidden, use `skip_hidden False` to get get into those."
1066 "all frames below hidden, use `skip_hidden False` to get get into those."
1062 )
1067 )
1063 return
1068 return
1064
1069
1065 Colors = self.color_scheme_table.active_colors
1070 Colors = self.color_scheme_table.active_colors
1066 ColorsNormal = Colors.Normal
1071 ColorsNormal = Colors.Normal
1067 if skipped:
1072 if skipped:
1068 print(
1073 print(
1069 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
1074 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
1070 )
1075 )
1071 _newframe = i
1076 _newframe = i
1072
1077
1073 self._select_frame(_newframe)
1078 self._select_frame(_newframe)
1074
1079
1075 do_d = do_down
1080 do_d = do_down
1076 do_u = do_up
1081 do_u = do_up
1077
1082
1078 def do_context(self, context):
1083 def do_context(self, context):
1079 """context number_of_lines
1084 """context number_of_lines
1080 Set the number of lines of source code to show when displaying
1085 Set the number of lines of source code to show when displaying
1081 stacktrace information.
1086 stacktrace information.
1082 """
1087 """
1083 try:
1088 try:
1084 new_context = int(context)
1089 new_context = int(context)
1085 if new_context <= 0:
1090 if new_context <= 0:
1086 raise ValueError()
1091 raise ValueError()
1087 self.context = new_context
1092 self.context = new_context
1088 except ValueError:
1093 except ValueError:
1089 self.error(
1094 self.error(
1090 f"The 'context' command requires a positive integer argument (current value {self.context})."
1095 f"The 'context' command requires a positive integer argument (current value {self.context})."
1091 )
1096 )
1092
1097
1093
1098
1094 class InterruptiblePdb(Pdb):
1099 class InterruptiblePdb(Pdb):
1095 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
1100 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
1096
1101
1097 def cmdloop(self, intro=None):
1102 def cmdloop(self, intro=None):
1098 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
1103 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
1099 try:
1104 try:
1100 return OldPdb.cmdloop(self, intro=intro)
1105 return OldPdb.cmdloop(self, intro=intro)
1101 except KeyboardInterrupt:
1106 except KeyboardInterrupt:
1102 self.stop_here = lambda frame: False
1107 self.stop_here = lambda frame: False
1103 self.do_quit("")
1108 self.do_quit("")
1104 sys.settrace(None)
1109 sys.settrace(None)
1105 self.quitting = False
1110 self.quitting = False
1106 raise
1111 raise
1107
1112
1108 def _cmdloop(self):
1113 def _cmdloop(self):
1109 while True:
1114 while True:
1110 try:
1115 try:
1111 # keyboard interrupts allow for an easy way to cancel
1116 # keyboard interrupts allow for an easy way to cancel
1112 # the current command, so allow them during interactive input
1117 # the current command, so allow them during interactive input
1113 self.allow_kbdint = True
1118 self.allow_kbdint = True
1114 self.cmdloop()
1119 self.cmdloop()
1115 self.allow_kbdint = False
1120 self.allow_kbdint = False
1116 break
1121 break
1117 except KeyboardInterrupt:
1122 except KeyboardInterrupt:
1118 self.message('--KeyboardInterrupt--')
1123 self.message('--KeyboardInterrupt--')
1119 raise
1124 raise
1120
1125
1121
1126
1122 def set_trace(frame=None, header=None):
1127 def set_trace(frame=None, header=None):
1123 """
1128 """
1124 Start debugging from `frame`.
1129 Start debugging from `frame`.
1125
1130
1126 If frame is not specified, debugging starts from caller's frame.
1131 If frame is not specified, debugging starts from caller's frame.
1127 """
1132 """
1128 pdb = Pdb()
1133 pdb = Pdb()
1129 if header is not None:
1134 if header is not None:
1130 pdb.message(header)
1135 pdb.message(header)
1131 pdb.set_trace(frame or sys._getframe().f_back)
1136 pdb.set_trace(frame or sys._getframe().f_back)
@@ -1,336 +1,336
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Displayhook for IPython.
2 """Displayhook for IPython.
3
3
4 This defines a callable class that IPython uses for `sys.displayhook`.
4 This defines a callable class that IPython uses for `sys.displayhook`.
5 """
5 """
6
6
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10 import builtins as builtin_mod
10 import builtins as builtin_mod
11 import sys
11 import sys
12 import io as _io
12 import io as _io
13 import tokenize
13 import tokenize
14
14
15 from traitlets.config.configurable import Configurable
15 from traitlets.config.configurable import Configurable
16 from traitlets import Instance, Float
16 from traitlets import Instance, Float
17 from warnings import warn
17 from warnings import warn
18
18
19 # TODO: Move the various attributes (cache_size, [others now moved]). Some
19 # TODO: Move the various attributes (cache_size, [others now moved]). Some
20 # of these are also attributes of InteractiveShell. They should be on ONE object
20 # of these are also attributes of InteractiveShell. They should be on ONE object
21 # only and the other objects should ask that one object for their values.
21 # only and the other objects should ask that one object for their values.
22
22
23 class DisplayHook(Configurable):
23 class DisplayHook(Configurable):
24 """The custom IPython displayhook to replace sys.displayhook.
24 """The custom IPython displayhook to replace sys.displayhook.
25
25
26 This class does many things, but the basic idea is that it is a callable
26 This class does many things, but the basic idea is that it is a callable
27 that gets called anytime user code returns a value.
27 that gets called anytime user code returns a value.
28 """
28 """
29
29
30 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
30 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
31 allow_none=True)
31 allow_none=True)
32 exec_result = Instance('IPython.core.interactiveshell.ExecutionResult',
32 exec_result = Instance('IPython.core.interactiveshell.ExecutionResult',
33 allow_none=True)
33 allow_none=True)
34 cull_fraction = Float(0.2)
34 cull_fraction = Float(0.2)
35
35
36 def __init__(self, shell=None, cache_size=1000, **kwargs):
36 def __init__(self, shell=None, cache_size=1000, **kwargs):
37 super(DisplayHook, self).__init__(shell=shell, **kwargs)
37 super(DisplayHook, self).__init__(shell=shell, **kwargs)
38 cache_size_min = 3
38 cache_size_min = 3
39 if cache_size <= 0:
39 if cache_size <= 0:
40 self.do_full_cache = 0
40 self.do_full_cache = 0
41 cache_size = 0
41 cache_size = 0
42 elif cache_size < cache_size_min:
42 elif cache_size < cache_size_min:
43 self.do_full_cache = 0
43 self.do_full_cache = 0
44 cache_size = 0
44 cache_size = 0
45 warn('caching was disabled (min value for cache size is %s).' %
45 warn('caching was disabled (min value for cache size is %s).' %
46 cache_size_min,stacklevel=3)
46 cache_size_min,stacklevel=3)
47 else:
47 else:
48 self.do_full_cache = 1
48 self.do_full_cache = 1
49
49
50 self.cache_size = cache_size
50 self.cache_size = cache_size
51
51
52 # we need a reference to the user-level namespace
52 # we need a reference to the user-level namespace
53 self.shell = shell
53 self.shell = shell
54
54
55 self._,self.__,self.___ = '','',''
55 self._,self.__,self.___ = '','',''
56
56
57 # these are deliberately global:
57 # these are deliberately global:
58 to_user_ns = {'_':self._,'__':self.__,'___':self.___}
58 to_user_ns = {'_':self._,'__':self.__,'___':self.___}
59 self.shell.user_ns.update(to_user_ns)
59 self.shell.user_ns.update(to_user_ns)
60
60
61 @property
61 @property
62 def prompt_count(self):
62 def prompt_count(self):
63 return self.shell.execution_count
63 return self.shell.execution_count
64
64
65 #-------------------------------------------------------------------------
65 #-------------------------------------------------------------------------
66 # Methods used in __call__. Override these methods to modify the behavior
66 # Methods used in __call__. Override these methods to modify the behavior
67 # of the displayhook.
67 # of the displayhook.
68 #-------------------------------------------------------------------------
68 #-------------------------------------------------------------------------
69
69
70 def check_for_underscore(self):
70 def check_for_underscore(self):
71 """Check if the user has set the '_' variable by hand."""
71 """Check if the user has set the '_' variable by hand."""
72 # If something injected a '_' variable in __builtin__, delete
72 # If something injected a '_' variable in __builtin__, delete
73 # ipython's automatic one so we don't clobber that. gettext() in
73 # ipython's automatic one so we don't clobber that. gettext() in
74 # particular uses _, so we need to stay away from it.
74 # particular uses _, so we need to stay away from it.
75 if '_' in builtin_mod.__dict__:
75 if '_' in builtin_mod.__dict__:
76 try:
76 try:
77 user_value = self.shell.user_ns['_']
77 user_value = self.shell.user_ns['_']
78 if user_value is not self._:
78 if user_value is not self._:
79 return
79 return
80 del self.shell.user_ns['_']
80 del self.shell.user_ns['_']
81 except KeyError:
81 except KeyError:
82 pass
82 pass
83
83
84 def quiet(self):
84 def quiet(self):
85 """Should we silence the display hook because of ';'?"""
85 """Should we silence the display hook because of ';'?"""
86 # do not print output if input ends in ';'
86 # do not print output if input ends in ';'
87
87
88 try:
88 try:
89 cell = self.shell.history_manager.input_hist_parsed[-1]
89 cell = self.shell.history_manager.input_hist_parsed[-1]
90 except IndexError:
90 except IndexError:
91 # some uses of ipshellembed may fail here
91 # some uses of ipshellembed may fail here
92 return False
92 return False
93
93
94 return self.semicolon_at_end_of_expression(cell)
94 return self.semicolon_at_end_of_expression(cell)
95
95
96 @staticmethod
96 @staticmethod
97 def semicolon_at_end_of_expression(expression):
97 def semicolon_at_end_of_expression(expression):
98 """Parse Python expression and detects whether last token is ';'"""
98 """Parse Python expression and detects whether last token is ';'"""
99
99
100 sio = _io.StringIO(expression)
100 sio = _io.StringIO(expression)
101 tokens = list(tokenize.generate_tokens(sio.readline))
101 tokens = list(tokenize.generate_tokens(sio.readline))
102
102
103 for token in reversed(tokens):
103 for token in reversed(tokens):
104 if token[0] in (tokenize.ENDMARKER, tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT):
104 if token[0] in (tokenize.ENDMARKER, tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT):
105 continue
105 continue
106 if (token[0] == tokenize.OP) and (token[1] == ';'):
106 if (token[0] == tokenize.OP) and (token[1] == ';'):
107 return True
107 return True
108 else:
108 else:
109 return False
109 return False
110
110
111 def start_displayhook(self):
111 def start_displayhook(self):
112 """Start the displayhook, initializing resources."""
112 """Start the displayhook, initializing resources."""
113 pass
113 pass
114
114
115 def write_output_prompt(self):
115 def write_output_prompt(self):
116 """Write the output prompt.
116 """Write the output prompt.
117
117
118 The default implementation simply writes the prompt to
118 The default implementation simply writes the prompt to
119 ``sys.stdout``.
119 ``sys.stdout``.
120 """
120 """
121 # Use write, not print which adds an extra space.
121 # Use write, not print which adds an extra space.
122 sys.stdout.write(self.shell.separate_out)
122 sys.stdout.write(self.shell.separate_out)
123 outprompt = 'Out[{}]: '.format(self.shell.execution_count)
123 outprompt = 'Out[{}]: '.format(self.shell.execution_count)
124 if self.do_full_cache:
124 if self.do_full_cache:
125 sys.stdout.write(outprompt)
125 sys.stdout.write(outprompt)
126
126
127 def compute_format_data(self, result):
127 def compute_format_data(self, result):
128 """Compute format data of the object to be displayed.
128 """Compute format data of the object to be displayed.
129
129
130 The format data is a generalization of the :func:`repr` of an object.
130 The format data is a generalization of the :func:`repr` of an object.
131 In the default implementation the format data is a :class:`dict` of
131 In the default implementation the format data is a :class:`dict` of
132 key value pair where the keys are valid MIME types and the values
132 key value pair where the keys are valid MIME types and the values
133 are JSON'able data structure containing the raw data for that MIME
133 are JSON'able data structure containing the raw data for that MIME
134 type. It is up to frontends to determine pick a MIME to to use and
134 type. It is up to frontends to determine pick a MIME to to use and
135 display that data in an appropriate manner.
135 display that data in an appropriate manner.
136
136
137 This method only computes the format data for the object and should
137 This method only computes the format data for the object and should
138 NOT actually print or write that to a stream.
138 NOT actually print or write that to a stream.
139
139
140 Parameters
140 Parameters
141 ----------
141 ----------
142 result : object
142 result : object
143 The Python object passed to the display hook, whose format will be
143 The Python object passed to the display hook, whose format will be
144 computed.
144 computed.
145
145
146 Returns
146 Returns
147 -------
147 -------
148 (format_dict, md_dict) : dict
148 (format_dict, md_dict) : dict
149 format_dict is a :class:`dict` whose keys are valid MIME types and values are
149 format_dict is a :class:`dict` whose keys are valid MIME types and values are
150 JSON'able raw data for that MIME type. It is recommended that
150 JSON'able raw data for that MIME type. It is recommended that
151 all return values of this should always include the "text/plain"
151 all return values of this should always include the "text/plain"
152 MIME type representation of the object.
152 MIME type representation of the object.
153 md_dict is a :class:`dict` with the same MIME type keys
153 md_dict is a :class:`dict` with the same MIME type keys
154 of metadata associated with each output.
154 of metadata associated with each output.
155
155
156 """
156 """
157 return self.shell.display_formatter.format(result)
157 return self.shell.display_formatter.format(result)
158
158
159 # This can be set to True by the write_output_prompt method in a subclass
159 # This can be set to True by the write_output_prompt method in a subclass
160 prompt_end_newline = False
160 prompt_end_newline = False
161
161
162 def write_format_data(self, format_dict, md_dict=None) -> None:
162 def write_format_data(self, format_dict, md_dict=None) -> None:
163 """Write the format data dict to the frontend.
163 """Write the format data dict to the frontend.
164
164
165 This default version of this method simply writes the plain text
165 This default version of this method simply writes the plain text
166 representation of the object to ``sys.stdout``. Subclasses should
166 representation of the object to ``sys.stdout``. Subclasses should
167 override this method to send the entire `format_dict` to the
167 override this method to send the entire `format_dict` to the
168 frontends.
168 frontends.
169
169
170 Parameters
170 Parameters
171 ----------
171 ----------
172 format_dict : dict
172 format_dict : dict
173 The format dict for the object passed to `sys.displayhook`.
173 The format dict for the object passed to `sys.displayhook`.
174 md_dict : dict (optional)
174 md_dict : dict (optional)
175 The metadata dict to be associated with the display data.
175 The metadata dict to be associated with the display data.
176 """
176 """
177 if 'text/plain' not in format_dict:
177 if 'text/plain' not in format_dict:
178 # nothing to do
178 # nothing to do
179 return
179 return
180 # We want to print because we want to always make sure we have a
180 # We want to print because we want to always make sure we have a
181 # newline, even if all the prompt separators are ''. This is the
181 # newline, even if all the prompt separators are ''. This is the
182 # standard IPython behavior.
182 # standard IPython behavior.
183 result_repr = format_dict['text/plain']
183 result_repr = format_dict['text/plain']
184 if '\n' in result_repr:
184 if '\n' in result_repr:
185 # So that multi-line strings line up with the left column of
185 # So that multi-line strings line up with the left column of
186 # the screen, instead of having the output prompt mess up
186 # the screen, instead of having the output prompt mess up
187 # their first line.
187 # their first line.
188 # We use the prompt template instead of the expanded prompt
188 # We use the prompt template instead of the expanded prompt
189 # because the expansion may add ANSI escapes that will interfere
189 # because the expansion may add ANSI escapes that will interfere
190 # with our ability to determine whether or not we should add
190 # with our ability to determine whether or not we should add
191 # a newline.
191 # a newline.
192 if not self.prompt_end_newline:
192 if not self.prompt_end_newline:
193 # But avoid extraneous empty lines.
193 # But avoid extraneous empty lines.
194 result_repr = '\n' + result_repr
194 result_repr = '\n' + result_repr
195
195
196 try:
196 try:
197 print(result_repr)
197 print(result_repr)
198 except UnicodeEncodeError:
198 except UnicodeEncodeError:
199 # If a character is not supported by the terminal encoding replace
199 # If a character is not supported by the terminal encoding replace
200 # it with its \u or \x representation
200 # it with its \u or \x representation
201 print(result_repr.encode(sys.stdout.encoding,'backslashreplace').decode(sys.stdout.encoding))
201 print(result_repr.encode(sys.stdout.encoding,'backslashreplace').decode(sys.stdout.encoding))
202
202
203 def update_user_ns(self, result):
203 def update_user_ns(self, result):
204 """Update user_ns with various things like _, __, _1, etc."""
204 """Update user_ns with various things like _, __, _1, etc."""
205
205
206 # Avoid recursive reference when displaying _oh/Out
206 # Avoid recursive reference when displaying _oh/Out
207 if self.cache_size and result is not self.shell.user_ns['_oh']:
207 if self.cache_size and result is not self.shell.user_ns['_oh']:
208 if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache:
208 if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache:
209 self.cull_cache()
209 self.cull_cache()
210
210
211 # Don't overwrite '_' and friends if '_' is in __builtin__
211 # Don't overwrite '_' and friends if '_' is in __builtin__
212 # (otherwise we cause buggy behavior for things like gettext). and
212 # (otherwise we cause buggy behavior for things like gettext). and
213 # do not overwrite _, __ or ___ if one of these has been assigned
213 # do not overwrite _, __ or ___ if one of these has been assigned
214 # by the user.
214 # by the user.
215 update_unders = True
215 update_unders = True
216 for unders in ['_'*i for i in range(1,4)]:
216 for unders in ['_'*i for i in range(1,4)]:
217 if unders not in self.shell.user_ns:
217 if not unders in self.shell.user_ns:
218 continue
218 continue
219 if getattr(self, unders) is not self.shell.user_ns.get(unders):
219 if getattr(self, unders) is not self.shell.user_ns.get(unders):
220 update_unders = False
220 update_unders = False
221
221
222 self.___ = self.__
222 self.___ = self.__
223 self.__ = self._
223 self.__ = self._
224 self._ = result
224 self._ = result
225
225
226 if ('_' not in builtin_mod.__dict__) and (update_unders):
226 if ('_' not in builtin_mod.__dict__) and (update_unders):
227 self.shell.push({'_':self._,
227 self.shell.push({'_':self._,
228 '__':self.__,
228 '__':self.__,
229 '___':self.___}, interactive=False)
229 '___':self.___}, interactive=False)
230
230
231 # hackish access to top-level namespace to create _1,_2... dynamically
231 # hackish access to top-level namespace to create _1,_2... dynamically
232 to_main = {}
232 to_main = {}
233 if self.do_full_cache:
233 if self.do_full_cache:
234 new_result = '_%s' % self.prompt_count
234 new_result = '_%s' % self.prompt_count
235 to_main[new_result] = result
235 to_main[new_result] = result
236 self.shell.push(to_main, interactive=False)
236 self.shell.push(to_main, interactive=False)
237 self.shell.user_ns['_oh'][self.prompt_count] = result
237 self.shell.user_ns['_oh'][self.prompt_count] = result
238
238
239 def fill_exec_result(self, result):
239 def fill_exec_result(self, result):
240 if self.exec_result is not None:
240 if self.exec_result is not None:
241 self.exec_result.result = result
241 self.exec_result.result = result
242
242
243 def log_output(self, format_dict):
243 def log_output(self, format_dict):
244 """Log the output."""
244 """Log the output."""
245 if 'text/plain' not in format_dict:
245 if 'text/plain' not in format_dict:
246 # nothing to do
246 # nothing to do
247 return
247 return
248 if self.shell.logger.log_output:
248 if self.shell.logger.log_output:
249 self.shell.logger.log_write(format_dict['text/plain'], 'output')
249 self.shell.logger.log_write(format_dict['text/plain'], 'output')
250 self.shell.history_manager.output_hist_reprs[self.prompt_count] = \
250 self.shell.history_manager.output_hist_reprs[self.prompt_count] = \
251 format_dict['text/plain']
251 format_dict['text/plain']
252
252
253 def finish_displayhook(self):
253 def finish_displayhook(self):
254 """Finish up all displayhook activities."""
254 """Finish up all displayhook activities."""
255 sys.stdout.write(self.shell.separate_out2)
255 sys.stdout.write(self.shell.separate_out2)
256 sys.stdout.flush()
256 sys.stdout.flush()
257
257
258 def __call__(self, result=None):
258 def __call__(self, result=None):
259 """Printing with history cache management.
259 """Printing with history cache management.
260
260
261 This is invoked every time the interpreter needs to print, and is
261 This is invoked every time the interpreter needs to print, and is
262 activated by setting the variable sys.displayhook to it.
262 activated by setting the variable sys.displayhook to it.
263 """
263 """
264 self.check_for_underscore()
264 self.check_for_underscore()
265 if result is not None and not self.quiet():
265 if result is not None and not self.quiet():
266 self.start_displayhook()
266 self.start_displayhook()
267 self.write_output_prompt()
267 self.write_output_prompt()
268 format_dict, md_dict = self.compute_format_data(result)
268 format_dict, md_dict = self.compute_format_data(result)
269 self.update_user_ns(result)
269 self.update_user_ns(result)
270 self.fill_exec_result(result)
270 self.fill_exec_result(result)
271 if format_dict:
271 if format_dict:
272 self.write_format_data(format_dict, md_dict)
272 self.write_format_data(format_dict, md_dict)
273 self.log_output(format_dict)
273 self.log_output(format_dict)
274 self.finish_displayhook()
274 self.finish_displayhook()
275
275
276 def cull_cache(self):
276 def cull_cache(self):
277 """Output cache is full, cull the oldest entries"""
277 """Output cache is full, cull the oldest entries"""
278 oh = self.shell.user_ns.get('_oh', {})
278 oh = self.shell.user_ns.get('_oh', {})
279 sz = len(oh)
279 sz = len(oh)
280 cull_count = max(int(sz * self.cull_fraction), 2)
280 cull_count = max(int(sz * self.cull_fraction), 2)
281 warn('Output cache limit (currently {sz} entries) hit.\n'
281 warn('Output cache limit (currently {sz} entries) hit.\n'
282 'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count))
282 'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count))
283
283
284 for i, n in enumerate(sorted(oh)):
284 for i, n in enumerate(sorted(oh)):
285 if i >= cull_count:
285 if i >= cull_count:
286 break
286 break
287 self.shell.user_ns.pop('_%i' % n, None)
287 self.shell.user_ns.pop('_%i' % n, None)
288 oh.pop(n, None)
288 oh.pop(n, None)
289
289
290
290
291 def flush(self):
291 def flush(self):
292 if not self.do_full_cache:
292 if not self.do_full_cache:
293 raise ValueError("You shouldn't have reached the cache flush "
293 raise ValueError("You shouldn't have reached the cache flush "
294 "if full caching is not enabled!")
294 "if full caching is not enabled!")
295 # delete auto-generated vars from global namespace
295 # delete auto-generated vars from global namespace
296
296
297 for n in range(1,self.prompt_count + 1):
297 for n in range(1,self.prompt_count + 1):
298 key = '_'+repr(n)
298 key = '_'+repr(n)
299 try:
299 try:
300 del self.shell.user_ns_hidden[key]
300 del self.shell.user_ns_hidden[key]
301 except KeyError:
301 except KeyError:
302 pass
302 pass
303 try:
303 try:
304 del self.shell.user_ns[key]
304 del self.shell.user_ns[key]
305 except KeyError:
305 except KeyError:
306 pass
306 pass
307 # In some embedded circumstances, the user_ns doesn't have the
307 # In some embedded circumstances, the user_ns doesn't have the
308 # '_oh' key set up.
308 # '_oh' key set up.
309 oh = self.shell.user_ns.get('_oh', None)
309 oh = self.shell.user_ns.get('_oh', None)
310 if oh is not None:
310 if oh is not None:
311 oh.clear()
311 oh.clear()
312
312
313 # Release our own references to objects:
313 # Release our own references to objects:
314 self._, self.__, self.___ = '', '', ''
314 self._, self.__, self.___ = '', '', ''
315
315
316 if '_' not in builtin_mod.__dict__:
316 if '_' not in builtin_mod.__dict__:
317 self.shell.user_ns.update({'_':self._,'__':self.__,'___':self.___})
317 self.shell.user_ns.update({'_':self._,'__':self.__,'___':self.___})
318 import gc
318 import gc
319 # TODO: Is this really needed?
319 # TODO: Is this really needed?
320 # IronPython blocks here forever
320 # IronPython blocks here forever
321 if sys.platform != "cli":
321 if sys.platform != "cli":
322 gc.collect()
322 gc.collect()
323
323
324
324
325 class CapturingDisplayHook(object):
325 class CapturingDisplayHook(object):
326 def __init__(self, shell, outputs=None):
326 def __init__(self, shell, outputs=None):
327 self.shell = shell
327 self.shell = shell
328 if outputs is None:
328 if outputs is None:
329 outputs = []
329 outputs = []
330 self.outputs = outputs
330 self.outputs = outputs
331
331
332 def __call__(self, result=None):
332 def __call__(self, result=None):
333 if result is None:
333 if result is None:
334 return
334 return
335 format_dict, md_dict = self.shell.display_formatter.format(result)
335 format_dict, md_dict = self.shell.display_formatter.format(result)
336 self.outputs.append({ 'data': format_dict, 'metadata': md_dict })
336 self.outputs.append({ 'data': format_dict, 'metadata': md_dict })
@@ -1,146 +1,149
1 """An interface for publishing rich data to frontends.
1 """An interface for publishing rich data to frontends.
2
2
3 There are two components of the display system:
3 There are two components of the display system:
4
4
5 * Display formatters, which take a Python object and compute the
5 * Display formatters, which take a Python object and compute the
6 representation of the object in various formats (text, HTML, SVG, etc.).
6 representation of the object in various formats (text, HTML, SVG, etc.).
7 * The display publisher that is used to send the representation data to the
7 * The display publisher that is used to send the representation data to the
8 various frontends.
8 various frontends.
9
9
10 This module defines the logic display publishing. The display publisher uses
10 This module defines the logic display publishing. The display publisher uses
11 the ``display_data`` message type that is defined in the IPython messaging
11 the ``display_data`` message type that is defined in the IPython messaging
12 spec.
12 spec.
13 """
13 """
14
14
15 # Copyright (c) IPython Development Team.
15 # Copyright (c) IPython Development Team.
16 # Distributed under the terms of the Modified BSD License.
16 # Distributed under the terms of the Modified BSD License.
17
17
18
18
19 import sys
19 import sys
20
20
21 from traitlets.config.configurable import Configurable
21 from traitlets.config.configurable import Configurable
22 from traitlets import List
22 from traitlets import List
23
23
24 # This used to be defined here - it is imported for backwards compatibility
25 from .display_functions import publish_display_data
26
24 import typing as t
27 import typing as t
25
28
26 # -----------------------------------------------------------------------------
29 # -----------------------------------------------------------------------------
27 # Main payload class
30 # Main payload class
28 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
29
32
30
33
31 class DisplayPublisher(Configurable):
34 class DisplayPublisher(Configurable):
32 """A traited class that publishes display data to frontends.
35 """A traited class that publishes display data to frontends.
33
36
34 Instances of this class are created by the main IPython object and should
37 Instances of this class are created by the main IPython object and should
35 be accessed there.
38 be accessed there.
36 """
39 """
37
40
38 def __init__(self, shell=None, *args, **kwargs):
41 def __init__(self, shell=None, *args, **kwargs):
39 self.shell = shell
42 self.shell = shell
40 super().__init__(*args, **kwargs)
43 super().__init__(*args, **kwargs)
41
44
42 def _validate_data(self, data, metadata=None):
45 def _validate_data(self, data, metadata=None):
43 """Validate the display data.
46 """Validate the display data.
44
47
45 Parameters
48 Parameters
46 ----------
49 ----------
47 data : dict
50 data : dict
48 The formata data dictionary.
51 The formata data dictionary.
49 metadata : dict
52 metadata : dict
50 Any metadata for the data.
53 Any metadata for the data.
51 """
54 """
52
55
53 if not isinstance(data, dict):
56 if not isinstance(data, dict):
54 raise TypeError('data must be a dict, got: %r' % data)
57 raise TypeError('data must be a dict, got: %r' % data)
55 if metadata is not None:
58 if metadata is not None:
56 if not isinstance(metadata, dict):
59 if not isinstance(metadata, dict):
57 raise TypeError('metadata must be a dict, got: %r' % data)
60 raise TypeError('metadata must be a dict, got: %r' % data)
58
61
59 # use * to indicate transient, update are keyword-only
62 # use * to indicate transient, update are keyword-only
60 def publish(self, data, metadata=None, source=None, *, transient=None, update=False, **kwargs) -> None:
63 def publish(self, data, metadata=None, source=None, *, transient=None, update=False, **kwargs) -> None:
61 """Publish data and metadata to all frontends.
64 """Publish data and metadata to all frontends.
62
65
63 See the ``display_data`` message in the messaging documentation for
66 See the ``display_data`` message in the messaging documentation for
64 more details about this message type.
67 more details about this message type.
65
68
66 The following MIME types are currently implemented:
69 The following MIME types are currently implemented:
67
70
68 * text/plain
71 * text/plain
69 * text/html
72 * text/html
70 * text/markdown
73 * text/markdown
71 * text/latex
74 * text/latex
72 * application/json
75 * application/json
73 * application/javascript
76 * application/javascript
74 * image/png
77 * image/png
75 * image/jpeg
78 * image/jpeg
76 * image/svg+xml
79 * image/svg+xml
77
80
78 Parameters
81 Parameters
79 ----------
82 ----------
80 data : dict
83 data : dict
81 A dictionary having keys that are valid MIME types (like
84 A dictionary having keys that are valid MIME types (like
82 'text/plain' or 'image/svg+xml') and values that are the data for
85 'text/plain' or 'image/svg+xml') and values that are the data for
83 that MIME type. The data itself must be a JSON'able data
86 that MIME type. The data itself must be a JSON'able data
84 structure. Minimally all data should have the 'text/plain' data,
87 structure. Minimally all data should have the 'text/plain' data,
85 which can be displayed by all frontends. If more than the plain
88 which can be displayed by all frontends. If more than the plain
86 text is given, it is up to the frontend to decide which
89 text is given, it is up to the frontend to decide which
87 representation to use.
90 representation to use.
88 metadata : dict
91 metadata : dict
89 A dictionary for metadata related to the data. This can contain
92 A dictionary for metadata related to the data. This can contain
90 arbitrary key, value pairs that frontends can use to interpret
93 arbitrary key, value pairs that frontends can use to interpret
91 the data. Metadata specific to each mime-type can be specified
94 the data. Metadata specific to each mime-type can be specified
92 in the metadata dict with the same mime-type keys as
95 in the metadata dict with the same mime-type keys as
93 the data itself.
96 the data itself.
94 source : str, deprecated
97 source : str, deprecated
95 Unused.
98 Unused.
96 transient : dict, keyword-only
99 transient : dict, keyword-only
97 A dictionary for transient data.
100 A dictionary for transient data.
98 Data in this dictionary should not be persisted as part of saving this output.
101 Data in this dictionary should not be persisted as part of saving this output.
99 Examples include 'display_id'.
102 Examples include 'display_id'.
100 update : bool, keyword-only, default: False
103 update : bool, keyword-only, default: False
101 If True, only update existing outputs with the same display_id,
104 If True, only update existing outputs with the same display_id,
102 rather than creating a new output.
105 rather than creating a new output.
103 """
106 """
104
107
105 handlers: t.Dict = {}
108 handlers: t.Dict = {}
106 if self.shell is not None:
109 if self.shell is not None:
107 handlers = getattr(self.shell, "mime_renderers", {})
110 handlers = getattr(self.shell, "mime_renderers", {})
108
111
109 for mime, handler in handlers.items():
112 for mime, handler in handlers.items():
110 if mime in data:
113 if mime in data:
111 handler(data[mime], metadata.get(mime, None))
114 handler(data[mime], metadata.get(mime, None))
112 return
115 return
113
116
114 if 'text/plain' in data:
117 if 'text/plain' in data:
115 print(data['text/plain'])
118 print(data['text/plain'])
116
119
117 def clear_output(self, wait=False):
120 def clear_output(self, wait=False):
118 """Clear the output of the cell receiving output."""
121 """Clear the output of the cell receiving output."""
119 print('\033[2K\r', end='')
122 print('\033[2K\r', end='')
120 sys.stdout.flush()
123 sys.stdout.flush()
121 print('\033[2K\r', end='')
124 print('\033[2K\r', end='')
122 sys.stderr.flush()
125 sys.stderr.flush()
123
126
124
127
125 class CapturingDisplayPublisher(DisplayPublisher):
128 class CapturingDisplayPublisher(DisplayPublisher):
126 """A DisplayPublisher that stores"""
129 """A DisplayPublisher that stores"""
127
130
128 outputs: List = List()
131 outputs: List = List()
129
132
130 def publish(
133 def publish(
131 self, data, metadata=None, source=None, *, transient=None, update=False
134 self, data, metadata=None, source=None, *, transient=None, update=False
132 ):
135 ):
133 self.outputs.append(
136 self.outputs.append(
134 {
137 {
135 "data": data,
138 "data": data,
136 "metadata": metadata,
139 "metadata": metadata,
137 "transient": transient,
140 "transient": transient,
138 "update": update,
141 "update": update,
139 }
142 }
140 )
143 )
141
144
142 def clear_output(self, wait=False):
145 def clear_output(self, wait=False):
143 super(CapturingDisplayPublisher, self).clear_output(wait)
146 super(CapturingDisplayPublisher, self).clear_output(wait)
144
147
145 # empty the list, *do not* reassign a new list
148 # empty the list, *do not* reassign a new list
146 self.outputs.clear()
149 self.outputs.clear()
@@ -1,132 +1,135
1 # encoding: utf-8
1 # encoding: utf-8
2 """A class for managing IPython extensions."""
2 """A class for managing IPython extensions."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 import os
8 import os.path
7 import sys
9 import sys
8 from importlib import import_module, reload
10 from importlib import import_module, reload
9
11
10 from traitlets.config.configurable import Configurable
12 from traitlets.config.configurable import Configurable
13 from IPython.utils.path import ensure_dir_exists
11 from traitlets import Instance
14 from traitlets import Instance
12
15
13
16
14 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
15 # Main class
18 # Main class
16 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
17
20
18 BUILTINS_EXTS = {"storemagic": False, "autoreload": False}
21 BUILTINS_EXTS = {"storemagic": False, "autoreload": False}
19
22
20
23
21 class ExtensionManager(Configurable):
24 class ExtensionManager(Configurable):
22 """A class to manage IPython extensions.
25 """A class to manage IPython extensions.
23
26
24 An IPython extension is an importable Python module that has
27 An IPython extension is an importable Python module that has
25 a function with the signature::
28 a function with the signature::
26
29
27 def load_ipython_extension(ipython):
30 def load_ipython_extension(ipython):
28 # Do things with ipython
31 # Do things with ipython
29
32
30 This function is called after your extension is imported and the
33 This function is called after your extension is imported and the
31 currently active :class:`InteractiveShell` instance is passed as
34 currently active :class:`InteractiveShell` instance is passed as
32 the only argument. You can do anything you want with IPython at
35 the only argument. You can do anything you want with IPython at
33 that point, including defining new magic and aliases, adding new
36 that point, including defining new magic and aliases, adding new
34 components, etc.
37 components, etc.
35
38
36 You can also optionally define an :func:`unload_ipython_extension(ipython)`
39 You can also optionally define an :func:`unload_ipython_extension(ipython)`
37 function, which will be called if the user unloads or reloads the extension.
40 function, which will be called if the user unloads or reloads the extension.
38 The extension manager will only call :func:`load_ipython_extension` again
41 The extension manager will only call :func:`load_ipython_extension` again
39 if the extension is reloaded.
42 if the extension is reloaded.
40
43
41 You can put your extension modules anywhere you want, as long as
44 You can put your extension modules anywhere you want, as long as
42 they can be imported by Python's standard import mechanism.
45 they can be imported by Python's standard import mechanism.
43 """
46 """
44
47
45 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
48 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
46
49
47 def __init__(self, shell=None, **kwargs):
50 def __init__(self, shell=None, **kwargs):
48 super(ExtensionManager, self).__init__(shell=shell, **kwargs)
51 super(ExtensionManager, self).__init__(shell=shell, **kwargs)
49 self.loaded = set()
52 self.loaded = set()
50
53
51 def load_extension(self, module_str: str):
54 def load_extension(self, module_str: str):
52 """Load an IPython extension by its module name.
55 """Load an IPython extension by its module name.
53
56
54 Returns the string "already loaded" if the extension is already loaded,
57 Returns the string "already loaded" if the extension is already loaded,
55 "no load function" if the module doesn't have a load_ipython_extension
58 "no load function" if the module doesn't have a load_ipython_extension
56 function, or None if it succeeded.
59 function, or None if it succeeded.
57 """
60 """
58 try:
61 try:
59 return self._load_extension(module_str)
62 return self._load_extension(module_str)
60 except ModuleNotFoundError:
63 except ModuleNotFoundError:
61 if module_str in BUILTINS_EXTS:
64 if module_str in BUILTINS_EXTS:
62 BUILTINS_EXTS[module_str] = True
65 BUILTINS_EXTS[module_str] = True
63 return self._load_extension("IPython.extensions." + module_str)
66 return self._load_extension("IPython.extensions." + module_str)
64 raise
67 raise
65
68
66 def _load_extension(self, module_str: str):
69 def _load_extension(self, module_str: str):
67 if module_str in self.loaded:
70 if module_str in self.loaded:
68 return "already loaded"
71 return "already loaded"
69
72
70 assert self.shell is not None
73 assert self.shell is not None
71
74
72 with self.shell.builtin_trap:
75 with self.shell.builtin_trap:
73 if module_str not in sys.modules:
76 if module_str not in sys.modules:
74 mod = import_module(module_str)
77 mod = import_module(module_str)
75 mod = sys.modules[module_str]
78 mod = sys.modules[module_str]
76 if self._call_load_ipython_extension(mod):
79 if self._call_load_ipython_extension(mod):
77 self.loaded.add(module_str)
80 self.loaded.add(module_str)
78 else:
81 else:
79 return "no load function"
82 return "no load function"
80
83
81 def unload_extension(self, module_str: str):
84 def unload_extension(self, module_str: str):
82 """Unload an IPython extension by its module name.
85 """Unload an IPython extension by its module name.
83
86
84 This function looks up the extension's name in ``sys.modules`` and
87 This function looks up the extension's name in ``sys.modules`` and
85 simply calls ``mod.unload_ipython_extension(self)``.
88 simply calls ``mod.unload_ipython_extension(self)``.
86
89
87 Returns the string "no unload function" if the extension doesn't define
90 Returns the string "no unload function" if the extension doesn't define
88 a function to unload itself, "not loaded" if the extension isn't loaded,
91 a function to unload itself, "not loaded" if the extension isn't loaded,
89 otherwise None.
92 otherwise None.
90 """
93 """
91 if BUILTINS_EXTS.get(module_str, False) is True:
94 if BUILTINS_EXTS.get(module_str, False) is True:
92 module_str = "IPython.extensions." + module_str
95 module_str = "IPython.extensions." + module_str
93 if module_str not in self.loaded:
96 if module_str not in self.loaded:
94 return "not loaded"
97 return "not loaded"
95
98
96 if module_str in sys.modules:
99 if module_str in sys.modules:
97 mod = sys.modules[module_str]
100 mod = sys.modules[module_str]
98 if self._call_unload_ipython_extension(mod):
101 if self._call_unload_ipython_extension(mod):
99 self.loaded.discard(module_str)
102 self.loaded.discard(module_str)
100 else:
103 else:
101 return "no unload function"
104 return "no unload function"
102
105
103 def reload_extension(self, module_str: str):
106 def reload_extension(self, module_str: str):
104 """Reload an IPython extension by calling reload.
107 """Reload an IPython extension by calling reload.
105
108
106 If the module has not been loaded before,
109 If the module has not been loaded before,
107 :meth:`InteractiveShell.load_extension` is called. Otherwise
110 :meth:`InteractiveShell.load_extension` is called. Otherwise
108 :func:`reload` is called and then the :func:`load_ipython_extension`
111 :func:`reload` is called and then the :func:`load_ipython_extension`
109 function of the module, if it exists is called.
112 function of the module, if it exists is called.
110 """
113 """
111
114
112 if BUILTINS_EXTS.get(module_str, False) is True:
115 if BUILTINS_EXTS.get(module_str, False) is True:
113 module_str = "IPython.extensions." + module_str
116 module_str = "IPython.extensions." + module_str
114
117
115 if (module_str in self.loaded) and (module_str in sys.modules):
118 if (module_str in self.loaded) and (module_str in sys.modules):
116 self.unload_extension(module_str)
119 self.unload_extension(module_str)
117 mod = sys.modules[module_str]
120 mod = sys.modules[module_str]
118 reload(mod)
121 reload(mod)
119 if self._call_load_ipython_extension(mod):
122 if self._call_load_ipython_extension(mod):
120 self.loaded.add(module_str)
123 self.loaded.add(module_str)
121 else:
124 else:
122 self.load_extension(module_str)
125 self.load_extension(module_str)
123
126
124 def _call_load_ipython_extension(self, mod):
127 def _call_load_ipython_extension(self, mod):
125 if hasattr(mod, 'load_ipython_extension'):
128 if hasattr(mod, 'load_ipython_extension'):
126 mod.load_ipython_extension(self.shell)
129 mod.load_ipython_extension(self.shell)
127 return True
130 return True
128
131
129 def _call_unload_ipython_extension(self, mod):
132 def _call_unload_ipython_extension(self, mod):
130 if hasattr(mod, 'unload_ipython_extension'):
133 if hasattr(mod, 'unload_ipython_extension'):
131 mod.unload_ipython_extension(self.shell)
134 mod.unload_ipython_extension(self.shell)
132 return True
135 return True
@@ -1,1140 +1,1141
1 """History related magics and functionality"""
1 """History related magics and functionality"""
2
2
3 from __future__ import annotations
3 from __future__ import annotations
4
4
5 # Copyright (c) IPython Development Team.
5 # Copyright (c) IPython Development Team.
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7
7
8
8
9 import atexit
9 import atexit
10 import datetime
10 import datetime
11 import re
11 import re
12
12
13
13
14 import threading
14 import threading
15 from pathlib import Path
15 from pathlib import Path
16
16
17 from decorator import decorator
17 from decorator import decorator
18 from traitlets import (
18 from traitlets import (
19 Any,
19 Any,
20 Bool,
20 Bool,
21 Dict,
21 Dict,
22 Instance,
22 Instance,
23 Integer,
23 Integer,
24 List,
24 List,
25 TraitError,
25 TraitError,
26 Unicode,
26 Unicode,
27 Union,
27 Union,
28 default,
28 default,
29 observe,
29 observe,
30 )
30 )
31 from traitlets.config.configurable import LoggingConfigurable
31 from traitlets.config.configurable import LoggingConfigurable
32
32
33 from IPython.paths import locate_profile
33 from IPython.paths import locate_profile
34 from IPython.utils.decorators import undoc
34 from IPython.utils.decorators import undoc
35 from typing import Iterable, Tuple, Optional, TYPE_CHECKING
35 from typing import Iterable, Tuple, Optional, TYPE_CHECKING
36 import typing
36 import typing
37 from warnings import warn
37 from warnings import warn
38
38
39 if TYPE_CHECKING:
39 if TYPE_CHECKING:
40 from IPython.core.interactiveshell import InteractiveShell
40 from IPython.core.interactiveshell import InteractiveShell
41 from IPython.config.Configuration import Configuration
41 from IPython.config.Configuration import Configuration
42
42
43 try:
43 try:
44 from sqlite3 import DatabaseError, OperationalError
44 from sqlite3 import DatabaseError, OperationalError
45 import sqlite3
45 import sqlite3
46
46
47 sqlite3_found = True
47 sqlite3_found = True
48 except ModuleNotFoundError:
48 except ModuleNotFoundError:
49 sqlite3_found = False
49 sqlite3_found = False
50
50
51 class DatabaseError(Exception): # type: ignore [no-redef]
51 class DatabaseError(Exception): # type: ignore [no-redef]
52 pass
52 pass
53
53
54 class OperationalError(Exception): # type: ignore [no-redef]
54 class OperationalError(Exception): # type: ignore [no-redef]
55 pass
55 pass
56
56
57
57
58 InOrInOut = typing.Union[str, Tuple[str, Optional[str]]]
58 InOrInOut = typing.Union[str, Tuple[str, Optional[str]]]
59
59
60 # -----------------------------------------------------------------------------
60 # -----------------------------------------------------------------------------
61 # Classes and functions
61 # Classes and functions
62 # -----------------------------------------------------------------------------
62 # -----------------------------------------------------------------------------
63
63
64
64
65 @undoc
65 @undoc
66 class DummyDB:
66 class DummyDB:
67 """Dummy DB that will act as a black hole for history.
67 """Dummy DB that will act as a black hole for history.
68
68
69 Only used in the absence of sqlite"""
69 Only used in the absence of sqlite"""
70
70
71 def execute(*args: typing.Any, **kwargs: typing.Any) -> typing.List:
71 def execute(*args: typing.Any, **kwargs: typing.Any) -> typing.List:
72 return []
72 return []
73
73
74 def commit(self, *args, **kwargs): # type: ignore [no-untyped-def]
74 def commit(self, *args, **kwargs): # type: ignore [no-untyped-def]
75 pass
75 pass
76
76
77 def __enter__(self, *args, **kwargs): # type: ignore [no-untyped-def]
77 def __enter__(self, *args, **kwargs): # type: ignore [no-untyped-def]
78 pass
78 pass
79
79
80 def __exit__(self, *args, **kwargs): # type: ignore [no-untyped-def]
80 def __exit__(self, *args, **kwargs): # type: ignore [no-untyped-def]
81 pass
81 pass
82
82
83
83
84 @decorator
84 @decorator
85 def only_when_enabled(f, self, *a, **kw): # type: ignore [no-untyped-def]
85 def only_when_enabled(f, self, *a, **kw): # type: ignore [no-untyped-def]
86 """Decorator: return an empty list in the absence of sqlite."""
86 """Decorator: return an empty list in the absence of sqlite."""
87 if not self.enabled:
87 if not self.enabled:
88 return []
88 return []
89 else:
89 else:
90 return f(self, *a, **kw)
90 return f(self, *a, **kw)
91
91
92
92
93 # use 16kB as threshold for whether a corrupt history db should be saved
93 # use 16kB as threshold for whether a corrupt history db should be saved
94 # that should be at least 100 entries or so
94 # that should be at least 100 entries or so
95 _SAVE_DB_SIZE = 16384
95 _SAVE_DB_SIZE = 16384
96
96
97
97
98 @decorator
98 @decorator
99 def catch_corrupt_db(f, self, *a, **kw): # type: ignore [no-untyped-def]
99 def catch_corrupt_db(f, self, *a, **kw): # type: ignore [no-untyped-def]
100 """A decorator which wraps HistoryAccessor method calls to catch errors from
100 """A decorator which wraps HistoryAccessor method calls to catch errors from
101 a corrupt SQLite database, move the old database out of the way, and create
101 a corrupt SQLite database, move the old database out of the way, and create
102 a new one.
102 a new one.
103
103
104 We avoid clobbering larger databases because this may be triggered due to filesystem issues,
104 We avoid clobbering larger databases because this may be triggered due to filesystem issues,
105 not just a corrupt file.
105 not just a corrupt file.
106 """
106 """
107 try:
107 try:
108 return f(self, *a, **kw)
108 return f(self, *a, **kw)
109 except (DatabaseError, OperationalError) as e:
109 except (DatabaseError, OperationalError) as e:
110 self._corrupt_db_counter += 1
110 self._corrupt_db_counter += 1
111 self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e)
111 self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e)
112 if self.hist_file != ":memory:":
112 if self.hist_file != ":memory:":
113 if self._corrupt_db_counter > self._corrupt_db_limit:
113 if self._corrupt_db_counter > self._corrupt_db_limit:
114 self.hist_file = ":memory:"
114 self.hist_file = ":memory:"
115 self.log.error(
115 self.log.error(
116 "Failed to load history too many times, history will not be saved."
116 "Failed to load history too many times, history will not be saved."
117 )
117 )
118 elif self.hist_file.is_file():
118 elif self.hist_file.is_file():
119 # move the file out of the way
119 # move the file out of the way
120 base = str(self.hist_file.parent / self.hist_file.stem)
120 base = str(self.hist_file.parent / self.hist_file.stem)
121 ext = self.hist_file.suffix
121 ext = self.hist_file.suffix
122 size = self.hist_file.stat().st_size
122 size = self.hist_file.stat().st_size
123 if size >= _SAVE_DB_SIZE:
123 if size >= _SAVE_DB_SIZE:
124 # if there's significant content, avoid clobbering
124 # if there's significant content, avoid clobbering
125 now = datetime.datetime.now().isoformat().replace(":", ".")
125 now = datetime.datetime.now().isoformat().replace(":", ".")
126 newpath = base + "-corrupt-" + now + ext
126 newpath = base + "-corrupt-" + now + ext
127 # don't clobber previous corrupt backups
127 # don't clobber previous corrupt backups
128 for i in range(100):
128 for i in range(100):
129 if not Path(newpath).exists():
129 if not Path(newpath).exists():
130 break
130 break
131 else:
131 else:
132 newpath = base + "-corrupt-" + now + ("-%i" % i) + ext
132 newpath = base + "-corrupt-" + now + ("-%i" % i) + ext
133 else:
133 else:
134 # not much content, possibly empty; don't worry about clobbering
134 # not much content, possibly empty; don't worry about clobbering
135 # maybe we should just delete it?
135 # maybe we should just delete it?
136 newpath = base + "-corrupt" + ext
136 newpath = base + "-corrupt" + ext
137 self.hist_file.rename(newpath)
137 self.hist_file.rename(newpath)
138 self.log.error(
138 self.log.error(
139 "History file was moved to %s and a new file created.", newpath
139 "History file was moved to %s and a new file created.", newpath
140 )
140 )
141 self.init_db()
141 self.init_db()
142 return []
142 return []
143 else:
143 else:
144 # Failed with :memory:, something serious is wrong
144 # Failed with :memory:, something serious is wrong
145 raise
145 raise
146
146
147
147
148 class HistoryAccessorBase(LoggingConfigurable):
148 class HistoryAccessorBase(LoggingConfigurable):
149 """An abstract class for History Accessors"""
149 """An abstract class for History Accessors"""
150
150
151 def get_tail(
151 def get_tail(
152 self,
152 self,
153 n: int = 10,
153 n: int = 10,
154 raw: bool = True,
154 raw: bool = True,
155 output: bool = False,
155 output: bool = False,
156 include_latest: bool = False,
156 include_latest: bool = False,
157 ) -> Iterable[Tuple[int, int, InOrInOut]]:
157 ) -> Iterable[Tuple[int, int, InOrInOut]]:
158 raise NotImplementedError
158 raise NotImplementedError
159
159
160 def search(
160 def search(
161 self,
161 self,
162 pattern: str = "*",
162 pattern: str = "*",
163 raw: bool = True,
163 raw: bool = True,
164 search_raw: bool = True,
164 search_raw: bool = True,
165 output: bool = False,
165 output: bool = False,
166 n: Optional[int] = None,
166 n: Optional[int] = None,
167 unique: bool = False,
167 unique: bool = False,
168 ) -> Iterable[Tuple[int, int, InOrInOut]]:
168 ) -> Iterable[Tuple[int, int, InOrInOut]]:
169 raise NotImplementedError
169 raise NotImplementedError
170
170
171 def get_range(
171 def get_range(
172 self,
172 self,
173 session: int,
173 session: int,
174 start: int = 1,
174 start: int = 1,
175 stop: Optional[int] = None,
175 stop: Optional[int] = None,
176 raw: bool = True,
176 raw: bool = True,
177 output: bool = False,
177 output: bool = False,
178 ) -> Iterable[Tuple[int, int, InOrInOut]]:
178 ) -> Iterable[Tuple[int, int, InOrInOut]]:
179 raise NotImplementedError
179 raise NotImplementedError
180
180
181 def get_range_by_str(
181 def get_range_by_str(
182 self, rangestr: str, raw: bool = True, output: bool = False
182 self, rangestr: str, raw: bool = True, output: bool = False
183 ) -> Iterable[Tuple[int, int, InOrInOut]]:
183 ) -> Iterable[Tuple[int, int, InOrInOut]]:
184 raise NotImplementedError
184 raise NotImplementedError
185
185
186
186
187 class HistoryAccessor(HistoryAccessorBase):
187 class HistoryAccessor(HistoryAccessorBase):
188 """Access the history database without adding to it.
188 """Access the history database without adding to it.
189
189
190 This is intended for use by standalone history tools. IPython shells use
190 This is intended for use by standalone history tools. IPython shells use
191 HistoryManager, below, which is a subclass of this."""
191 HistoryManager, below, which is a subclass of this."""
192
192
193 # counter for init_db retries, so we don't keep trying over and over
193 # counter for init_db retries, so we don't keep trying over and over
194 _corrupt_db_counter = 0
194 _corrupt_db_counter = 0
195 # after two failures, fallback on :memory:
195 # after two failures, fallback on :memory:
196 _corrupt_db_limit = 2
196 _corrupt_db_limit = 2
197
197
198 # String holding the path to the history file
198 # String holding the path to the history file
199 hist_file = Union(
199 hist_file = Union(
200 [Instance(Path), Unicode()],
200 [Instance(Path), Unicode()],
201 help="""Path to file to use for SQLite history database.
201 help="""Path to file to use for SQLite history database.
202
202
203 By default, IPython will put the history database in the IPython
203 By default, IPython will put the history database in the IPython
204 profile directory. If you would rather share one history among
204 profile directory. If you would rather share one history among
205 profiles, you can set this value in each, so that they are consistent.
205 profiles, you can set this value in each, so that they are consistent.
206
206
207 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
207 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
208 mounts. If you see IPython hanging, try setting this to something on a
208 mounts. If you see IPython hanging, try setting this to something on a
209 local disk, e.g::
209 local disk, e.g::
210
210
211 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
211 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
212
212
213 you can also use the specific value `:memory:` (including the colon
213 you can also use the specific value `:memory:` (including the colon
214 at both end but not the back ticks), to avoid creating an history file.
214 at both end but not the back ticks), to avoid creating an history file.
215
215
216 """,
216 """,
217 ).tag(config=True)
217 ).tag(config=True)
218
218
219 enabled = Bool(
219 enabled = Bool(
220 sqlite3_found,
220 sqlite3_found,
221 help="""enable the SQLite history
221 help="""enable the SQLite history
222
222
223 set enabled=False to disable the SQLite history,
223 set enabled=False to disable the SQLite history,
224 in which case there will be no stored history, no SQLite connection,
224 in which case there will be no stored history, no SQLite connection,
225 and no background saving thread. This may be necessary in some
225 and no background saving thread. This may be necessary in some
226 threaded environments where IPython is embedded.
226 threaded environments where IPython is embedded.
227 """,
227 """,
228 ).tag(config=True)
228 ).tag(config=True)
229
229
230 connection_options = Dict(
230 connection_options = Dict(
231 help="""Options for configuring the SQLite connection
231 help="""Options for configuring the SQLite connection
232
232
233 These options are passed as keyword args to sqlite3.connect
233 These options are passed as keyword args to sqlite3.connect
234 when establishing database connections.
234 when establishing database connections.
235 """
235 """
236 ).tag(config=True)
236 ).tag(config=True)
237
237
238 @default("connection_options")
238 @default("connection_options")
239 def _default_connection_options(self) -> typing.Dict[str, bool]:
239 def _default_connection_options(self) -> typing.Dict[str, bool]:
240 return dict(check_same_thread=False)
240 return dict(check_same_thread=False)
241
241
242 # The SQLite database
242 # The SQLite database
243 db = Any()
243 db = Any()
244
244
245 @observe("db")
245 @observe("db")
246 @only_when_enabled
246 @only_when_enabled
247 def _db_changed(self, change): # type: ignore [no-untyped-def]
247 def _db_changed(self, change): # type: ignore [no-untyped-def]
248 """validate the db, since it can be an Instance of two different types"""
248 """validate the db, since it can be an Instance of two different types"""
249 new = change["new"]
249 new = change["new"]
250 connection_types = (DummyDB, sqlite3.Connection)
250 connection_types = (DummyDB, sqlite3.Connection)
251 if not isinstance(new, connection_types):
251 if not isinstance(new, connection_types):
252 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % (
252 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % (
253 self.__class__.__name__,
253 self.__class__.__name__,
254 new,
254 new,
255 )
255 )
256 raise TraitError(msg)
256 raise TraitError(msg)
257
257
258 def __init__(
258 def __init__(
259 self, profile: str = "default", hist_file: str = "", **traits: typing.Any
259 self, profile: str = "default", hist_file: str = "", **traits: typing.Any
260 ) -> None:
260 ) -> None:
261 """Create a new history accessor.
261 """Create a new history accessor.
262
262
263 Parameters
263 Parameters
264 ----------
264 ----------
265 profile : str
265 profile : str
266 The name of the profile from which to open history.
266 The name of the profile from which to open history.
267 hist_file : str
267 hist_file : str
268 Path to an SQLite history database stored by IPython. If specified,
268 Path to an SQLite history database stored by IPython. If specified,
269 hist_file overrides profile.
269 hist_file overrides profile.
270 config : :class:`~traitlets.config.loader.Config`
270 config : :class:`~traitlets.config.loader.Config`
271 Config object. hist_file can also be set through this.
271 Config object. hist_file can also be set through this.
272 """
272 """
273 super(HistoryAccessor, self).__init__(**traits)
273 super(HistoryAccessor, self).__init__(**traits)
274 # defer setting hist_file from kwarg until after init,
274 # defer setting hist_file from kwarg until after init,
275 # otherwise the default kwarg value would clobber any value
275 # otherwise the default kwarg value would clobber any value
276 # set by config
276 # set by config
277 if hist_file:
277 if hist_file:
278 self.hist_file = hist_file
278 self.hist_file = hist_file
279
279
280 try:
280 try:
281 self.hist_file
281 self.hist_file
282 except TraitError:
282 except TraitError:
283 # No one has set the hist_file, yet.
283 # No one has set the hist_file, yet.
284 self.hist_file = self._get_hist_file_name(profile)
284 self.hist_file = self._get_hist_file_name(profile)
285
285
286 self.init_db()
286 self.init_db()
287
287
288 def _get_hist_file_name(self, profile: str = "default") -> Path:
288 def _get_hist_file_name(self, profile: str = "default") -> Path:
289 """Find the history file for the given profile name.
289 """Find the history file for the given profile name.
290
290
291 This is overridden by the HistoryManager subclass, to use the shell's
291 This is overridden by the HistoryManager subclass, to use the shell's
292 active profile.
292 active profile.
293
293
294 Parameters
294 Parameters
295 ----------
295 ----------
296 profile : str
296 profile : str
297 The name of a profile which has a history file.
297 The name of a profile which has a history file.
298 """
298 """
299 return Path(locate_profile(profile)) / "history.sqlite"
299 return Path(locate_profile(profile)) / "history.sqlite"
300
300
301 @catch_corrupt_db
301 @catch_corrupt_db
302 def init_db(self) -> None:
302 def init_db(self) -> None:
303 """Connect to the database, and create tables if necessary."""
303 """Connect to the database, and create tables if necessary."""
304 if not self.enabled:
304 if not self.enabled:
305 self.db = DummyDB()
305 self.db = DummyDB()
306 return
306 return
307
307
308 # use detect_types so that timestamps return datetime objects
308 # use detect_types so that timestamps return datetime objects
309 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES)
309 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES)
310 kwargs.update(self.connection_options)
310 kwargs.update(self.connection_options)
311 self.db = sqlite3.connect(str(self.hist_file), **kwargs) # type: ignore [call-overload]
311 self.db = sqlite3.connect(str(self.hist_file), **kwargs) # type: ignore [call-overload]
312 with self.db:
312 with self.db:
313 self.db.execute(
313 self.db.execute(
314 """CREATE TABLE IF NOT EXISTS sessions (session integer
314 """CREATE TABLE IF NOT EXISTS sessions (session integer
315 primary key autoincrement, start timestamp,
315 primary key autoincrement, start timestamp,
316 end timestamp, num_cmds integer, remark text)"""
316 end timestamp, num_cmds integer, remark text)"""
317 )
317 )
318 self.db.execute(
318 self.db.execute(
319 """CREATE TABLE IF NOT EXISTS history
319 """CREATE TABLE IF NOT EXISTS history
320 (session integer, line integer, source text, source_raw text,
320 (session integer, line integer, source text, source_raw text,
321 PRIMARY KEY (session, line))"""
321 PRIMARY KEY (session, line))"""
322 )
322 )
323 # Output history is optional, but ensure the table's there so it can be
323 # Output history is optional, but ensure the table's there so it can be
324 # enabled later.
324 # enabled later.
325 self.db.execute(
325 self.db.execute(
326 """CREATE TABLE IF NOT EXISTS output_history
326 """CREATE TABLE IF NOT EXISTS output_history
327 (session integer, line integer, output text,
327 (session integer, line integer, output text,
328 PRIMARY KEY (session, line))"""
328 PRIMARY KEY (session, line))"""
329 )
329 )
330 # success! reset corrupt db count
330 # success! reset corrupt db count
331 self._corrupt_db_counter = 0
331 self._corrupt_db_counter = 0
332
332
333 def writeout_cache(self) -> None:
333 def writeout_cache(self) -> None:
334 """Overridden by HistoryManager to dump the cache before certain
334 """Overridden by HistoryManager to dump the cache before certain
335 database lookups."""
335 database lookups."""
336 pass
336 pass
337
337
338 ## -------------------------------
338 ## -------------------------------
339 ## Methods for retrieving history:
339 ## Methods for retrieving history:
340 ## -------------------------------
340 ## -------------------------------
341 def _run_sql(
341 def _run_sql(
342 self,
342 self,
343 sql: str,
343 sql: str,
344 params: typing.Tuple,
344 params: typing.Tuple,
345 raw: bool = True,
345 raw: bool = True,
346 output: bool = False,
346 output: bool = False,
347 latest: bool = False,
347 latest: bool = False,
348 ) -> Iterable[Tuple[int, int, InOrInOut]]:
348 ) -> Iterable[Tuple[int, int, InOrInOut]]:
349 """Prepares and runs an SQL query for the history database.
349 """Prepares and runs an SQL query for the history database.
350
350
351 Parameters
351 Parameters
352 ----------
352 ----------
353 sql : str
353 sql : str
354 Any filtering expressions to go after SELECT ... FROM ...
354 Any filtering expressions to go after SELECT ... FROM ...
355 params : tuple
355 params : tuple
356 Parameters passed to the SQL query (to replace "?")
356 Parameters passed to the SQL query (to replace "?")
357 raw, output : bool
357 raw, output : bool
358 See :meth:`get_range`
358 See :meth:`get_range`
359 latest : bool
359 latest : bool
360 Select rows with max (session, line)
360 Select rows with max (session, line)
361
361
362 Returns
362 Returns
363 -------
363 -------
364 Tuples as :meth:`get_range`
364 Tuples as :meth:`get_range`
365 """
365 """
366 toget = "source_raw" if raw else "source"
366 toget = "source_raw" if raw else "source"
367 sqlfrom = "history"
367 sqlfrom = "history"
368 if output:
368 if output:
369 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
369 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
370 toget = "history.%s, output_history.output" % toget
370 toget = "history.%s, output_history.output" % toget
371 if latest:
371 if latest:
372 toget += ", MAX(session * 128 * 1024 + line)"
372 toget += ", MAX(session * 128 * 1024 + line)"
373 this_querry = "SELECT session, line, %s FROM %s " % (toget, sqlfrom) + sql
373 this_querry = "SELECT session, line, %s FROM %s " % (toget, sqlfrom) + sql
374 cur = self.db.execute(this_querry, params)
374 cur = self.db.execute(this_querry, params)
375 if latest:
375 if latest:
376 cur = (row[:-1] for row in cur)
376 cur = (row[:-1] for row in cur)
377 if output: # Regroup into 3-tuples, and parse JSON
377 if output: # Regroup into 3-tuples, and parse JSON
378 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
378 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
379 return cur
379 return cur
380
380
381 @only_when_enabled
381 @only_when_enabled
382 @catch_corrupt_db
382 @catch_corrupt_db
383 def get_session_info(
383 def get_session_info(
384 self, session: int
384 self, session: int
385 ) -> Tuple[int, datetime.datetime, Optional[datetime.datetime], Optional[int], str]:
385 ) -> Tuple[int, datetime.datetime, Optional[datetime.datetime], Optional[int], str]:
386 """Get info about a session.
386 """Get info about a session.
387
387
388 Parameters
388 Parameters
389 ----------
389 ----------
390 session : int
390 session : int
391 Session number to retrieve.
391 Session number to retrieve.
392
392
393 Returns
393 Returns
394 -------
394 -------
395 session_id : int
395 session_id : int
396 Session ID number
396 Session ID number
397 start : datetime
397 start : datetime
398 Timestamp for the start of the session.
398 Timestamp for the start of the session.
399 end : datetime
399 end : datetime
400 Timestamp for the end of the session, or None if IPython crashed.
400 Timestamp for the end of the session, or None if IPython crashed.
401 num_cmds : int
401 num_cmds : int
402 Number of commands run, or None if IPython crashed.
402 Number of commands run, or None if IPython crashed.
403 remark : str
403 remark : str
404 A manually set description.
404 A manually set description.
405 """
405 """
406 query = "SELECT * from sessions where session == ?"
406 query = "SELECT * from sessions where session == ?"
407 return self.db.execute(query, (session,)).fetchone()
407 return self.db.execute(query, (session,)).fetchone()
408
408
409 @catch_corrupt_db
409 @catch_corrupt_db
410 def get_last_session_id(self) -> Optional[int]:
410 def get_last_session_id(self) -> Optional[int]:
411 """Get the last session ID currently in the database.
411 """Get the last session ID currently in the database.
412
412
413 Within IPython, this should be the same as the value stored in
413 Within IPython, this should be the same as the value stored in
414 :attr:`HistoryManager.session_number`.
414 :attr:`HistoryManager.session_number`.
415 """
415 """
416 for record in self.get_tail(n=1, include_latest=True):
416 for record in self.get_tail(n=1, include_latest=True):
417 return record[0]
417 return record[0]
418 return None
418 return None
419
419
420 @catch_corrupt_db
420 @catch_corrupt_db
421 def get_tail(
421 def get_tail(
422 self,
422 self,
423 n: int = 10,
423 n: int = 10,
424 raw: bool = True,
424 raw: bool = True,
425 output: bool = False,
425 output: bool = False,
426 include_latest: bool = False,
426 include_latest: bool = False,
427 ) -> Iterable[Tuple[int, int, InOrInOut]]:
427 ) -> Iterable[Tuple[int, int, InOrInOut]]:
428 """Get the last n lines from the history database.
428 """Get the last n lines from the history database.
429
429
430 Parameters
430 Parameters
431 ----------
431 ----------
432 n : int
432 n : int
433 The number of lines to get
433 The number of lines to get
434 raw, output : bool
434 raw, output : bool
435 See :meth:`get_range`
435 See :meth:`get_range`
436 include_latest : bool
436 include_latest : bool
437 If False (default), n+1 lines are fetched, and the latest one
437 If False (default), n+1 lines are fetched, and the latest one
438 is discarded. This is intended to be used where the function
438 is discarded. This is intended to be used where the function
439 is called by a user command, which it should not return.
439 is called by a user command, which it should not return.
440
440
441 Returns
441 Returns
442 -------
442 -------
443 Tuples as :meth:`get_range`
443 Tuples as :meth:`get_range`
444 """
444 """
445 self.writeout_cache()
445 self.writeout_cache()
446 if not include_latest:
446 if not include_latest:
447 n += 1
447 n += 1
448 cur = self._run_sql(
448 cur = self._run_sql(
449 "ORDER BY session DESC, line DESC LIMIT ?", (n,), raw=raw, output=output
449 "ORDER BY session DESC, line DESC LIMIT ?", (n,), raw=raw, output=output
450 )
450 )
451 if not include_latest:
451 if not include_latest:
452 return reversed(list(cur)[1:])
452 return reversed(list(cur)[1:])
453 return reversed(list(cur))
453 return reversed(list(cur))
454
454
455 @catch_corrupt_db
455 @catch_corrupt_db
456 def search(
456 def search(
457 self,
457 self,
458 pattern: str = "*",
458 pattern: str = "*",
459 raw: bool = True,
459 raw: bool = True,
460 search_raw: bool = True,
460 search_raw: bool = True,
461 output: bool = False,
461 output: bool = False,
462 n: Optional[int] = None,
462 n: Optional[int] = None,
463 unique: bool = False,
463 unique: bool = False,
464 ) -> Iterable[Tuple[int, int, InOrInOut]]:
464 ) -> Iterable[Tuple[int, int, InOrInOut]]:
465 """Search the database using unix glob-style matching (wildcards
465 """Search the database using unix glob-style matching (wildcards
466 * and ?).
466 * and ?).
467
467
468 Parameters
468 Parameters
469 ----------
469 ----------
470 pattern : str
470 pattern : str
471 The wildcarded pattern to match when searching
471 The wildcarded pattern to match when searching
472 search_raw : bool
472 search_raw : bool
473 If True, search the raw input, otherwise, the parsed input
473 If True, search the raw input, otherwise, the parsed input
474 raw, output : bool
474 raw, output : bool
475 See :meth:`get_range`
475 See :meth:`get_range`
476 n : None or int
476 n : None or int
477 If an integer is given, it defines the limit of
477 If an integer is given, it defines the limit of
478 returned entries.
478 returned entries.
479 unique : bool
479 unique : bool
480 When it is true, return only unique entries.
480 When it is true, return only unique entries.
481
481
482 Returns
482 Returns
483 -------
483 -------
484 Tuples as :meth:`get_range`
484 Tuples as :meth:`get_range`
485 """
485 """
486 tosearch = "source_raw" if search_raw else "source"
486 tosearch = "source_raw" if search_raw else "source"
487 if output:
487 if output:
488 tosearch = "history." + tosearch
488 tosearch = "history." + tosearch
489 self.writeout_cache()
489 self.writeout_cache()
490 sqlform = "WHERE %s GLOB ?" % tosearch
490 sqlform = "WHERE %s GLOB ?" % tosearch
491 params: typing.Tuple[typing.Any, ...] = (pattern,)
491 params: typing.Tuple[typing.Any, ...] = (pattern,)
492 if unique:
492 if unique:
493 sqlform += " GROUP BY {0}".format(tosearch)
493 sqlform += " GROUP BY {0}".format(tosearch)
494 if n is not None:
494 if n is not None:
495 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
495 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
496 params += (n,)
496 params += (n,)
497 elif unique:
497 elif unique:
498 sqlform += " ORDER BY session, line"
498 sqlform += " ORDER BY session, line"
499 cur = self._run_sql(sqlform, params, raw=raw, output=output, latest=unique)
499 cur = self._run_sql(sqlform, params, raw=raw, output=output, latest=unique)
500 if n is not None:
500 if n is not None:
501 return reversed(list(cur))
501 return reversed(list(cur))
502 return cur
502 return cur
503
503
504 @catch_corrupt_db
504 @catch_corrupt_db
505 def get_range(
505 def get_range(
506 self,
506 self,
507 session: int,
507 session: int,
508 start: int = 1,
508 start: int = 1,
509 stop: Optional[int] = None,
509 stop: Optional[int] = None,
510 raw: bool = True,
510 raw: bool = True,
511 output: bool = False,
511 output: bool = False,
512 ) -> Iterable[Tuple[int, int, InOrInOut]]:
512 ) -> Iterable[Tuple[int, int, InOrInOut]]:
513 """Retrieve input by session.
513 """Retrieve input by session.
514
514
515 Parameters
515 Parameters
516 ----------
516 ----------
517 session : int
517 session : int
518 Session number to retrieve.
518 Session number to retrieve.
519 start : int
519 start : int
520 First line to retrieve.
520 First line to retrieve.
521 stop : int
521 stop : int
522 End of line range (excluded from output itself). If None, retrieve
522 End of line range (excluded from output itself). If None, retrieve
523 to the end of the session.
523 to the end of the session.
524 raw : bool
524 raw : bool
525 If True, return untranslated input
525 If True, return untranslated input
526 output : bool
526 output : bool
527 If True, attempt to include output. This will be 'real' Python
527 If True, attempt to include output. This will be 'real' Python
528 objects for the current session, or text reprs from previous
528 objects for the current session, or text reprs from previous
529 sessions if db_log_output was enabled at the time. Where no output
529 sessions if db_log_output was enabled at the time. Where no output
530 is found, None is used.
530 is found, None is used.
531
531
532 Returns
532 Returns
533 -------
533 -------
534 entries
534 entries
535 An iterator over the desired lines. Each line is a 3-tuple, either
535 An iterator over the desired lines. Each line is a 3-tuple, either
536 (session, line, input) if output is False, or
536 (session, line, input) if output is False, or
537 (session, line, (input, output)) if output is True.
537 (session, line, (input, output)) if output is True.
538 """
538 """
539 params: typing.Tuple[typing.Any, ...]
539 params: typing.Tuple[typing.Any, ...]
540 if stop:
540 if stop:
541 lineclause = "line >= ? AND line < ?"
541 lineclause = "line >= ? AND line < ?"
542 params = (session, start, stop)
542 params = (session, start, stop)
543 else:
543 else:
544 lineclause = "line>=?"
544 lineclause = "line>=?"
545 params = (session, start)
545 params = (session, start)
546
546
547 return self._run_sql(
547 return self._run_sql(
548 "WHERE session==? AND %s" % lineclause, params, raw=raw, output=output
548 "WHERE session==? AND %s" % lineclause, params, raw=raw, output=output
549 )
549 )
550
550
551 def get_range_by_str(
551 def get_range_by_str(
552 self, rangestr: str, raw: bool = True, output: bool = False
552 self, rangestr: str, raw: bool = True, output: bool = False
553 ) -> Iterable[Tuple[int, int, InOrInOut]]:
553 ) -> Iterable[Tuple[int, int, InOrInOut]]:
554 """Get lines of history from a string of ranges, as used by magic
554 """Get lines of history from a string of ranges, as used by magic
555 commands %hist, %save, %macro, etc.
555 commands %hist, %save, %macro, etc.
556
556
557 Parameters
557 Parameters
558 ----------
558 ----------
559 rangestr : str
559 rangestr : str
560 A string specifying ranges, e.g. "5 ~2/1-4". If empty string is used,
560 A string specifying ranges, e.g. "5 ~2/1-4". If empty string is used,
561 this will return everything from current session's history.
561 this will return everything from current session's history.
562
562
563 See the documentation of :func:`%history` for the full details.
563 See the documentation of :func:`%history` for the full details.
564
564
565 raw, output : bool
565 raw, output : bool
566 As :meth:`get_range`
566 As :meth:`get_range`
567
567
568 Returns
568 Returns
569 -------
569 -------
570 Tuples as :meth:`get_range`
570 Tuples as :meth:`get_range`
571 """
571 """
572 for sess, s, e in extract_hist_ranges(rangestr):
572 for sess, s, e in extract_hist_ranges(rangestr):
573 for line in self.get_range(sess, s, e, raw=raw, output=output):
573 for line in self.get_range(sess, s, e, raw=raw, output=output):
574 yield line
574 yield line
575
575
576
576
577 class HistoryManager(HistoryAccessor):
577 class HistoryManager(HistoryAccessor):
578 """A class to organize all history-related functionality in one place."""
578 """A class to organize all history-related functionality in one place."""
579
579
580 # Public interface
580 # Public interface
581
581
582 # An instance of the IPython shell we are attached to
582 # An instance of the IPython shell we are attached to
583 shell = Instance(
583 shell = Instance(
584 "IPython.core.interactiveshell.InteractiveShellABC", allow_none=False
584 "IPython.core.interactiveshell.InteractiveShellABC", allow_none=False
585 )
585 )
586 # Lists to hold processed and raw history. These start with a blank entry
586 # Lists to hold processed and raw history. These start with a blank entry
587 # so that we can index them starting from 1
587 # so that we can index them starting from 1
588 input_hist_parsed = List([""])
588 input_hist_parsed = List([""])
589 input_hist_raw = List([""])
589 input_hist_raw = List([""])
590 # A list of directories visited during session
590 # A list of directories visited during session
591 dir_hist: List = List()
591 dir_hist: List = List()
592
592
593 @default("dir_hist")
593 @default("dir_hist")
594 def _dir_hist_default(self) -> typing.List[Path]:
594 def _dir_hist_default(self) -> typing.List[Path]:
595 try:
595 try:
596 return [Path.cwd()]
596 return [Path.cwd()]
597 except OSError:
597 except OSError:
598 return []
598 return []
599
599
600 # A dict of output history, keyed with ints from the shell's
600 # A dict of output history, keyed with ints from the shell's
601 # execution count.
601 # execution count.
602 output_hist = Dict()
602 output_hist = Dict()
603 # The text/plain repr of outputs.
603 # The text/plain repr of outputs.
604 output_hist_reprs: typing.Dict[int, str] = Dict() # type: ignore [assignment]
604 output_hist_reprs: typing.Dict[int, str] = Dict() # type: ignore [assignment]
605
605
606 # The number of the current session in the history database
606 # The number of the current session in the history database
607 session_number: int = Integer() # type: ignore [assignment]
607 session_number: int = Integer() # type: ignore [assignment]
608
608
609 db_log_output = Bool(
609 db_log_output = Bool(
610 False, help="Should the history database include output? (default: no)"
610 False, help="Should the history database include output? (default: no)"
611 ).tag(config=True)
611 ).tag(config=True)
612 db_cache_size = Integer(
612 db_cache_size = Integer(
613 0,
613 0,
614 help="Write to database every x commands (higher values save disk access & power).\n"
614 help="Write to database every x commands (higher values save disk access & power).\n"
615 "Values of 1 or less effectively disable caching.",
615 "Values of 1 or less effectively disable caching.",
616 ).tag(config=True)
616 ).tag(config=True)
617 # The input and output caches
617 # The input and output caches
618 db_input_cache: List[Tuple[int, str, str]] = List()
618 db_input_cache: List[Tuple[int, str, str]] = List()
619 db_output_cache: List[Tuple[int, str]] = List()
619 db_output_cache: List[Tuple[int, str]] = List()
620
620
621 # History saving in separate thread
621 # History saving in separate thread
622 save_thread = Instance("IPython.core.history.HistorySavingThread", allow_none=True)
622 save_thread = Instance("IPython.core.history.HistorySavingThread", allow_none=True)
623 save_flag = Instance(threading.Event, allow_none=False)
623 save_flag = Instance(threading.Event, allow_none=False)
624
624
625 # Private interface
625 # Private interface
626 # Variables used to store the three last inputs from the user. On each new
626 # Variables used to store the three last inputs from the user. On each new
627 # history update, we populate the user's namespace with these, shifted as
627 # history update, we populate the user's namespace with these, shifted as
628 # necessary.
628 # necessary.
629 _i00 = Unicode("")
629 _i00 = Unicode("")
630 _i = Unicode("")
630 _i = Unicode("")
631 _ii = Unicode("")
631 _ii = Unicode("")
632 _iii = Unicode("")
632 _iii = Unicode("")
633
633
634 # A regex matching all forms of the exit command, so that we don't store
634 # A regex matching all forms of the exit command, so that we don't store
635 # them in the history (it's annoying to rewind the first entry and land on
635 # them in the history (it's annoying to rewind the first entry and land on
636 # an exit call).
636 # an exit call).
637 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
637 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
638
638
639 def __init__(
639 def __init__(
640 self,
640 self,
641 shell: InteractiveShell,
641 shell: InteractiveShell,
642 config: Optional[Configuration] = None,
642 config: Optional[Configuration] = None,
643 **traits: typing.Any,
643 **traits: typing.Any,
644 ):
644 ):
645 """Create a new history manager associated with a shell instance."""
645 """Create a new history manager associated with a shell instance."""
646 super().__init__(shell=shell, config=config, **traits)
646 super().__init__(shell=shell, config=config, **traits)
647 self.save_flag = threading.Event()
647 self.save_flag = threading.Event()
648 self.db_input_cache_lock = threading.Lock()
648 self.db_input_cache_lock = threading.Lock()
649 self.db_output_cache_lock = threading.Lock()
649 self.db_output_cache_lock = threading.Lock()
650
650
651 try:
651 try:
652 self.new_session()
652 self.new_session()
653 except OperationalError:
653 except OperationalError:
654 self.log.error(
654 self.log.error(
655 "Failed to create history session in %s. History will not be saved.",
655 "Failed to create history session in %s. History will not be saved.",
656 self.hist_file,
656 self.hist_file,
657 exc_info=True,
657 exc_info=True,
658 )
658 )
659 self.hist_file = ":memory:"
659 self.hist_file = ":memory:"
660
660
661 if self.enabled and self.hist_file != ":memory:":
661 if self.enabled and self.hist_file != ":memory:":
662 self.save_thread = HistorySavingThread(self)
662 self.save_thread = HistorySavingThread(self)
663 try:
663 try:
664 self.save_thread.start()
664 self.save_thread.start()
665 except RuntimeError:
665 except RuntimeError:
666 self.log.error(
666 self.log.error(
667 "Failed to start history saving thread. History will not be saved.",
667 "Failed to start history saving thread. History will not be saved.",
668 exc_info=True,
668 exc_info=True,
669 )
669 )
670 self.hist_file = ":memory:"
670 self.hist_file = ":memory:"
671
671
672 def _get_hist_file_name(self, profile: Optional[str] = None) -> Path:
672 def _get_hist_file_name(self, profile: Optional[str] = None) -> Path:
673 """Get default history file name based on the Shell's profile.
673 """Get default history file name based on the Shell's profile.
674
674
675 The profile parameter is ignored, but must exist for compatibility with
675 The profile parameter is ignored, but must exist for compatibility with
676 the parent class."""
676 the parent class."""
677 profile_dir = self.shell.profile_dir.location
677 profile_dir = self.shell.profile_dir.location
678 return Path(profile_dir) / "history.sqlite"
678 return Path(profile_dir) / "history.sqlite"
679
679
680 @only_when_enabled
680 @only_when_enabled
681 def new_session(self, conn: Optional[sqlite3.Connection] = None) -> None:
681 def new_session(self, conn: Optional[sqlite3.Connection] = None) -> None:
682 """Get a new session number."""
682 """Get a new session number."""
683 if conn is None:
683 if conn is None:
684 conn = self.db
684 conn = self.db
685
685
686 with conn:
686 with conn:
687 cur = conn.execute(
687 cur = conn.execute(
688 """INSERT INTO sessions VALUES (NULL, ?, NULL,
688 """INSERT INTO sessions VALUES (NULL, ?, NULL,
689 NULL, '') """,
689 NULL, '') """,
690 (datetime.datetime.now().isoformat(" "),),
690 (datetime.datetime.now().isoformat(" "),),
691 )
691 )
692 assert isinstance(cur.lastrowid, int)
692 assert isinstance(cur.lastrowid, int)
693 self.session_number = cur.lastrowid
693 self.session_number = cur.lastrowid
694
694
695 def end_session(self) -> None:
695 def end_session(self) -> None:
696 """Close the database session, filling in the end time and line count."""
696 """Close the database session, filling in the end time and line count."""
697 self.writeout_cache()
697 self.writeout_cache()
698 with self.db:
698 with self.db:
699 self.db.execute(
699 self.db.execute(
700 """UPDATE sessions SET end=?, num_cmds=? WHERE
700 """UPDATE sessions SET end=?, num_cmds=? WHERE
701 session==?""",
701 session==?""",
702 (
702 (
703 datetime.datetime.now().isoformat(" "),
703 datetime.datetime.now().isoformat(" "),
704 len(self.input_hist_parsed) - 1,
704 len(self.input_hist_parsed) - 1,
705 self.session_number,
705 self.session_number,
706 ),
706 ),
707 )
707 )
708 self.session_number = 0
708 self.session_number = 0
709
709
710 def name_session(self, name: str) -> None:
710 def name_session(self, name: str) -> None:
711 """Give the current session a name in the history database."""
711 """Give the current session a name in the history database."""
712 warn(
712 warn(
713 "name_session is deprecated in IPython 9.0 and will be removed in future versions",
713 "name_session is deprecated in IPython 9.0 and will be removed in future versions",
714 DeprecationWarning,
714 DeprecationWarning,
715 stacklevel=2,
715 stacklevel=2,
716 )
716 )
717 with self.db:
717 with self.db:
718 self.db.execute(
718 self.db.execute(
719 "UPDATE sessions SET remark=? WHERE session==?",
719 "UPDATE sessions SET remark=? WHERE session==?",
720 (name, self.session_number),
720 (name, self.session_number),
721 )
721 )
722
722
723 def reset(self, new_session: bool = True) -> None:
723 def reset(self, new_session: bool = True) -> None:
724 """Clear the session history, releasing all object references, and
724 """Clear the session history, releasing all object references, and
725 optionally open a new session."""
725 optionally open a new session."""
726 self.output_hist.clear()
726 self.output_hist.clear()
727 # The directory history can't be completely empty
727 # The directory history can't be completely empty
728 self.dir_hist[:] = [Path.cwd()]
728 self.dir_hist[:] = [Path.cwd()]
729
729
730 if new_session:
730 if new_session:
731 if self.session_number:
731 if self.session_number:
732 self.end_session()
732 self.end_session()
733 self.input_hist_parsed[:] = [""]
733 self.input_hist_parsed[:] = [""]
734 self.input_hist_raw[:] = [""]
734 self.input_hist_raw[:] = [""]
735 self.new_session()
735 self.new_session()
736
736
737 # ------------------------------
737 # ------------------------------
738 # Methods for retrieving history
738 # Methods for retrieving history
739 # ------------------------------
739 # ------------------------------
740 def get_session_info(
740 def get_session_info(
741 self, session: int = 0
741 self, session: int = 0
742 ) -> Tuple[int, datetime.datetime, Optional[datetime.datetime], Optional[int], str]:
742 ) -> Tuple[int, datetime.datetime, Optional[datetime.datetime], Optional[int], str]:
743 """Get info about a session.
743 """Get info about a session.
744
744
745 Parameters
745 Parameters
746 ----------
746 ----------
747 session : int
747 session : int
748 Session number to retrieve. The current session is 0, and negative
748 Session number to retrieve. The current session is 0, and negative
749 numbers count back from current session, so -1 is the previous session.
749 numbers count back from current session, so -1 is the previous session.
750
750
751 Returns
751 Returns
752 -------
752 -------
753 session_id : int
753 session_id : int
754 Session ID number
754 Session ID number
755 start : datetime
755 start : datetime
756 Timestamp for the start of the session.
756 Timestamp for the start of the session.
757 end : datetime
757 end : datetime
758 Timestamp for the end of the session, or None if IPython crashed.
758 Timestamp for the end of the session, or None if IPython crashed.
759 num_cmds : int
759 num_cmds : int
760 Number of commands run, or None if IPython crashed.
760 Number of commands run, or None if IPython crashed.
761 remark : str
761 remark : str
762 A manually set description.
762 A manually set description.
763 """
763 """
764 if session <= 0:
764 if session <= 0:
765 session += self.session_number
765 session += self.session_number
766
766
767 return super(HistoryManager, self).get_session_info(session=session)
767 return super(HistoryManager, self).get_session_info(session=session)
768
768
769 @catch_corrupt_db
769 @catch_corrupt_db
770 def get_tail(
770 def get_tail(
771 self,
771 self,
772 n: int = 10,
772 n: int = 10,
773 raw: bool = True,
773 raw: bool = True,
774 output: bool = False,
774 output: bool = False,
775 include_latest: bool = False,
775 include_latest: bool = False,
776 ) -> Iterable[Tuple[int, int, InOrInOut]]:
776 ) -> Iterable[Tuple[int, int, InOrInOut]]:
777 """Get the last n lines from the history database.
777 """Get the last n lines from the history database.
778
778
779 Most recent entry last.
779 Most recent entry last.
780
780
781 Completion will be reordered so that that the last ones are when
781 Completion will be reordered so that that the last ones are when
782 possible from current session.
782 possible from current session.
783
783
784 Parameters
784 Parameters
785 ----------
785 ----------
786 n : int
786 n : int
787 The number of lines to get
787 The number of lines to get
788 raw, output : bool
788 raw, output : bool
789 See :meth:`get_range`
789 See :meth:`get_range`
790 include_latest : bool
790 include_latest : bool
791 If False (default), n+1 lines are fetched, and the latest one
791 If False (default), n+1 lines are fetched, and the latest one
792 is discarded. This is intended to be used where the function
792 is discarded. This is intended to be used where the function
793 is called by a user command, which it should not return.
793 is called by a user command, which it should not return.
794
794
795 Returns
795 Returns
796 -------
796 -------
797 Tuples as :meth:`get_range`
797 Tuples as :meth:`get_range`
798 """
798 """
799 self.writeout_cache()
799 self.writeout_cache()
800 if not include_latest:
800 if not include_latest:
801 n += 1
801 n += 1
802 # cursor/line/entry
802 # cursor/line/entry
803 this_cur = list(
803 this_cur = list(
804 self._run_sql(
804 self._run_sql(
805 "WHERE session == ? ORDER BY line DESC LIMIT ? ",
805 "WHERE session == ? ORDER BY line DESC LIMIT ? ",
806 (self.session_number, n),
806 (self.session_number, n),
807 raw=raw,
807 raw=raw,
808 output=output,
808 output=output,
809 )
809 )
810 )
810 )
811 other_cur = list(
811 other_cur = list(
812 self._run_sql(
812 self._run_sql(
813 "WHERE session != ? ORDER BY session DESC, line DESC LIMIT ?",
813 "WHERE session != ? ORDER BY session DESC, line DESC LIMIT ?",
814 (self.session_number, n),
814 (self.session_number, n),
815 raw=raw,
815 raw=raw,
816 output=output,
816 output=output,
817 )
817 )
818 )
818 )
819
819
820 everything: typing.List[Tuple[int, int, InOrInOut]] = this_cur + other_cur
820 everything: typing.List[Tuple[int, int, InOrInOut]] = this_cur + other_cur
821
821
822 everything = everything[:n]
822 everything = everything[:n]
823
823
824 if not include_latest:
824 if not include_latest:
825 return list(everything)[:0:-1]
825 return list(everything)[:0:-1]
826 return list(everything)[::-1]
826 return list(everything)[::-1]
827
827
828 def _get_range_session(
828 def _get_range_session(
829 self,
829 self,
830 start: int = 1,
830 start: int = 1,
831 stop: Optional[int] = None,
831 stop: Optional[int] = None,
832 raw: bool = True,
832 raw: bool = True,
833 output: bool = False,
833 output: bool = False,
834 ) -> Iterable[Tuple[int, int, InOrInOut]]:
834 ) -> Iterable[Tuple[int, int, InOrInOut]]:
835 """Get input and output history from the current session. Called by
835 """Get input and output history from the current session. Called by
836 get_range, and takes similar parameters."""
836 get_range, and takes similar parameters."""
837 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
837 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
838
838
839 n = len(input_hist)
839 n = len(input_hist)
840 if start < 0:
840 if start < 0:
841 start += n
841 start += n
842 if not stop or (stop > n):
842 if not stop or (stop > n):
843 stop = n
843 stop = n
844 elif stop < 0:
844 elif stop < 0:
845 stop += n
845 stop += n
846 line: InOrInOut
846 line: InOrInOut
847 for i in range(start, stop):
847 for i in range(start, stop):
848 if output:
848 if output:
849 line = (input_hist[i], self.output_hist_reprs.get(i))
849 line = (input_hist[i], self.output_hist_reprs.get(i))
850 else:
850 else:
851 line = input_hist[i]
851 line = input_hist[i]
852 yield (0, i, line)
852 yield (0, i, line)
853
853
854 def get_range(
854 def get_range(
855 self,
855 self,
856 session: int = 0,
856 session: int = 0,
857 start: int = 1,
857 start: int = 1,
858 stop: Optional[int] = None,
858 stop: Optional[int] = None,
859 raw: bool = True,
859 raw: bool = True,
860 output: bool = False,
860 output: bool = False,
861 ) -> Iterable[Tuple[int, int, InOrInOut]]:
861 ) -> Iterable[Tuple[int, int, InOrInOut]]:
862 """Retrieve input by session.
862 """Retrieve input by session.
863
863
864 Parameters
864 Parameters
865 ----------
865 ----------
866 session : int
866 session : int
867 Session number to retrieve. The current session is 0, and negative
867 Session number to retrieve. The current session is 0, and negative
868 numbers count back from current session, so -1 is previous session.
868 numbers count back from current session, so -1 is previous session.
869 start : int
869 start : int
870 First line to retrieve.
870 First line to retrieve.
871 stop : int
871 stop : int
872 End of line range (excluded from output itself). If None, retrieve
872 End of line range (excluded from output itself). If None, retrieve
873 to the end of the session.
873 to the end of the session.
874 raw : bool
874 raw : bool
875 If True, return untranslated input
875 If True, return untranslated input
876 output : bool
876 output : bool
877 If True, attempt to include output. This will be 'real' Python
877 If True, attempt to include output. This will be 'real' Python
878 objects for the current session, or text reprs from previous
878 objects for the current session, or text reprs from previous
879 sessions if db_log_output was enabled at the time. Where no output
879 sessions if db_log_output was enabled at the time. Where no output
880 is found, None is used.
880 is found, None is used.
881
881
882 Returns
882 Returns
883 -------
883 -------
884 entries
884 entries
885 An iterator over the desired lines. Each line is a 3-tuple, either
885 An iterator over the desired lines. Each line is a 3-tuple, either
886 (session, line, input) if output is False, or
886 (session, line, input) if output is False, or
887 (session, line, (input, output)) if output is True.
887 (session, line, (input, output)) if output is True.
888 """
888 """
889 if session <= 0:
889 if session <= 0:
890 session += self.session_number
890 session += self.session_number
891 if session == self.session_number: # Current session
891 if session == self.session_number: # Current session
892 return self._get_range_session(start, stop, raw, output)
892 return self._get_range_session(start, stop, raw, output)
893 return super(HistoryManager, self).get_range(session, start, stop, raw, output)
893 return super(HistoryManager, self).get_range(session, start, stop, raw, output)
894
894
895 ## ----------------------------
895 ## ----------------------------
896 ## Methods for storing history:
896 ## Methods for storing history:
897 ## ----------------------------
897 ## ----------------------------
898 def store_inputs(
898 def store_inputs(
899 self, line_num: int, source: str, source_raw: Optional[str] = None
899 self, line_num: int, source: str, source_raw: Optional[str] = None
900 ) -> None:
900 ) -> None:
901 """Store source and raw input in history and create input cache
901 """Store source and raw input in history and create input cache
902 variables ``_i*``.
902 variables ``_i*``.
903
903
904 Parameters
904 Parameters
905 ----------
905 ----------
906 line_num : int
906 line_num : int
907 The prompt number of this input.
907 The prompt number of this input.
908 source : str
908 source : str
909 Python input.
909 Python input.
910 source_raw : str, optional
910 source_raw : str, optional
911 If given, this is the raw input without any IPython transformations
911 If given, this is the raw input without any IPython transformations
912 applied to it. If not given, ``source`` is used.
912 applied to it. If not given, ``source`` is used.
913 """
913 """
914 if source_raw is None:
914 if source_raw is None:
915 source_raw = source
915 source_raw = source
916 source = source.rstrip("\n")
916 source = source.rstrip("\n")
917 source_raw = source_raw.rstrip("\n")
917 source_raw = source_raw.rstrip("\n")
918
918
919 # do not store exit/quit commands
919 # do not store exit/quit commands
920 if self._exit_re.match(source_raw.strip()):
920 if self._exit_re.match(source_raw.strip()):
921 return
921 return
922
922
923 self.input_hist_parsed.append(source)
923 self.input_hist_parsed.append(source)
924 self.input_hist_raw.append(source_raw)
924 self.input_hist_raw.append(source_raw)
925
925
926 with self.db_input_cache_lock:
926 with self.db_input_cache_lock:
927 self.db_input_cache.append((line_num, source, source_raw))
927 self.db_input_cache.append((line_num, source, source_raw))
928 # Trigger to flush cache and write to DB.
928 # Trigger to flush cache and write to DB.
929 if len(self.db_input_cache) >= self.db_cache_size:
929 if len(self.db_input_cache) >= self.db_cache_size:
930 self.save_flag.set()
930 self.save_flag.set()
931
931
932 # update the auto _i variables
932 # update the auto _i variables
933 self._iii = self._ii
933 self._iii = self._ii
934 self._ii = self._i
934 self._ii = self._i
935 self._i = self._i00
935 self._i = self._i00
936 self._i00 = source_raw
936 self._i00 = source_raw
937
937
938 # hackish access to user namespace to create _i1,_i2... dynamically
938 # hackish access to user namespace to create _i1,_i2... dynamically
939 new_i = "_i%s" % line_num
939 new_i = "_i%s" % line_num
940 to_main = {"_i": self._i, "_ii": self._ii, "_iii": self._iii, new_i: self._i00}
940 to_main = {"_i": self._i, "_ii": self._ii, "_iii": self._iii, new_i: self._i00}
941
941
942 if self.shell is not None:
942 if self.shell is not None:
943 self.shell.push(to_main, interactive=False)
943 self.shell.push(to_main, interactive=False)
944
944
945 def store_output(self, line_num: int) -> None:
945 def store_output(self, line_num: int) -> None:
946 """If database output logging is enabled, this saves all the
946 """If database output logging is enabled, this saves all the
947 outputs from the indicated prompt number to the database. It's
947 outputs from the indicated prompt number to the database. It's
948 called by run_cell after code has been executed.
948 called by run_cell after code has been executed.
949
949
950 Parameters
950 Parameters
951 ----------
951 ----------
952 line_num : int
952 line_num : int
953 The line number from which to save outputs
953 The line number from which to save outputs
954 """
954 """
955 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
955 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
956 return
956 return
957 lnum: int = line_num
957 output = self.output_hist_reprs[line_num]
958 output = self.output_hist_reprs[line_num]
958
959
959 with self.db_output_cache_lock:
960 with self.db_output_cache_lock:
960 self.db_output_cache.append((line_num, output))
961 self.db_output_cache.append((line_num, output))
961 if self.db_cache_size <= 1:
962 if self.db_cache_size <= 1:
962 self.save_flag.set()
963 self.save_flag.set()
963
964
964 def _writeout_input_cache(self, conn: sqlite3.Connection) -> None:
965 def _writeout_input_cache(self, conn: sqlite3.Connection) -> None:
965 with conn:
966 with conn:
966 for line in self.db_input_cache:
967 for line in self.db_input_cache:
967 conn.execute(
968 conn.execute(
968 "INSERT INTO history VALUES (?, ?, ?, ?)",
969 "INSERT INTO history VALUES (?, ?, ?, ?)",
969 (self.session_number,) + line,
970 (self.session_number,) + line,
970 )
971 )
971
972
972 def _writeout_output_cache(self, conn: sqlite3.Connection) -> None:
973 def _writeout_output_cache(self, conn: sqlite3.Connection) -> None:
973 with conn:
974 with conn:
974 for line in self.db_output_cache:
975 for line in self.db_output_cache:
975 conn.execute(
976 conn.execute(
976 "INSERT INTO output_history VALUES (?, ?, ?)",
977 "INSERT INTO output_history VALUES (?, ?, ?)",
977 (self.session_number,) + line,
978 (self.session_number,) + line,
978 )
979 )
979
980
980 @only_when_enabled
981 @only_when_enabled
981 def writeout_cache(self, conn: Optional[sqlite3.Connection] = None) -> None:
982 def writeout_cache(self, conn: Optional[sqlite3.Connection] = None) -> None:
982 """Write any entries in the cache to the database."""
983 """Write any entries in the cache to the database."""
983 if conn is None:
984 if conn is None:
984 conn = self.db
985 conn = self.db
985
986
986 with self.db_input_cache_lock:
987 with self.db_input_cache_lock:
987 try:
988 try:
988 self._writeout_input_cache(conn)
989 self._writeout_input_cache(conn)
989 except sqlite3.IntegrityError:
990 except sqlite3.IntegrityError:
990 self.new_session(conn)
991 self.new_session(conn)
991 print(
992 print(
992 "ERROR! Session/line number was not unique in",
993 "ERROR! Session/line number was not unique in",
993 "database. History logging moved to new session",
994 "database. History logging moved to new session",
994 self.session_number,
995 self.session_number,
995 )
996 )
996 try:
997 try:
997 # Try writing to the new session. If this fails, don't
998 # Try writing to the new session. If this fails, don't
998 # recurse
999 # recurse
999 self._writeout_input_cache(conn)
1000 self._writeout_input_cache(conn)
1000 except sqlite3.IntegrityError:
1001 except sqlite3.IntegrityError:
1001 pass
1002 pass
1002 finally:
1003 finally:
1003 self.db_input_cache = []
1004 self.db_input_cache = []
1004
1005
1005 with self.db_output_cache_lock:
1006 with self.db_output_cache_lock:
1006 try:
1007 try:
1007 self._writeout_output_cache(conn)
1008 self._writeout_output_cache(conn)
1008 except sqlite3.IntegrityError:
1009 except sqlite3.IntegrityError:
1009 print(
1010 print(
1010 "!! Session/line number for output was not unique",
1011 "!! Session/line number for output was not unique",
1011 "in database. Output will not be stored.",
1012 "in database. Output will not be stored.",
1012 )
1013 )
1013 finally:
1014 finally:
1014 self.db_output_cache = []
1015 self.db_output_cache = []
1015
1016
1016
1017
1017 class HistorySavingThread(threading.Thread):
1018 class HistorySavingThread(threading.Thread):
1018 """This thread takes care of writing history to the database, so that
1019 """This thread takes care of writing history to the database, so that
1019 the UI isn't held up while that happens.
1020 the UI isn't held up while that happens.
1020
1021
1021 It waits for the HistoryManager's save_flag to be set, then writes out
1022 It waits for the HistoryManager's save_flag to be set, then writes out
1022 the history cache. The main thread is responsible for setting the flag when
1023 the history cache. The main thread is responsible for setting the flag when
1023 the cache size reaches a defined threshold."""
1024 the cache size reaches a defined threshold."""
1024
1025
1025 daemon = True
1026 daemon = True
1026 stop_now = False
1027 stop_now = False
1027 enabled = True
1028 enabled = True
1028 history_manager: HistoryManager
1029 history_manager: HistoryManager
1029
1030
1030 def __init__(self, history_manager: HistoryManager) -> None:
1031 def __init__(self, history_manager: HistoryManager) -> None:
1031 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
1032 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
1032 self.history_manager = history_manager
1033 self.history_manager = history_manager
1033 self.enabled = history_manager.enabled
1034 self.enabled = history_manager.enabled
1034
1035
1035 @only_when_enabled
1036 @only_when_enabled
1036 def run(self) -> None:
1037 def run(self) -> None:
1037 atexit.register(self.stop)
1038 atexit.register(self.stop)
1038 # We need a separate db connection per thread:
1039 # We need a separate db connection per thread:
1039 try:
1040 try:
1040 self.db = sqlite3.connect(
1041 self.db = sqlite3.connect(
1041 str(self.history_manager.hist_file),
1042 str(self.history_manager.hist_file),
1042 **self.history_manager.connection_options,
1043 **self.history_manager.connection_options,
1043 )
1044 )
1044 while True:
1045 while True:
1045 self.history_manager.save_flag.wait()
1046 self.history_manager.save_flag.wait()
1046 if self.stop_now:
1047 if self.stop_now:
1047 self.db.close()
1048 self.db.close()
1048 return
1049 return
1049 self.history_manager.save_flag.clear()
1050 self.history_manager.save_flag.clear()
1050 self.history_manager.writeout_cache(self.db)
1051 self.history_manager.writeout_cache(self.db)
1051 except Exception as e:
1052 except Exception as e:
1052 print(
1053 print(
1053 (
1054 (
1054 "The history saving thread hit an unexpected error (%s)."
1055 "The history saving thread hit an unexpected error (%s)."
1055 "History will not be written to the database."
1056 "History will not be written to the database."
1056 )
1057 )
1057 % repr(e)
1058 % repr(e)
1058 )
1059 )
1059 finally:
1060 finally:
1060 atexit.unregister(self.stop)
1061 atexit.unregister(self.stop)
1061
1062
1062 def stop(self) -> None:
1063 def stop(self) -> None:
1063 """This can be called from the main thread to safely stop this thread.
1064 """This can be called from the main thread to safely stop this thread.
1064
1065
1065 Note that it does not attempt to write out remaining history before
1066 Note that it does not attempt to write out remaining history before
1066 exiting. That should be done by calling the HistoryManager's
1067 exiting. That should be done by calling the HistoryManager's
1067 end_session method."""
1068 end_session method."""
1068 self.stop_now = True
1069 self.stop_now = True
1069 self.history_manager.save_flag.set()
1070 self.history_manager.save_flag.set()
1070 self.join()
1071 self.join()
1071
1072
1072
1073
1073 # To match, e.g. ~5/8-~2/3
1074 # To match, e.g. ~5/8-~2/3
1074 range_re = re.compile(
1075 range_re = re.compile(
1075 r"""
1076 r"""
1076 ((?P<startsess>~?\d+)/)?
1077 ((?P<startsess>~?\d+)/)?
1077 (?P<start>\d+)?
1078 (?P<start>\d+)?
1078 ((?P<sep>[\-:])
1079 ((?P<sep>[\-:])
1079 ((?P<endsess>~?\d+)/)?
1080 ((?P<endsess>~?\d+)/)?
1080 (?P<end>\d+))?
1081 (?P<end>\d+))?
1081 $""",
1082 $""",
1082 re.VERBOSE,
1083 re.VERBOSE,
1083 )
1084 )
1084
1085
1085
1086
1086 def extract_hist_ranges(ranges_str: str) -> Iterable[Tuple[int, int, Optional[int]]]:
1087 def extract_hist_ranges(ranges_str: str) -> Iterable[Tuple[int, int, Optional[int]]]:
1087 """Turn a string of history ranges into 3-tuples of (session, start, stop).
1088 """Turn a string of history ranges into 3-tuples of (session, start, stop).
1088
1089
1089 Empty string results in a `[(0, 1, None)]`, i.e. "everything from current
1090 Empty string results in a `[(0, 1, None)]`, i.e. "everything from current
1090 session".
1091 session".
1091
1092
1092 Examples
1093 Examples
1093 --------
1094 --------
1094 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
1095 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
1095 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
1096 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
1096 """
1097 """
1097 if ranges_str == "":
1098 if ranges_str == "":
1098 yield (0, 1, None) # Everything from current session
1099 yield (0, 1, None) # Everything from current session
1099 return
1100 return
1100
1101
1101 for range_str in ranges_str.split():
1102 for range_str in ranges_str.split():
1102 rmatch = range_re.match(range_str)
1103 rmatch = range_re.match(range_str)
1103 if not rmatch:
1104 if not rmatch:
1104 continue
1105 continue
1105 start = rmatch.group("start")
1106 start = rmatch.group("start")
1106 if start:
1107 if start:
1107 start = int(start)
1108 start = int(start)
1108 end = rmatch.group("end")
1109 end = rmatch.group("end")
1109 # If no end specified, get (a, a + 1)
1110 # If no end specified, get (a, a + 1)
1110 end = int(end) if end else start + 1
1111 end = int(end) if end else start + 1
1111 else: # start not specified
1112 else: # start not specified
1112 if not rmatch.group("startsess"): # no startsess
1113 if not rmatch.group("startsess"): # no startsess
1113 continue
1114 continue
1114 start = 1
1115 start = 1
1115 end = None # provide the entire session hist
1116 end = None # provide the entire session hist
1116
1117
1117 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
1118 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
1118 assert end is not None
1119 assert end is not None
1119 end += 1
1120 end += 1
1120 startsess = rmatch.group("startsess") or "0"
1121 startsess = rmatch.group("startsess") or "0"
1121 endsess = rmatch.group("endsess") or startsess
1122 endsess = rmatch.group("endsess") or startsess
1122 startsess = int(startsess.replace("~", "-"))
1123 startsess = int(startsess.replace("~", "-"))
1123 endsess = int(endsess.replace("~", "-"))
1124 endsess = int(endsess.replace("~", "-"))
1124 assert endsess >= startsess, "start session must be earlier than end session"
1125 assert endsess >= startsess, "start session must be earlier than end session"
1125
1126
1126 if endsess == startsess:
1127 if endsess == startsess:
1127 yield (startsess, start, end)
1128 yield (startsess, start, end)
1128 continue
1129 continue
1129 # Multiple sessions in one range:
1130 # Multiple sessions in one range:
1130 yield (startsess, start, None)
1131 yield (startsess, start, None)
1131 for sess in range(startsess + 1, endsess):
1132 for sess in range(startsess + 1, endsess):
1132 yield (sess, 1, None)
1133 yield (sess, 1, None)
1133 yield (endsess, 1, end)
1134 yield (endsess, 1, end)
1134
1135
1135
1136
1136 def _format_lineno(session: int, line: int) -> str:
1137 def _format_lineno(session: int, line: int) -> str:
1137 """Helper function to format line numbers properly."""
1138 """Helper function to format line numbers properly."""
1138 if session == 0:
1139 if session == 0:
1139 return str(line)
1140 return str(line)
1140 return "%s#%s" % (session, line)
1141 return "%s#%s" % (session, line)
@@ -1,3984 +1,3985
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Main IPython class."""
2 """Main IPython class."""
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de>
5 # Copyright (C) 2001 Janko Hauser <jhauser@zscout.de>
6 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
6 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Copyright (C) 2008-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13
13
14 import abc
14 import abc
15 import ast
15 import ast
16 import atexit
16 import atexit
17 import bdb
17 import bdb
18 import builtins as builtin_mod
18 import builtins as builtin_mod
19 import functools
19 import functools
20 import inspect
20 import inspect
21 import os
21 import os
22 import re
22 import re
23 import runpy
23 import runpy
24 import shutil
24 import shutil
25 import subprocess
25 import subprocess
26 import sys
26 import sys
27 import tempfile
27 import tempfile
28 import traceback
28 import traceback
29 import types
29 import types
30 import warnings
30 import warnings
31 from ast import stmt
31 from ast import stmt
32 from io import open as io_open
32 from io import open as io_open
33 from logging import error
33 from logging import error
34 from pathlib import Path
34 from pathlib import Path
35 from typing import Callable
35 from typing import Callable
36 from typing import List as ListType, Any as AnyType
36 from typing import List as ListType, Dict as DictType, Any as AnyType
37 from typing import Optional, Sequence, Tuple
37 from typing import Optional, Sequence, Tuple
38 from warnings import warn
38 from warnings import warn
39
39
40 try:
40 try:
41 from pickleshare import PickleShareDB
41 from pickleshare import PickleShareDB
42 except ModuleNotFoundError:
42 except ModuleNotFoundError:
43
43
44 class PickleShareDB: # type: ignore [no-redef]
44 class PickleShareDB: # type: ignore [no-redef]
45 _mock = True
45 _mock = True
46
46
47 def __init__(self, path):
47 def __init__(self, path):
48 pass
48 pass
49
49
50 def get(self, key, default=None):
50 def get(self, key, default=None):
51 warn(
51 warn(
52 f"This is now an optional IPython functionality, using {key} requires you to install the `pickleshare` library.",
52 f"This is now an optional IPython functionality, using {key} requires you to install the `pickleshare` library.",
53 stacklevel=2,
53 stacklevel=2,
54 )
54 )
55 return default
55 return default
56
56
57 def __getitem__(self, key):
57 def __getitem__(self, key):
58 warn(
58 warn(
59 f"This is now an optional IPython functionality, using {key} requires you to install the `pickleshare` library.",
59 f"This is now an optional IPython functionality, using {key} requires you to install the `pickleshare` library.",
60 stacklevel=2,
60 stacklevel=2,
61 )
61 )
62 return None
62 return None
63
63
64 def __setitem__(self, key, value):
64 def __setitem__(self, key, value):
65 warn(
65 warn(
66 f"This is now an optional IPython functionality, setting {key} requires you to install the `pickleshare` library.",
66 f"This is now an optional IPython functionality, setting {key} requires you to install the `pickleshare` library.",
67 stacklevel=2,
67 stacklevel=2,
68 )
68 )
69
69
70 def __delitem__(self, key):
70 def __delitem__(self, key):
71 warn(
71 warn(
72 f"This is now an optional IPython functionality, deleting {key} requires you to install the `pickleshare` library.",
72 f"This is now an optional IPython functionality, deleting {key} requires you to install the `pickleshare` library.",
73 stacklevel=2,
73 stacklevel=2,
74 )
74 )
75
75
76
76
77 from tempfile import TemporaryDirectory
77 from tempfile import TemporaryDirectory
78 from traitlets import (
78 from traitlets import (
79 Any,
79 Any,
80 Bool,
80 Bool,
81 CaselessStrEnum,
81 CaselessStrEnum,
82 Dict,
82 Dict,
83 Enum,
83 Enum,
84 Instance,
84 Instance,
85 Integer,
85 Integer,
86 List,
86 List,
87 Type,
87 Type,
88 Unicode,
88 Unicode,
89 default,
89 default,
90 observe,
90 observe,
91 validate,
91 validate,
92 )
92 )
93 from traitlets.config.configurable import SingletonConfigurable
93 from traitlets.config.configurable import SingletonConfigurable
94 from traitlets.utils.importstring import import_item
94 from traitlets.utils.importstring import import_item
95
95
96 import IPython.core.hooks
96 import IPython.core.hooks
97 from IPython.core import magic, oinspect, page, prefilter, ultratb
97 from IPython.core import magic, oinspect, page, prefilter, ultratb
98 from IPython.core.alias import Alias, AliasManager
98 from IPython.core.alias import Alias, AliasManager
99 from IPython.core.autocall import ExitAutocall
99 from IPython.core.autocall import ExitAutocall
100 from IPython.core.builtin_trap import BuiltinTrap
100 from IPython.core.builtin_trap import BuiltinTrap
101 from IPython.core.compilerop import CachingCompiler
101 from IPython.core.compilerop import CachingCompiler
102 from IPython.core.debugger import InterruptiblePdb
102 from IPython.core.debugger import InterruptiblePdb
103 from IPython.core.display_trap import DisplayTrap
103 from IPython.core.display_trap import DisplayTrap
104 from IPython.core.displayhook import DisplayHook
104 from IPython.core.displayhook import DisplayHook
105 from IPython.core.displaypub import DisplayPublisher
105 from IPython.core.displaypub import DisplayPublisher
106 from IPython.core.error import InputRejected, UsageError
106 from IPython.core.error import InputRejected, UsageError
107 from IPython.core.events import EventManager, available_events
107 from IPython.core.events import EventManager, available_events
108 from IPython.core.extensions import ExtensionManager
108 from IPython.core.extensions import ExtensionManager
109 from IPython.core.formatters import DisplayFormatter
109 from IPython.core.formatters import DisplayFormatter
110 from IPython.core.history import HistoryManager
110 from IPython.core.history import HistoryManager
111 from IPython.core.inputtransformer2 import ESC_MAGIC, ESC_MAGIC2
111 from IPython.core.inputtransformer2 import ESC_MAGIC, ESC_MAGIC2
112 from IPython.core.logger import Logger
112 from IPython.core.logger import Logger
113 from IPython.core.macro import Macro
113 from IPython.core.macro import Macro
114 from IPython.core.payload import PayloadManager
114 from IPython.core.payload import PayloadManager
115 from IPython.core.prefilter import PrefilterManager
115 from IPython.core.prefilter import PrefilterManager
116 from IPython.core.profiledir import ProfileDir
116 from IPython.core.profiledir import ProfileDir
117 from IPython.core.usage import default_banner
117 from IPython.core.usage import default_banner
118 from IPython.display import display
118 from IPython.display import display
119 from IPython.paths import get_ipython_dir
119 from IPython.paths import get_ipython_dir
120 from IPython.testing.skipdoctest import skip_doctest
120 from IPython.testing.skipdoctest import skip_doctest
121 from IPython.utils import PyColorize, openpy, py3compat
121 from IPython.utils import PyColorize, io, openpy, py3compat
122 from IPython.utils.decorators import undoc
122 from IPython.utils.decorators import undoc
123 from IPython.utils.io import ask_yes_no
123 from IPython.utils.io import ask_yes_no
124 from IPython.utils.ipstruct import Struct
124 from IPython.utils.ipstruct import Struct
125 from IPython.utils.path import ensure_dir_exists, get_home_dir, get_py_filename
125 from IPython.utils.path import ensure_dir_exists, get_home_dir, get_py_filename
126 from IPython.utils.process import getoutput, system
126 from IPython.utils.process import getoutput, system
127 from IPython.utils.strdispatch import StrDispatch
127 from IPython.utils.strdispatch import StrDispatch
128 from IPython.utils.syspathcontext import prepended_to_syspath
128 from IPython.utils.syspathcontext import prepended_to_syspath
129 from IPython.utils.text import DollarFormatter, LSString, SList, format_screen
129 from IPython.utils.text import DollarFormatter, LSString, SList, format_screen
130 from IPython.core.oinspect import OInfo
130 from IPython.core.oinspect import OInfo
131
131
132 from ast import Module
133
134 # we still need to run things using the asyncio eventloop, but there is no
135 # async integration
136
137 from .async_helpers import (
138 _asyncio_runner,
139 _curio_runner,
140 _pseudo_sync_runner,
141 _should_be_async,
142 _trio_runner,
143 )
144
132
145 sphinxify: Optional[Callable]
133 sphinxify: Optional[Callable]
146
134
147 try:
135 try:
148 import docrepr.sphinxify as sphx
136 import docrepr.sphinxify as sphx
149
137
150 def sphinxify(oinfo):
138 def sphinxify(oinfo):
151 wrapped_docstring = sphx.wrap_main_docstring(oinfo)
139 wrapped_docstring = sphx.wrap_main_docstring(oinfo)
152
140
153 def sphinxify_docstring(docstring):
141 def sphinxify_docstring(docstring):
154 with TemporaryDirectory() as dirname:
142 with TemporaryDirectory() as dirname:
155 return {
143 return {
156 "text/html": sphx.sphinxify(wrapped_docstring, dirname),
144 "text/html": sphx.sphinxify(wrapped_docstring, dirname),
157 "text/plain": docstring,
145 "text/plain": docstring,
158 }
146 }
159
147
160 return sphinxify_docstring
148 return sphinxify_docstring
161 except ImportError:
149 except ImportError:
162 sphinxify = None
150 sphinxify = None
163
151
164
152
165 class ProvisionalWarning(DeprecationWarning):
153 class ProvisionalWarning(DeprecationWarning):
166 """
154 """
167 Warning class for unstable features
155 Warning class for unstable features
168 """
156 """
169 pass
157 pass
170
158
159 from ast import Module
171
160
172 _assign_nodes = (ast.AugAssign, ast.AnnAssign, ast.Assign)
161 _assign_nodes = (ast.AugAssign, ast.AnnAssign, ast.Assign)
173 _single_targets_nodes = (ast.AugAssign, ast.AnnAssign)
162 _single_targets_nodes = (ast.AugAssign, ast.AnnAssign)
174
163
175 #-----------------------------------------------------------------------------
164 #-----------------------------------------------------------------------------
165 # Await Helpers
166 #-----------------------------------------------------------------------------
167
168 # we still need to run things using the asyncio eventloop, but there is no
169 # async integration
170 from .async_helpers import (
171 _asyncio_runner,
172 _curio_runner,
173 _pseudo_sync_runner,
174 _should_be_async,
175 _trio_runner,
176 )
177
178 #-----------------------------------------------------------------------------
176 # Globals
179 # Globals
177 #-----------------------------------------------------------------------------
180 #-----------------------------------------------------------------------------
178
181
179 # compiled regexps for autoindent management
182 # compiled regexps for autoindent management
180 dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass')
183 dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass')
181
184
182 #-----------------------------------------------------------------------------
185 #-----------------------------------------------------------------------------
183 # Utilities
186 # Utilities
184 #-----------------------------------------------------------------------------
187 #-----------------------------------------------------------------------------
185
188
186
189
187 def is_integer_string(s: str):
190 def is_integer_string(s: str):
188 """
191 """
189 Variant of "str.isnumeric()" that allow negative values and other ints.
192 Variant of "str.isnumeric()" that allow negative values and other ints.
190 """
193 """
191 try:
194 try:
192 int(s)
195 int(s)
193 return True
196 return True
194 except ValueError:
197 except ValueError:
195 return False
198 return False
196 raise ValueError("Unexpected error")
199 raise ValueError("Unexpected error")
197
200
198
201
199 @undoc
202 @undoc
200 def softspace(file, newvalue):
203 def softspace(file, newvalue):
201 """Copied from code.py, to remove the dependency"""
204 """Copied from code.py, to remove the dependency"""
202
205
203 oldvalue = 0
206 oldvalue = 0
204 try:
207 try:
205 oldvalue = file.softspace
208 oldvalue = file.softspace
206 except AttributeError:
209 except AttributeError:
207 pass
210 pass
208 try:
211 try:
209 file.softspace = newvalue
212 file.softspace = newvalue
210 except (AttributeError, TypeError):
213 except (AttributeError, TypeError):
211 # "attribute-less object" or "read-only attributes"
214 # "attribute-less object" or "read-only attributes"
212 pass
215 pass
213 return oldvalue
216 return oldvalue
214
217
215 @undoc
218 @undoc
216 def no_op(*a, **kw):
219 def no_op(*a, **kw):
217 pass
220 pass
218
221
219
222
220 class SpaceInInput(Exception):
223 class SpaceInInput(Exception): pass
221 pass
222
224
223
225
224 class SeparateUnicode(Unicode):
226 class SeparateUnicode(Unicode):
225 r"""A Unicode subclass to validate separate_in, separate_out, etc.
227 r"""A Unicode subclass to validate separate_in, separate_out, etc.
226
228
227 This is a Unicode based trait that converts '0'->'' and ``'\\n'->'\n'``.
229 This is a Unicode based trait that converts '0'->'' and ``'\\n'->'\n'``.
228 """
230 """
229
231
230 def validate(self, obj, value):
232 def validate(self, obj, value):
231 if value == "0":
233 if value == '0': value = ''
232 value = ""
234 value = value.replace('\\n','\n')
233 value = value.replace("\\n", "\n")
234 return super(SeparateUnicode, self).validate(obj, value)
235 return super(SeparateUnicode, self).validate(obj, value)
235
236
236
237
237 @undoc
238 @undoc
238 class DummyMod(object):
239 class DummyMod(object):
239 """A dummy module used for IPython's interactive module when
240 """A dummy module used for IPython's interactive module when
240 a namespace must be assigned to the module's __dict__."""
241 a namespace must be assigned to the module's __dict__."""
241 __spec__ = None
242 __spec__ = None
242
243
243
244
244 class ExecutionInfo(object):
245 class ExecutionInfo(object):
245 """The arguments used for a call to :meth:`InteractiveShell.run_cell`
246 """The arguments used for a call to :meth:`InteractiveShell.run_cell`
246
247
247 Stores information about what is going to happen.
248 Stores information about what is going to happen.
248 """
249 """
249 raw_cell = None
250 raw_cell = None
250 store_history = False
251 store_history = False
251 silent = False
252 silent = False
252 shell_futures = True
253 shell_futures = True
253 cell_id = None
254 cell_id = None
254
255
255 def __init__(self, raw_cell, store_history, silent, shell_futures, cell_id):
256 def __init__(self, raw_cell, store_history, silent, shell_futures, cell_id):
256 self.raw_cell = raw_cell
257 self.raw_cell = raw_cell
257 self.store_history = store_history
258 self.store_history = store_history
258 self.silent = silent
259 self.silent = silent
259 self.shell_futures = shell_futures
260 self.shell_futures = shell_futures
260 self.cell_id = cell_id
261 self.cell_id = cell_id
261
262
262 def __repr__(self):
263 def __repr__(self):
263 name = self.__class__.__qualname__
264 name = self.__class__.__qualname__
264 raw_cell = (
265 raw_cell = (
265 (self.raw_cell[:50] + "..") if len(self.raw_cell) > 50 else self.raw_cell
266 (self.raw_cell[:50] + "..") if len(self.raw_cell) > 50 else self.raw_cell
266 )
267 )
267 return (
268 return (
268 '<%s object at %x, raw_cell="%s" store_history=%s silent=%s shell_futures=%s cell_id=%s>'
269 '<%s object at %x, raw_cell="%s" store_history=%s silent=%s shell_futures=%s cell_id=%s>'
269 % (
270 % (
270 name,
271 name,
271 id(self),
272 id(self),
272 raw_cell,
273 raw_cell,
273 self.store_history,
274 self.store_history,
274 self.silent,
275 self.silent,
275 self.shell_futures,
276 self.shell_futures,
276 self.cell_id,
277 self.cell_id,
277 )
278 )
278 )
279 )
279
280
280
281
281 class ExecutionResult:
282 class ExecutionResult:
282 """The result of a call to :meth:`InteractiveShell.run_cell`
283 """The result of a call to :meth:`InteractiveShell.run_cell`
283
284
284 Stores information about what took place.
285 Stores information about what took place.
285 """
286 """
286
287
287 execution_count: Optional[int] = None
288 execution_count: Optional[int] = None
288 error_before_exec: Optional[bool] = None
289 error_before_exec: Optional[bool] = None
289 error_in_exec: Optional[BaseException] = None
290 error_in_exec: Optional[BaseException] = None
290 info = None
291 info = None
291 result = None
292 result = None
292
293
293 def __init__(self, info):
294 def __init__(self, info):
294 self.info = info
295 self.info = info
295
296
296 @property
297 @property
297 def success(self):
298 def success(self):
298 return (self.error_before_exec is None) and (self.error_in_exec is None)
299 return (self.error_before_exec is None) and (self.error_in_exec is None)
299
300
300 def raise_error(self):
301 def raise_error(self):
301 """Reraises error if `success` is `False`, otherwise does nothing"""
302 """Reraises error if `success` is `False`, otherwise does nothing"""
302 if self.error_before_exec is not None:
303 if self.error_before_exec is not None:
303 raise self.error_before_exec
304 raise self.error_before_exec
304 if self.error_in_exec is not None:
305 if self.error_in_exec is not None:
305 raise self.error_in_exec
306 raise self.error_in_exec
306
307
307 def __repr__(self):
308 def __repr__(self):
308 name = self.__class__.__qualname__
309 name = self.__class__.__qualname__
309 return '<%s object at %x, execution_count=%s error_before_exec=%s error_in_exec=%s info=%s result=%s>' %\
310 return '<%s object at %x, execution_count=%s error_before_exec=%s error_in_exec=%s info=%s result=%s>' %\
310 (name, id(self), self.execution_count, self.error_before_exec, self.error_in_exec, repr(self.info), repr(self.result))
311 (name, id(self), self.execution_count, self.error_before_exec, self.error_in_exec, repr(self.info), repr(self.result))
311
312
312 @functools.wraps(io_open)
313 @functools.wraps(io_open)
313 def _modified_open(file, *args, **kwargs):
314 def _modified_open(file, *args, **kwargs):
314 if file in {0, 1, 2}:
315 if file in {0, 1, 2}:
315 raise ValueError(
316 raise ValueError(
316 f"IPython won't let you open fd={file} by default "
317 f"IPython won't let you open fd={file} by default "
317 "as it is likely to crash IPython. If you know what you are doing, "
318 "as it is likely to crash IPython. If you know what you are doing, "
318 "you can use builtins' open."
319 "you can use builtins' open."
319 )
320 )
320
321
321 return io_open(file, *args, **kwargs)
322 return io_open(file, *args, **kwargs)
322
323
323 class InteractiveShell(SingletonConfigurable):
324 class InteractiveShell(SingletonConfigurable):
324 """An enhanced, interactive shell for Python."""
325 """An enhanced, interactive shell for Python."""
325
326
326 _instance = None
327 _instance = None
327
328
328 ast_transformers: List[ast.NodeTransformer] = List(
329 ast_transformers: List[ast.NodeTransformer] = List(
329 [],
330 [],
330 help="""
331 help="""
331 A list of ast.NodeTransformer subclass instances, which will be applied
332 A list of ast.NodeTransformer subclass instances, which will be applied
332 to user input before code is run.
333 to user input before code is run.
333 """,
334 """,
334 ).tag(config=True)
335 ).tag(config=True)
335
336
336 autocall = Enum((0,1,2), default_value=0, help=
337 autocall = Enum((0,1,2), default_value=0, help=
337 """
338 """
338 Make IPython automatically call any callable object even if you didn't
339 Make IPython automatically call any callable object even if you didn't
339 type explicit parentheses. For example, 'str 43' becomes 'str(43)'
340 type explicit parentheses. For example, 'str 43' becomes 'str(43)'
340 automatically. The value can be '0' to disable the feature, '1' for
341 automatically. The value can be '0' to disable the feature, '1' for
341 'smart' autocall, where it is not applied if there are no more
342 'smart' autocall, where it is not applied if there are no more
342 arguments on the line, and '2' for 'full' autocall, where all callable
343 arguments on the line, and '2' for 'full' autocall, where all callable
343 objects are automatically called (even if no arguments are present).
344 objects are automatically called (even if no arguments are present).
344 """
345 """
345 ).tag(config=True)
346 ).tag(config=True)
346
347
347 autoindent = Bool(True, help=
348 autoindent = Bool(True, help=
348 """
349 """
349 Autoindent IPython code entered interactively.
350 Autoindent IPython code entered interactively.
350 """
351 """
351 ).tag(config=True)
352 ).tag(config=True)
352
353
353 autoawait = Bool(True, help=
354 autoawait = Bool(True, help=
354 """
355 """
355 Automatically run await statement in the top level repl.
356 Automatically run await statement in the top level repl.
356 """
357 """
357 ).tag(config=True)
358 ).tag(config=True)
358
359
359 loop_runner_map ={
360 loop_runner_map ={
360 'asyncio':(_asyncio_runner, True),
361 'asyncio':(_asyncio_runner, True),
361 'curio':(_curio_runner, True),
362 'curio':(_curio_runner, True),
362 'trio':(_trio_runner, True),
363 'trio':(_trio_runner, True),
363 'sync': (_pseudo_sync_runner, False)
364 'sync': (_pseudo_sync_runner, False)
364 }
365 }
365
366
366 loop_runner = Any(default_value="IPython.core.interactiveshell._asyncio_runner",
367 loop_runner = Any(default_value="IPython.core.interactiveshell._asyncio_runner",
367 allow_none=True,
368 allow_none=True,
368 help="""Select the loop runner that will be used to execute top-level asynchronous code"""
369 help="""Select the loop runner that will be used to execute top-level asynchronous code"""
369 ).tag(config=True)
370 ).tag(config=True)
370
371
371 @default('loop_runner')
372 @default('loop_runner')
372 def _default_loop_runner(self):
373 def _default_loop_runner(self):
373 return import_item("IPython.core.interactiveshell._asyncio_runner")
374 return import_item("IPython.core.interactiveshell._asyncio_runner")
374
375
375 @validate('loop_runner')
376 @validate('loop_runner')
376 def _import_runner(self, proposal):
377 def _import_runner(self, proposal):
377 if isinstance(proposal.value, str):
378 if isinstance(proposal.value, str):
378 if proposal.value in self.loop_runner_map:
379 if proposal.value in self.loop_runner_map:
379 runner, autoawait = self.loop_runner_map[proposal.value]
380 runner, autoawait = self.loop_runner_map[proposal.value]
380 self.autoawait = autoawait
381 self.autoawait = autoawait
381 return runner
382 return runner
382 runner = import_item(proposal.value)
383 runner = import_item(proposal.value)
383 if not callable(runner):
384 if not callable(runner):
384 raise ValueError('loop_runner must be callable')
385 raise ValueError('loop_runner must be callable')
385 return runner
386 return runner
386 if not callable(proposal.value):
387 if not callable(proposal.value):
387 raise ValueError('loop_runner must be callable')
388 raise ValueError('loop_runner must be callable')
388 return proposal.value
389 return proposal.value
389
390
390 automagic = Bool(True, help=
391 automagic = Bool(True, help=
391 """
392 """
392 Enable magic commands to be called without the leading %.
393 Enable magic commands to be called without the leading %.
393 """
394 """
394 ).tag(config=True)
395 ).tag(config=True)
395
396
396 banner1 = Unicode(default_banner,
397 banner1 = Unicode(default_banner,
397 help="""The part of the banner to be printed before the profile"""
398 help="""The part of the banner to be printed before the profile"""
398 ).tag(config=True)
399 ).tag(config=True)
399 banner2 = Unicode('',
400 banner2 = Unicode('',
400 help="""The part of the banner to be printed after the profile"""
401 help="""The part of the banner to be printed after the profile"""
401 ).tag(config=True)
402 ).tag(config=True)
402
403
403 cache_size = Integer(1000, help=
404 cache_size = Integer(1000, help=
404 """
405 """
405 Set the size of the output cache. The default is 1000, you can
406 Set the size of the output cache. The default is 1000, you can
406 change it permanently in your config file. Setting it to 0 completely
407 change it permanently in your config file. Setting it to 0 completely
407 disables the caching system, and the minimum value accepted is 3 (if
408 disables the caching system, and the minimum value accepted is 3 (if
408 you provide a value less than 3, it is reset to 0 and a warning is
409 you provide a value less than 3, it is reset to 0 and a warning is
409 issued). This limit is defined because otherwise you'll spend more
410 issued). This limit is defined because otherwise you'll spend more
410 time re-flushing a too small cache than working
411 time re-flushing a too small cache than working
411 """
412 """
412 ).tag(config=True)
413 ).tag(config=True)
413 color_info = Bool(True, help=
414 color_info = Bool(True, help=
414 """
415 """
415 Use colors for displaying information about objects. Because this
416 Use colors for displaying information about objects. Because this
416 information is passed through a pager (like 'less'), and some pagers
417 information is passed through a pager (like 'less'), and some pagers
417 get confused with color codes, this capability can be turned off.
418 get confused with color codes, this capability can be turned off.
418 """
419 """
419 ).tag(config=True)
420 ).tag(config=True)
420 colors = CaselessStrEnum(('Neutral', 'NoColor','LightBG','Linux'),
421 colors = CaselessStrEnum(('Neutral', 'NoColor','LightBG','Linux'),
421 default_value='Neutral',
422 default_value='Neutral',
422 help="Set the color scheme (NoColor, Neutral, Linux, or LightBG)."
423 help="Set the color scheme (NoColor, Neutral, Linux, or LightBG)."
423 ).tag(config=True)
424 ).tag(config=True)
424 debug = Bool(False).tag(config=True)
425 debug = Bool(False).tag(config=True)
425 disable_failing_post_execute = Bool(False,
426 disable_failing_post_execute = Bool(False,
426 help="Don't call post-execute functions that have failed in the past."
427 help="Don't call post-execute functions that have failed in the past."
427 ).tag(config=True)
428 ).tag(config=True)
428 display_formatter = Instance(DisplayFormatter, allow_none=True)
429 display_formatter = Instance(DisplayFormatter, allow_none=True)
429 displayhook_class = Type(DisplayHook)
430 displayhook_class = Type(DisplayHook)
430 display_pub_class = Type(DisplayPublisher)
431 display_pub_class = Type(DisplayPublisher)
431 compiler_class = Type(CachingCompiler)
432 compiler_class = Type(CachingCompiler)
432 inspector_class = Type(
433 inspector_class = Type(
433 oinspect.Inspector, help="Class to use to instantiate the shell inspector"
434 oinspect.Inspector, help="Class to use to instantiate the shell inspector"
434 ).tag(config=True)
435 ).tag(config=True)
435
436
436 sphinxify_docstring = Bool(False, help=
437 sphinxify_docstring = Bool(False, help=
437 """
438 """
438 Enables rich html representation of docstrings. (This requires the
439 Enables rich html representation of docstrings. (This requires the
439 docrepr module).
440 docrepr module).
440 """).tag(config=True)
441 """).tag(config=True)
441
442
442 @observe("sphinxify_docstring")
443 @observe("sphinxify_docstring")
443 def _sphinxify_docstring_changed(self, change):
444 def _sphinxify_docstring_changed(self, change):
444 if change['new']:
445 if change['new']:
445 warn("`sphinxify_docstring` is provisional since IPython 5.0 and might change in future versions." , ProvisionalWarning)
446 warn("`sphinxify_docstring` is provisional since IPython 5.0 and might change in future versions." , ProvisionalWarning)
446
447
447 enable_html_pager = Bool(False, help=
448 enable_html_pager = Bool(False, help=
448 """
449 """
449 (Provisional API) enables html representation in mime bundles sent
450 (Provisional API) enables html representation in mime bundles sent
450 to pagers.
451 to pagers.
451 """).tag(config=True)
452 """).tag(config=True)
452
453
453 @observe("enable_html_pager")
454 @observe("enable_html_pager")
454 def _enable_html_pager_changed(self, change):
455 def _enable_html_pager_changed(self, change):
455 if change['new']:
456 if change['new']:
456 warn("`enable_html_pager` is provisional since IPython 5.0 and might change in future versions.", ProvisionalWarning)
457 warn("`enable_html_pager` is provisional since IPython 5.0 and might change in future versions.", ProvisionalWarning)
457
458
458 data_pub_class = None
459 data_pub_class = None
459
460
460 exit_now = Bool(False)
461 exit_now = Bool(False)
461 exiter = Instance(ExitAutocall)
462 exiter = Instance(ExitAutocall)
462 @default('exiter')
463 @default('exiter')
463 def _exiter_default(self):
464 def _exiter_default(self):
464 return ExitAutocall(self)
465 return ExitAutocall(self)
465 # Monotonically increasing execution counter
466 # Monotonically increasing execution counter
466 execution_count = Integer(1)
467 execution_count = Integer(1)
467 filename = Unicode("<ipython console>")
468 filename = Unicode("<ipython console>")
468 ipython_dir= Unicode('').tag(config=True) # Set to get_ipython_dir() in __init__
469 ipython_dir= Unicode('').tag(config=True) # Set to get_ipython_dir() in __init__
469
470
470 # Used to transform cells before running them, and check whether code is complete
471 # Used to transform cells before running them, and check whether code is complete
471 input_transformer_manager = Instance('IPython.core.inputtransformer2.TransformerManager',
472 input_transformer_manager = Instance('IPython.core.inputtransformer2.TransformerManager',
472 ())
473 ())
473
474
474 @property
475 @property
475 def input_transformers_cleanup(self):
476 def input_transformers_cleanup(self):
476 return self.input_transformer_manager.cleanup_transforms
477 return self.input_transformer_manager.cleanup_transforms
477
478
478 input_transformers_post: List = List(
479 input_transformers_post: List = List(
479 [],
480 [],
480 help="A list of string input transformers, to be applied after IPython's "
481 help="A list of string input transformers, to be applied after IPython's "
481 "own input transformations."
482 "own input transformations."
482 )
483 )
483
484
484 @property
485 @property
485 def input_splitter(self):
486 def input_splitter(self):
486 """Make this available for backward compatibility (pre-7.0 release) with existing code.
487 """Make this available for backward compatibility (pre-7.0 release) with existing code.
487
488
488 For example, ipykernel ipykernel currently uses
489 For example, ipykernel ipykernel currently uses
489 `shell.input_splitter.check_complete`
490 `shell.input_splitter.check_complete`
490 """
491 """
491 from warnings import warn
492 from warnings import warn
492 warn("`input_splitter` is deprecated since IPython 7.0, prefer `input_transformer_manager`.",
493 warn("`input_splitter` is deprecated since IPython 7.0, prefer `input_transformer_manager`.",
493 DeprecationWarning, stacklevel=2
494 DeprecationWarning, stacklevel=2
494 )
495 )
495 return self.input_transformer_manager
496 return self.input_transformer_manager
496
497
497 logstart = Bool(False, help=
498 logstart = Bool(False, help=
498 """
499 """
499 Start logging to the default log file in overwrite mode.
500 Start logging to the default log file in overwrite mode.
500 Use `logappend` to specify a log file to **append** logs to.
501 Use `logappend` to specify a log file to **append** logs to.
501 """
502 """
502 ).tag(config=True)
503 ).tag(config=True)
503 logfile = Unicode('', help=
504 logfile = Unicode('', help=
504 """
505 """
505 The name of the logfile to use.
506 The name of the logfile to use.
506 """
507 """
507 ).tag(config=True)
508 ).tag(config=True)
508 logappend = Unicode('', help=
509 logappend = Unicode('', help=
509 """
510 """
510 Start logging to the given file in append mode.
511 Start logging to the given file in append mode.
511 Use `logfile` to specify a log file to **overwrite** logs to.
512 Use `logfile` to specify a log file to **overwrite** logs to.
512 """
513 """
513 ).tag(config=True)
514 ).tag(config=True)
514 object_info_string_level = Enum((0,1,2), default_value=0,
515 object_info_string_level = Enum((0,1,2), default_value=0,
515 ).tag(config=True)
516 ).tag(config=True)
516 pdb = Bool(False, help=
517 pdb = Bool(False, help=
517 """
518 """
518 Automatically call the pdb debugger after every exception.
519 Automatically call the pdb debugger after every exception.
519 """
520 """
520 ).tag(config=True)
521 ).tag(config=True)
521 display_page = Bool(False,
522 display_page = Bool(False,
522 help="""If True, anything that would be passed to the pager
523 help="""If True, anything that would be passed to the pager
523 will be displayed as regular output instead."""
524 will be displayed as regular output instead."""
524 ).tag(config=True)
525 ).tag(config=True)
525
526
526
527
527 show_rewritten_input = Bool(True,
528 show_rewritten_input = Bool(True,
528 help="Show rewritten input, e.g. for autocall."
529 help="Show rewritten input, e.g. for autocall."
529 ).tag(config=True)
530 ).tag(config=True)
530
531
531 quiet = Bool(False).tag(config=True)
532 quiet = Bool(False).tag(config=True)
532
533
533 history_length = Integer(10000,
534 history_length = Integer(10000,
534 help='Total length of command history'
535 help='Total length of command history'
535 ).tag(config=True)
536 ).tag(config=True)
536
537
537 history_load_length = Integer(1000, help=
538 history_load_length = Integer(1000, help=
538 """
539 """
539 The number of saved history entries to be loaded
540 The number of saved history entries to be loaded
540 into the history buffer at startup.
541 into the history buffer at startup.
541 """
542 """
542 ).tag(config=True)
543 ).tag(config=True)
543
544
544 ast_node_interactivity = Enum(['all', 'last', 'last_expr', 'none', 'last_expr_or_assign'],
545 ast_node_interactivity = Enum(['all', 'last', 'last_expr', 'none', 'last_expr_or_assign'],
545 default_value='last_expr',
546 default_value='last_expr',
546 help="""
547 help="""
547 'all', 'last', 'last_expr' or 'none', 'last_expr_or_assign' specifying
548 'all', 'last', 'last_expr' or 'none', 'last_expr_or_assign' specifying
548 which nodes should be run interactively (displaying output from expressions).
549 which nodes should be run interactively (displaying output from expressions).
549 """
550 """
550 ).tag(config=True)
551 ).tag(config=True)
551
552
552 warn_venv = Bool(
553 warn_venv = Bool(
553 True,
554 True,
554 help="Warn if running in a virtual environment with no IPython installed (so IPython from the global environment is used).",
555 help="Warn if running in a virtual environment with no IPython installed (so IPython from the global environment is used).",
555 ).tag(config=True)
556 ).tag(config=True)
556
557
557 # TODO: this part of prompt management should be moved to the frontends.
558 # TODO: this part of prompt management should be moved to the frontends.
558 # Use custom TraitTypes that convert '0'->'' and '\\n'->'\n'
559 # Use custom TraitTypes that convert '0'->'' and '\\n'->'\n'
559 separate_in = SeparateUnicode('\n').tag(config=True)
560 separate_in = SeparateUnicode('\n').tag(config=True)
560 separate_out = SeparateUnicode('').tag(config=True)
561 separate_out = SeparateUnicode('').tag(config=True)
561 separate_out2 = SeparateUnicode('').tag(config=True)
562 separate_out2 = SeparateUnicode('').tag(config=True)
562 wildcards_case_sensitive = Bool(True).tag(config=True)
563 wildcards_case_sensitive = Bool(True).tag(config=True)
563 xmode = CaselessStrEnum(('Context', 'Plain', 'Verbose', 'Minimal'),
564 xmode = CaselessStrEnum(('Context', 'Plain', 'Verbose', 'Minimal'),
564 default_value='Context',
565 default_value='Context',
565 help="Switch modes for the IPython exception handlers."
566 help="Switch modes for the IPython exception handlers."
566 ).tag(config=True)
567 ).tag(config=True)
567
568
568 # Subcomponents of InteractiveShell
569 # Subcomponents of InteractiveShell
569 alias_manager = Instance("IPython.core.alias.AliasManager", allow_none=True)
570 alias_manager = Instance("IPython.core.alias.AliasManager", allow_none=True)
570 prefilter_manager = Instance(
571 prefilter_manager = Instance(
571 "IPython.core.prefilter.PrefilterManager", allow_none=True
572 "IPython.core.prefilter.PrefilterManager", allow_none=True
572 )
573 )
573 builtin_trap = Instance("IPython.core.builtin_trap.BuiltinTrap")
574 builtin_trap = Instance("IPython.core.builtin_trap.BuiltinTrap")
574 display_trap = Instance("IPython.core.display_trap.DisplayTrap")
575 display_trap = Instance("IPython.core.display_trap.DisplayTrap")
575 extension_manager = Instance(
576 extension_manager = Instance(
576 "IPython.core.extensions.ExtensionManager", allow_none=True
577 "IPython.core.extensions.ExtensionManager", allow_none=True
577 )
578 )
578 payload_manager = Instance("IPython.core.payload.PayloadManager", allow_none=True)
579 payload_manager = Instance("IPython.core.payload.PayloadManager", allow_none=True)
579 history_manager = Instance(
580 history_manager = Instance(
580 "IPython.core.history.HistoryAccessorBase", allow_none=True
581 "IPython.core.history.HistoryAccessorBase", allow_none=True
581 )
582 )
582 magics_manager = Instance("IPython.core.magic.MagicsManager")
583 magics_manager = Instance("IPython.core.magic.MagicsManager")
583
584
584 profile_dir = Instance('IPython.core.application.ProfileDir', allow_none=True)
585 profile_dir = Instance('IPython.core.application.ProfileDir', allow_none=True)
585 @property
586 @property
586 def profile(self):
587 def profile(self):
587 if self.profile_dir is not None:
588 if self.profile_dir is not None:
588 name = os.path.basename(self.profile_dir.location)
589 name = os.path.basename(self.profile_dir.location)
589 return name.replace('profile_','')
590 return name.replace('profile_','')
590
591
591
592
592 # Private interface
593 # Private interface
593 _post_execute = Dict()
594 _post_execute = Dict()
594
595
595 # Tracks any GUI loop loaded for pylab
596 # Tracks any GUI loop loaded for pylab
596 pylab_gui_select = None
597 pylab_gui_select = None
597
598
598 last_execution_succeeded = Bool(True, help='Did last executed command succeeded')
599 last_execution_succeeded = Bool(True, help='Did last executed command succeeded')
599
600
600 last_execution_result = Instance('IPython.core.interactiveshell.ExecutionResult', help='Result of executing the last command', allow_none=True)
601 last_execution_result = Instance('IPython.core.interactiveshell.ExecutionResult', help='Result of executing the last command', allow_none=True)
601
602
602 def __init__(self, ipython_dir=None, profile_dir=None,
603 def __init__(self, ipython_dir=None, profile_dir=None,
603 user_module=None, user_ns=None,
604 user_module=None, user_ns=None,
604 custom_exceptions=((), None), **kwargs):
605 custom_exceptions=((), None), **kwargs):
605 # This is where traits with a config_key argument are updated
606 # This is where traits with a config_key argument are updated
606 # from the values on config.
607 # from the values on config.
607 super(InteractiveShell, self).__init__(**kwargs)
608 super(InteractiveShell, self).__init__(**kwargs)
608 if 'PromptManager' in self.config:
609 if 'PromptManager' in self.config:
609 warn('As of IPython 5.0 `PromptManager` config will have no effect'
610 warn('As of IPython 5.0 `PromptManager` config will have no effect'
610 ' and has been replaced by TerminalInteractiveShell.prompts_class')
611 ' and has been replaced by TerminalInteractiveShell.prompts_class')
611 self.configurables = [self]
612 self.configurables = [self]
612
613
613 # These are relatively independent and stateless
614 # These are relatively independent and stateless
614 self.init_ipython_dir(ipython_dir)
615 self.init_ipython_dir(ipython_dir)
615 self.init_profile_dir(profile_dir)
616 self.init_profile_dir(profile_dir)
616 self.init_instance_attrs()
617 self.init_instance_attrs()
617 self.init_environment()
618 self.init_environment()
618
619
619 # Check if we're in a virtualenv, and set up sys.path.
620 # Check if we're in a virtualenv, and set up sys.path.
620 self.init_virtualenv()
621 self.init_virtualenv()
621
622
622 # Create namespaces (user_ns, user_global_ns, etc.)
623 # Create namespaces (user_ns, user_global_ns, etc.)
623 self.init_create_namespaces(user_module, user_ns)
624 self.init_create_namespaces(user_module, user_ns)
624 # This has to be done after init_create_namespaces because it uses
625 # This has to be done after init_create_namespaces because it uses
625 # something in self.user_ns, but before init_sys_modules, which
626 # something in self.user_ns, but before init_sys_modules, which
626 # is the first thing to modify sys.
627 # is the first thing to modify sys.
627 # TODO: When we override sys.stdout and sys.stderr before this class
628 # TODO: When we override sys.stdout and sys.stderr before this class
628 # is created, we are saving the overridden ones here. Not sure if this
629 # is created, we are saving the overridden ones here. Not sure if this
629 # is what we want to do.
630 # is what we want to do.
630 self.save_sys_module_state()
631 self.save_sys_module_state()
631 self.init_sys_modules()
632 self.init_sys_modules()
632
633
633 # While we're trying to have each part of the code directly access what
634 # While we're trying to have each part of the code directly access what
634 # it needs without keeping redundant references to objects, we have too
635 # it needs without keeping redundant references to objects, we have too
635 # much legacy code that expects ip.db to exist.
636 # much legacy code that expects ip.db to exist.
636 self.db = PickleShareDB(os.path.join(self.profile_dir.location, 'db'))
637 self.db = PickleShareDB(os.path.join(self.profile_dir.location, 'db'))
637
638
638 self.init_history()
639 self.init_history()
639 self.init_encoding()
640 self.init_encoding()
640 self.init_prefilter()
641 self.init_prefilter()
641
642
642 self.init_syntax_highlighting()
643 self.init_syntax_highlighting()
643 self.init_hooks()
644 self.init_hooks()
644 self.init_events()
645 self.init_events()
645 self.init_pushd_popd_magic()
646 self.init_pushd_popd_magic()
646 self.init_user_ns()
647 self.init_user_ns()
647 self.init_logger()
648 self.init_logger()
648 self.init_builtins()
649 self.init_builtins()
649
650
650 # The following was in post_config_initialization
651 # The following was in post_config_initialization
651 self.init_inspector()
652 self.init_inspector()
652 self.raw_input_original = input
653 self.raw_input_original = input
653 self.init_completer()
654 self.init_completer()
654 # TODO: init_io() needs to happen before init_traceback handlers
655 # TODO: init_io() needs to happen before init_traceback handlers
655 # because the traceback handlers hardcode the stdout/stderr streams.
656 # because the traceback handlers hardcode the stdout/stderr streams.
656 # This logic in in debugger.Pdb and should eventually be changed.
657 # This logic in in debugger.Pdb and should eventually be changed.
657 self.init_io()
658 self.init_io()
658 self.init_traceback_handlers(custom_exceptions)
659 self.init_traceback_handlers(custom_exceptions)
659 self.init_prompts()
660 self.init_prompts()
660 self.init_display_formatter()
661 self.init_display_formatter()
661 self.init_display_pub()
662 self.init_display_pub()
662 self.init_data_pub()
663 self.init_data_pub()
663 self.init_displayhook()
664 self.init_displayhook()
664 self.init_magics()
665 self.init_magics()
665 self.init_alias()
666 self.init_alias()
666 self.init_logstart()
667 self.init_logstart()
667 self.init_pdb()
668 self.init_pdb()
668 self.init_extension_manager()
669 self.init_extension_manager()
669 self.init_payload()
670 self.init_payload()
670 self.events.trigger('shell_initialized', self)
671 self.events.trigger('shell_initialized', self)
671 atexit.register(self.atexit_operations)
672 atexit.register(self.atexit_operations)
672
673
673 # The trio runner is used for running Trio in the foreground thread. It
674 # The trio runner is used for running Trio in the foreground thread. It
674 # is different from `_trio_runner(async_fn)` in `async_helpers.py`
675 # is different from `_trio_runner(async_fn)` in `async_helpers.py`
675 # which calls `trio.run()` for every cell. This runner runs all cells
676 # which calls `trio.run()` for every cell. This runner runs all cells
676 # inside a single Trio event loop. If used, it is set from
677 # inside a single Trio event loop. If used, it is set from
677 # `ipykernel.kernelapp`.
678 # `ipykernel.kernelapp`.
678 self.trio_runner = None
679 self.trio_runner = None
679
680
680 def get_ipython(self):
681 def get_ipython(self):
681 """Return the currently running IPython instance."""
682 """Return the currently running IPython instance."""
682 return self
683 return self
683
684
684 #-------------------------------------------------------------------------
685 #-------------------------------------------------------------------------
685 # Trait changed handlers
686 # Trait changed handlers
686 #-------------------------------------------------------------------------
687 #-------------------------------------------------------------------------
687 @observe('ipython_dir')
688 @observe('ipython_dir')
688 def _ipython_dir_changed(self, change):
689 def _ipython_dir_changed(self, change):
689 ensure_dir_exists(change['new'])
690 ensure_dir_exists(change['new'])
690
691
691 def set_autoindent(self,value=None):
692 def set_autoindent(self,value=None):
692 """Set the autoindent flag.
693 """Set the autoindent flag.
693
694
694 If called with no arguments, it acts as a toggle."""
695 If called with no arguments, it acts as a toggle."""
695 if value is None:
696 if value is None:
696 self.autoindent = not self.autoindent
697 self.autoindent = not self.autoindent
697 else:
698 else:
698 self.autoindent = value
699 self.autoindent = value
699
700
700 def set_trio_runner(self, tr):
701 def set_trio_runner(self, tr):
701 self.trio_runner = tr
702 self.trio_runner = tr
702
703
703 #-------------------------------------------------------------------------
704 #-------------------------------------------------------------------------
704 # init_* methods called by __init__
705 # init_* methods called by __init__
705 #-------------------------------------------------------------------------
706 #-------------------------------------------------------------------------
706
707
707 def init_ipython_dir(self, ipython_dir):
708 def init_ipython_dir(self, ipython_dir):
708 if ipython_dir is not None:
709 if ipython_dir is not None:
709 self.ipython_dir = ipython_dir
710 self.ipython_dir = ipython_dir
710 return
711 return
711
712
712 self.ipython_dir = get_ipython_dir()
713 self.ipython_dir = get_ipython_dir()
713
714
714 def init_profile_dir(self, profile_dir):
715 def init_profile_dir(self, profile_dir):
715 if profile_dir is not None:
716 if profile_dir is not None:
716 self.profile_dir = profile_dir
717 self.profile_dir = profile_dir
717 return
718 return
718 self.profile_dir = ProfileDir.create_profile_dir_by_name(
719 self.profile_dir = ProfileDir.create_profile_dir_by_name(
719 self.ipython_dir, "default"
720 self.ipython_dir, "default"
720 )
721 )
721
722
722 def init_instance_attrs(self):
723 def init_instance_attrs(self):
723 self.more = False
724 self.more = False
724
725
725 # command compiler
726 # command compiler
726 self.compile = self.compiler_class()
727 self.compile = self.compiler_class()
727
728
728 # Make an empty namespace, which extension writers can rely on both
729 # Make an empty namespace, which extension writers can rely on both
729 # existing and NEVER being used by ipython itself. This gives them a
730 # existing and NEVER being used by ipython itself. This gives them a
730 # convenient location for storing additional information and state
731 # convenient location for storing additional information and state
731 # their extensions may require, without fear of collisions with other
732 # their extensions may require, without fear of collisions with other
732 # ipython names that may develop later.
733 # ipython names that may develop later.
733 self.meta = Struct()
734 self.meta = Struct()
734
735
735 # Temporary files used for various purposes. Deleted at exit.
736 # Temporary files used for various purposes. Deleted at exit.
736 # The files here are stored with Path from Pathlib
737 # The files here are stored with Path from Pathlib
737 self.tempfiles = []
738 self.tempfiles = []
738 self.tempdirs = []
739 self.tempdirs = []
739
740
740 # keep track of where we started running (mainly for crash post-mortem)
741 # keep track of where we started running (mainly for crash post-mortem)
741 # This is not being used anywhere currently.
742 # This is not being used anywhere currently.
742 self.starting_dir = os.getcwd()
743 self.starting_dir = os.getcwd()
743
744
744 # Indentation management
745 # Indentation management
745 self.indent_current_nsp = 0
746 self.indent_current_nsp = 0
746
747
747 # Dict to track post-execution functions that have been registered
748 # Dict to track post-execution functions that have been registered
748 self._post_execute = {}
749 self._post_execute = {}
749
750
750 def init_environment(self):
751 def init_environment(self):
751 """Any changes we need to make to the user's environment."""
752 """Any changes we need to make to the user's environment."""
752 pass
753 pass
753
754
754 def init_encoding(self):
755 def init_encoding(self):
755 # Get system encoding at startup time. Certain terminals (like Emacs
756 # Get system encoding at startup time. Certain terminals (like Emacs
756 # under Win32 have it set to None, and we need to have a known valid
757 # under Win32 have it set to None, and we need to have a known valid
757 # encoding to use in the raw_input() method
758 # encoding to use in the raw_input() method
758 try:
759 try:
759 self.stdin_encoding = sys.stdin.encoding or 'ascii'
760 self.stdin_encoding = sys.stdin.encoding or 'ascii'
760 except AttributeError:
761 except AttributeError:
761 self.stdin_encoding = 'ascii'
762 self.stdin_encoding = 'ascii'
762
763
763
764
764 @observe('colors')
765 @observe('colors')
765 def init_syntax_highlighting(self, changes=None):
766 def init_syntax_highlighting(self, changes=None):
766 # Python source parser/formatter for syntax highlighting
767 # Python source parser/formatter for syntax highlighting
767 pyformat = PyColorize.Parser(style=self.colors, parent=self).format
768 pyformat = PyColorize.Parser(style=self.colors, parent=self).format
768 self.pycolorize = lambda src: pyformat(src,'str')
769 self.pycolorize = lambda src: pyformat(src,'str')
769
770
770 def refresh_style(self):
771 def refresh_style(self):
771 # No-op here, used in subclass
772 # No-op here, used in subclass
772 pass
773 pass
773
774
774 def init_pushd_popd_magic(self):
775 def init_pushd_popd_magic(self):
775 # for pushd/popd management
776 # for pushd/popd management
776 self.home_dir = get_home_dir()
777 self.home_dir = get_home_dir()
777
778
778 self.dir_stack = []
779 self.dir_stack = []
779
780
780 def init_logger(self):
781 def init_logger(self):
781 self.logger = Logger(self.home_dir, logfname='ipython_log.py',
782 self.logger = Logger(self.home_dir, logfname='ipython_log.py',
782 logmode='rotate')
783 logmode='rotate')
783
784
784 def init_logstart(self):
785 def init_logstart(self):
785 """Initialize logging in case it was requested at the command line.
786 """Initialize logging in case it was requested at the command line.
786 """
787 """
787 if self.logappend:
788 if self.logappend:
788 self.magic('logstart %s append' % self.logappend)
789 self.magic('logstart %s append' % self.logappend)
789 elif self.logfile:
790 elif self.logfile:
790 self.magic('logstart %s' % self.logfile)
791 self.magic('logstart %s' % self.logfile)
791 elif self.logstart:
792 elif self.logstart:
792 self.magic('logstart')
793 self.magic('logstart')
793
794
794
795
795 def init_builtins(self):
796 def init_builtins(self):
796 # A single, static flag that we set to True. Its presence indicates
797 # A single, static flag that we set to True. Its presence indicates
797 # that an IPython shell has been created, and we make no attempts at
798 # that an IPython shell has been created, and we make no attempts at
798 # removing on exit or representing the existence of more than one
799 # removing on exit or representing the existence of more than one
799 # IPython at a time.
800 # IPython at a time.
800 builtin_mod.__dict__['__IPYTHON__'] = True
801 builtin_mod.__dict__['__IPYTHON__'] = True
801 builtin_mod.__dict__['display'] = display
802 builtin_mod.__dict__['display'] = display
802
803
803 self.builtin_trap = BuiltinTrap(shell=self)
804 self.builtin_trap = BuiltinTrap(shell=self)
804
805
805 @observe('colors')
806 @observe('colors')
806 def init_inspector(self, changes=None):
807 def init_inspector(self, changes=None):
807 # Object inspector
808 # Object inspector
808 self.inspector = self.inspector_class(
809 self.inspector = self.inspector_class(
809 oinspect.InspectColors,
810 oinspect.InspectColors,
810 PyColorize.ANSICodeColors,
811 PyColorize.ANSICodeColors,
811 self.colors,
812 self.colors,
812 self.object_info_string_level,
813 self.object_info_string_level,
813 )
814 )
814
815
815 def init_io(self):
816 def init_io(self):
816 # implemented in subclasses, TerminalInteractiveShell does call
817 # implemented in subclasses, TerminalInteractiveShell does call
817 # colorama.init().
818 # colorama.init().
818 pass
819 pass
819
820
820 def init_prompts(self):
821 def init_prompts(self):
821 # Set system prompts, so that scripts can decide if they are running
822 # Set system prompts, so that scripts can decide if they are running
822 # interactively.
823 # interactively.
823 sys.ps1 = 'In : '
824 sys.ps1 = 'In : '
824 sys.ps2 = '...: '
825 sys.ps2 = '...: '
825 sys.ps3 = 'Out: '
826 sys.ps3 = 'Out: '
826
827
827 def init_display_formatter(self):
828 def init_display_formatter(self):
828 self.display_formatter = DisplayFormatter(parent=self)
829 self.display_formatter = DisplayFormatter(parent=self)
829 self.configurables.append(self.display_formatter)
830 self.configurables.append(self.display_formatter)
830
831
831 def init_display_pub(self):
832 def init_display_pub(self):
832 self.display_pub = self.display_pub_class(parent=self, shell=self)
833 self.display_pub = self.display_pub_class(parent=self, shell=self)
833 self.configurables.append(self.display_pub)
834 self.configurables.append(self.display_pub)
834
835
835 def init_data_pub(self):
836 def init_data_pub(self):
836 if not self.data_pub_class:
837 if not self.data_pub_class:
837 self.data_pub = None
838 self.data_pub = None
838 return
839 return
839 self.data_pub = self.data_pub_class(parent=self)
840 self.data_pub = self.data_pub_class(parent=self)
840 self.configurables.append(self.data_pub)
841 self.configurables.append(self.data_pub)
841
842
842 def init_displayhook(self):
843 def init_displayhook(self):
843 # Initialize displayhook, set in/out prompts and printing system
844 # Initialize displayhook, set in/out prompts and printing system
844 self.displayhook = self.displayhook_class(
845 self.displayhook = self.displayhook_class(
845 parent=self,
846 parent=self,
846 shell=self,
847 shell=self,
847 cache_size=self.cache_size,
848 cache_size=self.cache_size,
848 )
849 )
849 self.configurables.append(self.displayhook)
850 self.configurables.append(self.displayhook)
850 # This is a context manager that installs/revmoes the displayhook at
851 # This is a context manager that installs/revmoes the displayhook at
851 # the appropriate time.
852 # the appropriate time.
852 self.display_trap = DisplayTrap(hook=self.displayhook)
853 self.display_trap = DisplayTrap(hook=self.displayhook)
853
854
854 @staticmethod
855 @staticmethod
855 def get_path_links(p: Path):
856 def get_path_links(p: Path):
856 """Gets path links including all symlinks
857 """Gets path links including all symlinks
857
858
858 Examples
859 Examples
859 --------
860 --------
860 In [1]: from IPython.core.interactiveshell import InteractiveShell
861 In [1]: from IPython.core.interactiveshell import InteractiveShell
861
862
862 In [2]: import sys, pathlib
863 In [2]: import sys, pathlib
863
864
864 In [3]: paths = InteractiveShell.get_path_links(pathlib.Path(sys.executable))
865 In [3]: paths = InteractiveShell.get_path_links(pathlib.Path(sys.executable))
865
866
866 In [4]: len(paths) == len(set(paths))
867 In [4]: len(paths) == len(set(paths))
867 Out[4]: True
868 Out[4]: True
868
869
869 In [5]: bool(paths)
870 In [5]: bool(paths)
870 Out[5]: True
871 Out[5]: True
871 """
872 """
872 paths = [p]
873 paths = [p]
873 while p.is_symlink():
874 while p.is_symlink():
874 new_path = Path(os.readlink(p))
875 new_path = Path(os.readlink(p))
875 if not new_path.is_absolute():
876 if not new_path.is_absolute():
876 new_path = p.parent / new_path
877 new_path = p.parent / new_path
877 p = new_path
878 p = new_path
878 paths.append(p)
879 paths.append(p)
879 return paths
880 return paths
880
881
881 def init_virtualenv(self):
882 def init_virtualenv(self):
882 """Add the current virtualenv to sys.path so the user can import modules from it.
883 """Add the current virtualenv to sys.path so the user can import modules from it.
883 This isn't perfect: it doesn't use the Python interpreter with which the
884 This isn't perfect: it doesn't use the Python interpreter with which the
884 virtualenv was built, and it ignores the --no-site-packages option. A
885 virtualenv was built, and it ignores the --no-site-packages option. A
885 warning will appear suggesting the user installs IPython in the
886 warning will appear suggesting the user installs IPython in the
886 virtualenv, but for many cases, it probably works well enough.
887 virtualenv, but for many cases, it probably works well enough.
887
888
888 Adapted from code snippets online.
889 Adapted from code snippets online.
889
890
890 http://blog.ufsoft.org/2009/1/29/ipython-and-virtualenv
891 http://blog.ufsoft.org/2009/1/29/ipython-and-virtualenv
891 """
892 """
892 if 'VIRTUAL_ENV' not in os.environ:
893 if 'VIRTUAL_ENV' not in os.environ:
893 # Not in a virtualenv
894 # Not in a virtualenv
894 return
895 return
895 elif os.environ["VIRTUAL_ENV"] == "":
896 elif os.environ["VIRTUAL_ENV"] == "":
896 warn("Virtual env path set to '', please check if this is intended.")
897 warn("Virtual env path set to '', please check if this is intended.")
897 return
898 return
898
899
899 p = Path(sys.executable)
900 p = Path(sys.executable)
900 p_venv = Path(os.environ["VIRTUAL_ENV"])
901 p_venv = Path(os.environ["VIRTUAL_ENV"])
901
902
902 # fallback venv detection:
903 # fallback venv detection:
903 # stdlib venv may symlink sys.executable, so we can't use realpath.
904 # stdlib venv may symlink sys.executable, so we can't use realpath.
904 # but others can symlink *to* the venv Python, so we can't just use sys.executable.
905 # but others can symlink *to* the venv Python, so we can't just use sys.executable.
905 # So we just check every item in the symlink tree (generally <= 3)
906 # So we just check every item in the symlink tree (generally <= 3)
906 paths = self.get_path_links(p)
907 paths = self.get_path_links(p)
907
908
908 # In Cygwin paths like "c:\..." and '\cygdrive\c\...' are possible
909 # In Cygwin paths like "c:\..." and '\cygdrive\c\...' are possible
909 if len(p_venv.parts) > 2 and p_venv.parts[1] == "cygdrive":
910 if len(p_venv.parts) > 2 and p_venv.parts[1] == "cygdrive":
910 drive_name = p_venv.parts[2]
911 drive_name = p_venv.parts[2]
911 p_venv = (drive_name + ":/") / Path(*p_venv.parts[3:])
912 p_venv = (drive_name + ":/") / Path(*p_venv.parts[3:])
912
913
913 if any(p_venv == p.parents[1] for p in paths):
914 if any(p_venv == p.parents[1] for p in paths):
914 # Our exe is inside or has access to the virtualenv, don't need to do anything.
915 # Our exe is inside or has access to the virtualenv, don't need to do anything.
915 return
916 return
916
917
917 if sys.platform == "win32":
918 if sys.platform == "win32":
918 virtual_env = str(Path(os.environ["VIRTUAL_ENV"], "Lib", "site-packages"))
919 virtual_env = str(Path(os.environ["VIRTUAL_ENV"], "Lib", "site-packages"))
919 else:
920 else:
920 virtual_env_path = Path(
921 virtual_env_path = Path(
921 os.environ["VIRTUAL_ENV"], "lib", "python{}.{}", "site-packages"
922 os.environ["VIRTUAL_ENV"], "lib", "python{}.{}", "site-packages"
922 )
923 )
923 p_ver = sys.version_info[:2]
924 p_ver = sys.version_info[:2]
924
925
925 # Predict version from py[thon]-x.x in the $VIRTUAL_ENV
926 # Predict version from py[thon]-x.x in the $VIRTUAL_ENV
926 re_m = re.search(r"\bpy(?:thon)?([23])\.(\d+)\b", os.environ["VIRTUAL_ENV"])
927 re_m = re.search(r"\bpy(?:thon)?([23])\.(\d+)\b", os.environ["VIRTUAL_ENV"])
927 if re_m:
928 if re_m:
928 predicted_path = Path(str(virtual_env_path).format(*re_m.groups()))
929 predicted_path = Path(str(virtual_env_path).format(*re_m.groups()))
929 if predicted_path.exists():
930 if predicted_path.exists():
930 p_ver = re_m.groups()
931 p_ver = re_m.groups()
931
932
932 virtual_env = str(virtual_env_path).format(*p_ver)
933 virtual_env = str(virtual_env_path).format(*p_ver)
933 if self.warn_venv:
934 if self.warn_venv:
934 warn(
935 warn(
935 "Attempting to work in a virtualenv. If you encounter problems, "
936 "Attempting to work in a virtualenv. If you encounter problems, "
936 "please install IPython inside the virtualenv."
937 "please install IPython inside the virtualenv."
937 )
938 )
938 import site
939 import site
939 sys.path.insert(0, virtual_env)
940 sys.path.insert(0, virtual_env)
940 site.addsitedir(virtual_env)
941 site.addsitedir(virtual_env)
941
942
942 #-------------------------------------------------------------------------
943 #-------------------------------------------------------------------------
943 # Things related to injections into the sys module
944 # Things related to injections into the sys module
944 #-------------------------------------------------------------------------
945 #-------------------------------------------------------------------------
945
946
946 def save_sys_module_state(self):
947 def save_sys_module_state(self):
947 """Save the state of hooks in the sys module.
948 """Save the state of hooks in the sys module.
948
949
949 This has to be called after self.user_module is created.
950 This has to be called after self.user_module is created.
950 """
951 """
951 self._orig_sys_module_state = {'stdin': sys.stdin,
952 self._orig_sys_module_state = {'stdin': sys.stdin,
952 'stdout': sys.stdout,
953 'stdout': sys.stdout,
953 'stderr': sys.stderr,
954 'stderr': sys.stderr,
954 'excepthook': sys.excepthook}
955 'excepthook': sys.excepthook}
955 self._orig_sys_modules_main_name = self.user_module.__name__
956 self._orig_sys_modules_main_name = self.user_module.__name__
956 self._orig_sys_modules_main_mod = sys.modules.get(self.user_module.__name__)
957 self._orig_sys_modules_main_mod = sys.modules.get(self.user_module.__name__)
957
958
958 def restore_sys_module_state(self):
959 def restore_sys_module_state(self):
959 """Restore the state of the sys module."""
960 """Restore the state of the sys module."""
960 try:
961 try:
961 for k, v in self._orig_sys_module_state.items():
962 for k, v in self._orig_sys_module_state.items():
962 setattr(sys, k, v)
963 setattr(sys, k, v)
963 except AttributeError:
964 except AttributeError:
964 pass
965 pass
965 # Reset what what done in self.init_sys_modules
966 # Reset what what done in self.init_sys_modules
966 if self._orig_sys_modules_main_mod is not None:
967 if self._orig_sys_modules_main_mod is not None:
967 sys.modules[self._orig_sys_modules_main_name] = self._orig_sys_modules_main_mod
968 sys.modules[self._orig_sys_modules_main_name] = self._orig_sys_modules_main_mod
968
969
969 #-------------------------------------------------------------------------
970 #-------------------------------------------------------------------------
970 # Things related to the banner
971 # Things related to the banner
971 #-------------------------------------------------------------------------
972 #-------------------------------------------------------------------------
972
973
973 @property
974 @property
974 def banner(self):
975 def banner(self):
975 banner = self.banner1
976 banner = self.banner1
976 if self.profile and self.profile != 'default':
977 if self.profile and self.profile != 'default':
977 banner += '\nIPython profile: %s\n' % self.profile
978 banner += '\nIPython profile: %s\n' % self.profile
978 if self.banner2:
979 if self.banner2:
979 banner += '\n' + self.banner2
980 banner += '\n' + self.banner2
980 return banner
981 return banner
981
982
982 def show_banner(self, banner=None):
983 def show_banner(self, banner=None):
983 if banner is None:
984 if banner is None:
984 banner = self.banner
985 banner = self.banner
985 sys.stdout.write(banner)
986 sys.stdout.write(banner)
986
987
987 #-------------------------------------------------------------------------
988 #-------------------------------------------------------------------------
988 # Things related to hooks
989 # Things related to hooks
989 #-------------------------------------------------------------------------
990 #-------------------------------------------------------------------------
990
991
991 def init_hooks(self):
992 def init_hooks(self):
992 # hooks holds pointers used for user-side customizations
993 # hooks holds pointers used for user-side customizations
993 self.hooks = Struct()
994 self.hooks = Struct()
994
995
995 self.strdispatchers = {}
996 self.strdispatchers = {}
996
997
997 # Set all default hooks, defined in the IPython.hooks module.
998 # Set all default hooks, defined in the IPython.hooks module.
998 hooks = IPython.core.hooks
999 hooks = IPython.core.hooks
999 for hook_name in hooks.__all__:
1000 for hook_name in hooks.__all__:
1000 # default hooks have priority 100, i.e. low; user hooks should have
1001 # default hooks have priority 100, i.e. low; user hooks should have
1001 # 0-100 priority
1002 # 0-100 priority
1002 self.set_hook(hook_name, getattr(hooks, hook_name), 100)
1003 self.set_hook(hook_name, getattr(hooks, hook_name), 100)
1003
1004
1004 if self.display_page:
1005 if self.display_page:
1005 self.set_hook('show_in_pager', page.as_hook(page.display_page), 90)
1006 self.set_hook('show_in_pager', page.as_hook(page.display_page), 90)
1006
1007
1007 def set_hook(self, name, hook, priority=50, str_key=None, re_key=None):
1008 def set_hook(self, name, hook, priority=50, str_key=None, re_key=None):
1008 """set_hook(name,hook) -> sets an internal IPython hook.
1009 """set_hook(name,hook) -> sets an internal IPython hook.
1009
1010
1010 IPython exposes some of its internal API as user-modifiable hooks. By
1011 IPython exposes some of its internal API as user-modifiable hooks. By
1011 adding your function to one of these hooks, you can modify IPython's
1012 adding your function to one of these hooks, you can modify IPython's
1012 behavior to call at runtime your own routines."""
1013 behavior to call at runtime your own routines."""
1013
1014
1014 # At some point in the future, this should validate the hook before it
1015 # At some point in the future, this should validate the hook before it
1015 # accepts it. Probably at least check that the hook takes the number
1016 # accepts it. Probably at least check that the hook takes the number
1016 # of args it's supposed to.
1017 # of args it's supposed to.
1017
1018
1018 f = types.MethodType(hook,self)
1019 f = types.MethodType(hook,self)
1019
1020
1020 # check if the hook is for strdispatcher first
1021 # check if the hook is for strdispatcher first
1021 if str_key is not None:
1022 if str_key is not None:
1022 sdp = self.strdispatchers.get(name, StrDispatch())
1023 sdp = self.strdispatchers.get(name, StrDispatch())
1023 sdp.add_s(str_key, f, priority )
1024 sdp.add_s(str_key, f, priority )
1024 self.strdispatchers[name] = sdp
1025 self.strdispatchers[name] = sdp
1025 return
1026 return
1026 if re_key is not None:
1027 if re_key is not None:
1027 sdp = self.strdispatchers.get(name, StrDispatch())
1028 sdp = self.strdispatchers.get(name, StrDispatch())
1028 sdp.add_re(re.compile(re_key), f, priority )
1029 sdp.add_re(re.compile(re_key), f, priority )
1029 self.strdispatchers[name] = sdp
1030 self.strdispatchers[name] = sdp
1030 return
1031 return
1031
1032
1032 dp = getattr(self.hooks, name, None)
1033 dp = getattr(self.hooks, name, None)
1033 if name not in IPython.core.hooks.__all__:
1034 if name not in IPython.core.hooks.__all__:
1034 print("Warning! Hook '%s' is not one of %s" % \
1035 print("Warning! Hook '%s' is not one of %s" % \
1035 (name, IPython.core.hooks.__all__ ))
1036 (name, IPython.core.hooks.__all__ ))
1036
1037
1037 if name in IPython.core.hooks.deprecated:
1038 if name in IPython.core.hooks.deprecated:
1038 alternative = IPython.core.hooks.deprecated[name]
1039 alternative = IPython.core.hooks.deprecated[name]
1039 raise ValueError(
1040 raise ValueError(
1040 "Hook {} has been deprecated since IPython 5.0. Use {} instead.".format(
1041 "Hook {} has been deprecated since IPython 5.0. Use {} instead.".format(
1041 name, alternative
1042 name, alternative
1042 )
1043 )
1043 )
1044 )
1044
1045
1045 if not dp:
1046 if not dp:
1046 dp = IPython.core.hooks.CommandChainDispatcher()
1047 dp = IPython.core.hooks.CommandChainDispatcher()
1047
1048
1048 try:
1049 try:
1049 dp.add(f,priority)
1050 dp.add(f,priority)
1050 except AttributeError:
1051 except AttributeError:
1051 # it was not commandchain, plain old func - replace
1052 # it was not commandchain, plain old func - replace
1052 dp = f
1053 dp = f
1053
1054
1054 setattr(self.hooks,name, dp)
1055 setattr(self.hooks,name, dp)
1055
1056
1056 #-------------------------------------------------------------------------
1057 #-------------------------------------------------------------------------
1057 # Things related to events
1058 # Things related to events
1058 #-------------------------------------------------------------------------
1059 #-------------------------------------------------------------------------
1059
1060
1060 def init_events(self):
1061 def init_events(self):
1061 self.events = EventManager(self, available_events)
1062 self.events = EventManager(self, available_events)
1062
1063
1063 self.events.register("pre_execute", self._clear_warning_registry)
1064 self.events.register("pre_execute", self._clear_warning_registry)
1064
1065
1065 def register_post_execute(self, func):
1066 def register_post_execute(self, func):
1066 """DEPRECATED: Use ip.events.register('post_run_cell', func)
1067 """DEPRECATED: Use ip.events.register('post_run_cell', func)
1067
1068
1068 Register a function for calling after code execution.
1069 Register a function for calling after code execution.
1069 """
1070 """
1070 raise ValueError(
1071 raise ValueError(
1071 "ip.register_post_execute is deprecated since IPython 1.0, use "
1072 "ip.register_post_execute is deprecated since IPython 1.0, use "
1072 "ip.events.register('post_run_cell', func) instead."
1073 "ip.events.register('post_run_cell', func) instead."
1073 )
1074 )
1074
1075
1075 def _clear_warning_registry(self):
1076 def _clear_warning_registry(self):
1076 # clear the warning registry, so that different code blocks with
1077 # clear the warning registry, so that different code blocks with
1077 # overlapping line number ranges don't cause spurious suppression of
1078 # overlapping line number ranges don't cause spurious suppression of
1078 # warnings (see gh-6611 for details)
1079 # warnings (see gh-6611 for details)
1079 if "__warningregistry__" in self.user_global_ns:
1080 if "__warningregistry__" in self.user_global_ns:
1080 del self.user_global_ns["__warningregistry__"]
1081 del self.user_global_ns["__warningregistry__"]
1081
1082
1082 #-------------------------------------------------------------------------
1083 #-------------------------------------------------------------------------
1083 # Things related to the "main" module
1084 # Things related to the "main" module
1084 #-------------------------------------------------------------------------
1085 #-------------------------------------------------------------------------
1085
1086
1086 def new_main_mod(self, filename, modname):
1087 def new_main_mod(self, filename, modname):
1087 """Return a new 'main' module object for user code execution.
1088 """Return a new 'main' module object for user code execution.
1088
1089
1089 ``filename`` should be the path of the script which will be run in the
1090 ``filename`` should be the path of the script which will be run in the
1090 module. Requests with the same filename will get the same module, with
1091 module. Requests with the same filename will get the same module, with
1091 its namespace cleared.
1092 its namespace cleared.
1092
1093
1093 ``modname`` should be the module name - normally either '__main__' or
1094 ``modname`` should be the module name - normally either '__main__' or
1094 the basename of the file without the extension.
1095 the basename of the file without the extension.
1095
1096
1096 When scripts are executed via %run, we must keep a reference to their
1097 When scripts are executed via %run, we must keep a reference to their
1097 __main__ module around so that Python doesn't
1098 __main__ module around so that Python doesn't
1098 clear it, rendering references to module globals useless.
1099 clear it, rendering references to module globals useless.
1099
1100
1100 This method keeps said reference in a private dict, keyed by the
1101 This method keeps said reference in a private dict, keyed by the
1101 absolute path of the script. This way, for multiple executions of the
1102 absolute path of the script. This way, for multiple executions of the
1102 same script we only keep one copy of the namespace (the last one),
1103 same script we only keep one copy of the namespace (the last one),
1103 thus preventing memory leaks from old references while allowing the
1104 thus preventing memory leaks from old references while allowing the
1104 objects from the last execution to be accessible.
1105 objects from the last execution to be accessible.
1105 """
1106 """
1106 filename = os.path.abspath(filename)
1107 filename = os.path.abspath(filename)
1107 try:
1108 try:
1108 main_mod = self._main_mod_cache[filename]
1109 main_mod = self._main_mod_cache[filename]
1109 except KeyError:
1110 except KeyError:
1110 main_mod = self._main_mod_cache[filename] = types.ModuleType(
1111 main_mod = self._main_mod_cache[filename] = types.ModuleType(
1111 modname,
1112 modname,
1112 doc="Module created for script run in IPython")
1113 doc="Module created for script run in IPython")
1113 else:
1114 else:
1114 main_mod.__dict__.clear()
1115 main_mod.__dict__.clear()
1115 main_mod.__name__ = modname
1116 main_mod.__name__ = modname
1116
1117
1117 main_mod.__file__ = filename
1118 main_mod.__file__ = filename
1118 # It seems pydoc (and perhaps others) needs any module instance to
1119 # It seems pydoc (and perhaps others) needs any module instance to
1119 # implement a __nonzero__ method
1120 # implement a __nonzero__ method
1120 main_mod.__nonzero__ = lambda : True
1121 main_mod.__nonzero__ = lambda : True
1121
1122
1122 return main_mod
1123 return main_mod
1123
1124
1124 def clear_main_mod_cache(self):
1125 def clear_main_mod_cache(self):
1125 """Clear the cache of main modules.
1126 """Clear the cache of main modules.
1126
1127
1127 Mainly for use by utilities like %reset.
1128 Mainly for use by utilities like %reset.
1128
1129
1129 Examples
1130 Examples
1130 --------
1131 --------
1131 In [15]: import IPython
1132 In [15]: import IPython
1132
1133
1133 In [16]: m = _ip.new_main_mod(IPython.__file__, 'IPython')
1134 In [16]: m = _ip.new_main_mod(IPython.__file__, 'IPython')
1134
1135
1135 In [17]: len(_ip._main_mod_cache) > 0
1136 In [17]: len(_ip._main_mod_cache) > 0
1136 Out[17]: True
1137 Out[17]: True
1137
1138
1138 In [18]: _ip.clear_main_mod_cache()
1139 In [18]: _ip.clear_main_mod_cache()
1139
1140
1140 In [19]: len(_ip._main_mod_cache) == 0
1141 In [19]: len(_ip._main_mod_cache) == 0
1141 Out[19]: True
1142 Out[19]: True
1142 """
1143 """
1143 self._main_mod_cache.clear()
1144 self._main_mod_cache.clear()
1144
1145
1145 #-------------------------------------------------------------------------
1146 #-------------------------------------------------------------------------
1146 # Things related to debugging
1147 # Things related to debugging
1147 #-------------------------------------------------------------------------
1148 #-------------------------------------------------------------------------
1148
1149
1149 def init_pdb(self):
1150 def init_pdb(self):
1150 # Set calling of pdb on exceptions
1151 # Set calling of pdb on exceptions
1151 # self.call_pdb is a property
1152 # self.call_pdb is a property
1152 self.call_pdb = self.pdb
1153 self.call_pdb = self.pdb
1153
1154
1154 def _get_call_pdb(self):
1155 def _get_call_pdb(self):
1155 return self._call_pdb
1156 return self._call_pdb
1156
1157
1157 def _set_call_pdb(self,val):
1158 def _set_call_pdb(self,val):
1158
1159
1159 if val not in (0,1,False,True):
1160 if val not in (0,1,False,True):
1160 raise ValueError('new call_pdb value must be boolean')
1161 raise ValueError('new call_pdb value must be boolean')
1161
1162
1162 # store value in instance
1163 # store value in instance
1163 self._call_pdb = val
1164 self._call_pdb = val
1164
1165
1165 # notify the actual exception handlers
1166 # notify the actual exception handlers
1166 self.InteractiveTB.call_pdb = val
1167 self.InteractiveTB.call_pdb = val
1167
1168
1168 call_pdb = property(_get_call_pdb,_set_call_pdb,None,
1169 call_pdb = property(_get_call_pdb,_set_call_pdb,None,
1169 'Control auto-activation of pdb at exceptions')
1170 'Control auto-activation of pdb at exceptions')
1170
1171
1171 def debugger(self,force=False):
1172 def debugger(self,force=False):
1172 """Call the pdb debugger.
1173 """Call the pdb debugger.
1173
1174
1174 Keywords:
1175 Keywords:
1175
1176
1176 - force(False): by default, this routine checks the instance call_pdb
1177 - force(False): by default, this routine checks the instance call_pdb
1177 flag and does not actually invoke the debugger if the flag is false.
1178 flag and does not actually invoke the debugger if the flag is false.
1178 The 'force' option forces the debugger to activate even if the flag
1179 The 'force' option forces the debugger to activate even if the flag
1179 is false.
1180 is false.
1180 """
1181 """
1181
1182
1182 if not (force or self.call_pdb):
1183 if not (force or self.call_pdb):
1183 return
1184 return
1184
1185
1185 if not hasattr(sys,'last_traceback'):
1186 if not hasattr(sys,'last_traceback'):
1186 error('No traceback has been produced, nothing to debug.')
1187 error('No traceback has been produced, nothing to debug.')
1187 return
1188 return
1188
1189
1189 self.InteractiveTB.debugger(force=True)
1190 self.InteractiveTB.debugger(force=True)
1190
1191
1191 #-------------------------------------------------------------------------
1192 #-------------------------------------------------------------------------
1192 # Things related to IPython's various namespaces
1193 # Things related to IPython's various namespaces
1193 #-------------------------------------------------------------------------
1194 #-------------------------------------------------------------------------
1194 default_user_namespaces = True
1195 default_user_namespaces = True
1195
1196
1196 def init_create_namespaces(self, user_module=None, user_ns=None):
1197 def init_create_namespaces(self, user_module=None, user_ns=None):
1197 # Create the namespace where the user will operate. user_ns is
1198 # Create the namespace where the user will operate. user_ns is
1198 # normally the only one used, and it is passed to the exec calls as
1199 # normally the only one used, and it is passed to the exec calls as
1199 # the locals argument. But we do carry a user_global_ns namespace
1200 # the locals argument. But we do carry a user_global_ns namespace
1200 # given as the exec 'globals' argument, This is useful in embedding
1201 # given as the exec 'globals' argument, This is useful in embedding
1201 # situations where the ipython shell opens in a context where the
1202 # situations where the ipython shell opens in a context where the
1202 # distinction between locals and globals is meaningful. For
1203 # distinction between locals and globals is meaningful. For
1203 # non-embedded contexts, it is just the same object as the user_ns dict.
1204 # non-embedded contexts, it is just the same object as the user_ns dict.
1204
1205
1205 # FIXME. For some strange reason, __builtins__ is showing up at user
1206 # FIXME. For some strange reason, __builtins__ is showing up at user
1206 # level as a dict instead of a module. This is a manual fix, but I
1207 # level as a dict instead of a module. This is a manual fix, but I
1207 # should really track down where the problem is coming from. Alex
1208 # should really track down where the problem is coming from. Alex
1208 # Schmolck reported this problem first.
1209 # Schmolck reported this problem first.
1209
1210
1210 # A useful post by Alex Martelli on this topic:
1211 # A useful post by Alex Martelli on this topic:
1211 # Re: inconsistent value from __builtins__
1212 # Re: inconsistent value from __builtins__
1212 # Von: Alex Martelli <aleaxit@yahoo.com>
1213 # Von: Alex Martelli <aleaxit@yahoo.com>
1213 # Datum: Freitag 01 Oktober 2004 04:45:34 nachmittags/abends
1214 # Datum: Freitag 01 Oktober 2004 04:45:34 nachmittags/abends
1214 # Gruppen: comp.lang.python
1215 # Gruppen: comp.lang.python
1215
1216
1216 # Michael Hohn <hohn@hooknose.lbl.gov> wrote:
1217 # Michael Hohn <hohn@hooknose.lbl.gov> wrote:
1217 # > >>> print type(builtin_check.get_global_binding('__builtins__'))
1218 # > >>> print type(builtin_check.get_global_binding('__builtins__'))
1218 # > <type 'dict'>
1219 # > <type 'dict'>
1219 # > >>> print type(__builtins__)
1220 # > >>> print type(__builtins__)
1220 # > <type 'module'>
1221 # > <type 'module'>
1221 # > Is this difference in return value intentional?
1222 # > Is this difference in return value intentional?
1222
1223
1223 # Well, it's documented that '__builtins__' can be either a dictionary
1224 # Well, it's documented that '__builtins__' can be either a dictionary
1224 # or a module, and it's been that way for a long time. Whether it's
1225 # or a module, and it's been that way for a long time. Whether it's
1225 # intentional (or sensible), I don't know. In any case, the idea is
1226 # intentional (or sensible), I don't know. In any case, the idea is
1226 # that if you need to access the built-in namespace directly, you
1227 # that if you need to access the built-in namespace directly, you
1227 # should start with "import __builtin__" (note, no 's') which will
1228 # should start with "import __builtin__" (note, no 's') which will
1228 # definitely give you a module. Yeah, it's somewhat confusing:-(.
1229 # definitely give you a module. Yeah, it's somewhat confusing:-(.
1229
1230
1230 # These routines return a properly built module and dict as needed by
1231 # These routines return a properly built module and dict as needed by
1231 # the rest of the code, and can also be used by extension writers to
1232 # the rest of the code, and can also be used by extension writers to
1232 # generate properly initialized namespaces.
1233 # generate properly initialized namespaces.
1233 if (user_ns is not None) or (user_module is not None):
1234 if (user_ns is not None) or (user_module is not None):
1234 self.default_user_namespaces = False
1235 self.default_user_namespaces = False
1235 self.user_module, self.user_ns = self.prepare_user_module(user_module, user_ns)
1236 self.user_module, self.user_ns = self.prepare_user_module(user_module, user_ns)
1236
1237
1237 # A record of hidden variables we have added to the user namespace, so
1238 # A record of hidden variables we have added to the user namespace, so
1238 # we can list later only variables defined in actual interactive use.
1239 # we can list later only variables defined in actual interactive use.
1239 self.user_ns_hidden = {}
1240 self.user_ns_hidden = {}
1240
1241
1241 # Now that FakeModule produces a real module, we've run into a nasty
1242 # Now that FakeModule produces a real module, we've run into a nasty
1242 # problem: after script execution (via %run), the module where the user
1243 # problem: after script execution (via %run), the module where the user
1243 # code ran is deleted. Now that this object is a true module (needed
1244 # code ran is deleted. Now that this object is a true module (needed
1244 # so doctest and other tools work correctly), the Python module
1245 # so doctest and other tools work correctly), the Python module
1245 # teardown mechanism runs over it, and sets to None every variable
1246 # teardown mechanism runs over it, and sets to None every variable
1246 # present in that module. Top-level references to objects from the
1247 # present in that module. Top-level references to objects from the
1247 # script survive, because the user_ns is updated with them. However,
1248 # script survive, because the user_ns is updated with them. However,
1248 # calling functions defined in the script that use other things from
1249 # calling functions defined in the script that use other things from
1249 # the script will fail, because the function's closure had references
1250 # the script will fail, because the function's closure had references
1250 # to the original objects, which are now all None. So we must protect
1251 # to the original objects, which are now all None. So we must protect
1251 # these modules from deletion by keeping a cache.
1252 # these modules from deletion by keeping a cache.
1252 #
1253 #
1253 # To avoid keeping stale modules around (we only need the one from the
1254 # To avoid keeping stale modules around (we only need the one from the
1254 # last run), we use a dict keyed with the full path to the script, so
1255 # last run), we use a dict keyed with the full path to the script, so
1255 # only the last version of the module is held in the cache. Note,
1256 # only the last version of the module is held in the cache. Note,
1256 # however, that we must cache the module *namespace contents* (their
1257 # however, that we must cache the module *namespace contents* (their
1257 # __dict__). Because if we try to cache the actual modules, old ones
1258 # __dict__). Because if we try to cache the actual modules, old ones
1258 # (uncached) could be destroyed while still holding references (such as
1259 # (uncached) could be destroyed while still holding references (such as
1259 # those held by GUI objects that tend to be long-lived)>
1260 # those held by GUI objects that tend to be long-lived)>
1260 #
1261 #
1261 # The %reset command will flush this cache. See the cache_main_mod()
1262 # The %reset command will flush this cache. See the cache_main_mod()
1262 # and clear_main_mod_cache() methods for details on use.
1263 # and clear_main_mod_cache() methods for details on use.
1263
1264
1264 # This is the cache used for 'main' namespaces
1265 # This is the cache used for 'main' namespaces
1265 self._main_mod_cache = {}
1266 self._main_mod_cache = {}
1266
1267
1267 # A table holding all the namespaces IPython deals with, so that
1268 # A table holding all the namespaces IPython deals with, so that
1268 # introspection facilities can search easily.
1269 # introspection facilities can search easily.
1269 self.ns_table = {'user_global':self.user_module.__dict__,
1270 self.ns_table = {'user_global':self.user_module.__dict__,
1270 'user_local':self.user_ns,
1271 'user_local':self.user_ns,
1271 'builtin':builtin_mod.__dict__
1272 'builtin':builtin_mod.__dict__
1272 }
1273 }
1273
1274
1274 @property
1275 @property
1275 def user_global_ns(self):
1276 def user_global_ns(self):
1276 return self.user_module.__dict__
1277 return self.user_module.__dict__
1277
1278
1278 def prepare_user_module(self, user_module=None, user_ns=None):
1279 def prepare_user_module(self, user_module=None, user_ns=None):
1279 """Prepare the module and namespace in which user code will be run.
1280 """Prepare the module and namespace in which user code will be run.
1280
1281
1281 When IPython is started normally, both parameters are None: a new module
1282 When IPython is started normally, both parameters are None: a new module
1282 is created automatically, and its __dict__ used as the namespace.
1283 is created automatically, and its __dict__ used as the namespace.
1283
1284
1284 If only user_module is provided, its __dict__ is used as the namespace.
1285 If only user_module is provided, its __dict__ is used as the namespace.
1285 If only user_ns is provided, a dummy module is created, and user_ns
1286 If only user_ns is provided, a dummy module is created, and user_ns
1286 becomes the global namespace. If both are provided (as they may be
1287 becomes the global namespace. If both are provided (as they may be
1287 when embedding), user_ns is the local namespace, and user_module
1288 when embedding), user_ns is the local namespace, and user_module
1288 provides the global namespace.
1289 provides the global namespace.
1289
1290
1290 Parameters
1291 Parameters
1291 ----------
1292 ----------
1292 user_module : module, optional
1293 user_module : module, optional
1293 The current user module in which IPython is being run. If None,
1294 The current user module in which IPython is being run. If None,
1294 a clean module will be created.
1295 a clean module will be created.
1295 user_ns : dict, optional
1296 user_ns : dict, optional
1296 A namespace in which to run interactive commands.
1297 A namespace in which to run interactive commands.
1297
1298
1298 Returns
1299 Returns
1299 -------
1300 -------
1300 A tuple of user_module and user_ns, each properly initialised.
1301 A tuple of user_module and user_ns, each properly initialised.
1301 """
1302 """
1302 if user_module is None and user_ns is not None:
1303 if user_module is None and user_ns is not None:
1303 user_ns.setdefault("__name__", "__main__")
1304 user_ns.setdefault("__name__", "__main__")
1304 user_module = DummyMod()
1305 user_module = DummyMod()
1305 user_module.__dict__ = user_ns
1306 user_module.__dict__ = user_ns
1306
1307
1307 if user_module is None:
1308 if user_module is None:
1308 user_module = types.ModuleType("__main__",
1309 user_module = types.ModuleType("__main__",
1309 doc="Automatically created module for IPython interactive environment")
1310 doc="Automatically created module for IPython interactive environment")
1310
1311
1311 # We must ensure that __builtin__ (without the final 's') is always
1312 # We must ensure that __builtin__ (without the final 's') is always
1312 # available and pointing to the __builtin__ *module*. For more details:
1313 # available and pointing to the __builtin__ *module*. For more details:
1313 # http://mail.python.org/pipermail/python-dev/2001-April/014068.html
1314 # http://mail.python.org/pipermail/python-dev/2001-April/014068.html
1314 user_module.__dict__.setdefault('__builtin__', builtin_mod)
1315 user_module.__dict__.setdefault('__builtin__', builtin_mod)
1315 user_module.__dict__.setdefault('__builtins__', builtin_mod)
1316 user_module.__dict__.setdefault('__builtins__', builtin_mod)
1316
1317
1317 if user_ns is None:
1318 if user_ns is None:
1318 user_ns = user_module.__dict__
1319 user_ns = user_module.__dict__
1319
1320
1320 return user_module, user_ns
1321 return user_module, user_ns
1321
1322
1322 def init_sys_modules(self):
1323 def init_sys_modules(self):
1323 # We need to insert into sys.modules something that looks like a
1324 # We need to insert into sys.modules something that looks like a
1324 # module but which accesses the IPython namespace, for shelve and
1325 # module but which accesses the IPython namespace, for shelve and
1325 # pickle to work interactively. Normally they rely on getting
1326 # pickle to work interactively. Normally they rely on getting
1326 # everything out of __main__, but for embedding purposes each IPython
1327 # everything out of __main__, but for embedding purposes each IPython
1327 # instance has its own private namespace, so we can't go shoving
1328 # instance has its own private namespace, so we can't go shoving
1328 # everything into __main__.
1329 # everything into __main__.
1329
1330
1330 # note, however, that we should only do this for non-embedded
1331 # note, however, that we should only do this for non-embedded
1331 # ipythons, which really mimic the __main__.__dict__ with their own
1332 # ipythons, which really mimic the __main__.__dict__ with their own
1332 # namespace. Embedded instances, on the other hand, should not do
1333 # namespace. Embedded instances, on the other hand, should not do
1333 # this because they need to manage the user local/global namespaces
1334 # this because they need to manage the user local/global namespaces
1334 # only, but they live within a 'normal' __main__ (meaning, they
1335 # only, but they live within a 'normal' __main__ (meaning, they
1335 # shouldn't overtake the execution environment of the script they're
1336 # shouldn't overtake the execution environment of the script they're
1336 # embedded in).
1337 # embedded in).
1337
1338
1338 # This is overridden in the InteractiveShellEmbed subclass to a no-op.
1339 # This is overridden in the InteractiveShellEmbed subclass to a no-op.
1339 main_name = self.user_module.__name__
1340 main_name = self.user_module.__name__
1340 sys.modules[main_name] = self.user_module
1341 sys.modules[main_name] = self.user_module
1341
1342
1342 def init_user_ns(self):
1343 def init_user_ns(self):
1343 """Initialize all user-visible namespaces to their minimum defaults.
1344 """Initialize all user-visible namespaces to their minimum defaults.
1344
1345
1345 Certain history lists are also initialized here, as they effectively
1346 Certain history lists are also initialized here, as they effectively
1346 act as user namespaces.
1347 act as user namespaces.
1347
1348
1348 Notes
1349 Notes
1349 -----
1350 -----
1350 All data structures here are only filled in, they are NOT reset by this
1351 All data structures here are only filled in, they are NOT reset by this
1351 method. If they were not empty before, data will simply be added to
1352 method. If they were not empty before, data will simply be added to
1352 them.
1353 them.
1353 """
1354 """
1354 # This function works in two parts: first we put a few things in
1355 # This function works in two parts: first we put a few things in
1355 # user_ns, and we sync that contents into user_ns_hidden so that these
1356 # user_ns, and we sync that contents into user_ns_hidden so that these
1356 # initial variables aren't shown by %who. After the sync, we add the
1357 # initial variables aren't shown by %who. After the sync, we add the
1357 # rest of what we *do* want the user to see with %who even on a new
1358 # rest of what we *do* want the user to see with %who even on a new
1358 # session (probably nothing, so they really only see their own stuff)
1359 # session (probably nothing, so they really only see their own stuff)
1359
1360
1360 # The user dict must *always* have a __builtin__ reference to the
1361 # The user dict must *always* have a __builtin__ reference to the
1361 # Python standard __builtin__ namespace, which must be imported.
1362 # Python standard __builtin__ namespace, which must be imported.
1362 # This is so that certain operations in prompt evaluation can be
1363 # This is so that certain operations in prompt evaluation can be
1363 # reliably executed with builtins. Note that we can NOT use
1364 # reliably executed with builtins. Note that we can NOT use
1364 # __builtins__ (note the 's'), because that can either be a dict or a
1365 # __builtins__ (note the 's'), because that can either be a dict or a
1365 # module, and can even mutate at runtime, depending on the context
1366 # module, and can even mutate at runtime, depending on the context
1366 # (Python makes no guarantees on it). In contrast, __builtin__ is
1367 # (Python makes no guarantees on it). In contrast, __builtin__ is
1367 # always a module object, though it must be explicitly imported.
1368 # always a module object, though it must be explicitly imported.
1368
1369
1369 # For more details:
1370 # For more details:
1370 # http://mail.python.org/pipermail/python-dev/2001-April/014068.html
1371 # http://mail.python.org/pipermail/python-dev/2001-April/014068.html
1371 ns = {}
1372 ns = {}
1372
1373
1373 # make global variables for user access to the histories
1374 # make global variables for user access to the histories
1374 ns['_ih'] = self.history_manager.input_hist_parsed
1375 ns['_ih'] = self.history_manager.input_hist_parsed
1375 ns['_oh'] = self.history_manager.output_hist
1376 ns['_oh'] = self.history_manager.output_hist
1376 ns['_dh'] = self.history_manager.dir_hist
1377 ns['_dh'] = self.history_manager.dir_hist
1377
1378
1378 # user aliases to input and output histories. These shouldn't show up
1379 # user aliases to input and output histories. These shouldn't show up
1379 # in %who, as they can have very large reprs.
1380 # in %who, as they can have very large reprs.
1380 ns['In'] = self.history_manager.input_hist_parsed
1381 ns['In'] = self.history_manager.input_hist_parsed
1381 ns['Out'] = self.history_manager.output_hist
1382 ns['Out'] = self.history_manager.output_hist
1382
1383
1383 # Store myself as the public api!!!
1384 # Store myself as the public api!!!
1384 ns['get_ipython'] = self.get_ipython
1385 ns['get_ipython'] = self.get_ipython
1385
1386
1386 ns['exit'] = self.exiter
1387 ns['exit'] = self.exiter
1387 ns['quit'] = self.exiter
1388 ns['quit'] = self.exiter
1388 ns["open"] = _modified_open
1389 ns["open"] = _modified_open
1389
1390
1390 # Sync what we've added so far to user_ns_hidden so these aren't seen
1391 # Sync what we've added so far to user_ns_hidden so these aren't seen
1391 # by %who
1392 # by %who
1392 self.user_ns_hidden.update(ns)
1393 self.user_ns_hidden.update(ns)
1393
1394
1394 # Anything put into ns now would show up in %who. Think twice before
1395 # Anything put into ns now would show up in %who. Think twice before
1395 # putting anything here, as we really want %who to show the user their
1396 # putting anything here, as we really want %who to show the user their
1396 # stuff, not our variables.
1397 # stuff, not our variables.
1397
1398
1398 # Finally, update the real user's namespace
1399 # Finally, update the real user's namespace
1399 self.user_ns.update(ns)
1400 self.user_ns.update(ns)
1400
1401
1401 @property
1402 @property
1402 def all_ns_refs(self):
1403 def all_ns_refs(self):
1403 """Get a list of references to all the namespace dictionaries in which
1404 """Get a list of references to all the namespace dictionaries in which
1404 IPython might store a user-created object.
1405 IPython might store a user-created object.
1405
1406
1406 Note that this does not include the displayhook, which also caches
1407 Note that this does not include the displayhook, which also caches
1407 objects from the output."""
1408 objects from the output."""
1408 return [self.user_ns, self.user_global_ns, self.user_ns_hidden] + \
1409 return [self.user_ns, self.user_global_ns, self.user_ns_hidden] + \
1409 [m.__dict__ for m in self._main_mod_cache.values()]
1410 [m.__dict__ for m in self._main_mod_cache.values()]
1410
1411
1411 def reset(self, new_session=True, aggressive=False):
1412 def reset(self, new_session=True, aggressive=False):
1412 """Clear all internal namespaces, and attempt to release references to
1413 """Clear all internal namespaces, and attempt to release references to
1413 user objects.
1414 user objects.
1414
1415
1415 If new_session is True, a new history session will be opened.
1416 If new_session is True, a new history session will be opened.
1416 """
1417 """
1417 # Clear histories
1418 # Clear histories
1418 assert self.history_manager is not None
1419 assert self.history_manager is not None
1419 self.history_manager.reset(new_session)
1420 self.history_manager.reset(new_session)
1420 # Reset counter used to index all histories
1421 # Reset counter used to index all histories
1421 if new_session:
1422 if new_session:
1422 self.execution_count = 1
1423 self.execution_count = 1
1423
1424
1424 # Reset last execution result
1425 # Reset last execution result
1425 self.last_execution_succeeded = True
1426 self.last_execution_succeeded = True
1426 self.last_execution_result = None
1427 self.last_execution_result = None
1427
1428
1428 # Flush cached output items
1429 # Flush cached output items
1429 if self.displayhook.do_full_cache:
1430 if self.displayhook.do_full_cache:
1430 self.displayhook.flush()
1431 self.displayhook.flush()
1431
1432
1432 # The main execution namespaces must be cleared very carefully,
1433 # The main execution namespaces must be cleared very carefully,
1433 # skipping the deletion of the builtin-related keys, because doing so
1434 # skipping the deletion of the builtin-related keys, because doing so
1434 # would cause errors in many object's __del__ methods.
1435 # would cause errors in many object's __del__ methods.
1435 if self.user_ns is not self.user_global_ns:
1436 if self.user_ns is not self.user_global_ns:
1436 self.user_ns.clear()
1437 self.user_ns.clear()
1437 ns = self.user_global_ns
1438 ns = self.user_global_ns
1438 drop_keys = set(ns.keys())
1439 drop_keys = set(ns.keys())
1439 drop_keys.discard('__builtin__')
1440 drop_keys.discard('__builtin__')
1440 drop_keys.discard('__builtins__')
1441 drop_keys.discard('__builtins__')
1441 drop_keys.discard('__name__')
1442 drop_keys.discard('__name__')
1442 for k in drop_keys:
1443 for k in drop_keys:
1443 del ns[k]
1444 del ns[k]
1444
1445
1445 self.user_ns_hidden.clear()
1446 self.user_ns_hidden.clear()
1446
1447
1447 # Restore the user namespaces to minimal usability
1448 # Restore the user namespaces to minimal usability
1448 self.init_user_ns()
1449 self.init_user_ns()
1449 if aggressive and not hasattr(self, "_sys_modules_keys"):
1450 if aggressive and not hasattr(self, "_sys_modules_keys"):
1450 print("Cannot restore sys.module, no snapshot")
1451 print("Cannot restore sys.module, no snapshot")
1451 elif aggressive:
1452 elif aggressive:
1452 print("culling sys module...")
1453 print("culling sys module...")
1453 current_keys = set(sys.modules.keys())
1454 current_keys = set(sys.modules.keys())
1454 for k in current_keys - self._sys_modules_keys:
1455 for k in current_keys - self._sys_modules_keys:
1455 if k.startswith("multiprocessing"):
1456 if k.startswith("multiprocessing"):
1456 continue
1457 continue
1457 del sys.modules[k]
1458 del sys.modules[k]
1458
1459
1459 # Restore the default and user aliases
1460 # Restore the default and user aliases
1460 self.alias_manager.clear_aliases()
1461 self.alias_manager.clear_aliases()
1461 self.alias_manager.init_aliases()
1462 self.alias_manager.init_aliases()
1462
1463
1463 # Now define aliases that only make sense on the terminal, because they
1464 # Now define aliases that only make sense on the terminal, because they
1464 # need direct access to the console in a way that we can't emulate in
1465 # need direct access to the console in a way that we can't emulate in
1465 # GUI or web frontend
1466 # GUI or web frontend
1466 if os.name == 'posix':
1467 if os.name == 'posix':
1467 for cmd in ('clear', 'more', 'less', 'man'):
1468 for cmd in ('clear', 'more', 'less', 'man'):
1468 if cmd not in self.magics_manager.magics['line']:
1469 if cmd not in self.magics_manager.magics['line']:
1469 self.alias_manager.soft_define_alias(cmd, cmd)
1470 self.alias_manager.soft_define_alias(cmd, cmd)
1470
1471
1471 # Flush the private list of module references kept for script
1472 # Flush the private list of module references kept for script
1472 # execution protection
1473 # execution protection
1473 self.clear_main_mod_cache()
1474 self.clear_main_mod_cache()
1474
1475
1475 def del_var(self, varname, by_name=False):
1476 def del_var(self, varname, by_name=False):
1476 """Delete a variable from the various namespaces, so that, as
1477 """Delete a variable from the various namespaces, so that, as
1477 far as possible, we're not keeping any hidden references to it.
1478 far as possible, we're not keeping any hidden references to it.
1478
1479
1479 Parameters
1480 Parameters
1480 ----------
1481 ----------
1481 varname : str
1482 varname : str
1482 The name of the variable to delete.
1483 The name of the variable to delete.
1483 by_name : bool
1484 by_name : bool
1484 If True, delete variables with the given name in each
1485 If True, delete variables with the given name in each
1485 namespace. If False (default), find the variable in the user
1486 namespace. If False (default), find the variable in the user
1486 namespace, and delete references to it.
1487 namespace, and delete references to it.
1487 """
1488 """
1488 if varname in ('__builtin__', '__builtins__'):
1489 if varname in ('__builtin__', '__builtins__'):
1489 raise ValueError("Refusing to delete %s" % varname)
1490 raise ValueError("Refusing to delete %s" % varname)
1490
1491
1491 ns_refs = self.all_ns_refs
1492 ns_refs = self.all_ns_refs
1492
1493
1493 if by_name: # Delete by name
1494 if by_name: # Delete by name
1494 for ns in ns_refs:
1495 for ns in ns_refs:
1495 try:
1496 try:
1496 del ns[varname]
1497 del ns[varname]
1497 except KeyError:
1498 except KeyError:
1498 pass
1499 pass
1499 else: # Delete by object
1500 else: # Delete by object
1500 try:
1501 try:
1501 obj = self.user_ns[varname]
1502 obj = self.user_ns[varname]
1502 except KeyError as e:
1503 except KeyError as e:
1503 raise NameError("name '%s' is not defined" % varname) from e
1504 raise NameError("name '%s' is not defined" % varname) from e
1504 # Also check in output history
1505 # Also check in output history
1505 assert self.history_manager is not None
1506 assert self.history_manager is not None
1506 ns_refs.append(self.history_manager.output_hist)
1507 ns_refs.append(self.history_manager.output_hist)
1507 for ns in ns_refs:
1508 for ns in ns_refs:
1508 to_delete = [n for n, o in ns.items() if o is obj]
1509 to_delete = [n for n, o in ns.items() if o is obj]
1509 for name in to_delete:
1510 for name in to_delete:
1510 del ns[name]
1511 del ns[name]
1511
1512
1512 # Ensure it is removed from the last execution result
1513 # Ensure it is removed from the last execution result
1513 if self.last_execution_result.result is obj:
1514 if self.last_execution_result.result is obj:
1514 self.last_execution_result = None
1515 self.last_execution_result = None
1515
1516
1516 # displayhook keeps extra references, but not in a dictionary
1517 # displayhook keeps extra references, but not in a dictionary
1517 for name in ('_', '__', '___'):
1518 for name in ('_', '__', '___'):
1518 if getattr(self.displayhook, name) is obj:
1519 if getattr(self.displayhook, name) is obj:
1519 setattr(self.displayhook, name, None)
1520 setattr(self.displayhook, name, None)
1520
1521
1521 def reset_selective(self, regex=None):
1522 def reset_selective(self, regex=None):
1522 """Clear selective variables from internal namespaces based on a
1523 """Clear selective variables from internal namespaces based on a
1523 specified regular expression.
1524 specified regular expression.
1524
1525
1525 Parameters
1526 Parameters
1526 ----------
1527 ----------
1527 regex : string or compiled pattern, optional
1528 regex : string or compiled pattern, optional
1528 A regular expression pattern that will be used in searching
1529 A regular expression pattern that will be used in searching
1529 variable names in the users namespaces.
1530 variable names in the users namespaces.
1530 """
1531 """
1531 if regex is not None:
1532 if regex is not None:
1532 try:
1533 try:
1533 m = re.compile(regex)
1534 m = re.compile(regex)
1534 except TypeError as e:
1535 except TypeError as e:
1535 raise TypeError('regex must be a string or compiled pattern') from e
1536 raise TypeError('regex must be a string or compiled pattern') from e
1536 # Search for keys in each namespace that match the given regex
1537 # Search for keys in each namespace that match the given regex
1537 # If a match is found, delete the key/value pair.
1538 # If a match is found, delete the key/value pair.
1538 for ns in self.all_ns_refs:
1539 for ns in self.all_ns_refs:
1539 for var in ns:
1540 for var in ns:
1540 if m.search(var):
1541 if m.search(var):
1541 del ns[var]
1542 del ns[var]
1542
1543
1543 def push(self, variables, interactive=True):
1544 def push(self, variables, interactive=True):
1544 """Inject a group of variables into the IPython user namespace.
1545 """Inject a group of variables into the IPython user namespace.
1545
1546
1546 Parameters
1547 Parameters
1547 ----------
1548 ----------
1548 variables : dict, str or list/tuple of str
1549 variables : dict, str or list/tuple of str
1549 The variables to inject into the user's namespace. If a dict, a
1550 The variables to inject into the user's namespace. If a dict, a
1550 simple update is done. If a str, the string is assumed to have
1551 simple update is done. If a str, the string is assumed to have
1551 variable names separated by spaces. A list/tuple of str can also
1552 variable names separated by spaces. A list/tuple of str can also
1552 be used to give the variable names. If just the variable names are
1553 be used to give the variable names. If just the variable names are
1553 give (list/tuple/str) then the variable values looked up in the
1554 give (list/tuple/str) then the variable values looked up in the
1554 callers frame.
1555 callers frame.
1555 interactive : bool
1556 interactive : bool
1556 If True (default), the variables will be listed with the ``who``
1557 If True (default), the variables will be listed with the ``who``
1557 magic.
1558 magic.
1558 """
1559 """
1559 vdict = None
1560 vdict = None
1560
1561
1561 # We need a dict of name/value pairs to do namespace updates.
1562 # We need a dict of name/value pairs to do namespace updates.
1562 if isinstance(variables, dict):
1563 if isinstance(variables, dict):
1563 vdict = variables
1564 vdict = variables
1564 elif isinstance(variables, (str, list, tuple)):
1565 elif isinstance(variables, (str, list, tuple)):
1565 if isinstance(variables, str):
1566 if isinstance(variables, str):
1566 vlist = variables.split()
1567 vlist = variables.split()
1567 else:
1568 else:
1568 vlist = variables
1569 vlist = variables
1569 vdict = {}
1570 vdict = {}
1570 cf = sys._getframe(1)
1571 cf = sys._getframe(1)
1571 for name in vlist:
1572 for name in vlist:
1572 try:
1573 try:
1573 vdict[name] = eval(name, cf.f_globals, cf.f_locals)
1574 vdict[name] = eval(name, cf.f_globals, cf.f_locals)
1574 except Exception:
1575 except:
1575 print('Could not get variable %s from %s' %
1576 print('Could not get variable %s from %s' %
1576 (name,cf.f_code.co_name))
1577 (name,cf.f_code.co_name))
1577 else:
1578 else:
1578 raise ValueError('variables must be a dict/str/list/tuple')
1579 raise ValueError('variables must be a dict/str/list/tuple')
1579
1580
1580 # Propagate variables to user namespace
1581 # Propagate variables to user namespace
1581 self.user_ns.update(vdict)
1582 self.user_ns.update(vdict)
1582
1583
1583 # And configure interactive visibility
1584 # And configure interactive visibility
1584 user_ns_hidden = self.user_ns_hidden
1585 user_ns_hidden = self.user_ns_hidden
1585 if interactive:
1586 if interactive:
1586 for name in vdict:
1587 for name in vdict:
1587 user_ns_hidden.pop(name, None)
1588 user_ns_hidden.pop(name, None)
1588 else:
1589 else:
1589 user_ns_hidden.update(vdict)
1590 user_ns_hidden.update(vdict)
1590
1591
1591 def drop_by_id(self, variables):
1592 def drop_by_id(self, variables):
1592 """Remove a dict of variables from the user namespace, if they are the
1593 """Remove a dict of variables from the user namespace, if they are the
1593 same as the values in the dictionary.
1594 same as the values in the dictionary.
1594
1595
1595 This is intended for use by extensions: variables that they've added can
1596 This is intended for use by extensions: variables that they've added can
1596 be taken back out if they are unloaded, without removing any that the
1597 be taken back out if they are unloaded, without removing any that the
1597 user has overwritten.
1598 user has overwritten.
1598
1599
1599 Parameters
1600 Parameters
1600 ----------
1601 ----------
1601 variables : dict
1602 variables : dict
1602 A dictionary mapping object names (as strings) to the objects.
1603 A dictionary mapping object names (as strings) to the objects.
1603 """
1604 """
1604 for name, obj in variables.items():
1605 for name, obj in variables.items():
1605 if name in self.user_ns and self.user_ns[name] is obj:
1606 if name in self.user_ns and self.user_ns[name] is obj:
1606 del self.user_ns[name]
1607 del self.user_ns[name]
1607 self.user_ns_hidden.pop(name, None)
1608 self.user_ns_hidden.pop(name, None)
1608
1609
1609 #-------------------------------------------------------------------------
1610 #-------------------------------------------------------------------------
1610 # Things related to object introspection
1611 # Things related to object introspection
1611 #-------------------------------------------------------------------------
1612 #-------------------------------------------------------------------------
1612 @staticmethod
1613 @staticmethod
1613 def _find_parts(oname: str) -> Tuple[bool, ListType[str]]:
1614 def _find_parts(oname: str) -> Tuple[bool, ListType[str]]:
1614 """
1615 """
1615 Given an object name, return a list of parts of this object name.
1616 Given an object name, return a list of parts of this object name.
1616
1617
1617 Basically split on docs when using attribute access,
1618 Basically split on docs when using attribute access,
1618 and extract the value when using square bracket.
1619 and extract the value when using square bracket.
1619
1620
1620
1621
1621 For example foo.bar[3].baz[x] -> foo, bar, 3, baz, x
1622 For example foo.bar[3].baz[x] -> foo, bar, 3, baz, x
1622
1623
1623
1624
1624 Returns
1625 Returns
1625 -------
1626 -------
1626 parts_ok: bool
1627 parts_ok: bool
1627 whether we were properly able to parse parts.
1628 whether we were properly able to parse parts.
1628 parts: list of str
1629 parts: list of str
1629 extracted parts
1630 extracted parts
1630
1631
1631
1632
1632
1633
1633 """
1634 """
1634 raw_parts = oname.split(".")
1635 raw_parts = oname.split(".")
1635 parts = []
1636 parts = []
1636 parts_ok = True
1637 parts_ok = True
1637 for p in raw_parts:
1638 for p in raw_parts:
1638 if p.endswith("]"):
1639 if p.endswith("]"):
1639 var, *indices = p.split("[")
1640 var, *indices = p.split("[")
1640 if not var.isidentifier():
1641 if not var.isidentifier():
1641 parts_ok = False
1642 parts_ok = False
1642 break
1643 break
1643 parts.append(var)
1644 parts.append(var)
1644 for ind in indices:
1645 for ind in indices:
1645 if ind[-1] != "]" and not is_integer_string(ind[:-1]):
1646 if ind[-1] != "]" and not is_integer_string(ind[:-1]):
1646 parts_ok = False
1647 parts_ok = False
1647 break
1648 break
1648 parts.append(ind[:-1])
1649 parts.append(ind[:-1])
1649 continue
1650 continue
1650
1651
1651 if not p.isidentifier():
1652 if not p.isidentifier():
1652 parts_ok = False
1653 parts_ok = False
1653 parts.append(p)
1654 parts.append(p)
1654
1655
1655 return parts_ok, parts
1656 return parts_ok, parts
1656
1657
1657 def _ofind(
1658 def _ofind(
1658 self, oname: str, namespaces: Optional[Sequence[Tuple[str, AnyType]]] = None
1659 self, oname: str, namespaces: Optional[Sequence[Tuple[str, AnyType]]] = None
1659 ) -> OInfo:
1660 ) -> OInfo:
1660 """Find an object in the available namespaces.
1661 """Find an object in the available namespaces.
1661
1662
1662
1663
1663 Returns
1664 Returns
1664 -------
1665 -------
1665 OInfo with fields:
1666 OInfo with fields:
1666 - ismagic
1667 - ismagic
1667 - isalias
1668 - isalias
1668 - found
1669 - found
1669 - obj
1670 - obj
1670 - namespac
1671 - namespac
1671 - parent
1672 - parent
1672
1673
1673 Has special code to detect magic functions.
1674 Has special code to detect magic functions.
1674 """
1675 """
1675 oname = oname.strip()
1676 oname = oname.strip()
1676 parts_ok, parts = self._find_parts(oname)
1677 parts_ok, parts = self._find_parts(oname)
1677
1678
1678 if (
1679 if (
1679 not oname.startswith(ESC_MAGIC)
1680 not oname.startswith(ESC_MAGIC)
1680 and not oname.startswith(ESC_MAGIC2)
1681 and not oname.startswith(ESC_MAGIC2)
1681 and not parts_ok
1682 and not parts_ok
1682 ):
1683 ):
1683 return OInfo(
1684 return OInfo(
1684 ismagic=False,
1685 ismagic=False,
1685 isalias=False,
1686 isalias=False,
1686 found=False,
1687 found=False,
1687 obj=None,
1688 obj=None,
1688 namespace=None,
1689 namespace=None,
1689 parent=None,
1690 parent=None,
1690 )
1691 )
1691
1692
1692 if namespaces is None:
1693 if namespaces is None:
1693 # Namespaces to search in:
1694 # Namespaces to search in:
1694 # Put them in a list. The order is important so that we
1695 # Put them in a list. The order is important so that we
1695 # find things in the same order that Python finds them.
1696 # find things in the same order that Python finds them.
1696 namespaces = [ ('Interactive', self.user_ns),
1697 namespaces = [ ('Interactive', self.user_ns),
1697 ('Interactive (global)', self.user_global_ns),
1698 ('Interactive (global)', self.user_global_ns),
1698 ('Python builtin', builtin_mod.__dict__),
1699 ('Python builtin', builtin_mod.__dict__),
1699 ]
1700 ]
1700
1701
1701 ismagic = False
1702 ismagic = False
1702 isalias = False
1703 isalias = False
1703 found = False
1704 found = False
1704 ospace = None
1705 ospace = None
1705 parent = None
1706 parent = None
1706 obj = None
1707 obj = None
1707
1708
1708
1709
1709 # Look for the given name by splitting it in parts. If the head is
1710 # Look for the given name by splitting it in parts. If the head is
1710 # found, then we look for all the remaining parts as members, and only
1711 # found, then we look for all the remaining parts as members, and only
1711 # declare success if we can find them all.
1712 # declare success if we can find them all.
1712 oname_parts = parts
1713 oname_parts = parts
1713 oname_head, oname_rest = oname_parts[0],oname_parts[1:]
1714 oname_head, oname_rest = oname_parts[0],oname_parts[1:]
1714 for nsname,ns in namespaces:
1715 for nsname,ns in namespaces:
1715 try:
1716 try:
1716 obj = ns[oname_head]
1717 obj = ns[oname_head]
1717 except KeyError:
1718 except KeyError:
1718 continue
1719 continue
1719 else:
1720 else:
1720 for idx, part in enumerate(oname_rest):
1721 for idx, part in enumerate(oname_rest):
1721 try:
1722 try:
1722 parent = obj
1723 parent = obj
1723 # The last part is looked up in a special way to avoid
1724 # The last part is looked up in a special way to avoid
1724 # descriptor invocation as it may raise or have side
1725 # descriptor invocation as it may raise or have side
1725 # effects.
1726 # effects.
1726 if idx == len(oname_rest) - 1:
1727 if idx == len(oname_rest) - 1:
1727 obj = self._getattr_property(obj, part)
1728 obj = self._getattr_property(obj, part)
1728 else:
1729 else:
1729 if is_integer_string(part):
1730 if is_integer_string(part):
1730 obj = obj[int(part)]
1731 obj = obj[int(part)]
1731 else:
1732 else:
1732 obj = getattr(obj, part)
1733 obj = getattr(obj, part)
1733 except Exception:
1734 except:
1734 # Blanket except b/c some badly implemented objects
1735 # Blanket except b/c some badly implemented objects
1735 # allow __getattr__ to raise exceptions other than
1736 # allow __getattr__ to raise exceptions other than
1736 # AttributeError, which then crashes IPython.
1737 # AttributeError, which then crashes IPython.
1737 break
1738 break
1738 else:
1739 else:
1739 # If we finish the for loop (no break), we got all members
1740 # If we finish the for loop (no break), we got all members
1740 found = True
1741 found = True
1741 ospace = nsname
1742 ospace = nsname
1742 break # namespace loop
1743 break # namespace loop
1743
1744
1744 # Try to see if it's magic
1745 # Try to see if it's magic
1745 if not found:
1746 if not found:
1746 obj = None
1747 obj = None
1747 if oname.startswith(ESC_MAGIC2):
1748 if oname.startswith(ESC_MAGIC2):
1748 oname = oname.lstrip(ESC_MAGIC2)
1749 oname = oname.lstrip(ESC_MAGIC2)
1749 obj = self.find_cell_magic(oname)
1750 obj = self.find_cell_magic(oname)
1750 elif oname.startswith(ESC_MAGIC):
1751 elif oname.startswith(ESC_MAGIC):
1751 oname = oname.lstrip(ESC_MAGIC)
1752 oname = oname.lstrip(ESC_MAGIC)
1752 obj = self.find_line_magic(oname)
1753 obj = self.find_line_magic(oname)
1753 else:
1754 else:
1754 # search without prefix, so run? will find %run?
1755 # search without prefix, so run? will find %run?
1755 obj = self.find_line_magic(oname)
1756 obj = self.find_line_magic(oname)
1756 if obj is None:
1757 if obj is None:
1757 obj = self.find_cell_magic(oname)
1758 obj = self.find_cell_magic(oname)
1758 if obj is not None:
1759 if obj is not None:
1759 found = True
1760 found = True
1760 ospace = 'IPython internal'
1761 ospace = 'IPython internal'
1761 ismagic = True
1762 ismagic = True
1762 isalias = isinstance(obj, Alias)
1763 isalias = isinstance(obj, Alias)
1763
1764
1764 # Last try: special-case some literals like '', [], {}, etc:
1765 # Last try: special-case some literals like '', [], {}, etc:
1765 if not found and oname_head in ["''",'""','[]','{}','()']:
1766 if not found and oname_head in ["''",'""','[]','{}','()']:
1766 obj = eval(oname_head)
1767 obj = eval(oname_head)
1767 found = True
1768 found = True
1768 ospace = 'Interactive'
1769 ospace = 'Interactive'
1769
1770
1770 return OInfo(
1771 return OInfo(
1771 obj=obj,
1772 obj=obj,
1772 found=found,
1773 found=found,
1773 parent=parent,
1774 parent=parent,
1774 ismagic=ismagic,
1775 ismagic=ismagic,
1775 isalias=isalias,
1776 isalias=isalias,
1776 namespace=ospace,
1777 namespace=ospace,
1777 )
1778 )
1778
1779
1779 @staticmethod
1780 @staticmethod
1780 def _getattr_property(obj, attrname):
1781 def _getattr_property(obj, attrname):
1781 """Property-aware getattr to use in object finding.
1782 """Property-aware getattr to use in object finding.
1782
1783
1783 If attrname represents a property, return it unevaluated (in case it has
1784 If attrname represents a property, return it unevaluated (in case it has
1784 side effects or raises an error.
1785 side effects or raises an error.
1785
1786
1786 """
1787 """
1787 if not isinstance(obj, type):
1788 if not isinstance(obj, type):
1788 try:
1789 try:
1789 # `getattr(type(obj), attrname)` is not guaranteed to return
1790 # `getattr(type(obj), attrname)` is not guaranteed to return
1790 # `obj`, but does so for property:
1791 # `obj`, but does so for property:
1791 #
1792 #
1792 # property.__get__(self, None, cls) -> self
1793 # property.__get__(self, None, cls) -> self
1793 #
1794 #
1794 # The universal alternative is to traverse the mro manually
1795 # The universal alternative is to traverse the mro manually
1795 # searching for attrname in class dicts.
1796 # searching for attrname in class dicts.
1796 if is_integer_string(attrname):
1797 if is_integer_string(attrname):
1797 return obj[int(attrname)]
1798 return obj[int(attrname)]
1798 else:
1799 else:
1799 attr = getattr(type(obj), attrname)
1800 attr = getattr(type(obj), attrname)
1800 except AttributeError:
1801 except AttributeError:
1801 pass
1802 pass
1802 else:
1803 else:
1803 # This relies on the fact that data descriptors (with both
1804 # This relies on the fact that data descriptors (with both
1804 # __get__ & __set__ magic methods) take precedence over
1805 # __get__ & __set__ magic methods) take precedence over
1805 # instance-level attributes:
1806 # instance-level attributes:
1806 #
1807 #
1807 # class A(object):
1808 # class A(object):
1808 # @property
1809 # @property
1809 # def foobar(self): return 123
1810 # def foobar(self): return 123
1810 # a = A()
1811 # a = A()
1811 # a.__dict__['foobar'] = 345
1812 # a.__dict__['foobar'] = 345
1812 # a.foobar # == 123
1813 # a.foobar # == 123
1813 #
1814 #
1814 # So, a property may be returned right away.
1815 # So, a property may be returned right away.
1815 if isinstance(attr, property):
1816 if isinstance(attr, property):
1816 return attr
1817 return attr
1817
1818
1818 # Nothing helped, fall back.
1819 # Nothing helped, fall back.
1819 return getattr(obj, attrname)
1820 return getattr(obj, attrname)
1820
1821
1821 def _object_find(self, oname, namespaces=None) -> OInfo:
1822 def _object_find(self, oname, namespaces=None) -> OInfo:
1822 """Find an object and return a struct with info about it."""
1823 """Find an object and return a struct with info about it."""
1823 return self._ofind(oname, namespaces)
1824 return self._ofind(oname, namespaces)
1824
1825
1825 def _inspect(self, meth, oname: str, namespaces=None, **kw):
1826 def _inspect(self, meth, oname: str, namespaces=None, **kw):
1826 """Generic interface to the inspector system.
1827 """Generic interface to the inspector system.
1827
1828
1828 This function is meant to be called by pdef, pdoc & friends.
1829 This function is meant to be called by pdef, pdoc & friends.
1829 """
1830 """
1830 info: OInfo = self._object_find(oname, namespaces)
1831 info: OInfo = self._object_find(oname, namespaces)
1831 if self.sphinxify_docstring:
1832 if self.sphinxify_docstring:
1832 if sphinxify is None:
1833 if sphinxify is None:
1833 raise ImportError("Module ``docrepr`` required but missing")
1834 raise ImportError("Module ``docrepr`` required but missing")
1834 docformat = sphinxify(self.object_inspect(oname))
1835 docformat = sphinxify(self.object_inspect(oname))
1835 else:
1836 else:
1836 docformat = None
1837 docformat = None
1837 if info.found or hasattr(info.parent, oinspect.HOOK_NAME):
1838 if info.found or hasattr(info.parent, oinspect.HOOK_NAME):
1838 pmethod = getattr(self.inspector, meth)
1839 pmethod = getattr(self.inspector, meth)
1839 # TODO: only apply format_screen to the plain/text repr of the mime
1840 # TODO: only apply format_screen to the plain/text repr of the mime
1840 # bundle.
1841 # bundle.
1841 formatter = format_screen if info.ismagic else docformat
1842 formatter = format_screen if info.ismagic else docformat
1842 if meth == 'pdoc':
1843 if meth == 'pdoc':
1843 pmethod(info.obj, oname, formatter)
1844 pmethod(info.obj, oname, formatter)
1844 elif meth == 'pinfo':
1845 elif meth == 'pinfo':
1845 pmethod(
1846 pmethod(
1846 info.obj,
1847 info.obj,
1847 oname,
1848 oname,
1848 formatter,
1849 formatter,
1849 info,
1850 info,
1850 enable_html_pager=self.enable_html_pager,
1851 enable_html_pager=self.enable_html_pager,
1851 **kw,
1852 **kw,
1852 )
1853 )
1853 else:
1854 else:
1854 pmethod(info.obj, oname)
1855 pmethod(info.obj, oname)
1855 else:
1856 else:
1856 print('Object `%s` not found.' % oname)
1857 print('Object `%s` not found.' % oname)
1857 return 'not found' # so callers can take other action
1858 return 'not found' # so callers can take other action
1858
1859
1859 def object_inspect(self, oname, detail_level=0):
1860 def object_inspect(self, oname, detail_level=0):
1860 """Get object info about oname"""
1861 """Get object info about oname"""
1861 with self.builtin_trap:
1862 with self.builtin_trap:
1862 info = self._object_find(oname)
1863 info = self._object_find(oname)
1863 if info.found:
1864 if info.found:
1864 return self.inspector.info(info.obj, oname, info=info,
1865 return self.inspector.info(info.obj, oname, info=info,
1865 detail_level=detail_level
1866 detail_level=detail_level
1866 )
1867 )
1867 else:
1868 else:
1868 return oinspect.object_info(name=oname, found=False)
1869 return oinspect.object_info(name=oname, found=False)
1869
1870
1870 def object_inspect_text(self, oname, detail_level=0):
1871 def object_inspect_text(self, oname, detail_level=0):
1871 """Get object info as formatted text"""
1872 """Get object info as formatted text"""
1872 return self.object_inspect_mime(oname, detail_level)['text/plain']
1873 return self.object_inspect_mime(oname, detail_level)['text/plain']
1873
1874
1874 def object_inspect_mime(self, oname, detail_level=0, omit_sections=()):
1875 def object_inspect_mime(self, oname, detail_level=0, omit_sections=()):
1875 """Get object info as a mimebundle of formatted representations.
1876 """Get object info as a mimebundle of formatted representations.
1876
1877
1877 A mimebundle is a dictionary, keyed by mime-type.
1878 A mimebundle is a dictionary, keyed by mime-type.
1878 It must always have the key `'text/plain'`.
1879 It must always have the key `'text/plain'`.
1879 """
1880 """
1880 with self.builtin_trap:
1881 with self.builtin_trap:
1881 info = self._object_find(oname)
1882 info = self._object_find(oname)
1882 if info.found:
1883 if info.found:
1883 docformat = (
1884 docformat = (
1884 sphinxify(self.object_inspect(oname))
1885 sphinxify(self.object_inspect(oname))
1885 if self.sphinxify_docstring
1886 if self.sphinxify_docstring
1886 else None
1887 else None
1887 )
1888 )
1888 return self.inspector._get_info(
1889 return self.inspector._get_info(
1889 info.obj,
1890 info.obj,
1890 oname,
1891 oname,
1891 info=info,
1892 info=info,
1892 detail_level=detail_level,
1893 detail_level=detail_level,
1893 formatter=docformat,
1894 formatter=docformat,
1894 omit_sections=omit_sections,
1895 omit_sections=omit_sections,
1895 )
1896 )
1896 else:
1897 else:
1897 raise KeyError(oname)
1898 raise KeyError(oname)
1898
1899
1899 #-------------------------------------------------------------------------
1900 #-------------------------------------------------------------------------
1900 # Things related to history management
1901 # Things related to history management
1901 #-------------------------------------------------------------------------
1902 #-------------------------------------------------------------------------
1902
1903
1903 def init_history(self):
1904 def init_history(self):
1904 """Sets up the command history, and starts regular autosaves."""
1905 """Sets up the command history, and starts regular autosaves."""
1905 self.history_manager = HistoryManager(shell=self, parent=self)
1906 self.history_manager = HistoryManager(shell=self, parent=self)
1906 self.configurables.append(self.history_manager)
1907 self.configurables.append(self.history_manager)
1907
1908
1908 #-------------------------------------------------------------------------
1909 #-------------------------------------------------------------------------
1909 # Things related to exception handling and tracebacks (not debugging)
1910 # Things related to exception handling and tracebacks (not debugging)
1910 #-------------------------------------------------------------------------
1911 #-------------------------------------------------------------------------
1911
1912
1912 debugger_cls = InterruptiblePdb
1913 debugger_cls = InterruptiblePdb
1913
1914
1914 def init_traceback_handlers(self, custom_exceptions):
1915 def init_traceback_handlers(self, custom_exceptions):
1915 # Syntax error handler.
1916 # Syntax error handler.
1916 self.SyntaxTB = ultratb.SyntaxTB(color_scheme='NoColor', parent=self)
1917 self.SyntaxTB = ultratb.SyntaxTB(color_scheme='NoColor', parent=self)
1917
1918
1918 # The interactive one is initialized with an offset, meaning we always
1919 # The interactive one is initialized with an offset, meaning we always
1919 # want to remove the topmost item in the traceback, which is our own
1920 # want to remove the topmost item in the traceback, which is our own
1920 # internal code. Valid modes: ['Plain','Context','Verbose','Minimal']
1921 # internal code. Valid modes: ['Plain','Context','Verbose','Minimal']
1921 self.InteractiveTB = ultratb.AutoFormattedTB(mode = 'Plain',
1922 self.InteractiveTB = ultratb.AutoFormattedTB(mode = 'Plain',
1922 color_scheme='NoColor',
1923 color_scheme='NoColor',
1923 tb_offset = 1,
1924 tb_offset = 1,
1924 debugger_cls=self.debugger_cls, parent=self)
1925 debugger_cls=self.debugger_cls, parent=self)
1925
1926
1926 # The instance will store a pointer to the system-wide exception hook,
1927 # The instance will store a pointer to the system-wide exception hook,
1927 # so that runtime code (such as magics) can access it. This is because
1928 # so that runtime code (such as magics) can access it. This is because
1928 # during the read-eval loop, it may get temporarily overwritten.
1929 # during the read-eval loop, it may get temporarily overwritten.
1929 self.sys_excepthook = sys.excepthook
1930 self.sys_excepthook = sys.excepthook
1930
1931
1931 # and add any custom exception handlers the user may have specified
1932 # and add any custom exception handlers the user may have specified
1932 self.set_custom_exc(*custom_exceptions)
1933 self.set_custom_exc(*custom_exceptions)
1933
1934
1934 # Set the exception mode
1935 # Set the exception mode
1935 self.InteractiveTB.set_mode(mode=self.xmode)
1936 self.InteractiveTB.set_mode(mode=self.xmode)
1936
1937
1937 def set_custom_exc(self, exc_tuple, handler):
1938 def set_custom_exc(self, exc_tuple, handler):
1938 """set_custom_exc(exc_tuple, handler)
1939 """set_custom_exc(exc_tuple, handler)
1939
1940
1940 Set a custom exception handler, which will be called if any of the
1941 Set a custom exception handler, which will be called if any of the
1941 exceptions in exc_tuple occur in the mainloop (specifically, in the
1942 exceptions in exc_tuple occur in the mainloop (specifically, in the
1942 run_code() method).
1943 run_code() method).
1943
1944
1944 Parameters
1945 Parameters
1945 ----------
1946 ----------
1946 exc_tuple : tuple of exception classes
1947 exc_tuple : tuple of exception classes
1947 A *tuple* of exception classes, for which to call the defined
1948 A *tuple* of exception classes, for which to call the defined
1948 handler. It is very important that you use a tuple, and NOT A
1949 handler. It is very important that you use a tuple, and NOT A
1949 LIST here, because of the way Python's except statement works. If
1950 LIST here, because of the way Python's except statement works. If
1950 you only want to trap a single exception, use a singleton tuple::
1951 you only want to trap a single exception, use a singleton tuple::
1951
1952
1952 exc_tuple == (MyCustomException,)
1953 exc_tuple == (MyCustomException,)
1953
1954
1954 handler : callable
1955 handler : callable
1955 handler must have the following signature::
1956 handler must have the following signature::
1956
1957
1957 def my_handler(self, etype, value, tb, tb_offset=None):
1958 def my_handler(self, etype, value, tb, tb_offset=None):
1958 ...
1959 ...
1959 return structured_traceback
1960 return structured_traceback
1960
1961
1961 Your handler must return a structured traceback (a list of strings),
1962 Your handler must return a structured traceback (a list of strings),
1962 or None.
1963 or None.
1963
1964
1964 This will be made into an instance method (via types.MethodType)
1965 This will be made into an instance method (via types.MethodType)
1965 of IPython itself, and it will be called if any of the exceptions
1966 of IPython itself, and it will be called if any of the exceptions
1966 listed in the exc_tuple are caught. If the handler is None, an
1967 listed in the exc_tuple are caught. If the handler is None, an
1967 internal basic one is used, which just prints basic info.
1968 internal basic one is used, which just prints basic info.
1968
1969
1969 To protect IPython from crashes, if your handler ever raises an
1970 To protect IPython from crashes, if your handler ever raises an
1970 exception or returns an invalid result, it will be immediately
1971 exception or returns an invalid result, it will be immediately
1971 disabled.
1972 disabled.
1972
1973
1973 Notes
1974 Notes
1974 -----
1975 -----
1975 WARNING: by putting in your own exception handler into IPython's main
1976 WARNING: by putting in your own exception handler into IPython's main
1976 execution loop, you run a very good chance of nasty crashes. This
1977 execution loop, you run a very good chance of nasty crashes. This
1977 facility should only be used if you really know what you are doing.
1978 facility should only be used if you really know what you are doing.
1978 """
1979 """
1979
1980
1980 if not isinstance(exc_tuple, tuple):
1981 if not isinstance(exc_tuple, tuple):
1981 raise TypeError("The custom exceptions must be given as a tuple.")
1982 raise TypeError("The custom exceptions must be given as a tuple.")
1982
1983
1983 def dummy_handler(self, etype, value, tb, tb_offset=None):
1984 def dummy_handler(self, etype, value, tb, tb_offset=None):
1984 print('*** Simple custom exception handler ***')
1985 print('*** Simple custom exception handler ***')
1985 print('Exception type :', etype)
1986 print('Exception type :', etype)
1986 print('Exception value:', value)
1987 print('Exception value:', value)
1987 print('Traceback :', tb)
1988 print('Traceback :', tb)
1988
1989
1989 def validate_stb(stb):
1990 def validate_stb(stb):
1990 """validate structured traceback return type
1991 """validate structured traceback return type
1991
1992
1992 return type of CustomTB *should* be a list of strings, but allow
1993 return type of CustomTB *should* be a list of strings, but allow
1993 single strings or None, which are harmless.
1994 single strings or None, which are harmless.
1994
1995
1995 This function will *always* return a list of strings,
1996 This function will *always* return a list of strings,
1996 and will raise a TypeError if stb is inappropriate.
1997 and will raise a TypeError if stb is inappropriate.
1997 """
1998 """
1998 msg = "CustomTB must return list of strings, not %r" % stb
1999 msg = "CustomTB must return list of strings, not %r" % stb
1999 if stb is None:
2000 if stb is None:
2000 return []
2001 return []
2001 elif isinstance(stb, str):
2002 elif isinstance(stb, str):
2002 return [stb]
2003 return [stb]
2003 elif not isinstance(stb, list):
2004 elif not isinstance(stb, list):
2004 raise TypeError(msg)
2005 raise TypeError(msg)
2005 # it's a list
2006 # it's a list
2006 for line in stb:
2007 for line in stb:
2007 # check every element
2008 # check every element
2008 if not isinstance(line, str):
2009 if not isinstance(line, str):
2009 raise TypeError(msg)
2010 raise TypeError(msg)
2010 return stb
2011 return stb
2011
2012
2012 if handler is None:
2013 if handler is None:
2013 wrapped = dummy_handler
2014 wrapped = dummy_handler
2014 else:
2015 else:
2015 def wrapped(self,etype,value,tb,tb_offset=None):
2016 def wrapped(self,etype,value,tb,tb_offset=None):
2016 """wrap CustomTB handler, to protect IPython from user code
2017 """wrap CustomTB handler, to protect IPython from user code
2017
2018
2018 This makes it harder (but not impossible) for custom exception
2019 This makes it harder (but not impossible) for custom exception
2019 handlers to crash IPython.
2020 handlers to crash IPython.
2020 """
2021 """
2021 try:
2022 try:
2022 stb = handler(self,etype,value,tb,tb_offset=tb_offset)
2023 stb = handler(self,etype,value,tb,tb_offset=tb_offset)
2023 return validate_stb(stb)
2024 return validate_stb(stb)
2024 except Exception:
2025 except:
2025 # clear custom handler immediately
2026 # clear custom handler immediately
2026 self.set_custom_exc((), None)
2027 self.set_custom_exc((), None)
2027 print("Custom TB Handler failed, unregistering", file=sys.stderr)
2028 print("Custom TB Handler failed, unregistering", file=sys.stderr)
2028 # show the exception in handler first
2029 # show the exception in handler first
2029 stb = self.InteractiveTB.structured_traceback(*sys.exc_info())
2030 stb = self.InteractiveTB.structured_traceback(*sys.exc_info())
2030 print(self.InteractiveTB.stb2text(stb))
2031 print(self.InteractiveTB.stb2text(stb))
2031 print("The original exception:")
2032 print("The original exception:")
2032 stb = self.InteractiveTB.structured_traceback(
2033 stb = self.InteractiveTB.structured_traceback(
2033 etype, value, tb, tb_offset=tb_offset
2034 etype, value, tb, tb_offset=tb_offset
2034 )
2035 )
2035 return stb
2036 return stb
2036
2037
2037 self.CustomTB = types.MethodType(wrapped,self)
2038 self.CustomTB = types.MethodType(wrapped,self)
2038 self.custom_exceptions = exc_tuple
2039 self.custom_exceptions = exc_tuple
2039
2040
2040 def excepthook(self, etype, value, tb):
2041 def excepthook(self, etype, value, tb):
2041 """One more defense for GUI apps that call sys.excepthook.
2042 """One more defense for GUI apps that call sys.excepthook.
2042
2043
2043 GUI frameworks like wxPython trap exceptions and call
2044 GUI frameworks like wxPython trap exceptions and call
2044 sys.excepthook themselves. I guess this is a feature that
2045 sys.excepthook themselves. I guess this is a feature that
2045 enables them to keep running after exceptions that would
2046 enables them to keep running after exceptions that would
2046 otherwise kill their mainloop. This is a bother for IPython
2047 otherwise kill their mainloop. This is a bother for IPython
2047 which expects to catch all of the program exceptions with a try:
2048 which expects to catch all of the program exceptions with a try:
2048 except: statement.
2049 except: statement.
2049
2050
2050 Normally, IPython sets sys.excepthook to a CrashHandler instance, so if
2051 Normally, IPython sets sys.excepthook to a CrashHandler instance, so if
2051 any app directly invokes sys.excepthook, it will look to the user like
2052 any app directly invokes sys.excepthook, it will look to the user like
2052 IPython crashed. In order to work around this, we can disable the
2053 IPython crashed. In order to work around this, we can disable the
2053 CrashHandler and replace it with this excepthook instead, which prints a
2054 CrashHandler and replace it with this excepthook instead, which prints a
2054 regular traceback using our InteractiveTB. In this fashion, apps which
2055 regular traceback using our InteractiveTB. In this fashion, apps which
2055 call sys.excepthook will generate a regular-looking exception from
2056 call sys.excepthook will generate a regular-looking exception from
2056 IPython, and the CrashHandler will only be triggered by real IPython
2057 IPython, and the CrashHandler will only be triggered by real IPython
2057 crashes.
2058 crashes.
2058
2059
2059 This hook should be used sparingly, only in places which are not likely
2060 This hook should be used sparingly, only in places which are not likely
2060 to be true IPython errors.
2061 to be true IPython errors.
2061 """
2062 """
2062 self.showtraceback((etype, value, tb), tb_offset=0)
2063 self.showtraceback((etype, value, tb), tb_offset=0)
2063
2064
2064 def _get_exc_info(self, exc_tuple=None):
2065 def _get_exc_info(self, exc_tuple=None):
2065 """get exc_info from a given tuple, sys.exc_info() or sys.last_type etc.
2066 """get exc_info from a given tuple, sys.exc_info() or sys.last_type etc.
2066
2067
2067 Ensures sys.last_type,value,traceback hold the exc_info we found,
2068 Ensures sys.last_type,value,traceback hold the exc_info we found,
2068 from whichever source.
2069 from whichever source.
2069
2070
2070 raises ValueError if none of these contain any information
2071 raises ValueError if none of these contain any information
2071 """
2072 """
2072 if exc_tuple is None:
2073 if exc_tuple is None:
2073 etype, value, tb = sys.exc_info()
2074 etype, value, tb = sys.exc_info()
2074 else:
2075 else:
2075 etype, value, tb = exc_tuple
2076 etype, value, tb = exc_tuple
2076
2077
2077 if etype is None:
2078 if etype is None:
2078 if hasattr(sys, 'last_type'):
2079 if hasattr(sys, 'last_type'):
2079 etype, value, tb = sys.last_type, sys.last_value, \
2080 etype, value, tb = sys.last_type, sys.last_value, \
2080 sys.last_traceback
2081 sys.last_traceback
2081
2082
2082 if etype is None:
2083 if etype is None:
2083 raise ValueError("No exception to find")
2084 raise ValueError("No exception to find")
2084
2085
2085 # Now store the exception info in sys.last_type etc.
2086 # Now store the exception info in sys.last_type etc.
2086 # WARNING: these variables are somewhat deprecated and not
2087 # WARNING: these variables are somewhat deprecated and not
2087 # necessarily safe to use in a threaded environment, but tools
2088 # necessarily safe to use in a threaded environment, but tools
2088 # like pdb depend on their existence, so let's set them. If we
2089 # like pdb depend on their existence, so let's set them. If we
2089 # find problems in the field, we'll need to revisit their use.
2090 # find problems in the field, we'll need to revisit their use.
2090 sys.last_type = etype
2091 sys.last_type = etype
2091 sys.last_value = value
2092 sys.last_value = value
2092 sys.last_traceback = tb
2093 sys.last_traceback = tb
2093
2094
2094 return etype, value, tb
2095 return etype, value, tb
2095
2096
2096 def show_usage_error(self, exc):
2097 def show_usage_error(self, exc):
2097 """Show a short message for UsageErrors
2098 """Show a short message for UsageErrors
2098
2099
2099 These are special exceptions that shouldn't show a traceback.
2100 These are special exceptions that shouldn't show a traceback.
2100 """
2101 """
2101 print("UsageError: %s" % exc, file=sys.stderr)
2102 print("UsageError: %s" % exc, file=sys.stderr)
2102
2103
2103 def get_exception_only(self, exc_tuple=None):
2104 def get_exception_only(self, exc_tuple=None):
2104 """
2105 """
2105 Return as a string (ending with a newline) the exception that
2106 Return as a string (ending with a newline) the exception that
2106 just occurred, without any traceback.
2107 just occurred, without any traceback.
2107 """
2108 """
2108 etype, value, tb = self._get_exc_info(exc_tuple)
2109 etype, value, tb = self._get_exc_info(exc_tuple)
2109 msg = traceback.format_exception_only(etype, value)
2110 msg = traceback.format_exception_only(etype, value)
2110 return ''.join(msg)
2111 return ''.join(msg)
2111
2112
2112 def showtraceback(self, exc_tuple=None, filename=None, tb_offset=None,
2113 def showtraceback(self, exc_tuple=None, filename=None, tb_offset=None,
2113 exception_only=False, running_compiled_code=False):
2114 exception_only=False, running_compiled_code=False):
2114 """Display the exception that just occurred.
2115 """Display the exception that just occurred.
2115
2116
2116 If nothing is known about the exception, this is the method which
2117 If nothing is known about the exception, this is the method which
2117 should be used throughout the code for presenting user tracebacks,
2118 should be used throughout the code for presenting user tracebacks,
2118 rather than directly invoking the InteractiveTB object.
2119 rather than directly invoking the InteractiveTB object.
2119
2120
2120 A specific showsyntaxerror() also exists, but this method can take
2121 A specific showsyntaxerror() also exists, but this method can take
2121 care of calling it if needed, so unless you are explicitly catching a
2122 care of calling it if needed, so unless you are explicitly catching a
2122 SyntaxError exception, don't try to analyze the stack manually and
2123 SyntaxError exception, don't try to analyze the stack manually and
2123 simply call this method."""
2124 simply call this method."""
2124
2125
2125 try:
2126 try:
2126 try:
2127 try:
2127 etype, value, tb = self._get_exc_info(exc_tuple)
2128 etype, value, tb = self._get_exc_info(exc_tuple)
2128 except ValueError:
2129 except ValueError:
2129 print('No traceback available to show.', file=sys.stderr)
2130 print('No traceback available to show.', file=sys.stderr)
2130 return
2131 return
2131
2132
2132 if issubclass(etype, SyntaxError):
2133 if issubclass(etype, SyntaxError):
2133 # Though this won't be called by syntax errors in the input
2134 # Though this won't be called by syntax errors in the input
2134 # line, there may be SyntaxError cases with imported code.
2135 # line, there may be SyntaxError cases with imported code.
2135 self.showsyntaxerror(filename, running_compiled_code)
2136 self.showsyntaxerror(filename, running_compiled_code)
2136 elif etype is UsageError:
2137 elif etype is UsageError:
2137 self.show_usage_error(value)
2138 self.show_usage_error(value)
2138 else:
2139 else:
2139 if exception_only:
2140 if exception_only:
2140 stb = ['An exception has occurred, use %tb to see '
2141 stb = ['An exception has occurred, use %tb to see '
2141 'the full traceback.\n']
2142 'the full traceback.\n']
2142 stb.extend(self.InteractiveTB.get_exception_only(etype,
2143 stb.extend(self.InteractiveTB.get_exception_only(etype,
2143 value))
2144 value))
2144 else:
2145 else:
2145
2146
2146 def contains_exceptiongroup(val):
2147 def contains_exceptiongroup(val):
2147 if val is None:
2148 if val is None:
2148 return False
2149 return False
2149 return isinstance(
2150 return isinstance(
2150 val, BaseExceptionGroup
2151 val, BaseExceptionGroup
2151 ) or contains_exceptiongroup(val.__context__)
2152 ) or contains_exceptiongroup(val.__context__)
2152
2153
2153 if contains_exceptiongroup(value):
2154 if contains_exceptiongroup(value):
2154 # fall back to native exception formatting until ultratb
2155 # fall back to native exception formatting until ultratb
2155 # supports exception groups
2156 # supports exception groups
2156 traceback.print_exc()
2157 traceback.print_exc()
2157 else:
2158 else:
2158 try:
2159 try:
2159 # Exception classes can customise their traceback - we
2160 # Exception classes can customise their traceback - we
2160 # use this in IPython.parallel for exceptions occurring
2161 # use this in IPython.parallel for exceptions occurring
2161 # in the engines. This should return a list of strings.
2162 # in the engines. This should return a list of strings.
2162 if hasattr(value, "_render_traceback_"):
2163 if hasattr(value, "_render_traceback_"):
2163 stb = value._render_traceback_()
2164 stb = value._render_traceback_()
2164 else:
2165 else:
2165 stb = self.InteractiveTB.structured_traceback(
2166 stb = self.InteractiveTB.structured_traceback(
2166 etype, value, tb, tb_offset=tb_offset
2167 etype, value, tb, tb_offset=tb_offset
2167 )
2168 )
2168
2169
2169 except Exception:
2170 except Exception:
2170 print(
2171 print(
2171 "Unexpected exception formatting exception. Falling back to standard exception"
2172 "Unexpected exception formatting exception. Falling back to standard exception"
2172 )
2173 )
2173 traceback.print_exc()
2174 traceback.print_exc()
2174 return None
2175 return None
2175
2176
2176 self._showtraceback(etype, value, stb)
2177 self._showtraceback(etype, value, stb)
2177 if self.call_pdb:
2178 if self.call_pdb:
2178 # drop into debugger
2179 # drop into debugger
2179 self.debugger(force=True)
2180 self.debugger(force=True)
2180 return
2181 return
2181
2182
2182 # Actually show the traceback
2183 # Actually show the traceback
2183 self._showtraceback(etype, value, stb)
2184 self._showtraceback(etype, value, stb)
2184
2185
2185 except KeyboardInterrupt:
2186 except KeyboardInterrupt:
2186 print('\n' + self.get_exception_only(), file=sys.stderr)
2187 print('\n' + self.get_exception_only(), file=sys.stderr)
2187
2188
2188 def _showtraceback(self, etype, evalue, stb: str):
2189 def _showtraceback(self, etype, evalue, stb: str):
2189 """Actually show a traceback.
2190 """Actually show a traceback.
2190
2191
2191 Subclasses may override this method to put the traceback on a different
2192 Subclasses may override this method to put the traceback on a different
2192 place, like a side channel.
2193 place, like a side channel.
2193 """
2194 """
2194 val = self.InteractiveTB.stb2text(stb)
2195 val = self.InteractiveTB.stb2text(stb)
2195 try:
2196 try:
2196 print(val)
2197 print(val)
2197 except UnicodeEncodeError:
2198 except UnicodeEncodeError:
2198 print(val.encode("utf-8", "backslashreplace").decode())
2199 print(val.encode("utf-8", "backslashreplace").decode())
2199
2200
2200 def showsyntaxerror(self, filename=None, running_compiled_code=False):
2201 def showsyntaxerror(self, filename=None, running_compiled_code=False):
2201 """Display the syntax error that just occurred.
2202 """Display the syntax error that just occurred.
2202
2203
2203 This doesn't display a stack trace because there isn't one.
2204 This doesn't display a stack trace because there isn't one.
2204
2205
2205 If a filename is given, it is stuffed in the exception instead
2206 If a filename is given, it is stuffed in the exception instead
2206 of what was there before (because Python's parser always uses
2207 of what was there before (because Python's parser always uses
2207 "<string>" when reading from a string).
2208 "<string>" when reading from a string).
2208
2209
2209 If the syntax error occurred when running a compiled code (i.e. running_compile_code=True),
2210 If the syntax error occurred when running a compiled code (i.e. running_compile_code=True),
2210 longer stack trace will be displayed.
2211 longer stack trace will be displayed.
2211 """
2212 """
2212 etype, value, last_traceback = self._get_exc_info()
2213 etype, value, last_traceback = self._get_exc_info()
2213
2214
2214 if filename and issubclass(etype, SyntaxError):
2215 if filename and issubclass(etype, SyntaxError):
2215 try:
2216 try:
2216 value.filename = filename
2217 value.filename = filename
2217 except Exception:
2218 except:
2218 # Not the format we expect; leave it alone
2219 # Not the format we expect; leave it alone
2219 pass
2220 pass
2220
2221
2221 # If the error occurred when executing compiled code, we should provide full stacktrace.
2222 # If the error occurred when executing compiled code, we should provide full stacktrace.
2222 elist = traceback.extract_tb(last_traceback) if running_compiled_code else []
2223 elist = traceback.extract_tb(last_traceback) if running_compiled_code else []
2223 stb = self.SyntaxTB.structured_traceback(etype, value, elist)
2224 stb = self.SyntaxTB.structured_traceback(etype, value, elist)
2224 self._showtraceback(etype, value, stb)
2225 self._showtraceback(etype, value, stb)
2225
2226
2226 # This is overridden in TerminalInteractiveShell to show a message about
2227 # This is overridden in TerminalInteractiveShell to show a message about
2227 # the %paste magic.
2228 # the %paste magic.
2228 def showindentationerror(self):
2229 def showindentationerror(self):
2229 """Called by _run_cell when there's an IndentationError in code entered
2230 """Called by _run_cell when there's an IndentationError in code entered
2230 at the prompt.
2231 at the prompt.
2231
2232
2232 This is overridden in TerminalInteractiveShell to show a message about
2233 This is overridden in TerminalInteractiveShell to show a message about
2233 the %paste magic."""
2234 the %paste magic."""
2234 self.showsyntaxerror()
2235 self.showsyntaxerror()
2235
2236
2236 @skip_doctest
2237 @skip_doctest
2237 def set_next_input(self, s, replace=False):
2238 def set_next_input(self, s, replace=False):
2238 """ Sets the 'default' input string for the next command line.
2239 """ Sets the 'default' input string for the next command line.
2239
2240
2240 Example::
2241 Example::
2241
2242
2242 In [1]: _ip.set_next_input("Hello Word")
2243 In [1]: _ip.set_next_input("Hello Word")
2243 In [2]: Hello Word_ # cursor is here
2244 In [2]: Hello Word_ # cursor is here
2244 """
2245 """
2245 self.rl_next_input = s
2246 self.rl_next_input = s
2246
2247
2247 def _indent_current_str(self):
2248 def _indent_current_str(self):
2248 """return the current level of indentation as a string"""
2249 """return the current level of indentation as a string"""
2249 return self.input_splitter.get_indent_spaces() * ' '
2250 return self.input_splitter.get_indent_spaces() * ' '
2250
2251
2251 #-------------------------------------------------------------------------
2252 #-------------------------------------------------------------------------
2252 # Things related to text completion
2253 # Things related to text completion
2253 #-------------------------------------------------------------------------
2254 #-------------------------------------------------------------------------
2254
2255
2255 def init_completer(self):
2256 def init_completer(self):
2256 """Initialize the completion machinery.
2257 """Initialize the completion machinery.
2257
2258
2258 This creates completion machinery that can be used by client code,
2259 This creates completion machinery that can be used by client code,
2259 either interactively in-process (typically triggered by the readline
2260 either interactively in-process (typically triggered by the readline
2260 library), programmatically (such as in test suites) or out-of-process
2261 library), programmatically (such as in test suites) or out-of-process
2261 (typically over the network by remote frontends).
2262 (typically over the network by remote frontends).
2262 """
2263 """
2263 from IPython.core.completer import IPCompleter
2264 from IPython.core.completer import IPCompleter
2264 from IPython.core.completerlib import (
2265 from IPython.core.completerlib import (
2265 cd_completer,
2266 cd_completer,
2266 magic_run_completer,
2267 magic_run_completer,
2267 module_completer,
2268 module_completer,
2268 reset_completer,
2269 reset_completer,
2269 )
2270 )
2270
2271
2271 self.Completer = IPCompleter(shell=self,
2272 self.Completer = IPCompleter(shell=self,
2272 namespace=self.user_ns,
2273 namespace=self.user_ns,
2273 global_namespace=self.user_global_ns,
2274 global_namespace=self.user_global_ns,
2274 parent=self,
2275 parent=self,
2275 )
2276 )
2276 self.configurables.append(self.Completer)
2277 self.configurables.append(self.Completer)
2277
2278
2278 # Add custom completers to the basic ones built into IPCompleter
2279 # Add custom completers to the basic ones built into IPCompleter
2279 sdisp = self.strdispatchers.get('complete_command', StrDispatch())
2280 sdisp = self.strdispatchers.get('complete_command', StrDispatch())
2280 self.strdispatchers['complete_command'] = sdisp
2281 self.strdispatchers['complete_command'] = sdisp
2281 self.Completer.custom_completers = sdisp
2282 self.Completer.custom_completers = sdisp
2282
2283
2283 self.set_hook('complete_command', module_completer, str_key = 'import')
2284 self.set_hook('complete_command', module_completer, str_key = 'import')
2284 self.set_hook('complete_command', module_completer, str_key = 'from')
2285 self.set_hook('complete_command', module_completer, str_key = 'from')
2285 self.set_hook('complete_command', module_completer, str_key = '%aimport')
2286 self.set_hook('complete_command', module_completer, str_key = '%aimport')
2286 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
2287 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
2287 self.set_hook('complete_command', cd_completer, str_key = '%cd')
2288 self.set_hook('complete_command', cd_completer, str_key = '%cd')
2288 self.set_hook('complete_command', reset_completer, str_key = '%reset')
2289 self.set_hook('complete_command', reset_completer, str_key = '%reset')
2289
2290
2290 @skip_doctest
2291 @skip_doctest
2291 def complete(self, text, line=None, cursor_pos=None):
2292 def complete(self, text, line=None, cursor_pos=None):
2292 """Return the completed text and a list of completions.
2293 """Return the completed text and a list of completions.
2293
2294
2294 Parameters
2295 Parameters
2295 ----------
2296 ----------
2296 text : string
2297 text : string
2297 A string of text to be completed on. It can be given as empty and
2298 A string of text to be completed on. It can be given as empty and
2298 instead a line/position pair are given. In this case, the
2299 instead a line/position pair are given. In this case, the
2299 completer itself will split the line like readline does.
2300 completer itself will split the line like readline does.
2300 line : string, optional
2301 line : string, optional
2301 The complete line that text is part of.
2302 The complete line that text is part of.
2302 cursor_pos : int, optional
2303 cursor_pos : int, optional
2303 The position of the cursor on the input line.
2304 The position of the cursor on the input line.
2304
2305
2305 Returns
2306 Returns
2306 -------
2307 -------
2307 text : string
2308 text : string
2308 The actual text that was completed.
2309 The actual text that was completed.
2309 matches : list
2310 matches : list
2310 A sorted list with all possible completions.
2311 A sorted list with all possible completions.
2311
2312
2312 Notes
2313 Notes
2313 -----
2314 -----
2314 The optional arguments allow the completion to take more context into
2315 The optional arguments allow the completion to take more context into
2315 account, and are part of the low-level completion API.
2316 account, and are part of the low-level completion API.
2316
2317
2317 This is a wrapper around the completion mechanism, similar to what
2318 This is a wrapper around the completion mechanism, similar to what
2318 readline does at the command line when the TAB key is hit. By
2319 readline does at the command line when the TAB key is hit. By
2319 exposing it as a method, it can be used by other non-readline
2320 exposing it as a method, it can be used by other non-readline
2320 environments (such as GUIs) for text completion.
2321 environments (such as GUIs) for text completion.
2321
2322
2322 Examples
2323 Examples
2323 --------
2324 --------
2324 In [1]: x = 'hello'
2325 In [1]: x = 'hello'
2325
2326
2326 In [2]: _ip.complete('x.l')
2327 In [2]: _ip.complete('x.l')
2327 Out[2]: ('x.l', ['x.ljust', 'x.lower', 'x.lstrip'])
2328 Out[2]: ('x.l', ['x.ljust', 'x.lower', 'x.lstrip'])
2328 """
2329 """
2329
2330
2330 # Inject names into __builtin__ so we can complete on the added names.
2331 # Inject names into __builtin__ so we can complete on the added names.
2331 with self.builtin_trap:
2332 with self.builtin_trap:
2332 return self.Completer.complete(text, line, cursor_pos)
2333 return self.Completer.complete(text, line, cursor_pos)
2333
2334
2334 def set_custom_completer(self, completer, pos=0) -> None:
2335 def set_custom_completer(self, completer, pos=0) -> None:
2335 """Adds a new custom completer function.
2336 """Adds a new custom completer function.
2336
2337
2337 The position argument (defaults to 0) is the index in the completers
2338 The position argument (defaults to 0) is the index in the completers
2338 list where you want the completer to be inserted.
2339 list where you want the completer to be inserted.
2339
2340
2340 `completer` should have the following signature::
2341 `completer` should have the following signature::
2341
2342
2342 def completion(self: Completer, text: string) -> List[str]:
2343 def completion(self: Completer, text: string) -> List[str]:
2343 raise NotImplementedError
2344 raise NotImplementedError
2344
2345
2345 It will be bound to the current Completer instance and pass some text
2346 It will be bound to the current Completer instance and pass some text
2346 and return a list with current completions to suggest to the user.
2347 and return a list with current completions to suggest to the user.
2347 """
2348 """
2348
2349
2349 newcomp = types.MethodType(completer, self.Completer)
2350 newcomp = types.MethodType(completer, self.Completer)
2350 self.Completer.custom_matchers.insert(pos,newcomp)
2351 self.Completer.custom_matchers.insert(pos,newcomp)
2351
2352
2352 def set_completer_frame(self, frame=None):
2353 def set_completer_frame(self, frame=None):
2353 """Set the frame of the completer."""
2354 """Set the frame of the completer."""
2354 if frame:
2355 if frame:
2355 self.Completer.namespace = frame.f_locals
2356 self.Completer.namespace = frame.f_locals
2356 self.Completer.global_namespace = frame.f_globals
2357 self.Completer.global_namespace = frame.f_globals
2357 else:
2358 else:
2358 self.Completer.namespace = self.user_ns
2359 self.Completer.namespace = self.user_ns
2359 self.Completer.global_namespace = self.user_global_ns
2360 self.Completer.global_namespace = self.user_global_ns
2360
2361
2361 #-------------------------------------------------------------------------
2362 #-------------------------------------------------------------------------
2362 # Things related to magics
2363 # Things related to magics
2363 #-------------------------------------------------------------------------
2364 #-------------------------------------------------------------------------
2364
2365
2365 def init_magics(self):
2366 def init_magics(self):
2366 from IPython.core import magics as m
2367 from IPython.core import magics as m
2367 self.magics_manager = magic.MagicsManager(shell=self,
2368 self.magics_manager = magic.MagicsManager(shell=self,
2368 parent=self,
2369 parent=self,
2369 user_magics=m.UserMagics(self))
2370 user_magics=m.UserMagics(self))
2370 self.configurables.append(self.magics_manager)
2371 self.configurables.append(self.magics_manager)
2371
2372
2372 # Expose as public API from the magics manager
2373 # Expose as public API from the magics manager
2373 self.register_magics = self.magics_manager.register
2374 self.register_magics = self.magics_manager.register
2374
2375
2375 self.register_magics(m.AutoMagics, m.BasicMagics, m.CodeMagics,
2376 self.register_magics(m.AutoMagics, m.BasicMagics, m.CodeMagics,
2376 m.ConfigMagics, m.DisplayMagics, m.ExecutionMagics,
2377 m.ConfigMagics, m.DisplayMagics, m.ExecutionMagics,
2377 m.ExtensionMagics, m.HistoryMagics, m.LoggingMagics,
2378 m.ExtensionMagics, m.HistoryMagics, m.LoggingMagics,
2378 m.NamespaceMagics, m.OSMagics, m.PackagingMagics,
2379 m.NamespaceMagics, m.OSMagics, m.PackagingMagics,
2379 m.PylabMagics, m.ScriptMagics,
2380 m.PylabMagics, m.ScriptMagics,
2380 )
2381 )
2381 self.register_magics(m.AsyncMagics)
2382 self.register_magics(m.AsyncMagics)
2382
2383
2383 # Register Magic Aliases
2384 # Register Magic Aliases
2384 mman = self.magics_manager
2385 mman = self.magics_manager
2385 # FIXME: magic aliases should be defined by the Magics classes
2386 # FIXME: magic aliases should be defined by the Magics classes
2386 # or in MagicsManager, not here
2387 # or in MagicsManager, not here
2387 mman.register_alias('ed', 'edit')
2388 mman.register_alias('ed', 'edit')
2388 mman.register_alias('hist', 'history')
2389 mman.register_alias('hist', 'history')
2389 mman.register_alias('rep', 'recall')
2390 mman.register_alias('rep', 'recall')
2390 mman.register_alias('SVG', 'svg', 'cell')
2391 mman.register_alias('SVG', 'svg', 'cell')
2391 mman.register_alias('HTML', 'html', 'cell')
2392 mman.register_alias('HTML', 'html', 'cell')
2392 mman.register_alias('file', 'writefile', 'cell')
2393 mman.register_alias('file', 'writefile', 'cell')
2393
2394
2394 # FIXME: Move the color initialization to the DisplayHook, which
2395 # FIXME: Move the color initialization to the DisplayHook, which
2395 # should be split into a prompt manager and displayhook. We probably
2396 # should be split into a prompt manager and displayhook. We probably
2396 # even need a centralize colors management object.
2397 # even need a centralize colors management object.
2397 self.run_line_magic('colors', self.colors)
2398 self.run_line_magic('colors', self.colors)
2398
2399
2399 # Defined here so that it's included in the documentation
2400 # Defined here so that it's included in the documentation
2400 @functools.wraps(magic.MagicsManager.register_function)
2401 @functools.wraps(magic.MagicsManager.register_function)
2401 def register_magic_function(self, func, magic_kind='line', magic_name=None):
2402 def register_magic_function(self, func, magic_kind='line', magic_name=None):
2402 self.magics_manager.register_function(
2403 self.magics_manager.register_function(
2403 func, magic_kind=magic_kind, magic_name=magic_name
2404 func, magic_kind=magic_kind, magic_name=magic_name
2404 )
2405 )
2405
2406
2406 def _find_with_lazy_load(self, /, type_, magic_name: str):
2407 def _find_with_lazy_load(self, /, type_, magic_name: str):
2407 """
2408 """
2408 Try to find a magic potentially lazy-loading it.
2409 Try to find a magic potentially lazy-loading it.
2409
2410
2410 Parameters
2411 Parameters
2411 ----------
2412 ----------
2412
2413
2413 type_: "line"|"cell"
2414 type_: "line"|"cell"
2414 the type of magics we are trying to find/lazy load.
2415 the type of magics we are trying to find/lazy load.
2415 magic_name: str
2416 magic_name: str
2416 The name of the magic we are trying to find/lazy load
2417 The name of the magic we are trying to find/lazy load
2417
2418
2418
2419
2419 Note that this may have any side effects
2420 Note that this may have any side effects
2420 """
2421 """
2421 finder = {"line": self.find_line_magic, "cell": self.find_cell_magic}[type_]
2422 finder = {"line": self.find_line_magic, "cell": self.find_cell_magic}[type_]
2422 fn = finder(magic_name)
2423 fn = finder(magic_name)
2423 if fn is not None:
2424 if fn is not None:
2424 return fn
2425 return fn
2425 lazy = self.magics_manager.lazy_magics.get(magic_name)
2426 lazy = self.magics_manager.lazy_magics.get(magic_name)
2426 if lazy is None:
2427 if lazy is None:
2427 return None
2428 return None
2428
2429
2429 self.run_line_magic("load_ext", lazy)
2430 self.run_line_magic("load_ext", lazy)
2430 res = finder(magic_name)
2431 res = finder(magic_name)
2431 return res
2432 return res
2432
2433
2433 def run_line_magic(self, magic_name: str, line: str, _stack_depth=1):
2434 def run_line_magic(self, magic_name: str, line: str, _stack_depth=1):
2434 """Execute the given line magic.
2435 """Execute the given line magic.
2435
2436
2436 Parameters
2437 Parameters
2437 ----------
2438 ----------
2438 magic_name : str
2439 magic_name : str
2439 Name of the desired magic function, without '%' prefix.
2440 Name of the desired magic function, without '%' prefix.
2440 line : str
2441 line : str
2441 The rest of the input line as a single string.
2442 The rest of the input line as a single string.
2442 _stack_depth : int
2443 _stack_depth : int
2443 If run_line_magic() is called from magic() then _stack_depth=2.
2444 If run_line_magic() is called from magic() then _stack_depth=2.
2444 This is added to ensure backward compatibility for use of 'get_ipython().magic()'
2445 This is added to ensure backward compatibility for use of 'get_ipython().magic()'
2445 """
2446 """
2446 fn = self._find_with_lazy_load("line", magic_name)
2447 fn = self._find_with_lazy_load("line", magic_name)
2447 if fn is None:
2448 if fn is None:
2448 lazy = self.magics_manager.lazy_magics.get(magic_name)
2449 lazy = self.magics_manager.lazy_magics.get(magic_name)
2449 if lazy:
2450 if lazy:
2450 self.run_line_magic("load_ext", lazy)
2451 self.run_line_magic("load_ext", lazy)
2451 fn = self.find_line_magic(magic_name)
2452 fn = self.find_line_magic(magic_name)
2452 if fn is None:
2453 if fn is None:
2453 cm = self.find_cell_magic(magic_name)
2454 cm = self.find_cell_magic(magic_name)
2454 etpl = "Line magic function `%%%s` not found%s."
2455 etpl = "Line magic function `%%%s` not found%s."
2455 extra = '' if cm is None else (' (But cell magic `%%%%%s` exists, '
2456 extra = '' if cm is None else (' (But cell magic `%%%%%s` exists, '
2456 'did you mean that instead?)' % magic_name )
2457 'did you mean that instead?)' % magic_name )
2457 raise UsageError(etpl % (magic_name, extra))
2458 raise UsageError(etpl % (magic_name, extra))
2458 else:
2459 else:
2459 # Note: this is the distance in the stack to the user's frame.
2460 # Note: this is the distance in the stack to the user's frame.
2460 # This will need to be updated if the internal calling logic gets
2461 # This will need to be updated if the internal calling logic gets
2461 # refactored, or else we'll be expanding the wrong variables.
2462 # refactored, or else we'll be expanding the wrong variables.
2462
2463
2463 # Determine stack_depth depending on where run_line_magic() has been called
2464 # Determine stack_depth depending on where run_line_magic() has been called
2464 stack_depth = _stack_depth
2465 stack_depth = _stack_depth
2465 if getattr(fn, magic.MAGIC_NO_VAR_EXPAND_ATTR, False):
2466 if getattr(fn, magic.MAGIC_NO_VAR_EXPAND_ATTR, False):
2466 # magic has opted out of var_expand
2467 # magic has opted out of var_expand
2467 magic_arg_s = line
2468 magic_arg_s = line
2468 else:
2469 else:
2469 magic_arg_s = self.var_expand(line, stack_depth)
2470 magic_arg_s = self.var_expand(line, stack_depth)
2470 # Put magic args in a list so we can call with f(*a) syntax
2471 # Put magic args in a list so we can call with f(*a) syntax
2471 args = [magic_arg_s]
2472 args = [magic_arg_s]
2472 kwargs = {}
2473 kwargs = {}
2473 # Grab local namespace if we need it:
2474 # Grab local namespace if we need it:
2474 if getattr(fn, "needs_local_scope", False):
2475 if getattr(fn, "needs_local_scope", False):
2475 kwargs['local_ns'] = self.get_local_scope(stack_depth)
2476 kwargs['local_ns'] = self.get_local_scope(stack_depth)
2476 with self.builtin_trap:
2477 with self.builtin_trap:
2477 result = fn(*args, **kwargs)
2478 result = fn(*args, **kwargs)
2478
2479
2479 # The code below prevents the output from being displayed
2480 # The code below prevents the output from being displayed
2480 # when using magics with decorator @output_can_be_silenced
2481 # when using magics with decorator @output_can_be_silenced
2481 # when the last Python token in the expression is a ';'.
2482 # when the last Python token in the expression is a ';'.
2482 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):
2483 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):
2483 if DisplayHook.semicolon_at_end_of_expression(magic_arg_s):
2484 if DisplayHook.semicolon_at_end_of_expression(magic_arg_s):
2484 return None
2485 return None
2485
2486
2486 return result
2487 return result
2487
2488
2488 def get_local_scope(self, stack_depth):
2489 def get_local_scope(self, stack_depth):
2489 """Get local scope at given stack depth.
2490 """Get local scope at given stack depth.
2490
2491
2491 Parameters
2492 Parameters
2492 ----------
2493 ----------
2493 stack_depth : int
2494 stack_depth : int
2494 Depth relative to calling frame
2495 Depth relative to calling frame
2495 """
2496 """
2496 return sys._getframe(stack_depth + 1).f_locals
2497 return sys._getframe(stack_depth + 1).f_locals
2497
2498
2498 def run_cell_magic(self, magic_name, line, cell):
2499 def run_cell_magic(self, magic_name, line, cell):
2499 """Execute the given cell magic.
2500 """Execute the given cell magic.
2500
2501
2501 Parameters
2502 Parameters
2502 ----------
2503 ----------
2503 magic_name : str
2504 magic_name : str
2504 Name of the desired magic function, without '%' prefix.
2505 Name of the desired magic function, without '%' prefix.
2505 line : str
2506 line : str
2506 The rest of the first input line as a single string.
2507 The rest of the first input line as a single string.
2507 cell : str
2508 cell : str
2508 The body of the cell as a (possibly multiline) string.
2509 The body of the cell as a (possibly multiline) string.
2509 """
2510 """
2510 fn = self._find_with_lazy_load("cell", magic_name)
2511 fn = self._find_with_lazy_load("cell", magic_name)
2511 if fn is None:
2512 if fn is None:
2512 lm = self.find_line_magic(magic_name)
2513 lm = self.find_line_magic(magic_name)
2513 etpl = "Cell magic `%%{0}` not found{1}."
2514 etpl = "Cell magic `%%{0}` not found{1}."
2514 extra = '' if lm is None else (' (But line magic `%{0}` exists, '
2515 extra = '' if lm is None else (' (But line magic `%{0}` exists, '
2515 'did you mean that instead?)'.format(magic_name))
2516 'did you mean that instead?)'.format(magic_name))
2516 raise UsageError(etpl.format(magic_name, extra))
2517 raise UsageError(etpl.format(magic_name, extra))
2517 elif cell == '':
2518 elif cell == '':
2518 message = '%%{0} is a cell magic, but the cell body is empty.'.format(magic_name)
2519 message = '%%{0} is a cell magic, but the cell body is empty.'.format(magic_name)
2519 if self.find_line_magic(magic_name) is not None:
2520 if self.find_line_magic(magic_name) is not None:
2520 message += ' Did you mean the line magic %{0} (single %)?'.format(magic_name)
2521 message += ' Did you mean the line magic %{0} (single %)?'.format(magic_name)
2521 raise UsageError(message)
2522 raise UsageError(message)
2522 else:
2523 else:
2523 # Note: this is the distance in the stack to the user's frame.
2524 # Note: this is the distance in the stack to the user's frame.
2524 # This will need to be updated if the internal calling logic gets
2525 # This will need to be updated if the internal calling logic gets
2525 # refactored, or else we'll be expanding the wrong variables.
2526 # refactored, or else we'll be expanding the wrong variables.
2526 stack_depth = 2
2527 stack_depth = 2
2527 if getattr(fn, magic.MAGIC_NO_VAR_EXPAND_ATTR, False):
2528 if getattr(fn, magic.MAGIC_NO_VAR_EXPAND_ATTR, False):
2528 # magic has opted out of var_expand
2529 # magic has opted out of var_expand
2529 magic_arg_s = line
2530 magic_arg_s = line
2530 else:
2531 else:
2531 magic_arg_s = self.var_expand(line, stack_depth)
2532 magic_arg_s = self.var_expand(line, stack_depth)
2532 kwargs = {}
2533 kwargs = {}
2533 if getattr(fn, "needs_local_scope", False):
2534 if getattr(fn, "needs_local_scope", False):
2534 kwargs['local_ns'] = self.user_ns
2535 kwargs['local_ns'] = self.user_ns
2535
2536
2536 with self.builtin_trap:
2537 with self.builtin_trap:
2537 args = (magic_arg_s, cell)
2538 args = (magic_arg_s, cell)
2538 result = fn(*args, **kwargs)
2539 result = fn(*args, **kwargs)
2539
2540
2540 # The code below prevents the output from being displayed
2541 # The code below prevents the output from being displayed
2541 # when using magics with decorator @output_can_be_silenced
2542 # when using magics with decorator @output_can_be_silenced
2542 # when the last Python token in the expression is a ';'.
2543 # when the last Python token in the expression is a ';'.
2543 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):
2544 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):
2544 if DisplayHook.semicolon_at_end_of_expression(cell):
2545 if DisplayHook.semicolon_at_end_of_expression(cell):
2545 return None
2546 return None
2546
2547
2547 return result
2548 return result
2548
2549
2549 def find_line_magic(self, magic_name):
2550 def find_line_magic(self, magic_name):
2550 """Find and return a line magic by name.
2551 """Find and return a line magic by name.
2551
2552
2552 Returns None if the magic isn't found."""
2553 Returns None if the magic isn't found."""
2553 return self.magics_manager.magics['line'].get(magic_name)
2554 return self.magics_manager.magics['line'].get(magic_name)
2554
2555
2555 def find_cell_magic(self, magic_name):
2556 def find_cell_magic(self, magic_name):
2556 """Find and return a cell magic by name.
2557 """Find and return a cell magic by name.
2557
2558
2558 Returns None if the magic isn't found."""
2559 Returns None if the magic isn't found."""
2559 return self.magics_manager.magics['cell'].get(magic_name)
2560 return self.magics_manager.magics['cell'].get(magic_name)
2560
2561
2561 def find_magic(self, magic_name, magic_kind='line'):
2562 def find_magic(self, magic_name, magic_kind='line'):
2562 """Find and return a magic of the given type by name.
2563 """Find and return a magic of the given type by name.
2563
2564
2564 Returns None if the magic isn't found."""
2565 Returns None if the magic isn't found."""
2565 return self.magics_manager.magics[magic_kind].get(magic_name)
2566 return self.magics_manager.magics[magic_kind].get(magic_name)
2566
2567
2567 def magic(self, arg_s):
2568 def magic(self, arg_s):
2568 """
2569 """
2569 DEPRECATED
2570 DEPRECATED
2570
2571
2571 Deprecated since IPython 0.13 (warning added in
2572 Deprecated since IPython 0.13 (warning added in
2572 8.1), use run_line_magic(magic_name, parameter_s).
2573 8.1), use run_line_magic(magic_name, parameter_s).
2573
2574
2574 Call a magic function by name.
2575 Call a magic function by name.
2575
2576
2576 Input: a string containing the name of the magic function to call and
2577 Input: a string containing the name of the magic function to call and
2577 any additional arguments to be passed to the magic.
2578 any additional arguments to be passed to the magic.
2578
2579
2579 magic('name -opt foo bar') is equivalent to typing at the ipython
2580 magic('name -opt foo bar') is equivalent to typing at the ipython
2580 prompt:
2581 prompt:
2581
2582
2582 In[1]: %name -opt foo bar
2583 In[1]: %name -opt foo bar
2583
2584
2584 To call a magic without arguments, simply use magic('name').
2585 To call a magic without arguments, simply use magic('name').
2585
2586
2586 This provides a proper Python function to call IPython's magics in any
2587 This provides a proper Python function to call IPython's magics in any
2587 valid Python code you can type at the interpreter, including loops and
2588 valid Python code you can type at the interpreter, including loops and
2588 compound statements.
2589 compound statements.
2589 """
2590 """
2590 warnings.warn(
2591 warnings.warn(
2591 "`magic(...)` is deprecated since IPython 0.13 (warning added in "
2592 "`magic(...)` is deprecated since IPython 0.13 (warning added in "
2592 "8.1), use run_line_magic(magic_name, parameter_s).",
2593 "8.1), use run_line_magic(magic_name, parameter_s).",
2593 DeprecationWarning,
2594 DeprecationWarning,
2594 stacklevel=2,
2595 stacklevel=2,
2595 )
2596 )
2596 # TODO: should we issue a loud deprecation warning here?
2597 # TODO: should we issue a loud deprecation warning here?
2597 magic_name, _, magic_arg_s = arg_s.partition(' ')
2598 magic_name, _, magic_arg_s = arg_s.partition(' ')
2598 magic_name = magic_name.lstrip(prefilter.ESC_MAGIC)
2599 magic_name = magic_name.lstrip(prefilter.ESC_MAGIC)
2599 return self.run_line_magic(magic_name, magic_arg_s, _stack_depth=2)
2600 return self.run_line_magic(magic_name, magic_arg_s, _stack_depth=2)
2600
2601
2601 #-------------------------------------------------------------------------
2602 #-------------------------------------------------------------------------
2602 # Things related to macros
2603 # Things related to macros
2603 #-------------------------------------------------------------------------
2604 #-------------------------------------------------------------------------
2604
2605
2605 def define_macro(self, name, themacro):
2606 def define_macro(self, name, themacro):
2606 """Define a new macro
2607 """Define a new macro
2607
2608
2608 Parameters
2609 Parameters
2609 ----------
2610 ----------
2610 name : str
2611 name : str
2611 The name of the macro.
2612 The name of the macro.
2612 themacro : str or Macro
2613 themacro : str or Macro
2613 The action to do upon invoking the macro. If a string, a new
2614 The action to do upon invoking the macro. If a string, a new
2614 Macro object is created by passing the string to it.
2615 Macro object is created by passing the string to it.
2615 """
2616 """
2616
2617
2617 from IPython.core import macro
2618 from IPython.core import macro
2618
2619
2619 if isinstance(themacro, str):
2620 if isinstance(themacro, str):
2620 themacro = macro.Macro(themacro)
2621 themacro = macro.Macro(themacro)
2621 if not isinstance(themacro, macro.Macro):
2622 if not isinstance(themacro, macro.Macro):
2622 raise ValueError('A macro must be a string or a Macro instance.')
2623 raise ValueError('A macro must be a string or a Macro instance.')
2623 self.user_ns[name] = themacro
2624 self.user_ns[name] = themacro
2624
2625
2625 #-------------------------------------------------------------------------
2626 #-------------------------------------------------------------------------
2626 # Things related to the running of system commands
2627 # Things related to the running of system commands
2627 #-------------------------------------------------------------------------
2628 #-------------------------------------------------------------------------
2628
2629
2629 def system_piped(self, cmd):
2630 def system_piped(self, cmd):
2630 """Call the given cmd in a subprocess, piping stdout/err
2631 """Call the given cmd in a subprocess, piping stdout/err
2631
2632
2632 Parameters
2633 Parameters
2633 ----------
2634 ----------
2634 cmd : str
2635 cmd : str
2635 Command to execute (can not end in '&', as background processes are
2636 Command to execute (can not end in '&', as background processes are
2636 not supported. Should not be a command that expects input
2637 not supported. Should not be a command that expects input
2637 other than simple text.
2638 other than simple text.
2638 """
2639 """
2639 if cmd.rstrip().endswith('&'):
2640 if cmd.rstrip().endswith('&'):
2640 # this is *far* from a rigorous test
2641 # this is *far* from a rigorous test
2641 # We do not support backgrounding processes because we either use
2642 # We do not support backgrounding processes because we either use
2642 # pexpect or pipes to read from. Users can always just call
2643 # pexpect or pipes to read from. Users can always just call
2643 # os.system() or use ip.system=ip.system_raw
2644 # os.system() or use ip.system=ip.system_raw
2644 # if they really want a background process.
2645 # if they really want a background process.
2645 raise OSError("Background processes not supported.")
2646 raise OSError("Background processes not supported.")
2646
2647
2647 # we explicitly do NOT return the subprocess status code, because
2648 # we explicitly do NOT return the subprocess status code, because
2648 # a non-None value would trigger :func:`sys.displayhook` calls.
2649 # a non-None value would trigger :func:`sys.displayhook` calls.
2649 # Instead, we store the exit_code in user_ns.
2650 # Instead, we store the exit_code in user_ns.
2650 self.user_ns['_exit_code'] = system(self.var_expand(cmd, depth=1))
2651 self.user_ns['_exit_code'] = system(self.var_expand(cmd, depth=1))
2651
2652
2652 def system_raw(self, cmd):
2653 def system_raw(self, cmd):
2653 """Call the given cmd in a subprocess using os.system on Windows or
2654 """Call the given cmd in a subprocess using os.system on Windows or
2654 subprocess.call using the system shell on other platforms.
2655 subprocess.call using the system shell on other platforms.
2655
2656
2656 Parameters
2657 Parameters
2657 ----------
2658 ----------
2658 cmd : str
2659 cmd : str
2659 Command to execute.
2660 Command to execute.
2660 """
2661 """
2661 cmd = self.var_expand(cmd, depth=1)
2662 cmd = self.var_expand(cmd, depth=1)
2662 # warn if there is an IPython magic alternative.
2663 # warn if there is an IPython magic alternative.
2663 if cmd == "":
2664 if cmd == "":
2664 main_cmd = ""
2665 main_cmd = ""
2665 else:
2666 else:
2666 main_cmd = cmd.split()[0]
2667 main_cmd = cmd.split()[0]
2667 has_magic_alternatives = ("pip", "conda", "cd")
2668 has_magic_alternatives = ("pip", "conda", "cd")
2668
2669
2669 if main_cmd in has_magic_alternatives:
2670 if main_cmd in has_magic_alternatives:
2670 warnings.warn(
2671 warnings.warn(
2671 (
2672 (
2672 "You executed the system command !{0} which may not work "
2673 "You executed the system command !{0} which may not work "
2673 "as expected. Try the IPython magic %{0} instead."
2674 "as expected. Try the IPython magic %{0} instead."
2674 ).format(main_cmd)
2675 ).format(main_cmd)
2675 )
2676 )
2676
2677
2677 # protect os.system from UNC paths on Windows, which it can't handle:
2678 # protect os.system from UNC paths on Windows, which it can't handle:
2678 if sys.platform == 'win32':
2679 if sys.platform == 'win32':
2679 from IPython.utils._process_win32 import AvoidUNCPath
2680 from IPython.utils._process_win32 import AvoidUNCPath
2680 with AvoidUNCPath() as path:
2681 with AvoidUNCPath() as path:
2681 if path is not None:
2682 if path is not None:
2682 cmd = '"pushd %s &&"%s' % (path, cmd)
2683 cmd = '"pushd %s &&"%s' % (path, cmd)
2683 try:
2684 try:
2684 ec = os.system(cmd)
2685 ec = os.system(cmd)
2685 except KeyboardInterrupt:
2686 except KeyboardInterrupt:
2686 print('\n' + self.get_exception_only(), file=sys.stderr)
2687 print('\n' + self.get_exception_only(), file=sys.stderr)
2687 ec = -2
2688 ec = -2
2688 else:
2689 else:
2689 # For posix the result of the subprocess.call() below is an exit
2690 # For posix the result of the subprocess.call() below is an exit
2690 # code, which by convention is zero for success, positive for
2691 # code, which by convention is zero for success, positive for
2691 # program failure. Exit codes above 128 are reserved for signals,
2692 # program failure. Exit codes above 128 are reserved for signals,
2692 # and the formula for converting a signal to an exit code is usually
2693 # and the formula for converting a signal to an exit code is usually
2693 # signal_number+128. To more easily differentiate between exit
2694 # signal_number+128. To more easily differentiate between exit
2694 # codes and signals, ipython uses negative numbers. For instance
2695 # codes and signals, ipython uses negative numbers. For instance
2695 # since control-c is signal 2 but exit code 130, ipython's
2696 # since control-c is signal 2 but exit code 130, ipython's
2696 # _exit_code variable will read -2. Note that some shells like
2697 # _exit_code variable will read -2. Note that some shells like
2697 # csh and fish don't follow sh/bash conventions for exit codes.
2698 # csh and fish don't follow sh/bash conventions for exit codes.
2698 executable = os.environ.get('SHELL', None)
2699 executable = os.environ.get('SHELL', None)
2699 try:
2700 try:
2700 # Use env shell instead of default /bin/sh
2701 # Use env shell instead of default /bin/sh
2701 ec = subprocess.call(cmd, shell=True, executable=executable)
2702 ec = subprocess.call(cmd, shell=True, executable=executable)
2702 except KeyboardInterrupt:
2703 except KeyboardInterrupt:
2703 # intercept control-C; a long traceback is not useful here
2704 # intercept control-C; a long traceback is not useful here
2704 print('\n' + self.get_exception_only(), file=sys.stderr)
2705 print('\n' + self.get_exception_only(), file=sys.stderr)
2705 ec = 130
2706 ec = 130
2706 if ec > 128:
2707 if ec > 128:
2707 ec = -(ec - 128)
2708 ec = -(ec - 128)
2708
2709
2709 # We explicitly do NOT return the subprocess status code, because
2710 # We explicitly do NOT return the subprocess status code, because
2710 # a non-None value would trigger :func:`sys.displayhook` calls.
2711 # a non-None value would trigger :func:`sys.displayhook` calls.
2711 # Instead, we store the exit_code in user_ns. Note the semantics
2712 # Instead, we store the exit_code in user_ns. Note the semantics
2712 # of _exit_code: for control-c, _exit_code == -signal.SIGNIT,
2713 # of _exit_code: for control-c, _exit_code == -signal.SIGNIT,
2713 # but raising SystemExit(_exit_code) will give status 254!
2714 # but raising SystemExit(_exit_code) will give status 254!
2714 self.user_ns['_exit_code'] = ec
2715 self.user_ns['_exit_code'] = ec
2715
2716
2716 # use piped system by default, because it is better behaved
2717 # use piped system by default, because it is better behaved
2717 system = system_piped
2718 system = system_piped
2718
2719
2719 def getoutput(self, cmd, split=True, depth=0):
2720 def getoutput(self, cmd, split=True, depth=0):
2720 """Get output (possibly including stderr) from a subprocess.
2721 """Get output (possibly including stderr) from a subprocess.
2721
2722
2722 Parameters
2723 Parameters
2723 ----------
2724 ----------
2724 cmd : str
2725 cmd : str
2725 Command to execute (can not end in '&', as background processes are
2726 Command to execute (can not end in '&', as background processes are
2726 not supported.
2727 not supported.
2727 split : bool, optional
2728 split : bool, optional
2728 If True, split the output into an IPython SList. Otherwise, an
2729 If True, split the output into an IPython SList. Otherwise, an
2729 IPython LSString is returned. These are objects similar to normal
2730 IPython LSString is returned. These are objects similar to normal
2730 lists and strings, with a few convenience attributes for easier
2731 lists and strings, with a few convenience attributes for easier
2731 manipulation of line-based output. You can use '?' on them for
2732 manipulation of line-based output. You can use '?' on them for
2732 details.
2733 details.
2733 depth : int, optional
2734 depth : int, optional
2734 How many frames above the caller are the local variables which should
2735 How many frames above the caller are the local variables which should
2735 be expanded in the command string? The default (0) assumes that the
2736 be expanded in the command string? The default (0) assumes that the
2736 expansion variables are in the stack frame calling this function.
2737 expansion variables are in the stack frame calling this function.
2737 """
2738 """
2738 if cmd.rstrip().endswith('&'):
2739 if cmd.rstrip().endswith('&'):
2739 # this is *far* from a rigorous test
2740 # this is *far* from a rigorous test
2740 raise OSError("Background processes not supported.")
2741 raise OSError("Background processes not supported.")
2741 out = getoutput(self.var_expand(cmd, depth=depth+1))
2742 out = getoutput(self.var_expand(cmd, depth=depth+1))
2742 if split:
2743 if split:
2743 out = SList(out.splitlines())
2744 out = SList(out.splitlines())
2744 else:
2745 else:
2745 out = LSString(out)
2746 out = LSString(out)
2746 return out
2747 return out
2747
2748
2748 #-------------------------------------------------------------------------
2749 #-------------------------------------------------------------------------
2749 # Things related to aliases
2750 # Things related to aliases
2750 #-------------------------------------------------------------------------
2751 #-------------------------------------------------------------------------
2751
2752
2752 def init_alias(self):
2753 def init_alias(self):
2753 self.alias_manager = AliasManager(shell=self, parent=self)
2754 self.alias_manager = AliasManager(shell=self, parent=self)
2754 self.configurables.append(self.alias_manager)
2755 self.configurables.append(self.alias_manager)
2755
2756
2756 #-------------------------------------------------------------------------
2757 #-------------------------------------------------------------------------
2757 # Things related to extensions
2758 # Things related to extensions
2758 #-------------------------------------------------------------------------
2759 #-------------------------------------------------------------------------
2759
2760
2760 def init_extension_manager(self):
2761 def init_extension_manager(self):
2761 self.extension_manager = ExtensionManager(shell=self, parent=self)
2762 self.extension_manager = ExtensionManager(shell=self, parent=self)
2762 self.configurables.append(self.extension_manager)
2763 self.configurables.append(self.extension_manager)
2763
2764
2764 #-------------------------------------------------------------------------
2765 #-------------------------------------------------------------------------
2765 # Things related to payloads
2766 # Things related to payloads
2766 #-------------------------------------------------------------------------
2767 #-------------------------------------------------------------------------
2767
2768
2768 def init_payload(self):
2769 def init_payload(self):
2769 self.payload_manager = PayloadManager(parent=self)
2770 self.payload_manager = PayloadManager(parent=self)
2770 self.configurables.append(self.payload_manager)
2771 self.configurables.append(self.payload_manager)
2771
2772
2772 #-------------------------------------------------------------------------
2773 #-------------------------------------------------------------------------
2773 # Things related to the prefilter
2774 # Things related to the prefilter
2774 #-------------------------------------------------------------------------
2775 #-------------------------------------------------------------------------
2775
2776
2776 def init_prefilter(self):
2777 def init_prefilter(self):
2777 self.prefilter_manager = PrefilterManager(shell=self, parent=self)
2778 self.prefilter_manager = PrefilterManager(shell=self, parent=self)
2778 self.configurables.append(self.prefilter_manager)
2779 self.configurables.append(self.prefilter_manager)
2779 # Ultimately this will be refactored in the new interpreter code, but
2780 # Ultimately this will be refactored in the new interpreter code, but
2780 # for now, we should expose the main prefilter method (there's legacy
2781 # for now, we should expose the main prefilter method (there's legacy
2781 # code out there that may rely on this).
2782 # code out there that may rely on this).
2782 self.prefilter = self.prefilter_manager.prefilter_lines
2783 self.prefilter = self.prefilter_manager.prefilter_lines
2783
2784
2784 def auto_rewrite_input(self, cmd):
2785 def auto_rewrite_input(self, cmd):
2785 """Print to the screen the rewritten form of the user's command.
2786 """Print to the screen the rewritten form of the user's command.
2786
2787
2787 This shows visual feedback by rewriting input lines that cause
2788 This shows visual feedback by rewriting input lines that cause
2788 automatic calling to kick in, like::
2789 automatic calling to kick in, like::
2789
2790
2790 /f x
2791 /f x
2791
2792
2792 into::
2793 into::
2793
2794
2794 ------> f(x)
2795 ------> f(x)
2795
2796
2796 after the user's input prompt. This helps the user understand that the
2797 after the user's input prompt. This helps the user understand that the
2797 input line was transformed automatically by IPython.
2798 input line was transformed automatically by IPython.
2798 """
2799 """
2799 if not self.show_rewritten_input:
2800 if not self.show_rewritten_input:
2800 return
2801 return
2801
2802
2802 # This is overridden in TerminalInteractiveShell to use fancy prompts
2803 # This is overridden in TerminalInteractiveShell to use fancy prompts
2803 print("------> " + cmd)
2804 print("------> " + cmd)
2804
2805
2805 #-------------------------------------------------------------------------
2806 #-------------------------------------------------------------------------
2806 # Things related to extracting values/expressions from kernel and user_ns
2807 # Things related to extracting values/expressions from kernel and user_ns
2807 #-------------------------------------------------------------------------
2808 #-------------------------------------------------------------------------
2808
2809
2809 def _user_obj_error(self):
2810 def _user_obj_error(self):
2810 """return simple exception dict
2811 """return simple exception dict
2811
2812
2812 for use in user_expressions
2813 for use in user_expressions
2813 """
2814 """
2814
2815
2815 etype, evalue, tb = self._get_exc_info()
2816 etype, evalue, tb = self._get_exc_info()
2816 stb = self.InteractiveTB.get_exception_only(etype, evalue)
2817 stb = self.InteractiveTB.get_exception_only(etype, evalue)
2817
2818
2818 exc_info = {
2819 exc_info = {
2819 "status": "error",
2820 "status": "error",
2820 "traceback": stb,
2821 "traceback": stb,
2821 "ename": etype.__name__,
2822 "ename": etype.__name__,
2822 "evalue": py3compat.safe_unicode(evalue),
2823 "evalue": py3compat.safe_unicode(evalue),
2823 }
2824 }
2824
2825
2825 return exc_info
2826 return exc_info
2826
2827
2827 def _format_user_obj(self, obj):
2828 def _format_user_obj(self, obj):
2828 """format a user object to display dict
2829 """format a user object to display dict
2829
2830
2830 for use in user_expressions
2831 for use in user_expressions
2831 """
2832 """
2832
2833
2833 data, md = self.display_formatter.format(obj)
2834 data, md = self.display_formatter.format(obj)
2834 value = {
2835 value = {
2835 'status' : 'ok',
2836 'status' : 'ok',
2836 'data' : data,
2837 'data' : data,
2837 'metadata' : md,
2838 'metadata' : md,
2838 }
2839 }
2839 return value
2840 return value
2840
2841
2841 def user_expressions(self, expressions):
2842 def user_expressions(self, expressions):
2842 """Evaluate a dict of expressions in the user's namespace.
2843 """Evaluate a dict of expressions in the user's namespace.
2843
2844
2844 Parameters
2845 Parameters
2845 ----------
2846 ----------
2846 expressions : dict
2847 expressions : dict
2847 A dict with string keys and string values. The expression values
2848 A dict with string keys and string values. The expression values
2848 should be valid Python expressions, each of which will be evaluated
2849 should be valid Python expressions, each of which will be evaluated
2849 in the user namespace.
2850 in the user namespace.
2850
2851
2851 Returns
2852 Returns
2852 -------
2853 -------
2853 A dict, keyed like the input expressions dict, with the rich mime-typed
2854 A dict, keyed like the input expressions dict, with the rich mime-typed
2854 display_data of each value.
2855 display_data of each value.
2855 """
2856 """
2856 out = {}
2857 out = {}
2857 user_ns = self.user_ns
2858 user_ns = self.user_ns
2858 global_ns = self.user_global_ns
2859 global_ns = self.user_global_ns
2859
2860
2860 for key, expr in expressions.items():
2861 for key, expr in expressions.items():
2861 try:
2862 try:
2862 value = self._format_user_obj(eval(expr, global_ns, user_ns))
2863 value = self._format_user_obj(eval(expr, global_ns, user_ns))
2863 except Exception:
2864 except:
2864 value = self._user_obj_error()
2865 value = self._user_obj_error()
2865 out[key] = value
2866 out[key] = value
2866 return out
2867 return out
2867
2868
2868 #-------------------------------------------------------------------------
2869 #-------------------------------------------------------------------------
2869 # Things related to the running of code
2870 # Things related to the running of code
2870 #-------------------------------------------------------------------------
2871 #-------------------------------------------------------------------------
2871
2872
2872 def ex(self, cmd):
2873 def ex(self, cmd):
2873 """Execute a normal python statement in user namespace."""
2874 """Execute a normal python statement in user namespace."""
2874 with self.builtin_trap:
2875 with self.builtin_trap:
2875 exec(cmd, self.user_global_ns, self.user_ns)
2876 exec(cmd, self.user_global_ns, self.user_ns)
2876
2877
2877 def ev(self, expr):
2878 def ev(self, expr):
2878 """Evaluate python expression expr in user namespace.
2879 """Evaluate python expression expr in user namespace.
2879
2880
2880 Returns the result of evaluation
2881 Returns the result of evaluation
2881 """
2882 """
2882 with self.builtin_trap:
2883 with self.builtin_trap:
2883 return eval(expr, self.user_global_ns, self.user_ns)
2884 return eval(expr, self.user_global_ns, self.user_ns)
2884
2885
2885 def safe_execfile(self, fname, *where, exit_ignore=False, raise_exceptions=False, shell_futures=False):
2886 def safe_execfile(self, fname, *where, exit_ignore=False, raise_exceptions=False, shell_futures=False):
2886 """A safe version of the builtin execfile().
2887 """A safe version of the builtin execfile().
2887
2888
2888 This version will never throw an exception, but instead print
2889 This version will never throw an exception, but instead print
2889 helpful error messages to the screen. This only works on pure
2890 helpful error messages to the screen. This only works on pure
2890 Python files with the .py extension.
2891 Python files with the .py extension.
2891
2892
2892 Parameters
2893 Parameters
2893 ----------
2894 ----------
2894 fname : string
2895 fname : string
2895 The name of the file to be executed.
2896 The name of the file to be executed.
2896 *where : tuple
2897 *where : tuple
2897 One or two namespaces, passed to execfile() as (globals,locals).
2898 One or two namespaces, passed to execfile() as (globals,locals).
2898 If only one is given, it is passed as both.
2899 If only one is given, it is passed as both.
2899 exit_ignore : bool (False)
2900 exit_ignore : bool (False)
2900 If True, then silence SystemExit for non-zero status (it is always
2901 If True, then silence SystemExit for non-zero status (it is always
2901 silenced for zero status, as it is so common).
2902 silenced for zero status, as it is so common).
2902 raise_exceptions : bool (False)
2903 raise_exceptions : bool (False)
2903 If True raise exceptions everywhere. Meant for testing.
2904 If True raise exceptions everywhere. Meant for testing.
2904 shell_futures : bool (False)
2905 shell_futures : bool (False)
2905 If True, the code will share future statements with the interactive
2906 If True, the code will share future statements with the interactive
2906 shell. It will both be affected by previous __future__ imports, and
2907 shell. It will both be affected by previous __future__ imports, and
2907 any __future__ imports in the code will affect the shell. If False,
2908 any __future__ imports in the code will affect the shell. If False,
2908 __future__ imports are not shared in either direction.
2909 __future__ imports are not shared in either direction.
2909
2910
2910 """
2911 """
2911 fname = Path(fname).expanduser().resolve()
2912 fname = Path(fname).expanduser().resolve()
2912
2913
2913 # Make sure we can open the file
2914 # Make sure we can open the file
2914 try:
2915 try:
2915 with fname.open("rb"):
2916 with fname.open("rb"):
2916 pass
2917 pass
2917 except Exception:
2918 except:
2918 warn('Could not open file <%s> for safe execution.' % fname)
2919 warn('Could not open file <%s> for safe execution.' % fname)
2919 return
2920 return
2920
2921
2921 # Find things also in current directory. This is needed to mimic the
2922 # Find things also in current directory. This is needed to mimic the
2922 # behavior of running a script from the system command line, where
2923 # behavior of running a script from the system command line, where
2923 # Python inserts the script's directory into sys.path
2924 # Python inserts the script's directory into sys.path
2924 dname = str(fname.parent)
2925 dname = str(fname.parent)
2925
2926
2926 with prepended_to_syspath(dname), self.builtin_trap:
2927 with prepended_to_syspath(dname), self.builtin_trap:
2927 try:
2928 try:
2928 glob, loc = (where + (None, ))[:2]
2929 glob, loc = (where + (None, ))[:2]
2929 py3compat.execfile(
2930 py3compat.execfile(
2930 fname, glob, loc,
2931 fname, glob, loc,
2931 self.compile if shell_futures else None)
2932 self.compile if shell_futures else None)
2932 except SystemExit as status:
2933 except SystemExit as status:
2933 # If the call was made with 0 or None exit status (sys.exit(0)
2934 # If the call was made with 0 or None exit status (sys.exit(0)
2934 # or sys.exit() ), don't bother showing a traceback, as both of
2935 # or sys.exit() ), don't bother showing a traceback, as both of
2935 # these are considered normal by the OS:
2936 # these are considered normal by the OS:
2936 # > python -c'import sys;sys.exit(0)'; echo $?
2937 # > python -c'import sys;sys.exit(0)'; echo $?
2937 # 0
2938 # 0
2938 # > python -c'import sys;sys.exit()'; echo $?
2939 # > python -c'import sys;sys.exit()'; echo $?
2939 # 0
2940 # 0
2940 # For other exit status, we show the exception unless
2941 # For other exit status, we show the exception unless
2941 # explicitly silenced, but only in short form.
2942 # explicitly silenced, but only in short form.
2942 if status.code:
2943 if status.code:
2943 if raise_exceptions:
2944 if raise_exceptions:
2944 raise
2945 raise
2945 if not exit_ignore:
2946 if not exit_ignore:
2946 self.showtraceback(exception_only=True)
2947 self.showtraceback(exception_only=True)
2947 except Exception:
2948 except:
2948 if raise_exceptions:
2949 if raise_exceptions:
2949 raise
2950 raise
2950 # tb offset is 2 because we wrap execfile
2951 # tb offset is 2 because we wrap execfile
2951 self.showtraceback(tb_offset=2)
2952 self.showtraceback(tb_offset=2)
2952
2953
2953 def safe_execfile_ipy(self, fname, shell_futures=False, raise_exceptions=False):
2954 def safe_execfile_ipy(self, fname, shell_futures=False, raise_exceptions=False):
2954 """Like safe_execfile, but for .ipy or .ipynb files with IPython syntax.
2955 """Like safe_execfile, but for .ipy or .ipynb files with IPython syntax.
2955
2956
2956 Parameters
2957 Parameters
2957 ----------
2958 ----------
2958 fname : str
2959 fname : str
2959 The name of the file to execute. The filename must have a
2960 The name of the file to execute. The filename must have a
2960 .ipy or .ipynb extension.
2961 .ipy or .ipynb extension.
2961 shell_futures : bool (False)
2962 shell_futures : bool (False)
2962 If True, the code will share future statements with the interactive
2963 If True, the code will share future statements with the interactive
2963 shell. It will both be affected by previous __future__ imports, and
2964 shell. It will both be affected by previous __future__ imports, and
2964 any __future__ imports in the code will affect the shell. If False,
2965 any __future__ imports in the code will affect the shell. If False,
2965 __future__ imports are not shared in either direction.
2966 __future__ imports are not shared in either direction.
2966 raise_exceptions : bool (False)
2967 raise_exceptions : bool (False)
2967 If True raise exceptions everywhere. Meant for testing.
2968 If True raise exceptions everywhere. Meant for testing.
2968 """
2969 """
2969 fname = Path(fname).expanduser().resolve()
2970 fname = Path(fname).expanduser().resolve()
2970
2971
2971 # Make sure we can open the file
2972 # Make sure we can open the file
2972 try:
2973 try:
2973 with fname.open("rb"):
2974 with fname.open("rb"):
2974 pass
2975 pass
2975 except Exception:
2976 except:
2976 warn('Could not open file <%s> for safe execution.' % fname)
2977 warn('Could not open file <%s> for safe execution.' % fname)
2977 return
2978 return
2978
2979
2979 # Find things also in current directory. This is needed to mimic the
2980 # Find things also in current directory. This is needed to mimic the
2980 # behavior of running a script from the system command line, where
2981 # behavior of running a script from the system command line, where
2981 # Python inserts the script's directory into sys.path
2982 # Python inserts the script's directory into sys.path
2982 dname = str(fname.parent)
2983 dname = str(fname.parent)
2983
2984
2984 def get_cells():
2985 def get_cells():
2985 """generator for sequence of code blocks to run"""
2986 """generator for sequence of code blocks to run"""
2986 if fname.suffix == ".ipynb":
2987 if fname.suffix == ".ipynb":
2987 from nbformat import read
2988 from nbformat import read
2988 nb = read(fname, as_version=4)
2989 nb = read(fname, as_version=4)
2989 if not nb.cells:
2990 if not nb.cells:
2990 return
2991 return
2991 for cell in nb.cells:
2992 for cell in nb.cells:
2992 if cell.cell_type == 'code':
2993 if cell.cell_type == 'code':
2993 yield cell.source
2994 yield cell.source
2994 else:
2995 else:
2995 yield fname.read_text(encoding="utf-8")
2996 yield fname.read_text(encoding="utf-8")
2996
2997
2997 with prepended_to_syspath(dname):
2998 with prepended_to_syspath(dname):
2998 try:
2999 try:
2999 for cell in get_cells():
3000 for cell in get_cells():
3000 result = self.run_cell(cell, silent=True, shell_futures=shell_futures)
3001 result = self.run_cell(cell, silent=True, shell_futures=shell_futures)
3001 if raise_exceptions:
3002 if raise_exceptions:
3002 result.raise_error()
3003 result.raise_error()
3003 elif not result.success:
3004 elif not result.success:
3004 break
3005 break
3005 except Exception:
3006 except:
3006 if raise_exceptions:
3007 if raise_exceptions:
3007 raise
3008 raise
3008 self.showtraceback()
3009 self.showtraceback()
3009 warn('Unknown failure executing file: <%s>' % fname)
3010 warn('Unknown failure executing file: <%s>' % fname)
3010
3011
3011 def safe_run_module(self, mod_name, where):
3012 def safe_run_module(self, mod_name, where):
3012 """A safe version of runpy.run_module().
3013 """A safe version of runpy.run_module().
3013
3014
3014 This version will never throw an exception, but instead print
3015 This version will never throw an exception, but instead print
3015 helpful error messages to the screen.
3016 helpful error messages to the screen.
3016
3017
3017 `SystemExit` exceptions with status code 0 or None are ignored.
3018 `SystemExit` exceptions with status code 0 or None are ignored.
3018
3019
3019 Parameters
3020 Parameters
3020 ----------
3021 ----------
3021 mod_name : string
3022 mod_name : string
3022 The name of the module to be executed.
3023 The name of the module to be executed.
3023 where : dict
3024 where : dict
3024 The globals namespace.
3025 The globals namespace.
3025 """
3026 """
3026 try:
3027 try:
3027 try:
3028 try:
3028 where.update(
3029 where.update(
3029 runpy.run_module(str(mod_name), run_name="__main__",
3030 runpy.run_module(str(mod_name), run_name="__main__",
3030 alter_sys=True)
3031 alter_sys=True)
3031 )
3032 )
3032 except SystemExit as status:
3033 except SystemExit as status:
3033 if status.code:
3034 if status.code:
3034 raise
3035 raise
3035 except Exception:
3036 except:
3036 self.showtraceback()
3037 self.showtraceback()
3037 warn('Unknown failure executing module: <%s>' % mod_name)
3038 warn('Unknown failure executing module: <%s>' % mod_name)
3038
3039
3039 def run_cell(
3040 def run_cell(
3040 self,
3041 self,
3041 raw_cell,
3042 raw_cell,
3042 store_history=False,
3043 store_history=False,
3043 silent=False,
3044 silent=False,
3044 shell_futures=True,
3045 shell_futures=True,
3045 cell_id=None,
3046 cell_id=None,
3046 ):
3047 ):
3047 """Run a complete IPython cell.
3048 """Run a complete IPython cell.
3048
3049
3049 Parameters
3050 Parameters
3050 ----------
3051 ----------
3051 raw_cell : str
3052 raw_cell : str
3052 The code (including IPython code such as %magic functions) to run.
3053 The code (including IPython code such as %magic functions) to run.
3053 store_history : bool
3054 store_history : bool
3054 If True, the raw and translated cell will be stored in IPython's
3055 If True, the raw and translated cell will be stored in IPython's
3055 history. For user code calling back into IPython's machinery, this
3056 history. For user code calling back into IPython's machinery, this
3056 should be set to False.
3057 should be set to False.
3057 silent : bool
3058 silent : bool
3058 If True, avoid side-effects, such as implicit displayhooks and
3059 If True, avoid side-effects, such as implicit displayhooks and
3059 and logging. silent=True forces store_history=False.
3060 and logging. silent=True forces store_history=False.
3060 shell_futures : bool
3061 shell_futures : bool
3061 If True, the code will share future statements with the interactive
3062 If True, the code will share future statements with the interactive
3062 shell. It will both be affected by previous __future__ imports, and
3063 shell. It will both be affected by previous __future__ imports, and
3063 any __future__ imports in the code will affect the shell. If False,
3064 any __future__ imports in the code will affect the shell. If False,
3064 __future__ imports are not shared in either direction.
3065 __future__ imports are not shared in either direction.
3065
3066
3066 Returns
3067 Returns
3067 -------
3068 -------
3068 result : :class:`ExecutionResult`
3069 result : :class:`ExecutionResult`
3069 """
3070 """
3070 result = None
3071 result = None
3071 try:
3072 try:
3072 result = self._run_cell(
3073 result = self._run_cell(
3073 raw_cell, store_history, silent, shell_futures, cell_id
3074 raw_cell, store_history, silent, shell_futures, cell_id
3074 )
3075 )
3075 finally:
3076 finally:
3076 self.events.trigger('post_execute')
3077 self.events.trigger('post_execute')
3077 if not silent:
3078 if not silent:
3078 self.events.trigger('post_run_cell', result)
3079 self.events.trigger('post_run_cell', result)
3079 return result
3080 return result
3080
3081
3081 def _run_cell(
3082 def _run_cell(
3082 self,
3083 self,
3083 raw_cell: str,
3084 raw_cell: str,
3084 store_history: bool,
3085 store_history: bool,
3085 silent: bool,
3086 silent: bool,
3086 shell_futures: bool,
3087 shell_futures: bool,
3087 cell_id: str,
3088 cell_id: str,
3088 ) -> ExecutionResult:
3089 ) -> ExecutionResult:
3089 """Internal method to run a complete IPython cell."""
3090 """Internal method to run a complete IPython cell."""
3090
3091
3091 # we need to avoid calling self.transform_cell multiple time on the same thing
3092 # we need to avoid calling self.transform_cell multiple time on the same thing
3092 # so we need to store some results:
3093 # so we need to store some results:
3093 preprocessing_exc_tuple = None
3094 preprocessing_exc_tuple = None
3094 try:
3095 try:
3095 transformed_cell = self.transform_cell(raw_cell)
3096 transformed_cell = self.transform_cell(raw_cell)
3096 except Exception:
3097 except Exception:
3097 transformed_cell = raw_cell
3098 transformed_cell = raw_cell
3098 preprocessing_exc_tuple = sys.exc_info()
3099 preprocessing_exc_tuple = sys.exc_info()
3099
3100
3100 assert transformed_cell is not None
3101 assert transformed_cell is not None
3101 coro = self.run_cell_async(
3102 coro = self.run_cell_async(
3102 raw_cell,
3103 raw_cell,
3103 store_history=store_history,
3104 store_history=store_history,
3104 silent=silent,
3105 silent=silent,
3105 shell_futures=shell_futures,
3106 shell_futures=shell_futures,
3106 transformed_cell=transformed_cell,
3107 transformed_cell=transformed_cell,
3107 preprocessing_exc_tuple=preprocessing_exc_tuple,
3108 preprocessing_exc_tuple=preprocessing_exc_tuple,
3108 cell_id=cell_id,
3109 cell_id=cell_id,
3109 )
3110 )
3110
3111
3111 # run_cell_async is async, but may not actually need an eventloop.
3112 # run_cell_async is async, but may not actually need an eventloop.
3112 # when this is the case, we want to run it using the pseudo_sync_runner
3113 # when this is the case, we want to run it using the pseudo_sync_runner
3113 # so that code can invoke eventloops (for example via the %run , and
3114 # so that code can invoke eventloops (for example via the %run , and
3114 # `%paste` magic.
3115 # `%paste` magic.
3115 if self.trio_runner:
3116 if self.trio_runner:
3116 runner = self.trio_runner
3117 runner = self.trio_runner
3117 elif self.should_run_async(
3118 elif self.should_run_async(
3118 raw_cell,
3119 raw_cell,
3119 transformed_cell=transformed_cell,
3120 transformed_cell=transformed_cell,
3120 preprocessing_exc_tuple=preprocessing_exc_tuple,
3121 preprocessing_exc_tuple=preprocessing_exc_tuple,
3121 ):
3122 ):
3122 runner = self.loop_runner
3123 runner = self.loop_runner
3123 else:
3124 else:
3124 runner = _pseudo_sync_runner
3125 runner = _pseudo_sync_runner
3125
3126
3126 try:
3127 try:
3127 result = runner(coro)
3128 result = runner(coro)
3128 except BaseException as e:
3129 except BaseException as e:
3129 info = ExecutionInfo(
3130 info = ExecutionInfo(
3130 raw_cell, store_history, silent, shell_futures, cell_id
3131 raw_cell, store_history, silent, shell_futures, cell_id
3131 )
3132 )
3132 result = ExecutionResult(info)
3133 result = ExecutionResult(info)
3133 result.error_in_exec = e
3134 result.error_in_exec = e
3134 self.showtraceback(running_compiled_code=True)
3135 self.showtraceback(running_compiled_code=True)
3135 finally:
3136 finally:
3136 return result
3137 return result
3137
3138
3138 def should_run_async(
3139 def should_run_async(
3139 self, raw_cell: str, *, transformed_cell=None, preprocessing_exc_tuple=None
3140 self, raw_cell: str, *, transformed_cell=None, preprocessing_exc_tuple=None
3140 ) -> bool:
3141 ) -> bool:
3141 """Return whether a cell should be run asynchronously via a coroutine runner
3142 """Return whether a cell should be run asynchronously via a coroutine runner
3142
3143
3143 Parameters
3144 Parameters
3144 ----------
3145 ----------
3145 raw_cell : str
3146 raw_cell : str
3146 The code to be executed
3147 The code to be executed
3147
3148
3148 Returns
3149 Returns
3149 -------
3150 -------
3150 result: bool
3151 result: bool
3151 Whether the code needs to be run with a coroutine runner or not
3152 Whether the code needs to be run with a coroutine runner or not
3152 .. versionadded:: 7.0
3153 .. versionadded:: 7.0
3153 """
3154 """
3154 if not self.autoawait:
3155 if not self.autoawait:
3155 return False
3156 return False
3156 if preprocessing_exc_tuple is not None:
3157 if preprocessing_exc_tuple is not None:
3157 return False
3158 return False
3158 assert preprocessing_exc_tuple is None
3159 assert preprocessing_exc_tuple is None
3159 if transformed_cell is None:
3160 if transformed_cell is None:
3160 warnings.warn(
3161 warnings.warn(
3161 "`should_run_async` will not call `transform_cell`"
3162 "`should_run_async` will not call `transform_cell`"
3162 " automatically in the future. Please pass the result to"
3163 " automatically in the future. Please pass the result to"
3163 " `transformed_cell` argument and any exception that happen"
3164 " `transformed_cell` argument and any exception that happen"
3164 " during the"
3165 " during the"
3165 "transform in `preprocessing_exc_tuple` in"
3166 "transform in `preprocessing_exc_tuple` in"
3166 " IPython 7.17 and above.",
3167 " IPython 7.17 and above.",
3167 DeprecationWarning,
3168 DeprecationWarning,
3168 stacklevel=2,
3169 stacklevel=2,
3169 )
3170 )
3170 try:
3171 try:
3171 cell = self.transform_cell(raw_cell)
3172 cell = self.transform_cell(raw_cell)
3172 except Exception:
3173 except Exception:
3173 # any exception during transform will be raised
3174 # any exception during transform will be raised
3174 # prior to execution
3175 # prior to execution
3175 return False
3176 return False
3176 else:
3177 else:
3177 cell = transformed_cell
3178 cell = transformed_cell
3178 return _should_be_async(cell)
3179 return _should_be_async(cell)
3179
3180
3180 async def run_cell_async(
3181 async def run_cell_async(
3181 self,
3182 self,
3182 raw_cell: str,
3183 raw_cell: str,
3183 store_history=False,
3184 store_history=False,
3184 silent=False,
3185 silent=False,
3185 shell_futures=True,
3186 shell_futures=True,
3186 *,
3187 *,
3187 transformed_cell: Optional[str] = None,
3188 transformed_cell: Optional[str] = None,
3188 preprocessing_exc_tuple: Optional[AnyType] = None,
3189 preprocessing_exc_tuple: Optional[AnyType] = None,
3189 cell_id=None,
3190 cell_id=None,
3190 ) -> ExecutionResult:
3191 ) -> ExecutionResult:
3191 """Run a complete IPython cell asynchronously.
3192 """Run a complete IPython cell asynchronously.
3192
3193
3193 Parameters
3194 Parameters
3194 ----------
3195 ----------
3195 raw_cell : str
3196 raw_cell : str
3196 The code (including IPython code such as %magic functions) to run.
3197 The code (including IPython code such as %magic functions) to run.
3197 store_history : bool
3198 store_history : bool
3198 If True, the raw and translated cell will be stored in IPython's
3199 If True, the raw and translated cell will be stored in IPython's
3199 history. For user code calling back into IPython's machinery, this
3200 history. For user code calling back into IPython's machinery, this
3200 should be set to False.
3201 should be set to False.
3201 silent : bool
3202 silent : bool
3202 If True, avoid side-effects, such as implicit displayhooks and
3203 If True, avoid side-effects, such as implicit displayhooks and
3203 and logging. silent=True forces store_history=False.
3204 and logging. silent=True forces store_history=False.
3204 shell_futures : bool
3205 shell_futures : bool
3205 If True, the code will share future statements with the interactive
3206 If True, the code will share future statements with the interactive
3206 shell. It will both be affected by previous __future__ imports, and
3207 shell. It will both be affected by previous __future__ imports, and
3207 any __future__ imports in the code will affect the shell. If False,
3208 any __future__ imports in the code will affect the shell. If False,
3208 __future__ imports are not shared in either direction.
3209 __future__ imports are not shared in either direction.
3209 transformed_cell: str
3210 transformed_cell: str
3210 cell that was passed through transformers
3211 cell that was passed through transformers
3211 preprocessing_exc_tuple:
3212 preprocessing_exc_tuple:
3212 trace if the transformation failed.
3213 trace if the transformation failed.
3213
3214
3214 Returns
3215 Returns
3215 -------
3216 -------
3216 result : :class:`ExecutionResult`
3217 result : :class:`ExecutionResult`
3217
3218
3218 .. versionadded:: 7.0
3219 .. versionadded:: 7.0
3219 """
3220 """
3220 info = ExecutionInfo(raw_cell, store_history, silent, shell_futures, cell_id)
3221 info = ExecutionInfo(raw_cell, store_history, silent, shell_futures, cell_id)
3221 result = ExecutionResult(info)
3222 result = ExecutionResult(info)
3222
3223
3223 if (not raw_cell) or raw_cell.isspace():
3224 if (not raw_cell) or raw_cell.isspace():
3224 self.last_execution_succeeded = True
3225 self.last_execution_succeeded = True
3225 self.last_execution_result = result
3226 self.last_execution_result = result
3226 return result
3227 return result
3227
3228
3228 if silent:
3229 if silent:
3229 store_history = False
3230 store_history = False
3230
3231
3231 if store_history:
3232 if store_history:
3232 result.execution_count = self.execution_count
3233 result.execution_count = self.execution_count
3233
3234
3234 def error_before_exec(value):
3235 def error_before_exec(value):
3235 if store_history:
3236 if store_history:
3236 self.execution_count += 1
3237 self.execution_count += 1
3237 result.error_before_exec = value
3238 result.error_before_exec = value
3238 self.last_execution_succeeded = False
3239 self.last_execution_succeeded = False
3239 self.last_execution_result = result
3240 self.last_execution_result = result
3240 return result
3241 return result
3241
3242
3242 self.events.trigger('pre_execute')
3243 self.events.trigger('pre_execute')
3243 if not silent:
3244 if not silent:
3244 self.events.trigger('pre_run_cell', info)
3245 self.events.trigger('pre_run_cell', info)
3245
3246
3246 if transformed_cell is None:
3247 if transformed_cell is None:
3247 warnings.warn(
3248 warnings.warn(
3248 "`run_cell_async` will not call `transform_cell`"
3249 "`run_cell_async` will not call `transform_cell`"
3249 " automatically in the future. Please pass the result to"
3250 " automatically in the future. Please pass the result to"
3250 " `transformed_cell` argument and any exception that happen"
3251 " `transformed_cell` argument and any exception that happen"
3251 " during the"
3252 " during the"
3252 "transform in `preprocessing_exc_tuple` in"
3253 "transform in `preprocessing_exc_tuple` in"
3253 " IPython 7.17 and above.",
3254 " IPython 7.17 and above.",
3254 DeprecationWarning,
3255 DeprecationWarning,
3255 stacklevel=2,
3256 stacklevel=2,
3256 )
3257 )
3257 # If any of our input transformation (input_transformer_manager or
3258 # If any of our input transformation (input_transformer_manager or
3258 # prefilter_manager) raises an exception, we store it in this variable
3259 # prefilter_manager) raises an exception, we store it in this variable
3259 # so that we can display the error after logging the input and storing
3260 # so that we can display the error after logging the input and storing
3260 # it in the history.
3261 # it in the history.
3261 try:
3262 try:
3262 cell = self.transform_cell(raw_cell)
3263 cell = self.transform_cell(raw_cell)
3263 except Exception:
3264 except Exception:
3264 preprocessing_exc_tuple = sys.exc_info()
3265 preprocessing_exc_tuple = sys.exc_info()
3265 cell = raw_cell # cell has to exist so it can be stored/logged
3266 cell = raw_cell # cell has to exist so it can be stored/logged
3266 else:
3267 else:
3267 preprocessing_exc_tuple = None
3268 preprocessing_exc_tuple = None
3268 else:
3269 else:
3269 if preprocessing_exc_tuple is None:
3270 if preprocessing_exc_tuple is None:
3270 cell = transformed_cell
3271 cell = transformed_cell
3271 else:
3272 else:
3272 cell = raw_cell
3273 cell = raw_cell
3273
3274
3274 # Do NOT store paste/cpaste magic history
3275 # Do NOT store paste/cpaste magic history
3275 if "get_ipython().run_line_magic(" in cell and "paste" in cell:
3276 if "get_ipython().run_line_magic(" in cell and "paste" in cell:
3276 store_history = False
3277 store_history = False
3277
3278
3278 # Store raw and processed history
3279 # Store raw and processed history
3279 if store_history:
3280 if store_history:
3280 assert self.history_manager is not None
3281 assert self.history_manager is not None
3281 self.history_manager.store_inputs(self.execution_count, cell, raw_cell)
3282 self.history_manager.store_inputs(self.execution_count, cell, raw_cell)
3282 if not silent:
3283 if not silent:
3283 self.logger.log(cell, raw_cell)
3284 self.logger.log(cell, raw_cell)
3284
3285
3285 # Display the exception if input processing failed.
3286 # Display the exception if input processing failed.
3286 if preprocessing_exc_tuple is not None:
3287 if preprocessing_exc_tuple is not None:
3287 self.showtraceback(preprocessing_exc_tuple)
3288 self.showtraceback(preprocessing_exc_tuple)
3288 if store_history:
3289 if store_history:
3289 self.execution_count += 1
3290 self.execution_count += 1
3290 return error_before_exec(preprocessing_exc_tuple[1])
3291 return error_before_exec(preprocessing_exc_tuple[1])
3291
3292
3292 # Our own compiler remembers the __future__ environment. If we want to
3293 # Our own compiler remembers the __future__ environment. If we want to
3293 # run code with a separate __future__ environment, use the default
3294 # run code with a separate __future__ environment, use the default
3294 # compiler
3295 # compiler
3295 compiler = self.compile if shell_futures else self.compiler_class()
3296 compiler = self.compile if shell_futures else self.compiler_class()
3296
3297
3297 with self.builtin_trap:
3298 with self.builtin_trap:
3298 cell_name = compiler.cache(cell, self.execution_count, raw_code=raw_cell)
3299 cell_name = compiler.cache(cell, self.execution_count, raw_code=raw_cell)
3299
3300
3300 with self.display_trap:
3301 with self.display_trap:
3301 # Compile to bytecode
3302 # Compile to bytecode
3302 try:
3303 try:
3303 code_ast = compiler.ast_parse(cell, filename=cell_name)
3304 code_ast = compiler.ast_parse(cell, filename=cell_name)
3304 except self.custom_exceptions as e:
3305 except self.custom_exceptions as e:
3305 etype, value, tb = sys.exc_info()
3306 etype, value, tb = sys.exc_info()
3306 self.CustomTB(etype, value, tb)
3307 self.CustomTB(etype, value, tb)
3307 return error_before_exec(e)
3308 return error_before_exec(e)
3308 except IndentationError as e:
3309 except IndentationError as e:
3309 self.showindentationerror()
3310 self.showindentationerror()
3310 return error_before_exec(e)
3311 return error_before_exec(e)
3311 except (OverflowError, SyntaxError, ValueError, TypeError,
3312 except (OverflowError, SyntaxError, ValueError, TypeError,
3312 MemoryError) as e:
3313 MemoryError) as e:
3313 self.showsyntaxerror()
3314 self.showsyntaxerror()
3314 return error_before_exec(e)
3315 return error_before_exec(e)
3315
3316
3316 # Apply AST transformations
3317 # Apply AST transformations
3317 try:
3318 try:
3318 code_ast = self.transform_ast(code_ast)
3319 code_ast = self.transform_ast(code_ast)
3319 except InputRejected as e:
3320 except InputRejected as e:
3320 self.showtraceback()
3321 self.showtraceback()
3321 return error_before_exec(e)
3322 return error_before_exec(e)
3322
3323
3323 # Give the displayhook a reference to our ExecutionResult so it
3324 # Give the displayhook a reference to our ExecutionResult so it
3324 # can fill in the output value.
3325 # can fill in the output value.
3325 self.displayhook.exec_result = result
3326 self.displayhook.exec_result = result
3326
3327
3327 # Execute the user code
3328 # Execute the user code
3328 interactivity = "none" if silent else self.ast_node_interactivity
3329 interactivity = "none" if silent else self.ast_node_interactivity
3329
3330
3330
3331
3331 has_raised = await self.run_ast_nodes(code_ast.body, cell_name,
3332 has_raised = await self.run_ast_nodes(code_ast.body, cell_name,
3332 interactivity=interactivity, compiler=compiler, result=result)
3333 interactivity=interactivity, compiler=compiler, result=result)
3333
3334
3334 self.last_execution_succeeded = not has_raised
3335 self.last_execution_succeeded = not has_raised
3335 self.last_execution_result = result
3336 self.last_execution_result = result
3336
3337
3337 # Reset this so later displayed values do not modify the
3338 # Reset this so later displayed values do not modify the
3338 # ExecutionResult
3339 # ExecutionResult
3339 self.displayhook.exec_result = None
3340 self.displayhook.exec_result = None
3340
3341
3341 if store_history:
3342 if store_history:
3342 assert self.history_manager is not None
3343 assert self.history_manager is not None
3343 # Write output to the database. Does nothing unless
3344 # Write output to the database. Does nothing unless
3344 # history output logging is enabled.
3345 # history output logging is enabled.
3345 self.history_manager.store_output(self.execution_count)
3346 self.history_manager.store_output(self.execution_count)
3346 # Each cell is a *single* input, regardless of how many lines it has
3347 # Each cell is a *single* input, regardless of how many lines it has
3347 self.execution_count += 1
3348 self.execution_count += 1
3348
3349
3349 return result
3350 return result
3350
3351
3351 def transform_cell(self, raw_cell):
3352 def transform_cell(self, raw_cell):
3352 """Transform an input cell before parsing it.
3353 """Transform an input cell before parsing it.
3353
3354
3354 Static transformations, implemented in IPython.core.inputtransformer2,
3355 Static transformations, implemented in IPython.core.inputtransformer2,
3355 deal with things like ``%magic`` and ``!system`` commands.
3356 deal with things like ``%magic`` and ``!system`` commands.
3356 These run on all input.
3357 These run on all input.
3357 Dynamic transformations, for things like unescaped magics and the exit
3358 Dynamic transformations, for things like unescaped magics and the exit
3358 autocall, depend on the state of the interpreter.
3359 autocall, depend on the state of the interpreter.
3359 These only apply to single line inputs.
3360 These only apply to single line inputs.
3360
3361
3361 These string-based transformations are followed by AST transformations;
3362 These string-based transformations are followed by AST transformations;
3362 see :meth:`transform_ast`.
3363 see :meth:`transform_ast`.
3363 """
3364 """
3364 # Static input transformations
3365 # Static input transformations
3365 cell = self.input_transformer_manager.transform_cell(raw_cell)
3366 cell = self.input_transformer_manager.transform_cell(raw_cell)
3366
3367
3367 if len(cell.splitlines()) == 1:
3368 if len(cell.splitlines()) == 1:
3368 # Dynamic transformations - only applied for single line commands
3369 # Dynamic transformations - only applied for single line commands
3369 with self.builtin_trap:
3370 with self.builtin_trap:
3370 # use prefilter_lines to handle trailing newlines
3371 # use prefilter_lines to handle trailing newlines
3371 # restore trailing newline for ast.parse
3372 # restore trailing newline for ast.parse
3372 cell = self.prefilter_manager.prefilter_lines(cell) + '\n'
3373 cell = self.prefilter_manager.prefilter_lines(cell) + '\n'
3373
3374
3374 lines = cell.splitlines(keepends=True)
3375 lines = cell.splitlines(keepends=True)
3375 for transform in self.input_transformers_post:
3376 for transform in self.input_transformers_post:
3376 lines = transform(lines)
3377 lines = transform(lines)
3377 cell = ''.join(lines)
3378 cell = ''.join(lines)
3378
3379
3379 return cell
3380 return cell
3380
3381
3381 def transform_ast(self, node):
3382 def transform_ast(self, node):
3382 """Apply the AST transformations from self.ast_transformers
3383 """Apply the AST transformations from self.ast_transformers
3383
3384
3384 Parameters
3385 Parameters
3385 ----------
3386 ----------
3386 node : ast.Node
3387 node : ast.Node
3387 The root node to be transformed. Typically called with the ast.Module
3388 The root node to be transformed. Typically called with the ast.Module
3388 produced by parsing user input.
3389 produced by parsing user input.
3389
3390
3390 Returns
3391 Returns
3391 -------
3392 -------
3392 An ast.Node corresponding to the node it was called with. Note that it
3393 An ast.Node corresponding to the node it was called with. Note that it
3393 may also modify the passed object, so don't rely on references to the
3394 may also modify the passed object, so don't rely on references to the
3394 original AST.
3395 original AST.
3395 """
3396 """
3396 for transformer in self.ast_transformers:
3397 for transformer in self.ast_transformers:
3397 try:
3398 try:
3398 node = transformer.visit(node)
3399 node = transformer.visit(node)
3399 except InputRejected:
3400 except InputRejected:
3400 # User-supplied AST transformers can reject an input by raising
3401 # User-supplied AST transformers can reject an input by raising
3401 # an InputRejected. Short-circuit in this case so that we
3402 # an InputRejected. Short-circuit in this case so that we
3402 # don't unregister the transform.
3403 # don't unregister the transform.
3403 raise
3404 raise
3404 except Exception as e:
3405 except Exception as e:
3405 warn(
3406 warn(
3406 "AST transformer %r threw an error. It will be unregistered. %s"
3407 "AST transformer %r threw an error. It will be unregistered. %s"
3407 % (transformer, e)
3408 % (transformer, e)
3408 )
3409 )
3409 self.ast_transformers.remove(transformer)
3410 self.ast_transformers.remove(transformer)
3410
3411
3411 if self.ast_transformers:
3412 if self.ast_transformers:
3412 ast.fix_missing_locations(node)
3413 ast.fix_missing_locations(node)
3413 return node
3414 return node
3414
3415
3415 async def run_ast_nodes(
3416 async def run_ast_nodes(
3416 self,
3417 self,
3417 nodelist: ListType[stmt],
3418 nodelist: ListType[stmt],
3418 cell_name: str,
3419 cell_name: str,
3419 interactivity="last_expr",
3420 interactivity="last_expr",
3420 compiler=compile,
3421 compiler=compile,
3421 result=None,
3422 result=None,
3422 ):
3423 ):
3423 """Run a sequence of AST nodes. The execution mode depends on the
3424 """Run a sequence of AST nodes. The execution mode depends on the
3424 interactivity parameter.
3425 interactivity parameter.
3425
3426
3426 Parameters
3427 Parameters
3427 ----------
3428 ----------
3428 nodelist : list
3429 nodelist : list
3429 A sequence of AST nodes to run.
3430 A sequence of AST nodes to run.
3430 cell_name : str
3431 cell_name : str
3431 Will be passed to the compiler as the filename of the cell. Typically
3432 Will be passed to the compiler as the filename of the cell. Typically
3432 the value returned by ip.compile.cache(cell).
3433 the value returned by ip.compile.cache(cell).
3433 interactivity : str
3434 interactivity : str
3434 'all', 'last', 'last_expr' , 'last_expr_or_assign' or 'none',
3435 'all', 'last', 'last_expr' , 'last_expr_or_assign' or 'none',
3435 specifying which nodes should be run interactively (displaying output
3436 specifying which nodes should be run interactively (displaying output
3436 from expressions). 'last_expr' will run the last node interactively
3437 from expressions). 'last_expr' will run the last node interactively
3437 only if it is an expression (i.e. expressions in loops or other blocks
3438 only if it is an expression (i.e. expressions in loops or other blocks
3438 are not displayed) 'last_expr_or_assign' will run the last expression
3439 are not displayed) 'last_expr_or_assign' will run the last expression
3439 or the last assignment. Other values for this parameter will raise a
3440 or the last assignment. Other values for this parameter will raise a
3440 ValueError.
3441 ValueError.
3441
3442
3442 compiler : callable
3443 compiler : callable
3443 A function with the same interface as the built-in compile(), to turn
3444 A function with the same interface as the built-in compile(), to turn
3444 the AST nodes into code objects. Default is the built-in compile().
3445 the AST nodes into code objects. Default is the built-in compile().
3445 result : ExecutionResult, optional
3446 result : ExecutionResult, optional
3446 An object to store exceptions that occur during execution.
3447 An object to store exceptions that occur during execution.
3447
3448
3448 Returns
3449 Returns
3449 -------
3450 -------
3450 True if an exception occurred while running code, False if it finished
3451 True if an exception occurred while running code, False if it finished
3451 running.
3452 running.
3452 """
3453 """
3453 if not nodelist:
3454 if not nodelist:
3454 return
3455 return
3455
3456
3456
3457
3457 if interactivity == 'last_expr_or_assign':
3458 if interactivity == 'last_expr_or_assign':
3458 if isinstance(nodelist[-1], _assign_nodes):
3459 if isinstance(nodelist[-1], _assign_nodes):
3459 asg = nodelist[-1]
3460 asg = nodelist[-1]
3460 if isinstance(asg, ast.Assign) and len(asg.targets) == 1:
3461 if isinstance(asg, ast.Assign) and len(asg.targets) == 1:
3461 target = asg.targets[0]
3462 target = asg.targets[0]
3462 elif isinstance(asg, _single_targets_nodes):
3463 elif isinstance(asg, _single_targets_nodes):
3463 target = asg.target
3464 target = asg.target
3464 else:
3465 else:
3465 target = None
3466 target = None
3466 if isinstance(target, ast.Name):
3467 if isinstance(target, ast.Name):
3467 nnode = ast.Expr(ast.Name(target.id, ast.Load()))
3468 nnode = ast.Expr(ast.Name(target.id, ast.Load()))
3468 ast.fix_missing_locations(nnode)
3469 ast.fix_missing_locations(nnode)
3469 nodelist.append(nnode)
3470 nodelist.append(nnode)
3470 interactivity = 'last_expr'
3471 interactivity = 'last_expr'
3471
3472
3472 _async = False
3473 _async = False
3473 if interactivity == 'last_expr':
3474 if interactivity == 'last_expr':
3474 if isinstance(nodelist[-1], ast.Expr):
3475 if isinstance(nodelist[-1], ast.Expr):
3475 interactivity = "last"
3476 interactivity = "last"
3476 else:
3477 else:
3477 interactivity = "none"
3478 interactivity = "none"
3478
3479
3479 if interactivity == 'none':
3480 if interactivity == 'none':
3480 to_run_exec, to_run_interactive = nodelist, []
3481 to_run_exec, to_run_interactive = nodelist, []
3481 elif interactivity == 'last':
3482 elif interactivity == 'last':
3482 to_run_exec, to_run_interactive = nodelist[:-1], nodelist[-1:]
3483 to_run_exec, to_run_interactive = nodelist[:-1], nodelist[-1:]
3483 elif interactivity == 'all':
3484 elif interactivity == 'all':
3484 to_run_exec, to_run_interactive = [], nodelist
3485 to_run_exec, to_run_interactive = [], nodelist
3485 else:
3486 else:
3486 raise ValueError("Interactivity was %r" % interactivity)
3487 raise ValueError("Interactivity was %r" % interactivity)
3487
3488
3488 try:
3489 try:
3489
3490
3490 def compare(code):
3491 def compare(code):
3491 is_async = inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE
3492 is_async = inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE
3492 return is_async
3493 return is_async
3493
3494
3494 # refactor that to just change the mod constructor.
3495 # refactor that to just change the mod constructor.
3495 to_run = []
3496 to_run = []
3496 for node in to_run_exec:
3497 for node in to_run_exec:
3497 to_run.append((node, "exec"))
3498 to_run.append((node, "exec"))
3498
3499
3499 for node in to_run_interactive:
3500 for node in to_run_interactive:
3500 to_run.append((node, "single"))
3501 to_run.append((node, "single"))
3501
3502
3502 for node, mode in to_run:
3503 for node, mode in to_run:
3503 if mode == "exec":
3504 if mode == "exec":
3504 mod = Module([node], [])
3505 mod = Module([node], [])
3505 elif mode == "single":
3506 elif mode == "single":
3506 mod = ast.Interactive([node]) # type: ignore
3507 mod = ast.Interactive([node]) # type: ignore
3507 with compiler.extra_flags(
3508 with compiler.extra_flags(
3508 getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x0)
3509 getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x0)
3509 if self.autoawait
3510 if self.autoawait
3510 else 0x0
3511 else 0x0
3511 ):
3512 ):
3512 code = compiler(mod, cell_name, mode)
3513 code = compiler(mod, cell_name, mode)
3513 asy = compare(code)
3514 asy = compare(code)
3514 if await self.run_code(code, result, async_=asy):
3515 if await self.run_code(code, result, async_=asy):
3515 return True
3516 return True
3516
3517
3517 # Flush softspace
3518 # Flush softspace
3518 if softspace(sys.stdout, 0):
3519 if softspace(sys.stdout, 0):
3519 print()
3520 print()
3520
3521
3521 except Exception:
3522 except:
3522 # It's possible to have exceptions raised here, typically by
3523 # It's possible to have exceptions raised here, typically by
3523 # compilation of odd code (such as a naked 'return' outside a
3524 # compilation of odd code (such as a naked 'return' outside a
3524 # function) that did parse but isn't valid. Typically the exception
3525 # function) that did parse but isn't valid. Typically the exception
3525 # is a SyntaxError, but it's safest just to catch anything and show
3526 # is a SyntaxError, but it's safest just to catch anything and show
3526 # the user a traceback.
3527 # the user a traceback.
3527
3528
3528 # We do only one try/except outside the loop to minimize the impact
3529 # We do only one try/except outside the loop to minimize the impact
3529 # on runtime, and also because if any node in the node list is
3530 # on runtime, and also because if any node in the node list is
3530 # broken, we should stop execution completely.
3531 # broken, we should stop execution completely.
3531 if result:
3532 if result:
3532 result.error_before_exec = sys.exc_info()[1]
3533 result.error_before_exec = sys.exc_info()[1]
3533 self.showtraceback()
3534 self.showtraceback()
3534 return True
3535 return True
3535
3536
3536 return False
3537 return False
3537
3538
3538 async def run_code(self, code_obj, result=None, *, async_=False):
3539 async def run_code(self, code_obj, result=None, *, async_=False):
3539 """Execute a code object.
3540 """Execute a code object.
3540
3541
3541 When an exception occurs, self.showtraceback() is called to display a
3542 When an exception occurs, self.showtraceback() is called to display a
3542 traceback.
3543 traceback.
3543
3544
3544 Parameters
3545 Parameters
3545 ----------
3546 ----------
3546 code_obj : code object
3547 code_obj : code object
3547 A compiled code object, to be executed
3548 A compiled code object, to be executed
3548 result : ExecutionResult, optional
3549 result : ExecutionResult, optional
3549 An object to store exceptions that occur during execution.
3550 An object to store exceptions that occur during execution.
3550 async_ : Bool (Experimental)
3551 async_ : Bool (Experimental)
3551 Attempt to run top-level asynchronous code in a default loop.
3552 Attempt to run top-level asynchronous code in a default loop.
3552
3553
3553 Returns
3554 Returns
3554 -------
3555 -------
3555 False : successful execution.
3556 False : successful execution.
3556 True : an error occurred.
3557 True : an error occurred.
3557 """
3558 """
3558 # special value to say that anything above is IPython and should be
3559 # special value to say that anything above is IPython and should be
3559 # hidden.
3560 # hidden.
3560 __tracebackhide__ = "__ipython_bottom__"
3561 __tracebackhide__ = "__ipython_bottom__"
3561 # Set our own excepthook in case the user code tries to call it
3562 # Set our own excepthook in case the user code tries to call it
3562 # directly, so that the IPython crash handler doesn't get triggered
3563 # directly, so that the IPython crash handler doesn't get triggered
3563 old_excepthook, sys.excepthook = sys.excepthook, self.excepthook
3564 old_excepthook, sys.excepthook = sys.excepthook, self.excepthook
3564
3565
3565 # we save the original sys.excepthook in the instance, in case config
3566 # we save the original sys.excepthook in the instance, in case config
3566 # code (such as magics) needs access to it.
3567 # code (such as magics) needs access to it.
3567 self.sys_excepthook = old_excepthook
3568 self.sys_excepthook = old_excepthook
3568 outflag = True # happens in more places, so it's easier as default
3569 outflag = True # happens in more places, so it's easier as default
3569 try:
3570 try:
3570 try:
3571 try:
3571 if async_:
3572 if async_:
3572 await eval(code_obj, self.user_global_ns, self.user_ns)
3573 await eval(code_obj, self.user_global_ns, self.user_ns)
3573 else:
3574 else:
3574 exec(code_obj, self.user_global_ns, self.user_ns)
3575 exec(code_obj, self.user_global_ns, self.user_ns)
3575 finally:
3576 finally:
3576 # Reset our crash handler in place
3577 # Reset our crash handler in place
3577 sys.excepthook = old_excepthook
3578 sys.excepthook = old_excepthook
3578 except SystemExit as e:
3579 except SystemExit as e:
3579 if result is not None:
3580 if result is not None:
3580 result.error_in_exec = e
3581 result.error_in_exec = e
3581 self.showtraceback(exception_only=True)
3582 self.showtraceback(exception_only=True)
3582 warn("To exit: use 'exit', 'quit', or Ctrl-D.", stacklevel=1)
3583 warn("To exit: use 'exit', 'quit', or Ctrl-D.", stacklevel=1)
3583 except bdb.BdbQuit:
3584 except bdb.BdbQuit:
3584 etype, value, tb = sys.exc_info()
3585 etype, value, tb = sys.exc_info()
3585 if result is not None:
3586 if result is not None:
3586 result.error_in_exec = value
3587 result.error_in_exec = value
3587 # the BdbQuit stops here
3588 # the BdbQuit stops here
3588 except self.custom_exceptions:
3589 except self.custom_exceptions:
3589 etype, value, tb = sys.exc_info()
3590 etype, value, tb = sys.exc_info()
3590 if result is not None:
3591 if result is not None:
3591 result.error_in_exec = value
3592 result.error_in_exec = value
3592 self.CustomTB(etype, value, tb)
3593 self.CustomTB(etype, value, tb)
3593 except Exception:
3594 except:
3594 if result is not None:
3595 if result is not None:
3595 result.error_in_exec = sys.exc_info()[1]
3596 result.error_in_exec = sys.exc_info()[1]
3596 self.showtraceback(running_compiled_code=True)
3597 self.showtraceback(running_compiled_code=True)
3597 else:
3598 else:
3598 outflag = False
3599 outflag = False
3599 return outflag
3600 return outflag
3600
3601
3601 # For backwards compatibility
3602 # For backwards compatibility
3602 runcode = run_code
3603 runcode = run_code
3603
3604
3604 def check_complete(self, code: str) -> Tuple[str, str]:
3605 def check_complete(self, code: str) -> Tuple[str, str]:
3605 """Return whether a block of code is ready to execute, or should be continued
3606 """Return whether a block of code is ready to execute, or should be continued
3606
3607
3607 Parameters
3608 Parameters
3608 ----------
3609 ----------
3609 code : string
3610 code : string
3610 Python input code, which can be multiline.
3611 Python input code, which can be multiline.
3611
3612
3612 Returns
3613 Returns
3613 -------
3614 -------
3614 status : str
3615 status : str
3615 One of 'complete', 'incomplete', or 'invalid' if source is not a
3616 One of 'complete', 'incomplete', or 'invalid' if source is not a
3616 prefix of valid code.
3617 prefix of valid code.
3617 indent : str
3618 indent : str
3618 When status is 'incomplete', this is some whitespace to insert on
3619 When status is 'incomplete', this is some whitespace to insert on
3619 the next line of the prompt.
3620 the next line of the prompt.
3620 """
3621 """
3621 status, nspaces = self.input_transformer_manager.check_complete(code)
3622 status, nspaces = self.input_transformer_manager.check_complete(code)
3622 return status, ' ' * (nspaces or 0)
3623 return status, ' ' * (nspaces or 0)
3623
3624
3624 #-------------------------------------------------------------------------
3625 #-------------------------------------------------------------------------
3625 # Things related to GUI support and pylab
3626 # Things related to GUI support and pylab
3626 #-------------------------------------------------------------------------
3627 #-------------------------------------------------------------------------
3627
3628
3628 active_eventloop: Optional[str] = None
3629 active_eventloop: Optional[str] = None
3629
3630
3630 def enable_gui(self, gui=None):
3631 def enable_gui(self, gui=None):
3631 raise NotImplementedError('Implement enable_gui in a subclass')
3632 raise NotImplementedError('Implement enable_gui in a subclass')
3632
3633
3633 def enable_matplotlib(self, gui=None):
3634 def enable_matplotlib(self, gui=None):
3634 """Enable interactive matplotlib and inline figure support.
3635 """Enable interactive matplotlib and inline figure support.
3635
3636
3636 This takes the following steps:
3637 This takes the following steps:
3637
3638
3638 1. select the appropriate eventloop and matplotlib backend
3639 1. select the appropriate eventloop and matplotlib backend
3639 2. set up matplotlib for interactive use with that backend
3640 2. set up matplotlib for interactive use with that backend
3640 3. configure formatters for inline figure display
3641 3. configure formatters for inline figure display
3641 4. enable the selected gui eventloop
3642 4. enable the selected gui eventloop
3642
3643
3643 Parameters
3644 Parameters
3644 ----------
3645 ----------
3645 gui : optional, string
3646 gui : optional, string
3646 If given, dictates the choice of matplotlib GUI backend to use
3647 If given, dictates the choice of matplotlib GUI backend to use
3647 (should be one of IPython's supported backends, 'qt', 'osx', 'tk',
3648 (should be one of IPython's supported backends, 'qt', 'osx', 'tk',
3648 'gtk', 'wx' or 'inline'), otherwise we use the default chosen by
3649 'gtk', 'wx' or 'inline'), otherwise we use the default chosen by
3649 matplotlib (as dictated by the matplotlib build-time options plus the
3650 matplotlib (as dictated by the matplotlib build-time options plus the
3650 user's matplotlibrc configuration file). Note that not all backends
3651 user's matplotlibrc configuration file). Note that not all backends
3651 make sense in all contexts, for example a terminal ipython can't
3652 make sense in all contexts, for example a terminal ipython can't
3652 display figures inline.
3653 display figures inline.
3653 """
3654 """
3654 from .pylabtools import _matplotlib_manages_backends
3655 from .pylabtools import _matplotlib_manages_backends
3655
3656
3656 if not _matplotlib_manages_backends() and gui in (None, "auto"):
3657 if not _matplotlib_manages_backends() and gui in (None, "auto"):
3657 # Early import of backend_inline required for its side effect of
3658 # Early import of backend_inline required for its side effect of
3658 # calling _enable_matplotlib_integration()
3659 # calling _enable_matplotlib_integration()
3659 import matplotlib_inline.backend_inline # noqa : F401
3660 import matplotlib_inline.backend_inline
3660
3661
3661 from IPython.core import pylabtools as pt
3662 from IPython.core import pylabtools as pt
3662 gui, backend = pt.find_gui_and_backend(gui, self.pylab_gui_select)
3663 gui, backend = pt.find_gui_and_backend(gui, self.pylab_gui_select)
3663
3664
3664 if gui is not None:
3665 if gui != None:
3665 # If we have our first gui selection, store it
3666 # If we have our first gui selection, store it
3666 if self.pylab_gui_select is None:
3667 if self.pylab_gui_select is None:
3667 self.pylab_gui_select = gui
3668 self.pylab_gui_select = gui
3668 # Otherwise if they are different
3669 # Otherwise if they are different
3669 elif gui != self.pylab_gui_select:
3670 elif gui != self.pylab_gui_select:
3670 print('Warning: Cannot change to a different GUI toolkit: %s.'
3671 print('Warning: Cannot change to a different GUI toolkit: %s.'
3671 ' Using %s instead.' % (gui, self.pylab_gui_select))
3672 ' Using %s instead.' % (gui, self.pylab_gui_select))
3672 gui, backend = pt.find_gui_and_backend(self.pylab_gui_select)
3673 gui, backend = pt.find_gui_and_backend(self.pylab_gui_select)
3673
3674
3674 pt.activate_matplotlib(backend)
3675 pt.activate_matplotlib(backend)
3675
3676
3676 from matplotlib_inline.backend_inline import configure_inline_support
3677 from matplotlib_inline.backend_inline import configure_inline_support
3677
3678
3678 configure_inline_support(self, backend)
3679 configure_inline_support(self, backend)
3679
3680
3680 # Now we must activate the gui pylab wants to use, and fix %run to take
3681 # Now we must activate the gui pylab wants to use, and fix %run to take
3681 # plot updates into account
3682 # plot updates into account
3682 self.enable_gui(gui)
3683 self.enable_gui(gui)
3683 self.magics_manager.registry['ExecutionMagics'].default_runner = \
3684 self.magics_manager.registry['ExecutionMagics'].default_runner = \
3684 pt.mpl_runner(self.safe_execfile)
3685 pt.mpl_runner(self.safe_execfile)
3685
3686
3686 return gui, backend
3687 return gui, backend
3687
3688
3688 def enable_pylab(self, gui=None, import_all=True, welcome_message=False):
3689 def enable_pylab(self, gui=None, import_all=True, welcome_message=False):
3689 """Activate pylab support at runtime.
3690 """Activate pylab support at runtime.
3690
3691
3691 This turns on support for matplotlib, preloads into the interactive
3692 This turns on support for matplotlib, preloads into the interactive
3692 namespace all of numpy and pylab, and configures IPython to correctly
3693 namespace all of numpy and pylab, and configures IPython to correctly
3693 interact with the GUI event loop. The GUI backend to be used can be
3694 interact with the GUI event loop. The GUI backend to be used can be
3694 optionally selected with the optional ``gui`` argument.
3695 optionally selected with the optional ``gui`` argument.
3695
3696
3696 This method only adds preloading the namespace to InteractiveShell.enable_matplotlib.
3697 This method only adds preloading the namespace to InteractiveShell.enable_matplotlib.
3697
3698
3698 Parameters
3699 Parameters
3699 ----------
3700 ----------
3700 gui : optional, string
3701 gui : optional, string
3701 If given, dictates the choice of matplotlib GUI backend to use
3702 If given, dictates the choice of matplotlib GUI backend to use
3702 (should be one of IPython's supported backends, 'qt', 'osx', 'tk',
3703 (should be one of IPython's supported backends, 'qt', 'osx', 'tk',
3703 'gtk', 'wx' or 'inline'), otherwise we use the default chosen by
3704 'gtk', 'wx' or 'inline'), otherwise we use the default chosen by
3704 matplotlib (as dictated by the matplotlib build-time options plus the
3705 matplotlib (as dictated by the matplotlib build-time options plus the
3705 user's matplotlibrc configuration file). Note that not all backends
3706 user's matplotlibrc configuration file). Note that not all backends
3706 make sense in all contexts, for example a terminal ipython can't
3707 make sense in all contexts, for example a terminal ipython can't
3707 display figures inline.
3708 display figures inline.
3708 import_all : optional, bool, default: True
3709 import_all : optional, bool, default: True
3709 Whether to do `from numpy import *` and `from pylab import *`
3710 Whether to do `from numpy import *` and `from pylab import *`
3710 in addition to module imports.
3711 in addition to module imports.
3711 welcome_message : deprecated
3712 welcome_message : deprecated
3712 This argument is ignored, no welcome message will be displayed.
3713 This argument is ignored, no welcome message will be displayed.
3713 """
3714 """
3714 from IPython.core.pylabtools import import_pylab
3715 from IPython.core.pylabtools import import_pylab
3715
3716
3716 gui, backend = self.enable_matplotlib(gui)
3717 gui, backend = self.enable_matplotlib(gui)
3717
3718
3718 # We want to prevent the loading of pylab to pollute the user's
3719 # We want to prevent the loading of pylab to pollute the user's
3719 # namespace as shown by the %who* magics, so we execute the activation
3720 # namespace as shown by the %who* magics, so we execute the activation
3720 # code in an empty namespace, and we update *both* user_ns and
3721 # code in an empty namespace, and we update *both* user_ns and
3721 # user_ns_hidden with this information.
3722 # user_ns_hidden with this information.
3722 ns = {}
3723 ns = {}
3723 import_pylab(ns, import_all)
3724 import_pylab(ns, import_all)
3724 # warn about clobbered names
3725 # warn about clobbered names
3725 ignored = {"__builtins__"}
3726 ignored = {"__builtins__"}
3726 both = set(ns).intersection(self.user_ns).difference(ignored)
3727 both = set(ns).intersection(self.user_ns).difference(ignored)
3727 clobbered = [ name for name in both if self.user_ns[name] is not ns[name] ]
3728 clobbered = [ name for name in both if self.user_ns[name] is not ns[name] ]
3728 self.user_ns.update(ns)
3729 self.user_ns.update(ns)
3729 self.user_ns_hidden.update(ns)
3730 self.user_ns_hidden.update(ns)
3730 return gui, backend, clobbered
3731 return gui, backend, clobbered
3731
3732
3732 #-------------------------------------------------------------------------
3733 #-------------------------------------------------------------------------
3733 # Utilities
3734 # Utilities
3734 #-------------------------------------------------------------------------
3735 #-------------------------------------------------------------------------
3735
3736
3736 def var_expand(self, cmd, depth=0, formatter=DollarFormatter()):
3737 def var_expand(self, cmd, depth=0, formatter=DollarFormatter()):
3737 """Expand python variables in a string.
3738 """Expand python variables in a string.
3738
3739
3739 The depth argument indicates how many frames above the caller should
3740 The depth argument indicates how many frames above the caller should
3740 be walked to look for the local namespace where to expand variables.
3741 be walked to look for the local namespace where to expand variables.
3741
3742
3742 The global namespace for expansion is always the user's interactive
3743 The global namespace for expansion is always the user's interactive
3743 namespace.
3744 namespace.
3744 """
3745 """
3745 ns = self.user_ns.copy()
3746 ns = self.user_ns.copy()
3746 try:
3747 try:
3747 frame = sys._getframe(depth+1)
3748 frame = sys._getframe(depth+1)
3748 except ValueError:
3749 except ValueError:
3749 # This is thrown if there aren't that many frames on the stack,
3750 # This is thrown if there aren't that many frames on the stack,
3750 # e.g. if a script called run_line_magic() directly.
3751 # e.g. if a script called run_line_magic() directly.
3751 pass
3752 pass
3752 else:
3753 else:
3753 ns.update(frame.f_locals)
3754 ns.update(frame.f_locals)
3754
3755
3755 try:
3756 try:
3756 # We have to use .vformat() here, because 'self' is a valid and common
3757 # We have to use .vformat() here, because 'self' is a valid and common
3757 # name, and expanding **ns for .format() would make it collide with
3758 # name, and expanding **ns for .format() would make it collide with
3758 # the 'self' argument of the method.
3759 # the 'self' argument of the method.
3759 cmd = formatter.vformat(cmd, args=[], kwargs=ns)
3760 cmd = formatter.vformat(cmd, args=[], kwargs=ns)
3760 except Exception:
3761 except Exception:
3761 # if formatter couldn't format, just let it go untransformed
3762 # if formatter couldn't format, just let it go untransformed
3762 pass
3763 pass
3763 return cmd
3764 return cmd
3764
3765
3765 def mktempfile(self, data=None, prefix='ipython_edit_'):
3766 def mktempfile(self, data=None, prefix='ipython_edit_'):
3766 """Make a new tempfile and return its filename.
3767 """Make a new tempfile and return its filename.
3767
3768
3768 This makes a call to tempfile.mkstemp (created in a tempfile.mkdtemp),
3769 This makes a call to tempfile.mkstemp (created in a tempfile.mkdtemp),
3769 but it registers the created filename internally so ipython cleans it up
3770 but it registers the created filename internally so ipython cleans it up
3770 at exit time.
3771 at exit time.
3771
3772
3772 Optional inputs:
3773 Optional inputs:
3773
3774
3774 - data(None): if data is given, it gets written out to the temp file
3775 - data(None): if data is given, it gets written out to the temp file
3775 immediately, and the file is closed again."""
3776 immediately, and the file is closed again."""
3776
3777
3777 dir_path = Path(tempfile.mkdtemp(prefix=prefix))
3778 dir_path = Path(tempfile.mkdtemp(prefix=prefix))
3778 self.tempdirs.append(dir_path)
3779 self.tempdirs.append(dir_path)
3779
3780
3780 handle, filename = tempfile.mkstemp(".py", prefix, dir=str(dir_path))
3781 handle, filename = tempfile.mkstemp(".py", prefix, dir=str(dir_path))
3781 os.close(handle) # On Windows, there can only be one open handle on a file
3782 os.close(handle) # On Windows, there can only be one open handle on a file
3782
3783
3783 file_path = Path(filename)
3784 file_path = Path(filename)
3784 self.tempfiles.append(file_path)
3785 self.tempfiles.append(file_path)
3785
3786
3786 if data:
3787 if data:
3787 file_path.write_text(data, encoding="utf-8")
3788 file_path.write_text(data, encoding="utf-8")
3788 return filename
3789 return filename
3789
3790
3790 def ask_yes_no(self, prompt, default=None, interrupt=None):
3791 def ask_yes_no(self, prompt, default=None, interrupt=None):
3791 if self.quiet:
3792 if self.quiet:
3792 return True
3793 return True
3793 return ask_yes_no(prompt,default,interrupt)
3794 return ask_yes_no(prompt,default,interrupt)
3794
3795
3795 def show_usage(self):
3796 def show_usage(self):
3796 """Show a usage message"""
3797 """Show a usage message"""
3797 page.page(IPython.core.usage.interactive_usage)
3798 page.page(IPython.core.usage.interactive_usage)
3798
3799
3799 def extract_input_lines(self, range_str, raw=False):
3800 def extract_input_lines(self, range_str, raw=False):
3800 """Return as a string a set of input history slices.
3801 """Return as a string a set of input history slices.
3801
3802
3802 Parameters
3803 Parameters
3803 ----------
3804 ----------
3804 range_str : str
3805 range_str : str
3805 The set of slices is given as a string, like "~5/6-~4/2 4:8 9",
3806 The set of slices is given as a string, like "~5/6-~4/2 4:8 9",
3806 since this function is for use by magic functions which get their
3807 since this function is for use by magic functions which get their
3807 arguments as strings. The number before the / is the session
3808 arguments as strings. The number before the / is the session
3808 number: ~n goes n back from the current session.
3809 number: ~n goes n back from the current session.
3809
3810
3810 If empty string is given, returns history of current session
3811 If empty string is given, returns history of current session
3811 without the last input.
3812 without the last input.
3812
3813
3813 raw : bool, optional
3814 raw : bool, optional
3814 By default, the processed input is used. If this is true, the raw
3815 By default, the processed input is used. If this is true, the raw
3815 input history is used instead.
3816 input history is used instead.
3816
3817
3817 Notes
3818 Notes
3818 -----
3819 -----
3819 Slices can be described with two notations:
3820 Slices can be described with two notations:
3820
3821
3821 * ``N:M`` -> standard python form, means including items N...(M-1).
3822 * ``N:M`` -> standard python form, means including items N...(M-1).
3822 * ``N-M`` -> include items N..M (closed endpoint).
3823 * ``N-M`` -> include items N..M (closed endpoint).
3823 """
3824 """
3824 lines = self.history_manager.get_range_by_str(range_str, raw=raw)
3825 lines = self.history_manager.get_range_by_str(range_str, raw=raw)
3825 text = "\n".join(x for _, _, x in lines)
3826 text = "\n".join(x for _, _, x in lines)
3826
3827
3827 # Skip the last line, as it's probably the magic that called this
3828 # Skip the last line, as it's probably the magic that called this
3828 if not range_str:
3829 if not range_str:
3829 if "\n" not in text:
3830 if "\n" not in text:
3830 text = ""
3831 text = ""
3831 else:
3832 else:
3832 text = text[: text.rfind("\n")]
3833 text = text[: text.rfind("\n")]
3833
3834
3834 return text
3835 return text
3835
3836
3836 def find_user_code(self, target, raw=True, py_only=False, skip_encoding_cookie=True, search_ns=False):
3837 def find_user_code(self, target, raw=True, py_only=False, skip_encoding_cookie=True, search_ns=False):
3837 """Get a code string from history, file, url, or a string or macro.
3838 """Get a code string from history, file, url, or a string or macro.
3838
3839
3839 This is mainly used by magic functions.
3840 This is mainly used by magic functions.
3840
3841
3841 Parameters
3842 Parameters
3842 ----------
3843 ----------
3843 target : str
3844 target : str
3844 A string specifying code to retrieve. This will be tried respectively
3845 A string specifying code to retrieve. This will be tried respectively
3845 as: ranges of input history (see %history for syntax), url,
3846 as: ranges of input history (see %history for syntax), url,
3846 corresponding .py file, filename, or an expression evaluating to a
3847 corresponding .py file, filename, or an expression evaluating to a
3847 string or Macro in the user namespace.
3848 string or Macro in the user namespace.
3848
3849
3849 If empty string is given, returns complete history of current
3850 If empty string is given, returns complete history of current
3850 session, without the last line.
3851 session, without the last line.
3851
3852
3852 raw : bool
3853 raw : bool
3853 If true (default), retrieve raw history. Has no effect on the other
3854 If true (default), retrieve raw history. Has no effect on the other
3854 retrieval mechanisms.
3855 retrieval mechanisms.
3855
3856
3856 py_only : bool (default False)
3857 py_only : bool (default False)
3857 Only try to fetch python code, do not try alternative methods to decode file
3858 Only try to fetch python code, do not try alternative methods to decode file
3858 if unicode fails.
3859 if unicode fails.
3859
3860
3860 Returns
3861 Returns
3861 -------
3862 -------
3862 A string of code.
3863 A string of code.
3863 ValueError is raised if nothing is found, and TypeError if it evaluates
3864 ValueError is raised if nothing is found, and TypeError if it evaluates
3864 to an object of another type. In each case, .args[0] is a printable
3865 to an object of another type. In each case, .args[0] is a printable
3865 message.
3866 message.
3866 """
3867 """
3867 code = self.extract_input_lines(target, raw=raw) # Grab history
3868 code = self.extract_input_lines(target, raw=raw) # Grab history
3868 if code:
3869 if code:
3869 return code
3870 return code
3870 try:
3871 try:
3871 if target.startswith(('http://', 'https://')):
3872 if target.startswith(('http://', 'https://')):
3872 return openpy.read_py_url(target, skip_encoding_cookie=skip_encoding_cookie)
3873 return openpy.read_py_url(target, skip_encoding_cookie=skip_encoding_cookie)
3873 except UnicodeDecodeError as e:
3874 except UnicodeDecodeError as e:
3874 if not py_only :
3875 if not py_only :
3875 # Deferred import
3876 # Deferred import
3876 from urllib.request import urlopen
3877 from urllib.request import urlopen
3877 response = urlopen(target)
3878 response = urlopen(target)
3878 return response.read().decode('latin1')
3879 return response.read().decode('latin1')
3879 raise ValueError(("'%s' seem to be unreadable.") % target) from e
3880 raise ValueError(("'%s' seem to be unreadable.") % target) from e
3880
3881
3881 potential_target = [target]
3882 potential_target = [target]
3882 try :
3883 try :
3883 potential_target.insert(0,get_py_filename(target))
3884 potential_target.insert(0,get_py_filename(target))
3884 except IOError:
3885 except IOError:
3885 pass
3886 pass
3886
3887
3887 for tgt in potential_target :
3888 for tgt in potential_target :
3888 if os.path.isfile(tgt): # Read file
3889 if os.path.isfile(tgt): # Read file
3889 try :
3890 try :
3890 return openpy.read_py_file(tgt, skip_encoding_cookie=skip_encoding_cookie)
3891 return openpy.read_py_file(tgt, skip_encoding_cookie=skip_encoding_cookie)
3891 except UnicodeDecodeError as e:
3892 except UnicodeDecodeError as e:
3892 if not py_only :
3893 if not py_only :
3893 with io_open(tgt,'r', encoding='latin1') as f :
3894 with io_open(tgt,'r', encoding='latin1') as f :
3894 return f.read()
3895 return f.read()
3895 raise ValueError(("'%s' seem to be unreadable.") % target) from e
3896 raise ValueError(("'%s' seem to be unreadable.") % target) from e
3896 elif os.path.isdir(os.path.expanduser(tgt)):
3897 elif os.path.isdir(os.path.expanduser(tgt)):
3897 raise ValueError("'%s' is a directory, not a regular file." % target)
3898 raise ValueError("'%s' is a directory, not a regular file." % target)
3898
3899
3899 if search_ns:
3900 if search_ns:
3900 # Inspect namespace to load object source
3901 # Inspect namespace to load object source
3901 object_info = self.object_inspect(target, detail_level=1)
3902 object_info = self.object_inspect(target, detail_level=1)
3902 if object_info['found'] and object_info['source']:
3903 if object_info['found'] and object_info['source']:
3903 return object_info['source']
3904 return object_info['source']
3904
3905
3905 try: # User namespace
3906 try: # User namespace
3906 codeobj = eval(target, self.user_ns)
3907 codeobj = eval(target, self.user_ns)
3907 except Exception as e:
3908 except Exception as e:
3908 raise ValueError(("'%s' was not found in history, as a file, url, "
3909 raise ValueError(("'%s' was not found in history, as a file, url, "
3909 "nor in the user namespace.") % target) from e
3910 "nor in the user namespace.") % target) from e
3910
3911
3911 if isinstance(codeobj, str):
3912 if isinstance(codeobj, str):
3912 return codeobj
3913 return codeobj
3913 elif isinstance(codeobj, Macro):
3914 elif isinstance(codeobj, Macro):
3914 return codeobj.value
3915 return codeobj.value
3915
3916
3916 raise TypeError("%s is neither a string nor a macro." % target,
3917 raise TypeError("%s is neither a string nor a macro." % target,
3917 codeobj)
3918 codeobj)
3918
3919
3919 def _atexit_once(self):
3920 def _atexit_once(self):
3920 """
3921 """
3921 At exist operation that need to be called at most once.
3922 At exist operation that need to be called at most once.
3922 Second call to this function per instance will do nothing.
3923 Second call to this function per instance will do nothing.
3923 """
3924 """
3924
3925
3925 if not getattr(self, "_atexit_once_called", False):
3926 if not getattr(self, "_atexit_once_called", False):
3926 self._atexit_once_called = True
3927 self._atexit_once_called = True
3927 # Clear all user namespaces to release all references cleanly.
3928 # Clear all user namespaces to release all references cleanly.
3928 self.reset(new_session=False)
3929 self.reset(new_session=False)
3929 # Close the history session (this stores the end time and line count)
3930 # Close the history session (this stores the end time and line count)
3930 # this must be *before* the tempfile cleanup, in case of temporary
3931 # this must be *before* the tempfile cleanup, in case of temporary
3931 # history db
3932 # history db
3932 self.history_manager.end_session()
3933 self.history_manager.end_session()
3933 self.history_manager = None
3934 self.history_manager = None
3934
3935
3935 #-------------------------------------------------------------------------
3936 #-------------------------------------------------------------------------
3936 # Things related to IPython exiting
3937 # Things related to IPython exiting
3937 #-------------------------------------------------------------------------
3938 #-------------------------------------------------------------------------
3938 def atexit_operations(self):
3939 def atexit_operations(self):
3939 """This will be executed at the time of exit.
3940 """This will be executed at the time of exit.
3940
3941
3941 Cleanup operations and saving of persistent data that is done
3942 Cleanup operations and saving of persistent data that is done
3942 unconditionally by IPython should be performed here.
3943 unconditionally by IPython should be performed here.
3943
3944
3944 For things that may depend on startup flags or platform specifics (such
3945 For things that may depend on startup flags or platform specifics (such
3945 as having readline or not), register a separate atexit function in the
3946 as having readline or not), register a separate atexit function in the
3946 code that has the appropriate information, rather than trying to
3947 code that has the appropriate information, rather than trying to
3947 clutter
3948 clutter
3948 """
3949 """
3949 self._atexit_once()
3950 self._atexit_once()
3950
3951
3951 # Cleanup all tempfiles and folders left around
3952 # Cleanup all tempfiles and folders left around
3952 for tfile in self.tempfiles:
3953 for tfile in self.tempfiles:
3953 try:
3954 try:
3954 tfile.unlink()
3955 tfile.unlink()
3955 self.tempfiles.remove(tfile)
3956 self.tempfiles.remove(tfile)
3956 except FileNotFoundError:
3957 except FileNotFoundError:
3957 pass
3958 pass
3958 del self.tempfiles
3959 del self.tempfiles
3959 for tdir in self.tempdirs:
3960 for tdir in self.tempdirs:
3960 try:
3961 try:
3961 shutil.rmtree(tdir)
3962 shutil.rmtree(tdir)
3962 self.tempdirs.remove(tdir)
3963 self.tempdirs.remove(tdir)
3963 except FileNotFoundError:
3964 except FileNotFoundError:
3964 pass
3965 pass
3965 del self.tempdirs
3966 del self.tempdirs
3966
3967
3967 # Restore user's cursor
3968 # Restore user's cursor
3968 if hasattr(self, "editing_mode") and self.editing_mode == "vi":
3969 if hasattr(self, "editing_mode") and self.editing_mode == "vi":
3969 sys.stdout.write("\x1b[0 q")
3970 sys.stdout.write("\x1b[0 q")
3970 sys.stdout.flush()
3971 sys.stdout.flush()
3971
3972
3972 def cleanup(self):
3973 def cleanup(self):
3973 self.restore_sys_module_state()
3974 self.restore_sys_module_state()
3974
3975
3975
3976
3976 # Overridden in terminal subclass to change prompts
3977 # Overridden in terminal subclass to change prompts
3977 def switch_doctest_mode(self, mode):
3978 def switch_doctest_mode(self, mode):
3978 pass
3979 pass
3979
3980
3980
3981
3981 class InteractiveShellABC(metaclass=abc.ABCMeta):
3982 class InteractiveShellABC(metaclass=abc.ABCMeta):
3982 """An abstract base class for InteractiveShell."""
3983 """An abstract base class for InteractiveShell."""
3983
3984
3984 InteractiveShellABC.register(InteractiveShell)
3985 InteractiveShellABC.register(InteractiveShell)
@@ -1,45 +1,42
1 """Implementation of all the magic functions built into IPython.
1 """Implementation of all the magic functions built into IPython.
2 """
2 """
3
3 #-----------------------------------------------------------------------------
4 # -----------------------------------------------------------------------------
5 # Copyright (c) 2012 The IPython Development Team.
4 # Copyright (c) 2012 The IPython Development Team.
6 #
5 #
7 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
8 #
7 #
9 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
10 # -----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
11
10
12 # -----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
13 # Imports
12 # Imports
14 # -----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
15
14
16 from ..magic import Magics as Magics, magics_class as magics_class
15 from ..magic import Magics, magics_class
17 from .auto import AutoMagics as AutoMagics
16 from .auto import AutoMagics
18 from .basic import BasicMagics as BasicMagics, AsyncMagics as AsyncMagics
17 from .basic import BasicMagics, AsyncMagics
19 from .code import CodeMagics as CodeMagics, MacroToEdit as MacroToEdit
18 from .code import CodeMagics, MacroToEdit
20 from .config import ConfigMagics as ConfigMagics
19 from .config import ConfigMagics
21 from .display import DisplayMagics as DisplayMagics
20 from .display import DisplayMagics
22 from .execution import ExecutionMagics as ExecutionMagics
21 from .execution import ExecutionMagics
23 from .extension import ExtensionMagics as ExtensionMagics
22 from .extension import ExtensionMagics
24 from .history import HistoryMagics as HistoryMagics
23 from .history import HistoryMagics
25 from .logging import LoggingMagics as LoggingMagics
24 from .logging import LoggingMagics
26 from .namespace import NamespaceMagics as NamespaceMagics
25 from .namespace import NamespaceMagics
27 from .osm import OSMagics as OSMagics
26 from .osm import OSMagics
28 from .packaging import PackagingMagics as PackagingMagics
27 from .packaging import PackagingMagics
29 from .pylab import PylabMagics as PylabMagics
28 from .pylab import PylabMagics
30 from .script import ScriptMagics as ScriptMagics
29 from .script import ScriptMagics
31
30
32
31 #-----------------------------------------------------------------------------
33 # -----------------------------------------------------------------------------
34 # Magic implementation classes
32 # Magic implementation classes
35 # -----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
36
37
34
38 @magics_class
35 @magics_class
39 class UserMagics(Magics):
36 class UserMagics(Magics):
40 """Placeholder for user-defined magics to be added at runtime.
37 """Placeholder for user-defined magics to be added at runtime.
41
38
42 All magics are eventually merged into a single namespace at runtime, but we
39 All magics are eventually merged into a single namespace at runtime, but we
43 use this class to isolate the magics defined dynamically by the user into
40 use this class to isolate the magics defined dynamically by the user into
44 their own class.
41 their own class.
45 """
42 """
@@ -1,144 +1,144
1 """Implementation of magic functions that control various automatic behaviors.
1 """Implementation of magic functions that control various automatic behaviors.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2012 The IPython Development Team.
4 # Copyright (c) 2012 The IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 # Our own packages
15 # Our own packages
16 from IPython.core.magic import Bunch, Magics, magics_class, line_magic
16 from IPython.core.magic import Bunch, Magics, magics_class, line_magic
17 from IPython.testing.skipdoctest import skip_doctest
17 from IPython.testing.skipdoctest import skip_doctest
18 from logging import error
18 from logging import error
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Magic implementation classes
21 # Magic implementation classes
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 @magics_class
24 @magics_class
25 class AutoMagics(Magics):
25 class AutoMagics(Magics):
26 """Magics that control various autoX behaviors."""
26 """Magics that control various autoX behaviors."""
27
27
28 def __init__(self, shell):
28 def __init__(self, shell):
29 super(AutoMagics, self).__init__(shell)
29 super(AutoMagics, self).__init__(shell)
30 # namespace for holding state we may need
30 # namespace for holding state we may need
31 self._magic_state = Bunch()
31 self._magic_state = Bunch()
32
32
33 @line_magic
33 @line_magic
34 def automagic(self, parameter_s=''):
34 def automagic(self, parameter_s=''):
35 """Make magic functions callable without having to type the initial %.
35 """Make magic functions callable without having to type the initial %.
36
36
37 Without arguments toggles on/off (when off, you must call it as
37 Without arguments toggles on/off (when off, you must call it as
38 %automagic, of course). With arguments it sets the value, and you can
38 %automagic, of course). With arguments it sets the value, and you can
39 use any of (case insensitive):
39 use any of (case insensitive):
40
40
41 - on, 1, True: to activate
41 - on, 1, True: to activate
42
42
43 - off, 0, False: to deactivate.
43 - off, 0, False: to deactivate.
44
44
45 Note that magic functions have lowest priority, so if there's a
45 Note that magic functions have lowest priority, so if there's a
46 variable whose name collides with that of a magic fn, automagic won't
46 variable whose name collides with that of a magic fn, automagic won't
47 work for that function (you get the variable instead). However, if you
47 work for that function (you get the variable instead). However, if you
48 delete the variable (del var), the previously shadowed magic function
48 delete the variable (del var), the previously shadowed magic function
49 becomes visible to automagic again."""
49 becomes visible to automagic again."""
50
50
51 arg = parameter_s.lower()
51 arg = parameter_s.lower()
52 mman = self.shell.magics_manager
52 mman = self.shell.magics_manager
53 if arg in ('on', '1', 'true'):
53 if arg in ('on', '1', 'true'):
54 val = True
54 val = True
55 elif arg in ('off', '0', 'false'):
55 elif arg in ('off', '0', 'false'):
56 val = False
56 val = False
57 else:
57 else:
58 val = not mman.auto_magic
58 val = not mman.auto_magic
59 mman.auto_magic = val
59 mman.auto_magic = val
60 print('\n' + self.shell.magics_manager.auto_status())
60 print('\n' + self.shell.magics_manager.auto_status())
61
61
62 @skip_doctest
62 @skip_doctest
63 @line_magic
63 @line_magic
64 def autocall(self, parameter_s=''):
64 def autocall(self, parameter_s=''):
65 """Make functions callable without having to type parentheses.
65 """Make functions callable without having to type parentheses.
66
66
67 Usage:
67 Usage:
68
68
69 %autocall [mode]
69 %autocall [mode]
70
70
71 The mode can be one of: 0->Off, 1->Smart, 2->Full. If not given, the
71 The mode can be one of: 0->Off, 1->Smart, 2->Full. If not given, the
72 value is toggled on and off (remembering the previous state).
72 value is toggled on and off (remembering the previous state).
73
73
74 In more detail, these values mean:
74 In more detail, these values mean:
75
75
76 0 -> fully disabled
76 0 -> fully disabled
77
77
78 1 -> active, but do not apply if there are no arguments on the line.
78 1 -> active, but do not apply if there are no arguments on the line.
79
79
80 In this mode, you get::
80 In this mode, you get::
81
81
82 In [1]: callable
82 In [1]: callable
83 Out[1]: <built-in function callable>
83 Out[1]: <built-in function callable>
84
84
85 In [2]: callable 'hello'
85 In [2]: callable 'hello'
86 ------> callable('hello')
86 ------> callable('hello')
87 Out[2]: False
87 Out[2]: False
88
88
89 2 -> Active always. Even if no arguments are present, the callable
89 2 -> Active always. Even if no arguments are present, the callable
90 object is called::
90 object is called::
91
91
92 In [2]: float
92 In [2]: float
93 ------> float()
93 ------> float()
94 Out[2]: 0.0
94 Out[2]: 0.0
95
95
96 Note that even with autocall off, you can still use '/' at the start of
96 Note that even with autocall off, you can still use '/' at the start of
97 a line to treat the first argument on the command line as a function
97 a line to treat the first argument on the command line as a function
98 and add parentheses to it::
98 and add parentheses to it::
99
99
100 In [8]: /str 43
100 In [8]: /str 43
101 ------> str(43)
101 ------> str(43)
102 Out[8]: '43'
102 Out[8]: '43'
103
103
104 # all-random (note for auto-testing)
104 # all-random (note for auto-testing)
105 """
105 """
106
106
107 valid_modes = {
107 valid_modes = {
108 0: "Off",
108 0: "Off",
109 1: "Smart",
109 1: "Smart",
110 2: "Full",
110 2: "Full",
111 }
111 }
112
112
113 def errorMessage() -> str:
113 def errorMessage() -> str:
114 error = "Valid modes: "
114 error = "Valid modes: "
115 for k, v in valid_modes.items():
115 for k, v in valid_modes.items():
116 error += str(k) + "->" + v + ", "
116 error += str(k) + "->" + v + ", "
117 error = error[:-2] # remove tailing `, ` after last element
117 error = error[:-2] # remove tailing `, ` after last element
118 return error
118 return error
119
119
120 if parameter_s:
120 if parameter_s:
121 if parameter_s not in map(str, valid_modes.keys()):
121 if not parameter_s in map(str, valid_modes.keys()):
122 error(errorMessage())
122 error(errorMessage())
123 return
123 return
124 arg = int(parameter_s)
124 arg = int(parameter_s)
125 else:
125 else:
126 arg = 'toggle'
126 arg = 'toggle'
127
127
128 if arg not in (*list(valid_modes.keys()), "toggle"):
128 if not arg in (*list(valid_modes.keys()), "toggle"):
129 error(errorMessage())
129 error(errorMessage())
130 return
130 return
131
131
132 if arg in (valid_modes.keys()):
132 if arg in (valid_modes.keys()):
133 self.shell.autocall = arg
133 self.shell.autocall = arg
134 else: # toggle
134 else: # toggle
135 if self.shell.autocall:
135 if self.shell.autocall:
136 self._magic_state.autocall_save = self.shell.autocall
136 self._magic_state.autocall_save = self.shell.autocall
137 self.shell.autocall = 0
137 self.shell.autocall = 0
138 else:
138 else:
139 try:
139 try:
140 self.shell.autocall = self._magic_state.autocall_save
140 self.shell.autocall = self._magic_state.autocall_save
141 except AttributeError:
141 except AttributeError:
142 self.shell.autocall = self._magic_state.autocall_save = 1
142 self.shell.autocall = self._magic_state.autocall_save = 1
143
143
144 print("Automatic calling is:", list(valid_modes.values())[self.shell.autocall])
144 print("Automatic calling is:", list(valid_modes.values())[self.shell.autocall])
@@ -1,1616 +1,1624
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Implementation of execution-related magic functions."""
2 """Implementation of execution-related magic functions."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7
7
8 import ast
8 import ast
9 import bdb
9 import bdb
10 import builtins as builtin_mod
10 import builtins as builtin_mod
11 import copy
11 import cProfile as profile
12 import cProfile as profile
12 import gc
13 import gc
13 import itertools
14 import itertools
14 import math
15 import math
15 import os
16 import os
16 import pstats
17 import pstats
17 import re
18 import re
18 import shlex
19 import shlex
19 import sys
20 import sys
20 import time
21 import time
21 import timeit
22 import timeit
22 from typing import Dict, Any
23 from typing import Dict, Any
23 from ast import (
24 from ast import (
25 Assign,
26 Call,
27 Expr,
28 Load,
24 Module,
29 Module,
30 Name,
31 NodeTransformer,
32 Store,
33 parse,
34 unparse,
25 )
35 )
26 from io import StringIO
36 from io import StringIO
27 from logging import error
37 from logging import error
28 from pathlib import Path
38 from pathlib import Path
29 from pdb import Restart
39 from pdb import Restart
30 from textwrap import indent
40 from textwrap import dedent, indent
31 from warnings import warn
41 from warnings import warn
32
42
33 from IPython.core import magic_arguments, page
43 from IPython.core import magic_arguments, oinspect, page
34 from IPython.core.displayhook import DisplayHook
44 from IPython.core.displayhook import DisplayHook
35 from IPython.core.error import UsageError
45 from IPython.core.error import UsageError
36 from IPython.core.macro import Macro
46 from IPython.core.macro import Macro
37 from IPython.core.magic import (
47 from IPython.core.magic import (
38 Magics,
48 Magics,
39 cell_magic,
49 cell_magic,
40 line_cell_magic,
50 line_cell_magic,
41 line_magic,
51 line_magic,
42 magics_class,
52 magics_class,
43 needs_local_scope,
53 needs_local_scope,
44 no_var_expand,
54 no_var_expand,
45 on_off,
55 on_off,
46 output_can_be_silenced,
56 output_can_be_silenced,
47 )
57 )
48 from IPython.testing.skipdoctest import skip_doctest
58 from IPython.testing.skipdoctest import skip_doctest
49 from IPython.utils.capture import capture_output
59 from IPython.utils.capture import capture_output
50 from IPython.utils.contexts import preserve_keys
60 from IPython.utils.contexts import preserve_keys
51 from IPython.utils.ipstruct import Struct
61 from IPython.utils.ipstruct import Struct
52 from IPython.utils.module_paths import find_mod
62 from IPython.utils.module_paths import find_mod
53 from IPython.utils.path import get_py_filename, shellglob
63 from IPython.utils.path import get_py_filename, shellglob
54 from IPython.utils.timing import clock, clock2
64 from IPython.utils.timing import clock, clock2
55 from IPython.core.magics.ast_mod import ReplaceCodeTransformer
65 from IPython.core.magics.ast_mod import ReplaceCodeTransformer
56
66
57 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
58 # Magic implementation classes
68 # Magic implementation classes
59 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
60
70
61
71
62 class TimeitResult(object):
72 class TimeitResult(object):
63 """
73 """
64 Object returned by the timeit magic with info about the run.
74 Object returned by the timeit magic with info about the run.
65
75
66 Contains the following attributes :
76 Contains the following attributes :
67
77
68 loops: (int) number of loops done per measurement
78 loops: (int) number of loops done per measurement
69 repeat: (int) number of times the measurement has been repeated
79 repeat: (int) number of times the measurement has been repeated
70 best: (float) best execution time / number
80 best: (float) best execution time / number
71 all_runs: (list of float) execution time of each run (in s)
81 all_runs: (list of float) execution time of each run (in s)
72 compile_time: (float) time of statement compilation (s)
82 compile_time: (float) time of statement compilation (s)
73
83
74 """
84 """
75 def __init__(self, loops, repeat, best, worst, all_runs, compile_time, precision):
85 def __init__(self, loops, repeat, best, worst, all_runs, compile_time, precision):
76 self.loops = loops
86 self.loops = loops
77 self.repeat = repeat
87 self.repeat = repeat
78 self.best = best
88 self.best = best
79 self.worst = worst
89 self.worst = worst
80 self.all_runs = all_runs
90 self.all_runs = all_runs
81 self.compile_time = compile_time
91 self.compile_time = compile_time
82 self._precision = precision
92 self._precision = precision
83 self.timings = [ dt / self.loops for dt in all_runs]
93 self.timings = [ dt / self.loops for dt in all_runs]
84
94
85 @property
95 @property
86 def average(self):
96 def average(self):
87 return math.fsum(self.timings) / len(self.timings)
97 return math.fsum(self.timings) / len(self.timings)
88
98
89 @property
99 @property
90 def stdev(self):
100 def stdev(self):
91 mean = self.average
101 mean = self.average
92 return (math.fsum([(x - mean) ** 2 for x in self.timings]) / len(self.timings)) ** 0.5
102 return (math.fsum([(x - mean) ** 2 for x in self.timings]) / len(self.timings)) ** 0.5
93
103
94 def __str__(self):
104 def __str__(self):
95 pm = '+-'
105 pm = '+-'
96 if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
106 if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
97 try:
107 try:
98 u'\xb1'.encode(sys.stdout.encoding)
108 u'\xb1'.encode(sys.stdout.encoding)
99 pm = u'\xb1'
109 pm = u'\xb1'
100 except:
110 except:
101 pass
111 pass
102 return "{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops:,} loop{loop_plural} each)".format(
112 return "{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops:,} loop{loop_plural} each)".format(
103 pm=pm,
113 pm=pm,
104 runs=self.repeat,
114 runs=self.repeat,
105 loops=self.loops,
115 loops=self.loops,
106 loop_plural="" if self.loops == 1 else "s",
116 loop_plural="" if self.loops == 1 else "s",
107 run_plural="" if self.repeat == 1 else "s",
117 run_plural="" if self.repeat == 1 else "s",
108 mean=_format_time(self.average, self._precision),
118 mean=_format_time(self.average, self._precision),
109 std=_format_time(self.stdev, self._precision),
119 std=_format_time(self.stdev, self._precision),
110 )
120 )
111
121
112 def _repr_pretty_(self, p , cycle):
122 def _repr_pretty_(self, p , cycle):
113 unic = self.__str__()
123 unic = self.__str__()
114 p.text(u'<TimeitResult : '+unic+u'>')
124 p.text(u'<TimeitResult : '+unic+u'>')
115
125
116
126
117 class TimeitTemplateFiller(ast.NodeTransformer):
127 class TimeitTemplateFiller(ast.NodeTransformer):
118 """Fill in the AST template for timing execution.
128 """Fill in the AST template for timing execution.
119
129
120 This is quite closely tied to the template definition, which is in
130 This is quite closely tied to the template definition, which is in
121 :meth:`ExecutionMagics.timeit`.
131 :meth:`ExecutionMagics.timeit`.
122 """
132 """
123 def __init__(self, ast_setup, ast_stmt):
133 def __init__(self, ast_setup, ast_stmt):
124 self.ast_setup = ast_setup
134 self.ast_setup = ast_setup
125 self.ast_stmt = ast_stmt
135 self.ast_stmt = ast_stmt
126
136
127 def visit_FunctionDef(self, node):
137 def visit_FunctionDef(self, node):
128 "Fill in the setup statement"
138 "Fill in the setup statement"
129 self.generic_visit(node)
139 self.generic_visit(node)
130 if node.name == "inner":
140 if node.name == "inner":
131 node.body[:1] = self.ast_setup.body
141 node.body[:1] = self.ast_setup.body
132
142
133 return node
143 return node
134
144
135 def visit_For(self, node):
145 def visit_For(self, node):
136 "Fill in the statement to be timed"
146 "Fill in the statement to be timed"
137 if getattr(getattr(node.body[0], 'value', None), 'id', None) == 'stmt':
147 if getattr(getattr(node.body[0], 'value', None), 'id', None) == 'stmt':
138 node.body = self.ast_stmt.body
148 node.body = self.ast_stmt.body
139 return node
149 return node
140
150
141
151
142 class Timer(timeit.Timer):
152 class Timer(timeit.Timer):
143 """Timer class that explicitly uses self.inner
153 """Timer class that explicitly uses self.inner
144
154
145 which is an undocumented implementation detail of CPython,
155 which is an undocumented implementation detail of CPython,
146 not shared by PyPy.
156 not shared by PyPy.
147 """
157 """
148 # Timer.timeit copied from CPython 3.4.2
158 # Timer.timeit copied from CPython 3.4.2
149 def timeit(self, number=timeit.default_number):
159 def timeit(self, number=timeit.default_number):
150 """Time 'number' executions of the main statement.
160 """Time 'number' executions of the main statement.
151
161
152 To be precise, this executes the setup statement once, and
162 To be precise, this executes the setup statement once, and
153 then returns the time it takes to execute the main statement
163 then returns the time it takes to execute the main statement
154 a number of times, as a float measured in seconds. The
164 a number of times, as a float measured in seconds. The
155 argument is the number of times through the loop, defaulting
165 argument is the number of times through the loop, defaulting
156 to one million. The main statement, the setup statement and
166 to one million. The main statement, the setup statement and
157 the timer function to be used are passed to the constructor.
167 the timer function to be used are passed to the constructor.
158 """
168 """
159 it = itertools.repeat(None, number)
169 it = itertools.repeat(None, number)
160 gcold = gc.isenabled()
170 gcold = gc.isenabled()
161 gc.disable()
171 gc.disable()
162 try:
172 try:
163 timing = self.inner(it, self.timer)
173 timing = self.inner(it, self.timer)
164 finally:
174 finally:
165 if gcold:
175 if gcold:
166 gc.enable()
176 gc.enable()
167 return timing
177 return timing
168
178
169
179
170 @magics_class
180 @magics_class
171 class ExecutionMagics(Magics):
181 class ExecutionMagics(Magics):
172 """Magics related to code execution, debugging, profiling, etc."""
182 """Magics related to code execution, debugging, profiling, etc."""
173
183
174 _transformers: Dict[str, Any] = {}
184 _transformers: Dict[str, Any] = {}
175
185
176 def __init__(self, shell):
186 def __init__(self, shell):
177 super(ExecutionMagics, self).__init__(shell)
187 super(ExecutionMagics, self).__init__(shell)
178 # Default execution function used to actually run user code.
188 # Default execution function used to actually run user code.
179 self.default_runner = None
189 self.default_runner = None
180
190
181 @skip_doctest
191 @skip_doctest
182 @no_var_expand
192 @no_var_expand
183 @line_cell_magic
193 @line_cell_magic
184 def prun(self, parameter_s='', cell=None):
194 def prun(self, parameter_s='', cell=None):
185
195
186 """Run a statement through the python code profiler.
196 """Run a statement through the python code profiler.
187
197
188 **Usage, in line mode:**
198 **Usage, in line mode:**
189
199
190 %prun [options] statement
200 %prun [options] statement
191
201
192 **Usage, in cell mode:**
202 **Usage, in cell mode:**
193
203
194 %%prun [options] [statement]
204 %%prun [options] [statement]
195
205
196 code...
206 code...
197
207
198 code...
208 code...
199
209
200 In cell mode, the additional code lines are appended to the (possibly
210 In cell mode, the additional code lines are appended to the (possibly
201 empty) statement in the first line. Cell mode allows you to easily
211 empty) statement in the first line. Cell mode allows you to easily
202 profile multiline blocks without having to put them in a separate
212 profile multiline blocks without having to put them in a separate
203 function.
213 function.
204
214
205 The given statement (which doesn't require quote marks) is run via the
215 The given statement (which doesn't require quote marks) is run via the
206 python profiler in a manner similar to the profile.run() function.
216 python profiler in a manner similar to the profile.run() function.
207 Namespaces are internally managed to work correctly; profile.run
217 Namespaces are internally managed to work correctly; profile.run
208 cannot be used in IPython because it makes certain assumptions about
218 cannot be used in IPython because it makes certain assumptions about
209 namespaces which do not hold under IPython.
219 namespaces which do not hold under IPython.
210
220
211 Options:
221 Options:
212
222
213 -l <limit>
223 -l <limit>
214 you can place restrictions on what or how much of the
224 you can place restrictions on what or how much of the
215 profile gets printed. The limit value can be:
225 profile gets printed. The limit value can be:
216
226
217 * A string: only information for function names containing this string
227 * A string: only information for function names containing this string
218 is printed.
228 is printed.
219
229
220 * An integer: only these many lines are printed.
230 * An integer: only these many lines are printed.
221
231
222 * A float (between 0 and 1): this fraction of the report is printed
232 * A float (between 0 and 1): this fraction of the report is printed
223 (for example, use a limit of 0.4 to see the topmost 40% only).
233 (for example, use a limit of 0.4 to see the topmost 40% only).
224
234
225 You can combine several limits with repeated use of the option. For
235 You can combine several limits with repeated use of the option. For
226 example, ``-l __init__ -l 5`` will print only the topmost 5 lines of
236 example, ``-l __init__ -l 5`` will print only the topmost 5 lines of
227 information about class constructors.
237 information about class constructors.
228
238
229 -r
239 -r
230 return the pstats.Stats object generated by the profiling. This
240 return the pstats.Stats object generated by the profiling. This
231 object has all the information about the profile in it, and you can
241 object has all the information about the profile in it, and you can
232 later use it for further analysis or in other functions.
242 later use it for further analysis or in other functions.
233
243
234 -s <key>
244 -s <key>
235 sort profile by given key. You can provide more than one key
245 sort profile by given key. You can provide more than one key
236 by using the option several times: '-s key1 -s key2 -s key3...'. The
246 by using the option several times: '-s key1 -s key2 -s key3...'. The
237 default sorting key is 'time'.
247 default sorting key is 'time'.
238
248
239 The following is copied verbatim from the profile documentation
249 The following is copied verbatim from the profile documentation
240 referenced below:
250 referenced below:
241
251
242 When more than one key is provided, additional keys are used as
252 When more than one key is provided, additional keys are used as
243 secondary criteria when the there is equality in all keys selected
253 secondary criteria when the there is equality in all keys selected
244 before them.
254 before them.
245
255
246 Abbreviations can be used for any key names, as long as the
256 Abbreviations can be used for any key names, as long as the
247 abbreviation is unambiguous. The following are the keys currently
257 abbreviation is unambiguous. The following are the keys currently
248 defined:
258 defined:
249
259
250 ============ =====================
260 ============ =====================
251 Valid Arg Meaning
261 Valid Arg Meaning
252 ============ =====================
262 ============ =====================
253 "calls" call count
263 "calls" call count
254 "cumulative" cumulative time
264 "cumulative" cumulative time
255 "file" file name
265 "file" file name
256 "module" file name
266 "module" file name
257 "pcalls" primitive call count
267 "pcalls" primitive call count
258 "line" line number
268 "line" line number
259 "name" function name
269 "name" function name
260 "nfl" name/file/line
270 "nfl" name/file/line
261 "stdname" standard name
271 "stdname" standard name
262 "time" internal time
272 "time" internal time
263 ============ =====================
273 ============ =====================
264
274
265 Note that all sorts on statistics are in descending order (placing
275 Note that all sorts on statistics are in descending order (placing
266 most time consuming items first), where as name, file, and line number
276 most time consuming items first), where as name, file, and line number
267 searches are in ascending order (i.e., alphabetical). The subtle
277 searches are in ascending order (i.e., alphabetical). The subtle
268 distinction between "nfl" and "stdname" is that the standard name is a
278 distinction between "nfl" and "stdname" is that the standard name is a
269 sort of the name as printed, which means that the embedded line
279 sort of the name as printed, which means that the embedded line
270 numbers get compared in an odd way. For example, lines 3, 20, and 40
280 numbers get compared in an odd way. For example, lines 3, 20, and 40
271 would (if the file names were the same) appear in the string order
281 would (if the file names were the same) appear in the string order
272 "20" "3" and "40". In contrast, "nfl" does a numeric compare of the
282 "20" "3" and "40". In contrast, "nfl" does a numeric compare of the
273 line numbers. In fact, sort_stats("nfl") is the same as
283 line numbers. In fact, sort_stats("nfl") is the same as
274 sort_stats("name", "file", "line").
284 sort_stats("name", "file", "line").
275
285
276 -T <filename>
286 -T <filename>
277 save profile results as shown on screen to a text
287 save profile results as shown on screen to a text
278 file. The profile is still shown on screen.
288 file. The profile is still shown on screen.
279
289
280 -D <filename>
290 -D <filename>
281 save (via dump_stats) profile statistics to given
291 save (via dump_stats) profile statistics to given
282 filename. This data is in a format understood by the pstats module, and
292 filename. This data is in a format understood by the pstats module, and
283 is generated by a call to the dump_stats() method of profile
293 is generated by a call to the dump_stats() method of profile
284 objects. The profile is still shown on screen.
294 objects. The profile is still shown on screen.
285
295
286 -q
296 -q
287 suppress output to the pager. Best used with -T and/or -D above.
297 suppress output to the pager. Best used with -T and/or -D above.
288
298
289 If you want to run complete programs under the profiler's control, use
299 If you want to run complete programs under the profiler's control, use
290 ``%run -p [prof_opts] filename.py [args to program]`` where prof_opts
300 ``%run -p [prof_opts] filename.py [args to program]`` where prof_opts
291 contains profiler specific options as described here.
301 contains profiler specific options as described here.
292
302
293 You can read the complete documentation for the profile module with::
303 You can read the complete documentation for the profile module with::
294
304
295 In [1]: import profile; profile.help()
305 In [1]: import profile; profile.help()
296
306
297 .. versionchanged:: 7.3
307 .. versionchanged:: 7.3
298 User variables are no longer expanded,
308 User variables are no longer expanded,
299 the magic line is always left unmodified.
309 the magic line is always left unmodified.
300
310
301 """
311 """
302 opts, arg_str = self.parse_options(parameter_s, 'D:l:rs:T:q',
312 opts, arg_str = self.parse_options(parameter_s, 'D:l:rs:T:q',
303 list_all=True, posix=False)
313 list_all=True, posix=False)
304 if cell is not None:
314 if cell is not None:
305 arg_str += '\n' + cell
315 arg_str += '\n' + cell
306 arg_str = self.shell.transform_cell(arg_str)
316 arg_str = self.shell.transform_cell(arg_str)
307 return self._run_with_profiler(arg_str, opts, self.shell.user_ns)
317 return self._run_with_profiler(arg_str, opts, self.shell.user_ns)
308
318
309 def _run_with_profiler(self, code, opts, namespace):
319 def _run_with_profiler(self, code, opts, namespace):
310 """
320 """
311 Run `code` with profiler. Used by ``%prun`` and ``%run -p``.
321 Run `code` with profiler. Used by ``%prun`` and ``%run -p``.
312
322
313 Parameters
323 Parameters
314 ----------
324 ----------
315 code : str
325 code : str
316 Code to be executed.
326 Code to be executed.
317 opts : Struct
327 opts : Struct
318 Options parsed by `self.parse_options`.
328 Options parsed by `self.parse_options`.
319 namespace : dict
329 namespace : dict
320 A dictionary for Python namespace (e.g., `self.shell.user_ns`).
330 A dictionary for Python namespace (e.g., `self.shell.user_ns`).
321
331
322 """
332 """
323
333
324 # Fill default values for unspecified options:
334 # Fill default values for unspecified options:
325 opts.merge(Struct(D=[''], l=[], s=['time'], T=['']))
335 opts.merge(Struct(D=[''], l=[], s=['time'], T=['']))
326
336
327 prof = profile.Profile()
337 prof = profile.Profile()
328 try:
338 try:
329 prof = prof.runctx(code, namespace, namespace)
339 prof = prof.runctx(code, namespace, namespace)
330 sys_exit = ''
340 sys_exit = ''
331 except SystemExit:
341 except SystemExit:
332 sys_exit = """*** SystemExit exception caught in code being profiled."""
342 sys_exit = """*** SystemExit exception caught in code being profiled."""
333
343
334 stats = pstats.Stats(prof).strip_dirs().sort_stats(*opts.s)
344 stats = pstats.Stats(prof).strip_dirs().sort_stats(*opts.s)
335
345
336 lims = opts.l
346 lims = opts.l
337 if lims:
347 if lims:
338 lims = [] # rebuild lims with ints/floats/strings
348 lims = [] # rebuild lims with ints/floats/strings
339 for lim in opts.l:
349 for lim in opts.l:
340 try:
350 try:
341 lims.append(int(lim))
351 lims.append(int(lim))
342 except ValueError:
352 except ValueError:
343 try:
353 try:
344 lims.append(float(lim))
354 lims.append(float(lim))
345 except ValueError:
355 except ValueError:
346 lims.append(lim)
356 lims.append(lim)
347
357
348 # Trap output.
358 # Trap output.
349 stdout_trap = StringIO()
359 stdout_trap = StringIO()
350 stats_stream = stats.stream
360 stats_stream = stats.stream
351 try:
361 try:
352 stats.stream = stdout_trap
362 stats.stream = stdout_trap
353 stats.print_stats(*lims)
363 stats.print_stats(*lims)
354 finally:
364 finally:
355 stats.stream = stats_stream
365 stats.stream = stats_stream
356
366
357 output = stdout_trap.getvalue()
367 output = stdout_trap.getvalue()
358 output = output.rstrip()
368 output = output.rstrip()
359
369
360 if 'q' not in opts:
370 if 'q' not in opts:
361 page.page(output)
371 page.page(output)
362 print(sys_exit, end=' ')
372 print(sys_exit, end=' ')
363
373
364 dump_file = opts.D[0]
374 dump_file = opts.D[0]
365 text_file = opts.T[0]
375 text_file = opts.T[0]
366 if dump_file:
376 if dump_file:
367 prof.dump_stats(dump_file)
377 prof.dump_stats(dump_file)
368 print(
378 print(
369 f"\n*** Profile stats marshalled to file {repr(dump_file)}.{sys_exit}"
379 f"\n*** Profile stats marshalled to file {repr(dump_file)}.{sys_exit}"
370 )
380 )
371 if text_file:
381 if text_file:
372 pfile = Path(text_file)
382 pfile = Path(text_file)
373 pfile.touch(exist_ok=True)
383 pfile.touch(exist_ok=True)
374 pfile.write_text(output, encoding="utf-8")
384 pfile.write_text(output, encoding="utf-8")
375
385
376 print(
386 print(
377 f"\n*** Profile printout saved to text file {repr(text_file)}.{sys_exit}"
387 f"\n*** Profile printout saved to text file {repr(text_file)}.{sys_exit}"
378 )
388 )
379
389
380 if 'r' in opts:
390 if 'r' in opts:
381 return stats
391 return stats
382
392
383 return None
393 return None
384
394
385 @line_magic
395 @line_magic
386 def pdb(self, parameter_s=''):
396 def pdb(self, parameter_s=''):
387 """Control the automatic calling of the pdb interactive debugger.
397 """Control the automatic calling of the pdb interactive debugger.
388
398
389 Call as '%pdb on', '%pdb 1', '%pdb off' or '%pdb 0'. If called without
399 Call as '%pdb on', '%pdb 1', '%pdb off' or '%pdb 0'. If called without
390 argument it works as a toggle.
400 argument it works as a toggle.
391
401
392 When an exception is triggered, IPython can optionally call the
402 When an exception is triggered, IPython can optionally call the
393 interactive pdb debugger after the traceback printout. %pdb toggles
403 interactive pdb debugger after the traceback printout. %pdb toggles
394 this feature on and off.
404 this feature on and off.
395
405
396 The initial state of this feature is set in your configuration
406 The initial state of this feature is set in your configuration
397 file (the option is ``InteractiveShell.pdb``).
407 file (the option is ``InteractiveShell.pdb``).
398
408
399 If you want to just activate the debugger AFTER an exception has fired,
409 If you want to just activate the debugger AFTER an exception has fired,
400 without having to type '%pdb on' and rerunning your code, you can use
410 without having to type '%pdb on' and rerunning your code, you can use
401 the %debug magic."""
411 the %debug magic."""
402
412
403 par = parameter_s.strip().lower()
413 par = parameter_s.strip().lower()
404
414
405 if par:
415 if par:
406 try:
416 try:
407 new_pdb = {'off':0,'0':0,'on':1,'1':1}[par]
417 new_pdb = {'off':0,'0':0,'on':1,'1':1}[par]
408 except KeyError:
418 except KeyError:
409 print ('Incorrect argument. Use on/1, off/0, '
419 print ('Incorrect argument. Use on/1, off/0, '
410 'or nothing for a toggle.')
420 'or nothing for a toggle.')
411 return
421 return
412 else:
422 else:
413 # toggle
423 # toggle
414 new_pdb = not self.shell.call_pdb
424 new_pdb = not self.shell.call_pdb
415
425
416 # set on the shell
426 # set on the shell
417 self.shell.call_pdb = new_pdb
427 self.shell.call_pdb = new_pdb
418 print('Automatic pdb calling has been turned',on_off(new_pdb))
428 print('Automatic pdb calling has been turned',on_off(new_pdb))
419
429
420 @magic_arguments.magic_arguments()
430 @magic_arguments.magic_arguments()
421 @magic_arguments.argument('--breakpoint', '-b', metavar='FILE:LINE',
431 @magic_arguments.argument('--breakpoint', '-b', metavar='FILE:LINE',
422 help="""
432 help="""
423 Set break point at LINE in FILE.
433 Set break point at LINE in FILE.
424 """
434 """
425 )
435 )
426 @magic_arguments.argument('statement', nargs='*',
436 @magic_arguments.argument('statement', nargs='*',
427 help="""
437 help="""
428 Code to run in debugger.
438 Code to run in debugger.
429 You can omit this in cell magic mode.
439 You can omit this in cell magic mode.
430 """
440 """
431 )
441 )
432 @no_var_expand
442 @no_var_expand
433 @line_cell_magic
443 @line_cell_magic
434 @needs_local_scope
444 @needs_local_scope
435 def debug(self, line="", cell=None, local_ns=None):
445 def debug(self, line="", cell=None, local_ns=None):
436 """Activate the interactive debugger.
446 """Activate the interactive debugger.
437
447
438 This magic command support two ways of activating debugger.
448 This magic command support two ways of activating debugger.
439 One is to activate debugger before executing code. This way, you
449 One is to activate debugger before executing code. This way, you
440 can set a break point, to step through the code from the point.
450 can set a break point, to step through the code from the point.
441 You can use this mode by giving statements to execute and optionally
451 You can use this mode by giving statements to execute and optionally
442 a breakpoint.
452 a breakpoint.
443
453
444 The other one is to activate debugger in post-mortem mode. You can
454 The other one is to activate debugger in post-mortem mode. You can
445 activate this mode simply running %debug without any argument.
455 activate this mode simply running %debug without any argument.
446 If an exception has just occurred, this lets you inspect its stack
456 If an exception has just occurred, this lets you inspect its stack
447 frames interactively. Note that this will always work only on the last
457 frames interactively. Note that this will always work only on the last
448 traceback that occurred, so you must call this quickly after an
458 traceback that occurred, so you must call this quickly after an
449 exception that you wish to inspect has fired, because if another one
459 exception that you wish to inspect has fired, because if another one
450 occurs, it clobbers the previous one.
460 occurs, it clobbers the previous one.
451
461
452 If you want IPython to automatically do this on every exception, see
462 If you want IPython to automatically do this on every exception, see
453 the %pdb magic for more details.
463 the %pdb magic for more details.
454
464
455 .. versionchanged:: 7.3
465 .. versionchanged:: 7.3
456 When running code, user variables are no longer expanded,
466 When running code, user variables are no longer expanded,
457 the magic line is always left unmodified.
467 the magic line is always left unmodified.
458
468
459 """
469 """
460 args = magic_arguments.parse_argstring(self.debug, line)
470 args = magic_arguments.parse_argstring(self.debug, line)
461
471
462 if not (args.breakpoint or args.statement or cell):
472 if not (args.breakpoint or args.statement or cell):
463 self._debug_post_mortem()
473 self._debug_post_mortem()
464 elif not (args.breakpoint or cell):
474 elif not (args.breakpoint or cell):
465 # If there is no breakpoints, the line is just code to execute
475 # If there is no breakpoints, the line is just code to execute
466 self._debug_exec(line, None, local_ns)
476 self._debug_exec(line, None, local_ns)
467 else:
477 else:
468 # Here we try to reconstruct the code from the output of
478 # Here we try to reconstruct the code from the output of
469 # parse_argstring. This might not work if the code has spaces
479 # parse_argstring. This might not work if the code has spaces
470 # For example this fails for `print("a b")`
480 # For example this fails for `print("a b")`
471 code = "\n".join(args.statement)
481 code = "\n".join(args.statement)
472 if cell:
482 if cell:
473 code += "\n" + cell
483 code += "\n" + cell
474 self._debug_exec(code, args.breakpoint, local_ns)
484 self._debug_exec(code, args.breakpoint, local_ns)
475
485
476 def _debug_post_mortem(self):
486 def _debug_post_mortem(self):
477 self.shell.debugger(force=True)
487 self.shell.debugger(force=True)
478
488
479 def _debug_exec(self, code, breakpoint, local_ns=None):
489 def _debug_exec(self, code, breakpoint, local_ns=None):
480 if breakpoint:
490 if breakpoint:
481 (filename, bp_line) = breakpoint.rsplit(':', 1)
491 (filename, bp_line) = breakpoint.rsplit(':', 1)
482 bp_line = int(bp_line)
492 bp_line = int(bp_line)
483 else:
493 else:
484 (filename, bp_line) = (None, None)
494 (filename, bp_line) = (None, None)
485 self._run_with_debugger(
495 self._run_with_debugger(
486 code, self.shell.user_ns, filename, bp_line, local_ns=local_ns
496 code, self.shell.user_ns, filename, bp_line, local_ns=local_ns
487 )
497 )
488
498
489 @line_magic
499 @line_magic
490 def tb(self, s):
500 def tb(self, s):
491 """Print the last traceback.
501 """Print the last traceback.
492
502
493 Optionally, specify an exception reporting mode, tuning the
503 Optionally, specify an exception reporting mode, tuning the
494 verbosity of the traceback. By default the currently-active exception
504 verbosity of the traceback. By default the currently-active exception
495 mode is used. See %xmode for changing exception reporting modes.
505 mode is used. See %xmode for changing exception reporting modes.
496
506
497 Valid modes: Plain, Context, Verbose, and Minimal.
507 Valid modes: Plain, Context, Verbose, and Minimal.
498 """
508 """
499 interactive_tb = self.shell.InteractiveTB
509 interactive_tb = self.shell.InteractiveTB
500 if s:
510 if s:
501 # Switch exception reporting mode for this one call.
511 # Switch exception reporting mode for this one call.
502 # Ensure it is switched back.
512 # Ensure it is switched back.
503 def xmode_switch_err(name):
513 def xmode_switch_err(name):
504 warn('Error changing %s exception modes.\n%s' %
514 warn('Error changing %s exception modes.\n%s' %
505 (name,sys.exc_info()[1]))
515 (name,sys.exc_info()[1]))
506
516
507 new_mode = s.strip().capitalize()
517 new_mode = s.strip().capitalize()
508 original_mode = interactive_tb.mode
518 original_mode = interactive_tb.mode
509 try:
519 try:
510 try:
520 try:
511 interactive_tb.set_mode(mode=new_mode)
521 interactive_tb.set_mode(mode=new_mode)
512 except Exception:
522 except Exception:
513 xmode_switch_err('user')
523 xmode_switch_err('user')
514 else:
524 else:
515 self.shell.showtraceback()
525 self.shell.showtraceback()
516 finally:
526 finally:
517 interactive_tb.set_mode(mode=original_mode)
527 interactive_tb.set_mode(mode=original_mode)
518 else:
528 else:
519 self.shell.showtraceback()
529 self.shell.showtraceback()
520
530
521 @skip_doctest
531 @skip_doctest
522 @line_magic
532 @line_magic
523 def run(self, parameter_s='', runner=None,
533 def run(self, parameter_s='', runner=None,
524 file_finder=get_py_filename):
534 file_finder=get_py_filename):
525 """Run the named file inside IPython as a program.
535 """Run the named file inside IPython as a program.
526
536
527 Usage::
537 Usage::
528
538
529 %run [-n -i -e -G]
539 %run [-n -i -e -G]
530 [( -t [-N<N>] | -d [-b<N>] | -p [profile options] )]
540 [( -t [-N<N>] | -d [-b<N>] | -p [profile options] )]
531 ( -m mod | filename ) [args]
541 ( -m mod | filename ) [args]
532
542
533 The filename argument should be either a pure Python script (with
543 The filename argument should be either a pure Python script (with
534 extension ``.py``), or a file with custom IPython syntax (such as
544 extension ``.py``), or a file with custom IPython syntax (such as
535 magics). If the latter, the file can be either a script with ``.ipy``
545 magics). If the latter, the file can be either a script with ``.ipy``
536 extension, or a Jupyter notebook with ``.ipynb`` extension. When running
546 extension, or a Jupyter notebook with ``.ipynb`` extension. When running
537 a Jupyter notebook, the output from print statements and other
547 a Jupyter notebook, the output from print statements and other
538 displayed objects will appear in the terminal (even matplotlib figures
548 displayed objects will appear in the terminal (even matplotlib figures
539 will open, if a terminal-compliant backend is being used). Note that,
549 will open, if a terminal-compliant backend is being used). Note that,
540 at the system command line, the ``jupyter run`` command offers similar
550 at the system command line, the ``jupyter run`` command offers similar
541 functionality for executing notebooks (albeit currently with some
551 functionality for executing notebooks (albeit currently with some
542 differences in supported options).
552 differences in supported options).
543
553
544 Parameters after the filename are passed as command-line arguments to
554 Parameters after the filename are passed as command-line arguments to
545 the program (put in sys.argv). Then, control returns to IPython's
555 the program (put in sys.argv). Then, control returns to IPython's
546 prompt.
556 prompt.
547
557
548 This is similar to running at a system prompt ``python file args``,
558 This is similar to running at a system prompt ``python file args``,
549 but with the advantage of giving you IPython's tracebacks, and of
559 but with the advantage of giving you IPython's tracebacks, and of
550 loading all variables into your interactive namespace for further use
560 loading all variables into your interactive namespace for further use
551 (unless -p is used, see below).
561 (unless -p is used, see below).
552
562
553 The file is executed in a namespace initially consisting only of
563 The file is executed in a namespace initially consisting only of
554 ``__name__=='__main__'`` and sys.argv constructed as indicated. It thus
564 ``__name__=='__main__'`` and sys.argv constructed as indicated. It thus
555 sees its environment as if it were being run as a stand-alone program
565 sees its environment as if it were being run as a stand-alone program
556 (except for sharing global objects such as previously imported
566 (except for sharing global objects such as previously imported
557 modules). But after execution, the IPython interactive namespace gets
567 modules). But after execution, the IPython interactive namespace gets
558 updated with all variables defined in the program (except for __name__
568 updated with all variables defined in the program (except for __name__
559 and sys.argv). This allows for very convenient loading of code for
569 and sys.argv). This allows for very convenient loading of code for
560 interactive work, while giving each program a 'clean sheet' to run in.
570 interactive work, while giving each program a 'clean sheet' to run in.
561
571
562 Arguments are expanded using shell-like glob match. Patterns
572 Arguments are expanded using shell-like glob match. Patterns
563 '*', '?', '[seq]' and '[!seq]' can be used. Additionally,
573 '*', '?', '[seq]' and '[!seq]' can be used. Additionally,
564 tilde '~' will be expanded into user's home directory. Unlike
574 tilde '~' will be expanded into user's home directory. Unlike
565 real shells, quotation does not suppress expansions. Use
575 real shells, quotation does not suppress expansions. Use
566 *two* back slashes (e.g. ``\\\\*``) to suppress expansions.
576 *two* back slashes (e.g. ``\\\\*``) to suppress expansions.
567 To completely disable these expansions, you can use -G flag.
577 To completely disable these expansions, you can use -G flag.
568
578
569 On Windows systems, the use of single quotes `'` when specifying
579 On Windows systems, the use of single quotes `'` when specifying
570 a file is not supported. Use double quotes `"`.
580 a file is not supported. Use double quotes `"`.
571
581
572 Options:
582 Options:
573
583
574 -n
584 -n
575 __name__ is NOT set to '__main__', but to the running file's name
585 __name__ is NOT set to '__main__', but to the running file's name
576 without extension (as python does under import). This allows running
586 without extension (as python does under import). This allows running
577 scripts and reloading the definitions in them without calling code
587 scripts and reloading the definitions in them without calling code
578 protected by an ``if __name__ == "__main__"`` clause.
588 protected by an ``if __name__ == "__main__"`` clause.
579
589
580 -i
590 -i
581 run the file in IPython's namespace instead of an empty one. This
591 run the file in IPython's namespace instead of an empty one. This
582 is useful if you are experimenting with code written in a text editor
592 is useful if you are experimenting with code written in a text editor
583 which depends on variables defined interactively.
593 which depends on variables defined interactively.
584
594
585 -e
595 -e
586 ignore sys.exit() calls or SystemExit exceptions in the script
596 ignore sys.exit() calls or SystemExit exceptions in the script
587 being run. This is particularly useful if IPython is being used to
597 being run. This is particularly useful if IPython is being used to
588 run unittests, which always exit with a sys.exit() call. In such
598 run unittests, which always exit with a sys.exit() call. In such
589 cases you are interested in the output of the test results, not in
599 cases you are interested in the output of the test results, not in
590 seeing a traceback of the unittest module.
600 seeing a traceback of the unittest module.
591
601
592 -t
602 -t
593 print timing information at the end of the run. IPython will give
603 print timing information at the end of the run. IPython will give
594 you an estimated CPU time consumption for your script, which under
604 you an estimated CPU time consumption for your script, which under
595 Unix uses the resource module to avoid the wraparound problems of
605 Unix uses the resource module to avoid the wraparound problems of
596 time.clock(). Under Unix, an estimate of time spent on system tasks
606 time.clock(). Under Unix, an estimate of time spent on system tasks
597 is also given (for Windows platforms this is reported as 0.0).
607 is also given (for Windows platforms this is reported as 0.0).
598
608
599 If -t is given, an additional ``-N<N>`` option can be given, where <N>
609 If -t is given, an additional ``-N<N>`` option can be given, where <N>
600 must be an integer indicating how many times you want the script to
610 must be an integer indicating how many times you want the script to
601 run. The final timing report will include total and per run results.
611 run. The final timing report will include total and per run results.
602
612
603 For example (testing the script uniq_stable.py)::
613 For example (testing the script uniq_stable.py)::
604
614
605 In [1]: run -t uniq_stable
615 In [1]: run -t uniq_stable
606
616
607 IPython CPU timings (estimated):
617 IPython CPU timings (estimated):
608 User : 0.19597 s.
618 User : 0.19597 s.
609 System: 0.0 s.
619 System: 0.0 s.
610
620
611 In [2]: run -t -N5 uniq_stable
621 In [2]: run -t -N5 uniq_stable
612
622
613 IPython CPU timings (estimated):
623 IPython CPU timings (estimated):
614 Total runs performed: 5
624 Total runs performed: 5
615 Times : Total Per run
625 Times : Total Per run
616 User : 0.910862 s, 0.1821724 s.
626 User : 0.910862 s, 0.1821724 s.
617 System: 0.0 s, 0.0 s.
627 System: 0.0 s, 0.0 s.
618
628
619 -d
629 -d
620 run your program under the control of pdb, the Python debugger.
630 run your program under the control of pdb, the Python debugger.
621 This allows you to execute your program step by step, watch variables,
631 This allows you to execute your program step by step, watch variables,
622 etc. Internally, what IPython does is similar to calling::
632 etc. Internally, what IPython does is similar to calling::
623
633
624 pdb.run('execfile("YOURFILENAME")')
634 pdb.run('execfile("YOURFILENAME")')
625
635
626 with a breakpoint set on line 1 of your file. You can change the line
636 with a breakpoint set on line 1 of your file. You can change the line
627 number for this automatic breakpoint to be <N> by using the -bN option
637 number for this automatic breakpoint to be <N> by using the -bN option
628 (where N must be an integer). For example::
638 (where N must be an integer). For example::
629
639
630 %run -d -b40 myscript
640 %run -d -b40 myscript
631
641
632 will set the first breakpoint at line 40 in myscript.py. Note that
642 will set the first breakpoint at line 40 in myscript.py. Note that
633 the first breakpoint must be set on a line which actually does
643 the first breakpoint must be set on a line which actually does
634 something (not a comment or docstring) for it to stop execution.
644 something (not a comment or docstring) for it to stop execution.
635
645
636 Or you can specify a breakpoint in a different file::
646 Or you can specify a breakpoint in a different file::
637
647
638 %run -d -b myotherfile.py:20 myscript
648 %run -d -b myotherfile.py:20 myscript
639
649
640 When the pdb debugger starts, you will see a (Pdb) prompt. You must
650 When the pdb debugger starts, you will see a (Pdb) prompt. You must
641 first enter 'c' (without quotes) to start execution up to the first
651 first enter 'c' (without quotes) to start execution up to the first
642 breakpoint.
652 breakpoint.
643
653
644 Entering 'help' gives information about the use of the debugger. You
654 Entering 'help' gives information about the use of the debugger. You
645 can easily see pdb's full documentation with "import pdb;pdb.help()"
655 can easily see pdb's full documentation with "import pdb;pdb.help()"
646 at a prompt.
656 at a prompt.
647
657
648 -p
658 -p
649 run program under the control of the Python profiler module (which
659 run program under the control of the Python profiler module (which
650 prints a detailed report of execution times, function calls, etc).
660 prints a detailed report of execution times, function calls, etc).
651
661
652 You can pass other options after -p which affect the behavior of the
662 You can pass other options after -p which affect the behavior of the
653 profiler itself. See the docs for %prun for details.
663 profiler itself. See the docs for %prun for details.
654
664
655 In this mode, the program's variables do NOT propagate back to the
665 In this mode, the program's variables do NOT propagate back to the
656 IPython interactive namespace (because they remain in the namespace
666 IPython interactive namespace (because they remain in the namespace
657 where the profiler executes them).
667 where the profiler executes them).
658
668
659 Internally this triggers a call to %prun, see its documentation for
669 Internally this triggers a call to %prun, see its documentation for
660 details on the options available specifically for profiling.
670 details on the options available specifically for profiling.
661
671
662 There is one special usage for which the text above doesn't apply:
672 There is one special usage for which the text above doesn't apply:
663 if the filename ends with .ipy[nb], the file is run as ipython script,
673 if the filename ends with .ipy[nb], the file is run as ipython script,
664 just as if the commands were written on IPython prompt.
674 just as if the commands were written on IPython prompt.
665
675
666 -m
676 -m
667 specify module name to load instead of script path. Similar to
677 specify module name to load instead of script path. Similar to
668 the -m option for the python interpreter. Use this option last if you
678 the -m option for the python interpreter. Use this option last if you
669 want to combine with other %run options. Unlike the python interpreter
679 want to combine with other %run options. Unlike the python interpreter
670 only source modules are allowed no .pyc or .pyo files.
680 only source modules are allowed no .pyc or .pyo files.
671 For example::
681 For example::
672
682
673 %run -m example
683 %run -m example
674
684
675 will run the example module.
685 will run the example module.
676
686
677 -G
687 -G
678 disable shell-like glob expansion of arguments.
688 disable shell-like glob expansion of arguments.
679
689
680 """
690 """
681
691
682 # Logic to handle issue #3664
692 # Logic to handle issue #3664
683 # Add '--' after '-m <module_name>' to ignore additional args passed to a module.
693 # Add '--' after '-m <module_name>' to ignore additional args passed to a module.
684 if '-m' in parameter_s and '--' not in parameter_s:
694 if '-m' in parameter_s and '--' not in parameter_s:
685 argv = shlex.split(parameter_s, posix=(os.name == 'posix'))
695 argv = shlex.split(parameter_s, posix=(os.name == 'posix'))
686 for idx, arg in enumerate(argv):
696 for idx, arg in enumerate(argv):
687 if arg and arg.startswith('-') and arg != '-':
697 if arg and arg.startswith('-') and arg != '-':
688 if arg == '-m':
698 if arg == '-m':
689 argv.insert(idx + 2, '--')
699 argv.insert(idx + 2, '--')
690 break
700 break
691 else:
701 else:
692 # Positional arg, break
702 # Positional arg, break
693 break
703 break
694 parameter_s = ' '.join(shlex.quote(arg) for arg in argv)
704 parameter_s = ' '.join(shlex.quote(arg) for arg in argv)
695
705
696 # get arguments and set sys.argv for program to be run.
706 # get arguments and set sys.argv for program to be run.
697 opts, arg_lst = self.parse_options(parameter_s,
707 opts, arg_lst = self.parse_options(parameter_s,
698 'nidtN:b:pD:l:rs:T:em:G',
708 'nidtN:b:pD:l:rs:T:em:G',
699 mode='list', list_all=1)
709 mode='list', list_all=1)
700 if "m" in opts:
710 if "m" in opts:
701 modulename = opts["m"][0]
711 modulename = opts["m"][0]
702 modpath = find_mod(modulename)
712 modpath = find_mod(modulename)
703 if modpath is None:
713 if modpath is None:
704 msg = '%r is not a valid modulename on sys.path'%modulename
714 msg = '%r is not a valid modulename on sys.path'%modulename
705 raise Exception(msg)
715 raise Exception(msg)
706 arg_lst = [modpath] + arg_lst
716 arg_lst = [modpath] + arg_lst
707 try:
717 try:
708 fpath = None # initialize to make sure fpath is in scope later
718 fpath = None # initialize to make sure fpath is in scope later
709 fpath = arg_lst[0]
719 fpath = arg_lst[0]
710 filename = file_finder(fpath)
720 filename = file_finder(fpath)
711 except IndexError as e:
721 except IndexError as e:
712 msg = 'you must provide at least a filename.'
722 msg = 'you must provide at least a filename.'
713 raise Exception(msg) from e
723 raise Exception(msg) from e
714 except IOError as e:
724 except IOError as e:
715 try:
725 try:
716 msg = str(e)
726 msg = str(e)
717 except UnicodeError:
727 except UnicodeError:
718 msg = e.message
728 msg = e.message
719 if os.name == 'nt' and re.match(r"^'.*'$",fpath):
729 if os.name == 'nt' and re.match(r"^'.*'$",fpath):
720 warn('For Windows, use double quotes to wrap a filename: %run "mypath\\myfile.py"')
730 warn('For Windows, use double quotes to wrap a filename: %run "mypath\\myfile.py"')
721 raise Exception(msg) from e
731 raise Exception(msg) from e
722 except TypeError:
732 except TypeError:
723 if fpath in sys.meta_path:
733 if fpath in sys.meta_path:
724 filename = ""
734 filename = ""
725 else:
735 else:
726 raise
736 raise
727
737
728 if filename.lower().endswith(('.ipy', '.ipynb')):
738 if filename.lower().endswith(('.ipy', '.ipynb')):
729 with preserve_keys(self.shell.user_ns, '__file__'):
739 with preserve_keys(self.shell.user_ns, '__file__'):
730 self.shell.user_ns['__file__'] = filename
740 self.shell.user_ns['__file__'] = filename
731 self.shell.safe_execfile_ipy(filename, raise_exceptions=True)
741 self.shell.safe_execfile_ipy(filename, raise_exceptions=True)
732 return
742 return
733
743
734 # Control the response to exit() calls made by the script being run
744 # Control the response to exit() calls made by the script being run
735 exit_ignore = 'e' in opts
745 exit_ignore = 'e' in opts
736
746
737 # Make sure that the running script gets a proper sys.argv as if it
747 # Make sure that the running script gets a proper sys.argv as if it
738 # were run from a system shell.
748 # were run from a system shell.
739 save_argv = sys.argv # save it for later restoring
749 save_argv = sys.argv # save it for later restoring
740
750
741 if 'G' in opts:
751 if 'G' in opts:
742 args = arg_lst[1:]
752 args = arg_lst[1:]
743 else:
753 else:
744 # tilde and glob expansion
754 # tilde and glob expansion
745 args = shellglob(map(os.path.expanduser, arg_lst[1:]))
755 args = shellglob(map(os.path.expanduser, arg_lst[1:]))
746
756
747 sys.argv = [filename] + args # put in the proper filename
757 sys.argv = [filename] + args # put in the proper filename
748
758
749 if 'n' in opts:
759 if 'n' in opts:
750 name = Path(filename).stem
760 name = Path(filename).stem
751 else:
761 else:
752 name = '__main__'
762 name = '__main__'
753
763
754 if 'i' in opts:
764 if 'i' in opts:
755 # Run in user's interactive namespace
765 # Run in user's interactive namespace
756 prog_ns = self.shell.user_ns
766 prog_ns = self.shell.user_ns
757 __name__save = self.shell.user_ns['__name__']
767 __name__save = self.shell.user_ns['__name__']
758 prog_ns['__name__'] = name
768 prog_ns['__name__'] = name
759 main_mod = self.shell.user_module
769 main_mod = self.shell.user_module
760
770
761 # Since '%run foo' emulates 'python foo.py' at the cmd line, we must
771 # Since '%run foo' emulates 'python foo.py' at the cmd line, we must
762 # set the __file__ global in the script's namespace
772 # set the __file__ global in the script's namespace
763 # TK: Is this necessary in interactive mode?
773 # TK: Is this necessary in interactive mode?
764 prog_ns['__file__'] = filename
774 prog_ns['__file__'] = filename
765 else:
775 else:
766 # Run in a fresh, empty namespace
776 # Run in a fresh, empty namespace
767
777
768 # The shell MUST hold a reference to prog_ns so after %run
778 # The shell MUST hold a reference to prog_ns so after %run
769 # exits, the python deletion mechanism doesn't zero it out
779 # exits, the python deletion mechanism doesn't zero it out
770 # (leaving dangling references). See interactiveshell for details
780 # (leaving dangling references). See interactiveshell for details
771 main_mod = self.shell.new_main_mod(filename, name)
781 main_mod = self.shell.new_main_mod(filename, name)
772 prog_ns = main_mod.__dict__
782 prog_ns = main_mod.__dict__
773
783
774 # pickle fix. See interactiveshell for an explanation. But we need to
784 # pickle fix. See interactiveshell for an explanation. But we need to
775 # make sure that, if we overwrite __main__, we replace it at the end
785 # make sure that, if we overwrite __main__, we replace it at the end
776 main_mod_name = prog_ns['__name__']
786 main_mod_name = prog_ns['__name__']
777
787
778 if main_mod_name == '__main__':
788 if main_mod_name == '__main__':
779 restore_main = sys.modules['__main__']
789 restore_main = sys.modules['__main__']
780 else:
790 else:
781 restore_main = False
791 restore_main = False
782
792
783 # This needs to be undone at the end to prevent holding references to
793 # This needs to be undone at the end to prevent holding references to
784 # every single object ever created.
794 # every single object ever created.
785 sys.modules[main_mod_name] = main_mod
795 sys.modules[main_mod_name] = main_mod
786
796
787 if 'p' in opts or 'd' in opts:
797 if 'p' in opts or 'd' in opts:
788 if 'm' in opts:
798 if 'm' in opts:
789 code = 'run_module(modulename, prog_ns)'
799 code = 'run_module(modulename, prog_ns)'
790 code_ns = {
800 code_ns = {
791 'run_module': self.shell.safe_run_module,
801 'run_module': self.shell.safe_run_module,
792 'prog_ns': prog_ns,
802 'prog_ns': prog_ns,
793 'modulename': modulename,
803 'modulename': modulename,
794 }
804 }
795 else:
805 else:
796 if 'd' in opts:
806 if 'd' in opts:
797 # allow exceptions to raise in debug mode
807 # allow exceptions to raise in debug mode
798 code = 'execfile(filename, prog_ns, raise_exceptions=True)'
808 code = 'execfile(filename, prog_ns, raise_exceptions=True)'
799 else:
809 else:
800 code = 'execfile(filename, prog_ns)'
810 code = 'execfile(filename, prog_ns)'
801 code_ns = {
811 code_ns = {
802 'execfile': self.shell.safe_execfile,
812 'execfile': self.shell.safe_execfile,
803 'prog_ns': prog_ns,
813 'prog_ns': prog_ns,
804 'filename': get_py_filename(filename),
814 'filename': get_py_filename(filename),
805 }
815 }
806
816
807 try:
817 try:
808 stats = None
818 stats = None
809 if 'p' in opts:
819 if 'p' in opts:
810 stats = self._run_with_profiler(code, opts, code_ns)
820 stats = self._run_with_profiler(code, opts, code_ns)
811 else:
821 else:
812 if 'd' in opts:
822 if 'd' in opts:
813 bp_file, bp_line = parse_breakpoint(
823 bp_file, bp_line = parse_breakpoint(
814 opts.get('b', ['1'])[0], filename)
824 opts.get('b', ['1'])[0], filename)
815 self._run_with_debugger(
825 self._run_with_debugger(
816 code, code_ns, filename, bp_line, bp_file)
826 code, code_ns, filename, bp_line, bp_file)
817 else:
827 else:
818 if 'm' in opts:
828 if 'm' in opts:
819 def run():
829 def run():
820 self.shell.safe_run_module(modulename, prog_ns)
830 self.shell.safe_run_module(modulename, prog_ns)
821 else:
831 else:
822 if runner is None:
832 if runner is None:
823 runner = self.default_runner
833 runner = self.default_runner
824 if runner is None:
834 if runner is None:
825 runner = self.shell.safe_execfile
835 runner = self.shell.safe_execfile
826
836
827 def run():
837 def run():
828 runner(filename, prog_ns, prog_ns,
838 runner(filename, prog_ns, prog_ns,
829 exit_ignore=exit_ignore)
839 exit_ignore=exit_ignore)
830
840
831 if 't' in opts:
841 if 't' in opts:
832 # timed execution
842 # timed execution
833 try:
843 try:
834 nruns = int(opts['N'][0])
844 nruns = int(opts['N'][0])
835 if nruns < 1:
845 if nruns < 1:
836 error('Number of runs must be >=1')
846 error('Number of runs must be >=1')
837 return
847 return
838 except (KeyError):
848 except (KeyError):
839 nruns = 1
849 nruns = 1
840 self._run_with_timing(run, nruns)
850 self._run_with_timing(run, nruns)
841 else:
851 else:
842 # regular execution
852 # regular execution
843 run()
853 run()
844
854
845 if 'i' in opts:
855 if 'i' in opts:
846 self.shell.user_ns['__name__'] = __name__save
856 self.shell.user_ns['__name__'] = __name__save
847 else:
857 else:
848 # update IPython interactive namespace
858 # update IPython interactive namespace
849
859
850 # Some forms of read errors on the file may mean the
860 # Some forms of read errors on the file may mean the
851 # __name__ key was never set; using pop we don't have to
861 # __name__ key was never set; using pop we don't have to
852 # worry about a possible KeyError.
862 # worry about a possible KeyError.
853 prog_ns.pop('__name__', None)
863 prog_ns.pop('__name__', None)
854
864
855 with preserve_keys(self.shell.user_ns, '__file__'):
865 with preserve_keys(self.shell.user_ns, '__file__'):
856 self.shell.user_ns.update(prog_ns)
866 self.shell.user_ns.update(prog_ns)
857 finally:
867 finally:
858 # It's a bit of a mystery why, but __builtins__ can change from
868 # It's a bit of a mystery why, but __builtins__ can change from
859 # being a module to becoming a dict missing some key data after
869 # being a module to becoming a dict missing some key data after
860 # %run. As best I can see, this is NOT something IPython is doing
870 # %run. As best I can see, this is NOT something IPython is doing
861 # at all, and similar problems have been reported before:
871 # at all, and similar problems have been reported before:
862 # http://coding.derkeiler.com/Archive/Python/comp.lang.python/2004-10/0188.html
872 # http://coding.derkeiler.com/Archive/Python/comp.lang.python/2004-10/0188.html
863 # Since this seems to be done by the interpreter itself, the best
873 # Since this seems to be done by the interpreter itself, the best
864 # we can do is to at least restore __builtins__ for the user on
874 # we can do is to at least restore __builtins__ for the user on
865 # exit.
875 # exit.
866 self.shell.user_ns['__builtins__'] = builtin_mod
876 self.shell.user_ns['__builtins__'] = builtin_mod
867
877
868 # Ensure key global structures are restored
878 # Ensure key global structures are restored
869 sys.argv = save_argv
879 sys.argv = save_argv
870 if restore_main:
880 if restore_main:
871 sys.modules['__main__'] = restore_main
881 sys.modules['__main__'] = restore_main
872 if '__mp_main__' in sys.modules:
882 if '__mp_main__' in sys.modules:
873 sys.modules['__mp_main__'] = restore_main
883 sys.modules['__mp_main__'] = restore_main
874 else:
884 else:
875 # Remove from sys.modules the reference to main_mod we'd
885 # Remove from sys.modules the reference to main_mod we'd
876 # added. Otherwise it will trap references to objects
886 # added. Otherwise it will trap references to objects
877 # contained therein.
887 # contained therein.
878 del sys.modules[main_mod_name]
888 del sys.modules[main_mod_name]
879
889
880 return stats
890 return stats
881
891
882 def _run_with_debugger(
892 def _run_with_debugger(
883 self, code, code_ns, filename=None, bp_line=None, bp_file=None, local_ns=None
893 self, code, code_ns, filename=None, bp_line=None, bp_file=None, local_ns=None
884 ):
894 ):
885 """
895 """
886 Run `code` in debugger with a break point.
896 Run `code` in debugger with a break point.
887
897
888 Parameters
898 Parameters
889 ----------
899 ----------
890 code : str
900 code : str
891 Code to execute.
901 Code to execute.
892 code_ns : dict
902 code_ns : dict
893 A namespace in which `code` is executed.
903 A namespace in which `code` is executed.
894 filename : str
904 filename : str
895 `code` is ran as if it is in `filename`.
905 `code` is ran as if it is in `filename`.
896 bp_line : int, optional
906 bp_line : int, optional
897 Line number of the break point.
907 Line number of the break point.
898 bp_file : str, optional
908 bp_file : str, optional
899 Path to the file in which break point is specified.
909 Path to the file in which break point is specified.
900 `filename` is used if not given.
910 `filename` is used if not given.
901 local_ns : dict, optional
911 local_ns : dict, optional
902 A local namespace in which `code` is executed.
912 A local namespace in which `code` is executed.
903
913
904 Raises
914 Raises
905 ------
915 ------
906 UsageError
916 UsageError
907 If the break point given by `bp_line` is not valid.
917 If the break point given by `bp_line` is not valid.
908
918
909 """
919 """
910 deb = self.shell.InteractiveTB.pdb
920 deb = self.shell.InteractiveTB.pdb
911 if not deb:
921 if not deb:
912 self.shell.InteractiveTB.pdb = self.shell.InteractiveTB.debugger_cls()
922 self.shell.InteractiveTB.pdb = self.shell.InteractiveTB.debugger_cls()
913 deb = self.shell.InteractiveTB.pdb
923 deb = self.shell.InteractiveTB.pdb
914
924
915 # deb.checkline() fails if deb.curframe exists but is None; it can
925 # deb.checkline() fails if deb.curframe exists but is None; it can
916 # handle it not existing. https://github.com/ipython/ipython/issues/10028
926 # handle it not existing. https://github.com/ipython/ipython/issues/10028
917 if hasattr(deb, 'curframe'):
927 if hasattr(deb, 'curframe'):
918 del deb.curframe
928 del deb.curframe
919
929
920 # reset Breakpoint state, which is moronically kept
930 # reset Breakpoint state, which is moronically kept
921 # in a class
931 # in a class
922 bdb.Breakpoint.next = 1
932 bdb.Breakpoint.next = 1
923 bdb.Breakpoint.bplist = {}
933 bdb.Breakpoint.bplist = {}
924 bdb.Breakpoint.bpbynumber = [None]
934 bdb.Breakpoint.bpbynumber = [None]
925 deb.clear_all_breaks()
935 deb.clear_all_breaks()
926 if bp_line is not None:
936 if bp_line is not None:
927 # Set an initial breakpoint to stop execution
937 # Set an initial breakpoint to stop execution
928 maxtries = 10
938 maxtries = 10
929 bp_file = bp_file or filename
939 bp_file = bp_file or filename
930 checkline = deb.checkline(bp_file, bp_line)
940 checkline = deb.checkline(bp_file, bp_line)
931 if not checkline:
941 if not checkline:
932 for bp in range(bp_line + 1, bp_line + maxtries + 1):
942 for bp in range(bp_line + 1, bp_line + maxtries + 1):
933 if deb.checkline(bp_file, bp):
943 if deb.checkline(bp_file, bp):
934 break
944 break
935 else:
945 else:
936 msg = ("\nI failed to find a valid line to set "
946 msg = ("\nI failed to find a valid line to set "
937 "a breakpoint\n"
947 "a breakpoint\n"
938 "after trying up to line: %s.\n"
948 "after trying up to line: %s.\n"
939 "Please set a valid breakpoint manually "
949 "Please set a valid breakpoint manually "
940 "with the -b option." % bp)
950 "with the -b option." % bp)
941 raise UsageError(msg)
951 raise UsageError(msg)
942 # if we find a good linenumber, set the breakpoint
952 # if we find a good linenumber, set the breakpoint
943 deb.do_break('%s:%s' % (bp_file, bp_line))
953 deb.do_break('%s:%s' % (bp_file, bp_line))
944
954
945 if filename:
955 if filename:
946 # Mimic Pdb._runscript(...)
956 # Mimic Pdb._runscript(...)
947 deb._wait_for_mainpyfile = True
957 deb._wait_for_mainpyfile = True
948 deb.mainpyfile = deb.canonic(filename)
958 deb.mainpyfile = deb.canonic(filename)
949
959
950 # Start file run
960 # Start file run
951 print("NOTE: Enter 'c' at the %s prompt to continue execution." % deb.prompt)
961 print("NOTE: Enter 'c' at the %s prompt to continue execution." % deb.prompt)
952 try:
962 try:
953 if filename:
963 if filename:
954 # save filename so it can be used by methods on the deb object
964 # save filename so it can be used by methods on the deb object
955 deb._exec_filename = filename
965 deb._exec_filename = filename
956 while True:
966 while True:
957 try:
967 try:
958 trace = sys.gettrace()
968 trace = sys.gettrace()
959 deb.run(code, code_ns, local_ns)
969 deb.run(code, code_ns, local_ns)
960 except Restart:
970 except Restart:
961 print("Restarting")
971 print("Restarting")
962 if filename:
972 if filename:
963 deb._wait_for_mainpyfile = True
973 deb._wait_for_mainpyfile = True
964 deb.mainpyfile = deb.canonic(filename)
974 deb.mainpyfile = deb.canonic(filename)
965 continue
975 continue
966 else:
976 else:
967 break
977 break
968 finally:
978 finally:
969 sys.settrace(trace)
979 sys.settrace(trace)
970
980
971
981
972 except:
982 except:
973 etype, value, tb = sys.exc_info()
983 etype, value, tb = sys.exc_info()
974 # Skip three frames in the traceback: the %run one,
984 # Skip three frames in the traceback: the %run one,
975 # one inside bdb.py, and the command-line typed by the
985 # one inside bdb.py, and the command-line typed by the
976 # user (run by exec in pdb itself).
986 # user (run by exec in pdb itself).
977 self.shell.InteractiveTB(etype, value, tb, tb_offset=3)
987 self.shell.InteractiveTB(etype, value, tb, tb_offset=3)
978
988
979 @staticmethod
989 @staticmethod
980 def _run_with_timing(run, nruns):
990 def _run_with_timing(run, nruns):
981 """
991 """
982 Run function `run` and print timing information.
992 Run function `run` and print timing information.
983
993
984 Parameters
994 Parameters
985 ----------
995 ----------
986 run : callable
996 run : callable
987 Any callable object which takes no argument.
997 Any callable object which takes no argument.
988 nruns : int
998 nruns : int
989 Number of times to execute `run`.
999 Number of times to execute `run`.
990
1000
991 """
1001 """
992 twall0 = time.perf_counter()
1002 twall0 = time.perf_counter()
993 if nruns == 1:
1003 if nruns == 1:
994 t0 = clock2()
1004 t0 = clock2()
995 run()
1005 run()
996 t1 = clock2()
1006 t1 = clock2()
997 t_usr = t1[0] - t0[0]
1007 t_usr = t1[0] - t0[0]
998 t_sys = t1[1] - t0[1]
1008 t_sys = t1[1] - t0[1]
999 print("\nIPython CPU timings (estimated):")
1009 print("\nIPython CPU timings (estimated):")
1000 print(" User : %10.2f s." % t_usr)
1010 print(" User : %10.2f s." % t_usr)
1001 print(" System : %10.2f s." % t_sys)
1011 print(" System : %10.2f s." % t_sys)
1002 else:
1012 else:
1003 runs = range(nruns)
1013 runs = range(nruns)
1004 t0 = clock2()
1014 t0 = clock2()
1005 for nr in runs:
1015 for nr in runs:
1006 run()
1016 run()
1007 t1 = clock2()
1017 t1 = clock2()
1008 t_usr = t1[0] - t0[0]
1018 t_usr = t1[0] - t0[0]
1009 t_sys = t1[1] - t0[1]
1019 t_sys = t1[1] - t0[1]
1010 print("\nIPython CPU timings (estimated):")
1020 print("\nIPython CPU timings (estimated):")
1011 print("Total runs performed:", nruns)
1021 print("Total runs performed:", nruns)
1012 print(" Times : %10s %10s" % ('Total', 'Per run'))
1022 print(" Times : %10s %10s" % ('Total', 'Per run'))
1013 print(" User : %10.2f s, %10.2f s." % (t_usr, t_usr / nruns))
1023 print(" User : %10.2f s, %10.2f s." % (t_usr, t_usr / nruns))
1014 print(" System : %10.2f s, %10.2f s." % (t_sys, t_sys / nruns))
1024 print(" System : %10.2f s, %10.2f s." % (t_sys, t_sys / nruns))
1015 twall1 = time.perf_counter()
1025 twall1 = time.perf_counter()
1016 print("Wall time: %10.2f s." % (twall1 - twall0))
1026 print("Wall time: %10.2f s." % (twall1 - twall0))
1017
1027
1018 @skip_doctest
1028 @skip_doctest
1019 @no_var_expand
1029 @no_var_expand
1020 @line_cell_magic
1030 @line_cell_magic
1021 @needs_local_scope
1031 @needs_local_scope
1022 def timeit(self, line='', cell=None, local_ns=None):
1032 def timeit(self, line='', cell=None, local_ns=None):
1023 """Time execution of a Python statement or expression
1033 """Time execution of a Python statement or expression
1024
1034
1025 **Usage, in line mode:**
1035 **Usage, in line mode:**
1026
1036
1027 %timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] statement
1037 %timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] statement
1028
1038
1029 **or in cell mode:**
1039 **or in cell mode:**
1030
1040
1031 %%timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] setup_code
1041 %%timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] setup_code
1032
1042
1033 code
1043 code
1034
1044
1035 code...
1045 code...
1036
1046
1037 Time execution of a Python statement or expression using the timeit
1047 Time execution of a Python statement or expression using the timeit
1038 module. This function can be used both as a line and cell magic:
1048 module. This function can be used both as a line and cell magic:
1039
1049
1040 - In line mode you can time a single-line statement (though multiple
1050 - In line mode you can time a single-line statement (though multiple
1041 ones can be chained with using semicolons).
1051 ones can be chained with using semicolons).
1042
1052
1043 - In cell mode, the statement in the first line is used as setup code
1053 - In cell mode, the statement in the first line is used as setup code
1044 (executed but not timed) and the body of the cell is timed. The cell
1054 (executed but not timed) and the body of the cell is timed. The cell
1045 body has access to any variables created in the setup code.
1055 body has access to any variables created in the setup code.
1046
1056
1047 Options:
1057 Options:
1048
1058
1049 -n<N>: execute the given statement <N> times in a loop. If <N> is not
1059 -n<N>: execute the given statement <N> times in a loop. If <N> is not
1050 provided, <N> is determined so as to get sufficient accuracy.
1060 provided, <N> is determined so as to get sufficient accuracy.
1051
1061
1052 -r<R>: number of repeats <R>, each consisting of <N> loops, and take the
1062 -r<R>: number of repeats <R>, each consisting of <N> loops, and take the
1053 average result.
1063 average result.
1054 Default: 7
1064 Default: 7
1055
1065
1056 -t: use time.time to measure the time, which is the default on Unix.
1066 -t: use time.time to measure the time, which is the default on Unix.
1057 This function measures wall time.
1067 This function measures wall time.
1058
1068
1059 -c: use time.clock to measure the time, which is the default on
1069 -c: use time.clock to measure the time, which is the default on
1060 Windows and measures wall time. On Unix, resource.getrusage is used
1070 Windows and measures wall time. On Unix, resource.getrusage is used
1061 instead and returns the CPU user time.
1071 instead and returns the CPU user time.
1062
1072
1063 -p<P>: use a precision of <P> digits to display the timing result.
1073 -p<P>: use a precision of <P> digits to display the timing result.
1064 Default: 3
1074 Default: 3
1065
1075
1066 -q: Quiet, do not print result.
1076 -q: Quiet, do not print result.
1067
1077
1068 -o: return a TimeitResult that can be stored in a variable to inspect
1078 -o: return a TimeitResult that can be stored in a variable to inspect
1069 the result in more details.
1079 the result in more details.
1070
1080
1071 .. versionchanged:: 7.3
1081 .. versionchanged:: 7.3
1072 User variables are no longer expanded,
1082 User variables are no longer expanded,
1073 the magic line is always left unmodified.
1083 the magic line is always left unmodified.
1074
1084
1075 Examples
1085 Examples
1076 --------
1086 --------
1077 ::
1087 ::
1078
1088
1079 In [1]: %timeit pass
1089 In [1]: %timeit pass
1080 8.26 ns ± 0.12 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)
1090 8.26 ns ± 0.12 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)
1081
1091
1082 In [2]: u = None
1092 In [2]: u = None
1083
1093
1084 In [3]: %timeit u is None
1094 In [3]: %timeit u is None
1085 29.9 ns ± 0.643 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
1095 29.9 ns ± 0.643 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
1086
1096
1087 In [4]: %timeit -r 4 u == None
1097 In [4]: %timeit -r 4 u == None
1088
1098
1089 In [5]: import time
1099 In [5]: import time
1090
1100
1091 In [6]: %timeit -n1 time.sleep(2)
1101 In [6]: %timeit -n1 time.sleep(2)
1092
1102
1093 The times reported by %timeit will be slightly higher than those
1103 The times reported by %timeit will be slightly higher than those
1094 reported by the timeit.py script when variables are accessed. This is
1104 reported by the timeit.py script when variables are accessed. This is
1095 due to the fact that %timeit executes the statement in the namespace
1105 due to the fact that %timeit executes the statement in the namespace
1096 of the shell, compared with timeit.py, which uses a single setup
1106 of the shell, compared with timeit.py, which uses a single setup
1097 statement to import function or create variables. Generally, the bias
1107 statement to import function or create variables. Generally, the bias
1098 does not matter as long as results from timeit.py are not mixed with
1108 does not matter as long as results from timeit.py are not mixed with
1099 those from %timeit."""
1109 those from %timeit."""
1100
1110
1101 opts, stmt = self.parse_options(
1111 opts, stmt = self.parse_options(
1102 line, "n:r:tcp:qo", posix=False, strict=False, preserve_non_opts=True
1112 line, "n:r:tcp:qo", posix=False, strict=False, preserve_non_opts=True
1103 )
1113 )
1104 if stmt == "" and cell is None:
1114 if stmt == "" and cell is None:
1105 return
1115 return
1106
1116
1107 timefunc = timeit.default_timer
1117 timefunc = timeit.default_timer
1108 number = int(getattr(opts, "n", 0))
1118 number = int(getattr(opts, "n", 0))
1109 default_repeat = 7 if timeit.default_repeat < 7 else timeit.default_repeat
1119 default_repeat = 7 if timeit.default_repeat < 7 else timeit.default_repeat
1110 repeat = int(getattr(opts, "r", default_repeat))
1120 repeat = int(getattr(opts, "r", default_repeat))
1111 precision = int(getattr(opts, "p", 3))
1121 precision = int(getattr(opts, "p", 3))
1112 quiet = 'q' in opts
1122 quiet = 'q' in opts
1113 return_result = 'o' in opts
1123 return_result = 'o' in opts
1114 if hasattr(opts, "t"):
1124 if hasattr(opts, "t"):
1115 timefunc = time.time
1125 timefunc = time.time
1116 if hasattr(opts, "c"):
1126 if hasattr(opts, "c"):
1117 timefunc = clock
1127 timefunc = clock
1118
1128
1119 timer = Timer(timer=timefunc)
1129 timer = Timer(timer=timefunc)
1120 # this code has tight coupling to the inner workings of timeit.Timer,
1130 # this code has tight coupling to the inner workings of timeit.Timer,
1121 # but is there a better way to achieve that the code stmt has access
1131 # but is there a better way to achieve that the code stmt has access
1122 # to the shell namespace?
1132 # to the shell namespace?
1123 transform = self.shell.transform_cell
1133 transform = self.shell.transform_cell
1124
1134
1125 if cell is None:
1135 if cell is None:
1126 # called as line magic
1136 # called as line magic
1127 ast_setup = self.shell.compile.ast_parse("pass")
1137 ast_setup = self.shell.compile.ast_parse("pass")
1128 ast_stmt = self.shell.compile.ast_parse(transform(stmt))
1138 ast_stmt = self.shell.compile.ast_parse(transform(stmt))
1129 else:
1139 else:
1130 ast_setup = self.shell.compile.ast_parse(transform(stmt))
1140 ast_setup = self.shell.compile.ast_parse(transform(stmt))
1131 ast_stmt = self.shell.compile.ast_parse(transform(cell))
1141 ast_stmt = self.shell.compile.ast_parse(transform(cell))
1132
1142
1133 ast_setup = self.shell.transform_ast(ast_setup)
1143 ast_setup = self.shell.transform_ast(ast_setup)
1134 ast_stmt = self.shell.transform_ast(ast_stmt)
1144 ast_stmt = self.shell.transform_ast(ast_stmt)
1135
1145
1136 # Check that these compile to valid Python code *outside* the timer func
1146 # Check that these compile to valid Python code *outside* the timer func
1137 # Invalid code may become valid when put inside the function & loop,
1147 # Invalid code may become valid when put inside the function & loop,
1138 # which messes up error messages.
1148 # which messes up error messages.
1139 # https://github.com/ipython/ipython/issues/10636
1149 # https://github.com/ipython/ipython/issues/10636
1140 self.shell.compile(ast_setup, "<magic-timeit-setup>", "exec")
1150 self.shell.compile(ast_setup, "<magic-timeit-setup>", "exec")
1141 self.shell.compile(ast_stmt, "<magic-timeit-stmt>", "exec")
1151 self.shell.compile(ast_stmt, "<magic-timeit-stmt>", "exec")
1142
1152
1143 # This codestring is taken from timeit.template - we fill it in as an
1153 # This codestring is taken from timeit.template - we fill it in as an
1144 # AST, so that we can apply our AST transformations to the user code
1154 # AST, so that we can apply our AST transformations to the user code
1145 # without affecting the timing code.
1155 # without affecting the timing code.
1146 timeit_ast_template = ast.parse('def inner(_it, _timer):\n'
1156 timeit_ast_template = ast.parse('def inner(_it, _timer):\n'
1147 ' setup\n'
1157 ' setup\n'
1148 ' _t0 = _timer()\n'
1158 ' _t0 = _timer()\n'
1149 ' for _i in _it:\n'
1159 ' for _i in _it:\n'
1150 ' stmt\n'
1160 ' stmt\n'
1151 ' _t1 = _timer()\n'
1161 ' _t1 = _timer()\n'
1152 ' return _t1 - _t0\n')
1162 ' return _t1 - _t0\n')
1153
1163
1154 timeit_ast = TimeitTemplateFiller(ast_setup, ast_stmt).visit(timeit_ast_template)
1164 timeit_ast = TimeitTemplateFiller(ast_setup, ast_stmt).visit(timeit_ast_template)
1155 timeit_ast = ast.fix_missing_locations(timeit_ast)
1165 timeit_ast = ast.fix_missing_locations(timeit_ast)
1156
1166
1157 # Track compilation time so it can be reported if too long
1167 # Track compilation time so it can be reported if too long
1158 # Minimum time above which compilation time will be reported
1168 # Minimum time above which compilation time will be reported
1159 tc_min = 0.1
1169 tc_min = 0.1
1160
1170
1161 t0 = clock()
1171 t0 = clock()
1162 code = self.shell.compile(timeit_ast, "<magic-timeit>", "exec")
1172 code = self.shell.compile(timeit_ast, "<magic-timeit>", "exec")
1163 tc = clock()-t0
1173 tc = clock()-t0
1164
1174
1165 ns = {}
1175 ns = {}
1166 glob = self.shell.user_ns
1176 glob = self.shell.user_ns
1167 # handles global vars with same name as local vars. We store them in conflict_globs.
1177 # handles global vars with same name as local vars. We store them in conflict_globs.
1168 conflict_globs = {}
1178 conflict_globs = {}
1169 if local_ns and cell is None:
1179 if local_ns and cell is None:
1170 for var_name, var_val in glob.items():
1180 for var_name, var_val in glob.items():
1171 if var_name in local_ns:
1181 if var_name in local_ns:
1172 conflict_globs[var_name] = var_val
1182 conflict_globs[var_name] = var_val
1173 glob.update(local_ns)
1183 glob.update(local_ns)
1174
1184
1175 exec(code, glob, ns)
1185 exec(code, glob, ns)
1176 timer.inner = ns["inner"]
1186 timer.inner = ns["inner"]
1177
1187
1178 # This is used to check if there is a huge difference between the
1188 # This is used to check if there is a huge difference between the
1179 # best and worst timings.
1189 # best and worst timings.
1180 # Issue: https://github.com/ipython/ipython/issues/6471
1190 # Issue: https://github.com/ipython/ipython/issues/6471
1181 if number == 0:
1191 if number == 0:
1182 # determine number so that 0.2 <= total time < 2.0
1192 # determine number so that 0.2 <= total time < 2.0
1183 for index in range(0, 10):
1193 for index in range(0, 10):
1184 number = 10 ** index
1194 number = 10 ** index
1185 time_number = timer.timeit(number)
1195 time_number = timer.timeit(number)
1186 if time_number >= 0.2:
1196 if time_number >= 0.2:
1187 break
1197 break
1188
1198
1189 all_runs = timer.repeat(repeat, number)
1199 all_runs = timer.repeat(repeat, number)
1190 best = min(all_runs) / number
1200 best = min(all_runs) / number
1191 worst = max(all_runs) / number
1201 worst = max(all_runs) / number
1192 timeit_result = TimeitResult(number, repeat, best, worst, all_runs, tc, precision)
1202 timeit_result = TimeitResult(number, repeat, best, worst, all_runs, tc, precision)
1193
1203
1194 # Restore global vars from conflict_globs
1204 # Restore global vars from conflict_globs
1195 if conflict_globs:
1205 if conflict_globs:
1196 glob.update(conflict_globs)
1206 glob.update(conflict_globs)
1197
1207
1198 if not quiet :
1208 if not quiet :
1199 # Check best timing is greater than zero to avoid a
1209 # Check best timing is greater than zero to avoid a
1200 # ZeroDivisionError.
1210 # ZeroDivisionError.
1201 # In cases where the slowest timing is lesser than a microsecond
1211 # In cases where the slowest timing is lesser than a microsecond
1202 # we assume that it does not really matter if the fastest
1212 # we assume that it does not really matter if the fastest
1203 # timing is 4 times faster than the slowest timing or not.
1213 # timing is 4 times faster than the slowest timing or not.
1204 if worst > 4 * best and best > 0 and worst > 1e-6:
1214 if worst > 4 * best and best > 0 and worst > 1e-6:
1205 print("The slowest run took %0.2f times longer than the "
1215 print("The slowest run took %0.2f times longer than the "
1206 "fastest. This could mean that an intermediate result "
1216 "fastest. This could mean that an intermediate result "
1207 "is being cached." % (worst / best))
1217 "is being cached." % (worst / best))
1208
1218
1209 print( timeit_result )
1219 print( timeit_result )
1210
1220
1211 if tc > tc_min:
1221 if tc > tc_min:
1212 print("Compiler time: %.2f s" % tc)
1222 print("Compiler time: %.2f s" % tc)
1213 if return_result:
1223 if return_result:
1214 return timeit_result
1224 return timeit_result
1215
1225
1216 @skip_doctest
1226 @skip_doctest
1217 @no_var_expand
1227 @no_var_expand
1218 @needs_local_scope
1228 @needs_local_scope
1219 @line_cell_magic
1229 @line_cell_magic
1220 @output_can_be_silenced
1230 @output_can_be_silenced
1221 def time(self,line='', cell=None, local_ns=None):
1231 def time(self,line='', cell=None, local_ns=None):
1222 """Time execution of a Python statement or expression.
1232 """Time execution of a Python statement or expression.
1223
1233
1224 The CPU and wall clock times are printed, and the value of the
1234 The CPU and wall clock times are printed, and the value of the
1225 expression (if any) is returned. Note that under Win32, system time
1235 expression (if any) is returned. Note that under Win32, system time
1226 is always reported as 0, since it can not be measured.
1236 is always reported as 0, since it can not be measured.
1227
1237
1228 This function can be used both as a line and cell magic:
1238 This function can be used both as a line and cell magic:
1229
1239
1230 - In line mode you can time a single-line statement (though multiple
1240 - In line mode you can time a single-line statement (though multiple
1231 ones can be chained with using semicolons).
1241 ones can be chained with using semicolons).
1232
1242
1233 - In cell mode, you can time the cell body (a directly
1243 - In cell mode, you can time the cell body (a directly
1234 following statement raises an error).
1244 following statement raises an error).
1235
1245
1236 This function provides very basic timing functionality. Use the timeit
1246 This function provides very basic timing functionality. Use the timeit
1237 magic for more control over the measurement.
1247 magic for more control over the measurement.
1238
1248
1239 .. versionchanged:: 7.3
1249 .. versionchanged:: 7.3
1240 User variables are no longer expanded,
1250 User variables are no longer expanded,
1241 the magic line is always left unmodified.
1251 the magic line is always left unmodified.
1242
1252
1243 Examples
1253 Examples
1244 --------
1254 --------
1245 ::
1255 ::
1246
1256
1247 In [1]: %time 2**128
1257 In [1]: %time 2**128
1248 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1258 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1249 Wall time: 0.00
1259 Wall time: 0.00
1250 Out[1]: 340282366920938463463374607431768211456L
1260 Out[1]: 340282366920938463463374607431768211456L
1251
1261
1252 In [2]: n = 1000000
1262 In [2]: n = 1000000
1253
1263
1254 In [3]: %time sum(range(n))
1264 In [3]: %time sum(range(n))
1255 CPU times: user 1.20 s, sys: 0.05 s, total: 1.25 s
1265 CPU times: user 1.20 s, sys: 0.05 s, total: 1.25 s
1256 Wall time: 1.37
1266 Wall time: 1.37
1257 Out[3]: 499999500000L
1267 Out[3]: 499999500000L
1258
1268
1259 In [4]: %time print('hello world')
1269 In [4]: %time print('hello world')
1260 hello world
1270 hello world
1261 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1271 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1262 Wall time: 0.00
1272 Wall time: 0.00
1263
1273
1264 .. note::
1274 .. note::
1265 The time needed by Python to compile the given expression will be
1275 The time needed by Python to compile the given expression will be
1266 reported if it is more than 0.1s.
1276 reported if it is more than 0.1s.
1267
1277
1268 In the example below, the actual exponentiation is done by Python
1278 In the example below, the actual exponentiation is done by Python
1269 at compilation time, so while the expression can take a noticeable
1279 at compilation time, so while the expression can take a noticeable
1270 amount of time to compute, that time is purely due to the
1280 amount of time to compute, that time is purely due to the
1271 compilation::
1281 compilation::
1272
1282
1273 In [5]: %time 3**9999;
1283 In [5]: %time 3**9999;
1274 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1284 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1275 Wall time: 0.00 s
1285 Wall time: 0.00 s
1276
1286
1277 In [6]: %time 3**999999;
1287 In [6]: %time 3**999999;
1278 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1288 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1279 Wall time: 0.00 s
1289 Wall time: 0.00 s
1280 Compiler : 0.78 s
1290 Compiler : 0.78 s
1281 """
1291 """
1282 # fail immediately if the given expression can't be compiled
1292 # fail immediately if the given expression can't be compiled
1283
1293
1284 if line and cell:
1294 if line and cell:
1285 raise UsageError("Can't use statement directly after '%%time'!")
1295 raise UsageError("Can't use statement directly after '%%time'!")
1286
1296
1287 if cell:
1297 if cell:
1288 expr = self.shell.transform_cell(cell)
1298 expr = self.shell.transform_cell(cell)
1289 else:
1299 else:
1290 expr = self.shell.transform_cell(line)
1300 expr = self.shell.transform_cell(line)
1291
1301
1292 # Minimum time above which parse time will be reported
1302 # Minimum time above which parse time will be reported
1293 tp_min = 0.1
1303 tp_min = 0.1
1294
1304
1295 t0 = clock()
1305 t0 = clock()
1296 expr_ast = self.shell.compile.ast_parse(expr)
1306 expr_ast = self.shell.compile.ast_parse(expr)
1297 tp = clock()-t0
1307 tp = clock()-t0
1298
1308
1299 # Apply AST transformations
1309 # Apply AST transformations
1300 expr_ast = self.shell.transform_ast(expr_ast)
1310 expr_ast = self.shell.transform_ast(expr_ast)
1301
1311
1302 # Minimum time above which compilation time will be reported
1312 # Minimum time above which compilation time will be reported
1303 tc_min = 0.1
1313 tc_min = 0.1
1304
1314
1305 expr_val=None
1315 expr_val=None
1306 if len(expr_ast.body)==1 and isinstance(expr_ast.body[0], ast.Expr):
1316 if len(expr_ast.body)==1 and isinstance(expr_ast.body[0], ast.Expr):
1307 mode = 'eval'
1317 mode = 'eval'
1308 source = '<timed eval>'
1318 source = '<timed eval>'
1309 expr_ast = ast.Expression(expr_ast.body[0].value)
1319 expr_ast = ast.Expression(expr_ast.body[0].value)
1310 else:
1320 else:
1311 mode = 'exec'
1321 mode = 'exec'
1312 source = '<timed exec>'
1322 source = '<timed exec>'
1313 # multi-line %%time case
1323 # multi-line %%time case
1314 if len(expr_ast.body) > 1 and isinstance(expr_ast.body[-1], ast.Expr):
1324 if len(expr_ast.body) > 1 and isinstance(expr_ast.body[-1], ast.Expr):
1315 expr_val= expr_ast.body[-1]
1325 expr_val= expr_ast.body[-1]
1316 expr_ast = expr_ast.body[:-1]
1326 expr_ast = expr_ast.body[:-1]
1317 expr_ast = Module(expr_ast, [])
1327 expr_ast = Module(expr_ast, [])
1318 expr_val = ast.Expression(expr_val.value)
1328 expr_val = ast.Expression(expr_val.value)
1319
1329
1320 t0 = clock()
1330 t0 = clock()
1321 code = self.shell.compile(expr_ast, source, mode)
1331 code = self.shell.compile(expr_ast, source, mode)
1322 tc = clock()-t0
1332 tc = clock()-t0
1323
1333
1324 # skew measurement as little as possible
1334 # skew measurement as little as possible
1325 glob = self.shell.user_ns
1335 glob = self.shell.user_ns
1326 wtime = time.time
1336 wtime = time.time
1327 # time execution
1337 # time execution
1328 wall_st = wtime()
1338 wall_st = wtime()
1329 if mode=='eval':
1339 if mode=='eval':
1330 st = clock2()
1340 st = clock2()
1331 try:
1341 try:
1332 out = eval(code, glob, local_ns)
1342 out = eval(code, glob, local_ns)
1333 except:
1343 except:
1334 self.shell.showtraceback()
1344 self.shell.showtraceback()
1335 return
1345 return
1336 end = clock2()
1346 end = clock2()
1337 else:
1347 else:
1338 st = clock2()
1348 st = clock2()
1339 try:
1349 try:
1340 exec(code, glob, local_ns)
1350 exec(code, glob, local_ns)
1341 out=None
1351 out=None
1342 # multi-line %%time case
1352 # multi-line %%time case
1343 if expr_val is not None:
1353 if expr_val is not None:
1344 code_2 = self.shell.compile(expr_val, source, 'eval')
1354 code_2 = self.shell.compile(expr_val, source, 'eval')
1345 out = eval(code_2, glob, local_ns)
1355 out = eval(code_2, glob, local_ns)
1346 except:
1356 except:
1347 self.shell.showtraceback()
1357 self.shell.showtraceback()
1348 return
1358 return
1349 end = clock2()
1359 end = clock2()
1350
1360
1351 wall_end = wtime()
1361 wall_end = wtime()
1352 # Compute actual times and report
1362 # Compute actual times and report
1353 wall_time = wall_end - wall_st
1363 wall_time = wall_end - wall_st
1354 cpu_user = end[0] - st[0]
1364 cpu_user = end[0] - st[0]
1355 cpu_sys = end[1] - st[1]
1365 cpu_sys = end[1] - st[1]
1356 cpu_tot = cpu_user + cpu_sys
1366 cpu_tot = cpu_user + cpu_sys
1357 # On windows cpu_sys is always zero, so only total is displayed
1367 # On windows cpu_sys is always zero, so only total is displayed
1358 if sys.platform != "win32":
1368 if sys.platform != "win32":
1359 print(
1369 print(
1360 f"CPU times: user {_format_time(cpu_user)}, sys: {_format_time(cpu_sys)}, total: {_format_time(cpu_tot)}"
1370 f"CPU times: user {_format_time(cpu_user)}, sys: {_format_time(cpu_sys)}, total: {_format_time(cpu_tot)}"
1361 )
1371 )
1362 else:
1372 else:
1363 print(f"CPU times: total: {_format_time(cpu_tot)}")
1373 print(f"CPU times: total: {_format_time(cpu_tot)}")
1364 print(f"Wall time: {_format_time(wall_time)}")
1374 print(f"Wall time: {_format_time(wall_time)}")
1365 if tc > tc_min:
1375 if tc > tc_min:
1366 print(f"Compiler : {_format_time(tc)}")
1376 print(f"Compiler : {_format_time(tc)}")
1367 if tp > tp_min:
1377 if tp > tp_min:
1368 print(f"Parser : {_format_time(tp)}")
1378 print(f"Parser : {_format_time(tp)}")
1369 return out
1379 return out
1370
1380
1371 @skip_doctest
1381 @skip_doctest
1372 @line_magic
1382 @line_magic
1373 def macro(self, parameter_s=''):
1383 def macro(self, parameter_s=''):
1374 """Define a macro for future re-execution. It accepts ranges of history,
1384 """Define a macro for future re-execution. It accepts ranges of history,
1375 filenames or string objects.
1385 filenames or string objects.
1376
1386
1377 Usage:\\
1387 Usage:\\
1378 %macro [options] name n1-n2 n3-n4 ... n5 .. n6 ...
1388 %macro [options] name n1-n2 n3-n4 ... n5 .. n6 ...
1379
1389
1380 Options:
1390 Options:
1381
1391
1382 -r: use 'raw' input. By default, the 'processed' history is used,
1392 -r: use 'raw' input. By default, the 'processed' history is used,
1383 so that magics are loaded in their transformed version to valid
1393 so that magics are loaded in their transformed version to valid
1384 Python. If this option is given, the raw input as typed at the
1394 Python. If this option is given, the raw input as typed at the
1385 command line is used instead.
1395 command line is used instead.
1386
1396
1387 -q: quiet macro definition. By default, a tag line is printed
1397 -q: quiet macro definition. By default, a tag line is printed
1388 to indicate the macro has been created, and then the contents of
1398 to indicate the macro has been created, and then the contents of
1389 the macro are printed. If this option is given, then no printout
1399 the macro are printed. If this option is given, then no printout
1390 is produced once the macro is created.
1400 is produced once the macro is created.
1391
1401
1392 This will define a global variable called `name` which is a string
1402 This will define a global variable called `name` which is a string
1393 made of joining the slices and lines you specify (n1,n2,... numbers
1403 made of joining the slices and lines you specify (n1,n2,... numbers
1394 above) from your input history into a single string. This variable
1404 above) from your input history into a single string. This variable
1395 acts like an automatic function which re-executes those lines as if
1405 acts like an automatic function which re-executes those lines as if
1396 you had typed them. You just type 'name' at the prompt and the code
1406 you had typed them. You just type 'name' at the prompt and the code
1397 executes.
1407 executes.
1398
1408
1399 The syntax for indicating input ranges is described in %history.
1409 The syntax for indicating input ranges is described in %history.
1400
1410
1401 Note: as a 'hidden' feature, you can also use traditional python slice
1411 Note: as a 'hidden' feature, you can also use traditional python slice
1402 notation, where N:M means numbers N through M-1.
1412 notation, where N:M means numbers N through M-1.
1403
1413
1404 For example, if your history contains (print using %hist -n )::
1414 For example, if your history contains (print using %hist -n )::
1405
1415
1406 44: x=1
1416 44: x=1
1407 45: y=3
1417 45: y=3
1408 46: z=x+y
1418 46: z=x+y
1409 47: print(x)
1419 47: print(x)
1410 48: a=5
1420 48: a=5
1411 49: print('x',x,'y',y)
1421 49: print('x',x,'y',y)
1412
1422
1413 you can create a macro with lines 44 through 47 (included) and line 49
1423 you can create a macro with lines 44 through 47 (included) and line 49
1414 called my_macro with::
1424 called my_macro with::
1415
1425
1416 In [55]: %macro my_macro 44-47 49
1426 In [55]: %macro my_macro 44-47 49
1417
1427
1418 Now, typing `my_macro` (without quotes) will re-execute all this code
1428 Now, typing `my_macro` (without quotes) will re-execute all this code
1419 in one pass.
1429 in one pass.
1420
1430
1421 You don't need to give the line-numbers in order, and any given line
1431 You don't need to give the line-numbers in order, and any given line
1422 number can appear multiple times. You can assemble macros with any
1432 number can appear multiple times. You can assemble macros with any
1423 lines from your input history in any order.
1433 lines from your input history in any order.
1424
1434
1425 The macro is a simple object which holds its value in an attribute,
1435 The macro is a simple object which holds its value in an attribute,
1426 but IPython's display system checks for macros and executes them as
1436 but IPython's display system checks for macros and executes them as
1427 code instead of printing them when you type their name.
1437 code instead of printing them when you type their name.
1428
1438
1429 You can view a macro's contents by explicitly printing it with::
1439 You can view a macro's contents by explicitly printing it with::
1430
1440
1431 print(macro_name)
1441 print(macro_name)
1432
1442
1433 """
1443 """
1434 opts,args = self.parse_options(parameter_s,'rq',mode='list')
1444 opts,args = self.parse_options(parameter_s,'rq',mode='list')
1435 if not args: # List existing macros
1445 if not args: # List existing macros
1436 return sorted(k for k,v in self.shell.user_ns.items() if isinstance(v, Macro))
1446 return sorted(k for k,v in self.shell.user_ns.items() if isinstance(v, Macro))
1437 if len(args) == 1:
1447 if len(args) == 1:
1438 raise UsageError(
1448 raise UsageError(
1439 "%macro insufficient args; usage '%macro name n1-n2 n3-4...")
1449 "%macro insufficient args; usage '%macro name n1-n2 n3-4...")
1440 name, codefrom = args[0], " ".join(args[1:])
1450 name, codefrom = args[0], " ".join(args[1:])
1441
1451
1442 # print('rng',ranges) # dbg
1452 # print('rng',ranges) # dbg
1443 try:
1453 try:
1444 lines = self.shell.find_user_code(codefrom, 'r' in opts)
1454 lines = self.shell.find_user_code(codefrom, 'r' in opts)
1445 except (ValueError, TypeError) as e:
1455 except (ValueError, TypeError) as e:
1446 print(e.args[0])
1456 print(e.args[0])
1447 return
1457 return
1448 macro = Macro(lines)
1458 macro = Macro(lines)
1449 self.shell.define_macro(name, macro)
1459 self.shell.define_macro(name, macro)
1450 if "q" not in opts:
1460 if not ( 'q' in opts) :
1451 print(
1461 print('Macro `%s` created. To execute, type its name (without quotes).' % name)
1452 "Macro `%s` created. To execute, type its name (without quotes)." % name
1462 print('=== Macro contents: ===')
1453 )
1463 print(macro, end=' ')
1454 print("=== Macro contents: ===")
1455 print(macro, end=" ")
1456
1464
1457 @magic_arguments.magic_arguments()
1465 @magic_arguments.magic_arguments()
1458 @magic_arguments.argument('output', type=str, default='', nargs='?',
1466 @magic_arguments.argument('output', type=str, default='', nargs='?',
1459 help="""The name of the variable in which to store output.
1467 help="""The name of the variable in which to store output.
1460 This is a utils.io.CapturedIO object with stdout/err attributes
1468 This is a utils.io.CapturedIO object with stdout/err attributes
1461 for the text of the captured output.
1469 for the text of the captured output.
1462
1470
1463 CapturedOutput also has a show() method for displaying the output,
1471 CapturedOutput also has a show() method for displaying the output,
1464 and __call__ as well, so you can use that to quickly display the
1472 and __call__ as well, so you can use that to quickly display the
1465 output.
1473 output.
1466
1474
1467 If unspecified, captured output is discarded.
1475 If unspecified, captured output is discarded.
1468 """
1476 """
1469 )
1477 )
1470 @magic_arguments.argument('--no-stderr', action="store_true",
1478 @magic_arguments.argument('--no-stderr', action="store_true",
1471 help="""Don't capture stderr."""
1479 help="""Don't capture stderr."""
1472 )
1480 )
1473 @magic_arguments.argument('--no-stdout', action="store_true",
1481 @magic_arguments.argument('--no-stdout', action="store_true",
1474 help="""Don't capture stdout."""
1482 help="""Don't capture stdout."""
1475 )
1483 )
1476 @magic_arguments.argument('--no-display', action="store_true",
1484 @magic_arguments.argument('--no-display', action="store_true",
1477 help="""Don't capture IPython's rich display."""
1485 help="""Don't capture IPython's rich display."""
1478 )
1486 )
1479 @cell_magic
1487 @cell_magic
1480 def capture(self, line, cell):
1488 def capture(self, line, cell):
1481 """run the cell, capturing stdout, stderr, and IPython's rich display() calls."""
1489 """run the cell, capturing stdout, stderr, and IPython's rich display() calls."""
1482 args = magic_arguments.parse_argstring(self.capture, line)
1490 args = magic_arguments.parse_argstring(self.capture, line)
1483 out = not args.no_stdout
1491 out = not args.no_stdout
1484 err = not args.no_stderr
1492 err = not args.no_stderr
1485 disp = not args.no_display
1493 disp = not args.no_display
1486 with capture_output(out, err, disp) as io:
1494 with capture_output(out, err, disp) as io:
1487 self.shell.run_cell(cell)
1495 self.shell.run_cell(cell)
1488 if DisplayHook.semicolon_at_end_of_expression(cell):
1496 if DisplayHook.semicolon_at_end_of_expression(cell):
1489 if args.output in self.shell.user_ns:
1497 if args.output in self.shell.user_ns:
1490 del self.shell.user_ns[args.output]
1498 del self.shell.user_ns[args.output]
1491 elif args.output:
1499 elif args.output:
1492 self.shell.user_ns[args.output] = io
1500 self.shell.user_ns[args.output] = io
1493
1501
1494 @skip_doctest
1502 @skip_doctest
1495 @magic_arguments.magic_arguments()
1503 @magic_arguments.magic_arguments()
1496 @magic_arguments.argument("name", type=str, default="default", nargs="?")
1504 @magic_arguments.argument("name", type=str, default="default", nargs="?")
1497 @magic_arguments.argument(
1505 @magic_arguments.argument(
1498 "--remove", action="store_true", help="remove the current transformer"
1506 "--remove", action="store_true", help="remove the current transformer"
1499 )
1507 )
1500 @magic_arguments.argument(
1508 @magic_arguments.argument(
1501 "--list", action="store_true", help="list existing transformers name"
1509 "--list", action="store_true", help="list existing transformers name"
1502 )
1510 )
1503 @magic_arguments.argument(
1511 @magic_arguments.argument(
1504 "--list-all",
1512 "--list-all",
1505 action="store_true",
1513 action="store_true",
1506 help="list existing transformers name and code template",
1514 help="list existing transformers name and code template",
1507 )
1515 )
1508 @line_cell_magic
1516 @line_cell_magic
1509 def code_wrap(self, line, cell=None):
1517 def code_wrap(self, line, cell=None):
1510 """
1518 """
1511 Simple magic to quickly define a code transformer for all IPython's future input.
1519 Simple magic to quickly define a code transformer for all IPython's future input.
1512
1520
1513 ``__code__`` and ``__ret__`` are special variable that represent the code to run
1521 ``__code__`` and ``__ret__`` are special variable that represent the code to run
1514 and the value of the last expression of ``__code__`` respectively.
1522 and the value of the last expression of ``__code__`` respectively.
1515
1523
1516 Examples
1524 Examples
1517 --------
1525 --------
1518
1526
1519 .. ipython::
1527 .. ipython::
1520
1528
1521 In [1]: %%code_wrap before_after
1529 In [1]: %%code_wrap before_after
1522 ...: print('before')
1530 ...: print('before')
1523 ...: __code__
1531 ...: __code__
1524 ...: print('after')
1532 ...: print('after')
1525 ...: __ret__
1533 ...: __ret__
1526
1534
1527
1535
1528 In [2]: 1
1536 In [2]: 1
1529 before
1537 before
1530 after
1538 after
1531 Out[2]: 1
1539 Out[2]: 1
1532
1540
1533 In [3]: %code_wrap --list
1541 In [3]: %code_wrap --list
1534 before_after
1542 before_after
1535
1543
1536 In [4]: %code_wrap --list-all
1544 In [4]: %code_wrap --list-all
1537 before_after :
1545 before_after :
1538 print('before')
1546 print('before')
1539 __code__
1547 __code__
1540 print('after')
1548 print('after')
1541 __ret__
1549 __ret__
1542
1550
1543 In [5]: %code_wrap --remove before_after
1551 In [5]: %code_wrap --remove before_after
1544
1552
1545 """
1553 """
1546 args = magic_arguments.parse_argstring(self.code_wrap, line)
1554 args = magic_arguments.parse_argstring(self.code_wrap, line)
1547
1555
1548 if args.list:
1556 if args.list:
1549 for name in self._transformers.keys():
1557 for name in self._transformers.keys():
1550 print(name)
1558 print(name)
1551 return
1559 return
1552 if args.list_all:
1560 if args.list_all:
1553 for name, _t in self._transformers.items():
1561 for name, _t in self._transformers.items():
1554 print(name, ":")
1562 print(name, ":")
1555 print(indent(ast.unparse(_t.template), " "))
1563 print(indent(ast.unparse(_t.template), " "))
1556 print()
1564 print()
1557 return
1565 return
1558
1566
1559 to_remove = self._transformers.pop(args.name, None)
1567 to_remove = self._transformers.pop(args.name, None)
1560 if to_remove in self.shell.ast_transformers:
1568 if to_remove in self.shell.ast_transformers:
1561 self.shell.ast_transformers.remove(to_remove)
1569 self.shell.ast_transformers.remove(to_remove)
1562 if cell is None or args.remove:
1570 if cell is None or args.remove:
1563 return
1571 return
1564
1572
1565 _trs = ReplaceCodeTransformer(ast.parse(cell))
1573 _trs = ReplaceCodeTransformer(ast.parse(cell))
1566
1574
1567 self._transformers[args.name] = _trs
1575 self._transformers[args.name] = _trs
1568 self.shell.ast_transformers.append(_trs)
1576 self.shell.ast_transformers.append(_trs)
1569
1577
1570
1578
1571 def parse_breakpoint(text, current_file):
1579 def parse_breakpoint(text, current_file):
1572 '''Returns (file, line) for file:line and (current_file, line) for line'''
1580 '''Returns (file, line) for file:line and (current_file, line) for line'''
1573 colon = text.find(':')
1581 colon = text.find(':')
1574 if colon == -1:
1582 if colon == -1:
1575 return current_file, int(text)
1583 return current_file, int(text)
1576 else:
1584 else:
1577 return text[:colon], int(text[colon+1:])
1585 return text[:colon], int(text[colon+1:])
1578
1586
1579 def _format_time(timespan, precision=3):
1587 def _format_time(timespan, precision=3):
1580 """Formats the timespan in a human readable form"""
1588 """Formats the timespan in a human readable form"""
1581
1589
1582 if timespan >= 60.0:
1590 if timespan >= 60.0:
1583 # we have more than a minute, format that in a human readable form
1591 # we have more than a minute, format that in a human readable form
1584 # Idea from http://snipplr.com/view/5713/
1592 # Idea from http://snipplr.com/view/5713/
1585 parts = [("d", 60*60*24),("h", 60*60),("min", 60), ("s", 1)]
1593 parts = [("d", 60*60*24),("h", 60*60),("min", 60), ("s", 1)]
1586 time = []
1594 time = []
1587 leftover = timespan
1595 leftover = timespan
1588 for suffix, length in parts:
1596 for suffix, length in parts:
1589 value = int(leftover / length)
1597 value = int(leftover / length)
1590 if value > 0:
1598 if value > 0:
1591 leftover = leftover % length
1599 leftover = leftover % length
1592 time.append(u'%s%s' % (str(value), suffix))
1600 time.append(u'%s%s' % (str(value), suffix))
1593 if leftover < 1:
1601 if leftover < 1:
1594 break
1602 break
1595 return " ".join(time)
1603 return " ".join(time)
1596
1604
1597
1605
1598 # Unfortunately characters outside of range(128) can cause problems in
1606 # Unfortunately characters outside of range(128) can cause problems in
1599 # certain terminals.
1607 # certain terminals.
1600 # See bug: https://bugs.launchpad.net/ipython/+bug/348466
1608 # See bug: https://bugs.launchpad.net/ipython/+bug/348466
1601 # Try to prevent crashes by being more secure than it needs to
1609 # Try to prevent crashes by being more secure than it needs to
1602 # E.g. eclipse is able to print a µ, but has no sys.stdout.encoding set.
1610 # E.g. eclipse is able to print a µ, but has no sys.stdout.encoding set.
1603 units = ["s", "ms", "us", "ns"] # the safe value
1611 units = ["s", "ms", "us", "ns"] # the safe value
1604 if hasattr(sys.stdout, "encoding") and sys.stdout.encoding:
1612 if hasattr(sys.stdout, "encoding") and sys.stdout.encoding:
1605 try:
1613 try:
1606 "μ".encode(sys.stdout.encoding)
1614 "μ".encode(sys.stdout.encoding)
1607 units = ["s", "ms", "μs", "ns"]
1615 units = ["s", "ms", "μs", "ns"]
1608 except:
1616 except:
1609 pass
1617 pass
1610 scaling = [1, 1e3, 1e6, 1e9]
1618 scaling = [1, 1e3, 1e6, 1e9]
1611
1619
1612 if timespan > 0.0:
1620 if timespan > 0.0:
1613 order = min(-int(math.floor(math.log10(timespan)) // 3), 3)
1621 order = min(-int(math.floor(math.log10(timespan)) // 3), 3)
1614 else:
1622 else:
1615 order = 3
1623 order = 3
1616 return "%.*g %s" % (precision, timespan * scaling[order], units[order])
1624 return "%.*g %s" % (precision, timespan * scaling[order], units[order])
@@ -1,855 +1,855
1 """Implementation of magic functions for interaction with the OS.
1 """Implementation of magic functions for interaction with the OS.
2
2
3 Note: this module is named 'osm' instead of 'os' to avoid a collision with the
3 Note: this module is named 'osm' instead of 'os' to avoid a collision with the
4 builtin.
4 builtin.
5 """
5 """
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 import io
9 import io
10 import os
10 import os
11 import pathlib
11 import pathlib
12 import re
12 import re
13 import sys
13 import sys
14 from pprint import pformat
14 from pprint import pformat
15
15
16 from IPython.core import magic_arguments
16 from IPython.core import magic_arguments
17 from IPython.core import oinspect
17 from IPython.core import oinspect
18 from IPython.core import page
18 from IPython.core import page
19 from IPython.core.alias import AliasError, Alias
19 from IPython.core.alias import AliasError, Alias
20 from IPython.core.error import UsageError
20 from IPython.core.error import UsageError
21 from IPython.core.magic import (
21 from IPython.core.magic import (
22 Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic
22 Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic
23 )
23 )
24 from IPython.testing.skipdoctest import skip_doctest
24 from IPython.testing.skipdoctest import skip_doctest
25 from IPython.utils.openpy import source_to_unicode
25 from IPython.utils.openpy import source_to_unicode
26 from IPython.utils.process import abbrev_cwd
26 from IPython.utils.process import abbrev_cwd
27 from IPython.utils.terminal import set_term_title
27 from IPython.utils.terminal import set_term_title
28 from traitlets import Bool
28 from traitlets import Bool
29 from warnings import warn
29 from warnings import warn
30
30
31
31
32 @magics_class
32 @magics_class
33 class OSMagics(Magics):
33 class OSMagics(Magics):
34 """Magics to interact with the underlying OS (shell-type functionality).
34 """Magics to interact with the underlying OS (shell-type functionality).
35 """
35 """
36
36
37 cd_force_quiet = Bool(False,
37 cd_force_quiet = Bool(False,
38 help="Force %cd magic to be quiet even if -q is not passed."
38 help="Force %cd magic to be quiet even if -q is not passed."
39 ).tag(config=True)
39 ).tag(config=True)
40
40
41 def __init__(self, shell=None, **kwargs):
41 def __init__(self, shell=None, **kwargs):
42
42
43 # Now define isexec in a cross platform manner.
43 # Now define isexec in a cross platform manner.
44 self.is_posix = False
44 self.is_posix = False
45 self.execre = None
45 self.execre = None
46 if os.name == 'posix':
46 if os.name == 'posix':
47 self.is_posix = True
47 self.is_posix = True
48 else:
48 else:
49 try:
49 try:
50 winext = os.environ['pathext'].replace(';','|').replace('.','')
50 winext = os.environ['pathext'].replace(';','|').replace('.','')
51 except KeyError:
51 except KeyError:
52 winext = 'exe|com|bat|py'
52 winext = 'exe|com|bat|py'
53 try:
53 try:
54 self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
54 self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
55 except re.error:
55 except re.error:
56 warn("Seems like your pathext environmental "
56 warn("Seems like your pathext environmental "
57 "variable is malformed. Please check it to "
57 "variable is malformed. Please check it to "
58 "enable a proper handle of file extensions "
58 "enable a proper handle of file extensions "
59 "managed for your system")
59 "managed for your system")
60 winext = 'exe|com|bat|py'
60 winext = 'exe|com|bat|py'
61 self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
61 self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
62
62
63 # call up the chain
63 # call up the chain
64 super().__init__(shell=shell, **kwargs)
64 super().__init__(shell=shell, **kwargs)
65
65
66
66
67 def _isexec_POSIX(self, file):
67 def _isexec_POSIX(self, file):
68 """
68 """
69 Test for executable on a POSIX system
69 Test for executable on a POSIX system
70 """
70 """
71 if os.access(file.path, os.X_OK):
71 if os.access(file.path, os.X_OK):
72 # will fail on maxOS if access is not X_OK
72 # will fail on maxOS if access is not X_OK
73 return file.is_file()
73 return file.is_file()
74 return False
74 return False
75
75
76
76
77
77
78 def _isexec_WIN(self, file):
78 def _isexec_WIN(self, file):
79 """
79 """
80 Test for executable file on non POSIX system
80 Test for executable file on non POSIX system
81 """
81 """
82 return file.is_file() and self.execre.match(file.name) is not None
82 return file.is_file() and self.execre.match(file.name) is not None
83
83
84 def isexec(self, file):
84 def isexec(self, file):
85 """
85 """
86 Test for executable file on non POSIX system
86 Test for executable file on non POSIX system
87 """
87 """
88 if self.is_posix:
88 if self.is_posix:
89 return self._isexec_POSIX(file)
89 return self._isexec_POSIX(file)
90 else:
90 else:
91 return self._isexec_WIN(file)
91 return self._isexec_WIN(file)
92
92
93
93
94 @skip_doctest
94 @skip_doctest
95 @line_magic
95 @line_magic
96 def alias(self, parameter_s=''):
96 def alias(self, parameter_s=''):
97 """Define an alias for a system command.
97 """Define an alias for a system command.
98
98
99 '%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd'
99 '%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd'
100
100
101 Then, typing 'alias_name params' will execute the system command 'cmd
101 Then, typing 'alias_name params' will execute the system command 'cmd
102 params' (from your underlying operating system).
102 params' (from your underlying operating system).
103
103
104 Aliases have lower precedence than magic functions and Python normal
104 Aliases have lower precedence than magic functions and Python normal
105 variables, so if 'foo' is both a Python variable and an alias, the
105 variables, so if 'foo' is both a Python variable and an alias, the
106 alias can not be executed until 'del foo' removes the Python variable.
106 alias can not be executed until 'del foo' removes the Python variable.
107
107
108 You can use the %l specifier in an alias definition to represent the
108 You can use the %l specifier in an alias definition to represent the
109 whole line when the alias is called. For example::
109 whole line when the alias is called. For example::
110
110
111 In [2]: alias bracket echo "Input in brackets: <%l>"
111 In [2]: alias bracket echo "Input in brackets: <%l>"
112 In [3]: bracket hello world
112 In [3]: bracket hello world
113 Input in brackets: <hello world>
113 Input in brackets: <hello world>
114
114
115 You can also define aliases with parameters using %s specifiers (one
115 You can also define aliases with parameters using %s specifiers (one
116 per parameter)::
116 per parameter)::
117
117
118 In [1]: alias parts echo first %s second %s
118 In [1]: alias parts echo first %s second %s
119 In [2]: %parts A B
119 In [2]: %parts A B
120 first A second B
120 first A second B
121 In [3]: %parts A
121 In [3]: %parts A
122 Incorrect number of arguments: 2 expected.
122 Incorrect number of arguments: 2 expected.
123 parts is an alias to: 'echo first %s second %s'
123 parts is an alias to: 'echo first %s second %s'
124
124
125 Note that %l and %s are mutually exclusive. You can only use one or
125 Note that %l and %s are mutually exclusive. You can only use one or
126 the other in your aliases.
126 the other in your aliases.
127
127
128 Aliases expand Python variables just like system calls using ! or !!
128 Aliases expand Python variables just like system calls using ! or !!
129 do: all expressions prefixed with '$' get expanded. For details of
129 do: all expressions prefixed with '$' get expanded. For details of
130 the semantic rules, see PEP-215:
130 the semantic rules, see PEP-215:
131 https://peps.python.org/pep-0215/. This is the library used by
131 https://peps.python.org/pep-0215/. This is the library used by
132 IPython for variable expansion. If you want to access a true shell
132 IPython for variable expansion. If you want to access a true shell
133 variable, an extra $ is necessary to prevent its expansion by
133 variable, an extra $ is necessary to prevent its expansion by
134 IPython::
134 IPython::
135
135
136 In [6]: alias show echo
136 In [6]: alias show echo
137 In [7]: PATH='A Python string'
137 In [7]: PATH='A Python string'
138 In [8]: show $PATH
138 In [8]: show $PATH
139 A Python string
139 A Python string
140 In [9]: show $$PATH
140 In [9]: show $$PATH
141 /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:...
141 /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:...
142
142
143 You can use the alias facility to access all of $PATH. See the %rehashx
143 You can use the alias facility to access all of $PATH. See the %rehashx
144 function, which automatically creates aliases for the contents of your
144 function, which automatically creates aliases for the contents of your
145 $PATH.
145 $PATH.
146
146
147 If called with no parameters, %alias prints the current alias table
147 If called with no parameters, %alias prints the current alias table
148 for your system. For posix systems, the default aliases are 'cat',
148 for your system. For posix systems, the default aliases are 'cat',
149 'cp', 'mv', 'rm', 'rmdir', and 'mkdir', and other platform-specific
149 'cp', 'mv', 'rm', 'rmdir', and 'mkdir', and other platform-specific
150 aliases are added. For windows-based systems, the default aliases are
150 aliases are added. For windows-based systems, the default aliases are
151 'copy', 'ddir', 'echo', 'ls', 'ldir', 'mkdir', 'ren', and 'rmdir'.
151 'copy', 'ddir', 'echo', 'ls', 'ldir', 'mkdir', 'ren', and 'rmdir'.
152
152
153 You can see the definition of alias by adding a question mark in the
153 You can see the definition of alias by adding a question mark in the
154 end::
154 end::
155
155
156 In [1]: cat?
156 In [1]: cat?
157 Repr: <alias cat for 'cat'>"""
157 Repr: <alias cat for 'cat'>"""
158
158
159 par = parameter_s.strip()
159 par = parameter_s.strip()
160 if not par:
160 if not par:
161 aliases = sorted(self.shell.alias_manager.aliases)
161 aliases = sorted(self.shell.alias_manager.aliases)
162 # stored = self.shell.db.get('stored_aliases', {} )
162 # stored = self.shell.db.get('stored_aliases', {} )
163 # for k, v in stored:
163 # for k, v in stored:
164 # atab.append(k, v[0])
164 # atab.append(k, v[0])
165
165
166 print("Total number of aliases:", len(aliases))
166 print("Total number of aliases:", len(aliases))
167 sys.stdout.flush()
167 sys.stdout.flush()
168 return aliases
168 return aliases
169
169
170 # Now try to define a new one
170 # Now try to define a new one
171 try:
171 try:
172 alias,cmd = par.split(None, 1)
172 alias,cmd = par.split(None, 1)
173 except TypeError:
173 except TypeError:
174 print(oinspect.getdoc(self.alias))
174 print(oinspect.getdoc(self.alias))
175 return
175 return
176
176
177 try:
177 try:
178 self.shell.alias_manager.define_alias(alias, cmd)
178 self.shell.alias_manager.define_alias(alias, cmd)
179 except AliasError as e:
179 except AliasError as e:
180 print(e)
180 print(e)
181 # end magic_alias
181 # end magic_alias
182
182
183 @line_magic
183 @line_magic
184 def unalias(self, parameter_s=''):
184 def unalias(self, parameter_s=''):
185 """Remove an alias"""
185 """Remove an alias"""
186
186
187 aname = parameter_s.strip()
187 aname = parameter_s.strip()
188 try:
188 try:
189 self.shell.alias_manager.undefine_alias(aname)
189 self.shell.alias_manager.undefine_alias(aname)
190 except ValueError as e:
190 except ValueError as e:
191 print(e)
191 print(e)
192 return
192 return
193
193
194 stored = self.shell.db.get('stored_aliases', {} )
194 stored = self.shell.db.get('stored_aliases', {} )
195 if aname in stored:
195 if aname in stored:
196 print("Removing %stored alias",aname)
196 print("Removing %stored alias",aname)
197 del stored[aname]
197 del stored[aname]
198 self.shell.db['stored_aliases'] = stored
198 self.shell.db['stored_aliases'] = stored
199
199
200 @line_magic
200 @line_magic
201 def rehashx(self, parameter_s=''):
201 def rehashx(self, parameter_s=''):
202 """Update the alias table with all executable files in $PATH.
202 """Update the alias table with all executable files in $PATH.
203
203
204 rehashx explicitly checks that every entry in $PATH is a file
204 rehashx explicitly checks that every entry in $PATH is a file
205 with execute access (os.X_OK).
205 with execute access (os.X_OK).
206
206
207 Under Windows, it checks executability as a match against a
207 Under Windows, it checks executability as a match against a
208 '|'-separated string of extensions, stored in the IPython config
208 '|'-separated string of extensions, stored in the IPython config
209 variable win_exec_ext. This defaults to 'exe|com|bat'.
209 variable win_exec_ext. This defaults to 'exe|com|bat'.
210
210
211 This function also resets the root module cache of module completer,
211 This function also resets the root module cache of module completer,
212 used on slow filesystems.
212 used on slow filesystems.
213 """
213 """
214 from IPython.core.alias import InvalidAliasError
214 from IPython.core.alias import InvalidAliasError
215
215
216 # for the benefit of module completer in ipy_completers.py
216 # for the benefit of module completer in ipy_completers.py
217 del self.shell.db['rootmodules_cache']
217 del self.shell.db['rootmodules_cache']
218
218
219 path = [os.path.abspath(os.path.expanduser(p)) for p in
219 path = [os.path.abspath(os.path.expanduser(p)) for p in
220 os.environ.get('PATH','').split(os.pathsep)]
220 os.environ.get('PATH','').split(os.pathsep)]
221
221
222 syscmdlist = []
222 syscmdlist = []
223 savedir = os.getcwd()
223 savedir = os.getcwd()
224
224
225 # Now walk the paths looking for executables to alias.
225 # Now walk the paths looking for executables to alias.
226 try:
226 try:
227 # write the whole loop for posix/Windows so we don't have an if in
227 # write the whole loop for posix/Windows so we don't have an if in
228 # the innermost part
228 # the innermost part
229 if self.is_posix:
229 if self.is_posix:
230 for pdir in path:
230 for pdir in path:
231 try:
231 try:
232 os.chdir(pdir)
232 os.chdir(pdir)
233 except OSError:
233 except OSError:
234 continue
234 continue
235
235
236 # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist:
236 # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist:
237 dirlist = os.scandir(path=pdir)
237 dirlist = os.scandir(path=pdir)
238 for ff in dirlist:
238 for ff in dirlist:
239 if self.isexec(ff):
239 if self.isexec(ff):
240 fname = ff.name
240 fname = ff.name
241 try:
241 try:
242 # Removes dots from the name since ipython
242 # Removes dots from the name since ipython
243 # will assume names with dots to be python.
243 # will assume names with dots to be python.
244 if not self.shell.alias_manager.is_alias(fname):
244 if not self.shell.alias_manager.is_alias(fname):
245 self.shell.alias_manager.define_alias(
245 self.shell.alias_manager.define_alias(
246 fname.replace('.',''), fname)
246 fname.replace('.',''), fname)
247 except InvalidAliasError:
247 except InvalidAliasError:
248 pass
248 pass
249 else:
249 else:
250 syscmdlist.append(fname)
250 syscmdlist.append(fname)
251 else:
251 else:
252 no_alias = Alias.blacklist
252 no_alias = Alias.blacklist
253 for pdir in path:
253 for pdir in path:
254 try:
254 try:
255 os.chdir(pdir)
255 os.chdir(pdir)
256 except OSError:
256 except OSError:
257 continue
257 continue
258
258
259 # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist:
259 # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist:
260 dirlist = os.scandir(pdir)
260 dirlist = os.scandir(pdir)
261 for ff in dirlist:
261 for ff in dirlist:
262 fname = ff.name
262 fname = ff.name
263 base, ext = os.path.splitext(fname)
263 base, ext = os.path.splitext(fname)
264 if self.isexec(ff) and base.lower() not in no_alias:
264 if self.isexec(ff) and base.lower() not in no_alias:
265 if ext.lower() == '.exe':
265 if ext.lower() == '.exe':
266 fname = base
266 fname = base
267 try:
267 try:
268 # Removes dots from the name since ipython
268 # Removes dots from the name since ipython
269 # will assume names with dots to be python.
269 # will assume names with dots to be python.
270 self.shell.alias_manager.define_alias(
270 self.shell.alias_manager.define_alias(
271 base.lower().replace('.',''), fname)
271 base.lower().replace('.',''), fname)
272 except InvalidAliasError:
272 except InvalidAliasError:
273 pass
273 pass
274 syscmdlist.append(fname)
274 syscmdlist.append(fname)
275
275
276 self.shell.db['syscmdlist'] = syscmdlist
276 self.shell.db['syscmdlist'] = syscmdlist
277 finally:
277 finally:
278 os.chdir(savedir)
278 os.chdir(savedir)
279
279
280 @skip_doctest
280 @skip_doctest
281 @line_magic
281 @line_magic
282 def pwd(self, parameter_s=''):
282 def pwd(self, parameter_s=''):
283 """Return the current working directory path.
283 """Return the current working directory path.
284
284
285 Examples
285 Examples
286 --------
286 --------
287 ::
287 ::
288
288
289 In [9]: pwd
289 In [9]: pwd
290 Out[9]: '/home/tsuser/sprint/ipython'
290 Out[9]: '/home/tsuser/sprint/ipython'
291 """
291 """
292 try:
292 try:
293 return os.getcwd()
293 return os.getcwd()
294 except FileNotFoundError as e:
294 except FileNotFoundError as e:
295 raise UsageError("CWD no longer exists - please use %cd to change directory.") from e
295 raise UsageError("CWD no longer exists - please use %cd to change directory.") from e
296
296
297 @skip_doctest
297 @skip_doctest
298 @line_magic
298 @line_magic
299 def cd(self, parameter_s=''):
299 def cd(self, parameter_s=''):
300 """Change the current working directory.
300 """Change the current working directory.
301
301
302 This command automatically maintains an internal list of directories
302 This command automatically maintains an internal list of directories
303 you visit during your IPython session, in the variable ``_dh``. The
303 you visit during your IPython session, in the variable ``_dh``. The
304 command :magic:`%dhist` shows this history nicely formatted. You can
304 command :magic:`%dhist` shows this history nicely formatted. You can
305 also do ``cd -<tab>`` to see directory history conveniently.
305 also do ``cd -<tab>`` to see directory history conveniently.
306 Usage:
306 Usage:
307
307
308 - ``cd 'dir'``: changes to directory 'dir'.
308 - ``cd 'dir'``: changes to directory 'dir'.
309 - ``cd -``: changes to the last visited directory.
309 - ``cd -``: changes to the last visited directory.
310 - ``cd -<n>``: changes to the n-th directory in the directory history.
310 - ``cd -<n>``: changes to the n-th directory in the directory history.
311 - ``cd --foo``: change to directory that matches 'foo' in history
311 - ``cd --foo``: change to directory that matches 'foo' in history
312 - ``cd -b <bookmark_name>``: jump to a bookmark set by %bookmark
312 - ``cd -b <bookmark_name>``: jump to a bookmark set by %bookmark
313 - Hitting a tab key after ``cd -b`` allows you to tab-complete
313 - Hitting a tab key after ``cd -b`` allows you to tab-complete
314 bookmark names.
314 bookmark names.
315
315
316 .. note::
316 .. note::
317 ``cd <bookmark_name>`` is enough if there is no directory
317 ``cd <bookmark_name>`` is enough if there is no directory
318 ``<bookmark_name>``, but a bookmark with the name exists.
318 ``<bookmark_name>``, but a bookmark with the name exists.
319
319
320 Options:
320 Options:
321
321
322 -q Be quiet. Do not print the working directory after the
322 -q Be quiet. Do not print the working directory after the
323 cd command is executed. By default IPython's cd
323 cd command is executed. By default IPython's cd
324 command does print this directory, since the default
324 command does print this directory, since the default
325 prompts do not display path information.
325 prompts do not display path information.
326
326
327 .. note::
327 .. note::
328 Note that ``!cd`` doesn't work for this purpose because the shell
328 Note that ``!cd`` doesn't work for this purpose because the shell
329 where ``!command`` runs is immediately discarded after executing
329 where ``!command`` runs is immediately discarded after executing
330 'command'.
330 'command'.
331
331
332 Examples
332 Examples
333 --------
333 --------
334 ::
334 ::
335
335
336 In [10]: cd parent/child
336 In [10]: cd parent/child
337 /home/tsuser/parent/child
337 /home/tsuser/parent/child
338 """
338 """
339
339
340 try:
340 try:
341 oldcwd = os.getcwd()
341 oldcwd = os.getcwd()
342 except FileNotFoundError:
342 except FileNotFoundError:
343 # Happens if the CWD has been deleted.
343 # Happens if the CWD has been deleted.
344 oldcwd = None
344 oldcwd = None
345
345
346 numcd = re.match(r'(-)(\d+)$',parameter_s)
346 numcd = re.match(r'(-)(\d+)$',parameter_s)
347 # jump in directory history by number
347 # jump in directory history by number
348 if numcd:
348 if numcd:
349 nn = int(numcd.group(2))
349 nn = int(numcd.group(2))
350 try:
350 try:
351 ps = self.shell.user_ns['_dh'][nn]
351 ps = self.shell.user_ns['_dh'][nn]
352 except IndexError:
352 except IndexError:
353 print('The requested directory does not exist in history.')
353 print('The requested directory does not exist in history.')
354 return
354 return
355 else:
355 else:
356 opts = {}
356 opts = {}
357 elif parameter_s.startswith('--'):
357 elif parameter_s.startswith('--'):
358 ps = None
358 ps = None
359 fallback = None
359 fallback = None
360 pat = parameter_s[2:]
360 pat = parameter_s[2:]
361 dh = self.shell.user_ns['_dh']
361 dh = self.shell.user_ns['_dh']
362 # first search only by basename (last component)
362 # first search only by basename (last component)
363 for ent in reversed(dh):
363 for ent in reversed(dh):
364 if pat in os.path.basename(ent) and os.path.isdir(ent):
364 if pat in os.path.basename(ent) and os.path.isdir(ent):
365 ps = ent
365 ps = ent
366 break
366 break
367
367
368 if fallback is None and pat in ent and os.path.isdir(ent):
368 if fallback is None and pat in ent and os.path.isdir(ent):
369 fallback = ent
369 fallback = ent
370
370
371 # if we have no last part match, pick the first full path match
371 # if we have no last part match, pick the first full path match
372 if ps is None:
372 if ps is None:
373 ps = fallback
373 ps = fallback
374
374
375 if ps is None:
375 if ps is None:
376 print("No matching entry in directory history")
376 print("No matching entry in directory history")
377 return
377 return
378 else:
378 else:
379 opts = {}
379 opts = {}
380
380
381
381
382 else:
382 else:
383 opts, ps = self.parse_options(parameter_s, 'qb', mode='string')
383 opts, ps = self.parse_options(parameter_s, 'qb', mode='string')
384 # jump to previous
384 # jump to previous
385 if ps == '-':
385 if ps == '-':
386 try:
386 try:
387 ps = self.shell.user_ns['_dh'][-2]
387 ps = self.shell.user_ns['_dh'][-2]
388 except IndexError as e:
388 except IndexError as e:
389 raise UsageError('%cd -: No previous directory to change to.') from e
389 raise UsageError('%cd -: No previous directory to change to.') from e
390 # jump to bookmark if needed
390 # jump to bookmark if needed
391 else:
391 else:
392 if not os.path.isdir(ps) or 'b' in opts:
392 if not os.path.isdir(ps) or 'b' in opts:
393 bkms = self.shell.db.get('bookmarks', {})
393 bkms = self.shell.db.get('bookmarks', {})
394
394
395 if ps in bkms:
395 if ps in bkms:
396 target = bkms[ps]
396 target = bkms[ps]
397 print('(bookmark:%s) -> %s' % (ps, target))
397 print('(bookmark:%s) -> %s' % (ps, target))
398 ps = target
398 ps = target
399 else:
399 else:
400 if 'b' in opts:
400 if 'b' in opts:
401 raise UsageError("Bookmark '%s' not found. "
401 raise UsageError("Bookmark '%s' not found. "
402 "Use '%%bookmark -l' to see your bookmarks." % ps)
402 "Use '%%bookmark -l' to see your bookmarks." % ps)
403
403
404 # at this point ps should point to the target dir
404 # at this point ps should point to the target dir
405 if ps:
405 if ps:
406 try:
406 try:
407 os.chdir(os.path.expanduser(ps))
407 os.chdir(os.path.expanduser(ps))
408 if hasattr(self.shell, 'term_title') and self.shell.term_title:
408 if hasattr(self.shell, 'term_title') and self.shell.term_title:
409 set_term_title(self.shell.term_title_format.format(cwd=abbrev_cwd()))
409 set_term_title(self.shell.term_title_format.format(cwd=abbrev_cwd()))
410 except OSError:
410 except OSError:
411 print(sys.exc_info()[1])
411 print(sys.exc_info()[1])
412 else:
412 else:
413 cwd = pathlib.Path.cwd()
413 cwd = pathlib.Path.cwd()
414 dhist = self.shell.user_ns['_dh']
414 dhist = self.shell.user_ns['_dh']
415 if oldcwd != cwd:
415 if oldcwd != cwd:
416 dhist.append(cwd)
416 dhist.append(cwd)
417 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
417 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
418
418
419 else:
419 else:
420 os.chdir(self.shell.home_dir)
420 os.chdir(self.shell.home_dir)
421 if hasattr(self.shell, 'term_title') and self.shell.term_title:
421 if hasattr(self.shell, 'term_title') and self.shell.term_title:
422 set_term_title(self.shell.term_title_format.format(cwd="~"))
422 set_term_title(self.shell.term_title_format.format(cwd="~"))
423 cwd = pathlib.Path.cwd()
423 cwd = pathlib.Path.cwd()
424 dhist = self.shell.user_ns['_dh']
424 dhist = self.shell.user_ns['_dh']
425
425
426 if oldcwd != cwd:
426 if oldcwd != cwd:
427 dhist.append(cwd)
427 dhist.append(cwd)
428 self.shell.db["dhist"] = compress_dhist(dhist)[-100:]
428 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
429 if "q" not in opts and not self.cd_force_quiet and self.shell.user_ns["_dh"]:
429 if not 'q' in opts and not self.cd_force_quiet and self.shell.user_ns['_dh']:
430 print(self.shell.user_ns["_dh"][-1])
430 print(self.shell.user_ns['_dh'][-1])
431
431
432 @line_magic
432 @line_magic
433 def env(self, parameter_s=''):
433 def env(self, parameter_s=''):
434 """Get, set, or list environment variables.
434 """Get, set, or list environment variables.
435
435
436 Usage:\\
436 Usage:\\
437
437
438 :``%env``: lists all environment variables/values
438 :``%env``: lists all environment variables/values
439 :``%env var``: get value for var
439 :``%env var``: get value for var
440 :``%env var val``: set value for var
440 :``%env var val``: set value for var
441 :``%env var=val``: set value for var
441 :``%env var=val``: set value for var
442 :``%env var=$val``: set value for var, using python expansion if possible
442 :``%env var=$val``: set value for var, using python expansion if possible
443 """
443 """
444 if parameter_s.strip():
444 if parameter_s.strip():
445 split = '=' if '=' in parameter_s else ' '
445 split = '=' if '=' in parameter_s else ' '
446 bits = parameter_s.split(split)
446 bits = parameter_s.split(split)
447 if len(bits) == 1:
447 if len(bits) == 1:
448 key = parameter_s.strip()
448 key = parameter_s.strip()
449 if key in os.environ:
449 if key in os.environ:
450 return os.environ[key]
450 return os.environ[key]
451 else:
451 else:
452 err = "Environment does not have key: {0}".format(key)
452 err = "Environment does not have key: {0}".format(key)
453 raise UsageError(err)
453 raise UsageError(err)
454 if len(bits) > 1:
454 if len(bits) > 1:
455 return self.set_env(parameter_s)
455 return self.set_env(parameter_s)
456 env = dict(os.environ)
456 env = dict(os.environ)
457 # hide likely secrets when printing the whole environment
457 # hide likely secrets when printing the whole environment
458 for key in list(env):
458 for key in list(env):
459 if any(s in key.lower() for s in ('key', 'token', 'secret')):
459 if any(s in key.lower() for s in ('key', 'token', 'secret')):
460 env[key] = '<hidden>'
460 env[key] = '<hidden>'
461
461
462 return env
462 return env
463
463
464 @line_magic
464 @line_magic
465 def set_env(self, parameter_s):
465 def set_env(self, parameter_s):
466 """Set environment variables. Assumptions are that either "val" is a
466 """Set environment variables. Assumptions are that either "val" is a
467 name in the user namespace, or val is something that evaluates to a
467 name in the user namespace, or val is something that evaluates to a
468 string.
468 string.
469
469
470 Usage:\\
470 Usage:\\
471 :``%set_env var val``: set value for var
471 :``%set_env var val``: set value for var
472 :``%set_env var=val``: set value for var
472 :``%set_env var=val``: set value for var
473 :``%set_env var=$val``: set value for var, using python expansion if possible
473 :``%set_env var=$val``: set value for var, using python expansion if possible
474 """
474 """
475 split = '=' if '=' in parameter_s else ' '
475 split = '=' if '=' in parameter_s else ' '
476 bits = parameter_s.split(split, 1)
476 bits = parameter_s.split(split, 1)
477 if not parameter_s.strip() or len(bits)<2:
477 if not parameter_s.strip() or len(bits)<2:
478 raise UsageError("usage is 'set_env var=val'")
478 raise UsageError("usage is 'set_env var=val'")
479 var = bits[0].strip()
479 var = bits[0].strip()
480 val = bits[1].strip()
480 val = bits[1].strip()
481 if re.match(r'.*\s.*', var):
481 if re.match(r'.*\s.*', var):
482 # an environment variable with whitespace is almost certainly
482 # an environment variable with whitespace is almost certainly
483 # not what the user intended. what's more likely is the wrong
483 # not what the user intended. what's more likely is the wrong
484 # split was chosen, ie for "set_env cmd_args A=B", we chose
484 # split was chosen, ie for "set_env cmd_args A=B", we chose
485 # '=' for the split and should have chosen ' '. to get around
485 # '=' for the split and should have chosen ' '. to get around
486 # this, users should just assign directly to os.environ or use
486 # this, users should just assign directly to os.environ or use
487 # standard magic {var} expansion.
487 # standard magic {var} expansion.
488 err = "refusing to set env var with whitespace: '{0}'"
488 err = "refusing to set env var with whitespace: '{0}'"
489 err = err.format(val)
489 err = err.format(val)
490 raise UsageError(err)
490 raise UsageError(err)
491 os.environ[var] = val
491 os.environ[var] = val
492 print('env: {0}={1}'.format(var,val))
492 print('env: {0}={1}'.format(var,val))
493
493
494 @line_magic
494 @line_magic
495 def pushd(self, parameter_s=''):
495 def pushd(self, parameter_s=''):
496 """Place the current dir on stack and change directory.
496 """Place the current dir on stack and change directory.
497
497
498 Usage:\\
498 Usage:\\
499 %pushd ['dirname']
499 %pushd ['dirname']
500 """
500 """
501
501
502 dir_s = self.shell.dir_stack
502 dir_s = self.shell.dir_stack
503 tgt = os.path.expanduser(parameter_s)
503 tgt = os.path.expanduser(parameter_s)
504 cwd = os.getcwd().replace(self.shell.home_dir,'~')
504 cwd = os.getcwd().replace(self.shell.home_dir,'~')
505 if tgt:
505 if tgt:
506 self.cd(parameter_s)
506 self.cd(parameter_s)
507 dir_s.insert(0,cwd)
507 dir_s.insert(0,cwd)
508 return self.shell.run_line_magic('dirs', '')
508 return self.shell.run_line_magic('dirs', '')
509
509
510 @line_magic
510 @line_magic
511 def popd(self, parameter_s=''):
511 def popd(self, parameter_s=''):
512 """Change to directory popped off the top of the stack.
512 """Change to directory popped off the top of the stack.
513 """
513 """
514 if not self.shell.dir_stack:
514 if not self.shell.dir_stack:
515 raise UsageError("%popd on empty stack")
515 raise UsageError("%popd on empty stack")
516 top = self.shell.dir_stack.pop(0)
516 top = self.shell.dir_stack.pop(0)
517 self.cd(top)
517 self.cd(top)
518 print("popd ->",top)
518 print("popd ->",top)
519
519
520 @line_magic
520 @line_magic
521 def dirs(self, parameter_s=''):
521 def dirs(self, parameter_s=''):
522 """Return the current directory stack."""
522 """Return the current directory stack."""
523
523
524 return self.shell.dir_stack
524 return self.shell.dir_stack
525
525
526 @line_magic
526 @line_magic
527 def dhist(self, parameter_s=''):
527 def dhist(self, parameter_s=''):
528 """Print your history of visited directories.
528 """Print your history of visited directories.
529
529
530 %dhist -> print full history\\
530 %dhist -> print full history\\
531 %dhist n -> print last n entries only\\
531 %dhist n -> print last n entries only\\
532 %dhist n1 n2 -> print entries between n1 and n2 (n2 not included)\\
532 %dhist n1 n2 -> print entries between n1 and n2 (n2 not included)\\
533
533
534 This history is automatically maintained by the %cd command, and
534 This history is automatically maintained by the %cd command, and
535 always available as the global list variable _dh. You can use %cd -<n>
535 always available as the global list variable _dh. You can use %cd -<n>
536 to go to directory number <n>.
536 to go to directory number <n>.
537
537
538 Note that most of time, you should view directory history by entering
538 Note that most of time, you should view directory history by entering
539 cd -<TAB>.
539 cd -<TAB>.
540
540
541 """
541 """
542
542
543 dh = self.shell.user_ns['_dh']
543 dh = self.shell.user_ns['_dh']
544 if parameter_s:
544 if parameter_s:
545 try:
545 try:
546 args = map(int,parameter_s.split())
546 args = map(int,parameter_s.split())
547 except:
547 except:
548 self.arg_err(self.dhist)
548 self.arg_err(self.dhist)
549 return
549 return
550 if len(args) == 1:
550 if len(args) == 1:
551 ini,fin = max(len(dh)-(args[0]),0),len(dh)
551 ini,fin = max(len(dh)-(args[0]),0),len(dh)
552 elif len(args) == 2:
552 elif len(args) == 2:
553 ini,fin = args
553 ini,fin = args
554 fin = min(fin, len(dh))
554 fin = min(fin, len(dh))
555 else:
555 else:
556 self.arg_err(self.dhist)
556 self.arg_err(self.dhist)
557 return
557 return
558 else:
558 else:
559 ini,fin = 0,len(dh)
559 ini,fin = 0,len(dh)
560 print('Directory history (kept in _dh)')
560 print('Directory history (kept in _dh)')
561 for i in range(ini, fin):
561 for i in range(ini, fin):
562 print("%d: %s" % (i, dh[i]))
562 print("%d: %s" % (i, dh[i]))
563
563
564 @skip_doctest
564 @skip_doctest
565 @line_magic
565 @line_magic
566 def sc(self, parameter_s=''):
566 def sc(self, parameter_s=''):
567 """Shell capture - run shell command and capture output (DEPRECATED use !).
567 """Shell capture - run shell command and capture output (DEPRECATED use !).
568
568
569 DEPRECATED. Suboptimal, retained for backwards compatibility.
569 DEPRECATED. Suboptimal, retained for backwards compatibility.
570
570
571 You should use the form 'var = !command' instead. Example:
571 You should use the form 'var = !command' instead. Example:
572
572
573 "%sc -l myfiles = ls ~" should now be written as
573 "%sc -l myfiles = ls ~" should now be written as
574
574
575 "myfiles = !ls ~"
575 "myfiles = !ls ~"
576
576
577 myfiles.s, myfiles.l and myfiles.n still apply as documented
577 myfiles.s, myfiles.l and myfiles.n still apply as documented
578 below.
578 below.
579
579
580 --
580 --
581 %sc [options] varname=command
581 %sc [options] varname=command
582
582
583 IPython will run the given command using commands.getoutput(), and
583 IPython will run the given command using commands.getoutput(), and
584 will then update the user's interactive namespace with a variable
584 will then update the user's interactive namespace with a variable
585 called varname, containing the value of the call. Your command can
585 called varname, containing the value of the call. Your command can
586 contain shell wildcards, pipes, etc.
586 contain shell wildcards, pipes, etc.
587
587
588 The '=' sign in the syntax is mandatory, and the variable name you
588 The '=' sign in the syntax is mandatory, and the variable name you
589 supply must follow Python's standard conventions for valid names.
589 supply must follow Python's standard conventions for valid names.
590
590
591 (A special format without variable name exists for internal use)
591 (A special format without variable name exists for internal use)
592
592
593 Options:
593 Options:
594
594
595 -l: list output. Split the output on newlines into a list before
595 -l: list output. Split the output on newlines into a list before
596 assigning it to the given variable. By default the output is stored
596 assigning it to the given variable. By default the output is stored
597 as a single string.
597 as a single string.
598
598
599 -v: verbose. Print the contents of the variable.
599 -v: verbose. Print the contents of the variable.
600
600
601 In most cases you should not need to split as a list, because the
601 In most cases you should not need to split as a list, because the
602 returned value is a special type of string which can automatically
602 returned value is a special type of string which can automatically
603 provide its contents either as a list (split on newlines) or as a
603 provide its contents either as a list (split on newlines) or as a
604 space-separated string. These are convenient, respectively, either
604 space-separated string. These are convenient, respectively, either
605 for sequential processing or to be passed to a shell command.
605 for sequential processing or to be passed to a shell command.
606
606
607 For example::
607 For example::
608
608
609 # Capture into variable a
609 # Capture into variable a
610 In [1]: sc a=ls *py
610 In [1]: sc a=ls *py
611
611
612 # a is a string with embedded newlines
612 # a is a string with embedded newlines
613 In [2]: a
613 In [2]: a
614 Out[2]: 'setup.py\\nwin32_manual_post_install.py'
614 Out[2]: 'setup.py\\nwin32_manual_post_install.py'
615
615
616 # which can be seen as a list:
616 # which can be seen as a list:
617 In [3]: a.l
617 In [3]: a.l
618 Out[3]: ['setup.py', 'win32_manual_post_install.py']
618 Out[3]: ['setup.py', 'win32_manual_post_install.py']
619
619
620 # or as a whitespace-separated string:
620 # or as a whitespace-separated string:
621 In [4]: a.s
621 In [4]: a.s
622 Out[4]: 'setup.py win32_manual_post_install.py'
622 Out[4]: 'setup.py win32_manual_post_install.py'
623
623
624 # a.s is useful to pass as a single command line:
624 # a.s is useful to pass as a single command line:
625 In [5]: !wc -l $a.s
625 In [5]: !wc -l $a.s
626 146 setup.py
626 146 setup.py
627 130 win32_manual_post_install.py
627 130 win32_manual_post_install.py
628 276 total
628 276 total
629
629
630 # while the list form is useful to loop over:
630 # while the list form is useful to loop over:
631 In [6]: for f in a.l:
631 In [6]: for f in a.l:
632 ...: !wc -l $f
632 ...: !wc -l $f
633 ...:
633 ...:
634 146 setup.py
634 146 setup.py
635 130 win32_manual_post_install.py
635 130 win32_manual_post_install.py
636
636
637 Similarly, the lists returned by the -l option are also special, in
637 Similarly, the lists returned by the -l option are also special, in
638 the sense that you can equally invoke the .s attribute on them to
638 the sense that you can equally invoke the .s attribute on them to
639 automatically get a whitespace-separated string from their contents::
639 automatically get a whitespace-separated string from their contents::
640
640
641 In [7]: sc -l b=ls *py
641 In [7]: sc -l b=ls *py
642
642
643 In [8]: b
643 In [8]: b
644 Out[8]: ['setup.py', 'win32_manual_post_install.py']
644 Out[8]: ['setup.py', 'win32_manual_post_install.py']
645
645
646 In [9]: b.s
646 In [9]: b.s
647 Out[9]: 'setup.py win32_manual_post_install.py'
647 Out[9]: 'setup.py win32_manual_post_install.py'
648
648
649 In summary, both the lists and strings used for output capture have
649 In summary, both the lists and strings used for output capture have
650 the following special attributes::
650 the following special attributes::
651
651
652 .l (or .list) : value as list.
652 .l (or .list) : value as list.
653 .n (or .nlstr): value as newline-separated string.
653 .n (or .nlstr): value as newline-separated string.
654 .s (or .spstr): value as space-separated string.
654 .s (or .spstr): value as space-separated string.
655 """
655 """
656
656
657 opts,args = self.parse_options(parameter_s, 'lv')
657 opts,args = self.parse_options(parameter_s, 'lv')
658 # Try to get a variable name and command to run
658 # Try to get a variable name and command to run
659 try:
659 try:
660 # the variable name must be obtained from the parse_options
660 # the variable name must be obtained from the parse_options
661 # output, which uses shlex.split to strip options out.
661 # output, which uses shlex.split to strip options out.
662 var,_ = args.split('=', 1)
662 var,_ = args.split('=', 1)
663 var = var.strip()
663 var = var.strip()
664 # But the command has to be extracted from the original input
664 # But the command has to be extracted from the original input
665 # parameter_s, not on what parse_options returns, to avoid the
665 # parameter_s, not on what parse_options returns, to avoid the
666 # quote stripping which shlex.split performs on it.
666 # quote stripping which shlex.split performs on it.
667 _,cmd = parameter_s.split('=', 1)
667 _,cmd = parameter_s.split('=', 1)
668 except ValueError:
668 except ValueError:
669 var,cmd = '',''
669 var,cmd = '',''
670 # If all looks ok, proceed
670 # If all looks ok, proceed
671 split = 'l' in opts
671 split = 'l' in opts
672 out = self.shell.getoutput(cmd, split=split)
672 out = self.shell.getoutput(cmd, split=split)
673 if 'v' in opts:
673 if 'v' in opts:
674 print('%s ==\n%s' % (var, pformat(out)))
674 print('%s ==\n%s' % (var, pformat(out)))
675 if var:
675 if var:
676 self.shell.user_ns.update({var:out})
676 self.shell.user_ns.update({var:out})
677 else:
677 else:
678 return out
678 return out
679
679
680 @line_cell_magic
680 @line_cell_magic
681 def sx(self, line='', cell=None):
681 def sx(self, line='', cell=None):
682 """Shell execute - run shell command and capture output (!! is short-hand).
682 """Shell execute - run shell command and capture output (!! is short-hand).
683
683
684 %sx command
684 %sx command
685
685
686 IPython will run the given command using commands.getoutput(), and
686 IPython will run the given command using commands.getoutput(), and
687 return the result formatted as a list (split on '\\n'). Since the
687 return the result formatted as a list (split on '\\n'). Since the
688 output is _returned_, it will be stored in ipython's regular output
688 output is _returned_, it will be stored in ipython's regular output
689 cache Out[N] and in the '_N' automatic variables.
689 cache Out[N] and in the '_N' automatic variables.
690
690
691 Notes:
691 Notes:
692
692
693 1) If an input line begins with '!!', then %sx is automatically
693 1) If an input line begins with '!!', then %sx is automatically
694 invoked. That is, while::
694 invoked. That is, while::
695
695
696 !ls
696 !ls
697
697
698 causes ipython to simply issue system('ls'), typing::
698 causes ipython to simply issue system('ls'), typing::
699
699
700 !!ls
700 !!ls
701
701
702 is a shorthand equivalent to::
702 is a shorthand equivalent to::
703
703
704 %sx ls
704 %sx ls
705
705
706 2) %sx differs from %sc in that %sx automatically splits into a list,
706 2) %sx differs from %sc in that %sx automatically splits into a list,
707 like '%sc -l'. The reason for this is to make it as easy as possible
707 like '%sc -l'. The reason for this is to make it as easy as possible
708 to process line-oriented shell output via further python commands.
708 to process line-oriented shell output via further python commands.
709 %sc is meant to provide much finer control, but requires more
709 %sc is meant to provide much finer control, but requires more
710 typing.
710 typing.
711
711
712 3) Just like %sc -l, this is a list with special attributes:
712 3) Just like %sc -l, this is a list with special attributes:
713 ::
713 ::
714
714
715 .l (or .list) : value as list.
715 .l (or .list) : value as list.
716 .n (or .nlstr): value as newline-separated string.
716 .n (or .nlstr): value as newline-separated string.
717 .s (or .spstr): value as whitespace-separated string.
717 .s (or .spstr): value as whitespace-separated string.
718
718
719 This is very useful when trying to use such lists as arguments to
719 This is very useful when trying to use such lists as arguments to
720 system commands."""
720 system commands."""
721
721
722 if cell is None:
722 if cell is None:
723 # line magic
723 # line magic
724 return self.shell.getoutput(line)
724 return self.shell.getoutput(line)
725 else:
725 else:
726 opts,args = self.parse_options(line, '', 'out=')
726 opts,args = self.parse_options(line, '', 'out=')
727 output = self.shell.getoutput(cell)
727 output = self.shell.getoutput(cell)
728 out_name = opts.get('out', opts.get('o'))
728 out_name = opts.get('out', opts.get('o'))
729 if out_name:
729 if out_name:
730 self.shell.user_ns[out_name] = output
730 self.shell.user_ns[out_name] = output
731 else:
731 else:
732 return output
732 return output
733
733
734 system = line_cell_magic('system')(sx)
734 system = line_cell_magic('system')(sx)
735 bang = cell_magic('!')(sx)
735 bang = cell_magic('!')(sx)
736
736
737 @line_magic
737 @line_magic
738 def bookmark(self, parameter_s=''):
738 def bookmark(self, parameter_s=''):
739 """Manage IPython's bookmark system.
739 """Manage IPython's bookmark system.
740
740
741 %bookmark <name> - set bookmark to current dir
741 %bookmark <name> - set bookmark to current dir
742 %bookmark <name> <dir> - set bookmark to <dir>
742 %bookmark <name> <dir> - set bookmark to <dir>
743 %bookmark -l - list all bookmarks
743 %bookmark -l - list all bookmarks
744 %bookmark -d <name> - remove bookmark
744 %bookmark -d <name> - remove bookmark
745 %bookmark -r - remove all bookmarks
745 %bookmark -r - remove all bookmarks
746
746
747 You can later on access a bookmarked folder with::
747 You can later on access a bookmarked folder with::
748
748
749 %cd -b <name>
749 %cd -b <name>
750
750
751 or simply '%cd <name>' if there is no directory called <name> AND
751 or simply '%cd <name>' if there is no directory called <name> AND
752 there is such a bookmark defined.
752 there is such a bookmark defined.
753
753
754 Your bookmarks persist through IPython sessions, but they are
754 Your bookmarks persist through IPython sessions, but they are
755 associated with each profile."""
755 associated with each profile."""
756
756
757 opts,args = self.parse_options(parameter_s,'drl',mode='list')
757 opts,args = self.parse_options(parameter_s,'drl',mode='list')
758 if len(args) > 2:
758 if len(args) > 2:
759 raise UsageError("%bookmark: too many arguments")
759 raise UsageError("%bookmark: too many arguments")
760
760
761 bkms = self.shell.db.get('bookmarks',{})
761 bkms = self.shell.db.get('bookmarks',{})
762
762
763 if 'd' in opts:
763 if 'd' in opts:
764 try:
764 try:
765 todel = args[0]
765 todel = args[0]
766 except IndexError as e:
766 except IndexError as e:
767 raise UsageError(
767 raise UsageError(
768 "%bookmark -d: must provide a bookmark to delete") from e
768 "%bookmark -d: must provide a bookmark to delete") from e
769 else:
769 else:
770 try:
770 try:
771 del bkms[todel]
771 del bkms[todel]
772 except KeyError as e:
772 except KeyError as e:
773 raise UsageError(
773 raise UsageError(
774 "%%bookmark -d: Can't delete bookmark '%s'" % todel) from e
774 "%%bookmark -d: Can't delete bookmark '%s'" % todel) from e
775
775
776 elif 'r' in opts:
776 elif 'r' in opts:
777 bkms = {}
777 bkms = {}
778 elif 'l' in opts:
778 elif 'l' in opts:
779 bks = sorted(bkms)
779 bks = sorted(bkms)
780 if bks:
780 if bks:
781 size = max(map(len, bks))
781 size = max(map(len, bks))
782 else:
782 else:
783 size = 0
783 size = 0
784 fmt = '%-'+str(size)+'s -> %s'
784 fmt = '%-'+str(size)+'s -> %s'
785 print('Current bookmarks:')
785 print('Current bookmarks:')
786 for bk in bks:
786 for bk in bks:
787 print(fmt % (bk, bkms[bk]))
787 print(fmt % (bk, bkms[bk]))
788 else:
788 else:
789 if not args:
789 if not args:
790 raise UsageError("%bookmark: You must specify the bookmark name")
790 raise UsageError("%bookmark: You must specify the bookmark name")
791 elif len(args)==1:
791 elif len(args)==1:
792 bkms[args[0]] = os.getcwd()
792 bkms[args[0]] = os.getcwd()
793 elif len(args)==2:
793 elif len(args)==2:
794 bkms[args[0]] = args[1]
794 bkms[args[0]] = args[1]
795 self.shell.db['bookmarks'] = bkms
795 self.shell.db['bookmarks'] = bkms
796
796
797 @line_magic
797 @line_magic
798 def pycat(self, parameter_s=''):
798 def pycat(self, parameter_s=''):
799 """Show a syntax-highlighted file through a pager.
799 """Show a syntax-highlighted file through a pager.
800
800
801 This magic is similar to the cat utility, but it will assume the file
801 This magic is similar to the cat utility, but it will assume the file
802 to be Python source and will show it with syntax highlighting.
802 to be Python source and will show it with syntax highlighting.
803
803
804 This magic command can either take a local filename, an url,
804 This magic command can either take a local filename, an url,
805 an history range (see %history) or a macro as argument.
805 an history range (see %history) or a macro as argument.
806
806
807 If no parameter is given, prints out history of current session up to
807 If no parameter is given, prints out history of current session up to
808 this point. ::
808 this point. ::
809
809
810 %pycat myscript.py
810 %pycat myscript.py
811 %pycat 7-27
811 %pycat 7-27
812 %pycat myMacro
812 %pycat myMacro
813 %pycat http://www.example.com/myscript.py
813 %pycat http://www.example.com/myscript.py
814 """
814 """
815 try:
815 try:
816 cont = self.shell.find_user_code(parameter_s, skip_encoding_cookie=False)
816 cont = self.shell.find_user_code(parameter_s, skip_encoding_cookie=False)
817 except (ValueError, IOError):
817 except (ValueError, IOError):
818 print("Error: no such file, variable, URL, history range or macro")
818 print("Error: no such file, variable, URL, history range or macro")
819 return
819 return
820
820
821 page.page(self.shell.pycolorize(source_to_unicode(cont)))
821 page.page(self.shell.pycolorize(source_to_unicode(cont)))
822
822
823 @magic_arguments.magic_arguments()
823 @magic_arguments.magic_arguments()
824 @magic_arguments.argument(
824 @magic_arguments.argument(
825 '-a', '--append', action='store_true', default=False,
825 '-a', '--append', action='store_true', default=False,
826 help='Append contents of the cell to an existing file. '
826 help='Append contents of the cell to an existing file. '
827 'The file will be created if it does not exist.'
827 'The file will be created if it does not exist.'
828 )
828 )
829 @magic_arguments.argument(
829 @magic_arguments.argument(
830 'filename', type=str,
830 'filename', type=str,
831 help='file to write'
831 help='file to write'
832 )
832 )
833 @cell_magic
833 @cell_magic
834 def writefile(self, line, cell):
834 def writefile(self, line, cell):
835 """Write the contents of the cell to a file.
835 """Write the contents of the cell to a file.
836
836
837 The file will be overwritten unless the -a (--append) flag is specified.
837 The file will be overwritten unless the -a (--append) flag is specified.
838 """
838 """
839 args = magic_arguments.parse_argstring(self.writefile, line)
839 args = magic_arguments.parse_argstring(self.writefile, line)
840 if re.match(r'^(\'.*\')|(".*")$', args.filename):
840 if re.match(r'^(\'.*\')|(".*")$', args.filename):
841 filename = os.path.expanduser(args.filename[1:-1])
841 filename = os.path.expanduser(args.filename[1:-1])
842 else:
842 else:
843 filename = os.path.expanduser(args.filename)
843 filename = os.path.expanduser(args.filename)
844
844
845 if os.path.exists(filename):
845 if os.path.exists(filename):
846 if args.append:
846 if args.append:
847 print("Appending to %s" % filename)
847 print("Appending to %s" % filename)
848 else:
848 else:
849 print("Overwriting %s" % filename)
849 print("Overwriting %s" % filename)
850 else:
850 else:
851 print("Writing %s" % filename)
851 print("Writing %s" % filename)
852
852
853 mode = 'a' if args.append else 'w'
853 mode = 'a' if args.append else 'w'
854 with io.open(filename, mode, encoding='utf-8') as f:
854 with io.open(filename, mode, encoding='utf-8') as f:
855 f.write(cell)
855 f.write(cell)
@@ -1,1209 +1,1210
1 """Tools for inspecting Python objects.
1 """Tools for inspecting Python objects.
2
2
3 Uses syntax highlighting for presenting the various information elements.
3 Uses syntax highlighting for presenting the various information elements.
4
4
5 Similar in spirit to the inspect module, but all calls take a name argument to
5 Similar in spirit to the inspect module, but all calls take a name argument to
6 reference the name under which an object is being read.
6 reference the name under which an object is being read.
7 """
7 """
8
8
9 # Copyright (c) IPython Development Team.
9 # Copyright (c) IPython Development Team.
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11
11
12 __all__ = ['Inspector','InspectColors']
12 __all__ = ['Inspector','InspectColors']
13
13
14 # stdlib modules
14 # stdlib modules
15 from dataclasses import dataclass
15 from dataclasses import dataclass
16 from inspect import signature
16 from inspect import signature
17 from textwrap import dedent
17 from textwrap import dedent
18 import ast
18 import ast
19 import html
19 import html
20 import inspect
20 import inspect
21 import io as stdlib_io
21 import io as stdlib_io
22 import linecache
22 import linecache
23 import os
23 import os
24 import types
24 import types
25 import warnings
25 import warnings
26
26
27
27
28 from typing import (
28 from typing import (
29 cast,
29 cast,
30 Any,
30 Any,
31 Optional,
31 Optional,
32 Dict,
32 Dict,
33 Union,
33 Union,
34 List,
34 List,
35 TypedDict,
35 TypedDict,
36 TypeAlias,
36 TypeAlias,
37 Tuple,
37 Tuple,
38 )
38 )
39
39
40 import traitlets
40 import traitlets
41
41
42 # IPython's own
42 # IPython's own
43 from IPython.core import page
43 from IPython.core import page
44 from IPython.lib.pretty import pretty
44 from IPython.lib.pretty import pretty
45 from IPython.testing.skipdoctest import skip_doctest
45 from IPython.testing.skipdoctest import skip_doctest
46 from IPython.utils import PyColorize, openpy
46 from IPython.utils import PyColorize, openpy
47 from IPython.utils.dir2 import safe_hasattr
47 from IPython.utils.dir2 import safe_hasattr
48 from IPython.utils.path import compress_user
48 from IPython.utils.path import compress_user
49 from IPython.utils.text import indent
49 from IPython.utils.text import indent
50 from IPython.utils.wildcard import list_namespace, typestr2type
50 from IPython.utils.wildcard import list_namespace, typestr2type
51 from IPython.utils.coloransi import TermColors
51 from IPython.utils.coloransi import TermColors
52 from IPython.utils.colorable import Colorable
52 from IPython.utils.colorable import Colorable
53 from IPython.utils.decorators import undoc
53 from IPython.utils.decorators import undoc
54
54
55 from pygments import highlight
55 from pygments import highlight
56 from pygments.lexers import PythonLexer
56 from pygments.lexers import PythonLexer
57 from pygments.formatters import HtmlFormatter
57 from pygments.formatters import HtmlFormatter
58
58
59 HOOK_NAME = "__custom_documentations__"
59 HOOK_NAME = "__custom_documentations__"
60
60
61
61
62 UnformattedBundle: TypeAlias = Dict[str, List[Tuple[str, str]]] # List of (title, body)
62 UnformattedBundle: TypeAlias = Dict[str, List[Tuple[str, str]]] # List of (title, body)
63 Bundle: TypeAlias = Dict[str, str]
63 Bundle: TypeAlias = Dict[str, str]
64
64
65
65
66 @dataclass
66 @dataclass
67 class OInfo:
67 class OInfo:
68 ismagic: bool
68 ismagic: bool
69 isalias: bool
69 isalias: bool
70 found: bool
70 found: bool
71 namespace: Optional[str]
71 namespace: Optional[str]
72 parent: Any
72 parent: Any
73 obj: Any
73 obj: Any
74
74
75 def get(self, field):
75 def get(self, field):
76 """Get a field from the object for backward compatibility with before 8.12
76 """Get a field from the object for backward compatibility with before 8.12
77
77
78 see https://github.com/h5py/h5py/issues/2253
78 see https://github.com/h5py/h5py/issues/2253
79 """
79 """
80 # We need to deprecate this at some point, but the warning will show in completion.
80 # We need to deprecate this at some point, but the warning will show in completion.
81 # Let's comment this for now and uncomment end of 2023 ish
81 # Let's comment this for now and uncomment end of 2023 ish
82 # warnings.warn(
82 # warnings.warn(
83 # f"OInfo dataclass with fields access since IPython 8.12 please use OInfo.{field} instead."
83 # f"OInfo dataclass with fields access since IPython 8.12 please use OInfo.{field} instead."
84 # "OInfo used to be a dict but a dataclass provide static fields verification with mypy."
84 # "OInfo used to be a dict but a dataclass provide static fields verification with mypy."
85 # "This warning and backward compatibility `get()` method were added in 8.13.",
85 # "This warning and backward compatibility `get()` method were added in 8.13.",
86 # DeprecationWarning,
86 # DeprecationWarning,
87 # stacklevel=2,
87 # stacklevel=2,
88 # )
88 # )
89 return getattr(self, field)
89 return getattr(self, field)
90
90
91
91
92 def pylight(code):
92 def pylight(code):
93 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
93 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
94
94
95 # builtin docstrings to ignore
95 # builtin docstrings to ignore
96 _func_call_docstring = types.FunctionType.__call__.__doc__
96 _func_call_docstring = types.FunctionType.__call__.__doc__
97 _object_init_docstring = object.__init__.__doc__
97 _object_init_docstring = object.__init__.__doc__
98 _builtin_type_docstrings = {
98 _builtin_type_docstrings = {
99 inspect.getdoc(t) for t in (types.ModuleType, types.MethodType,
99 inspect.getdoc(t) for t in (types.ModuleType, types.MethodType,
100 types.FunctionType, property)
100 types.FunctionType, property)
101 }
101 }
102
102
103 _builtin_func_type = type(all)
103 _builtin_func_type = type(all)
104 _builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions
104 _builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions
105 #****************************************************************************
105 #****************************************************************************
106 # Builtin color schemes
106 # Builtin color schemes
107
107
108 Colors = TermColors # just a shorthand
108 Colors = TermColors # just a shorthand
109
109
110 InspectColors = PyColorize.ANSICodeColors
110 InspectColors = PyColorize.ANSICodeColors
111
111
112 #****************************************************************************
112 #****************************************************************************
113 # Auxiliary functions and objects
113 # Auxiliary functions and objects
114
114
115
115
116 class InfoDict(TypedDict):
116 class InfoDict(TypedDict):
117 type_name: Optional[str]
117 type_name: Optional[str]
118 base_class: Optional[str]
118 base_class: Optional[str]
119 string_form: Optional[str]
119 string_form: Optional[str]
120 namespace: Optional[str]
120 namespace: Optional[str]
121 length: Optional[str]
121 length: Optional[str]
122 file: Optional[str]
122 file: Optional[str]
123 definition: Optional[str]
123 definition: Optional[str]
124 docstring: Optional[str]
124 docstring: Optional[str]
125 source: Optional[str]
125 source: Optional[str]
126 init_definition: Optional[str]
126 init_definition: Optional[str]
127 class_docstring: Optional[str]
127 class_docstring: Optional[str]
128 init_docstring: Optional[str]
128 init_docstring: Optional[str]
129 call_def: Optional[str]
129 call_def: Optional[str]
130 call_docstring: Optional[str]
130 call_docstring: Optional[str]
131 subclasses: Optional[str]
131 subclasses: Optional[str]
132 # These won't be printed but will be used to determine how to
132 # These won't be printed but will be used to determine how to
133 # format the object
133 # format the object
134 ismagic: bool
134 ismagic: bool
135 isalias: bool
135 isalias: bool
136 isclass: bool
136 isclass: bool
137 found: bool
137 found: bool
138 name: str
138 name: str
139
139
140
140
141 _info_fields = list(InfoDict.__annotations__.keys())
141 _info_fields = list(InfoDict.__annotations__.keys())
142
142
143
143
144 def __getattr__(name):
144 def __getattr__(name):
145 if name == "info_fields":
145 if name == "info_fields":
146 warnings.warn(
146 warnings.warn(
147 "IPython.core.oinspect's `info_fields` is considered for deprecation and may be removed in the Future. ",
147 "IPython.core.oinspect's `info_fields` is considered for deprecation and may be removed in the Future. ",
148 DeprecationWarning,
148 DeprecationWarning,
149 stacklevel=2,
149 stacklevel=2,
150 )
150 )
151 return _info_fields
151 return _info_fields
152
152
153 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
153 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
154
154
155
155
156 @dataclass
156 @dataclass
157 class InspectorHookData:
157 class InspectorHookData:
158 """Data passed to the mime hook"""
158 """Data passed to the mime hook"""
159
159
160 obj: Any
160 obj: Any
161 info: Optional[OInfo]
161 info: Optional[OInfo]
162 info_dict: InfoDict
162 info_dict: InfoDict
163 detail_level: int
163 detail_level: int
164 omit_sections: list[str]
164 omit_sections: list[str]
165
165
166
166
167 @undoc
167 @undoc
168 def object_info(
168 def object_info(
169 *,
169 *,
170 name: str,
170 name: str,
171 found: bool,
171 found: bool,
172 isclass: bool = False,
172 isclass: bool = False,
173 isalias: bool = False,
173 isalias: bool = False,
174 ismagic: bool = False,
174 ismagic: bool = False,
175 **kw,
175 **kw,
176 ) -> InfoDict:
176 ) -> InfoDict:
177 """Make an object info dict with all fields present."""
177 """Make an object info dict with all fields present."""
178 infodict = kw
178 infodict = kw
179 infodict = {k: None for k in _info_fields if k not in infodict}
179 infodict = {k: None for k in _info_fields if k not in infodict}
180 infodict["name"] = name # type: ignore
180 infodict["name"] = name # type: ignore
181 infodict["found"] = found # type: ignore
181 infodict["found"] = found # type: ignore
182 infodict["isclass"] = isclass # type: ignore
182 infodict["isclass"] = isclass # type: ignore
183 infodict["isalias"] = isalias # type: ignore
183 infodict["isalias"] = isalias # type: ignore
184 infodict["ismagic"] = ismagic # type: ignore
184 infodict["ismagic"] = ismagic # type: ignore
185
185
186 return InfoDict(**infodict) # type:ignore
186 return InfoDict(**infodict) # type:ignore
187
187
188
188
189 def get_encoding(obj):
189 def get_encoding(obj):
190 """Get encoding for python source file defining obj
190 """Get encoding for python source file defining obj
191
191
192 Returns None if obj is not defined in a sourcefile.
192 Returns None if obj is not defined in a sourcefile.
193 """
193 """
194 ofile = find_file(obj)
194 ofile = find_file(obj)
195 # run contents of file through pager starting at line where the object
195 # run contents of file through pager starting at line where the object
196 # is defined, as long as the file isn't binary and is actually on the
196 # is defined, as long as the file isn't binary and is actually on the
197 # filesystem.
197 # filesystem.
198 if ofile is None:
198 if ofile is None:
199 return None
199 return None
200 elif ofile.endswith(('.so', '.dll', '.pyd')):
200 elif ofile.endswith(('.so', '.dll', '.pyd')):
201 return None
201 return None
202 elif not os.path.isfile(ofile):
202 elif not os.path.isfile(ofile):
203 return None
203 return None
204 else:
204 else:
205 # Print only text files, not extension binaries. Note that
205 # Print only text files, not extension binaries. Note that
206 # getsourcelines returns lineno with 1-offset and page() uses
206 # getsourcelines returns lineno with 1-offset and page() uses
207 # 0-offset, so we must adjust.
207 # 0-offset, so we must adjust.
208 with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2
208 with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2
209 encoding, _lines = openpy.detect_encoding(buffer.readline)
209 encoding, _lines = openpy.detect_encoding(buffer.readline)
210 return encoding
210 return encoding
211
211
212
212
213 def getdoc(obj) -> Union[str, None]:
213 def getdoc(obj) -> Union[str, None]:
214 """Stable wrapper around inspect.getdoc.
214 """Stable wrapper around inspect.getdoc.
215
215
216 This can't crash because of attribute problems.
216 This can't crash because of attribute problems.
217
217
218 It also attempts to call a getdoc() method on the given object. This
218 It also attempts to call a getdoc() method on the given object. This
219 allows objects which provide their docstrings via non-standard mechanisms
219 allows objects which provide their docstrings via non-standard mechanisms
220 (like Pyro proxies) to still be inspected by ipython's ? system.
220 (like Pyro proxies) to still be inspected by ipython's ? system.
221 """
221 """
222 # Allow objects to offer customized documentation via a getdoc method:
222 # Allow objects to offer customized documentation via a getdoc method:
223 try:
223 try:
224 ds = obj.getdoc()
224 ds = obj.getdoc()
225 except Exception:
225 except Exception:
226 pass
226 pass
227 else:
227 else:
228 if isinstance(ds, str):
228 if isinstance(ds, str):
229 return inspect.cleandoc(ds)
229 return inspect.cleandoc(ds)
230 docstr = inspect.getdoc(obj)
230 docstr = inspect.getdoc(obj)
231 return docstr
231 return docstr
232
232
233
233
234 def getsource(obj, oname='') -> Union[str,None]:
234 def getsource(obj, oname='') -> Union[str,None]:
235 """Wrapper around inspect.getsource.
235 """Wrapper around inspect.getsource.
236
236
237 This can be modified by other projects to provide customized source
237 This can be modified by other projects to provide customized source
238 extraction.
238 extraction.
239
239
240 Parameters
240 Parameters
241 ----------
241 ----------
242 obj : object
242 obj : object
243 an object whose source code we will attempt to extract
243 an object whose source code we will attempt to extract
244 oname : str
244 oname : str
245 (optional) a name under which the object is known
245 (optional) a name under which the object is known
246
246
247 Returns
247 Returns
248 -------
248 -------
249 src : unicode or None
249 src : unicode or None
250
250
251 """
251 """
252
252
253 if isinstance(obj, property):
253 if isinstance(obj, property):
254 sources = []
254 sources = []
255 for attrname in ['fget', 'fset', 'fdel']:
255 for attrname in ['fget', 'fset', 'fdel']:
256 fn = getattr(obj, attrname)
256 fn = getattr(obj, attrname)
257 if fn is not None:
257 if fn is not None:
258 encoding = get_encoding(fn)
258 oname_prefix = ('%s.' % oname) if oname else ''
259 oname_prefix = ('%s.' % oname) if oname else ''
259 sources.append(''.join(('# ', oname_prefix, attrname)))
260 sources.append(''.join(('# ', oname_prefix, attrname)))
260 if inspect.isfunction(fn):
261 if inspect.isfunction(fn):
261 _src = getsource(fn)
262 _src = getsource(fn)
262 if _src:
263 if _src:
263 # assert _src is not None, "please mypy"
264 # assert _src is not None, "please mypy"
264 sources.append(dedent(_src))
265 sources.append(dedent(_src))
265 else:
266 else:
266 # Default str/repr only prints function name,
267 # Default str/repr only prints function name,
267 # pretty.pretty prints module name too.
268 # pretty.pretty prints module name too.
268 sources.append(
269 sources.append(
269 '%s%s = %s\n' % (oname_prefix, attrname, pretty(fn))
270 '%s%s = %s\n' % (oname_prefix, attrname, pretty(fn))
270 )
271 )
271 if sources:
272 if sources:
272 return '\n'.join(sources)
273 return '\n'.join(sources)
273 else:
274 else:
274 return None
275 return None
275
276
276 else:
277 else:
277 # Get source for non-property objects.
278 # Get source for non-property objects.
278
279
279 obj = _get_wrapped(obj)
280 obj = _get_wrapped(obj)
280
281
281 try:
282 try:
282 src = inspect.getsource(obj)
283 src = inspect.getsource(obj)
283 except TypeError:
284 except TypeError:
284 # The object itself provided no meaningful source, try looking for
285 # The object itself provided no meaningful source, try looking for
285 # its class definition instead.
286 # its class definition instead.
286 try:
287 try:
287 src = inspect.getsource(obj.__class__)
288 src = inspect.getsource(obj.__class__)
288 except (OSError, TypeError):
289 except (OSError, TypeError):
289 return None
290 return None
290 except OSError:
291 except OSError:
291 return None
292 return None
292
293
293 return src
294 return src
294
295
295
296
296 def is_simple_callable(obj):
297 def is_simple_callable(obj):
297 """True if obj is a function ()"""
298 """True if obj is a function ()"""
298 return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
299 return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
299 isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))
300 isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))
300
301
301 def _get_wrapped(obj):
302 def _get_wrapped(obj):
302 """Get the original object if wrapped in one or more @decorators
303 """Get the original object if wrapped in one or more @decorators
303
304
304 Some objects automatically construct similar objects on any unrecognised
305 Some objects automatically construct similar objects on any unrecognised
305 attribute access (e.g. unittest.mock.call). To protect against infinite loops,
306 attribute access (e.g. unittest.mock.call). To protect against infinite loops,
306 this will arbitrarily cut off after 100 levels of obj.__wrapped__
307 this will arbitrarily cut off after 100 levels of obj.__wrapped__
307 attribute access. --TK, Jan 2016
308 attribute access. --TK, Jan 2016
308 """
309 """
309 orig_obj = obj
310 orig_obj = obj
310 i = 0
311 i = 0
311 while safe_hasattr(obj, '__wrapped__'):
312 while safe_hasattr(obj, '__wrapped__'):
312 obj = obj.__wrapped__
313 obj = obj.__wrapped__
313 i += 1
314 i += 1
314 if i > 100:
315 if i > 100:
315 # __wrapped__ is probably a lie, so return the thing we started with
316 # __wrapped__ is probably a lie, so return the thing we started with
316 return orig_obj
317 return orig_obj
317 return obj
318 return obj
318
319
319 def find_file(obj) -> Optional[str]:
320 def find_file(obj) -> Optional[str]:
320 """Find the absolute path to the file where an object was defined.
321 """Find the absolute path to the file where an object was defined.
321
322
322 This is essentially a robust wrapper around `inspect.getabsfile`.
323 This is essentially a robust wrapper around `inspect.getabsfile`.
323
324
324 Returns None if no file can be found.
325 Returns None if no file can be found.
325
326
326 Parameters
327 Parameters
327 ----------
328 ----------
328 obj : any Python object
329 obj : any Python object
329
330
330 Returns
331 Returns
331 -------
332 -------
332 fname : str
333 fname : str
333 The absolute path to the file where the object was defined.
334 The absolute path to the file where the object was defined.
334 """
335 """
335 obj = _get_wrapped(obj)
336 obj = _get_wrapped(obj)
336
337
337 fname: Optional[str] = None
338 fname: Optional[str] = None
338 try:
339 try:
339 fname = inspect.getabsfile(obj)
340 fname = inspect.getabsfile(obj)
340 except TypeError:
341 except TypeError:
341 # For an instance, the file that matters is where its class was
342 # For an instance, the file that matters is where its class was
342 # declared.
343 # declared.
343 try:
344 try:
344 fname = inspect.getabsfile(obj.__class__)
345 fname = inspect.getabsfile(obj.__class__)
345 except (OSError, TypeError):
346 except (OSError, TypeError):
346 # Can happen for builtins
347 # Can happen for builtins
347 pass
348 pass
348 except OSError:
349 except OSError:
349 pass
350 pass
350
351
351 return fname
352 return fname
352
353
353
354
354 def find_source_lines(obj):
355 def find_source_lines(obj):
355 """Find the line number in a file where an object was defined.
356 """Find the line number in a file where an object was defined.
356
357
357 This is essentially a robust wrapper around `inspect.getsourcelines`.
358 This is essentially a robust wrapper around `inspect.getsourcelines`.
358
359
359 Returns None if no file can be found.
360 Returns None if no file can be found.
360
361
361 Parameters
362 Parameters
362 ----------
363 ----------
363 obj : any Python object
364 obj : any Python object
364
365
365 Returns
366 Returns
366 -------
367 -------
367 lineno : int
368 lineno : int
368 The line number where the object definition starts.
369 The line number where the object definition starts.
369 """
370 """
370 obj = _get_wrapped(obj)
371 obj = _get_wrapped(obj)
371
372
372 try:
373 try:
373 lineno = inspect.getsourcelines(obj)[1]
374 lineno = inspect.getsourcelines(obj)[1]
374 except TypeError:
375 except TypeError:
375 # For instances, try the class object like getsource() does
376 # For instances, try the class object like getsource() does
376 try:
377 try:
377 lineno = inspect.getsourcelines(obj.__class__)[1]
378 lineno = inspect.getsourcelines(obj.__class__)[1]
378 except (OSError, TypeError):
379 except (OSError, TypeError):
379 return None
380 return None
380 except OSError:
381 except OSError:
381 return None
382 return None
382
383
383 return lineno
384 return lineno
384
385
385 class Inspector(Colorable):
386 class Inspector(Colorable):
386
387
387 mime_hooks = traitlets.Dict(
388 mime_hooks = traitlets.Dict(
388 config=True,
389 config=True,
389 help="dictionary of mime to callable to add information into help mimebundle dict",
390 help="dictionary of mime to callable to add information into help mimebundle dict",
390 ).tag(config=True)
391 ).tag(config=True)
391
392
392 def __init__(
393 def __init__(
393 self,
394 self,
394 color_table=InspectColors,
395 color_table=InspectColors,
395 code_color_table=PyColorize.ANSICodeColors,
396 code_color_table=PyColorize.ANSICodeColors,
396 scheme=None,
397 scheme=None,
397 str_detail_level=0,
398 str_detail_level=0,
398 parent=None,
399 parent=None,
399 config=None,
400 config=None,
400 ):
401 ):
401 super(Inspector, self).__init__(parent=parent, config=config)
402 super(Inspector, self).__init__(parent=parent, config=config)
402 self.color_table = color_table
403 self.color_table = color_table
403 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
404 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
404 self.format = self.parser.format
405 self.format = self.parser.format
405 self.str_detail_level = str_detail_level
406 self.str_detail_level = str_detail_level
406 self.set_active_scheme(scheme)
407 self.set_active_scheme(scheme)
407
408
408 def _getdef(self,obj,oname='') -> Union[str,None]:
409 def _getdef(self,obj,oname='') -> Union[str,None]:
409 """Return the call signature for any callable object.
410 """Return the call signature for any callable object.
410
411
411 If any exception is generated, None is returned instead and the
412 If any exception is generated, None is returned instead and the
412 exception is suppressed."""
413 exception is suppressed."""
413 if not callable(obj):
414 if not callable(obj):
414 return None
415 return None
415 try:
416 try:
416 return _render_signature(signature(obj), oname)
417 return _render_signature(signature(obj), oname)
417 except:
418 except:
418 return None
419 return None
419
420
420 def __head(self,h) -> str:
421 def __head(self,h) -> str:
421 """Return a header string with proper colors."""
422 """Return a header string with proper colors."""
422 return '%s%s%s' % (self.color_table.active_colors.header,h,
423 return '%s%s%s' % (self.color_table.active_colors.header,h,
423 self.color_table.active_colors.normal)
424 self.color_table.active_colors.normal)
424
425
425 def set_active_scheme(self, scheme):
426 def set_active_scheme(self, scheme):
426 if scheme is not None:
427 if scheme is not None:
427 self.color_table.set_active_scheme(scheme)
428 self.color_table.set_active_scheme(scheme)
428 self.parser.color_table.set_active_scheme(scheme)
429 self.parser.color_table.set_active_scheme(scheme)
429
430
430 def noinfo(self, msg, oname):
431 def noinfo(self, msg, oname):
431 """Generic message when no information is found."""
432 """Generic message when no information is found."""
432 print('No %s found' % msg, end=' ')
433 print('No %s found' % msg, end=' ')
433 if oname:
434 if oname:
434 print('for %s' % oname)
435 print('for %s' % oname)
435 else:
436 else:
436 print()
437 print()
437
438
438 def pdef(self, obj, oname=''):
439 def pdef(self, obj, oname=''):
439 """Print the call signature for any callable object.
440 """Print the call signature for any callable object.
440
441
441 If the object is a class, print the constructor information."""
442 If the object is a class, print the constructor information."""
442
443
443 if not callable(obj):
444 if not callable(obj):
444 print('Object is not callable.')
445 print('Object is not callable.')
445 return
446 return
446
447
447 header = ''
448 header = ''
448
449
449 if inspect.isclass(obj):
450 if inspect.isclass(obj):
450 header = self.__head('Class constructor information:\n')
451 header = self.__head('Class constructor information:\n')
451
452
452
453
453 output = self._getdef(obj,oname)
454 output = self._getdef(obj,oname)
454 if output is None:
455 if output is None:
455 self.noinfo('definition header',oname)
456 self.noinfo('definition header',oname)
456 else:
457 else:
457 print(header,self.format(output), end=' ')
458 print(header,self.format(output), end=' ')
458
459
459 # In Python 3, all classes are new-style, so they all have __init__.
460 # In Python 3, all classes are new-style, so they all have __init__.
460 @skip_doctest
461 @skip_doctest
461 def pdoc(self, obj, oname='', formatter=None):
462 def pdoc(self, obj, oname='', formatter=None):
462 """Print the docstring for any object.
463 """Print the docstring for any object.
463
464
464 Optional:
465 Optional:
465 -formatter: a function to run the docstring through for specially
466 -formatter: a function to run the docstring through for specially
466 formatted docstrings.
467 formatted docstrings.
467
468
468 Examples
469 Examples
469 --------
470 --------
470 In [1]: class NoInit:
471 In [1]: class NoInit:
471 ...: pass
472 ...: pass
472
473
473 In [2]: class NoDoc:
474 In [2]: class NoDoc:
474 ...: def __init__(self):
475 ...: def __init__(self):
475 ...: pass
476 ...: pass
476
477
477 In [3]: %pdoc NoDoc
478 In [3]: %pdoc NoDoc
478 No documentation found for NoDoc
479 No documentation found for NoDoc
479
480
480 In [4]: %pdoc NoInit
481 In [4]: %pdoc NoInit
481 No documentation found for NoInit
482 No documentation found for NoInit
482
483
483 In [5]: obj = NoInit()
484 In [5]: obj = NoInit()
484
485
485 In [6]: %pdoc obj
486 In [6]: %pdoc obj
486 No documentation found for obj
487 No documentation found for obj
487
488
488 In [5]: obj2 = NoDoc()
489 In [5]: obj2 = NoDoc()
489
490
490 In [6]: %pdoc obj2
491 In [6]: %pdoc obj2
491 No documentation found for obj2
492 No documentation found for obj2
492 """
493 """
493
494
494 head = self.__head # For convenience
495 head = self.__head # For convenience
495 lines = []
496 lines = []
496 ds = getdoc(obj)
497 ds = getdoc(obj)
497 if formatter:
498 if formatter:
498 ds = formatter(ds).get('plain/text', ds)
499 ds = formatter(ds).get('plain/text', ds)
499 if ds:
500 if ds:
500 lines.append(head("Class docstring:"))
501 lines.append(head("Class docstring:"))
501 lines.append(indent(ds))
502 lines.append(indent(ds))
502 if inspect.isclass(obj) and hasattr(obj, '__init__'):
503 if inspect.isclass(obj) and hasattr(obj, '__init__'):
503 init_ds = getdoc(obj.__init__)
504 init_ds = getdoc(obj.__init__)
504 if init_ds is not None:
505 if init_ds is not None:
505 lines.append(head("Init docstring:"))
506 lines.append(head("Init docstring:"))
506 lines.append(indent(init_ds))
507 lines.append(indent(init_ds))
507 elif hasattr(obj,'__call__'):
508 elif hasattr(obj,'__call__'):
508 call_ds = getdoc(obj.__call__)
509 call_ds = getdoc(obj.__call__)
509 if call_ds:
510 if call_ds:
510 lines.append(head("Call docstring:"))
511 lines.append(head("Call docstring:"))
511 lines.append(indent(call_ds))
512 lines.append(indent(call_ds))
512
513
513 if not lines:
514 if not lines:
514 self.noinfo('documentation',oname)
515 self.noinfo('documentation',oname)
515 else:
516 else:
516 page.page('\n'.join(lines))
517 page.page('\n'.join(lines))
517
518
518 def psource(self, obj, oname=''):
519 def psource(self, obj, oname=''):
519 """Print the source code for an object."""
520 """Print the source code for an object."""
520
521
521 # Flush the source cache because inspect can return out-of-date source
522 # Flush the source cache because inspect can return out-of-date source
522 linecache.checkcache()
523 linecache.checkcache()
523 try:
524 try:
524 src = getsource(obj, oname=oname)
525 src = getsource(obj, oname=oname)
525 except Exception:
526 except Exception:
526 src = None
527 src = None
527
528
528 if src is None:
529 if src is None:
529 self.noinfo('source', oname)
530 self.noinfo('source', oname)
530 else:
531 else:
531 page.page(self.format(src))
532 page.page(self.format(src))
532
533
533 def pfile(self, obj, oname=''):
534 def pfile(self, obj, oname=''):
534 """Show the whole file where an object was defined."""
535 """Show the whole file where an object was defined."""
535
536
536 lineno = find_source_lines(obj)
537 lineno = find_source_lines(obj)
537 if lineno is None:
538 if lineno is None:
538 self.noinfo('file', oname)
539 self.noinfo('file', oname)
539 return
540 return
540
541
541 ofile = find_file(obj)
542 ofile = find_file(obj)
542 # run contents of file through pager starting at line where the object
543 # run contents of file through pager starting at line where the object
543 # is defined, as long as the file isn't binary and is actually on the
544 # is defined, as long as the file isn't binary and is actually on the
544 # filesystem.
545 # filesystem.
545 if ofile is None:
546 if ofile is None:
546 print("Could not find file for object")
547 print("Could not find file for object")
547 elif ofile.endswith((".so", ".dll", ".pyd")):
548 elif ofile.endswith((".so", ".dll", ".pyd")):
548 print("File %r is binary, not printing." % ofile)
549 print("File %r is binary, not printing." % ofile)
549 elif not os.path.isfile(ofile):
550 elif not os.path.isfile(ofile):
550 print('File %r does not exist, not printing.' % ofile)
551 print('File %r does not exist, not printing.' % ofile)
551 else:
552 else:
552 # Print only text files, not extension binaries. Note that
553 # Print only text files, not extension binaries. Note that
553 # getsourcelines returns lineno with 1-offset and page() uses
554 # getsourcelines returns lineno with 1-offset and page() uses
554 # 0-offset, so we must adjust.
555 # 0-offset, so we must adjust.
555 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
556 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
556
557
557
558
558 def _mime_format(self, text:str, formatter=None) -> dict:
559 def _mime_format(self, text:str, formatter=None) -> dict:
559 """Return a mime bundle representation of the input text.
560 """Return a mime bundle representation of the input text.
560
561
561 - if `formatter` is None, the returned mime bundle has
562 - if `formatter` is None, the returned mime bundle has
562 a ``text/plain`` field, with the input text.
563 a ``text/plain`` field, with the input text.
563 a ``text/html`` field with a ``<pre>`` tag containing the input text.
564 a ``text/html`` field with a ``<pre>`` tag containing the input text.
564
565
565 - if ``formatter`` is not None, it must be a callable transforming the
566 - if ``formatter`` is not None, it must be a callable transforming the
566 input text into a mime bundle. Default values for ``text/plain`` and
567 input text into a mime bundle. Default values for ``text/plain`` and
567 ``text/html`` representations are the ones described above.
568 ``text/html`` representations are the ones described above.
568
569
569 Note:
570 Note:
570
571
571 Formatters returning strings are supported but this behavior is deprecated.
572 Formatters returning strings are supported but this behavior is deprecated.
572
573
573 """
574 """
574 defaults = {
575 defaults = {
575 "text/plain": text,
576 "text/plain": text,
576 "text/html": f"<pre>{html.escape(text)}</pre>",
577 "text/html": f"<pre>{html.escape(text)}</pre>",
577 }
578 }
578
579
579 if formatter is None:
580 if formatter is None:
580 return defaults
581 return defaults
581 else:
582 else:
582 formatted = formatter(text)
583 formatted = formatter(text)
583
584
584 if not isinstance(formatted, dict):
585 if not isinstance(formatted, dict):
585 # Handle the deprecated behavior of a formatter returning
586 # Handle the deprecated behavior of a formatter returning
586 # a string instead of a mime bundle.
587 # a string instead of a mime bundle.
587 return {"text/plain": formatted, "text/html": f"<pre>{formatted}</pre>"}
588 return {"text/plain": formatted, "text/html": f"<pre>{formatted}</pre>"}
588
589
589 else:
590 else:
590 return dict(defaults, **formatted)
591 return dict(defaults, **formatted)
591
592
592 def format_mime(self, bundle: UnformattedBundle) -> Bundle:
593 def format_mime(self, bundle: UnformattedBundle) -> Bundle:
593 """Format a mimebundle being created by _make_info_unformatted into a real mimebundle"""
594 """Format a mimebundle being created by _make_info_unformatted into a real mimebundle"""
594 # Format text/plain mimetype
595 # Format text/plain mimetype
595 assert isinstance(bundle["text/plain"], list)
596 assert isinstance(bundle["text/plain"], list)
596 for item in bundle["text/plain"]:
597 for item in bundle["text/plain"]:
597 assert isinstance(item, tuple)
598 assert isinstance(item, tuple)
598
599
599 new_b: Bundle = {}
600 new_b: Bundle = {}
600 lines = []
601 lines = []
601 _len = max(len(h) for h, _ in bundle["text/plain"])
602 _len = max(len(h) for h, _ in bundle["text/plain"])
602
603
603 for head, body in bundle["text/plain"]:
604 for head, body in bundle["text/plain"]:
604 body = body.strip("\n")
605 body = body.strip("\n")
605 delim = "\n" if "\n" in body else " "
606 delim = "\n" if "\n" in body else " "
606 lines.append(
607 lines.append(
607 f"{self.__head(head+':')}{(_len - len(head))*' '}{delim}{body}"
608 f"{self.__head(head+':')}{(_len - len(head))*' '}{delim}{body}"
608 )
609 )
609
610
610 new_b["text/plain"] = "\n".join(lines)
611 new_b["text/plain"] = "\n".join(lines)
611
612
612 if "text/html" in bundle:
613 if "text/html" in bundle:
613 assert isinstance(bundle["text/html"], list)
614 assert isinstance(bundle["text/html"], list)
614 for item in bundle["text/html"]:
615 for item in bundle["text/html"]:
615 assert isinstance(item, tuple)
616 assert isinstance(item, tuple)
616 # Format the text/html mimetype
617 # Format the text/html mimetype
617 if isinstance(bundle["text/html"], (list, tuple)):
618 if isinstance(bundle["text/html"], (list, tuple)):
618 # bundle['text/html'] is a list of (head, formatted body) pairs
619 # bundle['text/html'] is a list of (head, formatted body) pairs
619 new_b["text/html"] = "\n".join(
620 new_b["text/html"] = "\n".join(
620 (f"<h1>{head}</h1>\n{body}" for (head, body) in bundle["text/html"])
621 (f"<h1>{head}</h1>\n{body}" for (head, body) in bundle["text/html"])
621 )
622 )
622
623
623 for k in bundle.keys():
624 for k in bundle.keys():
624 if k in ("text/html", "text/plain"):
625 if k in ("text/html", "text/plain"):
625 continue
626 continue
626 else:
627 else:
627 new_b[k] = bundle[k] # type:ignore
628 new_b[k] = bundle[k] # type:ignore
628 return new_b
629 return new_b
629
630
630 def _append_info_field(
631 def _append_info_field(
631 self,
632 self,
632 bundle: UnformattedBundle,
633 bundle: UnformattedBundle,
633 title: str,
634 title: str,
634 key: str,
635 key: str,
635 info,
636 info,
636 omit_sections: List[str],
637 omit_sections: List[str],
637 formatter,
638 formatter,
638 ):
639 ):
639 """Append an info value to the unformatted mimebundle being constructed by _make_info_unformatted"""
640 """Append an info value to the unformatted mimebundle being constructed by _make_info_unformatted"""
640 if title in omit_sections or key in omit_sections:
641 if title in omit_sections or key in omit_sections:
641 return
642 return
642 field = info[key]
643 field = info[key]
643 if field is not None:
644 if field is not None:
644 formatted_field = self._mime_format(field, formatter)
645 formatted_field = self._mime_format(field, formatter)
645 bundle["text/plain"].append((title, formatted_field["text/plain"]))
646 bundle["text/plain"].append((title, formatted_field["text/plain"]))
646 bundle["text/html"].append((title, formatted_field["text/html"]))
647 bundle["text/html"].append((title, formatted_field["text/html"]))
647
648
648 def _make_info_unformatted(
649 def _make_info_unformatted(
649 self, obj, info, formatter, detail_level, omit_sections
650 self, obj, info, formatter, detail_level, omit_sections
650 ) -> UnformattedBundle:
651 ) -> UnformattedBundle:
651 """Assemble the mimebundle as unformatted lists of information"""
652 """Assemble the mimebundle as unformatted lists of information"""
652 bundle: UnformattedBundle = {
653 bundle: UnformattedBundle = {
653 "text/plain": [],
654 "text/plain": [],
654 "text/html": [],
655 "text/html": [],
655 }
656 }
656
657
657 # A convenience function to simplify calls below
658 # A convenience function to simplify calls below
658 def append_field(
659 def append_field(
659 bundle: UnformattedBundle, title: str, key: str, formatter=None
660 bundle: UnformattedBundle, title: str, key: str, formatter=None
660 ):
661 ):
661 self._append_info_field(
662 self._append_info_field(
662 bundle,
663 bundle,
663 title=title,
664 title=title,
664 key=key,
665 key=key,
665 info=info,
666 info=info,
666 omit_sections=omit_sections,
667 omit_sections=omit_sections,
667 formatter=formatter,
668 formatter=formatter,
668 )
669 )
669
670
670 def code_formatter(text) -> Bundle:
671 def code_formatter(text) -> Bundle:
671 return {
672 return {
672 'text/plain': self.format(text),
673 'text/plain': self.format(text),
673 'text/html': pylight(text)
674 'text/html': pylight(text)
674 }
675 }
675
676
676 if info["isalias"]:
677 if info["isalias"]:
677 append_field(bundle, "Repr", "string_form")
678 append_field(bundle, "Repr", "string_form")
678
679
679 elif info['ismagic']:
680 elif info['ismagic']:
680 if detail_level > 0:
681 if detail_level > 0:
681 append_field(bundle, "Source", "source", code_formatter)
682 append_field(bundle, "Source", "source", code_formatter)
682 else:
683 else:
683 append_field(bundle, "Docstring", "docstring", formatter)
684 append_field(bundle, "Docstring", "docstring", formatter)
684 append_field(bundle, "File", "file")
685 append_field(bundle, "File", "file")
685
686
686 elif info['isclass'] or is_simple_callable(obj):
687 elif info['isclass'] or is_simple_callable(obj):
687 # Functions, methods, classes
688 # Functions, methods, classes
688 append_field(bundle, "Signature", "definition", code_formatter)
689 append_field(bundle, "Signature", "definition", code_formatter)
689 append_field(bundle, "Init signature", "init_definition", code_formatter)
690 append_field(bundle, "Init signature", "init_definition", code_formatter)
690 append_field(bundle, "Docstring", "docstring", formatter)
691 append_field(bundle, "Docstring", "docstring", formatter)
691 if detail_level > 0 and info["source"]:
692 if detail_level > 0 and info["source"]:
692 append_field(bundle, "Source", "source", code_formatter)
693 append_field(bundle, "Source", "source", code_formatter)
693 else:
694 else:
694 append_field(bundle, "Init docstring", "init_docstring", formatter)
695 append_field(bundle, "Init docstring", "init_docstring", formatter)
695
696
696 append_field(bundle, "File", "file")
697 append_field(bundle, "File", "file")
697 append_field(bundle, "Type", "type_name")
698 append_field(bundle, "Type", "type_name")
698 append_field(bundle, "Subclasses", "subclasses")
699 append_field(bundle, "Subclasses", "subclasses")
699
700
700 else:
701 else:
701 # General Python objects
702 # General Python objects
702 append_field(bundle, "Signature", "definition", code_formatter)
703 append_field(bundle, "Signature", "definition", code_formatter)
703 append_field(bundle, "Call signature", "call_def", code_formatter)
704 append_field(bundle, "Call signature", "call_def", code_formatter)
704 append_field(bundle, "Type", "type_name")
705 append_field(bundle, "Type", "type_name")
705 append_field(bundle, "String form", "string_form")
706 append_field(bundle, "String form", "string_form")
706
707
707 # Namespace
708 # Namespace
708 if info["namespace"] != "Interactive":
709 if info["namespace"] != "Interactive":
709 append_field(bundle, "Namespace", "namespace")
710 append_field(bundle, "Namespace", "namespace")
710
711
711 append_field(bundle, "Length", "length")
712 append_field(bundle, "Length", "length")
712 append_field(bundle, "File", "file")
713 append_field(bundle, "File", "file")
713
714
714 # Source or docstring, depending on detail level and whether
715 # Source or docstring, depending on detail level and whether
715 # source found.
716 # source found.
716 if detail_level > 0 and info["source"]:
717 if detail_level > 0 and info["source"]:
717 append_field(bundle, "Source", "source", code_formatter)
718 append_field(bundle, "Source", "source", code_formatter)
718 else:
719 else:
719 append_field(bundle, "Docstring", "docstring", formatter)
720 append_field(bundle, "Docstring", "docstring", formatter)
720
721
721 append_field(bundle, "Class docstring", "class_docstring", formatter)
722 append_field(bundle, "Class docstring", "class_docstring", formatter)
722 append_field(bundle, "Init docstring", "init_docstring", formatter)
723 append_field(bundle, "Init docstring", "init_docstring", formatter)
723 append_field(bundle, "Call docstring", "call_docstring", formatter)
724 append_field(bundle, "Call docstring", "call_docstring", formatter)
724 return bundle
725 return bundle
725
726
726
727
727 def _get_info(
728 def _get_info(
728 self,
729 self,
729 obj: Any,
730 obj: Any,
730 oname: str = "",
731 oname: str = "",
731 formatter=None,
732 formatter=None,
732 info: Optional[OInfo] = None,
733 info: Optional[OInfo] = None,
733 detail_level: int = 0,
734 detail_level: int = 0,
734 omit_sections: Union[List[str], Tuple[()]] = (),
735 omit_sections: Union[List[str], Tuple[()]] = (),
735 ) -> Bundle:
736 ) -> Bundle:
736 """Retrieve an info dict and format it.
737 """Retrieve an info dict and format it.
737
738
738 Parameters
739 Parameters
739 ----------
740 ----------
740 obj : any
741 obj : any
741 Object to inspect and return info from
742 Object to inspect and return info from
742 oname : str (default: ''):
743 oname : str (default: ''):
743 Name of the variable pointing to `obj`.
744 Name of the variable pointing to `obj`.
744 formatter : callable
745 formatter : callable
745 info
746 info
746 already computed information
747 already computed information
747 detail_level : integer
748 detail_level : integer
748 Granularity of detail level, if set to 1, give more information.
749 Granularity of detail level, if set to 1, give more information.
749 omit_sections : list[str]
750 omit_sections : list[str]
750 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`)
751 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`)
751 """
752 """
752
753
753 info_dict = self.info(obj, oname=oname, info=info, detail_level=detail_level)
754 info_dict = self.info(obj, oname=oname, info=info, detail_level=detail_level)
754 omit_sections = list(omit_sections)
755 omit_sections = list(omit_sections)
755
756
756 bundle = self._make_info_unformatted(
757 bundle = self._make_info_unformatted(
757 obj,
758 obj,
758 info_dict,
759 info_dict,
759 formatter,
760 formatter,
760 detail_level=detail_level,
761 detail_level=detail_level,
761 omit_sections=omit_sections,
762 omit_sections=omit_sections,
762 )
763 )
763 if self.mime_hooks:
764 if self.mime_hooks:
764 hook_data = InspectorHookData(
765 hook_data = InspectorHookData(
765 obj=obj,
766 obj=obj,
766 info=info,
767 info=info,
767 info_dict=info_dict,
768 info_dict=info_dict,
768 detail_level=detail_level,
769 detail_level=detail_level,
769 omit_sections=omit_sections,
770 omit_sections=omit_sections,
770 )
771 )
771 for key, hook in self.mime_hooks.items(): # type:ignore
772 for key, hook in self.mime_hooks.items(): # type:ignore
772 required_parameters = [
773 required_parameters = [
773 parameter
774 parameter
774 for parameter in inspect.signature(hook).parameters.values()
775 for parameter in inspect.signature(hook).parameters.values()
775 if parameter.default != inspect.Parameter.default
776 if parameter.default != inspect.Parameter.default
776 ]
777 ]
777 if len(required_parameters) == 1:
778 if len(required_parameters) == 1:
778 res = hook(hook_data)
779 res = hook(hook_data)
779 else:
780 else:
780 warnings.warn(
781 warnings.warn(
781 "MIME hook format changed in IPython 8.22; hooks should now accept"
782 "MIME hook format changed in IPython 8.22; hooks should now accept"
782 " a single parameter (InspectorHookData); support for hooks requiring"
783 " a single parameter (InspectorHookData); support for hooks requiring"
783 " two-parameters (obj and info) will be removed in a future version",
784 " two-parameters (obj and info) will be removed in a future version",
784 DeprecationWarning,
785 DeprecationWarning,
785 stacklevel=2,
786 stacklevel=2,
786 )
787 )
787 res = hook(obj, info)
788 res = hook(obj, info)
788 if res is not None:
789 if res is not None:
789 bundle[key] = res
790 bundle[key] = res
790 return self.format_mime(bundle)
791 return self.format_mime(bundle)
791
792
792 def pinfo(
793 def pinfo(
793 self,
794 self,
794 obj,
795 obj,
795 oname="",
796 oname="",
796 formatter=None,
797 formatter=None,
797 info: Optional[OInfo] = None,
798 info: Optional[OInfo] = None,
798 detail_level=0,
799 detail_level=0,
799 enable_html_pager=True,
800 enable_html_pager=True,
800 omit_sections=(),
801 omit_sections=(),
801 ):
802 ):
802 """Show detailed information about an object.
803 """Show detailed information about an object.
803
804
804 Optional arguments:
805 Optional arguments:
805
806
806 - oname: name of the variable pointing to the object.
807 - oname: name of the variable pointing to the object.
807
808
808 - formatter: callable (optional)
809 - formatter: callable (optional)
809 A special formatter for docstrings.
810 A special formatter for docstrings.
810
811
811 The formatter is a callable that takes a string as an input
812 The formatter is a callable that takes a string as an input
812 and returns either a formatted string or a mime type bundle
813 and returns either a formatted string or a mime type bundle
813 in the form of a dictionary.
814 in the form of a dictionary.
814
815
815 Although the support of custom formatter returning a string
816 Although the support of custom formatter returning a string
816 instead of a mime type bundle is deprecated.
817 instead of a mime type bundle is deprecated.
817
818
818 - info: a structure with some information fields which may have been
819 - info: a structure with some information fields which may have been
819 precomputed already.
820 precomputed already.
820
821
821 - detail_level: if set to 1, more information is given.
822 - detail_level: if set to 1, more information is given.
822
823
823 - omit_sections: set of section keys and titles to omit
824 - omit_sections: set of section keys and titles to omit
824 """
825 """
825 assert info is not None
826 assert info is not None
826 info_b: Bundle = self._get_info(
827 info_b: Bundle = self._get_info(
827 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
828 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
828 )
829 )
829 if not enable_html_pager:
830 if not enable_html_pager:
830 del info_b["text/html"]
831 del info_b["text/html"]
831 page.page(info_b)
832 page.page(info_b)
832
833
833 def _info(self, obj, oname="", info=None, detail_level=0):
834 def _info(self, obj, oname="", info=None, detail_level=0):
834 """
835 """
835 Inspector.info() was likely improperly marked as deprecated
836 Inspector.info() was likely improperly marked as deprecated
836 while only a parameter was deprecated. We "un-deprecate" it.
837 while only a parameter was deprecated. We "un-deprecate" it.
837 """
838 """
838
839
839 warnings.warn(
840 warnings.warn(
840 "The `Inspector.info()` method has been un-deprecated as of 8.0 "
841 "The `Inspector.info()` method has been un-deprecated as of 8.0 "
841 "and the `formatter=` keyword removed. `Inspector._info` is now "
842 "and the `formatter=` keyword removed. `Inspector._info` is now "
842 "an alias, and you can just call `.info()` directly.",
843 "an alias, and you can just call `.info()` directly.",
843 DeprecationWarning,
844 DeprecationWarning,
844 stacklevel=2,
845 stacklevel=2,
845 )
846 )
846 return self.info(obj, oname=oname, info=info, detail_level=detail_level)
847 return self.info(obj, oname=oname, info=info, detail_level=detail_level)
847
848
848 def info(self, obj, oname="", info=None, detail_level=0) -> InfoDict:
849 def info(self, obj, oname="", info=None, detail_level=0) -> InfoDict:
849 """Compute a dict with detailed information about an object.
850 """Compute a dict with detailed information about an object.
850
851
851 Parameters
852 Parameters
852 ----------
853 ----------
853 obj : any
854 obj : any
854 An object to find information about
855 An object to find information about
855 oname : str (default: '')
856 oname : str (default: '')
856 Name of the variable pointing to `obj`.
857 Name of the variable pointing to `obj`.
857 info : (default: None)
858 info : (default: None)
858 A struct (dict like with attr access) with some information fields
859 A struct (dict like with attr access) with some information fields
859 which may have been precomputed already.
860 which may have been precomputed already.
860 detail_level : int (default:0)
861 detail_level : int (default:0)
861 If set to 1, more information is given.
862 If set to 1, more information is given.
862
863
863 Returns
864 Returns
864 -------
865 -------
865 An object info dict with known fields from `info_fields` (see `InfoDict`).
866 An object info dict with known fields from `info_fields` (see `InfoDict`).
866 """
867 """
867
868
868 if info is None:
869 if info is None:
869 ismagic = False
870 ismagic = False
870 isalias = False
871 isalias = False
871 ospace = ''
872 ospace = ''
872 else:
873 else:
873 ismagic = info.ismagic
874 ismagic = info.ismagic
874 isalias = info.isalias
875 isalias = info.isalias
875 ospace = info.namespace
876 ospace = info.namespace
876
877
877 # Get docstring, special-casing aliases:
878 # Get docstring, special-casing aliases:
878 att_name = oname.split(".")[-1]
879 att_name = oname.split(".")[-1]
879 parents_docs = None
880 parents_docs = None
880 prelude = ""
881 prelude = ""
881 if info and info.parent is not None and hasattr(info.parent, HOOK_NAME):
882 if info and info.parent is not None and hasattr(info.parent, HOOK_NAME):
882 parents_docs_dict = getattr(info.parent, HOOK_NAME)
883 parents_docs_dict = getattr(info.parent, HOOK_NAME)
883 parents_docs = parents_docs_dict.get(att_name, None)
884 parents_docs = parents_docs_dict.get(att_name, None)
884 out: InfoDict = cast(
885 out: InfoDict = cast(
885 InfoDict,
886 InfoDict,
886 {
887 {
887 **{field: None for field in _info_fields},
888 **{field: None for field in _info_fields},
888 **{
889 **{
889 "name": oname,
890 "name": oname,
890 "found": True,
891 "found": True,
891 "isalias": isalias,
892 "isalias": isalias,
892 "ismagic": ismagic,
893 "ismagic": ismagic,
893 "subclasses": None,
894 "subclasses": None,
894 },
895 },
895 },
896 },
896 )
897 )
897
898
898 if parents_docs:
899 if parents_docs:
899 ds = parents_docs
900 ds = parents_docs
900 elif isalias:
901 elif isalias:
901 if not callable(obj):
902 if not callable(obj):
902 try:
903 try:
903 ds = "Alias to the system command:\n %s" % obj[1]
904 ds = "Alias to the system command:\n %s" % obj[1]
904 except:
905 except:
905 ds = "Alias: " + str(obj)
906 ds = "Alias: " + str(obj)
906 else:
907 else:
907 ds = "Alias to " + str(obj)
908 ds = "Alias to " + str(obj)
908 if obj.__doc__:
909 if obj.__doc__:
909 ds += "\nDocstring:\n" + obj.__doc__
910 ds += "\nDocstring:\n" + obj.__doc__
910 else:
911 else:
911 ds_or_None = getdoc(obj)
912 ds_or_None = getdoc(obj)
912 if ds_or_None is None:
913 if ds_or_None is None:
913 ds = '<no docstring>'
914 ds = '<no docstring>'
914 else:
915 else:
915 ds = ds_or_None
916 ds = ds_or_None
916
917
917 ds = prelude + ds
918 ds = prelude + ds
918
919
919 # store output in a dict, we initialize it here and fill it as we go
920 # store output in a dict, we initialize it here and fill it as we go
920
921
921 string_max = 200 # max size of strings to show (snipped if longer)
922 string_max = 200 # max size of strings to show (snipped if longer)
922 shalf = int((string_max - 5) / 2)
923 shalf = int((string_max - 5) / 2)
923
924
924 if ismagic:
925 if ismagic:
925 out['type_name'] = 'Magic function'
926 out['type_name'] = 'Magic function'
926 elif isalias:
927 elif isalias:
927 out['type_name'] = 'System alias'
928 out['type_name'] = 'System alias'
928 else:
929 else:
929 out['type_name'] = type(obj).__name__
930 out['type_name'] = type(obj).__name__
930
931
931 try:
932 try:
932 bclass = obj.__class__
933 bclass = obj.__class__
933 out['base_class'] = str(bclass)
934 out['base_class'] = str(bclass)
934 except:
935 except:
935 pass
936 pass
936
937
937 # String form, but snip if too long in ? form (full in ??)
938 # String form, but snip if too long in ? form (full in ??)
938 if detail_level >= self.str_detail_level:
939 if detail_level >= self.str_detail_level:
939 try:
940 try:
940 ostr = str(obj)
941 ostr = str(obj)
941 if not detail_level and len(ostr) > string_max:
942 if not detail_level and len(ostr) > string_max:
942 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
943 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
943 # TODO: `'string_form'.expandtabs()` seems wrong, but
944 # TODO: `'string_form'.expandtabs()` seems wrong, but
944 # it was (nearly) like this since the first commit ever.
945 # it was (nearly) like this since the first commit ever.
945 ostr = ("\n" + " " * len("string_form".expandtabs())).join(
946 ostr = ("\n" + " " * len("string_form".expandtabs())).join(
946 q.strip() for q in ostr.split("\n")
947 q.strip() for q in ostr.split("\n")
947 )
948 )
948 out["string_form"] = ostr
949 out["string_form"] = ostr
949 except:
950 except:
950 pass
951 pass
951
952
952 if ospace:
953 if ospace:
953 out['namespace'] = ospace
954 out['namespace'] = ospace
954
955
955 # Length (for strings and lists)
956 # Length (for strings and lists)
956 try:
957 try:
957 out['length'] = str(len(obj))
958 out['length'] = str(len(obj))
958 except Exception:
959 except Exception:
959 pass
960 pass
960
961
961 # Filename where object was defined
962 # Filename where object was defined
962 binary_file = False
963 binary_file = False
963 fname = find_file(obj)
964 fname = find_file(obj)
964 if fname is None:
965 if fname is None:
965 # if anything goes wrong, we don't want to show source, so it's as
966 # if anything goes wrong, we don't want to show source, so it's as
966 # if the file was binary
967 # if the file was binary
967 binary_file = True
968 binary_file = True
968 else:
969 else:
969 if fname.endswith(('.so', '.dll', '.pyd')):
970 if fname.endswith(('.so', '.dll', '.pyd')):
970 binary_file = True
971 binary_file = True
971 elif fname.endswith('<string>'):
972 elif fname.endswith('<string>'):
972 fname = 'Dynamically generated function. No source code available.'
973 fname = 'Dynamically generated function. No source code available.'
973 out['file'] = compress_user(fname)
974 out['file'] = compress_user(fname)
974
975
975 # Original source code for a callable, class or property.
976 # Original source code for a callable, class or property.
976 if detail_level:
977 if detail_level:
977 # Flush the source cache because inspect can return out-of-date
978 # Flush the source cache because inspect can return out-of-date
978 # source
979 # source
979 linecache.checkcache()
980 linecache.checkcache()
980 try:
981 try:
981 if isinstance(obj, property) or not binary_file:
982 if isinstance(obj, property) or not binary_file:
982 src = getsource(obj, oname)
983 src = getsource(obj, oname)
983 if src is not None:
984 if src is not None:
984 src = src.rstrip()
985 src = src.rstrip()
985 out['source'] = src
986 out['source'] = src
986
987
987 except Exception:
988 except Exception:
988 pass
989 pass
989
990
990 # Add docstring only if no source is to be shown (avoid repetitions).
991 # Add docstring only if no source is to be shown (avoid repetitions).
991 if ds and not self._source_contains_docstring(out.get('source'), ds):
992 if ds and not self._source_contains_docstring(out.get('source'), ds):
992 out['docstring'] = ds
993 out['docstring'] = ds
993
994
994 # Constructor docstring for classes
995 # Constructor docstring for classes
995 if inspect.isclass(obj):
996 if inspect.isclass(obj):
996 out['isclass'] = True
997 out['isclass'] = True
997
998
998 # get the init signature:
999 # get the init signature:
999 try:
1000 try:
1000 init_def = self._getdef(obj, oname)
1001 init_def = self._getdef(obj, oname)
1001 except AttributeError:
1002 except AttributeError:
1002 init_def = None
1003 init_def = None
1003
1004
1004 # get the __init__ docstring
1005 # get the __init__ docstring
1005 try:
1006 try:
1006 obj_init = obj.__init__
1007 obj_init = obj.__init__
1007 except AttributeError:
1008 except AttributeError:
1008 init_ds = None
1009 init_ds = None
1009 else:
1010 else:
1010 if init_def is None:
1011 if init_def is None:
1011 # Get signature from init if top-level sig failed.
1012 # Get signature from init if top-level sig failed.
1012 # Can happen for built-in types (list, etc.).
1013 # Can happen for built-in types (list, etc.).
1013 try:
1014 try:
1014 init_def = self._getdef(obj_init, oname)
1015 init_def = self._getdef(obj_init, oname)
1015 except AttributeError:
1016 except AttributeError:
1016 pass
1017 pass
1017 init_ds = getdoc(obj_init)
1018 init_ds = getdoc(obj_init)
1018 # Skip Python's auto-generated docstrings
1019 # Skip Python's auto-generated docstrings
1019 if init_ds == _object_init_docstring:
1020 if init_ds == _object_init_docstring:
1020 init_ds = None
1021 init_ds = None
1021
1022
1022 if init_def:
1023 if init_def:
1023 out['init_definition'] = init_def
1024 out['init_definition'] = init_def
1024
1025
1025 if init_ds:
1026 if init_ds:
1026 out['init_docstring'] = init_ds
1027 out['init_docstring'] = init_ds
1027
1028
1028 names = [sub.__name__ for sub in type.__subclasses__(obj)]
1029 names = [sub.__name__ for sub in type.__subclasses__(obj)]
1029 if len(names) < 10:
1030 if len(names) < 10:
1030 all_names = ', '.join(names)
1031 all_names = ', '.join(names)
1031 else:
1032 else:
1032 all_names = ', '.join(names[:10]+['...'])
1033 all_names = ', '.join(names[:10]+['...'])
1033 out['subclasses'] = all_names
1034 out['subclasses'] = all_names
1034 # and class docstring for instances:
1035 # and class docstring for instances:
1035 else:
1036 else:
1036 # reconstruct the function definition and print it:
1037 # reconstruct the function definition and print it:
1037 defln = self._getdef(obj, oname)
1038 defln = self._getdef(obj, oname)
1038 if defln:
1039 if defln:
1039 out['definition'] = defln
1040 out['definition'] = defln
1040
1041
1041 # First, check whether the instance docstring is identical to the
1042 # First, check whether the instance docstring is identical to the
1042 # class one, and print it separately if they don't coincide. In
1043 # class one, and print it separately if they don't coincide. In
1043 # most cases they will, but it's nice to print all the info for
1044 # most cases they will, but it's nice to print all the info for
1044 # objects which use instance-customized docstrings.
1045 # objects which use instance-customized docstrings.
1045 if ds:
1046 if ds:
1046 try:
1047 try:
1047 cls = getattr(obj,'__class__')
1048 cls = getattr(obj,'__class__')
1048 except:
1049 except:
1049 class_ds = None
1050 class_ds = None
1050 else:
1051 else:
1051 class_ds = getdoc(cls)
1052 class_ds = getdoc(cls)
1052 # Skip Python's auto-generated docstrings
1053 # Skip Python's auto-generated docstrings
1053 if class_ds in _builtin_type_docstrings:
1054 if class_ds in _builtin_type_docstrings:
1054 class_ds = None
1055 class_ds = None
1055 if class_ds and ds != class_ds:
1056 if class_ds and ds != class_ds:
1056 out['class_docstring'] = class_ds
1057 out['class_docstring'] = class_ds
1057
1058
1058 # Next, try to show constructor docstrings
1059 # Next, try to show constructor docstrings
1059 try:
1060 try:
1060 init_ds = getdoc(obj.__init__)
1061 init_ds = getdoc(obj.__init__)
1061 # Skip Python's auto-generated docstrings
1062 # Skip Python's auto-generated docstrings
1062 if init_ds == _object_init_docstring:
1063 if init_ds == _object_init_docstring:
1063 init_ds = None
1064 init_ds = None
1064 except AttributeError:
1065 except AttributeError:
1065 init_ds = None
1066 init_ds = None
1066 if init_ds:
1067 if init_ds:
1067 out['init_docstring'] = init_ds
1068 out['init_docstring'] = init_ds
1068
1069
1069 # Call form docstring for callable instances
1070 # Call form docstring for callable instances
1070 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
1071 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
1071 call_def = self._getdef(obj.__call__, oname)
1072 call_def = self._getdef(obj.__call__, oname)
1072 if call_def and (call_def != out.get('definition')):
1073 if call_def and (call_def != out.get('definition')):
1073 # it may never be the case that call def and definition differ,
1074 # it may never be the case that call def and definition differ,
1074 # but don't include the same signature twice
1075 # but don't include the same signature twice
1075 out['call_def'] = call_def
1076 out['call_def'] = call_def
1076 call_ds = getdoc(obj.__call__)
1077 call_ds = getdoc(obj.__call__)
1077 # Skip Python's auto-generated docstrings
1078 # Skip Python's auto-generated docstrings
1078 if call_ds == _func_call_docstring:
1079 if call_ds == _func_call_docstring:
1079 call_ds = None
1080 call_ds = None
1080 if call_ds:
1081 if call_ds:
1081 out['call_docstring'] = call_ds
1082 out['call_docstring'] = call_ds
1082
1083
1083 return out
1084 return out
1084
1085
1085 @staticmethod
1086 @staticmethod
1086 def _source_contains_docstring(src, doc):
1087 def _source_contains_docstring(src, doc):
1087 """
1088 """
1088 Check whether the source *src* contains the docstring *doc*.
1089 Check whether the source *src* contains the docstring *doc*.
1089
1090
1090 This is is helper function to skip displaying the docstring if the
1091 This is is helper function to skip displaying the docstring if the
1091 source already contains it, avoiding repetition of information.
1092 source already contains it, avoiding repetition of information.
1092 """
1093 """
1093 try:
1094 try:
1094 (def_node,) = ast.parse(dedent(src)).body
1095 (def_node,) = ast.parse(dedent(src)).body
1095 return ast.get_docstring(def_node) == doc # type: ignore[arg-type]
1096 return ast.get_docstring(def_node) == doc # type: ignore[arg-type]
1096 except Exception:
1097 except Exception:
1097 # The source can become invalid or even non-existent (because it
1098 # The source can become invalid or even non-existent (because it
1098 # is re-fetched from the source file) so the above code fail in
1099 # is re-fetched from the source file) so the above code fail in
1099 # arbitrary ways.
1100 # arbitrary ways.
1100 return False
1101 return False
1101
1102
1102 def psearch(self,pattern,ns_table,ns_search=[],
1103 def psearch(self,pattern,ns_table,ns_search=[],
1103 ignore_case=False,show_all=False, *, list_types=False):
1104 ignore_case=False,show_all=False, *, list_types=False):
1104 """Search namespaces with wildcards for objects.
1105 """Search namespaces with wildcards for objects.
1105
1106
1106 Arguments:
1107 Arguments:
1107
1108
1108 - pattern: string containing shell-like wildcards to use in namespace
1109 - pattern: string containing shell-like wildcards to use in namespace
1109 searches and optionally a type specification to narrow the search to
1110 searches and optionally a type specification to narrow the search to
1110 objects of that type.
1111 objects of that type.
1111
1112
1112 - ns_table: dict of name->namespaces for search.
1113 - ns_table: dict of name->namespaces for search.
1113
1114
1114 Optional arguments:
1115 Optional arguments:
1115
1116
1116 - ns_search: list of namespace names to include in search.
1117 - ns_search: list of namespace names to include in search.
1117
1118
1118 - ignore_case(False): make the search case-insensitive.
1119 - ignore_case(False): make the search case-insensitive.
1119
1120
1120 - show_all(False): show all names, including those starting with
1121 - show_all(False): show all names, including those starting with
1121 underscores.
1122 underscores.
1122
1123
1123 - list_types(False): list all available object types for object matching.
1124 - list_types(False): list all available object types for object matching.
1124 """
1125 """
1125 # print('ps pattern:<%r>' % pattern) # dbg
1126 # print('ps pattern:<%r>' % pattern) # dbg
1126
1127
1127 # defaults
1128 # defaults
1128 type_pattern = 'all'
1129 type_pattern = 'all'
1129 filter = ''
1130 filter = ''
1130
1131
1131 # list all object types
1132 # list all object types
1132 if list_types:
1133 if list_types:
1133 page.page('\n'.join(sorted(typestr2type)))
1134 page.page('\n'.join(sorted(typestr2type)))
1134 return
1135 return
1135
1136
1136 cmds = pattern.split()
1137 cmds = pattern.split()
1137 len_cmds = len(cmds)
1138 len_cmds = len(cmds)
1138 if len_cmds == 1:
1139 if len_cmds == 1:
1139 # Only filter pattern given
1140 # Only filter pattern given
1140 filter = cmds[0]
1141 filter = cmds[0]
1141 elif len_cmds == 2:
1142 elif len_cmds == 2:
1142 # Both filter and type specified
1143 # Both filter and type specified
1143 filter,type_pattern = cmds
1144 filter,type_pattern = cmds
1144 else:
1145 else:
1145 raise ValueError('invalid argument string for psearch: <%s>' %
1146 raise ValueError('invalid argument string for psearch: <%s>' %
1146 pattern)
1147 pattern)
1147
1148
1148 # filter search namespaces
1149 # filter search namespaces
1149 for name in ns_search:
1150 for name in ns_search:
1150 if name not in ns_table:
1151 if name not in ns_table:
1151 raise ValueError('invalid namespace <%s>. Valid names: %s' %
1152 raise ValueError('invalid namespace <%s>. Valid names: %s' %
1152 (name,ns_table.keys()))
1153 (name,ns_table.keys()))
1153
1154
1154 # print('type_pattern:',type_pattern) # dbg
1155 # print('type_pattern:',type_pattern) # dbg
1155 search_result, namespaces_seen = set(), set()
1156 search_result, namespaces_seen = set(), set()
1156 for ns_name in ns_search:
1157 for ns_name in ns_search:
1157 ns = ns_table[ns_name]
1158 ns = ns_table[ns_name]
1158 # Normally, locals and globals are the same, so we just check one.
1159 # Normally, locals and globals are the same, so we just check one.
1159 if id(ns) in namespaces_seen:
1160 if id(ns) in namespaces_seen:
1160 continue
1161 continue
1161 namespaces_seen.add(id(ns))
1162 namespaces_seen.add(id(ns))
1162 tmp_res = list_namespace(ns, type_pattern, filter,
1163 tmp_res = list_namespace(ns, type_pattern, filter,
1163 ignore_case=ignore_case, show_all=show_all)
1164 ignore_case=ignore_case, show_all=show_all)
1164 search_result.update(tmp_res)
1165 search_result.update(tmp_res)
1165
1166
1166 page.page('\n'.join(sorted(search_result)))
1167 page.page('\n'.join(sorted(search_result)))
1167
1168
1168
1169
1169 def _render_signature(obj_signature, obj_name) -> str:
1170 def _render_signature(obj_signature, obj_name) -> str:
1170 """
1171 """
1171 This was mostly taken from inspect.Signature.__str__.
1172 This was mostly taken from inspect.Signature.__str__.
1172 Look there for the comments.
1173 Look there for the comments.
1173 The only change is to add linebreaks when this gets too long.
1174 The only change is to add linebreaks when this gets too long.
1174 """
1175 """
1175 result = []
1176 result = []
1176 pos_only = False
1177 pos_only = False
1177 kw_only = True
1178 kw_only = True
1178 for param in obj_signature.parameters.values():
1179 for param in obj_signature.parameters.values():
1179 if param.kind == inspect.Parameter.POSITIONAL_ONLY:
1180 if param.kind == inspect.Parameter.POSITIONAL_ONLY:
1180 pos_only = True
1181 pos_only = True
1181 elif pos_only:
1182 elif pos_only:
1182 result.append('/')
1183 result.append('/')
1183 pos_only = False
1184 pos_only = False
1184
1185
1185 if param.kind == inspect.Parameter.VAR_POSITIONAL:
1186 if param.kind == inspect.Parameter.VAR_POSITIONAL:
1186 kw_only = False
1187 kw_only = False
1187 elif param.kind == inspect.Parameter.KEYWORD_ONLY and kw_only:
1188 elif param.kind == inspect.Parameter.KEYWORD_ONLY and kw_only:
1188 result.append('*')
1189 result.append('*')
1189 kw_only = False
1190 kw_only = False
1190
1191
1191 result.append(str(param))
1192 result.append(str(param))
1192
1193
1193 if pos_only:
1194 if pos_only:
1194 result.append('/')
1195 result.append('/')
1195
1196
1196 # add up name, parameters, braces (2), and commas
1197 # add up name, parameters, braces (2), and commas
1197 if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
1198 if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
1198 # This doesn’t fit behind “Signature: ” in an inspect window.
1199 # This doesn’t fit behind “Signature: ” in an inspect window.
1199 rendered = '{}(\n{})'.format(obj_name, ''.join(
1200 rendered = '{}(\n{})'.format(obj_name, ''.join(
1200 ' {},\n'.format(r) for r in result)
1201 ' {},\n'.format(r) for r in result)
1201 )
1202 )
1202 else:
1203 else:
1203 rendered = '{}({})'.format(obj_name, ', '.join(result))
1204 rendered = '{}({})'.format(obj_name, ', '.join(result))
1204
1205
1205 if obj_signature.return_annotation is not inspect._empty:
1206 if obj_signature.return_annotation is not inspect._empty:
1206 anno = inspect.formatannotation(obj_signature.return_annotation)
1207 anno = inspect.formatannotation(obj_signature.return_annotation)
1207 rendered += ' -> {}'.format(anno)
1208 rendered += ' -> {}'.format(anno)
1208
1209
1209 return rendered
1210 return rendered
@@ -1,347 +1,348
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Paging capabilities for IPython.core
3 Paging capabilities for IPython.core
4
4
5 Notes
5 Notes
6 -----
6 -----
7
7
8 For now this uses IPython hooks, so it can't be in IPython.utils. If we can get
8 For now this uses IPython hooks, so it can't be in IPython.utils. If we can get
9 rid of that dependency, we could move it there.
9 rid of that dependency, we could move it there.
10 -----
10 -----
11 """
11 """
12
12
13 # Copyright (c) IPython Development Team.
13 # Copyright (c) IPython Development Team.
14 # Distributed under the terms of the Modified BSD License.
14 # Distributed under the terms of the Modified BSD License.
15
15
16
16
17 import os
17 import os
18 import io
18 import io
19 import re
19 import re
20 import sys
20 import sys
21 import tempfile
21 import tempfile
22 import subprocess
22 import subprocess
23
23
24 from io import UnsupportedOperation
24 from io import UnsupportedOperation
25 from pathlib import Path
25 from pathlib import Path
26
26
27 from IPython import get_ipython
27 from IPython import get_ipython
28 from IPython.display import display
28 from IPython.display import display
29 from IPython.core.error import TryNext
29 from IPython.core.error import TryNext
30 from IPython.utils.data import chop
30 from IPython.utils.data import chop
31 from IPython.utils.process import system
31 from IPython.utils.process import system
32 from IPython.utils.terminal import get_terminal_size
32 from IPython.utils.terminal import get_terminal_size
33 from IPython.utils import py3compat
33 from IPython.utils import py3compat
34
34
35
35
36 def display_page(strng, start=0, screen_lines=25):
36 def display_page(strng, start=0, screen_lines=25):
37 """Just display, no paging. screen_lines is ignored."""
37 """Just display, no paging. screen_lines is ignored."""
38 if isinstance(strng, dict):
38 if isinstance(strng, dict):
39 data = strng
39 data = strng
40 else:
40 else:
41 if start:
41 if start:
42 strng = u'\n'.join(strng.splitlines()[start:])
42 strng = u'\n'.join(strng.splitlines()[start:])
43 data = { 'text/plain': strng }
43 data = { 'text/plain': strng }
44 display(data, raw=True)
44 display(data, raw=True)
45
45
46
46
47 def as_hook(page_func):
47 def as_hook(page_func):
48 """Wrap a pager func to strip the `self` arg
48 """Wrap a pager func to strip the `self` arg
49
49
50 so it can be called as a hook.
50 so it can be called as a hook.
51 """
51 """
52 return lambda self, *args, **kwargs: page_func(*args, **kwargs)
52 return lambda self, *args, **kwargs: page_func(*args, **kwargs)
53
53
54
54
55 esc_re = re.compile(r"(\x1b[^m]+m)")
55 esc_re = re.compile(r"(\x1b[^m]+m)")
56
56
57 def page_dumb(strng, start=0, screen_lines=25):
57 def page_dumb(strng, start=0, screen_lines=25):
58 """Very dumb 'pager' in Python, for when nothing else works.
58 """Very dumb 'pager' in Python, for when nothing else works.
59
59
60 Only moves forward, same interface as page(), except for pager_cmd and
60 Only moves forward, same interface as page(), except for pager_cmd and
61 mode.
61 mode.
62 """
62 """
63 if isinstance(strng, dict):
63 if isinstance(strng, dict):
64 strng = strng.get('text/plain', '')
64 strng = strng.get('text/plain', '')
65 out_ln = strng.splitlines()[start:]
65 out_ln = strng.splitlines()[start:]
66 screens = chop(out_ln,screen_lines-1)
66 screens = chop(out_ln,screen_lines-1)
67 if len(screens) == 1:
67 if len(screens) == 1:
68 print(os.linesep.join(screens[0]))
68 print(os.linesep.join(screens[0]))
69 else:
69 else:
70 last_escape = ""
70 last_escape = ""
71 for scr in screens[0:-1]:
71 for scr in screens[0:-1]:
72 hunk = os.linesep.join(scr)
72 hunk = os.linesep.join(scr)
73 print(last_escape + hunk)
73 print(last_escape + hunk)
74 if not page_more():
74 if not page_more():
75 return
75 return
76 esc_list = esc_re.findall(hunk)
76 esc_list = esc_re.findall(hunk)
77 if len(esc_list) > 0:
77 if len(esc_list) > 0:
78 last_escape = esc_list[-1]
78 last_escape = esc_list[-1]
79 print(last_escape + os.linesep.join(screens[-1]))
79 print(last_escape + os.linesep.join(screens[-1]))
80
80
81 def _detect_screen_size(screen_lines_def):
81 def _detect_screen_size(screen_lines_def):
82 """Attempt to work out the number of lines on the screen.
82 """Attempt to work out the number of lines on the screen.
83
83
84 This is called by page(). It can raise an error (e.g. when run in the
84 This is called by page(). It can raise an error (e.g. when run in the
85 test suite), so it's separated out so it can easily be called in a try block.
85 test suite), so it's separated out so it can easily be called in a try block.
86 """
86 """
87 TERM = os.environ.get('TERM',None)
87 TERM = os.environ.get('TERM',None)
88 if not((TERM=='xterm' or TERM=='xterm-color') and sys.platform != 'sunos5'):
88 if not((TERM=='xterm' or TERM=='xterm-color') and sys.platform != 'sunos5'):
89 # curses causes problems on many terminals other than xterm, and
89 # curses causes problems on many terminals other than xterm, and
90 # some termios calls lock up on Sun OS5.
90 # some termios calls lock up on Sun OS5.
91 return screen_lines_def
91 return screen_lines_def
92
92
93 try:
93 try:
94 import termios
94 import termios
95 import curses
95 import curses
96 except ImportError:
96 except ImportError:
97 return screen_lines_def
97 return screen_lines_def
98
98
99 # There is a bug in curses, where *sometimes* it fails to properly
99 # There is a bug in curses, where *sometimes* it fails to properly
100 # initialize, and then after the endwin() call is made, the
100 # initialize, and then after the endwin() call is made, the
101 # terminal is left in an unusable state. Rather than trying to
101 # terminal is left in an unusable state. Rather than trying to
102 # check every time for this (by requesting and comparing termios
102 # check every time for this (by requesting and comparing termios
103 # flags each time), we just save the initial terminal state and
103 # flags each time), we just save the initial terminal state and
104 # unconditionally reset it every time. It's cheaper than making
104 # unconditionally reset it every time. It's cheaper than making
105 # the checks.
105 # the checks.
106 try:
106 try:
107 term_flags = termios.tcgetattr(sys.stdout)
107 term_flags = termios.tcgetattr(sys.stdout)
108 except termios.error as err:
108 except termios.error as err:
109 # can fail on Linux 2.6, pager_page will catch the TypeError
109 # can fail on Linux 2.6, pager_page will catch the TypeError
110 raise TypeError('termios error: {0}'.format(err)) from err
110 raise TypeError('termios error: {0}'.format(err)) from err
111
111
112 try:
112 try:
113 scr = curses.initscr()
113 scr = curses.initscr()
114 except AttributeError:
114 except AttributeError:
115 # Curses on Solaris may not be complete, so we can't use it there
115 # Curses on Solaris may not be complete, so we can't use it there
116 return screen_lines_def
116 return screen_lines_def
117
117
118 screen_lines_real,screen_cols = scr.getmaxyx()
118 screen_lines_real,screen_cols = scr.getmaxyx()
119 curses.endwin()
119 curses.endwin()
120
120
121 # Restore terminal state in case endwin() didn't.
121 # Restore terminal state in case endwin() didn't.
122 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
122 termios.tcsetattr(sys.stdout,termios.TCSANOW,term_flags)
123 # Now we have what we needed: the screen size in rows/columns
123 # Now we have what we needed: the screen size in rows/columns
124 return screen_lines_real
124 return screen_lines_real
125 # print('***Screen size:',screen_lines_real,'lines x',
125 # print('***Screen size:',screen_lines_real,'lines x',
126 # screen_cols,'columns.') # dbg
126 # screen_cols,'columns.') # dbg
127
127
128 def pager_page(strng, start=0, screen_lines=0, pager_cmd=None) -> None:
128 def pager_page(strng, start=0, screen_lines=0, pager_cmd=None) -> None:
129 """Display a string, piping through a pager after a certain length.
129 """Display a string, piping through a pager after a certain length.
130
130
131 strng can be a mime-bundle dict, supplying multiple representations,
131 strng can be a mime-bundle dict, supplying multiple representations,
132 keyed by mime-type.
132 keyed by mime-type.
133
133
134 The screen_lines parameter specifies the number of *usable* lines of your
134 The screen_lines parameter specifies the number of *usable* lines of your
135 terminal screen (total lines minus lines you need to reserve to show other
135 terminal screen (total lines minus lines you need to reserve to show other
136 information).
136 information).
137
137
138 If you set screen_lines to a number <=0, page() will try to auto-determine
138 If you set screen_lines to a number <=0, page() will try to auto-determine
139 your screen size and will only use up to (screen_size+screen_lines) for
139 your screen size and will only use up to (screen_size+screen_lines) for
140 printing, paging after that. That is, if you want auto-detection but need
140 printing, paging after that. That is, if you want auto-detection but need
141 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
141 to reserve the bottom 3 lines of the screen, use screen_lines = -3, and for
142 auto-detection without any lines reserved simply use screen_lines = 0.
142 auto-detection without any lines reserved simply use screen_lines = 0.
143
143
144 If a string won't fit in the allowed lines, it is sent through the
144 If a string won't fit in the allowed lines, it is sent through the
145 specified pager command. If none given, look for PAGER in the environment,
145 specified pager command. If none given, look for PAGER in the environment,
146 and ultimately default to less.
146 and ultimately default to less.
147
147
148 If no system pager works, the string is sent through a 'dumb pager'
148 If no system pager works, the string is sent through a 'dumb pager'
149 written in python, very simplistic.
149 written in python, very simplistic.
150 """
150 """
151
151
152 # for compatibility with mime-bundle form:
152 # for compatibility with mime-bundle form:
153 if isinstance(strng, dict):
153 if isinstance(strng, dict):
154 strng = strng['text/plain']
154 strng = strng['text/plain']
155
155
156 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
156 # Ugly kludge, but calling curses.initscr() flat out crashes in emacs
157 TERM = os.environ.get('TERM','dumb')
157 TERM = os.environ.get('TERM','dumb')
158 if TERM in ['dumb','emacs'] and os.name != 'nt':
158 if TERM in ['dumb','emacs'] and os.name != 'nt':
159 print(strng)
159 print(strng)
160 return
160 return
161 # chop off the topmost part of the string we don't want to see
161 # chop off the topmost part of the string we don't want to see
162 str_lines = strng.splitlines()[start:]
162 str_lines = strng.splitlines()[start:]
163 str_toprint = os.linesep.join(str_lines)
163 str_toprint = os.linesep.join(str_lines)
164 num_newlines = len(str_lines)
164 num_newlines = len(str_lines)
165 len_str = len(str_toprint)
165 len_str = len(str_toprint)
166
166
167 # Dumb heuristics to guesstimate number of on-screen lines the string
167 # Dumb heuristics to guesstimate number of on-screen lines the string
168 # takes. Very basic, but good enough for docstrings in reasonable
168 # takes. Very basic, but good enough for docstrings in reasonable
169 # terminals. If someone later feels like refining it, it's not hard.
169 # terminals. If someone later feels like refining it, it's not hard.
170 numlines = max(num_newlines,int(len_str/80)+1)
170 numlines = max(num_newlines,int(len_str/80)+1)
171
171
172 screen_lines_def = get_terminal_size()[1]
172 screen_lines_def = get_terminal_size()[1]
173
173
174 # auto-determine screen size
174 # auto-determine screen size
175 if screen_lines <= 0:
175 if screen_lines <= 0:
176 try:
176 try:
177 screen_lines += _detect_screen_size(screen_lines_def)
177 screen_lines += _detect_screen_size(screen_lines_def)
178 except (TypeError, UnsupportedOperation):
178 except (TypeError, UnsupportedOperation):
179 print(str_toprint)
179 print(str_toprint)
180 return
180 return
181
181
182 # print('numlines',numlines,'screenlines',screen_lines) # dbg
182 # print('numlines',numlines,'screenlines',screen_lines) # dbg
183 if numlines <= screen_lines :
183 if numlines <= screen_lines :
184 # print('*** normal print') # dbg
184 # print('*** normal print') # dbg
185 print(str_toprint)
185 print(str_toprint)
186 else:
186 else:
187 # Try to open pager and default to internal one if that fails.
187 # Try to open pager and default to internal one if that fails.
188 # All failure modes are tagged as 'retval=1', to match the return
188 # All failure modes are tagged as 'retval=1', to match the return
189 # value of a failed system command. If any intermediate attempt
189 # value of a failed system command. If any intermediate attempt
190 # sets retval to 1, at the end we resort to our own page_dumb() pager.
190 # sets retval to 1, at the end we resort to our own page_dumb() pager.
191 pager_cmd = get_pager_cmd(pager_cmd)
191 pager_cmd = get_pager_cmd(pager_cmd)
192 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
192 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
193 if os.name == 'nt':
193 if os.name == 'nt':
194 if pager_cmd.startswith('type'):
194 if pager_cmd.startswith('type'):
195 # The default WinXP 'type' command is failing on complex strings.
195 # The default WinXP 'type' command is failing on complex strings.
196 retval = 1
196 retval = 1
197 else:
197 else:
198 fd, tmpname = tempfile.mkstemp('.txt')
198 fd, tmpname = tempfile.mkstemp('.txt')
199 tmppath = Path(tmpname)
199 tmppath = Path(tmpname)
200 try:
200 try:
201 os.close(fd)
201 os.close(fd)
202 with tmppath.open("wt", encoding="utf-8") as tmpfile:
202 with tmppath.open("wt", encoding="utf-8") as tmpfile:
203 tmpfile.write(strng)
203 tmpfile.write(strng)
204 cmd = "%s < %s" % (pager_cmd, tmppath)
204 cmd = "%s < %s" % (pager_cmd, tmppath)
205 # tmpfile needs to be closed for windows
205 # tmpfile needs to be closed for windows
206 if os.system(cmd):
206 if os.system(cmd):
207 retval = 1
207 retval = 1
208 else:
208 else:
209 retval = None
209 retval = None
210 finally:
210 finally:
211 Path.unlink(tmppath)
211 Path.unlink(tmppath)
212 else:
212 else:
213 try:
213 try:
214 retval = None
214 retval = None
215 # Emulate os.popen, but redirect stderr
215 # Emulate os.popen, but redirect stderr
216 proc = subprocess.Popen(
216 proc = subprocess.Popen(
217 pager_cmd,
217 pager_cmd,
218 shell=True,
218 shell=True,
219 stdin=subprocess.PIPE,
219 stdin=subprocess.PIPE,
220 stderr=subprocess.DEVNULL,
220 stderr=subprocess.DEVNULL,
221 )
221 )
222 pager = os._wrap_close(
222 pager = os._wrap_close(
223 io.TextIOWrapper(proc.stdin, encoding="utf-8"), proc
223 io.TextIOWrapper(proc.stdin, encoding="utf-8"), proc
224 )
224 )
225 try:
225 try:
226 pager_encoding = pager.encoding or sys.stdout.encoding
226 pager.write(strng)
227 pager.write(strng)
227 finally:
228 finally:
228 retval = pager.close()
229 retval = pager.close()
229 except IOError as msg: # broken pipe when user quits
230 except IOError as msg: # broken pipe when user quits
230 if msg.args == (32, 'Broken pipe'):
231 if msg.args == (32, 'Broken pipe'):
231 retval = None
232 retval = None
232 else:
233 else:
233 retval = 1
234 retval = 1
234 except OSError:
235 except OSError:
235 # Other strange problems, sometimes seen in Win2k/cygwin
236 # Other strange problems, sometimes seen in Win2k/cygwin
236 retval = 1
237 retval = 1
237 if retval is not None:
238 if retval is not None:
238 page_dumb(strng,screen_lines=screen_lines)
239 page_dumb(strng,screen_lines=screen_lines)
239
240
240
241
241 def page(data, start: int = 0, screen_lines: int = 0, pager_cmd=None):
242 def page(data, start: int = 0, screen_lines: int = 0, pager_cmd=None):
242 """Display content in a pager, piping through a pager after a certain length.
243 """Display content in a pager, piping through a pager after a certain length.
243
244
244 data can be a mime-bundle dict, supplying multiple representations,
245 data can be a mime-bundle dict, supplying multiple representations,
245 keyed by mime-type, or text.
246 keyed by mime-type, or text.
246
247
247 Pager is dispatched via the `show_in_pager` IPython hook.
248 Pager is dispatched via the `show_in_pager` IPython hook.
248 If no hook is registered, `pager_page` will be used.
249 If no hook is registered, `pager_page` will be used.
249 """
250 """
250 # Some routines may auto-compute start offsets incorrectly and pass a
251 # Some routines may auto-compute start offsets incorrectly and pass a
251 # negative value. Offset to 0 for robustness.
252 # negative value. Offset to 0 for robustness.
252 start = max(0, start)
253 start = max(0, start)
253
254
254 # first, try the hook
255 # first, try the hook
255 ip = get_ipython()
256 ip = get_ipython()
256 if ip:
257 if ip:
257 try:
258 try:
258 ip.hooks.show_in_pager(data, start=start, screen_lines=screen_lines)
259 ip.hooks.show_in_pager(data, start=start, screen_lines=screen_lines)
259 return
260 return
260 except TryNext:
261 except TryNext:
261 pass
262 pass
262
263
263 # fallback on default pager
264 # fallback on default pager
264 return pager_page(data, start, screen_lines, pager_cmd)
265 return pager_page(data, start, screen_lines, pager_cmd)
265
266
266
267
267 def page_file(fname, start=0, pager_cmd=None):
268 def page_file(fname, start=0, pager_cmd=None):
268 """Page a file, using an optional pager command and starting line.
269 """Page a file, using an optional pager command and starting line.
269 """
270 """
270
271
271 pager_cmd = get_pager_cmd(pager_cmd)
272 pager_cmd = get_pager_cmd(pager_cmd)
272 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
273 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
273
274
274 try:
275 try:
275 if os.environ['TERM'] in ['emacs','dumb']:
276 if os.environ['TERM'] in ['emacs','dumb']:
276 raise EnvironmentError
277 raise EnvironmentError
277 system(pager_cmd + ' ' + fname)
278 system(pager_cmd + ' ' + fname)
278 except:
279 except:
279 try:
280 try:
280 if start > 0:
281 if start > 0:
281 start -= 1
282 start -= 1
282 page(open(fname, encoding="utf-8").read(), start)
283 page(open(fname, encoding="utf-8").read(), start)
283 except:
284 except:
284 print('Unable to show file',repr(fname))
285 print('Unable to show file',repr(fname))
285
286
286
287
287 def get_pager_cmd(pager_cmd=None):
288 def get_pager_cmd(pager_cmd=None):
288 """Return a pager command.
289 """Return a pager command.
289
290
290 Makes some attempts at finding an OS-correct one.
291 Makes some attempts at finding an OS-correct one.
291 """
292 """
292 if os.name == 'posix':
293 if os.name == 'posix':
293 default_pager_cmd = 'less -R' # -R for color control sequences
294 default_pager_cmd = 'less -R' # -R for color control sequences
294 elif os.name in ['nt','dos']:
295 elif os.name in ['nt','dos']:
295 default_pager_cmd = 'type'
296 default_pager_cmd = 'type'
296
297
297 if pager_cmd is None:
298 if pager_cmd is None:
298 try:
299 try:
299 pager_cmd = os.environ['PAGER']
300 pager_cmd = os.environ['PAGER']
300 except:
301 except:
301 pager_cmd = default_pager_cmd
302 pager_cmd = default_pager_cmd
302
303
303 if pager_cmd == 'less' and '-r' not in os.environ.get('LESS', '').lower():
304 if pager_cmd == 'less' and '-r' not in os.environ.get('LESS', '').lower():
304 pager_cmd += ' -R'
305 pager_cmd += ' -R'
305
306
306 return pager_cmd
307 return pager_cmd
307
308
308
309
309 def get_pager_start(pager, start):
310 def get_pager_start(pager, start):
310 """Return the string for paging files with an offset.
311 """Return the string for paging files with an offset.
311
312
312 This is the '+N' argument which less and more (under Unix) accept.
313 This is the '+N' argument which less and more (under Unix) accept.
313 """
314 """
314
315
315 if pager in ['less','more']:
316 if pager in ['less','more']:
316 if start:
317 if start:
317 start_string = '+' + str(start)
318 start_string = '+' + str(start)
318 else:
319 else:
319 start_string = ''
320 start_string = ''
320 else:
321 else:
321 start_string = ''
322 start_string = ''
322 return start_string
323 return start_string
323
324
324
325
325 # (X)emacs on win32 doesn't like to be bypassed with msvcrt.getch()
326 # (X)emacs on win32 doesn't like to be bypassed with msvcrt.getch()
326 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
327 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
327 import msvcrt
328 import msvcrt
328 def page_more():
329 def page_more():
329 """ Smart pausing between pages
330 """ Smart pausing between pages
330
331
331 @return: True if need print more lines, False if quit
332 @return: True if need print more lines, False if quit
332 """
333 """
333 sys.stdout.write('---Return to continue, q to quit--- ')
334 sys.stdout.write('---Return to continue, q to quit--- ')
334 ans = msvcrt.getwch()
335 ans = msvcrt.getwch()
335 if ans in ("q", "Q"):
336 if ans in ("q", "Q"):
336 result = False
337 result = False
337 else:
338 else:
338 result = True
339 result = True
339 sys.stdout.write("\b"*37 + " "*37 + "\b"*37)
340 sys.stdout.write("\b"*37 + " "*37 + "\b"*37)
340 return result
341 return result
341 else:
342 else:
342 def page_more():
343 def page_more():
343 ans = py3compat.input('---Return to continue, q to quit--- ')
344 ans = py3compat.input('---Return to continue, q to quit--- ')
344 if ans.lower().startswith('q'):
345 if ans.lower().startswith('q'):
345 return False
346 return False
346 else:
347 else:
347 return True
348 return True
@@ -1,40 +1,41
1 """A payload based version of page."""
1 """A payload based version of page."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import warnings
6 from IPython.core.getipython import get_ipython
7 from IPython.core.getipython import get_ipython
7
8
8 # see https://github.com/ipython/ipykernel/issues/1304
9 # see https://github.com/ipython/ipykernel/issues/1304
9 # this should be moved to ipykernel and removed in the long run.
10 # this should be moved to ipykernel and removed in the long run.
10
11
11
12
12 def page(strng, start=0, screen_lines=0, pager_cmd=None):
13 def page(strng, start=0, screen_lines=0, pager_cmd=None):
13 """Print a string, piping through a pager.
14 """Print a string, piping through a pager.
14
15
15 This version ignores the screen_lines and pager_cmd arguments and uses
16 This version ignores the screen_lines and pager_cmd arguments and uses
16 IPython's payload system instead.
17 IPython's payload system instead.
17
18
18 Parameters
19 Parameters
19 ----------
20 ----------
20 strng : str or mime-dict
21 strng : str or mime-dict
21 Text to page, or a mime-type keyed dict of already formatted data.
22 Text to page, or a mime-type keyed dict of already formatted data.
22 start : int
23 start : int
23 Starting line at which to place the display.
24 Starting line at which to place the display.
24 """
25 """
25
26
26 # Some routines may auto-compute start offsets incorrectly and pass a
27 # Some routines may auto-compute start offsets incorrectly and pass a
27 # negative value. Offset to 0 for robustness.
28 # negative value. Offset to 0 for robustness.
28 start = max(0, start)
29 start = max(0, start)
29 shell = get_ipython()
30 shell = get_ipython()
30
31
31 if isinstance(strng, dict):
32 if isinstance(strng, dict):
32 data = strng
33 data = strng
33 else:
34 else:
34 data = {'text/plain' : strng}
35 data = {'text/plain' : strng}
35 payload = dict(
36 payload = dict(
36 source='page',
37 source='page',
37 data=data,
38 data=data,
38 start=start,
39 start=start,
39 )
40 )
40 shell.payload_manager.write_payload(payload)
41 shell.payload_manager.write_payload(payload)
@@ -1,46 +1,46
1 """Minimal script to reproduce our nasty reference counting bug.
1 """Minimal script to reproduce our nasty reference counting bug.
2
2
3 The problem is related to https://github.com/ipython/ipython/issues/141
3 The problem is related to https://github.com/ipython/ipython/issues/141
4
4
5 The original fix for that appeared to work, but John D. Hunter found a
5 The original fix for that appeared to work, but John D. Hunter found a
6 matplotlib example which, when run twice in a row, would break. The problem
6 matplotlib example which, when run twice in a row, would break. The problem
7 were references held by open figures to internals of Tkinter.
7 were references held by open figures to internals of Tkinter.
8
8
9 This code reproduces the problem that John saw, without matplotlib.
9 This code reproduces the problem that John saw, without matplotlib.
10
10
11 This script is meant to be called by other parts of the test suite that call it
11 This script is meant to be called by other parts of the test suite that call it
12 via %run as if it were executed interactively by the user. As of 2011-05-29,
12 via %run as if it were executed interactively by the user. As of 2011-05-29,
13 test_run.py calls it.
13 test_run.py calls it.
14 """
14 """
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Module imports
17 # Module imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 from IPython import get_ipython
20 from IPython import get_ipython
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Globals
23 # Globals
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 # This needs to be here because nose and other test runners will import
26 # This needs to be here because nose and other test runners will import
27 # this module. Importing this module has potential side effects that we
27 # this module. Importing this module has potential side effects that we
28 # want to prevent.
28 # want to prevent.
29 if __name__ == '__main__':
29 if __name__ == '__main__':
30
30
31 ip = get_ipython()
31 ip = get_ipython()
32
32
33 if "_refbug_cache" not in ip.user_ns:
33 if not '_refbug_cache' in ip.user_ns:
34 ip.user_ns["_refbug_cache"] = []
34 ip.user_ns['_refbug_cache'] = []
35
35
36
36
37 aglobal = 'Hello'
37 aglobal = 'Hello'
38 def f():
38 def f():
39 return aglobal
39 return aglobal
40
40
41 cache = ip.user_ns['_refbug_cache']
41 cache = ip.user_ns['_refbug_cache']
42 cache.append(f)
42 cache.append(f)
43
43
44 def call_f():
44 def call_f():
45 for func in cache:
45 for func in cache:
46 print('lowercased:',func().lower())
46 print('lowercased:',func().lower())
@@ -1,103 +1,108
1 import unittest
2 import re
1 from IPython.utils.capture import capture_output
3 from IPython.utils.capture import capture_output
4 import sys
2 import pytest
5 import pytest
6 from tempfile import TemporaryDirectory
7 from IPython.testing import tools as tt
3
8
4
9
5 def _exceptiongroup_common(
10 def _exceptiongroup_common(
6 outer_chain: str,
11 outer_chain: str,
7 inner_chain: str,
12 inner_chain: str,
8 native: bool,
13 native: bool,
9 ) -> None:
14 ) -> None:
10 pre_raise = "exceptiongroup." if not native else ""
15 pre_raise = "exceptiongroup." if not native else ""
11 filestr = f"""
16 filestr = f"""
12 {"import exceptiongroup" if not native else ""}
17 {"import exceptiongroup" if not native else ""}
13 import pytest
18 import pytest
14
19
15 def f(): raise ValueError("From f()")
20 def f(): raise ValueError("From f()")
16 def g(): raise BaseException("From g()")
21 def g(): raise BaseException("From g()")
17
22
18 def inner(inner_chain):
23 def inner(inner_chain):
19 excs = []
24 excs = []
20 for callback in [f, g]:
25 for callback in [f, g]:
21 try:
26 try:
22 callback()
27 callback()
23 except BaseException as err:
28 except BaseException as err:
24 excs.append(err)
29 excs.append(err)
25 if excs:
30 if excs:
26 if inner_chain == "none":
31 if inner_chain == "none":
27 raise {pre_raise}BaseExceptionGroup("Oops", excs)
32 raise {pre_raise}BaseExceptionGroup("Oops", excs)
28 try:
33 try:
29 raise SyntaxError()
34 raise SyntaxError()
30 except SyntaxError as e:
35 except SyntaxError as e:
31 if inner_chain == "from":
36 if inner_chain == "from":
32 raise {pre_raise}BaseExceptionGroup("Oops", excs) from e
37 raise {pre_raise}BaseExceptionGroup("Oops", excs) from e
33 else:
38 else:
34 raise {pre_raise}BaseExceptionGroup("Oops", excs)
39 raise {pre_raise}BaseExceptionGroup("Oops", excs)
35
40
36 def outer(outer_chain, inner_chain):
41 def outer(outer_chain, inner_chain):
37 try:
42 try:
38 inner(inner_chain)
43 inner(inner_chain)
39 except BaseExceptionGroup as e:
44 except BaseExceptionGroup as e:
40 if outer_chain == "none":
45 if outer_chain == "none":
41 raise
46 raise
42 if outer_chain == "from":
47 if outer_chain == "from":
43 raise IndexError() from e
48 raise IndexError() from e
44 else:
49 else:
45 raise IndexError
50 raise IndexError
46
51
47
52
48 outer("{outer_chain}", "{inner_chain}")
53 outer("{outer_chain}", "{inner_chain}")
49 """
54 """
50 with capture_output() as cap:
55 with capture_output() as cap:
51 ip.run_cell(filestr)
56 ip.run_cell(filestr)
52
57
53 match_lines = []
58 match_lines = []
54 if inner_chain == "another":
59 if inner_chain == "another":
55 match_lines += [
60 match_lines += [
56 "During handling of the above exception, another exception occurred:",
61 "During handling of the above exception, another exception occurred:",
57 ]
62 ]
58 elif inner_chain == "from":
63 elif inner_chain == "from":
59 match_lines += [
64 match_lines += [
60 "The above exception was the direct cause of the following exception:",
65 "The above exception was the direct cause of the following exception:",
61 ]
66 ]
62
67
63 match_lines += [
68 match_lines += [
64 " + Exception Group Traceback (most recent call last):",
69 " + Exception Group Traceback (most recent call last):",
65 " | BaseExceptionGroup: Oops (2 sub-exceptions)",
70 " | BaseExceptionGroup: Oops (2 sub-exceptions)",
66 " | ValueError: From f()",
71 " | ValueError: From f()",
67 " | BaseException: From g()",
72 " | BaseException: From g()",
68 ]
73 ]
69
74
70 if outer_chain == "another":
75 if outer_chain == "another":
71 match_lines += [
76 match_lines += [
72 "During handling of the above exception, another exception occurred:",
77 "During handling of the above exception, another exception occurred:",
73 "IndexError",
78 "IndexError",
74 ]
79 ]
75 elif outer_chain == "from":
80 elif outer_chain == "from":
76 match_lines += [
81 match_lines += [
77 "The above exception was the direct cause of the following exception:",
82 "The above exception was the direct cause of the following exception:",
78 "IndexError",
83 "IndexError",
79 ]
84 ]
80
85
81 error_lines = cap.stderr.split("\n")
86 error_lines = cap.stderr.split("\n")
82
87
83 err_index = match_index = 0
88 err_index = match_index = 0
84 for expected in match_lines:
89 for expected in match_lines:
85 for i, actual in enumerate(error_lines):
90 for i, actual in enumerate(error_lines):
86 if actual == expected:
91 if actual == expected:
87 error_lines = error_lines[i + 1 :]
92 error_lines = error_lines[i + 1 :]
88 break
93 break
89 else:
94 else:
90 assert False, f"{expected} not found in cap.stderr"
95 assert False, f"{expected} not found in cap.stderr"
91
96
92
97
93 @pytest.mark.parametrize("outer_chain", ["none", "from", "another"])
98 @pytest.mark.parametrize("outer_chain", ["none", "from", "another"])
94 @pytest.mark.parametrize("inner_chain", ["none", "from", "another"])
99 @pytest.mark.parametrize("inner_chain", ["none", "from", "another"])
95 def test_native_exceptiongroup(outer_chain, inner_chain) -> None:
100 def test_native_exceptiongroup(outer_chain, inner_chain) -> None:
96 _exceptiongroup_common(outer_chain, inner_chain, native=True)
101 _exceptiongroup_common(outer_chain, inner_chain, native=True)
97
102
98
103
99 @pytest.mark.parametrize("outer_chain", ["none", "from", "another"])
104 @pytest.mark.parametrize("outer_chain", ["none", "from", "another"])
100 @pytest.mark.parametrize("inner_chain", ["none", "from", "another"])
105 @pytest.mark.parametrize("inner_chain", ["none", "from", "another"])
101 def test_native_exceptiongroup(outer_chain, inner_chain) -> None:
106 def test_native_exceptiongroup(outer_chain, inner_chain) -> None:
102 pytest.importorskip("exceptiongroup")
107 pytest.importorskip("exceptiongroup")
103 _exceptiongroup_common(outer_chain, inner_chain, native=False)
108 _exceptiongroup_common(outer_chain, inner_chain, native=False)
@@ -1,544 +1,545
1 """Tests for the Formatters."""
1 """Tests for the Formatters."""
2
2
3 from math import pi
3 from math import pi
4
4
5 try:
5 try:
6 import numpy
6 import numpy
7 except:
7 except:
8 numpy = None
8 numpy = None
9 import pytest
9 import pytest
10
10
11 from IPython import get_ipython
11 from IPython import get_ipython
12 from traitlets.config import Config
12 from traitlets.config import Config
13 from IPython.core.formatters import (
13 from IPython.core.formatters import (
14 PlainTextFormatter, HTMLFormatter, PDFFormatter, _mod_name_key,
14 PlainTextFormatter, HTMLFormatter, PDFFormatter, _mod_name_key,
15 DisplayFormatter, JSONFormatter,
15 )
16 )
16 from IPython.utils.io import capture_output
17 from IPython.utils.io import capture_output
17
18
18 class A(object):
19 class A(object):
19 def __repr__(self):
20 def __repr__(self):
20 return 'A()'
21 return 'A()'
21
22
22 class B(A):
23 class B(A):
23 def __repr__(self):
24 def __repr__(self):
24 return 'B()'
25 return 'B()'
25
26
26 class C:
27 class C:
27 pass
28 pass
28
29
29 class BadRepr(object):
30 class BadRepr(object):
30 def __repr__(self):
31 def __repr__(self):
31 raise ValueError("bad repr")
32 raise ValueError("bad repr")
32
33
33 class BadPretty(object):
34 class BadPretty(object):
34 _repr_pretty_ = None
35 _repr_pretty_ = None
35
36
36 class GoodPretty(object):
37 class GoodPretty(object):
37 def _repr_pretty_(self, pp, cycle):
38 def _repr_pretty_(self, pp, cycle):
38 pp.text('foo')
39 pp.text('foo')
39
40
40 def __repr__(self):
41 def __repr__(self):
41 return 'GoodPretty()'
42 return 'GoodPretty()'
42
43
43 def foo_printer(obj, pp, cycle):
44 def foo_printer(obj, pp, cycle):
44 pp.text('foo')
45 pp.text('foo')
45
46
46 def test_pretty():
47 def test_pretty():
47 f = PlainTextFormatter()
48 f = PlainTextFormatter()
48 f.for_type(A, foo_printer)
49 f.for_type(A, foo_printer)
49 assert f(A()) == "foo"
50 assert f(A()) == "foo"
50 assert f(B()) == "B()"
51 assert f(B()) == "B()"
51 assert f(GoodPretty()) == "foo"
52 assert f(GoodPretty()) == "foo"
52 # Just don't raise an exception for the following:
53 # Just don't raise an exception for the following:
53 f(BadPretty())
54 f(BadPretty())
54
55
55 f.pprint = False
56 f.pprint = False
56 assert f(A()) == "A()"
57 assert f(A()) == "A()"
57 assert f(B()) == "B()"
58 assert f(B()) == "B()"
58 assert f(GoodPretty()) == "GoodPretty()"
59 assert f(GoodPretty()) == "GoodPretty()"
59
60
60
61
61 def test_deferred():
62 def test_deferred():
62 f = PlainTextFormatter()
63 f = PlainTextFormatter()
63
64
64 def test_precision():
65 def test_precision():
65 """test various values for float_precision."""
66 """test various values for float_precision."""
66 f = PlainTextFormatter()
67 f = PlainTextFormatter()
67 assert f(pi) == repr(pi)
68 assert f(pi) == repr(pi)
68 f.float_precision = 0
69 f.float_precision = 0
69 if numpy:
70 if numpy:
70 po = numpy.get_printoptions()
71 po = numpy.get_printoptions()
71 assert po["precision"] == 0
72 assert po["precision"] == 0
72 assert f(pi) == "3"
73 assert f(pi) == "3"
73 f.float_precision = 2
74 f.float_precision = 2
74 if numpy:
75 if numpy:
75 po = numpy.get_printoptions()
76 po = numpy.get_printoptions()
76 assert po["precision"] == 2
77 assert po["precision"] == 2
77 assert f(pi) == "3.14"
78 assert f(pi) == "3.14"
78 f.float_precision = "%g"
79 f.float_precision = "%g"
79 if numpy:
80 if numpy:
80 po = numpy.get_printoptions()
81 po = numpy.get_printoptions()
81 assert po["precision"] == 2
82 assert po["precision"] == 2
82 assert f(pi) == "3.14159"
83 assert f(pi) == "3.14159"
83 f.float_precision = "%e"
84 f.float_precision = "%e"
84 assert f(pi) == "3.141593e+00"
85 assert f(pi) == "3.141593e+00"
85 f.float_precision = ""
86 f.float_precision = ""
86 if numpy:
87 if numpy:
87 po = numpy.get_printoptions()
88 po = numpy.get_printoptions()
88 assert po["precision"] == 8
89 assert po["precision"] == 8
89 assert f(pi) == repr(pi)
90 assert f(pi) == repr(pi)
90
91
91
92
92 def test_bad_precision():
93 def test_bad_precision():
93 """test various invalid values for float_precision."""
94 """test various invalid values for float_precision."""
94 f = PlainTextFormatter()
95 f = PlainTextFormatter()
95 def set_fp(p):
96 def set_fp(p):
96 f.float_precision = p
97 f.float_precision = p
97
98
98 pytest.raises(ValueError, set_fp, "%")
99 pytest.raises(ValueError, set_fp, "%")
99 pytest.raises(ValueError, set_fp, "%.3f%i")
100 pytest.raises(ValueError, set_fp, "%.3f%i")
100 pytest.raises(ValueError, set_fp, "foo")
101 pytest.raises(ValueError, set_fp, "foo")
101 pytest.raises(ValueError, set_fp, -1)
102 pytest.raises(ValueError, set_fp, -1)
102
103
103 def test_for_type():
104 def test_for_type():
104 f = PlainTextFormatter()
105 f = PlainTextFormatter()
105
106
106 # initial return, None
107 # initial return, None
107 assert f.for_type(C, foo_printer) is None
108 assert f.for_type(C, foo_printer) is None
108 # no func queries
109 # no func queries
109 assert f.for_type(C) is foo_printer
110 assert f.for_type(C) is foo_printer
110 # shouldn't change anything
111 # shouldn't change anything
111 assert f.for_type(C) is foo_printer
112 assert f.for_type(C) is foo_printer
112 # None should do the same
113 # None should do the same
113 assert f.for_type(C, None) is foo_printer
114 assert f.for_type(C, None) is foo_printer
114 assert f.for_type(C, None) is foo_printer
115 assert f.for_type(C, None) is foo_printer
115
116
116 def test_for_type_string():
117 def test_for_type_string():
117 f = PlainTextFormatter()
118 f = PlainTextFormatter()
118
119
119 type_str = '%s.%s' % (C.__module__, 'C')
120 type_str = '%s.%s' % (C.__module__, 'C')
120
121
121 # initial return, None
122 # initial return, None
122 assert f.for_type(type_str, foo_printer) is None
123 assert f.for_type(type_str, foo_printer) is None
123 # no func queries
124 # no func queries
124 assert f.for_type(type_str) is foo_printer
125 assert f.for_type(type_str) is foo_printer
125 assert _mod_name_key(C) in f.deferred_printers
126 assert _mod_name_key(C) in f.deferred_printers
126 assert f.for_type(C) is foo_printer
127 assert f.for_type(C) is foo_printer
127 assert _mod_name_key(C) not in f.deferred_printers
128 assert _mod_name_key(C) not in f.deferred_printers
128 assert C in f.type_printers
129 assert C in f.type_printers
129
130
130 def test_for_type_by_name():
131 def test_for_type_by_name():
131 f = PlainTextFormatter()
132 f = PlainTextFormatter()
132
133
133 mod = C.__module__
134 mod = C.__module__
134
135
135 # initial return, None
136 # initial return, None
136 assert f.for_type_by_name(mod, "C", foo_printer) is None
137 assert f.for_type_by_name(mod, "C", foo_printer) is None
137 # no func queries
138 # no func queries
138 assert f.for_type_by_name(mod, "C") is foo_printer
139 assert f.for_type_by_name(mod, "C") is foo_printer
139 # shouldn't change anything
140 # shouldn't change anything
140 assert f.for_type_by_name(mod, "C") is foo_printer
141 assert f.for_type_by_name(mod, "C") is foo_printer
141 # None should do the same
142 # None should do the same
142 assert f.for_type_by_name(mod, "C", None) is foo_printer
143 assert f.for_type_by_name(mod, "C", None) is foo_printer
143 assert f.for_type_by_name(mod, "C", None) is foo_printer
144 assert f.for_type_by_name(mod, "C", None) is foo_printer
144
145
145
146
146 def test_lookup():
147 def test_lookup():
147 f = PlainTextFormatter()
148 f = PlainTextFormatter()
148
149
149 f.for_type(C, foo_printer)
150 f.for_type(C, foo_printer)
150 assert f.lookup(C()) is foo_printer
151 assert f.lookup(C()) is foo_printer
151 with pytest.raises(KeyError):
152 with pytest.raises(KeyError):
152 f.lookup(A())
153 f.lookup(A())
153
154
154 def test_lookup_string():
155 def test_lookup_string():
155 f = PlainTextFormatter()
156 f = PlainTextFormatter()
156 type_str = '%s.%s' % (C.__module__, 'C')
157 type_str = '%s.%s' % (C.__module__, 'C')
157
158
158 f.for_type(type_str, foo_printer)
159 f.for_type(type_str, foo_printer)
159 assert f.lookup(C()) is foo_printer
160 assert f.lookup(C()) is foo_printer
160 # should move from deferred to imported dict
161 # should move from deferred to imported dict
161 assert _mod_name_key(C) not in f.deferred_printers
162 assert _mod_name_key(C) not in f.deferred_printers
162 assert C in f.type_printers
163 assert C in f.type_printers
163
164
164 def test_lookup_by_type():
165 def test_lookup_by_type():
165 f = PlainTextFormatter()
166 f = PlainTextFormatter()
166 f.for_type(C, foo_printer)
167 f.for_type(C, foo_printer)
167 assert f.lookup_by_type(C) is foo_printer
168 assert f.lookup_by_type(C) is foo_printer
168 with pytest.raises(KeyError):
169 with pytest.raises(KeyError):
169 f.lookup_by_type(A)
170 f.lookup_by_type(A)
170
171
171 def test_lookup_by_type_string():
172 def test_lookup_by_type_string():
172 f = PlainTextFormatter()
173 f = PlainTextFormatter()
173 type_str = '%s.%s' % (C.__module__, 'C')
174 type_str = '%s.%s' % (C.__module__, 'C')
174 f.for_type(type_str, foo_printer)
175 f.for_type(type_str, foo_printer)
175
176
176 # verify insertion
177 # verify insertion
177 assert _mod_name_key(C) in f.deferred_printers
178 assert _mod_name_key(C) in f.deferred_printers
178 assert C not in f.type_printers
179 assert C not in f.type_printers
179
180
180 assert f.lookup_by_type(type_str) is foo_printer
181 assert f.lookup_by_type(type_str) is foo_printer
181 # lookup by string doesn't cause import
182 # lookup by string doesn't cause import
182 assert _mod_name_key(C) in f.deferred_printers
183 assert _mod_name_key(C) in f.deferred_printers
183 assert C not in f.type_printers
184 assert C not in f.type_printers
184
185
185 assert f.lookup_by_type(C) is foo_printer
186 assert f.lookup_by_type(C) is foo_printer
186 # should move from deferred to imported dict
187 # should move from deferred to imported dict
187 assert _mod_name_key(C) not in f.deferred_printers
188 assert _mod_name_key(C) not in f.deferred_printers
188 assert C in f.type_printers
189 assert C in f.type_printers
189
190
190 def test_in_formatter():
191 def test_in_formatter():
191 f = PlainTextFormatter()
192 f = PlainTextFormatter()
192 f.for_type(C, foo_printer)
193 f.for_type(C, foo_printer)
193 type_str = '%s.%s' % (C.__module__, 'C')
194 type_str = '%s.%s' % (C.__module__, 'C')
194 assert C in f
195 assert C in f
195 assert type_str in f
196 assert type_str in f
196
197
197 def test_string_in_formatter():
198 def test_string_in_formatter():
198 f = PlainTextFormatter()
199 f = PlainTextFormatter()
199 type_str = '%s.%s' % (C.__module__, 'C')
200 type_str = '%s.%s' % (C.__module__, 'C')
200 f.for_type(type_str, foo_printer)
201 f.for_type(type_str, foo_printer)
201 assert type_str in f
202 assert type_str in f
202 assert C in f
203 assert C in f
203
204
204 def test_pop():
205 def test_pop():
205 f = PlainTextFormatter()
206 f = PlainTextFormatter()
206 f.for_type(C, foo_printer)
207 f.for_type(C, foo_printer)
207 assert f.lookup_by_type(C) is foo_printer
208 assert f.lookup_by_type(C) is foo_printer
208 assert f.pop(C, None) is foo_printer
209 assert f.pop(C, None) is foo_printer
209 f.for_type(C, foo_printer)
210 f.for_type(C, foo_printer)
210 assert f.pop(C) is foo_printer
211 assert f.pop(C) is foo_printer
211 with pytest.raises(KeyError):
212 with pytest.raises(KeyError):
212 f.lookup_by_type(C)
213 f.lookup_by_type(C)
213 with pytest.raises(KeyError):
214 with pytest.raises(KeyError):
214 f.pop(C)
215 f.pop(C)
215 with pytest.raises(KeyError):
216 with pytest.raises(KeyError):
216 f.pop(A)
217 f.pop(A)
217 assert f.pop(A, None) is None
218 assert f.pop(A, None) is None
218
219
219 def test_pop_string():
220 def test_pop_string():
220 f = PlainTextFormatter()
221 f = PlainTextFormatter()
221 type_str = '%s.%s' % (C.__module__, 'C')
222 type_str = '%s.%s' % (C.__module__, 'C')
222
223
223 with pytest.raises(KeyError):
224 with pytest.raises(KeyError):
224 f.pop(type_str)
225 f.pop(type_str)
225
226
226 f.for_type(type_str, foo_printer)
227 f.for_type(type_str, foo_printer)
227 f.pop(type_str)
228 f.pop(type_str)
228 with pytest.raises(KeyError):
229 with pytest.raises(KeyError):
229 f.lookup_by_type(C)
230 f.lookup_by_type(C)
230 with pytest.raises(KeyError):
231 with pytest.raises(KeyError):
231 f.pop(type_str)
232 f.pop(type_str)
232
233
233 f.for_type(C, foo_printer)
234 f.for_type(C, foo_printer)
234 assert f.pop(type_str, None) is foo_printer
235 assert f.pop(type_str, None) is foo_printer
235 with pytest.raises(KeyError):
236 with pytest.raises(KeyError):
236 f.lookup_by_type(C)
237 f.lookup_by_type(C)
237 with pytest.raises(KeyError):
238 with pytest.raises(KeyError):
238 f.pop(type_str)
239 f.pop(type_str)
239 assert f.pop(type_str, None) is None
240 assert f.pop(type_str, None) is None
240
241
241
242
242 def test_error_method():
243 def test_error_method():
243 f = HTMLFormatter()
244 f = HTMLFormatter()
244 class BadHTML(object):
245 class BadHTML(object):
245 def _repr_html_(self):
246 def _repr_html_(self):
246 raise ValueError("Bad HTML")
247 raise ValueError("Bad HTML")
247 bad = BadHTML()
248 bad = BadHTML()
248 with capture_output() as captured:
249 with capture_output() as captured:
249 result = f(bad)
250 result = f(bad)
250 assert result is None
251 assert result is None
251 assert "Traceback" in captured.stdout
252 assert "Traceback" in captured.stdout
252 assert "Bad HTML" in captured.stdout
253 assert "Bad HTML" in captured.stdout
253 assert "_repr_html_" in captured.stdout
254 assert "_repr_html_" in captured.stdout
254
255
255 def test_nowarn_notimplemented():
256 def test_nowarn_notimplemented():
256 f = HTMLFormatter()
257 f = HTMLFormatter()
257 class HTMLNotImplemented(object):
258 class HTMLNotImplemented(object):
258 def _repr_html_(self):
259 def _repr_html_(self):
259 raise NotImplementedError
260 raise NotImplementedError
260 h = HTMLNotImplemented()
261 h = HTMLNotImplemented()
261 with capture_output() as captured:
262 with capture_output() as captured:
262 result = f(h)
263 result = f(h)
263 assert result is None
264 assert result is None
264 assert "" == captured.stderr
265 assert "" == captured.stderr
265 assert "" == captured.stdout
266 assert "" == captured.stdout
266
267
267
268
268 def test_warn_error_for_type():
269 def test_warn_error_for_type():
269 f = HTMLFormatter()
270 f = HTMLFormatter()
270 f.for_type(int, lambda i: name_error)
271 f.for_type(int, lambda i: name_error)
271 with capture_output() as captured:
272 with capture_output() as captured:
272 result = f(5)
273 result = f(5)
273 assert result is None
274 assert result is None
274 assert "Traceback" in captured.stdout
275 assert "Traceback" in captured.stdout
275 assert "NameError" in captured.stdout
276 assert "NameError" in captured.stdout
276 assert "name_error" in captured.stdout
277 assert "name_error" in captured.stdout
277
278
278 def test_error_pretty_method():
279 def test_error_pretty_method():
279 f = PlainTextFormatter()
280 f = PlainTextFormatter()
280 class BadPretty(object):
281 class BadPretty(object):
281 def _repr_pretty_(self):
282 def _repr_pretty_(self):
282 return "hello"
283 return "hello"
283 bad = BadPretty()
284 bad = BadPretty()
284 with capture_output() as captured:
285 with capture_output() as captured:
285 result = f(bad)
286 result = f(bad)
286 assert result is None
287 assert result is None
287 assert "Traceback" in captured.stdout
288 assert "Traceback" in captured.stdout
288 assert "_repr_pretty_" in captured.stdout
289 assert "_repr_pretty_" in captured.stdout
289 assert "given" in captured.stdout
290 assert "given" in captured.stdout
290 assert "argument" in captured.stdout
291 assert "argument" in captured.stdout
291
292
292
293
293 def test_bad_repr_traceback():
294 def test_bad_repr_traceback():
294 f = PlainTextFormatter()
295 f = PlainTextFormatter()
295 bad = BadRepr()
296 bad = BadRepr()
296 with capture_output() as captured:
297 with capture_output() as captured:
297 result = f(bad)
298 result = f(bad)
298 # catches error, returns None
299 # catches error, returns None
299 assert result is None
300 assert result is None
300 assert "Traceback" in captured.stdout
301 assert "Traceback" in captured.stdout
301 assert "__repr__" in captured.stdout
302 assert "__repr__" in captured.stdout
302 assert "ValueError" in captured.stdout
303 assert "ValueError" in captured.stdout
303
304
304
305
305 class MakePDF(object):
306 class MakePDF(object):
306 def _repr_pdf_(self):
307 def _repr_pdf_(self):
307 return 'PDF'
308 return 'PDF'
308
309
309 def test_pdf_formatter():
310 def test_pdf_formatter():
310 pdf = MakePDF()
311 pdf = MakePDF()
311 f = PDFFormatter()
312 f = PDFFormatter()
312 assert f(pdf) == "PDF"
313 assert f(pdf) == "PDF"
313
314
314
315
315 def test_print_method_bound():
316 def test_print_method_bound():
316 f = HTMLFormatter()
317 f = HTMLFormatter()
317 class MyHTML(object):
318 class MyHTML(object):
318 def _repr_html_(self):
319 def _repr_html_(self):
319 return "hello"
320 return "hello"
320 with capture_output() as captured:
321 with capture_output() as captured:
321 result = f(MyHTML)
322 result = f(MyHTML)
322 assert result is None
323 assert result is None
323 assert "FormatterWarning" not in captured.stderr
324 assert "FormatterWarning" not in captured.stderr
324
325
325 with capture_output() as captured:
326 with capture_output() as captured:
326 result = f(MyHTML())
327 result = f(MyHTML())
327 assert result == "hello"
328 assert result == "hello"
328 assert captured.stderr == ""
329 assert captured.stderr == ""
329
330
330
331
331 def test_print_method_weird():
332 def test_print_method_weird():
332
333
333 class TextMagicHat(object):
334 class TextMagicHat(object):
334 def __getattr__(self, key):
335 def __getattr__(self, key):
335 return key
336 return key
336
337
337 f = HTMLFormatter()
338 f = HTMLFormatter()
338
339
339 text_hat = TextMagicHat()
340 text_hat = TextMagicHat()
340 assert text_hat._repr_html_ == "_repr_html_"
341 assert text_hat._repr_html_ == "_repr_html_"
341 with capture_output() as captured:
342 with capture_output() as captured:
342 result = f(text_hat)
343 result = f(text_hat)
343
344
344 assert result is None
345 assert result is None
345 assert "FormatterWarning" not in captured.stderr
346 assert "FormatterWarning" not in captured.stderr
346
347
347 class CallableMagicHat(object):
348 class CallableMagicHat(object):
348 def __getattr__(self, key):
349 def __getattr__(self, key):
349 return lambda : key
350 return lambda : key
350
351
351 call_hat = CallableMagicHat()
352 call_hat = CallableMagicHat()
352 with capture_output() as captured:
353 with capture_output() as captured:
353 result = f(call_hat)
354 result = f(call_hat)
354
355
355 assert result is None
356 assert result is None
356
357
357 class BadReprArgs(object):
358 class BadReprArgs(object):
358 def _repr_html_(self, extra, args):
359 def _repr_html_(self, extra, args):
359 return "html"
360 return "html"
360
361
361 bad = BadReprArgs()
362 bad = BadReprArgs()
362 with capture_output() as captured:
363 with capture_output() as captured:
363 result = f(bad)
364 result = f(bad)
364
365
365 assert result is None
366 assert result is None
366 assert "FormatterWarning" not in captured.stderr
367 assert "FormatterWarning" not in captured.stderr
367
368
368
369
369 def test_format_config():
370 def test_format_config():
370 """config objects don't pretend to support fancy reprs with lazy attrs"""
371 """config objects don't pretend to support fancy reprs with lazy attrs"""
371 f = HTMLFormatter()
372 f = HTMLFormatter()
372 cfg = Config()
373 cfg = Config()
373 with capture_output() as captured:
374 with capture_output() as captured:
374 result = f(cfg)
375 result = f(cfg)
375 assert result is None
376 assert result is None
376 assert captured.stderr == ""
377 assert captured.stderr == ""
377
378
378 with capture_output() as captured:
379 with capture_output() as captured:
379 result = f(Config)
380 result = f(Config)
380 assert result is None
381 assert result is None
381 assert captured.stderr == ""
382 assert captured.stderr == ""
382
383
383
384
384 def test_pretty_max_seq_length():
385 def test_pretty_max_seq_length():
385 f = PlainTextFormatter(max_seq_length=1)
386 f = PlainTextFormatter(max_seq_length=1)
386 lis = list(range(3))
387 lis = list(range(3))
387 text = f(lis)
388 text = f(lis)
388 assert text == "[0, ...]"
389 assert text == "[0, ...]"
389 f.max_seq_length = 0
390 f.max_seq_length = 0
390 text = f(lis)
391 text = f(lis)
391 assert text == "[0, 1, 2]"
392 assert text == "[0, 1, 2]"
392 text = f(list(range(1024)))
393 text = f(list(range(1024)))
393 lines = text.splitlines()
394 lines = text.splitlines()
394 assert len(lines) == 1024
395 assert len(lines) == 1024
395
396
396
397
397 def test_ipython_display_formatter():
398 def test_ipython_display_formatter():
398 """Objects with _ipython_display_ defined bypass other formatters"""
399 """Objects with _ipython_display_ defined bypass other formatters"""
399 f = get_ipython().display_formatter
400 f = get_ipython().display_formatter
400 catcher = []
401 catcher = []
401 class SelfDisplaying(object):
402 class SelfDisplaying(object):
402 def _ipython_display_(self):
403 def _ipython_display_(self):
403 catcher.append(self)
404 catcher.append(self)
404
405
405 class NotSelfDisplaying(object):
406 class NotSelfDisplaying(object):
406 def __repr__(self):
407 def __repr__(self):
407 return "NotSelfDisplaying"
408 return "NotSelfDisplaying"
408
409
409 def _ipython_display_(self):
410 def _ipython_display_(self):
410 raise NotImplementedError
411 raise NotImplementedError
411
412
412 save_enabled = f.ipython_display_formatter.enabled
413 save_enabled = f.ipython_display_formatter.enabled
413 f.ipython_display_formatter.enabled = True
414 f.ipython_display_formatter.enabled = True
414
415
415 yes = SelfDisplaying()
416 yes = SelfDisplaying()
416 no = NotSelfDisplaying()
417 no = NotSelfDisplaying()
417
418
418 d, md = f.format(no)
419 d, md = f.format(no)
419 assert d == {"text/plain": repr(no)}
420 assert d == {"text/plain": repr(no)}
420 assert md == {}
421 assert md == {}
421 assert catcher == []
422 assert catcher == []
422
423
423 d, md = f.format(yes)
424 d, md = f.format(yes)
424 assert d == {}
425 assert d == {}
425 assert md == {}
426 assert md == {}
426 assert catcher == [yes]
427 assert catcher == [yes]
427
428
428 f.ipython_display_formatter.enabled = save_enabled
429 f.ipython_display_formatter.enabled = save_enabled
429
430
430
431
431 def test_repr_mime():
432 def test_repr_mime():
432 class HasReprMime(object):
433 class HasReprMime(object):
433 def _repr_mimebundle_(self, include=None, exclude=None):
434 def _repr_mimebundle_(self, include=None, exclude=None):
434 return {
435 return {
435 'application/json+test.v2': {
436 'application/json+test.v2': {
436 'x': 'y'
437 'x': 'y'
437 },
438 },
438 'plain/text' : '<HasReprMime>',
439 'plain/text' : '<HasReprMime>',
439 'image/png' : 'i-overwrite'
440 'image/png' : 'i-overwrite'
440 }
441 }
441
442
442 def _repr_png_(self):
443 def _repr_png_(self):
443 return 'should-be-overwritten'
444 return 'should-be-overwritten'
444 def _repr_html_(self):
445 def _repr_html_(self):
445 return '<b>hi!</b>'
446 return '<b>hi!</b>'
446
447
447 f = get_ipython().display_formatter
448 f = get_ipython().display_formatter
448 html_f = f.formatters['text/html']
449 html_f = f.formatters['text/html']
449 save_enabled = html_f.enabled
450 save_enabled = html_f.enabled
450 html_f.enabled = True
451 html_f.enabled = True
451 obj = HasReprMime()
452 obj = HasReprMime()
452 d, md = f.format(obj)
453 d, md = f.format(obj)
453 html_f.enabled = save_enabled
454 html_f.enabled = save_enabled
454
455
455 assert sorted(d) == [
456 assert sorted(d) == [
456 "application/json+test.v2",
457 "application/json+test.v2",
457 "image/png",
458 "image/png",
458 "plain/text",
459 "plain/text",
459 "text/html",
460 "text/html",
460 "text/plain",
461 "text/plain",
461 ]
462 ]
462 assert md == {}
463 assert md == {}
463
464
464 d, md = f.format(obj, include={"image/png"})
465 d, md = f.format(obj, include={"image/png"})
465 assert list(d.keys()) == [
466 assert list(d.keys()) == [
466 "image/png"
467 "image/png"
467 ], "Include should filter out even things from repr_mimebundle"
468 ], "Include should filter out even things from repr_mimebundle"
468
469
469 assert d["image/png"] == "i-overwrite", "_repr_mimebundle_ take precedence"
470 assert d["image/png"] == "i-overwrite", "_repr_mimebundle_ take precedence"
470
471
471
472
472 def test_pass_correct_include_exclude():
473 def test_pass_correct_include_exclude():
473 class Tester(object):
474 class Tester(object):
474
475
475 def __init__(self, include=None, exclude=None):
476 def __init__(self, include=None, exclude=None):
476 self.include = include
477 self.include = include
477 self.exclude = exclude
478 self.exclude = exclude
478
479
479 def _repr_mimebundle_(self, include, exclude, **kwargs):
480 def _repr_mimebundle_(self, include, exclude, **kwargs):
480 if include and (include != self.include):
481 if include and (include != self.include):
481 raise ValueError('include got modified: display() may be broken.')
482 raise ValueError('include got modified: display() may be broken.')
482 if exclude and (exclude != self.exclude):
483 if exclude and (exclude != self.exclude):
483 raise ValueError('exclude got modified: display() may be broken.')
484 raise ValueError('exclude got modified: display() may be broken.')
484
485
485 return None
486 return None
486
487
487 include = {'a', 'b', 'c'}
488 include = {'a', 'b', 'c'}
488 exclude = {'c', 'e' , 'f'}
489 exclude = {'c', 'e' , 'f'}
489
490
490 f = get_ipython().display_formatter
491 f = get_ipython().display_formatter
491 f.format(Tester(include=include, exclude=exclude), include=include, exclude=exclude)
492 f.format(Tester(include=include, exclude=exclude), include=include, exclude=exclude)
492 f.format(Tester(exclude=exclude), exclude=exclude)
493 f.format(Tester(exclude=exclude), exclude=exclude)
493 f.format(Tester(include=include), include=include)
494 f.format(Tester(include=include), include=include)
494
495
495
496
496 def test_repr_mime_meta():
497 def test_repr_mime_meta():
497 class HasReprMimeMeta(object):
498 class HasReprMimeMeta(object):
498 def _repr_mimebundle_(self, include=None, exclude=None):
499 def _repr_mimebundle_(self, include=None, exclude=None):
499 data = {
500 data = {
500 'image/png': 'base64-image-data',
501 'image/png': 'base64-image-data',
501 }
502 }
502 metadata = {
503 metadata = {
503 'image/png': {
504 'image/png': {
504 'width': 5,
505 'width': 5,
505 'height': 10,
506 'height': 10,
506 }
507 }
507 }
508 }
508 return (data, metadata)
509 return (data, metadata)
509
510
510 f = get_ipython().display_formatter
511 f = get_ipython().display_formatter
511 obj = HasReprMimeMeta()
512 obj = HasReprMimeMeta()
512 d, md = f.format(obj)
513 d, md = f.format(obj)
513 assert sorted(d) == ["image/png", "text/plain"]
514 assert sorted(d) == ["image/png", "text/plain"]
514 assert md == {
515 assert md == {
515 "image/png": {
516 "image/png": {
516 "width": 5,
517 "width": 5,
517 "height": 10,
518 "height": 10,
518 }
519 }
519 }
520 }
520
521
521
522
522 def test_repr_mime_failure():
523 def test_repr_mime_failure():
523 class BadReprMime(object):
524 class BadReprMime(object):
524 def _repr_mimebundle_(self, include=None, exclude=None):
525 def _repr_mimebundle_(self, include=None, exclude=None):
525 raise RuntimeError
526 raise RuntimeError
526
527
527 f = get_ipython().display_formatter
528 f = get_ipython().display_formatter
528 obj = BadReprMime()
529 obj = BadReprMime()
529 d, md = f.format(obj)
530 d, md = f.format(obj)
530 assert "text/plain" in d
531 assert "text/plain" in d
531
532
532
533
533 def test_custom_repr_namedtuple_partialmethod():
534 def test_custom_repr_namedtuple_partialmethod():
534 from functools import partialmethod
535 from functools import partialmethod
535 from typing import NamedTuple
536 from typing import NamedTuple
536
537
537 class Foo(NamedTuple): ...
538 class Foo(NamedTuple): ...
538
539
539 Foo.__repr__ = partialmethod(lambda obj: "Hello World")
540 Foo.__repr__ = partialmethod(lambda obj: "Hello World")
540 foo = Foo()
541 foo = Foo()
541
542
542 f = PlainTextFormatter()
543 f = PlainTextFormatter()
543 assert f.pprint
544 assert f.pprint
544 assert f(foo) == "Hello World"
545 assert f(foo) == "Hello World"
@@ -1,74 +1,75
1 """Tests for input handlers.
1 """Tests for input handlers.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Module imports
4 # Module imports
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6
6
7 # our own packages
7 # our own packages
8 from IPython.core import autocall
8 from IPython.core import autocall
9 from IPython.testing import tools as tt
9 import pytest
10 import pytest
10 from collections.abc import Callable
11 from collections.abc import Callable
11
12
12 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
13 # Globals
14 # Globals
14 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
15
16
16 # Test functions
17 # Test functions
17 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
18
19
19 class CallableIndexable(object):
20 class CallableIndexable(object):
20 def __getitem__(self, idx): return True
21 def __getitem__(self, idx): return True
21 def __call__(self, *args, **kws): return True
22 def __call__(self, *args, **kws): return True
22
23
23
24
24 class Autocallable(autocall.IPyAutocall):
25 class Autocallable(autocall.IPyAutocall):
25 def __call__(self):
26 def __call__(self):
26 return "called"
27 return "called"
27
28
28
29
29 @pytest.mark.parametrize(
30 @pytest.mark.parametrize(
30 "autocall, input, output",
31 "autocall, input, output",
31 [
32 [
32 # For many of the below, we're also checking that leading whitespace
33 # For many of the below, we're also checking that leading whitespace
33 # turns off the esc char, which it should unless there is a continuation
34 # turns off the esc char, which it should unless there is a continuation
34 # line.
35 # line.
35 ("1", '"no change"', '"no change"'), # normal
36 ("1", '"no change"', '"no change"'), # normal
36 ("1", "lsmagic", "get_ipython().run_line_magic('lsmagic', '')"), # magic
37 ("1", "lsmagic", "get_ipython().run_line_magic('lsmagic', '')"), # magic
37 # Only explicit escapes or instances of IPyAutocallable should get
38 # Only explicit escapes or instances of IPyAutocallable should get
38 # expanded
39 # expanded
39 ("0", 'len "abc"', 'len "abc"'),
40 ("0", 'len "abc"', 'len "abc"'),
40 ("0", "autocallable", "autocallable()"),
41 ("0", "autocallable", "autocallable()"),
41 # Don't add extra brackets (gh-1117)
42 # Don't add extra brackets (gh-1117)
42 ("0", "autocallable()", "autocallable()"),
43 ("0", "autocallable()", "autocallable()"),
43 ("1", 'len "abc"', 'len("abc")'),
44 ("1", 'len "abc"', 'len("abc")'),
44 ("1", 'len "abc";', 'len("abc");'), # ; is special -- moves out of parens
45 ("1", 'len "abc";', 'len("abc");'), # ; is special -- moves out of parens
45 # Autocall is turned off if first arg is [] and the object
46 # Autocall is turned off if first arg is [] and the object
46 # is both callable and indexable. Like so:
47 # is both callable and indexable. Like so:
47 ("1", "len [1,2]", "len([1,2])"), # len doesn't support __getitem__...
48 ("1", "len [1,2]", "len([1,2])"), # len doesn't support __getitem__...
48 ("1", "call_idx [1]", "call_idx [1]"), # call_idx *does*..
49 ("1", "call_idx [1]", "call_idx [1]"), # call_idx *does*..
49 ("1", "call_idx 1", "call_idx(1)"),
50 ("1", "call_idx 1", "call_idx(1)"),
50 ("1", "len", "len"), # only at 2 does it auto-call on single args
51 ("1", "len", "len"), # only at 2 does it auto-call on single args
51 ("2", 'len "abc"', 'len("abc")'),
52 ("2", 'len "abc"', 'len("abc")'),
52 ("2", 'len "abc";', 'len("abc");'),
53 ("2", 'len "abc";', 'len("abc");'),
53 ("2", "len [1,2]", "len([1,2])"),
54 ("2", "len [1,2]", "len([1,2])"),
54 ("2", "call_idx [1]", "call_idx [1]"),
55 ("2", "call_idx [1]", "call_idx [1]"),
55 ("2", "call_idx 1", "call_idx(1)"),
56 ("2", "call_idx 1", "call_idx(1)"),
56 # T his is what's different:
57 # T his is what's different:
57 ("2", "len", "len()"), # only at 2 does it auto-call on single args
58 ("2", "len", "len()"), # only at 2 does it auto-call on single args
58 ("0", "Callable[[int], None]", "Callable[[int], None]"),
59 ("0", "Callable[[int], None]", "Callable[[int], None]"),
59 ("1", "Callable[[int], None]", "Callable[[int], None]"),
60 ("1", "Callable[[int], None]", "Callable[[int], None]"),
60 ("1", "Callable[[int], None]", "Callable[[int], None]"),
61 ("1", "Callable[[int], None]", "Callable[[int], None]"),
61 ],
62 ],
62 )
63 )
63 def test_handlers_I(autocall, input, output):
64 def test_handlers_I(autocall, input, output):
64 autocallable = Autocallable()
65 autocallable = Autocallable()
65 ip.user_ns["autocallable"] = autocallable
66 ip.user_ns["autocallable"] = autocallable
66
67
67 call_idx = CallableIndexable()
68 call_idx = CallableIndexable()
68 ip.user_ns["call_idx"] = call_idx
69 ip.user_ns["call_idx"] = call_idx
69
70
70 ip.user_ns["Callable"] = Callable
71 ip.user_ns["Callable"] = Callable
71
72
72 ip.run_line_magic("autocall", autocall)
73 ip.run_line_magic("autocall", autocall)
73 assert ip.prefilter_manager.prefilter_lines(input) == output
74 assert ip.prefilter_manager.prefilter_lines(input) == output
74 ip.run_line_magic("autocall", "1")
75 ip.run_line_magic("autocall", "1")
@@ -1,1220 +1,1221
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for the key interactiveshell module.
2 """Tests for the key interactiveshell module.
3
3
4 Historically the main classes in interactiveshell have been under-tested. This
4 Historically the main classes in interactiveshell have been under-tested. This
5 module should grow as many single-method tests as possible to trap many of the
5 module should grow as many single-method tests as possible to trap many of the
6 recurring bugs we seem to encounter with high-level interaction.
6 recurring bugs we seem to encounter with high-level interaction.
7 """
7 """
8
8
9 # Copyright (c) IPython Development Team.
9 # Copyright (c) IPython Development Team.
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11
11
12 import asyncio
12 import asyncio
13 import ast
13 import ast
14 import os
14 import os
15 import signal
15 import signal
16 import shutil
16 import shutil
17 import sys
17 import sys
18 import tempfile
18 import tempfile
19 import unittest
19 import unittest
20 import pytest
20 import pytest
21 from unittest import mock
21 from unittest import mock
22
22
23 from os.path import join
23 from os.path import join
24
24
25 from IPython.core.error import InputRejected
25 from IPython.core.error import InputRejected
26 from IPython.core.inputtransformer import InputTransformer
26 from IPython.core import interactiveshell
27 from IPython.core import interactiveshell
27 from IPython.core.oinspect import OInfo
28 from IPython.core.oinspect import OInfo
28 from IPython.testing.decorators import (
29 from IPython.testing.decorators import (
29 skipif,
30 skipif,
30 skip_win32,
31 skip_win32,
31 onlyif_unicode_paths,
32 onlyif_unicode_paths,
32 onlyif_cmds_exist,
33 onlyif_cmds_exist,
33 skip_if_not_osx,
34 skip_if_not_osx,
34 )
35 )
35 from IPython.testing import tools as tt
36 from IPython.testing import tools as tt
36 from IPython.utils.process import find_cmd
37 from IPython.utils.process import find_cmd
37
38
38 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
39 # Globals
40 # Globals
40 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
41 # This is used by every single test, no point repeating it ad nauseam
42 # This is used by every single test, no point repeating it ad nauseam
42
43
43 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
44 # Tests
45 # Tests
45 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
46
47
47 class DerivedInterrupt(KeyboardInterrupt):
48 class DerivedInterrupt(KeyboardInterrupt):
48 pass
49 pass
49
50
50 class InteractiveShellTestCase(unittest.TestCase):
51 class InteractiveShellTestCase(unittest.TestCase):
51 def test_naked_string_cells(self):
52 def test_naked_string_cells(self):
52 """Test that cells with only naked strings are fully executed"""
53 """Test that cells with only naked strings are fully executed"""
53 # First, single-line inputs
54 # First, single-line inputs
54 ip.run_cell('"a"\n')
55 ip.run_cell('"a"\n')
55 self.assertEqual(ip.user_ns['_'], 'a')
56 self.assertEqual(ip.user_ns['_'], 'a')
56 # And also multi-line cells
57 # And also multi-line cells
57 ip.run_cell('"""a\nb"""\n')
58 ip.run_cell('"""a\nb"""\n')
58 self.assertEqual(ip.user_ns['_'], 'a\nb')
59 self.assertEqual(ip.user_ns['_'], 'a\nb')
59
60
60 def test_run_empty_cell(self):
61 def test_run_empty_cell(self):
61 """Just make sure we don't get a horrible error with a blank
62 """Just make sure we don't get a horrible error with a blank
62 cell of input. Yes, I did overlook that."""
63 cell of input. Yes, I did overlook that."""
63 old_xc = ip.execution_count
64 old_xc = ip.execution_count
64 res = ip.run_cell('')
65 res = ip.run_cell('')
65 self.assertEqual(ip.execution_count, old_xc)
66 self.assertEqual(ip.execution_count, old_xc)
66 self.assertEqual(res.execution_count, None)
67 self.assertEqual(res.execution_count, None)
67
68
68 def test_run_cell_multiline(self):
69 def test_run_cell_multiline(self):
69 """Multi-block, multi-line cells must execute correctly.
70 """Multi-block, multi-line cells must execute correctly.
70 """
71 """
71 src = '\n'.join(["x=1",
72 src = '\n'.join(["x=1",
72 "y=2",
73 "y=2",
73 "if 1:",
74 "if 1:",
74 " x += 1",
75 " x += 1",
75 " y += 1",])
76 " y += 1",])
76 res = ip.run_cell(src)
77 res = ip.run_cell(src)
77 self.assertEqual(ip.user_ns['x'], 2)
78 self.assertEqual(ip.user_ns['x'], 2)
78 self.assertEqual(ip.user_ns['y'], 3)
79 self.assertEqual(ip.user_ns['y'], 3)
79 self.assertEqual(res.success, True)
80 self.assertEqual(res.success, True)
80 self.assertEqual(res.result, None)
81 self.assertEqual(res.result, None)
81
82
82 def test_multiline_string_cells(self):
83 def test_multiline_string_cells(self):
83 "Code sprinkled with multiline strings should execute (GH-306)"
84 "Code sprinkled with multiline strings should execute (GH-306)"
84 ip.run_cell('tmp=0')
85 ip.run_cell('tmp=0')
85 self.assertEqual(ip.user_ns['tmp'], 0)
86 self.assertEqual(ip.user_ns['tmp'], 0)
86 res = ip.run_cell('tmp=1;"""a\nb"""\n')
87 res = ip.run_cell('tmp=1;"""a\nb"""\n')
87 self.assertEqual(ip.user_ns['tmp'], 1)
88 self.assertEqual(ip.user_ns['tmp'], 1)
88 self.assertEqual(res.success, True)
89 self.assertEqual(res.success, True)
89 self.assertEqual(res.result, "a\nb")
90 self.assertEqual(res.result, "a\nb")
90
91
91 def test_dont_cache_with_semicolon(self):
92 def test_dont_cache_with_semicolon(self):
92 "Ending a line with semicolon should not cache the returned object (GH-307)"
93 "Ending a line with semicolon should not cache the returned object (GH-307)"
93 oldlen = len(ip.user_ns['Out'])
94 oldlen = len(ip.user_ns['Out'])
94 for cell in ['1;', '1;1;']:
95 for cell in ['1;', '1;1;']:
95 res = ip.run_cell(cell, store_history=True)
96 res = ip.run_cell(cell, store_history=True)
96 newlen = len(ip.user_ns['Out'])
97 newlen = len(ip.user_ns['Out'])
97 self.assertEqual(oldlen, newlen)
98 self.assertEqual(oldlen, newlen)
98 self.assertIsNone(res.result)
99 self.assertIsNone(res.result)
99 i = 0
100 i = 0
100 #also test the default caching behavior
101 #also test the default caching behavior
101 for cell in ['1', '1;1']:
102 for cell in ['1', '1;1']:
102 ip.run_cell(cell, store_history=True)
103 ip.run_cell(cell, store_history=True)
103 newlen = len(ip.user_ns['Out'])
104 newlen = len(ip.user_ns['Out'])
104 i += 1
105 i += 1
105 self.assertEqual(oldlen+i, newlen)
106 self.assertEqual(oldlen+i, newlen)
106
107
107 def test_syntax_error(self):
108 def test_syntax_error(self):
108 res = ip.run_cell("raise = 3")
109 res = ip.run_cell("raise = 3")
109 self.assertIsInstance(res.error_before_exec, SyntaxError)
110 self.assertIsInstance(res.error_before_exec, SyntaxError)
110
111
111 def test_open_standard_input_stream(self):
112 def test_open_standard_input_stream(self):
112 res = ip.run_cell("open(0)")
113 res = ip.run_cell("open(0)")
113 self.assertIsInstance(res.error_in_exec, ValueError)
114 self.assertIsInstance(res.error_in_exec, ValueError)
114
115
115 def test_open_standard_output_stream(self):
116 def test_open_standard_output_stream(self):
116 res = ip.run_cell("open(1)")
117 res = ip.run_cell("open(1)")
117 self.assertIsInstance(res.error_in_exec, ValueError)
118 self.assertIsInstance(res.error_in_exec, ValueError)
118
119
119 def test_open_standard_error_stream(self):
120 def test_open_standard_error_stream(self):
120 res = ip.run_cell("open(2)")
121 res = ip.run_cell("open(2)")
121 self.assertIsInstance(res.error_in_exec, ValueError)
122 self.assertIsInstance(res.error_in_exec, ValueError)
122
123
123 def test_In_variable(self):
124 def test_In_variable(self):
124 "Verify that In variable grows with user input (GH-284)"
125 "Verify that In variable grows with user input (GH-284)"
125 oldlen = len(ip.user_ns['In'])
126 oldlen = len(ip.user_ns['In'])
126 ip.run_cell('1;', store_history=True)
127 ip.run_cell('1;', store_history=True)
127 newlen = len(ip.user_ns['In'])
128 newlen = len(ip.user_ns['In'])
128 self.assertEqual(oldlen+1, newlen)
129 self.assertEqual(oldlen+1, newlen)
129 self.assertEqual(ip.user_ns['In'][-1],'1;')
130 self.assertEqual(ip.user_ns['In'][-1],'1;')
130
131
131 def test_magic_names_in_string(self):
132 def test_magic_names_in_string(self):
132 ip.run_cell('a = """\n%exit\n"""')
133 ip.run_cell('a = """\n%exit\n"""')
133 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
134 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
134
135
135 def test_trailing_newline(self):
136 def test_trailing_newline(self):
136 """test that running !(command) does not raise a SyntaxError"""
137 """test that running !(command) does not raise a SyntaxError"""
137 ip.run_cell('!(true)\n', False)
138 ip.run_cell('!(true)\n', False)
138 ip.run_cell('!(true)\n\n\n', False)
139 ip.run_cell('!(true)\n\n\n', False)
139
140
140 def test_gh_597(self):
141 def test_gh_597(self):
141 """Pretty-printing lists of objects with non-ascii reprs may cause
142 """Pretty-printing lists of objects with non-ascii reprs may cause
142 problems."""
143 problems."""
143 class Spam(object):
144 class Spam(object):
144 def __repr__(self):
145 def __repr__(self):
145 return "\xe9"*50
146 return "\xe9"*50
146 import IPython.core.formatters
147 import IPython.core.formatters
147 f = IPython.core.formatters.PlainTextFormatter()
148 f = IPython.core.formatters.PlainTextFormatter()
148 f([Spam(), Spam()])
149 f([Spam(), Spam()])
149
150
150 def test_future_flags(self):
151 def test_future_flags(self):
151 """Check that future flags are used for parsing code (gh-777)"""
152 """Check that future flags are used for parsing code (gh-777)"""
152 ip.run_cell('from __future__ import barry_as_FLUFL')
153 ip.run_cell('from __future__ import barry_as_FLUFL')
153 try:
154 try:
154 ip.run_cell('prfunc_return_val = 1 <> 2')
155 ip.run_cell('prfunc_return_val = 1 <> 2')
155 assert 'prfunc_return_val' in ip.user_ns
156 assert 'prfunc_return_val' in ip.user_ns
156 finally:
157 finally:
157 # Reset compiler flags so we don't mess up other tests.
158 # Reset compiler flags so we don't mess up other tests.
158 ip.compile.reset_compiler_flags()
159 ip.compile.reset_compiler_flags()
159
160
160 def test_can_pickle(self):
161 def test_can_pickle(self):
161 "Can we pickle objects defined interactively (GH-29)"
162 "Can we pickle objects defined interactively (GH-29)"
162 ip = get_ipython()
163 ip = get_ipython()
163 ip.reset()
164 ip.reset()
164 ip.run_cell(("class Mylist(list):\n"
165 ip.run_cell(("class Mylist(list):\n"
165 " def __init__(self,x=[]):\n"
166 " def __init__(self,x=[]):\n"
166 " list.__init__(self,x)"))
167 " list.__init__(self,x)"))
167 ip.run_cell("w=Mylist([1,2,3])")
168 ip.run_cell("w=Mylist([1,2,3])")
168
169
169 from pickle import dumps
170 from pickle import dumps
170
171
171 # We need to swap in our main module - this is only necessary
172 # We need to swap in our main module - this is only necessary
172 # inside the test framework, because IPython puts the interactive module
173 # inside the test framework, because IPython puts the interactive module
173 # in place (but the test framework undoes this).
174 # in place (but the test framework undoes this).
174 _main = sys.modules['__main__']
175 _main = sys.modules['__main__']
175 sys.modules['__main__'] = ip.user_module
176 sys.modules['__main__'] = ip.user_module
176 try:
177 try:
177 res = dumps(ip.user_ns["w"])
178 res = dumps(ip.user_ns["w"])
178 finally:
179 finally:
179 sys.modules['__main__'] = _main
180 sys.modules['__main__'] = _main
180 self.assertTrue(isinstance(res, bytes))
181 self.assertTrue(isinstance(res, bytes))
181
182
182 def test_global_ns(self):
183 def test_global_ns(self):
183 "Code in functions must be able to access variables outside them."
184 "Code in functions must be able to access variables outside them."
184 ip = get_ipython()
185 ip = get_ipython()
185 ip.run_cell("a = 10")
186 ip.run_cell("a = 10")
186 ip.run_cell(("def f(x):\n"
187 ip.run_cell(("def f(x):\n"
187 " return x + a"))
188 " return x + a"))
188 ip.run_cell("b = f(12)")
189 ip.run_cell("b = f(12)")
189 self.assertEqual(ip.user_ns["b"], 22)
190 self.assertEqual(ip.user_ns["b"], 22)
190
191
191 def test_bad_custom_tb(self):
192 def test_bad_custom_tb(self):
192 """Check that InteractiveShell is protected from bad custom exception handlers"""
193 """Check that InteractiveShell is protected from bad custom exception handlers"""
193 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
194 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
194 self.assertEqual(ip.custom_exceptions, (IOError,))
195 self.assertEqual(ip.custom_exceptions, (IOError,))
195 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
196 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
196 ip.run_cell(u'raise IOError("foo")')
197 ip.run_cell(u'raise IOError("foo")')
197 self.assertEqual(ip.custom_exceptions, ())
198 self.assertEqual(ip.custom_exceptions, ())
198
199
199 def test_bad_custom_tb_return(self):
200 def test_bad_custom_tb_return(self):
200 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
201 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
201 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
202 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
202 self.assertEqual(ip.custom_exceptions, (NameError,))
203 self.assertEqual(ip.custom_exceptions, (NameError,))
203 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
204 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
204 ip.run_cell(u'a=abracadabra')
205 ip.run_cell(u'a=abracadabra')
205 self.assertEqual(ip.custom_exceptions, ())
206 self.assertEqual(ip.custom_exceptions, ())
206
207
207 def test_drop_by_id(self):
208 def test_drop_by_id(self):
208 myvars = {"a":object(), "b":object(), "c": object()}
209 myvars = {"a":object(), "b":object(), "c": object()}
209 ip.push(myvars, interactive=False)
210 ip.push(myvars, interactive=False)
210 for name in myvars:
211 for name in myvars:
211 assert name in ip.user_ns, name
212 assert name in ip.user_ns, name
212 assert name in ip.user_ns_hidden, name
213 assert name in ip.user_ns_hidden, name
213 ip.user_ns['b'] = 12
214 ip.user_ns['b'] = 12
214 ip.drop_by_id(myvars)
215 ip.drop_by_id(myvars)
215 for name in ["a", "c"]:
216 for name in ["a", "c"]:
216 assert name not in ip.user_ns, name
217 assert name not in ip.user_ns, name
217 assert name not in ip.user_ns_hidden, name
218 assert name not in ip.user_ns_hidden, name
218 assert ip.user_ns['b'] == 12
219 assert ip.user_ns['b'] == 12
219 ip.reset()
220 ip.reset()
220
221
221 def test_var_expand(self):
222 def test_var_expand(self):
222 ip.user_ns['f'] = u'Ca\xf1o'
223 ip.user_ns['f'] = u'Ca\xf1o'
223 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
224 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
224 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
225 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
225 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
226 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
226 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
227 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
227
228
228 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
229 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
229
230
230 ip.user_ns['f'] = b'Ca\xc3\xb1o'
231 ip.user_ns['f'] = b'Ca\xc3\xb1o'
231 # This should not raise any exception:
232 # This should not raise any exception:
232 ip.var_expand(u'echo $f')
233 ip.var_expand(u'echo $f')
233
234
234 def test_var_expand_local(self):
235 def test_var_expand_local(self):
235 """Test local variable expansion in !system and %magic calls"""
236 """Test local variable expansion in !system and %magic calls"""
236 # !system
237 # !system
237 ip.run_cell(
238 ip.run_cell(
238 "def test():\n"
239 "def test():\n"
239 ' lvar = "ttt"\n'
240 ' lvar = "ttt"\n'
240 " ret = !echo {lvar}\n"
241 " ret = !echo {lvar}\n"
241 " return ret[0]\n"
242 " return ret[0]\n"
242 )
243 )
243 res = ip.user_ns["test"]()
244 res = ip.user_ns["test"]()
244 self.assertIn("ttt", res)
245 self.assertIn("ttt", res)
245
246
246 # %magic
247 # %magic
247 ip.run_cell(
248 ip.run_cell(
248 "def makemacro():\n"
249 "def makemacro():\n"
249 ' macroname = "macro_var_expand_locals"\n'
250 ' macroname = "macro_var_expand_locals"\n'
250 " %macro {macroname} codestr\n"
251 " %macro {macroname} codestr\n"
251 )
252 )
252 ip.user_ns["codestr"] = "str(12)"
253 ip.user_ns["codestr"] = "str(12)"
253 ip.run_cell("makemacro()")
254 ip.run_cell("makemacro()")
254 self.assertIn("macro_var_expand_locals", ip.user_ns)
255 self.assertIn("macro_var_expand_locals", ip.user_ns)
255
256
256 def test_var_expand_self(self):
257 def test_var_expand_self(self):
257 """Test variable expansion with the name 'self', which was failing.
258 """Test variable expansion with the name 'self', which was failing.
258
259
259 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
260 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
260 """
261 """
261 ip.run_cell(
262 ip.run_cell(
262 "class cTest:\n"
263 "class cTest:\n"
263 ' classvar="see me"\n'
264 ' classvar="see me"\n'
264 " def test(self):\n"
265 " def test(self):\n"
265 " res = !echo Variable: {self.classvar}\n"
266 " res = !echo Variable: {self.classvar}\n"
266 " return res[0]\n"
267 " return res[0]\n"
267 )
268 )
268 self.assertIn("see me", ip.user_ns["cTest"]().test())
269 self.assertIn("see me", ip.user_ns["cTest"]().test())
269
270
270 def test_bad_var_expand(self):
271 def test_bad_var_expand(self):
271 """var_expand on invalid formats shouldn't raise"""
272 """var_expand on invalid formats shouldn't raise"""
272 # SyntaxError
273 # SyntaxError
273 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
274 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
274 # NameError
275 # NameError
275 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
276 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
276 # ZeroDivisionError
277 # ZeroDivisionError
277 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
278 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
278
279
279 def test_silent_postexec(self):
280 def test_silent_postexec(self):
280 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
281 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
281 pre_explicit = mock.Mock()
282 pre_explicit = mock.Mock()
282 pre_always = mock.Mock()
283 pre_always = mock.Mock()
283 post_explicit = mock.Mock()
284 post_explicit = mock.Mock()
284 post_always = mock.Mock()
285 post_always = mock.Mock()
285 all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
286 all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
286
287
287 ip.events.register('pre_run_cell', pre_explicit)
288 ip.events.register('pre_run_cell', pre_explicit)
288 ip.events.register('pre_execute', pre_always)
289 ip.events.register('pre_execute', pre_always)
289 ip.events.register('post_run_cell', post_explicit)
290 ip.events.register('post_run_cell', post_explicit)
290 ip.events.register('post_execute', post_always)
291 ip.events.register('post_execute', post_always)
291
292
292 try:
293 try:
293 ip.run_cell("1", silent=True)
294 ip.run_cell("1", silent=True)
294 assert pre_always.called
295 assert pre_always.called
295 assert not pre_explicit.called
296 assert not pre_explicit.called
296 assert post_always.called
297 assert post_always.called
297 assert not post_explicit.called
298 assert not post_explicit.called
298 # double-check that non-silent exec did what we expected
299 # double-check that non-silent exec did what we expected
299 # silent to avoid
300 # silent to avoid
300 ip.run_cell("1")
301 ip.run_cell("1")
301 assert pre_explicit.called
302 assert pre_explicit.called
302 assert post_explicit.called
303 assert post_explicit.called
303 info, = pre_explicit.call_args[0]
304 info, = pre_explicit.call_args[0]
304 result, = post_explicit.call_args[0]
305 result, = post_explicit.call_args[0]
305 self.assertEqual(info, result.info)
306 self.assertEqual(info, result.info)
306 # check that post hooks are always called
307 # check that post hooks are always called
307 [m.reset_mock() for m in all_mocks]
308 [m.reset_mock() for m in all_mocks]
308 ip.run_cell("syntax error")
309 ip.run_cell("syntax error")
309 assert pre_always.called
310 assert pre_always.called
310 assert pre_explicit.called
311 assert pre_explicit.called
311 assert post_always.called
312 assert post_always.called
312 assert post_explicit.called
313 assert post_explicit.called
313 info, = pre_explicit.call_args[0]
314 info, = pre_explicit.call_args[0]
314 result, = post_explicit.call_args[0]
315 result, = post_explicit.call_args[0]
315 self.assertEqual(info, result.info)
316 self.assertEqual(info, result.info)
316 finally:
317 finally:
317 # remove post-exec
318 # remove post-exec
318 ip.events.unregister('pre_run_cell', pre_explicit)
319 ip.events.unregister('pre_run_cell', pre_explicit)
319 ip.events.unregister('pre_execute', pre_always)
320 ip.events.unregister('pre_execute', pre_always)
320 ip.events.unregister('post_run_cell', post_explicit)
321 ip.events.unregister('post_run_cell', post_explicit)
321 ip.events.unregister('post_execute', post_always)
322 ip.events.unregister('post_execute', post_always)
322
323
323 def test_silent_noadvance(self):
324 def test_silent_noadvance(self):
324 """run_cell(silent=True) doesn't advance execution_count"""
325 """run_cell(silent=True) doesn't advance execution_count"""
325 ec = ip.execution_count
326 ec = ip.execution_count
326 # silent should force store_history=False
327 # silent should force store_history=False
327 ip.run_cell("1", store_history=True, silent=True)
328 ip.run_cell("1", store_history=True, silent=True)
328
329
329 self.assertEqual(ec, ip.execution_count)
330 self.assertEqual(ec, ip.execution_count)
330 # double-check that non-silent exec did what we expected
331 # double-check that non-silent exec did what we expected
331 # silent to avoid
332 # silent to avoid
332 ip.run_cell("1", store_history=True)
333 ip.run_cell("1", store_history=True)
333 self.assertEqual(ec+1, ip.execution_count)
334 self.assertEqual(ec+1, ip.execution_count)
334
335
335 def test_silent_nodisplayhook(self):
336 def test_silent_nodisplayhook(self):
336 """run_cell(silent=True) doesn't trigger displayhook"""
337 """run_cell(silent=True) doesn't trigger displayhook"""
337 d = dict(called=False)
338 d = dict(called=False)
338
339
339 trap = ip.display_trap
340 trap = ip.display_trap
340 save_hook = trap.hook
341 save_hook = trap.hook
341
342
342 def failing_hook(*args, **kwargs):
343 def failing_hook(*args, **kwargs):
343 d['called'] = True
344 d['called'] = True
344
345
345 try:
346 try:
346 trap.hook = failing_hook
347 trap.hook = failing_hook
347 res = ip.run_cell("1", silent=True)
348 res = ip.run_cell("1", silent=True)
348 self.assertFalse(d['called'])
349 self.assertFalse(d['called'])
349 self.assertIsNone(res.result)
350 self.assertIsNone(res.result)
350 # double-check that non-silent exec did what we expected
351 # double-check that non-silent exec did what we expected
351 # silent to avoid
352 # silent to avoid
352 ip.run_cell("1")
353 ip.run_cell("1")
353 self.assertTrue(d['called'])
354 self.assertTrue(d['called'])
354 finally:
355 finally:
355 trap.hook = save_hook
356 trap.hook = save_hook
356
357
357 def test_ofind_line_magic(self):
358 def test_ofind_line_magic(self):
358 from IPython.core.magic import register_line_magic
359 from IPython.core.magic import register_line_magic
359
360
360 @register_line_magic
361 @register_line_magic
361 def lmagic(line):
362 def lmagic(line):
362 "A line magic"
363 "A line magic"
363
364
364 # Get info on line magic
365 # Get info on line magic
365 lfind = ip._ofind("lmagic")
366 lfind = ip._ofind("lmagic")
366 info = OInfo(
367 info = OInfo(
367 found=True,
368 found=True,
368 isalias=False,
369 isalias=False,
369 ismagic=True,
370 ismagic=True,
370 namespace="IPython internal",
371 namespace="IPython internal",
371 obj=lmagic,
372 obj=lmagic,
372 parent=None,
373 parent=None,
373 )
374 )
374 self.assertEqual(lfind, info)
375 self.assertEqual(lfind, info)
375
376
376 def test_ofind_cell_magic(self):
377 def test_ofind_cell_magic(self):
377 from IPython.core.magic import register_cell_magic
378 from IPython.core.magic import register_cell_magic
378
379
379 @register_cell_magic
380 @register_cell_magic
380 def cmagic(line, cell):
381 def cmagic(line, cell):
381 "A cell magic"
382 "A cell magic"
382
383
383 # Get info on cell magic
384 # Get info on cell magic
384 find = ip._ofind("cmagic")
385 find = ip._ofind("cmagic")
385 info = OInfo(
386 info = OInfo(
386 found=True,
387 found=True,
387 isalias=False,
388 isalias=False,
388 ismagic=True,
389 ismagic=True,
389 namespace="IPython internal",
390 namespace="IPython internal",
390 obj=cmagic,
391 obj=cmagic,
391 parent=None,
392 parent=None,
392 )
393 )
393 self.assertEqual(find, info)
394 self.assertEqual(find, info)
394
395
395 def test_ofind_property_with_error(self):
396 def test_ofind_property_with_error(self):
396 class A(object):
397 class A(object):
397 @property
398 @property
398 def foo(self):
399 def foo(self):
399 raise NotImplementedError() # pragma: no cover
400 raise NotImplementedError() # pragma: no cover
400
401
401 a = A()
402 a = A()
402
403
403 found = ip._ofind("a.foo", [("locals", locals())])
404 found = ip._ofind("a.foo", [("locals", locals())])
404 info = OInfo(
405 info = OInfo(
405 found=True,
406 found=True,
406 isalias=False,
407 isalias=False,
407 ismagic=False,
408 ismagic=False,
408 namespace="locals",
409 namespace="locals",
409 obj=A.foo,
410 obj=A.foo,
410 parent=a,
411 parent=a,
411 )
412 )
412 self.assertEqual(found, info)
413 self.assertEqual(found, info)
413
414
414 def test_ofind_multiple_attribute_lookups(self):
415 def test_ofind_multiple_attribute_lookups(self):
415 class A(object):
416 class A(object):
416 @property
417 @property
417 def foo(self):
418 def foo(self):
418 raise NotImplementedError() # pragma: no cover
419 raise NotImplementedError() # pragma: no cover
419
420
420 a = A()
421 a = A()
421 a.a = A()
422 a.a = A()
422 a.a.a = A()
423 a.a.a = A()
423
424
424 found = ip._ofind("a.a.a.foo", [("locals", locals())])
425 found = ip._ofind("a.a.a.foo", [("locals", locals())])
425 info = OInfo(
426 info = OInfo(
426 found=True,
427 found=True,
427 isalias=False,
428 isalias=False,
428 ismagic=False,
429 ismagic=False,
429 namespace="locals",
430 namespace="locals",
430 obj=A.foo,
431 obj=A.foo,
431 parent=a.a.a,
432 parent=a.a.a,
432 )
433 )
433 self.assertEqual(found, info)
434 self.assertEqual(found, info)
434
435
435 def test_ofind_slotted_attributes(self):
436 def test_ofind_slotted_attributes(self):
436 class A(object):
437 class A(object):
437 __slots__ = ['foo']
438 __slots__ = ['foo']
438 def __init__(self):
439 def __init__(self):
439 self.foo = 'bar'
440 self.foo = 'bar'
440
441
441 a = A()
442 a = A()
442 found = ip._ofind("a.foo", [("locals", locals())])
443 found = ip._ofind("a.foo", [("locals", locals())])
443 info = OInfo(
444 info = OInfo(
444 found=True,
445 found=True,
445 isalias=False,
446 isalias=False,
446 ismagic=False,
447 ismagic=False,
447 namespace="locals",
448 namespace="locals",
448 obj=a.foo,
449 obj=a.foo,
449 parent=a,
450 parent=a,
450 )
451 )
451 self.assertEqual(found, info)
452 self.assertEqual(found, info)
452
453
453 found = ip._ofind("a.bar", [("locals", locals())])
454 found = ip._ofind("a.bar", [("locals", locals())])
454 expected = OInfo(
455 expected = OInfo(
455 found=False,
456 found=False,
456 isalias=False,
457 isalias=False,
457 ismagic=False,
458 ismagic=False,
458 namespace=None,
459 namespace=None,
459 obj=None,
460 obj=None,
460 parent=a,
461 parent=a,
461 )
462 )
462 assert found == expected
463 assert found == expected
463
464
464 def test_ofind_prefers_property_to_instance_level_attribute(self):
465 def test_ofind_prefers_property_to_instance_level_attribute(self):
465 class A(object):
466 class A(object):
466 @property
467 @property
467 def foo(self):
468 def foo(self):
468 return 'bar'
469 return 'bar'
469 a = A()
470 a = A()
470 a.__dict__["foo"] = "baz"
471 a.__dict__["foo"] = "baz"
471 self.assertEqual(a.foo, "bar")
472 self.assertEqual(a.foo, "bar")
472 found = ip._ofind("a.foo", [("locals", locals())])
473 found = ip._ofind("a.foo", [("locals", locals())])
473 self.assertIs(found.obj, A.foo)
474 self.assertIs(found.obj, A.foo)
474
475
475 def test_custom_syntaxerror_exception(self):
476 def test_custom_syntaxerror_exception(self):
476 called = []
477 called = []
477 def my_handler(shell, etype, value, tb, tb_offset=None):
478 def my_handler(shell, etype, value, tb, tb_offset=None):
478 called.append(etype)
479 called.append(etype)
479 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
480 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
480
481
481 ip.set_custom_exc((SyntaxError,), my_handler)
482 ip.set_custom_exc((SyntaxError,), my_handler)
482 try:
483 try:
483 ip.run_cell("1f")
484 ip.run_cell("1f")
484 # Check that this was called, and only once.
485 # Check that this was called, and only once.
485 self.assertEqual(called, [SyntaxError])
486 self.assertEqual(called, [SyntaxError])
486 finally:
487 finally:
487 # Reset the custom exception hook
488 # Reset the custom exception hook
488 ip.set_custom_exc((), None)
489 ip.set_custom_exc((), None)
489
490
490 def test_custom_exception(self):
491 def test_custom_exception(self):
491 called = []
492 called = []
492 def my_handler(shell, etype, value, tb, tb_offset=None):
493 def my_handler(shell, etype, value, tb, tb_offset=None):
493 called.append(etype)
494 called.append(etype)
494 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
495 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
495
496
496 ip.set_custom_exc((ValueError,), my_handler)
497 ip.set_custom_exc((ValueError,), my_handler)
497 try:
498 try:
498 res = ip.run_cell("raise ValueError('test')")
499 res = ip.run_cell("raise ValueError('test')")
499 # Check that this was called, and only once.
500 # Check that this was called, and only once.
500 self.assertEqual(called, [ValueError])
501 self.assertEqual(called, [ValueError])
501 # Check that the error is on the result object
502 # Check that the error is on the result object
502 self.assertIsInstance(res.error_in_exec, ValueError)
503 self.assertIsInstance(res.error_in_exec, ValueError)
503 finally:
504 finally:
504 # Reset the custom exception hook
505 # Reset the custom exception hook
505 ip.set_custom_exc((), None)
506 ip.set_custom_exc((), None)
506
507
507 @mock.patch("builtins.print")
508 @mock.patch("builtins.print")
508 def test_showtraceback_with_surrogates(self, mocked_print):
509 def test_showtraceback_with_surrogates(self, mocked_print):
509 values = []
510 values = []
510
511
511 def mock_print_func(value, sep=" ", end="\n", file=sys.stdout, flush=False):
512 def mock_print_func(value, sep=" ", end="\n", file=sys.stdout, flush=False):
512 values.append(value)
513 values.append(value)
513 if value == chr(0xD8FF):
514 if value == chr(0xD8FF):
514 raise UnicodeEncodeError("utf-8", chr(0xD8FF), 0, 1, "")
515 raise UnicodeEncodeError("utf-8", chr(0xD8FF), 0, 1, "")
515
516
516 # mock builtins.print
517 # mock builtins.print
517 mocked_print.side_effect = mock_print_func
518 mocked_print.side_effect = mock_print_func
518
519
519 # ip._showtraceback() is replaced in globalipapp.py.
520 # ip._showtraceback() is replaced in globalipapp.py.
520 # Call original method to test.
521 # Call original method to test.
521 interactiveshell.InteractiveShell._showtraceback(ip, None, None, chr(0xD8FF))
522 interactiveshell.InteractiveShell._showtraceback(ip, None, None, chr(0xD8FF))
522
523
523 self.assertEqual(mocked_print.call_count, 2)
524 self.assertEqual(mocked_print.call_count, 2)
524 self.assertEqual(values, [chr(0xD8FF), "\\ud8ff"])
525 self.assertEqual(values, [chr(0xD8FF), "\\ud8ff"])
525
526
526 def test_mktempfile(self):
527 def test_mktempfile(self):
527 filename = ip.mktempfile()
528 filename = ip.mktempfile()
528 # Check that we can open the file again on Windows
529 # Check that we can open the file again on Windows
529 with open(filename, "w", encoding="utf-8") as f:
530 with open(filename, "w", encoding="utf-8") as f:
530 f.write("abc")
531 f.write("abc")
531
532
532 filename = ip.mktempfile(data="blah")
533 filename = ip.mktempfile(data="blah")
533 with open(filename, "r", encoding="utf-8") as f:
534 with open(filename, "r", encoding="utf-8") as f:
534 self.assertEqual(f.read(), "blah")
535 self.assertEqual(f.read(), "blah")
535
536
536 def test_new_main_mod(self):
537 def test_new_main_mod(self):
537 # Smoketest to check that this accepts a unicode module name
538 # Smoketest to check that this accepts a unicode module name
538 name = u'jiefmw'
539 name = u'jiefmw'
539 mod = ip.new_main_mod(u'%s.py' % name, name)
540 mod = ip.new_main_mod(u'%s.py' % name, name)
540 self.assertEqual(mod.__name__, name)
541 self.assertEqual(mod.__name__, name)
541
542
542 def test_get_exception_only(self):
543 def test_get_exception_only(self):
543 try:
544 try:
544 raise KeyboardInterrupt
545 raise KeyboardInterrupt
545 except KeyboardInterrupt:
546 except KeyboardInterrupt:
546 msg = ip.get_exception_only()
547 msg = ip.get_exception_only()
547 self.assertEqual(msg, 'KeyboardInterrupt\n')
548 self.assertEqual(msg, 'KeyboardInterrupt\n')
548
549
549 try:
550 try:
550 raise DerivedInterrupt("foo")
551 raise DerivedInterrupt("foo")
551 except KeyboardInterrupt:
552 except KeyboardInterrupt:
552 msg = ip.get_exception_only()
553 msg = ip.get_exception_only()
553 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
554 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
554
555
555 def test_inspect_text(self):
556 def test_inspect_text(self):
556 ip.run_cell('a = 5')
557 ip.run_cell('a = 5')
557 text = ip.object_inspect_text('a')
558 text = ip.object_inspect_text('a')
558 self.assertIsInstance(text, str)
559 self.assertIsInstance(text, str)
559
560
560 def test_last_execution_result(self):
561 def test_last_execution_result(self):
561 """ Check that last execution result gets set correctly (GH-10702) """
562 """ Check that last execution result gets set correctly (GH-10702) """
562 result = ip.run_cell('a = 5; a')
563 result = ip.run_cell('a = 5; a')
563 self.assertTrue(ip.last_execution_succeeded)
564 self.assertTrue(ip.last_execution_succeeded)
564 self.assertEqual(ip.last_execution_result.result, 5)
565 self.assertEqual(ip.last_execution_result.result, 5)
565
566
566 result = ip.run_cell('a = x_invalid_id_x')
567 result = ip.run_cell('a = x_invalid_id_x')
567 self.assertFalse(ip.last_execution_succeeded)
568 self.assertFalse(ip.last_execution_succeeded)
568 self.assertFalse(ip.last_execution_result.success)
569 self.assertFalse(ip.last_execution_result.success)
569 self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
570 self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
570
571
571 def test_reset_aliasing(self):
572 def test_reset_aliasing(self):
572 """ Check that standard posix aliases work after %reset. """
573 """ Check that standard posix aliases work after %reset. """
573 if os.name != 'posix':
574 if os.name != 'posix':
574 return
575 return
575
576
576 ip.reset()
577 ip.reset()
577 for cmd in ('clear', 'more', 'less', 'man'):
578 for cmd in ('clear', 'more', 'less', 'man'):
578 res = ip.run_cell('%' + cmd)
579 res = ip.run_cell('%' + cmd)
579 self.assertEqual(res.success, True)
580 self.assertEqual(res.success, True)
580
581
581
582
582 @pytest.mark.skipif(
583 @pytest.mark.skipif(
583 sys.implementation.name == "pypy"
584 sys.implementation.name == "pypy"
584 and ((7, 3, 13) < sys.implementation.version < (7, 3, 16)),
585 and ((7, 3, 13) < sys.implementation.version < (7, 3, 16)),
585 reason="Unicode issues with scandir on PyPy, see https://github.com/pypy/pypy/issues/4860",
586 reason="Unicode issues with scandir on PyPy, see https://github.com/pypy/pypy/issues/4860",
586 )
587 )
587 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
588 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
588 @onlyif_unicode_paths
589 @onlyif_unicode_paths
589 def setUp(self):
590 def setUp(self):
590 self.BASETESTDIR = tempfile.mkdtemp()
591 self.BASETESTDIR = tempfile.mkdtemp()
591 self.TESTDIR = join(self.BASETESTDIR, u"åäö")
592 self.TESTDIR = join(self.BASETESTDIR, u"åäö")
592 os.mkdir(self.TESTDIR)
593 os.mkdir(self.TESTDIR)
593 with open(
594 with open(
594 join(self.TESTDIR, "åäötestscript.py"), "w", encoding="utf-8"
595 join(self.TESTDIR, "åäötestscript.py"), "w", encoding="utf-8"
595 ) as sfile:
596 ) as sfile:
596 sfile.write("pass\n")
597 sfile.write("pass\n")
597 self.oldpath = os.getcwd()
598 self.oldpath = os.getcwd()
598 os.chdir(self.TESTDIR)
599 os.chdir(self.TESTDIR)
599 self.fname = u"åäötestscript.py"
600 self.fname = u"åäötestscript.py"
600
601
601 def tearDown(self):
602 def tearDown(self):
602 os.chdir(self.oldpath)
603 os.chdir(self.oldpath)
603 shutil.rmtree(self.BASETESTDIR)
604 shutil.rmtree(self.BASETESTDIR)
604
605
605 @onlyif_unicode_paths
606 @onlyif_unicode_paths
606 def test_1(self):
607 def test_1(self):
607 """Test safe_execfile with non-ascii path
608 """Test safe_execfile with non-ascii path
608 """
609 """
609 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
610 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
610
611
611 class ExitCodeChecks(tt.TempFileMixin):
612 class ExitCodeChecks(tt.TempFileMixin):
612
613
613 def setUp(self):
614 def setUp(self):
614 self.system = ip.system_raw
615 self.system = ip.system_raw
615
616
616 def test_exit_code_ok(self):
617 def test_exit_code_ok(self):
617 self.system('exit 0')
618 self.system('exit 0')
618 self.assertEqual(ip.user_ns['_exit_code'], 0)
619 self.assertEqual(ip.user_ns['_exit_code'], 0)
619
620
620 def test_exit_code_error(self):
621 def test_exit_code_error(self):
621 self.system('exit 1')
622 self.system('exit 1')
622 self.assertEqual(ip.user_ns['_exit_code'], 1)
623 self.assertEqual(ip.user_ns['_exit_code'], 1)
623
624
624 @skipif(not hasattr(signal, 'SIGALRM'))
625 @skipif(not hasattr(signal, 'SIGALRM'))
625 def test_exit_code_signal(self):
626 def test_exit_code_signal(self):
626 self.mktmp("import signal, time\n"
627 self.mktmp("import signal, time\n"
627 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
628 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
628 "time.sleep(1)\n")
629 "time.sleep(1)\n")
629 self.system("%s %s" % (sys.executable, self.fname))
630 self.system("%s %s" % (sys.executable, self.fname))
630 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
631 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
631
632
632 @onlyif_cmds_exist("csh")
633 @onlyif_cmds_exist("csh")
633 def test_exit_code_signal_csh(self): # pragma: no cover
634 def test_exit_code_signal_csh(self): # pragma: no cover
634 SHELL = os.environ.get("SHELL", None)
635 SHELL = os.environ.get("SHELL", None)
635 os.environ["SHELL"] = find_cmd("csh")
636 os.environ["SHELL"] = find_cmd("csh")
636 try:
637 try:
637 self.test_exit_code_signal()
638 self.test_exit_code_signal()
638 finally:
639 finally:
639 if SHELL is not None:
640 if SHELL is not None:
640 os.environ['SHELL'] = SHELL
641 os.environ['SHELL'] = SHELL
641 else:
642 else:
642 del os.environ['SHELL']
643 del os.environ['SHELL']
643
644
644
645
645 class TestSystemRaw(ExitCodeChecks):
646 class TestSystemRaw(ExitCodeChecks):
646
647
647 def setUp(self):
648 def setUp(self):
648 super().setUp()
649 super().setUp()
649 self.system = ip.system_raw
650 self.system = ip.system_raw
650
651
651 @onlyif_unicode_paths
652 @onlyif_unicode_paths
652 def test_1(self):
653 def test_1(self):
653 """Test system_raw with non-ascii cmd
654 """Test system_raw with non-ascii cmd
654 """
655 """
655 cmd = u'''python -c "'åäö'" '''
656 cmd = u'''python -c "'åäö'" '''
656 ip.system_raw(cmd)
657 ip.system_raw(cmd)
657
658
658 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
659 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
659 @mock.patch('os.system', side_effect=KeyboardInterrupt)
660 @mock.patch('os.system', side_effect=KeyboardInterrupt)
660 def test_control_c(self, *mocks):
661 def test_control_c(self, *mocks):
661 try:
662 try:
662 self.system("sleep 1 # won't happen")
663 self.system("sleep 1 # won't happen")
663 except KeyboardInterrupt: # pragma: no cove
664 except KeyboardInterrupt: # pragma: no cove
664 self.fail(
665 self.fail(
665 "system call should intercept "
666 "system call should intercept "
666 "keyboard interrupt from subprocess.call"
667 "keyboard interrupt from subprocess.call"
667 )
668 )
668 self.assertEqual(ip.user_ns["_exit_code"], -signal.SIGINT)
669 self.assertEqual(ip.user_ns["_exit_code"], -signal.SIGINT)
669
670
670
671
671 @pytest.mark.parametrize("magic_cmd", ["pip", "conda", "cd"])
672 @pytest.mark.parametrize("magic_cmd", ["pip", "conda", "cd"])
672 def test_magic_warnings(magic_cmd):
673 def test_magic_warnings(magic_cmd):
673 if sys.platform == "win32":
674 if sys.platform == "win32":
674 to_mock = "os.system"
675 to_mock = "os.system"
675 expected_arg, expected_kwargs = magic_cmd, dict()
676 expected_arg, expected_kwargs = magic_cmd, dict()
676 else:
677 else:
677 to_mock = "subprocess.call"
678 to_mock = "subprocess.call"
678 expected_arg, expected_kwargs = magic_cmd, dict(
679 expected_arg, expected_kwargs = magic_cmd, dict(
679 shell=True, executable=os.environ.get("SHELL", None)
680 shell=True, executable=os.environ.get("SHELL", None)
680 )
681 )
681
682
682 with mock.patch(to_mock, return_value=0) as mock_sub:
683 with mock.patch(to_mock, return_value=0) as mock_sub:
683 with pytest.warns(Warning, match=r"You executed the system command"):
684 with pytest.warns(Warning, match=r"You executed the system command"):
684 ip.system_raw(magic_cmd)
685 ip.system_raw(magic_cmd)
685 mock_sub.assert_called_once_with(expected_arg, **expected_kwargs)
686 mock_sub.assert_called_once_with(expected_arg, **expected_kwargs)
686
687
687
688
688 # TODO: Exit codes are currently ignored on Windows.
689 # TODO: Exit codes are currently ignored on Windows.
689 class TestSystemPipedExitCode(ExitCodeChecks):
690 class TestSystemPipedExitCode(ExitCodeChecks):
690
691
691 def setUp(self):
692 def setUp(self):
692 super().setUp()
693 super().setUp()
693 self.system = ip.system_piped
694 self.system = ip.system_piped
694
695
695 @skip_win32
696 @skip_win32
696 def test_exit_code_ok(self):
697 def test_exit_code_ok(self):
697 ExitCodeChecks.test_exit_code_ok(self)
698 ExitCodeChecks.test_exit_code_ok(self)
698
699
699 @skip_win32
700 @skip_win32
700 def test_exit_code_error(self):
701 def test_exit_code_error(self):
701 ExitCodeChecks.test_exit_code_error(self)
702 ExitCodeChecks.test_exit_code_error(self)
702
703
703 @skip_win32
704 @skip_win32
704 def test_exit_code_signal(self):
705 def test_exit_code_signal(self):
705 ExitCodeChecks.test_exit_code_signal(self)
706 ExitCodeChecks.test_exit_code_signal(self)
706
707
707 class TestModules(tt.TempFileMixin):
708 class TestModules(tt.TempFileMixin):
708 def test_extraneous_loads(self):
709 def test_extraneous_loads(self):
709 """Test we're not loading modules on startup that we shouldn't.
710 """Test we're not loading modules on startup that we shouldn't.
710 """
711 """
711 self.mktmp("import sys\n"
712 self.mktmp("import sys\n"
712 "print('numpy' in sys.modules)\n"
713 "print('numpy' in sys.modules)\n"
713 "print('ipyparallel' in sys.modules)\n"
714 "print('ipyparallel' in sys.modules)\n"
714 "print('ipykernel' in sys.modules)\n"
715 "print('ipykernel' in sys.modules)\n"
715 )
716 )
716 out = "False\nFalse\nFalse\n"
717 out = "False\nFalse\nFalse\n"
717 tt.ipexec_validate(self.fname, out)
718 tt.ipexec_validate(self.fname, out)
718
719
719 class Negator(ast.NodeTransformer):
720 class Negator(ast.NodeTransformer):
720 """Negates all number literals in an AST."""
721 """Negates all number literals in an AST."""
721
722
722 def visit_Num(self, node):
723 def visit_Num(self, node):
723 node.value = -node.value
724 node.value = -node.value
724 return node
725 return node
725
726
726 def visit_Constant(self, node):
727 def visit_Constant(self, node):
727 if isinstance(node.value, int):
728 if isinstance(node.value, int):
728 return self.visit_Num(node)
729 return self.visit_Num(node)
729 return node
730 return node
730
731
731 class TestAstTransform(unittest.TestCase):
732 class TestAstTransform(unittest.TestCase):
732 def setUp(self):
733 def setUp(self):
733 self.negator = Negator()
734 self.negator = Negator()
734 ip.ast_transformers.append(self.negator)
735 ip.ast_transformers.append(self.negator)
735
736
736 def tearDown(self):
737 def tearDown(self):
737 ip.ast_transformers.remove(self.negator)
738 ip.ast_transformers.remove(self.negator)
738
739
739 def test_non_int_const(self):
740 def test_non_int_const(self):
740 with tt.AssertPrints("hello"):
741 with tt.AssertPrints("hello"):
741 ip.run_cell('print("hello")')
742 ip.run_cell('print("hello")')
742
743
743 def test_run_cell(self):
744 def test_run_cell(self):
744 with tt.AssertPrints("-34"):
745 with tt.AssertPrints("-34"):
745 ip.run_cell("print(12 + 22)")
746 ip.run_cell("print(12 + 22)")
746
747
747 # A named reference to a number shouldn't be transformed.
748 # A named reference to a number shouldn't be transformed.
748 ip.user_ns["n"] = 55
749 ip.user_ns["n"] = 55
749 with tt.AssertNotPrints("-55"):
750 with tt.AssertNotPrints("-55"):
750 ip.run_cell("print(n)")
751 ip.run_cell("print(n)")
751
752
752 def test_timeit(self):
753 def test_timeit(self):
753 called = set()
754 called = set()
754 def f(x):
755 def f(x):
755 called.add(x)
756 called.add(x)
756 ip.push({'f':f})
757 ip.push({'f':f})
757
758
758 with tt.AssertPrints("std. dev. of"):
759 with tt.AssertPrints("std. dev. of"):
759 ip.run_line_magic("timeit", "-n1 f(1)")
760 ip.run_line_magic("timeit", "-n1 f(1)")
760 self.assertEqual(called, {-1})
761 self.assertEqual(called, {-1})
761 called.clear()
762 called.clear()
762
763
763 with tt.AssertPrints("std. dev. of"):
764 with tt.AssertPrints("std. dev. of"):
764 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
765 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
765 self.assertEqual(called, {-2, -3})
766 self.assertEqual(called, {-2, -3})
766
767
767 def test_time(self):
768 def test_time(self):
768 called = []
769 called = []
769 def f(x):
770 def f(x):
770 called.append(x)
771 called.append(x)
771 ip.push({'f':f})
772 ip.push({'f':f})
772
773
773 # Test with an expression
774 # Test with an expression
774 with tt.AssertPrints("Wall time: "):
775 with tt.AssertPrints("Wall time: "):
775 ip.run_line_magic("time", "f(5+9)")
776 ip.run_line_magic("time", "f(5+9)")
776 self.assertEqual(called, [-14])
777 self.assertEqual(called, [-14])
777 called[:] = []
778 called[:] = []
778
779
779 # Test with a statement (different code path)
780 # Test with a statement (different code path)
780 with tt.AssertPrints("Wall time: "):
781 with tt.AssertPrints("Wall time: "):
781 ip.run_line_magic("time", "a = f(-3 + -2)")
782 ip.run_line_magic("time", "a = f(-3 + -2)")
782 self.assertEqual(called, [5])
783 self.assertEqual(called, [5])
783
784
784 def test_macro(self):
785 def test_macro(self):
785 ip.push({'a':10})
786 ip.push({'a':10})
786 # The AST transformation makes this do a+=-1
787 # The AST transformation makes this do a+=-1
787 ip.define_macro("amacro", "a+=1\nprint(a)")
788 ip.define_macro("amacro", "a+=1\nprint(a)")
788
789
789 with tt.AssertPrints("9"):
790 with tt.AssertPrints("9"):
790 ip.run_cell("amacro")
791 ip.run_cell("amacro")
791 with tt.AssertPrints("8"):
792 with tt.AssertPrints("8"):
792 ip.run_cell("amacro")
793 ip.run_cell("amacro")
793
794
794 class TestMiscTransform(unittest.TestCase):
795 class TestMiscTransform(unittest.TestCase):
795
796
796
797
797 def test_transform_only_once(self):
798 def test_transform_only_once(self):
798 cleanup = 0
799 cleanup = 0
799 line_t = 0
800 line_t = 0
800 def count_cleanup(lines):
801 def count_cleanup(lines):
801 nonlocal cleanup
802 nonlocal cleanup
802 cleanup += 1
803 cleanup += 1
803 return lines
804 return lines
804
805
805 def count_line_t(lines):
806 def count_line_t(lines):
806 nonlocal line_t
807 nonlocal line_t
807 line_t += 1
808 line_t += 1
808 return lines
809 return lines
809
810
810 ip.input_transformer_manager.cleanup_transforms.append(count_cleanup)
811 ip.input_transformer_manager.cleanup_transforms.append(count_cleanup)
811 ip.input_transformer_manager.line_transforms.append(count_line_t)
812 ip.input_transformer_manager.line_transforms.append(count_line_t)
812
813
813 ip.run_cell('1')
814 ip.run_cell('1')
814
815
815 assert cleanup == 1
816 assert cleanup == 1
816 assert line_t == 1
817 assert line_t == 1
817
818
818 class IntegerWrapper(ast.NodeTransformer):
819 class IntegerWrapper(ast.NodeTransformer):
819 """Wraps all integers in a call to Integer()"""
820 """Wraps all integers in a call to Integer()"""
820
821
821 # for Python 3.7 and earlier
822 # for Python 3.7 and earlier
822
823
823 # for Python 3.7 and earlier
824 # for Python 3.7 and earlier
824 def visit_Num(self, node):
825 def visit_Num(self, node):
825 if isinstance(node.n, int):
826 if isinstance(node.n, int):
826 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
827 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
827 args=[node], keywords=[])
828 args=[node], keywords=[])
828 return node
829 return node
829
830
830 # For Python 3.8+
831 # For Python 3.8+
831 def visit_Constant(self, node):
832 def visit_Constant(self, node):
832 if isinstance(node.value, int):
833 if isinstance(node.value, int):
833 return self.visit_Num(node)
834 return self.visit_Num(node)
834 return node
835 return node
835
836
836
837
837 class TestAstTransform2(unittest.TestCase):
838 class TestAstTransform2(unittest.TestCase):
838 def setUp(self):
839 def setUp(self):
839 self.intwrapper = IntegerWrapper()
840 self.intwrapper = IntegerWrapper()
840 ip.ast_transformers.append(self.intwrapper)
841 ip.ast_transformers.append(self.intwrapper)
841
842
842 self.calls = []
843 self.calls = []
843 def Integer(*args):
844 def Integer(*args):
844 self.calls.append(args)
845 self.calls.append(args)
845 return args
846 return args
846 ip.push({"Integer": Integer})
847 ip.push({"Integer": Integer})
847
848
848 def tearDown(self):
849 def tearDown(self):
849 ip.ast_transformers.remove(self.intwrapper)
850 ip.ast_transformers.remove(self.intwrapper)
850 del ip.user_ns['Integer']
851 del ip.user_ns['Integer']
851
852
852 def test_run_cell(self):
853 def test_run_cell(self):
853 ip.run_cell("n = 2")
854 ip.run_cell("n = 2")
854 self.assertEqual(self.calls, [(2,)])
855 self.assertEqual(self.calls, [(2,)])
855
856
856 # This shouldn't throw an error
857 # This shouldn't throw an error
857 ip.run_cell("o = 2.0")
858 ip.run_cell("o = 2.0")
858 self.assertEqual(ip.user_ns['o'], 2.0)
859 self.assertEqual(ip.user_ns['o'], 2.0)
859
860
860 def test_run_cell_non_int(self):
861 def test_run_cell_non_int(self):
861 ip.run_cell("n = 'a'")
862 ip.run_cell("n = 'a'")
862 assert self.calls == []
863 assert self.calls == []
863
864
864 def test_timeit(self):
865 def test_timeit(self):
865 called = set()
866 called = set()
866 def f(x):
867 def f(x):
867 called.add(x)
868 called.add(x)
868 ip.push({'f':f})
869 ip.push({'f':f})
869
870
870 with tt.AssertPrints("std. dev. of"):
871 with tt.AssertPrints("std. dev. of"):
871 ip.run_line_magic("timeit", "-n1 f(1)")
872 ip.run_line_magic("timeit", "-n1 f(1)")
872 self.assertEqual(called, {(1,)})
873 self.assertEqual(called, {(1,)})
873 called.clear()
874 called.clear()
874
875
875 with tt.AssertPrints("std. dev. of"):
876 with tt.AssertPrints("std. dev. of"):
876 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
877 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
877 self.assertEqual(called, {(2,), (3,)})
878 self.assertEqual(called, {(2,), (3,)})
878
879
879 class ErrorTransformer(ast.NodeTransformer):
880 class ErrorTransformer(ast.NodeTransformer):
880 """Throws an error when it sees a number."""
881 """Throws an error when it sees a number."""
881
882
882 def visit_Constant(self, node):
883 def visit_Constant(self, node):
883 if isinstance(node.value, int):
884 if isinstance(node.value, int):
884 raise ValueError("test")
885 raise ValueError("test")
885 return node
886 return node
886
887
887
888
888 class TestAstTransformError(unittest.TestCase):
889 class TestAstTransformError(unittest.TestCase):
889 def test_unregistering(self):
890 def test_unregistering(self):
890 err_transformer = ErrorTransformer()
891 err_transformer = ErrorTransformer()
891 ip.ast_transformers.append(err_transformer)
892 ip.ast_transformers.append(err_transformer)
892
893
893 with self.assertWarnsRegex(UserWarning, "It will be unregistered"):
894 with self.assertWarnsRegex(UserWarning, "It will be unregistered"):
894 ip.run_cell("1 + 2")
895 ip.run_cell("1 + 2")
895
896
896 # This should have been removed.
897 # This should have been removed.
897 self.assertNotIn(err_transformer, ip.ast_transformers)
898 self.assertNotIn(err_transformer, ip.ast_transformers)
898
899
899
900
900 class StringRejector(ast.NodeTransformer):
901 class StringRejector(ast.NodeTransformer):
901 """Throws an InputRejected when it sees a string literal.
902 """Throws an InputRejected when it sees a string literal.
902
903
903 Used to verify that NodeTransformers can signal that a piece of code should
904 Used to verify that NodeTransformers can signal that a piece of code should
904 not be executed by throwing an InputRejected.
905 not be executed by throwing an InputRejected.
905 """
906 """
906
907
907 def visit_Constant(self, node):
908 def visit_Constant(self, node):
908 if isinstance(node.value, str):
909 if isinstance(node.value, str):
909 raise InputRejected("test")
910 raise InputRejected("test")
910 return node
911 return node
911
912
912
913
913 class TestAstTransformInputRejection(unittest.TestCase):
914 class TestAstTransformInputRejection(unittest.TestCase):
914
915
915 def setUp(self):
916 def setUp(self):
916 self.transformer = StringRejector()
917 self.transformer = StringRejector()
917 ip.ast_transformers.append(self.transformer)
918 ip.ast_transformers.append(self.transformer)
918
919
919 def tearDown(self):
920 def tearDown(self):
920 ip.ast_transformers.remove(self.transformer)
921 ip.ast_transformers.remove(self.transformer)
921
922
922 def test_input_rejection(self):
923 def test_input_rejection(self):
923 """Check that NodeTransformers can reject input."""
924 """Check that NodeTransformers can reject input."""
924
925
925 expect_exception_tb = tt.AssertPrints("InputRejected: test")
926 expect_exception_tb = tt.AssertPrints("InputRejected: test")
926 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
927 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
927
928
928 # Run the same check twice to verify that the transformer is not
929 # Run the same check twice to verify that the transformer is not
929 # disabled after raising.
930 # disabled after raising.
930 with expect_exception_tb, expect_no_cell_output:
931 with expect_exception_tb, expect_no_cell_output:
931 ip.run_cell("'unsafe'")
932 ip.run_cell("'unsafe'")
932
933
933 with expect_exception_tb, expect_no_cell_output:
934 with expect_exception_tb, expect_no_cell_output:
934 res = ip.run_cell("'unsafe'")
935 res = ip.run_cell("'unsafe'")
935
936
936 self.assertIsInstance(res.error_before_exec, InputRejected)
937 self.assertIsInstance(res.error_before_exec, InputRejected)
937
938
938 def test__IPYTHON__():
939 def test__IPYTHON__():
939 # This shouldn't raise a NameError, that's all
940 # This shouldn't raise a NameError, that's all
940 __IPYTHON__
941 __IPYTHON__
941
942
942
943
943 class DummyRepr(object):
944 class DummyRepr(object):
944 def __repr__(self):
945 def __repr__(self):
945 return "DummyRepr"
946 return "DummyRepr"
946
947
947 def _repr_html_(self):
948 def _repr_html_(self):
948 return "<b>dummy</b>"
949 return "<b>dummy</b>"
949
950
950 def _repr_javascript_(self):
951 def _repr_javascript_(self):
951 return "console.log('hi');", {'key': 'value'}
952 return "console.log('hi');", {'key': 'value'}
952
953
953
954
954 def test_user_variables():
955 def test_user_variables():
955 # enable all formatters
956 # enable all formatters
956 ip.display_formatter.active_types = ip.display_formatter.format_types
957 ip.display_formatter.active_types = ip.display_formatter.format_types
957
958
958 ip.user_ns['dummy'] = d = DummyRepr()
959 ip.user_ns['dummy'] = d = DummyRepr()
959 keys = {'dummy', 'doesnotexist'}
960 keys = {'dummy', 'doesnotexist'}
960 r = ip.user_expressions({ key:key for key in keys})
961 r = ip.user_expressions({ key:key for key in keys})
961
962
962 assert keys == set(r.keys())
963 assert keys == set(r.keys())
963 dummy = r["dummy"]
964 dummy = r["dummy"]
964 assert {"status", "data", "metadata"} == set(dummy.keys())
965 assert {"status", "data", "metadata"} == set(dummy.keys())
965 assert dummy["status"] == "ok"
966 assert dummy["status"] == "ok"
966 data = dummy["data"]
967 data = dummy["data"]
967 metadata = dummy["metadata"]
968 metadata = dummy["metadata"]
968 assert data.get("text/html") == d._repr_html_()
969 assert data.get("text/html") == d._repr_html_()
969 js, jsmd = d._repr_javascript_()
970 js, jsmd = d._repr_javascript_()
970 assert data.get("application/javascript") == js
971 assert data.get("application/javascript") == js
971 assert metadata.get("application/javascript") == jsmd
972 assert metadata.get("application/javascript") == jsmd
972
973
973 dne = r["doesnotexist"]
974 dne = r["doesnotexist"]
974 assert dne["status"] == "error"
975 assert dne["status"] == "error"
975 assert dne["ename"] == "NameError"
976 assert dne["ename"] == "NameError"
976
977
977 # back to text only
978 # back to text only
978 ip.display_formatter.active_types = ['text/plain']
979 ip.display_formatter.active_types = ['text/plain']
979
980
980 def test_user_expression():
981 def test_user_expression():
981 # enable all formatters
982 # enable all formatters
982 ip.display_formatter.active_types = ip.display_formatter.format_types
983 ip.display_formatter.active_types = ip.display_formatter.format_types
983 query = {
984 query = {
984 'a' : '1 + 2',
985 'a' : '1 + 2',
985 'b' : '1/0',
986 'b' : '1/0',
986 }
987 }
987 r = ip.user_expressions(query)
988 r = ip.user_expressions(query)
988 import pprint
989 import pprint
989 pprint.pprint(r)
990 pprint.pprint(r)
990 assert set(r.keys()) == set(query.keys())
991 assert set(r.keys()) == set(query.keys())
991 a = r["a"]
992 a = r["a"]
992 assert {"status", "data", "metadata"} == set(a.keys())
993 assert {"status", "data", "metadata"} == set(a.keys())
993 assert a["status"] == "ok"
994 assert a["status"] == "ok"
994 data = a["data"]
995 data = a["data"]
995 metadata = a["metadata"]
996 metadata = a["metadata"]
996 assert data.get("text/plain") == "3"
997 assert data.get("text/plain") == "3"
997
998
998 b = r["b"]
999 b = r["b"]
999 assert b["status"] == "error"
1000 assert b["status"] == "error"
1000 assert b["ename"] == "ZeroDivisionError"
1001 assert b["ename"] == "ZeroDivisionError"
1001
1002
1002 # back to text only
1003 # back to text only
1003 ip.display_formatter.active_types = ['text/plain']
1004 ip.display_formatter.active_types = ['text/plain']
1004
1005
1005
1006
1006 class TestSyntaxErrorTransformer(unittest.TestCase):
1007 class TestSyntaxErrorTransformer(unittest.TestCase):
1007 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
1008 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
1008
1009
1009 @staticmethod
1010 @staticmethod
1010 def transformer(lines):
1011 def transformer(lines):
1011 for line in lines:
1012 for line in lines:
1012 pos = line.find('syntaxerror')
1013 pos = line.find('syntaxerror')
1013 if pos >= 0:
1014 if pos >= 0:
1014 e = SyntaxError('input contains "syntaxerror"')
1015 e = SyntaxError('input contains "syntaxerror"')
1015 e.text = line
1016 e.text = line
1016 e.offset = pos + 1
1017 e.offset = pos + 1
1017 raise e
1018 raise e
1018 return lines
1019 return lines
1019
1020
1020 def setUp(self):
1021 def setUp(self):
1021 ip.input_transformers_post.append(self.transformer)
1022 ip.input_transformers_post.append(self.transformer)
1022
1023
1023 def tearDown(self):
1024 def tearDown(self):
1024 ip.input_transformers_post.remove(self.transformer)
1025 ip.input_transformers_post.remove(self.transformer)
1025
1026
1026 def test_syntaxerror_input_transformer(self):
1027 def test_syntaxerror_input_transformer(self):
1027 with tt.AssertPrints('1234'):
1028 with tt.AssertPrints('1234'):
1028 ip.run_cell('1234')
1029 ip.run_cell('1234')
1029 with tt.AssertPrints('SyntaxError: invalid syntax'):
1030 with tt.AssertPrints('SyntaxError: invalid syntax'):
1030 ip.run_cell('1 2 3') # plain python syntax error
1031 ip.run_cell('1 2 3') # plain python syntax error
1031 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
1032 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
1032 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
1033 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
1033 with tt.AssertPrints('3456'):
1034 with tt.AssertPrints('3456'):
1034 ip.run_cell('3456')
1035 ip.run_cell('3456')
1035
1036
1036
1037
1037 class TestWarningSuppression(unittest.TestCase):
1038 class TestWarningSuppression(unittest.TestCase):
1038 def test_warning_suppression(self):
1039 def test_warning_suppression(self):
1039 ip.run_cell("import warnings")
1040 ip.run_cell("import warnings")
1040 try:
1041 try:
1041 with self.assertWarnsRegex(UserWarning, "asdf"):
1042 with self.assertWarnsRegex(UserWarning, "asdf"):
1042 ip.run_cell("warnings.warn('asdf')")
1043 ip.run_cell("warnings.warn('asdf')")
1043 # Here's the real test -- if we run that again, we should get the
1044 # Here's the real test -- if we run that again, we should get the
1044 # warning again. Traditionally, each warning was only issued once per
1045 # warning again. Traditionally, each warning was only issued once per
1045 # IPython session (approximately), even if the user typed in new and
1046 # IPython session (approximately), even if the user typed in new and
1046 # different code that should have also triggered the warning, leading
1047 # different code that should have also triggered the warning, leading
1047 # to much confusion.
1048 # to much confusion.
1048 with self.assertWarnsRegex(UserWarning, "asdf"):
1049 with self.assertWarnsRegex(UserWarning, "asdf"):
1049 ip.run_cell("warnings.warn('asdf')")
1050 ip.run_cell("warnings.warn('asdf')")
1050 finally:
1051 finally:
1051 ip.run_cell("del warnings")
1052 ip.run_cell("del warnings")
1052
1053
1053
1054
1054 def test_deprecation_warning(self):
1055 def test_deprecation_warning(self):
1055 ip.run_cell("""
1056 ip.run_cell("""
1056 import warnings
1057 import warnings
1057 def wrn():
1058 def wrn():
1058 warnings.warn(
1059 warnings.warn(
1059 "I AM A WARNING",
1060 "I AM A WARNING",
1060 DeprecationWarning
1061 DeprecationWarning
1061 )
1062 )
1062 """)
1063 """)
1063 try:
1064 try:
1064 with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"):
1065 with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"):
1065 ip.run_cell("wrn()")
1066 ip.run_cell("wrn()")
1066 finally:
1067 finally:
1067 ip.run_cell("del warnings")
1068 ip.run_cell("del warnings")
1068 ip.run_cell("del wrn")
1069 ip.run_cell("del wrn")
1069
1070
1070
1071
1071 class TestImportNoDeprecate(tt.TempFileMixin):
1072 class TestImportNoDeprecate(tt.TempFileMixin):
1072
1073
1073 def setUp(self):
1074 def setUp(self):
1074 """Make a valid python temp file."""
1075 """Make a valid python temp file."""
1075 self.mktmp("""
1076 self.mktmp("""
1076 import warnings
1077 import warnings
1077 def wrn():
1078 def wrn():
1078 warnings.warn(
1079 warnings.warn(
1079 "I AM A WARNING",
1080 "I AM A WARNING",
1080 DeprecationWarning
1081 DeprecationWarning
1081 )
1082 )
1082 """)
1083 """)
1083 super().setUp()
1084 super().setUp()
1084
1085
1085 def test_no_dep(self):
1086 def test_no_dep(self):
1086 """
1087 """
1087 No deprecation warning should be raised from imported functions
1088 No deprecation warning should be raised from imported functions
1088 """
1089 """
1089 ip.run_cell("from {} import wrn".format(self.fname))
1090 ip.run_cell("from {} import wrn".format(self.fname))
1090
1091
1091 with tt.AssertNotPrints("I AM A WARNING"):
1092 with tt.AssertNotPrints("I AM A WARNING"):
1092 ip.run_cell("wrn()")
1093 ip.run_cell("wrn()")
1093 ip.run_cell("del wrn")
1094 ip.run_cell("del wrn")
1094
1095
1095
1096
1096 def test_custom_exc_count():
1097 def test_custom_exc_count():
1097 hook = mock.Mock(return_value=None)
1098 hook = mock.Mock(return_value=None)
1098 ip.set_custom_exc((SyntaxError,), hook)
1099 ip.set_custom_exc((SyntaxError,), hook)
1099 before = ip.execution_count
1100 before = ip.execution_count
1100 ip.run_cell("def foo()", store_history=True)
1101 ip.run_cell("def foo()", store_history=True)
1101 # restore default excepthook
1102 # restore default excepthook
1102 ip.set_custom_exc((), None)
1103 ip.set_custom_exc((), None)
1103 assert hook.call_count == 1
1104 assert hook.call_count == 1
1104 assert ip.execution_count == before + 1
1105 assert ip.execution_count == before + 1
1105
1106
1106
1107
1107 def test_run_cell_async():
1108 def test_run_cell_async():
1108 ip.run_cell("import asyncio")
1109 ip.run_cell("import asyncio")
1109 coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5")
1110 coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5")
1110 assert asyncio.iscoroutine(coro)
1111 assert asyncio.iscoroutine(coro)
1111 loop = asyncio.new_event_loop()
1112 loop = asyncio.new_event_loop()
1112 result = loop.run_until_complete(coro)
1113 result = loop.run_until_complete(coro)
1113 assert isinstance(result, interactiveshell.ExecutionResult)
1114 assert isinstance(result, interactiveshell.ExecutionResult)
1114 assert result.result == 5
1115 assert result.result == 5
1115
1116
1116
1117
1117 def test_run_cell_await():
1118 def test_run_cell_await():
1118 ip.run_cell("import asyncio")
1119 ip.run_cell("import asyncio")
1119 result = ip.run_cell("await asyncio.sleep(0.01); 10")
1120 result = ip.run_cell("await asyncio.sleep(0.01); 10")
1120 assert ip.user_ns["_"] == 10
1121 assert ip.user_ns["_"] == 10
1121
1122
1122
1123
1123 def test_run_cell_asyncio_run():
1124 def test_run_cell_asyncio_run():
1124 ip.run_cell("import asyncio")
1125 ip.run_cell("import asyncio")
1125 result = ip.run_cell("await asyncio.sleep(0.01); 1")
1126 result = ip.run_cell("await asyncio.sleep(0.01); 1")
1126 assert ip.user_ns["_"] == 1
1127 assert ip.user_ns["_"] == 1
1127 result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2")
1128 result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2")
1128 assert ip.user_ns["_"] == 2
1129 assert ip.user_ns["_"] == 2
1129 result = ip.run_cell("await asyncio.sleep(0.01); 3")
1130 result = ip.run_cell("await asyncio.sleep(0.01); 3")
1130 assert ip.user_ns["_"] == 3
1131 assert ip.user_ns["_"] == 3
1131
1132
1132
1133
1133 def test_should_run_async():
1134 def test_should_run_async():
1134 assert not ip.should_run_async("a = 5", transformed_cell="a = 5")
1135 assert not ip.should_run_async("a = 5", transformed_cell="a = 5")
1135 assert ip.should_run_async("await x", transformed_cell="await x")
1136 assert ip.should_run_async("await x", transformed_cell="await x")
1136 assert ip.should_run_async(
1137 assert ip.should_run_async(
1137 "import asyncio; await asyncio.sleep(1)",
1138 "import asyncio; await asyncio.sleep(1)",
1138 transformed_cell="import asyncio; await asyncio.sleep(1)",
1139 transformed_cell="import asyncio; await asyncio.sleep(1)",
1139 )
1140 )
1140
1141
1141
1142
1142 def test_set_custom_completer():
1143 def test_set_custom_completer():
1143 num_completers = len(ip.Completer.matchers)
1144 num_completers = len(ip.Completer.matchers)
1144
1145
1145 def foo(*args, **kwargs):
1146 def foo(*args, **kwargs):
1146 return "I'm a completer!"
1147 return "I'm a completer!"
1147
1148
1148 ip.set_custom_completer(foo, 0)
1149 ip.set_custom_completer(foo, 0)
1149
1150
1150 # check that we've really added a new completer
1151 # check that we've really added a new completer
1151 assert len(ip.Completer.matchers) == num_completers + 1
1152 assert len(ip.Completer.matchers) == num_completers + 1
1152
1153
1153 # check that the first completer is the function we defined
1154 # check that the first completer is the function we defined
1154 assert ip.Completer.matchers[0]() == "I'm a completer!"
1155 assert ip.Completer.matchers[0]() == "I'm a completer!"
1155
1156
1156 # clean up
1157 # clean up
1157 ip.Completer.custom_matchers.pop()
1158 ip.Completer.custom_matchers.pop()
1158
1159
1159
1160
1160 class TestShowTracebackAttack(unittest.TestCase):
1161 class TestShowTracebackAttack(unittest.TestCase):
1161 """Test that the interactive shell is resilient against the client attack of
1162 """Test that the interactive shell is resilient against the client attack of
1162 manipulating the showtracebacks method. These attacks shouldn't result in an
1163 manipulating the showtracebacks method. These attacks shouldn't result in an
1163 unhandled exception in the kernel."""
1164 unhandled exception in the kernel."""
1164
1165
1165 def setUp(self):
1166 def setUp(self):
1166 self.orig_showtraceback = interactiveshell.InteractiveShell.showtraceback
1167 self.orig_showtraceback = interactiveshell.InteractiveShell.showtraceback
1167
1168
1168 def tearDown(self):
1169 def tearDown(self):
1169 interactiveshell.InteractiveShell.showtraceback = self.orig_showtraceback
1170 interactiveshell.InteractiveShell.showtraceback = self.orig_showtraceback
1170
1171
1171 def test_set_show_tracebacks_none(self):
1172 def test_set_show_tracebacks_none(self):
1172 """Test the case of the client setting showtracebacks to None"""
1173 """Test the case of the client setting showtracebacks to None"""
1173
1174
1174 result = ip.run_cell(
1175 result = ip.run_cell(
1175 """
1176 """
1176 import IPython.core.interactiveshell
1177 import IPython.core.interactiveshell
1177 IPython.core.interactiveshell.InteractiveShell.showtraceback = None
1178 IPython.core.interactiveshell.InteractiveShell.showtraceback = None
1178
1179
1179 assert False, "This should not raise an exception"
1180 assert False, "This should not raise an exception"
1180 """
1181 """
1181 )
1182 )
1182 print(result)
1183 print(result)
1183
1184
1184 assert result.result is None
1185 assert result.result is None
1185 assert isinstance(result.error_in_exec, TypeError)
1186 assert isinstance(result.error_in_exec, TypeError)
1186 assert str(result.error_in_exec) == "'NoneType' object is not callable"
1187 assert str(result.error_in_exec) == "'NoneType' object is not callable"
1187
1188
1188 def test_set_show_tracebacks_noop(self):
1189 def test_set_show_tracebacks_noop(self):
1189 """Test the case of the client setting showtracebacks to a no op lambda"""
1190 """Test the case of the client setting showtracebacks to a no op lambda"""
1190
1191
1191 result = ip.run_cell(
1192 result = ip.run_cell(
1192 """
1193 """
1193 import IPython.core.interactiveshell
1194 import IPython.core.interactiveshell
1194 IPython.core.interactiveshell.InteractiveShell.showtraceback = lambda *args, **kwargs: None
1195 IPython.core.interactiveshell.InteractiveShell.showtraceback = lambda *args, **kwargs: None
1195
1196
1196 assert False, "This should not raise an exception"
1197 assert False, "This should not raise an exception"
1197 """
1198 """
1198 )
1199 )
1199 print(result)
1200 print(result)
1200
1201
1201 assert result.result is None
1202 assert result.result is None
1202 assert isinstance(result.error_in_exec, AssertionError)
1203 assert isinstance(result.error_in_exec, AssertionError)
1203 assert str(result.error_in_exec) == "This should not raise an exception"
1204 assert str(result.error_in_exec) == "This should not raise an exception"
1204
1205
1205
1206
1206 @skip_if_not_osx
1207 @skip_if_not_osx
1207 def test_enable_gui_osx():
1208 def test_enable_gui_osx():
1208 simple_prompt = ip.simple_prompt
1209 simple_prompt = ip.simple_prompt
1209 ip.simple_prompt = False
1210 ip.simple_prompt = False
1210
1211
1211 ip.enable_gui("osx")
1212 ip.enable_gui("osx")
1212 assert ip.active_eventloop == "osx"
1213 assert ip.active_eventloop == "osx"
1213 ip.enable_gui()
1214 ip.enable_gui()
1214
1215
1215 # The following line fails for IPython <= 8.25.0
1216 # The following line fails for IPython <= 8.25.0
1216 ip.enable_gui("macosx")
1217 ip.enable_gui("macosx")
1217 assert ip.active_eventloop == "osx"
1218 assert ip.active_eventloop == "osx"
1218 ip.enable_gui()
1219 ip.enable_gui()
1219
1220
1220 ip.simple_prompt = simple_prompt
1221 ip.simple_prompt = simple_prompt
@@ -1,245 +1,246
1 """Tests for the key interactiveshell module, where the main ipython class is defined.
1 """Tests for the key interactiveshell module, where the main ipython class is defined.
2 """
2 """
3
3
4 import stack_data
4 import stack_data
5 import sys
5
6
6 SV_VERSION = tuple([int(x) for x in stack_data.__version__.split(".")[0:2]])
7 SV_VERSION = tuple([int(x) for x in stack_data.__version__.split(".")[0:2]])
7
8
8
9
9 def test_reset():
10 def test_reset():
10 """reset must clear most namespaces."""
11 """reset must clear most namespaces."""
11
12
12 # Check that reset runs without error
13 # Check that reset runs without error
13 ip.reset()
14 ip.reset()
14
15
15 # Once we've reset it (to clear of any junk that might have been there from
16 # Once we've reset it (to clear of any junk that might have been there from
16 # other tests, we can count how many variables are in the user's namespace
17 # other tests, we can count how many variables are in the user's namespace
17 nvars_user_ns = len(ip.user_ns)
18 nvars_user_ns = len(ip.user_ns)
18 nvars_hidden = len(ip.user_ns_hidden)
19 nvars_hidden = len(ip.user_ns_hidden)
19
20
20 # Now add a few variables to user_ns, and check that reset clears them
21 # Now add a few variables to user_ns, and check that reset clears them
21 ip.user_ns['x'] = 1
22 ip.user_ns['x'] = 1
22 ip.user_ns['y'] = 1
23 ip.user_ns['y'] = 1
23 ip.reset()
24 ip.reset()
24
25
25 # Finally, check that all namespaces have only as many variables as we
26 # Finally, check that all namespaces have only as many variables as we
26 # expect to find in them:
27 # expect to find in them:
27 assert len(ip.user_ns) == nvars_user_ns
28 assert len(ip.user_ns) == nvars_user_ns
28 assert len(ip.user_ns_hidden) == nvars_hidden
29 assert len(ip.user_ns_hidden) == nvars_hidden
29
30
30
31
31 # Tests for reporting of exceptions in various modes, handling of SystemExit,
32 # Tests for reporting of exceptions in various modes, handling of SystemExit,
32 # and %tb functionality. This is really a mix of testing ultraTB and interactiveshell.
33 # and %tb functionality. This is really a mix of testing ultraTB and interactiveshell.
33
34
34 def doctest_tb_plain():
35 def doctest_tb_plain():
35 """
36 """
36 In [18]: xmode plain
37 In [18]: xmode plain
37 Exception reporting mode: Plain
38 Exception reporting mode: Plain
38
39
39 In [19]: run simpleerr.py
40 In [19]: run simpleerr.py
40 Traceback (most recent call last):
41 Traceback (most recent call last):
41 File ...:...
42 File ...:...
42 bar(mode)
43 bar(mode)
43 File ...:... in bar
44 File ...:... in bar
44 div0()
45 div0()
45 File ...:... in div0
46 File ...:... in div0
46 x/y
47 x/y
47 ZeroDivisionError: ...
48 ZeroDivisionError: ...
48 """
49 """
49
50
50
51
51 def doctest_tb_context():
52 def doctest_tb_context():
52 """
53 """
53 In [3]: xmode context
54 In [3]: xmode context
54 Exception reporting mode: Context
55 Exception reporting mode: Context
55
56
56 In [4]: run simpleerr.py
57 In [4]: run simpleerr.py
57 ---------------------------------------------------------------------------
58 ---------------------------------------------------------------------------
58 ZeroDivisionError Traceback (most recent call last)
59 ZeroDivisionError Traceback (most recent call last)
59 <BLANKLINE>
60 <BLANKLINE>
60 ...
61 ...
61 30 except IndexError:
62 30 except IndexError:
62 31 mode = 'div'
63 31 mode = 'div'
63 ---> 33 bar(mode)
64 ---> 33 bar(mode)
64 <BLANKLINE>
65 <BLANKLINE>
65 ... in bar(mode)
66 ... in bar(mode)
66 15 "bar"
67 15 "bar"
67 16 if mode=='div':
68 16 if mode=='div':
68 ---> 17 div0()
69 ---> 17 div0()
69 18 elif mode=='exit':
70 18 elif mode=='exit':
70 19 try:
71 19 try:
71 <BLANKLINE>
72 <BLANKLINE>
72 ... in div0()
73 ... in div0()
73 6 x = 1
74 6 x = 1
74 7 y = 0
75 7 y = 0
75 ----> 8 x/y
76 ----> 8 x/y
76 <BLANKLINE>
77 <BLANKLINE>
77 ZeroDivisionError: ..."""
78 ZeroDivisionError: ..."""
78
79
79
80
80 def doctest_tb_verbose():
81 def doctest_tb_verbose():
81 """
82 """
82 In [5]: xmode verbose
83 In [5]: xmode verbose
83 Exception reporting mode: Verbose
84 Exception reporting mode: Verbose
84
85
85 In [6]: run simpleerr.py
86 In [6]: run simpleerr.py
86 ---------------------------------------------------------------------------
87 ---------------------------------------------------------------------------
87 ZeroDivisionError Traceback (most recent call last)
88 ZeroDivisionError Traceback (most recent call last)
88 <BLANKLINE>
89 <BLANKLINE>
89 ...
90 ...
90 30 except IndexError:
91 30 except IndexError:
91 31 mode = 'div'
92 31 mode = 'div'
92 ---> 33 bar(mode)
93 ---> 33 bar(mode)
93 mode = 'div'
94 mode = 'div'
94 <BLANKLINE>
95 <BLANKLINE>
95 ... in bar(mode='div')
96 ... in bar(mode='div')
96 15 "bar"
97 15 "bar"
97 16 if mode=='div':
98 16 if mode=='div':
98 ---> 17 div0()
99 ---> 17 div0()
99 18 elif mode=='exit':
100 18 elif mode=='exit':
100 19 try:
101 19 try:
101 <BLANKLINE>
102 <BLANKLINE>
102 ... in div0()
103 ... in div0()
103 6 x = 1
104 6 x = 1
104 7 y = 0
105 7 y = 0
105 ----> 8 x/y
106 ----> 8 x/y
106 x = 1
107 x = 1
107 y = 0
108 y = 0
108 <BLANKLINE>
109 <BLANKLINE>
109 ZeroDivisionError: ...
110 ZeroDivisionError: ...
110 """
111 """
111
112
112
113
113 def doctest_tb_sysexit():
114 def doctest_tb_sysexit():
114 """
115 """
115 In [17]: %xmode plain
116 In [17]: %xmode plain
116 Exception reporting mode: Plain
117 Exception reporting mode: Plain
117
118
118 In [18]: %run simpleerr.py exit
119 In [18]: %run simpleerr.py exit
119 An exception has occurred, use %tb to see the full traceback.
120 An exception has occurred, use %tb to see the full traceback.
120 SystemExit: (1, 'Mode = exit')
121 SystemExit: (1, 'Mode = exit')
121
122
122 In [19]: %run simpleerr.py exit 2
123 In [19]: %run simpleerr.py exit 2
123 An exception has occurred, use %tb to see the full traceback.
124 An exception has occurred, use %tb to see the full traceback.
124 SystemExit: (2, 'Mode = exit')
125 SystemExit: (2, 'Mode = exit')
125
126
126 In [20]: %tb
127 In [20]: %tb
127 Traceback (most recent call last):
128 Traceback (most recent call last):
128 File ...:... in execfile
129 File ...:... in execfile
129 exec(compiler(f.read(), fname, "exec"), glob, loc)
130 exec(compiler(f.read(), fname, "exec"), glob, loc)
130 File ...:...
131 File ...:...
131 bar(mode)
132 bar(mode)
132 File ...:... in bar
133 File ...:... in bar
133 sysexit(stat, mode)
134 sysexit(stat, mode)
134 File ...:... in sysexit
135 File ...:... in sysexit
135 raise SystemExit(stat, f"Mode = {mode}")
136 raise SystemExit(stat, f"Mode = {mode}")
136 SystemExit: (2, 'Mode = exit')
137 SystemExit: (2, 'Mode = exit')
137
138
138 In [21]: %xmode context
139 In [21]: %xmode context
139 Exception reporting mode: Context
140 Exception reporting mode: Context
140
141
141 In [22]: %tb
142 In [22]: %tb
142 ---------------------------------------------------------------------------
143 ---------------------------------------------------------------------------
143 SystemExit Traceback (most recent call last)
144 SystemExit Traceback (most recent call last)
144 File ..., in execfile(fname, glob, loc, compiler)
145 File ..., in execfile(fname, glob, loc, compiler)
145 ... with open(fname, "rb") as f:
146 ... with open(fname, "rb") as f:
146 ... compiler = compiler or compile
147 ... compiler = compiler or compile
147 ---> ... exec(compiler(f.read(), fname, "exec"), glob, loc)
148 ---> ... exec(compiler(f.read(), fname, "exec"), glob, loc)
148 ...
149 ...
149 30 except IndexError:
150 30 except IndexError:
150 31 mode = 'div'
151 31 mode = 'div'
151 ---> 33 bar(mode)
152 ---> 33 bar(mode)
152 <BLANKLINE>
153 <BLANKLINE>
153 ...bar(mode)
154 ...bar(mode)
154 21 except:
155 21 except:
155 22 stat = 1
156 22 stat = 1
156 ---> 23 sysexit(stat, mode)
157 ---> 23 sysexit(stat, mode)
157 24 else:
158 24 else:
158 25 raise ValueError('Unknown mode')
159 25 raise ValueError('Unknown mode')
159 <BLANKLINE>
160 <BLANKLINE>
160 ...sysexit(stat, mode)
161 ...sysexit(stat, mode)
161 10 def sysexit(stat, mode):
162 10 def sysexit(stat, mode):
162 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
163 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
163 <BLANKLINE>
164 <BLANKLINE>
164 SystemExit: (2, 'Mode = exit')
165 SystemExit: (2, 'Mode = exit')
165 """
166 """
166
167
167
168
168 if SV_VERSION < (0, 6):
169 if SV_VERSION < (0, 6):
169
170
170 def doctest_tb_sysexit_verbose_stack_data_05():
171 def doctest_tb_sysexit_verbose_stack_data_05():
171 """
172 """
172 In [18]: %run simpleerr.py exit
173 In [18]: %run simpleerr.py exit
173 An exception has occurred, use %tb to see the full traceback.
174 An exception has occurred, use %tb to see the full traceback.
174 SystemExit: (1, 'Mode = exit')
175 SystemExit: (1, 'Mode = exit')
175
176
176 In [19]: %run simpleerr.py exit 2
177 In [19]: %run simpleerr.py exit 2
177 An exception has occurred, use %tb to see the full traceback.
178 An exception has occurred, use %tb to see the full traceback.
178 SystemExit: (2, 'Mode = exit')
179 SystemExit: (2, 'Mode = exit')
179
180
180 In [23]: %xmode verbose
181 In [23]: %xmode verbose
181 Exception reporting mode: Verbose
182 Exception reporting mode: Verbose
182
183
183 In [24]: %tb
184 In [24]: %tb
184 ---------------------------------------------------------------------------
185 ---------------------------------------------------------------------------
185 SystemExit Traceback (most recent call last)
186 SystemExit Traceback (most recent call last)
186 <BLANKLINE>
187 <BLANKLINE>
187 ...
188 ...
188 30 except IndexError:
189 30 except IndexError:
189 31 mode = 'div'
190 31 mode = 'div'
190 ---> 33 bar(mode)
191 ---> 33 bar(mode)
191 mode = 'exit'
192 mode = 'exit'
192 <BLANKLINE>
193 <BLANKLINE>
193 ... in bar(mode='exit')
194 ... in bar(mode='exit')
194 ... except:
195 ... except:
195 ... stat = 1
196 ... stat = 1
196 ---> ... sysexit(stat, mode)
197 ---> ... sysexit(stat, mode)
197 mode = 'exit'
198 mode = 'exit'
198 stat = 2
199 stat = 2
199 ... else:
200 ... else:
200 ... raise ValueError('Unknown mode')
201 ... raise ValueError('Unknown mode')
201 <BLANKLINE>
202 <BLANKLINE>
202 ... in sysexit(stat=2, mode='exit')
203 ... in sysexit(stat=2, mode='exit')
203 10 def sysexit(stat, mode):
204 10 def sysexit(stat, mode):
204 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
205 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
205 stat = 2
206 stat = 2
206 <BLANKLINE>
207 <BLANKLINE>
207 SystemExit: (2, 'Mode = exit')
208 SystemExit: (2, 'Mode = exit')
208 """
209 """
209
210
210
211
211 def test_run_cell():
212 def test_run_cell():
212 import textwrap
213 import textwrap
213
214
214 ip.run_cell("a = 10\na+=1")
215 ip.run_cell("a = 10\na+=1")
215 ip.run_cell("assert a == 11\nassert 1")
216 ip.run_cell("assert a == 11\nassert 1")
216
217
217 assert ip.user_ns["a"] == 11
218 assert ip.user_ns["a"] == 11
218 complex = textwrap.dedent(
219 complex = textwrap.dedent(
219 """
220 """
220 if 1:
221 if 1:
221 print "hello"
222 print "hello"
222 if 1:
223 if 1:
223 print "world"
224 print "world"
224
225
225 if 2:
226 if 2:
226 print "foo"
227 print "foo"
227
228
228 if 3:
229 if 3:
229 print "bar"
230 print "bar"
230
231
231 if 4:
232 if 4:
232 print "bar"
233 print "bar"
233
234
234 """
235 """
235 )
236 )
236 # Simply verifies that this kind of input is run
237 # Simply verifies that this kind of input is run
237 ip.run_cell(complex)
238 ip.run_cell(complex)
238
239
239
240
240 def test_db():
241 def test_db():
241 """Test the internal database used for variable persistence."""
242 """Test the internal database used for variable persistence."""
242 ip.db["__unittest_"] = 12
243 ip.db["__unittest_"] = 12
243 assert ip.db["__unittest_"] == 12
244 assert ip.db["__unittest_"] == 12
244 del ip.db["__unittest_"]
245 del ip.db["__unittest_"]
245 assert "__unittest_" not in ip.db
246 assert "__unittest_" not in ip.db
@@ -1,1591 +1,1556
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for various magic functions."""
2 """Tests for various magic functions."""
3
3
4 import gc
4 import gc
5 import io
5 import io
6 import os
6 import os
7 import re
7 import re
8 import shlex
8 import shlex
9 import sys
9 import sys
10 import warnings
10 import warnings
11 from importlib import invalidate_caches
11 from importlib import invalidate_caches
12 from io import StringIO
12 from io import StringIO
13 from pathlib import Path
13 from pathlib import Path
14 from textwrap import dedent
14 from textwrap import dedent
15 from unittest import TestCase, mock
15 from unittest import TestCase, mock
16
16
17 import pytest
17 import pytest
18
18
19 from IPython import get_ipython
19 from IPython import get_ipython
20 from IPython.core import magic
20 from IPython.core import magic
21 from IPython.core.error import UsageError
21 from IPython.core.error import UsageError
22 from IPython.core.magic import (
22 from IPython.core.magic import (
23 Magics,
23 Magics,
24 cell_magic,
24 cell_magic,
25 line_magic,
25 line_magic,
26 magics_class,
26 magics_class,
27 register_cell_magic,
27 register_cell_magic,
28 register_line_magic,
28 register_line_magic,
29 )
29 )
30 from IPython.core.magics import code, execution, logging, osm, script
30 from IPython.core.magics import code, execution, logging, osm, script
31 from IPython.testing import decorators as dec
31 from IPython.testing import decorators as dec
32 from IPython.testing import tools as tt
32 from IPython.testing import tools as tt
33 from IPython.utils.io import capture_output
33 from IPython.utils.io import capture_output
34 from IPython.utils.process import find_cmd
34 from IPython.utils.process import find_cmd
35 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
35 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
36 from IPython.utils.syspathcontext import prepended_to_syspath
36 from IPython.utils.syspathcontext import prepended_to_syspath
37
37
38 # import needed by doctest
38 from .test_debugger import PdbTestInput
39 from .test_debugger import PdbTestInput # noqa: F401
40
41 _ip = get_ipython()
42
39
40 from tempfile import NamedTemporaryFile
43
41
44 @magic.magics_class
42 @magic.magics_class
45 class DummyMagics(magic.Magics):
43 class DummyMagics(magic.Magics): pass
46 pass
47
48
44
49 def test_extract_code_ranges():
45 def test_extract_code_ranges():
50 instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :"
46 instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :"
51 expected = [
47 expected = [
52 (0, 1),
48 (0, 1),
53 (2, 3),
49 (2, 3),
54 (4, 6),
50 (4, 6),
55 (6, 9),
51 (6, 9),
56 (9, 14),
52 (9, 14),
57 (16, None),
53 (16, None),
58 (None, 9),
54 (None, 9),
59 (9, None),
55 (9, None),
60 (None, 13),
56 (None, 13),
61 (None, None),
57 (None, None),
62 ]
58 ]
63 actual = list(code.extract_code_ranges(instr))
59 actual = list(code.extract_code_ranges(instr))
64 assert actual == expected
60 assert actual == expected
65
61
66
67 def test_extract_symbols():
62 def test_extract_symbols():
68 source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n"""
63 source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n"""
69 symbols_args = ["a", "b", "A", "A,b", "A,a", "z"]
64 symbols_args = ["a", "b", "A", "A,b", "A,a", "z"]
70 expected = [
65 expected = [([], ['a']),
71 ([], ["a"]),
66 (["def b():\n return 42\n"], []),
72 (["def b():\n return 42\n"], []),
67 (["class A: pass\n"], []),
73 (["class A: pass\n"], []),
68 (["class A: pass\n", "def b():\n return 42\n"], []),
74 (["class A: pass\n", "def b():\n return 42\n"], []),
69 (["class A: pass\n"], ['a']),
75 (["class A: pass\n"], ["a"]),
70 ([], ['z'])]
76 ([], ["z"]),
77 ]
78 for symbols, exp in zip(symbols_args, expected):
71 for symbols, exp in zip(symbols_args, expected):
79 assert code.extract_symbols(source, symbols) == exp
72 assert code.extract_symbols(source, symbols) == exp
80
73
81
74
82 def test_extract_symbols_raises_exception_with_non_python_code():
75 def test_extract_symbols_raises_exception_with_non_python_code():
83 source = "=begin A Ruby program :)=end\n" "def hello\n" "puts 'Hello world'\n" "end"
76 source = ("=begin A Ruby program :)=end\n"
77 "def hello\n"
78 "puts 'Hello world'\n"
79 "end")
84 with pytest.raises(SyntaxError):
80 with pytest.raises(SyntaxError):
85 code.extract_symbols(source, "hello")
81 code.extract_symbols(source, "hello")
86
82
87
83
88 def test_magic_not_found():
84 def test_magic_not_found():
89 # magic not found raises UsageError
85 # magic not found raises UsageError
90 with pytest.raises(UsageError):
86 with pytest.raises(UsageError):
91 _ip.run_line_magic("doesntexist", "")
87 _ip.run_line_magic("doesntexist", "")
92
88
93 # ensure result isn't success when a magic isn't found
89 # ensure result isn't success when a magic isn't found
94 result = _ip.run_cell("%doesntexist")
90 result = _ip.run_cell('%doesntexist')
95 assert isinstance(result.error_in_exec, UsageError)
91 assert isinstance(result.error_in_exec, UsageError)
96
92
97
93
98 def test_cell_magic_not_found():
94 def test_cell_magic_not_found():
99 # magic not found raises UsageError
95 # magic not found raises UsageError
100 with pytest.raises(UsageError):
96 with pytest.raises(UsageError):
101 _ip.run_cell_magic("doesntexist", "line", "cell")
97 _ip.run_cell_magic('doesntexist', 'line', 'cell')
102
98
103 # ensure result isn't success when a magic isn't found
99 # ensure result isn't success when a magic isn't found
104 result = _ip.run_cell("%%doesntexist")
100 result = _ip.run_cell('%%doesntexist')
105 assert isinstance(result.error_in_exec, UsageError)
101 assert isinstance(result.error_in_exec, UsageError)
106
102
107
103
108 def test_magic_error_status():
104 def test_magic_error_status():
109 def fail(shell):
105 def fail(shell):
110 1 / 0
106 1/0
111
112 _ip.register_magic_function(fail)
107 _ip.register_magic_function(fail)
113 result = _ip.run_cell("%fail")
108 result = _ip.run_cell('%fail')
114 assert isinstance(result.error_in_exec, ZeroDivisionError)
109 assert isinstance(result.error_in_exec, ZeroDivisionError)
115
110
116
111
117 def test_config():
112 def test_config():
118 """test that config magic does not raise
113 """ test that config magic does not raise
119 can happen if Configurable init is moved too early into
114 can happen if Configurable init is moved too early into
120 Magics.__init__ as then a Config object will be registered as a
115 Magics.__init__ as then a Config object will be registered as a
121 magic.
116 magic.
122 """
117 """
123 ## should not raise.
118 ## should not raise.
124 _ip.run_line_magic("config", "")
119 _ip.run_line_magic("config", "")
125
120
126
121
127 def test_config_available_configs():
122 def test_config_available_configs():
128 """test that config magic prints available configs in unique and
123 """ test that config magic prints available configs in unique and
129 sorted order."""
124 sorted order. """
130 with capture_output() as captured:
125 with capture_output() as captured:
131 _ip.run_line_magic("config", "")
126 _ip.run_line_magic("config", "")
132
127
133 stdout = captured.stdout
128 stdout = captured.stdout
134 config_classes = stdout.strip().split("\n")[1:]
129 config_classes = stdout.strip().split('\n')[1:]
135 assert config_classes == sorted(set(config_classes))
130 assert config_classes == sorted(set(config_classes))
136
131
137
138 def test_config_print_class():
132 def test_config_print_class():
139 """test that config with a classname prints the class's options."""
133 """ test that config with a classname prints the class's options. """
140 with capture_output() as captured:
134 with capture_output() as captured:
141 _ip.run_line_magic("config", "TerminalInteractiveShell")
135 _ip.run_line_magic("config", "TerminalInteractiveShell")
142
136
143 stdout = captured.stdout
137 stdout = captured.stdout
144 assert re.match(
138 assert re.match(
145 "TerminalInteractiveShell.* options", stdout.splitlines()[0]
139 "TerminalInteractiveShell.* options", stdout.splitlines()[0]
146 ), f"{stdout}\n\n1st line of stdout not like 'TerminalInteractiveShell.* options'"
140 ), f"{stdout}\n\n1st line of stdout not like 'TerminalInteractiveShell.* options'"
147
141
148
142
149 def test_rehashx():
143 def test_rehashx():
150 # clear up everything
144 # clear up everything
151 _ip.alias_manager.clear_aliases()
145 _ip.alias_manager.clear_aliases()
152 del _ip.db["syscmdlist"]
146 del _ip.db['syscmdlist']
153
147
154 _ip.run_line_magic("rehashx", "")
148 _ip.run_line_magic("rehashx", "")
155 # Practically ALL ipython development systems will have more than 10 aliases
149 # Practically ALL ipython development systems will have more than 10 aliases
156
150
157 assert len(_ip.alias_manager.aliases) > 10
151 assert len(_ip.alias_manager.aliases) > 10
158 for name, cmd in _ip.alias_manager.aliases:
152 for name, cmd in _ip.alias_manager.aliases:
159 # we must strip dots from alias names
153 # we must strip dots from alias names
160 assert "." not in name
154 assert "." not in name
161
155
162 # rehashx must fill up syscmdlist
156 # rehashx must fill up syscmdlist
163 scoms = _ip.db["syscmdlist"]
157 scoms = _ip.db['syscmdlist']
164 assert len(scoms) > 10
158 assert len(scoms) > 10
165
159
166
160
167 def test_magic_parse_options():
161 def test_magic_parse_options():
168 """Test that we don't mangle paths when parsing magic options."""
162 """Test that we don't mangle paths when parsing magic options."""
169 ip = get_ipython()
163 ip = get_ipython()
170 path = "c:\\x"
164 path = 'c:\\x'
171 m = DummyMagics(ip)
165 m = DummyMagics(ip)
172 opts = m.parse_options("-f %s" % path, "f:")[0]
166 opts = m.parse_options('-f %s' % path,'f:')[0]
173 # argv splitting is os-dependent
167 # argv splitting is os-dependent
174 if os.name == "posix":
168 if os.name == 'posix':
175 expected = "c:x"
169 expected = 'c:x'
176 else:
170 else:
177 expected = path
171 expected = path
178 assert opts["f"] == expected
172 assert opts["f"] == expected
179
173
180
174
181 def test_magic_parse_long_options():
175 def test_magic_parse_long_options():
182 """Magic.parse_options can handle --foo=bar long options"""
176 """Magic.parse_options can handle --foo=bar long options"""
183 ip = get_ipython()
177 ip = get_ipython()
184 m = DummyMagics(ip)
178 m = DummyMagics(ip)
185 opts, _ = m.parse_options("--foo --bar=bubble", "a", "foo", "bar=")
179 opts, _ = m.parse_options("--foo --bar=bubble", "a", "foo", "bar=")
186 assert "foo" in opts
180 assert "foo" in opts
187 assert "bar" in opts
181 assert "bar" in opts
188 assert opts["bar"] == "bubble"
182 assert opts["bar"] == "bubble"
189
183
190
184
191 def doctest_hist_f():
185 def doctest_hist_f():
192 """Test %hist -f with temporary filename.
186 """Test %hist -f with temporary filename.
193
187
194 In [9]: import tempfile
188 In [9]: import tempfile
195
189
196 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
190 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
197
191
198 In [11]: %hist -nl -f $tfile 3
192 In [11]: %hist -nl -f $tfile 3
199
193
200 In [13]: import os; os.unlink(tfile)
194 In [13]: import os; os.unlink(tfile)
201 """
195 """
202
196
203
197
204 def doctest_hist_op():
198 def doctest_hist_op():
205 """Test %hist -op
199 """Test %hist -op
206
200
207 In [1]: class b(float):
201 In [1]: class b(float):
208 ...: pass
202 ...: pass
209 ...:
203 ...:
210
204
211 In [2]: class s(object):
205 In [2]: class s(object):
212 ...: def __str__(self):
206 ...: def __str__(self):
213 ...: return 's'
207 ...: return 's'
214 ...:
208 ...:
215
209
216 In [3]:
210 In [3]:
217
211
218 In [4]: class r(b):
212 In [4]: class r(b):
219 ...: def __repr__(self):
213 ...: def __repr__(self):
220 ...: return 'r'
214 ...: return 'r'
221 ...:
215 ...:
222
216
223 In [5]: class sr(s,r): pass
217 In [5]: class sr(s,r): pass
224 ...:
218 ...:
225
219
226 In [6]:
220 In [6]:
227
221
228 In [7]: bb=b()
222 In [7]: bb=b()
229
223
230 In [8]: ss=s()
224 In [8]: ss=s()
231
225
232 In [9]: rr=r()
226 In [9]: rr=r()
233
227
234 In [10]: ssrr=sr()
228 In [10]: ssrr=sr()
235
229
236 In [11]: 4.5
230 In [11]: 4.5
237 Out[11]: 4.5
231 Out[11]: 4.5
238
232
239 In [12]: str(ss)
233 In [12]: str(ss)
240 Out[12]: 's'
234 Out[12]: 's'
241
235
242 In [13]:
236 In [13]:
243
237
244 In [14]: %hist -op
238 In [14]: %hist -op
245 >>> class b:
239 >>> class b:
246 ... pass
240 ... pass
247 ...
241 ...
248 >>> class s(b):
242 >>> class s(b):
249 ... def __str__(self):
243 ... def __str__(self):
250 ... return 's'
244 ... return 's'
251 ...
245 ...
252 >>>
246 >>>
253 >>> class r(b):
247 >>> class r(b):
254 ... def __repr__(self):
248 ... def __repr__(self):
255 ... return 'r'
249 ... return 'r'
256 ...
250 ...
257 >>> class sr(s,r): pass
251 >>> class sr(s,r): pass
258 >>>
252 >>>
259 >>> bb=b()
253 >>> bb=b()
260 >>> ss=s()
254 >>> ss=s()
261 >>> rr=r()
255 >>> rr=r()
262 >>> ssrr=sr()
256 >>> ssrr=sr()
263 >>> 4.5
257 >>> 4.5
264 4.5
258 4.5
265 >>> str(ss)
259 >>> str(ss)
266 's'
260 's'
267 >>>
261 >>>
268 """
262 """
269
263
270
271 def test_hist_pof():
264 def test_hist_pof():
272 ip = get_ipython()
265 ip = get_ipython()
273 ip.run_cell("1+2", store_history=True)
266 ip.run_cell("1+2", store_history=True)
274 # raise Exception(ip.history_manager.session_number)
267 #raise Exception(ip.history_manager.session_number)
275 # raise Exception(list(ip.history_manager._get_range_session()))
268 #raise Exception(list(ip.history_manager._get_range_session()))
276 with TemporaryDirectory() as td:
269 with TemporaryDirectory() as td:
277 tf = os.path.join(td, "hist.py")
270 tf = os.path.join(td, 'hist.py')
278 ip.run_line_magic("history", "-pof %s" % tf)
271 ip.run_line_magic('history', '-pof %s' % tf)
279 assert os.path.isfile(tf)
272 assert os.path.isfile(tf)
280
273
281
274
282 def test_macro():
275 def test_macro():
283 ip = get_ipython()
276 ip = get_ipython()
284 ip.history_manager.reset() # Clear any existing history.
277 ip.history_manager.reset() # Clear any existing history.
285 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
278 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
286 for i, cmd in enumerate(cmds, start=1):
279 for i, cmd in enumerate(cmds, start=1):
287 ip.history_manager.store_inputs(i, cmd)
280 ip.history_manager.store_inputs(i, cmd)
288 ip.run_line_magic("macro", "test 1-3")
281 ip.run_line_magic("macro", "test 1-3")
289 assert ip.user_ns["test"].value == "\n".join(cmds) + "\n"
282 assert ip.user_ns["test"].value == "\n".join(cmds) + "\n"
290
283
291 # List macros
284 # List macros
292 assert "test" in ip.run_line_magic("macro", "")
285 assert "test" in ip.run_line_magic("macro", "")
293
286
294
287
295 def test_macro_run():
288 def test_macro_run():
296 """Test that we can run a multi-line macro successfully."""
289 """Test that we can run a multi-line macro successfully."""
297 ip = get_ipython()
290 ip = get_ipython()
298 ip.history_manager.reset()
291 ip.history_manager.reset()
299 cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"]
292 cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"]
300 for cmd in cmds:
293 for cmd in cmds:
301 ip.run_cell(cmd, store_history=True)
294 ip.run_cell(cmd, store_history=True)
302 assert ip.user_ns["test"].value == "a+=1\nprint(a)\n"
295 assert ip.user_ns["test"].value == "a+=1\nprint(a)\n"
303 with tt.AssertPrints("12"):
296 with tt.AssertPrints("12"):
304 ip.run_cell("test")
297 ip.run_cell("test")
305 with tt.AssertPrints("13"):
298 with tt.AssertPrints("13"):
306 ip.run_cell("test")
299 ip.run_cell("test")
307
300
308
301
309 def test_magic_magic():
302 def test_magic_magic():
310 """Test %magic"""
303 """Test %magic"""
311 ip = get_ipython()
304 ip = get_ipython()
312 with capture_output() as captured:
305 with capture_output() as captured:
313 ip.run_line_magic("magic", "")
306 ip.run_line_magic("magic", "")
314
307
315 stdout = captured.stdout
308 stdout = captured.stdout
316 assert "%magic" in stdout
309 assert "%magic" in stdout
317 assert "IPython" in stdout
310 assert "IPython" in stdout
318 assert "Available" in stdout
311 assert "Available" in stdout
319
312
320
313
321 @dec.skipif_not_numpy
314 @dec.skipif_not_numpy
322 def test_numpy_reset_array_undec():
315 def test_numpy_reset_array_undec():
323 "Test '%reset array' functionality"
316 "Test '%reset array' functionality"
324 _ip.ex("import numpy as np")
317 _ip.ex("import numpy as np")
325 _ip.ex("a = np.empty(2)")
318 _ip.ex("a = np.empty(2)")
326 assert "a" in _ip.user_ns
319 assert "a" in _ip.user_ns
327 _ip.run_line_magic("reset", "-f array")
320 _ip.run_line_magic("reset", "-f array")
328 assert "a" not in _ip.user_ns
321 assert "a" not in _ip.user_ns
329
322
330
323
331 def test_reset_out():
324 def test_reset_out():
332 "Test '%reset out' magic"
325 "Test '%reset out' magic"
333 _ip.run_cell("parrot = 'dead'", store_history=True)
326 _ip.run_cell("parrot = 'dead'", store_history=True)
334 # test '%reset -f out', make an Out prompt
327 # test '%reset -f out', make an Out prompt
335 _ip.run_cell("parrot", store_history=True)
328 _ip.run_cell("parrot", store_history=True)
336 assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")]
329 assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")]
337 _ip.run_line_magic("reset", "-f out")
330 _ip.run_line_magic("reset", "-f out")
338 assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")]
331 assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")]
339 assert len(_ip.user_ns["Out"]) == 0
332 assert len(_ip.user_ns["Out"]) == 0
340
333
341
334
342 def test_reset_in():
335 def test_reset_in():
343 "Test '%reset in' magic"
336 "Test '%reset in' magic"
344 # test '%reset -f in'
337 # test '%reset -f in'
345 _ip.run_cell("parrot", store_history=True)
338 _ip.run_cell("parrot", store_history=True)
346 assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
339 assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
347 _ip.run_line_magic("reset", "-f in")
340 _ip.run_line_magic("reset", "-f in")
348 assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
341 assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
349 assert len(set(_ip.user_ns["In"])) == 1
342 assert len(set(_ip.user_ns["In"])) == 1
350
343
351
344
352 def test_reset_dhist():
345 def test_reset_dhist():
353 "Test '%reset dhist' magic"
346 "Test '%reset dhist' magic"
354 _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing
347 _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing
355 _ip.run_line_magic("cd", os.path.dirname(pytest.__file__))
348 _ip.run_line_magic("cd", os.path.dirname(pytest.__file__))
356 _ip.run_line_magic("cd", "-")
349 _ip.run_line_magic("cd", "-")
357 assert len(_ip.user_ns["_dh"]) > 0
350 assert len(_ip.user_ns["_dh"]) > 0
358 _ip.run_line_magic("reset", "-f dhist")
351 _ip.run_line_magic("reset", "-f dhist")
359 assert len(_ip.user_ns["_dh"]) == 0
352 assert len(_ip.user_ns["_dh"]) == 0
360 _ip.run_cell("_dh = [d for d in tmp]") # restore
353 _ip.run_cell("_dh = [d for d in tmp]") # restore
361
354
362
355
363 def test_reset_in_length():
356 def test_reset_in_length():
364 "Test that '%reset in' preserves In[] length"
357 "Test that '%reset in' preserves In[] length"
365 _ip.run_cell("print 'foo'")
358 _ip.run_cell("print 'foo'")
366 _ip.run_cell("reset -f in")
359 _ip.run_cell("reset -f in")
367 assert len(_ip.user_ns["In"]) == _ip.displayhook.prompt_count + 1
360 assert len(_ip.user_ns["In"]) == _ip.displayhook.prompt_count + 1
368
361
369
362
370 class TestResetErrors(TestCase):
363 class TestResetErrors(TestCase):
364
371 def test_reset_redefine(self):
365 def test_reset_redefine(self):
366
372 @magics_class
367 @magics_class
373 class KernelMagics(Magics):
368 class KernelMagics(Magics):
374 @line_magic
369 @line_magic
375 def less(self, shell):
370 def less(self, shell): pass
376 pass
377
371
378 _ip.register_magics(KernelMagics)
372 _ip.register_magics(KernelMagics)
379
373
380 with self.assertLogs() as cm:
374 with self.assertLogs() as cm:
381 # hack, we want to just capture logs, but assertLogs fails if not
375 # hack, we want to just capture logs, but assertLogs fails if not
382 # logs get produce.
376 # logs get produce.
383 # so log one things we ignore.
377 # so log one things we ignore.
384 import logging as log_mod
378 import logging as log_mod
385
386 log = log_mod.getLogger()
379 log = log_mod.getLogger()
387 log.info("Nothing")
380 log.info('Nothing')
388 # end hack.
381 # end hack.
389 _ip.run_cell("reset -f")
382 _ip.run_cell("reset -f")
390
383
391 assert len(cm.output) == 1
384 assert len(cm.output) == 1
392 for out in cm.output:
385 for out in cm.output:
393 assert "Invalid alias" not in out
386 assert "Invalid alias" not in out
394
387
395
396 def test_tb_syntaxerror():
388 def test_tb_syntaxerror():
397 """test %tb after a SyntaxError"""
389 """test %tb after a SyntaxError"""
398 ip = get_ipython()
390 ip = get_ipython()
399 ip.run_cell("for")
391 ip.run_cell("for")
400
392
401 # trap and validate stdout
393 # trap and validate stdout
402 save_stdout = sys.stdout
394 save_stdout = sys.stdout
403 try:
395 try:
404 sys.stdout = StringIO()
396 sys.stdout = StringIO()
405 ip.run_cell("%tb")
397 ip.run_cell("%tb")
406 out = sys.stdout.getvalue()
398 out = sys.stdout.getvalue()
407 finally:
399 finally:
408 sys.stdout = save_stdout
400 sys.stdout = save_stdout
409 # trim output, and only check the last line
401 # trim output, and only check the last line
410 last_line = out.rstrip().splitlines()[-1].strip()
402 last_line = out.rstrip().splitlines()[-1].strip()
411 assert last_line == "SyntaxError: invalid syntax"
403 assert last_line == "SyntaxError: invalid syntax"
412
404
413
405
414 def test_time():
406 def test_time():
415 ip = get_ipython()
407 ip = get_ipython()
416
408
417 with tt.AssertPrints("Wall time: "):
409 with tt.AssertPrints("Wall time: "):
418 ip.run_cell("%time None")
410 ip.run_cell("%time None")
419
411
420 ip.run_cell("def f(kmjy):\n" " %time print (2*kmjy)")
412 ip.run_cell("def f(kmjy):\n"
413 " %time print (2*kmjy)")
421
414
422 with tt.AssertPrints("Wall time: "):
415 with tt.AssertPrints("Wall time: "):
423 with tt.AssertPrints("hihi", suppress=False):
416 with tt.AssertPrints("hihi", suppress=False):
424 ip.run_cell("f('hi')")
417 ip.run_cell("f('hi')")
425
418
426
419
427 # ';' at the end of %time prevents instruction value to be printed.
420 # ';' at the end of %time prevents instruction value to be printed.
428 # This tests fix for #13837.
421 # This tests fix for #13837.
429 def test_time_no_output_with_semicolon():
422 def test_time_no_output_with_semicolon():
430 ip = get_ipython()
423 ip = get_ipython()
431
424
432 # Test %time cases
425 # Test %time cases
433 with tt.AssertPrints(" 123456"):
426 with tt.AssertPrints(" 123456"):
434 with tt.AssertPrints("Wall time: ", suppress=False):
427 with tt.AssertPrints("Wall time: ", suppress=False):
435 with tt.AssertPrints("CPU times: ", suppress=False):
428 with tt.AssertPrints("CPU times: ", suppress=False):
436 ip.run_cell("%time 123000+456")
429 ip.run_cell("%time 123000+456")
437
430
438 with tt.AssertNotPrints(" 123456"):
431 with tt.AssertNotPrints(" 123456"):
439 with tt.AssertPrints("Wall time: ", suppress=False):
432 with tt.AssertPrints("Wall time: ", suppress=False):
440 with tt.AssertPrints("CPU times: ", suppress=False):
433 with tt.AssertPrints("CPU times: ", suppress=False):
441 ip.run_cell("%time 123000+456;")
434 ip.run_cell("%time 123000+456;")
442
435
443 with tt.AssertPrints(" 123456"):
436 with tt.AssertPrints(" 123456"):
444 with tt.AssertPrints("Wall time: ", suppress=False):
437 with tt.AssertPrints("Wall time: ", suppress=False):
445 with tt.AssertPrints("CPU times: ", suppress=False):
438 with tt.AssertPrints("CPU times: ", suppress=False):
446 ip.run_cell("%time 123000+456 # Comment")
439 ip.run_cell("%time 123000+456 # Comment")
447
440
448 with tt.AssertNotPrints(" 123456"):
441 with tt.AssertNotPrints(" 123456"):
449 with tt.AssertPrints("Wall time: ", suppress=False):
442 with tt.AssertPrints("Wall time: ", suppress=False):
450 with tt.AssertPrints("CPU times: ", suppress=False):
443 with tt.AssertPrints("CPU times: ", suppress=False):
451 ip.run_cell("%time 123000+456; # Comment")
444 ip.run_cell("%time 123000+456; # Comment")
452
445
453 with tt.AssertPrints(" 123456"):
446 with tt.AssertPrints(" 123456"):
454 with tt.AssertPrints("Wall time: ", suppress=False):
447 with tt.AssertPrints("Wall time: ", suppress=False):
455 with tt.AssertPrints("CPU times: ", suppress=False):
448 with tt.AssertPrints("CPU times: ", suppress=False):
456 ip.run_cell("%time 123000+456 # ;Comment")
449 ip.run_cell("%time 123000+456 # ;Comment")
457
450
458 # Test %%time cases
451 # Test %%time cases
459 with tt.AssertPrints("123456"):
452 with tt.AssertPrints("123456"):
460 with tt.AssertPrints("Wall time: ", suppress=False):
453 with tt.AssertPrints("Wall time: ", suppress=False):
461 with tt.AssertPrints("CPU times: ", suppress=False):
454 with tt.AssertPrints("CPU times: ", suppress=False):
462 ip.run_cell("%%time\n123000+456\n\n\n")
455 ip.run_cell("%%time\n123000+456\n\n\n")
463
456
464 with tt.AssertNotPrints("123456"):
457 with tt.AssertNotPrints("123456"):
465 with tt.AssertPrints("Wall time: ", suppress=False):
458 with tt.AssertPrints("Wall time: ", suppress=False):
466 with tt.AssertPrints("CPU times: ", suppress=False):
459 with tt.AssertPrints("CPU times: ", suppress=False):
467 ip.run_cell("%%time\n123000+456;\n\n\n")
460 ip.run_cell("%%time\n123000+456;\n\n\n")
468
461
469 with tt.AssertPrints("123456"):
462 with tt.AssertPrints("123456"):
470 with tt.AssertPrints("Wall time: ", suppress=False):
463 with tt.AssertPrints("Wall time: ", suppress=False):
471 with tt.AssertPrints("CPU times: ", suppress=False):
464 with tt.AssertPrints("CPU times: ", suppress=False):
472 ip.run_cell("%%time\n123000+456 # Comment\n\n\n")
465 ip.run_cell("%%time\n123000+456 # Comment\n\n\n")
473
466
474 with tt.AssertNotPrints("123456"):
467 with tt.AssertNotPrints("123456"):
475 with tt.AssertPrints("Wall time: ", suppress=False):
468 with tt.AssertPrints("Wall time: ", suppress=False):
476 with tt.AssertPrints("CPU times: ", suppress=False):
469 with tt.AssertPrints("CPU times: ", suppress=False):
477 ip.run_cell("%%time\n123000+456; # Comment\n\n\n")
470 ip.run_cell("%%time\n123000+456; # Comment\n\n\n")
478
471
479 with tt.AssertPrints("123456"):
472 with tt.AssertPrints("123456"):
480 with tt.AssertPrints("Wall time: ", suppress=False):
473 with tt.AssertPrints("Wall time: ", suppress=False):
481 with tt.AssertPrints("CPU times: ", suppress=False):
474 with tt.AssertPrints("CPU times: ", suppress=False):
482 ip.run_cell("%%time\n123000+456 # ;Comment\n\n\n")
475 ip.run_cell("%%time\n123000+456 # ;Comment\n\n\n")
483
476
484
477
485 def test_time_last_not_expression():
478 def test_time_last_not_expression():
486 _ip.run_cell("%%time\n" "var_1 = 1\n" "var_2 = 2\n")
479 ip.run_cell("%%time\n"
487 assert _ip.user_ns["var_1"] == 1
480 "var_1 = 1\n"
488 del _ip.user_ns["var_1"]
481 "var_2 = 2\n")
489 assert _ip.user_ns["var_2"] == 2
482 assert ip.user_ns['var_1'] == 1
490 del _ip.user_ns["var_2"]
483 del ip.user_ns['var_1']
484 assert ip.user_ns['var_2'] == 2
485 del ip.user_ns['var_2']
491
486
492
487
493 @dec.skip_win32
488 @dec.skip_win32
494 def test_time2():
489 def test_time2():
495 ip = get_ipython()
490 ip = get_ipython()
496
491
497 with tt.AssertPrints("CPU times: user "):
492 with tt.AssertPrints("CPU times: user "):
498 ip.run_cell("%time None")
493 ip.run_cell("%time None")
499
494
500
501 def test_time3():
495 def test_time3():
502 """Erroneous magic function calls, issue gh-3334"""
496 """Erroneous magic function calls, issue gh-3334"""
503 ip = get_ipython()
497 ip = get_ipython()
504 ip.user_ns.pop("run", None)
498 ip.user_ns.pop('run', None)
505
506 with tt.AssertNotPrints("not found", channel="stderr"):
507 ip.run_cell("%%time\n" "run = 0\n" "run += 1")
508
499
500 with tt.AssertNotPrints("not found", channel='stderr'):
501 ip.run_cell("%%time\n"
502 "run = 0\n"
503 "run += 1")
509
504
510 def test_multiline_time():
505 def test_multiline_time():
511 """Make sure last statement from time return a value."""
506 """Make sure last statement from time return a value."""
512 ip = get_ipython()
507 ip = get_ipython()
513 ip.user_ns.pop("run", None)
508 ip.user_ns.pop('run', None)
514
509
515 ip.run_cell(
510 ip.run_cell(
516 dedent(
511 dedent(
517 """\
512 """\
518 %%time
513 %%time
519 a = "ho"
514 a = "ho"
520 b = "hey"
515 b = "hey"
521 a+b
516 a+b
522 """
517 """
523 )
518 )
524 )
519 )
525 assert ip.user_ns_hidden["_"] == "hohey"
520 assert ip.user_ns_hidden["_"] == "hohey"
526
521
527
522
528 def test_time_local_ns():
523 def test_time_local_ns():
529 """
524 """
530 Test that local_ns is actually global_ns when running a cell magic
525 Test that local_ns is actually global_ns when running a cell magic
531 """
526 """
532 ip = get_ipython()
527 ip = get_ipython()
533 ip.run_cell("%%time\n" "myvar = 1")
528 ip.run_cell("%%time\n" "myvar = 1")
534 assert ip.user_ns["myvar"] == 1
529 assert ip.user_ns["myvar"] == 1
535 del ip.user_ns["myvar"]
530 del ip.user_ns["myvar"]
536
531
537
532
538 def test_time_microseconds_display():
533 def test_time_microseconds_display():
539 """Ensure ASCII is used when necessary"""
534 """Ensure ASCII is used when necessary"""
540 with mock.patch("sys.stdout", io.TextIOWrapper(StringIO(), encoding="utf-8")):
535 with mock.patch("sys.stdout", io.TextIOWrapper(StringIO(), encoding="utf-8")):
541 assert execution._format_time(0.000001) == "1 \u03bcs"
536 assert execution._format_time(0.000001) == "1 \u03bcs"
542 with mock.patch("sys.stdout", io.TextIOWrapper(StringIO(), encoding="ascii")):
537 with mock.patch("sys.stdout", io.TextIOWrapper(StringIO(), encoding="ascii")):
543 assert execution._format_time(0.000001) == "1 us"
538 assert execution._format_time(0.000001) == "1 us"
544
539
545
540
546 # Test %%capture magic. Added to test issue #13926
541 # Test %%capture magic. Added to test issue #13926
547 def test_capture():
542 def test_capture():
548 ip = get_ipython()
543 ip = get_ipython()
549
544
550 # Test %%capture nominal case
545 # Test %%capture nominal case
551 ip.run_cell("%%capture abc\n1+2")
546 ip.run_cell("%%capture abc\n1+2")
552 with tt.AssertPrints("True", suppress=False):
547 with tt.AssertPrints("True", suppress=False):
553 ip.run_cell("'abc' in locals()")
548 ip.run_cell("'abc' in locals()")
554 with tt.AssertPrints("True", suppress=False):
549 with tt.AssertPrints("True", suppress=False):
555 ip.run_cell("'outputs' in dir(abc)")
550 ip.run_cell("'outputs' in dir(abc)")
556 with tt.AssertPrints("3", suppress=False):
551 with tt.AssertPrints("3", suppress=False):
557 ip.run_cell("abc.outputs[0]")
552 ip.run_cell("abc.outputs[0]")
558
553
559 # Test %%capture with ';' at end of expression
554 # Test %%capture with ';' at end of expression
560 ip.run_cell("%%capture abc\n7+8;")
555 ip.run_cell("%%capture abc\n7+8;")
561 with tt.AssertPrints("False", suppress=False):
556 with tt.AssertPrints("False", suppress=False):
562 ip.run_cell("'abc' in locals()")
557 ip.run_cell("'abc' in locals()")
563
558
564
559
565 def test_doctest_mode():
560 def test_doctest_mode():
566 "Toggle doctest_mode twice, it should be a no-op and run without error"
561 "Toggle doctest_mode twice, it should be a no-op and run without error"
567 _ip.run_line_magic("doctest_mode", "")
562 _ip.run_line_magic("doctest_mode", "")
568 _ip.run_line_magic("doctest_mode", "")
563 _ip.run_line_magic("doctest_mode", "")
569
564
570
565
571 def test_parse_options():
566 def test_parse_options():
572 """Tests for basic options parsing in magics."""
567 """Tests for basic options parsing in magics."""
573 # These are only the most minimal of tests, more should be added later. At
568 # These are only the most minimal of tests, more should be added later. At
574 # the very least we check that basic text/unicode calls work OK.
569 # the very least we check that basic text/unicode calls work OK.
575 m = DummyMagics(_ip)
570 m = DummyMagics(_ip)
576 assert m.parse_options("foo", "")[1] == "foo"
571 assert m.parse_options("foo", "")[1] == "foo"
577 assert m.parse_options("foo", "")[1] == "foo"
572 assert m.parse_options("foo", "")[1] == "foo"
578
573
579
574
580 def test_parse_options_preserve_non_option_string():
575 def test_parse_options_preserve_non_option_string():
581 """Test to assert preservation of non-option part of magic-block, while parsing magic options."""
576 """Test to assert preservation of non-option part of magic-block, while parsing magic options."""
582 m = DummyMagics(_ip)
577 m = DummyMagics(_ip)
583 opts, stmt = m.parse_options(
578 opts, stmt = m.parse_options(
584 " -n1 -r 13 _ = 314 + foo", "n:r:", preserve_non_opts=True
579 " -n1 -r 13 _ = 314 + foo", "n:r:", preserve_non_opts=True
585 )
580 )
586 assert opts == {"n": "1", "r": "13"}
581 assert opts == {"n": "1", "r": "13"}
587 assert stmt == "_ = 314 + foo"
582 assert stmt == "_ = 314 + foo"
588
583
589
584
590 def test_run_magic_preserve_code_block():
585 def test_run_magic_preserve_code_block():
591 """Test to assert preservation of non-option part of magic-block, while running magic."""
586 """Test to assert preservation of non-option part of magic-block, while running magic."""
592 _ip.user_ns["spaces"] = []
587 _ip.user_ns["spaces"] = []
593 _ip.run_line_magic(
588 _ip.run_line_magic(
594 "timeit", "-n1 -r1 spaces.append([s.count(' ') for s in ['document']])"
589 "timeit", "-n1 -r1 spaces.append([s.count(' ') for s in ['document']])"
595 )
590 )
596 assert _ip.user_ns["spaces"] == [[0]]
591 assert _ip.user_ns["spaces"] == [[0]]
597
592
598
593
599 def test_dirops():
594 def test_dirops():
600 """Test various directory handling operations."""
595 """Test various directory handling operations."""
601 # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/')
596 # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/')
602 curpath = os.getcwd
597 curpath = os.getcwd
603 startdir = os.getcwd()
598 startdir = os.getcwd()
604 ipdir = os.path.realpath(_ip.ipython_dir)
599 ipdir = os.path.realpath(_ip.ipython_dir)
605 try:
600 try:
606 _ip.run_line_magic("cd", '"%s"' % ipdir)
601 _ip.run_line_magic("cd", '"%s"' % ipdir)
607 assert curpath() == ipdir
602 assert curpath() == ipdir
608 _ip.run_line_magic("cd", "-")
603 _ip.run_line_magic("cd", "-")
609 assert curpath() == startdir
604 assert curpath() == startdir
610 _ip.run_line_magic("pushd", '"%s"' % ipdir)
605 _ip.run_line_magic("pushd", '"%s"' % ipdir)
611 assert curpath() == ipdir
606 assert curpath() == ipdir
612 _ip.run_line_magic("popd", "")
607 _ip.run_line_magic("popd", "")
613 assert curpath() == startdir
608 assert curpath() == startdir
614 finally:
609 finally:
615 os.chdir(startdir)
610 os.chdir(startdir)
616
611
617
612
618 def test_cd_force_quiet():
613 def test_cd_force_quiet():
619 """Test OSMagics.cd_force_quiet option"""
614 """Test OSMagics.cd_force_quiet option"""
620 _ip.config.OSMagics.cd_force_quiet = True
615 _ip.config.OSMagics.cd_force_quiet = True
621 osmagics = osm.OSMagics(shell=_ip)
616 osmagics = osm.OSMagics(shell=_ip)
622
617
623 startdir = os.getcwd()
618 startdir = os.getcwd()
624 ipdir = os.path.realpath(_ip.ipython_dir)
619 ipdir = os.path.realpath(_ip.ipython_dir)
625
620
626 try:
621 try:
627 with tt.AssertNotPrints(ipdir):
622 with tt.AssertNotPrints(ipdir):
628 osmagics.cd('"%s"' % ipdir)
623 osmagics.cd('"%s"' % ipdir)
629 with tt.AssertNotPrints(startdir):
624 with tt.AssertNotPrints(startdir):
630 osmagics.cd("-")
625 osmagics.cd('-')
631 finally:
626 finally:
632 os.chdir(startdir)
627 os.chdir(startdir)
633
628
634
629
635 def test_xmode():
630 def test_xmode():
636 # Calling xmode three times should be a no-op
631 # Calling xmode three times should be a no-op
637 xmode = _ip.InteractiveTB.mode
632 xmode = _ip.InteractiveTB.mode
638 for i in range(4):
633 for i in range(4):
639 _ip.run_line_magic("xmode", "")
634 _ip.run_line_magic("xmode", "")
640 assert _ip.InteractiveTB.mode == xmode
635 assert _ip.InteractiveTB.mode == xmode
641
636
642
643 def test_reset_hard():
637 def test_reset_hard():
644 monitor = []
638 monitor = []
645
646 class A(object):
639 class A(object):
647 def __del__(self):
640 def __del__(self):
648 monitor.append(1)
641 monitor.append(1)
649
650 def __repr__(self):
642 def __repr__(self):
651 return "<A instance>"
643 return "<A instance>"
652
644
653 _ip.user_ns["a"] = A()
645 _ip.user_ns["a"] = A()
654 _ip.run_cell("a")
646 _ip.run_cell("a")
655
647
656 assert monitor == []
648 assert monitor == []
657 _ip.run_line_magic("reset", "-f")
649 _ip.run_line_magic("reset", "-f")
658 assert monitor == [1]
650 assert monitor == [1]
659
651
660
661 class TestXdel(tt.TempFileMixin):
652 class TestXdel(tt.TempFileMixin):
662 def test_xdel(self):
653 def test_xdel(self):
663 """Test that references from %run are cleared by xdel."""
654 """Test that references from %run are cleared by xdel."""
664 src = (
655 src = ("class A(object):\n"
665 "class A(object):\n"
656 " monitor = []\n"
666 " monitor = []\n"
657 " def __del__(self):\n"
667 " def __del__(self):\n"
658 " self.monitor.append(1)\n"
668 " self.monitor.append(1)\n"
659 "a = A()\n")
669 "a = A()\n"
670 )
671 self.mktmp(src)
660 self.mktmp(src)
672 # %run creates some hidden references...
661 # %run creates some hidden references...
673 _ip.run_line_magic("run", "%s" % self.fname)
662 _ip.run_line_magic("run", "%s" % self.fname)
674 # ... as does the displayhook.
663 # ... as does the displayhook.
675 _ip.run_cell("a")
664 _ip.run_cell("a")
676
665
677 monitor = _ip.user_ns["A"].monitor
666 monitor = _ip.user_ns["A"].monitor
678 assert monitor == []
667 assert monitor == []
679
668
680 _ip.run_line_magic("xdel", "a")
669 _ip.run_line_magic("xdel", "a")
681
670
682 # Check that a's __del__ method has been called.
671 # Check that a's __del__ method has been called.
683 gc.collect(0)
672 gc.collect(0)
684 assert monitor == [1]
673 assert monitor == [1]
685
674
686
687 def doctest_who():
675 def doctest_who():
688 """doctest for %who
676 """doctest for %who
689
677
690 In [1]: %reset -sf
678 In [1]: %reset -sf
691
679
692 In [2]: alpha = 123
680 In [2]: alpha = 123
693
681
694 In [3]: beta = 'beta'
682 In [3]: beta = 'beta'
695
683
696 In [4]: %who int
684 In [4]: %who int
697 alpha
685 alpha
698
686
699 In [5]: %who str
687 In [5]: %who str
700 beta
688 beta
701
689
702 In [6]: %whos
690 In [6]: %whos
703 Variable Type Data/Info
691 Variable Type Data/Info
704 ----------------------------
692 ----------------------------
705 alpha int 123
693 alpha int 123
706 beta str beta
694 beta str beta
707
695
708 In [7]: %who_ls
696 In [7]: %who_ls
709 Out[7]: ['alpha', 'beta']
697 Out[7]: ['alpha', 'beta']
710 """
698 """
711
699
712
713 def test_whos():
700 def test_whos():
714 """Check that whos is protected against objects where repr() fails."""
701 """Check that whos is protected against objects where repr() fails."""
715
716 class A(object):
702 class A(object):
717 def __repr__(self):
703 def __repr__(self):
718 raise Exception()
704 raise Exception()
719
705 _ip.user_ns['a'] = A()
720 _ip.user_ns["a"] = A()
721 _ip.run_line_magic("whos", "")
706 _ip.run_line_magic("whos", "")
722
707
723
724 def doctest_precision():
708 def doctest_precision():
725 """doctest for %precision
709 """doctest for %precision
726
710
727 In [1]: f = get_ipython().display_formatter.formatters['text/plain']
711 In [1]: f = get_ipython().display_formatter.formatters['text/plain']
728
712
729 In [2]: %precision 5
713 In [2]: %precision 5
730 Out[2]: '%.5f'
714 Out[2]: '%.5f'
731
715
732 In [3]: f.float_format
716 In [3]: f.float_format
733 Out[3]: '%.5f'
717 Out[3]: '%.5f'
734
718
735 In [4]: %precision %e
719 In [4]: %precision %e
736 Out[4]: '%e'
720 Out[4]: '%e'
737
721
738 In [5]: f(3.1415927)
722 In [5]: f(3.1415927)
739 Out[5]: '3.141593e+00'
723 Out[5]: '3.141593e+00'
740 """
724 """
741
725
742
726
743 def test_debug_magic():
727 def test_debug_magic():
744 """Test debugging a small code with %debug
728 """Test debugging a small code with %debug
745
729
746 In [1]: with PdbTestInput(['c']):
730 In [1]: with PdbTestInput(['c']):
747 ...: %debug print("a b") #doctest: +ELLIPSIS
731 ...: %debug print("a b") #doctest: +ELLIPSIS
748 ...:
732 ...:
749 ...
733 ...
750 ipdb> c
734 ipdb> c
751 a b
735 a b
752 In [2]:
736 In [2]:
753 """
737 """
754
738
755
739
756 def test_debug_magic_locals():
740 def test_debug_magic_locals():
757 """Test debugging a small code with %debug with locals
741 """Test debugging a small code with %debug with locals
758
742
759 In [1]: with PdbTestInput(['c']):
743 In [1]: with PdbTestInput(['c']):
760 ...: def fun():
744 ...: def fun():
761 ...: res = 1
745 ...: res = 1
762 ...: %debug print(res)
746 ...: %debug print(res)
763 ...: fun()
747 ...: fun()
764 ...:
748 ...:
765 ...
749 ...
766 ipdb> c
750 ipdb> c
767 1
751 1
768 In [2]:
752 In [2]:
769 """
753 """
770
754
771
772 def test_psearch():
755 def test_psearch():
773 with tt.AssertPrints("dict.fromkeys"):
756 with tt.AssertPrints("dict.fromkeys"):
774 _ip.run_cell("dict.fr*?")
757 _ip.run_cell("dict.fr*?")
775 with tt.AssertPrints("π.is_integer"):
758 with tt.AssertPrints("π.is_integer"):
776 _ip.run_cell("π = 3.14;\nπ.is_integ*?")
759 _ip.run_cell("π = 3.14;\nπ.is_integ*?")
777
760
778
779 def test_timeit_shlex():
761 def test_timeit_shlex():
780 """test shlex issues with timeit (#1109)"""
762 """test shlex issues with timeit (#1109)"""
781 _ip.ex("def f(*a,**kw): pass")
763 _ip.ex("def f(*a,**kw): pass")
782 _ip.run_line_magic("timeit", '-n1 "this is a bug".count(" ")')
764 _ip.run_line_magic("timeit", '-n1 "this is a bug".count(" ")')
783 _ip.run_line_magic("timeit", '-r1 -n1 f(" ", 1)')
765 _ip.run_line_magic("timeit", '-r1 -n1 f(" ", 1)')
784 _ip.run_line_magic("timeit", '-r1 -n1 f(" ", 1, " ", 2, " ")')
766 _ip.run_line_magic("timeit", '-r1 -n1 f(" ", 1, " ", 2, " ")')
785 _ip.run_line_magic("timeit", '-r1 -n1 ("a " + "b")')
767 _ip.run_line_magic("timeit", '-r1 -n1 ("a " + "b")')
786 _ip.run_line_magic("timeit", '-r1 -n1 f("a " + "b")')
768 _ip.run_line_magic("timeit", '-r1 -n1 f("a " + "b")')
787 _ip.run_line_magic("timeit", '-r1 -n1 f("a " + "b ")')
769 _ip.run_line_magic("timeit", '-r1 -n1 f("a " + "b ")')
788
770
789
771
790 def test_timeit_special_syntax():
772 def test_timeit_special_syntax():
791 "Test %%timeit with IPython special syntax"
773 "Test %%timeit with IPython special syntax"
792
793 @register_line_magic
774 @register_line_magic
794 def lmagic(line):
775 def lmagic(line):
795 ip = get_ipython()
776 ip = get_ipython()
796 ip.user_ns["lmagic_out"] = line
777 ip.user_ns['lmagic_out'] = line
797
778
798 # line mode test
779 # line mode test
799 _ip.run_line_magic("timeit", "-n1 -r1 %lmagic my line")
780 _ip.run_line_magic("timeit", "-n1 -r1 %lmagic my line")
800 assert _ip.user_ns["lmagic_out"] == "my line"
781 assert _ip.user_ns["lmagic_out"] == "my line"
801 # cell mode test
782 # cell mode test
802 _ip.run_cell_magic("timeit", "-n1 -r1", "%lmagic my line2")
783 _ip.run_cell_magic("timeit", "-n1 -r1", "%lmagic my line2")
803 assert _ip.user_ns["lmagic_out"] == "my line2"
784 assert _ip.user_ns["lmagic_out"] == "my line2"
804
785
805
786
806 def test_timeit_return():
787 def test_timeit_return():
807 """
788 """
808 test whether timeit -o return object
789 test whether timeit -o return object
809 """
790 """
810
791
811 res = _ip.run_line_magic("timeit", "-n10 -r10 -o 1")
792 res = _ip.run_line_magic('timeit','-n10 -r10 -o 1')
812 assert res is not None
793 assert(res is not None)
813
814
794
815 def test_timeit_quiet():
795 def test_timeit_quiet():
816 """
796 """
817 test quiet option of timeit magic
797 test quiet option of timeit magic
818 """
798 """
819 with tt.AssertNotPrints("loops"):
799 with tt.AssertNotPrints("loops"):
820 _ip.run_cell("%timeit -n1 -r1 -q 1")
800 _ip.run_cell("%timeit -n1 -r1 -q 1")
821
801
822
823 def test_timeit_return_quiet():
802 def test_timeit_return_quiet():
824 with tt.AssertNotPrints("loops"):
803 with tt.AssertNotPrints("loops"):
825 res = _ip.run_line_magic("timeit", "-n1 -r1 -q -o 1")
804 res = _ip.run_line_magic('timeit', '-n1 -r1 -q -o 1')
826 assert res is not None
805 assert (res is not None)
827
828
806
829 def test_timeit_invalid_return():
807 def test_timeit_invalid_return():
830 with pytest.raises(SyntaxError):
808 with pytest.raises(SyntaxError):
831 _ip.run_line_magic("timeit", "return")
809 _ip.run_line_magic('timeit', 'return')
832
833
810
834 @dec.skipif(execution.profile is None)
811 @dec.skipif(execution.profile is None)
835 def test_prun_special_syntax():
812 def test_prun_special_syntax():
836 "Test %%prun with IPython special syntax"
813 "Test %%prun with IPython special syntax"
837
838 @register_line_magic
814 @register_line_magic
839 def lmagic(line):
815 def lmagic(line):
840 ip = get_ipython()
816 ip = get_ipython()
841 ip.user_ns["lmagic_out"] = line
817 ip.user_ns['lmagic_out'] = line
842
818
843 # line mode test
819 # line mode test
844 _ip.run_line_magic("prun", "-q %lmagic my line")
820 _ip.run_line_magic("prun", "-q %lmagic my line")
845 assert _ip.user_ns["lmagic_out"] == "my line"
821 assert _ip.user_ns["lmagic_out"] == "my line"
846 # cell mode test
822 # cell mode test
847 _ip.run_cell_magic("prun", "-q", "%lmagic my line2")
823 _ip.run_cell_magic("prun", "-q", "%lmagic my line2")
848 assert _ip.user_ns["lmagic_out"] == "my line2"
824 assert _ip.user_ns["lmagic_out"] == "my line2"
849
825
850
826
851 @dec.skipif(execution.profile is None)
827 @dec.skipif(execution.profile is None)
852 def test_prun_quotes():
828 def test_prun_quotes():
853 "Test that prun does not clobber string escapes (GH #1302)"
829 "Test that prun does not clobber string escapes (GH #1302)"
854 _ip.run_line_magic("prun", r"-q x = '\t'")
830 _ip.run_line_magic("prun", r"-q x = '\t'")
855 assert _ip.user_ns["x"] == "\t"
831 assert _ip.user_ns["x"] == "\t"
856
832
857
833
858 def test_extension():
834 def test_extension():
859 # Debugging information for failures of this test
835 # Debugging information for failures of this test
860 print("sys.path:")
836 print('sys.path:')
861 for p in sys.path:
837 for p in sys.path:
862 print(" ", p)
838 print(' ', p)
863 print("CWD", os.getcwd())
839 print('CWD', os.getcwd())
864
840
865 pytest.raises(ImportError, _ip.run_line_magic, "load_ext", "daft_extension")
841 pytest.raises(ImportError, _ip.run_line_magic, "load_ext", "daft_extension")
866 daft_path = os.path.join(os.path.dirname(__file__), "daft_extension")
842 daft_path = os.path.join(os.path.dirname(__file__), "daft_extension")
867 sys.path.insert(0, daft_path)
843 sys.path.insert(0, daft_path)
868 try:
844 try:
869 _ip.user_ns.pop("arq", None)
845 _ip.user_ns.pop('arq', None)
870 invalidate_caches() # Clear import caches
846 invalidate_caches() # Clear import caches
871 _ip.run_line_magic("load_ext", "daft_extension")
847 _ip.run_line_magic("load_ext", "daft_extension")
872 assert _ip.user_ns["arq"] == 185
848 assert _ip.user_ns["arq"] == 185
873 _ip.run_line_magic("unload_ext", "daft_extension")
849 _ip.run_line_magic("unload_ext", "daft_extension")
874 assert "arq" not in _ip.user_ns
850 assert 'arq' not in _ip.user_ns
875 finally:
851 finally:
876 sys.path.remove(daft_path)
852 sys.path.remove(daft_path)
877
853
878
854
879 def test_notebook_export_json():
855 def test_notebook_export_json():
880 pytest.importorskip("nbformat")
856 pytest.importorskip("nbformat")
881 _ip = get_ipython()
857 _ip = get_ipython()
882 _ip.history_manager.reset() # Clear any existing history.
858 _ip.history_manager.reset() # Clear any existing history.
883 cmds = ["a=1", "def b():\n return a**2", "print('noël, été', b())"]
859 cmds = ["a=1", "def b():\n return a**2", "print('noël, été', b())"]
884 for i, cmd in enumerate(cmds, start=1):
860 for i, cmd in enumerate(cmds, start=1):
885 _ip.history_manager.store_inputs(i, cmd)
861 _ip.history_manager.store_inputs(i, cmd)
886 with TemporaryDirectory() as td:
862 with TemporaryDirectory() as td:
887 outfile = os.path.join(td, "nb.ipynb")
863 outfile = os.path.join(td, "nb.ipynb")
888 _ip.run_line_magic("notebook", "%s" % outfile)
864 _ip.run_line_magic("notebook", "%s" % outfile)
889
865
890
866
891 class TestEnv(TestCase):
867 class TestEnv(TestCase):
868
892 def test_env(self):
869 def test_env(self):
893 env = _ip.run_line_magic("env", "")
870 env = _ip.run_line_magic("env", "")
894 self.assertTrue(isinstance(env, dict))
871 self.assertTrue(isinstance(env, dict))
895
872
896 def test_env_secret(self):
873 def test_env_secret(self):
897 env = _ip.run_line_magic("env", "")
874 env = _ip.run_line_magic("env", "")
898 hidden = "<hidden>"
875 hidden = "<hidden>"
899 with mock.patch.dict(
876 with mock.patch.dict(
900 os.environ,
877 os.environ,
901 {
878 {
902 "API_KEY": "abc123",
879 "API_KEY": "abc123",
903 "SECRET_THING": "ssshhh",
880 "SECRET_THING": "ssshhh",
904 "JUPYTER_TOKEN": "",
881 "JUPYTER_TOKEN": "",
905 "VAR": "abc",
882 "VAR": "abc"
906 },
883 }
907 ):
884 ):
908 env = _ip.run_line_magic("env", "")
885 env = _ip.run_line_magic("env", "")
909 assert env["API_KEY"] == hidden
886 assert env["API_KEY"] == hidden
910 assert env["SECRET_THING"] == hidden
887 assert env["SECRET_THING"] == hidden
911 assert env["JUPYTER_TOKEN"] == hidden
888 assert env["JUPYTER_TOKEN"] == hidden
912 assert env["VAR"] == "abc"
889 assert env["VAR"] == "abc"
913
890
914 def test_env_get_set_simple(self):
891 def test_env_get_set_simple(self):
915 env = _ip.run_line_magic("env", "var val1")
892 env = _ip.run_line_magic("env", "var val1")
916 self.assertEqual(env, None)
893 self.assertEqual(env, None)
917 self.assertEqual(os.environ["var"], "val1")
894 self.assertEqual(os.environ["var"], "val1")
918 self.assertEqual(_ip.run_line_magic("env", "var"), "val1")
895 self.assertEqual(_ip.run_line_magic("env", "var"), "val1")
919 env = _ip.run_line_magic("env", "var=val2")
896 env = _ip.run_line_magic("env", "var=val2")
920 self.assertEqual(env, None)
897 self.assertEqual(env, None)
921 self.assertEqual(os.environ["var"], "val2")
898 self.assertEqual(os.environ['var'], 'val2')
922
899
923 def test_env_get_set_complex(self):
900 def test_env_get_set_complex(self):
924 env = _ip.run_line_magic("env", "var 'val1 '' 'val2")
901 env = _ip.run_line_magic("env", "var 'val1 '' 'val2")
925 self.assertEqual(env, None)
902 self.assertEqual(env, None)
926 self.assertEqual(os.environ["var"], "'val1 '' 'val2")
903 self.assertEqual(os.environ['var'], "'val1 '' 'val2")
927 self.assertEqual(_ip.run_line_magic("env", "var"), "'val1 '' 'val2")
904 self.assertEqual(_ip.run_line_magic("env", "var"), "'val1 '' 'val2")
928 env = _ip.run_line_magic("env", 'var=val2 val3="val4')
905 env = _ip.run_line_magic("env", 'var=val2 val3="val4')
929 self.assertEqual(env, None)
906 self.assertEqual(env, None)
930 self.assertEqual(os.environ["var"], 'val2 val3="val4')
907 self.assertEqual(os.environ['var'], 'val2 val3="val4')
931
908
932 def test_env_set_bad_input(self):
909 def test_env_set_bad_input(self):
933 self.assertRaises(UsageError, lambda: _ip.run_line_magic("set_env", "var"))
910 self.assertRaises(UsageError, lambda: _ip.run_line_magic("set_env", "var"))
934
911
935 def test_env_set_whitespace(self):
912 def test_env_set_whitespace(self):
936 self.assertRaises(UsageError, lambda: _ip.run_line_magic("env", "var A=B"))
913 self.assertRaises(UsageError, lambda: _ip.run_line_magic("env", "var A=B"))
937
914
938
915
939 class CellMagicTestCase(TestCase):
916 class CellMagicTestCase(TestCase):
917
940 def check_ident(self, magic):
918 def check_ident(self, magic):
941 # Manually called, we get the result
919 # Manually called, we get the result
942 out = _ip.run_cell_magic(magic, "a", "b")
920 out = _ip.run_cell_magic(magic, "a", "b")
943 assert out == ("a", "b")
921 assert out == ("a", "b")
944 # Via run_cell, it goes into the user's namespace via displayhook
922 # Via run_cell, it goes into the user's namespace via displayhook
945 _ip.run_cell("%%" + magic + " c\nd\n")
923 _ip.run_cell("%%" + magic + " c\nd\n")
946 assert _ip.user_ns["_"] == ("c", "d\n")
924 assert _ip.user_ns["_"] == ("c", "d\n")
947
925
948 def test_cell_magic_func_deco(self):
926 def test_cell_magic_func_deco(self):
949 "Cell magic using simple decorator"
927 "Cell magic using simple decorator"
950
951 @register_cell_magic
928 @register_cell_magic
952 def cellm(line, cell):
929 def cellm(line, cell):
953 return line, cell
930 return line, cell
954
931
955 self.check_ident("cellm")
932 self.check_ident('cellm')
956
933
957 def test_cell_magic_reg(self):
934 def test_cell_magic_reg(self):
958 "Cell magic manually registered"
935 "Cell magic manually registered"
959
960 def cellm(line, cell):
936 def cellm(line, cell):
961 return line, cell
937 return line, cell
962
938
963 _ip.register_magic_function(cellm, "cell", "cellm2")
939 _ip.register_magic_function(cellm, 'cell', 'cellm2')
964 self.check_ident("cellm2")
940 self.check_ident('cellm2')
965
941
966 def test_cell_magic_class(self):
942 def test_cell_magic_class(self):
967 "Cell magics declared via a class"
943 "Cell magics declared via a class"
968
969 @magics_class
944 @magics_class
970 class MyMagics(Magics):
945 class MyMagics(Magics):
946
971 @cell_magic
947 @cell_magic
972 def cellm3(self, line, cell):
948 def cellm3(self, line, cell):
973 return line, cell
949 return line, cell
974
950
975 _ip.register_magics(MyMagics)
951 _ip.register_magics(MyMagics)
976 self.check_ident("cellm3")
952 self.check_ident('cellm3')
977
953
978 def test_cell_magic_class2(self):
954 def test_cell_magic_class2(self):
979 "Cell magics declared via a class, #2"
955 "Cell magics declared via a class, #2"
980
981 @magics_class
956 @magics_class
982 class MyMagics2(Magics):
957 class MyMagics2(Magics):
983 @cell_magic("cellm4")
958
959 @cell_magic('cellm4')
984 def cellm33(self, line, cell):
960 def cellm33(self, line, cell):
985 return line, cell
961 return line, cell
986
962
987 _ip.register_magics(MyMagics2)
963 _ip.register_magics(MyMagics2)
988 self.check_ident("cellm4")
964 self.check_ident('cellm4')
989 # Check that nothing is registered as 'cellm33'
965 # Check that nothing is registered as 'cellm33'
990 c33 = _ip.find_cell_magic("cellm33")
966 c33 = _ip.find_cell_magic('cellm33')
991 assert c33 is None
967 assert c33 == None
992
993
968
994 def test_file():
969 def test_file():
995 """Basic %%writefile"""
970 """Basic %%writefile"""
996 ip = get_ipython()
971 ip = get_ipython()
997 with TemporaryDirectory() as td:
972 with TemporaryDirectory() as td:
998 fname = os.path.join(td, "file1")
973 fname = os.path.join(td, "file1")
999 ip.run_cell_magic(
974 ip.run_cell_magic(
1000 "writefile",
975 "writefile",
1001 fname,
976 fname,
1002 "\n".join(
977 "\n".join(
1003 [
978 [
1004 "line1",
979 "line1",
1005 "line2",
980 "line2",
1006 ]
981 ]
1007 ),
982 ),
1008 )
983 )
1009 s = Path(fname).read_text(encoding="utf-8")
984 s = Path(fname).read_text(encoding="utf-8")
1010 assert "line1\n" in s
985 assert "line1\n" in s
1011 assert "line2" in s
986 assert "line2" in s
1012
987
1013
988
1014 @dec.skip_win32
989 @dec.skip_win32
1015 def test_file_single_quote():
990 def test_file_single_quote():
1016 """Basic %%writefile with embedded single quotes"""
991 """Basic %%writefile with embedded single quotes"""
1017 ip = get_ipython()
992 ip = get_ipython()
1018 with TemporaryDirectory() as td:
993 with TemporaryDirectory() as td:
1019 fname = os.path.join(td, "'file1'")
994 fname = os.path.join(td, "'file1'")
1020 ip.run_cell_magic(
995 ip.run_cell_magic(
1021 "writefile",
996 "writefile",
1022 fname,
997 fname,
1023 "\n".join(
998 "\n".join(
1024 [
999 [
1025 "line1",
1000 "line1",
1026 "line2",
1001 "line2",
1027 ]
1002 ]
1028 ),
1003 ),
1029 )
1004 )
1030 s = Path(fname).read_text(encoding="utf-8")
1005 s = Path(fname).read_text(encoding="utf-8")
1031 assert "line1\n" in s
1006 assert "line1\n" in s
1032 assert "line2" in s
1007 assert "line2" in s
1033
1008
1034
1009
1035 @dec.skip_win32
1010 @dec.skip_win32
1036 def test_file_double_quote():
1011 def test_file_double_quote():
1037 """Basic %%writefile with embedded double quotes"""
1012 """Basic %%writefile with embedded double quotes"""
1038 ip = get_ipython()
1013 ip = get_ipython()
1039 with TemporaryDirectory() as td:
1014 with TemporaryDirectory() as td:
1040 fname = os.path.join(td, '"file1"')
1015 fname = os.path.join(td, '"file1"')
1041 ip.run_cell_magic(
1016 ip.run_cell_magic(
1042 "writefile",
1017 "writefile",
1043 fname,
1018 fname,
1044 "\n".join(
1019 "\n".join(
1045 [
1020 [
1046 "line1",
1021 "line1",
1047 "line2",
1022 "line2",
1048 ]
1023 ]
1049 ),
1024 ),
1050 )
1025 )
1051 s = Path(fname).read_text(encoding="utf-8")
1026 s = Path(fname).read_text(encoding="utf-8")
1052 assert "line1\n" in s
1027 assert "line1\n" in s
1053 assert "line2" in s
1028 assert "line2" in s
1054
1029
1055
1030
1056 def test_file_var_expand():
1031 def test_file_var_expand():
1057 """%%writefile $filename"""
1032 """%%writefile $filename"""
1058 ip = get_ipython()
1033 ip = get_ipython()
1059 with TemporaryDirectory() as td:
1034 with TemporaryDirectory() as td:
1060 fname = os.path.join(td, "file1")
1035 fname = os.path.join(td, "file1")
1061 ip.user_ns["filename"] = fname
1036 ip.user_ns["filename"] = fname
1062 ip.run_cell_magic(
1037 ip.run_cell_magic(
1063 "writefile",
1038 "writefile",
1064 "$filename",
1039 "$filename",
1065 "\n".join(
1040 "\n".join(
1066 [
1041 [
1067 "line1",
1042 "line1",
1068 "line2",
1043 "line2",
1069 ]
1044 ]
1070 ),
1045 ),
1071 )
1046 )
1072 s = Path(fname).read_text(encoding="utf-8")
1047 s = Path(fname).read_text(encoding="utf-8")
1073 assert "line1\n" in s
1048 assert "line1\n" in s
1074 assert "line2" in s
1049 assert "line2" in s
1075
1050
1076
1051
1077 def test_file_unicode():
1052 def test_file_unicode():
1078 """%%writefile with unicode cell"""
1053 """%%writefile with unicode cell"""
1079 ip = get_ipython()
1054 ip = get_ipython()
1080 with TemporaryDirectory() as td:
1055 with TemporaryDirectory() as td:
1081 fname = os.path.join(td, "file1")
1056 fname = os.path.join(td, 'file1')
1082 ip.run_cell_magic(
1057 ip.run_cell_magic("writefile", fname, u'\n'.join([
1083 "writefile",
1058 u'liné1',
1084 fname,
1059 u'liné2',
1085 "\n".join(
1060 ]))
1086 [
1061 with io.open(fname, encoding='utf-8') as f:
1087 "liné1",
1088 "liné2",
1089 ]
1090 ),
1091 )
1092 with io.open(fname, encoding="utf-8") as f:
1093 s = f.read()
1062 s = f.read()
1094 assert "liné1\n" in s
1063 assert "liné1\n" in s
1095 assert "liné2" in s
1064 assert "liné2" in s
1096
1065
1097
1066
1098 def test_file_amend():
1067 def test_file_amend():
1099 """%%writefile -a amends files"""
1068 """%%writefile -a amends files"""
1100 ip = get_ipython()
1069 ip = get_ipython()
1101 with TemporaryDirectory() as td:
1070 with TemporaryDirectory() as td:
1102 fname = os.path.join(td, "file2")
1071 fname = os.path.join(td, "file2")
1103 ip.run_cell_magic(
1072 ip.run_cell_magic(
1104 "writefile",
1073 "writefile",
1105 fname,
1074 fname,
1106 "\n".join(
1075 "\n".join(
1107 [
1076 [
1108 "line1",
1077 "line1",
1109 "line2",
1078 "line2",
1110 ]
1079 ]
1111 ),
1080 ),
1112 )
1081 )
1113 ip.run_cell_magic(
1082 ip.run_cell_magic(
1114 "writefile",
1083 "writefile",
1115 "-a %s" % fname,
1084 "-a %s" % fname,
1116 "\n".join(
1085 "\n".join(
1117 [
1086 [
1118 "line3",
1087 "line3",
1119 "line4",
1088 "line4",
1120 ]
1089 ]
1121 ),
1090 ),
1122 )
1091 )
1123 s = Path(fname).read_text(encoding="utf-8")
1092 s = Path(fname).read_text(encoding="utf-8")
1124 assert "line1\n" in s
1093 assert "line1\n" in s
1125 assert "line3\n" in s
1094 assert "line3\n" in s
1126
1095
1127
1096
1128 def test_file_spaces():
1097 def test_file_spaces():
1129 """%%file with spaces in filename"""
1098 """%%file with spaces in filename"""
1130 ip = get_ipython()
1099 ip = get_ipython()
1131 with TemporaryWorkingDirectory():
1100 with TemporaryWorkingDirectory() as td:
1132 fname = "file name"
1101 fname = "file name"
1133 ip.run_cell_magic(
1102 ip.run_cell_magic(
1134 "file",
1103 "file",
1135 '"%s"' % fname,
1104 '"%s"' % fname,
1136 "\n".join(
1105 "\n".join(
1137 [
1106 [
1138 "line1",
1107 "line1",
1139 "line2",
1108 "line2",
1140 ]
1109 ]
1141 ),
1110 ),
1142 )
1111 )
1143 s = Path(fname).read_text(encoding="utf-8")
1112 s = Path(fname).read_text(encoding="utf-8")
1144 assert "line1\n" in s
1113 assert "line1\n" in s
1145 assert "line2" in s
1114 assert "line2" in s
1146
1115
1147
1116
1148 def test_script_config():
1117 def test_script_config():
1149 ip = get_ipython()
1118 ip = get_ipython()
1150 ip.config.ScriptMagics.script_magics = ["whoda"]
1119 ip.config.ScriptMagics.script_magics = ['whoda']
1151 sm = script.ScriptMagics(shell=ip)
1120 sm = script.ScriptMagics(shell=ip)
1152 assert "whoda" in sm.magics["cell"]
1121 assert "whoda" in sm.magics["cell"]
1153
1122
1154
1123
1155 def test_script_out():
1124 def test_script_out():
1156 ip = get_ipython()
1125 ip = get_ipython()
1157 ip.run_cell_magic("script", f"--out output {sys.executable}", "print('hi')")
1126 ip.run_cell_magic("script", f"--out output {sys.executable}", "print('hi')")
1158 assert ip.user_ns["output"].strip() == "hi"
1127 assert ip.user_ns["output"].strip() == "hi"
1159
1128
1160
1129
1161 def test_script_err():
1130 def test_script_err():
1162 ip = get_ipython()
1131 ip = get_ipython()
1163 ip.run_cell_magic(
1132 ip.run_cell_magic(
1164 "script",
1133 "script",
1165 f"--err error {sys.executable}",
1134 f"--err error {sys.executable}",
1166 "import sys; print('hello', file=sys.stderr)",
1135 "import sys; print('hello', file=sys.stderr)",
1167 )
1136 )
1168 assert ip.user_ns["error"].strip() == "hello"
1137 assert ip.user_ns["error"].strip() == "hello"
1169
1138
1170
1139
1171 def test_script_out_err():
1140 def test_script_out_err():
1172 ip = get_ipython()
1141 ip = get_ipython()
1173 ip.run_cell_magic(
1142 ip.run_cell_magic(
1174 "script",
1143 "script",
1175 f"--out output --err error {sys.executable}",
1144 f"--out output --err error {sys.executable}",
1176 "\n".join(
1145 "\n".join(
1177 [
1146 [
1178 "import sys",
1147 "import sys",
1179 "print('hi')",
1148 "print('hi')",
1180 "print('hello', file=sys.stderr)",
1149 "print('hello', file=sys.stderr)",
1181 ]
1150 ]
1182 ),
1151 ),
1183 )
1152 )
1184 assert ip.user_ns["output"].strip() == "hi"
1153 assert ip.user_ns["output"].strip() == "hi"
1185 assert ip.user_ns["error"].strip() == "hello"
1154 assert ip.user_ns["error"].strip() == "hello"
1186
1155
1187
1156
1188 async def test_script_bg_out():
1157 async def test_script_bg_out():
1189 ip = get_ipython()
1158 ip = get_ipython()
1190 ip.run_cell_magic("script", f"--bg --out output {sys.executable}", "print('hi')")
1159 ip.run_cell_magic("script", f"--bg --out output {sys.executable}", "print('hi')")
1191 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1160 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1192 assert ip.user_ns["output"].at_eof()
1161 assert ip.user_ns["output"].at_eof()
1193
1162
1194
1163
1195 async def test_script_bg_err():
1164 async def test_script_bg_err():
1196 ip = get_ipython()
1165 ip = get_ipython()
1197 ip.run_cell_magic(
1166 ip.run_cell_magic(
1198 "script",
1167 "script",
1199 f"--bg --err error {sys.executable}",
1168 f"--bg --err error {sys.executable}",
1200 "import sys; print('hello', file=sys.stderr)",
1169 "import sys; print('hello', file=sys.stderr)",
1201 )
1170 )
1202 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1171 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1203 assert ip.user_ns["error"].at_eof()
1172 assert ip.user_ns["error"].at_eof()
1204
1173
1205
1174
1206 async def test_script_bg_out_err():
1175 async def test_script_bg_out_err():
1207 ip = get_ipython()
1176 ip = get_ipython()
1208 ip.run_cell_magic(
1177 ip.run_cell_magic(
1209 "script",
1178 "script",
1210 f"--bg --out output --err error {sys.executable}",
1179 f"--bg --out output --err error {sys.executable}",
1211 "\n".join(
1180 "\n".join(
1212 [
1181 [
1213 "import sys",
1182 "import sys",
1214 "print('hi')",
1183 "print('hi')",
1215 "print('hello', file=sys.stderr)",
1184 "print('hello', file=sys.stderr)",
1216 ]
1185 ]
1217 ),
1186 ),
1218 )
1187 )
1219 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1188 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1220 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1189 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1221 assert ip.user_ns["output"].at_eof()
1190 assert ip.user_ns["output"].at_eof()
1222 assert ip.user_ns["error"].at_eof()
1191 assert ip.user_ns["error"].at_eof()
1223
1192
1224
1193
1225 async def test_script_bg_proc():
1194 async def test_script_bg_proc():
1226 ip = get_ipython()
1195 ip = get_ipython()
1227 ip.run_cell_magic(
1196 ip.run_cell_magic(
1228 "script",
1197 "script",
1229 f"--bg --out output --proc p {sys.executable}",
1198 f"--bg --out output --proc p {sys.executable}",
1230 "\n".join(
1199 "\n".join(
1231 [
1200 [
1232 "import sys",
1201 "import sys",
1233 "print('hi')",
1202 "print('hi')",
1234 "print('hello', file=sys.stderr)",
1203 "print('hello', file=sys.stderr)",
1235 ]
1204 ]
1236 ),
1205 ),
1237 )
1206 )
1238 p = ip.user_ns["p"]
1207 p = ip.user_ns["p"]
1239 await p.wait()
1208 await p.wait()
1240 assert p.returncode == 0
1209 assert p.returncode == 0
1241 assert (await p.stdout.read()).strip() == b"hi"
1210 assert (await p.stdout.read()).strip() == b"hi"
1242 # not captured, so empty
1211 # not captured, so empty
1243 assert (await p.stderr.read()) == b""
1212 assert (await p.stderr.read()) == b""
1244 assert p.stdout.at_eof()
1213 assert p.stdout.at_eof()
1245 assert p.stderr.at_eof()
1214 assert p.stderr.at_eof()
1246
1215
1247
1216
1248 def test_script_defaults():
1217 def test_script_defaults():
1249 ip = get_ipython()
1218 ip = get_ipython()
1250 for cmd in ["sh", "bash", "perl", "ruby"]:
1219 for cmd in ['sh', 'bash', 'perl', 'ruby']:
1251 try:
1220 try:
1252 find_cmd(cmd)
1221 find_cmd(cmd)
1253 except Exception:
1222 except Exception:
1254 pass
1223 pass
1255 else:
1224 else:
1256 assert cmd in ip.magics_manager.magics["cell"]
1225 assert cmd in ip.magics_manager.magics["cell"]
1257
1226
1258
1227
1259 @magics_class
1228 @magics_class
1260 class FooFoo(Magics):
1229 class FooFoo(Magics):
1261 """class with both %foo and %%foo magics"""
1230 """class with both %foo and %%foo magics"""
1262
1231 @line_magic('foo')
1263 @line_magic("foo")
1264 def line_foo(self, line):
1232 def line_foo(self, line):
1265 "I am line foo"
1233 "I am line foo"
1266 pass
1234 pass
1267
1235
1268 @cell_magic("foo")
1236 @cell_magic("foo")
1269 def cell_foo(self, line, cell):
1237 def cell_foo(self, line, cell):
1270 "I am cell foo, not line foo"
1238 "I am cell foo, not line foo"
1271 pass
1239 pass
1272
1240
1273
1274 def test_line_cell_info():
1241 def test_line_cell_info():
1275 """%%foo and %foo magics are distinguishable to inspect"""
1242 """%%foo and %foo magics are distinguishable to inspect"""
1276 ip = get_ipython()
1243 ip = get_ipython()
1277 ip.magics_manager.register(FooFoo)
1244 ip.magics_manager.register(FooFoo)
1278 oinfo = ip.object_inspect("foo")
1245 oinfo = ip.object_inspect("foo")
1279 assert oinfo["found"] is True
1246 assert oinfo["found"] is True
1280 assert oinfo["ismagic"] is True
1247 assert oinfo["ismagic"] is True
1281
1248
1282 oinfo = ip.object_inspect("%%foo")
1249 oinfo = ip.object_inspect("%%foo")
1283 assert oinfo["found"] is True
1250 assert oinfo["found"] is True
1284 assert oinfo["ismagic"] is True
1251 assert oinfo["ismagic"] is True
1285 assert oinfo["docstring"] == FooFoo.cell_foo.__doc__
1252 assert oinfo["docstring"] == FooFoo.cell_foo.__doc__
1286
1253
1287 oinfo = ip.object_inspect("%foo")
1254 oinfo = ip.object_inspect("%foo")
1288 assert oinfo["found"] is True
1255 assert oinfo["found"] is True
1289 assert oinfo["ismagic"] is True
1256 assert oinfo["ismagic"] is True
1290 assert oinfo["docstring"] == FooFoo.line_foo.__doc__
1257 assert oinfo["docstring"] == FooFoo.line_foo.__doc__
1291
1258
1292
1259
1293 def test_multiple_magics():
1260 def test_multiple_magics():
1294 ip = get_ipython()
1261 ip = get_ipython()
1295 foo1 = FooFoo(ip)
1262 foo1 = FooFoo(ip)
1296 foo2 = FooFoo(ip)
1263 foo2 = FooFoo(ip)
1297 mm = ip.magics_manager
1264 mm = ip.magics_manager
1298 mm.register(foo1)
1265 mm.register(foo1)
1299 assert mm.magics["line"]["foo"].__self__ is foo1
1266 assert mm.magics["line"]["foo"].__self__ is foo1
1300 mm.register(foo2)
1267 mm.register(foo2)
1301 assert mm.magics["line"]["foo"].__self__ is foo2
1268 assert mm.magics["line"]["foo"].__self__ is foo2
1302
1269
1303
1270
1304 def test_alias_magic():
1271 def test_alias_magic():
1305 """Test %alias_magic."""
1272 """Test %alias_magic."""
1306 ip = get_ipython()
1273 ip = get_ipython()
1307 mm = ip.magics_manager
1274 mm = ip.magics_manager
1308
1275
1309 # Basic operation: both cell and line magics are created, if possible.
1276 # Basic operation: both cell and line magics are created, if possible.
1310 ip.run_line_magic("alias_magic", "timeit_alias timeit")
1277 ip.run_line_magic("alias_magic", "timeit_alias timeit")
1311 assert "timeit_alias" in mm.magics["line"]
1278 assert "timeit_alias" in mm.magics["line"]
1312 assert "timeit_alias" in mm.magics["cell"]
1279 assert "timeit_alias" in mm.magics["cell"]
1313
1280
1314 # --cell is specified, line magic not created.
1281 # --cell is specified, line magic not created.
1315 ip.run_line_magic("alias_magic", "--cell timeit_cell_alias timeit")
1282 ip.run_line_magic("alias_magic", "--cell timeit_cell_alias timeit")
1316 assert "timeit_cell_alias" not in mm.magics["line"]
1283 assert "timeit_cell_alias" not in mm.magics["line"]
1317 assert "timeit_cell_alias" in mm.magics["cell"]
1284 assert "timeit_cell_alias" in mm.magics["cell"]
1318
1285
1319 # Test that line alias is created successfully.
1286 # Test that line alias is created successfully.
1320 ip.run_line_magic("alias_magic", "--line env_alias env")
1287 ip.run_line_magic("alias_magic", "--line env_alias env")
1321 assert ip.run_line_magic("env", "") == ip.run_line_magic("env_alias", "")
1288 assert ip.run_line_magic("env", "") == ip.run_line_magic("env_alias", "")
1322
1289
1323 # Test that line alias with parameters passed in is created successfully.
1290 # Test that line alias with parameters passed in is created successfully.
1324 ip.run_line_magic(
1291 ip.run_line_magic(
1325 "alias_magic", "--line history_alias history --params " + shlex.quote("3")
1292 "alias_magic", "--line history_alias history --params " + shlex.quote("3")
1326 )
1293 )
1327 assert "history_alias" in mm.magics["line"]
1294 assert "history_alias" in mm.magics["line"]
1328
1295
1329
1296
1330 def test_save():
1297 def test_save():
1331 """Test %save."""
1298 """Test %save."""
1332 ip = get_ipython()
1299 ip = get_ipython()
1333 ip.history_manager.reset() # Clear any existing history.
1300 ip.history_manager.reset() # Clear any existing history.
1334 cmds = ["a=1", "def b():\n return a**2", "print(a, b())"]
1301 cmds = ["a=1", "def b():\n return a**2", "print(a, b())"]
1335 for i, cmd in enumerate(cmds, start=1):
1302 for i, cmd in enumerate(cmds, start=1):
1336 ip.history_manager.store_inputs(i, cmd)
1303 ip.history_manager.store_inputs(i, cmd)
1337 with TemporaryDirectory() as tmpdir:
1304 with TemporaryDirectory() as tmpdir:
1338 file = os.path.join(tmpdir, "testsave.py")
1305 file = os.path.join(tmpdir, "testsave.py")
1339 ip.run_line_magic("save", "%s 1-10" % file)
1306 ip.run_line_magic("save", "%s 1-10" % file)
1340 content = Path(file).read_text(encoding="utf-8")
1307 content = Path(file).read_text(encoding="utf-8")
1341 assert content.count(cmds[0]) == 1
1308 assert content.count(cmds[0]) == 1
1342 assert "coding: utf-8" in content
1309 assert "coding: utf-8" in content
1343 ip.run_line_magic("save", "-a %s 1-10" % file)
1310 ip.run_line_magic("save", "-a %s 1-10" % file)
1344 content = Path(file).read_text(encoding="utf-8")
1311 content = Path(file).read_text(encoding="utf-8")
1345 assert content.count(cmds[0]) == 2
1312 assert content.count(cmds[0]) == 2
1346 assert "coding: utf-8" in content
1313 assert "coding: utf-8" in content
1347
1314
1348
1315
1349 def test_save_with_no_args():
1316 def test_save_with_no_args():
1350 ip = get_ipython()
1317 ip = get_ipython()
1351 ip.history_manager.reset() # Clear any existing history.
1318 ip.history_manager.reset() # Clear any existing history.
1352 cmds = ["a=1", "def b():\n return a**2", "print(a, b())", "%save"]
1319 cmds = ["a=1", "def b():\n return a**2", "print(a, b())", "%save"]
1353 for i, cmd in enumerate(cmds, start=1):
1320 for i, cmd in enumerate(cmds, start=1):
1354 ip.history_manager.store_inputs(i, cmd)
1321 ip.history_manager.store_inputs(i, cmd)
1355
1322
1356 with TemporaryDirectory() as tmpdir:
1323 with TemporaryDirectory() as tmpdir:
1357 path = os.path.join(tmpdir, "testsave.py")
1324 path = os.path.join(tmpdir, "testsave.py")
1358 ip.run_line_magic("save", path)
1325 ip.run_line_magic("save", path)
1359 content = Path(path).read_text(encoding="utf-8")
1326 content = Path(path).read_text(encoding="utf-8")
1360 expected_content = dedent(
1327 expected_content = dedent(
1361 """\
1328 """\
1362 # coding: utf-8
1329 # coding: utf-8
1363 a=1
1330 a=1
1364 def b():
1331 def b():
1365 return a**2
1332 return a**2
1366 print(a, b())
1333 print(a, b())
1367 """
1334 """
1368 )
1335 )
1369 assert content == expected_content
1336 assert content == expected_content
1370
1337
1371
1338
1372 def test_store():
1339 def test_store():
1373 """Test %store."""
1340 """Test %store."""
1374 ip = get_ipython()
1341 ip = get_ipython()
1375 ip.run_line_magic("load_ext", "storemagic")
1342 ip.run_line_magic('load_ext', 'storemagic')
1376
1343
1377 # make sure the storage is empty
1344 # make sure the storage is empty
1378 ip.run_line_magic("store", "-z")
1345 ip.run_line_magic("store", "-z")
1379 ip.user_ns["var"] = 42
1346 ip.user_ns["var"] = 42
1380 ip.run_line_magic("store", "var")
1347 ip.run_line_magic("store", "var")
1381 ip.user_ns["var"] = 39
1348 ip.user_ns["var"] = 39
1382 ip.run_line_magic("store", "-r")
1349 ip.run_line_magic("store", "-r")
1383 assert ip.user_ns["var"] == 42
1350 assert ip.user_ns["var"] == 42
1384
1351
1385 ip.run_line_magic("store", "-d var")
1352 ip.run_line_magic("store", "-d var")
1386 ip.user_ns["var"] = 39
1353 ip.user_ns["var"] = 39
1387 ip.run_line_magic("store", "-r")
1354 ip.run_line_magic("store", "-r")
1388 assert ip.user_ns["var"] == 39
1355 assert ip.user_ns["var"] == 39
1389
1356
1390
1357
1391 def _run_edit_test(
1358 def _run_edit_test(arg_s, exp_filename=None,
1392 arg_s, exp_filename=None, exp_lineno=-1, exp_contents=None, exp_is_temp=None
1359 exp_lineno=-1,
1393 ):
1360 exp_contents=None,
1361 exp_is_temp=None):
1394 ip = get_ipython()
1362 ip = get_ipython()
1395 M = code.CodeMagics(ip)
1363 M = code.CodeMagics(ip)
1396 last_call = ["", ""]
1364 last_call = ['','']
1397 opts, args = M.parse_options(arg_s, "prxn:")
1365 opts,args = M.parse_options(arg_s,'prxn:')
1398 filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call)
1366 filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call)
1399
1367
1400 if exp_filename is not None:
1368 if exp_filename is not None:
1401 assert exp_filename == filename
1369 assert exp_filename == filename
1402 if exp_contents is not None:
1370 if exp_contents is not None:
1403 with io.open(filename, "r", encoding="utf-8") as f:
1371 with io.open(filename, 'r', encoding='utf-8') as f:
1404 contents = f.read()
1372 contents = f.read()
1405 assert exp_contents == contents
1373 assert exp_contents == contents
1406 if exp_lineno != -1:
1374 if exp_lineno != -1:
1407 assert exp_lineno == lineno
1375 assert exp_lineno == lineno
1408 if exp_is_temp is not None:
1376 if exp_is_temp is not None:
1409 assert exp_is_temp == is_temp
1377 assert exp_is_temp == is_temp
1410
1378
1411
1379
1412 def test_edit_interactive():
1380 def test_edit_interactive():
1413 """%edit on interactively defined objects"""
1381 """%edit on interactively defined objects"""
1414 ip = get_ipython()
1382 ip = get_ipython()
1415 n = ip.execution_count
1383 n = ip.execution_count
1416 ip.run_cell("def foo(): return 1", store_history=True)
1384 ip.run_cell("def foo(): return 1", store_history=True)
1417
1385
1418 with pytest.raises(code.InteractivelyDefined) as e:
1386 with pytest.raises(code.InteractivelyDefined) as e:
1419 _run_edit_test("foo")
1387 _run_edit_test("foo")
1420 assert e.value.index == n
1388 assert e.value.index == n
1421
1389
1422
1390
1423 def test_edit_cell():
1391 def test_edit_cell():
1424 """%edit [cell id]"""
1392 """%edit [cell id]"""
1425 ip = get_ipython()
1393 ip = get_ipython()
1426
1394
1427 ip.run_cell("def foo(): return 1", store_history=True)
1395 ip.run_cell("def foo(): return 1", store_history=True)
1428
1396
1429 # test
1397 # test
1430 _run_edit_test("1", exp_contents=ip.user_ns["In"][1], exp_is_temp=True)
1398 _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True)
1431
1432
1399
1433 def test_edit_fname():
1400 def test_edit_fname():
1434 """%edit file"""
1401 """%edit file"""
1435 # test
1402 # test
1436 _run_edit_test("test file.py", exp_filename="test file.py")
1403 _run_edit_test("test file.py", exp_filename="test file.py")
1437
1404
1438
1439 def test_bookmark():
1405 def test_bookmark():
1440 ip = get_ipython()
1406 ip = get_ipython()
1441 ip.run_line_magic("bookmark", "bmname")
1407 ip.run_line_magic('bookmark', 'bmname')
1442 with tt.AssertPrints("bmname"):
1408 with tt.AssertPrints('bmname'):
1443 ip.run_line_magic("bookmark", "-l")
1409 ip.run_line_magic('bookmark', '-l')
1444 ip.run_line_magic("bookmark", "-d bmname")
1410 ip.run_line_magic('bookmark', '-d bmname')
1445
1446
1411
1447 def test_ls_magic():
1412 def test_ls_magic():
1448 ip = get_ipython()
1413 ip = get_ipython()
1449 json_formatter = ip.display_formatter.formatters["application/json"]
1414 json_formatter = ip.display_formatter.formatters['application/json']
1450 json_formatter.enabled = True
1415 json_formatter.enabled = True
1451 lsmagic = ip.run_line_magic("lsmagic", "")
1416 lsmagic = ip.run_line_magic("lsmagic", "")
1452 with warnings.catch_warnings(record=True) as w:
1417 with warnings.catch_warnings(record=True) as w:
1453 j = json_formatter(lsmagic)
1418 j = json_formatter(lsmagic)
1454 assert sorted(j) == ["cell", "line"]
1419 assert sorted(j) == ["cell", "line"]
1455 assert w == [] # no warnings
1420 assert w == [] # no warnings
1456
1421
1457
1422
1458 def test_strip_initial_indent():
1423 def test_strip_initial_indent():
1459 def sii(s):
1424 def sii(s):
1460 lines = s.splitlines()
1425 lines = s.splitlines()
1461 return "\n".join(code.strip_initial_indent(lines))
1426 return '\n'.join(code.strip_initial_indent(lines))
1462
1427
1463 assert sii(" a = 1\nb = 2") == "a = 1\nb = 2"
1428 assert sii(" a = 1\nb = 2") == "a = 1\nb = 2"
1464 assert sii(" a\n b\nc") == "a\n b\nc"
1429 assert sii(" a\n b\nc") == "a\n b\nc"
1465 assert sii("a\n b") == "a\n b"
1430 assert sii("a\n b") == "a\n b"
1466
1431
1467
1468 def test_logging_magic_quiet_from_arg():
1432 def test_logging_magic_quiet_from_arg():
1469 _ip.config.LoggingMagics.quiet = False
1433 _ip.config.LoggingMagics.quiet = False
1470 lm = logging.LoggingMagics(shell=_ip)
1434 lm = logging.LoggingMagics(shell=_ip)
1471 with TemporaryDirectory() as td:
1435 with TemporaryDirectory() as td:
1472 try:
1436 try:
1473 with tt.AssertNotPrints(re.compile("Activating.*")):
1437 with tt.AssertNotPrints(re.compile("Activating.*")):
1474 lm.logstart("-q {}".format(os.path.join(td, "quiet_from_arg.log")))
1438 lm.logstart('-q {}'.format(
1439 os.path.join(td, "quiet_from_arg.log")))
1475 finally:
1440 finally:
1476 _ip.logger.logstop()
1441 _ip.logger.logstop()
1477
1442
1478
1479 def test_logging_magic_quiet_from_config():
1443 def test_logging_magic_quiet_from_config():
1480 _ip.config.LoggingMagics.quiet = True
1444 _ip.config.LoggingMagics.quiet = True
1481 lm = logging.LoggingMagics(shell=_ip)
1445 lm = logging.LoggingMagics(shell=_ip)
1482 with TemporaryDirectory() as td:
1446 with TemporaryDirectory() as td:
1483 try:
1447 try:
1484 with tt.AssertNotPrints(re.compile("Activating.*")):
1448 with tt.AssertNotPrints(re.compile("Activating.*")):
1485 lm.logstart(os.path.join(td, "quiet_from_config.log"))
1449 lm.logstart(os.path.join(td, "quiet_from_config.log"))
1486 finally:
1450 finally:
1487 _ip.logger.logstop()
1451 _ip.logger.logstop()
1488
1452
1489
1453
1490 def test_logging_magic_not_quiet():
1454 def test_logging_magic_not_quiet():
1491 _ip.config.LoggingMagics.quiet = False
1455 _ip.config.LoggingMagics.quiet = False
1492 lm = logging.LoggingMagics(shell=_ip)
1456 lm = logging.LoggingMagics(shell=_ip)
1493 with TemporaryDirectory() as td:
1457 with TemporaryDirectory() as td:
1494 try:
1458 try:
1495 with tt.AssertPrints(re.compile("Activating.*")):
1459 with tt.AssertPrints(re.compile("Activating.*")):
1496 lm.logstart(os.path.join(td, "not_quiet.log"))
1460 lm.logstart(os.path.join(td, "not_quiet.log"))
1497 finally:
1461 finally:
1498 _ip.logger.logstop()
1462 _ip.logger.logstop()
1499
1463
1500
1464
1501 def test_time_no_var_expand():
1465 def test_time_no_var_expand():
1502 _ip.user_ns["a"] = 5
1466 _ip.user_ns["a"] = 5
1503 _ip.user_ns["b"] = []
1467 _ip.user_ns["b"] = []
1504 _ip.run_line_magic("time", 'b.append("{a}")')
1468 _ip.run_line_magic("time", 'b.append("{a}")')
1505 assert _ip.user_ns["b"] == ["{a}"]
1469 assert _ip.user_ns["b"] == ["{a}"]
1506
1470
1507
1471
1508 # this is slow, put at the end for local testing.
1472 # this is slow, put at the end for local testing.
1509 def test_timeit_arguments():
1473 def test_timeit_arguments():
1510 "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
1474 "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
1511 _ip.run_line_magic("timeit", "-n1 -r1 a=('#')")
1475 _ip.run_line_magic("timeit", "-n1 -r1 a=('#')")
1512
1476
1513
1477
1514 MINIMAL_LAZY_MAGIC = """
1478 MINIMAL_LAZY_MAGIC = """
1515 from IPython.core.magic import (
1479 from IPython.core.magic import (
1516 Magics,
1480 Magics,
1517 magics_class,
1481 magics_class,
1518 line_magic,
1482 line_magic,
1519 cell_magic,
1483 cell_magic,
1520 )
1484 )
1521
1485
1522
1486
1523 @magics_class
1487 @magics_class
1524 class LazyMagics(Magics):
1488 class LazyMagics(Magics):
1525 @line_magic
1489 @line_magic
1526 def lazy_line(self, line):
1490 def lazy_line(self, line):
1527 print("Lazy Line")
1491 print("Lazy Line")
1528
1492
1529 @cell_magic
1493 @cell_magic
1530 def lazy_cell(self, line, cell):
1494 def lazy_cell(self, line, cell):
1531 print("Lazy Cell")
1495 print("Lazy Cell")
1532
1496
1533
1497
1534 def load_ipython_extension(ipython):
1498 def load_ipython_extension(ipython):
1535 ipython.register_magics(LazyMagics)
1499 ipython.register_magics(LazyMagics)
1536 """
1500 """
1537
1501
1538
1502
1539 def test_lazy_magics():
1503 def test_lazy_magics():
1540 with pytest.raises(UsageError):
1504 with pytest.raises(UsageError):
1541 _ip.run_line_magic("lazy_line", "")
1505 ip.run_line_magic("lazy_line", "")
1506
1507 startdir = os.getcwd()
1542
1508
1543 with TemporaryDirectory() as tmpdir:
1509 with TemporaryDirectory() as tmpdir:
1544 with prepended_to_syspath(tmpdir):
1510 with prepended_to_syspath(tmpdir):
1545 ptempdir = Path(tmpdir)
1511 ptempdir = Path(tmpdir)
1546 tf = ptempdir / "lazy_magic_module.py"
1512 tf = ptempdir / "lazy_magic_module.py"
1547 tf.write_text(MINIMAL_LAZY_MAGIC)
1513 tf.write_text(MINIMAL_LAZY_MAGIC)
1548 _ip.magics_manager.register_lazy("lazy_line", Path(tf.name).name[:-3])
1514 ip.magics_manager.register_lazy("lazy_line", Path(tf.name).name[:-3])
1549 with tt.AssertPrints("Lazy Line"):
1515 with tt.AssertPrints("Lazy Line"):
1550 _ip.run_line_magic("lazy_line", "")
1516 ip.run_line_magic("lazy_line", "")
1551
1517
1552
1518
1553 TEST_MODULE = """
1519 TEST_MODULE = """
1554 print('Loaded my_tmp')
1520 print('Loaded my_tmp')
1555 if __name__ == "__main__":
1521 if __name__ == "__main__":
1556 print('I just ran a script')
1522 print('I just ran a script')
1557 """
1523 """
1558
1524
1559
1560 def test_run_module_from_import_hook():
1525 def test_run_module_from_import_hook():
1561 "Test that a module can be loaded via an import hook"
1526 "Test that a module can be loaded via an import hook"
1562 with TemporaryDirectory() as tmpdir:
1527 with TemporaryDirectory() as tmpdir:
1563 fullpath = os.path.join(tmpdir, "my_tmp.py")
1528 fullpath = os.path.join(tmpdir, "my_tmp.py")
1564 Path(fullpath).write_text(TEST_MODULE, encoding="utf-8")
1529 Path(fullpath).write_text(TEST_MODULE, encoding="utf-8")
1565
1530
1566 import importlib.abc
1531 import importlib.abc
1567 import importlib.util
1532 import importlib.util
1568
1533
1569 class MyTempImporter(importlib.abc.MetaPathFinder, importlib.abc.SourceLoader):
1534 class MyTempImporter(importlib.abc.MetaPathFinder, importlib.abc.SourceLoader):
1570 def find_spec(self, fullname, path, target=None):
1535 def find_spec(self, fullname, path, target=None):
1571 if fullname == "my_tmp":
1536 if fullname == "my_tmp":
1572 return importlib.util.spec_from_loader(fullname, self)
1537 return importlib.util.spec_from_loader(fullname, self)
1573
1538
1574 def get_filename(self, fullname):
1539 def get_filename(self, fullname):
1575 assert fullname == "my_tmp"
1540 assert fullname == "my_tmp"
1576 return fullpath
1541 return fullpath
1577
1542
1578 def get_data(self, path):
1543 def get_data(self, path):
1579 assert Path(path).samefile(fullpath)
1544 assert Path(path).samefile(fullpath)
1580 return Path(fullpath).read_text(encoding="utf-8")
1545 return Path(fullpath).read_text(encoding="utf-8")
1581
1546
1582 sys.meta_path.insert(0, MyTempImporter())
1547 sys.meta_path.insert(0, MyTempImporter())
1583
1548
1584 with capture_output() as captured:
1549 with capture_output() as captured:
1585 _ip.run_line_magic("run", "-m my_tmp")
1550 _ip.run_line_magic("run", "-m my_tmp")
1586 _ip.run_cell("import my_tmp")
1551 _ip.run_cell("import my_tmp")
1587
1552
1588 output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n"
1553 output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n"
1589 assert output == captured.stdout
1554 assert output == captured.stdout
1590
1555
1591 sys.meta_path.pop(0)
1556 sys.meta_path.pop(0)
@@ -1,202 +1,202
1 import errno
1 import errno
2 import os
2 import os
3 import shutil
3 import shutil
4 import tempfile
4 import tempfile
5 import warnings
5 import warnings
6 from unittest.mock import patch
6 from unittest.mock import patch
7
7
8 from tempfile import TemporaryDirectory
8 from tempfile import TemporaryDirectory
9 from testpath import assert_isdir, assert_isfile, modified_env
9 from testpath import assert_isdir, assert_isfile, modified_env
10
10
11 from IPython import paths
11 from IPython import paths
12 from IPython.testing.decorators import skip_win32
12 from IPython.testing.decorators import skip_win32
13
13
14 TMP_TEST_DIR = os.path.realpath(tempfile.mkdtemp())
14 TMP_TEST_DIR = os.path.realpath(tempfile.mkdtemp())
15 HOME_TEST_DIR = os.path.join(TMP_TEST_DIR, "home_test_dir")
15 HOME_TEST_DIR = os.path.join(TMP_TEST_DIR, "home_test_dir")
16 XDG_TEST_DIR = os.path.join(HOME_TEST_DIR, "xdg_test_dir")
16 XDG_TEST_DIR = os.path.join(HOME_TEST_DIR, "xdg_test_dir")
17 XDG_CACHE_DIR = os.path.join(HOME_TEST_DIR, "xdg_cache_dir")
17 XDG_CACHE_DIR = os.path.join(HOME_TEST_DIR, "xdg_cache_dir")
18 IP_TEST_DIR = os.path.join(HOME_TEST_DIR,'.ipython')
18 IP_TEST_DIR = os.path.join(HOME_TEST_DIR,'.ipython')
19
19
20 def setup_module():
20 def setup_module():
21 """Setup testenvironment for the module:
21 """Setup testenvironment for the module:
22
22
23 - Adds dummy home dir tree
23 - Adds dummy home dir tree
24 """
24 """
25 # Do not mask exceptions here. In particular, catching WindowsError is a
25 # Do not mask exceptions here. In particular, catching WindowsError is a
26 # problem because that exception is only defined on Windows...
26 # problem because that exception is only defined on Windows...
27 os.makedirs(IP_TEST_DIR)
27 os.makedirs(IP_TEST_DIR)
28 os.makedirs(os.path.join(XDG_TEST_DIR, 'ipython'))
28 os.makedirs(os.path.join(XDG_TEST_DIR, 'ipython'))
29 os.makedirs(os.path.join(XDG_CACHE_DIR, 'ipython'))
29 os.makedirs(os.path.join(XDG_CACHE_DIR, 'ipython'))
30
30
31
31
32 def teardown_module():
32 def teardown_module():
33 """Teardown testenvironment for the module:
33 """Teardown testenvironment for the module:
34
34
35 - Remove dummy home dir tree
35 - Remove dummy home dir tree
36 """
36 """
37 # Note: we remove the parent test dir, which is the root of all test
37 # Note: we remove the parent test dir, which is the root of all test
38 # subdirs we may have created. Use shutil instead of os.removedirs, so
38 # subdirs we may have created. Use shutil instead of os.removedirs, so
39 # that non-empty directories are all recursively removed.
39 # that non-empty directories are all recursively removed.
40 shutil.rmtree(TMP_TEST_DIR)
40 shutil.rmtree(TMP_TEST_DIR)
41
41
42 def patch_get_home_dir(dirpath):
42 def patch_get_home_dir(dirpath):
43 return patch.object(paths, 'get_home_dir', return_value=dirpath)
43 return patch.object(paths, 'get_home_dir', return_value=dirpath)
44
44
45
45
46 def test_get_ipython_dir_1():
46 def test_get_ipython_dir_1():
47 """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions."""
47 """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions."""
48 env_ipdir = os.path.join("someplace", ".ipython")
48 env_ipdir = os.path.join("someplace", ".ipython")
49 with patch.object(paths, '_writable_dir', return_value=True), \
49 with patch.object(paths, '_writable_dir', return_value=True), \
50 modified_env({'IPYTHONDIR': env_ipdir}):
50 modified_env({'IPYTHONDIR': env_ipdir}):
51 ipdir = paths.get_ipython_dir()
51 ipdir = paths.get_ipython_dir()
52
52
53 assert ipdir == env_ipdir
53 assert ipdir == env_ipdir
54
54
55 def test_get_ipython_dir_2():
55 def test_get_ipython_dir_2():
56 """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions."""
56 """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions."""
57 with patch_get_home_dir('someplace'), \
57 with patch_get_home_dir('someplace'), \
58 patch.object(paths, 'get_xdg_dir', return_value=None), \
58 patch.object(paths, 'get_xdg_dir', return_value=None), \
59 patch.object(paths, '_writable_dir', return_value=True), \
59 patch.object(paths, '_writable_dir', return_value=True), \
60 patch('os.name', "posix"), \
60 patch('os.name', "posix"), \
61 modified_env({'IPYTHON_DIR': None,
61 modified_env({'IPYTHON_DIR': None,
62 'IPYTHONDIR': None,
62 'IPYTHONDIR': None,
63 'XDG_CONFIG_HOME': None
63 'XDG_CONFIG_HOME': None
64 }):
64 }):
65 ipdir = paths.get_ipython_dir()
65 ipdir = paths.get_ipython_dir()
66
66
67 assert ipdir == os.path.join("someplace", ".ipython")
67 assert ipdir == os.path.join("someplace", ".ipython")
68
68
69 def test_get_ipython_dir_3():
69 def test_get_ipython_dir_3():
70 """test_get_ipython_dir_3, use XDG if defined and exists, and .ipython doesn't exist."""
70 """test_get_ipython_dir_3, use XDG if defined and exists, and .ipython doesn't exist."""
71 tmphome = TemporaryDirectory()
71 tmphome = TemporaryDirectory()
72 try:
72 try:
73 with patch_get_home_dir(tmphome.name), \
73 with patch_get_home_dir(tmphome.name), \
74 patch('os.name', 'posix'), \
74 patch('os.name', 'posix'), \
75 modified_env({
75 modified_env({
76 'IPYTHON_DIR': None,
76 'IPYTHON_DIR': None,
77 'IPYTHONDIR': None,
77 'IPYTHONDIR': None,
78 'XDG_CONFIG_HOME': XDG_TEST_DIR,
78 'XDG_CONFIG_HOME': XDG_TEST_DIR,
79 }), warnings.catch_warnings(record=True) as w:
79 }), warnings.catch_warnings(record=True) as w:
80 ipdir = paths.get_ipython_dir()
80 ipdir = paths.get_ipython_dir()
81
81
82 assert ipdir == os.path.join(tmphome.name, XDG_TEST_DIR, "ipython")
82 assert ipdir == os.path.join(tmphome.name, XDG_TEST_DIR, "ipython")
83 assert len(w) == 0
83 assert len(w) == 0
84 finally:
84 finally:
85 tmphome.cleanup()
85 tmphome.cleanup()
86
86
87 def test_get_ipython_dir_4():
87 def test_get_ipython_dir_4():
88 """test_get_ipython_dir_4, warn if XDG and home both exist."""
88 """test_get_ipython_dir_4, warn if XDG and home both exist."""
89 with patch_get_home_dir(HOME_TEST_DIR), \
89 with patch_get_home_dir(HOME_TEST_DIR), \
90 patch('os.name', 'posix'):
90 patch('os.name', 'posix'):
91 try:
91 try:
92 os.mkdir(os.path.join(XDG_TEST_DIR, 'ipython'))
92 os.mkdir(os.path.join(XDG_TEST_DIR, 'ipython'))
93 except OSError as e:
93 except OSError as e:
94 if e.errno != errno.EEXIST:
94 if e.errno != errno.EEXIST:
95 raise
95 raise
96
96
97
97
98 with modified_env({
98 with modified_env({
99 'IPYTHON_DIR': None,
99 'IPYTHON_DIR': None,
100 'IPYTHONDIR': None,
100 'IPYTHONDIR': None,
101 'XDG_CONFIG_HOME': XDG_TEST_DIR,
101 'XDG_CONFIG_HOME': XDG_TEST_DIR,
102 }), warnings.catch_warnings(record=True) as w:
102 }), warnings.catch_warnings(record=True) as w:
103 _ipdir = paths.get_ipython_dir()
103 ipdir = paths.get_ipython_dir()
104
104
105 assert len(w) == 1
105 assert len(w) == 1
106 assert "Ignoring" in str(w[0])
106 assert "Ignoring" in str(w[0])
107
107
108
108
109 def test_get_ipython_dir_5():
109 def test_get_ipython_dir_5():
110 """test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist."""
110 """test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist."""
111 with patch_get_home_dir(HOME_TEST_DIR), \
111 with patch_get_home_dir(HOME_TEST_DIR), \
112 patch('os.name', 'posix'):
112 patch('os.name', 'posix'):
113 try:
113 try:
114 os.rmdir(os.path.join(XDG_TEST_DIR, 'ipython'))
114 os.rmdir(os.path.join(XDG_TEST_DIR, 'ipython'))
115 except OSError as e:
115 except OSError as e:
116 if e.errno != errno.ENOENT:
116 if e.errno != errno.ENOENT:
117 raise
117 raise
118
118
119 with modified_env({
119 with modified_env({
120 'IPYTHON_DIR': None,
120 'IPYTHON_DIR': None,
121 'IPYTHONDIR': None,
121 'IPYTHONDIR': None,
122 'XDG_CONFIG_HOME': XDG_TEST_DIR,
122 'XDG_CONFIG_HOME': XDG_TEST_DIR,
123 }):
123 }):
124 ipdir = paths.get_ipython_dir()
124 ipdir = paths.get_ipython_dir()
125
125
126 assert ipdir == IP_TEST_DIR
126 assert ipdir == IP_TEST_DIR
127
127
128 def test_get_ipython_dir_6():
128 def test_get_ipython_dir_6():
129 """test_get_ipython_dir_6, use home over XDG if defined and neither exist."""
129 """test_get_ipython_dir_6, use home over XDG if defined and neither exist."""
130 xdg = os.path.join(HOME_TEST_DIR, 'somexdg')
130 xdg = os.path.join(HOME_TEST_DIR, 'somexdg')
131 os.mkdir(xdg)
131 os.mkdir(xdg)
132 shutil.rmtree(os.path.join(HOME_TEST_DIR, '.ipython'))
132 shutil.rmtree(os.path.join(HOME_TEST_DIR, '.ipython'))
133 print(paths._writable_dir)
133 print(paths._writable_dir)
134 with patch_get_home_dir(HOME_TEST_DIR), \
134 with patch_get_home_dir(HOME_TEST_DIR), \
135 patch.object(paths, 'get_xdg_dir', return_value=xdg), \
135 patch.object(paths, 'get_xdg_dir', return_value=xdg), \
136 patch('os.name', 'posix'), \
136 patch('os.name', 'posix'), \
137 modified_env({
137 modified_env({
138 'IPYTHON_DIR': None,
138 'IPYTHON_DIR': None,
139 'IPYTHONDIR': None,
139 'IPYTHONDIR': None,
140 'XDG_CONFIG_HOME': None,
140 'XDG_CONFIG_HOME': None,
141 }), warnings.catch_warnings(record=True) as w:
141 }), warnings.catch_warnings(record=True) as w:
142 ipdir = paths.get_ipython_dir()
142 ipdir = paths.get_ipython_dir()
143
143
144 assert ipdir == os.path.join(HOME_TEST_DIR, ".ipython")
144 assert ipdir == os.path.join(HOME_TEST_DIR, ".ipython")
145 assert len(w) == 0
145 assert len(w) == 0
146
146
147 def test_get_ipython_dir_7():
147 def test_get_ipython_dir_7():
148 """test_get_ipython_dir_7, test home directory expansion on IPYTHONDIR"""
148 """test_get_ipython_dir_7, test home directory expansion on IPYTHONDIR"""
149 home_dir = os.path.normpath(os.path.expanduser('~'))
149 home_dir = os.path.normpath(os.path.expanduser('~'))
150 with modified_env({'IPYTHONDIR': os.path.join('~', 'somewhere')}), \
150 with modified_env({'IPYTHONDIR': os.path.join('~', 'somewhere')}), \
151 patch.object(paths, '_writable_dir', return_value=True):
151 patch.object(paths, '_writable_dir', return_value=True):
152 ipdir = paths.get_ipython_dir()
152 ipdir = paths.get_ipython_dir()
153 assert ipdir == os.path.join(home_dir, "somewhere")
153 assert ipdir == os.path.join(home_dir, "somewhere")
154
154
155
155
156 @skip_win32
156 @skip_win32
157 def test_get_ipython_dir_8():
157 def test_get_ipython_dir_8():
158 """test_get_ipython_dir_8, test / home directory"""
158 """test_get_ipython_dir_8, test / home directory"""
159 if not os.access("/", os.W_OK):
159 if not os.access("/", os.W_OK):
160 # test only when HOME directory actually writable
160 # test only when HOME directory actually writable
161 return
161 return
162
162
163 with (
163 with (
164 patch.object(paths, "_writable_dir", lambda path: bool(path)),
164 patch.object(paths, "_writable_dir", lambda path: bool(path)),
165 patch.object(paths, "get_xdg_dir", return_value=None),
165 patch.object(paths, "get_xdg_dir", return_value=None),
166 modified_env(
166 modified_env(
167 {
167 {
168 "IPYTHON_DIR": None,
168 "IPYTHON_DIR": None,
169 "IPYTHONDIR": None,
169 "IPYTHONDIR": None,
170 "HOME": "/",
170 "HOME": "/",
171 }
171 }
172 ),
172 ),
173 ):
173 ):
174 assert paths.get_ipython_dir() == "/.ipython"
174 assert paths.get_ipython_dir() == "/.ipython"
175
175
176
176
177 def test_get_ipython_cache_dir():
177 def test_get_ipython_cache_dir():
178 with modified_env({'HOME': HOME_TEST_DIR}):
178 with modified_env({'HOME': HOME_TEST_DIR}):
179 if os.name == "posix":
179 if os.name == "posix":
180 # test default
180 # test default
181 os.makedirs(os.path.join(HOME_TEST_DIR, ".cache"))
181 os.makedirs(os.path.join(HOME_TEST_DIR, ".cache"))
182 with modified_env({'XDG_CACHE_HOME': None}):
182 with modified_env({'XDG_CACHE_HOME': None}):
183 ipdir = paths.get_ipython_cache_dir()
183 ipdir = paths.get_ipython_cache_dir()
184 assert os.path.join(HOME_TEST_DIR, ".cache", "ipython") == ipdir
184 assert os.path.join(HOME_TEST_DIR, ".cache", "ipython") == ipdir
185 assert_isdir(ipdir)
185 assert_isdir(ipdir)
186
186
187 # test env override
187 # test env override
188 with modified_env({"XDG_CACHE_HOME": XDG_CACHE_DIR}):
188 with modified_env({"XDG_CACHE_HOME": XDG_CACHE_DIR}):
189 ipdir = paths.get_ipython_cache_dir()
189 ipdir = paths.get_ipython_cache_dir()
190 assert_isdir(ipdir)
190 assert_isdir(ipdir)
191 assert ipdir == os.path.join(XDG_CACHE_DIR, "ipython")
191 assert ipdir == os.path.join(XDG_CACHE_DIR, "ipython")
192 else:
192 else:
193 assert paths.get_ipython_cache_dir() == paths.get_ipython_dir()
193 assert paths.get_ipython_cache_dir() == paths.get_ipython_dir()
194
194
195 def test_get_ipython_package_dir():
195 def test_get_ipython_package_dir():
196 ipdir = paths.get_ipython_package_dir()
196 ipdir = paths.get_ipython_package_dir()
197 assert_isdir(ipdir)
197 assert_isdir(ipdir)
198
198
199
199
200 def test_get_ipython_module_path():
200 def test_get_ipython_module_path():
201 ipapp_path = paths.get_ipython_module_path('IPython.terminal.ipapp')
201 ipapp_path = paths.get_ipython_module_path('IPython.terminal.ipapp')
202 assert_isfile(ipapp_path)
202 assert_isfile(ipapp_path)
@@ -1,148 +1,149
1 """Tests for input manipulation machinery."""
1 """Tests for input manipulation machinery."""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Imports
4 # Imports
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 import pytest
6
7
7 from IPython.core.prefilter import AutocallChecker
8 from IPython.core.prefilter import AutocallChecker
8
9
9 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
10 # Tests
11 # Tests
11 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
12
13
13 def test_prefilter():
14 def test_prefilter():
14 """Test user input conversions"""
15 """Test user input conversions"""
15
16
16 # pairs of (raw, expected correct) input
17 # pairs of (raw, expected correct) input
17 pairs = [ ('2+2','2+2'),
18 pairs = [ ('2+2','2+2'),
18 ]
19 ]
19
20
20 for raw, correct in pairs:
21 for raw, correct in pairs:
21 assert ip.prefilter(raw) == correct
22 assert ip.prefilter(raw) == correct
22
23
23 def test_prefilter_shadowed():
24 def test_prefilter_shadowed():
24 def dummy_magic(line): pass
25 def dummy_magic(line): pass
25
26
26 prev_automagic_state = ip.automagic
27 prev_automagic_state = ip.automagic
27 ip.automagic = True
28 ip.automagic = True
28 ip.autocall = 0
29 ip.autocall = 0
29
30
30 try:
31 try:
31 # These should not be transformed - they are shadowed by other names
32 # These should not be transformed - they are shadowed by other names
32 for name in ['if', 'zip', 'get_ipython']: # keyword, builtin, global
33 for name in ['if', 'zip', 'get_ipython']: # keyword, builtin, global
33 ip.register_magic_function(dummy_magic, magic_name=name)
34 ip.register_magic_function(dummy_magic, magic_name=name)
34 res = ip.prefilter(name + " foo")
35 res = ip.prefilter(name + " foo")
35 assert res == name + " foo"
36 assert res == name + " foo"
36 del ip.magics_manager.magics["line"][name]
37 del ip.magics_manager.magics["line"][name]
37
38
38 # These should be transformed
39 # These should be transformed
39 for name in ['fi', 'piz', 'nohtypi_teg']:
40 for name in ['fi', 'piz', 'nohtypi_teg']:
40 ip.register_magic_function(dummy_magic, magic_name=name)
41 ip.register_magic_function(dummy_magic, magic_name=name)
41 res = ip.prefilter(name + " foo")
42 res = ip.prefilter(name + " foo")
42 assert res != name + " foo"
43 assert res != name + " foo"
43 del ip.magics_manager.magics["line"][name]
44 del ip.magics_manager.magics["line"][name]
44
45
45 finally:
46 finally:
46 ip.automagic = prev_automagic_state
47 ip.automagic = prev_automagic_state
47
48
48 def test_autocall_binops():
49 def test_autocall_binops():
49 """See https://github.com/ipython/ipython/issues/81"""
50 """See https://github.com/ipython/ipython/issues/81"""
50 ip.run_line_magic("autocall", "2")
51 ip.run_line_magic("autocall", "2")
51 f = lambda x: x
52 f = lambda x: x
52 ip.user_ns['f'] = f
53 ip.user_ns['f'] = f
53 try:
54 try:
54 assert ip.prefilter("f 1") == "f(1)"
55 assert ip.prefilter("f 1") == "f(1)"
55 for t in ["f +1", "f -1"]:
56 for t in ["f +1", "f -1"]:
56 assert ip.prefilter(t) == t
57 assert ip.prefilter(t) == t
57
58
58 # Run tests again with a more permissive exclude_regexp, which will
59 # Run tests again with a more permissive exclude_regexp, which will
59 # allow transformation of binary operations ('f -1' -> 'f(-1)').
60 # allow transformation of binary operations ('f -1' -> 'f(-1)').
60 pm = ip.prefilter_manager
61 pm = ip.prefilter_manager
61 ac = AutocallChecker(shell=pm.shell, prefilter_manager=pm,
62 ac = AutocallChecker(shell=pm.shell, prefilter_manager=pm,
62 config=pm.config)
63 config=pm.config)
63 try:
64 try:
64 ac.priority = 1
65 ac.priority = 1
65 ac.exclude_regexp = r'^[,&^\|\*/]|^is |^not |^in |^and |^or '
66 ac.exclude_regexp = r'^[,&^\|\*/]|^is |^not |^in |^and |^or '
66 pm.sort_checkers()
67 pm.sort_checkers()
67
68
68 assert ip.prefilter("f -1") == "f(-1)"
69 assert ip.prefilter("f -1") == "f(-1)"
69 assert ip.prefilter("f +1") == "f(+1)"
70 assert ip.prefilter("f +1") == "f(+1)"
70 finally:
71 finally:
71 pm.unregister_checker(ac)
72 pm.unregister_checker(ac)
72 finally:
73 finally:
73 ip.run_line_magic("autocall", "0")
74 ip.run_line_magic("autocall", "0")
74 del ip.user_ns["f"]
75 del ip.user_ns["f"]
75
76
76
77
77 def test_issue_114():
78 def test_issue_114():
78 """Check that multiline string literals don't expand as magic
79 """Check that multiline string literals don't expand as magic
79 see http://github.com/ipython/ipython/issues/114"""
80 see http://github.com/ipython/ipython/issues/114"""
80
81
81 template = '"""\n%s\n"""'
82 template = '"""\n%s\n"""'
82 # Store the current value of multi_line_specials and turn it off before
83 # Store the current value of multi_line_specials and turn it off before
83 # running test, since it could be true (case in which the test doesn't make
84 # running test, since it could be true (case in which the test doesn't make
84 # sense, as multiline string literals *will* expand as magic in that case).
85 # sense, as multiline string literals *will* expand as magic in that case).
85 msp = ip.prefilter_manager.multi_line_specials
86 msp = ip.prefilter_manager.multi_line_specials
86 ip.prefilter_manager.multi_line_specials = False
87 ip.prefilter_manager.multi_line_specials = False
87 try:
88 try:
88 for mgk in ip.magics_manager.lsmagic()['line']:
89 for mgk in ip.magics_manager.lsmagic()['line']:
89 raw = template % mgk
90 raw = template % mgk
90 assert ip.prefilter(raw) == raw
91 assert ip.prefilter(raw) == raw
91 finally:
92 finally:
92 ip.prefilter_manager.multi_line_specials = msp
93 ip.prefilter_manager.multi_line_specials = msp
93
94
94
95
95 def test_prefilter_attribute_errors():
96 def test_prefilter_attribute_errors():
96 """Capture exceptions thrown by user objects on attribute access.
97 """Capture exceptions thrown by user objects on attribute access.
97
98
98 See http://github.com/ipython/ipython/issues/988."""
99 See http://github.com/ipython/ipython/issues/988."""
99
100
100 class X(object):
101 class X(object):
101 def __getattr__(self, k):
102 def __getattr__(self, k):
102 raise ValueError('broken object')
103 raise ValueError('broken object')
103 def __call__(self, x):
104 def __call__(self, x):
104 return x
105 return x
105
106
106 # Create a callable broken object
107 # Create a callable broken object
107 ip.user_ns["x"] = X()
108 ip.user_ns["x"] = X()
108 ip.run_line_magic("autocall", "2")
109 ip.run_line_magic("autocall", "2")
109 try:
110 try:
110 # Even if x throws an attribute error when looking at its rewrite
111 # Even if x throws an attribute error when looking at its rewrite
111 # attribute, we should not crash. So the test here is simply making
112 # attribute, we should not crash. So the test here is simply making
112 # the prefilter call and not having an exception.
113 # the prefilter call and not having an exception.
113 ip.prefilter('x 1')
114 ip.prefilter('x 1')
114 finally:
115 finally:
115 del ip.user_ns["x"]
116 del ip.user_ns["x"]
116 ip.run_line_magic("autocall", "0")
117 ip.run_line_magic("autocall", "0")
117
118
118
119
119 def test_autocall_type_ann():
120 def test_autocall_type_ann():
120 ip.run_cell("import collections.abc")
121 ip.run_cell("import collections.abc")
121 ip.run_line_magic("autocall", "1")
122 ip.run_line_magic("autocall", "1")
122 try:
123 try:
123 assert (
124 assert (
124 ip.prefilter("collections.abc.Callable[[int], None]")
125 ip.prefilter("collections.abc.Callable[[int], None]")
125 == "collections.abc.Callable[[int], None]"
126 == "collections.abc.Callable[[int], None]"
126 )
127 )
127 finally:
128 finally:
128 ip.run_line_magic("autocall", "0")
129 ip.run_line_magic("autocall", "0")
129
130
130
131
131 def test_autocall_should_support_unicode():
132 def test_autocall_should_support_unicode():
132 ip.run_line_magic("autocall", "2")
133 ip.run_line_magic("autocall", "2")
133 ip.user_ns["π"] = lambda x: x
134 ip.user_ns["π"] = lambda x: x
134 try:
135 try:
135 assert ip.prefilter("π 3") == "π(3)"
136 assert ip.prefilter("π 3") == "π(3)"
136 finally:
137 finally:
137 ip.run_line_magic("autocall", "0")
138 ip.run_line_magic("autocall", "0")
138 del ip.user_ns["π"]
139 del ip.user_ns["π"]
139
140
140
141
141 def test_autocall_regression_gh_14513():
142 def test_autocall_regression_gh_14513():
142 ip.run_line_magic("autocall", "2")
143 ip.run_line_magic("autocall", "2")
143 ip.user_ns["foo"] = dict()
144 ip.user_ns["foo"] = dict()
144 try:
145 try:
145 assert ip.prefilter("foo") == "foo"
146 assert ip.prefilter("foo") == "foo"
146 finally:
147 finally:
147 ip.run_line_magic("autocall", "0")
148 ip.run_line_magic("autocall", "0")
148 del ip.user_ns["foo"]
149 del ip.user_ns["foo"]
@@ -1,456 +1,457
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.core.ultratb
2 """Tests for IPython.core.ultratb
3 """
3 """
4 import io
4 import io
5 import os.path
5 import os.path
6 import platform
6 import platform
7 import re
7 import re
8 import sys
8 import sys
9 import traceback
9 import traceback
10 import unittest
10 import unittest
11 from textwrap import dedent
11 from textwrap import dedent
12
12
13 from tempfile import TemporaryDirectory
13 from tempfile import TemporaryDirectory
14
14
15 from IPython.core.ultratb import ColorTB, VerboseTB
15 from IPython.core.ultratb import ColorTB, VerboseTB
16 from IPython.testing import tools as tt
16 from IPython.testing import tools as tt
17 from IPython.testing.decorators import onlyif_unicode_paths, skip_without
17 from IPython.testing.decorators import onlyif_unicode_paths, skip_without
18 from IPython.utils.syspathcontext import prepended_to_syspath
18 from IPython.utils.syspathcontext import prepended_to_syspath
19
19
20 file_1 = """1
20 file_1 = """1
21 2
21 2
22 3
22 3
23 def f():
23 def f():
24 1/0
24 1/0
25 """
25 """
26
26
27 file_2 = """def f():
27 file_2 = """def f():
28 1/0
28 1/0
29 """
29 """
30
30
31
31
32 def recursionlimit(frames):
32 def recursionlimit(frames):
33 """
33 """
34 decorator to set the recursion limit temporarily
34 decorator to set the recursion limit temporarily
35 """
35 """
36
36
37 def inner(test_function):
37 def inner(test_function):
38 def wrapper(*args, **kwargs):
38 def wrapper(*args, **kwargs):
39 rl = sys.getrecursionlimit()
39 rl = sys.getrecursionlimit()
40 sys.setrecursionlimit(frames)
40 sys.setrecursionlimit(frames)
41 try:
41 try:
42 return test_function(*args, **kwargs)
42 return test_function(*args, **kwargs)
43 finally:
43 finally:
44 sys.setrecursionlimit(rl)
44 sys.setrecursionlimit(rl)
45
45
46 return wrapper
46 return wrapper
47
47
48 return inner
48 return inner
49
49
50
50
51 class ChangedPyFileTest(unittest.TestCase):
51 class ChangedPyFileTest(unittest.TestCase):
52 def test_changing_py_file(self):
52 def test_changing_py_file(self):
53 """Traceback produced if the line where the error occurred is missing?
53 """Traceback produced if the line where the error occurred is missing?
54
54
55 https://github.com/ipython/ipython/issues/1456
55 https://github.com/ipython/ipython/issues/1456
56 """
56 """
57 with TemporaryDirectory() as td:
57 with TemporaryDirectory() as td:
58 fname = os.path.join(td, "foo.py")
58 fname = os.path.join(td, "foo.py")
59 with open(fname, "w", encoding="utf-8") as f:
59 with open(fname, "w", encoding="utf-8") as f:
60 f.write(file_1)
60 f.write(file_1)
61
61
62 with prepended_to_syspath(td):
62 with prepended_to_syspath(td):
63 ip.run_cell("import foo")
63 ip.run_cell("import foo")
64
64
65 with tt.AssertPrints("ZeroDivisionError"):
65 with tt.AssertPrints("ZeroDivisionError"):
66 ip.run_cell("foo.f()")
66 ip.run_cell("foo.f()")
67
67
68 # Make the file shorter, so the line of the error is missing.
68 # Make the file shorter, so the line of the error is missing.
69 with open(fname, "w", encoding="utf-8") as f:
69 with open(fname, "w", encoding="utf-8") as f:
70 f.write(file_2)
70 f.write(file_2)
71
71
72 # For some reason, this was failing on the *second* call after
72 # For some reason, this was failing on the *second* call after
73 # changing the file, so we call f() twice.
73 # changing the file, so we call f() twice.
74 with tt.AssertNotPrints("Internal Python error", channel='stderr'):
74 with tt.AssertNotPrints("Internal Python error", channel='stderr'):
75 with tt.AssertPrints("ZeroDivisionError"):
75 with tt.AssertPrints("ZeroDivisionError"):
76 ip.run_cell("foo.f()")
76 ip.run_cell("foo.f()")
77 with tt.AssertPrints("ZeroDivisionError"):
77 with tt.AssertPrints("ZeroDivisionError"):
78 ip.run_cell("foo.f()")
78 ip.run_cell("foo.f()")
79
79
80 iso_8859_5_file = u'''# coding: iso-8859-5
80 iso_8859_5_file = u'''# coding: iso-8859-5
81
81
82 def fail():
82 def fail():
83 """дбИЖ"""
83 """дбИЖ"""
84 1/0 # дбИЖ
84 1/0 # дбИЖ
85 '''
85 '''
86
86
87 class NonAsciiTest(unittest.TestCase):
87 class NonAsciiTest(unittest.TestCase):
88 @onlyif_unicode_paths
88 @onlyif_unicode_paths
89 def test_nonascii_path(self):
89 def test_nonascii_path(self):
90 # Non-ascii directory name as well.
90 # Non-ascii directory name as well.
91 with TemporaryDirectory(suffix=u'é') as td:
91 with TemporaryDirectory(suffix=u'é') as td:
92 fname = os.path.join(td, u"fooé.py")
92 fname = os.path.join(td, u"fooé.py")
93 with open(fname, "w", encoding="utf-8") as f:
93 with open(fname, "w", encoding="utf-8") as f:
94 f.write(file_1)
94 f.write(file_1)
95
95
96 with prepended_to_syspath(td):
96 with prepended_to_syspath(td):
97 ip.run_cell("import foo")
97 ip.run_cell("import foo")
98
98
99 with tt.AssertPrints("ZeroDivisionError"):
99 with tt.AssertPrints("ZeroDivisionError"):
100 ip.run_cell("foo.f()")
100 ip.run_cell("foo.f()")
101
101
102 def test_iso8859_5(self):
102 def test_iso8859_5(self):
103 with TemporaryDirectory() as td:
103 with TemporaryDirectory() as td:
104 fname = os.path.join(td, 'dfghjkl.py')
104 fname = os.path.join(td, 'dfghjkl.py')
105
105
106 with io.open(fname, 'w', encoding='iso-8859-5') as f:
106 with io.open(fname, 'w', encoding='iso-8859-5') as f:
107 f.write(iso_8859_5_file)
107 f.write(iso_8859_5_file)
108
108
109 with prepended_to_syspath(td):
109 with prepended_to_syspath(td):
110 ip.run_cell("from dfghjkl import fail")
110 ip.run_cell("from dfghjkl import fail")
111
111
112 with tt.AssertPrints("ZeroDivisionError"):
112 with tt.AssertPrints("ZeroDivisionError"):
113 with tt.AssertPrints(u'дбИЖ', suppress=False):
113 with tt.AssertPrints(u'дбИЖ', suppress=False):
114 ip.run_cell('fail()')
114 ip.run_cell('fail()')
115
115
116 def test_nonascii_msg(self):
116 def test_nonascii_msg(self):
117 cell = u"raise Exception('é')"
117 cell = u"raise Exception('é')"
118 expected = u"Exception('é')"
118 expected = u"Exception('é')"
119 ip.run_cell("%xmode plain")
119 ip.run_cell("%xmode plain")
120 with tt.AssertPrints(expected):
120 with tt.AssertPrints(expected):
121 ip.run_cell(cell)
121 ip.run_cell(cell)
122
122
123 ip.run_cell("%xmode verbose")
123 ip.run_cell("%xmode verbose")
124 with tt.AssertPrints(expected):
124 with tt.AssertPrints(expected):
125 ip.run_cell(cell)
125 ip.run_cell(cell)
126
126
127 ip.run_cell("%xmode context")
127 ip.run_cell("%xmode context")
128 with tt.AssertPrints(expected):
128 with tt.AssertPrints(expected):
129 ip.run_cell(cell)
129 ip.run_cell(cell)
130
130
131 ip.run_cell("%xmode minimal")
131 ip.run_cell("%xmode minimal")
132 with tt.AssertPrints(u"Exception: é"):
132 with tt.AssertPrints(u"Exception: é"):
133 ip.run_cell(cell)
133 ip.run_cell(cell)
134
134
135 # Put this back into Context mode for later tests.
135 # Put this back into Context mode for later tests.
136 ip.run_cell("%xmode context")
136 ip.run_cell("%xmode context")
137
137
138 class NestedGenExprTestCase(unittest.TestCase):
138 class NestedGenExprTestCase(unittest.TestCase):
139 """
139 """
140 Regression test for the following issues:
140 Regression test for the following issues:
141 https://github.com/ipython/ipython/issues/8293
141 https://github.com/ipython/ipython/issues/8293
142 https://github.com/ipython/ipython/issues/8205
142 https://github.com/ipython/ipython/issues/8205
143 """
143 """
144 def test_nested_genexpr(self):
144 def test_nested_genexpr(self):
145 code = dedent(
145 code = dedent(
146 """\
146 """\
147 class SpecificException(Exception):
147 class SpecificException(Exception):
148 pass
148 pass
149
149
150 def foo(x):
150 def foo(x):
151 raise SpecificException("Success!")
151 raise SpecificException("Success!")
152
152
153 sum(sum(foo(x) for _ in [0]) for x in [0])
153 sum(sum(foo(x) for _ in [0]) for x in [0])
154 """
154 """
155 )
155 )
156 with tt.AssertPrints('SpecificException: Success!', suppress=False):
156 with tt.AssertPrints('SpecificException: Success!', suppress=False):
157 ip.run_cell(code)
157 ip.run_cell(code)
158
158
159
159
160 indentationerror_file = """if True:
160 indentationerror_file = """if True:
161 zoom()
161 zoom()
162 """
162 """
163
163
164 class IndentationErrorTest(unittest.TestCase):
164 class IndentationErrorTest(unittest.TestCase):
165 def test_indentationerror_shows_line(self):
165 def test_indentationerror_shows_line(self):
166 # See issue gh-2398
166 # See issue gh-2398
167 with tt.AssertPrints("IndentationError"):
167 with tt.AssertPrints("IndentationError"):
168 with tt.AssertPrints("zoom()", suppress=False):
168 with tt.AssertPrints("zoom()", suppress=False):
169 ip.run_cell(indentationerror_file)
169 ip.run_cell(indentationerror_file)
170
170
171 with TemporaryDirectory() as td:
171 with TemporaryDirectory() as td:
172 fname = os.path.join(td, "foo.py")
172 fname = os.path.join(td, "foo.py")
173 with open(fname, "w", encoding="utf-8") as f:
173 with open(fname, "w", encoding="utf-8") as f:
174 f.write(indentationerror_file)
174 f.write(indentationerror_file)
175
175
176 with tt.AssertPrints("IndentationError"):
176 with tt.AssertPrints("IndentationError"):
177 with tt.AssertPrints("zoom()", suppress=False):
177 with tt.AssertPrints("zoom()", suppress=False):
178 ip.run_line_magic("run", fname)
178 ip.run_line_magic("run", fname)
179
179
180
180
181 @skip_without("pandas")
181 @skip_without("pandas")
182 def test_dynamic_code():
182 def test_dynamic_code():
183 code = """
183 code = """
184 import pandas
184 import pandas
185 df = pandas.DataFrame([])
185 df = pandas.DataFrame([])
186
186
187 # Important: only fails inside of an "exec" call:
187 # Important: only fails inside of an "exec" call:
188 exec("df.foobarbaz()")
188 exec("df.foobarbaz()")
189 """
189 """
190
190
191 with tt.AssertPrints("Could not get source"):
191 with tt.AssertPrints("Could not get source"):
192 ip.run_cell(code)
192 ip.run_cell(code)
193
193
194
194
195 se_file_1 = """1
195 se_file_1 = """1
196 2
196 2
197 7/
197 7/
198 """
198 """
199
199
200 se_file_2 = """7/
200 se_file_2 = """7/
201 """
201 """
202
202
203 class SyntaxErrorTest(unittest.TestCase):
203 class SyntaxErrorTest(unittest.TestCase):
204
204
205 def test_syntaxerror_no_stacktrace_at_compile_time(self):
205 def test_syntaxerror_no_stacktrace_at_compile_time(self):
206 syntax_error_at_compile_time = """
206 syntax_error_at_compile_time = """
207 def foo():
207 def foo():
208 ..
208 ..
209 """
209 """
210 with tt.AssertPrints("SyntaxError"):
210 with tt.AssertPrints("SyntaxError"):
211 ip.run_cell(syntax_error_at_compile_time)
211 ip.run_cell(syntax_error_at_compile_time)
212
212
213 with tt.AssertNotPrints("foo()"):
213 with tt.AssertNotPrints("foo()"):
214 ip.run_cell(syntax_error_at_compile_time)
214 ip.run_cell(syntax_error_at_compile_time)
215
215
216 def test_syntaxerror_stacktrace_when_running_compiled_code(self):
216 def test_syntaxerror_stacktrace_when_running_compiled_code(self):
217 syntax_error_at_runtime = """
217 syntax_error_at_runtime = """
218 def foo():
218 def foo():
219 eval("..")
219 eval("..")
220
220
221 def bar():
221 def bar():
222 foo()
222 foo()
223
223
224 bar()
224 bar()
225 """
225 """
226 with tt.AssertPrints("SyntaxError"):
226 with tt.AssertPrints("SyntaxError"):
227 ip.run_cell(syntax_error_at_runtime)
227 ip.run_cell(syntax_error_at_runtime)
228 # Assert syntax error during runtime generate stacktrace
228 # Assert syntax error during runtime generate stacktrace
229 with tt.AssertPrints(["foo()", "bar()"]):
229 with tt.AssertPrints(["foo()", "bar()"]):
230 ip.run_cell(syntax_error_at_runtime)
230 ip.run_cell(syntax_error_at_runtime)
231 del ip.user_ns['bar']
231 del ip.user_ns['bar']
232 del ip.user_ns['foo']
232 del ip.user_ns['foo']
233
233
234 def test_changing_py_file(self):
234 def test_changing_py_file(self):
235 with TemporaryDirectory() as td:
235 with TemporaryDirectory() as td:
236 fname = os.path.join(td, "foo.py")
236 fname = os.path.join(td, "foo.py")
237 with open(fname, "w", encoding="utf-8") as f:
237 with open(fname, "w", encoding="utf-8") as f:
238 f.write(se_file_1)
238 f.write(se_file_1)
239
239
240 with tt.AssertPrints(["7/", "SyntaxError"]):
240 with tt.AssertPrints(["7/", "SyntaxError"]):
241 ip.run_line_magic("run", fname)
241 ip.run_line_magic("run", fname)
242
242
243 # Modify the file
243 # Modify the file
244 with open(fname, "w", encoding="utf-8") as f:
244 with open(fname, "w", encoding="utf-8") as f:
245 f.write(se_file_2)
245 f.write(se_file_2)
246
246
247 # The SyntaxError should point to the correct line
247 # The SyntaxError should point to the correct line
248 with tt.AssertPrints(["7/", "SyntaxError"]):
248 with tt.AssertPrints(["7/", "SyntaxError"]):
249 ip.run_line_magic("run", fname)
249 ip.run_line_magic("run", fname)
250
250
251 def test_non_syntaxerror(self):
251 def test_non_syntaxerror(self):
252 # SyntaxTB may be called with an error other than a SyntaxError
252 # SyntaxTB may be called with an error other than a SyntaxError
253 # See e.g. gh-4361
253 # See e.g. gh-4361
254 try:
254 try:
255 raise ValueError('QWERTY')
255 raise ValueError('QWERTY')
256 except ValueError:
256 except ValueError:
257 with tt.AssertPrints('QWERTY'):
257 with tt.AssertPrints('QWERTY'):
258 ip.showsyntaxerror()
258 ip.showsyntaxerror()
259
259
260 import sys
260
261
261 if platform.python_implementation() != "PyPy":
262 if platform.python_implementation() != "PyPy":
262 """
263 """
263 New 3.9 Pgen Parser does not raise Memory error, except on failed malloc.
264 New 3.9 Pgen Parser does not raise Memory error, except on failed malloc.
264 """
265 """
265 class MemoryErrorTest(unittest.TestCase):
266 class MemoryErrorTest(unittest.TestCase):
266 def test_memoryerror(self):
267 def test_memoryerror(self):
267 memoryerror_code = "(" * 200 + ")" * 200
268 memoryerror_code = "(" * 200 + ")" * 200
268 ip.run_cell(memoryerror_code)
269 ip.run_cell(memoryerror_code)
269
270
270
271
271 class Python3ChainedExceptionsTest(unittest.TestCase):
272 class Python3ChainedExceptionsTest(unittest.TestCase):
272 DIRECT_CAUSE_ERROR_CODE = """
273 DIRECT_CAUSE_ERROR_CODE = """
273 try:
274 try:
274 x = 1 + 2
275 x = 1 + 2
275 print(not_defined_here)
276 print(not_defined_here)
276 except Exception as e:
277 except Exception as e:
277 x += 55
278 x += 55
278 x - 1
279 x - 1
279 y = {}
280 y = {}
280 raise KeyError('uh') from e
281 raise KeyError('uh') from e
281 """
282 """
282
283
283 EXCEPTION_DURING_HANDLING_CODE = """
284 EXCEPTION_DURING_HANDLING_CODE = """
284 try:
285 try:
285 x = 1 + 2
286 x = 1 + 2
286 print(not_defined_here)
287 print(not_defined_here)
287 except Exception as e:
288 except Exception as e:
288 x += 55
289 x += 55
289 x - 1
290 x - 1
290 y = {}
291 y = {}
291 raise KeyError('uh')
292 raise KeyError('uh')
292 """
293 """
293
294
294 SUPPRESS_CHAINING_CODE = """
295 SUPPRESS_CHAINING_CODE = """
295 try:
296 try:
296 1/0
297 1/0
297 except Exception:
298 except Exception:
298 raise ValueError("Yikes") from None
299 raise ValueError("Yikes") from None
299 """
300 """
300
301
301 SYS_EXIT_WITH_CONTEXT_CODE = """
302 SYS_EXIT_WITH_CONTEXT_CODE = """
302 try:
303 try:
303 1/0
304 1/0
304 except Exception as e:
305 except Exception as e:
305 raise SystemExit(1)
306 raise SystemExit(1)
306 """
307 """
307
308
308 def test_direct_cause_error(self):
309 def test_direct_cause_error(self):
309 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
310 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
310 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
311 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
311
312
312 def test_exception_during_handling_error(self):
313 def test_exception_during_handling_error(self):
313 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
314 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
314 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
315 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
315
316
316 def test_sysexit_while_handling_error(self):
317 def test_sysexit_while_handling_error(self):
317 with tt.AssertPrints(["SystemExit", "to see the full traceback"]):
318 with tt.AssertPrints(["SystemExit", "to see the full traceback"]):
318 with tt.AssertNotPrints(["another exception"], suppress=False):
319 with tt.AssertNotPrints(["another exception"], suppress=False):
319 ip.run_cell(self.SYS_EXIT_WITH_CONTEXT_CODE)
320 ip.run_cell(self.SYS_EXIT_WITH_CONTEXT_CODE)
320
321
321 def test_suppress_exception_chaining(self):
322 def test_suppress_exception_chaining(self):
322 with tt.AssertNotPrints("ZeroDivisionError"), \
323 with tt.AssertNotPrints("ZeroDivisionError"), \
323 tt.AssertPrints("ValueError", suppress=False):
324 tt.AssertPrints("ValueError", suppress=False):
324 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
325 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
325
326
326 def test_plain_direct_cause_error(self):
327 def test_plain_direct_cause_error(self):
327 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
328 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
328 ip.run_cell("%xmode Plain")
329 ip.run_cell("%xmode Plain")
329 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
330 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
330 ip.run_cell("%xmode Verbose")
331 ip.run_cell("%xmode Verbose")
331
332
332 def test_plain_exception_during_handling_error(self):
333 def test_plain_exception_during_handling_error(self):
333 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
334 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
334 ip.run_cell("%xmode Plain")
335 ip.run_cell("%xmode Plain")
335 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
336 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
336 ip.run_cell("%xmode Verbose")
337 ip.run_cell("%xmode Verbose")
337
338
338 def test_plain_suppress_exception_chaining(self):
339 def test_plain_suppress_exception_chaining(self):
339 with tt.AssertNotPrints("ZeroDivisionError"), \
340 with tt.AssertNotPrints("ZeroDivisionError"), \
340 tt.AssertPrints("ValueError", suppress=False):
341 tt.AssertPrints("ValueError", suppress=False):
341 ip.run_cell("%xmode Plain")
342 ip.run_cell("%xmode Plain")
342 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
343 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
343 ip.run_cell("%xmode Verbose")
344 ip.run_cell("%xmode Verbose")
344
345
345
346
346 class RecursionTest(unittest.TestCase):
347 class RecursionTest(unittest.TestCase):
347 DEFINITIONS = """
348 DEFINITIONS = """
348 def non_recurs():
349 def non_recurs():
349 1/0
350 1/0
350
351
351 def r1():
352 def r1():
352 r1()
353 r1()
353
354
354 def r3a():
355 def r3a():
355 r3b()
356 r3b()
356
357
357 def r3b():
358 def r3b():
358 r3c()
359 r3c()
359
360
360 def r3c():
361 def r3c():
361 r3a()
362 r3a()
362
363
363 def r3o1():
364 def r3o1():
364 r3a()
365 r3a()
365
366
366 def r3o2():
367 def r3o2():
367 r3o1()
368 r3o1()
368 """
369 """
369 def setUp(self):
370 def setUp(self):
370 ip.run_cell(self.DEFINITIONS)
371 ip.run_cell(self.DEFINITIONS)
371
372
372 def test_no_recursion(self):
373 def test_no_recursion(self):
373 with tt.AssertNotPrints("skipping similar frames"):
374 with tt.AssertNotPrints("skipping similar frames"):
374 ip.run_cell("non_recurs()")
375 ip.run_cell("non_recurs()")
375
376
376 @recursionlimit(200)
377 @recursionlimit(200)
377 def test_recursion_one_frame(self):
378 def test_recursion_one_frame(self):
378 with tt.AssertPrints(re.compile(
379 with tt.AssertPrints(re.compile(
379 r"\[\.\.\. skipping similar frames: r1 at line 5 \(\d{2,3} times\)\]")
380 r"\[\.\.\. skipping similar frames: r1 at line 5 \(\d{2,3} times\)\]")
380 ):
381 ):
381 ip.run_cell("r1()")
382 ip.run_cell("r1()")
382
383
383 @recursionlimit(160)
384 @recursionlimit(160)
384 def test_recursion_three_frames(self):
385 def test_recursion_three_frames(self):
385 with tt.AssertPrints("[... skipping similar frames: "), \
386 with tt.AssertPrints("[... skipping similar frames: "), \
386 tt.AssertPrints(re.compile(r"r3a at line 8 \(\d{2} times\)"), suppress=False), \
387 tt.AssertPrints(re.compile(r"r3a at line 8 \(\d{2} times\)"), suppress=False), \
387 tt.AssertPrints(re.compile(r"r3b at line 11 \(\d{2} times\)"), suppress=False), \
388 tt.AssertPrints(re.compile(r"r3b at line 11 \(\d{2} times\)"), suppress=False), \
388 tt.AssertPrints(re.compile(r"r3c at line 14 \(\d{2} times\)"), suppress=False):
389 tt.AssertPrints(re.compile(r"r3c at line 14 \(\d{2} times\)"), suppress=False):
389 ip.run_cell("r3o2()")
390 ip.run_cell("r3o2()")
390
391
391
392
392 class PEP678NotesReportingTest(unittest.TestCase):
393 class PEP678NotesReportingTest(unittest.TestCase):
393 ERROR_WITH_NOTE = """
394 ERROR_WITH_NOTE = """
394 try:
395 try:
395 raise AssertionError("Message")
396 raise AssertionError("Message")
396 except Exception as e:
397 except Exception as e:
397 try:
398 try:
398 e.add_note("This is a PEP-678 note.")
399 e.add_note("This is a PEP-678 note.")
399 except AttributeError: # Python <= 3.10
400 except AttributeError: # Python <= 3.10
400 e.__notes__ = ("This is a PEP-678 note.",)
401 e.__notes__ = ("This is a PEP-678 note.",)
401 raise
402 raise
402 """
403 """
403
404
404 def test_verbose_reports_notes(self):
405 def test_verbose_reports_notes(self):
405 with tt.AssertPrints(["AssertionError", "Message", "This is a PEP-678 note."]):
406 with tt.AssertPrints(["AssertionError", "Message", "This is a PEP-678 note."]):
406 ip.run_cell(self.ERROR_WITH_NOTE)
407 ip.run_cell(self.ERROR_WITH_NOTE)
407
408
408 def test_plain_reports_notes(self):
409 def test_plain_reports_notes(self):
409 with tt.AssertPrints(["AssertionError", "Message", "This is a PEP-678 note."]):
410 with tt.AssertPrints(["AssertionError", "Message", "This is a PEP-678 note."]):
410 ip.run_cell("%xmode Plain")
411 ip.run_cell("%xmode Plain")
411 ip.run_cell(self.ERROR_WITH_NOTE)
412 ip.run_cell(self.ERROR_WITH_NOTE)
412 ip.run_cell("%xmode Verbose")
413 ip.run_cell("%xmode Verbose")
413
414
414
415
415 #----------------------------------------------------------------------------
416 #----------------------------------------------------------------------------
416
417
417 # module testing (minimal)
418 # module testing (minimal)
418 def test_handlers():
419 def test_handlers():
419 def spam(c, d_e):
420 def spam(c, d_e):
420 (d, e) = d_e
421 (d, e) = d_e
421 x = c + d
422 x = c + d
422 y = c * d
423 y = c * d
423 foo(x, y)
424 foo(x, y)
424
425
425 def foo(a, b, bar=1):
426 def foo(a, b, bar=1):
426 eggs(a, b + bar)
427 eggs(a, b + bar)
427
428
428 def eggs(f, g, z=globals()):
429 def eggs(f, g, z=globals()):
429 h = f + g
430 h = f + g
430 i = f - g
431 i = f - g
431 return h / i
432 return h / i
432
433
433 buff = io.StringIO()
434 buff = io.StringIO()
434
435
435 buff.write('')
436 buff.write('')
436 buff.write('*** Before ***')
437 buff.write('*** Before ***')
437 try:
438 try:
438 buff.write(spam(1, (2, 3)))
439 buff.write(spam(1, (2, 3)))
439 except:
440 except:
440 traceback.print_exc(file=buff)
441 traceback.print_exc(file=buff)
441
442
442 handler = ColorTB(ostream=buff)
443 handler = ColorTB(ostream=buff)
443 buff.write('*** ColorTB ***')
444 buff.write('*** ColorTB ***')
444 try:
445 try:
445 buff.write(spam(1, (2, 3)))
446 buff.write(spam(1, (2, 3)))
446 except:
447 except:
447 handler(*sys.exc_info())
448 handler(*sys.exc_info())
448 buff.write('')
449 buff.write('')
449
450
450 handler = VerboseTB(ostream=buff)
451 handler = VerboseTB(ostream=buff)
451 buff.write('*** VerboseTB ***')
452 buff.write('*** VerboseTB ***')
452 try:
453 try:
453 buff.write(spam(1, (2, 3)))
454 buff.write(spam(1, (2, 3)))
454 except:
455 except:
455 handler(*sys.exc_info())
456 handler(*sys.exc_info())
456 buff.write('')
457 buff.write('')
@@ -1,1545 +1,1547
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 Verbose and colourful traceback formatting.
3 Verbose and colourful traceback formatting.
4
4
5 **ColorTB**
5 **ColorTB**
6
6
7 I've always found it a bit hard to visually parse tracebacks in Python. The
7 I've always found it a bit hard to visually parse tracebacks in Python. The
8 ColorTB class is a solution to that problem. It colors the different parts of a
8 ColorTB class is a solution to that problem. It colors the different parts of a
9 traceback in a manner similar to what you would expect from a syntax-highlighting
9 traceback in a manner similar to what you would expect from a syntax-highlighting
10 text editor.
10 text editor.
11
11
12 Installation instructions for ColorTB::
12 Installation instructions for ColorTB::
13
13
14 import sys,ultratb
14 import sys,ultratb
15 sys.excepthook = ultratb.ColorTB()
15 sys.excepthook = ultratb.ColorTB()
16
16
17 **VerboseTB**
17 **VerboseTB**
18
18
19 I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds
19 I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds
20 of useful info when a traceback occurs. Ping originally had it spit out HTML
20 of useful info when a traceback occurs. Ping originally had it spit out HTML
21 and intended it for CGI programmers, but why should they have all the fun? I
21 and intended it for CGI programmers, but why should they have all the fun? I
22 altered it to spit out colored text to the terminal. It's a bit overwhelming,
22 altered it to spit out colored text to the terminal. It's a bit overwhelming,
23 but kind of neat, and maybe useful for long-running programs that you believe
23 but kind of neat, and maybe useful for long-running programs that you believe
24 are bug-free. If a crash *does* occur in that type of program you want details.
24 are bug-free. If a crash *does* occur in that type of program you want details.
25 Give it a shot--you'll love it or you'll hate it.
25 Give it a shot--you'll love it or you'll hate it.
26
26
27 .. note::
27 .. note::
28
28
29 The Verbose mode prints the variables currently visible where the exception
29 The Verbose mode prints the variables currently visible where the exception
30 happened (shortening their strings if too long). This can potentially be
30 happened (shortening their strings if too long). This can potentially be
31 very slow, if you happen to have a huge data structure whose string
31 very slow, if you happen to have a huge data structure whose string
32 representation is complex to compute. Your computer may appear to freeze for
32 representation is complex to compute. Your computer may appear to freeze for
33 a while with cpu usage at 100%. If this occurs, you can cancel the traceback
33 a while with cpu usage at 100%. If this occurs, you can cancel the traceback
34 with Ctrl-C (maybe hitting it more than once).
34 with Ctrl-C (maybe hitting it more than once).
35
35
36 If you encounter this kind of situation often, you may want to use the
36 If you encounter this kind of situation often, you may want to use the
37 Verbose_novars mode instead of the regular Verbose, which avoids formatting
37 Verbose_novars mode instead of the regular Verbose, which avoids formatting
38 variables (but otherwise includes the information and context given by
38 variables (but otherwise includes the information and context given by
39 Verbose).
39 Verbose).
40
40
41 .. note::
41 .. note::
42
42
43 The verbose mode print all variables in the stack, which means it can
43 The verbose mode print all variables in the stack, which means it can
44 potentially leak sensitive information like access keys, or unencrypted
44 potentially leak sensitive information like access keys, or unencrypted
45 password.
45 password.
46
46
47 Installation instructions for VerboseTB::
47 Installation instructions for VerboseTB::
48
48
49 import sys,ultratb
49 import sys,ultratb
50 sys.excepthook = ultratb.VerboseTB()
50 sys.excepthook = ultratb.VerboseTB()
51
51
52 Note: Much of the code in this module was lifted verbatim from the standard
52 Note: Much of the code in this module was lifted verbatim from the standard
53 library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'.
53 library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'.
54
54
55 Color schemes
55 Color schemes
56 -------------
56 -------------
57
57
58 The colors are defined in the class TBTools through the use of the
58 The colors are defined in the class TBTools through the use of the
59 ColorSchemeTable class. Currently the following exist:
59 ColorSchemeTable class. Currently the following exist:
60
60
61 - NoColor: allows all of this module to be used in any terminal (the color
61 - NoColor: allows all of this module to be used in any terminal (the color
62 escapes are just dummy blank strings).
62 escapes are just dummy blank strings).
63
63
64 - Linux: is meant to look good in a terminal like the Linux console (black
64 - Linux: is meant to look good in a terminal like the Linux console (black
65 or very dark background).
65 or very dark background).
66
66
67 - LightBG: similar to Linux but swaps dark/light colors to be more readable
67 - LightBG: similar to Linux but swaps dark/light colors to be more readable
68 in light background terminals.
68 in light background terminals.
69
69
70 - Neutral: a neutral color scheme that should be readable on both light and
70 - Neutral: a neutral color scheme that should be readable on both light and
71 dark background
71 dark background
72
72
73 You can implement other color schemes easily, the syntax is fairly
73 You can implement other color schemes easily, the syntax is fairly
74 self-explanatory. Please send back new schemes you develop to the author for
74 self-explanatory. Please send back new schemes you develop to the author for
75 possible inclusion in future releases.
75 possible inclusion in future releases.
76
76
77 Inheritance diagram:
77 Inheritance diagram:
78
78
79 .. inheritance-diagram:: IPython.core.ultratb
79 .. inheritance-diagram:: IPython.core.ultratb
80 :parts: 3
80 :parts: 3
81 """
81 """
82
82
83 #*****************************************************************************
83 #*****************************************************************************
84 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
84 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
85 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
85 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
86 #
86 #
87 # Distributed under the terms of the BSD License. The full license is in
87 # Distributed under the terms of the BSD License. The full license is in
88 # the file COPYING, distributed as part of this software.
88 # the file COPYING, distributed as part of this software.
89 #*****************************************************************************
89 #*****************************************************************************
90
90
91
91
92 from collections.abc import Sequence
92 from collections.abc import Sequence
93 import functools
93 import functools
94 import inspect
94 import inspect
95 import linecache
95 import linecache
96 import pydoc
96 import pydoc
97 import sys
97 import sys
98 import time
98 import time
99 import traceback
99 import traceback
100 import types
100 import types
101 from types import TracebackType
101 from types import TracebackType
102 from typing import Any, List, Optional, Tuple
102 from typing import Any, List, Optional, Tuple
103
103
104 import stack_data
104 import stack_data
105 from pygments.formatters.terminal256 import Terminal256Formatter
105 from pygments.formatters.terminal256 import Terminal256Formatter
106 from pygments.styles import get_style_by_name
106 from pygments.styles import get_style_by_name
107
107
108 import IPython.utils.colorable as colorable
108 import IPython.utils.colorable as colorable
109 # IPython's own modules
109 # IPython's own modules
110 from IPython import get_ipython
110 from IPython import get_ipython
111 from IPython.core import debugger
111 from IPython.core import debugger
112 from IPython.core.display_trap import DisplayTrap
112 from IPython.core.display_trap import DisplayTrap
113 from IPython.core.excolors import exception_colors
113 from IPython.core.excolors import exception_colors
114 from IPython.utils import PyColorize
114 from IPython.utils import PyColorize
115 from IPython.utils import path as util_path
115 from IPython.utils import path as util_path
116 from IPython.utils import py3compat
116 from IPython.utils import py3compat
117 from IPython.utils.terminal import get_terminal_size
117 from IPython.utils.terminal import get_terminal_size
118
118
119 # Globals
119 # Globals
120 # amount of space to put line numbers before verbose tracebacks
120 # amount of space to put line numbers before verbose tracebacks
121 INDENT_SIZE = 8
121 INDENT_SIZE = 8
122
122
123 # Default color scheme. This is used, for example, by the traceback
123 # Default color scheme. This is used, for example, by the traceback
124 # formatter. When running in an actual IPython instance, the user's rc.colors
124 # formatter. When running in an actual IPython instance, the user's rc.colors
125 # value is used, but having a module global makes this functionality available
125 # value is used, but having a module global makes this functionality available
126 # to users of ultratb who are NOT running inside ipython.
126 # to users of ultratb who are NOT running inside ipython.
127 DEFAULT_SCHEME = 'NoColor'
127 DEFAULT_SCHEME = 'NoColor'
128 FAST_THRESHOLD = 10_000
128 FAST_THRESHOLD = 10_000
129
129
130 # ---------------------------------------------------------------------------
130 # ---------------------------------------------------------------------------
131 # Code begins
131 # Code begins
132
132
133 # Helper function -- largely belongs to VerboseTB, but we need the same
133 # Helper function -- largely belongs to VerboseTB, but we need the same
134 # functionality to produce a pseudo verbose TB for SyntaxErrors, so that they
134 # functionality to produce a pseudo verbose TB for SyntaxErrors, so that they
135 # can be recognized properly by ipython.el's py-traceback-line-re
135 # can be recognized properly by ipython.el's py-traceback-line-re
136 # (SyntaxErrors have to be treated specially because they have no traceback)
136 # (SyntaxErrors have to be treated specially because they have no traceback)
137
137
138
138
139 @functools.lru_cache()
139 @functools.lru_cache()
140 def count_lines_in_py_file(filename: str) -> int:
140 def count_lines_in_py_file(filename: str) -> int:
141 """
141 """
142 Given a filename, returns the number of lines in the file
142 Given a filename, returns the number of lines in the file
143 if it ends with the extension ".py". Otherwise, returns 0.
143 if it ends with the extension ".py". Otherwise, returns 0.
144 """
144 """
145 if not filename.endswith(".py"):
145 if not filename.endswith(".py"):
146 return 0
146 return 0
147 else:
147 else:
148 try:
148 try:
149 with open(filename, "r") as file:
149 with open(filename, "r") as file:
150 s = sum(1 for line in file)
150 s = sum(1 for line in file)
151 except UnicodeError:
151 except UnicodeError:
152 return 0
152 return 0
153 return s
153 return s
154
154
155 """
155 """
156 Given a frame object, returns the total number of lines in the file
156 Given a frame object, returns the total number of lines in the file
157 if the filename ends with the extension ".py". Otherwise, returns 0.
157 if the filename ends with the extension ".py". Otherwise, returns 0.
158 """
158 """
159
159
160
160
161 def get_line_number_of_frame(frame: types.FrameType) -> int:
161 def get_line_number_of_frame(frame: types.FrameType) -> int:
162 """
162 """
163 Given a frame object, returns the total number of lines in the file
163 Given a frame object, returns the total number of lines in the file
164 containing the frame's code object, or the number of lines in the
164 containing the frame's code object, or the number of lines in the
165 frame's source code if the file is not available.
165 frame's source code if the file is not available.
166
166
167 Parameters
167 Parameters
168 ----------
168 ----------
169 frame : FrameType
169 frame : FrameType
170 The frame object whose line number is to be determined.
170 The frame object whose line number is to be determined.
171
171
172 Returns
172 Returns
173 -------
173 -------
174 int
174 int
175 The total number of lines in the file containing the frame's
175 The total number of lines in the file containing the frame's
176 code object, or the number of lines in the frame's source code
176 code object, or the number of lines in the frame's source code
177 if the file is not available.
177 if the file is not available.
178 """
178 """
179 filename = frame.f_code.co_filename
179 filename = frame.f_code.co_filename
180 if filename is None:
180 if filename is None:
181 print("No file....")
181 print("No file....")
182 lines, first = inspect.getsourcelines(frame)
182 lines, first = inspect.getsourcelines(frame)
183 return first + len(lines)
183 return first + len(lines)
184 return count_lines_in_py_file(filename)
184 return count_lines_in_py_file(filename)
185
185
186
186
187 def _safe_string(value, what, func=str):
187 def _safe_string(value, what, func=str):
188 # Copied from cpython/Lib/traceback.py
188 # Copied from cpython/Lib/traceback.py
189 try:
189 try:
190 return func(value)
190 return func(value)
191 except Exception:
191 except:
192 return f"<{what} {func.__name__}() failed>"
192 return f"<{what} {func.__name__}() failed>"
193
193
194
194
195 def _format_traceback_lines(lines, Colors, has_colors: bool, lvals):
195 def _format_traceback_lines(lines, Colors, has_colors: bool, lvals):
196 """
196 """
197 Format tracebacks lines with pointing arrow, leading numbers...
197 Format tracebacks lines with pointing arrow, leading numbers...
198
198
199 Parameters
199 Parameters
200 ----------
200 ----------
201 lines : list[Line]
201 lines : list[Line]
202 Colors
202 Colors
203 ColorScheme used.
203 ColorScheme used.
204 lvals : str
204 lvals : str
205 Values of local variables, already colored, to inject just after the error line.
205 Values of local variables, already colored, to inject just after the error line.
206 """
206 """
207 numbers_width = INDENT_SIZE - 1
207 numbers_width = INDENT_SIZE - 1
208 res = []
208 res = []
209
209
210 for stack_line in lines:
210 for stack_line in lines:
211 if stack_line is stack_data.LINE_GAP:
211 if stack_line is stack_data.LINE_GAP:
212 res.append('%s (...)%s\n' % (Colors.linenoEm, Colors.Normal))
212 res.append('%s (...)%s\n' % (Colors.linenoEm, Colors.Normal))
213 continue
213 continue
214
214
215 line = stack_line.render(pygmented=has_colors).rstrip('\n') + '\n'
215 line = stack_line.render(pygmented=has_colors).rstrip('\n') + '\n'
216 lineno = stack_line.lineno
216 lineno = stack_line.lineno
217 if stack_line.is_current:
217 if stack_line.is_current:
218 # This is the line with the error
218 # This is the line with the error
219 pad = numbers_width - len(str(lineno))
219 pad = numbers_width - len(str(lineno))
220 num = '%s%s' % (debugger.make_arrow(pad), str(lineno))
220 num = '%s%s' % (debugger.make_arrow(pad), str(lineno))
221 start_color = Colors.linenoEm
221 start_color = Colors.linenoEm
222 else:
222 else:
223 num = '%*s' % (numbers_width, lineno)
223 num = '%*s' % (numbers_width, lineno)
224 start_color = Colors.lineno
224 start_color = Colors.lineno
225
225
226 line = '%s%s%s %s' % (start_color, num, Colors.Normal, line)
226 line = '%s%s%s %s' % (start_color, num, Colors.Normal, line)
227
227
228 res.append(line)
228 res.append(line)
229 if lvals and stack_line.is_current:
229 if lvals and stack_line.is_current:
230 res.append(lvals + '\n')
230 res.append(lvals + '\n')
231 return res
231 return res
232
232
233 def _simple_format_traceback_lines(lnum, index, lines, Colors, lvals, _line_format):
233 def _simple_format_traceback_lines(lnum, index, lines, Colors, lvals, _line_format):
234 """
234 """
235 Format tracebacks lines with pointing arrow, leading numbers...
235 Format tracebacks lines with pointing arrow, leading numbers...
236
236
237 Parameters
237 Parameters
238 ==========
238 ==========
239
239
240 lnum: int
240 lnum: int
241 number of the target line of code.
241 number of the target line of code.
242 index: int
242 index: int
243 which line in the list should be highlighted.
243 which line in the list should be highlighted.
244 lines: list[string]
244 lines: list[string]
245 Colors:
245 Colors:
246 ColorScheme used.
246 ColorScheme used.
247 lvals: bytes
247 lvals: bytes
248 Values of local variables, already colored, to inject just after the error line.
248 Values of local variables, already colored, to inject just after the error line.
249 _line_format: f (str) -> (str, bool)
249 _line_format: f (str) -> (str, bool)
250 return (colorized version of str, failure to do so)
250 return (colorized version of str, failure to do so)
251 """
251 """
252 numbers_width = INDENT_SIZE - 1
252 numbers_width = INDENT_SIZE - 1
253 res = []
253 res = []
254 for i, line in enumerate(lines, lnum - index):
254 for i, line in enumerate(lines, lnum - index):
255 # assert isinstance(line, str)
255 # assert isinstance(line, str)
256 line = py3compat.cast_unicode(line)
256 line = py3compat.cast_unicode(line)
257
257
258 new_line, err = _line_format(line, "str")
258 new_line, err = _line_format(line, "str")
259 if not err:
259 if not err:
260 line = new_line
260 line = new_line
261
261
262 if i == lnum:
262 if i == lnum:
263 # This is the line with the error
263 # This is the line with the error
264 pad = numbers_width - len(str(i))
264 pad = numbers_width - len(str(i))
265 num = "%s%s" % (debugger.make_arrow(pad), str(lnum))
265 num = "%s%s" % (debugger.make_arrow(pad), str(lnum))
266 line = "%s%s%s %s%s" % (
266 line = "%s%s%s %s%s" % (
267 Colors.linenoEm,
267 Colors.linenoEm,
268 num,
268 num,
269 Colors.line,
269 Colors.line,
270 line,
270 line,
271 Colors.Normal,
271 Colors.Normal,
272 )
272 )
273 else:
273 else:
274 num = "%*s" % (numbers_width, i)
274 num = "%*s" % (numbers_width, i)
275 line = "%s%s%s %s" % (Colors.lineno, num, Colors.Normal, line)
275 line = "%s%s%s %s" % (Colors.lineno, num, Colors.Normal, line)
276
276
277 res.append(line)
277 res.append(line)
278 if lvals and i == lnum:
278 if lvals and i == lnum:
279 res.append(lvals + "\n")
279 res.append(lvals + "\n")
280 return res
280 return res
281
281
282
282
283 def _format_filename(file, ColorFilename, ColorNormal, *, lineno=None):
283 def _format_filename(file, ColorFilename, ColorNormal, *, lineno=None):
284 """
284 """
285 Format filename lines with custom formatting from caching compiler or `File *.py` by default
285 Format filename lines with custom formatting from caching compiler or `File *.py` by default
286
286
287 Parameters
287 Parameters
288 ----------
288 ----------
289 file : str
289 file : str
290 ColorFilename
290 ColorFilename
291 ColorScheme's filename coloring to be used.
291 ColorScheme's filename coloring to be used.
292 ColorNormal
292 ColorNormal
293 ColorScheme's normal coloring to be used.
293 ColorScheme's normal coloring to be used.
294 """
294 """
295 ipinst = get_ipython()
295 ipinst = get_ipython()
296 if (
296 if (
297 ipinst is not None
297 ipinst is not None
298 and (data := ipinst.compile.format_code_name(file)) is not None
298 and (data := ipinst.compile.format_code_name(file)) is not None
299 ):
299 ):
300 label, name = data
300 label, name = data
301 if lineno is None:
301 if lineno is None:
302 tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}"
302 tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}"
303 else:
303 else:
304 tpl_link = (
304 tpl_link = (
305 f"{{label}} {ColorFilename}{{name}}, line {{lineno}}{ColorNormal}"
305 f"{{label}} {ColorFilename}{{name}}, line {{lineno}}{ColorNormal}"
306 )
306 )
307 else:
307 else:
308 label = "File"
308 label = "File"
309 name = util_path.compress_user(
309 name = util_path.compress_user(
310 py3compat.cast_unicode(file, util_path.fs_encoding)
310 py3compat.cast_unicode(file, util_path.fs_encoding)
311 )
311 )
312 if lineno is None:
312 if lineno is None:
313 tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}"
313 tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}"
314 else:
314 else:
315 # can we make this the more friendly ", line {{lineno}}", or do we need to preserve the formatting with the colon?
315 # can we make this the more friendly ", line {{lineno}}", or do we need to preserve the formatting with the colon?
316 tpl_link = f"{{label}} {ColorFilename}{{name}}:{{lineno}}{ColorNormal}"
316 tpl_link = f"{{label}} {ColorFilename}{{name}}:{{lineno}}{ColorNormal}"
317
317
318 return tpl_link.format(label=label, name=name, lineno=lineno)
318 return tpl_link.format(label=label, name=name, lineno=lineno)
319
319
320 #---------------------------------------------------------------------------
320 #---------------------------------------------------------------------------
321 # Module classes
321 # Module classes
322 class TBTools(colorable.Colorable):
322 class TBTools(colorable.Colorable):
323 """Basic tools used by all traceback printer classes."""
323 """Basic tools used by all traceback printer classes."""
324
324
325 # Number of frames to skip when reporting tracebacks
325 # Number of frames to skip when reporting tracebacks
326 tb_offset = 0
326 tb_offset = 0
327
327
328 def __init__(
328 def __init__(
329 self,
329 self,
330 color_scheme="NoColor",
330 color_scheme="NoColor",
331 call_pdb=False,
331 call_pdb=False,
332 ostream=None,
332 ostream=None,
333 parent=None,
333 parent=None,
334 config=None,
334 config=None,
335 *,
335 *,
336 debugger_cls=None,
336 debugger_cls=None,
337 ):
337 ):
338 # Whether to call the interactive pdb debugger after printing
338 # Whether to call the interactive pdb debugger after printing
339 # tracebacks or not
339 # tracebacks or not
340 super(TBTools, self).__init__(parent=parent, config=config)
340 super(TBTools, self).__init__(parent=parent, config=config)
341 self.call_pdb = call_pdb
341 self.call_pdb = call_pdb
342
342
343 # Output stream to write to. Note that we store the original value in
343 # Output stream to write to. Note that we store the original value in
344 # a private attribute and then make the public ostream a property, so
344 # a private attribute and then make the public ostream a property, so
345 # that we can delay accessing sys.stdout until runtime. The way
345 # that we can delay accessing sys.stdout until runtime. The way
346 # things are written now, the sys.stdout object is dynamically managed
346 # things are written now, the sys.stdout object is dynamically managed
347 # so a reference to it should NEVER be stored statically. This
347 # so a reference to it should NEVER be stored statically. This
348 # property approach confines this detail to a single location, and all
348 # property approach confines this detail to a single location, and all
349 # subclasses can simply access self.ostream for writing.
349 # subclasses can simply access self.ostream for writing.
350 self._ostream = ostream
350 self._ostream = ostream
351
351
352 # Create color table
352 # Create color table
353 self.color_scheme_table = exception_colors()
353 self.color_scheme_table = exception_colors()
354
354
355 self.set_colors(color_scheme)
355 self.set_colors(color_scheme)
356 self.old_scheme = color_scheme # save initial value for toggles
356 self.old_scheme = color_scheme # save initial value for toggles
357 self.debugger_cls = debugger_cls or debugger.Pdb
357 self.debugger_cls = debugger_cls or debugger.Pdb
358
358
359 if call_pdb:
359 if call_pdb:
360 self.pdb = self.debugger_cls()
360 self.pdb = self.debugger_cls()
361 else:
361 else:
362 self.pdb = None
362 self.pdb = None
363
363
364 def _get_ostream(self):
364 def _get_ostream(self):
365 """Output stream that exceptions are written to.
365 """Output stream that exceptions are written to.
366
366
367 Valid values are:
367 Valid values are:
368
368
369 - None: the default, which means that IPython will dynamically resolve
369 - None: the default, which means that IPython will dynamically resolve
370 to sys.stdout. This ensures compatibility with most tools, including
370 to sys.stdout. This ensures compatibility with most tools, including
371 Windows (where plain stdout doesn't recognize ANSI escapes).
371 Windows (where plain stdout doesn't recognize ANSI escapes).
372
372
373 - Any object with 'write' and 'flush' attributes.
373 - Any object with 'write' and 'flush' attributes.
374 """
374 """
375 return sys.stdout if self._ostream is None else self._ostream
375 return sys.stdout if self._ostream is None else self._ostream
376
376
377 def _set_ostream(self, val):
377 def _set_ostream(self, val):
378 assert val is None or (hasattr(val, 'write') and hasattr(val, 'flush'))
378 assert val is None or (hasattr(val, 'write') and hasattr(val, 'flush'))
379 self._ostream = val
379 self._ostream = val
380
380
381 ostream = property(_get_ostream, _set_ostream)
381 ostream = property(_get_ostream, _set_ostream)
382
382
383 @staticmethod
383 @staticmethod
384 def _get_chained_exception(exception_value):
384 def _get_chained_exception(exception_value):
385 cause = getattr(exception_value, "__cause__", None)
385 cause = getattr(exception_value, "__cause__", None)
386 if cause:
386 if cause:
387 return cause
387 return cause
388 if getattr(exception_value, "__suppress_context__", False):
388 if getattr(exception_value, "__suppress_context__", False):
389 return None
389 return None
390 return getattr(exception_value, "__context__", None)
390 return getattr(exception_value, "__context__", None)
391
391
392 def get_parts_of_chained_exception(
392 def get_parts_of_chained_exception(
393 self, evalue
393 self, evalue
394 ) -> Optional[Tuple[type, BaseException, TracebackType]]:
394 ) -> Optional[Tuple[type, BaseException, TracebackType]]:
395 chained_evalue = self._get_chained_exception(evalue)
395 chained_evalue = self._get_chained_exception(evalue)
396
396
397 if chained_evalue:
397 if chained_evalue:
398 return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__
398 return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__
399 return None
399 return None
400
400
401 def prepare_chained_exception_message(self, cause) -> List[Any]:
401 def prepare_chained_exception_message(self, cause) -> List[Any]:
402 direct_cause = "\nThe above exception was the direct cause of the following exception:\n"
402 direct_cause = "\nThe above exception was the direct cause of the following exception:\n"
403 exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n"
403 exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n"
404
404
405 if cause:
405 if cause:
406 message = [[direct_cause]]
406 message = [[direct_cause]]
407 else:
407 else:
408 message = [[exception_during_handling]]
408 message = [[exception_during_handling]]
409 return message
409 return message
410
410
411 @property
411 @property
412 def has_colors(self) -> bool:
412 def has_colors(self) -> bool:
413 return self.color_scheme_table.active_scheme_name.lower() != "nocolor"
413 return self.color_scheme_table.active_scheme_name.lower() != "nocolor"
414
414
415 def set_colors(self, *args, **kw):
415 def set_colors(self, *args, **kw):
416 """Shorthand access to the color table scheme selector method."""
416 """Shorthand access to the color table scheme selector method."""
417
417
418 # Set own color table
418 # Set own color table
419 self.color_scheme_table.set_active_scheme(*args, **kw)
419 self.color_scheme_table.set_active_scheme(*args, **kw)
420 # for convenience, set Colors to the active scheme
420 # for convenience, set Colors to the active scheme
421 self.Colors = self.color_scheme_table.active_colors
421 self.Colors = self.color_scheme_table.active_colors
422 # Also set colors of debugger
422 # Also set colors of debugger
423 if hasattr(self, 'pdb') and self.pdb is not None:
423 if hasattr(self, 'pdb') and self.pdb is not None:
424 self.pdb.set_colors(*args, **kw)
424 self.pdb.set_colors(*args, **kw)
425
425
426 def color_toggle(self):
426 def color_toggle(self):
427 """Toggle between the currently active color scheme and NoColor."""
427 """Toggle between the currently active color scheme and NoColor."""
428
428
429 if self.color_scheme_table.active_scheme_name == 'NoColor':
429 if self.color_scheme_table.active_scheme_name == 'NoColor':
430 self.color_scheme_table.set_active_scheme(self.old_scheme)
430 self.color_scheme_table.set_active_scheme(self.old_scheme)
431 self.Colors = self.color_scheme_table.active_colors
431 self.Colors = self.color_scheme_table.active_colors
432 else:
432 else:
433 self.old_scheme = self.color_scheme_table.active_scheme_name
433 self.old_scheme = self.color_scheme_table.active_scheme_name
434 self.color_scheme_table.set_active_scheme('NoColor')
434 self.color_scheme_table.set_active_scheme('NoColor')
435 self.Colors = self.color_scheme_table.active_colors
435 self.Colors = self.color_scheme_table.active_colors
436
436
437 def stb2text(self, stb):
437 def stb2text(self, stb):
438 """Convert a structured traceback (a list) to a string."""
438 """Convert a structured traceback (a list) to a string."""
439 return '\n'.join(stb)
439 return '\n'.join(stb)
440
440
441 def text(self, etype, value, tb, tb_offset: Optional[int] = None, context=5):
441 def text(self, etype, value, tb, tb_offset: Optional[int] = None, context=5):
442 """Return formatted traceback.
442 """Return formatted traceback.
443
443
444 Subclasses may override this if they add extra arguments.
444 Subclasses may override this if they add extra arguments.
445 """
445 """
446 tb_list = self.structured_traceback(etype, value, tb,
446 tb_list = self.structured_traceback(etype, value, tb,
447 tb_offset, context)
447 tb_offset, context)
448 return self.stb2text(tb_list)
448 return self.stb2text(tb_list)
449
449
450 def structured_traceback(
450 def structured_traceback(
451 self,
451 self,
452 etype: type,
452 etype: type,
453 evalue: Optional[BaseException],
453 evalue: Optional[BaseException],
454 etb: Optional[TracebackType] = None,
454 etb: Optional[TracebackType] = None,
455 tb_offset: Optional[int] = None,
455 tb_offset: Optional[int] = None,
456 number_of_lines_of_context: int = 5,
456 number_of_lines_of_context: int = 5,
457 ):
457 ):
458 """Return a list of traceback frames.
458 """Return a list of traceback frames.
459
459
460 Must be implemented by each class.
460 Must be implemented by each class.
461 """
461 """
462 raise NotImplementedError()
462 raise NotImplementedError()
463
463
464
464
465 #---------------------------------------------------------------------------
465 #---------------------------------------------------------------------------
466 class ListTB(TBTools):
466 class ListTB(TBTools):
467 """Print traceback information from a traceback list, with optional color.
467 """Print traceback information from a traceback list, with optional color.
468
468
469 Calling requires 3 arguments: (etype, evalue, elist)
469 Calling requires 3 arguments: (etype, evalue, elist)
470 as would be obtained by::
470 as would be obtained by::
471
471
472 etype, evalue, tb = sys.exc_info()
472 etype, evalue, tb = sys.exc_info()
473 if tb:
473 if tb:
474 elist = traceback.extract_tb(tb)
474 elist = traceback.extract_tb(tb)
475 else:
475 else:
476 elist = None
476 elist = None
477
477
478 It can thus be used by programs which need to process the traceback before
478 It can thus be used by programs which need to process the traceback before
479 printing (such as console replacements based on the code module from the
479 printing (such as console replacements based on the code module from the
480 standard library).
480 standard library).
481
481
482 Because they are meant to be called without a full traceback (only a
482 Because they are meant to be called without a full traceback (only a
483 list), instances of this class can't call the interactive pdb debugger."""
483 list), instances of this class can't call the interactive pdb debugger."""
484
484
485
485
486 def __call__(self, etype, value, elist):
486 def __call__(self, etype, value, elist):
487 self.ostream.flush()
487 self.ostream.flush()
488 self.ostream.write(self.text(etype, value, elist))
488 self.ostream.write(self.text(etype, value, elist))
489 self.ostream.write('\n')
489 self.ostream.write('\n')
490
490
491 def _extract_tb(self, tb):
491 def _extract_tb(self, tb):
492 if tb:
492 if tb:
493 return traceback.extract_tb(tb)
493 return traceback.extract_tb(tb)
494 else:
494 else:
495 return None
495 return None
496
496
497 def structured_traceback(
497 def structured_traceback(
498 self,
498 self,
499 etype: type,
499 etype: type,
500 evalue: Optional[BaseException],
500 evalue: Optional[BaseException],
501 etb: Optional[TracebackType] = None,
501 etb: Optional[TracebackType] = None,
502 tb_offset: Optional[int] = None,
502 tb_offset: Optional[int] = None,
503 context=5,
503 context=5,
504 ):
504 ):
505 """Return a color formatted string with the traceback info.
505 """Return a color formatted string with the traceback info.
506
506
507 Parameters
507 Parameters
508 ----------
508 ----------
509 etype : exception type
509 etype : exception type
510 Type of the exception raised.
510 Type of the exception raised.
511 evalue : object
511 evalue : object
512 Data stored in the exception
512 Data stored in the exception
513 etb : list | TracebackType | None
513 etb : list | TracebackType | None
514 If list: List of frames, see class docstring for details.
514 If list: List of frames, see class docstring for details.
515 If Traceback: Traceback of the exception.
515 If Traceback: Traceback of the exception.
516 tb_offset : int, optional
516 tb_offset : int, optional
517 Number of frames in the traceback to skip. If not given, the
517 Number of frames in the traceback to skip. If not given, the
518 instance evalue is used (set in constructor).
518 instance evalue is used (set in constructor).
519 context : int, optional
519 context : int, optional
520 Number of lines of context information to print.
520 Number of lines of context information to print.
521
521
522 Returns
522 Returns
523 -------
523 -------
524 String with formatted exception.
524 String with formatted exception.
525 """
525 """
526 # This is a workaround to get chained_exc_ids in recursive calls
526 # This is a workaround to get chained_exc_ids in recursive calls
527 # etb should not be a tuple if structured_traceback is not recursive
527 # etb should not be a tuple if structured_traceback is not recursive
528 if isinstance(etb, tuple):
528 if isinstance(etb, tuple):
529 etb, chained_exc_ids = etb
529 etb, chained_exc_ids = etb
530 else:
530 else:
531 chained_exc_ids = set()
531 chained_exc_ids = set()
532
532
533 if isinstance(etb, list):
533 if isinstance(etb, list):
534 elist = etb
534 elist = etb
535 elif etb is not None:
535 elif etb is not None:
536 elist = self._extract_tb(etb)
536 elist = self._extract_tb(etb)
537 else:
537 else:
538 elist = []
538 elist = []
539 tb_offset = self.tb_offset if tb_offset is None else tb_offset
539 tb_offset = self.tb_offset if tb_offset is None else tb_offset
540 assert isinstance(tb_offset, int)
540 assert isinstance(tb_offset, int)
541 Colors = self.Colors
541 Colors = self.Colors
542 out_list = []
542 out_list = []
543 if elist:
543 if elist:
544
544
545 if tb_offset and len(elist) > tb_offset:
545 if tb_offset and len(elist) > tb_offset:
546 elist = elist[tb_offset:]
546 elist = elist[tb_offset:]
547
547
548 out_list.append('Traceback %s(most recent call last)%s:' %
548 out_list.append('Traceback %s(most recent call last)%s:' %
549 (Colors.normalEm, Colors.Normal) + '\n')
549 (Colors.normalEm, Colors.Normal) + '\n')
550 out_list.extend(self._format_list(elist))
550 out_list.extend(self._format_list(elist))
551 # The exception info should be a single entry in the list.
551 # The exception info should be a single entry in the list.
552 lines = ''.join(self._format_exception_only(etype, evalue))
552 lines = ''.join(self._format_exception_only(etype, evalue))
553 out_list.append(lines)
553 out_list.append(lines)
554
554
555 # Find chained exceptions if we have a traceback (not for exception-only mode)
555 # Find chained exceptions if we have a traceback (not for exception-only mode)
556 if etb is not None:
556 if etb is not None:
557 exception = self.get_parts_of_chained_exception(evalue)
557 exception = self.get_parts_of_chained_exception(evalue)
558
558
559 if exception and (id(exception[1]) not in chained_exc_ids):
559 if exception and (id(exception[1]) not in chained_exc_ids):
560 chained_exception_message = (
560 chained_exception_message = (
561 self.prepare_chained_exception_message(evalue.__cause__)[0]
561 self.prepare_chained_exception_message(evalue.__cause__)[0]
562 if evalue is not None
562 if evalue is not None
563 else ""
563 else ""
564 )
564 )
565 etype, evalue, etb = exception
565 etype, evalue, etb = exception
566 # Trace exception to avoid infinite 'cause' loop
566 # Trace exception to avoid infinite 'cause' loop
567 chained_exc_ids.add(id(exception[1]))
567 chained_exc_ids.add(id(exception[1]))
568 chained_exceptions_tb_offset = 0
568 chained_exceptions_tb_offset = 0
569 out_list = (
569 out_list = (
570 self.structured_traceback(
570 self.structured_traceback(
571 etype,
571 etype,
572 evalue,
572 evalue,
573 (etb, chained_exc_ids), # type: ignore
573 (etb, chained_exc_ids), # type: ignore
574 chained_exceptions_tb_offset,
574 chained_exceptions_tb_offset,
575 context,
575 context,
576 )
576 )
577 + chained_exception_message
577 + chained_exception_message
578 + out_list
578 + out_list
579 )
579 )
580
580
581 return out_list
581 return out_list
582
582
583 def _format_list(self, extracted_list):
583 def _format_list(self, extracted_list):
584 """Format a list of traceback entry tuples for printing.
584 """Format a list of traceback entry tuples for printing.
585
585
586 Given a list of tuples as returned by extract_tb() or
586 Given a list of tuples as returned by extract_tb() or
587 extract_stack(), return a list of strings ready for printing.
587 extract_stack(), return a list of strings ready for printing.
588 Each string in the resulting list corresponds to the item with the
588 Each string in the resulting list corresponds to the item with the
589 same index in the argument list. Each string ends in a newline;
589 same index in the argument list. Each string ends in a newline;
590 the strings may contain internal newlines as well, for those items
590 the strings may contain internal newlines as well, for those items
591 whose source text line is not None.
591 whose source text line is not None.
592
592
593 Lifted almost verbatim from traceback.py
593 Lifted almost verbatim from traceback.py
594 """
594 """
595
595
596 Colors = self.Colors
596 Colors = self.Colors
597 output_list = []
597 output_list = []
598 for ind, (filename, lineno, name, line) in enumerate(extracted_list):
598 for ind, (filename, lineno, name, line) in enumerate(extracted_list):
599 normalCol, nameCol, fileCol, lineCol = (
599 normalCol, nameCol, fileCol, lineCol = (
600 # Emphasize the last entry
600 # Emphasize the last entry
601 (Colors.normalEm, Colors.nameEm, Colors.filenameEm, Colors.line)
601 (Colors.normalEm, Colors.nameEm, Colors.filenameEm, Colors.line)
602 if ind == len(extracted_list) - 1
602 if ind == len(extracted_list) - 1
603 else (Colors.Normal, Colors.name, Colors.filename, "")
603 else (Colors.Normal, Colors.name, Colors.filename, "")
604 )
604 )
605
605
606 fns = _format_filename(filename, fileCol, normalCol, lineno=lineno)
606 fns = _format_filename(filename, fileCol, normalCol, lineno=lineno)
607 item = f"{normalCol} {fns}"
607 item = f"{normalCol} {fns}"
608
608
609 if name != "<module>":
609 if name != "<module>":
610 item += f" in {nameCol}{name}{normalCol}\n"
610 item += f" in {nameCol}{name}{normalCol}\n"
611 else:
611 else:
612 item += "\n"
612 item += "\n"
613 if line:
613 if line:
614 item += f"{lineCol} {line.strip()}{normalCol}\n"
614 item += f"{lineCol} {line.strip()}{normalCol}\n"
615 output_list.append(item)
615 output_list.append(item)
616
616
617 return output_list
617 return output_list
618
618
619 def _format_exception_only(self, etype, value):
619 def _format_exception_only(self, etype, value):
620 """Format the exception part of a traceback.
620 """Format the exception part of a traceback.
621
621
622 The arguments are the exception type and value such as given by
622 The arguments are the exception type and value such as given by
623 sys.exc_info()[:2]. The return value is a list of strings, each ending
623 sys.exc_info()[:2]. The return value is a list of strings, each ending
624 in a newline. Normally, the list contains a single string; however,
624 in a newline. Normally, the list contains a single string; however,
625 for SyntaxError exceptions, it contains several lines that (when
625 for SyntaxError exceptions, it contains several lines that (when
626 printed) display detailed information about where the syntax error
626 printed) display detailed information about where the syntax error
627 occurred. The message indicating which exception occurred is the
627 occurred. The message indicating which exception occurred is the
628 always last string in the list.
628 always last string in the list.
629
629
630 Also lifted nearly verbatim from traceback.py
630 Also lifted nearly verbatim from traceback.py
631 """
631 """
632 have_filedata = False
632 have_filedata = False
633 Colors = self.Colors
633 Colors = self.Colors
634 output_list = []
634 output_list = []
635 stype = py3compat.cast_unicode(Colors.excName + etype.__name__ + Colors.Normal)
635 stype = py3compat.cast_unicode(Colors.excName + etype.__name__ + Colors.Normal)
636 if value is None:
636 if value is None:
637 # Not sure if this can still happen in Python 2.6 and above
637 # Not sure if this can still happen in Python 2.6 and above
638 output_list.append(stype + "\n")
638 output_list.append(stype + "\n")
639 else:
639 else:
640 if issubclass(etype, SyntaxError):
640 if issubclass(etype, SyntaxError):
641 have_filedata = True
641 have_filedata = True
642 if not value.filename: value.filename = "<string>"
642 if not value.filename: value.filename = "<string>"
643 if value.lineno:
643 if value.lineno:
644 lineno = value.lineno
644 lineno = value.lineno
645 textline = linecache.getline(value.filename, value.lineno)
645 textline = linecache.getline(value.filename, value.lineno)
646 else:
646 else:
647 lineno = "unknown"
647 lineno = "unknown"
648 textline = ""
648 textline = ""
649 output_list.append(
649 output_list.append(
650 "%s %s%s\n"
650 "%s %s%s\n"
651 % (
651 % (
652 Colors.normalEm,
652 Colors.normalEm,
653 _format_filename(
653 _format_filename(
654 value.filename,
654 value.filename,
655 Colors.filenameEm,
655 Colors.filenameEm,
656 Colors.normalEm,
656 Colors.normalEm,
657 lineno=(None if lineno == "unknown" else lineno),
657 lineno=(None if lineno == "unknown" else lineno),
658 ),
658 ),
659 Colors.Normal,
659 Colors.Normal,
660 )
660 )
661 )
661 )
662 if textline == "":
662 if textline == "":
663 textline = py3compat.cast_unicode(value.text, "utf-8")
663 textline = py3compat.cast_unicode(value.text, "utf-8")
664
664
665 if textline is not None:
665 if textline is not None:
666 i = 0
666 i = 0
667 while i < len(textline) and textline[i].isspace():
667 while i < len(textline) and textline[i].isspace():
668 i += 1
668 i += 1
669 output_list.append(
669 output_list.append(
670 "%s %s%s\n" % (Colors.line, textline.strip(), Colors.Normal)
670 "%s %s%s\n" % (Colors.line, textline.strip(), Colors.Normal)
671 )
671 )
672 if value.offset is not None:
672 if value.offset is not None:
673 s = ' '
673 s = ' '
674 for c in textline[i:value.offset - 1]:
674 for c in textline[i:value.offset - 1]:
675 if c.isspace():
675 if c.isspace():
676 s += c
676 s += c
677 else:
677 else:
678 s += " "
678 s += " "
679 output_list.append(
679 output_list.append(
680 "%s%s^%s\n" % (Colors.caret, s, Colors.Normal)
680 "%s%s^%s\n" % (Colors.caret, s, Colors.Normal)
681 )
681 )
682
682
683 try:
683 try:
684 s = value.msg
684 s = value.msg
685 except Exception:
685 except Exception:
686 s = self._some_str(value)
686 s = self._some_str(value)
687 if s:
687 if s:
688 output_list.append(
688 output_list.append(
689 "%s%s:%s %s\n" % (stype, Colors.excName, Colors.Normal, s)
689 "%s%s:%s %s\n" % (stype, Colors.excName, Colors.Normal, s)
690 )
690 )
691 else:
691 else:
692 output_list.append("%s\n" % stype)
692 output_list.append("%s\n" % stype)
693
693
694 # PEP-678 notes
694 # PEP-678 notes
695 output_list.extend(f"{x}\n" for x in getattr(value, "__notes__", []))
695 output_list.extend(f"{x}\n" for x in getattr(value, "__notes__", []))
696
696
697 # sync with user hooks
697 # sync with user hooks
698 if have_filedata:
698 if have_filedata:
699 ipinst = get_ipython()
699 ipinst = get_ipython()
700 if ipinst is not None:
700 if ipinst is not None:
701 ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0)
701 ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0)
702
702
703 return output_list
703 return output_list
704
704
705 def get_exception_only(self, etype, value):
705 def get_exception_only(self, etype, value):
706 """Only print the exception type and message, without a traceback.
706 """Only print the exception type and message, without a traceback.
707
707
708 Parameters
708 Parameters
709 ----------
709 ----------
710 etype : exception type
710 etype : exception type
711 value : exception value
711 value : exception value
712 """
712 """
713 return ListTB.structured_traceback(self, etype, value)
713 return ListTB.structured_traceback(self, etype, value)
714
714
715 def show_exception_only(self, etype, evalue):
715 def show_exception_only(self, etype, evalue):
716 """Only print the exception type and message, without a traceback.
716 """Only print the exception type and message, without a traceback.
717
717
718 Parameters
718 Parameters
719 ----------
719 ----------
720 etype : exception type
720 etype : exception type
721 evalue : exception value
721 evalue : exception value
722 """
722 """
723 # This method needs to use __call__ from *this* class, not the one from
723 # This method needs to use __call__ from *this* class, not the one from
724 # a subclass whose signature or behavior may be different
724 # a subclass whose signature or behavior may be different
725 ostream = self.ostream
725 ostream = self.ostream
726 ostream.flush()
726 ostream.flush()
727 ostream.write('\n'.join(self.get_exception_only(etype, evalue)))
727 ostream.write('\n'.join(self.get_exception_only(etype, evalue)))
728 ostream.flush()
728 ostream.flush()
729
729
730 def _some_str(self, value):
730 def _some_str(self, value):
731 # Lifted from traceback.py
731 # Lifted from traceback.py
732 try:
732 try:
733 return str(value)
733 return py3compat.cast_unicode(str(value))
734 except:
734 except:
735 return "<unprintable %s object>" % type(value).__name__
735 return u'<unprintable %s object>' % type(value).__name__
736
736
737
737
738 class FrameInfo:
738 class FrameInfo:
739 """
739 """
740 Mirror of stack data's FrameInfo, but so that we can bypass highlighting on
740 Mirror of stack data's FrameInfo, but so that we can bypass highlighting on
741 really long frames.
741 really long frames.
742 """
742 """
743
743
744 description: Optional[str]
744 description: Optional[str]
745 filename: Optional[str]
745 filename: Optional[str]
746 lineno: Tuple[int]
746 lineno: Tuple[int]
747 # number of context lines to use
747 # number of context lines to use
748 context: Optional[int]
748 context: Optional[int]
749 raw_lines: List[str]
749 raw_lines: List[str]
750
750
751 @classmethod
751 @classmethod
752 def _from_stack_data_FrameInfo(cls, frame_info):
752 def _from_stack_data_FrameInfo(cls, frame_info):
753 return cls(
753 return cls(
754 getattr(frame_info, "description", None),
754 getattr(frame_info, "description", None),
755 getattr(frame_info, "filename", None), # type: ignore[arg-type]
755 getattr(frame_info, "filename", None), # type: ignore[arg-type]
756 getattr(frame_info, "lineno", None), # type: ignore[arg-type]
756 getattr(frame_info, "lineno", None), # type: ignore[arg-type]
757 getattr(frame_info, "frame", None),
757 getattr(frame_info, "frame", None),
758 getattr(frame_info, "code", None),
758 getattr(frame_info, "code", None),
759 sd=frame_info,
759 sd=frame_info,
760 context=None,
760 context=None,
761 )
761 )
762
762
763 def __init__(
763 def __init__(
764 self,
764 self,
765 description: Optional[str],
765 description: Optional[str],
766 filename: str,
766 filename: str,
767 lineno: Tuple[int],
767 lineno: Tuple[int],
768 frame,
768 frame,
769 code,
769 code,
770 *,
770 *,
771 sd=None,
771 sd=None,
772 context=None,
772 context=None,
773 ):
773 ):
774 self.description = description
774 self.description = description
775 self.filename = filename
775 self.filename = filename
776 self.lineno = lineno
776 self.lineno = lineno
777 self.frame = frame
777 self.frame = frame
778 self.code = code
778 self.code = code
779 self._sd = sd
779 self._sd = sd
780 self.context = context
780 self.context = context
781
781
782 # self.lines = []
782 # self.lines = []
783 if sd is None:
783 if sd is None:
784 try:
784 try:
785 # return a list of source lines and a starting line number
785 # return a list of source lines and a starting line number
786 self.raw_lines = inspect.getsourcelines(frame)[0]
786 self.raw_lines = inspect.getsourcelines(frame)[0]
787 except OSError:
787 except OSError:
788 self.raw_lines = [
788 self.raw_lines = [
789 "'Could not get source, probably due dynamically evaluated source code.'"
789 "'Could not get source, probably due dynamically evaluated source code.'"
790 ]
790 ]
791
791
792 @property
792 @property
793 def variables_in_executing_piece(self):
793 def variables_in_executing_piece(self):
794 if self._sd:
794 if self._sd:
795 return self._sd.variables_in_executing_piece
795 return self._sd.variables_in_executing_piece
796 else:
796 else:
797 return []
797 return []
798
798
799 @property
799 @property
800 def lines(self):
800 def lines(self):
801 from executing.executing import NotOneValueFound
801 from executing.executing import NotOneValueFound
802
802
803 try:
803 try:
804 return self._sd.lines
804 return self._sd.lines
805 except NotOneValueFound:
805 except NotOneValueFound:
806
806
807 class Dummy:
807 class Dummy:
808 lineno = 0
808 lineno = 0
809 is_current = False
809 is_current = False
810
810
811 def render(self, *, pygmented):
811 def render(self, *, pygmented):
812 return "<Error retrieving source code with stack_data see ipython/ipython#13598>"
812 return "<Error retrieving source code with stack_data see ipython/ipython#13598>"
813
813
814 return [Dummy()]
814 return [Dummy()]
815
815
816 @property
816 @property
817 def executing(self):
817 def executing(self):
818 if self._sd:
818 if self._sd:
819 return self._sd.executing
819 return self._sd.executing
820 else:
820 else:
821 return None
821 return None
822
822
823
823
824 # ----------------------------------------------------------------------------
824 # ----------------------------------------------------------------------------
825 class VerboseTB(TBTools):
825 class VerboseTB(TBTools):
826 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
826 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
827 of HTML. Requires inspect and pydoc. Crazy, man.
827 of HTML. Requires inspect and pydoc. Crazy, man.
828
828
829 Modified version which optionally strips the topmost entries from the
829 Modified version which optionally strips the topmost entries from the
830 traceback, to be used with alternate interpreters (because their own code
830 traceback, to be used with alternate interpreters (because their own code
831 would appear in the traceback)."""
831 would appear in the traceback)."""
832
832
833 tb_highlight = "bg:ansiyellow"
833 tb_highlight = "bg:ansiyellow"
834 tb_highlight_style = "default"
834 tb_highlight_style = "default"
835
835
836 def __init__(
836 def __init__(
837 self,
837 self,
838 color_scheme: str = "Linux",
838 color_scheme: str = "Linux",
839 call_pdb: bool = False,
839 call_pdb: bool = False,
840 ostream=None,
840 ostream=None,
841 tb_offset: int = 0,
841 tb_offset: int = 0,
842 long_header: bool = False,
842 long_header: bool = False,
843 include_vars: bool = True,
843 include_vars: bool = True,
844 check_cache=None,
844 check_cache=None,
845 debugger_cls=None,
845 debugger_cls=None,
846 parent=None,
846 parent=None,
847 config=None,
847 config=None,
848 ):
848 ):
849 """Specify traceback offset, headers and color scheme.
849 """Specify traceback offset, headers and color scheme.
850
850
851 Define how many frames to drop from the tracebacks. Calling it with
851 Define how many frames to drop from the tracebacks. Calling it with
852 tb_offset=1 allows use of this handler in interpreters which will have
852 tb_offset=1 allows use of this handler in interpreters which will have
853 their own code at the top of the traceback (VerboseTB will first
853 their own code at the top of the traceback (VerboseTB will first
854 remove that frame before printing the traceback info)."""
854 remove that frame before printing the traceback info)."""
855 TBTools.__init__(
855 TBTools.__init__(
856 self,
856 self,
857 color_scheme=color_scheme,
857 color_scheme=color_scheme,
858 call_pdb=call_pdb,
858 call_pdb=call_pdb,
859 ostream=ostream,
859 ostream=ostream,
860 parent=parent,
860 parent=parent,
861 config=config,
861 config=config,
862 debugger_cls=debugger_cls,
862 debugger_cls=debugger_cls,
863 )
863 )
864 self.tb_offset = tb_offset
864 self.tb_offset = tb_offset
865 self.long_header = long_header
865 self.long_header = long_header
866 self.include_vars = include_vars
866 self.include_vars = include_vars
867 # By default we use linecache.checkcache, but the user can provide a
867 # By default we use linecache.checkcache, but the user can provide a
868 # different check_cache implementation. This was formerly used by the
868 # different check_cache implementation. This was formerly used by the
869 # IPython kernel for interactive code, but is no longer necessary.
869 # IPython kernel for interactive code, but is no longer necessary.
870 if check_cache is None:
870 if check_cache is None:
871 check_cache = linecache.checkcache
871 check_cache = linecache.checkcache
872 self.check_cache = check_cache
872 self.check_cache = check_cache
873
873
874 self.skip_hidden = True
874 self.skip_hidden = True
875
875
876 def format_record(self, frame_info: FrameInfo):
876 def format_record(self, frame_info: FrameInfo):
877 """Format a single stack frame"""
877 """Format a single stack frame"""
878 assert isinstance(frame_info, FrameInfo)
878 assert isinstance(frame_info, FrameInfo)
879 Colors = self.Colors # just a shorthand + quicker name lookup
879 Colors = self.Colors # just a shorthand + quicker name lookup
880 ColorsNormal = Colors.Normal # used a lot
880 ColorsNormal = Colors.Normal # used a lot
881
881
882 if isinstance(frame_info._sd, stack_data.RepeatedFrames):
882 if isinstance(frame_info._sd, stack_data.RepeatedFrames):
883 return ' %s[... skipping similar frames: %s]%s\n' % (
883 return ' %s[... skipping similar frames: %s]%s\n' % (
884 Colors.excName, frame_info.description, ColorsNormal)
884 Colors.excName, frame_info.description, ColorsNormal)
885
885
886 indent = " " * INDENT_SIZE
886 indent = " " * INDENT_SIZE
887 em_normal = "%s\n%s%s" % (Colors.valEm, indent, ColorsNormal)
887 em_normal = "%s\n%s%s" % (Colors.valEm, indent, ColorsNormal)
888 tpl_call = f"in {Colors.vName}{{file}}{Colors.valEm}{{scope}}{ColorsNormal}"
888 tpl_call = f"in {Colors.vName}{{file}}{Colors.valEm}{{scope}}{ColorsNormal}"
889 tpl_call_fail = "in %s%%s%s(***failed resolving arguments***)%s" % (
889 tpl_call_fail = "in %s%%s%s(***failed resolving arguments***)%s" % (
890 Colors.vName,
890 Colors.vName,
891 Colors.valEm,
891 Colors.valEm,
892 ColorsNormal,
892 ColorsNormal,
893 )
893 )
894 tpl_name_val = "%%s %s= %%s%s" % (Colors.valEm, ColorsNormal)
894 tpl_name_val = "%%s %s= %%s%s" % (Colors.valEm, ColorsNormal)
895
895
896 link = _format_filename(
896 link = _format_filename(
897 frame_info.filename,
897 frame_info.filename,
898 Colors.filenameEm,
898 Colors.filenameEm,
899 ColorsNormal,
899 ColorsNormal,
900 lineno=frame_info.lineno,
900 lineno=frame_info.lineno,
901 )
901 )
902 args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame)
902 args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame)
903 if frame_info.executing is not None:
903 if frame_info.executing is not None:
904 func = frame_info.executing.code_qualname()
904 func = frame_info.executing.code_qualname()
905 else:
905 else:
906 func = "?"
906 func = "?"
907 if func == "<module>":
907 if func == "<module>":
908 call = ""
908 call = ""
909 else:
909 else:
910 # Decide whether to include variable details or not
910 # Decide whether to include variable details or not
911 var_repr = eqrepr if self.include_vars else nullrepr
911 var_repr = eqrepr if self.include_vars else nullrepr
912 try:
912 try:
913 scope = inspect.formatargvalues(
913 scope = inspect.formatargvalues(
914 args, varargs, varkw, locals_, formatvalue=var_repr
914 args, varargs, varkw, locals_, formatvalue=var_repr
915 )
915 )
916 call = tpl_call.format(file=func, scope=scope)
916 call = tpl_call.format(file=func, scope=scope)
917 except KeyError:
917 except KeyError:
918 # This happens in situations like errors inside generator
918 # This happens in situations like errors inside generator
919 # expressions, where local variables are listed in the
919 # expressions, where local variables are listed in the
920 # line, but can't be extracted from the frame. I'm not
920 # line, but can't be extracted from the frame. I'm not
921 # 100% sure this isn't actually a bug in inspect itself,
921 # 100% sure this isn't actually a bug in inspect itself,
922 # but since there's no info for us to compute with, the
922 # but since there's no info for us to compute with, the
923 # best we can do is report the failure and move on. Here
923 # best we can do is report the failure and move on. Here
924 # we must *not* call any traceback construction again,
924 # we must *not* call any traceback construction again,
925 # because that would mess up use of %debug later on. So we
925 # because that would mess up use of %debug later on. So we
926 # simply report the failure and move on. The only
926 # simply report the failure and move on. The only
927 # limitation will be that this frame won't have locals
927 # limitation will be that this frame won't have locals
928 # listed in the call signature. Quite subtle problem...
928 # listed in the call signature. Quite subtle problem...
929 # I can't think of a good way to validate this in a unit
929 # I can't think of a good way to validate this in a unit
930 # test, but running a script consisting of:
930 # test, but running a script consisting of:
931 # dict( (k,v.strip()) for (k,v) in range(10) )
931 # dict( (k,v.strip()) for (k,v) in range(10) )
932 # will illustrate the error, if this exception catch is
932 # will illustrate the error, if this exception catch is
933 # disabled.
933 # disabled.
934 call = tpl_call_fail % func
934 call = tpl_call_fail % func
935
935
936 lvals = ''
936 lvals = ''
937 lvals_list = []
937 lvals_list = []
938 if self.include_vars:
938 if self.include_vars:
939 try:
939 try:
940 # we likely want to fix stackdata at some point, but
940 # we likely want to fix stackdata at some point, but
941 # still need a workaround.
941 # still need a workaround.
942 fibp = frame_info.variables_in_executing_piece
942 fibp = frame_info.variables_in_executing_piece
943 for var in fibp:
943 for var in fibp:
944 lvals_list.append(tpl_name_val % (var.name, repr(var.value)))
944 lvals_list.append(tpl_name_val % (var.name, repr(var.value)))
945 except Exception:
945 except Exception:
946 lvals_list.append(
946 lvals_list.append(
947 "Exception trying to inspect frame. No more locals available."
947 "Exception trying to inspect frame. No more locals available."
948 )
948 )
949 if lvals_list:
949 if lvals_list:
950 lvals = '%s%s' % (indent, em_normal.join(lvals_list))
950 lvals = '%s%s' % (indent, em_normal.join(lvals_list))
951
951
952 result = f'{link}{", " if call else ""}{call}\n'
952 result = f'{link}{", " if call else ""}{call}\n'
953 if frame_info._sd is None:
953 if frame_info._sd is None:
954 # fast fallback if file is too long
954 # fast fallback if file is too long
955 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
955 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
956 link = tpl_link % util_path.compress_user(frame_info.filename)
956 link = tpl_link % util_path.compress_user(frame_info.filename)
957 level = "%s %s\n" % (link, call)
957 level = "%s %s\n" % (link, call)
958 _line_format = PyColorize.Parser(
958 _line_format = PyColorize.Parser(
959 style=self.color_scheme_table.active_scheme_name, parent=self
959 style=self.color_scheme_table.active_scheme_name, parent=self
960 ).format2
960 ).format2
961 first_line = frame_info.code.co_firstlineno
961 first_line = frame_info.code.co_firstlineno
962 current_line = frame_info.lineno[0]
962 current_line = frame_info.lineno[0]
963 raw_lines = frame_info.raw_lines
963 raw_lines = frame_info.raw_lines
964 index = current_line - first_line
964 index = current_line - first_line
965
965
966 if index >= frame_info.context:
966 if index >= frame_info.context:
967 start = max(index - frame_info.context, 0)
967 start = max(index - frame_info.context, 0)
968 stop = index + frame_info.context
968 stop = index + frame_info.context
969 index = frame_info.context
969 index = frame_info.context
970 else:
970 else:
971 start = 0
971 start = 0
972 stop = index + frame_info.context
972 stop = index + frame_info.context
973 raw_lines = raw_lines[start:stop]
973 raw_lines = raw_lines[start:stop]
974
974
975 return "%s%s" % (
975 return "%s%s" % (
976 level,
976 level,
977 "".join(
977 "".join(
978 _simple_format_traceback_lines(
978 _simple_format_traceback_lines(
979 current_line,
979 current_line,
980 index,
980 index,
981 raw_lines,
981 raw_lines,
982 Colors,
982 Colors,
983 lvals,
983 lvals,
984 _line_format,
984 _line_format,
985 )
985 )
986 ),
986 ),
987 )
987 )
988 # result += "\n".join(frame_info.raw_lines)
988 # result += "\n".join(frame_info.raw_lines)
989 else:
989 else:
990 result += "".join(
990 result += "".join(
991 _format_traceback_lines(
991 _format_traceback_lines(
992 frame_info.lines, Colors, self.has_colors, lvals
992 frame_info.lines, Colors, self.has_colors, lvals
993 )
993 )
994 )
994 )
995 return result
995 return result
996
996
997 def prepare_header(self, etype: str, long_version: bool = False):
997 def prepare_header(self, etype: str, long_version: bool = False):
998 colors = self.Colors # just a shorthand + quicker name lookup
998 colors = self.Colors # just a shorthand + quicker name lookup
999 colorsnormal = colors.Normal # used a lot
999 colorsnormal = colors.Normal # used a lot
1000 exc = '%s%s%s' % (colors.excName, etype, colorsnormal)
1000 exc = '%s%s%s' % (colors.excName, etype, colorsnormal)
1001 width = min(75, get_terminal_size()[0])
1001 width = min(75, get_terminal_size()[0])
1002 if long_version:
1002 if long_version:
1003 # Header with the exception type, python version, and date
1003 # Header with the exception type, python version, and date
1004 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
1004 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
1005 date = time.ctime(time.time())
1005 date = time.ctime(time.time())
1006
1006
1007 head = "%s%s%s\n%s%s%s\n%s" % (
1007 head = "%s%s%s\n%s%s%s\n%s" % (
1008 colors.topline,
1008 colors.topline,
1009 "-" * width,
1009 "-" * width,
1010 colorsnormal,
1010 colorsnormal,
1011 exc,
1011 exc,
1012 " " * (width - len(etype) - len(pyver)),
1012 " " * (width - len(etype) - len(pyver)),
1013 pyver,
1013 pyver,
1014 date.rjust(width),
1014 date.rjust(width),
1015 )
1015 )
1016 head += (
1016 head += (
1017 "\nA problem occurred executing Python code. Here is the sequence of function"
1017 "\nA problem occurred executing Python code. Here is the sequence of function"
1018 "\ncalls leading up to the error, with the most recent (innermost) call last."
1018 "\ncalls leading up to the error, with the most recent (innermost) call last."
1019 )
1019 )
1020 else:
1020 else:
1021 # Simplified header
1021 # Simplified header
1022 head = "%s%s" % (
1022 head = "%s%s" % (
1023 exc,
1023 exc,
1024 "Traceback (most recent call last)".rjust(width - len(etype)),
1024 "Traceback (most recent call last)".rjust(width - len(etype)),
1025 )
1025 )
1026
1026
1027 return head
1027 return head
1028
1028
1029 def format_exception(self, etype, evalue):
1029 def format_exception(self, etype, evalue):
1030 colors = self.Colors # just a shorthand + quicker name lookup
1030 colors = self.Colors # just a shorthand + quicker name lookup
1031 colorsnormal = colors.Normal # used a lot
1031 colorsnormal = colors.Normal # used a lot
1032 # Get (safely) a string form of the exception info
1032 # Get (safely) a string form of the exception info
1033 try:
1033 try:
1034 etype_str, evalue_str = map(str, (etype, evalue))
1034 etype_str, evalue_str = map(str, (etype, evalue))
1035 except:
1035 except:
1036 # User exception is improperly defined.
1036 # User exception is improperly defined.
1037 etype, evalue = str, sys.exc_info()[:2]
1037 etype, evalue = str, sys.exc_info()[:2]
1038 etype_str, evalue_str = map(str, (etype, evalue))
1038 etype_str, evalue_str = map(str, (etype, evalue))
1039
1039
1040 # PEP-678 notes
1040 # PEP-678 notes
1041 notes = getattr(evalue, "__notes__", [])
1041 notes = getattr(evalue, "__notes__", [])
1042 if not isinstance(notes, Sequence) or isinstance(notes, (str, bytes)):
1042 if not isinstance(notes, Sequence) or isinstance(notes, (str, bytes)):
1043 notes = [_safe_string(notes, "__notes__", func=repr)]
1043 notes = [_safe_string(notes, "__notes__", func=repr)]
1044
1044
1045 # ... and format it
1045 # ... and format it
1046 return [
1046 return [
1047 "{}{}{}: {}".format(
1047 "{}{}{}: {}".format(
1048 colors.excName,
1048 colors.excName,
1049 etype_str,
1049 etype_str,
1050 colorsnormal,
1050 colorsnormal,
1051 evalue_str,
1051 py3compat.cast_unicode(evalue_str),
1052 ),
1052 ),
1053 *(
1053 *(
1054 "{}{}".format(
1054 "{}{}".format(
1055 colorsnormal, _safe_string(py3compat.cast_unicode(n), "note")
1055 colorsnormal, _safe_string(py3compat.cast_unicode(n), "note")
1056 )
1056 )
1057 for n in notes
1057 for n in notes
1058 ),
1058 ),
1059 ]
1059 ]
1060
1060
1061 def format_exception_as_a_whole(
1061 def format_exception_as_a_whole(
1062 self,
1062 self,
1063 etype: type,
1063 etype: type,
1064 evalue: Optional[BaseException],
1064 evalue: Optional[BaseException],
1065 etb: Optional[TracebackType],
1065 etb: Optional[TracebackType],
1066 number_of_lines_of_context,
1066 number_of_lines_of_context,
1067 tb_offset: Optional[int],
1067 tb_offset: Optional[int],
1068 ):
1068 ):
1069 """Formats the header, traceback and exception message for a single exception.
1069 """Formats the header, traceback and exception message for a single exception.
1070
1070
1071 This may be called multiple times by Python 3 exception chaining
1071 This may be called multiple times by Python 3 exception chaining
1072 (PEP 3134).
1072 (PEP 3134).
1073 """
1073 """
1074 # some locals
1075 orig_etype = etype
1074 try:
1076 try:
1075 etype = etype.__name__ # type: ignore
1077 etype = etype.__name__ # type: ignore
1076 except AttributeError:
1078 except AttributeError:
1077 pass
1079 pass
1078
1080
1079 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1081 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1080 assert isinstance(tb_offset, int)
1082 assert isinstance(tb_offset, int)
1081 head = self.prepare_header(str(etype), self.long_header)
1083 head = self.prepare_header(str(etype), self.long_header)
1082 records = (
1084 records = (
1083 self.get_records(etb, number_of_lines_of_context, tb_offset) if etb else []
1085 self.get_records(etb, number_of_lines_of_context, tb_offset) if etb else []
1084 )
1086 )
1085
1087
1086 frames = []
1088 frames = []
1087 skipped = 0
1089 skipped = 0
1088 lastrecord = len(records) - 1
1090 lastrecord = len(records) - 1
1089 for i, record in enumerate(records):
1091 for i, record in enumerate(records):
1090 if (
1092 if (
1091 not isinstance(record._sd, stack_data.RepeatedFrames)
1093 not isinstance(record._sd, stack_data.RepeatedFrames)
1092 and self.skip_hidden
1094 and self.skip_hidden
1093 ):
1095 ):
1094 if (
1096 if (
1095 record.frame.f_locals.get("__tracebackhide__", 0)
1097 record.frame.f_locals.get("__tracebackhide__", 0)
1096 and i != lastrecord
1098 and i != lastrecord
1097 ):
1099 ):
1098 skipped += 1
1100 skipped += 1
1099 continue
1101 continue
1100 if skipped:
1102 if skipped:
1101 Colors = self.Colors # just a shorthand + quicker name lookup
1103 Colors = self.Colors # just a shorthand + quicker name lookup
1102 ColorsNormal = Colors.Normal # used a lot
1104 ColorsNormal = Colors.Normal # used a lot
1103 frames.append(
1105 frames.append(
1104 " %s[... skipping hidden %s frame]%s\n"
1106 " %s[... skipping hidden %s frame]%s\n"
1105 % (Colors.excName, skipped, ColorsNormal)
1107 % (Colors.excName, skipped, ColorsNormal)
1106 )
1108 )
1107 skipped = 0
1109 skipped = 0
1108 frames.append(self.format_record(record))
1110 frames.append(self.format_record(record))
1109 if skipped:
1111 if skipped:
1110 Colors = self.Colors # just a shorthand + quicker name lookup
1112 Colors = self.Colors # just a shorthand + quicker name lookup
1111 ColorsNormal = Colors.Normal # used a lot
1113 ColorsNormal = Colors.Normal # used a lot
1112 frames.append(
1114 frames.append(
1113 " %s[... skipping hidden %s frame]%s\n"
1115 " %s[... skipping hidden %s frame]%s\n"
1114 % (Colors.excName, skipped, ColorsNormal)
1116 % (Colors.excName, skipped, ColorsNormal)
1115 )
1117 )
1116
1118
1117 formatted_exception = self.format_exception(etype, evalue)
1119 formatted_exception = self.format_exception(etype, evalue)
1118 if records:
1120 if records:
1119 frame_info = records[-1]
1121 frame_info = records[-1]
1120 ipinst = get_ipython()
1122 ipinst = get_ipython()
1121 if ipinst is not None:
1123 if ipinst is not None:
1122 ipinst.hooks.synchronize_with_editor(frame_info.filename, frame_info.lineno, 0)
1124 ipinst.hooks.synchronize_with_editor(frame_info.filename, frame_info.lineno, 0)
1123
1125
1124 return [[head] + frames + formatted_exception]
1126 return [[head] + frames + formatted_exception]
1125
1127
1126 def get_records(
1128 def get_records(
1127 self, etb: TracebackType, number_of_lines_of_context: int, tb_offset: int
1129 self, etb: TracebackType, number_of_lines_of_context: int, tb_offset: int
1128 ):
1130 ):
1129 assert etb is not None
1131 assert etb is not None
1130 context = number_of_lines_of_context - 1
1132 context = number_of_lines_of_context - 1
1131 after = context // 2
1133 after = context // 2
1132 before = context - after
1134 before = context - after
1133 if self.has_colors:
1135 if self.has_colors:
1134 style = get_style_by_name(self.tb_highlight_style)
1136 style = get_style_by_name(self.tb_highlight_style)
1135 style = stack_data.style_with_executing_node(style, self.tb_highlight)
1137 style = stack_data.style_with_executing_node(style, self.tb_highlight)
1136 formatter = Terminal256Formatter(style=style)
1138 formatter = Terminal256Formatter(style=style)
1137 else:
1139 else:
1138 formatter = None
1140 formatter = None
1139 options = stack_data.Options(
1141 options = stack_data.Options(
1140 before=before,
1142 before=before,
1141 after=after,
1143 after=after,
1142 pygments_formatter=formatter,
1144 pygments_formatter=formatter,
1143 )
1145 )
1144
1146
1145 # Let's estimate the amount of code we will have to parse/highlight.
1147 # Let's estimate the amount of code we will have to parse/highlight.
1146 cf: Optional[TracebackType] = etb
1148 cf: Optional[TracebackType] = etb
1147 max_len = 0
1149 max_len = 0
1148 tbs = []
1150 tbs = []
1149 while cf is not None:
1151 while cf is not None:
1150 try:
1152 try:
1151 mod = inspect.getmodule(cf.tb_frame)
1153 mod = inspect.getmodule(cf.tb_frame)
1152 if mod is not None:
1154 if mod is not None:
1153 mod_name = mod.__name__
1155 mod_name = mod.__name__
1154 root_name, *_ = mod_name.split(".")
1156 root_name, *_ = mod_name.split(".")
1155 if root_name == "IPython":
1157 if root_name == "IPython":
1156 cf = cf.tb_next
1158 cf = cf.tb_next
1157 continue
1159 continue
1158 max_len = get_line_number_of_frame(cf.tb_frame)
1160 max_len = get_line_number_of_frame(cf.tb_frame)
1159
1161
1160 except OSError:
1162 except OSError:
1161 max_len = 0
1163 max_len = 0
1162 max_len = max(max_len, max_len)
1164 max_len = max(max_len, max_len)
1163 tbs.append(cf)
1165 tbs.append(cf)
1164 cf = getattr(cf, "tb_next", None)
1166 cf = getattr(cf, "tb_next", None)
1165
1167
1166 if max_len > FAST_THRESHOLD:
1168 if max_len > FAST_THRESHOLD:
1167 FIs = []
1169 FIs = []
1168 for tb in tbs:
1170 for tb in tbs:
1169 frame = tb.tb_frame # type: ignore
1171 frame = tb.tb_frame # type: ignore
1170 lineno = (frame.f_lineno,)
1172 lineno = (frame.f_lineno,)
1171 code = frame.f_code
1173 code = frame.f_code
1172 filename = code.co_filename
1174 filename = code.co_filename
1173 # TODO: Here we need to use before/after/
1175 # TODO: Here we need to use before/after/
1174 FIs.append(
1176 FIs.append(
1175 FrameInfo(
1177 FrameInfo(
1176 "Raw frame", filename, lineno, frame, code, context=context
1178 "Raw frame", filename, lineno, frame, code, context=context
1177 )
1179 )
1178 )
1180 )
1179 return FIs
1181 return FIs
1180 res = list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:]
1182 res = list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:]
1181 res = [FrameInfo._from_stack_data_FrameInfo(r) for r in res]
1183 res = [FrameInfo._from_stack_data_FrameInfo(r) for r in res]
1182 return res
1184 return res
1183
1185
1184 def structured_traceback(
1186 def structured_traceback(
1185 self,
1187 self,
1186 etype: type,
1188 etype: type,
1187 evalue: Optional[BaseException],
1189 evalue: Optional[BaseException],
1188 etb: Optional[TracebackType] = None,
1190 etb: Optional[TracebackType] = None,
1189 tb_offset: Optional[int] = None,
1191 tb_offset: Optional[int] = None,
1190 number_of_lines_of_context: int = 5,
1192 number_of_lines_of_context: int = 5,
1191 ):
1193 ):
1192 """Return a nice text document describing the traceback."""
1194 """Return a nice text document describing the traceback."""
1193 formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
1195 formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
1194 tb_offset)
1196 tb_offset)
1195
1197
1196 colors = self.Colors # just a shorthand + quicker name lookup
1198 colors = self.Colors # just a shorthand + quicker name lookup
1197 colorsnormal = colors.Normal # used a lot
1199 colorsnormal = colors.Normal # used a lot
1198 head = '%s%s%s' % (colors.topline, '-' * min(75, get_terminal_size()[0]), colorsnormal)
1200 head = '%s%s%s' % (colors.topline, '-' * min(75, get_terminal_size()[0]), colorsnormal)
1199 structured_traceback_parts = [head]
1201 structured_traceback_parts = [head]
1200 chained_exceptions_tb_offset = 0
1202 chained_exceptions_tb_offset = 0
1201 lines_of_context = 3
1203 lines_of_context = 3
1202 formatted_exceptions = formatted_exception
1204 formatted_exceptions = formatted_exception
1203 exception = self.get_parts_of_chained_exception(evalue)
1205 exception = self.get_parts_of_chained_exception(evalue)
1204 if exception:
1206 if exception:
1205 assert evalue is not None
1207 assert evalue is not None
1206 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1208 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1207 etype, evalue, etb = exception
1209 etype, evalue, etb = exception
1208 else:
1210 else:
1209 evalue = None
1211 evalue = None
1210 chained_exc_ids = set()
1212 chained_exc_ids = set()
1211 while evalue:
1213 while evalue:
1212 formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context,
1214 formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context,
1213 chained_exceptions_tb_offset)
1215 chained_exceptions_tb_offset)
1214 exception = self.get_parts_of_chained_exception(evalue)
1216 exception = self.get_parts_of_chained_exception(evalue)
1215
1217
1216 if exception and id(exception[1]) not in chained_exc_ids:
1218 if exception and not id(exception[1]) in chained_exc_ids:
1217 chained_exc_ids.add(id(exception[1])) # trace exception to avoid infinite 'cause' loop
1219 chained_exc_ids.add(id(exception[1])) # trace exception to avoid infinite 'cause' loop
1218 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1220 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1219 etype, evalue, etb = exception
1221 etype, evalue, etb = exception
1220 else:
1222 else:
1221 evalue = None
1223 evalue = None
1222
1224
1223 # we want to see exceptions in a reversed order:
1225 # we want to see exceptions in a reversed order:
1224 # the first exception should be on top
1226 # the first exception should be on top
1225 for formatted_exception in reversed(formatted_exceptions):
1227 for formatted_exception in reversed(formatted_exceptions):
1226 structured_traceback_parts += formatted_exception
1228 structured_traceback_parts += formatted_exception
1227
1229
1228 return structured_traceback_parts
1230 return structured_traceback_parts
1229
1231
1230 def debugger(self, force: bool = False):
1232 def debugger(self, force: bool = False):
1231 """Call up the pdb debugger if desired, always clean up the tb
1233 """Call up the pdb debugger if desired, always clean up the tb
1232 reference.
1234 reference.
1233
1235
1234 Keywords:
1236 Keywords:
1235
1237
1236 - force(False): by default, this routine checks the instance call_pdb
1238 - force(False): by default, this routine checks the instance call_pdb
1237 flag and does not actually invoke the debugger if the flag is false.
1239 flag and does not actually invoke the debugger if the flag is false.
1238 The 'force' option forces the debugger to activate even if the flag
1240 The 'force' option forces the debugger to activate even if the flag
1239 is false.
1241 is false.
1240
1242
1241 If the call_pdb flag is set, the pdb interactive debugger is
1243 If the call_pdb flag is set, the pdb interactive debugger is
1242 invoked. In all cases, the self.tb reference to the current traceback
1244 invoked. In all cases, the self.tb reference to the current traceback
1243 is deleted to prevent lingering references which hamper memory
1245 is deleted to prevent lingering references which hamper memory
1244 management.
1246 management.
1245
1247
1246 Note that each call to pdb() does an 'import readline', so if your app
1248 Note that each call to pdb() does an 'import readline', so if your app
1247 requires a special setup for the readline completers, you'll have to
1249 requires a special setup for the readline completers, you'll have to
1248 fix that by hand after invoking the exception handler."""
1250 fix that by hand after invoking the exception handler."""
1249
1251
1250 if force or self.call_pdb:
1252 if force or self.call_pdb:
1251 if self.pdb is None:
1253 if self.pdb is None:
1252 self.pdb = self.debugger_cls()
1254 self.pdb = self.debugger_cls()
1253 # the system displayhook may have changed, restore the original
1255 # the system displayhook may have changed, restore the original
1254 # for pdb
1256 # for pdb
1255 display_trap = DisplayTrap(hook=sys.__displayhook__)
1257 display_trap = DisplayTrap(hook=sys.__displayhook__)
1256 with display_trap:
1258 with display_trap:
1257 self.pdb.reset()
1259 self.pdb.reset()
1258 # Find the right frame so we don't pop up inside ipython itself
1260 # Find the right frame so we don't pop up inside ipython itself
1259 if hasattr(self, "tb") and self.tb is not None: # type: ignore[has-type]
1261 if hasattr(self, "tb") and self.tb is not None: # type: ignore[has-type]
1260 etb = self.tb # type: ignore[has-type]
1262 etb = self.tb # type: ignore[has-type]
1261 else:
1263 else:
1262 etb = self.tb = sys.last_traceback
1264 etb = self.tb = sys.last_traceback
1263 while self.tb is not None and self.tb.tb_next is not None:
1265 while self.tb is not None and self.tb.tb_next is not None:
1264 assert self.tb.tb_next is not None
1266 assert self.tb.tb_next is not None
1265 self.tb = self.tb.tb_next
1267 self.tb = self.tb.tb_next
1266 if etb and etb.tb_next:
1268 if etb and etb.tb_next:
1267 etb = etb.tb_next
1269 etb = etb.tb_next
1268 self.pdb.botframe = etb.tb_frame
1270 self.pdb.botframe = etb.tb_frame
1269 # last_value should be deprecated, but last-exc sometimme not set
1271 # last_value should be deprecated, but last-exc sometimme not set
1270 # please check why later and remove the getattr.
1272 # please check why later and remove the getattr.
1271 exc = sys.last_value if sys.version_info < (3, 12) else getattr(sys, "last_exc", sys.last_value) # type: ignore[attr-defined]
1273 exc = sys.last_value if sys.version_info < (3, 12) else getattr(sys, "last_exc", sys.last_value) # type: ignore[attr-defined]
1272 if exc:
1274 if exc:
1273 self.pdb.interaction(None, exc)
1275 self.pdb.interaction(None, exc)
1274 else:
1276 else:
1275 self.pdb.interaction(None, etb)
1277 self.pdb.interaction(None, etb)
1276
1278
1277 if hasattr(self, 'tb'):
1279 if hasattr(self, 'tb'):
1278 del self.tb
1280 del self.tb
1279
1281
1280 def handler(self, info=None):
1282 def handler(self, info=None):
1281 (etype, evalue, etb) = info or sys.exc_info()
1283 (etype, evalue, etb) = info or sys.exc_info()
1282 self.tb = etb
1284 self.tb = etb
1283 ostream = self.ostream
1285 ostream = self.ostream
1284 ostream.flush()
1286 ostream.flush()
1285 ostream.write(self.text(etype, evalue, etb))
1287 ostream.write(self.text(etype, evalue, etb))
1286 ostream.write('\n')
1288 ostream.write('\n')
1287 ostream.flush()
1289 ostream.flush()
1288
1290
1289 # Changed so an instance can just be called as VerboseTB_inst() and print
1291 # Changed so an instance can just be called as VerboseTB_inst() and print
1290 # out the right info on its own.
1292 # out the right info on its own.
1291 def __call__(self, etype=None, evalue=None, etb=None):
1293 def __call__(self, etype=None, evalue=None, etb=None):
1292 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
1294 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
1293 if etb is None:
1295 if etb is None:
1294 self.handler()
1296 self.handler()
1295 else:
1297 else:
1296 self.handler((etype, evalue, etb))
1298 self.handler((etype, evalue, etb))
1297 try:
1299 try:
1298 self.debugger()
1300 self.debugger()
1299 except KeyboardInterrupt:
1301 except KeyboardInterrupt:
1300 print("\nKeyboardInterrupt")
1302 print("\nKeyboardInterrupt")
1301
1303
1302
1304
1303 #----------------------------------------------------------------------------
1305 #----------------------------------------------------------------------------
1304 class FormattedTB(VerboseTB, ListTB):
1306 class FormattedTB(VerboseTB, ListTB):
1305 """Subclass ListTB but allow calling with a traceback.
1307 """Subclass ListTB but allow calling with a traceback.
1306
1308
1307 It can thus be used as a sys.excepthook for Python > 2.1.
1309 It can thus be used as a sys.excepthook for Python > 2.1.
1308
1310
1309 Also adds 'Context' and 'Verbose' modes, not available in ListTB.
1311 Also adds 'Context' and 'Verbose' modes, not available in ListTB.
1310
1312
1311 Allows a tb_offset to be specified. This is useful for situations where
1313 Allows a tb_offset to be specified. This is useful for situations where
1312 one needs to remove a number of topmost frames from the traceback (such as
1314 one needs to remove a number of topmost frames from the traceback (such as
1313 occurs with python programs that themselves execute other python code,
1315 occurs with python programs that themselves execute other python code,
1314 like Python shells). """
1316 like Python shells). """
1315
1317
1316 mode: str
1318 mode: str
1317
1319
1318 def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False,
1320 def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False,
1319 ostream=None,
1321 ostream=None,
1320 tb_offset=0, long_header=False, include_vars=False,
1322 tb_offset=0, long_header=False, include_vars=False,
1321 check_cache=None, debugger_cls=None,
1323 check_cache=None, debugger_cls=None,
1322 parent=None, config=None):
1324 parent=None, config=None):
1323
1325
1324 # NEVER change the order of this list. Put new modes at the end:
1326 # NEVER change the order of this list. Put new modes at the end:
1325 self.valid_modes = ['Plain', 'Context', 'Verbose', 'Minimal']
1327 self.valid_modes = ['Plain', 'Context', 'Verbose', 'Minimal']
1326 self.verbose_modes = self.valid_modes[1:3]
1328 self.verbose_modes = self.valid_modes[1:3]
1327
1329
1328 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
1330 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
1329 ostream=ostream, tb_offset=tb_offset,
1331 ostream=ostream, tb_offset=tb_offset,
1330 long_header=long_header, include_vars=include_vars,
1332 long_header=long_header, include_vars=include_vars,
1331 check_cache=check_cache, debugger_cls=debugger_cls,
1333 check_cache=check_cache, debugger_cls=debugger_cls,
1332 parent=parent, config=config)
1334 parent=parent, config=config)
1333
1335
1334 # Different types of tracebacks are joined with different separators to
1336 # Different types of tracebacks are joined with different separators to
1335 # form a single string. They are taken from this dict
1337 # form a single string. They are taken from this dict
1336 self._join_chars = dict(Plain='', Context='\n', Verbose='\n',
1338 self._join_chars = dict(Plain='', Context='\n', Verbose='\n',
1337 Minimal='')
1339 Minimal='')
1338 # set_mode also sets the tb_join_char attribute
1340 # set_mode also sets the tb_join_char attribute
1339 self.set_mode(mode)
1341 self.set_mode(mode)
1340
1342
1341 def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5):
1343 def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5):
1342 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1344 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1343 mode = self.mode
1345 mode = self.mode
1344 if mode in self.verbose_modes:
1346 if mode in self.verbose_modes:
1345 # Verbose modes need a full traceback
1347 # Verbose modes need a full traceback
1346 return VerboseTB.structured_traceback(
1348 return VerboseTB.structured_traceback(
1347 self, etype, value, tb, tb_offset, number_of_lines_of_context
1349 self, etype, value, tb, tb_offset, number_of_lines_of_context
1348 )
1350 )
1349 elif mode == 'Minimal':
1351 elif mode == 'Minimal':
1350 return ListTB.get_exception_only(self, etype, value)
1352 return ListTB.get_exception_only(self, etype, value)
1351 else:
1353 else:
1352 # We must check the source cache because otherwise we can print
1354 # We must check the source cache because otherwise we can print
1353 # out-of-date source code.
1355 # out-of-date source code.
1354 self.check_cache()
1356 self.check_cache()
1355 # Now we can extract and format the exception
1357 # Now we can extract and format the exception
1356 return ListTB.structured_traceback(
1358 return ListTB.structured_traceback(
1357 self, etype, value, tb, tb_offset, number_of_lines_of_context
1359 self, etype, value, tb, tb_offset, number_of_lines_of_context
1358 )
1360 )
1359
1361
1360 def stb2text(self, stb):
1362 def stb2text(self, stb):
1361 """Convert a structured traceback (a list) to a string."""
1363 """Convert a structured traceback (a list) to a string."""
1362 return self.tb_join_char.join(stb)
1364 return self.tb_join_char.join(stb)
1363
1365
1364 def set_mode(self, mode: Optional[str] = None):
1366 def set_mode(self, mode: Optional[str] = None):
1365 """Switch to the desired mode.
1367 """Switch to the desired mode.
1366
1368
1367 If mode is not specified, cycles through the available modes."""
1369 If mode is not specified, cycles through the available modes."""
1368
1370
1369 if not mode:
1371 if not mode:
1370 new_idx = (self.valid_modes.index(self.mode) + 1 ) % \
1372 new_idx = (self.valid_modes.index(self.mode) + 1 ) % \
1371 len(self.valid_modes)
1373 len(self.valid_modes)
1372 self.mode = self.valid_modes[new_idx]
1374 self.mode = self.valid_modes[new_idx]
1373 elif mode not in self.valid_modes:
1375 elif mode not in self.valid_modes:
1374 raise ValueError(
1376 raise ValueError(
1375 "Unrecognized mode in FormattedTB: <" + mode + ">\n"
1377 "Unrecognized mode in FormattedTB: <" + mode + ">\n"
1376 "Valid modes: " + str(self.valid_modes)
1378 "Valid modes: " + str(self.valid_modes)
1377 )
1379 )
1378 else:
1380 else:
1379 assert isinstance(mode, str)
1381 assert isinstance(mode, str)
1380 self.mode = mode
1382 self.mode = mode
1381 # include variable details only in 'Verbose' mode
1383 # include variable details only in 'Verbose' mode
1382 self.include_vars = (self.mode == self.valid_modes[2])
1384 self.include_vars = (self.mode == self.valid_modes[2])
1383 # Set the join character for generating text tracebacks
1385 # Set the join character for generating text tracebacks
1384 self.tb_join_char = self._join_chars[self.mode]
1386 self.tb_join_char = self._join_chars[self.mode]
1385
1387
1386 # some convenient shortcuts
1388 # some convenient shortcuts
1387 def plain(self):
1389 def plain(self):
1388 self.set_mode(self.valid_modes[0])
1390 self.set_mode(self.valid_modes[0])
1389
1391
1390 def context(self):
1392 def context(self):
1391 self.set_mode(self.valid_modes[1])
1393 self.set_mode(self.valid_modes[1])
1392
1394
1393 def verbose(self):
1395 def verbose(self):
1394 self.set_mode(self.valid_modes[2])
1396 self.set_mode(self.valid_modes[2])
1395
1397
1396 def minimal(self):
1398 def minimal(self):
1397 self.set_mode(self.valid_modes[3])
1399 self.set_mode(self.valid_modes[3])
1398
1400
1399
1401
1400 #----------------------------------------------------------------------------
1402 #----------------------------------------------------------------------------
1401 class AutoFormattedTB(FormattedTB):
1403 class AutoFormattedTB(FormattedTB):
1402 """A traceback printer which can be called on the fly.
1404 """A traceback printer which can be called on the fly.
1403
1405
1404 It will find out about exceptions by itself.
1406 It will find out about exceptions by itself.
1405
1407
1406 A brief example::
1408 A brief example::
1407
1409
1408 AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux')
1410 AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux')
1409 try:
1411 try:
1410 ...
1412 ...
1411 except Exception:
1413 except:
1412 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1414 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1413 """
1415 """
1414
1416
1415 def __call__(self, etype=None, evalue=None, etb=None,
1417 def __call__(self, etype=None, evalue=None, etb=None,
1416 out=None, tb_offset=None):
1418 out=None, tb_offset=None):
1417 """Print out a formatted exception traceback.
1419 """Print out a formatted exception traceback.
1418
1420
1419 Optional arguments:
1421 Optional arguments:
1420 - out: an open file-like object to direct output to.
1422 - out: an open file-like object to direct output to.
1421
1423
1422 - tb_offset: the number of frames to skip over in the stack, on a
1424 - tb_offset: the number of frames to skip over in the stack, on a
1423 per-call basis (this overrides temporarily the instance's tb_offset
1425 per-call basis (this overrides temporarily the instance's tb_offset
1424 given at initialization time."""
1426 given at initialization time."""
1425
1427
1426 if out is None:
1428 if out is None:
1427 out = self.ostream
1429 out = self.ostream
1428 out.flush()
1430 out.flush()
1429 out.write(self.text(etype, evalue, etb, tb_offset))
1431 out.write(self.text(etype, evalue, etb, tb_offset))
1430 out.write('\n')
1432 out.write('\n')
1431 out.flush()
1433 out.flush()
1432 # FIXME: we should remove the auto pdb behavior from here and leave
1434 # FIXME: we should remove the auto pdb behavior from here and leave
1433 # that to the clients.
1435 # that to the clients.
1434 try:
1436 try:
1435 self.debugger()
1437 self.debugger()
1436 except KeyboardInterrupt:
1438 except KeyboardInterrupt:
1437 print("\nKeyboardInterrupt")
1439 print("\nKeyboardInterrupt")
1438
1440
1439 def structured_traceback(
1441 def structured_traceback(
1440 self,
1442 self,
1441 etype: type,
1443 etype: type,
1442 evalue: Optional[BaseException],
1444 evalue: Optional[BaseException],
1443 etb: Optional[TracebackType] = None,
1445 etb: Optional[TracebackType] = None,
1444 tb_offset: Optional[int] = None,
1446 tb_offset: Optional[int] = None,
1445 number_of_lines_of_context: int = 5,
1447 number_of_lines_of_context: int = 5,
1446 ):
1448 ):
1447 # tb: TracebackType or tupleof tb types ?
1449 # tb: TracebackType or tupleof tb types ?
1448 if etype is None:
1450 if etype is None:
1449 etype, evalue, etb = sys.exc_info()
1451 etype, evalue, etb = sys.exc_info()
1450 if isinstance(etb, tuple):
1452 if isinstance(etb, tuple):
1451 # tb is a tuple if this is a chained exception.
1453 # tb is a tuple if this is a chained exception.
1452 self.tb = etb[0]
1454 self.tb = etb[0]
1453 else:
1455 else:
1454 self.tb = etb
1456 self.tb = etb
1455 return FormattedTB.structured_traceback(
1457 return FormattedTB.structured_traceback(
1456 self, etype, evalue, etb, tb_offset, number_of_lines_of_context
1458 self, etype, evalue, etb, tb_offset, number_of_lines_of_context
1457 )
1459 )
1458
1460
1459
1461
1460 #---------------------------------------------------------------------------
1462 #---------------------------------------------------------------------------
1461
1463
1462 # A simple class to preserve Nathan's original functionality.
1464 # A simple class to preserve Nathan's original functionality.
1463 class ColorTB(FormattedTB):
1465 class ColorTB(FormattedTB):
1464 """Shorthand to initialize a FormattedTB in Linux colors mode."""
1466 """Shorthand to initialize a FormattedTB in Linux colors mode."""
1465
1467
1466 def __init__(self, color_scheme='Linux', call_pdb=0, **kwargs):
1468 def __init__(self, color_scheme='Linux', call_pdb=0, **kwargs):
1467 FormattedTB.__init__(self, color_scheme=color_scheme,
1469 FormattedTB.__init__(self, color_scheme=color_scheme,
1468 call_pdb=call_pdb, **kwargs)
1470 call_pdb=call_pdb, **kwargs)
1469
1471
1470
1472
1471 class SyntaxTB(ListTB):
1473 class SyntaxTB(ListTB):
1472 """Extension which holds some state: the last exception value"""
1474 """Extension which holds some state: the last exception value"""
1473
1475
1474 def __init__(self, color_scheme='NoColor', parent=None, config=None):
1476 def __init__(self, color_scheme='NoColor', parent=None, config=None):
1475 ListTB.__init__(self, color_scheme, parent=parent, config=config)
1477 ListTB.__init__(self, color_scheme, parent=parent, config=config)
1476 self.last_syntax_error = None
1478 self.last_syntax_error = None
1477
1479
1478 def __call__(self, etype, value, elist):
1480 def __call__(self, etype, value, elist):
1479 self.last_syntax_error = value
1481 self.last_syntax_error = value
1480
1482
1481 ListTB.__call__(self, etype, value, elist)
1483 ListTB.__call__(self, etype, value, elist)
1482
1484
1483 def structured_traceback(self, etype, value, elist, tb_offset=None,
1485 def structured_traceback(self, etype, value, elist, tb_offset=None,
1484 context=5):
1486 context=5):
1485 # If the source file has been edited, the line in the syntax error can
1487 # If the source file has been edited, the line in the syntax error can
1486 # be wrong (retrieved from an outdated cache). This replaces it with
1488 # be wrong (retrieved from an outdated cache). This replaces it with
1487 # the current value.
1489 # the current value.
1488 if isinstance(value, SyntaxError) \
1490 if isinstance(value, SyntaxError) \
1489 and isinstance(value.filename, str) \
1491 and isinstance(value.filename, str) \
1490 and isinstance(value.lineno, int):
1492 and isinstance(value.lineno, int):
1491 linecache.checkcache(value.filename)
1493 linecache.checkcache(value.filename)
1492 newtext = linecache.getline(value.filename, value.lineno)
1494 newtext = linecache.getline(value.filename, value.lineno)
1493 if newtext:
1495 if newtext:
1494 value.text = newtext
1496 value.text = newtext
1495 self.last_syntax_error = value
1497 self.last_syntax_error = value
1496 return super(SyntaxTB, self).structured_traceback(etype, value, elist,
1498 return super(SyntaxTB, self).structured_traceback(etype, value, elist,
1497 tb_offset=tb_offset, context=context)
1499 tb_offset=tb_offset, context=context)
1498
1500
1499 def clear_err_state(self):
1501 def clear_err_state(self):
1500 """Return the current error state and clear it"""
1502 """Return the current error state and clear it"""
1501 e = self.last_syntax_error
1503 e = self.last_syntax_error
1502 self.last_syntax_error = None
1504 self.last_syntax_error = None
1503 return e
1505 return e
1504
1506
1505 def stb2text(self, stb):
1507 def stb2text(self, stb):
1506 """Convert a structured traceback (a list) to a string."""
1508 """Convert a structured traceback (a list) to a string."""
1507 return ''.join(stb)
1509 return ''.join(stb)
1508
1510
1509
1511
1510 # some internal-use functions
1512 # some internal-use functions
1511 def text_repr(value):
1513 def text_repr(value):
1512 """Hopefully pretty robust repr equivalent."""
1514 """Hopefully pretty robust repr equivalent."""
1513 # this is pretty horrible but should always return *something*
1515 # this is pretty horrible but should always return *something*
1514 try:
1516 try:
1515 return pydoc.text.repr(value) # type: ignore[call-arg]
1517 return pydoc.text.repr(value) # type: ignore[call-arg]
1516 except KeyboardInterrupt:
1518 except KeyboardInterrupt:
1517 raise
1519 raise
1518 except Exception:
1520 except:
1519 try:
1521 try:
1520 return repr(value)
1522 return repr(value)
1521 except KeyboardInterrupt:
1523 except KeyboardInterrupt:
1522 raise
1524 raise
1523 except Exception:
1525 except:
1524 try:
1526 try:
1525 # all still in an except block so we catch
1527 # all still in an except block so we catch
1526 # getattr raising
1528 # getattr raising
1527 name = getattr(value, '__name__', None)
1529 name = getattr(value, '__name__', None)
1528 if name:
1530 if name:
1529 # ick, recursion
1531 # ick, recursion
1530 return text_repr(name)
1532 return text_repr(name)
1531 klass = getattr(value, '__class__', None)
1533 klass = getattr(value, '__class__', None)
1532 if klass:
1534 if klass:
1533 return '%s instance' % text_repr(klass)
1535 return '%s instance' % text_repr(klass)
1534 except KeyboardInterrupt:
1536 except KeyboardInterrupt:
1535 raise
1537 raise
1536 except Exception:
1538 except:
1537 return 'UNRECOVERABLE REPR FAILURE'
1539 return 'UNRECOVERABLE REPR FAILURE'
1538
1540
1539
1541
1540 def eqrepr(value, repr=text_repr):
1542 def eqrepr(value, repr=text_repr):
1541 return '=%s' % repr(value)
1543 return '=%s' % repr(value)
1542
1544
1543
1545
1544 def nullrepr(value, repr=text_repr):
1546 def nullrepr(value, repr=text_repr):
1545 return ''
1547 return ''
@@ -1,672 +1,672
1 """Module for interactive demos using IPython.
1 """Module for interactive demos using IPython.
2
2
3 This module implements a few classes for running Python scripts interactively
3 This module implements a few classes for running Python scripts interactively
4 in IPython for demonstrations. With very simple markup (a few tags in
4 in IPython for demonstrations. With very simple markup (a few tags in
5 comments), you can control points where the script stops executing and returns
5 comments), you can control points where the script stops executing and returns
6 control to IPython.
6 control to IPython.
7
7
8
8
9 Provided classes
9 Provided classes
10 ----------------
10 ----------------
11
11
12 The classes are (see their docstrings for further details):
12 The classes are (see their docstrings for further details):
13
13
14 - Demo: pure python demos
14 - Demo: pure python demos
15
15
16 - IPythonDemo: demos with input to be processed by IPython as if it had been
16 - IPythonDemo: demos with input to be processed by IPython as if it had been
17 typed interactively (so magics work, as well as any other special syntax you
17 typed interactively (so magics work, as well as any other special syntax you
18 may have added via input prefilters).
18 may have added via input prefilters).
19
19
20 - LineDemo: single-line version of the Demo class. These demos are executed
20 - LineDemo: single-line version of the Demo class. These demos are executed
21 one line at a time, and require no markup.
21 one line at a time, and require no markup.
22
22
23 - IPythonLineDemo: IPython version of the LineDemo class (the demo is
23 - IPythonLineDemo: IPython version of the LineDemo class (the demo is
24 executed a line at a time, but processed via IPython).
24 executed a line at a time, but processed via IPython).
25
25
26 - ClearMixin: mixin to make Demo classes with less visual clutter. It
26 - ClearMixin: mixin to make Demo classes with less visual clutter. It
27 declares an empty marquee and a pre_cmd that clears the screen before each
27 declares an empty marquee and a pre_cmd that clears the screen before each
28 block (see Subclassing below).
28 block (see Subclassing below).
29
29
30 - ClearDemo, ClearIPDemo: mixin-enabled versions of the Demo and IPythonDemo
30 - ClearDemo, ClearIPDemo: mixin-enabled versions of the Demo and IPythonDemo
31 classes.
31 classes.
32
32
33 Inheritance diagram:
33 Inheritance diagram:
34
34
35 .. inheritance-diagram:: IPython.lib.demo
35 .. inheritance-diagram:: IPython.lib.demo
36 :parts: 3
36 :parts: 3
37
37
38 Subclassing
38 Subclassing
39 -----------
39 -----------
40
40
41 The classes here all include a few methods meant to make customization by
41 The classes here all include a few methods meant to make customization by
42 subclassing more convenient. Their docstrings below have some more details:
42 subclassing more convenient. Their docstrings below have some more details:
43
43
44 - highlight(): format every block and optionally highlight comments and
44 - highlight(): format every block and optionally highlight comments and
45 docstring content.
45 docstring content.
46
46
47 - marquee(): generates a marquee to provide visible on-screen markers at each
47 - marquee(): generates a marquee to provide visible on-screen markers at each
48 block start and end.
48 block start and end.
49
49
50 - pre_cmd(): run right before the execution of each block.
50 - pre_cmd(): run right before the execution of each block.
51
51
52 - post_cmd(): run right after the execution of each block. If the block
52 - post_cmd(): run right after the execution of each block. If the block
53 raises an exception, this is NOT called.
53 raises an exception, this is NOT called.
54
54
55
55
56 Operation
56 Operation
57 ---------
57 ---------
58
58
59 The file is run in its own empty namespace (though you can pass it a string of
59 The file is run in its own empty namespace (though you can pass it a string of
60 arguments as if in a command line environment, and it will see those as
60 arguments as if in a command line environment, and it will see those as
61 sys.argv). But at each stop, the global IPython namespace is updated with the
61 sys.argv). But at each stop, the global IPython namespace is updated with the
62 current internal demo namespace, so you can work interactively with the data
62 current internal demo namespace, so you can work interactively with the data
63 accumulated so far.
63 accumulated so far.
64
64
65 By default, each block of code is printed (with syntax highlighting) before
65 By default, each block of code is printed (with syntax highlighting) before
66 executing it and you have to confirm execution. This is intended to show the
66 executing it and you have to confirm execution. This is intended to show the
67 code to an audience first so you can discuss it, and only proceed with
67 code to an audience first so you can discuss it, and only proceed with
68 execution once you agree. There are a few tags which allow you to modify this
68 execution once you agree. There are a few tags which allow you to modify this
69 behavior.
69 behavior.
70
70
71 The supported tags are:
71 The supported tags are:
72
72
73 # <demo> stop
73 # <demo> stop
74
74
75 Defines block boundaries, the points where IPython stops execution of the
75 Defines block boundaries, the points where IPython stops execution of the
76 file and returns to the interactive prompt.
76 file and returns to the interactive prompt.
77
77
78 You can optionally mark the stop tag with extra dashes before and after the
78 You can optionally mark the stop tag with extra dashes before and after the
79 word 'stop', to help visually distinguish the blocks in a text editor:
79 word 'stop', to help visually distinguish the blocks in a text editor:
80
80
81 # <demo> --- stop ---
81 # <demo> --- stop ---
82
82
83
83
84 # <demo> silent
84 # <demo> silent
85
85
86 Make a block execute silently (and hence automatically). Typically used in
86 Make a block execute silently (and hence automatically). Typically used in
87 cases where you have some boilerplate or initialization code which you need
87 cases where you have some boilerplate or initialization code which you need
88 executed but do not want to be seen in the demo.
88 executed but do not want to be seen in the demo.
89
89
90 # <demo> auto
90 # <demo> auto
91
91
92 Make a block execute automatically, but still being printed. Useful for
92 Make a block execute automatically, but still being printed. Useful for
93 simple code which does not warrant discussion, since it avoids the extra
93 simple code which does not warrant discussion, since it avoids the extra
94 manual confirmation.
94 manual confirmation.
95
95
96 # <demo> auto_all
96 # <demo> auto_all
97
97
98 This tag can _only_ be in the first block, and if given it overrides the
98 This tag can _only_ be in the first block, and if given it overrides the
99 individual auto tags to make the whole demo fully automatic (no block asks
99 individual auto tags to make the whole demo fully automatic (no block asks
100 for confirmation). It can also be given at creation time (or the attribute
100 for confirmation). It can also be given at creation time (or the attribute
101 set later) to override what's in the file.
101 set later) to override what's in the file.
102
102
103 While _any_ python file can be run as a Demo instance, if there are no stop
103 While _any_ python file can be run as a Demo instance, if there are no stop
104 tags the whole file will run in a single block (no different that calling
104 tags the whole file will run in a single block (no different that calling
105 first %pycat and then %run). The minimal markup to make this useful is to
105 first %pycat and then %run). The minimal markup to make this useful is to
106 place a set of stop tags; the other tags are only there to let you fine-tune
106 place a set of stop tags; the other tags are only there to let you fine-tune
107 the execution.
107 the execution.
108
108
109 This is probably best explained with the simple example file below. You can
109 This is probably best explained with the simple example file below. You can
110 copy this into a file named ex_demo.py, and try running it via::
110 copy this into a file named ex_demo.py, and try running it via::
111
111
112 from IPython.lib.demo import Demo
112 from IPython.lib.demo import Demo
113 d = Demo('ex_demo.py')
113 d = Demo('ex_demo.py')
114 d()
114 d()
115
115
116 Each time you call the demo object, it runs the next block. The demo object
116 Each time you call the demo object, it runs the next block. The demo object
117 has a few useful methods for navigation, like again(), edit(), jump(), seek()
117 has a few useful methods for navigation, like again(), edit(), jump(), seek()
118 and back(). It can be reset for a new run via reset() or reloaded from disk
118 and back(). It can be reset for a new run via reset() or reloaded from disk
119 (in case you've edited the source) via reload(). See their docstrings below.
119 (in case you've edited the source) via reload(). See their docstrings below.
120
120
121 Note: To make this simpler to explore, a file called "demo-exercizer.py" has
121 Note: To make this simpler to explore, a file called "demo-exercizer.py" has
122 been added to the "docs/examples/core" directory. Just cd to this directory in
122 been added to the "docs/examples/core" directory. Just cd to this directory in
123 an IPython session, and type::
123 an IPython session, and type::
124
124
125 %run demo-exercizer.py
125 %run demo-exercizer.py
126
126
127 and then follow the directions.
127 and then follow the directions.
128
128
129 Example
129 Example
130 -------
130 -------
131
131
132 The following is a very simple example of a valid demo file.
132 The following is a very simple example of a valid demo file.
133
133
134 ::
134 ::
135
135
136 #################### EXAMPLE DEMO <ex_demo.py> ###############################
136 #################### EXAMPLE DEMO <ex_demo.py> ###############################
137 '''A simple interactive demo to illustrate the use of IPython's Demo class.'''
137 '''A simple interactive demo to illustrate the use of IPython's Demo class.'''
138
138
139 print('Hello, welcome to an interactive IPython demo.')
139 print('Hello, welcome to an interactive IPython demo.')
140
140
141 # The mark below defines a block boundary, which is a point where IPython will
141 # The mark below defines a block boundary, which is a point where IPython will
142 # stop execution and return to the interactive prompt. The dashes are actually
142 # stop execution and return to the interactive prompt. The dashes are actually
143 # optional and used only as a visual aid to clearly separate blocks while
143 # optional and used only as a visual aid to clearly separate blocks while
144 # editing the demo code.
144 # editing the demo code.
145 # <demo> stop
145 # <demo> stop
146
146
147 x = 1
147 x = 1
148 y = 2
148 y = 2
149
149
150 # <demo> stop
150 # <demo> stop
151
151
152 # the mark below makes this block as silent
152 # the mark below makes this block as silent
153 # <demo> silent
153 # <demo> silent
154
154
155 print('This is a silent block, which gets executed but not printed.')
155 print('This is a silent block, which gets executed but not printed.')
156
156
157 # <demo> stop
157 # <demo> stop
158 # <demo> auto
158 # <demo> auto
159 print('This is an automatic block.')
159 print('This is an automatic block.')
160 print('It is executed without asking for confirmation, but printed.')
160 print('It is executed without asking for confirmation, but printed.')
161 z = x + y
161 z = x + y
162
162
163 print('z =', x)
163 print('z =', x)
164
164
165 # <demo> stop
165 # <demo> stop
166 # This is just another normal block.
166 # This is just another normal block.
167 print('z is now:', z)
167 print('z is now:', z)
168
168
169 print('bye!')
169 print('bye!')
170 ################### END EXAMPLE DEMO <ex_demo.py> ############################
170 ################### END EXAMPLE DEMO <ex_demo.py> ############################
171 """
171 """
172
172
173
173
174 #*****************************************************************************
174 #*****************************************************************************
175 # Copyright (C) 2005-2006 Fernando Perez. <Fernando.Perez@colorado.edu>
175 # Copyright (C) 2005-2006 Fernando Perez. <Fernando.Perez@colorado.edu>
176 #
176 #
177 # Distributed under the terms of the BSD License. The full license is in
177 # Distributed under the terms of the BSD License. The full license is in
178 # the file COPYING, distributed as part of this software.
178 # the file COPYING, distributed as part of this software.
179 #
179 #
180 #*****************************************************************************
180 #*****************************************************************************
181
181
182 import os
182 import os
183 import re
183 import re
184 import shlex
184 import shlex
185 import sys
185 import sys
186 import pygments
186 import pygments
187 from pathlib import Path
187 from pathlib import Path
188
188
189 from IPython.utils.text import marquee
189 from IPython.utils.text import marquee
190 from IPython.utils import openpy
190 from IPython.utils import openpy
191 from IPython.utils import py3compat
191 from IPython.utils import py3compat
192 __all__ = ['Demo','IPythonDemo','LineDemo','IPythonLineDemo','DemoError']
192 __all__ = ['Demo','IPythonDemo','LineDemo','IPythonLineDemo','DemoError']
193
193
194 class DemoError(Exception): pass
194 class DemoError(Exception): pass
195
195
196 def re_mark(mark):
196 def re_mark(mark):
197 return re.compile(r'^\s*#\s+<demo>\s+%s\s*$' % mark,re.MULTILINE)
197 return re.compile(r'^\s*#\s+<demo>\s+%s\s*$' % mark,re.MULTILINE)
198
198
199 class Demo(object):
199 class Demo(object):
200
200
201 re_stop = re_mark(r'-*\s?stop\s?-*')
201 re_stop = re_mark(r'-*\s?stop\s?-*')
202 re_silent = re_mark('silent')
202 re_silent = re_mark('silent')
203 re_auto = re_mark('auto')
203 re_auto = re_mark('auto')
204 re_auto_all = re_mark('auto_all')
204 re_auto_all = re_mark('auto_all')
205
205
206 def __init__(self,src,title='',arg_str='',auto_all=None, format_rst=False,
206 def __init__(self,src,title='',arg_str='',auto_all=None, format_rst=False,
207 formatter='terminal', style='default'):
207 formatter='terminal', style='default'):
208 """Make a new demo object. To run the demo, simply call the object.
208 """Make a new demo object. To run the demo, simply call the object.
209
209
210 See the module docstring for full details and an example (you can use
210 See the module docstring for full details and an example (you can use
211 IPython.Demo? in IPython to see it).
211 IPython.Demo? in IPython to see it).
212
212
213 Inputs:
213 Inputs:
214
214
215 - src is either a file, or file-like object, or a
215 - src is either a file, or file-like object, or a
216 string that can be resolved to a filename.
216 string that can be resolved to a filename.
217
217
218 Optional inputs:
218 Optional inputs:
219
219
220 - title: a string to use as the demo name. Of most use when the demo
220 - title: a string to use as the demo name. Of most use when the demo
221 you are making comes from an object that has no filename, or if you
221 you are making comes from an object that has no filename, or if you
222 want an alternate denotation distinct from the filename.
222 want an alternate denotation distinct from the filename.
223
223
224 - arg_str(''): a string of arguments, internally converted to a list
224 - arg_str(''): a string of arguments, internally converted to a list
225 just like sys.argv, so the demo script can see a similar
225 just like sys.argv, so the demo script can see a similar
226 environment.
226 environment.
227
227
228 - auto_all(None): global flag to run all blocks automatically without
228 - auto_all(None): global flag to run all blocks automatically without
229 confirmation. This attribute overrides the block-level tags and
229 confirmation. This attribute overrides the block-level tags and
230 applies to the whole demo. It is an attribute of the object, and
230 applies to the whole demo. It is an attribute of the object, and
231 can be changed at runtime simply by reassigning it to a boolean
231 can be changed at runtime simply by reassigning it to a boolean
232 value.
232 value.
233
233
234 - format_rst(False): a bool to enable comments and doc strings
234 - format_rst(False): a bool to enable comments and doc strings
235 formatting with pygments rst lexer
235 formatting with pygments rst lexer
236
236
237 - formatter('terminal'): a string of pygments formatter name to be
237 - formatter('terminal'): a string of pygments formatter name to be
238 used. Useful values for terminals: terminal, terminal256,
238 used. Useful values for terminals: terminal, terminal256,
239 terminal16m
239 terminal16m
240
240
241 - style('default'): a string of pygments style name to be used.
241 - style('default'): a string of pygments style name to be used.
242 """
242 """
243 if hasattr(src, "read"):
243 if hasattr(src, "read"):
244 # It seems to be a file or a file-like object
244 # It seems to be a file or a file-like object
245 self.fname = "from a file-like object"
245 self.fname = "from a file-like object"
246 if title == '':
246 if title == '':
247 self.title = "from a file-like object"
247 self.title = "from a file-like object"
248 else:
248 else:
249 self.title = title
249 self.title = title
250 else:
250 else:
251 # Assume it's a string or something that can be converted to one
251 # Assume it's a string or something that can be converted to one
252 self.fname = src
252 self.fname = src
253 if title == '':
253 if title == '':
254 (filepath, filename) = os.path.split(src)
254 (filepath, filename) = os.path.split(src)
255 self.title = filename
255 self.title = filename
256 else:
256 else:
257 self.title = title
257 self.title = title
258 self.sys_argv = [src] + shlex.split(arg_str)
258 self.sys_argv = [src] + shlex.split(arg_str)
259 self.auto_all = auto_all
259 self.auto_all = auto_all
260 self.src = src
260 self.src = src
261
261
262 try:
262 try:
263 ip = get_ipython() # this is in builtins whenever IPython is running
263 ip = get_ipython() # this is in builtins whenever IPython is running
264 self.inside_ipython = True
264 self.inside_ipython = True
265 except NameError:
265 except NameError:
266 self.inside_ipython = False
266 self.inside_ipython = False
267
267
268 if self.inside_ipython:
268 if self.inside_ipython:
269 # get a few things from ipython. While it's a bit ugly design-wise,
269 # get a few things from ipython. While it's a bit ugly design-wise,
270 # it ensures that things like color scheme and the like are always in
270 # it ensures that things like color scheme and the like are always in
271 # sync with the ipython mode being used. This class is only meant to
271 # sync with the ipython mode being used. This class is only meant to
272 # be used inside ipython anyways, so it's OK.
272 # be used inside ipython anyways, so it's OK.
273 self.ip_ns = ip.user_ns
273 self.ip_ns = ip.user_ns
274 self.ip_colorize = ip.pycolorize
274 self.ip_colorize = ip.pycolorize
275 self.ip_showtb = ip.showtraceback
275 self.ip_showtb = ip.showtraceback
276 self.ip_run_cell = ip.run_cell
276 self.ip_run_cell = ip.run_cell
277 self.shell = ip
277 self.shell = ip
278
278
279 self.formatter = pygments.formatters.get_formatter_by_name(formatter,
279 self.formatter = pygments.formatters.get_formatter_by_name(formatter,
280 style=style)
280 style=style)
281 self.python_lexer = pygments.lexers.get_lexer_by_name("py3")
281 self.python_lexer = pygments.lexers.get_lexer_by_name("py3")
282 self.format_rst = format_rst
282 self.format_rst = format_rst
283 if format_rst:
283 if format_rst:
284 self.rst_lexer = pygments.lexers.get_lexer_by_name("rst")
284 self.rst_lexer = pygments.lexers.get_lexer_by_name("rst")
285
285
286 # load user data and initialize data structures
286 # load user data and initialize data structures
287 self.reload()
287 self.reload()
288
288
289 def fload(self):
289 def fload(self):
290 """Load file object."""
290 """Load file object."""
291 # read data and parse into blocks
291 # read data and parse into blocks
292 if hasattr(self, 'fobj') and self.fobj is not None:
292 if hasattr(self, 'fobj') and self.fobj is not None:
293 self.fobj.close()
293 self.fobj.close()
294 if hasattr(self.src, "read"):
294 if hasattr(self.src, "read"):
295 # It seems to be a file or a file-like object
295 # It seems to be a file or a file-like object
296 self.fobj = self.src
296 self.fobj = self.src
297 else:
297 else:
298 # Assume it's a string or something that can be converted to one
298 # Assume it's a string or something that can be converted to one
299 self.fobj = openpy.open(self.fname)
299 self.fobj = openpy.open(self.fname)
300
300
301 def reload(self):
301 def reload(self):
302 """Reload source from disk and initialize state."""
302 """Reload source from disk and initialize state."""
303 self.fload()
303 self.fload()
304
304
305 self.src = "".join(openpy.strip_encoding_cookie(self.fobj))
305 self.src = "".join(openpy.strip_encoding_cookie(self.fobj))
306 src_b = [b.strip() for b in self.re_stop.split(self.src) if b]
306 src_b = [b.strip() for b in self.re_stop.split(self.src) if b]
307 self._silent = [bool(self.re_silent.findall(b)) for b in src_b]
307 self._silent = [bool(self.re_silent.findall(b)) for b in src_b]
308 self._auto = [bool(self.re_auto.findall(b)) for b in src_b]
308 self._auto = [bool(self.re_auto.findall(b)) for b in src_b]
309
309
310 # if auto_all is not given (def. None), we read it from the file
310 # if auto_all is not given (def. None), we read it from the file
311 if self.auto_all is None:
311 if self.auto_all is None:
312 self.auto_all = bool(self.re_auto_all.findall(src_b[0]))
312 self.auto_all = bool(self.re_auto_all.findall(src_b[0]))
313 else:
313 else:
314 self.auto_all = bool(self.auto_all)
314 self.auto_all = bool(self.auto_all)
315
315
316 # Clean the sources from all markup so it doesn't get displayed when
316 # Clean the sources from all markup so it doesn't get displayed when
317 # running the demo
317 # running the demo
318 src_blocks = []
318 src_blocks = []
319 auto_strip = lambda s: self.re_auto.sub('',s)
319 auto_strip = lambda s: self.re_auto.sub('',s)
320 for i,b in enumerate(src_b):
320 for i,b in enumerate(src_b):
321 if self._auto[i]:
321 if self._auto[i]:
322 src_blocks.append(auto_strip(b))
322 src_blocks.append(auto_strip(b))
323 else:
323 else:
324 src_blocks.append(b)
324 src_blocks.append(b)
325 # remove the auto_all marker
325 # remove the auto_all marker
326 src_blocks[0] = self.re_auto_all.sub('',src_blocks[0])
326 src_blocks[0] = self.re_auto_all.sub('',src_blocks[0])
327
327
328 self.nblocks = len(src_blocks)
328 self.nblocks = len(src_blocks)
329 self.src_blocks = src_blocks
329 self.src_blocks = src_blocks
330
330
331 # also build syntax-highlighted source
331 # also build syntax-highlighted source
332 self.src_blocks_colored = list(map(self.highlight,self.src_blocks))
332 self.src_blocks_colored = list(map(self.highlight,self.src_blocks))
333
333
334 # ensure clean namespace and seek offset
334 # ensure clean namespace and seek offset
335 self.reset()
335 self.reset()
336
336
337 def reset(self):
337 def reset(self):
338 """Reset the namespace and seek pointer to restart the demo"""
338 """Reset the namespace and seek pointer to restart the demo"""
339 self.user_ns = {}
339 self.user_ns = {}
340 self.finished = False
340 self.finished = False
341 self.block_index = 0
341 self.block_index = 0
342
342
343 def _validate_index(self,index):
343 def _validate_index(self,index):
344 if index<0 or index>=self.nblocks:
344 if index<0 or index>=self.nblocks:
345 raise ValueError('invalid block index %s' % index)
345 raise ValueError('invalid block index %s' % index)
346
346
347 def _get_index(self,index):
347 def _get_index(self,index):
348 """Get the current block index, validating and checking status.
348 """Get the current block index, validating and checking status.
349
349
350 Returns None if the demo is finished"""
350 Returns None if the demo is finished"""
351
351
352 if index is None:
352 if index is None:
353 if self.finished:
353 if self.finished:
354 print('Demo finished. Use <demo_name>.reset() if you want to rerun it.')
354 print('Demo finished. Use <demo_name>.reset() if you want to rerun it.')
355 return None
355 return None
356 index = self.block_index
356 index = self.block_index
357 else:
357 else:
358 self._validate_index(index)
358 self._validate_index(index)
359 return index
359 return index
360
360
361 def seek(self,index):
361 def seek(self,index):
362 """Move the current seek pointer to the given block.
362 """Move the current seek pointer to the given block.
363
363
364 You can use negative indices to seek from the end, with identical
364 You can use negative indices to seek from the end, with identical
365 semantics to those of Python lists."""
365 semantics to those of Python lists."""
366 if index<0:
366 if index<0:
367 index = self.nblocks + index
367 index = self.nblocks + index
368 self._validate_index(index)
368 self._validate_index(index)
369 self.block_index = index
369 self.block_index = index
370 self.finished = False
370 self.finished = False
371
371
372 def back(self,num=1):
372 def back(self,num=1):
373 """Move the seek pointer back num blocks (default is 1)."""
373 """Move the seek pointer back num blocks (default is 1)."""
374 self.seek(self.block_index-num)
374 self.seek(self.block_index-num)
375
375
376 def jump(self,num=1):
376 def jump(self,num=1):
377 """Jump a given number of blocks relative to the current one.
377 """Jump a given number of blocks relative to the current one.
378
378
379 The offset can be positive or negative, defaults to 1."""
379 The offset can be positive or negative, defaults to 1."""
380 self.seek(self.block_index+num)
380 self.seek(self.block_index+num)
381
381
382 def again(self):
382 def again(self):
383 """Move the seek pointer back one block and re-execute."""
383 """Move the seek pointer back one block and re-execute."""
384 self.back(1)
384 self.back(1)
385 self()
385 self()
386
386
387 def edit(self,index=None):
387 def edit(self,index=None):
388 """Edit a block.
388 """Edit a block.
389
389
390 If no number is given, use the last block executed.
390 If no number is given, use the last block executed.
391
391
392 This edits the in-memory copy of the demo, it does NOT modify the
392 This edits the in-memory copy of the demo, it does NOT modify the
393 original source file. If you want to do that, simply open the file in
393 original source file. If you want to do that, simply open the file in
394 an editor and use reload() when you make changes to the file. This
394 an editor and use reload() when you make changes to the file. This
395 method is meant to let you change a block during a demonstration for
395 method is meant to let you change a block during a demonstration for
396 explanatory purposes, without damaging your original script."""
396 explanatory purposes, without damaging your original script."""
397
397
398 index = self._get_index(index)
398 index = self._get_index(index)
399 if index is None:
399 if index is None:
400 return
400 return
401 # decrease the index by one (unless we're at the very beginning), so
401 # decrease the index by one (unless we're at the very beginning), so
402 # that the default demo.edit() call opens up the sblock we've last run
402 # that the default demo.edit() call opens up the sblock we've last run
403 if index>0:
403 if index>0:
404 index -= 1
404 index -= 1
405
405
406 filename = self.shell.mktempfile(self.src_blocks[index])
406 filename = self.shell.mktempfile(self.src_blocks[index])
407 self.shell.hooks.editor(filename, 1)
407 self.shell.hooks.editor(filename, 1)
408 with open(Path(filename), "r", encoding="utf-8") as f:
408 with open(Path(filename), "r", encoding="utf-8") as f:
409 new_block = f.read()
409 new_block = f.read()
410 # update the source and colored block
410 # update the source and colored block
411 self.src_blocks[index] = new_block
411 self.src_blocks[index] = new_block
412 self.src_blocks_colored[index] = self.highlight(new_block)
412 self.src_blocks_colored[index] = self.highlight(new_block)
413 self.block_index = index
413 self.block_index = index
414 # call to run with the newly edited index
414 # call to run with the newly edited index
415 self()
415 self()
416
416
417 def show(self,index=None):
417 def show(self,index=None):
418 """Show a single block on screen"""
418 """Show a single block on screen"""
419
419
420 index = self._get_index(index)
420 index = self._get_index(index)
421 if index is None:
421 if index is None:
422 return
422 return
423
423
424 print(self.marquee('<%s> block # %s (%s remaining)' %
424 print(self.marquee('<%s> block # %s (%s remaining)' %
425 (self.title,index,self.nblocks-index-1)))
425 (self.title,index,self.nblocks-index-1)))
426 print(self.src_blocks_colored[index])
426 print(self.src_blocks_colored[index])
427 sys.stdout.flush()
427 sys.stdout.flush()
428
428
429 def show_all(self):
429 def show_all(self):
430 """Show entire demo on screen, block by block"""
430 """Show entire demo on screen, block by block"""
431
431
432 self.title
432 fname = self.title
433 title = self.title
433 title = self.title
434 nblocks = self.nblocks
434 nblocks = self.nblocks
435 silent = self._silent
435 silent = self._silent
436 marquee = self.marquee
436 marquee = self.marquee
437 for index,block in enumerate(self.src_blocks_colored):
437 for index,block in enumerate(self.src_blocks_colored):
438 if silent[index]:
438 if silent[index]:
439 print(marquee('<%s> SILENT block # %s (%s remaining)' %
439 print(marquee('<%s> SILENT block # %s (%s remaining)' %
440 (title,index,nblocks-index-1)))
440 (title,index,nblocks-index-1)))
441 else:
441 else:
442 print(marquee('<%s> block # %s (%s remaining)' %
442 print(marquee('<%s> block # %s (%s remaining)' %
443 (title,index,nblocks-index-1)))
443 (title,index,nblocks-index-1)))
444 print(block, end=' ')
444 print(block, end=' ')
445 sys.stdout.flush()
445 sys.stdout.flush()
446
446
447 def run_cell(self,source):
447 def run_cell(self,source):
448 """Execute a string with one or more lines of code"""
448 """Execute a string with one or more lines of code"""
449
449
450 exec(source, self.user_ns)
450 exec(source, self.user_ns)
451
451
452 def __call__(self,index=None):
452 def __call__(self,index=None):
453 """run a block of the demo.
453 """run a block of the demo.
454
454
455 If index is given, it should be an integer >=1 and <= nblocks. This
455 If index is given, it should be an integer >=1 and <= nblocks. This
456 means that the calling convention is one off from typical Python
456 means that the calling convention is one off from typical Python
457 lists. The reason for the inconsistency is that the demo always
457 lists. The reason for the inconsistency is that the demo always
458 prints 'Block n/N, and N is the total, so it would be very odd to use
458 prints 'Block n/N, and N is the total, so it would be very odd to use
459 zero-indexing here."""
459 zero-indexing here."""
460
460
461 index = self._get_index(index)
461 index = self._get_index(index)
462 if index is None:
462 if index is None:
463 return
463 return
464 try:
464 try:
465 marquee = self.marquee
465 marquee = self.marquee
466 next_block = self.src_blocks[index]
466 next_block = self.src_blocks[index]
467 self.block_index += 1
467 self.block_index += 1
468 if self._silent[index]:
468 if self._silent[index]:
469 print(marquee('Executing silent block # %s (%s remaining)' %
469 print(marquee('Executing silent block # %s (%s remaining)' %
470 (index,self.nblocks-index-1)))
470 (index,self.nblocks-index-1)))
471 else:
471 else:
472 self.pre_cmd()
472 self.pre_cmd()
473 self.show(index)
473 self.show(index)
474 if self.auto_all or self._auto[index]:
474 if self.auto_all or self._auto[index]:
475 print(marquee('output:'))
475 print(marquee('output:'))
476 else:
476 else:
477 print(marquee('Press <q> to quit, <Enter> to execute...'), end=' ')
477 print(marquee('Press <q> to quit, <Enter> to execute...'), end=' ')
478 ans = py3compat.input().strip()
478 ans = py3compat.input().strip()
479 if ans:
479 if ans:
480 print(marquee('Block NOT executed'))
480 print(marquee('Block NOT executed'))
481 return
481 return
482 try:
482 try:
483 save_argv = sys.argv
483 save_argv = sys.argv
484 sys.argv = self.sys_argv
484 sys.argv = self.sys_argv
485 self.run_cell(next_block)
485 self.run_cell(next_block)
486 self.post_cmd()
486 self.post_cmd()
487 finally:
487 finally:
488 sys.argv = save_argv
488 sys.argv = save_argv
489
489
490 except:
490 except:
491 if self.inside_ipython:
491 if self.inside_ipython:
492 self.ip_showtb(filename=self.fname)
492 self.ip_showtb(filename=self.fname)
493 else:
493 else:
494 if self.inside_ipython:
494 if self.inside_ipython:
495 self.ip_ns.update(self.user_ns)
495 self.ip_ns.update(self.user_ns)
496
496
497 if self.block_index == self.nblocks:
497 if self.block_index == self.nblocks:
498 mq1 = self.marquee('END OF DEMO')
498 mq1 = self.marquee('END OF DEMO')
499 if mq1:
499 if mq1:
500 # avoid spurious print if empty marquees are used
500 # avoid spurious print if empty marquees are used
501 print()
501 print()
502 print(mq1)
502 print(mq1)
503 print(self.marquee('Use <demo_name>.reset() if you want to rerun it.'))
503 print(self.marquee('Use <demo_name>.reset() if you want to rerun it.'))
504 self.finished = True
504 self.finished = True
505
505
506 # These methods are meant to be overridden by subclasses who may wish to
506 # These methods are meant to be overridden by subclasses who may wish to
507 # customize the behavior of of their demos.
507 # customize the behavior of of their demos.
508 def marquee(self,txt='',width=78,mark='*'):
508 def marquee(self,txt='',width=78,mark='*'):
509 """Return the input string centered in a 'marquee'."""
509 """Return the input string centered in a 'marquee'."""
510 return marquee(txt,width,mark)
510 return marquee(txt,width,mark)
511
511
512 def pre_cmd(self):
512 def pre_cmd(self):
513 """Method called before executing each block."""
513 """Method called before executing each block."""
514 pass
514 pass
515
515
516 def post_cmd(self):
516 def post_cmd(self):
517 """Method called after executing each block."""
517 """Method called after executing each block."""
518 pass
518 pass
519
519
520 def highlight(self, block):
520 def highlight(self, block):
521 """Method called on each block to highlight it content"""
521 """Method called on each block to highlight it content"""
522 tokens = pygments.lex(block, self.python_lexer)
522 tokens = pygments.lex(block, self.python_lexer)
523 if self.format_rst:
523 if self.format_rst:
524 from pygments.token import Token
524 from pygments.token import Token
525 toks = []
525 toks = []
526 for token in tokens:
526 for token in tokens:
527 if token[0] == Token.String.Doc and len(token[1]) > 6:
527 if token[0] == Token.String.Doc and len(token[1]) > 6:
528 toks += pygments.lex(token[1][:3], self.python_lexer)
528 toks += pygments.lex(token[1][:3], self.python_lexer)
529 # parse doc string content by rst lexer
529 # parse doc string content by rst lexer
530 toks += pygments.lex(token[1][3:-3], self.rst_lexer)
530 toks += pygments.lex(token[1][3:-3], self.rst_lexer)
531 toks += pygments.lex(token[1][-3:], self.python_lexer)
531 toks += pygments.lex(token[1][-3:], self.python_lexer)
532 elif token[0] == Token.Comment.Single:
532 elif token[0] == Token.Comment.Single:
533 toks.append((Token.Comment.Single, token[1][0]))
533 toks.append((Token.Comment.Single, token[1][0]))
534 # parse comment content by rst lexer
534 # parse comment content by rst lexer
535 # remove the extra newline added by rst lexer
535 # remove the extra newline added by rst lexer
536 toks += list(pygments.lex(token[1][1:], self.rst_lexer))[:-1]
536 toks += list(pygments.lex(token[1][1:], self.rst_lexer))[:-1]
537 else:
537 else:
538 toks.append(token)
538 toks.append(token)
539 tokens = toks
539 tokens = toks
540 return pygments.format(tokens, self.formatter)
540 return pygments.format(tokens, self.formatter)
541
541
542
542
543 class IPythonDemo(Demo):
543 class IPythonDemo(Demo):
544 """Class for interactive demos with IPython's input processing applied.
544 """Class for interactive demos with IPython's input processing applied.
545
545
546 This subclasses Demo, but instead of executing each block by the Python
546 This subclasses Demo, but instead of executing each block by the Python
547 interpreter (via exec), it actually calls IPython on it, so that any input
547 interpreter (via exec), it actually calls IPython on it, so that any input
548 filters which may be in place are applied to the input block.
548 filters which may be in place are applied to the input block.
549
549
550 If you have an interactive environment which exposes special input
550 If you have an interactive environment which exposes special input
551 processing, you can use this class instead to write demo scripts which
551 processing, you can use this class instead to write demo scripts which
552 operate exactly as if you had typed them interactively. The default Demo
552 operate exactly as if you had typed them interactively. The default Demo
553 class requires the input to be valid, pure Python code.
553 class requires the input to be valid, pure Python code.
554 """
554 """
555
555
556 def run_cell(self,source):
556 def run_cell(self,source):
557 """Execute a string with one or more lines of code"""
557 """Execute a string with one or more lines of code"""
558
558
559 self.shell.run_cell(source)
559 self.shell.run_cell(source)
560
560
561 class LineDemo(Demo):
561 class LineDemo(Demo):
562 """Demo where each line is executed as a separate block.
562 """Demo where each line is executed as a separate block.
563
563
564 The input script should be valid Python code.
564 The input script should be valid Python code.
565
565
566 This class doesn't require any markup at all, and it's meant for simple
566 This class doesn't require any markup at all, and it's meant for simple
567 scripts (with no nesting or any kind of indentation) which consist of
567 scripts (with no nesting or any kind of indentation) which consist of
568 multiple lines of input to be executed, one at a time, as if they had been
568 multiple lines of input to be executed, one at a time, as if they had been
569 typed in the interactive prompt.
569 typed in the interactive prompt.
570
570
571 Note: the input can not have *any* indentation, which means that only
571 Note: the input can not have *any* indentation, which means that only
572 single-lines of input are accepted, not even function definitions are
572 single-lines of input are accepted, not even function definitions are
573 valid."""
573 valid."""
574
574
575 def reload(self):
575 def reload(self):
576 """Reload source from disk and initialize state."""
576 """Reload source from disk and initialize state."""
577 # read data and parse into blocks
577 # read data and parse into blocks
578 self.fload()
578 self.fload()
579 lines = self.fobj.readlines()
579 lines = self.fobj.readlines()
580 src_b = [l for l in lines if l.strip()]
580 src_b = [l for l in lines if l.strip()]
581 nblocks = len(src_b)
581 nblocks = len(src_b)
582 self.src = ''.join(lines)
582 self.src = ''.join(lines)
583 self._silent = [False]*nblocks
583 self._silent = [False]*nblocks
584 self._auto = [True]*nblocks
584 self._auto = [True]*nblocks
585 self.auto_all = True
585 self.auto_all = True
586 self.nblocks = nblocks
586 self.nblocks = nblocks
587 self.src_blocks = src_b
587 self.src_blocks = src_b
588
588
589 # also build syntax-highlighted source
589 # also build syntax-highlighted source
590 self.src_blocks_colored = list(map(self.highlight,self.src_blocks))
590 self.src_blocks_colored = list(map(self.highlight,self.src_blocks))
591
591
592 # ensure clean namespace and seek offset
592 # ensure clean namespace and seek offset
593 self.reset()
593 self.reset()
594
594
595
595
596 class IPythonLineDemo(IPythonDemo,LineDemo):
596 class IPythonLineDemo(IPythonDemo,LineDemo):
597 """Variant of the LineDemo class whose input is processed by IPython."""
597 """Variant of the LineDemo class whose input is processed by IPython."""
598 pass
598 pass
599
599
600
600
601 class ClearMixin(object):
601 class ClearMixin(object):
602 """Use this mixin to make Demo classes with less visual clutter.
602 """Use this mixin to make Demo classes with less visual clutter.
603
603
604 Demos using this mixin will clear the screen before every block and use
604 Demos using this mixin will clear the screen before every block and use
605 blank marquees.
605 blank marquees.
606
606
607 Note that in order for the methods defined here to actually override those
607 Note that in order for the methods defined here to actually override those
608 of the classes it's mixed with, it must go /first/ in the inheritance
608 of the classes it's mixed with, it must go /first/ in the inheritance
609 tree. For example:
609 tree. For example:
610
610
611 class ClearIPDemo(ClearMixin,IPythonDemo): pass
611 class ClearIPDemo(ClearMixin,IPythonDemo): pass
612
612
613 will provide an IPythonDemo class with the mixin's features.
613 will provide an IPythonDemo class with the mixin's features.
614 """
614 """
615
615
616 def marquee(self,txt='',width=78,mark='*'):
616 def marquee(self,txt='',width=78,mark='*'):
617 """Blank marquee that returns '' no matter what the input."""
617 """Blank marquee that returns '' no matter what the input."""
618 return ''
618 return ''
619
619
620 def pre_cmd(self):
620 def pre_cmd(self):
621 """Method called before executing each block.
621 """Method called before executing each block.
622
622
623 This one simply clears the screen."""
623 This one simply clears the screen."""
624 from IPython.utils.terminal import _term_clear
624 from IPython.utils.terminal import _term_clear
625 _term_clear()
625 _term_clear()
626
626
627 class ClearDemo(ClearMixin,Demo):
627 class ClearDemo(ClearMixin,Demo):
628 pass
628 pass
629
629
630
630
631 class ClearIPDemo(ClearMixin,IPythonDemo):
631 class ClearIPDemo(ClearMixin,IPythonDemo):
632 pass
632 pass
633
633
634
634
635 def slide(file_path, noclear=False, format_rst=True, formatter="terminal",
635 def slide(file_path, noclear=False, format_rst=True, formatter="terminal",
636 style="native", auto_all=False, delimiter='...'):
636 style="native", auto_all=False, delimiter='...'):
637 if noclear:
637 if noclear:
638 demo_class = Demo
638 demo_class = Demo
639 else:
639 else:
640 demo_class = ClearDemo
640 demo_class = ClearDemo
641 demo = demo_class(file_path, format_rst=format_rst, formatter=formatter,
641 demo = demo_class(file_path, format_rst=format_rst, formatter=formatter,
642 style=style, auto_all=auto_all)
642 style=style, auto_all=auto_all)
643 while not demo.finished:
643 while not demo.finished:
644 demo()
644 demo()
645 try:
645 try:
646 py3compat.input('\n' + delimiter)
646 py3compat.input('\n' + delimiter)
647 except KeyboardInterrupt:
647 except KeyboardInterrupt:
648 exit(1)
648 exit(1)
649
649
650 if __name__ == '__main__':
650 if __name__ == '__main__':
651 import argparse
651 import argparse
652 parser = argparse.ArgumentParser(description='Run python demos')
652 parser = argparse.ArgumentParser(description='Run python demos')
653 parser.add_argument('--noclear', '-C', action='store_true',
653 parser.add_argument('--noclear', '-C', action='store_true',
654 help='Do not clear terminal on each slide')
654 help='Do not clear terminal on each slide')
655 parser.add_argument('--rst', '-r', action='store_true',
655 parser.add_argument('--rst', '-r', action='store_true',
656 help='Highlight comments and dostrings as rst')
656 help='Highlight comments and dostrings as rst')
657 parser.add_argument('--formatter', '-f', default='terminal',
657 parser.add_argument('--formatter', '-f', default='terminal',
658 help='pygments formatter name could be: terminal, '
658 help='pygments formatter name could be: terminal, '
659 'terminal256, terminal16m')
659 'terminal256, terminal16m')
660 parser.add_argument('--style', '-s', default='default',
660 parser.add_argument('--style', '-s', default='default',
661 help='pygments style name')
661 help='pygments style name')
662 parser.add_argument('--auto', '-a', action='store_true',
662 parser.add_argument('--auto', '-a', action='store_true',
663 help='Run all blocks automatically without'
663 help='Run all blocks automatically without'
664 'confirmation')
664 'confirmation')
665 parser.add_argument('--delimiter', '-d', default='...',
665 parser.add_argument('--delimiter', '-d', default='...',
666 help='slides delimiter added after each slide run')
666 help='slides delimiter added after each slide run')
667 parser.add_argument('file', nargs=1,
667 parser.add_argument('file', nargs=1,
668 help='python demo file')
668 help='python demo file')
669 args = parser.parse_args()
669 args = parser.parse_args()
670 slide(args.file[0], noclear=args.noclear, format_rst=args.rst,
670 slide(args.file[0], noclear=args.noclear, format_rst=args.rst,
671 formatter=args.formatter, style=args.style, auto_all=args.auto,
671 formatter=args.formatter, style=args.style, auto_all=args.auto,
672 delimiter=args.delimiter)
672 delimiter=args.delimiter)
@@ -1,278 +1,272
1 """Tests for IPython.lib.display.
1 """Tests for IPython.lib.display.
2
2
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2012, the IPython Development Team.
5 # Copyright (c) 2012, the IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from tempfile import NamedTemporaryFile, mkdtemp
15 from tempfile import NamedTemporaryFile, mkdtemp
16 from os.path import split, join as pjoin, dirname
16 from os.path import split, join as pjoin, dirname
17 import pathlib
17 import pathlib
18 from unittest import TestCase, mock
18 from unittest import TestCase, mock
19 import struct
19 import struct
20 import wave
20 import wave
21 from io import BytesIO
21 from io import BytesIO
22
22
23 # Third-party imports
23 # Third-party imports
24 import pytest
24 import pytest
25
25
26 try:
26 try:
27 import numpy
27 import numpy
28 except ImportError:
28 except ImportError:
29 pass
29 pass
30
30
31 # Our own imports
31 # Our own imports
32 from IPython.lib import display
32 from IPython.lib import display
33
33
34 from IPython.testing.decorators import skipif_not_numpy
34 from IPython.testing.decorators import skipif_not_numpy
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Classes and functions
37 # Classes and functions
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 #--------------------------
40 #--------------------------
41 # FileLink tests
41 # FileLink tests
42 #--------------------------
42 #--------------------------
43
43
44 def test_instantiation_FileLink():
44 def test_instantiation_FileLink():
45 """FileLink: Test class can be instantiated"""
45 """FileLink: Test class can be instantiated"""
46 fl = display.FileLink('example.txt')
46 fl = display.FileLink('example.txt')
47 # TODO: remove if when only Python >= 3.6 is supported
47 # TODO: remove if when only Python >= 3.6 is supported
48 fl = display.FileLink(pathlib.PurePath('example.txt'))
48 fl = display.FileLink(pathlib.PurePath('example.txt'))
49
49
50 def test_warning_on_non_existent_path_FileLink():
50 def test_warning_on_non_existent_path_FileLink():
51 """FileLink: Calling _repr_html_ on non-existent files returns a warning"""
51 """FileLink: Calling _repr_html_ on non-existent files returns a warning"""
52 fl = display.FileLink("example.txt")
52 fl = display.FileLink("example.txt")
53 assert fl._repr_html_().startswith("Path (<tt>example.txt</tt>)")
53 assert fl._repr_html_().startswith("Path (<tt>example.txt</tt>)")
54
54
55
55
56 def test_existing_path_FileLink():
56 def test_existing_path_FileLink():
57 """FileLink: Calling _repr_html_ functions as expected on existing filepath
57 """FileLink: Calling _repr_html_ functions as expected on existing filepath
58 """
58 """
59 with NamedTemporaryFile() as tf:
59 tf = NamedTemporaryFile()
60 fl = display.FileLink(tf.name)
60 fl = display.FileLink(tf.name)
61 actual = fl._repr_html_()
61 actual = fl._repr_html_()
62 expected = "<a href='%s' target='_blank'>%s</a><br>" % (tf.name, tf.name)
62 expected = "<a href='%s' target='_blank'>%s</a><br>" % (tf.name, tf.name)
63 assert actual == expected
63 assert actual == expected
64
64
65
65
66 def test_existing_path_FileLink_repr():
66 def test_existing_path_FileLink_repr():
67 """FileLink: Calling repr() functions as expected on existing filepath
67 """FileLink: Calling repr() functions as expected on existing filepath
68 """
68 """
69 with NamedTemporaryFile() as tf:
69 tf = NamedTemporaryFile()
70 fl = display.FileLink(tf.name)
70 fl = display.FileLink(tf.name)
71 actual = repr(fl)
71 actual = repr(fl)
72 expected = tf.name
72 expected = tf.name
73 assert actual == expected
73 assert actual == expected
74
74
75
75
76 def test_error_on_directory_to_FileLink():
76 def test_error_on_directory_to_FileLink():
77 """FileLink: Raises error when passed directory
77 """FileLink: Raises error when passed directory
78 """
78 """
79 td = mkdtemp()
79 td = mkdtemp()
80 pytest.raises(ValueError, display.FileLink, td)
80 pytest.raises(ValueError, display.FileLink, td)
81
81
82 #--------------------------
82 #--------------------------
83 # FileLinks tests
83 # FileLinks tests
84 #--------------------------
84 #--------------------------
85
85
86 def test_instantiation_FileLinks():
86 def test_instantiation_FileLinks():
87 """FileLinks: Test class can be instantiated
87 """FileLinks: Test class can be instantiated
88 """
88 """
89 fls = display.FileLinks('example')
89 fls = display.FileLinks('example')
90
90
91 def test_warning_on_non_existent_path_FileLinks():
91 def test_warning_on_non_existent_path_FileLinks():
92 """FileLinks: Calling _repr_html_ on non-existent files returns a warning"""
92 """FileLinks: Calling _repr_html_ on non-existent files returns a warning"""
93 fls = display.FileLinks("example")
93 fls = display.FileLinks("example")
94 assert fls._repr_html_().startswith("Path (<tt>example</tt>)")
94 assert fls._repr_html_().startswith("Path (<tt>example</tt>)")
95
95
96
96
97 def test_existing_path_FileLinks():
97 def test_existing_path_FileLinks():
98 """FileLinks: Calling _repr_html_ functions as expected on existing dir
98 """FileLinks: Calling _repr_html_ functions as expected on existing dir
99 """
99 """
100 td = mkdtemp()
100 td = mkdtemp()
101 with NamedTemporaryFile(dir=td) as tf1, NamedTemporaryFile(dir=td) as tf2:
101 tf1 = NamedTemporaryFile(dir=td)
102 fl = display.FileLinks(td)
102 tf2 = NamedTemporaryFile(dir=td)
103 actual = fl._repr_html_()
103 fl = display.FileLinks(td)
104 actual = actual.split("\n")
104 actual = fl._repr_html_()
105 actual.sort()
105 actual = actual.split('\n')
106 # the links should always have forward slashes, even on windows, so replace
106 actual.sort()
107 # backslashes with forward slashes here
107 # the links should always have forward slashes, even on windows, so replace
108 expected = [
108 # backslashes with forward slashes here
109 "%s/<br>" % td,
109 expected = ["%s/<br>" % td,
110 "&nbsp;&nbsp;<a href='%s' target='_blank'>%s</a><br>"
110 "&nbsp;&nbsp;<a href='%s' target='_blank'>%s</a><br>" %\
111 % (tf2.name.replace("\\", "/"), split(tf2.name)[1]),
111 (tf2.name.replace("\\","/"),split(tf2.name)[1]),
112 "&nbsp;&nbsp;<a href='%s' target='_blank'>%s</a><br>"
112 "&nbsp;&nbsp;<a href='%s' target='_blank'>%s</a><br>" %\
113 % (tf1.name.replace("\\", "/"), split(tf1.name)[1]),
113 (tf1.name.replace("\\","/"),split(tf1.name)[1])]
114 ]
114 expected.sort()
115 expected.sort()
115 # We compare the sorted list of links here as that's more reliable
116 # We compare the sorted list of links here as that's more reliable
116 assert actual == expected
117 assert actual == expected
118
117
119
118
120 def test_existing_path_FileLinks_alt_formatter():
119 def test_existing_path_FileLinks_alt_formatter():
121 """FileLinks: Calling _repr_html_ functions as expected w/ an alt formatter
120 """FileLinks: Calling _repr_html_ functions as expected w/ an alt formatter
122 """
121 """
123 td = mkdtemp()
122 td = mkdtemp()
124 with NamedTemporaryFile(dir=td), NamedTemporaryFile(dir=td):
123 tf1 = NamedTemporaryFile(dir=td)
125
124 tf2 = NamedTemporaryFile(dir=td)
126 def fake_formatter(dirname, fnames, included_suffixes):
125 def fake_formatter(dirname,fnames,included_suffixes):
127 return ["hello", "world"]
126 return ["hello","world"]
128
127 fl = display.FileLinks(td,notebook_display_formatter=fake_formatter)
129 fl = display.FileLinks(td, notebook_display_formatter=fake_formatter)
128 actual = fl._repr_html_()
130 actual = fl._repr_html_()
129 actual = actual.split('\n')
131 actual = actual.split("\n")
130 actual.sort()
132 actual.sort()
131 expected = ["hello","world"]
133 expected = ["hello", "world"]
132 expected.sort()
134 expected.sort()
133 # We compare the sorted list of links here as that's more reliable
135 # We compare the sorted list of links here as that's more reliable
134 assert actual == expected
136 assert actual == expected
137
135
138
136
139 def test_existing_path_FileLinks_repr():
137 def test_existing_path_FileLinks_repr():
140 """FileLinks: Calling repr() functions as expected on existing directory """
138 """FileLinks: Calling repr() functions as expected on existing directory """
141 td = mkdtemp()
139 td = mkdtemp()
142 with NamedTemporaryFile(dir=td) as tf1, NamedTemporaryFile(dir=td) as tf2:
140 tf1 = NamedTemporaryFile(dir=td)
143 fl = display.FileLinks(td)
141 tf2 = NamedTemporaryFile(dir=td)
144 actual = repr(fl)
142 fl = display.FileLinks(td)
145 actual = actual.split("\n")
143 actual = repr(fl)
146 actual.sort()
144 actual = actual.split('\n')
147 expected = [
145 actual.sort()
148 "%s/" % td,
146 expected = ['%s/' % td, ' %s' % split(tf1.name)[1],' %s' % split(tf2.name)[1]]
149 " %s" % split(tf1.name)[1],
147 expected.sort()
150 " %s" % split(tf2.name)[1],
148 # We compare the sorted list of links here as that's more reliable
151 ]
149 assert actual == expected
152 expected.sort()
153 # We compare the sorted list of links here as that's more reliable
154 assert actual == expected
155
150
156
151
157 def test_existing_path_FileLinks_repr_alt_formatter():
152 def test_existing_path_FileLinks_repr_alt_formatter():
158 """FileLinks: Calling repr() functions as expected w/ alt formatter
153 """FileLinks: Calling repr() functions as expected w/ alt formatter
159 """
154 """
160 td = mkdtemp()
155 td = mkdtemp()
161 with NamedTemporaryFile(dir=td), NamedTemporaryFile(dir=td):
156 tf1 = NamedTemporaryFile(dir=td)
162
157 tf2 = NamedTemporaryFile(dir=td)
163 def fake_formatter(dirname, fnames, included_suffixes):
158 def fake_formatter(dirname,fnames,included_suffixes):
164 return ["hello", "world"]
159 return ["hello","world"]
165
160 fl = display.FileLinks(td,terminal_display_formatter=fake_formatter)
166 fl = display.FileLinks(td, terminal_display_formatter=fake_formatter)
161 actual = repr(fl)
167 actual = repr(fl)
162 actual = actual.split('\n')
168 actual = actual.split("\n")
163 actual.sort()
169 actual.sort()
164 expected = ["hello","world"]
170 expected = ["hello", "world"]
165 expected.sort()
171 expected.sort()
166 # We compare the sorted list of links here as that's more reliable
172 # We compare the sorted list of links here as that's more reliable
167 assert actual == expected
173 assert actual == expected
174
168
175
169
176 def test_error_on_file_to_FileLinks():
170 def test_error_on_file_to_FileLinks():
177 """FileLinks: Raises error when passed file
171 """FileLinks: Raises error when passed file
178 """
172 """
179 td = mkdtemp()
173 td = mkdtemp()
180 with NamedTemporaryFile(dir=td) as tf:
174 tf1 = NamedTemporaryFile(dir=td)
181 pytest.raises(ValueError, display.FileLinks, tf.name)
175 pytest.raises(ValueError, display.FileLinks, tf1.name)
182
176
183
177
184 def test_recursive_FileLinks():
178 def test_recursive_FileLinks():
185 """FileLinks: Does not recurse when recursive=False
179 """FileLinks: Does not recurse when recursive=False
186 """
180 """
187 td = mkdtemp()
181 td = mkdtemp()
188 with NamedTemporaryFile(dir=td):
182 tf = NamedTemporaryFile(dir=td)
189 subtd = mkdtemp(dir=td)
183 subtd = mkdtemp(dir=td)
190 with NamedTemporaryFile(dir=subtd):
184 subtf = NamedTemporaryFile(dir=subtd)
191 fl = display.FileLinks(td)
185 fl = display.FileLinks(td)
192 actual = str(fl)
186 actual = str(fl)
193 actual = actual.split("\n")
187 actual = actual.split('\n')
194 assert len(actual) == 4, actual
188 assert len(actual) == 4, actual
195 fl = display.FileLinks(td, recursive=False)
189 fl = display.FileLinks(td, recursive=False)
196 actual = str(fl)
190 actual = str(fl)
197 actual = actual.split("\n")
191 actual = actual.split('\n')
198 assert len(actual) == 2, actual
192 assert len(actual) == 2, actual
199
193
200 def test_audio_from_file():
194 def test_audio_from_file():
201 path = pjoin(dirname(__file__), 'test.wav')
195 path = pjoin(dirname(__file__), 'test.wav')
202 display.Audio(filename=path)
196 display.Audio(filename=path)
203
197
204 class TestAudioDataWithNumpy(TestCase):
198 class TestAudioDataWithNumpy(TestCase):
205
199
206 @skipif_not_numpy
200 @skipif_not_numpy
207 def test_audio_from_numpy_array(self):
201 def test_audio_from_numpy_array(self):
208 test_tone = get_test_tone()
202 test_tone = get_test_tone()
209 audio = display.Audio(test_tone, rate=44100)
203 audio = display.Audio(test_tone, rate=44100)
210 assert len(read_wav(audio.data)) == len(test_tone)
204 assert len(read_wav(audio.data)) == len(test_tone)
211
205
212 @skipif_not_numpy
206 @skipif_not_numpy
213 def test_audio_from_list(self):
207 def test_audio_from_list(self):
214 test_tone = get_test_tone()
208 test_tone = get_test_tone()
215 audio = display.Audio(list(test_tone), rate=44100)
209 audio = display.Audio(list(test_tone), rate=44100)
216 assert len(read_wav(audio.data)) == len(test_tone)
210 assert len(read_wav(audio.data)) == len(test_tone)
217
211
218 @skipif_not_numpy
212 @skipif_not_numpy
219 def test_audio_from_numpy_array_without_rate_raises(self):
213 def test_audio_from_numpy_array_without_rate_raises(self):
220 self.assertRaises(ValueError, display.Audio, get_test_tone())
214 self.assertRaises(ValueError, display.Audio, get_test_tone())
221
215
222 @skipif_not_numpy
216 @skipif_not_numpy
223 def test_audio_data_normalization(self):
217 def test_audio_data_normalization(self):
224 expected_max_value = numpy.iinfo(numpy.int16).max
218 expected_max_value = numpy.iinfo(numpy.int16).max
225 for scale in [1, 0.5, 2]:
219 for scale in [1, 0.5, 2]:
226 audio = display.Audio(get_test_tone(scale), rate=44100)
220 audio = display.Audio(get_test_tone(scale), rate=44100)
227 actual_max_value = numpy.max(numpy.abs(read_wav(audio.data)))
221 actual_max_value = numpy.max(numpy.abs(read_wav(audio.data)))
228 assert actual_max_value == expected_max_value
222 assert actual_max_value == expected_max_value
229
223
230 @skipif_not_numpy
224 @skipif_not_numpy
231 def test_audio_data_without_normalization(self):
225 def test_audio_data_without_normalization(self):
232 max_int16 = numpy.iinfo(numpy.int16).max
226 max_int16 = numpy.iinfo(numpy.int16).max
233 for scale in [1, 0.5, 0.2]:
227 for scale in [1, 0.5, 0.2]:
234 test_tone = get_test_tone(scale)
228 test_tone = get_test_tone(scale)
235 test_tone_max_abs = numpy.max(numpy.abs(test_tone))
229 test_tone_max_abs = numpy.max(numpy.abs(test_tone))
236 expected_max_value = int(max_int16 * test_tone_max_abs)
230 expected_max_value = int(max_int16 * test_tone_max_abs)
237 audio = display.Audio(test_tone, rate=44100, normalize=False)
231 audio = display.Audio(test_tone, rate=44100, normalize=False)
238 actual_max_value = numpy.max(numpy.abs(read_wav(audio.data)))
232 actual_max_value = numpy.max(numpy.abs(read_wav(audio.data)))
239 assert actual_max_value == expected_max_value
233 assert actual_max_value == expected_max_value
240
234
241 def test_audio_data_without_normalization_raises_for_invalid_data(self):
235 def test_audio_data_without_normalization_raises_for_invalid_data(self):
242 self.assertRaises(
236 self.assertRaises(
243 ValueError,
237 ValueError,
244 lambda: display.Audio([1.001], rate=44100, normalize=False))
238 lambda: display.Audio([1.001], rate=44100, normalize=False))
245 self.assertRaises(
239 self.assertRaises(
246 ValueError,
240 ValueError,
247 lambda: display.Audio([-1.001], rate=44100, normalize=False))
241 lambda: display.Audio([-1.001], rate=44100, normalize=False))
248
242
249 def simulate_numpy_not_installed():
243 def simulate_numpy_not_installed():
250 try:
244 try:
251 import numpy
245 import numpy
252 return mock.patch('numpy.array', mock.MagicMock(side_effect=ImportError))
246 return mock.patch('numpy.array', mock.MagicMock(side_effect=ImportError))
253 except ModuleNotFoundError:
247 except ModuleNotFoundError:
254 return lambda x:x
248 return lambda x:x
255
249
256 @simulate_numpy_not_installed()
250 @simulate_numpy_not_installed()
257 class TestAudioDataWithoutNumpy(TestAudioDataWithNumpy):
251 class TestAudioDataWithoutNumpy(TestAudioDataWithNumpy):
258 # All tests from `TestAudioDataWithNumpy` are inherited.
252 # All tests from `TestAudioDataWithNumpy` are inherited.
259
253
260 @skipif_not_numpy
254 @skipif_not_numpy
261 def test_audio_raises_for_nested_list(self):
255 def test_audio_raises_for_nested_list(self):
262 stereo_signal = [list(get_test_tone())] * 2
256 stereo_signal = [list(get_test_tone())] * 2
263 self.assertRaises(TypeError, lambda: display.Audio(stereo_signal, rate=44100))
257 self.assertRaises(TypeError, lambda: display.Audio(stereo_signal, rate=44100))
264
258
265
259
266 @skipif_not_numpy
260 @skipif_not_numpy
267 def get_test_tone(scale=1):
261 def get_test_tone(scale=1):
268 return numpy.sin(2 * numpy.pi * 440 * numpy.linspace(0, 1, 44100)) * scale
262 return numpy.sin(2 * numpy.pi * 440 * numpy.linspace(0, 1, 44100)) * scale
269
263
270 def read_wav(data):
264 def read_wav(data):
271 with wave.open(BytesIO(data)) as wave_file:
265 with wave.open(BytesIO(data)) as wave_file:
272 wave_data = wave_file.readframes(wave_file.getnframes())
266 wave_data = wave_file.readframes(wave_file.getnframes())
273 num_samples = wave_file.getnframes() * wave_file.getnchannels()
267 num_samples = wave_file.getnframes() * wave_file.getnchannels()
274 return struct.unpack('<%sh' % num_samples, wave_data)
268 return struct.unpack('<%sh' % num_samples, wave_data)
275
269
276 def test_code_from_file():
270 def test_code_from_file():
277 c = display.Code(filename=__file__)
271 c = display.Code(filename=__file__)
278 assert c._repr_html_().startswith('<style>')
272 assert c._repr_html_().startswith('<style>')
@@ -1,203 +1,204
1 """prompt-toolkit utilities
1 """prompt-toolkit utilities
2
2
3 Everything in this module is a private API,
3 Everything in this module is a private API,
4 not to be used outside IPython.
4 not to be used outside IPython.
5 """
5 """
6
6
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10 import unicodedata
10 import unicodedata
11 from wcwidth import wcwidth
11 from wcwidth import wcwidth
12
12
13 from IPython.core.completer import (
13 from IPython.core.completer import (
14 provisionalcompleter, cursor_to_position,
14 provisionalcompleter, cursor_to_position,
15 _deduplicate_completions)
15 _deduplicate_completions)
16 from prompt_toolkit.completion import Completer, Completion
16 from prompt_toolkit.completion import Completer, Completion
17 from prompt_toolkit.lexers import Lexer
17 from prompt_toolkit.lexers import Lexer
18 from prompt_toolkit.lexers import PygmentsLexer
18 from prompt_toolkit.lexers import PygmentsLexer
19 from prompt_toolkit.patch_stdout import patch_stdout
19 from prompt_toolkit.patch_stdout import patch_stdout
20
20
21 import pygments.lexers as pygments_lexers
21 import pygments.lexers as pygments_lexers
22 import os
22 import os
23 import sys
23 import sys
24 import traceback
24 import traceback
25
25
26 _completion_sentinel = object()
26 _completion_sentinel = object()
27
27
28 def _elide_point(string:str, *, min_elide=30)->str:
28 def _elide_point(string:str, *, min_elide=30)->str:
29 """
29 """
30 If a string is long enough, and has at least 3 dots,
30 If a string is long enough, and has at least 3 dots,
31 replace the middle part with ellipses.
31 replace the middle part with ellipses.
32
32
33 If a string naming a file is long enough, and has at least 3 slashes,
33 If a string naming a file is long enough, and has at least 3 slashes,
34 replace the middle part with ellipses.
34 replace the middle part with ellipses.
35
35
36 If three consecutive dots, or two consecutive dots are encountered these are
36 If three consecutive dots, or two consecutive dots are encountered these are
37 replaced by the equivalents HORIZONTAL ELLIPSIS or TWO DOT LEADER unicode
37 replaced by the equivalents HORIZONTAL ELLIPSIS or TWO DOT LEADER unicode
38 equivalents
38 equivalents
39 """
39 """
40 string = string.replace('...','\N{HORIZONTAL ELLIPSIS}')
40 string = string.replace('...','\N{HORIZONTAL ELLIPSIS}')
41 string = string.replace('..','\N{TWO DOT LEADER}')
41 string = string.replace('..','\N{TWO DOT LEADER}')
42 if len(string) < min_elide:
42 if len(string) < min_elide:
43 return string
43 return string
44
44
45 object_parts = string.split('.')
45 object_parts = string.split('.')
46 file_parts = string.split(os.sep)
46 file_parts = string.split(os.sep)
47 if file_parts[-1] == '':
47 if file_parts[-1] == '':
48 file_parts.pop()
48 file_parts.pop()
49
49
50 if len(object_parts) > 3:
50 if len(object_parts) > 3:
51 return "{}.{}\N{HORIZONTAL ELLIPSIS}{}.{}".format(
51 return "{}.{}\N{HORIZONTAL ELLIPSIS}{}.{}".format(
52 object_parts[0],
52 object_parts[0],
53 object_parts[1][:1],
53 object_parts[1][:1],
54 object_parts[-2][-1:],
54 object_parts[-2][-1:],
55 object_parts[-1],
55 object_parts[-1],
56 )
56 )
57
57
58 elif len(file_parts) > 3:
58 elif len(file_parts) > 3:
59 return ("{}" + os.sep + "{}\N{HORIZONTAL ELLIPSIS}{}" + os.sep + "{}").format(
59 return ("{}" + os.sep + "{}\N{HORIZONTAL ELLIPSIS}{}" + os.sep + "{}").format(
60 file_parts[0], file_parts[1][:1], file_parts[-2][-1:], file_parts[-1]
60 file_parts[0], file_parts[1][:1], file_parts[-2][-1:], file_parts[-1]
61 )
61 )
62
62
63 return string
63 return string
64
64
65 def _elide_typed(string:str, typed:str, *, min_elide:int=30)->str:
65 def _elide_typed(string:str, typed:str, *, min_elide:int=30)->str:
66 """
66 """
67 Elide the middle of a long string if the beginning has already been typed.
67 Elide the middle of a long string if the beginning has already been typed.
68 """
68 """
69
69
70 if len(string) < min_elide:
70 if len(string) < min_elide:
71 return string
71 return string
72 cut_how_much = len(typed)-3
72 cut_how_much = len(typed)-3
73 if cut_how_much < 7:
73 if cut_how_much < 7:
74 return string
74 return string
75 if string.startswith(typed) and len(string)> len(typed):
75 if string.startswith(typed) and len(string)> len(typed):
76 return f"{string[:3]}\N{HORIZONTAL ELLIPSIS}{string[cut_how_much:]}"
76 return f"{string[:3]}\N{HORIZONTAL ELLIPSIS}{string[cut_how_much:]}"
77 return string
77 return string
78
78
79 def _elide(string:str, typed:str, min_elide=30)->str:
79 def _elide(string:str, typed:str, min_elide=30)->str:
80 return _elide_typed(
80 return _elide_typed(
81 _elide_point(string, min_elide=min_elide),
81 _elide_point(string, min_elide=min_elide),
82 typed, min_elide=min_elide)
82 typed, min_elide=min_elide)
83
83
84
84
85
85
86 def _adjust_completion_text_based_on_context(text, body, offset):
86 def _adjust_completion_text_based_on_context(text, body, offset):
87 if text.endswith('=') and len(body) > offset and body[offset] == '=':
87 if text.endswith('=') and len(body) > offset and body[offset] == '=':
88 return text[:-1]
88 return text[:-1]
89 else:
89 else:
90 return text
90 return text
91
91
92
92
93 class IPythonPTCompleter(Completer):
93 class IPythonPTCompleter(Completer):
94 """Adaptor to provide IPython completions to prompt_toolkit"""
94 """Adaptor to provide IPython completions to prompt_toolkit"""
95 def __init__(self, ipy_completer=None, shell=None):
95 def __init__(self, ipy_completer=None, shell=None):
96 if shell is None and ipy_completer is None:
96 if shell is None and ipy_completer is None:
97 raise TypeError("Please pass shell=an InteractiveShell instance.")
97 raise TypeError("Please pass shell=an InteractiveShell instance.")
98 self._ipy_completer = ipy_completer
98 self._ipy_completer = ipy_completer
99 self.shell = shell
99 self.shell = shell
100
100
101 @property
101 @property
102 def ipy_completer(self):
102 def ipy_completer(self):
103 if self._ipy_completer:
103 if self._ipy_completer:
104 return self._ipy_completer
104 return self._ipy_completer
105 else:
105 else:
106 return self.shell.Completer
106 return self.shell.Completer
107
107
108 def get_completions(self, document, complete_event):
108 def get_completions(self, document, complete_event):
109 if not document.current_line.strip():
109 if not document.current_line.strip():
110 return
110 return
111 # Some bits of our completion system may print stuff (e.g. if a module
111 # Some bits of our completion system may print stuff (e.g. if a module
112 # is imported). This context manager ensures that doesn't interfere with
112 # is imported). This context manager ensures that doesn't interfere with
113 # the prompt.
113 # the prompt.
114
114
115 with patch_stdout(), provisionalcompleter():
115 with patch_stdout(), provisionalcompleter():
116 body = document.text
116 body = document.text
117 cursor_row = document.cursor_position_row
117 cursor_row = document.cursor_position_row
118 cursor_col = document.cursor_position_col
118 cursor_col = document.cursor_position_col
119 cursor_position = document.cursor_position
119 cursor_position = document.cursor_position
120 offset = cursor_to_position(body, cursor_row, cursor_col)
120 offset = cursor_to_position(body, cursor_row, cursor_col)
121 try:
121 try:
122 yield from self._get_completions(body, offset, cursor_position, self.ipy_completer)
122 yield from self._get_completions(body, offset, cursor_position, self.ipy_completer)
123 except Exception:
123 except Exception as e:
124 try:
124 try:
125 exc_type, exc_value, exc_tb = sys.exc_info()
125 exc_type, exc_value, exc_tb = sys.exc_info()
126 traceback.print_exception(exc_type, exc_value, exc_tb)
126 traceback.print_exception(exc_type, exc_value, exc_tb)
127 except AttributeError:
127 except AttributeError:
128 print('Unrecoverable Error in completions')
128 print('Unrecoverable Error in completions')
129
129
130 @staticmethod
130 @staticmethod
131 def _get_completions(body, offset, cursor_position, ipyc):
131 def _get_completions(body, offset, cursor_position, ipyc):
132 """
132 """
133 Private equivalent of get_completions() use only for unit_testing.
133 Private equivalent of get_completions() use only for unit_testing.
134 """
134 """
135 debug = getattr(ipyc, 'debug', False)
135 completions = _deduplicate_completions(
136 completions = _deduplicate_completions(
136 body, ipyc.completions(body, offset))
137 body, ipyc.completions(body, offset))
137 for c in completions:
138 for c in completions:
138 if not c.text:
139 if not c.text:
139 # Guard against completion machinery giving us an empty string.
140 # Guard against completion machinery giving us an empty string.
140 continue
141 continue
141 text = unicodedata.normalize('NFC', c.text)
142 text = unicodedata.normalize('NFC', c.text)
142 # When the first character of the completion has a zero length,
143 # When the first character of the completion has a zero length,
143 # then it's probably a decomposed unicode character. E.g. caused by
144 # then it's probably a decomposed unicode character. E.g. caused by
144 # the "\dot" completion. Try to compose again with the previous
145 # the "\dot" completion. Try to compose again with the previous
145 # character.
146 # character.
146 if wcwidth(text[0]) == 0:
147 if wcwidth(text[0]) == 0:
147 if cursor_position + c.start > 0:
148 if cursor_position + c.start > 0:
148 char_before = body[c.start - 1]
149 char_before = body[c.start - 1]
149 fixed_text = unicodedata.normalize(
150 fixed_text = unicodedata.normalize(
150 'NFC', char_before + text)
151 'NFC', char_before + text)
151
152
152 # Yield the modified completion instead, if this worked.
153 # Yield the modified completion instead, if this worked.
153 if wcwidth(text[0:1]) == 1:
154 if wcwidth(text[0:1]) == 1:
154 yield Completion(fixed_text, start_position=c.start - offset - 1)
155 yield Completion(fixed_text, start_position=c.start - offset - 1)
155 continue
156 continue
156
157
157 # TODO: Use Jedi to determine meta_text
158 # TODO: Use Jedi to determine meta_text
158 # (Jedi currently has a bug that results in incorrect information.)
159 # (Jedi currently has a bug that results in incorrect information.)
159 # meta_text = ''
160 # meta_text = ''
160 # yield Completion(m, start_position=start_pos,
161 # yield Completion(m, start_position=start_pos,
161 # display_meta=meta_text)
162 # display_meta=meta_text)
162 display_text = c.text
163 display_text = c.text
163
164
164 adjusted_text = _adjust_completion_text_based_on_context(c.text, body, offset)
165 adjusted_text = _adjust_completion_text_based_on_context(c.text, body, offset)
165 if c.type == 'function':
166 if c.type == 'function':
166 yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text+'()', body[c.start:c.end]), display_meta=c.type+c.signature)
167 yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text+'()', body[c.start:c.end]), display_meta=c.type+c.signature)
167 else:
168 else:
168 yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text, body[c.start:c.end]), display_meta=c.type)
169 yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text, body[c.start:c.end]), display_meta=c.type)
169
170
170 class IPythonPTLexer(Lexer):
171 class IPythonPTLexer(Lexer):
171 """
172 """
172 Wrapper around PythonLexer and BashLexer.
173 Wrapper around PythonLexer and BashLexer.
173 """
174 """
174 def __init__(self):
175 def __init__(self):
175 l = pygments_lexers
176 l = pygments_lexers
176 self.python_lexer = PygmentsLexer(l.Python3Lexer)
177 self.python_lexer = PygmentsLexer(l.Python3Lexer)
177 self.shell_lexer = PygmentsLexer(l.BashLexer)
178 self.shell_lexer = PygmentsLexer(l.BashLexer)
178
179
179 self.magic_lexers = {
180 self.magic_lexers = {
180 'HTML': PygmentsLexer(l.HtmlLexer),
181 'HTML': PygmentsLexer(l.HtmlLexer),
181 'html': PygmentsLexer(l.HtmlLexer),
182 'html': PygmentsLexer(l.HtmlLexer),
182 'javascript': PygmentsLexer(l.JavascriptLexer),
183 'javascript': PygmentsLexer(l.JavascriptLexer),
183 'js': PygmentsLexer(l.JavascriptLexer),
184 'js': PygmentsLexer(l.JavascriptLexer),
184 'perl': PygmentsLexer(l.PerlLexer),
185 'perl': PygmentsLexer(l.PerlLexer),
185 'ruby': PygmentsLexer(l.RubyLexer),
186 'ruby': PygmentsLexer(l.RubyLexer),
186 'latex': PygmentsLexer(l.TexLexer),
187 'latex': PygmentsLexer(l.TexLexer),
187 }
188 }
188
189
189 def lex_document(self, document):
190 def lex_document(self, document):
190 text = document.text.lstrip()
191 text = document.text.lstrip()
191
192
192 lexer = self.python_lexer
193 lexer = self.python_lexer
193
194
194 if text.startswith('!') or text.startswith('%%bash'):
195 if text.startswith('!') or text.startswith('%%bash'):
195 lexer = self.shell_lexer
196 lexer = self.shell_lexer
196
197
197 elif text.startswith('%%'):
198 elif text.startswith('%%'):
198 for magic, l in self.magic_lexers.items():
199 for magic, l in self.magic_lexers.items():
199 if text.startswith('%%' + magic):
200 if text.startswith('%%' + magic):
200 lexer = l
201 lexer = l
201 break
202 break
202
203
203 return lexer.lex_document(document)
204 return lexer.lex_document(document)
@@ -1,638 +1,638
1 """
1 """
2 Module to define and register Terminal IPython shortcuts with
2 Module to define and register Terminal IPython shortcuts with
3 :mod:`prompt_toolkit`
3 :mod:`prompt_toolkit`
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 import os
9 import os
10 import signal
10 import signal
11 import sys
11 import sys
12 import warnings
12 import warnings
13 from dataclasses import dataclass
13 from dataclasses import dataclass
14 from typing import Callable, Any, Optional, List
14 from typing import Callable, Any, Optional, List
15
15
16 from prompt_toolkit.application.current import get_app
16 from prompt_toolkit.application.current import get_app
17 from prompt_toolkit.key_binding import KeyBindings
17 from prompt_toolkit.key_binding import KeyBindings
18 from prompt_toolkit.key_binding.key_processor import KeyPressEvent
18 from prompt_toolkit.key_binding.key_processor import KeyPressEvent
19 from prompt_toolkit.key_binding.bindings import named_commands as nc
19 from prompt_toolkit.key_binding.bindings import named_commands as nc
20 from prompt_toolkit.key_binding.bindings.completion import (
20 from prompt_toolkit.key_binding.bindings.completion import (
21 display_completions_like_readline,
21 display_completions_like_readline,
22 )
22 )
23 from prompt_toolkit.key_binding.vi_state import InputMode, ViState
23 from prompt_toolkit.key_binding.vi_state import InputMode, ViState
24 from prompt_toolkit.filters import Condition
24 from prompt_toolkit.filters import Condition
25
25
26 from IPython.core.getipython import get_ipython
26 from IPython.core.getipython import get_ipython
27 from IPython.terminal.shortcuts import auto_match as match
27 from IPython.terminal.shortcuts import auto_match as match
28 from IPython.terminal.shortcuts import auto_suggest
28 from IPython.terminal.shortcuts import auto_suggest
29 from IPython.terminal.shortcuts.filters import filter_from_string
29 from IPython.terminal.shortcuts.filters import filter_from_string
30 from IPython.utils.decorators import undoc
30 from IPython.utils.decorators import undoc
31
31
32 from prompt_toolkit.enums import DEFAULT_BUFFER
32 from prompt_toolkit.enums import DEFAULT_BUFFER
33
33
34 __all__ = ["create_ipython_shortcuts"]
34 __all__ = ["create_ipython_shortcuts"]
35
35
36
36
37 @dataclass
37 @dataclass
38 class BaseBinding:
38 class BaseBinding:
39 command: Callable[[KeyPressEvent], Any]
39 command: Callable[[KeyPressEvent], Any]
40 keys: List[str]
40 keys: List[str]
41
41
42
42
43 @dataclass
43 @dataclass
44 class RuntimeBinding(BaseBinding):
44 class RuntimeBinding(BaseBinding):
45 filter: Condition
45 filter: Condition
46
46
47
47
48 @dataclass
48 @dataclass
49 class Binding(BaseBinding):
49 class Binding(BaseBinding):
50 # while filter could be created by referencing variables directly (rather
50 # while filter could be created by referencing variables directly (rather
51 # than created from strings), by using strings we ensure that users will
51 # than created from strings), by using strings we ensure that users will
52 # be able to create filters in configuration (e.g. JSON) files too, which
52 # be able to create filters in configuration (e.g. JSON) files too, which
53 # also benefits the documentation by enforcing human-readable filter names.
53 # also benefits the documentation by enforcing human-readable filter names.
54 condition: Optional[str] = None
54 condition: Optional[str] = None
55
55
56 def __post_init__(self):
56 def __post_init__(self):
57 if self.condition:
57 if self.condition:
58 self.filter = filter_from_string(self.condition)
58 self.filter = filter_from_string(self.condition)
59 else:
59 else:
60 self.filter = None
60 self.filter = None
61
61
62
62
63 def create_identifier(handler: Callable):
63 def create_identifier(handler: Callable):
64 parts = handler.__module__.split(".")
64 parts = handler.__module__.split(".")
65 name = handler.__name__
65 name = handler.__name__
66 package = parts[0]
66 package = parts[0]
67 if len(parts) > 1:
67 if len(parts) > 1:
68 final_module = parts[-1]
68 final_module = parts[-1]
69 return f"{package}:{final_module}.{name}"
69 return f"{package}:{final_module}.{name}"
70 else:
70 else:
71 return f"{package}:{name}"
71 return f"{package}:{name}"
72
72
73
73
74 AUTO_MATCH_BINDINGS = [
74 AUTO_MATCH_BINDINGS = [
75 *[
75 *[
76 Binding(
76 Binding(
77 cmd, [key], "focused_insert & auto_match & followed_by_closing_paren_or_end"
77 cmd, [key], "focused_insert & auto_match & followed_by_closing_paren_or_end"
78 )
78 )
79 for key, cmd in match.auto_match_parens.items()
79 for key, cmd in match.auto_match_parens.items()
80 ],
80 ],
81 *[
81 *[
82 # raw string
82 # raw string
83 Binding(cmd, [key], "focused_insert & auto_match & preceded_by_raw_str_prefix")
83 Binding(cmd, [key], "focused_insert & auto_match & preceded_by_raw_str_prefix")
84 for key, cmd in match.auto_match_parens_raw_string.items()
84 for key, cmd in match.auto_match_parens_raw_string.items()
85 ],
85 ],
86 Binding(
86 Binding(
87 match.double_quote,
87 match.double_quote,
88 ['"'],
88 ['"'],
89 "focused_insert"
89 "focused_insert"
90 " & auto_match"
90 " & auto_match"
91 " & not_inside_unclosed_string"
91 " & not_inside_unclosed_string"
92 " & preceded_by_paired_double_quotes"
92 " & preceded_by_paired_double_quotes"
93 " & followed_by_closing_paren_or_end",
93 " & followed_by_closing_paren_or_end",
94 ),
94 ),
95 Binding(
95 Binding(
96 match.single_quote,
96 match.single_quote,
97 ["'"],
97 ["'"],
98 "focused_insert"
98 "focused_insert"
99 " & auto_match"
99 " & auto_match"
100 " & not_inside_unclosed_string"
100 " & not_inside_unclosed_string"
101 " & preceded_by_paired_single_quotes"
101 " & preceded_by_paired_single_quotes"
102 " & followed_by_closing_paren_or_end",
102 " & followed_by_closing_paren_or_end",
103 ),
103 ),
104 Binding(
104 Binding(
105 match.docstring_double_quotes,
105 match.docstring_double_quotes,
106 ['"'],
106 ['"'],
107 "focused_insert"
107 "focused_insert"
108 " & auto_match"
108 " & auto_match"
109 " & not_inside_unclosed_string"
109 " & not_inside_unclosed_string"
110 " & preceded_by_two_double_quotes",
110 " & preceded_by_two_double_quotes",
111 ),
111 ),
112 Binding(
112 Binding(
113 match.docstring_single_quotes,
113 match.docstring_single_quotes,
114 ["'"],
114 ["'"],
115 "focused_insert"
115 "focused_insert"
116 " & auto_match"
116 " & auto_match"
117 " & not_inside_unclosed_string"
117 " & not_inside_unclosed_string"
118 " & preceded_by_two_single_quotes",
118 " & preceded_by_two_single_quotes",
119 ),
119 ),
120 Binding(
120 Binding(
121 match.skip_over,
121 match.skip_over,
122 [")"],
122 [")"],
123 "focused_insert & auto_match & followed_by_closing_round_paren",
123 "focused_insert & auto_match & followed_by_closing_round_paren",
124 ),
124 ),
125 Binding(
125 Binding(
126 match.skip_over,
126 match.skip_over,
127 ["]"],
127 ["]"],
128 "focused_insert & auto_match & followed_by_closing_bracket",
128 "focused_insert & auto_match & followed_by_closing_bracket",
129 ),
129 ),
130 Binding(
130 Binding(
131 match.skip_over,
131 match.skip_over,
132 ["}"],
132 ["}"],
133 "focused_insert & auto_match & followed_by_closing_brace",
133 "focused_insert & auto_match & followed_by_closing_brace",
134 ),
134 ),
135 Binding(
135 Binding(
136 match.skip_over, ['"'], "focused_insert & auto_match & followed_by_double_quote"
136 match.skip_over, ['"'], "focused_insert & auto_match & followed_by_double_quote"
137 ),
137 ),
138 Binding(
138 Binding(
139 match.skip_over, ["'"], "focused_insert & auto_match & followed_by_single_quote"
139 match.skip_over, ["'"], "focused_insert & auto_match & followed_by_single_quote"
140 ),
140 ),
141 Binding(
141 Binding(
142 match.delete_pair,
142 match.delete_pair,
143 ["backspace"],
143 ["backspace"],
144 "focused_insert"
144 "focused_insert"
145 " & preceded_by_opening_round_paren"
145 " & preceded_by_opening_round_paren"
146 " & auto_match"
146 " & auto_match"
147 " & followed_by_closing_round_paren",
147 " & followed_by_closing_round_paren",
148 ),
148 ),
149 Binding(
149 Binding(
150 match.delete_pair,
150 match.delete_pair,
151 ["backspace"],
151 ["backspace"],
152 "focused_insert"
152 "focused_insert"
153 " & preceded_by_opening_bracket"
153 " & preceded_by_opening_bracket"
154 " & auto_match"
154 " & auto_match"
155 " & followed_by_closing_bracket",
155 " & followed_by_closing_bracket",
156 ),
156 ),
157 Binding(
157 Binding(
158 match.delete_pair,
158 match.delete_pair,
159 ["backspace"],
159 ["backspace"],
160 "focused_insert"
160 "focused_insert"
161 " & preceded_by_opening_brace"
161 " & preceded_by_opening_brace"
162 " & auto_match"
162 " & auto_match"
163 " & followed_by_closing_brace",
163 " & followed_by_closing_brace",
164 ),
164 ),
165 Binding(
165 Binding(
166 match.delete_pair,
166 match.delete_pair,
167 ["backspace"],
167 ["backspace"],
168 "focused_insert"
168 "focused_insert"
169 " & preceded_by_double_quote"
169 " & preceded_by_double_quote"
170 " & auto_match"
170 " & auto_match"
171 " & followed_by_double_quote",
171 " & followed_by_double_quote",
172 ),
172 ),
173 Binding(
173 Binding(
174 match.delete_pair,
174 match.delete_pair,
175 ["backspace"],
175 ["backspace"],
176 "focused_insert"
176 "focused_insert"
177 " & preceded_by_single_quote"
177 " & preceded_by_single_quote"
178 " & auto_match"
178 " & auto_match"
179 " & followed_by_single_quote",
179 " & followed_by_single_quote",
180 ),
180 ),
181 ]
181 ]
182
182
183 AUTO_SUGGEST_BINDINGS = [
183 AUTO_SUGGEST_BINDINGS = [
184 # there are two reasons for re-defining bindings defined upstream:
184 # there are two reasons for re-defining bindings defined upstream:
185 # 1) prompt-toolkit does not execute autosuggestion bindings in vi mode,
185 # 1) prompt-toolkit does not execute autosuggestion bindings in vi mode,
186 # 2) prompt-toolkit checks if we are at the end of text, not end of line
186 # 2) prompt-toolkit checks if we are at the end of text, not end of line
187 # hence it does not work in multi-line mode of navigable provider
187 # hence it does not work in multi-line mode of navigable provider
188 Binding(
188 Binding(
189 auto_suggest.accept_or_jump_to_end,
189 auto_suggest.accept_or_jump_to_end,
190 ["end"],
190 ["end"],
191 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
191 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
192 ),
192 ),
193 Binding(
193 Binding(
194 auto_suggest.accept_or_jump_to_end,
194 auto_suggest.accept_or_jump_to_end,
195 ["c-e"],
195 ["c-e"],
196 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
196 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
197 ),
197 ),
198 Binding(
198 Binding(
199 auto_suggest.accept,
199 auto_suggest.accept,
200 ["c-f"],
200 ["c-f"],
201 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
201 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
202 ),
202 ),
203 Binding(
203 Binding(
204 auto_suggest.accept,
204 auto_suggest.accept,
205 ["right"],
205 ["right"],
206 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
206 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
207 ),
207 ),
208 Binding(
208 Binding(
209 auto_suggest.accept_word,
209 auto_suggest.accept_word,
210 ["escape", "f"],
210 ["escape", "f"],
211 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
211 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
212 ),
212 ),
213 Binding(
213 Binding(
214 auto_suggest.accept_token,
214 auto_suggest.accept_token,
215 ["c-right"],
215 ["c-right"],
216 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
216 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
217 ),
217 ),
218 Binding(
218 Binding(
219 auto_suggest.discard,
219 auto_suggest.discard,
220 ["escape"],
220 ["escape"],
221 # note this one is using `emacs_insert_mode`, not `emacs_like_insert_mode`
221 # note this one is using `emacs_insert_mode`, not `emacs_like_insert_mode`
222 # as in `vi_insert_mode` we do not want `escape` to be shadowed (ever).
222 # as in `vi_insert_mode` we do not want `escape` to be shadowed (ever).
223 "has_suggestion & default_buffer_focused & emacs_insert_mode",
223 "has_suggestion & default_buffer_focused & emacs_insert_mode",
224 ),
224 ),
225 Binding(
225 Binding(
226 auto_suggest.discard,
226 auto_suggest.discard,
227 ["delete"],
227 ["delete"],
228 "has_suggestion & default_buffer_focused & emacs_insert_mode",
228 "has_suggestion & default_buffer_focused & emacs_insert_mode",
229 ),
229 ),
230 Binding(
230 Binding(
231 auto_suggest.swap_autosuggestion_up,
231 auto_suggest.swap_autosuggestion_up,
232 ["c-up"],
232 ["c-up"],
233 "navigable_suggestions"
233 "navigable_suggestions"
234 " & ~has_line_above"
234 " & ~has_line_above"
235 " & has_suggestion"
235 " & has_suggestion"
236 " & default_buffer_focused",
236 " & default_buffer_focused",
237 ),
237 ),
238 Binding(
238 Binding(
239 auto_suggest.swap_autosuggestion_down,
239 auto_suggest.swap_autosuggestion_down,
240 ["c-down"],
240 ["c-down"],
241 "navigable_suggestions"
241 "navigable_suggestions"
242 " & ~has_line_below"
242 " & ~has_line_below"
243 " & has_suggestion"
243 " & has_suggestion"
244 " & default_buffer_focused",
244 " & default_buffer_focused",
245 ),
245 ),
246 Binding(
246 Binding(
247 auto_suggest.up_and_update_hint,
247 auto_suggest.up_and_update_hint,
248 ["c-up"],
248 ["c-up"],
249 "has_line_above & navigable_suggestions & default_buffer_focused",
249 "has_line_above & navigable_suggestions & default_buffer_focused",
250 ),
250 ),
251 Binding(
251 Binding(
252 auto_suggest.down_and_update_hint,
252 auto_suggest.down_and_update_hint,
253 ["c-down"],
253 ["c-down"],
254 "has_line_below & navigable_suggestions & default_buffer_focused",
254 "has_line_below & navigable_suggestions & default_buffer_focused",
255 ),
255 ),
256 Binding(
256 Binding(
257 auto_suggest.accept_character,
257 auto_suggest.accept_character,
258 ["escape", "right"],
258 ["escape", "right"],
259 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
259 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
260 ),
260 ),
261 Binding(
261 Binding(
262 auto_suggest.accept_and_move_cursor_left,
262 auto_suggest.accept_and_move_cursor_left,
263 ["c-left"],
263 ["c-left"],
264 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
264 "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
265 ),
265 ),
266 Binding(
266 Binding(
267 auto_suggest.accept_and_keep_cursor,
267 auto_suggest.accept_and_keep_cursor,
268 ["escape", "down"],
268 ["escape", "down"],
269 "has_suggestion & default_buffer_focused & emacs_insert_mode",
269 "has_suggestion & default_buffer_focused & emacs_insert_mode",
270 ),
270 ),
271 Binding(
271 Binding(
272 auto_suggest.backspace_and_resume_hint,
272 auto_suggest.backspace_and_resume_hint,
273 ["backspace"],
273 ["backspace"],
274 # no `has_suggestion` here to allow resuming if no suggestion
274 # no `has_suggestion` here to allow resuming if no suggestion
275 "default_buffer_focused & emacs_like_insert_mode",
275 "default_buffer_focused & emacs_like_insert_mode",
276 ),
276 ),
277 Binding(
277 Binding(
278 auto_suggest.resume_hinting,
278 auto_suggest.resume_hinting,
279 ["right"],
279 ["right"],
280 "is_cursor_at_the_end_of_line"
280 "is_cursor_at_the_end_of_line"
281 " & default_buffer_focused"
281 " & default_buffer_focused"
282 " & emacs_like_insert_mode"
282 " & emacs_like_insert_mode"
283 " & pass_through",
283 " & pass_through",
284 ),
284 ),
285 ]
285 ]
286
286
287
287
288 SIMPLE_CONTROL_BINDINGS = [
288 SIMPLE_CONTROL_BINDINGS = [
289 Binding(cmd, [key], "vi_insert_mode & default_buffer_focused & ebivim")
289 Binding(cmd, [key], "vi_insert_mode & default_buffer_focused & ebivim")
290 for key, cmd in {
290 for key, cmd in {
291 "c-a": nc.beginning_of_line,
291 "c-a": nc.beginning_of_line,
292 "c-b": nc.backward_char,
292 "c-b": nc.backward_char,
293 "c-k": nc.kill_line,
293 "c-k": nc.kill_line,
294 "c-w": nc.backward_kill_word,
294 "c-w": nc.backward_kill_word,
295 "c-y": nc.yank,
295 "c-y": nc.yank,
296 "c-_": nc.undo,
296 "c-_": nc.undo,
297 }.items()
297 }.items()
298 ]
298 ]
299
299
300
300
301 ALT_AND_COMOBO_CONTROL_BINDINGS = [
301 ALT_AND_COMOBO_CONTROL_BINDINGS = [
302 Binding(cmd, list(keys), "vi_insert_mode & default_buffer_focused & ebivim")
302 Binding(cmd, list(keys), "vi_insert_mode & default_buffer_focused & ebivim")
303 for keys, cmd in {
303 for keys, cmd in {
304 # Control Combos
304 # Control Combos
305 ("c-x", "c-e"): nc.edit_and_execute,
305 ("c-x", "c-e"): nc.edit_and_execute,
306 ("c-x", "e"): nc.edit_and_execute,
306 ("c-x", "e"): nc.edit_and_execute,
307 # Alt
307 # Alt
308 ("escape", "b"): nc.backward_word,
308 ("escape", "b"): nc.backward_word,
309 ("escape", "c"): nc.capitalize_word,
309 ("escape", "c"): nc.capitalize_word,
310 ("escape", "d"): nc.kill_word,
310 ("escape", "d"): nc.kill_word,
311 ("escape", "h"): nc.backward_kill_word,
311 ("escape", "h"): nc.backward_kill_word,
312 ("escape", "l"): nc.downcase_word,
312 ("escape", "l"): nc.downcase_word,
313 ("escape", "u"): nc.uppercase_word,
313 ("escape", "u"): nc.uppercase_word,
314 ("escape", "y"): nc.yank_pop,
314 ("escape", "y"): nc.yank_pop,
315 ("escape", "."): nc.yank_last_arg,
315 ("escape", "."): nc.yank_last_arg,
316 }.items()
316 }.items()
317 ]
317 ]
318
318
319
319
320 def add_binding(bindings: KeyBindings, binding: Binding):
320 def add_binding(bindings: KeyBindings, binding: Binding):
321 bindings.add(
321 bindings.add(
322 *binding.keys,
322 *binding.keys,
323 **({"filter": binding.filter} if binding.filter is not None else {}),
323 **({"filter": binding.filter} if binding.filter is not None else {}),
324 )(binding.command)
324 )(binding.command)
325
325
326
326
327 def create_ipython_shortcuts(shell, skip=None) -> KeyBindings:
327 def create_ipython_shortcuts(shell, skip=None) -> KeyBindings:
328 """Set up the prompt_toolkit keyboard shortcuts for IPython.
328 """Set up the prompt_toolkit keyboard shortcuts for IPython.
329
329
330 Parameters
330 Parameters
331 ----------
331 ----------
332 shell: InteractiveShell
332 shell: InteractiveShell
333 The current IPython shell Instance
333 The current IPython shell Instance
334 skip: List[Binding]
334 skip: List[Binding]
335 Bindings to skip.
335 Bindings to skip.
336
336
337 Returns
337 Returns
338 -------
338 -------
339 KeyBindings
339 KeyBindings
340 the keybinding instance for prompt toolkit.
340 the keybinding instance for prompt toolkit.
341
341
342 """
342 """
343 kb = KeyBindings()
343 kb = KeyBindings()
344 skip = skip or []
344 skip = skip or []
345 for binding in KEY_BINDINGS:
345 for binding in KEY_BINDINGS:
346 skip_this_one = False
346 skip_this_one = False
347 for to_skip in skip:
347 for to_skip in skip:
348 if (
348 if (
349 to_skip.command == binding.command
349 to_skip.command == binding.command
350 and to_skip.filter == binding.filter
350 and to_skip.filter == binding.filter
351 and to_skip.keys == binding.keys
351 and to_skip.keys == binding.keys
352 ):
352 ):
353 skip_this_one = True
353 skip_this_one = True
354 break
354 break
355 if skip_this_one:
355 if skip_this_one:
356 continue
356 continue
357 add_binding(kb, binding)
357 add_binding(kb, binding)
358
358
359 def get_input_mode(self):
359 def get_input_mode(self):
360 app = get_app()
360 app = get_app()
361 app.ttimeoutlen = shell.ttimeoutlen
361 app.ttimeoutlen = shell.ttimeoutlen
362 app.timeoutlen = shell.timeoutlen
362 app.timeoutlen = shell.timeoutlen
363
363
364 return self._input_mode
364 return self._input_mode
365
365
366 def set_input_mode(self, mode):
366 def set_input_mode(self, mode):
367 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
367 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
368 cursor = "\x1b[{} q".format(shape)
368 cursor = "\x1b[{} q".format(shape)
369
369
370 sys.stdout.write(cursor)
370 sys.stdout.write(cursor)
371 sys.stdout.flush()
371 sys.stdout.flush()
372
372
373 self._input_mode = mode
373 self._input_mode = mode
374
374
375 if shell.editing_mode == "vi" and shell.modal_cursor:
375 if shell.editing_mode == "vi" and shell.modal_cursor:
376 ViState._input_mode = InputMode.INSERT # type: ignore
376 ViState._input_mode = InputMode.INSERT # type: ignore
377 ViState.input_mode = property(get_input_mode, set_input_mode) # type: ignore
377 ViState.input_mode = property(get_input_mode, set_input_mode) # type: ignore
378
378
379 return kb
379 return kb
380
380
381
381
382 def reformat_and_execute(event):
382 def reformat_and_execute(event):
383 """Reformat code and execute it"""
383 """Reformat code and execute it"""
384 shell = get_ipython()
384 shell = get_ipython()
385 reformat_text_before_cursor(
385 reformat_text_before_cursor(
386 event.current_buffer, event.current_buffer.document, shell
386 event.current_buffer, event.current_buffer.document, shell
387 )
387 )
388 event.current_buffer.validate_and_handle()
388 event.current_buffer.validate_and_handle()
389
389
390
390
391 def reformat_text_before_cursor(buffer, document, shell):
391 def reformat_text_before_cursor(buffer, document, shell):
392 text = buffer.delete_before_cursor(len(document.text[: document.cursor_position]))
392 text = buffer.delete_before_cursor(len(document.text[: document.cursor_position]))
393 try:
393 try:
394 formatted_text = shell.reformat_handler(text)
394 formatted_text = shell.reformat_handler(text)
395 buffer.insert_text(formatted_text)
395 buffer.insert_text(formatted_text)
396 except Exception:
396 except Exception as e:
397 buffer.insert_text(text)
397 buffer.insert_text(text)
398
398
399
399
400 def handle_return_or_newline_or_execute(event):
400 def handle_return_or_newline_or_execute(event):
401 shell = get_ipython()
401 shell = get_ipython()
402 if getattr(shell, "handle_return", None):
402 if getattr(shell, "handle_return", None):
403 return shell.handle_return(shell)(event)
403 return shell.handle_return(shell)(event)
404 else:
404 else:
405 return newline_or_execute_outer(shell)(event)
405 return newline_or_execute_outer(shell)(event)
406
406
407
407
408 def newline_or_execute_outer(shell):
408 def newline_or_execute_outer(shell):
409 def newline_or_execute(event):
409 def newline_or_execute(event):
410 """When the user presses return, insert a newline or execute the code."""
410 """When the user presses return, insert a newline or execute the code."""
411 b = event.current_buffer
411 b = event.current_buffer
412 d = b.document
412 d = b.document
413
413
414 if b.complete_state:
414 if b.complete_state:
415 cc = b.complete_state.current_completion
415 cc = b.complete_state.current_completion
416 if cc:
416 if cc:
417 b.apply_completion(cc)
417 b.apply_completion(cc)
418 else:
418 else:
419 b.cancel_completion()
419 b.cancel_completion()
420 return
420 return
421
421
422 # If there's only one line, treat it as if the cursor is at the end.
422 # If there's only one line, treat it as if the cursor is at the end.
423 # See https://github.com/ipython/ipython/issues/10425
423 # See https://github.com/ipython/ipython/issues/10425
424 if d.line_count == 1:
424 if d.line_count == 1:
425 check_text = d.text
425 check_text = d.text
426 else:
426 else:
427 check_text = d.text[: d.cursor_position]
427 check_text = d.text[: d.cursor_position]
428 status, indent = shell.check_complete(check_text)
428 status, indent = shell.check_complete(check_text)
429
429
430 # if all we have after the cursor is whitespace: reformat current text
430 # if all we have after the cursor is whitespace: reformat current text
431 # before cursor
431 # before cursor
432 after_cursor = d.text[d.cursor_position :]
432 after_cursor = d.text[d.cursor_position :]
433 reformatted = False
433 reformatted = False
434 if not after_cursor.strip():
434 if not after_cursor.strip():
435 reformat_text_before_cursor(b, d, shell)
435 reformat_text_before_cursor(b, d, shell)
436 reformatted = True
436 reformatted = True
437 if not (
437 if not (
438 d.on_last_line
438 d.on_last_line
439 or d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
439 or d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
440 ):
440 ):
441 if shell.autoindent:
441 if shell.autoindent:
442 b.insert_text("\n" + indent)
442 b.insert_text("\n" + indent)
443 else:
443 else:
444 b.insert_text("\n")
444 b.insert_text("\n")
445 return
445 return
446
446
447 if (status != "incomplete") and b.accept_handler:
447 if (status != "incomplete") and b.accept_handler:
448 if not reformatted:
448 if not reformatted:
449 reformat_text_before_cursor(b, d, shell)
449 reformat_text_before_cursor(b, d, shell)
450 b.validate_and_handle()
450 b.validate_and_handle()
451 else:
451 else:
452 if shell.autoindent:
452 if shell.autoindent:
453 b.insert_text("\n" + indent)
453 b.insert_text("\n" + indent)
454 else:
454 else:
455 b.insert_text("\n")
455 b.insert_text("\n")
456
456
457 return newline_or_execute
457 return newline_or_execute
458
458
459
459
460 def previous_history_or_previous_completion(event):
460 def previous_history_or_previous_completion(event):
461 """
461 """
462 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
462 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
463
463
464 If completer is open this still select previous completion.
464 If completer is open this still select previous completion.
465 """
465 """
466 event.current_buffer.auto_up()
466 event.current_buffer.auto_up()
467
467
468
468
469 def next_history_or_next_completion(event):
469 def next_history_or_next_completion(event):
470 """
470 """
471 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
471 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
472
472
473 If completer is open this still select next completion.
473 If completer is open this still select next completion.
474 """
474 """
475 event.current_buffer.auto_down()
475 event.current_buffer.auto_down()
476
476
477
477
478 def dismiss_completion(event):
478 def dismiss_completion(event):
479 """Dismiss completion"""
479 """Dismiss completion"""
480 b = event.current_buffer
480 b = event.current_buffer
481 if b.complete_state:
481 if b.complete_state:
482 b.cancel_completion()
482 b.cancel_completion()
483
483
484
484
485 def reset_buffer(event):
485 def reset_buffer(event):
486 """Reset buffer"""
486 """Reset buffer"""
487 b = event.current_buffer
487 b = event.current_buffer
488 if b.complete_state:
488 if b.complete_state:
489 b.cancel_completion()
489 b.cancel_completion()
490 else:
490 else:
491 b.reset()
491 b.reset()
492
492
493
493
494 def reset_search_buffer(event):
494 def reset_search_buffer(event):
495 """Reset search buffer"""
495 """Reset search buffer"""
496 if event.current_buffer.document.text:
496 if event.current_buffer.document.text:
497 event.current_buffer.reset()
497 event.current_buffer.reset()
498 else:
498 else:
499 event.app.layout.focus(DEFAULT_BUFFER)
499 event.app.layout.focus(DEFAULT_BUFFER)
500
500
501
501
502 def suspend_to_bg(event):
502 def suspend_to_bg(event):
503 """Suspend to background"""
503 """Suspend to background"""
504 event.app.suspend_to_background()
504 event.app.suspend_to_background()
505
505
506
506
507 def quit(event):
507 def quit(event):
508 """
508 """
509 Quit application with ``SIGQUIT`` if supported or ``sys.exit`` otherwise.
509 Quit application with ``SIGQUIT`` if supported or ``sys.exit`` otherwise.
510
510
511 On platforms that support SIGQUIT, send SIGQUIT to the current process.
511 On platforms that support SIGQUIT, send SIGQUIT to the current process.
512 On other platforms, just exit the process with a message.
512 On other platforms, just exit the process with a message.
513 """
513 """
514 sigquit = getattr(signal, "SIGQUIT", None)
514 sigquit = getattr(signal, "SIGQUIT", None)
515 if sigquit is not None:
515 if sigquit is not None:
516 os.kill(0, signal.SIGQUIT)
516 os.kill(0, signal.SIGQUIT)
517 else:
517 else:
518 sys.exit("Quit")
518 sys.exit("Quit")
519
519
520
520
521 def indent_buffer(event):
521 def indent_buffer(event):
522 """Indent buffer"""
522 """Indent buffer"""
523 event.current_buffer.insert_text(" " * 4)
523 event.current_buffer.insert_text(" " * 4)
524
524
525
525
526 def newline_autoindent(event):
526 def newline_autoindent(event):
527 """Insert a newline after the cursor indented appropriately.
527 """Insert a newline after the cursor indented appropriately.
528
528
529 Fancier version of former ``newline_with_copy_margin`` which should
529 Fancier version of former ``newline_with_copy_margin`` which should
530 compute the correct indentation of the inserted line. That is to say, indent
530 compute the correct indentation of the inserted line. That is to say, indent
531 by 4 extra space after a function definition, class definition, context
531 by 4 extra space after a function definition, class definition, context
532 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
532 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
533 """
533 """
534 shell = get_ipython()
534 shell = get_ipython()
535 inputsplitter = shell.input_transformer_manager
535 inputsplitter = shell.input_transformer_manager
536 b = event.current_buffer
536 b = event.current_buffer
537 d = b.document
537 d = b.document
538
538
539 if b.complete_state:
539 if b.complete_state:
540 b.cancel_completion()
540 b.cancel_completion()
541 text = d.text[: d.cursor_position] + "\n"
541 text = d.text[: d.cursor_position] + "\n"
542 _, indent = inputsplitter.check_complete(text)
542 _, indent = inputsplitter.check_complete(text)
543 b.insert_text("\n" + (" " * (indent or 0)), move_cursor=False)
543 b.insert_text("\n" + (" " * (indent or 0)), move_cursor=False)
544
544
545
545
546 def open_input_in_editor(event):
546 def open_input_in_editor(event):
547 """Open code from input in external editor"""
547 """Open code from input in external editor"""
548 event.app.current_buffer.open_in_editor()
548 event.app.current_buffer.open_in_editor()
549
549
550
550
551 if sys.platform == "win32":
551 if sys.platform == "win32":
552 from IPython.core.error import TryNext
552 from IPython.core.error import TryNext
553 from IPython.lib.clipboard import (
553 from IPython.lib.clipboard import (
554 ClipboardEmpty,
554 ClipboardEmpty,
555 tkinter_clipboard_get,
555 tkinter_clipboard_get,
556 win32_clipboard_get,
556 win32_clipboard_get,
557 )
557 )
558
558
559 @undoc
559 @undoc
560 def win_paste(event):
560 def win_paste(event):
561 try:
561 try:
562 text = win32_clipboard_get()
562 text = win32_clipboard_get()
563 except TryNext:
563 except TryNext:
564 try:
564 try:
565 text = tkinter_clipboard_get()
565 text = tkinter_clipboard_get()
566 except (TryNext, ClipboardEmpty):
566 except (TryNext, ClipboardEmpty):
567 return
567 return
568 except ClipboardEmpty:
568 except ClipboardEmpty:
569 return
569 return
570 event.current_buffer.insert_text(text.replace("\t", " " * 4))
570 event.current_buffer.insert_text(text.replace("\t", " " * 4))
571
571
572 else:
572 else:
573
573
574 @undoc
574 @undoc
575 def win_paste(event):
575 def win_paste(event):
576 """Stub used on other platforms"""
576 """Stub used on other platforms"""
577 pass
577 pass
578
578
579
579
580 KEY_BINDINGS = [
580 KEY_BINDINGS = [
581 Binding(
581 Binding(
582 handle_return_or_newline_or_execute,
582 handle_return_or_newline_or_execute,
583 ["enter"],
583 ["enter"],
584 "default_buffer_focused & ~has_selection & insert_mode",
584 "default_buffer_focused & ~has_selection & insert_mode",
585 ),
585 ),
586 Binding(
586 Binding(
587 reformat_and_execute,
587 reformat_and_execute,
588 ["escape", "enter"],
588 ["escape", "enter"],
589 "default_buffer_focused & ~has_selection & insert_mode & ebivim",
589 "default_buffer_focused & ~has_selection & insert_mode & ebivim",
590 ),
590 ),
591 Binding(quit, ["c-\\"]),
591 Binding(quit, ["c-\\"]),
592 Binding(
592 Binding(
593 previous_history_or_previous_completion,
593 previous_history_or_previous_completion,
594 ["c-p"],
594 ["c-p"],
595 "vi_insert_mode & default_buffer_focused",
595 "vi_insert_mode & default_buffer_focused",
596 ),
596 ),
597 Binding(
597 Binding(
598 next_history_or_next_completion,
598 next_history_or_next_completion,
599 ["c-n"],
599 ["c-n"],
600 "vi_insert_mode & default_buffer_focused",
600 "vi_insert_mode & default_buffer_focused",
601 ),
601 ),
602 Binding(dismiss_completion, ["c-g"], "default_buffer_focused & has_completions"),
602 Binding(dismiss_completion, ["c-g"], "default_buffer_focused & has_completions"),
603 Binding(reset_buffer, ["c-c"], "default_buffer_focused"),
603 Binding(reset_buffer, ["c-c"], "default_buffer_focused"),
604 Binding(reset_search_buffer, ["c-c"], "search_buffer_focused"),
604 Binding(reset_search_buffer, ["c-c"], "search_buffer_focused"),
605 Binding(suspend_to_bg, ["c-z"], "supports_suspend"),
605 Binding(suspend_to_bg, ["c-z"], "supports_suspend"),
606 Binding(
606 Binding(
607 indent_buffer,
607 indent_buffer,
608 ["tab"], # Ctrl+I == Tab
608 ["tab"], # Ctrl+I == Tab
609 "default_buffer_focused"
609 "default_buffer_focused"
610 " & ~has_selection"
610 " & ~has_selection"
611 " & insert_mode"
611 " & insert_mode"
612 " & cursor_in_leading_ws",
612 " & cursor_in_leading_ws",
613 ),
613 ),
614 Binding(newline_autoindent, ["c-o"], "default_buffer_focused & emacs_insert_mode"),
614 Binding(newline_autoindent, ["c-o"], "default_buffer_focused & emacs_insert_mode"),
615 Binding(open_input_in_editor, ["f2"], "default_buffer_focused"),
615 Binding(open_input_in_editor, ["f2"], "default_buffer_focused"),
616 *AUTO_MATCH_BINDINGS,
616 *AUTO_MATCH_BINDINGS,
617 *AUTO_SUGGEST_BINDINGS,
617 *AUTO_SUGGEST_BINDINGS,
618 Binding(
618 Binding(
619 display_completions_like_readline,
619 display_completions_like_readline,
620 ["c-i"],
620 ["c-i"],
621 "readline_like_completions"
621 "readline_like_completions"
622 " & default_buffer_focused"
622 " & default_buffer_focused"
623 " & ~has_selection"
623 " & ~has_selection"
624 " & insert_mode"
624 " & insert_mode"
625 " & ~cursor_in_leading_ws",
625 " & ~cursor_in_leading_ws",
626 ),
626 ),
627 Binding(win_paste, ["c-v"], "default_buffer_focused & ~vi_mode & is_windows_os"),
627 Binding(win_paste, ["c-v"], "default_buffer_focused & ~vi_mode & is_windows_os"),
628 *SIMPLE_CONTROL_BINDINGS,
628 *SIMPLE_CONTROL_BINDINGS,
629 *ALT_AND_COMOBO_CONTROL_BINDINGS,
629 *ALT_AND_COMOBO_CONTROL_BINDINGS,
630 ]
630 ]
631
631
632 UNASSIGNED_ALLOWED_COMMANDS = [
632 UNASSIGNED_ALLOWED_COMMANDS = [
633 nc.beginning_of_buffer,
633 nc.beginning_of_buffer,
634 nc.end_of_buffer,
634 nc.end_of_buffer,
635 nc.end_of_line,
635 nc.end_of_line,
636 nc.forward_word,
636 nc.forward_word,
637 nc.unix_line_discard,
637 nc.unix_line_discard,
638 ]
638 ]
@@ -1,204 +1,204
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Decorators for labeling test objects.
2 """Decorators for labeling test objects.
3
3
4 Decorators that merely return a modified version of the original function
4 Decorators that merely return a modified version of the original function
5 object are straightforward. Decorators that return a new function object need
5 object are straightforward. Decorators that return a new function object need
6 to use nose.tools.make_decorator(original_function)(decorator) in returning the
6 to use nose.tools.make_decorator(original_function)(decorator) in returning the
7 decorator, in order to preserve metadata such as function name, setup and
7 decorator, in order to preserve metadata such as function name, setup and
8 teardown functions and so on - see nose.tools for more information.
8 teardown functions and so on - see nose.tools for more information.
9
9
10 This module provides a set of useful decorators meant to be ready to use in
10 This module provides a set of useful decorators meant to be ready to use in
11 your own tests. See the bottom of the file for the ready-made ones, and if you
11 your own tests. See the bottom of the file for the ready-made ones, and if you
12 find yourself writing a new one that may be of generic use, add it here.
12 find yourself writing a new one that may be of generic use, add it here.
13
13
14 Included decorators:
14 Included decorators:
15
15
16
16
17 Lightweight testing that remains unittest-compatible.
17 Lightweight testing that remains unittest-compatible.
18
18
19 - An @as_unittest decorator can be used to tag any normal parameter-less
19 - An @as_unittest decorator can be used to tag any normal parameter-less
20 function as a unittest TestCase. Then, both nose and normal unittest will
20 function as a unittest TestCase. Then, both nose and normal unittest will
21 recognize it as such. This will make it easier to migrate away from Nose if
21 recognize it as such. This will make it easier to migrate away from Nose if
22 we ever need/want to while maintaining very lightweight tests.
22 we ever need/want to while maintaining very lightweight tests.
23
23
24 NOTE: This file contains IPython-specific decorators. Using the machinery in
24 NOTE: This file contains IPython-specific decorators. Using the machinery in
25 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
25 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
26 available, OR use equivalent code in IPython.external._decorators, which
26 available, OR use equivalent code in IPython.external._decorators, which
27 we've copied verbatim from numpy.
27 we've copied verbatim from numpy.
28
28
29 """
29 """
30
30
31 # Copyright (c) IPython Development Team.
31 # Copyright (c) IPython Development Team.
32 # Distributed under the terms of the Modified BSD License.
32 # Distributed under the terms of the Modified BSD License.
33
33
34 import os
34 import os
35 import shutil
35 import shutil
36 import sys
36 import sys
37 import tempfile
37 import tempfile
38 import unittest
38 import unittest
39 from importlib import import_module
39 from importlib import import_module
40
40
41 from decorator import decorator
41 from decorator import decorator
42
42
43 # Expose the unittest-driven decorators
43 # Expose the unittest-driven decorators
44 from .ipunittest import ipdoctest, ipdocstring
44 from .ipunittest import ipdoctest, ipdocstring
45
45
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # Classes and functions
47 # Classes and functions
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49
49
50 # Simple example of the basic idea
50 # Simple example of the basic idea
51 def as_unittest(func):
51 def as_unittest(func):
52 """Decorator to make a simple function into a normal test via unittest."""
52 """Decorator to make a simple function into a normal test via unittest."""
53 class Tester(unittest.TestCase):
53 class Tester(unittest.TestCase):
54 def test(self):
54 def test(self):
55 func()
55 func()
56
56
57 Tester.__name__ = func.__name__
57 Tester.__name__ = func.__name__
58
58
59 return Tester
59 return Tester
60
60
61 # Utility functions
61 # Utility functions
62
62
63
63
64 def skipif(skip_condition, msg=None):
64 def skipif(skip_condition, msg=None):
65 """Make function raise SkipTest exception if skip_condition is true
65 """Make function raise SkipTest exception if skip_condition is true
66
66
67 Parameters
67 Parameters
68 ----------
68 ----------
69
69
70 skip_condition : bool or callable
70 skip_condition : bool or callable
71 Flag to determine whether to skip test. If the condition is a
71 Flag to determine whether to skip test. If the condition is a
72 callable, it is used at runtime to dynamically make the decision. This
72 callable, it is used at runtime to dynamically make the decision. This
73 is useful for tests that may require costly imports, to delay the cost
73 is useful for tests that may require costly imports, to delay the cost
74 until the test suite is actually executed.
74 until the test suite is actually executed.
75 msg : string
75 msg : string
76 Message to give on raising a SkipTest exception.
76 Message to give on raising a SkipTest exception.
77
77
78 Returns
78 Returns
79 -------
79 -------
80 decorator : function
80 decorator : function
81 Decorator, which, when applied to a function, causes SkipTest
81 Decorator, which, when applied to a function, causes SkipTest
82 to be raised when the skip_condition was True, and the function
82 to be raised when the skip_condition was True, and the function
83 to be called normally otherwise.
83 to be called normally otherwise.
84 """
84 """
85 if msg is None:
85 if msg is None:
86 msg = "Test skipped due to test condition."
86 msg = "Test skipped due to test condition."
87
87
88 import pytest
88 import pytest
89
89
90 assert isinstance(skip_condition, bool)
90 assert isinstance(skip_condition, bool)
91 return pytest.mark.skipif(skip_condition, reason=msg)
91 return pytest.mark.skipif(skip_condition, reason=msg)
92
92
93
93
94 # A version with the condition set to true, common case just to attach a message
94 # A version with the condition set to true, common case just to attach a message
95 # to a skip decorator
95 # to a skip decorator
96 def skip(msg=None):
96 def skip(msg=None):
97 """Decorator factory - mark a test function for skipping from test suite.
97 """Decorator factory - mark a test function for skipping from test suite.
98
98
99 Parameters
99 Parameters
100 ----------
100 ----------
101 msg : string
101 msg : string
102 Optional message to be added.
102 Optional message to be added.
103
103
104 Returns
104 Returns
105 -------
105 -------
106 decorator : function
106 decorator : function
107 Decorator, which, when applied to a function, causes SkipTest
107 Decorator, which, when applied to a function, causes SkipTest
108 to be raised, with the optional message added.
108 to be raised, with the optional message added.
109 """
109 """
110 if msg and not isinstance(msg, str):
110 if msg and not isinstance(msg, str):
111 raise ValueError('invalid object passed to `@skip` decorator, did you '
111 raise ValueError('invalid object passed to `@skip` decorator, did you '
112 'meant `@skip()` with brackets ?')
112 'meant `@skip()` with brackets ?')
113 return skipif(True, msg)
113 return skipif(True, msg)
114
114
115
115
116 def onlyif(condition, msg):
116 def onlyif(condition, msg):
117 """The reverse from skipif, see skipif for details."""
117 """The reverse from skipif, see skipif for details."""
118
118
119 return skipif(not condition, msg)
119 return skipif(not condition, msg)
120
120
121 #-----------------------------------------------------------------------------
121 #-----------------------------------------------------------------------------
122 # Utility functions for decorators
122 # Utility functions for decorators
123 def module_available(module):
123 def module_not_available(module):
124 """Can module be imported? Returns true if module does NOT import.
124 """Can module be imported? Returns true if module does NOT import.
125
125
126 This is used to make a decorator to skip tests that require module to be
126 This is used to make a decorator to skip tests that require module to be
127 available, but delay the 'import numpy' to test execution time.
127 available, but delay the 'import numpy' to test execution time.
128 """
128 """
129 try:
129 try:
130 import_module(module)
130 mod = import_module(module)
131 return True
131 mod_not_avail = False
132 except ImportError:
132 except ImportError:
133 return False
133 mod_not_avail = True
134
135 return mod_not_avail
134
136
135
137
136 #-----------------------------------------------------------------------------
138 #-----------------------------------------------------------------------------
137 # Decorators for public use
139 # Decorators for public use
138
140
139 # Decorators to skip certain tests on specific platforms.
141 # Decorators to skip certain tests on specific platforms.
140 skip_win32 = skipif(sys.platform == 'win32',
142 skip_win32 = skipif(sys.platform == 'win32',
141 "This test does not run under Windows")
143 "This test does not run under Windows")
142 skip_linux = skipif(sys.platform.startswith('linux'),
144 skip_linux = skipif(sys.platform.startswith('linux'),
143 "This test does not run under Linux")
145 "This test does not run under Linux")
144 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
146 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
145
147
146
148
147 # Decorators to skip tests if not on specific platforms.
149 # Decorators to skip tests if not on specific platforms.
148 skip_if_not_win32 = skipif(sys.platform != "win32", "This test only runs under Windows")
150 skip_if_not_win32 = skipif(sys.platform != "win32", "This test only runs under Windows")
149 skip_if_not_linux = skipif(
151 skip_if_not_linux = skipif(
150 not sys.platform.startswith("linux"), "This test only runs under Linux"
152 not sys.platform.startswith("linux"), "This test only runs under Linux"
151 )
153 )
152 skip_if_not_osx = skipif(
154 skip_if_not_osx = skipif(
153 not sys.platform.startswith("darwin"), "This test only runs under macOS"
155 not sys.platform.startswith("darwin"), "This test only runs under macOS"
154 )
156 )
155
157
156 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
158 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
157 os.environ.get('DISPLAY', '') == '')
159 os.environ.get('DISPLAY', '') == '')
158 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
160 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
159
161
160 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
162 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
161
163
162 # Other skip decorators
164 # Other skip decorators
163
165
164 # generic skip without module
166 # generic skip without module
165 skip_without = lambda mod: skipif(
167 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
166 not module_available(mod), "This test requires %s" % mod
167 )
168
168
169 skipif_not_numpy = skip_without('numpy')
169 skipif_not_numpy = skip_without('numpy')
170
170
171 skipif_not_matplotlib = skip_without('matplotlib')
171 skipif_not_matplotlib = skip_without('matplotlib')
172
172
173 # A null 'decorator', useful to make more readable code that needs to pick
173 # A null 'decorator', useful to make more readable code that needs to pick
174 # between different decorators based on OS or other conditions
174 # between different decorators based on OS or other conditions
175 null_deco = lambda f: f
175 null_deco = lambda f: f
176
176
177 # Some tests only run where we can use unicode paths. Note that we can't just
177 # Some tests only run where we can use unicode paths. Note that we can't just
178 # check os.path.supports_unicode_filenames, which is always False on Linux.
178 # check os.path.supports_unicode_filenames, which is always False on Linux.
179 try:
179 try:
180 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
180 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
181 except UnicodeEncodeError:
181 except UnicodeEncodeError:
182 unicode_paths = False
182 unicode_paths = False
183 else:
183 else:
184 unicode_paths = True
184 unicode_paths = True
185 f.close()
185 f.close()
186
186
187 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
187 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
188 "where we can use unicode in filenames."))
188 "where we can use unicode in filenames."))
189
189
190
190
191 def onlyif_cmds_exist(*commands):
191 def onlyif_cmds_exist(*commands):
192 """
192 """
193 Decorator to skip test when at least one of `commands` is not found.
193 Decorator to skip test when at least one of `commands` is not found.
194 """
194 """
195 assert (
195 assert (
196 os.environ.get("IPTEST_WORKING_DIR", None) is None
196 os.environ.get("IPTEST_WORKING_DIR", None) is None
197 ), "iptest deprecated since IPython 8.0"
197 ), "iptest deprecated since IPython 8.0"
198 for cmd in commands:
198 for cmd in commands:
199 reason = f"This test runs only if command '{cmd}' is installed"
199 reason = f"This test runs only if command '{cmd}' is installed"
200 if not shutil.which(cmd):
200 if not shutil.which(cmd):
201 import pytest
201 import pytest
202
202
203 return pytest.mark.skip(reason=reason)
203 return pytest.mark.skip(reason=reason)
204 return null_deco
204 return null_deco
@@ -1,66 +1,66
1 """
1 """
2 Test that CVEs stay fixed.
2 Test that CVEs stay fixed.
3 """
3 """
4
4
5 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
5 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
6 from pathlib import Path
6 from pathlib import Path
7 import random
7 import random
8 import sys
8 import sys
9 import os
9 import os
10 import string
10 import string
11 import subprocess
11 import subprocess
12
12
13
13
14 def test_cve_2022_21699():
14 def test_cve_2022_21699():
15 """
15 """
16 Here we test CVE-2022-21699.
16 Here we test CVE-2022-21699.
17
17
18 We create a temporary directory, cd into it.
18 We create a temporary directory, cd into it.
19 Make a profile file that should not be executed and start IPython in a subprocess,
19 Make a profile file that should not be executed and start IPython in a subprocess,
20 checking for the value.
20 checking for the value.
21
21
22
22
23
23
24 """
24 """
25
25
26 dangerous_profile_dir = Path("profile_default")
26 dangerous_profile_dir = Path("profile_default")
27
27
28 dangerous_startup_dir = dangerous_profile_dir / "startup"
28 dangerous_startup_dir = dangerous_profile_dir / "startup"
29 dangerous_expected = "CVE-2022-21699-" + "".join(
29 dangerous_expected = "CVE-2022-21699-" + "".join(
30 [random.choice(string.ascii_letters) for i in range(10)]
30 [random.choice(string.ascii_letters) for i in range(10)]
31 )
31 )
32
32
33 with TemporaryWorkingDirectory():
33 with TemporaryWorkingDirectory() as t:
34 dangerous_startup_dir.mkdir(parents=True)
34 dangerous_startup_dir.mkdir(parents=True)
35 (dangerous_startup_dir / "foo.py").write_text(
35 (dangerous_startup_dir / "foo.py").write_text(
36 f'print("{dangerous_expected}")', encoding="utf-8"
36 f'print("{dangerous_expected}")', encoding="utf-8"
37 )
37 )
38 # 1 sec to make sure FS is flushed.
38 # 1 sec to make sure FS is flushed.
39 # time.sleep(1)
39 # time.sleep(1)
40 cmd = [sys.executable, "-m", "IPython"]
40 cmd = [sys.executable, "-m", "IPython"]
41 env = os.environ.copy()
41 env = os.environ.copy()
42 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
42 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
43
43
44 # First we fake old behavior, making sure the profile is/was actually dangerous
44 # First we fake old behavior, making sure the profile is/was actually dangerous
45 p_dangerous = subprocess.Popen(
45 p_dangerous = subprocess.Popen(
46 cmd + [f"--profile-dir={dangerous_profile_dir}"],
46 cmd + [f"--profile-dir={dangerous_profile_dir}"],
47 env=env,
47 env=env,
48 stdin=subprocess.PIPE,
48 stdin=subprocess.PIPE,
49 stdout=subprocess.PIPE,
49 stdout=subprocess.PIPE,
50 stderr=subprocess.PIPE,
50 stderr=subprocess.PIPE,
51 )
51 )
52 out_dangerous, err_dangerouns = p_dangerous.communicate(b"exit\r")
52 out_dangerous, err_dangerouns = p_dangerous.communicate(b"exit\r")
53 assert dangerous_expected in out_dangerous.decode()
53 assert dangerous_expected in out_dangerous.decode()
54
54
55 # Now that we know it _would_ have been dangerous, we test it's not loaded
55 # Now that we know it _would_ have been dangerous, we test it's not loaded
56 p = subprocess.Popen(
56 p = subprocess.Popen(
57 cmd,
57 cmd,
58 env=env,
58 env=env,
59 stdin=subprocess.PIPE,
59 stdin=subprocess.PIPE,
60 stdout=subprocess.PIPE,
60 stdout=subprocess.PIPE,
61 stderr=subprocess.PIPE,
61 stderr=subprocess.PIPE,
62 )
62 )
63 out, err = p.communicate(b"exit\r")
63 out, err = p.communicate(b"exit\r")
64 assert b"IPython" in out
64 assert b"IPython" in out
65 assert dangerous_expected not in out.decode()
65 assert dangerous_expected not in out.decode()
66 assert err == b""
66 assert err == b""
@@ -1,71 +1,71
1 """cli-specific implementation of process utilities.
1 """cli-specific implementation of process utilities.
2
2
3 cli - Common Language Infrastructure for IronPython. Code
3 cli - Common Language Infrastructure for IronPython. Code
4 can run on any operating system. Check os.name for os-
4 can run on any operating system. Check os.name for os-
5 specific settings.
5 specific settings.
6
6
7 This file is only meant to be imported by process.py, not by end-users.
7 This file is only meant to be imported by process.py, not by end-users.
8
8
9 This file is largely untested. To become a full drop-in process
9 This file is largely untested. To become a full drop-in process
10 interface for IronPython will probably require you to help fill
10 interface for IronPython will probably require you to help fill
11 in the details.
11 in the details.
12 """
12 """
13
13
14 # Import cli libraries:
14 # Import cli libraries:
15 import clr
15 import clr
16 import System
16 import System
17
17
18 # Import Python libraries:
18 # Import Python libraries:
19 import os
19 import os
20
20
21 # Import IPython libraries:
21 # Import IPython libraries:
22 from ._process_common import arg_split
22 from ._process_common import arg_split
23
23
24
24
25 def system(cmd: str):
25 def system(cmd):
26 """
26 """
27 system(cmd) should work in a cli environment on Mac OSX, Linux,
27 system(cmd) should work in a cli environment on Mac OSX, Linux,
28 and Windows
28 and Windows
29 """
29 """
30 psi = System.Diagnostics.ProcessStartInfo(cmd)
30 psi = System.Diagnostics.ProcessStartInfo(cmd)
31 psi.RedirectStandardOutput = True
31 psi.RedirectStandardOutput = True
32 psi.RedirectStandardError = True
32 psi.RedirectStandardError = True
33 psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal
33 psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal
34 psi.UseShellExecute = False
34 psi.UseShellExecute = False
35 # Start up process:
35 # Start up process:
36 _reg = System.Diagnostics.Process.Start(psi)
36 reg = System.Diagnostics.Process.Start(psi)
37
37
38
38
39 def getoutput(cmd: str):
39 def getoutput(cmd):
40 """
40 """
41 getoutput(cmd) should work in a cli environment on Mac OSX, Linux,
41 getoutput(cmd) should work in a cli environment on Mac OSX, Linux,
42 and Windows
42 and Windows
43 """
43 """
44 psi = System.Diagnostics.ProcessStartInfo(cmd)
44 psi = System.Diagnostics.ProcessStartInfo(cmd)
45 psi.RedirectStandardOutput = True
45 psi.RedirectStandardOutput = True
46 psi.RedirectStandardError = True
46 psi.RedirectStandardError = True
47 psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal
47 psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal
48 psi.UseShellExecute = False
48 psi.UseShellExecute = False
49 # Start up process:
49 # Start up process:
50 reg = System.Diagnostics.Process.Start(psi)
50 reg = System.Diagnostics.Process.Start(psi)
51 myOutput = reg.StandardOutput
51 myOutput = reg.StandardOutput
52 output = myOutput.ReadToEnd()
52 output = myOutput.ReadToEnd()
53 myError = reg.StandardError
53 myError = reg.StandardError
54 _error = myError.ReadToEnd()
54 error = myError.ReadToEnd()
55 return output
55 return output
56
56
57
57
58 def check_pid(pid: int):
58 def check_pid(pid):
59 """
59 """
60 Check if a process with the given PID (pid) exists
60 Check if a process with the given PID (pid) exists
61 """
61 """
62 try:
62 try:
63 System.Diagnostics.Process.GetProcessById(pid)
63 System.Diagnostics.Process.GetProcessById(pid)
64 # process with given pid is running
64 # process with given pid is running
65 return True
65 return True
66 except System.InvalidOperationException:
66 except System.InvalidOperationException:
67 # process wasn't started by this object (but is running)
67 # process wasn't started by this object (but is running)
68 return True
68 return True
69 except System.ArgumentException:
69 except System.ArgumentException:
70 # process with given pid isn't running
70 # process with given pid isn't running
71 return False
71 return False
@@ -1,130 +1,134
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 IO related utilities.
3 IO related utilities.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9
9
10
10
11 import atexit
12 import os
11 import sys
13 import sys
12 import tempfile
14 import tempfile
13 from pathlib import Path
15 from pathlib import Path
16 from warnings import warn
14
17
15 from .capture import capture_output as capture_output
18 from IPython.utils.decorators import undoc
19 from .capture import CapturedIO, capture_output
16
20
17 class Tee(object):
21 class Tee(object):
18 """A class to duplicate an output stream to stdout/err.
22 """A class to duplicate an output stream to stdout/err.
19
23
20 This works in a manner very similar to the Unix 'tee' command.
24 This works in a manner very similar to the Unix 'tee' command.
21
25
22 When the object is closed or deleted, it closes the original file given to
26 When the object is closed or deleted, it closes the original file given to
23 it for duplication.
27 it for duplication.
24 """
28 """
25 # Inspired by:
29 # Inspired by:
26 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
30 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
27
31
28 def __init__(self, file_or_name, mode="w", channel='stdout'):
32 def __init__(self, file_or_name, mode="w", channel='stdout'):
29 """Construct a new Tee object.
33 """Construct a new Tee object.
30
34
31 Parameters
35 Parameters
32 ----------
36 ----------
33 file_or_name : filename or open filehandle (writable)
37 file_or_name : filename or open filehandle (writable)
34 File that will be duplicated
38 File that will be duplicated
35 mode : optional, valid mode for open().
39 mode : optional, valid mode for open().
36 If a filename was give, open with this mode.
40 If a filename was give, open with this mode.
37 channel : str, one of ['stdout', 'stderr']
41 channel : str, one of ['stdout', 'stderr']
38 """
42 """
39 if channel not in ['stdout', 'stderr']:
43 if channel not in ['stdout', 'stderr']:
40 raise ValueError('Invalid channel spec %s' % channel)
44 raise ValueError('Invalid channel spec %s' % channel)
41
45
42 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
46 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
43 self.file = file_or_name
47 self.file = file_or_name
44 else:
48 else:
45 encoding = None if "b" in mode else "utf-8"
49 encoding = None if "b" in mode else "utf-8"
46 self.file = open(file_or_name, mode, encoding=encoding)
50 self.file = open(file_or_name, mode, encoding=encoding)
47 self.channel = channel
51 self.channel = channel
48 self.ostream = getattr(sys, channel)
52 self.ostream = getattr(sys, channel)
49 setattr(sys, channel, self)
53 setattr(sys, channel, self)
50 self._closed = False
54 self._closed = False
51
55
52 def close(self):
56 def close(self):
53 """Close the file and restore the channel."""
57 """Close the file and restore the channel."""
54 self.flush()
58 self.flush()
55 setattr(sys, self.channel, self.ostream)
59 setattr(sys, self.channel, self.ostream)
56 self.file.close()
60 self.file.close()
57 self._closed = True
61 self._closed = True
58
62
59 def write(self, data):
63 def write(self, data):
60 """Write data to both channels."""
64 """Write data to both channels."""
61 self.file.write(data)
65 self.file.write(data)
62 self.ostream.write(data)
66 self.ostream.write(data)
63 self.ostream.flush()
67 self.ostream.flush()
64
68
65 def flush(self):
69 def flush(self):
66 """Flush both channels."""
70 """Flush both channels."""
67 self.file.flush()
71 self.file.flush()
68 self.ostream.flush()
72 self.ostream.flush()
69
73
70 def __del__(self):
74 def __del__(self):
71 if not self._closed:
75 if not self._closed:
72 self.close()
76 self.close()
73
77
74 def isatty(self):
78 def isatty(self):
75 return False
79 return False
76
80
77 def ask_yes_no(prompt, default=None, interrupt=None):
81 def ask_yes_no(prompt, default=None, interrupt=None):
78 """Asks a question and returns a boolean (y/n) answer.
82 """Asks a question and returns a boolean (y/n) answer.
79
83
80 If default is given (one of 'y','n'), it is used if the user input is
84 If default is given (one of 'y','n'), it is used if the user input is
81 empty. If interrupt is given (one of 'y','n'), it is used if the user
85 empty. If interrupt is given (one of 'y','n'), it is used if the user
82 presses Ctrl-C. Otherwise the question is repeated until an answer is
86 presses Ctrl-C. Otherwise the question is repeated until an answer is
83 given.
87 given.
84
88
85 An EOF is treated as the default answer. If there is no default, an
89 An EOF is treated as the default answer. If there is no default, an
86 exception is raised to prevent infinite loops.
90 exception is raised to prevent infinite loops.
87
91
88 Valid answers are: y/yes/n/no (match is not case sensitive)."""
92 Valid answers are: y/yes/n/no (match is not case sensitive)."""
89
93
90 answers = {'y':True,'n':False,'yes':True,'no':False}
94 answers = {'y':True,'n':False,'yes':True,'no':False}
91 ans = None
95 ans = None
92 while ans not in answers.keys():
96 while ans not in answers.keys():
93 try:
97 try:
94 ans = input(prompt+' ').lower()
98 ans = input(prompt+' ').lower()
95 if not ans: # response was an empty string
99 if not ans: # response was an empty string
96 ans = default
100 ans = default
97 except KeyboardInterrupt:
101 except KeyboardInterrupt:
98 if interrupt:
102 if interrupt:
99 ans = interrupt
103 ans = interrupt
100 print("\r")
104 print("\r")
101 except EOFError:
105 except EOFError:
102 if default in answers.keys():
106 if default in answers.keys():
103 ans = default
107 ans = default
104 print()
108 print()
105 else:
109 else:
106 raise
110 raise
107
111
108 return answers[ans]
112 return answers[ans]
109
113
110
114
111 def temp_pyfile(src, ext='.py'):
115 def temp_pyfile(src, ext='.py'):
112 """Make a temporary python file, return filename and filehandle.
116 """Make a temporary python file, return filename and filehandle.
113
117
114 Parameters
118 Parameters
115 ----------
119 ----------
116 src : string or list of strings (no need for ending newlines if list)
120 src : string or list of strings (no need for ending newlines if list)
117 Source code to be written to the file.
121 Source code to be written to the file.
118 ext : optional, string
122 ext : optional, string
119 Extension for the generated file.
123 Extension for the generated file.
120
124
121 Returns
125 Returns
122 -------
126 -------
123 (filename, open filehandle)
127 (filename, open filehandle)
124 It is the caller's responsibility to close the open file and unlink it.
128 It is the caller's responsibility to close the open file and unlink it.
125 """
129 """
126 fname = tempfile.mkstemp(ext)[1]
130 fname = tempfile.mkstemp(ext)[1]
127 with open(Path(fname), "w", encoding="utf-8") as f:
131 with open(Path(fname), "w", encoding="utf-8") as f:
128 f.write(src)
132 f.write(src)
129 f.flush()
133 f.flush()
130 return fname
134 return fname
@@ -1,61 +1,61
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for io.py"""
2 """Tests for io.py"""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7
7
8 import sys
8 import sys
9 from io import StringIO
9 from io import StringIO
10
10
11 import unittest
11 import unittest
12
12
13 from IPython.utils.io import Tee, capture_output
13 from IPython.utils.io import Tee, capture_output
14
14
15
15
16 def test_tee_simple():
16 def test_tee_simple():
17 "Very simple check with stdout only"
17 "Very simple check with stdout only"
18 chan = StringIO()
18 chan = StringIO()
19 text = 'Hello'
19 text = 'Hello'
20 tee = Tee(chan, channel='stdout')
20 tee = Tee(chan, channel='stdout')
21 print(text, file=chan)
21 print(text, file=chan)
22 assert chan.getvalue() == text + "\n"
22 assert chan.getvalue() == text + "\n"
23 tee.close()
23 tee.close()
24
24
25
25
26 class TeeTestCase(unittest.TestCase):
26 class TeeTestCase(unittest.TestCase):
27
27
28 def tchan(self, channel):
28 def tchan(self, channel):
29 trap = StringIO()
29 trap = StringIO()
30 chan = StringIO()
30 chan = StringIO()
31 text = 'Hello'
31 text = 'Hello'
32
32
33 std_ori = getattr(sys, channel)
33 std_ori = getattr(sys, channel)
34 setattr(sys, channel, trap)
34 setattr(sys, channel, trap)
35
35
36 tee = Tee(chan, channel=channel)
36 tee = Tee(chan, channel=channel)
37
37
38 print(text, end='', file=chan)
38 print(text, end='', file=chan)
39 _trap_val = trap.getvalue()
39 trap_val = trap.getvalue()
40 self.assertEqual(chan.getvalue(), text)
40 self.assertEqual(chan.getvalue(), text)
41
41
42 tee.close()
42 tee.close()
43
43
44 setattr(sys, channel, std_ori)
44 setattr(sys, channel, std_ori)
45 assert getattr(sys, channel) == std_ori
45 assert getattr(sys, channel) == std_ori
46
46
47 def test(self):
47 def test(self):
48 for chan in ['stdout', 'stderr']:
48 for chan in ['stdout', 'stderr']:
49 self.tchan(chan)
49 self.tchan(chan)
50
50
51 class TestIOStream(unittest.TestCase):
51 class TestIOStream(unittest.TestCase):
52
52
53 def test_capture_output(self):
53 def test_capture_output(self):
54 """capture_output() context works"""
54 """capture_output() context works"""
55
55
56 with capture_output() as io:
56 with capture_output() as io:
57 print("hi, stdout")
57 print("hi, stdout")
58 print("hi, stderr", file=sys.stderr)
58 print("hi, stderr", file=sys.stderr)
59
59
60 self.assertEqual(io.stdout, "hi, stdout\n")
60 self.assertEqual(io.stdout, "hi, stdout\n")
61 self.assertEqual(io.stderr, "hi, stderr\n")
61 self.assertEqual(io.stderr, "hi, stderr\n")
@@ -1,506 +1,506
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.utils.path.py"""
2 """Tests for IPython.utils.path.py"""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 import os
7 import os
8 import shutil
8 import shutil
9 import sys
9 import sys
10 import tempfile
10 import tempfile
11 import unittest
11 import unittest
12 from contextlib import contextmanager
12 from contextlib import contextmanager
13 from importlib import reload
13 from importlib import reload
14 from os.path import abspath, join
14 from os.path import abspath, join
15 from unittest.mock import patch
15 from unittest.mock import patch
16
16
17 import pytest
17 import pytest
18 from tempfile import TemporaryDirectory
18 from tempfile import TemporaryDirectory
19
19
20 import IPython
20 import IPython
21 from IPython import paths
21 from IPython import paths
22 from IPython.testing import decorators as dec
22 from IPython.testing import decorators as dec
23 from IPython.testing.decorators import (
23 from IPython.testing.decorators import (
24 onlyif_unicode_paths,
24 onlyif_unicode_paths,
25 skip_if_not_win32,
25 skip_if_not_win32,
26 skip_win32,
26 skip_win32,
27 )
27 )
28 from IPython.testing.tools import make_tempfile
28 from IPython.testing.tools import make_tempfile
29 from IPython.utils import path
29 from IPython.utils import path
30
30
31 # Platform-dependent imports
31 # Platform-dependent imports
32 try:
32 try:
33 import winreg as wreg
33 import winreg as wreg
34 except ImportError:
34 except ImportError:
35 #Fake _winreg module on non-windows platforms
35 #Fake _winreg module on non-windows platforms
36 import types
36 import types
37 wr_name = "winreg"
37 wr_name = "winreg"
38 sys.modules[wr_name] = types.ModuleType(wr_name)
38 sys.modules[wr_name] = types.ModuleType(wr_name)
39 try:
39 try:
40 import winreg as wreg
40 import winreg as wreg
41 except ImportError:
41 except ImportError:
42 import _winreg as wreg
42 import _winreg as wreg
43
43
44 #Add entries that needs to be stubbed by the testing code
44 #Add entries that needs to be stubbed by the testing code
45 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
45 (wreg.OpenKey, wreg.QueryValueEx,) = (None, None)
46
46
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48 # Globals
48 # Globals
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50 env = os.environ
50 env = os.environ
51 TMP_TEST_DIR = tempfile.mkdtemp()
51 TMP_TEST_DIR = tempfile.mkdtemp()
52 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
52 HOME_TEST_DIR = join(TMP_TEST_DIR, "home_test_dir")
53 #
53 #
54 # Setup/teardown functions/decorators
54 # Setup/teardown functions/decorators
55 #
55 #
56
56
57 def setup_module():
57 def setup_module():
58 """Setup testenvironment for the module:
58 """Setup testenvironment for the module:
59
59
60 - Adds dummy home dir tree
60 - Adds dummy home dir tree
61 """
61 """
62 # Do not mask exceptions here. In particular, catching WindowsError is a
62 # Do not mask exceptions here. In particular, catching WindowsError is a
63 # problem because that exception is only defined on Windows...
63 # problem because that exception is only defined on Windows...
64 os.makedirs(os.path.join(HOME_TEST_DIR, 'ipython'))
64 os.makedirs(os.path.join(HOME_TEST_DIR, 'ipython'))
65
65
66
66
67 def teardown_module():
67 def teardown_module():
68 """Teardown testenvironment for the module:
68 """Teardown testenvironment for the module:
69
69
70 - Remove dummy home dir tree
70 - Remove dummy home dir tree
71 """
71 """
72 # Note: we remove the parent test dir, which is the root of all test
72 # Note: we remove the parent test dir, which is the root of all test
73 # subdirs we may have created. Use shutil instead of os.removedirs, so
73 # subdirs we may have created. Use shutil instead of os.removedirs, so
74 # that non-empty directories are all recursively removed.
74 # that non-empty directories are all recursively removed.
75 shutil.rmtree(TMP_TEST_DIR)
75 shutil.rmtree(TMP_TEST_DIR)
76
76
77
77
78 # Build decorator that uses the setup_environment/setup_environment
78 # Build decorator that uses the setup_environment/setup_environment
79 @pytest.fixture
79 @pytest.fixture
80 def environment():
80 def environment():
81 global oldstuff, platformstuff
81 global oldstuff, platformstuff
82 oldstuff = (
82 oldstuff = (
83 env.copy(),
83 env.copy(),
84 os.name,
84 os.name,
85 sys.platform,
85 sys.platform,
86 path.get_home_dir,
86 path.get_home_dir,
87 IPython.__file__,
87 IPython.__file__,
88 os.getcwd(),
88 os.getcwd(),
89 )
89 )
90
90
91 yield
91 yield
92
92
93 (
93 (
94 oldenv,
94 oldenv,
95 os.name,
95 os.name,
96 sys.platform,
96 sys.platform,
97 path.get_home_dir,
97 path.get_home_dir,
98 IPython.__file__,
98 IPython.__file__,
99 old_wd,
99 old_wd,
100 ) = oldstuff
100 ) = oldstuff
101 os.chdir(old_wd)
101 os.chdir(old_wd)
102 reload(path)
102 reload(path)
103
103
104 for key in list(env):
104 for key in list(env):
105 if key not in oldenv:
105 if key not in oldenv:
106 del env[key]
106 del env[key]
107 env.update(oldenv)
107 env.update(oldenv)
108 assert not hasattr(sys, "frozen")
108 assert not hasattr(sys, "frozen")
109
109
110
110
111 with_environment = pytest.mark.usefixtures("environment")
111 with_environment = pytest.mark.usefixtures("environment")
112
112
113
113
114 @skip_if_not_win32
114 @skip_if_not_win32
115 @with_environment
115 @with_environment
116 def test_get_home_dir_1(monkeypatch):
116 def test_get_home_dir_1(monkeypatch):
117 """Testcase for py2exe logic, un-compressed lib
117 """Testcase for py2exe logic, un-compressed lib
118 """
118 """
119 unfrozen = path.get_home_dir()
119 unfrozen = path.get_home_dir()
120 monkeypatch.setattr(sys, "frozen", True, raising=False)
120 monkeypatch.setattr(sys, "frozen", True, raising=False)
121
121
122 #fake filename for IPython.__init__
122 #fake filename for IPython.__init__
123 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
123 IPython.__file__ = abspath(join(HOME_TEST_DIR, "Lib/IPython/__init__.py"))
124
124
125 home_dir = path.get_home_dir()
125 home_dir = path.get_home_dir()
126 assert home_dir == unfrozen
126 assert home_dir == unfrozen
127
127
128
128
129 @skip_if_not_win32
129 @skip_if_not_win32
130 @with_environment
130 @with_environment
131 def test_get_home_dir_2(monkeypatch):
131 def test_get_home_dir_2(monkeypatch):
132 """Testcase for py2exe logic, compressed lib
132 """Testcase for py2exe logic, compressed lib
133 """
133 """
134 unfrozen = path.get_home_dir()
134 unfrozen = path.get_home_dir()
135 monkeypatch.setattr(sys, "frozen", True, raising=False)
135 monkeypatch.setattr(sys, "frozen", True, raising=False)
136 # fake filename for IPython.__init__
136 # fake filename for IPython.__init__
137 IPython.__file__ = abspath(
137 IPython.__file__ = abspath(
138 join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")
138 join(HOME_TEST_DIR, "Library.zip/IPython/__init__.py")
139 ).lower()
139 ).lower()
140
140
141 home_dir = path.get_home_dir(True)
141 home_dir = path.get_home_dir(True)
142 assert home_dir == unfrozen
142 assert home_dir == unfrozen
143
143
144
144
145 @skip_win32
145 @skip_win32
146 @with_environment
146 @with_environment
147 def test_get_home_dir_3():
147 def test_get_home_dir_3():
148 """get_home_dir() uses $HOME if set"""
148 """get_home_dir() uses $HOME if set"""
149 env["HOME"] = HOME_TEST_DIR
149 env["HOME"] = HOME_TEST_DIR
150 home_dir = path.get_home_dir(True)
150 home_dir = path.get_home_dir(True)
151 # get_home_dir expands symlinks
151 # get_home_dir expands symlinks
152 assert home_dir == os.path.realpath(env["HOME"])
152 assert home_dir == os.path.realpath(env["HOME"])
153
153
154
154
155 @with_environment
155 @with_environment
156 def test_get_home_dir_4():
156 def test_get_home_dir_4():
157 """get_home_dir() still works if $HOME is not set"""
157 """get_home_dir() still works if $HOME is not set"""
158
158
159 if 'HOME' in env: del env['HOME']
159 if 'HOME' in env: del env['HOME']
160 # this should still succeed, but we don't care what the answer is
160 # this should still succeed, but we don't care what the answer is
161 _home = path.get_home_dir(False)
161 home = path.get_home_dir(False)
162
162
163 @skip_win32
163 @skip_win32
164 @with_environment
164 @with_environment
165 def test_get_home_dir_5(monkeypatch):
165 def test_get_home_dir_5(monkeypatch):
166 """raise HomeDirError if $HOME is specified, but not a writable dir"""
166 """raise HomeDirError if $HOME is specified, but not a writable dir"""
167 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
167 env['HOME'] = abspath(HOME_TEST_DIR+'garbage')
168 # set os.name = posix, to prevent My Documents fallback on Windows
168 # set os.name = posix, to prevent My Documents fallback on Windows
169 monkeypatch.setattr(os, "name", "posix")
169 monkeypatch.setattr(os, "name", "posix")
170 pytest.raises(path.HomeDirError, path.get_home_dir, True)
170 pytest.raises(path.HomeDirError, path.get_home_dir, True)
171
171
172 # Should we stub wreg fully so we can run the test on all platforms?
172 # Should we stub wreg fully so we can run the test on all platforms?
173 @skip_if_not_win32
173 @skip_if_not_win32
174 @with_environment
174 @with_environment
175 def test_get_home_dir_8(monkeypatch):
175 def test_get_home_dir_8(monkeypatch):
176 """Using registry hack for 'My Documents', os=='nt'
176 """Using registry hack for 'My Documents', os=='nt'
177
177
178 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
178 HOMESHARE, HOMEDRIVE, HOMEPATH, USERPROFILE and others are missing.
179 """
179 """
180 monkeypatch.setattr(os, "name", "nt")
180 monkeypatch.setattr(os, "name", "nt")
181 # Remove from stub environment all keys that may be set
181 # Remove from stub environment all keys that may be set
182 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
182 for key in ['HOME', 'HOMESHARE', 'HOMEDRIVE', 'HOMEPATH', 'USERPROFILE']:
183 env.pop(key, None)
183 env.pop(key, None)
184
184
185 class key:
185 class key:
186 def __enter__(self):
186 def __enter__(self):
187 pass
187 pass
188 def Close(self):
188 def Close(self):
189 pass
189 pass
190 def __exit__(*args, **kwargs):
190 def __exit__(*args, **kwargs):
191 pass
191 pass
192
192
193 with patch.object(wreg, 'OpenKey', return_value=key()), \
193 with patch.object(wreg, 'OpenKey', return_value=key()), \
194 patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]):
194 patch.object(wreg, 'QueryValueEx', return_value=[abspath(HOME_TEST_DIR)]):
195 home_dir = path.get_home_dir()
195 home_dir = path.get_home_dir()
196 assert home_dir == abspath(HOME_TEST_DIR)
196 assert home_dir == abspath(HOME_TEST_DIR)
197
197
198 @with_environment
198 @with_environment
199 def test_get_xdg_dir_0(monkeypatch):
199 def test_get_xdg_dir_0(monkeypatch):
200 """test_get_xdg_dir_0, check xdg_dir"""
200 """test_get_xdg_dir_0, check xdg_dir"""
201 monkeypatch.setattr(path, "_writable_dir", lambda path: True)
201 monkeypatch.setattr(path, "_writable_dir", lambda path: True)
202 monkeypatch.setattr(path, "get_home_dir", lambda: "somewhere")
202 monkeypatch.setattr(path, "get_home_dir", lambda: "somewhere")
203 monkeypatch.setattr(os, "name", "posix")
203 monkeypatch.setattr(os, "name", "posix")
204 monkeypatch.setattr(sys, "platform", "linux2")
204 monkeypatch.setattr(sys, "platform", "linux2")
205 env.pop('IPYTHON_DIR', None)
205 env.pop('IPYTHON_DIR', None)
206 env.pop('IPYTHONDIR', None)
206 env.pop('IPYTHONDIR', None)
207 env.pop('XDG_CONFIG_HOME', None)
207 env.pop('XDG_CONFIG_HOME', None)
208
208
209 assert path.get_xdg_dir() == os.path.join("somewhere", ".config")
209 assert path.get_xdg_dir() == os.path.join("somewhere", ".config")
210
210
211
211
212 @with_environment
212 @with_environment
213 def test_get_xdg_dir_1(monkeypatch):
213 def test_get_xdg_dir_1(monkeypatch):
214 """test_get_xdg_dir_1, check nonexistent xdg_dir"""
214 """test_get_xdg_dir_1, check nonexistent xdg_dir"""
215 monkeypatch.setattr(path, "get_home_dir", lambda: HOME_TEST_DIR)
215 monkeypatch.setattr(path, "get_home_dir", lambda: HOME_TEST_DIR)
216 monkeypatch.setattr(os, "name", "posix")
216 monkeypatch.setattr(os, "name", "posix")
217 monkeypatch.setattr(sys, "platform", "linux2")
217 monkeypatch.setattr(sys, "platform", "linux2")
218 env.pop("IPYTHON_DIR", None)
218 env.pop("IPYTHON_DIR", None)
219 env.pop("IPYTHONDIR", None)
219 env.pop("IPYTHONDIR", None)
220 env.pop("XDG_CONFIG_HOME", None)
220 env.pop("XDG_CONFIG_HOME", None)
221 assert path.get_xdg_dir() is None
221 assert path.get_xdg_dir() is None
222
222
223 @with_environment
223 @with_environment
224 def test_get_xdg_dir_2(monkeypatch):
224 def test_get_xdg_dir_2(monkeypatch):
225 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
225 """test_get_xdg_dir_2, check xdg_dir default to ~/.config"""
226 monkeypatch.setattr(path, "get_home_dir", lambda: HOME_TEST_DIR)
226 monkeypatch.setattr(path, "get_home_dir", lambda: HOME_TEST_DIR)
227 monkeypatch.setattr(os, "name", "posix")
227 monkeypatch.setattr(os, "name", "posix")
228 monkeypatch.setattr(sys, "platform", "linux2")
228 monkeypatch.setattr(sys, "platform", "linux2")
229 env.pop("IPYTHON_DIR", None)
229 env.pop("IPYTHON_DIR", None)
230 env.pop("IPYTHONDIR", None)
230 env.pop("IPYTHONDIR", None)
231 env.pop("XDG_CONFIG_HOME", None)
231 env.pop("XDG_CONFIG_HOME", None)
232 cfgdir = os.path.join(path.get_home_dir(), ".config")
232 cfgdir = os.path.join(path.get_home_dir(), ".config")
233 if not os.path.exists(cfgdir):
233 if not os.path.exists(cfgdir):
234 os.makedirs(cfgdir)
234 os.makedirs(cfgdir)
235
235
236 assert path.get_xdg_dir() == cfgdir
236 assert path.get_xdg_dir() == cfgdir
237
237
238 @with_environment
238 @with_environment
239 def test_get_xdg_dir_3(monkeypatch):
239 def test_get_xdg_dir_3(monkeypatch):
240 """test_get_xdg_dir_3, check xdg_dir not used on non-posix systems"""
240 """test_get_xdg_dir_3, check xdg_dir not used on non-posix systems"""
241 monkeypatch.setattr(path, "get_home_dir", lambda: HOME_TEST_DIR)
241 monkeypatch.setattr(path, "get_home_dir", lambda: HOME_TEST_DIR)
242 monkeypatch.setattr(os, "name", "nt")
242 monkeypatch.setattr(os, "name", "nt")
243 monkeypatch.setattr(sys, "platform", "win32")
243 monkeypatch.setattr(sys, "platform", "win32")
244 env.pop("IPYTHON_DIR", None)
244 env.pop("IPYTHON_DIR", None)
245 env.pop("IPYTHONDIR", None)
245 env.pop("IPYTHONDIR", None)
246 env.pop("XDG_CONFIG_HOME", None)
246 env.pop("XDG_CONFIG_HOME", None)
247 cfgdir = os.path.join(path.get_home_dir(), ".config")
247 cfgdir = os.path.join(path.get_home_dir(), ".config")
248 os.makedirs(cfgdir, exist_ok=True)
248 os.makedirs(cfgdir, exist_ok=True)
249
249
250 assert path.get_xdg_dir() is None
250 assert path.get_xdg_dir() is None
251
251
252 def test_filefind():
252 def test_filefind():
253 """Various tests for filefind"""
253 """Various tests for filefind"""
254 f = tempfile.NamedTemporaryFile()
254 f = tempfile.NamedTemporaryFile()
255 # print('fname:',f.name)
255 # print('fname:',f.name)
256 alt_dirs = paths.get_ipython_dir()
256 alt_dirs = paths.get_ipython_dir()
257 _t = path.filefind(f.name, alt_dirs)
257 t = path.filefind(f.name, alt_dirs)
258 # print('found:',t)
258 # print('found:',t)
259
259
260
260
261 @dec.skip_if_not_win32
261 @dec.skip_if_not_win32
262 def test_get_long_path_name_win32():
262 def test_get_long_path_name_win32():
263 with TemporaryDirectory() as tmpdir:
263 with TemporaryDirectory() as tmpdir:
264
264
265 # Make a long path. Expands the path of tmpdir prematurely as it may already have a long
265 # Make a long path. Expands the path of tmpdir prematurely as it may already have a long
266 # path component, so ensure we include the long form of it
266 # path component, so ensure we include the long form of it
267 long_path = os.path.join(path.get_long_path_name(tmpdir), 'this is my long path name')
267 long_path = os.path.join(path.get_long_path_name(tmpdir), 'this is my long path name')
268 os.makedirs(long_path)
268 os.makedirs(long_path)
269
269
270 # Test to see if the short path evaluates correctly.
270 # Test to see if the short path evaluates correctly.
271 short_path = os.path.join(tmpdir, 'THISIS~1')
271 short_path = os.path.join(tmpdir, 'THISIS~1')
272 evaluated_path = path.get_long_path_name(short_path)
272 evaluated_path = path.get_long_path_name(short_path)
273 assert evaluated_path.lower() == long_path.lower()
273 assert evaluated_path.lower() == long_path.lower()
274
274
275
275
276 @dec.skip_win32
276 @dec.skip_win32
277 def test_get_long_path_name():
277 def test_get_long_path_name():
278 p = path.get_long_path_name("/usr/local")
278 p = path.get_long_path_name("/usr/local")
279 assert p == "/usr/local"
279 assert p == "/usr/local"
280
280
281
281
282 @dec.skip_win32 # can't create not-user-writable dir on win
282 @dec.skip_win32 # can't create not-user-writable dir on win
283 @with_environment
283 @with_environment
284 def test_not_writable_ipdir():
284 def test_not_writable_ipdir():
285 tmpdir = tempfile.mkdtemp()
285 tmpdir = tempfile.mkdtemp()
286 os.name = "posix"
286 os.name = "posix"
287 env.pop("IPYTHON_DIR", None)
287 env.pop("IPYTHON_DIR", None)
288 env.pop("IPYTHONDIR", None)
288 env.pop("IPYTHONDIR", None)
289 env.pop("XDG_CONFIG_HOME", None)
289 env.pop("XDG_CONFIG_HOME", None)
290 env["HOME"] = tmpdir
290 env["HOME"] = tmpdir
291 ipdir = os.path.join(tmpdir, ".ipython")
291 ipdir = os.path.join(tmpdir, ".ipython")
292 os.mkdir(ipdir, 0o555)
292 os.mkdir(ipdir, 0o555)
293 try:
293 try:
294 open(os.path.join(ipdir, "_foo_"), "w", encoding="utf-8").close()
294 open(os.path.join(ipdir, "_foo_"), "w", encoding="utf-8").close()
295 except IOError:
295 except IOError:
296 pass
296 pass
297 else:
297 else:
298 # I can still write to an unwritable dir,
298 # I can still write to an unwritable dir,
299 # assume I'm root and skip the test
299 # assume I'm root and skip the test
300 pytest.skip("I can't create directories that I can't write to")
300 pytest.skip("I can't create directories that I can't write to")
301
301
302 with pytest.warns(UserWarning, match="is not a writable location"):
302 with pytest.warns(UserWarning, match="is not a writable location"):
303 ipdir = paths.get_ipython_dir()
303 ipdir = paths.get_ipython_dir()
304 env.pop("IPYTHON_DIR", None)
304 env.pop("IPYTHON_DIR", None)
305
305
306
306
307 @with_environment
307 @with_environment
308 def test_get_py_filename():
308 def test_get_py_filename():
309 os.chdir(TMP_TEST_DIR)
309 os.chdir(TMP_TEST_DIR)
310 with make_tempfile("foo.py"):
310 with make_tempfile("foo.py"):
311 assert path.get_py_filename("foo.py") == "foo.py"
311 assert path.get_py_filename("foo.py") == "foo.py"
312 assert path.get_py_filename("foo") == "foo.py"
312 assert path.get_py_filename("foo") == "foo.py"
313 with make_tempfile("foo"):
313 with make_tempfile("foo"):
314 assert path.get_py_filename("foo") == "foo"
314 assert path.get_py_filename("foo") == "foo"
315 pytest.raises(IOError, path.get_py_filename, "foo.py")
315 pytest.raises(IOError, path.get_py_filename, "foo.py")
316 pytest.raises(IOError, path.get_py_filename, "foo")
316 pytest.raises(IOError, path.get_py_filename, "foo")
317 pytest.raises(IOError, path.get_py_filename, "foo.py")
317 pytest.raises(IOError, path.get_py_filename, "foo.py")
318 true_fn = "foo with spaces.py"
318 true_fn = "foo with spaces.py"
319 with make_tempfile(true_fn):
319 with make_tempfile(true_fn):
320 assert path.get_py_filename("foo with spaces") == true_fn
320 assert path.get_py_filename("foo with spaces") == true_fn
321 assert path.get_py_filename("foo with spaces.py") == true_fn
321 assert path.get_py_filename("foo with spaces.py") == true_fn
322 pytest.raises(IOError, path.get_py_filename, '"foo with spaces.py"')
322 pytest.raises(IOError, path.get_py_filename, '"foo with spaces.py"')
323 pytest.raises(IOError, path.get_py_filename, "'foo with spaces.py'")
323 pytest.raises(IOError, path.get_py_filename, "'foo with spaces.py'")
324
324
325 @onlyif_unicode_paths
325 @onlyif_unicode_paths
326 def test_unicode_in_filename():
326 def test_unicode_in_filename():
327 """When a file doesn't exist, the exception raised should be safe to call
327 """When a file doesn't exist, the exception raised should be safe to call
328 str() on - i.e. in Python 2 it must only have ASCII characters.
328 str() on - i.e. in Python 2 it must only have ASCII characters.
329
329
330 https://github.com/ipython/ipython/issues/875
330 https://github.com/ipython/ipython/issues/875
331 """
331 """
332 try:
332 try:
333 # these calls should not throw unicode encode exceptions
333 # these calls should not throw unicode encode exceptions
334 path.get_py_filename('fooéè.py')
334 path.get_py_filename('fooéè.py')
335 except IOError as ex:
335 except IOError as ex:
336 str(ex)
336 str(ex)
337
337
338
338
339 class TestShellGlob(unittest.TestCase):
339 class TestShellGlob(unittest.TestCase):
340
340
341 @classmethod
341 @classmethod
342 def setUpClass(cls):
342 def setUpClass(cls):
343 cls.filenames_start_with_a = ['a0', 'a1', 'a2']
343 cls.filenames_start_with_a = ['a0', 'a1', 'a2']
344 cls.filenames_end_with_b = ['0b', '1b', '2b']
344 cls.filenames_end_with_b = ['0b', '1b', '2b']
345 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
345 cls.filenames = cls.filenames_start_with_a + cls.filenames_end_with_b
346 cls.tempdir = TemporaryDirectory()
346 cls.tempdir = TemporaryDirectory()
347 td = cls.tempdir.name
347 td = cls.tempdir.name
348
348
349 with cls.in_tempdir():
349 with cls.in_tempdir():
350 # Create empty files
350 # Create empty files
351 for fname in cls.filenames:
351 for fname in cls.filenames:
352 open(os.path.join(td, fname), "w", encoding="utf-8").close()
352 open(os.path.join(td, fname), "w", encoding="utf-8").close()
353
353
354 @classmethod
354 @classmethod
355 def tearDownClass(cls):
355 def tearDownClass(cls):
356 cls.tempdir.cleanup()
356 cls.tempdir.cleanup()
357
357
358 @classmethod
358 @classmethod
359 @contextmanager
359 @contextmanager
360 def in_tempdir(cls):
360 def in_tempdir(cls):
361 save = os.getcwd()
361 save = os.getcwd()
362 try:
362 try:
363 os.chdir(cls.tempdir.name)
363 os.chdir(cls.tempdir.name)
364 yield
364 yield
365 finally:
365 finally:
366 os.chdir(save)
366 os.chdir(save)
367
367
368 def check_match(self, patterns, matches):
368 def check_match(self, patterns, matches):
369 with self.in_tempdir():
369 with self.in_tempdir():
370 # glob returns unordered list. that's why sorted is required.
370 # glob returns unordered list. that's why sorted is required.
371 assert sorted(path.shellglob(patterns)) == sorted(matches)
371 assert sorted(path.shellglob(patterns)) == sorted(matches)
372
372
373 def common_cases(self):
373 def common_cases(self):
374 return [
374 return [
375 (['*'], self.filenames),
375 (['*'], self.filenames),
376 (['a*'], self.filenames_start_with_a),
376 (['a*'], self.filenames_start_with_a),
377 (['*c'], ['*c']),
377 (['*c'], ['*c']),
378 (['*', 'a*', '*b', '*c'], self.filenames
378 (['*', 'a*', '*b', '*c'], self.filenames
379 + self.filenames_start_with_a
379 + self.filenames_start_with_a
380 + self.filenames_end_with_b
380 + self.filenames_end_with_b
381 + ['*c']),
381 + ['*c']),
382 (['a[012]'], self.filenames_start_with_a),
382 (['a[012]'], self.filenames_start_with_a),
383 ]
383 ]
384
384
385 @skip_win32
385 @skip_win32
386 def test_match_posix(self):
386 def test_match_posix(self):
387 for (patterns, matches) in self.common_cases() + [
387 for (patterns, matches) in self.common_cases() + [
388 ([r'\*'], ['*']),
388 ([r'\*'], ['*']),
389 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
389 ([r'a\*', 'a*'], ['a*'] + self.filenames_start_with_a),
390 ([r'a\[012]'], ['a[012]']),
390 ([r'a\[012]'], ['a[012]']),
391 ]:
391 ]:
392 self.check_match(patterns, matches)
392 self.check_match(patterns, matches)
393
393
394 @skip_if_not_win32
394 @skip_if_not_win32
395 def test_match_windows(self):
395 def test_match_windows(self):
396 for (patterns, matches) in self.common_cases() + [
396 for (patterns, matches) in self.common_cases() + [
397 # In windows, backslash is interpreted as path
397 # In windows, backslash is interpreted as path
398 # separator. Therefore, you can't escape glob
398 # separator. Therefore, you can't escape glob
399 # using it.
399 # using it.
400 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
400 ([r'a\*', 'a*'], [r'a\*'] + self.filenames_start_with_a),
401 ([r'a\[012]'], [r'a\[012]']),
401 ([r'a\[012]'], [r'a\[012]']),
402 ]:
402 ]:
403 self.check_match(patterns, matches)
403 self.check_match(patterns, matches)
404
404
405
405
406 @pytest.mark.parametrize(
406 @pytest.mark.parametrize(
407 "globstr, unescaped_globstr",
407 "globstr, unescaped_globstr",
408 [
408 [
409 (r"\*\[\!\]\?", "*[!]?"),
409 (r"\*\[\!\]\?", "*[!]?"),
410 (r"\\*", r"\*"),
410 (r"\\*", r"\*"),
411 (r"\\\*", r"\*"),
411 (r"\\\*", r"\*"),
412 (r"\\a", r"\a"),
412 (r"\\a", r"\a"),
413 (r"\a", r"\a"),
413 (r"\a", r"\a"),
414 ],
414 ],
415 )
415 )
416 def test_unescape_glob(globstr, unescaped_globstr):
416 def test_unescape_glob(globstr, unescaped_globstr):
417 assert path.unescape_glob(globstr) == unescaped_globstr
417 assert path.unescape_glob(globstr) == unescaped_globstr
418
418
419
419
420 @onlyif_unicode_paths
420 @onlyif_unicode_paths
421 def test_ensure_dir_exists():
421 def test_ensure_dir_exists():
422 with TemporaryDirectory() as td:
422 with TemporaryDirectory() as td:
423 d = os.path.join(td, '∂ir')
423 d = os.path.join(td, '∂ir')
424 path.ensure_dir_exists(d) # create it
424 path.ensure_dir_exists(d) # create it
425 assert os.path.isdir(d)
425 assert os.path.isdir(d)
426 path.ensure_dir_exists(d) # no-op
426 path.ensure_dir_exists(d) # no-op
427 f = os.path.join(td, "ƒile")
427 f = os.path.join(td, "ƒile")
428 open(f, "w", encoding="utf-8").close() # touch
428 open(f, "w", encoding="utf-8").close() # touch
429 with pytest.raises(IOError):
429 with pytest.raises(IOError):
430 path.ensure_dir_exists(f)
430 path.ensure_dir_exists(f)
431
431
432 class TestLinkOrCopy(unittest.TestCase):
432 class TestLinkOrCopy(unittest.TestCase):
433 def setUp(self):
433 def setUp(self):
434 self.tempdir = TemporaryDirectory()
434 self.tempdir = TemporaryDirectory()
435 self.src = self.dst("src")
435 self.src = self.dst("src")
436 with open(self.src, "w", encoding="utf-8") as f:
436 with open(self.src, "w", encoding="utf-8") as f:
437 f.write("Hello, world!")
437 f.write("Hello, world!")
438
438
439 def tearDown(self):
439 def tearDown(self):
440 self.tempdir.cleanup()
440 self.tempdir.cleanup()
441
441
442 def dst(self, *args):
442 def dst(self, *args):
443 return os.path.join(self.tempdir.name, *args)
443 return os.path.join(self.tempdir.name, *args)
444
444
445 def assert_inode_not_equal(self, a, b):
445 def assert_inode_not_equal(self, a, b):
446 assert (
446 assert (
447 os.stat(a).st_ino != os.stat(b).st_ino
447 os.stat(a).st_ino != os.stat(b).st_ino
448 ), "%r and %r do reference the same indoes" % (a, b)
448 ), "%r and %r do reference the same indoes" % (a, b)
449
449
450 def assert_inode_equal(self, a, b):
450 def assert_inode_equal(self, a, b):
451 assert (
451 assert (
452 os.stat(a).st_ino == os.stat(b).st_ino
452 os.stat(a).st_ino == os.stat(b).st_ino
453 ), "%r and %r do not reference the same indoes" % (a, b)
453 ), "%r and %r do not reference the same indoes" % (a, b)
454
454
455 def assert_content_equal(self, a, b):
455 def assert_content_equal(self, a, b):
456 with open(a, "rb") as a_f:
456 with open(a, "rb") as a_f:
457 with open(b, "rb") as b_f:
457 with open(b, "rb") as b_f:
458 assert a_f.read() == b_f.read()
458 assert a_f.read() == b_f.read()
459
459
460 @skip_win32
460 @skip_win32
461 def test_link_successful(self):
461 def test_link_successful(self):
462 dst = self.dst("target")
462 dst = self.dst("target")
463 path.link_or_copy(self.src, dst)
463 path.link_or_copy(self.src, dst)
464 self.assert_inode_equal(self.src, dst)
464 self.assert_inode_equal(self.src, dst)
465
465
466 @skip_win32
466 @skip_win32
467 def test_link_into_dir(self):
467 def test_link_into_dir(self):
468 dst = self.dst("some_dir")
468 dst = self.dst("some_dir")
469 os.mkdir(dst)
469 os.mkdir(dst)
470 path.link_or_copy(self.src, dst)
470 path.link_or_copy(self.src, dst)
471 expected_dst = self.dst("some_dir", os.path.basename(self.src))
471 expected_dst = self.dst("some_dir", os.path.basename(self.src))
472 self.assert_inode_equal(self.src, expected_dst)
472 self.assert_inode_equal(self.src, expected_dst)
473
473
474 @skip_win32
474 @skip_win32
475 def test_target_exists(self):
475 def test_target_exists(self):
476 dst = self.dst("target")
476 dst = self.dst("target")
477 open(dst, "w", encoding="utf-8").close()
477 open(dst, "w", encoding="utf-8").close()
478 path.link_or_copy(self.src, dst)
478 path.link_or_copy(self.src, dst)
479 self.assert_inode_equal(self.src, dst)
479 self.assert_inode_equal(self.src, dst)
480
480
481 @skip_win32
481 @skip_win32
482 def test_no_link(self):
482 def test_no_link(self):
483 real_link = os.link
483 real_link = os.link
484 try:
484 try:
485 del os.link
485 del os.link
486 dst = self.dst("target")
486 dst = self.dst("target")
487 path.link_or_copy(self.src, dst)
487 path.link_or_copy(self.src, dst)
488 self.assert_content_equal(self.src, dst)
488 self.assert_content_equal(self.src, dst)
489 self.assert_inode_not_equal(self.src, dst)
489 self.assert_inode_not_equal(self.src, dst)
490 finally:
490 finally:
491 os.link = real_link
491 os.link = real_link
492
492
493 @skip_if_not_win32
493 @skip_if_not_win32
494 def test_windows(self):
494 def test_windows(self):
495 dst = self.dst("target")
495 dst = self.dst("target")
496 path.link_or_copy(self.src, dst)
496 path.link_or_copy(self.src, dst)
497 self.assert_content_equal(self.src, dst)
497 self.assert_content_equal(self.src, dst)
498
498
499 def test_link_twice(self):
499 def test_link_twice(self):
500 # Linking the same file twice shouldn't leave duplicates around.
500 # Linking the same file twice shouldn't leave duplicates around.
501 # See https://github.com/ipython/ipython/issues/6450
501 # See https://github.com/ipython/ipython/issues/6450
502 dst = self.dst('target')
502 dst = self.dst('target')
503 path.link_or_copy(self.src, dst)
503 path.link_or_copy(self.src, dst)
504 path.link_or_copy(self.src, dst)
504 path.link_or_copy(self.src, dst)
505 self.assert_inode_equal(self.src, dst)
505 self.assert_inode_equal(self.src, dst)
506 assert sorted(os.listdir(self.tempdir.name)) == ["src", "target"]
506 assert sorted(os.listdir(self.tempdir.name)) == ["src", "target"]
@@ -1,140 +1,141
1 """Tests for tokenutil"""
1 """Tests for tokenutil"""
2 # Copyright (c) IPython Development Team.
2 # Copyright (c) IPython Development Team.
3 # Distributed under the terms of the Modified BSD License.
3 # Distributed under the terms of the Modified BSD License.
4
4
5 import pytest
5 import pytest
6
6
7 from IPython.utils.tokenutil import token_at_cursor, line_at_cursor
7 from IPython.utils.tokenutil import token_at_cursor, line_at_cursor
8
8
9 def expect_token(expected, cell, cursor_pos):
9 def expect_token(expected, cell, cursor_pos):
10 token = token_at_cursor(cell, cursor_pos)
10 token = token_at_cursor(cell, cursor_pos)
11 offset = 0
11 offset = 0
12 for line in cell.splitlines():
12 for line in cell.splitlines():
13 if offset + len(line) >= cursor_pos:
13 if offset + len(line) >= cursor_pos:
14 break
14 break
15 else:
15 else:
16 offset += len(line)+1
16 offset += len(line)+1
17 column = cursor_pos - offset
17 column = cursor_pos - offset
18 line_with_cursor = "%s|%s" % (line[:column], line[column:])
18 line_with_cursor = "%s|%s" % (line[:column], line[column:])
19 assert token == expected, "Expected %r, got %r in: %r (pos %i)" % (
19 assert token == expected, "Expected %r, got %r in: %r (pos %i)" % (
20 expected,
20 expected,
21 token,
21 token,
22 line_with_cursor,
22 line_with_cursor,
23 cursor_pos,
23 cursor_pos,
24 )
24 )
25
25
26
26
27 def test_simple():
27 def test_simple():
28 cell = "foo"
28 cell = "foo"
29 for i in range(len(cell)):
29 for i in range(len(cell)):
30 expect_token("foo", cell, i)
30 expect_token("foo", cell, i)
31
31
32 def test_function():
32 def test_function():
33 cell = "foo(a=5, b='10')"
33 cell = "foo(a=5, b='10')"
34 expected = 'foo'
34 # up to `foo(|a=`
35 # up to `foo(|a=`
35 for i in range(cell.find('a=') + 1):
36 for i in range(cell.find('a=') + 1):
36 expect_token("foo", cell, i)
37 expect_token("foo", cell, i)
37 # find foo after `=`
38 # find foo after `=`
38 for i in [cell.find('=') + 1, cell.rfind('=') + 1]:
39 for i in [cell.find('=') + 1, cell.rfind('=') + 1]:
39 expect_token("foo", cell, i)
40 expect_token("foo", cell, i)
40 # in between `5,|` and `|b=`
41 # in between `5,|` and `|b=`
41 for i in range(cell.find(','), cell.find('b=')):
42 for i in range(cell.find(','), cell.find('b=')):
42 expect_token("foo", cell, i)
43 expect_token("foo", cell, i)
43
44
44 def test_multiline():
45 def test_multiline():
45 cell = '\n'.join([
46 cell = '\n'.join([
46 'a = 5',
47 'a = 5',
47 'b = hello("string", there)'
48 'b = hello("string", there)'
48 ])
49 ])
49 expected = 'hello'
50 expected = 'hello'
50 start = cell.index(expected) + 1
51 start = cell.index(expected) + 1
51 for i in range(start, start + len(expected)):
52 for i in range(start, start + len(expected)):
52 expect_token(expected, cell, i)
53 expect_token(expected, cell, i)
53 expected = 'hello'
54 expected = 'hello'
54 start = cell.index(expected) + 1
55 start = cell.index(expected) + 1
55 for i in range(start, start + len(expected)):
56 for i in range(start, start + len(expected)):
56 expect_token(expected, cell, i)
57 expect_token(expected, cell, i)
57
58
58 def test_multiline_token():
59 def test_multiline_token():
59 cell = '\n'.join([
60 cell = '\n'.join([
60 '"""\n\nxxxxxxxxxx\n\n"""',
61 '"""\n\nxxxxxxxxxx\n\n"""',
61 '5, """',
62 '5, """',
62 'docstring',
63 'docstring',
63 'multiline token',
64 'multiline token',
64 '""", [',
65 '""", [',
65 '2, 3, "complicated"]',
66 '2, 3, "complicated"]',
66 'b = hello("string", there)'
67 'b = hello("string", there)'
67 ])
68 ])
68 expected = 'hello'
69 expected = 'hello'
69 start = cell.index(expected) + 1
70 start = cell.index(expected) + 1
70 for i in range(start, start + len(expected)):
71 for i in range(start, start + len(expected)):
71 expect_token(expected, cell, i)
72 expect_token(expected, cell, i)
72 expected = 'hello'
73 expected = 'hello'
73 start = cell.index(expected) + 1
74 start = cell.index(expected) + 1
74 for i in range(start, start + len(expected)):
75 for i in range(start, start + len(expected)):
75 expect_token(expected, cell, i)
76 expect_token(expected, cell, i)
76
77
77 def test_nested_call():
78 def test_nested_call():
78 cell = "foo(bar(a=5), b=10)"
79 cell = "foo(bar(a=5), b=10)"
79 expected = 'foo'
80 expected = 'foo'
80 start = cell.index('bar') + 1
81 start = cell.index('bar') + 1
81 for i in range(start, start + 3):
82 for i in range(start, start + 3):
82 expect_token(expected, cell, i)
83 expect_token(expected, cell, i)
83 expected = 'bar'
84 expected = 'bar'
84 start = cell.index('a=')
85 start = cell.index('a=')
85 for i in range(start, start + 3):
86 for i in range(start, start + 3):
86 expect_token(expected, cell, i)
87 expect_token(expected, cell, i)
87 expected = 'foo'
88 expected = 'foo'
88 start = cell.index(')') + 1
89 start = cell.index(')') + 1
89 for i in range(start, len(cell)-1):
90 for i in range(start, len(cell)-1):
90 expect_token(expected, cell, i)
91 expect_token(expected, cell, i)
91
92
92 def test_attrs():
93 def test_attrs():
93 cell = "a = obj.attr.subattr"
94 cell = "a = obj.attr.subattr"
94 expected = 'obj'
95 expected = 'obj'
95 idx = cell.find('obj') + 1
96 idx = cell.find('obj') + 1
96 for i in range(idx, idx + 3):
97 for i in range(idx, idx + 3):
97 expect_token(expected, cell, i)
98 expect_token(expected, cell, i)
98 idx = cell.find('.attr') + 2
99 idx = cell.find('.attr') + 2
99 expected = 'obj.attr'
100 expected = 'obj.attr'
100 for i in range(idx, idx + 4):
101 for i in range(idx, idx + 4):
101 expect_token(expected, cell, i)
102 expect_token(expected, cell, i)
102 idx = cell.find('.subattr') + 2
103 idx = cell.find('.subattr') + 2
103 expected = 'obj.attr.subattr'
104 expected = 'obj.attr.subattr'
104 for i in range(idx, len(cell)):
105 for i in range(idx, len(cell)):
105 expect_token(expected, cell, i)
106 expect_token(expected, cell, i)
106
107
107 def test_line_at_cursor():
108 def test_line_at_cursor():
108 cell = ""
109 cell = ""
109 (line, offset) = line_at_cursor(cell, cursor_pos=11)
110 (line, offset) = line_at_cursor(cell, cursor_pos=11)
110 assert line == ""
111 assert line == ""
111 assert offset == 0
112 assert offset == 0
112
113
113 # The position after a newline should be the start of the following line.
114 # The position after a newline should be the start of the following line.
114 cell = "One\nTwo\n"
115 cell = "One\nTwo\n"
115 (line, offset) = line_at_cursor(cell, cursor_pos=4)
116 (line, offset) = line_at_cursor(cell, cursor_pos=4)
116 assert line == "Two\n"
117 assert line == "Two\n"
117 assert offset == 4
118 assert offset == 4
118
119
119 # The end of a cell should be on the last line
120 # The end of a cell should be on the last line
120 cell = "pri\npri"
121 cell = "pri\npri"
121 (line, offset) = line_at_cursor(cell, cursor_pos=7)
122 (line, offset) = line_at_cursor(cell, cursor_pos=7)
122 assert line == "pri"
123 assert line == "pri"
123 assert offset == 4
124 assert offset == 4
124
125
125
126
126 @pytest.mark.parametrize(
127 @pytest.mark.parametrize(
127 "c, token",
128 "c, token",
128 zip(
129 zip(
129 list(range(16, 22)) + list(range(22, 28)),
130 list(range(16, 22)) + list(range(22, 28)),
130 ["int"] * (22 - 16) + ["map"] * (28 - 22),
131 ["int"] * (22 - 16) + ["map"] * (28 - 22),
131 ),
132 ),
132 )
133 )
133 def test_multiline_statement(c, token):
134 def test_multiline_statement(c, token):
134 cell = """a = (1,
135 cell = """a = (1,
135 3)
136 3)
136
137
137 int()
138 int()
138 map()
139 map()
139 """
140 """
140 expect_token(token, cell, c)
141 expect_token(token, cell, c)
@@ -1,166 +1,168
1 """Define text roles for GitHub
1 """Define text roles for GitHub
2
2
3 * ghissue - Issue
3 * ghissue - Issue
4 * ghpull - Pull Request
4 * ghpull - Pull Request
5 * ghuser - User
5 * ghuser - User
6
6
7 Adapted from bitbucket example here:
7 Adapted from bitbucket example here:
8 https://bitbucket.org/birkenfeld/sphinx-contrib/src/tip/bitbucket/sphinxcontrib/bitbucket.py
8 https://bitbucket.org/birkenfeld/sphinx-contrib/src/tip/bitbucket/sphinxcontrib/bitbucket.py
9
9
10 Authors
10 Authors
11 -------
11 -------
12
12
13 * Doug Hellmann
13 * Doug Hellmann
14 * Min RK
14 * Min RK
15 """
15 """
16 #
16 #
17 # Original Copyright (c) 2010 Doug Hellmann. All rights reserved.
17 # Original Copyright (c) 2010 Doug Hellmann. All rights reserved.
18 #
18 #
19
19
20 from docutils import nodes, utils
20 from docutils import nodes, utils
21 from docutils.parsers.rst.roles import set_classes
21 from docutils.parsers.rst.roles import set_classes
22 from sphinx.util.logging import getLogger
22 from sphinx.util.logging import getLogger
23
23
24 info = getLogger(__name__).info
24 info = getLogger(__name__).info
25
25
26 def make_link_node(rawtext, app, type, slug, options):
26 def make_link_node(rawtext, app, type, slug, options):
27 """Create a link to a github resource.
27 """Create a link to a github resource.
28
28
29 :param rawtext: Text being replaced with link node.
29 :param rawtext: Text being replaced with link node.
30 :param app: Sphinx application context
30 :param app: Sphinx application context
31 :param type: Link type (issues, changeset, etc.)
31 :param type: Link type (issues, changeset, etc.)
32 :param slug: ID of the thing to link to
32 :param slug: ID of the thing to link to
33 :param options: Options dictionary passed to role func.
33 :param options: Options dictionary passed to role func.
34 """
34 """
35
35
36 try:
36 try:
37 base = app.config.github_project_url
37 base = app.config.github_project_url
38 if not base:
38 if not base:
39 raise AttributeError
39 raise AttributeError
40 if not base.endswith('/'):
40 if not base.endswith('/'):
41 base += '/'
41 base += '/'
42 except AttributeError as err:
42 except AttributeError as err:
43 raise ValueError('github_project_url configuration value is not set (%s)' % str(err)) from err
43 raise ValueError('github_project_url configuration value is not set (%s)' % str(err)) from err
44
44
45 ref = base + type + '/' + slug + '/'
45 ref = base + type + '/' + slug + '/'
46 set_classes(options)
46 set_classes(options)
47 prefix = "#"
47 prefix = "#"
48 if type == 'pull':
48 if type == 'pull':
49 prefix = "PR " + prefix
49 prefix = "PR " + prefix
50 node = nodes.reference(rawtext, prefix + utils.unescape(slug), refuri=ref,
50 node = nodes.reference(rawtext, prefix + utils.unescape(slug), refuri=ref,
51 **options)
51 **options)
52 return node
52 return node
53
53
54 def ghissue_role(name, rawtext, text, lineno, inliner, options=None, content=None):
54 def ghissue_role(name, rawtext, text, lineno, inliner, options=None, content=None):
55 """Link to a GitHub issue.
55 """Link to a GitHub issue.
56
56
57 Returns 2 part tuple containing list of nodes to insert into the
57 Returns 2 part tuple containing list of nodes to insert into the
58 document and a list of system messages. Both are allowed to be
58 document and a list of system messages. Both are allowed to be
59 empty.
59 empty.
60
60
61 :param name: The role name used in the document.
61 :param name: The role name used in the document.
62 :param rawtext: The entire markup snippet, with role.
62 :param rawtext: The entire markup snippet, with role.
63 :param text: The text marked with the role.
63 :param text: The text marked with the role.
64 :param lineno: The line number where rawtext appears in the input.
64 :param lineno: The line number where rawtext appears in the input.
65 :param inliner: The inliner instance that called us.
65 :param inliner: The inliner instance that called us.
66 :param options: Directive options for customization.
66 :param options: Directive options for customization.
67 :param content: The directive content for customization.
67 :param content: The directive content for customization.
68 """
68 """
69 if options is None:
69 if options is None:
70 options = {}
70 options = {}
71
71
72 try:
72 try:
73 issue_num = int(text)
73 issue_num = int(text)
74 if issue_num <= 0:
74 if issue_num <= 0:
75 raise ValueError
75 raise ValueError
76 except ValueError:
76 except ValueError:
77 msg = inliner.reporter.error(
77 msg = inliner.reporter.error(
78 'GitHub issue number must be a number greater than or equal to 1; '
78 'GitHub issue number must be a number greater than or equal to 1; '
79 '"%s" is invalid.' % text, line=lineno)
79 '"%s" is invalid.' % text, line=lineno)
80 prb = inliner.problematic(rawtext, rawtext, msg)
80 prb = inliner.problematic(rawtext, rawtext, msg)
81 return [prb], [msg]
81 return [prb], [msg]
82 app = inliner.document.settings.env.app
82 #info('issue %r' % text)
83 #info('issue %r' % text)
83 if 'pull' in name.lower():
84 if 'pull' in name.lower():
84 category = 'pull'
85 category = 'pull'
85 elif 'issue' in name.lower():
86 elif 'issue' in name.lower():
86 category = 'issues'
87 category = 'issues'
87 else:
88 else:
88 msg = inliner.reporter.error(
89 msg = inliner.reporter.error(
89 'GitHub roles include "ghpull" and "ghissue", '
90 'GitHub roles include "ghpull" and "ghissue", '
90 '"%s" is invalid.' % name, line=lineno)
91 '"%s" is invalid.' % name, line=lineno)
91 prb = inliner.problematic(rawtext, rawtext, msg)
92 prb = inliner.problematic(rawtext, rawtext, msg)
92 return [prb], [msg]
93 return [prb], [msg]
93 app = inliner.document.settings.env.app
94 node = make_link_node(rawtext, app, category, str(issue_num), options)
94 node = make_link_node(rawtext, app, category, str(issue_num), options)
95 return [node], []
95 return [node], []
96
96
97 def ghuser_role(name, rawtext, text, lineno, inliner, options=None, content=None):
97 def ghuser_role(name, rawtext, text, lineno, inliner, options=None, content=None):
98 """Link to a GitHub user.
98 """Link to a GitHub user.
99
99
100 Returns 2 part tuple containing list of nodes to insert into the
100 Returns 2 part tuple containing list of nodes to insert into the
101 document and a list of system messages. Both are allowed to be
101 document and a list of system messages. Both are allowed to be
102 empty.
102 empty.
103
103
104 :param name: The role name used in the document.
104 :param name: The role name used in the document.
105 :param rawtext: The entire markup snippet, with role.
105 :param rawtext: The entire markup snippet, with role.
106 :param text: The text marked with the role.
106 :param text: The text marked with the role.
107 :param lineno: The line number where rawtext appears in the input.
107 :param lineno: The line number where rawtext appears in the input.
108 :param inliner: The inliner instance that called us.
108 :param inliner: The inliner instance that called us.
109 :param options: Directive options for customization.
109 :param options: Directive options for customization.
110 :param content: The directive content for customization.
110 :param content: The directive content for customization.
111 """
111 """
112 if options is None:
112 if options is None:
113 options = {}
113 options = {}
114
114
115 app = inliner.document.settings.env.app
115 #info('user link %r' % text)
116 #info('user link %r' % text)
116 ref = 'https://www.github.com/' + text
117 ref = 'https://www.github.com/' + text
117 node = nodes.reference(rawtext, text, refuri=ref, **options)
118 node = nodes.reference(rawtext, text, refuri=ref, **options)
118 return [node], []
119 return [node], []
119
120
120 def ghcommit_role(name, rawtext, text, lineno, inliner, options=None, content=None):
121 def ghcommit_role(name, rawtext, text, lineno, inliner, options=None, content=None):
121 """Link to a GitHub commit.
122 """Link to a GitHub commit.
122
123
123 Returns 2 part tuple containing list of nodes to insert into the
124 Returns 2 part tuple containing list of nodes to insert into the
124 document and a list of system messages. Both are allowed to be
125 document and a list of system messages. Both are allowed to be
125 empty.
126 empty.
126
127
127 :param name: The role name used in the document.
128 :param name: The role name used in the document.
128 :param rawtext: The entire markup snippet, with role.
129 :param rawtext: The entire markup snippet, with role.
129 :param text: The text marked with the role.
130 :param text: The text marked with the role.
130 :param lineno: The line number where rawtext appears in the input.
131 :param lineno: The line number where rawtext appears in the input.
131 :param inliner: The inliner instance that called us.
132 :param inliner: The inliner instance that called us.
132 :param options: Directive options for customization.
133 :param options: Directive options for customization.
133 :param content: The directive content for customization.
134 :param content: The directive content for customization.
134 """
135 """
135 if options is None:
136 if options is None:
136 options = {}
137 options = {}
137
138
139 app = inliner.document.settings.env.app
138 #info('user link %r' % text)
140 #info('user link %r' % text)
139 try:
141 try:
140 base = app.config.github_project_url
142 base = app.config.github_project_url
141 if not base:
143 if not base:
142 raise AttributeError
144 raise AttributeError
143 if not base.endswith('/'):
145 if not base.endswith('/'):
144 base += '/'
146 base += '/'
145 except AttributeError as err:
147 except AttributeError as err:
146 raise ValueError('github_project_url configuration value is not set (%s)' % str(err)) from err
148 raise ValueError('github_project_url configuration value is not set (%s)' % str(err)) from err
147
149
148 ref = base + text
150 ref = base + text
149 node = nodes.reference(rawtext, text[:6], refuri=ref, **options)
151 node = nodes.reference(rawtext, text[:6], refuri=ref, **options)
150 return [node], []
152 return [node], []
151
153
152
154
153 def setup(app):
155 def setup(app):
154 """Install the plugin.
156 """Install the plugin.
155
157
156 :param app: Sphinx application context.
158 :param app: Sphinx application context.
157 """
159 """
158 info('Initializing GitHub plugin')
160 info('Initializing GitHub plugin')
159 app.add_role('ghissue', ghissue_role)
161 app.add_role('ghissue', ghissue_role)
160 app.add_role('ghpull', ghissue_role)
162 app.add_role('ghpull', ghissue_role)
161 app.add_role('ghuser', ghuser_role)
163 app.add_role('ghuser', ghuser_role)
162 app.add_role('ghcommit', ghcommit_role)
164 app.add_role('ghcommit', ghcommit_role)
163 app.add_config_value('github_project_url', None, 'env')
165 app.add_config_value('github_project_url', None, 'env')
164
166
165 metadata = {'parallel_read_safe': True, 'parallel_write_safe': True}
167 metadata = {'parallel_read_safe': True, 'parallel_write_safe': True}
166 return metadata
168 return metadata
@@ -1,392 +1,385
1 [build-system]
1 [build-system]
2 requires = ["setuptools>=61.2"]
2 requires = ["setuptools>=61.2"]
3 # We need access to the 'setupbase' module at build time.
3 # We need access to the 'setupbase' module at build time.
4 # Hence we declare a custom build backend.
4 # Hence we declare a custom build backend.
5 build-backend = "_build_meta" # just re-exports setuptools.build_meta definitions
5 build-backend = "_build_meta" # just re-exports setuptools.build_meta definitions
6 backend-path = ["."]
6 backend-path = ["."]
7
7
8 [project]
8 [project]
9 name = "ipython"
9 name = "ipython"
10 description = "IPython: Productive Interactive Computing"
10 description = "IPython: Productive Interactive Computing"
11 keywords = ["Interactive", "Interpreter", "Shell", "Embedding"]
11 keywords = ["Interactive", "Interpreter", "Shell", "Embedding"]
12 classifiers = [
12 classifiers = [
13 "Framework :: IPython",
13 "Framework :: IPython",
14 "Framework :: Jupyter",
14 "Framework :: Jupyter",
15 "Intended Audience :: Developers",
15 "Intended Audience :: Developers",
16 "Intended Audience :: Science/Research",
16 "Intended Audience :: Science/Research",
17 "License :: OSI Approved :: BSD License",
17 "License :: OSI Approved :: BSD License",
18 "Programming Language :: Python",
18 "Programming Language :: Python",
19 "Programming Language :: Python :: 3",
19 "Programming Language :: Python :: 3",
20 "Programming Language :: Python :: 3 :: Only",
20 "Programming Language :: Python :: 3 :: Only",
21 "Topic :: System :: Shells",
21 "Topic :: System :: Shells",
22 ]
22 ]
23 requires-python = ">=3.11"
23 requires-python = ">=3.11"
24 dependencies = [
24 dependencies = [
25 'colorama; sys_platform == "win32"',
25 'colorama; sys_platform == "win32"',
26 "decorator",
26 "decorator",
27 "jedi>=0.16",
27 "jedi>=0.16",
28 "matplotlib-inline",
28 "matplotlib-inline",
29 'pexpect>4.3; sys_platform != "win32" and sys_platform != "emscripten"',
29 'pexpect>4.3; sys_platform != "win32" and sys_platform != "emscripten"',
30 "prompt_toolkit>=3.0.41,<3.1.0",
30 "prompt_toolkit>=3.0.41,<3.1.0",
31 "pygments>=2.4.0",
31 "pygments>=2.4.0",
32 "stack_data",
32 "stack_data",
33 "traitlets>=5.13.0",
33 "traitlets>=5.13.0",
34 "typing_extensions>=4.6; python_version<'3.12'",
34 "typing_extensions>=4.6; python_version<'3.12'",
35 ]
35 ]
36 dynamic = ["authors", "license", "version"]
36 dynamic = ["authors", "license", "version"]
37
37
38 [project.entry-points."pygments.lexers"]
38 [project.entry-points."pygments.lexers"]
39 ipythonconsole = "IPython.lib.lexers:IPythonConsoleLexer"
39 ipythonconsole = "IPython.lib.lexers:IPythonConsoleLexer"
40 ipython = "IPython.lib.lexers:IPythonLexer"
40 ipython = "IPython.lib.lexers:IPythonLexer"
41 ipython3 = "IPython.lib.lexers:IPython3Lexer"
41 ipython3 = "IPython.lib.lexers:IPython3Lexer"
42
42
43 [project.scripts]
43 [project.scripts]
44 ipython = "IPython:start_ipython"
44 ipython = "IPython:start_ipython"
45 ipython3 = "IPython:start_ipython"
45 ipython3 = "IPython:start_ipython"
46
46
47 [project.readme]
47 [project.readme]
48 file = "long_description.rst"
48 file = "long_description.rst"
49 content-type = "text/x-rst"
49 content-type = "text/x-rst"
50
50
51 [project.urls]
51 [project.urls]
52 Homepage = "https://ipython.org"
52 Homepage = "https://ipython.org"
53 Documentation = "https://ipython.readthedocs.io/"
53 Documentation = "https://ipython.readthedocs.io/"
54 Funding = "https://numfocus.org/"
54 Funding = "https://numfocus.org/"
55 Source = "https://github.com/ipython/ipython"
55 Source = "https://github.com/ipython/ipython"
56 Tracker = "https://github.com/ipython/ipython/issues"
56 Tracker = "https://github.com/ipython/ipython/issues"
57
57
58 [project.optional-dependencies]
58 [project.optional-dependencies]
59 black = ["black"]
59 black = [
60 "black",
61 ]
60 doc = [
62 doc = [
61 "docrepr",
63 "docrepr",
62 "exceptiongroup",
64 "exceptiongroup",
63 "intersphinx_registry",
65 "intersphinx_registry",
64 "ipykernel",
66 "ipykernel",
65 "ipython[test]",
67 "ipython[test]",
66 "matplotlib",
68 "matplotlib",
67 "setuptools>=18.5",
69 "setuptools>=18.5",
68 "sphinx-rtd-theme",
70 "sphinx-rtd-theme",
69 "sphinx>=1.3",
71 "sphinx>=1.3",
70 "sphinxcontrib-jquery",
72 "sphinxcontrib-jquery",
71 ]
73 ]
72 kernel = ["ipykernel"]
74 kernel = [
73 nbconvert = ["nbconvert"]
75 "ipykernel",
74 nbformat = ["nbformat"]
76 ]
75 notebook = ["ipywidgets", "notebook"]
77 nbconvert = [
76 parallel = ["ipyparallel"]
78 "nbconvert",
77 qtconsole = ["qtconsole"]
79 ]
80 nbformat = [
81 "nbformat",
82 ]
83 notebook = [
84 "ipywidgets",
85 "notebook",
86 ]
87 parallel = [
88 "ipyparallel",
89 ]
90 qtconsole = [
91 "qtconsole",
92 ]
78 terminal = []
93 terminal = []
79 test = ["pytest", "pytest-asyncio<0.22", "testpath", "pickleshare", "packaging"]
94 test = [
95 "pytest",
96 "pytest-asyncio<0.22",
97 "testpath",
98 "pickleshare",
99 "packaging",
100 ]
80 test_extra = [
101 test_extra = [
81 "ipython[test]",
102 "ipython[test]",
82 "curio",
103 "curio",
83 "matplotlib!=3.2.0",
104 "matplotlib!=3.2.0",
84 "nbformat",
105 "nbformat",
85 "numpy>=1.23",
106 "numpy>=1.23",
86 "pandas",
107 "pandas",
87 "trio",
108 "trio",
88 ]
109 ]
89 matplotlib = ["matplotlib"]
110 matplotlib = [
111 "matplotlib"
112 ]
90 all = [
113 all = [
91 "ipython[black,doc,kernel,nbconvert,nbformat,notebook,parallel,qtconsole,matplotlib]",
114 "ipython[black,doc,kernel,nbconvert,nbformat,notebook,parallel,qtconsole,matplotlib]",
92 "ipython[test,test_extra]",
115 "ipython[test,test_extra]",
93 ]
116 ]
94
117
95 [tool.mypy]
118 [tool.mypy]
96 python_version = "3.10"
119 python_version = "3.10"
97 ignore_missing_imports = true
120 ignore_missing_imports = true
98 follow_imports = 'silent'
121 follow_imports = 'silent'
99 exclude = [
122 exclude = [
100 'test_\.+\.py',
123 'test_\.+\.py',
101 'IPython.utils.tests.test_wildcard',
124 'IPython.utils.tests.test_wildcard',
102 'testing',
125 'testing',
103 'tests',
126 'tests',
104 'PyColorize.py',
127 'PyColorize.py',
105 '_process_win32_controller.py',
128 '_process_win32_controller.py',
106 'IPython/core/application.py',
129 'IPython/core/application.py',
107 'IPython/core/profileapp.py',
130 'IPython/core/profileapp.py',
108 'IPython/lib/deepreload.py',
131 'IPython/lib/deepreload.py',
109 'IPython/sphinxext/ipython_directive.py',
132 'IPython/sphinxext/ipython_directive.py',
110 'IPython/terminal/ipapp.py',
133 'IPython/terminal/ipapp.py',
111 'IPython/utils/path.py',
134 'IPython/utils/path.py',
112 ]
135 ]
113 # check_untyped_defs = true
136 # check_untyped_defs = true
114 # disallow_untyped_calls = true
137 # disallow_untyped_calls = true
115 # disallow_untyped_decorators = true
138 # disallow_untyped_decorators = true
116 # ignore_errors = false
139 # ignore_errors = false
117 # ignore_missing_imports = false
140 # ignore_missing_imports = false
118 disallow_incomplete_defs = true
141 disallow_incomplete_defs = true
119 disallow_untyped_defs = true
142 disallow_untyped_defs = true
120 warn_redundant_casts = true
143 warn_redundant_casts = true
121
144
122 [[tool.mypy.overrides]]
145 [[tool.mypy.overrides]]
123 module = ["IPython.core.crashhandler"]
146 module = [
147 "IPython.core.crashhandler",
148 ]
124 check_untyped_defs = true
149 check_untyped_defs = true
125 disallow_incomplete_defs = true
150 disallow_incomplete_defs = true
126 disallow_untyped_calls = true
151 disallow_untyped_calls = true
127 disallow_untyped_decorators = true
152 disallow_untyped_decorators = true
128 disallow_untyped_defs = true
153 disallow_untyped_defs = true
129 ignore_errors = false
154 ignore_errors = false
130 ignore_missing_imports = false
155 ignore_missing_imports = false
131
156
132 [[tool.mypy.overrides]]
157 [[tool.mypy.overrides]]
133 module = ["IPython.utils.text"]
158 module = [
159 "IPython.utils.text",
160 ]
134 disallow_untyped_defs = true
161 disallow_untyped_defs = true
135 check_untyped_defs = false
162 check_untyped_defs = false
136 disallow_untyped_decorators = true
163 disallow_untyped_decorators = true
137
164
138 [[tool.mypy.overrides]]
165 [[tool.mypy.overrides]]
139 module = []
166 module = [
167 ]
140 disallow_untyped_defs = false
168 disallow_untyped_defs = false
141 ignore_errors = true
169 ignore_errors = true
142 ignore_missing_imports = true
170 ignore_missing_imports = true
143 disallow_untyped_calls = false
171 disallow_untyped_calls = false
144 disallow_incomplete_defs = false
172 disallow_incomplete_defs = false
145 check_untyped_defs = false
173 check_untyped_defs = false
146 disallow_untyped_decorators = false
174 disallow_untyped_decorators = false
147
175
148
176
149 # gloabl ignore error
177 # gloabl ignore error
150 [[tool.mypy.overrides]]
178 [[tool.mypy.overrides]]
151 module = [
179 module = [
152 "IPython",
180 "IPython",
153 "IPython.conftest",
181 "IPython.conftest",
154 "IPython.core.alias",
182 "IPython.core.alias",
155 "IPython.core.async_helpers",
183 "IPython.core.async_helpers",
156 "IPython.core.autocall",
184 "IPython.core.autocall",
157 "IPython.core.builtin_trap",
185 "IPython.core.builtin_trap",
158 "IPython.core.compilerop",
186 "IPython.core.compilerop",
159 "IPython.core.completer",
187 "IPython.core.completer",
160 "IPython.core.completerlib",
188 "IPython.core.completerlib",
161 "IPython.core.debugger",
189 "IPython.core.debugger",
162 "IPython.core.display",
190 "IPython.core.display",
163 "IPython.core.display_functions",
191 "IPython.core.display_functions",
164 "IPython.core.display_trap",
192 "IPython.core.display_trap",
165 "IPython.core.displayhook",
193 "IPython.core.displayhook",
166 "IPython.core.displaypub",
194 "IPython.core.displaypub",
167 "IPython.core.events",
195 "IPython.core.events",
168 "IPython.core.excolors",
196 "IPython.core.excolors",
169 "IPython.core.extensions",
197 "IPython.core.extensions",
170 "IPython.core.formatters",
198 "IPython.core.formatters",
171 "IPython.core.getipython",
199 "IPython.core.getipython",
172 "IPython.core.guarded_eval",
200 "IPython.core.guarded_eval",
173 "IPython.core.historyapp",
201 "IPython.core.historyapp",
174 "IPython.core.hooks",
202 "IPython.core.hooks",
175 "IPython.core.inputtransformer",
203 "IPython.core.inputtransformer",
176 "IPython.core.inputtransformer2",
204 "IPython.core.inputtransformer2",
177 "IPython.core.interactiveshell",
205 "IPython.core.interactiveshell",
178 "IPython.core.logger",
206 "IPython.core.logger",
179 "IPython.core.macro",
207 "IPython.core.macro",
180 "IPython.core.magic",
208 "IPython.core.magic",
181 "IPython.core.magic_arguments",
209 "IPython.core.magic_arguments",
182 "IPython.core.magics.ast_mod",
210 "IPython.core.magics.ast_mod",
183 "IPython.core.magics.auto",
211 "IPython.core.magics.auto",
184 "IPython.core.magics.basic",
212 "IPython.core.magics.basic",
185 "IPython.core.magics.code",
213 "IPython.core.magics.code",
186 "IPython.core.magics.config",
214 "IPython.core.magics.config",
187 "IPython.core.magics.display",
215 "IPython.core.magics.display",
188 "IPython.core.magics.execution",
216 "IPython.core.magics.execution",
189 "IPython.core.magics.extension",
217 "IPython.core.magics.extension",
190 "IPython.core.magics.history",
218 "IPython.core.magics.history",
191 "IPython.core.magics.logging",
219 "IPython.core.magics.logging",
192 "IPython.core.magics.namespace",
220 "IPython.core.magics.namespace",
193 "IPython.core.magics.osm",
221 "IPython.core.magics.osm",
194 "IPython.core.magics.packaging",
222 "IPython.core.magics.packaging",
195 "IPython.core.magics.pylab",
223 "IPython.core.magics.pylab",
196 "IPython.core.magics.script",
224 "IPython.core.magics.script",
197 "IPython.core.oinspect",
225 "IPython.core.oinspect",
198 "IPython.core.page",
226 "IPython.core.page",
199 "IPython.core.payload",
227 "IPython.core.payload",
200 "IPython.core.payloadpage",
228 "IPython.core.payloadpage",
201 "IPython.core.prefilter",
229 "IPython.core.prefilter",
202 "IPython.core.profiledir",
230 "IPython.core.profiledir",
203 "IPython.core.prompts",
231 "IPython.core.prompts",
204 "IPython.core.pylabtools",
232 "IPython.core.pylabtools",
205 "IPython.core.shellapp",
233 "IPython.core.shellapp",
206 "IPython.core.splitinput",
234 "IPython.core.splitinput",
207 "IPython.core.ultratb",
235 "IPython.core.ultratb",
208 "IPython.extensions.autoreload",
236 "IPython.extensions.autoreload",
209 "IPython.extensions.storemagic",
237 "IPython.extensions.storemagic",
210 "IPython.external.qt_for_kernel",
238 "IPython.external.qt_for_kernel",
211 "IPython.external.qt_loaders",
239 "IPython.external.qt_loaders",
212 "IPython.lib.backgroundjobs",
240 "IPython.lib.backgroundjobs",
213 "IPython.lib.clipboard",
241 "IPython.lib.clipboard",
214 "IPython.lib.demo",
242 "IPython.lib.demo",
215 "IPython.lib.display",
243 "IPython.lib.display",
216 "IPython.lib.editorhooks",
244 "IPython.lib.editorhooks",
217 "IPython.lib.guisupport",
245 "IPython.lib.guisupport",
218 "IPython.lib.latextools",
246 "IPython.lib.latextools",
219 "IPython.lib.lexers",
247 "IPython.lib.lexers",
220 "IPython.lib.pretty",
248 "IPython.lib.pretty",
221 "IPython.paths",
249 "IPython.paths",
222 "IPython.sphinxext.ipython_console_highlighting",
250 "IPython.sphinxext.ipython_console_highlighting",
223 "IPython.terminal.debugger",
251 "IPython.terminal.debugger",
224 "IPython.terminal.embed",
252 "IPython.terminal.embed",
225 "IPython.terminal.interactiveshell",
253 "IPython.terminal.interactiveshell",
226 "IPython.terminal.magics",
254 "IPython.terminal.magics",
227 "IPython.terminal.prompts",
255 "IPython.terminal.prompts",
228 "IPython.terminal.pt_inputhooks",
256 "IPython.terminal.pt_inputhooks",
229 "IPython.terminal.pt_inputhooks.asyncio",
257 "IPython.terminal.pt_inputhooks.asyncio",
230 "IPython.terminal.pt_inputhooks.glut",
258 "IPython.terminal.pt_inputhooks.glut",
231 "IPython.terminal.pt_inputhooks.gtk",
259 "IPython.terminal.pt_inputhooks.gtk",
232 "IPython.terminal.pt_inputhooks.gtk3",
260 "IPython.terminal.pt_inputhooks.gtk3",
233 "IPython.terminal.pt_inputhooks.gtk4",
261 "IPython.terminal.pt_inputhooks.gtk4",
234 "IPython.terminal.pt_inputhooks.osx",
262 "IPython.terminal.pt_inputhooks.osx",
235 "IPython.terminal.pt_inputhooks.pyglet",
263 "IPython.terminal.pt_inputhooks.pyglet",
236 "IPython.terminal.pt_inputhooks.qt",
264 "IPython.terminal.pt_inputhooks.qt",
237 "IPython.terminal.pt_inputhooks.tk",
265 "IPython.terminal.pt_inputhooks.tk",
238 "IPython.terminal.pt_inputhooks.wx",
266 "IPython.terminal.pt_inputhooks.wx",
239 "IPython.terminal.ptutils",
267 "IPython.terminal.ptutils",
240 "IPython.terminal.shortcuts",
268 "IPython.terminal.shortcuts",
241 "IPython.terminal.shortcuts.auto_match",
269 "IPython.terminal.shortcuts.auto_match",
242 "IPython.terminal.shortcuts.auto_suggest",
270 "IPython.terminal.shortcuts.auto_suggest",
243 "IPython.terminal.shortcuts.filters",
271 "IPython.terminal.shortcuts.filters",
244 "IPython.utils._process_cli",
272 "IPython.utils._process_cli",
245 "IPython.utils._process_common",
273 "IPython.utils._process_common",
246 "IPython.utils._process_emscripten",
274 "IPython.utils._process_emscripten",
247 "IPython.utils._process_posix",
275 "IPython.utils._process_posix",
248 "IPython.utils.capture",
276 "IPython.utils.capture",
249 "IPython.utils.coloransi",
277 "IPython.utils.coloransi",
250 "IPython.utils.contexts",
278 "IPython.utils.contexts",
251 "IPython.utils.data",
279 "IPython.utils.data",
252 "IPython.utils.decorators",
280 "IPython.utils.decorators",
253 "IPython.utils.dir2",
281 "IPython.utils.dir2",
254 "IPython.utils.encoding",
282 "IPython.utils.encoding",
255 "IPython.utils.frame",
283 "IPython.utils.frame",
256 "IPython.utils.generics",
284 "IPython.utils.generics",
257 "IPython.utils.importstring",
285 "IPython.utils.importstring",
258 "IPython.utils.io",
286 "IPython.utils.io",
259 "IPython.utils.ipstruct",
287 "IPython.utils.ipstruct",
260 "IPython.utils.module_paths",
288 "IPython.utils.module_paths",
261 "IPython.utils.openpy",
289 "IPython.utils.openpy",
262 "IPython.utils.process",
290 "IPython.utils.process",
263 "IPython.utils.py3compat",
291 "IPython.utils.py3compat",
264 "IPython.utils.sentinel",
292 "IPython.utils.sentinel",
265 "IPython.utils.strdispatch",
293 "IPython.utils.strdispatch",
266 "IPython.utils.sysinfo",
294 "IPython.utils.sysinfo",
267 "IPython.utils.syspathcontext",
295 "IPython.utils.syspathcontext",
268 "IPython.utils.tempdir",
296 "IPython.utils.tempdir",
269 "IPython.utils.terminal",
297 "IPython.utils.terminal",
270 "IPython.utils.timing",
298 "IPython.utils.timing",
271 "IPython.utils.tokenutil",
299 "IPython.utils.tokenutil",
272 "IPython.utils.version",
300 "IPython.utils.version",
273 "IPython.utils.wildcard",
301 "IPython.utils.wildcard",
274
302
275 ]
303 ]
276 disallow_untyped_defs = false
304 disallow_untyped_defs = false
277 ignore_errors = true
305 ignore_errors = true
278 ignore_missing_imports = true
306 ignore_missing_imports = true
279 disallow_untyped_calls = false
307 disallow_untyped_calls = false
280 disallow_incomplete_defs = false
308 disallow_incomplete_defs = false
281 check_untyped_defs = false
309 check_untyped_defs = false
282 disallow_untyped_decorators = false
310 disallow_untyped_decorators = false
283
311
284 [tool.pytest.ini_options]
312 [tool.pytest.ini_options]
285 addopts = [
313 addopts = [
286 "--durations=10",
314 "--durations=10",
287 "-pIPython.testing.plugin.pytest_ipdoctest",
315 "-pIPython.testing.plugin.pytest_ipdoctest",
288 "--ipdoctest-modules",
316 "--ipdoctest-modules",
289 "--ignore=docs",
317 "--ignore=docs",
290 "--ignore=examples",
318 "--ignore=examples",
291 "--ignore=htmlcov",
319 "--ignore=htmlcov",
292 "--ignore=ipython_kernel",
320 "--ignore=ipython_kernel",
293 "--ignore=ipython_parallel",
321 "--ignore=ipython_parallel",
294 "--ignore=results",
322 "--ignore=results",
295 "--ignore=tmp",
323 "--ignore=tmp",
296 "--ignore=tools",
324 "--ignore=tools",
297 "--ignore=traitlets",
325 "--ignore=traitlets",
298 "--ignore=IPython/core/tests/daft_extension",
326 "--ignore=IPython/core/tests/daft_extension",
299 "--ignore=IPython/sphinxext",
327 "--ignore=IPython/sphinxext",
300 "--ignore=IPython/terminal/pt_inputhooks",
328 "--ignore=IPython/terminal/pt_inputhooks",
301 "--ignore=IPython/__main__.py",
329 "--ignore=IPython/__main__.py",
302 "--ignore=IPython/external/qt_for_kernel.py",
330 "--ignore=IPython/external/qt_for_kernel.py",
303 "--ignore=IPython/html/widgets/widget_link.py",
331 "--ignore=IPython/html/widgets/widget_link.py",
304 "--ignore=IPython/html/widgets/widget_output.py",
332 "--ignore=IPython/html/widgets/widget_output.py",
305 "--ignore=IPython/terminal/console.py",
333 "--ignore=IPython/terminal/console.py",
306 "--ignore=IPython/utils/_process_cli.py",
334 "--ignore=IPython/utils/_process_cli.py",
307 "--ignore=IPython/utils/_process_posix.py",
335 "--ignore=IPython/utils/_process_posix.py",
308 "--ignore=IPython/utils/_process_win32_controller.py",
336 "--ignore=IPython/utils/_process_win32_controller.py",
309 "--ignore=IPython/utils/daemonize.py",
337 "--ignore=IPython/utils/daemonize.py",
310 "--ignore=IPython/utils/eventful.py",
338 "--ignore=IPython/utils/eventful.py",
311 "--ignore=IPython/kernel",
339 "--ignore=IPython/kernel",
312 "--ignore=IPython/consoleapp.py",
340 "--ignore=IPython/consoleapp.py",
313 "--ignore=IPython/lib/kernel.py",
341 "--ignore=IPython/lib/kernel.py",
314 "--ignore=IPython/utils/jsonutil.py",
342 "--ignore=IPython/utils/jsonutil.py",
315 "--ignore=IPython/utils/localinterfaces.py",
343 "--ignore=IPython/utils/localinterfaces.py",
316 "--ignore=IPython/utils/log.py",
344 "--ignore=IPython/utils/log.py",
317 "--ignore=IPython/utils/signatures.py",
345 "--ignore=IPython/utils/signatures.py",
318 "--ignore=IPython/utils/version.py",
346 "--ignore=IPython/utils/version.py"
347 ]
348 doctest_optionflags = [
349 "NORMALIZE_WHITESPACE",
350 "ELLIPSIS"
351 ]
352 ipdoctest_optionflags = [
353 "NORMALIZE_WHITESPACE",
354 "ELLIPSIS"
319 ]
355 ]
320 doctest_optionflags = ["NORMALIZE_WHITESPACE", "ELLIPSIS"]
321 ipdoctest_optionflags = ["NORMALIZE_WHITESPACE", "ELLIPSIS"]
322 asyncio_mode = "strict"
356 asyncio_mode = "strict"
323
357
324 [tool.pyright]
358 [tool.pyright]
325 pythonPlatform = "All"
359 pythonPlatform="All"
326
360
327 [tool.setuptools]
361 [tool.setuptools]
328 zip-safe = false
362 zip-safe = false
329 platforms = ["Linux", "Mac OSX", "Windows"]
363 platforms = ["Linux", "Mac OSX", "Windows"]
330 license-files = ["LICENSE"]
364 license-files = ["LICENSE"]
331 include-package-data = false
365 include-package-data = false
332
366
333 [tool.setuptools.packages.find]
367 [tool.setuptools.packages.find]
334 exclude = ["setupext"]
368 exclude = ["setupext"]
335 namespaces = false
369 namespaces = false
336
370
337 [tool.setuptools.package-data]
371 [tool.setuptools.package-data]
338 "IPython" = ["py.typed"]
372 "IPython" = ["py.typed"]
339 "IPython.core" = ["profile/README*"]
373 "IPython.core" = ["profile/README*"]
340 "IPython.core.tests" = ["*.png", "*.jpg", "daft_extension/*.py"]
374 "IPython.core.tests" = ["*.png", "*.jpg", "daft_extension/*.py"]
341 "IPython.lib.tests" = ["*.wav"]
375 "IPython.lib.tests" = ["*.wav"]
342 "IPython.testing.plugin" = ["*.txt"]
376 "IPython.testing.plugin" = ["*.txt"]
343
377
344 [tool.setuptools.dynamic]
378 [tool.setuptools.dynamic]
345 version = { attr = "IPython.core.release.__version__" }
379 version = {attr = "IPython.core.release.__version__"}
346
380
347 [tool.coverage.run]
381 [tool.coverage.run]
348 omit = [
382 omit = [
349 # omit everything in /tmp as we run tempfile
383 # omit everything in /tmp as we run tempfile
350 "/tmp/*",
384 "/tmp/*",
351 ]
385 ]
352
353 [tool.ruff.lint]
354 extend-select = [
355 # "B", # flake8-bugbear
356 # "I", # isort
357 # that will be a problem for pytest fixture unless you swap with the usefixture decorator https://docs.pytest.org/en/7.1.x/how-to/fixtures.html#use-fixtures-in-classes-and-modules-with-usefixtures
358 # "ARG", # flake8-unused-arguments
359 # "C4", # flake8-comprehensions
360 # "EM", # flake8-errmsg
361 # "ICN", # flake8-import-conventions
362 # "G", # flake8-logging-format
363 # "PGH", # pygrep-hooks
364 # "PIE", # flake8-pie
365 # "PL", # pylint
366 # "PTH", # flake8-use-pathlib
367 # "PT", # flake8-pytest-style
368 # "RET", # flake8-return
369 # "RUF", # Ruff-specific
370 # "SIM", # flake8-simplify
371 # "T20", # flake8-print
372 # "UP", # pyupgrade
373 # "YTT", # flake8-2020
374 # "EXE", # flake8-executable
375 # "PYI", # flake8-pyi
376 # "S", # flake8-bandit
377 ]
378 ignore = [
379 # "E501", # E501 Line too long (158 > 100 characters)
380 # "SIM105", # SIM105 Use `contextlib.suppress(...)`
381 # "PLR", # Design related pylint codes
382 # "S101", # Use of `assert` detected
383 ]
384 unfixable = [
385 # Don't touch print statements
386 "T201",
387 # Don't touch noqa lines
388 "RUF100",
389 ]
390
391 [tool.ruff]
392 extend-exclude = ["tests"]
General Comments 0
You need to be logged in to leave comments. Login now