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

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

@@ -1,490 +1,490 b''
1 # 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 glob
17 import glob
18 import logging
18 import logging
19 import os
19 import os
20 import shutil
20 import shutil
21 import sys
21 import sys
22
22
23 from pathlib import Path
23 from pathlib import Path
24
24
25 from traitlets.config.application import Application, catch_config_error
25 from traitlets.config.application import Application, catch_config_error
26 from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader
26 from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader
27 from IPython.core import release, crashhandler
27 from IPython.core import release, crashhandler
28 from IPython.core.profiledir import ProfileDir, ProfileDirError
28 from IPython.core.profiledir import ProfileDir, ProfileDirError
29 from IPython.paths import get_ipython_dir, get_ipython_package_dir
29 from IPython.paths import get_ipython_dir, get_ipython_package_dir
30 from IPython.utils.path import ensure_dir_exists
30 from IPython.utils.path import ensure_dir_exists
31 from traitlets import (
31 from traitlets import (
32 List, Unicode, Type, Bool, Set, Instance, Undefined,
32 List, Unicode, Type, Bool, Set, Instance, Undefined,
33 default, observe,
33 default, observe,
34 )
34 )
35
35
36 if os.name == "nt":
36 if os.name == "nt":
37 programdata = os.environ.get("PROGRAMDATA", None)
37 programdata = os.environ.get("PROGRAMDATA", None)
38 if programdata is not None:
38 if programdata is not None:
39 SYSTEM_CONFIG_DIRS = [str(Path(programdata) / "ipython")]
39 SYSTEM_CONFIG_DIRS = [str(Path(programdata) / "ipython")]
40 else: # PROGRAMDATA is not defined by default on XP.
40 else: # PROGRAMDATA is not defined by default on XP.
41 SYSTEM_CONFIG_DIRS = []
41 SYSTEM_CONFIG_DIRS = []
42 else:
42 else:
43 SYSTEM_CONFIG_DIRS = [
43 SYSTEM_CONFIG_DIRS = [
44 "/usr/local/etc/ipython",
44 "/usr/local/etc/ipython",
45 "/etc/ipython",
45 "/etc/ipython",
46 ]
46 ]
47
47
48
48
49 ENV_CONFIG_DIRS = []
49 ENV_CONFIG_DIRS = []
50 _env_config_dir = os.path.join(sys.prefix, 'etc', 'ipython')
50 _env_config_dir = os.path.join(sys.prefix, 'etc', 'ipython')
51 if _env_config_dir not in SYSTEM_CONFIG_DIRS:
51 if _env_config_dir not in SYSTEM_CONFIG_DIRS:
52 # only add ENV_CONFIG if sys.prefix is not already included
52 # only add ENV_CONFIG if sys.prefix is not already included
53 ENV_CONFIG_DIRS.append(_env_config_dir)
53 ENV_CONFIG_DIRS.append(_env_config_dir)
54
54
55
55
56 _envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS')
56 _envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS')
57 if _envvar in {None, ''}:
57 if _envvar in {None, ''}:
58 IPYTHON_SUPPRESS_CONFIG_ERRORS = None
58 IPYTHON_SUPPRESS_CONFIG_ERRORS = None
59 else:
59 else:
60 if _envvar.lower() in {'1','true'}:
60 if _envvar.lower() in {'1','true'}:
61 IPYTHON_SUPPRESS_CONFIG_ERRORS = True
61 IPYTHON_SUPPRESS_CONFIG_ERRORS = True
62 elif _envvar.lower() in {'0','false'} :
62 elif _envvar.lower() in {'0','false'} :
63 IPYTHON_SUPPRESS_CONFIG_ERRORS = False
63 IPYTHON_SUPPRESS_CONFIG_ERRORS = False
64 else:
64 else:
65 sys.exit("Unsupported value for environment variable: 'IPYTHON_SUPPRESS_CONFIG_ERRORS' is set to '%s' which is none of {'0', '1', 'false', 'true', ''}."% _envvar )
65 sys.exit("Unsupported value for environment variable: 'IPYTHON_SUPPRESS_CONFIG_ERRORS' is set to '%s' which is none of {'0', '1', 'false', 'true', ''}."% _envvar )
66
66
67 # aliases and flags
67 # aliases and flags
68
68
69 base_aliases = {}
69 base_aliases = {}
70 if isinstance(Application.aliases, dict):
70 if isinstance(Application.aliases, dict):
71 # traitlets 5
71 # traitlets 5
72 base_aliases.update(Application.aliases)
72 base_aliases.update(Application.aliases)
73 base_aliases.update(
73 base_aliases.update(
74 {
74 {
75 "profile-dir": "ProfileDir.location",
75 "profile-dir": "ProfileDir.location",
76 "profile": "BaseIPythonApplication.profile",
76 "profile": "BaseIPythonApplication.profile",
77 "ipython-dir": "BaseIPythonApplication.ipython_dir",
77 "ipython-dir": "BaseIPythonApplication.ipython_dir",
78 "log-level": "Application.log_level",
78 "log-level": "Application.log_level",
79 "config": "BaseIPythonApplication.extra_config_file",
79 "config": "BaseIPythonApplication.extra_config_file",
80 }
80 }
81 )
81 )
82
82
83 base_flags = dict()
83 base_flags = dict()
84 if isinstance(Application.flags, dict):
84 if isinstance(Application.flags, dict):
85 # traitlets 5
85 # traitlets 5
86 base_flags.update(Application.flags)
86 base_flags.update(Application.flags)
87 base_flags.update(
87 base_flags.update(
88 dict(
88 dict(
89 debug=(
89 debug=(
90 {"Application": {"log_level": logging.DEBUG}},
90 {"Application": {"log_level": logging.DEBUG}},
91 "set log level to logging.DEBUG (maximize logging output)",
91 "set log level to logging.DEBUG (maximize logging output)",
92 ),
92 ),
93 quiet=(
93 quiet=(
94 {"Application": {"log_level": logging.CRITICAL}},
94 {"Application": {"log_level": logging.CRITICAL}},
95 "set log level to logging.CRITICAL (minimize logging output)",
95 "set log level to logging.CRITICAL (minimize logging output)",
96 ),
96 ),
97 init=(
97 init=(
98 {
98 {
99 "BaseIPythonApplication": {
99 "BaseIPythonApplication": {
100 "copy_config_files": True,
100 "copy_config_files": True,
101 "auto_create": True,
101 "auto_create": True,
102 }
102 }
103 },
103 },
104 """Initialize profile with default config files. This is equivalent
104 """Initialize profile with default config files. This is equivalent
105 to running `ipython profile create <profile>` prior to startup.
105 to running `ipython profile create <profile>` prior to startup.
106 """,
106 """,
107 ),
107 ),
108 )
108 )
109 )
109 )
110
110
111
111
112 class ProfileAwareConfigLoader(PyFileConfigLoader):
112 class ProfileAwareConfigLoader(PyFileConfigLoader):
113 """A Python file config loader that is aware of IPython profiles."""
113 """A Python file config loader that is aware of IPython profiles."""
114 def load_subconfig(self, fname, path=None, profile=None):
114 def load_subconfig(self, fname, path=None, profile=None):
115 if profile is not None:
115 if profile is not None:
116 try:
116 try:
117 profile_dir = ProfileDir.find_profile_dir_by_name(
117 profile_dir = ProfileDir.find_profile_dir_by_name(
118 get_ipython_dir(),
118 get_ipython_dir(),
119 profile,
119 profile,
120 )
120 )
121 except ProfileDirError:
121 except ProfileDirError:
122 return
122 return
123 path = profile_dir.location
123 path = profile_dir.location
124 return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path)
124 return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path)
125
125
126 class BaseIPythonApplication(Application):
126 class BaseIPythonApplication(Application):
127
127
128 name = u'ipython'
128 name = u'ipython'
129 description = Unicode(u'IPython: an enhanced interactive Python shell.')
129 description = Unicode(u'IPython: an enhanced interactive Python shell.')
130 version = Unicode(release.version)
130 version = Unicode(release.version)
131
131
132 aliases = base_aliases
132 aliases = base_aliases
133 flags = base_flags
133 flags = base_flags
134 classes = List([ProfileDir])
134 classes = List([ProfileDir])
135
135
136 # enable `load_subconfig('cfg.py', profile='name')`
136 # enable `load_subconfig('cfg.py', profile='name')`
137 python_config_loader_class = ProfileAwareConfigLoader
137 python_config_loader_class = ProfileAwareConfigLoader
138
138
139 # Track whether the config_file has changed,
139 # Track whether the config_file has changed,
140 # because some logic happens only if we aren't using the default.
140 # because some logic happens only if we aren't using the default.
141 config_file_specified = Set()
141 config_file_specified = Set()
142
142
143 config_file_name = Unicode()
143 config_file_name = Unicode()
144 @default('config_file_name')
144 @default('config_file_name')
145 def _config_file_name_default(self):
145 def _config_file_name_default(self):
146 return self.name.replace('-','_') + u'_config.py'
146 return self.name.replace('-','_') + u'_config.py'
147 @observe('config_file_name')
147 @observe('config_file_name')
148 def _config_file_name_changed(self, change):
148 def _config_file_name_changed(self, change):
149 if change['new'] != change['old']:
149 if change['new'] != change['old']:
150 self.config_file_specified.add(change['new'])
150 self.config_file_specified.add(change['new'])
151
151
152 # The directory that contains IPython's builtin profiles.
152 # The directory that contains IPython's builtin profiles.
153 builtin_profile_dir = Unicode(
153 builtin_profile_dir = Unicode(
154 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
154 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
155 )
155 )
156
156
157 config_file_paths = List(Unicode())
157 config_file_paths = List(Unicode())
158 @default('config_file_paths')
158 @default('config_file_paths')
159 def _config_file_paths_default(self):
159 def _config_file_paths_default(self):
160 return []
160 return []
161
161
162 extra_config_file = Unicode(
162 extra_config_file = Unicode(
163 help="""Path to an extra config file to load.
163 help="""Path to an extra config file to load.
164
164
165 If specified, load this config file in addition to any other IPython config.
165 If specified, load this config file in addition to any other IPython config.
166 """).tag(config=True)
166 """).tag(config=True)
167 @observe('extra_config_file')
167 @observe('extra_config_file')
168 def _extra_config_file_changed(self, change):
168 def _extra_config_file_changed(self, change):
169 old = change['old']
169 old = change['old']
170 new = change['new']
170 new = change['new']
171 try:
171 try:
172 self.config_files.remove(old)
172 self.config_files.remove(old)
173 except ValueError:
173 except ValueError:
174 pass
174 pass
175 self.config_file_specified.add(new)
175 self.config_file_specified.add(new)
176 self.config_files.append(new)
176 self.config_files.append(new)
177
177
178 profile = Unicode(u'default',
178 profile = Unicode(u'default',
179 help="""The IPython profile to use."""
179 help="""The IPython profile to use."""
180 ).tag(config=True)
180 ).tag(config=True)
181
181
182 @observe('profile')
182 @observe('profile')
183 def _profile_changed(self, change):
183 def _profile_changed(self, change):
184 self.builtin_profile_dir = os.path.join(
184 self.builtin_profile_dir = os.path.join(
185 get_ipython_package_dir(), u'config', u'profile', change['new']
185 get_ipython_package_dir(), u'config', u'profile', change['new']
186 )
186 )
187
187
188 add_ipython_dir_to_sys_path = Bool(
188 add_ipython_dir_to_sys_path = Bool(
189 False,
189 False,
190 """Should the IPython profile directory be added to sys path ?
190 """Should the IPython profile directory be added to sys path ?
191
191
192 This option was non-existing before IPython 8.0, and ipython_dir was added to
192 This option was non-existing before IPython 8.0, and ipython_dir was added to
193 sys path to allow import of extensions present there. This was historical
193 sys path to allow import of extensions present there. This was historical
194 baggage from when pip did not exist. This now default to false,
194 baggage from when pip did not exist. This now default to false,
195 but can be set to true for legacy reasons.
195 but can be set to true for legacy reasons.
196 """,
196 """,
197 ).tag(config=True)
197 ).tag(config=True)
198
198
199 ipython_dir = Unicode(
199 ipython_dir = Unicode(
200 help="""
200 help="""
201 The name of the IPython directory. This directory is used for logging
201 The name of the IPython directory. This directory is used for logging
202 configuration (through profiles), history storage, etc. The default
202 configuration (through profiles), history storage, etc. The default
203 is usually $HOME/.ipython. This option can also be specified through
203 is usually $HOME/.ipython. This option can also be specified through
204 the environment variable IPYTHONDIR.
204 the environment variable IPYTHONDIR.
205 """
205 """
206 ).tag(config=True)
206 ).tag(config=True)
207 @default('ipython_dir')
207 @default('ipython_dir')
208 def _ipython_dir_default(self):
208 def _ipython_dir_default(self):
209 d = get_ipython_dir()
209 d = get_ipython_dir()
210 self._ipython_dir_changed({
210 self._ipython_dir_changed({
211 'name': 'ipython_dir',
211 'name': 'ipython_dir',
212 'old': d,
212 'old': d,
213 'new': d,
213 'new': d,
214 })
214 })
215 return d
215 return d
216
216
217 _in_init_profile_dir = False
217 _in_init_profile_dir = False
218 profile_dir = Instance(ProfileDir, allow_none=True)
218 profile_dir = Instance(ProfileDir, allow_none=True)
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 auto_create = Bool(False,
231 auto_create = Bool(False,
232 help="""Whether to create profile dir if it doesn't exist"""
232 help="""Whether to create profile dir if it doesn't exist"""
233 ).tag(config=True)
233 ).tag(config=True)
234
234
235 config_files = List(Unicode())
235 config_files = List(Unicode())
236 @default('config_files')
236 @default('config_files')
237 def _config_files_default(self):
237 def _config_files_default(self):
238 return [self.config_file_name]
238 return [self.config_file_name]
239
239
240 copy_config_files = Bool(False,
240 copy_config_files = Bool(False,
241 help="""Whether to install the default config files into the profile dir.
241 help="""Whether to install the default config files into the profile dir.
242 If a new profile is being created, and IPython contains config files for that
242 If a new profile is being created, and IPython contains config files for that
243 profile, then they will be staged into the new directory. Otherwise,
243 profile, then they will be staged into the new directory. Otherwise,
244 default config files will be automatically generated.
244 default config files will be automatically generated.
245 """).tag(config=True)
245 """).tag(config=True)
246
246
247 verbose_crash = Bool(False,
247 verbose_crash = Bool(False,
248 help="""Create a massive crash report when IPython encounters what may be an
248 help="""Create a massive crash report when IPython encounters what may be an
249 internal error. The default is to append a short message to the
249 internal error. The default is to append a short message to the
250 usual traceback""").tag(config=True)
250 usual traceback""").tag(config=True)
251
251
252 # The class to use as the crash handler.
252 # The class to use as the crash handler.
253 crash_handler_class = Type(crashhandler.CrashHandler)
253 crash_handler_class = Type(crashhandler.CrashHandler)
254
254
255 @catch_config_error
255 @catch_config_error
256 def __init__(self, **kwargs):
256 def __init__(self, **kwargs):
257 super(BaseIPythonApplication, self).__init__(**kwargs)
257 super(BaseIPythonApplication, self).__init__(**kwargs)
258 # ensure current working directory exists
258 # ensure current working directory exists
259 try:
259 try:
260 os.getcwd()
260 os.getcwd()
261 except:
261 except:
262 # exit if cwd doesn't exist
262 # exit if cwd doesn't exist
263 self.log.error("Current working directory doesn't exist.")
263 self.log.error("Current working directory doesn't exist.")
264 self.exit(1)
264 self.exit(1)
265
265
266 #-------------------------------------------------------------------------
266 #-------------------------------------------------------------------------
267 # Various stages of Application creation
267 # Various stages of Application creation
268 #-------------------------------------------------------------------------
268 #-------------------------------------------------------------------------
269
269
270 def init_crash_handler(self):
270 def init_crash_handler(self):
271 """Create a crash handler, typically setting sys.excepthook to it."""
271 """Create a crash handler, typically setting sys.excepthook to it."""
272 self.crash_handler = self.crash_handler_class(self)
272 self.crash_handler = self.crash_handler_class(self)
273 sys.excepthook = self.excepthook
273 sys.excepthook = self.excepthook
274 def unset_crashhandler():
274 def unset_crashhandler():
275 sys.excepthook = sys.__excepthook__
275 sys.excepthook = sys.__excepthook__
276 atexit.register(unset_crashhandler)
276 atexit.register(unset_crashhandler)
277
277
278 def excepthook(self, etype, evalue, tb):
278 def excepthook(self, etype, evalue, tb):
279 """this is sys.excepthook after init_crashhandler
279 """this is sys.excepthook after init_crashhandler
280
280
281 set self.verbose_crash=True to use our full crashhandler, instead of
281 set self.verbose_crash=True to use our full crashhandler, instead of
282 a regular traceback with a short message (crash_handler_lite)
282 a regular traceback with a short message (crash_handler_lite)
283 """
283 """
284
284
285 if self.verbose_crash:
285 if self.verbose_crash:
286 return self.crash_handler(etype, evalue, tb)
286 return self.crash_handler(etype, evalue, tb)
287 else:
287 else:
288 return crashhandler.crash_handler_lite(etype, evalue, tb)
288 return crashhandler.crash_handler_lite(etype, evalue, tb)
289
289
290 @observe('ipython_dir')
290 @observe('ipython_dir')
291 def _ipython_dir_changed(self, change):
291 def _ipython_dir_changed(self, change):
292 old = change['old']
292 old = change['old']
293 new = change['new']
293 new = change['new']
294 if old is not Undefined:
294 if old is not Undefined:
295 str_old = os.path.abspath(old)
295 str_old = os.path.abspath(old)
296 if str_old in sys.path:
296 if str_old in sys.path:
297 sys.path.remove(str_old)
297 sys.path.remove(str_old)
298 if self.add_ipython_dir_to_sys_path:
298 if self.add_ipython_dir_to_sys_path:
299 str_path = os.path.abspath(new)
299 str_path = os.path.abspath(new)
300 sys.path.append(str_path)
300 sys.path.append(str_path)
301 ensure_dir_exists(new)
301 ensure_dir_exists(new)
302 readme = os.path.join(new, "README")
302 readme = os.path.join(new, "README")
303 readme_src = os.path.join(
303 readme_src = os.path.join(
304 get_ipython_package_dir(), "config", "profile", "README"
304 get_ipython_package_dir(), "config", "profile", "README"
305 )
305 )
306 if not os.path.exists(readme) and os.path.exists(readme_src):
306 if not os.path.exists(readme) and os.path.exists(readme_src):
307 shutil.copy(readme_src, readme)
307 shutil.copy(readme_src, readme)
308 for d in ("extensions", "nbextensions"):
308 for d in ("extensions", "nbextensions"):
309 path = os.path.join(new, d)
309 path = os.path.join(new, d)
310 try:
310 try:
311 ensure_dir_exists(path)
311 ensure_dir_exists(path)
312 except OSError as e:
312 except OSError as e:
313 # this will not be EEXIST
313 # this will not be EEXIST
314 self.log.error("couldn't create path %s: %s", path, e)
314 self.log.error("couldn't create path %s: %s", path, e)
315 self.log.debug("IPYTHONDIR set to: %s" % new)
315 self.log.debug("IPYTHONDIR set to: %s" % new)
316
316
317 def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS):
317 def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS):
318 """Load the config file.
318 """Load the config file.
319
319
320 By default, errors in loading config are handled, and a warning
320 By default, errors in loading config are handled, and a warning
321 printed on screen. For testing, the suppress_errors option is set
321 printed on screen. For testing, the suppress_errors option is set
322 to False, so errors will make tests fail.
322 to False, so errors will make tests fail.
323
323
324 `suppress_errors` default value is to be `None` in which case the
324 `suppress_errors` default value is to be `None` in which case the
325 behavior default to the one of `traitlets.Application`.
325 behavior default to the one of `traitlets.Application`.
326
326
327 The default value can be set :
327 The default value can be set :
328 - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive).
328 - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive).
329 - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive).
329 - to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive).
330 - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset.
330 - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset.
331
331
332 Any other value are invalid, and will make IPython exit with a non-zero return code.
332 Any other value are invalid, and will make IPython exit with a non-zero return code.
333 """
333 """
334
334
335
335
336 self.log.debug("Searching path %s for config files", self.config_file_paths)
336 self.log.debug("Searching path %s for config files", self.config_file_paths)
337 base_config = 'ipython_config.py'
337 base_config = 'ipython_config.py'
338 self.log.debug("Attempting to load config file: %s" %
338 self.log.debug("Attempting to load config file: %s" %
339 base_config)
339 base_config)
340 try:
340 try:
341 if suppress_errors is not None:
341 if suppress_errors is not None:
342 old_value = Application.raise_config_file_errors
342 old_value = Application.raise_config_file_errors
343 Application.raise_config_file_errors = not suppress_errors;
343 Application.raise_config_file_errors = not suppress_errors;
344 Application.load_config_file(
344 Application.load_config_file(
345 self,
345 self,
346 base_config,
346 base_config,
347 path=self.config_file_paths
347 path=self.config_file_paths
348 )
348 )
349 except ConfigFileNotFound:
349 except ConfigFileNotFound:
350 # ignore errors loading parent
350 # ignore errors loading parent
351 self.log.debug("Config file %s not found", base_config)
351 self.log.debug("Config file %s not found", base_config)
352 pass
352 pass
353 if suppress_errors is not None:
353 if suppress_errors is not None:
354 Application.raise_config_file_errors = old_value
354 Application.raise_config_file_errors = old_value
355
355
356 for config_file_name in self.config_files:
356 for config_file_name in self.config_files:
357 if not config_file_name or config_file_name == base_config:
357 if not config_file_name or config_file_name == base_config:
358 continue
358 continue
359 self.log.debug("Attempting to load config file: %s" %
359 self.log.debug("Attempting to load config file: %s" %
360 self.config_file_name)
360 self.config_file_name)
361 try:
361 try:
362 Application.load_config_file(
362 Application.load_config_file(
363 self,
363 self,
364 config_file_name,
364 config_file_name,
365 path=self.config_file_paths
365 path=self.config_file_paths
366 )
366 )
367 except ConfigFileNotFound:
367 except ConfigFileNotFound:
368 # Only warn if the default config file was NOT being used.
368 # Only warn if the default config file was NOT being used.
369 if config_file_name in self.config_file_specified:
369 if config_file_name in self.config_file_specified:
370 msg = self.log.warning
370 msg = self.log.warning
371 else:
371 else:
372 msg = self.log.debug
372 msg = self.log.debug
373 msg("Config file not found, skipping: %s", config_file_name)
373 msg("Config file not found, skipping: %s", config_file_name)
374 except Exception:
374 except Exception:
375 # For testing purposes.
375 # For testing purposes.
376 if not suppress_errors:
376 if not suppress_errors:
377 raise
377 raise
378 self.log.warning("Error loading config file: %s" %
378 self.log.warning("Error loading config file: %s" %
379 self.config_file_name, exc_info=True)
379 self.config_file_name, exc_info=True)
380
380
381 def init_profile_dir(self):
381 def init_profile_dir(self):
382 """initialize the profile dir"""
382 """initialize the profile dir"""
383 self._in_init_profile_dir = True
383 self._in_init_profile_dir = True
384 if self.profile_dir is not None:
384 if self.profile_dir is not None:
385 # already ran
385 # already ran
386 return
386 return
387 if 'ProfileDir.location' not in self.config:
387 if 'ProfileDir.location' not in self.config:
388 # location not specified, find by profile name
388 # location not specified, find by profile name
389 try:
389 try:
390 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
390 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
391 except ProfileDirError:
391 except ProfileDirError:
392 # not found, maybe create it (always create default profile)
392 # not found, maybe create it (always create default profile)
393 if self.auto_create or self.profile == 'default':
393 if self.auto_create or self.profile == 'default':
394 try:
394 try:
395 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
395 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
396 except ProfileDirError:
396 except ProfileDirError:
397 self.log.fatal("Could not create profile: %r"%self.profile)
397 self.log.fatal("Could not create profile: %r"%self.profile)
398 self.exit(1)
398 self.exit(1)
399 else:
399 else:
400 self.log.info("Created profile dir: %r"%p.location)
400 self.log.info("Created profile dir: %r"%p.location)
401 else:
401 else:
402 self.log.fatal("Profile %r not found."%self.profile)
402 self.log.fatal("Profile %r not found."%self.profile)
403 self.exit(1)
403 self.exit(1)
404 else:
404 else:
405 self.log.debug(f"Using existing profile dir: {p.location!r}")
405 self.log.debug(f"Using existing profile dir: {p.location!r}")
406 else:
406 else:
407 location = self.config.ProfileDir.location
407 location = self.config.ProfileDir.location
408 # location is fully specified
408 # location is fully specified
409 try:
409 try:
410 p = ProfileDir.find_profile_dir(location, self.config)
410 p = ProfileDir.find_profile_dir(location, self.config)
411 except ProfileDirError:
411 except ProfileDirError:
412 # not found, maybe create it
412 # not found, maybe create it
413 if self.auto_create:
413 if self.auto_create:
414 try:
414 try:
415 p = ProfileDir.create_profile_dir(location, self.config)
415 p = ProfileDir.create_profile_dir(location, self.config)
416 except ProfileDirError:
416 except ProfileDirError:
417 self.log.fatal("Could not create profile directory: %r"%location)
417 self.log.fatal("Could not create profile directory: %r"%location)
418 self.exit(1)
418 self.exit(1)
419 else:
419 else:
420 self.log.debug("Creating new profile dir: %r"%location)
420 self.log.debug("Creating new profile dir: %r"%location)
421 else:
421 else:
422 self.log.fatal("Profile directory %r not found."%location)
422 self.log.fatal("Profile directory %r not found."%location)
423 self.exit(1)
423 self.exit(1)
424 else:
424 else:
425 self.log.debug(f"Using existing profile dir: {p.location!r}")
425 self.log.debug(f"Using existing profile dir: {p.location!r}")
426 # if profile_dir is specified explicitly, set profile name
426 # if profile_dir is specified explicitly, set profile name
427 dir_name = os.path.basename(p.location)
427 dir_name = os.path.basename(p.location)
428 if dir_name.startswith('profile_'):
428 if dir_name.startswith('profile_'):
429 self.profile = dir_name[8:]
429 self.profile = dir_name[8:]
430
430
431 self.profile_dir = p
431 self.profile_dir = p
432 self.config_file_paths.append(p.location)
432 self.config_file_paths.append(p.location)
433 self._in_init_profile_dir = False
433 self._in_init_profile_dir = False
434
434
435 def init_config_files(self):
435 def init_config_files(self):
436 """[optionally] copy default config files into profile dir."""
436 """[optionally] copy default config files into profile dir."""
437 self.config_file_paths.extend(ENV_CONFIG_DIRS)
437 self.config_file_paths.extend(ENV_CONFIG_DIRS)
438 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
438 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
439 # copy config files
439 # copy config files
440 path = Path(self.builtin_profile_dir)
440 path = Path(self.builtin_profile_dir)
441 if self.copy_config_files:
441 if self.copy_config_files:
442 src = self.profile
442 src = self.profile
443
443
444 cfg = self.config_file_name
444 cfg = self.config_file_name
445 if path and (path / cfg).exists():
445 if path and (path / cfg).exists():
446 self.log.warning(
446 self.log.warning(
447 "Staging %r from %s into %r [overwrite=%s]"
447 "Staging %r from %s into %r [overwrite=%s]"
448 % (cfg, src, self.profile_dir.location, self.overwrite)
448 % (cfg, src, self.profile_dir.location, self.overwrite)
449 )
449 )
450 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
450 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
451 else:
451 else:
452 self.stage_default_config_file()
452 self.stage_default_config_file()
453 else:
453 else:
454 # Still stage *bundled* config files, but not generated ones
454 # Still stage *bundled* config files, but not generated ones
455 # This is necessary for `ipython profile=sympy` to load the profile
455 # This is necessary for `ipython profile=sympy` to load the profile
456 # on the first go
456 # on the first go
457 files = path.glob("*.py")
457 files = path.glob("*.py")
458 for fullpath in files:
458 for fullpath in files:
459 cfg = fullpath.name
459 cfg = fullpath.name
460 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
460 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
461 # file was copied
461 # file was copied
462 self.log.warning("Staging bundled %s from %s into %r"%(
462 self.log.warning("Staging bundled %s from %s into %r"%(
463 cfg, self.profile, self.profile_dir.location)
463 cfg, self.profile, self.profile_dir.location)
464 )
464 )
465
465
466
466
467 def stage_default_config_file(self):
467 def stage_default_config_file(self):
468 """auto generate default config file, and stage it into the profile."""
468 """auto generate default config file, and stage it into the profile."""
469 s = self.generate_config_file()
469 s = self.generate_config_file()
470 config_file = Path(self.profile_dir.location) / self.config_file_name
470 config_file = Path(self.profile_dir.location) / self.config_file_name
471 if self.overwrite or not config_file.exists():
471 if self.overwrite or not config_file.exists():
472 self.log.warning("Generating default config file: %r" % (config_file))
472 self.log.warning("Generating default config file: %r" % (config_file))
473 config_file.write_text(s, encoding='utf-8')
473 config_file.write_text(s, encoding="utf-8")
474
474
475 @catch_config_error
475 @catch_config_error
476 def initialize(self, argv=None):
476 def initialize(self, argv=None):
477 # don't hook up crash handler before parsing command-line
477 # don't hook up crash handler before parsing command-line
478 self.parse_command_line(argv)
478 self.parse_command_line(argv)
479 self.init_crash_handler()
479 self.init_crash_handler()
480 if self.subapp is not None:
480 if self.subapp is not None:
481 # stop here if subapp is taking over
481 # stop here if subapp is taking over
482 return
482 return
483 # save a copy of CLI config to re-load after config files
483 # save a copy of CLI config to re-load after config files
484 # so that it has highest priority
484 # so that it has highest priority
485 cl_config = deepcopy(self.config)
485 cl_config = deepcopy(self.config)
486 self.init_profile_dir()
486 self.init_profile_dir()
487 self.init_config_files()
487 self.init_config_files()
488 self.load_config_file()
488 self.load_config_file()
489 # enforce cl-opts override configfile opts:
489 # enforce cl-opts override configfile opts:
490 self.update_config(cl_config)
490 self.update_config(cl_config)
@@ -1,237 +1,237 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """sys.excepthook for IPython itself, leaves a detailed report on disk.
2 """sys.excepthook for IPython itself, leaves a detailed report on disk.
3
3
4 Authors:
4 Authors:
5
5
6 * Fernando Perez
6 * Fernando Perez
7 * Brian E. Granger
7 * Brian E. Granger
8 """
8 """
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
11 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
12 # Copyright (C) 2008-2011 The IPython Development Team
12 # Copyright (C) 2008-2011 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 import os
22 import os
23 import sys
23 import sys
24 import traceback
24 import traceback
25 from pprint import pformat
25 from pprint import pformat
26 from pathlib import Path
26 from pathlib import Path
27
27
28 from IPython.core import ultratb
28 from IPython.core import ultratb
29 from IPython.core.release import author_email
29 from IPython.core.release import author_email
30 from IPython.utils.sysinfo import sys_info
30 from IPython.utils.sysinfo import sys_info
31 from IPython.utils.py3compat import input
31 from IPython.utils.py3compat import input
32
32
33 from IPython.core.release import __version__ as version
33 from IPython.core.release import __version__ as version
34
34
35 from typing import Optional
35 from typing import Optional
36
36
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # Code
38 # Code
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40
40
41 # Template for the user message.
41 # Template for the user message.
42 _default_message_template = """\
42 _default_message_template = """\
43 Oops, {app_name} crashed. We do our best to make it stable, but...
43 Oops, {app_name} crashed. We do our best to make it stable, but...
44
44
45 A crash report was automatically generated with the following information:
45 A crash report was automatically generated with the following information:
46 - A verbatim copy of the crash traceback.
46 - A verbatim copy of the crash traceback.
47 - A copy of your input history during this session.
47 - A copy of your input history during this session.
48 - Data on your current {app_name} configuration.
48 - Data on your current {app_name} configuration.
49
49
50 It was left in the file named:
50 It was left in the file named:
51 \t'{crash_report_fname}'
51 \t'{crash_report_fname}'
52 If you can email this file to the developers, the information in it will help
52 If you can email this file to the developers, the information in it will help
53 them in understanding and correcting the problem.
53 them in understanding and correcting the problem.
54
54
55 You can mail it to: {contact_name} at {contact_email}
55 You can mail it to: {contact_name} at {contact_email}
56 with the subject '{app_name} Crash Report'.
56 with the subject '{app_name} Crash Report'.
57
57
58 If you want to do it now, the following command will work (under Unix):
58 If you want to do it now, the following command will work (under Unix):
59 mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname}
59 mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname}
60
60
61 In your email, please also include information about:
61 In your email, please also include information about:
62 - The operating system under which the crash happened: Linux, macOS, Windows,
62 - The operating system under which the crash happened: Linux, macOS, Windows,
63 other, and which exact version (for example: Ubuntu 16.04.3, macOS 10.13.2,
63 other, and which exact version (for example: Ubuntu 16.04.3, macOS 10.13.2,
64 Windows 10 Pro), and whether it is 32-bit or 64-bit;
64 Windows 10 Pro), and whether it is 32-bit or 64-bit;
65 - How {app_name} was installed: using pip or conda, from GitHub, as part of
65 - How {app_name} was installed: using pip or conda, from GitHub, as part of
66 a Docker container, or other, providing more detail if possible;
66 a Docker container, or other, providing more detail if possible;
67 - How to reproduce the crash: what exact sequence of instructions can one
67 - How to reproduce the crash: what exact sequence of instructions can one
68 input to get the same crash? Ideally, find a minimal yet complete sequence
68 input to get the same crash? Ideally, find a minimal yet complete sequence
69 of instructions that yields the crash.
69 of instructions that yields the crash.
70
70
71 To ensure accurate tracking of this issue, please file a report about it at:
71 To ensure accurate tracking of this issue, please file a report about it at:
72 {bug_tracker}
72 {bug_tracker}
73 """
73 """
74
74
75 _lite_message_template = """
75 _lite_message_template = """
76 If you suspect this is an IPython {version} bug, please report it at:
76 If you suspect this is an IPython {version} bug, please report it at:
77 https://github.com/ipython/ipython/issues
77 https://github.com/ipython/ipython/issues
78 or send an email to the mailing list at {email}
78 or send an email to the mailing list at {email}
79
79
80 You can print a more detailed traceback right now with "%tb", or use "%debug"
80 You can print a more detailed traceback right now with "%tb", or use "%debug"
81 to interactively debug it.
81 to interactively debug it.
82
82
83 Extra-detailed tracebacks for bug-reporting purposes can be enabled via:
83 Extra-detailed tracebacks for bug-reporting purposes can be enabled via:
84 {config}Application.verbose_crash=True
84 {config}Application.verbose_crash=True
85 """
85 """
86
86
87
87
88 class CrashHandler(object):
88 class CrashHandler(object):
89 """Customizable crash handlers for IPython applications.
89 """Customizable crash handlers for IPython applications.
90
90
91 Instances of this class provide a :meth:`__call__` method which can be
91 Instances of this class provide a :meth:`__call__` method which can be
92 used as a ``sys.excepthook``. The :meth:`__call__` signature is::
92 used as a ``sys.excepthook``. The :meth:`__call__` signature is::
93
93
94 def __call__(self, etype, evalue, etb)
94 def __call__(self, etype, evalue, etb)
95 """
95 """
96
96
97 message_template = _default_message_template
97 message_template = _default_message_template
98 section_sep = '\n\n'+'*'*75+'\n\n'
98 section_sep = '\n\n'+'*'*75+'\n\n'
99
99
100 def __init__(
100 def __init__(
101 self,
101 self,
102 app,
102 app,
103 contact_name: Optional[str] = None,
103 contact_name: Optional[str] = None,
104 contact_email: Optional[str] = None,
104 contact_email: Optional[str] = None,
105 bug_tracker: Optional[str] = None,
105 bug_tracker: Optional[str] = None,
106 show_crash_traceback: bool = True,
106 show_crash_traceback: bool = True,
107 call_pdb: bool = False,
107 call_pdb: bool = False,
108 ):
108 ):
109 """Create a new crash handler
109 """Create a new crash handler
110
110
111 Parameters
111 Parameters
112 ----------
112 ----------
113 app : Application
113 app : Application
114 A running :class:`Application` instance, which will be queried at
114 A running :class:`Application` instance, which will be queried at
115 crash time for internal information.
115 crash time for internal information.
116 contact_name : str
116 contact_name : str
117 A string with the name of the person to contact.
117 A string with the name of the person to contact.
118 contact_email : str
118 contact_email : str
119 A string with the email address of the contact.
119 A string with the email address of the contact.
120 bug_tracker : str
120 bug_tracker : str
121 A string with the URL for your project's bug tracker.
121 A string with the URL for your project's bug tracker.
122 show_crash_traceback : bool
122 show_crash_traceback : bool
123 If false, don't print the crash traceback on stderr, only generate
123 If false, don't print the crash traceback on stderr, only generate
124 the on-disk report
124 the on-disk report
125 call_pdb
125 call_pdb
126 Whether to call pdb on crash
126 Whether to call pdb on crash
127
127
128 Attributes
128 Attributes
129 ----------
129 ----------
130 These instances contain some non-argument attributes which allow for
130 These instances contain some non-argument attributes which allow for
131 further customization of the crash handler's behavior. Please see the
131 further customization of the crash handler's behavior. Please see the
132 source for further details.
132 source for further details.
133
133
134 """
134 """
135 self.crash_report_fname = "Crash_report_%s.txt" % app.name
135 self.crash_report_fname = "Crash_report_%s.txt" % app.name
136 self.app = app
136 self.app = app
137 self.call_pdb = call_pdb
137 self.call_pdb = call_pdb
138 #self.call_pdb = True # dbg
138 #self.call_pdb = True # dbg
139 self.show_crash_traceback = show_crash_traceback
139 self.show_crash_traceback = show_crash_traceback
140 self.info = dict(app_name = app.name,
140 self.info = dict(app_name = app.name,
141 contact_name = contact_name,
141 contact_name = contact_name,
142 contact_email = contact_email,
142 contact_email = contact_email,
143 bug_tracker = bug_tracker,
143 bug_tracker = bug_tracker,
144 crash_report_fname = self.crash_report_fname)
144 crash_report_fname = self.crash_report_fname)
145
145
146
146
147 def __call__(self, etype, evalue, etb):
147 def __call__(self, etype, evalue, etb):
148 """Handle an exception, call for compatible with sys.excepthook"""
148 """Handle an exception, call for compatible with sys.excepthook"""
149
149
150 # do not allow the crash handler to be called twice without reinstalling it
150 # do not allow the crash handler to be called twice without reinstalling it
151 # this prevents unlikely errors in the crash handling from entering an
151 # this prevents unlikely errors in the crash handling from entering an
152 # infinite loop.
152 # infinite loop.
153 sys.excepthook = sys.__excepthook__
153 sys.excepthook = sys.__excepthook__
154
154
155 # Report tracebacks shouldn't use color in general (safer for users)
155 # Report tracebacks shouldn't use color in general (safer for users)
156 color_scheme = 'NoColor'
156 color_scheme = 'NoColor'
157
157
158 # Use this ONLY for developer debugging (keep commented out for release)
158 # Use this ONLY for developer debugging (keep commented out for release)
159 #color_scheme = 'Linux' # dbg
159 #color_scheme = 'Linux' # dbg
160 try:
160 try:
161 rptdir = self.app.ipython_dir
161 rptdir = self.app.ipython_dir
162 except:
162 except:
163 rptdir = Path.cwd()
163 rptdir = Path.cwd()
164 if rptdir is None or not Path.is_dir(rptdir):
164 if rptdir is None or not Path.is_dir(rptdir):
165 rptdir = Path.cwd()
165 rptdir = Path.cwd()
166 report_name = rptdir / self.crash_report_fname
166 report_name = rptdir / self.crash_report_fname
167 # write the report filename into the instance dict so it can get
167 # write the report filename into the instance dict so it can get
168 # properly expanded out in the user message template
168 # properly expanded out in the user message template
169 self.crash_report_fname = report_name
169 self.crash_report_fname = report_name
170 self.info['crash_report_fname'] = report_name
170 self.info['crash_report_fname'] = report_name
171 TBhandler = ultratb.VerboseTB(
171 TBhandler = ultratb.VerboseTB(
172 color_scheme=color_scheme,
172 color_scheme=color_scheme,
173 long_header=1,
173 long_header=1,
174 call_pdb=self.call_pdb,
174 call_pdb=self.call_pdb,
175 )
175 )
176 if self.call_pdb:
176 if self.call_pdb:
177 TBhandler(etype,evalue,etb)
177 TBhandler(etype,evalue,etb)
178 return
178 return
179 else:
179 else:
180 traceback = TBhandler.text(etype,evalue,etb,context=31)
180 traceback = TBhandler.text(etype,evalue,etb,context=31)
181
181
182 # print traceback to screen
182 # print traceback to screen
183 if self.show_crash_traceback:
183 if self.show_crash_traceback:
184 print(traceback, file=sys.stderr)
184 print(traceback, file=sys.stderr)
185
185
186 # and generate a complete report on disk
186 # and generate a complete report on disk
187 try:
187 try:
188 report = open(report_name, 'w', encoding='utf-8')
188 report = open(report_name, "w", encoding="utf-8")
189 except:
189 except:
190 print('Could not create crash report on disk.', file=sys.stderr)
190 print('Could not create crash report on disk.', file=sys.stderr)
191 return
191 return
192
192
193 with report:
193 with report:
194 # Inform user on stderr of what happened
194 # Inform user on stderr of what happened
195 print('\n'+'*'*70+'\n', file=sys.stderr)
195 print('\n'+'*'*70+'\n', file=sys.stderr)
196 print(self.message_template.format(**self.info), file=sys.stderr)
196 print(self.message_template.format(**self.info), file=sys.stderr)
197
197
198 # Construct report on disk
198 # Construct report on disk
199 report.write(self.make_report(traceback))
199 report.write(self.make_report(traceback))
200
200
201 input("Hit <Enter> to quit (your terminal may close):")
201 input("Hit <Enter> to quit (your terminal may close):")
202
202
203 def make_report(self,traceback):
203 def make_report(self,traceback):
204 """Return a string containing a crash report."""
204 """Return a string containing a crash report."""
205
205
206 sec_sep = self.section_sep
206 sec_sep = self.section_sep
207
207
208 report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
208 report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
209 rpt_add = report.append
209 rpt_add = report.append
210 rpt_add(sys_info())
210 rpt_add(sys_info())
211
211
212 try:
212 try:
213 config = pformat(self.app.config)
213 config = pformat(self.app.config)
214 rpt_add(sec_sep)
214 rpt_add(sec_sep)
215 rpt_add('Application name: %s\n\n' % self.app_name)
215 rpt_add('Application name: %s\n\n' % self.app_name)
216 rpt_add('Current user configuration structure:\n\n')
216 rpt_add('Current user configuration structure:\n\n')
217 rpt_add(config)
217 rpt_add(config)
218 except:
218 except:
219 pass
219 pass
220 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
220 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
221
221
222 return ''.join(report)
222 return ''.join(report)
223
223
224
224
225 def crash_handler_lite(etype, evalue, tb):
225 def crash_handler_lite(etype, evalue, tb):
226 """a light excepthook, adding a small message to the usual traceback"""
226 """a light excepthook, adding a small message to the usual traceback"""
227 traceback.print_exception(etype, evalue, tb)
227 traceback.print_exception(etype, evalue, tb)
228
228
229 from IPython.core.interactiveshell import InteractiveShell
229 from IPython.core.interactiveshell import InteractiveShell
230 if InteractiveShell.initialized():
230 if InteractiveShell.initialized():
231 # we are in a Shell environment, give %magic example
231 # we are in a Shell environment, give %magic example
232 config = "%config "
232 config = "%config "
233 else:
233 else:
234 # we are not in a shell, show generic config
234 # we are not in a shell, show generic config
235 config = "c."
235 config = "c."
236 print(_lite_message_template.format(email=author_email, config=config, version=version), file=sys.stderr)
236 print(_lite_message_template.format(email=author_email, config=config, version=version), file=sys.stderr)
237
237
@@ -1,1274 +1,1277 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Top-level display functions for displaying object in different formats."""
2 """Top-level display functions for displaying object in different formats."""
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 from binascii import b2a_base64, hexlify
8 from binascii import b2a_base64, hexlify
9 import html
9 import html
10 import json
10 import json
11 import mimetypes
11 import mimetypes
12 import os
12 import os
13 import struct
13 import struct
14 import warnings
14 import warnings
15 from copy import deepcopy
15 from copy import deepcopy
16 from os.path import splitext
16 from os.path import splitext
17 from pathlib import Path, PurePath
17 from pathlib import Path, PurePath
18
18
19 from IPython.utils.py3compat import cast_unicode
19 from IPython.utils.py3compat import cast_unicode
20 from IPython.testing.skipdoctest import skip_doctest
20 from IPython.testing.skipdoctest import skip_doctest
21 from . import display_functions
21 from . import display_functions
22
22
23
23
24 __all__ = ['display_pretty', 'display_html', 'display_markdown',
24 __all__ = ['display_pretty', 'display_html', 'display_markdown',
25 'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json',
25 'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json',
26 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject',
26 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject',
27 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'ProgressBar', 'JSON',
27 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'ProgressBar', 'JSON',
28 'GeoJSON', 'Javascript', 'Image', 'set_matplotlib_formats',
28 'GeoJSON', 'Javascript', 'Image', 'set_matplotlib_formats',
29 'set_matplotlib_close',
29 'set_matplotlib_close',
30 'Video']
30 'Video']
31
31
32 _deprecated_names = ["display", "clear_output", "publish_display_data", "update_display", "DisplayHandle"]
32 _deprecated_names = ["display", "clear_output", "publish_display_data", "update_display", "DisplayHandle"]
33
33
34 __all__ = __all__ + _deprecated_names
34 __all__ = __all__ + _deprecated_names
35
35
36
36
37 # ----- warn to import from IPython.display -----
37 # ----- warn to import from IPython.display -----
38
38
39 from warnings import warn
39 from warnings import warn
40
40
41
41
42 def __getattr__(name):
42 def __getattr__(name):
43 if name in _deprecated_names:
43 if name in _deprecated_names:
44 warn(f"Importing {name} from IPython.core.display is deprecated since IPython 7.14, please import from IPython display", DeprecationWarning, stacklevel=2)
44 warn(f"Importing {name} from IPython.core.display is deprecated since IPython 7.14, please import from IPython display", DeprecationWarning, stacklevel=2)
45 return getattr(display_functions, name)
45 return getattr(display_functions, name)
46
46
47 if name in globals().keys():
47 if name in globals().keys():
48 return globals()[name]
48 return globals()[name]
49 else:
49 else:
50 raise AttributeError(f"module {__name__} has no attribute {name}")
50 raise AttributeError(f"module {__name__} has no attribute {name}")
51
51
52
52
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54 # utility functions
54 # utility functions
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56
56
57 def _safe_exists(path):
57 def _safe_exists(path):
58 """Check path, but don't let exceptions raise"""
58 """Check path, but don't let exceptions raise"""
59 try:
59 try:
60 return os.path.exists(path)
60 return os.path.exists(path)
61 except Exception:
61 except Exception:
62 return False
62 return False
63
63
64
64
65 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
65 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
66 """internal implementation of all display_foo methods
66 """internal implementation of all display_foo methods
67
67
68 Parameters
68 Parameters
69 ----------
69 ----------
70 mimetype : str
70 mimetype : str
71 The mimetype to be published (e.g. 'image/png')
71 The mimetype to be published (e.g. 'image/png')
72 *objs : object
72 *objs : object
73 The Python objects to display, or if raw=True raw text data to
73 The Python objects to display, or if raw=True raw text data to
74 display.
74 display.
75 raw : bool
75 raw : bool
76 Are the data objects raw data or Python objects that need to be
76 Are the data objects raw data or Python objects that need to be
77 formatted before display? [default: False]
77 formatted before display? [default: False]
78 metadata : dict (optional)
78 metadata : dict (optional)
79 Metadata to be associated with the specific mimetype output.
79 Metadata to be associated with the specific mimetype output.
80 """
80 """
81 if metadata:
81 if metadata:
82 metadata = {mimetype: metadata}
82 metadata = {mimetype: metadata}
83 if raw:
83 if raw:
84 # turn list of pngdata into list of { 'image/png': pngdata }
84 # turn list of pngdata into list of { 'image/png': pngdata }
85 objs = [ {mimetype: obj} for obj in objs ]
85 objs = [ {mimetype: obj} for obj in objs ]
86 display_functions.display(*objs, raw=raw, metadata=metadata, include=[mimetype])
86 display_functions.display(*objs, raw=raw, metadata=metadata, include=[mimetype])
87
87
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89 # Main functions
89 # Main functions
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91
91
92
92
93 def display_pretty(*objs, **kwargs):
93 def display_pretty(*objs, **kwargs):
94 """Display the pretty (default) representation of an object.
94 """Display the pretty (default) representation of an object.
95
95
96 Parameters
96 Parameters
97 ----------
97 ----------
98 *objs : object
98 *objs : object
99 The Python objects to display, or if raw=True raw text data to
99 The Python objects to display, or if raw=True raw text data to
100 display.
100 display.
101 raw : bool
101 raw : bool
102 Are the data objects raw data or Python objects that need to be
102 Are the data objects raw data or Python objects that need to be
103 formatted before display? [default: False]
103 formatted before display? [default: False]
104 metadata : dict (optional)
104 metadata : dict (optional)
105 Metadata to be associated with the specific mimetype output.
105 Metadata to be associated with the specific mimetype output.
106 """
106 """
107 _display_mimetype('text/plain', objs, **kwargs)
107 _display_mimetype('text/plain', objs, **kwargs)
108
108
109
109
110 def display_html(*objs, **kwargs):
110 def display_html(*objs, **kwargs):
111 """Display the HTML representation of an object.
111 """Display the HTML representation of an object.
112
112
113 Note: If raw=False and the object does not have a HTML
113 Note: If raw=False and the object does not have a HTML
114 representation, no HTML will be shown.
114 representation, no HTML will be shown.
115
115
116 Parameters
116 Parameters
117 ----------
117 ----------
118 *objs : object
118 *objs : object
119 The Python objects to display, or if raw=True raw HTML data to
119 The Python objects to display, or if raw=True raw HTML data to
120 display.
120 display.
121 raw : bool
121 raw : bool
122 Are the data objects raw data or Python objects that need to be
122 Are the data objects raw data or Python objects that need to be
123 formatted before display? [default: False]
123 formatted before display? [default: False]
124 metadata : dict (optional)
124 metadata : dict (optional)
125 Metadata to be associated with the specific mimetype output.
125 Metadata to be associated with the specific mimetype output.
126 """
126 """
127 _display_mimetype('text/html', objs, **kwargs)
127 _display_mimetype('text/html', objs, **kwargs)
128
128
129
129
130 def display_markdown(*objs, **kwargs):
130 def display_markdown(*objs, **kwargs):
131 """Displays the Markdown representation of an object.
131 """Displays the Markdown representation of an object.
132
132
133 Parameters
133 Parameters
134 ----------
134 ----------
135 *objs : object
135 *objs : object
136 The Python objects to display, or if raw=True raw markdown data to
136 The Python objects to display, or if raw=True raw markdown data to
137 display.
137 display.
138 raw : bool
138 raw : bool
139 Are the data objects raw data or Python objects that need to be
139 Are the data objects raw data or Python objects that need to be
140 formatted before display? [default: False]
140 formatted before display? [default: False]
141 metadata : dict (optional)
141 metadata : dict (optional)
142 Metadata to be associated with the specific mimetype output.
142 Metadata to be associated with the specific mimetype output.
143 """
143 """
144
144
145 _display_mimetype('text/markdown', objs, **kwargs)
145 _display_mimetype('text/markdown', objs, **kwargs)
146
146
147
147
148 def display_svg(*objs, **kwargs):
148 def display_svg(*objs, **kwargs):
149 """Display the SVG representation of an object.
149 """Display the SVG representation of an object.
150
150
151 Parameters
151 Parameters
152 ----------
152 ----------
153 *objs : object
153 *objs : object
154 The Python objects to display, or if raw=True raw svg data to
154 The Python objects to display, or if raw=True raw svg data to
155 display.
155 display.
156 raw : bool
156 raw : bool
157 Are the data objects raw data or Python objects that need to be
157 Are the data objects raw data or Python objects that need to be
158 formatted before display? [default: False]
158 formatted before display? [default: False]
159 metadata : dict (optional)
159 metadata : dict (optional)
160 Metadata to be associated with the specific mimetype output.
160 Metadata to be associated with the specific mimetype output.
161 """
161 """
162 _display_mimetype('image/svg+xml', objs, **kwargs)
162 _display_mimetype('image/svg+xml', objs, **kwargs)
163
163
164
164
165 def display_png(*objs, **kwargs):
165 def display_png(*objs, **kwargs):
166 """Display the PNG representation of an object.
166 """Display the PNG representation of an object.
167
167
168 Parameters
168 Parameters
169 ----------
169 ----------
170 *objs : object
170 *objs : object
171 The Python objects to display, or if raw=True raw png data to
171 The Python objects to display, or if raw=True raw png data to
172 display.
172 display.
173 raw : bool
173 raw : bool
174 Are the data objects raw data or Python objects that need to be
174 Are the data objects raw data or Python objects that need to be
175 formatted before display? [default: False]
175 formatted before display? [default: False]
176 metadata : dict (optional)
176 metadata : dict (optional)
177 Metadata to be associated with the specific mimetype output.
177 Metadata to be associated with the specific mimetype output.
178 """
178 """
179 _display_mimetype('image/png', objs, **kwargs)
179 _display_mimetype('image/png', objs, **kwargs)
180
180
181
181
182 def display_jpeg(*objs, **kwargs):
182 def display_jpeg(*objs, **kwargs):
183 """Display the JPEG representation of an object.
183 """Display the JPEG representation of an object.
184
184
185 Parameters
185 Parameters
186 ----------
186 ----------
187 *objs : object
187 *objs : object
188 The Python objects to display, or if raw=True raw JPEG data to
188 The Python objects to display, or if raw=True raw JPEG data to
189 display.
189 display.
190 raw : bool
190 raw : bool
191 Are the data objects raw data or Python objects that need to be
191 Are the data objects raw data or Python objects that need to be
192 formatted before display? [default: False]
192 formatted before display? [default: False]
193 metadata : dict (optional)
193 metadata : dict (optional)
194 Metadata to be associated with the specific mimetype output.
194 Metadata to be associated with the specific mimetype output.
195 """
195 """
196 _display_mimetype('image/jpeg', objs, **kwargs)
196 _display_mimetype('image/jpeg', objs, **kwargs)
197
197
198
198
199 def display_latex(*objs, **kwargs):
199 def display_latex(*objs, **kwargs):
200 """Display the LaTeX representation of an object.
200 """Display the LaTeX representation of an object.
201
201
202 Parameters
202 Parameters
203 ----------
203 ----------
204 *objs : object
204 *objs : object
205 The Python objects to display, or if raw=True raw latex data to
205 The Python objects to display, or if raw=True raw latex data to
206 display.
206 display.
207 raw : bool
207 raw : bool
208 Are the data objects raw data or Python objects that need to be
208 Are the data objects raw data or Python objects that need to be
209 formatted before display? [default: False]
209 formatted before display? [default: False]
210 metadata : dict (optional)
210 metadata : dict (optional)
211 Metadata to be associated with the specific mimetype output.
211 Metadata to be associated with the specific mimetype output.
212 """
212 """
213 _display_mimetype('text/latex', objs, **kwargs)
213 _display_mimetype('text/latex', objs, **kwargs)
214
214
215
215
216 def display_json(*objs, **kwargs):
216 def display_json(*objs, **kwargs):
217 """Display the JSON representation of an object.
217 """Display the JSON representation of an object.
218
218
219 Note that not many frontends support displaying JSON.
219 Note that not many frontends support displaying JSON.
220
220
221 Parameters
221 Parameters
222 ----------
222 ----------
223 *objs : object
223 *objs : object
224 The Python objects to display, or if raw=True raw json data to
224 The Python objects to display, or if raw=True raw json data to
225 display.
225 display.
226 raw : bool
226 raw : bool
227 Are the data objects raw data or Python objects that need to be
227 Are the data objects raw data or Python objects that need to be
228 formatted before display? [default: False]
228 formatted before display? [default: False]
229 metadata : dict (optional)
229 metadata : dict (optional)
230 Metadata to be associated with the specific mimetype output.
230 Metadata to be associated with the specific mimetype output.
231 """
231 """
232 _display_mimetype('application/json', objs, **kwargs)
232 _display_mimetype('application/json', objs, **kwargs)
233
233
234
234
235 def display_javascript(*objs, **kwargs):
235 def display_javascript(*objs, **kwargs):
236 """Display the Javascript representation of an object.
236 """Display the Javascript representation of an object.
237
237
238 Parameters
238 Parameters
239 ----------
239 ----------
240 *objs : object
240 *objs : object
241 The Python objects to display, or if raw=True raw javascript data to
241 The Python objects to display, or if raw=True raw javascript data to
242 display.
242 display.
243 raw : bool
243 raw : bool
244 Are the data objects raw data or Python objects that need to be
244 Are the data objects raw data or Python objects that need to be
245 formatted before display? [default: False]
245 formatted before display? [default: False]
246 metadata : dict (optional)
246 metadata : dict (optional)
247 Metadata to be associated with the specific mimetype output.
247 Metadata to be associated with the specific mimetype output.
248 """
248 """
249 _display_mimetype('application/javascript', objs, **kwargs)
249 _display_mimetype('application/javascript', objs, **kwargs)
250
250
251
251
252 def display_pdf(*objs, **kwargs):
252 def display_pdf(*objs, **kwargs):
253 """Display the PDF representation of an object.
253 """Display the PDF representation of an object.
254
254
255 Parameters
255 Parameters
256 ----------
256 ----------
257 *objs : object
257 *objs : object
258 The Python objects to display, or if raw=True raw javascript data to
258 The Python objects to display, or if raw=True raw javascript data to
259 display.
259 display.
260 raw : bool
260 raw : bool
261 Are the data objects raw data or Python objects that need to be
261 Are the data objects raw data or Python objects that need to be
262 formatted before display? [default: False]
262 formatted before display? [default: False]
263 metadata : dict (optional)
263 metadata : dict (optional)
264 Metadata to be associated with the specific mimetype output.
264 Metadata to be associated with the specific mimetype output.
265 """
265 """
266 _display_mimetype('application/pdf', objs, **kwargs)
266 _display_mimetype('application/pdf', objs, **kwargs)
267
267
268
268
269 #-----------------------------------------------------------------------------
269 #-----------------------------------------------------------------------------
270 # Smart classes
270 # Smart classes
271 #-----------------------------------------------------------------------------
271 #-----------------------------------------------------------------------------
272
272
273
273
274 class DisplayObject(object):
274 class DisplayObject(object):
275 """An object that wraps data to be displayed."""
275 """An object that wraps data to be displayed."""
276
276
277 _read_flags = 'r'
277 _read_flags = 'r'
278 _show_mem_addr = False
278 _show_mem_addr = False
279 metadata = None
279 metadata = None
280
280
281 def __init__(self, data=None, url=None, filename=None, metadata=None):
281 def __init__(self, data=None, url=None, filename=None, metadata=None):
282 """Create a display object given raw data.
282 """Create a display object given raw data.
283
283
284 When this object is returned by an expression or passed to the
284 When this object is returned by an expression or passed to the
285 display function, it will result in the data being displayed
285 display function, it will result in the data being displayed
286 in the frontend. The MIME type of the data should match the
286 in the frontend. The MIME type of the data should match the
287 subclasses used, so the Png subclass should be used for 'image/png'
287 subclasses used, so the Png subclass should be used for 'image/png'
288 data. If the data is a URL, the data will first be downloaded
288 data. If the data is a URL, the data will first be downloaded
289 and then displayed. If
289 and then displayed. If
290
290
291 Parameters
291 Parameters
292 ----------
292 ----------
293 data : unicode, str or bytes
293 data : unicode, str or bytes
294 The raw data or a URL or file to load the data from
294 The raw data or a URL or file to load the data from
295 url : unicode
295 url : unicode
296 A URL to download the data from.
296 A URL to download the data from.
297 filename : unicode
297 filename : unicode
298 Path to a local file to load the data from.
298 Path to a local file to load the data from.
299 metadata : dict
299 metadata : dict
300 Dict of metadata associated to be the object when displayed
300 Dict of metadata associated to be the object when displayed
301 """
301 """
302 if isinstance(data, (Path, PurePath)):
302 if isinstance(data, (Path, PurePath)):
303 data = str(data)
303 data = str(data)
304
304
305 if data is not None and isinstance(data, str):
305 if data is not None and isinstance(data, str):
306 if data.startswith('http') and url is None:
306 if data.startswith('http') and url is None:
307 url = data
307 url = data
308 filename = None
308 filename = None
309 data = None
309 data = None
310 elif _safe_exists(data) and filename is None:
310 elif _safe_exists(data) and filename is None:
311 url = None
311 url = None
312 filename = data
312 filename = data
313 data = None
313 data = None
314
314
315 self.url = url
315 self.url = url
316 self.filename = filename
316 self.filename = filename
317 # because of @data.setter methods in
317 # because of @data.setter methods in
318 # subclasses ensure url and filename are set
318 # subclasses ensure url and filename are set
319 # before assigning to self.data
319 # before assigning to self.data
320 self.data = data
320 self.data = data
321
321
322 if metadata is not None:
322 if metadata is not None:
323 self.metadata = metadata
323 self.metadata = metadata
324 elif self.metadata is None:
324 elif self.metadata is None:
325 self.metadata = {}
325 self.metadata = {}
326
326
327 self.reload()
327 self.reload()
328 self._check_data()
328 self._check_data()
329
329
330 def __repr__(self):
330 def __repr__(self):
331 if not self._show_mem_addr:
331 if not self._show_mem_addr:
332 cls = self.__class__
332 cls = self.__class__
333 r = "<%s.%s object>" % (cls.__module__, cls.__name__)
333 r = "<%s.%s object>" % (cls.__module__, cls.__name__)
334 else:
334 else:
335 r = super(DisplayObject, self).__repr__()
335 r = super(DisplayObject, self).__repr__()
336 return r
336 return r
337
337
338 def _check_data(self):
338 def _check_data(self):
339 """Override in subclasses if there's something to check."""
339 """Override in subclasses if there's something to check."""
340 pass
340 pass
341
341
342 def _data_and_metadata(self):
342 def _data_and_metadata(self):
343 """shortcut for returning metadata with shape information, if defined"""
343 """shortcut for returning metadata with shape information, if defined"""
344 if self.metadata:
344 if self.metadata:
345 return self.data, deepcopy(self.metadata)
345 return self.data, deepcopy(self.metadata)
346 else:
346 else:
347 return self.data
347 return self.data
348
348
349 def reload(self):
349 def reload(self):
350 """Reload the raw data from file or URL."""
350 """Reload the raw data from file or URL."""
351 if self.filename is not None:
351 if self.filename is not None:
352 encoding = None if 'b' in self._read_flags else 'utf-8'
352 encoding = None if "b" in self._read_flags else "utf-8"
353 with open(self.filename, self._read_flags, encoding=encoding) as f:
353 with open(self.filename, self._read_flags, encoding=encoding) as f:
354 self.data = f.read()
354 self.data = f.read()
355 elif self.url is not None:
355 elif self.url is not None:
356 # Deferred import
356 # Deferred import
357 from urllib.request import urlopen
357 from urllib.request import urlopen
358 response = urlopen(self.url)
358 response = urlopen(self.url)
359 data = response.read()
359 data = response.read()
360 # extract encoding from header, if there is one:
360 # extract encoding from header, if there is one:
361 encoding = None
361 encoding = None
362 if 'content-type' in response.headers:
362 if 'content-type' in response.headers:
363 for sub in response.headers['content-type'].split(';'):
363 for sub in response.headers['content-type'].split(';'):
364 sub = sub.strip()
364 sub = sub.strip()
365 if sub.startswith('charset'):
365 if sub.startswith('charset'):
366 encoding = sub.split('=')[-1].strip()
366 encoding = sub.split('=')[-1].strip()
367 break
367 break
368 if 'content-encoding' in response.headers:
368 if 'content-encoding' in response.headers:
369 # TODO: do deflate?
369 # TODO: do deflate?
370 if 'gzip' in response.headers['content-encoding']:
370 if 'gzip' in response.headers['content-encoding']:
371 import gzip
371 import gzip
372 from io import BytesIO
372 from io import BytesIO
373
373 # assume utf-8 if encoding is not specified
374 # assume utf-8 if encoding is not specified
374 with gzip.open(BytesIO(data), 'rt', encoding=encoding or 'utf-8') as fp:
375 with gzip.open(
376 BytesIO(data), "rt", encoding=encoding or "utf-8"
377 ) as fp:
375 encoding = None
378 encoding = None
376 data = fp.read()
379 data = fp.read()
377
380
378 # decode data, if an encoding was specified
381 # decode data, if an encoding was specified
379 # We only touch self.data once since
382 # We only touch self.data once since
380 # subclasses such as SVG have @data.setter methods
383 # subclasses such as SVG have @data.setter methods
381 # that transform self.data into ... well svg.
384 # that transform self.data into ... well svg.
382 if encoding:
385 if encoding:
383 self.data = data.decode(encoding, 'replace')
386 self.data = data.decode(encoding, 'replace')
384 else:
387 else:
385 self.data = data
388 self.data = data
386
389
387
390
388 class TextDisplayObject(DisplayObject):
391 class TextDisplayObject(DisplayObject):
389 """Validate that display data is text"""
392 """Validate that display data is text"""
390 def _check_data(self):
393 def _check_data(self):
391 if self.data is not None and not isinstance(self.data, str):
394 if self.data is not None and not isinstance(self.data, str):
392 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
395 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
393
396
394 class Pretty(TextDisplayObject):
397 class Pretty(TextDisplayObject):
395
398
396 def _repr_pretty_(self, pp, cycle):
399 def _repr_pretty_(self, pp, cycle):
397 return pp.text(self.data)
400 return pp.text(self.data)
398
401
399
402
400 class HTML(TextDisplayObject):
403 class HTML(TextDisplayObject):
401
404
402 def __init__(self, data=None, url=None, filename=None, metadata=None):
405 def __init__(self, data=None, url=None, filename=None, metadata=None):
403 def warn():
406 def warn():
404 if not data:
407 if not data:
405 return False
408 return False
406
409
407 #
410 #
408 # Avoid calling lower() on the entire data, because it could be a
411 # Avoid calling lower() on the entire data, because it could be a
409 # long string and we're only interested in its beginning and end.
412 # long string and we're only interested in its beginning and end.
410 #
413 #
411 prefix = data[:10].lower()
414 prefix = data[:10].lower()
412 suffix = data[-10:].lower()
415 suffix = data[-10:].lower()
413 return prefix.startswith("<iframe ") and suffix.endswith("</iframe>")
416 return prefix.startswith("<iframe ") and suffix.endswith("</iframe>")
414
417
415 if warn():
418 if warn():
416 warnings.warn("Consider using IPython.display.IFrame instead")
419 warnings.warn("Consider using IPython.display.IFrame instead")
417 super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata)
420 super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata)
418
421
419 def _repr_html_(self):
422 def _repr_html_(self):
420 return self._data_and_metadata()
423 return self._data_and_metadata()
421
424
422 def __html__(self):
425 def __html__(self):
423 """
426 """
424 This method exists to inform other HTML-using modules (e.g. Markupsafe,
427 This method exists to inform other HTML-using modules (e.g. Markupsafe,
425 htmltag, etc) that this object is HTML and does not need things like
428 htmltag, etc) that this object is HTML and does not need things like
426 special characters (<>&) escaped.
429 special characters (<>&) escaped.
427 """
430 """
428 return self._repr_html_()
431 return self._repr_html_()
429
432
430
433
431 class Markdown(TextDisplayObject):
434 class Markdown(TextDisplayObject):
432
435
433 def _repr_markdown_(self):
436 def _repr_markdown_(self):
434 return self._data_and_metadata()
437 return self._data_and_metadata()
435
438
436
439
437 class Math(TextDisplayObject):
440 class Math(TextDisplayObject):
438
441
439 def _repr_latex_(self):
442 def _repr_latex_(self):
440 s = r"$\displaystyle %s$" % self.data.strip('$')
443 s = r"$\displaystyle %s$" % self.data.strip('$')
441 if self.metadata:
444 if self.metadata:
442 return s, deepcopy(self.metadata)
445 return s, deepcopy(self.metadata)
443 else:
446 else:
444 return s
447 return s
445
448
446
449
447 class Latex(TextDisplayObject):
450 class Latex(TextDisplayObject):
448
451
449 def _repr_latex_(self):
452 def _repr_latex_(self):
450 return self._data_and_metadata()
453 return self._data_and_metadata()
451
454
452
455
453 class SVG(DisplayObject):
456 class SVG(DisplayObject):
454 """Embed an SVG into the display.
457 """Embed an SVG into the display.
455
458
456 Note if you just want to view a svg image via a URL use `:class:Image` with
459 Note if you just want to view a svg image via a URL use `:class:Image` with
457 a url=URL keyword argument.
460 a url=URL keyword argument.
458 """
461 """
459
462
460 _read_flags = 'rb'
463 _read_flags = 'rb'
461 # wrap data in a property, which extracts the <svg> tag, discarding
464 # wrap data in a property, which extracts the <svg> tag, discarding
462 # document headers
465 # document headers
463 _data = None
466 _data = None
464
467
465 @property
468 @property
466 def data(self):
469 def data(self):
467 return self._data
470 return self._data
468
471
469 @data.setter
472 @data.setter
470 def data(self, svg):
473 def data(self, svg):
471 if svg is None:
474 if svg is None:
472 self._data = None
475 self._data = None
473 return
476 return
474 # parse into dom object
477 # parse into dom object
475 from xml.dom import minidom
478 from xml.dom import minidom
476 x = minidom.parseString(svg)
479 x = minidom.parseString(svg)
477 # get svg tag (should be 1)
480 # get svg tag (should be 1)
478 found_svg = x.getElementsByTagName('svg')
481 found_svg = x.getElementsByTagName('svg')
479 if found_svg:
482 if found_svg:
480 svg = found_svg[0].toxml()
483 svg = found_svg[0].toxml()
481 else:
484 else:
482 # fallback on the input, trust the user
485 # fallback on the input, trust the user
483 # but this is probably an error.
486 # but this is probably an error.
484 pass
487 pass
485 svg = cast_unicode(svg)
488 svg = cast_unicode(svg)
486 self._data = svg
489 self._data = svg
487
490
488 def _repr_svg_(self):
491 def _repr_svg_(self):
489 return self._data_and_metadata()
492 return self._data_and_metadata()
490
493
491 class ProgressBar(DisplayObject):
494 class ProgressBar(DisplayObject):
492 """Progressbar supports displaying a progressbar like element
495 """Progressbar supports displaying a progressbar like element
493 """
496 """
494 def __init__(self, total):
497 def __init__(self, total):
495 """Creates a new progressbar
498 """Creates a new progressbar
496
499
497 Parameters
500 Parameters
498 ----------
501 ----------
499 total : int
502 total : int
500 maximum size of the progressbar
503 maximum size of the progressbar
501 """
504 """
502 self.total = total
505 self.total = total
503 self._progress = 0
506 self._progress = 0
504 self.html_width = '60ex'
507 self.html_width = '60ex'
505 self.text_width = 60
508 self.text_width = 60
506 self._display_id = hexlify(os.urandom(8)).decode('ascii')
509 self._display_id = hexlify(os.urandom(8)).decode('ascii')
507
510
508 def __repr__(self):
511 def __repr__(self):
509 fraction = self.progress / self.total
512 fraction = self.progress / self.total
510 filled = '=' * int(fraction * self.text_width)
513 filled = '=' * int(fraction * self.text_width)
511 rest = ' ' * (self.text_width - len(filled))
514 rest = ' ' * (self.text_width - len(filled))
512 return '[{}{}] {}/{}'.format(
515 return '[{}{}] {}/{}'.format(
513 filled, rest,
516 filled, rest,
514 self.progress, self.total,
517 self.progress, self.total,
515 )
518 )
516
519
517 def _repr_html_(self):
520 def _repr_html_(self):
518 return "<progress style='width:{}' max='{}' value='{}'></progress>".format(
521 return "<progress style='width:{}' max='{}' value='{}'></progress>".format(
519 self.html_width, self.total, self.progress)
522 self.html_width, self.total, self.progress)
520
523
521 def display(self):
524 def display(self):
522 display_functions.display(self, display_id=self._display_id)
525 display_functions.display(self, display_id=self._display_id)
523
526
524 def update(self):
527 def update(self):
525 display_functions.display(self, display_id=self._display_id, update=True)
528 display_functions.display(self, display_id=self._display_id, update=True)
526
529
527 @property
530 @property
528 def progress(self):
531 def progress(self):
529 return self._progress
532 return self._progress
530
533
531 @progress.setter
534 @progress.setter
532 def progress(self, value):
535 def progress(self, value):
533 self._progress = value
536 self._progress = value
534 self.update()
537 self.update()
535
538
536 def __iter__(self):
539 def __iter__(self):
537 self.display()
540 self.display()
538 self._progress = -1 # First iteration is 0
541 self._progress = -1 # First iteration is 0
539 return self
542 return self
540
543
541 def __next__(self):
544 def __next__(self):
542 """Returns current value and increments display by one."""
545 """Returns current value and increments display by one."""
543 self.progress += 1
546 self.progress += 1
544 if self.progress < self.total:
547 if self.progress < self.total:
545 return self.progress
548 return self.progress
546 else:
549 else:
547 raise StopIteration()
550 raise StopIteration()
548
551
549 class JSON(DisplayObject):
552 class JSON(DisplayObject):
550 """JSON expects a JSON-able dict or list
553 """JSON expects a JSON-able dict or list
551
554
552 not an already-serialized JSON string.
555 not an already-serialized JSON string.
553
556
554 Scalar types (None, number, string) are not allowed, only dict or list containers.
557 Scalar types (None, number, string) are not allowed, only dict or list containers.
555 """
558 """
556 # wrap data in a property, which warns about passing already-serialized JSON
559 # wrap data in a property, which warns about passing already-serialized JSON
557 _data = None
560 _data = None
558 def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, root='root', **kwargs):
561 def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, root='root', **kwargs):
559 """Create a JSON display object given raw data.
562 """Create a JSON display object given raw data.
560
563
561 Parameters
564 Parameters
562 ----------
565 ----------
563 data : dict or list
566 data : dict or list
564 JSON data to display. Not an already-serialized JSON string.
567 JSON data to display. Not an already-serialized JSON string.
565 Scalar types (None, number, string) are not allowed, only dict
568 Scalar types (None, number, string) are not allowed, only dict
566 or list containers.
569 or list containers.
567 url : unicode
570 url : unicode
568 A URL to download the data from.
571 A URL to download the data from.
569 filename : unicode
572 filename : unicode
570 Path to a local file to load the data from.
573 Path to a local file to load the data from.
571 expanded : boolean
574 expanded : boolean
572 Metadata to control whether a JSON display component is expanded.
575 Metadata to control whether a JSON display component is expanded.
573 metadata : dict
576 metadata : dict
574 Specify extra metadata to attach to the json display object.
577 Specify extra metadata to attach to the json display object.
575 root : str
578 root : str
576 The name of the root element of the JSON tree
579 The name of the root element of the JSON tree
577 """
580 """
578 self.metadata = {
581 self.metadata = {
579 'expanded': expanded,
582 'expanded': expanded,
580 'root': root,
583 'root': root,
581 }
584 }
582 if metadata:
585 if metadata:
583 self.metadata.update(metadata)
586 self.metadata.update(metadata)
584 if kwargs:
587 if kwargs:
585 self.metadata.update(kwargs)
588 self.metadata.update(kwargs)
586 super(JSON, self).__init__(data=data, url=url, filename=filename)
589 super(JSON, self).__init__(data=data, url=url, filename=filename)
587
590
588 def _check_data(self):
591 def _check_data(self):
589 if self.data is not None and not isinstance(self.data, (dict, list)):
592 if self.data is not None and not isinstance(self.data, (dict, list)):
590 raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data))
593 raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data))
591
594
592 @property
595 @property
593 def data(self):
596 def data(self):
594 return self._data
597 return self._data
595
598
596 @data.setter
599 @data.setter
597 def data(self, data):
600 def data(self, data):
598 if isinstance(data, (Path, PurePath)):
601 if isinstance(data, (Path, PurePath)):
599 data = str(data)
602 data = str(data)
600
603
601 if isinstance(data, str):
604 if isinstance(data, str):
602 if self.filename is None and self.url is None:
605 if self.filename is None and self.url is None:
603 warnings.warn("JSON expects JSONable dict or list, not JSON strings")
606 warnings.warn("JSON expects JSONable dict or list, not JSON strings")
604 data = json.loads(data)
607 data = json.loads(data)
605 self._data = data
608 self._data = data
606
609
607 def _data_and_metadata(self):
610 def _data_and_metadata(self):
608 return self.data, self.metadata
611 return self.data, self.metadata
609
612
610 def _repr_json_(self):
613 def _repr_json_(self):
611 return self._data_and_metadata()
614 return self._data_and_metadata()
612
615
613 _css_t = """var link = document.createElement("link");
616 _css_t = """var link = document.createElement("link");
614 link.ref = "stylesheet";
617 link.ref = "stylesheet";
615 link.type = "text/css";
618 link.type = "text/css";
616 link.href = "%s";
619 link.href = "%s";
617 document.head.appendChild(link);
620 document.head.appendChild(link);
618 """
621 """
619
622
620 _lib_t1 = """new Promise(function(resolve, reject) {
623 _lib_t1 = """new Promise(function(resolve, reject) {
621 var script = document.createElement("script");
624 var script = document.createElement("script");
622 script.onload = resolve;
625 script.onload = resolve;
623 script.onerror = reject;
626 script.onerror = reject;
624 script.src = "%s";
627 script.src = "%s";
625 document.head.appendChild(script);
628 document.head.appendChild(script);
626 }).then(() => {
629 }).then(() => {
627 """
630 """
628
631
629 _lib_t2 = """
632 _lib_t2 = """
630 });"""
633 });"""
631
634
632 class GeoJSON(JSON):
635 class GeoJSON(JSON):
633 """GeoJSON expects JSON-able dict
636 """GeoJSON expects JSON-able dict
634
637
635 not an already-serialized JSON string.
638 not an already-serialized JSON string.
636
639
637 Scalar types (None, number, string) are not allowed, only dict containers.
640 Scalar types (None, number, string) are not allowed, only dict containers.
638 """
641 """
639
642
640 def __init__(self, *args, **kwargs):
643 def __init__(self, *args, **kwargs):
641 """Create a GeoJSON display object given raw data.
644 """Create a GeoJSON display object given raw data.
642
645
643 Parameters
646 Parameters
644 ----------
647 ----------
645 data : dict or list
648 data : dict or list
646 VegaLite data. Not an already-serialized JSON string.
649 VegaLite data. Not an already-serialized JSON string.
647 Scalar types (None, number, string) are not allowed, only dict
650 Scalar types (None, number, string) are not allowed, only dict
648 or list containers.
651 or list containers.
649 url_template : string
652 url_template : string
650 Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template
653 Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template
651 layer_options : dict
654 layer_options : dict
652 Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options
655 Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options
653 url : unicode
656 url : unicode
654 A URL to download the data from.
657 A URL to download the data from.
655 filename : unicode
658 filename : unicode
656 Path to a local file to load the data from.
659 Path to a local file to load the data from.
657 metadata : dict
660 metadata : dict
658 Specify extra metadata to attach to the json display object.
661 Specify extra metadata to attach to the json display object.
659
662
660 Examples
663 Examples
661 --------
664 --------
662 The following will display an interactive map of Mars with a point of
665 The following will display an interactive map of Mars with a point of
663 interest on frontend that do support GeoJSON display.
666 interest on frontend that do support GeoJSON display.
664
667
665 >>> from IPython.display import GeoJSON
668 >>> from IPython.display import GeoJSON
666
669
667 >>> GeoJSON(data={
670 >>> GeoJSON(data={
668 ... "type": "Feature",
671 ... "type": "Feature",
669 ... "geometry": {
672 ... "geometry": {
670 ... "type": "Point",
673 ... "type": "Point",
671 ... "coordinates": [-81.327, 296.038]
674 ... "coordinates": [-81.327, 296.038]
672 ... }
675 ... }
673 ... },
676 ... },
674 ... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
677 ... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
675 ... layer_options={
678 ... layer_options={
676 ... "basemap_id": "celestia_mars-shaded-16k_global",
679 ... "basemap_id": "celestia_mars-shaded-16k_global",
677 ... "attribution" : "Celestia/praesepe",
680 ... "attribution" : "Celestia/praesepe",
678 ... "minZoom" : 0,
681 ... "minZoom" : 0,
679 ... "maxZoom" : 18,
682 ... "maxZoom" : 18,
680 ... })
683 ... })
681 <IPython.core.display.GeoJSON object>
684 <IPython.core.display.GeoJSON object>
682
685
683 In the terminal IPython, you will only see the text representation of
686 In the terminal IPython, you will only see the text representation of
684 the GeoJSON object.
687 the GeoJSON object.
685
688
686 """
689 """
687
690
688 super(GeoJSON, self).__init__(*args, **kwargs)
691 super(GeoJSON, self).__init__(*args, **kwargs)
689
692
690
693
691 def _ipython_display_(self):
694 def _ipython_display_(self):
692 bundle = {
695 bundle = {
693 'application/geo+json': self.data,
696 'application/geo+json': self.data,
694 'text/plain': '<IPython.display.GeoJSON object>'
697 'text/plain': '<IPython.display.GeoJSON object>'
695 }
698 }
696 metadata = {
699 metadata = {
697 'application/geo+json': self.metadata
700 'application/geo+json': self.metadata
698 }
701 }
699 display_functions.display(bundle, metadata=metadata, raw=True)
702 display_functions.display(bundle, metadata=metadata, raw=True)
700
703
701 class Javascript(TextDisplayObject):
704 class Javascript(TextDisplayObject):
702
705
703 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
706 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
704 """Create a Javascript display object given raw data.
707 """Create a Javascript display object given raw data.
705
708
706 When this object is returned by an expression or passed to the
709 When this object is returned by an expression or passed to the
707 display function, it will result in the data being displayed
710 display function, it will result in the data being displayed
708 in the frontend. If the data is a URL, the data will first be
711 in the frontend. If the data is a URL, the data will first be
709 downloaded and then displayed.
712 downloaded and then displayed.
710
713
711 In the Notebook, the containing element will be available as `element`,
714 In the Notebook, the containing element will be available as `element`,
712 and jQuery will be available. Content appended to `element` will be
715 and jQuery will be available. Content appended to `element` will be
713 visible in the output area.
716 visible in the output area.
714
717
715 Parameters
718 Parameters
716 ----------
719 ----------
717 data : unicode, str or bytes
720 data : unicode, str or bytes
718 The Javascript source code or a URL to download it from.
721 The Javascript source code or a URL to download it from.
719 url : unicode
722 url : unicode
720 A URL to download the data from.
723 A URL to download the data from.
721 filename : unicode
724 filename : unicode
722 Path to a local file to load the data from.
725 Path to a local file to load the data from.
723 lib : list or str
726 lib : list or str
724 A sequence of Javascript library URLs to load asynchronously before
727 A sequence of Javascript library URLs to load asynchronously before
725 running the source code. The full URLs of the libraries should
728 running the source code. The full URLs of the libraries should
726 be given. A single Javascript library URL can also be given as a
729 be given. A single Javascript library URL can also be given as a
727 string.
730 string.
728 css : list or str
731 css : list or str
729 A sequence of css files to load before running the source code.
732 A sequence of css files to load before running the source code.
730 The full URLs of the css files should be given. A single css URL
733 The full URLs of the css files should be given. A single css URL
731 can also be given as a string.
734 can also be given as a string.
732 """
735 """
733 if isinstance(lib, str):
736 if isinstance(lib, str):
734 lib = [lib]
737 lib = [lib]
735 elif lib is None:
738 elif lib is None:
736 lib = []
739 lib = []
737 if isinstance(css, str):
740 if isinstance(css, str):
738 css = [css]
741 css = [css]
739 elif css is None:
742 elif css is None:
740 css = []
743 css = []
741 if not isinstance(lib, (list,tuple)):
744 if not isinstance(lib, (list,tuple)):
742 raise TypeError('expected sequence, got: %r' % lib)
745 raise TypeError('expected sequence, got: %r' % lib)
743 if not isinstance(css, (list,tuple)):
746 if not isinstance(css, (list,tuple)):
744 raise TypeError('expected sequence, got: %r' % css)
747 raise TypeError('expected sequence, got: %r' % css)
745 self.lib = lib
748 self.lib = lib
746 self.css = css
749 self.css = css
747 super(Javascript, self).__init__(data=data, url=url, filename=filename)
750 super(Javascript, self).__init__(data=data, url=url, filename=filename)
748
751
749 def _repr_javascript_(self):
752 def _repr_javascript_(self):
750 r = ''
753 r = ''
751 for c in self.css:
754 for c in self.css:
752 r += _css_t % c
755 r += _css_t % c
753 for l in self.lib:
756 for l in self.lib:
754 r += _lib_t1 % l
757 r += _lib_t1 % l
755 r += self.data
758 r += self.data
756 r += _lib_t2*len(self.lib)
759 r += _lib_t2*len(self.lib)
757 return r
760 return r
758
761
759 # constants for identifying png/jpeg data
762 # constants for identifying png/jpeg data
760 _PNG = b'\x89PNG\r\n\x1a\n'
763 _PNG = b'\x89PNG\r\n\x1a\n'
761 _JPEG = b'\xff\xd8'
764 _JPEG = b'\xff\xd8'
762
765
763 def _pngxy(data):
766 def _pngxy(data):
764 """read the (width, height) from a PNG header"""
767 """read the (width, height) from a PNG header"""
765 ihdr = data.index(b'IHDR')
768 ihdr = data.index(b'IHDR')
766 # next 8 bytes are width/height
769 # next 8 bytes are width/height
767 return struct.unpack('>ii', data[ihdr+4:ihdr+12])
770 return struct.unpack('>ii', data[ihdr+4:ihdr+12])
768
771
769 def _jpegxy(data):
772 def _jpegxy(data):
770 """read the (width, height) from a JPEG header"""
773 """read the (width, height) from a JPEG header"""
771 # adapted from http://www.64lines.com/jpeg-width-height
774 # adapted from http://www.64lines.com/jpeg-width-height
772
775
773 idx = 4
776 idx = 4
774 while True:
777 while True:
775 block_size = struct.unpack('>H', data[idx:idx+2])[0]
778 block_size = struct.unpack('>H', data[idx:idx+2])[0]
776 idx = idx + block_size
779 idx = idx + block_size
777 if data[idx:idx+2] == b'\xFF\xC0':
780 if data[idx:idx+2] == b'\xFF\xC0':
778 # found Start of Frame
781 # found Start of Frame
779 iSOF = idx
782 iSOF = idx
780 break
783 break
781 else:
784 else:
782 # read another block
785 # read another block
783 idx += 2
786 idx += 2
784
787
785 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
788 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
786 return w, h
789 return w, h
787
790
788 def _gifxy(data):
791 def _gifxy(data):
789 """read the (width, height) from a GIF header"""
792 """read the (width, height) from a GIF header"""
790 return struct.unpack('<HH', data[6:10])
793 return struct.unpack('<HH', data[6:10])
791
794
792
795
793 class Image(DisplayObject):
796 class Image(DisplayObject):
794
797
795 _read_flags = 'rb'
798 _read_flags = 'rb'
796 _FMT_JPEG = u'jpeg'
799 _FMT_JPEG = u'jpeg'
797 _FMT_PNG = u'png'
800 _FMT_PNG = u'png'
798 _FMT_GIF = u'gif'
801 _FMT_GIF = u'gif'
799 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF]
802 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF]
800 _MIMETYPES = {
803 _MIMETYPES = {
801 _FMT_PNG: 'image/png',
804 _FMT_PNG: 'image/png',
802 _FMT_JPEG: 'image/jpeg',
805 _FMT_JPEG: 'image/jpeg',
803 _FMT_GIF: 'image/gif',
806 _FMT_GIF: 'image/gif',
804 }
807 }
805
808
806 def __init__(
809 def __init__(
807 self,
810 self,
808 data=None,
811 data=None,
809 url=None,
812 url=None,
810 filename=None,
813 filename=None,
811 format=None,
814 format=None,
812 embed=None,
815 embed=None,
813 width=None,
816 width=None,
814 height=None,
817 height=None,
815 retina=False,
818 retina=False,
816 unconfined=False,
819 unconfined=False,
817 metadata=None,
820 metadata=None,
818 alt=None,
821 alt=None,
819 ):
822 ):
820 """Create a PNG/JPEG/GIF image object given raw data.
823 """Create a PNG/JPEG/GIF image object given raw data.
821
824
822 When this object is returned by an input cell or passed to the
825 When this object is returned by an input cell or passed to the
823 display function, it will result in the image being displayed
826 display function, it will result in the image being displayed
824 in the frontend.
827 in the frontend.
825
828
826 Parameters
829 Parameters
827 ----------
830 ----------
828 data : unicode, str or bytes
831 data : unicode, str or bytes
829 The raw image data or a URL or filename to load the data from.
832 The raw image data or a URL or filename to load the data from.
830 This always results in embedded image data.
833 This always results in embedded image data.
831
834
832 url : unicode
835 url : unicode
833 A URL to download the data from. If you specify `url=`,
836 A URL to download the data from. If you specify `url=`,
834 the image data will not be embedded unless you also specify `embed=True`.
837 the image data will not be embedded unless you also specify `embed=True`.
835
838
836 filename : unicode
839 filename : unicode
837 Path to a local file to load the data from.
840 Path to a local file to load the data from.
838 Images from a file are always embedded.
841 Images from a file are always embedded.
839
842
840 format : unicode
843 format : unicode
841 The format of the image data (png/jpeg/jpg/gif). If a filename or URL is given
844 The format of the image data (png/jpeg/jpg/gif). If a filename or URL is given
842 for format will be inferred from the filename extension.
845 for format will be inferred from the filename extension.
843
846
844 embed : bool
847 embed : bool
845 Should the image data be embedded using a data URI (True) or be
848 Should the image data be embedded using a data URI (True) or be
846 loaded using an <img> tag. Set this to True if you want the image
849 loaded using an <img> tag. Set this to True if you want the image
847 to be viewable later with no internet connection in the notebook.
850 to be viewable later with no internet connection in the notebook.
848
851
849 Default is `True`, unless the keyword argument `url` is set, then
852 Default is `True`, unless the keyword argument `url` is set, then
850 default value is `False`.
853 default value is `False`.
851
854
852 Note that QtConsole is not able to display images if `embed` is set to `False`
855 Note that QtConsole is not able to display images if `embed` is set to `False`
853
856
854 width : int
857 width : int
855 Width in pixels to which to constrain the image in html
858 Width in pixels to which to constrain the image in html
856
859
857 height : int
860 height : int
858 Height in pixels to which to constrain the image in html
861 Height in pixels to which to constrain the image in html
859
862
860 retina : bool
863 retina : bool
861 Automatically set the width and height to half of the measured
864 Automatically set the width and height to half of the measured
862 width and height.
865 width and height.
863 This only works for embedded images because it reads the width/height
866 This only works for embedded images because it reads the width/height
864 from image data.
867 from image data.
865 For non-embedded images, you can just set the desired display width
868 For non-embedded images, you can just set the desired display width
866 and height directly.
869 and height directly.
867
870
868 unconfined : bool
871 unconfined : bool
869 Set unconfined=True to disable max-width confinement of the image.
872 Set unconfined=True to disable max-width confinement of the image.
870
873
871 metadata : dict
874 metadata : dict
872 Specify extra metadata to attach to the image.
875 Specify extra metadata to attach to the image.
873
876
874 alt : unicode
877 alt : unicode
875 Alternative text for the image, for use by screen readers.
878 Alternative text for the image, for use by screen readers.
876
879
877 Examples
880 Examples
878 --------
881 --------
879 embedded image data, works in qtconsole and notebook
882 embedded image data, works in qtconsole and notebook
880 when passed positionally, the first arg can be any of raw image data,
883 when passed positionally, the first arg can be any of raw image data,
881 a URL, or a filename from which to load image data.
884 a URL, or a filename from which to load image data.
882 The result is always embedding image data for inline images.
885 The result is always embedding image data for inline images.
883
886
884 >>> Image('http://www.google.fr/images/srpr/logo3w.png')
887 >>> Image('http://www.google.fr/images/srpr/logo3w.png')
885 <IPython.core.display.Image object>
888 <IPython.core.display.Image object>
886
889
887 >>> Image('/path/to/image.jpg')
890 >>> Image('/path/to/image.jpg')
888 <IPython.core.display.Image object>
891 <IPython.core.display.Image object>
889
892
890 >>> Image(b'RAW_PNG_DATA...')
893 >>> Image(b'RAW_PNG_DATA...')
891 <IPython.core.display.Image object>
894 <IPython.core.display.Image object>
892
895
893 Specifying Image(url=...) does not embed the image data,
896 Specifying Image(url=...) does not embed the image data,
894 it only generates ``<img>`` tag with a link to the source.
897 it only generates ``<img>`` tag with a link to the source.
895 This will not work in the qtconsole or offline.
898 This will not work in the qtconsole or offline.
896
899
897 >>> Image(url='http://www.google.fr/images/srpr/logo3w.png')
900 >>> Image(url='http://www.google.fr/images/srpr/logo3w.png')
898 <IPython.core.display.Image object>
901 <IPython.core.display.Image object>
899
902
900 """
903 """
901 if isinstance(data, (Path, PurePath)):
904 if isinstance(data, (Path, PurePath)):
902 data = str(data)
905 data = str(data)
903
906
904 if filename is not None:
907 if filename is not None:
905 ext = self._find_ext(filename)
908 ext = self._find_ext(filename)
906 elif url is not None:
909 elif url is not None:
907 ext = self._find_ext(url)
910 ext = self._find_ext(url)
908 elif data is None:
911 elif data is None:
909 raise ValueError("No image data found. Expecting filename, url, or data.")
912 raise ValueError("No image data found. Expecting filename, url, or data.")
910 elif isinstance(data, str) and (
913 elif isinstance(data, str) and (
911 data.startswith('http') or _safe_exists(data)
914 data.startswith('http') or _safe_exists(data)
912 ):
915 ):
913 ext = self._find_ext(data)
916 ext = self._find_ext(data)
914 else:
917 else:
915 ext = None
918 ext = None
916
919
917 if format is None:
920 if format is None:
918 if ext is not None:
921 if ext is not None:
919 if ext == u'jpg' or ext == u'jpeg':
922 if ext == u'jpg' or ext == u'jpeg':
920 format = self._FMT_JPEG
923 format = self._FMT_JPEG
921 elif ext == u'png':
924 elif ext == u'png':
922 format = self._FMT_PNG
925 format = self._FMT_PNG
923 elif ext == u'gif':
926 elif ext == u'gif':
924 format = self._FMT_GIF
927 format = self._FMT_GIF
925 else:
928 else:
926 format = ext.lower()
929 format = ext.lower()
927 elif isinstance(data, bytes):
930 elif isinstance(data, bytes):
928 # infer image type from image data header,
931 # infer image type from image data header,
929 # only if format has not been specified.
932 # only if format has not been specified.
930 if data[:2] == _JPEG:
933 if data[:2] == _JPEG:
931 format = self._FMT_JPEG
934 format = self._FMT_JPEG
932
935
933 # failed to detect format, default png
936 # failed to detect format, default png
934 if format is None:
937 if format is None:
935 format = self._FMT_PNG
938 format = self._FMT_PNG
936
939
937 if format.lower() == 'jpg':
940 if format.lower() == 'jpg':
938 # jpg->jpeg
941 # jpg->jpeg
939 format = self._FMT_JPEG
942 format = self._FMT_JPEG
940
943
941 self.format = format.lower()
944 self.format = format.lower()
942 self.embed = embed if embed is not None else (url is None)
945 self.embed = embed if embed is not None else (url is None)
943
946
944 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
947 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
945 raise ValueError("Cannot embed the '%s' image format" % (self.format))
948 raise ValueError("Cannot embed the '%s' image format" % (self.format))
946 if self.embed:
949 if self.embed:
947 self._mimetype = self._MIMETYPES.get(self.format)
950 self._mimetype = self._MIMETYPES.get(self.format)
948
951
949 self.width = width
952 self.width = width
950 self.height = height
953 self.height = height
951 self.retina = retina
954 self.retina = retina
952 self.unconfined = unconfined
955 self.unconfined = unconfined
953 self.alt = alt
956 self.alt = alt
954 super(Image, self).__init__(data=data, url=url, filename=filename,
957 super(Image, self).__init__(data=data, url=url, filename=filename,
955 metadata=metadata)
958 metadata=metadata)
956
959
957 if self.width is None and self.metadata.get('width', {}):
960 if self.width is None and self.metadata.get('width', {}):
958 self.width = metadata['width']
961 self.width = metadata['width']
959
962
960 if self.height is None and self.metadata.get('height', {}):
963 if self.height is None and self.metadata.get('height', {}):
961 self.height = metadata['height']
964 self.height = metadata['height']
962
965
963 if self.alt is None and self.metadata.get("alt", {}):
966 if self.alt is None and self.metadata.get("alt", {}):
964 self.alt = metadata["alt"]
967 self.alt = metadata["alt"]
965
968
966 if retina:
969 if retina:
967 self._retina_shape()
970 self._retina_shape()
968
971
969
972
970 def _retina_shape(self):
973 def _retina_shape(self):
971 """load pixel-doubled width and height from image data"""
974 """load pixel-doubled width and height from image data"""
972 if not self.embed:
975 if not self.embed:
973 return
976 return
974 if self.format == self._FMT_PNG:
977 if self.format == self._FMT_PNG:
975 w, h = _pngxy(self.data)
978 w, h = _pngxy(self.data)
976 elif self.format == self._FMT_JPEG:
979 elif self.format == self._FMT_JPEG:
977 w, h = _jpegxy(self.data)
980 w, h = _jpegxy(self.data)
978 elif self.format == self._FMT_GIF:
981 elif self.format == self._FMT_GIF:
979 w, h = _gifxy(self.data)
982 w, h = _gifxy(self.data)
980 else:
983 else:
981 # retina only supports png
984 # retina only supports png
982 return
985 return
983 self.width = w // 2
986 self.width = w // 2
984 self.height = h // 2
987 self.height = h // 2
985
988
986 def reload(self):
989 def reload(self):
987 """Reload the raw data from file or URL."""
990 """Reload the raw data from file or URL."""
988 if self.embed:
991 if self.embed:
989 super(Image,self).reload()
992 super(Image,self).reload()
990 if self.retina:
993 if self.retina:
991 self._retina_shape()
994 self._retina_shape()
992
995
993 def _repr_html_(self):
996 def _repr_html_(self):
994 if not self.embed:
997 if not self.embed:
995 width = height = klass = alt = ""
998 width = height = klass = alt = ""
996 if self.width:
999 if self.width:
997 width = ' width="%d"' % self.width
1000 width = ' width="%d"' % self.width
998 if self.height:
1001 if self.height:
999 height = ' height="%d"' % self.height
1002 height = ' height="%d"' % self.height
1000 if self.unconfined:
1003 if self.unconfined:
1001 klass = ' class="unconfined"'
1004 klass = ' class="unconfined"'
1002 if self.alt:
1005 if self.alt:
1003 alt = ' alt="%s"' % html.escape(self.alt)
1006 alt = ' alt="%s"' % html.escape(self.alt)
1004 return '<img src="{url}"{width}{height}{klass}{alt}/>'.format(
1007 return '<img src="{url}"{width}{height}{klass}{alt}/>'.format(
1005 url=self.url,
1008 url=self.url,
1006 width=width,
1009 width=width,
1007 height=height,
1010 height=height,
1008 klass=klass,
1011 klass=klass,
1009 alt=alt,
1012 alt=alt,
1010 )
1013 )
1011
1014
1012 def _repr_mimebundle_(self, include=None, exclude=None):
1015 def _repr_mimebundle_(self, include=None, exclude=None):
1013 """Return the image as a mimebundle
1016 """Return the image as a mimebundle
1014
1017
1015 Any new mimetype support should be implemented here.
1018 Any new mimetype support should be implemented here.
1016 """
1019 """
1017 if self.embed:
1020 if self.embed:
1018 mimetype = self._mimetype
1021 mimetype = self._mimetype
1019 data, metadata = self._data_and_metadata(always_both=True)
1022 data, metadata = self._data_and_metadata(always_both=True)
1020 if metadata:
1023 if metadata:
1021 metadata = {mimetype: metadata}
1024 metadata = {mimetype: metadata}
1022 return {mimetype: data}, metadata
1025 return {mimetype: data}, metadata
1023 else:
1026 else:
1024 return {'text/html': self._repr_html_()}
1027 return {'text/html': self._repr_html_()}
1025
1028
1026 def _data_and_metadata(self, always_both=False):
1029 def _data_and_metadata(self, always_both=False):
1027 """shortcut for returning metadata with shape information, if defined"""
1030 """shortcut for returning metadata with shape information, if defined"""
1028 try:
1031 try:
1029 b64_data = b2a_base64(self.data).decode('ascii')
1032 b64_data = b2a_base64(self.data).decode('ascii')
1030 except TypeError as e:
1033 except TypeError as e:
1031 raise FileNotFoundError(
1034 raise FileNotFoundError(
1032 "No such file or directory: '%s'" % (self.data)) from e
1035 "No such file or directory: '%s'" % (self.data)) from e
1033 md = {}
1036 md = {}
1034 if self.metadata:
1037 if self.metadata:
1035 md.update(self.metadata)
1038 md.update(self.metadata)
1036 if self.width:
1039 if self.width:
1037 md['width'] = self.width
1040 md['width'] = self.width
1038 if self.height:
1041 if self.height:
1039 md['height'] = self.height
1042 md['height'] = self.height
1040 if self.unconfined:
1043 if self.unconfined:
1041 md['unconfined'] = self.unconfined
1044 md['unconfined'] = self.unconfined
1042 if self.alt:
1045 if self.alt:
1043 md["alt"] = self.alt
1046 md["alt"] = self.alt
1044 if md or always_both:
1047 if md or always_both:
1045 return b64_data, md
1048 return b64_data, md
1046 else:
1049 else:
1047 return b64_data
1050 return b64_data
1048
1051
1049 def _repr_png_(self):
1052 def _repr_png_(self):
1050 if self.embed and self.format == self._FMT_PNG:
1053 if self.embed and self.format == self._FMT_PNG:
1051 return self._data_and_metadata()
1054 return self._data_and_metadata()
1052
1055
1053 def _repr_jpeg_(self):
1056 def _repr_jpeg_(self):
1054 if self.embed and self.format == self._FMT_JPEG:
1057 if self.embed and self.format == self._FMT_JPEG:
1055 return self._data_and_metadata()
1058 return self._data_and_metadata()
1056
1059
1057 def _find_ext(self, s):
1060 def _find_ext(self, s):
1058 base, ext = splitext(s)
1061 base, ext = splitext(s)
1059
1062
1060 if not ext:
1063 if not ext:
1061 return base
1064 return base
1062
1065
1063 # `splitext` includes leading period, so we skip it
1066 # `splitext` includes leading period, so we skip it
1064 return ext[1:].lower()
1067 return ext[1:].lower()
1065
1068
1066
1069
1067 class Video(DisplayObject):
1070 class Video(DisplayObject):
1068
1071
1069 def __init__(self, data=None, url=None, filename=None, embed=False,
1072 def __init__(self, data=None, url=None, filename=None, embed=False,
1070 mimetype=None, width=None, height=None, html_attributes="controls"):
1073 mimetype=None, width=None, height=None, html_attributes="controls"):
1071 """Create a video object given raw data or an URL.
1074 """Create a video object given raw data or an URL.
1072
1075
1073 When this object is returned by an input cell or passed to the
1076 When this object is returned by an input cell or passed to the
1074 display function, it will result in the video being displayed
1077 display function, it will result in the video being displayed
1075 in the frontend.
1078 in the frontend.
1076
1079
1077 Parameters
1080 Parameters
1078 ----------
1081 ----------
1079 data : unicode, str or bytes
1082 data : unicode, str or bytes
1080 The raw video data or a URL or filename to load the data from.
1083 The raw video data or a URL or filename to load the data from.
1081 Raw data will require passing ``embed=True``.
1084 Raw data will require passing ``embed=True``.
1082
1085
1083 url : unicode
1086 url : unicode
1084 A URL for the video. If you specify ``url=``,
1087 A URL for the video. If you specify ``url=``,
1085 the image data will not be embedded.
1088 the image data will not be embedded.
1086
1089
1087 filename : unicode
1090 filename : unicode
1088 Path to a local file containing the video.
1091 Path to a local file containing the video.
1089 Will be interpreted as a local URL unless ``embed=True``.
1092 Will be interpreted as a local URL unless ``embed=True``.
1090
1093
1091 embed : bool
1094 embed : bool
1092 Should the video be embedded using a data URI (True) or be
1095 Should the video be embedded using a data URI (True) or be
1093 loaded using a <video> tag (False).
1096 loaded using a <video> tag (False).
1094
1097
1095 Since videos are large, embedding them should be avoided, if possible.
1098 Since videos are large, embedding them should be avoided, if possible.
1096 You must confirm embedding as your intention by passing ``embed=True``.
1099 You must confirm embedding as your intention by passing ``embed=True``.
1097
1100
1098 Local files can be displayed with URLs without embedding the content, via::
1101 Local files can be displayed with URLs without embedding the content, via::
1099
1102
1100 Video('./video.mp4')
1103 Video('./video.mp4')
1101
1104
1102 mimetype : unicode
1105 mimetype : unicode
1103 Specify the mimetype for embedded videos.
1106 Specify the mimetype for embedded videos.
1104 Default will be guessed from file extension, if available.
1107 Default will be guessed from file extension, if available.
1105
1108
1106 width : int
1109 width : int
1107 Width in pixels to which to constrain the video in HTML.
1110 Width in pixels to which to constrain the video in HTML.
1108 If not supplied, defaults to the width of the video.
1111 If not supplied, defaults to the width of the video.
1109
1112
1110 height : int
1113 height : int
1111 Height in pixels to which to constrain the video in html.
1114 Height in pixels to which to constrain the video in html.
1112 If not supplied, defaults to the height of the video.
1115 If not supplied, defaults to the height of the video.
1113
1116
1114 html_attributes : str
1117 html_attributes : str
1115 Attributes for the HTML ``<video>`` block.
1118 Attributes for the HTML ``<video>`` block.
1116 Default: ``"controls"`` to get video controls.
1119 Default: ``"controls"`` to get video controls.
1117 Other examples: ``"controls muted"`` for muted video with controls,
1120 Other examples: ``"controls muted"`` for muted video with controls,
1118 ``"loop autoplay"`` for looping autoplaying video without controls.
1121 ``"loop autoplay"`` for looping autoplaying video without controls.
1119
1122
1120 Examples
1123 Examples
1121 --------
1124 --------
1122 ::
1125 ::
1123
1126
1124 Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4')
1127 Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4')
1125 Video('path/to/video.mp4')
1128 Video('path/to/video.mp4')
1126 Video('path/to/video.mp4', embed=True)
1129 Video('path/to/video.mp4', embed=True)
1127 Video('path/to/video.mp4', embed=True, html_attributes="controls muted autoplay")
1130 Video('path/to/video.mp4', embed=True, html_attributes="controls muted autoplay")
1128 Video(b'raw-videodata', embed=True)
1131 Video(b'raw-videodata', embed=True)
1129 """
1132 """
1130 if isinstance(data, (Path, PurePath)):
1133 if isinstance(data, (Path, PurePath)):
1131 data = str(data)
1134 data = str(data)
1132
1135
1133 if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')):
1136 if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')):
1134 url = data
1137 url = data
1135 data = None
1138 data = None
1136 elif data is not None and os.path.exists(data):
1139 elif data is not None and os.path.exists(data):
1137 filename = data
1140 filename = data
1138 data = None
1141 data = None
1139
1142
1140 if data and not embed:
1143 if data and not embed:
1141 msg = ''.join([
1144 msg = ''.join([
1142 "To embed videos, you must pass embed=True ",
1145 "To embed videos, you must pass embed=True ",
1143 "(this may make your notebook files huge)\n",
1146 "(this may make your notebook files huge)\n",
1144 "Consider passing Video(url='...')",
1147 "Consider passing Video(url='...')",
1145 ])
1148 ])
1146 raise ValueError(msg)
1149 raise ValueError(msg)
1147
1150
1148 self.mimetype = mimetype
1151 self.mimetype = mimetype
1149 self.embed = embed
1152 self.embed = embed
1150 self.width = width
1153 self.width = width
1151 self.height = height
1154 self.height = height
1152 self.html_attributes = html_attributes
1155 self.html_attributes = html_attributes
1153 super(Video, self).__init__(data=data, url=url, filename=filename)
1156 super(Video, self).__init__(data=data, url=url, filename=filename)
1154
1157
1155 def _repr_html_(self):
1158 def _repr_html_(self):
1156 width = height = ''
1159 width = height = ''
1157 if self.width:
1160 if self.width:
1158 width = ' width="%d"' % self.width
1161 width = ' width="%d"' % self.width
1159 if self.height:
1162 if self.height:
1160 height = ' height="%d"' % self.height
1163 height = ' height="%d"' % self.height
1161
1164
1162 # External URLs and potentially local files are not embedded into the
1165 # External URLs and potentially local files are not embedded into the
1163 # notebook output.
1166 # notebook output.
1164 if not self.embed:
1167 if not self.embed:
1165 url = self.url if self.url is not None else self.filename
1168 url = self.url if self.url is not None else self.filename
1166 output = """<video src="{0}" {1} {2} {3}>
1169 output = """<video src="{0}" {1} {2} {3}>
1167 Your browser does not support the <code>video</code> element.
1170 Your browser does not support the <code>video</code> element.
1168 </video>""".format(url, self.html_attributes, width, height)
1171 </video>""".format(url, self.html_attributes, width, height)
1169 return output
1172 return output
1170
1173
1171 # Embedded videos are base64-encoded.
1174 # Embedded videos are base64-encoded.
1172 mimetype = self.mimetype
1175 mimetype = self.mimetype
1173 if self.filename is not None:
1176 if self.filename is not None:
1174 if not mimetype:
1177 if not mimetype:
1175 mimetype, _ = mimetypes.guess_type(self.filename)
1178 mimetype, _ = mimetypes.guess_type(self.filename)
1176
1179
1177 with open(self.filename, 'rb') as f:
1180 with open(self.filename, 'rb') as f:
1178 video = f.read()
1181 video = f.read()
1179 else:
1182 else:
1180 video = self.data
1183 video = self.data
1181 if isinstance(video, str):
1184 if isinstance(video, str):
1182 # unicode input is already b64-encoded
1185 # unicode input is already b64-encoded
1183 b64_video = video
1186 b64_video = video
1184 else:
1187 else:
1185 b64_video = b2a_base64(video).decode('ascii').rstrip()
1188 b64_video = b2a_base64(video).decode('ascii').rstrip()
1186
1189
1187 output = """<video {0} {1} {2}>
1190 output = """<video {0} {1} {2}>
1188 <source src="data:{3};base64,{4}" type="{3}">
1191 <source src="data:{3};base64,{4}" type="{3}">
1189 Your browser does not support the video tag.
1192 Your browser does not support the video tag.
1190 </video>""".format(self.html_attributes, width, height, mimetype, b64_video)
1193 </video>""".format(self.html_attributes, width, height, mimetype, b64_video)
1191 return output
1194 return output
1192
1195
1193 def reload(self):
1196 def reload(self):
1194 # TODO
1197 # TODO
1195 pass
1198 pass
1196
1199
1197
1200
1198 @skip_doctest
1201 @skip_doctest
1199 def set_matplotlib_formats(*formats, **kwargs):
1202 def set_matplotlib_formats(*formats, **kwargs):
1200 """
1203 """
1201 .. deprecated:: 7.23
1204 .. deprecated:: 7.23
1202
1205
1203 use `matplotlib_inline.backend_inline.set_matplotlib_formats()`
1206 use `matplotlib_inline.backend_inline.set_matplotlib_formats()`
1204
1207
1205 Select figure formats for the inline backend. Optionally pass quality for JPEG.
1208 Select figure formats for the inline backend. Optionally pass quality for JPEG.
1206
1209
1207 For example, this enables PNG and JPEG output with a JPEG quality of 90%::
1210 For example, this enables PNG and JPEG output with a JPEG quality of 90%::
1208
1211
1209 In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
1212 In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
1210
1213
1211 To set this in your config files use the following::
1214 To set this in your config files use the following::
1212
1215
1213 c.InlineBackend.figure_formats = {'png', 'jpeg'}
1216 c.InlineBackend.figure_formats = {'png', 'jpeg'}
1214 c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
1217 c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
1215
1218
1216 Parameters
1219 Parameters
1217 ----------
1220 ----------
1218 *formats : strs
1221 *formats : strs
1219 One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
1222 One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
1220 **kwargs
1223 **kwargs
1221 Keyword args will be relayed to ``figure.canvas.print_figure``.
1224 Keyword args will be relayed to ``figure.canvas.print_figure``.
1222 """
1225 """
1223 warnings.warn(
1226 warnings.warn(
1224 "`set_matplotlib_formats` is deprecated since IPython 7.23, directly "
1227 "`set_matplotlib_formats` is deprecated since IPython 7.23, directly "
1225 "use `matplotlib_inline.backend_inline.set_matplotlib_formats()`",
1228 "use `matplotlib_inline.backend_inline.set_matplotlib_formats()`",
1226 DeprecationWarning,
1229 DeprecationWarning,
1227 stacklevel=2,
1230 stacklevel=2,
1228 )
1231 )
1229
1232
1230 from matplotlib_inline.backend_inline import (
1233 from matplotlib_inline.backend_inline import (
1231 set_matplotlib_formats as set_matplotlib_formats_orig,
1234 set_matplotlib_formats as set_matplotlib_formats_orig,
1232 )
1235 )
1233
1236
1234 set_matplotlib_formats_orig(*formats, **kwargs)
1237 set_matplotlib_formats_orig(*formats, **kwargs)
1235
1238
1236 @skip_doctest
1239 @skip_doctest
1237 def set_matplotlib_close(close=True):
1240 def set_matplotlib_close(close=True):
1238 """
1241 """
1239 .. deprecated:: 7.23
1242 .. deprecated:: 7.23
1240
1243
1241 use `matplotlib_inline.backend_inline.set_matplotlib_close()`
1244 use `matplotlib_inline.backend_inline.set_matplotlib_close()`
1242
1245
1243 Set whether the inline backend closes all figures automatically or not.
1246 Set whether the inline backend closes all figures automatically or not.
1244
1247
1245 By default, the inline backend used in the IPython Notebook will close all
1248 By default, the inline backend used in the IPython Notebook will close all
1246 matplotlib figures automatically after each cell is run. This means that
1249 matplotlib figures automatically after each cell is run. This means that
1247 plots in different cells won't interfere. Sometimes, you may want to make
1250 plots in different cells won't interfere. Sometimes, you may want to make
1248 a plot in one cell and then refine it in later cells. This can be accomplished
1251 a plot in one cell and then refine it in later cells. This can be accomplished
1249 by::
1252 by::
1250
1253
1251 In [1]: set_matplotlib_close(False)
1254 In [1]: set_matplotlib_close(False)
1252
1255
1253 To set this in your config files use the following::
1256 To set this in your config files use the following::
1254
1257
1255 c.InlineBackend.close_figures = False
1258 c.InlineBackend.close_figures = False
1256
1259
1257 Parameters
1260 Parameters
1258 ----------
1261 ----------
1259 close : bool
1262 close : bool
1260 Should all matplotlib figures be automatically closed after each cell is
1263 Should all matplotlib figures be automatically closed after each cell is
1261 run?
1264 run?
1262 """
1265 """
1263 warnings.warn(
1266 warnings.warn(
1264 "`set_matplotlib_close` is deprecated since IPython 7.23, directly "
1267 "`set_matplotlib_close` is deprecated since IPython 7.23, directly "
1265 "use `matplotlib_inline.backend_inline.set_matplotlib_close()`",
1268 "use `matplotlib_inline.backend_inline.set_matplotlib_close()`",
1266 DeprecationWarning,
1269 DeprecationWarning,
1267 stacklevel=2,
1270 stacklevel=2,
1268 )
1271 )
1269
1272
1270 from matplotlib_inline.backend_inline import (
1273 from matplotlib_inline.backend_inline import (
1271 set_matplotlib_close as set_matplotlib_close_orig,
1274 set_matplotlib_close as set_matplotlib_close_orig,
1272 )
1275 )
1273
1276
1274 set_matplotlib_close_orig(close)
1277 set_matplotlib_close_orig(close)
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,755 +1,755 b''
1 """Implementation of code management magic functions.
1 """Implementation of code management magic functions.
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 # Stdlib
15 # Stdlib
16 import inspect
16 import inspect
17 import io
17 import io
18 import os
18 import os
19 import re
19 import re
20 import sys
20 import sys
21 import ast
21 import ast
22 from itertools import chain
22 from itertools import chain
23 from urllib.request import Request, urlopen
23 from urllib.request import Request, urlopen
24 from urllib.parse import urlencode
24 from urllib.parse import urlencode
25 from pathlib import Path
25 from pathlib import Path
26
26
27 # Our own packages
27 # Our own packages
28 from IPython.core.error import TryNext, StdinNotImplementedError, UsageError
28 from IPython.core.error import TryNext, StdinNotImplementedError, UsageError
29 from IPython.core.macro import Macro
29 from IPython.core.macro import Macro
30 from IPython.core.magic import Magics, magics_class, line_magic
30 from IPython.core.magic import Magics, magics_class, line_magic
31 from IPython.core.oinspect import find_file, find_source_lines
31 from IPython.core.oinspect import find_file, find_source_lines
32 from IPython.core.release import version
32 from IPython.core.release import version
33 from IPython.testing.skipdoctest import skip_doctest
33 from IPython.testing.skipdoctest import skip_doctest
34 from IPython.utils.contexts import preserve_keys
34 from IPython.utils.contexts import preserve_keys
35 from IPython.utils.path import get_py_filename
35 from IPython.utils.path import get_py_filename
36 from warnings import warn
36 from warnings import warn
37 from logging import error
37 from logging import error
38 from IPython.utils.text import get_text_list
38 from IPython.utils.text import get_text_list
39
39
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41 # Magic implementation classes
41 # Magic implementation classes
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44 # Used for exception handling in magic_edit
44 # Used for exception handling in magic_edit
45 class MacroToEdit(ValueError): pass
45 class MacroToEdit(ValueError): pass
46
46
47 ipython_input_pat = re.compile(r"<ipython\-input\-(\d+)-[a-z\d]+>$")
47 ipython_input_pat = re.compile(r"<ipython\-input\-(\d+)-[a-z\d]+>$")
48
48
49 # To match, e.g. 8-10 1:5 :10 3-
49 # To match, e.g. 8-10 1:5 :10 3-
50 range_re = re.compile(r"""
50 range_re = re.compile(r"""
51 (?P<start>\d+)?
51 (?P<start>\d+)?
52 ((?P<sep>[\-:])
52 ((?P<sep>[\-:])
53 (?P<end>\d+)?)?
53 (?P<end>\d+)?)?
54 $""", re.VERBOSE)
54 $""", re.VERBOSE)
55
55
56
56
57 def extract_code_ranges(ranges_str):
57 def extract_code_ranges(ranges_str):
58 """Turn a string of range for %%load into 2-tuples of (start, stop)
58 """Turn a string of range for %%load into 2-tuples of (start, stop)
59 ready to use as a slice of the content split by lines.
59 ready to use as a slice of the content split by lines.
60
60
61 Examples
61 Examples
62 --------
62 --------
63 list(extract_input_ranges("5-10 2"))
63 list(extract_input_ranges("5-10 2"))
64 [(4, 10), (1, 2)]
64 [(4, 10), (1, 2)]
65 """
65 """
66 for range_str in ranges_str.split():
66 for range_str in ranges_str.split():
67 rmatch = range_re.match(range_str)
67 rmatch = range_re.match(range_str)
68 if not rmatch:
68 if not rmatch:
69 continue
69 continue
70 sep = rmatch.group("sep")
70 sep = rmatch.group("sep")
71 start = rmatch.group("start")
71 start = rmatch.group("start")
72 end = rmatch.group("end")
72 end = rmatch.group("end")
73
73
74 if sep == '-':
74 if sep == '-':
75 start = int(start) - 1 if start else None
75 start = int(start) - 1 if start else None
76 end = int(end) if end else None
76 end = int(end) if end else None
77 elif sep == ':':
77 elif sep == ':':
78 start = int(start) - 1 if start else None
78 start = int(start) - 1 if start else None
79 end = int(end) - 1 if end else None
79 end = int(end) - 1 if end else None
80 else:
80 else:
81 end = int(start)
81 end = int(start)
82 start = int(start) - 1
82 start = int(start) - 1
83 yield (start, end)
83 yield (start, end)
84
84
85
85
86 def extract_symbols(code, symbols):
86 def extract_symbols(code, symbols):
87 """
87 """
88 Return a tuple (blocks, not_found)
88 Return a tuple (blocks, not_found)
89 where ``blocks`` is a list of code fragments
89 where ``blocks`` is a list of code fragments
90 for each symbol parsed from code, and ``not_found`` are
90 for each symbol parsed from code, and ``not_found`` are
91 symbols not found in the code.
91 symbols not found in the code.
92
92
93 For example::
93 For example::
94
94
95 In [1]: code = '''a = 10
95 In [1]: code = '''a = 10
96 ...: def b(): return 42
96 ...: def b(): return 42
97 ...: class A: pass'''
97 ...: class A: pass'''
98
98
99 In [2]: extract_symbols(code, 'A,b,z')
99 In [2]: extract_symbols(code, 'A,b,z')
100 Out[2]: (['class A: pass\\n', 'def b(): return 42\\n'], ['z'])
100 Out[2]: (['class A: pass\\n', 'def b(): return 42\\n'], ['z'])
101 """
101 """
102 symbols = symbols.split(',')
102 symbols = symbols.split(',')
103
103
104 # this will raise SyntaxError if code isn't valid Python
104 # this will raise SyntaxError if code isn't valid Python
105 py_code = ast.parse(code)
105 py_code = ast.parse(code)
106
106
107 marks = [(getattr(s, 'name', None), s.lineno) for s in py_code.body]
107 marks = [(getattr(s, 'name', None), s.lineno) for s in py_code.body]
108 code = code.split('\n')
108 code = code.split('\n')
109
109
110 symbols_lines = {}
110 symbols_lines = {}
111
111
112 # we already know the start_lineno of each symbol (marks).
112 # we already know the start_lineno of each symbol (marks).
113 # To find each end_lineno, we traverse in reverse order until each
113 # To find each end_lineno, we traverse in reverse order until each
114 # non-blank line
114 # non-blank line
115 end = len(code)
115 end = len(code)
116 for name, start in reversed(marks):
116 for name, start in reversed(marks):
117 while not code[end - 1].strip():
117 while not code[end - 1].strip():
118 end -= 1
118 end -= 1
119 if name:
119 if name:
120 symbols_lines[name] = (start - 1, end)
120 symbols_lines[name] = (start - 1, end)
121 end = start - 1
121 end = start - 1
122
122
123 # Now symbols_lines is a map
123 # Now symbols_lines is a map
124 # {'symbol_name': (start_lineno, end_lineno), ...}
124 # {'symbol_name': (start_lineno, end_lineno), ...}
125
125
126 # fill a list with chunks of codes for each requested symbol
126 # fill a list with chunks of codes for each requested symbol
127 blocks = []
127 blocks = []
128 not_found = []
128 not_found = []
129 for symbol in symbols:
129 for symbol in symbols:
130 if symbol in symbols_lines:
130 if symbol in symbols_lines:
131 start, end = symbols_lines[symbol]
131 start, end = symbols_lines[symbol]
132 blocks.append('\n'.join(code[start:end]) + '\n')
132 blocks.append('\n'.join(code[start:end]) + '\n')
133 else:
133 else:
134 not_found.append(symbol)
134 not_found.append(symbol)
135
135
136 return blocks, not_found
136 return blocks, not_found
137
137
138 def strip_initial_indent(lines):
138 def strip_initial_indent(lines):
139 """For %load, strip indent from lines until finding an unindented line.
139 """For %load, strip indent from lines until finding an unindented line.
140
140
141 https://github.com/ipython/ipython/issues/9775
141 https://github.com/ipython/ipython/issues/9775
142 """
142 """
143 indent_re = re.compile(r'\s+')
143 indent_re = re.compile(r'\s+')
144
144
145 it = iter(lines)
145 it = iter(lines)
146 first_line = next(it)
146 first_line = next(it)
147 indent_match = indent_re.match(first_line)
147 indent_match = indent_re.match(first_line)
148
148
149 if indent_match:
149 if indent_match:
150 # First line was indented
150 # First line was indented
151 indent = indent_match.group()
151 indent = indent_match.group()
152 yield first_line[len(indent):]
152 yield first_line[len(indent):]
153
153
154 for line in it:
154 for line in it:
155 if line.startswith(indent):
155 if line.startswith(indent):
156 yield line[len(indent):]
156 yield line[len(indent):]
157 else:
157 else:
158 # Less indented than the first line - stop dedenting
158 # Less indented than the first line - stop dedenting
159 yield line
159 yield line
160 break
160 break
161 else:
161 else:
162 yield first_line
162 yield first_line
163
163
164 # Pass the remaining lines through without dedenting
164 # Pass the remaining lines through without dedenting
165 for line in it:
165 for line in it:
166 yield line
166 yield line
167
167
168
168
169 class InteractivelyDefined(Exception):
169 class InteractivelyDefined(Exception):
170 """Exception for interactively defined variable in magic_edit"""
170 """Exception for interactively defined variable in magic_edit"""
171 def __init__(self, index):
171 def __init__(self, index):
172 self.index = index
172 self.index = index
173
173
174
174
175 @magics_class
175 @magics_class
176 class CodeMagics(Magics):
176 class CodeMagics(Magics):
177 """Magics related to code management (loading, saving, editing, ...)."""
177 """Magics related to code management (loading, saving, editing, ...)."""
178
178
179 def __init__(self, *args, **kwargs):
179 def __init__(self, *args, **kwargs):
180 self._knowntemps = set()
180 self._knowntemps = set()
181 super(CodeMagics, self).__init__(*args, **kwargs)
181 super(CodeMagics, self).__init__(*args, **kwargs)
182
182
183 @line_magic
183 @line_magic
184 def save(self, parameter_s=''):
184 def save(self, parameter_s=''):
185 """Save a set of lines or a macro to a given filename.
185 """Save a set of lines or a macro to a given filename.
186
186
187 Usage:\\
187 Usage:\\
188 %save [options] filename [history]
188 %save [options] filename [history]
189
189
190 Options:
190 Options:
191
191
192 -r: use 'raw' input. By default, the 'processed' history is used,
192 -r: use 'raw' input. By default, the 'processed' history is used,
193 so that magics are loaded in their transformed version to valid
193 so that magics are loaded in their transformed version to valid
194 Python. If this option is given, the raw input as typed as the
194 Python. If this option is given, the raw input as typed as the
195 command line is used instead.
195 command line is used instead.
196
196
197 -f: force overwrite. If file exists, %save will prompt for overwrite
197 -f: force overwrite. If file exists, %save will prompt for overwrite
198 unless -f is given.
198 unless -f is given.
199
199
200 -a: append to the file instead of overwriting it.
200 -a: append to the file instead of overwriting it.
201
201
202 The history argument uses the same syntax as %history for input ranges,
202 The history argument uses the same syntax as %history for input ranges,
203 then saves the lines to the filename you specify.
203 then saves the lines to the filename you specify.
204
204
205 If no ranges are specified, saves history of the current session up to
205 If no ranges are specified, saves history of the current session up to
206 this point.
206 this point.
207
207
208 It adds a '.py' extension to the file if you don't do so yourself, and
208 It adds a '.py' extension to the file if you don't do so yourself, and
209 it asks for confirmation before overwriting existing files.
209 it asks for confirmation before overwriting existing files.
210
210
211 If `-r` option is used, the default extension is `.ipy`.
211 If `-r` option is used, the default extension is `.ipy`.
212 """
212 """
213
213
214 opts,args = self.parse_options(parameter_s,'fra',mode='list')
214 opts,args = self.parse_options(parameter_s,'fra',mode='list')
215 if not args:
215 if not args:
216 raise UsageError('Missing filename.')
216 raise UsageError('Missing filename.')
217 raw = 'r' in opts
217 raw = 'r' in opts
218 force = 'f' in opts
218 force = 'f' in opts
219 append = 'a' in opts
219 append = 'a' in opts
220 mode = 'a' if append else 'w'
220 mode = 'a' if append else 'w'
221 ext = '.ipy' if raw else '.py'
221 ext = '.ipy' if raw else '.py'
222 fname, codefrom = args[0], " ".join(args[1:])
222 fname, codefrom = args[0], " ".join(args[1:])
223 if not fname.endswith(('.py','.ipy')):
223 if not fname.endswith(('.py','.ipy')):
224 fname += ext
224 fname += ext
225 fname = os.path.expanduser(fname)
225 fname = os.path.expanduser(fname)
226 file_exists = os.path.isfile(fname)
226 file_exists = os.path.isfile(fname)
227 if file_exists and not force and not append:
227 if file_exists and not force and not append:
228 try:
228 try:
229 overwrite = self.shell.ask_yes_no('File `%s` exists. Overwrite (y/[N])? ' % fname, default='n')
229 overwrite = self.shell.ask_yes_no('File `%s` exists. Overwrite (y/[N])? ' % fname, default='n')
230 except StdinNotImplementedError:
230 except StdinNotImplementedError:
231 print("File `%s` exists. Use `%%save -f %s` to force overwrite" % (fname, parameter_s))
231 print("File `%s` exists. Use `%%save -f %s` to force overwrite" % (fname, parameter_s))
232 return
232 return
233 if not overwrite :
233 if not overwrite :
234 print('Operation cancelled.')
234 print('Operation cancelled.')
235 return
235 return
236 try:
236 try:
237 cmds = self.shell.find_user_code(codefrom,raw)
237 cmds = self.shell.find_user_code(codefrom,raw)
238 except (TypeError, ValueError) as e:
238 except (TypeError, ValueError) as e:
239 print(e.args[0])
239 print(e.args[0])
240 return
240 return
241 with io.open(fname, mode, encoding="utf-8") as f:
241 with io.open(fname, mode, encoding="utf-8") as f:
242 if not file_exists or not append:
242 if not file_exists or not append:
243 f.write("# coding: utf-8\n")
243 f.write("# coding: utf-8\n")
244 f.write(cmds)
244 f.write(cmds)
245 # make sure we end on a newline
245 # make sure we end on a newline
246 if not cmds.endswith('\n'):
246 if not cmds.endswith('\n'):
247 f.write('\n')
247 f.write('\n')
248 print('The following commands were written to file `%s`:' % fname)
248 print('The following commands were written to file `%s`:' % fname)
249 print(cmds)
249 print(cmds)
250
250
251 @line_magic
251 @line_magic
252 def pastebin(self, parameter_s=''):
252 def pastebin(self, parameter_s=''):
253 """Upload code to dpaste.com, returning the URL.
253 """Upload code to dpaste.com, returning the URL.
254
254
255 Usage:\\
255 Usage:\\
256 %pastebin [-d "Custom description"][-e 24] 1-7
256 %pastebin [-d "Custom description"][-e 24] 1-7
257
257
258 The argument can be an input history range, a filename, or the name of a
258 The argument can be an input history range, a filename, or the name of a
259 string or macro.
259 string or macro.
260
260
261 If no arguments are given, uploads the history of this session up to
261 If no arguments are given, uploads the history of this session up to
262 this point.
262 this point.
263
263
264 Options:
264 Options:
265
265
266 -d: Pass a custom description. The default will say
266 -d: Pass a custom description. The default will say
267 "Pasted from IPython".
267 "Pasted from IPython".
268 -e: Pass number of days for the link to be expired.
268 -e: Pass number of days for the link to be expired.
269 The default will be 7 days.
269 The default will be 7 days.
270 """
270 """
271 opts, args = self.parse_options(parameter_s, "d:e:")
271 opts, args = self.parse_options(parameter_s, "d:e:")
272
272
273 try:
273 try:
274 code = self.shell.find_user_code(args)
274 code = self.shell.find_user_code(args)
275 except (ValueError, TypeError) as e:
275 except (ValueError, TypeError) as e:
276 print(e.args[0])
276 print(e.args[0])
277 return
277 return
278
278
279 expiry_days = 7
279 expiry_days = 7
280 try:
280 try:
281 expiry_days = int(opts.get("e", 7))
281 expiry_days = int(opts.get("e", 7))
282 except ValueError as e:
282 except ValueError as e:
283 print(e.args[0].capitalize())
283 print(e.args[0].capitalize())
284 return
284 return
285 if expiry_days < 1 or expiry_days > 365:
285 if expiry_days < 1 or expiry_days > 365:
286 print("Expiry days should be in range of 1 to 365")
286 print("Expiry days should be in range of 1 to 365")
287 return
287 return
288
288
289 post_data = urlencode(
289 post_data = urlencode(
290 {
290 {
291 "title": opts.get("d", "Pasted from IPython"),
291 "title": opts.get("d", "Pasted from IPython"),
292 "syntax": "python",
292 "syntax": "python",
293 "content": code,
293 "content": code,
294 "expiry_days": expiry_days,
294 "expiry_days": expiry_days,
295 }
295 }
296 ).encode("utf-8")
296 ).encode("utf-8")
297
297
298 request = Request(
298 request = Request(
299 "https://dpaste.com/api/v2/",
299 "https://dpaste.com/api/v2/",
300 headers={"User-Agent": "IPython v{}".format(version)},
300 headers={"User-Agent": "IPython v{}".format(version)},
301 )
301 )
302 response = urlopen(request, post_data)
302 response = urlopen(request, post_data)
303 return response.headers.get('Location')
303 return response.headers.get('Location')
304
304
305 @line_magic
305 @line_magic
306 def loadpy(self, arg_s):
306 def loadpy(self, arg_s):
307 """Alias of `%load`
307 """Alias of `%load`
308
308
309 `%loadpy` has gained some flexibility and dropped the requirement of a `.py`
309 `%loadpy` has gained some flexibility and dropped the requirement of a `.py`
310 extension. So it has been renamed simply into %load. You can look at
310 extension. So it has been renamed simply into %load. You can look at
311 `%load`'s docstring for more info.
311 `%load`'s docstring for more info.
312 """
312 """
313 self.load(arg_s)
313 self.load(arg_s)
314
314
315 @line_magic
315 @line_magic
316 def load(self, arg_s):
316 def load(self, arg_s):
317 """Load code into the current frontend.
317 """Load code into the current frontend.
318
318
319 Usage:\\
319 Usage:\\
320 %load [options] source
320 %load [options] source
321
321
322 where source can be a filename, URL, input history range, macro, or
322 where source can be a filename, URL, input history range, macro, or
323 element in the user namespace
323 element in the user namespace
324
324
325 If no arguments are given, loads the history of this session up to this
325 If no arguments are given, loads the history of this session up to this
326 point.
326 point.
327
327
328 Options:
328 Options:
329
329
330 -r <lines>: Specify lines or ranges of lines to load from the source.
330 -r <lines>: Specify lines or ranges of lines to load from the source.
331 Ranges could be specified as x-y (x..y) or in python-style x:y
331 Ranges could be specified as x-y (x..y) or in python-style x:y
332 (x..(y-1)). Both limits x and y can be left blank (meaning the
332 (x..(y-1)). Both limits x and y can be left blank (meaning the
333 beginning and end of the file, respectively).
333 beginning and end of the file, respectively).
334
334
335 -s <symbols>: Specify function or classes to load from python source.
335 -s <symbols>: Specify function or classes to load from python source.
336
336
337 -y : Don't ask confirmation for loading source above 200 000 characters.
337 -y : Don't ask confirmation for loading source above 200 000 characters.
338
338
339 -n : Include the user's namespace when searching for source code.
339 -n : Include the user's namespace when searching for source code.
340
340
341 This magic command can either take a local filename, a URL, an history
341 This magic command can either take a local filename, a URL, an history
342 range (see %history) or a macro as argument, it will prompt for
342 range (see %history) or a macro as argument, it will prompt for
343 confirmation before loading source with more than 200 000 characters, unless
343 confirmation before loading source with more than 200 000 characters, unless
344 -y flag is passed or if the frontend does not support raw_input::
344 -y flag is passed or if the frontend does not support raw_input::
345
345
346 %load
346 %load
347 %load myscript.py
347 %load myscript.py
348 %load 7-27
348 %load 7-27
349 %load myMacro
349 %load myMacro
350 %load http://www.example.com/myscript.py
350 %load http://www.example.com/myscript.py
351 %load -r 5-10 myscript.py
351 %load -r 5-10 myscript.py
352 %load -r 10-20,30,40: foo.py
352 %load -r 10-20,30,40: foo.py
353 %load -s MyClass,wonder_function myscript.py
353 %load -s MyClass,wonder_function myscript.py
354 %load -n MyClass
354 %load -n MyClass
355 %load -n my_module.wonder_function
355 %load -n my_module.wonder_function
356 """
356 """
357 opts,args = self.parse_options(arg_s,'yns:r:')
357 opts,args = self.parse_options(arg_s,'yns:r:')
358 search_ns = 'n' in opts
358 search_ns = 'n' in opts
359 contents = self.shell.find_user_code(args, search_ns=search_ns)
359 contents = self.shell.find_user_code(args, search_ns=search_ns)
360
360
361 if 's' in opts:
361 if 's' in opts:
362 try:
362 try:
363 blocks, not_found = extract_symbols(contents, opts['s'])
363 blocks, not_found = extract_symbols(contents, opts['s'])
364 except SyntaxError:
364 except SyntaxError:
365 # non python code
365 # non python code
366 error("Unable to parse the input as valid Python code")
366 error("Unable to parse the input as valid Python code")
367 return
367 return
368
368
369 if len(not_found) == 1:
369 if len(not_found) == 1:
370 warn('The symbol `%s` was not found' % not_found[0])
370 warn('The symbol `%s` was not found' % not_found[0])
371 elif len(not_found) > 1:
371 elif len(not_found) > 1:
372 warn('The symbols %s were not found' % get_text_list(not_found,
372 warn('The symbols %s were not found' % get_text_list(not_found,
373 wrap_item_with='`')
373 wrap_item_with='`')
374 )
374 )
375
375
376 contents = '\n'.join(blocks)
376 contents = '\n'.join(blocks)
377
377
378 if 'r' in opts:
378 if 'r' in opts:
379 ranges = opts['r'].replace(',', ' ')
379 ranges = opts['r'].replace(',', ' ')
380 lines = contents.split('\n')
380 lines = contents.split('\n')
381 slices = extract_code_ranges(ranges)
381 slices = extract_code_ranges(ranges)
382 contents = [lines[slice(*slc)] for slc in slices]
382 contents = [lines[slice(*slc)] for slc in slices]
383 contents = '\n'.join(strip_initial_indent(chain.from_iterable(contents)))
383 contents = '\n'.join(strip_initial_indent(chain.from_iterable(contents)))
384
384
385 l = len(contents)
385 l = len(contents)
386
386
387 # 200 000 is ~ 2500 full 80 character lines
387 # 200 000 is ~ 2500 full 80 character lines
388 # so in average, more than 5000 lines
388 # so in average, more than 5000 lines
389 if l > 200000 and 'y' not in opts:
389 if l > 200000 and 'y' not in opts:
390 try:
390 try:
391 ans = self.shell.ask_yes_no(("The text you're trying to load seems pretty big"\
391 ans = self.shell.ask_yes_no(("The text you're trying to load seems pretty big"\
392 " (%d characters). Continue (y/[N]) ?" % l), default='n' )
392 " (%d characters). Continue (y/[N]) ?" % l), default='n' )
393 except StdinNotImplementedError:
393 except StdinNotImplementedError:
394 #assume yes if raw input not implemented
394 #assume yes if raw input not implemented
395 ans = True
395 ans = True
396
396
397 if ans is False :
397 if ans is False :
398 print('Operation cancelled.')
398 print('Operation cancelled.')
399 return
399 return
400
400
401 contents = "# %load {}\n".format(arg_s) + contents
401 contents = "# %load {}\n".format(arg_s) + contents
402
402
403 self.shell.set_next_input(contents, replace=True)
403 self.shell.set_next_input(contents, replace=True)
404
404
405 @staticmethod
405 @staticmethod
406 def _find_edit_target(shell, args, opts, last_call):
406 def _find_edit_target(shell, args, opts, last_call):
407 """Utility method used by magic_edit to find what to edit."""
407 """Utility method used by magic_edit to find what to edit."""
408
408
409 def make_filename(arg):
409 def make_filename(arg):
410 "Make a filename from the given args"
410 "Make a filename from the given args"
411 try:
411 try:
412 filename = get_py_filename(arg)
412 filename = get_py_filename(arg)
413 except IOError:
413 except IOError:
414 # If it ends with .py but doesn't already exist, assume we want
414 # If it ends with .py but doesn't already exist, assume we want
415 # a new file.
415 # a new file.
416 if arg.endswith('.py'):
416 if arg.endswith('.py'):
417 filename = arg
417 filename = arg
418 else:
418 else:
419 filename = None
419 filename = None
420 return filename
420 return filename
421
421
422 # Set a few locals from the options for convenience:
422 # Set a few locals from the options for convenience:
423 opts_prev = 'p' in opts
423 opts_prev = 'p' in opts
424 opts_raw = 'r' in opts
424 opts_raw = 'r' in opts
425
425
426 # custom exceptions
426 # custom exceptions
427 class DataIsObject(Exception): pass
427 class DataIsObject(Exception): pass
428
428
429 # Default line number value
429 # Default line number value
430 lineno = opts.get('n',None)
430 lineno = opts.get('n',None)
431
431
432 if opts_prev:
432 if opts_prev:
433 args = '_%s' % last_call[0]
433 args = '_%s' % last_call[0]
434 if args not in shell.user_ns:
434 if args not in shell.user_ns:
435 args = last_call[1]
435 args = last_call[1]
436
436
437 # by default this is done with temp files, except when the given
437 # by default this is done with temp files, except when the given
438 # arg is a filename
438 # arg is a filename
439 use_temp = True
439 use_temp = True
440
440
441 data = ''
441 data = ''
442
442
443 # First, see if the arguments should be a filename.
443 # First, see if the arguments should be a filename.
444 filename = make_filename(args)
444 filename = make_filename(args)
445 if filename:
445 if filename:
446 use_temp = False
446 use_temp = False
447 elif args:
447 elif args:
448 # Mode where user specifies ranges of lines, like in %macro.
448 # Mode where user specifies ranges of lines, like in %macro.
449 data = shell.extract_input_lines(args, opts_raw)
449 data = shell.extract_input_lines(args, opts_raw)
450 if not data:
450 if not data:
451 try:
451 try:
452 # Load the parameter given as a variable. If not a string,
452 # Load the parameter given as a variable. If not a string,
453 # process it as an object instead (below)
453 # process it as an object instead (below)
454
454
455 #print '*** args',args,'type',type(args) # dbg
455 #print '*** args',args,'type',type(args) # dbg
456 data = eval(args, shell.user_ns)
456 data = eval(args, shell.user_ns)
457 if not isinstance(data, str):
457 if not isinstance(data, str):
458 raise DataIsObject
458 raise DataIsObject
459
459
460 except (NameError,SyntaxError):
460 except (NameError,SyntaxError):
461 # given argument is not a variable, try as a filename
461 # given argument is not a variable, try as a filename
462 filename = make_filename(args)
462 filename = make_filename(args)
463 if filename is None:
463 if filename is None:
464 warn("Argument given (%s) can't be found as a variable "
464 warn("Argument given (%s) can't be found as a variable "
465 "or as a filename." % args)
465 "or as a filename." % args)
466 return (None, None, None)
466 return (None, None, None)
467 use_temp = False
467 use_temp = False
468
468
469 except DataIsObject as e:
469 except DataIsObject as e:
470 # macros have a special edit function
470 # macros have a special edit function
471 if isinstance(data, Macro):
471 if isinstance(data, Macro):
472 raise MacroToEdit(data) from e
472 raise MacroToEdit(data) from e
473
473
474 # For objects, try to edit the file where they are defined
474 # For objects, try to edit the file where they are defined
475 filename = find_file(data)
475 filename = find_file(data)
476 if filename:
476 if filename:
477 if 'fakemodule' in filename.lower() and \
477 if 'fakemodule' in filename.lower() and \
478 inspect.isclass(data):
478 inspect.isclass(data):
479 # class created by %edit? Try to find source
479 # class created by %edit? Try to find source
480 # by looking for method definitions instead, the
480 # by looking for method definitions instead, the
481 # __module__ in those classes is FakeModule.
481 # __module__ in those classes is FakeModule.
482 attrs = [getattr(data, aname) for aname in dir(data)]
482 attrs = [getattr(data, aname) for aname in dir(data)]
483 for attr in attrs:
483 for attr in attrs:
484 if not inspect.ismethod(attr):
484 if not inspect.ismethod(attr):
485 continue
485 continue
486 filename = find_file(attr)
486 filename = find_file(attr)
487 if filename and \
487 if filename and \
488 'fakemodule' not in filename.lower():
488 'fakemodule' not in filename.lower():
489 # change the attribute to be the edit
489 # change the attribute to be the edit
490 # target instead
490 # target instead
491 data = attr
491 data = attr
492 break
492 break
493
493
494 m = ipython_input_pat.match(os.path.basename(filename))
494 m = ipython_input_pat.match(os.path.basename(filename))
495 if m:
495 if m:
496 raise InteractivelyDefined(int(m.groups()[0])) from e
496 raise InteractivelyDefined(int(m.groups()[0])) from e
497
497
498 datafile = 1
498 datafile = 1
499 if filename is None:
499 if filename is None:
500 filename = make_filename(args)
500 filename = make_filename(args)
501 datafile = 1
501 datafile = 1
502 if filename is not None:
502 if filename is not None:
503 # only warn about this if we get a real name
503 # only warn about this if we get a real name
504 warn('Could not find file where `%s` is defined.\n'
504 warn('Could not find file where `%s` is defined.\n'
505 'Opening a file named `%s`' % (args, filename))
505 'Opening a file named `%s`' % (args, filename))
506 # Now, make sure we can actually read the source (if it was
506 # Now, make sure we can actually read the source (if it was
507 # in a temp file it's gone by now).
507 # in a temp file it's gone by now).
508 if datafile:
508 if datafile:
509 if lineno is None:
509 if lineno is None:
510 lineno = find_source_lines(data)
510 lineno = find_source_lines(data)
511 if lineno is None:
511 if lineno is None:
512 filename = make_filename(args)
512 filename = make_filename(args)
513 if filename is None:
513 if filename is None:
514 warn('The file where `%s` was defined '
514 warn('The file where `%s` was defined '
515 'cannot be read or found.' % data)
515 'cannot be read or found.' % data)
516 return (None, None, None)
516 return (None, None, None)
517 use_temp = False
517 use_temp = False
518
518
519 if use_temp:
519 if use_temp:
520 filename = shell.mktempfile(data)
520 filename = shell.mktempfile(data)
521 print('IPython will make a temporary file named:',filename)
521 print('IPython will make a temporary file named:',filename)
522
522
523 # use last_call to remember the state of the previous call, but don't
523 # use last_call to remember the state of the previous call, but don't
524 # let it be clobbered by successive '-p' calls.
524 # let it be clobbered by successive '-p' calls.
525 try:
525 try:
526 last_call[0] = shell.displayhook.prompt_count
526 last_call[0] = shell.displayhook.prompt_count
527 if not opts_prev:
527 if not opts_prev:
528 last_call[1] = args
528 last_call[1] = args
529 except:
529 except:
530 pass
530 pass
531
531
532
532
533 return filename, lineno, use_temp
533 return filename, lineno, use_temp
534
534
535 def _edit_macro(self,mname,macro):
535 def _edit_macro(self,mname,macro):
536 """open an editor with the macro data in a file"""
536 """open an editor with the macro data in a file"""
537 filename = self.shell.mktempfile(macro.value)
537 filename = self.shell.mktempfile(macro.value)
538 self.shell.hooks.editor(filename)
538 self.shell.hooks.editor(filename)
539
539
540 # and make a new macro object, to replace the old one
540 # and make a new macro object, to replace the old one
541 mvalue = Path(filename).read_text(encoding='utf-8')
541 mvalue = Path(filename).read_text(encoding="utf-8")
542 self.shell.user_ns[mname] = Macro(mvalue)
542 self.shell.user_ns[mname] = Macro(mvalue)
543
543
544 @skip_doctest
544 @skip_doctest
545 @line_magic
545 @line_magic
546 def edit(self, parameter_s='',last_call=['','']):
546 def edit(self, parameter_s='',last_call=['','']):
547 """Bring up an editor and execute the resulting code.
547 """Bring up an editor and execute the resulting code.
548
548
549 Usage:
549 Usage:
550 %edit [options] [args]
550 %edit [options] [args]
551
551
552 %edit runs IPython's editor hook. The default version of this hook is
552 %edit runs IPython's editor hook. The default version of this hook is
553 set to call the editor specified by your $EDITOR environment variable.
553 set to call the editor specified by your $EDITOR environment variable.
554 If this isn't found, it will default to vi under Linux/Unix and to
554 If this isn't found, it will default to vi under Linux/Unix and to
555 notepad under Windows. See the end of this docstring for how to change
555 notepad under Windows. See the end of this docstring for how to change
556 the editor hook.
556 the editor hook.
557
557
558 You can also set the value of this editor via the
558 You can also set the value of this editor via the
559 ``TerminalInteractiveShell.editor`` option in your configuration file.
559 ``TerminalInteractiveShell.editor`` option in your configuration file.
560 This is useful if you wish to use a different editor from your typical
560 This is useful if you wish to use a different editor from your typical
561 default with IPython (and for Windows users who typically don't set
561 default with IPython (and for Windows users who typically don't set
562 environment variables).
562 environment variables).
563
563
564 This command allows you to conveniently edit multi-line code right in
564 This command allows you to conveniently edit multi-line code right in
565 your IPython session.
565 your IPython session.
566
566
567 If called without arguments, %edit opens up an empty editor with a
567 If called without arguments, %edit opens up an empty editor with a
568 temporary file and will execute the contents of this file when you
568 temporary file and will execute the contents of this file when you
569 close it (don't forget to save it!).
569 close it (don't forget to save it!).
570
570
571
571
572 Options:
572 Options:
573
573
574 -n <number>: open the editor at a specified line number. By default,
574 -n <number>: open the editor at a specified line number. By default,
575 the IPython editor hook uses the unix syntax 'editor +N filename', but
575 the IPython editor hook uses the unix syntax 'editor +N filename', but
576 you can configure this by providing your own modified hook if your
576 you can configure this by providing your own modified hook if your
577 favorite editor supports line-number specifications with a different
577 favorite editor supports line-number specifications with a different
578 syntax.
578 syntax.
579
579
580 -p: this will call the editor with the same data as the previous time
580 -p: this will call the editor with the same data as the previous time
581 it was used, regardless of how long ago (in your current session) it
581 it was used, regardless of how long ago (in your current session) it
582 was.
582 was.
583
583
584 -r: use 'raw' input. This option only applies to input taken from the
584 -r: use 'raw' input. This option only applies to input taken from the
585 user's history. By default, the 'processed' history is used, so that
585 user's history. By default, the 'processed' history is used, so that
586 magics are loaded in their transformed version to valid Python. If
586 magics are loaded in their transformed version to valid Python. If
587 this option is given, the raw input as typed as the command line is
587 this option is given, the raw input as typed as the command line is
588 used instead. When you exit the editor, it will be executed by
588 used instead. When you exit the editor, it will be executed by
589 IPython's own processor.
589 IPython's own processor.
590
590
591 -x: do not execute the edited code immediately upon exit. This is
591 -x: do not execute the edited code immediately upon exit. This is
592 mainly useful if you are editing programs which need to be called with
592 mainly useful if you are editing programs which need to be called with
593 command line arguments, which you can then do using %run.
593 command line arguments, which you can then do using %run.
594
594
595
595
596 Arguments:
596 Arguments:
597
597
598 If arguments are given, the following possibilities exist:
598 If arguments are given, the following possibilities exist:
599
599
600 - If the argument is a filename, IPython will load that into the
600 - If the argument is a filename, IPython will load that into the
601 editor. It will execute its contents with execfile() when you exit,
601 editor. It will execute its contents with execfile() when you exit,
602 loading any code in the file into your interactive namespace.
602 loading any code in the file into your interactive namespace.
603
603
604 - The arguments are ranges of input history, e.g. "7 ~1/4-6".
604 - The arguments are ranges of input history, e.g. "7 ~1/4-6".
605 The syntax is the same as in the %history magic.
605 The syntax is the same as in the %history magic.
606
606
607 - If the argument is a string variable, its contents are loaded
607 - If the argument is a string variable, its contents are loaded
608 into the editor. You can thus edit any string which contains
608 into the editor. You can thus edit any string which contains
609 python code (including the result of previous edits).
609 python code (including the result of previous edits).
610
610
611 - If the argument is the name of an object (other than a string),
611 - If the argument is the name of an object (other than a string),
612 IPython will try to locate the file where it was defined and open the
612 IPython will try to locate the file where it was defined and open the
613 editor at the point where it is defined. You can use `%edit function`
613 editor at the point where it is defined. You can use `%edit function`
614 to load an editor exactly at the point where 'function' is defined,
614 to load an editor exactly at the point where 'function' is defined,
615 edit it and have the file be executed automatically.
615 edit it and have the file be executed automatically.
616
616
617 - If the object is a macro (see %macro for details), this opens up your
617 - If the object is a macro (see %macro for details), this opens up your
618 specified editor with a temporary file containing the macro's data.
618 specified editor with a temporary file containing the macro's data.
619 Upon exit, the macro is reloaded with the contents of the file.
619 Upon exit, the macro is reloaded with the contents of the file.
620
620
621 Note: opening at an exact line is only supported under Unix, and some
621 Note: opening at an exact line is only supported under Unix, and some
622 editors (like kedit and gedit up to Gnome 2.8) do not understand the
622 editors (like kedit and gedit up to Gnome 2.8) do not understand the
623 '+NUMBER' parameter necessary for this feature. Good editors like
623 '+NUMBER' parameter necessary for this feature. Good editors like
624 (X)Emacs, vi, jed, pico and joe all do.
624 (X)Emacs, vi, jed, pico and joe all do.
625
625
626 After executing your code, %edit will return as output the code you
626 After executing your code, %edit will return as output the code you
627 typed in the editor (except when it was an existing file). This way
627 typed in the editor (except when it was an existing file). This way
628 you can reload the code in further invocations of %edit as a variable,
628 you can reload the code in further invocations of %edit as a variable,
629 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
629 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
630 the output.
630 the output.
631
631
632 Note that %edit is also available through the alias %ed.
632 Note that %edit is also available through the alias %ed.
633
633
634 This is an example of creating a simple function inside the editor and
634 This is an example of creating a simple function inside the editor and
635 then modifying it. First, start up the editor::
635 then modifying it. First, start up the editor::
636
636
637 In [1]: edit
637 In [1]: edit
638 Editing... done. Executing edited code...
638 Editing... done. Executing edited code...
639 Out[1]: 'def foo():\\n print "foo() was defined in an editing
639 Out[1]: 'def foo():\\n print "foo() was defined in an editing
640 session"\\n'
640 session"\\n'
641
641
642 We can then call the function foo()::
642 We can then call the function foo()::
643
643
644 In [2]: foo()
644 In [2]: foo()
645 foo() was defined in an editing session
645 foo() was defined in an editing session
646
646
647 Now we edit foo. IPython automatically loads the editor with the
647 Now we edit foo. IPython automatically loads the editor with the
648 (temporary) file where foo() was previously defined::
648 (temporary) file where foo() was previously defined::
649
649
650 In [3]: edit foo
650 In [3]: edit foo
651 Editing... done. Executing edited code...
651 Editing... done. Executing edited code...
652
652
653 And if we call foo() again we get the modified version::
653 And if we call foo() again we get the modified version::
654
654
655 In [4]: foo()
655 In [4]: foo()
656 foo() has now been changed!
656 foo() has now been changed!
657
657
658 Here is an example of how to edit a code snippet successive
658 Here is an example of how to edit a code snippet successive
659 times. First we call the editor::
659 times. First we call the editor::
660
660
661 In [5]: edit
661 In [5]: edit
662 Editing... done. Executing edited code...
662 Editing... done. Executing edited code...
663 hello
663 hello
664 Out[5]: "print 'hello'\\n"
664 Out[5]: "print 'hello'\\n"
665
665
666 Now we call it again with the previous output (stored in _)::
666 Now we call it again with the previous output (stored in _)::
667
667
668 In [6]: edit _
668 In [6]: edit _
669 Editing... done. Executing edited code...
669 Editing... done. Executing edited code...
670 hello world
670 hello world
671 Out[6]: "print 'hello world'\\n"
671 Out[6]: "print 'hello world'\\n"
672
672
673 Now we call it with the output #8 (stored in _8, also as Out[8])::
673 Now we call it with the output #8 (stored in _8, also as Out[8])::
674
674
675 In [7]: edit _8
675 In [7]: edit _8
676 Editing... done. Executing edited code...
676 Editing... done. Executing edited code...
677 hello again
677 hello again
678 Out[7]: "print 'hello again'\\n"
678 Out[7]: "print 'hello again'\\n"
679
679
680
680
681 Changing the default editor hook:
681 Changing the default editor hook:
682
682
683 If you wish to write your own editor hook, you can put it in a
683 If you wish to write your own editor hook, you can put it in a
684 configuration file which you load at startup time. The default hook
684 configuration file which you load at startup time. The default hook
685 is defined in the IPython.core.hooks module, and you can use that as a
685 is defined in the IPython.core.hooks module, and you can use that as a
686 starting example for further modifications. That file also has
686 starting example for further modifications. That file also has
687 general instructions on how to set a new hook for use once you've
687 general instructions on how to set a new hook for use once you've
688 defined it."""
688 defined it."""
689 opts,args = self.parse_options(parameter_s,'prxn:')
689 opts,args = self.parse_options(parameter_s,'prxn:')
690
690
691 try:
691 try:
692 filename, lineno, is_temp = self._find_edit_target(self.shell,
692 filename, lineno, is_temp = self._find_edit_target(self.shell,
693 args, opts, last_call)
693 args, opts, last_call)
694 except MacroToEdit as e:
694 except MacroToEdit as e:
695 self._edit_macro(args, e.args[0])
695 self._edit_macro(args, e.args[0])
696 return
696 return
697 except InteractivelyDefined as e:
697 except InteractivelyDefined as e:
698 print("Editing In[%i]" % e.index)
698 print("Editing In[%i]" % e.index)
699 args = str(e.index)
699 args = str(e.index)
700 filename, lineno, is_temp = self._find_edit_target(self.shell,
700 filename, lineno, is_temp = self._find_edit_target(self.shell,
701 args, opts, last_call)
701 args, opts, last_call)
702 if filename is None:
702 if filename is None:
703 # nothing was found, warnings have already been issued,
703 # nothing was found, warnings have already been issued,
704 # just give up.
704 # just give up.
705 return
705 return
706
706
707 if is_temp:
707 if is_temp:
708 self._knowntemps.add(filename)
708 self._knowntemps.add(filename)
709 elif (filename in self._knowntemps):
709 elif (filename in self._knowntemps):
710 is_temp = True
710 is_temp = True
711
711
712
712
713 # do actual editing here
713 # do actual editing here
714 print('Editing...', end=' ')
714 print('Editing...', end=' ')
715 sys.stdout.flush()
715 sys.stdout.flush()
716 filepath = Path(filename)
716 filepath = Path(filename)
717 try:
717 try:
718 # Quote filenames that may have spaces in them when opening
718 # Quote filenames that may have spaces in them when opening
719 # the editor
719 # the editor
720 quoted = filename = str(filepath.absolute())
720 quoted = filename = str(filepath.absolute())
721 if " " in quoted:
721 if " " in quoted:
722 quoted = "'%s'" % quoted
722 quoted = "'%s'" % quoted
723 self.shell.hooks.editor(quoted, lineno)
723 self.shell.hooks.editor(quoted, lineno)
724 except TryNext:
724 except TryNext:
725 warn('Could not open editor')
725 warn('Could not open editor')
726 return
726 return
727
727
728 # XXX TODO: should this be generalized for all string vars?
728 # XXX TODO: should this be generalized for all string vars?
729 # For now, this is special-cased to blocks created by cpaste
729 # For now, this is special-cased to blocks created by cpaste
730 if args.strip() == "pasted_block":
730 if args.strip() == "pasted_block":
731 self.shell.user_ns["pasted_block"] = filepath.read_text(encoding='utf-8')
731 self.shell.user_ns["pasted_block"] = filepath.read_text(encoding="utf-8")
732
732
733 if 'x' in opts: # -x prevents actual execution
733 if 'x' in opts: # -x prevents actual execution
734 print()
734 print()
735 else:
735 else:
736 print('done. Executing edited code...')
736 print('done. Executing edited code...')
737 with preserve_keys(self.shell.user_ns, '__file__'):
737 with preserve_keys(self.shell.user_ns, '__file__'):
738 if not is_temp:
738 if not is_temp:
739 self.shell.user_ns['__file__'] = filename
739 self.shell.user_ns["__file__"] = filename
740 if 'r' in opts: # Untranslated IPython code
740 if "r" in opts: # Untranslated IPython code
741 source = filepath.read_text(encoding='utf-8')
741 source = filepath.read_text(encoding="utf-8")
742 self.shell.run_cell(source, store_history=False)
742 self.shell.run_cell(source, store_history=False)
743 else:
743 else:
744 self.shell.safe_execfile(filename, self.shell.user_ns,
744 self.shell.safe_execfile(filename, self.shell.user_ns,
745 self.shell.user_ns)
745 self.shell.user_ns)
746
746
747 if is_temp:
747 if is_temp:
748 try:
748 try:
749 return filepath.read_text(encoding='utf-8')
749 return filepath.read_text(encoding="utf-8")
750 except IOError as msg:
750 except IOError as msg:
751 if Path(msg.filename) == filepath:
751 if Path(msg.filename) == filepath:
752 warn('File not found. Did you forget to save?')
752 warn('File not found. Did you forget to save?')
753 return
753 return
754 else:
754 else:
755 self.shell.showtraceback()
755 self.shell.showtraceback()
@@ -1,1510 +1,1510 b''
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 cProfile as profile
11 import cProfile as profile
12 import gc
12 import gc
13 import itertools
13 import itertools
14 import math
14 import math
15 import os
15 import os
16 import pstats
16 import pstats
17 import re
17 import re
18 import shlex
18 import shlex
19 import sys
19 import sys
20 import time
20 import time
21 import timeit
21 import timeit
22 from ast import Module
22 from ast import Module
23 from io import StringIO
23 from io import StringIO
24 from logging import error
24 from logging import error
25 from pathlib import Path
25 from pathlib import Path
26 from pdb import Restart
26 from pdb import Restart
27 from warnings import warn
27 from warnings import warn
28
28
29 from IPython.core import magic_arguments, oinspect, page
29 from IPython.core import magic_arguments, oinspect, page
30 from IPython.core.error import UsageError
30 from IPython.core.error import UsageError
31 from IPython.core.macro import Macro
31 from IPython.core.macro import Macro
32 from IPython.core.magic import (
32 from IPython.core.magic import (
33 Magics,
33 Magics,
34 cell_magic,
34 cell_magic,
35 line_cell_magic,
35 line_cell_magic,
36 line_magic,
36 line_magic,
37 magics_class,
37 magics_class,
38 needs_local_scope,
38 needs_local_scope,
39 no_var_expand,
39 no_var_expand,
40 on_off,
40 on_off,
41 )
41 )
42 from IPython.testing.skipdoctest import skip_doctest
42 from IPython.testing.skipdoctest import skip_doctest
43 from IPython.utils.capture import capture_output
43 from IPython.utils.capture import capture_output
44 from IPython.utils.contexts import preserve_keys
44 from IPython.utils.contexts import preserve_keys
45 from IPython.utils.ipstruct import Struct
45 from IPython.utils.ipstruct import Struct
46 from IPython.utils.module_paths import find_mod
46 from IPython.utils.module_paths import find_mod
47 from IPython.utils.path import get_py_filename, shellglob
47 from IPython.utils.path import get_py_filename, shellglob
48 from IPython.utils.timing import clock, clock2
48 from IPython.utils.timing import clock, clock2
49
49
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51 # Magic implementation classes
51 # Magic implementation classes
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53
53
54
54
55 class TimeitResult(object):
55 class TimeitResult(object):
56 """
56 """
57 Object returned by the timeit magic with info about the run.
57 Object returned by the timeit magic with info about the run.
58
58
59 Contains the following attributes :
59 Contains the following attributes :
60
60
61 loops: (int) number of loops done per measurement
61 loops: (int) number of loops done per measurement
62 repeat: (int) number of times the measurement has been repeated
62 repeat: (int) number of times the measurement has been repeated
63 best: (float) best execution time / number
63 best: (float) best execution time / number
64 all_runs: (list of float) execution time of each run (in s)
64 all_runs: (list of float) execution time of each run (in s)
65 compile_time: (float) time of statement compilation (s)
65 compile_time: (float) time of statement compilation (s)
66
66
67 """
67 """
68 def __init__(self, loops, repeat, best, worst, all_runs, compile_time, precision):
68 def __init__(self, loops, repeat, best, worst, all_runs, compile_time, precision):
69 self.loops = loops
69 self.loops = loops
70 self.repeat = repeat
70 self.repeat = repeat
71 self.best = best
71 self.best = best
72 self.worst = worst
72 self.worst = worst
73 self.all_runs = all_runs
73 self.all_runs = all_runs
74 self.compile_time = compile_time
74 self.compile_time = compile_time
75 self._precision = precision
75 self._precision = precision
76 self.timings = [ dt / self.loops for dt in all_runs]
76 self.timings = [ dt / self.loops for dt in all_runs]
77
77
78 @property
78 @property
79 def average(self):
79 def average(self):
80 return math.fsum(self.timings) / len(self.timings)
80 return math.fsum(self.timings) / len(self.timings)
81
81
82 @property
82 @property
83 def stdev(self):
83 def stdev(self):
84 mean = self.average
84 mean = self.average
85 return (math.fsum([(x - mean) ** 2 for x in self.timings]) / len(self.timings)) ** 0.5
85 return (math.fsum([(x - mean) ** 2 for x in self.timings]) / len(self.timings)) ** 0.5
86
86
87 def __str__(self):
87 def __str__(self):
88 pm = '+-'
88 pm = '+-'
89 if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
89 if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
90 try:
90 try:
91 u'\xb1'.encode(sys.stdout.encoding)
91 u'\xb1'.encode(sys.stdout.encoding)
92 pm = u'\xb1'
92 pm = u'\xb1'
93 except:
93 except:
94 pass
94 pass
95 return "{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops:,} loop{loop_plural} each)".format(
95 return "{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops:,} loop{loop_plural} each)".format(
96 pm=pm,
96 pm=pm,
97 runs=self.repeat,
97 runs=self.repeat,
98 loops=self.loops,
98 loops=self.loops,
99 loop_plural="" if self.loops == 1 else "s",
99 loop_plural="" if self.loops == 1 else "s",
100 run_plural="" if self.repeat == 1 else "s",
100 run_plural="" if self.repeat == 1 else "s",
101 mean=_format_time(self.average, self._precision),
101 mean=_format_time(self.average, self._precision),
102 std=_format_time(self.stdev, self._precision),
102 std=_format_time(self.stdev, self._precision),
103 )
103 )
104
104
105 def _repr_pretty_(self, p , cycle):
105 def _repr_pretty_(self, p , cycle):
106 unic = self.__str__()
106 unic = self.__str__()
107 p.text(u'<TimeitResult : '+unic+u'>')
107 p.text(u'<TimeitResult : '+unic+u'>')
108
108
109
109
110 class TimeitTemplateFiller(ast.NodeTransformer):
110 class TimeitTemplateFiller(ast.NodeTransformer):
111 """Fill in the AST template for timing execution.
111 """Fill in the AST template for timing execution.
112
112
113 This is quite closely tied to the template definition, which is in
113 This is quite closely tied to the template definition, which is in
114 :meth:`ExecutionMagics.timeit`.
114 :meth:`ExecutionMagics.timeit`.
115 """
115 """
116 def __init__(self, ast_setup, ast_stmt):
116 def __init__(self, ast_setup, ast_stmt):
117 self.ast_setup = ast_setup
117 self.ast_setup = ast_setup
118 self.ast_stmt = ast_stmt
118 self.ast_stmt = ast_stmt
119
119
120 def visit_FunctionDef(self, node):
120 def visit_FunctionDef(self, node):
121 "Fill in the setup statement"
121 "Fill in the setup statement"
122 self.generic_visit(node)
122 self.generic_visit(node)
123 if node.name == "inner":
123 if node.name == "inner":
124 node.body[:1] = self.ast_setup.body
124 node.body[:1] = self.ast_setup.body
125
125
126 return node
126 return node
127
127
128 def visit_For(self, node):
128 def visit_For(self, node):
129 "Fill in the statement to be timed"
129 "Fill in the statement to be timed"
130 if getattr(getattr(node.body[0], 'value', None), 'id', None) == 'stmt':
130 if getattr(getattr(node.body[0], 'value', None), 'id', None) == 'stmt':
131 node.body = self.ast_stmt.body
131 node.body = self.ast_stmt.body
132 return node
132 return node
133
133
134
134
135 class Timer(timeit.Timer):
135 class Timer(timeit.Timer):
136 """Timer class that explicitly uses self.inner
136 """Timer class that explicitly uses self.inner
137
137
138 which is an undocumented implementation detail of CPython,
138 which is an undocumented implementation detail of CPython,
139 not shared by PyPy.
139 not shared by PyPy.
140 """
140 """
141 # Timer.timeit copied from CPython 3.4.2
141 # Timer.timeit copied from CPython 3.4.2
142 def timeit(self, number=timeit.default_number):
142 def timeit(self, number=timeit.default_number):
143 """Time 'number' executions of the main statement.
143 """Time 'number' executions of the main statement.
144
144
145 To be precise, this executes the setup statement once, and
145 To be precise, this executes the setup statement once, and
146 then returns the time it takes to execute the main statement
146 then returns the time it takes to execute the main statement
147 a number of times, as a float measured in seconds. The
147 a number of times, as a float measured in seconds. The
148 argument is the number of times through the loop, defaulting
148 argument is the number of times through the loop, defaulting
149 to one million. The main statement, the setup statement and
149 to one million. The main statement, the setup statement and
150 the timer function to be used are passed to the constructor.
150 the timer function to be used are passed to the constructor.
151 """
151 """
152 it = itertools.repeat(None, number)
152 it = itertools.repeat(None, number)
153 gcold = gc.isenabled()
153 gcold = gc.isenabled()
154 gc.disable()
154 gc.disable()
155 try:
155 try:
156 timing = self.inner(it, self.timer)
156 timing = self.inner(it, self.timer)
157 finally:
157 finally:
158 if gcold:
158 if gcold:
159 gc.enable()
159 gc.enable()
160 return timing
160 return timing
161
161
162
162
163 @magics_class
163 @magics_class
164 class ExecutionMagics(Magics):
164 class ExecutionMagics(Magics):
165 """Magics related to code execution, debugging, profiling, etc.
165 """Magics related to code execution, debugging, profiling, etc.
166
166
167 """
167 """
168
168
169 def __init__(self, shell):
169 def __init__(self, shell):
170 super(ExecutionMagics, self).__init__(shell)
170 super(ExecutionMagics, self).__init__(shell)
171 # Default execution function used to actually run user code.
171 # Default execution function used to actually run user code.
172 self.default_runner = None
172 self.default_runner = None
173
173
174 @skip_doctest
174 @skip_doctest
175 @no_var_expand
175 @no_var_expand
176 @line_cell_magic
176 @line_cell_magic
177 def prun(self, parameter_s='', cell=None):
177 def prun(self, parameter_s='', cell=None):
178
178
179 """Run a statement through the python code profiler.
179 """Run a statement through the python code profiler.
180
180
181 Usage, in line mode:
181 Usage, in line mode:
182 %prun [options] statement
182 %prun [options] statement
183
183
184 Usage, in cell mode:
184 Usage, in cell mode:
185 %%prun [options] [statement]
185 %%prun [options] [statement]
186 code...
186 code...
187 code...
187 code...
188
188
189 In cell mode, the additional code lines are appended to the (possibly
189 In cell mode, the additional code lines are appended to the (possibly
190 empty) statement in the first line. Cell mode allows you to easily
190 empty) statement in the first line. Cell mode allows you to easily
191 profile multiline blocks without having to put them in a separate
191 profile multiline blocks without having to put them in a separate
192 function.
192 function.
193
193
194 The given statement (which doesn't require quote marks) is run via the
194 The given statement (which doesn't require quote marks) is run via the
195 python profiler in a manner similar to the profile.run() function.
195 python profiler in a manner similar to the profile.run() function.
196 Namespaces are internally managed to work correctly; profile.run
196 Namespaces are internally managed to work correctly; profile.run
197 cannot be used in IPython because it makes certain assumptions about
197 cannot be used in IPython because it makes certain assumptions about
198 namespaces which do not hold under IPython.
198 namespaces which do not hold under IPython.
199
199
200 Options:
200 Options:
201
201
202 -l <limit>
202 -l <limit>
203 you can place restrictions on what or how much of the
203 you can place restrictions on what or how much of the
204 profile gets printed. The limit value can be:
204 profile gets printed. The limit value can be:
205
205
206 * A string: only information for function names containing this string
206 * A string: only information for function names containing this string
207 is printed.
207 is printed.
208
208
209 * An integer: only these many lines are printed.
209 * An integer: only these many lines are printed.
210
210
211 * A float (between 0 and 1): this fraction of the report is printed
211 * A float (between 0 and 1): this fraction of the report is printed
212 (for example, use a limit of 0.4 to see the topmost 40% only).
212 (for example, use a limit of 0.4 to see the topmost 40% only).
213
213
214 You can combine several limits with repeated use of the option. For
214 You can combine several limits with repeated use of the option. For
215 example, ``-l __init__ -l 5`` will print only the topmost 5 lines of
215 example, ``-l __init__ -l 5`` will print only the topmost 5 lines of
216 information about class constructors.
216 information about class constructors.
217
217
218 -r
218 -r
219 return the pstats.Stats object generated by the profiling. This
219 return the pstats.Stats object generated by the profiling. This
220 object has all the information about the profile in it, and you can
220 object has all the information about the profile in it, and you can
221 later use it for further analysis or in other functions.
221 later use it for further analysis or in other functions.
222
222
223 -s <key>
223 -s <key>
224 sort profile by given key. You can provide more than one key
224 sort profile by given key. You can provide more than one key
225 by using the option several times: '-s key1 -s key2 -s key3...'. The
225 by using the option several times: '-s key1 -s key2 -s key3...'. The
226 default sorting key is 'time'.
226 default sorting key is 'time'.
227
227
228 The following is copied verbatim from the profile documentation
228 The following is copied verbatim from the profile documentation
229 referenced below:
229 referenced below:
230
230
231 When more than one key is provided, additional keys are used as
231 When more than one key is provided, additional keys are used as
232 secondary criteria when the there is equality in all keys selected
232 secondary criteria when the there is equality in all keys selected
233 before them.
233 before them.
234
234
235 Abbreviations can be used for any key names, as long as the
235 Abbreviations can be used for any key names, as long as the
236 abbreviation is unambiguous. The following are the keys currently
236 abbreviation is unambiguous. The following are the keys currently
237 defined:
237 defined:
238
238
239 ============ =====================
239 ============ =====================
240 Valid Arg Meaning
240 Valid Arg Meaning
241 ============ =====================
241 ============ =====================
242 "calls" call count
242 "calls" call count
243 "cumulative" cumulative time
243 "cumulative" cumulative time
244 "file" file name
244 "file" file name
245 "module" file name
245 "module" file name
246 "pcalls" primitive call count
246 "pcalls" primitive call count
247 "line" line number
247 "line" line number
248 "name" function name
248 "name" function name
249 "nfl" name/file/line
249 "nfl" name/file/line
250 "stdname" standard name
250 "stdname" standard name
251 "time" internal time
251 "time" internal time
252 ============ =====================
252 ============ =====================
253
253
254 Note that all sorts on statistics are in descending order (placing
254 Note that all sorts on statistics are in descending order (placing
255 most time consuming items first), where as name, file, and line number
255 most time consuming items first), where as name, file, and line number
256 searches are in ascending order (i.e., alphabetical). The subtle
256 searches are in ascending order (i.e., alphabetical). The subtle
257 distinction between "nfl" and "stdname" is that the standard name is a
257 distinction between "nfl" and "stdname" is that the standard name is a
258 sort of the name as printed, which means that the embedded line
258 sort of the name as printed, which means that the embedded line
259 numbers get compared in an odd way. For example, lines 3, 20, and 40
259 numbers get compared in an odd way. For example, lines 3, 20, and 40
260 would (if the file names were the same) appear in the string order
260 would (if the file names were the same) appear in the string order
261 "20" "3" and "40". In contrast, "nfl" does a numeric compare of the
261 "20" "3" and "40". In contrast, "nfl" does a numeric compare of the
262 line numbers. In fact, sort_stats("nfl") is the same as
262 line numbers. In fact, sort_stats("nfl") is the same as
263 sort_stats("name", "file", "line").
263 sort_stats("name", "file", "line").
264
264
265 -T <filename>
265 -T <filename>
266 save profile results as shown on screen to a text
266 save profile results as shown on screen to a text
267 file. The profile is still shown on screen.
267 file. The profile is still shown on screen.
268
268
269 -D <filename>
269 -D <filename>
270 save (via dump_stats) profile statistics to given
270 save (via dump_stats) profile statistics to given
271 filename. This data is in a format understood by the pstats module, and
271 filename. This data is in a format understood by the pstats module, and
272 is generated by a call to the dump_stats() method of profile
272 is generated by a call to the dump_stats() method of profile
273 objects. The profile is still shown on screen.
273 objects. The profile is still shown on screen.
274
274
275 -q
275 -q
276 suppress output to the pager. Best used with -T and/or -D above.
276 suppress output to the pager. Best used with -T and/or -D above.
277
277
278 If you want to run complete programs under the profiler's control, use
278 If you want to run complete programs under the profiler's control, use
279 ``%run -p [prof_opts] filename.py [args to program]`` where prof_opts
279 ``%run -p [prof_opts] filename.py [args to program]`` where prof_opts
280 contains profiler specific options as described here.
280 contains profiler specific options as described here.
281
281
282 You can read the complete documentation for the profile module with::
282 You can read the complete documentation for the profile module with::
283
283
284 In [1]: import profile; profile.help()
284 In [1]: import profile; profile.help()
285
285
286 .. versionchanged:: 7.3
286 .. versionchanged:: 7.3
287 User variables are no longer expanded,
287 User variables are no longer expanded,
288 the magic line is always left unmodified.
288 the magic line is always left unmodified.
289
289
290 """
290 """
291 opts, arg_str = self.parse_options(parameter_s, 'D:l:rs:T:q',
291 opts, arg_str = self.parse_options(parameter_s, 'D:l:rs:T:q',
292 list_all=True, posix=False)
292 list_all=True, posix=False)
293 if cell is not None:
293 if cell is not None:
294 arg_str += '\n' + cell
294 arg_str += '\n' + cell
295 arg_str = self.shell.transform_cell(arg_str)
295 arg_str = self.shell.transform_cell(arg_str)
296 return self._run_with_profiler(arg_str, opts, self.shell.user_ns)
296 return self._run_with_profiler(arg_str, opts, self.shell.user_ns)
297
297
298 def _run_with_profiler(self, code, opts, namespace):
298 def _run_with_profiler(self, code, opts, namespace):
299 """
299 """
300 Run `code` with profiler. Used by ``%prun`` and ``%run -p``.
300 Run `code` with profiler. Used by ``%prun`` and ``%run -p``.
301
301
302 Parameters
302 Parameters
303 ----------
303 ----------
304 code : str
304 code : str
305 Code to be executed.
305 Code to be executed.
306 opts : Struct
306 opts : Struct
307 Options parsed by `self.parse_options`.
307 Options parsed by `self.parse_options`.
308 namespace : dict
308 namespace : dict
309 A dictionary for Python namespace (e.g., `self.shell.user_ns`).
309 A dictionary for Python namespace (e.g., `self.shell.user_ns`).
310
310
311 """
311 """
312
312
313 # Fill default values for unspecified options:
313 # Fill default values for unspecified options:
314 opts.merge(Struct(D=[''], l=[], s=['time'], T=['']))
314 opts.merge(Struct(D=[''], l=[], s=['time'], T=['']))
315
315
316 prof = profile.Profile()
316 prof = profile.Profile()
317 try:
317 try:
318 prof = prof.runctx(code, namespace, namespace)
318 prof = prof.runctx(code, namespace, namespace)
319 sys_exit = ''
319 sys_exit = ''
320 except SystemExit:
320 except SystemExit:
321 sys_exit = """*** SystemExit exception caught in code being profiled."""
321 sys_exit = """*** SystemExit exception caught in code being profiled."""
322
322
323 stats = pstats.Stats(prof).strip_dirs().sort_stats(*opts.s)
323 stats = pstats.Stats(prof).strip_dirs().sort_stats(*opts.s)
324
324
325 lims = opts.l
325 lims = opts.l
326 if lims:
326 if lims:
327 lims = [] # rebuild lims with ints/floats/strings
327 lims = [] # rebuild lims with ints/floats/strings
328 for lim in opts.l:
328 for lim in opts.l:
329 try:
329 try:
330 lims.append(int(lim))
330 lims.append(int(lim))
331 except ValueError:
331 except ValueError:
332 try:
332 try:
333 lims.append(float(lim))
333 lims.append(float(lim))
334 except ValueError:
334 except ValueError:
335 lims.append(lim)
335 lims.append(lim)
336
336
337 # Trap output.
337 # Trap output.
338 stdout_trap = StringIO()
338 stdout_trap = StringIO()
339 stats_stream = stats.stream
339 stats_stream = stats.stream
340 try:
340 try:
341 stats.stream = stdout_trap
341 stats.stream = stdout_trap
342 stats.print_stats(*lims)
342 stats.print_stats(*lims)
343 finally:
343 finally:
344 stats.stream = stats_stream
344 stats.stream = stats_stream
345
345
346 output = stdout_trap.getvalue()
346 output = stdout_trap.getvalue()
347 output = output.rstrip()
347 output = output.rstrip()
348
348
349 if 'q' not in opts:
349 if 'q' not in opts:
350 page.page(output)
350 page.page(output)
351 print(sys_exit, end=' ')
351 print(sys_exit, end=' ')
352
352
353 dump_file = opts.D[0]
353 dump_file = opts.D[0]
354 text_file = opts.T[0]
354 text_file = opts.T[0]
355 if dump_file:
355 if dump_file:
356 prof.dump_stats(dump_file)
356 prof.dump_stats(dump_file)
357 print(
357 print(
358 f"\n*** Profile stats marshalled to file {repr(dump_file)}.{sys_exit}"
358 f"\n*** Profile stats marshalled to file {repr(dump_file)}.{sys_exit}"
359 )
359 )
360 if text_file:
360 if text_file:
361 pfile = Path(text_file)
361 pfile = Path(text_file)
362 pfile.touch(exist_ok=True)
362 pfile.touch(exist_ok=True)
363 pfile.write_text(output, encoding='utf-8')
363 pfile.write_text(output, encoding="utf-8")
364
364
365 print(
365 print(
366 f"\n*** Profile printout saved to text file {repr(text_file)}.{sys_exit}"
366 f"\n*** Profile printout saved to text file {repr(text_file)}.{sys_exit}"
367 )
367 )
368
368
369 if 'r' in opts:
369 if 'r' in opts:
370 return stats
370 return stats
371
371
372 return None
372 return None
373
373
374 @line_magic
374 @line_magic
375 def pdb(self, parameter_s=''):
375 def pdb(self, parameter_s=''):
376 """Control the automatic calling of the pdb interactive debugger.
376 """Control the automatic calling of the pdb interactive debugger.
377
377
378 Call as '%pdb on', '%pdb 1', '%pdb off' or '%pdb 0'. If called without
378 Call as '%pdb on', '%pdb 1', '%pdb off' or '%pdb 0'. If called without
379 argument it works as a toggle.
379 argument it works as a toggle.
380
380
381 When an exception is triggered, IPython can optionally call the
381 When an exception is triggered, IPython can optionally call the
382 interactive pdb debugger after the traceback printout. %pdb toggles
382 interactive pdb debugger after the traceback printout. %pdb toggles
383 this feature on and off.
383 this feature on and off.
384
384
385 The initial state of this feature is set in your configuration
385 The initial state of this feature is set in your configuration
386 file (the option is ``InteractiveShell.pdb``).
386 file (the option is ``InteractiveShell.pdb``).
387
387
388 If you want to just activate the debugger AFTER an exception has fired,
388 If you want to just activate the debugger AFTER an exception has fired,
389 without having to type '%pdb on' and rerunning your code, you can use
389 without having to type '%pdb on' and rerunning your code, you can use
390 the %debug magic."""
390 the %debug magic."""
391
391
392 par = parameter_s.strip().lower()
392 par = parameter_s.strip().lower()
393
393
394 if par:
394 if par:
395 try:
395 try:
396 new_pdb = {'off':0,'0':0,'on':1,'1':1}[par]
396 new_pdb = {'off':0,'0':0,'on':1,'1':1}[par]
397 except KeyError:
397 except KeyError:
398 print ('Incorrect argument. Use on/1, off/0, '
398 print ('Incorrect argument. Use on/1, off/0, '
399 'or nothing for a toggle.')
399 'or nothing for a toggle.')
400 return
400 return
401 else:
401 else:
402 # toggle
402 # toggle
403 new_pdb = not self.shell.call_pdb
403 new_pdb = not self.shell.call_pdb
404
404
405 # set on the shell
405 # set on the shell
406 self.shell.call_pdb = new_pdb
406 self.shell.call_pdb = new_pdb
407 print('Automatic pdb calling has been turned',on_off(new_pdb))
407 print('Automatic pdb calling has been turned',on_off(new_pdb))
408
408
409 @magic_arguments.magic_arguments()
409 @magic_arguments.magic_arguments()
410 @magic_arguments.argument('--breakpoint', '-b', metavar='FILE:LINE',
410 @magic_arguments.argument('--breakpoint', '-b', metavar='FILE:LINE',
411 help="""
411 help="""
412 Set break point at LINE in FILE.
412 Set break point at LINE in FILE.
413 """
413 """
414 )
414 )
415 @magic_arguments.argument('statement', nargs='*',
415 @magic_arguments.argument('statement', nargs='*',
416 help="""
416 help="""
417 Code to run in debugger.
417 Code to run in debugger.
418 You can omit this in cell magic mode.
418 You can omit this in cell magic mode.
419 """
419 """
420 )
420 )
421 @no_var_expand
421 @no_var_expand
422 @line_cell_magic
422 @line_cell_magic
423 def debug(self, line='', cell=None):
423 def debug(self, line='', cell=None):
424 """Activate the interactive debugger.
424 """Activate the interactive debugger.
425
425
426 This magic command support two ways of activating debugger.
426 This magic command support two ways of activating debugger.
427 One is to activate debugger before executing code. This way, you
427 One is to activate debugger before executing code. This way, you
428 can set a break point, to step through the code from the point.
428 can set a break point, to step through the code from the point.
429 You can use this mode by giving statements to execute and optionally
429 You can use this mode by giving statements to execute and optionally
430 a breakpoint.
430 a breakpoint.
431
431
432 The other one is to activate debugger in post-mortem mode. You can
432 The other one is to activate debugger in post-mortem mode. You can
433 activate this mode simply running %debug without any argument.
433 activate this mode simply running %debug without any argument.
434 If an exception has just occurred, this lets you inspect its stack
434 If an exception has just occurred, this lets you inspect its stack
435 frames interactively. Note that this will always work only on the last
435 frames interactively. Note that this will always work only on the last
436 traceback that occurred, so you must call this quickly after an
436 traceback that occurred, so you must call this quickly after an
437 exception that you wish to inspect has fired, because if another one
437 exception that you wish to inspect has fired, because if another one
438 occurs, it clobbers the previous one.
438 occurs, it clobbers the previous one.
439
439
440 If you want IPython to automatically do this on every exception, see
440 If you want IPython to automatically do this on every exception, see
441 the %pdb magic for more details.
441 the %pdb magic for more details.
442
442
443 .. versionchanged:: 7.3
443 .. versionchanged:: 7.3
444 When running code, user variables are no longer expanded,
444 When running code, user variables are no longer expanded,
445 the magic line is always left unmodified.
445 the magic line is always left unmodified.
446
446
447 """
447 """
448 args = magic_arguments.parse_argstring(self.debug, line)
448 args = magic_arguments.parse_argstring(self.debug, line)
449
449
450 if not (args.breakpoint or args.statement or cell):
450 if not (args.breakpoint or args.statement or cell):
451 self._debug_post_mortem()
451 self._debug_post_mortem()
452 elif not (args.breakpoint or cell):
452 elif not (args.breakpoint or cell):
453 # If there is no breakpoints, the line is just code to execute
453 # If there is no breakpoints, the line is just code to execute
454 self._debug_exec(line, None)
454 self._debug_exec(line, None)
455 else:
455 else:
456 # Here we try to reconstruct the code from the output of
456 # Here we try to reconstruct the code from the output of
457 # parse_argstring. This might not work if the code has spaces
457 # parse_argstring. This might not work if the code has spaces
458 # For example this fails for `print("a b")`
458 # For example this fails for `print("a b")`
459 code = "\n".join(args.statement)
459 code = "\n".join(args.statement)
460 if cell:
460 if cell:
461 code += "\n" + cell
461 code += "\n" + cell
462 self._debug_exec(code, args.breakpoint)
462 self._debug_exec(code, args.breakpoint)
463
463
464 def _debug_post_mortem(self):
464 def _debug_post_mortem(self):
465 self.shell.debugger(force=True)
465 self.shell.debugger(force=True)
466
466
467 def _debug_exec(self, code, breakpoint):
467 def _debug_exec(self, code, breakpoint):
468 if breakpoint:
468 if breakpoint:
469 (filename, bp_line) = breakpoint.rsplit(':', 1)
469 (filename, bp_line) = breakpoint.rsplit(':', 1)
470 bp_line = int(bp_line)
470 bp_line = int(bp_line)
471 else:
471 else:
472 (filename, bp_line) = (None, None)
472 (filename, bp_line) = (None, None)
473 self._run_with_debugger(code, self.shell.user_ns, filename, bp_line)
473 self._run_with_debugger(code, self.shell.user_ns, filename, bp_line)
474
474
475 @line_magic
475 @line_magic
476 def tb(self, s):
476 def tb(self, s):
477 """Print the last traceback.
477 """Print the last traceback.
478
478
479 Optionally, specify an exception reporting mode, tuning the
479 Optionally, specify an exception reporting mode, tuning the
480 verbosity of the traceback. By default the currently-active exception
480 verbosity of the traceback. By default the currently-active exception
481 mode is used. See %xmode for changing exception reporting modes.
481 mode is used. See %xmode for changing exception reporting modes.
482
482
483 Valid modes: Plain, Context, Verbose, and Minimal.
483 Valid modes: Plain, Context, Verbose, and Minimal.
484 """
484 """
485 interactive_tb = self.shell.InteractiveTB
485 interactive_tb = self.shell.InteractiveTB
486 if s:
486 if s:
487 # Switch exception reporting mode for this one call.
487 # Switch exception reporting mode for this one call.
488 # Ensure it is switched back.
488 # Ensure it is switched back.
489 def xmode_switch_err(name):
489 def xmode_switch_err(name):
490 warn('Error changing %s exception modes.\n%s' %
490 warn('Error changing %s exception modes.\n%s' %
491 (name,sys.exc_info()[1]))
491 (name,sys.exc_info()[1]))
492
492
493 new_mode = s.strip().capitalize()
493 new_mode = s.strip().capitalize()
494 original_mode = interactive_tb.mode
494 original_mode = interactive_tb.mode
495 try:
495 try:
496 try:
496 try:
497 interactive_tb.set_mode(mode=new_mode)
497 interactive_tb.set_mode(mode=new_mode)
498 except Exception:
498 except Exception:
499 xmode_switch_err('user')
499 xmode_switch_err('user')
500 else:
500 else:
501 self.shell.showtraceback()
501 self.shell.showtraceback()
502 finally:
502 finally:
503 interactive_tb.set_mode(mode=original_mode)
503 interactive_tb.set_mode(mode=original_mode)
504 else:
504 else:
505 self.shell.showtraceback()
505 self.shell.showtraceback()
506
506
507 @skip_doctest
507 @skip_doctest
508 @line_magic
508 @line_magic
509 def run(self, parameter_s='', runner=None,
509 def run(self, parameter_s='', runner=None,
510 file_finder=get_py_filename):
510 file_finder=get_py_filename):
511 """Run the named file inside IPython as a program.
511 """Run the named file inside IPython as a program.
512
512
513 Usage::
513 Usage::
514
514
515 %run [-n -i -e -G]
515 %run [-n -i -e -G]
516 [( -t [-N<N>] | -d [-b<N>] | -p [profile options] )]
516 [( -t [-N<N>] | -d [-b<N>] | -p [profile options] )]
517 ( -m mod | filename ) [args]
517 ( -m mod | filename ) [args]
518
518
519 The filename argument should be either a pure Python script (with
519 The filename argument should be either a pure Python script (with
520 extension ``.py``), or a file with custom IPython syntax (such as
520 extension ``.py``), or a file with custom IPython syntax (such as
521 magics). If the latter, the file can be either a script with ``.ipy``
521 magics). If the latter, the file can be either a script with ``.ipy``
522 extension, or a Jupyter notebook with ``.ipynb`` extension. When running
522 extension, or a Jupyter notebook with ``.ipynb`` extension. When running
523 a Jupyter notebook, the output from print statements and other
523 a Jupyter notebook, the output from print statements and other
524 displayed objects will appear in the terminal (even matplotlib figures
524 displayed objects will appear in the terminal (even matplotlib figures
525 will open, if a terminal-compliant backend is being used). Note that,
525 will open, if a terminal-compliant backend is being used). Note that,
526 at the system command line, the ``jupyter run`` command offers similar
526 at the system command line, the ``jupyter run`` command offers similar
527 functionality for executing notebooks (albeit currently with some
527 functionality for executing notebooks (albeit currently with some
528 differences in supported options).
528 differences in supported options).
529
529
530 Parameters after the filename are passed as command-line arguments to
530 Parameters after the filename are passed as command-line arguments to
531 the program (put in sys.argv). Then, control returns to IPython's
531 the program (put in sys.argv). Then, control returns to IPython's
532 prompt.
532 prompt.
533
533
534 This is similar to running at a system prompt ``python file args``,
534 This is similar to running at a system prompt ``python file args``,
535 but with the advantage of giving you IPython's tracebacks, and of
535 but with the advantage of giving you IPython's tracebacks, and of
536 loading all variables into your interactive namespace for further use
536 loading all variables into your interactive namespace for further use
537 (unless -p is used, see below).
537 (unless -p is used, see below).
538
538
539 The file is executed in a namespace initially consisting only of
539 The file is executed in a namespace initially consisting only of
540 ``__name__=='__main__'`` and sys.argv constructed as indicated. It thus
540 ``__name__=='__main__'`` and sys.argv constructed as indicated. It thus
541 sees its environment as if it were being run as a stand-alone program
541 sees its environment as if it were being run as a stand-alone program
542 (except for sharing global objects such as previously imported
542 (except for sharing global objects such as previously imported
543 modules). But after execution, the IPython interactive namespace gets
543 modules). But after execution, the IPython interactive namespace gets
544 updated with all variables defined in the program (except for __name__
544 updated with all variables defined in the program (except for __name__
545 and sys.argv). This allows for very convenient loading of code for
545 and sys.argv). This allows for very convenient loading of code for
546 interactive work, while giving each program a 'clean sheet' to run in.
546 interactive work, while giving each program a 'clean sheet' to run in.
547
547
548 Arguments are expanded using shell-like glob match. Patterns
548 Arguments are expanded using shell-like glob match. Patterns
549 '*', '?', '[seq]' and '[!seq]' can be used. Additionally,
549 '*', '?', '[seq]' and '[!seq]' can be used. Additionally,
550 tilde '~' will be expanded into user's home directory. Unlike
550 tilde '~' will be expanded into user's home directory. Unlike
551 real shells, quotation does not suppress expansions. Use
551 real shells, quotation does not suppress expansions. Use
552 *two* back slashes (e.g. ``\\\\*``) to suppress expansions.
552 *two* back slashes (e.g. ``\\\\*``) to suppress expansions.
553 To completely disable these expansions, you can use -G flag.
553 To completely disable these expansions, you can use -G flag.
554
554
555 On Windows systems, the use of single quotes `'` when specifying
555 On Windows systems, the use of single quotes `'` when specifying
556 a file is not supported. Use double quotes `"`.
556 a file is not supported. Use double quotes `"`.
557
557
558 Options:
558 Options:
559
559
560 -n
560 -n
561 __name__ is NOT set to '__main__', but to the running file's name
561 __name__ is NOT set to '__main__', but to the running file's name
562 without extension (as python does under import). This allows running
562 without extension (as python does under import). This allows running
563 scripts and reloading the definitions in them without calling code
563 scripts and reloading the definitions in them without calling code
564 protected by an ``if __name__ == "__main__"`` clause.
564 protected by an ``if __name__ == "__main__"`` clause.
565
565
566 -i
566 -i
567 run the file in IPython's namespace instead of an empty one. This
567 run the file in IPython's namespace instead of an empty one. This
568 is useful if you are experimenting with code written in a text editor
568 is useful if you are experimenting with code written in a text editor
569 which depends on variables defined interactively.
569 which depends on variables defined interactively.
570
570
571 -e
571 -e
572 ignore sys.exit() calls or SystemExit exceptions in the script
572 ignore sys.exit() calls or SystemExit exceptions in the script
573 being run. This is particularly useful if IPython is being used to
573 being run. This is particularly useful if IPython is being used to
574 run unittests, which always exit with a sys.exit() call. In such
574 run unittests, which always exit with a sys.exit() call. In such
575 cases you are interested in the output of the test results, not in
575 cases you are interested in the output of the test results, not in
576 seeing a traceback of the unittest module.
576 seeing a traceback of the unittest module.
577
577
578 -t
578 -t
579 print timing information at the end of the run. IPython will give
579 print timing information at the end of the run. IPython will give
580 you an estimated CPU time consumption for your script, which under
580 you an estimated CPU time consumption for your script, which under
581 Unix uses the resource module to avoid the wraparound problems of
581 Unix uses the resource module to avoid the wraparound problems of
582 time.clock(). Under Unix, an estimate of time spent on system tasks
582 time.clock(). Under Unix, an estimate of time spent on system tasks
583 is also given (for Windows platforms this is reported as 0.0).
583 is also given (for Windows platforms this is reported as 0.0).
584
584
585 If -t is given, an additional ``-N<N>`` option can be given, where <N>
585 If -t is given, an additional ``-N<N>`` option can be given, where <N>
586 must be an integer indicating how many times you want the script to
586 must be an integer indicating how many times you want the script to
587 run. The final timing report will include total and per run results.
587 run. The final timing report will include total and per run results.
588
588
589 For example (testing the script uniq_stable.py)::
589 For example (testing the script uniq_stable.py)::
590
590
591 In [1]: run -t uniq_stable
591 In [1]: run -t uniq_stable
592
592
593 IPython CPU timings (estimated):
593 IPython CPU timings (estimated):
594 User : 0.19597 s.
594 User : 0.19597 s.
595 System: 0.0 s.
595 System: 0.0 s.
596
596
597 In [2]: run -t -N5 uniq_stable
597 In [2]: run -t -N5 uniq_stable
598
598
599 IPython CPU timings (estimated):
599 IPython CPU timings (estimated):
600 Total runs performed: 5
600 Total runs performed: 5
601 Times : Total Per run
601 Times : Total Per run
602 User : 0.910862 s, 0.1821724 s.
602 User : 0.910862 s, 0.1821724 s.
603 System: 0.0 s, 0.0 s.
603 System: 0.0 s, 0.0 s.
604
604
605 -d
605 -d
606 run your program under the control of pdb, the Python debugger.
606 run your program under the control of pdb, the Python debugger.
607 This allows you to execute your program step by step, watch variables,
607 This allows you to execute your program step by step, watch variables,
608 etc. Internally, what IPython does is similar to calling::
608 etc. Internally, what IPython does is similar to calling::
609
609
610 pdb.run('execfile("YOURFILENAME")')
610 pdb.run('execfile("YOURFILENAME")')
611
611
612 with a breakpoint set on line 1 of your file. You can change the line
612 with a breakpoint set on line 1 of your file. You can change the line
613 number for this automatic breakpoint to be <N> by using the -bN option
613 number for this automatic breakpoint to be <N> by using the -bN option
614 (where N must be an integer). For example::
614 (where N must be an integer). For example::
615
615
616 %run -d -b40 myscript
616 %run -d -b40 myscript
617
617
618 will set the first breakpoint at line 40 in myscript.py. Note that
618 will set the first breakpoint at line 40 in myscript.py. Note that
619 the first breakpoint must be set on a line which actually does
619 the first breakpoint must be set on a line which actually does
620 something (not a comment or docstring) for it to stop execution.
620 something (not a comment or docstring) for it to stop execution.
621
621
622 Or you can specify a breakpoint in a different file::
622 Or you can specify a breakpoint in a different file::
623
623
624 %run -d -b myotherfile.py:20 myscript
624 %run -d -b myotherfile.py:20 myscript
625
625
626 When the pdb debugger starts, you will see a (Pdb) prompt. You must
626 When the pdb debugger starts, you will see a (Pdb) prompt. You must
627 first enter 'c' (without quotes) to start execution up to the first
627 first enter 'c' (without quotes) to start execution up to the first
628 breakpoint.
628 breakpoint.
629
629
630 Entering 'help' gives information about the use of the debugger. You
630 Entering 'help' gives information about the use of the debugger. You
631 can easily see pdb's full documentation with "import pdb;pdb.help()"
631 can easily see pdb's full documentation with "import pdb;pdb.help()"
632 at a prompt.
632 at a prompt.
633
633
634 -p
634 -p
635 run program under the control of the Python profiler module (which
635 run program under the control of the Python profiler module (which
636 prints a detailed report of execution times, function calls, etc).
636 prints a detailed report of execution times, function calls, etc).
637
637
638 You can pass other options after -p which affect the behavior of the
638 You can pass other options after -p which affect the behavior of the
639 profiler itself. See the docs for %prun for details.
639 profiler itself. See the docs for %prun for details.
640
640
641 In this mode, the program's variables do NOT propagate back to the
641 In this mode, the program's variables do NOT propagate back to the
642 IPython interactive namespace (because they remain in the namespace
642 IPython interactive namespace (because they remain in the namespace
643 where the profiler executes them).
643 where the profiler executes them).
644
644
645 Internally this triggers a call to %prun, see its documentation for
645 Internally this triggers a call to %prun, see its documentation for
646 details on the options available specifically for profiling.
646 details on the options available specifically for profiling.
647
647
648 There is one special usage for which the text above doesn't apply:
648 There is one special usage for which the text above doesn't apply:
649 if the filename ends with .ipy[nb], the file is run as ipython script,
649 if the filename ends with .ipy[nb], the file is run as ipython script,
650 just as if the commands were written on IPython prompt.
650 just as if the commands were written on IPython prompt.
651
651
652 -m
652 -m
653 specify module name to load instead of script path. Similar to
653 specify module name to load instead of script path. Similar to
654 the -m option for the python interpreter. Use this option last if you
654 the -m option for the python interpreter. Use this option last if you
655 want to combine with other %run options. Unlike the python interpreter
655 want to combine with other %run options. Unlike the python interpreter
656 only source modules are allowed no .pyc or .pyo files.
656 only source modules are allowed no .pyc or .pyo files.
657 For example::
657 For example::
658
658
659 %run -m example
659 %run -m example
660
660
661 will run the example module.
661 will run the example module.
662
662
663 -G
663 -G
664 disable shell-like glob expansion of arguments.
664 disable shell-like glob expansion of arguments.
665
665
666 """
666 """
667
667
668 # Logic to handle issue #3664
668 # Logic to handle issue #3664
669 # Add '--' after '-m <module_name>' to ignore additional args passed to a module.
669 # Add '--' after '-m <module_name>' to ignore additional args passed to a module.
670 if '-m' in parameter_s and '--' not in parameter_s:
670 if '-m' in parameter_s and '--' not in parameter_s:
671 argv = shlex.split(parameter_s, posix=(os.name == 'posix'))
671 argv = shlex.split(parameter_s, posix=(os.name == 'posix'))
672 for idx, arg in enumerate(argv):
672 for idx, arg in enumerate(argv):
673 if arg and arg.startswith('-') and arg != '-':
673 if arg and arg.startswith('-') and arg != '-':
674 if arg == '-m':
674 if arg == '-m':
675 argv.insert(idx + 2, '--')
675 argv.insert(idx + 2, '--')
676 break
676 break
677 else:
677 else:
678 # Positional arg, break
678 # Positional arg, break
679 break
679 break
680 parameter_s = ' '.join(shlex.quote(arg) for arg in argv)
680 parameter_s = ' '.join(shlex.quote(arg) for arg in argv)
681
681
682 # get arguments and set sys.argv for program to be run.
682 # get arguments and set sys.argv for program to be run.
683 opts, arg_lst = self.parse_options(parameter_s,
683 opts, arg_lst = self.parse_options(parameter_s,
684 'nidtN:b:pD:l:rs:T:em:G',
684 'nidtN:b:pD:l:rs:T:em:G',
685 mode='list', list_all=1)
685 mode='list', list_all=1)
686 if "m" in opts:
686 if "m" in opts:
687 modulename = opts["m"][0]
687 modulename = opts["m"][0]
688 modpath = find_mod(modulename)
688 modpath = find_mod(modulename)
689 if modpath is None:
689 if modpath is None:
690 msg = '%r is not a valid modulename on sys.path'%modulename
690 msg = '%r is not a valid modulename on sys.path'%modulename
691 raise Exception(msg)
691 raise Exception(msg)
692 arg_lst = [modpath] + arg_lst
692 arg_lst = [modpath] + arg_lst
693 try:
693 try:
694 fpath = None # initialize to make sure fpath is in scope later
694 fpath = None # initialize to make sure fpath is in scope later
695 fpath = arg_lst[0]
695 fpath = arg_lst[0]
696 filename = file_finder(fpath)
696 filename = file_finder(fpath)
697 except IndexError as e:
697 except IndexError as e:
698 msg = 'you must provide at least a filename.'
698 msg = 'you must provide at least a filename.'
699 raise Exception(msg) from e
699 raise Exception(msg) from e
700 except IOError as e:
700 except IOError as e:
701 try:
701 try:
702 msg = str(e)
702 msg = str(e)
703 except UnicodeError:
703 except UnicodeError:
704 msg = e.message
704 msg = e.message
705 if os.name == 'nt' and re.match(r"^'.*'$",fpath):
705 if os.name == 'nt' and re.match(r"^'.*'$",fpath):
706 warn('For Windows, use double quotes to wrap a filename: %run "mypath\\myfile.py"')
706 warn('For Windows, use double quotes to wrap a filename: %run "mypath\\myfile.py"')
707 raise Exception(msg) from e
707 raise Exception(msg) from e
708 except TypeError:
708 except TypeError:
709 if fpath in sys.meta_path:
709 if fpath in sys.meta_path:
710 filename = ""
710 filename = ""
711 else:
711 else:
712 raise
712 raise
713
713
714 if filename.lower().endswith(('.ipy', '.ipynb')):
714 if filename.lower().endswith(('.ipy', '.ipynb')):
715 with preserve_keys(self.shell.user_ns, '__file__'):
715 with preserve_keys(self.shell.user_ns, '__file__'):
716 self.shell.user_ns['__file__'] = filename
716 self.shell.user_ns['__file__'] = filename
717 self.shell.safe_execfile_ipy(filename, raise_exceptions=True)
717 self.shell.safe_execfile_ipy(filename, raise_exceptions=True)
718 return
718 return
719
719
720 # Control the response to exit() calls made by the script being run
720 # Control the response to exit() calls made by the script being run
721 exit_ignore = 'e' in opts
721 exit_ignore = 'e' in opts
722
722
723 # Make sure that the running script gets a proper sys.argv as if it
723 # Make sure that the running script gets a proper sys.argv as if it
724 # were run from a system shell.
724 # were run from a system shell.
725 save_argv = sys.argv # save it for later restoring
725 save_argv = sys.argv # save it for later restoring
726
726
727 if 'G' in opts:
727 if 'G' in opts:
728 args = arg_lst[1:]
728 args = arg_lst[1:]
729 else:
729 else:
730 # tilde and glob expansion
730 # tilde and glob expansion
731 args = shellglob(map(os.path.expanduser, arg_lst[1:]))
731 args = shellglob(map(os.path.expanduser, arg_lst[1:]))
732
732
733 sys.argv = [filename] + args # put in the proper filename
733 sys.argv = [filename] + args # put in the proper filename
734
734
735 if 'n' in opts:
735 if 'n' in opts:
736 name = Path(filename).stem
736 name = Path(filename).stem
737 else:
737 else:
738 name = '__main__'
738 name = '__main__'
739
739
740 if 'i' in opts:
740 if 'i' in opts:
741 # Run in user's interactive namespace
741 # Run in user's interactive namespace
742 prog_ns = self.shell.user_ns
742 prog_ns = self.shell.user_ns
743 __name__save = self.shell.user_ns['__name__']
743 __name__save = self.shell.user_ns['__name__']
744 prog_ns['__name__'] = name
744 prog_ns['__name__'] = name
745 main_mod = self.shell.user_module
745 main_mod = self.shell.user_module
746
746
747 # Since '%run foo' emulates 'python foo.py' at the cmd line, we must
747 # Since '%run foo' emulates 'python foo.py' at the cmd line, we must
748 # set the __file__ global in the script's namespace
748 # set the __file__ global in the script's namespace
749 # TK: Is this necessary in interactive mode?
749 # TK: Is this necessary in interactive mode?
750 prog_ns['__file__'] = filename
750 prog_ns['__file__'] = filename
751 else:
751 else:
752 # Run in a fresh, empty namespace
752 # Run in a fresh, empty namespace
753
753
754 # The shell MUST hold a reference to prog_ns so after %run
754 # The shell MUST hold a reference to prog_ns so after %run
755 # exits, the python deletion mechanism doesn't zero it out
755 # exits, the python deletion mechanism doesn't zero it out
756 # (leaving dangling references). See interactiveshell for details
756 # (leaving dangling references). See interactiveshell for details
757 main_mod = self.shell.new_main_mod(filename, name)
757 main_mod = self.shell.new_main_mod(filename, name)
758 prog_ns = main_mod.__dict__
758 prog_ns = main_mod.__dict__
759
759
760 # pickle fix. See interactiveshell for an explanation. But we need to
760 # pickle fix. See interactiveshell for an explanation. But we need to
761 # make sure that, if we overwrite __main__, we replace it at the end
761 # make sure that, if we overwrite __main__, we replace it at the end
762 main_mod_name = prog_ns['__name__']
762 main_mod_name = prog_ns['__name__']
763
763
764 if main_mod_name == '__main__':
764 if main_mod_name == '__main__':
765 restore_main = sys.modules['__main__']
765 restore_main = sys.modules['__main__']
766 else:
766 else:
767 restore_main = False
767 restore_main = False
768
768
769 # This needs to be undone at the end to prevent holding references to
769 # This needs to be undone at the end to prevent holding references to
770 # every single object ever created.
770 # every single object ever created.
771 sys.modules[main_mod_name] = main_mod
771 sys.modules[main_mod_name] = main_mod
772
772
773 if 'p' in opts or 'd' in opts:
773 if 'p' in opts or 'd' in opts:
774 if 'm' in opts:
774 if 'm' in opts:
775 code = 'run_module(modulename, prog_ns)'
775 code = 'run_module(modulename, prog_ns)'
776 code_ns = {
776 code_ns = {
777 'run_module': self.shell.safe_run_module,
777 'run_module': self.shell.safe_run_module,
778 'prog_ns': prog_ns,
778 'prog_ns': prog_ns,
779 'modulename': modulename,
779 'modulename': modulename,
780 }
780 }
781 else:
781 else:
782 if 'd' in opts:
782 if 'd' in opts:
783 # allow exceptions to raise in debug mode
783 # allow exceptions to raise in debug mode
784 code = 'execfile(filename, prog_ns, raise_exceptions=True)'
784 code = 'execfile(filename, prog_ns, raise_exceptions=True)'
785 else:
785 else:
786 code = 'execfile(filename, prog_ns)'
786 code = 'execfile(filename, prog_ns)'
787 code_ns = {
787 code_ns = {
788 'execfile': self.shell.safe_execfile,
788 'execfile': self.shell.safe_execfile,
789 'prog_ns': prog_ns,
789 'prog_ns': prog_ns,
790 'filename': get_py_filename(filename),
790 'filename': get_py_filename(filename),
791 }
791 }
792
792
793 try:
793 try:
794 stats = None
794 stats = None
795 if 'p' in opts:
795 if 'p' in opts:
796 stats = self._run_with_profiler(code, opts, code_ns)
796 stats = self._run_with_profiler(code, opts, code_ns)
797 else:
797 else:
798 if 'd' in opts:
798 if 'd' in opts:
799 bp_file, bp_line = parse_breakpoint(
799 bp_file, bp_line = parse_breakpoint(
800 opts.get('b', ['1'])[0], filename)
800 opts.get('b', ['1'])[0], filename)
801 self._run_with_debugger(
801 self._run_with_debugger(
802 code, code_ns, filename, bp_line, bp_file)
802 code, code_ns, filename, bp_line, bp_file)
803 else:
803 else:
804 if 'm' in opts:
804 if 'm' in opts:
805 def run():
805 def run():
806 self.shell.safe_run_module(modulename, prog_ns)
806 self.shell.safe_run_module(modulename, prog_ns)
807 else:
807 else:
808 if runner is None:
808 if runner is None:
809 runner = self.default_runner
809 runner = self.default_runner
810 if runner is None:
810 if runner is None:
811 runner = self.shell.safe_execfile
811 runner = self.shell.safe_execfile
812
812
813 def run():
813 def run():
814 runner(filename, prog_ns, prog_ns,
814 runner(filename, prog_ns, prog_ns,
815 exit_ignore=exit_ignore)
815 exit_ignore=exit_ignore)
816
816
817 if 't' in opts:
817 if 't' in opts:
818 # timed execution
818 # timed execution
819 try:
819 try:
820 nruns = int(opts['N'][0])
820 nruns = int(opts['N'][0])
821 if nruns < 1:
821 if nruns < 1:
822 error('Number of runs must be >=1')
822 error('Number of runs must be >=1')
823 return
823 return
824 except (KeyError):
824 except (KeyError):
825 nruns = 1
825 nruns = 1
826 self._run_with_timing(run, nruns)
826 self._run_with_timing(run, nruns)
827 else:
827 else:
828 # regular execution
828 # regular execution
829 run()
829 run()
830
830
831 if 'i' in opts:
831 if 'i' in opts:
832 self.shell.user_ns['__name__'] = __name__save
832 self.shell.user_ns['__name__'] = __name__save
833 else:
833 else:
834 # update IPython interactive namespace
834 # update IPython interactive namespace
835
835
836 # Some forms of read errors on the file may mean the
836 # Some forms of read errors on the file may mean the
837 # __name__ key was never set; using pop we don't have to
837 # __name__ key was never set; using pop we don't have to
838 # worry about a possible KeyError.
838 # worry about a possible KeyError.
839 prog_ns.pop('__name__', None)
839 prog_ns.pop('__name__', None)
840
840
841 with preserve_keys(self.shell.user_ns, '__file__'):
841 with preserve_keys(self.shell.user_ns, '__file__'):
842 self.shell.user_ns.update(prog_ns)
842 self.shell.user_ns.update(prog_ns)
843 finally:
843 finally:
844 # It's a bit of a mystery why, but __builtins__ can change from
844 # It's a bit of a mystery why, but __builtins__ can change from
845 # being a module to becoming a dict missing some key data after
845 # being a module to becoming a dict missing some key data after
846 # %run. As best I can see, this is NOT something IPython is doing
846 # %run. As best I can see, this is NOT something IPython is doing
847 # at all, and similar problems have been reported before:
847 # at all, and similar problems have been reported before:
848 # http://coding.derkeiler.com/Archive/Python/comp.lang.python/2004-10/0188.html
848 # http://coding.derkeiler.com/Archive/Python/comp.lang.python/2004-10/0188.html
849 # Since this seems to be done by the interpreter itself, the best
849 # Since this seems to be done by the interpreter itself, the best
850 # we can do is to at least restore __builtins__ for the user on
850 # we can do is to at least restore __builtins__ for the user on
851 # exit.
851 # exit.
852 self.shell.user_ns['__builtins__'] = builtin_mod
852 self.shell.user_ns['__builtins__'] = builtin_mod
853
853
854 # Ensure key global structures are restored
854 # Ensure key global structures are restored
855 sys.argv = save_argv
855 sys.argv = save_argv
856 if restore_main:
856 if restore_main:
857 sys.modules['__main__'] = restore_main
857 sys.modules['__main__'] = restore_main
858 if '__mp_main__' in sys.modules:
858 if '__mp_main__' in sys.modules:
859 sys.modules['__mp_main__'] = restore_main
859 sys.modules['__mp_main__'] = restore_main
860 else:
860 else:
861 # Remove from sys.modules the reference to main_mod we'd
861 # Remove from sys.modules the reference to main_mod we'd
862 # added. Otherwise it will trap references to objects
862 # added. Otherwise it will trap references to objects
863 # contained therein.
863 # contained therein.
864 del sys.modules[main_mod_name]
864 del sys.modules[main_mod_name]
865
865
866 return stats
866 return stats
867
867
868 def _run_with_debugger(self, code, code_ns, filename=None,
868 def _run_with_debugger(self, code, code_ns, filename=None,
869 bp_line=None, bp_file=None):
869 bp_line=None, bp_file=None):
870 """
870 """
871 Run `code` in debugger with a break point.
871 Run `code` in debugger with a break point.
872
872
873 Parameters
873 Parameters
874 ----------
874 ----------
875 code : str
875 code : str
876 Code to execute.
876 Code to execute.
877 code_ns : dict
877 code_ns : dict
878 A namespace in which `code` is executed.
878 A namespace in which `code` is executed.
879 filename : str
879 filename : str
880 `code` is ran as if it is in `filename`.
880 `code` is ran as if it is in `filename`.
881 bp_line : int, optional
881 bp_line : int, optional
882 Line number of the break point.
882 Line number of the break point.
883 bp_file : str, optional
883 bp_file : str, optional
884 Path to the file in which break point is specified.
884 Path to the file in which break point is specified.
885 `filename` is used if not given.
885 `filename` is used if not given.
886
886
887 Raises
887 Raises
888 ------
888 ------
889 UsageError
889 UsageError
890 If the break point given by `bp_line` is not valid.
890 If the break point given by `bp_line` is not valid.
891
891
892 """
892 """
893 deb = self.shell.InteractiveTB.pdb
893 deb = self.shell.InteractiveTB.pdb
894 if not deb:
894 if not deb:
895 self.shell.InteractiveTB.pdb = self.shell.InteractiveTB.debugger_cls()
895 self.shell.InteractiveTB.pdb = self.shell.InteractiveTB.debugger_cls()
896 deb = self.shell.InteractiveTB.pdb
896 deb = self.shell.InteractiveTB.pdb
897
897
898 # deb.checkline() fails if deb.curframe exists but is None; it can
898 # deb.checkline() fails if deb.curframe exists but is None; it can
899 # handle it not existing. https://github.com/ipython/ipython/issues/10028
899 # handle it not existing. https://github.com/ipython/ipython/issues/10028
900 if hasattr(deb, 'curframe'):
900 if hasattr(deb, 'curframe'):
901 del deb.curframe
901 del deb.curframe
902
902
903 # reset Breakpoint state, which is moronically kept
903 # reset Breakpoint state, which is moronically kept
904 # in a class
904 # in a class
905 bdb.Breakpoint.next = 1
905 bdb.Breakpoint.next = 1
906 bdb.Breakpoint.bplist = {}
906 bdb.Breakpoint.bplist = {}
907 bdb.Breakpoint.bpbynumber = [None]
907 bdb.Breakpoint.bpbynumber = [None]
908 deb.clear_all_breaks()
908 deb.clear_all_breaks()
909 if bp_line is not None:
909 if bp_line is not None:
910 # Set an initial breakpoint to stop execution
910 # Set an initial breakpoint to stop execution
911 maxtries = 10
911 maxtries = 10
912 bp_file = bp_file or filename
912 bp_file = bp_file or filename
913 checkline = deb.checkline(bp_file, bp_line)
913 checkline = deb.checkline(bp_file, bp_line)
914 if not checkline:
914 if not checkline:
915 for bp in range(bp_line + 1, bp_line + maxtries + 1):
915 for bp in range(bp_line + 1, bp_line + maxtries + 1):
916 if deb.checkline(bp_file, bp):
916 if deb.checkline(bp_file, bp):
917 break
917 break
918 else:
918 else:
919 msg = ("\nI failed to find a valid line to set "
919 msg = ("\nI failed to find a valid line to set "
920 "a breakpoint\n"
920 "a breakpoint\n"
921 "after trying up to line: %s.\n"
921 "after trying up to line: %s.\n"
922 "Please set a valid breakpoint manually "
922 "Please set a valid breakpoint manually "
923 "with the -b option." % bp)
923 "with the -b option." % bp)
924 raise UsageError(msg)
924 raise UsageError(msg)
925 # if we find a good linenumber, set the breakpoint
925 # if we find a good linenumber, set the breakpoint
926 deb.do_break('%s:%s' % (bp_file, bp_line))
926 deb.do_break('%s:%s' % (bp_file, bp_line))
927
927
928 if filename:
928 if filename:
929 # Mimic Pdb._runscript(...)
929 # Mimic Pdb._runscript(...)
930 deb._wait_for_mainpyfile = True
930 deb._wait_for_mainpyfile = True
931 deb.mainpyfile = deb.canonic(filename)
931 deb.mainpyfile = deb.canonic(filename)
932
932
933 # Start file run
933 # Start file run
934 print("NOTE: Enter 'c' at the %s prompt to continue execution." % deb.prompt)
934 print("NOTE: Enter 'c' at the %s prompt to continue execution." % deb.prompt)
935 try:
935 try:
936 if filename:
936 if filename:
937 # save filename so it can be used by methods on the deb object
937 # save filename so it can be used by methods on the deb object
938 deb._exec_filename = filename
938 deb._exec_filename = filename
939 while True:
939 while True:
940 try:
940 try:
941 trace = sys.gettrace()
941 trace = sys.gettrace()
942 deb.run(code, code_ns)
942 deb.run(code, code_ns)
943 except Restart:
943 except Restart:
944 print("Restarting")
944 print("Restarting")
945 if filename:
945 if filename:
946 deb._wait_for_mainpyfile = True
946 deb._wait_for_mainpyfile = True
947 deb.mainpyfile = deb.canonic(filename)
947 deb.mainpyfile = deb.canonic(filename)
948 continue
948 continue
949 else:
949 else:
950 break
950 break
951 finally:
951 finally:
952 sys.settrace(trace)
952 sys.settrace(trace)
953
953
954
954
955 except:
955 except:
956 etype, value, tb = sys.exc_info()
956 etype, value, tb = sys.exc_info()
957 # Skip three frames in the traceback: the %run one,
957 # Skip three frames in the traceback: the %run one,
958 # one inside bdb.py, and the command-line typed by the
958 # one inside bdb.py, and the command-line typed by the
959 # user (run by exec in pdb itself).
959 # user (run by exec in pdb itself).
960 self.shell.InteractiveTB(etype, value, tb, tb_offset=3)
960 self.shell.InteractiveTB(etype, value, tb, tb_offset=3)
961
961
962 @staticmethod
962 @staticmethod
963 def _run_with_timing(run, nruns):
963 def _run_with_timing(run, nruns):
964 """
964 """
965 Run function `run` and print timing information.
965 Run function `run` and print timing information.
966
966
967 Parameters
967 Parameters
968 ----------
968 ----------
969 run : callable
969 run : callable
970 Any callable object which takes no argument.
970 Any callable object which takes no argument.
971 nruns : int
971 nruns : int
972 Number of times to execute `run`.
972 Number of times to execute `run`.
973
973
974 """
974 """
975 twall0 = time.perf_counter()
975 twall0 = time.perf_counter()
976 if nruns == 1:
976 if nruns == 1:
977 t0 = clock2()
977 t0 = clock2()
978 run()
978 run()
979 t1 = clock2()
979 t1 = clock2()
980 t_usr = t1[0] - t0[0]
980 t_usr = t1[0] - t0[0]
981 t_sys = t1[1] - t0[1]
981 t_sys = t1[1] - t0[1]
982 print("\nIPython CPU timings (estimated):")
982 print("\nIPython CPU timings (estimated):")
983 print(" User : %10.2f s." % t_usr)
983 print(" User : %10.2f s." % t_usr)
984 print(" System : %10.2f s." % t_sys)
984 print(" System : %10.2f s." % t_sys)
985 else:
985 else:
986 runs = range(nruns)
986 runs = range(nruns)
987 t0 = clock2()
987 t0 = clock2()
988 for nr in runs:
988 for nr in runs:
989 run()
989 run()
990 t1 = clock2()
990 t1 = clock2()
991 t_usr = t1[0] - t0[0]
991 t_usr = t1[0] - t0[0]
992 t_sys = t1[1] - t0[1]
992 t_sys = t1[1] - t0[1]
993 print("\nIPython CPU timings (estimated):")
993 print("\nIPython CPU timings (estimated):")
994 print("Total runs performed:", nruns)
994 print("Total runs performed:", nruns)
995 print(" Times : %10s %10s" % ('Total', 'Per run'))
995 print(" Times : %10s %10s" % ('Total', 'Per run'))
996 print(" User : %10.2f s, %10.2f s." % (t_usr, t_usr / nruns))
996 print(" User : %10.2f s, %10.2f s." % (t_usr, t_usr / nruns))
997 print(" System : %10.2f s, %10.2f s." % (t_sys, t_sys / nruns))
997 print(" System : %10.2f s, %10.2f s." % (t_sys, t_sys / nruns))
998 twall1 = time.perf_counter()
998 twall1 = time.perf_counter()
999 print("Wall time: %10.2f s." % (twall1 - twall0))
999 print("Wall time: %10.2f s." % (twall1 - twall0))
1000
1000
1001 @skip_doctest
1001 @skip_doctest
1002 @no_var_expand
1002 @no_var_expand
1003 @line_cell_magic
1003 @line_cell_magic
1004 @needs_local_scope
1004 @needs_local_scope
1005 def timeit(self, line='', cell=None, local_ns=None):
1005 def timeit(self, line='', cell=None, local_ns=None):
1006 """Time execution of a Python statement or expression
1006 """Time execution of a Python statement or expression
1007
1007
1008 Usage, in line mode:
1008 Usage, in line mode:
1009 %timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] statement
1009 %timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] statement
1010 or in cell mode:
1010 or in cell mode:
1011 %%timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] setup_code
1011 %%timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] setup_code
1012 code
1012 code
1013 code...
1013 code...
1014
1014
1015 Time execution of a Python statement or expression using the timeit
1015 Time execution of a Python statement or expression using the timeit
1016 module. This function can be used both as a line and cell magic:
1016 module. This function can be used both as a line and cell magic:
1017
1017
1018 - In line mode you can time a single-line statement (though multiple
1018 - In line mode you can time a single-line statement (though multiple
1019 ones can be chained with using semicolons).
1019 ones can be chained with using semicolons).
1020
1020
1021 - In cell mode, the statement in the first line is used as setup code
1021 - In cell mode, the statement in the first line is used as setup code
1022 (executed but not timed) and the body of the cell is timed. The cell
1022 (executed but not timed) and the body of the cell is timed. The cell
1023 body has access to any variables created in the setup code.
1023 body has access to any variables created in the setup code.
1024
1024
1025 Options:
1025 Options:
1026 -n<N>: execute the given statement <N> times in a loop. If <N> is not
1026 -n<N>: execute the given statement <N> times in a loop. If <N> is not
1027 provided, <N> is determined so as to get sufficient accuracy.
1027 provided, <N> is determined so as to get sufficient accuracy.
1028
1028
1029 -r<R>: number of repeats <R>, each consisting of <N> loops, and take the
1029 -r<R>: number of repeats <R>, each consisting of <N> loops, and take the
1030 best result.
1030 best result.
1031 Default: 7
1031 Default: 7
1032
1032
1033 -t: use time.time to measure the time, which is the default on Unix.
1033 -t: use time.time to measure the time, which is the default on Unix.
1034 This function measures wall time.
1034 This function measures wall time.
1035
1035
1036 -c: use time.clock to measure the time, which is the default on
1036 -c: use time.clock to measure the time, which is the default on
1037 Windows and measures wall time. On Unix, resource.getrusage is used
1037 Windows and measures wall time. On Unix, resource.getrusage is used
1038 instead and returns the CPU user time.
1038 instead and returns the CPU user time.
1039
1039
1040 -p<P>: use a precision of <P> digits to display the timing result.
1040 -p<P>: use a precision of <P> digits to display the timing result.
1041 Default: 3
1041 Default: 3
1042
1042
1043 -q: Quiet, do not print result.
1043 -q: Quiet, do not print result.
1044
1044
1045 -o: return a TimeitResult that can be stored in a variable to inspect
1045 -o: return a TimeitResult that can be stored in a variable to inspect
1046 the result in more details.
1046 the result in more details.
1047
1047
1048 .. versionchanged:: 7.3
1048 .. versionchanged:: 7.3
1049 User variables are no longer expanded,
1049 User variables are no longer expanded,
1050 the magic line is always left unmodified.
1050 the magic line is always left unmodified.
1051
1051
1052 Examples
1052 Examples
1053 --------
1053 --------
1054 ::
1054 ::
1055
1055
1056 In [1]: %timeit pass
1056 In [1]: %timeit pass
1057 8.26 ns ± 0.12 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)
1057 8.26 ns ± 0.12 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)
1058
1058
1059 In [2]: u = None
1059 In [2]: u = None
1060
1060
1061 In [3]: %timeit u is None
1061 In [3]: %timeit u is None
1062 29.9 ns ± 0.643 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
1062 29.9 ns ± 0.643 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
1063
1063
1064 In [4]: %timeit -r 4 u == None
1064 In [4]: %timeit -r 4 u == None
1065
1065
1066 In [5]: import time
1066 In [5]: import time
1067
1067
1068 In [6]: %timeit -n1 time.sleep(2)
1068 In [6]: %timeit -n1 time.sleep(2)
1069
1069
1070 The times reported by %timeit will be slightly higher than those
1070 The times reported by %timeit will be slightly higher than those
1071 reported by the timeit.py script when variables are accessed. This is
1071 reported by the timeit.py script when variables are accessed. This is
1072 due to the fact that %timeit executes the statement in the namespace
1072 due to the fact that %timeit executes the statement in the namespace
1073 of the shell, compared with timeit.py, which uses a single setup
1073 of the shell, compared with timeit.py, which uses a single setup
1074 statement to import function or create variables. Generally, the bias
1074 statement to import function or create variables. Generally, the bias
1075 does not matter as long as results from timeit.py are not mixed with
1075 does not matter as long as results from timeit.py are not mixed with
1076 those from %timeit."""
1076 those from %timeit."""
1077
1077
1078 opts, stmt = self.parse_options(
1078 opts, stmt = self.parse_options(
1079 line, "n:r:tcp:qo", posix=False, strict=False, preserve_non_opts=True
1079 line, "n:r:tcp:qo", posix=False, strict=False, preserve_non_opts=True
1080 )
1080 )
1081 if stmt == "" and cell is None:
1081 if stmt == "" and cell is None:
1082 return
1082 return
1083
1083
1084 timefunc = timeit.default_timer
1084 timefunc = timeit.default_timer
1085 number = int(getattr(opts, "n", 0))
1085 number = int(getattr(opts, "n", 0))
1086 default_repeat = 7 if timeit.default_repeat < 7 else timeit.default_repeat
1086 default_repeat = 7 if timeit.default_repeat < 7 else timeit.default_repeat
1087 repeat = int(getattr(opts, "r", default_repeat))
1087 repeat = int(getattr(opts, "r", default_repeat))
1088 precision = int(getattr(opts, "p", 3))
1088 precision = int(getattr(opts, "p", 3))
1089 quiet = 'q' in opts
1089 quiet = 'q' in opts
1090 return_result = 'o' in opts
1090 return_result = 'o' in opts
1091 if hasattr(opts, "t"):
1091 if hasattr(opts, "t"):
1092 timefunc = time.time
1092 timefunc = time.time
1093 if hasattr(opts, "c"):
1093 if hasattr(opts, "c"):
1094 timefunc = clock
1094 timefunc = clock
1095
1095
1096 timer = Timer(timer=timefunc)
1096 timer = Timer(timer=timefunc)
1097 # this code has tight coupling to the inner workings of timeit.Timer,
1097 # this code has tight coupling to the inner workings of timeit.Timer,
1098 # but is there a better way to achieve that the code stmt has access
1098 # but is there a better way to achieve that the code stmt has access
1099 # to the shell namespace?
1099 # to the shell namespace?
1100 transform = self.shell.transform_cell
1100 transform = self.shell.transform_cell
1101
1101
1102 if cell is None:
1102 if cell is None:
1103 # called as line magic
1103 # called as line magic
1104 ast_setup = self.shell.compile.ast_parse("pass")
1104 ast_setup = self.shell.compile.ast_parse("pass")
1105 ast_stmt = self.shell.compile.ast_parse(transform(stmt))
1105 ast_stmt = self.shell.compile.ast_parse(transform(stmt))
1106 else:
1106 else:
1107 ast_setup = self.shell.compile.ast_parse(transform(stmt))
1107 ast_setup = self.shell.compile.ast_parse(transform(stmt))
1108 ast_stmt = self.shell.compile.ast_parse(transform(cell))
1108 ast_stmt = self.shell.compile.ast_parse(transform(cell))
1109
1109
1110 ast_setup = self.shell.transform_ast(ast_setup)
1110 ast_setup = self.shell.transform_ast(ast_setup)
1111 ast_stmt = self.shell.transform_ast(ast_stmt)
1111 ast_stmt = self.shell.transform_ast(ast_stmt)
1112
1112
1113 # Check that these compile to valid Python code *outside* the timer func
1113 # Check that these compile to valid Python code *outside* the timer func
1114 # Invalid code may become valid when put inside the function & loop,
1114 # Invalid code may become valid when put inside the function & loop,
1115 # which messes up error messages.
1115 # which messes up error messages.
1116 # https://github.com/ipython/ipython/issues/10636
1116 # https://github.com/ipython/ipython/issues/10636
1117 self.shell.compile(ast_setup, "<magic-timeit-setup>", "exec")
1117 self.shell.compile(ast_setup, "<magic-timeit-setup>", "exec")
1118 self.shell.compile(ast_stmt, "<magic-timeit-stmt>", "exec")
1118 self.shell.compile(ast_stmt, "<magic-timeit-stmt>", "exec")
1119
1119
1120 # This codestring is taken from timeit.template - we fill it in as an
1120 # This codestring is taken from timeit.template - we fill it in as an
1121 # AST, so that we can apply our AST transformations to the user code
1121 # AST, so that we can apply our AST transformations to the user code
1122 # without affecting the timing code.
1122 # without affecting the timing code.
1123 timeit_ast_template = ast.parse('def inner(_it, _timer):\n'
1123 timeit_ast_template = ast.parse('def inner(_it, _timer):\n'
1124 ' setup\n'
1124 ' setup\n'
1125 ' _t0 = _timer()\n'
1125 ' _t0 = _timer()\n'
1126 ' for _i in _it:\n'
1126 ' for _i in _it:\n'
1127 ' stmt\n'
1127 ' stmt\n'
1128 ' _t1 = _timer()\n'
1128 ' _t1 = _timer()\n'
1129 ' return _t1 - _t0\n')
1129 ' return _t1 - _t0\n')
1130
1130
1131 timeit_ast = TimeitTemplateFiller(ast_setup, ast_stmt).visit(timeit_ast_template)
1131 timeit_ast = TimeitTemplateFiller(ast_setup, ast_stmt).visit(timeit_ast_template)
1132 timeit_ast = ast.fix_missing_locations(timeit_ast)
1132 timeit_ast = ast.fix_missing_locations(timeit_ast)
1133
1133
1134 # Track compilation time so it can be reported if too long
1134 # Track compilation time so it can be reported if too long
1135 # Minimum time above which compilation time will be reported
1135 # Minimum time above which compilation time will be reported
1136 tc_min = 0.1
1136 tc_min = 0.1
1137
1137
1138 t0 = clock()
1138 t0 = clock()
1139 code = self.shell.compile(timeit_ast, "<magic-timeit>", "exec")
1139 code = self.shell.compile(timeit_ast, "<magic-timeit>", "exec")
1140 tc = clock()-t0
1140 tc = clock()-t0
1141
1141
1142 ns = {}
1142 ns = {}
1143 glob = self.shell.user_ns
1143 glob = self.shell.user_ns
1144 # handles global vars with same name as local vars. We store them in conflict_globs.
1144 # handles global vars with same name as local vars. We store them in conflict_globs.
1145 conflict_globs = {}
1145 conflict_globs = {}
1146 if local_ns and cell is None:
1146 if local_ns and cell is None:
1147 for var_name, var_val in glob.items():
1147 for var_name, var_val in glob.items():
1148 if var_name in local_ns:
1148 if var_name in local_ns:
1149 conflict_globs[var_name] = var_val
1149 conflict_globs[var_name] = var_val
1150 glob.update(local_ns)
1150 glob.update(local_ns)
1151
1151
1152 exec(code, glob, ns)
1152 exec(code, glob, ns)
1153 timer.inner = ns["inner"]
1153 timer.inner = ns["inner"]
1154
1154
1155 # This is used to check if there is a huge difference between the
1155 # This is used to check if there is a huge difference between the
1156 # best and worst timings.
1156 # best and worst timings.
1157 # Issue: https://github.com/ipython/ipython/issues/6471
1157 # Issue: https://github.com/ipython/ipython/issues/6471
1158 if number == 0:
1158 if number == 0:
1159 # determine number so that 0.2 <= total time < 2.0
1159 # determine number so that 0.2 <= total time < 2.0
1160 for index in range(0, 10):
1160 for index in range(0, 10):
1161 number = 10 ** index
1161 number = 10 ** index
1162 time_number = timer.timeit(number)
1162 time_number = timer.timeit(number)
1163 if time_number >= 0.2:
1163 if time_number >= 0.2:
1164 break
1164 break
1165
1165
1166 all_runs = timer.repeat(repeat, number)
1166 all_runs = timer.repeat(repeat, number)
1167 best = min(all_runs) / number
1167 best = min(all_runs) / number
1168 worst = max(all_runs) / number
1168 worst = max(all_runs) / number
1169 timeit_result = TimeitResult(number, repeat, best, worst, all_runs, tc, precision)
1169 timeit_result = TimeitResult(number, repeat, best, worst, all_runs, tc, precision)
1170
1170
1171 # Restore global vars from conflict_globs
1171 # Restore global vars from conflict_globs
1172 if conflict_globs:
1172 if conflict_globs:
1173 glob.update(conflict_globs)
1173 glob.update(conflict_globs)
1174
1174
1175 if not quiet :
1175 if not quiet :
1176 # Check best timing is greater than zero to avoid a
1176 # Check best timing is greater than zero to avoid a
1177 # ZeroDivisionError.
1177 # ZeroDivisionError.
1178 # In cases where the slowest timing is lesser than a microsecond
1178 # In cases where the slowest timing is lesser than a microsecond
1179 # we assume that it does not really matter if the fastest
1179 # we assume that it does not really matter if the fastest
1180 # timing is 4 times faster than the slowest timing or not.
1180 # timing is 4 times faster than the slowest timing or not.
1181 if worst > 4 * best and best > 0 and worst > 1e-6:
1181 if worst > 4 * best and best > 0 and worst > 1e-6:
1182 print("The slowest run took %0.2f times longer than the "
1182 print("The slowest run took %0.2f times longer than the "
1183 "fastest. This could mean that an intermediate result "
1183 "fastest. This could mean that an intermediate result "
1184 "is being cached." % (worst / best))
1184 "is being cached." % (worst / best))
1185
1185
1186 print( timeit_result )
1186 print( timeit_result )
1187
1187
1188 if tc > tc_min:
1188 if tc > tc_min:
1189 print("Compiler time: %.2f s" % tc)
1189 print("Compiler time: %.2f s" % tc)
1190 if return_result:
1190 if return_result:
1191 return timeit_result
1191 return timeit_result
1192
1192
1193 @skip_doctest
1193 @skip_doctest
1194 @no_var_expand
1194 @no_var_expand
1195 @needs_local_scope
1195 @needs_local_scope
1196 @line_cell_magic
1196 @line_cell_magic
1197 def time(self,line='', cell=None, local_ns=None):
1197 def time(self,line='', cell=None, local_ns=None):
1198 """Time execution of a Python statement or expression.
1198 """Time execution of a Python statement or expression.
1199
1199
1200 The CPU and wall clock times are printed, and the value of the
1200 The CPU and wall clock times are printed, and the value of the
1201 expression (if any) is returned. Note that under Win32, system time
1201 expression (if any) is returned. Note that under Win32, system time
1202 is always reported as 0, since it can not be measured.
1202 is always reported as 0, since it can not be measured.
1203
1203
1204 This function can be used both as a line and cell magic:
1204 This function can be used both as a line and cell magic:
1205
1205
1206 - In line mode you can time a single-line statement (though multiple
1206 - In line mode you can time a single-line statement (though multiple
1207 ones can be chained with using semicolons).
1207 ones can be chained with using semicolons).
1208
1208
1209 - In cell mode, you can time the cell body (a directly
1209 - In cell mode, you can time the cell body (a directly
1210 following statement raises an error).
1210 following statement raises an error).
1211
1211
1212 This function provides very basic timing functionality. Use the timeit
1212 This function provides very basic timing functionality. Use the timeit
1213 magic for more control over the measurement.
1213 magic for more control over the measurement.
1214
1214
1215 .. versionchanged:: 7.3
1215 .. versionchanged:: 7.3
1216 User variables are no longer expanded,
1216 User variables are no longer expanded,
1217 the magic line is always left unmodified.
1217 the magic line is always left unmodified.
1218
1218
1219 Examples
1219 Examples
1220 --------
1220 --------
1221 ::
1221 ::
1222
1222
1223 In [1]: %time 2**128
1223 In [1]: %time 2**128
1224 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1224 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1225 Wall time: 0.00
1225 Wall time: 0.00
1226 Out[1]: 340282366920938463463374607431768211456L
1226 Out[1]: 340282366920938463463374607431768211456L
1227
1227
1228 In [2]: n = 1000000
1228 In [2]: n = 1000000
1229
1229
1230 In [3]: %time sum(range(n))
1230 In [3]: %time sum(range(n))
1231 CPU times: user 1.20 s, sys: 0.05 s, total: 1.25 s
1231 CPU times: user 1.20 s, sys: 0.05 s, total: 1.25 s
1232 Wall time: 1.37
1232 Wall time: 1.37
1233 Out[3]: 499999500000L
1233 Out[3]: 499999500000L
1234
1234
1235 In [4]: %time print 'hello world'
1235 In [4]: %time print 'hello world'
1236 hello world
1236 hello world
1237 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1237 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1238 Wall time: 0.00
1238 Wall time: 0.00
1239
1239
1240 .. note::
1240 .. note::
1241 The time needed by Python to compile the given expression will be
1241 The time needed by Python to compile the given expression will be
1242 reported if it is more than 0.1s.
1242 reported if it is more than 0.1s.
1243
1243
1244 In the example below, the actual exponentiation is done by Python
1244 In the example below, the actual exponentiation is done by Python
1245 at compilation time, so while the expression can take a noticeable
1245 at compilation time, so while the expression can take a noticeable
1246 amount of time to compute, that time is purely due to the
1246 amount of time to compute, that time is purely due to the
1247 compilation::
1247 compilation::
1248
1248
1249 In [5]: %time 3**9999;
1249 In [5]: %time 3**9999;
1250 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1250 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1251 Wall time: 0.00 s
1251 Wall time: 0.00 s
1252
1252
1253 In [6]: %time 3**999999;
1253 In [6]: %time 3**999999;
1254 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1254 CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
1255 Wall time: 0.00 s
1255 Wall time: 0.00 s
1256 Compiler : 0.78 s
1256 Compiler : 0.78 s
1257 """
1257 """
1258 # fail immediately if the given expression can't be compiled
1258 # fail immediately if the given expression can't be compiled
1259
1259
1260 if line and cell:
1260 if line and cell:
1261 raise UsageError("Can't use statement directly after '%%time'!")
1261 raise UsageError("Can't use statement directly after '%%time'!")
1262
1262
1263 if cell:
1263 if cell:
1264 expr = self.shell.transform_cell(cell)
1264 expr = self.shell.transform_cell(cell)
1265 else:
1265 else:
1266 expr = self.shell.transform_cell(line)
1266 expr = self.shell.transform_cell(line)
1267
1267
1268 # Minimum time above which parse time will be reported
1268 # Minimum time above which parse time will be reported
1269 tp_min = 0.1
1269 tp_min = 0.1
1270
1270
1271 t0 = clock()
1271 t0 = clock()
1272 expr_ast = self.shell.compile.ast_parse(expr)
1272 expr_ast = self.shell.compile.ast_parse(expr)
1273 tp = clock()-t0
1273 tp = clock()-t0
1274
1274
1275 # Apply AST transformations
1275 # Apply AST transformations
1276 expr_ast = self.shell.transform_ast(expr_ast)
1276 expr_ast = self.shell.transform_ast(expr_ast)
1277
1277
1278 # Minimum time above which compilation time will be reported
1278 # Minimum time above which compilation time will be reported
1279 tc_min = 0.1
1279 tc_min = 0.1
1280
1280
1281 expr_val=None
1281 expr_val=None
1282 if len(expr_ast.body)==1 and isinstance(expr_ast.body[0], ast.Expr):
1282 if len(expr_ast.body)==1 and isinstance(expr_ast.body[0], ast.Expr):
1283 mode = 'eval'
1283 mode = 'eval'
1284 source = '<timed eval>'
1284 source = '<timed eval>'
1285 expr_ast = ast.Expression(expr_ast.body[0].value)
1285 expr_ast = ast.Expression(expr_ast.body[0].value)
1286 else:
1286 else:
1287 mode = 'exec'
1287 mode = 'exec'
1288 source = '<timed exec>'
1288 source = '<timed exec>'
1289 # multi-line %%time case
1289 # multi-line %%time case
1290 if len(expr_ast.body) > 1 and isinstance(expr_ast.body[-1], ast.Expr):
1290 if len(expr_ast.body) > 1 and isinstance(expr_ast.body[-1], ast.Expr):
1291 expr_val= expr_ast.body[-1]
1291 expr_val= expr_ast.body[-1]
1292 expr_ast = expr_ast.body[:-1]
1292 expr_ast = expr_ast.body[:-1]
1293 expr_ast = Module(expr_ast, [])
1293 expr_ast = Module(expr_ast, [])
1294 expr_val = ast.Expression(expr_val.value)
1294 expr_val = ast.Expression(expr_val.value)
1295
1295
1296 t0 = clock()
1296 t0 = clock()
1297 code = self.shell.compile(expr_ast, source, mode)
1297 code = self.shell.compile(expr_ast, source, mode)
1298 tc = clock()-t0
1298 tc = clock()-t0
1299
1299
1300 # skew measurement as little as possible
1300 # skew measurement as little as possible
1301 glob = self.shell.user_ns
1301 glob = self.shell.user_ns
1302 wtime = time.time
1302 wtime = time.time
1303 # time execution
1303 # time execution
1304 wall_st = wtime()
1304 wall_st = wtime()
1305 if mode=='eval':
1305 if mode=='eval':
1306 st = clock2()
1306 st = clock2()
1307 try:
1307 try:
1308 out = eval(code, glob, local_ns)
1308 out = eval(code, glob, local_ns)
1309 except:
1309 except:
1310 self.shell.showtraceback()
1310 self.shell.showtraceback()
1311 return
1311 return
1312 end = clock2()
1312 end = clock2()
1313 else:
1313 else:
1314 st = clock2()
1314 st = clock2()
1315 try:
1315 try:
1316 exec(code, glob, local_ns)
1316 exec(code, glob, local_ns)
1317 out=None
1317 out=None
1318 # multi-line %%time case
1318 # multi-line %%time case
1319 if expr_val is not None:
1319 if expr_val is not None:
1320 code_2 = self.shell.compile(expr_val, source, 'eval')
1320 code_2 = self.shell.compile(expr_val, source, 'eval')
1321 out = eval(code_2, glob, local_ns)
1321 out = eval(code_2, glob, local_ns)
1322 except:
1322 except:
1323 self.shell.showtraceback()
1323 self.shell.showtraceback()
1324 return
1324 return
1325 end = clock2()
1325 end = clock2()
1326
1326
1327 wall_end = wtime()
1327 wall_end = wtime()
1328 # Compute actual times and report
1328 # Compute actual times and report
1329 wall_time = wall_end - wall_st
1329 wall_time = wall_end - wall_st
1330 cpu_user = end[0] - st[0]
1330 cpu_user = end[0] - st[0]
1331 cpu_sys = end[1] - st[1]
1331 cpu_sys = end[1] - st[1]
1332 cpu_tot = cpu_user + cpu_sys
1332 cpu_tot = cpu_user + cpu_sys
1333 # On windows cpu_sys is always zero, so only total is displayed
1333 # On windows cpu_sys is always zero, so only total is displayed
1334 if sys.platform != "win32":
1334 if sys.platform != "win32":
1335 print(
1335 print(
1336 f"CPU times: user {_format_time(cpu_user)}, sys: {_format_time(cpu_sys)}, total: {_format_time(cpu_tot)}"
1336 f"CPU times: user {_format_time(cpu_user)}, sys: {_format_time(cpu_sys)}, total: {_format_time(cpu_tot)}"
1337 )
1337 )
1338 else:
1338 else:
1339 print(f"CPU times: total: {_format_time(cpu_tot)}")
1339 print(f"CPU times: total: {_format_time(cpu_tot)}")
1340 print(f"Wall time: {_format_time(wall_time)}")
1340 print(f"Wall time: {_format_time(wall_time)}")
1341 if tc > tc_min:
1341 if tc > tc_min:
1342 print(f"Compiler : {_format_time(tc)}")
1342 print(f"Compiler : {_format_time(tc)}")
1343 if tp > tp_min:
1343 if tp > tp_min:
1344 print(f"Parser : {_format_time(tp)}")
1344 print(f"Parser : {_format_time(tp)}")
1345 return out
1345 return out
1346
1346
1347 @skip_doctest
1347 @skip_doctest
1348 @line_magic
1348 @line_magic
1349 def macro(self, parameter_s=''):
1349 def macro(self, parameter_s=''):
1350 """Define a macro for future re-execution. It accepts ranges of history,
1350 """Define a macro for future re-execution. It accepts ranges of history,
1351 filenames or string objects.
1351 filenames or string objects.
1352
1352
1353 Usage:\\
1353 Usage:\\
1354 %macro [options] name n1-n2 n3-n4 ... n5 .. n6 ...
1354 %macro [options] name n1-n2 n3-n4 ... n5 .. n6 ...
1355
1355
1356 Options:
1356 Options:
1357
1357
1358 -r: use 'raw' input. By default, the 'processed' history is used,
1358 -r: use 'raw' input. By default, the 'processed' history is used,
1359 so that magics are loaded in their transformed version to valid
1359 so that magics are loaded in their transformed version to valid
1360 Python. If this option is given, the raw input as typed at the
1360 Python. If this option is given, the raw input as typed at the
1361 command line is used instead.
1361 command line is used instead.
1362
1362
1363 -q: quiet macro definition. By default, a tag line is printed
1363 -q: quiet macro definition. By default, a tag line is printed
1364 to indicate the macro has been created, and then the contents of
1364 to indicate the macro has been created, and then the contents of
1365 the macro are printed. If this option is given, then no printout
1365 the macro are printed. If this option is given, then no printout
1366 is produced once the macro is created.
1366 is produced once the macro is created.
1367
1367
1368 This will define a global variable called `name` which is a string
1368 This will define a global variable called `name` which is a string
1369 made of joining the slices and lines you specify (n1,n2,... numbers
1369 made of joining the slices and lines you specify (n1,n2,... numbers
1370 above) from your input history into a single string. This variable
1370 above) from your input history into a single string. This variable
1371 acts like an automatic function which re-executes those lines as if
1371 acts like an automatic function which re-executes those lines as if
1372 you had typed them. You just type 'name' at the prompt and the code
1372 you had typed them. You just type 'name' at the prompt and the code
1373 executes.
1373 executes.
1374
1374
1375 The syntax for indicating input ranges is described in %history.
1375 The syntax for indicating input ranges is described in %history.
1376
1376
1377 Note: as a 'hidden' feature, you can also use traditional python slice
1377 Note: as a 'hidden' feature, you can also use traditional python slice
1378 notation, where N:M means numbers N through M-1.
1378 notation, where N:M means numbers N through M-1.
1379
1379
1380 For example, if your history contains (print using %hist -n )::
1380 For example, if your history contains (print using %hist -n )::
1381
1381
1382 44: x=1
1382 44: x=1
1383 45: y=3
1383 45: y=3
1384 46: z=x+y
1384 46: z=x+y
1385 47: print x
1385 47: print x
1386 48: a=5
1386 48: a=5
1387 49: print 'x',x,'y',y
1387 49: print 'x',x,'y',y
1388
1388
1389 you can create a macro with lines 44 through 47 (included) and line 49
1389 you can create a macro with lines 44 through 47 (included) and line 49
1390 called my_macro with::
1390 called my_macro with::
1391
1391
1392 In [55]: %macro my_macro 44-47 49
1392 In [55]: %macro my_macro 44-47 49
1393
1393
1394 Now, typing `my_macro` (without quotes) will re-execute all this code
1394 Now, typing `my_macro` (without quotes) will re-execute all this code
1395 in one pass.
1395 in one pass.
1396
1396
1397 You don't need to give the line-numbers in order, and any given line
1397 You don't need to give the line-numbers in order, and any given line
1398 number can appear multiple times. You can assemble macros with any
1398 number can appear multiple times. You can assemble macros with any
1399 lines from your input history in any order.
1399 lines from your input history in any order.
1400
1400
1401 The macro is a simple object which holds its value in an attribute,
1401 The macro is a simple object which holds its value in an attribute,
1402 but IPython's display system checks for macros and executes them as
1402 but IPython's display system checks for macros and executes them as
1403 code instead of printing them when you type their name.
1403 code instead of printing them when you type their name.
1404
1404
1405 You can view a macro's contents by explicitly printing it with::
1405 You can view a macro's contents by explicitly printing it with::
1406
1406
1407 print macro_name
1407 print macro_name
1408
1408
1409 """
1409 """
1410 opts,args = self.parse_options(parameter_s,'rq',mode='list')
1410 opts,args = self.parse_options(parameter_s,'rq',mode='list')
1411 if not args: # List existing macros
1411 if not args: # List existing macros
1412 return sorted(k for k,v in self.shell.user_ns.items() if isinstance(v, Macro))
1412 return sorted(k for k,v in self.shell.user_ns.items() if isinstance(v, Macro))
1413 if len(args) == 1:
1413 if len(args) == 1:
1414 raise UsageError(
1414 raise UsageError(
1415 "%macro insufficient args; usage '%macro name n1-n2 n3-4...")
1415 "%macro insufficient args; usage '%macro name n1-n2 n3-4...")
1416 name, codefrom = args[0], " ".join(args[1:])
1416 name, codefrom = args[0], " ".join(args[1:])
1417
1417
1418 #print 'rng',ranges # dbg
1418 #print 'rng',ranges # dbg
1419 try:
1419 try:
1420 lines = self.shell.find_user_code(codefrom, 'r' in opts)
1420 lines = self.shell.find_user_code(codefrom, 'r' in opts)
1421 except (ValueError, TypeError) as e:
1421 except (ValueError, TypeError) as e:
1422 print(e.args[0])
1422 print(e.args[0])
1423 return
1423 return
1424 macro = Macro(lines)
1424 macro = Macro(lines)
1425 self.shell.define_macro(name, macro)
1425 self.shell.define_macro(name, macro)
1426 if not ( 'q' in opts) :
1426 if not ( 'q' in opts) :
1427 print('Macro `%s` created. To execute, type its name (without quotes).' % name)
1427 print('Macro `%s` created. To execute, type its name (without quotes).' % name)
1428 print('=== Macro contents: ===')
1428 print('=== Macro contents: ===')
1429 print(macro, end=' ')
1429 print(macro, end=' ')
1430
1430
1431 @magic_arguments.magic_arguments()
1431 @magic_arguments.magic_arguments()
1432 @magic_arguments.argument('output', type=str, default='', nargs='?',
1432 @magic_arguments.argument('output', type=str, default='', nargs='?',
1433 help="""The name of the variable in which to store output.
1433 help="""The name of the variable in which to store output.
1434 This is a utils.io.CapturedIO object with stdout/err attributes
1434 This is a utils.io.CapturedIO object with stdout/err attributes
1435 for the text of the captured output.
1435 for the text of the captured output.
1436
1436
1437 CapturedOutput also has a show() method for displaying the output,
1437 CapturedOutput also has a show() method for displaying the output,
1438 and __call__ as well, so you can use that to quickly display the
1438 and __call__ as well, so you can use that to quickly display the
1439 output.
1439 output.
1440
1440
1441 If unspecified, captured output is discarded.
1441 If unspecified, captured output is discarded.
1442 """
1442 """
1443 )
1443 )
1444 @magic_arguments.argument('--no-stderr', action="store_true",
1444 @magic_arguments.argument('--no-stderr', action="store_true",
1445 help="""Don't capture stderr."""
1445 help="""Don't capture stderr."""
1446 )
1446 )
1447 @magic_arguments.argument('--no-stdout', action="store_true",
1447 @magic_arguments.argument('--no-stdout', action="store_true",
1448 help="""Don't capture stdout."""
1448 help="""Don't capture stdout."""
1449 )
1449 )
1450 @magic_arguments.argument('--no-display', action="store_true",
1450 @magic_arguments.argument('--no-display', action="store_true",
1451 help="""Don't capture IPython's rich display."""
1451 help="""Don't capture IPython's rich display."""
1452 )
1452 )
1453 @cell_magic
1453 @cell_magic
1454 def capture(self, line, cell):
1454 def capture(self, line, cell):
1455 """run the cell, capturing stdout, stderr, and IPython's rich display() calls."""
1455 """run the cell, capturing stdout, stderr, and IPython's rich display() calls."""
1456 args = magic_arguments.parse_argstring(self.capture, line)
1456 args = magic_arguments.parse_argstring(self.capture, line)
1457 out = not args.no_stdout
1457 out = not args.no_stdout
1458 err = not args.no_stderr
1458 err = not args.no_stderr
1459 disp = not args.no_display
1459 disp = not args.no_display
1460 with capture_output(out, err, disp) as io:
1460 with capture_output(out, err, disp) as io:
1461 self.shell.run_cell(cell)
1461 self.shell.run_cell(cell)
1462 if args.output:
1462 if args.output:
1463 self.shell.user_ns[args.output] = io
1463 self.shell.user_ns[args.output] = io
1464
1464
1465 def parse_breakpoint(text, current_file):
1465 def parse_breakpoint(text, current_file):
1466 '''Returns (file, line) for file:line and (current_file, line) for line'''
1466 '''Returns (file, line) for file:line and (current_file, line) for line'''
1467 colon = text.find(':')
1467 colon = text.find(':')
1468 if colon == -1:
1468 if colon == -1:
1469 return current_file, int(text)
1469 return current_file, int(text)
1470 else:
1470 else:
1471 return text[:colon], int(text[colon+1:])
1471 return text[:colon], int(text[colon+1:])
1472
1472
1473 def _format_time(timespan, precision=3):
1473 def _format_time(timespan, precision=3):
1474 """Formats the timespan in a human readable form"""
1474 """Formats the timespan in a human readable form"""
1475
1475
1476 if timespan >= 60.0:
1476 if timespan >= 60.0:
1477 # we have more than a minute, format that in a human readable form
1477 # we have more than a minute, format that in a human readable form
1478 # Idea from http://snipplr.com/view/5713/
1478 # Idea from http://snipplr.com/view/5713/
1479 parts = [("d", 60*60*24),("h", 60*60),("min", 60), ("s", 1)]
1479 parts = [("d", 60*60*24),("h", 60*60),("min", 60), ("s", 1)]
1480 time = []
1480 time = []
1481 leftover = timespan
1481 leftover = timespan
1482 for suffix, length in parts:
1482 for suffix, length in parts:
1483 value = int(leftover / length)
1483 value = int(leftover / length)
1484 if value > 0:
1484 if value > 0:
1485 leftover = leftover % length
1485 leftover = leftover % length
1486 time.append(u'%s%s' % (str(value), suffix))
1486 time.append(u'%s%s' % (str(value), suffix))
1487 if leftover < 1:
1487 if leftover < 1:
1488 break
1488 break
1489 return " ".join(time)
1489 return " ".join(time)
1490
1490
1491
1491
1492 # Unfortunately the unicode 'micro' symbol can cause problems in
1492 # Unfortunately the unicode 'micro' symbol can cause problems in
1493 # certain terminals.
1493 # certain terminals.
1494 # See bug: https://bugs.launchpad.net/ipython/+bug/348466
1494 # See bug: https://bugs.launchpad.net/ipython/+bug/348466
1495 # Try to prevent crashes by being more secure than it needs to
1495 # Try to prevent crashes by being more secure than it needs to
1496 # E.g. eclipse is able to print a µ, but has no sys.stdout.encoding set.
1496 # E.g. eclipse is able to print a µ, but has no sys.stdout.encoding set.
1497 units = [u"s", u"ms",u'us',"ns"] # the save value
1497 units = [u"s", u"ms",u'us',"ns"] # the save value
1498 if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
1498 if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
1499 try:
1499 try:
1500 u'\xb5'.encode(sys.stdout.encoding)
1500 u'\xb5'.encode(sys.stdout.encoding)
1501 units = [u"s", u"ms",u'\xb5s',"ns"]
1501 units = [u"s", u"ms",u'\xb5s',"ns"]
1502 except:
1502 except:
1503 pass
1503 pass
1504 scaling = [1, 1e3, 1e6, 1e9]
1504 scaling = [1, 1e3, 1e6, 1e9]
1505
1505
1506 if timespan > 0.0:
1506 if timespan > 0.0:
1507 order = min(-int(math.floor(math.log10(timespan)) // 3), 3)
1507 order = min(-int(math.floor(math.log10(timespan)) // 3), 3)
1508 else:
1508 else:
1509 order = 3
1509 order = 3
1510 return u"%.*g %s" % (precision, timespan * scaling[order], units[order])
1510 return u"%.*g %s" % (precision, timespan * scaling[order], units[order])
@@ -1,112 +1,112 b''
1 """Implementation of packaging-related magic functions.
1 """Implementation of packaging-related magic functions.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2018 The IPython Development Team.
4 # Copyright (c) 2018 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 import re
11 import re
12 import shlex
12 import shlex
13 import sys
13 import sys
14 from pathlib import Path
14 from pathlib import Path
15
15
16 from IPython.core.magic import Magics, magics_class, line_magic
16 from IPython.core.magic import Magics, magics_class, line_magic
17
17
18
18
19 def _is_conda_environment():
19 def _is_conda_environment():
20 """Return True if the current Python executable is in a conda env"""
20 """Return True if the current Python executable is in a conda env"""
21 # TODO: does this need to change on windows?
21 # TODO: does this need to change on windows?
22 return Path(sys.prefix, "conda-meta", "history").exists()
22 return Path(sys.prefix, "conda-meta", "history").exists()
23
23
24
24
25 def _get_conda_executable():
25 def _get_conda_executable():
26 """Find the path to the conda executable"""
26 """Find the path to the conda executable"""
27 # Check if there is a conda executable in the same directory as the Python executable.
27 # Check if there is a conda executable in the same directory as the Python executable.
28 # This is the case within conda's root environment.
28 # This is the case within conda's root environment.
29 conda = Path(sys.executable).parent / "conda"
29 conda = Path(sys.executable).parent / "conda"
30 if conda.is_file():
30 if conda.is_file():
31 return str(conda)
31 return str(conda)
32
32
33 # Otherwise, attempt to extract the executable from conda history.
33 # Otherwise, attempt to extract the executable from conda history.
34 # This applies in any conda environment.
34 # This applies in any conda environment.
35 history = Path(sys.prefix, "conda-meta", "history").read_text(encoding='utf-8')
35 history = Path(sys.prefix, "conda-meta", "history").read_text(encoding="utf-8")
36 match = re.search(
36 match = re.search(
37 r"^#\s*cmd:\s*(?P<command>.*conda)\s[create|install]",
37 r"^#\s*cmd:\s*(?P<command>.*conda)\s[create|install]",
38 history,
38 history,
39 flags=re.MULTILINE,
39 flags=re.MULTILINE,
40 )
40 )
41 if match:
41 if match:
42 return match.groupdict()["command"]
42 return match.groupdict()["command"]
43
43
44 # Fallback: assume conda is available on the system path.
44 # Fallback: assume conda is available on the system path.
45 return "conda"
45 return "conda"
46
46
47
47
48 CONDA_COMMANDS_REQUIRING_PREFIX = {
48 CONDA_COMMANDS_REQUIRING_PREFIX = {
49 'install', 'list', 'remove', 'uninstall', 'update', 'upgrade',
49 'install', 'list', 'remove', 'uninstall', 'update', 'upgrade',
50 }
50 }
51 CONDA_COMMANDS_REQUIRING_YES = {
51 CONDA_COMMANDS_REQUIRING_YES = {
52 'install', 'remove', 'uninstall', 'update', 'upgrade',
52 'install', 'remove', 'uninstall', 'update', 'upgrade',
53 }
53 }
54 CONDA_ENV_FLAGS = {'-p', '--prefix', '-n', '--name'}
54 CONDA_ENV_FLAGS = {'-p', '--prefix', '-n', '--name'}
55 CONDA_YES_FLAGS = {'-y', '--y'}
55 CONDA_YES_FLAGS = {'-y', '--y'}
56
56
57
57
58 @magics_class
58 @magics_class
59 class PackagingMagics(Magics):
59 class PackagingMagics(Magics):
60 """Magics related to packaging & installation"""
60 """Magics related to packaging & installation"""
61
61
62 @line_magic
62 @line_magic
63 def pip(self, line):
63 def pip(self, line):
64 """Run the pip package manager within the current kernel.
64 """Run the pip package manager within the current kernel.
65
65
66 Usage:
66 Usage:
67 %pip install [pkgs]
67 %pip install [pkgs]
68 """
68 """
69 python = sys.executable
69 python = sys.executable
70 if sys.platform == "win32":
70 if sys.platform == "win32":
71 python = '"' + python + '"'
71 python = '"' + python + '"'
72 else:
72 else:
73 python = shlex.quote(python)
73 python = shlex.quote(python)
74
74
75 self.shell.system(" ".join([python, "-m", "pip", line]))
75 self.shell.system(" ".join([python, "-m", "pip", line]))
76
76
77 print("Note: you may need to restart the kernel to use updated packages.")
77 print("Note: you may need to restart the kernel to use updated packages.")
78
78
79 @line_magic
79 @line_magic
80 def conda(self, line):
80 def conda(self, line):
81 """Run the conda package manager within the current kernel.
81 """Run the conda package manager within the current kernel.
82
82
83 Usage:
83 Usage:
84 %conda install [pkgs]
84 %conda install [pkgs]
85 """
85 """
86 if not _is_conda_environment():
86 if not _is_conda_environment():
87 raise ValueError("The python kernel does not appear to be a conda environment. "
87 raise ValueError("The python kernel does not appear to be a conda environment. "
88 "Please use ``%pip install`` instead.")
88 "Please use ``%pip install`` instead.")
89
89
90 conda = _get_conda_executable()
90 conda = _get_conda_executable()
91 args = shlex.split(line)
91 args = shlex.split(line)
92 command = args[0] if len(args) > 0 else ""
92 command = args[0] if len(args) > 0 else ""
93 args = args[1:] if len(args) > 1 else [""]
93 args = args[1:] if len(args) > 1 else [""]
94
94
95 extra_args = []
95 extra_args = []
96
96
97 # When the subprocess does not allow us to respond "yes" during the installation,
97 # When the subprocess does not allow us to respond "yes" during the installation,
98 # we need to insert --yes in the argument list for some commands
98 # we need to insert --yes in the argument list for some commands
99 stdin_disabled = getattr(self.shell, 'kernel', None) is not None
99 stdin_disabled = getattr(self.shell, 'kernel', None) is not None
100 needs_yes = command in CONDA_COMMANDS_REQUIRING_YES
100 needs_yes = command in CONDA_COMMANDS_REQUIRING_YES
101 has_yes = set(args).intersection(CONDA_YES_FLAGS)
101 has_yes = set(args).intersection(CONDA_YES_FLAGS)
102 if stdin_disabled and needs_yes and not has_yes:
102 if stdin_disabled and needs_yes and not has_yes:
103 extra_args.append("--yes")
103 extra_args.append("--yes")
104
104
105 # Add --prefix to point conda installation to the current environment
105 # Add --prefix to point conda installation to the current environment
106 needs_prefix = command in CONDA_COMMANDS_REQUIRING_PREFIX
106 needs_prefix = command in CONDA_COMMANDS_REQUIRING_PREFIX
107 has_prefix = set(args).intersection(CONDA_ENV_FLAGS)
107 has_prefix = set(args).intersection(CONDA_ENV_FLAGS)
108 if needs_prefix and not has_prefix:
108 if needs_prefix and not has_prefix:
109 extra_args.extend(["--prefix", sys.prefix])
109 extra_args.extend(["--prefix", sys.prefix])
110
110
111 self.shell.system(' '.join([conda, command] + extra_args + args))
111 self.shell.system(' '.join([conda, command] + extra_args + args))
112 print("\nNote: you may need to restart the kernel to use updated packages.")
112 print("\nNote: you may need to restart the kernel to use updated packages.")
@@ -1,345 +1,348 b''
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):
128 def pager_page(strng, start=0, screen_lines=0, pager_cmd=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(pager_cmd,
216 proc = subprocess.Popen(
217 shell=True,
217 pager_cmd,
218 stdin=subprocess.PIPE,
218 shell=True,
219 stderr=subprocess.DEVNULL
219 stdin=subprocess.PIPE,
220 )
220 stderr=subprocess.DEVNULL,
221 pager = os._wrap_close(io.TextIOWrapper(proc.stdin, encoding='utf-8'), proc)
221 )
222 pager = os._wrap_close(
223 io.TextIOWrapper(proc.stdin, encoding="utf-8"), proc
224 )
222 try:
225 try:
223 pager_encoding = pager.encoding or sys.stdout.encoding
226 pager_encoding = pager.encoding or sys.stdout.encoding
224 pager.write(strng)
227 pager.write(strng)
225 finally:
228 finally:
226 retval = pager.close()
229 retval = pager.close()
227 except IOError as msg: # broken pipe when user quits
230 except IOError as msg: # broken pipe when user quits
228 if msg.args == (32, 'Broken pipe'):
231 if msg.args == (32, 'Broken pipe'):
229 retval = None
232 retval = None
230 else:
233 else:
231 retval = 1
234 retval = 1
232 except OSError:
235 except OSError:
233 # Other strange problems, sometimes seen in Win2k/cygwin
236 # Other strange problems, sometimes seen in Win2k/cygwin
234 retval = 1
237 retval = 1
235 if retval is not None:
238 if retval is not None:
236 page_dumb(strng,screen_lines=screen_lines)
239 page_dumb(strng,screen_lines=screen_lines)
237
240
238
241
239 def page(data, start=0, screen_lines=0, pager_cmd=None):
242 def page(data, start=0, screen_lines=0, pager_cmd=None):
240 """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.
241
244
242 data can be a mime-bundle dict, supplying multiple representations,
245 data can be a mime-bundle dict, supplying multiple representations,
243 keyed by mime-type, or text.
246 keyed by mime-type, or text.
244
247
245 Pager is dispatched via the `show_in_pager` IPython hook.
248 Pager is dispatched via the `show_in_pager` IPython hook.
246 If no hook is registered, `pager_page` will be used.
249 If no hook is registered, `pager_page` will be used.
247 """
250 """
248 # Some routines may auto-compute start offsets incorrectly and pass a
251 # Some routines may auto-compute start offsets incorrectly and pass a
249 # negative value. Offset to 0 for robustness.
252 # negative value. Offset to 0 for robustness.
250 start = max(0, start)
253 start = max(0, start)
251
254
252 # first, try the hook
255 # first, try the hook
253 ip = get_ipython()
256 ip = get_ipython()
254 if ip:
257 if ip:
255 try:
258 try:
256 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)
257 return
260 return
258 except TryNext:
261 except TryNext:
259 pass
262 pass
260
263
261 # fallback on default pager
264 # fallback on default pager
262 return pager_page(data, start, screen_lines, pager_cmd)
265 return pager_page(data, start, screen_lines, pager_cmd)
263
266
264
267
265 def page_file(fname, start=0, pager_cmd=None):
268 def page_file(fname, start=0, pager_cmd=None):
266 """Page a file, using an optional pager command and starting line.
269 """Page a file, using an optional pager command and starting line.
267 """
270 """
268
271
269 pager_cmd = get_pager_cmd(pager_cmd)
272 pager_cmd = get_pager_cmd(pager_cmd)
270 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
273 pager_cmd += ' ' + get_pager_start(pager_cmd,start)
271
274
272 try:
275 try:
273 if os.environ['TERM'] in ['emacs','dumb']:
276 if os.environ['TERM'] in ['emacs','dumb']:
274 raise EnvironmentError
277 raise EnvironmentError
275 system(pager_cmd + ' ' + fname)
278 system(pager_cmd + ' ' + fname)
276 except:
279 except:
277 try:
280 try:
278 if start > 0:
281 if start > 0:
279 start -= 1
282 start -= 1
280 page(open(fname, encoding='utf-8').read(),start)
283 page(open(fname, encoding="utf-8").read(), start)
281 except:
284 except:
282 print('Unable to show file',repr(fname))
285 print('Unable to show file',repr(fname))
283
286
284
287
285 def get_pager_cmd(pager_cmd=None):
288 def get_pager_cmd(pager_cmd=None):
286 """Return a pager command.
289 """Return a pager command.
287
290
288 Makes some attempts at finding an OS-correct one.
291 Makes some attempts at finding an OS-correct one.
289 """
292 """
290 if os.name == 'posix':
293 if os.name == 'posix':
291 default_pager_cmd = 'less -R' # -R for color control sequences
294 default_pager_cmd = 'less -R' # -R for color control sequences
292 elif os.name in ['nt','dos']:
295 elif os.name in ['nt','dos']:
293 default_pager_cmd = 'type'
296 default_pager_cmd = 'type'
294
297
295 if pager_cmd is None:
298 if pager_cmd is None:
296 try:
299 try:
297 pager_cmd = os.environ['PAGER']
300 pager_cmd = os.environ['PAGER']
298 except:
301 except:
299 pager_cmd = default_pager_cmd
302 pager_cmd = default_pager_cmd
300
303
301 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():
302 pager_cmd += ' -R'
305 pager_cmd += ' -R'
303
306
304 return pager_cmd
307 return pager_cmd
305
308
306
309
307 def get_pager_start(pager, start):
310 def get_pager_start(pager, start):
308 """Return the string for paging files with an offset.
311 """Return the string for paging files with an offset.
309
312
310 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.
311 """
314 """
312
315
313 if pager in ['less','more']:
316 if pager in ['less','more']:
314 if start:
317 if start:
315 start_string = '+' + str(start)
318 start_string = '+' + str(start)
316 else:
319 else:
317 start_string = ''
320 start_string = ''
318 else:
321 else:
319 start_string = ''
322 start_string = ''
320 return start_string
323 return start_string
321
324
322
325
323 # (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()
324 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
327 if os.name == 'nt' and os.environ.get('TERM','dumb') != 'emacs':
325 import msvcrt
328 import msvcrt
326 def page_more():
329 def page_more():
327 """ Smart pausing between pages
330 """ Smart pausing between pages
328
331
329 @return: True if need print more lines, False if quit
332 @return: True if need print more lines, False if quit
330 """
333 """
331 sys.stdout.write('---Return to continue, q to quit--- ')
334 sys.stdout.write('---Return to continue, q to quit--- ')
332 ans = msvcrt.getwch()
335 ans = msvcrt.getwch()
333 if ans in ("q", "Q"):
336 if ans in ("q", "Q"):
334 result = False
337 result = False
335 else:
338 else:
336 result = True
339 result = True
337 sys.stdout.write("\b"*37 + " "*37 + "\b"*37)
340 sys.stdout.write("\b"*37 + " "*37 + "\b"*37)
338 return result
341 return result
339 else:
342 else:
340 def page_more():
343 def page_more():
341 ans = py3compat.input('---Return to continue, q to quit--- ')
344 ans = py3compat.input('---Return to continue, q to quit--- ')
342 if ans.lower().startswith('q'):
345 if ans.lower().startswith('q'):
343 return False
346 return False
344 else:
347 else:
345 return True
348 return True
@@ -1,70 +1,70 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Tests for IPython.core.application"""
2 """Tests for IPython.core.application"""
3
3
4 import os
4 import os
5 import tempfile
5 import tempfile
6
6
7 from traitlets import Unicode
7 from traitlets import Unicode
8
8
9 from IPython.core.application import BaseIPythonApplication
9 from IPython.core.application import BaseIPythonApplication
10 from IPython.testing import decorators as dec
10 from IPython.testing import decorators as dec
11 from IPython.utils.tempdir import TemporaryDirectory
11 from IPython.utils.tempdir import TemporaryDirectory
12
12
13
13
14 @dec.onlyif_unicode_paths
14 @dec.onlyif_unicode_paths
15 def test_unicode_cwd():
15 def test_unicode_cwd():
16 """Check that IPython starts with non-ascii characters in the path."""
16 """Check that IPython starts with non-ascii characters in the path."""
17 wd = tempfile.mkdtemp(suffix=u"€")
17 wd = tempfile.mkdtemp(suffix=u"€")
18
18
19 old_wd = os.getcwd()
19 old_wd = os.getcwd()
20 os.chdir(wd)
20 os.chdir(wd)
21 #raise Exception(repr(os.getcwd()))
21 #raise Exception(repr(os.getcwd()))
22 try:
22 try:
23 app = BaseIPythonApplication()
23 app = BaseIPythonApplication()
24 # The lines below are copied from Application.initialize()
24 # The lines below are copied from Application.initialize()
25 app.init_profile_dir()
25 app.init_profile_dir()
26 app.init_config_files()
26 app.init_config_files()
27 app.load_config_file(suppress_errors=False)
27 app.load_config_file(suppress_errors=False)
28 finally:
28 finally:
29 os.chdir(old_wd)
29 os.chdir(old_wd)
30
30
31 @dec.onlyif_unicode_paths
31 @dec.onlyif_unicode_paths
32 def test_unicode_ipdir():
32 def test_unicode_ipdir():
33 """Check that IPython starts with non-ascii characters in the IP dir."""
33 """Check that IPython starts with non-ascii characters in the IP dir."""
34 ipdir = tempfile.mkdtemp(suffix=u"€")
34 ipdir = tempfile.mkdtemp(suffix=u"€")
35
35
36 # Create the config file, so it tries to load it.
36 # Create the config file, so it tries to load it.
37 with open(os.path.join(ipdir, 'ipython_config.py'), "w", encoding='utf-8') as f:
37 with open(os.path.join(ipdir, "ipython_config.py"), "w", encoding="utf-8") as f:
38 pass
38 pass
39
39
40 old_ipdir1 = os.environ.pop("IPYTHONDIR", None)
40 old_ipdir1 = os.environ.pop("IPYTHONDIR", None)
41 old_ipdir2 = os.environ.pop("IPYTHON_DIR", None)
41 old_ipdir2 = os.environ.pop("IPYTHON_DIR", None)
42 os.environ["IPYTHONDIR"] = ipdir
42 os.environ["IPYTHONDIR"] = ipdir
43 try:
43 try:
44 app = BaseIPythonApplication()
44 app = BaseIPythonApplication()
45 # The lines below are copied from Application.initialize()
45 # The lines below are copied from Application.initialize()
46 app.init_profile_dir()
46 app.init_profile_dir()
47 app.init_config_files()
47 app.init_config_files()
48 app.load_config_file(suppress_errors=False)
48 app.load_config_file(suppress_errors=False)
49 finally:
49 finally:
50 if old_ipdir1:
50 if old_ipdir1:
51 os.environ["IPYTHONDIR"] = old_ipdir1
51 os.environ["IPYTHONDIR"] = old_ipdir1
52 if old_ipdir2:
52 if old_ipdir2:
53 os.environ["IPYTHONDIR"] = old_ipdir2
53 os.environ["IPYTHONDIR"] = old_ipdir2
54
54
55 def test_cli_priority():
55 def test_cli_priority():
56 with TemporaryDirectory() as td:
56 with TemporaryDirectory() as td:
57
57
58 class TestApp(BaseIPythonApplication):
58 class TestApp(BaseIPythonApplication):
59 test = Unicode().tag(config=True)
59 test = Unicode().tag(config=True)
60
60
61 # Create the config file, so it tries to load it.
61 # Create the config file, so it tries to load it.
62 with open(os.path.join(td, 'ipython_config.py'), "w", encoding='utf-8') as f:
62 with open(os.path.join(td, "ipython_config.py"), "w", encoding="utf-8") as f:
63 f.write("c.TestApp.test = 'config file'")
63 f.write("c.TestApp.test = 'config file'")
64
64
65 app = TestApp()
65 app = TestApp()
66 app.initialize(["--profile-dir", td])
66 app.initialize(["--profile-dir", td])
67 assert app.test == "config file"
67 assert app.test == "config file"
68 app = TestApp()
68 app = TestApp()
69 app.initialize(["--profile-dir", td, "--TestApp.test=cli"])
69 app.initialize(["--profile-dir", td, "--TestApp.test=cli"])
70 assert app.test == "cli"
70 assert app.test == "cli"
@@ -1,1264 +1,1264 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for the IPython tab-completion machinery."""
2 """Tests for the IPython tab-completion machinery."""
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 pytest
8 import pytest
9 import sys
9 import sys
10 import textwrap
10 import textwrap
11 import unittest
11 import unittest
12
12
13 from contextlib import contextmanager
13 from contextlib import contextmanager
14
14
15 from traitlets.config.loader import Config
15 from traitlets.config.loader import Config
16 from IPython import get_ipython
16 from IPython import get_ipython
17 from IPython.core import completer
17 from IPython.core import completer
18 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
18 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
19 from IPython.utils.generics import complete_object
19 from IPython.utils.generics import complete_object
20 from IPython.testing import decorators as dec
20 from IPython.testing import decorators as dec
21
21
22 from IPython.core.completer import (
22 from IPython.core.completer import (
23 Completion,
23 Completion,
24 provisionalcompleter,
24 provisionalcompleter,
25 match_dict_keys,
25 match_dict_keys,
26 _deduplicate_completions,
26 _deduplicate_completions,
27 )
27 )
28
28
29 # -----------------------------------------------------------------------------
29 # -----------------------------------------------------------------------------
30 # Test functions
30 # Test functions
31 # -----------------------------------------------------------------------------
31 # -----------------------------------------------------------------------------
32
32
33 def recompute_unicode_ranges():
33 def recompute_unicode_ranges():
34 """
34 """
35 utility to recompute the largest unicode range without any characters
35 utility to recompute the largest unicode range without any characters
36
36
37 use to recompute the gap in the global _UNICODE_RANGES of completer.py
37 use to recompute the gap in the global _UNICODE_RANGES of completer.py
38 """
38 """
39 import itertools
39 import itertools
40 import unicodedata
40 import unicodedata
41 valid = []
41 valid = []
42 for c in range(0,0x10FFFF + 1):
42 for c in range(0,0x10FFFF + 1):
43 try:
43 try:
44 unicodedata.name(chr(c))
44 unicodedata.name(chr(c))
45 except ValueError:
45 except ValueError:
46 continue
46 continue
47 valid.append(c)
47 valid.append(c)
48
48
49 def ranges(i):
49 def ranges(i):
50 for a, b in itertools.groupby(enumerate(i), lambda pair: pair[1] - pair[0]):
50 for a, b in itertools.groupby(enumerate(i), lambda pair: pair[1] - pair[0]):
51 b = list(b)
51 b = list(b)
52 yield b[0][1], b[-1][1]
52 yield b[0][1], b[-1][1]
53
53
54 rg = list(ranges(valid))
54 rg = list(ranges(valid))
55 lens = []
55 lens = []
56 gap_lens = []
56 gap_lens = []
57 pstart, pstop = 0,0
57 pstart, pstop = 0,0
58 for start, stop in rg:
58 for start, stop in rg:
59 lens.append(stop-start)
59 lens.append(stop-start)
60 gap_lens.append((start - pstop, hex(pstop), hex(start), f'{round((start - pstop)/0xe01f0*100)}%'))
60 gap_lens.append((start - pstop, hex(pstop), hex(start), f'{round((start - pstop)/0xe01f0*100)}%'))
61 pstart, pstop = start, stop
61 pstart, pstop = start, stop
62
62
63 return sorted(gap_lens)[-1]
63 return sorted(gap_lens)[-1]
64
64
65
65
66
66
67 def test_unicode_range():
67 def test_unicode_range():
68 """
68 """
69 Test that the ranges we test for unicode names give the same number of
69 Test that the ranges we test for unicode names give the same number of
70 results than testing the full length.
70 results than testing the full length.
71 """
71 """
72 from IPython.core.completer import _unicode_name_compute, _UNICODE_RANGES
72 from IPython.core.completer import _unicode_name_compute, _UNICODE_RANGES
73
73
74 expected_list = _unicode_name_compute([(0, 0x110000)])
74 expected_list = _unicode_name_compute([(0, 0x110000)])
75 test = _unicode_name_compute(_UNICODE_RANGES)
75 test = _unicode_name_compute(_UNICODE_RANGES)
76 len_exp = len(expected_list)
76 len_exp = len(expected_list)
77 len_test = len(test)
77 len_test = len(test)
78
78
79 # do not inline the len() or on error pytest will try to print the 130 000 +
79 # do not inline the len() or on error pytest will try to print the 130 000 +
80 # elements.
80 # elements.
81 message = None
81 message = None
82 if len_exp != len_test or len_exp > 131808:
82 if len_exp != len_test or len_exp > 131808:
83 size, start, stop, prct = recompute_unicode_ranges()
83 size, start, stop, prct = recompute_unicode_ranges()
84 message = f"""_UNICODE_RANGES likely wrong and need updating. This is
84 message = f"""_UNICODE_RANGES likely wrong and need updating. This is
85 likely due to a new release of Python. We've find that the biggest gap
85 likely due to a new release of Python. We've find that the biggest gap
86 in unicode characters has reduces in size to be {size} characters
86 in unicode characters has reduces in size to be {size} characters
87 ({prct}), from {start}, to {stop}. In completer.py likely update to
87 ({prct}), from {start}, to {stop}. In completer.py likely update to
88
88
89 _UNICODE_RANGES = [(32, {start}), ({stop}, 0xe01f0)]
89 _UNICODE_RANGES = [(32, {start}), ({stop}, 0xe01f0)]
90
90
91 And update the assertion below to use
91 And update the assertion below to use
92
92
93 len_exp <= {len_exp}
93 len_exp <= {len_exp}
94 """
94 """
95 assert len_exp == len_test, message
95 assert len_exp == len_test, message
96
96
97 # fail if new unicode symbols have been added.
97 # fail if new unicode symbols have been added.
98 assert len_exp <= 138552, message
98 assert len_exp <= 138552, message
99
99
100
100
101 @contextmanager
101 @contextmanager
102 def greedy_completion():
102 def greedy_completion():
103 ip = get_ipython()
103 ip = get_ipython()
104 greedy_original = ip.Completer.greedy
104 greedy_original = ip.Completer.greedy
105 try:
105 try:
106 ip.Completer.greedy = True
106 ip.Completer.greedy = True
107 yield
107 yield
108 finally:
108 finally:
109 ip.Completer.greedy = greedy_original
109 ip.Completer.greedy = greedy_original
110
110
111
111
112 def test_protect_filename():
112 def test_protect_filename():
113 if sys.platform == "win32":
113 if sys.platform == "win32":
114 pairs = [
114 pairs = [
115 ("abc", "abc"),
115 ("abc", "abc"),
116 (" abc", '" abc"'),
116 (" abc", '" abc"'),
117 ("a bc", '"a bc"'),
117 ("a bc", '"a bc"'),
118 ("a bc", '"a bc"'),
118 ("a bc", '"a bc"'),
119 (" bc", '" bc"'),
119 (" bc", '" bc"'),
120 ]
120 ]
121 else:
121 else:
122 pairs = [
122 pairs = [
123 ("abc", "abc"),
123 ("abc", "abc"),
124 (" abc", r"\ abc"),
124 (" abc", r"\ abc"),
125 ("a bc", r"a\ bc"),
125 ("a bc", r"a\ bc"),
126 ("a bc", r"a\ \ bc"),
126 ("a bc", r"a\ \ bc"),
127 (" bc", r"\ \ bc"),
127 (" bc", r"\ \ bc"),
128 # On posix, we also protect parens and other special characters.
128 # On posix, we also protect parens and other special characters.
129 ("a(bc", r"a\(bc"),
129 ("a(bc", r"a\(bc"),
130 ("a)bc", r"a\)bc"),
130 ("a)bc", r"a\)bc"),
131 ("a( )bc", r"a\(\ \)bc"),
131 ("a( )bc", r"a\(\ \)bc"),
132 ("a[1]bc", r"a\[1\]bc"),
132 ("a[1]bc", r"a\[1\]bc"),
133 ("a{1}bc", r"a\{1\}bc"),
133 ("a{1}bc", r"a\{1\}bc"),
134 ("a#bc", r"a\#bc"),
134 ("a#bc", r"a\#bc"),
135 ("a?bc", r"a\?bc"),
135 ("a?bc", r"a\?bc"),
136 ("a=bc", r"a\=bc"),
136 ("a=bc", r"a\=bc"),
137 ("a\\bc", r"a\\bc"),
137 ("a\\bc", r"a\\bc"),
138 ("a|bc", r"a\|bc"),
138 ("a|bc", r"a\|bc"),
139 ("a;bc", r"a\;bc"),
139 ("a;bc", r"a\;bc"),
140 ("a:bc", r"a\:bc"),
140 ("a:bc", r"a\:bc"),
141 ("a'bc", r"a\'bc"),
141 ("a'bc", r"a\'bc"),
142 ("a*bc", r"a\*bc"),
142 ("a*bc", r"a\*bc"),
143 ('a"bc', r"a\"bc"),
143 ('a"bc', r"a\"bc"),
144 ("a^bc", r"a\^bc"),
144 ("a^bc", r"a\^bc"),
145 ("a&bc", r"a\&bc"),
145 ("a&bc", r"a\&bc"),
146 ]
146 ]
147 # run the actual tests
147 # run the actual tests
148 for s1, s2 in pairs:
148 for s1, s2 in pairs:
149 s1p = completer.protect_filename(s1)
149 s1p = completer.protect_filename(s1)
150 assert s1p == s2
150 assert s1p == s2
151
151
152
152
153 def check_line_split(splitter, test_specs):
153 def check_line_split(splitter, test_specs):
154 for part1, part2, split in test_specs:
154 for part1, part2, split in test_specs:
155 cursor_pos = len(part1)
155 cursor_pos = len(part1)
156 line = part1 + part2
156 line = part1 + part2
157 out = splitter.split_line(line, cursor_pos)
157 out = splitter.split_line(line, cursor_pos)
158 assert out == split
158 assert out == split
159
159
160
160
161 def test_line_split():
161 def test_line_split():
162 """Basic line splitter test with default specs."""
162 """Basic line splitter test with default specs."""
163 sp = completer.CompletionSplitter()
163 sp = completer.CompletionSplitter()
164 # The format of the test specs is: part1, part2, expected answer. Parts 1
164 # The format of the test specs is: part1, part2, expected answer. Parts 1
165 # and 2 are joined into the 'line' sent to the splitter, as if the cursor
165 # and 2 are joined into the 'line' sent to the splitter, as if the cursor
166 # was at the end of part1. So an empty part2 represents someone hitting
166 # was at the end of part1. So an empty part2 represents someone hitting
167 # tab at the end of the line, the most common case.
167 # tab at the end of the line, the most common case.
168 t = [
168 t = [
169 ("run some/scrip", "", "some/scrip"),
169 ("run some/scrip", "", "some/scrip"),
170 ("run scripts/er", "ror.py foo", "scripts/er"),
170 ("run scripts/er", "ror.py foo", "scripts/er"),
171 ("echo $HOM", "", "HOM"),
171 ("echo $HOM", "", "HOM"),
172 ("print sys.pa", "", "sys.pa"),
172 ("print sys.pa", "", "sys.pa"),
173 ("print(sys.pa", "", "sys.pa"),
173 ("print(sys.pa", "", "sys.pa"),
174 ("execfile('scripts/er", "", "scripts/er"),
174 ("execfile('scripts/er", "", "scripts/er"),
175 ("a[x.", "", "x."),
175 ("a[x.", "", "x."),
176 ("a[x.", "y", "x."),
176 ("a[x.", "y", "x."),
177 ('cd "some_file/', "", "some_file/"),
177 ('cd "some_file/', "", "some_file/"),
178 ]
178 ]
179 check_line_split(sp, t)
179 check_line_split(sp, t)
180 # Ensure splitting works OK with unicode by re-running the tests with
180 # Ensure splitting works OK with unicode by re-running the tests with
181 # all inputs turned into unicode
181 # all inputs turned into unicode
182 check_line_split(sp, [map(str, p) for p in t])
182 check_line_split(sp, [map(str, p) for p in t])
183
183
184
184
185 class NamedInstanceClass:
185 class NamedInstanceClass:
186 instances = {}
186 instances = {}
187
187
188 def __init__(self, name):
188 def __init__(self, name):
189 self.instances[name] = self
189 self.instances[name] = self
190
190
191 @classmethod
191 @classmethod
192 def _ipython_key_completions_(cls):
192 def _ipython_key_completions_(cls):
193 return cls.instances.keys()
193 return cls.instances.keys()
194
194
195
195
196 class KeyCompletable:
196 class KeyCompletable:
197 def __init__(self, things=()):
197 def __init__(self, things=()):
198 self.things = things
198 self.things = things
199
199
200 def _ipython_key_completions_(self):
200 def _ipython_key_completions_(self):
201 return list(self.things)
201 return list(self.things)
202
202
203
203
204 class TestCompleter(unittest.TestCase):
204 class TestCompleter(unittest.TestCase):
205 def setUp(self):
205 def setUp(self):
206 """
206 """
207 We want to silence all PendingDeprecationWarning when testing the completer
207 We want to silence all PendingDeprecationWarning when testing the completer
208 """
208 """
209 self._assertwarns = self.assertWarns(PendingDeprecationWarning)
209 self._assertwarns = self.assertWarns(PendingDeprecationWarning)
210 self._assertwarns.__enter__()
210 self._assertwarns.__enter__()
211
211
212 def tearDown(self):
212 def tearDown(self):
213 try:
213 try:
214 self._assertwarns.__exit__(None, None, None)
214 self._assertwarns.__exit__(None, None, None)
215 except AssertionError:
215 except AssertionError:
216 pass
216 pass
217
217
218 def test_custom_completion_error(self):
218 def test_custom_completion_error(self):
219 """Test that errors from custom attribute completers are silenced."""
219 """Test that errors from custom attribute completers are silenced."""
220 ip = get_ipython()
220 ip = get_ipython()
221
221
222 class A:
222 class A:
223 pass
223 pass
224
224
225 ip.user_ns["x"] = A()
225 ip.user_ns["x"] = A()
226
226
227 @complete_object.register(A)
227 @complete_object.register(A)
228 def complete_A(a, existing_completions):
228 def complete_A(a, existing_completions):
229 raise TypeError("this should be silenced")
229 raise TypeError("this should be silenced")
230
230
231 ip.complete("x.")
231 ip.complete("x.")
232
232
233 def test_custom_completion_ordering(self):
233 def test_custom_completion_ordering(self):
234 """Test that errors from custom attribute completers are silenced."""
234 """Test that errors from custom attribute completers are silenced."""
235 ip = get_ipython()
235 ip = get_ipython()
236
236
237 _, matches = ip.complete('in')
237 _, matches = ip.complete('in')
238 assert matches.index('input') < matches.index('int')
238 assert matches.index('input') < matches.index('int')
239
239
240 def complete_example(a):
240 def complete_example(a):
241 return ['example2', 'example1']
241 return ['example2', 'example1']
242
242
243 ip.Completer.custom_completers.add_re('ex*', complete_example)
243 ip.Completer.custom_completers.add_re('ex*', complete_example)
244 _, matches = ip.complete('ex')
244 _, matches = ip.complete('ex')
245 assert matches.index('example2') < matches.index('example1')
245 assert matches.index('example2') < matches.index('example1')
246
246
247 def test_unicode_completions(self):
247 def test_unicode_completions(self):
248 ip = get_ipython()
248 ip = get_ipython()
249 # Some strings that trigger different types of completion. Check them both
249 # Some strings that trigger different types of completion. Check them both
250 # in str and unicode forms
250 # in str and unicode forms
251 s = ["ru", "%ru", "cd /", "floa", "float(x)/"]
251 s = ["ru", "%ru", "cd /", "floa", "float(x)/"]
252 for t in s + list(map(str, s)):
252 for t in s + list(map(str, s)):
253 # We don't need to check exact completion values (they may change
253 # We don't need to check exact completion values (they may change
254 # depending on the state of the namespace, but at least no exceptions
254 # depending on the state of the namespace, but at least no exceptions
255 # should be thrown and the return value should be a pair of text, list
255 # should be thrown and the return value should be a pair of text, list
256 # values.
256 # values.
257 text, matches = ip.complete(t)
257 text, matches = ip.complete(t)
258 self.assertIsInstance(text, str)
258 self.assertIsInstance(text, str)
259 self.assertIsInstance(matches, list)
259 self.assertIsInstance(matches, list)
260
260
261 def test_latex_completions(self):
261 def test_latex_completions(self):
262 from IPython.core.latex_symbols import latex_symbols
262 from IPython.core.latex_symbols import latex_symbols
263 import random
263 import random
264
264
265 ip = get_ipython()
265 ip = get_ipython()
266 # Test some random unicode symbols
266 # Test some random unicode symbols
267 keys = random.sample(sorted(latex_symbols), 10)
267 keys = random.sample(sorted(latex_symbols), 10)
268 for k in keys:
268 for k in keys:
269 text, matches = ip.complete(k)
269 text, matches = ip.complete(k)
270 self.assertEqual(text, k)
270 self.assertEqual(text, k)
271 self.assertEqual(matches, [latex_symbols[k]])
271 self.assertEqual(matches, [latex_symbols[k]])
272 # Test a more complex line
272 # Test a more complex line
273 text, matches = ip.complete("print(\\alpha")
273 text, matches = ip.complete("print(\\alpha")
274 self.assertEqual(text, "\\alpha")
274 self.assertEqual(text, "\\alpha")
275 self.assertEqual(matches[0], latex_symbols["\\alpha"])
275 self.assertEqual(matches[0], latex_symbols["\\alpha"])
276 # Test multiple matching latex symbols
276 # Test multiple matching latex symbols
277 text, matches = ip.complete("\\al")
277 text, matches = ip.complete("\\al")
278 self.assertIn("\\alpha", matches)
278 self.assertIn("\\alpha", matches)
279 self.assertIn("\\aleph", matches)
279 self.assertIn("\\aleph", matches)
280
280
281 def test_latex_no_results(self):
281 def test_latex_no_results(self):
282 """
282 """
283 forward latex should really return nothing in either field if nothing is found.
283 forward latex should really return nothing in either field if nothing is found.
284 """
284 """
285 ip = get_ipython()
285 ip = get_ipython()
286 text, matches = ip.Completer.latex_matches("\\really_i_should_match_nothing")
286 text, matches = ip.Completer.latex_matches("\\really_i_should_match_nothing")
287 self.assertEqual(text, "")
287 self.assertEqual(text, "")
288 self.assertEqual(matches, ())
288 self.assertEqual(matches, ())
289
289
290 def test_back_latex_completion(self):
290 def test_back_latex_completion(self):
291 ip = get_ipython()
291 ip = get_ipython()
292
292
293 # do not return more than 1 matches for \beta, only the latex one.
293 # do not return more than 1 matches for \beta, only the latex one.
294 name, matches = ip.complete("\\β")
294 name, matches = ip.complete("\\β")
295 self.assertEqual(matches, ["\\beta"])
295 self.assertEqual(matches, ["\\beta"])
296
296
297 def test_back_unicode_completion(self):
297 def test_back_unicode_completion(self):
298 ip = get_ipython()
298 ip = get_ipython()
299
299
300 name, matches = ip.complete("\\Ⅴ")
300 name, matches = ip.complete("\\Ⅴ")
301 self.assertEqual(matches, ("\\ROMAN NUMERAL FIVE",))
301 self.assertEqual(matches, ("\\ROMAN NUMERAL FIVE",))
302
302
303 def test_forward_unicode_completion(self):
303 def test_forward_unicode_completion(self):
304 ip = get_ipython()
304 ip = get_ipython()
305
305
306 name, matches = ip.complete("\\ROMAN NUMERAL FIVE")
306 name, matches = ip.complete("\\ROMAN NUMERAL FIVE")
307 self.assertEqual(matches, ["Ⅴ"]) # This is not a V
307 self.assertEqual(matches, ["Ⅴ"]) # This is not a V
308 self.assertEqual(matches, ["\u2164"]) # same as above but explicit.
308 self.assertEqual(matches, ["\u2164"]) # same as above but explicit.
309
309
310 def test_delim_setting(self):
310 def test_delim_setting(self):
311 sp = completer.CompletionSplitter()
311 sp = completer.CompletionSplitter()
312 sp.delims = " "
312 sp.delims = " "
313 self.assertEqual(sp.delims, " ")
313 self.assertEqual(sp.delims, " ")
314 self.assertEqual(sp._delim_expr, r"[\ ]")
314 self.assertEqual(sp._delim_expr, r"[\ ]")
315
315
316 def test_spaces(self):
316 def test_spaces(self):
317 """Test with only spaces as split chars."""
317 """Test with only spaces as split chars."""
318 sp = completer.CompletionSplitter()
318 sp = completer.CompletionSplitter()
319 sp.delims = " "
319 sp.delims = " "
320 t = [("foo", "", "foo"), ("run foo", "", "foo"), ("run foo", "bar", "foo")]
320 t = [("foo", "", "foo"), ("run foo", "", "foo"), ("run foo", "bar", "foo")]
321 check_line_split(sp, t)
321 check_line_split(sp, t)
322
322
323 def test_has_open_quotes1(self):
323 def test_has_open_quotes1(self):
324 for s in ["'", "'''", "'hi' '"]:
324 for s in ["'", "'''", "'hi' '"]:
325 self.assertEqual(completer.has_open_quotes(s), "'")
325 self.assertEqual(completer.has_open_quotes(s), "'")
326
326
327 def test_has_open_quotes2(self):
327 def test_has_open_quotes2(self):
328 for s in ['"', '"""', '"hi" "']:
328 for s in ['"', '"""', '"hi" "']:
329 self.assertEqual(completer.has_open_quotes(s), '"')
329 self.assertEqual(completer.has_open_quotes(s), '"')
330
330
331 def test_has_open_quotes3(self):
331 def test_has_open_quotes3(self):
332 for s in ["''", "''' '''", "'hi' 'ipython'"]:
332 for s in ["''", "''' '''", "'hi' 'ipython'"]:
333 self.assertFalse(completer.has_open_quotes(s))
333 self.assertFalse(completer.has_open_quotes(s))
334
334
335 def test_has_open_quotes4(self):
335 def test_has_open_quotes4(self):
336 for s in ['""', '""" """', '"hi" "ipython"']:
336 for s in ['""', '""" """', '"hi" "ipython"']:
337 self.assertFalse(completer.has_open_quotes(s))
337 self.assertFalse(completer.has_open_quotes(s))
338
338
339 @pytest.mark.xfail(
339 @pytest.mark.xfail(
340 sys.platform == "win32", reason="abspath completions fail on Windows"
340 sys.platform == "win32", reason="abspath completions fail on Windows"
341 )
341 )
342 def test_abspath_file_completions(self):
342 def test_abspath_file_completions(self):
343 ip = get_ipython()
343 ip = get_ipython()
344 with TemporaryDirectory() as tmpdir:
344 with TemporaryDirectory() as tmpdir:
345 prefix = os.path.join(tmpdir, "foo")
345 prefix = os.path.join(tmpdir, "foo")
346 suffixes = ["1", "2"]
346 suffixes = ["1", "2"]
347 names = [prefix + s for s in suffixes]
347 names = [prefix + s for s in suffixes]
348 for n in names:
348 for n in names:
349 open(n, "w", encoding='utf-8').close()
349 open(n, "w", encoding="utf-8").close()
350
350
351 # Check simple completion
351 # Check simple completion
352 c = ip.complete(prefix)[1]
352 c = ip.complete(prefix)[1]
353 self.assertEqual(c, names)
353 self.assertEqual(c, names)
354
354
355 # Now check with a function call
355 # Now check with a function call
356 cmd = 'a = f("%s' % prefix
356 cmd = 'a = f("%s' % prefix
357 c = ip.complete(prefix, cmd)[1]
357 c = ip.complete(prefix, cmd)[1]
358 comp = [prefix + s for s in suffixes]
358 comp = [prefix + s for s in suffixes]
359 self.assertEqual(c, comp)
359 self.assertEqual(c, comp)
360
360
361 def test_local_file_completions(self):
361 def test_local_file_completions(self):
362 ip = get_ipython()
362 ip = get_ipython()
363 with TemporaryWorkingDirectory():
363 with TemporaryWorkingDirectory():
364 prefix = "./foo"
364 prefix = "./foo"
365 suffixes = ["1", "2"]
365 suffixes = ["1", "2"]
366 names = [prefix + s for s in suffixes]
366 names = [prefix + s for s in suffixes]
367 for n in names:
367 for n in names:
368 open(n, "w", encoding='utf-8').close()
368 open(n, "w", encoding="utf-8").close()
369
369
370 # Check simple completion
370 # Check simple completion
371 c = ip.complete(prefix)[1]
371 c = ip.complete(prefix)[1]
372 self.assertEqual(c, names)
372 self.assertEqual(c, names)
373
373
374 # Now check with a function call
374 # Now check with a function call
375 cmd = 'a = f("%s' % prefix
375 cmd = 'a = f("%s' % prefix
376 c = ip.complete(prefix, cmd)[1]
376 c = ip.complete(prefix, cmd)[1]
377 comp = {prefix + s for s in suffixes}
377 comp = {prefix + s for s in suffixes}
378 self.assertTrue(comp.issubset(set(c)))
378 self.assertTrue(comp.issubset(set(c)))
379
379
380 def test_quoted_file_completions(self):
380 def test_quoted_file_completions(self):
381 ip = get_ipython()
381 ip = get_ipython()
382 with TemporaryWorkingDirectory():
382 with TemporaryWorkingDirectory():
383 name = "foo'bar"
383 name = "foo'bar"
384 open(name, "w", encoding='utf-8').close()
384 open(name, "w", encoding="utf-8").close()
385
385
386 # Don't escape Windows
386 # Don't escape Windows
387 escaped = name if sys.platform == "win32" else "foo\\'bar"
387 escaped = name if sys.platform == "win32" else "foo\\'bar"
388
388
389 # Single quote matches embedded single quote
389 # Single quote matches embedded single quote
390 text = "open('foo"
390 text = "open('foo"
391 c = ip.Completer._complete(
391 c = ip.Completer._complete(
392 cursor_line=0, cursor_pos=len(text), full_text=text
392 cursor_line=0, cursor_pos=len(text), full_text=text
393 )[1]
393 )[1]
394 self.assertEqual(c, [escaped])
394 self.assertEqual(c, [escaped])
395
395
396 # Double quote requires no escape
396 # Double quote requires no escape
397 text = 'open("foo'
397 text = 'open("foo'
398 c = ip.Completer._complete(
398 c = ip.Completer._complete(
399 cursor_line=0, cursor_pos=len(text), full_text=text
399 cursor_line=0, cursor_pos=len(text), full_text=text
400 )[1]
400 )[1]
401 self.assertEqual(c, [name])
401 self.assertEqual(c, [name])
402
402
403 # No quote requires an escape
403 # No quote requires an escape
404 text = "%ls foo"
404 text = "%ls foo"
405 c = ip.Completer._complete(
405 c = ip.Completer._complete(
406 cursor_line=0, cursor_pos=len(text), full_text=text
406 cursor_line=0, cursor_pos=len(text), full_text=text
407 )[1]
407 )[1]
408 self.assertEqual(c, [escaped])
408 self.assertEqual(c, [escaped])
409
409
410 def test_all_completions_dups(self):
410 def test_all_completions_dups(self):
411 """
411 """
412 Make sure the output of `IPCompleter.all_completions` does not have
412 Make sure the output of `IPCompleter.all_completions` does not have
413 duplicated prefixes.
413 duplicated prefixes.
414 """
414 """
415 ip = get_ipython()
415 ip = get_ipython()
416 c = ip.Completer
416 c = ip.Completer
417 ip.ex("class TestClass():\n\ta=1\n\ta1=2")
417 ip.ex("class TestClass():\n\ta=1\n\ta1=2")
418 for jedi_status in [True, False]:
418 for jedi_status in [True, False]:
419 with provisionalcompleter():
419 with provisionalcompleter():
420 ip.Completer.use_jedi = jedi_status
420 ip.Completer.use_jedi = jedi_status
421 matches = c.all_completions("TestCl")
421 matches = c.all_completions("TestCl")
422 assert matches == ["TestClass"], (jedi_status, matches)
422 assert matches == ["TestClass"], (jedi_status, matches)
423 matches = c.all_completions("TestClass.")
423 matches = c.all_completions("TestClass.")
424 assert len(matches) > 2, (jedi_status, matches)
424 assert len(matches) > 2, (jedi_status, matches)
425 matches = c.all_completions("TestClass.a")
425 matches = c.all_completions("TestClass.a")
426 assert matches == ['TestClass.a', 'TestClass.a1'], jedi_status
426 assert matches == ['TestClass.a', 'TestClass.a1'], jedi_status
427
427
428 def test_jedi(self):
428 def test_jedi(self):
429 """
429 """
430 A couple of issue we had with Jedi
430 A couple of issue we had with Jedi
431 """
431 """
432 ip = get_ipython()
432 ip = get_ipython()
433
433
434 def _test_complete(reason, s, comp, start=None, end=None):
434 def _test_complete(reason, s, comp, start=None, end=None):
435 l = len(s)
435 l = len(s)
436 start = start if start is not None else l
436 start = start if start is not None else l
437 end = end if end is not None else l
437 end = end if end is not None else l
438 with provisionalcompleter():
438 with provisionalcompleter():
439 ip.Completer.use_jedi = True
439 ip.Completer.use_jedi = True
440 completions = set(ip.Completer.completions(s, l))
440 completions = set(ip.Completer.completions(s, l))
441 ip.Completer.use_jedi = False
441 ip.Completer.use_jedi = False
442 assert Completion(start, end, comp) in completions, reason
442 assert Completion(start, end, comp) in completions, reason
443
443
444 def _test_not_complete(reason, s, comp):
444 def _test_not_complete(reason, s, comp):
445 l = len(s)
445 l = len(s)
446 with provisionalcompleter():
446 with provisionalcompleter():
447 ip.Completer.use_jedi = True
447 ip.Completer.use_jedi = True
448 completions = set(ip.Completer.completions(s, l))
448 completions = set(ip.Completer.completions(s, l))
449 ip.Completer.use_jedi = False
449 ip.Completer.use_jedi = False
450 assert Completion(l, l, comp) not in completions, reason
450 assert Completion(l, l, comp) not in completions, reason
451
451
452 import jedi
452 import jedi
453
453
454 jedi_version = tuple(int(i) for i in jedi.__version__.split(".")[:3])
454 jedi_version = tuple(int(i) for i in jedi.__version__.split(".")[:3])
455 if jedi_version > (0, 10):
455 if jedi_version > (0, 10):
456 _test_complete("jedi >0.9 should complete and not crash", "a=1;a.", "real")
456 _test_complete("jedi >0.9 should complete and not crash", "a=1;a.", "real")
457 _test_complete("can infer first argument", 'a=(1,"foo");a[0].', "real")
457 _test_complete("can infer first argument", 'a=(1,"foo");a[0].', "real")
458 _test_complete("can infer second argument", 'a=(1,"foo");a[1].', "capitalize")
458 _test_complete("can infer second argument", 'a=(1,"foo");a[1].', "capitalize")
459 _test_complete("cover duplicate completions", "im", "import", 0, 2)
459 _test_complete("cover duplicate completions", "im", "import", 0, 2)
460
460
461 _test_not_complete("does not mix types", 'a=(1,"foo");a[0].', "capitalize")
461 _test_not_complete("does not mix types", 'a=(1,"foo");a[0].', "capitalize")
462
462
463 def test_completion_have_signature(self):
463 def test_completion_have_signature(self):
464 """
464 """
465 Lets make sure jedi is capable of pulling out the signature of the function we are completing.
465 Lets make sure jedi is capable of pulling out the signature of the function we are completing.
466 """
466 """
467 ip = get_ipython()
467 ip = get_ipython()
468 with provisionalcompleter():
468 with provisionalcompleter():
469 ip.Completer.use_jedi = True
469 ip.Completer.use_jedi = True
470 completions = ip.Completer.completions("ope", 3)
470 completions = ip.Completer.completions("ope", 3)
471 c = next(completions) # should be `open`
471 c = next(completions) # should be `open`
472 ip.Completer.use_jedi = False
472 ip.Completer.use_jedi = False
473 assert "file" in c.signature, "Signature of function was not found by completer"
473 assert "file" in c.signature, "Signature of function was not found by completer"
474 assert (
474 assert (
475 "encoding" in c.signature
475 "encoding" in c.signature
476 ), "Signature of function was not found by completer"
476 ), "Signature of function was not found by completer"
477
477
478 @pytest.mark.xfail(reason="Known failure on jedi<=0.18.0")
478 @pytest.mark.xfail(reason="Known failure on jedi<=0.18.0")
479 def test_deduplicate_completions(self):
479 def test_deduplicate_completions(self):
480 """
480 """
481 Test that completions are correctly deduplicated (even if ranges are not the same)
481 Test that completions are correctly deduplicated (even if ranges are not the same)
482 """
482 """
483 ip = get_ipython()
483 ip = get_ipython()
484 ip.ex(
484 ip.ex(
485 textwrap.dedent(
485 textwrap.dedent(
486 """
486 """
487 class Z:
487 class Z:
488 zoo = 1
488 zoo = 1
489 """
489 """
490 )
490 )
491 )
491 )
492 with provisionalcompleter():
492 with provisionalcompleter():
493 ip.Completer.use_jedi = True
493 ip.Completer.use_jedi = True
494 l = list(
494 l = list(
495 _deduplicate_completions("Z.z", ip.Completer.completions("Z.z", 3))
495 _deduplicate_completions("Z.z", ip.Completer.completions("Z.z", 3))
496 )
496 )
497 ip.Completer.use_jedi = False
497 ip.Completer.use_jedi = False
498
498
499 assert len(l) == 1, "Completions (Z.z<tab>) correctly deduplicate: %s " % l
499 assert len(l) == 1, "Completions (Z.z<tab>) correctly deduplicate: %s " % l
500 assert l[0].text == "zoo" # and not `it.accumulate`
500 assert l[0].text == "zoo" # and not `it.accumulate`
501
501
502 def test_greedy_completions(self):
502 def test_greedy_completions(self):
503 """
503 """
504 Test the capability of the Greedy completer.
504 Test the capability of the Greedy completer.
505
505
506 Most of the test here does not really show off the greedy completer, for proof
506 Most of the test here does not really show off the greedy completer, for proof
507 each of the text below now pass with Jedi. The greedy completer is capable of more.
507 each of the text below now pass with Jedi. The greedy completer is capable of more.
508
508
509 See the :any:`test_dict_key_completion_contexts`
509 See the :any:`test_dict_key_completion_contexts`
510
510
511 """
511 """
512 ip = get_ipython()
512 ip = get_ipython()
513 ip.ex("a=list(range(5))")
513 ip.ex("a=list(range(5))")
514 _, c = ip.complete(".", line="a[0].")
514 _, c = ip.complete(".", line="a[0].")
515 self.assertFalse(".real" in c, "Shouldn't have completed on a[0]: %s" % c)
515 self.assertFalse(".real" in c, "Shouldn't have completed on a[0]: %s" % c)
516
516
517 def _(line, cursor_pos, expect, message, completion):
517 def _(line, cursor_pos, expect, message, completion):
518 with greedy_completion(), provisionalcompleter():
518 with greedy_completion(), provisionalcompleter():
519 ip.Completer.use_jedi = False
519 ip.Completer.use_jedi = False
520 _, c = ip.complete(".", line=line, cursor_pos=cursor_pos)
520 _, c = ip.complete(".", line=line, cursor_pos=cursor_pos)
521 self.assertIn(expect, c, message % c)
521 self.assertIn(expect, c, message % c)
522
522
523 ip.Completer.use_jedi = True
523 ip.Completer.use_jedi = True
524 with provisionalcompleter():
524 with provisionalcompleter():
525 completions = ip.Completer.completions(line, cursor_pos)
525 completions = ip.Completer.completions(line, cursor_pos)
526 self.assertIn(completion, completions)
526 self.assertIn(completion, completions)
527
527
528 with provisionalcompleter():
528 with provisionalcompleter():
529 _(
529 _(
530 "a[0].",
530 "a[0].",
531 5,
531 5,
532 "a[0].real",
532 "a[0].real",
533 "Should have completed on a[0].: %s",
533 "Should have completed on a[0].: %s",
534 Completion(5, 5, "real"),
534 Completion(5, 5, "real"),
535 )
535 )
536 _(
536 _(
537 "a[0].r",
537 "a[0].r",
538 6,
538 6,
539 "a[0].real",
539 "a[0].real",
540 "Should have completed on a[0].r: %s",
540 "Should have completed on a[0].r: %s",
541 Completion(5, 6, "real"),
541 Completion(5, 6, "real"),
542 )
542 )
543
543
544 _(
544 _(
545 "a[0].from_",
545 "a[0].from_",
546 10,
546 10,
547 "a[0].from_bytes",
547 "a[0].from_bytes",
548 "Should have completed on a[0].from_: %s",
548 "Should have completed on a[0].from_: %s",
549 Completion(5, 10, "from_bytes"),
549 Completion(5, 10, "from_bytes"),
550 )
550 )
551
551
552 def test_omit__names(self):
552 def test_omit__names(self):
553 # also happens to test IPCompleter as a configurable
553 # also happens to test IPCompleter as a configurable
554 ip = get_ipython()
554 ip = get_ipython()
555 ip._hidden_attr = 1
555 ip._hidden_attr = 1
556 ip._x = {}
556 ip._x = {}
557 c = ip.Completer
557 c = ip.Completer
558 ip.ex("ip=get_ipython()")
558 ip.ex("ip=get_ipython()")
559 cfg = Config()
559 cfg = Config()
560 cfg.IPCompleter.omit__names = 0
560 cfg.IPCompleter.omit__names = 0
561 c.update_config(cfg)
561 c.update_config(cfg)
562 with provisionalcompleter():
562 with provisionalcompleter():
563 c.use_jedi = False
563 c.use_jedi = False
564 s, matches = c.complete("ip.")
564 s, matches = c.complete("ip.")
565 self.assertIn("ip.__str__", matches)
565 self.assertIn("ip.__str__", matches)
566 self.assertIn("ip._hidden_attr", matches)
566 self.assertIn("ip._hidden_attr", matches)
567
567
568 # c.use_jedi = True
568 # c.use_jedi = True
569 # completions = set(c.completions('ip.', 3))
569 # completions = set(c.completions('ip.', 3))
570 # self.assertIn(Completion(3, 3, '__str__'), completions)
570 # self.assertIn(Completion(3, 3, '__str__'), completions)
571 # self.assertIn(Completion(3,3, "_hidden_attr"), completions)
571 # self.assertIn(Completion(3,3, "_hidden_attr"), completions)
572
572
573 cfg = Config()
573 cfg = Config()
574 cfg.IPCompleter.omit__names = 1
574 cfg.IPCompleter.omit__names = 1
575 c.update_config(cfg)
575 c.update_config(cfg)
576 with provisionalcompleter():
576 with provisionalcompleter():
577 c.use_jedi = False
577 c.use_jedi = False
578 s, matches = c.complete("ip.")
578 s, matches = c.complete("ip.")
579 self.assertNotIn("ip.__str__", matches)
579 self.assertNotIn("ip.__str__", matches)
580 # self.assertIn('ip._hidden_attr', matches)
580 # self.assertIn('ip._hidden_attr', matches)
581
581
582 # c.use_jedi = True
582 # c.use_jedi = True
583 # completions = set(c.completions('ip.', 3))
583 # completions = set(c.completions('ip.', 3))
584 # self.assertNotIn(Completion(3,3,'__str__'), completions)
584 # self.assertNotIn(Completion(3,3,'__str__'), completions)
585 # self.assertIn(Completion(3,3, "_hidden_attr"), completions)
585 # self.assertIn(Completion(3,3, "_hidden_attr"), completions)
586
586
587 cfg = Config()
587 cfg = Config()
588 cfg.IPCompleter.omit__names = 2
588 cfg.IPCompleter.omit__names = 2
589 c.update_config(cfg)
589 c.update_config(cfg)
590 with provisionalcompleter():
590 with provisionalcompleter():
591 c.use_jedi = False
591 c.use_jedi = False
592 s, matches = c.complete("ip.")
592 s, matches = c.complete("ip.")
593 self.assertNotIn("ip.__str__", matches)
593 self.assertNotIn("ip.__str__", matches)
594 self.assertNotIn("ip._hidden_attr", matches)
594 self.assertNotIn("ip._hidden_attr", matches)
595
595
596 # c.use_jedi = True
596 # c.use_jedi = True
597 # completions = set(c.completions('ip.', 3))
597 # completions = set(c.completions('ip.', 3))
598 # self.assertNotIn(Completion(3,3,'__str__'), completions)
598 # self.assertNotIn(Completion(3,3,'__str__'), completions)
599 # self.assertNotIn(Completion(3,3, "_hidden_attr"), completions)
599 # self.assertNotIn(Completion(3,3, "_hidden_attr"), completions)
600
600
601 with provisionalcompleter():
601 with provisionalcompleter():
602 c.use_jedi = False
602 c.use_jedi = False
603 s, matches = c.complete("ip._x.")
603 s, matches = c.complete("ip._x.")
604 self.assertIn("ip._x.keys", matches)
604 self.assertIn("ip._x.keys", matches)
605
605
606 # c.use_jedi = True
606 # c.use_jedi = True
607 # completions = set(c.completions('ip._x.', 6))
607 # completions = set(c.completions('ip._x.', 6))
608 # self.assertIn(Completion(6,6, "keys"), completions)
608 # self.assertIn(Completion(6,6, "keys"), completions)
609
609
610 del ip._hidden_attr
610 del ip._hidden_attr
611 del ip._x
611 del ip._x
612
612
613 def test_limit_to__all__False_ok(self):
613 def test_limit_to__all__False_ok(self):
614 """
614 """
615 Limit to all is deprecated, once we remove it this test can go away.
615 Limit to all is deprecated, once we remove it this test can go away.
616 """
616 """
617 ip = get_ipython()
617 ip = get_ipython()
618 c = ip.Completer
618 c = ip.Completer
619 c.use_jedi = False
619 c.use_jedi = False
620 ip.ex("class D: x=24")
620 ip.ex("class D: x=24")
621 ip.ex("d=D()")
621 ip.ex("d=D()")
622 cfg = Config()
622 cfg = Config()
623 cfg.IPCompleter.limit_to__all__ = False
623 cfg.IPCompleter.limit_to__all__ = False
624 c.update_config(cfg)
624 c.update_config(cfg)
625 s, matches = c.complete("d.")
625 s, matches = c.complete("d.")
626 self.assertIn("d.x", matches)
626 self.assertIn("d.x", matches)
627
627
628 def test_get__all__entries_ok(self):
628 def test_get__all__entries_ok(self):
629 class A:
629 class A:
630 __all__ = ["x", 1]
630 __all__ = ["x", 1]
631
631
632 words = completer.get__all__entries(A())
632 words = completer.get__all__entries(A())
633 self.assertEqual(words, ["x"])
633 self.assertEqual(words, ["x"])
634
634
635 def test_get__all__entries_no__all__ok(self):
635 def test_get__all__entries_no__all__ok(self):
636 class A:
636 class A:
637 pass
637 pass
638
638
639 words = completer.get__all__entries(A())
639 words = completer.get__all__entries(A())
640 self.assertEqual(words, [])
640 self.assertEqual(words, [])
641
641
642 def test_func_kw_completions(self):
642 def test_func_kw_completions(self):
643 ip = get_ipython()
643 ip = get_ipython()
644 c = ip.Completer
644 c = ip.Completer
645 c.use_jedi = False
645 c.use_jedi = False
646 ip.ex("def myfunc(a=1,b=2): return a+b")
646 ip.ex("def myfunc(a=1,b=2): return a+b")
647 s, matches = c.complete(None, "myfunc(1,b")
647 s, matches = c.complete(None, "myfunc(1,b")
648 self.assertIn("b=", matches)
648 self.assertIn("b=", matches)
649 # Simulate completing with cursor right after b (pos==10):
649 # Simulate completing with cursor right after b (pos==10):
650 s, matches = c.complete(None, "myfunc(1,b)", 10)
650 s, matches = c.complete(None, "myfunc(1,b)", 10)
651 self.assertIn("b=", matches)
651 self.assertIn("b=", matches)
652 s, matches = c.complete(None, 'myfunc(a="escaped\\")string",b')
652 s, matches = c.complete(None, 'myfunc(a="escaped\\")string",b')
653 self.assertIn("b=", matches)
653 self.assertIn("b=", matches)
654 # builtin function
654 # builtin function
655 s, matches = c.complete(None, "min(k, k")
655 s, matches = c.complete(None, "min(k, k")
656 self.assertIn("key=", matches)
656 self.assertIn("key=", matches)
657
657
658 def test_default_arguments_from_docstring(self):
658 def test_default_arguments_from_docstring(self):
659 ip = get_ipython()
659 ip = get_ipython()
660 c = ip.Completer
660 c = ip.Completer
661 kwd = c._default_arguments_from_docstring("min(iterable[, key=func]) -> value")
661 kwd = c._default_arguments_from_docstring("min(iterable[, key=func]) -> value")
662 self.assertEqual(kwd, ["key"])
662 self.assertEqual(kwd, ["key"])
663 # with cython type etc
663 # with cython type etc
664 kwd = c._default_arguments_from_docstring(
664 kwd = c._default_arguments_from_docstring(
665 "Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n"
665 "Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n"
666 )
666 )
667 self.assertEqual(kwd, ["ncall", "resume", "nsplit"])
667 self.assertEqual(kwd, ["ncall", "resume", "nsplit"])
668 # white spaces
668 # white spaces
669 kwd = c._default_arguments_from_docstring(
669 kwd = c._default_arguments_from_docstring(
670 "\n Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n"
670 "\n Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)\n"
671 )
671 )
672 self.assertEqual(kwd, ["ncall", "resume", "nsplit"])
672 self.assertEqual(kwd, ["ncall", "resume", "nsplit"])
673
673
674 def test_line_magics(self):
674 def test_line_magics(self):
675 ip = get_ipython()
675 ip = get_ipython()
676 c = ip.Completer
676 c = ip.Completer
677 s, matches = c.complete(None, "lsmag")
677 s, matches = c.complete(None, "lsmag")
678 self.assertIn("%lsmagic", matches)
678 self.assertIn("%lsmagic", matches)
679 s, matches = c.complete(None, "%lsmag")
679 s, matches = c.complete(None, "%lsmag")
680 self.assertIn("%lsmagic", matches)
680 self.assertIn("%lsmagic", matches)
681
681
682 def test_cell_magics(self):
682 def test_cell_magics(self):
683 from IPython.core.magic import register_cell_magic
683 from IPython.core.magic import register_cell_magic
684
684
685 @register_cell_magic
685 @register_cell_magic
686 def _foo_cellm(line, cell):
686 def _foo_cellm(line, cell):
687 pass
687 pass
688
688
689 ip = get_ipython()
689 ip = get_ipython()
690 c = ip.Completer
690 c = ip.Completer
691
691
692 s, matches = c.complete(None, "_foo_ce")
692 s, matches = c.complete(None, "_foo_ce")
693 self.assertIn("%%_foo_cellm", matches)
693 self.assertIn("%%_foo_cellm", matches)
694 s, matches = c.complete(None, "%%_foo_ce")
694 s, matches = c.complete(None, "%%_foo_ce")
695 self.assertIn("%%_foo_cellm", matches)
695 self.assertIn("%%_foo_cellm", matches)
696
696
697 def test_line_cell_magics(self):
697 def test_line_cell_magics(self):
698 from IPython.core.magic import register_line_cell_magic
698 from IPython.core.magic import register_line_cell_magic
699
699
700 @register_line_cell_magic
700 @register_line_cell_magic
701 def _bar_cellm(line, cell):
701 def _bar_cellm(line, cell):
702 pass
702 pass
703
703
704 ip = get_ipython()
704 ip = get_ipython()
705 c = ip.Completer
705 c = ip.Completer
706
706
707 # The policy here is trickier, see comments in completion code. The
707 # The policy here is trickier, see comments in completion code. The
708 # returned values depend on whether the user passes %% or not explicitly,
708 # returned values depend on whether the user passes %% or not explicitly,
709 # and this will show a difference if the same name is both a line and cell
709 # and this will show a difference if the same name is both a line and cell
710 # magic.
710 # magic.
711 s, matches = c.complete(None, "_bar_ce")
711 s, matches = c.complete(None, "_bar_ce")
712 self.assertIn("%_bar_cellm", matches)
712 self.assertIn("%_bar_cellm", matches)
713 self.assertIn("%%_bar_cellm", matches)
713 self.assertIn("%%_bar_cellm", matches)
714 s, matches = c.complete(None, "%_bar_ce")
714 s, matches = c.complete(None, "%_bar_ce")
715 self.assertIn("%_bar_cellm", matches)
715 self.assertIn("%_bar_cellm", matches)
716 self.assertIn("%%_bar_cellm", matches)
716 self.assertIn("%%_bar_cellm", matches)
717 s, matches = c.complete(None, "%%_bar_ce")
717 s, matches = c.complete(None, "%%_bar_ce")
718 self.assertNotIn("%_bar_cellm", matches)
718 self.assertNotIn("%_bar_cellm", matches)
719 self.assertIn("%%_bar_cellm", matches)
719 self.assertIn("%%_bar_cellm", matches)
720
720
721 def test_magic_completion_order(self):
721 def test_magic_completion_order(self):
722 ip = get_ipython()
722 ip = get_ipython()
723 c = ip.Completer
723 c = ip.Completer
724
724
725 # Test ordering of line and cell magics.
725 # Test ordering of line and cell magics.
726 text, matches = c.complete("timeit")
726 text, matches = c.complete("timeit")
727 self.assertEqual(matches, ["%timeit", "%%timeit"])
727 self.assertEqual(matches, ["%timeit", "%%timeit"])
728
728
729 def test_magic_completion_shadowing(self):
729 def test_magic_completion_shadowing(self):
730 ip = get_ipython()
730 ip = get_ipython()
731 c = ip.Completer
731 c = ip.Completer
732 c.use_jedi = False
732 c.use_jedi = False
733
733
734 # Before importing matplotlib, %matplotlib magic should be the only option.
734 # Before importing matplotlib, %matplotlib magic should be the only option.
735 text, matches = c.complete("mat")
735 text, matches = c.complete("mat")
736 self.assertEqual(matches, ["%matplotlib"])
736 self.assertEqual(matches, ["%matplotlib"])
737
737
738 # The newly introduced name should shadow the magic.
738 # The newly introduced name should shadow the magic.
739 ip.run_cell("matplotlib = 1")
739 ip.run_cell("matplotlib = 1")
740 text, matches = c.complete("mat")
740 text, matches = c.complete("mat")
741 self.assertEqual(matches, ["matplotlib"])
741 self.assertEqual(matches, ["matplotlib"])
742
742
743 # After removing matplotlib from namespace, the magic should again be
743 # After removing matplotlib from namespace, the magic should again be
744 # the only option.
744 # the only option.
745 del ip.user_ns["matplotlib"]
745 del ip.user_ns["matplotlib"]
746 text, matches = c.complete("mat")
746 text, matches = c.complete("mat")
747 self.assertEqual(matches, ["%matplotlib"])
747 self.assertEqual(matches, ["%matplotlib"])
748
748
749 def test_magic_completion_shadowing_explicit(self):
749 def test_magic_completion_shadowing_explicit(self):
750 """
750 """
751 If the user try to complete a shadowed magic, and explicit % start should
751 If the user try to complete a shadowed magic, and explicit % start should
752 still return the completions.
752 still return the completions.
753 """
753 """
754 ip = get_ipython()
754 ip = get_ipython()
755 c = ip.Completer
755 c = ip.Completer
756
756
757 # Before importing matplotlib, %matplotlib magic should be the only option.
757 # Before importing matplotlib, %matplotlib magic should be the only option.
758 text, matches = c.complete("%mat")
758 text, matches = c.complete("%mat")
759 self.assertEqual(matches, ["%matplotlib"])
759 self.assertEqual(matches, ["%matplotlib"])
760
760
761 ip.run_cell("matplotlib = 1")
761 ip.run_cell("matplotlib = 1")
762
762
763 # After removing matplotlib from namespace, the magic should still be
763 # After removing matplotlib from namespace, the magic should still be
764 # the only option.
764 # the only option.
765 text, matches = c.complete("%mat")
765 text, matches = c.complete("%mat")
766 self.assertEqual(matches, ["%matplotlib"])
766 self.assertEqual(matches, ["%matplotlib"])
767
767
768 def test_magic_config(self):
768 def test_magic_config(self):
769 ip = get_ipython()
769 ip = get_ipython()
770 c = ip.Completer
770 c = ip.Completer
771
771
772 s, matches = c.complete(None, "conf")
772 s, matches = c.complete(None, "conf")
773 self.assertIn("%config", matches)
773 self.assertIn("%config", matches)
774 s, matches = c.complete(None, "conf")
774 s, matches = c.complete(None, "conf")
775 self.assertNotIn("AliasManager", matches)
775 self.assertNotIn("AliasManager", matches)
776 s, matches = c.complete(None, "config ")
776 s, matches = c.complete(None, "config ")
777 self.assertIn("AliasManager", matches)
777 self.assertIn("AliasManager", matches)
778 s, matches = c.complete(None, "%config ")
778 s, matches = c.complete(None, "%config ")
779 self.assertIn("AliasManager", matches)
779 self.assertIn("AliasManager", matches)
780 s, matches = c.complete(None, "config Ali")
780 s, matches = c.complete(None, "config Ali")
781 self.assertListEqual(["AliasManager"], matches)
781 self.assertListEqual(["AliasManager"], matches)
782 s, matches = c.complete(None, "%config Ali")
782 s, matches = c.complete(None, "%config Ali")
783 self.assertListEqual(["AliasManager"], matches)
783 self.assertListEqual(["AliasManager"], matches)
784 s, matches = c.complete(None, "config AliasManager")
784 s, matches = c.complete(None, "config AliasManager")
785 self.assertListEqual(["AliasManager"], matches)
785 self.assertListEqual(["AliasManager"], matches)
786 s, matches = c.complete(None, "%config AliasManager")
786 s, matches = c.complete(None, "%config AliasManager")
787 self.assertListEqual(["AliasManager"], matches)
787 self.assertListEqual(["AliasManager"], matches)
788 s, matches = c.complete(None, "config AliasManager.")
788 s, matches = c.complete(None, "config AliasManager.")
789 self.assertIn("AliasManager.default_aliases", matches)
789 self.assertIn("AliasManager.default_aliases", matches)
790 s, matches = c.complete(None, "%config AliasManager.")
790 s, matches = c.complete(None, "%config AliasManager.")
791 self.assertIn("AliasManager.default_aliases", matches)
791 self.assertIn("AliasManager.default_aliases", matches)
792 s, matches = c.complete(None, "config AliasManager.de")
792 s, matches = c.complete(None, "config AliasManager.de")
793 self.assertListEqual(["AliasManager.default_aliases"], matches)
793 self.assertListEqual(["AliasManager.default_aliases"], matches)
794 s, matches = c.complete(None, "config AliasManager.de")
794 s, matches = c.complete(None, "config AliasManager.de")
795 self.assertListEqual(["AliasManager.default_aliases"], matches)
795 self.assertListEqual(["AliasManager.default_aliases"], matches)
796
796
797 def test_magic_color(self):
797 def test_magic_color(self):
798 ip = get_ipython()
798 ip = get_ipython()
799 c = ip.Completer
799 c = ip.Completer
800
800
801 s, matches = c.complete(None, "colo")
801 s, matches = c.complete(None, "colo")
802 self.assertIn("%colors", matches)
802 self.assertIn("%colors", matches)
803 s, matches = c.complete(None, "colo")
803 s, matches = c.complete(None, "colo")
804 self.assertNotIn("NoColor", matches)
804 self.assertNotIn("NoColor", matches)
805 s, matches = c.complete(None, "%colors") # No trailing space
805 s, matches = c.complete(None, "%colors") # No trailing space
806 self.assertNotIn("NoColor", matches)
806 self.assertNotIn("NoColor", matches)
807 s, matches = c.complete(None, "colors ")
807 s, matches = c.complete(None, "colors ")
808 self.assertIn("NoColor", matches)
808 self.assertIn("NoColor", matches)
809 s, matches = c.complete(None, "%colors ")
809 s, matches = c.complete(None, "%colors ")
810 self.assertIn("NoColor", matches)
810 self.assertIn("NoColor", matches)
811 s, matches = c.complete(None, "colors NoCo")
811 s, matches = c.complete(None, "colors NoCo")
812 self.assertListEqual(["NoColor"], matches)
812 self.assertListEqual(["NoColor"], matches)
813 s, matches = c.complete(None, "%colors NoCo")
813 s, matches = c.complete(None, "%colors NoCo")
814 self.assertListEqual(["NoColor"], matches)
814 self.assertListEqual(["NoColor"], matches)
815
815
816 def test_match_dict_keys(self):
816 def test_match_dict_keys(self):
817 """
817 """
818 Test that match_dict_keys works on a couple of use case does return what
818 Test that match_dict_keys works on a couple of use case does return what
819 expected, and does not crash
819 expected, and does not crash
820 """
820 """
821 delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?"
821 delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?"
822
822
823 keys = ["foo", b"far"]
823 keys = ["foo", b"far"]
824 assert match_dict_keys(keys, "b'", delims=delims) == ("'", 2, ["far"])
824 assert match_dict_keys(keys, "b'", delims=delims) == ("'", 2, ["far"])
825 assert match_dict_keys(keys, "b'f", delims=delims) == ("'", 2, ["far"])
825 assert match_dict_keys(keys, "b'f", delims=delims) == ("'", 2, ["far"])
826 assert match_dict_keys(keys, 'b"', delims=delims) == ('"', 2, ["far"])
826 assert match_dict_keys(keys, 'b"', delims=delims) == ('"', 2, ["far"])
827 assert match_dict_keys(keys, 'b"f', delims=delims) == ('"', 2, ["far"])
827 assert match_dict_keys(keys, 'b"f', delims=delims) == ('"', 2, ["far"])
828
828
829 assert match_dict_keys(keys, "'", delims=delims) == ("'", 1, ["foo"])
829 assert match_dict_keys(keys, "'", delims=delims) == ("'", 1, ["foo"])
830 assert match_dict_keys(keys, "'f", delims=delims) == ("'", 1, ["foo"])
830 assert match_dict_keys(keys, "'f", delims=delims) == ("'", 1, ["foo"])
831 assert match_dict_keys(keys, '"', delims=delims) == ('"', 1, ["foo"])
831 assert match_dict_keys(keys, '"', delims=delims) == ('"', 1, ["foo"])
832 assert match_dict_keys(keys, '"f', delims=delims) == ('"', 1, ["foo"])
832 assert match_dict_keys(keys, '"f', delims=delims) == ('"', 1, ["foo"])
833
833
834 match_dict_keys
834 match_dict_keys
835
835
836 def test_match_dict_keys_tuple(self):
836 def test_match_dict_keys_tuple(self):
837 """
837 """
838 Test that match_dict_keys called with extra prefix works on a couple of use case,
838 Test that match_dict_keys called with extra prefix works on a couple of use case,
839 does return what expected, and does not crash.
839 does return what expected, and does not crash.
840 """
840 """
841 delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?"
841 delims = " \t\n`!@#$^&*()=+[{]}\\|;:'\",<>?"
842
842
843 keys = [("foo", "bar"), ("foo", "oof"), ("foo", b"bar"), ('other', 'test')]
843 keys = [("foo", "bar"), ("foo", "oof"), ("foo", b"bar"), ('other', 'test')]
844
844
845 # Completion on first key == "foo"
845 # Completion on first key == "foo"
846 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["bar", "oof"])
846 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["bar", "oof"])
847 assert match_dict_keys(keys, "\"", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["bar", "oof"])
847 assert match_dict_keys(keys, "\"", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["bar", "oof"])
848 assert match_dict_keys(keys, "'o", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["oof"])
848 assert match_dict_keys(keys, "'o", delims=delims, extra_prefix=("foo",)) == ("'", 1, ["oof"])
849 assert match_dict_keys(keys, "\"o", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["oof"])
849 assert match_dict_keys(keys, "\"o", delims=delims, extra_prefix=("foo",)) == ("\"", 1, ["oof"])
850 assert match_dict_keys(keys, "b'", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
850 assert match_dict_keys(keys, "b'", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
851 assert match_dict_keys(keys, "b\"", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
851 assert match_dict_keys(keys, "b\"", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
852 assert match_dict_keys(keys, "b'b", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
852 assert match_dict_keys(keys, "b'b", delims=delims, extra_prefix=("foo",)) == ("'", 2, ["bar"])
853 assert match_dict_keys(keys, "b\"b", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
853 assert match_dict_keys(keys, "b\"b", delims=delims, extra_prefix=("foo",)) == ("\"", 2, ["bar"])
854
854
855 # No Completion
855 # No Completion
856 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("no_foo",)) == ("'", 1, [])
856 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("no_foo",)) == ("'", 1, [])
857 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("fo",)) == ("'", 1, [])
857 assert match_dict_keys(keys, "'", delims=delims, extra_prefix=("fo",)) == ("'", 1, [])
858
858
859 keys = [('foo1', 'foo2', 'foo3', 'foo4'), ('foo1', 'foo2', 'bar', 'foo4')]
859 keys = [('foo1', 'foo2', 'foo3', 'foo4'), ('foo1', 'foo2', 'bar', 'foo4')]
860 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1',)) == ("'", 1, ["foo2", "foo2"])
860 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1',)) == ("'", 1, ["foo2", "foo2"])
861 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2')) == ("'", 1, ["foo3"])
861 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2')) == ("'", 1, ["foo3"])
862 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3')) == ("'", 1, ["foo4"])
862 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3')) == ("'", 1, ["foo4"])
863 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3', 'foo4')) == ("'", 1, [])
863 assert match_dict_keys(keys, "'foo", delims=delims, extra_prefix=('foo1', 'foo2', 'foo3', 'foo4')) == ("'", 1, [])
864
864
865 def test_dict_key_completion_string(self):
865 def test_dict_key_completion_string(self):
866 """Test dictionary key completion for string keys"""
866 """Test dictionary key completion for string keys"""
867 ip = get_ipython()
867 ip = get_ipython()
868 complete = ip.Completer.complete
868 complete = ip.Completer.complete
869
869
870 ip.user_ns["d"] = {"abc": None}
870 ip.user_ns["d"] = {"abc": None}
871
871
872 # check completion at different stages
872 # check completion at different stages
873 _, matches = complete(line_buffer="d[")
873 _, matches = complete(line_buffer="d[")
874 self.assertIn("'abc'", matches)
874 self.assertIn("'abc'", matches)
875 self.assertNotIn("'abc']", matches)
875 self.assertNotIn("'abc']", matches)
876
876
877 _, matches = complete(line_buffer="d['")
877 _, matches = complete(line_buffer="d['")
878 self.assertIn("abc", matches)
878 self.assertIn("abc", matches)
879 self.assertNotIn("abc']", matches)
879 self.assertNotIn("abc']", matches)
880
880
881 _, matches = complete(line_buffer="d['a")
881 _, matches = complete(line_buffer="d['a")
882 self.assertIn("abc", matches)
882 self.assertIn("abc", matches)
883 self.assertNotIn("abc']", matches)
883 self.assertNotIn("abc']", matches)
884
884
885 # check use of different quoting
885 # check use of different quoting
886 _, matches = complete(line_buffer='d["')
886 _, matches = complete(line_buffer='d["')
887 self.assertIn("abc", matches)
887 self.assertIn("abc", matches)
888 self.assertNotIn('abc"]', matches)
888 self.assertNotIn('abc"]', matches)
889
889
890 _, matches = complete(line_buffer='d["a')
890 _, matches = complete(line_buffer='d["a')
891 self.assertIn("abc", matches)
891 self.assertIn("abc", matches)
892 self.assertNotIn('abc"]', matches)
892 self.assertNotIn('abc"]', matches)
893
893
894 # check sensitivity to following context
894 # check sensitivity to following context
895 _, matches = complete(line_buffer="d[]", cursor_pos=2)
895 _, matches = complete(line_buffer="d[]", cursor_pos=2)
896 self.assertIn("'abc'", matches)
896 self.assertIn("'abc'", matches)
897
897
898 _, matches = complete(line_buffer="d['']", cursor_pos=3)
898 _, matches = complete(line_buffer="d['']", cursor_pos=3)
899 self.assertIn("abc", matches)
899 self.assertIn("abc", matches)
900 self.assertNotIn("abc'", matches)
900 self.assertNotIn("abc'", matches)
901 self.assertNotIn("abc']", matches)
901 self.assertNotIn("abc']", matches)
902
902
903 # check multiple solutions are correctly returned and that noise is not
903 # check multiple solutions are correctly returned and that noise is not
904 ip.user_ns["d"] = {
904 ip.user_ns["d"] = {
905 "abc": None,
905 "abc": None,
906 "abd": None,
906 "abd": None,
907 "bad": None,
907 "bad": None,
908 object(): None,
908 object(): None,
909 5: None,
909 5: None,
910 ("abe", None): None,
910 ("abe", None): None,
911 (None, "abf"): None
911 (None, "abf"): None
912 }
912 }
913
913
914 _, matches = complete(line_buffer="d['a")
914 _, matches = complete(line_buffer="d['a")
915 self.assertIn("abc", matches)
915 self.assertIn("abc", matches)
916 self.assertIn("abd", matches)
916 self.assertIn("abd", matches)
917 self.assertNotIn("bad", matches)
917 self.assertNotIn("bad", matches)
918 self.assertNotIn("abe", matches)
918 self.assertNotIn("abe", matches)
919 self.assertNotIn("abf", matches)
919 self.assertNotIn("abf", matches)
920 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
920 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
921
921
922 # check escaping and whitespace
922 # check escaping and whitespace
923 ip.user_ns["d"] = {"a\nb": None, "a'b": None, 'a"b': None, "a word": None}
923 ip.user_ns["d"] = {"a\nb": None, "a'b": None, 'a"b': None, "a word": None}
924 _, matches = complete(line_buffer="d['a")
924 _, matches = complete(line_buffer="d['a")
925 self.assertIn("a\\nb", matches)
925 self.assertIn("a\\nb", matches)
926 self.assertIn("a\\'b", matches)
926 self.assertIn("a\\'b", matches)
927 self.assertIn('a"b', matches)
927 self.assertIn('a"b', matches)
928 self.assertIn("a word", matches)
928 self.assertIn("a word", matches)
929 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
929 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
930
930
931 # - can complete on non-initial word of the string
931 # - can complete on non-initial word of the string
932 _, matches = complete(line_buffer="d['a w")
932 _, matches = complete(line_buffer="d['a w")
933 self.assertIn("word", matches)
933 self.assertIn("word", matches)
934
934
935 # - understands quote escaping
935 # - understands quote escaping
936 _, matches = complete(line_buffer="d['a\\'")
936 _, matches = complete(line_buffer="d['a\\'")
937 self.assertIn("b", matches)
937 self.assertIn("b", matches)
938
938
939 # - default quoting should work like repr
939 # - default quoting should work like repr
940 _, matches = complete(line_buffer="d[")
940 _, matches = complete(line_buffer="d[")
941 self.assertIn('"a\'b"', matches)
941 self.assertIn('"a\'b"', matches)
942
942
943 # - when opening quote with ", possible to match with unescaped apostrophe
943 # - when opening quote with ", possible to match with unescaped apostrophe
944 _, matches = complete(line_buffer="d[\"a'")
944 _, matches = complete(line_buffer="d[\"a'")
945 self.assertIn("b", matches)
945 self.assertIn("b", matches)
946
946
947 # need to not split at delims that readline won't split at
947 # need to not split at delims that readline won't split at
948 if "-" not in ip.Completer.splitter.delims:
948 if "-" not in ip.Completer.splitter.delims:
949 ip.user_ns["d"] = {"before-after": None}
949 ip.user_ns["d"] = {"before-after": None}
950 _, matches = complete(line_buffer="d['before-af")
950 _, matches = complete(line_buffer="d['before-af")
951 self.assertIn("before-after", matches)
951 self.assertIn("before-after", matches)
952
952
953 # check completion on tuple-of-string keys at different stage - on first key
953 # check completion on tuple-of-string keys at different stage - on first key
954 ip.user_ns["d"] = {('foo', 'bar'): None}
954 ip.user_ns["d"] = {('foo', 'bar'): None}
955 _, matches = complete(line_buffer="d[")
955 _, matches = complete(line_buffer="d[")
956 self.assertIn("'foo'", matches)
956 self.assertIn("'foo'", matches)
957 self.assertNotIn("'foo']", matches)
957 self.assertNotIn("'foo']", matches)
958 self.assertNotIn("'bar'", matches)
958 self.assertNotIn("'bar'", matches)
959 self.assertNotIn("foo", matches)
959 self.assertNotIn("foo", matches)
960 self.assertNotIn("bar", matches)
960 self.assertNotIn("bar", matches)
961
961
962 # - match the prefix
962 # - match the prefix
963 _, matches = complete(line_buffer="d['f")
963 _, matches = complete(line_buffer="d['f")
964 self.assertIn("foo", matches)
964 self.assertIn("foo", matches)
965 self.assertNotIn("foo']", matches)
965 self.assertNotIn("foo']", matches)
966 self.assertNotIn('foo"]', matches)
966 self.assertNotIn('foo"]', matches)
967 _, matches = complete(line_buffer="d['foo")
967 _, matches = complete(line_buffer="d['foo")
968 self.assertIn("foo", matches)
968 self.assertIn("foo", matches)
969
969
970 # - can complete on second key
970 # - can complete on second key
971 _, matches = complete(line_buffer="d['foo', ")
971 _, matches = complete(line_buffer="d['foo', ")
972 self.assertIn("'bar'", matches)
972 self.assertIn("'bar'", matches)
973 _, matches = complete(line_buffer="d['foo', 'b")
973 _, matches = complete(line_buffer="d['foo', 'b")
974 self.assertIn("bar", matches)
974 self.assertIn("bar", matches)
975 self.assertNotIn("foo", matches)
975 self.assertNotIn("foo", matches)
976
976
977 # - does not propose missing keys
977 # - does not propose missing keys
978 _, matches = complete(line_buffer="d['foo', 'f")
978 _, matches = complete(line_buffer="d['foo', 'f")
979 self.assertNotIn("bar", matches)
979 self.assertNotIn("bar", matches)
980 self.assertNotIn("foo", matches)
980 self.assertNotIn("foo", matches)
981
981
982 # check sensitivity to following context
982 # check sensitivity to following context
983 _, matches = complete(line_buffer="d['foo',]", cursor_pos=8)
983 _, matches = complete(line_buffer="d['foo',]", cursor_pos=8)
984 self.assertIn("'bar'", matches)
984 self.assertIn("'bar'", matches)
985 self.assertNotIn("bar", matches)
985 self.assertNotIn("bar", matches)
986 self.assertNotIn("'foo'", matches)
986 self.assertNotIn("'foo'", matches)
987 self.assertNotIn("foo", matches)
987 self.assertNotIn("foo", matches)
988
988
989 _, matches = complete(line_buffer="d['']", cursor_pos=3)
989 _, matches = complete(line_buffer="d['']", cursor_pos=3)
990 self.assertIn("foo", matches)
990 self.assertIn("foo", matches)
991 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
991 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
992
992
993 _, matches = complete(line_buffer='d[""]', cursor_pos=3)
993 _, matches = complete(line_buffer='d[""]', cursor_pos=3)
994 self.assertIn("foo", matches)
994 self.assertIn("foo", matches)
995 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
995 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
996
996
997 _, matches = complete(line_buffer='d["foo","]', cursor_pos=9)
997 _, matches = complete(line_buffer='d["foo","]', cursor_pos=9)
998 self.assertIn("bar", matches)
998 self.assertIn("bar", matches)
999 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
999 assert not any(m.endswith(("]", '"', "'")) for m in matches), matches
1000
1000
1001 _, matches = complete(line_buffer='d["foo",]', cursor_pos=8)
1001 _, matches = complete(line_buffer='d["foo",]', cursor_pos=8)
1002 self.assertIn("'bar'", matches)
1002 self.assertIn("'bar'", matches)
1003 self.assertNotIn("bar", matches)
1003 self.assertNotIn("bar", matches)
1004
1004
1005 # Can complete with longer tuple keys
1005 # Can complete with longer tuple keys
1006 ip.user_ns["d"] = {('foo', 'bar', 'foobar'): None}
1006 ip.user_ns["d"] = {('foo', 'bar', 'foobar'): None}
1007
1007
1008 # - can complete second key
1008 # - can complete second key
1009 _, matches = complete(line_buffer="d['foo', 'b")
1009 _, matches = complete(line_buffer="d['foo', 'b")
1010 self.assertIn("bar", matches)
1010 self.assertIn("bar", matches)
1011 self.assertNotIn("foo", matches)
1011 self.assertNotIn("foo", matches)
1012 self.assertNotIn("foobar", matches)
1012 self.assertNotIn("foobar", matches)
1013
1013
1014 # - can complete third key
1014 # - can complete third key
1015 _, matches = complete(line_buffer="d['foo', 'bar', 'fo")
1015 _, matches = complete(line_buffer="d['foo', 'bar', 'fo")
1016 self.assertIn("foobar", matches)
1016 self.assertIn("foobar", matches)
1017 self.assertNotIn("foo", matches)
1017 self.assertNotIn("foo", matches)
1018 self.assertNotIn("bar", matches)
1018 self.assertNotIn("bar", matches)
1019
1019
1020 def test_dict_key_completion_contexts(self):
1020 def test_dict_key_completion_contexts(self):
1021 """Test expression contexts in which dict key completion occurs"""
1021 """Test expression contexts in which dict key completion occurs"""
1022 ip = get_ipython()
1022 ip = get_ipython()
1023 complete = ip.Completer.complete
1023 complete = ip.Completer.complete
1024 d = {"abc": None}
1024 d = {"abc": None}
1025 ip.user_ns["d"] = d
1025 ip.user_ns["d"] = d
1026
1026
1027 class C:
1027 class C:
1028 data = d
1028 data = d
1029
1029
1030 ip.user_ns["C"] = C
1030 ip.user_ns["C"] = C
1031 ip.user_ns["get"] = lambda: d
1031 ip.user_ns["get"] = lambda: d
1032
1032
1033 def assert_no_completion(**kwargs):
1033 def assert_no_completion(**kwargs):
1034 _, matches = complete(**kwargs)
1034 _, matches = complete(**kwargs)
1035 self.assertNotIn("abc", matches)
1035 self.assertNotIn("abc", matches)
1036 self.assertNotIn("abc'", matches)
1036 self.assertNotIn("abc'", matches)
1037 self.assertNotIn("abc']", matches)
1037 self.assertNotIn("abc']", matches)
1038 self.assertNotIn("'abc'", matches)
1038 self.assertNotIn("'abc'", matches)
1039 self.assertNotIn("'abc']", matches)
1039 self.assertNotIn("'abc']", matches)
1040
1040
1041 def assert_completion(**kwargs):
1041 def assert_completion(**kwargs):
1042 _, matches = complete(**kwargs)
1042 _, matches = complete(**kwargs)
1043 self.assertIn("'abc'", matches)
1043 self.assertIn("'abc'", matches)
1044 self.assertNotIn("'abc']", matches)
1044 self.assertNotIn("'abc']", matches)
1045
1045
1046 # no completion after string closed, even if reopened
1046 # no completion after string closed, even if reopened
1047 assert_no_completion(line_buffer="d['a'")
1047 assert_no_completion(line_buffer="d['a'")
1048 assert_no_completion(line_buffer='d["a"')
1048 assert_no_completion(line_buffer='d["a"')
1049 assert_no_completion(line_buffer="d['a' + ")
1049 assert_no_completion(line_buffer="d['a' + ")
1050 assert_no_completion(line_buffer="d['a' + '")
1050 assert_no_completion(line_buffer="d['a' + '")
1051
1051
1052 # completion in non-trivial expressions
1052 # completion in non-trivial expressions
1053 assert_completion(line_buffer="+ d[")
1053 assert_completion(line_buffer="+ d[")
1054 assert_completion(line_buffer="(d[")
1054 assert_completion(line_buffer="(d[")
1055 assert_completion(line_buffer="C.data[")
1055 assert_completion(line_buffer="C.data[")
1056
1056
1057 # greedy flag
1057 # greedy flag
1058 def assert_completion(**kwargs):
1058 def assert_completion(**kwargs):
1059 _, matches = complete(**kwargs)
1059 _, matches = complete(**kwargs)
1060 self.assertIn("get()['abc']", matches)
1060 self.assertIn("get()['abc']", matches)
1061
1061
1062 assert_no_completion(line_buffer="get()[")
1062 assert_no_completion(line_buffer="get()[")
1063 with greedy_completion():
1063 with greedy_completion():
1064 assert_completion(line_buffer="get()[")
1064 assert_completion(line_buffer="get()[")
1065 assert_completion(line_buffer="get()['")
1065 assert_completion(line_buffer="get()['")
1066 assert_completion(line_buffer="get()['a")
1066 assert_completion(line_buffer="get()['a")
1067 assert_completion(line_buffer="get()['ab")
1067 assert_completion(line_buffer="get()['ab")
1068 assert_completion(line_buffer="get()['abc")
1068 assert_completion(line_buffer="get()['abc")
1069
1069
1070 def test_dict_key_completion_bytes(self):
1070 def test_dict_key_completion_bytes(self):
1071 """Test handling of bytes in dict key completion"""
1071 """Test handling of bytes in dict key completion"""
1072 ip = get_ipython()
1072 ip = get_ipython()
1073 complete = ip.Completer.complete
1073 complete = ip.Completer.complete
1074
1074
1075 ip.user_ns["d"] = {"abc": None, b"abd": None}
1075 ip.user_ns["d"] = {"abc": None, b"abd": None}
1076
1076
1077 _, matches = complete(line_buffer="d[")
1077 _, matches = complete(line_buffer="d[")
1078 self.assertIn("'abc'", matches)
1078 self.assertIn("'abc'", matches)
1079 self.assertIn("b'abd'", matches)
1079 self.assertIn("b'abd'", matches)
1080
1080
1081 if False: # not currently implemented
1081 if False: # not currently implemented
1082 _, matches = complete(line_buffer="d[b")
1082 _, matches = complete(line_buffer="d[b")
1083 self.assertIn("b'abd'", matches)
1083 self.assertIn("b'abd'", matches)
1084 self.assertNotIn("b'abc'", matches)
1084 self.assertNotIn("b'abc'", matches)
1085
1085
1086 _, matches = complete(line_buffer="d[b'")
1086 _, matches = complete(line_buffer="d[b'")
1087 self.assertIn("abd", matches)
1087 self.assertIn("abd", matches)
1088 self.assertNotIn("abc", matches)
1088 self.assertNotIn("abc", matches)
1089
1089
1090 _, matches = complete(line_buffer="d[B'")
1090 _, matches = complete(line_buffer="d[B'")
1091 self.assertIn("abd", matches)
1091 self.assertIn("abd", matches)
1092 self.assertNotIn("abc", matches)
1092 self.assertNotIn("abc", matches)
1093
1093
1094 _, matches = complete(line_buffer="d['")
1094 _, matches = complete(line_buffer="d['")
1095 self.assertIn("abc", matches)
1095 self.assertIn("abc", matches)
1096 self.assertNotIn("abd", matches)
1096 self.assertNotIn("abd", matches)
1097
1097
1098 def test_dict_key_completion_unicode_py3(self):
1098 def test_dict_key_completion_unicode_py3(self):
1099 """Test handling of unicode in dict key completion"""
1099 """Test handling of unicode in dict key completion"""
1100 ip = get_ipython()
1100 ip = get_ipython()
1101 complete = ip.Completer.complete
1101 complete = ip.Completer.complete
1102
1102
1103 ip.user_ns["d"] = {"a\u05d0": None}
1103 ip.user_ns["d"] = {"a\u05d0": None}
1104
1104
1105 # query using escape
1105 # query using escape
1106 if sys.platform != "win32":
1106 if sys.platform != "win32":
1107 # Known failure on Windows
1107 # Known failure on Windows
1108 _, matches = complete(line_buffer="d['a\\u05d0")
1108 _, matches = complete(line_buffer="d['a\\u05d0")
1109 self.assertIn("u05d0", matches) # tokenized after \\
1109 self.assertIn("u05d0", matches) # tokenized after \\
1110
1110
1111 # query using character
1111 # query using character
1112 _, matches = complete(line_buffer="d['a\u05d0")
1112 _, matches = complete(line_buffer="d['a\u05d0")
1113 self.assertIn("a\u05d0", matches)
1113 self.assertIn("a\u05d0", matches)
1114
1114
1115 with greedy_completion():
1115 with greedy_completion():
1116 # query using escape
1116 # query using escape
1117 _, matches = complete(line_buffer="d['a\\u05d0")
1117 _, matches = complete(line_buffer="d['a\\u05d0")
1118 self.assertIn("d['a\\u05d0']", matches) # tokenized after \\
1118 self.assertIn("d['a\\u05d0']", matches) # tokenized after \\
1119
1119
1120 # query using character
1120 # query using character
1121 _, matches = complete(line_buffer="d['a\u05d0")
1121 _, matches = complete(line_buffer="d['a\u05d0")
1122 self.assertIn("d['a\u05d0']", matches)
1122 self.assertIn("d['a\u05d0']", matches)
1123
1123
1124 @dec.skip_without("numpy")
1124 @dec.skip_without("numpy")
1125 def test_struct_array_key_completion(self):
1125 def test_struct_array_key_completion(self):
1126 """Test dict key completion applies to numpy struct arrays"""
1126 """Test dict key completion applies to numpy struct arrays"""
1127 import numpy
1127 import numpy
1128
1128
1129 ip = get_ipython()
1129 ip = get_ipython()
1130 complete = ip.Completer.complete
1130 complete = ip.Completer.complete
1131 ip.user_ns["d"] = numpy.array([], dtype=[("hello", "f"), ("world", "f")])
1131 ip.user_ns["d"] = numpy.array([], dtype=[("hello", "f"), ("world", "f")])
1132 _, matches = complete(line_buffer="d['")
1132 _, matches = complete(line_buffer="d['")
1133 self.assertIn("hello", matches)
1133 self.assertIn("hello", matches)
1134 self.assertIn("world", matches)
1134 self.assertIn("world", matches)
1135 # complete on the numpy struct itself
1135 # complete on the numpy struct itself
1136 dt = numpy.dtype(
1136 dt = numpy.dtype(
1137 [("my_head", [("my_dt", ">u4"), ("my_df", ">u4")]), ("my_data", ">f4", 5)]
1137 [("my_head", [("my_dt", ">u4"), ("my_df", ">u4")]), ("my_data", ">f4", 5)]
1138 )
1138 )
1139 x = numpy.zeros(2, dtype=dt)
1139 x = numpy.zeros(2, dtype=dt)
1140 ip.user_ns["d"] = x[1]
1140 ip.user_ns["d"] = x[1]
1141 _, matches = complete(line_buffer="d['")
1141 _, matches = complete(line_buffer="d['")
1142 self.assertIn("my_head", matches)
1142 self.assertIn("my_head", matches)
1143 self.assertIn("my_data", matches)
1143 self.assertIn("my_data", matches)
1144 # complete on a nested level
1144 # complete on a nested level
1145 with greedy_completion():
1145 with greedy_completion():
1146 ip.user_ns["d"] = numpy.zeros(2, dtype=dt)
1146 ip.user_ns["d"] = numpy.zeros(2, dtype=dt)
1147 _, matches = complete(line_buffer="d[1]['my_head']['")
1147 _, matches = complete(line_buffer="d[1]['my_head']['")
1148 self.assertTrue(any(["my_dt" in m for m in matches]))
1148 self.assertTrue(any(["my_dt" in m for m in matches]))
1149 self.assertTrue(any(["my_df" in m for m in matches]))
1149 self.assertTrue(any(["my_df" in m for m in matches]))
1150
1150
1151 @dec.skip_without("pandas")
1151 @dec.skip_without("pandas")
1152 def test_dataframe_key_completion(self):
1152 def test_dataframe_key_completion(self):
1153 """Test dict key completion applies to pandas DataFrames"""
1153 """Test dict key completion applies to pandas DataFrames"""
1154 import pandas
1154 import pandas
1155
1155
1156 ip = get_ipython()
1156 ip = get_ipython()
1157 complete = ip.Completer.complete
1157 complete = ip.Completer.complete
1158 ip.user_ns["d"] = pandas.DataFrame({"hello": [1], "world": [2]})
1158 ip.user_ns["d"] = pandas.DataFrame({"hello": [1], "world": [2]})
1159 _, matches = complete(line_buffer="d['")
1159 _, matches = complete(line_buffer="d['")
1160 self.assertIn("hello", matches)
1160 self.assertIn("hello", matches)
1161 self.assertIn("world", matches)
1161 self.assertIn("world", matches)
1162
1162
1163 def test_dict_key_completion_invalids(self):
1163 def test_dict_key_completion_invalids(self):
1164 """Smoke test cases dict key completion can't handle"""
1164 """Smoke test cases dict key completion can't handle"""
1165 ip = get_ipython()
1165 ip = get_ipython()
1166 complete = ip.Completer.complete
1166 complete = ip.Completer.complete
1167
1167
1168 ip.user_ns["no_getitem"] = None
1168 ip.user_ns["no_getitem"] = None
1169 ip.user_ns["no_keys"] = []
1169 ip.user_ns["no_keys"] = []
1170 ip.user_ns["cant_call_keys"] = dict
1170 ip.user_ns["cant_call_keys"] = dict
1171 ip.user_ns["empty"] = {}
1171 ip.user_ns["empty"] = {}
1172 ip.user_ns["d"] = {"abc": 5}
1172 ip.user_ns["d"] = {"abc": 5}
1173
1173
1174 _, matches = complete(line_buffer="no_getitem['")
1174 _, matches = complete(line_buffer="no_getitem['")
1175 _, matches = complete(line_buffer="no_keys['")
1175 _, matches = complete(line_buffer="no_keys['")
1176 _, matches = complete(line_buffer="cant_call_keys['")
1176 _, matches = complete(line_buffer="cant_call_keys['")
1177 _, matches = complete(line_buffer="empty['")
1177 _, matches = complete(line_buffer="empty['")
1178 _, matches = complete(line_buffer="name_error['")
1178 _, matches = complete(line_buffer="name_error['")
1179 _, matches = complete(line_buffer="d['\\") # incomplete escape
1179 _, matches = complete(line_buffer="d['\\") # incomplete escape
1180
1180
1181 def test_object_key_completion(self):
1181 def test_object_key_completion(self):
1182 ip = get_ipython()
1182 ip = get_ipython()
1183 ip.user_ns["key_completable"] = KeyCompletable(["qwerty", "qwick"])
1183 ip.user_ns["key_completable"] = KeyCompletable(["qwerty", "qwick"])
1184
1184
1185 _, matches = ip.Completer.complete(line_buffer="key_completable['qw")
1185 _, matches = ip.Completer.complete(line_buffer="key_completable['qw")
1186 self.assertIn("qwerty", matches)
1186 self.assertIn("qwerty", matches)
1187 self.assertIn("qwick", matches)
1187 self.assertIn("qwick", matches)
1188
1188
1189 def test_class_key_completion(self):
1189 def test_class_key_completion(self):
1190 ip = get_ipython()
1190 ip = get_ipython()
1191 NamedInstanceClass("qwerty")
1191 NamedInstanceClass("qwerty")
1192 NamedInstanceClass("qwick")
1192 NamedInstanceClass("qwick")
1193 ip.user_ns["named_instance_class"] = NamedInstanceClass
1193 ip.user_ns["named_instance_class"] = NamedInstanceClass
1194
1194
1195 _, matches = ip.Completer.complete(line_buffer="named_instance_class['qw")
1195 _, matches = ip.Completer.complete(line_buffer="named_instance_class['qw")
1196 self.assertIn("qwerty", matches)
1196 self.assertIn("qwerty", matches)
1197 self.assertIn("qwick", matches)
1197 self.assertIn("qwick", matches)
1198
1198
1199 def test_tryimport(self):
1199 def test_tryimport(self):
1200 """
1200 """
1201 Test that try-import don't crash on trailing dot, and import modules before
1201 Test that try-import don't crash on trailing dot, and import modules before
1202 """
1202 """
1203 from IPython.core.completerlib import try_import
1203 from IPython.core.completerlib import try_import
1204
1204
1205 assert try_import("IPython.")
1205 assert try_import("IPython.")
1206
1206
1207 def test_aimport_module_completer(self):
1207 def test_aimport_module_completer(self):
1208 ip = get_ipython()
1208 ip = get_ipython()
1209 _, matches = ip.complete("i", "%aimport i")
1209 _, matches = ip.complete("i", "%aimport i")
1210 self.assertIn("io", matches)
1210 self.assertIn("io", matches)
1211 self.assertNotIn("int", matches)
1211 self.assertNotIn("int", matches)
1212
1212
1213 def test_nested_import_module_completer(self):
1213 def test_nested_import_module_completer(self):
1214 ip = get_ipython()
1214 ip = get_ipython()
1215 _, matches = ip.complete(None, "import IPython.co", 17)
1215 _, matches = ip.complete(None, "import IPython.co", 17)
1216 self.assertIn("IPython.core", matches)
1216 self.assertIn("IPython.core", matches)
1217 self.assertNotIn("import IPython.core", matches)
1217 self.assertNotIn("import IPython.core", matches)
1218 self.assertNotIn("IPython.display", matches)
1218 self.assertNotIn("IPython.display", matches)
1219
1219
1220 def test_import_module_completer(self):
1220 def test_import_module_completer(self):
1221 ip = get_ipython()
1221 ip = get_ipython()
1222 _, matches = ip.complete("i", "import i")
1222 _, matches = ip.complete("i", "import i")
1223 self.assertIn("io", matches)
1223 self.assertIn("io", matches)
1224 self.assertNotIn("int", matches)
1224 self.assertNotIn("int", matches)
1225
1225
1226 def test_from_module_completer(self):
1226 def test_from_module_completer(self):
1227 ip = get_ipython()
1227 ip = get_ipython()
1228 _, matches = ip.complete("B", "from io import B", 16)
1228 _, matches = ip.complete("B", "from io import B", 16)
1229 self.assertIn("BytesIO", matches)
1229 self.assertIn("BytesIO", matches)
1230 self.assertNotIn("BaseException", matches)
1230 self.assertNotIn("BaseException", matches)
1231
1231
1232 def test_snake_case_completion(self):
1232 def test_snake_case_completion(self):
1233 ip = get_ipython()
1233 ip = get_ipython()
1234 ip.Completer.use_jedi = False
1234 ip.Completer.use_jedi = False
1235 ip.user_ns["some_three"] = 3
1235 ip.user_ns["some_three"] = 3
1236 ip.user_ns["some_four"] = 4
1236 ip.user_ns["some_four"] = 4
1237 _, matches = ip.complete("s_", "print(s_f")
1237 _, matches = ip.complete("s_", "print(s_f")
1238 self.assertIn("some_three", matches)
1238 self.assertIn("some_three", matches)
1239 self.assertIn("some_four", matches)
1239 self.assertIn("some_four", matches)
1240
1240
1241 def test_mix_terms(self):
1241 def test_mix_terms(self):
1242 ip = get_ipython()
1242 ip = get_ipython()
1243 from textwrap import dedent
1243 from textwrap import dedent
1244
1244
1245 ip.Completer.use_jedi = False
1245 ip.Completer.use_jedi = False
1246 ip.ex(
1246 ip.ex(
1247 dedent(
1247 dedent(
1248 """
1248 """
1249 class Test:
1249 class Test:
1250 def meth(self, meth_arg1):
1250 def meth(self, meth_arg1):
1251 print("meth")
1251 print("meth")
1252
1252
1253 def meth_1(self, meth1_arg1, meth1_arg2):
1253 def meth_1(self, meth1_arg1, meth1_arg2):
1254 print("meth1")
1254 print("meth1")
1255
1255
1256 def meth_2(self, meth2_arg1, meth2_arg2):
1256 def meth_2(self, meth2_arg1, meth2_arg2):
1257 print("meth2")
1257 print("meth2")
1258 test = Test()
1258 test = Test()
1259 """
1259 """
1260 )
1260 )
1261 )
1261 )
1262 _, matches = ip.complete(None, "test.meth(")
1262 _, matches = ip.complete(None, "test.meth(")
1263 self.assertIn("meth_arg1=", matches)
1263 self.assertIn("meth_arg1=", matches)
1264 self.assertNotIn("meth2_arg1=", matches)
1264 self.assertNotIn("meth2_arg1=", matches)
@@ -1,192 +1,192 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for completerlib.
2 """Tests for completerlib.
3
3
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Imports
7 # Imports
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 import os
10 import os
11 import shutil
11 import shutil
12 import sys
12 import sys
13 import tempfile
13 import tempfile
14 import unittest
14 import unittest
15 from os.path import join
15 from os.path import join
16
16
17 from IPython.core.completerlib import magic_run_completer, module_completion, try_import
17 from IPython.core.completerlib import magic_run_completer, module_completion, try_import
18 from IPython.utils.tempdir import TemporaryDirectory
18 from IPython.utils.tempdir import TemporaryDirectory
19 from IPython.testing.decorators import onlyif_unicode_paths
19 from IPython.testing.decorators import onlyif_unicode_paths
20
20
21
21
22 class MockEvent(object):
22 class MockEvent(object):
23 def __init__(self, line):
23 def __init__(self, line):
24 self.line = line
24 self.line = line
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Test functions begin
27 # Test functions begin
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 class Test_magic_run_completer(unittest.TestCase):
29 class Test_magic_run_completer(unittest.TestCase):
30 files = [u"aao.py", u"a.py", u"b.py", u"aao.txt"]
30 files = [u"aao.py", u"a.py", u"b.py", u"aao.txt"]
31 dirs = [u"adir/", "bdir/"]
31 dirs = [u"adir/", "bdir/"]
32
32
33 def setUp(self):
33 def setUp(self):
34 self.BASETESTDIR = tempfile.mkdtemp()
34 self.BASETESTDIR = tempfile.mkdtemp()
35 for fil in self.files:
35 for fil in self.files:
36 with open(join(self.BASETESTDIR, fil), "w", encoding='utf-8') as sfile:
36 with open(join(self.BASETESTDIR, fil), "w", encoding="utf-8") as sfile:
37 sfile.write("pass\n")
37 sfile.write("pass\n")
38 for d in self.dirs:
38 for d in self.dirs:
39 os.mkdir(join(self.BASETESTDIR, d))
39 os.mkdir(join(self.BASETESTDIR, d))
40
40
41 self.oldpath = os.getcwd()
41 self.oldpath = os.getcwd()
42 os.chdir(self.BASETESTDIR)
42 os.chdir(self.BASETESTDIR)
43
43
44 def tearDown(self):
44 def tearDown(self):
45 os.chdir(self.oldpath)
45 os.chdir(self.oldpath)
46 shutil.rmtree(self.BASETESTDIR)
46 shutil.rmtree(self.BASETESTDIR)
47
47
48 def test_1(self):
48 def test_1(self):
49 """Test magic_run_completer, should match two alternatives
49 """Test magic_run_completer, should match two alternatives
50 """
50 """
51 event = MockEvent(u"%run a")
51 event = MockEvent(u"%run a")
52 mockself = None
52 mockself = None
53 match = set(magic_run_completer(mockself, event))
53 match = set(magic_run_completer(mockself, event))
54 self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
54 self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
55
55
56 def test_2(self):
56 def test_2(self):
57 """Test magic_run_completer, should match one alternative
57 """Test magic_run_completer, should match one alternative
58 """
58 """
59 event = MockEvent(u"%run aa")
59 event = MockEvent(u"%run aa")
60 mockself = None
60 mockself = None
61 match = set(magic_run_completer(mockself, event))
61 match = set(magic_run_completer(mockself, event))
62 self.assertEqual(match, {u"aao.py"})
62 self.assertEqual(match, {u"aao.py"})
63
63
64 def test_3(self):
64 def test_3(self):
65 """Test magic_run_completer with unterminated " """
65 """Test magic_run_completer with unterminated " """
66 event = MockEvent(u'%run "a')
66 event = MockEvent(u'%run "a')
67 mockself = None
67 mockself = None
68 match = set(magic_run_completer(mockself, event))
68 match = set(magic_run_completer(mockself, event))
69 self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
69 self.assertEqual(match, {u"a.py", u"aao.py", u"adir/"})
70
70
71 def test_completion_more_args(self):
71 def test_completion_more_args(self):
72 event = MockEvent(u'%run a.py ')
72 event = MockEvent(u'%run a.py ')
73 match = set(magic_run_completer(None, event))
73 match = set(magic_run_completer(None, event))
74 self.assertEqual(match, set(self.files + self.dirs))
74 self.assertEqual(match, set(self.files + self.dirs))
75
75
76 def test_completion_in_dir(self):
76 def test_completion_in_dir(self):
77 # Github issue #3459
77 # Github issue #3459
78 event = MockEvent(u'%run a.py {}'.format(join(self.BASETESTDIR, 'a')))
78 event = MockEvent(u'%run a.py {}'.format(join(self.BASETESTDIR, 'a')))
79 print(repr(event.line))
79 print(repr(event.line))
80 match = set(magic_run_completer(None, event))
80 match = set(magic_run_completer(None, event))
81 # We specifically use replace here rather than normpath, because
81 # We specifically use replace here rather than normpath, because
82 # at one point there were duplicates 'adir' and 'adir/', and normpath
82 # at one point there were duplicates 'adir' and 'adir/', and normpath
83 # would hide the failure for that.
83 # would hide the failure for that.
84 self.assertEqual(match, {join(self.BASETESTDIR, f).replace('\\','/')
84 self.assertEqual(match, {join(self.BASETESTDIR, f).replace('\\','/')
85 for f in (u'a.py', u'aao.py', u'aao.txt', u'adir/')})
85 for f in (u'a.py', u'aao.py', u'aao.txt', u'adir/')})
86
86
87 class Test_magic_run_completer_nonascii(unittest.TestCase):
87 class Test_magic_run_completer_nonascii(unittest.TestCase):
88 @onlyif_unicode_paths
88 @onlyif_unicode_paths
89 def setUp(self):
89 def setUp(self):
90 self.BASETESTDIR = tempfile.mkdtemp()
90 self.BASETESTDIR = tempfile.mkdtemp()
91 for fil in [u"aaø.py", u"a.py", u"b.py"]:
91 for fil in [u"aaø.py", u"a.py", u"b.py"]:
92 with open(join(self.BASETESTDIR, fil), "w", encoding='utf-8') as sfile:
92 with open(join(self.BASETESTDIR, fil), "w", encoding="utf-8") as sfile:
93 sfile.write("pass\n")
93 sfile.write("pass\n")
94 self.oldpath = os.getcwd()
94 self.oldpath = os.getcwd()
95 os.chdir(self.BASETESTDIR)
95 os.chdir(self.BASETESTDIR)
96
96
97 def tearDown(self):
97 def tearDown(self):
98 os.chdir(self.oldpath)
98 os.chdir(self.oldpath)
99 shutil.rmtree(self.BASETESTDIR)
99 shutil.rmtree(self.BASETESTDIR)
100
100
101 @onlyif_unicode_paths
101 @onlyif_unicode_paths
102 def test_1(self):
102 def test_1(self):
103 """Test magic_run_completer, should match two alternatives
103 """Test magic_run_completer, should match two alternatives
104 """
104 """
105 event = MockEvent(u"%run a")
105 event = MockEvent(u"%run a")
106 mockself = None
106 mockself = None
107 match = set(magic_run_completer(mockself, event))
107 match = set(magic_run_completer(mockself, event))
108 self.assertEqual(match, {u"a.py", u"aaø.py"})
108 self.assertEqual(match, {u"a.py", u"aaø.py"})
109
109
110 @onlyif_unicode_paths
110 @onlyif_unicode_paths
111 def test_2(self):
111 def test_2(self):
112 """Test magic_run_completer, should match one alternative
112 """Test magic_run_completer, should match one alternative
113 """
113 """
114 event = MockEvent(u"%run aa")
114 event = MockEvent(u"%run aa")
115 mockself = None
115 mockself = None
116 match = set(magic_run_completer(mockself, event))
116 match = set(magic_run_completer(mockself, event))
117 self.assertEqual(match, {u"aaø.py"})
117 self.assertEqual(match, {u"aaø.py"})
118
118
119 @onlyif_unicode_paths
119 @onlyif_unicode_paths
120 def test_3(self):
120 def test_3(self):
121 """Test magic_run_completer with unterminated " """
121 """Test magic_run_completer with unterminated " """
122 event = MockEvent(u'%run "a')
122 event = MockEvent(u'%run "a')
123 mockself = None
123 mockself = None
124 match = set(magic_run_completer(mockself, event))
124 match = set(magic_run_completer(mockself, event))
125 self.assertEqual(match, {u"a.py", u"aaø.py"})
125 self.assertEqual(match, {u"a.py", u"aaø.py"})
126
126
127 # module_completer:
127 # module_completer:
128
128
129 def test_import_invalid_module():
129 def test_import_invalid_module():
130 """Testing of issue https://github.com/ipython/ipython/issues/1107"""
130 """Testing of issue https://github.com/ipython/ipython/issues/1107"""
131 invalid_module_names = {'foo-bar', 'foo:bar', '10foo'}
131 invalid_module_names = {'foo-bar', 'foo:bar', '10foo'}
132 valid_module_names = {'foobar'}
132 valid_module_names = {'foobar'}
133 with TemporaryDirectory() as tmpdir:
133 with TemporaryDirectory() as tmpdir:
134 sys.path.insert( 0, tmpdir )
134 sys.path.insert( 0, tmpdir )
135 for name in invalid_module_names | valid_module_names:
135 for name in invalid_module_names | valid_module_names:
136 filename = os.path.join(tmpdir, name + '.py')
136 filename = os.path.join(tmpdir, name + ".py")
137 open(filename, 'w', encoding='utf-8').close()
137 open(filename, "w", encoding="utf-8").close()
138
138
139 s = set( module_completion('import foo') )
139 s = set( module_completion('import foo') )
140 intersection = s.intersection(invalid_module_names)
140 intersection = s.intersection(invalid_module_names)
141 assert intersection == set()
141 assert intersection == set()
142
142
143 assert valid_module_names.issubset(s), valid_module_names.intersection(s)
143 assert valid_module_names.issubset(s), valid_module_names.intersection(s)
144
144
145
145
146 def test_bad_module_all():
146 def test_bad_module_all():
147 """Test module with invalid __all__
147 """Test module with invalid __all__
148
148
149 https://github.com/ipython/ipython/issues/9678
149 https://github.com/ipython/ipython/issues/9678
150 """
150 """
151 testsdir = os.path.dirname(__file__)
151 testsdir = os.path.dirname(__file__)
152 sys.path.insert(0, testsdir)
152 sys.path.insert(0, testsdir)
153 try:
153 try:
154 results = module_completion("from bad_all import ")
154 results = module_completion("from bad_all import ")
155 assert "puppies" in results
155 assert "puppies" in results
156 for r in results:
156 for r in results:
157 assert isinstance(r, str)
157 assert isinstance(r, str)
158
158
159 # bad_all doesn't contain submodules, but this completion
159 # bad_all doesn't contain submodules, but this completion
160 # should finish without raising an exception:
160 # should finish without raising an exception:
161 results = module_completion("import bad_all.")
161 results = module_completion("import bad_all.")
162 assert results == []
162 assert results == []
163 finally:
163 finally:
164 sys.path.remove(testsdir)
164 sys.path.remove(testsdir)
165
165
166
166
167 def test_module_without_init():
167 def test_module_without_init():
168 """
168 """
169 Test module without __init__.py.
169 Test module without __init__.py.
170
170
171 https://github.com/ipython/ipython/issues/11226
171 https://github.com/ipython/ipython/issues/11226
172 """
172 """
173 fake_module_name = "foo"
173 fake_module_name = "foo"
174 with TemporaryDirectory() as tmpdir:
174 with TemporaryDirectory() as tmpdir:
175 sys.path.insert(0, tmpdir)
175 sys.path.insert(0, tmpdir)
176 try:
176 try:
177 os.makedirs(os.path.join(tmpdir, fake_module_name))
177 os.makedirs(os.path.join(tmpdir, fake_module_name))
178 s = try_import(mod=fake_module_name)
178 s = try_import(mod=fake_module_name)
179 assert s == []
179 assert s == []
180 finally:
180 finally:
181 sys.path.remove(tmpdir)
181 sys.path.remove(tmpdir)
182
182
183
183
184 def test_valid_exported_submodules():
184 def test_valid_exported_submodules():
185 """
185 """
186 Test checking exported (__all__) objects are submodules
186 Test checking exported (__all__) objects are submodules
187 """
187 """
188 results = module_completion("import os.pa")
188 results = module_completion("import os.pa")
189 # ensure we get a valid submodule:
189 # ensure we get a valid submodule:
190 assert "os.path" in results
190 assert "os.path" in results
191 # ensure we don't get objects that aren't submodules:
191 # ensure we don't get objects that aren't submodules:
192 assert "os.pathconf" not in results
192 assert "os.pathconf" not in results
@@ -1,94 +1,94 b''
1 import os.path
1 import os.path
2
2
3 import IPython.testing.tools as tt
3 import IPython.testing.tools as tt
4 from IPython.utils.syspathcontext import prepended_to_syspath
4 from IPython.utils.syspathcontext import prepended_to_syspath
5 from IPython.utils.tempdir import TemporaryDirectory
5 from IPython.utils.tempdir import TemporaryDirectory
6
6
7 ext1_content = """
7 ext1_content = """
8 def load_ipython_extension(ip):
8 def load_ipython_extension(ip):
9 print("Running ext1 load")
9 print("Running ext1 load")
10
10
11 def unload_ipython_extension(ip):
11 def unload_ipython_extension(ip):
12 print("Running ext1 unload")
12 print("Running ext1 unload")
13 """
13 """
14
14
15 ext2_content = """
15 ext2_content = """
16 def load_ipython_extension(ip):
16 def load_ipython_extension(ip):
17 print("Running ext2 load")
17 print("Running ext2 load")
18 """
18 """
19
19
20 ext3_content = """
20 ext3_content = """
21 def load_ipython_extension(ip):
21 def load_ipython_extension(ip):
22 ip2 = get_ipython()
22 ip2 = get_ipython()
23 print(ip is ip2)
23 print(ip is ip2)
24 """
24 """
25
25
26 def test_extension_loading():
26 def test_extension_loading():
27 em = get_ipython().extension_manager
27 em = get_ipython().extension_manager
28 with TemporaryDirectory() as td:
28 with TemporaryDirectory() as td:
29 ext1 = os.path.join(td, 'ext1.py')
29 ext1 = os.path.join(td, "ext1.py")
30 with open(ext1, 'w', encoding='utf-8') as f:
30 with open(ext1, "w", encoding="utf-8") as f:
31 f.write(ext1_content)
31 f.write(ext1_content)
32
32
33 ext2 = os.path.join(td, 'ext2.py')
33 ext2 = os.path.join(td, "ext2.py")
34 with open(ext2, 'w', encoding='utf-8') as f:
34 with open(ext2, "w", encoding="utf-8") as f:
35 f.write(ext2_content)
35 f.write(ext2_content)
36
36
37 with prepended_to_syspath(td):
37 with prepended_to_syspath(td):
38 assert 'ext1' not in em.loaded
38 assert 'ext1' not in em.loaded
39 assert 'ext2' not in em.loaded
39 assert 'ext2' not in em.loaded
40
40
41 # Load extension
41 # Load extension
42 with tt.AssertPrints("Running ext1 load"):
42 with tt.AssertPrints("Running ext1 load"):
43 assert em.load_extension('ext1') is None
43 assert em.load_extension('ext1') is None
44 assert 'ext1' in em.loaded
44 assert 'ext1' in em.loaded
45
45
46 # Should refuse to load it again
46 # Should refuse to load it again
47 with tt.AssertNotPrints("Running ext1 load"):
47 with tt.AssertNotPrints("Running ext1 load"):
48 assert em.load_extension('ext1') == 'already loaded'
48 assert em.load_extension('ext1') == 'already loaded'
49
49
50 # Reload
50 # Reload
51 with tt.AssertPrints("Running ext1 unload"):
51 with tt.AssertPrints("Running ext1 unload"):
52 with tt.AssertPrints("Running ext1 load", suppress=False):
52 with tt.AssertPrints("Running ext1 load", suppress=False):
53 em.reload_extension('ext1')
53 em.reload_extension('ext1')
54
54
55 # Unload
55 # Unload
56 with tt.AssertPrints("Running ext1 unload"):
56 with tt.AssertPrints("Running ext1 unload"):
57 assert em.unload_extension('ext1') is None
57 assert em.unload_extension('ext1') is None
58
58
59 # Can't unload again
59 # Can't unload again
60 with tt.AssertNotPrints("Running ext1 unload"):
60 with tt.AssertNotPrints("Running ext1 unload"):
61 assert em.unload_extension('ext1') == 'not loaded'
61 assert em.unload_extension('ext1') == 'not loaded'
62 assert em.unload_extension('ext2') == 'not loaded'
62 assert em.unload_extension('ext2') == 'not loaded'
63
63
64 # Load extension 2
64 # Load extension 2
65 with tt.AssertPrints("Running ext2 load"):
65 with tt.AssertPrints("Running ext2 load"):
66 assert em.load_extension('ext2') is None
66 assert em.load_extension('ext2') is None
67
67
68 # Can't unload this
68 # Can't unload this
69 assert em.unload_extension('ext2') == 'no unload function'
69 assert em.unload_extension('ext2') == 'no unload function'
70
70
71 # But can reload it
71 # But can reload it
72 with tt.AssertPrints("Running ext2 load"):
72 with tt.AssertPrints("Running ext2 load"):
73 em.reload_extension('ext2')
73 em.reload_extension('ext2')
74
74
75
75
76 def test_extension_builtins():
76 def test_extension_builtins():
77 em = get_ipython().extension_manager
77 em = get_ipython().extension_manager
78 with TemporaryDirectory() as td:
78 with TemporaryDirectory() as td:
79 ext3 = os.path.join(td, 'ext3.py')
79 ext3 = os.path.join(td, "ext3.py")
80 with open(ext3, 'w', encoding='utf-8') as f:
80 with open(ext3, "w", encoding="utf-8") as f:
81 f.write(ext3_content)
81 f.write(ext3_content)
82
82
83 assert 'ext3' not in em.loaded
83 assert 'ext3' not in em.loaded
84
84
85 with prepended_to_syspath(td):
85 with prepended_to_syspath(td):
86 # Load extension
86 # Load extension
87 with tt.AssertPrints("True"):
87 with tt.AssertPrints("True"):
88 assert em.load_extension('ext3') is None
88 assert em.load_extension('ext3') is None
89 assert 'ext3' in em.loaded
89 assert 'ext3' in em.loaded
90
90
91
91
92 def test_non_extension():
92 def test_non_extension():
93 em = get_ipython().extension_manager
93 em = get_ipython().extension_manager
94 assert em.load_extension("sys") == "no load function"
94 assert em.load_extension("sys") == "no load function"
@@ -1,1098 +1,1100 b''
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 from unittest import mock
20 from unittest import mock
21
21
22 from os.path import join
22 from os.path import join
23
23
24 from IPython.core.error import InputRejected
24 from IPython.core.error import InputRejected
25 from IPython.core.inputtransformer import InputTransformer
25 from IPython.core.inputtransformer import InputTransformer
26 from IPython.core import interactiveshell
26 from IPython.core import interactiveshell
27 from IPython.testing.decorators import (
27 from IPython.testing.decorators import (
28 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
28 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
29 )
29 )
30 from IPython.testing import tools as tt
30 from IPython.testing import tools as tt
31 from IPython.utils.process import find_cmd
31 from IPython.utils.process import find_cmd
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Globals
34 # Globals
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # This is used by every single test, no point repeating it ad nauseam
36 # This is used by every single test, no point repeating it ad nauseam
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Tests
39 # Tests
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 class DerivedInterrupt(KeyboardInterrupt):
42 class DerivedInterrupt(KeyboardInterrupt):
43 pass
43 pass
44
44
45 class InteractiveShellTestCase(unittest.TestCase):
45 class InteractiveShellTestCase(unittest.TestCase):
46 def test_naked_string_cells(self):
46 def test_naked_string_cells(self):
47 """Test that cells with only naked strings are fully executed"""
47 """Test that cells with only naked strings are fully executed"""
48 # First, single-line inputs
48 # First, single-line inputs
49 ip.run_cell('"a"\n')
49 ip.run_cell('"a"\n')
50 self.assertEqual(ip.user_ns['_'], 'a')
50 self.assertEqual(ip.user_ns['_'], 'a')
51 # And also multi-line cells
51 # And also multi-line cells
52 ip.run_cell('"""a\nb"""\n')
52 ip.run_cell('"""a\nb"""\n')
53 self.assertEqual(ip.user_ns['_'], 'a\nb')
53 self.assertEqual(ip.user_ns['_'], 'a\nb')
54
54
55 def test_run_empty_cell(self):
55 def test_run_empty_cell(self):
56 """Just make sure we don't get a horrible error with a blank
56 """Just make sure we don't get a horrible error with a blank
57 cell of input. Yes, I did overlook that."""
57 cell of input. Yes, I did overlook that."""
58 old_xc = ip.execution_count
58 old_xc = ip.execution_count
59 res = ip.run_cell('')
59 res = ip.run_cell('')
60 self.assertEqual(ip.execution_count, old_xc)
60 self.assertEqual(ip.execution_count, old_xc)
61 self.assertEqual(res.execution_count, None)
61 self.assertEqual(res.execution_count, None)
62
62
63 def test_run_cell_multiline(self):
63 def test_run_cell_multiline(self):
64 """Multi-block, multi-line cells must execute correctly.
64 """Multi-block, multi-line cells must execute correctly.
65 """
65 """
66 src = '\n'.join(["x=1",
66 src = '\n'.join(["x=1",
67 "y=2",
67 "y=2",
68 "if 1:",
68 "if 1:",
69 " x += 1",
69 " x += 1",
70 " y += 1",])
70 " y += 1",])
71 res = ip.run_cell(src)
71 res = ip.run_cell(src)
72 self.assertEqual(ip.user_ns['x'], 2)
72 self.assertEqual(ip.user_ns['x'], 2)
73 self.assertEqual(ip.user_ns['y'], 3)
73 self.assertEqual(ip.user_ns['y'], 3)
74 self.assertEqual(res.success, True)
74 self.assertEqual(res.success, True)
75 self.assertEqual(res.result, None)
75 self.assertEqual(res.result, None)
76
76
77 def test_multiline_string_cells(self):
77 def test_multiline_string_cells(self):
78 "Code sprinkled with multiline strings should execute (GH-306)"
78 "Code sprinkled with multiline strings should execute (GH-306)"
79 ip.run_cell('tmp=0')
79 ip.run_cell('tmp=0')
80 self.assertEqual(ip.user_ns['tmp'], 0)
80 self.assertEqual(ip.user_ns['tmp'], 0)
81 res = ip.run_cell('tmp=1;"""a\nb"""\n')
81 res = ip.run_cell('tmp=1;"""a\nb"""\n')
82 self.assertEqual(ip.user_ns['tmp'], 1)
82 self.assertEqual(ip.user_ns['tmp'], 1)
83 self.assertEqual(res.success, True)
83 self.assertEqual(res.success, True)
84 self.assertEqual(res.result, "a\nb")
84 self.assertEqual(res.result, "a\nb")
85
85
86 def test_dont_cache_with_semicolon(self):
86 def test_dont_cache_with_semicolon(self):
87 "Ending a line with semicolon should not cache the returned object (GH-307)"
87 "Ending a line with semicolon should not cache the returned object (GH-307)"
88 oldlen = len(ip.user_ns['Out'])
88 oldlen = len(ip.user_ns['Out'])
89 for cell in ['1;', '1;1;']:
89 for cell in ['1;', '1;1;']:
90 res = ip.run_cell(cell, store_history=True)
90 res = ip.run_cell(cell, store_history=True)
91 newlen = len(ip.user_ns['Out'])
91 newlen = len(ip.user_ns['Out'])
92 self.assertEqual(oldlen, newlen)
92 self.assertEqual(oldlen, newlen)
93 self.assertIsNone(res.result)
93 self.assertIsNone(res.result)
94 i = 0
94 i = 0
95 #also test the default caching behavior
95 #also test the default caching behavior
96 for cell in ['1', '1;1']:
96 for cell in ['1', '1;1']:
97 ip.run_cell(cell, store_history=True)
97 ip.run_cell(cell, store_history=True)
98 newlen = len(ip.user_ns['Out'])
98 newlen = len(ip.user_ns['Out'])
99 i += 1
99 i += 1
100 self.assertEqual(oldlen+i, newlen)
100 self.assertEqual(oldlen+i, newlen)
101
101
102 def test_syntax_error(self):
102 def test_syntax_error(self):
103 res = ip.run_cell("raise = 3")
103 res = ip.run_cell("raise = 3")
104 self.assertIsInstance(res.error_before_exec, SyntaxError)
104 self.assertIsInstance(res.error_before_exec, SyntaxError)
105
105
106 def test_In_variable(self):
106 def test_In_variable(self):
107 "Verify that In variable grows with user input (GH-284)"
107 "Verify that In variable grows with user input (GH-284)"
108 oldlen = len(ip.user_ns['In'])
108 oldlen = len(ip.user_ns['In'])
109 ip.run_cell('1;', store_history=True)
109 ip.run_cell('1;', store_history=True)
110 newlen = len(ip.user_ns['In'])
110 newlen = len(ip.user_ns['In'])
111 self.assertEqual(oldlen+1, newlen)
111 self.assertEqual(oldlen+1, newlen)
112 self.assertEqual(ip.user_ns['In'][-1],'1;')
112 self.assertEqual(ip.user_ns['In'][-1],'1;')
113
113
114 def test_magic_names_in_string(self):
114 def test_magic_names_in_string(self):
115 ip.run_cell('a = """\n%exit\n"""')
115 ip.run_cell('a = """\n%exit\n"""')
116 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
116 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
117
117
118 def test_trailing_newline(self):
118 def test_trailing_newline(self):
119 """test that running !(command) does not raise a SyntaxError"""
119 """test that running !(command) does not raise a SyntaxError"""
120 ip.run_cell('!(true)\n', False)
120 ip.run_cell('!(true)\n', False)
121 ip.run_cell('!(true)\n\n\n', False)
121 ip.run_cell('!(true)\n\n\n', False)
122
122
123 def test_gh_597(self):
123 def test_gh_597(self):
124 """Pretty-printing lists of objects with non-ascii reprs may cause
124 """Pretty-printing lists of objects with non-ascii reprs may cause
125 problems."""
125 problems."""
126 class Spam(object):
126 class Spam(object):
127 def __repr__(self):
127 def __repr__(self):
128 return "\xe9"*50
128 return "\xe9"*50
129 import IPython.core.formatters
129 import IPython.core.formatters
130 f = IPython.core.formatters.PlainTextFormatter()
130 f = IPython.core.formatters.PlainTextFormatter()
131 f([Spam(),Spam()])
131 f([Spam(),Spam()])
132
132
133
133
134 def test_future_flags(self):
134 def test_future_flags(self):
135 """Check that future flags are used for parsing code (gh-777)"""
135 """Check that future flags are used for parsing code (gh-777)"""
136 ip.run_cell('from __future__ import barry_as_FLUFL')
136 ip.run_cell('from __future__ import barry_as_FLUFL')
137 try:
137 try:
138 ip.run_cell('prfunc_return_val = 1 <> 2')
138 ip.run_cell('prfunc_return_val = 1 <> 2')
139 assert 'prfunc_return_val' in ip.user_ns
139 assert 'prfunc_return_val' in ip.user_ns
140 finally:
140 finally:
141 # Reset compiler flags so we don't mess up other tests.
141 # Reset compiler flags so we don't mess up other tests.
142 ip.compile.reset_compiler_flags()
142 ip.compile.reset_compiler_flags()
143
143
144 def test_can_pickle(self):
144 def test_can_pickle(self):
145 "Can we pickle objects defined interactively (GH-29)"
145 "Can we pickle objects defined interactively (GH-29)"
146 ip = get_ipython()
146 ip = get_ipython()
147 ip.reset()
147 ip.reset()
148 ip.run_cell(("class Mylist(list):\n"
148 ip.run_cell(("class Mylist(list):\n"
149 " def __init__(self,x=[]):\n"
149 " def __init__(self,x=[]):\n"
150 " list.__init__(self,x)"))
150 " list.__init__(self,x)"))
151 ip.run_cell("w=Mylist([1,2,3])")
151 ip.run_cell("w=Mylist([1,2,3])")
152
152
153 from pickle import dumps
153 from pickle import dumps
154
154
155 # We need to swap in our main module - this is only necessary
155 # We need to swap in our main module - this is only necessary
156 # inside the test framework, because IPython puts the interactive module
156 # inside the test framework, because IPython puts the interactive module
157 # in place (but the test framework undoes this).
157 # in place (but the test framework undoes this).
158 _main = sys.modules['__main__']
158 _main = sys.modules['__main__']
159 sys.modules['__main__'] = ip.user_module
159 sys.modules['__main__'] = ip.user_module
160 try:
160 try:
161 res = dumps(ip.user_ns["w"])
161 res = dumps(ip.user_ns["w"])
162 finally:
162 finally:
163 sys.modules['__main__'] = _main
163 sys.modules['__main__'] = _main
164 self.assertTrue(isinstance(res, bytes))
164 self.assertTrue(isinstance(res, bytes))
165
165
166 def test_global_ns(self):
166 def test_global_ns(self):
167 "Code in functions must be able to access variables outside them."
167 "Code in functions must be able to access variables outside them."
168 ip = get_ipython()
168 ip = get_ipython()
169 ip.run_cell("a = 10")
169 ip.run_cell("a = 10")
170 ip.run_cell(("def f(x):\n"
170 ip.run_cell(("def f(x):\n"
171 " return x + a"))
171 " return x + a"))
172 ip.run_cell("b = f(12)")
172 ip.run_cell("b = f(12)")
173 self.assertEqual(ip.user_ns["b"], 22)
173 self.assertEqual(ip.user_ns["b"], 22)
174
174
175 def test_bad_custom_tb(self):
175 def test_bad_custom_tb(self):
176 """Check that InteractiveShell is protected from bad custom exception handlers"""
176 """Check that InteractiveShell is protected from bad custom exception handlers"""
177 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
177 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
178 self.assertEqual(ip.custom_exceptions, (IOError,))
178 self.assertEqual(ip.custom_exceptions, (IOError,))
179 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
179 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
180 ip.run_cell(u'raise IOError("foo")')
180 ip.run_cell(u'raise IOError("foo")')
181 self.assertEqual(ip.custom_exceptions, ())
181 self.assertEqual(ip.custom_exceptions, ())
182
182
183 def test_bad_custom_tb_return(self):
183 def test_bad_custom_tb_return(self):
184 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
184 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
185 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
185 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
186 self.assertEqual(ip.custom_exceptions, (NameError,))
186 self.assertEqual(ip.custom_exceptions, (NameError,))
187 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
187 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
188 ip.run_cell(u'a=abracadabra')
188 ip.run_cell(u'a=abracadabra')
189 self.assertEqual(ip.custom_exceptions, ())
189 self.assertEqual(ip.custom_exceptions, ())
190
190
191 def test_drop_by_id(self):
191 def test_drop_by_id(self):
192 myvars = {"a":object(), "b":object(), "c": object()}
192 myvars = {"a":object(), "b":object(), "c": object()}
193 ip.push(myvars, interactive=False)
193 ip.push(myvars, interactive=False)
194 for name in myvars:
194 for name in myvars:
195 assert name in ip.user_ns, name
195 assert name in ip.user_ns, name
196 assert name in ip.user_ns_hidden, name
196 assert name in ip.user_ns_hidden, name
197 ip.user_ns['b'] = 12
197 ip.user_ns['b'] = 12
198 ip.drop_by_id(myvars)
198 ip.drop_by_id(myvars)
199 for name in ["a", "c"]:
199 for name in ["a", "c"]:
200 assert name not in ip.user_ns, name
200 assert name not in ip.user_ns, name
201 assert name not in ip.user_ns_hidden, name
201 assert name not in ip.user_ns_hidden, name
202 assert ip.user_ns['b'] == 12
202 assert ip.user_ns['b'] == 12
203 ip.reset()
203 ip.reset()
204
204
205 def test_var_expand(self):
205 def test_var_expand(self):
206 ip.user_ns['f'] = u'Ca\xf1o'
206 ip.user_ns['f'] = u'Ca\xf1o'
207 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
207 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
208 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
208 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
209 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
209 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
210 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
210 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
211
211
212 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
212 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
213
213
214 ip.user_ns['f'] = b'Ca\xc3\xb1o'
214 ip.user_ns['f'] = b'Ca\xc3\xb1o'
215 # This should not raise any exception:
215 # This should not raise any exception:
216 ip.var_expand(u'echo $f')
216 ip.var_expand(u'echo $f')
217
217
218 def test_var_expand_local(self):
218 def test_var_expand_local(self):
219 """Test local variable expansion in !system and %magic calls"""
219 """Test local variable expansion in !system and %magic calls"""
220 # !system
220 # !system
221 ip.run_cell(
221 ip.run_cell(
222 "def test():\n"
222 "def test():\n"
223 ' lvar = "ttt"\n'
223 ' lvar = "ttt"\n'
224 " ret = !echo {lvar}\n"
224 " ret = !echo {lvar}\n"
225 " return ret[0]\n"
225 " return ret[0]\n"
226 )
226 )
227 res = ip.user_ns["test"]()
227 res = ip.user_ns["test"]()
228 self.assertIn("ttt", res)
228 self.assertIn("ttt", res)
229
229
230 # %magic
230 # %magic
231 ip.run_cell(
231 ip.run_cell(
232 "def makemacro():\n"
232 "def makemacro():\n"
233 ' macroname = "macro_var_expand_locals"\n'
233 ' macroname = "macro_var_expand_locals"\n'
234 " %macro {macroname} codestr\n"
234 " %macro {macroname} codestr\n"
235 )
235 )
236 ip.user_ns["codestr"] = "str(12)"
236 ip.user_ns["codestr"] = "str(12)"
237 ip.run_cell("makemacro()")
237 ip.run_cell("makemacro()")
238 self.assertIn("macro_var_expand_locals", ip.user_ns)
238 self.assertIn("macro_var_expand_locals", ip.user_ns)
239
239
240 def test_var_expand_self(self):
240 def test_var_expand_self(self):
241 """Test variable expansion with the name 'self', which was failing.
241 """Test variable expansion with the name 'self', which was failing.
242
242
243 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
243 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
244 """
244 """
245 ip.run_cell(
245 ip.run_cell(
246 "class cTest:\n"
246 "class cTest:\n"
247 ' classvar="see me"\n'
247 ' classvar="see me"\n'
248 " def test(self):\n"
248 " def test(self):\n"
249 " res = !echo Variable: {self.classvar}\n"
249 " res = !echo Variable: {self.classvar}\n"
250 " return res[0]\n"
250 " return res[0]\n"
251 )
251 )
252 self.assertIn("see me", ip.user_ns["cTest"]().test())
252 self.assertIn("see me", ip.user_ns["cTest"]().test())
253
253
254 def test_bad_var_expand(self):
254 def test_bad_var_expand(self):
255 """var_expand on invalid formats shouldn't raise"""
255 """var_expand on invalid formats shouldn't raise"""
256 # SyntaxError
256 # SyntaxError
257 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
257 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
258 # NameError
258 # NameError
259 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
259 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
260 # ZeroDivisionError
260 # ZeroDivisionError
261 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
261 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
262
262
263 def test_silent_postexec(self):
263 def test_silent_postexec(self):
264 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
264 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
265 pre_explicit = mock.Mock()
265 pre_explicit = mock.Mock()
266 pre_always = mock.Mock()
266 pre_always = mock.Mock()
267 post_explicit = mock.Mock()
267 post_explicit = mock.Mock()
268 post_always = mock.Mock()
268 post_always = mock.Mock()
269 all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
269 all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
270
270
271 ip.events.register('pre_run_cell', pre_explicit)
271 ip.events.register('pre_run_cell', pre_explicit)
272 ip.events.register('pre_execute', pre_always)
272 ip.events.register('pre_execute', pre_always)
273 ip.events.register('post_run_cell', post_explicit)
273 ip.events.register('post_run_cell', post_explicit)
274 ip.events.register('post_execute', post_always)
274 ip.events.register('post_execute', post_always)
275
275
276 try:
276 try:
277 ip.run_cell("1", silent=True)
277 ip.run_cell("1", silent=True)
278 assert pre_always.called
278 assert pre_always.called
279 assert not pre_explicit.called
279 assert not pre_explicit.called
280 assert post_always.called
280 assert post_always.called
281 assert not post_explicit.called
281 assert not post_explicit.called
282 # double-check that non-silent exec did what we expected
282 # double-check that non-silent exec did what we expected
283 # silent to avoid
283 # silent to avoid
284 ip.run_cell("1")
284 ip.run_cell("1")
285 assert pre_explicit.called
285 assert pre_explicit.called
286 assert post_explicit.called
286 assert post_explicit.called
287 info, = pre_explicit.call_args[0]
287 info, = pre_explicit.call_args[0]
288 result, = post_explicit.call_args[0]
288 result, = post_explicit.call_args[0]
289 self.assertEqual(info, result.info)
289 self.assertEqual(info, result.info)
290 # check that post hooks are always called
290 # check that post hooks are always called
291 [m.reset_mock() for m in all_mocks]
291 [m.reset_mock() for m in all_mocks]
292 ip.run_cell("syntax error")
292 ip.run_cell("syntax error")
293 assert pre_always.called
293 assert pre_always.called
294 assert pre_explicit.called
294 assert pre_explicit.called
295 assert post_always.called
295 assert post_always.called
296 assert post_explicit.called
296 assert post_explicit.called
297 info, = pre_explicit.call_args[0]
297 info, = pre_explicit.call_args[0]
298 result, = post_explicit.call_args[0]
298 result, = post_explicit.call_args[0]
299 self.assertEqual(info, result.info)
299 self.assertEqual(info, result.info)
300 finally:
300 finally:
301 # remove post-exec
301 # remove post-exec
302 ip.events.unregister('pre_run_cell', pre_explicit)
302 ip.events.unregister('pre_run_cell', pre_explicit)
303 ip.events.unregister('pre_execute', pre_always)
303 ip.events.unregister('pre_execute', pre_always)
304 ip.events.unregister('post_run_cell', post_explicit)
304 ip.events.unregister('post_run_cell', post_explicit)
305 ip.events.unregister('post_execute', post_always)
305 ip.events.unregister('post_execute', post_always)
306
306
307 def test_silent_noadvance(self):
307 def test_silent_noadvance(self):
308 """run_cell(silent=True) doesn't advance execution_count"""
308 """run_cell(silent=True) doesn't advance execution_count"""
309 ec = ip.execution_count
309 ec = ip.execution_count
310 # silent should force store_history=False
310 # silent should force store_history=False
311 ip.run_cell("1", store_history=True, silent=True)
311 ip.run_cell("1", store_history=True, silent=True)
312
312
313 self.assertEqual(ec, ip.execution_count)
313 self.assertEqual(ec, ip.execution_count)
314 # double-check that non-silent exec did what we expected
314 # double-check that non-silent exec did what we expected
315 # silent to avoid
315 # silent to avoid
316 ip.run_cell("1", store_history=True)
316 ip.run_cell("1", store_history=True)
317 self.assertEqual(ec+1, ip.execution_count)
317 self.assertEqual(ec+1, ip.execution_count)
318
318
319 def test_silent_nodisplayhook(self):
319 def test_silent_nodisplayhook(self):
320 """run_cell(silent=True) doesn't trigger displayhook"""
320 """run_cell(silent=True) doesn't trigger displayhook"""
321 d = dict(called=False)
321 d = dict(called=False)
322
322
323 trap = ip.display_trap
323 trap = ip.display_trap
324 save_hook = trap.hook
324 save_hook = trap.hook
325
325
326 def failing_hook(*args, **kwargs):
326 def failing_hook(*args, **kwargs):
327 d['called'] = True
327 d['called'] = True
328
328
329 try:
329 try:
330 trap.hook = failing_hook
330 trap.hook = failing_hook
331 res = ip.run_cell("1", silent=True)
331 res = ip.run_cell("1", silent=True)
332 self.assertFalse(d['called'])
332 self.assertFalse(d['called'])
333 self.assertIsNone(res.result)
333 self.assertIsNone(res.result)
334 # double-check that non-silent exec did what we expected
334 # double-check that non-silent exec did what we expected
335 # silent to avoid
335 # silent to avoid
336 ip.run_cell("1")
336 ip.run_cell("1")
337 self.assertTrue(d['called'])
337 self.assertTrue(d['called'])
338 finally:
338 finally:
339 trap.hook = save_hook
339 trap.hook = save_hook
340
340
341 def test_ofind_line_magic(self):
341 def test_ofind_line_magic(self):
342 from IPython.core.magic import register_line_magic
342 from IPython.core.magic import register_line_magic
343
343
344 @register_line_magic
344 @register_line_magic
345 def lmagic(line):
345 def lmagic(line):
346 "A line magic"
346 "A line magic"
347
347
348 # Get info on line magic
348 # Get info on line magic
349 lfind = ip._ofind("lmagic")
349 lfind = ip._ofind("lmagic")
350 info = dict(
350 info = dict(
351 found=True,
351 found=True,
352 isalias=False,
352 isalias=False,
353 ismagic=True,
353 ismagic=True,
354 namespace="IPython internal",
354 namespace="IPython internal",
355 obj=lmagic,
355 obj=lmagic,
356 parent=None,
356 parent=None,
357 )
357 )
358 self.assertEqual(lfind, info)
358 self.assertEqual(lfind, info)
359
359
360 def test_ofind_cell_magic(self):
360 def test_ofind_cell_magic(self):
361 from IPython.core.magic import register_cell_magic
361 from IPython.core.magic import register_cell_magic
362
362
363 @register_cell_magic
363 @register_cell_magic
364 def cmagic(line, cell):
364 def cmagic(line, cell):
365 "A cell magic"
365 "A cell magic"
366
366
367 # Get info on cell magic
367 # Get info on cell magic
368 find = ip._ofind("cmagic")
368 find = ip._ofind("cmagic")
369 info = dict(
369 info = dict(
370 found=True,
370 found=True,
371 isalias=False,
371 isalias=False,
372 ismagic=True,
372 ismagic=True,
373 namespace="IPython internal",
373 namespace="IPython internal",
374 obj=cmagic,
374 obj=cmagic,
375 parent=None,
375 parent=None,
376 )
376 )
377 self.assertEqual(find, info)
377 self.assertEqual(find, info)
378
378
379 def test_ofind_property_with_error(self):
379 def test_ofind_property_with_error(self):
380 class A(object):
380 class A(object):
381 @property
381 @property
382 def foo(self):
382 def foo(self):
383 raise NotImplementedError()
383 raise NotImplementedError()
384 a = A()
384 a = A()
385
385
386 found = ip._ofind('a.foo', [('locals', locals())])
386 found = ip._ofind('a.foo', [('locals', locals())])
387 info = dict(found=True, isalias=False, ismagic=False,
387 info = dict(found=True, isalias=False, ismagic=False,
388 namespace='locals', obj=A.foo, parent=a)
388 namespace='locals', obj=A.foo, parent=a)
389 self.assertEqual(found, info)
389 self.assertEqual(found, info)
390
390
391 def test_ofind_multiple_attribute_lookups(self):
391 def test_ofind_multiple_attribute_lookups(self):
392 class A(object):
392 class A(object):
393 @property
393 @property
394 def foo(self):
394 def foo(self):
395 raise NotImplementedError()
395 raise NotImplementedError()
396
396
397 a = A()
397 a = A()
398 a.a = A()
398 a.a = A()
399 a.a.a = A()
399 a.a.a = A()
400
400
401 found = ip._ofind('a.a.a.foo', [('locals', locals())])
401 found = ip._ofind('a.a.a.foo', [('locals', locals())])
402 info = dict(found=True, isalias=False, ismagic=False,
402 info = dict(found=True, isalias=False, ismagic=False,
403 namespace='locals', obj=A.foo, parent=a.a.a)
403 namespace='locals', obj=A.foo, parent=a.a.a)
404 self.assertEqual(found, info)
404 self.assertEqual(found, info)
405
405
406 def test_ofind_slotted_attributes(self):
406 def test_ofind_slotted_attributes(self):
407 class A(object):
407 class A(object):
408 __slots__ = ['foo']
408 __slots__ = ['foo']
409 def __init__(self):
409 def __init__(self):
410 self.foo = 'bar'
410 self.foo = 'bar'
411
411
412 a = A()
412 a = A()
413 found = ip._ofind('a.foo', [('locals', locals())])
413 found = ip._ofind('a.foo', [('locals', locals())])
414 info = dict(found=True, isalias=False, ismagic=False,
414 info = dict(found=True, isalias=False, ismagic=False,
415 namespace='locals', obj=a.foo, parent=a)
415 namespace='locals', obj=a.foo, parent=a)
416 self.assertEqual(found, info)
416 self.assertEqual(found, info)
417
417
418 found = ip._ofind('a.bar', [('locals', locals())])
418 found = ip._ofind('a.bar', [('locals', locals())])
419 info = dict(found=False, isalias=False, ismagic=False,
419 info = dict(found=False, isalias=False, ismagic=False,
420 namespace=None, obj=None, parent=a)
420 namespace=None, obj=None, parent=a)
421 self.assertEqual(found, info)
421 self.assertEqual(found, info)
422
422
423 def test_ofind_prefers_property_to_instance_level_attribute(self):
423 def test_ofind_prefers_property_to_instance_level_attribute(self):
424 class A(object):
424 class A(object):
425 @property
425 @property
426 def foo(self):
426 def foo(self):
427 return 'bar'
427 return 'bar'
428 a = A()
428 a = A()
429 a.__dict__["foo"] = "baz"
429 a.__dict__["foo"] = "baz"
430 self.assertEqual(a.foo, "bar")
430 self.assertEqual(a.foo, "bar")
431 found = ip._ofind("a.foo", [("locals", locals())])
431 found = ip._ofind("a.foo", [("locals", locals())])
432 self.assertIs(found["obj"], A.foo)
432 self.assertIs(found["obj"], A.foo)
433
433
434 def test_custom_syntaxerror_exception(self):
434 def test_custom_syntaxerror_exception(self):
435 called = []
435 called = []
436 def my_handler(shell, etype, value, tb, tb_offset=None):
436 def my_handler(shell, etype, value, tb, tb_offset=None):
437 called.append(etype)
437 called.append(etype)
438 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
438 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
439
439
440 ip.set_custom_exc((SyntaxError,), my_handler)
440 ip.set_custom_exc((SyntaxError,), my_handler)
441 try:
441 try:
442 ip.run_cell("1f")
442 ip.run_cell("1f")
443 # Check that this was called, and only once.
443 # Check that this was called, and only once.
444 self.assertEqual(called, [SyntaxError])
444 self.assertEqual(called, [SyntaxError])
445 finally:
445 finally:
446 # Reset the custom exception hook
446 # Reset the custom exception hook
447 ip.set_custom_exc((), None)
447 ip.set_custom_exc((), None)
448
448
449 def test_custom_exception(self):
449 def test_custom_exception(self):
450 called = []
450 called = []
451 def my_handler(shell, etype, value, tb, tb_offset=None):
451 def my_handler(shell, etype, value, tb, tb_offset=None):
452 called.append(etype)
452 called.append(etype)
453 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
453 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
454
454
455 ip.set_custom_exc((ValueError,), my_handler)
455 ip.set_custom_exc((ValueError,), my_handler)
456 try:
456 try:
457 res = ip.run_cell("raise ValueError('test')")
457 res = ip.run_cell("raise ValueError('test')")
458 # Check that this was called, and only once.
458 # Check that this was called, and only once.
459 self.assertEqual(called, [ValueError])
459 self.assertEqual(called, [ValueError])
460 # Check that the error is on the result object
460 # Check that the error is on the result object
461 self.assertIsInstance(res.error_in_exec, ValueError)
461 self.assertIsInstance(res.error_in_exec, ValueError)
462 finally:
462 finally:
463 # Reset the custom exception hook
463 # Reset the custom exception hook
464 ip.set_custom_exc((), None)
464 ip.set_custom_exc((), None)
465
465
466 @mock.patch("builtins.print")
466 @mock.patch("builtins.print")
467 def test_showtraceback_with_surrogates(self, mocked_print):
467 def test_showtraceback_with_surrogates(self, mocked_print):
468 values = []
468 values = []
469
469
470 def mock_print_func(value, sep=" ", end="\n", file=sys.stdout, flush=False):
470 def mock_print_func(value, sep=" ", end="\n", file=sys.stdout, flush=False):
471 values.append(value)
471 values.append(value)
472 if value == chr(0xD8FF):
472 if value == chr(0xD8FF):
473 raise UnicodeEncodeError("utf-8", chr(0xD8FF), 0, 1, "")
473 raise UnicodeEncodeError("utf-8", chr(0xD8FF), 0, 1, "")
474
474
475 # mock builtins.print
475 # mock builtins.print
476 mocked_print.side_effect = mock_print_func
476 mocked_print.side_effect = mock_print_func
477
477
478 # ip._showtraceback() is replaced in globalipapp.py.
478 # ip._showtraceback() is replaced in globalipapp.py.
479 # Call original method to test.
479 # Call original method to test.
480 interactiveshell.InteractiveShell._showtraceback(ip, None, None, chr(0xD8FF))
480 interactiveshell.InteractiveShell._showtraceback(ip, None, None, chr(0xD8FF))
481
481
482 self.assertEqual(mocked_print.call_count, 2)
482 self.assertEqual(mocked_print.call_count, 2)
483 self.assertEqual(values, [chr(0xD8FF), "\\ud8ff"])
483 self.assertEqual(values, [chr(0xD8FF), "\\ud8ff"])
484
484
485 def test_mktempfile(self):
485 def test_mktempfile(self):
486 filename = ip.mktempfile()
486 filename = ip.mktempfile()
487 # Check that we can open the file again on Windows
487 # Check that we can open the file again on Windows
488 with open(filename, 'w', encoding='utf-8') as f:
488 with open(filename, "w", encoding="utf-8") as f:
489 f.write('abc')
489 f.write("abc")
490
490
491 filename = ip.mktempfile(data='blah')
491 filename = ip.mktempfile(data="blah")
492 with open(filename, 'r', encoding='utf-8') as f:
492 with open(filename, "r", encoding="utf-8") as f:
493 self.assertEqual(f.read(), 'blah')
493 self.assertEqual(f.read(), "blah")
494
494
495 def test_new_main_mod(self):
495 def test_new_main_mod(self):
496 # Smoketest to check that this accepts a unicode module name
496 # Smoketest to check that this accepts a unicode module name
497 name = u'jiefmw'
497 name = u'jiefmw'
498 mod = ip.new_main_mod(u'%s.py' % name, name)
498 mod = ip.new_main_mod(u'%s.py' % name, name)
499 self.assertEqual(mod.__name__, name)
499 self.assertEqual(mod.__name__, name)
500
500
501 def test_get_exception_only(self):
501 def test_get_exception_only(self):
502 try:
502 try:
503 raise KeyboardInterrupt
503 raise KeyboardInterrupt
504 except KeyboardInterrupt:
504 except KeyboardInterrupt:
505 msg = ip.get_exception_only()
505 msg = ip.get_exception_only()
506 self.assertEqual(msg, 'KeyboardInterrupt\n')
506 self.assertEqual(msg, 'KeyboardInterrupt\n')
507
507
508 try:
508 try:
509 raise DerivedInterrupt("foo")
509 raise DerivedInterrupt("foo")
510 except KeyboardInterrupt:
510 except KeyboardInterrupt:
511 msg = ip.get_exception_only()
511 msg = ip.get_exception_only()
512 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
512 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
513
513
514 def test_inspect_text(self):
514 def test_inspect_text(self):
515 ip.run_cell('a = 5')
515 ip.run_cell('a = 5')
516 text = ip.object_inspect_text('a')
516 text = ip.object_inspect_text('a')
517 self.assertIsInstance(text, str)
517 self.assertIsInstance(text, str)
518
518
519 def test_last_execution_result(self):
519 def test_last_execution_result(self):
520 """ Check that last execution result gets set correctly (GH-10702) """
520 """ Check that last execution result gets set correctly (GH-10702) """
521 result = ip.run_cell('a = 5; a')
521 result = ip.run_cell('a = 5; a')
522 self.assertTrue(ip.last_execution_succeeded)
522 self.assertTrue(ip.last_execution_succeeded)
523 self.assertEqual(ip.last_execution_result.result, 5)
523 self.assertEqual(ip.last_execution_result.result, 5)
524
524
525 result = ip.run_cell('a = x_invalid_id_x')
525 result = ip.run_cell('a = x_invalid_id_x')
526 self.assertFalse(ip.last_execution_succeeded)
526 self.assertFalse(ip.last_execution_succeeded)
527 self.assertFalse(ip.last_execution_result.success)
527 self.assertFalse(ip.last_execution_result.success)
528 self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
528 self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
529
529
530 def test_reset_aliasing(self):
530 def test_reset_aliasing(self):
531 """ Check that standard posix aliases work after %reset. """
531 """ Check that standard posix aliases work after %reset. """
532 if os.name != 'posix':
532 if os.name != 'posix':
533 return
533 return
534
534
535 ip.reset()
535 ip.reset()
536 for cmd in ('clear', 'more', 'less', 'man'):
536 for cmd in ('clear', 'more', 'less', 'man'):
537 res = ip.run_cell('%' + cmd)
537 res = ip.run_cell('%' + cmd)
538 self.assertEqual(res.success, True)
538 self.assertEqual(res.success, True)
539
539
540
540
541 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
541 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
542
542
543 @onlyif_unicode_paths
543 @onlyif_unicode_paths
544 def setUp(self):
544 def setUp(self):
545 self.BASETESTDIR = tempfile.mkdtemp()
545 self.BASETESTDIR = tempfile.mkdtemp()
546 self.TESTDIR = join(self.BASETESTDIR, u"åäö")
546 self.TESTDIR = join(self.BASETESTDIR, u"åäö")
547 os.mkdir(self.TESTDIR)
547 os.mkdir(self.TESTDIR)
548 with open(join(self.TESTDIR, u"åäötestscript.py"), "w", encoding='utf-8') as sfile:
548 with open(
549 join(self.TESTDIR, u"åäötestscript.py"), "w", encoding="utf-8"
550 ) as sfile:
549 sfile.write("pass\n")
551 sfile.write("pass\n")
550 self.oldpath = os.getcwd()
552 self.oldpath = os.getcwd()
551 os.chdir(self.TESTDIR)
553 os.chdir(self.TESTDIR)
552 self.fname = u"åäötestscript.py"
554 self.fname = u"åäötestscript.py"
553
555
554 def tearDown(self):
556 def tearDown(self):
555 os.chdir(self.oldpath)
557 os.chdir(self.oldpath)
556 shutil.rmtree(self.BASETESTDIR)
558 shutil.rmtree(self.BASETESTDIR)
557
559
558 @onlyif_unicode_paths
560 @onlyif_unicode_paths
559 def test_1(self):
561 def test_1(self):
560 """Test safe_execfile with non-ascii path
562 """Test safe_execfile with non-ascii path
561 """
563 """
562 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
564 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
563
565
564 class ExitCodeChecks(tt.TempFileMixin):
566 class ExitCodeChecks(tt.TempFileMixin):
565
567
566 def setUp(self):
568 def setUp(self):
567 self.system = ip.system_raw
569 self.system = ip.system_raw
568
570
569 def test_exit_code_ok(self):
571 def test_exit_code_ok(self):
570 self.system('exit 0')
572 self.system('exit 0')
571 self.assertEqual(ip.user_ns['_exit_code'], 0)
573 self.assertEqual(ip.user_ns['_exit_code'], 0)
572
574
573 def test_exit_code_error(self):
575 def test_exit_code_error(self):
574 self.system('exit 1')
576 self.system('exit 1')
575 self.assertEqual(ip.user_ns['_exit_code'], 1)
577 self.assertEqual(ip.user_ns['_exit_code'], 1)
576
578
577 @skipif(not hasattr(signal, 'SIGALRM'))
579 @skipif(not hasattr(signal, 'SIGALRM'))
578 def test_exit_code_signal(self):
580 def test_exit_code_signal(self):
579 self.mktmp("import signal, time\n"
581 self.mktmp("import signal, time\n"
580 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
582 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
581 "time.sleep(1)\n")
583 "time.sleep(1)\n")
582 self.system("%s %s" % (sys.executable, self.fname))
584 self.system("%s %s" % (sys.executable, self.fname))
583 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
585 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
584
586
585 @onlyif_cmds_exist("csh")
587 @onlyif_cmds_exist("csh")
586 def test_exit_code_signal_csh(self):
588 def test_exit_code_signal_csh(self):
587 SHELL = os.environ.get('SHELL', None)
589 SHELL = os.environ.get('SHELL', None)
588 os.environ['SHELL'] = find_cmd("csh")
590 os.environ['SHELL'] = find_cmd("csh")
589 try:
591 try:
590 self.test_exit_code_signal()
592 self.test_exit_code_signal()
591 finally:
593 finally:
592 if SHELL is not None:
594 if SHELL is not None:
593 os.environ['SHELL'] = SHELL
595 os.environ['SHELL'] = SHELL
594 else:
596 else:
595 del os.environ['SHELL']
597 del os.environ['SHELL']
596
598
597
599
598 class TestSystemRaw(ExitCodeChecks):
600 class TestSystemRaw(ExitCodeChecks):
599
601
600 def setUp(self):
602 def setUp(self):
601 super().setUp()
603 super().setUp()
602 self.system = ip.system_raw
604 self.system = ip.system_raw
603
605
604 @onlyif_unicode_paths
606 @onlyif_unicode_paths
605 def test_1(self):
607 def test_1(self):
606 """Test system_raw with non-ascii cmd
608 """Test system_raw with non-ascii cmd
607 """
609 """
608 cmd = u'''python -c "'åäö'" '''
610 cmd = u'''python -c "'åäö'" '''
609 ip.system_raw(cmd)
611 ip.system_raw(cmd)
610
612
611 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
613 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
612 @mock.patch('os.system', side_effect=KeyboardInterrupt)
614 @mock.patch('os.system', side_effect=KeyboardInterrupt)
613 def test_control_c(self, *mocks):
615 def test_control_c(self, *mocks):
614 try:
616 try:
615 self.system("sleep 1 # wont happen")
617 self.system("sleep 1 # wont happen")
616 except KeyboardInterrupt:
618 except KeyboardInterrupt:
617 self.fail(
619 self.fail(
618 "system call should intercept "
620 "system call should intercept "
619 "keyboard interrupt from subprocess.call"
621 "keyboard interrupt from subprocess.call"
620 )
622 )
621 self.assertEqual(ip.user_ns["_exit_code"], -signal.SIGINT)
623 self.assertEqual(ip.user_ns["_exit_code"], -signal.SIGINT)
622
624
623 def test_magic_warnings(self):
625 def test_magic_warnings(self):
624 for magic_cmd in ("ls", "pip", "conda", "cd"):
626 for magic_cmd in ("ls", "pip", "conda", "cd"):
625 with self.assertWarnsRegex(Warning, "You executed the system command"):
627 with self.assertWarnsRegex(Warning, "You executed the system command"):
626 ip.system_raw(magic_cmd)
628 ip.system_raw(magic_cmd)
627
629
628 # TODO: Exit codes are currently ignored on Windows.
630 # TODO: Exit codes are currently ignored on Windows.
629 class TestSystemPipedExitCode(ExitCodeChecks):
631 class TestSystemPipedExitCode(ExitCodeChecks):
630
632
631 def setUp(self):
633 def setUp(self):
632 super().setUp()
634 super().setUp()
633 self.system = ip.system_piped
635 self.system = ip.system_piped
634
636
635 @skip_win32
637 @skip_win32
636 def test_exit_code_ok(self):
638 def test_exit_code_ok(self):
637 ExitCodeChecks.test_exit_code_ok(self)
639 ExitCodeChecks.test_exit_code_ok(self)
638
640
639 @skip_win32
641 @skip_win32
640 def test_exit_code_error(self):
642 def test_exit_code_error(self):
641 ExitCodeChecks.test_exit_code_error(self)
643 ExitCodeChecks.test_exit_code_error(self)
642
644
643 @skip_win32
645 @skip_win32
644 def test_exit_code_signal(self):
646 def test_exit_code_signal(self):
645 ExitCodeChecks.test_exit_code_signal(self)
647 ExitCodeChecks.test_exit_code_signal(self)
646
648
647 class TestModules(tt.TempFileMixin):
649 class TestModules(tt.TempFileMixin):
648 def test_extraneous_loads(self):
650 def test_extraneous_loads(self):
649 """Test we're not loading modules on startup that we shouldn't.
651 """Test we're not loading modules on startup that we shouldn't.
650 """
652 """
651 self.mktmp("import sys\n"
653 self.mktmp("import sys\n"
652 "print('numpy' in sys.modules)\n"
654 "print('numpy' in sys.modules)\n"
653 "print('ipyparallel' in sys.modules)\n"
655 "print('ipyparallel' in sys.modules)\n"
654 "print('ipykernel' in sys.modules)\n"
656 "print('ipykernel' in sys.modules)\n"
655 )
657 )
656 out = "False\nFalse\nFalse\n"
658 out = "False\nFalse\nFalse\n"
657 tt.ipexec_validate(self.fname, out)
659 tt.ipexec_validate(self.fname, out)
658
660
659 class Negator(ast.NodeTransformer):
661 class Negator(ast.NodeTransformer):
660 """Negates all number literals in an AST."""
662 """Negates all number literals in an AST."""
661
663
662 # for python 3.7 and earlier
664 # for python 3.7 and earlier
663 def visit_Num(self, node):
665 def visit_Num(self, node):
664 node.n = -node.n
666 node.n = -node.n
665 return node
667 return node
666
668
667 # for python 3.8+
669 # for python 3.8+
668 def visit_Constant(self, node):
670 def visit_Constant(self, node):
669 if isinstance(node.value, int):
671 if isinstance(node.value, int):
670 return self.visit_Num(node)
672 return self.visit_Num(node)
671 return node
673 return node
672
674
673 class TestAstTransform(unittest.TestCase):
675 class TestAstTransform(unittest.TestCase):
674 def setUp(self):
676 def setUp(self):
675 self.negator = Negator()
677 self.negator = Negator()
676 ip.ast_transformers.append(self.negator)
678 ip.ast_transformers.append(self.negator)
677
679
678 def tearDown(self):
680 def tearDown(self):
679 ip.ast_transformers.remove(self.negator)
681 ip.ast_transformers.remove(self.negator)
680
682
681 def test_run_cell(self):
683 def test_run_cell(self):
682 with tt.AssertPrints('-34'):
684 with tt.AssertPrints('-34'):
683 ip.run_cell('print (12 + 22)')
685 ip.run_cell('print (12 + 22)')
684
686
685 # A named reference to a number shouldn't be transformed.
687 # A named reference to a number shouldn't be transformed.
686 ip.user_ns['n'] = 55
688 ip.user_ns['n'] = 55
687 with tt.AssertNotPrints('-55'):
689 with tt.AssertNotPrints('-55'):
688 ip.run_cell('print (n)')
690 ip.run_cell('print (n)')
689
691
690 def test_timeit(self):
692 def test_timeit(self):
691 called = set()
693 called = set()
692 def f(x):
694 def f(x):
693 called.add(x)
695 called.add(x)
694 ip.push({'f':f})
696 ip.push({'f':f})
695
697
696 with tt.AssertPrints("std. dev. of"):
698 with tt.AssertPrints("std. dev. of"):
697 ip.run_line_magic("timeit", "-n1 f(1)")
699 ip.run_line_magic("timeit", "-n1 f(1)")
698 self.assertEqual(called, {-1})
700 self.assertEqual(called, {-1})
699 called.clear()
701 called.clear()
700
702
701 with tt.AssertPrints("std. dev. of"):
703 with tt.AssertPrints("std. dev. of"):
702 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
704 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
703 self.assertEqual(called, {-2, -3})
705 self.assertEqual(called, {-2, -3})
704
706
705 def test_time(self):
707 def test_time(self):
706 called = []
708 called = []
707 def f(x):
709 def f(x):
708 called.append(x)
710 called.append(x)
709 ip.push({'f':f})
711 ip.push({'f':f})
710
712
711 # Test with an expression
713 # Test with an expression
712 with tt.AssertPrints("Wall time: "):
714 with tt.AssertPrints("Wall time: "):
713 ip.run_line_magic("time", "f(5+9)")
715 ip.run_line_magic("time", "f(5+9)")
714 self.assertEqual(called, [-14])
716 self.assertEqual(called, [-14])
715 called[:] = []
717 called[:] = []
716
718
717 # Test with a statement (different code path)
719 # Test with a statement (different code path)
718 with tt.AssertPrints("Wall time: "):
720 with tt.AssertPrints("Wall time: "):
719 ip.run_line_magic("time", "a = f(-3 + -2)")
721 ip.run_line_magic("time", "a = f(-3 + -2)")
720 self.assertEqual(called, [5])
722 self.assertEqual(called, [5])
721
723
722 def test_macro(self):
724 def test_macro(self):
723 ip.push({'a':10})
725 ip.push({'a':10})
724 # The AST transformation makes this do a+=-1
726 # The AST transformation makes this do a+=-1
725 ip.define_macro("amacro", "a+=1\nprint(a)")
727 ip.define_macro("amacro", "a+=1\nprint(a)")
726
728
727 with tt.AssertPrints("9"):
729 with tt.AssertPrints("9"):
728 ip.run_cell("amacro")
730 ip.run_cell("amacro")
729 with tt.AssertPrints("8"):
731 with tt.AssertPrints("8"):
730 ip.run_cell("amacro")
732 ip.run_cell("amacro")
731
733
732 class TestMiscTransform(unittest.TestCase):
734 class TestMiscTransform(unittest.TestCase):
733
735
734
736
735 def test_transform_only_once(self):
737 def test_transform_only_once(self):
736 cleanup = 0
738 cleanup = 0
737 line_t = 0
739 line_t = 0
738 def count_cleanup(lines):
740 def count_cleanup(lines):
739 nonlocal cleanup
741 nonlocal cleanup
740 cleanup += 1
742 cleanup += 1
741 return lines
743 return lines
742
744
743 def count_line_t(lines):
745 def count_line_t(lines):
744 nonlocal line_t
746 nonlocal line_t
745 line_t += 1
747 line_t += 1
746 return lines
748 return lines
747
749
748 ip.input_transformer_manager.cleanup_transforms.append(count_cleanup)
750 ip.input_transformer_manager.cleanup_transforms.append(count_cleanup)
749 ip.input_transformer_manager.line_transforms.append(count_line_t)
751 ip.input_transformer_manager.line_transforms.append(count_line_t)
750
752
751 ip.run_cell('1')
753 ip.run_cell('1')
752
754
753 assert cleanup == 1
755 assert cleanup == 1
754 assert line_t == 1
756 assert line_t == 1
755
757
756 class IntegerWrapper(ast.NodeTransformer):
758 class IntegerWrapper(ast.NodeTransformer):
757 """Wraps all integers in a call to Integer()"""
759 """Wraps all integers in a call to Integer()"""
758
760
759 # for Python 3.7 and earlier
761 # for Python 3.7 and earlier
760
762
761 # for Python 3.7 and earlier
763 # for Python 3.7 and earlier
762 def visit_Num(self, node):
764 def visit_Num(self, node):
763 if isinstance(node.n, int):
765 if isinstance(node.n, int):
764 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
766 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
765 args=[node], keywords=[])
767 args=[node], keywords=[])
766 return node
768 return node
767
769
768 # For Python 3.8+
770 # For Python 3.8+
769 def visit_Constant(self, node):
771 def visit_Constant(self, node):
770 if isinstance(node.value, int):
772 if isinstance(node.value, int):
771 return self.visit_Num(node)
773 return self.visit_Num(node)
772 return node
774 return node
773
775
774
776
775 class TestAstTransform2(unittest.TestCase):
777 class TestAstTransform2(unittest.TestCase):
776 def setUp(self):
778 def setUp(self):
777 self.intwrapper = IntegerWrapper()
779 self.intwrapper = IntegerWrapper()
778 ip.ast_transformers.append(self.intwrapper)
780 ip.ast_transformers.append(self.intwrapper)
779
781
780 self.calls = []
782 self.calls = []
781 def Integer(*args):
783 def Integer(*args):
782 self.calls.append(args)
784 self.calls.append(args)
783 return args
785 return args
784 ip.push({"Integer": Integer})
786 ip.push({"Integer": Integer})
785
787
786 def tearDown(self):
788 def tearDown(self):
787 ip.ast_transformers.remove(self.intwrapper)
789 ip.ast_transformers.remove(self.intwrapper)
788 del ip.user_ns['Integer']
790 del ip.user_ns['Integer']
789
791
790 def test_run_cell(self):
792 def test_run_cell(self):
791 ip.run_cell("n = 2")
793 ip.run_cell("n = 2")
792 self.assertEqual(self.calls, [(2,)])
794 self.assertEqual(self.calls, [(2,)])
793
795
794 # This shouldn't throw an error
796 # This shouldn't throw an error
795 ip.run_cell("o = 2.0")
797 ip.run_cell("o = 2.0")
796 self.assertEqual(ip.user_ns['o'], 2.0)
798 self.assertEqual(ip.user_ns['o'], 2.0)
797
799
798 def test_timeit(self):
800 def test_timeit(self):
799 called = set()
801 called = set()
800 def f(x):
802 def f(x):
801 called.add(x)
803 called.add(x)
802 ip.push({'f':f})
804 ip.push({'f':f})
803
805
804 with tt.AssertPrints("std. dev. of"):
806 with tt.AssertPrints("std. dev. of"):
805 ip.run_line_magic("timeit", "-n1 f(1)")
807 ip.run_line_magic("timeit", "-n1 f(1)")
806 self.assertEqual(called, {(1,)})
808 self.assertEqual(called, {(1,)})
807 called.clear()
809 called.clear()
808
810
809 with tt.AssertPrints("std. dev. of"):
811 with tt.AssertPrints("std. dev. of"):
810 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
812 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
811 self.assertEqual(called, {(2,), (3,)})
813 self.assertEqual(called, {(2,), (3,)})
812
814
813 class ErrorTransformer(ast.NodeTransformer):
815 class ErrorTransformer(ast.NodeTransformer):
814 """Throws an error when it sees a number."""
816 """Throws an error when it sees a number."""
815
817
816 # for Python 3.7 and earlier
818 # for Python 3.7 and earlier
817 def visit_Num(self, node):
819 def visit_Num(self, node):
818 raise ValueError("test")
820 raise ValueError("test")
819
821
820 # for Python 3.8+
822 # for Python 3.8+
821 def visit_Constant(self, node):
823 def visit_Constant(self, node):
822 if isinstance(node.value, int):
824 if isinstance(node.value, int):
823 return self.visit_Num(node)
825 return self.visit_Num(node)
824 return node
826 return node
825
827
826
828
827 class TestAstTransformError(unittest.TestCase):
829 class TestAstTransformError(unittest.TestCase):
828 def test_unregistering(self):
830 def test_unregistering(self):
829 err_transformer = ErrorTransformer()
831 err_transformer = ErrorTransformer()
830 ip.ast_transformers.append(err_transformer)
832 ip.ast_transformers.append(err_transformer)
831
833
832 with self.assertWarnsRegex(UserWarning, "It will be unregistered"):
834 with self.assertWarnsRegex(UserWarning, "It will be unregistered"):
833 ip.run_cell("1 + 2")
835 ip.run_cell("1 + 2")
834
836
835 # This should have been removed.
837 # This should have been removed.
836 self.assertNotIn(err_transformer, ip.ast_transformers)
838 self.assertNotIn(err_transformer, ip.ast_transformers)
837
839
838
840
839 class StringRejector(ast.NodeTransformer):
841 class StringRejector(ast.NodeTransformer):
840 """Throws an InputRejected when it sees a string literal.
842 """Throws an InputRejected when it sees a string literal.
841
843
842 Used to verify that NodeTransformers can signal that a piece of code should
844 Used to verify that NodeTransformers can signal that a piece of code should
843 not be executed by throwing an InputRejected.
845 not be executed by throwing an InputRejected.
844 """
846 """
845
847
846 #for python 3.7 and earlier
848 #for python 3.7 and earlier
847 def visit_Str(self, node):
849 def visit_Str(self, node):
848 raise InputRejected("test")
850 raise InputRejected("test")
849
851
850 # 3.8 only
852 # 3.8 only
851 def visit_Constant(self, node):
853 def visit_Constant(self, node):
852 if isinstance(node.value, str):
854 if isinstance(node.value, str):
853 raise InputRejected("test")
855 raise InputRejected("test")
854 return node
856 return node
855
857
856
858
857 class TestAstTransformInputRejection(unittest.TestCase):
859 class TestAstTransformInputRejection(unittest.TestCase):
858
860
859 def setUp(self):
861 def setUp(self):
860 self.transformer = StringRejector()
862 self.transformer = StringRejector()
861 ip.ast_transformers.append(self.transformer)
863 ip.ast_transformers.append(self.transformer)
862
864
863 def tearDown(self):
865 def tearDown(self):
864 ip.ast_transformers.remove(self.transformer)
866 ip.ast_transformers.remove(self.transformer)
865
867
866 def test_input_rejection(self):
868 def test_input_rejection(self):
867 """Check that NodeTransformers can reject input."""
869 """Check that NodeTransformers can reject input."""
868
870
869 expect_exception_tb = tt.AssertPrints("InputRejected: test")
871 expect_exception_tb = tt.AssertPrints("InputRejected: test")
870 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
872 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
871
873
872 # Run the same check twice to verify that the transformer is not
874 # Run the same check twice to verify that the transformer is not
873 # disabled after raising.
875 # disabled after raising.
874 with expect_exception_tb, expect_no_cell_output:
876 with expect_exception_tb, expect_no_cell_output:
875 ip.run_cell("'unsafe'")
877 ip.run_cell("'unsafe'")
876
878
877 with expect_exception_tb, expect_no_cell_output:
879 with expect_exception_tb, expect_no_cell_output:
878 res = ip.run_cell("'unsafe'")
880 res = ip.run_cell("'unsafe'")
879
881
880 self.assertIsInstance(res.error_before_exec, InputRejected)
882 self.assertIsInstance(res.error_before_exec, InputRejected)
881
883
882 def test__IPYTHON__():
884 def test__IPYTHON__():
883 # This shouldn't raise a NameError, that's all
885 # This shouldn't raise a NameError, that's all
884 __IPYTHON__
886 __IPYTHON__
885
887
886
888
887 class DummyRepr(object):
889 class DummyRepr(object):
888 def __repr__(self):
890 def __repr__(self):
889 return "DummyRepr"
891 return "DummyRepr"
890
892
891 def _repr_html_(self):
893 def _repr_html_(self):
892 return "<b>dummy</b>"
894 return "<b>dummy</b>"
893
895
894 def _repr_javascript_(self):
896 def _repr_javascript_(self):
895 return "console.log('hi');", {'key': 'value'}
897 return "console.log('hi');", {'key': 'value'}
896
898
897
899
898 def test_user_variables():
900 def test_user_variables():
899 # enable all formatters
901 # enable all formatters
900 ip.display_formatter.active_types = ip.display_formatter.format_types
902 ip.display_formatter.active_types = ip.display_formatter.format_types
901
903
902 ip.user_ns['dummy'] = d = DummyRepr()
904 ip.user_ns['dummy'] = d = DummyRepr()
903 keys = {'dummy', 'doesnotexist'}
905 keys = {'dummy', 'doesnotexist'}
904 r = ip.user_expressions({ key:key for key in keys})
906 r = ip.user_expressions({ key:key for key in keys})
905
907
906 assert keys == set(r.keys())
908 assert keys == set(r.keys())
907 dummy = r["dummy"]
909 dummy = r["dummy"]
908 assert {"status", "data", "metadata"} == set(dummy.keys())
910 assert {"status", "data", "metadata"} == set(dummy.keys())
909 assert dummy["status"] == "ok"
911 assert dummy["status"] == "ok"
910 data = dummy["data"]
912 data = dummy["data"]
911 metadata = dummy["metadata"]
913 metadata = dummy["metadata"]
912 assert data.get("text/html") == d._repr_html_()
914 assert data.get("text/html") == d._repr_html_()
913 js, jsmd = d._repr_javascript_()
915 js, jsmd = d._repr_javascript_()
914 assert data.get("application/javascript") == js
916 assert data.get("application/javascript") == js
915 assert metadata.get("application/javascript") == jsmd
917 assert metadata.get("application/javascript") == jsmd
916
918
917 dne = r["doesnotexist"]
919 dne = r["doesnotexist"]
918 assert dne["status"] == "error"
920 assert dne["status"] == "error"
919 assert dne["ename"] == "NameError"
921 assert dne["ename"] == "NameError"
920
922
921 # back to text only
923 # back to text only
922 ip.display_formatter.active_types = ['text/plain']
924 ip.display_formatter.active_types = ['text/plain']
923
925
924 def test_user_expression():
926 def test_user_expression():
925 # enable all formatters
927 # enable all formatters
926 ip.display_formatter.active_types = ip.display_formatter.format_types
928 ip.display_formatter.active_types = ip.display_formatter.format_types
927 query = {
929 query = {
928 'a' : '1 + 2',
930 'a' : '1 + 2',
929 'b' : '1/0',
931 'b' : '1/0',
930 }
932 }
931 r = ip.user_expressions(query)
933 r = ip.user_expressions(query)
932 import pprint
934 import pprint
933 pprint.pprint(r)
935 pprint.pprint(r)
934 assert set(r.keys()) == set(query.keys())
936 assert set(r.keys()) == set(query.keys())
935 a = r["a"]
937 a = r["a"]
936 assert {"status", "data", "metadata"} == set(a.keys())
938 assert {"status", "data", "metadata"} == set(a.keys())
937 assert a["status"] == "ok"
939 assert a["status"] == "ok"
938 data = a["data"]
940 data = a["data"]
939 metadata = a["metadata"]
941 metadata = a["metadata"]
940 assert data.get("text/plain") == "3"
942 assert data.get("text/plain") == "3"
941
943
942 b = r["b"]
944 b = r["b"]
943 assert b["status"] == "error"
945 assert b["status"] == "error"
944 assert b["ename"] == "ZeroDivisionError"
946 assert b["ename"] == "ZeroDivisionError"
945
947
946 # back to text only
948 # back to text only
947 ip.display_formatter.active_types = ['text/plain']
949 ip.display_formatter.active_types = ['text/plain']
948
950
949
951
950 class TestSyntaxErrorTransformer(unittest.TestCase):
952 class TestSyntaxErrorTransformer(unittest.TestCase):
951 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
953 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
952
954
953 @staticmethod
955 @staticmethod
954 def transformer(lines):
956 def transformer(lines):
955 for line in lines:
957 for line in lines:
956 pos = line.find('syntaxerror')
958 pos = line.find('syntaxerror')
957 if pos >= 0:
959 if pos >= 0:
958 e = SyntaxError('input contains "syntaxerror"')
960 e = SyntaxError('input contains "syntaxerror"')
959 e.text = line
961 e.text = line
960 e.offset = pos + 1
962 e.offset = pos + 1
961 raise e
963 raise e
962 return lines
964 return lines
963
965
964 def setUp(self):
966 def setUp(self):
965 ip.input_transformers_post.append(self.transformer)
967 ip.input_transformers_post.append(self.transformer)
966
968
967 def tearDown(self):
969 def tearDown(self):
968 ip.input_transformers_post.remove(self.transformer)
970 ip.input_transformers_post.remove(self.transformer)
969
971
970 def test_syntaxerror_input_transformer(self):
972 def test_syntaxerror_input_transformer(self):
971 with tt.AssertPrints('1234'):
973 with tt.AssertPrints('1234'):
972 ip.run_cell('1234')
974 ip.run_cell('1234')
973 with tt.AssertPrints('SyntaxError: invalid syntax'):
975 with tt.AssertPrints('SyntaxError: invalid syntax'):
974 ip.run_cell('1 2 3') # plain python syntax error
976 ip.run_cell('1 2 3') # plain python syntax error
975 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
977 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
976 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
978 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
977 with tt.AssertPrints('3456'):
979 with tt.AssertPrints('3456'):
978 ip.run_cell('3456')
980 ip.run_cell('3456')
979
981
980
982
981 class TestWarningSuppression(unittest.TestCase):
983 class TestWarningSuppression(unittest.TestCase):
982 def test_warning_suppression(self):
984 def test_warning_suppression(self):
983 ip.run_cell("import warnings")
985 ip.run_cell("import warnings")
984 try:
986 try:
985 with self.assertWarnsRegex(UserWarning, "asdf"):
987 with self.assertWarnsRegex(UserWarning, "asdf"):
986 ip.run_cell("warnings.warn('asdf')")
988 ip.run_cell("warnings.warn('asdf')")
987 # Here's the real test -- if we run that again, we should get the
989 # Here's the real test -- if we run that again, we should get the
988 # warning again. Traditionally, each warning was only issued once per
990 # warning again. Traditionally, each warning was only issued once per
989 # IPython session (approximately), even if the user typed in new and
991 # IPython session (approximately), even if the user typed in new and
990 # different code that should have also triggered the warning, leading
992 # different code that should have also triggered the warning, leading
991 # to much confusion.
993 # to much confusion.
992 with self.assertWarnsRegex(UserWarning, "asdf"):
994 with self.assertWarnsRegex(UserWarning, "asdf"):
993 ip.run_cell("warnings.warn('asdf')")
995 ip.run_cell("warnings.warn('asdf')")
994 finally:
996 finally:
995 ip.run_cell("del warnings")
997 ip.run_cell("del warnings")
996
998
997
999
998 def test_deprecation_warning(self):
1000 def test_deprecation_warning(self):
999 ip.run_cell("""
1001 ip.run_cell("""
1000 import warnings
1002 import warnings
1001 def wrn():
1003 def wrn():
1002 warnings.warn(
1004 warnings.warn(
1003 "I AM A WARNING",
1005 "I AM A WARNING",
1004 DeprecationWarning
1006 DeprecationWarning
1005 )
1007 )
1006 """)
1008 """)
1007 try:
1009 try:
1008 with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"):
1010 with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"):
1009 ip.run_cell("wrn()")
1011 ip.run_cell("wrn()")
1010 finally:
1012 finally:
1011 ip.run_cell("del warnings")
1013 ip.run_cell("del warnings")
1012 ip.run_cell("del wrn")
1014 ip.run_cell("del wrn")
1013
1015
1014
1016
1015 class TestImportNoDeprecate(tt.TempFileMixin):
1017 class TestImportNoDeprecate(tt.TempFileMixin):
1016
1018
1017 def setUp(self):
1019 def setUp(self):
1018 """Make a valid python temp file."""
1020 """Make a valid python temp file."""
1019 self.mktmp("""
1021 self.mktmp("""
1020 import warnings
1022 import warnings
1021 def wrn():
1023 def wrn():
1022 warnings.warn(
1024 warnings.warn(
1023 "I AM A WARNING",
1025 "I AM A WARNING",
1024 DeprecationWarning
1026 DeprecationWarning
1025 )
1027 )
1026 """)
1028 """)
1027 super().setUp()
1029 super().setUp()
1028
1030
1029 def test_no_dep(self):
1031 def test_no_dep(self):
1030 """
1032 """
1031 No deprecation warning should be raised from imported functions
1033 No deprecation warning should be raised from imported functions
1032 """
1034 """
1033 ip.run_cell("from {} import wrn".format(self.fname))
1035 ip.run_cell("from {} import wrn".format(self.fname))
1034
1036
1035 with tt.AssertNotPrints("I AM A WARNING"):
1037 with tt.AssertNotPrints("I AM A WARNING"):
1036 ip.run_cell("wrn()")
1038 ip.run_cell("wrn()")
1037 ip.run_cell("del wrn")
1039 ip.run_cell("del wrn")
1038
1040
1039
1041
1040 def test_custom_exc_count():
1042 def test_custom_exc_count():
1041 hook = mock.Mock(return_value=None)
1043 hook = mock.Mock(return_value=None)
1042 ip.set_custom_exc((SyntaxError,), hook)
1044 ip.set_custom_exc((SyntaxError,), hook)
1043 before = ip.execution_count
1045 before = ip.execution_count
1044 ip.run_cell("def foo()", store_history=True)
1046 ip.run_cell("def foo()", store_history=True)
1045 # restore default excepthook
1047 # restore default excepthook
1046 ip.set_custom_exc((), None)
1048 ip.set_custom_exc((), None)
1047 assert hook.call_count == 1
1049 assert hook.call_count == 1
1048 assert ip.execution_count == before + 1
1050 assert ip.execution_count == before + 1
1049
1051
1050
1052
1051 def test_run_cell_async():
1053 def test_run_cell_async():
1052 ip.run_cell("import asyncio")
1054 ip.run_cell("import asyncio")
1053 coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5")
1055 coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5")
1054 assert asyncio.iscoroutine(coro)
1056 assert asyncio.iscoroutine(coro)
1055 loop = asyncio.new_event_loop()
1057 loop = asyncio.new_event_loop()
1056 result = loop.run_until_complete(coro)
1058 result = loop.run_until_complete(coro)
1057 assert isinstance(result, interactiveshell.ExecutionResult)
1059 assert isinstance(result, interactiveshell.ExecutionResult)
1058 assert result.result == 5
1060 assert result.result == 5
1059
1061
1060
1062
1061 def test_run_cell_await():
1063 def test_run_cell_await():
1062 ip.run_cell("import asyncio")
1064 ip.run_cell("import asyncio")
1063 result = ip.run_cell("await asyncio.sleep(0.01); 10")
1065 result = ip.run_cell("await asyncio.sleep(0.01); 10")
1064 assert ip.user_ns["_"] == 10
1066 assert ip.user_ns["_"] == 10
1065
1067
1066
1068
1067 def test_run_cell_asyncio_run():
1069 def test_run_cell_asyncio_run():
1068 ip.run_cell("import asyncio")
1070 ip.run_cell("import asyncio")
1069 result = ip.run_cell("await asyncio.sleep(0.01); 1")
1071 result = ip.run_cell("await asyncio.sleep(0.01); 1")
1070 assert ip.user_ns["_"] == 1
1072 assert ip.user_ns["_"] == 1
1071 result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2")
1073 result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2")
1072 assert ip.user_ns["_"] == 2
1074 assert ip.user_ns["_"] == 2
1073 result = ip.run_cell("await asyncio.sleep(0.01); 3")
1075 result = ip.run_cell("await asyncio.sleep(0.01); 3")
1074 assert ip.user_ns["_"] == 3
1076 assert ip.user_ns["_"] == 3
1075
1077
1076
1078
1077 def test_should_run_async():
1079 def test_should_run_async():
1078 assert not ip.should_run_async("a = 5")
1080 assert not ip.should_run_async("a = 5")
1079 assert ip.should_run_async("await x")
1081 assert ip.should_run_async("await x")
1080 assert ip.should_run_async("import asyncio; await asyncio.sleep(1)")
1082 assert ip.should_run_async("import asyncio; await asyncio.sleep(1)")
1081
1083
1082
1084
1083 def test_set_custom_completer():
1085 def test_set_custom_completer():
1084 num_completers = len(ip.Completer.matchers)
1086 num_completers = len(ip.Completer.matchers)
1085
1087
1086 def foo(*args, **kwargs):
1088 def foo(*args, **kwargs):
1087 return "I'm a completer!"
1089 return "I'm a completer!"
1088
1090
1089 ip.set_custom_completer(foo, 0)
1091 ip.set_custom_completer(foo, 0)
1090
1092
1091 # check that we've really added a new completer
1093 # check that we've really added a new completer
1092 assert len(ip.Completer.matchers) == num_completers + 1
1094 assert len(ip.Completer.matchers) == num_completers + 1
1093
1095
1094 # check that the first completer is the function we defined
1096 # check that the first completer is the function we defined
1095 assert ip.Completer.matchers[0]() == "I'm a completer!"
1097 assert ip.Completer.matchers[0]() == "I'm a completer!"
1096
1098
1097 # clean up
1099 # clean up
1098 ip.Completer.custom_matchers.pop()
1100 ip.Completer.custom_matchers.pop()
@@ -1,1366 +1,1408 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for various magic functions."""
2 """Tests for various magic functions."""
3
3
4 import asyncio
4 import asyncio
5 import gc
5 import gc
6 import io
6 import io
7 import os
7 import os
8 import re
8 import re
9 import shlex
9 import shlex
10 import sys
10 import sys
11 import warnings
11 import warnings
12 from importlib import invalidate_caches
12 from importlib import invalidate_caches
13 from io import StringIO
13 from io import StringIO
14 from pathlib import Path
14 from pathlib import Path
15 from textwrap import dedent
15 from textwrap import dedent
16 from unittest import TestCase, mock
16 from unittest import TestCase, mock
17
17
18 import pytest
18 import pytest
19
19
20 from IPython import get_ipython
20 from IPython import get_ipython
21 from IPython.core import magic
21 from IPython.core import magic
22 from IPython.core.error import UsageError
22 from IPython.core.error import UsageError
23 from IPython.core.magic import (
23 from IPython.core.magic import (
24 Magics,
24 Magics,
25 cell_magic,
25 cell_magic,
26 line_magic,
26 line_magic,
27 magics_class,
27 magics_class,
28 register_cell_magic,
28 register_cell_magic,
29 register_line_magic,
29 register_line_magic,
30 )
30 )
31 from IPython.core.magics import code, execution, logging, osm, script
31 from IPython.core.magics import code, execution, logging, osm, script
32 from IPython.testing import decorators as dec
32 from IPython.testing import decorators as dec
33 from IPython.testing import tools as tt
33 from IPython.testing import tools as tt
34 from IPython.utils.io import capture_output
34 from IPython.utils.io import capture_output
35 from IPython.utils.process import find_cmd
35 from IPython.utils.process import find_cmd
36 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
36 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
37
37
38 from .test_debugger import PdbTestInput
38 from .test_debugger import PdbTestInput
39
39
40
40
41 @magic.magics_class
41 @magic.magics_class
42 class DummyMagics(magic.Magics): pass
42 class DummyMagics(magic.Magics): pass
43
43
44 def test_extract_code_ranges():
44 def test_extract_code_ranges():
45 instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :"
45 instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :"
46 expected = [
46 expected = [
47 (0, 1),
47 (0, 1),
48 (2, 3),
48 (2, 3),
49 (4, 6),
49 (4, 6),
50 (6, 9),
50 (6, 9),
51 (9, 14),
51 (9, 14),
52 (16, None),
52 (16, None),
53 (None, 9),
53 (None, 9),
54 (9, None),
54 (9, None),
55 (None, 13),
55 (None, 13),
56 (None, None),
56 (None, None),
57 ]
57 ]
58 actual = list(code.extract_code_ranges(instr))
58 actual = list(code.extract_code_ranges(instr))
59 assert actual == expected
59 assert actual == expected
60
60
61 def test_extract_symbols():
61 def test_extract_symbols():
62 source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n"""
62 source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n"""
63 symbols_args = ["a", "b", "A", "A,b", "A,a", "z"]
63 symbols_args = ["a", "b", "A", "A,b", "A,a", "z"]
64 expected = [([], ['a']),
64 expected = [([], ['a']),
65 (["def b():\n return 42\n"], []),
65 (["def b():\n return 42\n"], []),
66 (["class A: pass\n"], []),
66 (["class A: pass\n"], []),
67 (["class A: pass\n", "def b():\n return 42\n"], []),
67 (["class A: pass\n", "def b():\n return 42\n"], []),
68 (["class A: pass\n"], ['a']),
68 (["class A: pass\n"], ['a']),
69 ([], ['z'])]
69 ([], ['z'])]
70 for symbols, exp in zip(symbols_args, expected):
70 for symbols, exp in zip(symbols_args, expected):
71 assert code.extract_symbols(source, symbols) == exp
71 assert code.extract_symbols(source, symbols) == exp
72
72
73
73
74 def test_extract_symbols_raises_exception_with_non_python_code():
74 def test_extract_symbols_raises_exception_with_non_python_code():
75 source = ("=begin A Ruby program :)=end\n"
75 source = ("=begin A Ruby program :)=end\n"
76 "def hello\n"
76 "def hello\n"
77 "puts 'Hello world'\n"
77 "puts 'Hello world'\n"
78 "end")
78 "end")
79 with pytest.raises(SyntaxError):
79 with pytest.raises(SyntaxError):
80 code.extract_symbols(source, "hello")
80 code.extract_symbols(source, "hello")
81
81
82
82
83 def test_magic_not_found():
83 def test_magic_not_found():
84 # magic not found raises UsageError
84 # magic not found raises UsageError
85 with pytest.raises(UsageError):
85 with pytest.raises(UsageError):
86 _ip.magic('doesntexist')
86 _ip.magic('doesntexist')
87
87
88 # ensure result isn't success when a magic isn't found
88 # ensure result isn't success when a magic isn't found
89 result = _ip.run_cell('%doesntexist')
89 result = _ip.run_cell('%doesntexist')
90 assert isinstance(result.error_in_exec, UsageError)
90 assert isinstance(result.error_in_exec, UsageError)
91
91
92
92
93 def test_cell_magic_not_found():
93 def test_cell_magic_not_found():
94 # magic not found raises UsageError
94 # magic not found raises UsageError
95 with pytest.raises(UsageError):
95 with pytest.raises(UsageError):
96 _ip.run_cell_magic('doesntexist', 'line', 'cell')
96 _ip.run_cell_magic('doesntexist', 'line', 'cell')
97
97
98 # ensure result isn't success when a magic isn't found
98 # ensure result isn't success when a magic isn't found
99 result = _ip.run_cell('%%doesntexist')
99 result = _ip.run_cell('%%doesntexist')
100 assert isinstance(result.error_in_exec, UsageError)
100 assert isinstance(result.error_in_exec, UsageError)
101
101
102
102
103 def test_magic_error_status():
103 def test_magic_error_status():
104 def fail(shell):
104 def fail(shell):
105 1/0
105 1/0
106 _ip.register_magic_function(fail)
106 _ip.register_magic_function(fail)
107 result = _ip.run_cell('%fail')
107 result = _ip.run_cell('%fail')
108 assert isinstance(result.error_in_exec, ZeroDivisionError)
108 assert isinstance(result.error_in_exec, ZeroDivisionError)
109
109
110
110
111 def test_config():
111 def test_config():
112 """ test that config magic does not raise
112 """ test that config magic does not raise
113 can happen if Configurable init is moved too early into
113 can happen if Configurable init is moved too early into
114 Magics.__init__ as then a Config object will be registered as a
114 Magics.__init__ as then a Config object will be registered as a
115 magic.
115 magic.
116 """
116 """
117 ## should not raise.
117 ## should not raise.
118 _ip.magic('config')
118 _ip.magic('config')
119
119
120 def test_config_available_configs():
120 def test_config_available_configs():
121 """ test that config magic prints available configs in unique and
121 """ test that config magic prints available configs in unique and
122 sorted order. """
122 sorted order. """
123 with capture_output() as captured:
123 with capture_output() as captured:
124 _ip.magic('config')
124 _ip.magic('config')
125
125
126 stdout = captured.stdout
126 stdout = captured.stdout
127 config_classes = stdout.strip().split('\n')[1:]
127 config_classes = stdout.strip().split('\n')[1:]
128 assert config_classes == sorted(set(config_classes))
128 assert config_classes == sorted(set(config_classes))
129
129
130 def test_config_print_class():
130 def test_config_print_class():
131 """ test that config with a classname prints the class's options. """
131 """ test that config with a classname prints the class's options. """
132 with capture_output() as captured:
132 with capture_output() as captured:
133 _ip.magic('config TerminalInteractiveShell')
133 _ip.magic('config TerminalInteractiveShell')
134
134
135 stdout = captured.stdout
135 stdout = captured.stdout
136 assert re.match(
136 assert re.match(
137 "TerminalInteractiveShell.* options", stdout.splitlines()[0]
137 "TerminalInteractiveShell.* options", stdout.splitlines()[0]
138 ), f"{stdout}\n\n1st line of stdout not like 'TerminalInteractiveShell.* options'"
138 ), f"{stdout}\n\n1st line of stdout not like 'TerminalInteractiveShell.* options'"
139
139
140
140
141 def test_rehashx():
141 def test_rehashx():
142 # clear up everything
142 # clear up everything
143 _ip.alias_manager.clear_aliases()
143 _ip.alias_manager.clear_aliases()
144 del _ip.db['syscmdlist']
144 del _ip.db['syscmdlist']
145
145
146 _ip.magic('rehashx')
146 _ip.magic('rehashx')
147 # Practically ALL ipython development systems will have more than 10 aliases
147 # Practically ALL ipython development systems will have more than 10 aliases
148
148
149 assert len(_ip.alias_manager.aliases) > 10
149 assert len(_ip.alias_manager.aliases) > 10
150 for name, cmd in _ip.alias_manager.aliases:
150 for name, cmd in _ip.alias_manager.aliases:
151 # we must strip dots from alias names
151 # we must strip dots from alias names
152 assert "." not in name
152 assert "." not in name
153
153
154 # rehashx must fill up syscmdlist
154 # rehashx must fill up syscmdlist
155 scoms = _ip.db['syscmdlist']
155 scoms = _ip.db['syscmdlist']
156 assert len(scoms) > 10
156 assert len(scoms) > 10
157
157
158
158
159 def test_magic_parse_options():
159 def test_magic_parse_options():
160 """Test that we don't mangle paths when parsing magic options."""
160 """Test that we don't mangle paths when parsing magic options."""
161 ip = get_ipython()
161 ip = get_ipython()
162 path = 'c:\\x'
162 path = 'c:\\x'
163 m = DummyMagics(ip)
163 m = DummyMagics(ip)
164 opts = m.parse_options('-f %s' % path,'f:')[0]
164 opts = m.parse_options('-f %s' % path,'f:')[0]
165 # argv splitting is os-dependent
165 # argv splitting is os-dependent
166 if os.name == 'posix':
166 if os.name == 'posix':
167 expected = 'c:x'
167 expected = 'c:x'
168 else:
168 else:
169 expected = path
169 expected = path
170 assert opts["f"] == expected
170 assert opts["f"] == expected
171
171
172
172
173 def test_magic_parse_long_options():
173 def test_magic_parse_long_options():
174 """Magic.parse_options can handle --foo=bar long options"""
174 """Magic.parse_options can handle --foo=bar long options"""
175 ip = get_ipython()
175 ip = get_ipython()
176 m = DummyMagics(ip)
176 m = DummyMagics(ip)
177 opts, _ = m.parse_options("--foo --bar=bubble", "a", "foo", "bar=")
177 opts, _ = m.parse_options("--foo --bar=bubble", "a", "foo", "bar=")
178 assert "foo" in opts
178 assert "foo" in opts
179 assert "bar" in opts
179 assert "bar" in opts
180 assert opts["bar"] == "bubble"
180 assert opts["bar"] == "bubble"
181
181
182
182
183 def doctest_hist_f():
183 def doctest_hist_f():
184 """Test %hist -f with temporary filename.
184 """Test %hist -f with temporary filename.
185
185
186 In [9]: import tempfile
186 In [9]: import tempfile
187
187
188 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
188 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
189
189
190 In [11]: %hist -nl -f $tfile 3
190 In [11]: %hist -nl -f $tfile 3
191
191
192 In [13]: import os; os.unlink(tfile)
192 In [13]: import os; os.unlink(tfile)
193 """
193 """
194
194
195
195
196 def doctest_hist_op():
196 def doctest_hist_op():
197 """Test %hist -op
197 """Test %hist -op
198
198
199 In [1]: class b(float):
199 In [1]: class b(float):
200 ...: pass
200 ...: pass
201 ...:
201 ...:
202
202
203 In [2]: class s(object):
203 In [2]: class s(object):
204 ...: def __str__(self):
204 ...: def __str__(self):
205 ...: return 's'
205 ...: return 's'
206 ...:
206 ...:
207
207
208 In [3]:
208 In [3]:
209
209
210 In [4]: class r(b):
210 In [4]: class r(b):
211 ...: def __repr__(self):
211 ...: def __repr__(self):
212 ...: return 'r'
212 ...: return 'r'
213 ...:
213 ...:
214
214
215 In [5]: class sr(s,r): pass
215 In [5]: class sr(s,r): pass
216 ...:
216 ...:
217
217
218 In [6]:
218 In [6]:
219
219
220 In [7]: bb=b()
220 In [7]: bb=b()
221
221
222 In [8]: ss=s()
222 In [8]: ss=s()
223
223
224 In [9]: rr=r()
224 In [9]: rr=r()
225
225
226 In [10]: ssrr=sr()
226 In [10]: ssrr=sr()
227
227
228 In [11]: 4.5
228 In [11]: 4.5
229 Out[11]: 4.5
229 Out[11]: 4.5
230
230
231 In [12]: str(ss)
231 In [12]: str(ss)
232 Out[12]: 's'
232 Out[12]: 's'
233
233
234 In [13]:
234 In [13]:
235
235
236 In [14]: %hist -op
236 In [14]: %hist -op
237 >>> class b:
237 >>> class b:
238 ... pass
238 ... pass
239 ...
239 ...
240 >>> class s(b):
240 >>> class s(b):
241 ... def __str__(self):
241 ... def __str__(self):
242 ... return 's'
242 ... return 's'
243 ...
243 ...
244 >>>
244 >>>
245 >>> class r(b):
245 >>> class r(b):
246 ... def __repr__(self):
246 ... def __repr__(self):
247 ... return 'r'
247 ... return 'r'
248 ...
248 ...
249 >>> class sr(s,r): pass
249 >>> class sr(s,r): pass
250 >>>
250 >>>
251 >>> bb=b()
251 >>> bb=b()
252 >>> ss=s()
252 >>> ss=s()
253 >>> rr=r()
253 >>> rr=r()
254 >>> ssrr=sr()
254 >>> ssrr=sr()
255 >>> 4.5
255 >>> 4.5
256 4.5
256 4.5
257 >>> str(ss)
257 >>> str(ss)
258 's'
258 's'
259 >>>
259 >>>
260 """
260 """
261
261
262 def test_hist_pof():
262 def test_hist_pof():
263 ip = get_ipython()
263 ip = get_ipython()
264 ip.run_cell("1+2", store_history=True)
264 ip.run_cell("1+2", store_history=True)
265 #raise Exception(ip.history_manager.session_number)
265 #raise Exception(ip.history_manager.session_number)
266 #raise Exception(list(ip.history_manager._get_range_session()))
266 #raise Exception(list(ip.history_manager._get_range_session()))
267 with TemporaryDirectory() as td:
267 with TemporaryDirectory() as td:
268 tf = os.path.join(td, 'hist.py')
268 tf = os.path.join(td, 'hist.py')
269 ip.run_line_magic('history', '-pof %s' % tf)
269 ip.run_line_magic('history', '-pof %s' % tf)
270 assert os.path.isfile(tf)
270 assert os.path.isfile(tf)
271
271
272
272
273 def test_macro():
273 def test_macro():
274 ip = get_ipython()
274 ip = get_ipython()
275 ip.history_manager.reset() # Clear any existing history.
275 ip.history_manager.reset() # Clear any existing history.
276 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
276 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
277 for i, cmd in enumerate(cmds, start=1):
277 for i, cmd in enumerate(cmds, start=1):
278 ip.history_manager.store_inputs(i, cmd)
278 ip.history_manager.store_inputs(i, cmd)
279 ip.magic("macro test 1-3")
279 ip.magic("macro test 1-3")
280 assert ip.user_ns["test"].value == "\n".join(cmds) + "\n"
280 assert ip.user_ns["test"].value == "\n".join(cmds) + "\n"
281
281
282 # List macros
282 # List macros
283 assert "test" in ip.magic("macro")
283 assert "test" in ip.magic("macro")
284
284
285
285
286 def test_macro_run():
286 def test_macro_run():
287 """Test that we can run a multi-line macro successfully."""
287 """Test that we can run a multi-line macro successfully."""
288 ip = get_ipython()
288 ip = get_ipython()
289 ip.history_manager.reset()
289 ip.history_manager.reset()
290 cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"]
290 cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"]
291 for cmd in cmds:
291 for cmd in cmds:
292 ip.run_cell(cmd, store_history=True)
292 ip.run_cell(cmd, store_history=True)
293 assert ip.user_ns["test"].value == "a+=1\nprint(a)\n"
293 assert ip.user_ns["test"].value == "a+=1\nprint(a)\n"
294 with tt.AssertPrints("12"):
294 with tt.AssertPrints("12"):
295 ip.run_cell("test")
295 ip.run_cell("test")
296 with tt.AssertPrints("13"):
296 with tt.AssertPrints("13"):
297 ip.run_cell("test")
297 ip.run_cell("test")
298
298
299
299
300 def test_magic_magic():
300 def test_magic_magic():
301 """Test %magic"""
301 """Test %magic"""
302 ip = get_ipython()
302 ip = get_ipython()
303 with capture_output() as captured:
303 with capture_output() as captured:
304 ip.magic("magic")
304 ip.magic("magic")
305
305
306 stdout = captured.stdout
306 stdout = captured.stdout
307 assert "%magic" in stdout
307 assert "%magic" in stdout
308 assert "IPython" in stdout
308 assert "IPython" in stdout
309 assert "Available" in stdout
309 assert "Available" in stdout
310
310
311
311
312 @dec.skipif_not_numpy
312 @dec.skipif_not_numpy
313 def test_numpy_reset_array_undec():
313 def test_numpy_reset_array_undec():
314 "Test '%reset array' functionality"
314 "Test '%reset array' functionality"
315 _ip.ex("import numpy as np")
315 _ip.ex("import numpy as np")
316 _ip.ex("a = np.empty(2)")
316 _ip.ex("a = np.empty(2)")
317 assert "a" in _ip.user_ns
317 assert "a" in _ip.user_ns
318 _ip.magic("reset -f array")
318 _ip.magic("reset -f array")
319 assert "a" not in _ip.user_ns
319 assert "a" not in _ip.user_ns
320
320
321
321
322 def test_reset_out():
322 def test_reset_out():
323 "Test '%reset out' magic"
323 "Test '%reset out' magic"
324 _ip.run_cell("parrot = 'dead'", store_history=True)
324 _ip.run_cell("parrot = 'dead'", store_history=True)
325 # test '%reset -f out', make an Out prompt
325 # test '%reset -f out', make an Out prompt
326 _ip.run_cell("parrot", store_history=True)
326 _ip.run_cell("parrot", store_history=True)
327 assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")]
327 assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")]
328 _ip.magic("reset -f out")
328 _ip.magic("reset -f out")
329 assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")]
329 assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")]
330 assert len(_ip.user_ns["Out"]) == 0
330 assert len(_ip.user_ns["Out"]) == 0
331
331
332
332
333 def test_reset_in():
333 def test_reset_in():
334 "Test '%reset in' magic"
334 "Test '%reset in' magic"
335 # test '%reset -f in'
335 # test '%reset -f in'
336 _ip.run_cell("parrot", store_history=True)
336 _ip.run_cell("parrot", store_history=True)
337 assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
337 assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
338 _ip.magic("%reset -f in")
338 _ip.magic("%reset -f in")
339 assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
339 assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
340 assert len(set(_ip.user_ns["In"])) == 1
340 assert len(set(_ip.user_ns["In"])) == 1
341
341
342
342
343 def test_reset_dhist():
343 def test_reset_dhist():
344 "Test '%reset dhist' magic"
344 "Test '%reset dhist' magic"
345 _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing
345 _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing
346 _ip.magic("cd " + os.path.dirname(pytest.__file__))
346 _ip.magic("cd " + os.path.dirname(pytest.__file__))
347 _ip.magic("cd -")
347 _ip.magic("cd -")
348 assert len(_ip.user_ns["_dh"]) > 0
348 assert len(_ip.user_ns["_dh"]) > 0
349 _ip.magic("reset -f dhist")
349 _ip.magic("reset -f dhist")
350 assert len(_ip.user_ns["_dh"]) == 0
350 assert len(_ip.user_ns["_dh"]) == 0
351 _ip.run_cell("_dh = [d for d in tmp]") # restore
351 _ip.run_cell("_dh = [d for d in tmp]") # restore
352
352
353
353
354 def test_reset_in_length():
354 def test_reset_in_length():
355 "Test that '%reset in' preserves In[] length"
355 "Test that '%reset in' preserves In[] length"
356 _ip.run_cell("print 'foo'")
356 _ip.run_cell("print 'foo'")
357 _ip.run_cell("reset -f in")
357 _ip.run_cell("reset -f in")
358 assert len(_ip.user_ns["In"]) == _ip.displayhook.prompt_count + 1
358 assert len(_ip.user_ns["In"]) == _ip.displayhook.prompt_count + 1
359
359
360
360
361 class TestResetErrors(TestCase):
361 class TestResetErrors(TestCase):
362
362
363 def test_reset_redefine(self):
363 def test_reset_redefine(self):
364
364
365 @magics_class
365 @magics_class
366 class KernelMagics(Magics):
366 class KernelMagics(Magics):
367 @line_magic
367 @line_magic
368 def less(self, shell): pass
368 def less(self, shell): pass
369
369
370 _ip.register_magics(KernelMagics)
370 _ip.register_magics(KernelMagics)
371
371
372 with self.assertLogs() as cm:
372 with self.assertLogs() as cm:
373 # hack, we want to just capture logs, but assertLogs fails if not
373 # hack, we want to just capture logs, but assertLogs fails if not
374 # logs get produce.
374 # logs get produce.
375 # so log one things we ignore.
375 # so log one things we ignore.
376 import logging as log_mod
376 import logging as log_mod
377 log = log_mod.getLogger()
377 log = log_mod.getLogger()
378 log.info('Nothing')
378 log.info('Nothing')
379 # end hack.
379 # end hack.
380 _ip.run_cell("reset -f")
380 _ip.run_cell("reset -f")
381
381
382 assert len(cm.output) == 1
382 assert len(cm.output) == 1
383 for out in cm.output:
383 for out in cm.output:
384 assert "Invalid alias" not in out
384 assert "Invalid alias" not in out
385
385
386 def test_tb_syntaxerror():
386 def test_tb_syntaxerror():
387 """test %tb after a SyntaxError"""
387 """test %tb after a SyntaxError"""
388 ip = get_ipython()
388 ip = get_ipython()
389 ip.run_cell("for")
389 ip.run_cell("for")
390
390
391 # trap and validate stdout
391 # trap and validate stdout
392 save_stdout = sys.stdout
392 save_stdout = sys.stdout
393 try:
393 try:
394 sys.stdout = StringIO()
394 sys.stdout = StringIO()
395 ip.run_cell("%tb")
395 ip.run_cell("%tb")
396 out = sys.stdout.getvalue()
396 out = sys.stdout.getvalue()
397 finally:
397 finally:
398 sys.stdout = save_stdout
398 sys.stdout = save_stdout
399 # trim output, and only check the last line
399 # trim output, and only check the last line
400 last_line = out.rstrip().splitlines()[-1].strip()
400 last_line = out.rstrip().splitlines()[-1].strip()
401 assert last_line == "SyntaxError: invalid syntax"
401 assert last_line == "SyntaxError: invalid syntax"
402
402
403
403
404 def test_time():
404 def test_time():
405 ip = get_ipython()
405 ip = get_ipython()
406
406
407 with tt.AssertPrints("Wall time: "):
407 with tt.AssertPrints("Wall time: "):
408 ip.run_cell("%time None")
408 ip.run_cell("%time None")
409
409
410 ip.run_cell("def f(kmjy):\n"
410 ip.run_cell("def f(kmjy):\n"
411 " %time print (2*kmjy)")
411 " %time print (2*kmjy)")
412
412
413 with tt.AssertPrints("Wall time: "):
413 with tt.AssertPrints("Wall time: "):
414 with tt.AssertPrints("hihi", suppress=False):
414 with tt.AssertPrints("hihi", suppress=False):
415 ip.run_cell("f('hi')")
415 ip.run_cell("f('hi')")
416
416
417 def test_time_last_not_expression():
417 def test_time_last_not_expression():
418 ip.run_cell("%%time\n"
418 ip.run_cell("%%time\n"
419 "var_1 = 1\n"
419 "var_1 = 1\n"
420 "var_2 = 2\n")
420 "var_2 = 2\n")
421 assert ip.user_ns['var_1'] == 1
421 assert ip.user_ns['var_1'] == 1
422 del ip.user_ns['var_1']
422 del ip.user_ns['var_1']
423 assert ip.user_ns['var_2'] == 2
423 assert ip.user_ns['var_2'] == 2
424 del ip.user_ns['var_2']
424 del ip.user_ns['var_2']
425
425
426
426
427 @dec.skip_win32
427 @dec.skip_win32
428 def test_time2():
428 def test_time2():
429 ip = get_ipython()
429 ip = get_ipython()
430
430
431 with tt.AssertPrints("CPU times: user "):
431 with tt.AssertPrints("CPU times: user "):
432 ip.run_cell("%time None")
432 ip.run_cell("%time None")
433
433
434 def test_time3():
434 def test_time3():
435 """Erroneous magic function calls, issue gh-3334"""
435 """Erroneous magic function calls, issue gh-3334"""
436 ip = get_ipython()
436 ip = get_ipython()
437 ip.user_ns.pop('run', None)
437 ip.user_ns.pop('run', None)
438
438
439 with tt.AssertNotPrints("not found", channel='stderr'):
439 with tt.AssertNotPrints("not found", channel='stderr'):
440 ip.run_cell("%%time\n"
440 ip.run_cell("%%time\n"
441 "run = 0\n"
441 "run = 0\n"
442 "run += 1")
442 "run += 1")
443
443
444 def test_multiline_time():
444 def test_multiline_time():
445 """Make sure last statement from time return a value."""
445 """Make sure last statement from time return a value."""
446 ip = get_ipython()
446 ip = get_ipython()
447 ip.user_ns.pop('run', None)
447 ip.user_ns.pop('run', None)
448
448
449 ip.run_cell(dedent("""\
449 ip.run_cell(dedent("""\
450 %%time
450 %%time
451 a = "ho"
451 a = "ho"
452 b = "hey"
452 b = "hey"
453 a+b
453 a+b
454 """
454 """
455 )
455 )
456 )
456 )
457 assert ip.user_ns_hidden["_"] == "hohey"
457 assert ip.user_ns_hidden["_"] == "hohey"
458
458
459
459
460 def test_time_local_ns():
460 def test_time_local_ns():
461 """
461 """
462 Test that local_ns is actually global_ns when running a cell magic
462 Test that local_ns is actually global_ns when running a cell magic
463 """
463 """
464 ip = get_ipython()
464 ip = get_ipython()
465 ip.run_cell("%%time\n" "myvar = 1")
465 ip.run_cell("%%time\n" "myvar = 1")
466 assert ip.user_ns["myvar"] == 1
466 assert ip.user_ns["myvar"] == 1
467 del ip.user_ns["myvar"]
467 del ip.user_ns["myvar"]
468
468
469
469
470 def test_doctest_mode():
470 def test_doctest_mode():
471 "Toggle doctest_mode twice, it should be a no-op and run without error"
471 "Toggle doctest_mode twice, it should be a no-op and run without error"
472 _ip.magic('doctest_mode')
472 _ip.magic('doctest_mode')
473 _ip.magic('doctest_mode')
473 _ip.magic('doctest_mode')
474
474
475
475
476 def test_parse_options():
476 def test_parse_options():
477 """Tests for basic options parsing in magics."""
477 """Tests for basic options parsing in magics."""
478 # These are only the most minimal of tests, more should be added later. At
478 # These are only the most minimal of tests, more should be added later. At
479 # the very least we check that basic text/unicode calls work OK.
479 # the very least we check that basic text/unicode calls work OK.
480 m = DummyMagics(_ip)
480 m = DummyMagics(_ip)
481 assert m.parse_options("foo", "")[1] == "foo"
481 assert m.parse_options("foo", "")[1] == "foo"
482 assert m.parse_options("foo", "")[1] == "foo"
482 assert m.parse_options("foo", "")[1] == "foo"
483
483
484
484
485 def test_parse_options_preserve_non_option_string():
485 def test_parse_options_preserve_non_option_string():
486 """Test to assert preservation of non-option part of magic-block, while parsing magic options."""
486 """Test to assert preservation of non-option part of magic-block, while parsing magic options."""
487 m = DummyMagics(_ip)
487 m = DummyMagics(_ip)
488 opts, stmt = m.parse_options(
488 opts, stmt = m.parse_options(
489 " -n1 -r 13 _ = 314 + foo", "n:r:", preserve_non_opts=True
489 " -n1 -r 13 _ = 314 + foo", "n:r:", preserve_non_opts=True
490 )
490 )
491 assert opts == {"n": "1", "r": "13"}
491 assert opts == {"n": "1", "r": "13"}
492 assert stmt == "_ = 314 + foo"
492 assert stmt == "_ = 314 + foo"
493
493
494
494
495 def test_run_magic_preserve_code_block():
495 def test_run_magic_preserve_code_block():
496 """Test to assert preservation of non-option part of magic-block, while running magic."""
496 """Test to assert preservation of non-option part of magic-block, while running magic."""
497 _ip.user_ns["spaces"] = []
497 _ip.user_ns["spaces"] = []
498 _ip.magic("timeit -n1 -r1 spaces.append([s.count(' ') for s in ['document']])")
498 _ip.magic("timeit -n1 -r1 spaces.append([s.count(' ') for s in ['document']])")
499 assert _ip.user_ns["spaces"] == [[0]]
499 assert _ip.user_ns["spaces"] == [[0]]
500
500
501
501
502 def test_dirops():
502 def test_dirops():
503 """Test various directory handling operations."""
503 """Test various directory handling operations."""
504 # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/')
504 # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/')
505 curpath = os.getcwd
505 curpath = os.getcwd
506 startdir = os.getcwd()
506 startdir = os.getcwd()
507 ipdir = os.path.realpath(_ip.ipython_dir)
507 ipdir = os.path.realpath(_ip.ipython_dir)
508 try:
508 try:
509 _ip.magic('cd "%s"' % ipdir)
509 _ip.magic('cd "%s"' % ipdir)
510 assert curpath() == ipdir
510 assert curpath() == ipdir
511 _ip.magic('cd -')
511 _ip.magic('cd -')
512 assert curpath() == startdir
512 assert curpath() == startdir
513 _ip.magic('pushd "%s"' % ipdir)
513 _ip.magic('pushd "%s"' % ipdir)
514 assert curpath() == ipdir
514 assert curpath() == ipdir
515 _ip.magic('popd')
515 _ip.magic('popd')
516 assert curpath() == startdir
516 assert curpath() == startdir
517 finally:
517 finally:
518 os.chdir(startdir)
518 os.chdir(startdir)
519
519
520
520
521 def test_cd_force_quiet():
521 def test_cd_force_quiet():
522 """Test OSMagics.cd_force_quiet option"""
522 """Test OSMagics.cd_force_quiet option"""
523 _ip.config.OSMagics.cd_force_quiet = True
523 _ip.config.OSMagics.cd_force_quiet = True
524 osmagics = osm.OSMagics(shell=_ip)
524 osmagics = osm.OSMagics(shell=_ip)
525
525
526 startdir = os.getcwd()
526 startdir = os.getcwd()
527 ipdir = os.path.realpath(_ip.ipython_dir)
527 ipdir = os.path.realpath(_ip.ipython_dir)
528
528
529 try:
529 try:
530 with tt.AssertNotPrints(ipdir):
530 with tt.AssertNotPrints(ipdir):
531 osmagics.cd('"%s"' % ipdir)
531 osmagics.cd('"%s"' % ipdir)
532 with tt.AssertNotPrints(startdir):
532 with tt.AssertNotPrints(startdir):
533 osmagics.cd('-')
533 osmagics.cd('-')
534 finally:
534 finally:
535 os.chdir(startdir)
535 os.chdir(startdir)
536
536
537
537
538 def test_xmode():
538 def test_xmode():
539 # Calling xmode three times should be a no-op
539 # Calling xmode three times should be a no-op
540 xmode = _ip.InteractiveTB.mode
540 xmode = _ip.InteractiveTB.mode
541 for i in range(4):
541 for i in range(4):
542 _ip.magic("xmode")
542 _ip.magic("xmode")
543 assert _ip.InteractiveTB.mode == xmode
543 assert _ip.InteractiveTB.mode == xmode
544
544
545 def test_reset_hard():
545 def test_reset_hard():
546 monitor = []
546 monitor = []
547 class A(object):
547 class A(object):
548 def __del__(self):
548 def __del__(self):
549 monitor.append(1)
549 monitor.append(1)
550 def __repr__(self):
550 def __repr__(self):
551 return "<A instance>"
551 return "<A instance>"
552
552
553 _ip.user_ns["a"] = A()
553 _ip.user_ns["a"] = A()
554 _ip.run_cell("a")
554 _ip.run_cell("a")
555
555
556 assert monitor == []
556 assert monitor == []
557 _ip.magic("reset -f")
557 _ip.magic("reset -f")
558 assert monitor == [1]
558 assert monitor == [1]
559
559
560 class TestXdel(tt.TempFileMixin):
560 class TestXdel(tt.TempFileMixin):
561 def test_xdel(self):
561 def test_xdel(self):
562 """Test that references from %run are cleared by xdel."""
562 """Test that references from %run are cleared by xdel."""
563 src = ("class A(object):\n"
563 src = ("class A(object):\n"
564 " monitor = []\n"
564 " monitor = []\n"
565 " def __del__(self):\n"
565 " def __del__(self):\n"
566 " self.monitor.append(1)\n"
566 " self.monitor.append(1)\n"
567 "a = A()\n")
567 "a = A()\n")
568 self.mktmp(src)
568 self.mktmp(src)
569 # %run creates some hidden references...
569 # %run creates some hidden references...
570 _ip.magic("run %s" % self.fname)
570 _ip.magic("run %s" % self.fname)
571 # ... as does the displayhook.
571 # ... as does the displayhook.
572 _ip.run_cell("a")
572 _ip.run_cell("a")
573
573
574 monitor = _ip.user_ns["A"].monitor
574 monitor = _ip.user_ns["A"].monitor
575 assert monitor == []
575 assert monitor == []
576
576
577 _ip.magic("xdel a")
577 _ip.magic("xdel a")
578
578
579 # Check that a's __del__ method has been called.
579 # Check that a's __del__ method has been called.
580 gc.collect(0)
580 gc.collect(0)
581 assert monitor == [1]
581 assert monitor == [1]
582
582
583 def doctest_who():
583 def doctest_who():
584 """doctest for %who
584 """doctest for %who
585
585
586 In [1]: %reset -sf
586 In [1]: %reset -sf
587
587
588 In [2]: alpha = 123
588 In [2]: alpha = 123
589
589
590 In [3]: beta = 'beta'
590 In [3]: beta = 'beta'
591
591
592 In [4]: %who int
592 In [4]: %who int
593 alpha
593 alpha
594
594
595 In [5]: %who str
595 In [5]: %who str
596 beta
596 beta
597
597
598 In [6]: %whos
598 In [6]: %whos
599 Variable Type Data/Info
599 Variable Type Data/Info
600 ----------------------------
600 ----------------------------
601 alpha int 123
601 alpha int 123
602 beta str beta
602 beta str beta
603
603
604 In [7]: %who_ls
604 In [7]: %who_ls
605 Out[7]: ['alpha', 'beta']
605 Out[7]: ['alpha', 'beta']
606 """
606 """
607
607
608 def test_whos():
608 def test_whos():
609 """Check that whos is protected against objects where repr() fails."""
609 """Check that whos is protected against objects where repr() fails."""
610 class A(object):
610 class A(object):
611 def __repr__(self):
611 def __repr__(self):
612 raise Exception()
612 raise Exception()
613 _ip.user_ns['a'] = A()
613 _ip.user_ns['a'] = A()
614 _ip.magic("whos")
614 _ip.magic("whos")
615
615
616 def doctest_precision():
616 def doctest_precision():
617 """doctest for %precision
617 """doctest for %precision
618
618
619 In [1]: f = get_ipython().display_formatter.formatters['text/plain']
619 In [1]: f = get_ipython().display_formatter.formatters['text/plain']
620
620
621 In [2]: %precision 5
621 In [2]: %precision 5
622 Out[2]: '%.5f'
622 Out[2]: '%.5f'
623
623
624 In [3]: f.float_format
624 In [3]: f.float_format
625 Out[3]: '%.5f'
625 Out[3]: '%.5f'
626
626
627 In [4]: %precision %e
627 In [4]: %precision %e
628 Out[4]: '%e'
628 Out[4]: '%e'
629
629
630 In [5]: f(3.1415927)
630 In [5]: f(3.1415927)
631 Out[5]: '3.141593e+00'
631 Out[5]: '3.141593e+00'
632 """
632 """
633
633
634 def test_debug_magic():
634 def test_debug_magic():
635 """Test debugging a small code with %debug
635 """Test debugging a small code with %debug
636
636
637 In [1]: with PdbTestInput(['c']):
637 In [1]: with PdbTestInput(['c']):
638 ...: %debug print("a b") #doctest: +ELLIPSIS
638 ...: %debug print("a b") #doctest: +ELLIPSIS
639 ...:
639 ...:
640 ...
640 ...
641 ipdb> c
641 ipdb> c
642 a b
642 a b
643 In [2]:
643 In [2]:
644 """
644 """
645
645
646 def test_psearch():
646 def test_psearch():
647 with tt.AssertPrints("dict.fromkeys"):
647 with tt.AssertPrints("dict.fromkeys"):
648 _ip.run_cell("dict.fr*?")
648 _ip.run_cell("dict.fr*?")
649 with tt.AssertPrints("π.is_integer"):
649 with tt.AssertPrints("π.is_integer"):
650 _ip.run_cell("π = 3.14;\nπ.is_integ*?")
650 _ip.run_cell("π = 3.14;\nπ.is_integ*?")
651
651
652 def test_timeit_shlex():
652 def test_timeit_shlex():
653 """test shlex issues with timeit (#1109)"""
653 """test shlex issues with timeit (#1109)"""
654 _ip.ex("def f(*a,**kw): pass")
654 _ip.ex("def f(*a,**kw): pass")
655 _ip.magic('timeit -n1 "this is a bug".count(" ")')
655 _ip.magic('timeit -n1 "this is a bug".count(" ")')
656 _ip.magic('timeit -r1 -n1 f(" ", 1)')
656 _ip.magic('timeit -r1 -n1 f(" ", 1)')
657 _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")')
657 _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")')
658 _ip.magic('timeit -r1 -n1 ("a " + "b")')
658 _ip.magic('timeit -r1 -n1 ("a " + "b")')
659 _ip.magic('timeit -r1 -n1 f("a " + "b")')
659 _ip.magic('timeit -r1 -n1 f("a " + "b")')
660 _ip.magic('timeit -r1 -n1 f("a " + "b ")')
660 _ip.magic('timeit -r1 -n1 f("a " + "b ")')
661
661
662
662
663 def test_timeit_special_syntax():
663 def test_timeit_special_syntax():
664 "Test %%timeit with IPython special syntax"
664 "Test %%timeit with IPython special syntax"
665 @register_line_magic
665 @register_line_magic
666 def lmagic(line):
666 def lmagic(line):
667 ip = get_ipython()
667 ip = get_ipython()
668 ip.user_ns['lmagic_out'] = line
668 ip.user_ns['lmagic_out'] = line
669
669
670 # line mode test
670 # line mode test
671 _ip.run_line_magic("timeit", "-n1 -r1 %lmagic my line")
671 _ip.run_line_magic("timeit", "-n1 -r1 %lmagic my line")
672 assert _ip.user_ns["lmagic_out"] == "my line"
672 assert _ip.user_ns["lmagic_out"] == "my line"
673 # cell mode test
673 # cell mode test
674 _ip.run_cell_magic("timeit", "-n1 -r1", "%lmagic my line2")
674 _ip.run_cell_magic("timeit", "-n1 -r1", "%lmagic my line2")
675 assert _ip.user_ns["lmagic_out"] == "my line2"
675 assert _ip.user_ns["lmagic_out"] == "my line2"
676
676
677
677
678 def test_timeit_return():
678 def test_timeit_return():
679 """
679 """
680 test whether timeit -o return object
680 test whether timeit -o return object
681 """
681 """
682
682
683 res = _ip.run_line_magic('timeit','-n10 -r10 -o 1')
683 res = _ip.run_line_magic('timeit','-n10 -r10 -o 1')
684 assert(res is not None)
684 assert(res is not None)
685
685
686 def test_timeit_quiet():
686 def test_timeit_quiet():
687 """
687 """
688 test quiet option of timeit magic
688 test quiet option of timeit magic
689 """
689 """
690 with tt.AssertNotPrints("loops"):
690 with tt.AssertNotPrints("loops"):
691 _ip.run_cell("%timeit -n1 -r1 -q 1")
691 _ip.run_cell("%timeit -n1 -r1 -q 1")
692
692
693 def test_timeit_return_quiet():
693 def test_timeit_return_quiet():
694 with tt.AssertNotPrints("loops"):
694 with tt.AssertNotPrints("loops"):
695 res = _ip.run_line_magic('timeit', '-n1 -r1 -q -o 1')
695 res = _ip.run_line_magic('timeit', '-n1 -r1 -q -o 1')
696 assert (res is not None)
696 assert (res is not None)
697
697
698 def test_timeit_invalid_return():
698 def test_timeit_invalid_return():
699 with pytest.raises(SyntaxError):
699 with pytest.raises(SyntaxError):
700 _ip.run_line_magic('timeit', 'return')
700 _ip.run_line_magic('timeit', 'return')
701
701
702 @dec.skipif(execution.profile is None)
702 @dec.skipif(execution.profile is None)
703 def test_prun_special_syntax():
703 def test_prun_special_syntax():
704 "Test %%prun with IPython special syntax"
704 "Test %%prun with IPython special syntax"
705 @register_line_magic
705 @register_line_magic
706 def lmagic(line):
706 def lmagic(line):
707 ip = get_ipython()
707 ip = get_ipython()
708 ip.user_ns['lmagic_out'] = line
708 ip.user_ns['lmagic_out'] = line
709
709
710 # line mode test
710 # line mode test
711 _ip.run_line_magic("prun", "-q %lmagic my line")
711 _ip.run_line_magic("prun", "-q %lmagic my line")
712 assert _ip.user_ns["lmagic_out"] == "my line"
712 assert _ip.user_ns["lmagic_out"] == "my line"
713 # cell mode test
713 # cell mode test
714 _ip.run_cell_magic("prun", "-q", "%lmagic my line2")
714 _ip.run_cell_magic("prun", "-q", "%lmagic my line2")
715 assert _ip.user_ns["lmagic_out"] == "my line2"
715 assert _ip.user_ns["lmagic_out"] == "my line2"
716
716
717
717
718 @dec.skipif(execution.profile is None)
718 @dec.skipif(execution.profile is None)
719 def test_prun_quotes():
719 def test_prun_quotes():
720 "Test that prun does not clobber string escapes (GH #1302)"
720 "Test that prun does not clobber string escapes (GH #1302)"
721 _ip.magic(r"prun -q x = '\t'")
721 _ip.magic(r"prun -q x = '\t'")
722 assert _ip.user_ns["x"] == "\t"
722 assert _ip.user_ns["x"] == "\t"
723
723
724
724
725 def test_extension():
725 def test_extension():
726 # Debugging information for failures of this test
726 # Debugging information for failures of this test
727 print('sys.path:')
727 print('sys.path:')
728 for p in sys.path:
728 for p in sys.path:
729 print(' ', p)
729 print(' ', p)
730 print('CWD', os.getcwd())
730 print('CWD', os.getcwd())
731
731
732 pytest.raises(ImportError, _ip.magic, "load_ext daft_extension")
732 pytest.raises(ImportError, _ip.magic, "load_ext daft_extension")
733 daft_path = os.path.join(os.path.dirname(__file__), "daft_extension")
733 daft_path = os.path.join(os.path.dirname(__file__), "daft_extension")
734 sys.path.insert(0, daft_path)
734 sys.path.insert(0, daft_path)
735 try:
735 try:
736 _ip.user_ns.pop('arq', None)
736 _ip.user_ns.pop('arq', None)
737 invalidate_caches() # Clear import caches
737 invalidate_caches() # Clear import caches
738 _ip.magic("load_ext daft_extension")
738 _ip.magic("load_ext daft_extension")
739 assert _ip.user_ns["arq"] == 185
739 assert _ip.user_ns["arq"] == 185
740 _ip.magic("unload_ext daft_extension")
740 _ip.magic("unload_ext daft_extension")
741 assert 'arq' not in _ip.user_ns
741 assert 'arq' not in _ip.user_ns
742 finally:
742 finally:
743 sys.path.remove(daft_path)
743 sys.path.remove(daft_path)
744
744
745
745
746 def test_notebook_export_json():
746 def test_notebook_export_json():
747 pytest.importorskip("nbformat")
747 pytest.importorskip("nbformat")
748 _ip = get_ipython()
748 _ip = get_ipython()
749 _ip.history_manager.reset() # Clear any existing history.
749 _ip.history_manager.reset() # Clear any existing history.
750 cmds = ["a=1", "def b():\n return a**2", "print('noël, été', b())"]
750 cmds = ["a=1", "def b():\n return a**2", "print('noël, été', b())"]
751 for i, cmd in enumerate(cmds, start=1):
751 for i, cmd in enumerate(cmds, start=1):
752 _ip.history_manager.store_inputs(i, cmd)
752 _ip.history_manager.store_inputs(i, cmd)
753 with TemporaryDirectory() as td:
753 with TemporaryDirectory() as td:
754 outfile = os.path.join(td, "nb.ipynb")
754 outfile = os.path.join(td, "nb.ipynb")
755 _ip.magic("notebook %s" % outfile)
755 _ip.magic("notebook %s" % outfile)
756
756
757
757
758 class TestEnv(TestCase):
758 class TestEnv(TestCase):
759
759
760 def test_env(self):
760 def test_env(self):
761 env = _ip.magic("env")
761 env = _ip.magic("env")
762 self.assertTrue(isinstance(env, dict))
762 self.assertTrue(isinstance(env, dict))
763
763
764 def test_env_secret(self):
764 def test_env_secret(self):
765 env = _ip.magic("env")
765 env = _ip.magic("env")
766 hidden = "<hidden>"
766 hidden = "<hidden>"
767 with mock.patch.dict(
767 with mock.patch.dict(
768 os.environ,
768 os.environ,
769 {
769 {
770 "API_KEY": "abc123",
770 "API_KEY": "abc123",
771 "SECRET_THING": "ssshhh",
771 "SECRET_THING": "ssshhh",
772 "JUPYTER_TOKEN": "",
772 "JUPYTER_TOKEN": "",
773 "VAR": "abc"
773 "VAR": "abc"
774 }
774 }
775 ):
775 ):
776 env = _ip.magic("env")
776 env = _ip.magic("env")
777 assert env["API_KEY"] == hidden
777 assert env["API_KEY"] == hidden
778 assert env["SECRET_THING"] == hidden
778 assert env["SECRET_THING"] == hidden
779 assert env["JUPYTER_TOKEN"] == hidden
779 assert env["JUPYTER_TOKEN"] == hidden
780 assert env["VAR"] == "abc"
780 assert env["VAR"] == "abc"
781
781
782 def test_env_get_set_simple(self):
782 def test_env_get_set_simple(self):
783 env = _ip.magic("env var val1")
783 env = _ip.magic("env var val1")
784 self.assertEqual(env, None)
784 self.assertEqual(env, None)
785 self.assertEqual(os.environ['var'], 'val1')
785 self.assertEqual(os.environ['var'], 'val1')
786 self.assertEqual(_ip.magic("env var"), 'val1')
786 self.assertEqual(_ip.magic("env var"), 'val1')
787 env = _ip.magic("env var=val2")
787 env = _ip.magic("env var=val2")
788 self.assertEqual(env, None)
788 self.assertEqual(env, None)
789 self.assertEqual(os.environ['var'], 'val2')
789 self.assertEqual(os.environ['var'], 'val2')
790
790
791 def test_env_get_set_complex(self):
791 def test_env_get_set_complex(self):
792 env = _ip.magic("env var 'val1 '' 'val2")
792 env = _ip.magic("env var 'val1 '' 'val2")
793 self.assertEqual(env, None)
793 self.assertEqual(env, None)
794 self.assertEqual(os.environ['var'], "'val1 '' 'val2")
794 self.assertEqual(os.environ['var'], "'val1 '' 'val2")
795 self.assertEqual(_ip.magic("env var"), "'val1 '' 'val2")
795 self.assertEqual(_ip.magic("env var"), "'val1 '' 'val2")
796 env = _ip.magic('env var=val2 val3="val4')
796 env = _ip.magic('env var=val2 val3="val4')
797 self.assertEqual(env, None)
797 self.assertEqual(env, None)
798 self.assertEqual(os.environ['var'], 'val2 val3="val4')
798 self.assertEqual(os.environ['var'], 'val2 val3="val4')
799
799
800 def test_env_set_bad_input(self):
800 def test_env_set_bad_input(self):
801 self.assertRaises(UsageError, lambda: _ip.magic("set_env var"))
801 self.assertRaises(UsageError, lambda: _ip.magic("set_env var"))
802
802
803 def test_env_set_whitespace(self):
803 def test_env_set_whitespace(self):
804 self.assertRaises(UsageError, lambda: _ip.magic("env var A=B"))
804 self.assertRaises(UsageError, lambda: _ip.magic("env var A=B"))
805
805
806
806
807 class CellMagicTestCase(TestCase):
807 class CellMagicTestCase(TestCase):
808
808
809 def check_ident(self, magic):
809 def check_ident(self, magic):
810 # Manually called, we get the result
810 # Manually called, we get the result
811 out = _ip.run_cell_magic(magic, "a", "b")
811 out = _ip.run_cell_magic(magic, "a", "b")
812 assert out == ("a", "b")
812 assert out == ("a", "b")
813 # Via run_cell, it goes into the user's namespace via displayhook
813 # Via run_cell, it goes into the user's namespace via displayhook
814 _ip.run_cell("%%" + magic + " c\nd\n")
814 _ip.run_cell("%%" + magic + " c\nd\n")
815 assert _ip.user_ns["_"] == ("c", "d\n")
815 assert _ip.user_ns["_"] == ("c", "d\n")
816
816
817 def test_cell_magic_func_deco(self):
817 def test_cell_magic_func_deco(self):
818 "Cell magic using simple decorator"
818 "Cell magic using simple decorator"
819 @register_cell_magic
819 @register_cell_magic
820 def cellm(line, cell):
820 def cellm(line, cell):
821 return line, cell
821 return line, cell
822
822
823 self.check_ident('cellm')
823 self.check_ident('cellm')
824
824
825 def test_cell_magic_reg(self):
825 def test_cell_magic_reg(self):
826 "Cell magic manually registered"
826 "Cell magic manually registered"
827 def cellm(line, cell):
827 def cellm(line, cell):
828 return line, cell
828 return line, cell
829
829
830 _ip.register_magic_function(cellm, 'cell', 'cellm2')
830 _ip.register_magic_function(cellm, 'cell', 'cellm2')
831 self.check_ident('cellm2')
831 self.check_ident('cellm2')
832
832
833 def test_cell_magic_class(self):
833 def test_cell_magic_class(self):
834 "Cell magics declared via a class"
834 "Cell magics declared via a class"
835 @magics_class
835 @magics_class
836 class MyMagics(Magics):
836 class MyMagics(Magics):
837
837
838 @cell_magic
838 @cell_magic
839 def cellm3(self, line, cell):
839 def cellm3(self, line, cell):
840 return line, cell
840 return line, cell
841
841
842 _ip.register_magics(MyMagics)
842 _ip.register_magics(MyMagics)
843 self.check_ident('cellm3')
843 self.check_ident('cellm3')
844
844
845 def test_cell_magic_class2(self):
845 def test_cell_magic_class2(self):
846 "Cell magics declared via a class, #2"
846 "Cell magics declared via a class, #2"
847 @magics_class
847 @magics_class
848 class MyMagics2(Magics):
848 class MyMagics2(Magics):
849
849
850 @cell_magic('cellm4')
850 @cell_magic('cellm4')
851 def cellm33(self, line, cell):
851 def cellm33(self, line, cell):
852 return line, cell
852 return line, cell
853
853
854 _ip.register_magics(MyMagics2)
854 _ip.register_magics(MyMagics2)
855 self.check_ident('cellm4')
855 self.check_ident('cellm4')
856 # Check that nothing is registered as 'cellm33'
856 # Check that nothing is registered as 'cellm33'
857 c33 = _ip.find_cell_magic('cellm33')
857 c33 = _ip.find_cell_magic('cellm33')
858 assert c33 == None
858 assert c33 == None
859
859
860 def test_file():
860 def test_file():
861 """Basic %%writefile"""
861 """Basic %%writefile"""
862 ip = get_ipython()
862 ip = get_ipython()
863 with TemporaryDirectory() as td:
863 with TemporaryDirectory() as td:
864 fname = os.path.join(td, 'file1')
864 fname = os.path.join(td, "file1")
865 ip.run_cell_magic("writefile", fname, u'\n'.join([
865 ip.run_cell_magic(
866 'line1',
866 "writefile",
867 'line2',
867 fname,
868 ]))
868 "\n".join(
869 s = Path(fname).read_text(encoding='utf-8')
869 [
870 "line1",
871 "line2",
872 ]
873 ),
874 )
875 s = Path(fname).read_text(encoding="utf-8")
870 assert "line1\n" in s
876 assert "line1\n" in s
871 assert "line2" in s
877 assert "line2" in s
872
878
873
879
874 @dec.skip_win32
880 @dec.skip_win32
875 def test_file_single_quote():
881 def test_file_single_quote():
876 """Basic %%writefile with embedded single quotes"""
882 """Basic %%writefile with embedded single quotes"""
877 ip = get_ipython()
883 ip = get_ipython()
878 with TemporaryDirectory() as td:
884 with TemporaryDirectory() as td:
879 fname = os.path.join(td, '\'file1\'')
885 fname = os.path.join(td, "'file1'")
880 ip.run_cell_magic("writefile", fname, u'\n'.join([
886 ip.run_cell_magic(
881 'line1',
887 "writefile",
882 'line2',
888 fname,
883 ]))
889 "\n".join(
884 s = Path(fname).read_text(encoding='utf-8')
890 [
891 "line1",
892 "line2",
893 ]
894 ),
895 )
896 s = Path(fname).read_text(encoding="utf-8")
885 assert "line1\n" in s
897 assert "line1\n" in s
886 assert "line2" in s
898 assert "line2" in s
887
899
888
900
889 @dec.skip_win32
901 @dec.skip_win32
890 def test_file_double_quote():
902 def test_file_double_quote():
891 """Basic %%writefile with embedded double quotes"""
903 """Basic %%writefile with embedded double quotes"""
892 ip = get_ipython()
904 ip = get_ipython()
893 with TemporaryDirectory() as td:
905 with TemporaryDirectory() as td:
894 fname = os.path.join(td, '"file1"')
906 fname = os.path.join(td, '"file1"')
895 ip.run_cell_magic("writefile", fname, u'\n'.join([
907 ip.run_cell_magic(
896 'line1',
908 "writefile",
897 'line2',
909 fname,
898 ]))
910 "\n".join(
899 s = Path(fname).read_text(encoding='utf-8')
911 [
912 "line1",
913 "line2",
914 ]
915 ),
916 )
917 s = Path(fname).read_text(encoding="utf-8")
900 assert "line1\n" in s
918 assert "line1\n" in s
901 assert "line2" in s
919 assert "line2" in s
902
920
903
921
904 def test_file_var_expand():
922 def test_file_var_expand():
905 """%%writefile $filename"""
923 """%%writefile $filename"""
906 ip = get_ipython()
924 ip = get_ipython()
907 with TemporaryDirectory() as td:
925 with TemporaryDirectory() as td:
908 fname = os.path.join(td, 'file1')
926 fname = os.path.join(td, "file1")
909 ip.user_ns['filename'] = fname
927 ip.user_ns["filename"] = fname
910 ip.run_cell_magic("writefile", '$filename', u'\n'.join([
928 ip.run_cell_magic(
911 'line1',
929 "writefile",
912 'line2',
930 "$filename",
913 ]))
931 "\n".join(
914 s = Path(fname).read_text(encoding='utf-8')
932 [
933 "line1",
934 "line2",
935 ]
936 ),
937 )
938 s = Path(fname).read_text(encoding="utf-8")
915 assert "line1\n" in s
939 assert "line1\n" in s
916 assert "line2" in s
940 assert "line2" in s
917
941
918
942
919 def test_file_unicode():
943 def test_file_unicode():
920 """%%writefile with unicode cell"""
944 """%%writefile with unicode cell"""
921 ip = get_ipython()
945 ip = get_ipython()
922 with TemporaryDirectory() as td:
946 with TemporaryDirectory() as td:
923 fname = os.path.join(td, 'file1')
947 fname = os.path.join(td, 'file1')
924 ip.run_cell_magic("writefile", fname, u'\n'.join([
948 ip.run_cell_magic("writefile", fname, u'\n'.join([
925 u'liné1',
949 u'liné1',
926 u'liné2',
950 u'liné2',
927 ]))
951 ]))
928 with io.open(fname, encoding='utf-8') as f:
952 with io.open(fname, encoding='utf-8') as f:
929 s = f.read()
953 s = f.read()
930 assert "liné1\n" in s
954 assert "liné1\n" in s
931 assert "liné2" in s
955 assert "liné2" in s
932
956
933
957
934 def test_file_amend():
958 def test_file_amend():
935 """%%writefile -a amends files"""
959 """%%writefile -a amends files"""
936 ip = get_ipython()
960 ip = get_ipython()
937 with TemporaryDirectory() as td:
961 with TemporaryDirectory() as td:
938 fname = os.path.join(td, 'file2')
962 fname = os.path.join(td, "file2")
939 ip.run_cell_magic("writefile", fname, u'\n'.join([
963 ip.run_cell_magic(
940 'line1',
964 "writefile",
941 'line2',
965 fname,
942 ]))
966 "\n".join(
943 ip.run_cell_magic("writefile", "-a %s" % fname, u'\n'.join([
967 [
944 'line3',
968 "line1",
945 'line4',
969 "line2",
946 ]))
970 ]
947 s = Path(fname).read_text(encoding='utf-8')
971 ),
972 )
973 ip.run_cell_magic(
974 "writefile",
975 "-a %s" % fname,
976 "\n".join(
977 [
978 "line3",
979 "line4",
980 ]
981 ),
982 )
983 s = Path(fname).read_text(encoding="utf-8")
948 assert "line1\n" in s
984 assert "line1\n" in s
949 assert "line3\n" in s
985 assert "line3\n" in s
950
986
951
987
952 def test_file_spaces():
988 def test_file_spaces():
953 """%%file with spaces in filename"""
989 """%%file with spaces in filename"""
954 ip = get_ipython()
990 ip = get_ipython()
955 with TemporaryWorkingDirectory() as td:
991 with TemporaryWorkingDirectory() as td:
956 fname = "file name"
992 fname = "file name"
957 ip.run_cell_magic("file", '"%s"'%fname, u'\n'.join([
993 ip.run_cell_magic(
958 'line1',
994 "file",
959 'line2',
995 '"%s"' % fname,
960 ]))
996 "\n".join(
961 s = Path(fname).read_text(encoding='utf-8')
997 [
998 "line1",
999 "line2",
1000 ]
1001 ),
1002 )
1003 s = Path(fname).read_text(encoding="utf-8")
962 assert "line1\n" in s
1004 assert "line1\n" in s
963 assert "line2" in s
1005 assert "line2" in s
964
1006
965
1007
966 def test_script_config():
1008 def test_script_config():
967 ip = get_ipython()
1009 ip = get_ipython()
968 ip.config.ScriptMagics.script_magics = ['whoda']
1010 ip.config.ScriptMagics.script_magics = ['whoda']
969 sm = script.ScriptMagics(shell=ip)
1011 sm = script.ScriptMagics(shell=ip)
970 assert "whoda" in sm.magics["cell"]
1012 assert "whoda" in sm.magics["cell"]
971
1013
972
1014
973 def test_script_out():
1015 def test_script_out():
974 ip = get_ipython()
1016 ip = get_ipython()
975 ip.run_cell_magic("script", f"--out output {sys.executable}", "print('hi')")
1017 ip.run_cell_magic("script", f"--out output {sys.executable}", "print('hi')")
976 assert ip.user_ns["output"].strip() == "hi"
1018 assert ip.user_ns["output"].strip() == "hi"
977
1019
978
1020
979 def test_script_err():
1021 def test_script_err():
980 ip = get_ipython()
1022 ip = get_ipython()
981 ip.run_cell_magic(
1023 ip.run_cell_magic(
982 "script",
1024 "script",
983 f"--err error {sys.executable}",
1025 f"--err error {sys.executable}",
984 "import sys; print('hello', file=sys.stderr)",
1026 "import sys; print('hello', file=sys.stderr)",
985 )
1027 )
986 assert ip.user_ns["error"].strip() == "hello"
1028 assert ip.user_ns["error"].strip() == "hello"
987
1029
988
1030
989 def test_script_out_err():
1031 def test_script_out_err():
990
1032
991 ip = get_ipython()
1033 ip = get_ipython()
992 ip.run_cell_magic(
1034 ip.run_cell_magic(
993 "script",
1035 "script",
994 f"--out output --err error {sys.executable}",
1036 f"--out output --err error {sys.executable}",
995 "\n".join(
1037 "\n".join(
996 [
1038 [
997 "import sys",
1039 "import sys",
998 "print('hi')",
1040 "print('hi')",
999 "print('hello', file=sys.stderr)",
1041 "print('hello', file=sys.stderr)",
1000 ]
1042 ]
1001 ),
1043 ),
1002 )
1044 )
1003 assert ip.user_ns["output"].strip() == "hi"
1045 assert ip.user_ns["output"].strip() == "hi"
1004 assert ip.user_ns["error"].strip() == "hello"
1046 assert ip.user_ns["error"].strip() == "hello"
1005
1047
1006
1048
1007 async def test_script_bg_out():
1049 async def test_script_bg_out():
1008 ip = get_ipython()
1050 ip = get_ipython()
1009 ip.run_cell_magic("script", f"--bg --out output {sys.executable}", "print('hi')")
1051 ip.run_cell_magic("script", f"--bg --out output {sys.executable}", "print('hi')")
1010 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1052 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1011 assert ip.user_ns["output"].at_eof()
1053 assert ip.user_ns["output"].at_eof()
1012
1054
1013
1055
1014 async def test_script_bg_err():
1056 async def test_script_bg_err():
1015 ip = get_ipython()
1057 ip = get_ipython()
1016 ip.run_cell_magic(
1058 ip.run_cell_magic(
1017 "script",
1059 "script",
1018 f"--bg --err error {sys.executable}",
1060 f"--bg --err error {sys.executable}",
1019 "import sys; print('hello', file=sys.stderr)",
1061 "import sys; print('hello', file=sys.stderr)",
1020 )
1062 )
1021 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1063 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1022 assert ip.user_ns["error"].at_eof()
1064 assert ip.user_ns["error"].at_eof()
1023
1065
1024
1066
1025 async def test_script_bg_out_err():
1067 async def test_script_bg_out_err():
1026 ip = get_ipython()
1068 ip = get_ipython()
1027 ip.run_cell_magic(
1069 ip.run_cell_magic(
1028 "script",
1070 "script",
1029 f"--bg --out output --err error {sys.executable}",
1071 f"--bg --out output --err error {sys.executable}",
1030 "\n".join(
1072 "\n".join(
1031 [
1073 [
1032 "import sys",
1074 "import sys",
1033 "print('hi')",
1075 "print('hi')",
1034 "print('hello', file=sys.stderr)",
1076 "print('hello', file=sys.stderr)",
1035 ]
1077 ]
1036 ),
1078 ),
1037 )
1079 )
1038 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1080 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1039 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1081 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1040 assert ip.user_ns["output"].at_eof()
1082 assert ip.user_ns["output"].at_eof()
1041 assert ip.user_ns["error"].at_eof()
1083 assert ip.user_ns["error"].at_eof()
1042
1084
1043
1085
1044 async def test_script_bg_proc():
1086 async def test_script_bg_proc():
1045 ip = get_ipython()
1087 ip = get_ipython()
1046 ip.run_cell_magic(
1088 ip.run_cell_magic(
1047 "script",
1089 "script",
1048 f"--bg --out output --proc p {sys.executable}",
1090 f"--bg --out output --proc p {sys.executable}",
1049 "\n".join(
1091 "\n".join(
1050 [
1092 [
1051 "import sys",
1093 "import sys",
1052 "print('hi')",
1094 "print('hi')",
1053 "print('hello', file=sys.stderr)",
1095 "print('hello', file=sys.stderr)",
1054 ]
1096 ]
1055 ),
1097 ),
1056 )
1098 )
1057 p = ip.user_ns["p"]
1099 p = ip.user_ns["p"]
1058 await p.wait()
1100 await p.wait()
1059 assert p.returncode == 0
1101 assert p.returncode == 0
1060 assert (await p.stdout.read()).strip() == b"hi"
1102 assert (await p.stdout.read()).strip() == b"hi"
1061 # not captured, so empty
1103 # not captured, so empty
1062 assert (await p.stderr.read()) == b""
1104 assert (await p.stderr.read()) == b""
1063 assert p.stdout.at_eof()
1105 assert p.stdout.at_eof()
1064 assert p.stderr.at_eof()
1106 assert p.stderr.at_eof()
1065
1107
1066
1108
1067 def test_script_defaults():
1109 def test_script_defaults():
1068 ip = get_ipython()
1110 ip = get_ipython()
1069 for cmd in ['sh', 'bash', 'perl', 'ruby']:
1111 for cmd in ['sh', 'bash', 'perl', 'ruby']:
1070 try:
1112 try:
1071 find_cmd(cmd)
1113 find_cmd(cmd)
1072 except Exception:
1114 except Exception:
1073 pass
1115 pass
1074 else:
1116 else:
1075 assert cmd in ip.magics_manager.magics["cell"]
1117 assert cmd in ip.magics_manager.magics["cell"]
1076
1118
1077
1119
1078 @magics_class
1120 @magics_class
1079 class FooFoo(Magics):
1121 class FooFoo(Magics):
1080 """class with both %foo and %%foo magics"""
1122 """class with both %foo and %%foo magics"""
1081 @line_magic('foo')
1123 @line_magic('foo')
1082 def line_foo(self, line):
1124 def line_foo(self, line):
1083 "I am line foo"
1125 "I am line foo"
1084 pass
1126 pass
1085
1127
1086 @cell_magic("foo")
1128 @cell_magic("foo")
1087 def cell_foo(self, line, cell):
1129 def cell_foo(self, line, cell):
1088 "I am cell foo, not line foo"
1130 "I am cell foo, not line foo"
1089 pass
1131 pass
1090
1132
1091 def test_line_cell_info():
1133 def test_line_cell_info():
1092 """%%foo and %foo magics are distinguishable to inspect"""
1134 """%%foo and %foo magics are distinguishable to inspect"""
1093 ip = get_ipython()
1135 ip = get_ipython()
1094 ip.magics_manager.register(FooFoo)
1136 ip.magics_manager.register(FooFoo)
1095 oinfo = ip.object_inspect("foo")
1137 oinfo = ip.object_inspect("foo")
1096 assert oinfo["found"] is True
1138 assert oinfo["found"] is True
1097 assert oinfo["ismagic"] is True
1139 assert oinfo["ismagic"] is True
1098
1140
1099 oinfo = ip.object_inspect("%%foo")
1141 oinfo = ip.object_inspect("%%foo")
1100 assert oinfo["found"] is True
1142 assert oinfo["found"] is True
1101 assert oinfo["ismagic"] is True
1143 assert oinfo["ismagic"] is True
1102 assert oinfo["docstring"] == FooFoo.cell_foo.__doc__
1144 assert oinfo["docstring"] == FooFoo.cell_foo.__doc__
1103
1145
1104 oinfo = ip.object_inspect("%foo")
1146 oinfo = ip.object_inspect("%foo")
1105 assert oinfo["found"] is True
1147 assert oinfo["found"] is True
1106 assert oinfo["ismagic"] is True
1148 assert oinfo["ismagic"] is True
1107 assert oinfo["docstring"] == FooFoo.line_foo.__doc__
1149 assert oinfo["docstring"] == FooFoo.line_foo.__doc__
1108
1150
1109
1151
1110 def test_multiple_magics():
1152 def test_multiple_magics():
1111 ip = get_ipython()
1153 ip = get_ipython()
1112 foo1 = FooFoo(ip)
1154 foo1 = FooFoo(ip)
1113 foo2 = FooFoo(ip)
1155 foo2 = FooFoo(ip)
1114 mm = ip.magics_manager
1156 mm = ip.magics_manager
1115 mm.register(foo1)
1157 mm.register(foo1)
1116 assert mm.magics["line"]["foo"].__self__ is foo1
1158 assert mm.magics["line"]["foo"].__self__ is foo1
1117 mm.register(foo2)
1159 mm.register(foo2)
1118 assert mm.magics["line"]["foo"].__self__ is foo2
1160 assert mm.magics["line"]["foo"].__self__ is foo2
1119
1161
1120
1162
1121 def test_alias_magic():
1163 def test_alias_magic():
1122 """Test %alias_magic."""
1164 """Test %alias_magic."""
1123 ip = get_ipython()
1165 ip = get_ipython()
1124 mm = ip.magics_manager
1166 mm = ip.magics_manager
1125
1167
1126 # Basic operation: both cell and line magics are created, if possible.
1168 # Basic operation: both cell and line magics are created, if possible.
1127 ip.run_line_magic("alias_magic", "timeit_alias timeit")
1169 ip.run_line_magic("alias_magic", "timeit_alias timeit")
1128 assert "timeit_alias" in mm.magics["line"]
1170 assert "timeit_alias" in mm.magics["line"]
1129 assert "timeit_alias" in mm.magics["cell"]
1171 assert "timeit_alias" in mm.magics["cell"]
1130
1172
1131 # --cell is specified, line magic not created.
1173 # --cell is specified, line magic not created.
1132 ip.run_line_magic("alias_magic", "--cell timeit_cell_alias timeit")
1174 ip.run_line_magic("alias_magic", "--cell timeit_cell_alias timeit")
1133 assert "timeit_cell_alias" not in mm.magics["line"]
1175 assert "timeit_cell_alias" not in mm.magics["line"]
1134 assert "timeit_cell_alias" in mm.magics["cell"]
1176 assert "timeit_cell_alias" in mm.magics["cell"]
1135
1177
1136 # Test that line alias is created successfully.
1178 # Test that line alias is created successfully.
1137 ip.run_line_magic("alias_magic", "--line env_alias env")
1179 ip.run_line_magic("alias_magic", "--line env_alias env")
1138 assert ip.run_line_magic("env", "") == ip.run_line_magic("env_alias", "")
1180 assert ip.run_line_magic("env", "") == ip.run_line_magic("env_alias", "")
1139
1181
1140 # Test that line alias with parameters passed in is created successfully.
1182 # Test that line alias with parameters passed in is created successfully.
1141 ip.run_line_magic(
1183 ip.run_line_magic(
1142 "alias_magic", "--line history_alias history --params " + shlex.quote("3")
1184 "alias_magic", "--line history_alias history --params " + shlex.quote("3")
1143 )
1185 )
1144 assert "history_alias" in mm.magics["line"]
1186 assert "history_alias" in mm.magics["line"]
1145
1187
1146
1188
1147 def test_save():
1189 def test_save():
1148 """Test %save."""
1190 """Test %save."""
1149 ip = get_ipython()
1191 ip = get_ipython()
1150 ip.history_manager.reset() # Clear any existing history.
1192 ip.history_manager.reset() # Clear any existing history.
1151 cmds = ["a=1", "def b():\n return a**2", "print(a, b())"]
1193 cmds = ["a=1", "def b():\n return a**2", "print(a, b())"]
1152 for i, cmd in enumerate(cmds, start=1):
1194 for i, cmd in enumerate(cmds, start=1):
1153 ip.history_manager.store_inputs(i, cmd)
1195 ip.history_manager.store_inputs(i, cmd)
1154 with TemporaryDirectory() as tmpdir:
1196 with TemporaryDirectory() as tmpdir:
1155 file = os.path.join(tmpdir, "testsave.py")
1197 file = os.path.join(tmpdir, "testsave.py")
1156 ip.run_line_magic("save", "%s 1-10" % file)
1198 ip.run_line_magic("save", "%s 1-10" % file)
1157 content = Path(file).read_text(encoding='utf-8')
1199 content = Path(file).read_text(encoding="utf-8")
1158 assert content.count(cmds[0]) == 1
1200 assert content.count(cmds[0]) == 1
1159 assert "coding: utf-8" in content
1201 assert "coding: utf-8" in content
1160 ip.run_line_magic("save", "-a %s 1-10" % file)
1202 ip.run_line_magic("save", "-a %s 1-10" % file)
1161 content = Path(file).read_text(encoding='utf-8')
1203 content = Path(file).read_text(encoding="utf-8")
1162 assert content.count(cmds[0]) == 2
1204 assert content.count(cmds[0]) == 2
1163 assert "coding: utf-8" in content
1205 assert "coding: utf-8" in content
1164
1206
1165
1207
1166 def test_save_with_no_args():
1208 def test_save_with_no_args():
1167 ip = get_ipython()
1209 ip = get_ipython()
1168 ip.history_manager.reset() # Clear any existing history.
1210 ip.history_manager.reset() # Clear any existing history.
1169 cmds = ["a=1", "def b():\n return a**2", "print(a, b())", "%save"]
1211 cmds = ["a=1", "def b():\n return a**2", "print(a, b())", "%save"]
1170 for i, cmd in enumerate(cmds, start=1):
1212 for i, cmd in enumerate(cmds, start=1):
1171 ip.history_manager.store_inputs(i, cmd)
1213 ip.history_manager.store_inputs(i, cmd)
1172
1214
1173 with TemporaryDirectory() as tmpdir:
1215 with TemporaryDirectory() as tmpdir:
1174 path = os.path.join(tmpdir, "testsave.py")
1216 path = os.path.join(tmpdir, "testsave.py")
1175 ip.run_line_magic("save", path)
1217 ip.run_line_magic("save", path)
1176 content = Path(path).read_text(encoding='utf-8')
1218 content = Path(path).read_text(encoding="utf-8")
1177 expected_content = dedent(
1219 expected_content = dedent(
1178 """\
1220 """\
1179 # coding: utf-8
1221 # coding: utf-8
1180 a=1
1222 a=1
1181 def b():
1223 def b():
1182 return a**2
1224 return a**2
1183 print(a, b())
1225 print(a, b())
1184 """
1226 """
1185 )
1227 )
1186 assert content == expected_content
1228 assert content == expected_content
1187
1229
1188
1230
1189 def test_store():
1231 def test_store():
1190 """Test %store."""
1232 """Test %store."""
1191 ip = get_ipython()
1233 ip = get_ipython()
1192 ip.run_line_magic('load_ext', 'storemagic')
1234 ip.run_line_magic('load_ext', 'storemagic')
1193
1235
1194 # make sure the storage is empty
1236 # make sure the storage is empty
1195 ip.run_line_magic("store", "-z")
1237 ip.run_line_magic("store", "-z")
1196 ip.user_ns["var"] = 42
1238 ip.user_ns["var"] = 42
1197 ip.run_line_magic("store", "var")
1239 ip.run_line_magic("store", "var")
1198 ip.user_ns["var"] = 39
1240 ip.user_ns["var"] = 39
1199 ip.run_line_magic("store", "-r")
1241 ip.run_line_magic("store", "-r")
1200 assert ip.user_ns["var"] == 42
1242 assert ip.user_ns["var"] == 42
1201
1243
1202 ip.run_line_magic("store", "-d var")
1244 ip.run_line_magic("store", "-d var")
1203 ip.user_ns["var"] = 39
1245 ip.user_ns["var"] = 39
1204 ip.run_line_magic("store", "-r")
1246 ip.run_line_magic("store", "-r")
1205 assert ip.user_ns["var"] == 39
1247 assert ip.user_ns["var"] == 39
1206
1248
1207
1249
1208 def _run_edit_test(arg_s, exp_filename=None,
1250 def _run_edit_test(arg_s, exp_filename=None,
1209 exp_lineno=-1,
1251 exp_lineno=-1,
1210 exp_contents=None,
1252 exp_contents=None,
1211 exp_is_temp=None):
1253 exp_is_temp=None):
1212 ip = get_ipython()
1254 ip = get_ipython()
1213 M = code.CodeMagics(ip)
1255 M = code.CodeMagics(ip)
1214 last_call = ['','']
1256 last_call = ['','']
1215 opts,args = M.parse_options(arg_s,'prxn:')
1257 opts,args = M.parse_options(arg_s,'prxn:')
1216 filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call)
1258 filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call)
1217
1259
1218 if exp_filename is not None:
1260 if exp_filename is not None:
1219 assert exp_filename == filename
1261 assert exp_filename == filename
1220 if exp_contents is not None:
1262 if exp_contents is not None:
1221 with io.open(filename, 'r', encoding='utf-8') as f:
1263 with io.open(filename, 'r', encoding='utf-8') as f:
1222 contents = f.read()
1264 contents = f.read()
1223 assert exp_contents == contents
1265 assert exp_contents == contents
1224 if exp_lineno != -1:
1266 if exp_lineno != -1:
1225 assert exp_lineno == lineno
1267 assert exp_lineno == lineno
1226 if exp_is_temp is not None:
1268 if exp_is_temp is not None:
1227 assert exp_is_temp == is_temp
1269 assert exp_is_temp == is_temp
1228
1270
1229
1271
1230 def test_edit_interactive():
1272 def test_edit_interactive():
1231 """%edit on interactively defined objects"""
1273 """%edit on interactively defined objects"""
1232 ip = get_ipython()
1274 ip = get_ipython()
1233 n = ip.execution_count
1275 n = ip.execution_count
1234 ip.run_cell("def foo(): return 1", store_history=True)
1276 ip.run_cell("def foo(): return 1", store_history=True)
1235
1277
1236 with pytest.raises(code.InteractivelyDefined) as e:
1278 with pytest.raises(code.InteractivelyDefined) as e:
1237 _run_edit_test("foo")
1279 _run_edit_test("foo")
1238 assert e.value.index == n
1280 assert e.value.index == n
1239
1281
1240
1282
1241 def test_edit_cell():
1283 def test_edit_cell():
1242 """%edit [cell id]"""
1284 """%edit [cell id]"""
1243 ip = get_ipython()
1285 ip = get_ipython()
1244
1286
1245 ip.run_cell("def foo(): return 1", store_history=True)
1287 ip.run_cell("def foo(): return 1", store_history=True)
1246
1288
1247 # test
1289 # test
1248 _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True)
1290 _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True)
1249
1291
1250 def test_edit_fname():
1292 def test_edit_fname():
1251 """%edit file"""
1293 """%edit file"""
1252 # test
1294 # test
1253 _run_edit_test("test file.py", exp_filename="test file.py")
1295 _run_edit_test("test file.py", exp_filename="test file.py")
1254
1296
1255 def test_bookmark():
1297 def test_bookmark():
1256 ip = get_ipython()
1298 ip = get_ipython()
1257 ip.run_line_magic('bookmark', 'bmname')
1299 ip.run_line_magic('bookmark', 'bmname')
1258 with tt.AssertPrints('bmname'):
1300 with tt.AssertPrints('bmname'):
1259 ip.run_line_magic('bookmark', '-l')
1301 ip.run_line_magic('bookmark', '-l')
1260 ip.run_line_magic('bookmark', '-d bmname')
1302 ip.run_line_magic('bookmark', '-d bmname')
1261
1303
1262 def test_ls_magic():
1304 def test_ls_magic():
1263 ip = get_ipython()
1305 ip = get_ipython()
1264 json_formatter = ip.display_formatter.formatters['application/json']
1306 json_formatter = ip.display_formatter.formatters['application/json']
1265 json_formatter.enabled = True
1307 json_formatter.enabled = True
1266 lsmagic = ip.magic('lsmagic')
1308 lsmagic = ip.magic('lsmagic')
1267 with warnings.catch_warnings(record=True) as w:
1309 with warnings.catch_warnings(record=True) as w:
1268 j = json_formatter(lsmagic)
1310 j = json_formatter(lsmagic)
1269 assert sorted(j) == ["cell", "line"]
1311 assert sorted(j) == ["cell", "line"]
1270 assert w == [] # no warnings
1312 assert w == [] # no warnings
1271
1313
1272
1314
1273 def test_strip_initial_indent():
1315 def test_strip_initial_indent():
1274 def sii(s):
1316 def sii(s):
1275 lines = s.splitlines()
1317 lines = s.splitlines()
1276 return '\n'.join(code.strip_initial_indent(lines))
1318 return '\n'.join(code.strip_initial_indent(lines))
1277
1319
1278 assert sii(" a = 1\nb = 2") == "a = 1\nb = 2"
1320 assert sii(" a = 1\nb = 2") == "a = 1\nb = 2"
1279 assert sii(" a\n b\nc") == "a\n b\nc"
1321 assert sii(" a\n b\nc") == "a\n b\nc"
1280 assert sii("a\n b") == "a\n b"
1322 assert sii("a\n b") == "a\n b"
1281
1323
1282 def test_logging_magic_quiet_from_arg():
1324 def test_logging_magic_quiet_from_arg():
1283 _ip.config.LoggingMagics.quiet = False
1325 _ip.config.LoggingMagics.quiet = False
1284 lm = logging.LoggingMagics(shell=_ip)
1326 lm = logging.LoggingMagics(shell=_ip)
1285 with TemporaryDirectory() as td:
1327 with TemporaryDirectory() as td:
1286 try:
1328 try:
1287 with tt.AssertNotPrints(re.compile("Activating.*")):
1329 with tt.AssertNotPrints(re.compile("Activating.*")):
1288 lm.logstart('-q {}'.format(
1330 lm.logstart('-q {}'.format(
1289 os.path.join(td, "quiet_from_arg.log")))
1331 os.path.join(td, "quiet_from_arg.log")))
1290 finally:
1332 finally:
1291 _ip.logger.logstop()
1333 _ip.logger.logstop()
1292
1334
1293 def test_logging_magic_quiet_from_config():
1335 def test_logging_magic_quiet_from_config():
1294 _ip.config.LoggingMagics.quiet = True
1336 _ip.config.LoggingMagics.quiet = True
1295 lm = logging.LoggingMagics(shell=_ip)
1337 lm = logging.LoggingMagics(shell=_ip)
1296 with TemporaryDirectory() as td:
1338 with TemporaryDirectory() as td:
1297 try:
1339 try:
1298 with tt.AssertNotPrints(re.compile("Activating.*")):
1340 with tt.AssertNotPrints(re.compile("Activating.*")):
1299 lm.logstart(os.path.join(td, "quiet_from_config.log"))
1341 lm.logstart(os.path.join(td, "quiet_from_config.log"))
1300 finally:
1342 finally:
1301 _ip.logger.logstop()
1343 _ip.logger.logstop()
1302
1344
1303
1345
1304 def test_logging_magic_not_quiet():
1346 def test_logging_magic_not_quiet():
1305 _ip.config.LoggingMagics.quiet = False
1347 _ip.config.LoggingMagics.quiet = False
1306 lm = logging.LoggingMagics(shell=_ip)
1348 lm = logging.LoggingMagics(shell=_ip)
1307 with TemporaryDirectory() as td:
1349 with TemporaryDirectory() as td:
1308 try:
1350 try:
1309 with tt.AssertPrints(re.compile("Activating.*")):
1351 with tt.AssertPrints(re.compile("Activating.*")):
1310 lm.logstart(os.path.join(td, "not_quiet.log"))
1352 lm.logstart(os.path.join(td, "not_quiet.log"))
1311 finally:
1353 finally:
1312 _ip.logger.logstop()
1354 _ip.logger.logstop()
1313
1355
1314
1356
1315 def test_time_no_var_expand():
1357 def test_time_no_var_expand():
1316 _ip.user_ns['a'] = 5
1358 _ip.user_ns['a'] = 5
1317 _ip.user_ns['b'] = []
1359 _ip.user_ns['b'] = []
1318 _ip.magic('time b.append("{a}")')
1360 _ip.magic('time b.append("{a}")')
1319 assert _ip.user_ns['b'] == ['{a}']
1361 assert _ip.user_ns['b'] == ['{a}']
1320
1362
1321
1363
1322 # this is slow, put at the end for local testing.
1364 # this is slow, put at the end for local testing.
1323 def test_timeit_arguments():
1365 def test_timeit_arguments():
1324 "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
1366 "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
1325 _ip.magic("timeit -n1 -r1 a=('#')")
1367 _ip.magic("timeit -n1 -r1 a=('#')")
1326
1368
1327
1369
1328 TEST_MODULE = """
1370 TEST_MODULE = """
1329 print('Loaded my_tmp')
1371 print('Loaded my_tmp')
1330 if __name__ == "__main__":
1372 if __name__ == "__main__":
1331 print('I just ran a script')
1373 print('I just ran a script')
1332 """
1374 """
1333
1375
1334
1376
1335 def test_run_module_from_import_hook():
1377 def test_run_module_from_import_hook():
1336 "Test that a module can be loaded via an import hook"
1378 "Test that a module can be loaded via an import hook"
1337 with TemporaryDirectory() as tmpdir:
1379 with TemporaryDirectory() as tmpdir:
1338 fullpath = os.path.join(tmpdir, 'my_tmp.py')
1380 fullpath = os.path.join(tmpdir, "my_tmp.py")
1339 Path(fullpath).write_text(TEST_MODULE, encoding='utf-8')
1381 Path(fullpath).write_text(TEST_MODULE, encoding="utf-8")
1340
1382
1341 import importlib.abc
1383 import importlib.abc
1342 import importlib.util
1384 import importlib.util
1343
1385
1344 class MyTempImporter(importlib.abc.MetaPathFinder, importlib.abc.SourceLoader):
1386 class MyTempImporter(importlib.abc.MetaPathFinder, importlib.abc.SourceLoader):
1345 def find_spec(self, fullname, path, target=None):
1387 def find_spec(self, fullname, path, target=None):
1346 if fullname == "my_tmp":
1388 if fullname == "my_tmp":
1347 return importlib.util.spec_from_loader(fullname, self)
1389 return importlib.util.spec_from_loader(fullname, self)
1348
1390
1349 def get_filename(self, fullname):
1391 def get_filename(self, fullname):
1350 assert fullname == "my_tmp"
1392 assert fullname == "my_tmp"
1351 return fullpath
1393 return fullpath
1352
1394
1353 def get_data(self, path):
1395 def get_data(self, path):
1354 assert Path(path).samefile(fullpath)
1396 assert Path(path).samefile(fullpath)
1355 return Path(fullpath).read_text(encoding='utf-8')
1397 return Path(fullpath).read_text(encoding="utf-8")
1356
1398
1357 sys.meta_path.insert(0, MyTempImporter())
1399 sys.meta_path.insert(0, MyTempImporter())
1358
1400
1359 with capture_output() as captured:
1401 with capture_output() as captured:
1360 _ip.magic("run -m my_tmp")
1402 _ip.magic("run -m my_tmp")
1361 _ip.run_cell("import my_tmp")
1403 _ip.run_cell("import my_tmp")
1362
1404
1363 output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n"
1405 output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n"
1364 assert output == captured.stdout
1406 assert output == captured.stdout
1365
1407
1366 sys.meta_path.pop(0)
1408 sys.meta_path.pop(0)
@@ -1,156 +1,156 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Tests for profile-related functions.
2 """Tests for profile-related functions.
3
3
4 Currently only the startup-dir functionality is tested, but more tests should
4 Currently only the startup-dir functionality is tested, but more tests should
5 be added for:
5 be added for:
6
6
7 * ipython profile create
7 * ipython profile create
8 * ipython profile list
8 * ipython profile list
9 * ipython profile create --parallel
9 * ipython profile create --parallel
10 * security dir permissions
10 * security dir permissions
11
11
12 Authors
12 Authors
13 -------
13 -------
14
14
15 * MinRK
15 * MinRK
16
16
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import shutil
23 import shutil
24 import sys
24 import sys
25 import tempfile
25 import tempfile
26
26
27 from pathlib import Path
27 from pathlib import Path
28 from unittest import TestCase
28 from unittest import TestCase
29
29
30 from IPython.core.profileapp import list_profiles_in, list_bundled_profiles
30 from IPython.core.profileapp import list_profiles_in, list_bundled_profiles
31 from IPython.core.profiledir import ProfileDir
31 from IPython.core.profiledir import ProfileDir
32
32
33 from IPython.testing import decorators as dec
33 from IPython.testing import decorators as dec
34 from IPython.testing import tools as tt
34 from IPython.testing import tools as tt
35 from IPython.utils.process import getoutput
35 from IPython.utils.process import getoutput
36 from IPython.utils.tempdir import TemporaryDirectory
36 from IPython.utils.tempdir import TemporaryDirectory
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Globals
39 # Globals
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41 TMP_TEST_DIR = Path(tempfile.mkdtemp())
41 TMP_TEST_DIR = Path(tempfile.mkdtemp())
42 HOME_TEST_DIR = TMP_TEST_DIR / "home_test_dir"
42 HOME_TEST_DIR = TMP_TEST_DIR / "home_test_dir"
43 IP_TEST_DIR = HOME_TEST_DIR / ".ipython"
43 IP_TEST_DIR = HOME_TEST_DIR / ".ipython"
44
44
45 #
45 #
46 # Setup/teardown functions/decorators
46 # Setup/teardown functions/decorators
47 #
47 #
48
48
49 def setup_module():
49 def setup_module():
50 """Setup test environment for the module:
50 """Setup test environment for the module:
51
51
52 - Adds dummy home dir tree
52 - Adds dummy home dir tree
53 """
53 """
54 # Do not mask exceptions here. In particular, catching WindowsError is a
54 # Do not mask exceptions here. In particular, catching WindowsError is a
55 # problem because that exception is only defined on Windows...
55 # problem because that exception is only defined on Windows...
56 (Path.cwd() / IP_TEST_DIR).mkdir(parents=True)
56 (Path.cwd() / IP_TEST_DIR).mkdir(parents=True)
57
57
58
58
59 def teardown_module():
59 def teardown_module():
60 """Teardown test environment for the module:
60 """Teardown test environment for the module:
61
61
62 - Remove dummy home dir tree
62 - Remove dummy home dir tree
63 """
63 """
64 # Note: we remove the parent test dir, which is the root of all test
64 # Note: we remove the parent test dir, which is the root of all test
65 # subdirs we may have created. Use shutil instead of os.removedirs, so
65 # subdirs we may have created. Use shutil instead of os.removedirs, so
66 # that non-empty directories are all recursively removed.
66 # that non-empty directories are all recursively removed.
67 shutil.rmtree(TMP_TEST_DIR)
67 shutil.rmtree(TMP_TEST_DIR)
68
68
69
69
70 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
71 # Test functions
71 # Test functions
72 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
73 class ProfileStartupTest(TestCase):
73 class ProfileStartupTest(TestCase):
74 def setUp(self):
74 def setUp(self):
75 # create profile dir
75 # create profile dir
76 self.pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, "test")
76 self.pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, "test")
77 self.options = ["--ipython-dir", IP_TEST_DIR, "--profile", "test"]
77 self.options = ["--ipython-dir", IP_TEST_DIR, "--profile", "test"]
78 self.fname = TMP_TEST_DIR / "test.py"
78 self.fname = TMP_TEST_DIR / "test.py"
79
79
80 def tearDown(self):
80 def tearDown(self):
81 # We must remove this profile right away so its presence doesn't
81 # We must remove this profile right away so its presence doesn't
82 # confuse other tests.
82 # confuse other tests.
83 shutil.rmtree(self.pd.location)
83 shutil.rmtree(self.pd.location)
84
84
85 def init(self, startup_file, startup, test):
85 def init(self, startup_file, startup, test):
86 # write startup python file
86 # write startup python file
87 with open(Path(self.pd.startup_dir) / startup_file, "w", encoding='utf-8') as f:
87 with open(Path(self.pd.startup_dir) / startup_file, "w", encoding="utf-8") as f:
88 f.write(startup)
88 f.write(startup)
89 # write simple test file, to check that the startup file was run
89 # write simple test file, to check that the startup file was run
90 with open(self.fname, 'w', encoding='utf-8') as f:
90 with open(self.fname, "w", encoding="utf-8") as f:
91 f.write(test)
91 f.write(test)
92
92
93 def validate(self, output):
93 def validate(self, output):
94 tt.ipexec_validate(self.fname, output, "", options=self.options)
94 tt.ipexec_validate(self.fname, output, "", options=self.options)
95
95
96 def test_startup_py(self):
96 def test_startup_py(self):
97 self.init('00-start.py', 'zzz=123\n', 'print(zzz)\n')
97 self.init('00-start.py', 'zzz=123\n', 'print(zzz)\n')
98 self.validate('123')
98 self.validate('123')
99
99
100 def test_startup_ipy(self):
100 def test_startup_ipy(self):
101 self.init('00-start.ipy', '%xmode plain\n', '')
101 self.init('00-start.ipy', '%xmode plain\n', '')
102 self.validate('Exception reporting mode: Plain')
102 self.validate('Exception reporting mode: Plain')
103
103
104
104
105 def test_list_profiles_in():
105 def test_list_profiles_in():
106 # No need to remove these directories and files, as they will get nuked in
106 # No need to remove these directories and files, as they will get nuked in
107 # the module-level teardown.
107 # the module-level teardown.
108 td = Path(tempfile.mkdtemp(dir=TMP_TEST_DIR))
108 td = Path(tempfile.mkdtemp(dir=TMP_TEST_DIR))
109 for name in ("profile_foo", "profile_hello", "not_a_profile"):
109 for name in ("profile_foo", "profile_hello", "not_a_profile"):
110 Path(td / name).mkdir(parents=True)
110 Path(td / name).mkdir(parents=True)
111 if dec.unicode_paths:
111 if dec.unicode_paths:
112 Path(td / u"profile_ünicode").mkdir(parents=True)
112 Path(td / u"profile_ünicode").mkdir(parents=True)
113
113
114 with open(td / "profile_file", "w", encoding='utf-8') as f:
114 with open(td / "profile_file", "w", encoding="utf-8") as f:
115 f.write("I am not a profile directory")
115 f.write("I am not a profile directory")
116 profiles = list_profiles_in(td)
116 profiles = list_profiles_in(td)
117
117
118 # unicode normalization can turn u'ünicode' into u'u\0308nicode',
118 # unicode normalization can turn u'ünicode' into u'u\0308nicode',
119 # so only check for *nicode, and that creating a ProfileDir from the
119 # so only check for *nicode, and that creating a ProfileDir from the
120 # name remains valid
120 # name remains valid
121 found_unicode = False
121 found_unicode = False
122 for p in list(profiles):
122 for p in list(profiles):
123 if p.endswith('nicode'):
123 if p.endswith('nicode'):
124 pd = ProfileDir.find_profile_dir_by_name(td, p)
124 pd = ProfileDir.find_profile_dir_by_name(td, p)
125 profiles.remove(p)
125 profiles.remove(p)
126 found_unicode = True
126 found_unicode = True
127 break
127 break
128 if dec.unicode_paths:
128 if dec.unicode_paths:
129 assert found_unicode is True
129 assert found_unicode is True
130 assert set(profiles) == {"foo", "hello"}
130 assert set(profiles) == {"foo", "hello"}
131
131
132
132
133 def test_list_bundled_profiles():
133 def test_list_bundled_profiles():
134 # This variable will need to be updated when a new profile gets bundled
134 # This variable will need to be updated when a new profile gets bundled
135 bundled = sorted(list_bundled_profiles())
135 bundled = sorted(list_bundled_profiles())
136 assert bundled == []
136 assert bundled == []
137
137
138
138
139 def test_profile_create_ipython_dir():
139 def test_profile_create_ipython_dir():
140 """ipython profile create respects --ipython-dir"""
140 """ipython profile create respects --ipython-dir"""
141 with TemporaryDirectory() as td:
141 with TemporaryDirectory() as td:
142 getoutput(
142 getoutput(
143 [
143 [
144 sys.executable,
144 sys.executable,
145 "-m",
145 "-m",
146 "IPython",
146 "IPython",
147 "profile",
147 "profile",
148 "create",
148 "create",
149 "foo",
149 "foo",
150 "--ipython-dir=%s" % td,
150 "--ipython-dir=%s" % td,
151 ]
151 ]
152 )
152 )
153 profile_dir = Path(td) / "profile_foo"
153 profile_dir = Path(td) / "profile_foo"
154 assert Path(profile_dir).exists()
154 assert Path(profile_dir).exists()
155 ipython_config = profile_dir / "ipython_config.py"
155 ipython_config = profile_dir / "ipython_config.py"
156 assert Path(ipython_config).exists()
156 assert Path(ipython_config).exists()
@@ -1,612 +1,620 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for code execution (%run and related), which is particularly tricky.
2 """Tests for code execution (%run and related), which is particularly tricky.
3
3
4 Because of how %run manages namespaces, and the fact that we are trying here to
4 Because of how %run manages namespaces, and the fact that we are trying here to
5 verify subtle object deletion and reference counting issues, the %run tests
5 verify subtle object deletion and reference counting issues, the %run tests
6 will be kept in this separate file. This makes it easier to aggregate in one
6 will be kept in this separate file. This makes it easier to aggregate in one
7 place the tricks needed to handle it; most other magics are much easier to test
7 place the tricks needed to handle it; most other magics are much easier to test
8 and we do so in a common test_magic file.
8 and we do so in a common test_magic file.
9
9
10 Note that any test using `run -i` should make sure to do a `reset` afterwards,
10 Note that any test using `run -i` should make sure to do a `reset` afterwards,
11 as otherwise it may influence later tests.
11 as otherwise it may influence later tests.
12 """
12 """
13
13
14 # Copyright (c) IPython Development Team.
14 # Copyright (c) IPython Development Team.
15 # Distributed under the terms of the Modified BSD License.
15 # Distributed under the terms of the Modified BSD License.
16
16
17
17
18
18
19 import functools
19 import functools
20 import os
20 import os
21 import platform
21 import platform
22 from os.path import join as pjoin
22 from os.path import join as pjoin
23 import random
23 import random
24 import string
24 import string
25 import sys
25 import sys
26 import textwrap
26 import textwrap
27 import unittest
27 import unittest
28 from unittest.mock import patch
28 from unittest.mock import patch
29
29
30 import pytest
30 import pytest
31
31
32 from IPython.testing import decorators as dec
32 from IPython.testing import decorators as dec
33 from IPython.testing import tools as tt
33 from IPython.testing import tools as tt
34 from IPython.utils.io import capture_output
34 from IPython.utils.io import capture_output
35 from IPython.utils.tempdir import TemporaryDirectory
35 from IPython.utils.tempdir import TemporaryDirectory
36 from IPython.core import debugger
36 from IPython.core import debugger
37
37
38 def doctest_refbug():
38 def doctest_refbug():
39 """Very nasty problem with references held by multiple runs of a script.
39 """Very nasty problem with references held by multiple runs of a script.
40 See: https://github.com/ipython/ipython/issues/141
40 See: https://github.com/ipython/ipython/issues/141
41
41
42 In [1]: _ip.clear_main_mod_cache()
42 In [1]: _ip.clear_main_mod_cache()
43 # random
43 # random
44
44
45 In [2]: %run refbug
45 In [2]: %run refbug
46
46
47 In [3]: call_f()
47 In [3]: call_f()
48 lowercased: hello
48 lowercased: hello
49
49
50 In [4]: %run refbug
50 In [4]: %run refbug
51
51
52 In [5]: call_f()
52 In [5]: call_f()
53 lowercased: hello
53 lowercased: hello
54 lowercased: hello
54 lowercased: hello
55 """
55 """
56
56
57
57
58 def doctest_run_builtins():
58 def doctest_run_builtins():
59 r"""Check that %run doesn't damage __builtins__.
59 r"""Check that %run doesn't damage __builtins__.
60
60
61 In [1]: import tempfile
61 In [1]: import tempfile
62
62
63 In [2]: bid1 = id(__builtins__)
63 In [2]: bid1 = id(__builtins__)
64
64
65 In [3]: fname = tempfile.mkstemp('.py')[1]
65 In [3]: fname = tempfile.mkstemp('.py')[1]
66
66
67 In [3]: f = open(fname, 'w', encoding='utf-8')
67 In [3]: f = open(fname, 'w', encoding='utf-8')
68
68
69 In [4]: dummy= f.write('pass\n')
69 In [4]: dummy= f.write('pass\n')
70
70
71 In [5]: f.flush()
71 In [5]: f.flush()
72
72
73 In [6]: t1 = type(__builtins__)
73 In [6]: t1 = type(__builtins__)
74
74
75 In [7]: %run $fname
75 In [7]: %run $fname
76
76
77 In [7]: f.close()
77 In [7]: f.close()
78
78
79 In [8]: bid2 = id(__builtins__)
79 In [8]: bid2 = id(__builtins__)
80
80
81 In [9]: t2 = type(__builtins__)
81 In [9]: t2 = type(__builtins__)
82
82
83 In [10]: t1 == t2
83 In [10]: t1 == t2
84 Out[10]: True
84 Out[10]: True
85
85
86 In [10]: bid1 == bid2
86 In [10]: bid1 == bid2
87 Out[10]: True
87 Out[10]: True
88
88
89 In [12]: try:
89 In [12]: try:
90 ....: os.unlink(fname)
90 ....: os.unlink(fname)
91 ....: except:
91 ....: except:
92 ....: pass
92 ....: pass
93 ....:
93 ....:
94 """
94 """
95
95
96
96
97 def doctest_run_option_parser():
97 def doctest_run_option_parser():
98 r"""Test option parser in %run.
98 r"""Test option parser in %run.
99
99
100 In [1]: %run print_argv.py
100 In [1]: %run print_argv.py
101 []
101 []
102
102
103 In [2]: %run print_argv.py print*.py
103 In [2]: %run print_argv.py print*.py
104 ['print_argv.py']
104 ['print_argv.py']
105
105
106 In [3]: %run -G print_argv.py print*.py
106 In [3]: %run -G print_argv.py print*.py
107 ['print*.py']
107 ['print*.py']
108
108
109 """
109 """
110
110
111
111
112 @dec.skip_win32
112 @dec.skip_win32
113 def doctest_run_option_parser_for_posix():
113 def doctest_run_option_parser_for_posix():
114 r"""Test option parser in %run (Linux/OSX specific).
114 r"""Test option parser in %run (Linux/OSX specific).
115
115
116 You need double quote to escape glob in POSIX systems:
116 You need double quote to escape glob in POSIX systems:
117
117
118 In [1]: %run print_argv.py print\\*.py
118 In [1]: %run print_argv.py print\\*.py
119 ['print*.py']
119 ['print*.py']
120
120
121 You can't use quote to escape glob in POSIX systems:
121 You can't use quote to escape glob in POSIX systems:
122
122
123 In [2]: %run print_argv.py 'print*.py'
123 In [2]: %run print_argv.py 'print*.py'
124 ['print_argv.py']
124 ['print_argv.py']
125
125
126 """
126 """
127
127
128
128
129 doctest_run_option_parser_for_posix.__skip_doctest__ = sys.platform == "win32"
129 doctest_run_option_parser_for_posix.__skip_doctest__ = sys.platform == "win32"
130
130
131
131
132 @dec.skip_if_not_win32
132 @dec.skip_if_not_win32
133 def doctest_run_option_parser_for_windows():
133 def doctest_run_option_parser_for_windows():
134 r"""Test option parser in %run (Windows specific).
134 r"""Test option parser in %run (Windows specific).
135
135
136 In Windows, you can't escape ``*` `by backslash:
136 In Windows, you can't escape ``*` `by backslash:
137
137
138 In [1]: %run print_argv.py print\\*.py
138 In [1]: %run print_argv.py print\\*.py
139 ['print\\\\*.py']
139 ['print\\\\*.py']
140
140
141 You can use quote to escape glob:
141 You can use quote to escape glob:
142
142
143 In [2]: %run print_argv.py 'print*.py'
143 In [2]: %run print_argv.py 'print*.py'
144 ["'print*.py'"]
144 ["'print*.py'"]
145
145
146 """
146 """
147
147
148
148
149 doctest_run_option_parser_for_windows.__skip_doctest__ = sys.platform != "win32"
149 doctest_run_option_parser_for_windows.__skip_doctest__ = sys.platform != "win32"
150
150
151
151
152 def doctest_reset_del():
152 def doctest_reset_del():
153 """Test that resetting doesn't cause errors in __del__ methods.
153 """Test that resetting doesn't cause errors in __del__ methods.
154
154
155 In [2]: class A(object):
155 In [2]: class A(object):
156 ...: def __del__(self):
156 ...: def __del__(self):
157 ...: print(str("Hi"))
157 ...: print(str("Hi"))
158 ...:
158 ...:
159
159
160 In [3]: a = A()
160 In [3]: a = A()
161
161
162 In [4]: get_ipython().reset(); import gc; x = gc.collect(0)
162 In [4]: get_ipython().reset(); import gc; x = gc.collect(0)
163 Hi
163 Hi
164
164
165 In [5]: 1+1
165 In [5]: 1+1
166 Out[5]: 2
166 Out[5]: 2
167 """
167 """
168
168
169 # For some tests, it will be handy to organize them in a class with a common
169 # For some tests, it will be handy to organize them in a class with a common
170 # setup that makes a temp file
170 # setup that makes a temp file
171
171
172 class TestMagicRunPass(tt.TempFileMixin):
172 class TestMagicRunPass(tt.TempFileMixin):
173
173
174 def setUp(self):
174 def setUp(self):
175 content = "a = [1,2,3]\nb = 1"
175 content = "a = [1,2,3]\nb = 1"
176 self.mktmp(content)
176 self.mktmp(content)
177
177
178 def run_tmpfile(self):
178 def run_tmpfile(self):
179 _ip = get_ipython()
179 _ip = get_ipython()
180 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
180 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
181 # See below and ticket https://bugs.launchpad.net/bugs/366353
181 # See below and ticket https://bugs.launchpad.net/bugs/366353
182 _ip.magic('run %s' % self.fname)
182 _ip.magic('run %s' % self.fname)
183
183
184 def run_tmpfile_p(self):
184 def run_tmpfile_p(self):
185 _ip = get_ipython()
185 _ip = get_ipython()
186 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
186 # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
187 # See below and ticket https://bugs.launchpad.net/bugs/366353
187 # See below and ticket https://bugs.launchpad.net/bugs/366353
188 _ip.magic('run -p %s' % self.fname)
188 _ip.magic('run -p %s' % self.fname)
189
189
190 def test_builtins_id(self):
190 def test_builtins_id(self):
191 """Check that %run doesn't damage __builtins__ """
191 """Check that %run doesn't damage __builtins__ """
192 _ip = get_ipython()
192 _ip = get_ipython()
193 # Test that the id of __builtins__ is not modified by %run
193 # Test that the id of __builtins__ is not modified by %run
194 bid1 = id(_ip.user_ns['__builtins__'])
194 bid1 = id(_ip.user_ns['__builtins__'])
195 self.run_tmpfile()
195 self.run_tmpfile()
196 bid2 = id(_ip.user_ns['__builtins__'])
196 bid2 = id(_ip.user_ns['__builtins__'])
197 assert bid1 == bid2
197 assert bid1 == bid2
198
198
199 def test_builtins_type(self):
199 def test_builtins_type(self):
200 """Check that the type of __builtins__ doesn't change with %run.
200 """Check that the type of __builtins__ doesn't change with %run.
201
201
202 However, the above could pass if __builtins__ was already modified to
202 However, the above could pass if __builtins__ was already modified to
203 be a dict (it should be a module) by a previous use of %run. So we
203 be a dict (it should be a module) by a previous use of %run. So we
204 also check explicitly that it really is a module:
204 also check explicitly that it really is a module:
205 """
205 """
206 _ip = get_ipython()
206 _ip = get_ipython()
207 self.run_tmpfile()
207 self.run_tmpfile()
208 assert type(_ip.user_ns["__builtins__"]) == type(sys)
208 assert type(_ip.user_ns["__builtins__"]) == type(sys)
209
209
210 def test_run_profile(self):
210 def test_run_profile(self):
211 """Test that the option -p, which invokes the profiler, do not
211 """Test that the option -p, which invokes the profiler, do not
212 crash by invoking execfile"""
212 crash by invoking execfile"""
213 self.run_tmpfile_p()
213 self.run_tmpfile_p()
214
214
215 def test_run_debug_twice(self):
215 def test_run_debug_twice(self):
216 # https://github.com/ipython/ipython/issues/10028
216 # https://github.com/ipython/ipython/issues/10028
217 _ip = get_ipython()
217 _ip = get_ipython()
218 with tt.fake_input(['c']):
218 with tt.fake_input(['c']):
219 _ip.magic('run -d %s' % self.fname)
219 _ip.magic('run -d %s' % self.fname)
220 with tt.fake_input(['c']):
220 with tt.fake_input(['c']):
221 _ip.magic('run -d %s' % self.fname)
221 _ip.magic('run -d %s' % self.fname)
222
222
223 def test_run_debug_twice_with_breakpoint(self):
223 def test_run_debug_twice_with_breakpoint(self):
224 """Make a valid python temp file."""
224 """Make a valid python temp file."""
225 _ip = get_ipython()
225 _ip = get_ipython()
226 with tt.fake_input(['b 2', 'c', 'c']):
226 with tt.fake_input(['b 2', 'c', 'c']):
227 _ip.magic('run -d %s' % self.fname)
227 _ip.magic('run -d %s' % self.fname)
228
228
229 with tt.fake_input(['c']):
229 with tt.fake_input(['c']):
230 with tt.AssertNotPrints('KeyError'):
230 with tt.AssertNotPrints('KeyError'):
231 _ip.magic('run -d %s' % self.fname)
231 _ip.magic('run -d %s' % self.fname)
232
232
233
233
234 class TestMagicRunSimple(tt.TempFileMixin):
234 class TestMagicRunSimple(tt.TempFileMixin):
235
235
236 def test_simpledef(self):
236 def test_simpledef(self):
237 """Test that simple class definitions work."""
237 """Test that simple class definitions work."""
238 src = ("class foo: pass\n"
238 src = ("class foo: pass\n"
239 "def f(): return foo()")
239 "def f(): return foo()")
240 self.mktmp(src)
240 self.mktmp(src)
241 _ip.magic("run %s" % self.fname)
241 _ip.magic("run %s" % self.fname)
242 _ip.run_cell("t = isinstance(f(), foo)")
242 _ip.run_cell("t = isinstance(f(), foo)")
243 assert _ip.user_ns["t"] is True
243 assert _ip.user_ns["t"] is True
244
244
245 @pytest.mark.xfail(
245 @pytest.mark.xfail(
246 platform.python_implementation() == "PyPy",
246 platform.python_implementation() == "PyPy",
247 reason="expecting __del__ call on exit is unreliable and doesn't happen on PyPy",
247 reason="expecting __del__ call on exit is unreliable and doesn't happen on PyPy",
248 )
248 )
249 def test_obj_del(self):
249 def test_obj_del(self):
250 """Test that object's __del__ methods are called on exit."""
250 """Test that object's __del__ methods are called on exit."""
251 src = ("class A(object):\n"
251 src = ("class A(object):\n"
252 " def __del__(self):\n"
252 " def __del__(self):\n"
253 " print('object A deleted')\n"
253 " print('object A deleted')\n"
254 "a = A()\n")
254 "a = A()\n")
255 self.mktmp(src)
255 self.mktmp(src)
256 err = None
256 err = None
257 tt.ipexec_validate(self.fname, 'object A deleted', err)
257 tt.ipexec_validate(self.fname, 'object A deleted', err)
258
258
259 def test_aggressive_namespace_cleanup(self):
259 def test_aggressive_namespace_cleanup(self):
260 """Test that namespace cleanup is not too aggressive GH-238
260 """Test that namespace cleanup is not too aggressive GH-238
261
261
262 Returning from another run magic deletes the namespace"""
262 Returning from another run magic deletes the namespace"""
263 # see ticket https://github.com/ipython/ipython/issues/238
263 # see ticket https://github.com/ipython/ipython/issues/238
264
264
265 with tt.TempFileMixin() as empty:
265 with tt.TempFileMixin() as empty:
266 empty.mktmp("")
266 empty.mktmp("")
267 # On Windows, the filename will have \users in it, so we need to use the
267 # On Windows, the filename will have \users in it, so we need to use the
268 # repr so that the \u becomes \\u.
268 # repr so that the \u becomes \\u.
269 src = (
269 src = (
270 "ip = get_ipython()\n"
270 "ip = get_ipython()\n"
271 "for i in range(5):\n"
271 "for i in range(5):\n"
272 " try:\n"
272 " try:\n"
273 " ip.magic(%r)\n"
273 " ip.magic(%r)\n"
274 " except NameError as e:\n"
274 " except NameError as e:\n"
275 " print(i)\n"
275 " print(i)\n"
276 " break\n" % ("run " + empty.fname)
276 " break\n" % ("run " + empty.fname)
277 )
277 )
278 self.mktmp(src)
278 self.mktmp(src)
279 _ip.magic("run %s" % self.fname)
279 _ip.magic("run %s" % self.fname)
280 _ip.run_cell("ip == get_ipython()")
280 _ip.run_cell("ip == get_ipython()")
281 assert _ip.user_ns["i"] == 4
281 assert _ip.user_ns["i"] == 4
282
282
283 def test_run_second(self):
283 def test_run_second(self):
284 """Test that running a second file doesn't clobber the first, gh-3547"""
284 """Test that running a second file doesn't clobber the first, gh-3547"""
285 self.mktmp("avar = 1\n" "def afunc():\n" " return avar\n")
285 self.mktmp("avar = 1\n" "def afunc():\n" " return avar\n")
286
286
287 with tt.TempFileMixin() as empty:
287 with tt.TempFileMixin() as empty:
288 empty.mktmp("")
288 empty.mktmp("")
289
289
290 _ip.magic("run %s" % self.fname)
290 _ip.magic("run %s" % self.fname)
291 _ip.magic("run %s" % empty.fname)
291 _ip.magic("run %s" % empty.fname)
292 assert _ip.user_ns["afunc"]() == 1
292 assert _ip.user_ns["afunc"]() == 1
293
293
294 def test_tclass(self):
294 def test_tclass(self):
295 mydir = os.path.dirname(__file__)
295 mydir = os.path.dirname(__file__)
296 tc = os.path.join(mydir, "tclass")
296 tc = os.path.join(mydir, "tclass")
297 src = f"""\
297 src = f"""\
298 import gc
298 import gc
299 %run "{tc}" C-first
299 %run "{tc}" C-first
300 gc.collect(0)
300 gc.collect(0)
301 %run "{tc}" C-second
301 %run "{tc}" C-second
302 gc.collect(0)
302 gc.collect(0)
303 %run "{tc}" C-third
303 %run "{tc}" C-third
304 gc.collect(0)
304 gc.collect(0)
305 %reset -f
305 %reset -f
306 """
306 """
307 self.mktmp(src, ".ipy")
307 self.mktmp(src, ".ipy")
308 out = """\
308 out = """\
309 ARGV 1-: ['C-first']
309 ARGV 1-: ['C-first']
310 ARGV 1-: ['C-second']
310 ARGV 1-: ['C-second']
311 tclass.py: deleting object: C-first
311 tclass.py: deleting object: C-first
312 ARGV 1-: ['C-third']
312 ARGV 1-: ['C-third']
313 tclass.py: deleting object: C-second
313 tclass.py: deleting object: C-second
314 tclass.py: deleting object: C-third
314 tclass.py: deleting object: C-third
315 """
315 """
316 err = None
316 err = None
317 tt.ipexec_validate(self.fname, out, err)
317 tt.ipexec_validate(self.fname, out, err)
318
318
319 def test_run_i_after_reset(self):
319 def test_run_i_after_reset(self):
320 """Check that %run -i still works after %reset (gh-693)"""
320 """Check that %run -i still works after %reset (gh-693)"""
321 src = "yy = zz\n"
321 src = "yy = zz\n"
322 self.mktmp(src)
322 self.mktmp(src)
323 _ip.run_cell("zz = 23")
323 _ip.run_cell("zz = 23")
324 try:
324 try:
325 _ip.magic("run -i %s" % self.fname)
325 _ip.magic("run -i %s" % self.fname)
326 assert _ip.user_ns["yy"] == 23
326 assert _ip.user_ns["yy"] == 23
327 finally:
327 finally:
328 _ip.magic('reset -f')
328 _ip.magic('reset -f')
329
329
330 _ip.run_cell("zz = 23")
330 _ip.run_cell("zz = 23")
331 try:
331 try:
332 _ip.magic("run -i %s" % self.fname)
332 _ip.magic("run -i %s" % self.fname)
333 assert _ip.user_ns["yy"] == 23
333 assert _ip.user_ns["yy"] == 23
334 finally:
334 finally:
335 _ip.magic('reset -f')
335 _ip.magic('reset -f')
336
336
337 def test_unicode(self):
337 def test_unicode(self):
338 """Check that files in odd encodings are accepted."""
338 """Check that files in odd encodings are accepted."""
339 mydir = os.path.dirname(__file__)
339 mydir = os.path.dirname(__file__)
340 na = os.path.join(mydir, 'nonascii.py')
340 na = os.path.join(mydir, 'nonascii.py')
341 _ip.magic('run "%s"' % na)
341 _ip.magic('run "%s"' % na)
342 assert _ip.user_ns["u"] == "Ўт№Ф"
342 assert _ip.user_ns["u"] == "Ўт№Ф"
343
343
344 def test_run_py_file_attribute(self):
344 def test_run_py_file_attribute(self):
345 """Test handling of `__file__` attribute in `%run <file>.py`."""
345 """Test handling of `__file__` attribute in `%run <file>.py`."""
346 src = "t = __file__\n"
346 src = "t = __file__\n"
347 self.mktmp(src)
347 self.mktmp(src)
348 _missing = object()
348 _missing = object()
349 file1 = _ip.user_ns.get('__file__', _missing)
349 file1 = _ip.user_ns.get('__file__', _missing)
350 _ip.magic('run %s' % self.fname)
350 _ip.magic('run %s' % self.fname)
351 file2 = _ip.user_ns.get('__file__', _missing)
351 file2 = _ip.user_ns.get('__file__', _missing)
352
352
353 # Check that __file__ was equal to the filename in the script's
353 # Check that __file__ was equal to the filename in the script's
354 # namespace.
354 # namespace.
355 assert _ip.user_ns["t"] == self.fname
355 assert _ip.user_ns["t"] == self.fname
356
356
357 # Check that __file__ was not leaked back into user_ns.
357 # Check that __file__ was not leaked back into user_ns.
358 assert file1 == file2
358 assert file1 == file2
359
359
360 def test_run_ipy_file_attribute(self):
360 def test_run_ipy_file_attribute(self):
361 """Test handling of `__file__` attribute in `%run <file.ipy>`."""
361 """Test handling of `__file__` attribute in `%run <file.ipy>`."""
362 src = "t = __file__\n"
362 src = "t = __file__\n"
363 self.mktmp(src, ext='.ipy')
363 self.mktmp(src, ext='.ipy')
364 _missing = object()
364 _missing = object()
365 file1 = _ip.user_ns.get('__file__', _missing)
365 file1 = _ip.user_ns.get('__file__', _missing)
366 _ip.magic('run %s' % self.fname)
366 _ip.magic('run %s' % self.fname)
367 file2 = _ip.user_ns.get('__file__', _missing)
367 file2 = _ip.user_ns.get('__file__', _missing)
368
368
369 # Check that __file__ was equal to the filename in the script's
369 # Check that __file__ was equal to the filename in the script's
370 # namespace.
370 # namespace.
371 assert _ip.user_ns["t"] == self.fname
371 assert _ip.user_ns["t"] == self.fname
372
372
373 # Check that __file__ was not leaked back into user_ns.
373 # Check that __file__ was not leaked back into user_ns.
374 assert file1 == file2
374 assert file1 == file2
375
375
376 def test_run_formatting(self):
376 def test_run_formatting(self):
377 """ Test that %run -t -N<N> does not raise a TypeError for N > 1."""
377 """ Test that %run -t -N<N> does not raise a TypeError for N > 1."""
378 src = "pass"
378 src = "pass"
379 self.mktmp(src)
379 self.mktmp(src)
380 _ip.magic('run -t -N 1 %s' % self.fname)
380 _ip.magic('run -t -N 1 %s' % self.fname)
381 _ip.magic('run -t -N 10 %s' % self.fname)
381 _ip.magic('run -t -N 10 %s' % self.fname)
382
382
383 def test_ignore_sys_exit(self):
383 def test_ignore_sys_exit(self):
384 """Test the -e option to ignore sys.exit()"""
384 """Test the -e option to ignore sys.exit()"""
385 src = "import sys; sys.exit(1)"
385 src = "import sys; sys.exit(1)"
386 self.mktmp(src)
386 self.mktmp(src)
387 with tt.AssertPrints('SystemExit'):
387 with tt.AssertPrints('SystemExit'):
388 _ip.magic('run %s' % self.fname)
388 _ip.magic('run %s' % self.fname)
389
389
390 with tt.AssertNotPrints('SystemExit'):
390 with tt.AssertNotPrints('SystemExit'):
391 _ip.magic('run -e %s' % self.fname)
391 _ip.magic('run -e %s' % self.fname)
392
392
393 def test_run_nb(self):
393 def test_run_nb(self):
394 """Test %run notebook.ipynb"""
394 """Test %run notebook.ipynb"""
395 pytest.importorskip("nbformat")
395 pytest.importorskip("nbformat")
396 from nbformat import v4, writes
396 from nbformat import v4, writes
397 nb = v4.new_notebook(
397 nb = v4.new_notebook(
398 cells=[
398 cells=[
399 v4.new_markdown_cell("The Ultimate Question of Everything"),
399 v4.new_markdown_cell("The Ultimate Question of Everything"),
400 v4.new_code_cell("answer=42")
400 v4.new_code_cell("answer=42")
401 ]
401 ]
402 )
402 )
403 src = writes(nb, version=4)
403 src = writes(nb, version=4)
404 self.mktmp(src, ext='.ipynb')
404 self.mktmp(src, ext='.ipynb')
405
405
406 _ip.magic("run %s" % self.fname)
406 _ip.magic("run %s" % self.fname)
407
407
408 assert _ip.user_ns["answer"] == 42
408 assert _ip.user_ns["answer"] == 42
409
409
410 def test_run_nb_error(self):
410 def test_run_nb_error(self):
411 """Test %run notebook.ipynb error"""
411 """Test %run notebook.ipynb error"""
412 pytest.importorskip("nbformat")
412 pytest.importorskip("nbformat")
413 from nbformat import v4, writes
413 from nbformat import v4, writes
414 # %run when a file name isn't provided
414 # %run when a file name isn't provided
415 pytest.raises(Exception, _ip.magic, "run")
415 pytest.raises(Exception, _ip.magic, "run")
416
416
417 # %run when a file doesn't exist
417 # %run when a file doesn't exist
418 pytest.raises(Exception, _ip.magic, "run foobar.ipynb")
418 pytest.raises(Exception, _ip.magic, "run foobar.ipynb")
419
419
420 # %run on a notebook with an error
420 # %run on a notebook with an error
421 nb = v4.new_notebook(
421 nb = v4.new_notebook(
422 cells=[
422 cells=[
423 v4.new_code_cell("0/0")
423 v4.new_code_cell("0/0")
424 ]
424 ]
425 )
425 )
426 src = writes(nb, version=4)
426 src = writes(nb, version=4)
427 self.mktmp(src, ext='.ipynb')
427 self.mktmp(src, ext='.ipynb')
428 pytest.raises(Exception, _ip.magic, "run %s" % self.fname)
428 pytest.raises(Exception, _ip.magic, "run %s" % self.fname)
429
429
430 def test_file_options(self):
430 def test_file_options(self):
431 src = ('import sys\n'
431 src = ('import sys\n'
432 'a = " ".join(sys.argv[1:])\n')
432 'a = " ".join(sys.argv[1:])\n')
433 self.mktmp(src)
433 self.mktmp(src)
434 test_opts = "-x 3 --verbose"
434 test_opts = "-x 3 --verbose"
435 _ip.run_line_magic("run", "{0} {1}".format(self.fname, test_opts))
435 _ip.run_line_magic("run", "{0} {1}".format(self.fname, test_opts))
436 assert _ip.user_ns["a"] == test_opts
436 assert _ip.user_ns["a"] == test_opts
437
437
438
438
439 class TestMagicRunWithPackage(unittest.TestCase):
439 class TestMagicRunWithPackage(unittest.TestCase):
440
440
441 def writefile(self, name, content):
441 def writefile(self, name, content):
442 path = os.path.join(self.tempdir.name, name)
442 path = os.path.join(self.tempdir.name, name)
443 d = os.path.dirname(path)
443 d = os.path.dirname(path)
444 if not os.path.isdir(d):
444 if not os.path.isdir(d):
445 os.makedirs(d)
445 os.makedirs(d)
446 with open(path, 'w', encoding='utf-8') as f:
446 with open(path, "w", encoding="utf-8") as f:
447 f.write(textwrap.dedent(content))
447 f.write(textwrap.dedent(content))
448
448
449 def setUp(self):
449 def setUp(self):
450 self.package = package = 'tmp{0}'.format(''.join([random.choice(string.ascii_letters) for i in range(10)]))
450 self.package = package = 'tmp{0}'.format(''.join([random.choice(string.ascii_letters) for i in range(10)]))
451 """Temporary (probably) valid python package name."""
451 """Temporary (probably) valid python package name."""
452
452
453 self.value = int(random.random() * 10000)
453 self.value = int(random.random() * 10000)
454
454
455 self.tempdir = TemporaryDirectory()
455 self.tempdir = TemporaryDirectory()
456 self.__orig_cwd = os.getcwd()
456 self.__orig_cwd = os.getcwd()
457 sys.path.insert(0, self.tempdir.name)
457 sys.path.insert(0, self.tempdir.name)
458
458
459 self.writefile(os.path.join(package, '__init__.py'), '')
459 self.writefile(os.path.join(package, '__init__.py'), '')
460 self.writefile(os.path.join(package, 'sub.py'), """
460 self.writefile(os.path.join(package, 'sub.py'), """
461 x = {0!r}
461 x = {0!r}
462 """.format(self.value))
462 """.format(self.value))
463 self.writefile(os.path.join(package, 'relative.py'), """
463 self.writefile(os.path.join(package, 'relative.py'), """
464 from .sub import x
464 from .sub import x
465 """)
465 """)
466 self.writefile(os.path.join(package, 'absolute.py'), """
466 self.writefile(os.path.join(package, 'absolute.py'), """
467 from {0}.sub import x
467 from {0}.sub import x
468 """.format(package))
468 """.format(package))
469 self.writefile(os.path.join(package, 'args.py'), """
469 self.writefile(os.path.join(package, 'args.py'), """
470 import sys
470 import sys
471 a = " ".join(sys.argv[1:])
471 a = " ".join(sys.argv[1:])
472 """.format(package))
472 """.format(package))
473
473
474 def tearDown(self):
474 def tearDown(self):
475 os.chdir(self.__orig_cwd)
475 os.chdir(self.__orig_cwd)
476 sys.path[:] = [p for p in sys.path if p != self.tempdir.name]
476 sys.path[:] = [p for p in sys.path if p != self.tempdir.name]
477 self.tempdir.cleanup()
477 self.tempdir.cleanup()
478
478
479 def check_run_submodule(self, submodule, opts=''):
479 def check_run_submodule(self, submodule, opts=''):
480 _ip.user_ns.pop('x', None)
480 _ip.user_ns.pop('x', None)
481 _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts))
481 _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts))
482 self.assertEqual(_ip.user_ns['x'], self.value,
482 self.assertEqual(_ip.user_ns['x'], self.value,
483 'Variable `x` is not loaded from module `{0}`.'
483 'Variable `x` is not loaded from module `{0}`.'
484 .format(submodule))
484 .format(submodule))
485
485
486 def test_run_submodule_with_absolute_import(self):
486 def test_run_submodule_with_absolute_import(self):
487 self.check_run_submodule('absolute')
487 self.check_run_submodule('absolute')
488
488
489 def test_run_submodule_with_relative_import(self):
489 def test_run_submodule_with_relative_import(self):
490 """Run submodule that has a relative import statement (#2727)."""
490 """Run submodule that has a relative import statement (#2727)."""
491 self.check_run_submodule('relative')
491 self.check_run_submodule('relative')
492
492
493 def test_prun_submodule_with_absolute_import(self):
493 def test_prun_submodule_with_absolute_import(self):
494 self.check_run_submodule('absolute', '-p')
494 self.check_run_submodule('absolute', '-p')
495
495
496 def test_prun_submodule_with_relative_import(self):
496 def test_prun_submodule_with_relative_import(self):
497 self.check_run_submodule('relative', '-p')
497 self.check_run_submodule('relative', '-p')
498
498
499 def with_fake_debugger(func):
499 def with_fake_debugger(func):
500 @functools.wraps(func)
500 @functools.wraps(func)
501 def wrapper(*args, **kwds):
501 def wrapper(*args, **kwds):
502 with patch.object(debugger.Pdb, 'run', staticmethod(eval)):
502 with patch.object(debugger.Pdb, 'run', staticmethod(eval)):
503 return func(*args, **kwds)
503 return func(*args, **kwds)
504 return wrapper
504 return wrapper
505
505
506 @with_fake_debugger
506 @with_fake_debugger
507 def test_debug_run_submodule_with_absolute_import(self):
507 def test_debug_run_submodule_with_absolute_import(self):
508 self.check_run_submodule('absolute', '-d')
508 self.check_run_submodule('absolute', '-d')
509
509
510 @with_fake_debugger
510 @with_fake_debugger
511 def test_debug_run_submodule_with_relative_import(self):
511 def test_debug_run_submodule_with_relative_import(self):
512 self.check_run_submodule('relative', '-d')
512 self.check_run_submodule('relative', '-d')
513
513
514 def test_module_options(self):
514 def test_module_options(self):
515 _ip.user_ns.pop("a", None)
515 _ip.user_ns.pop("a", None)
516 test_opts = "-x abc -m test"
516 test_opts = "-x abc -m test"
517 _ip.run_line_magic("run", "-m {0}.args {1}".format(self.package, test_opts))
517 _ip.run_line_magic("run", "-m {0}.args {1}".format(self.package, test_opts))
518 assert _ip.user_ns["a"] == test_opts
518 assert _ip.user_ns["a"] == test_opts
519
519
520 def test_module_options_with_separator(self):
520 def test_module_options_with_separator(self):
521 _ip.user_ns.pop("a", None)
521 _ip.user_ns.pop("a", None)
522 test_opts = "-x abc -m test"
522 test_opts = "-x abc -m test"
523 _ip.run_line_magic("run", "-m {0}.args -- {1}".format(self.package, test_opts))
523 _ip.run_line_magic("run", "-m {0}.args -- {1}".format(self.package, test_opts))
524 assert _ip.user_ns["a"] == test_opts
524 assert _ip.user_ns["a"] == test_opts
525
525
526
526
527 def test_run__name__():
527 def test_run__name__():
528 with TemporaryDirectory() as td:
528 with TemporaryDirectory() as td:
529 path = pjoin(td, 'foo.py')
529 path = pjoin(td, "foo.py")
530 with open(path, 'w', encoding='utf-8') as f:
530 with open(path, "w", encoding="utf-8") as f:
531 f.write("q = __name__")
531 f.write("q = __name__")
532
532
533 _ip.user_ns.pop("q", None)
533 _ip.user_ns.pop("q", None)
534 _ip.magic("run {}".format(path))
534 _ip.magic("run {}".format(path))
535 assert _ip.user_ns.pop("q") == "__main__"
535 assert _ip.user_ns.pop("q") == "__main__"
536
536
537 _ip.magic("run -n {}".format(path))
537 _ip.magic("run -n {}".format(path))
538 assert _ip.user_ns.pop("q") == "foo"
538 assert _ip.user_ns.pop("q") == "foo"
539
539
540 try:
540 try:
541 _ip.magic("run -i -n {}".format(path))
541 _ip.magic("run -i -n {}".format(path))
542 assert _ip.user_ns.pop("q") == "foo"
542 assert _ip.user_ns.pop("q") == "foo"
543 finally:
543 finally:
544 _ip.magic('reset -f')
544 _ip.magic('reset -f')
545
545
546
546
547 def test_run_tb():
547 def test_run_tb():
548 """Test traceback offset in %run"""
548 """Test traceback offset in %run"""
549 with TemporaryDirectory() as td:
549 with TemporaryDirectory() as td:
550 path = pjoin(td, 'foo.py')
550 path = pjoin(td, "foo.py")
551 with open(path, 'w', encoding='utf-8') as f:
551 with open(path, "w", encoding="utf-8") as f:
552 f.write('\n'.join([
552 f.write(
553 "def foo():",
553 "\n".join(
554 " return bar()",
554 [
555 "def bar():",
555 "def foo():",
556 " raise RuntimeError('hello!')",
556 " return bar()",
557 "foo()",
557 "def bar():",
558 ]))
558 " raise RuntimeError('hello!')",
559 "foo()",
560 ]
561 )
562 )
559 with capture_output() as io:
563 with capture_output() as io:
560 _ip.magic('run {}'.format(path))
564 _ip.magic('run {}'.format(path))
561 out = io.stdout
565 out = io.stdout
562 assert "execfile" not in out
566 assert "execfile" not in out
563 assert "RuntimeError" in out
567 assert "RuntimeError" in out
564 assert out.count("---->") == 3
568 assert out.count("---->") == 3
565 del ip.user_ns['bar']
569 del ip.user_ns['bar']
566 del ip.user_ns['foo']
570 del ip.user_ns['foo']
567
571
568
572
569 def test_multiprocessing_run():
573 def test_multiprocessing_run():
570 """Set we can run mutiprocesgin without messing up up main namespace
574 """Set we can run mutiprocesgin without messing up up main namespace
571
575
572 Note that import `nose.tools as nt` mdify the value s
576 Note that import `nose.tools as nt` mdify the value s
573 sys.module['__mp_main__'] so we need to temporarily set it to None to test
577 sys.module['__mp_main__'] so we need to temporarily set it to None to test
574 the issue.
578 the issue.
575 """
579 """
576 with TemporaryDirectory() as td:
580 with TemporaryDirectory() as td:
577 mpm = sys.modules.get('__mp_main__')
581 mpm = sys.modules.get('__mp_main__')
578 sys.modules['__mp_main__'] = None
582 sys.modules['__mp_main__'] = None
579 try:
583 try:
580 path = pjoin(td, 'test.py')
584 path = pjoin(td, "test.py")
581 with open(path, 'w', encoding='utf-8') as f:
585 with open(path, "w", encoding="utf-8") as f:
582 f.write("import multiprocessing\nprint('hoy')")
586 f.write("import multiprocessing\nprint('hoy')")
583 with capture_output() as io:
587 with capture_output() as io:
584 _ip.run_line_magic('run', path)
588 _ip.run_line_magic('run', path)
585 _ip.run_cell("i_m_undefined")
589 _ip.run_cell("i_m_undefined")
586 out = io.stdout
590 out = io.stdout
587 assert "hoy" in out
591 assert "hoy" in out
588 assert "AttributeError" not in out
592 assert "AttributeError" not in out
589 assert "NameError" in out
593 assert "NameError" in out
590 assert out.count("---->") == 1
594 assert out.count("---->") == 1
591 except:
595 except:
592 raise
596 raise
593 finally:
597 finally:
594 sys.modules['__mp_main__'] = mpm
598 sys.modules['__mp_main__'] = mpm
595
599
596
600
597 def test_script_tb():
601 def test_script_tb():
598 """Test traceback offset in `ipython script.py`"""
602 """Test traceback offset in `ipython script.py`"""
599 with TemporaryDirectory() as td:
603 with TemporaryDirectory() as td:
600 path = pjoin(td, 'foo.py')
604 path = pjoin(td, "foo.py")
601 with open(path, 'w', encoding='utf-8') as f:
605 with open(path, "w", encoding="utf-8") as f:
602 f.write('\n'.join([
606 f.write(
603 "def foo():",
607 "\n".join(
604 " return bar()",
608 [
605 "def bar():",
609 "def foo():",
606 " raise RuntimeError('hello!')",
610 " return bar()",
607 "foo()",
611 "def bar():",
608 ]))
612 " raise RuntimeError('hello!')",
613 "foo()",
614 ]
615 )
616 )
609 out, err = tt.ipexec(path)
617 out, err = tt.ipexec(path)
610 assert "execfile" not in out
618 assert "execfile" not in out
611 assert "RuntimeError" in out
619 assert "RuntimeError" in out
612 assert out.count("---->") == 3
620 assert out.count("---->") == 3
@@ -1,410 +1,410 b''
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 logging
5 import logging
6 import platform
6 import platform
7 import re
7 import re
8 import sys
8 import sys
9 import os.path
9 import os.path
10 from textwrap import dedent
10 from textwrap import dedent
11 import traceback
11 import traceback
12 import unittest
12 import unittest
13
13
14 from IPython.core.ultratb import ColorTB, VerboseTB
14 from IPython.core.ultratb import ColorTB, VerboseTB
15
15
16
16
17 from IPython.testing import tools as tt
17 from IPython.testing import tools as tt
18 from IPython.testing.decorators import onlyif_unicode_paths
18 from IPython.testing.decorators import onlyif_unicode_paths
19 from IPython.utils.syspathcontext import prepended_to_syspath
19 from IPython.utils.syspathcontext import prepended_to_syspath
20 from IPython.utils.tempdir import TemporaryDirectory
20 from IPython.utils.tempdir import TemporaryDirectory
21
21
22 file_1 = """1
22 file_1 = """1
23 2
23 2
24 3
24 3
25 def f():
25 def f():
26 1/0
26 1/0
27 """
27 """
28
28
29 file_2 = """def f():
29 file_2 = """def f():
30 1/0
30 1/0
31 """
31 """
32
32
33
33
34 def recursionlimit(frames):
34 def recursionlimit(frames):
35 """
35 """
36 decorator to set the recursion limit temporarily
36 decorator to set the recursion limit temporarily
37 """
37 """
38
38
39 def inner(test_function):
39 def inner(test_function):
40 def wrapper(*args, **kwargs):
40 def wrapper(*args, **kwargs):
41 rl = sys.getrecursionlimit()
41 rl = sys.getrecursionlimit()
42 sys.setrecursionlimit(frames)
42 sys.setrecursionlimit(frames)
43 try:
43 try:
44 return test_function(*args, **kwargs)
44 return test_function(*args, **kwargs)
45 finally:
45 finally:
46 sys.setrecursionlimit(rl)
46 sys.setrecursionlimit(rl)
47
47
48 return wrapper
48 return wrapper
49
49
50 return inner
50 return inner
51
51
52
52
53 class ChangedPyFileTest(unittest.TestCase):
53 class ChangedPyFileTest(unittest.TestCase):
54 def test_changing_py_file(self):
54 def test_changing_py_file(self):
55 """Traceback produced if the line where the error occurred is missing?
55 """Traceback produced if the line where the error occurred is missing?
56
56
57 https://github.com/ipython/ipython/issues/1456
57 https://github.com/ipython/ipython/issues/1456
58 """
58 """
59 with TemporaryDirectory() as td:
59 with TemporaryDirectory() as td:
60 fname = os.path.join(td, "foo.py")
60 fname = os.path.join(td, "foo.py")
61 with open(fname, "w", encoding='utf-8') as f:
61 with open(fname, "w", encoding="utf-8") as f:
62 f.write(file_1)
62 f.write(file_1)
63
63
64 with prepended_to_syspath(td):
64 with prepended_to_syspath(td):
65 ip.run_cell("import foo")
65 ip.run_cell("import foo")
66
66
67 with tt.AssertPrints("ZeroDivisionError"):
67 with tt.AssertPrints("ZeroDivisionError"):
68 ip.run_cell("foo.f()")
68 ip.run_cell("foo.f()")
69
69
70 # Make the file shorter, so the line of the error is missing.
70 # Make the file shorter, so the line of the error is missing.
71 with open(fname, "w", encoding='utf-8') as f:
71 with open(fname, "w", encoding="utf-8") as f:
72 f.write(file_2)
72 f.write(file_2)
73
73
74 # For some reason, this was failing on the *second* call after
74 # For some reason, this was failing on the *second* call after
75 # changing the file, so we call f() twice.
75 # changing the file, so we call f() twice.
76 with tt.AssertNotPrints("Internal Python error", channel='stderr'):
76 with tt.AssertNotPrints("Internal Python error", channel='stderr'):
77 with tt.AssertPrints("ZeroDivisionError"):
77 with tt.AssertPrints("ZeroDivisionError"):
78 ip.run_cell("foo.f()")
78 ip.run_cell("foo.f()")
79 with tt.AssertPrints("ZeroDivisionError"):
79 with tt.AssertPrints("ZeroDivisionError"):
80 ip.run_cell("foo.f()")
80 ip.run_cell("foo.f()")
81
81
82 iso_8859_5_file = u'''# coding: iso-8859-5
82 iso_8859_5_file = u'''# coding: iso-8859-5
83
83
84 def fail():
84 def fail():
85 """дбИЖ"""
85 """дбИЖ"""
86 1/0 # дбИЖ
86 1/0 # дбИЖ
87 '''
87 '''
88
88
89 class NonAsciiTest(unittest.TestCase):
89 class NonAsciiTest(unittest.TestCase):
90 @onlyif_unicode_paths
90 @onlyif_unicode_paths
91 def test_nonascii_path(self):
91 def test_nonascii_path(self):
92 # Non-ascii directory name as well.
92 # Non-ascii directory name as well.
93 with TemporaryDirectory(suffix=u'é') as td:
93 with TemporaryDirectory(suffix=u'é') as td:
94 fname = os.path.join(td, u"fooé.py")
94 fname = os.path.join(td, u"fooé.py")
95 with open(fname, "w", encoding='utf-8') as f:
95 with open(fname, "w", encoding="utf-8") as f:
96 f.write(file_1)
96 f.write(file_1)
97
97
98 with prepended_to_syspath(td):
98 with prepended_to_syspath(td):
99 ip.run_cell("import foo")
99 ip.run_cell("import foo")
100
100
101 with tt.AssertPrints("ZeroDivisionError"):
101 with tt.AssertPrints("ZeroDivisionError"):
102 ip.run_cell("foo.f()")
102 ip.run_cell("foo.f()")
103
103
104 def test_iso8859_5(self):
104 def test_iso8859_5(self):
105 with TemporaryDirectory() as td:
105 with TemporaryDirectory() as td:
106 fname = os.path.join(td, 'dfghjkl.py')
106 fname = os.path.join(td, 'dfghjkl.py')
107
107
108 with io.open(fname, 'w', encoding='iso-8859-5') as f:
108 with io.open(fname, 'w', encoding='iso-8859-5') as f:
109 f.write(iso_8859_5_file)
109 f.write(iso_8859_5_file)
110
110
111 with prepended_to_syspath(td):
111 with prepended_to_syspath(td):
112 ip.run_cell("from dfghjkl import fail")
112 ip.run_cell("from dfghjkl import fail")
113
113
114 with tt.AssertPrints("ZeroDivisionError"):
114 with tt.AssertPrints("ZeroDivisionError"):
115 with tt.AssertPrints(u'дбИЖ', suppress=False):
115 with tt.AssertPrints(u'дбИЖ', suppress=False):
116 ip.run_cell('fail()')
116 ip.run_cell('fail()')
117
117
118 def test_nonascii_msg(self):
118 def test_nonascii_msg(self):
119 cell = u"raise Exception('é')"
119 cell = u"raise Exception('é')"
120 expected = u"Exception('é')"
120 expected = u"Exception('é')"
121 ip.run_cell("%xmode plain")
121 ip.run_cell("%xmode plain")
122 with tt.AssertPrints(expected):
122 with tt.AssertPrints(expected):
123 ip.run_cell(cell)
123 ip.run_cell(cell)
124
124
125 ip.run_cell("%xmode verbose")
125 ip.run_cell("%xmode verbose")
126 with tt.AssertPrints(expected):
126 with tt.AssertPrints(expected):
127 ip.run_cell(cell)
127 ip.run_cell(cell)
128
128
129 ip.run_cell("%xmode context")
129 ip.run_cell("%xmode context")
130 with tt.AssertPrints(expected):
130 with tt.AssertPrints(expected):
131 ip.run_cell(cell)
131 ip.run_cell(cell)
132
132
133 ip.run_cell("%xmode minimal")
133 ip.run_cell("%xmode minimal")
134 with tt.AssertPrints(u"Exception: é"):
134 with tt.AssertPrints(u"Exception: é"):
135 ip.run_cell(cell)
135 ip.run_cell(cell)
136
136
137 # Put this back into Context mode for later tests.
137 # Put this back into Context mode for later tests.
138 ip.run_cell("%xmode context")
138 ip.run_cell("%xmode context")
139
139
140 class NestedGenExprTestCase(unittest.TestCase):
140 class NestedGenExprTestCase(unittest.TestCase):
141 """
141 """
142 Regression test for the following issues:
142 Regression test for the following issues:
143 https://github.com/ipython/ipython/issues/8293
143 https://github.com/ipython/ipython/issues/8293
144 https://github.com/ipython/ipython/issues/8205
144 https://github.com/ipython/ipython/issues/8205
145 """
145 """
146 def test_nested_genexpr(self):
146 def test_nested_genexpr(self):
147 code = dedent(
147 code = dedent(
148 """\
148 """\
149 class SpecificException(Exception):
149 class SpecificException(Exception):
150 pass
150 pass
151
151
152 def foo(x):
152 def foo(x):
153 raise SpecificException("Success!")
153 raise SpecificException("Success!")
154
154
155 sum(sum(foo(x) for _ in [0]) for x in [0])
155 sum(sum(foo(x) for _ in [0]) for x in [0])
156 """
156 """
157 )
157 )
158 with tt.AssertPrints('SpecificException: Success!', suppress=False):
158 with tt.AssertPrints('SpecificException: Success!', suppress=False):
159 ip.run_cell(code)
159 ip.run_cell(code)
160
160
161
161
162 indentationerror_file = """if True:
162 indentationerror_file = """if True:
163 zoon()
163 zoon()
164 """
164 """
165
165
166 class IndentationErrorTest(unittest.TestCase):
166 class IndentationErrorTest(unittest.TestCase):
167 def test_indentationerror_shows_line(self):
167 def test_indentationerror_shows_line(self):
168 # See issue gh-2398
168 # See issue gh-2398
169 with tt.AssertPrints("IndentationError"):
169 with tt.AssertPrints("IndentationError"):
170 with tt.AssertPrints("zoon()", suppress=False):
170 with tt.AssertPrints("zoon()", suppress=False):
171 ip.run_cell(indentationerror_file)
171 ip.run_cell(indentationerror_file)
172
172
173 with TemporaryDirectory() as td:
173 with TemporaryDirectory() as td:
174 fname = os.path.join(td, "foo.py")
174 fname = os.path.join(td, "foo.py")
175 with open(fname, "w", encoding='utf-8') as f:
175 with open(fname, "w", encoding="utf-8") as f:
176 f.write(indentationerror_file)
176 f.write(indentationerror_file)
177
177
178 with tt.AssertPrints("IndentationError"):
178 with tt.AssertPrints("IndentationError"):
179 with tt.AssertPrints("zoon()", suppress=False):
179 with tt.AssertPrints("zoon()", suppress=False):
180 ip.magic('run %s' % fname)
180 ip.magic('run %s' % fname)
181
181
182 se_file_1 = """1
182 se_file_1 = """1
183 2
183 2
184 7/
184 7/
185 """
185 """
186
186
187 se_file_2 = """7/
187 se_file_2 = """7/
188 """
188 """
189
189
190 class SyntaxErrorTest(unittest.TestCase):
190 class SyntaxErrorTest(unittest.TestCase):
191
191
192 def test_syntaxerror_no_stacktrace_at_compile_time(self):
192 def test_syntaxerror_no_stacktrace_at_compile_time(self):
193 syntax_error_at_compile_time = """
193 syntax_error_at_compile_time = """
194 def foo():
194 def foo():
195 ..
195 ..
196 """
196 """
197 with tt.AssertPrints("SyntaxError"):
197 with tt.AssertPrints("SyntaxError"):
198 ip.run_cell(syntax_error_at_compile_time)
198 ip.run_cell(syntax_error_at_compile_time)
199
199
200 with tt.AssertNotPrints("foo()"):
200 with tt.AssertNotPrints("foo()"):
201 ip.run_cell(syntax_error_at_compile_time)
201 ip.run_cell(syntax_error_at_compile_time)
202
202
203 def test_syntaxerror_stacktrace_when_running_compiled_code(self):
203 def test_syntaxerror_stacktrace_when_running_compiled_code(self):
204 syntax_error_at_runtime = """
204 syntax_error_at_runtime = """
205 def foo():
205 def foo():
206 eval("..")
206 eval("..")
207
207
208 def bar():
208 def bar():
209 foo()
209 foo()
210
210
211 bar()
211 bar()
212 """
212 """
213 with tt.AssertPrints("SyntaxError"):
213 with tt.AssertPrints("SyntaxError"):
214 ip.run_cell(syntax_error_at_runtime)
214 ip.run_cell(syntax_error_at_runtime)
215 # Assert syntax error during runtime generate stacktrace
215 # Assert syntax error during runtime generate stacktrace
216 with tt.AssertPrints(["foo()", "bar()"]):
216 with tt.AssertPrints(["foo()", "bar()"]):
217 ip.run_cell(syntax_error_at_runtime)
217 ip.run_cell(syntax_error_at_runtime)
218 del ip.user_ns['bar']
218 del ip.user_ns['bar']
219 del ip.user_ns['foo']
219 del ip.user_ns['foo']
220
220
221 def test_changing_py_file(self):
221 def test_changing_py_file(self):
222 with TemporaryDirectory() as td:
222 with TemporaryDirectory() as td:
223 fname = os.path.join(td, "foo.py")
223 fname = os.path.join(td, "foo.py")
224 with open(fname, 'w', encoding='utf-8') as f:
224 with open(fname, "w", encoding="utf-8") as f:
225 f.write(se_file_1)
225 f.write(se_file_1)
226
226
227 with tt.AssertPrints(["7/", "SyntaxError"]):
227 with tt.AssertPrints(["7/", "SyntaxError"]):
228 ip.magic("run " + fname)
228 ip.magic("run " + fname)
229
229
230 # Modify the file
230 # Modify the file
231 with open(fname, 'w', encoding='utf-8') as f:
231 with open(fname, "w", encoding="utf-8") as f:
232 f.write(se_file_2)
232 f.write(se_file_2)
233
233
234 # The SyntaxError should point to the correct line
234 # The SyntaxError should point to the correct line
235 with tt.AssertPrints(["7/", "SyntaxError"]):
235 with tt.AssertPrints(["7/", "SyntaxError"]):
236 ip.magic("run " + fname)
236 ip.magic("run " + fname)
237
237
238 def test_non_syntaxerror(self):
238 def test_non_syntaxerror(self):
239 # SyntaxTB may be called with an error other than a SyntaxError
239 # SyntaxTB may be called with an error other than a SyntaxError
240 # See e.g. gh-4361
240 # See e.g. gh-4361
241 try:
241 try:
242 raise ValueError('QWERTY')
242 raise ValueError('QWERTY')
243 except ValueError:
243 except ValueError:
244 with tt.AssertPrints('QWERTY'):
244 with tt.AssertPrints('QWERTY'):
245 ip.showsyntaxerror()
245 ip.showsyntaxerror()
246
246
247 import sys
247 import sys
248
248
249 if sys.version_info < (3, 9) and platform.python_implementation() != "PyPy":
249 if sys.version_info < (3, 9) and platform.python_implementation() != "PyPy":
250 """
250 """
251 New 3.9 Pgen Parser does not raise Memory error, except on failed malloc.
251 New 3.9 Pgen Parser does not raise Memory error, except on failed malloc.
252 """
252 """
253 class MemoryErrorTest(unittest.TestCase):
253 class MemoryErrorTest(unittest.TestCase):
254 def test_memoryerror(self):
254 def test_memoryerror(self):
255 memoryerror_code = "(" * 200 + ")" * 200
255 memoryerror_code = "(" * 200 + ")" * 200
256 with tt.AssertPrints("MemoryError"):
256 with tt.AssertPrints("MemoryError"):
257 ip.run_cell(memoryerror_code)
257 ip.run_cell(memoryerror_code)
258
258
259
259
260 class Python3ChainedExceptionsTest(unittest.TestCase):
260 class Python3ChainedExceptionsTest(unittest.TestCase):
261 DIRECT_CAUSE_ERROR_CODE = """
261 DIRECT_CAUSE_ERROR_CODE = """
262 try:
262 try:
263 x = 1 + 2
263 x = 1 + 2
264 print(not_defined_here)
264 print(not_defined_here)
265 except Exception as e:
265 except Exception as e:
266 x += 55
266 x += 55
267 x - 1
267 x - 1
268 y = {}
268 y = {}
269 raise KeyError('uh') from e
269 raise KeyError('uh') from e
270 """
270 """
271
271
272 EXCEPTION_DURING_HANDLING_CODE = """
272 EXCEPTION_DURING_HANDLING_CODE = """
273 try:
273 try:
274 x = 1 + 2
274 x = 1 + 2
275 print(not_defined_here)
275 print(not_defined_here)
276 except Exception as e:
276 except Exception as e:
277 x += 55
277 x += 55
278 x - 1
278 x - 1
279 y = {}
279 y = {}
280 raise KeyError('uh')
280 raise KeyError('uh')
281 """
281 """
282
282
283 SUPPRESS_CHAINING_CODE = """
283 SUPPRESS_CHAINING_CODE = """
284 try:
284 try:
285 1/0
285 1/0
286 except Exception:
286 except Exception:
287 raise ValueError("Yikes") from None
287 raise ValueError("Yikes") from None
288 """
288 """
289
289
290 def test_direct_cause_error(self):
290 def test_direct_cause_error(self):
291 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
291 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
292 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
292 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
293
293
294 def test_exception_during_handling_error(self):
294 def test_exception_during_handling_error(self):
295 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
295 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
296 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
296 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
297
297
298 def test_suppress_exception_chaining(self):
298 def test_suppress_exception_chaining(self):
299 with tt.AssertNotPrints("ZeroDivisionError"), \
299 with tt.AssertNotPrints("ZeroDivisionError"), \
300 tt.AssertPrints("ValueError", suppress=False):
300 tt.AssertPrints("ValueError", suppress=False):
301 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
301 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
302
302
303 def test_plain_direct_cause_error(self):
303 def test_plain_direct_cause_error(self):
304 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
304 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
305 ip.run_cell("%xmode Plain")
305 ip.run_cell("%xmode Plain")
306 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
306 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
307 ip.run_cell("%xmode Verbose")
307 ip.run_cell("%xmode Verbose")
308
308
309 def test_plain_exception_during_handling_error(self):
309 def test_plain_exception_during_handling_error(self):
310 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
310 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
311 ip.run_cell("%xmode Plain")
311 ip.run_cell("%xmode Plain")
312 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
312 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
313 ip.run_cell("%xmode Verbose")
313 ip.run_cell("%xmode Verbose")
314
314
315 def test_plain_suppress_exception_chaining(self):
315 def test_plain_suppress_exception_chaining(self):
316 with tt.AssertNotPrints("ZeroDivisionError"), \
316 with tt.AssertNotPrints("ZeroDivisionError"), \
317 tt.AssertPrints("ValueError", suppress=False):
317 tt.AssertPrints("ValueError", suppress=False):
318 ip.run_cell("%xmode Plain")
318 ip.run_cell("%xmode Plain")
319 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
319 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
320 ip.run_cell("%xmode Verbose")
320 ip.run_cell("%xmode Verbose")
321
321
322
322
323 class RecursionTest(unittest.TestCase):
323 class RecursionTest(unittest.TestCase):
324 DEFINITIONS = """
324 DEFINITIONS = """
325 def non_recurs():
325 def non_recurs():
326 1/0
326 1/0
327
327
328 def r1():
328 def r1():
329 r1()
329 r1()
330
330
331 def r3a():
331 def r3a():
332 r3b()
332 r3b()
333
333
334 def r3b():
334 def r3b():
335 r3c()
335 r3c()
336
336
337 def r3c():
337 def r3c():
338 r3a()
338 r3a()
339
339
340 def r3o1():
340 def r3o1():
341 r3a()
341 r3a()
342
342
343 def r3o2():
343 def r3o2():
344 r3o1()
344 r3o1()
345 """
345 """
346 def setUp(self):
346 def setUp(self):
347 ip.run_cell(self.DEFINITIONS)
347 ip.run_cell(self.DEFINITIONS)
348
348
349 def test_no_recursion(self):
349 def test_no_recursion(self):
350 with tt.AssertNotPrints("skipping similar frames"):
350 with tt.AssertNotPrints("skipping similar frames"):
351 ip.run_cell("non_recurs()")
351 ip.run_cell("non_recurs()")
352
352
353 @recursionlimit(200)
353 @recursionlimit(200)
354 def test_recursion_one_frame(self):
354 def test_recursion_one_frame(self):
355 with tt.AssertPrints(re.compile(
355 with tt.AssertPrints(re.compile(
356 r"\[\.\.\. skipping similar frames: r1 at line 5 \(\d{2,3} times\)\]")
356 r"\[\.\.\. skipping similar frames: r1 at line 5 \(\d{2,3} times\)\]")
357 ):
357 ):
358 ip.run_cell("r1()")
358 ip.run_cell("r1()")
359
359
360 @recursionlimit(160)
360 @recursionlimit(160)
361 def test_recursion_three_frames(self):
361 def test_recursion_three_frames(self):
362 with tt.AssertPrints("[... skipping similar frames: "), \
362 with tt.AssertPrints("[... skipping similar frames: "), \
363 tt.AssertPrints(re.compile(r"r3a at line 8 \(\d{2} times\)"), suppress=False), \
363 tt.AssertPrints(re.compile(r"r3a at line 8 \(\d{2} times\)"), suppress=False), \
364 tt.AssertPrints(re.compile(r"r3b at line 11 \(\d{2} times\)"), suppress=False), \
364 tt.AssertPrints(re.compile(r"r3b at line 11 \(\d{2} times\)"), suppress=False), \
365 tt.AssertPrints(re.compile(r"r3c at line 14 \(\d{2} times\)"), suppress=False):
365 tt.AssertPrints(re.compile(r"r3c at line 14 \(\d{2} times\)"), suppress=False):
366 ip.run_cell("r3o2()")
366 ip.run_cell("r3o2()")
367
367
368
368
369 #----------------------------------------------------------------------------
369 #----------------------------------------------------------------------------
370
370
371 # module testing (minimal)
371 # module testing (minimal)
372 def test_handlers():
372 def test_handlers():
373 def spam(c, d_e):
373 def spam(c, d_e):
374 (d, e) = d_e
374 (d, e) = d_e
375 x = c + d
375 x = c + d
376 y = c * d
376 y = c * d
377 foo(x, y)
377 foo(x, y)
378
378
379 def foo(a, b, bar=1):
379 def foo(a, b, bar=1):
380 eggs(a, b + bar)
380 eggs(a, b + bar)
381
381
382 def eggs(f, g, z=globals()):
382 def eggs(f, g, z=globals()):
383 h = f + g
383 h = f + g
384 i = f - g
384 i = f - g
385 return h / i
385 return h / i
386
386
387 buff = io.StringIO()
387 buff = io.StringIO()
388
388
389 buff.write('')
389 buff.write('')
390 buff.write('*** Before ***')
390 buff.write('*** Before ***')
391 try:
391 try:
392 buff.write(spam(1, (2, 3)))
392 buff.write(spam(1, (2, 3)))
393 except:
393 except:
394 traceback.print_exc(file=buff)
394 traceback.print_exc(file=buff)
395
395
396 handler = ColorTB(ostream=buff)
396 handler = ColorTB(ostream=buff)
397 buff.write('*** ColorTB ***')
397 buff.write('*** ColorTB ***')
398 try:
398 try:
399 buff.write(spam(1, (2, 3)))
399 buff.write(spam(1, (2, 3)))
400 except:
400 except:
401 handler(*sys.exc_info())
401 handler(*sys.exc_info())
402 buff.write('')
402 buff.write('')
403
403
404 handler = VerboseTB(ostream=buff)
404 handler = VerboseTB(ostream=buff)
405 buff.write('*** VerboseTB ***')
405 buff.write('*** VerboseTB ***')
406 try:
406 try:
407 buff.write(spam(1, (2, 3)))
407 buff.write(spam(1, (2, 3)))
408 except:
408 except:
409 handler(*sys.exc_info())
409 handler(*sys.exc_info())
410 buff.write('')
410 buff.write('')
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now