##// END OF EJS Templates
Merge pull request #13741 from Carreau/ruff...
Matthias Bussonnier -
r27785:ae0dfcd9 merge
parent child Browse files
Show More
@@ -1,156 +1,155 b''
1 """
1 """
2 IPython: tools for interactive and parallel computing in Python.
2 IPython: tools for interactive and parallel computing in Python.
3
3
4 https://ipython.org
4 https://ipython.org
5 """
5 """
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (c) 2008-2011, IPython Development Team.
7 # Copyright (c) 2008-2011, IPython Development Team.
8 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
8 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
9 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
9 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
10 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
10 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
11 #
11 #
12 # Distributed under the terms of the Modified BSD License.
12 # Distributed under the terms of the Modified BSD License.
13 #
13 #
14 # The full license is in the file COPYING.txt, distributed with this software.
14 # The full license is in the file COPYING.txt, distributed with this software.
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 import os
22 import sys
21 import sys
23
22
24 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
25 # Setup everything
24 # Setup everything
26 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
27
26
28 # Don't forget to also update setup.py when this changes!
27 # Don't forget to also update setup.py when this changes!
29 if sys.version_info < (3, 8):
28 if sys.version_info < (3, 8):
30 raise ImportError(
29 raise ImportError(
31 """
30 """
32 IPython 8+ supports Python 3.8 and above, following NEP 29.
31 IPython 8+ supports Python 3.8 and above, following NEP 29.
33 When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
32 When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
34 Python 3.3 and 3.4 were supported up to IPython 6.x.
33 Python 3.3 and 3.4 were supported up to IPython 6.x.
35 Python 3.5 was supported with IPython 7.0 to 7.9.
34 Python 3.5 was supported with IPython 7.0 to 7.9.
36 Python 3.6 was supported with IPython up to 7.16.
35 Python 3.6 was supported with IPython up to 7.16.
37 Python 3.7 was still supported with the 7.x branch.
36 Python 3.7 was still supported with the 7.x branch.
38
37
39 See IPython `README.rst` file for more information:
38 See IPython `README.rst` file for more information:
40
39
41 https://github.com/ipython/ipython/blob/main/README.rst
40 https://github.com/ipython/ipython/blob/main/README.rst
42
41
43 """
42 """
44 )
43 )
45
44
46 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
47 # Setup the top level names
46 # Setup the top level names
48 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
49
48
50 from .core.getipython import get_ipython
49 from .core.getipython import get_ipython
51 from .core import release
50 from .core import release
52 from .core.application import Application
51 from .core.application import Application
53 from .terminal.embed import embed
52 from .terminal.embed import embed
54
53
55 from .core.interactiveshell import InteractiveShell
54 from .core.interactiveshell import InteractiveShell
56 from .utils.sysinfo import sys_info
55 from .utils.sysinfo import sys_info
57 from .utils.frame import extract_module_locals
56 from .utils.frame import extract_module_locals
58
57
59 # Release data
58 # Release data
60 __author__ = '%s <%s>' % (release.author, release.author_email)
59 __author__ = '%s <%s>' % (release.author, release.author_email)
61 __license__ = release.license
60 __license__ = release.license
62 __version__ = release.version
61 __version__ = release.version
63 version_info = release.version_info
62 version_info = release.version_info
64 # list of CVEs that should have been patched in this release.
63 # list of CVEs that should have been patched in this release.
65 # this is informational and should not be relied upon.
64 # this is informational and should not be relied upon.
66 __patched_cves__ = {"CVE-2022-21699"}
65 __patched_cves__ = {"CVE-2022-21699"}
67
66
68
67
69 def embed_kernel(module=None, local_ns=None, **kwargs):
68 def embed_kernel(module=None, local_ns=None, **kwargs):
70 """Embed and start an IPython kernel in a given scope.
69 """Embed and start an IPython kernel in a given scope.
71
70
72 If you don't want the kernel to initialize the namespace
71 If you don't want the kernel to initialize the namespace
73 from the scope of the surrounding function,
72 from the scope of the surrounding function,
74 and/or you want to load full IPython configuration,
73 and/or you want to load full IPython configuration,
75 you probably want `IPython.start_kernel()` instead.
74 you probably want `IPython.start_kernel()` instead.
76
75
77 Parameters
76 Parameters
78 ----------
77 ----------
79 module : types.ModuleType, optional
78 module : types.ModuleType, optional
80 The module to load into IPython globals (default: caller)
79 The module to load into IPython globals (default: caller)
81 local_ns : dict, optional
80 local_ns : dict, optional
82 The namespace to load into IPython user namespace (default: caller)
81 The namespace to load into IPython user namespace (default: caller)
83 **kwargs : various, optional
82 **kwargs : various, optional
84 Further keyword args are relayed to the IPKernelApp constructor,
83 Further keyword args are relayed to the IPKernelApp constructor,
85 allowing configuration of the Kernel. Will only have an effect
84 allowing configuration of the Kernel. Will only have an effect
86 on the first embed_kernel call for a given process.
85 on the first embed_kernel call for a given process.
87 """
86 """
88
87
89 (caller_module, caller_locals) = extract_module_locals(1)
88 (caller_module, caller_locals) = extract_module_locals(1)
90 if module is None:
89 if module is None:
91 module = caller_module
90 module = caller_module
92 if local_ns is None:
91 if local_ns is None:
93 local_ns = caller_locals
92 local_ns = caller_locals
94
93
95 # Only import .zmq when we really need it
94 # Only import .zmq when we really need it
96 from ipykernel.embed import embed_kernel as real_embed_kernel
95 from ipykernel.embed import embed_kernel as real_embed_kernel
97 real_embed_kernel(module=module, local_ns=local_ns, **kwargs)
96 real_embed_kernel(module=module, local_ns=local_ns, **kwargs)
98
97
99 def start_ipython(argv=None, **kwargs):
98 def start_ipython(argv=None, **kwargs):
100 """Launch a normal IPython instance (as opposed to embedded)
99 """Launch a normal IPython instance (as opposed to embedded)
101
100
102 `IPython.embed()` puts a shell in a particular calling scope,
101 `IPython.embed()` puts a shell in a particular calling scope,
103 such as a function or method for debugging purposes,
102 such as a function or method for debugging purposes,
104 which is often not desirable.
103 which is often not desirable.
105
104
106 `start_ipython()` does full, regular IPython initialization,
105 `start_ipython()` does full, regular IPython initialization,
107 including loading startup files, configuration, etc.
106 including loading startup files, configuration, etc.
108 much of which is skipped by `embed()`.
107 much of which is skipped by `embed()`.
109
108
110 This is a public API method, and will survive implementation changes.
109 This is a public API method, and will survive implementation changes.
111
110
112 Parameters
111 Parameters
113 ----------
112 ----------
114 argv : list or None, optional
113 argv : list or None, optional
115 If unspecified or None, IPython will parse command-line options from sys.argv.
114 If unspecified or None, IPython will parse command-line options from sys.argv.
116 To prevent any command-line parsing, pass an empty list: `argv=[]`.
115 To prevent any command-line parsing, pass an empty list: `argv=[]`.
117 user_ns : dict, optional
116 user_ns : dict, optional
118 specify this dictionary to initialize the IPython user namespace with particular values.
117 specify this dictionary to initialize the IPython user namespace with particular values.
119 **kwargs : various, optional
118 **kwargs : various, optional
120 Any other kwargs will be passed to the Application constructor,
119 Any other kwargs will be passed to the Application constructor,
121 such as `config`.
120 such as `config`.
122 """
121 """
123 from IPython.terminal.ipapp import launch_new_instance
122 from IPython.terminal.ipapp import launch_new_instance
124 return launch_new_instance(argv=argv, **kwargs)
123 return launch_new_instance(argv=argv, **kwargs)
125
124
126 def start_kernel(argv=None, **kwargs):
125 def start_kernel(argv=None, **kwargs):
127 """Launch a normal IPython kernel instance (as opposed to embedded)
126 """Launch a normal IPython kernel instance (as opposed to embedded)
128
127
129 `IPython.embed_kernel()` puts a shell in a particular calling scope,
128 `IPython.embed_kernel()` puts a shell in a particular calling scope,
130 such as a function or method for debugging purposes,
129 such as a function or method for debugging purposes,
131 which is often not desirable.
130 which is often not desirable.
132
131
133 `start_kernel()` does full, regular IPython initialization,
132 `start_kernel()` does full, regular IPython initialization,
134 including loading startup files, configuration, etc.
133 including loading startup files, configuration, etc.
135 much of which is skipped by `embed()`.
134 much of which is skipped by `embed()`.
136
135
137 Parameters
136 Parameters
138 ----------
137 ----------
139 argv : list or None, optional
138 argv : list or None, optional
140 If unspecified or None, IPython will parse command-line options from sys.argv.
139 If unspecified or None, IPython will parse command-line options from sys.argv.
141 To prevent any command-line parsing, pass an empty list: `argv=[]`.
140 To prevent any command-line parsing, pass an empty list: `argv=[]`.
142 user_ns : dict, optional
141 user_ns : dict, optional
143 specify this dictionary to initialize the IPython user namespace with particular values.
142 specify this dictionary to initialize the IPython user namespace with particular values.
144 **kwargs : various, optional
143 **kwargs : various, optional
145 Any other kwargs will be passed to the Application constructor,
144 Any other kwargs will be passed to the Application constructor,
146 such as `config`.
145 such as `config`.
147 """
146 """
148 import warnings
147 import warnings
149
148
150 warnings.warn(
149 warnings.warn(
151 "start_kernel is deprecated since IPython 8.0, use from `ipykernel.kernelapp.launch_new_instance`",
150 "start_kernel is deprecated since IPython 8.0, use from `ipykernel.kernelapp.launch_new_instance`",
152 DeprecationWarning,
151 DeprecationWarning,
153 stacklevel=2,
152 stacklevel=2,
154 )
153 )
155 from ipykernel.kernelapp import launch_new_instance
154 from ipykernel.kernelapp import launch_new_instance
156 return launch_new_instance(argv=argv, **kwargs)
155 return launch_new_instance(argv=argv, **kwargs)
@@ -1,490 +1,489 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
18 import logging
17 import logging
19 import os
18 import os
20 import shutil
19 import shutil
21 import sys
20 import sys
22
21
23 from pathlib import Path
22 from pathlib import Path
24
23
25 from traitlets.config.application import Application, catch_config_error
24 from traitlets.config.application import Application, catch_config_error
26 from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader
25 from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader
27 from IPython.core import release, crashhandler
26 from IPython.core import release, crashhandler
28 from IPython.core.profiledir import ProfileDir, ProfileDirError
27 from IPython.core.profiledir import ProfileDir, ProfileDirError
29 from IPython.paths import get_ipython_dir, get_ipython_package_dir
28 from IPython.paths import get_ipython_dir, get_ipython_package_dir
30 from IPython.utils.path import ensure_dir_exists
29 from IPython.utils.path import ensure_dir_exists
31 from traitlets import (
30 from traitlets import (
32 List, Unicode, Type, Bool, Set, Instance, Undefined,
31 List, Unicode, Type, Bool, Set, Instance, Undefined,
33 default, observe,
32 default, observe,
34 )
33 )
35
34
36 if os.name == "nt":
35 if os.name == "nt":
37 programdata = os.environ.get("PROGRAMDATA", None)
36 programdata = os.environ.get("PROGRAMDATA", None)
38 if programdata is not None:
37 if programdata is not None:
39 SYSTEM_CONFIG_DIRS = [str(Path(programdata) / "ipython")]
38 SYSTEM_CONFIG_DIRS = [str(Path(programdata) / "ipython")]
40 else: # PROGRAMDATA is not defined by default on XP.
39 else: # PROGRAMDATA is not defined by default on XP.
41 SYSTEM_CONFIG_DIRS = []
40 SYSTEM_CONFIG_DIRS = []
42 else:
41 else:
43 SYSTEM_CONFIG_DIRS = [
42 SYSTEM_CONFIG_DIRS = [
44 "/usr/local/etc/ipython",
43 "/usr/local/etc/ipython",
45 "/etc/ipython",
44 "/etc/ipython",
46 ]
45 ]
47
46
48
47
49 ENV_CONFIG_DIRS = []
48 ENV_CONFIG_DIRS = []
50 _env_config_dir = os.path.join(sys.prefix, 'etc', 'ipython')
49 _env_config_dir = os.path.join(sys.prefix, 'etc', 'ipython')
51 if _env_config_dir not in SYSTEM_CONFIG_DIRS:
50 if _env_config_dir not in SYSTEM_CONFIG_DIRS:
52 # only add ENV_CONFIG if sys.prefix is not already included
51 # only add ENV_CONFIG if sys.prefix is not already included
53 ENV_CONFIG_DIRS.append(_env_config_dir)
52 ENV_CONFIG_DIRS.append(_env_config_dir)
54
53
55
54
56 _envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS')
55 _envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS')
57 if _envvar in {None, ''}:
56 if _envvar in {None, ''}:
58 IPYTHON_SUPPRESS_CONFIG_ERRORS = None
57 IPYTHON_SUPPRESS_CONFIG_ERRORS = None
59 else:
58 else:
60 if _envvar.lower() in {'1','true'}:
59 if _envvar.lower() in {'1','true'}:
61 IPYTHON_SUPPRESS_CONFIG_ERRORS = True
60 IPYTHON_SUPPRESS_CONFIG_ERRORS = True
62 elif _envvar.lower() in {'0','false'} :
61 elif _envvar.lower() in {'0','false'} :
63 IPYTHON_SUPPRESS_CONFIG_ERRORS = False
62 IPYTHON_SUPPRESS_CONFIG_ERRORS = False
64 else:
63 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 )
64 sys.exit("Unsupported value for environment variable: 'IPYTHON_SUPPRESS_CONFIG_ERRORS' is set to '%s' which is none of {'0', '1', 'false', 'true', ''}."% _envvar )
66
65
67 # aliases and flags
66 # aliases and flags
68
67
69 base_aliases = {}
68 base_aliases = {}
70 if isinstance(Application.aliases, dict):
69 if isinstance(Application.aliases, dict):
71 # traitlets 5
70 # traitlets 5
72 base_aliases.update(Application.aliases)
71 base_aliases.update(Application.aliases)
73 base_aliases.update(
72 base_aliases.update(
74 {
73 {
75 "profile-dir": "ProfileDir.location",
74 "profile-dir": "ProfileDir.location",
76 "profile": "BaseIPythonApplication.profile",
75 "profile": "BaseIPythonApplication.profile",
77 "ipython-dir": "BaseIPythonApplication.ipython_dir",
76 "ipython-dir": "BaseIPythonApplication.ipython_dir",
78 "log-level": "Application.log_level",
77 "log-level": "Application.log_level",
79 "config": "BaseIPythonApplication.extra_config_file",
78 "config": "BaseIPythonApplication.extra_config_file",
80 }
79 }
81 )
80 )
82
81
83 base_flags = dict()
82 base_flags = dict()
84 if isinstance(Application.flags, dict):
83 if isinstance(Application.flags, dict):
85 # traitlets 5
84 # traitlets 5
86 base_flags.update(Application.flags)
85 base_flags.update(Application.flags)
87 base_flags.update(
86 base_flags.update(
88 dict(
87 dict(
89 debug=(
88 debug=(
90 {"Application": {"log_level": logging.DEBUG}},
89 {"Application": {"log_level": logging.DEBUG}},
91 "set log level to logging.DEBUG (maximize logging output)",
90 "set log level to logging.DEBUG (maximize logging output)",
92 ),
91 ),
93 quiet=(
92 quiet=(
94 {"Application": {"log_level": logging.CRITICAL}},
93 {"Application": {"log_level": logging.CRITICAL}},
95 "set log level to logging.CRITICAL (minimize logging output)",
94 "set log level to logging.CRITICAL (minimize logging output)",
96 ),
95 ),
97 init=(
96 init=(
98 {
97 {
99 "BaseIPythonApplication": {
98 "BaseIPythonApplication": {
100 "copy_config_files": True,
99 "copy_config_files": True,
101 "auto_create": True,
100 "auto_create": True,
102 }
101 }
103 },
102 },
104 """Initialize profile with default config files. This is equivalent
103 """Initialize profile with default config files. This is equivalent
105 to running `ipython profile create <profile>` prior to startup.
104 to running `ipython profile create <profile>` prior to startup.
106 """,
105 """,
107 ),
106 ),
108 )
107 )
109 )
108 )
110
109
111
110
112 class ProfileAwareConfigLoader(PyFileConfigLoader):
111 class ProfileAwareConfigLoader(PyFileConfigLoader):
113 """A Python file config loader that is aware of IPython profiles."""
112 """A Python file config loader that is aware of IPython profiles."""
114 def load_subconfig(self, fname, path=None, profile=None):
113 def load_subconfig(self, fname, path=None, profile=None):
115 if profile is not None:
114 if profile is not None:
116 try:
115 try:
117 profile_dir = ProfileDir.find_profile_dir_by_name(
116 profile_dir = ProfileDir.find_profile_dir_by_name(
118 get_ipython_dir(),
117 get_ipython_dir(),
119 profile,
118 profile,
120 )
119 )
121 except ProfileDirError:
120 except ProfileDirError:
122 return
121 return
123 path = profile_dir.location
122 path = profile_dir.location
124 return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path)
123 return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path)
125
124
126 class BaseIPythonApplication(Application):
125 class BaseIPythonApplication(Application):
127
126
128 name = u'ipython'
127 name = u'ipython'
129 description = Unicode(u'IPython: an enhanced interactive Python shell.')
128 description = Unicode(u'IPython: an enhanced interactive Python shell.')
130 version = Unicode(release.version)
129 version = Unicode(release.version)
131
130
132 aliases = base_aliases
131 aliases = base_aliases
133 flags = base_flags
132 flags = base_flags
134 classes = List([ProfileDir])
133 classes = List([ProfileDir])
135
134
136 # enable `load_subconfig('cfg.py', profile='name')`
135 # enable `load_subconfig('cfg.py', profile='name')`
137 python_config_loader_class = ProfileAwareConfigLoader
136 python_config_loader_class = ProfileAwareConfigLoader
138
137
139 # Track whether the config_file has changed,
138 # Track whether the config_file has changed,
140 # because some logic happens only if we aren't using the default.
139 # because some logic happens only if we aren't using the default.
141 config_file_specified = Set()
140 config_file_specified = Set()
142
141
143 config_file_name = Unicode()
142 config_file_name = Unicode()
144 @default('config_file_name')
143 @default('config_file_name')
145 def _config_file_name_default(self):
144 def _config_file_name_default(self):
146 return self.name.replace('-','_') + u'_config.py'
145 return self.name.replace('-','_') + u'_config.py'
147 @observe('config_file_name')
146 @observe('config_file_name')
148 def _config_file_name_changed(self, change):
147 def _config_file_name_changed(self, change):
149 if change['new'] != change['old']:
148 if change['new'] != change['old']:
150 self.config_file_specified.add(change['new'])
149 self.config_file_specified.add(change['new'])
151
150
152 # The directory that contains IPython's builtin profiles.
151 # The directory that contains IPython's builtin profiles.
153 builtin_profile_dir = Unicode(
152 builtin_profile_dir = Unicode(
154 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
153 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
155 )
154 )
156
155
157 config_file_paths = List(Unicode())
156 config_file_paths = List(Unicode())
158 @default('config_file_paths')
157 @default('config_file_paths')
159 def _config_file_paths_default(self):
158 def _config_file_paths_default(self):
160 return []
159 return []
161
160
162 extra_config_file = Unicode(
161 extra_config_file = Unicode(
163 help="""Path to an extra config file to load.
162 help="""Path to an extra config file to load.
164
163
165 If specified, load this config file in addition to any other IPython config.
164 If specified, load this config file in addition to any other IPython config.
166 """).tag(config=True)
165 """).tag(config=True)
167 @observe('extra_config_file')
166 @observe('extra_config_file')
168 def _extra_config_file_changed(self, change):
167 def _extra_config_file_changed(self, change):
169 old = change['old']
168 old = change['old']
170 new = change['new']
169 new = change['new']
171 try:
170 try:
172 self.config_files.remove(old)
171 self.config_files.remove(old)
173 except ValueError:
172 except ValueError:
174 pass
173 pass
175 self.config_file_specified.add(new)
174 self.config_file_specified.add(new)
176 self.config_files.append(new)
175 self.config_files.append(new)
177
176
178 profile = Unicode(u'default',
177 profile = Unicode(u'default',
179 help="""The IPython profile to use."""
178 help="""The IPython profile to use."""
180 ).tag(config=True)
179 ).tag(config=True)
181
180
182 @observe('profile')
181 @observe('profile')
183 def _profile_changed(self, change):
182 def _profile_changed(self, change):
184 self.builtin_profile_dir = os.path.join(
183 self.builtin_profile_dir = os.path.join(
185 get_ipython_package_dir(), u'config', u'profile', change['new']
184 get_ipython_package_dir(), u'config', u'profile', change['new']
186 )
185 )
187
186
188 add_ipython_dir_to_sys_path = Bool(
187 add_ipython_dir_to_sys_path = Bool(
189 False,
188 False,
190 """Should the IPython profile directory be added to sys path ?
189 """Should the IPython profile directory be added to sys path ?
191
190
192 This option was non-existing before IPython 8.0, and ipython_dir was added to
191 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
192 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,
193 baggage from when pip did not exist. This now default to false,
195 but can be set to true for legacy reasons.
194 but can be set to true for legacy reasons.
196 """,
195 """,
197 ).tag(config=True)
196 ).tag(config=True)
198
197
199 ipython_dir = Unicode(
198 ipython_dir = Unicode(
200 help="""
199 help="""
201 The name of the IPython directory. This directory is used for logging
200 The name of the IPython directory. This directory is used for logging
202 configuration (through profiles), history storage, etc. The default
201 configuration (through profiles), history storage, etc. The default
203 is usually $HOME/.ipython. This option can also be specified through
202 is usually $HOME/.ipython. This option can also be specified through
204 the environment variable IPYTHONDIR.
203 the environment variable IPYTHONDIR.
205 """
204 """
206 ).tag(config=True)
205 ).tag(config=True)
207 @default('ipython_dir')
206 @default('ipython_dir')
208 def _ipython_dir_default(self):
207 def _ipython_dir_default(self):
209 d = get_ipython_dir()
208 d = get_ipython_dir()
210 self._ipython_dir_changed({
209 self._ipython_dir_changed({
211 'name': 'ipython_dir',
210 'name': 'ipython_dir',
212 'old': d,
211 'old': d,
213 'new': d,
212 'new': d,
214 })
213 })
215 return d
214 return d
216
215
217 _in_init_profile_dir = False
216 _in_init_profile_dir = False
218 profile_dir = Instance(ProfileDir, allow_none=True)
217 profile_dir = Instance(ProfileDir, allow_none=True)
219 @default('profile_dir')
218 @default('profile_dir')
220 def _profile_dir_default(self):
219 def _profile_dir_default(self):
221 # avoid recursion
220 # avoid recursion
222 if self._in_init_profile_dir:
221 if self._in_init_profile_dir:
223 return
222 return
224 # profile_dir requested early, force initialization
223 # profile_dir requested early, force initialization
225 self.init_profile_dir()
224 self.init_profile_dir()
226 return self.profile_dir
225 return self.profile_dir
227
226
228 overwrite = Bool(False,
227 overwrite = Bool(False,
229 help="""Whether to overwrite existing config files when copying"""
228 help="""Whether to overwrite existing config files when copying"""
230 ).tag(config=True)
229 ).tag(config=True)
231 auto_create = Bool(False,
230 auto_create = Bool(False,
232 help="""Whether to create profile dir if it doesn't exist"""
231 help="""Whether to create profile dir if it doesn't exist"""
233 ).tag(config=True)
232 ).tag(config=True)
234
233
235 config_files = List(Unicode())
234 config_files = List(Unicode())
236 @default('config_files')
235 @default('config_files')
237 def _config_files_default(self):
236 def _config_files_default(self):
238 return [self.config_file_name]
237 return [self.config_file_name]
239
238
240 copy_config_files = Bool(False,
239 copy_config_files = Bool(False,
241 help="""Whether to install the default config files into the profile dir.
240 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
241 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,
242 profile, then they will be staged into the new directory. Otherwise,
244 default config files will be automatically generated.
243 default config files will be automatically generated.
245 """).tag(config=True)
244 """).tag(config=True)
246
245
247 verbose_crash = Bool(False,
246 verbose_crash = Bool(False,
248 help="""Create a massive crash report when IPython encounters what may be an
247 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
248 internal error. The default is to append a short message to the
250 usual traceback""").tag(config=True)
249 usual traceback""").tag(config=True)
251
250
252 # The class to use as the crash handler.
251 # The class to use as the crash handler.
253 crash_handler_class = Type(crashhandler.CrashHandler)
252 crash_handler_class = Type(crashhandler.CrashHandler)
254
253
255 @catch_config_error
254 @catch_config_error
256 def __init__(self, **kwargs):
255 def __init__(self, **kwargs):
257 super(BaseIPythonApplication, self).__init__(**kwargs)
256 super(BaseIPythonApplication, self).__init__(**kwargs)
258 # ensure current working directory exists
257 # ensure current working directory exists
259 try:
258 try:
260 os.getcwd()
259 os.getcwd()
261 except:
260 except:
262 # exit if cwd doesn't exist
261 # exit if cwd doesn't exist
263 self.log.error("Current working directory doesn't exist.")
262 self.log.error("Current working directory doesn't exist.")
264 self.exit(1)
263 self.exit(1)
265
264
266 #-------------------------------------------------------------------------
265 #-------------------------------------------------------------------------
267 # Various stages of Application creation
266 # Various stages of Application creation
268 #-------------------------------------------------------------------------
267 #-------------------------------------------------------------------------
269
268
270 def init_crash_handler(self):
269 def init_crash_handler(self):
271 """Create a crash handler, typically setting sys.excepthook to it."""
270 """Create a crash handler, typically setting sys.excepthook to it."""
272 self.crash_handler = self.crash_handler_class(self)
271 self.crash_handler = self.crash_handler_class(self)
273 sys.excepthook = self.excepthook
272 sys.excepthook = self.excepthook
274 def unset_crashhandler():
273 def unset_crashhandler():
275 sys.excepthook = sys.__excepthook__
274 sys.excepthook = sys.__excepthook__
276 atexit.register(unset_crashhandler)
275 atexit.register(unset_crashhandler)
277
276
278 def excepthook(self, etype, evalue, tb):
277 def excepthook(self, etype, evalue, tb):
279 """this is sys.excepthook after init_crashhandler
278 """this is sys.excepthook after init_crashhandler
280
279
281 set self.verbose_crash=True to use our full crashhandler, instead of
280 set self.verbose_crash=True to use our full crashhandler, instead of
282 a regular traceback with a short message (crash_handler_lite)
281 a regular traceback with a short message (crash_handler_lite)
283 """
282 """
284
283
285 if self.verbose_crash:
284 if self.verbose_crash:
286 return self.crash_handler(etype, evalue, tb)
285 return self.crash_handler(etype, evalue, tb)
287 else:
286 else:
288 return crashhandler.crash_handler_lite(etype, evalue, tb)
287 return crashhandler.crash_handler_lite(etype, evalue, tb)
289
288
290 @observe('ipython_dir')
289 @observe('ipython_dir')
291 def _ipython_dir_changed(self, change):
290 def _ipython_dir_changed(self, change):
292 old = change['old']
291 old = change['old']
293 new = change['new']
292 new = change['new']
294 if old is not Undefined:
293 if old is not Undefined:
295 str_old = os.path.abspath(old)
294 str_old = os.path.abspath(old)
296 if str_old in sys.path:
295 if str_old in sys.path:
297 sys.path.remove(str_old)
296 sys.path.remove(str_old)
298 if self.add_ipython_dir_to_sys_path:
297 if self.add_ipython_dir_to_sys_path:
299 str_path = os.path.abspath(new)
298 str_path = os.path.abspath(new)
300 sys.path.append(str_path)
299 sys.path.append(str_path)
301 ensure_dir_exists(new)
300 ensure_dir_exists(new)
302 readme = os.path.join(new, "README")
301 readme = os.path.join(new, "README")
303 readme_src = os.path.join(
302 readme_src = os.path.join(
304 get_ipython_package_dir(), "config", "profile", "README"
303 get_ipython_package_dir(), "config", "profile", "README"
305 )
304 )
306 if not os.path.exists(readme) and os.path.exists(readme_src):
305 if not os.path.exists(readme) and os.path.exists(readme_src):
307 shutil.copy(readme_src, readme)
306 shutil.copy(readme_src, readme)
308 for d in ("extensions", "nbextensions"):
307 for d in ("extensions", "nbextensions"):
309 path = os.path.join(new, d)
308 path = os.path.join(new, d)
310 try:
309 try:
311 ensure_dir_exists(path)
310 ensure_dir_exists(path)
312 except OSError as e:
311 except OSError as e:
313 # this will not be EEXIST
312 # this will not be EEXIST
314 self.log.error("couldn't create path %s: %s", path, e)
313 self.log.error("couldn't create path %s: %s", path, e)
315 self.log.debug("IPYTHONDIR set to: %s" % new)
314 self.log.debug("IPYTHONDIR set to: %s" % new)
316
315
317 def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS):
316 def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS):
318 """Load the config file.
317 """Load the config file.
319
318
320 By default, errors in loading config are handled, and a warning
319 By default, errors in loading config are handled, and a warning
321 printed on screen. For testing, the suppress_errors option is set
320 printed on screen. For testing, the suppress_errors option is set
322 to False, so errors will make tests fail.
321 to False, so errors will make tests fail.
323
322
324 `suppress_errors` default value is to be `None` in which case the
323 `suppress_errors` default value is to be `None` in which case the
325 behavior default to the one of `traitlets.Application`.
324 behavior default to the one of `traitlets.Application`.
326
325
327 The default value can be set :
326 The default value can be set :
328 - to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive).
327 - 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).
328 - 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.
329 - to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset.
331
330
332 Any other value are invalid, and will make IPython exit with a non-zero return code.
331 Any other value are invalid, and will make IPython exit with a non-zero return code.
333 """
332 """
334
333
335
334
336 self.log.debug("Searching path %s for config files", self.config_file_paths)
335 self.log.debug("Searching path %s for config files", self.config_file_paths)
337 base_config = 'ipython_config.py'
336 base_config = 'ipython_config.py'
338 self.log.debug("Attempting to load config file: %s" %
337 self.log.debug("Attempting to load config file: %s" %
339 base_config)
338 base_config)
340 try:
339 try:
341 if suppress_errors is not None:
340 if suppress_errors is not None:
342 old_value = Application.raise_config_file_errors
341 old_value = Application.raise_config_file_errors
343 Application.raise_config_file_errors = not suppress_errors;
342 Application.raise_config_file_errors = not suppress_errors;
344 Application.load_config_file(
343 Application.load_config_file(
345 self,
344 self,
346 base_config,
345 base_config,
347 path=self.config_file_paths
346 path=self.config_file_paths
348 )
347 )
349 except ConfigFileNotFound:
348 except ConfigFileNotFound:
350 # ignore errors loading parent
349 # ignore errors loading parent
351 self.log.debug("Config file %s not found", base_config)
350 self.log.debug("Config file %s not found", base_config)
352 pass
351 pass
353 if suppress_errors is not None:
352 if suppress_errors is not None:
354 Application.raise_config_file_errors = old_value
353 Application.raise_config_file_errors = old_value
355
354
356 for config_file_name in self.config_files:
355 for config_file_name in self.config_files:
357 if not config_file_name or config_file_name == base_config:
356 if not config_file_name or config_file_name == base_config:
358 continue
357 continue
359 self.log.debug("Attempting to load config file: %s" %
358 self.log.debug("Attempting to load config file: %s" %
360 self.config_file_name)
359 self.config_file_name)
361 try:
360 try:
362 Application.load_config_file(
361 Application.load_config_file(
363 self,
362 self,
364 config_file_name,
363 config_file_name,
365 path=self.config_file_paths
364 path=self.config_file_paths
366 )
365 )
367 except ConfigFileNotFound:
366 except ConfigFileNotFound:
368 # Only warn if the default config file was NOT being used.
367 # Only warn if the default config file was NOT being used.
369 if config_file_name in self.config_file_specified:
368 if config_file_name in self.config_file_specified:
370 msg = self.log.warning
369 msg = self.log.warning
371 else:
370 else:
372 msg = self.log.debug
371 msg = self.log.debug
373 msg("Config file not found, skipping: %s", config_file_name)
372 msg("Config file not found, skipping: %s", config_file_name)
374 except Exception:
373 except Exception:
375 # For testing purposes.
374 # For testing purposes.
376 if not suppress_errors:
375 if not suppress_errors:
377 raise
376 raise
378 self.log.warning("Error loading config file: %s" %
377 self.log.warning("Error loading config file: %s" %
379 self.config_file_name, exc_info=True)
378 self.config_file_name, exc_info=True)
380
379
381 def init_profile_dir(self):
380 def init_profile_dir(self):
382 """initialize the profile dir"""
381 """initialize the profile dir"""
383 self._in_init_profile_dir = True
382 self._in_init_profile_dir = True
384 if self.profile_dir is not None:
383 if self.profile_dir is not None:
385 # already ran
384 # already ran
386 return
385 return
387 if 'ProfileDir.location' not in self.config:
386 if 'ProfileDir.location' not in self.config:
388 # location not specified, find by profile name
387 # location not specified, find by profile name
389 try:
388 try:
390 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
389 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
391 except ProfileDirError:
390 except ProfileDirError:
392 # not found, maybe create it (always create default profile)
391 # not found, maybe create it (always create default profile)
393 if self.auto_create or self.profile == 'default':
392 if self.auto_create or self.profile == 'default':
394 try:
393 try:
395 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
394 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
396 except ProfileDirError:
395 except ProfileDirError:
397 self.log.fatal("Could not create profile: %r"%self.profile)
396 self.log.fatal("Could not create profile: %r"%self.profile)
398 self.exit(1)
397 self.exit(1)
399 else:
398 else:
400 self.log.info("Created profile dir: %r"%p.location)
399 self.log.info("Created profile dir: %r"%p.location)
401 else:
400 else:
402 self.log.fatal("Profile %r not found."%self.profile)
401 self.log.fatal("Profile %r not found."%self.profile)
403 self.exit(1)
402 self.exit(1)
404 else:
403 else:
405 self.log.debug(f"Using existing profile dir: {p.location!r}")
404 self.log.debug(f"Using existing profile dir: {p.location!r}")
406 else:
405 else:
407 location = self.config.ProfileDir.location
406 location = self.config.ProfileDir.location
408 # location is fully specified
407 # location is fully specified
409 try:
408 try:
410 p = ProfileDir.find_profile_dir(location, self.config)
409 p = ProfileDir.find_profile_dir(location, self.config)
411 except ProfileDirError:
410 except ProfileDirError:
412 # not found, maybe create it
411 # not found, maybe create it
413 if self.auto_create:
412 if self.auto_create:
414 try:
413 try:
415 p = ProfileDir.create_profile_dir(location, self.config)
414 p = ProfileDir.create_profile_dir(location, self.config)
416 except ProfileDirError:
415 except ProfileDirError:
417 self.log.fatal("Could not create profile directory: %r"%location)
416 self.log.fatal("Could not create profile directory: %r"%location)
418 self.exit(1)
417 self.exit(1)
419 else:
418 else:
420 self.log.debug("Creating new profile dir: %r"%location)
419 self.log.debug("Creating new profile dir: %r"%location)
421 else:
420 else:
422 self.log.fatal("Profile directory %r not found."%location)
421 self.log.fatal("Profile directory %r not found."%location)
423 self.exit(1)
422 self.exit(1)
424 else:
423 else:
425 self.log.debug(f"Using existing profile dir: {p.location!r}")
424 self.log.debug(f"Using existing profile dir: {p.location!r}")
426 # if profile_dir is specified explicitly, set profile name
425 # if profile_dir is specified explicitly, set profile name
427 dir_name = os.path.basename(p.location)
426 dir_name = os.path.basename(p.location)
428 if dir_name.startswith('profile_'):
427 if dir_name.startswith('profile_'):
429 self.profile = dir_name[8:]
428 self.profile = dir_name[8:]
430
429
431 self.profile_dir = p
430 self.profile_dir = p
432 self.config_file_paths.append(p.location)
431 self.config_file_paths.append(p.location)
433 self._in_init_profile_dir = False
432 self._in_init_profile_dir = False
434
433
435 def init_config_files(self):
434 def init_config_files(self):
436 """[optionally] copy default config files into profile dir."""
435 """[optionally] copy default config files into profile dir."""
437 self.config_file_paths.extend(ENV_CONFIG_DIRS)
436 self.config_file_paths.extend(ENV_CONFIG_DIRS)
438 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
437 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
439 # copy config files
438 # copy config files
440 path = Path(self.builtin_profile_dir)
439 path = Path(self.builtin_profile_dir)
441 if self.copy_config_files:
440 if self.copy_config_files:
442 src = self.profile
441 src = self.profile
443
442
444 cfg = self.config_file_name
443 cfg = self.config_file_name
445 if path and (path / cfg).exists():
444 if path and (path / cfg).exists():
446 self.log.warning(
445 self.log.warning(
447 "Staging %r from %s into %r [overwrite=%s]"
446 "Staging %r from %s into %r [overwrite=%s]"
448 % (cfg, src, self.profile_dir.location, self.overwrite)
447 % (cfg, src, self.profile_dir.location, self.overwrite)
449 )
448 )
450 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
449 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
451 else:
450 else:
452 self.stage_default_config_file()
451 self.stage_default_config_file()
453 else:
452 else:
454 # Still stage *bundled* config files, but not generated ones
453 # Still stage *bundled* config files, but not generated ones
455 # This is necessary for `ipython profile=sympy` to load the profile
454 # This is necessary for `ipython profile=sympy` to load the profile
456 # on the first go
455 # on the first go
457 files = path.glob("*.py")
456 files = path.glob("*.py")
458 for fullpath in files:
457 for fullpath in files:
459 cfg = fullpath.name
458 cfg = fullpath.name
460 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
459 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
461 # file was copied
460 # file was copied
462 self.log.warning("Staging bundled %s from %s into %r"%(
461 self.log.warning("Staging bundled %s from %s into %r"%(
463 cfg, self.profile, self.profile_dir.location)
462 cfg, self.profile, self.profile_dir.location)
464 )
463 )
465
464
466
465
467 def stage_default_config_file(self):
466 def stage_default_config_file(self):
468 """auto generate default config file, and stage it into the profile."""
467 """auto generate default config file, and stage it into the profile."""
469 s = self.generate_config_file()
468 s = self.generate_config_file()
470 config_file = Path(self.profile_dir.location) / self.config_file_name
469 config_file = Path(self.profile_dir.location) / self.config_file_name
471 if self.overwrite or not config_file.exists():
470 if self.overwrite or not config_file.exists():
472 self.log.warning("Generating default config file: %r" % (config_file))
471 self.log.warning("Generating default config file: %r" % (config_file))
473 config_file.write_text(s, encoding="utf-8")
472 config_file.write_text(s, encoding="utf-8")
474
473
475 @catch_config_error
474 @catch_config_error
476 def initialize(self, argv=None):
475 def initialize(self, argv=None):
477 # don't hook up crash handler before parsing command-line
476 # don't hook up crash handler before parsing command-line
478 self.parse_command_line(argv)
477 self.parse_command_line(argv)
479 self.init_crash_handler()
478 self.init_crash_handler()
480 if self.subapp is not None:
479 if self.subapp is not None:
481 # stop here if subapp is taking over
480 # stop here if subapp is taking over
482 return
481 return
483 # save a copy of CLI config to re-load after config files
482 # save a copy of CLI config to re-load after config files
484 # so that it has highest priority
483 # so that it has highest priority
485 cl_config = deepcopy(self.config)
484 cl_config = deepcopy(self.config)
486 self.init_profile_dir()
485 self.init_profile_dir()
487 self.init_config_files()
486 self.init_config_files()
488 self.load_config_file()
487 self.load_config_file()
489 # enforce cl-opts override configfile opts:
488 # enforce cl-opts override configfile opts:
490 self.update_config(cl_config)
489 self.update_config(cl_config)
@@ -1,237 +1,236 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
23 import sys
22 import sys
24 import traceback
23 import traceback
25 from pprint import pformat
24 from pprint import pformat
26 from pathlib import Path
25 from pathlib import Path
27
26
28 from IPython.core import ultratb
27 from IPython.core import ultratb
29 from IPython.core.release import author_email
28 from IPython.core.release import author_email
30 from IPython.utils.sysinfo import sys_info
29 from IPython.utils.sysinfo import sys_info
31 from IPython.utils.py3compat import input
30 from IPython.utils.py3compat import input
32
31
33 from IPython.core.release import __version__ as version
32 from IPython.core.release import __version__ as version
34
33
35 from typing import Optional
34 from typing import Optional
36
35
37 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
38 # Code
37 # Code
39 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
40
39
41 # Template for the user message.
40 # Template for the user message.
42 _default_message_template = """\
41 _default_message_template = """\
43 Oops, {app_name} crashed. We do our best to make it stable, but...
42 Oops, {app_name} crashed. We do our best to make it stable, but...
44
43
45 A crash report was automatically generated with the following information:
44 A crash report was automatically generated with the following information:
46 - A verbatim copy of the crash traceback.
45 - A verbatim copy of the crash traceback.
47 - A copy of your input history during this session.
46 - A copy of your input history during this session.
48 - Data on your current {app_name} configuration.
47 - Data on your current {app_name} configuration.
49
48
50 It was left in the file named:
49 It was left in the file named:
51 \t'{crash_report_fname}'
50 \t'{crash_report_fname}'
52 If you can email this file to the developers, the information in it will help
51 If you can email this file to the developers, the information in it will help
53 them in understanding and correcting the problem.
52 them in understanding and correcting the problem.
54
53
55 You can mail it to: {contact_name} at {contact_email}
54 You can mail it to: {contact_name} at {contact_email}
56 with the subject '{app_name} Crash Report'.
55 with the subject '{app_name} Crash Report'.
57
56
58 If you want to do it now, the following command will work (under Unix):
57 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}
58 mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname}
60
59
61 In your email, please also include information about:
60 In your email, please also include information about:
62 - The operating system under which the crash happened: Linux, macOS, Windows,
61 - 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,
62 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;
63 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
64 - 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;
65 a Docker container, or other, providing more detail if possible;
67 - How to reproduce the crash: what exact sequence of instructions can one
66 - 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
67 input to get the same crash? Ideally, find a minimal yet complete sequence
69 of instructions that yields the crash.
68 of instructions that yields the crash.
70
69
71 To ensure accurate tracking of this issue, please file a report about it at:
70 To ensure accurate tracking of this issue, please file a report about it at:
72 {bug_tracker}
71 {bug_tracker}
73 """
72 """
74
73
75 _lite_message_template = """
74 _lite_message_template = """
76 If you suspect this is an IPython {version} bug, please report it at:
75 If you suspect this is an IPython {version} bug, please report it at:
77 https://github.com/ipython/ipython/issues
76 https://github.com/ipython/ipython/issues
78 or send an email to the mailing list at {email}
77 or send an email to the mailing list at {email}
79
78
80 You can print a more detailed traceback right now with "%tb", or use "%debug"
79 You can print a more detailed traceback right now with "%tb", or use "%debug"
81 to interactively debug it.
80 to interactively debug it.
82
81
83 Extra-detailed tracebacks for bug-reporting purposes can be enabled via:
82 Extra-detailed tracebacks for bug-reporting purposes can be enabled via:
84 {config}Application.verbose_crash=True
83 {config}Application.verbose_crash=True
85 """
84 """
86
85
87
86
88 class CrashHandler(object):
87 class CrashHandler(object):
89 """Customizable crash handlers for IPython applications.
88 """Customizable crash handlers for IPython applications.
90
89
91 Instances of this class provide a :meth:`__call__` method which can be
90 Instances of this class provide a :meth:`__call__` method which can be
92 used as a ``sys.excepthook``. The :meth:`__call__` signature is::
91 used as a ``sys.excepthook``. The :meth:`__call__` signature is::
93
92
94 def __call__(self, etype, evalue, etb)
93 def __call__(self, etype, evalue, etb)
95 """
94 """
96
95
97 message_template = _default_message_template
96 message_template = _default_message_template
98 section_sep = '\n\n'+'*'*75+'\n\n'
97 section_sep = '\n\n'+'*'*75+'\n\n'
99
98
100 def __init__(
99 def __init__(
101 self,
100 self,
102 app,
101 app,
103 contact_name: Optional[str] = None,
102 contact_name: Optional[str] = None,
104 contact_email: Optional[str] = None,
103 contact_email: Optional[str] = None,
105 bug_tracker: Optional[str] = None,
104 bug_tracker: Optional[str] = None,
106 show_crash_traceback: bool = True,
105 show_crash_traceback: bool = True,
107 call_pdb: bool = False,
106 call_pdb: bool = False,
108 ):
107 ):
109 """Create a new crash handler
108 """Create a new crash handler
110
109
111 Parameters
110 Parameters
112 ----------
111 ----------
113 app : Application
112 app : Application
114 A running :class:`Application` instance, which will be queried at
113 A running :class:`Application` instance, which will be queried at
115 crash time for internal information.
114 crash time for internal information.
116 contact_name : str
115 contact_name : str
117 A string with the name of the person to contact.
116 A string with the name of the person to contact.
118 contact_email : str
117 contact_email : str
119 A string with the email address of the contact.
118 A string with the email address of the contact.
120 bug_tracker : str
119 bug_tracker : str
121 A string with the URL for your project's bug tracker.
120 A string with the URL for your project's bug tracker.
122 show_crash_traceback : bool
121 show_crash_traceback : bool
123 If false, don't print the crash traceback on stderr, only generate
122 If false, don't print the crash traceback on stderr, only generate
124 the on-disk report
123 the on-disk report
125 call_pdb
124 call_pdb
126 Whether to call pdb on crash
125 Whether to call pdb on crash
127
126
128 Attributes
127 Attributes
129 ----------
128 ----------
130 These instances contain some non-argument attributes which allow for
129 These instances contain some non-argument attributes which allow for
131 further customization of the crash handler's behavior. Please see the
130 further customization of the crash handler's behavior. Please see the
132 source for further details.
131 source for further details.
133
132
134 """
133 """
135 self.crash_report_fname = "Crash_report_%s.txt" % app.name
134 self.crash_report_fname = "Crash_report_%s.txt" % app.name
136 self.app = app
135 self.app = app
137 self.call_pdb = call_pdb
136 self.call_pdb = call_pdb
138 #self.call_pdb = True # dbg
137 #self.call_pdb = True # dbg
139 self.show_crash_traceback = show_crash_traceback
138 self.show_crash_traceback = show_crash_traceback
140 self.info = dict(app_name = app.name,
139 self.info = dict(app_name = app.name,
141 contact_name = contact_name,
140 contact_name = contact_name,
142 contact_email = contact_email,
141 contact_email = contact_email,
143 bug_tracker = bug_tracker,
142 bug_tracker = bug_tracker,
144 crash_report_fname = self.crash_report_fname)
143 crash_report_fname = self.crash_report_fname)
145
144
146
145
147 def __call__(self, etype, evalue, etb):
146 def __call__(self, etype, evalue, etb):
148 """Handle an exception, call for compatible with sys.excepthook"""
147 """Handle an exception, call for compatible with sys.excepthook"""
149
148
150 # do not allow the crash handler to be called twice without reinstalling it
149 # 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
150 # this prevents unlikely errors in the crash handling from entering an
152 # infinite loop.
151 # infinite loop.
153 sys.excepthook = sys.__excepthook__
152 sys.excepthook = sys.__excepthook__
154
153
155 # Report tracebacks shouldn't use color in general (safer for users)
154 # Report tracebacks shouldn't use color in general (safer for users)
156 color_scheme = 'NoColor'
155 color_scheme = 'NoColor'
157
156
158 # Use this ONLY for developer debugging (keep commented out for release)
157 # Use this ONLY for developer debugging (keep commented out for release)
159 #color_scheme = 'Linux' # dbg
158 #color_scheme = 'Linux' # dbg
160 try:
159 try:
161 rptdir = self.app.ipython_dir
160 rptdir = self.app.ipython_dir
162 except:
161 except:
163 rptdir = Path.cwd()
162 rptdir = Path.cwd()
164 if rptdir is None or not Path.is_dir(rptdir):
163 if rptdir is None or not Path.is_dir(rptdir):
165 rptdir = Path.cwd()
164 rptdir = Path.cwd()
166 report_name = rptdir / self.crash_report_fname
165 report_name = rptdir / self.crash_report_fname
167 # write the report filename into the instance dict so it can get
166 # write the report filename into the instance dict so it can get
168 # properly expanded out in the user message template
167 # properly expanded out in the user message template
169 self.crash_report_fname = report_name
168 self.crash_report_fname = report_name
170 self.info['crash_report_fname'] = report_name
169 self.info['crash_report_fname'] = report_name
171 TBhandler = ultratb.VerboseTB(
170 TBhandler = ultratb.VerboseTB(
172 color_scheme=color_scheme,
171 color_scheme=color_scheme,
173 long_header=1,
172 long_header=1,
174 call_pdb=self.call_pdb,
173 call_pdb=self.call_pdb,
175 )
174 )
176 if self.call_pdb:
175 if self.call_pdb:
177 TBhandler(etype,evalue,etb)
176 TBhandler(etype,evalue,etb)
178 return
177 return
179 else:
178 else:
180 traceback = TBhandler.text(etype,evalue,etb,context=31)
179 traceback = TBhandler.text(etype,evalue,etb,context=31)
181
180
182 # print traceback to screen
181 # print traceback to screen
183 if self.show_crash_traceback:
182 if self.show_crash_traceback:
184 print(traceback, file=sys.stderr)
183 print(traceback, file=sys.stderr)
185
184
186 # and generate a complete report on disk
185 # and generate a complete report on disk
187 try:
186 try:
188 report = open(report_name, "w", encoding="utf-8")
187 report = open(report_name, "w", encoding="utf-8")
189 except:
188 except:
190 print('Could not create crash report on disk.', file=sys.stderr)
189 print('Could not create crash report on disk.', file=sys.stderr)
191 return
190 return
192
191
193 with report:
192 with report:
194 # Inform user on stderr of what happened
193 # Inform user on stderr of what happened
195 print('\n'+'*'*70+'\n', file=sys.stderr)
194 print('\n'+'*'*70+'\n', file=sys.stderr)
196 print(self.message_template.format(**self.info), file=sys.stderr)
195 print(self.message_template.format(**self.info), file=sys.stderr)
197
196
198 # Construct report on disk
197 # Construct report on disk
199 report.write(self.make_report(traceback))
198 report.write(self.make_report(traceback))
200
199
201 input("Hit <Enter> to quit (your terminal may close):")
200 input("Hit <Enter> to quit (your terminal may close):")
202
201
203 def make_report(self,traceback):
202 def make_report(self,traceback):
204 """Return a string containing a crash report."""
203 """Return a string containing a crash report."""
205
204
206 sec_sep = self.section_sep
205 sec_sep = self.section_sep
207
206
208 report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
207 report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
209 rpt_add = report.append
208 rpt_add = report.append
210 rpt_add(sys_info())
209 rpt_add(sys_info())
211
210
212 try:
211 try:
213 config = pformat(self.app.config)
212 config = pformat(self.app.config)
214 rpt_add(sec_sep)
213 rpt_add(sec_sep)
215 rpt_add('Application name: %s\n\n' % self.app_name)
214 rpt_add('Application name: %s\n\n' % self.app_name)
216 rpt_add('Current user configuration structure:\n\n')
215 rpt_add('Current user configuration structure:\n\n')
217 rpt_add(config)
216 rpt_add(config)
218 except:
217 except:
219 pass
218 pass
220 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
219 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
221
220
222 return ''.join(report)
221 return ''.join(report)
223
222
224
223
225 def crash_handler_lite(etype, evalue, tb):
224 def crash_handler_lite(etype, evalue, tb):
226 """a light excepthook, adding a small message to the usual traceback"""
225 """a light excepthook, adding a small message to the usual traceback"""
227 traceback.print_exception(etype, evalue, tb)
226 traceback.print_exception(etype, evalue, tb)
228
227
229 from IPython.core.interactiveshell import InteractiveShell
228 from IPython.core.interactiveshell import InteractiveShell
230 if InteractiveShell.initialized():
229 if InteractiveShell.initialized():
231 # we are in a Shell environment, give %magic example
230 # we are in a Shell environment, give %magic example
232 config = "%config "
231 config = "%config "
233 else:
232 else:
234 # we are not in a shell, show generic config
233 # we are not in a shell, show generic config
235 config = "c."
234 config = "c."
236 print(_lite_message_template.format(email=author_email, config=config, version=version), file=sys.stderr)
235 print(_lite_message_template.format(email=author_email, config=config, version=version), file=sys.stderr)
237
236
@@ -1,999 +1,998 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 Pdb debugger class.
3 Pdb debugger class.
4
4
5
5
6 This is an extension to PDB which adds a number of new features.
6 This is an extension to PDB which adds a number of new features.
7 Note that there is also the `IPython.terminal.debugger` class which provides UI
7 Note that there is also the `IPython.terminal.debugger` class which provides UI
8 improvements.
8 improvements.
9
9
10 We also strongly recommend to use this via the `ipdb` package, which provides
10 We also strongly recommend to use this via the `ipdb` package, which provides
11 extra configuration options.
11 extra configuration options.
12
12
13 Among other things, this subclass of PDB:
13 Among other things, this subclass of PDB:
14 - supports many IPython magics like pdef/psource
14 - supports many IPython magics like pdef/psource
15 - hide frames in tracebacks based on `__tracebackhide__`
15 - hide frames in tracebacks based on `__tracebackhide__`
16 - allows to skip frames based on `__debuggerskip__`
16 - allows to skip frames based on `__debuggerskip__`
17
17
18 The skipping and hiding frames are configurable via the `skip_predicates`
18 The skipping and hiding frames are configurable via the `skip_predicates`
19 command.
19 command.
20
20
21 By default, frames from readonly files will be hidden, frames containing
21 By default, frames from readonly files will be hidden, frames containing
22 ``__tracebackhide__=True`` will be hidden.
22 ``__tracebackhide__=True`` will be hidden.
23
23
24 Frames containing ``__debuggerskip__`` will be stepped over, frames who's parent
24 Frames containing ``__debuggerskip__`` will be stepped over, frames who's parent
25 frames value of ``__debuggerskip__`` is ``True`` will be skipped.
25 frames value of ``__debuggerskip__`` is ``True`` will be skipped.
26
26
27 >>> def helpers_helper():
27 >>> def helpers_helper():
28 ... pass
28 ... pass
29 ...
29 ...
30 ... def helper_1():
30 ... def helper_1():
31 ... print("don't step in me")
31 ... print("don't step in me")
32 ... helpers_helpers() # will be stepped over unless breakpoint set.
32 ... helpers_helpers() # will be stepped over unless breakpoint set.
33 ...
33 ...
34 ...
34 ...
35 ... def helper_2():
35 ... def helper_2():
36 ... print("in me neither")
36 ... print("in me neither")
37 ...
37 ...
38
38
39 One can define a decorator that wraps a function between the two helpers:
39 One can define a decorator that wraps a function between the two helpers:
40
40
41 >>> def pdb_skipped_decorator(function):
41 >>> def pdb_skipped_decorator(function):
42 ...
42 ...
43 ...
43 ...
44 ... def wrapped_fn(*args, **kwargs):
44 ... def wrapped_fn(*args, **kwargs):
45 ... __debuggerskip__ = True
45 ... __debuggerskip__ = True
46 ... helper_1()
46 ... helper_1()
47 ... __debuggerskip__ = False
47 ... __debuggerskip__ = False
48 ... result = function(*args, **kwargs)
48 ... result = function(*args, **kwargs)
49 ... __debuggerskip__ = True
49 ... __debuggerskip__ = True
50 ... helper_2()
50 ... helper_2()
51 ... # setting __debuggerskip__ to False again is not necessary
51 ... # setting __debuggerskip__ to False again is not necessary
52 ... return result
52 ... return result
53 ...
53 ...
54 ... return wrapped_fn
54 ... return wrapped_fn
55
55
56 When decorating a function, ipdb will directly step into ``bar()`` by
56 When decorating a function, ipdb will directly step into ``bar()`` by
57 default:
57 default:
58
58
59 >>> @foo_decorator
59 >>> @foo_decorator
60 ... def bar(x, y):
60 ... def bar(x, y):
61 ... return x * y
61 ... return x * y
62
62
63
63
64 You can toggle the behavior with
64 You can toggle the behavior with
65
65
66 ipdb> skip_predicates debuggerskip false
66 ipdb> skip_predicates debuggerskip false
67
67
68 or configure it in your ``.pdbrc``
68 or configure it in your ``.pdbrc``
69
69
70
70
71
71
72 License
72 License
73 -------
73 -------
74
74
75 Modified from the standard pdb.Pdb class to avoid including readline, so that
75 Modified from the standard pdb.Pdb class to avoid including readline, so that
76 the command line completion of other programs which include this isn't
76 the command line completion of other programs which include this isn't
77 damaged.
77 damaged.
78
78
79 In the future, this class will be expanded with improvements over the standard
79 In the future, this class will be expanded with improvements over the standard
80 pdb.
80 pdb.
81
81
82 The original code in this file is mainly lifted out of cmd.py in Python 2.2,
82 The original code in this file is mainly lifted out of cmd.py in Python 2.2,
83 with minor changes. Licensing should therefore be under the standard Python
83 with minor changes. Licensing should therefore be under the standard Python
84 terms. For details on the PSF (Python Software Foundation) standard license,
84 terms. For details on the PSF (Python Software Foundation) standard license,
85 see:
85 see:
86
86
87 https://docs.python.org/2/license.html
87 https://docs.python.org/2/license.html
88
88
89
89
90 All the changes since then are under the same license as IPython.
90 All the changes since then are under the same license as IPython.
91
91
92 """
92 """
93
93
94 #*****************************************************************************
94 #*****************************************************************************
95 #
95 #
96 # This file is licensed under the PSF license.
96 # This file is licensed under the PSF license.
97 #
97 #
98 # Copyright (C) 2001 Python Software Foundation, www.python.org
98 # Copyright (C) 2001 Python Software Foundation, www.python.org
99 # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
99 # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
100 #
100 #
101 #
101 #
102 #*****************************************************************************
102 #*****************************************************************************
103
103
104 import inspect
104 import inspect
105 import linecache
105 import linecache
106 import sys
106 import sys
107 import warnings
108 import re
107 import re
109 import os
108 import os
110
109
111 from IPython import get_ipython
110 from IPython import get_ipython
112 from IPython.utils import PyColorize
111 from IPython.utils import PyColorize
113 from IPython.utils import coloransi, py3compat
112 from IPython.utils import coloransi, py3compat
114 from IPython.core.excolors import exception_colors
113 from IPython.core.excolors import exception_colors
115
114
116 # skip module docstests
115 # skip module docstests
117 __skip_doctest__ = True
116 __skip_doctest__ = True
118
117
119 prompt = 'ipdb> '
118 prompt = 'ipdb> '
120
119
121 # We have to check this directly from sys.argv, config struct not yet available
120 # We have to check this directly from sys.argv, config struct not yet available
122 from pdb import Pdb as OldPdb
121 from pdb import Pdb as OldPdb
123
122
124 # Allow the set_trace code to operate outside of an ipython instance, even if
123 # Allow the set_trace code to operate outside of an ipython instance, even if
125 # it does so with some limitations. The rest of this support is implemented in
124 # it does so with some limitations. The rest of this support is implemented in
126 # the Tracer constructor.
125 # the Tracer constructor.
127
126
128 DEBUGGERSKIP = "__debuggerskip__"
127 DEBUGGERSKIP = "__debuggerskip__"
129
128
130
129
131 def make_arrow(pad):
130 def make_arrow(pad):
132 """generate the leading arrow in front of traceback or debugger"""
131 """generate the leading arrow in front of traceback or debugger"""
133 if pad >= 2:
132 if pad >= 2:
134 return '-'*(pad-2) + '> '
133 return '-'*(pad-2) + '> '
135 elif pad == 1:
134 elif pad == 1:
136 return '>'
135 return '>'
137 return ''
136 return ''
138
137
139
138
140 def BdbQuit_excepthook(et, ev, tb, excepthook=None):
139 def BdbQuit_excepthook(et, ev, tb, excepthook=None):
141 """Exception hook which handles `BdbQuit` exceptions.
140 """Exception hook which handles `BdbQuit` exceptions.
142
141
143 All other exceptions are processed using the `excepthook`
142 All other exceptions are processed using the `excepthook`
144 parameter.
143 parameter.
145 """
144 """
146 raise ValueError(
145 raise ValueError(
147 "`BdbQuit_excepthook` is deprecated since version 5.1",
146 "`BdbQuit_excepthook` is deprecated since version 5.1",
148 )
147 )
149
148
150
149
151 def BdbQuit_IPython_excepthook(self, et, ev, tb, tb_offset=None):
150 def BdbQuit_IPython_excepthook(self, et, ev, tb, tb_offset=None):
152 raise ValueError(
151 raise ValueError(
153 "`BdbQuit_IPython_excepthook` is deprecated since version 5.1",
152 "`BdbQuit_IPython_excepthook` is deprecated since version 5.1",
154 DeprecationWarning, stacklevel=2)
153 DeprecationWarning, stacklevel=2)
155
154
156
155
157 RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+')
156 RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+')
158
157
159
158
160 def strip_indentation(multiline_string):
159 def strip_indentation(multiline_string):
161 return RGX_EXTRA_INDENT.sub('', multiline_string)
160 return RGX_EXTRA_INDENT.sub('', multiline_string)
162
161
163
162
164 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
163 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
165 """Make new_fn have old_fn's doc string. This is particularly useful
164 """Make new_fn have old_fn's doc string. This is particularly useful
166 for the ``do_...`` commands that hook into the help system.
165 for the ``do_...`` commands that hook into the help system.
167 Adapted from from a comp.lang.python posting
166 Adapted from from a comp.lang.python posting
168 by Duncan Booth."""
167 by Duncan Booth."""
169 def wrapper(*args, **kw):
168 def wrapper(*args, **kw):
170 return new_fn(*args, **kw)
169 return new_fn(*args, **kw)
171 if old_fn.__doc__:
170 if old_fn.__doc__:
172 wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text
171 wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text
173 return wrapper
172 return wrapper
174
173
175
174
176 class Pdb(OldPdb):
175 class Pdb(OldPdb):
177 """Modified Pdb class, does not load readline.
176 """Modified Pdb class, does not load readline.
178
177
179 for a standalone version that uses prompt_toolkit, see
178 for a standalone version that uses prompt_toolkit, see
180 `IPython.terminal.debugger.TerminalPdb` and
179 `IPython.terminal.debugger.TerminalPdb` and
181 `IPython.terminal.debugger.set_trace()`
180 `IPython.terminal.debugger.set_trace()`
182
181
183
182
184 This debugger can hide and skip frames that are tagged according to some predicates.
183 This debugger can hide and skip frames that are tagged according to some predicates.
185 See the `skip_predicates` commands.
184 See the `skip_predicates` commands.
186
185
187 """
186 """
188
187
189 default_predicates = {
188 default_predicates = {
190 "tbhide": True,
189 "tbhide": True,
191 "readonly": False,
190 "readonly": False,
192 "ipython_internal": True,
191 "ipython_internal": True,
193 "debuggerskip": True,
192 "debuggerskip": True,
194 }
193 }
195
194
196 def __init__(self, completekey=None, stdin=None, stdout=None, context=5, **kwargs):
195 def __init__(self, completekey=None, stdin=None, stdout=None, context=5, **kwargs):
197 """Create a new IPython debugger.
196 """Create a new IPython debugger.
198
197
199 Parameters
198 Parameters
200 ----------
199 ----------
201 completekey : default None
200 completekey : default None
202 Passed to pdb.Pdb.
201 Passed to pdb.Pdb.
203 stdin : default None
202 stdin : default None
204 Passed to pdb.Pdb.
203 Passed to pdb.Pdb.
205 stdout : default None
204 stdout : default None
206 Passed to pdb.Pdb.
205 Passed to pdb.Pdb.
207 context : int
206 context : int
208 Number of lines of source code context to show when
207 Number of lines of source code context to show when
209 displaying stacktrace information.
208 displaying stacktrace information.
210 **kwargs
209 **kwargs
211 Passed to pdb.Pdb.
210 Passed to pdb.Pdb.
212
211
213 Notes
212 Notes
214 -----
213 -----
215 The possibilities are python version dependent, see the python
214 The possibilities are python version dependent, see the python
216 docs for more info.
215 docs for more info.
217 """
216 """
218
217
219 # Parent constructor:
218 # Parent constructor:
220 try:
219 try:
221 self.context = int(context)
220 self.context = int(context)
222 if self.context <= 0:
221 if self.context <= 0:
223 raise ValueError("Context must be a positive integer")
222 raise ValueError("Context must be a positive integer")
224 except (TypeError, ValueError) as e:
223 except (TypeError, ValueError) as e:
225 raise ValueError("Context must be a positive integer") from e
224 raise ValueError("Context must be a positive integer") from e
226
225
227 # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`.
226 # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`.
228 OldPdb.__init__(self, completekey, stdin, stdout, **kwargs)
227 OldPdb.__init__(self, completekey, stdin, stdout, **kwargs)
229
228
230 # IPython changes...
229 # IPython changes...
231 self.shell = get_ipython()
230 self.shell = get_ipython()
232
231
233 if self.shell is None:
232 if self.shell is None:
234 save_main = sys.modules['__main__']
233 save_main = sys.modules['__main__']
235 # No IPython instance running, we must create one
234 # No IPython instance running, we must create one
236 from IPython.terminal.interactiveshell import \
235 from IPython.terminal.interactiveshell import \
237 TerminalInteractiveShell
236 TerminalInteractiveShell
238 self.shell = TerminalInteractiveShell.instance()
237 self.shell = TerminalInteractiveShell.instance()
239 # needed by any code which calls __import__("__main__") after
238 # needed by any code which calls __import__("__main__") after
240 # the debugger was entered. See also #9941.
239 # the debugger was entered. See also #9941.
241 sys.modules["__main__"] = save_main
240 sys.modules["__main__"] = save_main
242
241
243
242
244 color_scheme = self.shell.colors
243 color_scheme = self.shell.colors
245
244
246 self.aliases = {}
245 self.aliases = {}
247
246
248 # Create color table: we copy the default one from the traceback
247 # Create color table: we copy the default one from the traceback
249 # module and add a few attributes needed for debugging
248 # module and add a few attributes needed for debugging
250 self.color_scheme_table = exception_colors()
249 self.color_scheme_table = exception_colors()
251
250
252 # shorthands
251 # shorthands
253 C = coloransi.TermColors
252 C = coloransi.TermColors
254 cst = self.color_scheme_table
253 cst = self.color_scheme_table
255
254
256 cst['NoColor'].colors.prompt = C.NoColor
255 cst['NoColor'].colors.prompt = C.NoColor
257 cst['NoColor'].colors.breakpoint_enabled = C.NoColor
256 cst['NoColor'].colors.breakpoint_enabled = C.NoColor
258 cst['NoColor'].colors.breakpoint_disabled = C.NoColor
257 cst['NoColor'].colors.breakpoint_disabled = C.NoColor
259
258
260 cst['Linux'].colors.prompt = C.Green
259 cst['Linux'].colors.prompt = C.Green
261 cst['Linux'].colors.breakpoint_enabled = C.LightRed
260 cst['Linux'].colors.breakpoint_enabled = C.LightRed
262 cst['Linux'].colors.breakpoint_disabled = C.Red
261 cst['Linux'].colors.breakpoint_disabled = C.Red
263
262
264 cst['LightBG'].colors.prompt = C.Blue
263 cst['LightBG'].colors.prompt = C.Blue
265 cst['LightBG'].colors.breakpoint_enabled = C.LightRed
264 cst['LightBG'].colors.breakpoint_enabled = C.LightRed
266 cst['LightBG'].colors.breakpoint_disabled = C.Red
265 cst['LightBG'].colors.breakpoint_disabled = C.Red
267
266
268 cst['Neutral'].colors.prompt = C.Blue
267 cst['Neutral'].colors.prompt = C.Blue
269 cst['Neutral'].colors.breakpoint_enabled = C.LightRed
268 cst['Neutral'].colors.breakpoint_enabled = C.LightRed
270 cst['Neutral'].colors.breakpoint_disabled = C.Red
269 cst['Neutral'].colors.breakpoint_disabled = C.Red
271
270
272 # Add a python parser so we can syntax highlight source while
271 # Add a python parser so we can syntax highlight source while
273 # debugging.
272 # debugging.
274 self.parser = PyColorize.Parser(style=color_scheme)
273 self.parser = PyColorize.Parser(style=color_scheme)
275 self.set_colors(color_scheme)
274 self.set_colors(color_scheme)
276
275
277 # Set the prompt - the default prompt is '(Pdb)'
276 # Set the prompt - the default prompt is '(Pdb)'
278 self.prompt = prompt
277 self.prompt = prompt
279 self.skip_hidden = True
278 self.skip_hidden = True
280 self.report_skipped = True
279 self.report_skipped = True
281
280
282 # list of predicates we use to skip frames
281 # list of predicates we use to skip frames
283 self._predicates = self.default_predicates
282 self._predicates = self.default_predicates
284
283
285 #
284 #
286 def set_colors(self, scheme):
285 def set_colors(self, scheme):
287 """Shorthand access to the color table scheme selector method."""
286 """Shorthand access to the color table scheme selector method."""
288 self.color_scheme_table.set_active_scheme(scheme)
287 self.color_scheme_table.set_active_scheme(scheme)
289 self.parser.style = scheme
288 self.parser.style = scheme
290
289
291 def set_trace(self, frame=None):
290 def set_trace(self, frame=None):
292 if frame is None:
291 if frame is None:
293 frame = sys._getframe().f_back
292 frame = sys._getframe().f_back
294 self.initial_frame = frame
293 self.initial_frame = frame
295 return super().set_trace(frame)
294 return super().set_trace(frame)
296
295
297 def _hidden_predicate(self, frame):
296 def _hidden_predicate(self, frame):
298 """
297 """
299 Given a frame return whether it it should be hidden or not by IPython.
298 Given a frame return whether it it should be hidden or not by IPython.
300 """
299 """
301
300
302 if self._predicates["readonly"]:
301 if self._predicates["readonly"]:
303 fname = frame.f_code.co_filename
302 fname = frame.f_code.co_filename
304 # we need to check for file existence and interactively define
303 # we need to check for file existence and interactively define
305 # function would otherwise appear as RO.
304 # function would otherwise appear as RO.
306 if os.path.isfile(fname) and not os.access(fname, os.W_OK):
305 if os.path.isfile(fname) and not os.access(fname, os.W_OK):
307 return True
306 return True
308
307
309 if self._predicates["tbhide"]:
308 if self._predicates["tbhide"]:
310 if frame in (self.curframe, getattr(self, "initial_frame", None)):
309 if frame in (self.curframe, getattr(self, "initial_frame", None)):
311 return False
310 return False
312 frame_locals = self._get_frame_locals(frame)
311 frame_locals = self._get_frame_locals(frame)
313 if "__tracebackhide__" not in frame_locals:
312 if "__tracebackhide__" not in frame_locals:
314 return False
313 return False
315 return frame_locals["__tracebackhide__"]
314 return frame_locals["__tracebackhide__"]
316 return False
315 return False
317
316
318 def hidden_frames(self, stack):
317 def hidden_frames(self, stack):
319 """
318 """
320 Given an index in the stack return whether it should be skipped.
319 Given an index in the stack return whether it should be skipped.
321
320
322 This is used in up/down and where to skip frames.
321 This is used in up/down and where to skip frames.
323 """
322 """
324 # The f_locals dictionary is updated from the actual frame
323 # The f_locals dictionary is updated from the actual frame
325 # locals whenever the .f_locals accessor is called, so we
324 # locals whenever the .f_locals accessor is called, so we
326 # avoid calling it here to preserve self.curframe_locals.
325 # avoid calling it here to preserve self.curframe_locals.
327 # Furthermore, there is no good reason to hide the current frame.
326 # Furthermore, there is no good reason to hide the current frame.
328 ip_hide = [self._hidden_predicate(s[0]) for s in stack]
327 ip_hide = [self._hidden_predicate(s[0]) for s in stack]
329 ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"]
328 ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"]
330 if ip_start and self._predicates["ipython_internal"]:
329 if ip_start and self._predicates["ipython_internal"]:
331 ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)]
330 ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)]
332 return ip_hide
331 return ip_hide
333
332
334 def interaction(self, frame, traceback):
333 def interaction(self, frame, traceback):
335 try:
334 try:
336 OldPdb.interaction(self, frame, traceback)
335 OldPdb.interaction(self, frame, traceback)
337 except KeyboardInterrupt:
336 except KeyboardInterrupt:
338 self.stdout.write("\n" + self.shell.get_exception_only())
337 self.stdout.write("\n" + self.shell.get_exception_only())
339
338
340 def precmd(self, line):
339 def precmd(self, line):
341 """Perform useful escapes on the command before it is executed."""
340 """Perform useful escapes on the command before it is executed."""
342
341
343 if line.endswith("??"):
342 if line.endswith("??"):
344 line = "pinfo2 " + line[:-2]
343 line = "pinfo2 " + line[:-2]
345 elif line.endswith("?"):
344 elif line.endswith("?"):
346 line = "pinfo " + line[:-1]
345 line = "pinfo " + line[:-1]
347
346
348 line = super().precmd(line)
347 line = super().precmd(line)
349
348
350 return line
349 return line
351
350
352 def new_do_frame(self, arg):
351 def new_do_frame(self, arg):
353 OldPdb.do_frame(self, arg)
352 OldPdb.do_frame(self, arg)
354
353
355 def new_do_quit(self, arg):
354 def new_do_quit(self, arg):
356
355
357 if hasattr(self, 'old_all_completions'):
356 if hasattr(self, 'old_all_completions'):
358 self.shell.Completer.all_completions = self.old_all_completions
357 self.shell.Completer.all_completions = self.old_all_completions
359
358
360 return OldPdb.do_quit(self, arg)
359 return OldPdb.do_quit(self, arg)
361
360
362 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
361 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
363
362
364 def new_do_restart(self, arg):
363 def new_do_restart(self, arg):
365 """Restart command. In the context of ipython this is exactly the same
364 """Restart command. In the context of ipython this is exactly the same
366 thing as 'quit'."""
365 thing as 'quit'."""
367 self.msg("Restart doesn't make sense here. Using 'quit' instead.")
366 self.msg("Restart doesn't make sense here. Using 'quit' instead.")
368 return self.do_quit(arg)
367 return self.do_quit(arg)
369
368
370 def print_stack_trace(self, context=None):
369 def print_stack_trace(self, context=None):
371 Colors = self.color_scheme_table.active_colors
370 Colors = self.color_scheme_table.active_colors
372 ColorsNormal = Colors.Normal
371 ColorsNormal = Colors.Normal
373 if context is None:
372 if context is None:
374 context = self.context
373 context = self.context
375 try:
374 try:
376 context = int(context)
375 context = int(context)
377 if context <= 0:
376 if context <= 0:
378 raise ValueError("Context must be a positive integer")
377 raise ValueError("Context must be a positive integer")
379 except (TypeError, ValueError) as e:
378 except (TypeError, ValueError) as e:
380 raise ValueError("Context must be a positive integer") from e
379 raise ValueError("Context must be a positive integer") from e
381 try:
380 try:
382 skipped = 0
381 skipped = 0
383 for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack):
382 for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack):
384 if hidden and self.skip_hidden:
383 if hidden and self.skip_hidden:
385 skipped += 1
384 skipped += 1
386 continue
385 continue
387 if skipped:
386 if skipped:
388 print(
387 print(
389 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
388 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
390 )
389 )
391 skipped = 0
390 skipped = 0
392 self.print_stack_entry(frame_lineno, context=context)
391 self.print_stack_entry(frame_lineno, context=context)
393 if skipped:
392 if skipped:
394 print(
393 print(
395 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
394 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
396 )
395 )
397 except KeyboardInterrupt:
396 except KeyboardInterrupt:
398 pass
397 pass
399
398
400 def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ',
399 def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ',
401 context=None):
400 context=None):
402 if context is None:
401 if context is None:
403 context = self.context
402 context = self.context
404 try:
403 try:
405 context = int(context)
404 context = int(context)
406 if context <= 0:
405 if context <= 0:
407 raise ValueError("Context must be a positive integer")
406 raise ValueError("Context must be a positive integer")
408 except (TypeError, ValueError) as e:
407 except (TypeError, ValueError) as e:
409 raise ValueError("Context must be a positive integer") from e
408 raise ValueError("Context must be a positive integer") from e
410 print(self.format_stack_entry(frame_lineno, '', context), file=self.stdout)
409 print(self.format_stack_entry(frame_lineno, '', context), file=self.stdout)
411
410
412 # vds: >>
411 # vds: >>
413 frame, lineno = frame_lineno
412 frame, lineno = frame_lineno
414 filename = frame.f_code.co_filename
413 filename = frame.f_code.co_filename
415 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
414 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
416 # vds: <<
415 # vds: <<
417
416
418 def _get_frame_locals(self, frame):
417 def _get_frame_locals(self, frame):
419 """ "
418 """ "
420 Accessing f_local of current frame reset the namespace, so we want to avoid
419 Accessing f_local of current frame reset the namespace, so we want to avoid
421 that or the following can happen
420 that or the following can happen
422
421
423 ipdb> foo
422 ipdb> foo
424 "old"
423 "old"
425 ipdb> foo = "new"
424 ipdb> foo = "new"
426 ipdb> foo
425 ipdb> foo
427 "new"
426 "new"
428 ipdb> where
427 ipdb> where
429 ipdb> foo
428 ipdb> foo
430 "old"
429 "old"
431
430
432 So if frame is self.current_frame we instead return self.curframe_locals
431 So if frame is self.current_frame we instead return self.curframe_locals
433
432
434 """
433 """
435 if frame is self.curframe:
434 if frame is self.curframe:
436 return self.curframe_locals
435 return self.curframe_locals
437 else:
436 else:
438 return frame.f_locals
437 return frame.f_locals
439
438
440 def format_stack_entry(self, frame_lineno, lprefix=': ', context=None):
439 def format_stack_entry(self, frame_lineno, lprefix=': ', context=None):
441 if context is None:
440 if context is None:
442 context = self.context
441 context = self.context
443 try:
442 try:
444 context = int(context)
443 context = int(context)
445 if context <= 0:
444 if context <= 0:
446 print("Context must be a positive integer", file=self.stdout)
445 print("Context must be a positive integer", file=self.stdout)
447 except (TypeError, ValueError):
446 except (TypeError, ValueError):
448 print("Context must be a positive integer", file=self.stdout)
447 print("Context must be a positive integer", file=self.stdout)
449
448
450 import reprlib
449 import reprlib
451
450
452 ret = []
451 ret = []
453
452
454 Colors = self.color_scheme_table.active_colors
453 Colors = self.color_scheme_table.active_colors
455 ColorsNormal = Colors.Normal
454 ColorsNormal = Colors.Normal
456 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
455 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
457 tpl_call = "%s%%s%s%%s%s" % (Colors.vName, Colors.valEm, ColorsNormal)
456 tpl_call = "%s%%s%s%%s%s" % (Colors.vName, Colors.valEm, ColorsNormal)
458 tpl_line = "%%s%s%%s %s%%s" % (Colors.lineno, ColorsNormal)
457 tpl_line = "%%s%s%%s %s%%s" % (Colors.lineno, ColorsNormal)
459 tpl_line_em = "%%s%s%%s %s%%s%s" % (Colors.linenoEm, Colors.line, ColorsNormal)
458 tpl_line_em = "%%s%s%%s %s%%s%s" % (Colors.linenoEm, Colors.line, ColorsNormal)
460
459
461 frame, lineno = frame_lineno
460 frame, lineno = frame_lineno
462
461
463 return_value = ''
462 return_value = ''
464 loc_frame = self._get_frame_locals(frame)
463 loc_frame = self._get_frame_locals(frame)
465 if "__return__" in loc_frame:
464 if "__return__" in loc_frame:
466 rv = loc_frame["__return__"]
465 rv = loc_frame["__return__"]
467 # return_value += '->'
466 # return_value += '->'
468 return_value += reprlib.repr(rv) + "\n"
467 return_value += reprlib.repr(rv) + "\n"
469 ret.append(return_value)
468 ret.append(return_value)
470
469
471 #s = filename + '(' + `lineno` + ')'
470 #s = filename + '(' + `lineno` + ')'
472 filename = self.canonic(frame.f_code.co_filename)
471 filename = self.canonic(frame.f_code.co_filename)
473 link = tpl_link % py3compat.cast_unicode(filename)
472 link = tpl_link % py3compat.cast_unicode(filename)
474
473
475 if frame.f_code.co_name:
474 if frame.f_code.co_name:
476 func = frame.f_code.co_name
475 func = frame.f_code.co_name
477 else:
476 else:
478 func = "<lambda>"
477 func = "<lambda>"
479
478
480 call = ""
479 call = ""
481 if func != "?":
480 if func != "?":
482 if "__args__" in loc_frame:
481 if "__args__" in loc_frame:
483 args = reprlib.repr(loc_frame["__args__"])
482 args = reprlib.repr(loc_frame["__args__"])
484 else:
483 else:
485 args = '()'
484 args = '()'
486 call = tpl_call % (func, args)
485 call = tpl_call % (func, args)
487
486
488 # The level info should be generated in the same format pdb uses, to
487 # The level info should be generated in the same format pdb uses, to
489 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
488 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
490 if frame is self.curframe:
489 if frame is self.curframe:
491 ret.append('> ')
490 ret.append('> ')
492 else:
491 else:
493 ret.append(" ")
492 ret.append(" ")
494 ret.append("%s(%s)%s\n" % (link, lineno, call))
493 ret.append("%s(%s)%s\n" % (link, lineno, call))
495
494
496 start = lineno - 1 - context//2
495 start = lineno - 1 - context//2
497 lines = linecache.getlines(filename)
496 lines = linecache.getlines(filename)
498 start = min(start, len(lines) - context)
497 start = min(start, len(lines) - context)
499 start = max(start, 0)
498 start = max(start, 0)
500 lines = lines[start : start + context]
499 lines = lines[start : start + context]
501
500
502 for i, line in enumerate(lines):
501 for i, line in enumerate(lines):
503 show_arrow = start + 1 + i == lineno
502 show_arrow = start + 1 + i == lineno
504 linetpl = (frame is self.curframe or show_arrow) and tpl_line_em or tpl_line
503 linetpl = (frame is self.curframe or show_arrow) and tpl_line_em or tpl_line
505 ret.append(
504 ret.append(
506 self.__format_line(
505 self.__format_line(
507 linetpl, filename, start + 1 + i, line, arrow=show_arrow
506 linetpl, filename, start + 1 + i, line, arrow=show_arrow
508 )
507 )
509 )
508 )
510 return "".join(ret)
509 return "".join(ret)
511
510
512 def __format_line(self, tpl_line, filename, lineno, line, arrow=False):
511 def __format_line(self, tpl_line, filename, lineno, line, arrow=False):
513 bp_mark = ""
512 bp_mark = ""
514 bp_mark_color = ""
513 bp_mark_color = ""
515
514
516 new_line, err = self.parser.format2(line, 'str')
515 new_line, err = self.parser.format2(line, 'str')
517 if not err:
516 if not err:
518 line = new_line
517 line = new_line
519
518
520 bp = None
519 bp = None
521 if lineno in self.get_file_breaks(filename):
520 if lineno in self.get_file_breaks(filename):
522 bps = self.get_breaks(filename, lineno)
521 bps = self.get_breaks(filename, lineno)
523 bp = bps[-1]
522 bp = bps[-1]
524
523
525 if bp:
524 if bp:
526 Colors = self.color_scheme_table.active_colors
525 Colors = self.color_scheme_table.active_colors
527 bp_mark = str(bp.number)
526 bp_mark = str(bp.number)
528 bp_mark_color = Colors.breakpoint_enabled
527 bp_mark_color = Colors.breakpoint_enabled
529 if not bp.enabled:
528 if not bp.enabled:
530 bp_mark_color = Colors.breakpoint_disabled
529 bp_mark_color = Colors.breakpoint_disabled
531
530
532 numbers_width = 7
531 numbers_width = 7
533 if arrow:
532 if arrow:
534 # This is the line with the error
533 # This is the line with the error
535 pad = numbers_width - len(str(lineno)) - len(bp_mark)
534 pad = numbers_width - len(str(lineno)) - len(bp_mark)
536 num = '%s%s' % (make_arrow(pad), str(lineno))
535 num = '%s%s' % (make_arrow(pad), str(lineno))
537 else:
536 else:
538 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
537 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
539
538
540 return tpl_line % (bp_mark_color + bp_mark, num, line)
539 return tpl_line % (bp_mark_color + bp_mark, num, line)
541
540
542 def print_list_lines(self, filename, first, last):
541 def print_list_lines(self, filename, first, last):
543 """The printing (as opposed to the parsing part of a 'list'
542 """The printing (as opposed to the parsing part of a 'list'
544 command."""
543 command."""
545 try:
544 try:
546 Colors = self.color_scheme_table.active_colors
545 Colors = self.color_scheme_table.active_colors
547 ColorsNormal = Colors.Normal
546 ColorsNormal = Colors.Normal
548 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
547 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
549 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
548 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
550 src = []
549 src = []
551 if filename == "<string>" and hasattr(self, "_exec_filename"):
550 if filename == "<string>" and hasattr(self, "_exec_filename"):
552 filename = self._exec_filename
551 filename = self._exec_filename
553
552
554 for lineno in range(first, last+1):
553 for lineno in range(first, last+1):
555 line = linecache.getline(filename, lineno)
554 line = linecache.getline(filename, lineno)
556 if not line:
555 if not line:
557 break
556 break
558
557
559 if lineno == self.curframe.f_lineno:
558 if lineno == self.curframe.f_lineno:
560 line = self.__format_line(
559 line = self.__format_line(
561 tpl_line_em, filename, lineno, line, arrow=True
560 tpl_line_em, filename, lineno, line, arrow=True
562 )
561 )
563 else:
562 else:
564 line = self.__format_line(
563 line = self.__format_line(
565 tpl_line, filename, lineno, line, arrow=False
564 tpl_line, filename, lineno, line, arrow=False
566 )
565 )
567
566
568 src.append(line)
567 src.append(line)
569 self.lineno = lineno
568 self.lineno = lineno
570
569
571 print(''.join(src), file=self.stdout)
570 print(''.join(src), file=self.stdout)
572
571
573 except KeyboardInterrupt:
572 except KeyboardInterrupt:
574 pass
573 pass
575
574
576 def do_skip_predicates(self, args):
575 def do_skip_predicates(self, args):
577 """
576 """
578 Turn on/off individual predicates as to whether a frame should be hidden/skip.
577 Turn on/off individual predicates as to whether a frame should be hidden/skip.
579
578
580 The global option to skip (or not) hidden frames is set with skip_hidden
579 The global option to skip (or not) hidden frames is set with skip_hidden
581
580
582 To change the value of a predicate
581 To change the value of a predicate
583
582
584 skip_predicates key [true|false]
583 skip_predicates key [true|false]
585
584
586 Call without arguments to see the current values.
585 Call without arguments to see the current values.
587
586
588 To permanently change the value of an option add the corresponding
587 To permanently change the value of an option add the corresponding
589 command to your ``~/.pdbrc`` file. If you are programmatically using the
588 command to your ``~/.pdbrc`` file. If you are programmatically using the
590 Pdb instance you can also change the ``default_predicates`` class
589 Pdb instance you can also change the ``default_predicates`` class
591 attribute.
590 attribute.
592 """
591 """
593 if not args.strip():
592 if not args.strip():
594 print("current predicates:")
593 print("current predicates:")
595 for (p, v) in self._predicates.items():
594 for (p, v) in self._predicates.items():
596 print(" ", p, ":", v)
595 print(" ", p, ":", v)
597 return
596 return
598 type_value = args.strip().split(" ")
597 type_value = args.strip().split(" ")
599 if len(type_value) != 2:
598 if len(type_value) != 2:
600 print(
599 print(
601 f"Usage: skip_predicates <type> <value>, with <type> one of {set(self._predicates.keys())}"
600 f"Usage: skip_predicates <type> <value>, with <type> one of {set(self._predicates.keys())}"
602 )
601 )
603 return
602 return
604
603
605 type_, value = type_value
604 type_, value = type_value
606 if type_ not in self._predicates:
605 if type_ not in self._predicates:
607 print(f"{type_!r} not in {set(self._predicates.keys())}")
606 print(f"{type_!r} not in {set(self._predicates.keys())}")
608 return
607 return
609 if value.lower() not in ("true", "yes", "1", "no", "false", "0"):
608 if value.lower() not in ("true", "yes", "1", "no", "false", "0"):
610 print(
609 print(
611 f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')"
610 f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')"
612 )
611 )
613 return
612 return
614
613
615 self._predicates[type_] = value.lower() in ("true", "yes", "1")
614 self._predicates[type_] = value.lower() in ("true", "yes", "1")
616 if not any(self._predicates.values()):
615 if not any(self._predicates.values()):
617 print(
616 print(
618 "Warning, all predicates set to False, skip_hidden may not have any effects."
617 "Warning, all predicates set to False, skip_hidden may not have any effects."
619 )
618 )
620
619
621 def do_skip_hidden(self, arg):
620 def do_skip_hidden(self, arg):
622 """
621 """
623 Change whether or not we should skip frames with the
622 Change whether or not we should skip frames with the
624 __tracebackhide__ attribute.
623 __tracebackhide__ attribute.
625 """
624 """
626 if not arg.strip():
625 if not arg.strip():
627 print(
626 print(
628 f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change."
627 f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change."
629 )
628 )
630 elif arg.strip().lower() in ("true", "yes"):
629 elif arg.strip().lower() in ("true", "yes"):
631 self.skip_hidden = True
630 self.skip_hidden = True
632 elif arg.strip().lower() in ("false", "no"):
631 elif arg.strip().lower() in ("false", "no"):
633 self.skip_hidden = False
632 self.skip_hidden = False
634 if not any(self._predicates.values()):
633 if not any(self._predicates.values()):
635 print(
634 print(
636 "Warning, all predicates set to False, skip_hidden may not have any effects."
635 "Warning, all predicates set to False, skip_hidden may not have any effects."
637 )
636 )
638
637
639 def do_list(self, arg):
638 def do_list(self, arg):
640 """Print lines of code from the current stack frame
639 """Print lines of code from the current stack frame
641 """
640 """
642 self.lastcmd = 'list'
641 self.lastcmd = 'list'
643 last = None
642 last = None
644 if arg:
643 if arg:
645 try:
644 try:
646 x = eval(arg, {}, {})
645 x = eval(arg, {}, {})
647 if type(x) == type(()):
646 if type(x) == type(()):
648 first, last = x
647 first, last = x
649 first = int(first)
648 first = int(first)
650 last = int(last)
649 last = int(last)
651 if last < first:
650 if last < first:
652 # Assume it's a count
651 # Assume it's a count
653 last = first + last
652 last = first + last
654 else:
653 else:
655 first = max(1, int(x) - 5)
654 first = max(1, int(x) - 5)
656 except:
655 except:
657 print('*** Error in argument:', repr(arg), file=self.stdout)
656 print('*** Error in argument:', repr(arg), file=self.stdout)
658 return
657 return
659 elif self.lineno is None:
658 elif self.lineno is None:
660 first = max(1, self.curframe.f_lineno - 5)
659 first = max(1, self.curframe.f_lineno - 5)
661 else:
660 else:
662 first = self.lineno + 1
661 first = self.lineno + 1
663 if last is None:
662 if last is None:
664 last = first + 10
663 last = first + 10
665 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
664 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
666
665
667 # vds: >>
666 # vds: >>
668 lineno = first
667 lineno = first
669 filename = self.curframe.f_code.co_filename
668 filename = self.curframe.f_code.co_filename
670 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
669 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
671 # vds: <<
670 # vds: <<
672
671
673 do_l = do_list
672 do_l = do_list
674
673
675 def getsourcelines(self, obj):
674 def getsourcelines(self, obj):
676 lines, lineno = inspect.findsource(obj)
675 lines, lineno = inspect.findsource(obj)
677 if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj):
676 if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj):
678 # must be a module frame: do not try to cut a block out of it
677 # must be a module frame: do not try to cut a block out of it
679 return lines, 1
678 return lines, 1
680 elif inspect.ismodule(obj):
679 elif inspect.ismodule(obj):
681 return lines, 1
680 return lines, 1
682 return inspect.getblock(lines[lineno:]), lineno+1
681 return inspect.getblock(lines[lineno:]), lineno+1
683
682
684 def do_longlist(self, arg):
683 def do_longlist(self, arg):
685 """Print lines of code from the current stack frame.
684 """Print lines of code from the current stack frame.
686
685
687 Shows more lines than 'list' does.
686 Shows more lines than 'list' does.
688 """
687 """
689 self.lastcmd = 'longlist'
688 self.lastcmd = 'longlist'
690 try:
689 try:
691 lines, lineno = self.getsourcelines(self.curframe)
690 lines, lineno = self.getsourcelines(self.curframe)
692 except OSError as err:
691 except OSError as err:
693 self.error(err)
692 self.error(err)
694 return
693 return
695 last = lineno + len(lines)
694 last = lineno + len(lines)
696 self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
695 self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
697 do_ll = do_longlist
696 do_ll = do_longlist
698
697
699 def do_debug(self, arg):
698 def do_debug(self, arg):
700 """debug code
699 """debug code
701 Enter a recursive debugger that steps through the code
700 Enter a recursive debugger that steps through the code
702 argument (which is an arbitrary expression or statement to be
701 argument (which is an arbitrary expression or statement to be
703 executed in the current environment).
702 executed in the current environment).
704 """
703 """
705 trace_function = sys.gettrace()
704 trace_function = sys.gettrace()
706 sys.settrace(None)
705 sys.settrace(None)
707 globals = self.curframe.f_globals
706 globals = self.curframe.f_globals
708 locals = self.curframe_locals
707 locals = self.curframe_locals
709 p = self.__class__(completekey=self.completekey,
708 p = self.__class__(completekey=self.completekey,
710 stdin=self.stdin, stdout=self.stdout)
709 stdin=self.stdin, stdout=self.stdout)
711 p.use_rawinput = self.use_rawinput
710 p.use_rawinput = self.use_rawinput
712 p.prompt = "(%s) " % self.prompt.strip()
711 p.prompt = "(%s) " % self.prompt.strip()
713 self.message("ENTERING RECURSIVE DEBUGGER")
712 self.message("ENTERING RECURSIVE DEBUGGER")
714 sys.call_tracing(p.run, (arg, globals, locals))
713 sys.call_tracing(p.run, (arg, globals, locals))
715 self.message("LEAVING RECURSIVE DEBUGGER")
714 self.message("LEAVING RECURSIVE DEBUGGER")
716 sys.settrace(trace_function)
715 sys.settrace(trace_function)
717 self.lastcmd = p.lastcmd
716 self.lastcmd = p.lastcmd
718
717
719 def do_pdef(self, arg):
718 def do_pdef(self, arg):
720 """Print the call signature for any callable object.
719 """Print the call signature for any callable object.
721
720
722 The debugger interface to %pdef"""
721 The debugger interface to %pdef"""
723 namespaces = [
722 namespaces = [
724 ("Locals", self.curframe_locals),
723 ("Locals", self.curframe_locals),
725 ("Globals", self.curframe.f_globals),
724 ("Globals", self.curframe.f_globals),
726 ]
725 ]
727 self.shell.find_line_magic("pdef")(arg, namespaces=namespaces)
726 self.shell.find_line_magic("pdef")(arg, namespaces=namespaces)
728
727
729 def do_pdoc(self, arg):
728 def do_pdoc(self, arg):
730 """Print the docstring for an object.
729 """Print the docstring for an object.
731
730
732 The debugger interface to %pdoc."""
731 The debugger interface to %pdoc."""
733 namespaces = [
732 namespaces = [
734 ("Locals", self.curframe_locals),
733 ("Locals", self.curframe_locals),
735 ("Globals", self.curframe.f_globals),
734 ("Globals", self.curframe.f_globals),
736 ]
735 ]
737 self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces)
736 self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces)
738
737
739 def do_pfile(self, arg):
738 def do_pfile(self, arg):
740 """Print (or run through pager) the file where an object is defined.
739 """Print (or run through pager) the file where an object is defined.
741
740
742 The debugger interface to %pfile.
741 The debugger interface to %pfile.
743 """
742 """
744 namespaces = [
743 namespaces = [
745 ("Locals", self.curframe_locals),
744 ("Locals", self.curframe_locals),
746 ("Globals", self.curframe.f_globals),
745 ("Globals", self.curframe.f_globals),
747 ]
746 ]
748 self.shell.find_line_magic("pfile")(arg, namespaces=namespaces)
747 self.shell.find_line_magic("pfile")(arg, namespaces=namespaces)
749
748
750 def do_pinfo(self, arg):
749 def do_pinfo(self, arg):
751 """Provide detailed information about an object.
750 """Provide detailed information about an object.
752
751
753 The debugger interface to %pinfo, i.e., obj?."""
752 The debugger interface to %pinfo, i.e., obj?."""
754 namespaces = [
753 namespaces = [
755 ("Locals", self.curframe_locals),
754 ("Locals", self.curframe_locals),
756 ("Globals", self.curframe.f_globals),
755 ("Globals", self.curframe.f_globals),
757 ]
756 ]
758 self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces)
757 self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces)
759
758
760 def do_pinfo2(self, arg):
759 def do_pinfo2(self, arg):
761 """Provide extra detailed information about an object.
760 """Provide extra detailed information about an object.
762
761
763 The debugger interface to %pinfo2, i.e., obj??."""
762 The debugger interface to %pinfo2, i.e., obj??."""
764 namespaces = [
763 namespaces = [
765 ("Locals", self.curframe_locals),
764 ("Locals", self.curframe_locals),
766 ("Globals", self.curframe.f_globals),
765 ("Globals", self.curframe.f_globals),
767 ]
766 ]
768 self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces)
767 self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces)
769
768
770 def do_psource(self, arg):
769 def do_psource(self, arg):
771 """Print (or run through pager) the source code for an object."""
770 """Print (or run through pager) the source code for an object."""
772 namespaces = [
771 namespaces = [
773 ("Locals", self.curframe_locals),
772 ("Locals", self.curframe_locals),
774 ("Globals", self.curframe.f_globals),
773 ("Globals", self.curframe.f_globals),
775 ]
774 ]
776 self.shell.find_line_magic("psource")(arg, namespaces=namespaces)
775 self.shell.find_line_magic("psource")(arg, namespaces=namespaces)
777
776
778 def do_where(self, arg):
777 def do_where(self, arg):
779 """w(here)
778 """w(here)
780 Print a stack trace, with the most recent frame at the bottom.
779 Print a stack trace, with the most recent frame at the bottom.
781 An arrow indicates the "current frame", which determines the
780 An arrow indicates the "current frame", which determines the
782 context of most commands. 'bt' is an alias for this command.
781 context of most commands. 'bt' is an alias for this command.
783
782
784 Take a number as argument as an (optional) number of context line to
783 Take a number as argument as an (optional) number of context line to
785 print"""
784 print"""
786 if arg:
785 if arg:
787 try:
786 try:
788 context = int(arg)
787 context = int(arg)
789 except ValueError as err:
788 except ValueError as err:
790 self.error(err)
789 self.error(err)
791 return
790 return
792 self.print_stack_trace(context)
791 self.print_stack_trace(context)
793 else:
792 else:
794 self.print_stack_trace()
793 self.print_stack_trace()
795
794
796 do_w = do_where
795 do_w = do_where
797
796
798 def break_anywhere(self, frame):
797 def break_anywhere(self, frame):
799 """
798 """
800 _stop_in_decorator_internals is overly restrictive, as we may still want
799 _stop_in_decorator_internals is overly restrictive, as we may still want
801 to trace function calls, so we need to also update break_anywhere so
800 to trace function calls, so we need to also update break_anywhere so
802 that is we don't `stop_here`, because of debugger skip, we may still
801 that is we don't `stop_here`, because of debugger skip, we may still
803 stop at any point inside the function
802 stop at any point inside the function
804
803
805 """
804 """
806
805
807 sup = super().break_anywhere(frame)
806 sup = super().break_anywhere(frame)
808 if sup:
807 if sup:
809 return sup
808 return sup
810 if self._predicates["debuggerskip"]:
809 if self._predicates["debuggerskip"]:
811 if DEBUGGERSKIP in frame.f_code.co_varnames:
810 if DEBUGGERSKIP in frame.f_code.co_varnames:
812 return True
811 return True
813 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
812 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
814 return True
813 return True
815 return False
814 return False
816
815
817 def _is_in_decorator_internal_and_should_skip(self, frame):
816 def _is_in_decorator_internal_and_should_skip(self, frame):
818 """
817 """
819 Utility to tell us whether we are in a decorator internal and should stop.
818 Utility to tell us whether we are in a decorator internal and should stop.
820
819
821 """
820 """
822
821
823 # if we are disabled don't skip
822 # if we are disabled don't skip
824 if not self._predicates["debuggerskip"]:
823 if not self._predicates["debuggerskip"]:
825 return False
824 return False
826
825
827 # if frame is tagged, skip by default.
826 # if frame is tagged, skip by default.
828 if DEBUGGERSKIP in frame.f_code.co_varnames:
827 if DEBUGGERSKIP in frame.f_code.co_varnames:
829 return True
828 return True
830
829
831 # if one of the parent frame value set to True skip as well.
830 # if one of the parent frame value set to True skip as well.
832
831
833 cframe = frame
832 cframe = frame
834 while getattr(cframe, "f_back", None):
833 while getattr(cframe, "f_back", None):
835 cframe = cframe.f_back
834 cframe = cframe.f_back
836 if self._get_frame_locals(cframe).get(DEBUGGERSKIP):
835 if self._get_frame_locals(cframe).get(DEBUGGERSKIP):
837 return True
836 return True
838
837
839 return False
838 return False
840
839
841 def stop_here(self, frame):
840 def stop_here(self, frame):
842
841
843 if self._is_in_decorator_internal_and_should_skip(frame) is True:
842 if self._is_in_decorator_internal_and_should_skip(frame) is True:
844 return False
843 return False
845
844
846 hidden = False
845 hidden = False
847 if self.skip_hidden:
846 if self.skip_hidden:
848 hidden = self._hidden_predicate(frame)
847 hidden = self._hidden_predicate(frame)
849 if hidden:
848 if hidden:
850 if self.report_skipped:
849 if self.report_skipped:
851 Colors = self.color_scheme_table.active_colors
850 Colors = self.color_scheme_table.active_colors
852 ColorsNormal = Colors.Normal
851 ColorsNormal = Colors.Normal
853 print(
852 print(
854 f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n"
853 f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n"
855 )
854 )
856 return super().stop_here(frame)
855 return super().stop_here(frame)
857
856
858 def do_up(self, arg):
857 def do_up(self, arg):
859 """u(p) [count]
858 """u(p) [count]
860 Move the current frame count (default one) levels up in the
859 Move the current frame count (default one) levels up in the
861 stack trace (to an older frame).
860 stack trace (to an older frame).
862
861
863 Will skip hidden frames.
862 Will skip hidden frames.
864 """
863 """
865 # modified version of upstream that skips
864 # modified version of upstream that skips
866 # frames with __tracebackhide__
865 # frames with __tracebackhide__
867 if self.curindex == 0:
866 if self.curindex == 0:
868 self.error("Oldest frame")
867 self.error("Oldest frame")
869 return
868 return
870 try:
869 try:
871 count = int(arg or 1)
870 count = int(arg or 1)
872 except ValueError:
871 except ValueError:
873 self.error("Invalid frame count (%s)" % arg)
872 self.error("Invalid frame count (%s)" % arg)
874 return
873 return
875 skipped = 0
874 skipped = 0
876 if count < 0:
875 if count < 0:
877 _newframe = 0
876 _newframe = 0
878 else:
877 else:
879 counter = 0
878 counter = 0
880 hidden_frames = self.hidden_frames(self.stack)
879 hidden_frames = self.hidden_frames(self.stack)
881 for i in range(self.curindex - 1, -1, -1):
880 for i in range(self.curindex - 1, -1, -1):
882 if hidden_frames[i] and self.skip_hidden:
881 if hidden_frames[i] and self.skip_hidden:
883 skipped += 1
882 skipped += 1
884 continue
883 continue
885 counter += 1
884 counter += 1
886 if counter >= count:
885 if counter >= count:
887 break
886 break
888 else:
887 else:
889 # if no break occurred.
888 # if no break occurred.
890 self.error(
889 self.error(
891 "all frames above hidden, use `skip_hidden False` to get get into those."
890 "all frames above hidden, use `skip_hidden False` to get get into those."
892 )
891 )
893 return
892 return
894
893
895 Colors = self.color_scheme_table.active_colors
894 Colors = self.color_scheme_table.active_colors
896 ColorsNormal = Colors.Normal
895 ColorsNormal = Colors.Normal
897 _newframe = i
896 _newframe = i
898 self._select_frame(_newframe)
897 self._select_frame(_newframe)
899 if skipped:
898 if skipped:
900 print(
899 print(
901 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
900 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
902 )
901 )
903
902
904 def do_down(self, arg):
903 def do_down(self, arg):
905 """d(own) [count]
904 """d(own) [count]
906 Move the current frame count (default one) levels down in the
905 Move the current frame count (default one) levels down in the
907 stack trace (to a newer frame).
906 stack trace (to a newer frame).
908
907
909 Will skip hidden frames.
908 Will skip hidden frames.
910 """
909 """
911 if self.curindex + 1 == len(self.stack):
910 if self.curindex + 1 == len(self.stack):
912 self.error("Newest frame")
911 self.error("Newest frame")
913 return
912 return
914 try:
913 try:
915 count = int(arg or 1)
914 count = int(arg or 1)
916 except ValueError:
915 except ValueError:
917 self.error("Invalid frame count (%s)" % arg)
916 self.error("Invalid frame count (%s)" % arg)
918 return
917 return
919 if count < 0:
918 if count < 0:
920 _newframe = len(self.stack) - 1
919 _newframe = len(self.stack) - 1
921 else:
920 else:
922 counter = 0
921 counter = 0
923 skipped = 0
922 skipped = 0
924 hidden_frames = self.hidden_frames(self.stack)
923 hidden_frames = self.hidden_frames(self.stack)
925 for i in range(self.curindex + 1, len(self.stack)):
924 for i in range(self.curindex + 1, len(self.stack)):
926 if hidden_frames[i] and self.skip_hidden:
925 if hidden_frames[i] and self.skip_hidden:
927 skipped += 1
926 skipped += 1
928 continue
927 continue
929 counter += 1
928 counter += 1
930 if counter >= count:
929 if counter >= count:
931 break
930 break
932 else:
931 else:
933 self.error(
932 self.error(
934 "all frames below hidden, use `skip_hidden False` to get get into those."
933 "all frames below hidden, use `skip_hidden False` to get get into those."
935 )
934 )
936 return
935 return
937
936
938 Colors = self.color_scheme_table.active_colors
937 Colors = self.color_scheme_table.active_colors
939 ColorsNormal = Colors.Normal
938 ColorsNormal = Colors.Normal
940 if skipped:
939 if skipped:
941 print(
940 print(
942 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
941 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
943 )
942 )
944 _newframe = i
943 _newframe = i
945
944
946 self._select_frame(_newframe)
945 self._select_frame(_newframe)
947
946
948 do_d = do_down
947 do_d = do_down
949 do_u = do_up
948 do_u = do_up
950
949
951 def do_context(self, context):
950 def do_context(self, context):
952 """context number_of_lines
951 """context number_of_lines
953 Set the number of lines of source code to show when displaying
952 Set the number of lines of source code to show when displaying
954 stacktrace information.
953 stacktrace information.
955 """
954 """
956 try:
955 try:
957 new_context = int(context)
956 new_context = int(context)
958 if new_context <= 0:
957 if new_context <= 0:
959 raise ValueError()
958 raise ValueError()
960 self.context = new_context
959 self.context = new_context
961 except ValueError:
960 except ValueError:
962 self.error("The 'context' command requires a positive integer argument.")
961 self.error("The 'context' command requires a positive integer argument.")
963
962
964
963
965 class InterruptiblePdb(Pdb):
964 class InterruptiblePdb(Pdb):
966 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
965 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
967
966
968 def cmdloop(self, intro=None):
967 def cmdloop(self, intro=None):
969 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
968 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
970 try:
969 try:
971 return OldPdb.cmdloop(self, intro=intro)
970 return OldPdb.cmdloop(self, intro=intro)
972 except KeyboardInterrupt:
971 except KeyboardInterrupt:
973 self.stop_here = lambda frame: False
972 self.stop_here = lambda frame: False
974 self.do_quit("")
973 self.do_quit("")
975 sys.settrace(None)
974 sys.settrace(None)
976 self.quitting = False
975 self.quitting = False
977 raise
976 raise
978
977
979 def _cmdloop(self):
978 def _cmdloop(self):
980 while True:
979 while True:
981 try:
980 try:
982 # keyboard interrupts allow for an easy way to cancel
981 # keyboard interrupts allow for an easy way to cancel
983 # the current command, so allow them during interactive input
982 # the current command, so allow them during interactive input
984 self.allow_kbdint = True
983 self.allow_kbdint = True
985 self.cmdloop()
984 self.cmdloop()
986 self.allow_kbdint = False
985 self.allow_kbdint = False
987 break
986 break
988 except KeyboardInterrupt:
987 except KeyboardInterrupt:
989 self.message('--KeyboardInterrupt--')
988 self.message('--KeyboardInterrupt--')
990 raise
989 raise
991
990
992
991
993 def set_trace(frame=None):
992 def set_trace(frame=None):
994 """
993 """
995 Start debugging from `frame`.
994 Start debugging from `frame`.
996
995
997 If frame is not specified, debugging starts from caller's frame.
996 If frame is not specified, debugging starts from caller's frame.
998 """
997 """
999 Pdb().set_trace(frame or sys._getframe().f_back)
998 Pdb().set_trace(frame or sys._getframe().f_back)
@@ -1,166 +1,165 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 Color schemes for exception handling code in IPython.
3 Color schemes for exception handling code in IPython.
4 """
4 """
5
5
6 import os
6 import os
7 import warnings
8
7
9 #*****************************************************************************
8 #*****************************************************************************
10 # Copyright (C) 2005-2006 Fernando Perez <fperez@colorado.edu>
9 # Copyright (C) 2005-2006 Fernando Perez <fperez@colorado.edu>
11 #
10 #
12 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
14 #*****************************************************************************
13 #*****************************************************************************
15
14
16 from IPython.utils.coloransi import ColorSchemeTable, TermColors, ColorScheme
15 from IPython.utils.coloransi import ColorSchemeTable, TermColors, ColorScheme
17
16
18 def exception_colors():
17 def exception_colors():
19 """Return a color table with fields for exception reporting.
18 """Return a color table with fields for exception reporting.
20
19
21 The table is an instance of ColorSchemeTable with schemes added for
20 The table is an instance of ColorSchemeTable with schemes added for
22 'Neutral', 'Linux', 'LightBG' and 'NoColor' and fields for exception handling filled
21 'Neutral', 'Linux', 'LightBG' and 'NoColor' and fields for exception handling filled
23 in.
22 in.
24
23
25 Examples:
24 Examples:
26
25
27 >>> ec = exception_colors()
26 >>> ec = exception_colors()
28 >>> ec.active_scheme_name
27 >>> ec.active_scheme_name
29 ''
28 ''
30 >>> print(ec.active_colors)
29 >>> print(ec.active_colors)
31 None
30 None
32
31
33 Now we activate a color scheme:
32 Now we activate a color scheme:
34 >>> ec.set_active_scheme('NoColor')
33 >>> ec.set_active_scheme('NoColor')
35 >>> ec.active_scheme_name
34 >>> ec.active_scheme_name
36 'NoColor'
35 'NoColor'
37 >>> sorted(ec.active_colors.keys())
36 >>> sorted(ec.active_colors.keys())
38 ['Normal', 'caret', 'em', 'excName', 'filename', 'filenameEm', 'line',
37 ['Normal', 'caret', 'em', 'excName', 'filename', 'filenameEm', 'line',
39 'lineno', 'linenoEm', 'name', 'nameEm', 'normalEm', 'topline', 'vName',
38 'lineno', 'linenoEm', 'name', 'nameEm', 'normalEm', 'topline', 'vName',
40 'val', 'valEm']
39 'val', 'valEm']
41 """
40 """
42
41
43 ex_colors = ColorSchemeTable()
42 ex_colors = ColorSchemeTable()
44
43
45 # Populate it with color schemes
44 # Populate it with color schemes
46 C = TermColors # shorthand and local lookup
45 C = TermColors # shorthand and local lookup
47 ex_colors.add_scheme(ColorScheme(
46 ex_colors.add_scheme(ColorScheme(
48 'NoColor',
47 'NoColor',
49 # The color to be used for the top line
48 # The color to be used for the top line
50 topline = C.NoColor,
49 topline = C.NoColor,
51
50
52 # The colors to be used in the traceback
51 # The colors to be used in the traceback
53 filename = C.NoColor,
52 filename = C.NoColor,
54 lineno = C.NoColor,
53 lineno = C.NoColor,
55 name = C.NoColor,
54 name = C.NoColor,
56 vName = C.NoColor,
55 vName = C.NoColor,
57 val = C.NoColor,
56 val = C.NoColor,
58 em = C.NoColor,
57 em = C.NoColor,
59
58
60 # Emphasized colors for the last frame of the traceback
59 # Emphasized colors for the last frame of the traceback
61 normalEm = C.NoColor,
60 normalEm = C.NoColor,
62 filenameEm = C.NoColor,
61 filenameEm = C.NoColor,
63 linenoEm = C.NoColor,
62 linenoEm = C.NoColor,
64 nameEm = C.NoColor,
63 nameEm = C.NoColor,
65 valEm = C.NoColor,
64 valEm = C.NoColor,
66
65
67 # Colors for printing the exception
66 # Colors for printing the exception
68 excName = C.NoColor,
67 excName = C.NoColor,
69 line = C.NoColor,
68 line = C.NoColor,
70 caret = C.NoColor,
69 caret = C.NoColor,
71 Normal = C.NoColor
70 Normal = C.NoColor
72 ))
71 ))
73
72
74 # make some schemes as instances so we can copy them for modification easily
73 # make some schemes as instances so we can copy them for modification easily
75 ex_colors.add_scheme(ColorScheme(
74 ex_colors.add_scheme(ColorScheme(
76 'Linux',
75 'Linux',
77 # The color to be used for the top line
76 # The color to be used for the top line
78 topline = C.LightRed,
77 topline = C.LightRed,
79
78
80 # The colors to be used in the traceback
79 # The colors to be used in the traceback
81 filename = C.Green,
80 filename = C.Green,
82 lineno = C.Green,
81 lineno = C.Green,
83 name = C.Purple,
82 name = C.Purple,
84 vName = C.Cyan,
83 vName = C.Cyan,
85 val = C.Green,
84 val = C.Green,
86 em = C.LightCyan,
85 em = C.LightCyan,
87
86
88 # Emphasized colors for the last frame of the traceback
87 # Emphasized colors for the last frame of the traceback
89 normalEm = C.LightCyan,
88 normalEm = C.LightCyan,
90 filenameEm = C.LightGreen,
89 filenameEm = C.LightGreen,
91 linenoEm = C.LightGreen,
90 linenoEm = C.LightGreen,
92 nameEm = C.LightPurple,
91 nameEm = C.LightPurple,
93 valEm = C.LightBlue,
92 valEm = C.LightBlue,
94
93
95 # Colors for printing the exception
94 # Colors for printing the exception
96 excName = C.LightRed,
95 excName = C.LightRed,
97 line = C.Yellow,
96 line = C.Yellow,
98 caret = C.White,
97 caret = C.White,
99 Normal = C.Normal
98 Normal = C.Normal
100 ))
99 ))
101
100
102 # For light backgrounds, swap dark/light colors
101 # For light backgrounds, swap dark/light colors
103 ex_colors.add_scheme(ColorScheme(
102 ex_colors.add_scheme(ColorScheme(
104 'LightBG',
103 'LightBG',
105 # The color to be used for the top line
104 # The color to be used for the top line
106 topline = C.Red,
105 topline = C.Red,
107
106
108 # The colors to be used in the traceback
107 # The colors to be used in the traceback
109 filename = C.LightGreen,
108 filename = C.LightGreen,
110 lineno = C.LightGreen,
109 lineno = C.LightGreen,
111 name = C.LightPurple,
110 name = C.LightPurple,
112 vName = C.Cyan,
111 vName = C.Cyan,
113 val = C.LightGreen,
112 val = C.LightGreen,
114 em = C.Cyan,
113 em = C.Cyan,
115
114
116 # Emphasized colors for the last frame of the traceback
115 # Emphasized colors for the last frame of the traceback
117 normalEm = C.Cyan,
116 normalEm = C.Cyan,
118 filenameEm = C.Green,
117 filenameEm = C.Green,
119 linenoEm = C.Green,
118 linenoEm = C.Green,
120 nameEm = C.Purple,
119 nameEm = C.Purple,
121 valEm = C.Blue,
120 valEm = C.Blue,
122
121
123 # Colors for printing the exception
122 # Colors for printing the exception
124 excName = C.Red,
123 excName = C.Red,
125 #line = C.Brown, # brown often is displayed as yellow
124 #line = C.Brown, # brown often is displayed as yellow
126 line = C.Red,
125 line = C.Red,
127 caret = C.Normal,
126 caret = C.Normal,
128 Normal = C.Normal,
127 Normal = C.Normal,
129 ))
128 ))
130
129
131 ex_colors.add_scheme(ColorScheme(
130 ex_colors.add_scheme(ColorScheme(
132 'Neutral',
131 'Neutral',
133 # The color to be used for the top line
132 # The color to be used for the top line
134 topline = C.Red,
133 topline = C.Red,
135
134
136 # The colors to be used in the traceback
135 # The colors to be used in the traceback
137 filename = C.LightGreen,
136 filename = C.LightGreen,
138 lineno = C.LightGreen,
137 lineno = C.LightGreen,
139 name = C.LightPurple,
138 name = C.LightPurple,
140 vName = C.Cyan,
139 vName = C.Cyan,
141 val = C.LightGreen,
140 val = C.LightGreen,
142 em = C.Cyan,
141 em = C.Cyan,
143
142
144 # Emphasized colors for the last frame of the traceback
143 # Emphasized colors for the last frame of the traceback
145 normalEm = C.Cyan,
144 normalEm = C.Cyan,
146 filenameEm = C.Green,
145 filenameEm = C.Green,
147 linenoEm = C.Green,
146 linenoEm = C.Green,
148 nameEm = C.Purple,
147 nameEm = C.Purple,
149 valEm = C.Blue,
148 valEm = C.Blue,
150
149
151 # Colors for printing the exception
150 # Colors for printing the exception
152 excName = C.Red,
151 excName = C.Red,
153 #line = C.Brown, # brown often is displayed as yellow
152 #line = C.Brown, # brown often is displayed as yellow
154 line = C.Red,
153 line = C.Red,
155 caret = C.Normal,
154 caret = C.Normal,
156 Normal = C.Normal,
155 Normal = C.Normal,
157 ))
156 ))
158
157
159 # Hack: the 'neutral' colours are not very visible on a dark background on
158 # Hack: the 'neutral' colours are not very visible on a dark background on
160 # Windows. Since Windows command prompts have a dark background by default, and
159 # Windows. Since Windows command prompts have a dark background by default, and
161 # relatively few users are likely to alter that, we will use the 'Linux' colours,
160 # relatively few users are likely to alter that, we will use the 'Linux' colours,
162 # designed for a dark background, as the default on Windows.
161 # designed for a dark background, as the default on Windows.
163 if os.name == "nt":
162 if os.name == "nt":
164 ex_colors.add_scheme(ex_colors['Linux'].copy('Neutral'))
163 ex_colors.add_scheme(ex_colors['Linux'].copy('Neutral'))
165
164
166 return ex_colors
165 return ex_colors
@@ -1,1027 +1,1026 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Display formatters.
2 """Display formatters.
3
3
4 Inheritance diagram:
4 Inheritance diagram:
5
5
6 .. inheritance-diagram:: IPython.core.formatters
6 .. inheritance-diagram:: IPython.core.formatters
7 :parts: 3
7 :parts: 3
8 """
8 """
9
9
10 # Copyright (c) IPython Development Team.
10 # Copyright (c) IPython Development Team.
11 # Distributed under the terms of the Modified BSD License.
11 # Distributed under the terms of the Modified BSD License.
12
12
13 import abc
13 import abc
14 import json
15 import sys
14 import sys
16 import traceback
15 import traceback
17 import warnings
16 import warnings
18 from io import StringIO
17 from io import StringIO
19
18
20 from decorator import decorator
19 from decorator import decorator
21
20
22 from traitlets.config.configurable import Configurable
21 from traitlets.config.configurable import Configurable
23 from .getipython import get_ipython
22 from .getipython import get_ipython
24 from ..utils.sentinel import Sentinel
23 from ..utils.sentinel import Sentinel
25 from ..utils.dir2 import get_real_method
24 from ..utils.dir2 import get_real_method
26 from ..lib import pretty
25 from ..lib import pretty
27 from traitlets import (
26 from traitlets import (
28 Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
27 Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
29 ForwardDeclaredInstance,
28 ForwardDeclaredInstance,
30 default, observe,
29 default, observe,
31 )
30 )
32
31
33
32
34 class DisplayFormatter(Configurable):
33 class DisplayFormatter(Configurable):
35
34
36 active_types = List(Unicode(),
35 active_types = List(Unicode(),
37 help="""List of currently active mime-types to display.
36 help="""List of currently active mime-types to display.
38 You can use this to set a white-list for formats to display.
37 You can use this to set a white-list for formats to display.
39
38
40 Most users will not need to change this value.
39 Most users will not need to change this value.
41 """).tag(config=True)
40 """).tag(config=True)
42
41
43 @default('active_types')
42 @default('active_types')
44 def _active_types_default(self):
43 def _active_types_default(self):
45 return self.format_types
44 return self.format_types
46
45
47 @observe('active_types')
46 @observe('active_types')
48 def _active_types_changed(self, change):
47 def _active_types_changed(self, change):
49 for key, formatter in self.formatters.items():
48 for key, formatter in self.formatters.items():
50 if key in change['new']:
49 if key in change['new']:
51 formatter.enabled = True
50 formatter.enabled = True
52 else:
51 else:
53 formatter.enabled = False
52 formatter.enabled = False
54
53
55 ipython_display_formatter = ForwardDeclaredInstance('FormatterABC')
54 ipython_display_formatter = ForwardDeclaredInstance('FormatterABC')
56 @default('ipython_display_formatter')
55 @default('ipython_display_formatter')
57 def _default_formatter(self):
56 def _default_formatter(self):
58 return IPythonDisplayFormatter(parent=self)
57 return IPythonDisplayFormatter(parent=self)
59
58
60 mimebundle_formatter = ForwardDeclaredInstance('FormatterABC')
59 mimebundle_formatter = ForwardDeclaredInstance('FormatterABC')
61 @default('mimebundle_formatter')
60 @default('mimebundle_formatter')
62 def _default_mime_formatter(self):
61 def _default_mime_formatter(self):
63 return MimeBundleFormatter(parent=self)
62 return MimeBundleFormatter(parent=self)
64
63
65 # A dict of formatter whose keys are format types (MIME types) and whose
64 # A dict of formatter whose keys are format types (MIME types) and whose
66 # values are subclasses of BaseFormatter.
65 # values are subclasses of BaseFormatter.
67 formatters = Dict()
66 formatters = Dict()
68 @default('formatters')
67 @default('formatters')
69 def _formatters_default(self):
68 def _formatters_default(self):
70 """Activate the default formatters."""
69 """Activate the default formatters."""
71 formatter_classes = [
70 formatter_classes = [
72 PlainTextFormatter,
71 PlainTextFormatter,
73 HTMLFormatter,
72 HTMLFormatter,
74 MarkdownFormatter,
73 MarkdownFormatter,
75 SVGFormatter,
74 SVGFormatter,
76 PNGFormatter,
75 PNGFormatter,
77 PDFFormatter,
76 PDFFormatter,
78 JPEGFormatter,
77 JPEGFormatter,
79 LatexFormatter,
78 LatexFormatter,
80 JSONFormatter,
79 JSONFormatter,
81 JavascriptFormatter
80 JavascriptFormatter
82 ]
81 ]
83 d = {}
82 d = {}
84 for cls in formatter_classes:
83 for cls in formatter_classes:
85 f = cls(parent=self)
84 f = cls(parent=self)
86 d[f.format_type] = f
85 d[f.format_type] = f
87 return d
86 return d
88
87
89 def format(self, obj, include=None, exclude=None):
88 def format(self, obj, include=None, exclude=None):
90 """Return a format data dict for an object.
89 """Return a format data dict for an object.
91
90
92 By default all format types will be computed.
91 By default all format types will be computed.
93
92
94 The following MIME types are usually implemented:
93 The following MIME types are usually implemented:
95
94
96 * text/plain
95 * text/plain
97 * text/html
96 * text/html
98 * text/markdown
97 * text/markdown
99 * text/latex
98 * text/latex
100 * application/json
99 * application/json
101 * application/javascript
100 * application/javascript
102 * application/pdf
101 * application/pdf
103 * image/png
102 * image/png
104 * image/jpeg
103 * image/jpeg
105 * image/svg+xml
104 * image/svg+xml
106
105
107 Parameters
106 Parameters
108 ----------
107 ----------
109 obj : object
108 obj : object
110 The Python object whose format data will be computed.
109 The Python object whose format data will be computed.
111 include : list, tuple or set; optional
110 include : list, tuple or set; optional
112 A list of format type strings (MIME types) to include in the
111 A list of format type strings (MIME types) to include in the
113 format data dict. If this is set *only* the format types included
112 format data dict. If this is set *only* the format types included
114 in this list will be computed.
113 in this list will be computed.
115 exclude : list, tuple or set; optional
114 exclude : list, tuple or set; optional
116 A list of format type string (MIME types) to exclude in the format
115 A list of format type string (MIME types) to exclude in the format
117 data dict. If this is set all format types will be computed,
116 data dict. If this is set all format types will be computed,
118 except for those included in this argument.
117 except for those included in this argument.
119 Mimetypes present in exclude will take precedence over the ones in include
118 Mimetypes present in exclude will take precedence over the ones in include
120
119
121 Returns
120 Returns
122 -------
121 -------
123 (format_dict, metadata_dict) : tuple of two dicts
122 (format_dict, metadata_dict) : tuple of two dicts
124 format_dict is a dictionary of key/value pairs, one of each format that was
123 format_dict is a dictionary of key/value pairs, one of each format that was
125 generated for the object. The keys are the format types, which
124 generated for the object. The keys are the format types, which
126 will usually be MIME type strings and the values and JSON'able
125 will usually be MIME type strings and the values and JSON'able
127 data structure containing the raw data for the representation in
126 data structure containing the raw data for the representation in
128 that format.
127 that format.
129
128
130 metadata_dict is a dictionary of metadata about each mime-type output.
129 metadata_dict is a dictionary of metadata about each mime-type output.
131 Its keys will be a strict subset of the keys in format_dict.
130 Its keys will be a strict subset of the keys in format_dict.
132
131
133 Notes
132 Notes
134 -----
133 -----
135 If an object implement `_repr_mimebundle_` as well as various
134 If an object implement `_repr_mimebundle_` as well as various
136 `_repr_*_`, the data returned by `_repr_mimebundle_` will take
135 `_repr_*_`, the data returned by `_repr_mimebundle_` will take
137 precedence and the corresponding `_repr_*_` for this mimetype will
136 precedence and the corresponding `_repr_*_` for this mimetype will
138 not be called.
137 not be called.
139
138
140 """
139 """
141 format_dict = {}
140 format_dict = {}
142 md_dict = {}
141 md_dict = {}
143
142
144 if self.ipython_display_formatter(obj):
143 if self.ipython_display_formatter(obj):
145 # object handled itself, don't proceed
144 # object handled itself, don't proceed
146 return {}, {}
145 return {}, {}
147
146
148 format_dict, md_dict = self.mimebundle_formatter(obj, include=include, exclude=exclude)
147 format_dict, md_dict = self.mimebundle_formatter(obj, include=include, exclude=exclude)
149
148
150 if format_dict or md_dict:
149 if format_dict or md_dict:
151 if include:
150 if include:
152 format_dict = {k:v for k,v in format_dict.items() if k in include}
151 format_dict = {k:v for k,v in format_dict.items() if k in include}
153 md_dict = {k:v for k,v in md_dict.items() if k in include}
152 md_dict = {k:v for k,v in md_dict.items() if k in include}
154 if exclude:
153 if exclude:
155 format_dict = {k:v for k,v in format_dict.items() if k not in exclude}
154 format_dict = {k:v for k,v in format_dict.items() if k not in exclude}
156 md_dict = {k:v for k,v in md_dict.items() if k not in exclude}
155 md_dict = {k:v for k,v in md_dict.items() if k not in exclude}
157
156
158 for format_type, formatter in self.formatters.items():
157 for format_type, formatter in self.formatters.items():
159 if format_type in format_dict:
158 if format_type in format_dict:
160 # already got it from mimebundle, maybe don't render again.
159 # already got it from mimebundle, maybe don't render again.
161 # exception: manually registered per-mime renderer
160 # exception: manually registered per-mime renderer
162 # check priority:
161 # check priority:
163 # 1. user-registered per-mime formatter
162 # 1. user-registered per-mime formatter
164 # 2. mime-bundle (user-registered or repr method)
163 # 2. mime-bundle (user-registered or repr method)
165 # 3. default per-mime formatter (e.g. repr method)
164 # 3. default per-mime formatter (e.g. repr method)
166 try:
165 try:
167 formatter.lookup(obj)
166 formatter.lookup(obj)
168 except KeyError:
167 except KeyError:
169 # no special formatter, use mime-bundle-provided value
168 # no special formatter, use mime-bundle-provided value
170 continue
169 continue
171 if include and format_type not in include:
170 if include and format_type not in include:
172 continue
171 continue
173 if exclude and format_type in exclude:
172 if exclude and format_type in exclude:
174 continue
173 continue
175
174
176 md = None
175 md = None
177 try:
176 try:
178 data = formatter(obj)
177 data = formatter(obj)
179 except:
178 except:
180 # FIXME: log the exception
179 # FIXME: log the exception
181 raise
180 raise
182
181
183 # formatters can return raw data or (data, metadata)
182 # formatters can return raw data or (data, metadata)
184 if isinstance(data, tuple) and len(data) == 2:
183 if isinstance(data, tuple) and len(data) == 2:
185 data, md = data
184 data, md = data
186
185
187 if data is not None:
186 if data is not None:
188 format_dict[format_type] = data
187 format_dict[format_type] = data
189 if md is not None:
188 if md is not None:
190 md_dict[format_type] = md
189 md_dict[format_type] = md
191 return format_dict, md_dict
190 return format_dict, md_dict
192
191
193 @property
192 @property
194 def format_types(self):
193 def format_types(self):
195 """Return the format types (MIME types) of the active formatters."""
194 """Return the format types (MIME types) of the active formatters."""
196 return list(self.formatters.keys())
195 return list(self.formatters.keys())
197
196
198
197
199 #-----------------------------------------------------------------------------
198 #-----------------------------------------------------------------------------
200 # Formatters for specific format types (text, html, svg, etc.)
199 # Formatters for specific format types (text, html, svg, etc.)
201 #-----------------------------------------------------------------------------
200 #-----------------------------------------------------------------------------
202
201
203
202
204 def _safe_repr(obj):
203 def _safe_repr(obj):
205 """Try to return a repr of an object
204 """Try to return a repr of an object
206
205
207 always returns a string, at least.
206 always returns a string, at least.
208 """
207 """
209 try:
208 try:
210 return repr(obj)
209 return repr(obj)
211 except Exception as e:
210 except Exception as e:
212 return "un-repr-able object (%r)" % e
211 return "un-repr-able object (%r)" % e
213
212
214
213
215 class FormatterWarning(UserWarning):
214 class FormatterWarning(UserWarning):
216 """Warning class for errors in formatters"""
215 """Warning class for errors in formatters"""
217
216
218 @decorator
217 @decorator
219 def catch_format_error(method, self, *args, **kwargs):
218 def catch_format_error(method, self, *args, **kwargs):
220 """show traceback on failed format call"""
219 """show traceback on failed format call"""
221 try:
220 try:
222 r = method(self, *args, **kwargs)
221 r = method(self, *args, **kwargs)
223 except NotImplementedError:
222 except NotImplementedError:
224 # don't warn on NotImplementedErrors
223 # don't warn on NotImplementedErrors
225 return self._check_return(None, args[0])
224 return self._check_return(None, args[0])
226 except Exception:
225 except Exception:
227 exc_info = sys.exc_info()
226 exc_info = sys.exc_info()
228 ip = get_ipython()
227 ip = get_ipython()
229 if ip is not None:
228 if ip is not None:
230 ip.showtraceback(exc_info)
229 ip.showtraceback(exc_info)
231 else:
230 else:
232 traceback.print_exception(*exc_info)
231 traceback.print_exception(*exc_info)
233 return self._check_return(None, args[0])
232 return self._check_return(None, args[0])
234 return self._check_return(r, args[0])
233 return self._check_return(r, args[0])
235
234
236
235
237 class FormatterABC(metaclass=abc.ABCMeta):
236 class FormatterABC(metaclass=abc.ABCMeta):
238 """ Abstract base class for Formatters.
237 """ Abstract base class for Formatters.
239
238
240 A formatter is a callable class that is responsible for computing the
239 A formatter is a callable class that is responsible for computing the
241 raw format data for a particular format type (MIME type). For example,
240 raw format data for a particular format type (MIME type). For example,
242 an HTML formatter would have a format type of `text/html` and would return
241 an HTML formatter would have a format type of `text/html` and would return
243 the HTML representation of the object when called.
242 the HTML representation of the object when called.
244 """
243 """
245
244
246 # The format type of the data returned, usually a MIME type.
245 # The format type of the data returned, usually a MIME type.
247 format_type = 'text/plain'
246 format_type = 'text/plain'
248
247
249 # Is the formatter enabled...
248 # Is the formatter enabled...
250 enabled = True
249 enabled = True
251
250
252 @abc.abstractmethod
251 @abc.abstractmethod
253 def __call__(self, obj):
252 def __call__(self, obj):
254 """Return a JSON'able representation of the object.
253 """Return a JSON'able representation of the object.
255
254
256 If the object cannot be formatted by this formatter,
255 If the object cannot be formatted by this formatter,
257 warn and return None.
256 warn and return None.
258 """
257 """
259 return repr(obj)
258 return repr(obj)
260
259
261
260
262 def _mod_name_key(typ):
261 def _mod_name_key(typ):
263 """Return a (__module__, __name__) tuple for a type.
262 """Return a (__module__, __name__) tuple for a type.
264
263
265 Used as key in Formatter.deferred_printers.
264 Used as key in Formatter.deferred_printers.
266 """
265 """
267 module = getattr(typ, '__module__', None)
266 module = getattr(typ, '__module__', None)
268 name = getattr(typ, '__name__', None)
267 name = getattr(typ, '__name__', None)
269 return (module, name)
268 return (module, name)
270
269
271
270
272 def _get_type(obj):
271 def _get_type(obj):
273 """Return the type of an instance (old and new-style)"""
272 """Return the type of an instance (old and new-style)"""
274 return getattr(obj, '__class__', None) or type(obj)
273 return getattr(obj, '__class__', None) or type(obj)
275
274
276
275
277 _raise_key_error = Sentinel('_raise_key_error', __name__,
276 _raise_key_error = Sentinel('_raise_key_error', __name__,
278 """
277 """
279 Special value to raise a KeyError
278 Special value to raise a KeyError
280
279
281 Raise KeyError in `BaseFormatter.pop` if passed as the default value to `pop`
280 Raise KeyError in `BaseFormatter.pop` if passed as the default value to `pop`
282 """)
281 """)
283
282
284
283
285 class BaseFormatter(Configurable):
284 class BaseFormatter(Configurable):
286 """A base formatter class that is configurable.
285 """A base formatter class that is configurable.
287
286
288 This formatter should usually be used as the base class of all formatters.
287 This formatter should usually be used as the base class of all formatters.
289 It is a traited :class:`Configurable` class and includes an extensible
288 It is a traited :class:`Configurable` class and includes an extensible
290 API for users to determine how their objects are formatted. The following
289 API for users to determine how their objects are formatted. The following
291 logic is used to find a function to format an given object.
290 logic is used to find a function to format an given object.
292
291
293 1. The object is introspected to see if it has a method with the name
292 1. The object is introspected to see if it has a method with the name
294 :attr:`print_method`. If is does, that object is passed to that method
293 :attr:`print_method`. If is does, that object is passed to that method
295 for formatting.
294 for formatting.
296 2. If no print method is found, three internal dictionaries are consulted
295 2. If no print method is found, three internal dictionaries are consulted
297 to find print method: :attr:`singleton_printers`, :attr:`type_printers`
296 to find print method: :attr:`singleton_printers`, :attr:`type_printers`
298 and :attr:`deferred_printers`.
297 and :attr:`deferred_printers`.
299
298
300 Users should use these dictionaries to register functions that will be
299 Users should use these dictionaries to register functions that will be
301 used to compute the format data for their objects (if those objects don't
300 used to compute the format data for their objects (if those objects don't
302 have the special print methods). The easiest way of using these
301 have the special print methods). The easiest way of using these
303 dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name`
302 dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name`
304 methods.
303 methods.
305
304
306 If no function/callable is found to compute the format data, ``None`` is
305 If no function/callable is found to compute the format data, ``None`` is
307 returned and this format type is not used.
306 returned and this format type is not used.
308 """
307 """
309
308
310 format_type = Unicode('text/plain')
309 format_type = Unicode('text/plain')
311 _return_type = str
310 _return_type = str
312
311
313 enabled = Bool(True).tag(config=True)
312 enabled = Bool(True).tag(config=True)
314
313
315 print_method = ObjectName('__repr__')
314 print_method = ObjectName('__repr__')
316
315
317 # The singleton printers.
316 # The singleton printers.
318 # Maps the IDs of the builtin singleton objects to the format functions.
317 # Maps the IDs of the builtin singleton objects to the format functions.
319 singleton_printers = Dict().tag(config=True)
318 singleton_printers = Dict().tag(config=True)
320
319
321 # The type-specific printers.
320 # The type-specific printers.
322 # Map type objects to the format functions.
321 # Map type objects to the format functions.
323 type_printers = Dict().tag(config=True)
322 type_printers = Dict().tag(config=True)
324
323
325 # The deferred-import type-specific printers.
324 # The deferred-import type-specific printers.
326 # Map (modulename, classname) pairs to the format functions.
325 # Map (modulename, classname) pairs to the format functions.
327 deferred_printers = Dict().tag(config=True)
326 deferred_printers = Dict().tag(config=True)
328
327
329 @catch_format_error
328 @catch_format_error
330 def __call__(self, obj):
329 def __call__(self, obj):
331 """Compute the format for an object."""
330 """Compute the format for an object."""
332 if self.enabled:
331 if self.enabled:
333 # lookup registered printer
332 # lookup registered printer
334 try:
333 try:
335 printer = self.lookup(obj)
334 printer = self.lookup(obj)
336 except KeyError:
335 except KeyError:
337 pass
336 pass
338 else:
337 else:
339 return printer(obj)
338 return printer(obj)
340 # Finally look for special method names
339 # Finally look for special method names
341 method = get_real_method(obj, self.print_method)
340 method = get_real_method(obj, self.print_method)
342 if method is not None:
341 if method is not None:
343 return method()
342 return method()
344 return None
343 return None
345 else:
344 else:
346 return None
345 return None
347
346
348 def __contains__(self, typ):
347 def __contains__(self, typ):
349 """map in to lookup_by_type"""
348 """map in to lookup_by_type"""
350 try:
349 try:
351 self.lookup_by_type(typ)
350 self.lookup_by_type(typ)
352 except KeyError:
351 except KeyError:
353 return False
352 return False
354 else:
353 else:
355 return True
354 return True
356
355
357 def _check_return(self, r, obj):
356 def _check_return(self, r, obj):
358 """Check that a return value is appropriate
357 """Check that a return value is appropriate
359
358
360 Return the value if so, None otherwise, warning if invalid.
359 Return the value if so, None otherwise, warning if invalid.
361 """
360 """
362 if r is None or isinstance(r, self._return_type) or \
361 if r is None or isinstance(r, self._return_type) or \
363 (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)):
362 (isinstance(r, tuple) and r and isinstance(r[0], self._return_type)):
364 return r
363 return r
365 else:
364 else:
366 warnings.warn(
365 warnings.warn(
367 "%s formatter returned invalid type %s (expected %s) for object: %s" % \
366 "%s formatter returned invalid type %s (expected %s) for object: %s" % \
368 (self.format_type, type(r), self._return_type, _safe_repr(obj)),
367 (self.format_type, type(r), self._return_type, _safe_repr(obj)),
369 FormatterWarning
368 FormatterWarning
370 )
369 )
371
370
372 def lookup(self, obj):
371 def lookup(self, obj):
373 """Look up the formatter for a given instance.
372 """Look up the formatter for a given instance.
374
373
375 Parameters
374 Parameters
376 ----------
375 ----------
377 obj : object instance
376 obj : object instance
378
377
379 Returns
378 Returns
380 -------
379 -------
381 f : callable
380 f : callable
382 The registered formatting callable for the type.
381 The registered formatting callable for the type.
383
382
384 Raises
383 Raises
385 ------
384 ------
386 KeyError if the type has not been registered.
385 KeyError if the type has not been registered.
387 """
386 """
388 # look for singleton first
387 # look for singleton first
389 obj_id = id(obj)
388 obj_id = id(obj)
390 if obj_id in self.singleton_printers:
389 if obj_id in self.singleton_printers:
391 return self.singleton_printers[obj_id]
390 return self.singleton_printers[obj_id]
392 # then lookup by type
391 # then lookup by type
393 return self.lookup_by_type(_get_type(obj))
392 return self.lookup_by_type(_get_type(obj))
394
393
395 def lookup_by_type(self, typ):
394 def lookup_by_type(self, typ):
396 """Look up the registered formatter for a type.
395 """Look up the registered formatter for a type.
397
396
398 Parameters
397 Parameters
399 ----------
398 ----------
400 typ : type or '__module__.__name__' string for a type
399 typ : type or '__module__.__name__' string for a type
401
400
402 Returns
401 Returns
403 -------
402 -------
404 f : callable
403 f : callable
405 The registered formatting callable for the type.
404 The registered formatting callable for the type.
406
405
407 Raises
406 Raises
408 ------
407 ------
409 KeyError if the type has not been registered.
408 KeyError if the type has not been registered.
410 """
409 """
411 if isinstance(typ, str):
410 if isinstance(typ, str):
412 typ_key = tuple(typ.rsplit('.',1))
411 typ_key = tuple(typ.rsplit('.',1))
413 if typ_key not in self.deferred_printers:
412 if typ_key not in self.deferred_printers:
414 # We may have it cached in the type map. We will have to
413 # We may have it cached in the type map. We will have to
415 # iterate over all of the types to check.
414 # iterate over all of the types to check.
416 for cls in self.type_printers:
415 for cls in self.type_printers:
417 if _mod_name_key(cls) == typ_key:
416 if _mod_name_key(cls) == typ_key:
418 return self.type_printers[cls]
417 return self.type_printers[cls]
419 else:
418 else:
420 return self.deferred_printers[typ_key]
419 return self.deferred_printers[typ_key]
421 else:
420 else:
422 for cls in pretty._get_mro(typ):
421 for cls in pretty._get_mro(typ):
423 if cls in self.type_printers or self._in_deferred_types(cls):
422 if cls in self.type_printers or self._in_deferred_types(cls):
424 return self.type_printers[cls]
423 return self.type_printers[cls]
425
424
426 # If we have reached here, the lookup failed.
425 # If we have reached here, the lookup failed.
427 raise KeyError("No registered printer for {0!r}".format(typ))
426 raise KeyError("No registered printer for {0!r}".format(typ))
428
427
429 def for_type(self, typ, func=None):
428 def for_type(self, typ, func=None):
430 """Add a format function for a given type.
429 """Add a format function for a given type.
431
430
432 Parameters
431 Parameters
433 ----------
432 ----------
434 typ : type or '__module__.__name__' string for a type
433 typ : type or '__module__.__name__' string for a type
435 The class of the object that will be formatted using `func`.
434 The class of the object that will be formatted using `func`.
436
435
437 func : callable
436 func : callable
438 A callable for computing the format data.
437 A callable for computing the format data.
439 `func` will be called with the object to be formatted,
438 `func` will be called with the object to be formatted,
440 and will return the raw data in this formatter's format.
439 and will return the raw data in this formatter's format.
441 Subclasses may use a different call signature for the
440 Subclasses may use a different call signature for the
442 `func` argument.
441 `func` argument.
443
442
444 If `func` is None or not specified, there will be no change,
443 If `func` is None or not specified, there will be no change,
445 only returning the current value.
444 only returning the current value.
446
445
447 Returns
446 Returns
448 -------
447 -------
449 oldfunc : callable
448 oldfunc : callable
450 The currently registered callable.
449 The currently registered callable.
451 If you are registering a new formatter,
450 If you are registering a new formatter,
452 this will be the previous value (to enable restoring later).
451 this will be the previous value (to enable restoring later).
453 """
452 """
454 # if string given, interpret as 'pkg.module.class_name'
453 # if string given, interpret as 'pkg.module.class_name'
455 if isinstance(typ, str):
454 if isinstance(typ, str):
456 type_module, type_name = typ.rsplit('.', 1)
455 type_module, type_name = typ.rsplit('.', 1)
457 return self.for_type_by_name(type_module, type_name, func)
456 return self.for_type_by_name(type_module, type_name, func)
458
457
459 try:
458 try:
460 oldfunc = self.lookup_by_type(typ)
459 oldfunc = self.lookup_by_type(typ)
461 except KeyError:
460 except KeyError:
462 oldfunc = None
461 oldfunc = None
463
462
464 if func is not None:
463 if func is not None:
465 self.type_printers[typ] = func
464 self.type_printers[typ] = func
466
465
467 return oldfunc
466 return oldfunc
468
467
469 def for_type_by_name(self, type_module, type_name, func=None):
468 def for_type_by_name(self, type_module, type_name, func=None):
470 """Add a format function for a type specified by the full dotted
469 """Add a format function for a type specified by the full dotted
471 module and name of the type, rather than the type of the object.
470 module and name of the type, rather than the type of the object.
472
471
473 Parameters
472 Parameters
474 ----------
473 ----------
475 type_module : str
474 type_module : str
476 The full dotted name of the module the type is defined in, like
475 The full dotted name of the module the type is defined in, like
477 ``numpy``.
476 ``numpy``.
478
477
479 type_name : str
478 type_name : str
480 The name of the type (the class name), like ``dtype``
479 The name of the type (the class name), like ``dtype``
481
480
482 func : callable
481 func : callable
483 A callable for computing the format data.
482 A callable for computing the format data.
484 `func` will be called with the object to be formatted,
483 `func` will be called with the object to be formatted,
485 and will return the raw data in this formatter's format.
484 and will return the raw data in this formatter's format.
486 Subclasses may use a different call signature for the
485 Subclasses may use a different call signature for the
487 `func` argument.
486 `func` argument.
488
487
489 If `func` is None or unspecified, there will be no change,
488 If `func` is None or unspecified, there will be no change,
490 only returning the current value.
489 only returning the current value.
491
490
492 Returns
491 Returns
493 -------
492 -------
494 oldfunc : callable
493 oldfunc : callable
495 The currently registered callable.
494 The currently registered callable.
496 If you are registering a new formatter,
495 If you are registering a new formatter,
497 this will be the previous value (to enable restoring later).
496 this will be the previous value (to enable restoring later).
498 """
497 """
499 key = (type_module, type_name)
498 key = (type_module, type_name)
500
499
501 try:
500 try:
502 oldfunc = self.lookup_by_type("%s.%s" % key)
501 oldfunc = self.lookup_by_type("%s.%s" % key)
503 except KeyError:
502 except KeyError:
504 oldfunc = None
503 oldfunc = None
505
504
506 if func is not None:
505 if func is not None:
507 self.deferred_printers[key] = func
506 self.deferred_printers[key] = func
508 return oldfunc
507 return oldfunc
509
508
510 def pop(self, typ, default=_raise_key_error):
509 def pop(self, typ, default=_raise_key_error):
511 """Pop a formatter for the given type.
510 """Pop a formatter for the given type.
512
511
513 Parameters
512 Parameters
514 ----------
513 ----------
515 typ : type or '__module__.__name__' string for a type
514 typ : type or '__module__.__name__' string for a type
516 default : object
515 default : object
517 value to be returned if no formatter is registered for typ.
516 value to be returned if no formatter is registered for typ.
518
517
519 Returns
518 Returns
520 -------
519 -------
521 obj : object
520 obj : object
522 The last registered object for the type.
521 The last registered object for the type.
523
522
524 Raises
523 Raises
525 ------
524 ------
526 KeyError if the type is not registered and default is not specified.
525 KeyError if the type is not registered and default is not specified.
527 """
526 """
528
527
529 if isinstance(typ, str):
528 if isinstance(typ, str):
530 typ_key = tuple(typ.rsplit('.',1))
529 typ_key = tuple(typ.rsplit('.',1))
531 if typ_key not in self.deferred_printers:
530 if typ_key not in self.deferred_printers:
532 # We may have it cached in the type map. We will have to
531 # We may have it cached in the type map. We will have to
533 # iterate over all of the types to check.
532 # iterate over all of the types to check.
534 for cls in self.type_printers:
533 for cls in self.type_printers:
535 if _mod_name_key(cls) == typ_key:
534 if _mod_name_key(cls) == typ_key:
536 old = self.type_printers.pop(cls)
535 old = self.type_printers.pop(cls)
537 break
536 break
538 else:
537 else:
539 old = default
538 old = default
540 else:
539 else:
541 old = self.deferred_printers.pop(typ_key)
540 old = self.deferred_printers.pop(typ_key)
542 else:
541 else:
543 if typ in self.type_printers:
542 if typ in self.type_printers:
544 old = self.type_printers.pop(typ)
543 old = self.type_printers.pop(typ)
545 else:
544 else:
546 old = self.deferred_printers.pop(_mod_name_key(typ), default)
545 old = self.deferred_printers.pop(_mod_name_key(typ), default)
547 if old is _raise_key_error:
546 if old is _raise_key_error:
548 raise KeyError("No registered value for {0!r}".format(typ))
547 raise KeyError("No registered value for {0!r}".format(typ))
549 return old
548 return old
550
549
551 def _in_deferred_types(self, cls):
550 def _in_deferred_types(self, cls):
552 """
551 """
553 Check if the given class is specified in the deferred type registry.
552 Check if the given class is specified in the deferred type registry.
554
553
555 Successful matches will be moved to the regular type registry for future use.
554 Successful matches will be moved to the regular type registry for future use.
556 """
555 """
557 mod = getattr(cls, '__module__', None)
556 mod = getattr(cls, '__module__', None)
558 name = getattr(cls, '__name__', None)
557 name = getattr(cls, '__name__', None)
559 key = (mod, name)
558 key = (mod, name)
560 if key in self.deferred_printers:
559 if key in self.deferred_printers:
561 # Move the printer over to the regular registry.
560 # Move the printer over to the regular registry.
562 printer = self.deferred_printers.pop(key)
561 printer = self.deferred_printers.pop(key)
563 self.type_printers[cls] = printer
562 self.type_printers[cls] = printer
564 return True
563 return True
565 return False
564 return False
566
565
567
566
568 class PlainTextFormatter(BaseFormatter):
567 class PlainTextFormatter(BaseFormatter):
569 """The default pretty-printer.
568 """The default pretty-printer.
570
569
571 This uses :mod:`IPython.lib.pretty` to compute the format data of
570 This uses :mod:`IPython.lib.pretty` to compute the format data of
572 the object. If the object cannot be pretty printed, :func:`repr` is used.
571 the object. If the object cannot be pretty printed, :func:`repr` is used.
573 See the documentation of :mod:`IPython.lib.pretty` for details on
572 See the documentation of :mod:`IPython.lib.pretty` for details on
574 how to write pretty printers. Here is a simple example::
573 how to write pretty printers. Here is a simple example::
575
574
576 def dtype_pprinter(obj, p, cycle):
575 def dtype_pprinter(obj, p, cycle):
577 if cycle:
576 if cycle:
578 return p.text('dtype(...)')
577 return p.text('dtype(...)')
579 if hasattr(obj, 'fields'):
578 if hasattr(obj, 'fields'):
580 if obj.fields is None:
579 if obj.fields is None:
581 p.text(repr(obj))
580 p.text(repr(obj))
582 else:
581 else:
583 p.begin_group(7, 'dtype([')
582 p.begin_group(7, 'dtype([')
584 for i, field in enumerate(obj.descr):
583 for i, field in enumerate(obj.descr):
585 if i > 0:
584 if i > 0:
586 p.text(',')
585 p.text(',')
587 p.breakable()
586 p.breakable()
588 p.pretty(field)
587 p.pretty(field)
589 p.end_group(7, '])')
588 p.end_group(7, '])')
590 """
589 """
591
590
592 # The format type of data returned.
591 # The format type of data returned.
593 format_type = Unicode('text/plain')
592 format_type = Unicode('text/plain')
594
593
595 # This subclass ignores this attribute as it always need to return
594 # This subclass ignores this attribute as it always need to return
596 # something.
595 # something.
597 enabled = Bool(True).tag(config=False)
596 enabled = Bool(True).tag(config=False)
598
597
599 max_seq_length = Integer(pretty.MAX_SEQ_LENGTH,
598 max_seq_length = Integer(pretty.MAX_SEQ_LENGTH,
600 help="""Truncate large collections (lists, dicts, tuples, sets) to this size.
599 help="""Truncate large collections (lists, dicts, tuples, sets) to this size.
601
600
602 Set to 0 to disable truncation.
601 Set to 0 to disable truncation.
603 """
602 """
604 ).tag(config=True)
603 ).tag(config=True)
605
604
606 # Look for a _repr_pretty_ methods to use for pretty printing.
605 # Look for a _repr_pretty_ methods to use for pretty printing.
607 print_method = ObjectName('_repr_pretty_')
606 print_method = ObjectName('_repr_pretty_')
608
607
609 # Whether to pretty-print or not.
608 # Whether to pretty-print or not.
610 pprint = Bool(True).tag(config=True)
609 pprint = Bool(True).tag(config=True)
611
610
612 # Whether to be verbose or not.
611 # Whether to be verbose or not.
613 verbose = Bool(False).tag(config=True)
612 verbose = Bool(False).tag(config=True)
614
613
615 # The maximum width.
614 # The maximum width.
616 max_width = Integer(79).tag(config=True)
615 max_width = Integer(79).tag(config=True)
617
616
618 # The newline character.
617 # The newline character.
619 newline = Unicode('\n').tag(config=True)
618 newline = Unicode('\n').tag(config=True)
620
619
621 # format-string for pprinting floats
620 # format-string for pprinting floats
622 float_format = Unicode('%r')
621 float_format = Unicode('%r')
623 # setter for float precision, either int or direct format-string
622 # setter for float precision, either int or direct format-string
624 float_precision = CUnicode('').tag(config=True)
623 float_precision = CUnicode('').tag(config=True)
625
624
626 @observe('float_precision')
625 @observe('float_precision')
627 def _float_precision_changed(self, change):
626 def _float_precision_changed(self, change):
628 """float_precision changed, set float_format accordingly.
627 """float_precision changed, set float_format accordingly.
629
628
630 float_precision can be set by int or str.
629 float_precision can be set by int or str.
631 This will set float_format, after interpreting input.
630 This will set float_format, after interpreting input.
632 If numpy has been imported, numpy print precision will also be set.
631 If numpy has been imported, numpy print precision will also be set.
633
632
634 integer `n` sets format to '%.nf', otherwise, format set directly.
633 integer `n` sets format to '%.nf', otherwise, format set directly.
635
634
636 An empty string returns to defaults (repr for float, 8 for numpy).
635 An empty string returns to defaults (repr for float, 8 for numpy).
637
636
638 This parameter can be set via the '%precision' magic.
637 This parameter can be set via the '%precision' magic.
639 """
638 """
640 new = change['new']
639 new = change['new']
641 if '%' in new:
640 if '%' in new:
642 # got explicit format string
641 # got explicit format string
643 fmt = new
642 fmt = new
644 try:
643 try:
645 fmt%3.14159
644 fmt%3.14159
646 except Exception as e:
645 except Exception as e:
647 raise ValueError("Precision must be int or format string, not %r"%new) from e
646 raise ValueError("Precision must be int or format string, not %r"%new) from e
648 elif new:
647 elif new:
649 # otherwise, should be an int
648 # otherwise, should be an int
650 try:
649 try:
651 i = int(new)
650 i = int(new)
652 assert i >= 0
651 assert i >= 0
653 except ValueError as e:
652 except ValueError as e:
654 raise ValueError("Precision must be int or format string, not %r"%new) from e
653 raise ValueError("Precision must be int or format string, not %r"%new) from e
655 except AssertionError as e:
654 except AssertionError as e:
656 raise ValueError("int precision must be non-negative, not %r"%i) from e
655 raise ValueError("int precision must be non-negative, not %r"%i) from e
657
656
658 fmt = '%%.%if'%i
657 fmt = '%%.%if'%i
659 if 'numpy' in sys.modules:
658 if 'numpy' in sys.modules:
660 # set numpy precision if it has been imported
659 # set numpy precision if it has been imported
661 import numpy
660 import numpy
662 numpy.set_printoptions(precision=i)
661 numpy.set_printoptions(precision=i)
663 else:
662 else:
664 # default back to repr
663 # default back to repr
665 fmt = '%r'
664 fmt = '%r'
666 if 'numpy' in sys.modules:
665 if 'numpy' in sys.modules:
667 import numpy
666 import numpy
668 # numpy default is 8
667 # numpy default is 8
669 numpy.set_printoptions(precision=8)
668 numpy.set_printoptions(precision=8)
670 self.float_format = fmt
669 self.float_format = fmt
671
670
672 # Use the default pretty printers from IPython.lib.pretty.
671 # Use the default pretty printers from IPython.lib.pretty.
673 @default('singleton_printers')
672 @default('singleton_printers')
674 def _singleton_printers_default(self):
673 def _singleton_printers_default(self):
675 return pretty._singleton_pprinters.copy()
674 return pretty._singleton_pprinters.copy()
676
675
677 @default('type_printers')
676 @default('type_printers')
678 def _type_printers_default(self):
677 def _type_printers_default(self):
679 d = pretty._type_pprinters.copy()
678 d = pretty._type_pprinters.copy()
680 d[float] = lambda obj,p,cycle: p.text(self.float_format%obj)
679 d[float] = lambda obj,p,cycle: p.text(self.float_format%obj)
681 # if NumPy is used, set precision for its float64 type
680 # if NumPy is used, set precision for its float64 type
682 if "numpy" in sys.modules:
681 if "numpy" in sys.modules:
683 import numpy
682 import numpy
684
683
685 d[numpy.float64] = lambda obj, p, cycle: p.text(self.float_format % obj)
684 d[numpy.float64] = lambda obj, p, cycle: p.text(self.float_format % obj)
686 return d
685 return d
687
686
688 @default('deferred_printers')
687 @default('deferred_printers')
689 def _deferred_printers_default(self):
688 def _deferred_printers_default(self):
690 return pretty._deferred_type_pprinters.copy()
689 return pretty._deferred_type_pprinters.copy()
691
690
692 #### FormatterABC interface ####
691 #### FormatterABC interface ####
693
692
694 @catch_format_error
693 @catch_format_error
695 def __call__(self, obj):
694 def __call__(self, obj):
696 """Compute the pretty representation of the object."""
695 """Compute the pretty representation of the object."""
697 if not self.pprint:
696 if not self.pprint:
698 return repr(obj)
697 return repr(obj)
699 else:
698 else:
700 stream = StringIO()
699 stream = StringIO()
701 printer = pretty.RepresentationPrinter(stream, self.verbose,
700 printer = pretty.RepresentationPrinter(stream, self.verbose,
702 self.max_width, self.newline,
701 self.max_width, self.newline,
703 max_seq_length=self.max_seq_length,
702 max_seq_length=self.max_seq_length,
704 singleton_pprinters=self.singleton_printers,
703 singleton_pprinters=self.singleton_printers,
705 type_pprinters=self.type_printers,
704 type_pprinters=self.type_printers,
706 deferred_pprinters=self.deferred_printers)
705 deferred_pprinters=self.deferred_printers)
707 printer.pretty(obj)
706 printer.pretty(obj)
708 printer.flush()
707 printer.flush()
709 return stream.getvalue()
708 return stream.getvalue()
710
709
711
710
712 class HTMLFormatter(BaseFormatter):
711 class HTMLFormatter(BaseFormatter):
713 """An HTML formatter.
712 """An HTML formatter.
714
713
715 To define the callables that compute the HTML representation of your
714 To define the callables that compute the HTML representation of your
716 objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
715 objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
717 or :meth:`for_type_by_name` methods to register functions that handle
716 or :meth:`for_type_by_name` methods to register functions that handle
718 this.
717 this.
719
718
720 The return value of this formatter should be a valid HTML snippet that
719 The return value of this formatter should be a valid HTML snippet that
721 could be injected into an existing DOM. It should *not* include the
720 could be injected into an existing DOM. It should *not* include the
722 ```<html>`` or ```<body>`` tags.
721 ```<html>`` or ```<body>`` tags.
723 """
722 """
724 format_type = Unicode('text/html')
723 format_type = Unicode('text/html')
725
724
726 print_method = ObjectName('_repr_html_')
725 print_method = ObjectName('_repr_html_')
727
726
728
727
729 class MarkdownFormatter(BaseFormatter):
728 class MarkdownFormatter(BaseFormatter):
730 """A Markdown formatter.
729 """A Markdown formatter.
731
730
732 To define the callables that compute the Markdown representation of your
731 To define the callables that compute the Markdown representation of your
733 objects, define a :meth:`_repr_markdown_` method or use the :meth:`for_type`
732 objects, define a :meth:`_repr_markdown_` method or use the :meth:`for_type`
734 or :meth:`for_type_by_name` methods to register functions that handle
733 or :meth:`for_type_by_name` methods to register functions that handle
735 this.
734 this.
736
735
737 The return value of this formatter should be a valid Markdown.
736 The return value of this formatter should be a valid Markdown.
738 """
737 """
739 format_type = Unicode('text/markdown')
738 format_type = Unicode('text/markdown')
740
739
741 print_method = ObjectName('_repr_markdown_')
740 print_method = ObjectName('_repr_markdown_')
742
741
743 class SVGFormatter(BaseFormatter):
742 class SVGFormatter(BaseFormatter):
744 """An SVG formatter.
743 """An SVG formatter.
745
744
746 To define the callables that compute the SVG representation of your
745 To define the callables that compute the SVG representation of your
747 objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
746 objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
748 or :meth:`for_type_by_name` methods to register functions that handle
747 or :meth:`for_type_by_name` methods to register functions that handle
749 this.
748 this.
750
749
751 The return value of this formatter should be valid SVG enclosed in
750 The return value of this formatter should be valid SVG enclosed in
752 ```<svg>``` tags, that could be injected into an existing DOM. It should
751 ```<svg>``` tags, that could be injected into an existing DOM. It should
753 *not* include the ```<html>`` or ```<body>`` tags.
752 *not* include the ```<html>`` or ```<body>`` tags.
754 """
753 """
755 format_type = Unicode('image/svg+xml')
754 format_type = Unicode('image/svg+xml')
756
755
757 print_method = ObjectName('_repr_svg_')
756 print_method = ObjectName('_repr_svg_')
758
757
759
758
760 class PNGFormatter(BaseFormatter):
759 class PNGFormatter(BaseFormatter):
761 """A PNG formatter.
760 """A PNG formatter.
762
761
763 To define the callables that compute the PNG representation of your
762 To define the callables that compute the PNG representation of your
764 objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
763 objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
765 or :meth:`for_type_by_name` methods to register functions that handle
764 or :meth:`for_type_by_name` methods to register functions that handle
766 this.
765 this.
767
766
768 The return value of this formatter should be raw PNG data, *not*
767 The return value of this formatter should be raw PNG data, *not*
769 base64 encoded.
768 base64 encoded.
770 """
769 """
771 format_type = Unicode('image/png')
770 format_type = Unicode('image/png')
772
771
773 print_method = ObjectName('_repr_png_')
772 print_method = ObjectName('_repr_png_')
774
773
775 _return_type = (bytes, str)
774 _return_type = (bytes, str)
776
775
777
776
778 class JPEGFormatter(BaseFormatter):
777 class JPEGFormatter(BaseFormatter):
779 """A JPEG formatter.
778 """A JPEG formatter.
780
779
781 To define the callables that compute the JPEG representation of your
780 To define the callables that compute the JPEG representation of your
782 objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type`
781 objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type`
783 or :meth:`for_type_by_name` methods to register functions that handle
782 or :meth:`for_type_by_name` methods to register functions that handle
784 this.
783 this.
785
784
786 The return value of this formatter should be raw JPEG data, *not*
785 The return value of this formatter should be raw JPEG data, *not*
787 base64 encoded.
786 base64 encoded.
788 """
787 """
789 format_type = Unicode('image/jpeg')
788 format_type = Unicode('image/jpeg')
790
789
791 print_method = ObjectName('_repr_jpeg_')
790 print_method = ObjectName('_repr_jpeg_')
792
791
793 _return_type = (bytes, str)
792 _return_type = (bytes, str)
794
793
795
794
796 class LatexFormatter(BaseFormatter):
795 class LatexFormatter(BaseFormatter):
797 """A LaTeX formatter.
796 """A LaTeX formatter.
798
797
799 To define the callables that compute the LaTeX representation of your
798 To define the callables that compute the LaTeX representation of your
800 objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
799 objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
801 or :meth:`for_type_by_name` methods to register functions that handle
800 or :meth:`for_type_by_name` methods to register functions that handle
802 this.
801 this.
803
802
804 The return value of this formatter should be a valid LaTeX equation,
803 The return value of this formatter should be a valid LaTeX equation,
805 enclosed in either ```$```, ```$$``` or another LaTeX equation
804 enclosed in either ```$```, ```$$``` or another LaTeX equation
806 environment.
805 environment.
807 """
806 """
808 format_type = Unicode('text/latex')
807 format_type = Unicode('text/latex')
809
808
810 print_method = ObjectName('_repr_latex_')
809 print_method = ObjectName('_repr_latex_')
811
810
812
811
813 class JSONFormatter(BaseFormatter):
812 class JSONFormatter(BaseFormatter):
814 """A JSON string formatter.
813 """A JSON string formatter.
815
814
816 To define the callables that compute the JSONable representation of
815 To define the callables that compute the JSONable representation of
817 your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
816 your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
818 or :meth:`for_type_by_name` methods to register functions that handle
817 or :meth:`for_type_by_name` methods to register functions that handle
819 this.
818 this.
820
819
821 The return value of this formatter should be a JSONable list or dict.
820 The return value of this formatter should be a JSONable list or dict.
822 JSON scalars (None, number, string) are not allowed, only dict or list containers.
821 JSON scalars (None, number, string) are not allowed, only dict or list containers.
823 """
822 """
824 format_type = Unicode('application/json')
823 format_type = Unicode('application/json')
825 _return_type = (list, dict)
824 _return_type = (list, dict)
826
825
827 print_method = ObjectName('_repr_json_')
826 print_method = ObjectName('_repr_json_')
828
827
829 def _check_return(self, r, obj):
828 def _check_return(self, r, obj):
830 """Check that a return value is appropriate
829 """Check that a return value is appropriate
831
830
832 Return the value if so, None otherwise, warning if invalid.
831 Return the value if so, None otherwise, warning if invalid.
833 """
832 """
834 if r is None:
833 if r is None:
835 return
834 return
836 md = None
835 md = None
837 if isinstance(r, tuple):
836 if isinstance(r, tuple):
838 # unpack data, metadata tuple for type checking on first element
837 # unpack data, metadata tuple for type checking on first element
839 r, md = r
838 r, md = r
840
839
841 assert not isinstance(
840 assert not isinstance(
842 r, str
841 r, str
843 ), "JSON-as-string has been deprecated since IPython < 3"
842 ), "JSON-as-string has been deprecated since IPython < 3"
844
843
845 if md is not None:
844 if md is not None:
846 # put the tuple back together
845 # put the tuple back together
847 r = (r, md)
846 r = (r, md)
848 return super(JSONFormatter, self)._check_return(r, obj)
847 return super(JSONFormatter, self)._check_return(r, obj)
849
848
850
849
851 class JavascriptFormatter(BaseFormatter):
850 class JavascriptFormatter(BaseFormatter):
852 """A Javascript formatter.
851 """A Javascript formatter.
853
852
854 To define the callables that compute the Javascript representation of
853 To define the callables that compute the Javascript representation of
855 your objects, define a :meth:`_repr_javascript_` method or use the
854 your objects, define a :meth:`_repr_javascript_` method or use the
856 :meth:`for_type` or :meth:`for_type_by_name` methods to register functions
855 :meth:`for_type` or :meth:`for_type_by_name` methods to register functions
857 that handle this.
856 that handle this.
858
857
859 The return value of this formatter should be valid Javascript code and
858 The return value of this formatter should be valid Javascript code and
860 should *not* be enclosed in ```<script>``` tags.
859 should *not* be enclosed in ```<script>``` tags.
861 """
860 """
862 format_type = Unicode('application/javascript')
861 format_type = Unicode('application/javascript')
863
862
864 print_method = ObjectName('_repr_javascript_')
863 print_method = ObjectName('_repr_javascript_')
865
864
866
865
867 class PDFFormatter(BaseFormatter):
866 class PDFFormatter(BaseFormatter):
868 """A PDF formatter.
867 """A PDF formatter.
869
868
870 To define the callables that compute the PDF representation of your
869 To define the callables that compute the PDF representation of your
871 objects, define a :meth:`_repr_pdf_` method or use the :meth:`for_type`
870 objects, define a :meth:`_repr_pdf_` method or use the :meth:`for_type`
872 or :meth:`for_type_by_name` methods to register functions that handle
871 or :meth:`for_type_by_name` methods to register functions that handle
873 this.
872 this.
874
873
875 The return value of this formatter should be raw PDF data, *not*
874 The return value of this formatter should be raw PDF data, *not*
876 base64 encoded.
875 base64 encoded.
877 """
876 """
878 format_type = Unicode('application/pdf')
877 format_type = Unicode('application/pdf')
879
878
880 print_method = ObjectName('_repr_pdf_')
879 print_method = ObjectName('_repr_pdf_')
881
880
882 _return_type = (bytes, str)
881 _return_type = (bytes, str)
883
882
884 class IPythonDisplayFormatter(BaseFormatter):
883 class IPythonDisplayFormatter(BaseFormatter):
885 """An escape-hatch Formatter for objects that know how to display themselves.
884 """An escape-hatch Formatter for objects that know how to display themselves.
886
885
887 To define the callables that compute the representation of your
886 To define the callables that compute the representation of your
888 objects, define a :meth:`_ipython_display_` method or use the :meth:`for_type`
887 objects, define a :meth:`_ipython_display_` method or use the :meth:`for_type`
889 or :meth:`for_type_by_name` methods to register functions that handle
888 or :meth:`for_type_by_name` methods to register functions that handle
890 this. Unlike mime-type displays, this method should not return anything,
889 this. Unlike mime-type displays, this method should not return anything,
891 instead calling any appropriate display methods itself.
890 instead calling any appropriate display methods itself.
892
891
893 This display formatter has highest priority.
892 This display formatter has highest priority.
894 If it fires, no other display formatter will be called.
893 If it fires, no other display formatter will be called.
895
894
896 Prior to IPython 6.1, `_ipython_display_` was the only way to display custom mime-types
895 Prior to IPython 6.1, `_ipython_display_` was the only way to display custom mime-types
897 without registering a new Formatter.
896 without registering a new Formatter.
898
897
899 IPython 6.1 introduces `_repr_mimebundle_` for displaying custom mime-types,
898 IPython 6.1 introduces `_repr_mimebundle_` for displaying custom mime-types,
900 so `_ipython_display_` should only be used for objects that require unusual
899 so `_ipython_display_` should only be used for objects that require unusual
901 display patterns, such as multiple display calls.
900 display patterns, such as multiple display calls.
902 """
901 """
903 print_method = ObjectName('_ipython_display_')
902 print_method = ObjectName('_ipython_display_')
904 _return_type = (type(None), bool)
903 _return_type = (type(None), bool)
905
904
906 @catch_format_error
905 @catch_format_error
907 def __call__(self, obj):
906 def __call__(self, obj):
908 """Compute the format for an object."""
907 """Compute the format for an object."""
909 if self.enabled:
908 if self.enabled:
910 # lookup registered printer
909 # lookup registered printer
911 try:
910 try:
912 printer = self.lookup(obj)
911 printer = self.lookup(obj)
913 except KeyError:
912 except KeyError:
914 pass
913 pass
915 else:
914 else:
916 printer(obj)
915 printer(obj)
917 return True
916 return True
918 # Finally look for special method names
917 # Finally look for special method names
919 method = get_real_method(obj, self.print_method)
918 method = get_real_method(obj, self.print_method)
920 if method is not None:
919 if method is not None:
921 method()
920 method()
922 return True
921 return True
923
922
924
923
925 class MimeBundleFormatter(BaseFormatter):
924 class MimeBundleFormatter(BaseFormatter):
926 """A Formatter for arbitrary mime-types.
925 """A Formatter for arbitrary mime-types.
927
926
928 Unlike other `_repr_<mimetype>_` methods,
927 Unlike other `_repr_<mimetype>_` methods,
929 `_repr_mimebundle_` should return mime-bundle data,
928 `_repr_mimebundle_` should return mime-bundle data,
930 either the mime-keyed `data` dictionary or the tuple `(data, metadata)`.
929 either the mime-keyed `data` dictionary or the tuple `(data, metadata)`.
931 Any mime-type is valid.
930 Any mime-type is valid.
932
931
933 To define the callables that compute the mime-bundle representation of your
932 To define the callables that compute the mime-bundle representation of your
934 objects, define a :meth:`_repr_mimebundle_` method or use the :meth:`for_type`
933 objects, define a :meth:`_repr_mimebundle_` method or use the :meth:`for_type`
935 or :meth:`for_type_by_name` methods to register functions that handle
934 or :meth:`for_type_by_name` methods to register functions that handle
936 this.
935 this.
937
936
938 .. versionadded:: 6.1
937 .. versionadded:: 6.1
939 """
938 """
940 print_method = ObjectName('_repr_mimebundle_')
939 print_method = ObjectName('_repr_mimebundle_')
941 _return_type = dict
940 _return_type = dict
942
941
943 def _check_return(self, r, obj):
942 def _check_return(self, r, obj):
944 r = super(MimeBundleFormatter, self)._check_return(r, obj)
943 r = super(MimeBundleFormatter, self)._check_return(r, obj)
945 # always return (data, metadata):
944 # always return (data, metadata):
946 if r is None:
945 if r is None:
947 return {}, {}
946 return {}, {}
948 if not isinstance(r, tuple):
947 if not isinstance(r, tuple):
949 return r, {}
948 return r, {}
950 return r
949 return r
951
950
952 @catch_format_error
951 @catch_format_error
953 def __call__(self, obj, include=None, exclude=None):
952 def __call__(self, obj, include=None, exclude=None):
954 """Compute the format for an object.
953 """Compute the format for an object.
955
954
956 Identical to parent's method but we pass extra parameters to the method.
955 Identical to parent's method but we pass extra parameters to the method.
957
956
958 Unlike other _repr_*_ `_repr_mimebundle_` should allow extra kwargs, in
957 Unlike other _repr_*_ `_repr_mimebundle_` should allow extra kwargs, in
959 particular `include` and `exclude`.
958 particular `include` and `exclude`.
960 """
959 """
961 if self.enabled:
960 if self.enabled:
962 # lookup registered printer
961 # lookup registered printer
963 try:
962 try:
964 printer = self.lookup(obj)
963 printer = self.lookup(obj)
965 except KeyError:
964 except KeyError:
966 pass
965 pass
967 else:
966 else:
968 return printer(obj)
967 return printer(obj)
969 # Finally look for special method names
968 # Finally look for special method names
970 method = get_real_method(obj, self.print_method)
969 method = get_real_method(obj, self.print_method)
971
970
972 if method is not None:
971 if method is not None:
973 return method(include=include, exclude=exclude)
972 return method(include=include, exclude=exclude)
974 return None
973 return None
975 else:
974 else:
976 return None
975 return None
977
976
978
977
979 FormatterABC.register(BaseFormatter)
978 FormatterABC.register(BaseFormatter)
980 FormatterABC.register(PlainTextFormatter)
979 FormatterABC.register(PlainTextFormatter)
981 FormatterABC.register(HTMLFormatter)
980 FormatterABC.register(HTMLFormatter)
982 FormatterABC.register(MarkdownFormatter)
981 FormatterABC.register(MarkdownFormatter)
983 FormatterABC.register(SVGFormatter)
982 FormatterABC.register(SVGFormatter)
984 FormatterABC.register(PNGFormatter)
983 FormatterABC.register(PNGFormatter)
985 FormatterABC.register(PDFFormatter)
984 FormatterABC.register(PDFFormatter)
986 FormatterABC.register(JPEGFormatter)
985 FormatterABC.register(JPEGFormatter)
987 FormatterABC.register(LatexFormatter)
986 FormatterABC.register(LatexFormatter)
988 FormatterABC.register(JSONFormatter)
987 FormatterABC.register(JSONFormatter)
989 FormatterABC.register(JavascriptFormatter)
988 FormatterABC.register(JavascriptFormatter)
990 FormatterABC.register(IPythonDisplayFormatter)
989 FormatterABC.register(IPythonDisplayFormatter)
991 FormatterABC.register(MimeBundleFormatter)
990 FormatterABC.register(MimeBundleFormatter)
992
991
993
992
994 def format_display_data(obj, include=None, exclude=None):
993 def format_display_data(obj, include=None, exclude=None):
995 """Return a format data dict for an object.
994 """Return a format data dict for an object.
996
995
997 By default all format types will be computed.
996 By default all format types will be computed.
998
997
999 Parameters
998 Parameters
1000 ----------
999 ----------
1001 obj : object
1000 obj : object
1002 The Python object whose format data will be computed.
1001 The Python object whose format data will be computed.
1003
1002
1004 Returns
1003 Returns
1005 -------
1004 -------
1006 format_dict : dict
1005 format_dict : dict
1007 A dictionary of key/value pairs, one or each format that was
1006 A dictionary of key/value pairs, one or each format that was
1008 generated for the object. The keys are the format types, which
1007 generated for the object. The keys are the format types, which
1009 will usually be MIME type strings and the values and JSON'able
1008 will usually be MIME type strings and the values and JSON'able
1010 data structure containing the raw data for the representation in
1009 data structure containing the raw data for the representation in
1011 that format.
1010 that format.
1012 include : list or tuple, optional
1011 include : list or tuple, optional
1013 A list of format type strings (MIME types) to include in the
1012 A list of format type strings (MIME types) to include in the
1014 format data dict. If this is set *only* the format types included
1013 format data dict. If this is set *only* the format types included
1015 in this list will be computed.
1014 in this list will be computed.
1016 exclude : list or tuple, optional
1015 exclude : list or tuple, optional
1017 A list of format type string (MIME types) to exclude in the format
1016 A list of format type string (MIME types) to exclude in the format
1018 data dict. If this is set all format types will be computed,
1017 data dict. If this is set all format types will be computed,
1019 except for those included in this argument.
1018 except for those included in this argument.
1020 """
1019 """
1021 from .interactiveshell import InteractiveShell
1020 from .interactiveshell import InteractiveShell
1022
1021
1023 return InteractiveShell.instance().display_formatter.format(
1022 return InteractiveShell.instance().display_formatter.format(
1024 obj,
1023 obj,
1025 include,
1024 include,
1026 exclude
1025 exclude
1027 )
1026 )
@@ -1,788 +1,787 b''
1 """Input transformer machinery to support IPython special syntax.
1 """Input transformer machinery to support IPython special syntax.
2
2
3 This includes the machinery to recognise and transform ``%magic`` commands,
3 This includes the machinery to recognise and transform ``%magic`` commands,
4 ``!system`` commands, ``help?`` querying, prompt stripping, and so forth.
4 ``!system`` commands, ``help?`` querying, prompt stripping, and so forth.
5
5
6 Added: IPython 7.0. Replaces inputsplitter and inputtransformer which were
6 Added: IPython 7.0. Replaces inputsplitter and inputtransformer which were
7 deprecated in 7.0.
7 deprecated in 7.0.
8 """
8 """
9
9
10 # Copyright (c) IPython Development Team.
10 # Copyright (c) IPython Development Team.
11 # Distributed under the terms of the Modified BSD License.
11 # Distributed under the terms of the Modified BSD License.
12
12
13 import ast
13 import ast
14 import sys
15 from codeop import CommandCompiler, Compile
14 from codeop import CommandCompiler, Compile
16 import re
15 import re
17 import tokenize
16 import tokenize
18 from typing import List, Tuple, Optional, Any
17 from typing import List, Tuple, Optional, Any
19 import warnings
18 import warnings
20
19
21 _indent_re = re.compile(r'^[ \t]+')
20 _indent_re = re.compile(r'^[ \t]+')
22
21
23 def leading_empty_lines(lines):
22 def leading_empty_lines(lines):
24 """Remove leading empty lines
23 """Remove leading empty lines
25
24
26 If the leading lines are empty or contain only whitespace, they will be
25 If the leading lines are empty or contain only whitespace, they will be
27 removed.
26 removed.
28 """
27 """
29 if not lines:
28 if not lines:
30 return lines
29 return lines
31 for i, line in enumerate(lines):
30 for i, line in enumerate(lines):
32 if line and not line.isspace():
31 if line and not line.isspace():
33 return lines[i:]
32 return lines[i:]
34 return lines
33 return lines
35
34
36 def leading_indent(lines):
35 def leading_indent(lines):
37 """Remove leading indentation.
36 """Remove leading indentation.
38
37
39 If the first line starts with a spaces or tabs, the same whitespace will be
38 If the first line starts with a spaces or tabs, the same whitespace will be
40 removed from each following line in the cell.
39 removed from each following line in the cell.
41 """
40 """
42 if not lines:
41 if not lines:
43 return lines
42 return lines
44 m = _indent_re.match(lines[0])
43 m = _indent_re.match(lines[0])
45 if not m:
44 if not m:
46 return lines
45 return lines
47 space = m.group(0)
46 space = m.group(0)
48 n = len(space)
47 n = len(space)
49 return [l[n:] if l.startswith(space) else l
48 return [l[n:] if l.startswith(space) else l
50 for l in lines]
49 for l in lines]
51
50
52 class PromptStripper:
51 class PromptStripper:
53 """Remove matching input prompts from a block of input.
52 """Remove matching input prompts from a block of input.
54
53
55 Parameters
54 Parameters
56 ----------
55 ----------
57 prompt_re : regular expression
56 prompt_re : regular expression
58 A regular expression matching any input prompt (including continuation,
57 A regular expression matching any input prompt (including continuation,
59 e.g. ``...``)
58 e.g. ``...``)
60 initial_re : regular expression, optional
59 initial_re : regular expression, optional
61 A regular expression matching only the initial prompt, but not continuation.
60 A regular expression matching only the initial prompt, but not continuation.
62 If no initial expression is given, prompt_re will be used everywhere.
61 If no initial expression is given, prompt_re will be used everywhere.
63 Used mainly for plain Python prompts (``>>>``), where the continuation prompt
62 Used mainly for plain Python prompts (``>>>``), where the continuation prompt
64 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
63 ``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
65
64
66 Notes
65 Notes
67 -----
66 -----
68
67
69 If initial_re and prompt_re differ,
68 If initial_re and prompt_re differ,
70 only initial_re will be tested against the first line.
69 only initial_re will be tested against the first line.
71 If any prompt is found on the first two lines,
70 If any prompt is found on the first two lines,
72 prompts will be stripped from the rest of the block.
71 prompts will be stripped from the rest of the block.
73 """
72 """
74 def __init__(self, prompt_re, initial_re=None):
73 def __init__(self, prompt_re, initial_re=None):
75 self.prompt_re = prompt_re
74 self.prompt_re = prompt_re
76 self.initial_re = initial_re or prompt_re
75 self.initial_re = initial_re or prompt_re
77
76
78 def _strip(self, lines):
77 def _strip(self, lines):
79 return [self.prompt_re.sub('', l, count=1) for l in lines]
78 return [self.prompt_re.sub('', l, count=1) for l in lines]
80
79
81 def __call__(self, lines):
80 def __call__(self, lines):
82 if not lines:
81 if not lines:
83 return lines
82 return lines
84 if self.initial_re.match(lines[0]) or \
83 if self.initial_re.match(lines[0]) or \
85 (len(lines) > 1 and self.prompt_re.match(lines[1])):
84 (len(lines) > 1 and self.prompt_re.match(lines[1])):
86 return self._strip(lines)
85 return self._strip(lines)
87 return lines
86 return lines
88
87
89 classic_prompt = PromptStripper(
88 classic_prompt = PromptStripper(
90 prompt_re=re.compile(r'^(>>>|\.\.\.)( |$)'),
89 prompt_re=re.compile(r'^(>>>|\.\.\.)( |$)'),
91 initial_re=re.compile(r'^>>>( |$)')
90 initial_re=re.compile(r'^>>>( |$)')
92 )
91 )
93
92
94 ipython_prompt = PromptStripper(
93 ipython_prompt = PromptStripper(
95 re.compile(
94 re.compile(
96 r"""
95 r"""
97 ^( # Match from the beginning of a line, either:
96 ^( # Match from the beginning of a line, either:
98
97
99 # 1. First-line prompt:
98 # 1. First-line prompt:
100 ((\[nav\]|\[ins\])?\ )? # Vi editing mode prompt, if it's there
99 ((\[nav\]|\[ins\])?\ )? # Vi editing mode prompt, if it's there
101 In\ # The 'In' of the prompt, with a space
100 In\ # The 'In' of the prompt, with a space
102 \[\d+\]: # Command index, as displayed in the prompt
101 \[\d+\]: # Command index, as displayed in the prompt
103 \ # With a mandatory trailing space
102 \ # With a mandatory trailing space
104
103
105 | # ... or ...
104 | # ... or ...
106
105
107 # 2. The three dots of the multiline prompt
106 # 2. The three dots of the multiline prompt
108 \s* # All leading whitespace characters
107 \s* # All leading whitespace characters
109 \.{3,}: # The three (or more) dots
108 \.{3,}: # The three (or more) dots
110 \ ? # With an optional trailing space
109 \ ? # With an optional trailing space
111
110
112 )
111 )
113 """,
112 """,
114 re.VERBOSE,
113 re.VERBOSE,
115 )
114 )
116 )
115 )
117
116
118
117
119 def cell_magic(lines):
118 def cell_magic(lines):
120 if not lines or not lines[0].startswith('%%'):
119 if not lines or not lines[0].startswith('%%'):
121 return lines
120 return lines
122 if re.match(r'%%\w+\?', lines[0]):
121 if re.match(r'%%\w+\?', lines[0]):
123 # This case will be handled by help_end
122 # This case will be handled by help_end
124 return lines
123 return lines
125 magic_name, _, first_line = lines[0][2:].rstrip().partition(' ')
124 magic_name, _, first_line = lines[0][2:].rstrip().partition(' ')
126 body = ''.join(lines[1:])
125 body = ''.join(lines[1:])
127 return ['get_ipython().run_cell_magic(%r, %r, %r)\n'
126 return ['get_ipython().run_cell_magic(%r, %r, %r)\n'
128 % (magic_name, first_line, body)]
127 % (magic_name, first_line, body)]
129
128
130
129
131 def _find_assign_op(token_line) -> Optional[int]:
130 def _find_assign_op(token_line) -> Optional[int]:
132 """Get the index of the first assignment in the line ('=' not inside brackets)
131 """Get the index of the first assignment in the line ('=' not inside brackets)
133
132
134 Note: We don't try to support multiple special assignment (a = b = %foo)
133 Note: We don't try to support multiple special assignment (a = b = %foo)
135 """
134 """
136 paren_level = 0
135 paren_level = 0
137 for i, ti in enumerate(token_line):
136 for i, ti in enumerate(token_line):
138 s = ti.string
137 s = ti.string
139 if s == '=' and paren_level == 0:
138 if s == '=' and paren_level == 0:
140 return i
139 return i
141 if s in {'(','[','{'}:
140 if s in {'(','[','{'}:
142 paren_level += 1
141 paren_level += 1
143 elif s in {')', ']', '}'}:
142 elif s in {')', ']', '}'}:
144 if paren_level > 0:
143 if paren_level > 0:
145 paren_level -= 1
144 paren_level -= 1
146 return None
145 return None
147
146
148 def find_end_of_continued_line(lines, start_line: int):
147 def find_end_of_continued_line(lines, start_line: int):
149 """Find the last line of a line explicitly extended using backslashes.
148 """Find the last line of a line explicitly extended using backslashes.
150
149
151 Uses 0-indexed line numbers.
150 Uses 0-indexed line numbers.
152 """
151 """
153 end_line = start_line
152 end_line = start_line
154 while lines[end_line].endswith('\\\n'):
153 while lines[end_line].endswith('\\\n'):
155 end_line += 1
154 end_line += 1
156 if end_line >= len(lines):
155 if end_line >= len(lines):
157 break
156 break
158 return end_line
157 return end_line
159
158
160 def assemble_continued_line(lines, start: Tuple[int, int], end_line: int):
159 def assemble_continued_line(lines, start: Tuple[int, int], end_line: int):
161 r"""Assemble a single line from multiple continued line pieces
160 r"""Assemble a single line from multiple continued line pieces
162
161
163 Continued lines are lines ending in ``\``, and the line following the last
162 Continued lines are lines ending in ``\``, and the line following the last
164 ``\`` in the block.
163 ``\`` in the block.
165
164
166 For example, this code continues over multiple lines::
165 For example, this code continues over multiple lines::
167
166
168 if (assign_ix is not None) \
167 if (assign_ix is not None) \
169 and (len(line) >= assign_ix + 2) \
168 and (len(line) >= assign_ix + 2) \
170 and (line[assign_ix+1].string == '%') \
169 and (line[assign_ix+1].string == '%') \
171 and (line[assign_ix+2].type == tokenize.NAME):
170 and (line[assign_ix+2].type == tokenize.NAME):
172
171
173 This statement contains four continued line pieces.
172 This statement contains four continued line pieces.
174 Assembling these pieces into a single line would give::
173 Assembling these pieces into a single line would give::
175
174
176 if (assign_ix is not None) and (len(line) >= assign_ix + 2) and (line[...
175 if (assign_ix is not None) and (len(line) >= assign_ix + 2) and (line[...
177
176
178 This uses 0-indexed line numbers. *start* is (lineno, colno).
177 This uses 0-indexed line numbers. *start* is (lineno, colno).
179
178
180 Used to allow ``%magic`` and ``!system`` commands to be continued over
179 Used to allow ``%magic`` and ``!system`` commands to be continued over
181 multiple lines.
180 multiple lines.
182 """
181 """
183 parts = [lines[start[0]][start[1]:]] + lines[start[0]+1:end_line+1]
182 parts = [lines[start[0]][start[1]:]] + lines[start[0]+1:end_line+1]
184 return ' '.join([p.rstrip()[:-1] for p in parts[:-1]] # Strip backslash+newline
183 return ' '.join([p.rstrip()[:-1] for p in parts[:-1]] # Strip backslash+newline
185 + [parts[-1].rstrip()]) # Strip newline from last line
184 + [parts[-1].rstrip()]) # Strip newline from last line
186
185
187 class TokenTransformBase:
186 class TokenTransformBase:
188 """Base class for transformations which examine tokens.
187 """Base class for transformations which examine tokens.
189
188
190 Special syntax should not be transformed when it occurs inside strings or
189 Special syntax should not be transformed when it occurs inside strings or
191 comments. This is hard to reliably avoid with regexes. The solution is to
190 comments. This is hard to reliably avoid with regexes. The solution is to
192 tokenise the code as Python, and recognise the special syntax in the tokens.
191 tokenise the code as Python, and recognise the special syntax in the tokens.
193
192
194 IPython's special syntax is not valid Python syntax, so tokenising may go
193 IPython's special syntax is not valid Python syntax, so tokenising may go
195 wrong after the special syntax starts. These classes therefore find and
194 wrong after the special syntax starts. These classes therefore find and
196 transform *one* instance of special syntax at a time into regular Python
195 transform *one* instance of special syntax at a time into regular Python
197 syntax. After each transformation, tokens are regenerated to find the next
196 syntax. After each transformation, tokens are regenerated to find the next
198 piece of special syntax.
197 piece of special syntax.
199
198
200 Subclasses need to implement one class method (find)
199 Subclasses need to implement one class method (find)
201 and one regular method (transform).
200 and one regular method (transform).
202
201
203 The priority attribute can select which transformation to apply if multiple
202 The priority attribute can select which transformation to apply if multiple
204 transformers match in the same place. Lower numbers have higher priority.
203 transformers match in the same place. Lower numbers have higher priority.
205 This allows "%magic?" to be turned into a help call rather than a magic call.
204 This allows "%magic?" to be turned into a help call rather than a magic call.
206 """
205 """
207 # Lower numbers -> higher priority (for matches in the same location)
206 # Lower numbers -> higher priority (for matches in the same location)
208 priority = 10
207 priority = 10
209
208
210 def sortby(self):
209 def sortby(self):
211 return self.start_line, self.start_col, self.priority
210 return self.start_line, self.start_col, self.priority
212
211
213 def __init__(self, start):
212 def __init__(self, start):
214 self.start_line = start[0] - 1 # Shift from 1-index to 0-index
213 self.start_line = start[0] - 1 # Shift from 1-index to 0-index
215 self.start_col = start[1]
214 self.start_col = start[1]
216
215
217 @classmethod
216 @classmethod
218 def find(cls, tokens_by_line):
217 def find(cls, tokens_by_line):
219 """Find one instance of special syntax in the provided tokens.
218 """Find one instance of special syntax in the provided tokens.
220
219
221 Tokens are grouped into logical lines for convenience,
220 Tokens are grouped into logical lines for convenience,
222 so it is easy to e.g. look at the first token of each line.
221 so it is easy to e.g. look at the first token of each line.
223 *tokens_by_line* is a list of lists of tokenize.TokenInfo objects.
222 *tokens_by_line* is a list of lists of tokenize.TokenInfo objects.
224
223
225 This should return an instance of its class, pointing to the start
224 This should return an instance of its class, pointing to the start
226 position it has found, or None if it found no match.
225 position it has found, or None if it found no match.
227 """
226 """
228 raise NotImplementedError
227 raise NotImplementedError
229
228
230 def transform(self, lines: List[str]):
229 def transform(self, lines: List[str]):
231 """Transform one instance of special syntax found by ``find()``
230 """Transform one instance of special syntax found by ``find()``
232
231
233 Takes a list of strings representing physical lines,
232 Takes a list of strings representing physical lines,
234 returns a similar list of transformed lines.
233 returns a similar list of transformed lines.
235 """
234 """
236 raise NotImplementedError
235 raise NotImplementedError
237
236
238 class MagicAssign(TokenTransformBase):
237 class MagicAssign(TokenTransformBase):
239 """Transformer for assignments from magics (a = %foo)"""
238 """Transformer for assignments from magics (a = %foo)"""
240 @classmethod
239 @classmethod
241 def find(cls, tokens_by_line):
240 def find(cls, tokens_by_line):
242 """Find the first magic assignment (a = %foo) in the cell.
241 """Find the first magic assignment (a = %foo) in the cell.
243 """
242 """
244 for line in tokens_by_line:
243 for line in tokens_by_line:
245 assign_ix = _find_assign_op(line)
244 assign_ix = _find_assign_op(line)
246 if (assign_ix is not None) \
245 if (assign_ix is not None) \
247 and (len(line) >= assign_ix + 2) \
246 and (len(line) >= assign_ix + 2) \
248 and (line[assign_ix+1].string == '%') \
247 and (line[assign_ix+1].string == '%') \
249 and (line[assign_ix+2].type == tokenize.NAME):
248 and (line[assign_ix+2].type == tokenize.NAME):
250 return cls(line[assign_ix+1].start)
249 return cls(line[assign_ix+1].start)
251
250
252 def transform(self, lines: List[str]):
251 def transform(self, lines: List[str]):
253 """Transform a magic assignment found by the ``find()`` classmethod.
252 """Transform a magic assignment found by the ``find()`` classmethod.
254 """
253 """
255 start_line, start_col = self.start_line, self.start_col
254 start_line, start_col = self.start_line, self.start_col
256 lhs = lines[start_line][:start_col]
255 lhs = lines[start_line][:start_col]
257 end_line = find_end_of_continued_line(lines, start_line)
256 end_line = find_end_of_continued_line(lines, start_line)
258 rhs = assemble_continued_line(lines, (start_line, start_col), end_line)
257 rhs = assemble_continued_line(lines, (start_line, start_col), end_line)
259 assert rhs.startswith('%'), rhs
258 assert rhs.startswith('%'), rhs
260 magic_name, _, args = rhs[1:].partition(' ')
259 magic_name, _, args = rhs[1:].partition(' ')
261
260
262 lines_before = lines[:start_line]
261 lines_before = lines[:start_line]
263 call = "get_ipython().run_line_magic({!r}, {!r})".format(magic_name, args)
262 call = "get_ipython().run_line_magic({!r}, {!r})".format(magic_name, args)
264 new_line = lhs + call + '\n'
263 new_line = lhs + call + '\n'
265 lines_after = lines[end_line+1:]
264 lines_after = lines[end_line+1:]
266
265
267 return lines_before + [new_line] + lines_after
266 return lines_before + [new_line] + lines_after
268
267
269
268
270 class SystemAssign(TokenTransformBase):
269 class SystemAssign(TokenTransformBase):
271 """Transformer for assignments from system commands (a = !foo)"""
270 """Transformer for assignments from system commands (a = !foo)"""
272 @classmethod
271 @classmethod
273 def find(cls, tokens_by_line):
272 def find(cls, tokens_by_line):
274 """Find the first system assignment (a = !foo) in the cell.
273 """Find the first system assignment (a = !foo) in the cell.
275 """
274 """
276 for line in tokens_by_line:
275 for line in tokens_by_line:
277 assign_ix = _find_assign_op(line)
276 assign_ix = _find_assign_op(line)
278 if (assign_ix is not None) \
277 if (assign_ix is not None) \
279 and not line[assign_ix].line.strip().startswith('=') \
278 and not line[assign_ix].line.strip().startswith('=') \
280 and (len(line) >= assign_ix + 2) \
279 and (len(line) >= assign_ix + 2) \
281 and (line[assign_ix + 1].type == tokenize.ERRORTOKEN):
280 and (line[assign_ix + 1].type == tokenize.ERRORTOKEN):
282 ix = assign_ix + 1
281 ix = assign_ix + 1
283
282
284 while ix < len(line) and line[ix].type == tokenize.ERRORTOKEN:
283 while ix < len(line) and line[ix].type == tokenize.ERRORTOKEN:
285 if line[ix].string == '!':
284 if line[ix].string == '!':
286 return cls(line[ix].start)
285 return cls(line[ix].start)
287 elif not line[ix].string.isspace():
286 elif not line[ix].string.isspace():
288 break
287 break
289 ix += 1
288 ix += 1
290
289
291 def transform(self, lines: List[str]):
290 def transform(self, lines: List[str]):
292 """Transform a system assignment found by the ``find()`` classmethod.
291 """Transform a system assignment found by the ``find()`` classmethod.
293 """
292 """
294 start_line, start_col = self.start_line, self.start_col
293 start_line, start_col = self.start_line, self.start_col
295
294
296 lhs = lines[start_line][:start_col]
295 lhs = lines[start_line][:start_col]
297 end_line = find_end_of_continued_line(lines, start_line)
296 end_line = find_end_of_continued_line(lines, start_line)
298 rhs = assemble_continued_line(lines, (start_line, start_col), end_line)
297 rhs = assemble_continued_line(lines, (start_line, start_col), end_line)
299 assert rhs.startswith('!'), rhs
298 assert rhs.startswith('!'), rhs
300 cmd = rhs[1:]
299 cmd = rhs[1:]
301
300
302 lines_before = lines[:start_line]
301 lines_before = lines[:start_line]
303 call = "get_ipython().getoutput({!r})".format(cmd)
302 call = "get_ipython().getoutput({!r})".format(cmd)
304 new_line = lhs + call + '\n'
303 new_line = lhs + call + '\n'
305 lines_after = lines[end_line + 1:]
304 lines_after = lines[end_line + 1:]
306
305
307 return lines_before + [new_line] + lines_after
306 return lines_before + [new_line] + lines_after
308
307
309 # The escape sequences that define the syntax transformations IPython will
308 # The escape sequences that define the syntax transformations IPython will
310 # apply to user input. These can NOT be just changed here: many regular
309 # apply to user input. These can NOT be just changed here: many regular
311 # expressions and other parts of the code may use their hardcoded values, and
310 # expressions and other parts of the code may use their hardcoded values, and
312 # for all intents and purposes they constitute the 'IPython syntax', so they
311 # for all intents and purposes they constitute the 'IPython syntax', so they
313 # should be considered fixed.
312 # should be considered fixed.
314
313
315 ESC_SHELL = '!' # Send line to underlying system shell
314 ESC_SHELL = '!' # Send line to underlying system shell
316 ESC_SH_CAP = '!!' # Send line to system shell and capture output
315 ESC_SH_CAP = '!!' # Send line to system shell and capture output
317 ESC_HELP = '?' # Find information about object
316 ESC_HELP = '?' # Find information about object
318 ESC_HELP2 = '??' # Find extra-detailed information about object
317 ESC_HELP2 = '??' # Find extra-detailed information about object
319 ESC_MAGIC = '%' # Call magic function
318 ESC_MAGIC = '%' # Call magic function
320 ESC_MAGIC2 = '%%' # Call cell-magic function
319 ESC_MAGIC2 = '%%' # Call cell-magic function
321 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
320 ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
322 ESC_QUOTE2 = ';' # Quote all args as a single string, call
321 ESC_QUOTE2 = ';' # Quote all args as a single string, call
323 ESC_PAREN = '/' # Call first argument with rest of line as arguments
322 ESC_PAREN = '/' # Call first argument with rest of line as arguments
324
323
325 ESCAPE_SINGLES = {'!', '?', '%', ',', ';', '/'}
324 ESCAPE_SINGLES = {'!', '?', '%', ',', ';', '/'}
326 ESCAPE_DOUBLES = {'!!', '??'} # %% (cell magic) is handled separately
325 ESCAPE_DOUBLES = {'!!', '??'} # %% (cell magic) is handled separately
327
326
328 def _make_help_call(target, esc):
327 def _make_help_call(target, esc):
329 """Prepares a pinfo(2)/psearch call from a target name and the escape
328 """Prepares a pinfo(2)/psearch call from a target name and the escape
330 (i.e. ? or ??)"""
329 (i.e. ? or ??)"""
331 method = 'pinfo2' if esc == '??' \
330 method = 'pinfo2' if esc == '??' \
332 else 'psearch' if '*' in target \
331 else 'psearch' if '*' in target \
333 else 'pinfo'
332 else 'pinfo'
334 arg = " ".join([method, target])
333 arg = " ".join([method, target])
335 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
334 #Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
336 t_magic_name, _, t_magic_arg_s = arg.partition(' ')
335 t_magic_name, _, t_magic_arg_s = arg.partition(' ')
337 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
336 t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
338 return "get_ipython().run_line_magic(%r, %r)" % (t_magic_name, t_magic_arg_s)
337 return "get_ipython().run_line_magic(%r, %r)" % (t_magic_name, t_magic_arg_s)
339
338
340
339
341 def _tr_help(content):
340 def _tr_help(content):
342 """Translate lines escaped with: ?
341 """Translate lines escaped with: ?
343
342
344 A naked help line should fire the intro help screen (shell.show_usage())
343 A naked help line should fire the intro help screen (shell.show_usage())
345 """
344 """
346 if not content:
345 if not content:
347 return 'get_ipython().show_usage()'
346 return 'get_ipython().show_usage()'
348
347
349 return _make_help_call(content, '?')
348 return _make_help_call(content, '?')
350
349
351 def _tr_help2(content):
350 def _tr_help2(content):
352 """Translate lines escaped with: ??
351 """Translate lines escaped with: ??
353
352
354 A naked help line should fire the intro help screen (shell.show_usage())
353 A naked help line should fire the intro help screen (shell.show_usage())
355 """
354 """
356 if not content:
355 if not content:
357 return 'get_ipython().show_usage()'
356 return 'get_ipython().show_usage()'
358
357
359 return _make_help_call(content, '??')
358 return _make_help_call(content, '??')
360
359
361 def _tr_magic(content):
360 def _tr_magic(content):
362 "Translate lines escaped with a percent sign: %"
361 "Translate lines escaped with a percent sign: %"
363 name, _, args = content.partition(' ')
362 name, _, args = content.partition(' ')
364 return 'get_ipython().run_line_magic(%r, %r)' % (name, args)
363 return 'get_ipython().run_line_magic(%r, %r)' % (name, args)
365
364
366 def _tr_quote(content):
365 def _tr_quote(content):
367 "Translate lines escaped with a comma: ,"
366 "Translate lines escaped with a comma: ,"
368 name, _, args = content.partition(' ')
367 name, _, args = content.partition(' ')
369 return '%s("%s")' % (name, '", "'.join(args.split()) )
368 return '%s("%s")' % (name, '", "'.join(args.split()) )
370
369
371 def _tr_quote2(content):
370 def _tr_quote2(content):
372 "Translate lines escaped with a semicolon: ;"
371 "Translate lines escaped with a semicolon: ;"
373 name, _, args = content.partition(' ')
372 name, _, args = content.partition(' ')
374 return '%s("%s")' % (name, args)
373 return '%s("%s")' % (name, args)
375
374
376 def _tr_paren(content):
375 def _tr_paren(content):
377 "Translate lines escaped with a slash: /"
376 "Translate lines escaped with a slash: /"
378 name, _, args = content.partition(' ')
377 name, _, args = content.partition(' ')
379 return '%s(%s)' % (name, ", ".join(args.split()))
378 return '%s(%s)' % (name, ", ".join(args.split()))
380
379
381 tr = { ESC_SHELL : 'get_ipython().system({!r})'.format,
380 tr = { ESC_SHELL : 'get_ipython().system({!r})'.format,
382 ESC_SH_CAP : 'get_ipython().getoutput({!r})'.format,
381 ESC_SH_CAP : 'get_ipython().getoutput({!r})'.format,
383 ESC_HELP : _tr_help,
382 ESC_HELP : _tr_help,
384 ESC_HELP2 : _tr_help2,
383 ESC_HELP2 : _tr_help2,
385 ESC_MAGIC : _tr_magic,
384 ESC_MAGIC : _tr_magic,
386 ESC_QUOTE : _tr_quote,
385 ESC_QUOTE : _tr_quote,
387 ESC_QUOTE2 : _tr_quote2,
386 ESC_QUOTE2 : _tr_quote2,
388 ESC_PAREN : _tr_paren }
387 ESC_PAREN : _tr_paren }
389
388
390 class EscapedCommand(TokenTransformBase):
389 class EscapedCommand(TokenTransformBase):
391 """Transformer for escaped commands like %foo, !foo, or /foo"""
390 """Transformer for escaped commands like %foo, !foo, or /foo"""
392 @classmethod
391 @classmethod
393 def find(cls, tokens_by_line):
392 def find(cls, tokens_by_line):
394 """Find the first escaped command (%foo, !foo, etc.) in the cell.
393 """Find the first escaped command (%foo, !foo, etc.) in the cell.
395 """
394 """
396 for line in tokens_by_line:
395 for line in tokens_by_line:
397 if not line:
396 if not line:
398 continue
397 continue
399 ix = 0
398 ix = 0
400 ll = len(line)
399 ll = len(line)
401 while ll > ix and line[ix].type in {tokenize.INDENT, tokenize.DEDENT}:
400 while ll > ix and line[ix].type in {tokenize.INDENT, tokenize.DEDENT}:
402 ix += 1
401 ix += 1
403 if ix >= ll:
402 if ix >= ll:
404 continue
403 continue
405 if line[ix].string in ESCAPE_SINGLES:
404 if line[ix].string in ESCAPE_SINGLES:
406 return cls(line[ix].start)
405 return cls(line[ix].start)
407
406
408 def transform(self, lines):
407 def transform(self, lines):
409 """Transform an escaped line found by the ``find()`` classmethod.
408 """Transform an escaped line found by the ``find()`` classmethod.
410 """
409 """
411 start_line, start_col = self.start_line, self.start_col
410 start_line, start_col = self.start_line, self.start_col
412
411
413 indent = lines[start_line][:start_col]
412 indent = lines[start_line][:start_col]
414 end_line = find_end_of_continued_line(lines, start_line)
413 end_line = find_end_of_continued_line(lines, start_line)
415 line = assemble_continued_line(lines, (start_line, start_col), end_line)
414 line = assemble_continued_line(lines, (start_line, start_col), end_line)
416
415
417 if len(line) > 1 and line[:2] in ESCAPE_DOUBLES:
416 if len(line) > 1 and line[:2] in ESCAPE_DOUBLES:
418 escape, content = line[:2], line[2:]
417 escape, content = line[:2], line[2:]
419 else:
418 else:
420 escape, content = line[:1], line[1:]
419 escape, content = line[:1], line[1:]
421
420
422 if escape in tr:
421 if escape in tr:
423 call = tr[escape](content)
422 call = tr[escape](content)
424 else:
423 else:
425 call = ''
424 call = ''
426
425
427 lines_before = lines[:start_line]
426 lines_before = lines[:start_line]
428 new_line = indent + call + '\n'
427 new_line = indent + call + '\n'
429 lines_after = lines[end_line + 1:]
428 lines_after = lines[end_line + 1:]
430
429
431 return lines_before + [new_line] + lines_after
430 return lines_before + [new_line] + lines_after
432
431
433 _help_end_re = re.compile(r"""(%{0,2}
432 _help_end_re = re.compile(r"""(%{0,2}
434 (?!\d)[\w*]+ # Variable name
433 (?!\d)[\w*]+ # Variable name
435 (\.(?!\d)[\w*]+)* # .etc.etc
434 (\.(?!\d)[\w*]+)* # .etc.etc
436 )
435 )
437 (\?\??)$ # ? or ??
436 (\?\??)$ # ? or ??
438 """,
437 """,
439 re.VERBOSE)
438 re.VERBOSE)
440
439
441 class HelpEnd(TokenTransformBase):
440 class HelpEnd(TokenTransformBase):
442 """Transformer for help syntax: obj? and obj??"""
441 """Transformer for help syntax: obj? and obj??"""
443 # This needs to be higher priority (lower number) than EscapedCommand so
442 # This needs to be higher priority (lower number) than EscapedCommand so
444 # that inspecting magics (%foo?) works.
443 # that inspecting magics (%foo?) works.
445 priority = 5
444 priority = 5
446
445
447 def __init__(self, start, q_locn):
446 def __init__(self, start, q_locn):
448 super().__init__(start)
447 super().__init__(start)
449 self.q_line = q_locn[0] - 1 # Shift from 1-indexed to 0-indexed
448 self.q_line = q_locn[0] - 1 # Shift from 1-indexed to 0-indexed
450 self.q_col = q_locn[1]
449 self.q_col = q_locn[1]
451
450
452 @classmethod
451 @classmethod
453 def find(cls, tokens_by_line):
452 def find(cls, tokens_by_line):
454 """Find the first help command (foo?) in the cell.
453 """Find the first help command (foo?) in the cell.
455 """
454 """
456 for line in tokens_by_line:
455 for line in tokens_by_line:
457 # Last token is NEWLINE; look at last but one
456 # Last token is NEWLINE; look at last but one
458 if len(line) > 2 and line[-2].string == '?':
457 if len(line) > 2 and line[-2].string == '?':
459 # Find the first token that's not INDENT/DEDENT
458 # Find the first token that's not INDENT/DEDENT
460 ix = 0
459 ix = 0
461 while line[ix].type in {tokenize.INDENT, tokenize.DEDENT}:
460 while line[ix].type in {tokenize.INDENT, tokenize.DEDENT}:
462 ix += 1
461 ix += 1
463 return cls(line[ix].start, line[-2].start)
462 return cls(line[ix].start, line[-2].start)
464
463
465 def transform(self, lines):
464 def transform(self, lines):
466 """Transform a help command found by the ``find()`` classmethod.
465 """Transform a help command found by the ``find()`` classmethod.
467 """
466 """
468 piece = ''.join(lines[self.start_line:self.q_line+1])
467 piece = ''.join(lines[self.start_line:self.q_line+1])
469 indent, content = piece[:self.start_col], piece[self.start_col:]
468 indent, content = piece[:self.start_col], piece[self.start_col:]
470 lines_before = lines[:self.start_line]
469 lines_before = lines[:self.start_line]
471 lines_after = lines[self.q_line + 1:]
470 lines_after = lines[self.q_line + 1:]
472
471
473 m = _help_end_re.search(content)
472 m = _help_end_re.search(content)
474 if not m:
473 if not m:
475 raise SyntaxError(content)
474 raise SyntaxError(content)
476 assert m is not None, content
475 assert m is not None, content
477 target = m.group(1)
476 target = m.group(1)
478 esc = m.group(3)
477 esc = m.group(3)
479
478
480
479
481 call = _make_help_call(target, esc)
480 call = _make_help_call(target, esc)
482 new_line = indent + call + '\n'
481 new_line = indent + call + '\n'
483
482
484 return lines_before + [new_line] + lines_after
483 return lines_before + [new_line] + lines_after
485
484
486 def make_tokens_by_line(lines:List[str]):
485 def make_tokens_by_line(lines:List[str]):
487 """Tokenize a series of lines and group tokens by line.
486 """Tokenize a series of lines and group tokens by line.
488
487
489 The tokens for a multiline Python string or expression are grouped as one
488 The tokens for a multiline Python string or expression are grouped as one
490 line. All lines except the last lines should keep their line ending ('\\n',
489 line. All lines except the last lines should keep their line ending ('\\n',
491 '\\r\\n') for this to properly work. Use `.splitlines(keeplineending=True)`
490 '\\r\\n') for this to properly work. Use `.splitlines(keeplineending=True)`
492 for example when passing block of text to this function.
491 for example when passing block of text to this function.
493
492
494 """
493 """
495 # NL tokens are used inside multiline expressions, but also after blank
494 # NL tokens are used inside multiline expressions, but also after blank
496 # lines or comments. This is intentional - see https://bugs.python.org/issue17061
495 # lines or comments. This is intentional - see https://bugs.python.org/issue17061
497 # We want to group the former case together but split the latter, so we
496 # We want to group the former case together but split the latter, so we
498 # track parentheses level, similar to the internals of tokenize.
497 # track parentheses level, similar to the internals of tokenize.
499
498
500 # reexported from token on 3.7+
499 # reexported from token on 3.7+
501 NEWLINE, NL = tokenize.NEWLINE, tokenize.NL # type: ignore
500 NEWLINE, NL = tokenize.NEWLINE, tokenize.NL # type: ignore
502 tokens_by_line: List[List[Any]] = [[]]
501 tokens_by_line: List[List[Any]] = [[]]
503 if len(lines) > 1 and not lines[0].endswith(("\n", "\r", "\r\n", "\x0b", "\x0c")):
502 if len(lines) > 1 and not lines[0].endswith(("\n", "\r", "\r\n", "\x0b", "\x0c")):
504 warnings.warn(
503 warnings.warn(
505 "`make_tokens_by_line` received a list of lines which do not have lineending markers ('\\n', '\\r', '\\r\\n', '\\x0b', '\\x0c'), behavior will be unspecified",
504 "`make_tokens_by_line` received a list of lines which do not have lineending markers ('\\n', '\\r', '\\r\\n', '\\x0b', '\\x0c'), behavior will be unspecified",
506 stacklevel=2,
505 stacklevel=2,
507 )
506 )
508 parenlev = 0
507 parenlev = 0
509 try:
508 try:
510 for token in tokenize.generate_tokens(iter(lines).__next__):
509 for token in tokenize.generate_tokens(iter(lines).__next__):
511 tokens_by_line[-1].append(token)
510 tokens_by_line[-1].append(token)
512 if (token.type == NEWLINE) \
511 if (token.type == NEWLINE) \
513 or ((token.type == NL) and (parenlev <= 0)):
512 or ((token.type == NL) and (parenlev <= 0)):
514 tokens_by_line.append([])
513 tokens_by_line.append([])
515 elif token.string in {'(', '[', '{'}:
514 elif token.string in {'(', '[', '{'}:
516 parenlev += 1
515 parenlev += 1
517 elif token.string in {')', ']', '}'}:
516 elif token.string in {')', ']', '}'}:
518 if parenlev > 0:
517 if parenlev > 0:
519 parenlev -= 1
518 parenlev -= 1
520 except tokenize.TokenError:
519 except tokenize.TokenError:
521 # Input ended in a multiline string or expression. That's OK for us.
520 # Input ended in a multiline string or expression. That's OK for us.
522 pass
521 pass
523
522
524
523
525 if not tokens_by_line[-1]:
524 if not tokens_by_line[-1]:
526 tokens_by_line.pop()
525 tokens_by_line.pop()
527
526
528
527
529 return tokens_by_line
528 return tokens_by_line
530
529
531
530
532 def has_sunken_brackets(tokens: List[tokenize.TokenInfo]):
531 def has_sunken_brackets(tokens: List[tokenize.TokenInfo]):
533 """Check if the depth of brackets in the list of tokens drops below 0"""
532 """Check if the depth of brackets in the list of tokens drops below 0"""
534 parenlev = 0
533 parenlev = 0
535 for token in tokens:
534 for token in tokens:
536 if token.string in {"(", "[", "{"}:
535 if token.string in {"(", "[", "{"}:
537 parenlev += 1
536 parenlev += 1
538 elif token.string in {")", "]", "}"}:
537 elif token.string in {")", "]", "}"}:
539 parenlev -= 1
538 parenlev -= 1
540 if parenlev < 0:
539 if parenlev < 0:
541 return True
540 return True
542 return False
541 return False
543
542
544
543
545 def show_linewise_tokens(s: str):
544 def show_linewise_tokens(s: str):
546 """For investigation and debugging"""
545 """For investigation and debugging"""
547 if not s.endswith('\n'):
546 if not s.endswith('\n'):
548 s += '\n'
547 s += '\n'
549 lines = s.splitlines(keepends=True)
548 lines = s.splitlines(keepends=True)
550 for line in make_tokens_by_line(lines):
549 for line in make_tokens_by_line(lines):
551 print("Line -------")
550 print("Line -------")
552 for tokinfo in line:
551 for tokinfo in line:
553 print(" ", tokinfo)
552 print(" ", tokinfo)
554
553
555 # Arbitrary limit to prevent getting stuck in infinite loops
554 # Arbitrary limit to prevent getting stuck in infinite loops
556 TRANSFORM_LOOP_LIMIT = 500
555 TRANSFORM_LOOP_LIMIT = 500
557
556
558 class TransformerManager:
557 class TransformerManager:
559 """Applies various transformations to a cell or code block.
558 """Applies various transformations to a cell or code block.
560
559
561 The key methods for external use are ``transform_cell()``
560 The key methods for external use are ``transform_cell()``
562 and ``check_complete()``.
561 and ``check_complete()``.
563 """
562 """
564 def __init__(self):
563 def __init__(self):
565 self.cleanup_transforms = [
564 self.cleanup_transforms = [
566 leading_empty_lines,
565 leading_empty_lines,
567 leading_indent,
566 leading_indent,
568 classic_prompt,
567 classic_prompt,
569 ipython_prompt,
568 ipython_prompt,
570 ]
569 ]
571 self.line_transforms = [
570 self.line_transforms = [
572 cell_magic,
571 cell_magic,
573 ]
572 ]
574 self.token_transformers = [
573 self.token_transformers = [
575 MagicAssign,
574 MagicAssign,
576 SystemAssign,
575 SystemAssign,
577 EscapedCommand,
576 EscapedCommand,
578 HelpEnd,
577 HelpEnd,
579 ]
578 ]
580
579
581 def do_one_token_transform(self, lines):
580 def do_one_token_transform(self, lines):
582 """Find and run the transform earliest in the code.
581 """Find and run the transform earliest in the code.
583
582
584 Returns (changed, lines).
583 Returns (changed, lines).
585
584
586 This method is called repeatedly until changed is False, indicating
585 This method is called repeatedly until changed is False, indicating
587 that all available transformations are complete.
586 that all available transformations are complete.
588
587
589 The tokens following IPython special syntax might not be valid, so
588 The tokens following IPython special syntax might not be valid, so
590 the transformed code is retokenised every time to identify the next
589 the transformed code is retokenised every time to identify the next
591 piece of special syntax. Hopefully long code cells are mostly valid
590 piece of special syntax. Hopefully long code cells are mostly valid
592 Python, not using lots of IPython special syntax, so this shouldn't be
591 Python, not using lots of IPython special syntax, so this shouldn't be
593 a performance issue.
592 a performance issue.
594 """
593 """
595 tokens_by_line = make_tokens_by_line(lines)
594 tokens_by_line = make_tokens_by_line(lines)
596 candidates = []
595 candidates = []
597 for transformer_cls in self.token_transformers:
596 for transformer_cls in self.token_transformers:
598 transformer = transformer_cls.find(tokens_by_line)
597 transformer = transformer_cls.find(tokens_by_line)
599 if transformer:
598 if transformer:
600 candidates.append(transformer)
599 candidates.append(transformer)
601
600
602 if not candidates:
601 if not candidates:
603 # Nothing to transform
602 # Nothing to transform
604 return False, lines
603 return False, lines
605 ordered_transformers = sorted(candidates, key=TokenTransformBase.sortby)
604 ordered_transformers = sorted(candidates, key=TokenTransformBase.sortby)
606 for transformer in ordered_transformers:
605 for transformer in ordered_transformers:
607 try:
606 try:
608 return True, transformer.transform(lines)
607 return True, transformer.transform(lines)
609 except SyntaxError:
608 except SyntaxError:
610 pass
609 pass
611 return False, lines
610 return False, lines
612
611
613 def do_token_transforms(self, lines):
612 def do_token_transforms(self, lines):
614 for _ in range(TRANSFORM_LOOP_LIMIT):
613 for _ in range(TRANSFORM_LOOP_LIMIT):
615 changed, lines = self.do_one_token_transform(lines)
614 changed, lines = self.do_one_token_transform(lines)
616 if not changed:
615 if not changed:
617 return lines
616 return lines
618
617
619 raise RuntimeError("Input transformation still changing after "
618 raise RuntimeError("Input transformation still changing after "
620 "%d iterations. Aborting." % TRANSFORM_LOOP_LIMIT)
619 "%d iterations. Aborting." % TRANSFORM_LOOP_LIMIT)
621
620
622 def transform_cell(self, cell: str) -> str:
621 def transform_cell(self, cell: str) -> str:
623 """Transforms a cell of input code"""
622 """Transforms a cell of input code"""
624 if not cell.endswith('\n'):
623 if not cell.endswith('\n'):
625 cell += '\n' # Ensure the cell has a trailing newline
624 cell += '\n' # Ensure the cell has a trailing newline
626 lines = cell.splitlines(keepends=True)
625 lines = cell.splitlines(keepends=True)
627 for transform in self.cleanup_transforms + self.line_transforms:
626 for transform in self.cleanup_transforms + self.line_transforms:
628 lines = transform(lines)
627 lines = transform(lines)
629
628
630 lines = self.do_token_transforms(lines)
629 lines = self.do_token_transforms(lines)
631 return ''.join(lines)
630 return ''.join(lines)
632
631
633 def check_complete(self, cell: str):
632 def check_complete(self, cell: str):
634 """Return whether a block of code is ready to execute, or should be continued
633 """Return whether a block of code is ready to execute, or should be continued
635
634
636 Parameters
635 Parameters
637 ----------
636 ----------
638 cell : string
637 cell : string
639 Python input code, which can be multiline.
638 Python input code, which can be multiline.
640
639
641 Returns
640 Returns
642 -------
641 -------
643 status : str
642 status : str
644 One of 'complete', 'incomplete', or 'invalid' if source is not a
643 One of 'complete', 'incomplete', or 'invalid' if source is not a
645 prefix of valid code.
644 prefix of valid code.
646 indent_spaces : int or None
645 indent_spaces : int or None
647 The number of spaces by which to indent the next line of code. If
646 The number of spaces by which to indent the next line of code. If
648 status is not 'incomplete', this is None.
647 status is not 'incomplete', this is None.
649 """
648 """
650 # Remember if the lines ends in a new line.
649 # Remember if the lines ends in a new line.
651 ends_with_newline = False
650 ends_with_newline = False
652 for character in reversed(cell):
651 for character in reversed(cell):
653 if character == '\n':
652 if character == '\n':
654 ends_with_newline = True
653 ends_with_newline = True
655 break
654 break
656 elif character.strip():
655 elif character.strip():
657 break
656 break
658 else:
657 else:
659 continue
658 continue
660
659
661 if not ends_with_newline:
660 if not ends_with_newline:
662 # Append an newline for consistent tokenization
661 # Append an newline for consistent tokenization
663 # See https://bugs.python.org/issue33899
662 # See https://bugs.python.org/issue33899
664 cell += '\n'
663 cell += '\n'
665
664
666 lines = cell.splitlines(keepends=True)
665 lines = cell.splitlines(keepends=True)
667
666
668 if not lines:
667 if not lines:
669 return 'complete', None
668 return 'complete', None
670
669
671 if lines[-1].endswith('\\'):
670 if lines[-1].endswith('\\'):
672 # Explicit backslash continuation
671 # Explicit backslash continuation
673 return 'incomplete', find_last_indent(lines)
672 return 'incomplete', find_last_indent(lines)
674
673
675 try:
674 try:
676 for transform in self.cleanup_transforms:
675 for transform in self.cleanup_transforms:
677 if not getattr(transform, 'has_side_effects', False):
676 if not getattr(transform, 'has_side_effects', False):
678 lines = transform(lines)
677 lines = transform(lines)
679 except SyntaxError:
678 except SyntaxError:
680 return 'invalid', None
679 return 'invalid', None
681
680
682 if lines[0].startswith('%%'):
681 if lines[0].startswith('%%'):
683 # Special case for cell magics - completion marked by blank line
682 # Special case for cell magics - completion marked by blank line
684 if lines[-1].strip():
683 if lines[-1].strip():
685 return 'incomplete', find_last_indent(lines)
684 return 'incomplete', find_last_indent(lines)
686 else:
685 else:
687 return 'complete', None
686 return 'complete', None
688
687
689 try:
688 try:
690 for transform in self.line_transforms:
689 for transform in self.line_transforms:
691 if not getattr(transform, 'has_side_effects', False):
690 if not getattr(transform, 'has_side_effects', False):
692 lines = transform(lines)
691 lines = transform(lines)
693 lines = self.do_token_transforms(lines)
692 lines = self.do_token_transforms(lines)
694 except SyntaxError:
693 except SyntaxError:
695 return 'invalid', None
694 return 'invalid', None
696
695
697 tokens_by_line = make_tokens_by_line(lines)
696 tokens_by_line = make_tokens_by_line(lines)
698
697
699 # Bail if we got one line and there are more closing parentheses than
698 # Bail if we got one line and there are more closing parentheses than
700 # the opening ones
699 # the opening ones
701 if (
700 if (
702 len(lines) == 1
701 len(lines) == 1
703 and tokens_by_line
702 and tokens_by_line
704 and has_sunken_brackets(tokens_by_line[0])
703 and has_sunken_brackets(tokens_by_line[0])
705 ):
704 ):
706 return "invalid", None
705 return "invalid", None
707
706
708 if not tokens_by_line:
707 if not tokens_by_line:
709 return 'incomplete', find_last_indent(lines)
708 return 'incomplete', find_last_indent(lines)
710
709
711 if tokens_by_line[-1][-1].type != tokenize.ENDMARKER:
710 if tokens_by_line[-1][-1].type != tokenize.ENDMARKER:
712 # We're in a multiline string or expression
711 # We're in a multiline string or expression
713 return 'incomplete', find_last_indent(lines)
712 return 'incomplete', find_last_indent(lines)
714
713
715 newline_types = {tokenize.NEWLINE, tokenize.COMMENT, tokenize.ENDMARKER} # type: ignore
714 newline_types = {tokenize.NEWLINE, tokenize.COMMENT, tokenize.ENDMARKER} # type: ignore
716
715
717 # Pop the last line which only contains DEDENTs and ENDMARKER
716 # Pop the last line which only contains DEDENTs and ENDMARKER
718 last_token_line = None
717 last_token_line = None
719 if {t.type for t in tokens_by_line[-1]} in [
718 if {t.type for t in tokens_by_line[-1]} in [
720 {tokenize.DEDENT, tokenize.ENDMARKER},
719 {tokenize.DEDENT, tokenize.ENDMARKER},
721 {tokenize.ENDMARKER}
720 {tokenize.ENDMARKER}
722 ] and len(tokens_by_line) > 1:
721 ] and len(tokens_by_line) > 1:
723 last_token_line = tokens_by_line.pop()
722 last_token_line = tokens_by_line.pop()
724
723
725 while tokens_by_line[-1] and tokens_by_line[-1][-1].type in newline_types:
724 while tokens_by_line[-1] and tokens_by_line[-1][-1].type in newline_types:
726 tokens_by_line[-1].pop()
725 tokens_by_line[-1].pop()
727
726
728 if not tokens_by_line[-1]:
727 if not tokens_by_line[-1]:
729 return 'incomplete', find_last_indent(lines)
728 return 'incomplete', find_last_indent(lines)
730
729
731 if tokens_by_line[-1][-1].string == ':':
730 if tokens_by_line[-1][-1].string == ':':
732 # The last line starts a block (e.g. 'if foo:')
731 # The last line starts a block (e.g. 'if foo:')
733 ix = 0
732 ix = 0
734 while tokens_by_line[-1][ix].type in {tokenize.INDENT, tokenize.DEDENT}:
733 while tokens_by_line[-1][ix].type in {tokenize.INDENT, tokenize.DEDENT}:
735 ix += 1
734 ix += 1
736
735
737 indent = tokens_by_line[-1][ix].start[1]
736 indent = tokens_by_line[-1][ix].start[1]
738 return 'incomplete', indent + 4
737 return 'incomplete', indent + 4
739
738
740 if tokens_by_line[-1][0].line.endswith('\\'):
739 if tokens_by_line[-1][0].line.endswith('\\'):
741 return 'incomplete', None
740 return 'incomplete', None
742
741
743 # At this point, our checks think the code is complete (or invalid).
742 # At this point, our checks think the code is complete (or invalid).
744 # We'll use codeop.compile_command to check this with the real parser
743 # We'll use codeop.compile_command to check this with the real parser
745 try:
744 try:
746 with warnings.catch_warnings():
745 with warnings.catch_warnings():
747 warnings.simplefilter('error', SyntaxWarning)
746 warnings.simplefilter('error', SyntaxWarning)
748 res = compile_command(''.join(lines), symbol='exec')
747 res = compile_command(''.join(lines), symbol='exec')
749 except (SyntaxError, OverflowError, ValueError, TypeError,
748 except (SyntaxError, OverflowError, ValueError, TypeError,
750 MemoryError, SyntaxWarning):
749 MemoryError, SyntaxWarning):
751 return 'invalid', None
750 return 'invalid', None
752 else:
751 else:
753 if res is None:
752 if res is None:
754 return 'incomplete', find_last_indent(lines)
753 return 'incomplete', find_last_indent(lines)
755
754
756 if last_token_line and last_token_line[0].type == tokenize.DEDENT:
755 if last_token_line and last_token_line[0].type == tokenize.DEDENT:
757 if ends_with_newline:
756 if ends_with_newline:
758 return 'complete', None
757 return 'complete', None
759 return 'incomplete', find_last_indent(lines)
758 return 'incomplete', find_last_indent(lines)
760
759
761 # If there's a blank line at the end, assume we're ready to execute
760 # If there's a blank line at the end, assume we're ready to execute
762 if not lines[-1].strip():
761 if not lines[-1].strip():
763 return 'complete', None
762 return 'complete', None
764
763
765 return 'complete', None
764 return 'complete', None
766
765
767
766
768 def find_last_indent(lines):
767 def find_last_indent(lines):
769 m = _indent_re.match(lines[-1])
768 m = _indent_re.match(lines[-1])
770 if not m:
769 if not m:
771 return 0
770 return 0
772 return len(m.group(0).replace('\t', ' '*4))
771 return len(m.group(0).replace('\t', ' '*4))
773
772
774
773
775 class MaybeAsyncCompile(Compile):
774 class MaybeAsyncCompile(Compile):
776 def __init__(self, extra_flags=0):
775 def __init__(self, extra_flags=0):
777 super().__init__()
776 super().__init__()
778 self.flags |= extra_flags
777 self.flags |= extra_flags
779
778
780
779
781 class MaybeAsyncCommandCompiler(CommandCompiler):
780 class MaybeAsyncCommandCompiler(CommandCompiler):
782 def __init__(self, extra_flags=0):
781 def __init__(self, extra_flags=0):
783 self.compiler = MaybeAsyncCompile(extra_flags=extra_flags)
782 self.compiler = MaybeAsyncCompile(extra_flags=extra_flags)
784
783
785
784
786 _extra_flags = ast.PyCF_ALLOW_TOP_LEVEL_AWAIT
785 _extra_flags = ast.PyCF_ALLOW_TOP_LEVEL_AWAIT
787
786
788 compile_command = MaybeAsyncCommandCompiler(extra_flags=_extra_flags)
787 compile_command = MaybeAsyncCommandCompiler(extra_flags=_extra_flags)
@@ -1,659 +1,658 b''
1 """Implementation of basic magic functions."""
1 """Implementation of basic magic functions."""
2
2
3
3
4 import argparse
5 from logging import error
4 from logging import error
6 import io
5 import io
7 import os
6 import os
8 from pprint import pformat
7 from pprint import pformat
9 import sys
8 import sys
10 from warnings import warn
9 from warnings import warn
11
10
12 from traitlets.utils.importstring import import_item
11 from traitlets.utils.importstring import import_item
13 from IPython.core import magic_arguments, page
12 from IPython.core import magic_arguments, page
14 from IPython.core.error import UsageError
13 from IPython.core.error import UsageError
15 from IPython.core.magic import Magics, magics_class, line_magic, magic_escapes
14 from IPython.core.magic import Magics, magics_class, line_magic, magic_escapes
16 from IPython.utils.text import format_screen, dedent, indent
15 from IPython.utils.text import format_screen, dedent, indent
17 from IPython.testing.skipdoctest import skip_doctest
16 from IPython.testing.skipdoctest import skip_doctest
18 from IPython.utils.ipstruct import Struct
17 from IPython.utils.ipstruct import Struct
19
18
20
19
21 class MagicsDisplay(object):
20 class MagicsDisplay(object):
22 def __init__(self, magics_manager, ignore=None):
21 def __init__(self, magics_manager, ignore=None):
23 self.ignore = ignore if ignore else []
22 self.ignore = ignore if ignore else []
24 self.magics_manager = magics_manager
23 self.magics_manager = magics_manager
25
24
26 def _lsmagic(self):
25 def _lsmagic(self):
27 """The main implementation of the %lsmagic"""
26 """The main implementation of the %lsmagic"""
28 mesc = magic_escapes['line']
27 mesc = magic_escapes['line']
29 cesc = magic_escapes['cell']
28 cesc = magic_escapes['cell']
30 mman = self.magics_manager
29 mman = self.magics_manager
31 magics = mman.lsmagic()
30 magics = mman.lsmagic()
32 out = ['Available line magics:',
31 out = ['Available line magics:',
33 mesc + (' '+mesc).join(sorted([m for m,v in magics['line'].items() if (v not in self.ignore)])),
32 mesc + (' '+mesc).join(sorted([m for m,v in magics['line'].items() if (v not in self.ignore)])),
34 '',
33 '',
35 'Available cell magics:',
34 'Available cell magics:',
36 cesc + (' '+cesc).join(sorted([m for m,v in magics['cell'].items() if (v not in self.ignore)])),
35 cesc + (' '+cesc).join(sorted([m for m,v in magics['cell'].items() if (v not in self.ignore)])),
37 '',
36 '',
38 mman.auto_status()]
37 mman.auto_status()]
39 return '\n'.join(out)
38 return '\n'.join(out)
40
39
41 def _repr_pretty_(self, p, cycle):
40 def _repr_pretty_(self, p, cycle):
42 p.text(self._lsmagic())
41 p.text(self._lsmagic())
43
42
44 def __str__(self):
43 def __str__(self):
45 return self._lsmagic()
44 return self._lsmagic()
46
45
47 def _jsonable(self):
46 def _jsonable(self):
48 """turn magics dict into jsonable dict of the same structure
47 """turn magics dict into jsonable dict of the same structure
49
48
50 replaces object instances with their class names as strings
49 replaces object instances with their class names as strings
51 """
50 """
52 magic_dict = {}
51 magic_dict = {}
53 mman = self.magics_manager
52 mman = self.magics_manager
54 magics = mman.lsmagic()
53 magics = mman.lsmagic()
55 for key, subdict in magics.items():
54 for key, subdict in magics.items():
56 d = {}
55 d = {}
57 magic_dict[key] = d
56 magic_dict[key] = d
58 for name, obj in subdict.items():
57 for name, obj in subdict.items():
59 try:
58 try:
60 classname = obj.__self__.__class__.__name__
59 classname = obj.__self__.__class__.__name__
61 except AttributeError:
60 except AttributeError:
62 classname = 'Other'
61 classname = 'Other'
63
62
64 d[name] = classname
63 d[name] = classname
65 return magic_dict
64 return magic_dict
66
65
67 def _repr_json_(self):
66 def _repr_json_(self):
68 return self._jsonable()
67 return self._jsonable()
69
68
70
69
71 @magics_class
70 @magics_class
72 class BasicMagics(Magics):
71 class BasicMagics(Magics):
73 """Magics that provide central IPython functionality.
72 """Magics that provide central IPython functionality.
74
73
75 These are various magics that don't fit into specific categories but that
74 These are various magics that don't fit into specific categories but that
76 are all part of the base 'IPython experience'."""
75 are all part of the base 'IPython experience'."""
77
76
78 @skip_doctest
77 @skip_doctest
79 @magic_arguments.magic_arguments()
78 @magic_arguments.magic_arguments()
80 @magic_arguments.argument(
79 @magic_arguments.argument(
81 '-l', '--line', action='store_true',
80 '-l', '--line', action='store_true',
82 help="""Create a line magic alias."""
81 help="""Create a line magic alias."""
83 )
82 )
84 @magic_arguments.argument(
83 @magic_arguments.argument(
85 '-c', '--cell', action='store_true',
84 '-c', '--cell', action='store_true',
86 help="""Create a cell magic alias."""
85 help="""Create a cell magic alias."""
87 )
86 )
88 @magic_arguments.argument(
87 @magic_arguments.argument(
89 'name',
88 'name',
90 help="""Name of the magic to be created."""
89 help="""Name of the magic to be created."""
91 )
90 )
92 @magic_arguments.argument(
91 @magic_arguments.argument(
93 'target',
92 'target',
94 help="""Name of the existing line or cell magic."""
93 help="""Name of the existing line or cell magic."""
95 )
94 )
96 @magic_arguments.argument(
95 @magic_arguments.argument(
97 '-p', '--params', default=None,
96 '-p', '--params', default=None,
98 help="""Parameters passed to the magic function."""
97 help="""Parameters passed to the magic function."""
99 )
98 )
100 @line_magic
99 @line_magic
101 def alias_magic(self, line=''):
100 def alias_magic(self, line=''):
102 """Create an alias for an existing line or cell magic.
101 """Create an alias for an existing line or cell magic.
103
102
104 Examples
103 Examples
105 --------
104 --------
106 ::
105 ::
107
106
108 In [1]: %alias_magic t timeit
107 In [1]: %alias_magic t timeit
109 Created `%t` as an alias for `%timeit`.
108 Created `%t` as an alias for `%timeit`.
110 Created `%%t` as an alias for `%%timeit`.
109 Created `%%t` as an alias for `%%timeit`.
111
110
112 In [2]: %t -n1 pass
111 In [2]: %t -n1 pass
113 1 loops, best of 3: 954 ns per loop
112 1 loops, best of 3: 954 ns per loop
114
113
115 In [3]: %%t -n1
114 In [3]: %%t -n1
116 ...: pass
115 ...: pass
117 ...:
116 ...:
118 1 loops, best of 3: 954 ns per loop
117 1 loops, best of 3: 954 ns per loop
119
118
120 In [4]: %alias_magic --cell whereami pwd
119 In [4]: %alias_magic --cell whereami pwd
121 UsageError: Cell magic function `%%pwd` not found.
120 UsageError: Cell magic function `%%pwd` not found.
122 In [5]: %alias_magic --line whereami pwd
121 In [5]: %alias_magic --line whereami pwd
123 Created `%whereami` as an alias for `%pwd`.
122 Created `%whereami` as an alias for `%pwd`.
124
123
125 In [6]: %whereami
124 In [6]: %whereami
126 Out[6]: u'/home/testuser'
125 Out[6]: u'/home/testuser'
127
126
128 In [7]: %alias_magic h history "-p -l 30" --line
127 In [7]: %alias_magic h history "-p -l 30" --line
129 Created `%h` as an alias for `%history -l 30`.
128 Created `%h` as an alias for `%history -l 30`.
130 """
129 """
131
130
132 args = magic_arguments.parse_argstring(self.alias_magic, line)
131 args = magic_arguments.parse_argstring(self.alias_magic, line)
133 shell = self.shell
132 shell = self.shell
134 mman = self.shell.magics_manager
133 mman = self.shell.magics_manager
135 escs = ''.join(magic_escapes.values())
134 escs = ''.join(magic_escapes.values())
136
135
137 target = args.target.lstrip(escs)
136 target = args.target.lstrip(escs)
138 name = args.name.lstrip(escs)
137 name = args.name.lstrip(escs)
139
138
140 params = args.params
139 params = args.params
141 if (params and
140 if (params and
142 ((params.startswith('"') and params.endswith('"'))
141 ((params.startswith('"') and params.endswith('"'))
143 or (params.startswith("'") and params.endswith("'")))):
142 or (params.startswith("'") and params.endswith("'")))):
144 params = params[1:-1]
143 params = params[1:-1]
145
144
146 # Find the requested magics.
145 # Find the requested magics.
147 m_line = shell.find_magic(target, 'line')
146 m_line = shell.find_magic(target, 'line')
148 m_cell = shell.find_magic(target, 'cell')
147 m_cell = shell.find_magic(target, 'cell')
149 if args.line and m_line is None:
148 if args.line and m_line is None:
150 raise UsageError('Line magic function `%s%s` not found.' %
149 raise UsageError('Line magic function `%s%s` not found.' %
151 (magic_escapes['line'], target))
150 (magic_escapes['line'], target))
152 if args.cell and m_cell is None:
151 if args.cell and m_cell is None:
153 raise UsageError('Cell magic function `%s%s` not found.' %
152 raise UsageError('Cell magic function `%s%s` not found.' %
154 (magic_escapes['cell'], target))
153 (magic_escapes['cell'], target))
155
154
156 # If --line and --cell are not specified, default to the ones
155 # If --line and --cell are not specified, default to the ones
157 # that are available.
156 # that are available.
158 if not args.line and not args.cell:
157 if not args.line and not args.cell:
159 if not m_line and not m_cell:
158 if not m_line and not m_cell:
160 raise UsageError(
159 raise UsageError(
161 'No line or cell magic with name `%s` found.' % target
160 'No line or cell magic with name `%s` found.' % target
162 )
161 )
163 args.line = bool(m_line)
162 args.line = bool(m_line)
164 args.cell = bool(m_cell)
163 args.cell = bool(m_cell)
165
164
166 params_str = "" if params is None else " " + params
165 params_str = "" if params is None else " " + params
167
166
168 if args.line:
167 if args.line:
169 mman.register_alias(name, target, 'line', params)
168 mman.register_alias(name, target, 'line', params)
170 print('Created `%s%s` as an alias for `%s%s%s`.' % (
169 print('Created `%s%s` as an alias for `%s%s%s`.' % (
171 magic_escapes['line'], name,
170 magic_escapes['line'], name,
172 magic_escapes['line'], target, params_str))
171 magic_escapes['line'], target, params_str))
173
172
174 if args.cell:
173 if args.cell:
175 mman.register_alias(name, target, 'cell', params)
174 mman.register_alias(name, target, 'cell', params)
176 print('Created `%s%s` as an alias for `%s%s%s`.' % (
175 print('Created `%s%s` as an alias for `%s%s%s`.' % (
177 magic_escapes['cell'], name,
176 magic_escapes['cell'], name,
178 magic_escapes['cell'], target, params_str))
177 magic_escapes['cell'], target, params_str))
179
178
180 @line_magic
179 @line_magic
181 def lsmagic(self, parameter_s=''):
180 def lsmagic(self, parameter_s=''):
182 """List currently available magic functions."""
181 """List currently available magic functions."""
183 return MagicsDisplay(self.shell.magics_manager, ignore=[])
182 return MagicsDisplay(self.shell.magics_manager, ignore=[])
184
183
185 def _magic_docs(self, brief=False, rest=False):
184 def _magic_docs(self, brief=False, rest=False):
186 """Return docstrings from magic functions."""
185 """Return docstrings from magic functions."""
187 mman = self.shell.magics_manager
186 mman = self.shell.magics_manager
188 docs = mman.lsmagic_docs(brief, missing='No documentation')
187 docs = mman.lsmagic_docs(brief, missing='No documentation')
189
188
190 if rest:
189 if rest:
191 format_string = '**%s%s**::\n\n%s\n\n'
190 format_string = '**%s%s**::\n\n%s\n\n'
192 else:
191 else:
193 format_string = '%s%s:\n%s\n'
192 format_string = '%s%s:\n%s\n'
194
193
195 return ''.join(
194 return ''.join(
196 [format_string % (magic_escapes['line'], fname,
195 [format_string % (magic_escapes['line'], fname,
197 indent(dedent(fndoc)))
196 indent(dedent(fndoc)))
198 for fname, fndoc in sorted(docs['line'].items())]
197 for fname, fndoc in sorted(docs['line'].items())]
199 +
198 +
200 [format_string % (magic_escapes['cell'], fname,
199 [format_string % (magic_escapes['cell'], fname,
201 indent(dedent(fndoc)))
200 indent(dedent(fndoc)))
202 for fname, fndoc in sorted(docs['cell'].items())]
201 for fname, fndoc in sorted(docs['cell'].items())]
203 )
202 )
204
203
205 @line_magic
204 @line_magic
206 def magic(self, parameter_s=''):
205 def magic(self, parameter_s=''):
207 """Print information about the magic function system.
206 """Print information about the magic function system.
208
207
209 Supported formats: -latex, -brief, -rest
208 Supported formats: -latex, -brief, -rest
210 """
209 """
211
210
212 mode = ''
211 mode = ''
213 try:
212 try:
214 mode = parameter_s.split()[0][1:]
213 mode = parameter_s.split()[0][1:]
215 except IndexError:
214 except IndexError:
216 pass
215 pass
217
216
218 brief = (mode == 'brief')
217 brief = (mode == 'brief')
219 rest = (mode == 'rest')
218 rest = (mode == 'rest')
220 magic_docs = self._magic_docs(brief, rest)
219 magic_docs = self._magic_docs(brief, rest)
221
220
222 if mode == 'latex':
221 if mode == 'latex':
223 print(self.format_latex(magic_docs))
222 print(self.format_latex(magic_docs))
224 return
223 return
225 else:
224 else:
226 magic_docs = format_screen(magic_docs)
225 magic_docs = format_screen(magic_docs)
227
226
228 out = ["""
227 out = ["""
229 IPython's 'magic' functions
228 IPython's 'magic' functions
230 ===========================
229 ===========================
231
230
232 The magic function system provides a series of functions which allow you to
231 The magic function system provides a series of functions which allow you to
233 control the behavior of IPython itself, plus a lot of system-type
232 control the behavior of IPython itself, plus a lot of system-type
234 features. There are two kinds of magics, line-oriented and cell-oriented.
233 features. There are two kinds of magics, line-oriented and cell-oriented.
235
234
236 Line magics are prefixed with the % character and work much like OS
235 Line magics are prefixed with the % character and work much like OS
237 command-line calls: they get as an argument the rest of the line, where
236 command-line calls: they get as an argument the rest of the line, where
238 arguments are passed without parentheses or quotes. For example, this will
237 arguments are passed without parentheses or quotes. For example, this will
239 time the given statement::
238 time the given statement::
240
239
241 %timeit range(1000)
240 %timeit range(1000)
242
241
243 Cell magics are prefixed with a double %%, and they are functions that get as
242 Cell magics are prefixed with a double %%, and they are functions that get as
244 an argument not only the rest of the line, but also the lines below it in a
243 an argument not only the rest of the line, but also the lines below it in a
245 separate argument. These magics are called with two arguments: the rest of the
244 separate argument. These magics are called with two arguments: the rest of the
246 call line and the body of the cell, consisting of the lines below the first.
245 call line and the body of the cell, consisting of the lines below the first.
247 For example::
246 For example::
248
247
249 %%timeit x = numpy.random.randn((100, 100))
248 %%timeit x = numpy.random.randn((100, 100))
250 numpy.linalg.svd(x)
249 numpy.linalg.svd(x)
251
250
252 will time the execution of the numpy svd routine, running the assignment of x
251 will time the execution of the numpy svd routine, running the assignment of x
253 as part of the setup phase, which is not timed.
252 as part of the setup phase, which is not timed.
254
253
255 In a line-oriented client (the terminal or Qt console IPython), starting a new
254 In a line-oriented client (the terminal or Qt console IPython), starting a new
256 input with %% will automatically enter cell mode, and IPython will continue
255 input with %% will automatically enter cell mode, and IPython will continue
257 reading input until a blank line is given. In the notebook, simply type the
256 reading input until a blank line is given. In the notebook, simply type the
258 whole cell as one entity, but keep in mind that the %% escape can only be at
257 whole cell as one entity, but keep in mind that the %% escape can only be at
259 the very start of the cell.
258 the very start of the cell.
260
259
261 NOTE: If you have 'automagic' enabled (via the command line option or with the
260 NOTE: If you have 'automagic' enabled (via the command line option or with the
262 %automagic function), you don't need to type in the % explicitly for line
261 %automagic function), you don't need to type in the % explicitly for line
263 magics; cell magics always require an explicit '%%' escape. By default,
262 magics; cell magics always require an explicit '%%' escape. By default,
264 IPython ships with automagic on, so you should only rarely need the % escape.
263 IPython ships with automagic on, so you should only rarely need the % escape.
265
264
266 Example: typing '%cd mydir' (without the quotes) changes your working directory
265 Example: typing '%cd mydir' (without the quotes) changes your working directory
267 to 'mydir', if it exists.
266 to 'mydir', if it exists.
268
267
269 For a list of the available magic functions, use %lsmagic. For a description
268 For a list of the available magic functions, use %lsmagic. For a description
270 of any of them, type %magic_name?, e.g. '%cd?'.
269 of any of them, type %magic_name?, e.g. '%cd?'.
271
270
272 Currently the magic system has the following functions:""",
271 Currently the magic system has the following functions:""",
273 magic_docs,
272 magic_docs,
274 "Summary of magic functions (from %slsmagic):" % magic_escapes['line'],
273 "Summary of magic functions (from %slsmagic):" % magic_escapes['line'],
275 str(self.lsmagic()),
274 str(self.lsmagic()),
276 ]
275 ]
277 page.page('\n'.join(out))
276 page.page('\n'.join(out))
278
277
279
278
280 @line_magic
279 @line_magic
281 def page(self, parameter_s=''):
280 def page(self, parameter_s=''):
282 """Pretty print the object and display it through a pager.
281 """Pretty print the object and display it through a pager.
283
282
284 %page [options] OBJECT
283 %page [options] OBJECT
285
284
286 If no object is given, use _ (last output).
285 If no object is given, use _ (last output).
287
286
288 Options:
287 Options:
289
288
290 -r: page str(object), don't pretty-print it."""
289 -r: page str(object), don't pretty-print it."""
291
290
292 # After a function contributed by Olivier Aubert, slightly modified.
291 # After a function contributed by Olivier Aubert, slightly modified.
293
292
294 # Process options/args
293 # Process options/args
295 opts, args = self.parse_options(parameter_s, 'r')
294 opts, args = self.parse_options(parameter_s, 'r')
296 raw = 'r' in opts
295 raw = 'r' in opts
297
296
298 oname = args and args or '_'
297 oname = args and args or '_'
299 info = self.shell._ofind(oname)
298 info = self.shell._ofind(oname)
300 if info['found']:
299 if info['found']:
301 txt = (raw and str or pformat)( info['obj'] )
300 txt = (raw and str or pformat)( info['obj'] )
302 page.page(txt)
301 page.page(txt)
303 else:
302 else:
304 print('Object `%s` not found' % oname)
303 print('Object `%s` not found' % oname)
305
304
306 @line_magic
305 @line_magic
307 def pprint(self, parameter_s=''):
306 def pprint(self, parameter_s=''):
308 """Toggle pretty printing on/off."""
307 """Toggle pretty printing on/off."""
309 ptformatter = self.shell.display_formatter.formatters['text/plain']
308 ptformatter = self.shell.display_formatter.formatters['text/plain']
310 ptformatter.pprint = bool(1 - ptformatter.pprint)
309 ptformatter.pprint = bool(1 - ptformatter.pprint)
311 print('Pretty printing has been turned',
310 print('Pretty printing has been turned',
312 ['OFF','ON'][ptformatter.pprint])
311 ['OFF','ON'][ptformatter.pprint])
313
312
314 @line_magic
313 @line_magic
315 def colors(self, parameter_s=''):
314 def colors(self, parameter_s=''):
316 """Switch color scheme for prompts, info system and exception handlers.
315 """Switch color scheme for prompts, info system and exception handlers.
317
316
318 Currently implemented schemes: NoColor, Linux, LightBG.
317 Currently implemented schemes: NoColor, Linux, LightBG.
319
318
320 Color scheme names are not case-sensitive.
319 Color scheme names are not case-sensitive.
321
320
322 Examples
321 Examples
323 --------
322 --------
324 To get a plain black and white terminal::
323 To get a plain black and white terminal::
325
324
326 %colors nocolor
325 %colors nocolor
327 """
326 """
328 def color_switch_err(name):
327 def color_switch_err(name):
329 warn('Error changing %s color schemes.\n%s' %
328 warn('Error changing %s color schemes.\n%s' %
330 (name, sys.exc_info()[1]), stacklevel=2)
329 (name, sys.exc_info()[1]), stacklevel=2)
331
330
332
331
333 new_scheme = parameter_s.strip()
332 new_scheme = parameter_s.strip()
334 if not new_scheme:
333 if not new_scheme:
335 raise UsageError(
334 raise UsageError(
336 "%colors: you must specify a color scheme. See '%colors?'")
335 "%colors: you must specify a color scheme. See '%colors?'")
337 # local shortcut
336 # local shortcut
338 shell = self.shell
337 shell = self.shell
339
338
340 # Set shell colour scheme
339 # Set shell colour scheme
341 try:
340 try:
342 shell.colors = new_scheme
341 shell.colors = new_scheme
343 shell.refresh_style()
342 shell.refresh_style()
344 except:
343 except:
345 color_switch_err('shell')
344 color_switch_err('shell')
346
345
347 # Set exception colors
346 # Set exception colors
348 try:
347 try:
349 shell.InteractiveTB.set_colors(scheme = new_scheme)
348 shell.InteractiveTB.set_colors(scheme = new_scheme)
350 shell.SyntaxTB.set_colors(scheme = new_scheme)
349 shell.SyntaxTB.set_colors(scheme = new_scheme)
351 except:
350 except:
352 color_switch_err('exception')
351 color_switch_err('exception')
353
352
354 # Set info (for 'object?') colors
353 # Set info (for 'object?') colors
355 if shell.color_info:
354 if shell.color_info:
356 try:
355 try:
357 shell.inspector.set_active_scheme(new_scheme)
356 shell.inspector.set_active_scheme(new_scheme)
358 except:
357 except:
359 color_switch_err('object inspector')
358 color_switch_err('object inspector')
360 else:
359 else:
361 shell.inspector.set_active_scheme('NoColor')
360 shell.inspector.set_active_scheme('NoColor')
362
361
363 @line_magic
362 @line_magic
364 def xmode(self, parameter_s=''):
363 def xmode(self, parameter_s=''):
365 """Switch modes for the exception handlers.
364 """Switch modes for the exception handlers.
366
365
367 Valid modes: Plain, Context, Verbose, and Minimal.
366 Valid modes: Plain, Context, Verbose, and Minimal.
368
367
369 If called without arguments, acts as a toggle.
368 If called without arguments, acts as a toggle.
370
369
371 When in verbose mode the value `--show` (and `--hide`)
370 When in verbose mode the value `--show` (and `--hide`)
372 will respectively show (or hide) frames with ``__tracebackhide__ =
371 will respectively show (or hide) frames with ``__tracebackhide__ =
373 True`` value set.
372 True`` value set.
374 """
373 """
375
374
376 def xmode_switch_err(name):
375 def xmode_switch_err(name):
377 warn('Error changing %s exception modes.\n%s' %
376 warn('Error changing %s exception modes.\n%s' %
378 (name,sys.exc_info()[1]))
377 (name,sys.exc_info()[1]))
379
378
380 shell = self.shell
379 shell = self.shell
381 if parameter_s.strip() == "--show":
380 if parameter_s.strip() == "--show":
382 shell.InteractiveTB.skip_hidden = False
381 shell.InteractiveTB.skip_hidden = False
383 return
382 return
384 if parameter_s.strip() == "--hide":
383 if parameter_s.strip() == "--hide":
385 shell.InteractiveTB.skip_hidden = True
384 shell.InteractiveTB.skip_hidden = True
386 return
385 return
387
386
388 new_mode = parameter_s.strip().capitalize()
387 new_mode = parameter_s.strip().capitalize()
389 try:
388 try:
390 shell.InteractiveTB.set_mode(mode=new_mode)
389 shell.InteractiveTB.set_mode(mode=new_mode)
391 print('Exception reporting mode:',shell.InteractiveTB.mode)
390 print('Exception reporting mode:',shell.InteractiveTB.mode)
392 except:
391 except:
393 xmode_switch_err('user')
392 xmode_switch_err('user')
394
393
395 @line_magic
394 @line_magic
396 def quickref(self, arg):
395 def quickref(self, arg):
397 """ Show a quick reference sheet """
396 """ Show a quick reference sheet """
398 from IPython.core.usage import quick_reference
397 from IPython.core.usage import quick_reference
399 qr = quick_reference + self._magic_docs(brief=True)
398 qr = quick_reference + self._magic_docs(brief=True)
400 page.page(qr)
399 page.page(qr)
401
400
402 @line_magic
401 @line_magic
403 def doctest_mode(self, parameter_s=''):
402 def doctest_mode(self, parameter_s=''):
404 """Toggle doctest mode on and off.
403 """Toggle doctest mode on and off.
405
404
406 This mode is intended to make IPython behave as much as possible like a
405 This mode is intended to make IPython behave as much as possible like a
407 plain Python shell, from the perspective of how its prompts, exceptions
406 plain Python shell, from the perspective of how its prompts, exceptions
408 and output look. This makes it easy to copy and paste parts of a
407 and output look. This makes it easy to copy and paste parts of a
409 session into doctests. It does so by:
408 session into doctests. It does so by:
410
409
411 - Changing the prompts to the classic ``>>>`` ones.
410 - Changing the prompts to the classic ``>>>`` ones.
412 - Changing the exception reporting mode to 'Plain'.
411 - Changing the exception reporting mode to 'Plain'.
413 - Disabling pretty-printing of output.
412 - Disabling pretty-printing of output.
414
413
415 Note that IPython also supports the pasting of code snippets that have
414 Note that IPython also supports the pasting of code snippets that have
416 leading '>>>' and '...' prompts in them. This means that you can paste
415 leading '>>>' and '...' prompts in them. This means that you can paste
417 doctests from files or docstrings (even if they have leading
416 doctests from files or docstrings (even if they have leading
418 whitespace), and the code will execute correctly. You can then use
417 whitespace), and the code will execute correctly. You can then use
419 '%history -t' to see the translated history; this will give you the
418 '%history -t' to see the translated history; this will give you the
420 input after removal of all the leading prompts and whitespace, which
419 input after removal of all the leading prompts and whitespace, which
421 can be pasted back into an editor.
420 can be pasted back into an editor.
422
421
423 With these features, you can switch into this mode easily whenever you
422 With these features, you can switch into this mode easily whenever you
424 need to do testing and changes to doctests, without having to leave
423 need to do testing and changes to doctests, without having to leave
425 your existing IPython session.
424 your existing IPython session.
426 """
425 """
427
426
428 # Shorthands
427 # Shorthands
429 shell = self.shell
428 shell = self.shell
430 meta = shell.meta
429 meta = shell.meta
431 disp_formatter = self.shell.display_formatter
430 disp_formatter = self.shell.display_formatter
432 ptformatter = disp_formatter.formatters['text/plain']
431 ptformatter = disp_formatter.formatters['text/plain']
433 # dstore is a data store kept in the instance metadata bag to track any
432 # dstore is a data store kept in the instance metadata bag to track any
434 # changes we make, so we can undo them later.
433 # changes we make, so we can undo them later.
435 dstore = meta.setdefault('doctest_mode',Struct())
434 dstore = meta.setdefault('doctest_mode',Struct())
436 save_dstore = dstore.setdefault
435 save_dstore = dstore.setdefault
437
436
438 # save a few values we'll need to recover later
437 # save a few values we'll need to recover later
439 mode = save_dstore('mode',False)
438 mode = save_dstore('mode',False)
440 save_dstore('rc_pprint',ptformatter.pprint)
439 save_dstore('rc_pprint',ptformatter.pprint)
441 save_dstore('xmode',shell.InteractiveTB.mode)
440 save_dstore('xmode',shell.InteractiveTB.mode)
442 save_dstore('rc_separate_out',shell.separate_out)
441 save_dstore('rc_separate_out',shell.separate_out)
443 save_dstore('rc_separate_out2',shell.separate_out2)
442 save_dstore('rc_separate_out2',shell.separate_out2)
444 save_dstore('rc_separate_in',shell.separate_in)
443 save_dstore('rc_separate_in',shell.separate_in)
445 save_dstore('rc_active_types',disp_formatter.active_types)
444 save_dstore('rc_active_types',disp_formatter.active_types)
446
445
447 if not mode:
446 if not mode:
448 # turn on
447 # turn on
449
448
450 # Prompt separators like plain python
449 # Prompt separators like plain python
451 shell.separate_in = ''
450 shell.separate_in = ''
452 shell.separate_out = ''
451 shell.separate_out = ''
453 shell.separate_out2 = ''
452 shell.separate_out2 = ''
454
453
455
454
456 ptformatter.pprint = False
455 ptformatter.pprint = False
457 disp_formatter.active_types = ['text/plain']
456 disp_formatter.active_types = ['text/plain']
458
457
459 shell.magic('xmode Plain')
458 shell.magic('xmode Plain')
460 else:
459 else:
461 # turn off
460 # turn off
462 shell.separate_in = dstore.rc_separate_in
461 shell.separate_in = dstore.rc_separate_in
463
462
464 shell.separate_out = dstore.rc_separate_out
463 shell.separate_out = dstore.rc_separate_out
465 shell.separate_out2 = dstore.rc_separate_out2
464 shell.separate_out2 = dstore.rc_separate_out2
466
465
467 ptformatter.pprint = dstore.rc_pprint
466 ptformatter.pprint = dstore.rc_pprint
468 disp_formatter.active_types = dstore.rc_active_types
467 disp_formatter.active_types = dstore.rc_active_types
469
468
470 shell.magic('xmode ' + dstore.xmode)
469 shell.magic('xmode ' + dstore.xmode)
471
470
472 # mode here is the state before we switch; switch_doctest_mode takes
471 # mode here is the state before we switch; switch_doctest_mode takes
473 # the mode we're switching to.
472 # the mode we're switching to.
474 shell.switch_doctest_mode(not mode)
473 shell.switch_doctest_mode(not mode)
475
474
476 # Store new mode and inform
475 # Store new mode and inform
477 dstore.mode = bool(not mode)
476 dstore.mode = bool(not mode)
478 mode_label = ['OFF','ON'][dstore.mode]
477 mode_label = ['OFF','ON'][dstore.mode]
479 print('Doctest mode is:', mode_label)
478 print('Doctest mode is:', mode_label)
480
479
481 @line_magic
480 @line_magic
482 def gui(self, parameter_s=''):
481 def gui(self, parameter_s=''):
483 """Enable or disable IPython GUI event loop integration.
482 """Enable or disable IPython GUI event loop integration.
484
483
485 %gui [GUINAME]
484 %gui [GUINAME]
486
485
487 This magic replaces IPython's threaded shells that were activated
486 This magic replaces IPython's threaded shells that were activated
488 using the (pylab/wthread/etc.) command line flags. GUI toolkits
487 using the (pylab/wthread/etc.) command line flags. GUI toolkits
489 can now be enabled at runtime and keyboard
488 can now be enabled at runtime and keyboard
490 interrupts should work without any problems. The following toolkits
489 interrupts should work without any problems. The following toolkits
491 are supported: wxPython, PyQt4, PyGTK, Tk and Cocoa (OSX)::
490 are supported: wxPython, PyQt4, PyGTK, Tk and Cocoa (OSX)::
492
491
493 %gui wx # enable wxPython event loop integration
492 %gui wx # enable wxPython event loop integration
494 %gui qt4|qt # enable PyQt4 event loop integration
493 %gui qt4|qt # enable PyQt4 event loop integration
495 %gui qt5 # enable PyQt5 event loop integration
494 %gui qt5 # enable PyQt5 event loop integration
496 %gui gtk # enable PyGTK event loop integration
495 %gui gtk # enable PyGTK event loop integration
497 %gui gtk3 # enable Gtk3 event loop integration
496 %gui gtk3 # enable Gtk3 event loop integration
498 %gui gtk4 # enable Gtk4 event loop integration
497 %gui gtk4 # enable Gtk4 event loop integration
499 %gui tk # enable Tk event loop integration
498 %gui tk # enable Tk event loop integration
500 %gui osx # enable Cocoa event loop integration
499 %gui osx # enable Cocoa event loop integration
501 # (requires %matplotlib 1.1)
500 # (requires %matplotlib 1.1)
502 %gui # disable all event loop integration
501 %gui # disable all event loop integration
503
502
504 WARNING: after any of these has been called you can simply create
503 WARNING: after any of these has been called you can simply create
505 an application object, but DO NOT start the event loop yourself, as
504 an application object, but DO NOT start the event loop yourself, as
506 we have already handled that.
505 we have already handled that.
507 """
506 """
508 opts, arg = self.parse_options(parameter_s, '')
507 opts, arg = self.parse_options(parameter_s, '')
509 if arg=='': arg = None
508 if arg=='': arg = None
510 try:
509 try:
511 return self.shell.enable_gui(arg)
510 return self.shell.enable_gui(arg)
512 except Exception as e:
511 except Exception as e:
513 # print simple error message, rather than traceback if we can't
512 # print simple error message, rather than traceback if we can't
514 # hook up the GUI
513 # hook up the GUI
515 error(str(e))
514 error(str(e))
516
515
517 @skip_doctest
516 @skip_doctest
518 @line_magic
517 @line_magic
519 def precision(self, s=''):
518 def precision(self, s=''):
520 """Set floating point precision for pretty printing.
519 """Set floating point precision for pretty printing.
521
520
522 Can set either integer precision or a format string.
521 Can set either integer precision or a format string.
523
522
524 If numpy has been imported and precision is an int,
523 If numpy has been imported and precision is an int,
525 numpy display precision will also be set, via ``numpy.set_printoptions``.
524 numpy display precision will also be set, via ``numpy.set_printoptions``.
526
525
527 If no argument is given, defaults will be restored.
526 If no argument is given, defaults will be restored.
528
527
529 Examples
528 Examples
530 --------
529 --------
531 ::
530 ::
532
531
533 In [1]: from math import pi
532 In [1]: from math import pi
534
533
535 In [2]: %precision 3
534 In [2]: %precision 3
536 Out[2]: u'%.3f'
535 Out[2]: u'%.3f'
537
536
538 In [3]: pi
537 In [3]: pi
539 Out[3]: 3.142
538 Out[3]: 3.142
540
539
541 In [4]: %precision %i
540 In [4]: %precision %i
542 Out[4]: u'%i'
541 Out[4]: u'%i'
543
542
544 In [5]: pi
543 In [5]: pi
545 Out[5]: 3
544 Out[5]: 3
546
545
547 In [6]: %precision %e
546 In [6]: %precision %e
548 Out[6]: u'%e'
547 Out[6]: u'%e'
549
548
550 In [7]: pi**10
549 In [7]: pi**10
551 Out[7]: 9.364805e+04
550 Out[7]: 9.364805e+04
552
551
553 In [8]: %precision
552 In [8]: %precision
554 Out[8]: u'%r'
553 Out[8]: u'%r'
555
554
556 In [9]: pi**10
555 In [9]: pi**10
557 Out[9]: 93648.047476082982
556 Out[9]: 93648.047476082982
558 """
557 """
559 ptformatter = self.shell.display_formatter.formatters['text/plain']
558 ptformatter = self.shell.display_formatter.formatters['text/plain']
560 ptformatter.float_precision = s
559 ptformatter.float_precision = s
561 return ptformatter.float_format
560 return ptformatter.float_format
562
561
563 @magic_arguments.magic_arguments()
562 @magic_arguments.magic_arguments()
564 @magic_arguments.argument(
563 @magic_arguments.argument(
565 'filename', type=str,
564 'filename', type=str,
566 help='Notebook name or filename'
565 help='Notebook name or filename'
567 )
566 )
568 @line_magic
567 @line_magic
569 def notebook(self, s):
568 def notebook(self, s):
570 """Export and convert IPython notebooks.
569 """Export and convert IPython notebooks.
571
570
572 This function can export the current IPython history to a notebook file.
571 This function can export the current IPython history to a notebook file.
573 For example, to export the history to "foo.ipynb" do "%notebook foo.ipynb".
572 For example, to export the history to "foo.ipynb" do "%notebook foo.ipynb".
574 """
573 """
575 args = magic_arguments.parse_argstring(self.notebook, s)
574 args = magic_arguments.parse_argstring(self.notebook, s)
576 outfname = os.path.expanduser(args.filename)
575 outfname = os.path.expanduser(args.filename)
577
576
578 from nbformat import write, v4
577 from nbformat import write, v4
579
578
580 cells = []
579 cells = []
581 hist = list(self.shell.history_manager.get_range())
580 hist = list(self.shell.history_manager.get_range())
582 if(len(hist)<=1):
581 if(len(hist)<=1):
583 raise ValueError('History is empty, cannot export')
582 raise ValueError('History is empty, cannot export')
584 for session, execution_count, source in hist[:-1]:
583 for session, execution_count, source in hist[:-1]:
585 cells.append(v4.new_code_cell(
584 cells.append(v4.new_code_cell(
586 execution_count=execution_count,
585 execution_count=execution_count,
587 source=source
586 source=source
588 ))
587 ))
589 nb = v4.new_notebook(cells=cells)
588 nb = v4.new_notebook(cells=cells)
590 with io.open(outfname, "w", encoding="utf-8") as f:
589 with io.open(outfname, "w", encoding="utf-8") as f:
591 write(nb, f, version=4)
590 write(nb, f, version=4)
592
591
593 @magics_class
592 @magics_class
594 class AsyncMagics(BasicMagics):
593 class AsyncMagics(BasicMagics):
595
594
596 @line_magic
595 @line_magic
597 def autoawait(self, parameter_s):
596 def autoawait(self, parameter_s):
598 """
597 """
599 Allow to change the status of the autoawait option.
598 Allow to change the status of the autoawait option.
600
599
601 This allow you to set a specific asynchronous code runner.
600 This allow you to set a specific asynchronous code runner.
602
601
603 If no value is passed, print the currently used asynchronous integration
602 If no value is passed, print the currently used asynchronous integration
604 and whether it is activated.
603 and whether it is activated.
605
604
606 It can take a number of value evaluated in the following order:
605 It can take a number of value evaluated in the following order:
607
606
608 - False/false/off deactivate autoawait integration
607 - False/false/off deactivate autoawait integration
609 - True/true/on activate autoawait integration using configured default
608 - True/true/on activate autoawait integration using configured default
610 loop
609 loop
611 - asyncio/curio/trio activate autoawait integration and use integration
610 - asyncio/curio/trio activate autoawait integration and use integration
612 with said library.
611 with said library.
613
612
614 - `sync` turn on the pseudo-sync integration (mostly used for
613 - `sync` turn on the pseudo-sync integration (mostly used for
615 `IPython.embed()` which does not run IPython with a real eventloop and
614 `IPython.embed()` which does not run IPython with a real eventloop and
616 deactivate running asynchronous code. Turning on Asynchronous code with
615 deactivate running asynchronous code. Turning on Asynchronous code with
617 the pseudo sync loop is undefined behavior and may lead IPython to crash.
616 the pseudo sync loop is undefined behavior and may lead IPython to crash.
618
617
619 If the passed parameter does not match any of the above and is a python
618 If the passed parameter does not match any of the above and is a python
620 identifier, get said object from user namespace and set it as the
619 identifier, get said object from user namespace and set it as the
621 runner, and activate autoawait.
620 runner, and activate autoawait.
622
621
623 If the object is a fully qualified object name, attempt to import it and
622 If the object is a fully qualified object name, attempt to import it and
624 set it as the runner, and activate autoawait.
623 set it as the runner, and activate autoawait.
625
624
626 The exact behavior of autoawait is experimental and subject to change
625 The exact behavior of autoawait is experimental and subject to change
627 across version of IPython and Python.
626 across version of IPython and Python.
628 """
627 """
629
628
630 param = parameter_s.strip()
629 param = parameter_s.strip()
631 d = {True: "on", False: "off"}
630 d = {True: "on", False: "off"}
632
631
633 if not param:
632 if not param:
634 print("IPython autoawait is `{}`, and set to use `{}`".format(
633 print("IPython autoawait is `{}`, and set to use `{}`".format(
635 d[self.shell.autoawait],
634 d[self.shell.autoawait],
636 self.shell.loop_runner
635 self.shell.loop_runner
637 ))
636 ))
638 return None
637 return None
639
638
640 if param.lower() in ('false', 'off'):
639 if param.lower() in ('false', 'off'):
641 self.shell.autoawait = False
640 self.shell.autoawait = False
642 return None
641 return None
643 if param.lower() in ('true', 'on'):
642 if param.lower() in ('true', 'on'):
644 self.shell.autoawait = True
643 self.shell.autoawait = True
645 return None
644 return None
646
645
647 if param in self.shell.loop_runner_map:
646 if param in self.shell.loop_runner_map:
648 self.shell.loop_runner, self.shell.autoawait = self.shell.loop_runner_map[param]
647 self.shell.loop_runner, self.shell.autoawait = self.shell.loop_runner_map[param]
649 return None
648 return None
650
649
651 if param in self.shell.user_ns :
650 if param in self.shell.user_ns :
652 self.shell.loop_runner = self.shell.user_ns[param]
651 self.shell.loop_runner = self.shell.user_ns[param]
653 self.shell.autoawait = True
652 self.shell.autoawait = True
654 return None
653 return None
655
654
656 runner = import_item(param)
655 runner = import_item(param)
657
656
658 self.shell.loop_runner = runner
657 self.shell.loop_runner = runner
659 self.shell.autoawait = True
658 self.shell.autoawait = True
@@ -1,1055 +1,1054 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tools for inspecting Python objects.
2 """Tools for inspecting Python objects.
3
3
4 Uses syntax highlighting for presenting the various information elements.
4 Uses syntax highlighting for presenting the various information elements.
5
5
6 Similar in spirit to the inspect module, but all calls take a name argument to
6 Similar in spirit to the inspect module, but all calls take a name argument to
7 reference the name under which an object is being read.
7 reference the name under which an object is being read.
8 """
8 """
9
9
10 # Copyright (c) IPython Development Team.
10 # Copyright (c) IPython Development Team.
11 # Distributed under the terms of the Modified BSD License.
11 # Distributed under the terms of the Modified BSD License.
12
12
13 __all__ = ['Inspector','InspectColors']
13 __all__ = ['Inspector','InspectColors']
14
14
15 # stdlib modules
15 # stdlib modules
16 import ast
16 import ast
17 import inspect
17 import inspect
18 from inspect import signature
18 from inspect import signature
19 import linecache
19 import linecache
20 import warnings
20 import warnings
21 import os
21 import os
22 from textwrap import dedent
22 from textwrap import dedent
23 import types
23 import types
24 import io as stdlib_io
24 import io as stdlib_io
25
25
26 from typing import Union
26 from typing import Union
27
27
28 # IPython's own
28 # IPython's own
29 from IPython.core import page
29 from IPython.core import page
30 from IPython.lib.pretty import pretty
30 from IPython.lib.pretty import pretty
31 from IPython.testing.skipdoctest import skip_doctest
31 from IPython.testing.skipdoctest import skip_doctest
32 from IPython.utils import PyColorize
32 from IPython.utils import PyColorize
33 from IPython.utils import openpy
33 from IPython.utils import openpy
34 from IPython.utils import py3compat
35 from IPython.utils.dir2 import safe_hasattr
34 from IPython.utils.dir2 import safe_hasattr
36 from IPython.utils.path import compress_user
35 from IPython.utils.path import compress_user
37 from IPython.utils.text import indent
36 from IPython.utils.text import indent
38 from IPython.utils.wildcard import list_namespace
37 from IPython.utils.wildcard import list_namespace
39 from IPython.utils.wildcard import typestr2type
38 from IPython.utils.wildcard import typestr2type
40 from IPython.utils.coloransi import TermColors, ColorScheme, ColorSchemeTable
39 from IPython.utils.coloransi import TermColors, ColorScheme, ColorSchemeTable
41 from IPython.utils.py3compat import cast_unicode
40 from IPython.utils.py3compat import cast_unicode
42 from IPython.utils.colorable import Colorable
41 from IPython.utils.colorable import Colorable
43 from IPython.utils.decorators import undoc
42 from IPython.utils.decorators import undoc
44
43
45 from pygments import highlight
44 from pygments import highlight
46 from pygments.lexers import PythonLexer
45 from pygments.lexers import PythonLexer
47 from pygments.formatters import HtmlFormatter
46 from pygments.formatters import HtmlFormatter
48
47
49 def pylight(code):
48 def pylight(code):
50 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
49 return highlight(code, PythonLexer(), HtmlFormatter(noclasses=True))
51
50
52 # builtin docstrings to ignore
51 # builtin docstrings to ignore
53 _func_call_docstring = types.FunctionType.__call__.__doc__
52 _func_call_docstring = types.FunctionType.__call__.__doc__
54 _object_init_docstring = object.__init__.__doc__
53 _object_init_docstring = object.__init__.__doc__
55 _builtin_type_docstrings = {
54 _builtin_type_docstrings = {
56 inspect.getdoc(t) for t in (types.ModuleType, types.MethodType,
55 inspect.getdoc(t) for t in (types.ModuleType, types.MethodType,
57 types.FunctionType, property)
56 types.FunctionType, property)
58 }
57 }
59
58
60 _builtin_func_type = type(all)
59 _builtin_func_type = type(all)
61 _builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions
60 _builtin_meth_type = type(str.upper) # Bound methods have the same type as builtin functions
62 #****************************************************************************
61 #****************************************************************************
63 # Builtin color schemes
62 # Builtin color schemes
64
63
65 Colors = TermColors # just a shorthand
64 Colors = TermColors # just a shorthand
66
65
67 InspectColors = PyColorize.ANSICodeColors
66 InspectColors = PyColorize.ANSICodeColors
68
67
69 #****************************************************************************
68 #****************************************************************************
70 # Auxiliary functions and objects
69 # Auxiliary functions and objects
71
70
72 # See the messaging spec for the definition of all these fields. This list
71 # See the messaging spec for the definition of all these fields. This list
73 # effectively defines the order of display
72 # effectively defines the order of display
74 info_fields = ['type_name', 'base_class', 'string_form', 'namespace',
73 info_fields = ['type_name', 'base_class', 'string_form', 'namespace',
75 'length', 'file', 'definition', 'docstring', 'source',
74 'length', 'file', 'definition', 'docstring', 'source',
76 'init_definition', 'class_docstring', 'init_docstring',
75 'init_definition', 'class_docstring', 'init_docstring',
77 'call_def', 'call_docstring',
76 'call_def', 'call_docstring',
78 # These won't be printed but will be used to determine how to
77 # These won't be printed but will be used to determine how to
79 # format the object
78 # format the object
80 'ismagic', 'isalias', 'isclass', 'found', 'name'
79 'ismagic', 'isalias', 'isclass', 'found', 'name'
81 ]
80 ]
82
81
83
82
84 def object_info(**kw):
83 def object_info(**kw):
85 """Make an object info dict with all fields present."""
84 """Make an object info dict with all fields present."""
86 infodict = {k:None for k in info_fields}
85 infodict = {k:None for k in info_fields}
87 infodict.update(kw)
86 infodict.update(kw)
88 return infodict
87 return infodict
89
88
90
89
91 def get_encoding(obj):
90 def get_encoding(obj):
92 """Get encoding for python source file defining obj
91 """Get encoding for python source file defining obj
93
92
94 Returns None if obj is not defined in a sourcefile.
93 Returns None if obj is not defined in a sourcefile.
95 """
94 """
96 ofile = find_file(obj)
95 ofile = find_file(obj)
97 # run contents of file through pager starting at line where the object
96 # run contents of file through pager starting at line where the object
98 # is defined, as long as the file isn't binary and is actually on the
97 # is defined, as long as the file isn't binary and is actually on the
99 # filesystem.
98 # filesystem.
100 if ofile is None:
99 if ofile is None:
101 return None
100 return None
102 elif ofile.endswith(('.so', '.dll', '.pyd')):
101 elif ofile.endswith(('.so', '.dll', '.pyd')):
103 return None
102 return None
104 elif not os.path.isfile(ofile):
103 elif not os.path.isfile(ofile):
105 return None
104 return None
106 else:
105 else:
107 # Print only text files, not extension binaries. Note that
106 # Print only text files, not extension binaries. Note that
108 # getsourcelines returns lineno with 1-offset and page() uses
107 # getsourcelines returns lineno with 1-offset and page() uses
109 # 0-offset, so we must adjust.
108 # 0-offset, so we must adjust.
110 with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2
109 with stdlib_io.open(ofile, 'rb') as buffer: # Tweaked to use io.open for Python 2
111 encoding, lines = openpy.detect_encoding(buffer.readline)
110 encoding, lines = openpy.detect_encoding(buffer.readline)
112 return encoding
111 return encoding
113
112
114 def getdoc(obj) -> Union[str,None]:
113 def getdoc(obj) -> Union[str,None]:
115 """Stable wrapper around inspect.getdoc.
114 """Stable wrapper around inspect.getdoc.
116
115
117 This can't crash because of attribute problems.
116 This can't crash because of attribute problems.
118
117
119 It also attempts to call a getdoc() method on the given object. This
118 It also attempts to call a getdoc() method on the given object. This
120 allows objects which provide their docstrings via non-standard mechanisms
119 allows objects which provide their docstrings via non-standard mechanisms
121 (like Pyro proxies) to still be inspected by ipython's ? system.
120 (like Pyro proxies) to still be inspected by ipython's ? system.
122 """
121 """
123 # Allow objects to offer customized documentation via a getdoc method:
122 # Allow objects to offer customized documentation via a getdoc method:
124 try:
123 try:
125 ds = obj.getdoc()
124 ds = obj.getdoc()
126 except Exception:
125 except Exception:
127 pass
126 pass
128 else:
127 else:
129 if isinstance(ds, str):
128 if isinstance(ds, str):
130 return inspect.cleandoc(ds)
129 return inspect.cleandoc(ds)
131 docstr = inspect.getdoc(obj)
130 docstr = inspect.getdoc(obj)
132 return docstr
131 return docstr
133
132
134
133
135 def getsource(obj, oname='') -> Union[str,None]:
134 def getsource(obj, oname='') -> Union[str,None]:
136 """Wrapper around inspect.getsource.
135 """Wrapper around inspect.getsource.
137
136
138 This can be modified by other projects to provide customized source
137 This can be modified by other projects to provide customized source
139 extraction.
138 extraction.
140
139
141 Parameters
140 Parameters
142 ----------
141 ----------
143 obj : object
142 obj : object
144 an object whose source code we will attempt to extract
143 an object whose source code we will attempt to extract
145 oname : str
144 oname : str
146 (optional) a name under which the object is known
145 (optional) a name under which the object is known
147
146
148 Returns
147 Returns
149 -------
148 -------
150 src : unicode or None
149 src : unicode or None
151
150
152 """
151 """
153
152
154 if isinstance(obj, property):
153 if isinstance(obj, property):
155 sources = []
154 sources = []
156 for attrname in ['fget', 'fset', 'fdel']:
155 for attrname in ['fget', 'fset', 'fdel']:
157 fn = getattr(obj, attrname)
156 fn = getattr(obj, attrname)
158 if fn is not None:
157 if fn is not None:
159 encoding = get_encoding(fn)
158 encoding = get_encoding(fn)
160 oname_prefix = ('%s.' % oname) if oname else ''
159 oname_prefix = ('%s.' % oname) if oname else ''
161 sources.append(''.join(('# ', oname_prefix, attrname)))
160 sources.append(''.join(('# ', oname_prefix, attrname)))
162 if inspect.isfunction(fn):
161 if inspect.isfunction(fn):
163 sources.append(dedent(getsource(fn)))
162 sources.append(dedent(getsource(fn)))
164 else:
163 else:
165 # Default str/repr only prints function name,
164 # Default str/repr only prints function name,
166 # pretty.pretty prints module name too.
165 # pretty.pretty prints module name too.
167 sources.append(
166 sources.append(
168 '%s%s = %s\n' % (oname_prefix, attrname, pretty(fn))
167 '%s%s = %s\n' % (oname_prefix, attrname, pretty(fn))
169 )
168 )
170 if sources:
169 if sources:
171 return '\n'.join(sources)
170 return '\n'.join(sources)
172 else:
171 else:
173 return None
172 return None
174
173
175 else:
174 else:
176 # Get source for non-property objects.
175 # Get source for non-property objects.
177
176
178 obj = _get_wrapped(obj)
177 obj = _get_wrapped(obj)
179
178
180 try:
179 try:
181 src = inspect.getsource(obj)
180 src = inspect.getsource(obj)
182 except TypeError:
181 except TypeError:
183 # The object itself provided no meaningful source, try looking for
182 # The object itself provided no meaningful source, try looking for
184 # its class definition instead.
183 # its class definition instead.
185 try:
184 try:
186 src = inspect.getsource(obj.__class__)
185 src = inspect.getsource(obj.__class__)
187 except (OSError, TypeError):
186 except (OSError, TypeError):
188 return None
187 return None
189 except OSError:
188 except OSError:
190 return None
189 return None
191
190
192 return src
191 return src
193
192
194
193
195 def is_simple_callable(obj):
194 def is_simple_callable(obj):
196 """True if obj is a function ()"""
195 """True if obj is a function ()"""
197 return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
196 return (inspect.isfunction(obj) or inspect.ismethod(obj) or \
198 isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))
197 isinstance(obj, _builtin_func_type) or isinstance(obj, _builtin_meth_type))
199
198
200 @undoc
199 @undoc
201 def getargspec(obj):
200 def getargspec(obj):
202 """Wrapper around :func:`inspect.getfullargspec`
201 """Wrapper around :func:`inspect.getfullargspec`
203
202
204 In addition to functions and methods, this can also handle objects with a
203 In addition to functions and methods, this can also handle objects with a
205 ``__call__`` attribute.
204 ``__call__`` attribute.
206
205
207 DEPRECATED: Deprecated since 7.10. Do not use, will be removed.
206 DEPRECATED: Deprecated since 7.10. Do not use, will be removed.
208 """
207 """
209
208
210 warnings.warn('`getargspec` function is deprecated as of IPython 7.10'
209 warnings.warn('`getargspec` function is deprecated as of IPython 7.10'
211 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
210 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
212
211
213 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
212 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
214 obj = obj.__call__
213 obj = obj.__call__
215
214
216 return inspect.getfullargspec(obj)
215 return inspect.getfullargspec(obj)
217
216
218 @undoc
217 @undoc
219 def format_argspec(argspec):
218 def format_argspec(argspec):
220 """Format argspect, convenience wrapper around inspect's.
219 """Format argspect, convenience wrapper around inspect's.
221
220
222 This takes a dict instead of ordered arguments and calls
221 This takes a dict instead of ordered arguments and calls
223 inspect.format_argspec with the arguments in the necessary order.
222 inspect.format_argspec with the arguments in the necessary order.
224
223
225 DEPRECATED (since 7.10): Do not use; will be removed in future versions.
224 DEPRECATED (since 7.10): Do not use; will be removed in future versions.
226 """
225 """
227
226
228 warnings.warn('`format_argspec` function is deprecated as of IPython 7.10'
227 warnings.warn('`format_argspec` function is deprecated as of IPython 7.10'
229 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
228 'and will be removed in future versions.', DeprecationWarning, stacklevel=2)
230
229
231
230
232 return inspect.formatargspec(argspec['args'], argspec['varargs'],
231 return inspect.formatargspec(argspec['args'], argspec['varargs'],
233 argspec['varkw'], argspec['defaults'])
232 argspec['varkw'], argspec['defaults'])
234
233
235 @undoc
234 @undoc
236 def call_tip(oinfo, format_call=True):
235 def call_tip(oinfo, format_call=True):
237 """DEPRECATED since 6.0. Extract call tip data from an oinfo dict."""
236 """DEPRECATED since 6.0. Extract call tip data from an oinfo dict."""
238 warnings.warn(
237 warnings.warn(
239 "`call_tip` function is deprecated as of IPython 6.0"
238 "`call_tip` function is deprecated as of IPython 6.0"
240 "and will be removed in future versions.",
239 "and will be removed in future versions.",
241 DeprecationWarning,
240 DeprecationWarning,
242 stacklevel=2,
241 stacklevel=2,
243 )
242 )
244 # Get call definition
243 # Get call definition
245 argspec = oinfo.get('argspec')
244 argspec = oinfo.get('argspec')
246 if argspec is None:
245 if argspec is None:
247 call_line = None
246 call_line = None
248 else:
247 else:
249 # Callable objects will have 'self' as their first argument, prune
248 # Callable objects will have 'self' as their first argument, prune
250 # it out if it's there for clarity (since users do *not* pass an
249 # it out if it's there for clarity (since users do *not* pass an
251 # extra first argument explicitly).
250 # extra first argument explicitly).
252 try:
251 try:
253 has_self = argspec['args'][0] == 'self'
252 has_self = argspec['args'][0] == 'self'
254 except (KeyError, IndexError):
253 except (KeyError, IndexError):
255 pass
254 pass
256 else:
255 else:
257 if has_self:
256 if has_self:
258 argspec['args'] = argspec['args'][1:]
257 argspec['args'] = argspec['args'][1:]
259
258
260 call_line = oinfo['name']+format_argspec(argspec)
259 call_line = oinfo['name']+format_argspec(argspec)
261
260
262 # Now get docstring.
261 # Now get docstring.
263 # The priority is: call docstring, constructor docstring, main one.
262 # The priority is: call docstring, constructor docstring, main one.
264 doc = oinfo.get('call_docstring')
263 doc = oinfo.get('call_docstring')
265 if doc is None:
264 if doc is None:
266 doc = oinfo.get('init_docstring')
265 doc = oinfo.get('init_docstring')
267 if doc is None:
266 if doc is None:
268 doc = oinfo.get('docstring','')
267 doc = oinfo.get('docstring','')
269
268
270 return call_line, doc
269 return call_line, doc
271
270
272
271
273 def _get_wrapped(obj):
272 def _get_wrapped(obj):
274 """Get the original object if wrapped in one or more @decorators
273 """Get the original object if wrapped in one or more @decorators
275
274
276 Some objects automatically construct similar objects on any unrecognised
275 Some objects automatically construct similar objects on any unrecognised
277 attribute access (e.g. unittest.mock.call). To protect against infinite loops,
276 attribute access (e.g. unittest.mock.call). To protect against infinite loops,
278 this will arbitrarily cut off after 100 levels of obj.__wrapped__
277 this will arbitrarily cut off after 100 levels of obj.__wrapped__
279 attribute access. --TK, Jan 2016
278 attribute access. --TK, Jan 2016
280 """
279 """
281 orig_obj = obj
280 orig_obj = obj
282 i = 0
281 i = 0
283 while safe_hasattr(obj, '__wrapped__'):
282 while safe_hasattr(obj, '__wrapped__'):
284 obj = obj.__wrapped__
283 obj = obj.__wrapped__
285 i += 1
284 i += 1
286 if i > 100:
285 if i > 100:
287 # __wrapped__ is probably a lie, so return the thing we started with
286 # __wrapped__ is probably a lie, so return the thing we started with
288 return orig_obj
287 return orig_obj
289 return obj
288 return obj
290
289
291 def find_file(obj) -> str:
290 def find_file(obj) -> str:
292 """Find the absolute path to the file where an object was defined.
291 """Find the absolute path to the file where an object was defined.
293
292
294 This is essentially a robust wrapper around `inspect.getabsfile`.
293 This is essentially a robust wrapper around `inspect.getabsfile`.
295
294
296 Returns None if no file can be found.
295 Returns None if no file can be found.
297
296
298 Parameters
297 Parameters
299 ----------
298 ----------
300 obj : any Python object
299 obj : any Python object
301
300
302 Returns
301 Returns
303 -------
302 -------
304 fname : str
303 fname : str
305 The absolute path to the file where the object was defined.
304 The absolute path to the file where the object was defined.
306 """
305 """
307 obj = _get_wrapped(obj)
306 obj = _get_wrapped(obj)
308
307
309 fname = None
308 fname = None
310 try:
309 try:
311 fname = inspect.getabsfile(obj)
310 fname = inspect.getabsfile(obj)
312 except TypeError:
311 except TypeError:
313 # For an instance, the file that matters is where its class was
312 # For an instance, the file that matters is where its class was
314 # declared.
313 # declared.
315 try:
314 try:
316 fname = inspect.getabsfile(obj.__class__)
315 fname = inspect.getabsfile(obj.__class__)
317 except (OSError, TypeError):
316 except (OSError, TypeError):
318 # Can happen for builtins
317 # Can happen for builtins
319 pass
318 pass
320 except OSError:
319 except OSError:
321 pass
320 pass
322
321
323 return cast_unicode(fname)
322 return cast_unicode(fname)
324
323
325
324
326 def find_source_lines(obj):
325 def find_source_lines(obj):
327 """Find the line number in a file where an object was defined.
326 """Find the line number in a file where an object was defined.
328
327
329 This is essentially a robust wrapper around `inspect.getsourcelines`.
328 This is essentially a robust wrapper around `inspect.getsourcelines`.
330
329
331 Returns None if no file can be found.
330 Returns None if no file can be found.
332
331
333 Parameters
332 Parameters
334 ----------
333 ----------
335 obj : any Python object
334 obj : any Python object
336
335
337 Returns
336 Returns
338 -------
337 -------
339 lineno : int
338 lineno : int
340 The line number where the object definition starts.
339 The line number where the object definition starts.
341 """
340 """
342 obj = _get_wrapped(obj)
341 obj = _get_wrapped(obj)
343
342
344 try:
343 try:
345 lineno = inspect.getsourcelines(obj)[1]
344 lineno = inspect.getsourcelines(obj)[1]
346 except TypeError:
345 except TypeError:
347 # For instances, try the class object like getsource() does
346 # For instances, try the class object like getsource() does
348 try:
347 try:
349 lineno = inspect.getsourcelines(obj.__class__)[1]
348 lineno = inspect.getsourcelines(obj.__class__)[1]
350 except (OSError, TypeError):
349 except (OSError, TypeError):
351 return None
350 return None
352 except OSError:
351 except OSError:
353 return None
352 return None
354
353
355 return lineno
354 return lineno
356
355
357 class Inspector(Colorable):
356 class Inspector(Colorable):
358
357
359 def __init__(self, color_table=InspectColors,
358 def __init__(self, color_table=InspectColors,
360 code_color_table=PyColorize.ANSICodeColors,
359 code_color_table=PyColorize.ANSICodeColors,
361 scheme=None,
360 scheme=None,
362 str_detail_level=0,
361 str_detail_level=0,
363 parent=None, config=None):
362 parent=None, config=None):
364 super(Inspector, self).__init__(parent=parent, config=config)
363 super(Inspector, self).__init__(parent=parent, config=config)
365 self.color_table = color_table
364 self.color_table = color_table
366 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
365 self.parser = PyColorize.Parser(out='str', parent=self, style=scheme)
367 self.format = self.parser.format
366 self.format = self.parser.format
368 self.str_detail_level = str_detail_level
367 self.str_detail_level = str_detail_level
369 self.set_active_scheme(scheme)
368 self.set_active_scheme(scheme)
370
369
371 def _getdef(self,obj,oname='') -> Union[str,None]:
370 def _getdef(self,obj,oname='') -> Union[str,None]:
372 """Return the call signature for any callable object.
371 """Return the call signature for any callable object.
373
372
374 If any exception is generated, None is returned instead and the
373 If any exception is generated, None is returned instead and the
375 exception is suppressed."""
374 exception is suppressed."""
376 try:
375 try:
377 return _render_signature(signature(obj), oname)
376 return _render_signature(signature(obj), oname)
378 except:
377 except:
379 return None
378 return None
380
379
381 def __head(self,h) -> str:
380 def __head(self,h) -> str:
382 """Return a header string with proper colors."""
381 """Return a header string with proper colors."""
383 return '%s%s%s' % (self.color_table.active_colors.header,h,
382 return '%s%s%s' % (self.color_table.active_colors.header,h,
384 self.color_table.active_colors.normal)
383 self.color_table.active_colors.normal)
385
384
386 def set_active_scheme(self, scheme):
385 def set_active_scheme(self, scheme):
387 if scheme is not None:
386 if scheme is not None:
388 self.color_table.set_active_scheme(scheme)
387 self.color_table.set_active_scheme(scheme)
389 self.parser.color_table.set_active_scheme(scheme)
388 self.parser.color_table.set_active_scheme(scheme)
390
389
391 def noinfo(self, msg, oname):
390 def noinfo(self, msg, oname):
392 """Generic message when no information is found."""
391 """Generic message when no information is found."""
393 print('No %s found' % msg, end=' ')
392 print('No %s found' % msg, end=' ')
394 if oname:
393 if oname:
395 print('for %s' % oname)
394 print('for %s' % oname)
396 else:
395 else:
397 print()
396 print()
398
397
399 def pdef(self, obj, oname=''):
398 def pdef(self, obj, oname=''):
400 """Print the call signature for any callable object.
399 """Print the call signature for any callable object.
401
400
402 If the object is a class, print the constructor information."""
401 If the object is a class, print the constructor information."""
403
402
404 if not callable(obj):
403 if not callable(obj):
405 print('Object is not callable.')
404 print('Object is not callable.')
406 return
405 return
407
406
408 header = ''
407 header = ''
409
408
410 if inspect.isclass(obj):
409 if inspect.isclass(obj):
411 header = self.__head('Class constructor information:\n')
410 header = self.__head('Class constructor information:\n')
412
411
413
412
414 output = self._getdef(obj,oname)
413 output = self._getdef(obj,oname)
415 if output is None:
414 if output is None:
416 self.noinfo('definition header',oname)
415 self.noinfo('definition header',oname)
417 else:
416 else:
418 print(header,self.format(output), end=' ')
417 print(header,self.format(output), end=' ')
419
418
420 # In Python 3, all classes are new-style, so they all have __init__.
419 # In Python 3, all classes are new-style, so they all have __init__.
421 @skip_doctest
420 @skip_doctest
422 def pdoc(self, obj, oname='', formatter=None):
421 def pdoc(self, obj, oname='', formatter=None):
423 """Print the docstring for any object.
422 """Print the docstring for any object.
424
423
425 Optional:
424 Optional:
426 -formatter: a function to run the docstring through for specially
425 -formatter: a function to run the docstring through for specially
427 formatted docstrings.
426 formatted docstrings.
428
427
429 Examples
428 Examples
430 --------
429 --------
431 In [1]: class NoInit:
430 In [1]: class NoInit:
432 ...: pass
431 ...: pass
433
432
434 In [2]: class NoDoc:
433 In [2]: class NoDoc:
435 ...: def __init__(self):
434 ...: def __init__(self):
436 ...: pass
435 ...: pass
437
436
438 In [3]: %pdoc NoDoc
437 In [3]: %pdoc NoDoc
439 No documentation found for NoDoc
438 No documentation found for NoDoc
440
439
441 In [4]: %pdoc NoInit
440 In [4]: %pdoc NoInit
442 No documentation found for NoInit
441 No documentation found for NoInit
443
442
444 In [5]: obj = NoInit()
443 In [5]: obj = NoInit()
445
444
446 In [6]: %pdoc obj
445 In [6]: %pdoc obj
447 No documentation found for obj
446 No documentation found for obj
448
447
449 In [5]: obj2 = NoDoc()
448 In [5]: obj2 = NoDoc()
450
449
451 In [6]: %pdoc obj2
450 In [6]: %pdoc obj2
452 No documentation found for obj2
451 No documentation found for obj2
453 """
452 """
454
453
455 head = self.__head # For convenience
454 head = self.__head # For convenience
456 lines = []
455 lines = []
457 ds = getdoc(obj)
456 ds = getdoc(obj)
458 if formatter:
457 if formatter:
459 ds = formatter(ds).get('plain/text', ds)
458 ds = formatter(ds).get('plain/text', ds)
460 if ds:
459 if ds:
461 lines.append(head("Class docstring:"))
460 lines.append(head("Class docstring:"))
462 lines.append(indent(ds))
461 lines.append(indent(ds))
463 if inspect.isclass(obj) and hasattr(obj, '__init__'):
462 if inspect.isclass(obj) and hasattr(obj, '__init__'):
464 init_ds = getdoc(obj.__init__)
463 init_ds = getdoc(obj.__init__)
465 if init_ds is not None:
464 if init_ds is not None:
466 lines.append(head("Init docstring:"))
465 lines.append(head("Init docstring:"))
467 lines.append(indent(init_ds))
466 lines.append(indent(init_ds))
468 elif hasattr(obj,'__call__'):
467 elif hasattr(obj,'__call__'):
469 call_ds = getdoc(obj.__call__)
468 call_ds = getdoc(obj.__call__)
470 if call_ds:
469 if call_ds:
471 lines.append(head("Call docstring:"))
470 lines.append(head("Call docstring:"))
472 lines.append(indent(call_ds))
471 lines.append(indent(call_ds))
473
472
474 if not lines:
473 if not lines:
475 self.noinfo('documentation',oname)
474 self.noinfo('documentation',oname)
476 else:
475 else:
477 page.page('\n'.join(lines))
476 page.page('\n'.join(lines))
478
477
479 def psource(self, obj, oname=''):
478 def psource(self, obj, oname=''):
480 """Print the source code for an object."""
479 """Print the source code for an object."""
481
480
482 # Flush the source cache because inspect can return out-of-date source
481 # Flush the source cache because inspect can return out-of-date source
483 linecache.checkcache()
482 linecache.checkcache()
484 try:
483 try:
485 src = getsource(obj, oname=oname)
484 src = getsource(obj, oname=oname)
486 except Exception:
485 except Exception:
487 src = None
486 src = None
488
487
489 if src is None:
488 if src is None:
490 self.noinfo('source', oname)
489 self.noinfo('source', oname)
491 else:
490 else:
492 page.page(self.format(src))
491 page.page(self.format(src))
493
492
494 def pfile(self, obj, oname=''):
493 def pfile(self, obj, oname=''):
495 """Show the whole file where an object was defined."""
494 """Show the whole file where an object was defined."""
496
495
497 lineno = find_source_lines(obj)
496 lineno = find_source_lines(obj)
498 if lineno is None:
497 if lineno is None:
499 self.noinfo('file', oname)
498 self.noinfo('file', oname)
500 return
499 return
501
500
502 ofile = find_file(obj)
501 ofile = find_file(obj)
503 # run contents of file through pager starting at line where the object
502 # run contents of file through pager starting at line where the object
504 # is defined, as long as the file isn't binary and is actually on the
503 # is defined, as long as the file isn't binary and is actually on the
505 # filesystem.
504 # filesystem.
506 if ofile.endswith(('.so', '.dll', '.pyd')):
505 if ofile.endswith(('.so', '.dll', '.pyd')):
507 print('File %r is binary, not printing.' % ofile)
506 print('File %r is binary, not printing.' % ofile)
508 elif not os.path.isfile(ofile):
507 elif not os.path.isfile(ofile):
509 print('File %r does not exist, not printing.' % ofile)
508 print('File %r does not exist, not printing.' % ofile)
510 else:
509 else:
511 # Print only text files, not extension binaries. Note that
510 # Print only text files, not extension binaries. Note that
512 # getsourcelines returns lineno with 1-offset and page() uses
511 # getsourcelines returns lineno with 1-offset and page() uses
513 # 0-offset, so we must adjust.
512 # 0-offset, so we must adjust.
514 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
513 page.page(self.format(openpy.read_py_file(ofile, skip_encoding_cookie=False)), lineno - 1)
515
514
516
515
517 def _mime_format(self, text:str, formatter=None) -> dict:
516 def _mime_format(self, text:str, formatter=None) -> dict:
518 """Return a mime bundle representation of the input text.
517 """Return a mime bundle representation of the input text.
519
518
520 - if `formatter` is None, the returned mime bundle has
519 - if `formatter` is None, the returned mime bundle has
521 a ``text/plain`` field, with the input text.
520 a ``text/plain`` field, with the input text.
522 a ``text/html`` field with a ``<pre>`` tag containing the input text.
521 a ``text/html`` field with a ``<pre>`` tag containing the input text.
523
522
524 - if ``formatter`` is not None, it must be a callable transforming the
523 - if ``formatter`` is not None, it must be a callable transforming the
525 input text into a mime bundle. Default values for ``text/plain`` and
524 input text into a mime bundle. Default values for ``text/plain`` and
526 ``text/html`` representations are the ones described above.
525 ``text/html`` representations are the ones described above.
527
526
528 Note:
527 Note:
529
528
530 Formatters returning strings are supported but this behavior is deprecated.
529 Formatters returning strings are supported but this behavior is deprecated.
531
530
532 """
531 """
533 defaults = {
532 defaults = {
534 'text/plain': text,
533 'text/plain': text,
535 'text/html': '<pre>' + text + '</pre>'
534 'text/html': '<pre>' + text + '</pre>'
536 }
535 }
537
536
538 if formatter is None:
537 if formatter is None:
539 return defaults
538 return defaults
540 else:
539 else:
541 formatted = formatter(text)
540 formatted = formatter(text)
542
541
543 if not isinstance(formatted, dict):
542 if not isinstance(formatted, dict):
544 # Handle the deprecated behavior of a formatter returning
543 # Handle the deprecated behavior of a formatter returning
545 # a string instead of a mime bundle.
544 # a string instead of a mime bundle.
546 return {
545 return {
547 'text/plain': formatted,
546 'text/plain': formatted,
548 'text/html': '<pre>' + formatted + '</pre>'
547 'text/html': '<pre>' + formatted + '</pre>'
549 }
548 }
550
549
551 else:
550 else:
552 return dict(defaults, **formatted)
551 return dict(defaults, **formatted)
553
552
554
553
555 def format_mime(self, bundle):
554 def format_mime(self, bundle):
556
555
557 text_plain = bundle['text/plain']
556 text_plain = bundle['text/plain']
558
557
559 text = ''
558 text = ''
560 heads, bodies = list(zip(*text_plain))
559 heads, bodies = list(zip(*text_plain))
561 _len = max(len(h) for h in heads)
560 _len = max(len(h) for h in heads)
562
561
563 for head, body in zip(heads, bodies):
562 for head, body in zip(heads, bodies):
564 body = body.strip('\n')
563 body = body.strip('\n')
565 delim = '\n' if '\n' in body else ' '
564 delim = '\n' if '\n' in body else ' '
566 text += self.__head(head+':') + (_len - len(head))*' ' +delim + body +'\n'
565 text += self.__head(head+':') + (_len - len(head))*' ' +delim + body +'\n'
567
566
568 bundle['text/plain'] = text
567 bundle['text/plain'] = text
569 return bundle
568 return bundle
570
569
571 def _get_info(
570 def _get_info(
572 self, obj, oname="", formatter=None, info=None, detail_level=0, omit_sections=()
571 self, obj, oname="", formatter=None, info=None, detail_level=0, omit_sections=()
573 ):
572 ):
574 """Retrieve an info dict and format it.
573 """Retrieve an info dict and format it.
575
574
576 Parameters
575 Parameters
577 ----------
576 ----------
578 obj : any
577 obj : any
579 Object to inspect and return info from
578 Object to inspect and return info from
580 oname : str (default: ''):
579 oname : str (default: ''):
581 Name of the variable pointing to `obj`.
580 Name of the variable pointing to `obj`.
582 formatter : callable
581 formatter : callable
583 info
582 info
584 already computed information
583 already computed information
585 detail_level : integer
584 detail_level : integer
586 Granularity of detail level, if set to 1, give more information.
585 Granularity of detail level, if set to 1, give more information.
587 omit_sections : container[str]
586 omit_sections : container[str]
588 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`)
587 Titles or keys to omit from output (can be set, tuple, etc., anything supporting `in`)
589 """
588 """
590
589
591 info = self.info(obj, oname=oname, info=info, detail_level=detail_level)
590 info = self.info(obj, oname=oname, info=info, detail_level=detail_level)
592
591
593 _mime = {
592 _mime = {
594 'text/plain': [],
593 'text/plain': [],
595 'text/html': '',
594 'text/html': '',
596 }
595 }
597
596
598 def append_field(bundle, title:str, key:str, formatter=None):
597 def append_field(bundle, title:str, key:str, formatter=None):
599 if title in omit_sections or key in omit_sections:
598 if title in omit_sections or key in omit_sections:
600 return
599 return
601 field = info[key]
600 field = info[key]
602 if field is not None:
601 if field is not None:
603 formatted_field = self._mime_format(field, formatter)
602 formatted_field = self._mime_format(field, formatter)
604 bundle['text/plain'].append((title, formatted_field['text/plain']))
603 bundle['text/plain'].append((title, formatted_field['text/plain']))
605 bundle['text/html'] += '<h1>' + title + '</h1>\n' + formatted_field['text/html'] + '\n'
604 bundle['text/html'] += '<h1>' + title + '</h1>\n' + formatted_field['text/html'] + '\n'
606
605
607 def code_formatter(text):
606 def code_formatter(text):
608 return {
607 return {
609 'text/plain': self.format(text),
608 'text/plain': self.format(text),
610 'text/html': pylight(text)
609 'text/html': pylight(text)
611 }
610 }
612
611
613 if info['isalias']:
612 if info['isalias']:
614 append_field(_mime, 'Repr', 'string_form')
613 append_field(_mime, 'Repr', 'string_form')
615
614
616 elif info['ismagic']:
615 elif info['ismagic']:
617 if detail_level > 0:
616 if detail_level > 0:
618 append_field(_mime, 'Source', 'source', code_formatter)
617 append_field(_mime, 'Source', 'source', code_formatter)
619 else:
618 else:
620 append_field(_mime, 'Docstring', 'docstring', formatter)
619 append_field(_mime, 'Docstring', 'docstring', formatter)
621 append_field(_mime, 'File', 'file')
620 append_field(_mime, 'File', 'file')
622
621
623 elif info['isclass'] or is_simple_callable(obj):
622 elif info['isclass'] or is_simple_callable(obj):
624 # Functions, methods, classes
623 # Functions, methods, classes
625 append_field(_mime, 'Signature', 'definition', code_formatter)
624 append_field(_mime, 'Signature', 'definition', code_formatter)
626 append_field(_mime, 'Init signature', 'init_definition', code_formatter)
625 append_field(_mime, 'Init signature', 'init_definition', code_formatter)
627 append_field(_mime, 'Docstring', 'docstring', formatter)
626 append_field(_mime, 'Docstring', 'docstring', formatter)
628 if detail_level > 0 and info['source']:
627 if detail_level > 0 and info['source']:
629 append_field(_mime, 'Source', 'source', code_formatter)
628 append_field(_mime, 'Source', 'source', code_formatter)
630 else:
629 else:
631 append_field(_mime, 'Init docstring', 'init_docstring', formatter)
630 append_field(_mime, 'Init docstring', 'init_docstring', formatter)
632
631
633 append_field(_mime, 'File', 'file')
632 append_field(_mime, 'File', 'file')
634 append_field(_mime, 'Type', 'type_name')
633 append_field(_mime, 'Type', 'type_name')
635 append_field(_mime, 'Subclasses', 'subclasses')
634 append_field(_mime, 'Subclasses', 'subclasses')
636
635
637 else:
636 else:
638 # General Python objects
637 # General Python objects
639 append_field(_mime, 'Signature', 'definition', code_formatter)
638 append_field(_mime, 'Signature', 'definition', code_formatter)
640 append_field(_mime, 'Call signature', 'call_def', code_formatter)
639 append_field(_mime, 'Call signature', 'call_def', code_formatter)
641 append_field(_mime, 'Type', 'type_name')
640 append_field(_mime, 'Type', 'type_name')
642 append_field(_mime, 'String form', 'string_form')
641 append_field(_mime, 'String form', 'string_form')
643
642
644 # Namespace
643 # Namespace
645 if info['namespace'] != 'Interactive':
644 if info['namespace'] != 'Interactive':
646 append_field(_mime, 'Namespace', 'namespace')
645 append_field(_mime, 'Namespace', 'namespace')
647
646
648 append_field(_mime, 'Length', 'length')
647 append_field(_mime, 'Length', 'length')
649 append_field(_mime, 'File', 'file')
648 append_field(_mime, 'File', 'file')
650
649
651 # Source or docstring, depending on detail level and whether
650 # Source or docstring, depending on detail level and whether
652 # source found.
651 # source found.
653 if detail_level > 0 and info['source']:
652 if detail_level > 0 and info['source']:
654 append_field(_mime, 'Source', 'source', code_formatter)
653 append_field(_mime, 'Source', 'source', code_formatter)
655 else:
654 else:
656 append_field(_mime, 'Docstring', 'docstring', formatter)
655 append_field(_mime, 'Docstring', 'docstring', formatter)
657
656
658 append_field(_mime, 'Class docstring', 'class_docstring', formatter)
657 append_field(_mime, 'Class docstring', 'class_docstring', formatter)
659 append_field(_mime, 'Init docstring', 'init_docstring', formatter)
658 append_field(_mime, 'Init docstring', 'init_docstring', formatter)
660 append_field(_mime, 'Call docstring', 'call_docstring', formatter)
659 append_field(_mime, 'Call docstring', 'call_docstring', formatter)
661
660
662
661
663 return self.format_mime(_mime)
662 return self.format_mime(_mime)
664
663
665 def pinfo(
664 def pinfo(
666 self,
665 self,
667 obj,
666 obj,
668 oname="",
667 oname="",
669 formatter=None,
668 formatter=None,
670 info=None,
669 info=None,
671 detail_level=0,
670 detail_level=0,
672 enable_html_pager=True,
671 enable_html_pager=True,
673 omit_sections=(),
672 omit_sections=(),
674 ):
673 ):
675 """Show detailed information about an object.
674 """Show detailed information about an object.
676
675
677 Optional arguments:
676 Optional arguments:
678
677
679 - oname: name of the variable pointing to the object.
678 - oname: name of the variable pointing to the object.
680
679
681 - formatter: callable (optional)
680 - formatter: callable (optional)
682 A special formatter for docstrings.
681 A special formatter for docstrings.
683
682
684 The formatter is a callable that takes a string as an input
683 The formatter is a callable that takes a string as an input
685 and returns either a formatted string or a mime type bundle
684 and returns either a formatted string or a mime type bundle
686 in the form of a dictionary.
685 in the form of a dictionary.
687
686
688 Although the support of custom formatter returning a string
687 Although the support of custom formatter returning a string
689 instead of a mime type bundle is deprecated.
688 instead of a mime type bundle is deprecated.
690
689
691 - info: a structure with some information fields which may have been
690 - info: a structure with some information fields which may have been
692 precomputed already.
691 precomputed already.
693
692
694 - detail_level: if set to 1, more information is given.
693 - detail_level: if set to 1, more information is given.
695
694
696 - omit_sections: set of section keys and titles to omit
695 - omit_sections: set of section keys and titles to omit
697 """
696 """
698 info = self._get_info(
697 info = self._get_info(
699 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
698 obj, oname, formatter, info, detail_level, omit_sections=omit_sections
700 )
699 )
701 if not enable_html_pager:
700 if not enable_html_pager:
702 del info['text/html']
701 del info['text/html']
703 page.page(info)
702 page.page(info)
704
703
705 def _info(self, obj, oname="", info=None, detail_level=0):
704 def _info(self, obj, oname="", info=None, detail_level=0):
706 """
705 """
707 Inspector.info() was likely improperly marked as deprecated
706 Inspector.info() was likely improperly marked as deprecated
708 while only a parameter was deprecated. We "un-deprecate" it.
707 while only a parameter was deprecated. We "un-deprecate" it.
709 """
708 """
710
709
711 warnings.warn(
710 warnings.warn(
712 "The `Inspector.info()` method has been un-deprecated as of 8.0 "
711 "The `Inspector.info()` method has been un-deprecated as of 8.0 "
713 "and the `formatter=` keyword removed. `Inspector._info` is now "
712 "and the `formatter=` keyword removed. `Inspector._info` is now "
714 "an alias, and you can just call `.info()` directly.",
713 "an alias, and you can just call `.info()` directly.",
715 DeprecationWarning,
714 DeprecationWarning,
716 stacklevel=2,
715 stacklevel=2,
717 )
716 )
718 return self.info(obj, oname=oname, info=info, detail_level=detail_level)
717 return self.info(obj, oname=oname, info=info, detail_level=detail_level)
719
718
720 def info(self, obj, oname="", info=None, detail_level=0) -> dict:
719 def info(self, obj, oname="", info=None, detail_level=0) -> dict:
721 """Compute a dict with detailed information about an object.
720 """Compute a dict with detailed information about an object.
722
721
723 Parameters
722 Parameters
724 ----------
723 ----------
725 obj : any
724 obj : any
726 An object to find information about
725 An object to find information about
727 oname : str (default: '')
726 oname : str (default: '')
728 Name of the variable pointing to `obj`.
727 Name of the variable pointing to `obj`.
729 info : (default: None)
728 info : (default: None)
730 A struct (dict like with attr access) with some information fields
729 A struct (dict like with attr access) with some information fields
731 which may have been precomputed already.
730 which may have been precomputed already.
732 detail_level : int (default:0)
731 detail_level : int (default:0)
733 If set to 1, more information is given.
732 If set to 1, more information is given.
734
733
735 Returns
734 Returns
736 -------
735 -------
737 An object info dict with known fields from `info_fields`. Keys are
736 An object info dict with known fields from `info_fields`. Keys are
738 strings, values are string or None.
737 strings, values are string or None.
739 """
738 """
740
739
741 if info is None:
740 if info is None:
742 ismagic = False
741 ismagic = False
743 isalias = False
742 isalias = False
744 ospace = ''
743 ospace = ''
745 else:
744 else:
746 ismagic = info.ismagic
745 ismagic = info.ismagic
747 isalias = info.isalias
746 isalias = info.isalias
748 ospace = info.namespace
747 ospace = info.namespace
749
748
750 # Get docstring, special-casing aliases:
749 # Get docstring, special-casing aliases:
751 if isalias:
750 if isalias:
752 if not callable(obj):
751 if not callable(obj):
753 try:
752 try:
754 ds = "Alias to the system command:\n %s" % obj[1]
753 ds = "Alias to the system command:\n %s" % obj[1]
755 except:
754 except:
756 ds = "Alias: " + str(obj)
755 ds = "Alias: " + str(obj)
757 else:
756 else:
758 ds = "Alias to " + str(obj)
757 ds = "Alias to " + str(obj)
759 if obj.__doc__:
758 if obj.__doc__:
760 ds += "\nDocstring:\n" + obj.__doc__
759 ds += "\nDocstring:\n" + obj.__doc__
761 else:
760 else:
762 ds = getdoc(obj)
761 ds = getdoc(obj)
763 if ds is None:
762 if ds is None:
764 ds = '<no docstring>'
763 ds = '<no docstring>'
765
764
766 # store output in a dict, we initialize it here and fill it as we go
765 # store output in a dict, we initialize it here and fill it as we go
767 out = dict(name=oname, found=True, isalias=isalias, ismagic=ismagic, subclasses=None)
766 out = dict(name=oname, found=True, isalias=isalias, ismagic=ismagic, subclasses=None)
768
767
769 string_max = 200 # max size of strings to show (snipped if longer)
768 string_max = 200 # max size of strings to show (snipped if longer)
770 shalf = int((string_max - 5) / 2)
769 shalf = int((string_max - 5) / 2)
771
770
772 if ismagic:
771 if ismagic:
773 out['type_name'] = 'Magic function'
772 out['type_name'] = 'Magic function'
774 elif isalias:
773 elif isalias:
775 out['type_name'] = 'System alias'
774 out['type_name'] = 'System alias'
776 else:
775 else:
777 out['type_name'] = type(obj).__name__
776 out['type_name'] = type(obj).__name__
778
777
779 try:
778 try:
780 bclass = obj.__class__
779 bclass = obj.__class__
781 out['base_class'] = str(bclass)
780 out['base_class'] = str(bclass)
782 except:
781 except:
783 pass
782 pass
784
783
785 # String form, but snip if too long in ? form (full in ??)
784 # String form, but snip if too long in ? form (full in ??)
786 if detail_level >= self.str_detail_level:
785 if detail_level >= self.str_detail_level:
787 try:
786 try:
788 ostr = str(obj)
787 ostr = str(obj)
789 str_head = 'string_form'
788 str_head = 'string_form'
790 if not detail_level and len(ostr)>string_max:
789 if not detail_level and len(ostr)>string_max:
791 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
790 ostr = ostr[:shalf] + ' <...> ' + ostr[-shalf:]
792 ostr = ("\n" + " " * len(str_head.expandtabs())).\
791 ostr = ("\n" + " " * len(str_head.expandtabs())).\
793 join(q.strip() for q in ostr.split("\n"))
792 join(q.strip() for q in ostr.split("\n"))
794 out[str_head] = ostr
793 out[str_head] = ostr
795 except:
794 except:
796 pass
795 pass
797
796
798 if ospace:
797 if ospace:
799 out['namespace'] = ospace
798 out['namespace'] = ospace
800
799
801 # Length (for strings and lists)
800 # Length (for strings and lists)
802 try:
801 try:
803 out['length'] = str(len(obj))
802 out['length'] = str(len(obj))
804 except Exception:
803 except Exception:
805 pass
804 pass
806
805
807 # Filename where object was defined
806 # Filename where object was defined
808 binary_file = False
807 binary_file = False
809 fname = find_file(obj)
808 fname = find_file(obj)
810 if fname is None:
809 if fname is None:
811 # if anything goes wrong, we don't want to show source, so it's as
810 # if anything goes wrong, we don't want to show source, so it's as
812 # if the file was binary
811 # if the file was binary
813 binary_file = True
812 binary_file = True
814 else:
813 else:
815 if fname.endswith(('.so', '.dll', '.pyd')):
814 if fname.endswith(('.so', '.dll', '.pyd')):
816 binary_file = True
815 binary_file = True
817 elif fname.endswith('<string>'):
816 elif fname.endswith('<string>'):
818 fname = 'Dynamically generated function. No source code available.'
817 fname = 'Dynamically generated function. No source code available.'
819 out['file'] = compress_user(fname)
818 out['file'] = compress_user(fname)
820
819
821 # Original source code for a callable, class or property.
820 # Original source code for a callable, class or property.
822 if detail_level:
821 if detail_level:
823 # Flush the source cache because inspect can return out-of-date
822 # Flush the source cache because inspect can return out-of-date
824 # source
823 # source
825 linecache.checkcache()
824 linecache.checkcache()
826 try:
825 try:
827 if isinstance(obj, property) or not binary_file:
826 if isinstance(obj, property) or not binary_file:
828 src = getsource(obj, oname)
827 src = getsource(obj, oname)
829 if src is not None:
828 if src is not None:
830 src = src.rstrip()
829 src = src.rstrip()
831 out['source'] = src
830 out['source'] = src
832
831
833 except Exception:
832 except Exception:
834 pass
833 pass
835
834
836 # Add docstring only if no source is to be shown (avoid repetitions).
835 # Add docstring only if no source is to be shown (avoid repetitions).
837 if ds and not self._source_contains_docstring(out.get('source'), ds):
836 if ds and not self._source_contains_docstring(out.get('source'), ds):
838 out['docstring'] = ds
837 out['docstring'] = ds
839
838
840 # Constructor docstring for classes
839 # Constructor docstring for classes
841 if inspect.isclass(obj):
840 if inspect.isclass(obj):
842 out['isclass'] = True
841 out['isclass'] = True
843
842
844 # get the init signature:
843 # get the init signature:
845 try:
844 try:
846 init_def = self._getdef(obj, oname)
845 init_def = self._getdef(obj, oname)
847 except AttributeError:
846 except AttributeError:
848 init_def = None
847 init_def = None
849
848
850 # get the __init__ docstring
849 # get the __init__ docstring
851 try:
850 try:
852 obj_init = obj.__init__
851 obj_init = obj.__init__
853 except AttributeError:
852 except AttributeError:
854 init_ds = None
853 init_ds = None
855 else:
854 else:
856 if init_def is None:
855 if init_def is None:
857 # Get signature from init if top-level sig failed.
856 # Get signature from init if top-level sig failed.
858 # Can happen for built-in types (list, etc.).
857 # Can happen for built-in types (list, etc.).
859 try:
858 try:
860 init_def = self._getdef(obj_init, oname)
859 init_def = self._getdef(obj_init, oname)
861 except AttributeError:
860 except AttributeError:
862 pass
861 pass
863 init_ds = getdoc(obj_init)
862 init_ds = getdoc(obj_init)
864 # Skip Python's auto-generated docstrings
863 # Skip Python's auto-generated docstrings
865 if init_ds == _object_init_docstring:
864 if init_ds == _object_init_docstring:
866 init_ds = None
865 init_ds = None
867
866
868 if init_def:
867 if init_def:
869 out['init_definition'] = init_def
868 out['init_definition'] = init_def
870
869
871 if init_ds:
870 if init_ds:
872 out['init_docstring'] = init_ds
871 out['init_docstring'] = init_ds
873
872
874 names = [sub.__name__ for sub in type.__subclasses__(obj)]
873 names = [sub.__name__ for sub in type.__subclasses__(obj)]
875 if len(names) < 10:
874 if len(names) < 10:
876 all_names = ', '.join(names)
875 all_names = ', '.join(names)
877 else:
876 else:
878 all_names = ', '.join(names[:10]+['...'])
877 all_names = ', '.join(names[:10]+['...'])
879 out['subclasses'] = all_names
878 out['subclasses'] = all_names
880 # and class docstring for instances:
879 # and class docstring for instances:
881 else:
880 else:
882 # reconstruct the function definition and print it:
881 # reconstruct the function definition and print it:
883 defln = self._getdef(obj, oname)
882 defln = self._getdef(obj, oname)
884 if defln:
883 if defln:
885 out['definition'] = defln
884 out['definition'] = defln
886
885
887 # First, check whether the instance docstring is identical to the
886 # First, check whether the instance docstring is identical to the
888 # class one, and print it separately if they don't coincide. In
887 # class one, and print it separately if they don't coincide. In
889 # most cases they will, but it's nice to print all the info for
888 # most cases they will, but it's nice to print all the info for
890 # objects which use instance-customized docstrings.
889 # objects which use instance-customized docstrings.
891 if ds:
890 if ds:
892 try:
891 try:
893 cls = getattr(obj,'__class__')
892 cls = getattr(obj,'__class__')
894 except:
893 except:
895 class_ds = None
894 class_ds = None
896 else:
895 else:
897 class_ds = getdoc(cls)
896 class_ds = getdoc(cls)
898 # Skip Python's auto-generated docstrings
897 # Skip Python's auto-generated docstrings
899 if class_ds in _builtin_type_docstrings:
898 if class_ds in _builtin_type_docstrings:
900 class_ds = None
899 class_ds = None
901 if class_ds and ds != class_ds:
900 if class_ds and ds != class_ds:
902 out['class_docstring'] = class_ds
901 out['class_docstring'] = class_ds
903
902
904 # Next, try to show constructor docstrings
903 # Next, try to show constructor docstrings
905 try:
904 try:
906 init_ds = getdoc(obj.__init__)
905 init_ds = getdoc(obj.__init__)
907 # Skip Python's auto-generated docstrings
906 # Skip Python's auto-generated docstrings
908 if init_ds == _object_init_docstring:
907 if init_ds == _object_init_docstring:
909 init_ds = None
908 init_ds = None
910 except AttributeError:
909 except AttributeError:
911 init_ds = None
910 init_ds = None
912 if init_ds:
911 if init_ds:
913 out['init_docstring'] = init_ds
912 out['init_docstring'] = init_ds
914
913
915 # Call form docstring for callable instances
914 # Call form docstring for callable instances
916 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
915 if safe_hasattr(obj, '__call__') and not is_simple_callable(obj):
917 call_def = self._getdef(obj.__call__, oname)
916 call_def = self._getdef(obj.__call__, oname)
918 if call_def and (call_def != out.get('definition')):
917 if call_def and (call_def != out.get('definition')):
919 # it may never be the case that call def and definition differ,
918 # it may never be the case that call def and definition differ,
920 # but don't include the same signature twice
919 # but don't include the same signature twice
921 out['call_def'] = call_def
920 out['call_def'] = call_def
922 call_ds = getdoc(obj.__call__)
921 call_ds = getdoc(obj.__call__)
923 # Skip Python's auto-generated docstrings
922 # Skip Python's auto-generated docstrings
924 if call_ds == _func_call_docstring:
923 if call_ds == _func_call_docstring:
925 call_ds = None
924 call_ds = None
926 if call_ds:
925 if call_ds:
927 out['call_docstring'] = call_ds
926 out['call_docstring'] = call_ds
928
927
929 return object_info(**out)
928 return object_info(**out)
930
929
931 @staticmethod
930 @staticmethod
932 def _source_contains_docstring(src, doc):
931 def _source_contains_docstring(src, doc):
933 """
932 """
934 Check whether the source *src* contains the docstring *doc*.
933 Check whether the source *src* contains the docstring *doc*.
935
934
936 This is is helper function to skip displaying the docstring if the
935 This is is helper function to skip displaying the docstring if the
937 source already contains it, avoiding repetition of information.
936 source already contains it, avoiding repetition of information.
938 """
937 """
939 try:
938 try:
940 def_node, = ast.parse(dedent(src)).body
939 def_node, = ast.parse(dedent(src)).body
941 return ast.get_docstring(def_node) == doc
940 return ast.get_docstring(def_node) == doc
942 except Exception:
941 except Exception:
943 # The source can become invalid or even non-existent (because it
942 # The source can become invalid or even non-existent (because it
944 # is re-fetched from the source file) so the above code fail in
943 # is re-fetched from the source file) so the above code fail in
945 # arbitrary ways.
944 # arbitrary ways.
946 return False
945 return False
947
946
948 def psearch(self,pattern,ns_table,ns_search=[],
947 def psearch(self,pattern,ns_table,ns_search=[],
949 ignore_case=False,show_all=False, *, list_types=False):
948 ignore_case=False,show_all=False, *, list_types=False):
950 """Search namespaces with wildcards for objects.
949 """Search namespaces with wildcards for objects.
951
950
952 Arguments:
951 Arguments:
953
952
954 - pattern: string containing shell-like wildcards to use in namespace
953 - pattern: string containing shell-like wildcards to use in namespace
955 searches and optionally a type specification to narrow the search to
954 searches and optionally a type specification to narrow the search to
956 objects of that type.
955 objects of that type.
957
956
958 - ns_table: dict of name->namespaces for search.
957 - ns_table: dict of name->namespaces for search.
959
958
960 Optional arguments:
959 Optional arguments:
961
960
962 - ns_search: list of namespace names to include in search.
961 - ns_search: list of namespace names to include in search.
963
962
964 - ignore_case(False): make the search case-insensitive.
963 - ignore_case(False): make the search case-insensitive.
965
964
966 - show_all(False): show all names, including those starting with
965 - show_all(False): show all names, including those starting with
967 underscores.
966 underscores.
968
967
969 - list_types(False): list all available object types for object matching.
968 - list_types(False): list all available object types for object matching.
970 """
969 """
971 #print 'ps pattern:<%r>' % pattern # dbg
970 #print 'ps pattern:<%r>' % pattern # dbg
972
971
973 # defaults
972 # defaults
974 type_pattern = 'all'
973 type_pattern = 'all'
975 filter = ''
974 filter = ''
976
975
977 # list all object types
976 # list all object types
978 if list_types:
977 if list_types:
979 page.page('\n'.join(sorted(typestr2type)))
978 page.page('\n'.join(sorted(typestr2type)))
980 return
979 return
981
980
982 cmds = pattern.split()
981 cmds = pattern.split()
983 len_cmds = len(cmds)
982 len_cmds = len(cmds)
984 if len_cmds == 1:
983 if len_cmds == 1:
985 # Only filter pattern given
984 # Only filter pattern given
986 filter = cmds[0]
985 filter = cmds[0]
987 elif len_cmds == 2:
986 elif len_cmds == 2:
988 # Both filter and type specified
987 # Both filter and type specified
989 filter,type_pattern = cmds
988 filter,type_pattern = cmds
990 else:
989 else:
991 raise ValueError('invalid argument string for psearch: <%s>' %
990 raise ValueError('invalid argument string for psearch: <%s>' %
992 pattern)
991 pattern)
993
992
994 # filter search namespaces
993 # filter search namespaces
995 for name in ns_search:
994 for name in ns_search:
996 if name not in ns_table:
995 if name not in ns_table:
997 raise ValueError('invalid namespace <%s>. Valid names: %s' %
996 raise ValueError('invalid namespace <%s>. Valid names: %s' %
998 (name,ns_table.keys()))
997 (name,ns_table.keys()))
999
998
1000 #print 'type_pattern:',type_pattern # dbg
999 #print 'type_pattern:',type_pattern # dbg
1001 search_result, namespaces_seen = set(), set()
1000 search_result, namespaces_seen = set(), set()
1002 for ns_name in ns_search:
1001 for ns_name in ns_search:
1003 ns = ns_table[ns_name]
1002 ns = ns_table[ns_name]
1004 # Normally, locals and globals are the same, so we just check one.
1003 # Normally, locals and globals are the same, so we just check one.
1005 if id(ns) in namespaces_seen:
1004 if id(ns) in namespaces_seen:
1006 continue
1005 continue
1007 namespaces_seen.add(id(ns))
1006 namespaces_seen.add(id(ns))
1008 tmp_res = list_namespace(ns, type_pattern, filter,
1007 tmp_res = list_namespace(ns, type_pattern, filter,
1009 ignore_case=ignore_case, show_all=show_all)
1008 ignore_case=ignore_case, show_all=show_all)
1010 search_result.update(tmp_res)
1009 search_result.update(tmp_res)
1011
1010
1012 page.page('\n'.join(sorted(search_result)))
1011 page.page('\n'.join(sorted(search_result)))
1013
1012
1014
1013
1015 def _render_signature(obj_signature, obj_name) -> str:
1014 def _render_signature(obj_signature, obj_name) -> str:
1016 """
1015 """
1017 This was mostly taken from inspect.Signature.__str__.
1016 This was mostly taken from inspect.Signature.__str__.
1018 Look there for the comments.
1017 Look there for the comments.
1019 The only change is to add linebreaks when this gets too long.
1018 The only change is to add linebreaks when this gets too long.
1020 """
1019 """
1021 result = []
1020 result = []
1022 pos_only = False
1021 pos_only = False
1023 kw_only = True
1022 kw_only = True
1024 for param in obj_signature.parameters.values():
1023 for param in obj_signature.parameters.values():
1025 if param.kind == inspect._POSITIONAL_ONLY:
1024 if param.kind == inspect._POSITIONAL_ONLY:
1026 pos_only = True
1025 pos_only = True
1027 elif pos_only:
1026 elif pos_only:
1028 result.append('/')
1027 result.append('/')
1029 pos_only = False
1028 pos_only = False
1030
1029
1031 if param.kind == inspect._VAR_POSITIONAL:
1030 if param.kind == inspect._VAR_POSITIONAL:
1032 kw_only = False
1031 kw_only = False
1033 elif param.kind == inspect._KEYWORD_ONLY and kw_only:
1032 elif param.kind == inspect._KEYWORD_ONLY and kw_only:
1034 result.append('*')
1033 result.append('*')
1035 kw_only = False
1034 kw_only = False
1036
1035
1037 result.append(str(param))
1036 result.append(str(param))
1038
1037
1039 if pos_only:
1038 if pos_only:
1040 result.append('/')
1039 result.append('/')
1041
1040
1042 # add up name, parameters, braces (2), and commas
1041 # add up name, parameters, braces (2), and commas
1043 if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
1042 if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
1044 # This doesn’t fit behind β€œSignature: ” in an inspect window.
1043 # This doesn’t fit behind β€œSignature: ” in an inspect window.
1045 rendered = '{}(\n{})'.format(obj_name, ''.join(
1044 rendered = '{}(\n{})'.format(obj_name, ''.join(
1046 ' {},\n'.format(r) for r in result)
1045 ' {},\n'.format(r) for r in result)
1047 )
1046 )
1048 else:
1047 else:
1049 rendered = '{}({})'.format(obj_name, ', '.join(result))
1048 rendered = '{}({})'.format(obj_name, ', '.join(result))
1050
1049
1051 if obj_signature.return_annotation is not inspect._empty:
1050 if obj_signature.return_annotation is not inspect._empty:
1052 anno = inspect.formatannotation(obj_signature.return_annotation)
1051 anno = inspect.formatannotation(obj_signature.return_annotation)
1053 rendered += ' -> {}'.format(anno)
1052 rendered += ' -> {}'.format(anno)
1054
1053
1055 return rendered
1054 return rendered
@@ -1,452 +1,451 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A mixin for :class:`~IPython.core.application.Application` classes that
3 A mixin for :class:`~IPython.core.application.Application` classes that
4 launch InteractiveShell instances, load extensions, etc.
4 launch InteractiveShell instances, load extensions, etc.
5 """
5 """
6
6
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10 import glob
10 import glob
11 from itertools import chain
11 from itertools import chain
12 import os
12 import os
13 import sys
13 import sys
14
14
15 from traitlets.config.application import boolean_flag
15 from traitlets.config.application import boolean_flag
16 from traitlets.config.configurable import Configurable
16 from traitlets.config.configurable import Configurable
17 from traitlets.config.loader import Config
17 from traitlets.config.loader import Config
18 from IPython.core.application import SYSTEM_CONFIG_DIRS, ENV_CONFIG_DIRS
18 from IPython.core.application import SYSTEM_CONFIG_DIRS, ENV_CONFIG_DIRS
19 from IPython.core import pylabtools
19 from IPython.core import pylabtools
20 from IPython.utils.contexts import preserve_keys
20 from IPython.utils.contexts import preserve_keys
21 from IPython.utils.path import filefind
21 from IPython.utils.path import filefind
22 import traitlets
23 from traitlets import (
22 from traitlets import (
24 Unicode, Instance, List, Bool, CaselessStrEnum, observe,
23 Unicode, Instance, List, Bool, CaselessStrEnum, observe,
25 DottedObjectName,
24 DottedObjectName,
26 )
25 )
27 from IPython.terminal import pt_inputhooks
26 from IPython.terminal import pt_inputhooks
28
27
29 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
30 # Aliases and Flags
29 # Aliases and Flags
31 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
32
31
33 gui_keys = tuple(sorted(pt_inputhooks.backends) + sorted(pt_inputhooks.aliases))
32 gui_keys = tuple(sorted(pt_inputhooks.backends) + sorted(pt_inputhooks.aliases))
34
33
35 backend_keys = sorted(pylabtools.backends.keys())
34 backend_keys = sorted(pylabtools.backends.keys())
36 backend_keys.insert(0, 'auto')
35 backend_keys.insert(0, 'auto')
37
36
38 shell_flags = {}
37 shell_flags = {}
39
38
40 addflag = lambda *args: shell_flags.update(boolean_flag(*args))
39 addflag = lambda *args: shell_flags.update(boolean_flag(*args))
41 addflag('autoindent', 'InteractiveShell.autoindent',
40 addflag('autoindent', 'InteractiveShell.autoindent',
42 'Turn on autoindenting.', 'Turn off autoindenting.'
41 'Turn on autoindenting.', 'Turn off autoindenting.'
43 )
42 )
44 addflag('automagic', 'InteractiveShell.automagic',
43 addflag('automagic', 'InteractiveShell.automagic',
45 """Turn on the auto calling of magic commands. Type %%magic at the
44 """Turn on the auto calling of magic commands. Type %%magic at the
46 IPython prompt for more information.""",
45 IPython prompt for more information.""",
47 'Turn off the auto calling of magic commands.'
46 'Turn off the auto calling of magic commands.'
48 )
47 )
49 addflag('pdb', 'InteractiveShell.pdb',
48 addflag('pdb', 'InteractiveShell.pdb',
50 "Enable auto calling the pdb debugger after every exception.",
49 "Enable auto calling the pdb debugger after every exception.",
51 "Disable auto calling the pdb debugger after every exception."
50 "Disable auto calling the pdb debugger after every exception."
52 )
51 )
53 addflag('pprint', 'PlainTextFormatter.pprint',
52 addflag('pprint', 'PlainTextFormatter.pprint',
54 "Enable auto pretty printing of results.",
53 "Enable auto pretty printing of results.",
55 "Disable auto pretty printing of results."
54 "Disable auto pretty printing of results."
56 )
55 )
57 addflag('color-info', 'InteractiveShell.color_info',
56 addflag('color-info', 'InteractiveShell.color_info',
58 """IPython can display information about objects via a set of functions,
57 """IPython can display information about objects via a set of functions,
59 and optionally can use colors for this, syntax highlighting
58 and optionally can use colors for this, syntax highlighting
60 source code and various other elements. This is on by default, but can cause
59 source code and various other elements. This is on by default, but can cause
61 problems with some pagers. If you see such problems, you can disable the
60 problems with some pagers. If you see such problems, you can disable the
62 colours.""",
61 colours.""",
63 "Disable using colors for info related things."
62 "Disable using colors for info related things."
64 )
63 )
65 addflag('ignore-cwd', 'InteractiveShellApp.ignore_cwd',
64 addflag('ignore-cwd', 'InteractiveShellApp.ignore_cwd',
66 "Exclude the current working directory from sys.path",
65 "Exclude the current working directory from sys.path",
67 "Include the current working directory in sys.path",
66 "Include the current working directory in sys.path",
68 )
67 )
69 nosep_config = Config()
68 nosep_config = Config()
70 nosep_config.InteractiveShell.separate_in = ''
69 nosep_config.InteractiveShell.separate_in = ''
71 nosep_config.InteractiveShell.separate_out = ''
70 nosep_config.InteractiveShell.separate_out = ''
72 nosep_config.InteractiveShell.separate_out2 = ''
71 nosep_config.InteractiveShell.separate_out2 = ''
73
72
74 shell_flags['nosep']=(nosep_config, "Eliminate all spacing between prompts.")
73 shell_flags['nosep']=(nosep_config, "Eliminate all spacing between prompts.")
75 shell_flags['pylab'] = (
74 shell_flags['pylab'] = (
76 {'InteractiveShellApp' : {'pylab' : 'auto'}},
75 {'InteractiveShellApp' : {'pylab' : 'auto'}},
77 """Pre-load matplotlib and numpy for interactive use with
76 """Pre-load matplotlib and numpy for interactive use with
78 the default matplotlib backend."""
77 the default matplotlib backend."""
79 )
78 )
80 shell_flags['matplotlib'] = (
79 shell_flags['matplotlib'] = (
81 {'InteractiveShellApp' : {'matplotlib' : 'auto'}},
80 {'InteractiveShellApp' : {'matplotlib' : 'auto'}},
82 """Configure matplotlib for interactive use with
81 """Configure matplotlib for interactive use with
83 the default matplotlib backend."""
82 the default matplotlib backend."""
84 )
83 )
85
84
86 # it's possible we don't want short aliases for *all* of these:
85 # it's possible we don't want short aliases for *all* of these:
87 shell_aliases = dict(
86 shell_aliases = dict(
88 autocall='InteractiveShell.autocall',
87 autocall='InteractiveShell.autocall',
89 colors='InteractiveShell.colors',
88 colors='InteractiveShell.colors',
90 logfile='InteractiveShell.logfile',
89 logfile='InteractiveShell.logfile',
91 logappend='InteractiveShell.logappend',
90 logappend='InteractiveShell.logappend',
92 c='InteractiveShellApp.code_to_run',
91 c='InteractiveShellApp.code_to_run',
93 m='InteractiveShellApp.module_to_run',
92 m='InteractiveShellApp.module_to_run',
94 ext="InteractiveShellApp.extra_extensions",
93 ext="InteractiveShellApp.extra_extensions",
95 gui='InteractiveShellApp.gui',
94 gui='InteractiveShellApp.gui',
96 pylab='InteractiveShellApp.pylab',
95 pylab='InteractiveShellApp.pylab',
97 matplotlib='InteractiveShellApp.matplotlib',
96 matplotlib='InteractiveShellApp.matplotlib',
98 )
97 )
99 shell_aliases['cache-size'] = 'InteractiveShell.cache_size'
98 shell_aliases['cache-size'] = 'InteractiveShell.cache_size'
100
99
101 #-----------------------------------------------------------------------------
100 #-----------------------------------------------------------------------------
102 # Main classes and functions
101 # Main classes and functions
103 #-----------------------------------------------------------------------------
102 #-----------------------------------------------------------------------------
104
103
105 class InteractiveShellApp(Configurable):
104 class InteractiveShellApp(Configurable):
106 """A Mixin for applications that start InteractiveShell instances.
105 """A Mixin for applications that start InteractiveShell instances.
107
106
108 Provides configurables for loading extensions and executing files
107 Provides configurables for loading extensions and executing files
109 as part of configuring a Shell environment.
108 as part of configuring a Shell environment.
110
109
111 The following methods should be called by the :meth:`initialize` method
110 The following methods should be called by the :meth:`initialize` method
112 of the subclass:
111 of the subclass:
113
112
114 - :meth:`init_path`
113 - :meth:`init_path`
115 - :meth:`init_shell` (to be implemented by the subclass)
114 - :meth:`init_shell` (to be implemented by the subclass)
116 - :meth:`init_gui_pylab`
115 - :meth:`init_gui_pylab`
117 - :meth:`init_extensions`
116 - :meth:`init_extensions`
118 - :meth:`init_code`
117 - :meth:`init_code`
119 """
118 """
120 extensions = List(Unicode(),
119 extensions = List(Unicode(),
121 help="A list of dotted module names of IPython extensions to load."
120 help="A list of dotted module names of IPython extensions to load."
122 ).tag(config=True)
121 ).tag(config=True)
123
122
124 extra_extensions = List(
123 extra_extensions = List(
125 DottedObjectName(),
124 DottedObjectName(),
126 help="""
125 help="""
127 Dotted module name(s) of one or more IPython extensions to load.
126 Dotted module name(s) of one or more IPython extensions to load.
128
127
129 For specifying extra extensions to load on the command-line.
128 For specifying extra extensions to load on the command-line.
130
129
131 .. versionadded:: 7.10
130 .. versionadded:: 7.10
132 """,
131 """,
133 ).tag(config=True)
132 ).tag(config=True)
134
133
135 reraise_ipython_extension_failures = Bool(False,
134 reraise_ipython_extension_failures = Bool(False,
136 help="Reraise exceptions encountered loading IPython extensions?",
135 help="Reraise exceptions encountered loading IPython extensions?",
137 ).tag(config=True)
136 ).tag(config=True)
138
137
139 # Extensions that are always loaded (not configurable)
138 # Extensions that are always loaded (not configurable)
140 default_extensions = List(Unicode(), [u'storemagic']).tag(config=False)
139 default_extensions = List(Unicode(), [u'storemagic']).tag(config=False)
141
140
142 hide_initial_ns = Bool(True,
141 hide_initial_ns = Bool(True,
143 help="""Should variables loaded at startup (by startup files, exec_lines, etc.)
142 help="""Should variables loaded at startup (by startup files, exec_lines, etc.)
144 be hidden from tools like %who?"""
143 be hidden from tools like %who?"""
145 ).tag(config=True)
144 ).tag(config=True)
146
145
147 exec_files = List(Unicode(),
146 exec_files = List(Unicode(),
148 help="""List of files to run at IPython startup."""
147 help="""List of files to run at IPython startup."""
149 ).tag(config=True)
148 ).tag(config=True)
150 exec_PYTHONSTARTUP = Bool(True,
149 exec_PYTHONSTARTUP = Bool(True,
151 help="""Run the file referenced by the PYTHONSTARTUP environment
150 help="""Run the file referenced by the PYTHONSTARTUP environment
152 variable at IPython startup."""
151 variable at IPython startup."""
153 ).tag(config=True)
152 ).tag(config=True)
154 file_to_run = Unicode('',
153 file_to_run = Unicode('',
155 help="""A file to be run""").tag(config=True)
154 help="""A file to be run""").tag(config=True)
156
155
157 exec_lines = List(Unicode(),
156 exec_lines = List(Unicode(),
158 help="""lines of code to run at IPython startup."""
157 help="""lines of code to run at IPython startup."""
159 ).tag(config=True)
158 ).tag(config=True)
160 code_to_run = Unicode('',
159 code_to_run = Unicode('',
161 help="Execute the given command string."
160 help="Execute the given command string."
162 ).tag(config=True)
161 ).tag(config=True)
163 module_to_run = Unicode('',
162 module_to_run = Unicode('',
164 help="Run the module as a script."
163 help="Run the module as a script."
165 ).tag(config=True)
164 ).tag(config=True)
166 gui = CaselessStrEnum(gui_keys, allow_none=True,
165 gui = CaselessStrEnum(gui_keys, allow_none=True,
167 help="Enable GUI event loop integration with any of {0}.".format(gui_keys)
166 help="Enable GUI event loop integration with any of {0}.".format(gui_keys)
168 ).tag(config=True)
167 ).tag(config=True)
169 matplotlib = CaselessStrEnum(backend_keys, allow_none=True,
168 matplotlib = CaselessStrEnum(backend_keys, allow_none=True,
170 help="""Configure matplotlib for interactive use with
169 help="""Configure matplotlib for interactive use with
171 the default matplotlib backend."""
170 the default matplotlib backend."""
172 ).tag(config=True)
171 ).tag(config=True)
173 pylab = CaselessStrEnum(backend_keys, allow_none=True,
172 pylab = CaselessStrEnum(backend_keys, allow_none=True,
174 help="""Pre-load matplotlib and numpy for interactive use,
173 help="""Pre-load matplotlib and numpy for interactive use,
175 selecting a particular matplotlib backend and loop integration.
174 selecting a particular matplotlib backend and loop integration.
176 """
175 """
177 ).tag(config=True)
176 ).tag(config=True)
178 pylab_import_all = Bool(True,
177 pylab_import_all = Bool(True,
179 help="""If true, IPython will populate the user namespace with numpy, pylab, etc.
178 help="""If true, IPython will populate the user namespace with numpy, pylab, etc.
180 and an ``import *`` is done from numpy and pylab, when using pylab mode.
179 and an ``import *`` is done from numpy and pylab, when using pylab mode.
181
180
182 When False, pylab mode should not import any names into the user namespace.
181 When False, pylab mode should not import any names into the user namespace.
183 """
182 """
184 ).tag(config=True)
183 ).tag(config=True)
185 ignore_cwd = Bool(
184 ignore_cwd = Bool(
186 False,
185 False,
187 help="""If True, IPython will not add the current working directory to sys.path.
186 help="""If True, IPython will not add the current working directory to sys.path.
188 When False, the current working directory is added to sys.path, allowing imports
187 When False, the current working directory is added to sys.path, allowing imports
189 of modules defined in the current directory."""
188 of modules defined in the current directory."""
190 ).tag(config=True)
189 ).tag(config=True)
191 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
190 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
192 allow_none=True)
191 allow_none=True)
193 # whether interact-loop should start
192 # whether interact-loop should start
194 interact = Bool(True)
193 interact = Bool(True)
195
194
196 user_ns = Instance(dict, args=None, allow_none=True)
195 user_ns = Instance(dict, args=None, allow_none=True)
197 @observe('user_ns')
196 @observe('user_ns')
198 def _user_ns_changed(self, change):
197 def _user_ns_changed(self, change):
199 if self.shell is not None:
198 if self.shell is not None:
200 self.shell.user_ns = change['new']
199 self.shell.user_ns = change['new']
201 self.shell.init_user_ns()
200 self.shell.init_user_ns()
202
201
203 def init_path(self):
202 def init_path(self):
204 """Add current working directory, '', to sys.path
203 """Add current working directory, '', to sys.path
205
204
206 Unlike Python's default, we insert before the first `site-packages`
205 Unlike Python's default, we insert before the first `site-packages`
207 or `dist-packages` directory,
206 or `dist-packages` directory,
208 so that it is after the standard library.
207 so that it is after the standard library.
209
208
210 .. versionchanged:: 7.2
209 .. versionchanged:: 7.2
211 Try to insert after the standard library, instead of first.
210 Try to insert after the standard library, instead of first.
212 .. versionchanged:: 8.0
211 .. versionchanged:: 8.0
213 Allow optionally not including the current directory in sys.path
212 Allow optionally not including the current directory in sys.path
214 """
213 """
215 if '' in sys.path or self.ignore_cwd:
214 if '' in sys.path or self.ignore_cwd:
216 return
215 return
217 for idx, path in enumerate(sys.path):
216 for idx, path in enumerate(sys.path):
218 parent, last_part = os.path.split(path)
217 parent, last_part = os.path.split(path)
219 if last_part in {'site-packages', 'dist-packages'}:
218 if last_part in {'site-packages', 'dist-packages'}:
220 break
219 break
221 else:
220 else:
222 # no site-packages or dist-packages found (?!)
221 # no site-packages or dist-packages found (?!)
223 # back to original behavior of inserting at the front
222 # back to original behavior of inserting at the front
224 idx = 0
223 idx = 0
225 sys.path.insert(idx, '')
224 sys.path.insert(idx, '')
226
225
227 def init_shell(self):
226 def init_shell(self):
228 raise NotImplementedError("Override in subclasses")
227 raise NotImplementedError("Override in subclasses")
229
228
230 def init_gui_pylab(self):
229 def init_gui_pylab(self):
231 """Enable GUI event loop integration, taking pylab into account."""
230 """Enable GUI event loop integration, taking pylab into account."""
232 enable = False
231 enable = False
233 shell = self.shell
232 shell = self.shell
234 if self.pylab:
233 if self.pylab:
235 enable = lambda key: shell.enable_pylab(key, import_all=self.pylab_import_all)
234 enable = lambda key: shell.enable_pylab(key, import_all=self.pylab_import_all)
236 key = self.pylab
235 key = self.pylab
237 elif self.matplotlib:
236 elif self.matplotlib:
238 enable = shell.enable_matplotlib
237 enable = shell.enable_matplotlib
239 key = self.matplotlib
238 key = self.matplotlib
240 elif self.gui:
239 elif self.gui:
241 enable = shell.enable_gui
240 enable = shell.enable_gui
242 key = self.gui
241 key = self.gui
243
242
244 if not enable:
243 if not enable:
245 return
244 return
246
245
247 try:
246 try:
248 r = enable(key)
247 r = enable(key)
249 except ImportError:
248 except ImportError:
250 self.log.warning("Eventloop or matplotlib integration failed. Is matplotlib installed?")
249 self.log.warning("Eventloop or matplotlib integration failed. Is matplotlib installed?")
251 self.shell.showtraceback()
250 self.shell.showtraceback()
252 return
251 return
253 except Exception:
252 except Exception:
254 self.log.warning("GUI event loop or pylab initialization failed")
253 self.log.warning("GUI event loop or pylab initialization failed")
255 self.shell.showtraceback()
254 self.shell.showtraceback()
256 return
255 return
257
256
258 if isinstance(r, tuple):
257 if isinstance(r, tuple):
259 gui, backend = r[:2]
258 gui, backend = r[:2]
260 self.log.info("Enabling GUI event loop integration, "
259 self.log.info("Enabling GUI event loop integration, "
261 "eventloop=%s, matplotlib=%s", gui, backend)
260 "eventloop=%s, matplotlib=%s", gui, backend)
262 if key == "auto":
261 if key == "auto":
263 print("Using matplotlib backend: %s" % backend)
262 print("Using matplotlib backend: %s" % backend)
264 else:
263 else:
265 gui = r
264 gui = r
266 self.log.info("Enabling GUI event loop integration, "
265 self.log.info("Enabling GUI event loop integration, "
267 "eventloop=%s", gui)
266 "eventloop=%s", gui)
268
267
269 def init_extensions(self):
268 def init_extensions(self):
270 """Load all IPython extensions in IPythonApp.extensions.
269 """Load all IPython extensions in IPythonApp.extensions.
271
270
272 This uses the :meth:`ExtensionManager.load_extensions` to load all
271 This uses the :meth:`ExtensionManager.load_extensions` to load all
273 the extensions listed in ``self.extensions``.
272 the extensions listed in ``self.extensions``.
274 """
273 """
275 try:
274 try:
276 self.log.debug("Loading IPython extensions...")
275 self.log.debug("Loading IPython extensions...")
277 extensions = (
276 extensions = (
278 self.default_extensions + self.extensions + self.extra_extensions
277 self.default_extensions + self.extensions + self.extra_extensions
279 )
278 )
280 for ext in extensions:
279 for ext in extensions:
281 try:
280 try:
282 self.log.info("Loading IPython extension: %s" % ext)
281 self.log.info("Loading IPython extension: %s" % ext)
283 self.shell.extension_manager.load_extension(ext)
282 self.shell.extension_manager.load_extension(ext)
284 except:
283 except:
285 if self.reraise_ipython_extension_failures:
284 if self.reraise_ipython_extension_failures:
286 raise
285 raise
287 msg = ("Error in loading extension: {ext}\n"
286 msg = ("Error in loading extension: {ext}\n"
288 "Check your config files in {location}".format(
287 "Check your config files in {location}".format(
289 ext=ext,
288 ext=ext,
290 location=self.profile_dir.location
289 location=self.profile_dir.location
291 ))
290 ))
292 self.log.warning(msg, exc_info=True)
291 self.log.warning(msg, exc_info=True)
293 except:
292 except:
294 if self.reraise_ipython_extension_failures:
293 if self.reraise_ipython_extension_failures:
295 raise
294 raise
296 self.log.warning("Unknown error in loading extensions:", exc_info=True)
295 self.log.warning("Unknown error in loading extensions:", exc_info=True)
297
296
298 def init_code(self):
297 def init_code(self):
299 """run the pre-flight code, specified via exec_lines"""
298 """run the pre-flight code, specified via exec_lines"""
300 self._run_startup_files()
299 self._run_startup_files()
301 self._run_exec_lines()
300 self._run_exec_lines()
302 self._run_exec_files()
301 self._run_exec_files()
303
302
304 # Hide variables defined here from %who etc.
303 # Hide variables defined here from %who etc.
305 if self.hide_initial_ns:
304 if self.hide_initial_ns:
306 self.shell.user_ns_hidden.update(self.shell.user_ns)
305 self.shell.user_ns_hidden.update(self.shell.user_ns)
307
306
308 # command-line execution (ipython -i script.py, ipython -m module)
307 # command-line execution (ipython -i script.py, ipython -m module)
309 # should *not* be excluded from %whos
308 # should *not* be excluded from %whos
310 self._run_cmd_line_code()
309 self._run_cmd_line_code()
311 self._run_module()
310 self._run_module()
312
311
313 # flush output, so itwon't be attached to the first cell
312 # flush output, so itwon't be attached to the first cell
314 sys.stdout.flush()
313 sys.stdout.flush()
315 sys.stderr.flush()
314 sys.stderr.flush()
316 self.shell._sys_modules_keys = set(sys.modules.keys())
315 self.shell._sys_modules_keys = set(sys.modules.keys())
317
316
318 def _run_exec_lines(self):
317 def _run_exec_lines(self):
319 """Run lines of code in IPythonApp.exec_lines in the user's namespace."""
318 """Run lines of code in IPythonApp.exec_lines in the user's namespace."""
320 if not self.exec_lines:
319 if not self.exec_lines:
321 return
320 return
322 try:
321 try:
323 self.log.debug("Running code from IPythonApp.exec_lines...")
322 self.log.debug("Running code from IPythonApp.exec_lines...")
324 for line in self.exec_lines:
323 for line in self.exec_lines:
325 try:
324 try:
326 self.log.info("Running code in user namespace: %s" %
325 self.log.info("Running code in user namespace: %s" %
327 line)
326 line)
328 self.shell.run_cell(line, store_history=False)
327 self.shell.run_cell(line, store_history=False)
329 except:
328 except:
330 self.log.warning("Error in executing line in user "
329 self.log.warning("Error in executing line in user "
331 "namespace: %s" % line)
330 "namespace: %s" % line)
332 self.shell.showtraceback()
331 self.shell.showtraceback()
333 except:
332 except:
334 self.log.warning("Unknown error in handling IPythonApp.exec_lines:")
333 self.log.warning("Unknown error in handling IPythonApp.exec_lines:")
335 self.shell.showtraceback()
334 self.shell.showtraceback()
336
335
337 def _exec_file(self, fname, shell_futures=False):
336 def _exec_file(self, fname, shell_futures=False):
338 try:
337 try:
339 full_filename = filefind(fname, [u'.', self.ipython_dir])
338 full_filename = filefind(fname, [u'.', self.ipython_dir])
340 except IOError:
339 except IOError:
341 self.log.warning("File not found: %r"%fname)
340 self.log.warning("File not found: %r"%fname)
342 return
341 return
343 # Make sure that the running script gets a proper sys.argv as if it
342 # Make sure that the running script gets a proper sys.argv as if it
344 # were run from a system shell.
343 # were run from a system shell.
345 save_argv = sys.argv
344 save_argv = sys.argv
346 sys.argv = [full_filename] + self.extra_args[1:]
345 sys.argv = [full_filename] + self.extra_args[1:]
347 try:
346 try:
348 if os.path.isfile(full_filename):
347 if os.path.isfile(full_filename):
349 self.log.info("Running file in user namespace: %s" %
348 self.log.info("Running file in user namespace: %s" %
350 full_filename)
349 full_filename)
351 # Ensure that __file__ is always defined to match Python
350 # Ensure that __file__ is always defined to match Python
352 # behavior.
351 # behavior.
353 with preserve_keys(self.shell.user_ns, '__file__'):
352 with preserve_keys(self.shell.user_ns, '__file__'):
354 self.shell.user_ns['__file__'] = fname
353 self.shell.user_ns['__file__'] = fname
355 if full_filename.endswith('.ipy') or full_filename.endswith('.ipynb'):
354 if full_filename.endswith('.ipy') or full_filename.endswith('.ipynb'):
356 self.shell.safe_execfile_ipy(full_filename,
355 self.shell.safe_execfile_ipy(full_filename,
357 shell_futures=shell_futures)
356 shell_futures=shell_futures)
358 else:
357 else:
359 # default to python, even without extension
358 # default to python, even without extension
360 self.shell.safe_execfile(full_filename,
359 self.shell.safe_execfile(full_filename,
361 self.shell.user_ns,
360 self.shell.user_ns,
362 shell_futures=shell_futures,
361 shell_futures=shell_futures,
363 raise_exceptions=True)
362 raise_exceptions=True)
364 finally:
363 finally:
365 sys.argv = save_argv
364 sys.argv = save_argv
366
365
367 def _run_startup_files(self):
366 def _run_startup_files(self):
368 """Run files from profile startup directory"""
367 """Run files from profile startup directory"""
369 startup_dirs = [self.profile_dir.startup_dir] + [
368 startup_dirs = [self.profile_dir.startup_dir] + [
370 os.path.join(p, 'startup') for p in chain(ENV_CONFIG_DIRS, SYSTEM_CONFIG_DIRS)
369 os.path.join(p, 'startup') for p in chain(ENV_CONFIG_DIRS, SYSTEM_CONFIG_DIRS)
371 ]
370 ]
372 startup_files = []
371 startup_files = []
373
372
374 if self.exec_PYTHONSTARTUP and os.environ.get('PYTHONSTARTUP', False) and \
373 if self.exec_PYTHONSTARTUP and os.environ.get('PYTHONSTARTUP', False) and \
375 not (self.file_to_run or self.code_to_run or self.module_to_run):
374 not (self.file_to_run or self.code_to_run or self.module_to_run):
376 python_startup = os.environ['PYTHONSTARTUP']
375 python_startup = os.environ['PYTHONSTARTUP']
377 self.log.debug("Running PYTHONSTARTUP file %s...", python_startup)
376 self.log.debug("Running PYTHONSTARTUP file %s...", python_startup)
378 try:
377 try:
379 self._exec_file(python_startup)
378 self._exec_file(python_startup)
380 except:
379 except:
381 self.log.warning("Unknown error in handling PYTHONSTARTUP file %s:", python_startup)
380 self.log.warning("Unknown error in handling PYTHONSTARTUP file %s:", python_startup)
382 self.shell.showtraceback()
381 self.shell.showtraceback()
383 for startup_dir in startup_dirs[::-1]:
382 for startup_dir in startup_dirs[::-1]:
384 startup_files += glob.glob(os.path.join(startup_dir, '*.py'))
383 startup_files += glob.glob(os.path.join(startup_dir, '*.py'))
385 startup_files += glob.glob(os.path.join(startup_dir, '*.ipy'))
384 startup_files += glob.glob(os.path.join(startup_dir, '*.ipy'))
386 if not startup_files:
385 if not startup_files:
387 return
386 return
388
387
389 self.log.debug("Running startup files from %s...", startup_dir)
388 self.log.debug("Running startup files from %s...", startup_dir)
390 try:
389 try:
391 for fname in sorted(startup_files):
390 for fname in sorted(startup_files):
392 self._exec_file(fname)
391 self._exec_file(fname)
393 except:
392 except:
394 self.log.warning("Unknown error in handling startup files:")
393 self.log.warning("Unknown error in handling startup files:")
395 self.shell.showtraceback()
394 self.shell.showtraceback()
396
395
397 def _run_exec_files(self):
396 def _run_exec_files(self):
398 """Run files from IPythonApp.exec_files"""
397 """Run files from IPythonApp.exec_files"""
399 if not self.exec_files:
398 if not self.exec_files:
400 return
399 return
401
400
402 self.log.debug("Running files in IPythonApp.exec_files...")
401 self.log.debug("Running files in IPythonApp.exec_files...")
403 try:
402 try:
404 for fname in self.exec_files:
403 for fname in self.exec_files:
405 self._exec_file(fname)
404 self._exec_file(fname)
406 except:
405 except:
407 self.log.warning("Unknown error in handling IPythonApp.exec_files:")
406 self.log.warning("Unknown error in handling IPythonApp.exec_files:")
408 self.shell.showtraceback()
407 self.shell.showtraceback()
409
408
410 def _run_cmd_line_code(self):
409 def _run_cmd_line_code(self):
411 """Run code or file specified at the command-line"""
410 """Run code or file specified at the command-line"""
412 if self.code_to_run:
411 if self.code_to_run:
413 line = self.code_to_run
412 line = self.code_to_run
414 try:
413 try:
415 self.log.info("Running code given at command line (c=): %s" %
414 self.log.info("Running code given at command line (c=): %s" %
416 line)
415 line)
417 self.shell.run_cell(line, store_history=False)
416 self.shell.run_cell(line, store_history=False)
418 except:
417 except:
419 self.log.warning("Error in executing line in user namespace: %s" %
418 self.log.warning("Error in executing line in user namespace: %s" %
420 line)
419 line)
421 self.shell.showtraceback()
420 self.shell.showtraceback()
422 if not self.interact:
421 if not self.interact:
423 self.exit(1)
422 self.exit(1)
424
423
425 # Like Python itself, ignore the second if the first of these is present
424 # Like Python itself, ignore the second if the first of these is present
426 elif self.file_to_run:
425 elif self.file_to_run:
427 fname = self.file_to_run
426 fname = self.file_to_run
428 if os.path.isdir(fname):
427 if os.path.isdir(fname):
429 fname = os.path.join(fname, "__main__.py")
428 fname = os.path.join(fname, "__main__.py")
430 if not os.path.exists(fname):
429 if not os.path.exists(fname):
431 self.log.warning("File '%s' doesn't exist", fname)
430 self.log.warning("File '%s' doesn't exist", fname)
432 if not self.interact:
431 if not self.interact:
433 self.exit(2)
432 self.exit(2)
434 try:
433 try:
435 self._exec_file(fname, shell_futures=True)
434 self._exec_file(fname, shell_futures=True)
436 except:
435 except:
437 self.shell.showtraceback(tb_offset=4)
436 self.shell.showtraceback(tb_offset=4)
438 if not self.interact:
437 if not self.interact:
439 self.exit(1)
438 self.exit(1)
440
439
441 def _run_module(self):
440 def _run_module(self):
442 """Run module specified at the command-line."""
441 """Run module specified at the command-line."""
443 if self.module_to_run:
442 if self.module_to_run:
444 # Make sure that the module gets a proper sys.argv as if it were
443 # Make sure that the module gets a proper sys.argv as if it were
445 # run using `python -m`.
444 # run using `python -m`.
446 save_argv = sys.argv
445 save_argv = sys.argv
447 sys.argv = [sys.executable] + self.extra_args
446 sys.argv = [sys.executable] + self.extra_args
448 try:
447 try:
449 self.shell.safe_run_module(self.module_to_run,
448 self.shell.safe_run_module(self.module_to_run,
450 self.shell.user_ns)
449 self.shell.user_ns)
451 finally:
450 finally:
452 sys.argv = save_argv
451 sys.argv = save_argv
@@ -1,71 +1,68 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Tests for the compilerop module.
2 """Tests for the compilerop module.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2010-2011 The IPython Development Team.
5 # Copyright (C) 2010-2011 The IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the BSD License.
7 # Distributed under the terms of the BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 # Stdlib imports
16 # Stdlib imports
17 import linecache
17 import linecache
18 import sys
18 import sys
19
19
20 # Third-party imports
21 import pytest
22
23 # Our own imports
20 # Our own imports
24 from IPython.core import compilerop
21 from IPython.core import compilerop
25
22
26 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
27 # Test functions
24 # Test functions
28 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
29
26
30 def test_code_name():
27 def test_code_name():
31 code = 'x=1'
28 code = 'x=1'
32 name = compilerop.code_name(code)
29 name = compilerop.code_name(code)
33 assert name.startswith("<ipython-input-0")
30 assert name.startswith("<ipython-input-0")
34
31
35
32
36 def test_code_name2():
33 def test_code_name2():
37 code = 'x=1'
34 code = 'x=1'
38 name = compilerop.code_name(code, 9)
35 name = compilerop.code_name(code, 9)
39 assert name.startswith("<ipython-input-9")
36 assert name.startswith("<ipython-input-9")
40
37
41
38
42 def test_cache():
39 def test_cache():
43 """Test the compiler correctly compiles and caches inputs
40 """Test the compiler correctly compiles and caches inputs
44 """
41 """
45 cp = compilerop.CachingCompiler()
42 cp = compilerop.CachingCompiler()
46 ncache = len(linecache.cache)
43 ncache = len(linecache.cache)
47 cp.cache('x=1')
44 cp.cache('x=1')
48 assert len(linecache.cache) > ncache
45 assert len(linecache.cache) > ncache
49
46
50 def test_proper_default_encoding():
47 def test_proper_default_encoding():
51 # Check we're in a proper Python 2 environment (some imports, such
48 # Check we're in a proper Python 2 environment (some imports, such
52 # as GTK, can change the default encoding, which can hide bugs.)
49 # as GTK, can change the default encoding, which can hide bugs.)
53 assert sys.getdefaultencoding() == "utf-8"
50 assert sys.getdefaultencoding() == "utf-8"
54
51
55 def test_cache_unicode():
52 def test_cache_unicode():
56 cp = compilerop.CachingCompiler()
53 cp = compilerop.CachingCompiler()
57 ncache = len(linecache.cache)
54 ncache = len(linecache.cache)
58 cp.cache(u"t = 'žćčőđ'")
55 cp.cache(u"t = 'žćčőđ'")
59 assert len(linecache.cache) > ncache
56 assert len(linecache.cache) > ncache
60
57
61 def test_compiler_check_cache():
58 def test_compiler_check_cache():
62 """Test the compiler properly manages the cache.
59 """Test the compiler properly manages the cache.
63 """
60 """
64 # Rather simple-minded tests that just exercise the API
61 # Rather simple-minded tests that just exercise the API
65 cp = compilerop.CachingCompiler()
62 cp = compilerop.CachingCompiler()
66 cp.cache('x=1', 99)
63 cp.cache('x=1', 99)
67 # Ensure now that after clearing the cache, our entries survive
64 # Ensure now that after clearing the cache, our entries survive
68 linecache.checkcache()
65 linecache.checkcache()
69 assert any(
66 assert any(
70 k.startswith("<ipython-input-99") for k in linecache.cache
67 k.startswith("<ipython-input-99") for k in linecache.cache
71 ), "Entry for input-99 missing from linecache"
68 ), "Entry for input-99 missing from linecache"
@@ -1,576 +1,575 b''
1 """Tests for debugging machinery.
1 """Tests for debugging machinery.
2 """
2 """
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 bdb
8 import builtins
7 import builtins
9 import os
8 import os
10 import sys
9 import sys
11 import platform
10 import platform
12
11
13 from tempfile import NamedTemporaryFile
12 from tempfile import NamedTemporaryFile
14 from textwrap import dedent
13 from textwrap import dedent
15 from unittest.mock import patch
14 from unittest.mock import patch
16
15
17 from IPython.core import debugger
16 from IPython.core import debugger
18 from IPython.testing import IPYTHON_TESTING_TIMEOUT_SCALE
17 from IPython.testing import IPYTHON_TESTING_TIMEOUT_SCALE
19 from IPython.testing.decorators import skip_win32
18 from IPython.testing.decorators import skip_win32
20 import pytest
19 import pytest
21
20
22 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
23 # Helper classes, from CPython's Pdb test suite
22 # Helper classes, from CPython's Pdb test suite
24 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
25
24
26 class _FakeInput(object):
25 class _FakeInput(object):
27 """
26 """
28 A fake input stream for pdb's interactive debugger. Whenever a
27 A fake input stream for pdb's interactive debugger. Whenever a
29 line is read, print it (to simulate the user typing it), and then
28 line is read, print it (to simulate the user typing it), and then
30 return it. The set of lines to return is specified in the
29 return it. The set of lines to return is specified in the
31 constructor; they should not have trailing newlines.
30 constructor; they should not have trailing newlines.
32 """
31 """
33 def __init__(self, lines):
32 def __init__(self, lines):
34 self.lines = iter(lines)
33 self.lines = iter(lines)
35
34
36 def readline(self):
35 def readline(self):
37 line = next(self.lines)
36 line = next(self.lines)
38 print(line)
37 print(line)
39 return line+'\n'
38 return line+'\n'
40
39
41 class PdbTestInput(object):
40 class PdbTestInput(object):
42 """Context manager that makes testing Pdb in doctests easier."""
41 """Context manager that makes testing Pdb in doctests easier."""
43
42
44 def __init__(self, input):
43 def __init__(self, input):
45 self.input = input
44 self.input = input
46
45
47 def __enter__(self):
46 def __enter__(self):
48 self.real_stdin = sys.stdin
47 self.real_stdin = sys.stdin
49 sys.stdin = _FakeInput(self.input)
48 sys.stdin = _FakeInput(self.input)
50
49
51 def __exit__(self, *exc):
50 def __exit__(self, *exc):
52 sys.stdin = self.real_stdin
51 sys.stdin = self.real_stdin
53
52
54 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
55 # Tests
54 # Tests
56 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
57
56
58 def test_ipdb_magics():
57 def test_ipdb_magics():
59 '''Test calling some IPython magics from ipdb.
58 '''Test calling some IPython magics from ipdb.
60
59
61 First, set up some test functions and classes which we can inspect.
60 First, set up some test functions and classes which we can inspect.
62
61
63 >>> class ExampleClass(object):
62 >>> class ExampleClass(object):
64 ... """Docstring for ExampleClass."""
63 ... """Docstring for ExampleClass."""
65 ... def __init__(self):
64 ... def __init__(self):
66 ... """Docstring for ExampleClass.__init__"""
65 ... """Docstring for ExampleClass.__init__"""
67 ... pass
66 ... pass
68 ... def __str__(self):
67 ... def __str__(self):
69 ... return "ExampleClass()"
68 ... return "ExampleClass()"
70
69
71 >>> def example_function(x, y, z="hello"):
70 >>> def example_function(x, y, z="hello"):
72 ... """Docstring for example_function."""
71 ... """Docstring for example_function."""
73 ... pass
72 ... pass
74
73
75 >>> old_trace = sys.gettrace()
74 >>> old_trace = sys.gettrace()
76
75
77 Create a function which triggers ipdb.
76 Create a function which triggers ipdb.
78
77
79 >>> def trigger_ipdb():
78 >>> def trigger_ipdb():
80 ... a = ExampleClass()
79 ... a = ExampleClass()
81 ... debugger.Pdb().set_trace()
80 ... debugger.Pdb().set_trace()
82
81
83 >>> with PdbTestInput([
82 >>> with PdbTestInput([
84 ... 'pdef example_function',
83 ... 'pdef example_function',
85 ... 'pdoc ExampleClass',
84 ... 'pdoc ExampleClass',
86 ... 'up',
85 ... 'up',
87 ... 'down',
86 ... 'down',
88 ... 'list',
87 ... 'list',
89 ... 'pinfo a',
88 ... 'pinfo a',
90 ... 'll',
89 ... 'll',
91 ... 'continue',
90 ... 'continue',
92 ... ]):
91 ... ]):
93 ... trigger_ipdb()
92 ... trigger_ipdb()
94 --Return--
93 --Return--
95 None
94 None
96 > <doctest ...>(3)trigger_ipdb()
95 > <doctest ...>(3)trigger_ipdb()
97 1 def trigger_ipdb():
96 1 def trigger_ipdb():
98 2 a = ExampleClass()
97 2 a = ExampleClass()
99 ----> 3 debugger.Pdb().set_trace()
98 ----> 3 debugger.Pdb().set_trace()
100 <BLANKLINE>
99 <BLANKLINE>
101 ipdb> pdef example_function
100 ipdb> pdef example_function
102 example_function(x, y, z='hello')
101 example_function(x, y, z='hello')
103 ipdb> pdoc ExampleClass
102 ipdb> pdoc ExampleClass
104 Class docstring:
103 Class docstring:
105 Docstring for ExampleClass.
104 Docstring for ExampleClass.
106 Init docstring:
105 Init docstring:
107 Docstring for ExampleClass.__init__
106 Docstring for ExampleClass.__init__
108 ipdb> up
107 ipdb> up
109 > <doctest ...>(11)<module>()
108 > <doctest ...>(11)<module>()
110 7 'pinfo a',
109 7 'pinfo a',
111 8 'll',
110 8 'll',
112 9 'continue',
111 9 'continue',
113 10 ]):
112 10 ]):
114 ---> 11 trigger_ipdb()
113 ---> 11 trigger_ipdb()
115 <BLANKLINE>
114 <BLANKLINE>
116 ipdb> down
115 ipdb> down
117 None
116 None
118 > <doctest ...>(3)trigger_ipdb()
117 > <doctest ...>(3)trigger_ipdb()
119 1 def trigger_ipdb():
118 1 def trigger_ipdb():
120 2 a = ExampleClass()
119 2 a = ExampleClass()
121 ----> 3 debugger.Pdb().set_trace()
120 ----> 3 debugger.Pdb().set_trace()
122 <BLANKLINE>
121 <BLANKLINE>
123 ipdb> list
122 ipdb> list
124 1 def trigger_ipdb():
123 1 def trigger_ipdb():
125 2 a = ExampleClass()
124 2 a = ExampleClass()
126 ----> 3 debugger.Pdb().set_trace()
125 ----> 3 debugger.Pdb().set_trace()
127 <BLANKLINE>
126 <BLANKLINE>
128 ipdb> pinfo a
127 ipdb> pinfo a
129 Type: ExampleClass
128 Type: ExampleClass
130 String form: ExampleClass()
129 String form: ExampleClass()
131 Namespace: Local...
130 Namespace: Local...
132 Docstring: Docstring for ExampleClass.
131 Docstring: Docstring for ExampleClass.
133 Init docstring: Docstring for ExampleClass.__init__
132 Init docstring: Docstring for ExampleClass.__init__
134 ipdb> ll
133 ipdb> ll
135 1 def trigger_ipdb():
134 1 def trigger_ipdb():
136 2 a = ExampleClass()
135 2 a = ExampleClass()
137 ----> 3 debugger.Pdb().set_trace()
136 ----> 3 debugger.Pdb().set_trace()
138 <BLANKLINE>
137 <BLANKLINE>
139 ipdb> continue
138 ipdb> continue
140
139
141 Restore previous trace function, e.g. for coverage.py
140 Restore previous trace function, e.g. for coverage.py
142
141
143 >>> sys.settrace(old_trace)
142 >>> sys.settrace(old_trace)
144 '''
143 '''
145
144
146 def test_ipdb_magics2():
145 def test_ipdb_magics2():
147 '''Test ipdb with a very short function.
146 '''Test ipdb with a very short function.
148
147
149 >>> old_trace = sys.gettrace()
148 >>> old_trace = sys.gettrace()
150
149
151 >>> def bar():
150 >>> def bar():
152 ... pass
151 ... pass
153
152
154 Run ipdb.
153 Run ipdb.
155
154
156 >>> with PdbTestInput([
155 >>> with PdbTestInput([
157 ... 'continue',
156 ... 'continue',
158 ... ]):
157 ... ]):
159 ... debugger.Pdb().runcall(bar)
158 ... debugger.Pdb().runcall(bar)
160 > <doctest ...>(2)bar()
159 > <doctest ...>(2)bar()
161 1 def bar():
160 1 def bar():
162 ----> 2 pass
161 ----> 2 pass
163 <BLANKLINE>
162 <BLANKLINE>
164 ipdb> continue
163 ipdb> continue
165
164
166 Restore previous trace function, e.g. for coverage.py
165 Restore previous trace function, e.g. for coverage.py
167
166
168 >>> sys.settrace(old_trace)
167 >>> sys.settrace(old_trace)
169 '''
168 '''
170
169
171 def can_quit():
170 def can_quit():
172 '''Test that quit work in ipydb
171 '''Test that quit work in ipydb
173
172
174 >>> old_trace = sys.gettrace()
173 >>> old_trace = sys.gettrace()
175
174
176 >>> def bar():
175 >>> def bar():
177 ... pass
176 ... pass
178
177
179 >>> with PdbTestInput([
178 >>> with PdbTestInput([
180 ... 'quit',
179 ... 'quit',
181 ... ]):
180 ... ]):
182 ... debugger.Pdb().runcall(bar)
181 ... debugger.Pdb().runcall(bar)
183 > <doctest ...>(2)bar()
182 > <doctest ...>(2)bar()
184 1 def bar():
183 1 def bar():
185 ----> 2 pass
184 ----> 2 pass
186 <BLANKLINE>
185 <BLANKLINE>
187 ipdb> quit
186 ipdb> quit
188
187
189 Restore previous trace function, e.g. for coverage.py
188 Restore previous trace function, e.g. for coverage.py
190
189
191 >>> sys.settrace(old_trace)
190 >>> sys.settrace(old_trace)
192 '''
191 '''
193
192
194
193
195 def can_exit():
194 def can_exit():
196 '''Test that quit work in ipydb
195 '''Test that quit work in ipydb
197
196
198 >>> old_trace = sys.gettrace()
197 >>> old_trace = sys.gettrace()
199
198
200 >>> def bar():
199 >>> def bar():
201 ... pass
200 ... pass
202
201
203 >>> with PdbTestInput([
202 >>> with PdbTestInput([
204 ... 'exit',
203 ... 'exit',
205 ... ]):
204 ... ]):
206 ... debugger.Pdb().runcall(bar)
205 ... debugger.Pdb().runcall(bar)
207 > <doctest ...>(2)bar()
206 > <doctest ...>(2)bar()
208 1 def bar():
207 1 def bar():
209 ----> 2 pass
208 ----> 2 pass
210 <BLANKLINE>
209 <BLANKLINE>
211 ipdb> exit
210 ipdb> exit
212
211
213 Restore previous trace function, e.g. for coverage.py
212 Restore previous trace function, e.g. for coverage.py
214
213
215 >>> sys.settrace(old_trace)
214 >>> sys.settrace(old_trace)
216 '''
215 '''
217
216
218
217
219 def test_interruptible_core_debugger():
218 def test_interruptible_core_debugger():
220 """The debugger can be interrupted.
219 """The debugger can be interrupted.
221
220
222 The presumption is there is some mechanism that causes a KeyboardInterrupt
221 The presumption is there is some mechanism that causes a KeyboardInterrupt
223 (this is implemented in ipykernel). We want to ensure the
222 (this is implemented in ipykernel). We want to ensure the
224 KeyboardInterrupt cause debugging to cease.
223 KeyboardInterrupt cause debugging to cease.
225 """
224 """
226 def raising_input(msg="", called=[0]):
225 def raising_input(msg="", called=[0]):
227 called[0] += 1
226 called[0] += 1
228 assert called[0] == 1, "input() should only be called once!"
227 assert called[0] == 1, "input() should only be called once!"
229 raise KeyboardInterrupt()
228 raise KeyboardInterrupt()
230
229
231 tracer_orig = sys.gettrace()
230 tracer_orig = sys.gettrace()
232 try:
231 try:
233 with patch.object(builtins, "input", raising_input):
232 with patch.object(builtins, "input", raising_input):
234 debugger.InterruptiblePdb().set_trace()
233 debugger.InterruptiblePdb().set_trace()
235 # The way this test will fail is by set_trace() never exiting,
234 # The way this test will fail is by set_trace() never exiting,
236 # resulting in a timeout by the test runner. The alternative
235 # resulting in a timeout by the test runner. The alternative
237 # implementation would involve a subprocess, but that adds issues
236 # implementation would involve a subprocess, but that adds issues
238 # with interrupting subprocesses that are rather complex, so it's
237 # with interrupting subprocesses that are rather complex, so it's
239 # simpler just to do it this way.
238 # simpler just to do it this way.
240 finally:
239 finally:
241 # restore the original trace function
240 # restore the original trace function
242 sys.settrace(tracer_orig)
241 sys.settrace(tracer_orig)
243
242
244
243
245 @skip_win32
244 @skip_win32
246 def test_xmode_skip():
245 def test_xmode_skip():
247 """that xmode skip frames
246 """that xmode skip frames
248
247
249 Not as a doctest as pytest does not run doctests.
248 Not as a doctest as pytest does not run doctests.
250 """
249 """
251 import pexpect
250 import pexpect
252 env = os.environ.copy()
251 env = os.environ.copy()
253 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
252 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
254
253
255 child = pexpect.spawn(
254 child = pexpect.spawn(
256 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
255 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
257 )
256 )
258 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
257 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
259
258
260 child.expect("IPython")
259 child.expect("IPython")
261 child.expect("\n")
260 child.expect("\n")
262 child.expect_exact("In [1]")
261 child.expect_exact("In [1]")
263
262
264 block = dedent(
263 block = dedent(
265 """
264 """
266 def f():
265 def f():
267 __tracebackhide__ = True
266 __tracebackhide__ = True
268 g()
267 g()
269
268
270 def g():
269 def g():
271 raise ValueError
270 raise ValueError
272
271
273 f()
272 f()
274 """
273 """
275 )
274 )
276
275
277 for line in block.splitlines():
276 for line in block.splitlines():
278 child.sendline(line)
277 child.sendline(line)
279 child.expect_exact(line)
278 child.expect_exact(line)
280 child.expect_exact("skipping")
279 child.expect_exact("skipping")
281
280
282 block = dedent(
281 block = dedent(
283 """
282 """
284 def f():
283 def f():
285 __tracebackhide__ = True
284 __tracebackhide__ = True
286 g()
285 g()
287
286
288 def g():
287 def g():
289 from IPython.core.debugger import set_trace
288 from IPython.core.debugger import set_trace
290 set_trace()
289 set_trace()
291
290
292 f()
291 f()
293 """
292 """
294 )
293 )
295
294
296 for line in block.splitlines():
295 for line in block.splitlines():
297 child.sendline(line)
296 child.sendline(line)
298 child.expect_exact(line)
297 child.expect_exact(line)
299
298
300 child.expect("ipdb>")
299 child.expect("ipdb>")
301 child.sendline("w")
300 child.sendline("w")
302 child.expect("hidden")
301 child.expect("hidden")
303 child.expect("ipdb>")
302 child.expect("ipdb>")
304 child.sendline("skip_hidden false")
303 child.sendline("skip_hidden false")
305 child.sendline("w")
304 child.sendline("w")
306 child.expect("__traceba")
305 child.expect("__traceba")
307 child.expect("ipdb>")
306 child.expect("ipdb>")
308
307
309 child.close()
308 child.close()
310
309
311
310
312 skip_decorators_blocks = (
311 skip_decorators_blocks = (
313 """
312 """
314 def helpers_helper():
313 def helpers_helper():
315 pass # should not stop here except breakpoint
314 pass # should not stop here except breakpoint
316 """,
315 """,
317 """
316 """
318 def helper_1():
317 def helper_1():
319 helpers_helper() # should not stop here
318 helpers_helper() # should not stop here
320 """,
319 """,
321 """
320 """
322 def helper_2():
321 def helper_2():
323 pass # should not stop here
322 pass # should not stop here
324 """,
323 """,
325 """
324 """
326 def pdb_skipped_decorator2(function):
325 def pdb_skipped_decorator2(function):
327 def wrapped_fn(*args, **kwargs):
326 def wrapped_fn(*args, **kwargs):
328 __debuggerskip__ = True
327 __debuggerskip__ = True
329 helper_2()
328 helper_2()
330 __debuggerskip__ = False
329 __debuggerskip__ = False
331 result = function(*args, **kwargs)
330 result = function(*args, **kwargs)
332 __debuggerskip__ = True
331 __debuggerskip__ = True
333 helper_2()
332 helper_2()
334 return result
333 return result
335 return wrapped_fn
334 return wrapped_fn
336 """,
335 """,
337 """
336 """
338 def pdb_skipped_decorator(function):
337 def pdb_skipped_decorator(function):
339 def wrapped_fn(*args, **kwargs):
338 def wrapped_fn(*args, **kwargs):
340 __debuggerskip__ = True
339 __debuggerskip__ = True
341 helper_1()
340 helper_1()
342 __debuggerskip__ = False
341 __debuggerskip__ = False
343 result = function(*args, **kwargs)
342 result = function(*args, **kwargs)
344 __debuggerskip__ = True
343 __debuggerskip__ = True
345 helper_2()
344 helper_2()
346 return result
345 return result
347 return wrapped_fn
346 return wrapped_fn
348 """,
347 """,
349 """
348 """
350 @pdb_skipped_decorator
349 @pdb_skipped_decorator
351 @pdb_skipped_decorator2
350 @pdb_skipped_decorator2
352 def bar(x, y):
351 def bar(x, y):
353 return x * y
352 return x * y
354 """,
353 """,
355 """import IPython.terminal.debugger as ipdb""",
354 """import IPython.terminal.debugger as ipdb""",
356 """
355 """
357 def f():
356 def f():
358 ipdb.set_trace()
357 ipdb.set_trace()
359 bar(3, 4)
358 bar(3, 4)
360 """,
359 """,
361 """
360 """
362 f()
361 f()
363 """,
362 """,
364 )
363 )
365
364
366
365
367 def _decorator_skip_setup():
366 def _decorator_skip_setup():
368 import pexpect
367 import pexpect
369
368
370 env = os.environ.copy()
369 env = os.environ.copy()
371 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
370 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
372 env["PROMPT_TOOLKIT_NO_CPR"] = "1"
371 env["PROMPT_TOOLKIT_NO_CPR"] = "1"
373
372
374 child = pexpect.spawn(
373 child = pexpect.spawn(
375 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
374 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
376 )
375 )
377 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
376 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
378
377
379 child.expect("IPython")
378 child.expect("IPython")
380 child.expect("\n")
379 child.expect("\n")
381
380
382 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
381 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
383 child.str_last_chars = 500
382 child.str_last_chars = 500
384
383
385 dedented_blocks = [dedent(b).strip() for b in skip_decorators_blocks]
384 dedented_blocks = [dedent(b).strip() for b in skip_decorators_blocks]
386 in_prompt_number = 1
385 in_prompt_number = 1
387 for cblock in dedented_blocks:
386 for cblock in dedented_blocks:
388 child.expect_exact(f"In [{in_prompt_number}]:")
387 child.expect_exact(f"In [{in_prompt_number}]:")
389 in_prompt_number += 1
388 in_prompt_number += 1
390 for line in cblock.splitlines():
389 for line in cblock.splitlines():
391 child.sendline(line)
390 child.sendline(line)
392 child.expect_exact(line)
391 child.expect_exact(line)
393 child.sendline("")
392 child.sendline("")
394 return child
393 return child
395
394
396
395
397 @pytest.mark.skip(reason="recently fail for unknown reason on CI")
396 @pytest.mark.skip(reason="recently fail for unknown reason on CI")
398 @skip_win32
397 @skip_win32
399 def test_decorator_skip():
398 def test_decorator_skip():
400 """test that decorator frames can be skipped."""
399 """test that decorator frames can be skipped."""
401
400
402 child = _decorator_skip_setup()
401 child = _decorator_skip_setup()
403
402
404 child.expect_exact("ipython-input-8")
403 child.expect_exact("ipython-input-8")
405 child.expect_exact("3 bar(3, 4)")
404 child.expect_exact("3 bar(3, 4)")
406 child.expect("ipdb>")
405 child.expect("ipdb>")
407
406
408 child.expect("ipdb>")
407 child.expect("ipdb>")
409 child.sendline("step")
408 child.sendline("step")
410 child.expect_exact("step")
409 child.expect_exact("step")
411 child.expect_exact("--Call--")
410 child.expect_exact("--Call--")
412 child.expect_exact("ipython-input-6")
411 child.expect_exact("ipython-input-6")
413
412
414 child.expect_exact("1 @pdb_skipped_decorator")
413 child.expect_exact("1 @pdb_skipped_decorator")
415
414
416 child.sendline("s")
415 child.sendline("s")
417 child.expect_exact("return x * y")
416 child.expect_exact("return x * y")
418
417
419 child.close()
418 child.close()
420
419
421
420
422 @pytest.mark.skip(reason="recently fail for unknown reason on CI")
421 @pytest.mark.skip(reason="recently fail for unknown reason on CI")
423 @pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy")
422 @pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy")
424 @skip_win32
423 @skip_win32
425 def test_decorator_skip_disabled():
424 def test_decorator_skip_disabled():
426 """test that decorator frame skipping can be disabled"""
425 """test that decorator frame skipping can be disabled"""
427
426
428 child = _decorator_skip_setup()
427 child = _decorator_skip_setup()
429
428
430 child.expect_exact("3 bar(3, 4)")
429 child.expect_exact("3 bar(3, 4)")
431
430
432 for input_, expected in [
431 for input_, expected in [
433 ("skip_predicates debuggerskip False", ""),
432 ("skip_predicates debuggerskip False", ""),
434 ("skip_predicates", "debuggerskip : False"),
433 ("skip_predicates", "debuggerskip : False"),
435 ("step", "---> 2 def wrapped_fn"),
434 ("step", "---> 2 def wrapped_fn"),
436 ("step", "----> 3 __debuggerskip__"),
435 ("step", "----> 3 __debuggerskip__"),
437 ("step", "----> 4 helper_1()"),
436 ("step", "----> 4 helper_1()"),
438 ("step", "---> 1 def helper_1():"),
437 ("step", "---> 1 def helper_1():"),
439 ("next", "----> 2 helpers_helper()"),
438 ("next", "----> 2 helpers_helper()"),
440 ("next", "--Return--"),
439 ("next", "--Return--"),
441 ("next", "----> 5 __debuggerskip__ = False"),
440 ("next", "----> 5 __debuggerskip__ = False"),
442 ]:
441 ]:
443 child.expect("ipdb>")
442 child.expect("ipdb>")
444 child.sendline(input_)
443 child.sendline(input_)
445 child.expect_exact(input_)
444 child.expect_exact(input_)
446 child.expect_exact(expected)
445 child.expect_exact(expected)
447
446
448 child.close()
447 child.close()
449
448
450
449
451 @pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy")
450 @pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="issues on PyPy")
452 @skip_win32
451 @skip_win32
453 def test_decorator_skip_with_breakpoint():
452 def test_decorator_skip_with_breakpoint():
454 """test that decorator frame skipping can be disabled"""
453 """test that decorator frame skipping can be disabled"""
455
454
456 import pexpect
455 import pexpect
457
456
458 env = os.environ.copy()
457 env = os.environ.copy()
459 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
458 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
460 env["PROMPT_TOOLKIT_NO_CPR"] = "1"
459 env["PROMPT_TOOLKIT_NO_CPR"] = "1"
461
460
462 child = pexpect.spawn(
461 child = pexpect.spawn(
463 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
462 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
464 )
463 )
465 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
464 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
466 child.str_last_chars = 500
465 child.str_last_chars = 500
467
466
468 child.expect("IPython")
467 child.expect("IPython")
469 child.expect("\n")
468 child.expect("\n")
470
469
471 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
470 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
472
471
473 ### we need a filename, so we need to exec the full block with a filename
472 ### we need a filename, so we need to exec the full block with a filename
474 with NamedTemporaryFile(suffix=".py", dir=".", delete=True) as tf:
473 with NamedTemporaryFile(suffix=".py", dir=".", delete=True) as tf:
475
474
476 name = tf.name[:-3].split("/")[-1]
475 name = tf.name[:-3].split("/")[-1]
477 tf.write("\n".join([dedent(x) for x in skip_decorators_blocks[:-1]]).encode())
476 tf.write("\n".join([dedent(x) for x in skip_decorators_blocks[:-1]]).encode())
478 tf.flush()
477 tf.flush()
479 codeblock = f"from {name} import f"
478 codeblock = f"from {name} import f"
480
479
481 dedented_blocks = [
480 dedented_blocks = [
482 codeblock,
481 codeblock,
483 "f()",
482 "f()",
484 ]
483 ]
485
484
486 in_prompt_number = 1
485 in_prompt_number = 1
487 for cblock in dedented_blocks:
486 for cblock in dedented_blocks:
488 child.expect_exact(f"In [{in_prompt_number}]:")
487 child.expect_exact(f"In [{in_prompt_number}]:")
489 in_prompt_number += 1
488 in_prompt_number += 1
490 for line in cblock.splitlines():
489 for line in cblock.splitlines():
491 child.sendline(line)
490 child.sendline(line)
492 child.expect_exact(line)
491 child.expect_exact(line)
493 child.sendline("")
492 child.sendline("")
494
493
495 # as the filename does not exists, we'll rely on the filename prompt
494 # as the filename does not exists, we'll rely on the filename prompt
496 child.expect_exact("47 bar(3, 4)")
495 child.expect_exact("47 bar(3, 4)")
497
496
498 for input_, expected in [
497 for input_, expected in [
499 (f"b {name}.py:3", ""),
498 (f"b {name}.py:3", ""),
500 ("step", "1---> 3 pass # should not stop here except"),
499 ("step", "1---> 3 pass # should not stop here except"),
501 ("step", "---> 38 @pdb_skipped_decorator"),
500 ("step", "---> 38 @pdb_skipped_decorator"),
502 ("continue", ""),
501 ("continue", ""),
503 ]:
502 ]:
504 child.expect("ipdb>")
503 child.expect("ipdb>")
505 child.sendline(input_)
504 child.sendline(input_)
506 child.expect_exact(input_)
505 child.expect_exact(input_)
507 child.expect_exact(expected)
506 child.expect_exact(expected)
508
507
509 child.close()
508 child.close()
510
509
511
510
512 @skip_win32
511 @skip_win32
513 def test_where_erase_value():
512 def test_where_erase_value():
514 """Test that `where` does not access f_locals and erase values."""
513 """Test that `where` does not access f_locals and erase values."""
515 import pexpect
514 import pexpect
516
515
517 env = os.environ.copy()
516 env = os.environ.copy()
518 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
517 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
519
518
520 child = pexpect.spawn(
519 child = pexpect.spawn(
521 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
520 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
522 )
521 )
523 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
522 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
524
523
525 child.expect("IPython")
524 child.expect("IPython")
526 child.expect("\n")
525 child.expect("\n")
527 child.expect_exact("In [1]")
526 child.expect_exact("In [1]")
528
527
529 block = dedent(
528 block = dedent(
530 """
529 """
531 def simple_f():
530 def simple_f():
532 myvar = 1
531 myvar = 1
533 print(myvar)
532 print(myvar)
534 1/0
533 1/0
535 print(myvar)
534 print(myvar)
536 simple_f() """
535 simple_f() """
537 )
536 )
538
537
539 for line in block.splitlines():
538 for line in block.splitlines():
540 child.sendline(line)
539 child.sendline(line)
541 child.expect_exact(line)
540 child.expect_exact(line)
542 child.expect_exact("ZeroDivisionError")
541 child.expect_exact("ZeroDivisionError")
543 child.expect_exact("In [2]:")
542 child.expect_exact("In [2]:")
544
543
545 child.sendline("%debug")
544 child.sendline("%debug")
546
545
547 ##
546 ##
548 child.expect("ipdb>")
547 child.expect("ipdb>")
549
548
550 child.sendline("myvar")
549 child.sendline("myvar")
551 child.expect("1")
550 child.expect("1")
552
551
553 ##
552 ##
554 child.expect("ipdb>")
553 child.expect("ipdb>")
555
554
556 child.sendline("myvar = 2")
555 child.sendline("myvar = 2")
557
556
558 ##
557 ##
559 child.expect_exact("ipdb>")
558 child.expect_exact("ipdb>")
560
559
561 child.sendline("myvar")
560 child.sendline("myvar")
562
561
563 child.expect_exact("2")
562 child.expect_exact("2")
564
563
565 ##
564 ##
566 child.expect("ipdb>")
565 child.expect("ipdb>")
567 child.sendline("where")
566 child.sendline("where")
568
567
569 ##
568 ##
570 child.expect("ipdb>")
569 child.expect("ipdb>")
571 child.sendline("myvar")
570 child.sendline("myvar")
572
571
573 child.expect_exact("2")
572 child.expect_exact("2")
574 child.expect("ipdb>")
573 child.expect("ipdb>")
575
574
576 child.close()
575 child.close()
@@ -1,532 +1,531 b''
1 """Tests for the Formatters."""
1 """Tests for the Formatters."""
2
2
3 import warnings
4 from math import pi
3 from math import pi
5
4
6 try:
5 try:
7 import numpy
6 import numpy
8 except:
7 except:
9 numpy = None
8 numpy = None
10 import pytest
9 import pytest
11
10
12 from IPython import get_ipython
11 from IPython import get_ipython
13 from traitlets.config import Config
12 from traitlets.config import Config
14 from IPython.core.formatters import (
13 from IPython.core.formatters import (
15 PlainTextFormatter, HTMLFormatter, PDFFormatter, _mod_name_key,
14 PlainTextFormatter, HTMLFormatter, PDFFormatter, _mod_name_key,
16 DisplayFormatter, JSONFormatter,
15 DisplayFormatter, JSONFormatter,
17 )
16 )
18 from IPython.utils.io import capture_output
17 from IPython.utils.io import capture_output
19
18
20 class A(object):
19 class A(object):
21 def __repr__(self):
20 def __repr__(self):
22 return 'A()'
21 return 'A()'
23
22
24 class B(A):
23 class B(A):
25 def __repr__(self):
24 def __repr__(self):
26 return 'B()'
25 return 'B()'
27
26
28 class C:
27 class C:
29 pass
28 pass
30
29
31 class BadRepr(object):
30 class BadRepr(object):
32 def __repr__(self):
31 def __repr__(self):
33 raise ValueError("bad repr")
32 raise ValueError("bad repr")
34
33
35 class BadPretty(object):
34 class BadPretty(object):
36 _repr_pretty_ = None
35 _repr_pretty_ = None
37
36
38 class GoodPretty(object):
37 class GoodPretty(object):
39 def _repr_pretty_(self, pp, cycle):
38 def _repr_pretty_(self, pp, cycle):
40 pp.text('foo')
39 pp.text('foo')
41
40
42 def __repr__(self):
41 def __repr__(self):
43 return 'GoodPretty()'
42 return 'GoodPretty()'
44
43
45 def foo_printer(obj, pp, cycle):
44 def foo_printer(obj, pp, cycle):
46 pp.text('foo')
45 pp.text('foo')
47
46
48 def test_pretty():
47 def test_pretty():
49 f = PlainTextFormatter()
48 f = PlainTextFormatter()
50 f.for_type(A, foo_printer)
49 f.for_type(A, foo_printer)
51 assert f(A()) == "foo"
50 assert f(A()) == "foo"
52 assert f(B()) == "B()"
51 assert f(B()) == "B()"
53 assert f(GoodPretty()) == "foo"
52 assert f(GoodPretty()) == "foo"
54 # Just don't raise an exception for the following:
53 # Just don't raise an exception for the following:
55 f(BadPretty())
54 f(BadPretty())
56
55
57 f.pprint = False
56 f.pprint = False
58 assert f(A()) == "A()"
57 assert f(A()) == "A()"
59 assert f(B()) == "B()"
58 assert f(B()) == "B()"
60 assert f(GoodPretty()) == "GoodPretty()"
59 assert f(GoodPretty()) == "GoodPretty()"
61
60
62
61
63 def test_deferred():
62 def test_deferred():
64 f = PlainTextFormatter()
63 f = PlainTextFormatter()
65
64
66 def test_precision():
65 def test_precision():
67 """test various values for float_precision."""
66 """test various values for float_precision."""
68 f = PlainTextFormatter()
67 f = PlainTextFormatter()
69 assert f(pi) == repr(pi)
68 assert f(pi) == repr(pi)
70 f.float_precision = 0
69 f.float_precision = 0
71 if numpy:
70 if numpy:
72 po = numpy.get_printoptions()
71 po = numpy.get_printoptions()
73 assert po["precision"] == 0
72 assert po["precision"] == 0
74 assert f(pi) == "3"
73 assert f(pi) == "3"
75 f.float_precision = 2
74 f.float_precision = 2
76 if numpy:
75 if numpy:
77 po = numpy.get_printoptions()
76 po = numpy.get_printoptions()
78 assert po["precision"] == 2
77 assert po["precision"] == 2
79 assert f(pi) == "3.14"
78 assert f(pi) == "3.14"
80 f.float_precision = "%g"
79 f.float_precision = "%g"
81 if numpy:
80 if numpy:
82 po = numpy.get_printoptions()
81 po = numpy.get_printoptions()
83 assert po["precision"] == 2
82 assert po["precision"] == 2
84 assert f(pi) == "3.14159"
83 assert f(pi) == "3.14159"
85 f.float_precision = "%e"
84 f.float_precision = "%e"
86 assert f(pi) == "3.141593e+00"
85 assert f(pi) == "3.141593e+00"
87 f.float_precision = ""
86 f.float_precision = ""
88 if numpy:
87 if numpy:
89 po = numpy.get_printoptions()
88 po = numpy.get_printoptions()
90 assert po["precision"] == 8
89 assert po["precision"] == 8
91 assert f(pi) == repr(pi)
90 assert f(pi) == repr(pi)
92
91
93
92
94 def test_bad_precision():
93 def test_bad_precision():
95 """test various invalid values for float_precision."""
94 """test various invalid values for float_precision."""
96 f = PlainTextFormatter()
95 f = PlainTextFormatter()
97 def set_fp(p):
96 def set_fp(p):
98 f.float_precision = p
97 f.float_precision = p
99
98
100 pytest.raises(ValueError, set_fp, "%")
99 pytest.raises(ValueError, set_fp, "%")
101 pytest.raises(ValueError, set_fp, "%.3f%i")
100 pytest.raises(ValueError, set_fp, "%.3f%i")
102 pytest.raises(ValueError, set_fp, "foo")
101 pytest.raises(ValueError, set_fp, "foo")
103 pytest.raises(ValueError, set_fp, -1)
102 pytest.raises(ValueError, set_fp, -1)
104
103
105 def test_for_type():
104 def test_for_type():
106 f = PlainTextFormatter()
105 f = PlainTextFormatter()
107
106
108 # initial return, None
107 # initial return, None
109 assert f.for_type(C, foo_printer) is None
108 assert f.for_type(C, foo_printer) is None
110 # no func queries
109 # no func queries
111 assert f.for_type(C) is foo_printer
110 assert f.for_type(C) is foo_printer
112 # shouldn't change anything
111 # shouldn't change anything
113 assert f.for_type(C) is foo_printer
112 assert f.for_type(C) is foo_printer
114 # None should do the same
113 # None should do the same
115 assert f.for_type(C, None) is foo_printer
114 assert f.for_type(C, None) is foo_printer
116 assert f.for_type(C, None) is foo_printer
115 assert f.for_type(C, None) is foo_printer
117
116
118 def test_for_type_string():
117 def test_for_type_string():
119 f = PlainTextFormatter()
118 f = PlainTextFormatter()
120
119
121 type_str = '%s.%s' % (C.__module__, 'C')
120 type_str = '%s.%s' % (C.__module__, 'C')
122
121
123 # initial return, None
122 # initial return, None
124 assert f.for_type(type_str, foo_printer) is None
123 assert f.for_type(type_str, foo_printer) is None
125 # no func queries
124 # no func queries
126 assert f.for_type(type_str) is foo_printer
125 assert f.for_type(type_str) is foo_printer
127 assert _mod_name_key(C) in f.deferred_printers
126 assert _mod_name_key(C) in f.deferred_printers
128 assert f.for_type(C) is foo_printer
127 assert f.for_type(C) is foo_printer
129 assert _mod_name_key(C) not in f.deferred_printers
128 assert _mod_name_key(C) not in f.deferred_printers
130 assert C in f.type_printers
129 assert C in f.type_printers
131
130
132 def test_for_type_by_name():
131 def test_for_type_by_name():
133 f = PlainTextFormatter()
132 f = PlainTextFormatter()
134
133
135 mod = C.__module__
134 mod = C.__module__
136
135
137 # initial return, None
136 # initial return, None
138 assert f.for_type_by_name(mod, "C", foo_printer) is None
137 assert f.for_type_by_name(mod, "C", foo_printer) is None
139 # no func queries
138 # no func queries
140 assert f.for_type_by_name(mod, "C") is foo_printer
139 assert f.for_type_by_name(mod, "C") is foo_printer
141 # shouldn't change anything
140 # shouldn't change anything
142 assert f.for_type_by_name(mod, "C") is foo_printer
141 assert f.for_type_by_name(mod, "C") is foo_printer
143 # None should do the same
142 # None should do the same
144 assert f.for_type_by_name(mod, "C", None) is foo_printer
143 assert f.for_type_by_name(mod, "C", None) is foo_printer
145 assert f.for_type_by_name(mod, "C", None) is foo_printer
144 assert f.for_type_by_name(mod, "C", None) is foo_printer
146
145
147
146
148 def test_lookup():
147 def test_lookup():
149 f = PlainTextFormatter()
148 f = PlainTextFormatter()
150
149
151 f.for_type(C, foo_printer)
150 f.for_type(C, foo_printer)
152 assert f.lookup(C()) is foo_printer
151 assert f.lookup(C()) is foo_printer
153 with pytest.raises(KeyError):
152 with pytest.raises(KeyError):
154 f.lookup(A())
153 f.lookup(A())
155
154
156 def test_lookup_string():
155 def test_lookup_string():
157 f = PlainTextFormatter()
156 f = PlainTextFormatter()
158 type_str = '%s.%s' % (C.__module__, 'C')
157 type_str = '%s.%s' % (C.__module__, 'C')
159
158
160 f.for_type(type_str, foo_printer)
159 f.for_type(type_str, foo_printer)
161 assert f.lookup(C()) is foo_printer
160 assert f.lookup(C()) is foo_printer
162 # should move from deferred to imported dict
161 # should move from deferred to imported dict
163 assert _mod_name_key(C) not in f.deferred_printers
162 assert _mod_name_key(C) not in f.deferred_printers
164 assert C in f.type_printers
163 assert C in f.type_printers
165
164
166 def test_lookup_by_type():
165 def test_lookup_by_type():
167 f = PlainTextFormatter()
166 f = PlainTextFormatter()
168 f.for_type(C, foo_printer)
167 f.for_type(C, foo_printer)
169 assert f.lookup_by_type(C) is foo_printer
168 assert f.lookup_by_type(C) is foo_printer
170 with pytest.raises(KeyError):
169 with pytest.raises(KeyError):
171 f.lookup_by_type(A)
170 f.lookup_by_type(A)
172
171
173 def test_lookup_by_type_string():
172 def test_lookup_by_type_string():
174 f = PlainTextFormatter()
173 f = PlainTextFormatter()
175 type_str = '%s.%s' % (C.__module__, 'C')
174 type_str = '%s.%s' % (C.__module__, 'C')
176 f.for_type(type_str, foo_printer)
175 f.for_type(type_str, foo_printer)
177
176
178 # verify insertion
177 # verify insertion
179 assert _mod_name_key(C) in f.deferred_printers
178 assert _mod_name_key(C) in f.deferred_printers
180 assert C not in f.type_printers
179 assert C not in f.type_printers
181
180
182 assert f.lookup_by_type(type_str) is foo_printer
181 assert f.lookup_by_type(type_str) is foo_printer
183 # lookup by string doesn't cause import
182 # lookup by string doesn't cause import
184 assert _mod_name_key(C) in f.deferred_printers
183 assert _mod_name_key(C) in f.deferred_printers
185 assert C not in f.type_printers
184 assert C not in f.type_printers
186
185
187 assert f.lookup_by_type(C) is foo_printer
186 assert f.lookup_by_type(C) is foo_printer
188 # should move from deferred to imported dict
187 # should move from deferred to imported dict
189 assert _mod_name_key(C) not in f.deferred_printers
188 assert _mod_name_key(C) not in f.deferred_printers
190 assert C in f.type_printers
189 assert C in f.type_printers
191
190
192 def test_in_formatter():
191 def test_in_formatter():
193 f = PlainTextFormatter()
192 f = PlainTextFormatter()
194 f.for_type(C, foo_printer)
193 f.for_type(C, foo_printer)
195 type_str = '%s.%s' % (C.__module__, 'C')
194 type_str = '%s.%s' % (C.__module__, 'C')
196 assert C in f
195 assert C in f
197 assert type_str in f
196 assert type_str in f
198
197
199 def test_string_in_formatter():
198 def test_string_in_formatter():
200 f = PlainTextFormatter()
199 f = PlainTextFormatter()
201 type_str = '%s.%s' % (C.__module__, 'C')
200 type_str = '%s.%s' % (C.__module__, 'C')
202 f.for_type(type_str, foo_printer)
201 f.for_type(type_str, foo_printer)
203 assert type_str in f
202 assert type_str in f
204 assert C in f
203 assert C in f
205
204
206 def test_pop():
205 def test_pop():
207 f = PlainTextFormatter()
206 f = PlainTextFormatter()
208 f.for_type(C, foo_printer)
207 f.for_type(C, foo_printer)
209 assert f.lookup_by_type(C) is foo_printer
208 assert f.lookup_by_type(C) is foo_printer
210 assert f.pop(C, None) is foo_printer
209 assert f.pop(C, None) is foo_printer
211 f.for_type(C, foo_printer)
210 f.for_type(C, foo_printer)
212 assert f.pop(C) is foo_printer
211 assert f.pop(C) is foo_printer
213 with pytest.raises(KeyError):
212 with pytest.raises(KeyError):
214 f.lookup_by_type(C)
213 f.lookup_by_type(C)
215 with pytest.raises(KeyError):
214 with pytest.raises(KeyError):
216 f.pop(C)
215 f.pop(C)
217 with pytest.raises(KeyError):
216 with pytest.raises(KeyError):
218 f.pop(A)
217 f.pop(A)
219 assert f.pop(A, None) is None
218 assert f.pop(A, None) is None
220
219
221 def test_pop_string():
220 def test_pop_string():
222 f = PlainTextFormatter()
221 f = PlainTextFormatter()
223 type_str = '%s.%s' % (C.__module__, 'C')
222 type_str = '%s.%s' % (C.__module__, 'C')
224
223
225 with pytest.raises(KeyError):
224 with pytest.raises(KeyError):
226 f.pop(type_str)
225 f.pop(type_str)
227
226
228 f.for_type(type_str, foo_printer)
227 f.for_type(type_str, foo_printer)
229 f.pop(type_str)
228 f.pop(type_str)
230 with pytest.raises(KeyError):
229 with pytest.raises(KeyError):
231 f.lookup_by_type(C)
230 f.lookup_by_type(C)
232 with pytest.raises(KeyError):
231 with pytest.raises(KeyError):
233 f.pop(type_str)
232 f.pop(type_str)
234
233
235 f.for_type(C, foo_printer)
234 f.for_type(C, foo_printer)
236 assert f.pop(type_str, None) is foo_printer
235 assert f.pop(type_str, None) is foo_printer
237 with pytest.raises(KeyError):
236 with pytest.raises(KeyError):
238 f.lookup_by_type(C)
237 f.lookup_by_type(C)
239 with pytest.raises(KeyError):
238 with pytest.raises(KeyError):
240 f.pop(type_str)
239 f.pop(type_str)
241 assert f.pop(type_str, None) is None
240 assert f.pop(type_str, None) is None
242
241
243
242
244 def test_error_method():
243 def test_error_method():
245 f = HTMLFormatter()
244 f = HTMLFormatter()
246 class BadHTML(object):
245 class BadHTML(object):
247 def _repr_html_(self):
246 def _repr_html_(self):
248 raise ValueError("Bad HTML")
247 raise ValueError("Bad HTML")
249 bad = BadHTML()
248 bad = BadHTML()
250 with capture_output() as captured:
249 with capture_output() as captured:
251 result = f(bad)
250 result = f(bad)
252 assert result is None
251 assert result is None
253 assert "Traceback" in captured.stdout
252 assert "Traceback" in captured.stdout
254 assert "Bad HTML" in captured.stdout
253 assert "Bad HTML" in captured.stdout
255 assert "_repr_html_" in captured.stdout
254 assert "_repr_html_" in captured.stdout
256
255
257 def test_nowarn_notimplemented():
256 def test_nowarn_notimplemented():
258 f = HTMLFormatter()
257 f = HTMLFormatter()
259 class HTMLNotImplemented(object):
258 class HTMLNotImplemented(object):
260 def _repr_html_(self):
259 def _repr_html_(self):
261 raise NotImplementedError
260 raise NotImplementedError
262 h = HTMLNotImplemented()
261 h = HTMLNotImplemented()
263 with capture_output() as captured:
262 with capture_output() as captured:
264 result = f(h)
263 result = f(h)
265 assert result is None
264 assert result is None
266 assert "" == captured.stderr
265 assert "" == captured.stderr
267 assert "" == captured.stdout
266 assert "" == captured.stdout
268
267
269
268
270 def test_warn_error_for_type():
269 def test_warn_error_for_type():
271 f = HTMLFormatter()
270 f = HTMLFormatter()
272 f.for_type(int, lambda i: name_error)
271 f.for_type(int, lambda i: name_error)
273 with capture_output() as captured:
272 with capture_output() as captured:
274 result = f(5)
273 result = f(5)
275 assert result is None
274 assert result is None
276 assert "Traceback" in captured.stdout
275 assert "Traceback" in captured.stdout
277 assert "NameError" in captured.stdout
276 assert "NameError" in captured.stdout
278 assert "name_error" in captured.stdout
277 assert "name_error" in captured.stdout
279
278
280 def test_error_pretty_method():
279 def test_error_pretty_method():
281 f = PlainTextFormatter()
280 f = PlainTextFormatter()
282 class BadPretty(object):
281 class BadPretty(object):
283 def _repr_pretty_(self):
282 def _repr_pretty_(self):
284 return "hello"
283 return "hello"
285 bad = BadPretty()
284 bad = BadPretty()
286 with capture_output() as captured:
285 with capture_output() as captured:
287 result = f(bad)
286 result = f(bad)
288 assert result is None
287 assert result is None
289 assert "Traceback" in captured.stdout
288 assert "Traceback" in captured.stdout
290 assert "_repr_pretty_" in captured.stdout
289 assert "_repr_pretty_" in captured.stdout
291 assert "given" in captured.stdout
290 assert "given" in captured.stdout
292 assert "argument" in captured.stdout
291 assert "argument" in captured.stdout
293
292
294
293
295 def test_bad_repr_traceback():
294 def test_bad_repr_traceback():
296 f = PlainTextFormatter()
295 f = PlainTextFormatter()
297 bad = BadRepr()
296 bad = BadRepr()
298 with capture_output() as captured:
297 with capture_output() as captured:
299 result = f(bad)
298 result = f(bad)
300 # catches error, returns None
299 # catches error, returns None
301 assert result is None
300 assert result is None
302 assert "Traceback" in captured.stdout
301 assert "Traceback" in captured.stdout
303 assert "__repr__" in captured.stdout
302 assert "__repr__" in captured.stdout
304 assert "ValueError" in captured.stdout
303 assert "ValueError" in captured.stdout
305
304
306
305
307 class MakePDF(object):
306 class MakePDF(object):
308 def _repr_pdf_(self):
307 def _repr_pdf_(self):
309 return 'PDF'
308 return 'PDF'
310
309
311 def test_pdf_formatter():
310 def test_pdf_formatter():
312 pdf = MakePDF()
311 pdf = MakePDF()
313 f = PDFFormatter()
312 f = PDFFormatter()
314 assert f(pdf) == "PDF"
313 assert f(pdf) == "PDF"
315
314
316
315
317 def test_print_method_bound():
316 def test_print_method_bound():
318 f = HTMLFormatter()
317 f = HTMLFormatter()
319 class MyHTML(object):
318 class MyHTML(object):
320 def _repr_html_(self):
319 def _repr_html_(self):
321 return "hello"
320 return "hello"
322 with capture_output() as captured:
321 with capture_output() as captured:
323 result = f(MyHTML)
322 result = f(MyHTML)
324 assert result is None
323 assert result is None
325 assert "FormatterWarning" not in captured.stderr
324 assert "FormatterWarning" not in captured.stderr
326
325
327 with capture_output() as captured:
326 with capture_output() as captured:
328 result = f(MyHTML())
327 result = f(MyHTML())
329 assert result == "hello"
328 assert result == "hello"
330 assert captured.stderr == ""
329 assert captured.stderr == ""
331
330
332
331
333 def test_print_method_weird():
332 def test_print_method_weird():
334
333
335 class TextMagicHat(object):
334 class TextMagicHat(object):
336 def __getattr__(self, key):
335 def __getattr__(self, key):
337 return key
336 return key
338
337
339 f = HTMLFormatter()
338 f = HTMLFormatter()
340
339
341 text_hat = TextMagicHat()
340 text_hat = TextMagicHat()
342 assert text_hat._repr_html_ == "_repr_html_"
341 assert text_hat._repr_html_ == "_repr_html_"
343 with capture_output() as captured:
342 with capture_output() as captured:
344 result = f(text_hat)
343 result = f(text_hat)
345
344
346 assert result is None
345 assert result is None
347 assert "FormatterWarning" not in captured.stderr
346 assert "FormatterWarning" not in captured.stderr
348
347
349 class CallableMagicHat(object):
348 class CallableMagicHat(object):
350 def __getattr__(self, key):
349 def __getattr__(self, key):
351 return lambda : key
350 return lambda : key
352
351
353 call_hat = CallableMagicHat()
352 call_hat = CallableMagicHat()
354 with capture_output() as captured:
353 with capture_output() as captured:
355 result = f(call_hat)
354 result = f(call_hat)
356
355
357 assert result is None
356 assert result is None
358
357
359 class BadReprArgs(object):
358 class BadReprArgs(object):
360 def _repr_html_(self, extra, args):
359 def _repr_html_(self, extra, args):
361 return "html"
360 return "html"
362
361
363 bad = BadReprArgs()
362 bad = BadReprArgs()
364 with capture_output() as captured:
363 with capture_output() as captured:
365 result = f(bad)
364 result = f(bad)
366
365
367 assert result is None
366 assert result is None
368 assert "FormatterWarning" not in captured.stderr
367 assert "FormatterWarning" not in captured.stderr
369
368
370
369
371 def test_format_config():
370 def test_format_config():
372 """config objects don't pretend to support fancy reprs with lazy attrs"""
371 """config objects don't pretend to support fancy reprs with lazy attrs"""
373 f = HTMLFormatter()
372 f = HTMLFormatter()
374 cfg = Config()
373 cfg = Config()
375 with capture_output() as captured:
374 with capture_output() as captured:
376 result = f(cfg)
375 result = f(cfg)
377 assert result is None
376 assert result is None
378 assert captured.stderr == ""
377 assert captured.stderr == ""
379
378
380 with capture_output() as captured:
379 with capture_output() as captured:
381 result = f(Config)
380 result = f(Config)
382 assert result is None
381 assert result is None
383 assert captured.stderr == ""
382 assert captured.stderr == ""
384
383
385
384
386 def test_pretty_max_seq_length():
385 def test_pretty_max_seq_length():
387 f = PlainTextFormatter(max_seq_length=1)
386 f = PlainTextFormatter(max_seq_length=1)
388 lis = list(range(3))
387 lis = list(range(3))
389 text = f(lis)
388 text = f(lis)
390 assert text == "[0, ...]"
389 assert text == "[0, ...]"
391 f.max_seq_length = 0
390 f.max_seq_length = 0
392 text = f(lis)
391 text = f(lis)
393 assert text == "[0, 1, 2]"
392 assert text == "[0, 1, 2]"
394 text = f(list(range(1024)))
393 text = f(list(range(1024)))
395 lines = text.splitlines()
394 lines = text.splitlines()
396 assert len(lines) == 1024
395 assert len(lines) == 1024
397
396
398
397
399 def test_ipython_display_formatter():
398 def test_ipython_display_formatter():
400 """Objects with _ipython_display_ defined bypass other formatters"""
399 """Objects with _ipython_display_ defined bypass other formatters"""
401 f = get_ipython().display_formatter
400 f = get_ipython().display_formatter
402 catcher = []
401 catcher = []
403 class SelfDisplaying(object):
402 class SelfDisplaying(object):
404 def _ipython_display_(self):
403 def _ipython_display_(self):
405 catcher.append(self)
404 catcher.append(self)
406
405
407 class NotSelfDisplaying(object):
406 class NotSelfDisplaying(object):
408 def __repr__(self):
407 def __repr__(self):
409 return "NotSelfDisplaying"
408 return "NotSelfDisplaying"
410
409
411 def _ipython_display_(self):
410 def _ipython_display_(self):
412 raise NotImplementedError
411 raise NotImplementedError
413
412
414 save_enabled = f.ipython_display_formatter.enabled
413 save_enabled = f.ipython_display_formatter.enabled
415 f.ipython_display_formatter.enabled = True
414 f.ipython_display_formatter.enabled = True
416
415
417 yes = SelfDisplaying()
416 yes = SelfDisplaying()
418 no = NotSelfDisplaying()
417 no = NotSelfDisplaying()
419
418
420 d, md = f.format(no)
419 d, md = f.format(no)
421 assert d == {"text/plain": repr(no)}
420 assert d == {"text/plain": repr(no)}
422 assert md == {}
421 assert md == {}
423 assert catcher == []
422 assert catcher == []
424
423
425 d, md = f.format(yes)
424 d, md = f.format(yes)
426 assert d == {}
425 assert d == {}
427 assert md == {}
426 assert md == {}
428 assert catcher == [yes]
427 assert catcher == [yes]
429
428
430 f.ipython_display_formatter.enabled = save_enabled
429 f.ipython_display_formatter.enabled = save_enabled
431
430
432
431
433 def test_repr_mime():
432 def test_repr_mime():
434 class HasReprMime(object):
433 class HasReprMime(object):
435 def _repr_mimebundle_(self, include=None, exclude=None):
434 def _repr_mimebundle_(self, include=None, exclude=None):
436 return {
435 return {
437 'application/json+test.v2': {
436 'application/json+test.v2': {
438 'x': 'y'
437 'x': 'y'
439 },
438 },
440 'plain/text' : '<HasReprMime>',
439 'plain/text' : '<HasReprMime>',
441 'image/png' : 'i-overwrite'
440 'image/png' : 'i-overwrite'
442 }
441 }
443
442
444 def _repr_png_(self):
443 def _repr_png_(self):
445 return 'should-be-overwritten'
444 return 'should-be-overwritten'
446 def _repr_html_(self):
445 def _repr_html_(self):
447 return '<b>hi!</b>'
446 return '<b>hi!</b>'
448
447
449 f = get_ipython().display_formatter
448 f = get_ipython().display_formatter
450 html_f = f.formatters['text/html']
449 html_f = f.formatters['text/html']
451 save_enabled = html_f.enabled
450 save_enabled = html_f.enabled
452 html_f.enabled = True
451 html_f.enabled = True
453 obj = HasReprMime()
452 obj = HasReprMime()
454 d, md = f.format(obj)
453 d, md = f.format(obj)
455 html_f.enabled = save_enabled
454 html_f.enabled = save_enabled
456
455
457 assert sorted(d) == [
456 assert sorted(d) == [
458 "application/json+test.v2",
457 "application/json+test.v2",
459 "image/png",
458 "image/png",
460 "plain/text",
459 "plain/text",
461 "text/html",
460 "text/html",
462 "text/plain",
461 "text/plain",
463 ]
462 ]
464 assert md == {}
463 assert md == {}
465
464
466 d, md = f.format(obj, include={"image/png"})
465 d, md = f.format(obj, include={"image/png"})
467 assert list(d.keys()) == [
466 assert list(d.keys()) == [
468 "image/png"
467 "image/png"
469 ], "Include should filter out even things from repr_mimebundle"
468 ], "Include should filter out even things from repr_mimebundle"
470
469
471 assert d["image/png"] == "i-overwrite", "_repr_mimebundle_ take precedence"
470 assert d["image/png"] == "i-overwrite", "_repr_mimebundle_ take precedence"
472
471
473
472
474 def test_pass_correct_include_exclude():
473 def test_pass_correct_include_exclude():
475 class Tester(object):
474 class Tester(object):
476
475
477 def __init__(self, include=None, exclude=None):
476 def __init__(self, include=None, exclude=None):
478 self.include = include
477 self.include = include
479 self.exclude = exclude
478 self.exclude = exclude
480
479
481 def _repr_mimebundle_(self, include, exclude, **kwargs):
480 def _repr_mimebundle_(self, include, exclude, **kwargs):
482 if include and (include != self.include):
481 if include and (include != self.include):
483 raise ValueError('include got modified: display() may be broken.')
482 raise ValueError('include got modified: display() may be broken.')
484 if exclude and (exclude != self.exclude):
483 if exclude and (exclude != self.exclude):
485 raise ValueError('exclude got modified: display() may be broken.')
484 raise ValueError('exclude got modified: display() may be broken.')
486
485
487 return None
486 return None
488
487
489 include = {'a', 'b', 'c'}
488 include = {'a', 'b', 'c'}
490 exclude = {'c', 'e' , 'f'}
489 exclude = {'c', 'e' , 'f'}
491
490
492 f = get_ipython().display_formatter
491 f = get_ipython().display_formatter
493 f.format(Tester(include=include, exclude=exclude), include=include, exclude=exclude)
492 f.format(Tester(include=include, exclude=exclude), include=include, exclude=exclude)
494 f.format(Tester(exclude=exclude), exclude=exclude)
493 f.format(Tester(exclude=exclude), exclude=exclude)
495 f.format(Tester(include=include), include=include)
494 f.format(Tester(include=include), include=include)
496
495
497
496
498 def test_repr_mime_meta():
497 def test_repr_mime_meta():
499 class HasReprMimeMeta(object):
498 class HasReprMimeMeta(object):
500 def _repr_mimebundle_(self, include=None, exclude=None):
499 def _repr_mimebundle_(self, include=None, exclude=None):
501 data = {
500 data = {
502 'image/png': 'base64-image-data',
501 'image/png': 'base64-image-data',
503 }
502 }
504 metadata = {
503 metadata = {
505 'image/png': {
504 'image/png': {
506 'width': 5,
505 'width': 5,
507 'height': 10,
506 'height': 10,
508 }
507 }
509 }
508 }
510 return (data, metadata)
509 return (data, metadata)
511
510
512 f = get_ipython().display_formatter
511 f = get_ipython().display_formatter
513 obj = HasReprMimeMeta()
512 obj = HasReprMimeMeta()
514 d, md = f.format(obj)
513 d, md = f.format(obj)
515 assert sorted(d) == ["image/png", "text/plain"]
514 assert sorted(d) == ["image/png", "text/plain"]
516 assert md == {
515 assert md == {
517 "image/png": {
516 "image/png": {
518 "width": 5,
517 "width": 5,
519 "height": 10,
518 "height": 10,
520 }
519 }
521 }
520 }
522
521
523
522
524 def test_repr_mime_failure():
523 def test_repr_mime_failure():
525 class BadReprMime(object):
524 class BadReprMime(object):
526 def _repr_mimebundle_(self, include=None, exclude=None):
525 def _repr_mimebundle_(self, include=None, exclude=None):
527 raise RuntimeError
526 raise RuntimeError
528
527
529 f = get_ipython().display_formatter
528 f = get_ipython().display_formatter
530 obj = BadReprMime()
529 obj = BadReprMime()
531 d, md = f.format(obj)
530 d, md = f.format(obj)
532 assert "text/plain" in d
531 assert "text/plain" in d
@@ -1,307 +1,306 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Tests for the IPython tab-completion machinery.
2 """Tests for the IPython tab-completion machinery.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Module imports
5 # Module imports
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7
7
8 # stdlib
8 # stdlib
9 import io
9 import io
10 import sqlite3
11 import sys
10 import sys
12 import tempfile
11 import tempfile
13 from datetime import datetime
12 from datetime import datetime
14 from pathlib import Path
13 from pathlib import Path
15
14
16 from tempfile import TemporaryDirectory
15 from tempfile import TemporaryDirectory
17 # our own packages
16 # our own packages
18 from traitlets.config.loader import Config
17 from traitlets.config.loader import Config
19
18
20 from IPython.core.history import HistoryAccessor, HistoryManager, extract_hist_ranges
19 from IPython.core.history import HistoryAccessor, HistoryManager, extract_hist_ranges
21
20
22
21
23 def test_proper_default_encoding():
22 def test_proper_default_encoding():
24 assert sys.getdefaultencoding() == "utf-8"
23 assert sys.getdefaultencoding() == "utf-8"
25
24
26 def test_history():
25 def test_history():
27 ip = get_ipython()
26 ip = get_ipython()
28 with TemporaryDirectory() as tmpdir:
27 with TemporaryDirectory() as tmpdir:
29 tmp_path = Path(tmpdir)
28 tmp_path = Path(tmpdir)
30 hist_manager_ori = ip.history_manager
29 hist_manager_ori = ip.history_manager
31 hist_file = tmp_path / "history.sqlite"
30 hist_file = tmp_path / "history.sqlite"
32 try:
31 try:
33 ip.history_manager = HistoryManager(shell=ip, hist_file=hist_file)
32 ip.history_manager = HistoryManager(shell=ip, hist_file=hist_file)
34 hist = ["a=1", "def f():\n test = 1\n return test", "b='β‚¬Γ†ΒΎΓ·ΓŸ'"]
33 hist = ["a=1", "def f():\n test = 1\n return test", "b='β‚¬Γ†ΒΎΓ·ΓŸ'"]
35 for i, h in enumerate(hist, start=1):
34 for i, h in enumerate(hist, start=1):
36 ip.history_manager.store_inputs(i, h)
35 ip.history_manager.store_inputs(i, h)
37
36
38 ip.history_manager.db_log_output = True
37 ip.history_manager.db_log_output = True
39 # Doesn't match the input, but we'll just check it's stored.
38 # Doesn't match the input, but we'll just check it's stored.
40 ip.history_manager.output_hist_reprs[3] = "spam"
39 ip.history_manager.output_hist_reprs[3] = "spam"
41 ip.history_manager.store_output(3)
40 ip.history_manager.store_output(3)
42
41
43 assert ip.history_manager.input_hist_raw == [""] + hist
42 assert ip.history_manager.input_hist_raw == [""] + hist
44
43
45 # Detailed tests for _get_range_session
44 # Detailed tests for _get_range_session
46 grs = ip.history_manager._get_range_session
45 grs = ip.history_manager._get_range_session
47 assert list(grs(start=2, stop=-1)) == list(zip([0], [2], hist[1:-1]))
46 assert list(grs(start=2, stop=-1)) == list(zip([0], [2], hist[1:-1]))
48 assert list(grs(start=-2)) == list(zip([0, 0], [2, 3], hist[-2:]))
47 assert list(grs(start=-2)) == list(zip([0, 0], [2, 3], hist[-2:]))
49 assert list(grs(output=True)) == list(
48 assert list(grs(output=True)) == list(
50 zip([0, 0, 0], [1, 2, 3], zip(hist, [None, None, "spam"]))
49 zip([0, 0, 0], [1, 2, 3], zip(hist, [None, None, "spam"]))
51 )
50 )
52
51
53 # Check whether specifying a range beyond the end of the current
52 # Check whether specifying a range beyond the end of the current
54 # session results in an error (gh-804)
53 # session results in an error (gh-804)
55 ip.run_line_magic("hist", "2-500")
54 ip.run_line_magic("hist", "2-500")
56
55
57 # Check that we can write non-ascii characters to a file
56 # Check that we can write non-ascii characters to a file
58 ip.run_line_magic("hist", "-f %s" % (tmp_path / "test1"))
57 ip.run_line_magic("hist", "-f %s" % (tmp_path / "test1"))
59 ip.run_line_magic("hist", "-pf %s" % (tmp_path / "test2"))
58 ip.run_line_magic("hist", "-pf %s" % (tmp_path / "test2"))
60 ip.run_line_magic("hist", "-nf %s" % (tmp_path / "test3"))
59 ip.run_line_magic("hist", "-nf %s" % (tmp_path / "test3"))
61 ip.run_line_magic("save", "%s 1-10" % (tmp_path / "test4"))
60 ip.run_line_magic("save", "%s 1-10" % (tmp_path / "test4"))
62
61
63 # New session
62 # New session
64 ip.history_manager.reset()
63 ip.history_manager.reset()
65 newcmds = ["z=5", "class X(object):\n pass", "k='p'", "z=5"]
64 newcmds = ["z=5", "class X(object):\n pass", "k='p'", "z=5"]
66 for i, cmd in enumerate(newcmds, start=1):
65 for i, cmd in enumerate(newcmds, start=1):
67 ip.history_manager.store_inputs(i, cmd)
66 ip.history_manager.store_inputs(i, cmd)
68 gothist = ip.history_manager.get_range(start=1, stop=4)
67 gothist = ip.history_manager.get_range(start=1, stop=4)
69 assert list(gothist) == list(zip([0, 0, 0], [1, 2, 3], newcmds))
68 assert list(gothist) == list(zip([0, 0, 0], [1, 2, 3], newcmds))
70 # Previous session:
69 # Previous session:
71 gothist = ip.history_manager.get_range(-1, 1, 4)
70 gothist = ip.history_manager.get_range(-1, 1, 4)
72 assert list(gothist) == list(zip([1, 1, 1], [1, 2, 3], hist))
71 assert list(gothist) == list(zip([1, 1, 1], [1, 2, 3], hist))
73
72
74 newhist = [(2, i, c) for (i, c) in enumerate(newcmds, 1)]
73 newhist = [(2, i, c) for (i, c) in enumerate(newcmds, 1)]
75
74
76 # Check get_hist_tail
75 # Check get_hist_tail
77 gothist = ip.history_manager.get_tail(5, output=True,
76 gothist = ip.history_manager.get_tail(5, output=True,
78 include_latest=True)
77 include_latest=True)
79 expected = [(1, 3, (hist[-1], "spam"))] \
78 expected = [(1, 3, (hist[-1], "spam"))] \
80 + [(s, n, (c, None)) for (s, n, c) in newhist]
79 + [(s, n, (c, None)) for (s, n, c) in newhist]
81 assert list(gothist) == expected
80 assert list(gothist) == expected
82
81
83 gothist = ip.history_manager.get_tail(2)
82 gothist = ip.history_manager.get_tail(2)
84 expected = newhist[-3:-1]
83 expected = newhist[-3:-1]
85 assert list(gothist) == expected
84 assert list(gothist) == expected
86
85
87 # Check get_hist_search
86 # Check get_hist_search
88
87
89 gothist = ip.history_manager.search("*test*")
88 gothist = ip.history_manager.search("*test*")
90 assert list(gothist) == [(1, 2, hist[1])]
89 assert list(gothist) == [(1, 2, hist[1])]
91
90
92 gothist = ip.history_manager.search("*=*")
91 gothist = ip.history_manager.search("*=*")
93 assert list(gothist) == [
92 assert list(gothist) == [
94 (1, 1, hist[0]),
93 (1, 1, hist[0]),
95 (1, 2, hist[1]),
94 (1, 2, hist[1]),
96 (1, 3, hist[2]),
95 (1, 3, hist[2]),
97 newhist[0],
96 newhist[0],
98 newhist[2],
97 newhist[2],
99 newhist[3],
98 newhist[3],
100 ]
99 ]
101
100
102 gothist = ip.history_manager.search("*=*", n=4)
101 gothist = ip.history_manager.search("*=*", n=4)
103 assert list(gothist) == [
102 assert list(gothist) == [
104 (1, 3, hist[2]),
103 (1, 3, hist[2]),
105 newhist[0],
104 newhist[0],
106 newhist[2],
105 newhist[2],
107 newhist[3],
106 newhist[3],
108 ]
107 ]
109
108
110 gothist = ip.history_manager.search("*=*", unique=True)
109 gothist = ip.history_manager.search("*=*", unique=True)
111 assert list(gothist) == [
110 assert list(gothist) == [
112 (1, 1, hist[0]),
111 (1, 1, hist[0]),
113 (1, 2, hist[1]),
112 (1, 2, hist[1]),
114 (1, 3, hist[2]),
113 (1, 3, hist[2]),
115 newhist[2],
114 newhist[2],
116 newhist[3],
115 newhist[3],
117 ]
116 ]
118
117
119 gothist = ip.history_manager.search("*=*", unique=True, n=3)
118 gothist = ip.history_manager.search("*=*", unique=True, n=3)
120 assert list(gothist) == [(1, 3, hist[2]), newhist[2], newhist[3]]
119 assert list(gothist) == [(1, 3, hist[2]), newhist[2], newhist[3]]
121
120
122 gothist = ip.history_manager.search("b*", output=True)
121 gothist = ip.history_manager.search("b*", output=True)
123 assert list(gothist) == [(1, 3, (hist[2], "spam"))]
122 assert list(gothist) == [(1, 3, (hist[2], "spam"))]
124
123
125 # Cross testing: check that magic %save can get previous session.
124 # Cross testing: check that magic %save can get previous session.
126 testfilename = (tmp_path / "test.py").resolve()
125 testfilename = (tmp_path / "test.py").resolve()
127 ip.run_line_magic("save", str(testfilename) + " ~1/1-3")
126 ip.run_line_magic("save", str(testfilename) + " ~1/1-3")
128 with io.open(testfilename, encoding="utf-8") as testfile:
127 with io.open(testfilename, encoding="utf-8") as testfile:
129 assert testfile.read() == "# coding: utf-8\n" + "\n".join(hist) + "\n"
128 assert testfile.read() == "# coding: utf-8\n" + "\n".join(hist) + "\n"
130
129
131 # Duplicate line numbers - check that it doesn't crash, and
130 # Duplicate line numbers - check that it doesn't crash, and
132 # gets a new session
131 # gets a new session
133 ip.history_manager.store_inputs(1, "rogue")
132 ip.history_manager.store_inputs(1, "rogue")
134 ip.history_manager.writeout_cache()
133 ip.history_manager.writeout_cache()
135 assert ip.history_manager.session_number == 3
134 assert ip.history_manager.session_number == 3
136
135
137 # Check that session and line values are not just max values
136 # Check that session and line values are not just max values
138 sessid, lineno, entry = newhist[-1]
137 sessid, lineno, entry = newhist[-1]
139 assert lineno > 1
138 assert lineno > 1
140 ip.history_manager.reset()
139 ip.history_manager.reset()
141 lineno = 1
140 lineno = 1
142 ip.history_manager.store_inputs(lineno, entry)
141 ip.history_manager.store_inputs(lineno, entry)
143 gothist = ip.history_manager.search("*=*", unique=True)
142 gothist = ip.history_manager.search("*=*", unique=True)
144 hist = list(gothist)[-1]
143 hist = list(gothist)[-1]
145 assert sessid < hist[0]
144 assert sessid < hist[0]
146 assert hist[1:] == (lineno, entry)
145 assert hist[1:] == (lineno, entry)
147 finally:
146 finally:
148 # Ensure saving thread is shut down before we try to clean up the files
147 # Ensure saving thread is shut down before we try to clean up the files
149 ip.history_manager.save_thread.stop()
148 ip.history_manager.save_thread.stop()
150 # Forcibly close database rather than relying on garbage collection
149 # Forcibly close database rather than relying on garbage collection
151 ip.history_manager.db.close()
150 ip.history_manager.db.close()
152 # Restore history manager
151 # Restore history manager
153 ip.history_manager = hist_manager_ori
152 ip.history_manager = hist_manager_ori
154
153
155
154
156 def test_extract_hist_ranges():
155 def test_extract_hist_ranges():
157 instr = "1 2/3 ~4/5-6 ~4/7-~4/9 ~9/2-~7/5 ~10/"
156 instr = "1 2/3 ~4/5-6 ~4/7-~4/9 ~9/2-~7/5 ~10/"
158 expected = [(0, 1, 2), # 0 == current session
157 expected = [(0, 1, 2), # 0 == current session
159 (2, 3, 4),
158 (2, 3, 4),
160 (-4, 5, 7),
159 (-4, 5, 7),
161 (-4, 7, 10),
160 (-4, 7, 10),
162 (-9, 2, None), # None == to end
161 (-9, 2, None), # None == to end
163 (-8, 1, None),
162 (-8, 1, None),
164 (-7, 1, 6),
163 (-7, 1, 6),
165 (-10, 1, None)]
164 (-10, 1, None)]
166 actual = list(extract_hist_ranges(instr))
165 actual = list(extract_hist_ranges(instr))
167 assert actual == expected
166 assert actual == expected
168
167
169
168
170 def test_extract_hist_ranges_empty_str():
169 def test_extract_hist_ranges_empty_str():
171 instr = ""
170 instr = ""
172 expected = [(0, 1, None)] # 0 == current session, None == to end
171 expected = [(0, 1, None)] # 0 == current session, None == to end
173 actual = list(extract_hist_ranges(instr))
172 actual = list(extract_hist_ranges(instr))
174 assert actual == expected
173 assert actual == expected
175
174
176
175
177 def test_magic_rerun():
176 def test_magic_rerun():
178 """Simple test for %rerun (no args -> rerun last line)"""
177 """Simple test for %rerun (no args -> rerun last line)"""
179 ip = get_ipython()
178 ip = get_ipython()
180 ip.run_cell("a = 10", store_history=True)
179 ip.run_cell("a = 10", store_history=True)
181 ip.run_cell("a += 1", store_history=True)
180 ip.run_cell("a += 1", store_history=True)
182 assert ip.user_ns["a"] == 11
181 assert ip.user_ns["a"] == 11
183 ip.run_cell("%rerun", store_history=True)
182 ip.run_cell("%rerun", store_history=True)
184 assert ip.user_ns["a"] == 12
183 assert ip.user_ns["a"] == 12
185
184
186 def test_timestamp_type():
185 def test_timestamp_type():
187 ip = get_ipython()
186 ip = get_ipython()
188 info = ip.history_manager.get_session_info()
187 info = ip.history_manager.get_session_info()
189 assert isinstance(info[1], datetime)
188 assert isinstance(info[1], datetime)
190
189
191 def test_hist_file_config():
190 def test_hist_file_config():
192 cfg = Config()
191 cfg = Config()
193 tfile = tempfile.NamedTemporaryFile(delete=False)
192 tfile = tempfile.NamedTemporaryFile(delete=False)
194 cfg.HistoryManager.hist_file = Path(tfile.name)
193 cfg.HistoryManager.hist_file = Path(tfile.name)
195 try:
194 try:
196 hm = HistoryManager(shell=get_ipython(), config=cfg)
195 hm = HistoryManager(shell=get_ipython(), config=cfg)
197 assert hm.hist_file == cfg.HistoryManager.hist_file
196 assert hm.hist_file == cfg.HistoryManager.hist_file
198 finally:
197 finally:
199 try:
198 try:
200 Path(tfile.name).unlink()
199 Path(tfile.name).unlink()
201 except OSError:
200 except OSError:
202 # same catch as in testing.tools.TempFileMixin
201 # same catch as in testing.tools.TempFileMixin
203 # On Windows, even though we close the file, we still can't
202 # On Windows, even though we close the file, we still can't
204 # delete it. I have no clue why
203 # delete it. I have no clue why
205 pass
204 pass
206
205
207 def test_histmanager_disabled():
206 def test_histmanager_disabled():
208 """Ensure that disabling the history manager doesn't create a database."""
207 """Ensure that disabling the history manager doesn't create a database."""
209 cfg = Config()
208 cfg = Config()
210 cfg.HistoryAccessor.enabled = False
209 cfg.HistoryAccessor.enabled = False
211
210
212 ip = get_ipython()
211 ip = get_ipython()
213 with TemporaryDirectory() as tmpdir:
212 with TemporaryDirectory() as tmpdir:
214 hist_manager_ori = ip.history_manager
213 hist_manager_ori = ip.history_manager
215 hist_file = Path(tmpdir) / "history.sqlite"
214 hist_file = Path(tmpdir) / "history.sqlite"
216 cfg.HistoryManager.hist_file = hist_file
215 cfg.HistoryManager.hist_file = hist_file
217 try:
216 try:
218 ip.history_manager = HistoryManager(shell=ip, config=cfg)
217 ip.history_manager = HistoryManager(shell=ip, config=cfg)
219 hist = ["a=1", "def f():\n test = 1\n return test", "b='β‚¬Γ†ΒΎΓ·ΓŸ'"]
218 hist = ["a=1", "def f():\n test = 1\n return test", "b='β‚¬Γ†ΒΎΓ·ΓŸ'"]
220 for i, h in enumerate(hist, start=1):
219 for i, h in enumerate(hist, start=1):
221 ip.history_manager.store_inputs(i, h)
220 ip.history_manager.store_inputs(i, h)
222 assert ip.history_manager.input_hist_raw == [""] + hist
221 assert ip.history_manager.input_hist_raw == [""] + hist
223 ip.history_manager.reset()
222 ip.history_manager.reset()
224 ip.history_manager.end_session()
223 ip.history_manager.end_session()
225 finally:
224 finally:
226 ip.history_manager = hist_manager_ori
225 ip.history_manager = hist_manager_ori
227
226
228 # hist_file should not be created
227 # hist_file should not be created
229 assert hist_file.exists() is False
228 assert hist_file.exists() is False
230
229
231
230
232 def test_get_tail_session_awareness():
231 def test_get_tail_session_awareness():
233 """Test .get_tail() is:
232 """Test .get_tail() is:
234 - session specific in HistoryManager
233 - session specific in HistoryManager
235 - session agnostic in HistoryAccessor
234 - session agnostic in HistoryAccessor
236 same for .get_last_session_id()
235 same for .get_last_session_id()
237 """
236 """
238 ip = get_ipython()
237 ip = get_ipython()
239 with TemporaryDirectory() as tmpdir:
238 with TemporaryDirectory() as tmpdir:
240 tmp_path = Path(tmpdir)
239 tmp_path = Path(tmpdir)
241 hist_file = tmp_path / "history.sqlite"
240 hist_file = tmp_path / "history.sqlite"
242 get_source = lambda x: x[2]
241 get_source = lambda x: x[2]
243 hm1 = None
242 hm1 = None
244 hm2 = None
243 hm2 = None
245 ha = None
244 ha = None
246 try:
245 try:
247 # hm1 creates a new session and adds history entries,
246 # hm1 creates a new session and adds history entries,
248 # ha catches up
247 # ha catches up
249 hm1 = HistoryManager(shell=ip, hist_file=hist_file)
248 hm1 = HistoryManager(shell=ip, hist_file=hist_file)
250 hm1_last_sid = hm1.get_last_session_id
249 hm1_last_sid = hm1.get_last_session_id
251 ha = HistoryAccessor(hist_file=hist_file)
250 ha = HistoryAccessor(hist_file=hist_file)
252 ha_last_sid = ha.get_last_session_id
251 ha_last_sid = ha.get_last_session_id
253
252
254 hist1 = ["a=1", "b=1", "c=1"]
253 hist1 = ["a=1", "b=1", "c=1"]
255 for i, h in enumerate(hist1 + [""], start=1):
254 for i, h in enumerate(hist1 + [""], start=1):
256 hm1.store_inputs(i, h)
255 hm1.store_inputs(i, h)
257 assert list(map(get_source, hm1.get_tail())) == hist1
256 assert list(map(get_source, hm1.get_tail())) == hist1
258 assert list(map(get_source, ha.get_tail())) == hist1
257 assert list(map(get_source, ha.get_tail())) == hist1
259 sid1 = hm1_last_sid()
258 sid1 = hm1_last_sid()
260 assert sid1 is not None
259 assert sid1 is not None
261 assert ha_last_sid() == sid1
260 assert ha_last_sid() == sid1
262
261
263 # hm2 creates a new session and adds entries,
262 # hm2 creates a new session and adds entries,
264 # ha catches up
263 # ha catches up
265 hm2 = HistoryManager(shell=ip, hist_file=hist_file)
264 hm2 = HistoryManager(shell=ip, hist_file=hist_file)
266 hm2_last_sid = hm2.get_last_session_id
265 hm2_last_sid = hm2.get_last_session_id
267
266
268 hist2 = ["a=2", "b=2", "c=2"]
267 hist2 = ["a=2", "b=2", "c=2"]
269 for i, h in enumerate(hist2 + [""], start=1):
268 for i, h in enumerate(hist2 + [""], start=1):
270 hm2.store_inputs(i, h)
269 hm2.store_inputs(i, h)
271 tail = hm2.get_tail(n=3)
270 tail = hm2.get_tail(n=3)
272 assert list(map(get_source, tail)) == hist2
271 assert list(map(get_source, tail)) == hist2
273 tail = ha.get_tail(n=3)
272 tail = ha.get_tail(n=3)
274 assert list(map(get_source, tail)) == hist2
273 assert list(map(get_source, tail)) == hist2
275 sid2 = hm2_last_sid()
274 sid2 = hm2_last_sid()
276 assert sid2 is not None
275 assert sid2 is not None
277 assert ha_last_sid() == sid2
276 assert ha_last_sid() == sid2
278 assert sid2 != sid1
277 assert sid2 != sid1
279
278
280 # but hm1 still maintains its point of reference
279 # but hm1 still maintains its point of reference
281 # and adding more entries to it doesn't change others
280 # and adding more entries to it doesn't change others
282 # immediate perspective
281 # immediate perspective
283 assert hm1_last_sid() == sid1
282 assert hm1_last_sid() == sid1
284 tail = hm1.get_tail(n=3)
283 tail = hm1.get_tail(n=3)
285 assert list(map(get_source, tail)) == hist1
284 assert list(map(get_source, tail)) == hist1
286
285
287 hist3 = ["a=3", "b=3", "c=3"]
286 hist3 = ["a=3", "b=3", "c=3"]
288 for i, h in enumerate(hist3 + [""], start=5):
287 for i, h in enumerate(hist3 + [""], start=5):
289 hm1.store_inputs(i, h)
288 hm1.store_inputs(i, h)
290 tail = hm1.get_tail(n=7)
289 tail = hm1.get_tail(n=7)
291 assert list(map(get_source, tail)) == hist1 + [""] + hist3
290 assert list(map(get_source, tail)) == hist1 + [""] + hist3
292 tail = hm2.get_tail(n=3)
291 tail = hm2.get_tail(n=3)
293 assert list(map(get_source, tail)) == hist2
292 assert list(map(get_source, tail)) == hist2
294 tail = ha.get_tail(n=3)
293 tail = ha.get_tail(n=3)
295 assert list(map(get_source, tail)) == hist2
294 assert list(map(get_source, tail)) == hist2
296 assert hm1_last_sid() == sid1
295 assert hm1_last_sid() == sid1
297 assert hm2_last_sid() == sid2
296 assert hm2_last_sid() == sid2
298 assert ha_last_sid() == sid2
297 assert ha_last_sid() == sid2
299 finally:
298 finally:
300 if hm1:
299 if hm1:
301 hm1.save_thread.stop()
300 hm1.save_thread.stop()
302 hm1.db.close()
301 hm1.db.close()
303 if hm2:
302 if hm2:
304 hm2.save_thread.stop()
303 hm2.save_thread.stop()
305 hm2.db.close()
304 hm2.db.close()
306 if ha:
305 if ha:
307 ha.db.close()
306 ha.db.close()
@@ -1,168 +1,167 b''
1 """Tests for the line-based transformers in IPython.core.inputtransformer2
1 """Tests for the line-based transformers in IPython.core.inputtransformer2
2
2
3 Line-based transformers are the simpler ones; token-based transformers are
3 Line-based transformers are the simpler ones; token-based transformers are
4 more complex. See test_inputtransformer2 for tests for token-based transformers.
4 more complex. See test_inputtransformer2 for tests for token-based transformers.
5 """
5 """
6 import pytest
7
6
8 from IPython.core import inputtransformer2 as ipt2
7 from IPython.core import inputtransformer2 as ipt2
9
8
10 CELL_MAGIC = ("""\
9 CELL_MAGIC = ("""\
11 %%foo arg
10 %%foo arg
12 body 1
11 body 1
13 body 2
12 body 2
14 """, """\
13 """, """\
15 get_ipython().run_cell_magic('foo', 'arg', 'body 1\\nbody 2\\n')
14 get_ipython().run_cell_magic('foo', 'arg', 'body 1\\nbody 2\\n')
16 """)
15 """)
17
16
18 def test_cell_magic():
17 def test_cell_magic():
19 for sample, expected in [CELL_MAGIC]:
18 for sample, expected in [CELL_MAGIC]:
20 assert ipt2.cell_magic(sample.splitlines(keepends=True)) == expected.splitlines(
19 assert ipt2.cell_magic(sample.splitlines(keepends=True)) == expected.splitlines(
21 keepends=True
20 keepends=True
22 )
21 )
23
22
24 CLASSIC_PROMPT = ("""\
23 CLASSIC_PROMPT = ("""\
25 >>> for a in range(5):
24 >>> for a in range(5):
26 ... print(a)
25 ... print(a)
27 """, """\
26 """, """\
28 for a in range(5):
27 for a in range(5):
29 print(a)
28 print(a)
30 """)
29 """)
31
30
32 CLASSIC_PROMPT_L2 = ("""\
31 CLASSIC_PROMPT_L2 = ("""\
33 for a in range(5):
32 for a in range(5):
34 ... print(a)
33 ... print(a)
35 ... print(a ** 2)
34 ... print(a ** 2)
36 """, """\
35 """, """\
37 for a in range(5):
36 for a in range(5):
38 print(a)
37 print(a)
39 print(a ** 2)
38 print(a ** 2)
40 """)
39 """)
41
40
42 def test_classic_prompt():
41 def test_classic_prompt():
43 for sample, expected in [CLASSIC_PROMPT, CLASSIC_PROMPT_L2]:
42 for sample, expected in [CLASSIC_PROMPT, CLASSIC_PROMPT_L2]:
44 assert ipt2.classic_prompt(
43 assert ipt2.classic_prompt(
45 sample.splitlines(keepends=True)
44 sample.splitlines(keepends=True)
46 ) == expected.splitlines(keepends=True)
45 ) == expected.splitlines(keepends=True)
47
46
48 IPYTHON_PROMPT = ("""\
47 IPYTHON_PROMPT = ("""\
49 In [1]: for a in range(5):
48 In [1]: for a in range(5):
50 ...: print(a)
49 ...: print(a)
51 """, """\
50 """, """\
52 for a in range(5):
51 for a in range(5):
53 print(a)
52 print(a)
54 """)
53 """)
55
54
56 IPYTHON_PROMPT_L2 = ("""\
55 IPYTHON_PROMPT_L2 = ("""\
57 for a in range(5):
56 for a in range(5):
58 ...: print(a)
57 ...: print(a)
59 ...: print(a ** 2)
58 ...: print(a ** 2)
60 """, """\
59 """, """\
61 for a in range(5):
60 for a in range(5):
62 print(a)
61 print(a)
63 print(a ** 2)
62 print(a ** 2)
64 """)
63 """)
65
64
66
65
67 IPYTHON_PROMPT_VI_INS = (
66 IPYTHON_PROMPT_VI_INS = (
68 """\
67 """\
69 [ins] In [11]: def a():
68 [ins] In [11]: def a():
70 ...: 123
69 ...: 123
71 ...:
70 ...:
72 ...: 123
71 ...: 123
73 """,
72 """,
74 """\
73 """\
75 def a():
74 def a():
76 123
75 123
77
76
78 123
77 123
79 """,
78 """,
80 )
79 )
81
80
82 IPYTHON_PROMPT_VI_NAV = (
81 IPYTHON_PROMPT_VI_NAV = (
83 """\
82 """\
84 [nav] In [11]: def a():
83 [nav] In [11]: def a():
85 ...: 123
84 ...: 123
86 ...:
85 ...:
87 ...: 123
86 ...: 123
88 """,
87 """,
89 """\
88 """\
90 def a():
89 def a():
91 123
90 123
92
91
93 123
92 123
94 """,
93 """,
95 )
94 )
96
95
97
96
98 def test_ipython_prompt():
97 def test_ipython_prompt():
99 for sample, expected in [
98 for sample, expected in [
100 IPYTHON_PROMPT,
99 IPYTHON_PROMPT,
101 IPYTHON_PROMPT_L2,
100 IPYTHON_PROMPT_L2,
102 IPYTHON_PROMPT_VI_INS,
101 IPYTHON_PROMPT_VI_INS,
103 IPYTHON_PROMPT_VI_NAV,
102 IPYTHON_PROMPT_VI_NAV,
104 ]:
103 ]:
105 assert ipt2.ipython_prompt(
104 assert ipt2.ipython_prompt(
106 sample.splitlines(keepends=True)
105 sample.splitlines(keepends=True)
107 ) == expected.splitlines(keepends=True)
106 ) == expected.splitlines(keepends=True)
108
107
109
108
110 INDENT_SPACES = ("""\
109 INDENT_SPACES = ("""\
111 if True:
110 if True:
112 a = 3
111 a = 3
113 """, """\
112 """, """\
114 if True:
113 if True:
115 a = 3
114 a = 3
116 """)
115 """)
117
116
118 INDENT_TABS = ("""\
117 INDENT_TABS = ("""\
119 \tif True:
118 \tif True:
120 \t\tb = 4
119 \t\tb = 4
121 """, """\
120 """, """\
122 if True:
121 if True:
123 \tb = 4
122 \tb = 4
124 """)
123 """)
125
124
126 def test_leading_indent():
125 def test_leading_indent():
127 for sample, expected in [INDENT_SPACES, INDENT_TABS]:
126 for sample, expected in [INDENT_SPACES, INDENT_TABS]:
128 assert ipt2.leading_indent(
127 assert ipt2.leading_indent(
129 sample.splitlines(keepends=True)
128 sample.splitlines(keepends=True)
130 ) == expected.splitlines(keepends=True)
129 ) == expected.splitlines(keepends=True)
131
130
132 LEADING_EMPTY_LINES = ("""\
131 LEADING_EMPTY_LINES = ("""\
133 \t
132 \t
134
133
135 if True:
134 if True:
136 a = 3
135 a = 3
137
136
138 b = 4
137 b = 4
139 """, """\
138 """, """\
140 if True:
139 if True:
141 a = 3
140 a = 3
142
141
143 b = 4
142 b = 4
144 """)
143 """)
145
144
146 ONLY_EMPTY_LINES = ("""\
145 ONLY_EMPTY_LINES = ("""\
147 \t
146 \t
148
147
149 """, """\
148 """, """\
150 \t
149 \t
151
150
152 """)
151 """)
153
152
154 def test_leading_empty_lines():
153 def test_leading_empty_lines():
155 for sample, expected in [LEADING_EMPTY_LINES, ONLY_EMPTY_LINES]:
154 for sample, expected in [LEADING_EMPTY_LINES, ONLY_EMPTY_LINES]:
156 assert ipt2.leading_empty_lines(
155 assert ipt2.leading_empty_lines(
157 sample.splitlines(keepends=True)
156 sample.splitlines(keepends=True)
158 ) == expected.splitlines(keepends=True)
157 ) == expected.splitlines(keepends=True)
159
158
160 CRLF_MAGIC = ([
159 CRLF_MAGIC = ([
161 "%%ls\r\n"
160 "%%ls\r\n"
162 ], [
161 ], [
163 "get_ipython().run_cell_magic('ls', '', '')\n"
162 "get_ipython().run_cell_magic('ls', '', '')\n"
164 ])
163 ])
165
164
166 def test_crlf_magic():
165 def test_crlf_magic():
167 for sample, expected in [CRLF_MAGIC]:
166 for sample, expected in [CRLF_MAGIC]:
168 assert ipt2.cell_magic(sample) == expected
167 assert ipt2.cell_magic(sample) == expected
@@ -1,250 +1,248 b''
1 """Tests for the key interactiveshell module, where the main ipython class is defined.
1 """Tests for the key interactiveshell module, where the main ipython class is defined.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Module imports
4 # Module imports
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6
6
7 # third party
8 import pytest
9
7
10 # our own packages
8 # our own packages
11
9
12 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
13 # Test functions
11 # Test functions
14 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
15
13
16 def test_reset():
14 def test_reset():
17 """reset must clear most namespaces."""
15 """reset must clear most namespaces."""
18
16
19 # Check that reset runs without error
17 # Check that reset runs without error
20 ip.reset()
18 ip.reset()
21
19
22 # Once we've reset it (to clear of any junk that might have been there from
20 # Once we've reset it (to clear of any junk that might have been there from
23 # other tests, we can count how many variables are in the user's namespace
21 # other tests, we can count how many variables are in the user's namespace
24 nvars_user_ns = len(ip.user_ns)
22 nvars_user_ns = len(ip.user_ns)
25 nvars_hidden = len(ip.user_ns_hidden)
23 nvars_hidden = len(ip.user_ns_hidden)
26
24
27 # Now add a few variables to user_ns, and check that reset clears them
25 # Now add a few variables to user_ns, and check that reset clears them
28 ip.user_ns['x'] = 1
26 ip.user_ns['x'] = 1
29 ip.user_ns['y'] = 1
27 ip.user_ns['y'] = 1
30 ip.reset()
28 ip.reset()
31
29
32 # Finally, check that all namespaces have only as many variables as we
30 # Finally, check that all namespaces have only as many variables as we
33 # expect to find in them:
31 # expect to find in them:
34 assert len(ip.user_ns) == nvars_user_ns
32 assert len(ip.user_ns) == nvars_user_ns
35 assert len(ip.user_ns_hidden) == nvars_hidden
33 assert len(ip.user_ns_hidden) == nvars_hidden
36
34
37
35
38 # Tests for reporting of exceptions in various modes, handling of SystemExit,
36 # Tests for reporting of exceptions in various modes, handling of SystemExit,
39 # and %tb functionality. This is really a mix of testing ultraTB and interactiveshell.
37 # and %tb functionality. This is really a mix of testing ultraTB and interactiveshell.
40
38
41 def doctest_tb_plain():
39 def doctest_tb_plain():
42 """
40 """
43 In [18]: xmode plain
41 In [18]: xmode plain
44 Exception reporting mode: Plain
42 Exception reporting mode: Plain
45
43
46 In [19]: run simpleerr.py
44 In [19]: run simpleerr.py
47 Traceback (most recent call last):
45 Traceback (most recent call last):
48 File ...:...
46 File ...:...
49 bar(mode)
47 bar(mode)
50 File ...:... in bar
48 File ...:... in bar
51 div0()
49 div0()
52 File ...:... in div0
50 File ...:... in div0
53 x/y
51 x/y
54 ZeroDivisionError: ...
52 ZeroDivisionError: ...
55 """
53 """
56
54
57
55
58 def doctest_tb_context():
56 def doctest_tb_context():
59 """
57 """
60 In [3]: xmode context
58 In [3]: xmode context
61 Exception reporting mode: Context
59 Exception reporting mode: Context
62
60
63 In [4]: run simpleerr.py
61 In [4]: run simpleerr.py
64 ---------------------------------------------------------------------------
62 ---------------------------------------------------------------------------
65 ZeroDivisionError Traceback (most recent call last)
63 ZeroDivisionError Traceback (most recent call last)
66 <BLANKLINE>
64 <BLANKLINE>
67 ...
65 ...
68 30 except IndexError:
66 30 except IndexError:
69 31 mode = 'div'
67 31 mode = 'div'
70 ---> 33 bar(mode)
68 ---> 33 bar(mode)
71 <BLANKLINE>
69 <BLANKLINE>
72 ... in bar(mode)
70 ... in bar(mode)
73 15 "bar"
71 15 "bar"
74 16 if mode=='div':
72 16 if mode=='div':
75 ---> 17 div0()
73 ---> 17 div0()
76 18 elif mode=='exit':
74 18 elif mode=='exit':
77 19 try:
75 19 try:
78 <BLANKLINE>
76 <BLANKLINE>
79 ... in div0()
77 ... in div0()
80 6 x = 1
78 6 x = 1
81 7 y = 0
79 7 y = 0
82 ----> 8 x/y
80 ----> 8 x/y
83 <BLANKLINE>
81 <BLANKLINE>
84 ZeroDivisionError: ..."""
82 ZeroDivisionError: ..."""
85
83
86
84
87 def doctest_tb_verbose():
85 def doctest_tb_verbose():
88 """
86 """
89 In [5]: xmode verbose
87 In [5]: xmode verbose
90 Exception reporting mode: Verbose
88 Exception reporting mode: Verbose
91
89
92 In [6]: run simpleerr.py
90 In [6]: run simpleerr.py
93 ---------------------------------------------------------------------------
91 ---------------------------------------------------------------------------
94 ZeroDivisionError Traceback (most recent call last)
92 ZeroDivisionError Traceback (most recent call last)
95 <BLANKLINE>
93 <BLANKLINE>
96 ...
94 ...
97 30 except IndexError:
95 30 except IndexError:
98 31 mode = 'div'
96 31 mode = 'div'
99 ---> 33 bar(mode)
97 ---> 33 bar(mode)
100 mode = 'div'
98 mode = 'div'
101 <BLANKLINE>
99 <BLANKLINE>
102 ... in bar(mode='div')
100 ... in bar(mode='div')
103 15 "bar"
101 15 "bar"
104 16 if mode=='div':
102 16 if mode=='div':
105 ---> 17 div0()
103 ---> 17 div0()
106 18 elif mode=='exit':
104 18 elif mode=='exit':
107 19 try:
105 19 try:
108 <BLANKLINE>
106 <BLANKLINE>
109 ... in div0()
107 ... in div0()
110 6 x = 1
108 6 x = 1
111 7 y = 0
109 7 y = 0
112 ----> 8 x/y
110 ----> 8 x/y
113 x = 1
111 x = 1
114 y = 0
112 y = 0
115 <BLANKLINE>
113 <BLANKLINE>
116 ZeroDivisionError: ...
114 ZeroDivisionError: ...
117 """
115 """
118
116
119
117
120 def doctest_tb_sysexit():
118 def doctest_tb_sysexit():
121 """
119 """
122 In [17]: %xmode plain
120 In [17]: %xmode plain
123 Exception reporting mode: Plain
121 Exception reporting mode: Plain
124
122
125 In [18]: %run simpleerr.py exit
123 In [18]: %run simpleerr.py exit
126 An exception has occurred, use %tb to see the full traceback.
124 An exception has occurred, use %tb to see the full traceback.
127 SystemExit: (1, 'Mode = exit')
125 SystemExit: (1, 'Mode = exit')
128
126
129 In [19]: %run simpleerr.py exit 2
127 In [19]: %run simpleerr.py exit 2
130 An exception has occurred, use %tb to see the full traceback.
128 An exception has occurred, use %tb to see the full traceback.
131 SystemExit: (2, 'Mode = exit')
129 SystemExit: (2, 'Mode = exit')
132
130
133 In [20]: %tb
131 In [20]: %tb
134 Traceback (most recent call last):
132 Traceback (most recent call last):
135 File ...:... in execfile
133 File ...:... in execfile
136 exec(compiler(f.read(), fname, "exec"), glob, loc)
134 exec(compiler(f.read(), fname, "exec"), glob, loc)
137 File ...:...
135 File ...:...
138 bar(mode)
136 bar(mode)
139 File ...:... in bar
137 File ...:... in bar
140 sysexit(stat, mode)
138 sysexit(stat, mode)
141 File ...:... in sysexit
139 File ...:... in sysexit
142 raise SystemExit(stat, f"Mode = {mode}")
140 raise SystemExit(stat, f"Mode = {mode}")
143 SystemExit: (2, 'Mode = exit')
141 SystemExit: (2, 'Mode = exit')
144
142
145 In [21]: %xmode context
143 In [21]: %xmode context
146 Exception reporting mode: Context
144 Exception reporting mode: Context
147
145
148 In [22]: %tb
146 In [22]: %tb
149 ---------------------------------------------------------------------------
147 ---------------------------------------------------------------------------
150 SystemExit Traceback (most recent call last)
148 SystemExit Traceback (most recent call last)
151 File ..., in execfile(fname, glob, loc, compiler)
149 File ..., in execfile(fname, glob, loc, compiler)
152 ... with open(fname, "rb") as f:
150 ... with open(fname, "rb") as f:
153 ... compiler = compiler or compile
151 ... compiler = compiler or compile
154 ---> ... exec(compiler(f.read(), fname, "exec"), glob, loc)
152 ---> ... exec(compiler(f.read(), fname, "exec"), glob, loc)
155 ...
153 ...
156 30 except IndexError:
154 30 except IndexError:
157 31 mode = 'div'
155 31 mode = 'div'
158 ---> 33 bar(mode)
156 ---> 33 bar(mode)
159 <BLANKLINE>
157 <BLANKLINE>
160 ...bar(mode)
158 ...bar(mode)
161 21 except:
159 21 except:
162 22 stat = 1
160 22 stat = 1
163 ---> 23 sysexit(stat, mode)
161 ---> 23 sysexit(stat, mode)
164 24 else:
162 24 else:
165 25 raise ValueError('Unknown mode')
163 25 raise ValueError('Unknown mode')
166 <BLANKLINE>
164 <BLANKLINE>
167 ...sysexit(stat, mode)
165 ...sysexit(stat, mode)
168 10 def sysexit(stat, mode):
166 10 def sysexit(stat, mode):
169 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
167 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
170 <BLANKLINE>
168 <BLANKLINE>
171 SystemExit: (2, 'Mode = exit')
169 SystemExit: (2, 'Mode = exit')
172 """
170 """
173
171
174
172
175 def doctest_tb_sysexit_verbose():
173 def doctest_tb_sysexit_verbose():
176 """
174 """
177 In [18]: %run simpleerr.py exit
175 In [18]: %run simpleerr.py exit
178 An exception has occurred, use %tb to see the full traceback.
176 An exception has occurred, use %tb to see the full traceback.
179 SystemExit: (1, 'Mode = exit')
177 SystemExit: (1, 'Mode = exit')
180
178
181 In [19]: %run simpleerr.py exit 2
179 In [19]: %run simpleerr.py exit 2
182 An exception has occurred, use %tb to see the full traceback.
180 An exception has occurred, use %tb to see the full traceback.
183 SystemExit: (2, 'Mode = exit')
181 SystemExit: (2, 'Mode = exit')
184
182
185 In [23]: %xmode verbose
183 In [23]: %xmode verbose
186 Exception reporting mode: Verbose
184 Exception reporting mode: Verbose
187
185
188 In [24]: %tb
186 In [24]: %tb
189 ---------------------------------------------------------------------------
187 ---------------------------------------------------------------------------
190 SystemExit Traceback (most recent call last)
188 SystemExit Traceback (most recent call last)
191 <BLANKLINE>
189 <BLANKLINE>
192 ...
190 ...
193 30 except IndexError:
191 30 except IndexError:
194 31 mode = 'div'
192 31 mode = 'div'
195 ---> 33 bar(mode)
193 ---> 33 bar(mode)
196 mode = 'exit'
194 mode = 'exit'
197 <BLANKLINE>
195 <BLANKLINE>
198 ... in bar(mode='exit')
196 ... in bar(mode='exit')
199 ... except:
197 ... except:
200 ... stat = 1
198 ... stat = 1
201 ---> ... sysexit(stat, mode)
199 ---> ... sysexit(stat, mode)
202 mode = 'exit'
200 mode = 'exit'
203 stat = 2
201 stat = 2
204 ... else:
202 ... else:
205 ... raise ValueError('Unknown mode')
203 ... raise ValueError('Unknown mode')
206 <BLANKLINE>
204 <BLANKLINE>
207 ... in sysexit(stat=2, mode='exit')
205 ... in sysexit(stat=2, mode='exit')
208 10 def sysexit(stat, mode):
206 10 def sysexit(stat, mode):
209 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
207 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
210 stat = 2
208 stat = 2
211 <BLANKLINE>
209 <BLANKLINE>
212 SystemExit: (2, 'Mode = exit')
210 SystemExit: (2, 'Mode = exit')
213 """
211 """
214
212
215
213
216 def test_run_cell():
214 def test_run_cell():
217 import textwrap
215 import textwrap
218
216
219 ip.run_cell("a = 10\na+=1")
217 ip.run_cell("a = 10\na+=1")
220 ip.run_cell("assert a == 11\nassert 1")
218 ip.run_cell("assert a == 11\nassert 1")
221
219
222 assert ip.user_ns["a"] == 11
220 assert ip.user_ns["a"] == 11
223 complex = textwrap.dedent(
221 complex = textwrap.dedent(
224 """
222 """
225 if 1:
223 if 1:
226 print "hello"
224 print "hello"
227 if 1:
225 if 1:
228 print "world"
226 print "world"
229
227
230 if 2:
228 if 2:
231 print "foo"
229 print "foo"
232
230
233 if 3:
231 if 3:
234 print "bar"
232 print "bar"
235
233
236 if 4:
234 if 4:
237 print "bar"
235 print "bar"
238
236
239 """
237 """
240 )
238 )
241 # Simply verifies that this kind of input is run
239 # Simply verifies that this kind of input is run
242 ip.run_cell(complex)
240 ip.run_cell(complex)
243
241
244
242
245 def test_db():
243 def test_db():
246 """Test the internal database used for variable persistence."""
244 """Test the internal database used for variable persistence."""
247 ip.db["__unittest_"] = 12
245 ip.db["__unittest_"] = 12
248 assert ip.db["__unittest_"] == 12
246 assert ip.db["__unittest_"] == 12
249 del ip.db["__unittest_"]
247 del ip.db["__unittest_"]
250 assert "__unittest_" not in ip.db
248 assert "__unittest_" not in ip.db
@@ -1,1452 +1,1451 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
5 import gc
4 import gc
6 import io
5 import io
7 import os
6 import os
8 import re
7 import re
9 import shlex
8 import shlex
10 import sys
9 import sys
11 import warnings
10 import warnings
12 from importlib import invalidate_caches
11 from importlib import invalidate_caches
13 from io import StringIO
12 from io import StringIO
14 from pathlib import Path
13 from pathlib import Path
15 from textwrap import dedent
14 from textwrap import dedent
16 from unittest import TestCase, mock
15 from unittest import TestCase, mock
17
16
18 import pytest
17 import pytest
19
18
20 from IPython import get_ipython
19 from IPython import get_ipython
21 from IPython.core import magic
20 from IPython.core import magic
22 from IPython.core.error import UsageError
21 from IPython.core.error import UsageError
23 from IPython.core.magic import (
22 from IPython.core.magic import (
24 Magics,
23 Magics,
25 cell_magic,
24 cell_magic,
26 line_magic,
25 line_magic,
27 magics_class,
26 magics_class,
28 register_cell_magic,
27 register_cell_magic,
29 register_line_magic,
28 register_line_magic,
30 )
29 )
31 from IPython.core.magics import code, execution, logging, osm, script
30 from IPython.core.magics import code, execution, logging, osm, script
32 from IPython.testing import decorators as dec
31 from IPython.testing import decorators as dec
33 from IPython.testing import tools as tt
32 from IPython.testing import tools as tt
34 from IPython.utils.io import capture_output
33 from IPython.utils.io import capture_output
35 from IPython.utils.process import find_cmd
34 from IPython.utils.process import find_cmd
36 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
35 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
37 from IPython.utils.syspathcontext import prepended_to_syspath
36 from IPython.utils.syspathcontext import prepended_to_syspath
38
37
39 from .test_debugger import PdbTestInput
38 from .test_debugger import PdbTestInput
40
39
41 from tempfile import NamedTemporaryFile
40 from tempfile import NamedTemporaryFile
42
41
43 @magic.magics_class
42 @magic.magics_class
44 class DummyMagics(magic.Magics): pass
43 class DummyMagics(magic.Magics): pass
45
44
46 def test_extract_code_ranges():
45 def test_extract_code_ranges():
47 instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :"
46 instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :"
48 expected = [
47 expected = [
49 (0, 1),
48 (0, 1),
50 (2, 3),
49 (2, 3),
51 (4, 6),
50 (4, 6),
52 (6, 9),
51 (6, 9),
53 (9, 14),
52 (9, 14),
54 (16, None),
53 (16, None),
55 (None, 9),
54 (None, 9),
56 (9, None),
55 (9, None),
57 (None, 13),
56 (None, 13),
58 (None, None),
57 (None, None),
59 ]
58 ]
60 actual = list(code.extract_code_ranges(instr))
59 actual = list(code.extract_code_ranges(instr))
61 assert actual == expected
60 assert actual == expected
62
61
63 def test_extract_symbols():
62 def test_extract_symbols():
64 source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n"""
63 source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n"""
65 symbols_args = ["a", "b", "A", "A,b", "A,a", "z"]
64 symbols_args = ["a", "b", "A", "A,b", "A,a", "z"]
66 expected = [([], ['a']),
65 expected = [([], ['a']),
67 (["def b():\n return 42\n"], []),
66 (["def b():\n return 42\n"], []),
68 (["class A: pass\n"], []),
67 (["class A: pass\n"], []),
69 (["class A: pass\n", "def b():\n return 42\n"], []),
68 (["class A: pass\n", "def b():\n return 42\n"], []),
70 (["class A: pass\n"], ['a']),
69 (["class A: pass\n"], ['a']),
71 ([], ['z'])]
70 ([], ['z'])]
72 for symbols, exp in zip(symbols_args, expected):
71 for symbols, exp in zip(symbols_args, expected):
73 assert code.extract_symbols(source, symbols) == exp
72 assert code.extract_symbols(source, symbols) == exp
74
73
75
74
76 def test_extract_symbols_raises_exception_with_non_python_code():
75 def test_extract_symbols_raises_exception_with_non_python_code():
77 source = ("=begin A Ruby program :)=end\n"
76 source = ("=begin A Ruby program :)=end\n"
78 "def hello\n"
77 "def hello\n"
79 "puts 'Hello world'\n"
78 "puts 'Hello world'\n"
80 "end")
79 "end")
81 with pytest.raises(SyntaxError):
80 with pytest.raises(SyntaxError):
82 code.extract_symbols(source, "hello")
81 code.extract_symbols(source, "hello")
83
82
84
83
85 def test_magic_not_found():
84 def test_magic_not_found():
86 # magic not found raises UsageError
85 # magic not found raises UsageError
87 with pytest.raises(UsageError):
86 with pytest.raises(UsageError):
88 _ip.magic('doesntexist')
87 _ip.magic('doesntexist')
89
88
90 # ensure result isn't success when a magic isn't found
89 # ensure result isn't success when a magic isn't found
91 result = _ip.run_cell('%doesntexist')
90 result = _ip.run_cell('%doesntexist')
92 assert isinstance(result.error_in_exec, UsageError)
91 assert isinstance(result.error_in_exec, UsageError)
93
92
94
93
95 def test_cell_magic_not_found():
94 def test_cell_magic_not_found():
96 # magic not found raises UsageError
95 # magic not found raises UsageError
97 with pytest.raises(UsageError):
96 with pytest.raises(UsageError):
98 _ip.run_cell_magic('doesntexist', 'line', 'cell')
97 _ip.run_cell_magic('doesntexist', 'line', 'cell')
99
98
100 # ensure result isn't success when a magic isn't found
99 # ensure result isn't success when a magic isn't found
101 result = _ip.run_cell('%%doesntexist')
100 result = _ip.run_cell('%%doesntexist')
102 assert isinstance(result.error_in_exec, UsageError)
101 assert isinstance(result.error_in_exec, UsageError)
103
102
104
103
105 def test_magic_error_status():
104 def test_magic_error_status():
106 def fail(shell):
105 def fail(shell):
107 1/0
106 1/0
108 _ip.register_magic_function(fail)
107 _ip.register_magic_function(fail)
109 result = _ip.run_cell('%fail')
108 result = _ip.run_cell('%fail')
110 assert isinstance(result.error_in_exec, ZeroDivisionError)
109 assert isinstance(result.error_in_exec, ZeroDivisionError)
111
110
112
111
113 def test_config():
112 def test_config():
114 """ test that config magic does not raise
113 """ test that config magic does not raise
115 can happen if Configurable init is moved too early into
114 can happen if Configurable init is moved too early into
116 Magics.__init__ as then a Config object will be registered as a
115 Magics.__init__ as then a Config object will be registered as a
117 magic.
116 magic.
118 """
117 """
119 ## should not raise.
118 ## should not raise.
120 _ip.magic('config')
119 _ip.magic('config')
121
120
122 def test_config_available_configs():
121 def test_config_available_configs():
123 """ test that config magic prints available configs in unique and
122 """ test that config magic prints available configs in unique and
124 sorted order. """
123 sorted order. """
125 with capture_output() as captured:
124 with capture_output() as captured:
126 _ip.magic('config')
125 _ip.magic('config')
127
126
128 stdout = captured.stdout
127 stdout = captured.stdout
129 config_classes = stdout.strip().split('\n')[1:]
128 config_classes = stdout.strip().split('\n')[1:]
130 assert config_classes == sorted(set(config_classes))
129 assert config_classes == sorted(set(config_classes))
131
130
132 def test_config_print_class():
131 def test_config_print_class():
133 """ test that config with a classname prints the class's options. """
132 """ test that config with a classname prints the class's options. """
134 with capture_output() as captured:
133 with capture_output() as captured:
135 _ip.magic('config TerminalInteractiveShell')
134 _ip.magic('config TerminalInteractiveShell')
136
135
137 stdout = captured.stdout
136 stdout = captured.stdout
138 assert re.match(
137 assert re.match(
139 "TerminalInteractiveShell.* options", stdout.splitlines()[0]
138 "TerminalInteractiveShell.* options", stdout.splitlines()[0]
140 ), f"{stdout}\n\n1st line of stdout not like 'TerminalInteractiveShell.* options'"
139 ), f"{stdout}\n\n1st line of stdout not like 'TerminalInteractiveShell.* options'"
141
140
142
141
143 def test_rehashx():
142 def test_rehashx():
144 # clear up everything
143 # clear up everything
145 _ip.alias_manager.clear_aliases()
144 _ip.alias_manager.clear_aliases()
146 del _ip.db['syscmdlist']
145 del _ip.db['syscmdlist']
147
146
148 _ip.magic('rehashx')
147 _ip.magic('rehashx')
149 # Practically ALL ipython development systems will have more than 10 aliases
148 # Practically ALL ipython development systems will have more than 10 aliases
150
149
151 assert len(_ip.alias_manager.aliases) > 10
150 assert len(_ip.alias_manager.aliases) > 10
152 for name, cmd in _ip.alias_manager.aliases:
151 for name, cmd in _ip.alias_manager.aliases:
153 # we must strip dots from alias names
152 # we must strip dots from alias names
154 assert "." not in name
153 assert "." not in name
155
154
156 # rehashx must fill up syscmdlist
155 # rehashx must fill up syscmdlist
157 scoms = _ip.db['syscmdlist']
156 scoms = _ip.db['syscmdlist']
158 assert len(scoms) > 10
157 assert len(scoms) > 10
159
158
160
159
161 def test_magic_parse_options():
160 def test_magic_parse_options():
162 """Test that we don't mangle paths when parsing magic options."""
161 """Test that we don't mangle paths when parsing magic options."""
163 ip = get_ipython()
162 ip = get_ipython()
164 path = 'c:\\x'
163 path = 'c:\\x'
165 m = DummyMagics(ip)
164 m = DummyMagics(ip)
166 opts = m.parse_options('-f %s' % path,'f:')[0]
165 opts = m.parse_options('-f %s' % path,'f:')[0]
167 # argv splitting is os-dependent
166 # argv splitting is os-dependent
168 if os.name == 'posix':
167 if os.name == 'posix':
169 expected = 'c:x'
168 expected = 'c:x'
170 else:
169 else:
171 expected = path
170 expected = path
172 assert opts["f"] == expected
171 assert opts["f"] == expected
173
172
174
173
175 def test_magic_parse_long_options():
174 def test_magic_parse_long_options():
176 """Magic.parse_options can handle --foo=bar long options"""
175 """Magic.parse_options can handle --foo=bar long options"""
177 ip = get_ipython()
176 ip = get_ipython()
178 m = DummyMagics(ip)
177 m = DummyMagics(ip)
179 opts, _ = m.parse_options("--foo --bar=bubble", "a", "foo", "bar=")
178 opts, _ = m.parse_options("--foo --bar=bubble", "a", "foo", "bar=")
180 assert "foo" in opts
179 assert "foo" in opts
181 assert "bar" in opts
180 assert "bar" in opts
182 assert opts["bar"] == "bubble"
181 assert opts["bar"] == "bubble"
183
182
184
183
185 def doctest_hist_f():
184 def doctest_hist_f():
186 """Test %hist -f with temporary filename.
185 """Test %hist -f with temporary filename.
187
186
188 In [9]: import tempfile
187 In [9]: import tempfile
189
188
190 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
189 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
191
190
192 In [11]: %hist -nl -f $tfile 3
191 In [11]: %hist -nl -f $tfile 3
193
192
194 In [13]: import os; os.unlink(tfile)
193 In [13]: import os; os.unlink(tfile)
195 """
194 """
196
195
197
196
198 def doctest_hist_op():
197 def doctest_hist_op():
199 """Test %hist -op
198 """Test %hist -op
200
199
201 In [1]: class b(float):
200 In [1]: class b(float):
202 ...: pass
201 ...: pass
203 ...:
202 ...:
204
203
205 In [2]: class s(object):
204 In [2]: class s(object):
206 ...: def __str__(self):
205 ...: def __str__(self):
207 ...: return 's'
206 ...: return 's'
208 ...:
207 ...:
209
208
210 In [3]:
209 In [3]:
211
210
212 In [4]: class r(b):
211 In [4]: class r(b):
213 ...: def __repr__(self):
212 ...: def __repr__(self):
214 ...: return 'r'
213 ...: return 'r'
215 ...:
214 ...:
216
215
217 In [5]: class sr(s,r): pass
216 In [5]: class sr(s,r): pass
218 ...:
217 ...:
219
218
220 In [6]:
219 In [6]:
221
220
222 In [7]: bb=b()
221 In [7]: bb=b()
223
222
224 In [8]: ss=s()
223 In [8]: ss=s()
225
224
226 In [9]: rr=r()
225 In [9]: rr=r()
227
226
228 In [10]: ssrr=sr()
227 In [10]: ssrr=sr()
229
228
230 In [11]: 4.5
229 In [11]: 4.5
231 Out[11]: 4.5
230 Out[11]: 4.5
232
231
233 In [12]: str(ss)
232 In [12]: str(ss)
234 Out[12]: 's'
233 Out[12]: 's'
235
234
236 In [13]:
235 In [13]:
237
236
238 In [14]: %hist -op
237 In [14]: %hist -op
239 >>> class b:
238 >>> class b:
240 ... pass
239 ... pass
241 ...
240 ...
242 >>> class s(b):
241 >>> class s(b):
243 ... def __str__(self):
242 ... def __str__(self):
244 ... return 's'
243 ... return 's'
245 ...
244 ...
246 >>>
245 >>>
247 >>> class r(b):
246 >>> class r(b):
248 ... def __repr__(self):
247 ... def __repr__(self):
249 ... return 'r'
248 ... return 'r'
250 ...
249 ...
251 >>> class sr(s,r): pass
250 >>> class sr(s,r): pass
252 >>>
251 >>>
253 >>> bb=b()
252 >>> bb=b()
254 >>> ss=s()
253 >>> ss=s()
255 >>> rr=r()
254 >>> rr=r()
256 >>> ssrr=sr()
255 >>> ssrr=sr()
257 >>> 4.5
256 >>> 4.5
258 4.5
257 4.5
259 >>> str(ss)
258 >>> str(ss)
260 's'
259 's'
261 >>>
260 >>>
262 """
261 """
263
262
264 def test_hist_pof():
263 def test_hist_pof():
265 ip = get_ipython()
264 ip = get_ipython()
266 ip.run_cell("1+2", store_history=True)
265 ip.run_cell("1+2", store_history=True)
267 #raise Exception(ip.history_manager.session_number)
266 #raise Exception(ip.history_manager.session_number)
268 #raise Exception(list(ip.history_manager._get_range_session()))
267 #raise Exception(list(ip.history_manager._get_range_session()))
269 with TemporaryDirectory() as td:
268 with TemporaryDirectory() as td:
270 tf = os.path.join(td, 'hist.py')
269 tf = os.path.join(td, 'hist.py')
271 ip.run_line_magic('history', '-pof %s' % tf)
270 ip.run_line_magic('history', '-pof %s' % tf)
272 assert os.path.isfile(tf)
271 assert os.path.isfile(tf)
273
272
274
273
275 def test_macro():
274 def test_macro():
276 ip = get_ipython()
275 ip = get_ipython()
277 ip.history_manager.reset() # Clear any existing history.
276 ip.history_manager.reset() # Clear any existing history.
278 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
277 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
279 for i, cmd in enumerate(cmds, start=1):
278 for i, cmd in enumerate(cmds, start=1):
280 ip.history_manager.store_inputs(i, cmd)
279 ip.history_manager.store_inputs(i, cmd)
281 ip.magic("macro test 1-3")
280 ip.magic("macro test 1-3")
282 assert ip.user_ns["test"].value == "\n".join(cmds) + "\n"
281 assert ip.user_ns["test"].value == "\n".join(cmds) + "\n"
283
282
284 # List macros
283 # List macros
285 assert "test" in ip.magic("macro")
284 assert "test" in ip.magic("macro")
286
285
287
286
288 def test_macro_run():
287 def test_macro_run():
289 """Test that we can run a multi-line macro successfully."""
288 """Test that we can run a multi-line macro successfully."""
290 ip = get_ipython()
289 ip = get_ipython()
291 ip.history_manager.reset()
290 ip.history_manager.reset()
292 cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"]
291 cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"]
293 for cmd in cmds:
292 for cmd in cmds:
294 ip.run_cell(cmd, store_history=True)
293 ip.run_cell(cmd, store_history=True)
295 assert ip.user_ns["test"].value == "a+=1\nprint(a)\n"
294 assert ip.user_ns["test"].value == "a+=1\nprint(a)\n"
296 with tt.AssertPrints("12"):
295 with tt.AssertPrints("12"):
297 ip.run_cell("test")
296 ip.run_cell("test")
298 with tt.AssertPrints("13"):
297 with tt.AssertPrints("13"):
299 ip.run_cell("test")
298 ip.run_cell("test")
300
299
301
300
302 def test_magic_magic():
301 def test_magic_magic():
303 """Test %magic"""
302 """Test %magic"""
304 ip = get_ipython()
303 ip = get_ipython()
305 with capture_output() as captured:
304 with capture_output() as captured:
306 ip.magic("magic")
305 ip.magic("magic")
307
306
308 stdout = captured.stdout
307 stdout = captured.stdout
309 assert "%magic" in stdout
308 assert "%magic" in stdout
310 assert "IPython" in stdout
309 assert "IPython" in stdout
311 assert "Available" in stdout
310 assert "Available" in stdout
312
311
313
312
314 @dec.skipif_not_numpy
313 @dec.skipif_not_numpy
315 def test_numpy_reset_array_undec():
314 def test_numpy_reset_array_undec():
316 "Test '%reset array' functionality"
315 "Test '%reset array' functionality"
317 _ip.ex("import numpy as np")
316 _ip.ex("import numpy as np")
318 _ip.ex("a = np.empty(2)")
317 _ip.ex("a = np.empty(2)")
319 assert "a" in _ip.user_ns
318 assert "a" in _ip.user_ns
320 _ip.magic("reset -f array")
319 _ip.magic("reset -f array")
321 assert "a" not in _ip.user_ns
320 assert "a" not in _ip.user_ns
322
321
323
322
324 def test_reset_out():
323 def test_reset_out():
325 "Test '%reset out' magic"
324 "Test '%reset out' magic"
326 _ip.run_cell("parrot = 'dead'", store_history=True)
325 _ip.run_cell("parrot = 'dead'", store_history=True)
327 # test '%reset -f out', make an Out prompt
326 # test '%reset -f out', make an Out prompt
328 _ip.run_cell("parrot", store_history=True)
327 _ip.run_cell("parrot", store_history=True)
329 assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")]
328 assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")]
330 _ip.magic("reset -f out")
329 _ip.magic("reset -f out")
331 assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")]
330 assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")]
332 assert len(_ip.user_ns["Out"]) == 0
331 assert len(_ip.user_ns["Out"]) == 0
333
332
334
333
335 def test_reset_in():
334 def test_reset_in():
336 "Test '%reset in' magic"
335 "Test '%reset in' magic"
337 # test '%reset -f in'
336 # test '%reset -f in'
338 _ip.run_cell("parrot", store_history=True)
337 _ip.run_cell("parrot", store_history=True)
339 assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
338 assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
340 _ip.magic("%reset -f in")
339 _ip.magic("%reset -f in")
341 assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
340 assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
342 assert len(set(_ip.user_ns["In"])) == 1
341 assert len(set(_ip.user_ns["In"])) == 1
343
342
344
343
345 def test_reset_dhist():
344 def test_reset_dhist():
346 "Test '%reset dhist' magic"
345 "Test '%reset dhist' magic"
347 _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing
346 _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing
348 _ip.magic("cd " + os.path.dirname(pytest.__file__))
347 _ip.magic("cd " + os.path.dirname(pytest.__file__))
349 _ip.magic("cd -")
348 _ip.magic("cd -")
350 assert len(_ip.user_ns["_dh"]) > 0
349 assert len(_ip.user_ns["_dh"]) > 0
351 _ip.magic("reset -f dhist")
350 _ip.magic("reset -f dhist")
352 assert len(_ip.user_ns["_dh"]) == 0
351 assert len(_ip.user_ns["_dh"]) == 0
353 _ip.run_cell("_dh = [d for d in tmp]") # restore
352 _ip.run_cell("_dh = [d for d in tmp]") # restore
354
353
355
354
356 def test_reset_in_length():
355 def test_reset_in_length():
357 "Test that '%reset in' preserves In[] length"
356 "Test that '%reset in' preserves In[] length"
358 _ip.run_cell("print 'foo'")
357 _ip.run_cell("print 'foo'")
359 _ip.run_cell("reset -f in")
358 _ip.run_cell("reset -f in")
360 assert len(_ip.user_ns["In"]) == _ip.displayhook.prompt_count + 1
359 assert len(_ip.user_ns["In"]) == _ip.displayhook.prompt_count + 1
361
360
362
361
363 class TestResetErrors(TestCase):
362 class TestResetErrors(TestCase):
364
363
365 def test_reset_redefine(self):
364 def test_reset_redefine(self):
366
365
367 @magics_class
366 @magics_class
368 class KernelMagics(Magics):
367 class KernelMagics(Magics):
369 @line_magic
368 @line_magic
370 def less(self, shell): pass
369 def less(self, shell): pass
371
370
372 _ip.register_magics(KernelMagics)
371 _ip.register_magics(KernelMagics)
373
372
374 with self.assertLogs() as cm:
373 with self.assertLogs() as cm:
375 # hack, we want to just capture logs, but assertLogs fails if not
374 # hack, we want to just capture logs, but assertLogs fails if not
376 # logs get produce.
375 # logs get produce.
377 # so log one things we ignore.
376 # so log one things we ignore.
378 import logging as log_mod
377 import logging as log_mod
379 log = log_mod.getLogger()
378 log = log_mod.getLogger()
380 log.info('Nothing')
379 log.info('Nothing')
381 # end hack.
380 # end hack.
382 _ip.run_cell("reset -f")
381 _ip.run_cell("reset -f")
383
382
384 assert len(cm.output) == 1
383 assert len(cm.output) == 1
385 for out in cm.output:
384 for out in cm.output:
386 assert "Invalid alias" not in out
385 assert "Invalid alias" not in out
387
386
388 def test_tb_syntaxerror():
387 def test_tb_syntaxerror():
389 """test %tb after a SyntaxError"""
388 """test %tb after a SyntaxError"""
390 ip = get_ipython()
389 ip = get_ipython()
391 ip.run_cell("for")
390 ip.run_cell("for")
392
391
393 # trap and validate stdout
392 # trap and validate stdout
394 save_stdout = sys.stdout
393 save_stdout = sys.stdout
395 try:
394 try:
396 sys.stdout = StringIO()
395 sys.stdout = StringIO()
397 ip.run_cell("%tb")
396 ip.run_cell("%tb")
398 out = sys.stdout.getvalue()
397 out = sys.stdout.getvalue()
399 finally:
398 finally:
400 sys.stdout = save_stdout
399 sys.stdout = save_stdout
401 # trim output, and only check the last line
400 # trim output, and only check the last line
402 last_line = out.rstrip().splitlines()[-1].strip()
401 last_line = out.rstrip().splitlines()[-1].strip()
403 assert last_line == "SyntaxError: invalid syntax"
402 assert last_line == "SyntaxError: invalid syntax"
404
403
405
404
406 def test_time():
405 def test_time():
407 ip = get_ipython()
406 ip = get_ipython()
408
407
409 with tt.AssertPrints("Wall time: "):
408 with tt.AssertPrints("Wall time: "):
410 ip.run_cell("%time None")
409 ip.run_cell("%time None")
411
410
412 ip.run_cell("def f(kmjy):\n"
411 ip.run_cell("def f(kmjy):\n"
413 " %time print (2*kmjy)")
412 " %time print (2*kmjy)")
414
413
415 with tt.AssertPrints("Wall time: "):
414 with tt.AssertPrints("Wall time: "):
416 with tt.AssertPrints("hihi", suppress=False):
415 with tt.AssertPrints("hihi", suppress=False):
417 ip.run_cell("f('hi')")
416 ip.run_cell("f('hi')")
418
417
419 def test_time_last_not_expression():
418 def test_time_last_not_expression():
420 ip.run_cell("%%time\n"
419 ip.run_cell("%%time\n"
421 "var_1 = 1\n"
420 "var_1 = 1\n"
422 "var_2 = 2\n")
421 "var_2 = 2\n")
423 assert ip.user_ns['var_1'] == 1
422 assert ip.user_ns['var_1'] == 1
424 del ip.user_ns['var_1']
423 del ip.user_ns['var_1']
425 assert ip.user_ns['var_2'] == 2
424 assert ip.user_ns['var_2'] == 2
426 del ip.user_ns['var_2']
425 del ip.user_ns['var_2']
427
426
428
427
429 @dec.skip_win32
428 @dec.skip_win32
430 def test_time2():
429 def test_time2():
431 ip = get_ipython()
430 ip = get_ipython()
432
431
433 with tt.AssertPrints("CPU times: user "):
432 with tt.AssertPrints("CPU times: user "):
434 ip.run_cell("%time None")
433 ip.run_cell("%time None")
435
434
436 def test_time3():
435 def test_time3():
437 """Erroneous magic function calls, issue gh-3334"""
436 """Erroneous magic function calls, issue gh-3334"""
438 ip = get_ipython()
437 ip = get_ipython()
439 ip.user_ns.pop('run', None)
438 ip.user_ns.pop('run', None)
440
439
441 with tt.AssertNotPrints("not found", channel='stderr'):
440 with tt.AssertNotPrints("not found", channel='stderr'):
442 ip.run_cell("%%time\n"
441 ip.run_cell("%%time\n"
443 "run = 0\n"
442 "run = 0\n"
444 "run += 1")
443 "run += 1")
445
444
446 def test_multiline_time():
445 def test_multiline_time():
447 """Make sure last statement from time return a value."""
446 """Make sure last statement from time return a value."""
448 ip = get_ipython()
447 ip = get_ipython()
449 ip.user_ns.pop('run', None)
448 ip.user_ns.pop('run', None)
450
449
451 ip.run_cell(
450 ip.run_cell(
452 dedent(
451 dedent(
453 """\
452 """\
454 %%time
453 %%time
455 a = "ho"
454 a = "ho"
456 b = "hey"
455 b = "hey"
457 a+b
456 a+b
458 """
457 """
459 )
458 )
460 )
459 )
461 assert ip.user_ns_hidden["_"] == "hohey"
460 assert ip.user_ns_hidden["_"] == "hohey"
462
461
463
462
464 def test_time_local_ns():
463 def test_time_local_ns():
465 """
464 """
466 Test that local_ns is actually global_ns when running a cell magic
465 Test that local_ns is actually global_ns when running a cell magic
467 """
466 """
468 ip = get_ipython()
467 ip = get_ipython()
469 ip.run_cell("%%time\n" "myvar = 1")
468 ip.run_cell("%%time\n" "myvar = 1")
470 assert ip.user_ns["myvar"] == 1
469 assert ip.user_ns["myvar"] == 1
471 del ip.user_ns["myvar"]
470 del ip.user_ns["myvar"]
472
471
473
472
474 def test_doctest_mode():
473 def test_doctest_mode():
475 "Toggle doctest_mode twice, it should be a no-op and run without error"
474 "Toggle doctest_mode twice, it should be a no-op and run without error"
476 _ip.magic('doctest_mode')
475 _ip.magic('doctest_mode')
477 _ip.magic('doctest_mode')
476 _ip.magic('doctest_mode')
478
477
479
478
480 def test_parse_options():
479 def test_parse_options():
481 """Tests for basic options parsing in magics."""
480 """Tests for basic options parsing in magics."""
482 # These are only the most minimal of tests, more should be added later. At
481 # These are only the most minimal of tests, more should be added later. At
483 # the very least we check that basic text/unicode calls work OK.
482 # the very least we check that basic text/unicode calls work OK.
484 m = DummyMagics(_ip)
483 m = DummyMagics(_ip)
485 assert m.parse_options("foo", "")[1] == "foo"
484 assert m.parse_options("foo", "")[1] == "foo"
486 assert m.parse_options("foo", "")[1] == "foo"
485 assert m.parse_options("foo", "")[1] == "foo"
487
486
488
487
489 def test_parse_options_preserve_non_option_string():
488 def test_parse_options_preserve_non_option_string():
490 """Test to assert preservation of non-option part of magic-block, while parsing magic options."""
489 """Test to assert preservation of non-option part of magic-block, while parsing magic options."""
491 m = DummyMagics(_ip)
490 m = DummyMagics(_ip)
492 opts, stmt = m.parse_options(
491 opts, stmt = m.parse_options(
493 " -n1 -r 13 _ = 314 + foo", "n:r:", preserve_non_opts=True
492 " -n1 -r 13 _ = 314 + foo", "n:r:", preserve_non_opts=True
494 )
493 )
495 assert opts == {"n": "1", "r": "13"}
494 assert opts == {"n": "1", "r": "13"}
496 assert stmt == "_ = 314 + foo"
495 assert stmt == "_ = 314 + foo"
497
496
498
497
499 def test_run_magic_preserve_code_block():
498 def test_run_magic_preserve_code_block():
500 """Test to assert preservation of non-option part of magic-block, while running magic."""
499 """Test to assert preservation of non-option part of magic-block, while running magic."""
501 _ip.user_ns["spaces"] = []
500 _ip.user_ns["spaces"] = []
502 _ip.magic("timeit -n1 -r1 spaces.append([s.count(' ') for s in ['document']])")
501 _ip.magic("timeit -n1 -r1 spaces.append([s.count(' ') for s in ['document']])")
503 assert _ip.user_ns["spaces"] == [[0]]
502 assert _ip.user_ns["spaces"] == [[0]]
504
503
505
504
506 def test_dirops():
505 def test_dirops():
507 """Test various directory handling operations."""
506 """Test various directory handling operations."""
508 # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/')
507 # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/')
509 curpath = os.getcwd
508 curpath = os.getcwd
510 startdir = os.getcwd()
509 startdir = os.getcwd()
511 ipdir = os.path.realpath(_ip.ipython_dir)
510 ipdir = os.path.realpath(_ip.ipython_dir)
512 try:
511 try:
513 _ip.magic('cd "%s"' % ipdir)
512 _ip.magic('cd "%s"' % ipdir)
514 assert curpath() == ipdir
513 assert curpath() == ipdir
515 _ip.magic('cd -')
514 _ip.magic('cd -')
516 assert curpath() == startdir
515 assert curpath() == startdir
517 _ip.magic('pushd "%s"' % ipdir)
516 _ip.magic('pushd "%s"' % ipdir)
518 assert curpath() == ipdir
517 assert curpath() == ipdir
519 _ip.magic('popd')
518 _ip.magic('popd')
520 assert curpath() == startdir
519 assert curpath() == startdir
521 finally:
520 finally:
522 os.chdir(startdir)
521 os.chdir(startdir)
523
522
524
523
525 def test_cd_force_quiet():
524 def test_cd_force_quiet():
526 """Test OSMagics.cd_force_quiet option"""
525 """Test OSMagics.cd_force_quiet option"""
527 _ip.config.OSMagics.cd_force_quiet = True
526 _ip.config.OSMagics.cd_force_quiet = True
528 osmagics = osm.OSMagics(shell=_ip)
527 osmagics = osm.OSMagics(shell=_ip)
529
528
530 startdir = os.getcwd()
529 startdir = os.getcwd()
531 ipdir = os.path.realpath(_ip.ipython_dir)
530 ipdir = os.path.realpath(_ip.ipython_dir)
532
531
533 try:
532 try:
534 with tt.AssertNotPrints(ipdir):
533 with tt.AssertNotPrints(ipdir):
535 osmagics.cd('"%s"' % ipdir)
534 osmagics.cd('"%s"' % ipdir)
536 with tt.AssertNotPrints(startdir):
535 with tt.AssertNotPrints(startdir):
537 osmagics.cd('-')
536 osmagics.cd('-')
538 finally:
537 finally:
539 os.chdir(startdir)
538 os.chdir(startdir)
540
539
541
540
542 def test_xmode():
541 def test_xmode():
543 # Calling xmode three times should be a no-op
542 # Calling xmode three times should be a no-op
544 xmode = _ip.InteractiveTB.mode
543 xmode = _ip.InteractiveTB.mode
545 for i in range(4):
544 for i in range(4):
546 _ip.magic("xmode")
545 _ip.magic("xmode")
547 assert _ip.InteractiveTB.mode == xmode
546 assert _ip.InteractiveTB.mode == xmode
548
547
549 def test_reset_hard():
548 def test_reset_hard():
550 monitor = []
549 monitor = []
551 class A(object):
550 class A(object):
552 def __del__(self):
551 def __del__(self):
553 monitor.append(1)
552 monitor.append(1)
554 def __repr__(self):
553 def __repr__(self):
555 return "<A instance>"
554 return "<A instance>"
556
555
557 _ip.user_ns["a"] = A()
556 _ip.user_ns["a"] = A()
558 _ip.run_cell("a")
557 _ip.run_cell("a")
559
558
560 assert monitor == []
559 assert monitor == []
561 _ip.magic("reset -f")
560 _ip.magic("reset -f")
562 assert monitor == [1]
561 assert monitor == [1]
563
562
564 class TestXdel(tt.TempFileMixin):
563 class TestXdel(tt.TempFileMixin):
565 def test_xdel(self):
564 def test_xdel(self):
566 """Test that references from %run are cleared by xdel."""
565 """Test that references from %run are cleared by xdel."""
567 src = ("class A(object):\n"
566 src = ("class A(object):\n"
568 " monitor = []\n"
567 " monitor = []\n"
569 " def __del__(self):\n"
568 " def __del__(self):\n"
570 " self.monitor.append(1)\n"
569 " self.monitor.append(1)\n"
571 "a = A()\n")
570 "a = A()\n")
572 self.mktmp(src)
571 self.mktmp(src)
573 # %run creates some hidden references...
572 # %run creates some hidden references...
574 _ip.magic("run %s" % self.fname)
573 _ip.magic("run %s" % self.fname)
575 # ... as does the displayhook.
574 # ... as does the displayhook.
576 _ip.run_cell("a")
575 _ip.run_cell("a")
577
576
578 monitor = _ip.user_ns["A"].monitor
577 monitor = _ip.user_ns["A"].monitor
579 assert monitor == []
578 assert monitor == []
580
579
581 _ip.magic("xdel a")
580 _ip.magic("xdel a")
582
581
583 # Check that a's __del__ method has been called.
582 # Check that a's __del__ method has been called.
584 gc.collect(0)
583 gc.collect(0)
585 assert monitor == [1]
584 assert monitor == [1]
586
585
587 def doctest_who():
586 def doctest_who():
588 """doctest for %who
587 """doctest for %who
589
588
590 In [1]: %reset -sf
589 In [1]: %reset -sf
591
590
592 In [2]: alpha = 123
591 In [2]: alpha = 123
593
592
594 In [3]: beta = 'beta'
593 In [3]: beta = 'beta'
595
594
596 In [4]: %who int
595 In [4]: %who int
597 alpha
596 alpha
598
597
599 In [5]: %who str
598 In [5]: %who str
600 beta
599 beta
601
600
602 In [6]: %whos
601 In [6]: %whos
603 Variable Type Data/Info
602 Variable Type Data/Info
604 ----------------------------
603 ----------------------------
605 alpha int 123
604 alpha int 123
606 beta str beta
605 beta str beta
607
606
608 In [7]: %who_ls
607 In [7]: %who_ls
609 Out[7]: ['alpha', 'beta']
608 Out[7]: ['alpha', 'beta']
610 """
609 """
611
610
612 def test_whos():
611 def test_whos():
613 """Check that whos is protected against objects where repr() fails."""
612 """Check that whos is protected against objects where repr() fails."""
614 class A(object):
613 class A(object):
615 def __repr__(self):
614 def __repr__(self):
616 raise Exception()
615 raise Exception()
617 _ip.user_ns['a'] = A()
616 _ip.user_ns['a'] = A()
618 _ip.magic("whos")
617 _ip.magic("whos")
619
618
620 def doctest_precision():
619 def doctest_precision():
621 """doctest for %precision
620 """doctest for %precision
622
621
623 In [1]: f = get_ipython().display_formatter.formatters['text/plain']
622 In [1]: f = get_ipython().display_formatter.formatters['text/plain']
624
623
625 In [2]: %precision 5
624 In [2]: %precision 5
626 Out[2]: '%.5f'
625 Out[2]: '%.5f'
627
626
628 In [3]: f.float_format
627 In [3]: f.float_format
629 Out[3]: '%.5f'
628 Out[3]: '%.5f'
630
629
631 In [4]: %precision %e
630 In [4]: %precision %e
632 Out[4]: '%e'
631 Out[4]: '%e'
633
632
634 In [5]: f(3.1415927)
633 In [5]: f(3.1415927)
635 Out[5]: '3.141593e+00'
634 Out[5]: '3.141593e+00'
636 """
635 """
637
636
638 def test_debug_magic():
637 def test_debug_magic():
639 """Test debugging a small code with %debug
638 """Test debugging a small code with %debug
640
639
641 In [1]: with PdbTestInput(['c']):
640 In [1]: with PdbTestInput(['c']):
642 ...: %debug print("a b") #doctest: +ELLIPSIS
641 ...: %debug print("a b") #doctest: +ELLIPSIS
643 ...:
642 ...:
644 ...
643 ...
645 ipdb> c
644 ipdb> c
646 a b
645 a b
647 In [2]:
646 In [2]:
648 """
647 """
649
648
650 def test_psearch():
649 def test_psearch():
651 with tt.AssertPrints("dict.fromkeys"):
650 with tt.AssertPrints("dict.fromkeys"):
652 _ip.run_cell("dict.fr*?")
651 _ip.run_cell("dict.fr*?")
653 with tt.AssertPrints("Ο€.is_integer"):
652 with tt.AssertPrints("Ο€.is_integer"):
654 _ip.run_cell("Ο€ = 3.14;\nΟ€.is_integ*?")
653 _ip.run_cell("Ο€ = 3.14;\nΟ€.is_integ*?")
655
654
656 def test_timeit_shlex():
655 def test_timeit_shlex():
657 """test shlex issues with timeit (#1109)"""
656 """test shlex issues with timeit (#1109)"""
658 _ip.ex("def f(*a,**kw): pass")
657 _ip.ex("def f(*a,**kw): pass")
659 _ip.magic('timeit -n1 "this is a bug".count(" ")')
658 _ip.magic('timeit -n1 "this is a bug".count(" ")')
660 _ip.magic('timeit -r1 -n1 f(" ", 1)')
659 _ip.magic('timeit -r1 -n1 f(" ", 1)')
661 _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")')
660 _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")')
662 _ip.magic('timeit -r1 -n1 ("a " + "b")')
661 _ip.magic('timeit -r1 -n1 ("a " + "b")')
663 _ip.magic('timeit -r1 -n1 f("a " + "b")')
662 _ip.magic('timeit -r1 -n1 f("a " + "b")')
664 _ip.magic('timeit -r1 -n1 f("a " + "b ")')
663 _ip.magic('timeit -r1 -n1 f("a " + "b ")')
665
664
666
665
667 def test_timeit_special_syntax():
666 def test_timeit_special_syntax():
668 "Test %%timeit with IPython special syntax"
667 "Test %%timeit with IPython special syntax"
669 @register_line_magic
668 @register_line_magic
670 def lmagic(line):
669 def lmagic(line):
671 ip = get_ipython()
670 ip = get_ipython()
672 ip.user_ns['lmagic_out'] = line
671 ip.user_ns['lmagic_out'] = line
673
672
674 # line mode test
673 # line mode test
675 _ip.run_line_magic("timeit", "-n1 -r1 %lmagic my line")
674 _ip.run_line_magic("timeit", "-n1 -r1 %lmagic my line")
676 assert _ip.user_ns["lmagic_out"] == "my line"
675 assert _ip.user_ns["lmagic_out"] == "my line"
677 # cell mode test
676 # cell mode test
678 _ip.run_cell_magic("timeit", "-n1 -r1", "%lmagic my line2")
677 _ip.run_cell_magic("timeit", "-n1 -r1", "%lmagic my line2")
679 assert _ip.user_ns["lmagic_out"] == "my line2"
678 assert _ip.user_ns["lmagic_out"] == "my line2"
680
679
681
680
682 def test_timeit_return():
681 def test_timeit_return():
683 """
682 """
684 test whether timeit -o return object
683 test whether timeit -o return object
685 """
684 """
686
685
687 res = _ip.run_line_magic('timeit','-n10 -r10 -o 1')
686 res = _ip.run_line_magic('timeit','-n10 -r10 -o 1')
688 assert(res is not None)
687 assert(res is not None)
689
688
690 def test_timeit_quiet():
689 def test_timeit_quiet():
691 """
690 """
692 test quiet option of timeit magic
691 test quiet option of timeit magic
693 """
692 """
694 with tt.AssertNotPrints("loops"):
693 with tt.AssertNotPrints("loops"):
695 _ip.run_cell("%timeit -n1 -r1 -q 1")
694 _ip.run_cell("%timeit -n1 -r1 -q 1")
696
695
697 def test_timeit_return_quiet():
696 def test_timeit_return_quiet():
698 with tt.AssertNotPrints("loops"):
697 with tt.AssertNotPrints("loops"):
699 res = _ip.run_line_magic('timeit', '-n1 -r1 -q -o 1')
698 res = _ip.run_line_magic('timeit', '-n1 -r1 -q -o 1')
700 assert (res is not None)
699 assert (res is not None)
701
700
702 def test_timeit_invalid_return():
701 def test_timeit_invalid_return():
703 with pytest.raises(SyntaxError):
702 with pytest.raises(SyntaxError):
704 _ip.run_line_magic('timeit', 'return')
703 _ip.run_line_magic('timeit', 'return')
705
704
706 @dec.skipif(execution.profile is None)
705 @dec.skipif(execution.profile is None)
707 def test_prun_special_syntax():
706 def test_prun_special_syntax():
708 "Test %%prun with IPython special syntax"
707 "Test %%prun with IPython special syntax"
709 @register_line_magic
708 @register_line_magic
710 def lmagic(line):
709 def lmagic(line):
711 ip = get_ipython()
710 ip = get_ipython()
712 ip.user_ns['lmagic_out'] = line
711 ip.user_ns['lmagic_out'] = line
713
712
714 # line mode test
713 # line mode test
715 _ip.run_line_magic("prun", "-q %lmagic my line")
714 _ip.run_line_magic("prun", "-q %lmagic my line")
716 assert _ip.user_ns["lmagic_out"] == "my line"
715 assert _ip.user_ns["lmagic_out"] == "my line"
717 # cell mode test
716 # cell mode test
718 _ip.run_cell_magic("prun", "-q", "%lmagic my line2")
717 _ip.run_cell_magic("prun", "-q", "%lmagic my line2")
719 assert _ip.user_ns["lmagic_out"] == "my line2"
718 assert _ip.user_ns["lmagic_out"] == "my line2"
720
719
721
720
722 @dec.skipif(execution.profile is None)
721 @dec.skipif(execution.profile is None)
723 def test_prun_quotes():
722 def test_prun_quotes():
724 "Test that prun does not clobber string escapes (GH #1302)"
723 "Test that prun does not clobber string escapes (GH #1302)"
725 _ip.magic(r"prun -q x = '\t'")
724 _ip.magic(r"prun -q x = '\t'")
726 assert _ip.user_ns["x"] == "\t"
725 assert _ip.user_ns["x"] == "\t"
727
726
728
727
729 def test_extension():
728 def test_extension():
730 # Debugging information for failures of this test
729 # Debugging information for failures of this test
731 print('sys.path:')
730 print('sys.path:')
732 for p in sys.path:
731 for p in sys.path:
733 print(' ', p)
732 print(' ', p)
734 print('CWD', os.getcwd())
733 print('CWD', os.getcwd())
735
734
736 pytest.raises(ImportError, _ip.magic, "load_ext daft_extension")
735 pytest.raises(ImportError, _ip.magic, "load_ext daft_extension")
737 daft_path = os.path.join(os.path.dirname(__file__), "daft_extension")
736 daft_path = os.path.join(os.path.dirname(__file__), "daft_extension")
738 sys.path.insert(0, daft_path)
737 sys.path.insert(0, daft_path)
739 try:
738 try:
740 _ip.user_ns.pop('arq', None)
739 _ip.user_ns.pop('arq', None)
741 invalidate_caches() # Clear import caches
740 invalidate_caches() # Clear import caches
742 _ip.magic("load_ext daft_extension")
741 _ip.magic("load_ext daft_extension")
743 assert _ip.user_ns["arq"] == 185
742 assert _ip.user_ns["arq"] == 185
744 _ip.magic("unload_ext daft_extension")
743 _ip.magic("unload_ext daft_extension")
745 assert 'arq' not in _ip.user_ns
744 assert 'arq' not in _ip.user_ns
746 finally:
745 finally:
747 sys.path.remove(daft_path)
746 sys.path.remove(daft_path)
748
747
749
748
750 def test_notebook_export_json():
749 def test_notebook_export_json():
751 pytest.importorskip("nbformat")
750 pytest.importorskip("nbformat")
752 _ip = get_ipython()
751 _ip = get_ipython()
753 _ip.history_manager.reset() # Clear any existing history.
752 _ip.history_manager.reset() # Clear any existing history.
754 cmds = ["a=1", "def b():\n return a**2", "print('noΓ«l, Γ©tΓ©', b())"]
753 cmds = ["a=1", "def b():\n return a**2", "print('noΓ«l, Γ©tΓ©', b())"]
755 for i, cmd in enumerate(cmds, start=1):
754 for i, cmd in enumerate(cmds, start=1):
756 _ip.history_manager.store_inputs(i, cmd)
755 _ip.history_manager.store_inputs(i, cmd)
757 with TemporaryDirectory() as td:
756 with TemporaryDirectory() as td:
758 outfile = os.path.join(td, "nb.ipynb")
757 outfile = os.path.join(td, "nb.ipynb")
759 _ip.magic("notebook %s" % outfile)
758 _ip.magic("notebook %s" % outfile)
760
759
761
760
762 class TestEnv(TestCase):
761 class TestEnv(TestCase):
763
762
764 def test_env(self):
763 def test_env(self):
765 env = _ip.magic("env")
764 env = _ip.magic("env")
766 self.assertTrue(isinstance(env, dict))
765 self.assertTrue(isinstance(env, dict))
767
766
768 def test_env_secret(self):
767 def test_env_secret(self):
769 env = _ip.magic("env")
768 env = _ip.magic("env")
770 hidden = "<hidden>"
769 hidden = "<hidden>"
771 with mock.patch.dict(
770 with mock.patch.dict(
772 os.environ,
771 os.environ,
773 {
772 {
774 "API_KEY": "abc123",
773 "API_KEY": "abc123",
775 "SECRET_THING": "ssshhh",
774 "SECRET_THING": "ssshhh",
776 "JUPYTER_TOKEN": "",
775 "JUPYTER_TOKEN": "",
777 "VAR": "abc"
776 "VAR": "abc"
778 }
777 }
779 ):
778 ):
780 env = _ip.magic("env")
779 env = _ip.magic("env")
781 assert env["API_KEY"] == hidden
780 assert env["API_KEY"] == hidden
782 assert env["SECRET_THING"] == hidden
781 assert env["SECRET_THING"] == hidden
783 assert env["JUPYTER_TOKEN"] == hidden
782 assert env["JUPYTER_TOKEN"] == hidden
784 assert env["VAR"] == "abc"
783 assert env["VAR"] == "abc"
785
784
786 def test_env_get_set_simple(self):
785 def test_env_get_set_simple(self):
787 env = _ip.magic("env var val1")
786 env = _ip.magic("env var val1")
788 self.assertEqual(env, None)
787 self.assertEqual(env, None)
789 self.assertEqual(os.environ['var'], 'val1')
788 self.assertEqual(os.environ['var'], 'val1')
790 self.assertEqual(_ip.magic("env var"), 'val1')
789 self.assertEqual(_ip.magic("env var"), 'val1')
791 env = _ip.magic("env var=val2")
790 env = _ip.magic("env var=val2")
792 self.assertEqual(env, None)
791 self.assertEqual(env, None)
793 self.assertEqual(os.environ['var'], 'val2')
792 self.assertEqual(os.environ['var'], 'val2')
794
793
795 def test_env_get_set_complex(self):
794 def test_env_get_set_complex(self):
796 env = _ip.magic("env var 'val1 '' 'val2")
795 env = _ip.magic("env var 'val1 '' 'val2")
797 self.assertEqual(env, None)
796 self.assertEqual(env, None)
798 self.assertEqual(os.environ['var'], "'val1 '' 'val2")
797 self.assertEqual(os.environ['var'], "'val1 '' 'val2")
799 self.assertEqual(_ip.magic("env var"), "'val1 '' 'val2")
798 self.assertEqual(_ip.magic("env var"), "'val1 '' 'val2")
800 env = _ip.magic('env var=val2 val3="val4')
799 env = _ip.magic('env var=val2 val3="val4')
801 self.assertEqual(env, None)
800 self.assertEqual(env, None)
802 self.assertEqual(os.environ['var'], 'val2 val3="val4')
801 self.assertEqual(os.environ['var'], 'val2 val3="val4')
803
802
804 def test_env_set_bad_input(self):
803 def test_env_set_bad_input(self):
805 self.assertRaises(UsageError, lambda: _ip.magic("set_env var"))
804 self.assertRaises(UsageError, lambda: _ip.magic("set_env var"))
806
805
807 def test_env_set_whitespace(self):
806 def test_env_set_whitespace(self):
808 self.assertRaises(UsageError, lambda: _ip.magic("env var A=B"))
807 self.assertRaises(UsageError, lambda: _ip.magic("env var A=B"))
809
808
810
809
811 class CellMagicTestCase(TestCase):
810 class CellMagicTestCase(TestCase):
812
811
813 def check_ident(self, magic):
812 def check_ident(self, magic):
814 # Manually called, we get the result
813 # Manually called, we get the result
815 out = _ip.run_cell_magic(magic, "a", "b")
814 out = _ip.run_cell_magic(magic, "a", "b")
816 assert out == ("a", "b")
815 assert out == ("a", "b")
817 # Via run_cell, it goes into the user's namespace via displayhook
816 # Via run_cell, it goes into the user's namespace via displayhook
818 _ip.run_cell("%%" + magic + " c\nd\n")
817 _ip.run_cell("%%" + magic + " c\nd\n")
819 assert _ip.user_ns["_"] == ("c", "d\n")
818 assert _ip.user_ns["_"] == ("c", "d\n")
820
819
821 def test_cell_magic_func_deco(self):
820 def test_cell_magic_func_deco(self):
822 "Cell magic using simple decorator"
821 "Cell magic using simple decorator"
823 @register_cell_magic
822 @register_cell_magic
824 def cellm(line, cell):
823 def cellm(line, cell):
825 return line, cell
824 return line, cell
826
825
827 self.check_ident('cellm')
826 self.check_ident('cellm')
828
827
829 def test_cell_magic_reg(self):
828 def test_cell_magic_reg(self):
830 "Cell magic manually registered"
829 "Cell magic manually registered"
831 def cellm(line, cell):
830 def cellm(line, cell):
832 return line, cell
831 return line, cell
833
832
834 _ip.register_magic_function(cellm, 'cell', 'cellm2')
833 _ip.register_magic_function(cellm, 'cell', 'cellm2')
835 self.check_ident('cellm2')
834 self.check_ident('cellm2')
836
835
837 def test_cell_magic_class(self):
836 def test_cell_magic_class(self):
838 "Cell magics declared via a class"
837 "Cell magics declared via a class"
839 @magics_class
838 @magics_class
840 class MyMagics(Magics):
839 class MyMagics(Magics):
841
840
842 @cell_magic
841 @cell_magic
843 def cellm3(self, line, cell):
842 def cellm3(self, line, cell):
844 return line, cell
843 return line, cell
845
844
846 _ip.register_magics(MyMagics)
845 _ip.register_magics(MyMagics)
847 self.check_ident('cellm3')
846 self.check_ident('cellm3')
848
847
849 def test_cell_magic_class2(self):
848 def test_cell_magic_class2(self):
850 "Cell magics declared via a class, #2"
849 "Cell magics declared via a class, #2"
851 @magics_class
850 @magics_class
852 class MyMagics2(Magics):
851 class MyMagics2(Magics):
853
852
854 @cell_magic('cellm4')
853 @cell_magic('cellm4')
855 def cellm33(self, line, cell):
854 def cellm33(self, line, cell):
856 return line, cell
855 return line, cell
857
856
858 _ip.register_magics(MyMagics2)
857 _ip.register_magics(MyMagics2)
859 self.check_ident('cellm4')
858 self.check_ident('cellm4')
860 # Check that nothing is registered as 'cellm33'
859 # Check that nothing is registered as 'cellm33'
861 c33 = _ip.find_cell_magic('cellm33')
860 c33 = _ip.find_cell_magic('cellm33')
862 assert c33 == None
861 assert c33 == None
863
862
864 def test_file():
863 def test_file():
865 """Basic %%writefile"""
864 """Basic %%writefile"""
866 ip = get_ipython()
865 ip = get_ipython()
867 with TemporaryDirectory() as td:
866 with TemporaryDirectory() as td:
868 fname = os.path.join(td, "file1")
867 fname = os.path.join(td, "file1")
869 ip.run_cell_magic(
868 ip.run_cell_magic(
870 "writefile",
869 "writefile",
871 fname,
870 fname,
872 "\n".join(
871 "\n".join(
873 [
872 [
874 "line1",
873 "line1",
875 "line2",
874 "line2",
876 ]
875 ]
877 ),
876 ),
878 )
877 )
879 s = Path(fname).read_text(encoding="utf-8")
878 s = Path(fname).read_text(encoding="utf-8")
880 assert "line1\n" in s
879 assert "line1\n" in s
881 assert "line2" in s
880 assert "line2" in s
882
881
883
882
884 @dec.skip_win32
883 @dec.skip_win32
885 def test_file_single_quote():
884 def test_file_single_quote():
886 """Basic %%writefile with embedded single quotes"""
885 """Basic %%writefile with embedded single quotes"""
887 ip = get_ipython()
886 ip = get_ipython()
888 with TemporaryDirectory() as td:
887 with TemporaryDirectory() as td:
889 fname = os.path.join(td, "'file1'")
888 fname = os.path.join(td, "'file1'")
890 ip.run_cell_magic(
889 ip.run_cell_magic(
891 "writefile",
890 "writefile",
892 fname,
891 fname,
893 "\n".join(
892 "\n".join(
894 [
893 [
895 "line1",
894 "line1",
896 "line2",
895 "line2",
897 ]
896 ]
898 ),
897 ),
899 )
898 )
900 s = Path(fname).read_text(encoding="utf-8")
899 s = Path(fname).read_text(encoding="utf-8")
901 assert "line1\n" in s
900 assert "line1\n" in s
902 assert "line2" in s
901 assert "line2" in s
903
902
904
903
905 @dec.skip_win32
904 @dec.skip_win32
906 def test_file_double_quote():
905 def test_file_double_quote():
907 """Basic %%writefile with embedded double quotes"""
906 """Basic %%writefile with embedded double quotes"""
908 ip = get_ipython()
907 ip = get_ipython()
909 with TemporaryDirectory() as td:
908 with TemporaryDirectory() as td:
910 fname = os.path.join(td, '"file1"')
909 fname = os.path.join(td, '"file1"')
911 ip.run_cell_magic(
910 ip.run_cell_magic(
912 "writefile",
911 "writefile",
913 fname,
912 fname,
914 "\n".join(
913 "\n".join(
915 [
914 [
916 "line1",
915 "line1",
917 "line2",
916 "line2",
918 ]
917 ]
919 ),
918 ),
920 )
919 )
921 s = Path(fname).read_text(encoding="utf-8")
920 s = Path(fname).read_text(encoding="utf-8")
922 assert "line1\n" in s
921 assert "line1\n" in s
923 assert "line2" in s
922 assert "line2" in s
924
923
925
924
926 def test_file_var_expand():
925 def test_file_var_expand():
927 """%%writefile $filename"""
926 """%%writefile $filename"""
928 ip = get_ipython()
927 ip = get_ipython()
929 with TemporaryDirectory() as td:
928 with TemporaryDirectory() as td:
930 fname = os.path.join(td, "file1")
929 fname = os.path.join(td, "file1")
931 ip.user_ns["filename"] = fname
930 ip.user_ns["filename"] = fname
932 ip.run_cell_magic(
931 ip.run_cell_magic(
933 "writefile",
932 "writefile",
934 "$filename",
933 "$filename",
935 "\n".join(
934 "\n".join(
936 [
935 [
937 "line1",
936 "line1",
938 "line2",
937 "line2",
939 ]
938 ]
940 ),
939 ),
941 )
940 )
942 s = Path(fname).read_text(encoding="utf-8")
941 s = Path(fname).read_text(encoding="utf-8")
943 assert "line1\n" in s
942 assert "line1\n" in s
944 assert "line2" in s
943 assert "line2" in s
945
944
946
945
947 def test_file_unicode():
946 def test_file_unicode():
948 """%%writefile with unicode cell"""
947 """%%writefile with unicode cell"""
949 ip = get_ipython()
948 ip = get_ipython()
950 with TemporaryDirectory() as td:
949 with TemporaryDirectory() as td:
951 fname = os.path.join(td, 'file1')
950 fname = os.path.join(td, 'file1')
952 ip.run_cell_magic("writefile", fname, u'\n'.join([
951 ip.run_cell_magic("writefile", fname, u'\n'.join([
953 u'linΓ©1',
952 u'linΓ©1',
954 u'linΓ©2',
953 u'linΓ©2',
955 ]))
954 ]))
956 with io.open(fname, encoding='utf-8') as f:
955 with io.open(fname, encoding='utf-8') as f:
957 s = f.read()
956 s = f.read()
958 assert "linΓ©1\n" in s
957 assert "linΓ©1\n" in s
959 assert "linΓ©2" in s
958 assert "linΓ©2" in s
960
959
961
960
962 def test_file_amend():
961 def test_file_amend():
963 """%%writefile -a amends files"""
962 """%%writefile -a amends files"""
964 ip = get_ipython()
963 ip = get_ipython()
965 with TemporaryDirectory() as td:
964 with TemporaryDirectory() as td:
966 fname = os.path.join(td, "file2")
965 fname = os.path.join(td, "file2")
967 ip.run_cell_magic(
966 ip.run_cell_magic(
968 "writefile",
967 "writefile",
969 fname,
968 fname,
970 "\n".join(
969 "\n".join(
971 [
970 [
972 "line1",
971 "line1",
973 "line2",
972 "line2",
974 ]
973 ]
975 ),
974 ),
976 )
975 )
977 ip.run_cell_magic(
976 ip.run_cell_magic(
978 "writefile",
977 "writefile",
979 "-a %s" % fname,
978 "-a %s" % fname,
980 "\n".join(
979 "\n".join(
981 [
980 [
982 "line3",
981 "line3",
983 "line4",
982 "line4",
984 ]
983 ]
985 ),
984 ),
986 )
985 )
987 s = Path(fname).read_text(encoding="utf-8")
986 s = Path(fname).read_text(encoding="utf-8")
988 assert "line1\n" in s
987 assert "line1\n" in s
989 assert "line3\n" in s
988 assert "line3\n" in s
990
989
991
990
992 def test_file_spaces():
991 def test_file_spaces():
993 """%%file with spaces in filename"""
992 """%%file with spaces in filename"""
994 ip = get_ipython()
993 ip = get_ipython()
995 with TemporaryWorkingDirectory() as td:
994 with TemporaryWorkingDirectory() as td:
996 fname = "file name"
995 fname = "file name"
997 ip.run_cell_magic(
996 ip.run_cell_magic(
998 "file",
997 "file",
999 '"%s"' % fname,
998 '"%s"' % fname,
1000 "\n".join(
999 "\n".join(
1001 [
1000 [
1002 "line1",
1001 "line1",
1003 "line2",
1002 "line2",
1004 ]
1003 ]
1005 ),
1004 ),
1006 )
1005 )
1007 s = Path(fname).read_text(encoding="utf-8")
1006 s = Path(fname).read_text(encoding="utf-8")
1008 assert "line1\n" in s
1007 assert "line1\n" in s
1009 assert "line2" in s
1008 assert "line2" in s
1010
1009
1011
1010
1012 def test_script_config():
1011 def test_script_config():
1013 ip = get_ipython()
1012 ip = get_ipython()
1014 ip.config.ScriptMagics.script_magics = ['whoda']
1013 ip.config.ScriptMagics.script_magics = ['whoda']
1015 sm = script.ScriptMagics(shell=ip)
1014 sm = script.ScriptMagics(shell=ip)
1016 assert "whoda" in sm.magics["cell"]
1015 assert "whoda" in sm.magics["cell"]
1017
1016
1018
1017
1019 def test_script_out():
1018 def test_script_out():
1020 ip = get_ipython()
1019 ip = get_ipython()
1021 ip.run_cell_magic("script", f"--out output {sys.executable}", "print('hi')")
1020 ip.run_cell_magic("script", f"--out output {sys.executable}", "print('hi')")
1022 assert ip.user_ns["output"].strip() == "hi"
1021 assert ip.user_ns["output"].strip() == "hi"
1023
1022
1024
1023
1025 def test_script_err():
1024 def test_script_err():
1026 ip = get_ipython()
1025 ip = get_ipython()
1027 ip.run_cell_magic(
1026 ip.run_cell_magic(
1028 "script",
1027 "script",
1029 f"--err error {sys.executable}",
1028 f"--err error {sys.executable}",
1030 "import sys; print('hello', file=sys.stderr)",
1029 "import sys; print('hello', file=sys.stderr)",
1031 )
1030 )
1032 assert ip.user_ns["error"].strip() == "hello"
1031 assert ip.user_ns["error"].strip() == "hello"
1033
1032
1034
1033
1035 def test_script_out_err():
1034 def test_script_out_err():
1036
1035
1037 ip = get_ipython()
1036 ip = get_ipython()
1038 ip.run_cell_magic(
1037 ip.run_cell_magic(
1039 "script",
1038 "script",
1040 f"--out output --err error {sys.executable}",
1039 f"--out output --err error {sys.executable}",
1041 "\n".join(
1040 "\n".join(
1042 [
1041 [
1043 "import sys",
1042 "import sys",
1044 "print('hi')",
1043 "print('hi')",
1045 "print('hello', file=sys.stderr)",
1044 "print('hello', file=sys.stderr)",
1046 ]
1045 ]
1047 ),
1046 ),
1048 )
1047 )
1049 assert ip.user_ns["output"].strip() == "hi"
1048 assert ip.user_ns["output"].strip() == "hi"
1050 assert ip.user_ns["error"].strip() == "hello"
1049 assert ip.user_ns["error"].strip() == "hello"
1051
1050
1052
1051
1053 async def test_script_bg_out():
1052 async def test_script_bg_out():
1054 ip = get_ipython()
1053 ip = get_ipython()
1055 ip.run_cell_magic("script", f"--bg --out output {sys.executable}", "print('hi')")
1054 ip.run_cell_magic("script", f"--bg --out output {sys.executable}", "print('hi')")
1056 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1055 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1057 assert ip.user_ns["output"].at_eof()
1056 assert ip.user_ns["output"].at_eof()
1058
1057
1059
1058
1060 async def test_script_bg_err():
1059 async def test_script_bg_err():
1061 ip = get_ipython()
1060 ip = get_ipython()
1062 ip.run_cell_magic(
1061 ip.run_cell_magic(
1063 "script",
1062 "script",
1064 f"--bg --err error {sys.executable}",
1063 f"--bg --err error {sys.executable}",
1065 "import sys; print('hello', file=sys.stderr)",
1064 "import sys; print('hello', file=sys.stderr)",
1066 )
1065 )
1067 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1066 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1068 assert ip.user_ns["error"].at_eof()
1067 assert ip.user_ns["error"].at_eof()
1069
1068
1070
1069
1071 async def test_script_bg_out_err():
1070 async def test_script_bg_out_err():
1072 ip = get_ipython()
1071 ip = get_ipython()
1073 ip.run_cell_magic(
1072 ip.run_cell_magic(
1074 "script",
1073 "script",
1075 f"--bg --out output --err error {sys.executable}",
1074 f"--bg --out output --err error {sys.executable}",
1076 "\n".join(
1075 "\n".join(
1077 [
1076 [
1078 "import sys",
1077 "import sys",
1079 "print('hi')",
1078 "print('hi')",
1080 "print('hello', file=sys.stderr)",
1079 "print('hello', file=sys.stderr)",
1081 ]
1080 ]
1082 ),
1081 ),
1083 )
1082 )
1084 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1083 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1085 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1084 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1086 assert ip.user_ns["output"].at_eof()
1085 assert ip.user_ns["output"].at_eof()
1087 assert ip.user_ns["error"].at_eof()
1086 assert ip.user_ns["error"].at_eof()
1088
1087
1089
1088
1090 async def test_script_bg_proc():
1089 async def test_script_bg_proc():
1091 ip = get_ipython()
1090 ip = get_ipython()
1092 ip.run_cell_magic(
1091 ip.run_cell_magic(
1093 "script",
1092 "script",
1094 f"--bg --out output --proc p {sys.executable}",
1093 f"--bg --out output --proc p {sys.executable}",
1095 "\n".join(
1094 "\n".join(
1096 [
1095 [
1097 "import sys",
1096 "import sys",
1098 "print('hi')",
1097 "print('hi')",
1099 "print('hello', file=sys.stderr)",
1098 "print('hello', file=sys.stderr)",
1100 ]
1099 ]
1101 ),
1100 ),
1102 )
1101 )
1103 p = ip.user_ns["p"]
1102 p = ip.user_ns["p"]
1104 await p.wait()
1103 await p.wait()
1105 assert p.returncode == 0
1104 assert p.returncode == 0
1106 assert (await p.stdout.read()).strip() == b"hi"
1105 assert (await p.stdout.read()).strip() == b"hi"
1107 # not captured, so empty
1106 # not captured, so empty
1108 assert (await p.stderr.read()) == b""
1107 assert (await p.stderr.read()) == b""
1109 assert p.stdout.at_eof()
1108 assert p.stdout.at_eof()
1110 assert p.stderr.at_eof()
1109 assert p.stderr.at_eof()
1111
1110
1112
1111
1113 def test_script_defaults():
1112 def test_script_defaults():
1114 ip = get_ipython()
1113 ip = get_ipython()
1115 for cmd in ['sh', 'bash', 'perl', 'ruby']:
1114 for cmd in ['sh', 'bash', 'perl', 'ruby']:
1116 try:
1115 try:
1117 find_cmd(cmd)
1116 find_cmd(cmd)
1118 except Exception:
1117 except Exception:
1119 pass
1118 pass
1120 else:
1119 else:
1121 assert cmd in ip.magics_manager.magics["cell"]
1120 assert cmd in ip.magics_manager.magics["cell"]
1122
1121
1123
1122
1124 @magics_class
1123 @magics_class
1125 class FooFoo(Magics):
1124 class FooFoo(Magics):
1126 """class with both %foo and %%foo magics"""
1125 """class with both %foo and %%foo magics"""
1127 @line_magic('foo')
1126 @line_magic('foo')
1128 def line_foo(self, line):
1127 def line_foo(self, line):
1129 "I am line foo"
1128 "I am line foo"
1130 pass
1129 pass
1131
1130
1132 @cell_magic("foo")
1131 @cell_magic("foo")
1133 def cell_foo(self, line, cell):
1132 def cell_foo(self, line, cell):
1134 "I am cell foo, not line foo"
1133 "I am cell foo, not line foo"
1135 pass
1134 pass
1136
1135
1137 def test_line_cell_info():
1136 def test_line_cell_info():
1138 """%%foo and %foo magics are distinguishable to inspect"""
1137 """%%foo and %foo magics are distinguishable to inspect"""
1139 ip = get_ipython()
1138 ip = get_ipython()
1140 ip.magics_manager.register(FooFoo)
1139 ip.magics_manager.register(FooFoo)
1141 oinfo = ip.object_inspect("foo")
1140 oinfo = ip.object_inspect("foo")
1142 assert oinfo["found"] is True
1141 assert oinfo["found"] is True
1143 assert oinfo["ismagic"] is True
1142 assert oinfo["ismagic"] is True
1144
1143
1145 oinfo = ip.object_inspect("%%foo")
1144 oinfo = ip.object_inspect("%%foo")
1146 assert oinfo["found"] is True
1145 assert oinfo["found"] is True
1147 assert oinfo["ismagic"] is True
1146 assert oinfo["ismagic"] is True
1148 assert oinfo["docstring"] == FooFoo.cell_foo.__doc__
1147 assert oinfo["docstring"] == FooFoo.cell_foo.__doc__
1149
1148
1150 oinfo = ip.object_inspect("%foo")
1149 oinfo = ip.object_inspect("%foo")
1151 assert oinfo["found"] is True
1150 assert oinfo["found"] is True
1152 assert oinfo["ismagic"] is True
1151 assert oinfo["ismagic"] is True
1153 assert oinfo["docstring"] == FooFoo.line_foo.__doc__
1152 assert oinfo["docstring"] == FooFoo.line_foo.__doc__
1154
1153
1155
1154
1156 def test_multiple_magics():
1155 def test_multiple_magics():
1157 ip = get_ipython()
1156 ip = get_ipython()
1158 foo1 = FooFoo(ip)
1157 foo1 = FooFoo(ip)
1159 foo2 = FooFoo(ip)
1158 foo2 = FooFoo(ip)
1160 mm = ip.magics_manager
1159 mm = ip.magics_manager
1161 mm.register(foo1)
1160 mm.register(foo1)
1162 assert mm.magics["line"]["foo"].__self__ is foo1
1161 assert mm.magics["line"]["foo"].__self__ is foo1
1163 mm.register(foo2)
1162 mm.register(foo2)
1164 assert mm.magics["line"]["foo"].__self__ is foo2
1163 assert mm.magics["line"]["foo"].__self__ is foo2
1165
1164
1166
1165
1167 def test_alias_magic():
1166 def test_alias_magic():
1168 """Test %alias_magic."""
1167 """Test %alias_magic."""
1169 ip = get_ipython()
1168 ip = get_ipython()
1170 mm = ip.magics_manager
1169 mm = ip.magics_manager
1171
1170
1172 # Basic operation: both cell and line magics are created, if possible.
1171 # Basic operation: both cell and line magics are created, if possible.
1173 ip.run_line_magic("alias_magic", "timeit_alias timeit")
1172 ip.run_line_magic("alias_magic", "timeit_alias timeit")
1174 assert "timeit_alias" in mm.magics["line"]
1173 assert "timeit_alias" in mm.magics["line"]
1175 assert "timeit_alias" in mm.magics["cell"]
1174 assert "timeit_alias" in mm.magics["cell"]
1176
1175
1177 # --cell is specified, line magic not created.
1176 # --cell is specified, line magic not created.
1178 ip.run_line_magic("alias_magic", "--cell timeit_cell_alias timeit")
1177 ip.run_line_magic("alias_magic", "--cell timeit_cell_alias timeit")
1179 assert "timeit_cell_alias" not in mm.magics["line"]
1178 assert "timeit_cell_alias" not in mm.magics["line"]
1180 assert "timeit_cell_alias" in mm.magics["cell"]
1179 assert "timeit_cell_alias" in mm.magics["cell"]
1181
1180
1182 # Test that line alias is created successfully.
1181 # Test that line alias is created successfully.
1183 ip.run_line_magic("alias_magic", "--line env_alias env")
1182 ip.run_line_magic("alias_magic", "--line env_alias env")
1184 assert ip.run_line_magic("env", "") == ip.run_line_magic("env_alias", "")
1183 assert ip.run_line_magic("env", "") == ip.run_line_magic("env_alias", "")
1185
1184
1186 # Test that line alias with parameters passed in is created successfully.
1185 # Test that line alias with parameters passed in is created successfully.
1187 ip.run_line_magic(
1186 ip.run_line_magic(
1188 "alias_magic", "--line history_alias history --params " + shlex.quote("3")
1187 "alias_magic", "--line history_alias history --params " + shlex.quote("3")
1189 )
1188 )
1190 assert "history_alias" in mm.magics["line"]
1189 assert "history_alias" in mm.magics["line"]
1191
1190
1192
1191
1193 def test_save():
1192 def test_save():
1194 """Test %save."""
1193 """Test %save."""
1195 ip = get_ipython()
1194 ip = get_ipython()
1196 ip.history_manager.reset() # Clear any existing history.
1195 ip.history_manager.reset() # Clear any existing history.
1197 cmds = ["a=1", "def b():\n return a**2", "print(a, b())"]
1196 cmds = ["a=1", "def b():\n return a**2", "print(a, b())"]
1198 for i, cmd in enumerate(cmds, start=1):
1197 for i, cmd in enumerate(cmds, start=1):
1199 ip.history_manager.store_inputs(i, cmd)
1198 ip.history_manager.store_inputs(i, cmd)
1200 with TemporaryDirectory() as tmpdir:
1199 with TemporaryDirectory() as tmpdir:
1201 file = os.path.join(tmpdir, "testsave.py")
1200 file = os.path.join(tmpdir, "testsave.py")
1202 ip.run_line_magic("save", "%s 1-10" % file)
1201 ip.run_line_magic("save", "%s 1-10" % file)
1203 content = Path(file).read_text(encoding="utf-8")
1202 content = Path(file).read_text(encoding="utf-8")
1204 assert content.count(cmds[0]) == 1
1203 assert content.count(cmds[0]) == 1
1205 assert "coding: utf-8" in content
1204 assert "coding: utf-8" in content
1206 ip.run_line_magic("save", "-a %s 1-10" % file)
1205 ip.run_line_magic("save", "-a %s 1-10" % file)
1207 content = Path(file).read_text(encoding="utf-8")
1206 content = Path(file).read_text(encoding="utf-8")
1208 assert content.count(cmds[0]) == 2
1207 assert content.count(cmds[0]) == 2
1209 assert "coding: utf-8" in content
1208 assert "coding: utf-8" in content
1210
1209
1211
1210
1212 def test_save_with_no_args():
1211 def test_save_with_no_args():
1213 ip = get_ipython()
1212 ip = get_ipython()
1214 ip.history_manager.reset() # Clear any existing history.
1213 ip.history_manager.reset() # Clear any existing history.
1215 cmds = ["a=1", "def b():\n return a**2", "print(a, b())", "%save"]
1214 cmds = ["a=1", "def b():\n return a**2", "print(a, b())", "%save"]
1216 for i, cmd in enumerate(cmds, start=1):
1215 for i, cmd in enumerate(cmds, start=1):
1217 ip.history_manager.store_inputs(i, cmd)
1216 ip.history_manager.store_inputs(i, cmd)
1218
1217
1219 with TemporaryDirectory() as tmpdir:
1218 with TemporaryDirectory() as tmpdir:
1220 path = os.path.join(tmpdir, "testsave.py")
1219 path = os.path.join(tmpdir, "testsave.py")
1221 ip.run_line_magic("save", path)
1220 ip.run_line_magic("save", path)
1222 content = Path(path).read_text(encoding="utf-8")
1221 content = Path(path).read_text(encoding="utf-8")
1223 expected_content = dedent(
1222 expected_content = dedent(
1224 """\
1223 """\
1225 # coding: utf-8
1224 # coding: utf-8
1226 a=1
1225 a=1
1227 def b():
1226 def b():
1228 return a**2
1227 return a**2
1229 print(a, b())
1228 print(a, b())
1230 """
1229 """
1231 )
1230 )
1232 assert content == expected_content
1231 assert content == expected_content
1233
1232
1234
1233
1235 def test_store():
1234 def test_store():
1236 """Test %store."""
1235 """Test %store."""
1237 ip = get_ipython()
1236 ip = get_ipython()
1238 ip.run_line_magic('load_ext', 'storemagic')
1237 ip.run_line_magic('load_ext', 'storemagic')
1239
1238
1240 # make sure the storage is empty
1239 # make sure the storage is empty
1241 ip.run_line_magic("store", "-z")
1240 ip.run_line_magic("store", "-z")
1242 ip.user_ns["var"] = 42
1241 ip.user_ns["var"] = 42
1243 ip.run_line_magic("store", "var")
1242 ip.run_line_magic("store", "var")
1244 ip.user_ns["var"] = 39
1243 ip.user_ns["var"] = 39
1245 ip.run_line_magic("store", "-r")
1244 ip.run_line_magic("store", "-r")
1246 assert ip.user_ns["var"] == 42
1245 assert ip.user_ns["var"] == 42
1247
1246
1248 ip.run_line_magic("store", "-d var")
1247 ip.run_line_magic("store", "-d var")
1249 ip.user_ns["var"] = 39
1248 ip.user_ns["var"] = 39
1250 ip.run_line_magic("store", "-r")
1249 ip.run_line_magic("store", "-r")
1251 assert ip.user_ns["var"] == 39
1250 assert ip.user_ns["var"] == 39
1252
1251
1253
1252
1254 def _run_edit_test(arg_s, exp_filename=None,
1253 def _run_edit_test(arg_s, exp_filename=None,
1255 exp_lineno=-1,
1254 exp_lineno=-1,
1256 exp_contents=None,
1255 exp_contents=None,
1257 exp_is_temp=None):
1256 exp_is_temp=None):
1258 ip = get_ipython()
1257 ip = get_ipython()
1259 M = code.CodeMagics(ip)
1258 M = code.CodeMagics(ip)
1260 last_call = ['','']
1259 last_call = ['','']
1261 opts,args = M.parse_options(arg_s,'prxn:')
1260 opts,args = M.parse_options(arg_s,'prxn:')
1262 filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call)
1261 filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call)
1263
1262
1264 if exp_filename is not None:
1263 if exp_filename is not None:
1265 assert exp_filename == filename
1264 assert exp_filename == filename
1266 if exp_contents is not None:
1265 if exp_contents is not None:
1267 with io.open(filename, 'r', encoding='utf-8') as f:
1266 with io.open(filename, 'r', encoding='utf-8') as f:
1268 contents = f.read()
1267 contents = f.read()
1269 assert exp_contents == contents
1268 assert exp_contents == contents
1270 if exp_lineno != -1:
1269 if exp_lineno != -1:
1271 assert exp_lineno == lineno
1270 assert exp_lineno == lineno
1272 if exp_is_temp is not None:
1271 if exp_is_temp is not None:
1273 assert exp_is_temp == is_temp
1272 assert exp_is_temp == is_temp
1274
1273
1275
1274
1276 def test_edit_interactive():
1275 def test_edit_interactive():
1277 """%edit on interactively defined objects"""
1276 """%edit on interactively defined objects"""
1278 ip = get_ipython()
1277 ip = get_ipython()
1279 n = ip.execution_count
1278 n = ip.execution_count
1280 ip.run_cell("def foo(): return 1", store_history=True)
1279 ip.run_cell("def foo(): return 1", store_history=True)
1281
1280
1282 with pytest.raises(code.InteractivelyDefined) as e:
1281 with pytest.raises(code.InteractivelyDefined) as e:
1283 _run_edit_test("foo")
1282 _run_edit_test("foo")
1284 assert e.value.index == n
1283 assert e.value.index == n
1285
1284
1286
1285
1287 def test_edit_cell():
1286 def test_edit_cell():
1288 """%edit [cell id]"""
1287 """%edit [cell id]"""
1289 ip = get_ipython()
1288 ip = get_ipython()
1290
1289
1291 ip.run_cell("def foo(): return 1", store_history=True)
1290 ip.run_cell("def foo(): return 1", store_history=True)
1292
1291
1293 # test
1292 # test
1294 _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True)
1293 _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True)
1295
1294
1296 def test_edit_fname():
1295 def test_edit_fname():
1297 """%edit file"""
1296 """%edit file"""
1298 # test
1297 # test
1299 _run_edit_test("test file.py", exp_filename="test file.py")
1298 _run_edit_test("test file.py", exp_filename="test file.py")
1300
1299
1301 def test_bookmark():
1300 def test_bookmark():
1302 ip = get_ipython()
1301 ip = get_ipython()
1303 ip.run_line_magic('bookmark', 'bmname')
1302 ip.run_line_magic('bookmark', 'bmname')
1304 with tt.AssertPrints('bmname'):
1303 with tt.AssertPrints('bmname'):
1305 ip.run_line_magic('bookmark', '-l')
1304 ip.run_line_magic('bookmark', '-l')
1306 ip.run_line_magic('bookmark', '-d bmname')
1305 ip.run_line_magic('bookmark', '-d bmname')
1307
1306
1308 def test_ls_magic():
1307 def test_ls_magic():
1309 ip = get_ipython()
1308 ip = get_ipython()
1310 json_formatter = ip.display_formatter.formatters['application/json']
1309 json_formatter = ip.display_formatter.formatters['application/json']
1311 json_formatter.enabled = True
1310 json_formatter.enabled = True
1312 lsmagic = ip.magic('lsmagic')
1311 lsmagic = ip.magic('lsmagic')
1313 with warnings.catch_warnings(record=True) as w:
1312 with warnings.catch_warnings(record=True) as w:
1314 j = json_formatter(lsmagic)
1313 j = json_formatter(lsmagic)
1315 assert sorted(j) == ["cell", "line"]
1314 assert sorted(j) == ["cell", "line"]
1316 assert w == [] # no warnings
1315 assert w == [] # no warnings
1317
1316
1318
1317
1319 def test_strip_initial_indent():
1318 def test_strip_initial_indent():
1320 def sii(s):
1319 def sii(s):
1321 lines = s.splitlines()
1320 lines = s.splitlines()
1322 return '\n'.join(code.strip_initial_indent(lines))
1321 return '\n'.join(code.strip_initial_indent(lines))
1323
1322
1324 assert sii(" a = 1\nb = 2") == "a = 1\nb = 2"
1323 assert sii(" a = 1\nb = 2") == "a = 1\nb = 2"
1325 assert sii(" a\n b\nc") == "a\n b\nc"
1324 assert sii(" a\n b\nc") == "a\n b\nc"
1326 assert sii("a\n b") == "a\n b"
1325 assert sii("a\n b") == "a\n b"
1327
1326
1328 def test_logging_magic_quiet_from_arg():
1327 def test_logging_magic_quiet_from_arg():
1329 _ip.config.LoggingMagics.quiet = False
1328 _ip.config.LoggingMagics.quiet = False
1330 lm = logging.LoggingMagics(shell=_ip)
1329 lm = logging.LoggingMagics(shell=_ip)
1331 with TemporaryDirectory() as td:
1330 with TemporaryDirectory() as td:
1332 try:
1331 try:
1333 with tt.AssertNotPrints(re.compile("Activating.*")):
1332 with tt.AssertNotPrints(re.compile("Activating.*")):
1334 lm.logstart('-q {}'.format(
1333 lm.logstart('-q {}'.format(
1335 os.path.join(td, "quiet_from_arg.log")))
1334 os.path.join(td, "quiet_from_arg.log")))
1336 finally:
1335 finally:
1337 _ip.logger.logstop()
1336 _ip.logger.logstop()
1338
1337
1339 def test_logging_magic_quiet_from_config():
1338 def test_logging_magic_quiet_from_config():
1340 _ip.config.LoggingMagics.quiet = True
1339 _ip.config.LoggingMagics.quiet = True
1341 lm = logging.LoggingMagics(shell=_ip)
1340 lm = logging.LoggingMagics(shell=_ip)
1342 with TemporaryDirectory() as td:
1341 with TemporaryDirectory() as td:
1343 try:
1342 try:
1344 with tt.AssertNotPrints(re.compile("Activating.*")):
1343 with tt.AssertNotPrints(re.compile("Activating.*")):
1345 lm.logstart(os.path.join(td, "quiet_from_config.log"))
1344 lm.logstart(os.path.join(td, "quiet_from_config.log"))
1346 finally:
1345 finally:
1347 _ip.logger.logstop()
1346 _ip.logger.logstop()
1348
1347
1349
1348
1350 def test_logging_magic_not_quiet():
1349 def test_logging_magic_not_quiet():
1351 _ip.config.LoggingMagics.quiet = False
1350 _ip.config.LoggingMagics.quiet = False
1352 lm = logging.LoggingMagics(shell=_ip)
1351 lm = logging.LoggingMagics(shell=_ip)
1353 with TemporaryDirectory() as td:
1352 with TemporaryDirectory() as td:
1354 try:
1353 try:
1355 with tt.AssertPrints(re.compile("Activating.*")):
1354 with tt.AssertPrints(re.compile("Activating.*")):
1356 lm.logstart(os.path.join(td, "not_quiet.log"))
1355 lm.logstart(os.path.join(td, "not_quiet.log"))
1357 finally:
1356 finally:
1358 _ip.logger.logstop()
1357 _ip.logger.logstop()
1359
1358
1360
1359
1361 def test_time_no_var_expand():
1360 def test_time_no_var_expand():
1362 _ip.user_ns['a'] = 5
1361 _ip.user_ns['a'] = 5
1363 _ip.user_ns['b'] = []
1362 _ip.user_ns['b'] = []
1364 _ip.magic('time b.append("{a}")')
1363 _ip.magic('time b.append("{a}")')
1365 assert _ip.user_ns['b'] == ['{a}']
1364 assert _ip.user_ns['b'] == ['{a}']
1366
1365
1367
1366
1368 # this is slow, put at the end for local testing.
1367 # this is slow, put at the end for local testing.
1369 def test_timeit_arguments():
1368 def test_timeit_arguments():
1370 "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
1369 "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
1371 _ip.magic("timeit -n1 -r1 a=('#')")
1370 _ip.magic("timeit -n1 -r1 a=('#')")
1372
1371
1373
1372
1374 MINIMAL_LAZY_MAGIC = """
1373 MINIMAL_LAZY_MAGIC = """
1375 from IPython.core.magic import (
1374 from IPython.core.magic import (
1376 Magics,
1375 Magics,
1377 magics_class,
1376 magics_class,
1378 line_magic,
1377 line_magic,
1379 cell_magic,
1378 cell_magic,
1380 )
1379 )
1381
1380
1382
1381
1383 @magics_class
1382 @magics_class
1384 class LazyMagics(Magics):
1383 class LazyMagics(Magics):
1385 @line_magic
1384 @line_magic
1386 def lazy_line(self, line):
1385 def lazy_line(self, line):
1387 print("Lazy Line")
1386 print("Lazy Line")
1388
1387
1389 @cell_magic
1388 @cell_magic
1390 def lazy_cell(self, line, cell):
1389 def lazy_cell(self, line, cell):
1391 print("Lazy Cell")
1390 print("Lazy Cell")
1392
1391
1393
1392
1394 def load_ipython_extension(ipython):
1393 def load_ipython_extension(ipython):
1395 ipython.register_magics(LazyMagics)
1394 ipython.register_magics(LazyMagics)
1396 """
1395 """
1397
1396
1398
1397
1399 def test_lazy_magics():
1398 def test_lazy_magics():
1400 with pytest.raises(UsageError):
1399 with pytest.raises(UsageError):
1401 ip.run_line_magic("lazy_line", "")
1400 ip.run_line_magic("lazy_line", "")
1402
1401
1403 startdir = os.getcwd()
1402 startdir = os.getcwd()
1404
1403
1405 with TemporaryDirectory() as tmpdir:
1404 with TemporaryDirectory() as tmpdir:
1406 with prepended_to_syspath(tmpdir):
1405 with prepended_to_syspath(tmpdir):
1407 ptempdir = Path(tmpdir)
1406 ptempdir = Path(tmpdir)
1408 tf = ptempdir / "lazy_magic_module.py"
1407 tf = ptempdir / "lazy_magic_module.py"
1409 tf.write_text(MINIMAL_LAZY_MAGIC)
1408 tf.write_text(MINIMAL_LAZY_MAGIC)
1410 ip.magics_manager.register_lazy("lazy_line", Path(tf.name).name[:-3])
1409 ip.magics_manager.register_lazy("lazy_line", Path(tf.name).name[:-3])
1411 with tt.AssertPrints("Lazy Line"):
1410 with tt.AssertPrints("Lazy Line"):
1412 ip.run_line_magic("lazy_line", "")
1411 ip.run_line_magic("lazy_line", "")
1413
1412
1414
1413
1415 TEST_MODULE = """
1414 TEST_MODULE = """
1416 print('Loaded my_tmp')
1415 print('Loaded my_tmp')
1417 if __name__ == "__main__":
1416 if __name__ == "__main__":
1418 print('I just ran a script')
1417 print('I just ran a script')
1419 """
1418 """
1420
1419
1421 def test_run_module_from_import_hook():
1420 def test_run_module_from_import_hook():
1422 "Test that a module can be loaded via an import hook"
1421 "Test that a module can be loaded via an import hook"
1423 with TemporaryDirectory() as tmpdir:
1422 with TemporaryDirectory() as tmpdir:
1424 fullpath = os.path.join(tmpdir, "my_tmp.py")
1423 fullpath = os.path.join(tmpdir, "my_tmp.py")
1425 Path(fullpath).write_text(TEST_MODULE, encoding="utf-8")
1424 Path(fullpath).write_text(TEST_MODULE, encoding="utf-8")
1426
1425
1427 import importlib.abc
1426 import importlib.abc
1428 import importlib.util
1427 import importlib.util
1429
1428
1430 class MyTempImporter(importlib.abc.MetaPathFinder, importlib.abc.SourceLoader):
1429 class MyTempImporter(importlib.abc.MetaPathFinder, importlib.abc.SourceLoader):
1431 def find_spec(self, fullname, path, target=None):
1430 def find_spec(self, fullname, path, target=None):
1432 if fullname == "my_tmp":
1431 if fullname == "my_tmp":
1433 return importlib.util.spec_from_loader(fullname, self)
1432 return importlib.util.spec_from_loader(fullname, self)
1434
1433
1435 def get_filename(self, fullname):
1434 def get_filename(self, fullname):
1436 assert fullname == "my_tmp"
1435 assert fullname == "my_tmp"
1437 return fullpath
1436 return fullpath
1438
1437
1439 def get_data(self, path):
1438 def get_data(self, path):
1440 assert Path(path).samefile(fullpath)
1439 assert Path(path).samefile(fullpath)
1441 return Path(fullpath).read_text(encoding="utf-8")
1440 return Path(fullpath).read_text(encoding="utf-8")
1442
1441
1443 sys.meta_path.insert(0, MyTempImporter())
1442 sys.meta_path.insert(0, MyTempImporter())
1444
1443
1445 with capture_output() as captured:
1444 with capture_output() as captured:
1446 _ip.magic("run -m my_tmp")
1445 _ip.magic("run -m my_tmp")
1447 _ip.run_cell("import my_tmp")
1446 _ip.run_cell("import my_tmp")
1448
1447
1449 output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n"
1448 output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n"
1450 assert output == captured.stdout
1449 assert output == captured.stdout
1451
1450
1452 sys.meta_path.pop(0)
1451 sys.meta_path.pop(0)
@@ -1,201 +1,200 b''
1 import errno
1 import errno
2 import os
2 import os
3 import shutil
3 import shutil
4 import sys
5 import tempfile
4 import tempfile
6 import warnings
5 import warnings
7 from unittest.mock import patch
6 from unittest.mock import patch
8
7
9 from tempfile import TemporaryDirectory
8 from tempfile import TemporaryDirectory
10 from testpath import assert_isdir, assert_isfile, modified_env
9 from testpath import assert_isdir, assert_isfile, modified_env
11
10
12 from IPython import paths
11 from IPython import paths
13 from IPython.testing.decorators import skip_win32
12 from IPython.testing.decorators import skip_win32
14
13
15 TMP_TEST_DIR = os.path.realpath(tempfile.mkdtemp())
14 TMP_TEST_DIR = os.path.realpath(tempfile.mkdtemp())
16 HOME_TEST_DIR = os.path.join(TMP_TEST_DIR, "home_test_dir")
15 HOME_TEST_DIR = os.path.join(TMP_TEST_DIR, "home_test_dir")
17 XDG_TEST_DIR = os.path.join(HOME_TEST_DIR, "xdg_test_dir")
16 XDG_TEST_DIR = os.path.join(HOME_TEST_DIR, "xdg_test_dir")
18 XDG_CACHE_DIR = os.path.join(HOME_TEST_DIR, "xdg_cache_dir")
17 XDG_CACHE_DIR = os.path.join(HOME_TEST_DIR, "xdg_cache_dir")
19 IP_TEST_DIR = os.path.join(HOME_TEST_DIR,'.ipython')
18 IP_TEST_DIR = os.path.join(HOME_TEST_DIR,'.ipython')
20
19
21 def setup_module():
20 def setup_module():
22 """Setup testenvironment for the module:
21 """Setup testenvironment for the module:
23
22
24 - Adds dummy home dir tree
23 - Adds dummy home dir tree
25 """
24 """
26 # Do not mask exceptions here. In particular, catching WindowsError is a
25 # Do not mask exceptions here. In particular, catching WindowsError is a
27 # problem because that exception is only defined on Windows...
26 # problem because that exception is only defined on Windows...
28 os.makedirs(IP_TEST_DIR)
27 os.makedirs(IP_TEST_DIR)
29 os.makedirs(os.path.join(XDG_TEST_DIR, 'ipython'))
28 os.makedirs(os.path.join(XDG_TEST_DIR, 'ipython'))
30 os.makedirs(os.path.join(XDG_CACHE_DIR, 'ipython'))
29 os.makedirs(os.path.join(XDG_CACHE_DIR, 'ipython'))
31
30
32
31
33 def teardown_module():
32 def teardown_module():
34 """Teardown testenvironment for the module:
33 """Teardown testenvironment for the module:
35
34
36 - Remove dummy home dir tree
35 - Remove dummy home dir tree
37 """
36 """
38 # Note: we remove the parent test dir, which is the root of all test
37 # Note: we remove the parent test dir, which is the root of all test
39 # subdirs we may have created. Use shutil instead of os.removedirs, so
38 # subdirs we may have created. Use shutil instead of os.removedirs, so
40 # that non-empty directories are all recursively removed.
39 # that non-empty directories are all recursively removed.
41 shutil.rmtree(TMP_TEST_DIR)
40 shutil.rmtree(TMP_TEST_DIR)
42
41
43 def patch_get_home_dir(dirpath):
42 def patch_get_home_dir(dirpath):
44 return patch.object(paths, 'get_home_dir', return_value=dirpath)
43 return patch.object(paths, 'get_home_dir', return_value=dirpath)
45
44
46
45
47 def test_get_ipython_dir_1():
46 def test_get_ipython_dir_1():
48 """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions."""
47 """test_get_ipython_dir_1, Testcase to see if we can call get_ipython_dir without Exceptions."""
49 env_ipdir = os.path.join("someplace", ".ipython")
48 env_ipdir = os.path.join("someplace", ".ipython")
50 with patch.object(paths, '_writable_dir', return_value=True), \
49 with patch.object(paths, '_writable_dir', return_value=True), \
51 modified_env({'IPYTHONDIR': env_ipdir}):
50 modified_env({'IPYTHONDIR': env_ipdir}):
52 ipdir = paths.get_ipython_dir()
51 ipdir = paths.get_ipython_dir()
53
52
54 assert ipdir == env_ipdir
53 assert ipdir == env_ipdir
55
54
56 def test_get_ipython_dir_2():
55 def test_get_ipython_dir_2():
57 """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions."""
56 """test_get_ipython_dir_2, Testcase to see if we can call get_ipython_dir without Exceptions."""
58 with patch_get_home_dir('someplace'), \
57 with patch_get_home_dir('someplace'), \
59 patch.object(paths, 'get_xdg_dir', return_value=None), \
58 patch.object(paths, 'get_xdg_dir', return_value=None), \
60 patch.object(paths, '_writable_dir', return_value=True), \
59 patch.object(paths, '_writable_dir', return_value=True), \
61 patch('os.name', "posix"), \
60 patch('os.name', "posix"), \
62 modified_env({'IPYTHON_DIR': None,
61 modified_env({'IPYTHON_DIR': None,
63 'IPYTHONDIR': None,
62 'IPYTHONDIR': None,
64 'XDG_CONFIG_HOME': None
63 'XDG_CONFIG_HOME': None
65 }):
64 }):
66 ipdir = paths.get_ipython_dir()
65 ipdir = paths.get_ipython_dir()
67
66
68 assert ipdir == os.path.join("someplace", ".ipython")
67 assert ipdir == os.path.join("someplace", ".ipython")
69
68
70 def test_get_ipython_dir_3():
69 def test_get_ipython_dir_3():
71 """test_get_ipython_dir_3, use XDG if defined and exists, and .ipython doesn't exist."""
70 """test_get_ipython_dir_3, use XDG if defined and exists, and .ipython doesn't exist."""
72 tmphome = TemporaryDirectory()
71 tmphome = TemporaryDirectory()
73 try:
72 try:
74 with patch_get_home_dir(tmphome.name), \
73 with patch_get_home_dir(tmphome.name), \
75 patch('os.name', 'posix'), \
74 patch('os.name', 'posix'), \
76 modified_env({
75 modified_env({
77 'IPYTHON_DIR': None,
76 'IPYTHON_DIR': None,
78 'IPYTHONDIR': None,
77 'IPYTHONDIR': None,
79 'XDG_CONFIG_HOME': XDG_TEST_DIR,
78 'XDG_CONFIG_HOME': XDG_TEST_DIR,
80 }), warnings.catch_warnings(record=True) as w:
79 }), warnings.catch_warnings(record=True) as w:
81 ipdir = paths.get_ipython_dir()
80 ipdir = paths.get_ipython_dir()
82
81
83 assert ipdir == os.path.join(tmphome.name, XDG_TEST_DIR, "ipython")
82 assert ipdir == os.path.join(tmphome.name, XDG_TEST_DIR, "ipython")
84 assert len(w) == 0
83 assert len(w) == 0
85 finally:
84 finally:
86 tmphome.cleanup()
85 tmphome.cleanup()
87
86
88 def test_get_ipython_dir_4():
87 def test_get_ipython_dir_4():
89 """test_get_ipython_dir_4, warn if XDG and home both exist."""
88 """test_get_ipython_dir_4, warn if XDG and home both exist."""
90 with patch_get_home_dir(HOME_TEST_DIR), \
89 with patch_get_home_dir(HOME_TEST_DIR), \
91 patch('os.name', 'posix'):
90 patch('os.name', 'posix'):
92 try:
91 try:
93 os.mkdir(os.path.join(XDG_TEST_DIR, 'ipython'))
92 os.mkdir(os.path.join(XDG_TEST_DIR, 'ipython'))
94 except OSError as e:
93 except OSError as e:
95 if e.errno != errno.EEXIST:
94 if e.errno != errno.EEXIST:
96 raise
95 raise
97
96
98
97
99 with modified_env({
98 with modified_env({
100 'IPYTHON_DIR': None,
99 'IPYTHON_DIR': None,
101 'IPYTHONDIR': None,
100 'IPYTHONDIR': None,
102 'XDG_CONFIG_HOME': XDG_TEST_DIR,
101 'XDG_CONFIG_HOME': XDG_TEST_DIR,
103 }), warnings.catch_warnings(record=True) as w:
102 }), warnings.catch_warnings(record=True) as w:
104 ipdir = paths.get_ipython_dir()
103 ipdir = paths.get_ipython_dir()
105
104
106 assert len(w) == 1
105 assert len(w) == 1
107 assert "Ignoring" in str(w[0])
106 assert "Ignoring" in str(w[0])
108
107
109
108
110 def test_get_ipython_dir_5():
109 def test_get_ipython_dir_5():
111 """test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist."""
110 """test_get_ipython_dir_5, use .ipython if exists and XDG defined, but doesn't exist."""
112 with patch_get_home_dir(HOME_TEST_DIR), \
111 with patch_get_home_dir(HOME_TEST_DIR), \
113 patch('os.name', 'posix'):
112 patch('os.name', 'posix'):
114 try:
113 try:
115 os.rmdir(os.path.join(XDG_TEST_DIR, 'ipython'))
114 os.rmdir(os.path.join(XDG_TEST_DIR, 'ipython'))
116 except OSError as e:
115 except OSError as e:
117 if e.errno != errno.ENOENT:
116 if e.errno != errno.ENOENT:
118 raise
117 raise
119
118
120 with modified_env({
119 with modified_env({
121 'IPYTHON_DIR': None,
120 'IPYTHON_DIR': None,
122 'IPYTHONDIR': None,
121 'IPYTHONDIR': None,
123 'XDG_CONFIG_HOME': XDG_TEST_DIR,
122 'XDG_CONFIG_HOME': XDG_TEST_DIR,
124 }):
123 }):
125 ipdir = paths.get_ipython_dir()
124 ipdir = paths.get_ipython_dir()
126
125
127 assert ipdir == IP_TEST_DIR
126 assert ipdir == IP_TEST_DIR
128
127
129 def test_get_ipython_dir_6():
128 def test_get_ipython_dir_6():
130 """test_get_ipython_dir_6, use home over XDG if defined and neither exist."""
129 """test_get_ipython_dir_6, use home over XDG if defined and neither exist."""
131 xdg = os.path.join(HOME_TEST_DIR, 'somexdg')
130 xdg = os.path.join(HOME_TEST_DIR, 'somexdg')
132 os.mkdir(xdg)
131 os.mkdir(xdg)
133 shutil.rmtree(os.path.join(HOME_TEST_DIR, '.ipython'))
132 shutil.rmtree(os.path.join(HOME_TEST_DIR, '.ipython'))
134 print(paths._writable_dir)
133 print(paths._writable_dir)
135 with patch_get_home_dir(HOME_TEST_DIR), \
134 with patch_get_home_dir(HOME_TEST_DIR), \
136 patch.object(paths, 'get_xdg_dir', return_value=xdg), \
135 patch.object(paths, 'get_xdg_dir', return_value=xdg), \
137 patch('os.name', 'posix'), \
136 patch('os.name', 'posix'), \
138 modified_env({
137 modified_env({
139 'IPYTHON_DIR': None,
138 'IPYTHON_DIR': None,
140 'IPYTHONDIR': None,
139 'IPYTHONDIR': None,
141 'XDG_CONFIG_HOME': None,
140 'XDG_CONFIG_HOME': None,
142 }), warnings.catch_warnings(record=True) as w:
141 }), warnings.catch_warnings(record=True) as w:
143 ipdir = paths.get_ipython_dir()
142 ipdir = paths.get_ipython_dir()
144
143
145 assert ipdir == os.path.join(HOME_TEST_DIR, ".ipython")
144 assert ipdir == os.path.join(HOME_TEST_DIR, ".ipython")
146 assert len(w) == 0
145 assert len(w) == 0
147
146
148 def test_get_ipython_dir_7():
147 def test_get_ipython_dir_7():
149 """test_get_ipython_dir_7, test home directory expansion on IPYTHONDIR"""
148 """test_get_ipython_dir_7, test home directory expansion on IPYTHONDIR"""
150 home_dir = os.path.normpath(os.path.expanduser('~'))
149 home_dir = os.path.normpath(os.path.expanduser('~'))
151 with modified_env({'IPYTHONDIR': os.path.join('~', 'somewhere')}), \
150 with modified_env({'IPYTHONDIR': os.path.join('~', 'somewhere')}), \
152 patch.object(paths, '_writable_dir', return_value=True):
151 patch.object(paths, '_writable_dir', return_value=True):
153 ipdir = paths.get_ipython_dir()
152 ipdir = paths.get_ipython_dir()
154 assert ipdir == os.path.join(home_dir, "somewhere")
153 assert ipdir == os.path.join(home_dir, "somewhere")
155
154
156
155
157 @skip_win32
156 @skip_win32
158 def test_get_ipython_dir_8():
157 def test_get_ipython_dir_8():
159 """test_get_ipython_dir_8, test / home directory"""
158 """test_get_ipython_dir_8, test / home directory"""
160 if not os.access("/", os.W_OK):
159 if not os.access("/", os.W_OK):
161 # test only when HOME directory actually writable
160 # test only when HOME directory actually writable
162 return
161 return
163
162
164 with patch.object(paths, "_writable_dir", lambda path: bool(path)), patch.object(
163 with patch.object(paths, "_writable_dir", lambda path: bool(path)), patch.object(
165 paths, "get_xdg_dir", return_value=None
164 paths, "get_xdg_dir", return_value=None
166 ), modified_env(
165 ), modified_env(
167 {
166 {
168 "IPYTHON_DIR": None,
167 "IPYTHON_DIR": None,
169 "IPYTHONDIR": None,
168 "IPYTHONDIR": None,
170 "HOME": "/",
169 "HOME": "/",
171 }
170 }
172 ):
171 ):
173 assert paths.get_ipython_dir() == "/.ipython"
172 assert paths.get_ipython_dir() == "/.ipython"
174
173
175
174
176 def test_get_ipython_cache_dir():
175 def test_get_ipython_cache_dir():
177 with modified_env({'HOME': HOME_TEST_DIR}):
176 with modified_env({'HOME': HOME_TEST_DIR}):
178 if os.name == "posix":
177 if os.name == "posix":
179 # test default
178 # test default
180 os.makedirs(os.path.join(HOME_TEST_DIR, ".cache"))
179 os.makedirs(os.path.join(HOME_TEST_DIR, ".cache"))
181 with modified_env({'XDG_CACHE_HOME': None}):
180 with modified_env({'XDG_CACHE_HOME': None}):
182 ipdir = paths.get_ipython_cache_dir()
181 ipdir = paths.get_ipython_cache_dir()
183 assert os.path.join(HOME_TEST_DIR, ".cache", "ipython") == ipdir
182 assert os.path.join(HOME_TEST_DIR, ".cache", "ipython") == ipdir
184 assert_isdir(ipdir)
183 assert_isdir(ipdir)
185
184
186 # test env override
185 # test env override
187 with modified_env({"XDG_CACHE_HOME": XDG_CACHE_DIR}):
186 with modified_env({"XDG_CACHE_HOME": XDG_CACHE_DIR}):
188 ipdir = paths.get_ipython_cache_dir()
187 ipdir = paths.get_ipython_cache_dir()
189 assert_isdir(ipdir)
188 assert_isdir(ipdir)
190 assert ipdir == os.path.join(XDG_CACHE_DIR, "ipython")
189 assert ipdir == os.path.join(XDG_CACHE_DIR, "ipython")
191 else:
190 else:
192 assert paths.get_ipython_cache_dir() == paths.get_ipython_dir()
191 assert paths.get_ipython_cache_dir() == paths.get_ipython_dir()
193
192
194 def test_get_ipython_package_dir():
193 def test_get_ipython_package_dir():
195 ipdir = paths.get_ipython_package_dir()
194 ipdir = paths.get_ipython_package_dir()
196 assert_isdir(ipdir)
195 assert_isdir(ipdir)
197
196
198
197
199 def test_get_ipython_module_path():
198 def test_get_ipython_module_path():
200 ipapp_path = paths.get_ipython_module_path('IPython.terminal.ipapp')
199 ipapp_path = paths.get_ipython_module_path('IPython.terminal.ipapp')
201 assert_isfile(ipapp_path)
200 assert_isfile(ipapp_path)
@@ -1,274 +1,271 b''
1 """Tests for pylab tools module.
1 """Tests for pylab tools module.
2 """
2 """
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 a2b_base64
8 from binascii import a2b_base64
9 from io import BytesIO
9 from io import BytesIO
10
10
11 import pytest
11 import pytest
12
12
13 matplotlib = pytest.importorskip("matplotlib")
13 matplotlib = pytest.importorskip("matplotlib")
14 matplotlib.use('Agg')
14 matplotlib.use('Agg')
15 from matplotlib.figure import Figure
15 from matplotlib.figure import Figure
16
16
17 from matplotlib import pyplot as plt
17 from matplotlib import pyplot as plt
18 from matplotlib_inline import backend_inline
18 from matplotlib_inline import backend_inline
19 import numpy as np
19 import numpy as np
20
20
21 from IPython.core.getipython import get_ipython
21 from IPython.core.getipython import get_ipython
22 from IPython.core.interactiveshell import InteractiveShell
22 from IPython.core.interactiveshell import InteractiveShell
23 from IPython.core.display import _PNG, _JPEG
23 from IPython.core.display import _PNG, _JPEG
24 from .. import pylabtools as pt
24 from .. import pylabtools as pt
25
25
26 from IPython.testing import decorators as dec
26 from IPython.testing import decorators as dec
27
27
28
28
29 def test_figure_to_svg():
29 def test_figure_to_svg():
30 # simple empty-figure test
30 # simple empty-figure test
31 fig = plt.figure()
31 fig = plt.figure()
32 assert pt.print_figure(fig, "svg") is None
32 assert pt.print_figure(fig, "svg") is None
33
33
34 plt.close('all')
34 plt.close('all')
35
35
36 # simple check for at least svg-looking output
36 # simple check for at least svg-looking output
37 fig = plt.figure()
37 fig = plt.figure()
38 ax = fig.add_subplot(1,1,1)
38 ax = fig.add_subplot(1,1,1)
39 ax.plot([1,2,3])
39 ax.plot([1,2,3])
40 plt.draw()
40 plt.draw()
41 svg = pt.print_figure(fig, "svg")[:100].lower()
41 svg = pt.print_figure(fig, "svg")[:100].lower()
42 assert "doctype svg" in svg
42 assert "doctype svg" in svg
43
43
44
44
45 def _check_pil_jpeg_bytes():
45 def _check_pil_jpeg_bytes():
46 """Skip if PIL can't write JPEGs to BytesIO objects"""
46 """Skip if PIL can't write JPEGs to BytesIO objects"""
47 # PIL's JPEG plugin can't write to BytesIO objects
47 # PIL's JPEG plugin can't write to BytesIO objects
48 # Pillow fixes this
48 # Pillow fixes this
49 from PIL import Image
49 from PIL import Image
50 buf = BytesIO()
50 buf = BytesIO()
51 img = Image.new("RGB", (4,4))
51 img = Image.new("RGB", (4,4))
52 try:
52 try:
53 img.save(buf, 'jpeg')
53 img.save(buf, 'jpeg')
54 except Exception as e:
54 except Exception as e:
55 ename = e.__class__.__name__
55 ename = e.__class__.__name__
56 raise pytest.skip("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e)) from e
56 raise pytest.skip("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e)) from e
57
57
58 @dec.skip_without("PIL.Image")
58 @dec.skip_without("PIL.Image")
59 def test_figure_to_jpeg():
59 def test_figure_to_jpeg():
60 _check_pil_jpeg_bytes()
60 _check_pil_jpeg_bytes()
61 # simple check for at least jpeg-looking output
61 # simple check for at least jpeg-looking output
62 fig = plt.figure()
62 fig = plt.figure()
63 ax = fig.add_subplot(1,1,1)
63 ax = fig.add_subplot(1,1,1)
64 ax.plot([1,2,3])
64 ax.plot([1,2,3])
65 plt.draw()
65 plt.draw()
66 jpeg = pt.print_figure(fig, 'jpeg', pil_kwargs={'optimize': 50})[:100].lower()
66 jpeg = pt.print_figure(fig, 'jpeg', pil_kwargs={'optimize': 50})[:100].lower()
67 assert jpeg.startswith(_JPEG)
67 assert jpeg.startswith(_JPEG)
68
68
69 def test_retina_figure():
69 def test_retina_figure():
70 # simple empty-figure test
70 # simple empty-figure test
71 fig = plt.figure()
71 fig = plt.figure()
72 assert pt.retina_figure(fig) == None
72 assert pt.retina_figure(fig) == None
73 plt.close('all')
73 plt.close('all')
74
74
75 fig = plt.figure()
75 fig = plt.figure()
76 ax = fig.add_subplot(1,1,1)
76 ax = fig.add_subplot(1,1,1)
77 ax.plot([1,2,3])
77 ax.plot([1,2,3])
78 plt.draw()
78 plt.draw()
79 png, md = pt.retina_figure(fig)
79 png, md = pt.retina_figure(fig)
80 assert png.startswith(_PNG)
80 assert png.startswith(_PNG)
81 assert "width" in md
81 assert "width" in md
82 assert "height" in md
82 assert "height" in md
83
83
84
84
85 _fmt_mime_map = {
85 _fmt_mime_map = {
86 'png': 'image/png',
86 'png': 'image/png',
87 'jpeg': 'image/jpeg',
87 'jpeg': 'image/jpeg',
88 'pdf': 'application/pdf',
88 'pdf': 'application/pdf',
89 'retina': 'image/png',
89 'retina': 'image/png',
90 'svg': 'image/svg+xml',
90 'svg': 'image/svg+xml',
91 }
91 }
92
92
93 def test_select_figure_formats_str():
93 def test_select_figure_formats_str():
94 ip = get_ipython()
94 ip = get_ipython()
95 for fmt, active_mime in _fmt_mime_map.items():
95 for fmt, active_mime in _fmt_mime_map.items():
96 pt.select_figure_formats(ip, fmt)
96 pt.select_figure_formats(ip, fmt)
97 for mime, f in ip.display_formatter.formatters.items():
97 for mime, f in ip.display_formatter.formatters.items():
98 if mime == active_mime:
98 if mime == active_mime:
99 assert Figure in f
99 assert Figure in f
100 else:
100 else:
101 assert Figure not in f
101 assert Figure not in f
102
102
103 def test_select_figure_formats_kwargs():
103 def test_select_figure_formats_kwargs():
104 ip = get_ipython()
104 ip = get_ipython()
105 kwargs = dict(bbox_inches="tight")
105 kwargs = dict(bbox_inches="tight")
106 pt.select_figure_formats(ip, "png", **kwargs)
106 pt.select_figure_formats(ip, "png", **kwargs)
107 formatter = ip.display_formatter.formatters["image/png"]
107 formatter = ip.display_formatter.formatters["image/png"]
108 f = formatter.lookup_by_type(Figure)
108 f = formatter.lookup_by_type(Figure)
109 cell = f.keywords
109 cell = f.keywords
110 expected = kwargs
110 expected = kwargs
111 expected["base64"] = True
111 expected["base64"] = True
112 expected["fmt"] = "png"
112 expected["fmt"] = "png"
113 assert cell == expected
113 assert cell == expected
114
114
115 # check that the formatter doesn't raise
115 # check that the formatter doesn't raise
116 fig = plt.figure()
116 fig = plt.figure()
117 ax = fig.add_subplot(1,1,1)
117 ax = fig.add_subplot(1,1,1)
118 ax.plot([1,2,3])
118 ax.plot([1,2,3])
119 plt.draw()
119 plt.draw()
120 formatter.enabled = True
120 formatter.enabled = True
121 png = formatter(fig)
121 png = formatter(fig)
122 assert isinstance(png, str)
122 assert isinstance(png, str)
123 png_bytes = a2b_base64(png)
123 png_bytes = a2b_base64(png)
124 assert png_bytes.startswith(_PNG)
124 assert png_bytes.startswith(_PNG)
125
125
126 def test_select_figure_formats_set():
126 def test_select_figure_formats_set():
127 ip = get_ipython()
127 ip = get_ipython()
128 for fmts in [
128 for fmts in [
129 {'png', 'svg'},
129 {'png', 'svg'},
130 ['png'],
130 ['png'],
131 ('jpeg', 'pdf', 'retina'),
131 ('jpeg', 'pdf', 'retina'),
132 {'svg'},
132 {'svg'},
133 ]:
133 ]:
134 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
134 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
135 pt.select_figure_formats(ip, fmts)
135 pt.select_figure_formats(ip, fmts)
136 for mime, f in ip.display_formatter.formatters.items():
136 for mime, f in ip.display_formatter.formatters.items():
137 if mime in active_mimes:
137 if mime in active_mimes:
138 assert Figure in f
138 assert Figure in f
139 else:
139 else:
140 assert Figure not in f
140 assert Figure not in f
141
141
142 def test_select_figure_formats_bad():
142 def test_select_figure_formats_bad():
143 ip = get_ipython()
143 ip = get_ipython()
144 with pytest.raises(ValueError):
144 with pytest.raises(ValueError):
145 pt.select_figure_formats(ip, 'foo')
145 pt.select_figure_formats(ip, 'foo')
146 with pytest.raises(ValueError):
146 with pytest.raises(ValueError):
147 pt.select_figure_formats(ip, {'png', 'foo'})
147 pt.select_figure_formats(ip, {'png', 'foo'})
148 with pytest.raises(ValueError):
148 with pytest.raises(ValueError):
149 pt.select_figure_formats(ip, ['retina', 'pdf', 'bar', 'bad'])
149 pt.select_figure_formats(ip, ['retina', 'pdf', 'bar', 'bad'])
150
150
151 def test_import_pylab():
151 def test_import_pylab():
152 ns = {}
152 ns = {}
153 pt.import_pylab(ns, import_all=False)
153 pt.import_pylab(ns, import_all=False)
154 assert "plt" in ns
154 assert "plt" in ns
155 assert ns["np"] == np
155 assert ns["np"] == np
156
156
157
157
158 from traitlets.config import Config
159
160
161 class TestPylabSwitch(object):
158 class TestPylabSwitch(object):
162 class Shell(InteractiveShell):
159 class Shell(InteractiveShell):
163 def init_history(self):
160 def init_history(self):
164 """Sets up the command history, and starts regular autosaves."""
161 """Sets up the command history, and starts regular autosaves."""
165 self.config.HistoryManager.hist_file = ":memory:"
162 self.config.HistoryManager.hist_file = ":memory:"
166 super().init_history()
163 super().init_history()
167
164
168 def enable_gui(self, gui):
165 def enable_gui(self, gui):
169 pass
166 pass
170
167
171 def setup(self):
168 def setup(self):
172 import matplotlib
169 import matplotlib
173 def act_mpl(backend):
170 def act_mpl(backend):
174 matplotlib.rcParams['backend'] = backend
171 matplotlib.rcParams['backend'] = backend
175
172
176 # Save rcParams since they get modified
173 # Save rcParams since they get modified
177 self._saved_rcParams = matplotlib.rcParams
174 self._saved_rcParams = matplotlib.rcParams
178 self._saved_rcParamsOrig = matplotlib.rcParamsOrig
175 self._saved_rcParamsOrig = matplotlib.rcParamsOrig
179 matplotlib.rcParams = dict(backend='Qt4Agg')
176 matplotlib.rcParams = dict(backend='Qt4Agg')
180 matplotlib.rcParamsOrig = dict(backend='Qt4Agg')
177 matplotlib.rcParamsOrig = dict(backend='Qt4Agg')
181
178
182 # Mock out functions
179 # Mock out functions
183 self._save_am = pt.activate_matplotlib
180 self._save_am = pt.activate_matplotlib
184 pt.activate_matplotlib = act_mpl
181 pt.activate_matplotlib = act_mpl
185 self._save_ip = pt.import_pylab
182 self._save_ip = pt.import_pylab
186 pt.import_pylab = lambda *a,**kw:None
183 pt.import_pylab = lambda *a,**kw:None
187 self._save_cis = backend_inline.configure_inline_support
184 self._save_cis = backend_inline.configure_inline_support
188 backend_inline.configure_inline_support = lambda *a, **kw: None
185 backend_inline.configure_inline_support = lambda *a, **kw: None
189
186
190 def teardown(self):
187 def teardown(self):
191 pt.activate_matplotlib = self._save_am
188 pt.activate_matplotlib = self._save_am
192 pt.import_pylab = self._save_ip
189 pt.import_pylab = self._save_ip
193 backend_inline.configure_inline_support = self._save_cis
190 backend_inline.configure_inline_support = self._save_cis
194 import matplotlib
191 import matplotlib
195 matplotlib.rcParams = self._saved_rcParams
192 matplotlib.rcParams = self._saved_rcParams
196 matplotlib.rcParamsOrig = self._saved_rcParamsOrig
193 matplotlib.rcParamsOrig = self._saved_rcParamsOrig
197
194
198 def test_qt(self):
195 def test_qt(self):
199
196
200 s = self.Shell()
197 s = self.Shell()
201 gui, backend = s.enable_matplotlib(None)
198 gui, backend = s.enable_matplotlib(None)
202 assert gui == "qt"
199 assert gui == "qt"
203 assert s.pylab_gui_select == "qt"
200 assert s.pylab_gui_select == "qt"
204
201
205 gui, backend = s.enable_matplotlib("inline")
202 gui, backend = s.enable_matplotlib("inline")
206 assert gui == "inline"
203 assert gui == "inline"
207 assert s.pylab_gui_select == "qt"
204 assert s.pylab_gui_select == "qt"
208
205
209 gui, backend = s.enable_matplotlib("qt")
206 gui, backend = s.enable_matplotlib("qt")
210 assert gui == "qt"
207 assert gui == "qt"
211 assert s.pylab_gui_select == "qt"
208 assert s.pylab_gui_select == "qt"
212
209
213 gui, backend = s.enable_matplotlib("inline")
210 gui, backend = s.enable_matplotlib("inline")
214 assert gui == "inline"
211 assert gui == "inline"
215 assert s.pylab_gui_select == "qt"
212 assert s.pylab_gui_select == "qt"
216
213
217 gui, backend = s.enable_matplotlib()
214 gui, backend = s.enable_matplotlib()
218 assert gui == "qt"
215 assert gui == "qt"
219 assert s.pylab_gui_select == "qt"
216 assert s.pylab_gui_select == "qt"
220
217
221 def test_inline(self):
218 def test_inline(self):
222 s = self.Shell()
219 s = self.Shell()
223 gui, backend = s.enable_matplotlib("inline")
220 gui, backend = s.enable_matplotlib("inline")
224 assert gui == "inline"
221 assert gui == "inline"
225 assert s.pylab_gui_select == None
222 assert s.pylab_gui_select == None
226
223
227 gui, backend = s.enable_matplotlib("inline")
224 gui, backend = s.enable_matplotlib("inline")
228 assert gui == "inline"
225 assert gui == "inline"
229 assert s.pylab_gui_select == None
226 assert s.pylab_gui_select == None
230
227
231 gui, backend = s.enable_matplotlib("qt")
228 gui, backend = s.enable_matplotlib("qt")
232 assert gui == "qt"
229 assert gui == "qt"
233 assert s.pylab_gui_select == "qt"
230 assert s.pylab_gui_select == "qt"
234
231
235 def test_inline_twice(self):
232 def test_inline_twice(self):
236 "Using '%matplotlib inline' twice should not reset formatters"
233 "Using '%matplotlib inline' twice should not reset formatters"
237
234
238 ip = self.Shell()
235 ip = self.Shell()
239 gui, backend = ip.enable_matplotlib("inline")
236 gui, backend = ip.enable_matplotlib("inline")
240 assert gui == "inline"
237 assert gui == "inline"
241
238
242 fmts = {'png'}
239 fmts = {'png'}
243 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
240 active_mimes = {_fmt_mime_map[fmt] for fmt in fmts}
244 pt.select_figure_formats(ip, fmts)
241 pt.select_figure_formats(ip, fmts)
245
242
246 gui, backend = ip.enable_matplotlib("inline")
243 gui, backend = ip.enable_matplotlib("inline")
247 assert gui == "inline"
244 assert gui == "inline"
248
245
249 for mime, f in ip.display_formatter.formatters.items():
246 for mime, f in ip.display_formatter.formatters.items():
250 if mime in active_mimes:
247 if mime in active_mimes:
251 assert Figure in f
248 assert Figure in f
252 else:
249 else:
253 assert Figure not in f
250 assert Figure not in f
254
251
255 def test_qt_gtk(self):
252 def test_qt_gtk(self):
256 s = self.Shell()
253 s = self.Shell()
257 gui, backend = s.enable_matplotlib("qt")
254 gui, backend = s.enable_matplotlib("qt")
258 assert gui == "qt"
255 assert gui == "qt"
259 assert s.pylab_gui_select == "qt"
256 assert s.pylab_gui_select == "qt"
260
257
261 gui, backend = s.enable_matplotlib("gtk")
258 gui, backend = s.enable_matplotlib("gtk")
262 assert gui == "qt"
259 assert gui == "qt"
263 assert s.pylab_gui_select == "qt"
260 assert s.pylab_gui_select == "qt"
264
261
265
262
266 def test_no_gui_backends():
263 def test_no_gui_backends():
267 for k in ['agg', 'svg', 'pdf', 'ps']:
264 for k in ['agg', 'svg', 'pdf', 'ps']:
268 assert k not in pt.backend2gui
265 assert k not in pt.backend2gui
269
266
270
267
271 def test_figure_no_canvas():
268 def test_figure_no_canvas():
272 fig = Figure()
269 fig = Figure()
273 fig.canvas = None
270 fig.canvas = None
274 pt.print_figure(fig)
271 pt.print_figure(fig)
@@ -1,409 +1,408 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
6 import os.path
5 import os.path
7 import platform
6 import platform
8 import re
7 import re
9 import sys
8 import sys
10 import traceback
9 import traceback
11 import unittest
10 import unittest
12 from textwrap import dedent
11 from textwrap import dedent
13
12
14 from tempfile import TemporaryDirectory
13 from tempfile import TemporaryDirectory
15
14
16 from IPython.core.ultratb import ColorTB, VerboseTB
15 from IPython.core.ultratb import ColorTB, VerboseTB
17 from IPython.testing import tools as tt
16 from IPython.testing import tools as tt
18 from IPython.testing.decorators import onlyif_unicode_paths
17 from IPython.testing.decorators import onlyif_unicode_paths
19 from IPython.utils.syspathcontext import prepended_to_syspath
18 from IPython.utils.syspathcontext import prepended_to_syspath
20
19
21 file_1 = """1
20 file_1 = """1
22 2
21 2
23 3
22 3
24 def f():
23 def f():
25 1/0
24 1/0
26 """
25 """
27
26
28 file_2 = """def f():
27 file_2 = """def f():
29 1/0
28 1/0
30 """
29 """
31
30
32
31
33 def recursionlimit(frames):
32 def recursionlimit(frames):
34 """
33 """
35 decorator to set the recursion limit temporarily
34 decorator to set the recursion limit temporarily
36 """
35 """
37
36
38 def inner(test_function):
37 def inner(test_function):
39 def wrapper(*args, **kwargs):
38 def wrapper(*args, **kwargs):
40 rl = sys.getrecursionlimit()
39 rl = sys.getrecursionlimit()
41 sys.setrecursionlimit(frames)
40 sys.setrecursionlimit(frames)
42 try:
41 try:
43 return test_function(*args, **kwargs)
42 return test_function(*args, **kwargs)
44 finally:
43 finally:
45 sys.setrecursionlimit(rl)
44 sys.setrecursionlimit(rl)
46
45
47 return wrapper
46 return wrapper
48
47
49 return inner
48 return inner
50
49
51
50
52 class ChangedPyFileTest(unittest.TestCase):
51 class ChangedPyFileTest(unittest.TestCase):
53 def test_changing_py_file(self):
52 def test_changing_py_file(self):
54 """Traceback produced if the line where the error occurred is missing?
53 """Traceback produced if the line where the error occurred is missing?
55
54
56 https://github.com/ipython/ipython/issues/1456
55 https://github.com/ipython/ipython/issues/1456
57 """
56 """
58 with TemporaryDirectory() as td:
57 with TemporaryDirectory() as td:
59 fname = os.path.join(td, "foo.py")
58 fname = os.path.join(td, "foo.py")
60 with open(fname, "w", encoding="utf-8") as f:
59 with open(fname, "w", encoding="utf-8") as f:
61 f.write(file_1)
60 f.write(file_1)
62
61
63 with prepended_to_syspath(td):
62 with prepended_to_syspath(td):
64 ip.run_cell("import foo")
63 ip.run_cell("import foo")
65
64
66 with tt.AssertPrints("ZeroDivisionError"):
65 with tt.AssertPrints("ZeroDivisionError"):
67 ip.run_cell("foo.f()")
66 ip.run_cell("foo.f()")
68
67
69 # Make the file shorter, so the line of the error is missing.
68 # Make the file shorter, so the line of the error is missing.
70 with open(fname, "w", encoding="utf-8") as f:
69 with open(fname, "w", encoding="utf-8") as f:
71 f.write(file_2)
70 f.write(file_2)
72
71
73 # For some reason, this was failing on the *second* call after
72 # For some reason, this was failing on the *second* call after
74 # changing the file, so we call f() twice.
73 # changing the file, so we call f() twice.
75 with tt.AssertNotPrints("Internal Python error", channel='stderr'):
74 with tt.AssertNotPrints("Internal Python error", channel='stderr'):
76 with tt.AssertPrints("ZeroDivisionError"):
75 with tt.AssertPrints("ZeroDivisionError"):
77 ip.run_cell("foo.f()")
76 ip.run_cell("foo.f()")
78 with tt.AssertPrints("ZeroDivisionError"):
77 with tt.AssertPrints("ZeroDivisionError"):
79 ip.run_cell("foo.f()")
78 ip.run_cell("foo.f()")
80
79
81 iso_8859_5_file = u'''# coding: iso-8859-5
80 iso_8859_5_file = u'''# coding: iso-8859-5
82
81
83 def fail():
82 def fail():
84 """Π΄Π±Π˜Π–"""
83 """Π΄Π±Π˜Π–"""
85 1/0 # Π΄Π±Π˜Π–
84 1/0 # Π΄Π±Π˜Π–
86 '''
85 '''
87
86
88 class NonAsciiTest(unittest.TestCase):
87 class NonAsciiTest(unittest.TestCase):
89 @onlyif_unicode_paths
88 @onlyif_unicode_paths
90 def test_nonascii_path(self):
89 def test_nonascii_path(self):
91 # Non-ascii directory name as well.
90 # Non-ascii directory name as well.
92 with TemporaryDirectory(suffix=u'Γ©') as td:
91 with TemporaryDirectory(suffix=u'Γ©') as td:
93 fname = os.path.join(td, u"fooΓ©.py")
92 fname = os.path.join(td, u"fooΓ©.py")
94 with open(fname, "w", encoding="utf-8") as f:
93 with open(fname, "w", encoding="utf-8") as f:
95 f.write(file_1)
94 f.write(file_1)
96
95
97 with prepended_to_syspath(td):
96 with prepended_to_syspath(td):
98 ip.run_cell("import foo")
97 ip.run_cell("import foo")
99
98
100 with tt.AssertPrints("ZeroDivisionError"):
99 with tt.AssertPrints("ZeroDivisionError"):
101 ip.run_cell("foo.f()")
100 ip.run_cell("foo.f()")
102
101
103 def test_iso8859_5(self):
102 def test_iso8859_5(self):
104 with TemporaryDirectory() as td:
103 with TemporaryDirectory() as td:
105 fname = os.path.join(td, 'dfghjkl.py')
104 fname = os.path.join(td, 'dfghjkl.py')
106
105
107 with io.open(fname, 'w', encoding='iso-8859-5') as f:
106 with io.open(fname, 'w', encoding='iso-8859-5') as f:
108 f.write(iso_8859_5_file)
107 f.write(iso_8859_5_file)
109
108
110 with prepended_to_syspath(td):
109 with prepended_to_syspath(td):
111 ip.run_cell("from dfghjkl import fail")
110 ip.run_cell("from dfghjkl import fail")
112
111
113 with tt.AssertPrints("ZeroDivisionError"):
112 with tt.AssertPrints("ZeroDivisionError"):
114 with tt.AssertPrints(u'Π΄Π±Π˜Π–', suppress=False):
113 with tt.AssertPrints(u'Π΄Π±Π˜Π–', suppress=False):
115 ip.run_cell('fail()')
114 ip.run_cell('fail()')
116
115
117 def test_nonascii_msg(self):
116 def test_nonascii_msg(self):
118 cell = u"raise Exception('Γ©')"
117 cell = u"raise Exception('Γ©')"
119 expected = u"Exception('Γ©')"
118 expected = u"Exception('Γ©')"
120 ip.run_cell("%xmode plain")
119 ip.run_cell("%xmode plain")
121 with tt.AssertPrints(expected):
120 with tt.AssertPrints(expected):
122 ip.run_cell(cell)
121 ip.run_cell(cell)
123
122
124 ip.run_cell("%xmode verbose")
123 ip.run_cell("%xmode verbose")
125 with tt.AssertPrints(expected):
124 with tt.AssertPrints(expected):
126 ip.run_cell(cell)
125 ip.run_cell(cell)
127
126
128 ip.run_cell("%xmode context")
127 ip.run_cell("%xmode context")
129 with tt.AssertPrints(expected):
128 with tt.AssertPrints(expected):
130 ip.run_cell(cell)
129 ip.run_cell(cell)
131
130
132 ip.run_cell("%xmode minimal")
131 ip.run_cell("%xmode minimal")
133 with tt.AssertPrints(u"Exception: Γ©"):
132 with tt.AssertPrints(u"Exception: Γ©"):
134 ip.run_cell(cell)
133 ip.run_cell(cell)
135
134
136 # Put this back into Context mode for later tests.
135 # Put this back into Context mode for later tests.
137 ip.run_cell("%xmode context")
136 ip.run_cell("%xmode context")
138
137
139 class NestedGenExprTestCase(unittest.TestCase):
138 class NestedGenExprTestCase(unittest.TestCase):
140 """
139 """
141 Regression test for the following issues:
140 Regression test for the following issues:
142 https://github.com/ipython/ipython/issues/8293
141 https://github.com/ipython/ipython/issues/8293
143 https://github.com/ipython/ipython/issues/8205
142 https://github.com/ipython/ipython/issues/8205
144 """
143 """
145 def test_nested_genexpr(self):
144 def test_nested_genexpr(self):
146 code = dedent(
145 code = dedent(
147 """\
146 """\
148 class SpecificException(Exception):
147 class SpecificException(Exception):
149 pass
148 pass
150
149
151 def foo(x):
150 def foo(x):
152 raise SpecificException("Success!")
151 raise SpecificException("Success!")
153
152
154 sum(sum(foo(x) for _ in [0]) for x in [0])
153 sum(sum(foo(x) for _ in [0]) for x in [0])
155 """
154 """
156 )
155 )
157 with tt.AssertPrints('SpecificException: Success!', suppress=False):
156 with tt.AssertPrints('SpecificException: Success!', suppress=False):
158 ip.run_cell(code)
157 ip.run_cell(code)
159
158
160
159
161 indentationerror_file = """if True:
160 indentationerror_file = """if True:
162 zoon()
161 zoon()
163 """
162 """
164
163
165 class IndentationErrorTest(unittest.TestCase):
164 class IndentationErrorTest(unittest.TestCase):
166 def test_indentationerror_shows_line(self):
165 def test_indentationerror_shows_line(self):
167 # See issue gh-2398
166 # See issue gh-2398
168 with tt.AssertPrints("IndentationError"):
167 with tt.AssertPrints("IndentationError"):
169 with tt.AssertPrints("zoon()", suppress=False):
168 with tt.AssertPrints("zoon()", suppress=False):
170 ip.run_cell(indentationerror_file)
169 ip.run_cell(indentationerror_file)
171
170
172 with TemporaryDirectory() as td:
171 with TemporaryDirectory() as td:
173 fname = os.path.join(td, "foo.py")
172 fname = os.path.join(td, "foo.py")
174 with open(fname, "w", encoding="utf-8") as f:
173 with open(fname, "w", encoding="utf-8") as f:
175 f.write(indentationerror_file)
174 f.write(indentationerror_file)
176
175
177 with tt.AssertPrints("IndentationError"):
176 with tt.AssertPrints("IndentationError"):
178 with tt.AssertPrints("zoon()", suppress=False):
177 with tt.AssertPrints("zoon()", suppress=False):
179 ip.magic('run %s' % fname)
178 ip.magic('run %s' % fname)
180
179
181 se_file_1 = """1
180 se_file_1 = """1
182 2
181 2
183 7/
182 7/
184 """
183 """
185
184
186 se_file_2 = """7/
185 se_file_2 = """7/
187 """
186 """
188
187
189 class SyntaxErrorTest(unittest.TestCase):
188 class SyntaxErrorTest(unittest.TestCase):
190
189
191 def test_syntaxerror_no_stacktrace_at_compile_time(self):
190 def test_syntaxerror_no_stacktrace_at_compile_time(self):
192 syntax_error_at_compile_time = """
191 syntax_error_at_compile_time = """
193 def foo():
192 def foo():
194 ..
193 ..
195 """
194 """
196 with tt.AssertPrints("SyntaxError"):
195 with tt.AssertPrints("SyntaxError"):
197 ip.run_cell(syntax_error_at_compile_time)
196 ip.run_cell(syntax_error_at_compile_time)
198
197
199 with tt.AssertNotPrints("foo()"):
198 with tt.AssertNotPrints("foo()"):
200 ip.run_cell(syntax_error_at_compile_time)
199 ip.run_cell(syntax_error_at_compile_time)
201
200
202 def test_syntaxerror_stacktrace_when_running_compiled_code(self):
201 def test_syntaxerror_stacktrace_when_running_compiled_code(self):
203 syntax_error_at_runtime = """
202 syntax_error_at_runtime = """
204 def foo():
203 def foo():
205 eval("..")
204 eval("..")
206
205
207 def bar():
206 def bar():
208 foo()
207 foo()
209
208
210 bar()
209 bar()
211 """
210 """
212 with tt.AssertPrints("SyntaxError"):
211 with tt.AssertPrints("SyntaxError"):
213 ip.run_cell(syntax_error_at_runtime)
212 ip.run_cell(syntax_error_at_runtime)
214 # Assert syntax error during runtime generate stacktrace
213 # Assert syntax error during runtime generate stacktrace
215 with tt.AssertPrints(["foo()", "bar()"]):
214 with tt.AssertPrints(["foo()", "bar()"]):
216 ip.run_cell(syntax_error_at_runtime)
215 ip.run_cell(syntax_error_at_runtime)
217 del ip.user_ns['bar']
216 del ip.user_ns['bar']
218 del ip.user_ns['foo']
217 del ip.user_ns['foo']
219
218
220 def test_changing_py_file(self):
219 def test_changing_py_file(self):
221 with TemporaryDirectory() as td:
220 with TemporaryDirectory() as td:
222 fname = os.path.join(td, "foo.py")
221 fname = os.path.join(td, "foo.py")
223 with open(fname, "w", encoding="utf-8") as f:
222 with open(fname, "w", encoding="utf-8") as f:
224 f.write(se_file_1)
223 f.write(se_file_1)
225
224
226 with tt.AssertPrints(["7/", "SyntaxError"]):
225 with tt.AssertPrints(["7/", "SyntaxError"]):
227 ip.magic("run " + fname)
226 ip.magic("run " + fname)
228
227
229 # Modify the file
228 # Modify the file
230 with open(fname, "w", encoding="utf-8") as f:
229 with open(fname, "w", encoding="utf-8") as f:
231 f.write(se_file_2)
230 f.write(se_file_2)
232
231
233 # The SyntaxError should point to the correct line
232 # The SyntaxError should point to the correct line
234 with tt.AssertPrints(["7/", "SyntaxError"]):
233 with tt.AssertPrints(["7/", "SyntaxError"]):
235 ip.magic("run " + fname)
234 ip.magic("run " + fname)
236
235
237 def test_non_syntaxerror(self):
236 def test_non_syntaxerror(self):
238 # SyntaxTB may be called with an error other than a SyntaxError
237 # SyntaxTB may be called with an error other than a SyntaxError
239 # See e.g. gh-4361
238 # See e.g. gh-4361
240 try:
239 try:
241 raise ValueError('QWERTY')
240 raise ValueError('QWERTY')
242 except ValueError:
241 except ValueError:
243 with tt.AssertPrints('QWERTY'):
242 with tt.AssertPrints('QWERTY'):
244 ip.showsyntaxerror()
243 ip.showsyntaxerror()
245
244
246 import sys
245 import sys
247
246
248 if sys.version_info < (3, 9) and platform.python_implementation() != "PyPy":
247 if sys.version_info < (3, 9) and platform.python_implementation() != "PyPy":
249 """
248 """
250 New 3.9 Pgen Parser does not raise Memory error, except on failed malloc.
249 New 3.9 Pgen Parser does not raise Memory error, except on failed malloc.
251 """
250 """
252 class MemoryErrorTest(unittest.TestCase):
251 class MemoryErrorTest(unittest.TestCase):
253 def test_memoryerror(self):
252 def test_memoryerror(self):
254 memoryerror_code = "(" * 200 + ")" * 200
253 memoryerror_code = "(" * 200 + ")" * 200
255 with tt.AssertPrints("MemoryError"):
254 with tt.AssertPrints("MemoryError"):
256 ip.run_cell(memoryerror_code)
255 ip.run_cell(memoryerror_code)
257
256
258
257
259 class Python3ChainedExceptionsTest(unittest.TestCase):
258 class Python3ChainedExceptionsTest(unittest.TestCase):
260 DIRECT_CAUSE_ERROR_CODE = """
259 DIRECT_CAUSE_ERROR_CODE = """
261 try:
260 try:
262 x = 1 + 2
261 x = 1 + 2
263 print(not_defined_here)
262 print(not_defined_here)
264 except Exception as e:
263 except Exception as e:
265 x += 55
264 x += 55
266 x - 1
265 x - 1
267 y = {}
266 y = {}
268 raise KeyError('uh') from e
267 raise KeyError('uh') from e
269 """
268 """
270
269
271 EXCEPTION_DURING_HANDLING_CODE = """
270 EXCEPTION_DURING_HANDLING_CODE = """
272 try:
271 try:
273 x = 1 + 2
272 x = 1 + 2
274 print(not_defined_here)
273 print(not_defined_here)
275 except Exception as e:
274 except Exception as e:
276 x += 55
275 x += 55
277 x - 1
276 x - 1
278 y = {}
277 y = {}
279 raise KeyError('uh')
278 raise KeyError('uh')
280 """
279 """
281
280
282 SUPPRESS_CHAINING_CODE = """
281 SUPPRESS_CHAINING_CODE = """
283 try:
282 try:
284 1/0
283 1/0
285 except Exception:
284 except Exception:
286 raise ValueError("Yikes") from None
285 raise ValueError("Yikes") from None
287 """
286 """
288
287
289 def test_direct_cause_error(self):
288 def test_direct_cause_error(self):
290 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
289 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
291 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
290 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
292
291
293 def test_exception_during_handling_error(self):
292 def test_exception_during_handling_error(self):
294 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
293 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
295 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
294 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
296
295
297 def test_suppress_exception_chaining(self):
296 def test_suppress_exception_chaining(self):
298 with tt.AssertNotPrints("ZeroDivisionError"), \
297 with tt.AssertNotPrints("ZeroDivisionError"), \
299 tt.AssertPrints("ValueError", suppress=False):
298 tt.AssertPrints("ValueError", suppress=False):
300 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
299 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
301
300
302 def test_plain_direct_cause_error(self):
301 def test_plain_direct_cause_error(self):
303 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
302 with tt.AssertPrints(["KeyError", "NameError", "direct cause"]):
304 ip.run_cell("%xmode Plain")
303 ip.run_cell("%xmode Plain")
305 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
304 ip.run_cell(self.DIRECT_CAUSE_ERROR_CODE)
306 ip.run_cell("%xmode Verbose")
305 ip.run_cell("%xmode Verbose")
307
306
308 def test_plain_exception_during_handling_error(self):
307 def test_plain_exception_during_handling_error(self):
309 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
308 with tt.AssertPrints(["KeyError", "NameError", "During handling"]):
310 ip.run_cell("%xmode Plain")
309 ip.run_cell("%xmode Plain")
311 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
310 ip.run_cell(self.EXCEPTION_DURING_HANDLING_CODE)
312 ip.run_cell("%xmode Verbose")
311 ip.run_cell("%xmode Verbose")
313
312
314 def test_plain_suppress_exception_chaining(self):
313 def test_plain_suppress_exception_chaining(self):
315 with tt.AssertNotPrints("ZeroDivisionError"), \
314 with tt.AssertNotPrints("ZeroDivisionError"), \
316 tt.AssertPrints("ValueError", suppress=False):
315 tt.AssertPrints("ValueError", suppress=False):
317 ip.run_cell("%xmode Plain")
316 ip.run_cell("%xmode Plain")
318 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
317 ip.run_cell(self.SUPPRESS_CHAINING_CODE)
319 ip.run_cell("%xmode Verbose")
318 ip.run_cell("%xmode Verbose")
320
319
321
320
322 class RecursionTest(unittest.TestCase):
321 class RecursionTest(unittest.TestCase):
323 DEFINITIONS = """
322 DEFINITIONS = """
324 def non_recurs():
323 def non_recurs():
325 1/0
324 1/0
326
325
327 def r1():
326 def r1():
328 r1()
327 r1()
329
328
330 def r3a():
329 def r3a():
331 r3b()
330 r3b()
332
331
333 def r3b():
332 def r3b():
334 r3c()
333 r3c()
335
334
336 def r3c():
335 def r3c():
337 r3a()
336 r3a()
338
337
339 def r3o1():
338 def r3o1():
340 r3a()
339 r3a()
341
340
342 def r3o2():
341 def r3o2():
343 r3o1()
342 r3o1()
344 """
343 """
345 def setUp(self):
344 def setUp(self):
346 ip.run_cell(self.DEFINITIONS)
345 ip.run_cell(self.DEFINITIONS)
347
346
348 def test_no_recursion(self):
347 def test_no_recursion(self):
349 with tt.AssertNotPrints("skipping similar frames"):
348 with tt.AssertNotPrints("skipping similar frames"):
350 ip.run_cell("non_recurs()")
349 ip.run_cell("non_recurs()")
351
350
352 @recursionlimit(200)
351 @recursionlimit(200)
353 def test_recursion_one_frame(self):
352 def test_recursion_one_frame(self):
354 with tt.AssertPrints(re.compile(
353 with tt.AssertPrints(re.compile(
355 r"\[\.\.\. skipping similar frames: r1 at line 5 \(\d{2,3} times\)\]")
354 r"\[\.\.\. skipping similar frames: r1 at line 5 \(\d{2,3} times\)\]")
356 ):
355 ):
357 ip.run_cell("r1()")
356 ip.run_cell("r1()")
358
357
359 @recursionlimit(160)
358 @recursionlimit(160)
360 def test_recursion_three_frames(self):
359 def test_recursion_three_frames(self):
361 with tt.AssertPrints("[... skipping similar frames: "), \
360 with tt.AssertPrints("[... skipping similar frames: "), \
362 tt.AssertPrints(re.compile(r"r3a at line 8 \(\d{2} times\)"), suppress=False), \
361 tt.AssertPrints(re.compile(r"r3a at line 8 \(\d{2} times\)"), suppress=False), \
363 tt.AssertPrints(re.compile(r"r3b at line 11 \(\d{2} times\)"), suppress=False), \
362 tt.AssertPrints(re.compile(r"r3b at line 11 \(\d{2} times\)"), suppress=False), \
364 tt.AssertPrints(re.compile(r"r3c at line 14 \(\d{2} times\)"), suppress=False):
363 tt.AssertPrints(re.compile(r"r3c at line 14 \(\d{2} times\)"), suppress=False):
365 ip.run_cell("r3o2()")
364 ip.run_cell("r3o2()")
366
365
367
366
368 #----------------------------------------------------------------------------
367 #----------------------------------------------------------------------------
369
368
370 # module testing (minimal)
369 # module testing (minimal)
371 def test_handlers():
370 def test_handlers():
372 def spam(c, d_e):
371 def spam(c, d_e):
373 (d, e) = d_e
372 (d, e) = d_e
374 x = c + d
373 x = c + d
375 y = c * d
374 y = c * d
376 foo(x, y)
375 foo(x, y)
377
376
378 def foo(a, b, bar=1):
377 def foo(a, b, bar=1):
379 eggs(a, b + bar)
378 eggs(a, b + bar)
380
379
381 def eggs(f, g, z=globals()):
380 def eggs(f, g, z=globals()):
382 h = f + g
381 h = f + g
383 i = f - g
382 i = f - g
384 return h / i
383 return h / i
385
384
386 buff = io.StringIO()
385 buff = io.StringIO()
387
386
388 buff.write('')
387 buff.write('')
389 buff.write('*** Before ***')
388 buff.write('*** Before ***')
390 try:
389 try:
391 buff.write(spam(1, (2, 3)))
390 buff.write(spam(1, (2, 3)))
392 except:
391 except:
393 traceback.print_exc(file=buff)
392 traceback.print_exc(file=buff)
394
393
395 handler = ColorTB(ostream=buff)
394 handler = ColorTB(ostream=buff)
396 buff.write('*** ColorTB ***')
395 buff.write('*** ColorTB ***')
397 try:
396 try:
398 buff.write(spam(1, (2, 3)))
397 buff.write(spam(1, (2, 3)))
399 except:
398 except:
400 handler(*sys.exc_info())
399 handler(*sys.exc_info())
401 buff.write('')
400 buff.write('')
402
401
403 handler = VerboseTB(ostream=buff)
402 handler = VerboseTB(ostream=buff)
404 buff.write('*** VerboseTB ***')
403 buff.write('*** VerboseTB ***')
405 try:
404 try:
406 buff.write(spam(1, (2, 3)))
405 buff.write(spam(1, (2, 3)))
407 except:
406 except:
408 handler(*sys.exc_info())
407 handler(*sys.exc_info())
409 buff.write('')
408 buff.write('')
@@ -1,258 +1,258 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tools for handling LaTeX."""
2 """Tools for handling LaTeX."""
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 from io import BytesIO, open
7 from io import BytesIO, open
8 import os
8 import os
9 import tempfile
9 import tempfile
10 import shutil
10 import shutil
11 import subprocess
11 import subprocess
12 from base64 import encodebytes
12 from base64 import encodebytes
13 import textwrap
13 import textwrap
14
14
15 from pathlib import Path, PurePath
15 from pathlib import Path
16
16
17 from IPython.utils.process import find_cmd, FindCmdError
17 from IPython.utils.process import find_cmd, FindCmdError
18 from traitlets.config import get_config
18 from traitlets.config import get_config
19 from traitlets.config.configurable import SingletonConfigurable
19 from traitlets.config.configurable import SingletonConfigurable
20 from traitlets import List, Bool, Unicode
20 from traitlets import List, Bool, Unicode
21 from IPython.utils.py3compat import cast_unicode
21 from IPython.utils.py3compat import cast_unicode
22
22
23
23
24 class LaTeXTool(SingletonConfigurable):
24 class LaTeXTool(SingletonConfigurable):
25 """An object to store configuration of the LaTeX tool."""
25 """An object to store configuration of the LaTeX tool."""
26 def _config_default(self):
26 def _config_default(self):
27 return get_config()
27 return get_config()
28
28
29 backends = List(
29 backends = List(
30 Unicode(), ["matplotlib", "dvipng"],
30 Unicode(), ["matplotlib", "dvipng"],
31 help="Preferred backend to draw LaTeX math equations. "
31 help="Preferred backend to draw LaTeX math equations. "
32 "Backends in the list are checked one by one and the first "
32 "Backends in the list are checked one by one and the first "
33 "usable one is used. Note that `matplotlib` backend "
33 "usable one is used. Note that `matplotlib` backend "
34 "is usable only for inline style equations. To draw "
34 "is usable only for inline style equations. To draw "
35 "display style equations, `dvipng` backend must be specified. ",
35 "display style equations, `dvipng` backend must be specified. ",
36 # It is a List instead of Enum, to make configuration more
36 # It is a List instead of Enum, to make configuration more
37 # flexible. For example, to use matplotlib mainly but dvipng
37 # flexible. For example, to use matplotlib mainly but dvipng
38 # for display style, the default ["matplotlib", "dvipng"] can
38 # for display style, the default ["matplotlib", "dvipng"] can
39 # be used. To NOT use dvipng so that other repr such as
39 # be used. To NOT use dvipng so that other repr such as
40 # unicode pretty printing is used, you can use ["matplotlib"].
40 # unicode pretty printing is used, you can use ["matplotlib"].
41 ).tag(config=True)
41 ).tag(config=True)
42
42
43 use_breqn = Bool(
43 use_breqn = Bool(
44 True,
44 True,
45 help="Use breqn.sty to automatically break long equations. "
45 help="Use breqn.sty to automatically break long equations. "
46 "This configuration takes effect only for dvipng backend.",
46 "This configuration takes effect only for dvipng backend.",
47 ).tag(config=True)
47 ).tag(config=True)
48
48
49 packages = List(
49 packages = List(
50 ['amsmath', 'amsthm', 'amssymb', 'bm'],
50 ['amsmath', 'amsthm', 'amssymb', 'bm'],
51 help="A list of packages to use for dvipng backend. "
51 help="A list of packages to use for dvipng backend. "
52 "'breqn' will be automatically appended when use_breqn=True.",
52 "'breqn' will be automatically appended when use_breqn=True.",
53 ).tag(config=True)
53 ).tag(config=True)
54
54
55 preamble = Unicode(
55 preamble = Unicode(
56 help="Additional preamble to use when generating LaTeX source "
56 help="Additional preamble to use when generating LaTeX source "
57 "for dvipng backend.",
57 "for dvipng backend.",
58 ).tag(config=True)
58 ).tag(config=True)
59
59
60
60
61 def latex_to_png(s, encode=False, backend=None, wrap=False, color='Black',
61 def latex_to_png(s, encode=False, backend=None, wrap=False, color='Black',
62 scale=1.0):
62 scale=1.0):
63 """Render a LaTeX string to PNG.
63 """Render a LaTeX string to PNG.
64
64
65 Parameters
65 Parameters
66 ----------
66 ----------
67 s : str
67 s : str
68 The raw string containing valid inline LaTeX.
68 The raw string containing valid inline LaTeX.
69 encode : bool, optional
69 encode : bool, optional
70 Should the PNG data base64 encoded to make it JSON'able.
70 Should the PNG data base64 encoded to make it JSON'able.
71 backend : {matplotlib, dvipng}
71 backend : {matplotlib, dvipng}
72 Backend for producing PNG data.
72 Backend for producing PNG data.
73 wrap : bool
73 wrap : bool
74 If true, Automatically wrap `s` as a LaTeX equation.
74 If true, Automatically wrap `s` as a LaTeX equation.
75 color : string
75 color : string
76 Foreground color name among dvipsnames, e.g. 'Maroon' or on hex RGB
76 Foreground color name among dvipsnames, e.g. 'Maroon' or on hex RGB
77 format, e.g. '#AA20FA'.
77 format, e.g. '#AA20FA'.
78 scale : float
78 scale : float
79 Scale factor for the resulting PNG.
79 Scale factor for the resulting PNG.
80 None is returned when the backend cannot be used.
80 None is returned when the backend cannot be used.
81
81
82 """
82 """
83 s = cast_unicode(s)
83 s = cast_unicode(s)
84 allowed_backends = LaTeXTool.instance().backends
84 allowed_backends = LaTeXTool.instance().backends
85 if backend is None:
85 if backend is None:
86 backend = allowed_backends[0]
86 backend = allowed_backends[0]
87 if backend not in allowed_backends:
87 if backend not in allowed_backends:
88 return None
88 return None
89 if backend == 'matplotlib':
89 if backend == 'matplotlib':
90 f = latex_to_png_mpl
90 f = latex_to_png_mpl
91 elif backend == 'dvipng':
91 elif backend == 'dvipng':
92 f = latex_to_png_dvipng
92 f = latex_to_png_dvipng
93 if color.startswith('#'):
93 if color.startswith('#'):
94 # Convert hex RGB color to LaTeX RGB color.
94 # Convert hex RGB color to LaTeX RGB color.
95 if len(color) == 7:
95 if len(color) == 7:
96 try:
96 try:
97 color = "RGB {}".format(" ".join([str(int(x, 16)) for x in
97 color = "RGB {}".format(" ".join([str(int(x, 16)) for x in
98 textwrap.wrap(color[1:], 2)]))
98 textwrap.wrap(color[1:], 2)]))
99 except ValueError as e:
99 except ValueError as e:
100 raise ValueError('Invalid color specification {}.'.format(color)) from e
100 raise ValueError('Invalid color specification {}.'.format(color)) from e
101 else:
101 else:
102 raise ValueError('Invalid color specification {}.'.format(color))
102 raise ValueError('Invalid color specification {}.'.format(color))
103 else:
103 else:
104 raise ValueError('No such backend {0}'.format(backend))
104 raise ValueError('No such backend {0}'.format(backend))
105 bin_data = f(s, wrap, color, scale)
105 bin_data = f(s, wrap, color, scale)
106 if encode and bin_data:
106 if encode and bin_data:
107 bin_data = encodebytes(bin_data)
107 bin_data = encodebytes(bin_data)
108 return bin_data
108 return bin_data
109
109
110
110
111 def latex_to_png_mpl(s, wrap, color='Black', scale=1.0):
111 def latex_to_png_mpl(s, wrap, color='Black', scale=1.0):
112 try:
112 try:
113 from matplotlib import figure, font_manager, mathtext
113 from matplotlib import figure, font_manager, mathtext
114 from matplotlib.backends import backend_agg
114 from matplotlib.backends import backend_agg
115 from pyparsing import ParseFatalException
115 from pyparsing import ParseFatalException
116 except ImportError:
116 except ImportError:
117 return None
117 return None
118
118
119 # mpl mathtext doesn't support display math, force inline
119 # mpl mathtext doesn't support display math, force inline
120 s = s.replace('$$', '$')
120 s = s.replace('$$', '$')
121 if wrap:
121 if wrap:
122 s = u'${0}$'.format(s)
122 s = u'${0}$'.format(s)
123
123
124 try:
124 try:
125 prop = font_manager.FontProperties(size=12)
125 prop = font_manager.FontProperties(size=12)
126 dpi = 120 * scale
126 dpi = 120 * scale
127 buffer = BytesIO()
127 buffer = BytesIO()
128
128
129 # Adapted from mathtext.math_to_image
129 # Adapted from mathtext.math_to_image
130 parser = mathtext.MathTextParser("path")
130 parser = mathtext.MathTextParser("path")
131 width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
131 width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
132 fig = figure.Figure(figsize=(width / 72, height / 72))
132 fig = figure.Figure(figsize=(width / 72, height / 72))
133 fig.text(0, depth / height, s, fontproperties=prop, color=color)
133 fig.text(0, depth / height, s, fontproperties=prop, color=color)
134 backend_agg.FigureCanvasAgg(fig)
134 backend_agg.FigureCanvasAgg(fig)
135 fig.savefig(buffer, dpi=dpi, format="png", transparent=True)
135 fig.savefig(buffer, dpi=dpi, format="png", transparent=True)
136 return buffer.getvalue()
136 return buffer.getvalue()
137 except (ValueError, RuntimeError, ParseFatalException):
137 except (ValueError, RuntimeError, ParseFatalException):
138 return None
138 return None
139
139
140
140
141 def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0):
141 def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0):
142 try:
142 try:
143 find_cmd('latex')
143 find_cmd('latex')
144 find_cmd('dvipng')
144 find_cmd('dvipng')
145 except FindCmdError:
145 except FindCmdError:
146 return None
146 return None
147
147
148 startupinfo = None
148 startupinfo = None
149 if os.name == "nt":
149 if os.name == "nt":
150 # prevent popup-windows
150 # prevent popup-windows
151 startupinfo = subprocess.STARTUPINFO()
151 startupinfo = subprocess.STARTUPINFO()
152 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
152 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
153
153
154 try:
154 try:
155 workdir = Path(tempfile.mkdtemp())
155 workdir = Path(tempfile.mkdtemp())
156 tmpfile = "tmp.tex"
156 tmpfile = "tmp.tex"
157 dvifile = "tmp.dvi"
157 dvifile = "tmp.dvi"
158 outfile = "tmp.png"
158 outfile = "tmp.png"
159
159
160 with workdir.joinpath(tmpfile).open("w", encoding="utf8") as f:
160 with workdir.joinpath(tmpfile).open("w", encoding="utf8") as f:
161 f.writelines(genelatex(s, wrap))
161 f.writelines(genelatex(s, wrap))
162
162
163 with open(os.devnull, 'wb') as devnull:
163 with open(os.devnull, 'wb') as devnull:
164 subprocess.check_call(
164 subprocess.check_call(
165 ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile],
165 ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile],
166 cwd=workdir,
166 cwd=workdir,
167 stdout=devnull,
167 stdout=devnull,
168 stderr=devnull,
168 stderr=devnull,
169 startupinfo=startupinfo,
169 startupinfo=startupinfo,
170 )
170 )
171
171
172 resolution = round(150*scale)
172 resolution = round(150*scale)
173 subprocess.check_call(
173 subprocess.check_call(
174 [
174 [
175 "dvipng",
175 "dvipng",
176 "-T",
176 "-T",
177 "tight",
177 "tight",
178 "-D",
178 "-D",
179 str(resolution),
179 str(resolution),
180 "-z",
180 "-z",
181 "9",
181 "9",
182 "-bg",
182 "-bg",
183 "Transparent",
183 "Transparent",
184 "-o",
184 "-o",
185 outfile,
185 outfile,
186 dvifile,
186 dvifile,
187 "-fg",
187 "-fg",
188 color,
188 color,
189 ],
189 ],
190 cwd=workdir,
190 cwd=workdir,
191 stdout=devnull,
191 stdout=devnull,
192 stderr=devnull,
192 stderr=devnull,
193 startupinfo=startupinfo,
193 startupinfo=startupinfo,
194 )
194 )
195
195
196 with workdir.joinpath(outfile).open("rb") as f:
196 with workdir.joinpath(outfile).open("rb") as f:
197 return f.read()
197 return f.read()
198 except subprocess.CalledProcessError:
198 except subprocess.CalledProcessError:
199 return None
199 return None
200 finally:
200 finally:
201 shutil.rmtree(workdir)
201 shutil.rmtree(workdir)
202
202
203
203
204 def kpsewhich(filename):
204 def kpsewhich(filename):
205 """Invoke kpsewhich command with an argument `filename`."""
205 """Invoke kpsewhich command with an argument `filename`."""
206 try:
206 try:
207 find_cmd("kpsewhich")
207 find_cmd("kpsewhich")
208 proc = subprocess.Popen(
208 proc = subprocess.Popen(
209 ["kpsewhich", filename],
209 ["kpsewhich", filename],
210 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
210 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
211 (stdout, stderr) = proc.communicate()
211 (stdout, stderr) = proc.communicate()
212 return stdout.strip().decode('utf8', 'replace')
212 return stdout.strip().decode('utf8', 'replace')
213 except FindCmdError:
213 except FindCmdError:
214 pass
214 pass
215
215
216
216
217 def genelatex(body, wrap):
217 def genelatex(body, wrap):
218 """Generate LaTeX document for dvipng backend."""
218 """Generate LaTeX document for dvipng backend."""
219 lt = LaTeXTool.instance()
219 lt = LaTeXTool.instance()
220 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
220 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
221 yield r'\documentclass{article}'
221 yield r'\documentclass{article}'
222 packages = lt.packages
222 packages = lt.packages
223 if breqn:
223 if breqn:
224 packages = packages + ['breqn']
224 packages = packages + ['breqn']
225 for pack in packages:
225 for pack in packages:
226 yield r'\usepackage{{{0}}}'.format(pack)
226 yield r'\usepackage{{{0}}}'.format(pack)
227 yield r'\pagestyle{empty}'
227 yield r'\pagestyle{empty}'
228 if lt.preamble:
228 if lt.preamble:
229 yield lt.preamble
229 yield lt.preamble
230 yield r'\begin{document}'
230 yield r'\begin{document}'
231 if breqn:
231 if breqn:
232 yield r'\begin{dmath*}'
232 yield r'\begin{dmath*}'
233 yield body
233 yield body
234 yield r'\end{dmath*}'
234 yield r'\end{dmath*}'
235 elif wrap:
235 elif wrap:
236 yield u'$${0}$$'.format(body)
236 yield u'$${0}$$'.format(body)
237 else:
237 else:
238 yield body
238 yield body
239 yield u'\\end{document}'
239 yield u'\\end{document}'
240
240
241
241
242 _data_uri_template_png = u"""<img src="data:image/png;base64,%s" alt=%s />"""
242 _data_uri_template_png = u"""<img src="data:image/png;base64,%s" alt=%s />"""
243
243
244 def latex_to_html(s, alt='image'):
244 def latex_to_html(s, alt='image'):
245 """Render LaTeX to HTML with embedded PNG data using data URIs.
245 """Render LaTeX to HTML with embedded PNG data using data URIs.
246
246
247 Parameters
247 Parameters
248 ----------
248 ----------
249 s : str
249 s : str
250 The raw string containing valid inline LateX.
250 The raw string containing valid inline LateX.
251 alt : str
251 alt : str
252 The alt text to use for the HTML.
252 The alt text to use for the HTML.
253 """
253 """
254 base64_data = latex_to_png(s, encode=True).decode('ascii')
254 base64_data = latex_to_png(s, encode=True).decode('ascii')
255 if base64_data:
255 if base64_data:
256 return _data_uri_template_png % (base64_data, alt)
256 return _data_uri_template_png % (base64_data, alt)
257
257
258
258
@@ -1,126 +1,125 b''
1 """Find files and directories which IPython uses.
1 """Find files and directories which IPython uses.
2 """
2 """
3 import os.path
3 import os.path
4 import shutil
5 import tempfile
4 import tempfile
6 from warnings import warn
5 from warnings import warn
7
6
8 import IPython
7 import IPython
9 from IPython.utils.importstring import import_item
8 from IPython.utils.importstring import import_item
10 from IPython.utils.path import (
9 from IPython.utils.path import (
11 get_home_dir,
10 get_home_dir,
12 get_xdg_dir,
11 get_xdg_dir,
13 get_xdg_cache_dir,
12 get_xdg_cache_dir,
14 compress_user,
13 compress_user,
15 _writable_dir,
14 _writable_dir,
16 ensure_dir_exists,
15 ensure_dir_exists,
17 )
16 )
18
17
19
18
20 def get_ipython_dir() -> str:
19 def get_ipython_dir() -> str:
21 """Get the IPython directory for this platform and user.
20 """Get the IPython directory for this platform and user.
22
21
23 This uses the logic in `get_home_dir` to find the home directory
22 This uses the logic in `get_home_dir` to find the home directory
24 and then adds .ipython to the end of the path.
23 and then adds .ipython to the end of the path.
25 """
24 """
26
25
27 env = os.environ
26 env = os.environ
28 pjoin = os.path.join
27 pjoin = os.path.join
29
28
30
29
31 ipdir_def = '.ipython'
30 ipdir_def = '.ipython'
32
31
33 home_dir = get_home_dir()
32 home_dir = get_home_dir()
34 xdg_dir = get_xdg_dir()
33 xdg_dir = get_xdg_dir()
35
34
36 if 'IPYTHON_DIR' in env:
35 if 'IPYTHON_DIR' in env:
37 warn('The environment variable IPYTHON_DIR is deprecated since IPython 3.0. '
36 warn('The environment variable IPYTHON_DIR is deprecated since IPython 3.0. '
38 'Please use IPYTHONDIR instead.', DeprecationWarning)
37 'Please use IPYTHONDIR instead.', DeprecationWarning)
39 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
38 ipdir = env.get('IPYTHONDIR', env.get('IPYTHON_DIR', None))
40 if ipdir is None:
39 if ipdir is None:
41 # not set explicitly, use ~/.ipython
40 # not set explicitly, use ~/.ipython
42 ipdir = pjoin(home_dir, ipdir_def)
41 ipdir = pjoin(home_dir, ipdir_def)
43 if xdg_dir:
42 if xdg_dir:
44 # Several IPython versions (up to 1.x) defaulted to .config/ipython
43 # Several IPython versions (up to 1.x) defaulted to .config/ipython
45 # on Linux. We have decided to go back to using .ipython everywhere
44 # on Linux. We have decided to go back to using .ipython everywhere
46 xdg_ipdir = pjoin(xdg_dir, 'ipython')
45 xdg_ipdir = pjoin(xdg_dir, 'ipython')
47
46
48 if _writable_dir(xdg_ipdir):
47 if _writable_dir(xdg_ipdir):
49 cu = compress_user
48 cu = compress_user
50 if os.path.exists(ipdir):
49 if os.path.exists(ipdir):
51 warn(('Ignoring {0} in favour of {1}. Remove {0} to '
50 warn(('Ignoring {0} in favour of {1}. Remove {0} to '
52 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
51 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
53 elif os.path.islink(xdg_ipdir):
52 elif os.path.islink(xdg_ipdir):
54 warn(('{0} is deprecated. Move link to {1} to '
53 warn(('{0} is deprecated. Move link to {1} to '
55 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
54 'get rid of this message').format(cu(xdg_ipdir), cu(ipdir)))
56 else:
55 else:
57 ipdir = xdg_ipdir
56 ipdir = xdg_ipdir
58
57
59 ipdir = os.path.normpath(os.path.expanduser(ipdir))
58 ipdir = os.path.normpath(os.path.expanduser(ipdir))
60
59
61 if os.path.exists(ipdir) and not _writable_dir(ipdir):
60 if os.path.exists(ipdir) and not _writable_dir(ipdir):
62 # ipdir exists, but is not writable
61 # ipdir exists, but is not writable
63 warn("IPython dir '{0}' is not a writable location,"
62 warn("IPython dir '{0}' is not a writable location,"
64 " using a temp directory.".format(ipdir))
63 " using a temp directory.".format(ipdir))
65 ipdir = tempfile.mkdtemp()
64 ipdir = tempfile.mkdtemp()
66 elif not os.path.exists(ipdir):
65 elif not os.path.exists(ipdir):
67 parent = os.path.dirname(ipdir)
66 parent = os.path.dirname(ipdir)
68 if not _writable_dir(parent):
67 if not _writable_dir(parent):
69 # ipdir does not exist and parent isn't writable
68 # ipdir does not exist and parent isn't writable
70 warn("IPython parent '{0}' is not a writable location,"
69 warn("IPython parent '{0}' is not a writable location,"
71 " using a temp directory.".format(parent))
70 " using a temp directory.".format(parent))
72 ipdir = tempfile.mkdtemp()
71 ipdir = tempfile.mkdtemp()
73 else:
72 else:
74 os.makedirs(ipdir, exist_ok=True)
73 os.makedirs(ipdir, exist_ok=True)
75 assert isinstance(ipdir, str), "all path manipulation should be str(unicode), but are not."
74 assert isinstance(ipdir, str), "all path manipulation should be str(unicode), but are not."
76 return ipdir
75 return ipdir
77
76
78
77
79 def get_ipython_cache_dir() -> str:
78 def get_ipython_cache_dir() -> str:
80 """Get the cache directory it is created if it does not exist."""
79 """Get the cache directory it is created if it does not exist."""
81 xdgdir = get_xdg_cache_dir()
80 xdgdir = get_xdg_cache_dir()
82 if xdgdir is None:
81 if xdgdir is None:
83 return get_ipython_dir()
82 return get_ipython_dir()
84 ipdir = os.path.join(xdgdir, "ipython")
83 ipdir = os.path.join(xdgdir, "ipython")
85 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
84 if not os.path.exists(ipdir) and _writable_dir(xdgdir):
86 ensure_dir_exists(ipdir)
85 ensure_dir_exists(ipdir)
87 elif not _writable_dir(xdgdir):
86 elif not _writable_dir(xdgdir):
88 return get_ipython_dir()
87 return get_ipython_dir()
89
88
90 return ipdir
89 return ipdir
91
90
92
91
93 def get_ipython_package_dir() -> str:
92 def get_ipython_package_dir() -> str:
94 """Get the base directory where IPython itself is installed."""
93 """Get the base directory where IPython itself is installed."""
95 ipdir = os.path.dirname(IPython.__file__)
94 ipdir = os.path.dirname(IPython.__file__)
96 assert isinstance(ipdir, str)
95 assert isinstance(ipdir, str)
97 return ipdir
96 return ipdir
98
97
99
98
100 def get_ipython_module_path(module_str):
99 def get_ipython_module_path(module_str):
101 """Find the path to an IPython module in this version of IPython.
100 """Find the path to an IPython module in this version of IPython.
102
101
103 This will always find the version of the module that is in this importable
102 This will always find the version of the module that is in this importable
104 IPython package. This will always return the path to the ``.py``
103 IPython package. This will always return the path to the ``.py``
105 version of the module.
104 version of the module.
106 """
105 """
107 if module_str == 'IPython':
106 if module_str == 'IPython':
108 return os.path.join(get_ipython_package_dir(), '__init__.py')
107 return os.path.join(get_ipython_package_dir(), '__init__.py')
109 mod = import_item(module_str)
108 mod = import_item(module_str)
110 the_path = mod.__file__.replace('.pyc', '.py')
109 the_path = mod.__file__.replace('.pyc', '.py')
111 the_path = the_path.replace('.pyo', '.py')
110 the_path = the_path.replace('.pyo', '.py')
112 return the_path
111 return the_path
113
112
114
113
115 def locate_profile(profile='default'):
114 def locate_profile(profile='default'):
116 """Find the path to the folder associated with a given profile.
115 """Find the path to the folder associated with a given profile.
117
116
118 I.e. find $IPYTHONDIR/profile_whatever.
117 I.e. find $IPYTHONDIR/profile_whatever.
119 """
118 """
120 from IPython.core.profiledir import ProfileDir, ProfileDirError
119 from IPython.core.profiledir import ProfileDir, ProfileDirError
121 try:
120 try:
122 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
121 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
123 except ProfileDirError as e:
122 except ProfileDirError as e:
124 # IOError makes more sense when people are expecting a path
123 # IOError makes more sense when people are expecting a path
125 raise IOError("Couldn't find profile %r" % profile) from e
124 raise IOError("Couldn't find profile %r" % profile) from e
126 return pd.location
125 return pd.location
@@ -1,202 +1,201 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Decorators for labeling test objects.
2 """Decorators for labeling test objects.
3
3
4 Decorators that merely return a modified version of the original function
4 Decorators that merely return a modified version of the original function
5 object are straightforward. Decorators that return a new function object need
5 object are straightforward. Decorators that return a new function object need
6 to use nose.tools.make_decorator(original_function)(decorator) in returning the
6 to use nose.tools.make_decorator(original_function)(decorator) in returning the
7 decorator, in order to preserve metadata such as function name, setup and
7 decorator, in order to preserve metadata such as function name, setup and
8 teardown functions and so on - see nose.tools for more information.
8 teardown functions and so on - see nose.tools for more information.
9
9
10 This module provides a set of useful decorators meant to be ready to use in
10 This module provides a set of useful decorators meant to be ready to use in
11 your own tests. See the bottom of the file for the ready-made ones, and if you
11 your own tests. See the bottom of the file for the ready-made ones, and if you
12 find yourself writing a new one that may be of generic use, add it here.
12 find yourself writing a new one that may be of generic use, add it here.
13
13
14 Included decorators:
14 Included decorators:
15
15
16
16
17 Lightweight testing that remains unittest-compatible.
17 Lightweight testing that remains unittest-compatible.
18
18
19 - An @as_unittest decorator can be used to tag any normal parameter-less
19 - An @as_unittest decorator can be used to tag any normal parameter-less
20 function as a unittest TestCase. Then, both nose and normal unittest will
20 function as a unittest TestCase. Then, both nose and normal unittest will
21 recognize it as such. This will make it easier to migrate away from Nose if
21 recognize it as such. This will make it easier to migrate away from Nose if
22 we ever need/want to while maintaining very lightweight tests.
22 we ever need/want to while maintaining very lightweight tests.
23
23
24 NOTE: This file contains IPython-specific decorators. Using the machinery in
24 NOTE: This file contains IPython-specific decorators. Using the machinery in
25 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
25 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
26 available, OR use equivalent code in IPython.external._decorators, which
26 available, OR use equivalent code in IPython.external._decorators, which
27 we've copied verbatim from numpy.
27 we've copied verbatim from numpy.
28
28
29 """
29 """
30
30
31 # Copyright (c) IPython Development Team.
31 # Copyright (c) IPython Development Team.
32 # Distributed under the terms of the Modified BSD License.
32 # Distributed under the terms of the Modified BSD License.
33
33
34 import os
34 import os
35 import shutil
35 import shutil
36 import sys
36 import sys
37 import tempfile
37 import tempfile
38 import unittest
38 import unittest
39 import warnings
40 from importlib import import_module
39 from importlib import import_module
41
40
42 from decorator import decorator
41 from decorator import decorator
43
42
44 # Expose the unittest-driven decorators
43 # Expose the unittest-driven decorators
45 from .ipunittest import ipdoctest, ipdocstring
44 from .ipunittest import ipdoctest, ipdocstring
46
45
47 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
48 # Classes and functions
47 # Classes and functions
49 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
50
49
51 # Simple example of the basic idea
50 # Simple example of the basic idea
52 def as_unittest(func):
51 def as_unittest(func):
53 """Decorator to make a simple function into a normal test via unittest."""
52 """Decorator to make a simple function into a normal test via unittest."""
54 class Tester(unittest.TestCase):
53 class Tester(unittest.TestCase):
55 def test(self):
54 def test(self):
56 func()
55 func()
57
56
58 Tester.__name__ = func.__name__
57 Tester.__name__ = func.__name__
59
58
60 return Tester
59 return Tester
61
60
62 # Utility functions
61 # Utility functions
63
62
64
63
65 def skipif(skip_condition, msg=None):
64 def skipif(skip_condition, msg=None):
66 """Make function raise SkipTest exception if skip_condition is true
65 """Make function raise SkipTest exception if skip_condition is true
67
66
68 Parameters
67 Parameters
69 ----------
68 ----------
70
69
71 skip_condition : bool or callable
70 skip_condition : bool or callable
72 Flag to determine whether to skip test. If the condition is a
71 Flag to determine whether to skip test. If the condition is a
73 callable, it is used at runtime to dynamically make the decision. This
72 callable, it is used at runtime to dynamically make the decision. This
74 is useful for tests that may require costly imports, to delay the cost
73 is useful for tests that may require costly imports, to delay the cost
75 until the test suite is actually executed.
74 until the test suite is actually executed.
76 msg : string
75 msg : string
77 Message to give on raising a SkipTest exception.
76 Message to give on raising a SkipTest exception.
78
77
79 Returns
78 Returns
80 -------
79 -------
81 decorator : function
80 decorator : function
82 Decorator, which, when applied to a function, causes SkipTest
81 Decorator, which, when applied to a function, causes SkipTest
83 to be raised when the skip_condition was True, and the function
82 to be raised when the skip_condition was True, and the function
84 to be called normally otherwise.
83 to be called normally otherwise.
85 """
84 """
86 if msg is None:
85 if msg is None:
87 msg = "Test skipped due to test condition."
86 msg = "Test skipped due to test condition."
88
87
89 import pytest
88 import pytest
90
89
91 assert isinstance(skip_condition, bool)
90 assert isinstance(skip_condition, bool)
92 return pytest.mark.skipif(skip_condition, reason=msg)
91 return pytest.mark.skipif(skip_condition, reason=msg)
93
92
94
93
95 # A version with the condition set to true, common case just to attach a message
94 # A version with the condition set to true, common case just to attach a message
96 # to a skip decorator
95 # to a skip decorator
97 def skip(msg=None):
96 def skip(msg=None):
98 """Decorator factory - mark a test function for skipping from test suite.
97 """Decorator factory - mark a test function for skipping from test suite.
99
98
100 Parameters
99 Parameters
101 ----------
100 ----------
102 msg : string
101 msg : string
103 Optional message to be added.
102 Optional message to be added.
104
103
105 Returns
104 Returns
106 -------
105 -------
107 decorator : function
106 decorator : function
108 Decorator, which, when applied to a function, causes SkipTest
107 Decorator, which, when applied to a function, causes SkipTest
109 to be raised, with the optional message added.
108 to be raised, with the optional message added.
110 """
109 """
111 if msg and not isinstance(msg, str):
110 if msg and not isinstance(msg, str):
112 raise ValueError('invalid object passed to `@skip` decorator, did you '
111 raise ValueError('invalid object passed to `@skip` decorator, did you '
113 'meant `@skip()` with brackets ?')
112 'meant `@skip()` with brackets ?')
114 return skipif(True, msg)
113 return skipif(True, msg)
115
114
116
115
117 def onlyif(condition, msg):
116 def onlyif(condition, msg):
118 """The reverse from skipif, see skipif for details."""
117 """The reverse from skipif, see skipif for details."""
119
118
120 return skipif(not condition, msg)
119 return skipif(not condition, msg)
121
120
122 #-----------------------------------------------------------------------------
121 #-----------------------------------------------------------------------------
123 # Utility functions for decorators
122 # Utility functions for decorators
124 def module_not_available(module):
123 def module_not_available(module):
125 """Can module be imported? Returns true if module does NOT import.
124 """Can module be imported? Returns true if module does NOT import.
126
125
127 This is used to make a decorator to skip tests that require module to be
126 This is used to make a decorator to skip tests that require module to be
128 available, but delay the 'import numpy' to test execution time.
127 available, but delay the 'import numpy' to test execution time.
129 """
128 """
130 try:
129 try:
131 mod = import_module(module)
130 mod = import_module(module)
132 mod_not_avail = False
131 mod_not_avail = False
133 except ImportError:
132 except ImportError:
134 mod_not_avail = True
133 mod_not_avail = True
135
134
136 return mod_not_avail
135 return mod_not_avail
137
136
138
137
139 #-----------------------------------------------------------------------------
138 #-----------------------------------------------------------------------------
140 # Decorators for public use
139 # Decorators for public use
141
140
142 # Decorators to skip certain tests on specific platforms.
141 # Decorators to skip certain tests on specific platforms.
143 skip_win32 = skipif(sys.platform == 'win32',
142 skip_win32 = skipif(sys.platform == 'win32',
144 "This test does not run under Windows")
143 "This test does not run under Windows")
145 skip_linux = skipif(sys.platform.startswith('linux'),
144 skip_linux = skipif(sys.platform.startswith('linux'),
146 "This test does not run under Linux")
145 "This test does not run under Linux")
147 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
146 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
148
147
149
148
150 # Decorators to skip tests if not on specific platforms.
149 # Decorators to skip tests if not on specific platforms.
151 skip_if_not_win32 = skipif(sys.platform != 'win32',
150 skip_if_not_win32 = skipif(sys.platform != 'win32',
152 "This test only runs under Windows")
151 "This test only runs under Windows")
153 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
152 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
154 "This test only runs under Linux")
153 "This test only runs under Linux")
155
154
156 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
155 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
157 os.environ.get('DISPLAY', '') == '')
156 os.environ.get('DISPLAY', '') == '')
158 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
157 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
159
158
160 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
159 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
161
160
162 # Other skip decorators
161 # Other skip decorators
163
162
164 # generic skip without module
163 # generic skip without module
165 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
164 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
166
165
167 skipif_not_numpy = skip_without('numpy')
166 skipif_not_numpy = skip_without('numpy')
168
167
169 skipif_not_matplotlib = skip_without('matplotlib')
168 skipif_not_matplotlib = skip_without('matplotlib')
170
169
171 # A null 'decorator', useful to make more readable code that needs to pick
170 # A null 'decorator', useful to make more readable code that needs to pick
172 # between different decorators based on OS or other conditions
171 # between different decorators based on OS or other conditions
173 null_deco = lambda f: f
172 null_deco = lambda f: f
174
173
175 # Some tests only run where we can use unicode paths. Note that we can't just
174 # Some tests only run where we can use unicode paths. Note that we can't just
176 # check os.path.supports_unicode_filenames, which is always False on Linux.
175 # check os.path.supports_unicode_filenames, which is always False on Linux.
177 try:
176 try:
178 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
177 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
179 except UnicodeEncodeError:
178 except UnicodeEncodeError:
180 unicode_paths = False
179 unicode_paths = False
181 else:
180 else:
182 unicode_paths = True
181 unicode_paths = True
183 f.close()
182 f.close()
184
183
185 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
184 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
186 "where we can use unicode in filenames."))
185 "where we can use unicode in filenames."))
187
186
188
187
189 def onlyif_cmds_exist(*commands):
188 def onlyif_cmds_exist(*commands):
190 """
189 """
191 Decorator to skip test when at least one of `commands` is not found.
190 Decorator to skip test when at least one of `commands` is not found.
192 """
191 """
193 assert (
192 assert (
194 os.environ.get("IPTEST_WORKING_DIR", None) is None
193 os.environ.get("IPTEST_WORKING_DIR", None) is None
195 ), "iptest deprecated since IPython 8.0"
194 ), "iptest deprecated since IPython 8.0"
196 for cmd in commands:
195 for cmd in commands:
197 reason = f"This test runs only if command '{cmd}' is installed"
196 reason = f"This test runs only if command '{cmd}' is installed"
198 if not shutil.which(cmd):
197 if not shutil.which(cmd):
199 import pytest
198 import pytest
200
199
201 return pytest.mark.skip(reason=reason)
200 return pytest.mark.skip(reason=reason)
202 return null_deco
201 return null_deco
@@ -1,115 +1,114 b''
1 """Global IPython app to support test running.
1 """Global IPython app to support test running.
2
2
3 We must start our own ipython object and heavily muck with it so that all the
3 We must start our own ipython object and heavily muck with it so that all the
4 modifications IPython makes to system behavior don't send the doctest machinery
4 modifications IPython makes to system behavior don't send the doctest machinery
5 into a fit. This code should be considered a gross hack, but it gets the job
5 into a fit. This code should be considered a gross hack, but it gets the job
6 done.
6 done.
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 builtins as builtin_mod
12 import builtins as builtin_mod
13 import sys
13 import sys
14 import types
14 import types
15 import warnings
16
15
17 from pathlib import Path
16 from pathlib import Path
18
17
19 from . import tools
18 from . import tools
20
19
21 from IPython.core import page
20 from IPython.core import page
22 from IPython.utils import io
21 from IPython.utils import io
23 from IPython.terminal.interactiveshell import TerminalInteractiveShell
22 from IPython.terminal.interactiveshell import TerminalInteractiveShell
24
23
25
24
26 def get_ipython():
25 def get_ipython():
27 # This will get replaced by the real thing once we start IPython below
26 # This will get replaced by the real thing once we start IPython below
28 return start_ipython()
27 return start_ipython()
29
28
30
29
31 # A couple of methods to override those in the running IPython to interact
30 # A couple of methods to override those in the running IPython to interact
32 # better with doctest (doctest captures on raw stdout, so we need to direct
31 # better with doctest (doctest captures on raw stdout, so we need to direct
33 # various types of output there otherwise it will miss them).
32 # various types of output there otherwise it will miss them).
34
33
35 def xsys(self, cmd):
34 def xsys(self, cmd):
36 """Replace the default system call with a capturing one for doctest.
35 """Replace the default system call with a capturing one for doctest.
37 """
36 """
38 # We use getoutput, but we need to strip it because pexpect captures
37 # We use getoutput, but we need to strip it because pexpect captures
39 # the trailing newline differently from commands.getoutput
38 # the trailing newline differently from commands.getoutput
40 print(self.getoutput(cmd, split=False, depth=1).rstrip(), end='', file=sys.stdout)
39 print(self.getoutput(cmd, split=False, depth=1).rstrip(), end='', file=sys.stdout)
41 sys.stdout.flush()
40 sys.stdout.flush()
42
41
43
42
44 def _showtraceback(self, etype, evalue, stb):
43 def _showtraceback(self, etype, evalue, stb):
45 """Print the traceback purely on stdout for doctest to capture it.
44 """Print the traceback purely on stdout for doctest to capture it.
46 """
45 """
47 print(self.InteractiveTB.stb2text(stb), file=sys.stdout)
46 print(self.InteractiveTB.stb2text(stb), file=sys.stdout)
48
47
49
48
50 def start_ipython():
49 def start_ipython():
51 """Start a global IPython shell, which we need for IPython-specific syntax.
50 """Start a global IPython shell, which we need for IPython-specific syntax.
52 """
51 """
53 global get_ipython
52 global get_ipython
54
53
55 # This function should only ever run once!
54 # This function should only ever run once!
56 if hasattr(start_ipython, 'already_called'):
55 if hasattr(start_ipython, 'already_called'):
57 return
56 return
58 start_ipython.already_called = True
57 start_ipython.already_called = True
59
58
60 # Store certain global objects that IPython modifies
59 # Store certain global objects that IPython modifies
61 _displayhook = sys.displayhook
60 _displayhook = sys.displayhook
62 _excepthook = sys.excepthook
61 _excepthook = sys.excepthook
63 _main = sys.modules.get('__main__')
62 _main = sys.modules.get('__main__')
64
63
65 # Create custom argv and namespaces for our IPython to be test-friendly
64 # Create custom argv and namespaces for our IPython to be test-friendly
66 config = tools.default_config()
65 config = tools.default_config()
67 config.TerminalInteractiveShell.simple_prompt = True
66 config.TerminalInteractiveShell.simple_prompt = True
68
67
69 # Create and initialize our test-friendly IPython instance.
68 # Create and initialize our test-friendly IPython instance.
70 shell = TerminalInteractiveShell.instance(config=config,
69 shell = TerminalInteractiveShell.instance(config=config,
71 )
70 )
72
71
73 # A few more tweaks needed for playing nicely with doctests...
72 # A few more tweaks needed for playing nicely with doctests...
74
73
75 # remove history file
74 # remove history file
76 shell.tempfiles.append(Path(config.HistoryManager.hist_file))
75 shell.tempfiles.append(Path(config.HistoryManager.hist_file))
77
76
78 # These traps are normally only active for interactive use, set them
77 # These traps are normally only active for interactive use, set them
79 # permanently since we'll be mocking interactive sessions.
78 # permanently since we'll be mocking interactive sessions.
80 shell.builtin_trap.activate()
79 shell.builtin_trap.activate()
81
80
82 # Modify the IPython system call with one that uses getoutput, so that we
81 # Modify the IPython system call with one that uses getoutput, so that we
83 # can capture subcommands and print them to Python's stdout, otherwise the
82 # can capture subcommands and print them to Python's stdout, otherwise the
84 # doctest machinery would miss them.
83 # doctest machinery would miss them.
85 shell.system = types.MethodType(xsys, shell)
84 shell.system = types.MethodType(xsys, shell)
86
85
87 shell._showtraceback = types.MethodType(_showtraceback, shell)
86 shell._showtraceback = types.MethodType(_showtraceback, shell)
88
87
89 # IPython is ready, now clean up some global state...
88 # IPython is ready, now clean up some global state...
90
89
91 # Deactivate the various python system hooks added by ipython for
90 # Deactivate the various python system hooks added by ipython for
92 # interactive convenience so we don't confuse the doctest system
91 # interactive convenience so we don't confuse the doctest system
93 sys.modules['__main__'] = _main
92 sys.modules['__main__'] = _main
94 sys.displayhook = _displayhook
93 sys.displayhook = _displayhook
95 sys.excepthook = _excepthook
94 sys.excepthook = _excepthook
96
95
97 # So that ipython magics and aliases can be doctested (they work by making
96 # So that ipython magics and aliases can be doctested (they work by making
98 # a call into a global _ip object). Also make the top-level get_ipython
97 # a call into a global _ip object). Also make the top-level get_ipython
99 # now return this without recursively calling here again.
98 # now return this without recursively calling here again.
100 _ip = shell
99 _ip = shell
101 get_ipython = _ip.get_ipython
100 get_ipython = _ip.get_ipython
102 builtin_mod._ip = _ip
101 builtin_mod._ip = _ip
103 builtin_mod.ip = _ip
102 builtin_mod.ip = _ip
104 builtin_mod.get_ipython = get_ipython
103 builtin_mod.get_ipython = get_ipython
105
104
106 # Override paging, so we don't require user interaction during the tests.
105 # Override paging, so we don't require user interaction during the tests.
107 def nopage(strng, start=0, screen_lines=0, pager_cmd=None):
106 def nopage(strng, start=0, screen_lines=0, pager_cmd=None):
108 if isinstance(strng, dict):
107 if isinstance(strng, dict):
109 strng = strng.get('text/plain', '')
108 strng = strng.get('text/plain', '')
110 print(strng)
109 print(strng)
111
110
112 page.orig_page = page.pager_page
111 page.orig_page = page.pager_page
113 page.pager_page = nopage
112 page.pager_page = nopage
114
113
115 return _ip
114 return _ip
@@ -1,300 +1,299 b''
1 """Nose Plugin that supports IPython doctests.
1 """Nose Plugin that supports IPython doctests.
2
2
3 Limitations:
3 Limitations:
4
4
5 - When generating examples for use as doctests, make sure that you have
5 - When generating examples for use as doctests, make sure that you have
6 pretty-printing OFF. This can be done either by setting the
6 pretty-printing OFF. This can be done either by setting the
7 ``PlainTextFormatter.pprint`` option in your configuration file to False, or
7 ``PlainTextFormatter.pprint`` option in your configuration file to False, or
8 by interactively disabling it with %Pprint. This is required so that IPython
8 by interactively disabling it with %Pprint. This is required so that IPython
9 output matches that of normal Python, which is used by doctest for internal
9 output matches that of normal Python, which is used by doctest for internal
10 execution.
10 execution.
11
11
12 - Do not rely on specific prompt numbers for results (such as using
12 - Do not rely on specific prompt numbers for results (such as using
13 '_34==True', for example). For IPython tests run via an external process the
13 '_34==True', for example). For IPython tests run via an external process the
14 prompt numbers may be different, and IPython tests run as normal python code
14 prompt numbers may be different, and IPython tests run as normal python code
15 won't even have these special _NN variables set at all.
15 won't even have these special _NN variables set at all.
16 """
16 """
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Module imports
19 # Module imports
20
20
21 # From the standard library
21 # From the standard library
22 import doctest
22 import doctest
23 import logging
23 import logging
24 import os
25 import re
24 import re
26
25
27 from testpath import modified_env
26 from testpath import modified_env
28
27
29 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
30 # Module globals and other constants
29 # Module globals and other constants
31 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
32
31
33 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
34
33
35
34
36 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
37 # Classes and functions
36 # Classes and functions
38 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
39
38
40
39
41 class DocTestFinder(doctest.DocTestFinder):
40 class DocTestFinder(doctest.DocTestFinder):
42 def _get_test(self, obj, name, module, globs, source_lines):
41 def _get_test(self, obj, name, module, globs, source_lines):
43 test = super()._get_test(obj, name, module, globs, source_lines)
42 test = super()._get_test(obj, name, module, globs, source_lines)
44
43
45 if bool(getattr(obj, "__skip_doctest__", False)) and test is not None:
44 if bool(getattr(obj, "__skip_doctest__", False)) and test is not None:
46 for example in test.examples:
45 for example in test.examples:
47 example.options[doctest.SKIP] = True
46 example.options[doctest.SKIP] = True
48
47
49 return test
48 return test
50
49
51
50
52 class IPDoctestOutputChecker(doctest.OutputChecker):
51 class IPDoctestOutputChecker(doctest.OutputChecker):
53 """Second-chance checker with support for random tests.
52 """Second-chance checker with support for random tests.
54
53
55 If the default comparison doesn't pass, this checker looks in the expected
54 If the default comparison doesn't pass, this checker looks in the expected
56 output string for flags that tell us to ignore the output.
55 output string for flags that tell us to ignore the output.
57 """
56 """
58
57
59 random_re = re.compile(r'#\s*random\s+')
58 random_re = re.compile(r'#\s*random\s+')
60
59
61 def check_output(self, want, got, optionflags):
60 def check_output(self, want, got, optionflags):
62 """Check output, accepting special markers embedded in the output.
61 """Check output, accepting special markers embedded in the output.
63
62
64 If the output didn't pass the default validation but the special string
63 If the output didn't pass the default validation but the special string
65 '#random' is included, we accept it."""
64 '#random' is included, we accept it."""
66
65
67 # Let the original tester verify first, in case people have valid tests
66 # Let the original tester verify first, in case people have valid tests
68 # that happen to have a comment saying '#random' embedded in.
67 # that happen to have a comment saying '#random' embedded in.
69 ret = doctest.OutputChecker.check_output(self, want, got,
68 ret = doctest.OutputChecker.check_output(self, want, got,
70 optionflags)
69 optionflags)
71 if not ret and self.random_re.search(want):
70 if not ret and self.random_re.search(want):
72 #print >> sys.stderr, 'RANDOM OK:',want # dbg
71 #print >> sys.stderr, 'RANDOM OK:',want # dbg
73 return True
72 return True
74
73
75 return ret
74 return ret
76
75
77
76
78 # A simple subclassing of the original with a different class name, so we can
77 # A simple subclassing of the original with a different class name, so we can
79 # distinguish and treat differently IPython examples from pure python ones.
78 # distinguish and treat differently IPython examples from pure python ones.
80 class IPExample(doctest.Example): pass
79 class IPExample(doctest.Example): pass
81
80
82
81
83 class IPDocTestParser(doctest.DocTestParser):
82 class IPDocTestParser(doctest.DocTestParser):
84 """
83 """
85 A class used to parse strings containing doctest examples.
84 A class used to parse strings containing doctest examples.
86
85
87 Note: This is a version modified to properly recognize IPython input and
86 Note: This is a version modified to properly recognize IPython input and
88 convert any IPython examples into valid Python ones.
87 convert any IPython examples into valid Python ones.
89 """
88 """
90 # This regular expression is used to find doctest examples in a
89 # This regular expression is used to find doctest examples in a
91 # string. It defines three groups: `source` is the source code
90 # string. It defines three groups: `source` is the source code
92 # (including leading indentation and prompts); `indent` is the
91 # (including leading indentation and prompts); `indent` is the
93 # indentation of the first (PS1) line of the source code; and
92 # indentation of the first (PS1) line of the source code; and
94 # `want` is the expected output (including leading indentation).
93 # `want` is the expected output (including leading indentation).
95
94
96 # Classic Python prompts or default IPython ones
95 # Classic Python prompts or default IPython ones
97 _PS1_PY = r'>>>'
96 _PS1_PY = r'>>>'
98 _PS2_PY = r'\.\.\.'
97 _PS2_PY = r'\.\.\.'
99
98
100 _PS1_IP = r'In\ \[\d+\]:'
99 _PS1_IP = r'In\ \[\d+\]:'
101 _PS2_IP = r'\ \ \ \.\.\.+:'
100 _PS2_IP = r'\ \ \ \.\.\.+:'
102
101
103 _RE_TPL = r'''
102 _RE_TPL = r'''
104 # Source consists of a PS1 line followed by zero or more PS2 lines.
103 # Source consists of a PS1 line followed by zero or more PS2 lines.
105 (?P<source>
104 (?P<source>
106 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
105 (?:^(?P<indent> [ ]*) (?P<ps1> %s) .*) # PS1 line
107 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
106 (?:\n [ ]* (?P<ps2> %s) .*)*) # PS2 lines
108 \n? # a newline
107 \n? # a newline
109 # Want consists of any non-blank lines that do not start with PS1.
108 # Want consists of any non-blank lines that do not start with PS1.
110 (?P<want> (?:(?![ ]*$) # Not a blank line
109 (?P<want> (?:(?![ ]*$) # Not a blank line
111 (?![ ]*%s) # Not a line starting with PS1
110 (?![ ]*%s) # Not a line starting with PS1
112 (?![ ]*%s) # Not a line starting with PS2
111 (?![ ]*%s) # Not a line starting with PS2
113 .*$\n? # But any other line
112 .*$\n? # But any other line
114 )*)
113 )*)
115 '''
114 '''
116
115
117 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
116 _EXAMPLE_RE_PY = re.compile( _RE_TPL % (_PS1_PY,_PS2_PY,_PS1_PY,_PS2_PY),
118 re.MULTILINE | re.VERBOSE)
117 re.MULTILINE | re.VERBOSE)
119
118
120 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
119 _EXAMPLE_RE_IP = re.compile( _RE_TPL % (_PS1_IP,_PS2_IP,_PS1_IP,_PS2_IP),
121 re.MULTILINE | re.VERBOSE)
120 re.MULTILINE | re.VERBOSE)
122
121
123 # Mark a test as being fully random. In this case, we simply append the
122 # Mark a test as being fully random. In this case, we simply append the
124 # random marker ('#random') to each individual example's output. This way
123 # random marker ('#random') to each individual example's output. This way
125 # we don't need to modify any other code.
124 # we don't need to modify any other code.
126 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
125 _RANDOM_TEST = re.compile(r'#\s*all-random\s+')
127
126
128 def ip2py(self,source):
127 def ip2py(self,source):
129 """Convert input IPython source into valid Python."""
128 """Convert input IPython source into valid Python."""
130 block = _ip.input_transformer_manager.transform_cell(source)
129 block = _ip.input_transformer_manager.transform_cell(source)
131 if len(block.splitlines()) == 1:
130 if len(block.splitlines()) == 1:
132 return _ip.prefilter(block)
131 return _ip.prefilter(block)
133 else:
132 else:
134 return block
133 return block
135
134
136 def parse(self, string, name='<string>'):
135 def parse(self, string, name='<string>'):
137 """
136 """
138 Divide the given string into examples and intervening text,
137 Divide the given string into examples and intervening text,
139 and return them as a list of alternating Examples and strings.
138 and return them as a list of alternating Examples and strings.
140 Line numbers for the Examples are 0-based. The optional
139 Line numbers for the Examples are 0-based. The optional
141 argument `name` is a name identifying this string, and is only
140 argument `name` is a name identifying this string, and is only
142 used for error messages.
141 used for error messages.
143 """
142 """
144
143
145 #print 'Parse string:\n',string # dbg
144 #print 'Parse string:\n',string # dbg
146
145
147 string = string.expandtabs()
146 string = string.expandtabs()
148 # If all lines begin with the same indentation, then strip it.
147 # If all lines begin with the same indentation, then strip it.
149 min_indent = self._min_indent(string)
148 min_indent = self._min_indent(string)
150 if min_indent > 0:
149 if min_indent > 0:
151 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
150 string = '\n'.join([l[min_indent:] for l in string.split('\n')])
152
151
153 output = []
152 output = []
154 charno, lineno = 0, 0
153 charno, lineno = 0, 0
155
154
156 # We make 'all random' tests by adding the '# random' mark to every
155 # We make 'all random' tests by adding the '# random' mark to every
157 # block of output in the test.
156 # block of output in the test.
158 if self._RANDOM_TEST.search(string):
157 if self._RANDOM_TEST.search(string):
159 random_marker = '\n# random'
158 random_marker = '\n# random'
160 else:
159 else:
161 random_marker = ''
160 random_marker = ''
162
161
163 # Whether to convert the input from ipython to python syntax
162 # Whether to convert the input from ipython to python syntax
164 ip2py = False
163 ip2py = False
165 # Find all doctest examples in the string. First, try them as Python
164 # Find all doctest examples in the string. First, try them as Python
166 # examples, then as IPython ones
165 # examples, then as IPython ones
167 terms = list(self._EXAMPLE_RE_PY.finditer(string))
166 terms = list(self._EXAMPLE_RE_PY.finditer(string))
168 if terms:
167 if terms:
169 # Normal Python example
168 # Normal Python example
170 Example = doctest.Example
169 Example = doctest.Example
171 else:
170 else:
172 # It's an ipython example.
171 # It's an ipython example.
173 terms = list(self._EXAMPLE_RE_IP.finditer(string))
172 terms = list(self._EXAMPLE_RE_IP.finditer(string))
174 Example = IPExample
173 Example = IPExample
175 ip2py = True
174 ip2py = True
176
175
177 for m in terms:
176 for m in terms:
178 # Add the pre-example text to `output`.
177 # Add the pre-example text to `output`.
179 output.append(string[charno:m.start()])
178 output.append(string[charno:m.start()])
180 # Update lineno (lines before this example)
179 # Update lineno (lines before this example)
181 lineno += string.count('\n', charno, m.start())
180 lineno += string.count('\n', charno, m.start())
182 # Extract info from the regexp match.
181 # Extract info from the regexp match.
183 (source, options, want, exc_msg) = \
182 (source, options, want, exc_msg) = \
184 self._parse_example(m, name, lineno,ip2py)
183 self._parse_example(m, name, lineno,ip2py)
185
184
186 # Append the random-output marker (it defaults to empty in most
185 # Append the random-output marker (it defaults to empty in most
187 # cases, it's only non-empty for 'all-random' tests):
186 # cases, it's only non-empty for 'all-random' tests):
188 want += random_marker
187 want += random_marker
189
188
190 # Create an Example, and add it to the list.
189 # Create an Example, and add it to the list.
191 if not self._IS_BLANK_OR_COMMENT(source):
190 if not self._IS_BLANK_OR_COMMENT(source):
192 output.append(Example(source, want, exc_msg,
191 output.append(Example(source, want, exc_msg,
193 lineno=lineno,
192 lineno=lineno,
194 indent=min_indent+len(m.group('indent')),
193 indent=min_indent+len(m.group('indent')),
195 options=options))
194 options=options))
196 # Update lineno (lines inside this example)
195 # Update lineno (lines inside this example)
197 lineno += string.count('\n', m.start(), m.end())
196 lineno += string.count('\n', m.start(), m.end())
198 # Update charno.
197 # Update charno.
199 charno = m.end()
198 charno = m.end()
200 # Add any remaining post-example text to `output`.
199 # Add any remaining post-example text to `output`.
201 output.append(string[charno:])
200 output.append(string[charno:])
202 return output
201 return output
203
202
204 def _parse_example(self, m, name, lineno,ip2py=False):
203 def _parse_example(self, m, name, lineno,ip2py=False):
205 """
204 """
206 Given a regular expression match from `_EXAMPLE_RE` (`m`),
205 Given a regular expression match from `_EXAMPLE_RE` (`m`),
207 return a pair `(source, want)`, where `source` is the matched
206 return a pair `(source, want)`, where `source` is the matched
208 example's source code (with prompts and indentation stripped);
207 example's source code (with prompts and indentation stripped);
209 and `want` is the example's expected output (with indentation
208 and `want` is the example's expected output (with indentation
210 stripped).
209 stripped).
211
210
212 `name` is the string's name, and `lineno` is the line number
211 `name` is the string's name, and `lineno` is the line number
213 where the example starts; both are used for error messages.
212 where the example starts; both are used for error messages.
214
213
215 Optional:
214 Optional:
216 `ip2py`: if true, filter the input via IPython to convert the syntax
215 `ip2py`: if true, filter the input via IPython to convert the syntax
217 into valid python.
216 into valid python.
218 """
217 """
219
218
220 # Get the example's indentation level.
219 # Get the example's indentation level.
221 indent = len(m.group('indent'))
220 indent = len(m.group('indent'))
222
221
223 # Divide source into lines; check that they're properly
222 # Divide source into lines; check that they're properly
224 # indented; and then strip their indentation & prompts.
223 # indented; and then strip their indentation & prompts.
225 source_lines = m.group('source').split('\n')
224 source_lines = m.group('source').split('\n')
226
225
227 # We're using variable-length input prompts
226 # We're using variable-length input prompts
228 ps1 = m.group('ps1')
227 ps1 = m.group('ps1')
229 ps2 = m.group('ps2')
228 ps2 = m.group('ps2')
230 ps1_len = len(ps1)
229 ps1_len = len(ps1)
231
230
232 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
231 self._check_prompt_blank(source_lines, indent, name, lineno,ps1_len)
233 if ps2:
232 if ps2:
234 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
233 self._check_prefix(source_lines[1:], ' '*indent + ps2, name, lineno)
235
234
236 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
235 source = '\n'.join([sl[indent+ps1_len+1:] for sl in source_lines])
237
236
238 if ip2py:
237 if ip2py:
239 # Convert source input from IPython into valid Python syntax
238 # Convert source input from IPython into valid Python syntax
240 source = self.ip2py(source)
239 source = self.ip2py(source)
241
240
242 # Divide want into lines; check that it's properly indented; and
241 # Divide want into lines; check that it's properly indented; and
243 # then strip the indentation. Spaces before the last newline should
242 # then strip the indentation. Spaces before the last newline should
244 # be preserved, so plain rstrip() isn't good enough.
243 # be preserved, so plain rstrip() isn't good enough.
245 want = m.group('want')
244 want = m.group('want')
246 want_lines = want.split('\n')
245 want_lines = want.split('\n')
247 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
246 if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
248 del want_lines[-1] # forget final newline & spaces after it
247 del want_lines[-1] # forget final newline & spaces after it
249 self._check_prefix(want_lines, ' '*indent, name,
248 self._check_prefix(want_lines, ' '*indent, name,
250 lineno + len(source_lines))
249 lineno + len(source_lines))
251
250
252 # Remove ipython output prompt that might be present in the first line
251 # Remove ipython output prompt that might be present in the first line
253 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
252 want_lines[0] = re.sub(r'Out\[\d+\]: \s*?\n?','',want_lines[0])
254
253
255 want = '\n'.join([wl[indent:] for wl in want_lines])
254 want = '\n'.join([wl[indent:] for wl in want_lines])
256
255
257 # If `want` contains a traceback message, then extract it.
256 # If `want` contains a traceback message, then extract it.
258 m = self._EXCEPTION_RE.match(want)
257 m = self._EXCEPTION_RE.match(want)
259 if m:
258 if m:
260 exc_msg = m.group('msg')
259 exc_msg = m.group('msg')
261 else:
260 else:
262 exc_msg = None
261 exc_msg = None
263
262
264 # Extract options from the source.
263 # Extract options from the source.
265 options = self._find_options(source, name, lineno)
264 options = self._find_options(source, name, lineno)
266
265
267 return source, options, want, exc_msg
266 return source, options, want, exc_msg
268
267
269 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
268 def _check_prompt_blank(self, lines, indent, name, lineno, ps1_len):
270 """
269 """
271 Given the lines of a source string (including prompts and
270 Given the lines of a source string (including prompts and
272 leading indentation), check to make sure that every prompt is
271 leading indentation), check to make sure that every prompt is
273 followed by a space character. If any line is not followed by
272 followed by a space character. If any line is not followed by
274 a space character, then raise ValueError.
273 a space character, then raise ValueError.
275
274
276 Note: IPython-modified version which takes the input prompt length as a
275 Note: IPython-modified version which takes the input prompt length as a
277 parameter, so that prompts of variable length can be dealt with.
276 parameter, so that prompts of variable length can be dealt with.
278 """
277 """
279 space_idx = indent+ps1_len
278 space_idx = indent+ps1_len
280 min_len = space_idx+1
279 min_len = space_idx+1
281 for i, line in enumerate(lines):
280 for i, line in enumerate(lines):
282 if len(line) >= min_len and line[space_idx] != ' ':
281 if len(line) >= min_len and line[space_idx] != ' ':
283 raise ValueError('line %r of the docstring for %s '
282 raise ValueError('line %r of the docstring for %s '
284 'lacks blank after %s: %r' %
283 'lacks blank after %s: %r' %
285 (lineno+i+1, name,
284 (lineno+i+1, name,
286 line[indent:space_idx], line))
285 line[indent:space_idx], line))
287
286
288
287
289 SKIP = doctest.register_optionflag('SKIP')
288 SKIP = doctest.register_optionflag('SKIP')
290
289
291
290
292 class IPDocTestRunner(doctest.DocTestRunner,object):
291 class IPDocTestRunner(doctest.DocTestRunner,object):
293 """Test runner that synchronizes the IPython namespace with test globals.
292 """Test runner that synchronizes the IPython namespace with test globals.
294 """
293 """
295
294
296 def run(self, test, compileflags=None, out=None, clear_globs=True):
295 def run(self, test, compileflags=None, out=None, clear_globs=True):
297 # Override terminal size to standardise traceback format
296 # Override terminal size to standardise traceback format
298 with modified_env({'COLUMNS': '80', 'LINES': '24'}):
297 with modified_env({'COLUMNS': '80', 'LINES': '24'}):
299 return super(IPDocTestRunner,self).run(test,
298 return super(IPDocTestRunner,self).run(test,
300 compileflags,out,clear_globs)
299 compileflags,out,clear_globs)
@@ -1,67 +1,66 b''
1 """
1 """
2 Test that CVEs stay fixed.
2 Test that CVEs stay fixed.
3 """
3 """
4
4
5 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
5 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
6 from pathlib import Path
6 from pathlib import Path
7 import random
7 import random
8 import sys
8 import sys
9 import os
9 import os
10 import string
10 import string
11 import subprocess
11 import subprocess
12 import time
13
12
14
13
15 def test_cve_2022_21699():
14 def test_cve_2022_21699():
16 """
15 """
17 Here we test CVE-2022-21699.
16 Here we test CVE-2022-21699.
18
17
19 We create a temporary directory, cd into it.
18 We create a temporary directory, cd into it.
20 Make a profile file that should not be executed and start IPython in a subprocess,
19 Make a profile file that should not be executed and start IPython in a subprocess,
21 checking for the value.
20 checking for the value.
22
21
23
22
24
23
25 """
24 """
26
25
27 dangerous_profile_dir = Path("profile_default")
26 dangerous_profile_dir = Path("profile_default")
28
27
29 dangerous_startup_dir = dangerous_profile_dir / "startup"
28 dangerous_startup_dir = dangerous_profile_dir / "startup"
30 dangerous_expected = "CVE-2022-21699-" + "".join(
29 dangerous_expected = "CVE-2022-21699-" + "".join(
31 [random.choice(string.ascii_letters) for i in range(10)]
30 [random.choice(string.ascii_letters) for i in range(10)]
32 )
31 )
33
32
34 with TemporaryWorkingDirectory() as t:
33 with TemporaryWorkingDirectory() as t:
35 dangerous_startup_dir.mkdir(parents=True)
34 dangerous_startup_dir.mkdir(parents=True)
36 (dangerous_startup_dir / "foo.py").write_text(
35 (dangerous_startup_dir / "foo.py").write_text(
37 f'print("{dangerous_expected}")', encoding="utf-8"
36 f'print("{dangerous_expected}")', encoding="utf-8"
38 )
37 )
39 # 1 sec to make sure FS is flushed.
38 # 1 sec to make sure FS is flushed.
40 # time.sleep(1)
39 # time.sleep(1)
41 cmd = [sys.executable, "-m", "IPython"]
40 cmd = [sys.executable, "-m", "IPython"]
42 env = os.environ.copy()
41 env = os.environ.copy()
43 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
42 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
44
43
45 # First we fake old behavior, making sure the profile is/was actually dangerous
44 # First we fake old behavior, making sure the profile is/was actually dangerous
46 p_dangerous = subprocess.Popen(
45 p_dangerous = subprocess.Popen(
47 cmd + [f"--profile-dir={dangerous_profile_dir}"],
46 cmd + [f"--profile-dir={dangerous_profile_dir}"],
48 env=env,
47 env=env,
49 stdin=subprocess.PIPE,
48 stdin=subprocess.PIPE,
50 stdout=subprocess.PIPE,
49 stdout=subprocess.PIPE,
51 stderr=subprocess.PIPE,
50 stderr=subprocess.PIPE,
52 )
51 )
53 out_dangerous, err_dangerouns = p_dangerous.communicate(b"exit\r")
52 out_dangerous, err_dangerouns = p_dangerous.communicate(b"exit\r")
54 assert dangerous_expected in out_dangerous.decode()
53 assert dangerous_expected in out_dangerous.decode()
55
54
56 # Now that we know it _would_ have been dangerous, we test it's not loaded
55 # Now that we know it _would_ have been dangerous, we test it's not loaded
57 p = subprocess.Popen(
56 p = subprocess.Popen(
58 cmd,
57 cmd,
59 env=env,
58 env=env,
60 stdin=subprocess.PIPE,
59 stdin=subprocess.PIPE,
61 stdout=subprocess.PIPE,
60 stdout=subprocess.PIPE,
62 stderr=subprocess.PIPE,
61 stderr=subprocess.PIPE,
63 )
62 )
64 out, err = p.communicate(b"exit\r")
63 out, err = p.communicate(b"exit\r")
65 assert b"IPython" in out
64 assert b"IPython" in out
66 assert dangerous_expected not in out.decode()
65 assert dangerous_expected not in out.decode()
67 assert err == b""
66 assert err == b""
@@ -1,157 +1,156 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 IO related utilities.
3 IO related utilities.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9
9
10
10
11 import atexit
11 import atexit
12 import os
12 import os
13 import sys
13 import sys
14 import tempfile
14 import tempfile
15 import warnings
16 from pathlib import Path
15 from pathlib import Path
17 from warnings import warn
16 from warnings import warn
18
17
19 from IPython.utils.decorators import undoc
18 from IPython.utils.decorators import undoc
20 from .capture import CapturedIO, capture_output
19 from .capture import CapturedIO, capture_output
21
20
22 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
21 # setup stdin/stdout/stderr to sys.stdin/sys.stdout/sys.stderr
23 devnull = open(os.devnull, "w", encoding="utf-8")
22 devnull = open(os.devnull, "w", encoding="utf-8")
24 atexit.register(devnull.close)
23 atexit.register(devnull.close)
25
24
26
25
27 class Tee(object):
26 class Tee(object):
28 """A class to duplicate an output stream to stdout/err.
27 """A class to duplicate an output stream to stdout/err.
29
28
30 This works in a manner very similar to the Unix 'tee' command.
29 This works in a manner very similar to the Unix 'tee' command.
31
30
32 When the object is closed or deleted, it closes the original file given to
31 When the object is closed or deleted, it closes the original file given to
33 it for duplication.
32 it for duplication.
34 """
33 """
35 # Inspired by:
34 # Inspired by:
36 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
35 # http://mail.python.org/pipermail/python-list/2007-May/442737.html
37
36
38 def __init__(self, file_or_name, mode="w", channel='stdout'):
37 def __init__(self, file_or_name, mode="w", channel='stdout'):
39 """Construct a new Tee object.
38 """Construct a new Tee object.
40
39
41 Parameters
40 Parameters
42 ----------
41 ----------
43 file_or_name : filename or open filehandle (writable)
42 file_or_name : filename or open filehandle (writable)
44 File that will be duplicated
43 File that will be duplicated
45 mode : optional, valid mode for open().
44 mode : optional, valid mode for open().
46 If a filename was give, open with this mode.
45 If a filename was give, open with this mode.
47 channel : str, one of ['stdout', 'stderr']
46 channel : str, one of ['stdout', 'stderr']
48 """
47 """
49 if channel not in ['stdout', 'stderr']:
48 if channel not in ['stdout', 'stderr']:
50 raise ValueError('Invalid channel spec %s' % channel)
49 raise ValueError('Invalid channel spec %s' % channel)
51
50
52 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
51 if hasattr(file_or_name, 'write') and hasattr(file_or_name, 'seek'):
53 self.file = file_or_name
52 self.file = file_or_name
54 else:
53 else:
55 encoding = None if "b" in mode else "utf-8"
54 encoding = None if "b" in mode else "utf-8"
56 self.file = open(file_or_name, mode, encoding=encoding)
55 self.file = open(file_or_name, mode, encoding=encoding)
57 self.channel = channel
56 self.channel = channel
58 self.ostream = getattr(sys, channel)
57 self.ostream = getattr(sys, channel)
59 setattr(sys, channel, self)
58 setattr(sys, channel, self)
60 self._closed = False
59 self._closed = False
61
60
62 def close(self):
61 def close(self):
63 """Close the file and restore the channel."""
62 """Close the file and restore the channel."""
64 self.flush()
63 self.flush()
65 setattr(sys, self.channel, self.ostream)
64 setattr(sys, self.channel, self.ostream)
66 self.file.close()
65 self.file.close()
67 self._closed = True
66 self._closed = True
68
67
69 def write(self, data):
68 def write(self, data):
70 """Write data to both channels."""
69 """Write data to both channels."""
71 self.file.write(data)
70 self.file.write(data)
72 self.ostream.write(data)
71 self.ostream.write(data)
73 self.ostream.flush()
72 self.ostream.flush()
74
73
75 def flush(self):
74 def flush(self):
76 """Flush both channels."""
75 """Flush both channels."""
77 self.file.flush()
76 self.file.flush()
78 self.ostream.flush()
77 self.ostream.flush()
79
78
80 def __del__(self):
79 def __del__(self):
81 if not self._closed:
80 if not self._closed:
82 self.close()
81 self.close()
83
82
84
83
85 def ask_yes_no(prompt, default=None, interrupt=None):
84 def ask_yes_no(prompt, default=None, interrupt=None):
86 """Asks a question and returns a boolean (y/n) answer.
85 """Asks a question and returns a boolean (y/n) answer.
87
86
88 If default is given (one of 'y','n'), it is used if the user input is
87 If default is given (one of 'y','n'), it is used if the user input is
89 empty. If interrupt is given (one of 'y','n'), it is used if the user
88 empty. If interrupt is given (one of 'y','n'), it is used if the user
90 presses Ctrl-C. Otherwise the question is repeated until an answer is
89 presses Ctrl-C. Otherwise the question is repeated until an answer is
91 given.
90 given.
92
91
93 An EOF is treated as the default answer. If there is no default, an
92 An EOF is treated as the default answer. If there is no default, an
94 exception is raised to prevent infinite loops.
93 exception is raised to prevent infinite loops.
95
94
96 Valid answers are: y/yes/n/no (match is not case sensitive)."""
95 Valid answers are: y/yes/n/no (match is not case sensitive)."""
97
96
98 answers = {'y':True,'n':False,'yes':True,'no':False}
97 answers = {'y':True,'n':False,'yes':True,'no':False}
99 ans = None
98 ans = None
100 while ans not in answers.keys():
99 while ans not in answers.keys():
101 try:
100 try:
102 ans = input(prompt+' ').lower()
101 ans = input(prompt+' ').lower()
103 if not ans: # response was an empty string
102 if not ans: # response was an empty string
104 ans = default
103 ans = default
105 except KeyboardInterrupt:
104 except KeyboardInterrupt:
106 if interrupt:
105 if interrupt:
107 ans = interrupt
106 ans = interrupt
108 print("\r")
107 print("\r")
109 except EOFError:
108 except EOFError:
110 if default in answers.keys():
109 if default in answers.keys():
111 ans = default
110 ans = default
112 print()
111 print()
113 else:
112 else:
114 raise
113 raise
115
114
116 return answers[ans]
115 return answers[ans]
117
116
118
117
119 def temp_pyfile(src, ext='.py'):
118 def temp_pyfile(src, ext='.py'):
120 """Make a temporary python file, return filename and filehandle.
119 """Make a temporary python file, return filename and filehandle.
121
120
122 Parameters
121 Parameters
123 ----------
122 ----------
124 src : string or list of strings (no need for ending newlines if list)
123 src : string or list of strings (no need for ending newlines if list)
125 Source code to be written to the file.
124 Source code to be written to the file.
126 ext : optional, string
125 ext : optional, string
127 Extension for the generated file.
126 Extension for the generated file.
128
127
129 Returns
128 Returns
130 -------
129 -------
131 (filename, open filehandle)
130 (filename, open filehandle)
132 It is the caller's responsibility to close the open file and unlink it.
131 It is the caller's responsibility to close the open file and unlink it.
133 """
132 """
134 fname = tempfile.mkstemp(ext)[1]
133 fname = tempfile.mkstemp(ext)[1]
135 with open(Path(fname), "w", encoding="utf-8") as f:
134 with open(Path(fname), "w", encoding="utf-8") as f:
136 f.write(src)
135 f.write(src)
137 f.flush()
136 f.flush()
138 return fname
137 return fname
139
138
140
139
141 @undoc
140 @undoc
142 def raw_print(*args, **kw):
141 def raw_print(*args, **kw):
143 """DEPRECATED: Raw print to sys.__stdout__, otherwise identical interface to print()."""
142 """DEPRECATED: Raw print to sys.__stdout__, otherwise identical interface to print()."""
144 warn("IPython.utils.io.raw_print has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
143 warn("IPython.utils.io.raw_print has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
145
144
146 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
145 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
147 file=sys.__stdout__)
146 file=sys.__stdout__)
148 sys.__stdout__.flush()
147 sys.__stdout__.flush()
149
148
150 @undoc
149 @undoc
151 def raw_print_err(*args, **kw):
150 def raw_print_err(*args, **kw):
152 """DEPRECATED: Raw print to sys.__stderr__, otherwise identical interface to print()."""
151 """DEPRECATED: Raw print to sys.__stderr__, otherwise identical interface to print()."""
153 warn("IPython.utils.io.raw_print_err has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
152 warn("IPython.utils.io.raw_print_err has been deprecated since IPython 7.0", DeprecationWarning, stacklevel=2)
154
153
155 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
154 print(*args, sep=kw.get('sep', ' '), end=kw.get('end', '\n'),
156 file=sys.__stderr__)
155 file=sys.__stderr__)
157 sys.__stderr__.flush()
156 sys.__stderr__.flush()
@@ -1,71 +1,70 b''
1 """Utility functions for finding modules
1 """Utility functions for finding modules
2
2
3 Utility functions for finding modules on sys.path.
3 Utility functions for finding modules on sys.path.
4
4
5 """
5 """
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (c) 2011, the IPython Development Team.
7 # Copyright (c) 2011, the IPython Development Team.
8 #
8 #
9 # Distributed under the terms of the Modified BSD License.
9 # Distributed under the terms of the Modified BSD License.
10 #
10 #
11 # The full license is in the file COPYING.txt, distributed with this software.
11 # The full license is in the file COPYING.txt, distributed with this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Stdlib imports
18 # Stdlib imports
19 import importlib
19 import importlib
20 import os
21 import sys
20 import sys
22
21
23 # Third-party imports
22 # Third-party imports
24
23
25 # Our own imports
24 # Our own imports
26
25
27
26
28 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
29 # Globals and constants
28 # Globals and constants
30 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
31
30
32 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
33 # Local utilities
32 # Local utilities
34 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
35
34
36 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
37 # Classes and functions
36 # Classes and functions
38 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
39
38
40 def find_mod(module_name):
39 def find_mod(module_name):
41 """
40 """
42 Find module `module_name` on sys.path, and return the path to module `module_name`.
41 Find module `module_name` on sys.path, and return the path to module `module_name`.
43
42
44 - If `module_name` refers to a module directory, then return path to __init__ file.
43 - If `module_name` refers to a module directory, then return path to __init__ file.
45 - If `module_name` is a directory without an __init__file, return None.
44 - If `module_name` is a directory without an __init__file, return None.
46 - If module is missing or does not have a `.py` or `.pyw` extension, return None.
45 - If module is missing or does not have a `.py` or `.pyw` extension, return None.
47 - Note that we are not interested in running bytecode.
46 - Note that we are not interested in running bytecode.
48 - Otherwise, return the fill path of the module.
47 - Otherwise, return the fill path of the module.
49
48
50 Parameters
49 Parameters
51 ----------
50 ----------
52 module_name : str
51 module_name : str
53
52
54 Returns
53 Returns
55 -------
54 -------
56 module_path : str
55 module_path : str
57 Path to module `module_name`, its __init__.py, or None,
56 Path to module `module_name`, its __init__.py, or None,
58 depending on above conditions.
57 depending on above conditions.
59 """
58 """
60 spec = importlib.util.find_spec(module_name)
59 spec = importlib.util.find_spec(module_name)
61 module_path = spec.origin
60 module_path = spec.origin
62 if module_path is None:
61 if module_path is None:
63 if spec.loader in sys.meta_path:
62 if spec.loader in sys.meta_path:
64 return spec.loader
63 return spec.loader
65 return None
64 return None
66 else:
65 else:
67 split_path = module_path.split(".")
66 split_path = module_path.split(".")
68 if split_path[-1] in ["py", "pyw"]:
67 if split_path[-1] in ["py", "pyw"]:
69 return module_path
68 return module_path
70 else:
69 else:
71 return None
70 return None
@@ -1,393 +1,391 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for path handling.
3 Utilities for path handling.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 import os
9 import os
10 import sys
10 import sys
11 import errno
11 import errno
12 import shutil
12 import shutil
13 import random
13 import random
14 import glob
14 import glob
15 from warnings import warn
16
15
17 from IPython.utils.process import system
16 from IPython.utils.process import system
18 from IPython.utils.decorators import undoc
19
17
20 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
21 # Code
19 # Code
22 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
23 fs_encoding = sys.getfilesystemencoding()
21 fs_encoding = sys.getfilesystemencoding()
24
22
25 def _writable_dir(path):
23 def _writable_dir(path):
26 """Whether `path` is a directory, to which the user has write access."""
24 """Whether `path` is a directory, to which the user has write access."""
27 return os.path.isdir(path) and os.access(path, os.W_OK)
25 return os.path.isdir(path) and os.access(path, os.W_OK)
28
26
29 if sys.platform == 'win32':
27 if sys.platform == 'win32':
30 def _get_long_path_name(path):
28 def _get_long_path_name(path):
31 """Get a long path name (expand ~) on Windows using ctypes.
29 """Get a long path name (expand ~) on Windows using ctypes.
32
30
33 Examples
31 Examples
34 --------
32 --------
35
33
36 >>> get_long_path_name('c:\\\\docume~1')
34 >>> get_long_path_name('c:\\\\docume~1')
37 'c:\\\\Documents and Settings'
35 'c:\\\\Documents and Settings'
38
36
39 """
37 """
40 try:
38 try:
41 import ctypes
39 import ctypes
42 except ImportError as e:
40 except ImportError as e:
43 raise ImportError('you need to have ctypes installed for this to work') from e
41 raise ImportError('you need to have ctypes installed for this to work') from e
44 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
42 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
45 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
43 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
46 ctypes.c_uint ]
44 ctypes.c_uint ]
47
45
48 buf = ctypes.create_unicode_buffer(260)
46 buf = ctypes.create_unicode_buffer(260)
49 rv = _GetLongPathName(path, buf, 260)
47 rv = _GetLongPathName(path, buf, 260)
50 if rv == 0 or rv > 260:
48 if rv == 0 or rv > 260:
51 return path
49 return path
52 else:
50 else:
53 return buf.value
51 return buf.value
54 else:
52 else:
55 def _get_long_path_name(path):
53 def _get_long_path_name(path):
56 """Dummy no-op."""
54 """Dummy no-op."""
57 return path
55 return path
58
56
59
57
60
58
61 def get_long_path_name(path):
59 def get_long_path_name(path):
62 """Expand a path into its long form.
60 """Expand a path into its long form.
63
61
64 On Windows this expands any ~ in the paths. On other platforms, it is
62 On Windows this expands any ~ in the paths. On other platforms, it is
65 a null operation.
63 a null operation.
66 """
64 """
67 return _get_long_path_name(path)
65 return _get_long_path_name(path)
68
66
69
67
70 def compress_user(path):
68 def compress_user(path):
71 """Reverse of :func:`os.path.expanduser`
69 """Reverse of :func:`os.path.expanduser`
72 """
70 """
73 home = os.path.expanduser('~')
71 home = os.path.expanduser('~')
74 if path.startswith(home):
72 if path.startswith(home):
75 path = "~" + path[len(home):]
73 path = "~" + path[len(home):]
76 return path
74 return path
77
75
78 def get_py_filename(name):
76 def get_py_filename(name):
79 """Return a valid python filename in the current directory.
77 """Return a valid python filename in the current directory.
80
78
81 If the given name is not a file, it adds '.py' and searches again.
79 If the given name is not a file, it adds '.py' and searches again.
82 Raises IOError with an informative message if the file isn't found.
80 Raises IOError with an informative message if the file isn't found.
83 """
81 """
84
82
85 name = os.path.expanduser(name)
83 name = os.path.expanduser(name)
86 if os.path.isfile(name):
84 if os.path.isfile(name):
87 return name
85 return name
88 if not name.endswith(".py"):
86 if not name.endswith(".py"):
89 py_name = name + ".py"
87 py_name = name + ".py"
90 if os.path.isfile(py_name):
88 if os.path.isfile(py_name):
91 return py_name
89 return py_name
92 raise IOError("File `%r` not found." % name)
90 raise IOError("File `%r` not found." % name)
93
91
94
92
95 def filefind(filename: str, path_dirs=None) -> str:
93 def filefind(filename: str, path_dirs=None) -> str:
96 """Find a file by looking through a sequence of paths.
94 """Find a file by looking through a sequence of paths.
97
95
98 This iterates through a sequence of paths looking for a file and returns
96 This iterates through a sequence of paths looking for a file and returns
99 the full, absolute path of the first occurrence of the file. If no set of
97 the full, absolute path of the first occurrence of the file. If no set of
100 path dirs is given, the filename is tested as is, after running through
98 path dirs is given, the filename is tested as is, after running through
101 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
99 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
102
100
103 filefind('myfile.txt')
101 filefind('myfile.txt')
104
102
105 will find the file in the current working dir, but::
103 will find the file in the current working dir, but::
106
104
107 filefind('~/myfile.txt')
105 filefind('~/myfile.txt')
108
106
109 Will find the file in the users home directory. This function does not
107 Will find the file in the users home directory. This function does not
110 automatically try any paths, such as the cwd or the user's home directory.
108 automatically try any paths, such as the cwd or the user's home directory.
111
109
112 Parameters
110 Parameters
113 ----------
111 ----------
114 filename : str
112 filename : str
115 The filename to look for.
113 The filename to look for.
116 path_dirs : str, None or sequence of str
114 path_dirs : str, None or sequence of str
117 The sequence of paths to look for the file in. If None, the filename
115 The sequence of paths to look for the file in. If None, the filename
118 need to be absolute or be in the cwd. If a string, the string is
116 need to be absolute or be in the cwd. If a string, the string is
119 put into a sequence and the searched. If a sequence, walk through
117 put into a sequence and the searched. If a sequence, walk through
120 each element and join with ``filename``, calling :func:`expandvars`
118 each element and join with ``filename``, calling :func:`expandvars`
121 and :func:`expanduser` before testing for existence.
119 and :func:`expanduser` before testing for existence.
122
120
123 Returns
121 Returns
124 -------
122 -------
125 path : str
123 path : str
126 returns absolute path to file.
124 returns absolute path to file.
127
125
128 Raises
126 Raises
129 ------
127 ------
130 IOError
128 IOError
131 """
129 """
132
130
133 # If paths are quoted, abspath gets confused, strip them...
131 # If paths are quoted, abspath gets confused, strip them...
134 filename = filename.strip('"').strip("'")
132 filename = filename.strip('"').strip("'")
135 # If the input is an absolute path, just check it exists
133 # If the input is an absolute path, just check it exists
136 if os.path.isabs(filename) and os.path.isfile(filename):
134 if os.path.isabs(filename) and os.path.isfile(filename):
137 return filename
135 return filename
138
136
139 if path_dirs is None:
137 if path_dirs is None:
140 path_dirs = ("",)
138 path_dirs = ("",)
141 elif isinstance(path_dirs, str):
139 elif isinstance(path_dirs, str):
142 path_dirs = (path_dirs,)
140 path_dirs = (path_dirs,)
143
141
144 for path in path_dirs:
142 for path in path_dirs:
145 if path == '.': path = os.getcwd()
143 if path == '.': path = os.getcwd()
146 testname = expand_path(os.path.join(path, filename))
144 testname = expand_path(os.path.join(path, filename))
147 if os.path.isfile(testname):
145 if os.path.isfile(testname):
148 return os.path.abspath(testname)
146 return os.path.abspath(testname)
149
147
150 raise IOError("File %r does not exist in any of the search paths: %r" %
148 raise IOError("File %r does not exist in any of the search paths: %r" %
151 (filename, path_dirs) )
149 (filename, path_dirs) )
152
150
153
151
154 class HomeDirError(Exception):
152 class HomeDirError(Exception):
155 pass
153 pass
156
154
157
155
158 def get_home_dir(require_writable=False) -> str:
156 def get_home_dir(require_writable=False) -> str:
159 """Return the 'home' directory, as a unicode string.
157 """Return the 'home' directory, as a unicode string.
160
158
161 Uses os.path.expanduser('~'), and checks for writability.
159 Uses os.path.expanduser('~'), and checks for writability.
162
160
163 See stdlib docs for how this is determined.
161 See stdlib docs for how this is determined.
164 For Python <3.8, $HOME is first priority on *ALL* platforms.
162 For Python <3.8, $HOME is first priority on *ALL* platforms.
165 For Python >=3.8 on Windows, %HOME% is no longer considered.
163 For Python >=3.8 on Windows, %HOME% is no longer considered.
166
164
167 Parameters
165 Parameters
168 ----------
166 ----------
169 require_writable : bool [default: False]
167 require_writable : bool [default: False]
170 if True:
168 if True:
171 guarantees the return value is a writable directory, otherwise
169 guarantees the return value is a writable directory, otherwise
172 raises HomeDirError
170 raises HomeDirError
173 if False:
171 if False:
174 The path is resolved, but it is not guaranteed to exist or be writable.
172 The path is resolved, but it is not guaranteed to exist or be writable.
175 """
173 """
176
174
177 homedir = os.path.expanduser('~')
175 homedir = os.path.expanduser('~')
178 # Next line will make things work even when /home/ is a symlink to
176 # Next line will make things work even when /home/ is a symlink to
179 # /usr/home as it is on FreeBSD, for example
177 # /usr/home as it is on FreeBSD, for example
180 homedir = os.path.realpath(homedir)
178 homedir = os.path.realpath(homedir)
181
179
182 if not _writable_dir(homedir) and os.name == 'nt':
180 if not _writable_dir(homedir) and os.name == 'nt':
183 # expanduser failed, use the registry to get the 'My Documents' folder.
181 # expanduser failed, use the registry to get the 'My Documents' folder.
184 try:
182 try:
185 import winreg as wreg
183 import winreg as wreg
186 with wreg.OpenKey(
184 with wreg.OpenKey(
187 wreg.HKEY_CURRENT_USER,
185 wreg.HKEY_CURRENT_USER,
188 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
186 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
189 ) as key:
187 ) as key:
190 homedir = wreg.QueryValueEx(key,'Personal')[0]
188 homedir = wreg.QueryValueEx(key,'Personal')[0]
191 except:
189 except:
192 pass
190 pass
193
191
194 if (not require_writable) or _writable_dir(homedir):
192 if (not require_writable) or _writable_dir(homedir):
195 assert isinstance(homedir, str), "Homedir should be unicode not bytes"
193 assert isinstance(homedir, str), "Homedir should be unicode not bytes"
196 return homedir
194 return homedir
197 else:
195 else:
198 raise HomeDirError('%s is not a writable dir, '
196 raise HomeDirError('%s is not a writable dir, '
199 'set $HOME environment variable to override' % homedir)
197 'set $HOME environment variable to override' % homedir)
200
198
201 def get_xdg_dir():
199 def get_xdg_dir():
202 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
200 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
203
201
204 This is only for non-OS X posix (Linux,Unix,etc.) systems.
202 This is only for non-OS X posix (Linux,Unix,etc.) systems.
205 """
203 """
206
204
207 env = os.environ
205 env = os.environ
208
206
209 if os.name == "posix":
207 if os.name == "posix":
210 # Linux, Unix, AIX, etc.
208 # Linux, Unix, AIX, etc.
211 # use ~/.config if empty OR not set
209 # use ~/.config if empty OR not set
212 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
210 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
213 if xdg and _writable_dir(xdg):
211 if xdg and _writable_dir(xdg):
214 assert isinstance(xdg, str)
212 assert isinstance(xdg, str)
215 return xdg
213 return xdg
216
214
217 return None
215 return None
218
216
219
217
220 def get_xdg_cache_dir():
218 def get_xdg_cache_dir():
221 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
219 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
222
220
223 This is only for non-OS X posix (Linux,Unix,etc.) systems.
221 This is only for non-OS X posix (Linux,Unix,etc.) systems.
224 """
222 """
225
223
226 env = os.environ
224 env = os.environ
227
225
228 if os.name == "posix":
226 if os.name == "posix":
229 # Linux, Unix, AIX, etc.
227 # Linux, Unix, AIX, etc.
230 # use ~/.cache if empty OR not set
228 # use ~/.cache if empty OR not set
231 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
229 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
232 if xdg and _writable_dir(xdg):
230 if xdg and _writable_dir(xdg):
233 assert isinstance(xdg, str)
231 assert isinstance(xdg, str)
234 return xdg
232 return xdg
235
233
236 return None
234 return None
237
235
238
236
239 def expand_path(s):
237 def expand_path(s):
240 """Expand $VARS and ~names in a string, like a shell
238 """Expand $VARS and ~names in a string, like a shell
241
239
242 :Examples:
240 :Examples:
243
241
244 In [2]: os.environ['FOO']='test'
242 In [2]: os.environ['FOO']='test'
245
243
246 In [3]: expand_path('variable FOO is $FOO')
244 In [3]: expand_path('variable FOO is $FOO')
247 Out[3]: 'variable FOO is test'
245 Out[3]: 'variable FOO is test'
248 """
246 """
249 # This is a pretty subtle hack. When expand user is given a UNC path
247 # This is a pretty subtle hack. When expand user is given a UNC path
250 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
248 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
251 # the $ to get (\\server\share\%username%). I think it considered $
249 # the $ to get (\\server\share\%username%). I think it considered $
252 # alone an empty var. But, we need the $ to remains there (it indicates
250 # alone an empty var. But, we need the $ to remains there (it indicates
253 # a hidden share).
251 # a hidden share).
254 if os.name=='nt':
252 if os.name=='nt':
255 s = s.replace('$\\', 'IPYTHON_TEMP')
253 s = s.replace('$\\', 'IPYTHON_TEMP')
256 s = os.path.expandvars(os.path.expanduser(s))
254 s = os.path.expandvars(os.path.expanduser(s))
257 if os.name=='nt':
255 if os.name=='nt':
258 s = s.replace('IPYTHON_TEMP', '$\\')
256 s = s.replace('IPYTHON_TEMP', '$\\')
259 return s
257 return s
260
258
261
259
262 def unescape_glob(string):
260 def unescape_glob(string):
263 """Unescape glob pattern in `string`."""
261 """Unescape glob pattern in `string`."""
264 def unescape(s):
262 def unescape(s):
265 for pattern in '*[]!?':
263 for pattern in '*[]!?':
266 s = s.replace(r'\{0}'.format(pattern), pattern)
264 s = s.replace(r'\{0}'.format(pattern), pattern)
267 return s
265 return s
268 return '\\'.join(map(unescape, string.split('\\\\')))
266 return '\\'.join(map(unescape, string.split('\\\\')))
269
267
270
268
271 def shellglob(args):
269 def shellglob(args):
272 """
270 """
273 Do glob expansion for each element in `args` and return a flattened list.
271 Do glob expansion for each element in `args` and return a flattened list.
274
272
275 Unmatched glob pattern will remain as-is in the returned list.
273 Unmatched glob pattern will remain as-is in the returned list.
276
274
277 """
275 """
278 expanded = []
276 expanded = []
279 # Do not unescape backslash in Windows as it is interpreted as
277 # Do not unescape backslash in Windows as it is interpreted as
280 # path separator:
278 # path separator:
281 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
279 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
282 for a in args:
280 for a in args:
283 expanded.extend(glob.glob(a) or [unescape(a)])
281 expanded.extend(glob.glob(a) or [unescape(a)])
284 return expanded
282 return expanded
285
283
286
284
287 def target_outdated(target,deps):
285 def target_outdated(target,deps):
288 """Determine whether a target is out of date.
286 """Determine whether a target is out of date.
289
287
290 target_outdated(target,deps) -> 1/0
288 target_outdated(target,deps) -> 1/0
291
289
292 deps: list of filenames which MUST exist.
290 deps: list of filenames which MUST exist.
293 target: single filename which may or may not exist.
291 target: single filename which may or may not exist.
294
292
295 If target doesn't exist or is older than any file listed in deps, return
293 If target doesn't exist or is older than any file listed in deps, return
296 true, otherwise return false.
294 true, otherwise return false.
297 """
295 """
298 try:
296 try:
299 target_time = os.path.getmtime(target)
297 target_time = os.path.getmtime(target)
300 except os.error:
298 except os.error:
301 return 1
299 return 1
302 for dep in deps:
300 for dep in deps:
303 dep_time = os.path.getmtime(dep)
301 dep_time = os.path.getmtime(dep)
304 if dep_time > target_time:
302 if dep_time > target_time:
305 #print "For target",target,"Dep failed:",dep # dbg
303 #print "For target",target,"Dep failed:",dep # dbg
306 #print "times (dep,tar):",dep_time,target_time # dbg
304 #print "times (dep,tar):",dep_time,target_time # dbg
307 return 1
305 return 1
308 return 0
306 return 0
309
307
310
308
311 def target_update(target,deps,cmd):
309 def target_update(target,deps,cmd):
312 """Update a target with a given command given a list of dependencies.
310 """Update a target with a given command given a list of dependencies.
313
311
314 target_update(target,deps,cmd) -> runs cmd if target is outdated.
312 target_update(target,deps,cmd) -> runs cmd if target is outdated.
315
313
316 This is just a wrapper around target_outdated() which calls the given
314 This is just a wrapper around target_outdated() which calls the given
317 command if target is outdated."""
315 command if target is outdated."""
318
316
319 if target_outdated(target,deps):
317 if target_outdated(target,deps):
320 system(cmd)
318 system(cmd)
321
319
322
320
323 ENOLINK = 1998
321 ENOLINK = 1998
324
322
325 def link(src, dst):
323 def link(src, dst):
326 """Hard links ``src`` to ``dst``, returning 0 or errno.
324 """Hard links ``src`` to ``dst``, returning 0 or errno.
327
325
328 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
326 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
329 supported by the operating system.
327 supported by the operating system.
330 """
328 """
331
329
332 if not hasattr(os, "link"):
330 if not hasattr(os, "link"):
333 return ENOLINK
331 return ENOLINK
334 link_errno = 0
332 link_errno = 0
335 try:
333 try:
336 os.link(src, dst)
334 os.link(src, dst)
337 except OSError as e:
335 except OSError as e:
338 link_errno = e.errno
336 link_errno = e.errno
339 return link_errno
337 return link_errno
340
338
341
339
342 def link_or_copy(src, dst):
340 def link_or_copy(src, dst):
343 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
341 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
344
342
345 Attempts to maintain the semantics of ``shutil.copy``.
343 Attempts to maintain the semantics of ``shutil.copy``.
346
344
347 Because ``os.link`` does not overwrite files, a unique temporary file
345 Because ``os.link`` does not overwrite files, a unique temporary file
348 will be used if the target already exists, then that file will be moved
346 will be used if the target already exists, then that file will be moved
349 into place.
347 into place.
350 """
348 """
351
349
352 if os.path.isdir(dst):
350 if os.path.isdir(dst):
353 dst = os.path.join(dst, os.path.basename(src))
351 dst = os.path.join(dst, os.path.basename(src))
354
352
355 link_errno = link(src, dst)
353 link_errno = link(src, dst)
356 if link_errno == errno.EEXIST:
354 if link_errno == errno.EEXIST:
357 if os.stat(src).st_ino == os.stat(dst).st_ino:
355 if os.stat(src).st_ino == os.stat(dst).st_ino:
358 # dst is already a hard link to the correct file, so we don't need
356 # dst is already a hard link to the correct file, so we don't need
359 # to do anything else. If we try to link and rename the file
357 # to do anything else. If we try to link and rename the file
360 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
358 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
361 return
359 return
362
360
363 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
361 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
364 try:
362 try:
365 link_or_copy(src, new_dst)
363 link_or_copy(src, new_dst)
366 except:
364 except:
367 try:
365 try:
368 os.remove(new_dst)
366 os.remove(new_dst)
369 except OSError:
367 except OSError:
370 pass
368 pass
371 raise
369 raise
372 os.rename(new_dst, dst)
370 os.rename(new_dst, dst)
373 elif link_errno != 0:
371 elif link_errno != 0:
374 # Either link isn't supported, or the filesystem doesn't support
372 # Either link isn't supported, or the filesystem doesn't support
375 # linking, or 'src' and 'dst' are on different filesystems.
373 # linking, or 'src' and 'dst' are on different filesystems.
376 shutil.copy(src, dst)
374 shutil.copy(src, dst)
377
375
378 def ensure_dir_exists(path, mode=0o755):
376 def ensure_dir_exists(path, mode=0o755):
379 """ensure that a directory exists
377 """ensure that a directory exists
380
378
381 If it doesn't exist, try to create it and protect against a race condition
379 If it doesn't exist, try to create it and protect against a race condition
382 if another process is doing the same.
380 if another process is doing the same.
383
381
384 The default permissions are 755, which differ from os.makedirs default of 777.
382 The default permissions are 755, which differ from os.makedirs default of 777.
385 """
383 """
386 if not os.path.exists(path):
384 if not os.path.exists(path):
387 try:
385 try:
388 os.makedirs(path, mode=mode)
386 os.makedirs(path, mode=mode)
389 except OSError as e:
387 except OSError as e:
390 if e.errno != errno.EEXIST:
388 if e.errno != errno.EEXIST:
391 raise
389 raise
392 elif not os.path.isdir(path):
390 elif not os.path.isdir(path):
393 raise IOError("%r exists but is not a directory" % path)
391 raise IOError("%r exists but is not a directory" % path)
@@ -1,61 +1,60 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for io.py"""
2 """Tests for io.py"""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7
7
8 import sys
8 import sys
9 from io import StringIO
9 from io import StringIO
10
10
11 from subprocess import Popen, PIPE
12 import unittest
11 import unittest
13
12
14 from IPython.utils.io import Tee, capture_output
13 from IPython.utils.io import Tee, capture_output
15
14
16
15
17 def test_tee_simple():
16 def test_tee_simple():
18 "Very simple check with stdout only"
17 "Very simple check with stdout only"
19 chan = StringIO()
18 chan = StringIO()
20 text = 'Hello'
19 text = 'Hello'
21 tee = Tee(chan, channel='stdout')
20 tee = Tee(chan, channel='stdout')
22 print(text, file=chan)
21 print(text, file=chan)
23 assert chan.getvalue() == text + "\n"
22 assert chan.getvalue() == text + "\n"
24
23
25
24
26 class TeeTestCase(unittest.TestCase):
25 class TeeTestCase(unittest.TestCase):
27
26
28 def tchan(self, channel):
27 def tchan(self, channel):
29 trap = StringIO()
28 trap = StringIO()
30 chan = StringIO()
29 chan = StringIO()
31 text = 'Hello'
30 text = 'Hello'
32
31
33 std_ori = getattr(sys, channel)
32 std_ori = getattr(sys, channel)
34 setattr(sys, channel, trap)
33 setattr(sys, channel, trap)
35
34
36 tee = Tee(chan, channel=channel)
35 tee = Tee(chan, channel=channel)
37
36
38 print(text, end='', file=chan)
37 print(text, end='', file=chan)
39 trap_val = trap.getvalue()
38 trap_val = trap.getvalue()
40 self.assertEqual(chan.getvalue(), text)
39 self.assertEqual(chan.getvalue(), text)
41
40
42 tee.close()
41 tee.close()
43
42
44 setattr(sys, channel, std_ori)
43 setattr(sys, channel, std_ori)
45 assert getattr(sys, channel) == std_ori
44 assert getattr(sys, channel) == std_ori
46
45
47 def test(self):
46 def test(self):
48 for chan in ['stdout', 'stderr']:
47 for chan in ['stdout', 'stderr']:
49 self.tchan(chan)
48 self.tchan(chan)
50
49
51 class TestIOStream(unittest.TestCase):
50 class TestIOStream(unittest.TestCase):
52
51
53 def test_capture_output(self):
52 def test_capture_output(self):
54 """capture_output() context works"""
53 """capture_output() context works"""
55
54
56 with capture_output() as io:
55 with capture_output() as io:
57 print("hi, stdout")
56 print("hi, stdout")
58 print("hi, stderr", file=sys.stderr)
57 print("hi, stderr", file=sys.stderr)
59
58
60 self.assertEqual(io.stdout, "hi, stdout\n")
59 self.assertEqual(io.stdout, "hi, stdout\n")
61 self.assertEqual(io.stderr, "hi, stderr\n")
60 self.assertEqual(io.stderr, "hi, stderr\n")
@@ -1,109 +1,107 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.utils.module_paths.py"""
2 """Tests for IPython.utils.module_paths.py"""
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2008-2011 The IPython Development Team
5 # Copyright (C) 2008-2011 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 import shutil
15 import shutil
16 import sys
16 import sys
17 import tempfile
17 import tempfile
18
18
19 from pathlib import Path
19 from pathlib import Path
20
20
21 from IPython.testing.tools import make_tempfile
22
23 import IPython.utils.module_paths as mp
21 import IPython.utils.module_paths as mp
24
22
25 TEST_FILE_PATH = Path(__file__).resolve().parent
23 TEST_FILE_PATH = Path(__file__).resolve().parent
26
24
27 TMP_TEST_DIR = Path(tempfile.mkdtemp(suffix="with.dot"))
25 TMP_TEST_DIR = Path(tempfile.mkdtemp(suffix="with.dot"))
28 #
26 #
29 # Setup/teardown functions/decorators
27 # Setup/teardown functions/decorators
30 #
28 #
31
29
32 old_syspath = sys.path
30 old_syspath = sys.path
33
31
34 def make_empty_file(fname):
32 def make_empty_file(fname):
35 open(fname, "w", encoding="utf-8").close()
33 open(fname, "w", encoding="utf-8").close()
36
34
37
35
38 def setup_module():
36 def setup_module():
39 """Setup testenvironment for the module:
37 """Setup testenvironment for the module:
40
38
41 """
39 """
42 # Do not mask exceptions here. In particular, catching WindowsError is a
40 # Do not mask exceptions here. In particular, catching WindowsError is a
43 # problem because that exception is only defined on Windows...
41 # problem because that exception is only defined on Windows...
44 Path(TMP_TEST_DIR / "xmod").mkdir(parents=True)
42 Path(TMP_TEST_DIR / "xmod").mkdir(parents=True)
45 Path(TMP_TEST_DIR / "nomod").mkdir(parents=True)
43 Path(TMP_TEST_DIR / "nomod").mkdir(parents=True)
46 make_empty_file(TMP_TEST_DIR / "xmod/__init__.py")
44 make_empty_file(TMP_TEST_DIR / "xmod/__init__.py")
47 make_empty_file(TMP_TEST_DIR / "xmod/sub.py")
45 make_empty_file(TMP_TEST_DIR / "xmod/sub.py")
48 make_empty_file(TMP_TEST_DIR / "pack.py")
46 make_empty_file(TMP_TEST_DIR / "pack.py")
49 make_empty_file(TMP_TEST_DIR / "packpyc.pyc")
47 make_empty_file(TMP_TEST_DIR / "packpyc.pyc")
50 sys.path = [str(TMP_TEST_DIR)]
48 sys.path = [str(TMP_TEST_DIR)]
51
49
52 def teardown_module():
50 def teardown_module():
53 """Teardown testenvironment for the module:
51 """Teardown testenvironment for the module:
54
52
55 - Remove tempdir
53 - Remove tempdir
56 - restore sys.path
54 - restore sys.path
57 """
55 """
58 # Note: we remove the parent test dir, which is the root of all test
56 # Note: we remove the parent test dir, which is the root of all test
59 # subdirs we may have created. Use shutil instead of os.removedirs, so
57 # subdirs we may have created. Use shutil instead of os.removedirs, so
60 # that non-empty directories are all recursively removed.
58 # that non-empty directories are all recursively removed.
61 shutil.rmtree(TMP_TEST_DIR)
59 shutil.rmtree(TMP_TEST_DIR)
62 sys.path = old_syspath
60 sys.path = old_syspath
63
61
64 def test_tempdir():
62 def test_tempdir():
65 """
63 """
66 Ensure the test are done with a temporary file that have a dot somewhere.
64 Ensure the test are done with a temporary file that have a dot somewhere.
67 """
65 """
68 assert "." in str(TMP_TEST_DIR)
66 assert "." in str(TMP_TEST_DIR)
69
67
70
68
71 def test_find_mod_1():
69 def test_find_mod_1():
72 """
70 """
73 Search for a directory's file path.
71 Search for a directory's file path.
74 Expected output: a path to that directory's __init__.py file.
72 Expected output: a path to that directory's __init__.py file.
75 """
73 """
76 modpath = TMP_TEST_DIR / "xmod" / "__init__.py"
74 modpath = TMP_TEST_DIR / "xmod" / "__init__.py"
77 assert Path(mp.find_mod("xmod")) == modpath
75 assert Path(mp.find_mod("xmod")) == modpath
78
76
79 def test_find_mod_2():
77 def test_find_mod_2():
80 """
78 """
81 Search for a directory's file path.
79 Search for a directory's file path.
82 Expected output: a path to that directory's __init__.py file.
80 Expected output: a path to that directory's __init__.py file.
83 TODO: Confirm why this is a duplicate test.
81 TODO: Confirm why this is a duplicate test.
84 """
82 """
85 modpath = TMP_TEST_DIR / "xmod" / "__init__.py"
83 modpath = TMP_TEST_DIR / "xmod" / "__init__.py"
86 assert Path(mp.find_mod("xmod")) == modpath
84 assert Path(mp.find_mod("xmod")) == modpath
87
85
88 def test_find_mod_3():
86 def test_find_mod_3():
89 """
87 """
90 Search for a directory + a filename without its .py extension
88 Search for a directory + a filename without its .py extension
91 Expected output: full path with .py extension.
89 Expected output: full path with .py extension.
92 """
90 """
93 modpath = TMP_TEST_DIR / "xmod" / "sub.py"
91 modpath = TMP_TEST_DIR / "xmod" / "sub.py"
94 assert Path(mp.find_mod("xmod.sub")) == modpath
92 assert Path(mp.find_mod("xmod.sub")) == modpath
95
93
96 def test_find_mod_4():
94 def test_find_mod_4():
97 """
95 """
98 Search for a filename without its .py extension
96 Search for a filename without its .py extension
99 Expected output: full path with .py extension
97 Expected output: full path with .py extension
100 """
98 """
101 modpath = TMP_TEST_DIR / "pack.py"
99 modpath = TMP_TEST_DIR / "pack.py"
102 assert Path(mp.find_mod("pack")) == modpath
100 assert Path(mp.find_mod("pack")) == modpath
103
101
104 def test_find_mod_5():
102 def test_find_mod_5():
105 """
103 """
106 Search for a filename with a .pyc extension
104 Search for a filename with a .pyc extension
107 Expected output: TODO: do we exclude or include .pyc files?
105 Expected output: TODO: do we exclude or include .pyc files?
108 """
106 """
109 assert mp.find_mod("packpyc") == None
107 assert mp.find_mod("packpyc") == None
@@ -1,208 +1,207 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.utils.text"""
2 """Tests for IPython.utils.text"""
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2011 The IPython Development Team
5 # Copyright (C) 2011 The IPython Development Team
6 #
6 #
7 # Distributed under the terms of the BSD License. The full license is in
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 import os
15 import os
16 import math
16 import math
17 import random
17 import random
18 import sys
19
18
20 from pathlib import Path
19 from pathlib import Path
21
20
22 import pytest
21 import pytest
23
22
24 from IPython.utils import text
23 from IPython.utils import text
25
24
26 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
27 # Globals
26 # Globals
28 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
29
28
30 def test_columnize():
29 def test_columnize():
31 """Basic columnize tests."""
30 """Basic columnize tests."""
32 size = 5
31 size = 5
33 items = [l*size for l in 'abcd']
32 items = [l*size for l in 'abcd']
34
33
35 out = text.columnize(items, displaywidth=80)
34 out = text.columnize(items, displaywidth=80)
36 assert out == "aaaaa bbbbb ccccc ddddd\n"
35 assert out == "aaaaa bbbbb ccccc ddddd\n"
37 out = text.columnize(items, displaywidth=25)
36 out = text.columnize(items, displaywidth=25)
38 assert out == "aaaaa ccccc\nbbbbb ddddd\n"
37 assert out == "aaaaa ccccc\nbbbbb ddddd\n"
39 out = text.columnize(items, displaywidth=12)
38 out = text.columnize(items, displaywidth=12)
40 assert out == "aaaaa ccccc\nbbbbb ddddd\n"
39 assert out == "aaaaa ccccc\nbbbbb ddddd\n"
41 out = text.columnize(items, displaywidth=10)
40 out = text.columnize(items, displaywidth=10)
42 assert out == "aaaaa\nbbbbb\nccccc\nddddd\n"
41 assert out == "aaaaa\nbbbbb\nccccc\nddddd\n"
43
42
44 out = text.columnize(items, row_first=True, displaywidth=80)
43 out = text.columnize(items, row_first=True, displaywidth=80)
45 assert out == "aaaaa bbbbb ccccc ddddd\n"
44 assert out == "aaaaa bbbbb ccccc ddddd\n"
46 out = text.columnize(items, row_first=True, displaywidth=25)
45 out = text.columnize(items, row_first=True, displaywidth=25)
47 assert out == "aaaaa bbbbb\nccccc ddddd\n"
46 assert out == "aaaaa bbbbb\nccccc ddddd\n"
48 out = text.columnize(items, row_first=True, displaywidth=12)
47 out = text.columnize(items, row_first=True, displaywidth=12)
49 assert out == "aaaaa bbbbb\nccccc ddddd\n"
48 assert out == "aaaaa bbbbb\nccccc ddddd\n"
50 out = text.columnize(items, row_first=True, displaywidth=10)
49 out = text.columnize(items, row_first=True, displaywidth=10)
51 assert out == "aaaaa\nbbbbb\nccccc\nddddd\n"
50 assert out == "aaaaa\nbbbbb\nccccc\nddddd\n"
52
51
53 out = text.columnize(items, displaywidth=40, spread=True)
52 out = text.columnize(items, displaywidth=40, spread=True)
54 assert out == "aaaaa bbbbb ccccc ddddd\n"
53 assert out == "aaaaa bbbbb ccccc ddddd\n"
55 out = text.columnize(items, displaywidth=20, spread=True)
54 out = text.columnize(items, displaywidth=20, spread=True)
56 assert out == "aaaaa ccccc\nbbbbb ddddd\n"
55 assert out == "aaaaa ccccc\nbbbbb ddddd\n"
57 out = text.columnize(items, displaywidth=12, spread=True)
56 out = text.columnize(items, displaywidth=12, spread=True)
58 assert out == "aaaaa ccccc\nbbbbb ddddd\n"
57 assert out == "aaaaa ccccc\nbbbbb ddddd\n"
59 out = text.columnize(items, displaywidth=10, spread=True)
58 out = text.columnize(items, displaywidth=10, spread=True)
60 assert out == "aaaaa\nbbbbb\nccccc\nddddd\n"
59 assert out == "aaaaa\nbbbbb\nccccc\nddddd\n"
61
60
62
61
63 def test_columnize_random():
62 def test_columnize_random():
64 """Test with random input to hopefully catch edge case """
63 """Test with random input to hopefully catch edge case """
65 for row_first in [True, False]:
64 for row_first in [True, False]:
66 for nitems in [random.randint(2,70) for i in range(2,20)]:
65 for nitems in [random.randint(2,70) for i in range(2,20)]:
67 displaywidth = random.randint(20,200)
66 displaywidth = random.randint(20,200)
68 rand_len = [random.randint(2,displaywidth) for i in range(nitems)]
67 rand_len = [random.randint(2,displaywidth) for i in range(nitems)]
69 items = ['x'*l for l in rand_len]
68 items = ['x'*l for l in rand_len]
70 out = text.columnize(items, row_first=row_first, displaywidth=displaywidth)
69 out = text.columnize(items, row_first=row_first, displaywidth=displaywidth)
71 longer_line = max([len(x) for x in out.split('\n')])
70 longer_line = max([len(x) for x in out.split('\n')])
72 longer_element = max(rand_len)
71 longer_element = max(rand_len)
73 assert longer_line <= displaywidth, (
72 assert longer_line <= displaywidth, (
74 f"Columnize displayed something lager than displaywidth : {longer_line}\n"
73 f"Columnize displayed something lager than displaywidth : {longer_line}\n"
75 f"longer element : {longer_element}\n"
74 f"longer element : {longer_element}\n"
76 f"displaywidth : {displaywidth}\n"
75 f"displaywidth : {displaywidth}\n"
77 f"number of element : {nitems}\n"
76 f"number of element : {nitems}\n"
78 f"size of each element : {rand_len}\n"
77 f"size of each element : {rand_len}\n"
79 f"row_first={row_first}\n"
78 f"row_first={row_first}\n"
80 )
79 )
81
80
82
81
83 @pytest.mark.parametrize("row_first", [True, False])
82 @pytest.mark.parametrize("row_first", [True, False])
84 def test_columnize_medium(row_first):
83 def test_columnize_medium(row_first):
85 """Test with inputs than shouldn't be wider than 80"""
84 """Test with inputs than shouldn't be wider than 80"""
86 size = 40
85 size = 40
87 items = [l*size for l in 'abc']
86 items = [l*size for l in 'abc']
88 out = text.columnize(items, row_first=row_first, displaywidth=80)
87 out = text.columnize(items, row_first=row_first, displaywidth=80)
89 assert out == "\n".join(items + [""]), "row_first={0}".format(row_first)
88 assert out == "\n".join(items + [""]), "row_first={0}".format(row_first)
90
89
91
90
92 @pytest.mark.parametrize("row_first", [True, False])
91 @pytest.mark.parametrize("row_first", [True, False])
93 def test_columnize_long(row_first):
92 def test_columnize_long(row_first):
94 """Test columnize with inputs longer than the display window"""
93 """Test columnize with inputs longer than the display window"""
95 size = 11
94 size = 11
96 items = [l*size for l in 'abc']
95 items = [l*size for l in 'abc']
97 out = text.columnize(items, row_first=row_first, displaywidth=size - 1)
96 out = text.columnize(items, row_first=row_first, displaywidth=size - 1)
98 assert out == "\n".join(items + [""]), "row_first={0}".format(row_first)
97 assert out == "\n".join(items + [""]), "row_first={0}".format(row_first)
99
98
100
99
101 def eval_formatter_check(f):
100 def eval_formatter_check(f):
102 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os, u=u"cafΓ©", b="cafΓ©")
101 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os, u=u"cafΓ©", b="cafΓ©")
103 s = f.format("{n} {n//4} {stuff.split()[0]}", **ns)
102 s = f.format("{n} {n//4} {stuff.split()[0]}", **ns)
104 assert s == "12 3 hello"
103 assert s == "12 3 hello"
105 s = f.format(" ".join(["{n//%i}" % i for i in range(1, 8)]), **ns)
104 s = f.format(" ".join(["{n//%i}" % i for i in range(1, 8)]), **ns)
106 assert s == "12 6 4 3 2 2 1"
105 assert s == "12 6 4 3 2 2 1"
107 s = f.format("{[n//i for i in range(1,8)]}", **ns)
106 s = f.format("{[n//i for i in range(1,8)]}", **ns)
108 assert s == "[12, 6, 4, 3, 2, 2, 1]"
107 assert s == "[12, 6, 4, 3, 2, 2, 1]"
109 s = f.format("{stuff!s}", **ns)
108 s = f.format("{stuff!s}", **ns)
110 assert s == ns["stuff"]
109 assert s == ns["stuff"]
111 s = f.format("{stuff!r}", **ns)
110 s = f.format("{stuff!r}", **ns)
112 assert s == repr(ns["stuff"])
111 assert s == repr(ns["stuff"])
113
112
114 # Check with unicode:
113 # Check with unicode:
115 s = f.format("{u}", **ns)
114 s = f.format("{u}", **ns)
116 assert s == ns["u"]
115 assert s == ns["u"]
117 # This decodes in a platform dependent manner, but it shouldn't error out
116 # This decodes in a platform dependent manner, but it shouldn't error out
118 s = f.format("{b}", **ns)
117 s = f.format("{b}", **ns)
119
118
120 pytest.raises(NameError, f.format, "{dne}", **ns)
119 pytest.raises(NameError, f.format, "{dne}", **ns)
121
120
122
121
123 def eval_formatter_slicing_check(f):
122 def eval_formatter_slicing_check(f):
124 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
123 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
125 s = f.format(" {stuff.split()[:]} ", **ns)
124 s = f.format(" {stuff.split()[:]} ", **ns)
126 assert s == " ['hello', 'there'] "
125 assert s == " ['hello', 'there'] "
127 s = f.format(" {stuff.split()[::-1]} ", **ns)
126 s = f.format(" {stuff.split()[::-1]} ", **ns)
128 assert s == " ['there', 'hello'] "
127 assert s == " ['there', 'hello'] "
129 s = f.format("{stuff[::2]}", **ns)
128 s = f.format("{stuff[::2]}", **ns)
130 assert s == ns["stuff"][::2]
129 assert s == ns["stuff"][::2]
131
130
132 pytest.raises(SyntaxError, f.format, "{n:x}", **ns)
131 pytest.raises(SyntaxError, f.format, "{n:x}", **ns)
133
132
134 def eval_formatter_no_slicing_check(f):
133 def eval_formatter_no_slicing_check(f):
135 ns = dict(n=12, pi=math.pi, stuff="hello there", os=os)
134 ns = dict(n=12, pi=math.pi, stuff="hello there", os=os)
136
135
137 s = f.format("{n:x} {pi**2:+f}", **ns)
136 s = f.format("{n:x} {pi**2:+f}", **ns)
138 assert s == "c +9.869604"
137 assert s == "c +9.869604"
139
138
140 s = f.format("{stuff[slice(1,4)]}", **ns)
139 s = f.format("{stuff[slice(1,4)]}", **ns)
141 assert s == "ell"
140 assert s == "ell"
142
141
143 s = f.format("{a[:]}", a=[1, 2])
142 s = f.format("{a[:]}", a=[1, 2])
144 assert s == "[1, 2]"
143 assert s == "[1, 2]"
145
144
146 def test_eval_formatter():
145 def test_eval_formatter():
147 f = text.EvalFormatter()
146 f = text.EvalFormatter()
148 eval_formatter_check(f)
147 eval_formatter_check(f)
149 eval_formatter_no_slicing_check(f)
148 eval_formatter_no_slicing_check(f)
150
149
151 def test_full_eval_formatter():
150 def test_full_eval_formatter():
152 f = text.FullEvalFormatter()
151 f = text.FullEvalFormatter()
153 eval_formatter_check(f)
152 eval_formatter_check(f)
154 eval_formatter_slicing_check(f)
153 eval_formatter_slicing_check(f)
155
154
156 def test_dollar_formatter():
155 def test_dollar_formatter():
157 f = text.DollarFormatter()
156 f = text.DollarFormatter()
158 eval_formatter_check(f)
157 eval_formatter_check(f)
159 eval_formatter_slicing_check(f)
158 eval_formatter_slicing_check(f)
160
159
161 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
160 ns = dict(n=12, pi=math.pi, stuff='hello there', os=os)
162 s = f.format("$n", **ns)
161 s = f.format("$n", **ns)
163 assert s == "12"
162 assert s == "12"
164 s = f.format("$n.real", **ns)
163 s = f.format("$n.real", **ns)
165 assert s == "12"
164 assert s == "12"
166 s = f.format("$n/{stuff[:5]}", **ns)
165 s = f.format("$n/{stuff[:5]}", **ns)
167 assert s == "12/hello"
166 assert s == "12/hello"
168 s = f.format("$n $$HOME", **ns)
167 s = f.format("$n $$HOME", **ns)
169 assert s == "12 $HOME"
168 assert s == "12 $HOME"
170 s = f.format("${foo}", foo="HOME")
169 s = f.format("${foo}", foo="HOME")
171 assert s == "$HOME"
170 assert s == "$HOME"
172
171
173
172
174 def test_strip_email():
173 def test_strip_email():
175 src = """\
174 src = """\
176 >> >>> def f(x):
175 >> >>> def f(x):
177 >> ... return x+1
176 >> ... return x+1
178 >> ...
177 >> ...
179 >> >>> zz = f(2.5)"""
178 >> >>> zz = f(2.5)"""
180 cln = """\
179 cln = """\
181 >>> def f(x):
180 >>> def f(x):
182 ... return x+1
181 ... return x+1
183 ...
182 ...
184 >>> zz = f(2.5)"""
183 >>> zz = f(2.5)"""
185 assert text.strip_email_quotes(src) == cln
184 assert text.strip_email_quotes(src) == cln
186
185
187
186
188 def test_strip_email2():
187 def test_strip_email2():
189 src = "> > > list()"
188 src = "> > > list()"
190 cln = "list()"
189 cln = "list()"
191 assert text.strip_email_quotes(src) == cln
190 assert text.strip_email_quotes(src) == cln
192
191
193
192
194 def test_LSString():
193 def test_LSString():
195 lss = text.LSString("abc\ndef")
194 lss = text.LSString("abc\ndef")
196 assert lss.l == ["abc", "def"]
195 assert lss.l == ["abc", "def"]
197 assert lss.s == "abc def"
196 assert lss.s == "abc def"
198 lss = text.LSString(os.getcwd())
197 lss = text.LSString(os.getcwd())
199 assert isinstance(lss.p[0], Path)
198 assert isinstance(lss.p[0], Path)
200
199
201
200
202 def test_SList():
201 def test_SList():
203 sl = text.SList(["a 11", "b 1", "a 2"])
202 sl = text.SList(["a 11", "b 1", "a 2"])
204 assert sl.n == "a 11\nb 1\na 2"
203 assert sl.n == "a 11\nb 1\na 2"
205 assert sl.s == "a 11 b 1 a 2"
204 assert sl.s == "a 11 b 1 a 2"
206 assert sl.grep(lambda x: x.startswith("a")) == text.SList(["a 11", "a 2"])
205 assert sl.grep(lambda x: x.startswith("a")) == text.SList(["a 11", "a 2"])
207 assert sl.fields(0) == text.SList(["a", "b", "a"])
206 assert sl.fields(0) == text.SList(["a", "b", "a"])
208 assert sl.sort(field=1, nums=True) == text.SList(["b 1", "a 2", "a 11"])
207 assert sl.sort(field=1, nums=True) == text.SList(["b 1", "a 2", "a 11"])
General Comments 0
You need to be logged in to leave comments. Login now