##// END OF EJS Templates
typo and reformat
Matthias Bussonnier -
Show More

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

@@ -1,155 +1,156 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
21 import os
22 import sys
22 import sys
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Setup everything
25 # Setup everything
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 # Don't forget to also update setup.py when this changes!
28 # Don't forget to also update setup.py when this changes!
29 if sys.version_info < (3, 8):
29 if sys.version_info < (3, 8):
30 raise ImportError(
30 raise ImportError(
31 """
31 """
32 IPython 8+ supports Python 3.8 and above, following NEP 29.
32 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.
33 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.
34 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.
35 Python 3.5 was supported with IPython 7.0 to 7.9.
36 Python 3.6 was supported with IPython up to 7.16.
36 Python 3.6 was supported with IPython up to 7.16.
37 Python 3.7 was still supported with the 7.x branch.
37 Python 3.7 was still supported with the 7.x branch.
38
38
39 See IPython `README.rst` file for more information:
39 See IPython `README.rst` file for more information:
40
40
41 https://github.com/ipython/ipython/blob/master/README.rst
41 https://github.com/ipython/ipython/blob/master/README.rst
42
42
43 """)
43 """
44 )
44
45
45 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
46 # Setup the top level names
47 # Setup the top level names
47 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
48
49
49 from .core.getipython import get_ipython
50 from .core.getipython import get_ipython
50 from .core import release
51 from .core import release
51 from .core.application import Application
52 from .core.application import Application
52 from .terminal.embed import embed
53 from .terminal.embed import embed
53
54
54 from .core.interactiveshell import InteractiveShell
55 from .core.interactiveshell import InteractiveShell
55 from .utils.sysinfo import sys_info
56 from .utils.sysinfo import sys_info
56 from .utils.frame import extract_module_locals
57 from .utils.frame import extract_module_locals
57
58
58 # Release data
59 # Release data
59 __author__ = '%s <%s>' % (release.author, release.author_email)
60 __author__ = '%s <%s>' % (release.author, release.author_email)
60 __license__ = release.license
61 __license__ = release.license
61 __version__ = release.version
62 __version__ = release.version
62 version_info = release.version_info
63 version_info = release.version_info
63 # list of CVEs that should have been patched in this release.
64 # list of CVEs that should have been patched in this release.
64 # this is informational and should not be relied upon.
65 # this is informational and should not be relied upon.
65 __patched_cves__ = {"CVE-2022-21699"}
66 __patched_cves__ = {"CVE-2022-21699"}
66
67
67
68
68 def embed_kernel(module=None, local_ns=None, **kwargs):
69 def embed_kernel(module=None, local_ns=None, **kwargs):
69 """Embed and start an IPython kernel in a given scope.
70 """Embed and start an IPython kernel in a given scope.
70
71
71 If you don't want the kernel to initialize the namespace
72 If you don't want the kernel to initialize the namespace
72 from the scope of the surrounding function,
73 from the scope of the surrounding function,
73 and/or you want to load full IPython configuration,
74 and/or you want to load full IPython configuration,
74 you probably want `IPython.start_kernel()` instead.
75 you probably want `IPython.start_kernel()` instead.
75
76
76 Parameters
77 Parameters
77 ----------
78 ----------
78 module : types.ModuleType, optional
79 module : types.ModuleType, optional
79 The module to load into IPython globals (default: caller)
80 The module to load into IPython globals (default: caller)
80 local_ns : dict, optional
81 local_ns : dict, optional
81 The namespace to load into IPython user namespace (default: caller)
82 The namespace to load into IPython user namespace (default: caller)
82 **kwargs : various, optional
83 **kwargs : various, optional
83 Further keyword args are relayed to the IPKernelApp constructor,
84 Further keyword args are relayed to the IPKernelApp constructor,
84 allowing configuration of the Kernel. Will only have an effect
85 allowing configuration of the Kernel. Will only have an effect
85 on the first embed_kernel call for a given process.
86 on the first embed_kernel call for a given process.
86 """
87 """
87
88
88 (caller_module, caller_locals) = extract_module_locals(1)
89 (caller_module, caller_locals) = extract_module_locals(1)
89 if module is None:
90 if module is None:
90 module = caller_module
91 module = caller_module
91 if local_ns is None:
92 if local_ns is None:
92 local_ns = caller_locals
93 local_ns = caller_locals
93
94
94 # Only import .zmq when we really need it
95 # Only import .zmq when we really need it
95 from ipykernel.embed import embed_kernel as real_embed_kernel
96 from ipykernel.embed import embed_kernel as real_embed_kernel
96 real_embed_kernel(module=module, local_ns=local_ns, **kwargs)
97 real_embed_kernel(module=module, local_ns=local_ns, **kwargs)
97
98
98 def start_ipython(argv=None, **kwargs):
99 def start_ipython(argv=None, **kwargs):
99 """Launch a normal IPython instance (as opposed to embedded)
100 """Launch a normal IPython instance (as opposed to embedded)
100
101
101 `IPython.embed()` puts a shell in a particular calling scope,
102 `IPython.embed()` puts a shell in a particular calling scope,
102 such as a function or method for debugging purposes,
103 such as a function or method for debugging purposes,
103 which is often not desirable.
104 which is often not desirable.
104
105
105 `start_ipython()` does full, regular IPython initialization,
106 `start_ipython()` does full, regular IPython initialization,
106 including loading startup files, configuration, etc.
107 including loading startup files, configuration, etc.
107 much of which is skipped by `embed()`.
108 much of which is skipped by `embed()`.
108
109
109 This is a public API method, and will survive implementation changes.
110 This is a public API method, and will survive implementation changes.
110
111
111 Parameters
112 Parameters
112 ----------
113 ----------
113 argv : list or None, optional
114 argv : list or None, optional
114 If unspecified or None, IPython will parse command-line options from sys.argv.
115 If unspecified or None, IPython will parse command-line options from sys.argv.
115 To prevent any command-line parsing, pass an empty list: `argv=[]`.
116 To prevent any command-line parsing, pass an empty list: `argv=[]`.
116 user_ns : dict, optional
117 user_ns : dict, optional
117 specify this dictionary to initialize the IPython user namespace with particular values.
118 specify this dictionary to initialize the IPython user namespace with particular values.
118 **kwargs : various, optional
119 **kwargs : various, optional
119 Any other kwargs will be passed to the Application constructor,
120 Any other kwargs will be passed to the Application constructor,
120 such as `config`.
121 such as `config`.
121 """
122 """
122 from IPython.terminal.ipapp import launch_new_instance
123 from IPython.terminal.ipapp import launch_new_instance
123 return launch_new_instance(argv=argv, **kwargs)
124 return launch_new_instance(argv=argv, **kwargs)
124
125
125 def start_kernel(argv=None, **kwargs):
126 def start_kernel(argv=None, **kwargs):
126 """Launch a normal IPython kernel instance (as opposed to embedded)
127 """Launch a normal IPython kernel instance (as opposed to embedded)
127
128
128 `IPython.embed_kernel()` puts a shell in a particular calling scope,
129 `IPython.embed_kernel()` puts a shell in a particular calling scope,
129 such as a function or method for debugging purposes,
130 such as a function or method for debugging purposes,
130 which is often not desirable.
131 which is often not desirable.
131
132
132 `start_kernel()` does full, regular IPython initialization,
133 `start_kernel()` does full, regular IPython initialization,
133 including loading startup files, configuration, etc.
134 including loading startup files, configuration, etc.
134 much of which is skipped by `embed()`.
135 much of which is skipped by `embed()`.
135
136
136 Parameters
137 Parameters
137 ----------
138 ----------
138 argv : list or None, optional
139 argv : list or None, optional
139 If unspecified or None, IPython will parse command-line options from sys.argv.
140 If unspecified or None, IPython will parse command-line options from sys.argv.
140 To prevent any command-line parsing, pass an empty list: `argv=[]`.
141 To prevent any command-line parsing, pass an empty list: `argv=[]`.
141 user_ns : dict, optional
142 user_ns : dict, optional
142 specify this dictionary to initialize the IPython user namespace with particular values.
143 specify this dictionary to initialize the IPython user namespace with particular values.
143 **kwargs : various, optional
144 **kwargs : various, optional
144 Any other kwargs will be passed to the Application constructor,
145 Any other kwargs will be passed to the Application constructor,
145 such as `config`.
146 such as `config`.
146 """
147 """
147 import warnings
148 import warnings
148
149
149 warnings.warn(
150 warnings.warn(
150 "start_kernel is deprecated since IPython 8.0, use from `ipykernel.kernelapp.launch_new_instance`",
151 "start_kernel is deprecated since IPython 8.0, use from `ipykernel.kernelapp.launch_new_instance`",
151 DeprecationWarning,
152 DeprecationWarning,
152 stacklevel=2,
153 stacklevel=2,
153 )
154 )
154 from ipykernel.kernelapp import launch_new_instance
155 from ipykernel.kernelapp import launch_new_instance
155 return launch_new_instance(argv=argv, **kwargs)
156 return launch_new_instance(argv=argv, **kwargs)
1 NO CONTENT: modified file
NO CONTENT: modified file
@@ -1,2272 +1,2272 b''
1 """Completion for IPython.
1 """Completion for IPython.
2
2
3 This module started as fork of the rlcompleter module in the Python standard
3 This module started as fork of the rlcompleter module in the Python standard
4 library. The original enhancements made to rlcompleter have been sent
4 library. The original enhancements made to rlcompleter have been sent
5 upstream and were accepted as of Python 2.3,
5 upstream and were accepted as of Python 2.3,
6
6
7 This module now support a wide variety of completion mechanism both available
7 This module now support a wide variety of completion mechanism both available
8 for normal classic Python code, as well as completer for IPython specific
8 for normal classic Python code, as well as completer for IPython specific
9 Syntax like magics.
9 Syntax like magics.
10
10
11 Latex and Unicode completion
11 Latex and Unicode completion
12 ============================
12 ============================
13
13
14 IPython and compatible frontends not only can complete your code, but can help
14 IPython and compatible frontends not only can complete your code, but can help
15 you to input a wide range of characters. In particular we allow you to insert
15 you to input a wide range of characters. In particular we allow you to insert
16 a unicode character using the tab completion mechanism.
16 a unicode character using the tab completion mechanism.
17
17
18 Forward latex/unicode completion
18 Forward latex/unicode completion
19 --------------------------------
19 --------------------------------
20
20
21 Forward completion allows you to easily type a unicode character using its latex
21 Forward completion allows you to easily type a unicode character using its latex
22 name, or unicode long description. To do so type a backslash follow by the
22 name, or unicode long description. To do so type a backslash follow by the
23 relevant name and press tab:
23 relevant name and press tab:
24
24
25
25
26 Using latex completion:
26 Using latex completion:
27
27
28 .. code::
28 .. code::
29
29
30 \\alpha<tab>
30 \\alpha<tab>
31 Ξ±
31 Ξ±
32
32
33 or using unicode completion:
33 or using unicode completion:
34
34
35
35
36 .. code::
36 .. code::
37
37
38 \\GREEK SMALL LETTER ALPHA<tab>
38 \\GREEK SMALL LETTER ALPHA<tab>
39 Ξ±
39 Ξ±
40
40
41
41
42 Only valid Python identifiers will complete. Combining characters (like arrow or
42 Only valid Python identifiers will complete. Combining characters (like arrow or
43 dots) are also available, unlike latex they need to be put after the their
43 dots) are also available, unlike latex they need to be put after the their
44 counterpart that is to say, ``F\\\\vec<tab>`` is correct, not ``\\\\vec<tab>F``.
44 counterpart that is to say, ``F\\\\vec<tab>`` is correct, not ``\\\\vec<tab>F``.
45
45
46 Some browsers are known to display combining characters incorrectly.
46 Some browsers are known to display combining characters incorrectly.
47
47
48 Backward latex completion
48 Backward latex completion
49 -------------------------
49 -------------------------
50
50
51 It is sometime challenging to know how to type a character, if you are using
51 It is sometime challenging to know how to type a character, if you are using
52 IPython, or any compatible frontend you can prepend backslash to the character
52 IPython, or any compatible frontend you can prepend backslash to the character
53 and press ``<tab>`` to expand it to its latex form.
53 and press ``<tab>`` to expand it to its latex form.
54
54
55 .. code::
55 .. code::
56
56
57 \\Ξ±<tab>
57 \\Ξ±<tab>
58 \\alpha
58 \\alpha
59
59
60
60
61 Both forward and backward completions can be deactivated by setting the
61 Both forward and backward completions can be deactivated by setting the
62 ``Completer.backslash_combining_completions`` option to ``False``.
62 ``Completer.backslash_combining_completions`` option to ``False``.
63
63
64
64
65 Experimental
65 Experimental
66 ============
66 ============
67
67
68 Starting with IPython 6.0, this module can make use of the Jedi library to
68 Starting with IPython 6.0, this module can make use of the Jedi library to
69 generate completions both using static analysis of the code, and dynamically
69 generate completions both using static analysis of the code, and dynamically
70 inspecting multiple namespaces. Jedi is an autocompletion and static analysis
70 inspecting multiple namespaces. Jedi is an autocompletion and static analysis
71 for Python. The APIs attached to this new mechanism is unstable and will
71 for Python. The APIs attached to this new mechanism is unstable and will
72 raise unless use in an :any:`provisionalcompleter` context manager.
72 raise unless use in an :any:`provisionalcompleter` context manager.
73
73
74 You will find that the following are experimental:
74 You will find that the following are experimental:
75
75
76 - :any:`provisionalcompleter`
76 - :any:`provisionalcompleter`
77 - :any:`IPCompleter.completions`
77 - :any:`IPCompleter.completions`
78 - :any:`Completion`
78 - :any:`Completion`
79 - :any:`rectify_completions`
79 - :any:`rectify_completions`
80
80
81 .. note::
81 .. note::
82
82
83 better name for :any:`rectify_completions` ?
83 better name for :any:`rectify_completions` ?
84
84
85 We welcome any feedback on these new API, and we also encourage you to try this
85 We welcome any feedback on these new API, and we also encourage you to try this
86 module in debug mode (start IPython with ``--Completer.debug=True``) in order
86 module in debug mode (start IPython with ``--Completer.debug=True``) in order
87 to have extra logging information if :any:`jedi` is crashing, or if current
87 to have extra logging information if :any:`jedi` is crashing, or if current
88 IPython completer pending deprecations are returning results not yet handled
88 IPython completer pending deprecations are returning results not yet handled
89 by :any:`jedi`
89 by :any:`jedi`
90
90
91 Using Jedi for tab completion allow snippets like the following to work without
91 Using Jedi for tab completion allow snippets like the following to work without
92 having to execute any code:
92 having to execute any code:
93
93
94 >>> myvar = ['hello', 42]
94 >>> myvar = ['hello', 42]
95 ... myvar[1].bi<tab>
95 ... myvar[1].bi<tab>
96
96
97 Tab completion will be able to infer that ``myvar[1]`` is a real number without
97 Tab completion will be able to infer that ``myvar[1]`` is a real number without
98 executing any code unlike the previously available ``IPCompleter.greedy``
98 executing any code unlike the previously available ``IPCompleter.greedy``
99 option.
99 option.
100
100
101 Be sure to update :any:`jedi` to the latest stable version or to try the
101 Be sure to update :any:`jedi` to the latest stable version or to try the
102 current development version to get better completions.
102 current development version to get better completions.
103 """
103 """
104
104
105
105
106 # Copyright (c) IPython Development Team.
106 # Copyright (c) IPython Development Team.
107 # Distributed under the terms of the Modified BSD License.
107 # Distributed under the terms of the Modified BSD License.
108 #
108 #
109 # Some of this code originated from rlcompleter in the Python standard library
109 # Some of this code originated from rlcompleter in the Python standard library
110 # Copyright (C) 2001 Python Software Foundation, www.python.org
110 # Copyright (C) 2001 Python Software Foundation, www.python.org
111
111
112
112
113 import builtins as builtin_mod
113 import builtins as builtin_mod
114 import glob
114 import glob
115 import inspect
115 import inspect
116 import itertools
116 import itertools
117 import keyword
117 import keyword
118 import os
118 import os
119 import re
119 import re
120 import string
120 import string
121 import sys
121 import sys
122 import time
122 import time
123 import unicodedata
123 import unicodedata
124 import uuid
124 import uuid
125 import warnings
125 import warnings
126 from contextlib import contextmanager
126 from contextlib import contextmanager
127 from importlib import import_module
127 from importlib import import_module
128 from types import SimpleNamespace
128 from types import SimpleNamespace
129 from typing import Iterable, Iterator, List, Tuple, Union, Any, Sequence, Dict, NamedTuple, Pattern, Optional
129 from typing import Iterable, Iterator, List, Tuple, Union, Any, Sequence, Dict, NamedTuple, Pattern, Optional
130
130
131 from IPython.core.error import TryNext
131 from IPython.core.error import TryNext
132 from IPython.core.inputtransformer2 import ESC_MAGIC
132 from IPython.core.inputtransformer2 import ESC_MAGIC
133 from IPython.core.latex_symbols import latex_symbols, reverse_latex_symbol
133 from IPython.core.latex_symbols import latex_symbols, reverse_latex_symbol
134 from IPython.core.oinspect import InspectColors
134 from IPython.core.oinspect import InspectColors
135 from IPython.testing.skipdoctest import skip_doctest
135 from IPython.testing.skipdoctest import skip_doctest
136 from IPython.utils import generics
136 from IPython.utils import generics
137 from IPython.utils.dir2 import dir2, get_real_method
137 from IPython.utils.dir2 import dir2, get_real_method
138 from IPython.utils.path import ensure_dir_exists
138 from IPython.utils.path import ensure_dir_exists
139 from IPython.utils.process import arg_split
139 from IPython.utils.process import arg_split
140 from traitlets import Bool, Enum, Int, List as ListTrait, Unicode, default, observe
140 from traitlets import Bool, Enum, Int, List as ListTrait, Unicode, default, observe
141 from traitlets.config.configurable import Configurable
141 from traitlets.config.configurable import Configurable
142
142
143 import __main__
143 import __main__
144
144
145 # skip module docstests
145 # skip module docstests
146 __skip_doctest__ = True
146 __skip_doctest__ = True
147
147
148 try:
148 try:
149 import jedi
149 import jedi
150 jedi.settings.case_insensitive_completion = False
150 jedi.settings.case_insensitive_completion = False
151 import jedi.api.helpers
151 import jedi.api.helpers
152 import jedi.api.classes
152 import jedi.api.classes
153 JEDI_INSTALLED = True
153 JEDI_INSTALLED = True
154 except ImportError:
154 except ImportError:
155 JEDI_INSTALLED = False
155 JEDI_INSTALLED = False
156 #-----------------------------------------------------------------------------
156 #-----------------------------------------------------------------------------
157 # Globals
157 # Globals
158 #-----------------------------------------------------------------------------
158 #-----------------------------------------------------------------------------
159
159
160 # ranges where we have most of the valid unicode names. We could be more finer
160 # ranges where we have most of the valid unicode names. We could be more finer
161 # grained but is it worth it for performance While unicode have character in the
161 # grained but is it worth it for performance While unicode have character in the
162 # range 0, 0x110000, we seem to have name for about 10% of those. (131808 as I
162 # range 0, 0x110000, we seem to have name for about 10% of those. (131808 as I
163 # write this). With below range we cover them all, with a density of ~67%
163 # write this). With below range we cover them all, with a density of ~67%
164 # biggest next gap we consider only adds up about 1% density and there are 600
164 # biggest next gap we consider only adds up about 1% density and there are 600
165 # gaps that would need hard coding.
165 # gaps that would need hard coding.
166 _UNICODE_RANGES = [(32, 0x3134b), (0xe0001, 0xe01f0)]
166 _UNICODE_RANGES = [(32, 0x3134b), (0xe0001, 0xe01f0)]
167
167
168 # Public API
168 # Public API
169 __all__ = ['Completer','IPCompleter']
169 __all__ = ['Completer','IPCompleter']
170
170
171 if sys.platform == 'win32':
171 if sys.platform == 'win32':
172 PROTECTABLES = ' '
172 PROTECTABLES = ' '
173 else:
173 else:
174 PROTECTABLES = ' ()[]{}?=\\|;:\'#*"^&'
174 PROTECTABLES = ' ()[]{}?=\\|;:\'#*"^&'
175
175
176 # Protect against returning an enormous number of completions which the frontend
176 # Protect against returning an enormous number of completions which the frontend
177 # may have trouble processing.
177 # may have trouble processing.
178 MATCHES_LIMIT = 500
178 MATCHES_LIMIT = 500
179
179
180
180
181 class ProvisionalCompleterWarning(FutureWarning):
181 class ProvisionalCompleterWarning(FutureWarning):
182 """
182 """
183 Exception raise by an experimental feature in this module.
183 Exception raise by an experimental feature in this module.
184
184
185 Wrap code in :any:`provisionalcompleter` context manager if you
185 Wrap code in :any:`provisionalcompleter` context manager if you
186 are certain you want to use an unstable feature.
186 are certain you want to use an unstable feature.
187 """
187 """
188 pass
188 pass
189
189
190 warnings.filterwarnings('error', category=ProvisionalCompleterWarning)
190 warnings.filterwarnings('error', category=ProvisionalCompleterWarning)
191
191
192
192
193 @skip_doctest
193 @skip_doctest
194 @contextmanager
194 @contextmanager
195 def provisionalcompleter(action='ignore'):
195 def provisionalcompleter(action='ignore'):
196 """
196 """
197 This context manager has to be used in any place where unstable completer
197 This context manager has to be used in any place where unstable completer
198 behavior and API may be called.
198 behavior and API may be called.
199
199
200 >>> with provisionalcompleter():
200 >>> with provisionalcompleter():
201 ... completer.do_experimental_things() # works
201 ... completer.do_experimental_things() # works
202
202
203 >>> completer.do_experimental_things() # raises.
203 >>> completer.do_experimental_things() # raises.
204
204
205 .. note::
205 .. note::
206
206
207 Unstable
207 Unstable
208
208
209 By using this context manager you agree that the API in use may change
209 By using this context manager you agree that the API in use may change
210 without warning, and that you won't complain if they do so.
210 without warning, and that you won't complain if they do so.
211
211
212 You also understand that, if the API is not to your liking, you should report
212 You also understand that, if the API is not to your liking, you should report
213 a bug to explain your use case upstream.
213 a bug to explain your use case upstream.
214
214
215 We'll be happy to get your feedback, feature requests, and improvements on
215 We'll be happy to get your feedback, feature requests, and improvements on
216 any of the unstable APIs!
216 any of the unstable APIs!
217 """
217 """
218 with warnings.catch_warnings():
218 with warnings.catch_warnings():
219 warnings.filterwarnings(action, category=ProvisionalCompleterWarning)
219 warnings.filterwarnings(action, category=ProvisionalCompleterWarning)
220 yield
220 yield
221
221
222
222
223 def has_open_quotes(s):
223 def has_open_quotes(s):
224 """Return whether a string has open quotes.
224 """Return whether a string has open quotes.
225
225
226 This simply counts whether the number of quote characters of either type in
226 This simply counts whether the number of quote characters of either type in
227 the string is odd.
227 the string is odd.
228
228
229 Returns
229 Returns
230 -------
230 -------
231 If there is an open quote, the quote character is returned. Else, return
231 If there is an open quote, the quote character is returned. Else, return
232 False.
232 False.
233 """
233 """
234 # We check " first, then ', so complex cases with nested quotes will get
234 # We check " first, then ', so complex cases with nested quotes will get
235 # the " to take precedence.
235 # the " to take precedence.
236 if s.count('"') % 2:
236 if s.count('"') % 2:
237 return '"'
237 return '"'
238 elif s.count("'") % 2:
238 elif s.count("'") % 2:
239 return "'"
239 return "'"
240 else:
240 else:
241 return False
241 return False
242
242
243
243
244 def protect_filename(s, protectables=PROTECTABLES):
244 def protect_filename(s, protectables=PROTECTABLES):
245 """Escape a string to protect certain characters."""
245 """Escape a string to protect certain characters."""
246 if set(s) & set(protectables):
246 if set(s) & set(protectables):
247 if sys.platform == "win32":
247 if sys.platform == "win32":
248 return '"' + s + '"'
248 return '"' + s + '"'
249 else:
249 else:
250 return "".join(("\\" + c if c in protectables else c) for c in s)
250 return "".join(("\\" + c if c in protectables else c) for c in s)
251 else:
251 else:
252 return s
252 return s
253
253
254
254
255 def expand_user(path:str) -> Tuple[str, bool, str]:
255 def expand_user(path:str) -> Tuple[str, bool, str]:
256 """Expand ``~``-style usernames in strings.
256 """Expand ``~``-style usernames in strings.
257
257
258 This is similar to :func:`os.path.expanduser`, but it computes and returns
258 This is similar to :func:`os.path.expanduser`, but it computes and returns
259 extra information that will be useful if the input was being used in
259 extra information that will be useful if the input was being used in
260 computing completions, and you wish to return the completions with the
260 computing completions, and you wish to return the completions with the
261 original '~' instead of its expanded value.
261 original '~' instead of its expanded value.
262
262
263 Parameters
263 Parameters
264 ----------
264 ----------
265 path : str
265 path : str
266 String to be expanded. If no ~ is present, the output is the same as the
266 String to be expanded. If no ~ is present, the output is the same as the
267 input.
267 input.
268
268
269 Returns
269 Returns
270 -------
270 -------
271 newpath : str
271 newpath : str
272 Result of ~ expansion in the input path.
272 Result of ~ expansion in the input path.
273 tilde_expand : bool
273 tilde_expand : bool
274 Whether any expansion was performed or not.
274 Whether any expansion was performed or not.
275 tilde_val : str
275 tilde_val : str
276 The value that ~ was replaced with.
276 The value that ~ was replaced with.
277 """
277 """
278 # Default values
278 # Default values
279 tilde_expand = False
279 tilde_expand = False
280 tilde_val = ''
280 tilde_val = ''
281 newpath = path
281 newpath = path
282
282
283 if path.startswith('~'):
283 if path.startswith('~'):
284 tilde_expand = True
284 tilde_expand = True
285 rest = len(path)-1
285 rest = len(path)-1
286 newpath = os.path.expanduser(path)
286 newpath = os.path.expanduser(path)
287 if rest:
287 if rest:
288 tilde_val = newpath[:-rest]
288 tilde_val = newpath[:-rest]
289 else:
289 else:
290 tilde_val = newpath
290 tilde_val = newpath
291
291
292 return newpath, tilde_expand, tilde_val
292 return newpath, tilde_expand, tilde_val
293
293
294
294
295 def compress_user(path:str, tilde_expand:bool, tilde_val:str) -> str:
295 def compress_user(path:str, tilde_expand:bool, tilde_val:str) -> str:
296 """Does the opposite of expand_user, with its outputs.
296 """Does the opposite of expand_user, with its outputs.
297 """
297 """
298 if tilde_expand:
298 if tilde_expand:
299 return path.replace(tilde_val, '~')
299 return path.replace(tilde_val, '~')
300 else:
300 else:
301 return path
301 return path
302
302
303
303
304 def completions_sorting_key(word):
304 def completions_sorting_key(word):
305 """key for sorting completions
305 """key for sorting completions
306
306
307 This does several things:
307 This does several things:
308
308
309 - Demote any completions starting with underscores to the end
309 - Demote any completions starting with underscores to the end
310 - Insert any %magic and %%cellmagic completions in the alphabetical order
310 - Insert any %magic and %%cellmagic completions in the alphabetical order
311 by their name
311 by their name
312 """
312 """
313 prio1, prio2 = 0, 0
313 prio1, prio2 = 0, 0
314
314
315 if word.startswith('__'):
315 if word.startswith('__'):
316 prio1 = 2
316 prio1 = 2
317 elif word.startswith('_'):
317 elif word.startswith('_'):
318 prio1 = 1
318 prio1 = 1
319
319
320 if word.endswith('='):
320 if word.endswith('='):
321 prio1 = -1
321 prio1 = -1
322
322
323 if word.startswith('%%'):
323 if word.startswith('%%'):
324 # If there's another % in there, this is something else, so leave it alone
324 # If there's another % in there, this is something else, so leave it alone
325 if not "%" in word[2:]:
325 if not "%" in word[2:]:
326 word = word[2:]
326 word = word[2:]
327 prio2 = 2
327 prio2 = 2
328 elif word.startswith('%'):
328 elif word.startswith('%'):
329 if not "%" in word[1:]:
329 if not "%" in word[1:]:
330 word = word[1:]
330 word = word[1:]
331 prio2 = 1
331 prio2 = 1
332
332
333 return prio1, word, prio2
333 return prio1, word, prio2
334
334
335
335
336 class _FakeJediCompletion:
336 class _FakeJediCompletion:
337 """
337 """
338 This is a workaround to communicate to the UI that Jedi has crashed and to
338 This is a workaround to communicate to the UI that Jedi has crashed and to
339 report a bug. Will be used only id :any:`IPCompleter.debug` is set to true.
339 report a bug. Will be used only id :any:`IPCompleter.debug` is set to true.
340
340
341 Added in IPython 6.0 so should likely be removed for 7.0
341 Added in IPython 6.0 so should likely be removed for 7.0
342
342
343 """
343 """
344
344
345 def __init__(self, name):
345 def __init__(self, name):
346
346
347 self.name = name
347 self.name = name
348 self.complete = name
348 self.complete = name
349 self.type = 'crashed'
349 self.type = 'crashed'
350 self.name_with_symbols = name
350 self.name_with_symbols = name
351 self.signature = ''
351 self.signature = ''
352 self._origin = 'fake'
352 self._origin = 'fake'
353
353
354 def __repr__(self):
354 def __repr__(self):
355 return '<Fake completion object jedi has crashed>'
355 return '<Fake completion object jedi has crashed>'
356
356
357
357
358 class Completion:
358 class Completion:
359 """
359 """
360 Completion object used and return by IPython completers.
360 Completion object used and return by IPython completers.
361
361
362 .. warning::
362 .. warning::
363
363
364 Unstable
364 Unstable
365
365
366 This function is unstable, API may change without warning.
366 This function is unstable, API may change without warning.
367 It will also raise unless use in proper context manager.
367 It will also raise unless use in proper context manager.
368
368
369 This act as a middle ground :any:`Completion` object between the
369 This act as a middle ground :any:`Completion` object between the
370 :any:`jedi.api.classes.Completion` object and the Prompt Toolkit completion
370 :any:`jedi.api.classes.Completion` object and the Prompt Toolkit completion
371 object. While Jedi need a lot of information about evaluator and how the
371 object. While Jedi need a lot of information about evaluator and how the
372 code should be ran/inspected, PromptToolkit (and other frontend) mostly
372 code should be ran/inspected, PromptToolkit (and other frontend) mostly
373 need user facing information.
373 need user facing information.
374
374
375 - Which range should be replaced replaced by what.
375 - Which range should be replaced replaced by what.
376 - Some metadata (like completion type), or meta information to displayed to
376 - Some metadata (like completion type), or meta information to displayed to
377 the use user.
377 the use user.
378
378
379 For debugging purpose we can also store the origin of the completion (``jedi``,
379 For debugging purpose we can also store the origin of the completion (``jedi``,
380 ``IPython.python_matches``, ``IPython.magics_matches``...).
380 ``IPython.python_matches``, ``IPython.magics_matches``...).
381 """
381 """
382
382
383 __slots__ = ['start', 'end', 'text', 'type', 'signature', '_origin']
383 __slots__ = ['start', 'end', 'text', 'type', 'signature', '_origin']
384
384
385 def __init__(self, start: int, end: int, text: str, *, type: str=None, _origin='', signature='') -> None:
385 def __init__(self, start: int, end: int, text: str, *, type: str=None, _origin='', signature='') -> None:
386 warnings.warn("``Completion`` is a provisional API (as of IPython 6.0). "
386 warnings.warn("``Completion`` is a provisional API (as of IPython 6.0). "
387 "It may change without warnings. "
387 "It may change without warnings. "
388 "Use in corresponding context manager.",
388 "Use in corresponding context manager.",
389 category=ProvisionalCompleterWarning, stacklevel=2)
389 category=ProvisionalCompleterWarning, stacklevel=2)
390
390
391 self.start = start
391 self.start = start
392 self.end = end
392 self.end = end
393 self.text = text
393 self.text = text
394 self.type = type
394 self.type = type
395 self.signature = signature
395 self.signature = signature
396 self._origin = _origin
396 self._origin = _origin
397
397
398 def __repr__(self):
398 def __repr__(self):
399 return '<Completion start=%s end=%s text=%r type=%r, signature=%r,>' % \
399 return '<Completion start=%s end=%s text=%r type=%r, signature=%r,>' % \
400 (self.start, self.end, self.text, self.type or '?', self.signature or '?')
400 (self.start, self.end, self.text, self.type or '?', self.signature or '?')
401
401
402 def __eq__(self, other)->Bool:
402 def __eq__(self, other)->Bool:
403 """
403 """
404 Equality and hash do not hash the type (as some completer may not be
404 Equality and hash do not hash the type (as some completer may not be
405 able to infer the type), but are use to (partially) de-duplicate
405 able to infer the type), but are use to (partially) de-duplicate
406 completion.
406 completion.
407
407
408 Completely de-duplicating completion is a bit tricker that just
408 Completely de-duplicating completion is a bit tricker that just
409 comparing as it depends on surrounding text, which Completions are not
409 comparing as it depends on surrounding text, which Completions are not
410 aware of.
410 aware of.
411 """
411 """
412 return self.start == other.start and \
412 return self.start == other.start and \
413 self.end == other.end and \
413 self.end == other.end and \
414 self.text == other.text
414 self.text == other.text
415
415
416 def __hash__(self):
416 def __hash__(self):
417 return hash((self.start, self.end, self.text))
417 return hash((self.start, self.end, self.text))
418
418
419
419
420 _IC = Iterable[Completion]
420 _IC = Iterable[Completion]
421
421
422
422
423 def _deduplicate_completions(text: str, completions: _IC)-> _IC:
423 def _deduplicate_completions(text: str, completions: _IC)-> _IC:
424 """
424 """
425 Deduplicate a set of completions.
425 Deduplicate a set of completions.
426
426
427 .. warning::
427 .. warning::
428
428
429 Unstable
429 Unstable
430
430
431 This function is unstable, API may change without warning.
431 This function is unstable, API may change without warning.
432
432
433 Parameters
433 Parameters
434 ----------
434 ----------
435 text : str
435 text : str
436 text that should be completed.
436 text that should be completed.
437 completions : Iterator[Completion]
437 completions : Iterator[Completion]
438 iterator over the completions to deduplicate
438 iterator over the completions to deduplicate
439
439
440 Yields
440 Yields
441 ------
441 ------
442 `Completions` objects
442 `Completions` objects
443 Completions coming from multiple sources, may be different but end up having
443 Completions coming from multiple sources, may be different but end up having
444 the same effect when applied to ``text``. If this is the case, this will
444 the same effect when applied to ``text``. If this is the case, this will
445 consider completions as equal and only emit the first encountered.
445 consider completions as equal and only emit the first encountered.
446 Not folded in `completions()` yet for debugging purpose, and to detect when
446 Not folded in `completions()` yet for debugging purpose, and to detect when
447 the IPython completer does return things that Jedi does not, but should be
447 the IPython completer does return things that Jedi does not, but should be
448 at some point.
448 at some point.
449 """
449 """
450 completions = list(completions)
450 completions = list(completions)
451 if not completions:
451 if not completions:
452 return
452 return
453
453
454 new_start = min(c.start for c in completions)
454 new_start = min(c.start for c in completions)
455 new_end = max(c.end for c in completions)
455 new_end = max(c.end for c in completions)
456
456
457 seen = set()
457 seen = set()
458 for c in completions:
458 for c in completions:
459 new_text = text[new_start:c.start] + c.text + text[c.end:new_end]
459 new_text = text[new_start:c.start] + c.text + text[c.end:new_end]
460 if new_text not in seen:
460 if new_text not in seen:
461 yield c
461 yield c
462 seen.add(new_text)
462 seen.add(new_text)
463
463
464
464
465 def rectify_completions(text: str, completions: _IC, *, _debug: bool = False) -> _IC:
465 def rectify_completions(text: str, completions: _IC, *, _debug: bool = False) -> _IC:
466 """
466 """
467 Rectify a set of completions to all have the same ``start`` and ``end``
467 Rectify a set of completions to all have the same ``start`` and ``end``
468
468
469 .. warning::
469 .. warning::
470
470
471 Unstable
471 Unstable
472
472
473 This function is unstable, API may change without warning.
473 This function is unstable, API may change without warning.
474 It will also raise unless use in proper context manager.
474 It will also raise unless use in proper context manager.
475
475
476 Parameters
476 Parameters
477 ----------
477 ----------
478 text : str
478 text : str
479 text that should be completed.
479 text that should be completed.
480 completions : Iterator[Completion]
480 completions : Iterator[Completion]
481 iterator over the completions to rectify
481 iterator over the completions to rectify
482 _debug : bool
482 _debug : bool
483 Log failed completion
483 Log failed completion
484
484
485 Notes
485 Notes
486 -----
486 -----
487 :any:`jedi.api.classes.Completion` s returned by Jedi may not have the same start and end, though
487 :any:`jedi.api.classes.Completion` s returned by Jedi may not have the same start and end, though
488 the Jupyter Protocol requires them to behave like so. This will readjust
488 the Jupyter Protocol requires them to behave like so. This will readjust
489 the completion to have the same ``start`` and ``end`` by padding both
489 the completion to have the same ``start`` and ``end`` by padding both
490 extremities with surrounding text.
490 extremities with surrounding text.
491
491
492 During stabilisation should support a ``_debug`` option to log which
492 During stabilisation should support a ``_debug`` option to log which
493 completion are return by the IPython completer and not found in Jedi in
493 completion are return by the IPython completer and not found in Jedi in
494 order to make upstream bug report.
494 order to make upstream bug report.
495 """
495 """
496 warnings.warn("`rectify_completions` is a provisional API (as of IPython 6.0). "
496 warnings.warn("`rectify_completions` is a provisional API (as of IPython 6.0). "
497 "It may change without warnings. "
497 "It may change without warnings. "
498 "Use in corresponding context manager.",
498 "Use in corresponding context manager.",
499 category=ProvisionalCompleterWarning, stacklevel=2)
499 category=ProvisionalCompleterWarning, stacklevel=2)
500
500
501 completions = list(completions)
501 completions = list(completions)
502 if not completions:
502 if not completions:
503 return
503 return
504 starts = (c.start for c in completions)
504 starts = (c.start for c in completions)
505 ends = (c.end for c in completions)
505 ends = (c.end for c in completions)
506
506
507 new_start = min(starts)
507 new_start = min(starts)
508 new_end = max(ends)
508 new_end = max(ends)
509
509
510 seen_jedi = set()
510 seen_jedi = set()
511 seen_python_matches = set()
511 seen_python_matches = set()
512 for c in completions:
512 for c in completions:
513 new_text = text[new_start:c.start] + c.text + text[c.end:new_end]
513 new_text = text[new_start:c.start] + c.text + text[c.end:new_end]
514 if c._origin == 'jedi':
514 if c._origin == 'jedi':
515 seen_jedi.add(new_text)
515 seen_jedi.add(new_text)
516 elif c._origin == 'IPCompleter.python_matches':
516 elif c._origin == 'IPCompleter.python_matches':
517 seen_python_matches.add(new_text)
517 seen_python_matches.add(new_text)
518 yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin, signature=c.signature)
518 yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin, signature=c.signature)
519 diff = seen_python_matches.difference(seen_jedi)
519 diff = seen_python_matches.difference(seen_jedi)
520 if diff and _debug:
520 if diff and _debug:
521 print('IPython.python matches have extras:', diff)
521 print('IPython.python matches have extras:', diff)
522
522
523
523
524 if sys.platform == 'win32':
524 if sys.platform == 'win32':
525 DELIMS = ' \t\n`!@#$^&*()=+[{]}|;\'",<>?'
525 DELIMS = ' \t\n`!@#$^&*()=+[{]}|;\'",<>?'
526 else:
526 else:
527 DELIMS = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?'
527 DELIMS = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?'
528
528
529 GREEDY_DELIMS = ' =\r\n'
529 GREEDY_DELIMS = ' =\r\n'
530
530
531
531
532 class CompletionSplitter(object):
532 class CompletionSplitter(object):
533 """An object to split an input line in a manner similar to readline.
533 """An object to split an input line in a manner similar to readline.
534
534
535 By having our own implementation, we can expose readline-like completion in
535 By having our own implementation, we can expose readline-like completion in
536 a uniform manner to all frontends. This object only needs to be given the
536 a uniform manner to all frontends. This object only needs to be given the
537 line of text to be split and the cursor position on said line, and it
537 line of text to be split and the cursor position on said line, and it
538 returns the 'word' to be completed on at the cursor after splitting the
538 returns the 'word' to be completed on at the cursor after splitting the
539 entire line.
539 entire line.
540
540
541 What characters are used as splitting delimiters can be controlled by
541 What characters are used as splitting delimiters can be controlled by
542 setting the ``delims`` attribute (this is a property that internally
542 setting the ``delims`` attribute (this is a property that internally
543 automatically builds the necessary regular expression)"""
543 automatically builds the necessary regular expression)"""
544
544
545 # Private interface
545 # Private interface
546
546
547 # A string of delimiter characters. The default value makes sense for
547 # A string of delimiter characters. The default value makes sense for
548 # IPython's most typical usage patterns.
548 # IPython's most typical usage patterns.
549 _delims = DELIMS
549 _delims = DELIMS
550
550
551 # The expression (a normal string) to be compiled into a regular expression
551 # The expression (a normal string) to be compiled into a regular expression
552 # for actual splitting. We store it as an attribute mostly for ease of
552 # for actual splitting. We store it as an attribute mostly for ease of
553 # debugging, since this type of code can be so tricky to debug.
553 # debugging, since this type of code can be so tricky to debug.
554 _delim_expr = None
554 _delim_expr = None
555
555
556 # The regular expression that does the actual splitting
556 # The regular expression that does the actual splitting
557 _delim_re = None
557 _delim_re = None
558
558
559 def __init__(self, delims=None):
559 def __init__(self, delims=None):
560 delims = CompletionSplitter._delims if delims is None else delims
560 delims = CompletionSplitter._delims if delims is None else delims
561 self.delims = delims
561 self.delims = delims
562
562
563 @property
563 @property
564 def delims(self):
564 def delims(self):
565 """Return the string of delimiter characters."""
565 """Return the string of delimiter characters."""
566 return self._delims
566 return self._delims
567
567
568 @delims.setter
568 @delims.setter
569 def delims(self, delims):
569 def delims(self, delims):
570 """Set the delimiters for line splitting."""
570 """Set the delimiters for line splitting."""
571 expr = '[' + ''.join('\\'+ c for c in delims) + ']'
571 expr = '[' + ''.join('\\'+ c for c in delims) + ']'
572 self._delim_re = re.compile(expr)
572 self._delim_re = re.compile(expr)
573 self._delims = delims
573 self._delims = delims
574 self._delim_expr = expr
574 self._delim_expr = expr
575
575
576 def split_line(self, line, cursor_pos=None):
576 def split_line(self, line, cursor_pos=None):
577 """Split a line of text with a cursor at the given position.
577 """Split a line of text with a cursor at the given position.
578 """
578 """
579 l = line if cursor_pos is None else line[:cursor_pos]
579 l = line if cursor_pos is None else line[:cursor_pos]
580 return self._delim_re.split(l)[-1]
580 return self._delim_re.split(l)[-1]
581
581
582
582
583
583
584 class Completer(Configurable):
584 class Completer(Configurable):
585
585
586 greedy = Bool(False,
586 greedy = Bool(False,
587 help="""Activate greedy completion
587 help="""Activate greedy completion
588 PENDING DEPRECATION. this is now mostly taken care of with Jedi.
588 PENDING DEPRECATION. this is now mostly taken care of with Jedi.
589
589
590 This will enable completion on elements of lists, results of function calls, etc.,
590 This will enable completion on elements of lists, results of function calls, etc.,
591 but can be unsafe because the code is actually evaluated on TAB.
591 but can be unsafe because the code is actually evaluated on TAB.
592 """
592 """,
593 ).tag(config=True)
593 ).tag(config=True)
594
594
595 use_jedi = Bool(default_value=JEDI_INSTALLED,
595 use_jedi = Bool(default_value=JEDI_INSTALLED,
596 help="Experimental: Use Jedi to generate autocompletions. "
596 help="Experimental: Use Jedi to generate autocompletions. "
597 "Default to True if jedi is installed.").tag(config=True)
597 "Default to True if jedi is installed.").tag(config=True)
598
598
599 jedi_compute_type_timeout = Int(default_value=400,
599 jedi_compute_type_timeout = Int(default_value=400,
600 help="""Experimental: restrict time (in milliseconds) during which Jedi can compute types.
600 help="""Experimental: restrict time (in milliseconds) during which Jedi can compute types.
601 Set to 0 to stop computing types. Non-zero value lower than 100ms may hurt
601 Set to 0 to stop computing types. Non-zero value lower than 100ms may hurt
602 performance by preventing jedi to build its cache.
602 performance by preventing jedi to build its cache.
603 """).tag(config=True)
603 """).tag(config=True)
604
604
605 debug = Bool(default_value=False,
605 debug = Bool(default_value=False,
606 help='Enable debug for the Completer. Mostly print extra '
606 help='Enable debug for the Completer. Mostly print extra '
607 'information for experimental jedi integration.')\
607 'information for experimental jedi integration.')\
608 .tag(config=True)
608 .tag(config=True)
609
609
610 backslash_combining_completions = Bool(True,
610 backslash_combining_completions = Bool(True,
611 help="Enable unicode completions, e.g. \\alpha<tab> . "
611 help="Enable unicode completions, e.g. \\alpha<tab> . "
612 "Includes completion of latex commands, unicode names, and expanding "
612 "Includes completion of latex commands, unicode names, and expanding "
613 "unicode characters back to latex commands.").tag(config=True)
613 "unicode characters back to latex commands.").tag(config=True)
614
614
615 def __init__(self, namespace=None, global_namespace=None, **kwargs):
615 def __init__(self, namespace=None, global_namespace=None, **kwargs):
616 """Create a new completer for the command line.
616 """Create a new completer for the command line.
617
617
618 Completer(namespace=ns, global_namespace=ns2) -> completer instance.
618 Completer(namespace=ns, global_namespace=ns2) -> completer instance.
619
619
620 If unspecified, the default namespace where completions are performed
620 If unspecified, the default namespace where completions are performed
621 is __main__ (technically, __main__.__dict__). Namespaces should be
621 is __main__ (technically, __main__.__dict__). Namespaces should be
622 given as dictionaries.
622 given as dictionaries.
623
623
624 An optional second namespace can be given. This allows the completer
624 An optional second namespace can be given. This allows the completer
625 to handle cases where both the local and global scopes need to be
625 to handle cases where both the local and global scopes need to be
626 distinguished.
626 distinguished.
627 """
627 """
628
628
629 # Don't bind to namespace quite yet, but flag whether the user wants a
629 # Don't bind to namespace quite yet, but flag whether the user wants a
630 # specific namespace or to use __main__.__dict__. This will allow us
630 # specific namespace or to use __main__.__dict__. This will allow us
631 # to bind to __main__.__dict__ at completion time, not now.
631 # to bind to __main__.__dict__ at completion time, not now.
632 if namespace is None:
632 if namespace is None:
633 self.use_main_ns = True
633 self.use_main_ns = True
634 else:
634 else:
635 self.use_main_ns = False
635 self.use_main_ns = False
636 self.namespace = namespace
636 self.namespace = namespace
637
637
638 # The global namespace, if given, can be bound directly
638 # The global namespace, if given, can be bound directly
639 if global_namespace is None:
639 if global_namespace is None:
640 self.global_namespace = {}
640 self.global_namespace = {}
641 else:
641 else:
642 self.global_namespace = global_namespace
642 self.global_namespace = global_namespace
643
643
644 self.custom_matchers = []
644 self.custom_matchers = []
645
645
646 super(Completer, self).__init__(**kwargs)
646 super(Completer, self).__init__(**kwargs)
647
647
648 def complete(self, text, state):
648 def complete(self, text, state):
649 """Return the next possible completion for 'text'.
649 """Return the next possible completion for 'text'.
650
650
651 This is called successively with state == 0, 1, 2, ... until it
651 This is called successively with state == 0, 1, 2, ... until it
652 returns None. The completion should begin with 'text'.
652 returns None. The completion should begin with 'text'.
653
653
654 """
654 """
655 if self.use_main_ns:
655 if self.use_main_ns:
656 self.namespace = __main__.__dict__
656 self.namespace = __main__.__dict__
657
657
658 if state == 0:
658 if state == 0:
659 if "." in text:
659 if "." in text:
660 self.matches = self.attr_matches(text)
660 self.matches = self.attr_matches(text)
661 else:
661 else:
662 self.matches = self.global_matches(text)
662 self.matches = self.global_matches(text)
663 try:
663 try:
664 return self.matches[state]
664 return self.matches[state]
665 except IndexError:
665 except IndexError:
666 return None
666 return None
667
667
668 def global_matches(self, text):
668 def global_matches(self, text):
669 """Compute matches when text is a simple name.
669 """Compute matches when text is a simple name.
670
670
671 Return a list of all keywords, built-in functions and names currently
671 Return a list of all keywords, built-in functions and names currently
672 defined in self.namespace or self.global_namespace that match.
672 defined in self.namespace or self.global_namespace that match.
673
673
674 """
674 """
675 matches = []
675 matches = []
676 match_append = matches.append
676 match_append = matches.append
677 n = len(text)
677 n = len(text)
678 for lst in [keyword.kwlist,
678 for lst in [keyword.kwlist,
679 builtin_mod.__dict__.keys(),
679 builtin_mod.__dict__.keys(),
680 self.namespace.keys(),
680 self.namespace.keys(),
681 self.global_namespace.keys()]:
681 self.global_namespace.keys()]:
682 for word in lst:
682 for word in lst:
683 if word[:n] == text and word != "__builtins__":
683 if word[:n] == text and word != "__builtins__":
684 match_append(word)
684 match_append(word)
685
685
686 snake_case_re = re.compile(r"[^_]+(_[^_]+)+?\Z")
686 snake_case_re = re.compile(r"[^_]+(_[^_]+)+?\Z")
687 for lst in [self.namespace.keys(),
687 for lst in [self.namespace.keys(),
688 self.global_namespace.keys()]:
688 self.global_namespace.keys()]:
689 shortened = {"_".join([sub[0] for sub in word.split('_')]) : word
689 shortened = {"_".join([sub[0] for sub in word.split('_')]) : word
690 for word in lst if snake_case_re.match(word)}
690 for word in lst if snake_case_re.match(word)}
691 for word in shortened.keys():
691 for word in shortened.keys():
692 if word[:n] == text and word != "__builtins__":
692 if word[:n] == text and word != "__builtins__":
693 match_append(shortened[word])
693 match_append(shortened[word])
694 return matches
694 return matches
695
695
696 def attr_matches(self, text):
696 def attr_matches(self, text):
697 """Compute matches when text contains a dot.
697 """Compute matches when text contains a dot.
698
698
699 Assuming the text is of the form NAME.NAME....[NAME], and is
699 Assuming the text is of the form NAME.NAME....[NAME], and is
700 evaluatable in self.namespace or self.global_namespace, it will be
700 evaluatable in self.namespace or self.global_namespace, it will be
701 evaluated and its attributes (as revealed by dir()) are used as
701 evaluated and its attributes (as revealed by dir()) are used as
702 possible completions. (For class instances, class members are
702 possible completions. (For class instances, class members are
703 also considered.)
703 also considered.)
704
704
705 WARNING: this can still invoke arbitrary C code, if an object
705 WARNING: this can still invoke arbitrary C code, if an object
706 with a __getattr__ hook is evaluated.
706 with a __getattr__ hook is evaluated.
707
707
708 """
708 """
709
709
710 # Another option, seems to work great. Catches things like ''.<tab>
710 # Another option, seems to work great. Catches things like ''.<tab>
711 m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text)
711 m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text)
712
712
713 if m:
713 if m:
714 expr, attr = m.group(1, 3)
714 expr, attr = m.group(1, 3)
715 elif self.greedy:
715 elif self.greedy:
716 m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer)
716 m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer)
717 if not m2:
717 if not m2:
718 return []
718 return []
719 expr, attr = m2.group(1,2)
719 expr, attr = m2.group(1,2)
720 else:
720 else:
721 return []
721 return []
722
722
723 try:
723 try:
724 obj = eval(expr, self.namespace)
724 obj = eval(expr, self.namespace)
725 except:
725 except:
726 try:
726 try:
727 obj = eval(expr, self.global_namespace)
727 obj = eval(expr, self.global_namespace)
728 except:
728 except:
729 return []
729 return []
730
730
731 if self.limit_to__all__ and hasattr(obj, '__all__'):
731 if self.limit_to__all__ and hasattr(obj, '__all__'):
732 words = get__all__entries(obj)
732 words = get__all__entries(obj)
733 else:
733 else:
734 words = dir2(obj)
734 words = dir2(obj)
735
735
736 try:
736 try:
737 words = generics.complete_object(obj, words)
737 words = generics.complete_object(obj, words)
738 except TryNext:
738 except TryNext:
739 pass
739 pass
740 except AssertionError:
740 except AssertionError:
741 raise
741 raise
742 except Exception:
742 except Exception:
743 # Silence errors from completion function
743 # Silence errors from completion function
744 #raise # dbg
744 #raise # dbg
745 pass
745 pass
746 # Build match list to return
746 # Build match list to return
747 n = len(attr)
747 n = len(attr)
748 return [u"%s.%s" % (expr, w) for w in words if w[:n] == attr ]
748 return [u"%s.%s" % (expr, w) for w in words if w[:n] == attr ]
749
749
750
750
751 def get__all__entries(obj):
751 def get__all__entries(obj):
752 """returns the strings in the __all__ attribute"""
752 """returns the strings in the __all__ attribute"""
753 try:
753 try:
754 words = getattr(obj, '__all__')
754 words = getattr(obj, '__all__')
755 except:
755 except:
756 return []
756 return []
757
757
758 return [w for w in words if isinstance(w, str)]
758 return [w for w in words if isinstance(w, str)]
759
759
760
760
761 def match_dict_keys(keys: List[Union[str, bytes, Tuple[Union[str, bytes]]]], prefix: str, delims: str,
761 def match_dict_keys(keys: List[Union[str, bytes, Tuple[Union[str, bytes]]]], prefix: str, delims: str,
762 extra_prefix: Optional[Tuple[str, bytes]]=None) -> Tuple[str, int, List[str]]:
762 extra_prefix: Optional[Tuple[str, bytes]]=None) -> Tuple[str, int, List[str]]:
763 """Used by dict_key_matches, matching the prefix to a list of keys
763 """Used by dict_key_matches, matching the prefix to a list of keys
764
764
765 Parameters
765 Parameters
766 ----------
766 ----------
767 keys
767 keys
768 list of keys in dictionary currently being completed.
768 list of keys in dictionary currently being completed.
769 prefix
769 prefix
770 Part of the text already typed by the user. E.g. `mydict[b'fo`
770 Part of the text already typed by the user. E.g. `mydict[b'fo`
771 delims
771 delims
772 String of delimiters to consider when finding the current key.
772 String of delimiters to consider when finding the current key.
773 extra_prefix : optional
773 extra_prefix : optional
774 Part of the text already typed in multi-key index cases. E.g. for
774 Part of the text already typed in multi-key index cases. E.g. for
775 `mydict['foo', "bar", 'b`, this would be `('foo', 'bar')`.
775 `mydict['foo', "bar", 'b`, this would be `('foo', 'bar')`.
776
776
777 Returns
777 Returns
778 -------
778 -------
779 A tuple of three elements: ``quote``, ``token_start``, ``matched``, with
779 A tuple of three elements: ``quote``, ``token_start``, ``matched``, with
780 ``quote`` being the quote that need to be used to close current string.
780 ``quote`` being the quote that need to be used to close current string.
781 ``token_start`` the position where the replacement should start occurring,
781 ``token_start`` the position where the replacement should start occurring,
782 ``matches`` a list of replacement/completion
782 ``matches`` a list of replacement/completion
783
783
784 """
784 """
785 prefix_tuple = extra_prefix if extra_prefix else ()
785 prefix_tuple = extra_prefix if extra_prefix else ()
786 Nprefix = len(prefix_tuple)
786 Nprefix = len(prefix_tuple)
787 def filter_prefix_tuple(key):
787 def filter_prefix_tuple(key):
788 # Reject too short keys
788 # Reject too short keys
789 if len(key) <= Nprefix:
789 if len(key) <= Nprefix:
790 return False
790 return False
791 # Reject keys with non str/bytes in it
791 # Reject keys with non str/bytes in it
792 for k in key:
792 for k in key:
793 if not isinstance(k, (str, bytes)):
793 if not isinstance(k, (str, bytes)):
794 return False
794 return False
795 # Reject keys that do not match the prefix
795 # Reject keys that do not match the prefix
796 for k, pt in zip(key, prefix_tuple):
796 for k, pt in zip(key, prefix_tuple):
797 if k != pt:
797 if k != pt:
798 return False
798 return False
799 # All checks passed!
799 # All checks passed!
800 return True
800 return True
801
801
802 filtered_keys:List[Union[str,bytes]] = []
802 filtered_keys:List[Union[str,bytes]] = []
803 def _add_to_filtered_keys(key):
803 def _add_to_filtered_keys(key):
804 if isinstance(key, (str, bytes)):
804 if isinstance(key, (str, bytes)):
805 filtered_keys.append(key)
805 filtered_keys.append(key)
806
806
807 for k in keys:
807 for k in keys:
808 if isinstance(k, tuple):
808 if isinstance(k, tuple):
809 if filter_prefix_tuple(k):
809 if filter_prefix_tuple(k):
810 _add_to_filtered_keys(k[Nprefix])
810 _add_to_filtered_keys(k[Nprefix])
811 else:
811 else:
812 _add_to_filtered_keys(k)
812 _add_to_filtered_keys(k)
813
813
814 if not prefix:
814 if not prefix:
815 return '', 0, [repr(k) for k in filtered_keys]
815 return '', 0, [repr(k) for k in filtered_keys]
816 quote_match = re.search('["\']', prefix)
816 quote_match = re.search('["\']', prefix)
817 assert quote_match is not None # silence mypy
817 assert quote_match is not None # silence mypy
818 quote = quote_match.group()
818 quote = quote_match.group()
819 try:
819 try:
820 prefix_str = eval(prefix + quote, {})
820 prefix_str = eval(prefix + quote, {})
821 except Exception:
821 except Exception:
822 return '', 0, []
822 return '', 0, []
823
823
824 pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$'
824 pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$'
825 token_match = re.search(pattern, prefix, re.UNICODE)
825 token_match = re.search(pattern, prefix, re.UNICODE)
826 assert token_match is not None # silence mypy
826 assert token_match is not None # silence mypy
827 token_start = token_match.start()
827 token_start = token_match.start()
828 token_prefix = token_match.group()
828 token_prefix = token_match.group()
829
829
830 matched:List[str] = []
830 matched:List[str] = []
831 for key in filtered_keys:
831 for key in filtered_keys:
832 try:
832 try:
833 if not key.startswith(prefix_str):
833 if not key.startswith(prefix_str):
834 continue
834 continue
835 except (AttributeError, TypeError, UnicodeError):
835 except (AttributeError, TypeError, UnicodeError):
836 # Python 3+ TypeError on b'a'.startswith('a') or vice-versa
836 # Python 3+ TypeError on b'a'.startswith('a') or vice-versa
837 continue
837 continue
838
838
839 # reformat remainder of key to begin with prefix
839 # reformat remainder of key to begin with prefix
840 rem = key[len(prefix_str):]
840 rem = key[len(prefix_str):]
841 # force repr wrapped in '
841 # force repr wrapped in '
842 rem_repr = repr(rem + '"') if isinstance(rem, str) else repr(rem + b'"')
842 rem_repr = repr(rem + '"') if isinstance(rem, str) else repr(rem + b'"')
843 rem_repr = rem_repr[1 + rem_repr.index("'"):-2]
843 rem_repr = rem_repr[1 + rem_repr.index("'"):-2]
844 if quote == '"':
844 if quote == '"':
845 # The entered prefix is quoted with ",
845 # The entered prefix is quoted with ",
846 # but the match is quoted with '.
846 # but the match is quoted with '.
847 # A contained " hence needs escaping for comparison:
847 # A contained " hence needs escaping for comparison:
848 rem_repr = rem_repr.replace('"', '\\"')
848 rem_repr = rem_repr.replace('"', '\\"')
849
849
850 # then reinsert prefix from start of token
850 # then reinsert prefix from start of token
851 matched.append('%s%s' % (token_prefix, rem_repr))
851 matched.append('%s%s' % (token_prefix, rem_repr))
852 return quote, token_start, matched
852 return quote, token_start, matched
853
853
854
854
855 def cursor_to_position(text:str, line:int, column:int)->int:
855 def cursor_to_position(text:str, line:int, column:int)->int:
856 """
856 """
857 Convert the (line,column) position of the cursor in text to an offset in a
857 Convert the (line,column) position of the cursor in text to an offset in a
858 string.
858 string.
859
859
860 Parameters
860 Parameters
861 ----------
861 ----------
862 text : str
862 text : str
863 The text in which to calculate the cursor offset
863 The text in which to calculate the cursor offset
864 line : int
864 line : int
865 Line of the cursor; 0-indexed
865 Line of the cursor; 0-indexed
866 column : int
866 column : int
867 Column of the cursor 0-indexed
867 Column of the cursor 0-indexed
868
868
869 Returns
869 Returns
870 -------
870 -------
871 Position of the cursor in ``text``, 0-indexed.
871 Position of the cursor in ``text``, 0-indexed.
872
872
873 See Also
873 See Also
874 --------
874 --------
875 position_to_cursor : reciprocal of this function
875 position_to_cursor : reciprocal of this function
876
876
877 """
877 """
878 lines = text.split('\n')
878 lines = text.split('\n')
879 assert line <= len(lines), '{} <= {}'.format(str(line), str(len(lines)))
879 assert line <= len(lines), '{} <= {}'.format(str(line), str(len(lines)))
880
880
881 return sum(len(l) + 1 for l in lines[:line]) + column
881 return sum(len(l) + 1 for l in lines[:line]) + column
882
882
883 def position_to_cursor(text:str, offset:int)->Tuple[int, int]:
883 def position_to_cursor(text:str, offset:int)->Tuple[int, int]:
884 """
884 """
885 Convert the position of the cursor in text (0 indexed) to a line
885 Convert the position of the cursor in text (0 indexed) to a line
886 number(0-indexed) and a column number (0-indexed) pair
886 number(0-indexed) and a column number (0-indexed) pair
887
887
888 Position should be a valid position in ``text``.
888 Position should be a valid position in ``text``.
889
889
890 Parameters
890 Parameters
891 ----------
891 ----------
892 text : str
892 text : str
893 The text in which to calculate the cursor offset
893 The text in which to calculate the cursor offset
894 offset : int
894 offset : int
895 Position of the cursor in ``text``, 0-indexed.
895 Position of the cursor in ``text``, 0-indexed.
896
896
897 Returns
897 Returns
898 -------
898 -------
899 (line, column) : (int, int)
899 (line, column) : (int, int)
900 Line of the cursor; 0-indexed, column of the cursor 0-indexed
900 Line of the cursor; 0-indexed, column of the cursor 0-indexed
901
901
902 See Also
902 See Also
903 --------
903 --------
904 cursor_to_position : reciprocal of this function
904 cursor_to_position : reciprocal of this function
905
905
906 """
906 """
907
907
908 assert 0 <= offset <= len(text) , "0 <= %s <= %s" % (offset , len(text))
908 assert 0 <= offset <= len(text) , "0 <= %s <= %s" % (offset , len(text))
909
909
910 before = text[:offset]
910 before = text[:offset]
911 blines = before.split('\n') # ! splitnes trim trailing \n
911 blines = before.split('\n') # ! splitnes trim trailing \n
912 line = before.count('\n')
912 line = before.count('\n')
913 col = len(blines[-1])
913 col = len(blines[-1])
914 return line, col
914 return line, col
915
915
916
916
917 def _safe_isinstance(obj, module, class_name):
917 def _safe_isinstance(obj, module, class_name):
918 """Checks if obj is an instance of module.class_name if loaded
918 """Checks if obj is an instance of module.class_name if loaded
919 """
919 """
920 return (module in sys.modules and
920 return (module in sys.modules and
921 isinstance(obj, getattr(import_module(module), class_name)))
921 isinstance(obj, getattr(import_module(module), class_name)))
922
922
923 def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]:
923 def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]:
924 """Match Unicode characters back to Unicode name
924 """Match Unicode characters back to Unicode name
925
925
926 This does ``β˜ƒ`` -> ``\\snowman``
926 This does ``β˜ƒ`` -> ``\\snowman``
927
927
928 Note that snowman is not a valid python3 combining character but will be expanded.
928 Note that snowman is not a valid python3 combining character but will be expanded.
929 Though it will not recombine back to the snowman character by the completion machinery.
929 Though it will not recombine back to the snowman character by the completion machinery.
930
930
931 This will not either back-complete standard sequences like \\n, \\b ...
931 This will not either back-complete standard sequences like \\n, \\b ...
932
932
933 Returns
933 Returns
934 =======
934 =======
935
935
936 Return a tuple with two elements:
936 Return a tuple with two elements:
937
937
938 - The Unicode character that was matched (preceded with a backslash), or
938 - The Unicode character that was matched (preceded with a backslash), or
939 empty string,
939 empty string,
940 - a sequence (of 1), name for the match Unicode character, preceded by
940 - a sequence (of 1), name for the match Unicode character, preceded by
941 backslash, or empty if no match.
941 backslash, or empty if no match.
942
942
943 """
943 """
944 if len(text)<2:
944 if len(text)<2:
945 return '', ()
945 return '', ()
946 maybe_slash = text[-2]
946 maybe_slash = text[-2]
947 if maybe_slash != '\\':
947 if maybe_slash != '\\':
948 return '', ()
948 return '', ()
949
949
950 char = text[-1]
950 char = text[-1]
951 # no expand on quote for completion in strings.
951 # no expand on quote for completion in strings.
952 # nor backcomplete standard ascii keys
952 # nor backcomplete standard ascii keys
953 if char in string.ascii_letters or char in ('"',"'"):
953 if char in string.ascii_letters or char in ('"',"'"):
954 return '', ()
954 return '', ()
955 try :
955 try :
956 unic = unicodedata.name(char)
956 unic = unicodedata.name(char)
957 return '\\'+char,('\\'+unic,)
957 return '\\'+char,('\\'+unic,)
958 except KeyError:
958 except KeyError:
959 pass
959 pass
960 return '', ()
960 return '', ()
961
961
962 def back_latex_name_matches(text:str) -> Tuple[str, Sequence[str]] :
962 def back_latex_name_matches(text:str) -> Tuple[str, Sequence[str]] :
963 """Match latex characters back to unicode name
963 """Match latex characters back to unicode name
964
964
965 This does ``\\β„΅`` -> ``\\aleph``
965 This does ``\\β„΅`` -> ``\\aleph``
966
966
967 """
967 """
968 if len(text)<2:
968 if len(text)<2:
969 return '', ()
969 return '', ()
970 maybe_slash = text[-2]
970 maybe_slash = text[-2]
971 if maybe_slash != '\\':
971 if maybe_slash != '\\':
972 return '', ()
972 return '', ()
973
973
974
974
975 char = text[-1]
975 char = text[-1]
976 # no expand on quote for completion in strings.
976 # no expand on quote for completion in strings.
977 # nor backcomplete standard ascii keys
977 # nor backcomplete standard ascii keys
978 if char in string.ascii_letters or char in ('"',"'"):
978 if char in string.ascii_letters or char in ('"',"'"):
979 return '', ()
979 return '', ()
980 try :
980 try :
981 latex = reverse_latex_symbol[char]
981 latex = reverse_latex_symbol[char]
982 # '\\' replace the \ as well
982 # '\\' replace the \ as well
983 return '\\'+char,[latex]
983 return '\\'+char,[latex]
984 except KeyError:
984 except KeyError:
985 pass
985 pass
986 return '', ()
986 return '', ()
987
987
988
988
989 def _formatparamchildren(parameter) -> str:
989 def _formatparamchildren(parameter) -> str:
990 """
990 """
991 Get parameter name and value from Jedi Private API
991 Get parameter name and value from Jedi Private API
992
992
993 Jedi does not expose a simple way to get `param=value` from its API.
993 Jedi does not expose a simple way to get `param=value` from its API.
994
994
995 Parameters
995 Parameters
996 ----------
996 ----------
997 parameter
997 parameter
998 Jedi's function `Param`
998 Jedi's function `Param`
999
999
1000 Returns
1000 Returns
1001 -------
1001 -------
1002 A string like 'a', 'b=1', '*args', '**kwargs'
1002 A string like 'a', 'b=1', '*args', '**kwargs'
1003
1003
1004 """
1004 """
1005 description = parameter.description
1005 description = parameter.description
1006 if not description.startswith('param '):
1006 if not description.startswith('param '):
1007 raise ValueError('Jedi function parameter description have change format.'
1007 raise ValueError('Jedi function parameter description have change format.'
1008 'Expected "param ...", found %r".' % description)
1008 'Expected "param ...", found %r".' % description)
1009 return description[6:]
1009 return description[6:]
1010
1010
1011 def _make_signature(completion)-> str:
1011 def _make_signature(completion)-> str:
1012 """
1012 """
1013 Make the signature from a jedi completion
1013 Make the signature from a jedi completion
1014
1014
1015 Parameters
1015 Parameters
1016 ----------
1016 ----------
1017 completion : jedi.Completion
1017 completion : jedi.Completion
1018 object does not complete a function type
1018 object does not complete a function type
1019
1019
1020 Returns
1020 Returns
1021 -------
1021 -------
1022 a string consisting of the function signature, with the parenthesis but
1022 a string consisting of the function signature, with the parenthesis but
1023 without the function name. example:
1023 without the function name. example:
1024 `(a, *args, b=1, **kwargs)`
1024 `(a, *args, b=1, **kwargs)`
1025
1025
1026 """
1026 """
1027
1027
1028 # it looks like this might work on jedi 0.17
1028 # it looks like this might work on jedi 0.17
1029 if hasattr(completion, 'get_signatures'):
1029 if hasattr(completion, 'get_signatures'):
1030 signatures = completion.get_signatures()
1030 signatures = completion.get_signatures()
1031 if not signatures:
1031 if not signatures:
1032 return '(?)'
1032 return '(?)'
1033
1033
1034 c0 = completion.get_signatures()[0]
1034 c0 = completion.get_signatures()[0]
1035 return '('+c0.to_string().split('(', maxsplit=1)[1]
1035 return '('+c0.to_string().split('(', maxsplit=1)[1]
1036
1036
1037 return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for signature in completion.get_signatures()
1037 return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for signature in completion.get_signatures()
1038 for p in signature.defined_names()) if f])
1038 for p in signature.defined_names()) if f])
1039
1039
1040
1040
1041 class _CompleteResult(NamedTuple):
1041 class _CompleteResult(NamedTuple):
1042 matched_text : str
1042 matched_text : str
1043 matches: Sequence[str]
1043 matches: Sequence[str]
1044 matches_origin: Sequence[str]
1044 matches_origin: Sequence[str]
1045 jedi_matches: Any
1045 jedi_matches: Any
1046
1046
1047
1047
1048 class IPCompleter(Completer):
1048 class IPCompleter(Completer):
1049 """Extension of the completer class with IPython-specific features"""
1049 """Extension of the completer class with IPython-specific features"""
1050
1050
1051 __dict_key_regexps: Optional[Dict[bool,Pattern]] = None
1051 __dict_key_regexps: Optional[Dict[bool,Pattern]] = None
1052
1052
1053 @observe('greedy')
1053 @observe('greedy')
1054 def _greedy_changed(self, change):
1054 def _greedy_changed(self, change):
1055 """update the splitter and readline delims when greedy is changed"""
1055 """update the splitter and readline delims when greedy is changed"""
1056 if change['new']:
1056 if change['new']:
1057 self.splitter.delims = GREEDY_DELIMS
1057 self.splitter.delims = GREEDY_DELIMS
1058 else:
1058 else:
1059 self.splitter.delims = DELIMS
1059 self.splitter.delims = DELIMS
1060
1060
1061 dict_keys_only = Bool(False,
1061 dict_keys_only = Bool(False,
1062 help="""Whether to show dict key matches only""")
1062 help="""Whether to show dict key matches only""")
1063
1063
1064 merge_completions = Bool(True,
1064 merge_completions = Bool(True,
1065 help="""Whether to merge completion results into a single list
1065 help="""Whether to merge completion results into a single list
1066
1066
1067 If False, only the completion results from the first non-empty
1067 If False, only the completion results from the first non-empty
1068 completer will be returned.
1068 completer will be returned.
1069 """
1069 """
1070 ).tag(config=True)
1070 ).tag(config=True)
1071 omit__names = Enum((0,1,2), default_value=2,
1071 omit__names = Enum((0,1,2), default_value=2,
1072 help="""Instruct the completer to omit private method names
1072 help="""Instruct the completer to omit private method names
1073
1073
1074 Specifically, when completing on ``object.<tab>``.
1074 Specifically, when completing on ``object.<tab>``.
1075
1075
1076 When 2 [default]: all names that start with '_' will be excluded.
1076 When 2 [default]: all names that start with '_' will be excluded.
1077
1077
1078 When 1: all 'magic' names (``__foo__``) will be excluded.
1078 When 1: all 'magic' names (``__foo__``) will be excluded.
1079
1079
1080 When 0: nothing will be excluded.
1080 When 0: nothing will be excluded.
1081 """
1081 """
1082 ).tag(config=True)
1082 ).tag(config=True)
1083 limit_to__all__ = Bool(False,
1083 limit_to__all__ = Bool(False,
1084 help="""
1084 help="""
1085 DEPRECATED as of version 5.0.
1085 DEPRECATED as of version 5.0.
1086
1086
1087 Instruct the completer to use __all__ for the completion
1087 Instruct the completer to use __all__ for the completion
1088
1088
1089 Specifically, when completing on ``object.<tab>``.
1089 Specifically, when completing on ``object.<tab>``.
1090
1090
1091 When True: only those names in obj.__all__ will be included.
1091 When True: only those names in obj.__all__ will be included.
1092
1092
1093 When False [default]: the __all__ attribute is ignored
1093 When False [default]: the __all__ attribute is ignored
1094 """,
1094 """,
1095 ).tag(config=True)
1095 ).tag(config=True)
1096
1096
1097 profile_completions = Bool(
1097 profile_completions = Bool(
1098 default_value=False,
1098 default_value=False,
1099 help="If True, emit profiling data for completion subsystem using cProfile."
1099 help="If True, emit profiling data for completion subsystem using cProfile."
1100 ).tag(config=True)
1100 ).tag(config=True)
1101
1101
1102 profiler_output_dir = Unicode(
1102 profiler_output_dir = Unicode(
1103 default_value=".completion_profiles",
1103 default_value=".completion_profiles",
1104 help="Template for path at which to output profile data for completions."
1104 help="Template for path at which to output profile data for completions."
1105 ).tag(config=True)
1105 ).tag(config=True)
1106
1106
1107 @observe('limit_to__all__')
1107 @observe('limit_to__all__')
1108 def _limit_to_all_changed(self, change):
1108 def _limit_to_all_changed(self, change):
1109 warnings.warn('`IPython.core.IPCompleter.limit_to__all__` configuration '
1109 warnings.warn('`IPython.core.IPCompleter.limit_to__all__` configuration '
1110 'value has been deprecated since IPython 5.0, will be made to have '
1110 'value has been deprecated since IPython 5.0, will be made to have '
1111 'no effects and then removed in future version of IPython.',
1111 'no effects and then removed in future version of IPython.',
1112 UserWarning)
1112 UserWarning)
1113
1113
1114 def __init__(
1114 def __init__(
1115 self, shell=None, namespace=None, global_namespace=None, config=None, **kwargs
1115 self, shell=None, namespace=None, global_namespace=None, config=None, **kwargs
1116 ):
1116 ):
1117 """IPCompleter() -> completer
1117 """IPCompleter() -> completer
1118
1118
1119 Return a completer object.
1119 Return a completer object.
1120
1120
1121 Parameters
1121 Parameters
1122 ----------
1122 ----------
1123 shell
1123 shell
1124 a pointer to the ipython shell itself. This is needed
1124 a pointer to the ipython shell itself. This is needed
1125 because this completer knows about magic functions, and those can
1125 because this completer knows about magic functions, and those can
1126 only be accessed via the ipython instance.
1126 only be accessed via the ipython instance.
1127 namespace : dict, optional
1127 namespace : dict, optional
1128 an optional dict where completions are performed.
1128 an optional dict where completions are performed.
1129 global_namespace : dict, optional
1129 global_namespace : dict, optional
1130 secondary optional dict for completions, to
1130 secondary optional dict for completions, to
1131 handle cases (such as IPython embedded inside functions) where
1131 handle cases (such as IPython embedded inside functions) where
1132 both Python scopes are visible.
1132 both Python scopes are visible.
1133 config : Config
1133 config : Config
1134 traitlet's config object
1134 traitlet's config object
1135 **kwargs
1135 **kwargs
1136 passed to super class unmodified.
1136 passed to super class unmodified.
1137 """
1137 """
1138
1138
1139 self.magic_escape = ESC_MAGIC
1139 self.magic_escape = ESC_MAGIC
1140 self.splitter = CompletionSplitter()
1140 self.splitter = CompletionSplitter()
1141
1141
1142 # _greedy_changed() depends on splitter and readline being defined:
1142 # _greedy_changed() depends on splitter and readline being defined:
1143 super().__init__(
1143 super().__init__(
1144 namespace=namespace,
1144 namespace=namespace,
1145 global_namespace=global_namespace,
1145 global_namespace=global_namespace,
1146 config=config,
1146 config=config,
1147 **kwargs
1147 **kwargs
1148 )
1148 )
1149
1149
1150 # List where completion matches will be stored
1150 # List where completion matches will be stored
1151 self.matches = []
1151 self.matches = []
1152 self.shell = shell
1152 self.shell = shell
1153 # Regexp to split filenames with spaces in them
1153 # Regexp to split filenames with spaces in them
1154 self.space_name_re = re.compile(r'([^\\] )')
1154 self.space_name_re = re.compile(r'([^\\] )')
1155 # Hold a local ref. to glob.glob for speed
1155 # Hold a local ref. to glob.glob for speed
1156 self.glob = glob.glob
1156 self.glob = glob.glob
1157
1157
1158 # Determine if we are running on 'dumb' terminals, like (X)Emacs
1158 # Determine if we are running on 'dumb' terminals, like (X)Emacs
1159 # buffers, to avoid completion problems.
1159 # buffers, to avoid completion problems.
1160 term = os.environ.get('TERM','xterm')
1160 term = os.environ.get('TERM','xterm')
1161 self.dumb_terminal = term in ['dumb','emacs']
1161 self.dumb_terminal = term in ['dumb','emacs']
1162
1162
1163 # Special handling of backslashes needed in win32 platforms
1163 # Special handling of backslashes needed in win32 platforms
1164 if sys.platform == "win32":
1164 if sys.platform == "win32":
1165 self.clean_glob = self._clean_glob_win32
1165 self.clean_glob = self._clean_glob_win32
1166 else:
1166 else:
1167 self.clean_glob = self._clean_glob
1167 self.clean_glob = self._clean_glob
1168
1168
1169 #regexp to parse docstring for function signature
1169 #regexp to parse docstring for function signature
1170 self.docstring_sig_re = re.compile(r'^[\w|\s.]+\(([^)]*)\).*')
1170 self.docstring_sig_re = re.compile(r'^[\w|\s.]+\(([^)]*)\).*')
1171 self.docstring_kwd_re = re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)')
1171 self.docstring_kwd_re = re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)')
1172 #use this if positional argument name is also needed
1172 #use this if positional argument name is also needed
1173 #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)')
1173 #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)')
1174
1174
1175 self.magic_arg_matchers = [
1175 self.magic_arg_matchers = [
1176 self.magic_config_matches,
1176 self.magic_config_matches,
1177 self.magic_color_matches,
1177 self.magic_color_matches,
1178 ]
1178 ]
1179
1179
1180 # This is set externally by InteractiveShell
1180 # This is set externally by InteractiveShell
1181 self.custom_completers = None
1181 self.custom_completers = None
1182
1182
1183 # This is a list of names of unicode characters that can be completed
1183 # This is a list of names of unicode characters that can be completed
1184 # into their corresponding unicode value. The list is large, so we
1184 # into their corresponding unicode value. The list is large, so we
1185 # lazily initialize it on first use. Consuming code should access this
1185 # lazily initialize it on first use. Consuming code should access this
1186 # attribute through the `@unicode_names` property.
1186 # attribute through the `@unicode_names` property.
1187 self._unicode_names = None
1187 self._unicode_names = None
1188
1188
1189 @property
1189 @property
1190 def matchers(self) -> List[Any]:
1190 def matchers(self) -> List[Any]:
1191 """All active matcher routines for completion"""
1191 """All active matcher routines for completion"""
1192 if self.dict_keys_only:
1192 if self.dict_keys_only:
1193 return [self.dict_key_matches]
1193 return [self.dict_key_matches]
1194
1194
1195 if self.use_jedi:
1195 if self.use_jedi:
1196 return [
1196 return [
1197 *self.custom_matchers,
1197 *self.custom_matchers,
1198 self.dict_key_matches,
1198 self.dict_key_matches,
1199 self.file_matches,
1199 self.file_matches,
1200 self.magic_matches,
1200 self.magic_matches,
1201 ]
1201 ]
1202 else:
1202 else:
1203 return [
1203 return [
1204 *self.custom_matchers,
1204 *self.custom_matchers,
1205 self.dict_key_matches,
1205 self.dict_key_matches,
1206 self.python_matches,
1206 self.python_matches,
1207 self.file_matches,
1207 self.file_matches,
1208 self.magic_matches,
1208 self.magic_matches,
1209 self.python_func_kw_matches,
1209 self.python_func_kw_matches,
1210 ]
1210 ]
1211
1211
1212 def all_completions(self, text:str) -> List[str]:
1212 def all_completions(self, text:str) -> List[str]:
1213 """
1213 """
1214 Wrapper around the completion methods for the benefit of emacs.
1214 Wrapper around the completion methods for the benefit of emacs.
1215 """
1215 """
1216 prefix = text.rpartition('.')[0]
1216 prefix = text.rpartition('.')[0]
1217 with provisionalcompleter():
1217 with provisionalcompleter():
1218 return ['.'.join([prefix, c.text]) if prefix and self.use_jedi else c.text
1218 return ['.'.join([prefix, c.text]) if prefix and self.use_jedi else c.text
1219 for c in self.completions(text, len(text))]
1219 for c in self.completions(text, len(text))]
1220
1220
1221 return self.complete(text)[1]
1221 return self.complete(text)[1]
1222
1222
1223 def _clean_glob(self, text:str):
1223 def _clean_glob(self, text:str):
1224 return self.glob("%s*" % text)
1224 return self.glob("%s*" % text)
1225
1225
1226 def _clean_glob_win32(self, text:str):
1226 def _clean_glob_win32(self, text:str):
1227 return [f.replace("\\","/")
1227 return [f.replace("\\","/")
1228 for f in self.glob("%s*" % text)]
1228 for f in self.glob("%s*" % text)]
1229
1229
1230 def file_matches(self, text:str)->List[str]:
1230 def file_matches(self, text:str)->List[str]:
1231 """Match filenames, expanding ~USER type strings.
1231 """Match filenames, expanding ~USER type strings.
1232
1232
1233 Most of the seemingly convoluted logic in this completer is an
1233 Most of the seemingly convoluted logic in this completer is an
1234 attempt to handle filenames with spaces in them. And yet it's not
1234 attempt to handle filenames with spaces in them. And yet it's not
1235 quite perfect, because Python's readline doesn't expose all of the
1235 quite perfect, because Python's readline doesn't expose all of the
1236 GNU readline details needed for this to be done correctly.
1236 GNU readline details needed for this to be done correctly.
1237
1237
1238 For a filename with a space in it, the printed completions will be
1238 For a filename with a space in it, the printed completions will be
1239 only the parts after what's already been typed (instead of the
1239 only the parts after what's already been typed (instead of the
1240 full completions, as is normally done). I don't think with the
1240 full completions, as is normally done). I don't think with the
1241 current (as of Python 2.3) Python readline it's possible to do
1241 current (as of Python 2.3) Python readline it's possible to do
1242 better."""
1242 better."""
1243
1243
1244 # chars that require escaping with backslash - i.e. chars
1244 # chars that require escaping with backslash - i.e. chars
1245 # that readline treats incorrectly as delimiters, but we
1245 # that readline treats incorrectly as delimiters, but we
1246 # don't want to treat as delimiters in filename matching
1246 # don't want to treat as delimiters in filename matching
1247 # when escaped with backslash
1247 # when escaped with backslash
1248 if text.startswith('!'):
1248 if text.startswith('!'):
1249 text = text[1:]
1249 text = text[1:]
1250 text_prefix = u'!'
1250 text_prefix = u'!'
1251 else:
1251 else:
1252 text_prefix = u''
1252 text_prefix = u''
1253
1253
1254 text_until_cursor = self.text_until_cursor
1254 text_until_cursor = self.text_until_cursor
1255 # track strings with open quotes
1255 # track strings with open quotes
1256 open_quotes = has_open_quotes(text_until_cursor)
1256 open_quotes = has_open_quotes(text_until_cursor)
1257
1257
1258 if '(' in text_until_cursor or '[' in text_until_cursor:
1258 if '(' in text_until_cursor or '[' in text_until_cursor:
1259 lsplit = text
1259 lsplit = text
1260 else:
1260 else:
1261 try:
1261 try:
1262 # arg_split ~ shlex.split, but with unicode bugs fixed by us
1262 # arg_split ~ shlex.split, but with unicode bugs fixed by us
1263 lsplit = arg_split(text_until_cursor)[-1]
1263 lsplit = arg_split(text_until_cursor)[-1]
1264 except ValueError:
1264 except ValueError:
1265 # typically an unmatched ", or backslash without escaped char.
1265 # typically an unmatched ", or backslash without escaped char.
1266 if open_quotes:
1266 if open_quotes:
1267 lsplit = text_until_cursor.split(open_quotes)[-1]
1267 lsplit = text_until_cursor.split(open_quotes)[-1]
1268 else:
1268 else:
1269 return []
1269 return []
1270 except IndexError:
1270 except IndexError:
1271 # tab pressed on empty line
1271 # tab pressed on empty line
1272 lsplit = ""
1272 lsplit = ""
1273
1273
1274 if not open_quotes and lsplit != protect_filename(lsplit):
1274 if not open_quotes and lsplit != protect_filename(lsplit):
1275 # if protectables are found, do matching on the whole escaped name
1275 # if protectables are found, do matching on the whole escaped name
1276 has_protectables = True
1276 has_protectables = True
1277 text0,text = text,lsplit
1277 text0,text = text,lsplit
1278 else:
1278 else:
1279 has_protectables = False
1279 has_protectables = False
1280 text = os.path.expanduser(text)
1280 text = os.path.expanduser(text)
1281
1281
1282 if text == "":
1282 if text == "":
1283 return [text_prefix + protect_filename(f) for f in self.glob("*")]
1283 return [text_prefix + protect_filename(f) for f in self.glob("*")]
1284
1284
1285 # Compute the matches from the filesystem
1285 # Compute the matches from the filesystem
1286 if sys.platform == 'win32':
1286 if sys.platform == 'win32':
1287 m0 = self.clean_glob(text)
1287 m0 = self.clean_glob(text)
1288 else:
1288 else:
1289 m0 = self.clean_glob(text.replace('\\', ''))
1289 m0 = self.clean_glob(text.replace('\\', ''))
1290
1290
1291 if has_protectables:
1291 if has_protectables:
1292 # If we had protectables, we need to revert our changes to the
1292 # If we had protectables, we need to revert our changes to the
1293 # beginning of filename so that we don't double-write the part
1293 # beginning of filename so that we don't double-write the part
1294 # of the filename we have so far
1294 # of the filename we have so far
1295 len_lsplit = len(lsplit)
1295 len_lsplit = len(lsplit)
1296 matches = [text_prefix + text0 +
1296 matches = [text_prefix + text0 +
1297 protect_filename(f[len_lsplit:]) for f in m0]
1297 protect_filename(f[len_lsplit:]) for f in m0]
1298 else:
1298 else:
1299 if open_quotes:
1299 if open_quotes:
1300 # if we have a string with an open quote, we don't need to
1300 # if we have a string with an open quote, we don't need to
1301 # protect the names beyond the quote (and we _shouldn't_, as
1301 # protect the names beyond the quote (and we _shouldn't_, as
1302 # it would cause bugs when the filesystem call is made).
1302 # it would cause bugs when the filesystem call is made).
1303 matches = m0 if sys.platform == "win32" else\
1303 matches = m0 if sys.platform == "win32" else\
1304 [protect_filename(f, open_quotes) for f in m0]
1304 [protect_filename(f, open_quotes) for f in m0]
1305 else:
1305 else:
1306 matches = [text_prefix +
1306 matches = [text_prefix +
1307 protect_filename(f) for f in m0]
1307 protect_filename(f) for f in m0]
1308
1308
1309 # Mark directories in input list by appending '/' to their names.
1309 # Mark directories in input list by appending '/' to their names.
1310 return [x+'/' if os.path.isdir(x) else x for x in matches]
1310 return [x+'/' if os.path.isdir(x) else x for x in matches]
1311
1311
1312 def magic_matches(self, text:str):
1312 def magic_matches(self, text:str):
1313 """Match magics"""
1313 """Match magics"""
1314 # Get all shell magics now rather than statically, so magics loaded at
1314 # Get all shell magics now rather than statically, so magics loaded at
1315 # runtime show up too.
1315 # runtime show up too.
1316 lsm = self.shell.magics_manager.lsmagic()
1316 lsm = self.shell.magics_manager.lsmagic()
1317 line_magics = lsm['line']
1317 line_magics = lsm['line']
1318 cell_magics = lsm['cell']
1318 cell_magics = lsm['cell']
1319 pre = self.magic_escape
1319 pre = self.magic_escape
1320 pre2 = pre+pre
1320 pre2 = pre+pre
1321
1321
1322 explicit_magic = text.startswith(pre)
1322 explicit_magic = text.startswith(pre)
1323
1323
1324 # Completion logic:
1324 # Completion logic:
1325 # - user gives %%: only do cell magics
1325 # - user gives %%: only do cell magics
1326 # - user gives %: do both line and cell magics
1326 # - user gives %: do both line and cell magics
1327 # - no prefix: do both
1327 # - no prefix: do both
1328 # In other words, line magics are skipped if the user gives %% explicitly
1328 # In other words, line magics are skipped if the user gives %% explicitly
1329 #
1329 #
1330 # We also exclude magics that match any currently visible names:
1330 # We also exclude magics that match any currently visible names:
1331 # https://github.com/ipython/ipython/issues/4877, unless the user has
1331 # https://github.com/ipython/ipython/issues/4877, unless the user has
1332 # typed a %:
1332 # typed a %:
1333 # https://github.com/ipython/ipython/issues/10754
1333 # https://github.com/ipython/ipython/issues/10754
1334 bare_text = text.lstrip(pre)
1334 bare_text = text.lstrip(pre)
1335 global_matches = self.global_matches(bare_text)
1335 global_matches = self.global_matches(bare_text)
1336 if not explicit_magic:
1336 if not explicit_magic:
1337 def matches(magic):
1337 def matches(magic):
1338 """
1338 """
1339 Filter magics, in particular remove magics that match
1339 Filter magics, in particular remove magics that match
1340 a name present in global namespace.
1340 a name present in global namespace.
1341 """
1341 """
1342 return ( magic.startswith(bare_text) and
1342 return ( magic.startswith(bare_text) and
1343 magic not in global_matches )
1343 magic not in global_matches )
1344 else:
1344 else:
1345 def matches(magic):
1345 def matches(magic):
1346 return magic.startswith(bare_text)
1346 return magic.startswith(bare_text)
1347
1347
1348 comp = [ pre2+m for m in cell_magics if matches(m)]
1348 comp = [ pre2+m for m in cell_magics if matches(m)]
1349 if not text.startswith(pre2):
1349 if not text.startswith(pre2):
1350 comp += [ pre+m for m in line_magics if matches(m)]
1350 comp += [ pre+m for m in line_magics if matches(m)]
1351
1351
1352 return comp
1352 return comp
1353
1353
1354 def magic_config_matches(self, text:str) -> List[str]:
1354 def magic_config_matches(self, text:str) -> List[str]:
1355 """ Match class names and attributes for %config magic """
1355 """ Match class names and attributes for %config magic """
1356 texts = text.strip().split()
1356 texts = text.strip().split()
1357
1357
1358 if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'):
1358 if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'):
1359 # get all configuration classes
1359 # get all configuration classes
1360 classes = sorted(set([ c for c in self.shell.configurables
1360 classes = sorted(set([ c for c in self.shell.configurables
1361 if c.__class__.class_traits(config=True)
1361 if c.__class__.class_traits(config=True)
1362 ]), key=lambda x: x.__class__.__name__)
1362 ]), key=lambda x: x.__class__.__name__)
1363 classnames = [ c.__class__.__name__ for c in classes ]
1363 classnames = [ c.__class__.__name__ for c in classes ]
1364
1364
1365 # return all classnames if config or %config is given
1365 # return all classnames if config or %config is given
1366 if len(texts) == 1:
1366 if len(texts) == 1:
1367 return classnames
1367 return classnames
1368
1368
1369 # match classname
1369 # match classname
1370 classname_texts = texts[1].split('.')
1370 classname_texts = texts[1].split('.')
1371 classname = classname_texts[0]
1371 classname = classname_texts[0]
1372 classname_matches = [ c for c in classnames
1372 classname_matches = [ c for c in classnames
1373 if c.startswith(classname) ]
1373 if c.startswith(classname) ]
1374
1374
1375 # return matched classes or the matched class with attributes
1375 # return matched classes or the matched class with attributes
1376 if texts[1].find('.') < 0:
1376 if texts[1].find('.') < 0:
1377 return classname_matches
1377 return classname_matches
1378 elif len(classname_matches) == 1 and \
1378 elif len(classname_matches) == 1 and \
1379 classname_matches[0] == classname:
1379 classname_matches[0] == classname:
1380 cls = classes[classnames.index(classname)].__class__
1380 cls = classes[classnames.index(classname)].__class__
1381 help = cls.class_get_help()
1381 help = cls.class_get_help()
1382 # strip leading '--' from cl-args:
1382 # strip leading '--' from cl-args:
1383 help = re.sub(re.compile(r'^--', re.MULTILINE), '', help)
1383 help = re.sub(re.compile(r'^--', re.MULTILINE), '', help)
1384 return [ attr.split('=')[0]
1384 return [ attr.split('=')[0]
1385 for attr in help.strip().splitlines()
1385 for attr in help.strip().splitlines()
1386 if attr.startswith(texts[1]) ]
1386 if attr.startswith(texts[1]) ]
1387 return []
1387 return []
1388
1388
1389 def magic_color_matches(self, text:str) -> List[str] :
1389 def magic_color_matches(self, text:str) -> List[str] :
1390 """ Match color schemes for %colors magic"""
1390 """ Match color schemes for %colors magic"""
1391 texts = text.split()
1391 texts = text.split()
1392 if text.endswith(' '):
1392 if text.endswith(' '):
1393 # .split() strips off the trailing whitespace. Add '' back
1393 # .split() strips off the trailing whitespace. Add '' back
1394 # so that: '%colors ' -> ['%colors', '']
1394 # so that: '%colors ' -> ['%colors', '']
1395 texts.append('')
1395 texts.append('')
1396
1396
1397 if len(texts) == 2 and (texts[0] == 'colors' or texts[0] == '%colors'):
1397 if len(texts) == 2 and (texts[0] == 'colors' or texts[0] == '%colors'):
1398 prefix = texts[1]
1398 prefix = texts[1]
1399 return [ color for color in InspectColors.keys()
1399 return [ color for color in InspectColors.keys()
1400 if color.startswith(prefix) ]
1400 if color.startswith(prefix) ]
1401 return []
1401 return []
1402
1402
1403 def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str) -> Iterable[Any]:
1403 def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str) -> Iterable[Any]:
1404 """
1404 """
1405 Return a list of :any:`jedi.api.Completions` object from a ``text`` and
1405 Return a list of :any:`jedi.api.Completions` object from a ``text`` and
1406 cursor position.
1406 cursor position.
1407
1407
1408 Parameters
1408 Parameters
1409 ----------
1409 ----------
1410 cursor_column : int
1410 cursor_column : int
1411 column position of the cursor in ``text``, 0-indexed.
1411 column position of the cursor in ``text``, 0-indexed.
1412 cursor_line : int
1412 cursor_line : int
1413 line position of the cursor in ``text``, 0-indexed
1413 line position of the cursor in ``text``, 0-indexed
1414 text : str
1414 text : str
1415 text to complete
1415 text to complete
1416
1416
1417 Notes
1417 Notes
1418 -----
1418 -----
1419 If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion`
1419 If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion`
1420 object containing a string with the Jedi debug information attached.
1420 object containing a string with the Jedi debug information attached.
1421 """
1421 """
1422 namespaces = [self.namespace]
1422 namespaces = [self.namespace]
1423 if self.global_namespace is not None:
1423 if self.global_namespace is not None:
1424 namespaces.append(self.global_namespace)
1424 namespaces.append(self.global_namespace)
1425
1425
1426 completion_filter = lambda x:x
1426 completion_filter = lambda x:x
1427 offset = cursor_to_position(text, cursor_line, cursor_column)
1427 offset = cursor_to_position(text, cursor_line, cursor_column)
1428 # filter output if we are completing for object members
1428 # filter output if we are completing for object members
1429 if offset:
1429 if offset:
1430 pre = text[offset-1]
1430 pre = text[offset-1]
1431 if pre == '.':
1431 if pre == '.':
1432 if self.omit__names == 2:
1432 if self.omit__names == 2:
1433 completion_filter = lambda c:not c.name.startswith('_')
1433 completion_filter = lambda c:not c.name.startswith('_')
1434 elif self.omit__names == 1:
1434 elif self.omit__names == 1:
1435 completion_filter = lambda c:not (c.name.startswith('__') and c.name.endswith('__'))
1435 completion_filter = lambda c:not (c.name.startswith('__') and c.name.endswith('__'))
1436 elif self.omit__names == 0:
1436 elif self.omit__names == 0:
1437 completion_filter = lambda x:x
1437 completion_filter = lambda x:x
1438 else:
1438 else:
1439 raise ValueError("Don't understand self.omit__names == {}".format(self.omit__names))
1439 raise ValueError("Don't understand self.omit__names == {}".format(self.omit__names))
1440
1440
1441 interpreter = jedi.Interpreter(text[:offset], namespaces)
1441 interpreter = jedi.Interpreter(text[:offset], namespaces)
1442 try_jedi = True
1442 try_jedi = True
1443
1443
1444 try:
1444 try:
1445 # find the first token in the current tree -- if it is a ' or " then we are in a string
1445 # find the first token in the current tree -- if it is a ' or " then we are in a string
1446 completing_string = False
1446 completing_string = False
1447 try:
1447 try:
1448 first_child = next(c for c in interpreter._get_module().tree_node.children if hasattr(c, 'value'))
1448 first_child = next(c for c in interpreter._get_module().tree_node.children if hasattr(c, 'value'))
1449 except StopIteration:
1449 except StopIteration:
1450 pass
1450 pass
1451 else:
1451 else:
1452 # note the value may be ', ", or it may also be ''' or """, or
1452 # note the value may be ', ", or it may also be ''' or """, or
1453 # in some cases, """what/you/typed..., but all of these are
1453 # in some cases, """what/you/typed..., but all of these are
1454 # strings.
1454 # strings.
1455 completing_string = len(first_child.value) > 0 and first_child.value[0] in {"'", '"'}
1455 completing_string = len(first_child.value) > 0 and first_child.value[0] in {"'", '"'}
1456
1456
1457 # if we are in a string jedi is likely not the right candidate for
1457 # if we are in a string jedi is likely not the right candidate for
1458 # now. Skip it.
1458 # now. Skip it.
1459 try_jedi = not completing_string
1459 try_jedi = not completing_string
1460 except Exception as e:
1460 except Exception as e:
1461 # many of things can go wrong, we are using private API just don't crash.
1461 # many of things can go wrong, we are using private API just don't crash.
1462 if self.debug:
1462 if self.debug:
1463 print("Error detecting if completing a non-finished string :", e, '|')
1463 print("Error detecting if completing a non-finished string :", e, '|')
1464
1464
1465 if not try_jedi:
1465 if not try_jedi:
1466 return []
1466 return []
1467 try:
1467 try:
1468 return filter(completion_filter, interpreter.complete(column=cursor_column, line=cursor_line + 1))
1468 return filter(completion_filter, interpreter.complete(column=cursor_column, line=cursor_line + 1))
1469 except Exception as e:
1469 except Exception as e:
1470 if self.debug:
1470 if self.debug:
1471 return [_FakeJediCompletion('Oops Jedi has crashed, please report a bug with the following:\n"""\n%s\ns"""' % (e))]
1471 return [_FakeJediCompletion('Oops Jedi has crashed, please report a bug with the following:\n"""\n%s\ns"""' % (e))]
1472 else:
1472 else:
1473 return []
1473 return []
1474
1474
1475 def python_matches(self, text:str)->List[str]:
1475 def python_matches(self, text:str)->List[str]:
1476 """Match attributes or global python names"""
1476 """Match attributes or global python names"""
1477 if "." in text:
1477 if "." in text:
1478 try:
1478 try:
1479 matches = self.attr_matches(text)
1479 matches = self.attr_matches(text)
1480 if text.endswith('.') and self.omit__names:
1480 if text.endswith('.') and self.omit__names:
1481 if self.omit__names == 1:
1481 if self.omit__names == 1:
1482 # true if txt is _not_ a __ name, false otherwise:
1482 # true if txt is _not_ a __ name, false otherwise:
1483 no__name = (lambda txt:
1483 no__name = (lambda txt:
1484 re.match(r'.*\.__.*?__',txt) is None)
1484 re.match(r'.*\.__.*?__',txt) is None)
1485 else:
1485 else:
1486 # true if txt is _not_ a _ name, false otherwise:
1486 # true if txt is _not_ a _ name, false otherwise:
1487 no__name = (lambda txt:
1487 no__name = (lambda txt:
1488 re.match(r'\._.*?',txt[txt.rindex('.'):]) is None)
1488 re.match(r'\._.*?',txt[txt.rindex('.'):]) is None)
1489 matches = filter(no__name, matches)
1489 matches = filter(no__name, matches)
1490 except NameError:
1490 except NameError:
1491 # catches <undefined attributes>.<tab>
1491 # catches <undefined attributes>.<tab>
1492 matches = []
1492 matches = []
1493 else:
1493 else:
1494 matches = self.global_matches(text)
1494 matches = self.global_matches(text)
1495 return matches
1495 return matches
1496
1496
1497 def _default_arguments_from_docstring(self, doc):
1497 def _default_arguments_from_docstring(self, doc):
1498 """Parse the first line of docstring for call signature.
1498 """Parse the first line of docstring for call signature.
1499
1499
1500 Docstring should be of the form 'min(iterable[, key=func])\n'.
1500 Docstring should be of the form 'min(iterable[, key=func])\n'.
1501 It can also parse cython docstring of the form
1501 It can also parse cython docstring of the form
1502 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)'.
1502 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)'.
1503 """
1503 """
1504 if doc is None:
1504 if doc is None:
1505 return []
1505 return []
1506
1506
1507 #care only the firstline
1507 #care only the firstline
1508 line = doc.lstrip().splitlines()[0]
1508 line = doc.lstrip().splitlines()[0]
1509
1509
1510 #p = re.compile(r'^[\w|\s.]+\(([^)]*)\).*')
1510 #p = re.compile(r'^[\w|\s.]+\(([^)]*)\).*')
1511 #'min(iterable[, key=func])\n' -> 'iterable[, key=func]'
1511 #'min(iterable[, key=func])\n' -> 'iterable[, key=func]'
1512 sig = self.docstring_sig_re.search(line)
1512 sig = self.docstring_sig_re.search(line)
1513 if sig is None:
1513 if sig is None:
1514 return []
1514 return []
1515 # iterable[, key=func]' -> ['iterable[' ,' key=func]']
1515 # iterable[, key=func]' -> ['iterable[' ,' key=func]']
1516 sig = sig.groups()[0].split(',')
1516 sig = sig.groups()[0].split(',')
1517 ret = []
1517 ret = []
1518 for s in sig:
1518 for s in sig:
1519 #re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)')
1519 #re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)')
1520 ret += self.docstring_kwd_re.findall(s)
1520 ret += self.docstring_kwd_re.findall(s)
1521 return ret
1521 return ret
1522
1522
1523 def _default_arguments(self, obj):
1523 def _default_arguments(self, obj):
1524 """Return the list of default arguments of obj if it is callable,
1524 """Return the list of default arguments of obj if it is callable,
1525 or empty list otherwise."""
1525 or empty list otherwise."""
1526 call_obj = obj
1526 call_obj = obj
1527 ret = []
1527 ret = []
1528 if inspect.isbuiltin(obj):
1528 if inspect.isbuiltin(obj):
1529 pass
1529 pass
1530 elif not (inspect.isfunction(obj) or inspect.ismethod(obj)):
1530 elif not (inspect.isfunction(obj) or inspect.ismethod(obj)):
1531 if inspect.isclass(obj):
1531 if inspect.isclass(obj):
1532 #for cython embedsignature=True the constructor docstring
1532 #for cython embedsignature=True the constructor docstring
1533 #belongs to the object itself not __init__
1533 #belongs to the object itself not __init__
1534 ret += self._default_arguments_from_docstring(
1534 ret += self._default_arguments_from_docstring(
1535 getattr(obj, '__doc__', ''))
1535 getattr(obj, '__doc__', ''))
1536 # for classes, check for __init__,__new__
1536 # for classes, check for __init__,__new__
1537 call_obj = (getattr(obj, '__init__', None) or
1537 call_obj = (getattr(obj, '__init__', None) or
1538 getattr(obj, '__new__', None))
1538 getattr(obj, '__new__', None))
1539 # for all others, check if they are __call__able
1539 # for all others, check if they are __call__able
1540 elif hasattr(obj, '__call__'):
1540 elif hasattr(obj, '__call__'):
1541 call_obj = obj.__call__
1541 call_obj = obj.__call__
1542 ret += self._default_arguments_from_docstring(
1542 ret += self._default_arguments_from_docstring(
1543 getattr(call_obj, '__doc__', ''))
1543 getattr(call_obj, '__doc__', ''))
1544
1544
1545 _keeps = (inspect.Parameter.KEYWORD_ONLY,
1545 _keeps = (inspect.Parameter.KEYWORD_ONLY,
1546 inspect.Parameter.POSITIONAL_OR_KEYWORD)
1546 inspect.Parameter.POSITIONAL_OR_KEYWORD)
1547
1547
1548 try:
1548 try:
1549 sig = inspect.signature(obj)
1549 sig = inspect.signature(obj)
1550 ret.extend(k for k, v in sig.parameters.items() if
1550 ret.extend(k for k, v in sig.parameters.items() if
1551 v.kind in _keeps)
1551 v.kind in _keeps)
1552 except ValueError:
1552 except ValueError:
1553 pass
1553 pass
1554
1554
1555 return list(set(ret))
1555 return list(set(ret))
1556
1556
1557 def python_func_kw_matches(self, text):
1557 def python_func_kw_matches(self, text):
1558 """Match named parameters (kwargs) of the last open function"""
1558 """Match named parameters (kwargs) of the last open function"""
1559
1559
1560 if "." in text: # a parameter cannot be dotted
1560 if "." in text: # a parameter cannot be dotted
1561 return []
1561 return []
1562 try: regexp = self.__funcParamsRegex
1562 try: regexp = self.__funcParamsRegex
1563 except AttributeError:
1563 except AttributeError:
1564 regexp = self.__funcParamsRegex = re.compile(r'''
1564 regexp = self.__funcParamsRegex = re.compile(r'''
1565 '.*?(?<!\\)' | # single quoted strings or
1565 '.*?(?<!\\)' | # single quoted strings or
1566 ".*?(?<!\\)" | # double quoted strings or
1566 ".*?(?<!\\)" | # double quoted strings or
1567 \w+ | # identifier
1567 \w+ | # identifier
1568 \S # other characters
1568 \S # other characters
1569 ''', re.VERBOSE | re.DOTALL)
1569 ''', re.VERBOSE | re.DOTALL)
1570 # 1. find the nearest identifier that comes before an unclosed
1570 # 1. find the nearest identifier that comes before an unclosed
1571 # parenthesis before the cursor
1571 # parenthesis before the cursor
1572 # e.g. for "foo (1+bar(x), pa<cursor>,a=1)", the candidate is "foo"
1572 # e.g. for "foo (1+bar(x), pa<cursor>,a=1)", the candidate is "foo"
1573 tokens = regexp.findall(self.text_until_cursor)
1573 tokens = regexp.findall(self.text_until_cursor)
1574 iterTokens = reversed(tokens); openPar = 0
1574 iterTokens = reversed(tokens); openPar = 0
1575
1575
1576 for token in iterTokens:
1576 for token in iterTokens:
1577 if token == ')':
1577 if token == ')':
1578 openPar -= 1
1578 openPar -= 1
1579 elif token == '(':
1579 elif token == '(':
1580 openPar += 1
1580 openPar += 1
1581 if openPar > 0:
1581 if openPar > 0:
1582 # found the last unclosed parenthesis
1582 # found the last unclosed parenthesis
1583 break
1583 break
1584 else:
1584 else:
1585 return []
1585 return []
1586 # 2. Concatenate dotted names ("foo.bar" for "foo.bar(x, pa" )
1586 # 2. Concatenate dotted names ("foo.bar" for "foo.bar(x, pa" )
1587 ids = []
1587 ids = []
1588 isId = re.compile(r'\w+$').match
1588 isId = re.compile(r'\w+$').match
1589
1589
1590 while True:
1590 while True:
1591 try:
1591 try:
1592 ids.append(next(iterTokens))
1592 ids.append(next(iterTokens))
1593 if not isId(ids[-1]):
1593 if not isId(ids[-1]):
1594 ids.pop(); break
1594 ids.pop(); break
1595 if not next(iterTokens) == '.':
1595 if not next(iterTokens) == '.':
1596 break
1596 break
1597 except StopIteration:
1597 except StopIteration:
1598 break
1598 break
1599
1599
1600 # Find all named arguments already assigned to, as to avoid suggesting
1600 # Find all named arguments already assigned to, as to avoid suggesting
1601 # them again
1601 # them again
1602 usedNamedArgs = set()
1602 usedNamedArgs = set()
1603 par_level = -1
1603 par_level = -1
1604 for token, next_token in zip(tokens, tokens[1:]):
1604 for token, next_token in zip(tokens, tokens[1:]):
1605 if token == '(':
1605 if token == '(':
1606 par_level += 1
1606 par_level += 1
1607 elif token == ')':
1607 elif token == ')':
1608 par_level -= 1
1608 par_level -= 1
1609
1609
1610 if par_level != 0:
1610 if par_level != 0:
1611 continue
1611 continue
1612
1612
1613 if next_token != '=':
1613 if next_token != '=':
1614 continue
1614 continue
1615
1615
1616 usedNamedArgs.add(token)
1616 usedNamedArgs.add(token)
1617
1617
1618 argMatches = []
1618 argMatches = []
1619 try:
1619 try:
1620 callableObj = '.'.join(ids[::-1])
1620 callableObj = '.'.join(ids[::-1])
1621 namedArgs = self._default_arguments(eval(callableObj,
1621 namedArgs = self._default_arguments(eval(callableObj,
1622 self.namespace))
1622 self.namespace))
1623
1623
1624 # Remove used named arguments from the list, no need to show twice
1624 # Remove used named arguments from the list, no need to show twice
1625 for namedArg in set(namedArgs) - usedNamedArgs:
1625 for namedArg in set(namedArgs) - usedNamedArgs:
1626 if namedArg.startswith(text):
1626 if namedArg.startswith(text):
1627 argMatches.append("%s=" %namedArg)
1627 argMatches.append("%s=" %namedArg)
1628 except:
1628 except:
1629 pass
1629 pass
1630
1630
1631 return argMatches
1631 return argMatches
1632
1632
1633 @staticmethod
1633 @staticmethod
1634 def _get_keys(obj: Any) -> List[Any]:
1634 def _get_keys(obj: Any) -> List[Any]:
1635 # Objects can define their own completions by defining an
1635 # Objects can define their own completions by defining an
1636 # _ipy_key_completions_() method.
1636 # _ipy_key_completions_() method.
1637 method = get_real_method(obj, '_ipython_key_completions_')
1637 method = get_real_method(obj, '_ipython_key_completions_')
1638 if method is not None:
1638 if method is not None:
1639 return method()
1639 return method()
1640
1640
1641 # Special case some common in-memory dict-like types
1641 # Special case some common in-memory dict-like types
1642 if isinstance(obj, dict) or\
1642 if isinstance(obj, dict) or\
1643 _safe_isinstance(obj, 'pandas', 'DataFrame'):
1643 _safe_isinstance(obj, 'pandas', 'DataFrame'):
1644 try:
1644 try:
1645 return list(obj.keys())
1645 return list(obj.keys())
1646 except Exception:
1646 except Exception:
1647 return []
1647 return []
1648 elif _safe_isinstance(obj, 'numpy', 'ndarray') or\
1648 elif _safe_isinstance(obj, 'numpy', 'ndarray') or\
1649 _safe_isinstance(obj, 'numpy', 'void'):
1649 _safe_isinstance(obj, 'numpy', 'void'):
1650 return obj.dtype.names or []
1650 return obj.dtype.names or []
1651 return []
1651 return []
1652
1652
1653 def dict_key_matches(self, text:str) -> List[str]:
1653 def dict_key_matches(self, text:str) -> List[str]:
1654 "Match string keys in a dictionary, after e.g. 'foo[' "
1654 "Match string keys in a dictionary, after e.g. 'foo[' "
1655
1655
1656
1656
1657 if self.__dict_key_regexps is not None:
1657 if self.__dict_key_regexps is not None:
1658 regexps = self.__dict_key_regexps
1658 regexps = self.__dict_key_regexps
1659 else:
1659 else:
1660 dict_key_re_fmt = r'''(?x)
1660 dict_key_re_fmt = r'''(?x)
1661 ( # match dict-referring expression wrt greedy setting
1661 ( # match dict-referring expression wrt greedy setting
1662 %s
1662 %s
1663 )
1663 )
1664 \[ # open bracket
1664 \[ # open bracket
1665 \s* # and optional whitespace
1665 \s* # and optional whitespace
1666 # Capture any number of str-like objects (e.g. "a", "b", 'c')
1666 # Capture any number of str-like objects (e.g. "a", "b", 'c')
1667 ((?:[uUbB]? # string prefix (r not handled)
1667 ((?:[uUbB]? # string prefix (r not handled)
1668 (?:
1668 (?:
1669 '(?:[^']|(?<!\\)\\')*'
1669 '(?:[^']|(?<!\\)\\')*'
1670 |
1670 |
1671 "(?:[^"]|(?<!\\)\\")*"
1671 "(?:[^"]|(?<!\\)\\")*"
1672 )
1672 )
1673 \s*,\s*
1673 \s*,\s*
1674 )*)
1674 )*)
1675 ([uUbB]? # string prefix (r not handled)
1675 ([uUbB]? # string prefix (r not handled)
1676 (?: # unclosed string
1676 (?: # unclosed string
1677 '(?:[^']|(?<!\\)\\')*
1677 '(?:[^']|(?<!\\)\\')*
1678 |
1678 |
1679 "(?:[^"]|(?<!\\)\\")*
1679 "(?:[^"]|(?<!\\)\\")*
1680 )
1680 )
1681 )?
1681 )?
1682 $
1682 $
1683 '''
1683 '''
1684 regexps = self.__dict_key_regexps = {
1684 regexps = self.__dict_key_regexps = {
1685 False: re.compile(dict_key_re_fmt % r'''
1685 False: re.compile(dict_key_re_fmt % r'''
1686 # identifiers separated by .
1686 # identifiers separated by .
1687 (?!\d)\w+
1687 (?!\d)\w+
1688 (?:\.(?!\d)\w+)*
1688 (?:\.(?!\d)\w+)*
1689 '''),
1689 '''),
1690 True: re.compile(dict_key_re_fmt % '''
1690 True: re.compile(dict_key_re_fmt % '''
1691 .+
1691 .+
1692 ''')
1692 ''')
1693 }
1693 }
1694
1694
1695 match = regexps[self.greedy].search(self.text_until_cursor)
1695 match = regexps[self.greedy].search(self.text_until_cursor)
1696
1696
1697 if match is None:
1697 if match is None:
1698 return []
1698 return []
1699
1699
1700 expr, prefix0, prefix = match.groups()
1700 expr, prefix0, prefix = match.groups()
1701 try:
1701 try:
1702 obj = eval(expr, self.namespace)
1702 obj = eval(expr, self.namespace)
1703 except Exception:
1703 except Exception:
1704 try:
1704 try:
1705 obj = eval(expr, self.global_namespace)
1705 obj = eval(expr, self.global_namespace)
1706 except Exception:
1706 except Exception:
1707 return []
1707 return []
1708
1708
1709 keys = self._get_keys(obj)
1709 keys = self._get_keys(obj)
1710 if not keys:
1710 if not keys:
1711 return keys
1711 return keys
1712
1712
1713 extra_prefix = eval(prefix0) if prefix0 != '' else None
1713 extra_prefix = eval(prefix0) if prefix0 != '' else None
1714
1714
1715 closing_quote, token_offset, matches = match_dict_keys(keys, prefix, self.splitter.delims, extra_prefix=extra_prefix)
1715 closing_quote, token_offset, matches = match_dict_keys(keys, prefix, self.splitter.delims, extra_prefix=extra_prefix)
1716 if not matches:
1716 if not matches:
1717 return matches
1717 return matches
1718
1718
1719 # get the cursor position of
1719 # get the cursor position of
1720 # - the text being completed
1720 # - the text being completed
1721 # - the start of the key text
1721 # - the start of the key text
1722 # - the start of the completion
1722 # - the start of the completion
1723 text_start = len(self.text_until_cursor) - len(text)
1723 text_start = len(self.text_until_cursor) - len(text)
1724 if prefix:
1724 if prefix:
1725 key_start = match.start(3)
1725 key_start = match.start(3)
1726 completion_start = key_start + token_offset
1726 completion_start = key_start + token_offset
1727 else:
1727 else:
1728 key_start = completion_start = match.end()
1728 key_start = completion_start = match.end()
1729
1729
1730 # grab the leading prefix, to make sure all completions start with `text`
1730 # grab the leading prefix, to make sure all completions start with `text`
1731 if text_start > key_start:
1731 if text_start > key_start:
1732 leading = ''
1732 leading = ''
1733 else:
1733 else:
1734 leading = text[text_start:completion_start]
1734 leading = text[text_start:completion_start]
1735
1735
1736 # the index of the `[` character
1736 # the index of the `[` character
1737 bracket_idx = match.end(1)
1737 bracket_idx = match.end(1)
1738
1738
1739 # append closing quote and bracket as appropriate
1739 # append closing quote and bracket as appropriate
1740 # this is *not* appropriate if the opening quote or bracket is outside
1740 # this is *not* appropriate if the opening quote or bracket is outside
1741 # the text given to this method
1741 # the text given to this method
1742 suf = ''
1742 suf = ''
1743 continuation = self.line_buffer[len(self.text_until_cursor):]
1743 continuation = self.line_buffer[len(self.text_until_cursor):]
1744 if key_start > text_start and closing_quote:
1744 if key_start > text_start and closing_quote:
1745 # quotes were opened inside text, maybe close them
1745 # quotes were opened inside text, maybe close them
1746 if continuation.startswith(closing_quote):
1746 if continuation.startswith(closing_quote):
1747 continuation = continuation[len(closing_quote):]
1747 continuation = continuation[len(closing_quote):]
1748 else:
1748 else:
1749 suf += closing_quote
1749 suf += closing_quote
1750 if bracket_idx > text_start:
1750 if bracket_idx > text_start:
1751 # brackets were opened inside text, maybe close them
1751 # brackets were opened inside text, maybe close them
1752 if not continuation.startswith(']'):
1752 if not continuation.startswith(']'):
1753 suf += ']'
1753 suf += ']'
1754
1754
1755 return [leading + k + suf for k in matches]
1755 return [leading + k + suf for k in matches]
1756
1756
1757 @staticmethod
1757 @staticmethod
1758 def unicode_name_matches(text:str) -> Tuple[str, List[str]] :
1758 def unicode_name_matches(text:str) -> Tuple[str, List[str]] :
1759 """Match Latex-like syntax for unicode characters base
1759 """Match Latex-like syntax for unicode characters base
1760 on the name of the character.
1760 on the name of the character.
1761
1761
1762 This does ``\\GREEK SMALL LETTER ETA`` -> ``Ξ·``
1762 This does ``\\GREEK SMALL LETTER ETA`` -> ``Ξ·``
1763
1763
1764 Works only on valid python 3 identifier, or on combining characters that
1764 Works only on valid python 3 identifier, or on combining characters that
1765 will combine to form a valid identifier.
1765 will combine to form a valid identifier.
1766 """
1766 """
1767 slashpos = text.rfind('\\')
1767 slashpos = text.rfind('\\')
1768 if slashpos > -1:
1768 if slashpos > -1:
1769 s = text[slashpos+1:]
1769 s = text[slashpos+1:]
1770 try :
1770 try :
1771 unic = unicodedata.lookup(s)
1771 unic = unicodedata.lookup(s)
1772 # allow combining chars
1772 # allow combining chars
1773 if ('a'+unic).isidentifier():
1773 if ('a'+unic).isidentifier():
1774 return '\\'+s,[unic]
1774 return '\\'+s,[unic]
1775 except KeyError:
1775 except KeyError:
1776 pass
1776 pass
1777 return '', []
1777 return '', []
1778
1778
1779
1779
1780 def latex_matches(self, text:str) -> Tuple[str, Sequence[str]]:
1780 def latex_matches(self, text:str) -> Tuple[str, Sequence[str]]:
1781 """Match Latex syntax for unicode characters.
1781 """Match Latex syntax for unicode characters.
1782
1782
1783 This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``Ξ±``
1783 This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``Ξ±``
1784 """
1784 """
1785 slashpos = text.rfind('\\')
1785 slashpos = text.rfind('\\')
1786 if slashpos > -1:
1786 if slashpos > -1:
1787 s = text[slashpos:]
1787 s = text[slashpos:]
1788 if s in latex_symbols:
1788 if s in latex_symbols:
1789 # Try to complete a full latex symbol to unicode
1789 # Try to complete a full latex symbol to unicode
1790 # \\alpha -> Ξ±
1790 # \\alpha -> Ξ±
1791 return s, [latex_symbols[s]]
1791 return s, [latex_symbols[s]]
1792 else:
1792 else:
1793 # If a user has partially typed a latex symbol, give them
1793 # If a user has partially typed a latex symbol, give them
1794 # a full list of options \al -> [\aleph, \alpha]
1794 # a full list of options \al -> [\aleph, \alpha]
1795 matches = [k for k in latex_symbols if k.startswith(s)]
1795 matches = [k for k in latex_symbols if k.startswith(s)]
1796 if matches:
1796 if matches:
1797 return s, matches
1797 return s, matches
1798 return '', ()
1798 return '', ()
1799
1799
1800 def dispatch_custom_completer(self, text):
1800 def dispatch_custom_completer(self, text):
1801 if not self.custom_completers:
1801 if not self.custom_completers:
1802 return
1802 return
1803
1803
1804 line = self.line_buffer
1804 line = self.line_buffer
1805 if not line.strip():
1805 if not line.strip():
1806 return None
1806 return None
1807
1807
1808 # Create a little structure to pass all the relevant information about
1808 # Create a little structure to pass all the relevant information about
1809 # the current completion to any custom completer.
1809 # the current completion to any custom completer.
1810 event = SimpleNamespace()
1810 event = SimpleNamespace()
1811 event.line = line
1811 event.line = line
1812 event.symbol = text
1812 event.symbol = text
1813 cmd = line.split(None,1)[0]
1813 cmd = line.split(None,1)[0]
1814 event.command = cmd
1814 event.command = cmd
1815 event.text_until_cursor = self.text_until_cursor
1815 event.text_until_cursor = self.text_until_cursor
1816
1816
1817 # for foo etc, try also to find completer for %foo
1817 # for foo etc, try also to find completer for %foo
1818 if not cmd.startswith(self.magic_escape):
1818 if not cmd.startswith(self.magic_escape):
1819 try_magic = self.custom_completers.s_matches(
1819 try_magic = self.custom_completers.s_matches(
1820 self.magic_escape + cmd)
1820 self.magic_escape + cmd)
1821 else:
1821 else:
1822 try_magic = []
1822 try_magic = []
1823
1823
1824 for c in itertools.chain(self.custom_completers.s_matches(cmd),
1824 for c in itertools.chain(self.custom_completers.s_matches(cmd),
1825 try_magic,
1825 try_magic,
1826 self.custom_completers.flat_matches(self.text_until_cursor)):
1826 self.custom_completers.flat_matches(self.text_until_cursor)):
1827 try:
1827 try:
1828 res = c(event)
1828 res = c(event)
1829 if res:
1829 if res:
1830 # first, try case sensitive match
1830 # first, try case sensitive match
1831 withcase = [r for r in res if r.startswith(text)]
1831 withcase = [r for r in res if r.startswith(text)]
1832 if withcase:
1832 if withcase:
1833 return withcase
1833 return withcase
1834 # if none, then case insensitive ones are ok too
1834 # if none, then case insensitive ones are ok too
1835 text_low = text.lower()
1835 text_low = text.lower()
1836 return [r for r in res if r.lower().startswith(text_low)]
1836 return [r for r in res if r.lower().startswith(text_low)]
1837 except TryNext:
1837 except TryNext:
1838 pass
1838 pass
1839 except KeyboardInterrupt:
1839 except KeyboardInterrupt:
1840 """
1840 """
1841 If custom completer take too long,
1841 If custom completer take too long,
1842 let keyboard interrupt abort and return nothing.
1842 let keyboard interrupt abort and return nothing.
1843 """
1843 """
1844 break
1844 break
1845
1845
1846 return None
1846 return None
1847
1847
1848 def completions(self, text: str, offset: int)->Iterator[Completion]:
1848 def completions(self, text: str, offset: int)->Iterator[Completion]:
1849 """
1849 """
1850 Returns an iterator over the possible completions
1850 Returns an iterator over the possible completions
1851
1851
1852 .. warning::
1852 .. warning::
1853
1853
1854 Unstable
1854 Unstable
1855
1855
1856 This function is unstable, API may change without warning.
1856 This function is unstable, API may change without warning.
1857 It will also raise unless use in proper context manager.
1857 It will also raise unless use in proper context manager.
1858
1858
1859 Parameters
1859 Parameters
1860 ----------
1860 ----------
1861 text : str
1861 text : str
1862 Full text of the current input, multi line string.
1862 Full text of the current input, multi line string.
1863 offset : int
1863 offset : int
1864 Integer representing the position of the cursor in ``text``. Offset
1864 Integer representing the position of the cursor in ``text``. Offset
1865 is 0-based indexed.
1865 is 0-based indexed.
1866
1866
1867 Yields
1867 Yields
1868 ------
1868 ------
1869 Completion
1869 Completion
1870
1870
1871 Notes
1871 Notes
1872 -----
1872 -----
1873 The cursor on a text can either be seen as being "in between"
1873 The cursor on a text can either be seen as being "in between"
1874 characters or "On" a character depending on the interface visible to
1874 characters or "On" a character depending on the interface visible to
1875 the user. For consistency the cursor being on "in between" characters X
1875 the user. For consistency the cursor being on "in between" characters X
1876 and Y is equivalent to the cursor being "on" character Y, that is to say
1876 and Y is equivalent to the cursor being "on" character Y, that is to say
1877 the character the cursor is on is considered as being after the cursor.
1877 the character the cursor is on is considered as being after the cursor.
1878
1878
1879 Combining characters may span more that one position in the
1879 Combining characters may span more that one position in the
1880 text.
1880 text.
1881
1881
1882 .. note::
1882 .. note::
1883
1883
1884 If ``IPCompleter.debug`` is :any:`True` will yield a ``--jedi/ipython--``
1884 If ``IPCompleter.debug`` is :any:`True` will yield a ``--jedi/ipython--``
1885 fake Completion token to distinguish completion returned by Jedi
1885 fake Completion token to distinguish completion returned by Jedi
1886 and usual IPython completion.
1886 and usual IPython completion.
1887
1887
1888 .. note::
1888 .. note::
1889
1889
1890 Completions are not completely deduplicated yet. If identical
1890 Completions are not completely deduplicated yet. If identical
1891 completions are coming from different sources this function does not
1891 completions are coming from different sources this function does not
1892 ensure that each completion object will only be present once.
1892 ensure that each completion object will only be present once.
1893 """
1893 """
1894 warnings.warn("_complete is a provisional API (as of IPython 6.0). "
1894 warnings.warn("_complete is a provisional API (as of IPython 6.0). "
1895 "It may change without warnings. "
1895 "It may change without warnings. "
1896 "Use in corresponding context manager.",
1896 "Use in corresponding context manager.",
1897 category=ProvisionalCompleterWarning, stacklevel=2)
1897 category=ProvisionalCompleterWarning, stacklevel=2)
1898
1898
1899 seen = set()
1899 seen = set()
1900 profiler:Optional[cProfile.Profile]
1900 profiler:Optional[cProfile.Profile]
1901 try:
1901 try:
1902 if self.profile_completions:
1902 if self.profile_completions:
1903 import cProfile
1903 import cProfile
1904 profiler = cProfile.Profile()
1904 profiler = cProfile.Profile()
1905 profiler.enable()
1905 profiler.enable()
1906 else:
1906 else:
1907 profiler = None
1907 profiler = None
1908
1908
1909 for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000):
1909 for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000):
1910 if c and (c in seen):
1910 if c and (c in seen):
1911 continue
1911 continue
1912 yield c
1912 yield c
1913 seen.add(c)
1913 seen.add(c)
1914 except KeyboardInterrupt:
1914 except KeyboardInterrupt:
1915 """if completions take too long and users send keyboard interrupt,
1915 """if completions take too long and users send keyboard interrupt,
1916 do not crash and return ASAP. """
1916 do not crash and return ASAP. """
1917 pass
1917 pass
1918 finally:
1918 finally:
1919 if profiler is not None:
1919 if profiler is not None:
1920 profiler.disable()
1920 profiler.disable()
1921 ensure_dir_exists(self.profiler_output_dir)
1921 ensure_dir_exists(self.profiler_output_dir)
1922 output_path = os.path.join(self.profiler_output_dir, str(uuid.uuid4()))
1922 output_path = os.path.join(self.profiler_output_dir, str(uuid.uuid4()))
1923 print("Writing profiler output to", output_path)
1923 print("Writing profiler output to", output_path)
1924 profiler.dump_stats(output_path)
1924 profiler.dump_stats(output_path)
1925
1925
1926 def _completions(self, full_text: str, offset: int, *, _timeout) -> Iterator[Completion]:
1926 def _completions(self, full_text: str, offset: int, *, _timeout) -> Iterator[Completion]:
1927 """
1927 """
1928 Core completion module.Same signature as :any:`completions`, with the
1928 Core completion module.Same signature as :any:`completions`, with the
1929 extra `timeout` parameter (in seconds).
1929 extra `timeout` parameter (in seconds).
1930
1930
1931 Computing jedi's completion ``.type`` can be quite expensive (it is a
1931 Computing jedi's completion ``.type`` can be quite expensive (it is a
1932 lazy property) and can require some warm-up, more warm up than just
1932 lazy property) and can require some warm-up, more warm up than just
1933 computing the ``name`` of a completion. The warm-up can be :
1933 computing the ``name`` of a completion. The warm-up can be :
1934
1934
1935 - Long warm-up the first time a module is encountered after
1935 - Long warm-up the first time a module is encountered after
1936 install/update: actually build parse/inference tree.
1936 install/update: actually build parse/inference tree.
1937
1937
1938 - first time the module is encountered in a session: load tree from
1938 - first time the module is encountered in a session: load tree from
1939 disk.
1939 disk.
1940
1940
1941 We don't want to block completions for tens of seconds so we give the
1941 We don't want to block completions for tens of seconds so we give the
1942 completer a "budget" of ``_timeout`` seconds per invocation to compute
1942 completer a "budget" of ``_timeout`` seconds per invocation to compute
1943 completions types, the completions that have not yet been computed will
1943 completions types, the completions that have not yet been computed will
1944 be marked as "unknown" an will have a chance to be computed next round
1944 be marked as "unknown" an will have a chance to be computed next round
1945 are things get cached.
1945 are things get cached.
1946
1946
1947 Keep in mind that Jedi is not the only thing treating the completion so
1947 Keep in mind that Jedi is not the only thing treating the completion so
1948 keep the timeout short-ish as if we take more than 0.3 second we still
1948 keep the timeout short-ish as if we take more than 0.3 second we still
1949 have lots of processing to do.
1949 have lots of processing to do.
1950
1950
1951 """
1951 """
1952 deadline = time.monotonic() + _timeout
1952 deadline = time.monotonic() + _timeout
1953
1953
1954
1954
1955 before = full_text[:offset]
1955 before = full_text[:offset]
1956 cursor_line, cursor_column = position_to_cursor(full_text, offset)
1956 cursor_line, cursor_column = position_to_cursor(full_text, offset)
1957
1957
1958 matched_text, matches, matches_origin, jedi_matches = self._complete(
1958 matched_text, matches, matches_origin, jedi_matches = self._complete(
1959 full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column)
1959 full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column)
1960
1960
1961 iter_jm = iter(jedi_matches)
1961 iter_jm = iter(jedi_matches)
1962 if _timeout:
1962 if _timeout:
1963 for jm in iter_jm:
1963 for jm in iter_jm:
1964 try:
1964 try:
1965 type_ = jm.type
1965 type_ = jm.type
1966 except Exception:
1966 except Exception:
1967 if self.debug:
1967 if self.debug:
1968 print("Error in Jedi getting type of ", jm)
1968 print("Error in Jedi getting type of ", jm)
1969 type_ = None
1969 type_ = None
1970 delta = len(jm.name_with_symbols) - len(jm.complete)
1970 delta = len(jm.name_with_symbols) - len(jm.complete)
1971 if type_ == 'function':
1971 if type_ == 'function':
1972 signature = _make_signature(jm)
1972 signature = _make_signature(jm)
1973 else:
1973 else:
1974 signature = ''
1974 signature = ''
1975 yield Completion(start=offset - delta,
1975 yield Completion(start=offset - delta,
1976 end=offset,
1976 end=offset,
1977 text=jm.name_with_symbols,
1977 text=jm.name_with_symbols,
1978 type=type_,
1978 type=type_,
1979 signature=signature,
1979 signature=signature,
1980 _origin='jedi')
1980 _origin='jedi')
1981
1981
1982 if time.monotonic() > deadline:
1982 if time.monotonic() > deadline:
1983 break
1983 break
1984
1984
1985 for jm in iter_jm:
1985 for jm in iter_jm:
1986 delta = len(jm.name_with_symbols) - len(jm.complete)
1986 delta = len(jm.name_with_symbols) - len(jm.complete)
1987 yield Completion(start=offset - delta,
1987 yield Completion(start=offset - delta,
1988 end=offset,
1988 end=offset,
1989 text=jm.name_with_symbols,
1989 text=jm.name_with_symbols,
1990 type='<unknown>', # don't compute type for speed
1990 type='<unknown>', # don't compute type for speed
1991 _origin='jedi',
1991 _origin='jedi',
1992 signature='')
1992 signature='')
1993
1993
1994
1994
1995 start_offset = before.rfind(matched_text)
1995 start_offset = before.rfind(matched_text)
1996
1996
1997 # TODO:
1997 # TODO:
1998 # Suppress this, right now just for debug.
1998 # Suppress this, right now just for debug.
1999 if jedi_matches and matches and self.debug:
1999 if jedi_matches and matches and self.debug:
2000 yield Completion(start=start_offset, end=offset, text='--jedi/ipython--',
2000 yield Completion(start=start_offset, end=offset, text='--jedi/ipython--',
2001 _origin='debug', type='none', signature='')
2001 _origin='debug', type='none', signature='')
2002
2002
2003 # I'm unsure if this is always true, so let's assert and see if it
2003 # I'm unsure if this is always true, so let's assert and see if it
2004 # crash
2004 # crash
2005 assert before.endswith(matched_text)
2005 assert before.endswith(matched_text)
2006 for m, t in zip(matches, matches_origin):
2006 for m, t in zip(matches, matches_origin):
2007 yield Completion(start=start_offset, end=offset, text=m, _origin=t, signature='', type='<unknown>')
2007 yield Completion(start=start_offset, end=offset, text=m, _origin=t, signature='', type='<unknown>')
2008
2008
2009
2009
2010 def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]:
2010 def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]:
2011 """Find completions for the given text and line context.
2011 """Find completions for the given text and line context.
2012
2012
2013 Note that both the text and the line_buffer are optional, but at least
2013 Note that both the text and the line_buffer are optional, but at least
2014 one of them must be given.
2014 one of them must be given.
2015
2015
2016 Parameters
2016 Parameters
2017 ----------
2017 ----------
2018 text : string, optional
2018 text : string, optional
2019 Text to perform the completion on. If not given, the line buffer
2019 Text to perform the completion on. If not given, the line buffer
2020 is split using the instance's CompletionSplitter object.
2020 is split using the instance's CompletionSplitter object.
2021 line_buffer : string, optional
2021 line_buffer : string, optional
2022 If not given, the completer attempts to obtain the current line
2022 If not given, the completer attempts to obtain the current line
2023 buffer via readline. This keyword allows clients which are
2023 buffer via readline. This keyword allows clients which are
2024 requesting for text completions in non-readline contexts to inform
2024 requesting for text completions in non-readline contexts to inform
2025 the completer of the entire text.
2025 the completer of the entire text.
2026 cursor_pos : int, optional
2026 cursor_pos : int, optional
2027 Index of the cursor in the full line buffer. Should be provided by
2027 Index of the cursor in the full line buffer. Should be provided by
2028 remote frontends where kernel has no access to frontend state.
2028 remote frontends where kernel has no access to frontend state.
2029
2029
2030 Returns
2030 Returns
2031 -------
2031 -------
2032 Tuple of two items:
2032 Tuple of two items:
2033 text : str
2033 text : str
2034 Text that was actually used in the completion.
2034 Text that was actually used in the completion.
2035 matches : list
2035 matches : list
2036 A list of completion matches.
2036 A list of completion matches.
2037
2037
2038 Notes
2038 Notes
2039 -----
2039 -----
2040 This API is likely to be deprecated and replaced by
2040 This API is likely to be deprecated and replaced by
2041 :any:`IPCompleter.completions` in the future.
2041 :any:`IPCompleter.completions` in the future.
2042
2042
2043 """
2043 """
2044 warnings.warn('`Completer.complete` is pending deprecation since '
2044 warnings.warn('`Completer.complete` is pending deprecation since '
2045 'IPython 6.0 and will be replaced by `Completer.completions`.',
2045 'IPython 6.0 and will be replaced by `Completer.completions`.',
2046 PendingDeprecationWarning)
2046 PendingDeprecationWarning)
2047 # potential todo, FOLD the 3rd throw away argument of _complete
2047 # potential todo, FOLD the 3rd throw away argument of _complete
2048 # into the first 2 one.
2048 # into the first 2 one.
2049 return self._complete(line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0)[:2]
2049 return self._complete(line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0)[:2]
2050
2050
2051 def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None,
2051 def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None,
2052 full_text=None) -> _CompleteResult:
2052 full_text=None) -> _CompleteResult:
2053 """
2053 """
2054 Like complete but can also returns raw jedi completions as well as the
2054 Like complete but can also returns raw jedi completions as well as the
2055 origin of the completion text. This could (and should) be made much
2055 origin of the completion text. This could (and should) be made much
2056 cleaner but that will be simpler once we drop the old (and stateful)
2056 cleaner but that will be simpler once we drop the old (and stateful)
2057 :any:`complete` API.
2057 :any:`complete` API.
2058
2058
2059 With current provisional API, cursor_pos act both (depending on the
2059 With current provisional API, cursor_pos act both (depending on the
2060 caller) as the offset in the ``text`` or ``line_buffer``, or as the
2060 caller) as the offset in the ``text`` or ``line_buffer``, or as the
2061 ``column`` when passing multiline strings this could/should be renamed
2061 ``column`` when passing multiline strings this could/should be renamed
2062 but would add extra noise.
2062 but would add extra noise.
2063
2063
2064 Parameters
2064 Parameters
2065 ----------
2065 ----------
2066 cursor_line
2066 cursor_line
2067 Index of the line the cursor is on. 0 indexed.
2067 Index of the line the cursor is on. 0 indexed.
2068 cursor_pos
2068 cursor_pos
2069 Position of the cursor in the current line/line_buffer/text. 0
2069 Position of the cursor in the current line/line_buffer/text. 0
2070 indexed.
2070 indexed.
2071 line_buffer : optional, str
2071 line_buffer : optional, str
2072 The current line the cursor is in, this is mostly due to legacy
2072 The current line the cursor is in, this is mostly due to legacy
2073 reason that readline could only give a us the single current line.
2073 reason that readline could only give a us the single current line.
2074 Prefer `full_text`.
2074 Prefer `full_text`.
2075 text : str
2075 text : str
2076 The current "token" the cursor is in, mostly also for historical
2076 The current "token" the cursor is in, mostly also for historical
2077 reasons. as the completer would trigger only after the current line
2077 reasons. as the completer would trigger only after the current line
2078 was parsed.
2078 was parsed.
2079 full_text : str
2079 full_text : str
2080 Full text of the current cell.
2080 Full text of the current cell.
2081
2081
2082 Returns
2082 Returns
2083 -------
2083 -------
2084 A tuple of N elements which are (likely):
2084 A tuple of N elements which are (likely):
2085 matched_text: ? the text that the complete matched
2085 matched_text: ? the text that the complete matched
2086 matches: list of completions ?
2086 matches: list of completions ?
2087 matches_origin: ? list same length as matches, and where each completion came from
2087 matches_origin: ? list same length as matches, and where each completion came from
2088 jedi_matches: list of Jedi matches, have it's own structure.
2088 jedi_matches: list of Jedi matches, have it's own structure.
2089 """
2089 """
2090
2090
2091
2091
2092 # if the cursor position isn't given, the only sane assumption we can
2092 # if the cursor position isn't given, the only sane assumption we can
2093 # make is that it's at the end of the line (the common case)
2093 # make is that it's at the end of the line (the common case)
2094 if cursor_pos is None:
2094 if cursor_pos is None:
2095 cursor_pos = len(line_buffer) if text is None else len(text)
2095 cursor_pos = len(line_buffer) if text is None else len(text)
2096
2096
2097 if self.use_main_ns:
2097 if self.use_main_ns:
2098 self.namespace = __main__.__dict__
2098 self.namespace = __main__.__dict__
2099
2099
2100 # if text is either None or an empty string, rely on the line buffer
2100 # if text is either None or an empty string, rely on the line buffer
2101 if (not line_buffer) and full_text:
2101 if (not line_buffer) and full_text:
2102 line_buffer = full_text.split('\n')[cursor_line]
2102 line_buffer = full_text.split('\n')[cursor_line]
2103 if not text: # issue #11508: check line_buffer before calling split_line
2103 if not text: # issue #11508: check line_buffer before calling split_line
2104 text = self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else ''
2104 text = self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else ''
2105
2105
2106 if self.backslash_combining_completions:
2106 if self.backslash_combining_completions:
2107 # allow deactivation of these on windows.
2107 # allow deactivation of these on windows.
2108 base_text = text if not line_buffer else line_buffer[:cursor_pos]
2108 base_text = text if not line_buffer else line_buffer[:cursor_pos]
2109
2109
2110 for meth in (self.latex_matches,
2110 for meth in (self.latex_matches,
2111 self.unicode_name_matches,
2111 self.unicode_name_matches,
2112 back_latex_name_matches,
2112 back_latex_name_matches,
2113 back_unicode_name_matches,
2113 back_unicode_name_matches,
2114 self.fwd_unicode_match):
2114 self.fwd_unicode_match):
2115 name_text, name_matches = meth(base_text)
2115 name_text, name_matches = meth(base_text)
2116 if name_text:
2116 if name_text:
2117 return _CompleteResult(name_text, name_matches[:MATCHES_LIMIT], \
2117 return _CompleteResult(name_text, name_matches[:MATCHES_LIMIT], \
2118 [meth.__qualname__]*min(len(name_matches), MATCHES_LIMIT), ())
2118 [meth.__qualname__]*min(len(name_matches), MATCHES_LIMIT), ())
2119
2119
2120
2120
2121 # If no line buffer is given, assume the input text is all there was
2121 # If no line buffer is given, assume the input text is all there was
2122 if line_buffer is None:
2122 if line_buffer is None:
2123 line_buffer = text
2123 line_buffer = text
2124
2124
2125 self.line_buffer = line_buffer
2125 self.line_buffer = line_buffer
2126 self.text_until_cursor = self.line_buffer[:cursor_pos]
2126 self.text_until_cursor = self.line_buffer[:cursor_pos]
2127
2127
2128 # Do magic arg matches
2128 # Do magic arg matches
2129 for matcher in self.magic_arg_matchers:
2129 for matcher in self.magic_arg_matchers:
2130 matches = list(matcher(line_buffer))[:MATCHES_LIMIT]
2130 matches = list(matcher(line_buffer))[:MATCHES_LIMIT]
2131 if matches:
2131 if matches:
2132 origins = [matcher.__qualname__] * len(matches)
2132 origins = [matcher.__qualname__] * len(matches)
2133 return _CompleteResult(text, matches, origins, ())
2133 return _CompleteResult(text, matches, origins, ())
2134
2134
2135 # Start with a clean slate of completions
2135 # Start with a clean slate of completions
2136 matches = []
2136 matches = []
2137
2137
2138 # FIXME: we should extend our api to return a dict with completions for
2138 # FIXME: we should extend our api to return a dict with completions for
2139 # different types of objects. The rlcomplete() method could then
2139 # different types of objects. The rlcomplete() method could then
2140 # simply collapse the dict into a list for readline, but we'd have
2140 # simply collapse the dict into a list for readline, but we'd have
2141 # richer completion semantics in other environments.
2141 # richer completion semantics in other environments.
2142 is_magic_prefix = len(text) > 0 and text[0] == "%"
2142 is_magic_prefix = len(text) > 0 and text[0] == "%"
2143 completions: Iterable[Any] = []
2143 completions: Iterable[Any] = []
2144 if self.use_jedi and not is_magic_prefix:
2144 if self.use_jedi and not is_magic_prefix:
2145 if not full_text:
2145 if not full_text:
2146 full_text = line_buffer
2146 full_text = line_buffer
2147 completions = self._jedi_matches(
2147 completions = self._jedi_matches(
2148 cursor_pos, cursor_line, full_text)
2148 cursor_pos, cursor_line, full_text)
2149
2149
2150 if self.merge_completions:
2150 if self.merge_completions:
2151 matches = []
2151 matches = []
2152 for matcher in self.matchers:
2152 for matcher in self.matchers:
2153 try:
2153 try:
2154 matches.extend([(m, matcher.__qualname__)
2154 matches.extend([(m, matcher.__qualname__)
2155 for m in matcher(text)])
2155 for m in matcher(text)])
2156 except:
2156 except:
2157 # Show the ugly traceback if the matcher causes an
2157 # Show the ugly traceback if the matcher causes an
2158 # exception, but do NOT crash the kernel!
2158 # exception, but do NOT crash the kernel!
2159 sys.excepthook(*sys.exc_info())
2159 sys.excepthook(*sys.exc_info())
2160 else:
2160 else:
2161 for matcher in self.matchers:
2161 for matcher in self.matchers:
2162 matches = [(m, matcher.__qualname__)
2162 matches = [(m, matcher.__qualname__)
2163 for m in matcher(text)]
2163 for m in matcher(text)]
2164 if matches:
2164 if matches:
2165 break
2165 break
2166
2166
2167 seen = set()
2167 seen = set()
2168 filtered_matches = set()
2168 filtered_matches = set()
2169 for m in matches:
2169 for m in matches:
2170 t, c = m
2170 t, c = m
2171 if t not in seen:
2171 if t not in seen:
2172 filtered_matches.add(m)
2172 filtered_matches.add(m)
2173 seen.add(t)
2173 seen.add(t)
2174
2174
2175 _filtered_matches = sorted(filtered_matches, key=lambda x: completions_sorting_key(x[0]))
2175 _filtered_matches = sorted(filtered_matches, key=lambda x: completions_sorting_key(x[0]))
2176
2176
2177 custom_res = [(m, 'custom') for m in self.dispatch_custom_completer(text) or []]
2177 custom_res = [(m, 'custom') for m in self.dispatch_custom_completer(text) or []]
2178
2178
2179 _filtered_matches = custom_res or _filtered_matches
2179 _filtered_matches = custom_res or _filtered_matches
2180
2180
2181 _filtered_matches = _filtered_matches[:MATCHES_LIMIT]
2181 _filtered_matches = _filtered_matches[:MATCHES_LIMIT]
2182 _matches = [m[0] for m in _filtered_matches]
2182 _matches = [m[0] for m in _filtered_matches]
2183 origins = [m[1] for m in _filtered_matches]
2183 origins = [m[1] for m in _filtered_matches]
2184
2184
2185 self.matches = _matches
2185 self.matches = _matches
2186
2186
2187 return _CompleteResult(text, _matches, origins, completions)
2187 return _CompleteResult(text, _matches, origins, completions)
2188
2188
2189 def fwd_unicode_match(self, text:str) -> Tuple[str, Sequence[str]]:
2189 def fwd_unicode_match(self, text:str) -> Tuple[str, Sequence[str]]:
2190 """
2190 """
2191 Forward match a string starting with a backslash with a list of
2191 Forward match a string starting with a backslash with a list of
2192 potential Unicode completions.
2192 potential Unicode completions.
2193
2193
2194 Will compute list list of Unicode character names on first call and cache it.
2194 Will compute list list of Unicode character names on first call and cache it.
2195
2195
2196 Returns
2196 Returns
2197 -------
2197 -------
2198 At tuple with:
2198 At tuple with:
2199 - matched text (empty if no matches)
2199 - matched text (empty if no matches)
2200 - list of potential completions, empty tuple otherwise)
2200 - list of potential completions, empty tuple otherwise)
2201 """
2201 """
2202 # TODO: self.unicode_names is here a list we traverse each time with ~100k elements.
2202 # TODO: self.unicode_names is here a list we traverse each time with ~100k elements.
2203 # We could do a faster match using a Trie.
2203 # We could do a faster match using a Trie.
2204
2204
2205 # Using pygtrie the following seem to work:
2205 # Using pygtrie the following seem to work:
2206
2206
2207 # s = PrefixSet()
2207 # s = PrefixSet()
2208
2208
2209 # for c in range(0,0x10FFFF + 1):
2209 # for c in range(0,0x10FFFF + 1):
2210 # try:
2210 # try:
2211 # s.add(unicodedata.name(chr(c)))
2211 # s.add(unicodedata.name(chr(c)))
2212 # except ValueError:
2212 # except ValueError:
2213 # pass
2213 # pass
2214 # [''.join(k) for k in s.iter(prefix)]
2214 # [''.join(k) for k in s.iter(prefix)]
2215
2215
2216 # But need to be timed and adds an extra dependency.
2216 # But need to be timed and adds an extra dependency.
2217
2217
2218 slashpos = text.rfind('\\')
2218 slashpos = text.rfind('\\')
2219 # if text starts with slash
2219 # if text starts with slash
2220 if slashpos > -1:
2220 if slashpos > -1:
2221 # PERF: It's important that we don't access self._unicode_names
2221 # PERF: It's important that we don't access self._unicode_names
2222 # until we're inside this if-block. _unicode_names is lazily
2222 # until we're inside this if-block. _unicode_names is lazily
2223 # initialized, and it takes a user-noticeable amount of time to
2223 # initialized, and it takes a user-noticeable amount of time to
2224 # initialize it, so we don't want to initialize it unless we're
2224 # initialize it, so we don't want to initialize it unless we're
2225 # actually going to use it.
2225 # actually going to use it.
2226 s = text[slashpos + 1 :]
2226 s = text[slashpos + 1 :]
2227 sup = s.upper()
2227 sup = s.upper()
2228 candidates = [x for x in self.unicode_names if x.startswith(sup)]
2228 candidates = [x for x in self.unicode_names if x.startswith(sup)]
2229 if candidates:
2229 if candidates:
2230 return s, candidates
2230 return s, candidates
2231 candidates = [x for x in self.unicode_names if sup in x]
2231 candidates = [x for x in self.unicode_names if sup in x]
2232 if candidates:
2232 if candidates:
2233 return s, candidates
2233 return s, candidates
2234 splitsup = sup.split(" ")
2234 splitsup = sup.split(" ")
2235 candidates = [
2235 candidates = [
2236 x for x in self.unicode_names if all(u in x for u in splitsup)
2236 x for x in self.unicode_names if all(u in x for u in splitsup)
2237 ]
2237 ]
2238 if candidates:
2238 if candidates:
2239 return s, candidates
2239 return s, candidates
2240
2240
2241 return "", ()
2241 return "", ()
2242
2242
2243 # if text does not start with slash
2243 # if text does not start with slash
2244 else:
2244 else:
2245 return '', ()
2245 return '', ()
2246
2246
2247 @property
2247 @property
2248 def unicode_names(self) -> List[str]:
2248 def unicode_names(self) -> List[str]:
2249 """List of names of unicode code points that can be completed.
2249 """List of names of unicode code points that can be completed.
2250
2250
2251 The list is lazily initialized on first access.
2251 The list is lazily initialized on first access.
2252 """
2252 """
2253 if self._unicode_names is None:
2253 if self._unicode_names is None:
2254 names = []
2254 names = []
2255 for c in range(0,0x10FFFF + 1):
2255 for c in range(0,0x10FFFF + 1):
2256 try:
2256 try:
2257 names.append(unicodedata.name(chr(c)))
2257 names.append(unicodedata.name(chr(c)))
2258 except ValueError:
2258 except ValueError:
2259 pass
2259 pass
2260 self._unicode_names = _unicode_name_compute(_UNICODE_RANGES)
2260 self._unicode_names = _unicode_name_compute(_UNICODE_RANGES)
2261
2261
2262 return self._unicode_names
2262 return self._unicode_names
2263
2263
2264 def _unicode_name_compute(ranges:List[Tuple[int,int]]) -> List[str]:
2264 def _unicode_name_compute(ranges:List[Tuple[int,int]]) -> List[str]:
2265 names = []
2265 names = []
2266 for start,stop in ranges:
2266 for start,stop in ranges:
2267 for c in range(start, stop) :
2267 for c in range(start, stop) :
2268 try:
2268 try:
2269 names.append(unicodedata.name(chr(c)))
2269 names.append(unicodedata.name(chr(c)))
2270 except ValueError:
2270 except ValueError:
2271 pass
2271 pass
2272 return names
2272 return names
@@ -1,913 +1,913 b''
1 """ History related magics and functionality """
1 """ History related magics and functionality """
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6
6
7 import atexit
7 import atexit
8 import datetime
8 import datetime
9 from pathlib import Path
9 from pathlib import Path
10 import re
10 import re
11 import sqlite3
11 import sqlite3
12 import threading
12 import threading
13
13
14 from traitlets.config.configurable import LoggingConfigurable
14 from traitlets.config.configurable import LoggingConfigurable
15 from decorator import decorator
15 from decorator import decorator
16 from IPython.utils.decorators import undoc
16 from IPython.utils.decorators import undoc
17 from IPython.paths import locate_profile
17 from IPython.paths import locate_profile
18 from traitlets import (
18 from traitlets import (
19 Any,
19 Any,
20 Bool,
20 Bool,
21 Dict,
21 Dict,
22 Instance,
22 Instance,
23 Integer,
23 Integer,
24 List,
24 List,
25 Unicode,
25 Unicode,
26 Union,
26 Union,
27 TraitError,
27 TraitError,
28 default,
28 default,
29 observe,
29 observe,
30 )
30 )
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Classes and functions
33 # Classes and functions
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 @undoc
36 @undoc
37 class DummyDB(object):
37 class DummyDB(object):
38 """Dummy DB that will act as a black hole for history.
38 """Dummy DB that will act as a black hole for history.
39
39
40 Only used in the absence of sqlite"""
40 Only used in the absence of sqlite"""
41 def execute(*args, **kwargs):
41 def execute(*args, **kwargs):
42 return []
42 return []
43
43
44 def commit(self, *args, **kwargs):
44 def commit(self, *args, **kwargs):
45 pass
45 pass
46
46
47 def __enter__(self, *args, **kwargs):
47 def __enter__(self, *args, **kwargs):
48 pass
48 pass
49
49
50 def __exit__(self, *args, **kwargs):
50 def __exit__(self, *args, **kwargs):
51 pass
51 pass
52
52
53
53
54 @decorator
54 @decorator
55 def only_when_enabled(f, self, *a, **kw):
55 def only_when_enabled(f, self, *a, **kw):
56 """Decorator: return an empty list in the absence of sqlite."""
56 """Decorator: return an empty list in the absence of sqlite."""
57 if not self.enabled:
57 if not self.enabled:
58 return []
58 return []
59 else:
59 else:
60 return f(self, *a, **kw)
60 return f(self, *a, **kw)
61
61
62
62
63 # use 16kB as threshold for whether a corrupt history db should be saved
63 # use 16kB as threshold for whether a corrupt history db should be saved
64 # that should be at least 100 entries or so
64 # that should be at least 100 entries or so
65 _SAVE_DB_SIZE = 16384
65 _SAVE_DB_SIZE = 16384
66
66
67 @decorator
67 @decorator
68 def catch_corrupt_db(f, self, *a, **kw):
68 def catch_corrupt_db(f, self, *a, **kw):
69 """A decorator which wraps HistoryAccessor method calls to catch errors from
69 """A decorator which wraps HistoryAccessor method calls to catch errors from
70 a corrupt SQLite database, move the old database out of the way, and create
70 a corrupt SQLite database, move the old database out of the way, and create
71 a new one.
71 a new one.
72
72
73 We avoid clobbering larger databases because this may be triggered due to filesystem issues,
73 We avoid clobbering larger databases because this may be triggered due to filesystem issues,
74 not just a corrupt file.
74 not just a corrupt file.
75 """
75 """
76 try:
76 try:
77 return f(self, *a, **kw)
77 return f(self, *a, **kw)
78 except (sqlite3.DatabaseError, sqlite3.OperationalError) as e:
78 except (sqlite3.DatabaseError, sqlite3.OperationalError) as e:
79 self._corrupt_db_counter += 1
79 self._corrupt_db_counter += 1
80 self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e)
80 self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e)
81 if self.hist_file != ':memory:':
81 if self.hist_file != ':memory:':
82 if self._corrupt_db_counter > self._corrupt_db_limit:
82 if self._corrupt_db_counter > self._corrupt_db_limit:
83 self.hist_file = ':memory:'
83 self.hist_file = ':memory:'
84 self.log.error("Failed to load history too many times, history will not be saved.")
84 self.log.error("Failed to load history too many times, history will not be saved.")
85 elif self.hist_file.is_file():
85 elif self.hist_file.is_file():
86 # move the file out of the way
86 # move the file out of the way
87 base = str(self.hist_file.parent / self.hist_file.stem)
87 base = str(self.hist_file.parent / self.hist_file.stem)
88 ext = self.hist_file.suffix
88 ext = self.hist_file.suffix
89 size = self.hist_file.stat().st_size
89 size = self.hist_file.stat().st_size
90 if size >= _SAVE_DB_SIZE:
90 if size >= _SAVE_DB_SIZE:
91 # if there's significant content, avoid clobbering
91 # if there's significant content, avoid clobbering
92 now = datetime.datetime.now().isoformat().replace(':', '.')
92 now = datetime.datetime.now().isoformat().replace(':', '.')
93 newpath = base + '-corrupt-' + now + ext
93 newpath = base + '-corrupt-' + now + ext
94 # don't clobber previous corrupt backups
94 # don't clobber previous corrupt backups
95 for i in range(100):
95 for i in range(100):
96 if not Path(newpath).exists():
96 if not Path(newpath).exists():
97 break
97 break
98 else:
98 else:
99 newpath = base + '-corrupt-' + now + (u'-%i' % i) + ext
99 newpath = base + '-corrupt-' + now + (u'-%i' % i) + ext
100 else:
100 else:
101 # not much content, possibly empty; don't worry about clobbering
101 # not much content, possibly empty; don't worry about clobbering
102 # maybe we should just delete it?
102 # maybe we should just delete it?
103 newpath = base + '-corrupt' + ext
103 newpath = base + '-corrupt' + ext
104 self.hist_file.rename(newpath)
104 self.hist_file.rename(newpath)
105 self.log.error("History file was moved to %s and a new file created.", newpath)
105 self.log.error("History file was moved to %s and a new file created.", newpath)
106 self.init_db()
106 self.init_db()
107 return []
107 return []
108 else:
108 else:
109 # Failed with :memory:, something serious is wrong
109 # Failed with :memory:, something serious is wrong
110 raise
110 raise
111
111
112
112
113 class HistoryAccessorBase(LoggingConfigurable):
113 class HistoryAccessorBase(LoggingConfigurable):
114 """An abstract class for History Accessors """
114 """An abstract class for History Accessors """
115
115
116 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
116 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
117 raise NotImplementedError
117 raise NotImplementedError
118
118
119 def search(self, pattern="*", raw=True, search_raw=True,
119 def search(self, pattern="*", raw=True, search_raw=True,
120 output=False, n=None, unique=False):
120 output=False, n=None, unique=False):
121 raise NotImplementedError
121 raise NotImplementedError
122
122
123 def get_range(self, session, start=1, stop=None, raw=True,output=False):
123 def get_range(self, session, start=1, stop=None, raw=True,output=False):
124 raise NotImplementedError
124 raise NotImplementedError
125
125
126 def get_range_by_str(self, rangestr, raw=True, output=False):
126 def get_range_by_str(self, rangestr, raw=True, output=False):
127 raise NotImplementedError
127 raise NotImplementedError
128
128
129
129
130 class HistoryAccessor(HistoryAccessorBase):
130 class HistoryAccessor(HistoryAccessorBase):
131 """Access the history database without adding to it.
131 """Access the history database without adding to it.
132
132
133 This is intended for use by standalone history tools. IPython shells use
133 This is intended for use by standalone history tools. IPython shells use
134 HistoryManager, below, which is a subclass of this."""
134 HistoryManager, below, which is a subclass of this."""
135
135
136 # counter for init_db retries, so we don't keep trying over and over
136 # counter for init_db retries, so we don't keep trying over and over
137 _corrupt_db_counter = 0
137 _corrupt_db_counter = 0
138 # after two failures, fallback on :memory:
138 # after two failures, fallback on :memory:
139 _corrupt_db_limit = 2
139 _corrupt_db_limit = 2
140
140
141 # String holding the path to the history file
141 # String holding the path to the history file
142 hist_file = Union(
142 hist_file = Union(
143 [Instance(Path), Unicode()],
143 [Instance(Path), Unicode()],
144 help="""Path to file to use for SQLite history database.
144 help="""Path to file to use for SQLite history database.
145
145
146 By default, IPython will put the history database in the IPython
146 By default, IPython will put the history database in the IPython
147 profile directory. If you would rather share one history among
147 profile directory. If you would rather share one history among
148 profiles, you can set this value in each, so that they are consistent.
148 profiles, you can set this value in each, so that they are consistent.
149
149
150 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
150 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
151 mounts. If you see IPython hanging, try setting this to something on a
151 mounts. If you see IPython hanging, try setting this to something on a
152 local disk, e.g::
152 local disk, e.g::
153
153
154 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
154 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
155
155
156 you can also use the specific value `:memory:` (including the colon
156 you can also use the specific value `:memory:` (including the colon
157 at both end but not the back ticks), to avoid creating an history file.
157 at both end but not the back ticks), to avoid creating an history file.
158
158
159 """,
159 """,
160 ).tag(config=True)
160 ).tag(config=True)
161
161
162 enabled = Bool(True,
162 enabled = Bool(True,
163 help="""enable the SQLite history
163 help="""enable the SQLite history
164
164
165 set enabled=False to disable the SQLite history,
165 set enabled=False to disable the SQLite history,
166 in which case there will be no stored history, no SQLite connection,
166 in which case there will be no stored history, no SQLite connection,
167 and no background saving thread. This may be necessary in some
167 and no background saving thread. This may be necessary in some
168 threaded environments where IPython is embedded.
168 threaded environments where IPython is embedded.
169 """
169 """,
170 ).tag(config=True)
170 ).tag(config=True)
171
171
172 connection_options = Dict(
172 connection_options = Dict(
173 help="""Options for configuring the SQLite connection
173 help="""Options for configuring the SQLite connection
174
174
175 These options are passed as keyword args to sqlite3.connect
175 These options are passed as keyword args to sqlite3.connect
176 when establishing database connections.
176 when establishing database connections.
177 """
177 """
178 ).tag(config=True)
178 ).tag(config=True)
179
179
180 # The SQLite database
180 # The SQLite database
181 db = Any()
181 db = Any()
182 @observe('db')
182 @observe('db')
183 def _db_changed(self, change):
183 def _db_changed(self, change):
184 """validate the db, since it can be an Instance of two different types"""
184 """validate the db, since it can be an Instance of two different types"""
185 new = change['new']
185 new = change['new']
186 connection_types = (DummyDB, sqlite3.Connection)
186 connection_types = (DummyDB, sqlite3.Connection)
187 if not isinstance(new, connection_types):
187 if not isinstance(new, connection_types):
188 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
188 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
189 (self.__class__.__name__, new)
189 (self.__class__.__name__, new)
190 raise TraitError(msg)
190 raise TraitError(msg)
191
191
192 def __init__(self, profile="default", hist_file="", **traits):
192 def __init__(self, profile="default", hist_file="", **traits):
193 """Create a new history accessor.
193 """Create a new history accessor.
194
194
195 Parameters
195 Parameters
196 ----------
196 ----------
197 profile : str
197 profile : str
198 The name of the profile from which to open history.
198 The name of the profile from which to open history.
199 hist_file : str
199 hist_file : str
200 Path to an SQLite history database stored by IPython. If specified,
200 Path to an SQLite history database stored by IPython. If specified,
201 hist_file overrides profile.
201 hist_file overrides profile.
202 config : :class:`~traitlets.config.loader.Config`
202 config : :class:`~traitlets.config.loader.Config`
203 Config object. hist_file can also be set through this.
203 Config object. hist_file can also be set through this.
204 """
204 """
205 # We need a pointer back to the shell for various tasks.
205 # We need a pointer back to the shell for various tasks.
206 super(HistoryAccessor, self).__init__(**traits)
206 super(HistoryAccessor, self).__init__(**traits)
207 # defer setting hist_file from kwarg until after init,
207 # defer setting hist_file from kwarg until after init,
208 # otherwise the default kwarg value would clobber any value
208 # otherwise the default kwarg value would clobber any value
209 # set by config
209 # set by config
210 if hist_file:
210 if hist_file:
211 self.hist_file = hist_file
211 self.hist_file = hist_file
212
212
213 try:
213 try:
214 self.hist_file
214 self.hist_file
215 except TraitError:
215 except TraitError:
216 # No one has set the hist_file, yet.
216 # No one has set the hist_file, yet.
217 self.hist_file = self._get_hist_file_name(profile)
217 self.hist_file = self._get_hist_file_name(profile)
218
218
219 self.init_db()
219 self.init_db()
220
220
221 def _get_hist_file_name(self, profile='default'):
221 def _get_hist_file_name(self, profile='default'):
222 """Find the history file for the given profile name.
222 """Find the history file for the given profile name.
223
223
224 This is overridden by the HistoryManager subclass, to use the shell's
224 This is overridden by the HistoryManager subclass, to use the shell's
225 active profile.
225 active profile.
226
226
227 Parameters
227 Parameters
228 ----------
228 ----------
229 profile : str
229 profile : str
230 The name of a profile which has a history file.
230 The name of a profile which has a history file.
231 """
231 """
232 return Path(locate_profile(profile)) / "history.sqlite"
232 return Path(locate_profile(profile)) / "history.sqlite"
233
233
234 @catch_corrupt_db
234 @catch_corrupt_db
235 def init_db(self):
235 def init_db(self):
236 """Connect to the database, and create tables if necessary."""
236 """Connect to the database, and create tables if necessary."""
237 if not self.enabled:
237 if not self.enabled:
238 self.db = DummyDB()
238 self.db = DummyDB()
239 return
239 return
240
240
241 # use detect_types so that timestamps return datetime objects
241 # use detect_types so that timestamps return datetime objects
242 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
242 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
243 kwargs.update(self.connection_options)
243 kwargs.update(self.connection_options)
244 self.db = sqlite3.connect(str(self.hist_file), **kwargs)
244 self.db = sqlite3.connect(str(self.hist_file), **kwargs)
245 with self.db:
245 with self.db:
246 self.db.execute(
246 self.db.execute(
247 """CREATE TABLE IF NOT EXISTS sessions (session integer
247 """CREATE TABLE IF NOT EXISTS sessions (session integer
248 primary key autoincrement, start timestamp,
248 primary key autoincrement, start timestamp,
249 end timestamp, num_cmds integer, remark text)"""
249 end timestamp, num_cmds integer, remark text)"""
250 )
250 )
251 self.db.execute(
251 self.db.execute(
252 """CREATE TABLE IF NOT EXISTS history
252 """CREATE TABLE IF NOT EXISTS history
253 (session integer, line integer, source text, source_raw text,
253 (session integer, line integer, source text, source_raw text,
254 PRIMARY KEY (session, line))"""
254 PRIMARY KEY (session, line))"""
255 )
255 )
256 # Output history is optional, but ensure the table's there so it can be
256 # Output history is optional, but ensure the table's there so it can be
257 # enabled later.
257 # enabled later.
258 self.db.execute(
258 self.db.execute(
259 """CREATE TABLE IF NOT EXISTS output_history
259 """CREATE TABLE IF NOT EXISTS output_history
260 (session integer, line integer, output text,
260 (session integer, line integer, output text,
261 PRIMARY KEY (session, line))"""
261 PRIMARY KEY (session, line))"""
262 )
262 )
263 # success! reset corrupt db count
263 # success! reset corrupt db count
264 self._corrupt_db_counter = 0
264 self._corrupt_db_counter = 0
265
265
266 def writeout_cache(self):
266 def writeout_cache(self):
267 """Overridden by HistoryManager to dump the cache before certain
267 """Overridden by HistoryManager to dump the cache before certain
268 database lookups."""
268 database lookups."""
269 pass
269 pass
270
270
271 ## -------------------------------
271 ## -------------------------------
272 ## Methods for retrieving history:
272 ## Methods for retrieving history:
273 ## -------------------------------
273 ## -------------------------------
274 def _run_sql(self, sql, params, raw=True, output=False, latest=False):
274 def _run_sql(self, sql, params, raw=True, output=False, latest=False):
275 """Prepares and runs an SQL query for the history database.
275 """Prepares and runs an SQL query for the history database.
276
276
277 Parameters
277 Parameters
278 ----------
278 ----------
279 sql : str
279 sql : str
280 Any filtering expressions to go after SELECT ... FROM ...
280 Any filtering expressions to go after SELECT ... FROM ...
281 params : tuple
281 params : tuple
282 Parameters passed to the SQL query (to replace "?")
282 Parameters passed to the SQL query (to replace "?")
283 raw, output : bool
283 raw, output : bool
284 See :meth:`get_range`
284 See :meth:`get_range`
285 latest : bool
285 latest : bool
286 Select rows with max (session, line)
286 Select rows with max (session, line)
287
287
288 Returns
288 Returns
289 -------
289 -------
290 Tuples as :meth:`get_range`
290 Tuples as :meth:`get_range`
291 """
291 """
292 toget = 'source_raw' if raw else 'source'
292 toget = 'source_raw' if raw else 'source'
293 sqlfrom = "history"
293 sqlfrom = "history"
294 if output:
294 if output:
295 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
295 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
296 toget = "history.%s, output_history.output" % toget
296 toget = "history.%s, output_history.output" % toget
297 if latest:
297 if latest:
298 toget += ", MAX(session * 128 * 1024 + line)"
298 toget += ", MAX(session * 128 * 1024 + line)"
299 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
299 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
300 (toget, sqlfrom) + sql, params)
300 (toget, sqlfrom) + sql, params)
301 if latest:
301 if latest:
302 cur = (row[:-1] for row in cur)
302 cur = (row[:-1] for row in cur)
303 if output: # Regroup into 3-tuples, and parse JSON
303 if output: # Regroup into 3-tuples, and parse JSON
304 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
304 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
305 return cur
305 return cur
306
306
307 @only_when_enabled
307 @only_when_enabled
308 @catch_corrupt_db
308 @catch_corrupt_db
309 def get_session_info(self, session):
309 def get_session_info(self, session):
310 """Get info about a session.
310 """Get info about a session.
311
311
312 Parameters
312 Parameters
313 ----------
313 ----------
314 session : int
314 session : int
315 Session number to retrieve.
315 Session number to retrieve.
316
316
317 Returns
317 Returns
318 -------
318 -------
319 session_id : int
319 session_id : int
320 Session ID number
320 Session ID number
321 start : datetime
321 start : datetime
322 Timestamp for the start of the session.
322 Timestamp for the start of the session.
323 end : datetime
323 end : datetime
324 Timestamp for the end of the session, or None if IPython crashed.
324 Timestamp for the end of the session, or None if IPython crashed.
325 num_cmds : int
325 num_cmds : int
326 Number of commands run, or None if IPython crashed.
326 Number of commands run, or None if IPython crashed.
327 remark : unicode
327 remark : unicode
328 A manually set description.
328 A manually set description.
329 """
329 """
330 query = "SELECT * from sessions where session == ?"
330 query = "SELECT * from sessions where session == ?"
331 return self.db.execute(query, (session,)).fetchone()
331 return self.db.execute(query, (session,)).fetchone()
332
332
333 @catch_corrupt_db
333 @catch_corrupt_db
334 def get_last_session_id(self):
334 def get_last_session_id(self):
335 """Get the last session ID currently in the database.
335 """Get the last session ID currently in the database.
336
336
337 Within IPython, this should be the same as the value stored in
337 Within IPython, this should be the same as the value stored in
338 :attr:`HistoryManager.session_number`.
338 :attr:`HistoryManager.session_number`.
339 """
339 """
340 for record in self.get_tail(n=1, include_latest=True):
340 for record in self.get_tail(n=1, include_latest=True):
341 return record[0]
341 return record[0]
342
342
343 @catch_corrupt_db
343 @catch_corrupt_db
344 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
344 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
345 """Get the last n lines from the history database.
345 """Get the last n lines from the history database.
346
346
347 Parameters
347 Parameters
348 ----------
348 ----------
349 n : int
349 n : int
350 The number of lines to get
350 The number of lines to get
351 raw, output : bool
351 raw, output : bool
352 See :meth:`get_range`
352 See :meth:`get_range`
353 include_latest : bool
353 include_latest : bool
354 If False (default), n+1 lines are fetched, and the latest one
354 If False (default), n+1 lines are fetched, and the latest one
355 is discarded. This is intended to be used where the function
355 is discarded. This is intended to be used where the function
356 is called by a user command, which it should not return.
356 is called by a user command, which it should not return.
357
357
358 Returns
358 Returns
359 -------
359 -------
360 Tuples as :meth:`get_range`
360 Tuples as :meth:`get_range`
361 """
361 """
362 self.writeout_cache()
362 self.writeout_cache()
363 if not include_latest:
363 if not include_latest:
364 n += 1
364 n += 1
365 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
365 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
366 (n,), raw=raw, output=output)
366 (n,), raw=raw, output=output)
367 if not include_latest:
367 if not include_latest:
368 return reversed(list(cur)[1:])
368 return reversed(list(cur)[1:])
369 return reversed(list(cur))
369 return reversed(list(cur))
370
370
371 @catch_corrupt_db
371 @catch_corrupt_db
372 def search(self, pattern="*", raw=True, search_raw=True,
372 def search(self, pattern="*", raw=True, search_raw=True,
373 output=False, n=None, unique=False):
373 output=False, n=None, unique=False):
374 """Search the database using unix glob-style matching (wildcards
374 """Search the database using unix glob-style matching (wildcards
375 * and ?).
375 * and ?).
376
376
377 Parameters
377 Parameters
378 ----------
378 ----------
379 pattern : str
379 pattern : str
380 The wildcarded pattern to match when searching
380 The wildcarded pattern to match when searching
381 search_raw : bool
381 search_raw : bool
382 If True, search the raw input, otherwise, the parsed input
382 If True, search the raw input, otherwise, the parsed input
383 raw, output : bool
383 raw, output : bool
384 See :meth:`get_range`
384 See :meth:`get_range`
385 n : None or int
385 n : None or int
386 If an integer is given, it defines the limit of
386 If an integer is given, it defines the limit of
387 returned entries.
387 returned entries.
388 unique : bool
388 unique : bool
389 When it is true, return only unique entries.
389 When it is true, return only unique entries.
390
390
391 Returns
391 Returns
392 -------
392 -------
393 Tuples as :meth:`get_range`
393 Tuples as :meth:`get_range`
394 """
394 """
395 tosearch = "source_raw" if search_raw else "source"
395 tosearch = "source_raw" if search_raw else "source"
396 if output:
396 if output:
397 tosearch = "history." + tosearch
397 tosearch = "history." + tosearch
398 self.writeout_cache()
398 self.writeout_cache()
399 sqlform = "WHERE %s GLOB ?" % tosearch
399 sqlform = "WHERE %s GLOB ?" % tosearch
400 params = (pattern,)
400 params = (pattern,)
401 if unique:
401 if unique:
402 sqlform += ' GROUP BY {0}'.format(tosearch)
402 sqlform += ' GROUP BY {0}'.format(tosearch)
403 if n is not None:
403 if n is not None:
404 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
404 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
405 params += (n,)
405 params += (n,)
406 elif unique:
406 elif unique:
407 sqlform += " ORDER BY session, line"
407 sqlform += " ORDER BY session, line"
408 cur = self._run_sql(sqlform, params, raw=raw, output=output, latest=unique)
408 cur = self._run_sql(sqlform, params, raw=raw, output=output, latest=unique)
409 if n is not None:
409 if n is not None:
410 return reversed(list(cur))
410 return reversed(list(cur))
411 return cur
411 return cur
412
412
413 @catch_corrupt_db
413 @catch_corrupt_db
414 def get_range(self, session, start=1, stop=None, raw=True,output=False):
414 def get_range(self, session, start=1, stop=None, raw=True,output=False):
415 """Retrieve input by session.
415 """Retrieve input by session.
416
416
417 Parameters
417 Parameters
418 ----------
418 ----------
419 session : int
419 session : int
420 Session number to retrieve.
420 Session number to retrieve.
421 start : int
421 start : int
422 First line to retrieve.
422 First line to retrieve.
423 stop : int
423 stop : int
424 End of line range (excluded from output itself). If None, retrieve
424 End of line range (excluded from output itself). If None, retrieve
425 to the end of the session.
425 to the end of the session.
426 raw : bool
426 raw : bool
427 If True, return untranslated input
427 If True, return untranslated input
428 output : bool
428 output : bool
429 If True, attempt to include output. This will be 'real' Python
429 If True, attempt to include output. This will be 'real' Python
430 objects for the current session, or text reprs from previous
430 objects for the current session, or text reprs from previous
431 sessions if db_log_output was enabled at the time. Where no output
431 sessions if db_log_output was enabled at the time. Where no output
432 is found, None is used.
432 is found, None is used.
433
433
434 Returns
434 Returns
435 -------
435 -------
436 entries
436 entries
437 An iterator over the desired lines. Each line is a 3-tuple, either
437 An iterator over the desired lines. Each line is a 3-tuple, either
438 (session, line, input) if output is False, or
438 (session, line, input) if output is False, or
439 (session, line, (input, output)) if output is True.
439 (session, line, (input, output)) if output is True.
440 """
440 """
441 if stop:
441 if stop:
442 lineclause = "line >= ? AND line < ?"
442 lineclause = "line >= ? AND line < ?"
443 params = (session, start, stop)
443 params = (session, start, stop)
444 else:
444 else:
445 lineclause = "line>=?"
445 lineclause = "line>=?"
446 params = (session, start)
446 params = (session, start)
447
447
448 return self._run_sql("WHERE session==? AND %s" % lineclause,
448 return self._run_sql("WHERE session==? AND %s" % lineclause,
449 params, raw=raw, output=output)
449 params, raw=raw, output=output)
450
450
451 def get_range_by_str(self, rangestr, raw=True, output=False):
451 def get_range_by_str(self, rangestr, raw=True, output=False):
452 """Get lines of history from a string of ranges, as used by magic
452 """Get lines of history from a string of ranges, as used by magic
453 commands %hist, %save, %macro, etc.
453 commands %hist, %save, %macro, etc.
454
454
455 Parameters
455 Parameters
456 ----------
456 ----------
457 rangestr : str
457 rangestr : str
458 A string specifying ranges, e.g. "5 ~2/1-4". If empty string is used,
458 A string specifying ranges, e.g. "5 ~2/1-4". If empty string is used,
459 this will return everything from current session's history.
459 this will return everything from current session's history.
460
460
461 See the documentation of :func:`%history` for the full details.
461 See the documentation of :func:`%history` for the full details.
462
462
463 raw, output : bool
463 raw, output : bool
464 As :meth:`get_range`
464 As :meth:`get_range`
465
465
466 Returns
466 Returns
467 -------
467 -------
468 Tuples as :meth:`get_range`
468 Tuples as :meth:`get_range`
469 """
469 """
470 for sess, s, e in extract_hist_ranges(rangestr):
470 for sess, s, e in extract_hist_ranges(rangestr):
471 for line in self.get_range(sess, s, e, raw=raw, output=output):
471 for line in self.get_range(sess, s, e, raw=raw, output=output):
472 yield line
472 yield line
473
473
474
474
475 class HistoryManager(HistoryAccessor):
475 class HistoryManager(HistoryAccessor):
476 """A class to organize all history-related functionality in one place.
476 """A class to organize all history-related functionality in one place.
477 """
477 """
478 # Public interface
478 # Public interface
479
479
480 # An instance of the IPython shell we are attached to
480 # An instance of the IPython shell we are attached to
481 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
481 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
482 allow_none=True)
482 allow_none=True)
483 # Lists to hold processed and raw history. These start with a blank entry
483 # Lists to hold processed and raw history. These start with a blank entry
484 # so that we can index them starting from 1
484 # so that we can index them starting from 1
485 input_hist_parsed = List([""])
485 input_hist_parsed = List([""])
486 input_hist_raw = List([""])
486 input_hist_raw = List([""])
487 # A list of directories visited during session
487 # A list of directories visited during session
488 dir_hist = List()
488 dir_hist = List()
489 @default('dir_hist')
489 @default('dir_hist')
490 def _dir_hist_default(self):
490 def _dir_hist_default(self):
491 try:
491 try:
492 return [Path.cwd()]
492 return [Path.cwd()]
493 except OSError:
493 except OSError:
494 return []
494 return []
495
495
496 # A dict of output history, keyed with ints from the shell's
496 # A dict of output history, keyed with ints from the shell's
497 # execution count.
497 # execution count.
498 output_hist = Dict()
498 output_hist = Dict()
499 # The text/plain repr of outputs.
499 # The text/plain repr of outputs.
500 output_hist_reprs = Dict()
500 output_hist_reprs = Dict()
501
501
502 # The number of the current session in the history database
502 # The number of the current session in the history database
503 session_number = Integer()
503 session_number = Integer()
504
504
505 db_log_output = Bool(False,
505 db_log_output = Bool(False,
506 help="Should the history database include output? (default: no)"
506 help="Should the history database include output? (default: no)"
507 ).tag(config=True)
507 ).tag(config=True)
508 db_cache_size = Integer(0,
508 db_cache_size = Integer(0,
509 help="Write to database every x commands (higher values save disk access & power).\n"
509 help="Write to database every x commands (higher values save disk access & power).\n"
510 "Values of 1 or less effectively disable caching."
510 "Values of 1 or less effectively disable caching."
511 ).tag(config=True)
511 ).tag(config=True)
512 # The input and output caches
512 # The input and output caches
513 db_input_cache = List()
513 db_input_cache = List()
514 db_output_cache = List()
514 db_output_cache = List()
515
515
516 # History saving in separate thread
516 # History saving in separate thread
517 save_thread = Instance('IPython.core.history.HistorySavingThread',
517 save_thread = Instance('IPython.core.history.HistorySavingThread',
518 allow_none=True)
518 allow_none=True)
519 save_flag = Instance(threading.Event, allow_none=True)
519 save_flag = Instance(threading.Event, allow_none=True)
520
520
521 # Private interface
521 # Private interface
522 # Variables used to store the three last inputs from the user. On each new
522 # Variables used to store the three last inputs from the user. On each new
523 # history update, we populate the user's namespace with these, shifted as
523 # history update, we populate the user's namespace with these, shifted as
524 # necessary.
524 # necessary.
525 _i00 = Unicode(u'')
525 _i00 = Unicode(u'')
526 _i = Unicode(u'')
526 _i = Unicode(u'')
527 _ii = Unicode(u'')
527 _ii = Unicode(u'')
528 _iii = Unicode(u'')
528 _iii = Unicode(u'')
529
529
530 # A regex matching all forms of the exit command, so that we don't store
530 # A regex matching all forms of the exit command, so that we don't store
531 # them in the history (it's annoying to rewind the first entry and land on
531 # them in the history (it's annoying to rewind the first entry and land on
532 # an exit call).
532 # an exit call).
533 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
533 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
534
534
535 def __init__(self, shell=None, config=None, **traits):
535 def __init__(self, shell=None, config=None, **traits):
536 """Create a new history manager associated with a shell instance.
536 """Create a new history manager associated with a shell instance.
537 """
537 """
538 # We need a pointer back to the shell for various tasks.
538 # We need a pointer back to the shell for various tasks.
539 super(HistoryManager, self).__init__(shell=shell, config=config,
539 super(HistoryManager, self).__init__(shell=shell, config=config,
540 **traits)
540 **traits)
541 self.save_flag = threading.Event()
541 self.save_flag = threading.Event()
542 self.db_input_cache_lock = threading.Lock()
542 self.db_input_cache_lock = threading.Lock()
543 self.db_output_cache_lock = threading.Lock()
543 self.db_output_cache_lock = threading.Lock()
544
544
545 try:
545 try:
546 self.new_session()
546 self.new_session()
547 except sqlite3.OperationalError:
547 except sqlite3.OperationalError:
548 self.log.error("Failed to create history session in %s. History will not be saved.",
548 self.log.error("Failed to create history session in %s. History will not be saved.",
549 self.hist_file, exc_info=True)
549 self.hist_file, exc_info=True)
550 self.hist_file = ':memory:'
550 self.hist_file = ':memory:'
551
551
552 if self.enabled and self.hist_file != ':memory:':
552 if self.enabled and self.hist_file != ':memory:':
553 self.save_thread = HistorySavingThread(self)
553 self.save_thread = HistorySavingThread(self)
554 self.save_thread.start()
554 self.save_thread.start()
555
555
556 def _get_hist_file_name(self, profile=None):
556 def _get_hist_file_name(self, profile=None):
557 """Get default history file name based on the Shell's profile.
557 """Get default history file name based on the Shell's profile.
558
558
559 The profile parameter is ignored, but must exist for compatibility with
559 The profile parameter is ignored, but must exist for compatibility with
560 the parent class."""
560 the parent class."""
561 profile_dir = self.shell.profile_dir.location
561 profile_dir = self.shell.profile_dir.location
562 return Path(profile_dir) / "history.sqlite"
562 return Path(profile_dir) / "history.sqlite"
563
563
564 @only_when_enabled
564 @only_when_enabled
565 def new_session(self, conn=None):
565 def new_session(self, conn=None):
566 """Get a new session number."""
566 """Get a new session number."""
567 if conn is None:
567 if conn is None:
568 conn = self.db
568 conn = self.db
569
569
570 with conn:
570 with conn:
571 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
571 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
572 NULL, "") """, (datetime.datetime.now(),))
572 NULL, "") """, (datetime.datetime.now(),))
573 self.session_number = cur.lastrowid
573 self.session_number = cur.lastrowid
574
574
575 def end_session(self):
575 def end_session(self):
576 """Close the database session, filling in the end time and line count."""
576 """Close the database session, filling in the end time and line count."""
577 self.writeout_cache()
577 self.writeout_cache()
578 with self.db:
578 with self.db:
579 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
579 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
580 session==?""", (datetime.datetime.now(),
580 session==?""", (datetime.datetime.now(),
581 len(self.input_hist_parsed)-1, self.session_number))
581 len(self.input_hist_parsed)-1, self.session_number))
582 self.session_number = 0
582 self.session_number = 0
583
583
584 def name_session(self, name):
584 def name_session(self, name):
585 """Give the current session a name in the history database."""
585 """Give the current session a name in the history database."""
586 with self.db:
586 with self.db:
587 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
587 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
588 (name, self.session_number))
588 (name, self.session_number))
589
589
590 def reset(self, new_session=True):
590 def reset(self, new_session=True):
591 """Clear the session history, releasing all object references, and
591 """Clear the session history, releasing all object references, and
592 optionally open a new session."""
592 optionally open a new session."""
593 self.output_hist.clear()
593 self.output_hist.clear()
594 # The directory history can't be completely empty
594 # The directory history can't be completely empty
595 self.dir_hist[:] = [Path.cwd()]
595 self.dir_hist[:] = [Path.cwd()]
596
596
597 if new_session:
597 if new_session:
598 if self.session_number:
598 if self.session_number:
599 self.end_session()
599 self.end_session()
600 self.input_hist_parsed[:] = [""]
600 self.input_hist_parsed[:] = [""]
601 self.input_hist_raw[:] = [""]
601 self.input_hist_raw[:] = [""]
602 self.new_session()
602 self.new_session()
603
603
604 # ------------------------------
604 # ------------------------------
605 # Methods for retrieving history
605 # Methods for retrieving history
606 # ------------------------------
606 # ------------------------------
607 def get_session_info(self, session=0):
607 def get_session_info(self, session=0):
608 """Get info about a session.
608 """Get info about a session.
609
609
610 Parameters
610 Parameters
611 ----------
611 ----------
612 session : int
612 session : int
613 Session number to retrieve. The current session is 0, and negative
613 Session number to retrieve. The current session is 0, and negative
614 numbers count back from current session, so -1 is the previous session.
614 numbers count back from current session, so -1 is the previous session.
615
615
616 Returns
616 Returns
617 -------
617 -------
618 session_id : int
618 session_id : int
619 Session ID number
619 Session ID number
620 start : datetime
620 start : datetime
621 Timestamp for the start of the session.
621 Timestamp for the start of the session.
622 end : datetime
622 end : datetime
623 Timestamp for the end of the session, or None if IPython crashed.
623 Timestamp for the end of the session, or None if IPython crashed.
624 num_cmds : int
624 num_cmds : int
625 Number of commands run, or None if IPython crashed.
625 Number of commands run, or None if IPython crashed.
626 remark : unicode
626 remark : unicode
627 A manually set description.
627 A manually set description.
628 """
628 """
629 if session <= 0:
629 if session <= 0:
630 session += self.session_number
630 session += self.session_number
631
631
632 return super(HistoryManager, self).get_session_info(session=session)
632 return super(HistoryManager, self).get_session_info(session=session)
633
633
634 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
634 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
635 """Get input and output history from the current session. Called by
635 """Get input and output history from the current session. Called by
636 get_range, and takes similar parameters."""
636 get_range, and takes similar parameters."""
637 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
637 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
638
638
639 n = len(input_hist)
639 n = len(input_hist)
640 if start < 0:
640 if start < 0:
641 start += n
641 start += n
642 if not stop or (stop > n):
642 if not stop or (stop > n):
643 stop = n
643 stop = n
644 elif stop < 0:
644 elif stop < 0:
645 stop += n
645 stop += n
646
646
647 for i in range(start, stop):
647 for i in range(start, stop):
648 if output:
648 if output:
649 line = (input_hist[i], self.output_hist_reprs.get(i))
649 line = (input_hist[i], self.output_hist_reprs.get(i))
650 else:
650 else:
651 line = input_hist[i]
651 line = input_hist[i]
652 yield (0, i, line)
652 yield (0, i, line)
653
653
654 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
654 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
655 """Retrieve input by session.
655 """Retrieve input by session.
656
656
657 Parameters
657 Parameters
658 ----------
658 ----------
659 session : int
659 session : int
660 Session number to retrieve. The current session is 0, and negative
660 Session number to retrieve. The current session is 0, and negative
661 numbers count back from current session, so -1 is previous session.
661 numbers count back from current session, so -1 is previous session.
662 start : int
662 start : int
663 First line to retrieve.
663 First line to retrieve.
664 stop : int
664 stop : int
665 End of line range (excluded from output itself). If None, retrieve
665 End of line range (excluded from output itself). If None, retrieve
666 to the end of the session.
666 to the end of the session.
667 raw : bool
667 raw : bool
668 If True, return untranslated input
668 If True, return untranslated input
669 output : bool
669 output : bool
670 If True, attempt to include output. This will be 'real' Python
670 If True, attempt to include output. This will be 'real' Python
671 objects for the current session, or text reprs from previous
671 objects for the current session, or text reprs from previous
672 sessions if db_log_output was enabled at the time. Where no output
672 sessions if db_log_output was enabled at the time. Where no output
673 is found, None is used.
673 is found, None is used.
674
674
675 Returns
675 Returns
676 -------
676 -------
677 entries
677 entries
678 An iterator over the desired lines. Each line is a 3-tuple, either
678 An iterator over the desired lines. Each line is a 3-tuple, either
679 (session, line, input) if output is False, or
679 (session, line, input) if output is False, or
680 (session, line, (input, output)) if output is True.
680 (session, line, (input, output)) if output is True.
681 """
681 """
682 if session <= 0:
682 if session <= 0:
683 session += self.session_number
683 session += self.session_number
684 if session==self.session_number: # Current session
684 if session==self.session_number: # Current session
685 return self._get_range_session(start, stop, raw, output)
685 return self._get_range_session(start, stop, raw, output)
686 return super(HistoryManager, self).get_range(session, start, stop, raw,
686 return super(HistoryManager, self).get_range(session, start, stop, raw,
687 output)
687 output)
688
688
689 ## ----------------------------
689 ## ----------------------------
690 ## Methods for storing history:
690 ## Methods for storing history:
691 ## ----------------------------
691 ## ----------------------------
692 def store_inputs(self, line_num, source, source_raw=None):
692 def store_inputs(self, line_num, source, source_raw=None):
693 """Store source and raw input in history and create input cache
693 """Store source and raw input in history and create input cache
694 variables ``_i*``.
694 variables ``_i*``.
695
695
696 Parameters
696 Parameters
697 ----------
697 ----------
698 line_num : int
698 line_num : int
699 The prompt number of this input.
699 The prompt number of this input.
700 source : str
700 source : str
701 Python input.
701 Python input.
702 source_raw : str, optional
702 source_raw : str, optional
703 If given, this is the raw input without any IPython transformations
703 If given, this is the raw input without any IPython transformations
704 applied to it. If not given, ``source`` is used.
704 applied to it. If not given, ``source`` is used.
705 """
705 """
706 if source_raw is None:
706 if source_raw is None:
707 source_raw = source
707 source_raw = source
708 source = source.rstrip('\n')
708 source = source.rstrip('\n')
709 source_raw = source_raw.rstrip('\n')
709 source_raw = source_raw.rstrip('\n')
710
710
711 # do not store exit/quit commands
711 # do not store exit/quit commands
712 if self._exit_re.match(source_raw.strip()):
712 if self._exit_re.match(source_raw.strip()):
713 return
713 return
714
714
715 self.input_hist_parsed.append(source)
715 self.input_hist_parsed.append(source)
716 self.input_hist_raw.append(source_raw)
716 self.input_hist_raw.append(source_raw)
717
717
718 with self.db_input_cache_lock:
718 with self.db_input_cache_lock:
719 self.db_input_cache.append((line_num, source, source_raw))
719 self.db_input_cache.append((line_num, source, source_raw))
720 # Trigger to flush cache and write to DB.
720 # Trigger to flush cache and write to DB.
721 if len(self.db_input_cache) >= self.db_cache_size:
721 if len(self.db_input_cache) >= self.db_cache_size:
722 self.save_flag.set()
722 self.save_flag.set()
723
723
724 # update the auto _i variables
724 # update the auto _i variables
725 self._iii = self._ii
725 self._iii = self._ii
726 self._ii = self._i
726 self._ii = self._i
727 self._i = self._i00
727 self._i = self._i00
728 self._i00 = source_raw
728 self._i00 = source_raw
729
729
730 # hackish access to user namespace to create _i1,_i2... dynamically
730 # hackish access to user namespace to create _i1,_i2... dynamically
731 new_i = '_i%s' % line_num
731 new_i = '_i%s' % line_num
732 to_main = {'_i': self._i,
732 to_main = {'_i': self._i,
733 '_ii': self._ii,
733 '_ii': self._ii,
734 '_iii': self._iii,
734 '_iii': self._iii,
735 new_i : self._i00 }
735 new_i : self._i00 }
736
736
737 if self.shell is not None:
737 if self.shell is not None:
738 self.shell.push(to_main, interactive=False)
738 self.shell.push(to_main, interactive=False)
739
739
740 def store_output(self, line_num):
740 def store_output(self, line_num):
741 """If database output logging is enabled, this saves all the
741 """If database output logging is enabled, this saves all the
742 outputs from the indicated prompt number to the database. It's
742 outputs from the indicated prompt number to the database. It's
743 called by run_cell after code has been executed.
743 called by run_cell after code has been executed.
744
744
745 Parameters
745 Parameters
746 ----------
746 ----------
747 line_num : int
747 line_num : int
748 The line number from which to save outputs
748 The line number from which to save outputs
749 """
749 """
750 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
750 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
751 return
751 return
752 output = self.output_hist_reprs[line_num]
752 output = self.output_hist_reprs[line_num]
753
753
754 with self.db_output_cache_lock:
754 with self.db_output_cache_lock:
755 self.db_output_cache.append((line_num, output))
755 self.db_output_cache.append((line_num, output))
756 if self.db_cache_size <= 1:
756 if self.db_cache_size <= 1:
757 self.save_flag.set()
757 self.save_flag.set()
758
758
759 def _writeout_input_cache(self, conn):
759 def _writeout_input_cache(self, conn):
760 with conn:
760 with conn:
761 for line in self.db_input_cache:
761 for line in self.db_input_cache:
762 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
762 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
763 (self.session_number,)+line)
763 (self.session_number,)+line)
764
764
765 def _writeout_output_cache(self, conn):
765 def _writeout_output_cache(self, conn):
766 with conn:
766 with conn:
767 for line in self.db_output_cache:
767 for line in self.db_output_cache:
768 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
768 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
769 (self.session_number,)+line)
769 (self.session_number,)+line)
770
770
771 @only_when_enabled
771 @only_when_enabled
772 def writeout_cache(self, conn=None):
772 def writeout_cache(self, conn=None):
773 """Write any entries in the cache to the database."""
773 """Write any entries in the cache to the database."""
774 if conn is None:
774 if conn is None:
775 conn = self.db
775 conn = self.db
776
776
777 with self.db_input_cache_lock:
777 with self.db_input_cache_lock:
778 try:
778 try:
779 self._writeout_input_cache(conn)
779 self._writeout_input_cache(conn)
780 except sqlite3.IntegrityError:
780 except sqlite3.IntegrityError:
781 self.new_session(conn)
781 self.new_session(conn)
782 print("ERROR! Session/line number was not unique in",
782 print("ERROR! Session/line number was not unique in",
783 "database. History logging moved to new session",
783 "database. History logging moved to new session",
784 self.session_number)
784 self.session_number)
785 try:
785 try:
786 # Try writing to the new session. If this fails, don't
786 # Try writing to the new session. If this fails, don't
787 # recurse
787 # recurse
788 self._writeout_input_cache(conn)
788 self._writeout_input_cache(conn)
789 except sqlite3.IntegrityError:
789 except sqlite3.IntegrityError:
790 pass
790 pass
791 finally:
791 finally:
792 self.db_input_cache = []
792 self.db_input_cache = []
793
793
794 with self.db_output_cache_lock:
794 with self.db_output_cache_lock:
795 try:
795 try:
796 self._writeout_output_cache(conn)
796 self._writeout_output_cache(conn)
797 except sqlite3.IntegrityError:
797 except sqlite3.IntegrityError:
798 print("!! Session/line number for output was not unique",
798 print("!! Session/line number for output was not unique",
799 "in database. Output will not be stored.")
799 "in database. Output will not be stored.")
800 finally:
800 finally:
801 self.db_output_cache = []
801 self.db_output_cache = []
802
802
803
803
804 class HistorySavingThread(threading.Thread):
804 class HistorySavingThread(threading.Thread):
805 """This thread takes care of writing history to the database, so that
805 """This thread takes care of writing history to the database, so that
806 the UI isn't held up while that happens.
806 the UI isn't held up while that happens.
807
807
808 It waits for the HistoryManager's save_flag to be set, then writes out
808 It waits for the HistoryManager's save_flag to be set, then writes out
809 the history cache. The main thread is responsible for setting the flag when
809 the history cache. The main thread is responsible for setting the flag when
810 the cache size reaches a defined threshold."""
810 the cache size reaches a defined threshold."""
811 daemon = True
811 daemon = True
812 stop_now = False
812 stop_now = False
813 enabled = True
813 enabled = True
814 def __init__(self, history_manager):
814 def __init__(self, history_manager):
815 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
815 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
816 self.history_manager = history_manager
816 self.history_manager = history_manager
817 self.enabled = history_manager.enabled
817 self.enabled = history_manager.enabled
818 atexit.register(self.stop)
818 atexit.register(self.stop)
819
819
820 @only_when_enabled
820 @only_when_enabled
821 def run(self):
821 def run(self):
822 # We need a separate db connection per thread:
822 # We need a separate db connection per thread:
823 try:
823 try:
824 self.db = sqlite3.connect(
824 self.db = sqlite3.connect(
825 str(self.history_manager.hist_file),
825 str(self.history_manager.hist_file),
826 **self.history_manager.connection_options,
826 **self.history_manager.connection_options,
827 )
827 )
828 while True:
828 while True:
829 self.history_manager.save_flag.wait()
829 self.history_manager.save_flag.wait()
830 if self.stop_now:
830 if self.stop_now:
831 self.db.close()
831 self.db.close()
832 return
832 return
833 self.history_manager.save_flag.clear()
833 self.history_manager.save_flag.clear()
834 self.history_manager.writeout_cache(self.db)
834 self.history_manager.writeout_cache(self.db)
835 except Exception as e:
835 except Exception as e:
836 print(("The history saving thread hit an unexpected error (%s)."
836 print(("The history saving thread hit an unexpected error (%s)."
837 "History will not be written to the database.") % repr(e))
837 "History will not be written to the database.") % repr(e))
838
838
839 def stop(self):
839 def stop(self):
840 """This can be called from the main thread to safely stop this thread.
840 """This can be called from the main thread to safely stop this thread.
841
841
842 Note that it does not attempt to write out remaining history before
842 Note that it does not attempt to write out remaining history before
843 exiting. That should be done by calling the HistoryManager's
843 exiting. That should be done by calling the HistoryManager's
844 end_session method."""
844 end_session method."""
845 self.stop_now = True
845 self.stop_now = True
846 self.history_manager.save_flag.set()
846 self.history_manager.save_flag.set()
847 self.join()
847 self.join()
848
848
849
849
850 # To match, e.g. ~5/8-~2/3
850 # To match, e.g. ~5/8-~2/3
851 range_re = re.compile(r"""
851 range_re = re.compile(r"""
852 ((?P<startsess>~?\d+)/)?
852 ((?P<startsess>~?\d+)/)?
853 (?P<start>\d+)?
853 (?P<start>\d+)?
854 ((?P<sep>[\-:])
854 ((?P<sep>[\-:])
855 ((?P<endsess>~?\d+)/)?
855 ((?P<endsess>~?\d+)/)?
856 (?P<end>\d+))?
856 (?P<end>\d+))?
857 $""", re.VERBOSE)
857 $""", re.VERBOSE)
858
858
859
859
860 def extract_hist_ranges(ranges_str):
860 def extract_hist_ranges(ranges_str):
861 """Turn a string of history ranges into 3-tuples of (session, start, stop).
861 """Turn a string of history ranges into 3-tuples of (session, start, stop).
862
862
863 Empty string results in a `[(0, 1, None)]`, i.e. "everything from current
863 Empty string results in a `[(0, 1, None)]`, i.e. "everything from current
864 session".
864 session".
865
865
866 Examples
866 Examples
867 --------
867 --------
868 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
868 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
869 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
869 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
870 """
870 """
871 if ranges_str == "":
871 if ranges_str == "":
872 yield (0, 1, None) # Everything from current session
872 yield (0, 1, None) # Everything from current session
873 return
873 return
874
874
875 for range_str in ranges_str.split():
875 for range_str in ranges_str.split():
876 rmatch = range_re.match(range_str)
876 rmatch = range_re.match(range_str)
877 if not rmatch:
877 if not rmatch:
878 continue
878 continue
879 start = rmatch.group("start")
879 start = rmatch.group("start")
880 if start:
880 if start:
881 start = int(start)
881 start = int(start)
882 end = rmatch.group("end")
882 end = rmatch.group("end")
883 # If no end specified, get (a, a + 1)
883 # If no end specified, get (a, a + 1)
884 end = int(end) if end else start + 1
884 end = int(end) if end else start + 1
885 else: # start not specified
885 else: # start not specified
886 if not rmatch.group('startsess'): # no startsess
886 if not rmatch.group('startsess'): # no startsess
887 continue
887 continue
888 start = 1
888 start = 1
889 end = None # provide the entire session hist
889 end = None # provide the entire session hist
890
890
891 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
891 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
892 end += 1
892 end += 1
893 startsess = rmatch.group("startsess") or "0"
893 startsess = rmatch.group("startsess") or "0"
894 endsess = rmatch.group("endsess") or startsess
894 endsess = rmatch.group("endsess") or startsess
895 startsess = int(startsess.replace("~","-"))
895 startsess = int(startsess.replace("~","-"))
896 endsess = int(endsess.replace("~","-"))
896 endsess = int(endsess.replace("~","-"))
897 assert endsess >= startsess, "start session must be earlier than end session"
897 assert endsess >= startsess, "start session must be earlier than end session"
898
898
899 if endsess == startsess:
899 if endsess == startsess:
900 yield (startsess, start, end)
900 yield (startsess, start, end)
901 continue
901 continue
902 # Multiple sessions in one range:
902 # Multiple sessions in one range:
903 yield (startsess, start, None)
903 yield (startsess, start, None)
904 for sess in range(startsess+1, endsess):
904 for sess in range(startsess+1, endsess):
905 yield (sess, 1, None)
905 yield (sess, 1, None)
906 yield (endsess, 1, end)
906 yield (endsess, 1, end)
907
907
908
908
909 def _format_lineno(session, line):
909 def _format_lineno(session, line):
910 """Helper function to format line numbers properly."""
910 """Helper function to format line numbers properly."""
911 if session == 0:
911 if session == 0:
912 return str(line)
912 return str(line)
913 return "%s#%s" % (session, line)
913 return "%s#%s" % (session, line)
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
@@ -1,362 +1,362 b''
1 """Magic functions for running cells in various scripts."""
1 """Magic functions for running cells in various scripts."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import asyncio
6 import asyncio
7 import atexit
7 import atexit
8 import errno
8 import errno
9 import os
9 import os
10 import signal
10 import signal
11 import sys
11 import sys
12 import time
12 import time
13 from subprocess import CalledProcessError
13 from subprocess import CalledProcessError
14 from threading import Thread
14 from threading import Thread
15
15
16 from traitlets import Any, Dict, List, default
16 from traitlets import Any, Dict, List, default
17
17
18 from IPython.core import magic_arguments
18 from IPython.core import magic_arguments
19 from IPython.core.async_helpers import _AsyncIOProxy
19 from IPython.core.async_helpers import _AsyncIOProxy
20 from IPython.core.magic import Magics, cell_magic, line_magic, magics_class
20 from IPython.core.magic import Magics, cell_magic, line_magic, magics_class
21 from IPython.utils.process import arg_split
21 from IPython.utils.process import arg_split
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Magic implementation classes
24 # Magic implementation classes
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 def script_args(f):
27 def script_args(f):
28 """single decorator for adding script args"""
28 """single decorator for adding script args"""
29 args = [
29 args = [
30 magic_arguments.argument(
30 magic_arguments.argument(
31 '--out', type=str,
31 '--out', type=str,
32 help="""The variable in which to store stdout from the script.
32 help="""The variable in which to store stdout from the script.
33 If the script is backgrounded, this will be the stdout *pipe*,
33 If the script is backgrounded, this will be the stdout *pipe*,
34 instead of the stderr text itself and will not be auto closed.
34 instead of the stderr text itself and will not be auto closed.
35 """
35 """
36 ),
36 ),
37 magic_arguments.argument(
37 magic_arguments.argument(
38 '--err', type=str,
38 '--err', type=str,
39 help="""The variable in which to store stderr from the script.
39 help="""The variable in which to store stderr from the script.
40 If the script is backgrounded, this will be the stderr *pipe*,
40 If the script is backgrounded, this will be the stderr *pipe*,
41 instead of the stderr text itself and will not be autoclosed.
41 instead of the stderr text itself and will not be autoclosed.
42 """
42 """
43 ),
43 ),
44 magic_arguments.argument(
44 magic_arguments.argument(
45 '--bg', action="store_true",
45 '--bg', action="store_true",
46 help="""Whether to run the script in the background.
46 help="""Whether to run the script in the background.
47 If given, the only way to see the output of the command is
47 If given, the only way to see the output of the command is
48 with --out/err.
48 with --out/err.
49 """
49 """
50 ),
50 ),
51 magic_arguments.argument(
51 magic_arguments.argument(
52 '--proc', type=str,
52 '--proc', type=str,
53 help="""The variable in which to store Popen instance.
53 help="""The variable in which to store Popen instance.
54 This is used only when --bg option is given.
54 This is used only when --bg option is given.
55 """
55 """
56 ),
56 ),
57 magic_arguments.argument(
57 magic_arguments.argument(
58 '--no-raise-error', action="store_false", dest='raise_error',
58 '--no-raise-error', action="store_false", dest='raise_error',
59 help="""Whether you should raise an error message in addition to
59 help="""Whether you should raise an error message in addition to
60 a stream on stderr if you get a nonzero exit code.
60 a stream on stderr if you get a nonzero exit code.
61 """
61 """,
62 )
62 ),
63 ]
63 ]
64 for arg in args:
64 for arg in args:
65 f = arg(f)
65 f = arg(f)
66 return f
66 return f
67
67
68
68
69 @magics_class
69 @magics_class
70 class ScriptMagics(Magics):
70 class ScriptMagics(Magics):
71 """Magics for talking to scripts
71 """Magics for talking to scripts
72
72
73 This defines a base `%%script` cell magic for running a cell
73 This defines a base `%%script` cell magic for running a cell
74 with a program in a subprocess, and registers a few top-level
74 with a program in a subprocess, and registers a few top-level
75 magics that call %%script with common interpreters.
75 magics that call %%script with common interpreters.
76 """
76 """
77
77
78 event_loop = Any(
78 event_loop = Any(
79 help="""
79 help="""
80 The event loop on which to run subprocesses
80 The event loop on which to run subprocesses
81
81
82 Not the main event loop,
82 Not the main event loop,
83 because we want to be able to make blocking calls
83 because we want to be able to make blocking calls
84 and have certain requirements we don't want to impose on the main loop.
84 and have certain requirements we don't want to impose on the main loop.
85 """
85 """
86 )
86 )
87
87
88 script_magics = List(
88 script_magics = List(
89 help="""Extra script cell magics to define
89 help="""Extra script cell magics to define
90
90
91 This generates simple wrappers of `%%script foo` as `%%foo`.
91 This generates simple wrappers of `%%script foo` as `%%foo`.
92
92
93 If you want to add script magics that aren't on your path,
93 If you want to add script magics that aren't on your path,
94 specify them in script_paths
94 specify them in script_paths
95 """,
95 """,
96 ).tag(config=True)
96 ).tag(config=True)
97 @default('script_magics')
97 @default('script_magics')
98 def _script_magics_default(self):
98 def _script_magics_default(self):
99 """default to a common list of programs"""
99 """default to a common list of programs"""
100
100
101 defaults = [
101 defaults = [
102 'sh',
102 'sh',
103 'bash',
103 'bash',
104 'perl',
104 'perl',
105 'ruby',
105 'ruby',
106 'python',
106 'python',
107 'python2',
107 'python2',
108 'python3',
108 'python3',
109 'pypy',
109 'pypy',
110 ]
110 ]
111 if os.name == 'nt':
111 if os.name == 'nt':
112 defaults.extend([
112 defaults.extend([
113 'cmd',
113 'cmd',
114 ])
114 ])
115
115
116 return defaults
116 return defaults
117
117
118 script_paths = Dict(
118 script_paths = Dict(
119 help="""Dict mapping short 'ruby' names to full paths, such as '/opt/secret/bin/ruby'
119 help="""Dict mapping short 'ruby' names to full paths, such as '/opt/secret/bin/ruby'
120
120
121 Only necessary for items in script_magics where the default path will not
121 Only necessary for items in script_magics where the default path will not
122 find the right interpreter.
122 find the right interpreter.
123 """
123 """
124 ).tag(config=True)
124 ).tag(config=True)
125
125
126 def __init__(self, shell=None):
126 def __init__(self, shell=None):
127 super(ScriptMagics, self).__init__(shell=shell)
127 super(ScriptMagics, self).__init__(shell=shell)
128 self._generate_script_magics()
128 self._generate_script_magics()
129 self.bg_processes = []
129 self.bg_processes = []
130 atexit.register(self.kill_bg_processes)
130 atexit.register(self.kill_bg_processes)
131
131
132 def __del__(self):
132 def __del__(self):
133 self.kill_bg_processes()
133 self.kill_bg_processes()
134
134
135 def _generate_script_magics(self):
135 def _generate_script_magics(self):
136 cell_magics = self.magics['cell']
136 cell_magics = self.magics['cell']
137 for name in self.script_magics:
137 for name in self.script_magics:
138 cell_magics[name] = self._make_script_magic(name)
138 cell_magics[name] = self._make_script_magic(name)
139
139
140 def _make_script_magic(self, name):
140 def _make_script_magic(self, name):
141 """make a named magic, that calls %%script with a particular program"""
141 """make a named magic, that calls %%script with a particular program"""
142 # expand to explicit path if necessary:
142 # expand to explicit path if necessary:
143 script = self.script_paths.get(name, name)
143 script = self.script_paths.get(name, name)
144
144
145 @magic_arguments.magic_arguments()
145 @magic_arguments.magic_arguments()
146 @script_args
146 @script_args
147 def named_script_magic(line, cell):
147 def named_script_magic(line, cell):
148 # if line, add it as cl-flags
148 # if line, add it as cl-flags
149 if line:
149 if line:
150 line = "%s %s" % (script, line)
150 line = "%s %s" % (script, line)
151 else:
151 else:
152 line = script
152 line = script
153 return self.shebang(line, cell)
153 return self.shebang(line, cell)
154
154
155 # write a basic docstring:
155 # write a basic docstring:
156 named_script_magic.__doc__ = \
156 named_script_magic.__doc__ = \
157 """%%{name} script magic
157 """%%{name} script magic
158
158
159 Run cells with {script} in a subprocess.
159 Run cells with {script} in a subprocess.
160
160
161 This is a shortcut for `%%script {script}`
161 This is a shortcut for `%%script {script}`
162 """.format(**locals())
162 """.format(**locals())
163
163
164 return named_script_magic
164 return named_script_magic
165
165
166 @magic_arguments.magic_arguments()
166 @magic_arguments.magic_arguments()
167 @script_args
167 @script_args
168 @cell_magic("script")
168 @cell_magic("script")
169 def shebang(self, line, cell):
169 def shebang(self, line, cell):
170 """Run a cell via a shell command
170 """Run a cell via a shell command
171
171
172 The `%%script` line is like the #! line of script,
172 The `%%script` line is like the #! line of script,
173 specifying a program (bash, perl, ruby, etc.) with which to run.
173 specifying a program (bash, perl, ruby, etc.) with which to run.
174
174
175 The rest of the cell is run by that program.
175 The rest of the cell is run by that program.
176
176
177 Examples
177 Examples
178 --------
178 --------
179 ::
179 ::
180
180
181 In [1]: %%script bash
181 In [1]: %%script bash
182 ...: for i in 1 2 3; do
182 ...: for i in 1 2 3; do
183 ...: echo $i
183 ...: echo $i
184 ...: done
184 ...: done
185 1
185 1
186 2
186 2
187 3
187 3
188 """
188 """
189
189
190 # Create the event loop in which to run script magics
190 # Create the event loop in which to run script magics
191 # this operates on a background thread
191 # this operates on a background thread
192 if self.event_loop is None:
192 if self.event_loop is None:
193 if sys.platform == "win32":
193 if sys.platform == "win32":
194 # don't override the current policy,
194 # don't override the current policy,
195 # just create an event loop
195 # just create an event loop
196 event_loop = asyncio.WindowsProactorEventLoopPolicy().new_event_loop()
196 event_loop = asyncio.WindowsProactorEventLoopPolicy().new_event_loop()
197 else:
197 else:
198 event_loop = asyncio.new_event_loop()
198 event_loop = asyncio.new_event_loop()
199 self.event_loop = event_loop
199 self.event_loop = event_loop
200
200
201 # start the loop in a background thread
201 # start the loop in a background thread
202 asyncio_thread = Thread(target=event_loop.run_forever, daemon=True)
202 asyncio_thread = Thread(target=event_loop.run_forever, daemon=True)
203 asyncio_thread.start()
203 asyncio_thread.start()
204 else:
204 else:
205 event_loop = self.event_loop
205 event_loop = self.event_loop
206
206
207 def in_thread(coro):
207 def in_thread(coro):
208 """Call a coroutine on the asyncio thread"""
208 """Call a coroutine on the asyncio thread"""
209 return asyncio.run_coroutine_threadsafe(coro, event_loop).result()
209 return asyncio.run_coroutine_threadsafe(coro, event_loop).result()
210
210
211 async def _handle_stream(stream, stream_arg, file_object):
211 async def _handle_stream(stream, stream_arg, file_object):
212 while True:
212 while True:
213 line = (await stream.readline()).decode("utf8")
213 line = (await stream.readline()).decode("utf8")
214 if not line:
214 if not line:
215 break
215 break
216 if stream_arg:
216 if stream_arg:
217 self.shell.user_ns[stream_arg] = line
217 self.shell.user_ns[stream_arg] = line
218 else:
218 else:
219 file_object.write(line)
219 file_object.write(line)
220 file_object.flush()
220 file_object.flush()
221
221
222 async def _stream_communicate(process, cell):
222 async def _stream_communicate(process, cell):
223 process.stdin.write(cell)
223 process.stdin.write(cell)
224 process.stdin.close()
224 process.stdin.close()
225 stdout_task = asyncio.create_task(
225 stdout_task = asyncio.create_task(
226 _handle_stream(process.stdout, args.out, sys.stdout)
226 _handle_stream(process.stdout, args.out, sys.stdout)
227 )
227 )
228 stderr_task = asyncio.create_task(
228 stderr_task = asyncio.create_task(
229 _handle_stream(process.stderr, args.err, sys.stderr)
229 _handle_stream(process.stderr, args.err, sys.stderr)
230 )
230 )
231 await asyncio.wait([stdout_task, stderr_task])
231 await asyncio.wait([stdout_task, stderr_task])
232 await process.wait()
232 await process.wait()
233
233
234 argv = arg_split(line, posix=not sys.platform.startswith("win"))
234 argv = arg_split(line, posix=not sys.platform.startswith("win"))
235 args, cmd = self.shebang.parser.parse_known_args(argv)
235 args, cmd = self.shebang.parser.parse_known_args(argv)
236
236
237 try:
237 try:
238 p = in_thread(
238 p = in_thread(
239 asyncio.create_subprocess_exec(
239 asyncio.create_subprocess_exec(
240 *cmd,
240 *cmd,
241 stdout=asyncio.subprocess.PIPE,
241 stdout=asyncio.subprocess.PIPE,
242 stderr=asyncio.subprocess.PIPE,
242 stderr=asyncio.subprocess.PIPE,
243 stdin=asyncio.subprocess.PIPE,
243 stdin=asyncio.subprocess.PIPE,
244 )
244 )
245 )
245 )
246 except OSError as e:
246 except OSError as e:
247 if e.errno == errno.ENOENT:
247 if e.errno == errno.ENOENT:
248 print("Couldn't find program: %r" % cmd[0])
248 print("Couldn't find program: %r" % cmd[0])
249 return
249 return
250 else:
250 else:
251 raise
251 raise
252
252
253 if not cell.endswith('\n'):
253 if not cell.endswith('\n'):
254 cell += '\n'
254 cell += '\n'
255 cell = cell.encode('utf8', 'replace')
255 cell = cell.encode('utf8', 'replace')
256 if args.bg:
256 if args.bg:
257 self.bg_processes.append(p)
257 self.bg_processes.append(p)
258 self._gc_bg_processes()
258 self._gc_bg_processes()
259 to_close = []
259 to_close = []
260 if args.out:
260 if args.out:
261 self.shell.user_ns[args.out] = _AsyncIOProxy(p.stdout, event_loop)
261 self.shell.user_ns[args.out] = _AsyncIOProxy(p.stdout, event_loop)
262 else:
262 else:
263 to_close.append(p.stdout)
263 to_close.append(p.stdout)
264 if args.err:
264 if args.err:
265 self.shell.user_ns[args.err] = _AsyncIOProxy(p.stderr, event_loop)
265 self.shell.user_ns[args.err] = _AsyncIOProxy(p.stderr, event_loop)
266 else:
266 else:
267 to_close.append(p.stderr)
267 to_close.append(p.stderr)
268 event_loop.call_soon_threadsafe(
268 event_loop.call_soon_threadsafe(
269 lambda: asyncio.Task(self._run_script(p, cell, to_close))
269 lambda: asyncio.Task(self._run_script(p, cell, to_close))
270 )
270 )
271 if args.proc:
271 if args.proc:
272 proc_proxy = _AsyncIOProxy(p, event_loop)
272 proc_proxy = _AsyncIOProxy(p, event_loop)
273 proc_proxy.stdout = _AsyncIOProxy(p.stdout, event_loop)
273 proc_proxy.stdout = _AsyncIOProxy(p.stdout, event_loop)
274 proc_proxy.stderr = _AsyncIOProxy(p.stderr, event_loop)
274 proc_proxy.stderr = _AsyncIOProxy(p.stderr, event_loop)
275 self.shell.user_ns[args.proc] = proc_proxy
275 self.shell.user_ns[args.proc] = proc_proxy
276 return
276 return
277
277
278 try:
278 try:
279 in_thread(_stream_communicate(p, cell))
279 in_thread(_stream_communicate(p, cell))
280 except KeyboardInterrupt:
280 except KeyboardInterrupt:
281 try:
281 try:
282 p.send_signal(signal.SIGINT)
282 p.send_signal(signal.SIGINT)
283 in_thread(asyncio.wait_for(p.wait(), timeout=0.1))
283 in_thread(asyncio.wait_for(p.wait(), timeout=0.1))
284 if p.returncode is not None:
284 if p.returncode is not None:
285 print("Process is interrupted.")
285 print("Process is interrupted.")
286 return
286 return
287 p.terminate()
287 p.terminate()
288 in_thread(asyncio.wait_for(p.wait(), timeout=0.1))
288 in_thread(asyncio.wait_for(p.wait(), timeout=0.1))
289 if p.returncode is not None:
289 if p.returncode is not None:
290 print("Process is terminated.")
290 print("Process is terminated.")
291 return
291 return
292 p.kill()
292 p.kill()
293 print("Process is killed.")
293 print("Process is killed.")
294 except OSError:
294 except OSError:
295 pass
295 pass
296 except Exception as e:
296 except Exception as e:
297 print("Error while terminating subprocess (pid=%i): %s" % (p.pid, e))
297 print("Error while terminating subprocess (pid=%i): %s" % (p.pid, e))
298 return
298 return
299
299
300 if args.raise_error and p.returncode != 0:
300 if args.raise_error and p.returncode != 0:
301 # If we get here and p.returncode is still None, we must have
301 # If we get here and p.returncode is still None, we must have
302 # killed it but not yet seen its return code. We don't wait for it,
302 # killed it but not yet seen its return code. We don't wait for it,
303 # in case it's stuck in uninterruptible sleep. -9 = SIGKILL
303 # in case it's stuck in uninterruptible sleep. -9 = SIGKILL
304 rc = p.returncode or -9
304 rc = p.returncode or -9
305 raise CalledProcessError(rc, cell)
305 raise CalledProcessError(rc, cell)
306
306
307 shebang.__skip_doctest__ = os.name != "posix"
307 shebang.__skip_doctest__ = os.name != "posix"
308
308
309 async def _run_script(self, p, cell, to_close):
309 async def _run_script(self, p, cell, to_close):
310 """callback for running the script in the background"""
310 """callback for running the script in the background"""
311
311
312 p.stdin.write(cell)
312 p.stdin.write(cell)
313 await p.stdin.drain()
313 await p.stdin.drain()
314 p.stdin.close()
314 p.stdin.close()
315 await p.stdin.wait_closed()
315 await p.stdin.wait_closed()
316 await p.wait()
316 await p.wait()
317 # asyncio read pipes have no close
317 # asyncio read pipes have no close
318 # but we should drain the data anyway
318 # but we should drain the data anyway
319 for s in to_close:
319 for s in to_close:
320 await s.read()
320 await s.read()
321 self._gc_bg_processes()
321 self._gc_bg_processes()
322
322
323 @line_magic("killbgscripts")
323 @line_magic("killbgscripts")
324 def killbgscripts(self, _nouse_=''):
324 def killbgscripts(self, _nouse_=''):
325 """Kill all BG processes started by %%script and its family."""
325 """Kill all BG processes started by %%script and its family."""
326 self.kill_bg_processes()
326 self.kill_bg_processes()
327 print("All background processes were killed.")
327 print("All background processes were killed.")
328
328
329 def kill_bg_processes(self):
329 def kill_bg_processes(self):
330 """Kill all BG processes which are still running."""
330 """Kill all BG processes which are still running."""
331 if not self.bg_processes:
331 if not self.bg_processes:
332 return
332 return
333 for p in self.bg_processes:
333 for p in self.bg_processes:
334 if p.returncode is None:
334 if p.returncode is None:
335 try:
335 try:
336 p.send_signal(signal.SIGINT)
336 p.send_signal(signal.SIGINT)
337 except:
337 except:
338 pass
338 pass
339 time.sleep(0.1)
339 time.sleep(0.1)
340 self._gc_bg_processes()
340 self._gc_bg_processes()
341 if not self.bg_processes:
341 if not self.bg_processes:
342 return
342 return
343 for p in self.bg_processes:
343 for p in self.bg_processes:
344 if p.returncode is None:
344 if p.returncode is None:
345 try:
345 try:
346 p.terminate()
346 p.terminate()
347 except:
347 except:
348 pass
348 pass
349 time.sleep(0.1)
349 time.sleep(0.1)
350 self._gc_bg_processes()
350 self._gc_bg_processes()
351 if not self.bg_processes:
351 if not self.bg_processes:
352 return
352 return
353 for p in self.bg_processes:
353 for p in self.bg_processes:
354 if p.returncode is None:
354 if p.returncode is None:
355 try:
355 try:
356 p.kill()
356 p.kill()
357 except:
357 except:
358 pass
358 pass
359 self._gc_bg_processes()
359 self._gc_bg_processes()
360
360
361 def _gc_bg_processes(self):
361 def _gc_bg_processes(self):
362 self.bg_processes = [p for p in self.bg_processes if p.returncode is None]
362 self.bg_processes = [p for p in self.bg_processes if p.returncode is None]
@@ -1,442 +1,443 b''
1 """Tests for the token-based transformers in IPython.core.inputtransformer2
1 """Tests for the token-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_line for tests for line-based
4 more complex. See test_inputtransformer2_line for tests for line-based
5 transformations.
5 transformations.
6 """
6 """
7 import platform
7 import platform
8 import string
8 import string
9 import sys
9 import sys
10 from textwrap import dedent
10 from textwrap import dedent
11
11
12 import pytest
12 import pytest
13
13
14 from IPython.core import inputtransformer2 as ipt2
14 from IPython.core import inputtransformer2 as ipt2
15 from IPython.core.inputtransformer2 import _find_assign_op, make_tokens_by_line
15 from IPython.core.inputtransformer2 import _find_assign_op, make_tokens_by_line
16
16
17 MULTILINE_MAGIC = (
17 MULTILINE_MAGIC = (
18 """\
18 """\
19 a = f()
19 a = f()
20 %foo \\
20 %foo \\
21 bar
21 bar
22 g()
22 g()
23 """.splitlines(
23 """.splitlines(
24 keepends=True
24 keepends=True
25 ),
25 ),
26 (2, 0),
26 (2, 0),
27 """\
27 """\
28 a = f()
28 a = f()
29 get_ipython().run_line_magic('foo', ' bar')
29 get_ipython().run_line_magic('foo', ' bar')
30 g()
30 g()
31 """.splitlines(
31 """.splitlines(
32 keepends=True
32 keepends=True
33 ),
33 ),
34 )
34 )
35
35
36 INDENTED_MAGIC = (
36 INDENTED_MAGIC = (
37 """\
37 """\
38 for a in range(5):
38 for a in range(5):
39 %ls
39 %ls
40 """.splitlines(
40 """.splitlines(
41 keepends=True
41 keepends=True
42 ),
42 ),
43 (2, 4),
43 (2, 4),
44 """\
44 """\
45 for a in range(5):
45 for a in range(5):
46 get_ipython().run_line_magic('ls', '')
46 get_ipython().run_line_magic('ls', '')
47 """.splitlines(
47 """.splitlines(
48 keepends=True
48 keepends=True
49 ),
49 ),
50 )
50 )
51
51
52 CRLF_MAGIC = (
52 CRLF_MAGIC = (
53 ["a = f()\n", "%ls\r\n", "g()\n"],
53 ["a = f()\n", "%ls\r\n", "g()\n"],
54 (2, 0),
54 (2, 0),
55 ["a = f()\n", "get_ipython().run_line_magic('ls', '')\n", "g()\n"],
55 ["a = f()\n", "get_ipython().run_line_magic('ls', '')\n", "g()\n"],
56 )
56 )
57
57
58 MULTILINE_MAGIC_ASSIGN = (
58 MULTILINE_MAGIC_ASSIGN = (
59 """\
59 """\
60 a = f()
60 a = f()
61 b = %foo \\
61 b = %foo \\
62 bar
62 bar
63 g()
63 g()
64 """.splitlines(
64 """.splitlines(
65 keepends=True
65 keepends=True
66 ),
66 ),
67 (2, 4),
67 (2, 4),
68 """\
68 """\
69 a = f()
69 a = f()
70 b = get_ipython().run_line_magic('foo', ' bar')
70 b = get_ipython().run_line_magic('foo', ' bar')
71 g()
71 g()
72 """.splitlines(
72 """.splitlines(
73 keepends=True
73 keepends=True
74 ),
74 ),
75 )
75 )
76
76
77 MULTILINE_SYSTEM_ASSIGN = ("""\
77 MULTILINE_SYSTEM_ASSIGN = ("""\
78 a = f()
78 a = f()
79 b = !foo \\
79 b = !foo \\
80 bar
80 bar
81 g()
81 g()
82 """.splitlines(keepends=True), (2, 4), """\
82 """.splitlines(keepends=True), (2, 4), """\
83 a = f()
83 a = f()
84 b = get_ipython().getoutput('foo bar')
84 b = get_ipython().getoutput('foo bar')
85 g()
85 g()
86 """.splitlines(keepends=True))
86 """.splitlines(keepends=True))
87
87
88 #####
88 #####
89
89
90 MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT = ("""\
90 MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT = (
91 """\
91 def test():
92 def test():
92 for i in range(1):
93 for i in range(1):
93 print(i)
94 print(i)
94 res =! ls
95 res =! ls
95 """.splitlines(
96 """.splitlines(
96 keepends=True
97 keepends=True
97 ),
98 ),
98 (4, 7),
99 (4, 7),
99 """\
100 """\
100 def test():
101 def test():
101 for i in range(1):
102 for i in range(1):
102 print(i)
103 print(i)
103 res =get_ipython().getoutput(\' ls\')
104 res =get_ipython().getoutput(\' ls\')
104 """.splitlines(
105 """.splitlines(
105 keepends=True
106 keepends=True
106 ),
107 ),
107 )
108 )
108
109
109 ######
110 ######
110
111
111 AUTOCALL_QUOTE = ([",f 1 2 3\n"], (1, 0), ['f("1", "2", "3")\n'])
112 AUTOCALL_QUOTE = ([",f 1 2 3\n"], (1, 0), ['f("1", "2", "3")\n'])
112
113
113 AUTOCALL_QUOTE2 = ([";f 1 2 3\n"], (1, 0), ['f("1 2 3")\n'])
114 AUTOCALL_QUOTE2 = ([";f 1 2 3\n"], (1, 0), ['f("1 2 3")\n'])
114
115
115 AUTOCALL_PAREN = (["/f 1 2 3\n"], (1, 0), ["f(1, 2, 3)\n"])
116 AUTOCALL_PAREN = (["/f 1 2 3\n"], (1, 0), ["f(1, 2, 3)\n"])
116
117
117 SIMPLE_HELP = (["foo?\n"], (1, 0), ["get_ipython().run_line_magic('pinfo', 'foo')\n"])
118 SIMPLE_HELP = (["foo?\n"], (1, 0), ["get_ipython().run_line_magic('pinfo', 'foo')\n"])
118
119
119 DETAILED_HELP = (
120 DETAILED_HELP = (
120 ["foo??\n"],
121 ["foo??\n"],
121 (1, 0),
122 (1, 0),
122 ["get_ipython().run_line_magic('pinfo2', 'foo')\n"],
123 ["get_ipython().run_line_magic('pinfo2', 'foo')\n"],
123 )
124 )
124
125
125 MAGIC_HELP = (["%foo?\n"], (1, 0), ["get_ipython().run_line_magic('pinfo', '%foo')\n"])
126 MAGIC_HELP = (["%foo?\n"], (1, 0), ["get_ipython().run_line_magic('pinfo', '%foo')\n"])
126
127
127 HELP_IN_EXPR = (
128 HELP_IN_EXPR = (
128 ["a = b + c?\n"],
129 ["a = b + c?\n"],
129 (1, 0),
130 (1, 0),
130 ["get_ipython().run_line_magic('pinfo', 'c')\n"],
131 ["get_ipython().run_line_magic('pinfo', 'c')\n"],
131 )
132 )
132
133
133 HELP_CONTINUED_LINE = (
134 HELP_CONTINUED_LINE = (
134 """\
135 """\
135 a = \\
136 a = \\
136 zip?
137 zip?
137 """.splitlines(
138 """.splitlines(
138 keepends=True
139 keepends=True
139 ),
140 ),
140 (1, 0),
141 (1, 0),
141 [r"get_ipython().run_line_magic('pinfo', 'zip')" + "\n"],
142 [r"get_ipython().run_line_magic('pinfo', 'zip')" + "\n"],
142 )
143 )
143
144
144 HELP_MULTILINE = (
145 HELP_MULTILINE = (
145 """\
146 """\
146 (a,
147 (a,
147 b) = zip?
148 b) = zip?
148 """.splitlines(
149 """.splitlines(
149 keepends=True
150 keepends=True
150 ),
151 ),
151 (1, 0),
152 (1, 0),
152 [r"get_ipython().run_line_magic('pinfo', 'zip')" + "\n"],
153 [r"get_ipython().run_line_magic('pinfo', 'zip')" + "\n"],
153 )
154 )
154
155
155 HELP_UNICODE = (
156 HELP_UNICODE = (
156 ["Ο€.foo?\n"],
157 ["Ο€.foo?\n"],
157 (1, 0),
158 (1, 0),
158 ["get_ipython().run_line_magic('pinfo', 'Ο€.foo')\n"],
159 ["get_ipython().run_line_magic('pinfo', 'Ο€.foo')\n"],
159 )
160 )
160
161
161
162
162 def null_cleanup_transformer(lines):
163 def null_cleanup_transformer(lines):
163 """
164 """
164 A cleanup transform that returns an empty list.
165 A cleanup transform that returns an empty list.
165 """
166 """
166 return []
167 return []
167
168
168
169
169 def test_check_make_token_by_line_never_ends_empty():
170 def test_check_make_token_by_line_never_ends_empty():
170 """
171 """
171 Check that not sequence of single or double characters ends up leading to en empty list of tokens
172 Check that not sequence of single or double characters ends up leading to en empty list of tokens
172 """
173 """
173 from string import printable
174 from string import printable
174
175
175 for c in printable:
176 for c in printable:
176 assert make_tokens_by_line(c)[-1] != []
177 assert make_tokens_by_line(c)[-1] != []
177 for k in printable:
178 for k in printable:
178 assert make_tokens_by_line(c + k)[-1] != []
179 assert make_tokens_by_line(c + k)[-1] != []
179
180
180
181
181 def check_find(transformer, case, match=True):
182 def check_find(transformer, case, match=True):
182 sample, expected_start, _ = case
183 sample, expected_start, _ = case
183 tbl = make_tokens_by_line(sample)
184 tbl = make_tokens_by_line(sample)
184 res = transformer.find(tbl)
185 res = transformer.find(tbl)
185 if match:
186 if match:
186 # start_line is stored 0-indexed, expected values are 1-indexed
187 # start_line is stored 0-indexed, expected values are 1-indexed
187 assert (res.start_line + 1, res.start_col) == expected_start
188 assert (res.start_line + 1, res.start_col) == expected_start
188 return res
189 return res
189 else:
190 else:
190 assert res is None
191 assert res is None
191
192
192
193
193 def check_transform(transformer_cls, case):
194 def check_transform(transformer_cls, case):
194 lines, start, expected = case
195 lines, start, expected = case
195 transformer = transformer_cls(start)
196 transformer = transformer_cls(start)
196 assert transformer.transform(lines) == expected
197 assert transformer.transform(lines) == expected
197
198
198
199
199 def test_continued_line():
200 def test_continued_line():
200 lines = MULTILINE_MAGIC_ASSIGN[0]
201 lines = MULTILINE_MAGIC_ASSIGN[0]
201 assert ipt2.find_end_of_continued_line(lines, 1) == 2
202 assert ipt2.find_end_of_continued_line(lines, 1) == 2
202
203
203 assert ipt2.assemble_continued_line(lines, (1, 5), 2) == "foo bar"
204 assert ipt2.assemble_continued_line(lines, (1, 5), 2) == "foo bar"
204
205
205
206
206 def test_find_assign_magic():
207 def test_find_assign_magic():
207 check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
208 check_find(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
208 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN, match=False)
209 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN, match=False)
209 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT, match=False)
210 check_find(ipt2.MagicAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT, match=False)
210
211
211
212
212 def test_transform_assign_magic():
213 def test_transform_assign_magic():
213 check_transform(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
214 check_transform(ipt2.MagicAssign, MULTILINE_MAGIC_ASSIGN)
214
215
215
216
216 def test_find_assign_system():
217 def test_find_assign_system():
217 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
218 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
218 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
219 check_find(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
219 check_find(ipt2.SystemAssign, (["a = !ls\n"], (1, 5), None))
220 check_find(ipt2.SystemAssign, (["a = !ls\n"], (1, 5), None))
220 check_find(ipt2.SystemAssign, (["a=!ls\n"], (1, 2), None))
221 check_find(ipt2.SystemAssign, (["a=!ls\n"], (1, 2), None))
221 check_find(ipt2.SystemAssign, MULTILINE_MAGIC_ASSIGN, match=False)
222 check_find(ipt2.SystemAssign, MULTILINE_MAGIC_ASSIGN, match=False)
222
223
223
224
224 def test_transform_assign_system():
225 def test_transform_assign_system():
225 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
226 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN)
226 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
227 check_transform(ipt2.SystemAssign, MULTILINE_SYSTEM_ASSIGN_AFTER_DEDENT)
227
228
228
229
229 def test_find_magic_escape():
230 def test_find_magic_escape():
230 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC)
231 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC)
231 check_find(ipt2.EscapedCommand, INDENTED_MAGIC)
232 check_find(ipt2.EscapedCommand, INDENTED_MAGIC)
232 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC_ASSIGN, match=False)
233 check_find(ipt2.EscapedCommand, MULTILINE_MAGIC_ASSIGN, match=False)
233
234
234
235
235 def test_transform_magic_escape():
236 def test_transform_magic_escape():
236 check_transform(ipt2.EscapedCommand, MULTILINE_MAGIC)
237 check_transform(ipt2.EscapedCommand, MULTILINE_MAGIC)
237 check_transform(ipt2.EscapedCommand, INDENTED_MAGIC)
238 check_transform(ipt2.EscapedCommand, INDENTED_MAGIC)
238 check_transform(ipt2.EscapedCommand, CRLF_MAGIC)
239 check_transform(ipt2.EscapedCommand, CRLF_MAGIC)
239
240
240
241
241 def test_find_autocalls():
242 def test_find_autocalls():
242 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
243 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
243 print("Testing %r" % case[0])
244 print("Testing %r" % case[0])
244 check_find(ipt2.EscapedCommand, case)
245 check_find(ipt2.EscapedCommand, case)
245
246
246
247
247 def test_transform_autocall():
248 def test_transform_autocall():
248 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
249 for case in [AUTOCALL_QUOTE, AUTOCALL_QUOTE2, AUTOCALL_PAREN]:
249 print("Testing %r" % case[0])
250 print("Testing %r" % case[0])
250 check_transform(ipt2.EscapedCommand, case)
251 check_transform(ipt2.EscapedCommand, case)
251
252
252
253
253 def test_find_help():
254 def test_find_help():
254 for case in [SIMPLE_HELP, DETAILED_HELP, MAGIC_HELP, HELP_IN_EXPR]:
255 for case in [SIMPLE_HELP, DETAILED_HELP, MAGIC_HELP, HELP_IN_EXPR]:
255 check_find(ipt2.HelpEnd, case)
256 check_find(ipt2.HelpEnd, case)
256
257
257 tf = check_find(ipt2.HelpEnd, HELP_CONTINUED_LINE)
258 tf = check_find(ipt2.HelpEnd, HELP_CONTINUED_LINE)
258 assert tf.q_line == 1
259 assert tf.q_line == 1
259 assert tf.q_col == 3
260 assert tf.q_col == 3
260
261
261 tf = check_find(ipt2.HelpEnd, HELP_MULTILINE)
262 tf = check_find(ipt2.HelpEnd, HELP_MULTILINE)
262 assert tf.q_line == 1
263 assert tf.q_line == 1
263 assert tf.q_col == 8
264 assert tf.q_col == 8
264
265
265 # ? in a comment does not trigger help
266 # ? in a comment does not trigger help
266 check_find(ipt2.HelpEnd, (["foo # bar?\n"], None, None), match=False)
267 check_find(ipt2.HelpEnd, (["foo # bar?\n"], None, None), match=False)
267 # Nor in a string
268 # Nor in a string
268 check_find(ipt2.HelpEnd, (["foo = '''bar?\n"], None, None), match=False)
269 check_find(ipt2.HelpEnd, (["foo = '''bar?\n"], None, None), match=False)
269
270
270
271
271 def test_transform_help():
272 def test_transform_help():
272 tf = ipt2.HelpEnd((1, 0), (1, 9))
273 tf = ipt2.HelpEnd((1, 0), (1, 9))
273 assert tf.transform(HELP_IN_EXPR[0]) == HELP_IN_EXPR[2]
274 assert tf.transform(HELP_IN_EXPR[0]) == HELP_IN_EXPR[2]
274
275
275 tf = ipt2.HelpEnd((1, 0), (2, 3))
276 tf = ipt2.HelpEnd((1, 0), (2, 3))
276 assert tf.transform(HELP_CONTINUED_LINE[0]) == HELP_CONTINUED_LINE[2]
277 assert tf.transform(HELP_CONTINUED_LINE[0]) == HELP_CONTINUED_LINE[2]
277
278
278 tf = ipt2.HelpEnd((1, 0), (2, 8))
279 tf = ipt2.HelpEnd((1, 0), (2, 8))
279 assert tf.transform(HELP_MULTILINE[0]) == HELP_MULTILINE[2]
280 assert tf.transform(HELP_MULTILINE[0]) == HELP_MULTILINE[2]
280
281
281 tf = ipt2.HelpEnd((1, 0), (1, 0))
282 tf = ipt2.HelpEnd((1, 0), (1, 0))
282 assert tf.transform(HELP_UNICODE[0]) == HELP_UNICODE[2]
283 assert tf.transform(HELP_UNICODE[0]) == HELP_UNICODE[2]
283
284
284
285
285 def test_find_assign_op_dedent():
286 def test_find_assign_op_dedent():
286 """
287 """
287 be careful that empty token like dedent are not counted as parens
288 be careful that empty token like dedent are not counted as parens
288 """
289 """
289
290
290 class Tk:
291 class Tk:
291 def __init__(self, s):
292 def __init__(self, s):
292 self.string = s
293 self.string = s
293
294
294 assert _find_assign_op([Tk(s) for s in ("", "a", "=", "b")]) == 2
295 assert _find_assign_op([Tk(s) for s in ("", "a", "=", "b")]) == 2
295 assert (
296 assert (
296 _find_assign_op([Tk(s) for s in ("", "(", "a", "=", "b", ")", "=", "5")]) == 6
297 _find_assign_op([Tk(s) for s in ("", "(", "a", "=", "b", ")", "=", "5")]) == 6
297 )
298 )
298
299
299
300
300 examples = [
301 examples = [
301 pytest.param("a = 1", "complete", None),
302 pytest.param("a = 1", "complete", None),
302 pytest.param("for a in range(5):", "incomplete", 4),
303 pytest.param("for a in range(5):", "incomplete", 4),
303 pytest.param("for a in range(5):\n if a > 0:", "incomplete", 8),
304 pytest.param("for a in range(5):\n if a > 0:", "incomplete", 8),
304 pytest.param("raise = 2", "invalid", None),
305 pytest.param("raise = 2", "invalid", None),
305 pytest.param("a = [1,\n2,", "incomplete", 0),
306 pytest.param("a = [1,\n2,", "incomplete", 0),
306 pytest.param("(\n))", "incomplete", 0),
307 pytest.param("(\n))", "incomplete", 0),
307 pytest.param("\\\r\n", "incomplete", 0),
308 pytest.param("\\\r\n", "incomplete", 0),
308 pytest.param("a = '''\n hi", "incomplete", 3),
309 pytest.param("a = '''\n hi", "incomplete", 3),
309 pytest.param("def a():\n x=1\n global x", "invalid", None),
310 pytest.param("def a():\n x=1\n global x", "invalid", None),
310 pytest.param(
311 pytest.param(
311 "a \\ ",
312 "a \\ ",
312 "invalid",
313 "invalid",
313 None,
314 None,
314 marks=pytest.mark.xfail(
315 marks=pytest.mark.xfail(
315 reason="Bug in python 3.9.8 – bpo 45738",
316 reason="Bug in python 3.9.8 – bpo 45738",
316 condition=sys.version_info
317 condition=sys.version_info
317 in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)],
318 in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)],
318 raises=SystemError,
319 raises=SystemError,
319 strict=True,
320 strict=True,
320 ),
321 ),
321 ), # Nothing allowed after backslash,
322 ), # Nothing allowed after backslash,
322 pytest.param("1\\\n+2", "complete", None),
323 pytest.param("1\\\n+2", "complete", None),
323 ]
324 ]
324
325
325
326
326 @pytest.mark.parametrize("code, expected, number", examples)
327 @pytest.mark.parametrize("code, expected, number", examples)
327 def test_check_complete_param(code, expected, number):
328 def test_check_complete_param(code, expected, number):
328 cc = ipt2.TransformerManager().check_complete
329 cc = ipt2.TransformerManager().check_complete
329 assert cc(code) == (expected, number)
330 assert cc(code) == (expected, number)
330
331
331
332
332 @pytest.mark.xfail(platform.python_implementation() == "PyPy", reason="fail on pypy")
333 @pytest.mark.xfail(platform.python_implementation() == "PyPy", reason="fail on pypy")
333 @pytest.mark.xfail(
334 @pytest.mark.xfail(
334 reason="Bug in python 3.9.8 – bpo 45738",
335 reason="Bug in python 3.9.8 – bpo 45738",
335 condition=sys.version_info in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)],
336 condition=sys.version_info in [(3, 9, 8, "final", 0), (3, 11, 0, "alpha", 2)],
336 raises=SystemError,
337 raises=SystemError,
337 strict=True,
338 strict=True,
338 )
339 )
339 def test_check_complete():
340 def test_check_complete():
340 cc = ipt2.TransformerManager().check_complete
341 cc = ipt2.TransformerManager().check_complete
341
342
342 example = dedent(
343 example = dedent(
343 """
344 """
344 if True:
345 if True:
345 a=1"""
346 a=1"""
346 )
347 )
347
348
348 assert cc(example) == ("incomplete", 4)
349 assert cc(example) == ("incomplete", 4)
349 assert cc(example + "\n") == ("complete", None)
350 assert cc(example + "\n") == ("complete", None)
350 assert cc(example + "\n ") == ("complete", None)
351 assert cc(example + "\n ") == ("complete", None)
351
352
352 # no need to loop on all the letters/numbers.
353 # no need to loop on all the letters/numbers.
353 short = "12abAB" + string.printable[62:]
354 short = "12abAB" + string.printable[62:]
354 for c in short:
355 for c in short:
355 # test does not raise:
356 # test does not raise:
356 cc(c)
357 cc(c)
357 for k in short:
358 for k in short:
358 cc(c + k)
359 cc(c + k)
359
360
360 assert cc("def f():\n x=0\n \\\n ") == ("incomplete", 2)
361 assert cc("def f():\n x=0\n \\\n ") == ("incomplete", 2)
361
362
362
363
363 @pytest.mark.xfail(platform.python_implementation() == "PyPy", reason="fail on pypy")
364 @pytest.mark.xfail(platform.python_implementation() == "PyPy", reason="fail on pypy")
364 @pytest.mark.parametrize(
365 @pytest.mark.parametrize(
365 "value, expected",
366 "value, expected",
366 [
367 [
367 ('''def foo():\n """''', ("incomplete", 4)),
368 ('''def foo():\n """''', ("incomplete", 4)),
368 ("""async with example:\n pass""", ("incomplete", 4)),
369 ("""async with example:\n pass""", ("incomplete", 4)),
369 ("""async with example:\n pass\n """, ("complete", None)),
370 ("""async with example:\n pass\n """, ("complete", None)),
370 ],
371 ],
371 )
372 )
372 def test_check_complete_II(value, expected):
373 def test_check_complete_II(value, expected):
373 """
374 """
374 Test that multiple line strings are properly handled.
375 Test that multiple line strings are properly handled.
375
376
376 Separate test function for convenience
377 Separate test function for convenience
377
378
378 """
379 """
379 cc = ipt2.TransformerManager().check_complete
380 cc = ipt2.TransformerManager().check_complete
380 assert cc(value) == expected
381 assert cc(value) == expected
381
382
382
383
383 @pytest.mark.parametrize(
384 @pytest.mark.parametrize(
384 "value, expected",
385 "value, expected",
385 [
386 [
386 (")", ("invalid", None)),
387 (")", ("invalid", None)),
387 ("]", ("invalid", None)),
388 ("]", ("invalid", None)),
388 ("}", ("invalid", None)),
389 ("}", ("invalid", None)),
389 (")(", ("invalid", None)),
390 (")(", ("invalid", None)),
390 ("][", ("invalid", None)),
391 ("][", ("invalid", None)),
391 ("}{", ("invalid", None)),
392 ("}{", ("invalid", None)),
392 ("]()(", ("invalid", None)),
393 ("]()(", ("invalid", None)),
393 ("())(", ("invalid", None)),
394 ("())(", ("invalid", None)),
394 (")[](", ("invalid", None)),
395 (")[](", ("invalid", None)),
395 ("()](", ("invalid", None)),
396 ("()](", ("invalid", None)),
396 ],
397 ],
397 )
398 )
398 def test_check_complete_invalidates_sunken_brackets(value, expected):
399 def test_check_complete_invalidates_sunken_brackets(value, expected):
399 """
400 """
400 Test that a single line with more closing brackets than the opening ones is
401 Test that a single line with more closing brackets than the opening ones is
401 interpreted as invalid
402 interpreted as invalid
402 """
403 """
403 cc = ipt2.TransformerManager().check_complete
404 cc = ipt2.TransformerManager().check_complete
404 assert cc(value) == expected
405 assert cc(value) == expected
405
406
406
407
407 def test_null_cleanup_transformer():
408 def test_null_cleanup_transformer():
408 manager = ipt2.TransformerManager()
409 manager = ipt2.TransformerManager()
409 manager.cleanup_transforms.insert(0, null_cleanup_transformer)
410 manager.cleanup_transforms.insert(0, null_cleanup_transformer)
410 assert manager.transform_cell("") == ""
411 assert manager.transform_cell("") == ""
411
412
412
413
413 def test_side_effects_I():
414 def test_side_effects_I():
414 count = 0
415 count = 0
415
416
416 def counter(lines):
417 def counter(lines):
417 nonlocal count
418 nonlocal count
418 count += 1
419 count += 1
419 return lines
420 return lines
420
421
421 counter.has_side_effects = True
422 counter.has_side_effects = True
422
423
423 manager = ipt2.TransformerManager()
424 manager = ipt2.TransformerManager()
424 manager.cleanup_transforms.insert(0, counter)
425 manager.cleanup_transforms.insert(0, counter)
425 assert manager.check_complete("a=1\n") == ("complete", None)
426 assert manager.check_complete("a=1\n") == ("complete", None)
426 assert count == 0
427 assert count == 0
427
428
428
429
429 def test_side_effects_II():
430 def test_side_effects_II():
430 count = 0
431 count = 0
431
432
432 def counter(lines):
433 def counter(lines):
433 nonlocal count
434 nonlocal count
434 count += 1
435 count += 1
435 return lines
436 return lines
436
437
437 counter.has_side_effects = True
438 counter.has_side_effects = True
438
439
439 manager = ipt2.TransformerManager()
440 manager = ipt2.TransformerManager()
440 manager.line_transforms.insert(0, counter)
441 manager.line_transforms.insert(0, counter)
441 assert manager.check_complete("b=1\n") == ("complete", None)
442 assert manager.check_complete("b=1\n") == ("complete", None)
442 assert count == 0
443 assert count == 0
@@ -1,1100 +1,1100 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for the key interactiveshell module.
2 """Tests for the key interactiveshell module.
3
3
4 Historically the main classes in interactiveshell have been under-tested. This
4 Historically the main classes in interactiveshell have been under-tested. This
5 module should grow as many single-method tests as possible to trap many of the
5 module should grow as many single-method tests as possible to trap many of the
6 recurring bugs we seem to encounter with high-level interaction.
6 recurring bugs we seem to encounter with high-level interaction.
7 """
7 """
8
8
9 # Copyright (c) IPython Development Team.
9 # Copyright (c) IPython Development Team.
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11
11
12 import asyncio
12 import asyncio
13 import ast
13 import ast
14 import os
14 import os
15 import signal
15 import signal
16 import shutil
16 import shutil
17 import sys
17 import sys
18 import tempfile
18 import tempfile
19 import unittest
19 import unittest
20 from unittest import mock
20 from unittest import mock
21
21
22 from os.path import join
22 from os.path import join
23
23
24 from IPython.core.error import InputRejected
24 from IPython.core.error import InputRejected
25 from IPython.core.inputtransformer import InputTransformer
25 from IPython.core.inputtransformer import InputTransformer
26 from IPython.core import interactiveshell
26 from IPython.core import interactiveshell
27 from IPython.testing.decorators import (
27 from IPython.testing.decorators import (
28 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
28 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
29 )
29 )
30 from IPython.testing import tools as tt
30 from IPython.testing import tools as tt
31 from IPython.utils.process import find_cmd
31 from IPython.utils.process import find_cmd
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Globals
34 # Globals
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # This is used by every single test, no point repeating it ad nauseam
36 # This is used by every single test, no point repeating it ad nauseam
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Tests
39 # Tests
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 class DerivedInterrupt(KeyboardInterrupt):
42 class DerivedInterrupt(KeyboardInterrupt):
43 pass
43 pass
44
44
45 class InteractiveShellTestCase(unittest.TestCase):
45 class InteractiveShellTestCase(unittest.TestCase):
46 def test_naked_string_cells(self):
46 def test_naked_string_cells(self):
47 """Test that cells with only naked strings are fully executed"""
47 """Test that cells with only naked strings are fully executed"""
48 # First, single-line inputs
48 # First, single-line inputs
49 ip.run_cell('"a"\n')
49 ip.run_cell('"a"\n')
50 self.assertEqual(ip.user_ns['_'], 'a')
50 self.assertEqual(ip.user_ns['_'], 'a')
51 # And also multi-line cells
51 # And also multi-line cells
52 ip.run_cell('"""a\nb"""\n')
52 ip.run_cell('"""a\nb"""\n')
53 self.assertEqual(ip.user_ns['_'], 'a\nb')
53 self.assertEqual(ip.user_ns['_'], 'a\nb')
54
54
55 def test_run_empty_cell(self):
55 def test_run_empty_cell(self):
56 """Just make sure we don't get a horrible error with a blank
56 """Just make sure we don't get a horrible error with a blank
57 cell of input. Yes, I did overlook that."""
57 cell of input. Yes, I did overlook that."""
58 old_xc = ip.execution_count
58 old_xc = ip.execution_count
59 res = ip.run_cell('')
59 res = ip.run_cell('')
60 self.assertEqual(ip.execution_count, old_xc)
60 self.assertEqual(ip.execution_count, old_xc)
61 self.assertEqual(res.execution_count, None)
61 self.assertEqual(res.execution_count, None)
62
62
63 def test_run_cell_multiline(self):
63 def test_run_cell_multiline(self):
64 """Multi-block, multi-line cells must execute correctly.
64 """Multi-block, multi-line cells must execute correctly.
65 """
65 """
66 src = '\n'.join(["x=1",
66 src = '\n'.join(["x=1",
67 "y=2",
67 "y=2",
68 "if 1:",
68 "if 1:",
69 " x += 1",
69 " x += 1",
70 " y += 1",])
70 " y += 1",])
71 res = ip.run_cell(src)
71 res = ip.run_cell(src)
72 self.assertEqual(ip.user_ns['x'], 2)
72 self.assertEqual(ip.user_ns['x'], 2)
73 self.assertEqual(ip.user_ns['y'], 3)
73 self.assertEqual(ip.user_ns['y'], 3)
74 self.assertEqual(res.success, True)
74 self.assertEqual(res.success, True)
75 self.assertEqual(res.result, None)
75 self.assertEqual(res.result, None)
76
76
77 def test_multiline_string_cells(self):
77 def test_multiline_string_cells(self):
78 "Code sprinkled with multiline strings should execute (GH-306)"
78 "Code sprinkled with multiline strings should execute (GH-306)"
79 ip.run_cell('tmp=0')
79 ip.run_cell('tmp=0')
80 self.assertEqual(ip.user_ns['tmp'], 0)
80 self.assertEqual(ip.user_ns['tmp'], 0)
81 res = ip.run_cell('tmp=1;"""a\nb"""\n')
81 res = ip.run_cell('tmp=1;"""a\nb"""\n')
82 self.assertEqual(ip.user_ns['tmp'], 1)
82 self.assertEqual(ip.user_ns['tmp'], 1)
83 self.assertEqual(res.success, True)
83 self.assertEqual(res.success, True)
84 self.assertEqual(res.result, "a\nb")
84 self.assertEqual(res.result, "a\nb")
85
85
86 def test_dont_cache_with_semicolon(self):
86 def test_dont_cache_with_semicolon(self):
87 "Ending a line with semicolon should not cache the returned object (GH-307)"
87 "Ending a line with semicolon should not cache the returned object (GH-307)"
88 oldlen = len(ip.user_ns['Out'])
88 oldlen = len(ip.user_ns['Out'])
89 for cell in ['1;', '1;1;']:
89 for cell in ['1;', '1;1;']:
90 res = ip.run_cell(cell, store_history=True)
90 res = ip.run_cell(cell, store_history=True)
91 newlen = len(ip.user_ns['Out'])
91 newlen = len(ip.user_ns['Out'])
92 self.assertEqual(oldlen, newlen)
92 self.assertEqual(oldlen, newlen)
93 self.assertIsNone(res.result)
93 self.assertIsNone(res.result)
94 i = 0
94 i = 0
95 #also test the default caching behavior
95 #also test the default caching behavior
96 for cell in ['1', '1;1']:
96 for cell in ['1', '1;1']:
97 ip.run_cell(cell, store_history=True)
97 ip.run_cell(cell, store_history=True)
98 newlen = len(ip.user_ns['Out'])
98 newlen = len(ip.user_ns['Out'])
99 i += 1
99 i += 1
100 self.assertEqual(oldlen+i, newlen)
100 self.assertEqual(oldlen+i, newlen)
101
101
102 def test_syntax_error(self):
102 def test_syntax_error(self):
103 res = ip.run_cell("raise = 3")
103 res = ip.run_cell("raise = 3")
104 self.assertIsInstance(res.error_before_exec, SyntaxError)
104 self.assertIsInstance(res.error_before_exec, SyntaxError)
105
105
106 def test_In_variable(self):
106 def test_In_variable(self):
107 "Verify that In variable grows with user input (GH-284)"
107 "Verify that In variable grows with user input (GH-284)"
108 oldlen = len(ip.user_ns['In'])
108 oldlen = len(ip.user_ns['In'])
109 ip.run_cell('1;', store_history=True)
109 ip.run_cell('1;', store_history=True)
110 newlen = len(ip.user_ns['In'])
110 newlen = len(ip.user_ns['In'])
111 self.assertEqual(oldlen+1, newlen)
111 self.assertEqual(oldlen+1, newlen)
112 self.assertEqual(ip.user_ns['In'][-1],'1;')
112 self.assertEqual(ip.user_ns['In'][-1],'1;')
113
113
114 def test_magic_names_in_string(self):
114 def test_magic_names_in_string(self):
115 ip.run_cell('a = """\n%exit\n"""')
115 ip.run_cell('a = """\n%exit\n"""')
116 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
116 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
117
117
118 def test_trailing_newline(self):
118 def test_trailing_newline(self):
119 """test that running !(command) does not raise a SyntaxError"""
119 """test that running !(command) does not raise a SyntaxError"""
120 ip.run_cell('!(true)\n', False)
120 ip.run_cell('!(true)\n', False)
121 ip.run_cell('!(true)\n\n\n', False)
121 ip.run_cell('!(true)\n\n\n', False)
122
122
123 def test_gh_597(self):
123 def test_gh_597(self):
124 """Pretty-printing lists of objects with non-ascii reprs may cause
124 """Pretty-printing lists of objects with non-ascii reprs may cause
125 problems."""
125 problems."""
126 class Spam(object):
126 class Spam(object):
127 def __repr__(self):
127 def __repr__(self):
128 return "\xe9"*50
128 return "\xe9"*50
129 import IPython.core.formatters
129 import IPython.core.formatters
130 f = IPython.core.formatters.PlainTextFormatter()
130 f = IPython.core.formatters.PlainTextFormatter()
131 f([Spam(),Spam()])
131 f([Spam(),Spam()])
132
132
133
133
134 def test_future_flags(self):
134 def test_future_flags(self):
135 """Check that future flags are used for parsing code (gh-777)"""
135 """Check that future flags are used for parsing code (gh-777)"""
136 ip.run_cell('from __future__ import barry_as_FLUFL')
136 ip.run_cell('from __future__ import barry_as_FLUFL')
137 try:
137 try:
138 ip.run_cell('prfunc_return_val = 1 <> 2')
138 ip.run_cell('prfunc_return_val = 1 <> 2')
139 assert 'prfunc_return_val' in ip.user_ns
139 assert 'prfunc_return_val' in ip.user_ns
140 finally:
140 finally:
141 # Reset compiler flags so we don't mess up other tests.
141 # Reset compiler flags so we don't mess up other tests.
142 ip.compile.reset_compiler_flags()
142 ip.compile.reset_compiler_flags()
143
143
144 def test_can_pickle(self):
144 def test_can_pickle(self):
145 "Can we pickle objects defined interactively (GH-29)"
145 "Can we pickle objects defined interactively (GH-29)"
146 ip = get_ipython()
146 ip = get_ipython()
147 ip.reset()
147 ip.reset()
148 ip.run_cell(("class Mylist(list):\n"
148 ip.run_cell(("class Mylist(list):\n"
149 " def __init__(self,x=[]):\n"
149 " def __init__(self,x=[]):\n"
150 " list.__init__(self,x)"))
150 " list.__init__(self,x)"))
151 ip.run_cell("w=Mylist([1,2,3])")
151 ip.run_cell("w=Mylist([1,2,3])")
152
152
153 from pickle import dumps
153 from pickle import dumps
154
154
155 # We need to swap in our main module - this is only necessary
155 # We need to swap in our main module - this is only necessary
156 # inside the test framework, because IPython puts the interactive module
156 # inside the test framework, because IPython puts the interactive module
157 # in place (but the test framework undoes this).
157 # in place (but the test framework undoes this).
158 _main = sys.modules['__main__']
158 _main = sys.modules['__main__']
159 sys.modules['__main__'] = ip.user_module
159 sys.modules['__main__'] = ip.user_module
160 try:
160 try:
161 res = dumps(ip.user_ns["w"])
161 res = dumps(ip.user_ns["w"])
162 finally:
162 finally:
163 sys.modules['__main__'] = _main
163 sys.modules['__main__'] = _main
164 self.assertTrue(isinstance(res, bytes))
164 self.assertTrue(isinstance(res, bytes))
165
165
166 def test_global_ns(self):
166 def test_global_ns(self):
167 "Code in functions must be able to access variables outside them."
167 "Code in functions must be able to access variables outside them."
168 ip = get_ipython()
168 ip = get_ipython()
169 ip.run_cell("a = 10")
169 ip.run_cell("a = 10")
170 ip.run_cell(("def f(x):\n"
170 ip.run_cell(("def f(x):\n"
171 " return x + a"))
171 " return x + a"))
172 ip.run_cell("b = f(12)")
172 ip.run_cell("b = f(12)")
173 self.assertEqual(ip.user_ns["b"], 22)
173 self.assertEqual(ip.user_ns["b"], 22)
174
174
175 def test_bad_custom_tb(self):
175 def test_bad_custom_tb(self):
176 """Check that InteractiveShell is protected from bad custom exception handlers"""
176 """Check that InteractiveShell is protected from bad custom exception handlers"""
177 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
177 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
178 self.assertEqual(ip.custom_exceptions, (IOError,))
178 self.assertEqual(ip.custom_exceptions, (IOError,))
179 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
179 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
180 ip.run_cell(u'raise IOError("foo")')
180 ip.run_cell(u'raise IOError("foo")')
181 self.assertEqual(ip.custom_exceptions, ())
181 self.assertEqual(ip.custom_exceptions, ())
182
182
183 def test_bad_custom_tb_return(self):
183 def test_bad_custom_tb_return(self):
184 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
184 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
185 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
185 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
186 self.assertEqual(ip.custom_exceptions, (NameError,))
186 self.assertEqual(ip.custom_exceptions, (NameError,))
187 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
187 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
188 ip.run_cell(u'a=abracadabra')
188 ip.run_cell(u'a=abracadabra')
189 self.assertEqual(ip.custom_exceptions, ())
189 self.assertEqual(ip.custom_exceptions, ())
190
190
191 def test_drop_by_id(self):
191 def test_drop_by_id(self):
192 myvars = {"a":object(), "b":object(), "c": object()}
192 myvars = {"a":object(), "b":object(), "c": object()}
193 ip.push(myvars, interactive=False)
193 ip.push(myvars, interactive=False)
194 for name in myvars:
194 for name in myvars:
195 assert name in ip.user_ns, name
195 assert name in ip.user_ns, name
196 assert name in ip.user_ns_hidden, name
196 assert name in ip.user_ns_hidden, name
197 ip.user_ns['b'] = 12
197 ip.user_ns['b'] = 12
198 ip.drop_by_id(myvars)
198 ip.drop_by_id(myvars)
199 for name in ["a", "c"]:
199 for name in ["a", "c"]:
200 assert name not in ip.user_ns, name
200 assert name not in ip.user_ns, name
201 assert name not in ip.user_ns_hidden, name
201 assert name not in ip.user_ns_hidden, name
202 assert ip.user_ns['b'] == 12
202 assert ip.user_ns['b'] == 12
203 ip.reset()
203 ip.reset()
204
204
205 def test_var_expand(self):
205 def test_var_expand(self):
206 ip.user_ns['f'] = u'Ca\xf1o'
206 ip.user_ns['f'] = u'Ca\xf1o'
207 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
207 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
208 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
208 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
209 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
209 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
210 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
210 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
211
211
212 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
212 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
213
213
214 ip.user_ns['f'] = b'Ca\xc3\xb1o'
214 ip.user_ns['f'] = b'Ca\xc3\xb1o'
215 # This should not raise any exception:
215 # This should not raise any exception:
216 ip.var_expand(u'echo $f')
216 ip.var_expand(u'echo $f')
217
217
218 def test_var_expand_local(self):
218 def test_var_expand_local(self):
219 """Test local variable expansion in !system and %magic calls"""
219 """Test local variable expansion in !system and %magic calls"""
220 # !system
220 # !system
221 ip.run_cell(
221 ip.run_cell(
222 "def test():\n"
222 "def test():\n"
223 ' lvar = "ttt"\n'
223 ' lvar = "ttt"\n'
224 " ret = !echo {lvar}\n"
224 " ret = !echo {lvar}\n"
225 " return ret[0]\n"
225 " return ret[0]\n"
226 )
226 )
227 res = ip.user_ns["test"]()
227 res = ip.user_ns["test"]()
228 self.assertIn("ttt", res)
228 self.assertIn("ttt", res)
229
229
230 # %magic
230 # %magic
231 ip.run_cell(
231 ip.run_cell(
232 "def makemacro():\n"
232 "def makemacro():\n"
233 ' macroname = "macro_var_expand_locals"\n'
233 ' macroname = "macro_var_expand_locals"\n'
234 " %macro {macroname} codestr\n"
234 " %macro {macroname} codestr\n"
235 )
235 )
236 ip.user_ns["codestr"] = "str(12)"
236 ip.user_ns["codestr"] = "str(12)"
237 ip.run_cell("makemacro()")
237 ip.run_cell("makemacro()")
238 self.assertIn("macro_var_expand_locals", ip.user_ns)
238 self.assertIn("macro_var_expand_locals", ip.user_ns)
239
239
240 def test_var_expand_self(self):
240 def test_var_expand_self(self):
241 """Test variable expansion with the name 'self', which was failing.
241 """Test variable expansion with the name 'self', which was failing.
242
242
243 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
243 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
244 """
244 """
245 ip.run_cell(
245 ip.run_cell(
246 "class cTest:\n"
246 "class cTest:\n"
247 ' classvar="see me"\n'
247 ' classvar="see me"\n'
248 " def test(self):\n"
248 " def test(self):\n"
249 " res = !echo Variable: {self.classvar}\n"
249 " res = !echo Variable: {self.classvar}\n"
250 " return res[0]\n"
250 " return res[0]\n"
251 )
251 )
252 self.assertIn("see me", ip.user_ns["cTest"]().test())
252 self.assertIn("see me", ip.user_ns["cTest"]().test())
253
253
254 def test_bad_var_expand(self):
254 def test_bad_var_expand(self):
255 """var_expand on invalid formats shouldn't raise"""
255 """var_expand on invalid formats shouldn't raise"""
256 # SyntaxError
256 # SyntaxError
257 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
257 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
258 # NameError
258 # NameError
259 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
259 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
260 # ZeroDivisionError
260 # ZeroDivisionError
261 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
261 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
262
262
263 def test_silent_postexec(self):
263 def test_silent_postexec(self):
264 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
264 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
265 pre_explicit = mock.Mock()
265 pre_explicit = mock.Mock()
266 pre_always = mock.Mock()
266 pre_always = mock.Mock()
267 post_explicit = mock.Mock()
267 post_explicit = mock.Mock()
268 post_always = mock.Mock()
268 post_always = mock.Mock()
269 all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
269 all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
270
270
271 ip.events.register('pre_run_cell', pre_explicit)
271 ip.events.register('pre_run_cell', pre_explicit)
272 ip.events.register('pre_execute', pre_always)
272 ip.events.register('pre_execute', pre_always)
273 ip.events.register('post_run_cell', post_explicit)
273 ip.events.register('post_run_cell', post_explicit)
274 ip.events.register('post_execute', post_always)
274 ip.events.register('post_execute', post_always)
275
275
276 try:
276 try:
277 ip.run_cell("1", silent=True)
277 ip.run_cell("1", silent=True)
278 assert pre_always.called
278 assert pre_always.called
279 assert not pre_explicit.called
279 assert not pre_explicit.called
280 assert post_always.called
280 assert post_always.called
281 assert not post_explicit.called
281 assert not post_explicit.called
282 # double-check that non-silent exec did what we expected
282 # double-check that non-silent exec did what we expected
283 # silent to avoid
283 # silent to avoid
284 ip.run_cell("1")
284 ip.run_cell("1")
285 assert pre_explicit.called
285 assert pre_explicit.called
286 assert post_explicit.called
286 assert post_explicit.called
287 info, = pre_explicit.call_args[0]
287 info, = pre_explicit.call_args[0]
288 result, = post_explicit.call_args[0]
288 result, = post_explicit.call_args[0]
289 self.assertEqual(info, result.info)
289 self.assertEqual(info, result.info)
290 # check that post hooks are always called
290 # check that post hooks are always called
291 [m.reset_mock() for m in all_mocks]
291 [m.reset_mock() for m in all_mocks]
292 ip.run_cell("syntax error")
292 ip.run_cell("syntax error")
293 assert pre_always.called
293 assert pre_always.called
294 assert pre_explicit.called
294 assert pre_explicit.called
295 assert post_always.called
295 assert post_always.called
296 assert post_explicit.called
296 assert post_explicit.called
297 info, = pre_explicit.call_args[0]
297 info, = pre_explicit.call_args[0]
298 result, = post_explicit.call_args[0]
298 result, = post_explicit.call_args[0]
299 self.assertEqual(info, result.info)
299 self.assertEqual(info, result.info)
300 finally:
300 finally:
301 # remove post-exec
301 # remove post-exec
302 ip.events.unregister('pre_run_cell', pre_explicit)
302 ip.events.unregister('pre_run_cell', pre_explicit)
303 ip.events.unregister('pre_execute', pre_always)
303 ip.events.unregister('pre_execute', pre_always)
304 ip.events.unregister('post_run_cell', post_explicit)
304 ip.events.unregister('post_run_cell', post_explicit)
305 ip.events.unregister('post_execute', post_always)
305 ip.events.unregister('post_execute', post_always)
306
306
307 def test_silent_noadvance(self):
307 def test_silent_noadvance(self):
308 """run_cell(silent=True) doesn't advance execution_count"""
308 """run_cell(silent=True) doesn't advance execution_count"""
309 ec = ip.execution_count
309 ec = ip.execution_count
310 # silent should force store_history=False
310 # silent should force store_history=False
311 ip.run_cell("1", store_history=True, silent=True)
311 ip.run_cell("1", store_history=True, silent=True)
312
312
313 self.assertEqual(ec, ip.execution_count)
313 self.assertEqual(ec, ip.execution_count)
314 # double-check that non-silent exec did what we expected
314 # double-check that non-silent exec did what we expected
315 # silent to avoid
315 # silent to avoid
316 ip.run_cell("1", store_history=True)
316 ip.run_cell("1", store_history=True)
317 self.assertEqual(ec+1, ip.execution_count)
317 self.assertEqual(ec+1, ip.execution_count)
318
318
319 def test_silent_nodisplayhook(self):
319 def test_silent_nodisplayhook(self):
320 """run_cell(silent=True) doesn't trigger displayhook"""
320 """run_cell(silent=True) doesn't trigger displayhook"""
321 d = dict(called=False)
321 d = dict(called=False)
322
322
323 trap = ip.display_trap
323 trap = ip.display_trap
324 save_hook = trap.hook
324 save_hook = trap.hook
325
325
326 def failing_hook(*args, **kwargs):
326 def failing_hook(*args, **kwargs):
327 d['called'] = True
327 d['called'] = True
328
328
329 try:
329 try:
330 trap.hook = failing_hook
330 trap.hook = failing_hook
331 res = ip.run_cell("1", silent=True)
331 res = ip.run_cell("1", silent=True)
332 self.assertFalse(d['called'])
332 self.assertFalse(d['called'])
333 self.assertIsNone(res.result)
333 self.assertIsNone(res.result)
334 # double-check that non-silent exec did what we expected
334 # double-check that non-silent exec did what we expected
335 # silent to avoid
335 # silent to avoid
336 ip.run_cell("1")
336 ip.run_cell("1")
337 self.assertTrue(d['called'])
337 self.assertTrue(d['called'])
338 finally:
338 finally:
339 trap.hook = save_hook
339 trap.hook = save_hook
340
340
341 def test_ofind_line_magic(self):
341 def test_ofind_line_magic(self):
342 from IPython.core.magic import register_line_magic
342 from IPython.core.magic import register_line_magic
343
343
344 @register_line_magic
344 @register_line_magic
345 def lmagic(line):
345 def lmagic(line):
346 "A line magic"
346 "A line magic"
347
347
348 # Get info on line magic
348 # Get info on line magic
349 lfind = ip._ofind("lmagic")
349 lfind = ip._ofind("lmagic")
350 info = dict(
350 info = dict(
351 found=True,
351 found=True,
352 isalias=False,
352 isalias=False,
353 ismagic=True,
353 ismagic=True,
354 namespace="IPython internal",
354 namespace="IPython internal",
355 obj=lmagic,
355 obj=lmagic,
356 parent=None,
356 parent=None,
357 )
357 )
358 self.assertEqual(lfind, info)
358 self.assertEqual(lfind, info)
359
359
360 def test_ofind_cell_magic(self):
360 def test_ofind_cell_magic(self):
361 from IPython.core.magic import register_cell_magic
361 from IPython.core.magic import register_cell_magic
362
362
363 @register_cell_magic
363 @register_cell_magic
364 def cmagic(line, cell):
364 def cmagic(line, cell):
365 "A cell magic"
365 "A cell magic"
366
366
367 # Get info on cell magic
367 # Get info on cell magic
368 find = ip._ofind("cmagic")
368 find = ip._ofind("cmagic")
369 info = dict(
369 info = dict(
370 found=True,
370 found=True,
371 isalias=False,
371 isalias=False,
372 ismagic=True,
372 ismagic=True,
373 namespace="IPython internal",
373 namespace="IPython internal",
374 obj=cmagic,
374 obj=cmagic,
375 parent=None,
375 parent=None,
376 )
376 )
377 self.assertEqual(find, info)
377 self.assertEqual(find, info)
378
378
379 def test_ofind_property_with_error(self):
379 def test_ofind_property_with_error(self):
380 class A(object):
380 class A(object):
381 @property
381 @property
382 def foo(self):
382 def foo(self):
383 raise NotImplementedError() # pragma: no cover
383 raise NotImplementedError() # pragma: no cover
384
384
385 a = A()
385 a = A()
386
386
387 found = ip._ofind('a.foo', [('locals', locals())])
387 found = ip._ofind('a.foo', [('locals', locals())])
388 info = dict(found=True, isalias=False, ismagic=False,
388 info = dict(found=True, isalias=False, ismagic=False,
389 namespace='locals', obj=A.foo, parent=a)
389 namespace='locals', obj=A.foo, parent=a)
390 self.assertEqual(found, info)
390 self.assertEqual(found, info)
391
391
392 def test_ofind_multiple_attribute_lookups(self):
392 def test_ofind_multiple_attribute_lookups(self):
393 class A(object):
393 class A(object):
394 @property
394 @property
395 def foo(self):
395 def foo(self):
396 raise NotImplementedError() # pragma: no cover
396 raise NotImplementedError() # pragma: no cover
397
397
398 a = A()
398 a = A()
399 a.a = A()
399 a.a = A()
400 a.a.a = A()
400 a.a.a = A()
401
401
402 found = ip._ofind('a.a.a.foo', [('locals', locals())])
402 found = ip._ofind('a.a.a.foo', [('locals', locals())])
403 info = dict(found=True, isalias=False, ismagic=False,
403 info = dict(found=True, isalias=False, ismagic=False,
404 namespace='locals', obj=A.foo, parent=a.a.a)
404 namespace='locals', obj=A.foo, parent=a.a.a)
405 self.assertEqual(found, info)
405 self.assertEqual(found, info)
406
406
407 def test_ofind_slotted_attributes(self):
407 def test_ofind_slotted_attributes(self):
408 class A(object):
408 class A(object):
409 __slots__ = ['foo']
409 __slots__ = ['foo']
410 def __init__(self):
410 def __init__(self):
411 self.foo = 'bar'
411 self.foo = 'bar'
412
412
413 a = A()
413 a = A()
414 found = ip._ofind('a.foo', [('locals', locals())])
414 found = ip._ofind('a.foo', [('locals', locals())])
415 info = dict(found=True, isalias=False, ismagic=False,
415 info = dict(found=True, isalias=False, ismagic=False,
416 namespace='locals', obj=a.foo, parent=a)
416 namespace='locals', obj=a.foo, parent=a)
417 self.assertEqual(found, info)
417 self.assertEqual(found, info)
418
418
419 found = ip._ofind('a.bar', [('locals', locals())])
419 found = ip._ofind('a.bar', [('locals', locals())])
420 info = dict(found=False, isalias=False, ismagic=False,
420 info = dict(found=False, isalias=False, ismagic=False,
421 namespace=None, obj=None, parent=a)
421 namespace=None, obj=None, parent=a)
422 self.assertEqual(found, info)
422 self.assertEqual(found, info)
423
423
424 def test_ofind_prefers_property_to_instance_level_attribute(self):
424 def test_ofind_prefers_property_to_instance_level_attribute(self):
425 class A(object):
425 class A(object):
426 @property
426 @property
427 def foo(self):
427 def foo(self):
428 return 'bar'
428 return 'bar'
429 a = A()
429 a = A()
430 a.__dict__["foo"] = "baz"
430 a.__dict__["foo"] = "baz"
431 self.assertEqual(a.foo, "bar")
431 self.assertEqual(a.foo, "bar")
432 found = ip._ofind("a.foo", [("locals", locals())])
432 found = ip._ofind("a.foo", [("locals", locals())])
433 self.assertIs(found["obj"], A.foo)
433 self.assertIs(found["obj"], A.foo)
434
434
435 def test_custom_syntaxerror_exception(self):
435 def test_custom_syntaxerror_exception(self):
436 called = []
436 called = []
437 def my_handler(shell, etype, value, tb, tb_offset=None):
437 def my_handler(shell, etype, value, tb, tb_offset=None):
438 called.append(etype)
438 called.append(etype)
439 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
439 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
440
440
441 ip.set_custom_exc((SyntaxError,), my_handler)
441 ip.set_custom_exc((SyntaxError,), my_handler)
442 try:
442 try:
443 ip.run_cell("1f")
443 ip.run_cell("1f")
444 # Check that this was called, and only once.
444 # Check that this was called, and only once.
445 self.assertEqual(called, [SyntaxError])
445 self.assertEqual(called, [SyntaxError])
446 finally:
446 finally:
447 # Reset the custom exception hook
447 # Reset the custom exception hook
448 ip.set_custom_exc((), None)
448 ip.set_custom_exc((), None)
449
449
450 def test_custom_exception(self):
450 def test_custom_exception(self):
451 called = []
451 called = []
452 def my_handler(shell, etype, value, tb, tb_offset=None):
452 def my_handler(shell, etype, value, tb, tb_offset=None):
453 called.append(etype)
453 called.append(etype)
454 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
454 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
455
455
456 ip.set_custom_exc((ValueError,), my_handler)
456 ip.set_custom_exc((ValueError,), my_handler)
457 try:
457 try:
458 res = ip.run_cell("raise ValueError('test')")
458 res = ip.run_cell("raise ValueError('test')")
459 # Check that this was called, and only once.
459 # Check that this was called, and only once.
460 self.assertEqual(called, [ValueError])
460 self.assertEqual(called, [ValueError])
461 # Check that the error is on the result object
461 # Check that the error is on the result object
462 self.assertIsInstance(res.error_in_exec, ValueError)
462 self.assertIsInstance(res.error_in_exec, ValueError)
463 finally:
463 finally:
464 # Reset the custom exception hook
464 # Reset the custom exception hook
465 ip.set_custom_exc((), None)
465 ip.set_custom_exc((), None)
466
466
467 @mock.patch("builtins.print")
467 @mock.patch("builtins.print")
468 def test_showtraceback_with_surrogates(self, mocked_print):
468 def test_showtraceback_with_surrogates(self, mocked_print):
469 values = []
469 values = []
470
470
471 def mock_print_func(value, sep=" ", end="\n", file=sys.stdout, flush=False):
471 def mock_print_func(value, sep=" ", end="\n", file=sys.stdout, flush=False):
472 values.append(value)
472 values.append(value)
473 if value == chr(0xD8FF):
473 if value == chr(0xD8FF):
474 raise UnicodeEncodeError("utf-8", chr(0xD8FF), 0, 1, "")
474 raise UnicodeEncodeError("utf-8", chr(0xD8FF), 0, 1, "")
475
475
476 # mock builtins.print
476 # mock builtins.print
477 mocked_print.side_effect = mock_print_func
477 mocked_print.side_effect = mock_print_func
478
478
479 # ip._showtraceback() is replaced in globalipapp.py.
479 # ip._showtraceback() is replaced in globalipapp.py.
480 # Call original method to test.
480 # Call original method to test.
481 interactiveshell.InteractiveShell._showtraceback(ip, None, None, chr(0xD8FF))
481 interactiveshell.InteractiveShell._showtraceback(ip, None, None, chr(0xD8FF))
482
482
483 self.assertEqual(mocked_print.call_count, 2)
483 self.assertEqual(mocked_print.call_count, 2)
484 self.assertEqual(values, [chr(0xD8FF), "\\ud8ff"])
484 self.assertEqual(values, [chr(0xD8FF), "\\ud8ff"])
485
485
486 def test_mktempfile(self):
486 def test_mktempfile(self):
487 filename = ip.mktempfile()
487 filename = ip.mktempfile()
488 # Check that we can open the file again on Windows
488 # Check that we can open the file again on Windows
489 with open(filename, "w", encoding="utf-8") as f:
489 with open(filename, "w", encoding="utf-8") as f:
490 f.write("abc")
490 f.write("abc")
491
491
492 filename = ip.mktempfile(data="blah")
492 filename = ip.mktempfile(data="blah")
493 with open(filename, "r", encoding="utf-8") as f:
493 with open(filename, "r", encoding="utf-8") as f:
494 self.assertEqual(f.read(), "blah")
494 self.assertEqual(f.read(), "blah")
495
495
496 def test_new_main_mod(self):
496 def test_new_main_mod(self):
497 # Smoketest to check that this accepts a unicode module name
497 # Smoketest to check that this accepts a unicode module name
498 name = u'jiefmw'
498 name = u'jiefmw'
499 mod = ip.new_main_mod(u'%s.py' % name, name)
499 mod = ip.new_main_mod(u'%s.py' % name, name)
500 self.assertEqual(mod.__name__, name)
500 self.assertEqual(mod.__name__, name)
501
501
502 def test_get_exception_only(self):
502 def test_get_exception_only(self):
503 try:
503 try:
504 raise KeyboardInterrupt
504 raise KeyboardInterrupt
505 except KeyboardInterrupt:
505 except KeyboardInterrupt:
506 msg = ip.get_exception_only()
506 msg = ip.get_exception_only()
507 self.assertEqual(msg, 'KeyboardInterrupt\n')
507 self.assertEqual(msg, 'KeyboardInterrupt\n')
508
508
509 try:
509 try:
510 raise DerivedInterrupt("foo")
510 raise DerivedInterrupt("foo")
511 except KeyboardInterrupt:
511 except KeyboardInterrupt:
512 msg = ip.get_exception_only()
512 msg = ip.get_exception_only()
513 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
513 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
514
514
515 def test_inspect_text(self):
515 def test_inspect_text(self):
516 ip.run_cell('a = 5')
516 ip.run_cell('a = 5')
517 text = ip.object_inspect_text('a')
517 text = ip.object_inspect_text('a')
518 self.assertIsInstance(text, str)
518 self.assertIsInstance(text, str)
519
519
520 def test_last_execution_result(self):
520 def test_last_execution_result(self):
521 """ Check that last execution result gets set correctly (GH-10702) """
521 """ Check that last execution result gets set correctly (GH-10702) """
522 result = ip.run_cell('a = 5; a')
522 result = ip.run_cell('a = 5; a')
523 self.assertTrue(ip.last_execution_succeeded)
523 self.assertTrue(ip.last_execution_succeeded)
524 self.assertEqual(ip.last_execution_result.result, 5)
524 self.assertEqual(ip.last_execution_result.result, 5)
525
525
526 result = ip.run_cell('a = x_invalid_id_x')
526 result = ip.run_cell('a = x_invalid_id_x')
527 self.assertFalse(ip.last_execution_succeeded)
527 self.assertFalse(ip.last_execution_succeeded)
528 self.assertFalse(ip.last_execution_result.success)
528 self.assertFalse(ip.last_execution_result.success)
529 self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
529 self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
530
530
531 def test_reset_aliasing(self):
531 def test_reset_aliasing(self):
532 """ Check that standard posix aliases work after %reset. """
532 """ Check that standard posix aliases work after %reset. """
533 if os.name != 'posix':
533 if os.name != 'posix':
534 return
534 return
535
535
536 ip.reset()
536 ip.reset()
537 for cmd in ('clear', 'more', 'less', 'man'):
537 for cmd in ('clear', 'more', 'less', 'man'):
538 res = ip.run_cell('%' + cmd)
538 res = ip.run_cell('%' + cmd)
539 self.assertEqual(res.success, True)
539 self.assertEqual(res.success, True)
540
540
541
541
542 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
542 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
543
543
544 @onlyif_unicode_paths
544 @onlyif_unicode_paths
545 def setUp(self):
545 def setUp(self):
546 self.BASETESTDIR = tempfile.mkdtemp()
546 self.BASETESTDIR = tempfile.mkdtemp()
547 self.TESTDIR = join(self.BASETESTDIR, u"Γ₯Àâ")
547 self.TESTDIR = join(self.BASETESTDIR, u"Γ₯Àâ")
548 os.mkdir(self.TESTDIR)
548 os.mkdir(self.TESTDIR)
549 with open(
549 with open(
550 join(self.TESTDIR, u"Γ₯Àâtestscript.py"), "w", encoding="utf-8"
550 join(self.TESTDIR, "Γ₯Àâtestscript.py"), "w", encoding="utf-8"
551 ) as sfile:
551 ) as sfile:
552 sfile.write("pass\n")
552 sfile.write("pass\n")
553 self.oldpath = os.getcwd()
553 self.oldpath = os.getcwd()
554 os.chdir(self.TESTDIR)
554 os.chdir(self.TESTDIR)
555 self.fname = u"Γ₯Àâtestscript.py"
555 self.fname = u"Γ₯Àâtestscript.py"
556
556
557 def tearDown(self):
557 def tearDown(self):
558 os.chdir(self.oldpath)
558 os.chdir(self.oldpath)
559 shutil.rmtree(self.BASETESTDIR)
559 shutil.rmtree(self.BASETESTDIR)
560
560
561 @onlyif_unicode_paths
561 @onlyif_unicode_paths
562 def test_1(self):
562 def test_1(self):
563 """Test safe_execfile with non-ascii path
563 """Test safe_execfile with non-ascii path
564 """
564 """
565 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
565 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
566
566
567 class ExitCodeChecks(tt.TempFileMixin):
567 class ExitCodeChecks(tt.TempFileMixin):
568
568
569 def setUp(self):
569 def setUp(self):
570 self.system = ip.system_raw
570 self.system = ip.system_raw
571
571
572 def test_exit_code_ok(self):
572 def test_exit_code_ok(self):
573 self.system('exit 0')
573 self.system('exit 0')
574 self.assertEqual(ip.user_ns['_exit_code'], 0)
574 self.assertEqual(ip.user_ns['_exit_code'], 0)
575
575
576 def test_exit_code_error(self):
576 def test_exit_code_error(self):
577 self.system('exit 1')
577 self.system('exit 1')
578 self.assertEqual(ip.user_ns['_exit_code'], 1)
578 self.assertEqual(ip.user_ns['_exit_code'], 1)
579
579
580 @skipif(not hasattr(signal, 'SIGALRM'))
580 @skipif(not hasattr(signal, 'SIGALRM'))
581 def test_exit_code_signal(self):
581 def test_exit_code_signal(self):
582 self.mktmp("import signal, time\n"
582 self.mktmp("import signal, time\n"
583 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
583 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
584 "time.sleep(1)\n")
584 "time.sleep(1)\n")
585 self.system("%s %s" % (sys.executable, self.fname))
585 self.system("%s %s" % (sys.executable, self.fname))
586 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
586 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
587
587
588 @onlyif_cmds_exist("csh")
588 @onlyif_cmds_exist("csh")
589 def test_exit_code_signal_csh(self): # pragma: no cover
589 def test_exit_code_signal_csh(self): # pragma: no cover
590 SHELL = os.environ.get("SHELL", None)
590 SHELL = os.environ.get("SHELL", None)
591 os.environ["SHELL"] = find_cmd("csh")
591 os.environ["SHELL"] = find_cmd("csh")
592 try:
592 try:
593 self.test_exit_code_signal()
593 self.test_exit_code_signal()
594 finally:
594 finally:
595 if SHELL is not None:
595 if SHELL is not None:
596 os.environ['SHELL'] = SHELL
596 os.environ['SHELL'] = SHELL
597 else:
597 else:
598 del os.environ['SHELL']
598 del os.environ['SHELL']
599
599
600
600
601 class TestSystemRaw(ExitCodeChecks):
601 class TestSystemRaw(ExitCodeChecks):
602
602
603 def setUp(self):
603 def setUp(self):
604 super().setUp()
604 super().setUp()
605 self.system = ip.system_raw
605 self.system = ip.system_raw
606
606
607 @onlyif_unicode_paths
607 @onlyif_unicode_paths
608 def test_1(self):
608 def test_1(self):
609 """Test system_raw with non-ascii cmd
609 """Test system_raw with non-ascii cmd
610 """
610 """
611 cmd = u'''python -c "'Γ₯Àâ'" '''
611 cmd = u'''python -c "'Γ₯Àâ'" '''
612 ip.system_raw(cmd)
612 ip.system_raw(cmd)
613
613
614 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
614 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
615 @mock.patch('os.system', side_effect=KeyboardInterrupt)
615 @mock.patch('os.system', side_effect=KeyboardInterrupt)
616 def test_control_c(self, *mocks):
616 def test_control_c(self, *mocks):
617 try:
617 try:
618 self.system("sleep 1 # wont happen")
618 self.system("sleep 1 # wont happen")
619 except KeyboardInterrupt: # pragma: no cove
619 except KeyboardInterrupt: # pragma: no cove
620 self.fail(
620 self.fail(
621 "system call should intercept "
621 "system call should intercept "
622 "keyboard interrupt from subprocess.call"
622 "keyboard interrupt from subprocess.call"
623 )
623 )
624 self.assertEqual(ip.user_ns["_exit_code"], -signal.SIGINT)
624 self.assertEqual(ip.user_ns["_exit_code"], -signal.SIGINT)
625
625
626 def test_magic_warnings(self):
626 def test_magic_warnings(self):
627 for magic_cmd in ("pip", "conda", "cd"):
627 for magic_cmd in ("pip", "conda", "cd"):
628 with self.assertWarnsRegex(Warning, "You executed the system command"):
628 with self.assertWarnsRegex(Warning, "You executed the system command"):
629 ip.system_raw(magic_cmd)
629 ip.system_raw(magic_cmd)
630
630
631 # TODO: Exit codes are currently ignored on Windows.
631 # TODO: Exit codes are currently ignored on Windows.
632 class TestSystemPipedExitCode(ExitCodeChecks):
632 class TestSystemPipedExitCode(ExitCodeChecks):
633
633
634 def setUp(self):
634 def setUp(self):
635 super().setUp()
635 super().setUp()
636 self.system = ip.system_piped
636 self.system = ip.system_piped
637
637
638 @skip_win32
638 @skip_win32
639 def test_exit_code_ok(self):
639 def test_exit_code_ok(self):
640 ExitCodeChecks.test_exit_code_ok(self)
640 ExitCodeChecks.test_exit_code_ok(self)
641
641
642 @skip_win32
642 @skip_win32
643 def test_exit_code_error(self):
643 def test_exit_code_error(self):
644 ExitCodeChecks.test_exit_code_error(self)
644 ExitCodeChecks.test_exit_code_error(self)
645
645
646 @skip_win32
646 @skip_win32
647 def test_exit_code_signal(self):
647 def test_exit_code_signal(self):
648 ExitCodeChecks.test_exit_code_signal(self)
648 ExitCodeChecks.test_exit_code_signal(self)
649
649
650 class TestModules(tt.TempFileMixin):
650 class TestModules(tt.TempFileMixin):
651 def test_extraneous_loads(self):
651 def test_extraneous_loads(self):
652 """Test we're not loading modules on startup that we shouldn't.
652 """Test we're not loading modules on startup that we shouldn't.
653 """
653 """
654 self.mktmp("import sys\n"
654 self.mktmp("import sys\n"
655 "print('numpy' in sys.modules)\n"
655 "print('numpy' in sys.modules)\n"
656 "print('ipyparallel' in sys.modules)\n"
656 "print('ipyparallel' in sys.modules)\n"
657 "print('ipykernel' in sys.modules)\n"
657 "print('ipykernel' in sys.modules)\n"
658 )
658 )
659 out = "False\nFalse\nFalse\n"
659 out = "False\nFalse\nFalse\n"
660 tt.ipexec_validate(self.fname, out)
660 tt.ipexec_validate(self.fname, out)
661
661
662 class Negator(ast.NodeTransformer):
662 class Negator(ast.NodeTransformer):
663 """Negates all number literals in an AST."""
663 """Negates all number literals in an AST."""
664
664
665 # for python 3.7 and earlier
665 # for python 3.7 and earlier
666 def visit_Num(self, node):
666 def visit_Num(self, node):
667 node.n = -node.n
667 node.n = -node.n
668 return node
668 return node
669
669
670 # for python 3.8+
670 # for python 3.8+
671 def visit_Constant(self, node):
671 def visit_Constant(self, node):
672 if isinstance(node.value, int):
672 if isinstance(node.value, int):
673 return self.visit_Num(node)
673 return self.visit_Num(node)
674 return node
674 return node
675
675
676 class TestAstTransform(unittest.TestCase):
676 class TestAstTransform(unittest.TestCase):
677 def setUp(self):
677 def setUp(self):
678 self.negator = Negator()
678 self.negator = Negator()
679 ip.ast_transformers.append(self.negator)
679 ip.ast_transformers.append(self.negator)
680
680
681 def tearDown(self):
681 def tearDown(self):
682 ip.ast_transformers.remove(self.negator)
682 ip.ast_transformers.remove(self.negator)
683
683
684 def test_non_int_const(self):
684 def test_non_int_const(self):
685 with tt.AssertPrints("hello"):
685 with tt.AssertPrints("hello"):
686 ip.run_cell('print("hello")')
686 ip.run_cell('print("hello")')
687
687
688 def test_run_cell(self):
688 def test_run_cell(self):
689 with tt.AssertPrints("-34"):
689 with tt.AssertPrints("-34"):
690 ip.run_cell("print(12 + 22)")
690 ip.run_cell("print(12 + 22)")
691
691
692 # A named reference to a number shouldn't be transformed.
692 # A named reference to a number shouldn't be transformed.
693 ip.user_ns["n"] = 55
693 ip.user_ns["n"] = 55
694 with tt.AssertNotPrints("-55"):
694 with tt.AssertNotPrints("-55"):
695 ip.run_cell("print(n)")
695 ip.run_cell("print(n)")
696
696
697 def test_timeit(self):
697 def test_timeit(self):
698 called = set()
698 called = set()
699 def f(x):
699 def f(x):
700 called.add(x)
700 called.add(x)
701 ip.push({'f':f})
701 ip.push({'f':f})
702
702
703 with tt.AssertPrints("std. dev. of"):
703 with tt.AssertPrints("std. dev. of"):
704 ip.run_line_magic("timeit", "-n1 f(1)")
704 ip.run_line_magic("timeit", "-n1 f(1)")
705 self.assertEqual(called, {-1})
705 self.assertEqual(called, {-1})
706 called.clear()
706 called.clear()
707
707
708 with tt.AssertPrints("std. dev. of"):
708 with tt.AssertPrints("std. dev. of"):
709 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
709 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
710 self.assertEqual(called, {-2, -3})
710 self.assertEqual(called, {-2, -3})
711
711
712 def test_time(self):
712 def test_time(self):
713 called = []
713 called = []
714 def f(x):
714 def f(x):
715 called.append(x)
715 called.append(x)
716 ip.push({'f':f})
716 ip.push({'f':f})
717
717
718 # Test with an expression
718 # Test with an expression
719 with tt.AssertPrints("Wall time: "):
719 with tt.AssertPrints("Wall time: "):
720 ip.run_line_magic("time", "f(5+9)")
720 ip.run_line_magic("time", "f(5+9)")
721 self.assertEqual(called, [-14])
721 self.assertEqual(called, [-14])
722 called[:] = []
722 called[:] = []
723
723
724 # Test with a statement (different code path)
724 # Test with a statement (different code path)
725 with tt.AssertPrints("Wall time: "):
725 with tt.AssertPrints("Wall time: "):
726 ip.run_line_magic("time", "a = f(-3 + -2)")
726 ip.run_line_magic("time", "a = f(-3 + -2)")
727 self.assertEqual(called, [5])
727 self.assertEqual(called, [5])
728
728
729 def test_macro(self):
729 def test_macro(self):
730 ip.push({'a':10})
730 ip.push({'a':10})
731 # The AST transformation makes this do a+=-1
731 # The AST transformation makes this do a+=-1
732 ip.define_macro("amacro", "a+=1\nprint(a)")
732 ip.define_macro("amacro", "a+=1\nprint(a)")
733
733
734 with tt.AssertPrints("9"):
734 with tt.AssertPrints("9"):
735 ip.run_cell("amacro")
735 ip.run_cell("amacro")
736 with tt.AssertPrints("8"):
736 with tt.AssertPrints("8"):
737 ip.run_cell("amacro")
737 ip.run_cell("amacro")
738
738
739 class TestMiscTransform(unittest.TestCase):
739 class TestMiscTransform(unittest.TestCase):
740
740
741
741
742 def test_transform_only_once(self):
742 def test_transform_only_once(self):
743 cleanup = 0
743 cleanup = 0
744 line_t = 0
744 line_t = 0
745 def count_cleanup(lines):
745 def count_cleanup(lines):
746 nonlocal cleanup
746 nonlocal cleanup
747 cleanup += 1
747 cleanup += 1
748 return lines
748 return lines
749
749
750 def count_line_t(lines):
750 def count_line_t(lines):
751 nonlocal line_t
751 nonlocal line_t
752 line_t += 1
752 line_t += 1
753 return lines
753 return lines
754
754
755 ip.input_transformer_manager.cleanup_transforms.append(count_cleanup)
755 ip.input_transformer_manager.cleanup_transforms.append(count_cleanup)
756 ip.input_transformer_manager.line_transforms.append(count_line_t)
756 ip.input_transformer_manager.line_transforms.append(count_line_t)
757
757
758 ip.run_cell('1')
758 ip.run_cell('1')
759
759
760 assert cleanup == 1
760 assert cleanup == 1
761 assert line_t == 1
761 assert line_t == 1
762
762
763 class IntegerWrapper(ast.NodeTransformer):
763 class IntegerWrapper(ast.NodeTransformer):
764 """Wraps all integers in a call to Integer()"""
764 """Wraps all integers in a call to Integer()"""
765
765
766 # for Python 3.7 and earlier
766 # for Python 3.7 and earlier
767
767
768 # for Python 3.7 and earlier
768 # for Python 3.7 and earlier
769 def visit_Num(self, node):
769 def visit_Num(self, node):
770 if isinstance(node.n, int):
770 if isinstance(node.n, int):
771 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
771 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
772 args=[node], keywords=[])
772 args=[node], keywords=[])
773 return node
773 return node
774
774
775 # For Python 3.8+
775 # For Python 3.8+
776 def visit_Constant(self, node):
776 def visit_Constant(self, node):
777 if isinstance(node.value, int):
777 if isinstance(node.value, int):
778 return self.visit_Num(node)
778 return self.visit_Num(node)
779 return node
779 return node
780
780
781
781
782 class TestAstTransform2(unittest.TestCase):
782 class TestAstTransform2(unittest.TestCase):
783 def setUp(self):
783 def setUp(self):
784 self.intwrapper = IntegerWrapper()
784 self.intwrapper = IntegerWrapper()
785 ip.ast_transformers.append(self.intwrapper)
785 ip.ast_transformers.append(self.intwrapper)
786
786
787 self.calls = []
787 self.calls = []
788 def Integer(*args):
788 def Integer(*args):
789 self.calls.append(args)
789 self.calls.append(args)
790 return args
790 return args
791 ip.push({"Integer": Integer})
791 ip.push({"Integer": Integer})
792
792
793 def tearDown(self):
793 def tearDown(self):
794 ip.ast_transformers.remove(self.intwrapper)
794 ip.ast_transformers.remove(self.intwrapper)
795 del ip.user_ns['Integer']
795 del ip.user_ns['Integer']
796
796
797 def test_run_cell(self):
797 def test_run_cell(self):
798 ip.run_cell("n = 2")
798 ip.run_cell("n = 2")
799 self.assertEqual(self.calls, [(2,)])
799 self.assertEqual(self.calls, [(2,)])
800
800
801 # This shouldn't throw an error
801 # This shouldn't throw an error
802 ip.run_cell("o = 2.0")
802 ip.run_cell("o = 2.0")
803 self.assertEqual(ip.user_ns['o'], 2.0)
803 self.assertEqual(ip.user_ns['o'], 2.0)
804
804
805 def test_run_cell_non_int(self):
805 def test_run_cell_non_int(self):
806 ip.run_cell("n = 'a'")
806 ip.run_cell("n = 'a'")
807 assert self.calls == []
807 assert self.calls == []
808
808
809 def test_timeit(self):
809 def test_timeit(self):
810 called = set()
810 called = set()
811 def f(x):
811 def f(x):
812 called.add(x)
812 called.add(x)
813 ip.push({'f':f})
813 ip.push({'f':f})
814
814
815 with tt.AssertPrints("std. dev. of"):
815 with tt.AssertPrints("std. dev. of"):
816 ip.run_line_magic("timeit", "-n1 f(1)")
816 ip.run_line_magic("timeit", "-n1 f(1)")
817 self.assertEqual(called, {(1,)})
817 self.assertEqual(called, {(1,)})
818 called.clear()
818 called.clear()
819
819
820 with tt.AssertPrints("std. dev. of"):
820 with tt.AssertPrints("std. dev. of"):
821 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
821 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
822 self.assertEqual(called, {(2,), (3,)})
822 self.assertEqual(called, {(2,), (3,)})
823
823
824 class ErrorTransformer(ast.NodeTransformer):
824 class ErrorTransformer(ast.NodeTransformer):
825 """Throws an error when it sees a number."""
825 """Throws an error when it sees a number."""
826
826
827 def visit_Constant(self, node):
827 def visit_Constant(self, node):
828 if isinstance(node.value, int):
828 if isinstance(node.value, int):
829 raise ValueError("test")
829 raise ValueError("test")
830 return node
830 return node
831
831
832
832
833 class TestAstTransformError(unittest.TestCase):
833 class TestAstTransformError(unittest.TestCase):
834 def test_unregistering(self):
834 def test_unregistering(self):
835 err_transformer = ErrorTransformer()
835 err_transformer = ErrorTransformer()
836 ip.ast_transformers.append(err_transformer)
836 ip.ast_transformers.append(err_transformer)
837
837
838 with self.assertWarnsRegex(UserWarning, "It will be unregistered"):
838 with self.assertWarnsRegex(UserWarning, "It will be unregistered"):
839 ip.run_cell("1 + 2")
839 ip.run_cell("1 + 2")
840
840
841 # This should have been removed.
841 # This should have been removed.
842 self.assertNotIn(err_transformer, ip.ast_transformers)
842 self.assertNotIn(err_transformer, ip.ast_transformers)
843
843
844
844
845 class StringRejector(ast.NodeTransformer):
845 class StringRejector(ast.NodeTransformer):
846 """Throws an InputRejected when it sees a string literal.
846 """Throws an InputRejected when it sees a string literal.
847
847
848 Used to verify that NodeTransformers can signal that a piece of code should
848 Used to verify that NodeTransformers can signal that a piece of code should
849 not be executed by throwing an InputRejected.
849 not be executed by throwing an InputRejected.
850 """
850 """
851
851
852 # 3.8 only
852 # 3.8 only
853 def visit_Constant(self, node):
853 def visit_Constant(self, node):
854 if isinstance(node.value, str):
854 if isinstance(node.value, str):
855 raise InputRejected("test")
855 raise InputRejected("test")
856 return node
856 return node
857
857
858
858
859 class TestAstTransformInputRejection(unittest.TestCase):
859 class TestAstTransformInputRejection(unittest.TestCase):
860
860
861 def setUp(self):
861 def setUp(self):
862 self.transformer = StringRejector()
862 self.transformer = StringRejector()
863 ip.ast_transformers.append(self.transformer)
863 ip.ast_transformers.append(self.transformer)
864
864
865 def tearDown(self):
865 def tearDown(self):
866 ip.ast_transformers.remove(self.transformer)
866 ip.ast_transformers.remove(self.transformer)
867
867
868 def test_input_rejection(self):
868 def test_input_rejection(self):
869 """Check that NodeTransformers can reject input."""
869 """Check that NodeTransformers can reject input."""
870
870
871 expect_exception_tb = tt.AssertPrints("InputRejected: test")
871 expect_exception_tb = tt.AssertPrints("InputRejected: test")
872 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
872 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
873
873
874 # Run the same check twice to verify that the transformer is not
874 # Run the same check twice to verify that the transformer is not
875 # disabled after raising.
875 # disabled after raising.
876 with expect_exception_tb, expect_no_cell_output:
876 with expect_exception_tb, expect_no_cell_output:
877 ip.run_cell("'unsafe'")
877 ip.run_cell("'unsafe'")
878
878
879 with expect_exception_tb, expect_no_cell_output:
879 with expect_exception_tb, expect_no_cell_output:
880 res = ip.run_cell("'unsafe'")
880 res = ip.run_cell("'unsafe'")
881
881
882 self.assertIsInstance(res.error_before_exec, InputRejected)
882 self.assertIsInstance(res.error_before_exec, InputRejected)
883
883
884 def test__IPYTHON__():
884 def test__IPYTHON__():
885 # This shouldn't raise a NameError, that's all
885 # This shouldn't raise a NameError, that's all
886 __IPYTHON__
886 __IPYTHON__
887
887
888
888
889 class DummyRepr(object):
889 class DummyRepr(object):
890 def __repr__(self):
890 def __repr__(self):
891 return "DummyRepr"
891 return "DummyRepr"
892
892
893 def _repr_html_(self):
893 def _repr_html_(self):
894 return "<b>dummy</b>"
894 return "<b>dummy</b>"
895
895
896 def _repr_javascript_(self):
896 def _repr_javascript_(self):
897 return "console.log('hi');", {'key': 'value'}
897 return "console.log('hi');", {'key': 'value'}
898
898
899
899
900 def test_user_variables():
900 def test_user_variables():
901 # enable all formatters
901 # enable all formatters
902 ip.display_formatter.active_types = ip.display_formatter.format_types
902 ip.display_formatter.active_types = ip.display_formatter.format_types
903
903
904 ip.user_ns['dummy'] = d = DummyRepr()
904 ip.user_ns['dummy'] = d = DummyRepr()
905 keys = {'dummy', 'doesnotexist'}
905 keys = {'dummy', 'doesnotexist'}
906 r = ip.user_expressions({ key:key for key in keys})
906 r = ip.user_expressions({ key:key for key in keys})
907
907
908 assert keys == set(r.keys())
908 assert keys == set(r.keys())
909 dummy = r["dummy"]
909 dummy = r["dummy"]
910 assert {"status", "data", "metadata"} == set(dummy.keys())
910 assert {"status", "data", "metadata"} == set(dummy.keys())
911 assert dummy["status"] == "ok"
911 assert dummy["status"] == "ok"
912 data = dummy["data"]
912 data = dummy["data"]
913 metadata = dummy["metadata"]
913 metadata = dummy["metadata"]
914 assert data.get("text/html") == d._repr_html_()
914 assert data.get("text/html") == d._repr_html_()
915 js, jsmd = d._repr_javascript_()
915 js, jsmd = d._repr_javascript_()
916 assert data.get("application/javascript") == js
916 assert data.get("application/javascript") == js
917 assert metadata.get("application/javascript") == jsmd
917 assert metadata.get("application/javascript") == jsmd
918
918
919 dne = r["doesnotexist"]
919 dne = r["doesnotexist"]
920 assert dne["status"] == "error"
920 assert dne["status"] == "error"
921 assert dne["ename"] == "NameError"
921 assert dne["ename"] == "NameError"
922
922
923 # back to text only
923 # back to text only
924 ip.display_formatter.active_types = ['text/plain']
924 ip.display_formatter.active_types = ['text/plain']
925
925
926 def test_user_expression():
926 def test_user_expression():
927 # enable all formatters
927 # enable all formatters
928 ip.display_formatter.active_types = ip.display_formatter.format_types
928 ip.display_formatter.active_types = ip.display_formatter.format_types
929 query = {
929 query = {
930 'a' : '1 + 2',
930 'a' : '1 + 2',
931 'b' : '1/0',
931 'b' : '1/0',
932 }
932 }
933 r = ip.user_expressions(query)
933 r = ip.user_expressions(query)
934 import pprint
934 import pprint
935 pprint.pprint(r)
935 pprint.pprint(r)
936 assert set(r.keys()) == set(query.keys())
936 assert set(r.keys()) == set(query.keys())
937 a = r["a"]
937 a = r["a"]
938 assert {"status", "data", "metadata"} == set(a.keys())
938 assert {"status", "data", "metadata"} == set(a.keys())
939 assert a["status"] == "ok"
939 assert a["status"] == "ok"
940 data = a["data"]
940 data = a["data"]
941 metadata = a["metadata"]
941 metadata = a["metadata"]
942 assert data.get("text/plain") == "3"
942 assert data.get("text/plain") == "3"
943
943
944 b = r["b"]
944 b = r["b"]
945 assert b["status"] == "error"
945 assert b["status"] == "error"
946 assert b["ename"] == "ZeroDivisionError"
946 assert b["ename"] == "ZeroDivisionError"
947
947
948 # back to text only
948 # back to text only
949 ip.display_formatter.active_types = ['text/plain']
949 ip.display_formatter.active_types = ['text/plain']
950
950
951
951
952 class TestSyntaxErrorTransformer(unittest.TestCase):
952 class TestSyntaxErrorTransformer(unittest.TestCase):
953 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
953 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
954
954
955 @staticmethod
955 @staticmethod
956 def transformer(lines):
956 def transformer(lines):
957 for line in lines:
957 for line in lines:
958 pos = line.find('syntaxerror')
958 pos = line.find('syntaxerror')
959 if pos >= 0:
959 if pos >= 0:
960 e = SyntaxError('input contains "syntaxerror"')
960 e = SyntaxError('input contains "syntaxerror"')
961 e.text = line
961 e.text = line
962 e.offset = pos + 1
962 e.offset = pos + 1
963 raise e
963 raise e
964 return lines
964 return lines
965
965
966 def setUp(self):
966 def setUp(self):
967 ip.input_transformers_post.append(self.transformer)
967 ip.input_transformers_post.append(self.transformer)
968
968
969 def tearDown(self):
969 def tearDown(self):
970 ip.input_transformers_post.remove(self.transformer)
970 ip.input_transformers_post.remove(self.transformer)
971
971
972 def test_syntaxerror_input_transformer(self):
972 def test_syntaxerror_input_transformer(self):
973 with tt.AssertPrints('1234'):
973 with tt.AssertPrints('1234'):
974 ip.run_cell('1234')
974 ip.run_cell('1234')
975 with tt.AssertPrints('SyntaxError: invalid syntax'):
975 with tt.AssertPrints('SyntaxError: invalid syntax'):
976 ip.run_cell('1 2 3') # plain python syntax error
976 ip.run_cell('1 2 3') # plain python syntax error
977 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
977 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
978 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
978 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
979 with tt.AssertPrints('3456'):
979 with tt.AssertPrints('3456'):
980 ip.run_cell('3456')
980 ip.run_cell('3456')
981
981
982
982
983 class TestWarningSuppression(unittest.TestCase):
983 class TestWarningSuppression(unittest.TestCase):
984 def test_warning_suppression(self):
984 def test_warning_suppression(self):
985 ip.run_cell("import warnings")
985 ip.run_cell("import warnings")
986 try:
986 try:
987 with self.assertWarnsRegex(UserWarning, "asdf"):
987 with self.assertWarnsRegex(UserWarning, "asdf"):
988 ip.run_cell("warnings.warn('asdf')")
988 ip.run_cell("warnings.warn('asdf')")
989 # Here's the real test -- if we run that again, we should get the
989 # Here's the real test -- if we run that again, we should get the
990 # warning again. Traditionally, each warning was only issued once per
990 # warning again. Traditionally, each warning was only issued once per
991 # IPython session (approximately), even if the user typed in new and
991 # IPython session (approximately), even if the user typed in new and
992 # different code that should have also triggered the warning, leading
992 # different code that should have also triggered the warning, leading
993 # to much confusion.
993 # to much confusion.
994 with self.assertWarnsRegex(UserWarning, "asdf"):
994 with self.assertWarnsRegex(UserWarning, "asdf"):
995 ip.run_cell("warnings.warn('asdf')")
995 ip.run_cell("warnings.warn('asdf')")
996 finally:
996 finally:
997 ip.run_cell("del warnings")
997 ip.run_cell("del warnings")
998
998
999
999
1000 def test_deprecation_warning(self):
1000 def test_deprecation_warning(self):
1001 ip.run_cell("""
1001 ip.run_cell("""
1002 import warnings
1002 import warnings
1003 def wrn():
1003 def wrn():
1004 warnings.warn(
1004 warnings.warn(
1005 "I AM A WARNING",
1005 "I AM A WARNING",
1006 DeprecationWarning
1006 DeprecationWarning
1007 )
1007 )
1008 """)
1008 """)
1009 try:
1009 try:
1010 with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"):
1010 with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"):
1011 ip.run_cell("wrn()")
1011 ip.run_cell("wrn()")
1012 finally:
1012 finally:
1013 ip.run_cell("del warnings")
1013 ip.run_cell("del warnings")
1014 ip.run_cell("del wrn")
1014 ip.run_cell("del wrn")
1015
1015
1016
1016
1017 class TestImportNoDeprecate(tt.TempFileMixin):
1017 class TestImportNoDeprecate(tt.TempFileMixin):
1018
1018
1019 def setUp(self):
1019 def setUp(self):
1020 """Make a valid python temp file."""
1020 """Make a valid python temp file."""
1021 self.mktmp("""
1021 self.mktmp("""
1022 import warnings
1022 import warnings
1023 def wrn():
1023 def wrn():
1024 warnings.warn(
1024 warnings.warn(
1025 "I AM A WARNING",
1025 "I AM A WARNING",
1026 DeprecationWarning
1026 DeprecationWarning
1027 )
1027 )
1028 """)
1028 """)
1029 super().setUp()
1029 super().setUp()
1030
1030
1031 def test_no_dep(self):
1031 def test_no_dep(self):
1032 """
1032 """
1033 No deprecation warning should be raised from imported functions
1033 No deprecation warning should be raised from imported functions
1034 """
1034 """
1035 ip.run_cell("from {} import wrn".format(self.fname))
1035 ip.run_cell("from {} import wrn".format(self.fname))
1036
1036
1037 with tt.AssertNotPrints("I AM A WARNING"):
1037 with tt.AssertNotPrints("I AM A WARNING"):
1038 ip.run_cell("wrn()")
1038 ip.run_cell("wrn()")
1039 ip.run_cell("del wrn")
1039 ip.run_cell("del wrn")
1040
1040
1041
1041
1042 def test_custom_exc_count():
1042 def test_custom_exc_count():
1043 hook = mock.Mock(return_value=None)
1043 hook = mock.Mock(return_value=None)
1044 ip.set_custom_exc((SyntaxError,), hook)
1044 ip.set_custom_exc((SyntaxError,), hook)
1045 before = ip.execution_count
1045 before = ip.execution_count
1046 ip.run_cell("def foo()", store_history=True)
1046 ip.run_cell("def foo()", store_history=True)
1047 # restore default excepthook
1047 # restore default excepthook
1048 ip.set_custom_exc((), None)
1048 ip.set_custom_exc((), None)
1049 assert hook.call_count == 1
1049 assert hook.call_count == 1
1050 assert ip.execution_count == before + 1
1050 assert ip.execution_count == before + 1
1051
1051
1052
1052
1053 def test_run_cell_async():
1053 def test_run_cell_async():
1054 ip.run_cell("import asyncio")
1054 ip.run_cell("import asyncio")
1055 coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5")
1055 coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5")
1056 assert asyncio.iscoroutine(coro)
1056 assert asyncio.iscoroutine(coro)
1057 loop = asyncio.new_event_loop()
1057 loop = asyncio.new_event_loop()
1058 result = loop.run_until_complete(coro)
1058 result = loop.run_until_complete(coro)
1059 assert isinstance(result, interactiveshell.ExecutionResult)
1059 assert isinstance(result, interactiveshell.ExecutionResult)
1060 assert result.result == 5
1060 assert result.result == 5
1061
1061
1062
1062
1063 def test_run_cell_await():
1063 def test_run_cell_await():
1064 ip.run_cell("import asyncio")
1064 ip.run_cell("import asyncio")
1065 result = ip.run_cell("await asyncio.sleep(0.01); 10")
1065 result = ip.run_cell("await asyncio.sleep(0.01); 10")
1066 assert ip.user_ns["_"] == 10
1066 assert ip.user_ns["_"] == 10
1067
1067
1068
1068
1069 def test_run_cell_asyncio_run():
1069 def test_run_cell_asyncio_run():
1070 ip.run_cell("import asyncio")
1070 ip.run_cell("import asyncio")
1071 result = ip.run_cell("await asyncio.sleep(0.01); 1")
1071 result = ip.run_cell("await asyncio.sleep(0.01); 1")
1072 assert ip.user_ns["_"] == 1
1072 assert ip.user_ns["_"] == 1
1073 result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2")
1073 result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2")
1074 assert ip.user_ns["_"] == 2
1074 assert ip.user_ns["_"] == 2
1075 result = ip.run_cell("await asyncio.sleep(0.01); 3")
1075 result = ip.run_cell("await asyncio.sleep(0.01); 3")
1076 assert ip.user_ns["_"] == 3
1076 assert ip.user_ns["_"] == 3
1077
1077
1078
1078
1079 def test_should_run_async():
1079 def test_should_run_async():
1080 assert not ip.should_run_async("a = 5")
1080 assert not ip.should_run_async("a = 5")
1081 assert ip.should_run_async("await x")
1081 assert ip.should_run_async("await x")
1082 assert ip.should_run_async("import asyncio; await asyncio.sleep(1)")
1082 assert ip.should_run_async("import asyncio; await asyncio.sleep(1)")
1083
1083
1084
1084
1085 def test_set_custom_completer():
1085 def test_set_custom_completer():
1086 num_completers = len(ip.Completer.matchers)
1086 num_completers = len(ip.Completer.matchers)
1087
1087
1088 def foo(*args, **kwargs):
1088 def foo(*args, **kwargs):
1089 return "I'm a completer!"
1089 return "I'm a completer!"
1090
1090
1091 ip.set_custom_completer(foo, 0)
1091 ip.set_custom_completer(foo, 0)
1092
1092
1093 # check that we've really added a new completer
1093 # check that we've really added a new completer
1094 assert len(ip.Completer.matchers) == num_completers + 1
1094 assert len(ip.Completer.matchers) == num_completers + 1
1095
1095
1096 # check that the first completer is the function we defined
1096 # check that the first completer is the function we defined
1097 assert ip.Completer.matchers[0]() == "I'm a completer!"
1097 assert ip.Completer.matchers[0]() == "I'm a completer!"
1098
1098
1099 # clean up
1099 # clean up
1100 ip.Completer.custom_matchers.pop()
1100 ip.Completer.custom_matchers.pop()
@@ -1,249 +1,250 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
7 # third party
8 import pytest
8 import pytest
9
9
10 # our own packages
10 # our own packages
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Test functions
13 # Test functions
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 def test_reset():
16 def test_reset():
17 """reset must clear most namespaces."""
17 """reset must clear most namespaces."""
18
18
19 # Check that reset runs without error
19 # Check that reset runs without error
20 ip.reset()
20 ip.reset()
21
21
22 # Once we've reset it (to clear of any junk that might have been there from
22 # 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
23 # other tests, we can count how many variables are in the user's namespace
24 nvars_user_ns = len(ip.user_ns)
24 nvars_user_ns = len(ip.user_ns)
25 nvars_hidden = len(ip.user_ns_hidden)
25 nvars_hidden = len(ip.user_ns_hidden)
26
26
27 # Now add a few variables to user_ns, and check that reset clears them
27 # Now add a few variables to user_ns, and check that reset clears them
28 ip.user_ns['x'] = 1
28 ip.user_ns['x'] = 1
29 ip.user_ns['y'] = 1
29 ip.user_ns['y'] = 1
30 ip.reset()
30 ip.reset()
31
31
32 # Finally, check that all namespaces have only as many variables as we
32 # Finally, check that all namespaces have only as many variables as we
33 # expect to find in them:
33 # expect to find in them:
34 assert len(ip.user_ns) == nvars_user_ns
34 assert len(ip.user_ns) == nvars_user_ns
35 assert len(ip.user_ns_hidden) == nvars_hidden
35 assert len(ip.user_ns_hidden) == nvars_hidden
36
36
37
37
38 # Tests for reporting of exceptions in various modes, handling of SystemExit,
38 # 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.
39 # and %tb functionality. This is really a mix of testing ultraTB and interactiveshell.
40
40
41 def doctest_tb_plain():
41 def doctest_tb_plain():
42 """
42 """
43 In [18]: xmode plain
43 In [18]: xmode plain
44 Exception reporting mode: Plain
44 Exception reporting mode: Plain
45
45
46 In [19]: run simpleerr.py
46 In [19]: run simpleerr.py
47 Traceback (most recent call last):
47 Traceback (most recent call last):
48 File ...:... in <module>
48 File ...:... in <module>
49 bar(mode)
49 bar(mode)
50 File ...:... in bar
50 File ...:... in bar
51 div0()
51 div0()
52 File ...:... in div0
52 File ...:... in div0
53 x/y
53 x/y
54 ZeroDivisionError: ...
54 ZeroDivisionError: ...
55 """
55 """
56
56
57
57
58 def doctest_tb_context():
58 def doctest_tb_context():
59 """
59 """
60 In [3]: xmode context
60 In [3]: xmode context
61 Exception reporting mode: Context
61 Exception reporting mode: Context
62
62
63 In [4]: run simpleerr.py
63 In [4]: run simpleerr.py
64 ---------------------------------------------------------------------------
64 ---------------------------------------------------------------------------
65 ZeroDivisionError Traceback (most recent call last)
65 ZeroDivisionError Traceback (most recent call last)
66 <BLANKLINE>
66 <BLANKLINE>
67 ... in <module>
67 ... in <module>
68 30 except IndexError:
68 30 except IndexError:
69 31 mode = 'div'
69 31 mode = 'div'
70 ---> 33 bar(mode)
70 ---> 33 bar(mode)
71 <BLANKLINE>
71 <BLANKLINE>
72 ... in bar(mode)
72 ... in bar(mode)
73 15 "bar"
73 15 "bar"
74 16 if mode=='div':
74 16 if mode=='div':
75 ---> 17 div0()
75 ---> 17 div0()
76 18 elif mode=='exit':
76 18 elif mode=='exit':
77 19 try:
77 19 try:
78 <BLANKLINE>
78 <BLANKLINE>
79 ... in div0()
79 ... in div0()
80 6 x = 1
80 6 x = 1
81 7 y = 0
81 7 y = 0
82 ----> 8 x/y
82 ----> 8 x/y
83 <BLANKLINE>
83 <BLANKLINE>
84 ZeroDivisionError: ..."""
84 ZeroDivisionError: ..."""
85
85
86
86
87 def doctest_tb_verbose():
87 def doctest_tb_verbose():
88 """
88 """
89 In [5]: xmode verbose
89 In [5]: xmode verbose
90 Exception reporting mode: Verbose
90 Exception reporting mode: Verbose
91
91
92 In [6]: run simpleerr.py
92 In [6]: run simpleerr.py
93 ---------------------------------------------------------------------------
93 ---------------------------------------------------------------------------
94 ZeroDivisionError Traceback (most recent call last)
94 ZeroDivisionError Traceback (most recent call last)
95 <BLANKLINE>
95 <BLANKLINE>
96 ... in <module>
96 ... in <module>
97 30 except IndexError:
97 30 except IndexError:
98 31 mode = 'div'
98 31 mode = 'div'
99 ---> 33 bar(mode)
99 ---> 33 bar(mode)
100 mode = 'div'
100 mode = 'div'
101 <BLANKLINE>
101 <BLANKLINE>
102 ... in bar(mode='div')
102 ... in bar(mode='div')
103 15 "bar"
103 15 "bar"
104 16 if mode=='div':
104 16 if mode=='div':
105 ---> 17 div0()
105 ---> 17 div0()
106 18 elif mode=='exit':
106 18 elif mode=='exit':
107 19 try:
107 19 try:
108 <BLANKLINE>
108 <BLANKLINE>
109 ... in div0()
109 ... in div0()
110 6 x = 1
110 6 x = 1
111 7 y = 0
111 7 y = 0
112 ----> 8 x/y
112 ----> 8 x/y
113 x = 1
113 x = 1
114 y = 0
114 y = 0
115 <BLANKLINE>
115 <BLANKLINE>
116 ZeroDivisionError: ...
116 ZeroDivisionError: ...
117 """
117 """
118
118
119
119
120 def doctest_tb_sysexit():
120 def doctest_tb_sysexit():
121 """
121 """
122 In [17]: %xmode plain
122 In [17]: %xmode plain
123 Exception reporting mode: Plain
123 Exception reporting mode: Plain
124
124
125 In [18]: %run simpleerr.py exit
125 In [18]: %run simpleerr.py exit
126 An exception has occurred, use %tb to see the full traceback.
126 An exception has occurred, use %tb to see the full traceback.
127 SystemExit: (1, 'Mode = exit')
127 SystemExit: (1, 'Mode = exit')
128
128
129 In [19]: %run simpleerr.py exit 2
129 In [19]: %run simpleerr.py exit 2
130 An exception has occurred, use %tb to see the full traceback.
130 An exception has occurred, use %tb to see the full traceback.
131 SystemExit: (2, 'Mode = exit')
131 SystemExit: (2, 'Mode = exit')
132
132
133 In [20]: %tb
133 In [20]: %tb
134 Traceback (most recent call last):
134 Traceback (most recent call last):
135 File ...:... in execfile
135 File ...:... in execfile
136 exec(compiler(f.read(), fname, "exec"), glob, loc)
136 exec(compiler(f.read(), fname, "exec"), glob, loc)
137 File ...:... in <module>
137 File ...:... in <module>
138 bar(mode)
138 bar(mode)
139 File ...:... in bar
139 File ...:... in bar
140 sysexit(stat, mode)
140 sysexit(stat, mode)
141 File ...:... in sysexit
141 File ...:... in sysexit
142 raise SystemExit(stat, f"Mode = {mode}")
142 raise SystemExit(stat, f"Mode = {mode}")
143 SystemExit: (2, 'Mode = exit')
143 SystemExit: (2, 'Mode = exit')
144
144
145 In [21]: %xmode context
145 In [21]: %xmode context
146 Exception reporting mode: Context
146 Exception reporting mode: Context
147
147
148 In [22]: %tb
148 In [22]: %tb
149 ---------------------------------------------------------------------------
149 ---------------------------------------------------------------------------
150 SystemExit Traceback (most recent call last)
150 SystemExit Traceback (most recent call last)
151 File ..., in execfile(fname, glob, loc, compiler)
151 File ..., in execfile(fname, glob, loc, compiler)
152 ... with open(fname, "rb") as f:
152 ... with open(fname, "rb") as f:
153 ... compiler = compiler or compile
153 ... compiler = compiler or compile
154 ---> ... exec(compiler(f.read(), fname, "exec"), glob, loc)
154 ---> ... exec(compiler(f.read(), fname, "exec"), glob, loc)
155 ...<module>
155 ...<module>
156 30 except IndexError:
156 30 except IndexError:
157 31 mode = 'div'
157 31 mode = 'div'
158 ---> 33 bar(mode)
158 ---> 33 bar(mode)
159 <BLANKLINE>
159 <BLANKLINE>
160 ...bar(mode)
160 ...bar(mode)
161 21 except:
161 21 except:
162 22 stat = 1
162 22 stat = 1
163 ---> 23 sysexit(stat, mode)
163 ---> 23 sysexit(stat, mode)
164 24 else:
164 24 else:
165 25 raise ValueError('Unknown mode')
165 25 raise ValueError('Unknown mode')
166 <BLANKLINE>
166 <BLANKLINE>
167 ...sysexit(stat, mode)
167 ...sysexit(stat, mode)
168 10 def sysexit(stat, mode):
168 10 def sysexit(stat, mode):
169 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
169 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
170 <BLANKLINE>
170 <BLANKLINE>
171 SystemExit: (2, 'Mode = exit')
171 SystemExit: (2, 'Mode = exit')
172 """
172 """
173
173
174
174
175 def doctest_tb_sysexit_verbose():
175 def doctest_tb_sysexit_verbose():
176 """
176 """
177 In [18]: %run simpleerr.py exit
177 In [18]: %run simpleerr.py exit
178 An exception has occurred, use %tb to see the full traceback.
178 An exception has occurred, use %tb to see the full traceback.
179 SystemExit: (1, 'Mode = exit')
179 SystemExit: (1, 'Mode = exit')
180
180
181 In [19]: %run simpleerr.py exit 2
181 In [19]: %run simpleerr.py exit 2
182 An exception has occurred, use %tb to see the full traceback.
182 An exception has occurred, use %tb to see the full traceback.
183 SystemExit: (2, 'Mode = exit')
183 SystemExit: (2, 'Mode = exit')
184
184
185 In [23]: %xmode verbose
185 In [23]: %xmode verbose
186 Exception reporting mode: Verbose
186 Exception reporting mode: Verbose
187
187
188 In [24]: %tb
188 In [24]: %tb
189 ---------------------------------------------------------------------------
189 ---------------------------------------------------------------------------
190 SystemExit Traceback (most recent call last)
190 SystemExit Traceback (most recent call last)
191 <BLANKLINE>
191 <BLANKLINE>
192 ... in <module>
192 ... in <module>
193 30 except IndexError:
193 30 except IndexError:
194 31 mode = 'div'
194 31 mode = 'div'
195 ---> 33 bar(mode)
195 ---> 33 bar(mode)
196 mode = 'exit'
196 mode = 'exit'
197 <BLANKLINE>
197 <BLANKLINE>
198 ... in bar(mode='exit')
198 ... in bar(mode='exit')
199 ... except:
199 ... except:
200 ... stat = 1
200 ... stat = 1
201 ---> ... sysexit(stat, mode)
201 ---> ... sysexit(stat, mode)
202 mode = 'exit'
202 mode = 'exit'
203 stat = 2
203 stat = 2
204 ... else:
204 ... else:
205 ... raise ValueError('Unknown mode')
205 ... raise ValueError('Unknown mode')
206 <BLANKLINE>
206 <BLANKLINE>
207 ... in sysexit(stat=2, mode='exit')
207 ... in sysexit(stat=2, mode='exit')
208 10 def sysexit(stat, mode):
208 10 def sysexit(stat, mode):
209 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
209 ---> 11 raise SystemExit(stat, f"Mode = {mode}")
210 stat = 2
210 stat = 2
211 <BLANKLINE>
211 <BLANKLINE>
212 SystemExit: (2, 'Mode = exit')
212 SystemExit: (2, 'Mode = exit')
213 """
213 """
214
214
215
215
216 def test_run_cell():
216 def test_run_cell():
217 import textwrap
217 import textwrap
218
218
219 ip.run_cell("a = 10\na+=1")
219 ip.run_cell("a = 10\na+=1")
220 ip.run_cell("assert a == 11\nassert 1")
220 ip.run_cell("assert a == 11\nassert 1")
221
221
222 assert ip.user_ns["a"] == 11
222 assert ip.user_ns["a"] == 11
223 complex = textwrap.dedent(
223 complex = textwrap.dedent(
224 """
224 """
225 if 1:
225 if 1:
226 print "hello"
226 print "hello"
227 if 1:
227 if 1:
228 print "world"
228 print "world"
229
229
230 if 2:
230 if 2:
231 print "foo"
231 print "foo"
232
232
233 if 3:
233 if 3:
234 print "bar"
234 print "bar"
235
235
236 if 4:
236 if 4:
237 print "bar"
237 print "bar"
238
238
239 """)
239 """
240 )
240 # Simply verifies that this kind of input is run
241 # Simply verifies that this kind of input is run
241 ip.run_cell(complex)
242 ip.run_cell(complex)
242
243
243
244
244 def test_db():
245 def test_db():
245 """Test the internal database used for variable persistence."""
246 """Test the internal database used for variable persistence."""
246 ip.db["__unittest_"] = 12
247 ip.db["__unittest_"] = 12
247 assert ip.db["__unittest_"] == 12
248 assert ip.db["__unittest_"] == 12
248 del ip.db["__unittest_"]
249 del ip.db["__unittest_"]
249 assert "__unittest_" not in ip.db
250 assert "__unittest_" not in ip.db
@@ -1,1450 +1,1452 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for various magic functions."""
2 """Tests for various magic functions."""
3
3
4 import asyncio
4 import asyncio
5 import gc
5 import gc
6 import io
6 import io
7 import os
7 import os
8 import re
8 import re
9 import shlex
9 import shlex
10 import sys
10 import sys
11 import warnings
11 import warnings
12 from importlib import invalidate_caches
12 from importlib import invalidate_caches
13 from io import StringIO
13 from io import StringIO
14 from pathlib import Path
14 from pathlib import Path
15 from textwrap import dedent
15 from textwrap import dedent
16 from unittest import TestCase, mock
16 from unittest import TestCase, mock
17
17
18 import pytest
18 import pytest
19
19
20 from IPython import get_ipython
20 from IPython import get_ipython
21 from IPython.core import magic
21 from IPython.core import magic
22 from IPython.core.error import UsageError
22 from IPython.core.error import UsageError
23 from IPython.core.magic import (
23 from IPython.core.magic import (
24 Magics,
24 Magics,
25 cell_magic,
25 cell_magic,
26 line_magic,
26 line_magic,
27 magics_class,
27 magics_class,
28 register_cell_magic,
28 register_cell_magic,
29 register_line_magic,
29 register_line_magic,
30 )
30 )
31 from IPython.core.magics import code, execution, logging, osm, script
31 from IPython.core.magics import code, execution, logging, osm, script
32 from IPython.testing import decorators as dec
32 from IPython.testing import decorators as dec
33 from IPython.testing import tools as tt
33 from IPython.testing import tools as tt
34 from IPython.utils.io import capture_output
34 from IPython.utils.io import capture_output
35 from IPython.utils.process import find_cmd
35 from IPython.utils.process import find_cmd
36 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
36 from IPython.utils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory
37 from IPython.utils.syspathcontext import prepended_to_syspath
37 from IPython.utils.syspathcontext import prepended_to_syspath
38
38
39 from .test_debugger import PdbTestInput
39 from .test_debugger import PdbTestInput
40
40
41 from tempfile import NamedTemporaryFile
41 from tempfile import NamedTemporaryFile
42
42
43 @magic.magics_class
43 @magic.magics_class
44 class DummyMagics(magic.Magics): pass
44 class DummyMagics(magic.Magics): pass
45
45
46 def test_extract_code_ranges():
46 def test_extract_code_ranges():
47 instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :"
47 instr = "1 3 5-6 7-9 10:15 17: :10 10- -13 :"
48 expected = [
48 expected = [
49 (0, 1),
49 (0, 1),
50 (2, 3),
50 (2, 3),
51 (4, 6),
51 (4, 6),
52 (6, 9),
52 (6, 9),
53 (9, 14),
53 (9, 14),
54 (16, None),
54 (16, None),
55 (None, 9),
55 (None, 9),
56 (9, None),
56 (9, None),
57 (None, 13),
57 (None, 13),
58 (None, None),
58 (None, None),
59 ]
59 ]
60 actual = list(code.extract_code_ranges(instr))
60 actual = list(code.extract_code_ranges(instr))
61 assert actual == expected
61 assert actual == expected
62
62
63 def test_extract_symbols():
63 def test_extract_symbols():
64 source = """import foo\na = 10\ndef b():\n return 42\n\n\nclass A: pass\n\n\n"""
64 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"]
65 symbols_args = ["a", "b", "A", "A,b", "A,a", "z"]
66 expected = [([], ['a']),
66 expected = [([], ['a']),
67 (["def b():\n return 42\n"], []),
67 (["def b():\n return 42\n"], []),
68 (["class A: pass\n"], []),
68 (["class A: pass\n"], []),
69 (["class A: pass\n", "def b():\n return 42\n"], []),
69 (["class A: pass\n", "def b():\n return 42\n"], []),
70 (["class A: pass\n"], ['a']),
70 (["class A: pass\n"], ['a']),
71 ([], ['z'])]
71 ([], ['z'])]
72 for symbols, exp in zip(symbols_args, expected):
72 for symbols, exp in zip(symbols_args, expected):
73 assert code.extract_symbols(source, symbols) == exp
73 assert code.extract_symbols(source, symbols) == exp
74
74
75
75
76 def test_extract_symbols_raises_exception_with_non_python_code():
76 def test_extract_symbols_raises_exception_with_non_python_code():
77 source = ("=begin A Ruby program :)=end\n"
77 source = ("=begin A Ruby program :)=end\n"
78 "def hello\n"
78 "def hello\n"
79 "puts 'Hello world'\n"
79 "puts 'Hello world'\n"
80 "end")
80 "end")
81 with pytest.raises(SyntaxError):
81 with pytest.raises(SyntaxError):
82 code.extract_symbols(source, "hello")
82 code.extract_symbols(source, "hello")
83
83
84
84
85 def test_magic_not_found():
85 def test_magic_not_found():
86 # magic not found raises UsageError
86 # magic not found raises UsageError
87 with pytest.raises(UsageError):
87 with pytest.raises(UsageError):
88 _ip.magic('doesntexist')
88 _ip.magic('doesntexist')
89
89
90 # ensure result isn't success when a magic isn't found
90 # ensure result isn't success when a magic isn't found
91 result = _ip.run_cell('%doesntexist')
91 result = _ip.run_cell('%doesntexist')
92 assert isinstance(result.error_in_exec, UsageError)
92 assert isinstance(result.error_in_exec, UsageError)
93
93
94
94
95 def test_cell_magic_not_found():
95 def test_cell_magic_not_found():
96 # magic not found raises UsageError
96 # magic not found raises UsageError
97 with pytest.raises(UsageError):
97 with pytest.raises(UsageError):
98 _ip.run_cell_magic('doesntexist', 'line', 'cell')
98 _ip.run_cell_magic('doesntexist', 'line', 'cell')
99
99
100 # ensure result isn't success when a magic isn't found
100 # ensure result isn't success when a magic isn't found
101 result = _ip.run_cell('%%doesntexist')
101 result = _ip.run_cell('%%doesntexist')
102 assert isinstance(result.error_in_exec, UsageError)
102 assert isinstance(result.error_in_exec, UsageError)
103
103
104
104
105 def test_magic_error_status():
105 def test_magic_error_status():
106 def fail(shell):
106 def fail(shell):
107 1/0
107 1/0
108 _ip.register_magic_function(fail)
108 _ip.register_magic_function(fail)
109 result = _ip.run_cell('%fail')
109 result = _ip.run_cell('%fail')
110 assert isinstance(result.error_in_exec, ZeroDivisionError)
110 assert isinstance(result.error_in_exec, ZeroDivisionError)
111
111
112
112
113 def test_config():
113 def test_config():
114 """ test that config magic does not raise
114 """ test that config magic does not raise
115 can happen if Configurable init is moved too early into
115 can happen if Configurable init is moved too early into
116 Magics.__init__ as then a Config object will be registered as a
116 Magics.__init__ as then a Config object will be registered as a
117 magic.
117 magic.
118 """
118 """
119 ## should not raise.
119 ## should not raise.
120 _ip.magic('config')
120 _ip.magic('config')
121
121
122 def test_config_available_configs():
122 def test_config_available_configs():
123 """ test that config magic prints available configs in unique and
123 """ test that config magic prints available configs in unique and
124 sorted order. """
124 sorted order. """
125 with capture_output() as captured:
125 with capture_output() as captured:
126 _ip.magic('config')
126 _ip.magic('config')
127
127
128 stdout = captured.stdout
128 stdout = captured.stdout
129 config_classes = stdout.strip().split('\n')[1:]
129 config_classes = stdout.strip().split('\n')[1:]
130 assert config_classes == sorted(set(config_classes))
130 assert config_classes == sorted(set(config_classes))
131
131
132 def test_config_print_class():
132 def test_config_print_class():
133 """ test that config with a classname prints the class's options. """
133 """ test that config with a classname prints the class's options. """
134 with capture_output() as captured:
134 with capture_output() as captured:
135 _ip.magic('config TerminalInteractiveShell')
135 _ip.magic('config TerminalInteractiveShell')
136
136
137 stdout = captured.stdout
137 stdout = captured.stdout
138 assert re.match(
138 assert re.match(
139 "TerminalInteractiveShell.* options", stdout.splitlines()[0]
139 "TerminalInteractiveShell.* options", stdout.splitlines()[0]
140 ), f"{stdout}\n\n1st line of stdout not like 'TerminalInteractiveShell.* options'"
140 ), f"{stdout}\n\n1st line of stdout not like 'TerminalInteractiveShell.* options'"
141
141
142
142
143 def test_rehashx():
143 def test_rehashx():
144 # clear up everything
144 # clear up everything
145 _ip.alias_manager.clear_aliases()
145 _ip.alias_manager.clear_aliases()
146 del _ip.db['syscmdlist']
146 del _ip.db['syscmdlist']
147
147
148 _ip.magic('rehashx')
148 _ip.magic('rehashx')
149 # Practically ALL ipython development systems will have more than 10 aliases
149 # Practically ALL ipython development systems will have more than 10 aliases
150
150
151 assert len(_ip.alias_manager.aliases) > 10
151 assert len(_ip.alias_manager.aliases) > 10
152 for name, cmd in _ip.alias_manager.aliases:
152 for name, cmd in _ip.alias_manager.aliases:
153 # we must strip dots from alias names
153 # we must strip dots from alias names
154 assert "." not in name
154 assert "." not in name
155
155
156 # rehashx must fill up syscmdlist
156 # rehashx must fill up syscmdlist
157 scoms = _ip.db['syscmdlist']
157 scoms = _ip.db['syscmdlist']
158 assert len(scoms) > 10
158 assert len(scoms) > 10
159
159
160
160
161 def test_magic_parse_options():
161 def test_magic_parse_options():
162 """Test that we don't mangle paths when parsing magic options."""
162 """Test that we don't mangle paths when parsing magic options."""
163 ip = get_ipython()
163 ip = get_ipython()
164 path = 'c:\\x'
164 path = 'c:\\x'
165 m = DummyMagics(ip)
165 m = DummyMagics(ip)
166 opts = m.parse_options('-f %s' % path,'f:')[0]
166 opts = m.parse_options('-f %s' % path,'f:')[0]
167 # argv splitting is os-dependent
167 # argv splitting is os-dependent
168 if os.name == 'posix':
168 if os.name == 'posix':
169 expected = 'c:x'
169 expected = 'c:x'
170 else:
170 else:
171 expected = path
171 expected = path
172 assert opts["f"] == expected
172 assert opts["f"] == expected
173
173
174
174
175 def test_magic_parse_long_options():
175 def test_magic_parse_long_options():
176 """Magic.parse_options can handle --foo=bar long options"""
176 """Magic.parse_options can handle --foo=bar long options"""
177 ip = get_ipython()
177 ip = get_ipython()
178 m = DummyMagics(ip)
178 m = DummyMagics(ip)
179 opts, _ = m.parse_options("--foo --bar=bubble", "a", "foo", "bar=")
179 opts, _ = m.parse_options("--foo --bar=bubble", "a", "foo", "bar=")
180 assert "foo" in opts
180 assert "foo" in opts
181 assert "bar" in opts
181 assert "bar" in opts
182 assert opts["bar"] == "bubble"
182 assert opts["bar"] == "bubble"
183
183
184
184
185 def doctest_hist_f():
185 def doctest_hist_f():
186 """Test %hist -f with temporary filename.
186 """Test %hist -f with temporary filename.
187
187
188 In [9]: import tempfile
188 In [9]: import tempfile
189
189
190 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
190 In [10]: tfile = tempfile.mktemp('.py','tmp-ipython-')
191
191
192 In [11]: %hist -nl -f $tfile 3
192 In [11]: %hist -nl -f $tfile 3
193
193
194 In [13]: import os; os.unlink(tfile)
194 In [13]: import os; os.unlink(tfile)
195 """
195 """
196
196
197
197
198 def doctest_hist_op():
198 def doctest_hist_op():
199 """Test %hist -op
199 """Test %hist -op
200
200
201 In [1]: class b(float):
201 In [1]: class b(float):
202 ...: pass
202 ...: pass
203 ...:
203 ...:
204
204
205 In [2]: class s(object):
205 In [2]: class s(object):
206 ...: def __str__(self):
206 ...: def __str__(self):
207 ...: return 's'
207 ...: return 's'
208 ...:
208 ...:
209
209
210 In [3]:
210 In [3]:
211
211
212 In [4]: class r(b):
212 In [4]: class r(b):
213 ...: def __repr__(self):
213 ...: def __repr__(self):
214 ...: return 'r'
214 ...: return 'r'
215 ...:
215 ...:
216
216
217 In [5]: class sr(s,r): pass
217 In [5]: class sr(s,r): pass
218 ...:
218 ...:
219
219
220 In [6]:
220 In [6]:
221
221
222 In [7]: bb=b()
222 In [7]: bb=b()
223
223
224 In [8]: ss=s()
224 In [8]: ss=s()
225
225
226 In [9]: rr=r()
226 In [9]: rr=r()
227
227
228 In [10]: ssrr=sr()
228 In [10]: ssrr=sr()
229
229
230 In [11]: 4.5
230 In [11]: 4.5
231 Out[11]: 4.5
231 Out[11]: 4.5
232
232
233 In [12]: str(ss)
233 In [12]: str(ss)
234 Out[12]: 's'
234 Out[12]: 's'
235
235
236 In [13]:
236 In [13]:
237
237
238 In [14]: %hist -op
238 In [14]: %hist -op
239 >>> class b:
239 >>> class b:
240 ... pass
240 ... pass
241 ...
241 ...
242 >>> class s(b):
242 >>> class s(b):
243 ... def __str__(self):
243 ... def __str__(self):
244 ... return 's'
244 ... return 's'
245 ...
245 ...
246 >>>
246 >>>
247 >>> class r(b):
247 >>> class r(b):
248 ... def __repr__(self):
248 ... def __repr__(self):
249 ... return 'r'
249 ... return 'r'
250 ...
250 ...
251 >>> class sr(s,r): pass
251 >>> class sr(s,r): pass
252 >>>
252 >>>
253 >>> bb=b()
253 >>> bb=b()
254 >>> ss=s()
254 >>> ss=s()
255 >>> rr=r()
255 >>> rr=r()
256 >>> ssrr=sr()
256 >>> ssrr=sr()
257 >>> 4.5
257 >>> 4.5
258 4.5
258 4.5
259 >>> str(ss)
259 >>> str(ss)
260 's'
260 's'
261 >>>
261 >>>
262 """
262 """
263
263
264 def test_hist_pof():
264 def test_hist_pof():
265 ip = get_ipython()
265 ip = get_ipython()
266 ip.run_cell("1+2", store_history=True)
266 ip.run_cell("1+2", store_history=True)
267 #raise Exception(ip.history_manager.session_number)
267 #raise Exception(ip.history_manager.session_number)
268 #raise Exception(list(ip.history_manager._get_range_session()))
268 #raise Exception(list(ip.history_manager._get_range_session()))
269 with TemporaryDirectory() as td:
269 with TemporaryDirectory() as td:
270 tf = os.path.join(td, 'hist.py')
270 tf = os.path.join(td, 'hist.py')
271 ip.run_line_magic('history', '-pof %s' % tf)
271 ip.run_line_magic('history', '-pof %s' % tf)
272 assert os.path.isfile(tf)
272 assert os.path.isfile(tf)
273
273
274
274
275 def test_macro():
275 def test_macro():
276 ip = get_ipython()
276 ip = get_ipython()
277 ip.history_manager.reset() # Clear any existing history.
277 ip.history_manager.reset() # Clear any existing history.
278 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
278 cmds = ["a=1", "def b():\n return a**2", "print(a,b())"]
279 for i, cmd in enumerate(cmds, start=1):
279 for i, cmd in enumerate(cmds, start=1):
280 ip.history_manager.store_inputs(i, cmd)
280 ip.history_manager.store_inputs(i, cmd)
281 ip.magic("macro test 1-3")
281 ip.magic("macro test 1-3")
282 assert ip.user_ns["test"].value == "\n".join(cmds) + "\n"
282 assert ip.user_ns["test"].value == "\n".join(cmds) + "\n"
283
283
284 # List macros
284 # List macros
285 assert "test" in ip.magic("macro")
285 assert "test" in ip.magic("macro")
286
286
287
287
288 def test_macro_run():
288 def test_macro_run():
289 """Test that we can run a multi-line macro successfully."""
289 """Test that we can run a multi-line macro successfully."""
290 ip = get_ipython()
290 ip = get_ipython()
291 ip.history_manager.reset()
291 ip.history_manager.reset()
292 cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"]
292 cmds = ["a=10", "a+=1", "print(a)", "%macro test 2-3"]
293 for cmd in cmds:
293 for cmd in cmds:
294 ip.run_cell(cmd, store_history=True)
294 ip.run_cell(cmd, store_history=True)
295 assert ip.user_ns["test"].value == "a+=1\nprint(a)\n"
295 assert ip.user_ns["test"].value == "a+=1\nprint(a)\n"
296 with tt.AssertPrints("12"):
296 with tt.AssertPrints("12"):
297 ip.run_cell("test")
297 ip.run_cell("test")
298 with tt.AssertPrints("13"):
298 with tt.AssertPrints("13"):
299 ip.run_cell("test")
299 ip.run_cell("test")
300
300
301
301
302 def test_magic_magic():
302 def test_magic_magic():
303 """Test %magic"""
303 """Test %magic"""
304 ip = get_ipython()
304 ip = get_ipython()
305 with capture_output() as captured:
305 with capture_output() as captured:
306 ip.magic("magic")
306 ip.magic("magic")
307
307
308 stdout = captured.stdout
308 stdout = captured.stdout
309 assert "%magic" in stdout
309 assert "%magic" in stdout
310 assert "IPython" in stdout
310 assert "IPython" in stdout
311 assert "Available" in stdout
311 assert "Available" in stdout
312
312
313
313
314 @dec.skipif_not_numpy
314 @dec.skipif_not_numpy
315 def test_numpy_reset_array_undec():
315 def test_numpy_reset_array_undec():
316 "Test '%reset array' functionality"
316 "Test '%reset array' functionality"
317 _ip.ex("import numpy as np")
317 _ip.ex("import numpy as np")
318 _ip.ex("a = np.empty(2)")
318 _ip.ex("a = np.empty(2)")
319 assert "a" in _ip.user_ns
319 assert "a" in _ip.user_ns
320 _ip.magic("reset -f array")
320 _ip.magic("reset -f array")
321 assert "a" not in _ip.user_ns
321 assert "a" not in _ip.user_ns
322
322
323
323
324 def test_reset_out():
324 def test_reset_out():
325 "Test '%reset out' magic"
325 "Test '%reset out' magic"
326 _ip.run_cell("parrot = 'dead'", store_history=True)
326 _ip.run_cell("parrot = 'dead'", store_history=True)
327 # test '%reset -f out', make an Out prompt
327 # test '%reset -f out', make an Out prompt
328 _ip.run_cell("parrot", store_history=True)
328 _ip.run_cell("parrot", store_history=True)
329 assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")]
329 assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")]
330 _ip.magic("reset -f out")
330 _ip.magic("reset -f out")
331 assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")]
331 assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")]
332 assert len(_ip.user_ns["Out"]) == 0
332 assert len(_ip.user_ns["Out"]) == 0
333
333
334
334
335 def test_reset_in():
335 def test_reset_in():
336 "Test '%reset in' magic"
336 "Test '%reset in' magic"
337 # test '%reset -f in'
337 # test '%reset -f in'
338 _ip.run_cell("parrot", store_history=True)
338 _ip.run_cell("parrot", store_history=True)
339 assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
339 assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
340 _ip.magic("%reset -f in")
340 _ip.magic("%reset -f in")
341 assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
341 assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
342 assert len(set(_ip.user_ns["In"])) == 1
342 assert len(set(_ip.user_ns["In"])) == 1
343
343
344
344
345 def test_reset_dhist():
345 def test_reset_dhist():
346 "Test '%reset dhist' magic"
346 "Test '%reset dhist' magic"
347 _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing
347 _ip.run_cell("tmp = [d for d in _dh]") # copy before clearing
348 _ip.magic("cd " + os.path.dirname(pytest.__file__))
348 _ip.magic("cd " + os.path.dirname(pytest.__file__))
349 _ip.magic("cd -")
349 _ip.magic("cd -")
350 assert len(_ip.user_ns["_dh"]) > 0
350 assert len(_ip.user_ns["_dh"]) > 0
351 _ip.magic("reset -f dhist")
351 _ip.magic("reset -f dhist")
352 assert len(_ip.user_ns["_dh"]) == 0
352 assert len(_ip.user_ns["_dh"]) == 0
353 _ip.run_cell("_dh = [d for d in tmp]") # restore
353 _ip.run_cell("_dh = [d for d in tmp]") # restore
354
354
355
355
356 def test_reset_in_length():
356 def test_reset_in_length():
357 "Test that '%reset in' preserves In[] length"
357 "Test that '%reset in' preserves In[] length"
358 _ip.run_cell("print 'foo'")
358 _ip.run_cell("print 'foo'")
359 _ip.run_cell("reset -f in")
359 _ip.run_cell("reset -f in")
360 assert len(_ip.user_ns["In"]) == _ip.displayhook.prompt_count + 1
360 assert len(_ip.user_ns["In"]) == _ip.displayhook.prompt_count + 1
361
361
362
362
363 class TestResetErrors(TestCase):
363 class TestResetErrors(TestCase):
364
364
365 def test_reset_redefine(self):
365 def test_reset_redefine(self):
366
366
367 @magics_class
367 @magics_class
368 class KernelMagics(Magics):
368 class KernelMagics(Magics):
369 @line_magic
369 @line_magic
370 def less(self, shell): pass
370 def less(self, shell): pass
371
371
372 _ip.register_magics(KernelMagics)
372 _ip.register_magics(KernelMagics)
373
373
374 with self.assertLogs() as cm:
374 with self.assertLogs() as cm:
375 # hack, we want to just capture logs, but assertLogs fails if not
375 # hack, we want to just capture logs, but assertLogs fails if not
376 # logs get produce.
376 # logs get produce.
377 # so log one things we ignore.
377 # so log one things we ignore.
378 import logging as log_mod
378 import logging as log_mod
379 log = log_mod.getLogger()
379 log = log_mod.getLogger()
380 log.info('Nothing')
380 log.info('Nothing')
381 # end hack.
381 # end hack.
382 _ip.run_cell("reset -f")
382 _ip.run_cell("reset -f")
383
383
384 assert len(cm.output) == 1
384 assert len(cm.output) == 1
385 for out in cm.output:
385 for out in cm.output:
386 assert "Invalid alias" not in out
386 assert "Invalid alias" not in out
387
387
388 def test_tb_syntaxerror():
388 def test_tb_syntaxerror():
389 """test %tb after a SyntaxError"""
389 """test %tb after a SyntaxError"""
390 ip = get_ipython()
390 ip = get_ipython()
391 ip.run_cell("for")
391 ip.run_cell("for")
392
392
393 # trap and validate stdout
393 # trap and validate stdout
394 save_stdout = sys.stdout
394 save_stdout = sys.stdout
395 try:
395 try:
396 sys.stdout = StringIO()
396 sys.stdout = StringIO()
397 ip.run_cell("%tb")
397 ip.run_cell("%tb")
398 out = sys.stdout.getvalue()
398 out = sys.stdout.getvalue()
399 finally:
399 finally:
400 sys.stdout = save_stdout
400 sys.stdout = save_stdout
401 # trim output, and only check the last line
401 # trim output, and only check the last line
402 last_line = out.rstrip().splitlines()[-1].strip()
402 last_line = out.rstrip().splitlines()[-1].strip()
403 assert last_line == "SyntaxError: invalid syntax"
403 assert last_line == "SyntaxError: invalid syntax"
404
404
405
405
406 def test_time():
406 def test_time():
407 ip = get_ipython()
407 ip = get_ipython()
408
408
409 with tt.AssertPrints("Wall time: "):
409 with tt.AssertPrints("Wall time: "):
410 ip.run_cell("%time None")
410 ip.run_cell("%time None")
411
411
412 ip.run_cell("def f(kmjy):\n"
412 ip.run_cell("def f(kmjy):\n"
413 " %time print (2*kmjy)")
413 " %time print (2*kmjy)")
414
414
415 with tt.AssertPrints("Wall time: "):
415 with tt.AssertPrints("Wall time: "):
416 with tt.AssertPrints("hihi", suppress=False):
416 with tt.AssertPrints("hihi", suppress=False):
417 ip.run_cell("f('hi')")
417 ip.run_cell("f('hi')")
418
418
419 def test_time_last_not_expression():
419 def test_time_last_not_expression():
420 ip.run_cell("%%time\n"
420 ip.run_cell("%%time\n"
421 "var_1 = 1\n"
421 "var_1 = 1\n"
422 "var_2 = 2\n")
422 "var_2 = 2\n")
423 assert ip.user_ns['var_1'] == 1
423 assert ip.user_ns['var_1'] == 1
424 del ip.user_ns['var_1']
424 del ip.user_ns['var_1']
425 assert ip.user_ns['var_2'] == 2
425 assert ip.user_ns['var_2'] == 2
426 del ip.user_ns['var_2']
426 del ip.user_ns['var_2']
427
427
428
428
429 @dec.skip_win32
429 @dec.skip_win32
430 def test_time2():
430 def test_time2():
431 ip = get_ipython()
431 ip = get_ipython()
432
432
433 with tt.AssertPrints("CPU times: user "):
433 with tt.AssertPrints("CPU times: user "):
434 ip.run_cell("%time None")
434 ip.run_cell("%time None")
435
435
436 def test_time3():
436 def test_time3():
437 """Erroneous magic function calls, issue gh-3334"""
437 """Erroneous magic function calls, issue gh-3334"""
438 ip = get_ipython()
438 ip = get_ipython()
439 ip.user_ns.pop('run', None)
439 ip.user_ns.pop('run', None)
440
440
441 with tt.AssertNotPrints("not found", channel='stderr'):
441 with tt.AssertNotPrints("not found", channel='stderr'):
442 ip.run_cell("%%time\n"
442 ip.run_cell("%%time\n"
443 "run = 0\n"
443 "run = 0\n"
444 "run += 1")
444 "run += 1")
445
445
446 def test_multiline_time():
446 def test_multiline_time():
447 """Make sure last statement from time return a value."""
447 """Make sure last statement from time return a value."""
448 ip = get_ipython()
448 ip = get_ipython()
449 ip.user_ns.pop('run', None)
449 ip.user_ns.pop('run', None)
450
450
451 ip.run_cell(dedent("""\
451 ip.run_cell(
452 dedent(
453 """\
452 %%time
454 %%time
453 a = "ho"
455 a = "ho"
454 b = "hey"
456 b = "hey"
455 a+b
457 a+b
456 """
458 """
457 )
459 )
458 )
460 )
459 assert ip.user_ns_hidden["_"] == "hohey"
461 assert ip.user_ns_hidden["_"] == "hohey"
460
462
461
463
462 def test_time_local_ns():
464 def test_time_local_ns():
463 """
465 """
464 Test that local_ns is actually global_ns when running a cell magic
466 Test that local_ns is actually global_ns when running a cell magic
465 """
467 """
466 ip = get_ipython()
468 ip = get_ipython()
467 ip.run_cell("%%time\n" "myvar = 1")
469 ip.run_cell("%%time\n" "myvar = 1")
468 assert ip.user_ns["myvar"] == 1
470 assert ip.user_ns["myvar"] == 1
469 del ip.user_ns["myvar"]
471 del ip.user_ns["myvar"]
470
472
471
473
472 def test_doctest_mode():
474 def test_doctest_mode():
473 "Toggle doctest_mode twice, it should be a no-op and run without error"
475 "Toggle doctest_mode twice, it should be a no-op and run without error"
474 _ip.magic('doctest_mode')
476 _ip.magic('doctest_mode')
475 _ip.magic('doctest_mode')
477 _ip.magic('doctest_mode')
476
478
477
479
478 def test_parse_options():
480 def test_parse_options():
479 """Tests for basic options parsing in magics."""
481 """Tests for basic options parsing in magics."""
480 # These are only the most minimal of tests, more should be added later. At
482 # These are only the most minimal of tests, more should be added later. At
481 # the very least we check that basic text/unicode calls work OK.
483 # the very least we check that basic text/unicode calls work OK.
482 m = DummyMagics(_ip)
484 m = DummyMagics(_ip)
483 assert m.parse_options("foo", "")[1] == "foo"
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
487
486
488
487 def test_parse_options_preserve_non_option_string():
489 def test_parse_options_preserve_non_option_string():
488 """Test to assert preservation of non-option part of magic-block, while parsing magic options."""
490 """Test to assert preservation of non-option part of magic-block, while parsing magic options."""
489 m = DummyMagics(_ip)
491 m = DummyMagics(_ip)
490 opts, stmt = m.parse_options(
492 opts, stmt = m.parse_options(
491 " -n1 -r 13 _ = 314 + foo", "n:r:", preserve_non_opts=True
493 " -n1 -r 13 _ = 314 + foo", "n:r:", preserve_non_opts=True
492 )
494 )
493 assert opts == {"n": "1", "r": "13"}
495 assert opts == {"n": "1", "r": "13"}
494 assert stmt == "_ = 314 + foo"
496 assert stmt == "_ = 314 + foo"
495
497
496
498
497 def test_run_magic_preserve_code_block():
499 def test_run_magic_preserve_code_block():
498 """Test to assert preservation of non-option part of magic-block, while running magic."""
500 """Test to assert preservation of non-option part of magic-block, while running magic."""
499 _ip.user_ns["spaces"] = []
501 _ip.user_ns["spaces"] = []
500 _ip.magic("timeit -n1 -r1 spaces.append([s.count(' ') for s in ['document']])")
502 _ip.magic("timeit -n1 -r1 spaces.append([s.count(' ') for s in ['document']])")
501 assert _ip.user_ns["spaces"] == [[0]]
503 assert _ip.user_ns["spaces"] == [[0]]
502
504
503
505
504 def test_dirops():
506 def test_dirops():
505 """Test various directory handling operations."""
507 """Test various directory handling operations."""
506 # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/')
508 # curpath = lambda :os.path.splitdrive(os.getcwd())[1].replace('\\','/')
507 curpath = os.getcwd
509 curpath = os.getcwd
508 startdir = os.getcwd()
510 startdir = os.getcwd()
509 ipdir = os.path.realpath(_ip.ipython_dir)
511 ipdir = os.path.realpath(_ip.ipython_dir)
510 try:
512 try:
511 _ip.magic('cd "%s"' % ipdir)
513 _ip.magic('cd "%s"' % ipdir)
512 assert curpath() == ipdir
514 assert curpath() == ipdir
513 _ip.magic('cd -')
515 _ip.magic('cd -')
514 assert curpath() == startdir
516 assert curpath() == startdir
515 _ip.magic('pushd "%s"' % ipdir)
517 _ip.magic('pushd "%s"' % ipdir)
516 assert curpath() == ipdir
518 assert curpath() == ipdir
517 _ip.magic('popd')
519 _ip.magic('popd')
518 assert curpath() == startdir
520 assert curpath() == startdir
519 finally:
521 finally:
520 os.chdir(startdir)
522 os.chdir(startdir)
521
523
522
524
523 def test_cd_force_quiet():
525 def test_cd_force_quiet():
524 """Test OSMagics.cd_force_quiet option"""
526 """Test OSMagics.cd_force_quiet option"""
525 _ip.config.OSMagics.cd_force_quiet = True
527 _ip.config.OSMagics.cd_force_quiet = True
526 osmagics = osm.OSMagics(shell=_ip)
528 osmagics = osm.OSMagics(shell=_ip)
527
529
528 startdir = os.getcwd()
530 startdir = os.getcwd()
529 ipdir = os.path.realpath(_ip.ipython_dir)
531 ipdir = os.path.realpath(_ip.ipython_dir)
530
532
531 try:
533 try:
532 with tt.AssertNotPrints(ipdir):
534 with tt.AssertNotPrints(ipdir):
533 osmagics.cd('"%s"' % ipdir)
535 osmagics.cd('"%s"' % ipdir)
534 with tt.AssertNotPrints(startdir):
536 with tt.AssertNotPrints(startdir):
535 osmagics.cd('-')
537 osmagics.cd('-')
536 finally:
538 finally:
537 os.chdir(startdir)
539 os.chdir(startdir)
538
540
539
541
540 def test_xmode():
542 def test_xmode():
541 # Calling xmode three times should be a no-op
543 # Calling xmode three times should be a no-op
542 xmode = _ip.InteractiveTB.mode
544 xmode = _ip.InteractiveTB.mode
543 for i in range(4):
545 for i in range(4):
544 _ip.magic("xmode")
546 _ip.magic("xmode")
545 assert _ip.InteractiveTB.mode == xmode
547 assert _ip.InteractiveTB.mode == xmode
546
548
547 def test_reset_hard():
549 def test_reset_hard():
548 monitor = []
550 monitor = []
549 class A(object):
551 class A(object):
550 def __del__(self):
552 def __del__(self):
551 monitor.append(1)
553 monitor.append(1)
552 def __repr__(self):
554 def __repr__(self):
553 return "<A instance>"
555 return "<A instance>"
554
556
555 _ip.user_ns["a"] = A()
557 _ip.user_ns["a"] = A()
556 _ip.run_cell("a")
558 _ip.run_cell("a")
557
559
558 assert monitor == []
560 assert monitor == []
559 _ip.magic("reset -f")
561 _ip.magic("reset -f")
560 assert monitor == [1]
562 assert monitor == [1]
561
563
562 class TestXdel(tt.TempFileMixin):
564 class TestXdel(tt.TempFileMixin):
563 def test_xdel(self):
565 def test_xdel(self):
564 """Test that references from %run are cleared by xdel."""
566 """Test that references from %run are cleared by xdel."""
565 src = ("class A(object):\n"
567 src = ("class A(object):\n"
566 " monitor = []\n"
568 " monitor = []\n"
567 " def __del__(self):\n"
569 " def __del__(self):\n"
568 " self.monitor.append(1)\n"
570 " self.monitor.append(1)\n"
569 "a = A()\n")
571 "a = A()\n")
570 self.mktmp(src)
572 self.mktmp(src)
571 # %run creates some hidden references...
573 # %run creates some hidden references...
572 _ip.magic("run %s" % self.fname)
574 _ip.magic("run %s" % self.fname)
573 # ... as does the displayhook.
575 # ... as does the displayhook.
574 _ip.run_cell("a")
576 _ip.run_cell("a")
575
577
576 monitor = _ip.user_ns["A"].monitor
578 monitor = _ip.user_ns["A"].monitor
577 assert monitor == []
579 assert monitor == []
578
580
579 _ip.magic("xdel a")
581 _ip.magic("xdel a")
580
582
581 # Check that a's __del__ method has been called.
583 # Check that a's __del__ method has been called.
582 gc.collect(0)
584 gc.collect(0)
583 assert monitor == [1]
585 assert monitor == [1]
584
586
585 def doctest_who():
587 def doctest_who():
586 """doctest for %who
588 """doctest for %who
587
589
588 In [1]: %reset -sf
590 In [1]: %reset -sf
589
591
590 In [2]: alpha = 123
592 In [2]: alpha = 123
591
593
592 In [3]: beta = 'beta'
594 In [3]: beta = 'beta'
593
595
594 In [4]: %who int
596 In [4]: %who int
595 alpha
597 alpha
596
598
597 In [5]: %who str
599 In [5]: %who str
598 beta
600 beta
599
601
600 In [6]: %whos
602 In [6]: %whos
601 Variable Type Data/Info
603 Variable Type Data/Info
602 ----------------------------
604 ----------------------------
603 alpha int 123
605 alpha int 123
604 beta str beta
606 beta str beta
605
607
606 In [7]: %who_ls
608 In [7]: %who_ls
607 Out[7]: ['alpha', 'beta']
609 Out[7]: ['alpha', 'beta']
608 """
610 """
609
611
610 def test_whos():
612 def test_whos():
611 """Check that whos is protected against objects where repr() fails."""
613 """Check that whos is protected against objects where repr() fails."""
612 class A(object):
614 class A(object):
613 def __repr__(self):
615 def __repr__(self):
614 raise Exception()
616 raise Exception()
615 _ip.user_ns['a'] = A()
617 _ip.user_ns['a'] = A()
616 _ip.magic("whos")
618 _ip.magic("whos")
617
619
618 def doctest_precision():
620 def doctest_precision():
619 """doctest for %precision
621 """doctest for %precision
620
622
621 In [1]: f = get_ipython().display_formatter.formatters['text/plain']
623 In [1]: f = get_ipython().display_formatter.formatters['text/plain']
622
624
623 In [2]: %precision 5
625 In [2]: %precision 5
624 Out[2]: '%.5f'
626 Out[2]: '%.5f'
625
627
626 In [3]: f.float_format
628 In [3]: f.float_format
627 Out[3]: '%.5f'
629 Out[3]: '%.5f'
628
630
629 In [4]: %precision %e
631 In [4]: %precision %e
630 Out[4]: '%e'
632 Out[4]: '%e'
631
633
632 In [5]: f(3.1415927)
634 In [5]: f(3.1415927)
633 Out[5]: '3.141593e+00'
635 Out[5]: '3.141593e+00'
634 """
636 """
635
637
636 def test_debug_magic():
638 def test_debug_magic():
637 """Test debugging a small code with %debug
639 """Test debugging a small code with %debug
638
640
639 In [1]: with PdbTestInput(['c']):
641 In [1]: with PdbTestInput(['c']):
640 ...: %debug print("a b") #doctest: +ELLIPSIS
642 ...: %debug print("a b") #doctest: +ELLIPSIS
641 ...:
643 ...:
642 ...
644 ...
643 ipdb> c
645 ipdb> c
644 a b
646 a b
645 In [2]:
647 In [2]:
646 """
648 """
647
649
648 def test_psearch():
650 def test_psearch():
649 with tt.AssertPrints("dict.fromkeys"):
651 with tt.AssertPrints("dict.fromkeys"):
650 _ip.run_cell("dict.fr*?")
652 _ip.run_cell("dict.fr*?")
651 with tt.AssertPrints("Ο€.is_integer"):
653 with tt.AssertPrints("Ο€.is_integer"):
652 _ip.run_cell("Ο€ = 3.14;\nΟ€.is_integ*?")
654 _ip.run_cell("Ο€ = 3.14;\nΟ€.is_integ*?")
653
655
654 def test_timeit_shlex():
656 def test_timeit_shlex():
655 """test shlex issues with timeit (#1109)"""
657 """test shlex issues with timeit (#1109)"""
656 _ip.ex("def f(*a,**kw): pass")
658 _ip.ex("def f(*a,**kw): pass")
657 _ip.magic('timeit -n1 "this is a bug".count(" ")')
659 _ip.magic('timeit -n1 "this is a bug".count(" ")')
658 _ip.magic('timeit -r1 -n1 f(" ", 1)')
660 _ip.magic('timeit -r1 -n1 f(" ", 1)')
659 _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")')
661 _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")')
660 _ip.magic('timeit -r1 -n1 ("a " + "b")')
662 _ip.magic('timeit -r1 -n1 ("a " + "b")')
661 _ip.magic('timeit -r1 -n1 f("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
665
664
666
665 def test_timeit_special_syntax():
667 def test_timeit_special_syntax():
666 "Test %%timeit with IPython special syntax"
668 "Test %%timeit with IPython special syntax"
667 @register_line_magic
669 @register_line_magic
668 def lmagic(line):
670 def lmagic(line):
669 ip = get_ipython()
671 ip = get_ipython()
670 ip.user_ns['lmagic_out'] = line
672 ip.user_ns['lmagic_out'] = line
671
673
672 # line mode test
674 # line mode test
673 _ip.run_line_magic("timeit", "-n1 -r1 %lmagic my line")
675 _ip.run_line_magic("timeit", "-n1 -r1 %lmagic my line")
674 assert _ip.user_ns["lmagic_out"] == "my line"
676 assert _ip.user_ns["lmagic_out"] == "my line"
675 # cell mode test
677 # cell mode test
676 _ip.run_cell_magic("timeit", "-n1 -r1", "%lmagic my line2")
678 _ip.run_cell_magic("timeit", "-n1 -r1", "%lmagic my line2")
677 assert _ip.user_ns["lmagic_out"] == "my line2"
679 assert _ip.user_ns["lmagic_out"] == "my line2"
678
680
679
681
680 def test_timeit_return():
682 def test_timeit_return():
681 """
683 """
682 test whether timeit -o return object
684 test whether timeit -o return object
683 """
685 """
684
686
685 res = _ip.run_line_magic('timeit','-n10 -r10 -o 1')
687 res = _ip.run_line_magic('timeit','-n10 -r10 -o 1')
686 assert(res is not None)
688 assert(res is not None)
687
689
688 def test_timeit_quiet():
690 def test_timeit_quiet():
689 """
691 """
690 test quiet option of timeit magic
692 test quiet option of timeit magic
691 """
693 """
692 with tt.AssertNotPrints("loops"):
694 with tt.AssertNotPrints("loops"):
693 _ip.run_cell("%timeit -n1 -r1 -q 1")
695 _ip.run_cell("%timeit -n1 -r1 -q 1")
694
696
695 def test_timeit_return_quiet():
697 def test_timeit_return_quiet():
696 with tt.AssertNotPrints("loops"):
698 with tt.AssertNotPrints("loops"):
697 res = _ip.run_line_magic('timeit', '-n1 -r1 -q -o 1')
699 res = _ip.run_line_magic('timeit', '-n1 -r1 -q -o 1')
698 assert (res is not None)
700 assert (res is not None)
699
701
700 def test_timeit_invalid_return():
702 def test_timeit_invalid_return():
701 with pytest.raises(SyntaxError):
703 with pytest.raises(SyntaxError):
702 _ip.run_line_magic('timeit', 'return')
704 _ip.run_line_magic('timeit', 'return')
703
705
704 @dec.skipif(execution.profile is None)
706 @dec.skipif(execution.profile is None)
705 def test_prun_special_syntax():
707 def test_prun_special_syntax():
706 "Test %%prun with IPython special syntax"
708 "Test %%prun with IPython special syntax"
707 @register_line_magic
709 @register_line_magic
708 def lmagic(line):
710 def lmagic(line):
709 ip = get_ipython()
711 ip = get_ipython()
710 ip.user_ns['lmagic_out'] = line
712 ip.user_ns['lmagic_out'] = line
711
713
712 # line mode test
714 # line mode test
713 _ip.run_line_magic("prun", "-q %lmagic my line")
715 _ip.run_line_magic("prun", "-q %lmagic my line")
714 assert _ip.user_ns["lmagic_out"] == "my line"
716 assert _ip.user_ns["lmagic_out"] == "my line"
715 # cell mode test
717 # cell mode test
716 _ip.run_cell_magic("prun", "-q", "%lmagic my line2")
718 _ip.run_cell_magic("prun", "-q", "%lmagic my line2")
717 assert _ip.user_ns["lmagic_out"] == "my line2"
719 assert _ip.user_ns["lmagic_out"] == "my line2"
718
720
719
721
720 @dec.skipif(execution.profile is None)
722 @dec.skipif(execution.profile is None)
721 def test_prun_quotes():
723 def test_prun_quotes():
722 "Test that prun does not clobber string escapes (GH #1302)"
724 "Test that prun does not clobber string escapes (GH #1302)"
723 _ip.magic(r"prun -q x = '\t'")
725 _ip.magic(r"prun -q x = '\t'")
724 assert _ip.user_ns["x"] == "\t"
726 assert _ip.user_ns["x"] == "\t"
725
727
726
728
727 def test_extension():
729 def test_extension():
728 # Debugging information for failures of this test
730 # Debugging information for failures of this test
729 print('sys.path:')
731 print('sys.path:')
730 for p in sys.path:
732 for p in sys.path:
731 print(' ', p)
733 print(' ', p)
732 print('CWD', os.getcwd())
734 print('CWD', os.getcwd())
733
735
734 pytest.raises(ImportError, _ip.magic, "load_ext daft_extension")
736 pytest.raises(ImportError, _ip.magic, "load_ext daft_extension")
735 daft_path = os.path.join(os.path.dirname(__file__), "daft_extension")
737 daft_path = os.path.join(os.path.dirname(__file__), "daft_extension")
736 sys.path.insert(0, daft_path)
738 sys.path.insert(0, daft_path)
737 try:
739 try:
738 _ip.user_ns.pop('arq', None)
740 _ip.user_ns.pop('arq', None)
739 invalidate_caches() # Clear import caches
741 invalidate_caches() # Clear import caches
740 _ip.magic("load_ext daft_extension")
742 _ip.magic("load_ext daft_extension")
741 assert _ip.user_ns["arq"] == 185
743 assert _ip.user_ns["arq"] == 185
742 _ip.magic("unload_ext daft_extension")
744 _ip.magic("unload_ext daft_extension")
743 assert 'arq' not in _ip.user_ns
745 assert 'arq' not in _ip.user_ns
744 finally:
746 finally:
745 sys.path.remove(daft_path)
747 sys.path.remove(daft_path)
746
748
747
749
748 def test_notebook_export_json():
750 def test_notebook_export_json():
749 pytest.importorskip("nbformat")
751 pytest.importorskip("nbformat")
750 _ip = get_ipython()
752 _ip = get_ipython()
751 _ip.history_manager.reset() # Clear any existing history.
753 _ip.history_manager.reset() # Clear any existing history.
752 cmds = ["a=1", "def b():\n return a**2", "print('noΓ«l, Γ©tΓ©', b())"]
754 cmds = ["a=1", "def b():\n return a**2", "print('noΓ«l, Γ©tΓ©', b())"]
753 for i, cmd in enumerate(cmds, start=1):
755 for i, cmd in enumerate(cmds, start=1):
754 _ip.history_manager.store_inputs(i, cmd)
756 _ip.history_manager.store_inputs(i, cmd)
755 with TemporaryDirectory() as td:
757 with TemporaryDirectory() as td:
756 outfile = os.path.join(td, "nb.ipynb")
758 outfile = os.path.join(td, "nb.ipynb")
757 _ip.magic("notebook %s" % outfile)
759 _ip.magic("notebook %s" % outfile)
758
760
759
761
760 class TestEnv(TestCase):
762 class TestEnv(TestCase):
761
763
762 def test_env(self):
764 def test_env(self):
763 env = _ip.magic("env")
765 env = _ip.magic("env")
764 self.assertTrue(isinstance(env, dict))
766 self.assertTrue(isinstance(env, dict))
765
767
766 def test_env_secret(self):
768 def test_env_secret(self):
767 env = _ip.magic("env")
769 env = _ip.magic("env")
768 hidden = "<hidden>"
770 hidden = "<hidden>"
769 with mock.patch.dict(
771 with mock.patch.dict(
770 os.environ,
772 os.environ,
771 {
773 {
772 "API_KEY": "abc123",
774 "API_KEY": "abc123",
773 "SECRET_THING": "ssshhh",
775 "SECRET_THING": "ssshhh",
774 "JUPYTER_TOKEN": "",
776 "JUPYTER_TOKEN": "",
775 "VAR": "abc"
777 "VAR": "abc"
776 }
778 }
777 ):
779 ):
778 env = _ip.magic("env")
780 env = _ip.magic("env")
779 assert env["API_KEY"] == hidden
781 assert env["API_KEY"] == hidden
780 assert env["SECRET_THING"] == hidden
782 assert env["SECRET_THING"] == hidden
781 assert env["JUPYTER_TOKEN"] == hidden
783 assert env["JUPYTER_TOKEN"] == hidden
782 assert env["VAR"] == "abc"
784 assert env["VAR"] == "abc"
783
785
784 def test_env_get_set_simple(self):
786 def test_env_get_set_simple(self):
785 env = _ip.magic("env var val1")
787 env = _ip.magic("env var val1")
786 self.assertEqual(env, None)
788 self.assertEqual(env, None)
787 self.assertEqual(os.environ['var'], 'val1')
789 self.assertEqual(os.environ['var'], 'val1')
788 self.assertEqual(_ip.magic("env var"), 'val1')
790 self.assertEqual(_ip.magic("env var"), 'val1')
789 env = _ip.magic("env var=val2")
791 env = _ip.magic("env var=val2")
790 self.assertEqual(env, None)
792 self.assertEqual(env, None)
791 self.assertEqual(os.environ['var'], 'val2')
793 self.assertEqual(os.environ['var'], 'val2')
792
794
793 def test_env_get_set_complex(self):
795 def test_env_get_set_complex(self):
794 env = _ip.magic("env var 'val1 '' 'val2")
796 env = _ip.magic("env var 'val1 '' 'val2")
795 self.assertEqual(env, None)
797 self.assertEqual(env, None)
796 self.assertEqual(os.environ['var'], "'val1 '' 'val2")
798 self.assertEqual(os.environ['var'], "'val1 '' 'val2")
797 self.assertEqual(_ip.magic("env var"), "'val1 '' 'val2")
799 self.assertEqual(_ip.magic("env var"), "'val1 '' 'val2")
798 env = _ip.magic('env var=val2 val3="val4')
800 env = _ip.magic('env var=val2 val3="val4')
799 self.assertEqual(env, None)
801 self.assertEqual(env, None)
800 self.assertEqual(os.environ['var'], 'val2 val3="val4')
802 self.assertEqual(os.environ['var'], 'val2 val3="val4')
801
803
802 def test_env_set_bad_input(self):
804 def test_env_set_bad_input(self):
803 self.assertRaises(UsageError, lambda: _ip.magic("set_env var"))
805 self.assertRaises(UsageError, lambda: _ip.magic("set_env var"))
804
806
805 def test_env_set_whitespace(self):
807 def test_env_set_whitespace(self):
806 self.assertRaises(UsageError, lambda: _ip.magic("env var A=B"))
808 self.assertRaises(UsageError, lambda: _ip.magic("env var A=B"))
807
809
808
810
809 class CellMagicTestCase(TestCase):
811 class CellMagicTestCase(TestCase):
810
812
811 def check_ident(self, magic):
813 def check_ident(self, magic):
812 # Manually called, we get the result
814 # Manually called, we get the result
813 out = _ip.run_cell_magic(magic, "a", "b")
815 out = _ip.run_cell_magic(magic, "a", "b")
814 assert out == ("a", "b")
816 assert out == ("a", "b")
815 # Via run_cell, it goes into the user's namespace via displayhook
817 # Via run_cell, it goes into the user's namespace via displayhook
816 _ip.run_cell("%%" + magic + " c\nd\n")
818 _ip.run_cell("%%" + magic + " c\nd\n")
817 assert _ip.user_ns["_"] == ("c", "d\n")
819 assert _ip.user_ns["_"] == ("c", "d\n")
818
820
819 def test_cell_magic_func_deco(self):
821 def test_cell_magic_func_deco(self):
820 "Cell magic using simple decorator"
822 "Cell magic using simple decorator"
821 @register_cell_magic
823 @register_cell_magic
822 def cellm(line, cell):
824 def cellm(line, cell):
823 return line, cell
825 return line, cell
824
826
825 self.check_ident('cellm')
827 self.check_ident('cellm')
826
828
827 def test_cell_magic_reg(self):
829 def test_cell_magic_reg(self):
828 "Cell magic manually registered"
830 "Cell magic manually registered"
829 def cellm(line, cell):
831 def cellm(line, cell):
830 return line, cell
832 return line, cell
831
833
832 _ip.register_magic_function(cellm, 'cell', 'cellm2')
834 _ip.register_magic_function(cellm, 'cell', 'cellm2')
833 self.check_ident('cellm2')
835 self.check_ident('cellm2')
834
836
835 def test_cell_magic_class(self):
837 def test_cell_magic_class(self):
836 "Cell magics declared via a class"
838 "Cell magics declared via a class"
837 @magics_class
839 @magics_class
838 class MyMagics(Magics):
840 class MyMagics(Magics):
839
841
840 @cell_magic
842 @cell_magic
841 def cellm3(self, line, cell):
843 def cellm3(self, line, cell):
842 return line, cell
844 return line, cell
843
845
844 _ip.register_magics(MyMagics)
846 _ip.register_magics(MyMagics)
845 self.check_ident('cellm3')
847 self.check_ident('cellm3')
846
848
847 def test_cell_magic_class2(self):
849 def test_cell_magic_class2(self):
848 "Cell magics declared via a class, #2"
850 "Cell magics declared via a class, #2"
849 @magics_class
851 @magics_class
850 class MyMagics2(Magics):
852 class MyMagics2(Magics):
851
853
852 @cell_magic('cellm4')
854 @cell_magic('cellm4')
853 def cellm33(self, line, cell):
855 def cellm33(self, line, cell):
854 return line, cell
856 return line, cell
855
857
856 _ip.register_magics(MyMagics2)
858 _ip.register_magics(MyMagics2)
857 self.check_ident('cellm4')
859 self.check_ident('cellm4')
858 # Check that nothing is registered as 'cellm33'
860 # Check that nothing is registered as 'cellm33'
859 c33 = _ip.find_cell_magic('cellm33')
861 c33 = _ip.find_cell_magic('cellm33')
860 assert c33 == None
862 assert c33 == None
861
863
862 def test_file():
864 def test_file():
863 """Basic %%writefile"""
865 """Basic %%writefile"""
864 ip = get_ipython()
866 ip = get_ipython()
865 with TemporaryDirectory() as td:
867 with TemporaryDirectory() as td:
866 fname = os.path.join(td, "file1")
868 fname = os.path.join(td, "file1")
867 ip.run_cell_magic(
869 ip.run_cell_magic(
868 "writefile",
870 "writefile",
869 fname,
871 fname,
870 "\n".join(
872 "\n".join(
871 [
873 [
872 "line1",
874 "line1",
873 "line2",
875 "line2",
874 ]
876 ]
875 ),
877 ),
876 )
878 )
877 s = Path(fname).read_text(encoding="utf-8")
879 s = Path(fname).read_text(encoding="utf-8")
878 assert "line1\n" in s
880 assert "line1\n" in s
879 assert "line2" in s
881 assert "line2" in s
880
882
881
883
882 @dec.skip_win32
884 @dec.skip_win32
883 def test_file_single_quote():
885 def test_file_single_quote():
884 """Basic %%writefile with embedded single quotes"""
886 """Basic %%writefile with embedded single quotes"""
885 ip = get_ipython()
887 ip = get_ipython()
886 with TemporaryDirectory() as td:
888 with TemporaryDirectory() as td:
887 fname = os.path.join(td, "'file1'")
889 fname = os.path.join(td, "'file1'")
888 ip.run_cell_magic(
890 ip.run_cell_magic(
889 "writefile",
891 "writefile",
890 fname,
892 fname,
891 "\n".join(
893 "\n".join(
892 [
894 [
893 "line1",
895 "line1",
894 "line2",
896 "line2",
895 ]
897 ]
896 ),
898 ),
897 )
899 )
898 s = Path(fname).read_text(encoding="utf-8")
900 s = Path(fname).read_text(encoding="utf-8")
899 assert "line1\n" in s
901 assert "line1\n" in s
900 assert "line2" in s
902 assert "line2" in s
901
903
902
904
903 @dec.skip_win32
905 @dec.skip_win32
904 def test_file_double_quote():
906 def test_file_double_quote():
905 """Basic %%writefile with embedded double quotes"""
907 """Basic %%writefile with embedded double quotes"""
906 ip = get_ipython()
908 ip = get_ipython()
907 with TemporaryDirectory() as td:
909 with TemporaryDirectory() as td:
908 fname = os.path.join(td, '"file1"')
910 fname = os.path.join(td, '"file1"')
909 ip.run_cell_magic(
911 ip.run_cell_magic(
910 "writefile",
912 "writefile",
911 fname,
913 fname,
912 "\n".join(
914 "\n".join(
913 [
915 [
914 "line1",
916 "line1",
915 "line2",
917 "line2",
916 ]
918 ]
917 ),
919 ),
918 )
920 )
919 s = Path(fname).read_text(encoding="utf-8")
921 s = Path(fname).read_text(encoding="utf-8")
920 assert "line1\n" in s
922 assert "line1\n" in s
921 assert "line2" in s
923 assert "line2" in s
922
924
923
925
924 def test_file_var_expand():
926 def test_file_var_expand():
925 """%%writefile $filename"""
927 """%%writefile $filename"""
926 ip = get_ipython()
928 ip = get_ipython()
927 with TemporaryDirectory() as td:
929 with TemporaryDirectory() as td:
928 fname = os.path.join(td, "file1")
930 fname = os.path.join(td, "file1")
929 ip.user_ns["filename"] = fname
931 ip.user_ns["filename"] = fname
930 ip.run_cell_magic(
932 ip.run_cell_magic(
931 "writefile",
933 "writefile",
932 "$filename",
934 "$filename",
933 "\n".join(
935 "\n".join(
934 [
936 [
935 "line1",
937 "line1",
936 "line2",
938 "line2",
937 ]
939 ]
938 ),
940 ),
939 )
941 )
940 s = Path(fname).read_text(encoding="utf-8")
942 s = Path(fname).read_text(encoding="utf-8")
941 assert "line1\n" in s
943 assert "line1\n" in s
942 assert "line2" in s
944 assert "line2" in s
943
945
944
946
945 def test_file_unicode():
947 def test_file_unicode():
946 """%%writefile with unicode cell"""
948 """%%writefile with unicode cell"""
947 ip = get_ipython()
949 ip = get_ipython()
948 with TemporaryDirectory() as td:
950 with TemporaryDirectory() as td:
949 fname = os.path.join(td, 'file1')
951 fname = os.path.join(td, 'file1')
950 ip.run_cell_magic("writefile", fname, u'\n'.join([
952 ip.run_cell_magic("writefile", fname, u'\n'.join([
951 u'linΓ©1',
953 u'linΓ©1',
952 u'linΓ©2',
954 u'linΓ©2',
953 ]))
955 ]))
954 with io.open(fname, encoding='utf-8') as f:
956 with io.open(fname, encoding='utf-8') as f:
955 s = f.read()
957 s = f.read()
956 assert "linΓ©1\n" in s
958 assert "linΓ©1\n" in s
957 assert "linΓ©2" in s
959 assert "linΓ©2" in s
958
960
959
961
960 def test_file_amend():
962 def test_file_amend():
961 """%%writefile -a amends files"""
963 """%%writefile -a amends files"""
962 ip = get_ipython()
964 ip = get_ipython()
963 with TemporaryDirectory() as td:
965 with TemporaryDirectory() as td:
964 fname = os.path.join(td, "file2")
966 fname = os.path.join(td, "file2")
965 ip.run_cell_magic(
967 ip.run_cell_magic(
966 "writefile",
968 "writefile",
967 fname,
969 fname,
968 "\n".join(
970 "\n".join(
969 [
971 [
970 "line1",
972 "line1",
971 "line2",
973 "line2",
972 ]
974 ]
973 ),
975 ),
974 )
976 )
975 ip.run_cell_magic(
977 ip.run_cell_magic(
976 "writefile",
978 "writefile",
977 "-a %s" % fname,
979 "-a %s" % fname,
978 "\n".join(
980 "\n".join(
979 [
981 [
980 "line3",
982 "line3",
981 "line4",
983 "line4",
982 ]
984 ]
983 ),
985 ),
984 )
986 )
985 s = Path(fname).read_text(encoding="utf-8")
987 s = Path(fname).read_text(encoding="utf-8")
986 assert "line1\n" in s
988 assert "line1\n" in s
987 assert "line3\n" in s
989 assert "line3\n" in s
988
990
989
991
990 def test_file_spaces():
992 def test_file_spaces():
991 """%%file with spaces in filename"""
993 """%%file with spaces in filename"""
992 ip = get_ipython()
994 ip = get_ipython()
993 with TemporaryWorkingDirectory() as td:
995 with TemporaryWorkingDirectory() as td:
994 fname = "file name"
996 fname = "file name"
995 ip.run_cell_magic(
997 ip.run_cell_magic(
996 "file",
998 "file",
997 '"%s"' % fname,
999 '"%s"' % fname,
998 "\n".join(
1000 "\n".join(
999 [
1001 [
1000 "line1",
1002 "line1",
1001 "line2",
1003 "line2",
1002 ]
1004 ]
1003 ),
1005 ),
1004 )
1006 )
1005 s = Path(fname).read_text(encoding="utf-8")
1007 s = Path(fname).read_text(encoding="utf-8")
1006 assert "line1\n" in s
1008 assert "line1\n" in s
1007 assert "line2" in s
1009 assert "line2" in s
1008
1010
1009
1011
1010 def test_script_config():
1012 def test_script_config():
1011 ip = get_ipython()
1013 ip = get_ipython()
1012 ip.config.ScriptMagics.script_magics = ['whoda']
1014 ip.config.ScriptMagics.script_magics = ['whoda']
1013 sm = script.ScriptMagics(shell=ip)
1015 sm = script.ScriptMagics(shell=ip)
1014 assert "whoda" in sm.magics["cell"]
1016 assert "whoda" in sm.magics["cell"]
1015
1017
1016
1018
1017 def test_script_out():
1019 def test_script_out():
1018 ip = get_ipython()
1020 ip = get_ipython()
1019 ip.run_cell_magic("script", f"--out output {sys.executable}", "print('hi')")
1021 ip.run_cell_magic("script", f"--out output {sys.executable}", "print('hi')")
1020 assert ip.user_ns["output"].strip() == "hi"
1022 assert ip.user_ns["output"].strip() == "hi"
1021
1023
1022
1024
1023 def test_script_err():
1025 def test_script_err():
1024 ip = get_ipython()
1026 ip = get_ipython()
1025 ip.run_cell_magic(
1027 ip.run_cell_magic(
1026 "script",
1028 "script",
1027 f"--err error {sys.executable}",
1029 f"--err error {sys.executable}",
1028 "import sys; print('hello', file=sys.stderr)",
1030 "import sys; print('hello', file=sys.stderr)",
1029 )
1031 )
1030 assert ip.user_ns["error"].strip() == "hello"
1032 assert ip.user_ns["error"].strip() == "hello"
1031
1033
1032
1034
1033 def test_script_out_err():
1035 def test_script_out_err():
1034
1036
1035 ip = get_ipython()
1037 ip = get_ipython()
1036 ip.run_cell_magic(
1038 ip.run_cell_magic(
1037 "script",
1039 "script",
1038 f"--out output --err error {sys.executable}",
1040 f"--out output --err error {sys.executable}",
1039 "\n".join(
1041 "\n".join(
1040 [
1042 [
1041 "import sys",
1043 "import sys",
1042 "print('hi')",
1044 "print('hi')",
1043 "print('hello', file=sys.stderr)",
1045 "print('hello', file=sys.stderr)",
1044 ]
1046 ]
1045 ),
1047 ),
1046 )
1048 )
1047 assert ip.user_ns["output"].strip() == "hi"
1049 assert ip.user_ns["output"].strip() == "hi"
1048 assert ip.user_ns["error"].strip() == "hello"
1050 assert ip.user_ns["error"].strip() == "hello"
1049
1051
1050
1052
1051 async def test_script_bg_out():
1053 async def test_script_bg_out():
1052 ip = get_ipython()
1054 ip = get_ipython()
1053 ip.run_cell_magic("script", f"--bg --out output {sys.executable}", "print('hi')")
1055 ip.run_cell_magic("script", f"--bg --out output {sys.executable}", "print('hi')")
1054 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1056 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1055 assert ip.user_ns["output"].at_eof()
1057 assert ip.user_ns["output"].at_eof()
1056
1058
1057
1059
1058 async def test_script_bg_err():
1060 async def test_script_bg_err():
1059 ip = get_ipython()
1061 ip = get_ipython()
1060 ip.run_cell_magic(
1062 ip.run_cell_magic(
1061 "script",
1063 "script",
1062 f"--bg --err error {sys.executable}",
1064 f"--bg --err error {sys.executable}",
1063 "import sys; print('hello', file=sys.stderr)",
1065 "import sys; print('hello', file=sys.stderr)",
1064 )
1066 )
1065 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1067 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1066 assert ip.user_ns["error"].at_eof()
1068 assert ip.user_ns["error"].at_eof()
1067
1069
1068
1070
1069 async def test_script_bg_out_err():
1071 async def test_script_bg_out_err():
1070 ip = get_ipython()
1072 ip = get_ipython()
1071 ip.run_cell_magic(
1073 ip.run_cell_magic(
1072 "script",
1074 "script",
1073 f"--bg --out output --err error {sys.executable}",
1075 f"--bg --out output --err error {sys.executable}",
1074 "\n".join(
1076 "\n".join(
1075 [
1077 [
1076 "import sys",
1078 "import sys",
1077 "print('hi')",
1079 "print('hi')",
1078 "print('hello', file=sys.stderr)",
1080 "print('hello', file=sys.stderr)",
1079 ]
1081 ]
1080 ),
1082 ),
1081 )
1083 )
1082 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1084 assert (await ip.user_ns["output"].read()).strip() == b"hi"
1083 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1085 assert (await ip.user_ns["error"].read()).strip() == b"hello"
1084 assert ip.user_ns["output"].at_eof()
1086 assert ip.user_ns["output"].at_eof()
1085 assert ip.user_ns["error"].at_eof()
1087 assert ip.user_ns["error"].at_eof()
1086
1088
1087
1089
1088 async def test_script_bg_proc():
1090 async def test_script_bg_proc():
1089 ip = get_ipython()
1091 ip = get_ipython()
1090 ip.run_cell_magic(
1092 ip.run_cell_magic(
1091 "script",
1093 "script",
1092 f"--bg --out output --proc p {sys.executable}",
1094 f"--bg --out output --proc p {sys.executable}",
1093 "\n".join(
1095 "\n".join(
1094 [
1096 [
1095 "import sys",
1097 "import sys",
1096 "print('hi')",
1098 "print('hi')",
1097 "print('hello', file=sys.stderr)",
1099 "print('hello', file=sys.stderr)",
1098 ]
1100 ]
1099 ),
1101 ),
1100 )
1102 )
1101 p = ip.user_ns["p"]
1103 p = ip.user_ns["p"]
1102 await p.wait()
1104 await p.wait()
1103 assert p.returncode == 0
1105 assert p.returncode == 0
1104 assert (await p.stdout.read()).strip() == b"hi"
1106 assert (await p.stdout.read()).strip() == b"hi"
1105 # not captured, so empty
1107 # not captured, so empty
1106 assert (await p.stderr.read()) == b""
1108 assert (await p.stderr.read()) == b""
1107 assert p.stdout.at_eof()
1109 assert p.stdout.at_eof()
1108 assert p.stderr.at_eof()
1110 assert p.stderr.at_eof()
1109
1111
1110
1112
1111 def test_script_defaults():
1113 def test_script_defaults():
1112 ip = get_ipython()
1114 ip = get_ipython()
1113 for cmd in ['sh', 'bash', 'perl', 'ruby']:
1115 for cmd in ['sh', 'bash', 'perl', 'ruby']:
1114 try:
1116 try:
1115 find_cmd(cmd)
1117 find_cmd(cmd)
1116 except Exception:
1118 except Exception:
1117 pass
1119 pass
1118 else:
1120 else:
1119 assert cmd in ip.magics_manager.magics["cell"]
1121 assert cmd in ip.magics_manager.magics["cell"]
1120
1122
1121
1123
1122 @magics_class
1124 @magics_class
1123 class FooFoo(Magics):
1125 class FooFoo(Magics):
1124 """class with both %foo and %%foo magics"""
1126 """class with both %foo and %%foo magics"""
1125 @line_magic('foo')
1127 @line_magic('foo')
1126 def line_foo(self, line):
1128 def line_foo(self, line):
1127 "I am line foo"
1129 "I am line foo"
1128 pass
1130 pass
1129
1131
1130 @cell_magic("foo")
1132 @cell_magic("foo")
1131 def cell_foo(self, line, cell):
1133 def cell_foo(self, line, cell):
1132 "I am cell foo, not line foo"
1134 "I am cell foo, not line foo"
1133 pass
1135 pass
1134
1136
1135 def test_line_cell_info():
1137 def test_line_cell_info():
1136 """%%foo and %foo magics are distinguishable to inspect"""
1138 """%%foo and %foo magics are distinguishable to inspect"""
1137 ip = get_ipython()
1139 ip = get_ipython()
1138 ip.magics_manager.register(FooFoo)
1140 ip.magics_manager.register(FooFoo)
1139 oinfo = ip.object_inspect("foo")
1141 oinfo = ip.object_inspect("foo")
1140 assert oinfo["found"] is True
1142 assert oinfo["found"] is True
1141 assert oinfo["ismagic"] is True
1143 assert oinfo["ismagic"] is True
1142
1144
1143 oinfo = ip.object_inspect("%%foo")
1145 oinfo = ip.object_inspect("%%foo")
1144 assert oinfo["found"] is True
1146 assert oinfo["found"] is True
1145 assert oinfo["ismagic"] is True
1147 assert oinfo["ismagic"] is True
1146 assert oinfo["docstring"] == FooFoo.cell_foo.__doc__
1148 assert oinfo["docstring"] == FooFoo.cell_foo.__doc__
1147
1149
1148 oinfo = ip.object_inspect("%foo")
1150 oinfo = ip.object_inspect("%foo")
1149 assert oinfo["found"] is True
1151 assert oinfo["found"] is True
1150 assert oinfo["ismagic"] is True
1152 assert oinfo["ismagic"] is True
1151 assert oinfo["docstring"] == FooFoo.line_foo.__doc__
1153 assert oinfo["docstring"] == FooFoo.line_foo.__doc__
1152
1154
1153
1155
1154 def test_multiple_magics():
1156 def test_multiple_magics():
1155 ip = get_ipython()
1157 ip = get_ipython()
1156 foo1 = FooFoo(ip)
1158 foo1 = FooFoo(ip)
1157 foo2 = FooFoo(ip)
1159 foo2 = FooFoo(ip)
1158 mm = ip.magics_manager
1160 mm = ip.magics_manager
1159 mm.register(foo1)
1161 mm.register(foo1)
1160 assert mm.magics["line"]["foo"].__self__ is foo1
1162 assert mm.magics["line"]["foo"].__self__ is foo1
1161 mm.register(foo2)
1163 mm.register(foo2)
1162 assert mm.magics["line"]["foo"].__self__ is foo2
1164 assert mm.magics["line"]["foo"].__self__ is foo2
1163
1165
1164
1166
1165 def test_alias_magic():
1167 def test_alias_magic():
1166 """Test %alias_magic."""
1168 """Test %alias_magic."""
1167 ip = get_ipython()
1169 ip = get_ipython()
1168 mm = ip.magics_manager
1170 mm = ip.magics_manager
1169
1171
1170 # Basic operation: both cell and line magics are created, if possible.
1172 # Basic operation: both cell and line magics are created, if possible.
1171 ip.run_line_magic("alias_magic", "timeit_alias timeit")
1173 ip.run_line_magic("alias_magic", "timeit_alias timeit")
1172 assert "timeit_alias" in mm.magics["line"]
1174 assert "timeit_alias" in mm.magics["line"]
1173 assert "timeit_alias" in mm.magics["cell"]
1175 assert "timeit_alias" in mm.magics["cell"]
1174
1176
1175 # --cell is specified, line magic not created.
1177 # --cell is specified, line magic not created.
1176 ip.run_line_magic("alias_magic", "--cell timeit_cell_alias timeit")
1178 ip.run_line_magic("alias_magic", "--cell timeit_cell_alias timeit")
1177 assert "timeit_cell_alias" not in mm.magics["line"]
1179 assert "timeit_cell_alias" not in mm.magics["line"]
1178 assert "timeit_cell_alias" in mm.magics["cell"]
1180 assert "timeit_cell_alias" in mm.magics["cell"]
1179
1181
1180 # Test that line alias is created successfully.
1182 # Test that line alias is created successfully.
1181 ip.run_line_magic("alias_magic", "--line env_alias env")
1183 ip.run_line_magic("alias_magic", "--line env_alias env")
1182 assert ip.run_line_magic("env", "") == ip.run_line_magic("env_alias", "")
1184 assert ip.run_line_magic("env", "") == ip.run_line_magic("env_alias", "")
1183
1185
1184 # Test that line alias with parameters passed in is created successfully.
1186 # Test that line alias with parameters passed in is created successfully.
1185 ip.run_line_magic(
1187 ip.run_line_magic(
1186 "alias_magic", "--line history_alias history --params " + shlex.quote("3")
1188 "alias_magic", "--line history_alias history --params " + shlex.quote("3")
1187 )
1189 )
1188 assert "history_alias" in mm.magics["line"]
1190 assert "history_alias" in mm.magics["line"]
1189
1191
1190
1192
1191 def test_save():
1193 def test_save():
1192 """Test %save."""
1194 """Test %save."""
1193 ip = get_ipython()
1195 ip = get_ipython()
1194 ip.history_manager.reset() # Clear any existing history.
1196 ip.history_manager.reset() # Clear any existing history.
1195 cmds = ["a=1", "def b():\n return a**2", "print(a, b())"]
1197 cmds = ["a=1", "def b():\n return a**2", "print(a, b())"]
1196 for i, cmd in enumerate(cmds, start=1):
1198 for i, cmd in enumerate(cmds, start=1):
1197 ip.history_manager.store_inputs(i, cmd)
1199 ip.history_manager.store_inputs(i, cmd)
1198 with TemporaryDirectory() as tmpdir:
1200 with TemporaryDirectory() as tmpdir:
1199 file = os.path.join(tmpdir, "testsave.py")
1201 file = os.path.join(tmpdir, "testsave.py")
1200 ip.run_line_magic("save", "%s 1-10" % file)
1202 ip.run_line_magic("save", "%s 1-10" % file)
1201 content = Path(file).read_text(encoding="utf-8")
1203 content = Path(file).read_text(encoding="utf-8")
1202 assert content.count(cmds[0]) == 1
1204 assert content.count(cmds[0]) == 1
1203 assert "coding: utf-8" in content
1205 assert "coding: utf-8" in content
1204 ip.run_line_magic("save", "-a %s 1-10" % file)
1206 ip.run_line_magic("save", "-a %s 1-10" % file)
1205 content = Path(file).read_text(encoding="utf-8")
1207 content = Path(file).read_text(encoding="utf-8")
1206 assert content.count(cmds[0]) == 2
1208 assert content.count(cmds[0]) == 2
1207 assert "coding: utf-8" in content
1209 assert "coding: utf-8" in content
1208
1210
1209
1211
1210 def test_save_with_no_args():
1212 def test_save_with_no_args():
1211 ip = get_ipython()
1213 ip = get_ipython()
1212 ip.history_manager.reset() # Clear any existing history.
1214 ip.history_manager.reset() # Clear any existing history.
1213 cmds = ["a=1", "def b():\n return a**2", "print(a, b())", "%save"]
1215 cmds = ["a=1", "def b():\n return a**2", "print(a, b())", "%save"]
1214 for i, cmd in enumerate(cmds, start=1):
1216 for i, cmd in enumerate(cmds, start=1):
1215 ip.history_manager.store_inputs(i, cmd)
1217 ip.history_manager.store_inputs(i, cmd)
1216
1218
1217 with TemporaryDirectory() as tmpdir:
1219 with TemporaryDirectory() as tmpdir:
1218 path = os.path.join(tmpdir, "testsave.py")
1220 path = os.path.join(tmpdir, "testsave.py")
1219 ip.run_line_magic("save", path)
1221 ip.run_line_magic("save", path)
1220 content = Path(path).read_text(encoding="utf-8")
1222 content = Path(path).read_text(encoding="utf-8")
1221 expected_content = dedent(
1223 expected_content = dedent(
1222 """\
1224 """\
1223 # coding: utf-8
1225 # coding: utf-8
1224 a=1
1226 a=1
1225 def b():
1227 def b():
1226 return a**2
1228 return a**2
1227 print(a, b())
1229 print(a, b())
1228 """
1230 """
1229 )
1231 )
1230 assert content == expected_content
1232 assert content == expected_content
1231
1233
1232
1234
1233 def test_store():
1235 def test_store():
1234 """Test %store."""
1236 """Test %store."""
1235 ip = get_ipython()
1237 ip = get_ipython()
1236 ip.run_line_magic('load_ext', 'storemagic')
1238 ip.run_line_magic('load_ext', 'storemagic')
1237
1239
1238 # make sure the storage is empty
1240 # make sure the storage is empty
1239 ip.run_line_magic("store", "-z")
1241 ip.run_line_magic("store", "-z")
1240 ip.user_ns["var"] = 42
1242 ip.user_ns["var"] = 42
1241 ip.run_line_magic("store", "var")
1243 ip.run_line_magic("store", "var")
1242 ip.user_ns["var"] = 39
1244 ip.user_ns["var"] = 39
1243 ip.run_line_magic("store", "-r")
1245 ip.run_line_magic("store", "-r")
1244 assert ip.user_ns["var"] == 42
1246 assert ip.user_ns["var"] == 42
1245
1247
1246 ip.run_line_magic("store", "-d var")
1248 ip.run_line_magic("store", "-d var")
1247 ip.user_ns["var"] = 39
1249 ip.user_ns["var"] = 39
1248 ip.run_line_magic("store", "-r")
1250 ip.run_line_magic("store", "-r")
1249 assert ip.user_ns["var"] == 39
1251 assert ip.user_ns["var"] == 39
1250
1252
1251
1253
1252 def _run_edit_test(arg_s, exp_filename=None,
1254 def _run_edit_test(arg_s, exp_filename=None,
1253 exp_lineno=-1,
1255 exp_lineno=-1,
1254 exp_contents=None,
1256 exp_contents=None,
1255 exp_is_temp=None):
1257 exp_is_temp=None):
1256 ip = get_ipython()
1258 ip = get_ipython()
1257 M = code.CodeMagics(ip)
1259 M = code.CodeMagics(ip)
1258 last_call = ['','']
1260 last_call = ['','']
1259 opts,args = M.parse_options(arg_s,'prxn:')
1261 opts,args = M.parse_options(arg_s,'prxn:')
1260 filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call)
1262 filename, lineno, is_temp = M._find_edit_target(ip, args, opts, last_call)
1261
1263
1262 if exp_filename is not None:
1264 if exp_filename is not None:
1263 assert exp_filename == filename
1265 assert exp_filename == filename
1264 if exp_contents is not None:
1266 if exp_contents is not None:
1265 with io.open(filename, 'r', encoding='utf-8') as f:
1267 with io.open(filename, 'r', encoding='utf-8') as f:
1266 contents = f.read()
1268 contents = f.read()
1267 assert exp_contents == contents
1269 assert exp_contents == contents
1268 if exp_lineno != -1:
1270 if exp_lineno != -1:
1269 assert exp_lineno == lineno
1271 assert exp_lineno == lineno
1270 if exp_is_temp is not None:
1272 if exp_is_temp is not None:
1271 assert exp_is_temp == is_temp
1273 assert exp_is_temp == is_temp
1272
1274
1273
1275
1274 def test_edit_interactive():
1276 def test_edit_interactive():
1275 """%edit on interactively defined objects"""
1277 """%edit on interactively defined objects"""
1276 ip = get_ipython()
1278 ip = get_ipython()
1277 n = ip.execution_count
1279 n = ip.execution_count
1278 ip.run_cell("def foo(): return 1", store_history=True)
1280 ip.run_cell("def foo(): return 1", store_history=True)
1279
1281
1280 with pytest.raises(code.InteractivelyDefined) as e:
1282 with pytest.raises(code.InteractivelyDefined) as e:
1281 _run_edit_test("foo")
1283 _run_edit_test("foo")
1282 assert e.value.index == n
1284 assert e.value.index == n
1283
1285
1284
1286
1285 def test_edit_cell():
1287 def test_edit_cell():
1286 """%edit [cell id]"""
1288 """%edit [cell id]"""
1287 ip = get_ipython()
1289 ip = get_ipython()
1288
1290
1289 ip.run_cell("def foo(): return 1", store_history=True)
1291 ip.run_cell("def foo(): return 1", store_history=True)
1290
1292
1291 # test
1293 # test
1292 _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True)
1294 _run_edit_test("1", exp_contents=ip.user_ns['In'][1], exp_is_temp=True)
1293
1295
1294 def test_edit_fname():
1296 def test_edit_fname():
1295 """%edit file"""
1297 """%edit file"""
1296 # test
1298 # test
1297 _run_edit_test("test file.py", exp_filename="test file.py")
1299 _run_edit_test("test file.py", exp_filename="test file.py")
1298
1300
1299 def test_bookmark():
1301 def test_bookmark():
1300 ip = get_ipython()
1302 ip = get_ipython()
1301 ip.run_line_magic('bookmark', 'bmname')
1303 ip.run_line_magic('bookmark', 'bmname')
1302 with tt.AssertPrints('bmname'):
1304 with tt.AssertPrints('bmname'):
1303 ip.run_line_magic('bookmark', '-l')
1305 ip.run_line_magic('bookmark', '-l')
1304 ip.run_line_magic('bookmark', '-d bmname')
1306 ip.run_line_magic('bookmark', '-d bmname')
1305
1307
1306 def test_ls_magic():
1308 def test_ls_magic():
1307 ip = get_ipython()
1309 ip = get_ipython()
1308 json_formatter = ip.display_formatter.formatters['application/json']
1310 json_formatter = ip.display_formatter.formatters['application/json']
1309 json_formatter.enabled = True
1311 json_formatter.enabled = True
1310 lsmagic = ip.magic('lsmagic')
1312 lsmagic = ip.magic('lsmagic')
1311 with warnings.catch_warnings(record=True) as w:
1313 with warnings.catch_warnings(record=True) as w:
1312 j = json_formatter(lsmagic)
1314 j = json_formatter(lsmagic)
1313 assert sorted(j) == ["cell", "line"]
1315 assert sorted(j) == ["cell", "line"]
1314 assert w == [] # no warnings
1316 assert w == [] # no warnings
1315
1317
1316
1318
1317 def test_strip_initial_indent():
1319 def test_strip_initial_indent():
1318 def sii(s):
1320 def sii(s):
1319 lines = s.splitlines()
1321 lines = s.splitlines()
1320 return '\n'.join(code.strip_initial_indent(lines))
1322 return '\n'.join(code.strip_initial_indent(lines))
1321
1323
1322 assert sii(" a = 1\nb = 2") == "a = 1\nb = 2"
1324 assert sii(" a = 1\nb = 2") == "a = 1\nb = 2"
1323 assert sii(" a\n b\nc") == "a\n b\nc"
1325 assert sii(" a\n b\nc") == "a\n b\nc"
1324 assert sii("a\n b") == "a\n b"
1326 assert sii("a\n b") == "a\n b"
1325
1327
1326 def test_logging_magic_quiet_from_arg():
1328 def test_logging_magic_quiet_from_arg():
1327 _ip.config.LoggingMagics.quiet = False
1329 _ip.config.LoggingMagics.quiet = False
1328 lm = logging.LoggingMagics(shell=_ip)
1330 lm = logging.LoggingMagics(shell=_ip)
1329 with TemporaryDirectory() as td:
1331 with TemporaryDirectory() as td:
1330 try:
1332 try:
1331 with tt.AssertNotPrints(re.compile("Activating.*")):
1333 with tt.AssertNotPrints(re.compile("Activating.*")):
1332 lm.logstart('-q {}'.format(
1334 lm.logstart('-q {}'.format(
1333 os.path.join(td, "quiet_from_arg.log")))
1335 os.path.join(td, "quiet_from_arg.log")))
1334 finally:
1336 finally:
1335 _ip.logger.logstop()
1337 _ip.logger.logstop()
1336
1338
1337 def test_logging_magic_quiet_from_config():
1339 def test_logging_magic_quiet_from_config():
1338 _ip.config.LoggingMagics.quiet = True
1340 _ip.config.LoggingMagics.quiet = True
1339 lm = logging.LoggingMagics(shell=_ip)
1341 lm = logging.LoggingMagics(shell=_ip)
1340 with TemporaryDirectory() as td:
1342 with TemporaryDirectory() as td:
1341 try:
1343 try:
1342 with tt.AssertNotPrints(re.compile("Activating.*")):
1344 with tt.AssertNotPrints(re.compile("Activating.*")):
1343 lm.logstart(os.path.join(td, "quiet_from_config.log"))
1345 lm.logstart(os.path.join(td, "quiet_from_config.log"))
1344 finally:
1346 finally:
1345 _ip.logger.logstop()
1347 _ip.logger.logstop()
1346
1348
1347
1349
1348 def test_logging_magic_not_quiet():
1350 def test_logging_magic_not_quiet():
1349 _ip.config.LoggingMagics.quiet = False
1351 _ip.config.LoggingMagics.quiet = False
1350 lm = logging.LoggingMagics(shell=_ip)
1352 lm = logging.LoggingMagics(shell=_ip)
1351 with TemporaryDirectory() as td:
1353 with TemporaryDirectory() as td:
1352 try:
1354 try:
1353 with tt.AssertPrints(re.compile("Activating.*")):
1355 with tt.AssertPrints(re.compile("Activating.*")):
1354 lm.logstart(os.path.join(td, "not_quiet.log"))
1356 lm.logstart(os.path.join(td, "not_quiet.log"))
1355 finally:
1357 finally:
1356 _ip.logger.logstop()
1358 _ip.logger.logstop()
1357
1359
1358
1360
1359 def test_time_no_var_expand():
1361 def test_time_no_var_expand():
1360 _ip.user_ns['a'] = 5
1362 _ip.user_ns['a'] = 5
1361 _ip.user_ns['b'] = []
1363 _ip.user_ns['b'] = []
1362 _ip.magic('time b.append("{a}")')
1364 _ip.magic('time b.append("{a}")')
1363 assert _ip.user_ns['b'] == ['{a}']
1365 assert _ip.user_ns['b'] == ['{a}']
1364
1366
1365
1367
1366 # this is slow, put at the end for local testing.
1368 # this is slow, put at the end for local testing.
1367 def test_timeit_arguments():
1369 def test_timeit_arguments():
1368 "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
1370 "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
1369 _ip.magic("timeit -n1 -r1 a=('#')")
1371 _ip.magic("timeit -n1 -r1 a=('#')")
1370
1372
1371
1373
1372 MINIMAL_LAZY_MAGIC = """
1374 MINIMAL_LAZY_MAGIC = """
1373 from IPython.core.magic import (
1375 from IPython.core.magic import (
1374 Magics,
1376 Magics,
1375 magics_class,
1377 magics_class,
1376 line_magic,
1378 line_magic,
1377 cell_magic,
1379 cell_magic,
1378 )
1380 )
1379
1381
1380
1382
1381 @magics_class
1383 @magics_class
1382 class LazyMagics(Magics):
1384 class LazyMagics(Magics):
1383 @line_magic
1385 @line_magic
1384 def lazy_line(self, line):
1386 def lazy_line(self, line):
1385 print("Lazy Line")
1387 print("Lazy Line")
1386
1388
1387 @cell_magic
1389 @cell_magic
1388 def lazy_cell(self, line, cell):
1390 def lazy_cell(self, line, cell):
1389 print("Lazy Cell")
1391 print("Lazy Cell")
1390
1392
1391
1393
1392 def load_ipython_extension(ipython):
1394 def load_ipython_extension(ipython):
1393 ipython.register_magics(LazyMagics)
1395 ipython.register_magics(LazyMagics)
1394 """
1396 """
1395
1397
1396
1398
1397 def test_lazy_magics():
1399 def test_lazy_magics():
1398 with pytest.raises(UsageError):
1400 with pytest.raises(UsageError):
1399 ip.run_line_magic("lazy_line", "")
1401 ip.run_line_magic("lazy_line", "")
1400
1402
1401 startdir = os.getcwd()
1403 startdir = os.getcwd()
1402
1404
1403 with TemporaryDirectory() as tmpdir:
1405 with TemporaryDirectory() as tmpdir:
1404 with prepended_to_syspath(tmpdir):
1406 with prepended_to_syspath(tmpdir):
1405 ptempdir = Path(tmpdir)
1407 ptempdir = Path(tmpdir)
1406 tf = ptempdir / "lazy_magic_module.py"
1408 tf = ptempdir / "lazy_magic_module.py"
1407 tf.write_text(MINIMAL_LAZY_MAGIC)
1409 tf.write_text(MINIMAL_LAZY_MAGIC)
1408 ip.magics_manager.register_lazy("lazy_line", Path(tf.name).name[:-3])
1410 ip.magics_manager.register_lazy("lazy_line", Path(tf.name).name[:-3])
1409 with tt.AssertPrints("Lazy Line"):
1411 with tt.AssertPrints("Lazy Line"):
1410 ip.run_line_magic("lazy_line", "")
1412 ip.run_line_magic("lazy_line", "")
1411
1413
1412
1414
1413 TEST_MODULE = """
1415 TEST_MODULE = """
1414 print('Loaded my_tmp')
1416 print('Loaded my_tmp')
1415 if __name__ == "__main__":
1417 if __name__ == "__main__":
1416 print('I just ran a script')
1418 print('I just ran a script')
1417 """
1419 """
1418
1420
1419 def test_run_module_from_import_hook():
1421 def test_run_module_from_import_hook():
1420 "Test that a module can be loaded via an import hook"
1422 "Test that a module can be loaded via an import hook"
1421 with TemporaryDirectory() as tmpdir:
1423 with TemporaryDirectory() as tmpdir:
1422 fullpath = os.path.join(tmpdir, "my_tmp.py")
1424 fullpath = os.path.join(tmpdir, "my_tmp.py")
1423 Path(fullpath).write_text(TEST_MODULE, encoding="utf-8")
1425 Path(fullpath).write_text(TEST_MODULE, encoding="utf-8")
1424
1426
1425 import importlib.abc
1427 import importlib.abc
1426 import importlib.util
1428 import importlib.util
1427
1429
1428 class MyTempImporter(importlib.abc.MetaPathFinder, importlib.abc.SourceLoader):
1430 class MyTempImporter(importlib.abc.MetaPathFinder, importlib.abc.SourceLoader):
1429 def find_spec(self, fullname, path, target=None):
1431 def find_spec(self, fullname, path, target=None):
1430 if fullname == "my_tmp":
1432 if fullname == "my_tmp":
1431 return importlib.util.spec_from_loader(fullname, self)
1433 return importlib.util.spec_from_loader(fullname, self)
1432
1434
1433 def get_filename(self, fullname):
1435 def get_filename(self, fullname):
1434 assert fullname == "my_tmp"
1436 assert fullname == "my_tmp"
1435 return fullpath
1437 return fullpath
1436
1438
1437 def get_data(self, path):
1439 def get_data(self, path):
1438 assert Path(path).samefile(fullpath)
1440 assert Path(path).samefile(fullpath)
1439 return Path(fullpath).read_text(encoding="utf-8")
1441 return Path(fullpath).read_text(encoding="utf-8")
1440
1442
1441 sys.meta_path.insert(0, MyTempImporter())
1443 sys.meta_path.insert(0, MyTempImporter())
1442
1444
1443 with capture_output() as captured:
1445 with capture_output() as captured:
1444 _ip.magic("run -m my_tmp")
1446 _ip.magic("run -m my_tmp")
1445 _ip.run_cell("import my_tmp")
1447 _ip.run_cell("import my_tmp")
1446
1448
1447 output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n"
1449 output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n"
1448 assert output == captured.stdout
1450 assert output == captured.stdout
1449
1451
1450 sys.meta_path.pop(0)
1452 sys.meta_path.pop(0)
@@ -1,212 +1,216 b''
1 """Tests for various magic functions specific to the terminal frontend."""
1 """Tests for various magic functions specific to the terminal frontend."""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Imports
4 # Imports
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6
6
7 import sys
7 import sys
8 from io import StringIO
8 from io import StringIO
9 from unittest import TestCase
9 from unittest import TestCase
10
10
11 from IPython.testing import tools as tt
11 from IPython.testing import tools as tt
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Test functions begin
13 # Test functions begin
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16
16
17 MINIMAL_LAZY_MAGIC = """
17 MINIMAL_LAZY_MAGIC = """
18 from IPython.core.magic import (
18 from IPython.core.magic import (
19 Magics,
19 Magics,
20 magics_class,
20 magics_class,
21 line_magic,
21 line_magic,
22 cell_magic,
22 cell_magic,
23 )
23 )
24
24
25
25
26 @magics_class
26 @magics_class
27 class LazyMagics(Magics):
27 class LazyMagics(Magics):
28 @line_magic
28 @line_magic
29 def lazy_line(self, line):
29 def lazy_line(self, line):
30 print("Lazy Line")
30 print("Lazy Line")
31
31
32 @cell_magic
32 @cell_magic
33 def lazy_cell(self, line, cell):
33 def lazy_cell(self, line, cell):
34 print("Lazy Cell")
34 print("Lazy Cell")
35
35
36
36
37 def load_ipython_extension(ipython):
37 def load_ipython_extension(ipython):
38 ipython.register_magics(LazyMagics)
38 ipython.register_magics(LazyMagics)
39 """
39 """
40
40
41 def check_cpaste(code, should_fail=False):
41 def check_cpaste(code, should_fail=False):
42 """Execute code via 'cpaste' and ensure it was executed, unless
42 """Execute code via 'cpaste' and ensure it was executed, unless
43 should_fail is set.
43 should_fail is set.
44 """
44 """
45 ip.user_ns['code_ran'] = False
45 ip.user_ns['code_ran'] = False
46
46
47 src = StringIO()
47 src = StringIO()
48 src.write(code)
48 src.write(code)
49 src.write('\n--\n')
49 src.write('\n--\n')
50 src.seek(0)
50 src.seek(0)
51
51
52 stdin_save = sys.stdin
52 stdin_save = sys.stdin
53 sys.stdin = src
53 sys.stdin = src
54
54
55 try:
55 try:
56 context = tt.AssertPrints if should_fail else tt.AssertNotPrints
56 context = tt.AssertPrints if should_fail else tt.AssertNotPrints
57 with context("Traceback (most recent call last)"):
57 with context("Traceback (most recent call last)"):
58 ip.run_line_magic("cpaste", "")
58 ip.run_line_magic("cpaste", "")
59
59
60 if not should_fail:
60 if not should_fail:
61 assert ip.user_ns['code_ran'], "%r failed" % code
61 assert ip.user_ns['code_ran'], "%r failed" % code
62 finally:
62 finally:
63 sys.stdin = stdin_save
63 sys.stdin = stdin_save
64
64
65 def test_cpaste():
65 def test_cpaste():
66 """Test cpaste magic"""
66 """Test cpaste magic"""
67
67
68 def runf():
68 def runf():
69 """Marker function: sets a flag when executed.
69 """Marker function: sets a flag when executed.
70 """
70 """
71 ip.user_ns['code_ran'] = True
71 ip.user_ns['code_ran'] = True
72 return 'runf' # return string so '+ runf()' doesn't result in success
72 return 'runf' # return string so '+ runf()' doesn't result in success
73
73
74 tests = {'pass': ["runf()",
74 tests = {'pass': ["runf()",
75 "In [1]: runf()",
75 "In [1]: runf()",
76 "In [1]: if 1:\n ...: runf()",
76 "In [1]: if 1:\n ...: runf()",
77 "> > > runf()",
77 "> > > runf()",
78 ">>> runf()",
78 ">>> runf()",
79 " >>> runf()",
79 " >>> runf()",
80 ],
80 ],
81
81
82 'fail': ["1 + runf()",
82 'fail': ["1 + runf()",
83 "++ runf()",
83 "++ runf()",
84 ]}
84 ]}
85
85
86 ip.user_ns['runf'] = runf
86 ip.user_ns['runf'] = runf
87
87
88 for code in tests['pass']:
88 for code in tests['pass']:
89 check_cpaste(code)
89 check_cpaste(code)
90
90
91 for code in tests['fail']:
91 for code in tests['fail']:
92 check_cpaste(code, should_fail=True)
92 check_cpaste(code, should_fail=True)
93
93
94
94
95
95
96 class PasteTestCase(TestCase):
96 class PasteTestCase(TestCase):
97 """Multiple tests for clipboard pasting"""
97 """Multiple tests for clipboard pasting"""
98
98
99 def paste(self, txt, flags='-q'):
99 def paste(self, txt, flags='-q'):
100 """Paste input text, by default in quiet mode"""
100 """Paste input text, by default in quiet mode"""
101 ip.hooks.clipboard_get = lambda: txt
101 ip.hooks.clipboard_get = lambda: txt
102 ip.run_line_magic("paste", flags)
102 ip.run_line_magic("paste", flags)
103
103
104 def setUp(self):
104 def setUp(self):
105 # Inject fake clipboard hook but save original so we can restore it later
105 # Inject fake clipboard hook but save original so we can restore it later
106 self.original_clip = ip.hooks.clipboard_get
106 self.original_clip = ip.hooks.clipboard_get
107
107
108 def tearDown(self):
108 def tearDown(self):
109 # Restore original hook
109 # Restore original hook
110 ip.hooks.clipboard_get = self.original_clip
110 ip.hooks.clipboard_get = self.original_clip
111
111
112 def test_paste(self):
112 def test_paste(self):
113 ip.user_ns.pop("x", None)
113 ip.user_ns.pop("x", None)
114 self.paste("x = 1")
114 self.paste("x = 1")
115 self.assertEqual(ip.user_ns["x"], 1)
115 self.assertEqual(ip.user_ns["x"], 1)
116 ip.user_ns.pop("x")
116 ip.user_ns.pop("x")
117
117
118 def test_paste_pyprompt(self):
118 def test_paste_pyprompt(self):
119 ip.user_ns.pop("x", None)
119 ip.user_ns.pop("x", None)
120 self.paste(">>> x=2")
120 self.paste(">>> x=2")
121 self.assertEqual(ip.user_ns["x"], 2)
121 self.assertEqual(ip.user_ns["x"], 2)
122 ip.user_ns.pop("x")
122 ip.user_ns.pop("x")
123
123
124 def test_paste_py_multi(self):
124 def test_paste_py_multi(self):
125 self.paste("""
125 self.paste(
126 """
126 >>> x = [1,2,3]
127 >>> x = [1,2,3]
127 >>> y = []
128 >>> y = []
128 >>> for i in x:
129 >>> for i in x:
129 ... y.append(i**2)
130 ... y.append(i**2)
130 ...
131 ...
131 """
132 """
132 )
133 )
133 self.assertEqual(ip.user_ns["x"], [1, 2, 3])
134 self.assertEqual(ip.user_ns["x"], [1, 2, 3])
134 self.assertEqual(ip.user_ns["y"], [1, 4, 9])
135 self.assertEqual(ip.user_ns["y"], [1, 4, 9])
135
136
136 def test_paste_py_multi_r(self):
137 def test_paste_py_multi_r(self):
137 "Now, test that self.paste -r works"
138 "Now, test that self.paste -r works"
138 self.test_paste_py_multi()
139 self.test_paste_py_multi()
139 self.assertEqual(ip.user_ns.pop("x"), [1, 2, 3])
140 self.assertEqual(ip.user_ns.pop("x"), [1, 2, 3])
140 self.assertEqual(ip.user_ns.pop("y"), [1, 4, 9])
141 self.assertEqual(ip.user_ns.pop("y"), [1, 4, 9])
141 self.assertFalse("x" in ip.user_ns)
142 self.assertFalse("x" in ip.user_ns)
142 ip.run_line_magic("paste", "-r")
143 ip.run_line_magic("paste", "-r")
143 self.assertEqual(ip.user_ns["x"], [1, 2, 3])
144 self.assertEqual(ip.user_ns["x"], [1, 2, 3])
144 self.assertEqual(ip.user_ns["y"], [1, 4, 9])
145 self.assertEqual(ip.user_ns["y"], [1, 4, 9])
145
146
146 def test_paste_email(self):
147 def test_paste_email(self):
147 "Test pasting of email-quoted contents"
148 "Test pasting of email-quoted contents"
148 self.paste("""\
149 self.paste(
150 """\
149 >> def foo(x):
151 >> def foo(x):
150 >> return x + 1
152 >> return x + 1
151 >> xx = foo(1.1)"""
153 >> xx = foo(1.1)"""
152 )
154 )
153 self.assertEqual(ip.user_ns["xx"], 2.1)
155 self.assertEqual(ip.user_ns["xx"], 2.1)
154
156
155 def test_paste_email2(self):
157 def test_paste_email2(self):
156 "Email again; some programs add a space also at each quoting level"
158 "Email again; some programs add a space also at each quoting level"
157 self.paste("""\
159 self.paste(
160 """\
158 > > def foo(x):
161 > > def foo(x):
159 > > return x + 1
162 > > return x + 1
160 > > yy = foo(2.1) """
163 > > yy = foo(2.1) """
161 )
164 )
162 self.assertEqual(ip.user_ns["yy"], 3.1)
165 self.assertEqual(ip.user_ns["yy"], 3.1)
163
166
164 def test_paste_email_py(self):
167 def test_paste_email_py(self):
165 "Email quoting of interactive input"
168 "Email quoting of interactive input"
166 self.paste("""\
169 self.paste(
170 """\
167 >> >>> def f(x):
171 >> >>> def f(x):
168 >> ... return x+1
172 >> ... return x+1
169 >> ...
173 >> ...
170 >> >>> zz = f(2.5) """
174 >> >>> zz = f(2.5) """
171 )
175 )
172 self.assertEqual(ip.user_ns["zz"], 3.5)
176 self.assertEqual(ip.user_ns["zz"], 3.5)
173
177
174 def test_paste_echo(self):
178 def test_paste_echo(self):
175 "Also test self.paste echoing, by temporarily faking the writer"
179 "Also test self.paste echoing, by temporarily faking the writer"
176 w = StringIO()
180 w = StringIO()
177 old_write = sys.stdout.write
181 old_write = sys.stdout.write
178 sys.stdout.write = w.write
182 sys.stdout.write = w.write
179 code = """
183 code = """
180 a = 100
184 a = 100
181 b = 200"""
185 b = 200"""
182 try:
186 try:
183 self.paste(code,'')
187 self.paste(code,'')
184 out = w.getvalue()
188 out = w.getvalue()
185 finally:
189 finally:
186 sys.stdout.write = old_write
190 sys.stdout.write = old_write
187 self.assertEqual(ip.user_ns["a"], 100)
191 self.assertEqual(ip.user_ns["a"], 100)
188 self.assertEqual(ip.user_ns["b"], 200)
192 self.assertEqual(ip.user_ns["b"], 200)
189 assert out == code + "\n## -- End pasted text --\n"
193 assert out == code + "\n## -- End pasted text --\n"
190
194
191 def test_paste_leading_commas(self):
195 def test_paste_leading_commas(self):
192 "Test multiline strings with leading commas"
196 "Test multiline strings with leading commas"
193 tm = ip.magics_manager.registry['TerminalMagics']
197 tm = ip.magics_manager.registry['TerminalMagics']
194 s = '''\
198 s = '''\
195 a = """
199 a = """
196 ,1,2,3
200 ,1,2,3
197 """'''
201 """'''
198 ip.user_ns.pop("foo", None)
202 ip.user_ns.pop("foo", None)
199 tm.store_or_execute(s, "foo")
203 tm.store_or_execute(s, "foo")
200 self.assertIn("foo", ip.user_ns)
204 self.assertIn("foo", ip.user_ns)
201
205
202 def test_paste_trailing_question(self):
206 def test_paste_trailing_question(self):
203 "Test pasting sources with trailing question marks"
207 "Test pasting sources with trailing question marks"
204 tm = ip.magics_manager.registry['TerminalMagics']
208 tm = ip.magics_manager.registry['TerminalMagics']
205 s = '''\
209 s = '''\
206 def funcfoo():
210 def funcfoo():
207 if True: #am i true?
211 if True: #am i true?
208 return 'fooresult'
212 return 'fooresult'
209 '''
213 '''
210 ip.user_ns.pop('funcfoo', None)
214 ip.user_ns.pop('funcfoo', None)
211 self.paste(s)
215 self.paste(s)
212 self.assertEqual(ip.user_ns["funcfoo"](), "fooresult")
216 self.assertEqual(ip.user_ns["funcfoo"](), "fooresult")
@@ -1,155 +1,155 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Tests for profile-related functions.
2 """Tests for profile-related functions.
3
3
4 Currently only the startup-dir functionality is tested, but more tests should
4 Currently only the startup-dir functionality is tested, but more tests should
5 be added for:
5 be added for:
6
6
7 * ipython profile create
7 * ipython profile create
8 * ipython profile list
8 * ipython profile list
9 * ipython profile create --parallel
9 * ipython profile create --parallel
10 * security dir permissions
10 * security dir permissions
11
11
12 Authors
12 Authors
13 -------
13 -------
14
14
15 * MinRK
15 * MinRK
16
16
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import shutil
23 import shutil
24 import sys
24 import sys
25 import tempfile
25 import tempfile
26 from pathlib import Path
26 from pathlib import Path
27 from unittest import TestCase
27 from unittest import TestCase
28
28
29 from tempfile import TemporaryDirectory
29 from tempfile import TemporaryDirectory
30
30
31 from IPython.core.profileapp import list_bundled_profiles, list_profiles_in
31 from IPython.core.profileapp import list_bundled_profiles, list_profiles_in
32 from IPython.core.profiledir import ProfileDir
32 from IPython.core.profiledir import ProfileDir
33 from IPython.testing import decorators as dec
33 from IPython.testing import decorators as dec
34 from IPython.testing import tools as tt
34 from IPython.testing import tools as tt
35 from IPython.utils.process import getoutput
35 from IPython.utils.process import getoutput
36
36
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # Globals
38 # Globals
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 TMP_TEST_DIR = Path(tempfile.mkdtemp())
40 TMP_TEST_DIR = Path(tempfile.mkdtemp())
41 HOME_TEST_DIR = TMP_TEST_DIR / "home_test_dir"
41 HOME_TEST_DIR = TMP_TEST_DIR / "home_test_dir"
42 IP_TEST_DIR = HOME_TEST_DIR / ".ipython"
42 IP_TEST_DIR = HOME_TEST_DIR / ".ipython"
43
43
44 #
44 #
45 # Setup/teardown functions/decorators
45 # Setup/teardown functions/decorators
46 #
46 #
47
47
48 def setup_module():
48 def setup_module():
49 """Setup test environment for the module:
49 """Setup test environment for the module:
50
50
51 - Adds dummy home dir tree
51 - Adds dummy home dir tree
52 """
52 """
53 # Do not mask exceptions here. In particular, catching WindowsError is a
53 # Do not mask exceptions here. In particular, catching WindowsError is a
54 # problem because that exception is only defined on Windows...
54 # problem because that exception is only defined on Windows...
55 (Path.cwd() / IP_TEST_DIR).mkdir(parents=True)
55 (Path.cwd() / IP_TEST_DIR).mkdir(parents=True)
56
56
57
57
58 def teardown_module():
58 def teardown_module():
59 """Teardown test environment for the module:
59 """Teardown test environment for the module:
60
60
61 - Remove dummy home dir tree
61 - Remove dummy home dir tree
62 """
62 """
63 # Note: we remove the parent test dir, which is the root of all test
63 # Note: we remove the parent test dir, which is the root of all test
64 # subdirs we may have created. Use shutil instead of os.removedirs, so
64 # subdirs we may have created. Use shutil instead of os.removedirs, so
65 # that non-empty directories are all recursively removed.
65 # that non-empty directories are all recursively removed.
66 shutil.rmtree(TMP_TEST_DIR)
66 shutil.rmtree(TMP_TEST_DIR)
67
67
68
68
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70 # Test functions
70 # Test functions
71 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
72 class ProfileStartupTest(TestCase):
72 class ProfileStartupTest(TestCase):
73 def setUp(self):
73 def setUp(self):
74 # create profile dir
74 # create profile dir
75 self.pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, "test")
75 self.pd = ProfileDir.create_profile_dir_by_name(IP_TEST_DIR, "test")
76 self.options = ["--ipython-dir", IP_TEST_DIR, "--profile", "test"]
76 self.options = ["--ipython-dir", IP_TEST_DIR, "--profile", "test"]
77 self.fname = TMP_TEST_DIR / "test.py"
77 self.fname = TMP_TEST_DIR / "test.py"
78
78
79 def tearDown(self):
79 def tearDown(self):
80 # We must remove this profile right away so its presence doesn't
80 # We must remove this profile right away so its presence doesn't
81 # confuse other tests.
81 # confuse other tests.
82 shutil.rmtree(self.pd.location)
82 shutil.rmtree(self.pd.location)
83
83
84 def init(self, startup_file, startup, test):
84 def init(self, startup_file, startup, test):
85 # write startup python file
85 # write startup python file
86 with open(Path(self.pd.startup_dir) / startup_file, "w", encoding="utf-8") as f:
86 with open(Path(self.pd.startup_dir) / startup_file, "w", encoding="utf-8") as f:
87 f.write(startup)
87 f.write(startup)
88 # write simple test file, to check that the startup file was run
88 # write simple test file, to check that the startup file was run
89 with open(self.fname, "w", encoding="utf-8") as f:
89 with open(self.fname, "w", encoding="utf-8") as f:
90 f.write(test)
90 f.write(test)
91
91
92 def validate(self, output):
92 def validate(self, output):
93 tt.ipexec_validate(self.fname, output, "", options=self.options)
93 tt.ipexec_validate(self.fname, output, "", options=self.options)
94
94
95 def test_startup_py(self):
95 def test_startup_py(self):
96 self.init('00-start.py', 'zzz=123\n', 'print(zzz)\n')
96 self.init('00-start.py', 'zzz=123\n', 'print(zzz)\n')
97 self.validate('123')
97 self.validate('123')
98
98
99 def test_startup_ipy(self):
99 def test_startup_ipy(self):
100 self.init('00-start.ipy', '%xmode plain\n', '')
100 self.init('00-start.ipy', '%xmode plain\n', '')
101 self.validate('Exception reporting mode: Plain')
101 self.validate('Exception reporting mode: Plain')
102
102
103
103
104 def test_list_profiles_in():
104 def test_list_profiles_in():
105 # No need to remove these directories and files, as they will get nuked in
105 # No need to remove these directories and files, as they will get nuked in
106 # the module-level teardown.
106 # the module-level teardown.
107 td = Path(tempfile.mkdtemp(dir=TMP_TEST_DIR))
107 td = Path(tempfile.mkdtemp(dir=TMP_TEST_DIR))
108 for name in ("profile_foo", "profile_hello", "not_a_profile"):
108 for name in ("profile_foo", "profile_hello", "not_a_profile"):
109 Path(td / name).mkdir(parents=True)
109 Path(td / name).mkdir(parents=True)
110 if dec.unicode_paths:
110 if dec.unicode_paths:
111 Path(td / u"profile_ΓΌnicode").mkdir(parents=True)
111 Path(td / "profile_ΓΌnicode").mkdir(parents=True)
112
112
113 with open(td / "profile_file", "w", encoding="utf-8") as f:
113 with open(td / "profile_file", "w", encoding="utf-8") as f:
114 f.write("I am not a profile directory")
114 f.write("I am not a profile directory")
115 profiles = list_profiles_in(td)
115 profiles = list_profiles_in(td)
116
116
117 # unicode normalization can turn u'ΓΌnicode' into u'u\0308nicode',
117 # unicode normalization can turn u'ΓΌnicode' into u'u\0308nicode',
118 # so only check for *nicode, and that creating a ProfileDir from the
118 # so only check for *nicode, and that creating a ProfileDir from the
119 # name remains valid
119 # name remains valid
120 found_unicode = False
120 found_unicode = False
121 for p in list(profiles):
121 for p in list(profiles):
122 if p.endswith('nicode'):
122 if p.endswith('nicode'):
123 pd = ProfileDir.find_profile_dir_by_name(td, p)
123 pd = ProfileDir.find_profile_dir_by_name(td, p)
124 profiles.remove(p)
124 profiles.remove(p)
125 found_unicode = True
125 found_unicode = True
126 break
126 break
127 if dec.unicode_paths:
127 if dec.unicode_paths:
128 assert found_unicode is True
128 assert found_unicode is True
129 assert set(profiles) == {"foo", "hello"}
129 assert set(profiles) == {"foo", "hello"}
130
130
131
131
132 def test_list_bundled_profiles():
132 def test_list_bundled_profiles():
133 # This variable will need to be updated when a new profile gets bundled
133 # This variable will need to be updated when a new profile gets bundled
134 bundled = sorted(list_bundled_profiles())
134 bundled = sorted(list_bundled_profiles())
135 assert bundled == []
135 assert bundled == []
136
136
137
137
138 def test_profile_create_ipython_dir():
138 def test_profile_create_ipython_dir():
139 """ipython profile create respects --ipython-dir"""
139 """ipython profile create respects --ipython-dir"""
140 with TemporaryDirectory() as td:
140 with TemporaryDirectory() as td:
141 getoutput(
141 getoutput(
142 [
142 [
143 sys.executable,
143 sys.executable,
144 "-m",
144 "-m",
145 "IPython",
145 "IPython",
146 "profile",
146 "profile",
147 "create",
147 "create",
148 "foo",
148 "foo",
149 "--ipython-dir=%s" % td,
149 "--ipython-dir=%s" % td,
150 ]
150 ]
151 )
151 )
152 profile_dir = Path(td) / "profile_foo"
152 profile_dir = Path(td) / "profile_foo"
153 assert Path(profile_dir).exists()
153 assert Path(profile_dir).exists()
154 ipython_config = profile_dir / "ipython_config.py"
154 ipython_config = profile_dir / "ipython_config.py"
155 assert Path(ipython_config).exists()
155 assert Path(ipython_config).exists()
@@ -1,397 +1,399 b''
1 """
1 """
2 This module contains factory functions that attempt
2 This module contains factory functions that attempt
3 to return Qt submodules from the various python Qt bindings.
3 to return Qt submodules from the various python Qt bindings.
4
4
5 It also protects against double-importing Qt with different
5 It also protects against double-importing Qt with different
6 bindings, which is unstable and likely to crash
6 bindings, which is unstable and likely to crash
7
7
8 This is used primarily by qt and qt_for_kernel, and shouldn't
8 This is used primarily by qt and qt_for_kernel, and shouldn't
9 be accessed directly from the outside
9 be accessed directly from the outside
10 """
10 """
11 import importlib.abc
11 import importlib.abc
12 import sys
12 import sys
13 import types
13 import types
14 from functools import partial, lru_cache
14 from functools import partial, lru_cache
15 import operator
15 import operator
16
16
17 # ### Available APIs.
17 # ### Available APIs.
18 # Qt6
18 # Qt6
19 QT_API_PYQT6 = "pyqt6"
19 QT_API_PYQT6 = "pyqt6"
20 QT_API_PYSIDE6 = "pyside6"
20 QT_API_PYSIDE6 = "pyside6"
21
21
22 # Qt5
22 # Qt5
23 QT_API_PYQT5 = 'pyqt5'
23 QT_API_PYQT5 = 'pyqt5'
24 QT_API_PYSIDE2 = 'pyside2'
24 QT_API_PYSIDE2 = 'pyside2'
25
25
26 # Qt4
26 # Qt4
27 QT_API_PYQT = "pyqt" # Force version 2
27 QT_API_PYQT = "pyqt" # Force version 2
28 QT_API_PYQTv1 = "pyqtv1" # Force version 2
28 QT_API_PYQTv1 = "pyqtv1" # Force version 2
29 QT_API_PYSIDE = "pyside"
29 QT_API_PYSIDE = "pyside"
30
30
31 QT_API_PYQT_DEFAULT = "pyqtdefault" # use system default for version 1 vs. 2
31 QT_API_PYQT_DEFAULT = "pyqtdefault" # use system default for version 1 vs. 2
32
32
33 api_to_module = {
33 api_to_module = {
34 # Qt6
34 # Qt6
35 QT_API_PYQT6: "PyQt6",
35 QT_API_PYQT6: "PyQt6",
36 QT_API_PYSIDE6: "PySide6",
36 QT_API_PYSIDE6: "PySide6",
37 # Qt5
37 # Qt5
38 QT_API_PYQT5: "PyQt5",
38 QT_API_PYQT5: "PyQt5",
39 QT_API_PYSIDE2: "PySide2",
39 QT_API_PYSIDE2: "PySide2",
40 # Qt4
40 # Qt4
41 QT_API_PYSIDE: "PySide",
41 QT_API_PYSIDE: "PySide",
42 QT_API_PYQT: "PyQt4",
42 QT_API_PYQT: "PyQt4",
43 QT_API_PYQTv1: "PyQt4",
43 QT_API_PYQTv1: "PyQt4",
44 # default
44 # default
45 QT_API_PYQT_DEFAULT: "PyQt6",
45 QT_API_PYQT_DEFAULT: "PyQt6",
46 }
46 }
47
47
48
48
49 class ImportDenier(importlib.abc.MetaPathFinder):
49 class ImportDenier(importlib.abc.MetaPathFinder):
50 """Import Hook that will guard against bad Qt imports
50 """Import Hook that will guard against bad Qt imports
51 once IPython commits to a specific binding
51 once IPython commits to a specific binding
52 """
52 """
53
53
54 def __init__(self):
54 def __init__(self):
55 self.__forbidden = set()
55 self.__forbidden = set()
56
56
57 def forbid(self, module_name):
57 def forbid(self, module_name):
58 sys.modules.pop(module_name, None)
58 sys.modules.pop(module_name, None)
59 self.__forbidden.add(module_name)
59 self.__forbidden.add(module_name)
60
60
61 def find_spec(self, fullname, path, target=None):
61 def find_spec(self, fullname, path, target=None):
62 if path:
62 if path:
63 return
63 return
64 if fullname in self.__forbidden:
64 if fullname in self.__forbidden:
65 raise ImportError(
65 raise ImportError(
66 """
66 """
67 Importing %s disabled by IPython, which has
67 Importing %s disabled by IPython, which has
68 already imported an Incompatible QT Binding: %s
68 already imported an Incompatible QT Binding: %s
69 """ % (fullname, loaded_api()))
69 """
70 % (fullname, loaded_api())
71 )
70
72
71
73
72 ID = ImportDenier()
74 ID = ImportDenier()
73 sys.meta_path.insert(0, ID)
75 sys.meta_path.insert(0, ID)
74
76
75
77
76 def commit_api(api):
78 def commit_api(api):
77 """Commit to a particular API, and trigger ImportErrors on subsequent
79 """Commit to a particular API, and trigger ImportErrors on subsequent
78 dangerous imports"""
80 dangerous imports"""
79 modules = set(api_to_module.values())
81 modules = set(api_to_module.values())
80
82
81 modules.remove(api_to_module[api])
83 modules.remove(api_to_module[api])
82 for mod in modules:
84 for mod in modules:
83 ID.forbid(mod)
85 ID.forbid(mod)
84
86
85
87
86 def loaded_api():
88 def loaded_api():
87 """Return which API is loaded, if any
89 """Return which API is loaded, if any
88
90
89 If this returns anything besides None,
91 If this returns anything besides None,
90 importing any other Qt binding is unsafe.
92 importing any other Qt binding is unsafe.
91
93
92 Returns
94 Returns
93 -------
95 -------
94 None, 'pyside6', 'pyqt6', 'pyside2', 'pyside', 'pyqt', 'pyqt5', 'pyqtv1'
96 None, 'pyside6', 'pyqt6', 'pyside2', 'pyside', 'pyqt', 'pyqt5', 'pyqtv1'
95 """
97 """
96 if sys.modules.get("PyQt6.QtCore"):
98 if sys.modules.get("PyQt6.QtCore"):
97 return QT_API_PYQT6
99 return QT_API_PYQT6
98 elif sys.modules.get("PySide6.QtCore"):
100 elif sys.modules.get("PySide6.QtCore"):
99 return QT_API_PYSIDE6
101 return QT_API_PYSIDE6
100 elif sys.modules.get("PyQt5.QtCore"):
102 elif sys.modules.get("PyQt5.QtCore"):
101 return QT_API_PYQT5
103 return QT_API_PYQT5
102 elif sys.modules.get("PySide2.QtCore"):
104 elif sys.modules.get("PySide2.QtCore"):
103 return QT_API_PYSIDE2
105 return QT_API_PYSIDE2
104 elif sys.modules.get("PyQt4.QtCore"):
106 elif sys.modules.get("PyQt4.QtCore"):
105 if qtapi_version() == 2:
107 if qtapi_version() == 2:
106 return QT_API_PYQT
108 return QT_API_PYQT
107 else:
109 else:
108 return QT_API_PYQTv1
110 return QT_API_PYQTv1
109 elif sys.modules.get("PySide.QtCore"):
111 elif sys.modules.get("PySide.QtCore"):
110 return QT_API_PYSIDE
112 return QT_API_PYSIDE
111
113
112 return None
114 return None
113
115
114
116
115 def has_binding(api):
117 def has_binding(api):
116 """Safely check for PyQt4/5, PySide or PySide2, without importing submodules
118 """Safely check for PyQt4/5, PySide or PySide2, without importing submodules
117
119
118 Parameters
120 Parameters
119 ----------
121 ----------
120 api : str [ 'pyqtv1' | 'pyqt' | 'pyqt5' | 'pyside' | 'pyside2' | 'pyqtdefault']
122 api : str [ 'pyqtv1' | 'pyqt' | 'pyqt5' | 'pyside' | 'pyside2' | 'pyqtdefault']
121 Which module to check for
123 Which module to check for
122
124
123 Returns
125 Returns
124 -------
126 -------
125 True if the relevant module appears to be importable
127 True if the relevant module appears to be importable
126 """
128 """
127 module_name = api_to_module[api]
129 module_name = api_to_module[api]
128 from importlib.util import find_spec
130 from importlib.util import find_spec
129
131
130 required = ['QtCore', 'QtGui', 'QtSvg']
132 required = ['QtCore', 'QtGui', 'QtSvg']
131 if api in (QT_API_PYQT5, QT_API_PYSIDE2, QT_API_PYQT6, QT_API_PYSIDE6):
133 if api in (QT_API_PYQT5, QT_API_PYSIDE2, QT_API_PYQT6, QT_API_PYSIDE6):
132 # QT5 requires QtWidgets too
134 # QT5 requires QtWidgets too
133 required.append('QtWidgets')
135 required.append('QtWidgets')
134
136
135 for submod in required:
137 for submod in required:
136 try:
138 try:
137 spec = find_spec('%s.%s' % (module_name, submod))
139 spec = find_spec('%s.%s' % (module_name, submod))
138 except ImportError:
140 except ImportError:
139 # Package (e.g. PyQt5) not found
141 # Package (e.g. PyQt5) not found
140 return False
142 return False
141 else:
143 else:
142 if spec is None:
144 if spec is None:
143 # Submodule (e.g. PyQt5.QtCore) not found
145 # Submodule (e.g. PyQt5.QtCore) not found
144 return False
146 return False
145
147
146 if api == QT_API_PYSIDE:
148 if api == QT_API_PYSIDE:
147 # We can also safely check PySide version
149 # We can also safely check PySide version
148 import PySide
150 import PySide
149
151
150 return PySide.__version_info__ >= (1, 0, 3)
152 return PySide.__version_info__ >= (1, 0, 3)
151
153
152 return True
154 return True
153
155
154
156
155 def qtapi_version():
157 def qtapi_version():
156 """Return which QString API has been set, if any
158 """Return which QString API has been set, if any
157
159
158 Returns
160 Returns
159 -------
161 -------
160 The QString API version (1 or 2), or None if not set
162 The QString API version (1 or 2), or None if not set
161 """
163 """
162 try:
164 try:
163 import sip
165 import sip
164 except ImportError:
166 except ImportError:
165 # as of PyQt5 5.11, sip is no longer available as a top-level
167 # as of PyQt5 5.11, sip is no longer available as a top-level
166 # module and needs to be imported from the PyQt5 namespace
168 # module and needs to be imported from the PyQt5 namespace
167 try:
169 try:
168 from PyQt5 import sip
170 from PyQt5 import sip
169 except ImportError:
171 except ImportError:
170 return
172 return
171 try:
173 try:
172 return sip.getapi('QString')
174 return sip.getapi('QString')
173 except ValueError:
175 except ValueError:
174 return
176 return
175
177
176
178
177 def can_import(api):
179 def can_import(api):
178 """Safely query whether an API is importable, without importing it"""
180 """Safely query whether an API is importable, without importing it"""
179 if not has_binding(api):
181 if not has_binding(api):
180 return False
182 return False
181
183
182 current = loaded_api()
184 current = loaded_api()
183 if api == QT_API_PYQT_DEFAULT:
185 if api == QT_API_PYQT_DEFAULT:
184 return current in [QT_API_PYQT6, None]
186 return current in [QT_API_PYQT6, None]
185 else:
187 else:
186 return current in [api, None]
188 return current in [api, None]
187
189
188
190
189 def import_pyqt4(version=2):
191 def import_pyqt4(version=2):
190 """
192 """
191 Import PyQt4
193 Import PyQt4
192
194
193 Parameters
195 Parameters
194 ----------
196 ----------
195 version : 1, 2, or None
197 version : 1, 2, or None
196 Which QString/QVariant API to use. Set to None to use the system
198 Which QString/QVariant API to use. Set to None to use the system
197 default
199 default
198 ImportErrors raised within this function are non-recoverable
200 ImportErrors raised within this function are non-recoverable
199 """
201 """
200 # The new-style string API (version=2) automatically
202 # The new-style string API (version=2) automatically
201 # converts QStrings to Unicode Python strings. Also, automatically unpacks
203 # converts QStrings to Unicode Python strings. Also, automatically unpacks
202 # QVariants to their underlying objects.
204 # QVariants to their underlying objects.
203 import sip
205 import sip
204
206
205 if version is not None:
207 if version is not None:
206 sip.setapi('QString', version)
208 sip.setapi('QString', version)
207 sip.setapi('QVariant', version)
209 sip.setapi('QVariant', version)
208
210
209 from PyQt4 import QtGui, QtCore, QtSvg
211 from PyQt4 import QtGui, QtCore, QtSvg
210
212
211 if QtCore.PYQT_VERSION < 0x040700:
213 if QtCore.PYQT_VERSION < 0x040700:
212 raise ImportError("IPython requires PyQt4 >= 4.7, found %s" %
214 raise ImportError("IPython requires PyQt4 >= 4.7, found %s" %
213 QtCore.PYQT_VERSION_STR)
215 QtCore.PYQT_VERSION_STR)
214
216
215 # Alias PyQt-specific functions for PySide compatibility.
217 # Alias PyQt-specific functions for PySide compatibility.
216 QtCore.Signal = QtCore.pyqtSignal
218 QtCore.Signal = QtCore.pyqtSignal
217 QtCore.Slot = QtCore.pyqtSlot
219 QtCore.Slot = QtCore.pyqtSlot
218
220
219 # query for the API version (in case version == None)
221 # query for the API version (in case version == None)
220 version = sip.getapi('QString')
222 version = sip.getapi('QString')
221 api = QT_API_PYQTv1 if version == 1 else QT_API_PYQT
223 api = QT_API_PYQTv1 if version == 1 else QT_API_PYQT
222 return QtCore, QtGui, QtSvg, api
224 return QtCore, QtGui, QtSvg, api
223
225
224
226
225 def import_pyqt5():
227 def import_pyqt5():
226 """
228 """
227 Import PyQt5
229 Import PyQt5
228
230
229 ImportErrors raised within this function are non-recoverable
231 ImportErrors raised within this function are non-recoverable
230 """
232 """
231
233
232 from PyQt5 import QtCore, QtSvg, QtWidgets, QtGui
234 from PyQt5 import QtCore, QtSvg, QtWidgets, QtGui
233
235
234 # Alias PyQt-specific functions for PySide compatibility.
236 # Alias PyQt-specific functions for PySide compatibility.
235 QtCore.Signal = QtCore.pyqtSignal
237 QtCore.Signal = QtCore.pyqtSignal
236 QtCore.Slot = QtCore.pyqtSlot
238 QtCore.Slot = QtCore.pyqtSlot
237
239
238 # Join QtGui and QtWidgets for Qt4 compatibility.
240 # Join QtGui and QtWidgets for Qt4 compatibility.
239 QtGuiCompat = types.ModuleType('QtGuiCompat')
241 QtGuiCompat = types.ModuleType('QtGuiCompat')
240 QtGuiCompat.__dict__.update(QtGui.__dict__)
242 QtGuiCompat.__dict__.update(QtGui.__dict__)
241 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
243 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
242
244
243 api = QT_API_PYQT5
245 api = QT_API_PYQT5
244 return QtCore, QtGuiCompat, QtSvg, api
246 return QtCore, QtGuiCompat, QtSvg, api
245
247
246
248
247 def import_pyqt6():
249 def import_pyqt6():
248 """
250 """
249 Import PyQt6
251 Import PyQt6
250
252
251 ImportErrors raised within this function are non-recoverable
253 ImportErrors raised within this function are non-recoverable
252 """
254 """
253
255
254 from PyQt6 import QtCore, QtSvg, QtWidgets, QtGui
256 from PyQt6 import QtCore, QtSvg, QtWidgets, QtGui
255
257
256 # Alias PyQt-specific functions for PySide compatibility.
258 # Alias PyQt-specific functions for PySide compatibility.
257 QtCore.Signal = QtCore.pyqtSignal
259 QtCore.Signal = QtCore.pyqtSignal
258 QtCore.Slot = QtCore.pyqtSlot
260 QtCore.Slot = QtCore.pyqtSlot
259
261
260 # Join QtGui and QtWidgets for Qt4 compatibility.
262 # Join QtGui and QtWidgets for Qt4 compatibility.
261 QtGuiCompat = types.ModuleType("QtGuiCompat")
263 QtGuiCompat = types.ModuleType("QtGuiCompat")
262 QtGuiCompat.__dict__.update(QtGui.__dict__)
264 QtGuiCompat.__dict__.update(QtGui.__dict__)
263 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
265 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
264
266
265 api = QT_API_PYQT6
267 api = QT_API_PYQT6
266 return QtCore, QtGuiCompat, QtSvg, api
268 return QtCore, QtGuiCompat, QtSvg, api
267
269
268
270
269 def import_pyside():
271 def import_pyside():
270 """
272 """
271 Import PySide
273 Import PySide
272
274
273 ImportErrors raised within this function are non-recoverable
275 ImportErrors raised within this function are non-recoverable
274 """
276 """
275 from PySide import QtGui, QtCore, QtSvg
277 from PySide import QtGui, QtCore, QtSvg
276 return QtCore, QtGui, QtSvg, QT_API_PYSIDE
278 return QtCore, QtGui, QtSvg, QT_API_PYSIDE
277
279
278 def import_pyside2():
280 def import_pyside2():
279 """
281 """
280 Import PySide2
282 Import PySide2
281
283
282 ImportErrors raised within this function are non-recoverable
284 ImportErrors raised within this function are non-recoverable
283 """
285 """
284 from PySide2 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport
286 from PySide2 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport
285
287
286 # Join QtGui and QtWidgets for Qt4 compatibility.
288 # Join QtGui and QtWidgets for Qt4 compatibility.
287 QtGuiCompat = types.ModuleType('QtGuiCompat')
289 QtGuiCompat = types.ModuleType('QtGuiCompat')
288 QtGuiCompat.__dict__.update(QtGui.__dict__)
290 QtGuiCompat.__dict__.update(QtGui.__dict__)
289 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
291 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
290 QtGuiCompat.__dict__.update(QtPrintSupport.__dict__)
292 QtGuiCompat.__dict__.update(QtPrintSupport.__dict__)
291
293
292 return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE2
294 return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE2
293
295
294
296
295 def import_pyside6():
297 def import_pyside6():
296 """
298 """
297 Import PySide6
299 Import PySide6
298
300
299 ImportErrors raised within this function are non-recoverable
301 ImportErrors raised within this function are non-recoverable
300 """
302 """
301 from PySide6 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport
303 from PySide6 import QtGui, QtCore, QtSvg, QtWidgets, QtPrintSupport
302
304
303 # Join QtGui and QtWidgets for Qt4 compatibility.
305 # Join QtGui and QtWidgets for Qt4 compatibility.
304 QtGuiCompat = types.ModuleType("QtGuiCompat")
306 QtGuiCompat = types.ModuleType("QtGuiCompat")
305 QtGuiCompat.__dict__.update(QtGui.__dict__)
307 QtGuiCompat.__dict__.update(QtGui.__dict__)
306 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
308 QtGuiCompat.__dict__.update(QtWidgets.__dict__)
307 QtGuiCompat.__dict__.update(QtPrintSupport.__dict__)
309 QtGuiCompat.__dict__.update(QtPrintSupport.__dict__)
308
310
309 return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE6
311 return QtCore, QtGuiCompat, QtSvg, QT_API_PYSIDE6
310
312
311
313
312 def load_qt(api_options):
314 def load_qt(api_options):
313 """
315 """
314 Attempt to import Qt, given a preference list
316 Attempt to import Qt, given a preference list
315 of permissible bindings
317 of permissible bindings
316
318
317 It is safe to call this function multiple times.
319 It is safe to call this function multiple times.
318
320
319 Parameters
321 Parameters
320 ----------
322 ----------
321 api_options : List of strings
323 api_options : List of strings
322 The order of APIs to try. Valid items are 'pyside', 'pyside2',
324 The order of APIs to try. Valid items are 'pyside', 'pyside2',
323 'pyqt', 'pyqt5', 'pyqtv1' and 'pyqtdefault'
325 'pyqt', 'pyqt5', 'pyqtv1' and 'pyqtdefault'
324
326
325 Returns
327 Returns
326 -------
328 -------
327 A tuple of QtCore, QtGui, QtSvg, QT_API
329 A tuple of QtCore, QtGui, QtSvg, QT_API
328 The first three are the Qt modules. The last is the
330 The first three are the Qt modules. The last is the
329 string indicating which module was loaded.
331 string indicating which module was loaded.
330
332
331 Raises
333 Raises
332 ------
334 ------
333 ImportError, if it isn't possible to import any requested
335 ImportError, if it isn't possible to import any requested
334 bindings (either because they aren't installed, or because
336 bindings (either because they aren't installed, or because
335 an incompatible library has already been installed)
337 an incompatible library has already been installed)
336 """
338 """
337 loaders = {
339 loaders = {
338 # Qt6
340 # Qt6
339 QT_API_PYQT6: import_pyqt6,
341 QT_API_PYQT6: import_pyqt6,
340 QT_API_PYSIDE6: import_pyside6,
342 QT_API_PYSIDE6: import_pyside6,
341 # Qt5
343 # Qt5
342 QT_API_PYQT5: import_pyqt5,
344 QT_API_PYQT5: import_pyqt5,
343 QT_API_PYSIDE2: import_pyside2,
345 QT_API_PYSIDE2: import_pyside2,
344 # Qt4
346 # Qt4
345 QT_API_PYSIDE: import_pyside,
347 QT_API_PYSIDE: import_pyside,
346 QT_API_PYQT: import_pyqt4,
348 QT_API_PYQT: import_pyqt4,
347 QT_API_PYQTv1: partial(import_pyqt4, version=1),
349 QT_API_PYQTv1: partial(import_pyqt4, version=1),
348 # default
350 # default
349 QT_API_PYQT_DEFAULT: import_pyqt6,
351 QT_API_PYQT_DEFAULT: import_pyqt6,
350 }
352 }
351
353
352 for api in api_options:
354 for api in api_options:
353
355
354 if api not in loaders:
356 if api not in loaders:
355 raise RuntimeError(
357 raise RuntimeError(
356 "Invalid Qt API %r, valid values are: %s" %
358 "Invalid Qt API %r, valid values are: %s" %
357 (api, ", ".join(["%r" % k for k in loaders.keys()])))
359 (api, ", ".join(["%r" % k for k in loaders.keys()])))
358
360
359 if not can_import(api):
361 if not can_import(api):
360 continue
362 continue
361
363
362 #cannot safely recover from an ImportError during this
364 #cannot safely recover from an ImportError during this
363 result = loaders[api]()
365 result = loaders[api]()
364 api = result[-1] # changed if api = QT_API_PYQT_DEFAULT
366 api = result[-1] # changed if api = QT_API_PYQT_DEFAULT
365 commit_api(api)
367 commit_api(api)
366 return result
368 return result
367 else:
369 else:
368 raise ImportError("""
370 raise ImportError("""
369 Could not load requested Qt binding. Please ensure that
371 Could not load requested Qt binding. Please ensure that
370 PyQt4 >= 4.7, PyQt5, PySide >= 1.0.3 or PySide2 is available,
372 PyQt4 >= 4.7, PyQt5, PySide >= 1.0.3 or PySide2 is available,
371 and only one is imported per session.
373 and only one is imported per session.
372
374
373 Currently-imported Qt library: %r
375 Currently-imported Qt library: %r
374 PyQt4 available (requires QtCore, QtGui, QtSvg): %s
376 PyQt4 available (requires QtCore, QtGui, QtSvg): %s
375 PyQt5 available (requires QtCore, QtGui, QtSvg, QtWidgets): %s
377 PyQt5 available (requires QtCore, QtGui, QtSvg, QtWidgets): %s
376 PySide >= 1.0.3 installed: %s
378 PySide >= 1.0.3 installed: %s
377 PySide2 installed: %s
379 PySide2 installed: %s
378 Tried to load: %r
380 Tried to load: %r
379 """ % (loaded_api(),
381 """ % (loaded_api(),
380 has_binding(QT_API_PYQT),
382 has_binding(QT_API_PYQT),
381 has_binding(QT_API_PYQT5),
383 has_binding(QT_API_PYQT5),
382 has_binding(QT_API_PYSIDE),
384 has_binding(QT_API_PYSIDE),
383 has_binding(QT_API_PYSIDE2),
385 has_binding(QT_API_PYSIDE2),
384 api_options))
386 api_options))
385
387
386
388
387 def enum_factory(QT_API, QtCore):
389 def enum_factory(QT_API, QtCore):
388 """Construct an enum helper to account for PyQt5 <-> PyQt6 changes."""
390 """Construct an enum helper to account for PyQt5 <-> PyQt6 changes."""
389
391
390 @lru_cache(None)
392 @lru_cache(None)
391 def _enum(name):
393 def _enum(name):
392 # foo.bar.Enum.Entry (PyQt6) <=> foo.bar.Entry (non-PyQt6).
394 # foo.bar.Enum.Entry (PyQt6) <=> foo.bar.Entry (non-PyQt6).
393 return operator.attrgetter(
395 return operator.attrgetter(
394 name if QT_API == QT_API_PYQT6 else name.rpartition(".")[0]
396 name if QT_API == QT_API_PYQT6 else name.rpartition(".")[0]
395 )(sys.modules[QtCore.__package__])
397 )(sys.modules[QtCore.__package__])
396
398
397 return _enum
399 return _enum
@@ -1,677 +1,675 b''
1 """Various display related classes.
1 """Various display related classes.
2
2
3 Authors : MinRK, gregcaporaso, dannystaple
3 Authors : MinRK, gregcaporaso, dannystaple
4 """
4 """
5 from html import escape as html_escape
5 from html import escape as html_escape
6 from os.path import exists, isfile, splitext, abspath, join, isdir
6 from os.path import exists, isfile, splitext, abspath, join, isdir
7 from os import walk, sep, fsdecode
7 from os import walk, sep, fsdecode
8
8
9 from IPython.core.display import DisplayObject, TextDisplayObject
9 from IPython.core.display import DisplayObject, TextDisplayObject
10
10
11 from typing import Tuple, Iterable
11 from typing import Tuple, Iterable
12
12
13 __all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument',
13 __all__ = ['Audio', 'IFrame', 'YouTubeVideo', 'VimeoVideo', 'ScribdDocument',
14 'FileLink', 'FileLinks', 'Code']
14 'FileLink', 'FileLinks', 'Code']
15
15
16
16
17 class Audio(DisplayObject):
17 class Audio(DisplayObject):
18 """Create an audio object.
18 """Create an audio object.
19
19
20 When this object is returned by an input cell or passed to the
20 When this object is returned by an input cell or passed to the
21 display function, it will result in Audio controls being displayed
21 display function, it will result in Audio controls being displayed
22 in the frontend (only works in the notebook).
22 in the frontend (only works in the notebook).
23
23
24 Parameters
24 Parameters
25 ----------
25 ----------
26 data : numpy array, list, unicode, str or bytes
26 data : numpy array, list, unicode, str or bytes
27 Can be one of
27 Can be one of
28
28
29 * Numpy 1d array containing the desired waveform (mono)
29 * Numpy 1d array containing the desired waveform (mono)
30 * Numpy 2d array containing waveforms for each channel.
30 * Numpy 2d array containing waveforms for each channel.
31 Shape=(NCHAN, NSAMPLES). For the standard channel order, see
31 Shape=(NCHAN, NSAMPLES). For the standard channel order, see
32 http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
32 http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
33 * List of float or integer representing the waveform (mono)
33 * List of float or integer representing the waveform (mono)
34 * String containing the filename
34 * String containing the filename
35 * Bytestring containing raw PCM data or
35 * Bytestring containing raw PCM data or
36 * URL pointing to a file on the web.
36 * URL pointing to a file on the web.
37
37
38 If the array option is used, the waveform will be normalized.
38 If the array option is used, the waveform will be normalized.
39
39
40 If a filename or url is used, the format support will be browser
40 If a filename or url is used, the format support will be browser
41 dependent.
41 dependent.
42 url : unicode
42 url : unicode
43 A URL to download the data from.
43 A URL to download the data from.
44 filename : unicode
44 filename : unicode
45 Path to a local file to load the data from.
45 Path to a local file to load the data from.
46 embed : boolean
46 embed : boolean
47 Should the audio data be embedded using a data URI (True) or should
47 Should the audio data be embedded using a data URI (True) or should
48 the original source be referenced. Set this to True if you want the
48 the original source be referenced. Set this to True if you want the
49 audio to playable later with no internet connection in the notebook.
49 audio to playable later with no internet connection in the notebook.
50
50
51 Default is `True`, unless the keyword argument `url` is set, then
51 Default is `True`, unless the keyword argument `url` is set, then
52 default value is `False`.
52 default value is `False`.
53 rate : integer
53 rate : integer
54 The sampling rate of the raw data.
54 The sampling rate of the raw data.
55 Only required when data parameter is being used as an array
55 Only required when data parameter is being used as an array
56 autoplay : bool
56 autoplay : bool
57 Set to True if the audio should immediately start playing.
57 Set to True if the audio should immediately start playing.
58 Default is `False`.
58 Default is `False`.
59 normalize : bool
59 normalize : bool
60 Whether audio should be normalized (rescaled) to the maximum possible
60 Whether audio should be normalized (rescaled) to the maximum possible
61 range. Default is `True`. When set to `False`, `data` must be between
61 range. Default is `True`. When set to `False`, `data` must be between
62 -1 and 1 (inclusive), otherwise an error is raised.
62 -1 and 1 (inclusive), otherwise an error is raised.
63 Applies only when `data` is a list or array of samples; other types of
63 Applies only when `data` is a list or array of samples; other types of
64 audio are never normalized.
64 audio are never normalized.
65
65
66 Examples
66 Examples
67 --------
67 --------
68
68
69 >>> import pytest
69 >>> import pytest
70 >>> np = pytest.importorskip("numpy")
70 >>> np = pytest.importorskip("numpy")
71
71
72 Generate a sound
72 Generate a sound
73
73
74 >>> import numpy as np
74 >>> import numpy as np
75 >>> framerate = 44100
75 >>> framerate = 44100
76 >>> t = np.linspace(0,5,framerate*5)
76 >>> t = np.linspace(0,5,framerate*5)
77 >>> data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t)
77 >>> data = np.sin(2*np.pi*220*t) + np.sin(2*np.pi*224*t)
78 >>> Audio(data, rate=framerate)
78 >>> Audio(data, rate=framerate)
79 <IPython.lib.display.Audio object>
79 <IPython.lib.display.Audio object>
80
80
81 Can also do stereo or more channels
81 Can also do stereo or more channels
82
82
83 >>> dataleft = np.sin(2*np.pi*220*t)
83 >>> dataleft = np.sin(2*np.pi*220*t)
84 >>> dataright = np.sin(2*np.pi*224*t)
84 >>> dataright = np.sin(2*np.pi*224*t)
85 >>> Audio([dataleft, dataright], rate=framerate)
85 >>> Audio([dataleft, dataright], rate=framerate)
86 <IPython.lib.display.Audio object>
86 <IPython.lib.display.Audio object>
87
87
88 From URL:
88 From URL:
89
89
90 >>> Audio("http://www.nch.com.au/acm/8k16bitpcm.wav") # doctest: +SKIP
90 >>> Audio("http://www.nch.com.au/acm/8k16bitpcm.wav") # doctest: +SKIP
91 >>> Audio(url="http://www.w3schools.com/html/horse.ogg") # doctest: +SKIP
91 >>> Audio(url="http://www.w3schools.com/html/horse.ogg") # doctest: +SKIP
92
92
93 From a File:
93 From a File:
94
94
95 >>> Audio('IPython/lib/tests/test.wav') # doctest: +SKIP
95 >>> Audio('IPython/lib/tests/test.wav') # doctest: +SKIP
96 >>> Audio(filename='IPython/lib/tests/test.wav') # doctest: +SKIP
96 >>> Audio(filename='IPython/lib/tests/test.wav') # doctest: +SKIP
97
97
98 From Bytes:
98 From Bytes:
99
99
100 >>> Audio(b'RAW_WAV_DATA..') # doctest: +SKIP
100 >>> Audio(b'RAW_WAV_DATA..') # doctest: +SKIP
101 >>> Audio(data=b'RAW_WAV_DATA..') # doctest: +SKIP
101 >>> Audio(data=b'RAW_WAV_DATA..') # doctest: +SKIP
102
102
103 See Also
103 See Also
104 --------
104 --------
105 ipywidgets.Audio
105 ipywidgets.Audio
106
106
107 AUdio widget with more more flexibility and options.
107 Audio widget with more more flexibility and options.
108
108
109 """
109 """
110 _read_flags = 'rb'
110 _read_flags = 'rb'
111
111
112 def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, autoplay=False, normalize=True, *,
112 def __init__(self, data=None, filename=None, url=None, embed=None, rate=None, autoplay=False, normalize=True, *,
113 element_id=None):
113 element_id=None):
114 if filename is None and url is None and data is None:
114 if filename is None and url is None and data is None:
115 raise ValueError("No audio data found. Expecting filename, url, or data.")
115 raise ValueError("No audio data found. Expecting filename, url, or data.")
116 if embed is False and url is None:
116 if embed is False and url is None:
117 raise ValueError("No url found. Expecting url when embed=False")
117 raise ValueError("No url found. Expecting url when embed=False")
118
118
119 if url is not None and embed is not True:
119 if url is not None and embed is not True:
120 self.embed = False
120 self.embed = False
121 else:
121 else:
122 self.embed = True
122 self.embed = True
123 self.autoplay = autoplay
123 self.autoplay = autoplay
124 self.element_id = element_id
124 self.element_id = element_id
125 super(Audio, self).__init__(data=data, url=url, filename=filename)
125 super(Audio, self).__init__(data=data, url=url, filename=filename)
126
126
127 if self.data is not None and not isinstance(self.data, bytes):
127 if self.data is not None and not isinstance(self.data, bytes):
128 if rate is None:
128 if rate is None:
129 raise ValueError("rate must be specified when data is a numpy array or list of audio samples.")
129 raise ValueError("rate must be specified when data is a numpy array or list of audio samples.")
130 self.data = Audio._make_wav(data, rate, normalize)
130 self.data = Audio._make_wav(data, rate, normalize)
131
131
132 def reload(self):
132 def reload(self):
133 """Reload the raw data from file or URL."""
133 """Reload the raw data from file or URL."""
134 import mimetypes
134 import mimetypes
135 if self.embed:
135 if self.embed:
136 super(Audio, self).reload()
136 super(Audio, self).reload()
137
137
138 if self.filename is not None:
138 if self.filename is not None:
139 self.mimetype = mimetypes.guess_type(self.filename)[0]
139 self.mimetype = mimetypes.guess_type(self.filename)[0]
140 elif self.url is not None:
140 elif self.url is not None:
141 self.mimetype = mimetypes.guess_type(self.url)[0]
141 self.mimetype = mimetypes.guess_type(self.url)[0]
142 else:
142 else:
143 self.mimetype = "audio/wav"
143 self.mimetype = "audio/wav"
144
144
145 @staticmethod
145 @staticmethod
146 def _make_wav(data, rate, normalize):
146 def _make_wav(data, rate, normalize):
147 """ Transform a numpy array to a PCM bytestring """
147 """ Transform a numpy array to a PCM bytestring """
148 from io import BytesIO
148 from io import BytesIO
149 import wave
149 import wave
150
150
151 try:
151 try:
152 scaled, nchan = Audio._validate_and_normalize_with_numpy(data, normalize)
152 scaled, nchan = Audio._validate_and_normalize_with_numpy(data, normalize)
153 except ImportError:
153 except ImportError:
154 scaled, nchan = Audio._validate_and_normalize_without_numpy(data, normalize)
154 scaled, nchan = Audio._validate_and_normalize_without_numpy(data, normalize)
155
155
156 fp = BytesIO()
156 fp = BytesIO()
157 waveobj = wave.open(fp,mode='wb')
157 waveobj = wave.open(fp,mode='wb')
158 waveobj.setnchannels(nchan)
158 waveobj.setnchannels(nchan)
159 waveobj.setframerate(rate)
159 waveobj.setframerate(rate)
160 waveobj.setsampwidth(2)
160 waveobj.setsampwidth(2)
161 waveobj.setcomptype('NONE','NONE')
161 waveobj.setcomptype('NONE','NONE')
162 waveobj.writeframes(scaled)
162 waveobj.writeframes(scaled)
163 val = fp.getvalue()
163 val = fp.getvalue()
164 waveobj.close()
164 waveobj.close()
165
165
166 return val
166 return val
167
167
168 @staticmethod
168 @staticmethod
169 def _validate_and_normalize_with_numpy(data, normalize) -> Tuple[bytes, int]:
169 def _validate_and_normalize_with_numpy(data, normalize) -> Tuple[bytes, int]:
170 import numpy as np
170 import numpy as np
171
171
172 data = np.array(data, dtype=float)
172 data = np.array(data, dtype=float)
173 if len(data.shape) == 1:
173 if len(data.shape) == 1:
174 nchan = 1
174 nchan = 1
175 elif len(data.shape) == 2:
175 elif len(data.shape) == 2:
176 # In wave files,channels are interleaved. E.g.,
176 # In wave files,channels are interleaved. E.g.,
177 # "L1R1L2R2..." for stereo. See
177 # "L1R1L2R2..." for stereo. See
178 # http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
178 # http://msdn.microsoft.com/en-us/library/windows/hardware/dn653308(v=vs.85).aspx
179 # for channel ordering
179 # for channel ordering
180 nchan = data.shape[0]
180 nchan = data.shape[0]
181 data = data.T.ravel()
181 data = data.T.ravel()
182 else:
182 else:
183 raise ValueError('Array audio input must be a 1D or 2D array')
183 raise ValueError('Array audio input must be a 1D or 2D array')
184
184
185 max_abs_value = np.max(np.abs(data))
185 max_abs_value = np.max(np.abs(data))
186 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
186 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
187 scaled = data / normalization_factor * 32767
187 scaled = data / normalization_factor * 32767
188 return scaled.astype("<h").tobytes(), nchan
188 return scaled.astype("<h").tobytes(), nchan
189
189
190 @staticmethod
190 @staticmethod
191 def _validate_and_normalize_without_numpy(data, normalize):
191 def _validate_and_normalize_without_numpy(data, normalize):
192 import array
192 import array
193 import sys
193 import sys
194
194
195 data = array.array('f', data)
195 data = array.array('f', data)
196
196
197 try:
197 try:
198 max_abs_value = float(max([abs(x) for x in data]))
198 max_abs_value = float(max([abs(x) for x in data]))
199 except TypeError as e:
199 except TypeError as e:
200 raise TypeError('Only lists of mono audio are '
200 raise TypeError('Only lists of mono audio are '
201 'supported if numpy is not installed') from e
201 'supported if numpy is not installed') from e
202
202
203 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
203 normalization_factor = Audio._get_normalization_factor(max_abs_value, normalize)
204 scaled = array.array('h', [int(x / normalization_factor * 32767) for x in data])
204 scaled = array.array('h', [int(x / normalization_factor * 32767) for x in data])
205 if sys.byteorder == 'big':
205 if sys.byteorder == 'big':
206 scaled.byteswap()
206 scaled.byteswap()
207 nchan = 1
207 nchan = 1
208 return scaled.tobytes(), nchan
208 return scaled.tobytes(), nchan
209
209
210 @staticmethod
210 @staticmethod
211 def _get_normalization_factor(max_abs_value, normalize):
211 def _get_normalization_factor(max_abs_value, normalize):
212 if not normalize and max_abs_value > 1:
212 if not normalize and max_abs_value > 1:
213 raise ValueError('Audio data must be between -1 and 1 when normalize=False.')
213 raise ValueError('Audio data must be between -1 and 1 when normalize=False.')
214 return max_abs_value if normalize else 1
214 return max_abs_value if normalize else 1
215
215
216 def _data_and_metadata(self):
216 def _data_and_metadata(self):
217 """shortcut for returning metadata with url information, if defined"""
217 """shortcut for returning metadata with url information, if defined"""
218 md = {}
218 md = {}
219 if self.url:
219 if self.url:
220 md['url'] = self.url
220 md['url'] = self.url
221 if md:
221 if md:
222 return self.data, md
222 return self.data, md
223 else:
223 else:
224 return self.data
224 return self.data
225
225
226 def _repr_html_(self):
226 def _repr_html_(self):
227 src = """
227 src = """
228 <audio {element_id} controls="controls" {autoplay}>
228 <audio {element_id} controls="controls" {autoplay}>
229 <source src="{src}" type="{type}" />
229 <source src="{src}" type="{type}" />
230 Your browser does not support the audio element.
230 Your browser does not support the audio element.
231 </audio>
231 </audio>
232 """
232 """
233 return src.format(src=self.src_attr(), type=self.mimetype, autoplay=self.autoplay_attr(),
233 return src.format(src=self.src_attr(), type=self.mimetype, autoplay=self.autoplay_attr(),
234 element_id=self.element_id_attr())
234 element_id=self.element_id_attr())
235
235
236 def src_attr(self):
236 def src_attr(self):
237 import base64
237 import base64
238 if self.embed and (self.data is not None):
238 if self.embed and (self.data is not None):
239 data = base64=base64.b64encode(self.data).decode('ascii')
239 data = base64=base64.b64encode(self.data).decode('ascii')
240 return """data:{type};base64,{base64}""".format(type=self.mimetype,
240 return """data:{type};base64,{base64}""".format(type=self.mimetype,
241 base64=data)
241 base64=data)
242 elif self.url is not None:
242 elif self.url is not None:
243 return self.url
243 return self.url
244 else:
244 else:
245 return ""
245 return ""
246
246
247 def autoplay_attr(self):
247 def autoplay_attr(self):
248 if(self.autoplay):
248 if(self.autoplay):
249 return 'autoplay="autoplay"'
249 return 'autoplay="autoplay"'
250 else:
250 else:
251 return ''
251 return ''
252
252
253 def element_id_attr(self):
253 def element_id_attr(self):
254 if (self.element_id):
254 if (self.element_id):
255 return 'id="{element_id}"'.format(element_id=self.element_id)
255 return 'id="{element_id}"'.format(element_id=self.element_id)
256 else:
256 else:
257 return ''
257 return ''
258
258
259 class IFrame(object):
259 class IFrame(object):
260 """
260 """
261 Generic class to embed an iframe in an IPython notebook
261 Generic class to embed an iframe in an IPython notebook
262 """
262 """
263
263
264 iframe = """
264 iframe = """
265 <iframe
265 <iframe
266 width="{width}"
266 width="{width}"
267 height="{height}"
267 height="{height}"
268 src="{src}{params}"
268 src="{src}{params}"
269 frameborder="0"
269 frameborder="0"
270 allowfullscreen
270 allowfullscreen
271 {extras}
271 {extras}
272 ></iframe>
272 ></iframe>
273 """
273 """
274
274
275 def __init__(self, src, width, height, extras: Iterable[str] = None, **kwargs):
275 def __init__(self, src, width, height, extras: Iterable[str] = None, **kwargs):
276 if extras is None:
276 if extras is None:
277 extras = []
277 extras = []
278
278
279 self.src = src
279 self.src = src
280 self.width = width
280 self.width = width
281 self.height = height
281 self.height = height
282 self.extras = extras
282 self.extras = extras
283 self.params = kwargs
283 self.params = kwargs
284
284
285 def _repr_html_(self):
285 def _repr_html_(self):
286 """return the embed iframe"""
286 """return the embed iframe"""
287 if self.params:
287 if self.params:
288 from urllib.parse import urlencode
288 from urllib.parse import urlencode
289 params = "?" + urlencode(self.params)
289 params = "?" + urlencode(self.params)
290 else:
290 else:
291 params = ""
291 params = ""
292 return self.iframe.format(
292 return self.iframe.format(
293 src=self.src,
293 src=self.src,
294 width=self.width,
294 width=self.width,
295 height=self.height,
295 height=self.height,
296 params=params,
296 params=params,
297 extras=" ".join(self.extras),
297 extras=" ".join(self.extras),
298 )
298 )
299
299
300
300
301 class YouTubeVideo(IFrame):
301 class YouTubeVideo(IFrame):
302 """Class for embedding a YouTube Video in an IPython session, based on its video id.
302 """Class for embedding a YouTube Video in an IPython session, based on its video id.
303
303
304 e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would
304 e.g. to embed the video from https://www.youtube.com/watch?v=foo , you would
305 do::
305 do::
306
306
307 vid = YouTubeVideo("foo")
307 vid = YouTubeVideo("foo")
308 display(vid)
308 display(vid)
309
309
310 To start from 30 seconds::
310 To start from 30 seconds::
311
311
312 vid = YouTubeVideo("abc", start=30)
312 vid = YouTubeVideo("abc", start=30)
313 display(vid)
313 display(vid)
314
314
315 To calculate seconds from time as hours, minutes, seconds use
315 To calculate seconds from time as hours, minutes, seconds use
316 :class:`datetime.timedelta`::
316 :class:`datetime.timedelta`::
317
317
318 start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds())
318 start=int(timedelta(hours=1, minutes=46, seconds=40).total_seconds())
319
319
320 Other parameters can be provided as documented at
320 Other parameters can be provided as documented at
321 https://developers.google.com/youtube/player_parameters#Parameters
321 https://developers.google.com/youtube/player_parameters#Parameters
322
322
323 When converting the notebook using nbconvert, a jpeg representation of the video
323 When converting the notebook using nbconvert, a jpeg representation of the video
324 will be inserted in the document.
324 will be inserted in the document.
325 """
325 """
326
326
327 def __init__(self, id, width=400, height=300, allow_autoplay=False, **kwargs):
327 def __init__(self, id, width=400, height=300, allow_autoplay=False, **kwargs):
328 self.id=id
328 self.id=id
329 src = "https://www.youtube.com/embed/{0}".format(id)
329 src = "https://www.youtube.com/embed/{0}".format(id)
330 if allow_autoplay:
330 if allow_autoplay:
331 extras = list(kwargs.get("extras", [])) + ['allow="autoplay"']
331 extras = list(kwargs.get("extras", [])) + ['allow="autoplay"']
332 kwargs.update(autoplay=1, extras=extras)
332 kwargs.update(autoplay=1, extras=extras)
333 super(YouTubeVideo, self).__init__(src, width, height, **kwargs)
333 super(YouTubeVideo, self).__init__(src, width, height, **kwargs)
334
334
335 def _repr_jpeg_(self):
335 def _repr_jpeg_(self):
336 # Deferred import
336 # Deferred import
337 from urllib.request import urlopen
337 from urllib.request import urlopen
338
338
339 try:
339 try:
340 return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read()
340 return urlopen("https://img.youtube.com/vi/{id}/hqdefault.jpg".format(id=self.id)).read()
341 except IOError:
341 except IOError:
342 return None
342 return None
343
343
344 class VimeoVideo(IFrame):
344 class VimeoVideo(IFrame):
345 """
345 """
346 Class for embedding a Vimeo video in an IPython session, based on its video id.
346 Class for embedding a Vimeo video in an IPython session, based on its video id.
347 """
347 """
348
348
349 def __init__(self, id, width=400, height=300, **kwargs):
349 def __init__(self, id, width=400, height=300, **kwargs):
350 src="https://player.vimeo.com/video/{0}".format(id)
350 src="https://player.vimeo.com/video/{0}".format(id)
351 super(VimeoVideo, self).__init__(src, width, height, **kwargs)
351 super(VimeoVideo, self).__init__(src, width, height, **kwargs)
352
352
353 class ScribdDocument(IFrame):
353 class ScribdDocument(IFrame):
354 """
354 """
355 Class for embedding a Scribd document in an IPython session
355 Class for embedding a Scribd document in an IPython session
356
356
357 Use the start_page params to specify a starting point in the document
357 Use the start_page params to specify a starting point in the document
358 Use the view_mode params to specify display type one off scroll | slideshow | book
358 Use the view_mode params to specify display type one off scroll | slideshow | book
359
359
360 e.g to Display Wes' foundational paper about PANDAS in book mode from page 3
360 e.g to Display Wes' foundational paper about PANDAS in book mode from page 3
361
361
362 ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book")
362 ScribdDocument(71048089, width=800, height=400, start_page=3, view_mode="book")
363 """
363 """
364
364
365 def __init__(self, id, width=400, height=300, **kwargs):
365 def __init__(self, id, width=400, height=300, **kwargs):
366 src="https://www.scribd.com/embeds/{0}/content".format(id)
366 src="https://www.scribd.com/embeds/{0}/content".format(id)
367 super(ScribdDocument, self).__init__(src, width, height, **kwargs)
367 super(ScribdDocument, self).__init__(src, width, height, **kwargs)
368
368
369 class FileLink(object):
369 class FileLink(object):
370 """Class for embedding a local file link in an IPython session, based on path
370 """Class for embedding a local file link in an IPython session, based on path
371
371
372 e.g. to embed a link that was generated in the IPython notebook as my/data.txt
372 e.g. to embed a link that was generated in the IPython notebook as my/data.txt
373
373
374 you would do::
374 you would do::
375
375
376 local_file = FileLink("my/data.txt")
376 local_file = FileLink("my/data.txt")
377 display(local_file)
377 display(local_file)
378
378
379 or in the HTML notebook, just::
379 or in the HTML notebook, just::
380
380
381 FileLink("my/data.txt")
381 FileLink("my/data.txt")
382 """
382 """
383
383
384 html_link_str = "<a href='%s' target='_blank'>%s</a>"
384 html_link_str = "<a href='%s' target='_blank'>%s</a>"
385
385
386 def __init__(self,
386 def __init__(self,
387 path,
387 path,
388 url_prefix='',
388 url_prefix='',
389 result_html_prefix='',
389 result_html_prefix='',
390 result_html_suffix='<br>'):
390 result_html_suffix='<br>'):
391 """
391 """
392 Parameters
392 Parameters
393 ----------
393 ----------
394 path : str
394 path : str
395 path to the file or directory that should be formatted
395 path to the file or directory that should be formatted
396 url_prefix : str
396 url_prefix : str
397 prefix to be prepended to all files to form a working link [default:
397 prefix to be prepended to all files to form a working link [default:
398 '']
398 '']
399 result_html_prefix : str
399 result_html_prefix : str
400 text to append to beginning to link [default: '']
400 text to append to beginning to link [default: '']
401 result_html_suffix : str
401 result_html_suffix : str
402 text to append at the end of link [default: '<br>']
402 text to append at the end of link [default: '<br>']
403 """
403 """
404 if isdir(path):
404 if isdir(path):
405 raise ValueError("Cannot display a directory using FileLink. "
405 raise ValueError("Cannot display a directory using FileLink. "
406 "Use FileLinks to display '%s'." % path)
406 "Use FileLinks to display '%s'." % path)
407 self.path = fsdecode(path)
407 self.path = fsdecode(path)
408 self.url_prefix = url_prefix
408 self.url_prefix = url_prefix
409 self.result_html_prefix = result_html_prefix
409 self.result_html_prefix = result_html_prefix
410 self.result_html_suffix = result_html_suffix
410 self.result_html_suffix = result_html_suffix
411
411
412 def _format_path(self):
412 def _format_path(self):
413 fp = ''.join([self.url_prefix, html_escape(self.path)])
413 fp = ''.join([self.url_prefix, html_escape(self.path)])
414 return ''.join([self.result_html_prefix,
414 return ''.join([self.result_html_prefix,
415 self.html_link_str % \
415 self.html_link_str % \
416 (fp, html_escape(self.path, quote=False)),
416 (fp, html_escape(self.path, quote=False)),
417 self.result_html_suffix])
417 self.result_html_suffix])
418
418
419 def _repr_html_(self):
419 def _repr_html_(self):
420 """return html link to file
420 """return html link to file
421 """
421 """
422 if not exists(self.path):
422 if not exists(self.path):
423 return ("Path (<tt>%s</tt>) doesn't exist. "
423 return ("Path (<tt>%s</tt>) doesn't exist. "
424 "It may still be in the process of "
424 "It may still be in the process of "
425 "being generated, or you may have the "
425 "being generated, or you may have the "
426 "incorrect path." % self.path)
426 "incorrect path." % self.path)
427
427
428 return self._format_path()
428 return self._format_path()
429
429
430 def __repr__(self):
430 def __repr__(self):
431 """return absolute path to file
431 """return absolute path to file
432 """
432 """
433 return abspath(self.path)
433 return abspath(self.path)
434
434
435 class FileLinks(FileLink):
435 class FileLinks(FileLink):
436 """Class for embedding local file links in an IPython session, based on path
436 """Class for embedding local file links in an IPython session, based on path
437
437
438 e.g. to embed links to files that were generated in the IPython notebook
438 e.g. to embed links to files that were generated in the IPython notebook
439 under ``my/data``, you would do::
439 under ``my/data``, you would do::
440
440
441 local_files = FileLinks("my/data")
441 local_files = FileLinks("my/data")
442 display(local_files)
442 display(local_files)
443
443
444 or in the HTML notebook, just::
444 or in the HTML notebook, just::
445
445
446 FileLinks("my/data")
446 FileLinks("my/data")
447 """
447 """
448 def __init__(self,
448 def __init__(self,
449 path,
449 path,
450 url_prefix='',
450 url_prefix='',
451 included_suffixes=None,
451 included_suffixes=None,
452 result_html_prefix='',
452 result_html_prefix='',
453 result_html_suffix='<br>',
453 result_html_suffix='<br>',
454 notebook_display_formatter=None,
454 notebook_display_formatter=None,
455 terminal_display_formatter=None,
455 terminal_display_formatter=None,
456 recursive=True):
456 recursive=True):
457 """
457 """
458 See :class:`FileLink` for the ``path``, ``url_prefix``,
458 See :class:`FileLink` for the ``path``, ``url_prefix``,
459 ``result_html_prefix`` and ``result_html_suffix`` parameters.
459 ``result_html_prefix`` and ``result_html_suffix`` parameters.
460
460
461 included_suffixes : list
461 included_suffixes : list
462 Filename suffixes to include when formatting output [default: include
462 Filename suffixes to include when formatting output [default: include
463 all files]
463 all files]
464
464
465 notebook_display_formatter : function
465 notebook_display_formatter : function
466 Used to format links for display in the notebook. See discussion of
466 Used to format links for display in the notebook. See discussion of
467 formatter functions below.
467 formatter functions below.
468
468
469 terminal_display_formatter : function
469 terminal_display_formatter : function
470 Used to format links for display in the terminal. See discussion of
470 Used to format links for display in the terminal. See discussion of
471 formatter functions below.
471 formatter functions below.
472
472
473 Formatter functions must be of the form::
473 Formatter functions must be of the form::
474
474
475 f(dirname, fnames, included_suffixes)
475 f(dirname, fnames, included_suffixes)
476
476
477 dirname : str
477 dirname : str
478 The name of a directory
478 The name of a directory
479 fnames : list
479 fnames : list
480 The files in that directory
480 The files in that directory
481 included_suffixes : list
481 included_suffixes : list
482 The file suffixes that should be included in the output (passing None
482 The file suffixes that should be included in the output (passing None
483 meansto include all suffixes in the output in the built-in formatters)
483 meansto include all suffixes in the output in the built-in formatters)
484 recursive : boolean
484 recursive : boolean
485 Whether to recurse into subdirectories. Default is True.
485 Whether to recurse into subdirectories. Default is True.
486
486
487 The function should return a list of lines that will be printed in the
487 The function should return a list of lines that will be printed in the
488 notebook (if passing notebook_display_formatter) or the terminal (if
488 notebook (if passing notebook_display_formatter) or the terminal (if
489 passing terminal_display_formatter). This function is iterated over for
489 passing terminal_display_formatter). This function is iterated over for
490 each directory in self.path. Default formatters are in place, can be
490 each directory in self.path. Default formatters are in place, can be
491 passed here to support alternative formatting.
491 passed here to support alternative formatting.
492
492
493 """
493 """
494 if isfile(path):
494 if isfile(path):
495 raise ValueError("Cannot display a file using FileLinks. "
495 raise ValueError("Cannot display a file using FileLinks. "
496 "Use FileLink to display '%s'." % path)
496 "Use FileLink to display '%s'." % path)
497 self.included_suffixes = included_suffixes
497 self.included_suffixes = included_suffixes
498 # remove trailing slashes for more consistent output formatting
498 # remove trailing slashes for more consistent output formatting
499 path = path.rstrip('/')
499 path = path.rstrip('/')
500
500
501 self.path = path
501 self.path = path
502 self.url_prefix = url_prefix
502 self.url_prefix = url_prefix
503 self.result_html_prefix = result_html_prefix
503 self.result_html_prefix = result_html_prefix
504 self.result_html_suffix = result_html_suffix
504 self.result_html_suffix = result_html_suffix
505
505
506 self.notebook_display_formatter = \
506 self.notebook_display_formatter = \
507 notebook_display_formatter or self._get_notebook_display_formatter()
507 notebook_display_formatter or self._get_notebook_display_formatter()
508 self.terminal_display_formatter = \
508 self.terminal_display_formatter = \
509 terminal_display_formatter or self._get_terminal_display_formatter()
509 terminal_display_formatter or self._get_terminal_display_formatter()
510
510
511 self.recursive = recursive
511 self.recursive = recursive
512
512
513 def _get_display_formatter(self,
513 def _get_display_formatter(
514 dirname_output_format,
514 self, dirname_output_format, fname_output_format, fp_format, fp_cleaner=None
515 fname_output_format,
515 ):
516 fp_format,
517 fp_cleaner=None):
518 """ generate built-in formatter function
516 """generate built-in formatter function
519
517
520 this is used to define both the notebook and terminal built-in
518 this is used to define both the notebook and terminal built-in
521 formatters as they only differ by some wrapper text for each entry
519 formatters as they only differ by some wrapper text for each entry
522
520
523 dirname_output_format: string to use for formatting directory
521 dirname_output_format: string to use for formatting directory
524 names, dirname will be substituted for a single "%s" which
522 names, dirname will be substituted for a single "%s" which
525 must appear in this string
523 must appear in this string
526 fname_output_format: string to use for formatting file names,
524 fname_output_format: string to use for formatting file names,
527 if a single "%s" appears in the string, fname will be substituted
525 if a single "%s" appears in the string, fname will be substituted
528 if two "%s" appear in the string, the path to fname will be
526 if two "%s" appear in the string, the path to fname will be
529 substituted for the first and fname will be substituted for the
527 substituted for the first and fname will be substituted for the
530 second
528 second
531 fp_format: string to use for formatting filepaths, must contain
529 fp_format: string to use for formatting filepaths, must contain
532 exactly two "%s" and the dirname will be substituted for the first
530 exactly two "%s" and the dirname will be substituted for the first
533 and fname will be substituted for the second
531 and fname will be substituted for the second
534 """
532 """
535 def f(dirname, fnames, included_suffixes=None):
533 def f(dirname, fnames, included_suffixes=None):
536 result = []
534 result = []
537 # begin by figuring out which filenames, if any,
535 # begin by figuring out which filenames, if any,
538 # are going to be displayed
536 # are going to be displayed
539 display_fnames = []
537 display_fnames = []
540 for fname in fnames:
538 for fname in fnames:
541 if (isfile(join(dirname,fname)) and
539 if (isfile(join(dirname,fname)) and
542 (included_suffixes is None or
540 (included_suffixes is None or
543 splitext(fname)[1] in included_suffixes)):
541 splitext(fname)[1] in included_suffixes)):
544 display_fnames.append(fname)
542 display_fnames.append(fname)
545
543
546 if len(display_fnames) == 0:
544 if len(display_fnames) == 0:
547 # if there are no filenames to display, don't print anything
545 # if there are no filenames to display, don't print anything
548 # (not even the directory name)
546 # (not even the directory name)
549 pass
547 pass
550 else:
548 else:
551 # otherwise print the formatted directory name followed by
549 # otherwise print the formatted directory name followed by
552 # the formatted filenames
550 # the formatted filenames
553 dirname_output_line = dirname_output_format % dirname
551 dirname_output_line = dirname_output_format % dirname
554 result.append(dirname_output_line)
552 result.append(dirname_output_line)
555 for fname in display_fnames:
553 for fname in display_fnames:
556 fp = fp_format % (dirname,fname)
554 fp = fp_format % (dirname,fname)
557 if fp_cleaner is not None:
555 if fp_cleaner is not None:
558 fp = fp_cleaner(fp)
556 fp = fp_cleaner(fp)
559 try:
557 try:
560 # output can include both a filepath and a filename...
558 # output can include both a filepath and a filename...
561 fname_output_line = fname_output_format % (fp, fname)
559 fname_output_line = fname_output_format % (fp, fname)
562 except TypeError:
560 except TypeError:
563 # ... or just a single filepath
561 # ... or just a single filepath
564 fname_output_line = fname_output_format % fname
562 fname_output_line = fname_output_format % fname
565 result.append(fname_output_line)
563 result.append(fname_output_line)
566 return result
564 return result
567 return f
565 return f
568
566
569 def _get_notebook_display_formatter(self,
567 def _get_notebook_display_formatter(self,
570 spacer="&nbsp;&nbsp;"):
568 spacer="&nbsp;&nbsp;"):
571 """ generate function to use for notebook formatting
569 """ generate function to use for notebook formatting
572 """
570 """
573 dirname_output_format = \
571 dirname_output_format = \
574 self.result_html_prefix + "%s/" + self.result_html_suffix
572 self.result_html_prefix + "%s/" + self.result_html_suffix
575 fname_output_format = \
573 fname_output_format = \
576 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix
574 self.result_html_prefix + spacer + self.html_link_str + self.result_html_suffix
577 fp_format = self.url_prefix + '%s/%s'
575 fp_format = self.url_prefix + '%s/%s'
578 if sep == "\\":
576 if sep == "\\":
579 # Working on a platform where the path separator is "\", so
577 # Working on a platform where the path separator is "\", so
580 # must convert these to "/" for generating a URI
578 # must convert these to "/" for generating a URI
581 def fp_cleaner(fp):
579 def fp_cleaner(fp):
582 # Replace all occurrences of backslash ("\") with a forward
580 # Replace all occurrences of backslash ("\") with a forward
583 # slash ("/") - this is necessary on windows when a path is
581 # slash ("/") - this is necessary on windows when a path is
584 # provided as input, but we must link to a URI
582 # provided as input, but we must link to a URI
585 return fp.replace('\\','/')
583 return fp.replace('\\','/')
586 else:
584 else:
587 fp_cleaner = None
585 fp_cleaner = None
588
586
589 return self._get_display_formatter(dirname_output_format,
587 return self._get_display_formatter(dirname_output_format,
590 fname_output_format,
588 fname_output_format,
591 fp_format,
589 fp_format,
592 fp_cleaner)
590 fp_cleaner)
593
591
594 def _get_terminal_display_formatter(self,
592 def _get_terminal_display_formatter(self,
595 spacer=" "):
593 spacer=" "):
596 """ generate function to use for terminal formatting
594 """ generate function to use for terminal formatting
597 """
595 """
598 dirname_output_format = "%s/"
596 dirname_output_format = "%s/"
599 fname_output_format = spacer + "%s"
597 fname_output_format = spacer + "%s"
600 fp_format = '%s/%s'
598 fp_format = '%s/%s'
601
599
602 return self._get_display_formatter(dirname_output_format,
600 return self._get_display_formatter(dirname_output_format,
603 fname_output_format,
601 fname_output_format,
604 fp_format)
602 fp_format)
605
603
606 def _format_path(self):
604 def _format_path(self):
607 result_lines = []
605 result_lines = []
608 if self.recursive:
606 if self.recursive:
609 walked_dir = list(walk(self.path))
607 walked_dir = list(walk(self.path))
610 else:
608 else:
611 walked_dir = [next(walk(self.path))]
609 walked_dir = [next(walk(self.path))]
612 walked_dir.sort()
610 walked_dir.sort()
613 for dirname, subdirs, fnames in walked_dir:
611 for dirname, subdirs, fnames in walked_dir:
614 result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes)
612 result_lines += self.notebook_display_formatter(dirname, fnames, self.included_suffixes)
615 return '\n'.join(result_lines)
613 return '\n'.join(result_lines)
616
614
617 def __repr__(self):
615 def __repr__(self):
618 """return newline-separated absolute paths
616 """return newline-separated absolute paths
619 """
617 """
620 result_lines = []
618 result_lines = []
621 if self.recursive:
619 if self.recursive:
622 walked_dir = list(walk(self.path))
620 walked_dir = list(walk(self.path))
623 else:
621 else:
624 walked_dir = [next(walk(self.path))]
622 walked_dir = [next(walk(self.path))]
625 walked_dir.sort()
623 walked_dir.sort()
626 for dirname, subdirs, fnames in walked_dir:
624 for dirname, subdirs, fnames in walked_dir:
627 result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes)
625 result_lines += self.terminal_display_formatter(dirname, fnames, self.included_suffixes)
628 return '\n'.join(result_lines)
626 return '\n'.join(result_lines)
629
627
630
628
631 class Code(TextDisplayObject):
629 class Code(TextDisplayObject):
632 """Display syntax-highlighted source code.
630 """Display syntax-highlighted source code.
633
631
634 This uses Pygments to highlight the code for HTML and Latex output.
632 This uses Pygments to highlight the code for HTML and Latex output.
635
633
636 Parameters
634 Parameters
637 ----------
635 ----------
638 data : str
636 data : str
639 The code as a string
637 The code as a string
640 url : str
638 url : str
641 A URL to fetch the code from
639 A URL to fetch the code from
642 filename : str
640 filename : str
643 A local filename to load the code from
641 A local filename to load the code from
644 language : str
642 language : str
645 The short name of a Pygments lexer to use for highlighting.
643 The short name of a Pygments lexer to use for highlighting.
646 If not specified, it will guess the lexer based on the filename
644 If not specified, it will guess the lexer based on the filename
647 or the code. Available lexers: http://pygments.org/docs/lexers/
645 or the code. Available lexers: http://pygments.org/docs/lexers/
648 """
646 """
649 def __init__(self, data=None, url=None, filename=None, language=None):
647 def __init__(self, data=None, url=None, filename=None, language=None):
650 self.language = language
648 self.language = language
651 super().__init__(data=data, url=url, filename=filename)
649 super().__init__(data=data, url=url, filename=filename)
652
650
653 def _get_lexer(self):
651 def _get_lexer(self):
654 if self.language:
652 if self.language:
655 from pygments.lexers import get_lexer_by_name
653 from pygments.lexers import get_lexer_by_name
656 return get_lexer_by_name(self.language)
654 return get_lexer_by_name(self.language)
657 elif self.filename:
655 elif self.filename:
658 from pygments.lexers import get_lexer_for_filename
656 from pygments.lexers import get_lexer_for_filename
659 return get_lexer_for_filename(self.filename)
657 return get_lexer_for_filename(self.filename)
660 else:
658 else:
661 from pygments.lexers import guess_lexer
659 from pygments.lexers import guess_lexer
662 return guess_lexer(self.data)
660 return guess_lexer(self.data)
663
661
664 def __repr__(self):
662 def __repr__(self):
665 return self.data
663 return self.data
666
664
667 def _repr_html_(self):
665 def _repr_html_(self):
668 from pygments import highlight
666 from pygments import highlight
669 from pygments.formatters import HtmlFormatter
667 from pygments.formatters import HtmlFormatter
670 fmt = HtmlFormatter()
668 fmt = HtmlFormatter()
671 style = '<style>{}</style>'.format(fmt.get_style_defs('.output_html'))
669 style = '<style>{}</style>'.format(fmt.get_style_defs('.output_html'))
672 return style + highlight(self.data, self._get_lexer(), fmt)
670 return style + highlight(self.data, self._get_lexer(), fmt)
673
671
674 def _repr_latex_(self):
672 def _repr_latex_(self):
675 from pygments import highlight
673 from pygments import highlight
676 from pygments.formatters import LatexFormatter
674 from pygments.formatters import LatexFormatter
677 return highlight(self.data, self._get_lexer(), LatexFormatter())
675 return highlight(self.data, self._get_lexer(), LatexFormatter())
@@ -1,192 +1,192 b''
1 """Tests for IPython.utils.path.py"""
1 """Tests for IPython.utils.path.py"""
2 # Copyright (c) IPython Development Team.
2 # Copyright (c) IPython Development Team.
3 # Distributed under the terms of the Modified BSD License.
3 # Distributed under the terms of the Modified BSD License.
4
4
5 from contextlib import contextmanager
5 from contextlib import contextmanager
6 from unittest.mock import patch
6 from unittest.mock import patch
7
7
8 import pytest
8 import pytest
9
9
10 from IPython.lib import latextools
10 from IPython.lib import latextools
11 from IPython.testing.decorators import (
11 from IPython.testing.decorators import (
12 onlyif_cmds_exist,
12 onlyif_cmds_exist,
13 skipif_not_matplotlib,
13 skipif_not_matplotlib,
14 )
14 )
15 from IPython.utils.process import FindCmdError
15 from IPython.utils.process import FindCmdError
16
16
17
17
18 @pytest.mark.parametrize('command', ['latex', 'dvipng'])
18 @pytest.mark.parametrize('command', ['latex', 'dvipng'])
19 def test_check_latex_to_png_dvipng_fails_when_no_cmd(command):
19 def test_check_latex_to_png_dvipng_fails_when_no_cmd(command):
20 def mock_find_cmd(arg):
20 def mock_find_cmd(arg):
21 if arg == command:
21 if arg == command:
22 raise FindCmdError
22 raise FindCmdError
23
23
24 with patch.object(latextools, "find_cmd", mock_find_cmd):
24 with patch.object(latextools, "find_cmd", mock_find_cmd):
25 assert latextools.latex_to_png_dvipng("whatever", True) is None
25 assert latextools.latex_to_png_dvipng("whatever", True) is None
26
26
27
27
28 @contextmanager
28 @contextmanager
29 def no_op(*args, **kwargs):
29 def no_op(*args, **kwargs):
30 yield
30 yield
31
31
32
32
33 @onlyif_cmds_exist("latex", "dvipng")
33 @onlyif_cmds_exist("latex", "dvipng")
34 @pytest.mark.parametrize("s, wrap", [(u"$$x^2$$", False), (u"x^2", True)])
34 @pytest.mark.parametrize("s, wrap", [("$$x^2$$", False), ("x^2", True)])
35 def test_latex_to_png_dvipng_runs(s, wrap):
35 def test_latex_to_png_dvipng_runs(s, wrap):
36 """
36 """
37 Test that latex_to_png_dvipng just runs without error.
37 Test that latex_to_png_dvipng just runs without error.
38 """
38 """
39 def mock_kpsewhich(filename):
39 def mock_kpsewhich(filename):
40 assert filename == "breqn.sty"
40 assert filename == "breqn.sty"
41 return None
41 return None
42
42
43 latextools.latex_to_png_dvipng(s, wrap)
43 latextools.latex_to_png_dvipng(s, wrap)
44
44
45 with patch_latextool(mock_kpsewhich):
45 with patch_latextool(mock_kpsewhich):
46 latextools.latex_to_png_dvipng(s, wrap)
46 latextools.latex_to_png_dvipng(s, wrap)
47
47
48
48
49 def mock_kpsewhich(filename):
49 def mock_kpsewhich(filename):
50 assert filename == "breqn.sty"
50 assert filename == "breqn.sty"
51 return None
51 return None
52
52
53 @contextmanager
53 @contextmanager
54 def patch_latextool(mock=mock_kpsewhich):
54 def patch_latextool(mock=mock_kpsewhich):
55 with patch.object(latextools, "kpsewhich", mock):
55 with patch.object(latextools, "kpsewhich", mock):
56 yield
56 yield
57
57
58 @pytest.mark.parametrize('context', [no_op, patch_latextool])
58 @pytest.mark.parametrize('context', [no_op, patch_latextool])
59 @pytest.mark.parametrize('s_wrap', [("$x^2$", False), ("x^2", True)])
59 @pytest.mark.parametrize('s_wrap', [("$x^2$", False), ("x^2", True)])
60 def test_latex_to_png_mpl_runs(s_wrap, context):
60 def test_latex_to_png_mpl_runs(s_wrap, context):
61 """
61 """
62 Test that latex_to_png_mpl just runs without error.
62 Test that latex_to_png_mpl just runs without error.
63 """
63 """
64 try:
64 try:
65 import matplotlib
65 import matplotlib
66 except ImportError:
66 except ImportError:
67 pytest.skip("This needs matplotlib to be available")
67 pytest.skip("This needs matplotlib to be available")
68 return
68 return
69 s, wrap = s_wrap
69 s, wrap = s_wrap
70 with context():
70 with context():
71 latextools.latex_to_png_mpl(s, wrap)
71 latextools.latex_to_png_mpl(s, wrap)
72
72
73 @skipif_not_matplotlib
73 @skipif_not_matplotlib
74 def test_latex_to_html():
74 def test_latex_to_html():
75 img = latextools.latex_to_html("$x^2$")
75 img = latextools.latex_to_html("$x^2$")
76 assert "data:image/png;base64,iVBOR" in img
76 assert "data:image/png;base64,iVBOR" in img
77
77
78
78
79 def test_genelatex_no_wrap():
79 def test_genelatex_no_wrap():
80 """
80 """
81 Test genelatex with wrap=False.
81 Test genelatex with wrap=False.
82 """
82 """
83 def mock_kpsewhich(filename):
83 def mock_kpsewhich(filename):
84 assert False, ("kpsewhich should not be called "
84 assert False, ("kpsewhich should not be called "
85 "(called with {0})".format(filename))
85 "(called with {0})".format(filename))
86
86
87 with patch_latextool(mock_kpsewhich):
87 with patch_latextool(mock_kpsewhich):
88 assert '\n'.join(latextools.genelatex("body text", False)) == r'''\documentclass{article}
88 assert '\n'.join(latextools.genelatex("body text", False)) == r'''\documentclass{article}
89 \usepackage{amsmath}
89 \usepackage{amsmath}
90 \usepackage{amsthm}
90 \usepackage{amsthm}
91 \usepackage{amssymb}
91 \usepackage{amssymb}
92 \usepackage{bm}
92 \usepackage{bm}
93 \pagestyle{empty}
93 \pagestyle{empty}
94 \begin{document}
94 \begin{document}
95 body text
95 body text
96 \end{document}'''
96 \end{document}'''
97
97
98
98
99 def test_genelatex_wrap_with_breqn():
99 def test_genelatex_wrap_with_breqn():
100 """
100 """
101 Test genelatex with wrap=True for the case breqn.sty is installed.
101 Test genelatex with wrap=True for the case breqn.sty is installed.
102 """
102 """
103 def mock_kpsewhich(filename):
103 def mock_kpsewhich(filename):
104 assert filename == "breqn.sty"
104 assert filename == "breqn.sty"
105 return "path/to/breqn.sty"
105 return "path/to/breqn.sty"
106
106
107 with patch_latextool(mock_kpsewhich):
107 with patch_latextool(mock_kpsewhich):
108 assert '\n'.join(latextools.genelatex("x^2", True)) == r'''\documentclass{article}
108 assert '\n'.join(latextools.genelatex("x^2", True)) == r'''\documentclass{article}
109 \usepackage{amsmath}
109 \usepackage{amsmath}
110 \usepackage{amsthm}
110 \usepackage{amsthm}
111 \usepackage{amssymb}
111 \usepackage{amssymb}
112 \usepackage{bm}
112 \usepackage{bm}
113 \usepackage{breqn}
113 \usepackage{breqn}
114 \pagestyle{empty}
114 \pagestyle{empty}
115 \begin{document}
115 \begin{document}
116 \begin{dmath*}
116 \begin{dmath*}
117 x^2
117 x^2
118 \end{dmath*}
118 \end{dmath*}
119 \end{document}'''
119 \end{document}'''
120
120
121
121
122 def test_genelatex_wrap_without_breqn():
122 def test_genelatex_wrap_without_breqn():
123 """
123 """
124 Test genelatex with wrap=True for the case breqn.sty is not installed.
124 Test genelatex with wrap=True for the case breqn.sty is not installed.
125 """
125 """
126 def mock_kpsewhich(filename):
126 def mock_kpsewhich(filename):
127 assert filename == "breqn.sty"
127 assert filename == "breqn.sty"
128 return None
128 return None
129
129
130 with patch_latextool(mock_kpsewhich):
130 with patch_latextool(mock_kpsewhich):
131 assert '\n'.join(latextools.genelatex("x^2", True)) == r'''\documentclass{article}
131 assert '\n'.join(latextools.genelatex("x^2", True)) == r'''\documentclass{article}
132 \usepackage{amsmath}
132 \usepackage{amsmath}
133 \usepackage{amsthm}
133 \usepackage{amsthm}
134 \usepackage{amssymb}
134 \usepackage{amssymb}
135 \usepackage{bm}
135 \usepackage{bm}
136 \pagestyle{empty}
136 \pagestyle{empty}
137 \begin{document}
137 \begin{document}
138 $$x^2$$
138 $$x^2$$
139 \end{document}'''
139 \end{document}'''
140
140
141
141
142 @skipif_not_matplotlib
142 @skipif_not_matplotlib
143 @onlyif_cmds_exist('latex', 'dvipng')
143 @onlyif_cmds_exist('latex', 'dvipng')
144 def test_latex_to_png_color():
144 def test_latex_to_png_color():
145 """
145 """
146 Test color settings for latex_to_png.
146 Test color settings for latex_to_png.
147 """
147 """
148 latex_string = "$x^2$"
148 latex_string = "$x^2$"
149 default_value = latextools.latex_to_png(latex_string, wrap=False)
149 default_value = latextools.latex_to_png(latex_string, wrap=False)
150 default_hexblack = latextools.latex_to_png(latex_string, wrap=False,
150 default_hexblack = latextools.latex_to_png(latex_string, wrap=False,
151 color='#000000')
151 color='#000000')
152 dvipng_default = latextools.latex_to_png_dvipng(latex_string, False)
152 dvipng_default = latextools.latex_to_png_dvipng(latex_string, False)
153 dvipng_black = latextools.latex_to_png_dvipng(latex_string, False, 'Black')
153 dvipng_black = latextools.latex_to_png_dvipng(latex_string, False, 'Black')
154 assert dvipng_default == dvipng_black
154 assert dvipng_default == dvipng_black
155 mpl_default = latextools.latex_to_png_mpl(latex_string, False)
155 mpl_default = latextools.latex_to_png_mpl(latex_string, False)
156 mpl_black = latextools.latex_to_png_mpl(latex_string, False, 'Black')
156 mpl_black = latextools.latex_to_png_mpl(latex_string, False, 'Black')
157 assert mpl_default == mpl_black
157 assert mpl_default == mpl_black
158 assert default_value in [dvipng_black, mpl_black]
158 assert default_value in [dvipng_black, mpl_black]
159 assert default_hexblack in [dvipng_black, mpl_black]
159 assert default_hexblack in [dvipng_black, mpl_black]
160
160
161 # Test that dvips name colors can be used without error
161 # Test that dvips name colors can be used without error
162 dvipng_maroon = latextools.latex_to_png_dvipng(latex_string, False,
162 dvipng_maroon = latextools.latex_to_png_dvipng(latex_string, False,
163 'Maroon')
163 'Maroon')
164 # And that it doesn't return the black one
164 # And that it doesn't return the black one
165 assert dvipng_black != dvipng_maroon
165 assert dvipng_black != dvipng_maroon
166
166
167 mpl_maroon = latextools.latex_to_png_mpl(latex_string, False, 'Maroon')
167 mpl_maroon = latextools.latex_to_png_mpl(latex_string, False, 'Maroon')
168 assert mpl_black != mpl_maroon
168 assert mpl_black != mpl_maroon
169 mpl_white = latextools.latex_to_png_mpl(latex_string, False, 'White')
169 mpl_white = latextools.latex_to_png_mpl(latex_string, False, 'White')
170 mpl_hexwhite = latextools.latex_to_png_mpl(latex_string, False, '#FFFFFF')
170 mpl_hexwhite = latextools.latex_to_png_mpl(latex_string, False, '#FFFFFF')
171 assert mpl_white == mpl_hexwhite
171 assert mpl_white == mpl_hexwhite
172
172
173 mpl_white_scale = latextools.latex_to_png_mpl(latex_string, False,
173 mpl_white_scale = latextools.latex_to_png_mpl(latex_string, False,
174 'White', 1.2)
174 'White', 1.2)
175 assert mpl_white != mpl_white_scale
175 assert mpl_white != mpl_white_scale
176
176
177
177
178 def test_latex_to_png_invalid_hex_colors():
178 def test_latex_to_png_invalid_hex_colors():
179 """
179 """
180 Test that invalid hex colors provided to dvipng gives an exception.
180 Test that invalid hex colors provided to dvipng gives an exception.
181 """
181 """
182 latex_string = "$x^2$"
182 latex_string = "$x^2$"
183 pytest.raises(
183 pytest.raises(
184 ValueError,
184 ValueError,
185 lambda: latextools.latex_to_png(
185 lambda: latextools.latex_to_png(
186 latex_string, backend="dvipng", color="#f00bar"
186 latex_string, backend="dvipng", color="#f00bar"
187 ),
187 ),
188 )
188 )
189 pytest.raises(
189 pytest.raises(
190 ValueError,
190 ValueError,
191 lambda: latextools.latex_to_png(latex_string, backend="dvipng", color="#f00"),
191 lambda: latextools.latex_to_png(latex_string, backend="dvipng", color="#f00"),
192 )
192 )
@@ -1,536 +1,536 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Tests for IPython.lib.pretty."""
2 """Tests for IPython.lib.pretty."""
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 collections import Counter, defaultdict, deque, OrderedDict, UserList
8 from collections import Counter, defaultdict, deque, OrderedDict, UserList
9 import os
9 import os
10 import pytest
10 import pytest
11 import types
11 import types
12 import string
12 import string
13 import sys
13 import sys
14 import unittest
14 import unittest
15
15
16 import pytest
16 import pytest
17
17
18 from IPython.lib import pretty
18 from IPython.lib import pretty
19
19
20 from io import StringIO
20 from io import StringIO
21
21
22
22
23 class MyList(object):
23 class MyList(object):
24 def __init__(self, content):
24 def __init__(self, content):
25 self.content = content
25 self.content = content
26 def _repr_pretty_(self, p, cycle):
26 def _repr_pretty_(self, p, cycle):
27 if cycle:
27 if cycle:
28 p.text("MyList(...)")
28 p.text("MyList(...)")
29 else:
29 else:
30 with p.group(3, "MyList(", ")"):
30 with p.group(3, "MyList(", ")"):
31 for (i, child) in enumerate(self.content):
31 for (i, child) in enumerate(self.content):
32 if i:
32 if i:
33 p.text(",")
33 p.text(",")
34 p.breakable()
34 p.breakable()
35 else:
35 else:
36 p.breakable("")
36 p.breakable("")
37 p.pretty(child)
37 p.pretty(child)
38
38
39
39
40 class MyDict(dict):
40 class MyDict(dict):
41 def _repr_pretty_(self, p, cycle):
41 def _repr_pretty_(self, p, cycle):
42 p.text("MyDict(...)")
42 p.text("MyDict(...)")
43
43
44 class MyObj(object):
44 class MyObj(object):
45 def somemethod(self):
45 def somemethod(self):
46 pass
46 pass
47
47
48
48
49 class Dummy1(object):
49 class Dummy1(object):
50 def _repr_pretty_(self, p, cycle):
50 def _repr_pretty_(self, p, cycle):
51 p.text("Dummy1(...)")
51 p.text("Dummy1(...)")
52
52
53 class Dummy2(Dummy1):
53 class Dummy2(Dummy1):
54 _repr_pretty_ = None
54 _repr_pretty_ = None
55
55
56 class NoModule(object):
56 class NoModule(object):
57 pass
57 pass
58
58
59 NoModule.__module__ = None
59 NoModule.__module__ = None
60
60
61 class Breaking(object):
61 class Breaking(object):
62 def _repr_pretty_(self, p, cycle):
62 def _repr_pretty_(self, p, cycle):
63 with p.group(4,"TG: ",":"):
63 with p.group(4,"TG: ",":"):
64 p.text("Breaking(")
64 p.text("Breaking(")
65 p.break_()
65 p.break_()
66 p.text(")")
66 p.text(")")
67
67
68 class BreakingRepr(object):
68 class BreakingRepr(object):
69 def __repr__(self):
69 def __repr__(self):
70 return "Breaking(\n)"
70 return "Breaking(\n)"
71
71
72 class BadRepr(object):
72 class BadRepr(object):
73 def __repr__(self):
73 def __repr__(self):
74 return 1/0
74 return 1/0
75
75
76
76
77 def test_indentation():
77 def test_indentation():
78 """Test correct indentation in groups"""
78 """Test correct indentation in groups"""
79 count = 40
79 count = 40
80 gotoutput = pretty.pretty(MyList(range(count)))
80 gotoutput = pretty.pretty(MyList(range(count)))
81 expectedoutput = "MyList(\n" + ",\n".join(" %d" % i for i in range(count)) + ")"
81 expectedoutput = "MyList(\n" + ",\n".join(" %d" % i for i in range(count)) + ")"
82
82
83 assert gotoutput == expectedoutput
83 assert gotoutput == expectedoutput
84
84
85
85
86 def test_dispatch():
86 def test_dispatch():
87 """
87 """
88 Test correct dispatching: The _repr_pretty_ method for MyDict
88 Test correct dispatching: The _repr_pretty_ method for MyDict
89 must be found before the registered printer for dict.
89 must be found before the registered printer for dict.
90 """
90 """
91 gotoutput = pretty.pretty(MyDict())
91 gotoutput = pretty.pretty(MyDict())
92 expectedoutput = "MyDict(...)"
92 expectedoutput = "MyDict(...)"
93
93
94 assert gotoutput == expectedoutput
94 assert gotoutput == expectedoutput
95
95
96
96
97 def test_callability_checking():
97 def test_callability_checking():
98 """
98 """
99 Test that the _repr_pretty_ method is tested for callability and skipped if
99 Test that the _repr_pretty_ method is tested for callability and skipped if
100 not.
100 not.
101 """
101 """
102 gotoutput = pretty.pretty(Dummy2())
102 gotoutput = pretty.pretty(Dummy2())
103 expectedoutput = "Dummy1(...)"
103 expectedoutput = "Dummy1(...)"
104
104
105 assert gotoutput == expectedoutput
105 assert gotoutput == expectedoutput
106
106
107
107
108 @pytest.mark.parametrize(
108 @pytest.mark.parametrize(
109 "obj,expected_output",
109 "obj,expected_output",
110 zip(
110 zip(
111 [
111 [
112 set(),
112 set(),
113 frozenset(),
113 frozenset(),
114 set([1]),
114 set([1]),
115 frozenset([1]),
115 frozenset([1]),
116 set([1, 2]),
116 set([1, 2]),
117 frozenset([1, 2]),
117 frozenset([1, 2]),
118 set([-1, -2, -3]),
118 set([-1, -2, -3]),
119 ],
119 ],
120 [
120 [
121 "set()",
121 "set()",
122 "frozenset()",
122 "frozenset()",
123 "{1}",
123 "{1}",
124 "frozenset({1})",
124 "frozenset({1})",
125 "{1, 2}",
125 "{1, 2}",
126 "frozenset({1, 2})",
126 "frozenset({1, 2})",
127 "{-3, -2, -1}",
127 "{-3, -2, -1}",
128 ],
128 ],
129 ),
129 ),
130 )
130 )
131 def test_sets(obj, expected_output):
131 def test_sets(obj, expected_output):
132 """
132 """
133 Test that set and frozenset use Python 3 formatting.
133 Test that set and frozenset use Python 3 formatting.
134 """
134 """
135 got_output = pretty.pretty(obj)
135 got_output = pretty.pretty(obj)
136 assert got_output == expected_output
136 assert got_output == expected_output
137
137
138
138
139 def test_pprint_heap_allocated_type():
139 def test_pprint_heap_allocated_type():
140 """
140 """
141 Test that pprint works for heap allocated types.
141 Test that pprint works for heap allocated types.
142 """
142 """
143 module_name = "xxlimited" if sys.version_info < (3, 10) else "xxlimited_35"
143 module_name = "xxlimited" if sys.version_info < (3, 10) else "xxlimited_35"
144 xxlimited = pytest.importorskip(module_name)
144 xxlimited = pytest.importorskip(module_name)
145 output = pretty.pretty(xxlimited.Null)
145 output = pretty.pretty(xxlimited.Null)
146 assert output == "xxlimited.Null"
146 assert output == "xxlimited.Null"
147
147
148
148
149 def test_pprint_nomod():
149 def test_pprint_nomod():
150 """
150 """
151 Test that pprint works for classes with no __module__.
151 Test that pprint works for classes with no __module__.
152 """
152 """
153 output = pretty.pretty(NoModule)
153 output = pretty.pretty(NoModule)
154 assert output == "NoModule"
154 assert output == "NoModule"
155
155
156
156
157 def test_pprint_break():
157 def test_pprint_break():
158 """
158 """
159 Test that p.break_ produces expected output
159 Test that p.break_ produces expected output
160 """
160 """
161 output = pretty.pretty(Breaking())
161 output = pretty.pretty(Breaking())
162 expected = "TG: Breaking(\n ):"
162 expected = "TG: Breaking(\n ):"
163 assert output == expected
163 assert output == expected
164
164
165 def test_pprint_break_repr():
165 def test_pprint_break_repr():
166 """
166 """
167 Test that p.break_ is used in repr
167 Test that p.break_ is used in repr
168 """
168 """
169 output = pretty.pretty([[BreakingRepr()]])
169 output = pretty.pretty([[BreakingRepr()]])
170 expected = "[[Breaking(\n )]]"
170 expected = "[[Breaking(\n )]]"
171 assert output == expected
171 assert output == expected
172
172
173 output = pretty.pretty([[BreakingRepr()]*2])
173 output = pretty.pretty([[BreakingRepr()]*2])
174 expected = "[[Breaking(\n ),\n Breaking(\n )]]"
174 expected = "[[Breaking(\n ),\n Breaking(\n )]]"
175 assert output == expected
175 assert output == expected
176
176
177 def test_bad_repr():
177 def test_bad_repr():
178 """Don't catch bad repr errors"""
178 """Don't catch bad repr errors"""
179 with pytest.raises(ZeroDivisionError):
179 with pytest.raises(ZeroDivisionError):
180 pretty.pretty(BadRepr())
180 pretty.pretty(BadRepr())
181
181
182 class BadException(Exception):
182 class BadException(Exception):
183 def __str__(self):
183 def __str__(self):
184 return -1
184 return -1
185
185
186 class ReallyBadRepr(object):
186 class ReallyBadRepr(object):
187 __module__ = 1
187 __module__ = 1
188 @property
188 @property
189 def __class__(self):
189 def __class__(self):
190 raise ValueError("I am horrible")
190 raise ValueError("I am horrible")
191
191
192 def __repr__(self):
192 def __repr__(self):
193 raise BadException()
193 raise BadException()
194
194
195 def test_really_bad_repr():
195 def test_really_bad_repr():
196 with pytest.raises(BadException):
196 with pytest.raises(BadException):
197 pretty.pretty(ReallyBadRepr())
197 pretty.pretty(ReallyBadRepr())
198
198
199
199
200 class SA(object):
200 class SA(object):
201 pass
201 pass
202
202
203 class SB(SA):
203 class SB(SA):
204 pass
204 pass
205
205
206 class TestsPretty(unittest.TestCase):
206 class TestsPretty(unittest.TestCase):
207
207
208 def test_super_repr(self):
208 def test_super_repr(self):
209 # "<super: module_name.SA, None>"
209 # "<super: module_name.SA, None>"
210 output = pretty.pretty(super(SA))
210 output = pretty.pretty(super(SA))
211 self.assertRegex(output, r"<super: \S+.SA, None>")
211 self.assertRegex(output, r"<super: \S+.SA, None>")
212
212
213 # "<super: module_name.SA, <module_name.SB at 0x...>>"
213 # "<super: module_name.SA, <module_name.SB at 0x...>>"
214 sb = SB()
214 sb = SB()
215 output = pretty.pretty(super(SA, sb))
215 output = pretty.pretty(super(SA, sb))
216 self.assertRegex(output, r"<super: \S+.SA,\s+<\S+.SB at 0x\S+>>")
216 self.assertRegex(output, r"<super: \S+.SA,\s+<\S+.SB at 0x\S+>>")
217
217
218
218
219 def test_long_list(self):
219 def test_long_list(self):
220 lis = list(range(10000))
220 lis = list(range(10000))
221 p = pretty.pretty(lis)
221 p = pretty.pretty(lis)
222 last2 = p.rsplit('\n', 2)[-2:]
222 last2 = p.rsplit('\n', 2)[-2:]
223 self.assertEqual(last2, [' 999,', ' ...]'])
223 self.assertEqual(last2, [' 999,', ' ...]'])
224
224
225 def test_long_set(self):
225 def test_long_set(self):
226 s = set(range(10000))
226 s = set(range(10000))
227 p = pretty.pretty(s)
227 p = pretty.pretty(s)
228 last2 = p.rsplit('\n', 2)[-2:]
228 last2 = p.rsplit('\n', 2)[-2:]
229 self.assertEqual(last2, [' 999,', ' ...}'])
229 self.assertEqual(last2, [' 999,', ' ...}'])
230
230
231 def test_long_tuple(self):
231 def test_long_tuple(self):
232 tup = tuple(range(10000))
232 tup = tuple(range(10000))
233 p = pretty.pretty(tup)
233 p = pretty.pretty(tup)
234 last2 = p.rsplit('\n', 2)[-2:]
234 last2 = p.rsplit('\n', 2)[-2:]
235 self.assertEqual(last2, [' 999,', ' ...)'])
235 self.assertEqual(last2, [' 999,', ' ...)'])
236
236
237 def test_long_dict(self):
237 def test_long_dict(self):
238 d = { n:n for n in range(10000) }
238 d = { n:n for n in range(10000) }
239 p = pretty.pretty(d)
239 p = pretty.pretty(d)
240 last2 = p.rsplit('\n', 2)[-2:]
240 last2 = p.rsplit('\n', 2)[-2:]
241 self.assertEqual(last2, [' 999: 999,', ' ...}'])
241 self.assertEqual(last2, [' 999: 999,', ' ...}'])
242
242
243 def test_unbound_method(self):
243 def test_unbound_method(self):
244 output = pretty.pretty(MyObj.somemethod)
244 output = pretty.pretty(MyObj.somemethod)
245 self.assertIn('MyObj.somemethod', output)
245 self.assertIn('MyObj.somemethod', output)
246
246
247
247
248 class MetaClass(type):
248 class MetaClass(type):
249 def __new__(cls, name):
249 def __new__(cls, name):
250 return type.__new__(cls, name, (object,), {'name': name})
250 return type.__new__(cls, name, (object,), {'name': name})
251
251
252 def __repr__(self):
252 def __repr__(self):
253 return "[CUSTOM REPR FOR CLASS %s]" % self.name
253 return "[CUSTOM REPR FOR CLASS %s]" % self.name
254
254
255
255
256 ClassWithMeta = MetaClass('ClassWithMeta')
256 ClassWithMeta = MetaClass('ClassWithMeta')
257
257
258
258
259 def test_metaclass_repr():
259 def test_metaclass_repr():
260 output = pretty.pretty(ClassWithMeta)
260 output = pretty.pretty(ClassWithMeta)
261 assert output == "[CUSTOM REPR FOR CLASS ClassWithMeta]"
261 assert output == "[CUSTOM REPR FOR CLASS ClassWithMeta]"
262
262
263
263
264 def test_unicode_repr():
264 def test_unicode_repr():
265 u = u"üniçodé"
265 u = u"üniçodé"
266 ustr = u
266 ustr = u
267
267
268 class C(object):
268 class C(object):
269 def __repr__(self):
269 def __repr__(self):
270 return ustr
270 return ustr
271
271
272 c = C()
272 c = C()
273 p = pretty.pretty(c)
273 p = pretty.pretty(c)
274 assert p == u
274 assert p == u
275 p = pretty.pretty([c])
275 p = pretty.pretty([c])
276 assert p == u"[%s]" % u
276 assert p == "[%s]" % u
277
277
278
278
279 def test_basic_class():
279 def test_basic_class():
280 def type_pprint_wrapper(obj, p, cycle):
280 def type_pprint_wrapper(obj, p, cycle):
281 if obj is MyObj:
281 if obj is MyObj:
282 type_pprint_wrapper.called = True
282 type_pprint_wrapper.called = True
283 return pretty._type_pprint(obj, p, cycle)
283 return pretty._type_pprint(obj, p, cycle)
284 type_pprint_wrapper.called = False
284 type_pprint_wrapper.called = False
285
285
286 stream = StringIO()
286 stream = StringIO()
287 printer = pretty.RepresentationPrinter(stream)
287 printer = pretty.RepresentationPrinter(stream)
288 printer.type_pprinters[type] = type_pprint_wrapper
288 printer.type_pprinters[type] = type_pprint_wrapper
289 printer.pretty(MyObj)
289 printer.pretty(MyObj)
290 printer.flush()
290 printer.flush()
291 output = stream.getvalue()
291 output = stream.getvalue()
292
292
293 assert output == "%s.MyObj" % __name__
293 assert output == "%s.MyObj" % __name__
294 assert type_pprint_wrapper.called is True
294 assert type_pprint_wrapper.called is True
295
295
296
296
297 def test_collections_userlist():
297 def test_collections_userlist():
298 # Create userlist with cycle
298 # Create userlist with cycle
299 a = UserList()
299 a = UserList()
300 a.append(a)
300 a.append(a)
301
301
302 cases = [
302 cases = [
303 (UserList(), "UserList([])"),
303 (UserList(), "UserList([])"),
304 (
304 (
305 UserList(i for i in range(1000, 1020)),
305 UserList(i for i in range(1000, 1020)),
306 "UserList([1000,\n"
306 "UserList([1000,\n"
307 " 1001,\n"
307 " 1001,\n"
308 " 1002,\n"
308 " 1002,\n"
309 " 1003,\n"
309 " 1003,\n"
310 " 1004,\n"
310 " 1004,\n"
311 " 1005,\n"
311 " 1005,\n"
312 " 1006,\n"
312 " 1006,\n"
313 " 1007,\n"
313 " 1007,\n"
314 " 1008,\n"
314 " 1008,\n"
315 " 1009,\n"
315 " 1009,\n"
316 " 1010,\n"
316 " 1010,\n"
317 " 1011,\n"
317 " 1011,\n"
318 " 1012,\n"
318 " 1012,\n"
319 " 1013,\n"
319 " 1013,\n"
320 " 1014,\n"
320 " 1014,\n"
321 " 1015,\n"
321 " 1015,\n"
322 " 1016,\n"
322 " 1016,\n"
323 " 1017,\n"
323 " 1017,\n"
324 " 1018,\n"
324 " 1018,\n"
325 " 1019])",
325 " 1019])",
326 ),
326 ),
327 (a, "UserList([UserList(...)])"),
327 (a, "UserList([UserList(...)])"),
328 ]
328 ]
329 for obj, expected in cases:
329 for obj, expected in cases:
330 assert pretty.pretty(obj) == expected
330 assert pretty.pretty(obj) == expected
331
331
332
332
333 # TODO : pytest.mark.parametrise once nose is gone.
333 # TODO : pytest.mark.parametrise once nose is gone.
334 def test_collections_defaultdict():
334 def test_collections_defaultdict():
335 # Create defaultdicts with cycles
335 # Create defaultdicts with cycles
336 a = defaultdict()
336 a = defaultdict()
337 a.default_factory = a
337 a.default_factory = a
338 b = defaultdict(list)
338 b = defaultdict(list)
339 b['key'] = b
339 b['key'] = b
340
340
341 # Dictionary order cannot be relied on, test against single keys.
341 # Dictionary order cannot be relied on, test against single keys.
342 cases = [
342 cases = [
343 (defaultdict(list), 'defaultdict(list, {})'),
343 (defaultdict(list), 'defaultdict(list, {})'),
344 (defaultdict(list, {'key': '-' * 50}),
344 (defaultdict(list, {'key': '-' * 50}),
345 "defaultdict(list,\n"
345 "defaultdict(list,\n"
346 " {'key': '--------------------------------------------------'})"),
346 " {'key': '--------------------------------------------------'})"),
347 (a, 'defaultdict(defaultdict(...), {})'),
347 (a, 'defaultdict(defaultdict(...), {})'),
348 (b, "defaultdict(list, {'key': defaultdict(...)})"),
348 (b, "defaultdict(list, {'key': defaultdict(...)})"),
349 ]
349 ]
350 for obj, expected in cases:
350 for obj, expected in cases:
351 assert pretty.pretty(obj) == expected
351 assert pretty.pretty(obj) == expected
352
352
353
353
354 # TODO : pytest.mark.parametrise once nose is gone.
354 # TODO : pytest.mark.parametrise once nose is gone.
355 def test_collections_ordereddict():
355 def test_collections_ordereddict():
356 # Create OrderedDict with cycle
356 # Create OrderedDict with cycle
357 a = OrderedDict()
357 a = OrderedDict()
358 a['key'] = a
358 a['key'] = a
359
359
360 cases = [
360 cases = [
361 (OrderedDict(), 'OrderedDict()'),
361 (OrderedDict(), 'OrderedDict()'),
362 (OrderedDict((i, i) for i in range(1000, 1010)),
362 (OrderedDict((i, i) for i in range(1000, 1010)),
363 'OrderedDict([(1000, 1000),\n'
363 'OrderedDict([(1000, 1000),\n'
364 ' (1001, 1001),\n'
364 ' (1001, 1001),\n'
365 ' (1002, 1002),\n'
365 ' (1002, 1002),\n'
366 ' (1003, 1003),\n'
366 ' (1003, 1003),\n'
367 ' (1004, 1004),\n'
367 ' (1004, 1004),\n'
368 ' (1005, 1005),\n'
368 ' (1005, 1005),\n'
369 ' (1006, 1006),\n'
369 ' (1006, 1006),\n'
370 ' (1007, 1007),\n'
370 ' (1007, 1007),\n'
371 ' (1008, 1008),\n'
371 ' (1008, 1008),\n'
372 ' (1009, 1009)])'),
372 ' (1009, 1009)])'),
373 (a, "OrderedDict([('key', OrderedDict(...))])"),
373 (a, "OrderedDict([('key', OrderedDict(...))])"),
374 ]
374 ]
375 for obj, expected in cases:
375 for obj, expected in cases:
376 assert pretty.pretty(obj) == expected
376 assert pretty.pretty(obj) == expected
377
377
378
378
379 # TODO : pytest.mark.parametrise once nose is gone.
379 # TODO : pytest.mark.parametrise once nose is gone.
380 def test_collections_deque():
380 def test_collections_deque():
381 # Create deque with cycle
381 # Create deque with cycle
382 a = deque()
382 a = deque()
383 a.append(a)
383 a.append(a)
384
384
385 cases = [
385 cases = [
386 (deque(), 'deque([])'),
386 (deque(), 'deque([])'),
387 (deque(i for i in range(1000, 1020)),
387 (deque(i for i in range(1000, 1020)),
388 'deque([1000,\n'
388 'deque([1000,\n'
389 ' 1001,\n'
389 ' 1001,\n'
390 ' 1002,\n'
390 ' 1002,\n'
391 ' 1003,\n'
391 ' 1003,\n'
392 ' 1004,\n'
392 ' 1004,\n'
393 ' 1005,\n'
393 ' 1005,\n'
394 ' 1006,\n'
394 ' 1006,\n'
395 ' 1007,\n'
395 ' 1007,\n'
396 ' 1008,\n'
396 ' 1008,\n'
397 ' 1009,\n'
397 ' 1009,\n'
398 ' 1010,\n'
398 ' 1010,\n'
399 ' 1011,\n'
399 ' 1011,\n'
400 ' 1012,\n'
400 ' 1012,\n'
401 ' 1013,\n'
401 ' 1013,\n'
402 ' 1014,\n'
402 ' 1014,\n'
403 ' 1015,\n'
403 ' 1015,\n'
404 ' 1016,\n'
404 ' 1016,\n'
405 ' 1017,\n'
405 ' 1017,\n'
406 ' 1018,\n'
406 ' 1018,\n'
407 ' 1019])'),
407 ' 1019])'),
408 (a, 'deque([deque(...)])'),
408 (a, 'deque([deque(...)])'),
409 ]
409 ]
410 for obj, expected in cases:
410 for obj, expected in cases:
411 assert pretty.pretty(obj) == expected
411 assert pretty.pretty(obj) == expected
412
412
413
413
414 # TODO : pytest.mark.parametrise once nose is gone.
414 # TODO : pytest.mark.parametrise once nose is gone.
415 def test_collections_counter():
415 def test_collections_counter():
416 class MyCounter(Counter):
416 class MyCounter(Counter):
417 pass
417 pass
418 cases = [
418 cases = [
419 (Counter(), 'Counter()'),
419 (Counter(), 'Counter()'),
420 (Counter(a=1), "Counter({'a': 1})"),
420 (Counter(a=1), "Counter({'a': 1})"),
421 (MyCounter(a=1), "MyCounter({'a': 1})"),
421 (MyCounter(a=1), "MyCounter({'a': 1})"),
422 ]
422 ]
423 for obj, expected in cases:
423 for obj, expected in cases:
424 assert pretty.pretty(obj) == expected
424 assert pretty.pretty(obj) == expected
425
425
426 # TODO : pytest.mark.parametrise once nose is gone.
426 # TODO : pytest.mark.parametrise once nose is gone.
427 def test_mappingproxy():
427 def test_mappingproxy():
428 MP = types.MappingProxyType
428 MP = types.MappingProxyType
429 underlying_dict = {}
429 underlying_dict = {}
430 mp_recursive = MP(underlying_dict)
430 mp_recursive = MP(underlying_dict)
431 underlying_dict[2] = mp_recursive
431 underlying_dict[2] = mp_recursive
432 underlying_dict[3] = underlying_dict
432 underlying_dict[3] = underlying_dict
433
433
434 cases = [
434 cases = [
435 (MP({}), "mappingproxy({})"),
435 (MP({}), "mappingproxy({})"),
436 (MP({None: MP({})}), "mappingproxy({None: mappingproxy({})})"),
436 (MP({None: MP({})}), "mappingproxy({None: mappingproxy({})})"),
437 (MP({k: k.upper() for k in string.ascii_lowercase}),
437 (MP({k: k.upper() for k in string.ascii_lowercase}),
438 "mappingproxy({'a': 'A',\n"
438 "mappingproxy({'a': 'A',\n"
439 " 'b': 'B',\n"
439 " 'b': 'B',\n"
440 " 'c': 'C',\n"
440 " 'c': 'C',\n"
441 " 'd': 'D',\n"
441 " 'd': 'D',\n"
442 " 'e': 'E',\n"
442 " 'e': 'E',\n"
443 " 'f': 'F',\n"
443 " 'f': 'F',\n"
444 " 'g': 'G',\n"
444 " 'g': 'G',\n"
445 " 'h': 'H',\n"
445 " 'h': 'H',\n"
446 " 'i': 'I',\n"
446 " 'i': 'I',\n"
447 " 'j': 'J',\n"
447 " 'j': 'J',\n"
448 " 'k': 'K',\n"
448 " 'k': 'K',\n"
449 " 'l': 'L',\n"
449 " 'l': 'L',\n"
450 " 'm': 'M',\n"
450 " 'm': 'M',\n"
451 " 'n': 'N',\n"
451 " 'n': 'N',\n"
452 " 'o': 'O',\n"
452 " 'o': 'O',\n"
453 " 'p': 'P',\n"
453 " 'p': 'P',\n"
454 " 'q': 'Q',\n"
454 " 'q': 'Q',\n"
455 " 'r': 'R',\n"
455 " 'r': 'R',\n"
456 " 's': 'S',\n"
456 " 's': 'S',\n"
457 " 't': 'T',\n"
457 " 't': 'T',\n"
458 " 'u': 'U',\n"
458 " 'u': 'U',\n"
459 " 'v': 'V',\n"
459 " 'v': 'V',\n"
460 " 'w': 'W',\n"
460 " 'w': 'W',\n"
461 " 'x': 'X',\n"
461 " 'x': 'X',\n"
462 " 'y': 'Y',\n"
462 " 'y': 'Y',\n"
463 " 'z': 'Z'})"),
463 " 'z': 'Z'})"),
464 (mp_recursive, "mappingproxy({2: {...}, 3: {2: {...}, 3: {...}}})"),
464 (mp_recursive, "mappingproxy({2: {...}, 3: {2: {...}, 3: {...}}})"),
465 (underlying_dict,
465 (underlying_dict,
466 "{2: mappingproxy({2: {...}, 3: {...}}), 3: {...}}"),
466 "{2: mappingproxy({2: {...}, 3: {...}}), 3: {...}}"),
467 ]
467 ]
468 for obj, expected in cases:
468 for obj, expected in cases:
469 assert pretty.pretty(obj) == expected
469 assert pretty.pretty(obj) == expected
470
470
471
471
472 # TODO : pytest.mark.parametrise once nose is gone.
472 # TODO : pytest.mark.parametrise once nose is gone.
473 def test_simplenamespace():
473 def test_simplenamespace():
474 SN = types.SimpleNamespace
474 SN = types.SimpleNamespace
475
475
476 sn_recursive = SN()
476 sn_recursive = SN()
477 sn_recursive.first = sn_recursive
477 sn_recursive.first = sn_recursive
478 sn_recursive.second = sn_recursive
478 sn_recursive.second = sn_recursive
479 cases = [
479 cases = [
480 (SN(), "namespace()"),
480 (SN(), "namespace()"),
481 (SN(x=SN()), "namespace(x=namespace())"),
481 (SN(x=SN()), "namespace(x=namespace())"),
482 (SN(a_long_name=[SN(s=string.ascii_lowercase)]*3, a_short_name=None),
482 (SN(a_long_name=[SN(s=string.ascii_lowercase)]*3, a_short_name=None),
483 "namespace(a_long_name=[namespace(s='abcdefghijklmnopqrstuvwxyz'),\n"
483 "namespace(a_long_name=[namespace(s='abcdefghijklmnopqrstuvwxyz'),\n"
484 " namespace(s='abcdefghijklmnopqrstuvwxyz'),\n"
484 " namespace(s='abcdefghijklmnopqrstuvwxyz'),\n"
485 " namespace(s='abcdefghijklmnopqrstuvwxyz')],\n"
485 " namespace(s='abcdefghijklmnopqrstuvwxyz')],\n"
486 " a_short_name=None)"),
486 " a_short_name=None)"),
487 (sn_recursive, "namespace(first=namespace(...), second=namespace(...))"),
487 (sn_recursive, "namespace(first=namespace(...), second=namespace(...))"),
488 ]
488 ]
489 for obj, expected in cases:
489 for obj, expected in cases:
490 assert pretty.pretty(obj) == expected
490 assert pretty.pretty(obj) == expected
491
491
492
492
493 def test_pretty_environ():
493 def test_pretty_environ():
494 dict_repr = pretty.pretty(dict(os.environ))
494 dict_repr = pretty.pretty(dict(os.environ))
495 # reindent to align with 'environ' prefix
495 # reindent to align with 'environ' prefix
496 dict_indented = dict_repr.replace('\n', '\n' + (' ' * len('environ')))
496 dict_indented = dict_repr.replace('\n', '\n' + (' ' * len('environ')))
497 env_repr = pretty.pretty(os.environ)
497 env_repr = pretty.pretty(os.environ)
498 assert env_repr == "environ" + dict_indented
498 assert env_repr == "environ" + dict_indented
499
499
500
500
501 def test_function_pretty():
501 def test_function_pretty():
502 "Test pretty print of function"
502 "Test pretty print of function"
503 # posixpath is a pure python module, its interface is consistent
503 # posixpath is a pure python module, its interface is consistent
504 # across Python distributions
504 # across Python distributions
505 import posixpath
505 import posixpath
506
506
507 assert pretty.pretty(posixpath.join) == "<function posixpath.join(a, *p)>"
507 assert pretty.pretty(posixpath.join) == "<function posixpath.join(a, *p)>"
508
508
509 # custom function
509 # custom function
510 def meaning_of_life(question=None):
510 def meaning_of_life(question=None):
511 if question:
511 if question:
512 return 42
512 return 42
513 return "Don't panic"
513 return "Don't panic"
514
514
515 assert "meaning_of_life(question=None)" in pretty.pretty(meaning_of_life)
515 assert "meaning_of_life(question=None)" in pretty.pretty(meaning_of_life)
516
516
517
517
518 class OrderedCounter(Counter, OrderedDict):
518 class OrderedCounter(Counter, OrderedDict):
519 'Counter that remembers the order elements are first encountered'
519 'Counter that remembers the order elements are first encountered'
520
520
521 def __repr__(self):
521 def __repr__(self):
522 return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))
522 return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))
523
523
524 def __reduce__(self):
524 def __reduce__(self):
525 return self.__class__, (OrderedDict(self),)
525 return self.__class__, (OrderedDict(self),)
526
526
527 class MySet(set): # Override repr of a basic type
527 class MySet(set): # Override repr of a basic type
528 def __repr__(self):
528 def __repr__(self):
529 return 'mine'
529 return 'mine'
530
530
531 def test_custom_repr():
531 def test_custom_repr():
532 """A custom repr should override a pretty printer for a parent type"""
532 """A custom repr should override a pretty printer for a parent type"""
533 oc = OrderedCounter("abracadabra")
533 oc = OrderedCounter("abracadabra")
534 assert "OrderedCounter(OrderedDict" in pretty.pretty(oc)
534 assert "OrderedCounter(OrderedDict" in pretty.pretty(oc)
535
535
536 assert pretty.pretty(MySet()) == "mine"
536 assert pretty.pretty(MySet()) == "mine"
1 NO CONTENT: modified file
NO CONTENT: modified file
1 NO CONTENT: modified file
NO CONTENT: modified file
@@ -1,752 +1,752 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for working with strings and text.
3 Utilities for working with strings and text.
4
4
5 Inheritance diagram:
5 Inheritance diagram:
6
6
7 .. inheritance-diagram:: IPython.utils.text
7 .. inheritance-diagram:: IPython.utils.text
8 :parts: 3
8 :parts: 3
9 """
9 """
10
10
11 import os
11 import os
12 import re
12 import re
13 import string
13 import string
14 import sys
14 import sys
15 import textwrap
15 import textwrap
16 from string import Formatter
16 from string import Formatter
17 from pathlib import Path
17 from pathlib import Path
18
18
19
19
20 # datetime.strftime date format for ipython
20 # datetime.strftime date format for ipython
21 if sys.platform == 'win32':
21 if sys.platform == 'win32':
22 date_format = "%B %d, %Y"
22 date_format = "%B %d, %Y"
23 else:
23 else:
24 date_format = "%B %-d, %Y"
24 date_format = "%B %-d, %Y"
25
25
26 class LSString(str):
26 class LSString(str):
27 """String derivative with a special access attributes.
27 """String derivative with a special access attributes.
28
28
29 These are normal strings, but with the special attributes:
29 These are normal strings, but with the special attributes:
30
30
31 .l (or .list) : value as list (split on newlines).
31 .l (or .list) : value as list (split on newlines).
32 .n (or .nlstr): original value (the string itself).
32 .n (or .nlstr): original value (the string itself).
33 .s (or .spstr): value as whitespace-separated string.
33 .s (or .spstr): value as whitespace-separated string.
34 .p (or .paths): list of path objects (requires path.py package)
34 .p (or .paths): list of path objects (requires path.py package)
35
35
36 Any values which require transformations are computed only once and
36 Any values which require transformations are computed only once and
37 cached.
37 cached.
38
38
39 Such strings are very useful to efficiently interact with the shell, which
39 Such strings are very useful to efficiently interact with the shell, which
40 typically only understands whitespace-separated options for commands."""
40 typically only understands whitespace-separated options for commands."""
41
41
42 def get_list(self):
42 def get_list(self):
43 try:
43 try:
44 return self.__list
44 return self.__list
45 except AttributeError:
45 except AttributeError:
46 self.__list = self.split('\n')
46 self.__list = self.split('\n')
47 return self.__list
47 return self.__list
48
48
49 l = list = property(get_list)
49 l = list = property(get_list)
50
50
51 def get_spstr(self):
51 def get_spstr(self):
52 try:
52 try:
53 return self.__spstr
53 return self.__spstr
54 except AttributeError:
54 except AttributeError:
55 self.__spstr = self.replace('\n',' ')
55 self.__spstr = self.replace('\n',' ')
56 return self.__spstr
56 return self.__spstr
57
57
58 s = spstr = property(get_spstr)
58 s = spstr = property(get_spstr)
59
59
60 def get_nlstr(self):
60 def get_nlstr(self):
61 return self
61 return self
62
62
63 n = nlstr = property(get_nlstr)
63 n = nlstr = property(get_nlstr)
64
64
65 def get_paths(self):
65 def get_paths(self):
66 try:
66 try:
67 return self.__paths
67 return self.__paths
68 except AttributeError:
68 except AttributeError:
69 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
69 self.__paths = [Path(p) for p in self.split('\n') if os.path.exists(p)]
70 return self.__paths
70 return self.__paths
71
71
72 p = paths = property(get_paths)
72 p = paths = property(get_paths)
73
73
74 # FIXME: We need to reimplement type specific displayhook and then add this
74 # FIXME: We need to reimplement type specific displayhook and then add this
75 # back as a custom printer. This should also be moved outside utils into the
75 # back as a custom printer. This should also be moved outside utils into the
76 # core.
76 # core.
77
77
78 # def print_lsstring(arg):
78 # def print_lsstring(arg):
79 # """ Prettier (non-repr-like) and more informative printer for LSString """
79 # """ Prettier (non-repr-like) and more informative printer for LSString """
80 # print "LSString (.p, .n, .l, .s available). Value:"
80 # print "LSString (.p, .n, .l, .s available). Value:"
81 # print arg
81 # print arg
82 #
82 #
83 #
83 #
84 # print_lsstring = result_display.register(LSString)(print_lsstring)
84 # print_lsstring = result_display.register(LSString)(print_lsstring)
85
85
86
86
87 class SList(list):
87 class SList(list):
88 """List derivative with a special access attributes.
88 """List derivative with a special access attributes.
89
89
90 These are normal lists, but with the special attributes:
90 These are normal lists, but with the special attributes:
91
91
92 * .l (or .list) : value as list (the list itself).
92 * .l (or .list) : value as list (the list itself).
93 * .n (or .nlstr): value as a string, joined on newlines.
93 * .n (or .nlstr): value as a string, joined on newlines.
94 * .s (or .spstr): value as a string, joined on spaces.
94 * .s (or .spstr): value as a string, joined on spaces.
95 * .p (or .paths): list of path objects (requires path.py package)
95 * .p (or .paths): list of path objects (requires path.py package)
96
96
97 Any values which require transformations are computed only once and
97 Any values which require transformations are computed only once and
98 cached."""
98 cached."""
99
99
100 def get_list(self):
100 def get_list(self):
101 return self
101 return self
102
102
103 l = list = property(get_list)
103 l = list = property(get_list)
104
104
105 def get_spstr(self):
105 def get_spstr(self):
106 try:
106 try:
107 return self.__spstr
107 return self.__spstr
108 except AttributeError:
108 except AttributeError:
109 self.__spstr = ' '.join(self)
109 self.__spstr = ' '.join(self)
110 return self.__spstr
110 return self.__spstr
111
111
112 s = spstr = property(get_spstr)
112 s = spstr = property(get_spstr)
113
113
114 def get_nlstr(self):
114 def get_nlstr(self):
115 try:
115 try:
116 return self.__nlstr
116 return self.__nlstr
117 except AttributeError:
117 except AttributeError:
118 self.__nlstr = '\n'.join(self)
118 self.__nlstr = '\n'.join(self)
119 return self.__nlstr
119 return self.__nlstr
120
120
121 n = nlstr = property(get_nlstr)
121 n = nlstr = property(get_nlstr)
122
122
123 def get_paths(self):
123 def get_paths(self):
124 try:
124 try:
125 return self.__paths
125 return self.__paths
126 except AttributeError:
126 except AttributeError:
127 self.__paths = [Path(p) for p in self if os.path.exists(p)]
127 self.__paths = [Path(p) for p in self if os.path.exists(p)]
128 return self.__paths
128 return self.__paths
129
129
130 p = paths = property(get_paths)
130 p = paths = property(get_paths)
131
131
132 def grep(self, pattern, prune = False, field = None):
132 def grep(self, pattern, prune = False, field = None):
133 """ Return all strings matching 'pattern' (a regex or callable)
133 """ Return all strings matching 'pattern' (a regex or callable)
134
134
135 This is case-insensitive. If prune is true, return all items
135 This is case-insensitive. If prune is true, return all items
136 NOT matching the pattern.
136 NOT matching the pattern.
137
137
138 If field is specified, the match must occur in the specified
138 If field is specified, the match must occur in the specified
139 whitespace-separated field.
139 whitespace-separated field.
140
140
141 Examples::
141 Examples::
142
142
143 a.grep( lambda x: x.startswith('C') )
143 a.grep( lambda x: x.startswith('C') )
144 a.grep('Cha.*log', prune=1)
144 a.grep('Cha.*log', prune=1)
145 a.grep('chm', field=-1)
145 a.grep('chm', field=-1)
146 """
146 """
147
147
148 def match_target(s):
148 def match_target(s):
149 if field is None:
149 if field is None:
150 return s
150 return s
151 parts = s.split()
151 parts = s.split()
152 try:
152 try:
153 tgt = parts[field]
153 tgt = parts[field]
154 return tgt
154 return tgt
155 except IndexError:
155 except IndexError:
156 return ""
156 return ""
157
157
158 if isinstance(pattern, str):
158 if isinstance(pattern, str):
159 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
159 pred = lambda x : re.search(pattern, x, re.IGNORECASE)
160 else:
160 else:
161 pred = pattern
161 pred = pattern
162 if not prune:
162 if not prune:
163 return SList([el for el in self if pred(match_target(el))])
163 return SList([el for el in self if pred(match_target(el))])
164 else:
164 else:
165 return SList([el for el in self if not pred(match_target(el))])
165 return SList([el for el in self if not pred(match_target(el))])
166
166
167 def fields(self, *fields):
167 def fields(self, *fields):
168 """ Collect whitespace-separated fields from string list
168 """ Collect whitespace-separated fields from string list
169
169
170 Allows quick awk-like usage of string lists.
170 Allows quick awk-like usage of string lists.
171
171
172 Example data (in var a, created by 'a = !ls -l')::
172 Example data (in var a, created by 'a = !ls -l')::
173
173
174 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
174 -rwxrwxrwx 1 ville None 18 Dec 14 2006 ChangeLog
175 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
175 drwxrwxrwx+ 6 ville None 0 Oct 24 18:05 IPython
176
176
177 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
177 * ``a.fields(0)`` is ``['-rwxrwxrwx', 'drwxrwxrwx+']``
178 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
178 * ``a.fields(1,0)`` is ``['1 -rwxrwxrwx', '6 drwxrwxrwx+']``
179 (note the joining by space).
179 (note the joining by space).
180 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
180 * ``a.fields(-1)`` is ``['ChangeLog', 'IPython']``
181
181
182 IndexErrors are ignored.
182 IndexErrors are ignored.
183
183
184 Without args, fields() just split()'s the strings.
184 Without args, fields() just split()'s the strings.
185 """
185 """
186 if len(fields) == 0:
186 if len(fields) == 0:
187 return [el.split() for el in self]
187 return [el.split() for el in self]
188
188
189 res = SList()
189 res = SList()
190 for el in [f.split() for f in self]:
190 for el in [f.split() for f in self]:
191 lineparts = []
191 lineparts = []
192
192
193 for fd in fields:
193 for fd in fields:
194 try:
194 try:
195 lineparts.append(el[fd])
195 lineparts.append(el[fd])
196 except IndexError:
196 except IndexError:
197 pass
197 pass
198 if lineparts:
198 if lineparts:
199 res.append(" ".join(lineparts))
199 res.append(" ".join(lineparts))
200
200
201 return res
201 return res
202
202
203 def sort(self,field= None, nums = False):
203 def sort(self,field= None, nums = False):
204 """ sort by specified fields (see fields())
204 """ sort by specified fields (see fields())
205
205
206 Example::
206 Example::
207
207
208 a.sort(1, nums = True)
208 a.sort(1, nums = True)
209
209
210 Sorts a by second field, in numerical order (so that 21 > 3)
210 Sorts a by second field, in numerical order (so that 21 > 3)
211
211
212 """
212 """
213
213
214 #decorate, sort, undecorate
214 #decorate, sort, undecorate
215 if field is not None:
215 if field is not None:
216 dsu = [[SList([line]).fields(field), line] for line in self]
216 dsu = [[SList([line]).fields(field), line] for line in self]
217 else:
217 else:
218 dsu = [[line, line] for line in self]
218 dsu = [[line, line] for line in self]
219 if nums:
219 if nums:
220 for i in range(len(dsu)):
220 for i in range(len(dsu)):
221 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
221 numstr = "".join([ch for ch in dsu[i][0] if ch.isdigit()])
222 try:
222 try:
223 n = int(numstr)
223 n = int(numstr)
224 except ValueError:
224 except ValueError:
225 n = 0
225 n = 0
226 dsu[i][0] = n
226 dsu[i][0] = n
227
227
228
228
229 dsu.sort()
229 dsu.sort()
230 return SList([t[1] for t in dsu])
230 return SList([t[1] for t in dsu])
231
231
232
232
233 # FIXME: We need to reimplement type specific displayhook and then add this
233 # FIXME: We need to reimplement type specific displayhook and then add this
234 # back as a custom printer. This should also be moved outside utils into the
234 # back as a custom printer. This should also be moved outside utils into the
235 # core.
235 # core.
236
236
237 # def print_slist(arg):
237 # def print_slist(arg):
238 # """ Prettier (non-repr-like) and more informative printer for SList """
238 # """ Prettier (non-repr-like) and more informative printer for SList """
239 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
239 # print "SList (.p, .n, .l, .s, .grep(), .fields(), sort() available):"
240 # if hasattr(arg, 'hideonce') and arg.hideonce:
240 # if hasattr(arg, 'hideonce') and arg.hideonce:
241 # arg.hideonce = False
241 # arg.hideonce = False
242 # return
242 # return
243 #
243 #
244 # nlprint(arg) # This was a nested list printer, now removed.
244 # nlprint(arg) # This was a nested list printer, now removed.
245 #
245 #
246 # print_slist = result_display.register(SList)(print_slist)
246 # print_slist = result_display.register(SList)(print_slist)
247
247
248
248
249 def indent(instr,nspaces=4, ntabs=0, flatten=False):
249 def indent(instr,nspaces=4, ntabs=0, flatten=False):
250 """Indent a string a given number of spaces or tabstops.
250 """Indent a string a given number of spaces or tabstops.
251
251
252 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
252 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces.
253
253
254 Parameters
254 Parameters
255 ----------
255 ----------
256 instr : basestring
256 instr : basestring
257 The string to be indented.
257 The string to be indented.
258 nspaces : int (default: 4)
258 nspaces : int (default: 4)
259 The number of spaces to be indented.
259 The number of spaces to be indented.
260 ntabs : int (default: 0)
260 ntabs : int (default: 0)
261 The number of tabs to be indented.
261 The number of tabs to be indented.
262 flatten : bool (default: False)
262 flatten : bool (default: False)
263 Whether to scrub existing indentation. If True, all lines will be
263 Whether to scrub existing indentation. If True, all lines will be
264 aligned to the same indentation. If False, existing indentation will
264 aligned to the same indentation. If False, existing indentation will
265 be strictly increased.
265 be strictly increased.
266
266
267 Returns
267 Returns
268 -------
268 -------
269 str|unicode : string indented by ntabs and nspaces.
269 str|unicode : string indented by ntabs and nspaces.
270
270
271 """
271 """
272 if instr is None:
272 if instr is None:
273 return
273 return
274 ind = '\t'*ntabs+' '*nspaces
274 ind = '\t'*ntabs+' '*nspaces
275 if flatten:
275 if flatten:
276 pat = re.compile(r'^\s*', re.MULTILINE)
276 pat = re.compile(r'^\s*', re.MULTILINE)
277 else:
277 else:
278 pat = re.compile(r'^', re.MULTILINE)
278 pat = re.compile(r'^', re.MULTILINE)
279 outstr = re.sub(pat, ind, instr)
279 outstr = re.sub(pat, ind, instr)
280 if outstr.endswith(os.linesep+ind):
280 if outstr.endswith(os.linesep+ind):
281 return outstr[:-len(ind)]
281 return outstr[:-len(ind)]
282 else:
282 else:
283 return outstr
283 return outstr
284
284
285
285
286 def list_strings(arg):
286 def list_strings(arg):
287 """Always return a list of strings, given a string or list of strings
287 """Always return a list of strings, given a string or list of strings
288 as input.
288 as input.
289
289
290 Examples
290 Examples
291 --------
291 --------
292 ::
292 ::
293
293
294 In [7]: list_strings('A single string')
294 In [7]: list_strings('A single string')
295 Out[7]: ['A single string']
295 Out[7]: ['A single string']
296
296
297 In [8]: list_strings(['A single string in a list'])
297 In [8]: list_strings(['A single string in a list'])
298 Out[8]: ['A single string in a list']
298 Out[8]: ['A single string in a list']
299
299
300 In [9]: list_strings(['A','list','of','strings'])
300 In [9]: list_strings(['A','list','of','strings'])
301 Out[9]: ['A', 'list', 'of', 'strings']
301 Out[9]: ['A', 'list', 'of', 'strings']
302 """
302 """
303
303
304 if isinstance(arg, str):
304 if isinstance(arg, str):
305 return [arg]
305 return [arg]
306 else:
306 else:
307 return arg
307 return arg
308
308
309
309
310 def marquee(txt='',width=78,mark='*'):
310 def marquee(txt='',width=78,mark='*'):
311 """Return the input string centered in a 'marquee'.
311 """Return the input string centered in a 'marquee'.
312
312
313 Examples
313 Examples
314 --------
314 --------
315 ::
315 ::
316
316
317 In [16]: marquee('A test',40)
317 In [16]: marquee('A test',40)
318 Out[16]: '**************** A test ****************'
318 Out[16]: '**************** A test ****************'
319
319
320 In [17]: marquee('A test',40,'-')
320 In [17]: marquee('A test',40,'-')
321 Out[17]: '---------------- A test ----------------'
321 Out[17]: '---------------- A test ----------------'
322
322
323 In [18]: marquee('A test',40,' ')
323 In [18]: marquee('A test',40,' ')
324 Out[18]: ' A test '
324 Out[18]: ' A test '
325
325
326 """
326 """
327 if not txt:
327 if not txt:
328 return (mark*width)[:width]
328 return (mark*width)[:width]
329 nmark = (width-len(txt)-2)//len(mark)//2
329 nmark = (width-len(txt)-2)//len(mark)//2
330 if nmark < 0: nmark =0
330 if nmark < 0: nmark =0
331 marks = mark*nmark
331 marks = mark*nmark
332 return '%s %s %s' % (marks,txt,marks)
332 return '%s %s %s' % (marks,txt,marks)
333
333
334
334
335 ini_spaces_re = re.compile(r'^(\s+)')
335 ini_spaces_re = re.compile(r'^(\s+)')
336
336
337 def num_ini_spaces(strng):
337 def num_ini_spaces(strng):
338 """Return the number of initial spaces in a string"""
338 """Return the number of initial spaces in a string"""
339
339
340 ini_spaces = ini_spaces_re.match(strng)
340 ini_spaces = ini_spaces_re.match(strng)
341 if ini_spaces:
341 if ini_spaces:
342 return ini_spaces.end()
342 return ini_spaces.end()
343 else:
343 else:
344 return 0
344 return 0
345
345
346
346
347 def format_screen(strng):
347 def format_screen(strng):
348 """Format a string for screen printing.
348 """Format a string for screen printing.
349
349
350 This removes some latex-type format codes."""
350 This removes some latex-type format codes."""
351 # Paragraph continue
351 # Paragraph continue
352 par_re = re.compile(r'\\$',re.MULTILINE)
352 par_re = re.compile(r'\\$',re.MULTILINE)
353 strng = par_re.sub('',strng)
353 strng = par_re.sub('',strng)
354 return strng
354 return strng
355
355
356
356
357 def dedent(text):
357 def dedent(text):
358 """Equivalent of textwrap.dedent that ignores unindented first line.
358 """Equivalent of textwrap.dedent that ignores unindented first line.
359
359
360 This means it will still dedent strings like:
360 This means it will still dedent strings like:
361 '''foo
361 '''foo
362 is a bar
362 is a bar
363 '''
363 '''
364
364
365 For use in wrap_paragraphs.
365 For use in wrap_paragraphs.
366 """
366 """
367
367
368 if text.startswith('\n'):
368 if text.startswith('\n'):
369 # text starts with blank line, don't ignore the first line
369 # text starts with blank line, don't ignore the first line
370 return textwrap.dedent(text)
370 return textwrap.dedent(text)
371
371
372 # split first line
372 # split first line
373 splits = text.split('\n',1)
373 splits = text.split('\n',1)
374 if len(splits) == 1:
374 if len(splits) == 1:
375 # only one line
375 # only one line
376 return textwrap.dedent(text)
376 return textwrap.dedent(text)
377
377
378 first, rest = splits
378 first, rest = splits
379 # dedent everything but the first line
379 # dedent everything but the first line
380 rest = textwrap.dedent(rest)
380 rest = textwrap.dedent(rest)
381 return '\n'.join([first, rest])
381 return '\n'.join([first, rest])
382
382
383
383
384 def wrap_paragraphs(text, ncols=80):
384 def wrap_paragraphs(text, ncols=80):
385 """Wrap multiple paragraphs to fit a specified width.
385 """Wrap multiple paragraphs to fit a specified width.
386
386
387 This is equivalent to textwrap.wrap, but with support for multiple
387 This is equivalent to textwrap.wrap, but with support for multiple
388 paragraphs, as separated by empty lines.
388 paragraphs, as separated by empty lines.
389
389
390 Returns
390 Returns
391 -------
391 -------
392 list of complete paragraphs, wrapped to fill `ncols` columns.
392 list of complete paragraphs, wrapped to fill `ncols` columns.
393 """
393 """
394 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
394 paragraph_re = re.compile(r'\n(\s*\n)+', re.MULTILINE)
395 text = dedent(text).strip()
395 text = dedent(text).strip()
396 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
396 paragraphs = paragraph_re.split(text)[::2] # every other entry is space
397 out_ps = []
397 out_ps = []
398 indent_re = re.compile(r'\n\s+', re.MULTILINE)
398 indent_re = re.compile(r'\n\s+', re.MULTILINE)
399 for p in paragraphs:
399 for p in paragraphs:
400 # presume indentation that survives dedent is meaningful formatting,
400 # presume indentation that survives dedent is meaningful formatting,
401 # so don't fill unless text is flush.
401 # so don't fill unless text is flush.
402 if indent_re.search(p) is None:
402 if indent_re.search(p) is None:
403 # wrap paragraph
403 # wrap paragraph
404 p = textwrap.fill(p, ncols)
404 p = textwrap.fill(p, ncols)
405 out_ps.append(p)
405 out_ps.append(p)
406 return out_ps
406 return out_ps
407
407
408
408
409 def strip_email_quotes(text):
409 def strip_email_quotes(text):
410 """Strip leading email quotation characters ('>').
410 """Strip leading email quotation characters ('>').
411
411
412 Removes any combination of leading '>' interspersed with whitespace that
412 Removes any combination of leading '>' interspersed with whitespace that
413 appears *identically* in all lines of the input text.
413 appears *identically* in all lines of the input text.
414
414
415 Parameters
415 Parameters
416 ----------
416 ----------
417 text : str
417 text : str
418
418
419 Examples
419 Examples
420 --------
420 --------
421
421
422 Simple uses::
422 Simple uses::
423
423
424 In [2]: strip_email_quotes('> > text')
424 In [2]: strip_email_quotes('> > text')
425 Out[2]: 'text'
425 Out[2]: 'text'
426
426
427 In [3]: strip_email_quotes('> > text\\n> > more')
427 In [3]: strip_email_quotes('> > text\\n> > more')
428 Out[3]: 'text\\nmore'
428 Out[3]: 'text\\nmore'
429
429
430 Note how only the common prefix that appears in all lines is stripped::
430 Note how only the common prefix that appears in all lines is stripped::
431
431
432 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
432 In [4]: strip_email_quotes('> > text\\n> > more\\n> more...')
433 Out[4]: '> text\\n> more\\nmore...'
433 Out[4]: '> text\\n> more\\nmore...'
434
434
435 So if any line has no quote marks ('>'), then none are stripped from any
435 So if any line has no quote marks ('>'), then none are stripped from any
436 of them ::
436 of them ::
437
437
438 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
438 In [5]: strip_email_quotes('> > text\\n> > more\\nlast different')
439 Out[5]: '> > text\\n> > more\\nlast different'
439 Out[5]: '> > text\\n> > more\\nlast different'
440 """
440 """
441 lines = text.splitlines()
441 lines = text.splitlines()
442 strip_len = 0
442 strip_len = 0
443
443
444 for characters in zip(*lines):
444 for characters in zip(*lines):
445 # Check if all characters in this position are the same
445 # Check if all characters in this position are the same
446 if len(set(characters)) > 1:
446 if len(set(characters)) > 1:
447 break
447 break
448 prefix_char = characters[0]
448 prefix_char = characters[0]
449
449
450 if prefix_char in string.whitespace or prefix_char == ">":
450 if prefix_char in string.whitespace or prefix_char == ">":
451 strip_len += 1
451 strip_len += 1
452 else:
452 else:
453 break
453 break
454
454
455 text = "\n".join([ln[strip_len:] for ln in lines])
455 text = "\n".join([ln[strip_len:] for ln in lines])
456 return text
456 return text
457
457
458
458
459 def strip_ansi(source):
459 def strip_ansi(source):
460 """
460 """
461 Remove ansi escape codes from text.
461 Remove ansi escape codes from text.
462
462
463 Parameters
463 Parameters
464 ----------
464 ----------
465 source : str
465 source : str
466 Source to remove the ansi from
466 Source to remove the ansi from
467 """
467 """
468 return re.sub(r'\033\[(\d|;)+?m', '', source)
468 return re.sub(r'\033\[(\d|;)+?m', '', source)
469
469
470
470
471 class EvalFormatter(Formatter):
471 class EvalFormatter(Formatter):
472 """A String Formatter that allows evaluation of simple expressions.
472 """A String Formatter that allows evaluation of simple expressions.
473
473
474 Note that this version interprets a `:` as specifying a format string (as per
474 Note that this version interprets a `:` as specifying a format string (as per
475 standard string formatting), so if slicing is required, you must explicitly
475 standard string formatting), so if slicing is required, you must explicitly
476 create a slice.
476 create a slice.
477
477
478 This is to be used in templating cases, such as the parallel batch
478 This is to be used in templating cases, such as the parallel batch
479 script templates, where simple arithmetic on arguments is useful.
479 script templates, where simple arithmetic on arguments is useful.
480
480
481 Examples
481 Examples
482 --------
482 --------
483 ::
483 ::
484
484
485 In [1]: f = EvalFormatter()
485 In [1]: f = EvalFormatter()
486 In [2]: f.format('{n//4}', n=8)
486 In [2]: f.format('{n//4}', n=8)
487 Out[2]: '2'
487 Out[2]: '2'
488
488
489 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
489 In [3]: f.format("{greeting[slice(2,4)]}", greeting="Hello")
490 Out[3]: 'll'
490 Out[3]: 'll'
491 """
491 """
492 def get_field(self, name, args, kwargs):
492 def get_field(self, name, args, kwargs):
493 v = eval(name, kwargs)
493 v = eval(name, kwargs)
494 return v, name
494 return v, name
495
495
496 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
496 #XXX: As of Python 3.4, the format string parsing no longer splits on a colon
497 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
497 # inside [], so EvalFormatter can handle slicing. Once we only support 3.4 and
498 # above, it should be possible to remove FullEvalFormatter.
498 # above, it should be possible to remove FullEvalFormatter.
499
499
500 class FullEvalFormatter(Formatter):
500 class FullEvalFormatter(Formatter):
501 """A String Formatter that allows evaluation of simple expressions.
501 """A String Formatter that allows evaluation of simple expressions.
502
502
503 Any time a format key is not found in the kwargs,
503 Any time a format key is not found in the kwargs,
504 it will be tried as an expression in the kwargs namespace.
504 it will be tried as an expression in the kwargs namespace.
505
505
506 Note that this version allows slicing using [1:2], so you cannot specify
506 Note that this version allows slicing using [1:2], so you cannot specify
507 a format string. Use :class:`EvalFormatter` to permit format strings.
507 a format string. Use :class:`EvalFormatter` to permit format strings.
508
508
509 Examples
509 Examples
510 --------
510 --------
511 ::
511 ::
512
512
513 In [1]: f = FullEvalFormatter()
513 In [1]: f = FullEvalFormatter()
514 In [2]: f.format('{n//4}', n=8)
514 In [2]: f.format('{n//4}', n=8)
515 Out[2]: '2'
515 Out[2]: '2'
516
516
517 In [3]: f.format('{list(range(5))[2:4]}')
517 In [3]: f.format('{list(range(5))[2:4]}')
518 Out[3]: '[2, 3]'
518 Out[3]: '[2, 3]'
519
519
520 In [4]: f.format('{3*2}')
520 In [4]: f.format('{3*2}')
521 Out[4]: '6'
521 Out[4]: '6'
522 """
522 """
523 # copied from Formatter._vformat with minor changes to allow eval
523 # copied from Formatter._vformat with minor changes to allow eval
524 # and replace the format_spec code with slicing
524 # and replace the format_spec code with slicing
525 def vformat(self, format_string:str, args, kwargs)->str:
525 def vformat(self, format_string:str, args, kwargs)->str:
526 result = []
526 result = []
527 for literal_text, field_name, format_spec, conversion in \
527 for literal_text, field_name, format_spec, conversion in \
528 self.parse(format_string):
528 self.parse(format_string):
529
529
530 # output the literal text
530 # output the literal text
531 if literal_text:
531 if literal_text:
532 result.append(literal_text)
532 result.append(literal_text)
533
533
534 # if there's a field, output it
534 # if there's a field, output it
535 if field_name is not None:
535 if field_name is not None:
536 # this is some markup, find the object and do
536 # this is some markup, find the object and do
537 # the formatting
537 # the formatting
538
538
539 if format_spec:
539 if format_spec:
540 # override format spec, to allow slicing:
540 # override format spec, to allow slicing:
541 field_name = ':'.join([field_name, format_spec])
541 field_name = ':'.join([field_name, format_spec])
542
542
543 # eval the contents of the field for the object
543 # eval the contents of the field for the object
544 # to be formatted
544 # to be formatted
545 obj = eval(field_name, kwargs)
545 obj = eval(field_name, kwargs)
546
546
547 # do any conversion on the resulting object
547 # do any conversion on the resulting object
548 obj = self.convert_field(obj, conversion)
548 obj = self.convert_field(obj, conversion)
549
549
550 # format the object and append to the result
550 # format the object and append to the result
551 result.append(self.format_field(obj, ''))
551 result.append(self.format_field(obj, ''))
552
552
553 return ''.join(result)
553 return ''.join(result)
554
554
555
555
556 class DollarFormatter(FullEvalFormatter):
556 class DollarFormatter(FullEvalFormatter):
557 """Formatter allowing Itpl style $foo replacement, for names and attribute
557 """Formatter allowing Itpl style $foo replacement, for names and attribute
558 access only. Standard {foo} replacement also works, and allows full
558 access only. Standard {foo} replacement also works, and allows full
559 evaluation of its arguments.
559 evaluation of its arguments.
560
560
561 Examples
561 Examples
562 --------
562 --------
563 ::
563 ::
564
564
565 In [1]: f = DollarFormatter()
565 In [1]: f = DollarFormatter()
566 In [2]: f.format('{n//4}', n=8)
566 In [2]: f.format('{n//4}', n=8)
567 Out[2]: '2'
567 Out[2]: '2'
568
568
569 In [3]: f.format('23 * 76 is $result', result=23*76)
569 In [3]: f.format('23 * 76 is $result', result=23*76)
570 Out[3]: '23 * 76 is 1748'
570 Out[3]: '23 * 76 is 1748'
571
571
572 In [4]: f.format('$a or {b}', a=1, b=2)
572 In [4]: f.format('$a or {b}', a=1, b=2)
573 Out[4]: '1 or 2'
573 Out[4]: '1 or 2'
574 """
574 """
575 _dollar_pattern_ignore_single_quote = re.compile(r"(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)")
575 _dollar_pattern_ignore_single_quote = re.compile(r"(.*?)\$(\$?[\w\.]+)(?=([^']*'[^']*')*[^']*$)")
576 def parse(self, fmt_string):
576 def parse(self, fmt_string):
577 for literal_txt, field_name, format_spec, conversion \
577 for literal_txt, field_name, format_spec, conversion \
578 in Formatter.parse(self, fmt_string):
578 in Formatter.parse(self, fmt_string):
579
579
580 # Find $foo patterns in the literal text.
580 # Find $foo patterns in the literal text.
581 continue_from = 0
581 continue_from = 0
582 txt = ""
582 txt = ""
583 for m in self._dollar_pattern_ignore_single_quote.finditer(literal_txt):
583 for m in self._dollar_pattern_ignore_single_quote.finditer(literal_txt):
584 new_txt, new_field = m.group(1,2)
584 new_txt, new_field = m.group(1,2)
585 # $$foo --> $foo
585 # $$foo --> $foo
586 if new_field.startswith("$"):
586 if new_field.startswith("$"):
587 txt += new_txt + new_field
587 txt += new_txt + new_field
588 else:
588 else:
589 yield (txt + new_txt, new_field, "", None)
589 yield (txt + new_txt, new_field, "", None)
590 txt = ""
590 txt = ""
591 continue_from = m.end()
591 continue_from = m.end()
592
592
593 # Re-yield the {foo} style pattern
593 # Re-yield the {foo} style pattern
594 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
594 yield (txt + literal_txt[continue_from:], field_name, format_spec, conversion)
595
595
596 def __repr__(self):
596 def __repr__(self):
597 return "<DollarFormatter>"
597 return "<DollarFormatter>"
598
598
599 #-----------------------------------------------------------------------------
599 #-----------------------------------------------------------------------------
600 # Utils to columnize a list of string
600 # Utils to columnize a list of string
601 #-----------------------------------------------------------------------------
601 #-----------------------------------------------------------------------------
602
602
603 def _col_chunks(l, max_rows, row_first=False):
603 def _col_chunks(l, max_rows, row_first=False):
604 """Yield successive max_rows-sized column chunks from l."""
604 """Yield successive max_rows-sized column chunks from l."""
605 if row_first:
605 if row_first:
606 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
606 ncols = (len(l) // max_rows) + (len(l) % max_rows > 0)
607 for i in range(ncols):
607 for i in range(ncols):
608 yield [l[j] for j in range(i, len(l), ncols)]
608 yield [l[j] for j in range(i, len(l), ncols)]
609 else:
609 else:
610 for i in range(0, len(l), max_rows):
610 for i in range(0, len(l), max_rows):
611 yield l[i:(i + max_rows)]
611 yield l[i:(i + max_rows)]
612
612
613
613
614 def _find_optimal(rlist, row_first=False, separator_size=2, displaywidth=80):
614 def _find_optimal(rlist, row_first=False, separator_size=2, displaywidth=80):
615 """Calculate optimal info to columnize a list of string"""
615 """Calculate optimal info to columnize a list of string"""
616 for max_rows in range(1, len(rlist) + 1):
616 for max_rows in range(1, len(rlist) + 1):
617 col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first)))
617 col_widths = list(map(max, _col_chunks(rlist, max_rows, row_first)))
618 sumlength = sum(col_widths)
618 sumlength = sum(col_widths)
619 ncols = len(col_widths)
619 ncols = len(col_widths)
620 if sumlength + separator_size * (ncols - 1) <= displaywidth:
620 if sumlength + separator_size * (ncols - 1) <= displaywidth:
621 break
621 break
622 return {'num_columns': ncols,
622 return {'num_columns': ncols,
623 'optimal_separator_width': (displaywidth - sumlength) // (ncols - 1) if (ncols - 1) else 0,
623 'optimal_separator_width': (displaywidth - sumlength) // (ncols - 1) if (ncols - 1) else 0,
624 'max_rows': max_rows,
624 'max_rows': max_rows,
625 'column_widths': col_widths
625 'column_widths': col_widths
626 }
626 }
627
627
628
628
629 def _get_or_default(mylist, i, default=None):
629 def _get_or_default(mylist, i, default=None):
630 """return list item number, or default if don't exist"""
630 """return list item number, or default if don't exist"""
631 if i >= len(mylist):
631 if i >= len(mylist):
632 return default
632 return default
633 else :
633 else :
634 return mylist[i]
634 return mylist[i]
635
635
636
636
637 def compute_item_matrix(items, row_first=False, empty=None, *args, **kwargs) :
637 def compute_item_matrix(items, row_first=False, empty=None, *args, **kwargs) :
638 """Returns a nested list, and info to columnize items
638 """Returns a nested list, and info to columnize items
639
639
640 Parameters
640 Parameters
641 ----------
641 ----------
642 items
642 items
643 list of strings to columize
643 list of strings to columize
644 row_first : (default False)
644 row_first : (default False)
645 Whether to compute columns for a row-first matrix instead of
645 Whether to compute columns for a row-first matrix instead of
646 column-first (default).
646 column-first (default).
647 empty : (default None)
647 empty : (default None)
648 default value to fill list if needed
648 default value to fill list if needed
649 separator_size : int (default=2)
649 separator_size : int (default=2)
650 How much characters will be used as a separation between each columns.
650 How much characters will be used as a separation between each columns.
651 displaywidth : int (default=80)
651 displaywidth : int (default=80)
652 The width of the area onto which the columns should enter
652 The width of the area onto which the columns should enter
653
653
654 Returns
654 Returns
655 -------
655 -------
656 strings_matrix
656 strings_matrix
657 nested list of string, the outer most list contains as many list as
657 nested list of string, the outer most list contains as many list as
658 rows, the innermost lists have each as many element as columns. If the
658 rows, the innermost lists have each as many element as columns. If the
659 total number of elements in `items` does not equal the product of
659 total number of elements in `items` does not equal the product of
660 rows*columns, the last element of some lists are filled with `None`.
660 rows*columns, the last element of some lists are filled with `None`.
661 dict_info
661 dict_info
662 some info to make columnize easier:
662 some info to make columnize easier:
663
663
664 num_columns
664 num_columns
665 number of columns
665 number of columns
666 max_rows
666 max_rows
667 maximum number of rows (final number may be less)
667 maximum number of rows (final number may be less)
668 column_widths
668 column_widths
669 list of with of each columns
669 list of with of each columns
670 optimal_separator_width
670 optimal_separator_width
671 best separator width between columns
671 best separator width between columns
672
672
673 Examples
673 Examples
674 --------
674 --------
675 ::
675 ::
676
676
677 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
677 In [1]: l = ['aaa','b','cc','d','eeeee','f','g','h','i','j','k','l']
678 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
678 In [2]: list, info = compute_item_matrix(l, displaywidth=12)
679 In [3]: list
679 In [3]: list
680 Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]]
680 Out[3]: [['aaa', 'f', 'k'], ['b', 'g', 'l'], ['cc', 'h', None], ['d', 'i', None], ['eeeee', 'j', None]]
681 In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5}
681 In [4]: ideal = {'num_columns': 3, 'column_widths': [5, 1, 1], 'optimal_separator_width': 2, 'max_rows': 5}
682 In [5]: all((info[k] == ideal[k] for k in ideal.keys()))
682 In [5]: all((info[k] == ideal[k] for k in ideal.keys()))
683 Out[5]: True
683 Out[5]: True
684 """
684 """
685 info = _find_optimal(list(map(len, items)), row_first, *args, **kwargs)
685 info = _find_optimal(list(map(len, items)), row_first, *args, **kwargs)
686 nrow, ncol = info['max_rows'], info['num_columns']
686 nrow, ncol = info['max_rows'], info['num_columns']
687 if row_first:
687 if row_first:
688 return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info)
688 return ([[_get_or_default(items, r * ncol + c, default=empty) for c in range(ncol)] for r in range(nrow)], info)
689 else:
689 else:
690 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
690 return ([[_get_or_default(items, c * nrow + r, default=empty) for c in range(ncol)] for r in range(nrow)], info)
691
691
692
692
693 def columnize(items, row_first=False, separator=' ', displaywidth=80, spread=False):
693 def columnize(items, row_first=False, separator=" ", displaywidth=80, spread=False):
694 """ Transform a list of strings into a single string with columns.
694 """Transform a list of strings into a single string with columns.
695
695
696 Parameters
696 Parameters
697 ----------
697 ----------
698 items : sequence of strings
698 items : sequence of strings
699 The strings to process.
699 The strings to process.
700 row_first : (default False)
700 row_first : (default False)
701 Whether to compute columns for a row-first matrix instead of
701 Whether to compute columns for a row-first matrix instead of
702 column-first (default).
702 column-first (default).
703 separator : str, optional [default is two spaces]
703 separator : str, optional [default is two spaces]
704 The string that separates columns.
704 The string that separates columns.
705 displaywidth : int, optional [default is 80]
705 displaywidth : int, optional [default is 80]
706 Width of the display in number of characters.
706 Width of the display in number of characters.
707
707
708 Returns
708 Returns
709 -------
709 -------
710 The formatted string.
710 The formatted string.
711 """
711 """
712 if not items:
712 if not items:
713 return '\n'
713 return '\n'
714 matrix, info = compute_item_matrix(items, row_first=row_first, separator_size=len(separator), displaywidth=displaywidth)
714 matrix, info = compute_item_matrix(items, row_first=row_first, separator_size=len(separator), displaywidth=displaywidth)
715 if spread:
715 if spread:
716 separator = separator.ljust(int(info['optimal_separator_width']))
716 separator = separator.ljust(int(info['optimal_separator_width']))
717 fmatrix = [filter(None, x) for x in matrix]
717 fmatrix = [filter(None, x) for x in matrix]
718 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['column_widths'])])
718 sjoin = lambda x : separator.join([ y.ljust(w, ' ') for y, w in zip(x, info['column_widths'])])
719 return '\n'.join(map(sjoin, fmatrix))+'\n'
719 return '\n'.join(map(sjoin, fmatrix))+'\n'
720
720
721
721
722 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
722 def get_text_list(list_, last_sep=' and ', sep=", ", wrap_item_with=""):
723 """
723 """
724 Return a string with a natural enumeration of items
724 Return a string with a natural enumeration of items
725
725
726 >>> get_text_list(['a', 'b', 'c', 'd'])
726 >>> get_text_list(['a', 'b', 'c', 'd'])
727 'a, b, c and d'
727 'a, b, c and d'
728 >>> get_text_list(['a', 'b', 'c'], ' or ')
728 >>> get_text_list(['a', 'b', 'c'], ' or ')
729 'a, b or c'
729 'a, b or c'
730 >>> get_text_list(['a', 'b', 'c'], ', ')
730 >>> get_text_list(['a', 'b', 'c'], ', ')
731 'a, b, c'
731 'a, b, c'
732 >>> get_text_list(['a', 'b'], ' or ')
732 >>> get_text_list(['a', 'b'], ' or ')
733 'a or b'
733 'a or b'
734 >>> get_text_list(['a'])
734 >>> get_text_list(['a'])
735 'a'
735 'a'
736 >>> get_text_list([])
736 >>> get_text_list([])
737 ''
737 ''
738 >>> get_text_list(['a', 'b'], wrap_item_with="`")
738 >>> get_text_list(['a', 'b'], wrap_item_with="`")
739 '`a` and `b`'
739 '`a` and `b`'
740 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
740 >>> get_text_list(['a', 'b', 'c', 'd'], " = ", sep=" + ")
741 'a + b + c = d'
741 'a + b + c = d'
742 """
742 """
743 if len(list_) == 0:
743 if len(list_) == 0:
744 return ''
744 return ''
745 if wrap_item_with:
745 if wrap_item_with:
746 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
746 list_ = ['%s%s%s' % (wrap_item_with, item, wrap_item_with) for
747 item in list_]
747 item in list_]
748 if len(list_) == 1:
748 if len(list_) == 1:
749 return list_[0]
749 return list_[0]
750 return '%s%s%s' % (
750 return '%s%s%s' % (
751 sep.join(i for i in list_[:-1]),
751 sep.join(i for i in list_[:-1]),
752 last_sep, list_[-1])
752 last_sep, list_[-1])
General Comments 0
You need to be logged in to leave comments. Login now