##// END OF EJS Templates
Merge branch 'master' into Carreau-patch-4
Matthias Bussonnier -
r27644:c272c021 merge Carreau-patch-4
parent child Browse files
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
@@ -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,167 +1,167 b''
1 """Simple example using doctests.
1 """Simple example using doctests.
2
2
3 This file just contains doctests both using plain python and IPython prompts.
3 This file just contains doctests both using plain python and IPython prompts.
4 All tests should be loaded by nose.
4 All tests should be loaded by nose.
5 """
5 """
6
6
7 import os
7 import os
8
8
9
9
10 def pyfunc():
10 def pyfunc():
11 """Some pure python tests...
11 """Some pure python tests...
12
12
13 >>> pyfunc()
13 >>> pyfunc()
14 'pyfunc'
14 'pyfunc'
15
15
16 >>> import os
16 >>> import os
17
17
18 >>> 2+3
18 >>> 2+3
19 5
19 5
20
20
21 >>> for i in range(3):
21 >>> for i in range(3):
22 ... print(i, end=' ')
22 ... print(i, end=' ')
23 ... print(i+1, end=' ')
23 ... print(i+1, end=' ')
24 ...
24 ...
25 0 1 1 2 2 3
25 0 1 1 2 2 3
26 """
26 """
27 return 'pyfunc'
27 return 'pyfunc'
28
28
29 def ipfunc():
29 def ipfunc():
30 """Some ipython tests...
30 """Some ipython tests...
31
31
32 In [1]: import os
32 In [1]: import os
33
33
34 In [3]: 2+3
34 In [3]: 2+3
35 Out[3]: 5
35 Out[3]: 5
36
36
37 In [26]: for i in range(3):
37 In [26]: for i in range(3):
38 ....: print(i, end=' ')
38 ....: print(i, end=' ')
39 ....: print(i+1, end=' ')
39 ....: print(i+1, end=' ')
40 ....:
40 ....:
41 0 1 1 2 2 3
41 0 1 1 2 2 3
42
42
43
43
44 It's OK to use '_' for the last result, but do NOT try to use IPython's
44 It's OK to use '_' for the last result, but do NOT try to use IPython's
45 numbered history of _NN outputs, since those won't exist under the
45 numbered history of _NN outputs, since those won't exist under the
46 doctest environment:
46 doctest environment:
47
47
48 In [7]: 'hi'
48 In [7]: 'hi'
49 Out[7]: 'hi'
49 Out[7]: 'hi'
50
50
51 In [8]: print(repr(_))
51 In [8]: print(repr(_))
52 'hi'
52 'hi'
53
53
54 In [7]: 3+4
54 In [7]: 3+4
55 Out[7]: 7
55 Out[7]: 7
56
56
57 In [8]: _+3
57 In [8]: _+3
58 Out[8]: 10
58 Out[8]: 10
59
59
60 In [9]: ipfunc()
60 In [9]: ipfunc()
61 Out[9]: 'ipfunc'
61 Out[9]: 'ipfunc'
62 """
62 """
63 return 'ipfunc'
63 return "ipfunc"
64
64
65
65
66 def ipos():
66 def ipos():
67 """Examples that access the operating system work:
67 """Examples that access the operating system work:
68
68
69 In [1]: !echo hello
69 In [1]: !echo hello
70 hello
70 hello
71
71
72 In [2]: !echo hello > /tmp/foo_iptest
72 In [2]: !echo hello > /tmp/foo_iptest
73
73
74 In [3]: !cat /tmp/foo_iptest
74 In [3]: !cat /tmp/foo_iptest
75 hello
75 hello
76
76
77 In [4]: rm -f /tmp/foo_iptest
77 In [4]: rm -f /tmp/foo_iptest
78 """
78 """
79 pass
79 pass
80
80
81
81
82 ipos.__skip_doctest__ = os.name == "nt"
82 ipos.__skip_doctest__ = os.name == "nt"
83
83
84
84
85 def ranfunc():
85 def ranfunc():
86 """A function with some random output.
86 """A function with some random output.
87
87
88 Normal examples are verified as usual:
88 Normal examples are verified as usual:
89 >>> 1+3
89 >>> 1+3
90 4
90 4
91
91
92 But if you put '# random' in the output, it is ignored:
92 But if you put '# random' in the output, it is ignored:
93 >>> 1+3
93 >>> 1+3
94 junk goes here... # random
94 junk goes here... # random
95
95
96 >>> 1+2
96 >>> 1+2
97 again, anything goes #random
97 again, anything goes #random
98 if multiline, the random mark is only needed once.
98 if multiline, the random mark is only needed once.
99
99
100 >>> 1+2
100 >>> 1+2
101 You can also put the random marker at the end:
101 You can also put the random marker at the end:
102 # random
102 # random
103
103
104 >>> 1+2
104 >>> 1+2
105 # random
105 # random
106 .. or at the beginning.
106 .. or at the beginning.
107
107
108 More correct input is properly verified:
108 More correct input is properly verified:
109 >>> ranfunc()
109 >>> ranfunc()
110 'ranfunc'
110 'ranfunc'
111 """
111 """
112 return 'ranfunc'
112 return 'ranfunc'
113
113
114
114
115 def random_all():
115 def random_all():
116 """A function where we ignore the output of ALL examples.
116 """A function where we ignore the output of ALL examples.
117
117
118 Examples:
118 Examples:
119
119
120 # all-random
120 # all-random
121
121
122 This mark tells the testing machinery that all subsequent examples should
122 This mark tells the testing machinery that all subsequent examples should
123 be treated as random (ignoring their output). They are still executed,
123 be treated as random (ignoring their output). They are still executed,
124 so if a they raise an error, it will be detected as such, but their
124 so if a they raise an error, it will be detected as such, but their
125 output is completely ignored.
125 output is completely ignored.
126
126
127 >>> 1+3
127 >>> 1+3
128 junk goes here...
128 junk goes here...
129
129
130 >>> 1+3
130 >>> 1+3
131 klasdfj;
131 klasdfj;
132
132
133 >>> 1+2
133 >>> 1+2
134 again, anything goes
134 again, anything goes
135 blah...
135 blah...
136 """
136 """
137 pass
137 pass
138
138
139 def iprand():
139 def iprand():
140 """Some ipython tests with random output.
140 """Some ipython tests with random output.
141
141
142 In [7]: 3+4
142 In [7]: 3+4
143 Out[7]: 7
143 Out[7]: 7
144
144
145 In [8]: print('hello')
145 In [8]: print('hello')
146 world # random
146 world # random
147
147
148 In [9]: iprand()
148 In [9]: iprand()
149 Out[9]: 'iprand'
149 Out[9]: 'iprand'
150 """
150 """
151 return 'iprand'
151 return 'iprand'
152
152
153 def iprand_all():
153 def iprand_all():
154 """Some ipython tests with fully random output.
154 """Some ipython tests with fully random output.
155
155
156 # all-random
156 # all-random
157
157
158 In [7]: 1
158 In [7]: 1
159 Out[7]: 99
159 Out[7]: 99
160
160
161 In [8]: print('hello')
161 In [8]: print('hello')
162 world
162 world
163
163
164 In [9]: iprand_all()
164 In [9]: iprand_all()
165 Out[9]: 'junk'
165 Out[9]: 'junk'
166 """
166 """
167 return 'iprand_all'
167 return 'iprand_all'
@@ -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])
@@ -1,173 +1,173 b''
1 .. _integrating:
1 .. _integrating:
2
2
3 =====================================
3 =====================================
4 Integrating your objects with IPython
4 Integrating your objects with IPython
5 =====================================
5 =====================================
6
6
7 Tab completion
7 Tab completion
8 ==============
8 ==============
9
9
10 To change the attributes displayed by tab-completing your object, define a
10 To change the attributes displayed by tab-completing your object, define a
11 ``__dir__(self)`` method for it. For more details, see the documentation of the
11 ``__dir__(self)`` method for it. For more details, see the documentation of the
12 built-in `dir() function <http://docs.python.org/library/functions.html#dir>`_.
12 built-in `dir() function <http://docs.python.org/library/functions.html#dir>`_.
13
13
14 You can also customise key completions for your objects, e.g. pressing tab after
14 You can also customise key completions for your objects, e.g. pressing tab after
15 ``obj["a``. To do so, define a method ``_ipython_key_completions_()``, which
15 ``obj["a``. To do so, define a method ``_ipython_key_completions_()``, which
16 returns a list of objects which are possible keys in a subscript expression
16 returns a list of objects which are possible keys in a subscript expression
17 ``obj[key]``.
17 ``obj[key]``.
18
18
19 .. versionadded:: 5.0
19 .. versionadded:: 5.0
20 Custom key completions
20 Custom key completions
21
21
22 .. _integrating_rich_display:
22 .. _integrating_rich_display:
23
23
24 Rich display
24 Rich display
25 ============
25 ============
26
26
27 Custom methods
27 Custom methods
28 ----------------------
28 ----------------------
29 IPython can display richer representations of objects.
29 IPython can display richer representations of objects.
30 To do this, you can define ``_ipython_display_()``, or any of a number of
30 To do this, you can define ``_ipython_display_()``, or any of a number of
31 ``_repr_*_()`` methods.
31 ``_repr_*_()`` methods.
32 Note that these are surrounded by single, not double underscores.
32 Note that these are surrounded by single, not double underscores.
33
33
34 .. list-table:: Supported ``_repr_*_`` methods
34 .. list-table:: Supported ``_repr_*_`` methods
35 :widths: 20 15 15 15
35 :widths: 20 15 15 15
36 :header-rows: 1
36 :header-rows: 1
37
37
38 * - Format
38 * - Format
39 - REPL
39 - REPL
40 - Notebook
40 - Notebook
41 - Qt Console
41 - Qt Console
42 * - ``_repr_pretty_``
42 * - ``_repr_pretty_``
43 - yes
43 - yes
44 - yes
44 - yes
45 - yes
45 - yes
46 * - ``_repr_svg_``
46 * - ``_repr_svg_``
47 - no
47 - no
48 - yes
48 - yes
49 - yes
49 - yes
50 * - ``_repr_png_``
50 * - ``_repr_png_``
51 - no
51 - no
52 - yes
52 - yes
53 - yes
53 - yes
54 * - ``_repr_jpeg_``
54 * - ``_repr_jpeg_``
55 - no
55 - no
56 - yes
56 - yes
57 - yes
57 - yes
58 * - ``_repr_html_``
58 * - ``_repr_html_``
59 - no
59 - no
60 - yes
60 - yes
61 - no
61 - no
62 * - ``_repr_javascript_``
62 * - ``_repr_javascript_``
63 - no
63 - no
64 - yes
64 - yes
65 - no
65 - no
66 * - ``_repr_markdown_``
66 * - ``_repr_markdown_``
67 - no
67 - no
68 - yes
68 - yes
69 - no
69 - no
70 * - ``_repr_latex_``
70 * - ``_repr_latex_``
71 - no
71 - no
72 - yes
72 - yes
73 - no
73 - no
74 * - ``_repr_mimebundle_``
74 * - ``_repr_mimebundle_``
75 - no
75 - no
76 - ?
76 - ?
77 - ?
77 - ?
78
78
79 If the methods don't exist, or return ``None``, the standard ``repr()`` is used.
79 If the methods don't exist, or return ``None``, the standard ``repr()`` is used.
80
80
81 For example::
81 For example::
82
82
83 class Shout(object):
83 class Shout(object):
84 def __init__(self, text):
84 def __init__(self, text):
85 self.text = text
85 self.text = text
86
86
87 def _repr_html_(self):
87 def _repr_html_(self):
88 return "<h1>" + self.text + "</h1>"
88 return "<h1>" + self.text + "</h1>"
89
89
90
90
91 Special methods
91 Special methods
92 ^^^^^^^^^^^^^^^
92 ^^^^^^^^^^^^^^^
93
93
94 Pretty printing
94 Pretty printing
95 """""""""""""""
95 """""""""""""""
96
96
97 To customize how your object is pretty-printed, add a ``_repr_pretty_`` method
97 To customize how your object is pretty-printed, add a ``_repr_pretty_`` method
98 to the class.
98 to the class.
99 The method should accept a pretty printer, and a boolean that indicates whether
99 The method should accept a pretty printer, and a boolean that indicates whether
100 the printer detected a cycle.
100 the printer detected a cycle.
101 The method should act on the printer to produce your customized pretty output.
101 The method should act on the printer to produce your customized pretty output.
102 Here is an example::
102 Here is an example::
103
103
104 class MyObject(object):
104 class MyObject(object):
105
105
106 def _repr_pretty_(self, p, cycle):
106 def _repr_pretty_(self, p, cycle):
107 if cycle:
107 if cycle:
108 p.text('MyObject(...)')
108 p.text('MyObject(...)')
109 else:
109 else:
110 p.text('MyObject[...]')
110 p.text('MyObject[...]')
111
111
112 For details on how to use the pretty printer, see :py:mod:`IPython.lib.pretty`.
112 For details on how to use the pretty printer, see :py:mod:`IPython.lib.pretty`.
113
113
114 More powerful methods
114 More powerful methods
115 """""""""""""""""""""
115 """""""""""""""""""""
116
116
117 .. class:: MyObject
117 .. class:: MyObject
118
118
119 .. method:: _repr_mimebundle_(include=None, exclude=None)
119 .. method:: _repr_mimebundle_(include=None, exclude=None)
120
120
121 Should return a dictionary of multiple formats, keyed by mimetype, or a tuple
121 Should return a dictionary of multiple formats, keyed by mimetype, or a tuple
122 of two dictionaries: *data, metadata* (see :ref:`Metadata`).
122 of two dictionaries: *data, metadata* (see :ref:`Metadata`).
123 If this returns something, other ``_repr_*_`` methods are ignored.
123 If this returns something, other ``_repr_*_`` methods are ignored.
124 The method should take keyword arguments ``include`` and ``exclude``, though
124 The method should take keyword arguments ``include`` and ``exclude``, though
125 it is not required to respect them.
125 it is not required to respect them.
126
126
127 .. method:: _ipython_display_()
127 .. method:: _ipython_display_()
128
128
129 Displays the object as a side effect; the return value is ignored. If this
129 Displays the object as a side effect; the return value is ignored. If this
130 is defined, all other display methods are ignored.
130 is defined, all other display methods are ignored.
131 This method is ignored in the REPL.
131 This method is ignored in the REPL.
132
132
133
133
134 Metadata
134 Metadata
135 ^^^^^^^^
135 ^^^^^^^^
136
136
137 We often want to provide frontends with guidance on how to display the data. To
137 We often want to provide frontends with guidance on how to display the data. To
138 support this, ``_repr_*_()`` methods (except `_repr_pretty_``?) can also return a ``(data, metadata)``
138 support this, ``_repr_*_()`` methods (except ``_repr_pretty_``?) can also return a ``(data, metadata)``
139 tuple where ``metadata`` is a dictionary containing arbitrary key-value pairs for
139 tuple where ``metadata`` is a dictionary containing arbitrary key-value pairs for
140 the frontend to interpret. An example use case is ``_repr_jpeg_()``, which can
140 the frontend to interpret. An example use case is ``_repr_jpeg_()``, which can
141 be set to return a jpeg image and a ``{'height': 400, 'width': 600}`` dictionary
141 be set to return a jpeg image and a ``{'height': 400, 'width': 600}`` dictionary
142 to inform the frontend how to size the image.
142 to inform the frontend how to size the image.
143
143
144
144
145
145
146 Formatters for third-party types
146 Formatters for third-party types
147 --------------------------------
147 --------------------------------
148
148
149 The user can also register formatters for types without modifying the class::
149 The user can also register formatters for types without modifying the class::
150
150
151 from bar.baz import Foo
151 from bar.baz import Foo
152
152
153 def foo_html(obj):
153 def foo_html(obj):
154 return '<marquee>Foo object %s</marquee>' % obj.name
154 return '<marquee>Foo object %s</marquee>' % obj.name
155
155
156 html_formatter = get_ipython().display_formatter.formatters['text/html']
156 html_formatter = get_ipython().display_formatter.formatters['text/html']
157 html_formatter.for_type(Foo, foo_html)
157 html_formatter.for_type(Foo, foo_html)
158
158
159 # Or register a type without importing it - this does the same as above:
159 # Or register a type without importing it - this does the same as above:
160 html_formatter.for_type_by_name('bar.baz', 'Foo', foo_html)
160 html_formatter.for_type_by_name('bar.baz', 'Foo', foo_html)
161
161
162 Custom exception tracebacks
162 Custom exception tracebacks
163 ===========================
163 ===========================
164
164
165 Rarely, you might want to display a custom traceback when reporting an
165 Rarely, you might want to display a custom traceback when reporting an
166 exception. To do this, define the custom traceback using
166 exception. To do this, define the custom traceback using
167 `_render_traceback_(self)` method which returns a list of strings, one string
167 `_render_traceback_(self)` method which returns a list of strings, one string
168 for each line of the traceback. For example, the `ipyparallel
168 for each line of the traceback. For example, the `ipyparallel
169 <https://ipyparallel.readthedocs.io/>`__ a parallel computing framework for
169 <https://ipyparallel.readthedocs.io/>`__ a parallel computing framework for
170 IPython, does this to display errors from multiple engines.
170 IPython, does this to display errors from multiple engines.
171
171
172 Please be conservative in using this feature; by replacing the default traceback
172 Please be conservative in using this feature; by replacing the default traceback
173 you may hide important information from the user.
173 you may hide important information from the user.
1 NO CONTENT: modified file
NO CONTENT: modified file
@@ -1,528 +1,528 b''
1 .. _issues_list_011:
1 .. _issues_list_011:
2
2
3 Issues closed in the 0.11 development cycle
3 Issues closed in the 0.11 development cycle
4 ===========================================
4 ===========================================
5
5
6 In this cycle, we closed a total of 511 issues, 226 pull requests and 285
6 In this cycle, we closed a total of 511 issues, 226 pull requests and 285
7 regular issues; this is the full list (generated with the script
7 regular issues; this is the full list (generated with the script
8 `tools/github_stats.py`). We should note that a few of these were made on the
8 `tools/github_stats.py`). We should note that a few of these were made on the
9 0.10.x series, but we have no automatic way of filtering the issues by branch,
9 0.10.x series, but we have no automatic way of filtering the issues by branch,
10 so this reflects all of our development over the last two years, including work
10 so this reflects all of our development over the last two years, including work
11 already released in 0.10.2:
11 already released in 0.10.2:
12
12
13 Pull requests (226):
13 Pull requests (226):
14
14
15 * `620 <https://github.com/ipython/ipython/issues/620>`_: Release notes and updates to GUI support docs for 0.11
15 * `620 <https://github.com/ipython/ipython/issues/620>`_: Release notes and updates to GUI support docs for 0.11
16 * `642 <https://github.com/ipython/ipython/issues/642>`_: fix typo in docs/examples/vim/README.rst
16 * `642 <https://github.com/ipython/ipython/issues/642>`_: fix typo in docs/examples/vim/README.rst
17 * `631 <https://github.com/ipython/ipython/issues/631>`_: two-way vim-ipython integration
17 * `631 <https://github.com/ipython/ipython/issues/631>`_: two-way vim-ipython integration
18 * `637 <https://github.com/ipython/ipython/issues/637>`_: print is a function, this allows to properly exit ipython
18 * `637 <https://github.com/ipython/ipython/issues/637>`_: print is a function, this allows to properly exit ipython
19 * `635 <https://github.com/ipython/ipython/issues/635>`_: support html representations in the notebook frontend
19 * `635 <https://github.com/ipython/ipython/issues/635>`_: support html representations in the notebook frontend
20 * `639 <https://github.com/ipython/ipython/issues/639>`_: Updating the credits file
20 * `639 <https://github.com/ipython/ipython/issues/639>`_: Updating the credits file
21 * `628 <https://github.com/ipython/ipython/issues/628>`_: import pexpect from IPython.external in irunner
21 * `628 <https://github.com/ipython/ipython/issues/628>`_: import pexpect from IPython.external in irunner
22 * `596 <https://github.com/ipython/ipython/issues/596>`_: Irunner
22 * `596 <https://github.com/ipython/ipython/issues/596>`_: Irunner
23 * `598 <https://github.com/ipython/ipython/issues/598>`_: Fix templates for CrashHandler
23 * `598 <https://github.com/ipython/ipython/issues/598>`_: Fix templates for CrashHandler
24 * `590 <https://github.com/ipython/ipython/issues/590>`_: Desktop
24 * `590 <https://github.com/ipython/ipython/issues/590>`_: Desktop
25 * `600 <https://github.com/ipython/ipython/issues/600>`_: Fix bug with non-ascii reprs inside pretty-printed lists.
25 * `600 <https://github.com/ipython/ipython/issues/600>`_: Fix bug with non-ascii reprs inside pretty-printed lists.
26 * `618 <https://github.com/ipython/ipython/issues/618>`_: I617
26 * `618 <https://github.com/ipython/ipython/issues/618>`_: I617
27 * `599 <https://github.com/ipython/ipython/issues/599>`_: Gui Qt example and docs
27 * `599 <https://github.com/ipython/ipython/issues/599>`_: Gui Qt example and docs
28 * `619 <https://github.com/ipython/ipython/issues/619>`_: manpage update
28 * `619 <https://github.com/ipython/ipython/issues/619>`_: manpage update
29 * `582 <https://github.com/ipython/ipython/issues/582>`_: Updating sympy profile to match the exec_lines of isympy.
29 * `582 <https://github.com/ipython/ipython/issues/582>`_: Updating sympy profile to match the exec_lines of isympy.
30 * `578 <https://github.com/ipython/ipython/issues/578>`_: Check to see if correct source for decorated functions can be displayed
30 * `578 <https://github.com/ipython/ipython/issues/578>`_: Check to see if correct source for decorated functions can be displayed
31 * `589 <https://github.com/ipython/ipython/issues/589>`_: issue 588
31 * `589 <https://github.com/ipython/ipython/issues/589>`_: issue 588
32 * `591 <https://github.com/ipython/ipython/issues/591>`_: simulate shell expansion on %run arguments, at least tilde expansion
32 * `591 <https://github.com/ipython/ipython/issues/591>`_: simulate shell expansion on %run arguments, at least tilde expansion
33 * `576 <https://github.com/ipython/ipython/issues/576>`_: Show message about %paste magic on an IndentationError
33 * `576 <https://github.com/ipython/ipython/issues/576>`_: Show message about %paste magic on an IndentationError
34 * `574 <https://github.com/ipython/ipython/issues/574>`_: Getcwdu
34 * `574 <https://github.com/ipython/ipython/issues/574>`_: Getcwdu
35 * `565 <https://github.com/ipython/ipython/issues/565>`_: don't move old config files, keep nagging the user
35 * `565 <https://github.com/ipython/ipython/issues/565>`_: don't move old config files, keep nagging the user
36 * `575 <https://github.com/ipython/ipython/issues/575>`_: Added more docstrings to IPython.zmq.session.
36 * `575 <https://github.com/ipython/ipython/issues/575>`_: Added more docstrings to IPython.zmq.session.
37 * `567 <https://github.com/ipython/ipython/issues/567>`_: fix trailing whitespace from resetting indentation
37 * `567 <https://github.com/ipython/ipython/issues/567>`_: fix trailing whitespace from resetting indentation
38 * `564 <https://github.com/ipython/ipython/issues/564>`_: Command line args in docs
38 * `564 <https://github.com/ipython/ipython/issues/564>`_: Command line args in docs
39 * `560 <https://github.com/ipython/ipython/issues/560>`_: reorder qt support in kernel
39 * `560 <https://github.com/ipython/ipython/issues/560>`_: reorder qt support in kernel
40 * `561 <https://github.com/ipython/ipython/issues/561>`_: command-line suggestions
40 * `561 <https://github.com/ipython/ipython/issues/561>`_: command-line suggestions
41 * `556 <https://github.com/ipython/ipython/issues/556>`_: qt_for_kernel: use matplotlib rcParams to decide between PyQt4 and PySide
41 * `556 <https://github.com/ipython/ipython/issues/556>`_: qt_for_kernel: use matplotlib rcParams to decide between PyQt4 and PySide
42 * `557 <https://github.com/ipython/ipython/issues/557>`_: Update usage.py to newapp
42 * `557 <https://github.com/ipython/ipython/issues/557>`_: Update usage.py to newapp
43 * `555 <https://github.com/ipython/ipython/issues/555>`_: Rm default old config
43 * `555 <https://github.com/ipython/ipython/issues/555>`_: Rm default old config
44 * `552 <https://github.com/ipython/ipython/issues/552>`_: update parallel code for py3k
44 * `552 <https://github.com/ipython/ipython/issues/552>`_: update parallel code for py3k
45 * `504 <https://github.com/ipython/ipython/issues/504>`_: Updating string formatting
45 * `504 <https://github.com/ipython/ipython/issues/504>`_: Updating string formatting
46 * `551 <https://github.com/ipython/ipython/issues/551>`_: Make pylab import all configurable
46 * `551 <https://github.com/ipython/ipython/issues/551>`_: Make pylab import all configurable
47 * `496 <https://github.com/ipython/ipython/issues/496>`_: Qt editing keybindings
47 * `496 <https://github.com/ipython/ipython/issues/496>`_: Qt editing keybindings
48 * `550 <https://github.com/ipython/ipython/issues/550>`_: Support v2 PyQt4 APIs and PySide in kernel's GUI support
48 * `550 <https://github.com/ipython/ipython/issues/550>`_: Support v2 PyQt4 APIs and PySide in kernel's GUI support
49 * `546 <https://github.com/ipython/ipython/issues/546>`_: doc update
49 * `546 <https://github.com/ipython/ipython/issues/546>`_: doc update
50 * `548 <https://github.com/ipython/ipython/issues/548>`_: Fix sympy profile to work with sympy 0.7.
50 * `548 <https://github.com/ipython/ipython/issues/548>`_: Fix sympy profile to work with sympy 0.7.
51 * `542 <https://github.com/ipython/ipython/issues/542>`_: issue 440
51 * `542 <https://github.com/ipython/ipython/issues/542>`_: issue 440
52 * `533 <https://github.com/ipython/ipython/issues/533>`_: Remove unused configobj and validate libraries from externals.
52 * `533 <https://github.com/ipython/ipython/issues/533>`_: Remove unused configobj and validate libraries from externals.
53 * `538 <https://github.com/ipython/ipython/issues/538>`_: fix various tests on Windows
53 * `538 <https://github.com/ipython/ipython/issues/538>`_: fix various tests on Windows
54 * `540 <https://github.com/ipython/ipython/issues/540>`_: support `-pylab` flag with deprecation warning
54 * `540 <https://github.com/ipython/ipython/issues/540>`_: support ``-pylab`` flag with deprecation warning
55 * `537 <https://github.com/ipython/ipython/issues/537>`_: Docs update
55 * `537 <https://github.com/ipython/ipython/issues/537>`_: Docs update
56 * `536 <https://github.com/ipython/ipython/issues/536>`_: `setup.py install` depends on setuptools on Windows
56 * `536 <https://github.com/ipython/ipython/issues/536>`_: ``setup.py install`` depends on setuptools on Windows
57 * `480 <https://github.com/ipython/ipython/issues/480>`_: Get help mid-command
57 * `480 <https://github.com/ipython/ipython/issues/480>`_: Get help mid-command
58 * `462 <https://github.com/ipython/ipython/issues/462>`_: Str and Bytes traitlets
58 * `462 <https://github.com/ipython/ipython/issues/462>`_: Str and Bytes traitlets
59 * `534 <https://github.com/ipython/ipython/issues/534>`_: Handle unicode properly in IPython.zmq.iostream
59 * `534 <https://github.com/ipython/ipython/issues/534>`_: Handle unicode properly in IPython.zmq.iostream
60 * `527 <https://github.com/ipython/ipython/issues/527>`_: ZMQ displayhook
60 * `527 <https://github.com/ipython/ipython/issues/527>`_: ZMQ displayhook
61 * `526 <https://github.com/ipython/ipython/issues/526>`_: Handle asynchronous output in Qt console
61 * `526 <https://github.com/ipython/ipython/issues/526>`_: Handle asynchronous output in Qt console
62 * `528 <https://github.com/ipython/ipython/issues/528>`_: Do not import deprecated functions from external decorators library.
62 * `528 <https://github.com/ipython/ipython/issues/528>`_: Do not import deprecated functions from external decorators library.
63 * `454 <https://github.com/ipython/ipython/issues/454>`_: New BaseIPythonApplication
63 * `454 <https://github.com/ipython/ipython/issues/454>`_: New BaseIPythonApplication
64 * `532 <https://github.com/ipython/ipython/issues/532>`_: Zmq unicode
64 * `532 <https://github.com/ipython/ipython/issues/532>`_: Zmq unicode
65 * `531 <https://github.com/ipython/ipython/issues/531>`_: Fix Parallel test
65 * `531 <https://github.com/ipython/ipython/issues/531>`_: Fix Parallel test
66 * `525 <https://github.com/ipython/ipython/issues/525>`_: fallback on lsof if otool not found in libedit detection
66 * `525 <https://github.com/ipython/ipython/issues/525>`_: fallback on lsof if otool not found in libedit detection
67 * `517 <https://github.com/ipython/ipython/issues/517>`_: Merge IPython.parallel.streamsession into IPython.zmq.session
67 * `517 <https://github.com/ipython/ipython/issues/517>`_: Merge IPython.parallel.streamsession into IPython.zmq.session
68 * `521 <https://github.com/ipython/ipython/issues/521>`_: use dict.get(key) instead of dict[key] for safety from KeyErrors
68 * `521 <https://github.com/ipython/ipython/issues/521>`_: use dict.get(key) instead of dict[key] for safety from KeyErrors
69 * `492 <https://github.com/ipython/ipython/issues/492>`_: add QtConsoleApp using newapplication
69 * `492 <https://github.com/ipython/ipython/issues/492>`_: add QtConsoleApp using newapplication
70 * `485 <https://github.com/ipython/ipython/issues/485>`_: terminal IPython with newapp
70 * `485 <https://github.com/ipython/ipython/issues/485>`_: terminal IPython with newapp
71 * `486 <https://github.com/ipython/ipython/issues/486>`_: Use newapp in parallel code
71 * `486 <https://github.com/ipython/ipython/issues/486>`_: Use newapp in parallel code
72 * `511 <https://github.com/ipython/ipython/issues/511>`_: Add a new line before displaying multiline strings in the Qt console.
72 * `511 <https://github.com/ipython/ipython/issues/511>`_: Add a new line before displaying multiline strings in the Qt console.
73 * `509 <https://github.com/ipython/ipython/issues/509>`_: i508
73 * `509 <https://github.com/ipython/ipython/issues/509>`_: i508
74 * `501 <https://github.com/ipython/ipython/issues/501>`_: ignore EINTR in channel loops
74 * `501 <https://github.com/ipython/ipython/issues/501>`_: ignore EINTR in channel loops
75 * `495 <https://github.com/ipython/ipython/issues/495>`_: Better selection of Qt bindings when QT_API is not specified
75 * `495 <https://github.com/ipython/ipython/issues/495>`_: Better selection of Qt bindings when QT_API is not specified
76 * `498 <https://github.com/ipython/ipython/issues/498>`_: Check for .pyd as extension for binary files.
76 * `498 <https://github.com/ipython/ipython/issues/498>`_: Check for .pyd as extension for binary files.
77 * `494 <https://github.com/ipython/ipython/issues/494>`_: QtConsole zoom adjustments
77 * `494 <https://github.com/ipython/ipython/issues/494>`_: QtConsole zoom adjustments
78 * `490 <https://github.com/ipython/ipython/issues/490>`_: fix UnicodeEncodeError writing SVG string to .svg file, fixes #489
78 * `490 <https://github.com/ipython/ipython/issues/490>`_: fix UnicodeEncodeError writing SVG string to .svg file, fixes #489
79 * `491 <https://github.com/ipython/ipython/issues/491>`_: add QtConsoleApp using newapplication
79 * `491 <https://github.com/ipython/ipython/issues/491>`_: add QtConsoleApp using newapplication
80 * `479 <https://github.com/ipython/ipython/issues/479>`_: embed() doesn't load default config
80 * `479 <https://github.com/ipython/ipython/issues/479>`_: embed() doesn't load default config
81 * `483 <https://github.com/ipython/ipython/issues/483>`_: Links launchpad -> github
81 * `483 <https://github.com/ipython/ipython/issues/483>`_: Links launchpad -> github
82 * `419 <https://github.com/ipython/ipython/issues/419>`_: %xdel magic
82 * `419 <https://github.com/ipython/ipython/issues/419>`_: %xdel magic
83 * `477 <https://github.com/ipython/ipython/issues/477>`_: Add \n to lines in the log
83 * `477 <https://github.com/ipython/ipython/issues/477>`_: Add \n to lines in the log
84 * `459 <https://github.com/ipython/ipython/issues/459>`_: use os.system for shell.system in Terminal frontend
84 * `459 <https://github.com/ipython/ipython/issues/459>`_: use os.system for shell.system in Terminal frontend
85 * `475 <https://github.com/ipython/ipython/issues/475>`_: i473
85 * `475 <https://github.com/ipython/ipython/issues/475>`_: i473
86 * `471 <https://github.com/ipython/ipython/issues/471>`_: Add test decorator onlyif_unicode_paths.
86 * `471 <https://github.com/ipython/ipython/issues/471>`_: Add test decorator onlyif_unicode_paths.
87 * `474 <https://github.com/ipython/ipython/issues/474>`_: Fix support for raw GTK and WX matplotlib backends.
87 * `474 <https://github.com/ipython/ipython/issues/474>`_: Fix support for raw GTK and WX matplotlib backends.
88 * `472 <https://github.com/ipython/ipython/issues/472>`_: Kernel event loop is robust against random SIGINT.
88 * `472 <https://github.com/ipython/ipython/issues/472>`_: Kernel event loop is robust against random SIGINT.
89 * `460 <https://github.com/ipython/ipython/issues/460>`_: Share code for magic_edit
89 * `460 <https://github.com/ipython/ipython/issues/460>`_: Share code for magic_edit
90 * `469 <https://github.com/ipython/ipython/issues/469>`_: Add exit code when running all tests with iptest.
90 * `469 <https://github.com/ipython/ipython/issues/469>`_: Add exit code when running all tests with iptest.
91 * `464 <https://github.com/ipython/ipython/issues/464>`_: Add home directory expansion to IPYTHON_DIR environment variables.
91 * `464 <https://github.com/ipython/ipython/issues/464>`_: Add home directory expansion to IPYTHON_DIR environment variables.
92 * `455 <https://github.com/ipython/ipython/issues/455>`_: Bugfix with logger
92 * `455 <https://github.com/ipython/ipython/issues/455>`_: Bugfix with logger
93 * `448 <https://github.com/ipython/ipython/issues/448>`_: Separate out skip_doctest decorator
93 * `448 <https://github.com/ipython/ipython/issues/448>`_: Separate out skip_doctest decorator
94 * `453 <https://github.com/ipython/ipython/issues/453>`_: Draft of new main BaseIPythonApplication.
94 * `453 <https://github.com/ipython/ipython/issues/453>`_: Draft of new main BaseIPythonApplication.
95 * `452 <https://github.com/ipython/ipython/issues/452>`_: Use list/tuple/dict/set subclass's overridden __repr__ instead of the pretty
95 * `452 <https://github.com/ipython/ipython/issues/452>`_: Use list/tuple/dict/set subclass's overridden __repr__ instead of the pretty
96 * `398 <https://github.com/ipython/ipython/issues/398>`_: allow toggle of svg/png inline figure format
96 * `398 <https://github.com/ipython/ipython/issues/398>`_: allow toggle of svg/png inline figure format
97 * `381 <https://github.com/ipython/ipython/issues/381>`_: Support inline PNGs of matplotlib plots
97 * `381 <https://github.com/ipython/ipython/issues/381>`_: Support inline PNGs of matplotlib plots
98 * `413 <https://github.com/ipython/ipython/issues/413>`_: Retries and Resubmit (#411 and #412)
98 * `413 <https://github.com/ipython/ipython/issues/413>`_: Retries and Resubmit (#411 and #412)
99 * `370 <https://github.com/ipython/ipython/issues/370>`_: Fixes to the display system
99 * `370 <https://github.com/ipython/ipython/issues/370>`_: Fixes to the display system
100 * `449 <https://github.com/ipython/ipython/issues/449>`_: Fix issue 447 - inspecting old-style classes.
100 * `449 <https://github.com/ipython/ipython/issues/449>`_: Fix issue 447 - inspecting old-style classes.
101 * `423 <https://github.com/ipython/ipython/issues/423>`_: Allow type checking on elements of List,Tuple,Set traits
101 * `423 <https://github.com/ipython/ipython/issues/423>`_: Allow type checking on elements of List,Tuple,Set traits
102 * `400 <https://github.com/ipython/ipython/issues/400>`_: Config5
102 * `400 <https://github.com/ipython/ipython/issues/400>`_: Config5
103 * `421 <https://github.com/ipython/ipython/issues/421>`_: Generalise mechanism to put text at the next prompt in the Qt console.
103 * `421 <https://github.com/ipython/ipython/issues/421>`_: Generalise mechanism to put text at the next prompt in the Qt console.
104 * `443 <https://github.com/ipython/ipython/issues/443>`_: pinfo code duplication
104 * `443 <https://github.com/ipython/ipython/issues/443>`_: pinfo code duplication
105 * `429 <https://github.com/ipython/ipython/issues/429>`_: add check_pid, and handle stale PID info in ipcluster.
105 * `429 <https://github.com/ipython/ipython/issues/429>`_: add check_pid, and handle stale PID info in ipcluster.
106 * `431 <https://github.com/ipython/ipython/issues/431>`_: Fix error message in test_irunner
106 * `431 <https://github.com/ipython/ipython/issues/431>`_: Fix error message in test_irunner
107 * `427 <https://github.com/ipython/ipython/issues/427>`_: handle different SyntaxError messages in test_irunner
107 * `427 <https://github.com/ipython/ipython/issues/427>`_: handle different SyntaxError messages in test_irunner
108 * `424 <https://github.com/ipython/ipython/issues/424>`_: Irunner test failure
108 * `424 <https://github.com/ipython/ipython/issues/424>`_: Irunner test failure
109 * `430 <https://github.com/ipython/ipython/issues/430>`_: Small parallel doc typo
109 * `430 <https://github.com/ipython/ipython/issues/430>`_: Small parallel doc typo
110 * `422 <https://github.com/ipython/ipython/issues/422>`_: Make ipython-qtconsole a GUI script
110 * `422 <https://github.com/ipython/ipython/issues/422>`_: Make ipython-qtconsole a GUI script
111 * `420 <https://github.com/ipython/ipython/issues/420>`_: Permit kernel std* to be redirected
111 * `420 <https://github.com/ipython/ipython/issues/420>`_: Permit kernel std* to be redirected
112 * `408 <https://github.com/ipython/ipython/issues/408>`_: History request
112 * `408 <https://github.com/ipython/ipython/issues/408>`_: History request
113 * `388 <https://github.com/ipython/ipython/issues/388>`_: Add Emacs-style kill ring to Qt console
113 * `388 <https://github.com/ipython/ipython/issues/388>`_: Add Emacs-style kill ring to Qt console
114 * `414 <https://github.com/ipython/ipython/issues/414>`_: Warn on old config files
114 * `414 <https://github.com/ipython/ipython/issues/414>`_: Warn on old config files
115 * `415 <https://github.com/ipython/ipython/issues/415>`_: Prevent prefilter from crashing IPython
115 * `415 <https://github.com/ipython/ipython/issues/415>`_: Prevent prefilter from crashing IPython
116 * `418 <https://github.com/ipython/ipython/issues/418>`_: Minor configuration doc fixes
116 * `418 <https://github.com/ipython/ipython/issues/418>`_: Minor configuration doc fixes
117 * `407 <https://github.com/ipython/ipython/issues/407>`_: Update What's new documentation
117 * `407 <https://github.com/ipython/ipython/issues/407>`_: Update What's new documentation
118 * `410 <https://github.com/ipython/ipython/issues/410>`_: Install notebook frontend
118 * `410 <https://github.com/ipython/ipython/issues/410>`_: Install notebook frontend
119 * `406 <https://github.com/ipython/ipython/issues/406>`_: install IPython.zmq.gui
119 * `406 <https://github.com/ipython/ipython/issues/406>`_: install IPython.zmq.gui
120 * `393 <https://github.com/ipython/ipython/issues/393>`_: ipdir unicode
120 * `393 <https://github.com/ipython/ipython/issues/393>`_: ipdir unicode
121 * `397 <https://github.com/ipython/ipython/issues/397>`_: utils.io.Term.cin/out/err -> utils.io.stdin/out/err
121 * `397 <https://github.com/ipython/ipython/issues/397>`_: utils.io.Term.cin/out/err -> utils.io.stdin/out/err
122 * `389 <https://github.com/ipython/ipython/issues/389>`_: DB fixes and Scheduler HWM
122 * `389 <https://github.com/ipython/ipython/issues/389>`_: DB fixes and Scheduler HWM
123 * `374 <https://github.com/ipython/ipython/issues/374>`_: Various Windows-related fixes to IPython.parallel
123 * `374 <https://github.com/ipython/ipython/issues/374>`_: Various Windows-related fixes to IPython.parallel
124 * `362 <https://github.com/ipython/ipython/issues/362>`_: fallback on defaultencoding if filesystemencoding is None
124 * `362 <https://github.com/ipython/ipython/issues/362>`_: fallback on defaultencoding if filesystemencoding is None
125 * `382 <https://github.com/ipython/ipython/issues/382>`_: Shell's reset method clears namespace from last %run command.
125 * `382 <https://github.com/ipython/ipython/issues/382>`_: Shell's reset method clears namespace from last %run command.
126 * `385 <https://github.com/ipython/ipython/issues/385>`_: Update iptest exclusions (fix #375)
126 * `385 <https://github.com/ipython/ipython/issues/385>`_: Update iptest exclusions (fix #375)
127 * `383 <https://github.com/ipython/ipython/issues/383>`_: Catch errors in querying readline which occur with pyreadline.
127 * `383 <https://github.com/ipython/ipython/issues/383>`_: Catch errors in querying readline which occur with pyreadline.
128 * `373 <https://github.com/ipython/ipython/issues/373>`_: Remove runlines etc.
128 * `373 <https://github.com/ipython/ipython/issues/373>`_: Remove runlines etc.
129 * `364 <https://github.com/ipython/ipython/issues/364>`_: Single output
129 * `364 <https://github.com/ipython/ipython/issues/364>`_: Single output
130 * `372 <https://github.com/ipython/ipython/issues/372>`_: Multiline input push
130 * `372 <https://github.com/ipython/ipython/issues/372>`_: Multiline input push
131 * `363 <https://github.com/ipython/ipython/issues/363>`_: Issue 125
131 * `363 <https://github.com/ipython/ipython/issues/363>`_: Issue 125
132 * `361 <https://github.com/ipython/ipython/issues/361>`_: don't rely on setuptools for readline dependency check
132 * `361 <https://github.com/ipython/ipython/issues/361>`_: don't rely on setuptools for readline dependency check
133 * `349 <https://github.com/ipython/ipython/issues/349>`_: Fix %autopx magic
133 * `349 <https://github.com/ipython/ipython/issues/349>`_: Fix %autopx magic
134 * `355 <https://github.com/ipython/ipython/issues/355>`_: History save thread
134 * `355 <https://github.com/ipython/ipython/issues/355>`_: History save thread
135 * `356 <https://github.com/ipython/ipython/issues/356>`_: Usability improvements to history in Qt console
135 * `356 <https://github.com/ipython/ipython/issues/356>`_: Usability improvements to history in Qt console
136 * `357 <https://github.com/ipython/ipython/issues/357>`_: Exit autocall
136 * `357 <https://github.com/ipython/ipython/issues/357>`_: Exit autocall
137 * `353 <https://github.com/ipython/ipython/issues/353>`_: Rewrite quit()/exit()/Quit()/Exit() calls as magic
137 * `353 <https://github.com/ipython/ipython/issues/353>`_: Rewrite quit()/exit()/Quit()/Exit() calls as magic
138 * `354 <https://github.com/ipython/ipython/issues/354>`_: Cell tweaks
138 * `354 <https://github.com/ipython/ipython/issues/354>`_: Cell tweaks
139 * `345 <https://github.com/ipython/ipython/issues/345>`_: Attempt to address (partly) issue ipython/#342 by rewriting quit(), exit(), etc.
139 * `345 <https://github.com/ipython/ipython/issues/345>`_: Attempt to address (partly) issue ipython/#342 by rewriting quit(), exit(), etc.
140 * `352 <https://github.com/ipython/ipython/issues/352>`_: #342: Try to recover as intelligently as possible if user calls magic().
140 * `352 <https://github.com/ipython/ipython/issues/352>`_: #342: Try to recover as intelligently as possible if user calls magic().
141 * `346 <https://github.com/ipython/ipython/issues/346>`_: Dedent prefix bugfix + tests: #142
141 * `346 <https://github.com/ipython/ipython/issues/346>`_: Dedent prefix bugfix + tests: #142
142 * `348 <https://github.com/ipython/ipython/issues/348>`_: %reset doesn't reset prompt number.
142 * `348 <https://github.com/ipython/ipython/issues/348>`_: %reset doesn't reset prompt number.
143 * `347 <https://github.com/ipython/ipython/issues/347>`_: Make ip.reset() work the same in interactive or non-interactive code.
143 * `347 <https://github.com/ipython/ipython/issues/347>`_: Make ip.reset() work the same in interactive or non-interactive code.
144 * `343 <https://github.com/ipython/ipython/issues/343>`_: make readline a dependency on OSX
144 * `343 <https://github.com/ipython/ipython/issues/343>`_: make readline a dependency on OSX
145 * `344 <https://github.com/ipython/ipython/issues/344>`_: restore auto debug behavior
145 * `344 <https://github.com/ipython/ipython/issues/344>`_: restore auto debug behavior
146 * `339 <https://github.com/ipython/ipython/issues/339>`_: fix for issue 337: incorrect/phantom tooltips for magics
146 * `339 <https://github.com/ipython/ipython/issues/339>`_: fix for issue 337: incorrect/phantom tooltips for magics
147 * `254 <https://github.com/ipython/ipython/issues/254>`_: newparallel branch (add zmq.parallel submodule)
147 * `254 <https://github.com/ipython/ipython/issues/254>`_: newparallel branch (add zmq.parallel submodule)
148 * `334 <https://github.com/ipython/ipython/issues/334>`_: Hard reset
148 * `334 <https://github.com/ipython/ipython/issues/334>`_: Hard reset
149 * `316 <https://github.com/ipython/ipython/issues/316>`_: Unicode win process
149 * `316 <https://github.com/ipython/ipython/issues/316>`_: Unicode win process
150 * `332 <https://github.com/ipython/ipython/issues/332>`_: AST splitter
150 * `332 <https://github.com/ipython/ipython/issues/332>`_: AST splitter
151 * `325 <https://github.com/ipython/ipython/issues/325>`_: Removetwisted
151 * `325 <https://github.com/ipython/ipython/issues/325>`_: Removetwisted
152 * `330 <https://github.com/ipython/ipython/issues/330>`_: Magic pastebin
152 * `330 <https://github.com/ipython/ipython/issues/330>`_: Magic pastebin
153 * `309 <https://github.com/ipython/ipython/issues/309>`_: Bug tests for GH Issues 238, 284, 306, 307. Skip module machinery if not installed. Known failures reported as 'K'
153 * `309 <https://github.com/ipython/ipython/issues/309>`_: Bug tests for GH Issues 238, 284, 306, 307. Skip module machinery if not installed. Known failures reported as 'K'
154 * `331 <https://github.com/ipython/ipython/issues/331>`_: Tweak config loader for PyPy compatibility.
154 * `331 <https://github.com/ipython/ipython/issues/331>`_: Tweak config loader for PyPy compatibility.
155 * `319 <https://github.com/ipython/ipython/issues/319>`_: Rewrite code to restore readline history after an action
155 * `319 <https://github.com/ipython/ipython/issues/319>`_: Rewrite code to restore readline history after an action
156 * `329 <https://github.com/ipython/ipython/issues/329>`_: Do not store file contents in history when running a .ipy file.
156 * `329 <https://github.com/ipython/ipython/issues/329>`_: Do not store file contents in history when running a .ipy file.
157 * `179 <https://github.com/ipython/ipython/issues/179>`_: Html notebook
157 * `179 <https://github.com/ipython/ipython/issues/179>`_: Html notebook
158 * `323 <https://github.com/ipython/ipython/issues/323>`_: Add missing external.pexpect to packages
158 * `323 <https://github.com/ipython/ipython/issues/323>`_: Add missing external.pexpect to packages
159 * `295 <https://github.com/ipython/ipython/issues/295>`_: Magic local scope
159 * `295 <https://github.com/ipython/ipython/issues/295>`_: Magic local scope
160 * `315 <https://github.com/ipython/ipython/issues/315>`_: Unicode magic args
160 * `315 <https://github.com/ipython/ipython/issues/315>`_: Unicode magic args
161 * `310 <https://github.com/ipython/ipython/issues/310>`_: allow Unicode Command-Line options
161 * `310 <https://github.com/ipython/ipython/issues/310>`_: allow Unicode Command-Line options
162 * `313 <https://github.com/ipython/ipython/issues/313>`_: Readline shortcuts
162 * `313 <https://github.com/ipython/ipython/issues/313>`_: Readline shortcuts
163 * `311 <https://github.com/ipython/ipython/issues/311>`_: Qtconsole exit
163 * `311 <https://github.com/ipython/ipython/issues/311>`_: Qtconsole exit
164 * `312 <https://github.com/ipython/ipython/issues/312>`_: History memory
164 * `312 <https://github.com/ipython/ipython/issues/312>`_: History memory
165 * `294 <https://github.com/ipython/ipython/issues/294>`_: Issue 290
165 * `294 <https://github.com/ipython/ipython/issues/294>`_: Issue 290
166 * `292 <https://github.com/ipython/ipython/issues/292>`_: Issue 31
166 * `292 <https://github.com/ipython/ipython/issues/292>`_: Issue 31
167 * `252 <https://github.com/ipython/ipython/issues/252>`_: Unicode issues
167 * `252 <https://github.com/ipython/ipython/issues/252>`_: Unicode issues
168 * `235 <https://github.com/ipython/ipython/issues/235>`_: Fix history magic command's bugs wrt to full history and add -O option to display full history
168 * `235 <https://github.com/ipython/ipython/issues/235>`_: Fix history magic command's bugs wrt to full history and add -O option to display full history
169 * `236 <https://github.com/ipython/ipython/issues/236>`_: History minus p flag
169 * `236 <https://github.com/ipython/ipython/issues/236>`_: History minus p flag
170 * `261 <https://github.com/ipython/ipython/issues/261>`_: Adapt magic commands to new history system.
170 * `261 <https://github.com/ipython/ipython/issues/261>`_: Adapt magic commands to new history system.
171 * `282 <https://github.com/ipython/ipython/issues/282>`_: SQLite history
171 * `282 <https://github.com/ipython/ipython/issues/282>`_: SQLite history
172 * `191 <https://github.com/ipython/ipython/issues/191>`_: Unbundle external libraries
172 * `191 <https://github.com/ipython/ipython/issues/191>`_: Unbundle external libraries
173 * `199 <https://github.com/ipython/ipython/issues/199>`_: Magic arguments
173 * `199 <https://github.com/ipython/ipython/issues/199>`_: Magic arguments
174 * `204 <https://github.com/ipython/ipython/issues/204>`_: Emacs completion bugfix
174 * `204 <https://github.com/ipython/ipython/issues/204>`_: Emacs completion bugfix
175 * `293 <https://github.com/ipython/ipython/issues/293>`_: Issue 133
175 * `293 <https://github.com/ipython/ipython/issues/293>`_: Issue 133
176 * `249 <https://github.com/ipython/ipython/issues/249>`_: Writing unicode characters to a log file. (IPython 0.10.2.git)
176 * `249 <https://github.com/ipython/ipython/issues/249>`_: Writing unicode characters to a log file. (IPython 0.10.2.git)
177 * `283 <https://github.com/ipython/ipython/issues/283>`_: Support for 256-color escape sequences in Qt console
177 * `283 <https://github.com/ipython/ipython/issues/283>`_: Support for 256-color escape sequences in Qt console
178 * `281 <https://github.com/ipython/ipython/issues/281>`_: Refactored and improved Qt console's HTML export facility
178 * `281 <https://github.com/ipython/ipython/issues/281>`_: Refactored and improved Qt console's HTML export facility
179 * `237 <https://github.com/ipython/ipython/issues/237>`_: Fix185 (take two)
179 * `237 <https://github.com/ipython/ipython/issues/237>`_: Fix185 (take two)
180 * `251 <https://github.com/ipython/ipython/issues/251>`_: Issue 129
180 * `251 <https://github.com/ipython/ipython/issues/251>`_: Issue 129
181 * `278 <https://github.com/ipython/ipython/issues/278>`_: add basic XDG_CONFIG_HOME support
181 * `278 <https://github.com/ipython/ipython/issues/278>`_: add basic XDG_CONFIG_HOME support
182 * `275 <https://github.com/ipython/ipython/issues/275>`_: inline pylab cuts off labels on log plots
182 * `275 <https://github.com/ipython/ipython/issues/275>`_: inline pylab cuts off labels on log plots
183 * `280 <https://github.com/ipython/ipython/issues/280>`_: Add %precision magic
183 * `280 <https://github.com/ipython/ipython/issues/280>`_: Add %precision magic
184 * `259 <https://github.com/ipython/ipython/issues/259>`_: Pyside support
184 * `259 <https://github.com/ipython/ipython/issues/259>`_: Pyside support
185 * `193 <https://github.com/ipython/ipython/issues/193>`_: Make ipython cProfile-able
185 * `193 <https://github.com/ipython/ipython/issues/193>`_: Make ipython cProfile-able
186 * `272 <https://github.com/ipython/ipython/issues/272>`_: Magic examples
186 * `272 <https://github.com/ipython/ipython/issues/272>`_: Magic examples
187 * `219 <https://github.com/ipython/ipython/issues/219>`_: Doc magic pycat
187 * `219 <https://github.com/ipython/ipython/issues/219>`_: Doc magic pycat
188 * `221 <https://github.com/ipython/ipython/issues/221>`_: Doc magic alias
188 * `221 <https://github.com/ipython/ipython/issues/221>`_: Doc magic alias
189 * `230 <https://github.com/ipython/ipython/issues/230>`_: Doc magic edit
189 * `230 <https://github.com/ipython/ipython/issues/230>`_: Doc magic edit
190 * `224 <https://github.com/ipython/ipython/issues/224>`_: Doc magic cpaste
190 * `224 <https://github.com/ipython/ipython/issues/224>`_: Doc magic cpaste
191 * `229 <https://github.com/ipython/ipython/issues/229>`_: Doc magic pdef
191 * `229 <https://github.com/ipython/ipython/issues/229>`_: Doc magic pdef
192 * `273 <https://github.com/ipython/ipython/issues/273>`_: Docs build
192 * `273 <https://github.com/ipython/ipython/issues/273>`_: Docs build
193 * `228 <https://github.com/ipython/ipython/issues/228>`_: Doc magic who
193 * `228 <https://github.com/ipython/ipython/issues/228>`_: Doc magic who
194 * `233 <https://github.com/ipython/ipython/issues/233>`_: Doc magic cd
194 * `233 <https://github.com/ipython/ipython/issues/233>`_: Doc magic cd
195 * `226 <https://github.com/ipython/ipython/issues/226>`_: Doc magic pwd
195 * `226 <https://github.com/ipython/ipython/issues/226>`_: Doc magic pwd
196 * `218 <https://github.com/ipython/ipython/issues/218>`_: Doc magic history
196 * `218 <https://github.com/ipython/ipython/issues/218>`_: Doc magic history
197 * `231 <https://github.com/ipython/ipython/issues/231>`_: Doc magic reset
197 * `231 <https://github.com/ipython/ipython/issues/231>`_: Doc magic reset
198 * `225 <https://github.com/ipython/ipython/issues/225>`_: Doc magic save
198 * `225 <https://github.com/ipython/ipython/issues/225>`_: Doc magic save
199 * `222 <https://github.com/ipython/ipython/issues/222>`_: Doc magic timeit
199 * `222 <https://github.com/ipython/ipython/issues/222>`_: Doc magic timeit
200 * `223 <https://github.com/ipython/ipython/issues/223>`_: Doc magic colors
200 * `223 <https://github.com/ipython/ipython/issues/223>`_: Doc magic colors
201 * `203 <https://github.com/ipython/ipython/issues/203>`_: Small typos in zmq/blockingkernelmanager.py
201 * `203 <https://github.com/ipython/ipython/issues/203>`_: Small typos in zmq/blockingkernelmanager.py
202 * `227 <https://github.com/ipython/ipython/issues/227>`_: Doc magic logon
202 * `227 <https://github.com/ipython/ipython/issues/227>`_: Doc magic logon
203 * `232 <https://github.com/ipython/ipython/issues/232>`_: Doc magic profile
203 * `232 <https://github.com/ipython/ipython/issues/232>`_: Doc magic profile
204 * `264 <https://github.com/ipython/ipython/issues/264>`_: Kernel logging
204 * `264 <https://github.com/ipython/ipython/issues/264>`_: Kernel logging
205 * `220 <https://github.com/ipython/ipython/issues/220>`_: Doc magic edit
205 * `220 <https://github.com/ipython/ipython/issues/220>`_: Doc magic edit
206 * `268 <https://github.com/ipython/ipython/issues/268>`_: PyZMQ >= 2.0.10
206 * `268 <https://github.com/ipython/ipython/issues/268>`_: PyZMQ >= 2.0.10
207 * `267 <https://github.com/ipython/ipython/issues/267>`_: GitHub Pages (again)
207 * `267 <https://github.com/ipython/ipython/issues/267>`_: GitHub Pages (again)
208 * `266 <https://github.com/ipython/ipython/issues/266>`_: OSX-specific fixes to the Qt console
208 * `266 <https://github.com/ipython/ipython/issues/266>`_: OSX-specific fixes to the Qt console
209 * `255 <https://github.com/ipython/ipython/issues/255>`_: Gitwash typo
209 * `255 <https://github.com/ipython/ipython/issues/255>`_: Gitwash typo
210 * `265 <https://github.com/ipython/ipython/issues/265>`_: Fix string input2
210 * `265 <https://github.com/ipython/ipython/issues/265>`_: Fix string input2
211 * `260 <https://github.com/ipython/ipython/issues/260>`_: Kernel crash with empty history
211 * `260 <https://github.com/ipython/ipython/issues/260>`_: Kernel crash with empty history
212 * `243 <https://github.com/ipython/ipython/issues/243>`_: New display system
212 * `243 <https://github.com/ipython/ipython/issues/243>`_: New display system
213 * `242 <https://github.com/ipython/ipython/issues/242>`_: Fix terminal exit
213 * `242 <https://github.com/ipython/ipython/issues/242>`_: Fix terminal exit
214 * `250 <https://github.com/ipython/ipython/issues/250>`_: always use Session.send
214 * `250 <https://github.com/ipython/ipython/issues/250>`_: always use Session.send
215 * `239 <https://github.com/ipython/ipython/issues/239>`_: Makefile command & script for GitHub Pages
215 * `239 <https://github.com/ipython/ipython/issues/239>`_: Makefile command & script for GitHub Pages
216 * `244 <https://github.com/ipython/ipython/issues/244>`_: My exit
216 * `244 <https://github.com/ipython/ipython/issues/244>`_: My exit
217 * `234 <https://github.com/ipython/ipython/issues/234>`_: Timed history save
217 * `234 <https://github.com/ipython/ipython/issues/234>`_: Timed history save
218 * `217 <https://github.com/ipython/ipython/issues/217>`_: Doc magic lsmagic
218 * `217 <https://github.com/ipython/ipython/issues/217>`_: Doc magic lsmagic
219 * `215 <https://github.com/ipython/ipython/issues/215>`_: History fix
219 * `215 <https://github.com/ipython/ipython/issues/215>`_: History fix
220 * `195 <https://github.com/ipython/ipython/issues/195>`_: Formatters
220 * `195 <https://github.com/ipython/ipython/issues/195>`_: Formatters
221 * `192 <https://github.com/ipython/ipython/issues/192>`_: Ready colorize bug
221 * `192 <https://github.com/ipython/ipython/issues/192>`_: Ready colorize bug
222 * `198 <https://github.com/ipython/ipython/issues/198>`_: Windows workdir
222 * `198 <https://github.com/ipython/ipython/issues/198>`_: Windows workdir
223 * `174 <https://github.com/ipython/ipython/issues/174>`_: Whitespace cleanup
223 * `174 <https://github.com/ipython/ipython/issues/174>`_: Whitespace cleanup
224 * `188 <https://github.com/ipython/ipython/issues/188>`_: Version info: update our version management system to use git.
224 * `188 <https://github.com/ipython/ipython/issues/188>`_: Version info: update our version management system to use git.
225 * `158 <https://github.com/ipython/ipython/issues/158>`_: Ready for merge
225 * `158 <https://github.com/ipython/ipython/issues/158>`_: Ready for merge
226 * `187 <https://github.com/ipython/ipython/issues/187>`_: Resolved Print shortcut collision with ctrl-P emacs binding
226 * `187 <https://github.com/ipython/ipython/issues/187>`_: Resolved Print shortcut collision with ctrl-P emacs binding
227 * `183 <https://github.com/ipython/ipython/issues/183>`_: cleanup of exit/quit commands for qt console
227 * `183 <https://github.com/ipython/ipython/issues/183>`_: cleanup of exit/quit commands for qt console
228 * `184 <https://github.com/ipython/ipython/issues/184>`_: Logo added to sphinx docs
228 * `184 <https://github.com/ipython/ipython/issues/184>`_: Logo added to sphinx docs
229 * `180 <https://github.com/ipython/ipython/issues/180>`_: Cleanup old code
229 * `180 <https://github.com/ipython/ipython/issues/180>`_: Cleanup old code
230 * `171 <https://github.com/ipython/ipython/issues/171>`_: Expose Pygments styles as options
230 * `171 <https://github.com/ipython/ipython/issues/171>`_: Expose Pygments styles as options
231 * `170 <https://github.com/ipython/ipython/issues/170>`_: HTML Fixes
231 * `170 <https://github.com/ipython/ipython/issues/170>`_: HTML Fixes
232 * `172 <https://github.com/ipython/ipython/issues/172>`_: Fix del method exit test
232 * `172 <https://github.com/ipython/ipython/issues/172>`_: Fix del method exit test
233 * `164 <https://github.com/ipython/ipython/issues/164>`_: Qt frontend shutdown behavior fixes and enhancements
233 * `164 <https://github.com/ipython/ipython/issues/164>`_: Qt frontend shutdown behavior fixes and enhancements
234 * `167 <https://github.com/ipython/ipython/issues/167>`_: Added HTML export
234 * `167 <https://github.com/ipython/ipython/issues/167>`_: Added HTML export
235 * `163 <https://github.com/ipython/ipython/issues/163>`_: Execution refactor
235 * `163 <https://github.com/ipython/ipython/issues/163>`_: Execution refactor
236 * `159 <https://github.com/ipython/ipython/issues/159>`_: Ipy3 preparation
236 * `159 <https://github.com/ipython/ipython/issues/159>`_: Ipy3 preparation
237 * `155 <https://github.com/ipython/ipython/issues/155>`_: Ready startup fix
237 * `155 <https://github.com/ipython/ipython/issues/155>`_: Ready startup fix
238 * `152 <https://github.com/ipython/ipython/issues/152>`_: 0.10.1 sge
238 * `152 <https://github.com/ipython/ipython/issues/152>`_: 0.10.1 sge
239 * `151 <https://github.com/ipython/ipython/issues/151>`_: mk_object_info -> object_info
239 * `151 <https://github.com/ipython/ipython/issues/151>`_: mk_object_info -> object_info
240 * `149 <https://github.com/ipython/ipython/issues/149>`_: Simple bug-fix
240 * `149 <https://github.com/ipython/ipython/issues/149>`_: Simple bug-fix
241
241
242 Regular issues (285):
242 Regular issues (285):
243
243
244 * `630 <https://github.com/ipython/ipython/issues/630>`_: new.py in pwd prevents ipython from starting
244 * `630 <https://github.com/ipython/ipython/issues/630>`_: new.py in pwd prevents ipython from starting
245 * `623 <https://github.com/ipython/ipython/issues/623>`_: Execute DirectView commands while running LoadBalancedView tasks
245 * `623 <https://github.com/ipython/ipython/issues/623>`_: Execute DirectView commands while running LoadBalancedView tasks
246 * `437 <https://github.com/ipython/ipython/issues/437>`_: Users should have autocompletion in the notebook
246 * `437 <https://github.com/ipython/ipython/issues/437>`_: Users should have autocompletion in the notebook
247 * `583 <https://github.com/ipython/ipython/issues/583>`_: update manpages
247 * `583 <https://github.com/ipython/ipython/issues/583>`_: update manpages
248 * `594 <https://github.com/ipython/ipython/issues/594>`_: irunner command line options defer to file extensions
248 * `594 <https://github.com/ipython/ipython/issues/594>`_: irunner command line options defer to file extensions
249 * `603 <https://github.com/ipython/ipython/issues/603>`_: Users should see colored text in tracebacks and the pager
249 * `603 <https://github.com/ipython/ipython/issues/603>`_: Users should see colored text in tracebacks and the pager
250 * `597 <https://github.com/ipython/ipython/issues/597>`_: UnicodeDecodeError: 'ascii' codec can't decode byte 0xc2
250 * `597 <https://github.com/ipython/ipython/issues/597>`_: UnicodeDecodeError: 'ascii' codec can't decode byte 0xc2
251 * `608 <https://github.com/ipython/ipython/issues/608>`_: Organize and layout buttons in the notebook panel sections
251 * `608 <https://github.com/ipython/ipython/issues/608>`_: Organize and layout buttons in the notebook panel sections
252 * `609 <https://github.com/ipython/ipython/issues/609>`_: Implement controls in the Kernel panel section
252 * `609 <https://github.com/ipython/ipython/issues/609>`_: Implement controls in the Kernel panel section
253 * `611 <https://github.com/ipython/ipython/issues/611>`_: Add kernel status widget back to notebook
253 * `611 <https://github.com/ipython/ipython/issues/611>`_: Add kernel status widget back to notebook
254 * `610 <https://github.com/ipython/ipython/issues/610>`_: Implement controls in the Cell section panel
254 * `610 <https://github.com/ipython/ipython/issues/610>`_: Implement controls in the Cell section panel
255 * `612 <https://github.com/ipython/ipython/issues/612>`_: Implement Help panel section
255 * `612 <https://github.com/ipython/ipython/issues/612>`_: Implement Help panel section
256 * `621 <https://github.com/ipython/ipython/issues/621>`_: [qtconsole] on windows xp, cannot PageUp more than once
256 * `621 <https://github.com/ipython/ipython/issues/621>`_: [qtconsole] on windows xp, cannot PageUp more than once
257 * `616 <https://github.com/ipython/ipython/issues/616>`_: Store exit status of last command
257 * `616 <https://github.com/ipython/ipython/issues/616>`_: Store exit status of last command
258 * `605 <https://github.com/ipython/ipython/issues/605>`_: Users should be able to open different notebooks in the cwd
258 * `605 <https://github.com/ipython/ipython/issues/605>`_: Users should be able to open different notebooks in the cwd
259 * `302 <https://github.com/ipython/ipython/issues/302>`_: Users should see a consistent behavior in the Out prompt in the html notebook
259 * `302 <https://github.com/ipython/ipython/issues/302>`_: Users should see a consistent behavior in the Out prompt in the html notebook
260 * `435 <https://github.com/ipython/ipython/issues/435>`_: Notebook should not import anything by default
260 * `435 <https://github.com/ipython/ipython/issues/435>`_: Notebook should not import anything by default
261 * `595 <https://github.com/ipython/ipython/issues/595>`_: qtconsole command issue
261 * `595 <https://github.com/ipython/ipython/issues/595>`_: qtconsole command issue
262 * `588 <https://github.com/ipython/ipython/issues/588>`_: ipython-qtconsole uses 100% CPU
262 * `588 <https://github.com/ipython/ipython/issues/588>`_: ipython-qtconsole uses 100% CPU
263 * `586 <https://github.com/ipython/ipython/issues/586>`_: ? + plot() Command B0rks QTConsole Strangely
263 * `586 <https://github.com/ipython/ipython/issues/586>`_: ? + plot() Command B0rks QTConsole Strangely
264 * `585 <https://github.com/ipython/ipython/issues/585>`_: %pdoc throws Errors for classes without __init__ or docstring
264 * `585 <https://github.com/ipython/ipython/issues/585>`_: %pdoc throws Errors for classes without __init__ or docstring
265 * `584 <https://github.com/ipython/ipython/issues/584>`_: %pdoc throws TypeError
265 * `584 <https://github.com/ipython/ipython/issues/584>`_: %pdoc throws TypeError
266 * `580 <https://github.com/ipython/ipython/issues/580>`_: Client instantiation AssertionError
266 * `580 <https://github.com/ipython/ipython/issues/580>`_: Client instantiation AssertionError
267 * `569 <https://github.com/ipython/ipython/issues/569>`_: UnicodeDecodeError during startup
267 * `569 <https://github.com/ipython/ipython/issues/569>`_: UnicodeDecodeError during startup
268 * `572 <https://github.com/ipython/ipython/issues/572>`_: Indented command hits error
268 * `572 <https://github.com/ipython/ipython/issues/572>`_: Indented command hits error
269 * `573 <https://github.com/ipython/ipython/issues/573>`_: -wthread breaks indented top-level statements
269 * `573 <https://github.com/ipython/ipython/issues/573>`_: -wthread breaks indented top-level statements
270 * `570 <https://github.com/ipython/ipython/issues/570>`_: "--pylab inline" vs. "--pylab=inline"
270 * `570 <https://github.com/ipython/ipython/issues/570>`_: "--pylab inline" vs. "--pylab=inline"
271 * `566 <https://github.com/ipython/ipython/issues/566>`_: Can't use exec_file in config file
271 * `566 <https://github.com/ipython/ipython/issues/566>`_: Can't use exec_file in config file
272 * `562 <https://github.com/ipython/ipython/issues/562>`_: update docs to reflect '--args=values'
272 * `562 <https://github.com/ipython/ipython/issues/562>`_: update docs to reflect '--args=values'
273 * `558 <https://github.com/ipython/ipython/issues/558>`_: triple quote and %s at beginning of line
273 * `558 <https://github.com/ipython/ipython/issues/558>`_: triple quote and %s at beginning of line
274 * `554 <https://github.com/ipython/ipython/issues/554>`_: Update 0.11 docs to explain Qt console and how to do a clean install
274 * `554 <https://github.com/ipython/ipython/issues/554>`_: Update 0.11 docs to explain Qt console and how to do a clean install
275 * `553 <https://github.com/ipython/ipython/issues/553>`_: embed() fails if config files not installed
275 * `553 <https://github.com/ipython/ipython/issues/553>`_: embed() fails if config files not installed
276 * `8 <https://github.com/ipython/ipython/issues/8>`_: Ensure %gui qt works with new Mayavi and pylab
276 * `8 <https://github.com/ipython/ipython/issues/8>`_: Ensure %gui qt works with new Mayavi and pylab
277 * `269 <https://github.com/ipython/ipython/issues/269>`_: Provide compatibility api for IPython.Shell().start().mainloop()
277 * `269 <https://github.com/ipython/ipython/issues/269>`_: Provide compatibility api for IPython.Shell().start().mainloop()
278 * `66 <https://github.com/ipython/ipython/issues/66>`_: Update the main What's New document to reflect work on 0.11
278 * `66 <https://github.com/ipython/ipython/issues/66>`_: Update the main What's New document to reflect work on 0.11
279 * `549 <https://github.com/ipython/ipython/issues/549>`_: Don't check for 'linux2' value in sys.platform
279 * `549 <https://github.com/ipython/ipython/issues/549>`_: Don't check for 'linux2' value in sys.platform
280 * `505 <https://github.com/ipython/ipython/issues/505>`_: Qt windows created within imported functions won't show()
280 * `505 <https://github.com/ipython/ipython/issues/505>`_: Qt windows created within imported functions won't show()
281 * `545 <https://github.com/ipython/ipython/issues/545>`_: qtconsole ignores exec_lines
281 * `545 <https://github.com/ipython/ipython/issues/545>`_: qtconsole ignores exec_lines
282 * `371 <https://github.com/ipython/ipython/issues/371>`_: segfault in qtconsole when kernel quits
282 * `371 <https://github.com/ipython/ipython/issues/371>`_: segfault in qtconsole when kernel quits
283 * `377 <https://github.com/ipython/ipython/issues/377>`_: Failure: error (nothing to repeat)
283 * `377 <https://github.com/ipython/ipython/issues/377>`_: Failure: error (nothing to repeat)
284 * `544 <https://github.com/ipython/ipython/issues/544>`_: Ipython qtconsole pylab config issue.
284 * `544 <https://github.com/ipython/ipython/issues/544>`_: Ipython qtconsole pylab config issue.
285 * `543 <https://github.com/ipython/ipython/issues/543>`_: RuntimeError in completer
285 * `543 <https://github.com/ipython/ipython/issues/543>`_: RuntimeError in completer
286 * `440 <https://github.com/ipython/ipython/issues/440>`_: %run filename autocompletion "The kernel heartbeat has been inactive ... " error
286 * `440 <https://github.com/ipython/ipython/issues/440>`_: %run filename autocompletion "The kernel heartbeat has been inactive ... " error
287 * `541 <https://github.com/ipython/ipython/issues/541>`_: log_level is broken in the ipython Application
287 * `541 <https://github.com/ipython/ipython/issues/541>`_: log_level is broken in the ipython Application
288 * `369 <https://github.com/ipython/ipython/issues/369>`_: windows source install doesn't create scripts correctly
288 * `369 <https://github.com/ipython/ipython/issues/369>`_: windows source install doesn't create scripts correctly
289 * `351 <https://github.com/ipython/ipython/issues/351>`_: Make sure that the Windows installer handles the top-level IPython scripts.
289 * `351 <https://github.com/ipython/ipython/issues/351>`_: Make sure that the Windows installer handles the top-level IPython scripts.
290 * `512 <https://github.com/ipython/ipython/issues/512>`_: Two displayhooks in zmq
290 * `512 <https://github.com/ipython/ipython/issues/512>`_: Two displayhooks in zmq
291 * `340 <https://github.com/ipython/ipython/issues/340>`_: Make sure that the Windows HPC scheduler support is working for 0.11
291 * `340 <https://github.com/ipython/ipython/issues/340>`_: Make sure that the Windows HPC scheduler support is working for 0.11
292 * `98 <https://github.com/ipython/ipython/issues/98>`_: Should be able to get help on an object mid-command
292 * `98 <https://github.com/ipython/ipython/issues/98>`_: Should be able to get help on an object mid-command
293 * `529 <https://github.com/ipython/ipython/issues/529>`_: unicode problem in qtconsole for windows
293 * `529 <https://github.com/ipython/ipython/issues/529>`_: unicode problem in qtconsole for windows
294 * `476 <https://github.com/ipython/ipython/issues/476>`_: Separate input area in Qt Console
294 * `476 <https://github.com/ipython/ipython/issues/476>`_: Separate input area in Qt Console
295 * `175 <https://github.com/ipython/ipython/issues/175>`_: Qt console needs configuration support
295 * `175 <https://github.com/ipython/ipython/issues/175>`_: Qt console needs configuration support
296 * `156 <https://github.com/ipython/ipython/issues/156>`_: Key history lost when debugging program crash
296 * `156 <https://github.com/ipython/ipython/issues/156>`_: Key history lost when debugging program crash
297 * `470 <https://github.com/ipython/ipython/issues/470>`_: decorator: uses deprecated features
297 * `470 <https://github.com/ipython/ipython/issues/470>`_: decorator: uses deprecated features
298 * `30 <https://github.com/ipython/ipython/issues/30>`_: readline in OS X does not have correct key bindings
298 * `30 <https://github.com/ipython/ipython/issues/30>`_: readline in OS X does not have correct key bindings
299 * `503 <https://github.com/ipython/ipython/issues/503>`_: merge IPython.parallel.streamsession and IPython.zmq.session
299 * `503 <https://github.com/ipython/ipython/issues/503>`_: merge IPython.parallel.streamsession and IPython.zmq.session
300 * `456 <https://github.com/ipython/ipython/issues/456>`_: pathname in document punctuated by dots not slashes
300 * `456 <https://github.com/ipython/ipython/issues/456>`_: pathname in document punctuated by dots not slashes
301 * `451 <https://github.com/ipython/ipython/issues/451>`_: Allow switching the default image format for inline mpl backend
301 * `451 <https://github.com/ipython/ipython/issues/451>`_: Allow switching the default image format for inline mpl backend
302 * `79 <https://github.com/ipython/ipython/issues/79>`_: Implement more robust handling of config stages in Application
302 * `79 <https://github.com/ipython/ipython/issues/79>`_: Implement more robust handling of config stages in Application
303 * `522 <https://github.com/ipython/ipython/issues/522>`_: Encoding problems
303 * `522 <https://github.com/ipython/ipython/issues/522>`_: Encoding problems
304 * `524 <https://github.com/ipython/ipython/issues/524>`_: otool should not be unconditionally called on osx
304 * `524 <https://github.com/ipython/ipython/issues/524>`_: otool should not be unconditionally called on osx
305 * `523 <https://github.com/ipython/ipython/issues/523>`_: Get profile and config file inheritance working
305 * `523 <https://github.com/ipython/ipython/issues/523>`_: Get profile and config file inheritance working
306 * `519 <https://github.com/ipython/ipython/issues/519>`_: qtconsole --pure: "TypeError: string indices must be integers, not str"
306 * `519 <https://github.com/ipython/ipython/issues/519>`_: qtconsole --pure: "TypeError: string indices must be integers, not str"
307 * `516 <https://github.com/ipython/ipython/issues/516>`_: qtconsole --pure: "KeyError: 'ismagic'"
307 * `516 <https://github.com/ipython/ipython/issues/516>`_: qtconsole --pure: "KeyError: 'ismagic'"
308 * `520 <https://github.com/ipython/ipython/issues/520>`_: qtconsole --pure: "TypeError: string indices must be integers, not str"
308 * `520 <https://github.com/ipython/ipython/issues/520>`_: qtconsole --pure: "TypeError: string indices must be integers, not str"
309 * `450 <https://github.com/ipython/ipython/issues/450>`_: resubmitted tasks sometimes stuck as pending
309 * `450 <https://github.com/ipython/ipython/issues/450>`_: resubmitted tasks sometimes stuck as pending
310 * `518 <https://github.com/ipython/ipython/issues/518>`_: JSON serialization problems with ObjectId type (MongoDB)
310 * `518 <https://github.com/ipython/ipython/issues/518>`_: JSON serialization problems with ObjectId type (MongoDB)
311 * `178 <https://github.com/ipython/ipython/issues/178>`_: Channels should be named for their function, not their socket type
311 * `178 <https://github.com/ipython/ipython/issues/178>`_: Channels should be named for their function, not their socket type
312 * `515 <https://github.com/ipython/ipython/issues/515>`_: [ipcluster] termination on os x
312 * `515 <https://github.com/ipython/ipython/issues/515>`_: [ipcluster] termination on os x
313 * `510 <https://github.com/ipython/ipython/issues/510>`_: qtconsole: indentation problem printing numpy arrays
313 * `510 <https://github.com/ipython/ipython/issues/510>`_: qtconsole: indentation problem printing numpy arrays
314 * `508 <https://github.com/ipython/ipython/issues/508>`_: "AssertionError: Missing message part." in ipython-qtconsole --pure
314 * `508 <https://github.com/ipython/ipython/issues/508>`_: "AssertionError: Missing message part." in ipython-qtconsole --pure
315 * `499 <https://github.com/ipython/ipython/issues/499>`_: "ZMQError: Interrupted system call" when saving inline figure
315 * `499 <https://github.com/ipython/ipython/issues/499>`_: "ZMQError: Interrupted system call" when saving inline figure
316 * `426 <https://github.com/ipython/ipython/issues/426>`_: %edit magic fails in qtconsole
316 * `426 <https://github.com/ipython/ipython/issues/426>`_: %edit magic fails in qtconsole
317 * `497 <https://github.com/ipython/ipython/issues/497>`_: Don't show info from .pyd files
317 * `497 <https://github.com/ipython/ipython/issues/497>`_: Don't show info from .pyd files
318 * `493 <https://github.com/ipython/ipython/issues/493>`_: QFont::setPointSize: Point size <= 0 (0), must be greater than 0
318 * `493 <https://github.com/ipython/ipython/issues/493>`_: QFont::setPointSize: Point size <= 0 (0), must be greater than 0
319 * `489 <https://github.com/ipython/ipython/issues/489>`_: UnicodeEncodeError in qt.svg.save_svg
319 * `489 <https://github.com/ipython/ipython/issues/489>`_: UnicodeEncodeError in qt.svg.save_svg
320 * `458 <https://github.com/ipython/ipython/issues/458>`_: embed() doesn't load default config
320 * `458 <https://github.com/ipython/ipython/issues/458>`_: embed() doesn't load default config
321 * `488 <https://github.com/ipython/ipython/issues/488>`_: Using IPython with RubyPython leads to problems with IPython.parallel.client.client.Client.__init()
321 * `488 <https://github.com/ipython/ipython/issues/488>`_: Using IPython with RubyPython leads to problems with IPython.parallel.client.client.Client.__init()
322 * `401 <https://github.com/ipython/ipython/issues/401>`_: Race condition when running lbview.apply() fast multiple times in loop
322 * `401 <https://github.com/ipython/ipython/issues/401>`_: Race condition when running lbview.apply() fast multiple times in loop
323 * `168 <https://github.com/ipython/ipython/issues/168>`_: Scrub Launchpad links from code, docs
323 * `168 <https://github.com/ipython/ipython/issues/168>`_: Scrub Launchpad links from code, docs
324 * `141 <https://github.com/ipython/ipython/issues/141>`_: garbage collection problem (revisited)
324 * `141 <https://github.com/ipython/ipython/issues/141>`_: garbage collection problem (revisited)
325 * `59 <https://github.com/ipython/ipython/issues/59>`_: test_magic.test_obj_del fails on win32
325 * `59 <https://github.com/ipython/ipython/issues/59>`_: test_magic.test_obj_del fails on win32
326 * `457 <https://github.com/ipython/ipython/issues/457>`_: Backgrounded Tasks not Allowed? (but easy to slip by . . .)
326 * `457 <https://github.com/ipython/ipython/issues/457>`_: Backgrounded Tasks not Allowed? (but easy to slip by . . .)
327 * `297 <https://github.com/ipython/ipython/issues/297>`_: Shouldn't use pexpect for subprocesses in in-process terminal frontend
327 * `297 <https://github.com/ipython/ipython/issues/297>`_: Shouldn't use pexpect for subprocesses in in-process terminal frontend
328 * `110 <https://github.com/ipython/ipython/issues/110>`_: magic to return exit status
328 * `110 <https://github.com/ipython/ipython/issues/110>`_: magic to return exit status
329 * `473 <https://github.com/ipython/ipython/issues/473>`_: OSX readline detection fails in the debugger
329 * `473 <https://github.com/ipython/ipython/issues/473>`_: OSX readline detection fails in the debugger
330 * `466 <https://github.com/ipython/ipython/issues/466>`_: tests fail without unicode filename support
330 * `466 <https://github.com/ipython/ipython/issues/466>`_: tests fail without unicode filename support
331 * `468 <https://github.com/ipython/ipython/issues/468>`_: iptest script has 0 exit code even when tests fail
331 * `468 <https://github.com/ipython/ipython/issues/468>`_: iptest script has 0 exit code even when tests fail
332 * `465 <https://github.com/ipython/ipython/issues/465>`_: client.db_query() behaves different with SQLite and MongoDB
332 * `465 <https://github.com/ipython/ipython/issues/465>`_: client.db_query() behaves different with SQLite and MongoDB
333 * `467 <https://github.com/ipython/ipython/issues/467>`_: magic_install_default_config test fails when there is no .ipython directory
333 * `467 <https://github.com/ipython/ipython/issues/467>`_: magic_install_default_config test fails when there is no .ipython directory
334 * `463 <https://github.com/ipython/ipython/issues/463>`_: IPYTHON_DIR (and IPYTHONDIR) don't expand tilde to '~' directory
334 * `463 <https://github.com/ipython/ipython/issues/463>`_: IPYTHON_DIR (and IPYTHONDIR) don't expand tilde to '~' directory
335 * `446 <https://github.com/ipython/ipython/issues/446>`_: Test machinery is imported at normal runtime
335 * `446 <https://github.com/ipython/ipython/issues/446>`_: Test machinery is imported at normal runtime
336 * `438 <https://github.com/ipython/ipython/issues/438>`_: Users should be able to use Up/Down for cell navigation
336 * `438 <https://github.com/ipython/ipython/issues/438>`_: Users should be able to use Up/Down for cell navigation
337 * `439 <https://github.com/ipython/ipython/issues/439>`_: Users should be able to copy notebook input and output
337 * `439 <https://github.com/ipython/ipython/issues/439>`_: Users should be able to copy notebook input and output
338 * `291 <https://github.com/ipython/ipython/issues/291>`_: Rename special display methods and put them lower in priority than display functions
338 * `291 <https://github.com/ipython/ipython/issues/291>`_: Rename special display methods and put them lower in priority than display functions
339 * `447 <https://github.com/ipython/ipython/issues/447>`_: Instantiating classes without __init__ function causes kernel to crash
339 * `447 <https://github.com/ipython/ipython/issues/447>`_: Instantiating classes without __init__ function causes kernel to crash
340 * `444 <https://github.com/ipython/ipython/issues/444>`_: Ctrl + t in WxIPython Causes Unexpected Behavior
340 * `444 <https://github.com/ipython/ipython/issues/444>`_: Ctrl + t in WxIPython Causes Unexpected Behavior
341 * `445 <https://github.com/ipython/ipython/issues/445>`_: qt and console Based Startup Errors
341 * `445 <https://github.com/ipython/ipython/issues/445>`_: qt and console Based Startup Errors
342 * `428 <https://github.com/ipython/ipython/issues/428>`_: ipcluster doesn't handle stale pid info well
342 * `428 <https://github.com/ipython/ipython/issues/428>`_: ipcluster doesn't handle stale pid info well
343 * `434 <https://github.com/ipython/ipython/issues/434>`_: 10.0.2 seg fault with rpy2
343 * `434 <https://github.com/ipython/ipython/issues/434>`_: 10.0.2 seg fault with rpy2
344 * `441 <https://github.com/ipython/ipython/issues/441>`_: Allow running a block of code in a file
344 * `441 <https://github.com/ipython/ipython/issues/441>`_: Allow running a block of code in a file
345 * `432 <https://github.com/ipython/ipython/issues/432>`_: Silent request fails
345 * `432 <https://github.com/ipython/ipython/issues/432>`_: Silent request fails
346 * `409 <https://github.com/ipython/ipython/issues/409>`_: Test failure in IPython.lib
346 * `409 <https://github.com/ipython/ipython/issues/409>`_: Test failure in IPython.lib
347 * `402 <https://github.com/ipython/ipython/issues/402>`_: History section of messaging spec is incorrect
347 * `402 <https://github.com/ipython/ipython/issues/402>`_: History section of messaging spec is incorrect
348 * `88 <https://github.com/ipython/ipython/issues/88>`_: Error when inputting UTF8 CJK characters
348 * `88 <https://github.com/ipython/ipython/issues/88>`_: Error when inputting UTF8 CJK characters
349 * `366 <https://github.com/ipython/ipython/issues/366>`_: Ctrl-K should kill line and store it, so that Ctrl-y can yank it back
349 * `366 <https://github.com/ipython/ipython/issues/366>`_: Ctrl-K should kill line and store it, so that Ctrl-y can yank it back
350 * `425 <https://github.com/ipython/ipython/issues/425>`_: typo in %gui magic help
350 * `425 <https://github.com/ipython/ipython/issues/425>`_: typo in %gui magic help
351 * `304 <https://github.com/ipython/ipython/issues/304>`_: Persistent warnings if old configuration files exist
351 * `304 <https://github.com/ipython/ipython/issues/304>`_: Persistent warnings if old configuration files exist
352 * `216 <https://github.com/ipython/ipython/issues/216>`_: crash of ipython when alias is used with %s and echo
352 * `216 <https://github.com/ipython/ipython/issues/216>`_: crash of ipython when alias is used with %s and echo
353 * `412 <https://github.com/ipython/ipython/issues/412>`_: add support to automatic retry of tasks
353 * `412 <https://github.com/ipython/ipython/issues/412>`_: add support to automatic retry of tasks
354 * `411 <https://github.com/ipython/ipython/issues/411>`_: add support to continue tasks
354 * `411 <https://github.com/ipython/ipython/issues/411>`_: add support to continue tasks
355 * `417 <https://github.com/ipython/ipython/issues/417>`_: IPython should display things unsorted if it can't sort them
355 * `417 <https://github.com/ipython/ipython/issues/417>`_: IPython should display things unsorted if it can't sort them
356 * `416 <https://github.com/ipython/ipython/issues/416>`_: wrong encode when printing unicode string
356 * `416 <https://github.com/ipython/ipython/issues/416>`_: wrong encode when printing unicode string
357 * `376 <https://github.com/ipython/ipython/issues/376>`_: Failing InputsplitterTest
357 * `376 <https://github.com/ipython/ipython/issues/376>`_: Failing InputsplitterTest
358 * `405 <https://github.com/ipython/ipython/issues/405>`_: TraitError in traitlets.py(332) on any input
358 * `405 <https://github.com/ipython/ipython/issues/405>`_: TraitError in traitlets.py(332) on any input
359 * `392 <https://github.com/ipython/ipython/issues/392>`_: UnicodeEncodeError on start
359 * `392 <https://github.com/ipython/ipython/issues/392>`_: UnicodeEncodeError on start
360 * `137 <https://github.com/ipython/ipython/issues/137>`_: sys.getfilesystemencoding return value not checked
360 * `137 <https://github.com/ipython/ipython/issues/137>`_: sys.getfilesystemencoding return value not checked
361 * `300 <https://github.com/ipython/ipython/issues/300>`_: Users should be able to manage kernels and kernel sessions from the notebook UI
361 * `300 <https://github.com/ipython/ipython/issues/300>`_: Users should be able to manage kernels and kernel sessions from the notebook UI
362 * `301 <https://github.com/ipython/ipython/issues/301>`_: Users should have access to working Kernel, Tabs, Edit, Help menus in the notebook
362 * `301 <https://github.com/ipython/ipython/issues/301>`_: Users should have access to working Kernel, Tabs, Edit, Help menus in the notebook
363 * `396 <https://github.com/ipython/ipython/issues/396>`_: cursor move triggers a lot of IO access
363 * `396 <https://github.com/ipython/ipython/issues/396>`_: cursor move triggers a lot of IO access
364 * `379 <https://github.com/ipython/ipython/issues/379>`_: Minor doc nit: --paging argument
364 * `379 <https://github.com/ipython/ipython/issues/379>`_: Minor doc nit: --paging argument
365 * `399 <https://github.com/ipython/ipython/issues/399>`_: Add task queue limit in engine when load-balancing
365 * `399 <https://github.com/ipython/ipython/issues/399>`_: Add task queue limit in engine when load-balancing
366 * `78 <https://github.com/ipython/ipython/issues/78>`_: StringTask won't take unicode code strings
366 * `78 <https://github.com/ipython/ipython/issues/78>`_: StringTask won't take unicode code strings
367 * `391 <https://github.com/ipython/ipython/issues/391>`_: MongoDB.add_record() does not work in 0.11dev
367 * `391 <https://github.com/ipython/ipython/issues/391>`_: MongoDB.add_record() does not work in 0.11dev
368 * `365 <https://github.com/ipython/ipython/issues/365>`_: newparallel on Windows
368 * `365 <https://github.com/ipython/ipython/issues/365>`_: newparallel on Windows
369 * `386 <https://github.com/ipython/ipython/issues/386>`_: FAIL: test that pushed functions have access to globals
369 * `386 <https://github.com/ipython/ipython/issues/386>`_: FAIL: test that pushed functions have access to globals
370 * `387 <https://github.com/ipython/ipython/issues/387>`_: Interactively defined functions can't access user namespace
370 * `387 <https://github.com/ipython/ipython/issues/387>`_: Interactively defined functions can't access user namespace
371 * `118 <https://github.com/ipython/ipython/issues/118>`_: Snow Leopard ipy_vimserver POLL error
371 * `118 <https://github.com/ipython/ipython/issues/118>`_: Snow Leopard ipy_vimserver POLL error
372 * `394 <https://github.com/ipython/ipython/issues/394>`_: System escape interpreted in multi-line string
372 * `394 <https://github.com/ipython/ipython/issues/394>`_: System escape interpreted in multi-line string
373 * `26 <https://github.com/ipython/ipython/issues/26>`_: find_job_cmd is too hasty to fail on Windows
373 * `26 <https://github.com/ipython/ipython/issues/26>`_: find_job_cmd is too hasty to fail on Windows
374 * `368 <https://github.com/ipython/ipython/issues/368>`_: Installation instructions in dev docs are completely wrong
374 * `368 <https://github.com/ipython/ipython/issues/368>`_: Installation instructions in dev docs are completely wrong
375 * `380 <https://github.com/ipython/ipython/issues/380>`_: qtconsole pager RST - HTML not happening consistently
375 * `380 <https://github.com/ipython/ipython/issues/380>`_: qtconsole pager RST - HTML not happening consistently
376 * `367 <https://github.com/ipython/ipython/issues/367>`_: Qt console doesn't support ibus input method
376 * `367 <https://github.com/ipython/ipython/issues/367>`_: Qt console doesn't support ibus input method
377 * `375 <https://github.com/ipython/ipython/issues/375>`_: Missing libraries cause ImportError in tests
377 * `375 <https://github.com/ipython/ipython/issues/375>`_: Missing libraries cause ImportError in tests
378 * `71 <https://github.com/ipython/ipython/issues/71>`_: temp file errors in iptest IPython.core
378 * `71 <https://github.com/ipython/ipython/issues/71>`_: temp file errors in iptest IPython.core
379 * `350 <https://github.com/ipython/ipython/issues/350>`_: Decide how to handle displayhook being triggered multiple times
379 * `350 <https://github.com/ipython/ipython/issues/350>`_: Decide how to handle displayhook being triggered multiple times
380 * `360 <https://github.com/ipython/ipython/issues/360>`_: Remove `runlines` method
380 * `360 <https://github.com/ipython/ipython/issues/360>`_: Remove `runlines` method
381 * `125 <https://github.com/ipython/ipython/issues/125>`_: Exec lines in config should not contribute to line numbering or history
381 * `125 <https://github.com/ipython/ipython/issues/125>`_: Exec lines in config should not contribute to line numbering or history
382 * `20 <https://github.com/ipython/ipython/issues/20>`_: Robust readline support on OS X's builtin Python
382 * `20 <https://github.com/ipython/ipython/issues/20>`_: Robust readline support on OS X's builtin Python
383 * `147 <https://github.com/ipython/ipython/issues/147>`_: On Windows, %page is being too restrictive to split line by \r\n only
383 * `147 <https://github.com/ipython/ipython/issues/147>`_: On Windows, %page is being too restrictive to split line by \r\n only
384 * `326 <https://github.com/ipython/ipython/issues/326>`_: Update docs and examples for parallel stuff to reflect movement away from Twisted
384 * `326 <https://github.com/ipython/ipython/issues/326>`_: Update docs and examples for parallel stuff to reflect movement away from Twisted
385 * `341 <https://github.com/ipython/ipython/issues/341>`_: FIx Parallel Magics for newparallel
385 * `341 <https://github.com/ipython/ipython/issues/341>`_: FIx Parallel Magics for newparallel
386 * `338 <https://github.com/ipython/ipython/issues/338>`_: Usability improvements to Qt console
386 * `338 <https://github.com/ipython/ipython/issues/338>`_: Usability improvements to Qt console
387 * `142 <https://github.com/ipython/ipython/issues/142>`_: unexpected auto-indenting when variables names that start with 'pass'
387 * `142 <https://github.com/ipython/ipython/issues/142>`_: unexpected auto-indenting when variables names that start with 'pass'
388 * `296 <https://github.com/ipython/ipython/issues/296>`_: Automatic PDB via %pdb doesn't work
388 * `296 <https://github.com/ipython/ipython/issues/296>`_: Automatic PDB via %pdb doesn't work
389 * `337 <https://github.com/ipython/ipython/issues/337>`_: exit( and quit( in Qt console produces phantom signature/docstring popup, even though quit() or exit() raises NameError
389 * `337 <https://github.com/ipython/ipython/issues/337>`_: exit( and quit( in Qt console produces phantom signature/docstring popup, even though quit() or exit() raises NameError
390 * `318 <https://github.com/ipython/ipython/issues/318>`_: %debug broken in master: invokes missing save_history() method
390 * `318 <https://github.com/ipython/ipython/issues/318>`_: %debug broken in master: invokes missing save_history() method
391 * `307 <https://github.com/ipython/ipython/issues/307>`_: lines ending with semicolon should not go to cache
391 * `307 <https://github.com/ipython/ipython/issues/307>`_: lines ending with semicolon should not go to cache
392 * `104 <https://github.com/ipython/ipython/issues/104>`_: have ipengine run start-up scripts before registering with the controller
392 * `104 <https://github.com/ipython/ipython/issues/104>`_: have ipengine run start-up scripts before registering with the controller
393 * `33 <https://github.com/ipython/ipython/issues/33>`_: The skip_doctest decorator is failing to work on Shell.MatplotlibShellBase.magic_run
393 * `33 <https://github.com/ipython/ipython/issues/33>`_: The skip_doctest decorator is failing to work on Shell.MatplotlibShellBase.magic_run
394 * `336 <https://github.com/ipython/ipython/issues/336>`_: Missing figure development/figs/iopubfade.png for docs
394 * `336 <https://github.com/ipython/ipython/issues/336>`_: Missing figure development/figs/iopubfade.png for docs
395 * `49 <https://github.com/ipython/ipython/issues/49>`_: %clear should also delete _NN references and Out[NN] ones
395 * `49 <https://github.com/ipython/ipython/issues/49>`_: %clear should also delete _NN references and Out[NN] ones
396 * `335 <https://github.com/ipython/ipython/issues/335>`_: using setuptools installs every script twice
396 * `335 <https://github.com/ipython/ipython/issues/335>`_: using setuptools installs every script twice
397 * `306 <https://github.com/ipython/ipython/issues/306>`_: multiline strings at end of input cause noop
397 * `306 <https://github.com/ipython/ipython/issues/306>`_: multiline strings at end of input cause noop
398 * `327 <https://github.com/ipython/ipython/issues/327>`_: PyPy compatibility
398 * `327 <https://github.com/ipython/ipython/issues/327>`_: PyPy compatibility
399 * `328 <https://github.com/ipython/ipython/issues/328>`_: %run script.ipy raises "ERROR! Session/line number was not unique in database."
399 * `328 <https://github.com/ipython/ipython/issues/328>`_: %run script.ipy raises "ERROR! Session/line number was not unique in database."
400 * `7 <https://github.com/ipython/ipython/issues/7>`_: Update the changes doc to reflect the kernel config work
400 * `7 <https://github.com/ipython/ipython/issues/7>`_: Update the changes doc to reflect the kernel config work
401 * `303 <https://github.com/ipython/ipython/issues/303>`_: Users should be able to scroll a notebook w/o moving the menu/buttons
401 * `303 <https://github.com/ipython/ipython/issues/303>`_: Users should be able to scroll a notebook w/o moving the menu/buttons
402 * `322 <https://github.com/ipython/ipython/issues/322>`_: Embedding an interactive IPython shell
402 * `322 <https://github.com/ipython/ipython/issues/322>`_: Embedding an interactive IPython shell
403 * `321 <https://github.com/ipython/ipython/issues/321>`_: %debug broken in master
403 * `321 <https://github.com/ipython/ipython/issues/321>`_: %debug broken in master
404 * `287 <https://github.com/ipython/ipython/issues/287>`_: Crash when using %macros in sqlite-history branch
404 * `287 <https://github.com/ipython/ipython/issues/287>`_: Crash when using %macros in sqlite-history branch
405 * `55 <https://github.com/ipython/ipython/issues/55>`_: Can't edit files whose names begin with numbers
405 * `55 <https://github.com/ipython/ipython/issues/55>`_: Can't edit files whose names begin with numbers
406 * `284 <https://github.com/ipython/ipython/issues/284>`_: In variable no longer works in 0.11
406 * `284 <https://github.com/ipython/ipython/issues/284>`_: In variable no longer works in 0.11
407 * `92 <https://github.com/ipython/ipython/issues/92>`_: Using multiprocessing module crashes parallel IPython
407 * `92 <https://github.com/ipython/ipython/issues/92>`_: Using multiprocessing module crashes parallel IPython
408 * `262 <https://github.com/ipython/ipython/issues/262>`_: Fail to recover history after force-kill.
408 * `262 <https://github.com/ipython/ipython/issues/262>`_: Fail to recover history after force-kill.
409 * `320 <https://github.com/ipython/ipython/issues/320>`_: Tab completing re.search objects crashes IPython
409 * `320 <https://github.com/ipython/ipython/issues/320>`_: Tab completing re.search objects crashes IPython
410 * `317 <https://github.com/ipython/ipython/issues/317>`_: IPython.kernel: parallel map issues
410 * `317 <https://github.com/ipython/ipython/issues/317>`_: IPython.kernel: parallel map issues
411 * `197 <https://github.com/ipython/ipython/issues/197>`_: ipython-qtconsole unicode problem in magic ls
411 * `197 <https://github.com/ipython/ipython/issues/197>`_: ipython-qtconsole unicode problem in magic ls
412 * `305 <https://github.com/ipython/ipython/issues/305>`_: more readline shortcuts in qtconsole
412 * `305 <https://github.com/ipython/ipython/issues/305>`_: more readline shortcuts in qtconsole
413 * `314 <https://github.com/ipython/ipython/issues/314>`_: Multi-line, multi-block cells can't be executed.
413 * `314 <https://github.com/ipython/ipython/issues/314>`_: Multi-line, multi-block cells can't be executed.
414 * `308 <https://github.com/ipython/ipython/issues/308>`_: Test suite should set sqlite history to work in :memory:
414 * `308 <https://github.com/ipython/ipython/issues/308>`_: Test suite should set sqlite history to work in :memory:
415 * `202 <https://github.com/ipython/ipython/issues/202>`_: Matplotlib native 'MacOSX' backend broken in '-pylab' mode
415 * `202 <https://github.com/ipython/ipython/issues/202>`_: Matplotlib native 'MacOSX' backend broken in '-pylab' mode
416 * `196 <https://github.com/ipython/ipython/issues/196>`_: IPython can't deal with unicode file name.
416 * `196 <https://github.com/ipython/ipython/issues/196>`_: IPython can't deal with unicode file name.
417 * `25 <https://github.com/ipython/ipython/issues/25>`_: unicode bug - encoding input
417 * `25 <https://github.com/ipython/ipython/issues/25>`_: unicode bug - encoding input
418 * `290 <https://github.com/ipython/ipython/issues/290>`_: try/except/else clauses can't be typed, code input stops too early.
418 * `290 <https://github.com/ipython/ipython/issues/290>`_: try/except/else clauses can't be typed, code input stops too early.
419 * `43 <https://github.com/ipython/ipython/issues/43>`_: Implement SSH support in ipcluster
419 * `43 <https://github.com/ipython/ipython/issues/43>`_: Implement SSH support in ipcluster
420 * `6 <https://github.com/ipython/ipython/issues/6>`_: Update the Sphinx docs for the new ipcluster
420 * `6 <https://github.com/ipython/ipython/issues/6>`_: Update the Sphinx docs for the new ipcluster
421 * `9 <https://github.com/ipython/ipython/issues/9>`_: Getting "DeadReferenceError: Calling Stale Broker" after ipcontroller restart
421 * `9 <https://github.com/ipython/ipython/issues/9>`_: Getting "DeadReferenceError: Calling Stale Broker" after ipcontroller restart
422 * `132 <https://github.com/ipython/ipython/issues/132>`_: Ipython prevent south from working
422 * `132 <https://github.com/ipython/ipython/issues/132>`_: Ipython prevent south from working
423 * `27 <https://github.com/ipython/ipython/issues/27>`_: generics.complete_object broken
423 * `27 <https://github.com/ipython/ipython/issues/27>`_: generics.complete_object broken
424 * `60 <https://github.com/ipython/ipython/issues/60>`_: Improve absolute import management for iptest.py
424 * `60 <https://github.com/ipython/ipython/issues/60>`_: Improve absolute import management for iptest.py
425 * `31 <https://github.com/ipython/ipython/issues/31>`_: Issues in magic_whos code
425 * `31 <https://github.com/ipython/ipython/issues/31>`_: Issues in magic_whos code
426 * `52 <https://github.com/ipython/ipython/issues/52>`_: Document testing process better
426 * `52 <https://github.com/ipython/ipython/issues/52>`_: Document testing process better
427 * `44 <https://github.com/ipython/ipython/issues/44>`_: Merge history from multiple sessions
427 * `44 <https://github.com/ipython/ipython/issues/44>`_: Merge history from multiple sessions
428 * `182 <https://github.com/ipython/ipython/issues/182>`_: ipython q4thread in version 10.1 not starting properly
428 * `182 <https://github.com/ipython/ipython/issues/182>`_: ipython q4thread in version 10.1 not starting properly
429 * `143 <https://github.com/ipython/ipython/issues/143>`_: Ipython.gui.wx.ipython_view.IPShellWidget: ignores user*_ns arguments
429 * `143 <https://github.com/ipython/ipython/issues/143>`_: Ipython.gui.wx.ipython_view.IPShellWidget: ignores user*_ns arguments
430 * `127 <https://github.com/ipython/ipython/issues/127>`_: %edit does not work on filenames consisted of pure numbers
430 * `127 <https://github.com/ipython/ipython/issues/127>`_: %edit does not work on filenames consisted of pure numbers
431 * `126 <https://github.com/ipython/ipython/issues/126>`_: Can't transfer command line argument to script
431 * `126 <https://github.com/ipython/ipython/issues/126>`_: Can't transfer command line argument to script
432 * `28 <https://github.com/ipython/ipython/issues/28>`_: Offer finer control for initialization of input streams
432 * `28 <https://github.com/ipython/ipython/issues/28>`_: Offer finer control for initialization of input streams
433 * `58 <https://github.com/ipython/ipython/issues/58>`_: ipython change char '0xe9' to 4 spaces
433 * `58 <https://github.com/ipython/ipython/issues/58>`_: ipython change char '0xe9' to 4 spaces
434 * `68 <https://github.com/ipython/ipython/issues/68>`_: Problems with Control-C stopping ipcluster on Windows/Python2.6
434 * `68 <https://github.com/ipython/ipython/issues/68>`_: Problems with Control-C stopping ipcluster on Windows/Python2.6
435 * `24 <https://github.com/ipython/ipython/issues/24>`_: ipcluster does not start all the engines
435 * `24 <https://github.com/ipython/ipython/issues/24>`_: ipcluster does not start all the engines
436 * `240 <https://github.com/ipython/ipython/issues/240>`_: Incorrect method displayed in %psource
436 * `240 <https://github.com/ipython/ipython/issues/240>`_: Incorrect method displayed in %psource
437 * `120 <https://github.com/ipython/ipython/issues/120>`_: inspect.getsource fails for functions defined on command line
437 * `120 <https://github.com/ipython/ipython/issues/120>`_: inspect.getsource fails for functions defined on command line
438 * `212 <https://github.com/ipython/ipython/issues/212>`_: IPython ignores exceptions in the first evaulation of class attrs
438 * `212 <https://github.com/ipython/ipython/issues/212>`_: IPython ignores exceptions in the first evaulation of class attrs
439 * `108 <https://github.com/ipython/ipython/issues/108>`_: ipython disables python logger
439 * `108 <https://github.com/ipython/ipython/issues/108>`_: ipython disables python logger
440 * `100 <https://github.com/ipython/ipython/issues/100>`_: Overzealous introspection
440 * `100 <https://github.com/ipython/ipython/issues/100>`_: Overzealous introspection
441 * `18 <https://github.com/ipython/ipython/issues/18>`_: %cpaste freeze sync frontend
441 * `18 <https://github.com/ipython/ipython/issues/18>`_: %cpaste freeze sync frontend
442 * `200 <https://github.com/ipython/ipython/issues/200>`_: Unicode error when starting ipython in a folder with non-ascii path
442 * `200 <https://github.com/ipython/ipython/issues/200>`_: Unicode error when starting ipython in a folder with non-ascii path
443 * `130 <https://github.com/ipython/ipython/issues/130>`_: Deadlock when importing a module that creates an IPython client
443 * `130 <https://github.com/ipython/ipython/issues/130>`_: Deadlock when importing a module that creates an IPython client
444 * `134 <https://github.com/ipython/ipython/issues/134>`_: multline block scrolling
444 * `134 <https://github.com/ipython/ipython/issues/134>`_: multline block scrolling
445 * `46 <https://github.com/ipython/ipython/issues/46>`_: Input to %timeit is not preparsed
445 * `46 <https://github.com/ipython/ipython/issues/46>`_: Input to %timeit is not preparsed
446 * `285 <https://github.com/ipython/ipython/issues/285>`_: ipcluster local -n 4 fails
446 * `285 <https://github.com/ipython/ipython/issues/285>`_: ipcluster local -n 4 fails
447 * `205 <https://github.com/ipython/ipython/issues/205>`_: In the Qt console, Tab should insert 4 spaces when not completing
447 * `205 <https://github.com/ipython/ipython/issues/205>`_: In the Qt console, Tab should insert 4 spaces when not completing
448 * `145 <https://github.com/ipython/ipython/issues/145>`_: Bug on MSW systems: idle can not be set as default IPython editor. Fix Suggested.
448 * `145 <https://github.com/ipython/ipython/issues/145>`_: Bug on MSW systems: idle can not be set as default IPython editor. Fix Suggested.
449 * `77 <https://github.com/ipython/ipython/issues/77>`_: ipython oops in cygwin
449 * `77 <https://github.com/ipython/ipython/issues/77>`_: ipython oops in cygwin
450 * `121 <https://github.com/ipython/ipython/issues/121>`_: If plot windows are closed via window controls, no more plotting is possible.
450 * `121 <https://github.com/ipython/ipython/issues/121>`_: If plot windows are closed via window controls, no more plotting is possible.
451 * `111 <https://github.com/ipython/ipython/issues/111>`_: Iterator version of TaskClient.map() that returns results as they become available
451 * `111 <https://github.com/ipython/ipython/issues/111>`_: Iterator version of TaskClient.map() that returns results as they become available
452 * `109 <https://github.com/ipython/ipython/issues/109>`_: WinHPCLauncher is a hard dependency that causes errors in the test suite
452 * `109 <https://github.com/ipython/ipython/issues/109>`_: WinHPCLauncher is a hard dependency that causes errors in the test suite
453 * `86 <https://github.com/ipython/ipython/issues/86>`_: Make IPython work with multiprocessing
453 * `86 <https://github.com/ipython/ipython/issues/86>`_: Make IPython work with multiprocessing
454 * `15 <https://github.com/ipython/ipython/issues/15>`_: Implement SGE support in ipcluster
454 * `15 <https://github.com/ipython/ipython/issues/15>`_: Implement SGE support in ipcluster
455 * `3 <https://github.com/ipython/ipython/issues/3>`_: Implement PBS support in ipcluster
455 * `3 <https://github.com/ipython/ipython/issues/3>`_: Implement PBS support in ipcluster
456 * `53 <https://github.com/ipython/ipython/issues/53>`_: Internal Python error in the inspect module
456 * `53 <https://github.com/ipython/ipython/issues/53>`_: Internal Python error in the inspect module
457 * `74 <https://github.com/ipython/ipython/issues/74>`_: Manager() [from multiprocessing module] hangs ipythonx but not ipython
457 * `74 <https://github.com/ipython/ipython/issues/74>`_: Manager() [from multiprocessing module] hangs ipythonx but not ipython
458 * `51 <https://github.com/ipython/ipython/issues/51>`_: Out not working with ipythonx
458 * `51 <https://github.com/ipython/ipython/issues/51>`_: Out not working with ipythonx
459 * `201 <https://github.com/ipython/ipython/issues/201>`_: use session.send throughout zmq code
459 * `201 <https://github.com/ipython/ipython/issues/201>`_: use session.send throughout zmq code
460 * `115 <https://github.com/ipython/ipython/issues/115>`_: multiline specials not defined in 0.11 branch
460 * `115 <https://github.com/ipython/ipython/issues/115>`_: multiline specials not defined in 0.11 branch
461 * `93 <https://github.com/ipython/ipython/issues/93>`_: when looping, cursor appears at leftmost point in newline
461 * `93 <https://github.com/ipython/ipython/issues/93>`_: when looping, cursor appears at leftmost point in newline
462 * `133 <https://github.com/ipython/ipython/issues/133>`_: whitespace after Source introspection
462 * `133 <https://github.com/ipython/ipython/issues/133>`_: whitespace after Source introspection
463 * `50 <https://github.com/ipython/ipython/issues/50>`_: Ctrl-C with -gthread on Windows, causes uncaught IOError
463 * `50 <https://github.com/ipython/ipython/issues/50>`_: Ctrl-C with -gthread on Windows, causes uncaught IOError
464 * `65 <https://github.com/ipython/ipython/issues/65>`_: Do not use .message attributes in exceptions, deprecated in 2.6
464 * `65 <https://github.com/ipython/ipython/issues/65>`_: Do not use .message attributes in exceptions, deprecated in 2.6
465 * `76 <https://github.com/ipython/ipython/issues/76>`_: syntax error when raise is inside except process
465 * `76 <https://github.com/ipython/ipython/issues/76>`_: syntax error when raise is inside except process
466 * `107 <https://github.com/ipython/ipython/issues/107>`_: bdist_rpm causes traceback looking for a non-existant file
466 * `107 <https://github.com/ipython/ipython/issues/107>`_: bdist_rpm causes traceback looking for a non-existant file
467 * `113 <https://github.com/ipython/ipython/issues/113>`_: initial magic ? (question mark) fails before wildcard
467 * `113 <https://github.com/ipython/ipython/issues/113>`_: initial magic ? (question mark) fails before wildcard
468 * `128 <https://github.com/ipython/ipython/issues/128>`_: Pdb instance has no attribute 'curframe'
468 * `128 <https://github.com/ipython/ipython/issues/128>`_: Pdb instance has no attribute 'curframe'
469 * `139 <https://github.com/ipython/ipython/issues/139>`_: running with -pylab pollutes namespace
469 * `139 <https://github.com/ipython/ipython/issues/139>`_: running with -pylab pollutes namespace
470 * `140 <https://github.com/ipython/ipython/issues/140>`_: malloc error during tab completion of numpy array member functions starting with 'c'
470 * `140 <https://github.com/ipython/ipython/issues/140>`_: malloc error during tab completion of numpy array member functions starting with 'c'
471 * `153 <https://github.com/ipython/ipython/issues/153>`_: ipy_vimserver traceback on Windows
471 * `153 <https://github.com/ipython/ipython/issues/153>`_: ipy_vimserver traceback on Windows
472 * `154 <https://github.com/ipython/ipython/issues/154>`_: using ipython in Slicer3 show how os.environ['HOME'] is not defined
472 * `154 <https://github.com/ipython/ipython/issues/154>`_: using ipython in Slicer3 show how os.environ['HOME'] is not defined
473 * `185 <https://github.com/ipython/ipython/issues/185>`_: show() blocks in pylab mode with ipython 0.10.1
473 * `185 <https://github.com/ipython/ipython/issues/185>`_: show() blocks in pylab mode with ipython 0.10.1
474 * `189 <https://github.com/ipython/ipython/issues/189>`_: Crash on tab completion
474 * `189 <https://github.com/ipython/ipython/issues/189>`_: Crash on tab completion
475 * `274 <https://github.com/ipython/ipython/issues/274>`_: bashism in sshx.sh
475 * `274 <https://github.com/ipython/ipython/issues/274>`_: bashism in sshx.sh
476 * `276 <https://github.com/ipython/ipython/issues/276>`_: Calling `sip.setapi` does not work if app has already imported from PyQt4
476 * `276 <https://github.com/ipython/ipython/issues/276>`_: Calling `sip.setapi` does not work if app has already imported from PyQt4
477 * `277 <https://github.com/ipython/ipython/issues/277>`_: matplotlib.image imgshow from 10.1 segfault
477 * `277 <https://github.com/ipython/ipython/issues/277>`_: matplotlib.image imgshow from 10.1 segfault
478 * `288 <https://github.com/ipython/ipython/issues/288>`_: Incorrect docstring in zmq/kernelmanager.py
478 * `288 <https://github.com/ipython/ipython/issues/288>`_: Incorrect docstring in zmq/kernelmanager.py
479 * `286 <https://github.com/ipython/ipython/issues/286>`_: Fix IPython.Shell compatibility layer
479 * `286 <https://github.com/ipython/ipython/issues/286>`_: Fix IPython.Shell compatibility layer
480 * `99 <https://github.com/ipython/ipython/issues/99>`_: blank lines in history
480 * `99 <https://github.com/ipython/ipython/issues/99>`_: blank lines in history
481 * `129 <https://github.com/ipython/ipython/issues/129>`_: psearch: TypeError: expected string or buffer
481 * `129 <https://github.com/ipython/ipython/issues/129>`_: psearch: TypeError: expected string or buffer
482 * `190 <https://github.com/ipython/ipython/issues/190>`_: Add option to format float point output
482 * `190 <https://github.com/ipython/ipython/issues/190>`_: Add option to format float point output
483 * `246 <https://github.com/ipython/ipython/issues/246>`_: Application not conforms XDG Base Directory Specification
483 * `246 <https://github.com/ipython/ipython/issues/246>`_: Application not conforms XDG Base Directory Specification
484 * `48 <https://github.com/ipython/ipython/issues/48>`_: IPython should follow the XDG Base Directory spec for configuration
484 * `48 <https://github.com/ipython/ipython/issues/48>`_: IPython should follow the XDG Base Directory spec for configuration
485 * `176 <https://github.com/ipython/ipython/issues/176>`_: Make client-side history persistence readline-independent
485 * `176 <https://github.com/ipython/ipython/issues/176>`_: Make client-side history persistence readline-independent
486 * `279 <https://github.com/ipython/ipython/issues/279>`_: Backtraces when using ipdb do not respect -colour LightBG setting
486 * `279 <https://github.com/ipython/ipython/issues/279>`_: Backtraces when using ipdb do not respect -colour LightBG setting
487 * `119 <https://github.com/ipython/ipython/issues/119>`_: Broken type filter in magic_who_ls
487 * `119 <https://github.com/ipython/ipython/issues/119>`_: Broken type filter in magic_who_ls
488 * `271 <https://github.com/ipython/ipython/issues/271>`_: Intermittent problem with print output in Qt console.
488 * `271 <https://github.com/ipython/ipython/issues/271>`_: Intermittent problem with print output in Qt console.
489 * `270 <https://github.com/ipython/ipython/issues/270>`_: Small typo in IPython developer’s guide
489 * `270 <https://github.com/ipython/ipython/issues/270>`_: Small typo in IPython developer’s guide
490 * `166 <https://github.com/ipython/ipython/issues/166>`_: Add keyboard accelerators to Qt close dialog
490 * `166 <https://github.com/ipython/ipython/issues/166>`_: Add keyboard accelerators to Qt close dialog
491 * `173 <https://github.com/ipython/ipython/issues/173>`_: asymmetrical ctrl-A/ctrl-E behavior in multiline
491 * `173 <https://github.com/ipython/ipython/issues/173>`_: asymmetrical ctrl-A/ctrl-E behavior in multiline
492 * `45 <https://github.com/ipython/ipython/issues/45>`_: Autosave history for robustness
492 * `45 <https://github.com/ipython/ipython/issues/45>`_: Autosave history for robustness
493 * `162 <https://github.com/ipython/ipython/issues/162>`_: make command history persist in ipythonqt
493 * `162 <https://github.com/ipython/ipython/issues/162>`_: make command history persist in ipythonqt
494 * `161 <https://github.com/ipython/ipython/issues/161>`_: make ipythonqt exit without dialog when exit() is called
494 * `161 <https://github.com/ipython/ipython/issues/161>`_: make ipythonqt exit without dialog when exit() is called
495 * `263 <https://github.com/ipython/ipython/issues/263>`_: [ipython + numpy] Some test errors
495 * `263 <https://github.com/ipython/ipython/issues/263>`_: [ipython + numpy] Some test errors
496 * `256 <https://github.com/ipython/ipython/issues/256>`_: reset docstring ipython 0.10
496 * `256 <https://github.com/ipython/ipython/issues/256>`_: reset docstring ipython 0.10
497 * `258 <https://github.com/ipython/ipython/issues/258>`_: allow caching to avoid matplotlib object references
497 * `258 <https://github.com/ipython/ipython/issues/258>`_: allow caching to avoid matplotlib object references
498 * `248 <https://github.com/ipython/ipython/issues/248>`_: Can't open and read files after upgrade from 0.10 to 0.10.0
498 * `248 <https://github.com/ipython/ipython/issues/248>`_: Can't open and read files after upgrade from 0.10 to 0.10.0
499 * `247 <https://github.com/ipython/ipython/issues/247>`_: ipython + Stackless
499 * `247 <https://github.com/ipython/ipython/issues/247>`_: ipython + Stackless
500 * `245 <https://github.com/ipython/ipython/issues/245>`_: Magic save and macro missing newlines, line ranges don't match prompt numbers.
500 * `245 <https://github.com/ipython/ipython/issues/245>`_: Magic save and macro missing newlines, line ranges don't match prompt numbers.
501 * `241 <https://github.com/ipython/ipython/issues/241>`_: "exit" hangs on terminal version of IPython
501 * `241 <https://github.com/ipython/ipython/issues/241>`_: "exit" hangs on terminal version of IPython
502 * `213 <https://github.com/ipython/ipython/issues/213>`_: ipython -pylab no longer plots interactively on 0.10.1
502 * `213 <https://github.com/ipython/ipython/issues/213>`_: ipython -pylab no longer plots interactively on 0.10.1
503 * `4 <https://github.com/ipython/ipython/issues/4>`_: wx frontend don't display well commands output
503 * `4 <https://github.com/ipython/ipython/issues/4>`_: wx frontend don't display well commands output
504 * `5 <https://github.com/ipython/ipython/issues/5>`_: ls command not supported in ipythonx wx frontend
504 * `5 <https://github.com/ipython/ipython/issues/5>`_: ls command not supported in ipythonx wx frontend
505 * `1 <https://github.com/ipython/ipython/issues/1>`_: Document winhpcjob.py and launcher.py
505 * `1 <https://github.com/ipython/ipython/issues/1>`_: Document winhpcjob.py and launcher.py
506 * `83 <https://github.com/ipython/ipython/issues/83>`_: Usage of testing.util.DeferredTestCase should be replace with twisted.trial.unittest.TestCase
506 * `83 <https://github.com/ipython/ipython/issues/83>`_: Usage of testing.util.DeferredTestCase should be replace with twisted.trial.unittest.TestCase
507 * `117 <https://github.com/ipython/ipython/issues/117>`_: Redesign how Component instances are tracked and queried
507 * `117 <https://github.com/ipython/ipython/issues/117>`_: Redesign how Component instances are tracked and queried
508 * `47 <https://github.com/ipython/ipython/issues/47>`_: IPython.kernel.client cannot be imported inside an engine
508 * `47 <https://github.com/ipython/ipython/issues/47>`_: IPython.kernel.client cannot be imported inside an engine
509 * `105 <https://github.com/ipython/ipython/issues/105>`_: Refactor the task dependencies system
509 * `105 <https://github.com/ipython/ipython/issues/105>`_: Refactor the task dependencies system
510 * `210 <https://github.com/ipython/ipython/issues/210>`_: 0.10.1 doc mistake - New IPython Sphinx directive error
510 * `210 <https://github.com/ipython/ipython/issues/210>`_: 0.10.1 doc mistake - New IPython Sphinx directive error
511 * `209 <https://github.com/ipython/ipython/issues/209>`_: can't activate IPython parallel magics
511 * `209 <https://github.com/ipython/ipython/issues/209>`_: can't activate IPython parallel magics
512 * `206 <https://github.com/ipython/ipython/issues/206>`_: Buggy linewrap in Mac OSX Terminal
512 * `206 <https://github.com/ipython/ipython/issues/206>`_: Buggy linewrap in Mac OSX Terminal
513 * `194 <https://github.com/ipython/ipython/issues/194>`_: !sudo <command> displays password in plain text
513 * `194 <https://github.com/ipython/ipython/issues/194>`_: !sudo <command> displays password in plain text
514 * `186 <https://github.com/ipython/ipython/issues/186>`_: %edit issue under OS X 10.5 - IPython 0.10.1
514 * `186 <https://github.com/ipython/ipython/issues/186>`_: %edit issue under OS X 10.5 - IPython 0.10.1
515 * `11 <https://github.com/ipython/ipython/issues/11>`_: Create a daily build PPA for ipython
515 * `11 <https://github.com/ipython/ipython/issues/11>`_: Create a daily build PPA for ipython
516 * `144 <https://github.com/ipython/ipython/issues/144>`_: logo missing from sphinx docs
516 * `144 <https://github.com/ipython/ipython/issues/144>`_: logo missing from sphinx docs
517 * `181 <https://github.com/ipython/ipython/issues/181>`_: cls command does not work on windows
517 * `181 <https://github.com/ipython/ipython/issues/181>`_: cls command does not work on windows
518 * `169 <https://github.com/ipython/ipython/issues/169>`_: Kernel can only be bound to localhost
518 * `169 <https://github.com/ipython/ipython/issues/169>`_: Kernel can only be bound to localhost
519 * `36 <https://github.com/ipython/ipython/issues/36>`_: tab completion does not escape ()
519 * `36 <https://github.com/ipython/ipython/issues/36>`_: tab completion does not escape ()
520 * `177 <https://github.com/ipython/ipython/issues/177>`_: Report tracebacks of interactively entered input
520 * `177 <https://github.com/ipython/ipython/issues/177>`_: Report tracebacks of interactively entered input
521 * `148 <https://github.com/ipython/ipython/issues/148>`_: dictionary having multiple keys having frozenset fails to print on IPython
521 * `148 <https://github.com/ipython/ipython/issues/148>`_: dictionary having multiple keys having frozenset fails to print on IPython
522 * `160 <https://github.com/ipython/ipython/issues/160>`_: magic_gui throws TypeError when gui magic is used
522 * `160 <https://github.com/ipython/ipython/issues/160>`_: magic_gui throws TypeError when gui magic is used
523 * `150 <https://github.com/ipython/ipython/issues/150>`_: History entries ending with parentheses corrupt command line on OS X 10.6.4
523 * `150 <https://github.com/ipython/ipython/issues/150>`_: History entries ending with parentheses corrupt command line on OS X 10.6.4
524 * `146 <https://github.com/ipython/ipython/issues/146>`_: -ipythondir - using an alternative .ipython dir for rc type stuff
524 * `146 <https://github.com/ipython/ipython/issues/146>`_: -ipythondir - using an alternative .ipython dir for rc type stuff
525 * `114 <https://github.com/ipython/ipython/issues/114>`_: Interactive strings get mangled with "_ip.magic"
525 * `114 <https://github.com/ipython/ipython/issues/114>`_: Interactive strings get mangled with "_ip.magic"
526 * `135 <https://github.com/ipython/ipython/issues/135>`_: crash on invalid print
526 * `135 <https://github.com/ipython/ipython/issues/135>`_: crash on invalid print
527 * `69 <https://github.com/ipython/ipython/issues/69>`_: Usage of "mycluster" profile in docs and examples
527 * `69 <https://github.com/ipython/ipython/issues/69>`_: Usage of "mycluster" profile in docs and examples
528 * `37 <https://github.com/ipython/ipython/issues/37>`_: Fix colors in output of ResultList on Windows
528 * `37 <https://github.com/ipython/ipython/issues/37>`_: Fix colors in output of ResultList on Windows
@@ -1,1607 +1,1607 b''
1 .. _issues_list_200:
1 .. _issues_list_200:
2
2
3 Issues closed in the 2.x development cycle
3 Issues closed in the 2.x development cycle
4 ==========================================
4 ==========================================
5
5
6 Issues closed in 2.4.1
6 Issues closed in 2.4.1
7 ----------------------
7 ----------------------
8
8
9 GitHub stats for 2014/11/01 - 2015/01/30
9 GitHub stats for 2014/11/01 - 2015/01/30
10
10
11 .. note::
11 .. note::
12
12
13 IPython 2.4.0 was released without a few of the backports listed below.
13 IPython 2.4.0 was released without a few of the backports listed below.
14 2.4.1 has the correct patches intended for 2.4.0.
14 2.4.1 has the correct patches intended for 2.4.0.
15
15
16 These lists are automatically generated, and may be incomplete or contain duplicates.
16 These lists are automatically generated, and may be incomplete or contain duplicates.
17
17
18 The following 7 authors contributed 35 commits.
18 The following 7 authors contributed 35 commits.
19
19
20 * Benjamin Ragan-Kelley
20 * Benjamin Ragan-Kelley
21 * Carlos Cordoba
21 * Carlos Cordoba
22 * Damon Allen
22 * Damon Allen
23 * Jessica B. Hamrick
23 * Jessica B. Hamrick
24 * Mateusz Paprocki
24 * Mateusz Paprocki
25 * Peter Würtz
25 * Peter Würtz
26 * Thomas Kluyver
26 * Thomas Kluyver
27
27
28 We closed 10 issues and merged 6 pull requests;
28 We closed 10 issues and merged 6 pull requests;
29 this is the full list (generated with the script
29 this is the full list (generated with the script
30 :file:`tools/github_stats.py`):
30 :file:`tools/github_stats.py`):
31
31
32 Pull Requests (10):
32 Pull Requests (10):
33
33
34 * :ghpull:`7106`: Changed the display order of rich output in the live notebook.
34 * :ghpull:`7106`: Changed the display order of rich output in the live notebook.
35 * :ghpull:`6878`: Update pygments monkeypatch for compatibility with Pygments 2.0
35 * :ghpull:`6878`: Update pygments monkeypatch for compatibility with Pygments 2.0
36 * :ghpull:`6778`: backport nbformat v4 to 2.x
36 * :ghpull:`6778`: backport nbformat v4 to 2.x
37 * :ghpull:`6761`: object_info_reply field is oname, not name
37 * :ghpull:`6761`: object_info_reply field is oname, not name
38 * :ghpull:`6653`: Fix IPython.utils.ansispan() to ignore stray [0m
38 * :ghpull:`6653`: Fix IPython.utils.ansispan() to ignore stray [0m
39 * :ghpull:`6706`: Correctly display prompt numbers that are ``None``
39 * :ghpull:`6706`: Correctly display prompt numbers that are ``None``
40 * :ghpull:`6634`: don't use contains in SelectWidget item_query
40 * :ghpull:`6634`: don't use contains in SelectWidget item_query
41 * :ghpull:`6593`: note how to start the qtconsole
41 * :ghpull:`6593`: note how to start the qtconsole
42 * :ghpull:`6281`: more minor fixes to release scripts
42 * :ghpull:`6281`: more minor fixes to release scripts
43 * :ghpull:`5458`: Add support for PyQt5.
43 * :ghpull:`5458`: Add support for PyQt5.
44
44
45 Issues (6):
45 Issues (6):
46
46
47 * :ghissue:`7272`: qtconsole problems with pygments
47 * :ghissue:`7272`: qtconsole problems with pygments
48 * :ghissue:`7049`: Cause TypeError: 'NoneType' object is not callable in qtconsole
48 * :ghissue:`7049`: Cause TypeError: 'NoneType' object is not callable in qtconsole
49 * :ghissue:`6877`: Qt console doesn't work with pygments 2.0rc1
49 * :ghissue:`6877`: Qt console doesn't work with pygments 2.0rc1
50 * :ghissue:`6689`: Problem with string containing two or more question marks
50 * :ghissue:`6689`: Problem with string containing two or more question marks
51 * :ghissue:`6702`: Cell numbering after ``ClearOutput`` preprocessor
51 * :ghissue:`6702`: Cell numbering after ``ClearOutput`` preprocessor
52 * :ghissue:`6633`: selectwidget doesn't display 1 as a selection choice when passed in as a member of values list
52 * :ghissue:`6633`: selectwidget doesn't display 1 as a selection choice when passed in as a member of values list
53
53
54
54
55 Issues closed in 2.3.1
55 Issues closed in 2.3.1
56 ----------------------
56 ----------------------
57
57
58 Just one bugfix: fixed bad CRCRLF line-endings in notebooks on Windows
58 Just one bugfix: fixed bad CRCRLF line-endings in notebooks on Windows
59
59
60 Pull Requests (1):
60 Pull Requests (1):
61
61
62 * :ghpull:`6911`: don't use text mode in mkstemp
62 * :ghpull:`6911`: don't use text mode in mkstemp
63
63
64 Issues (1):
64 Issues (1):
65
65
66 * :ghissue:`6599`: Notebook.ipynb CR+LF turned into CR+CR+LF
66 * :ghissue:`6599`: Notebook.ipynb CR+LF turned into CR+CR+LF
67
67
68
68
69 Issues closed in 2.3.0
69 Issues closed in 2.3.0
70 ----------------------
70 ----------------------
71
71
72 GitHub stats for 2014/08/06 - 2014/10/01
72 GitHub stats for 2014/08/06 - 2014/10/01
73
73
74 These lists are automatically generated, and may be incomplete or contain duplicates.
74 These lists are automatically generated, and may be incomplete or contain duplicates.
75
75
76 The following 6 authors contributed 31 commits.
76 The following 6 authors contributed 31 commits.
77
77
78 * Benjamin Ragan-Kelley
78 * Benjamin Ragan-Kelley
79 * David Hirschfeld
79 * David Hirschfeld
80 * Eric Firing
80 * Eric Firing
81 * Jessica B. Hamrick
81 * Jessica B. Hamrick
82 * Matthias Bussonnier
82 * Matthias Bussonnier
83 * Thomas Kluyver
83 * Thomas Kluyver
84
84
85 We closed 16 issues and merged 9 pull requests;
85 We closed 16 issues and merged 9 pull requests;
86 this is the full list (generated with the script
86 this is the full list (generated with the script
87 :file:`tools/github_stats.py`):
87 :file:`tools/github_stats.py`):
88
88
89 Pull Requests (16):
89 Pull Requests (16):
90
90
91 * :ghpull:`6587`: support ``%matplotlib qt5`` and ``%matplotlib nbagg``
91 * :ghpull:`6587`: support ``%matplotlib qt5`` and ``%matplotlib nbagg``
92 * :ghpull:`6583`: Windows symlink test fixes
92 * :ghpull:`6583`: Windows symlink test fixes
93 * :ghpull:`6585`: fixes :ghissue:`6473`
93 * :ghpull:`6585`: fixes :ghissue:`6473`
94 * :ghpull:`6581`: Properly mock winreg functions for test
94 * :ghpull:`6581`: Properly mock winreg functions for test
95 * :ghpull:`6556`: Use some more informative asserts in inprocess kernel tests
95 * :ghpull:`6556`: Use some more informative asserts in inprocess kernel tests
96 * :ghpull:`6514`: Fix for copying metadata flags
96 * :ghpull:`6514`: Fix for copying metadata flags
97 * :ghpull:`6453`: Copy file metadata in atomic save
97 * :ghpull:`6453`: Copy file metadata in atomic save
98 * :ghpull:`6480`: only compare host:port in Websocket.check_origin
98 * :ghpull:`6480`: only compare host:port in Websocket.check_origin
99 * :ghpull:`6483`: Trim anchor link in heading cells, fixes :ghissue:`6324`
99 * :ghpull:`6483`: Trim anchor link in heading cells, fixes :ghissue:`6324`
100 * :ghpull:`6410`: Fix relative import in appnope
100 * :ghpull:`6410`: Fix relative import in appnope
101 * :ghpull:`6395`: update mathjax CDN url in nbconvert template
101 * :ghpull:`6395`: update mathjax CDN url in nbconvert template
102 * :ghpull:`6269`: Implement atomic save
102 * :ghpull:`6269`: Implement atomic save
103 * :ghpull:`6374`: Rename ``abort_queues`` --> ``_abort_queues``
103 * :ghpull:`6374`: Rename ``abort_queues`` --> ``_abort_queues``
104 * :ghpull:`6321`: Use appnope in qt and wx gui support from the terminal; closes :ghissue:`6189`
104 * :ghpull:`6321`: Use appnope in qt and wx gui support from the terminal; closes :ghissue:`6189`
105 * :ghpull:`6318`: use write_error instead of get_error_html
105 * :ghpull:`6318`: use write_error instead of get_error_html
106 * :ghpull:`6303`: Fix error message when failing to load a notebook
106 * :ghpull:`6303`: Fix error message when failing to load a notebook
107
107
108 Issues (9):
108 Issues (9):
109
109
110 * :ghissue:`6057`: ``%matplotlib`` + qt5
110 * :ghissue:`6057`: ``%matplotlib`` + qt5
111 * :ghissue:`6518`: Test failure in atomic save on Windows
111 * :ghissue:`6518`: Test failure in atomic save on Windows
112 * :ghissue:`6473`: Switching between "Raw Cell Format" and "Edit Metadata" does not work
112 * :ghissue:`6473`: Switching between "Raw Cell Format" and "Edit Metadata" does not work
113 * :ghissue:`6405`: Creating a notebook should respect directory permissions; saving should respect prior permissions
113 * :ghissue:`6405`: Creating a notebook should respect directory permissions; saving should respect prior permissions
114 * :ghissue:`6324`: Anchors in Heading don't work.
114 * :ghissue:`6324`: Anchors in Heading don't work.
115 * :ghissue:`6409`: No module named '_dummy'
115 * :ghissue:`6409`: No module named '_dummy'
116 * :ghissue:`6392`: Mathjax library link broken
116 * :ghissue:`6392`: Mathjax library link broken
117 * :ghissue:`6329`: IPython Notebook Server URL now requires "tree" at the end of the URL? (version 2.2)
117 * :ghissue:`6329`: IPython Notebook Server URL now requires "tree" at the end of the URL? (version 2.2)
118 * :ghissue:`6189`: ipython console freezes for increasing no of seconds in %pylab mode
118 * :ghissue:`6189`: ipython console freezes for increasing no of seconds in %pylab mode
119
119
120 Issues closed in 2.2.0
120 Issues closed in 2.2.0
121 ----------------------
121 ----------------------
122
122
123 GitHub stats for 2014/05/21 - 2014/08/06 (tag: rel-2.1.0)
123 GitHub stats for 2014/05/21 - 2014/08/06 (tag: rel-2.1.0)
124
124
125 These lists are automatically generated, and may be incomplete or contain duplicates.
125 These lists are automatically generated, and may be incomplete or contain duplicates.
126
126
127 The following 13 authors contributed 36 commits.
127 The following 13 authors contributed 36 commits.
128
128
129 * Adam Hodgen
129 * Adam Hodgen
130 * Benjamin Ragan-Kelley
130 * Benjamin Ragan-Kelley
131 * Björn Grüning
131 * Björn Grüning
132 * Dara Adib
132 * Dara Adib
133 * Eric Galloway
133 * Eric Galloway
134 * Jonathan Frederic
134 * Jonathan Frederic
135 * Kyle Kelley
135 * Kyle Kelley
136 * Matthias Bussonnier
136 * Matthias Bussonnier
137 * Paul Ivanov
137 * Paul Ivanov
138 * Shayne Hodge
138 * Shayne Hodge
139 * Steven Anton
139 * Steven Anton
140 * Thomas Kluyver
140 * Thomas Kluyver
141 * Zahari
141 * Zahari
142
142
143 We closed 23 issues and merged 11 pull requests;
143 We closed 23 issues and merged 11 pull requests;
144 this is the full list (generated with the script
144 this is the full list (generated with the script
145 :file:`tools/github_stats.py`):
145 :file:`tools/github_stats.py`):
146
146
147 Pull Requests (23):
147 Pull Requests (23):
148
148
149 * :ghpull:`6279`: minor updates to release scripts
149 * :ghpull:`6279`: minor updates to release scripts
150 * :ghpull:`6273`: Upgrade default mathjax version.
150 * :ghpull:`6273`: Upgrade default mathjax version.
151 * :ghpull:`6249`: always use HTTPS getting mathjax from CDN
151 * :ghpull:`6249`: always use HTTPS getting mathjax from CDN
152 * :ghpull:`6114`: update hmac signature comparison
152 * :ghpull:`6114`: update hmac signature comparison
153 * :ghpull:`6195`: Close handle on new temporary files before returning filename
153 * :ghpull:`6195`: Close handle on new temporary files before returning filename
154 * :ghpull:`6143`: pin tornado to < 4 on travis js tests
154 * :ghpull:`6143`: pin tornado to < 4 on travis js tests
155 * :ghpull:`6134`: remove rackcdn https workaround for mathjax cdn
155 * :ghpull:`6134`: remove rackcdn https workaround for mathjax cdn
156 * :ghpull:`6120`: Only allow iframe embedding on same origin.
156 * :ghpull:`6120`: Only allow iframe embedding on same origin.
157 * :ghpull:`6117`: Remove / from route of TreeRedirectHandler.
157 * :ghpull:`6117`: Remove / from route of TreeRedirectHandler.
158 * :ghpull:`6105`: only set allow_origin_pat if defined
158 * :ghpull:`6105`: only set allow_origin_pat if defined
159 * :ghpull:`6102`: Add newline if missing to end of script magic cell
159 * :ghpull:`6102`: Add newline if missing to end of script magic cell
160 * :ghpull:`6077`: allow unicode keys in dicts in json_clean
160 * :ghpull:`6077`: allow unicode keys in dicts in json_clean
161 * :ghpull:`6061`: make CORS configurable
161 * :ghpull:`6061`: make CORS configurable
162 * :ghpull:`6081`: don’t modify dict keys while iterating through them
162 * :ghpull:`6081`: don’t modify dict keys while iterating through them
163 * :ghpull:`5803`: unify visual line handling
163 * :ghpull:`5803`: unify visual line handling
164 * :ghpull:`6005`: Changed right arrow key movement function to mirror left arrow key
164 * :ghpull:`6005`: Changed right arrow key movement function to mirror left arrow key
165 * :ghpull:`6029`: add pickleutil.PICKLE_PROTOCOL
165 * :ghpull:`6029`: add pickleutil.PICKLE_PROTOCOL
166 * :ghpull:`6003`: Set kernel_id before checking websocket
166 * :ghpull:`6003`: Set kernel_id before checking websocket
167 * :ghpull:`5994`: Fix ssh tunnel for Python3
167 * :ghpull:`5994`: Fix ssh tunnel for Python3
168 * :ghpull:`5973`: Do not create checkpoint_dir relative to current dir
168 * :ghpull:`5973`: Do not create checkpoint_dir relative to current dir
169 * :ghpull:`5933`: fix qt_loader import hook signature
169 * :ghpull:`5933`: fix qt_loader import hook signature
170 * :ghpull:`5944`: Markdown rendering bug fix.
170 * :ghpull:`5944`: Markdown rendering bug fix.
171 * :ghpull:`5917`: use shutil.move instead of os.rename
171 * :ghpull:`5917`: use shutil.move instead of os.rename
172
172
173 Issues (11):
173 Issues (11):
174
174
175 * :ghissue:`6246`: Include MathJax by default or access the CDN over a secure connection
175 * :ghissue:`6246`: Include MathJax by default or access the CDN over a secure connection
176 * :ghissue:`5525`: Websocket origin check fails when used with Apache WS proxy
176 * :ghissue:`5525`: Websocket origin check fails when used with Apache WS proxy
177 * :ghissue:`5901`: 2 test failures in Python 3.4 in parallel group
177 * :ghissue:`5901`: 2 test failures in Python 3.4 in parallel group
178 * :ghissue:`5926`: QT console: text selection cannot be made from left to right with keyboard
178 * :ghissue:`5926`: QT console: text selection cannot be made from left to right with keyboard
179 * :ghissue:`5998`: use_dill does not work in Python 3.4
179 * :ghissue:`5998`: use_dill does not work in Python 3.4
180 * :ghissue:`5964`: Traceback on Qt console exit
180 * :ghissue:`5964`: Traceback on Qt console exit
181 * :ghissue:`5787`: Error in Notebook-Generated latex (nbconvert)
181 * :ghissue:`5787`: Error in Notebook-Generated latex (nbconvert)
182 * :ghissue:`5950`: qtconsole truncates help
182 * :ghissue:`5950`: qtconsole truncates help
183 * :ghissue:`5943`: 2.x: notebook fails to load when using HTML comments
183 * :ghissue:`5943`: 2.x: notebook fails to load when using HTML comments
184 * :ghissue:`5932`: Qt ImportDenier Does Not Adhere to PEP302
184 * :ghissue:`5932`: Qt ImportDenier Does Not Adhere to PEP302
185 * :ghissue:`5898`: OSError when moving configuration file
185 * :ghissue:`5898`: OSError when moving configuration file
186
186
187 Issues closed in 2.1.0
187 Issues closed in 2.1.0
188 ----------------------
188 ----------------------
189
189
190 GitHub stats for 2014/04/02 - 2014/05/21 (since 2.0.0)
190 GitHub stats for 2014/04/02 - 2014/05/21 (since 2.0.0)
191
191
192 These lists are automatically generated, and may be incomplete or contain duplicates.
192 These lists are automatically generated, and may be incomplete or contain duplicates.
193
193
194 The following 35 authors contributed 145 commits.
194 The following 35 authors contributed 145 commits.
195
195
196 * Adrian Price-Whelan
196 * Adrian Price-Whelan
197 * Aron Ahmadia
197 * Aron Ahmadia
198 * Benjamin Ragan-Kelley
198 * Benjamin Ragan-Kelley
199 * Benjamin Schultz
199 * Benjamin Schultz
200 * Björn Linse
200 * Björn Linse
201 * Blake Griffith
201 * Blake Griffith
202 * chebee7i
202 * chebee7i
203 * Damián Avila
203 * Damián Avila
204 * Dav Clark
204 * Dav Clark
205 * dexterdev
205 * dexterdev
206 * Erik Tollerud
206 * Erik Tollerud
207 * Grzegorz Rożniecki
207 * Grzegorz Rożniecki
208 * Jakob Gager
208 * Jakob Gager
209 * jdavidheiser
209 * jdavidheiser
210 * Jessica B. Hamrick
210 * Jessica B. Hamrick
211 * Jim Garrison
211 * Jim Garrison
212 * Jonathan Frederic
212 * Jonathan Frederic
213 * Matthias Bussonnier
213 * Matthias Bussonnier
214 * Maximilian Albert
214 * Maximilian Albert
215 * Mohan Raj Rajamanickam
215 * Mohan Raj Rajamanickam
216 * ncornette
216 * ncornette
217 * Nikolay Koldunov
217 * Nikolay Koldunov
218 * Nile Geisinger
218 * Nile Geisinger
219 * Pankaj Pandey
219 * Pankaj Pandey
220 * Paul Ivanov
220 * Paul Ivanov
221 * Pierre Haessig
221 * Pierre Haessig
222 * Raffaele De Feo
222 * Raffaele De Feo
223 * Renaud Richardet
223 * Renaud Richardet
224 * Spencer Nelson
224 * Spencer Nelson
225 * Steve Chan
225 * Steve Chan
226 * sunny
226 * sunny
227 * Susan Tan
227 * Susan Tan
228 * Thomas Kluyver
228 * Thomas Kluyver
229 * Yaroslav Halchenko
229 * Yaroslav Halchenko
230 * zah
230 * zah
231
231
232 We closed a total of 129 issues, 92 pull requests and 37 regular issues;
232 We closed a total of 129 issues, 92 pull requests and 37 regular issues;
233 this is the full list (generated with the script
233 this is the full list (generated with the script
234 :file:`tools/github_stats.py --milestone 2.1`):
234 :file:`tools/github_stats.py --milestone 2.1`):
235
235
236 Pull Requests (92):
236 Pull Requests (92):
237
237
238 * :ghpull:`5871`: specify encoding in msgpack.unpackb
238 * :ghpull:`5871`: specify encoding in msgpack.unpackb
239 * :ghpull:`5869`: Catch more errors from clipboard access on Windows
239 * :ghpull:`5869`: Catch more errors from clipboard access on Windows
240 * :ghpull:`5866`: Make test robust against differences in line endings
240 * :ghpull:`5866`: Make test robust against differences in line endings
241 * :ghpull:`5605`: Two cell toolbar fixes.
241 * :ghpull:`5605`: Two cell toolbar fixes.
242 * :ghpull:`5843`: remove Firefox-specific CSS workaround
242 * :ghpull:`5843`: remove Firefox-specific CSS workaround
243 * :ghpull:`5845`: Pass Windows interrupt event to kernels as an environment variable
243 * :ghpull:`5845`: Pass Windows interrupt event to kernels as an environment variable
244 * :ghpull:`5835`: fix typo in v2 convert
244 * :ghpull:`5835`: fix typo in v2 convert
245 * :ghpull:`5841`: Fix writing history with output to a file in Python 2
245 * :ghpull:`5841`: Fix writing history with output to a file in Python 2
246 * :ghpull:`5842`: fix typo in nbconvert help
246 * :ghpull:`5842`: fix typo in nbconvert help
247 * :ghpull:`5846`: Fix typos in Cython example
247 * :ghpull:`5846`: Fix typos in Cython example
248 * :ghpull:`5839`: Close graphics dev in finally clause
248 * :ghpull:`5839`: Close graphics dev in finally clause
249 * :ghpull:`5837`: pass on install docs
249 * :ghpull:`5837`: pass on install docs
250 * :ghpull:`5832`: Fixed example to work with python3
250 * :ghpull:`5832`: Fixed example to work with python3
251 * :ghpull:`5826`: allow notebook tour instantiation to fail
251 * :ghpull:`5826`: allow notebook tour instantiation to fail
252 * :ghpull:`5560`: Minor expansion of Cython example
252 * :ghpull:`5560`: Minor expansion of Cython example
253 * :ghpull:`5818`: interpret any exception in getcallargs as not callable
253 * :ghpull:`5818`: interpret any exception in getcallargs as not callable
254 * :ghpull:`5816`: Add output to IPython directive when in verbatim mode.
254 * :ghpull:`5816`: Add output to IPython directive when in verbatim mode.
255 * :ghpull:`5822`: Don't overwrite widget description in interact
255 * :ghpull:`5822`: Don't overwrite widget description in interact
256 * :ghpull:`5782`: Silence exception thrown by completer when dir() does not return a list
256 * :ghpull:`5782`: Silence exception thrown by completer when dir() does not return a list
257 * :ghpull:`5807`: Drop log level to info for Qt console shutdown
257 * :ghpull:`5807`: Drop log level to info for Qt console shutdown
258 * :ghpull:`5814`: Remove -i options from mv, rm and cp aliases
258 * :ghpull:`5814`: Remove -i options from mv, rm and cp aliases
259 * :ghpull:`5812`: Fix application name when printing subcommand help.
259 * :ghpull:`5812`: Fix application name when printing subcommand help.
260 * :ghpull:`5804`: remove an inappropriate ``!``
260 * :ghpull:`5804`: remove an inappropriate ``!``
261 * :ghpull:`5805`: fix engine startup files
261 * :ghpull:`5805`: fix engine startup files
262 * :ghpull:`5806`: Don't auto-move .config/ipython if symbolic link
262 * :ghpull:`5806`: Don't auto-move .config/ipython if symbolic link
263 * :ghpull:`5716`: Add booktabs package to latex base.tplx
263 * :ghpull:`5716`: Add booktabs package to latex base.tplx
264 * :ghpull:`5669`: allows threadsafe sys.stdout.flush from background threads
264 * :ghpull:`5669`: allows threadsafe sys.stdout.flush from background threads
265 * :ghpull:`5668`: allow async output on the most recent request
265 * :ghpull:`5668`: allow async output on the most recent request
266 * :ghpull:`5768`: fix cursor keys in long lines wrapped in markdown
266 * :ghpull:`5768`: fix cursor keys in long lines wrapped in markdown
267 * :ghpull:`5788`: run cells with ``silent=True`` in ``%run nb.ipynb``
267 * :ghpull:`5788`: run cells with ``silent=True`` in ``%run nb.ipynb``
268 * :ghpull:`5715`: log all failed ajax API requests
268 * :ghpull:`5715`: log all failed ajax API requests
269 * :ghpull:`5769`: Don't urlescape the text that goes into a title tag
269 * :ghpull:`5769`: Don't urlescape the text that goes into a title tag
270 * :ghpull:`5762`: Fix check for pickling closures
270 * :ghpull:`5762`: Fix check for pickling closures
271 * :ghpull:`5766`: View.map with empty sequence should return empty list
271 * :ghpull:`5766`: View.map with empty sequence should return empty list
272 * :ghpull:`5758`: Applied bug fix: using fc and ec did not properly set the figure canvas ...
272 * :ghpull:`5758`: Applied bug fix: using fc and ec did not properly set the figure canvas ...
273 * :ghpull:`5754`: Format command name into subcommand_description at run time, not import
273 * :ghpull:`5754`: Format command name into subcommand_description at run time, not import
274 * :ghpull:`5744`: Describe using PyPI/pip to distribute & install extensions
274 * :ghpull:`5744`: Describe using PyPI/pip to distribute & install extensions
275 * :ghpull:`5712`: monkeypatch inspect.findsource only when we use it
275 * :ghpull:`5712`: monkeypatch inspect.findsource only when we use it
276 * :ghpull:`5708`: create checkpoints dir in notebook subdirectories
276 * :ghpull:`5708`: create checkpoints dir in notebook subdirectories
277 * :ghpull:`5714`: log error message when API requests fail
277 * :ghpull:`5714`: log error message when API requests fail
278 * :ghpull:`5732`: Quick typo fix in nbformat/convert.py
278 * :ghpull:`5732`: Quick typo fix in nbformat/convert.py
279 * :ghpull:`5713`: Fix a NameError in IPython.parallel
279 * :ghpull:`5713`: Fix a NameError in IPython.parallel
280 * :ghpull:`5704`: Update nbconvertapp.py
280 * :ghpull:`5704`: Update nbconvertapp.py
281 * :ghpull:`5534`: cleanup some ``pre`` css inheritance
281 * :ghpull:`5534`: cleanup some ``pre`` css inheritance
282 * :ghpull:`5699`: don't use common names in require decorators
282 * :ghpull:`5699`: don't use common names in require decorators
283 * :ghpull:`5692`: Update notebook.rst fixing broken reference to notebook examples readme
283 * :ghpull:`5692`: Update notebook.rst fixing broken reference to notebook examples readme
284 * :ghpull:`5693`: Update parallel_intro.rst to fix a broken link to examples
284 * :ghpull:`5693`: Update parallel_intro.rst to fix a broken link to examples
285 * :ghpull:`5486`: disambiguate to location when no IPs can be determined
285 * :ghpull:`5486`: disambiguate to location when no IPs can be determined
286 * :ghpull:`5574`: Remove the outdated keyboard shortcuts from notebook docs
286 * :ghpull:`5574`: Remove the outdated keyboard shortcuts from notebook docs
287 * :ghpull:`5568`: Use ``__qualname__`` in pretty reprs for Python 3
287 * :ghpull:`5568`: Use ``__qualname__`` in pretty reprs for Python 3
288 * :ghpull:`5678`: Fix copy & paste error in docstring of ImageWidget class
288 * :ghpull:`5678`: Fix copy & paste error in docstring of ImageWidget class
289 * :ghpull:`5677`: Fix %bookmark -l for Python 3
289 * :ghpull:`5677`: Fix %bookmark -l for Python 3
290 * :ghpull:`5670`: nbconvert: Fix CWD imports
290 * :ghpull:`5670`: nbconvert: Fix CWD imports
291 * :ghpull:`5647`: Mention git hooks in install documentation
291 * :ghpull:`5647`: Mention git hooks in install documentation
292 * :ghpull:`5671`: Fix blank slides issue in Reveal slideshow pdf export
292 * :ghpull:`5671`: Fix blank slides issue in Reveal slideshow pdf export
293 * :ghpull:`5657`: use 'localhost' as default for the notebook server
293 * :ghpull:`5657`: use 'localhost' as default for the notebook server
294 * :ghpull:`5584`: more semantic icons
294 * :ghpull:`5584`: more semantic icons
295 * :ghpull:`5594`: update components with marked-0.3.2
295 * :ghpull:`5594`: update components with marked-0.3.2
296 * :ghpull:`5500`: check for Python 3.2
296 * :ghpull:`5500`: check for Python 3.2
297 * :ghpull:`5582`: reset readline after running PYTHONSTARTUP
297 * :ghpull:`5582`: reset readline after running PYTHONSTARTUP
298 * :ghpull:`5630`: Fixed Issue :ghissue:`4012` Added Help menubar link to Github markdown doc
298 * :ghpull:`5630`: Fixed Issue :ghissue:`4012` Added Help menubar link to Github markdown doc
299 * :ghpull:`5613`: Fixing bug :ghissue:`5607`
299 * :ghpull:`5613`: Fixing bug :ghissue:`5607`
300 * :ghpull:`5633`: Provide more help if lessc is not found.
300 * :ghpull:`5633`: Provide more help if lessc is not found.
301 * :ghpull:`5620`: fixed a typo in IPython.core.formatters
301 * :ghpull:`5620`: fixed a typo in IPython.core.formatters
302 * :ghpull:`5619`: Fix typo in storemagic module docstring
302 * :ghpull:`5619`: Fix typo in storemagic module docstring
303 * :ghpull:`5592`: add missing ``browser`` to notebook_aliases list
303 * :ghpull:`5592`: add missing ``browser`` to notebook_aliases list
304 * :ghpull:`5506`: Fix ipconfig regex pattern
304 * :ghpull:`5506`: Fix ipconfig regex pattern
305 * :ghpull:`5581`: Fix rmagic for cells ending in comment.
305 * :ghpull:`5581`: Fix rmagic for cells ending in comment.
306 * :ghpull:`5576`: only process cr if it's found
306 * :ghpull:`5576`: only process cr if it's found
307 * :ghpull:`5478`: Add git-hooks install script. Update README.md
307 * :ghpull:`5478`: Add git-hooks install script. Update README.md
308 * :ghpull:`5546`: do not shutdown notebook if 'n' is part of answer
308 * :ghpull:`5546`: do not shutdown notebook if 'n' is part of answer
309 * :ghpull:`5527`: Don't remove upload items from nav tree unless explicitly requested.
309 * :ghpull:`5527`: Don't remove upload items from nav tree unless explicitly requested.
310 * :ghpull:`5501`: remove inappropriate wheel tag override
310 * :ghpull:`5501`: remove inappropriate wheel tag override
311 * :ghpull:`5548`: FileNotebookManager: Use shutil.move() instead of os.rename()
311 * :ghpull:`5548`: FileNotebookManager: Use shutil.move() instead of os.rename()
312 * :ghpull:`5524`: never use ``for (var i in array)``
312 * :ghpull:`5524`: never use ``for (var i in array)``
313 * :ghpull:`5459`: Fix interact animation page jump FF
313 * :ghpull:`5459`: Fix interact animation page jump FF
314 * :ghpull:`5559`: Minor typo fix in "Cython Magics.ipynb"
314 * :ghpull:`5559`: Minor typo fix in "Cython Magics.ipynb"
315 * :ghpull:`5507`: Fix typo in interactive widgets examples index notebook
315 * :ghpull:`5507`: Fix typo in interactive widgets examples index notebook
316 * :ghpull:`5554`: Make HasTraits pickleable
316 * :ghpull:`5554`: Make HasTraits pickleable
317 * :ghpull:`5535`: fix n^2 performance issue in coalesce_streams preprocessor
317 * :ghpull:`5535`: fix n^2 performance issue in coalesce_streams preprocessor
318 * :ghpull:`5522`: fix iteration over Client
318 * :ghpull:`5522`: fix iteration over Client
319 * :ghpull:`5488`: Added missing require and jquery from cdn.
319 * :ghpull:`5488`: Added missing require and jquery from cdn.
320 * :ghpull:`5516`: ENH: list generated config files in generated, and rm them upon clean
320 * :ghpull:`5516`: ENH: list generated config files in generated, and rm them upon clean
321 * :ghpull:`5493`: made a minor fix to one of the widget examples
321 * :ghpull:`5493`: made a minor fix to one of the widget examples
322 * :ghpull:`5512`: Update tooltips to refer to shift-tab
322 * :ghpull:`5512`: Update tooltips to refer to shift-tab
323 * :ghpull:`5505`: Make backport_pr work on Python 3
323 * :ghpull:`5505`: Make backport_pr work on Python 3
324 * :ghpull:`5503`: check explicitly for 'dev' before adding the note to docs
324 * :ghpull:`5503`: check explicitly for 'dev' before adding the note to docs
325 * :ghpull:`5498`: use milestones to indicate backport
325 * :ghpull:`5498`: use milestones to indicate backport
326 * :ghpull:`5492`: Polish whatsnew docs
326 * :ghpull:`5492`: Polish whatsnew docs
327 * :ghpull:`5495`: Fix various broken things in docs
327 * :ghpull:`5495`: Fix various broken things in docs
328 * :ghpull:`5496`: Exclude whatsnew/pr directory from docs builds
328 * :ghpull:`5496`: Exclude whatsnew/pr directory from docs builds
329 * :ghpull:`5489`: Fix required Python versions
329 * :ghpull:`5489`: Fix required Python versions
330
330
331 Issues (37):
331 Issues (37):
332
332
333 * :ghissue:`5364`: Horizontal scrollbar hides cell's last line on Firefox
333 * :ghissue:`5364`: Horizontal scrollbar hides cell's last line on Firefox
334 * :ghissue:`5192`: horisontal scrollbar overlaps output or touches next cell
334 * :ghissue:`5192`: horisontal scrollbar overlaps output or touches next cell
335 * :ghissue:`5840`: Third-party Windows kernels don't get interrupt signal
335 * :ghissue:`5840`: Third-party Windows kernels don't get interrupt signal
336 * :ghissue:`2412`: print history to file using qtconsole and notebook
336 * :ghissue:`2412`: print history to file using qtconsole and notebook
337 * :ghissue:`5703`: Notebook doesn't render with "ask me every time" cookie setting in Firefox
337 * :ghissue:`5703`: Notebook doesn't render with "ask me every time" cookie setting in Firefox
338 * :ghissue:`5817`: calling mock object in IPython 2.0.0 under Python 3.4.0 raises AttributeError
338 * :ghissue:`5817`: calling mock object in IPython 2.0.0 under Python 3.4.0 raises AttributeError
339 * :ghissue:`5499`: Error running widgets nbconvert example
339 * :ghissue:`5499`: Error running widgets nbconvert example
340 * :ghissue:`5654`: Broken links from ipython documentation
340 * :ghissue:`5654`: Broken links from ipython documentation
341 * :ghissue:`5019`: print in QT event callback doesn't show up in ipython notebook.
341 * :ghissue:`5019`: print in QT event callback doesn't show up in ipython notebook.
342 * :ghissue:`5800`: Only last In prompt number set ?
342 * :ghissue:`5800`: Only last In prompt number set ?
343 * :ghissue:`5801`: startup_command specified in ipengine_config.py is not executed
343 * :ghissue:`5801`: startup_command specified in ipengine_config.py is not executed
344 * :ghissue:`5690`: ipython 2.0.0 and pandoc 1.12.2.1 problem
344 * :ghissue:`5690`: ipython 2.0.0 and pandoc 1.12.2.1 problem
345 * :ghissue:`5408`: Add checking/flushing of background output from kernel in mainloop
345 * :ghissue:`5408`: Add checking/flushing of background output from kernel in mainloop
346 * :ghissue:`5407`: clearing message handlers on status=idle loses async output
346 * :ghissue:`5407`: clearing message handlers on status=idle loses async output
347 * :ghissue:`5467`: Incorrect behavior of up/down keyboard arrows in code cells on wrapped lines
347 * :ghissue:`5467`: Incorrect behavior of up/down keyboard arrows in code cells on wrapped lines
348 * :ghissue:`3085`: nicer notebook error message when lacking permissions
348 * :ghissue:`3085`: nicer notebook error message when lacking permissions
349 * :ghissue:`5765`: map_sync over empty list raises IndexError
349 * :ghissue:`5765`: map_sync over empty list raises IndexError
350 * :ghissue:`5553`: Notebook matplotlib inline backend: can't set figure facecolor
350 * :ghissue:`5553`: Notebook matplotlib inline backend: can't set figure facecolor
351 * :ghissue:`5710`: inspect.findsource monkeypatch raises wrong exception for C extensions
351 * :ghissue:`5710`: inspect.findsource monkeypatch raises wrong exception for C extensions
352 * :ghissue:`5706`: Multi-Directory notebooks overwrite each other's checkpoints
352 * :ghissue:`5706`: Multi-Directory notebooks overwrite each other's checkpoints
353 * :ghissue:`5698`: can't require a function named ``f``
353 * :ghissue:`5698`: can't require a function named ``f``
354 * :ghissue:`5569`: Keyboard shortcuts in documentation are out of date
354 * :ghissue:`5569`: Keyboard shortcuts in documentation are out of date
355 * :ghissue:`5566`: Function name printing should use ``__qualname__`` instead of ``__name__`` (Python 3)
355 * :ghissue:`5566`: Function name printing should use ``__qualname__`` instead of ``__name__`` (Python 3)
356 * :ghissue:`5676`: "bookmark -l" not working in ipython 2.0
356 * :ghissue:`5676`: "bookmark -l" not working in ipython 2.0
357 * :ghissue:`5555`: Differentiate more clearly between Notebooks and Folders in new UI
357 * :ghissue:`5555`: Differentiate more clearly between Notebooks and Folders in new UI
358 * :ghissue:`5590`: Marked double escape
358 * :ghissue:`5590`: Marked double escape
359 * :ghissue:`5514`: import tab-complete fail with ipython 2.0 shell
359 * :ghissue:`5514`: import tab-complete fail with ipython 2.0 shell
360 * :ghissue:`4012`: Notebook: link to markdown formatting reference
360 * :ghissue:`4012`: Notebook: link to markdown formatting reference
361 * :ghissue:`5611`: Typo in 'storemagic' documentation
361 * :ghissue:`5611`: Typo in 'storemagic' documentation
362 * :ghissue:`5589`: Kernel start fails when using --browser argument
362 * :ghissue:`5589`: Kernel start fails when using --browser argument
363 * :ghissue:`5491`: Bug in Windows ipconfig ip address regular expression
363 * :ghissue:`5491`: Bug in Windows ipconfig ip address regular expression
364 * :ghissue:`5579`: rmagic extension throws 'Error while parsing the string.' when last line is comment
364 * :ghissue:`5579`: rmagic extension throws 'Error while parsing the string.' when last line is comment
365 * :ghissue:`5518`: Ipython2 will not open ipynb in example directory
365 * :ghissue:`5518`: Ipython2 will not open ipynb in example directory
366 * :ghissue:`5561`: New widget documentation has missing notebook link
366 * :ghissue:`5561`: New widget documentation has missing notebook link
367 * :ghissue:`5128`: Page jumping when output from widget interaction replaced
367 * :ghissue:`5128`: Page jumping when output from widget interaction replaced
368 * :ghissue:`5519`: IPython.parallel.Client behavior as iterator
368 * :ghissue:`5519`: IPython.parallel.Client behavior as iterator
369 * :ghissue:`5510`: Tab-completion for function argument list
369 * :ghissue:`5510`: Tab-completion for function argument list
370
370
371
371
372 Issues closed in 2.0.0
372 Issues closed in 2.0.0
373 ----------------------
373 ----------------------
374
374
375
375
376 GitHub stats for 2013/08/09 - 2014/04/01 (since 1.0.0)
376 GitHub stats for 2013/08/09 - 2014/04/01 (since 1.0.0)
377
377
378 These lists are automatically generated, and may be incomplete or contain duplicates.
378 These lists are automatically generated, and may be incomplete or contain duplicates.
379
379
380 The following 94 authors contributed 3949 commits.
380 The following 94 authors contributed 3949 commits.
381
381
382 * Aaron Meurer
382 * Aaron Meurer
383 * Abhinav Upadhyay
383 * Abhinav Upadhyay
384 * Adam Riggall
384 * Adam Riggall
385 * Alex Rudy
385 * Alex Rudy
386 * Andrew Mark
386 * Andrew Mark
387 * Angus Griffith
387 * Angus Griffith
388 * Antony Lee
388 * Antony Lee
389 * Aron Ahmadia
389 * Aron Ahmadia
390 * Arun Persaud
390 * Arun Persaud
391 * Benjamin Ragan-Kelley
391 * Benjamin Ragan-Kelley
392 * Bing Xia
392 * Bing Xia
393 * Blake Griffith
393 * Blake Griffith
394 * Bouke van der Bijl
394 * Bouke van der Bijl
395 * Bradley M. Froehle
395 * Bradley M. Froehle
396 * Brian E. Granger
396 * Brian E. Granger
397 * Carlos Cordoba
397 * Carlos Cordoba
398 * chapmanb
398 * chapmanb
399 * chebee7i
399 * chebee7i
400 * Christoph Gohlke
400 * Christoph Gohlke
401 * Christophe Pradal
401 * Christophe Pradal
402 * Cyrille Rossant
402 * Cyrille Rossant
403 * Damián Avila
403 * Damián Avila
404 * Daniel B. Vasquez
404 * Daniel B. Vasquez
405 * Dav Clark
405 * Dav Clark
406 * David Hirschfeld
406 * David Hirschfeld
407 * David P. Sanders
407 * David P. Sanders
408 * David Wyde
408 * David Wyde
409 * David Österberg
409 * David Österberg
410 * Doug Blank
410 * Doug Blank
411 * Dražen Lučanin
411 * Dražen Lučanin
412 * epifanio
412 * epifanio
413 * Fernando Perez
413 * Fernando Perez
414 * Gabriel Becker
414 * Gabriel Becker
415 * Geert Barentsen
415 * Geert Barentsen
416 * Hans Meine
416 * Hans Meine
417 * Ingolf Becker
417 * Ingolf Becker
418 * Jake Vanderplas
418 * Jake Vanderplas
419 * Jakob Gager
419 * Jakob Gager
420 * James Porter
420 * James Porter
421 * Jason Grout
421 * Jason Grout
422 * Jeffrey Tratner
422 * Jeffrey Tratner
423 * Jonah Graham
423 * Jonah Graham
424 * Jonathan Frederic
424 * Jonathan Frederic
425 * Joris Van den Bossche
425 * Joris Van den Bossche
426 * Juergen Hasch
426 * Juergen Hasch
427 * Julian Taylor
427 * Julian Taylor
428 * Katie Silverio
428 * Katie Silverio
429 * Kevin Burke
429 * Kevin Burke
430 * Kieran O'Mahony
430 * Kieran O'Mahony
431 * Konrad Hinsen
431 * Konrad Hinsen
432 * Kyle Kelley
432 * Kyle Kelley
433 * Lawrence Fu
433 * Lawrence Fu
434 * Marc Molla
434 * Marc Molla
435 * Martín Gaitán
435 * Martín Gaitán
436 * Matt Henderson
436 * Matt Henderson
437 * Matthew Brett
437 * Matthew Brett
438 * Matthias Bussonnier
438 * Matthias Bussonnier
439 * Michael Droettboom
439 * Michael Droettboom
440 * Mike McKerns
440 * Mike McKerns
441 * Nathan Goldbaum
441 * Nathan Goldbaum
442 * Pablo de Oliveira
442 * Pablo de Oliveira
443 * Pankaj Pandey
443 * Pankaj Pandey
444 * Pascal Schetelat
444 * Pascal Schetelat
445 * Paul Ivanov
445 * Paul Ivanov
446 * Paul Moore
446 * Paul Moore
447 * Pere Vilas
447 * Pere Vilas
448 * Peter Davis
448 * Peter Davis
449 * Philippe Mallet-Ladeira
449 * Philippe Mallet-Ladeira
450 * Preston Holmes
450 * Preston Holmes
451 * Puneeth Chaganti
451 * Puneeth Chaganti
452 * Richard Everson
452 * Richard Everson
453 * Roberto Bonvallet
453 * Roberto Bonvallet
454 * Samuel Ainsworth
454 * Samuel Ainsworth
455 * Sean Vig
455 * Sean Vig
456 * Shashi Gowda
456 * Shashi Gowda
457 * Skipper Seabold
457 * Skipper Seabold
458 * Stephan Rave
458 * Stephan Rave
459 * Steve Fox
459 * Steve Fox
460 * Steven Silvester
460 * Steven Silvester
461 * stonebig
461 * stonebig
462 * Susan Tan
462 * Susan Tan
463 * Sylvain Corlay
463 * Sylvain Corlay
464 * Takeshi Kanmae
464 * Takeshi Kanmae
465 * Ted Drain
465 * Ted Drain
466 * Thomas A Caswell
466 * Thomas A Caswell
467 * Thomas Kluyver
467 * Thomas Kluyver
468 * Théophile Studer
468 * Théophile Studer
469 * Volker Braun
469 * Volker Braun
470 * Wieland Hoffmann
470 * Wieland Hoffmann
471 * Yaroslav Halchenko
471 * Yaroslav Halchenko
472 * Yoval P.
472 * Yoval P.
473 * Yung Siang Liau
473 * Yung Siang Liau
474 * Zachary Sailer
474 * Zachary Sailer
475 * zah
475 * zah
476
476
477
477
478 We closed a total of 1121 issues, 687 pull requests and 434 regular issues;
478 We closed a total of 1121 issues, 687 pull requests and 434 regular issues;
479 this is the full list (generated with the script
479 this is the full list (generated with the script
480 :file:`tools/github_stats.py`):
480 :file:`tools/github_stats.py`):
481
481
482 Pull Requests (687):
482 Pull Requests (687):
483
483
484 * :ghpull:`5487`: remove weird unicode space in the new copyright header
484 * :ghpull:`5487`: remove weird unicode space in the new copyright header
485 * :ghpull:`5476`: For 2.0: Fix links in Notebook Help Menu
485 * :ghpull:`5476`: For 2.0: Fix links in Notebook Help Menu
486 * :ghpull:`5337`: Examples reorganization
486 * :ghpull:`5337`: Examples reorganization
487 * :ghpull:`5436`: CodeMirror shortcuts in QuickHelp
487 * :ghpull:`5436`: CodeMirror shortcuts in QuickHelp
488 * :ghpull:`5444`: Fix numeric verification for Int and Float text widgets.
488 * :ghpull:`5444`: Fix numeric verification for Int and Float text widgets.
489 * :ghpull:`5449`: Stretch keyboard shortcut dialog
489 * :ghpull:`5449`: Stretch keyboard shortcut dialog
490 * :ghpull:`5473`: Minor corrections of git-hooks setup instructions
490 * :ghpull:`5473`: Minor corrections of git-hooks setup instructions
491 * :ghpull:`5471`: Add coding magic comment to nbconvert Python template
491 * :ghpull:`5471`: Add coding magic comment to nbconvert Python template
492 * :ghpull:`5452`: print_figure returns unicode for svg
492 * :ghpull:`5452`: print_figure returns unicode for svg
493 * :ghpull:`5450`: proposal: remove codename
493 * :ghpull:`5450`: proposal: remove codename
494 * :ghpull:`5462`: DOC : fixed minor error in using topological sort
494 * :ghpull:`5462`: DOC : fixed minor error in using topological sort
495 * :ghpull:`5463`: make spin_thread tests more forgiving of slow VMs
495 * :ghpull:`5463`: make spin_thread tests more forgiving of slow VMs
496 * :ghpull:`5464`: Fix starting notebook server with file/directory at command line.
496 * :ghpull:`5464`: Fix starting notebook server with file/directory at command line.
497 * :ghpull:`5453`: remove gitwash
497 * :ghpull:`5453`: remove gitwash
498 * :ghpull:`5454`: Improve history API docs
498 * :ghpull:`5454`: Improve history API docs
499 * :ghpull:`5431`: update github_stats and gh_api for 2.0
499 * :ghpull:`5431`: update github_stats and gh_api for 2.0
500 * :ghpull:`5290`: Add dual mode JS tests
500 * :ghpull:`5290`: Add dual mode JS tests
501 * :ghpull:`5451`: check that a handler is actually registered in ShortcutManager.handles
501 * :ghpull:`5451`: check that a handler is actually registered in ShortcutManager.handles
502 * :ghpull:`5447`: Add %%python2 cell magic
502 * :ghpull:`5447`: Add %%python2 cell magic
503 * :ghpull:`5439`: Point to the stable SymPy docs, not the dev docs
503 * :ghpull:`5439`: Point to the stable SymPy docs, not the dev docs
504 * :ghpull:`5437`: Install jquery-ui images
504 * :ghpull:`5437`: Install jquery-ui images
505 * :ghpull:`5434`: fix check for empty cells in rst template
505 * :ghpull:`5434`: fix check for empty cells in rst template
506 * :ghpull:`5432`: update links in notebook help menu
506 * :ghpull:`5432`: update links in notebook help menu
507 * :ghpull:`5435`: Update whatsnew (notebook tour)
507 * :ghpull:`5435`: Update whatsnew (notebook tour)
508 * :ghpull:`5433`: Document extraction of octave and R magics
508 * :ghpull:`5433`: Document extraction of octave and R magics
509 * :ghpull:`5428`: Update COPYING.txt
509 * :ghpull:`5428`: Update COPYING.txt
510 * :ghpull:`5426`: Separate get_session_info between HistoryAccessor and HistoryManager
510 * :ghpull:`5426`: Separate get_session_info between HistoryAccessor and HistoryManager
511 * :ghpull:`5419`: move prompts from margin to main column on small screens
511 * :ghpull:`5419`: move prompts from margin to main column on small screens
512 * :ghpull:`5430`: Make sure `element` is correct in the context of displayed JS
512 * :ghpull:`5430`: Make sure `element` is correct in the context of displayed JS
513 * :ghpull:`5396`: prevent saving of partially loaded notebooks
513 * :ghpull:`5396`: prevent saving of partially loaded notebooks
514 * :ghpull:`5429`: Fix tooltip pager feature
514 * :ghpull:`5429`: Fix tooltip pager feature
515 * :ghpull:`5330`: Updates to shell reference doc
515 * :ghpull:`5330`: Updates to shell reference doc
516 * :ghpull:`5404`: Fix broken accordion widget
516 * :ghpull:`5404`: Fix broken accordion widget
517 * :ghpull:`5339`: Don't use fork to start the notebook in js tests
517 * :ghpull:`5339`: Don't use fork to start the notebook in js tests
518 * :ghpull:`5320`: Fix for Tooltip & completer click focus bug.
518 * :ghpull:`5320`: Fix for Tooltip & completer click focus bug.
519 * :ghpull:`5421`: Move configuration of Python test controllers into setup()
519 * :ghpull:`5421`: Move configuration of Python test controllers into setup()
520 * :ghpull:`5418`: fix typo in ssh launcher send_file
520 * :ghpull:`5418`: fix typo in ssh launcher send_file
521 * :ghpull:`5403`: remove alt-- shortcut
521 * :ghpull:`5403`: remove alt-- shortcut
522 * :ghpull:`5389`: better log message in deprecated files/ redirect
522 * :ghpull:`5389`: better log message in deprecated files/ redirect
523 * :ghpull:`5333`: Fix filenbmanager.list_dirs fails for Windows user profile directory
523 * :ghpull:`5333`: Fix filenbmanager.list_dirs fails for Windows user profile directory
524 * :ghpull:`5390`: finish PR #5333
524 * :ghpull:`5390`: finish PR #5333
525 * :ghpull:`5326`: Some gardening on iptest result reporting
525 * :ghpull:`5326`: Some gardening on iptest result reporting
526 * :ghpull:`5375`: remove unnecessary onload hack from mathjax macro
526 * :ghpull:`5375`: remove unnecessary onload hack from mathjax macro
527 * :ghpull:`5368`: Flexbox classes specificity fixes
527 * :ghpull:`5368`: Flexbox classes specificity fixes
528 * :ghpull:`5331`: fix raw_input CSS
528 * :ghpull:`5331`: fix raw_input CSS
529 * :ghpull:`5395`: urlencode images for rst files
529 * :ghpull:`5395`: urlencode images for rst files
530 * :ghpull:`5049`: update quickhelp on adding and removing shortcuts
530 * :ghpull:`5049`: update quickhelp on adding and removing shortcuts
531 * :ghpull:`5391`: Fix Gecko (Netscape) keyboard handling
531 * :ghpull:`5391`: Fix Gecko (Netscape) keyboard handling
532 * :ghpull:`5387`: Respect '\r' characters in nbconvert.
532 * :ghpull:`5387`: Respect '\r' characters in nbconvert.
533 * :ghpull:`5399`: Revert PR #5388
533 * :ghpull:`5399`: Revert PR #5388
534 * :ghpull:`5388`: Suppress output even when a comment follows ;. Fixes #4525.
534 * :ghpull:`5388`: Suppress output even when a comment follows ;. Fixes #4525.
535 * :ghpull:`5394`: nbconvert doc update
535 * :ghpull:`5394`: nbconvert doc update
536 * :ghpull:`5359`: do not install less sources
536 * :ghpull:`5359`: do not install less sources
537 * :ghpull:`5346`: give hint on where to find custom.js
537 * :ghpull:`5346`: give hint on where to find custom.js
538 * :ghpull:`5357`: catch exception in copystat
538 * :ghpull:`5357`: catch exception in copystat
539 * :ghpull:`5380`: Remove DefineShortVerb... line from latex base template
539 * :ghpull:`5380`: Remove DefineShortVerb... line from latex base template
540 * :ghpull:`5376`: elide long containers in pretty
540 * :ghpull:`5376`: elide long containers in pretty
541 * :ghpull:`5310`: remove raw cell placeholder on focus, closes #5238
541 * :ghpull:`5310`: remove raw cell placeholder on focus, closes #5238
542 * :ghpull:`5332`: semantic names for indicator icons
542 * :ghpull:`5332`: semantic names for indicator icons
543 * :ghpull:`5386`: Fix import of socketserver on Python 3
543 * :ghpull:`5386`: Fix import of socketserver on Python 3
544 * :ghpull:`5360`: remove some redundant font-family: monospace
544 * :ghpull:`5360`: remove some redundant font-family: monospace
545 * :ghpull:`5379`: don't instantiate Application just for default logger
545 * :ghpull:`5379`: don't instantiate Application just for default logger
546 * :ghpull:`5372`: Don't autoclose strings
546 * :ghpull:`5372`: Don't autoclose strings
547 * :ghpull:`5296`: unify keyboard shortcut and codemirror interaction
547 * :ghpull:`5296`: unify keyboard shortcut and codemirror interaction
548 * :ghpull:`5349`: Make Hub.registration_timeout configurable
548 * :ghpull:`5349`: Make Hub.registration_timeout configurable
549 * :ghpull:`5340`: install bootstrap-tour css
549 * :ghpull:`5340`: install bootstrap-tour css
550 * :ghpull:`5335`: Update docstring for deepreload module
550 * :ghpull:`5335`: Update docstring for deepreload module
551 * :ghpull:`5321`: Improve assignment regex to match more tuple unpacking syntax
551 * :ghpull:`5321`: Improve assignment regex to match more tuple unpacking syntax
552 * :ghpull:`5325`: add NotebookNotary to NotebookApp's class list
552 * :ghpull:`5325`: add NotebookNotary to NotebookApp's class list
553 * :ghpull:`5313`: avoid loading preprocessors twice
553 * :ghpull:`5313`: avoid loading preprocessors twice
554 * :ghpull:`5308`: fix HTML capitalization in Highlight2HTML
554 * :ghpull:`5308`: fix HTML capitalization in Highlight2HTML
555 * :ghpull:`5295`: OutputArea.append_type functions are not prototype methods
555 * :ghpull:`5295`: OutputArea.append_type functions are not prototype methods
556 * :ghpull:`5318`: Fix local import of select_figure_formats
556 * :ghpull:`5318`: Fix local import of select_figure_formats
557 * :ghpull:`5300`: Fix NameError: name '_rl' is not defined
557 * :ghpull:`5300`: Fix NameError: name '_rl' is not defined
558 * :ghpull:`5292`: focus next cell on shift+enter
558 * :ghpull:`5292`: focus next cell on shift+enter
559 * :ghpull:`5291`: debug occasional error in test_queue_status
559 * :ghpull:`5291`: debug occasional error in test_queue_status
560 * :ghpull:`5289`: Finishing up #5274 (widget paths fixes)
560 * :ghpull:`5289`: Finishing up #5274 (widget paths fixes)
561 * :ghpull:`5232`: Make nbconvert html full output like notebook's html.
561 * :ghpull:`5232`: Make nbconvert html full output like notebook's html.
562 * :ghpull:`5288`: Correct initial state of kernel status indicator
562 * :ghpull:`5288`: Correct initial state of kernel status indicator
563 * :ghpull:`5253`: display any output from this session in terminal console
563 * :ghpull:`5253`: display any output from this session in terminal console
564 * :ghpull:`4802`: Tour of the notebook UI (was UI elements inline with highlighting)
564 * :ghpull:`4802`: Tour of the notebook UI (was UI elements inline with highlighting)
565 * :ghpull:`5285`: Update signature presentation in pinfo classes
565 * :ghpull:`5285`: Update signature presentation in pinfo classes
566 * :ghpull:`5268`: Refactoring Notebook.command_mode
566 * :ghpull:`5268`: Refactoring Notebook.command_mode
567 * :ghpull:`5226`: Don't run PYTHONSTARTUP file if a file or code is passed
567 * :ghpull:`5226`: Don't run PYTHONSTARTUP file if a file or code is passed
568 * :ghpull:`5283`: Remove Widget.closed attribute
568 * :ghpull:`5283`: Remove Widget.closed attribute
569 * :ghpull:`5279`: nbconvert: Make sure node is atleast version 0.9.12
569 * :ghpull:`5279`: nbconvert: Make sure node is atleast version 0.9.12
570 * :ghpull:`5281`: fix a typo introduced by a rebased PR
570 * :ghpull:`5281`: fix a typo introduced by a rebased PR
571 * :ghpull:`5280`: append Firefox overflow-x fix
571 * :ghpull:`5280`: append Firefox overflow-x fix
572 * :ghpull:`5277`: check that PIL can save JPEG to BytesIO
572 * :ghpull:`5277`: check that PIL can save JPEG to BytesIO
573 * :ghpull:`5044`: Store timestamps for modules to autoreload
573 * :ghpull:`5044`: Store timestamps for modules to autoreload
574 * :ghpull:`5278`: Update whatsnew doc from pr files
574 * :ghpull:`5278`: Update whatsnew doc from pr files
575 * :ghpull:`5276`: Fix kernel restart in case connection file is deleted.
575 * :ghpull:`5276`: Fix kernel restart in case connection file is deleted.
576 * :ghpull:`5272`: allow highlighting language to be set from notebook metadata
576 * :ghpull:`5272`: allow highlighting language to be set from notebook metadata
577 * :ghpull:`5158`: log refusal to serve hidden directories
577 * :ghpull:`5158`: log refusal to serve hidden directories
578 * :ghpull:`5188`: New events system
578 * :ghpull:`5188`: New events system
579 * :ghpull:`5265`: Missing class def for TimeoutError
579 * :ghpull:`5265`: Missing class def for TimeoutError
580 * :ghpull:`5267`: normalize unicode in notebook API tests
580 * :ghpull:`5267`: normalize unicode in notebook API tests
581 * :ghpull:`5076`: Refactor keyboard handling
581 * :ghpull:`5076`: Refactor keyboard handling
582 * :ghpull:`5241`: Add some tests for utils
582 * :ghpull:`5241`: Add some tests for utils
583 * :ghpull:`5261`: Don't allow edit mode up arrow to continue past index == 0
583 * :ghpull:`5261`: Don't allow edit mode up arrow to continue past index == 0
584 * :ghpull:`5223`: use on-load event to trigger resizable images
584 * :ghpull:`5223`: use on-load event to trigger resizable images
585 * :ghpull:`5252`: make one strptime call at import of jsonutil
585 * :ghpull:`5252`: make one strptime call at import of jsonutil
586 * :ghpull:`5153`: Dashboard sorting
586 * :ghpull:`5153`: Dashboard sorting
587 * :ghpull:`5169`: Allow custom header
587 * :ghpull:`5169`: Allow custom header
588 * :ghpull:`5242`: clear _reply_content cache before using it
588 * :ghpull:`5242`: clear _reply_content cache before using it
589 * :ghpull:`5194`: require latex titles to be ascii
589 * :ghpull:`5194`: require latex titles to be ascii
590 * :ghpull:`5244`: try to avoid EADDRINUSE errors on travis
590 * :ghpull:`5244`: try to avoid EADDRINUSE errors on travis
591 * :ghpull:`5245`: support extracted output in HTML template
591 * :ghpull:`5245`: support extracted output in HTML template
592 * :ghpull:`5209`: make input_area css generic to cells
592 * :ghpull:`5209`: make input_area css generic to cells
593 * :ghpull:`5246`: less %pylab, more cowbell!
593 * :ghpull:`5246`: less %pylab, more cowbell!
594 * :ghpull:`4895`: Improvements to %run completions
594 * :ghpull:`4895`: Improvements to %run completions
595 * :ghpull:`5243`: Add Javscript to base display priority list.
595 * :ghpull:`5243`: Add Javscript to base display priority list.
596 * :ghpull:`5175`: Audit .html() calls take #2
596 * :ghpull:`5175`: Audit .html() calls take #2
597 * :ghpull:`5146`: Dual mode bug fixes.
597 * :ghpull:`5146`: Dual mode bug fixes.
598 * :ghpull:`5207`: Children fire event
598 * :ghpull:`5207`: Children fire event
599 * :ghpull:`5215`: Dashboard "Running" Tab
599 * :ghpull:`5215`: Dashboard "Running" Tab
600 * :ghpull:`5240`: Remove unused IPython.nbconvert.utils.console module
600 * :ghpull:`5240`: Remove unused IPython.nbconvert.utils.console module
601 * :ghpull:`5239`: Fix exclusion of tests directories from coverage reports
601 * :ghpull:`5239`: Fix exclusion of tests directories from coverage reports
602 * :ghpull:`5203`: capture some logging/warning output in some tests
602 * :ghpull:`5203`: capture some logging/warning output in some tests
603 * :ghpull:`5216`: fixup positional arg handling in notebook app
603 * :ghpull:`5216`: fixup positional arg handling in notebook app
604 * :ghpull:`5229`: get _ipython_display_ method safely
604 * :ghpull:`5229`: get _ipython_display_ method safely
605 * :ghpull:`5234`: DOC : modified docs is HasTraits.traits and HasTraits.class_traits
605 * :ghpull:`5234`: DOC : modified docs is HasTraits.traits and HasTraits.class_traits
606 * :ghpull:`5221`: Change widget children List to Tuple.
606 * :ghpull:`5221`: Change widget children List to Tuple.
607 * :ghpull:`5231`: don't forget base_url when updating address bar in rename
607 * :ghpull:`5231`: don't forget base_url when updating address bar in rename
608 * :ghpull:`5173`: Moved widget files into static/widgets/*
608 * :ghpull:`5173`: Moved widget files into static/widgets/*
609 * :ghpull:`5222`: Unset PYTHONWARNINGS envvar before running subprocess tests.
609 * :ghpull:`5222`: Unset PYTHONWARNINGS envvar before running subprocess tests.
610 * :ghpull:`5172`: Prevent page breaks when printing notebooks via print-view.
610 * :ghpull:`5172`: Prevent page breaks when printing notebooks via print-view.
611 * :ghpull:`4985`: Add automatic Closebrackets function to Codemirror.
611 * :ghpull:`4985`: Add automatic Closebrackets function to Codemirror.
612 * :ghpull:`5220`: Make traitlets notify check more robust against classes redefining equality and bool
612 * :ghpull:`5220`: Make traitlets notify check more robust against classes redefining equality and bool
613 * :ghpull:`5197`: If there is an error comparing traitlet values when setting a trait, default to go ahead and notify of the new value.
613 * :ghpull:`5197`: If there is an error comparing traitlet values when setting a trait, default to go ahead and notify of the new value.
614 * :ghpull:`5210`: fix pyreadline import in rlineimpl
614 * :ghpull:`5210`: fix pyreadline import in rlineimpl
615 * :ghpull:`5212`: Wrap nbconvert Markdown/Heading cells in live divs
615 * :ghpull:`5212`: Wrap nbconvert Markdown/Heading cells in live divs
616 * :ghpull:`5200`: Allow to pass option to jinja env
616 * :ghpull:`5200`: Allow to pass option to jinja env
617 * :ghpull:`5202`: handle nodejs executable on debian
617 * :ghpull:`5202`: handle nodejs executable on debian
618 * :ghpull:`5112`: band-aid for completion
618 * :ghpull:`5112`: band-aid for completion
619 * :ghpull:`5187`: handle missing output metadata in nbconvert
619 * :ghpull:`5187`: handle missing output metadata in nbconvert
620 * :ghpull:`5181`: use gnureadline on OS X
620 * :ghpull:`5181`: use gnureadline on OS X
621 * :ghpull:`5136`: set default value from signature defaults in interact
621 * :ghpull:`5136`: set default value from signature defaults in interact
622 * :ghpull:`5132`: remove application/pdf->pdf transform in javascript
622 * :ghpull:`5132`: remove application/pdf->pdf transform in javascript
623 * :ghpull:`5116`: reorganize who knows what about paths
623 * :ghpull:`5116`: reorganize who knows what about paths
624 * :ghpull:`5165`: Don't introspect __call__ for simple callables
624 * :ghpull:`5165`: Don't introspect __call__ for simple callables
625 * :ghpull:`5170`: Added msg_throttle sync=True widget traitlet
625 * :ghpull:`5170`: Added msg_throttle sync=True widget traitlet
626 * :ghpull:`5191`: Translate markdown link to rst
626 * :ghpull:`5191`: Translate markdown link to rst
627 * :ghpull:`5037`: FF Fix: alignment and scale of text widget
627 * :ghpull:`5037`: FF Fix: alignment and scale of text widget
628 * :ghpull:`5179`: remove websocket url
628 * :ghpull:`5179`: remove websocket url
629 * :ghpull:`5110`: add InlineBackend.print_figure_kwargs
629 * :ghpull:`5110`: add InlineBackend.print_figure_kwargs
630 * :ghpull:`5147`: Some template URL changes
630 * :ghpull:`5147`: Some template URL changes
631 * :ghpull:`5100`: remove base_kernel_url
631 * :ghpull:`5100`: remove base_kernel_url
632 * :ghpull:`5163`: Simplify implementation of TemporaryWorkingDirectory.
632 * :ghpull:`5163`: Simplify implementation of TemporaryWorkingDirectory.
633 * :ghpull:`5166`: remove mktemp usage
633 * :ghpull:`5166`: remove mktemp usage
634 * :ghpull:`5133`: don't use combine option on ucs package
634 * :ghpull:`5133`: don't use combine option on ucs package
635 * :ghpull:`5089`: Remove legacy azure nbmanager
635 * :ghpull:`5089`: Remove legacy azure nbmanager
636 * :ghpull:`5159`: remove append_json reference
636 * :ghpull:`5159`: remove append_json reference
637 * :ghpull:`5095`: handle image size metadata in nbconvert html
637 * :ghpull:`5095`: handle image size metadata in nbconvert html
638 * :ghpull:`5156`: fix IPython typo, closes #5155
638 * :ghpull:`5156`: fix IPython typo, closes #5155
639 * :ghpull:`5150`: fix a link that was broken
639 * :ghpull:`5150`: fix a link that was broken
640 * :ghpull:`5114`: use non-breaking space for button with no description
640 * :ghpull:`5114`: use non-breaking space for button with no description
641 * :ghpull:`4778`: add APIs for installing notebook extensions
641 * :ghpull:`4778`: add APIs for installing notebook extensions
642 * :ghpull:`5125`: Fix the display of functions with keyword-only arguments on Python 3.
642 * :ghpull:`5125`: Fix the display of functions with keyword-only arguments on Python 3.
643 * :ghpull:`5097`: minor notebook logging changes
643 * :ghpull:`5097`: minor notebook logging changes
644 * :ghpull:`5047`: only validate package_data when it might be used
644 * :ghpull:`5047`: only validate package_data when it might be used
645 * :ghpull:`5121`: fix remove event in KeyboardManager.register_events
645 * :ghpull:`5121`: fix remove event in KeyboardManager.register_events
646 * :ghpull:`5119`: Removed 'list' view from Variable Inspector example
646 * :ghpull:`5119`: Removed 'list' view from Variable Inspector example
647 * :ghpull:`4925`: Notebook manager api fixes
647 * :ghpull:`4925`: Notebook manager api fixes
648 * :ghpull:`4996`: require print_method to be a bound method
648 * :ghpull:`4996`: require print_method to be a bound method
649 * :ghpull:`5108`: require specifying the version for gh-pages
649 * :ghpull:`5108`: require specifying the version for gh-pages
650 * :ghpull:`5111`: Minor typo in docstring of IPython.parallel DirectView
650 * :ghpull:`5111`: Minor typo in docstring of IPython.parallel DirectView
651 * :ghpull:`5098`: mostly debugging changes for IPython.parallel
651 * :ghpull:`5098`: mostly debugging changes for IPython.parallel
652 * :ghpull:`5087`: trust cells with no output
652 * :ghpull:`5087`: trust cells with no output
653 * :ghpull:`5059`: Fix incorrect `Patch` logic in widget code
653 * :ghpull:`5059`: Fix incorrect `Patch` logic in widget code
654 * :ghpull:`5075`: More flexible box model fixes
654 * :ghpull:`5075`: More flexible box model fixes
655 * :ghpull:`5091`: Provide logging messages in ipcluster log when engine or controllers fail to start
655 * :ghpull:`5091`: Provide logging messages in ipcluster log when engine or controllers fail to start
656 * :ghpull:`5090`: Print a warning when iptest is run from the IPython source directory
656 * :ghpull:`5090`: Print a warning when iptest is run from the IPython source directory
657 * :ghpull:`5077`: flush replies when entering an eventloop
657 * :ghpull:`5077`: flush replies when entering an eventloop
658 * :ghpull:`5055`: Minimal changes to import IPython from IronPython
658 * :ghpull:`5055`: Minimal changes to import IPython from IronPython
659 * :ghpull:`5078`: Updating JS tests README.md
659 * :ghpull:`5078`: Updating JS tests README.md
660 * :ghpull:`5083`: don't create js test directories unless they are being used
660 * :ghpull:`5083`: don't create js test directories unless they are being used
661 * :ghpull:`5062`: adjust some events in nb_roundtrip
661 * :ghpull:`5062`: adjust some events in nb_roundtrip
662 * :ghpull:`5043`: various unicode / url fixes
662 * :ghpull:`5043`: various unicode / url fixes
663 * :ghpull:`5066`: remove (almost) all mentions of pylab from our examples
663 * :ghpull:`5066`: remove (almost) all mentions of pylab from our examples
664 * :ghpull:`4977`: ensure scp destination directories exist (with mkdir -p)
664 * :ghpull:`4977`: ensure scp destination directories exist (with mkdir -p)
665 * :ghpull:`5053`: Move&rename JS tests
665 * :ghpull:`5053`: Move&rename JS tests
666 * :ghpull:`5067`: show traceback in widget handlers
666 * :ghpull:`5067`: show traceback in widget handlers
667 * :ghpull:`4920`: Adding PDFFormatter and kernel side handling of PDF display data
667 * :ghpull:`4920`: Adding PDFFormatter and kernel side handling of PDF display data
668 * :ghpull:`5048`: Add edit/command mode indicator
668 * :ghpull:`5048`: Add edit/command mode indicator
669 * :ghpull:`5061`: make execute button in menu bar match shift-enter
669 * :ghpull:`5061`: make execute button in menu bar match shift-enter
670 * :ghpull:`5052`: Add q to toggle the pager.
670 * :ghpull:`5052`: Add q to toggle the pager.
671 * :ghpull:`5070`: fix flex: auto
671 * :ghpull:`5070`: fix flex: auto
672 * :ghpull:`5065`: Add example of using annotations in interact
672 * :ghpull:`5065`: Add example of using annotations in interact
673 * :ghpull:`5063`: another pass on Interact example notebooks
673 * :ghpull:`5063`: another pass on Interact example notebooks
674 * :ghpull:`5051`: FF Fix: code cell missing hscroll (2)
674 * :ghpull:`5051`: FF Fix: code cell missing hscroll (2)
675 * :ghpull:`4960`: Interact/Interactive for widget
675 * :ghpull:`4960`: Interact/Interactive for widget
676 * :ghpull:`5045`: Clear timeout in multi-press keyboard shortcuts.
676 * :ghpull:`5045`: Clear timeout in multi-press keyboard shortcuts.
677 * :ghpull:`5060`: Change 'bind' to 'link'
677 * :ghpull:`5060`: Change 'bind' to 'link'
678 * :ghpull:`5039`: Expose kernel_info method on inprocess kernel client
678 * :ghpull:`5039`: Expose kernel_info method on inprocess kernel client
679 * :ghpull:`5058`: Fix iopubwatcher.py example script.
679 * :ghpull:`5058`: Fix iopubwatcher.py example script.
680 * :ghpull:`5035`: FF Fix: code cell missing hscroll
680 * :ghpull:`5035`: FF Fix: code cell missing hscroll
681 * :ghpull:`5040`: Polishing some docs
681 * :ghpull:`5040`: Polishing some docs
682 * :ghpull:`5001`: Add directory navigation to dashboard
682 * :ghpull:`5001`: Add directory navigation to dashboard
683 * :ghpull:`5042`: Remove duplicated Channel ABC classes.
683 * :ghpull:`5042`: Remove duplicated Channel ABC classes.
684 * :ghpull:`5036`: FF Fix: ext link icon same line as link text in help menu
684 * :ghpull:`5036`: FF Fix: ext link icon same line as link text in help menu
685 * :ghpull:`4975`: setup.py changes for 2.0
685 * :ghpull:`4975`: setup.py changes for 2.0
686 * :ghpull:`4774`: emit event on appended element on dom
686 * :ghpull:`4774`: emit event on appended element on dom
687 * :ghpull:`5023`: Widgets- add ability to pack and unpack arrays on JS side.
687 * :ghpull:`5023`: Widgets- add ability to pack and unpack arrays on JS side.
688 * :ghpull:`5003`: Fix pretty reprs of super() objects
688 * :ghpull:`5003`: Fix pretty reprs of super() objects
689 * :ghpull:`4974`: make paste focus the pasted cell
689 * :ghpull:`4974`: make paste focus the pasted cell
690 * :ghpull:`5012`: Make `SelectionWidget.values` a dict
690 * :ghpull:`5012`: Make `SelectionWidget.values` a dict
691 * :ghpull:`5018`: Prevent 'iptest IPython' from trying to run.
691 * :ghpull:`5018`: Prevent 'iptest IPython' from trying to run.
692 * :ghpull:`5025`: citation2latex filter (using HTMLParser)
692 * :ghpull:`5025`: citation2latex filter (using HTMLParser)
693 * :ghpull:`5027`: pin lessc to 1.4
693 * :ghpull:`5027`: pin lessc to 1.4
694 * :ghpull:`4952`: Widget test inconsistencies
694 * :ghpull:`4952`: Widget test inconsistencies
695 * :ghpull:`5014`: Fix command mode & popup view bug
695 * :ghpull:`5014`: Fix command mode & popup view bug
696 * :ghpull:`4842`: more subtle kernel indicator
696 * :ghpull:`4842`: more subtle kernel indicator
697 * :ghpull:`5017`: Add notebook examples link to help menu.
697 * :ghpull:`5017`: Add notebook examples link to help menu.
698 * :ghpull:`5015`: don't write cell.trusted to disk
698 * :ghpull:`5015`: don't write cell.trusted to disk
699 * :ghpull:`5007`: Update whatsnew doc from PR files
699 * :ghpull:`5007`: Update whatsnew doc from PR files
700 * :ghpull:`5010`: Fixes for widget alignment in FF
700 * :ghpull:`5010`: Fixes for widget alignment in FF
701 * :ghpull:`4901`: Add a convenience class to sync traitlet attributes
701 * :ghpull:`4901`: Add a convenience class to sync traitlet attributes
702 * :ghpull:`5008`: updated explanation of 'pyin' messages
702 * :ghpull:`5008`: updated explanation of 'pyin' messages
703 * :ghpull:`5004`: Fix widget vslider spacing
703 * :ghpull:`5004`: Fix widget vslider spacing
704 * :ghpull:`4933`: Small Widget inconsistency fixes
704 * :ghpull:`4933`: Small Widget inconsistency fixes
705 * :ghpull:`4979`: add versioning notes to small message spec changes
705 * :ghpull:`4979`: add versioning notes to small message spec changes
706 * :ghpull:`4893`: add font-awesome 3.2.1
706 * :ghpull:`4893`: add font-awesome 3.2.1
707 * :ghpull:`4982`: Live readout for slider widgets
707 * :ghpull:`4982`: Live readout for slider widgets
708 * :ghpull:`4813`: make help menu a template
708 * :ghpull:`4813`: make help menu a template
709 * :ghpull:`4939`: Embed qtconsole docs (continued)
709 * :ghpull:`4939`: Embed qtconsole docs (continued)
710 * :ghpull:`4964`: remove shift-= merge keyboard shortcut
710 * :ghpull:`4964`: remove shift-= merge keyboard shortcut
711 * :ghpull:`4504`: Allow input transformers to raise SyntaxError
711 * :ghpull:`4504`: Allow input transformers to raise SyntaxError
712 * :ghpull:`4929`: Fixing various modal/focus related bugs
712 * :ghpull:`4929`: Fixing various modal/focus related bugs
713 * :ghpull:`4971`: Fixing issues with js tests
713 * :ghpull:`4971`: Fixing issues with js tests
714 * :ghpull:`4972`: Work around problem in doctest discovery in Python 3.4 with PyQt
714 * :ghpull:`4972`: Work around problem in doctest discovery in Python 3.4 with PyQt
715 * :ghpull:`4937`: pickle arrays with dtype=object
715 * :ghpull:`4937`: pickle arrays with dtype=object
716 * :ghpull:`4934`: `ipython profile create` respects `--ipython-dir`
716 * :ghpull:`4934`: `ipython profile create` respects `--ipython-dir`
717 * :ghpull:`4954`: generate unicode filename
717 * :ghpull:`4954`: generate unicode filename
718 * :ghpull:`4845`: Add Origin Checking.
718 * :ghpull:`4845`: Add Origin Checking.
719 * :ghpull:`4916`: Fine tuning the behavior of the modal UI
719 * :ghpull:`4916`: Fine tuning the behavior of the modal UI
720 * :ghpull:`4966`: Ignore sys.argv for NotebookNotary in tests
720 * :ghpull:`4966`: Ignore sys.argv for NotebookNotary in tests
721 * :ghpull:`4967`: Fix typo in warning about web socket being closed
721 * :ghpull:`4967`: Fix typo in warning about web socket being closed
722 * :ghpull:`4965`: Remove mention of iplogger from setup.py
722 * :ghpull:`4965`: Remove mention of iplogger from setup.py
723 * :ghpull:`4962`: Fixed typos in quick-help text
723 * :ghpull:`4962`: Fixed typos in quick-help text
724 * :ghpull:`4953`: add utils.wait_for_idle in js tests
724 * :ghpull:`4953`: add utils.wait_for_idle in js tests
725 * :ghpull:`4870`: ipython_directive, report except/warn in block and add :okexcept: :okwarning: options to suppress
725 * :ghpull:`4870`: ipython_directive, report except/warn in block and add :okexcept: :okwarning: options to suppress
726 * :ghpull:`4662`: Menu cleanup
726 * :ghpull:`4662`: Menu cleanup
727 * :ghpull:`4824`: sign notebooks
727 * :ghpull:`4824`: sign notebooks
728 * :ghpull:`4943`: Docs shotgun 4
728 * :ghpull:`4943`: Docs shotgun 4
729 * :ghpull:`4848`: avoid import of nearby temporary with %edit
729 * :ghpull:`4848`: avoid import of nearby temporary with %edit
730 * :ghpull:`4950`: Two fixes for file upload related bugs
730 * :ghpull:`4950`: Two fixes for file upload related bugs
731 * :ghpull:`4927`: there shouldn't be a 'files/' prefix in FileLink[s]
731 * :ghpull:`4927`: there shouldn't be a 'files/' prefix in FileLink[s]
732 * :ghpull:`4928`: use importlib.machinery when available
732 * :ghpull:`4928`: use importlib.machinery when available
733 * :ghpull:`4949`: Remove the docscrape modules, which are part of numpydoc
733 * :ghpull:`4949`: Remove the docscrape modules, which are part of numpydoc
734 * :ghpull:`4849`: Various unicode fixes (mostly on Windows)
734 * :ghpull:`4849`: Various unicode fixes (mostly on Windows)
735 * :ghpull:`4932`: always point py3compat.input to builtin_mod.input
735 * :ghpull:`4932`: always point py3compat.input to builtin_mod.input
736 * :ghpull:`4807`: Correct handling of ansi colour codes when nbconverting to latex
736 * :ghpull:`4807`: Correct handling of ansi colour codes when nbconverting to latex
737 * :ghpull:`4922`: Python nbconvert output shouldn't have output
737 * :ghpull:`4922`: Python nbconvert output shouldn't have output
738 * :ghpull:`4912`: Skip some Windows io failures
738 * :ghpull:`4912`: Skip some Windows io failures
739 * :ghpull:`4919`: flush output before showing tracebacks
739 * :ghpull:`4919`: flush output before showing tracebacks
740 * :ghpull:`4915`: ZMQCompleter inherits from IPCompleter
740 * :ghpull:`4915`: ZMQCompleter inherits from IPCompleter
741 * :ghpull:`4890`: better cleanup channel FDs
741 * :ghpull:`4890`: better cleanup channel FDs
742 * :ghpull:`4880`: set profile name from profile_dir
742 * :ghpull:`4880`: set profile name from profile_dir
743 * :ghpull:`4853`: fix setting image height/width from metadata
743 * :ghpull:`4853`: fix setting image height/width from metadata
744 * :ghpull:`4786`: Reduce spacing of heading cells
744 * :ghpull:`4786`: Reduce spacing of heading cells
745 * :ghpull:`4680`: Minimal pandoc version warning
745 * :ghpull:`4680`: Minimal pandoc version warning
746 * :ghpull:`4908`: detect builtin docstrings in oinspect
746 * :ghpull:`4908`: detect builtin docstrings in oinspect
747 * :ghpull:`4911`: Don't use `python -m package` on Windows Python 2
747 * :ghpull:`4911`: Don't use `python -m package` on Windows Python 2
748 * :ghpull:`4909`: sort dictionary keys before comparison, ordering is not guaranteed
748 * :ghpull:`4909`: sort dictionary keys before comparison, ordering is not guaranteed
749 * :ghpull:`4374`: IPEP 23: Backbone.js Widgets
749 * :ghpull:`4374`: IPEP 23: Backbone.js Widgets
750 * :ghpull:`4903`: use https for all embeds
750 * :ghpull:`4903`: use https for all embeds
751 * :ghpull:`4894`: Shortcut changes
751 * :ghpull:`4894`: Shortcut changes
752 * :ghpull:`4897`: More detailed documentation about kernel_cmd
752 * :ghpull:`4897`: More detailed documentation about kernel_cmd
753 * :ghpull:`4891`: Squash a few Sphinx warnings from nbconvert.utils.lexers docstrings
753 * :ghpull:`4891`: Squash a few Sphinx warnings from nbconvert.utils.lexers docstrings
754 * :ghpull:`4679`: JPG compression for inline pylab
754 * :ghpull:`4679`: JPG compression for inline pylab
755 * :ghpull:`4708`: Fix indent and center
755 * :ghpull:`4708`: Fix indent and center
756 * :ghpull:`4789`: fix IPython.embed
756 * :ghpull:`4789`: fix IPython.embed
757 * :ghpull:`4655`: prefer marked to pandoc for markdown2html
757 * :ghpull:`4655`: prefer marked to pandoc for markdown2html
758 * :ghpull:`4876`: don't show tooltip if object is not found
758 * :ghpull:`4876`: don't show tooltip if object is not found
759 * :ghpull:`4873`: use 'combine' option to ucs package
759 * :ghpull:`4873`: use 'combine' option to ucs package
760 * :ghpull:`4732`: Accents in notebook names and in command-line (nbconvert)
760 * :ghpull:`4732`: Accents in notebook names and in command-line (nbconvert)
761 * :ghpull:`4867`: Update URL for Lawrence Hall of Science webcam image
761 * :ghpull:`4867`: Update URL for Lawrence Hall of Science webcam image
762 * :ghpull:`4868`: Static path fixes
762 * :ghpull:`4868`: Static path fixes
763 * :ghpull:`4858`: fix tb_offset when running a file
763 * :ghpull:`4858`: fix tb_offset when running a file
764 * :ghpull:`4826`: some $.html( -> $.text(
764 * :ghpull:`4826`: some $.html( -> $.text(
765 * :ghpull:`4847`: add js kernel_info request
765 * :ghpull:`4847`: add js kernel_info request
766 * :ghpull:`4832`: allow NotImplementedError in formatters
766 * :ghpull:`4832`: allow NotImplementedError in formatters
767 * :ghpull:`4803`: BUG: fix cython magic support in ipython_directive
767 * :ghpull:`4803`: BUG: fix cython magic support in ipython_directive
768 * :ghpull:`4865`: `build` listed twice in .gitignore. Removing one.
768 * :ghpull:`4865`: `build` listed twice in .gitignore. Removing one.
769 * :ghpull:`4851`: fix tooltip token regex for single-character names
769 * :ghpull:`4851`: fix tooltip token regex for single-character names
770 * :ghpull:`4846`: Remove some leftover traces of irunner
770 * :ghpull:`4846`: Remove some leftover traces of irunner
771 * :ghpull:`4820`: fix regex for cleaning old logs with ipcluster
771 * :ghpull:`4820`: fix regex for cleaning old logs with ipcluster
772 * :ghpull:`4844`: adjustments to notebook app logging
772 * :ghpull:`4844`: adjustments to notebook app logging
773 * :ghpull:`4840`: Error in Session.send_raw()
773 * :ghpull:`4840`: Error in Session.send_raw()
774 * :ghpull:`4819`: update CodeMirror to 3.21
774 * :ghpull:`4819`: update CodeMirror to 3.21
775 * :ghpull:`4823`: Minor fixes for typos/inconsistencies in parallel docs
775 * :ghpull:`4823`: Minor fixes for typos/inconsistencies in parallel docs
776 * :ghpull:`4811`: document code mirror tab and shift-tab
776 * :ghpull:`4811`: document code mirror tab and shift-tab
777 * :ghpull:`4795`: merge reveal templates
777 * :ghpull:`4795`: merge reveal templates
778 * :ghpull:`4796`: update components
778 * :ghpull:`4796`: update components
779 * :ghpull:`4806`: Correct order of packages for unicode in nbconvert to LaTeX
779 * :ghpull:`4806`: Correct order of packages for unicode in nbconvert to LaTeX
780 * :ghpull:`4800`: Qt frontend: Handle 'aborted' prompt replies.
780 * :ghpull:`4800`: Qt frontend: Handle 'aborted' prompt replies.
781 * :ghpull:`4794`: Compatibility fix for Python3 (Issue #4783 )
781 * :ghpull:`4794`: Compatibility fix for Python3 (Issue #4783 )
782 * :ghpull:`4799`: minor js test fix
782 * :ghpull:`4799`: minor js test fix
783 * :ghpull:`4788`: warn when notebook is started in pylab mode
783 * :ghpull:`4788`: warn when notebook is started in pylab mode
784 * :ghpull:`4772`: Notebook server info files
784 * :ghpull:`4772`: Notebook server info files
785 * :ghpull:`4797`: be conservative about kernel_info implementation
785 * :ghpull:`4797`: be conservative about kernel_info implementation
786 * :ghpull:`4787`: non-python kernels run python code with qtconsole
786 * :ghpull:`4787`: non-python kernels run python code with qtconsole
787 * :ghpull:`4565`: various display type validations
787 * :ghpull:`4565`: various display type validations
788 * :ghpull:`4703`: Math macro in jinja templates.
788 * :ghpull:`4703`: Math macro in jinja templates.
789 * :ghpull:`4781`: Fix "Source" text for the "Other Syntax" section of the "Typesetting Math" notebook
789 * :ghpull:`4781`: Fix "Source" text for the "Other Syntax" section of the "Typesetting Math" notebook
790 * :ghpull:`4776`: Manually document py3compat module.
790 * :ghpull:`4776`: Manually document py3compat module.
791 * :ghpull:`4533`: propagate display metadata to all mimetypes
791 * :ghpull:`4533`: propagate display metadata to all mimetypes
792 * :ghpull:`4785`: Replacing a for-in loop by an index loop on an array
792 * :ghpull:`4785`: Replacing a for-in loop by an index loop on an array
793 * :ghpull:`4780`: Updating CSS for UI example.
793 * :ghpull:`4780`: Updating CSS for UI example.
794 * :ghpull:`3605`: Modal UI
794 * :ghpull:`3605`: Modal UI
795 * :ghpull:`4758`: Python 3.4 fixes
795 * :ghpull:`4758`: Python 3.4 fixes
796 * :ghpull:`4735`: add some HTML error pages
796 * :ghpull:`4735`: add some HTML error pages
797 * :ghpull:`4775`: Update whatsnew doc from PR files
797 * :ghpull:`4775`: Update whatsnew doc from PR files
798 * :ghpull:`4760`: Make examples and docs more Python 3 aware
798 * :ghpull:`4760`: Make examples and docs more Python 3 aware
799 * :ghpull:`4773`: Don't wait forever for notebook server to launch/die for tests
799 * :ghpull:`4773`: Don't wait forever for notebook server to launch/die for tests
800 * :ghpull:`4768`: Qt console: Fix _prompt_pos accounting on timer flush output.
800 * :ghpull:`4768`: Qt console: Fix _prompt_pos accounting on timer flush output.
801 * :ghpull:`4727`: Remove Nbconvert template loading magic
801 * :ghpull:`4727`: Remove Nbconvert template loading magic
802 * :ghpull:`4763`: Set numpydoc options to produce fewer Sphinx warnings.
802 * :ghpull:`4763`: Set numpydoc options to produce fewer Sphinx warnings.
803 * :ghpull:`4770`: always define aliases, even if empty
803 * :ghpull:`4770`: always define aliases, even if empty
804 * :ghpull:`4766`: add `python -m` entry points for everything
804 * :ghpull:`4766`: add `python -m` entry points for everything
805 * :ghpull:`4767`: remove manpages for irunner, iplogger
805 * :ghpull:`4767`: remove manpages for irunner, iplogger
806 * :ghpull:`4751`: Added --post-serve explanation into the nbconvert docs.
806 * :ghpull:`4751`: Added --post-serve explanation into the nbconvert docs.
807 * :ghpull:`4762`: whitelist alphanumeric characters for cookie_name
807 * :ghpull:`4762`: whitelist alphanumeric characters for cookie_name
808 * :ghpull:`4625`: Deprecate %profile magic
808 * :ghpull:`4625`: Deprecate %profile magic
809 * :ghpull:`4745`: warn on failed formatter calls
809 * :ghpull:`4745`: warn on failed formatter calls
810 * :ghpull:`4746`: remove redundant cls alias on Windows
810 * :ghpull:`4746`: remove redundant cls alias on Windows
811 * :ghpull:`4749`: Fix bug in determination of public ips.
811 * :ghpull:`4749`: Fix bug in determination of public ips.
812 * :ghpull:`4715`: restore use of tornado static_url in templates
812 * :ghpull:`4715`: restore use of tornado static_url in templates
813 * :ghpull:`4748`: fix race condition in profiledir creation.
813 * :ghpull:`4748`: fix race condition in profiledir creation.
814 * :ghpull:`4720`: never use ssh multiplexer in tunnels
814 * :ghpull:`4720`: never use ssh multiplexer in tunnels
815 * :ghpull:`4658`: Bug fix for #4643: Regex object needs to be reset between calls in toolt...
815 * :ghpull:`4658`: Bug fix for #4643: Regex object needs to be reset between calls in toolt...
816 * :ghpull:`4561`: Add Formatter.pop(type)
816 * :ghpull:`4561`: Add Formatter.pop(type)
817 * :ghpull:`4712`: Docs shotgun 3
817 * :ghpull:`4712`: Docs shotgun 3
818 * :ghpull:`4713`: Fix saving kernel history in Python 2
818 * :ghpull:`4713`: Fix saving kernel history in Python 2
819 * :ghpull:`4744`: don't use lazily-evaluated rc.ids in wait_for_idle
819 * :ghpull:`4744`: don't use lazily-evaluated rc.ids in wait_for_idle
820 * :ghpull:`4740`: %env can't set variables
820 * :ghpull:`4740`: %env can't set variables
821 * :ghpull:`4737`: check every link when detecting virutalenv
821 * :ghpull:`4737`: check every link when detecting virutalenv
822 * :ghpull:`4738`: don't inject help into user_ns
822 * :ghpull:`4738`: don't inject help into user_ns
823 * :ghpull:`4739`: skip html nbconvert tests when their dependencies are missing
823 * :ghpull:`4739`: skip html nbconvert tests when their dependencies are missing
824 * :ghpull:`4730`: Fix stripping continuation prompts when copying from Qt console
824 * :ghpull:`4730`: Fix stripping continuation prompts when copying from Qt console
825 * :ghpull:`4725`: Doc fixes
825 * :ghpull:`4725`: Doc fixes
826 * :ghpull:`4656`: Nbconvert HTTP service
826 * :ghpull:`4656`: Nbconvert HTTP service
827 * :ghpull:`4710`: make @interactive decorator friendlier with dill
827 * :ghpull:`4710`: make @interactive decorator friendlier with dill
828 * :ghpull:`4722`: allow purging local results as long as they are not outstanding
828 * :ghpull:`4722`: allow purging local results as long as they are not outstanding
829 * :ghpull:`4549`: Updated IPython console lexers.
829 * :ghpull:`4549`: Updated IPython console lexers.
830 * :ghpull:`4570`: Update IPython directive
830 * :ghpull:`4570`: Update IPython directive
831 * :ghpull:`4719`: Fix comment typo in prefilter.py
831 * :ghpull:`4719`: Fix comment typo in prefilter.py
832 * :ghpull:`4575`: make sure to encode URL components for API requests
832 * :ghpull:`4575`: make sure to encode URL components for API requests
833 * :ghpull:`4718`: Fixed typo in displaypub
833 * :ghpull:`4718`: Fixed typo in displaypub
834 * :ghpull:`4716`: Remove input_prefilter hook
834 * :ghpull:`4716`: Remove input_prefilter hook
835 * :ghpull:`4691`: survive failure to bind to localhost in zmq.iostream
835 * :ghpull:`4691`: survive failure to bind to localhost in zmq.iostream
836 * :ghpull:`4696`: don't do anything if add_anchor fails
836 * :ghpull:`4696`: don't do anything if add_anchor fails
837 * :ghpull:`4711`: some typos in the docs
837 * :ghpull:`4711`: some typos in the docs
838 * :ghpull:`4700`: use if main block in entry points
838 * :ghpull:`4700`: use if main block in entry points
839 * :ghpull:`4692`: setup.py symlink improvements
839 * :ghpull:`4692`: setup.py symlink improvements
840 * :ghpull:`4265`: JSON configuration file
840 * :ghpull:`4265`: JSON configuration file
841 * :ghpull:`4505`: Nbconvert latex markdown images2
841 * :ghpull:`4505`: Nbconvert latex markdown images2
842 * :ghpull:`4608`: transparent background match ... all colors
842 * :ghpull:`4608`: transparent background match ... all colors
843 * :ghpull:`4678`: allow ipython console to handle text/plain display
843 * :ghpull:`4678`: allow ipython console to handle text/plain display
844 * :ghpull:`4706`: remove irunner, iplogger
844 * :ghpull:`4706`: remove irunner, iplogger
845 * :ghpull:`4701`: Delete an old dictionary available for selecting the aligment of text.
845 * :ghpull:`4701`: Delete an old dictionary available for selecting the aligment of text.
846 * :ghpull:`4702`: Making reveal font-size a relative unit.
846 * :ghpull:`4702`: Making reveal font-size a relative unit.
847 * :ghpull:`4649`: added a quiet option to %cpaste to suppress output
847 * :ghpull:`4649`: added a quiet option to %cpaste to suppress output
848 * :ghpull:`4690`: Option to spew subprocess streams during tests
848 * :ghpull:`4690`: Option to spew subprocess streams during tests
849 * :ghpull:`4688`: Fixed various typos in docstrings.
849 * :ghpull:`4688`: Fixed various typos in docstrings.
850 * :ghpull:`4645`: CasperJs utility functions.
850 * :ghpull:`4645`: CasperJs utility functions.
851 * :ghpull:`4670`: Stop bundling the numpydoc Sphinx extension
851 * :ghpull:`4670`: Stop bundling the numpydoc Sphinx extension
852 * :ghpull:`4675`: common IPython prefix for ModIndex
852 * :ghpull:`4675`: common IPython prefix for ModIndex
853 * :ghpull:`4672`: Remove unused 'attic' module
853 * :ghpull:`4672`: Remove unused 'attic' module
854 * :ghpull:`4671`: Fix docstrings in utils.text
854 * :ghpull:`4671`: Fix docstrings in utils.text
855 * :ghpull:`4669`: add missing help strings to HistoryManager configurables
855 * :ghpull:`4669`: add missing help strings to HistoryManager configurables
856 * :ghpull:`4668`: Make non-ASCII docstring unicode
856 * :ghpull:`4668`: Make non-ASCII docstring unicode
857 * :ghpull:`4650`: added a note about sharing of nbconvert tempates
857 * :ghpull:`4650`: added a note about sharing of nbconvert tempates
858 * :ghpull:`4646`: Fixing various output related things:
858 * :ghpull:`4646`: Fixing various output related things:
859 * :ghpull:`4665`: check for libedit in readline on OS X
859 * :ghpull:`4665`: check for libedit in readline on OS X
860 * :ghpull:`4606`: Make running PYTHONSTARTUP optional
860 * :ghpull:`4606`: Make running PYTHONSTARTUP optional
861 * :ghpull:`4654`: Fixing left padding of text cells to match that of code cells.
861 * :ghpull:`4654`: Fixing left padding of text cells to match that of code cells.
862 * :ghpull:`4306`: add raw_mimetype metadata to raw cells
862 * :ghpull:`4306`: add raw_mimetype metadata to raw cells
863 * :ghpull:`4576`: Tighten up the vertical spacing on cells and make the padding of cells more consistent
863 * :ghpull:`4576`: Tighten up the vertical spacing on cells and make the padding of cells more consistent
864 * :ghpull:`4353`: Don't reset the readline completer after each prompt
864 * :ghpull:`4353`: Don't reset the readline completer after each prompt
865 * :ghpull:`4567`: Adding prompt area to non-CodeCells to indent content.
865 * :ghpull:`4567`: Adding prompt area to non-CodeCells to indent content.
866 * :ghpull:`4446`: Use SVG plots in OctaveMagic by default due to lack of Ghostscript on Windows Octave
866 * :ghpull:`4446`: Use SVG plots in OctaveMagic by default due to lack of Ghostscript on Windows Octave
867 * :ghpull:`4613`: remove configurable.created
867 * :ghpull:`4613`: remove configurable.created
868 * :ghpull:`4631`: Use argument lists for command help tests
868 * :ghpull:`4631`: Use argument lists for command help tests
869 * :ghpull:`4633`: Modifies test_get_long_path_name_winr32() to allow for long path names in temp dir
869 * :ghpull:`4633`: Modifies test_get_long_path_name_winr32() to allow for long path names in temp dir
870 * :ghpull:`4642`: Allow docs to build without PyQt installed.
870 * :ghpull:`4642`: Allow docs to build without PyQt installed.
871 * :ghpull:`4641`: Don't check for wx in the test suite.
871 * :ghpull:`4641`: Don't check for wx in the test suite.
872 * :ghpull:`4622`: make QtConsole Lexer configurable
872 * :ghpull:`4622`: make QtConsole Lexer configurable
873 * :ghpull:`4594`: Fixed #2923 Move Save Away from Cut in toolbar
873 * :ghpull:`4594`: Fixed #2923 Move Save Away from Cut in toolbar
874 * :ghpull:`4593`: don't interfere with set_next_input contents in qtconsole
874 * :ghpull:`4593`: don't interfere with set_next_input contents in qtconsole
875 * :ghpull:`4640`: Support matplotlib's Gtk3 backend in --pylab mode
875 * :ghpull:`4640`: Support matplotlib's Gtk3 backend in --pylab mode
876 * :ghpull:`4639`: Minor import fix to get qtconsole with --pylab=qt working
876 * :ghpull:`4639`: Minor import fix to get qtconsole with --pylab=qt working
877 * :ghpull:`4637`: Fixed typo in links.txt.
877 * :ghpull:`4637`: Fixed typo in links.txt.
878 * :ghpull:`4634`: Fix nbrun in notebooks with non-code cells.
878 * :ghpull:`4634`: Fix nbrun in notebooks with non-code cells.
879 * :ghpull:`4632`: Restore the ability to run tests from a function.
879 * :ghpull:`4632`: Restore the ability to run tests from a function.
880 * :ghpull:`4624`: Fix crash when $EDITOR is non-ASCII
880 * :ghpull:`4624`: Fix crash when $EDITOR is non-ASCII
881 * :ghpull:`4453`: Play nice with App Nap
881 * :ghpull:`4453`: Play nice with App Nap
882 * :ghpull:`4541`: relax ipconfig matching on Windows
882 * :ghpull:`4541`: relax ipconfig matching on Windows
883 * :ghpull:`4552`: add pickleutil.use_dill
883 * :ghpull:`4552`: add pickleutil.use_dill
884 * :ghpull:`4590`: Font awesome for IPython slides
884 * :ghpull:`4590`: Font awesome for IPython slides
885 * :ghpull:`4589`: Inherit the width of pre code inside the input code cells.
885 * :ghpull:`4589`: Inherit the width of pre code inside the input code cells.
886 * :ghpull:`4588`: Update reveal.js CDN to 2.5.0.
886 * :ghpull:`4588`: Update reveal.js CDN to 2.5.0.
887 * :ghpull:`4569`: store cell toolbar preset in notebook metadata
887 * :ghpull:`4569`: store cell toolbar preset in notebook metadata
888 * :ghpull:`4609`: Fix bytes regex for Python 3.
888 * :ghpull:`4609`: Fix bytes regex for Python 3.
889 * :ghpull:`4581`: Writing unicode to stdout
889 * :ghpull:`4581`: Writing unicode to stdout
890 * :ghpull:`4591`: Documenting codemirror shorcuts.
890 * :ghpull:`4591`: Documenting codemirror shorcuts.
891 * :ghpull:`4607`: Tutorial doc should link to user config intro
891 * :ghpull:`4607`: Tutorial doc should link to user config intro
892 * :ghpull:`4601`: test that rename fails with 409 if it would clobber
892 * :ghpull:`4601`: test that rename fails with 409 if it would clobber
893 * :ghpull:`4599`: re-cast int/float subclasses to int/float in json_clean
893 * :ghpull:`4599`: re-cast int/float subclasses to int/float in json_clean
894 * :ghpull:`4542`: new `ipython history clear` subcommand
894 * :ghpull:`4542`: new `ipython history clear` subcommand
895 * :ghpull:`4568`: don't use lazily-evaluated rc.ids in wait_for_idle
895 * :ghpull:`4568`: don't use lazily-evaluated rc.ids in wait_for_idle
896 * :ghpull:`4572`: DOC: %profile docstring should reference %prun
896 * :ghpull:`4572`: DOC: %profile docstring should reference %prun
897 * :ghpull:`4571`: no longer need 3 suffix on travis, tox
897 * :ghpull:`4571`: no longer need 3 suffix on travis, tox
898 * :ghpull:`4566`: Fixing cell_type in CodeCell constructor.
898 * :ghpull:`4566`: Fixing cell_type in CodeCell constructor.
899 * :ghpull:`4563`: Specify encoding for reading notebook file.
899 * :ghpull:`4563`: Specify encoding for reading notebook file.
900 * :ghpull:`4452`: support notebooks in %run
900 * :ghpull:`4452`: support notebooks in %run
901 * :ghpull:`4546`: fix warning condition on notebook startup
901 * :ghpull:`4546`: fix warning condition on notebook startup
902 * :ghpull:`4540`: Apidocs3
902 * :ghpull:`4540`: Apidocs3
903 * :ghpull:`4553`: Fix Python 3 handling of urllib
903 * :ghpull:`4553`: Fix Python 3 handling of urllib
904 * :ghpull:`4543`: make hiding of initial namespace optional
904 * :ghpull:`4543`: make hiding of initial namespace optional
905 * :ghpull:`4517`: send shutdown_request on exit of `ipython console`
905 * :ghpull:`4517`: send shutdown_request on exit of `ipython console`
906 * :ghpull:`4528`: improvements to bash completion
906 * :ghpull:`4528`: improvements to bash completion
907 * :ghpull:`4532`: Hide dynamically defined metaclass base from Sphinx.
907 * :ghpull:`4532`: Hide dynamically defined metaclass base from Sphinx.
908 * :ghpull:`4515`: Spring Cleaning, and Load speedup
908 * :ghpull:`4515`: Spring Cleaning, and Load speedup
909 * :ghpull:`4529`: note routing identities needed for input requests
909 * :ghpull:`4529`: note routing identities needed for input requests
910 * :ghpull:`4514`: allow restart in `%run -d`
910 * :ghpull:`4514`: allow restart in `%run -d`
911 * :ghpull:`4527`: add redirect for 1.0-style 'files/' prefix links
911 * :ghpull:`4527`: add redirect for 1.0-style 'files/' prefix links
912 * :ghpull:`4526`: Allow unicode arguments to passwd_check on Python 2
912 * :ghpull:`4526`: Allow unicode arguments to passwd_check on Python 2
913 * :ghpull:`4403`: Global highlight language selection.
913 * :ghpull:`4403`: Global highlight language selection.
914 * :ghpull:`4250`: outputarea.js: Wrap inline SVGs inside an iframe
914 * :ghpull:`4250`: outputarea.js: Wrap inline SVGs inside an iframe
915 * :ghpull:`4521`: Read wav files in binary mode
915 * :ghpull:`4521`: Read wav files in binary mode
916 * :ghpull:`4444`: Css cleaning
916 * :ghpull:`4444`: Css cleaning
917 * :ghpull:`4523`: Use username and password for MongoDB on ShiningPanda
917 * :ghpull:`4523`: Use username and password for MongoDB on ShiningPanda
918 * :ghpull:`4510`: Update whatsnew from PR files
918 * :ghpull:`4510`: Update whatsnew from PR files
919 * :ghpull:`4441`: add `setup.py jsversion`
919 * :ghpull:`4441`: add ``setup.py jsversion``
920 * :ghpull:`4518`: Fix for race condition in url file decoding.
920 * :ghpull:`4518`: Fix for race condition in url file decoding.
921 * :ghpull:`4497`: don't automatically unpack datetime objects in the message spec
921 * :ghpull:`4497`: don't automatically unpack datetime objects in the message spec
922 * :ghpull:`4506`: wait for empty queues as well as load-balanced tasks
922 * :ghpull:`4506`: wait for empty queues as well as load-balanced tasks
923 * :ghpull:`4492`: Configuration docs refresh
923 * :ghpull:`4492`: Configuration docs refresh
924 * :ghpull:`4508`: Fix some uses of map() in Qt console completion code.
924 * :ghpull:`4508`: Fix some uses of map() in Qt console completion code.
925 * :ghpull:`4498`: Daemon StreamCapturer
925 * :ghpull:`4498`: Daemon StreamCapturer
926 * :ghpull:`4499`: Skip clipboard test on unix systems if headless.
926 * :ghpull:`4499`: Skip clipboard test on unix systems if headless.
927 * :ghpull:`4460`: Better clipboard handling, esp. with pywin32
927 * :ghpull:`4460`: Better clipboard handling, esp. with pywin32
928 * :ghpull:`4496`: Pass nbformat object to write call to save .py script
928 * :ghpull:`4496`: Pass nbformat object to write call to save .py script
929 * :ghpull:`4466`: various pandoc latex fixes
929 * :ghpull:`4466`: various pandoc latex fixes
930 * :ghpull:`4473`: Setup for Python 2/3
930 * :ghpull:`4473`: Setup for Python 2/3
931 * :ghpull:`4459`: protect against broken repr in lib.pretty
931 * :ghpull:`4459`: protect against broken repr in lib.pretty
932 * :ghpull:`4457`: Use ~/.ipython as default config directory
932 * :ghpull:`4457`: Use ~/.ipython as default config directory
933 * :ghpull:`4489`: check realpath of env in init_virtualenv
933 * :ghpull:`4489`: check realpath of env in init_virtualenv
934 * :ghpull:`4490`: fix possible race condition in test_await_data
934 * :ghpull:`4490`: fix possible race condition in test_await_data
935 * :ghpull:`4476`: Fix: Remove space added by display(JavaScript) on page reload
935 * :ghpull:`4476`: Fix: Remove space added by display(JavaScript) on page reload
936 * :ghpull:`4398`: [Notebook] Deactivate tooltip on tab by default.
936 * :ghpull:`4398`: [Notebook] Deactivate tooltip on tab by default.
937 * :ghpull:`4480`: Docs shotgun 2
937 * :ghpull:`4480`: Docs shotgun 2
938 * :ghpull:`4488`: fix typo in message spec doc
938 * :ghpull:`4488`: fix typo in message spec doc
939 * :ghpull:`4479`: yet another JS race condition fix
939 * :ghpull:`4479`: yet another JS race condition fix
940 * :ghpull:`4477`: Allow incremental builds of the html_noapi docs target
940 * :ghpull:`4477`: Allow incremental builds of the html_noapi docs target
941 * :ghpull:`4470`: Various Config object cleanups
941 * :ghpull:`4470`: Various Config object cleanups
942 * :ghpull:`4410`: make close-and-halt work on new tabs in Chrome
942 * :ghpull:`4410`: make close-and-halt work on new tabs in Chrome
943 * :ghpull:`4469`: Python 3 & getcwdu
943 * :ghpull:`4469`: Python 3 & getcwdu
944 * :ghpull:`4451`: fix: allow JS test to run after shutdown test
944 * :ghpull:`4451`: fix: allow JS test to run after shutdown test
945 * :ghpull:`4456`: Simplify StreamCapturer for subprocess testing
945 * :ghpull:`4456`: Simplify StreamCapturer for subprocess testing
946 * :ghpull:`4464`: Correct description for Bytes traitlet type
946 * :ghpull:`4464`: Correct description for Bytes traitlet type
947 * :ghpull:`4465`: Clean up MANIFEST.in
947 * :ghpull:`4465`: Clean up MANIFEST.in
948 * :ghpull:`4461`: Correct TypeError message in svg2pdf
948 * :ghpull:`4461`: Correct TypeError message in svg2pdf
949 * :ghpull:`4458`: use signalstatus if exit status is undefined
949 * :ghpull:`4458`: use signalstatus if exit status is undefined
950 * :ghpull:`4438`: Single codebase Python 3 support (again)
950 * :ghpull:`4438`: Single codebase Python 3 support (again)
951 * :ghpull:`4198`: Version conversion, support for X to Y even if Y < X (nbformat)
951 * :ghpull:`4198`: Version conversion, support for X to Y even if Y < X (nbformat)
952 * :ghpull:`4415`: More tooltips in the Notebook menu
952 * :ghpull:`4415`: More tooltips in the Notebook menu
953 * :ghpull:`4450`: remove monkey patch for older versions of tornado
953 * :ghpull:`4450`: remove monkey patch for older versions of tornado
954 * :ghpull:`4423`: Fix progress bar and scrolling bug.
954 * :ghpull:`4423`: Fix progress bar and scrolling bug.
955 * :ghpull:`4435`: raise 404 on not found static file
955 * :ghpull:`4435`: raise 404 on not found static file
956 * :ghpull:`4442`: fix and add shim for change introduce by #4195
956 * :ghpull:`4442`: fix and add shim for change introduce by #4195
957 * :ghpull:`4436`: allow `require("nbextensions/extname")` to load from IPYTHONDIR/nbextensions
957 * :ghpull:`4436`: allow `require("nbextensions/extname")` to load from IPYTHONDIR/nbextensions
958 * :ghpull:`4437`: don't compute etags in static file handlers
958 * :ghpull:`4437`: don't compute etags in static file handlers
959 * :ghpull:`4427`: notebooks should always have one checkpoint
959 * :ghpull:`4427`: notebooks should always have one checkpoint
960 * :ghpull:`4425`: fix js pythonisme
960 * :ghpull:`4425`: fix js pythonisme
961 * :ghpull:`4195`: IPEP 21: widget messages
961 * :ghpull:`4195`: IPEP 21: widget messages
962 * :ghpull:`4434`: Fix broken link for Dive Into Python.
962 * :ghpull:`4434`: Fix broken link for Dive Into Python.
963 * :ghpull:`4428`: bump minimum tornado version to 3.1.0
963 * :ghpull:`4428`: bump minimum tornado version to 3.1.0
964 * :ghpull:`4302`: Add an Audio display class
964 * :ghpull:`4302`: Add an Audio display class
965 * :ghpull:`4285`: Notebook javascript test suite using CasperJS
965 * :ghpull:`4285`: Notebook javascript test suite using CasperJS
966 * :ghpull:`4420`: Allow checking for backports via milestone
966 * :ghpull:`4420`: Allow checking for backports via milestone
967 * :ghpull:`4426`: set kernel cwd to notebook's directory
967 * :ghpull:`4426`: set kernel cwd to notebook's directory
968 * :ghpull:`4389`: By default, Magics inherit from Configurable
968 * :ghpull:`4389`: By default, Magics inherit from Configurable
969 * :ghpull:`4393`: Capture output from subprocs during test, and display on failure
969 * :ghpull:`4393`: Capture output from subprocs during test, and display on failure
970 * :ghpull:`4419`: define InlineBackend configurable in its own file
970 * :ghpull:`4419`: define InlineBackend configurable in its own file
971 * :ghpull:`4303`: Multidirectory support for the Notebook
971 * :ghpull:`4303`: Multidirectory support for the Notebook
972 * :ghpull:`4371`: Restored ipython profile locate dir and fixed typo. (Fixes #3708).
972 * :ghpull:`4371`: Restored ipython profile locate dir and fixed typo. (Fixes #3708).
973 * :ghpull:`4414`: Specify unicode type properly in rmagic
973 * :ghpull:`4414`: Specify unicode type properly in rmagic
974 * :ghpull:`4413`: don't instantiate IPython shell as class attr
974 * :ghpull:`4413`: don't instantiate IPython shell as class attr
975 * :ghpull:`4400`: Remove 5s wait on inactivity on GUI inputhook loops
975 * :ghpull:`4400`: Remove 5s wait on inactivity on GUI inputhook loops
976 * :ghpull:`4412`: Fix traitlet _notify_trait by-ref issue
976 * :ghpull:`4412`: Fix traitlet _notify_trait by-ref issue
977 * :ghpull:`4378`: split adds new cell above, rather than below
977 * :ghpull:`4378`: split adds new cell above, rather than below
978 * :ghpull:`4405`: Bring display of builtin types and functions in line with Py 2
978 * :ghpull:`4405`: Bring display of builtin types and functions in line with Py 2
979 * :ghpull:`4367`: clean up of documentation files
979 * :ghpull:`4367`: clean up of documentation files
980 * :ghpull:`4401`: Provide a name of the HistorySavingThread
980 * :ghpull:`4401`: Provide a name of the HistorySavingThread
981 * :ghpull:`4384`: fix menubar height measurement
981 * :ghpull:`4384`: fix menubar height measurement
982 * :ghpull:`4377`: fix tooltip cancel
982 * :ghpull:`4377`: fix tooltip cancel
983 * :ghpull:`4293`: Factorise code in tooltip for julia monkeypatching
983 * :ghpull:`4293`: Factorise code in tooltip for julia monkeypatching
984 * :ghpull:`4292`: improve js-completer logic.
984 * :ghpull:`4292`: improve js-completer logic.
985 * :ghpull:`4363`: set_next_input: keep only last input when repeatedly called in a single cell
985 * :ghpull:`4363`: set_next_input: keep only last input when repeatedly called in a single cell
986 * :ghpull:`4382`: Use safe_hasattr in dir2
986 * :ghpull:`4382`: Use safe_hasattr in dir2
987 * :ghpull:`4379`: fix (CTRL-M -) shortcut for splitting cell in FF
987 * :ghpull:`4379`: fix (CTRL-M -) shortcut for splitting cell in FF
988 * :ghpull:`4380`: Test and fixes for localinterfaces
988 * :ghpull:`4380`: Test and fixes for localinterfaces
989 * :ghpull:`4372`: Don't assume that SyntaxTB is always called with a SyntaxError
989 * :ghpull:`4372`: Don't assume that SyntaxTB is always called with a SyntaxError
990 * :ghpull:`4342`: Return value directly from the try block and avoid a variable
990 * :ghpull:`4342`: Return value directly from the try block and avoid a variable
991 * :ghpull:`4154`: Center LaTeX and figures in markdown
991 * :ghpull:`4154`: Center LaTeX and figures in markdown
992 * :ghpull:`4311`: %load -s to load specific functions or classes
992 * :ghpull:`4311`: %load -s to load specific functions or classes
993 * :ghpull:`4350`: WinHPC launcher fixes
993 * :ghpull:`4350`: WinHPC launcher fixes
994 * :ghpull:`4345`: Make irunner compatible with upcoming pexpect 3.0 interface
994 * :ghpull:`4345`: Make irunner compatible with upcoming pexpect 3.0 interface
995 * :ghpull:`4276`: Support container methods in config
995 * :ghpull:`4276`: Support container methods in config
996 * :ghpull:`4359`: test_pylabtools also needs to modify matplotlib.rcParamsOrig
996 * :ghpull:`4359`: test_pylabtools also needs to modify matplotlib.rcParamsOrig
997 * :ghpull:`4355`: remove hardcoded box-orient
997 * :ghpull:`4355`: remove hardcoded box-orient
998 * :ghpull:`4333`: Add Edit Notebook Metadata to Edit menu
998 * :ghpull:`4333`: Add Edit Notebook Metadata to Edit menu
999 * :ghpull:`4349`: Script to update What's New file
999 * :ghpull:`4349`: Script to update What's New file
1000 * :ghpull:`4348`: Call PDF viewer after latex compiling (nbconvert)
1000 * :ghpull:`4348`: Call PDF viewer after latex compiling (nbconvert)
1001 * :ghpull:`4346`: getpass() on Windows & Python 2 needs bytes prompt
1001 * :ghpull:`4346`: getpass() on Windows & Python 2 needs bytes prompt
1002 * :ghpull:`4304`: use netifaces for faster IPython.utils.localinterfaces
1002 * :ghpull:`4304`: use netifaces for faster IPython.utils.localinterfaces
1003 * :ghpull:`4305`: Add even more ways to populate localinterfaces
1003 * :ghpull:`4305`: Add even more ways to populate localinterfaces
1004 * :ghpull:`4313`: remove strip_math_space
1004 * :ghpull:`4313`: remove strip_math_space
1005 * :ghpull:`4325`: Some changes to improve readability.
1005 * :ghpull:`4325`: Some changes to improve readability.
1006 * :ghpull:`4281`: Adjust tab completion widget if too close to bottom of page.
1006 * :ghpull:`4281`: Adjust tab completion widget if too close to bottom of page.
1007 * :ghpull:`4347`: Remove pycolor script
1007 * :ghpull:`4347`: Remove pycolor script
1008 * :ghpull:`4322`: Scroll to the top after change of slides in the IPython slides
1008 * :ghpull:`4322`: Scroll to the top after change of slides in the IPython slides
1009 * :ghpull:`4289`: Fix scrolling output (not working post clear_output changes)
1009 * :ghpull:`4289`: Fix scrolling output (not working post clear_output changes)
1010 * :ghpull:`4343`: Make parameters for kernel start method more general
1010 * :ghpull:`4343`: Make parameters for kernel start method more general
1011 * :ghpull:`4237`: Keywords should shadow magic functions
1011 * :ghpull:`4237`: Keywords should shadow magic functions
1012 * :ghpull:`4338`: adjust default value of level in sync_imports
1012 * :ghpull:`4338`: adjust default value of level in sync_imports
1013 * :ghpull:`4328`: Remove unused loop variable.
1013 * :ghpull:`4328`: Remove unused loop variable.
1014 * :ghpull:`4340`: fix mathjax download url to new GitHub format
1014 * :ghpull:`4340`: fix mathjax download url to new GitHub format
1015 * :ghpull:`4336`: use simple replacement rather than string formatting in format_kernel_cmd
1015 * :ghpull:`4336`: use simple replacement rather than string formatting in format_kernel_cmd
1016 * :ghpull:`4264`: catch unicode error listing profiles
1016 * :ghpull:`4264`: catch unicode error listing profiles
1017 * :ghpull:`4314`: catch EACCES when binding notebook app
1017 * :ghpull:`4314`: catch EACCES when binding notebook app
1018 * :ghpull:`4324`: Remove commented addthis toolbar
1018 * :ghpull:`4324`: Remove commented addthis toolbar
1019 * :ghpull:`4327`: Use the with statement to open a file.
1019 * :ghpull:`4327`: Use the with statement to open a file.
1020 * :ghpull:`4318`: fix initial sys.path
1020 * :ghpull:`4318`: fix initial sys.path
1021 * :ghpull:`4315`: Explicitly state what version of Pandoc is supported in docs/install
1021 * :ghpull:`4315`: Explicitly state what version of Pandoc is supported in docs/install
1022 * :ghpull:`4316`: underscore missing on notebook_p4
1022 * :ghpull:`4316`: underscore missing on notebook_p4
1023 * :ghpull:`4295`: Implement boundary option for load magic (#1093)
1023 * :ghpull:`4295`: Implement boundary option for load magic (#1093)
1024 * :ghpull:`4300`: traits defauts are strings not object
1024 * :ghpull:`4300`: traits defauts are strings not object
1025 * :ghpull:`4297`: Remove an unreachable return statement.
1025 * :ghpull:`4297`: Remove an unreachable return statement.
1026 * :ghpull:`4260`: Use subprocess for system_raw
1026 * :ghpull:`4260`: Use subprocess for system_raw
1027 * :ghpull:`4277`: add nbextensions
1027 * :ghpull:`4277`: add nbextensions
1028 * :ghpull:`4294`: don't require tornado 3 in `--post serve`
1028 * :ghpull:`4294`: don't require tornado 3 in `--post serve`
1029 * :ghpull:`4270`: adjust Scheduler timeout logic
1029 * :ghpull:`4270`: adjust Scheduler timeout logic
1030 * :ghpull:`4278`: add `-a` to easy_install command in libedit warning
1030 * :ghpull:`4278`: add `-a` to easy_install command in libedit warning
1031 * :ghpull:`4282`: Enable automatic line breaks in MathJax.
1031 * :ghpull:`4282`: Enable automatic line breaks in MathJax.
1032 * :ghpull:`4279`: Fixing line-height of list items in tree view.
1032 * :ghpull:`4279`: Fixing line-height of list items in tree view.
1033 * :ghpull:`4253`: fixes #4039.
1033 * :ghpull:`4253`: fixes #4039.
1034 * :ghpull:`4131`: Add module's name argument in %%cython magic
1034 * :ghpull:`4131`: Add module's name argument in %%cython magic
1035 * :ghpull:`4269`: Add mathletters option and longtable package to latex_base.tplx
1035 * :ghpull:`4269`: Add mathletters option and longtable package to latex_base.tplx
1036 * :ghpull:`4230`: Switch correctly to the user's default matplotlib backend after inline.
1036 * :ghpull:`4230`: Switch correctly to the user's default matplotlib backend after inline.
1037 * :ghpull:`4271`: Hopefully fix ordering of output on ShiningPanda
1037 * :ghpull:`4271`: Hopefully fix ordering of output on ShiningPanda
1038 * :ghpull:`4239`: more informative error message for bad serialization
1038 * :ghpull:`4239`: more informative error message for bad serialization
1039 * :ghpull:`4263`: Fix excludes for IPython.testing
1039 * :ghpull:`4263`: Fix excludes for IPython.testing
1040 * :ghpull:`4112`: nbconvert: Latex template refactor
1040 * :ghpull:`4112`: nbconvert: Latex template refactor
1041 * :ghpull:`4261`: Fixing a formatting error in the custom display example notebook.
1041 * :ghpull:`4261`: Fixing a formatting error in the custom display example notebook.
1042 * :ghpull:`4259`: Fix Windows test exclusions
1042 * :ghpull:`4259`: Fix Windows test exclusions
1043 * :ghpull:`4229`: Clear_output: Animation & widget related changes.
1043 * :ghpull:`4229`: Clear_output: Animation & widget related changes.
1044 * :ghpull:`4151`: Refactor alias machinery
1044 * :ghpull:`4151`: Refactor alias machinery
1045 * :ghpull:`4153`: make timeit return an object that contains values
1045 * :ghpull:`4153`: make timeit return an object that contains values
1046 * :ghpull:`4258`: to-backport label is now 1.2
1046 * :ghpull:`4258`: to-backport label is now 1.2
1047 * :ghpull:`4242`: Allow passing extra arguments to iptest through for nose
1047 * :ghpull:`4242`: Allow passing extra arguments to iptest through for nose
1048 * :ghpull:`4257`: fix unicode argv parsing
1048 * :ghpull:`4257`: fix unicode argv parsing
1049 * :ghpull:`4166`: avoid executing code in utils.localinterfaces at import time
1049 * :ghpull:`4166`: avoid executing code in utils.localinterfaces at import time
1050 * :ghpull:`4214`: engine ID metadata should be unicode, not bytes
1050 * :ghpull:`4214`: engine ID metadata should be unicode, not bytes
1051 * :ghpull:`4232`: no highlight if no language specified
1051 * :ghpull:`4232`: no highlight if no language specified
1052 * :ghpull:`4218`: Fix display of SyntaxError when .py file is modified
1052 * :ghpull:`4218`: Fix display of SyntaxError when .py file is modified
1053 * :ghpull:`4207`: add `setup.py css` command
1053 * :ghpull:`4207`: add ``setup.py css`` command
1054 * :ghpull:`4224`: clear previous callbacks on execute
1054 * :ghpull:`4224`: clear previous callbacks on execute
1055 * :ghpull:`4180`: Iptest refactoring
1055 * :ghpull:`4180`: Iptest refactoring
1056 * :ghpull:`4105`: JS output area misaligned
1056 * :ghpull:`4105`: JS output area misaligned
1057 * :ghpull:`4220`: Various improvements to docs formatting
1057 * :ghpull:`4220`: Various improvements to docs formatting
1058 * :ghpull:`4187`: Select adequate highlighter for cell magic languages
1058 * :ghpull:`4187`: Select adequate highlighter for cell magic languages
1059 * :ghpull:`4228`: update -dev docs to reflect latest stable version
1059 * :ghpull:`4228`: update -dev docs to reflect latest stable version
1060 * :ghpull:`4219`: Drop bundled argparse
1060 * :ghpull:`4219`: Drop bundled argparse
1061 * :ghpull:`3851`: Adds an explicit newline for pretty-printing.
1061 * :ghpull:`3851`: Adds an explicit newline for pretty-printing.
1062 * :ghpull:`3622`: Drop fakemodule
1062 * :ghpull:`3622`: Drop fakemodule
1063 * :ghpull:`4080`: change default behavior of database task storage
1063 * :ghpull:`4080`: change default behavior of database task storage
1064 * :ghpull:`4197`: enable cython highlight in notebook
1064 * :ghpull:`4197`: enable cython highlight in notebook
1065 * :ghpull:`4225`: Updated docstring for core.display.Image
1065 * :ghpull:`4225`: Updated docstring for core.display.Image
1066 * :ghpull:`4175`: nbconvert: Jinjaless exporter base
1066 * :ghpull:`4175`: nbconvert: Jinjaless exporter base
1067 * :ghpull:`4208`: Added a lightweight "htmlcore" Makefile entry
1067 * :ghpull:`4208`: Added a lightweight "htmlcore" Makefile entry
1068 * :ghpull:`4209`: Magic doc fixes
1068 * :ghpull:`4209`: Magic doc fixes
1069 * :ghpull:`4217`: avoid importing numpy at the module level
1069 * :ghpull:`4217`: avoid importing numpy at the module level
1070 * :ghpull:`4213`: fixed dead link in examples/notebooks readme to Part 3
1070 * :ghpull:`4213`: fixed dead link in examples/notebooks readme to Part 3
1071 * :ghpull:`4183`: ESC should be handled by CM if tooltip is not on
1071 * :ghpull:`4183`: ESC should be handled by CM if tooltip is not on
1072 * :ghpull:`4193`: Update for #3549: Append Firefox overflow-x fix
1072 * :ghpull:`4193`: Update for #3549: Append Firefox overflow-x fix
1073 * :ghpull:`4205`: use TextIOWrapper when communicating with pandoc subprocess
1073 * :ghpull:`4205`: use TextIOWrapper when communicating with pandoc subprocess
1074 * :ghpull:`4204`: remove some extraneous print statements from IPython.parallel
1074 * :ghpull:`4204`: remove some extraneous print statements from IPython.parallel
1075 * :ghpull:`4201`: HeadingCells cannot be split or merged
1075 * :ghpull:`4201`: HeadingCells cannot be split or merged
1076 * :ghpull:`4048`: finish up speaker-notes PR
1076 * :ghpull:`4048`: finish up speaker-notes PR
1077 * :ghpull:`4079`: trigger `Kernel.status_started` after websockets open
1077 * :ghpull:`4079`: trigger `Kernel.status_started` after websockets open
1078 * :ghpull:`4186`: moved DummyMod to proper namespace to enable dill pickling
1078 * :ghpull:`4186`: moved DummyMod to proper namespace to enable dill pickling
1079 * :ghpull:`4190`: update version-check message in setup.py and IPython.__init__
1079 * :ghpull:`4190`: update version-check message in setup.py and IPython.__init__
1080 * :ghpull:`4188`: Allow user_ns trait to be None
1080 * :ghpull:`4188`: Allow user_ns trait to be None
1081 * :ghpull:`4189`: always fire LOCAL_IPS.extend(PUBLIC_IPS)
1081 * :ghpull:`4189`: always fire LOCAL_IPS.extend(PUBLIC_IPS)
1082 * :ghpull:`4174`: various issues in markdown and rst templates
1082 * :ghpull:`4174`: various issues in markdown and rst templates
1083 * :ghpull:`4178`: add missing data_javascript
1083 * :ghpull:`4178`: add missing data_javascript
1084 * :ghpull:`4168`: Py3 failing tests
1084 * :ghpull:`4168`: Py3 failing tests
1085 * :ghpull:`4181`: nbconvert: Fix, sphinx template not removing new lines from headers
1085 * :ghpull:`4181`: nbconvert: Fix, sphinx template not removing new lines from headers
1086 * :ghpull:`4043`: don't 'restore_bytes' in from_JSON
1086 * :ghpull:`4043`: don't 'restore_bytes' in from_JSON
1087 * :ghpull:`4149`: reuse more kernels in kernel tests
1087 * :ghpull:`4149`: reuse more kernels in kernel tests
1088 * :ghpull:`4163`: Fix for incorrect default encoding on Windows.
1088 * :ghpull:`4163`: Fix for incorrect default encoding on Windows.
1089 * :ghpull:`4136`: catch javascript errors in any output
1089 * :ghpull:`4136`: catch javascript errors in any output
1090 * :ghpull:`4171`: add nbconvert config file when creating profiles
1090 * :ghpull:`4171`: add nbconvert config file when creating profiles
1091 * :ghpull:`4172`: add ability to check what PRs should be backported in backport_pr
1091 * :ghpull:`4172`: add ability to check what PRs should be backported in backport_pr
1092 * :ghpull:`4167`: --fast flag for test suite!
1092 * :ghpull:`4167`: --fast flag for test suite!
1093 * :ghpull:`4125`: Basic exercise of `ipython [subcommand] -h` and help-all
1093 * :ghpull:`4125`: Basic exercise of `ipython [subcommand] -h` and help-all
1094 * :ghpull:`4085`: nbconvert: Fix sphinx preprocessor date format string for Windows
1094 * :ghpull:`4085`: nbconvert: Fix sphinx preprocessor date format string for Windows
1095 * :ghpull:`4159`: don't split `.cell` and `div.cell` CSS
1095 * :ghpull:`4159`: don't split `.cell` and `div.cell` CSS
1096 * :ghpull:`4165`: Remove use of parametric tests
1096 * :ghpull:`4165`: Remove use of parametric tests
1097 * :ghpull:`4158`: generate choices for `--gui` configurable from real mapping
1097 * :ghpull:`4158`: generate choices for `--gui` configurable from real mapping
1098 * :ghpull:`4083`: Implement a better check for hidden values for %who etc.
1098 * :ghpull:`4083`: Implement a better check for hidden values for %who etc.
1099 * :ghpull:`4147`: Reference notebook examples, fixes #4146.
1099 * :ghpull:`4147`: Reference notebook examples, fixes #4146.
1100 * :ghpull:`4065`: do not include specific css in embedable one
1100 * :ghpull:`4065`: do not include specific css in embedable one
1101 * :ghpull:`4092`: nbconvert: Fix for unicode html headers, Windows + Python 2.x
1101 * :ghpull:`4092`: nbconvert: Fix for unicode html headers, Windows + Python 2.x
1102 * :ghpull:`4074`: close Client sockets if connection fails
1102 * :ghpull:`4074`: close Client sockets if connection fails
1103 * :ghpull:`4064`: Store default codemirror mode in only 1 place
1103 * :ghpull:`4064`: Store default codemirror mode in only 1 place
1104 * :ghpull:`4104`: Add way to install MathJax to a particular profile
1104 * :ghpull:`4104`: Add way to install MathJax to a particular profile
1105 * :ghpull:`4161`: Select name when renaming a notebook
1105 * :ghpull:`4161`: Select name when renaming a notebook
1106 * :ghpull:`4160`: Add quotes around ".[notebook]" in readme
1106 * :ghpull:`4160`: Add quotes around ".[notebook]" in readme
1107 * :ghpull:`4144`: help_end transformer shouldn't pick up ? in multiline string
1107 * :ghpull:`4144`: help_end transformer shouldn't pick up ? in multiline string
1108 * :ghpull:`4090`: Add LaTeX citation handling to nbconvert
1108 * :ghpull:`4090`: Add LaTeX citation handling to nbconvert
1109 * :ghpull:`4143`: update example custom.js
1109 * :ghpull:`4143`: update example custom.js
1110 * :ghpull:`4142`: DOC: unwrap openssl line in public_server doc
1110 * :ghpull:`4142`: DOC: unwrap openssl line in public_server doc
1111 * :ghpull:`4126`: update tox.ini
1111 * :ghpull:`4126`: update tox.ini
1112 * :ghpull:`4141`: add files with a separate `add` call in backport_pr
1112 * :ghpull:`4141`: add files with a separate `add` call in backport_pr
1113 * :ghpull:`4137`: Restore autorestore option for storemagic
1113 * :ghpull:`4137`: Restore autorestore option for storemagic
1114 * :ghpull:`4098`: pass profile-dir instead of profile name to Kernel
1114 * :ghpull:`4098`: pass profile-dir instead of profile name to Kernel
1115 * :ghpull:`4120`: support `input` in Python 2 kernels
1115 * :ghpull:`4120`: support `input` in Python 2 kernels
1116 * :ghpull:`4088`: nbconvert: Fix coalescestreams line with incorrect nesting causing strange behavior
1116 * :ghpull:`4088`: nbconvert: Fix coalescestreams line with incorrect nesting causing strange behavior
1117 * :ghpull:`4060`: only strip continuation prompts if regular prompts seen first
1117 * :ghpull:`4060`: only strip continuation prompts if regular prompts seen first
1118 * :ghpull:`4132`: Fixed name error bug in function safe_unicode in module py3compat.
1118 * :ghpull:`4132`: Fixed name error bug in function safe_unicode in module py3compat.
1119 * :ghpull:`4121`: move test_kernel from IPython.zmq to IPython.kernel
1119 * :ghpull:`4121`: move test_kernel from IPython.zmq to IPython.kernel
1120 * :ghpull:`4118`: ZMQ heartbeat channel: catch EINTR exceptions and continue.
1120 * :ghpull:`4118`: ZMQ heartbeat channel: catch EINTR exceptions and continue.
1121 * :ghpull:`4070`: New changes should go into pr/ folder
1121 * :ghpull:`4070`: New changes should go into pr/ folder
1122 * :ghpull:`4054`: use unicode for HTML export
1122 * :ghpull:`4054`: use unicode for HTML export
1123 * :ghpull:`4106`: fix a couple of default block values
1123 * :ghpull:`4106`: fix a couple of default block values
1124 * :ghpull:`4107`: update parallel magic tests with capture_output API
1124 * :ghpull:`4107`: update parallel magic tests with capture_output API
1125 * :ghpull:`4102`: Fix clashes between debugger tests and coverage.py
1125 * :ghpull:`4102`: Fix clashes between debugger tests and coverage.py
1126 * :ghpull:`4115`: Update docs on declaring a magic function
1126 * :ghpull:`4115`: Update docs on declaring a magic function
1127 * :ghpull:`4101`: restore accidentally removed EngineError
1127 * :ghpull:`4101`: restore accidentally removed EngineError
1128 * :ghpull:`4096`: minor docs changes
1128 * :ghpull:`4096`: minor docs changes
1129 * :ghpull:`4094`: Update target branch before backporting PR
1129 * :ghpull:`4094`: Update target branch before backporting PR
1130 * :ghpull:`4069`: Drop monkeypatch for pre-1.0 nose
1130 * :ghpull:`4069`: Drop monkeypatch for pre-1.0 nose
1131 * :ghpull:`4056`: respect `pylab_import_all` when `--pylab` specified at the command-line
1131 * :ghpull:`4056`: respect `pylab_import_all` when `--pylab` specified at the command-line
1132 * :ghpull:`4091`: Make Qt console banner configurable
1132 * :ghpull:`4091`: Make Qt console banner configurable
1133 * :ghpull:`4086`: fix missing errno import
1133 * :ghpull:`4086`: fix missing errno import
1134 * :ghpull:`4084`: Use msvcrt.getwch() for Windows pager.
1134 * :ghpull:`4084`: Use msvcrt.getwch() for Windows pager.
1135 * :ghpull:`4073`: rename ``post_processors`` submodule to ``postprocessors``
1135 * :ghpull:`4073`: rename ``post_processors`` submodule to ``postprocessors``
1136 * :ghpull:`4075`: Update supported Python versions in tools/test_pr
1136 * :ghpull:`4075`: Update supported Python versions in tools/test_pr
1137 * :ghpull:`4068`: minor bug fix, define 'cell' in dialog.js.
1137 * :ghpull:`4068`: minor bug fix, define 'cell' in dialog.js.
1138 * :ghpull:`4044`: rename call methods to transform and postprocess
1138 * :ghpull:`4044`: rename call methods to transform and postprocess
1139 * :ghpull:`3744`: capture rich output as well as stdout/err in capture_output
1139 * :ghpull:`3744`: capture rich output as well as stdout/err in capture_output
1140 * :ghpull:`3969`: "use strict" in most (if not all) our javascript
1140 * :ghpull:`3969`: "use strict" in most (if not all) our javascript
1141 * :ghpull:`4030`: exclude `.git` in MANIFEST.in
1141 * :ghpull:`4030`: exclude `.git` in MANIFEST.in
1142 * :ghpull:`4047`: Use istype() when checking if canned object is a dict
1142 * :ghpull:`4047`: Use istype() when checking if canned object is a dict
1143 * :ghpull:`4031`: don't close_fds on Windows
1143 * :ghpull:`4031`: don't close_fds on Windows
1144 * :ghpull:`4029`: bson.Binary moved
1144 * :ghpull:`4029`: bson.Binary moved
1145 * :ghpull:`3883`: skip test on unix when x11 not available
1145 * :ghpull:`3883`: skip test on unix when x11 not available
1146 * :ghpull:`3863`: Added working speaker notes for slides.
1146 * :ghpull:`3863`: Added working speaker notes for slides.
1147 * :ghpull:`4035`: Fixed custom jinja2 templates being ignored when setting template_path
1147 * :ghpull:`4035`: Fixed custom jinja2 templates being ignored when setting template_path
1148 * :ghpull:`4002`: Drop Python 2.6 and 3.2
1148 * :ghpull:`4002`: Drop Python 2.6 and 3.2
1149 * :ghpull:`4026`: small doc fix in nbconvert
1149 * :ghpull:`4026`: small doc fix in nbconvert
1150 * :ghpull:`4016`: Fix IPython.start_* functions
1150 * :ghpull:`4016`: Fix IPython.start_* functions
1151 * :ghpull:`4021`: Fix parallel.client.View map() on numpy arrays
1151 * :ghpull:`4021`: Fix parallel.client.View map() on numpy arrays
1152 * :ghpull:`4022`: DOC: fix links to matplotlib, notebook docs
1152 * :ghpull:`4022`: DOC: fix links to matplotlib, notebook docs
1153 * :ghpull:`4018`: Fix warning when running IPython.kernel tests
1153 * :ghpull:`4018`: Fix warning when running IPython.kernel tests
1154 * :ghpull:`4017`: Add REPL-like printing of final/return value to %%R cell magic
1154 * :ghpull:`4017`: Add REPL-like printing of final/return value to %%R cell magic
1155 * :ghpull:`4019`: Test skipping without unicode paths
1155 * :ghpull:`4019`: Test skipping without unicode paths
1156 * :ghpull:`4008`: Transform code before %prun/%%prun runs
1156 * :ghpull:`4008`: Transform code before %prun/%%prun runs
1157 * :ghpull:`4014`: Fix typo in ipapp
1157 * :ghpull:`4014`: Fix typo in ipapp
1158 * :ghpull:`3997`: DOC: typos + rewording in examples/notebooks/Cell Magics.ipynb
1158 * :ghpull:`3997`: DOC: typos + rewording in examples/notebooks/Cell Magics.ipynb
1159 * :ghpull:`3914`: nbconvert: Transformer tests
1159 * :ghpull:`3914`: nbconvert: Transformer tests
1160 * :ghpull:`3987`: get files list in backport_pr
1160 * :ghpull:`3987`: get files list in backport_pr
1161 * :ghpull:`3923`: nbconvert: Writer tests
1161 * :ghpull:`3923`: nbconvert: Writer tests
1162 * :ghpull:`3974`: nbconvert: Fix app tests on Window7 w/ Python 3.3
1162 * :ghpull:`3974`: nbconvert: Fix app tests on Window7 w/ Python 3.3
1163 * :ghpull:`3937`: make tab visible in codemirror and light red background
1163 * :ghpull:`3937`: make tab visible in codemirror and light red background
1164 * :ghpull:`3933`: nbconvert: Post-processor tests
1164 * :ghpull:`3933`: nbconvert: Post-processor tests
1165 * :ghpull:`3978`: fix `--existing` with non-localhost IP
1165 * :ghpull:`3978`: fix `--existing` with non-localhost IP
1166 * :ghpull:`3939`: minor checkpoint cleanup
1166 * :ghpull:`3939`: minor checkpoint cleanup
1167 * :ghpull:`3955`: complete on % for magic in notebook
1167 * :ghpull:`3955`: complete on % for magic in notebook
1168 * :ghpull:`3981`: BF: fix nbconert rst input prompt spacing
1168 * :ghpull:`3981`: BF: fix nbconert rst input prompt spacing
1169 * :ghpull:`3960`: Don't make sphinx a dependency for importing nbconvert
1169 * :ghpull:`3960`: Don't make sphinx a dependency for importing nbconvert
1170 * :ghpull:`3973`: logging.Formatter is not new-style in 2.6
1170 * :ghpull:`3973`: logging.Formatter is not new-style in 2.6
1171
1171
1172 Issues (434):
1172 Issues (434):
1173
1173
1174 * :ghissue:`5476`: For 2.0: Fix links in Notebook Help Menu
1174 * :ghissue:`5476`: For 2.0: Fix links in Notebook Help Menu
1175 * :ghissue:`5337`: Examples reorganization
1175 * :ghissue:`5337`: Examples reorganization
1176 * :ghissue:`5436`: CodeMirror shortcuts in QuickHelp
1176 * :ghissue:`5436`: CodeMirror shortcuts in QuickHelp
1177 * :ghissue:`5444`: Fix numeric verification for Int and Float text widgets.
1177 * :ghissue:`5444`: Fix numeric verification for Int and Float text widgets.
1178 * :ghissue:`5443`: Int and Float Widgets don't allow negative signs
1178 * :ghissue:`5443`: Int and Float Widgets don't allow negative signs
1179 * :ghissue:`5449`: Stretch keyboard shortcut dialog
1179 * :ghissue:`5449`: Stretch keyboard shortcut dialog
1180 * :ghissue:`5471`: Add coding magic comment to nbconvert Python template
1180 * :ghissue:`5471`: Add coding magic comment to nbconvert Python template
1181 * :ghissue:`5470`: UTF-8 Issue When Converting Notebook to a Script.
1181 * :ghissue:`5470`: UTF-8 Issue When Converting Notebook to a Script.
1182 * :ghissue:`5369`: FormatterWarning for SVG matplotlib output in notebook
1182 * :ghissue:`5369`: FormatterWarning for SVG matplotlib output in notebook
1183 * :ghissue:`5460`: Can't start the notebook server specifying a notebook
1183 * :ghissue:`5460`: Can't start the notebook server specifying a notebook
1184 * :ghissue:`2918`: CodeMirror related issues.
1184 * :ghissue:`2918`: CodeMirror related issues.
1185 * :ghissue:`5431`: update github_stats and gh_api for 2.0
1185 * :ghissue:`5431`: update github_stats and gh_api for 2.0
1186 * :ghissue:`4887`: Add tests for modal UI
1186 * :ghissue:`4887`: Add tests for modal UI
1187 * :ghissue:`5290`: Add dual mode JS tests
1187 * :ghissue:`5290`: Add dual mode JS tests
1188 * :ghissue:`5448`: Cmd+/ shortcut doesn't work in IPython master
1188 * :ghissue:`5448`: Cmd+/ shortcut doesn't work in IPython master
1189 * :ghissue:`5447`: Add %%python2 cell magic
1189 * :ghissue:`5447`: Add %%python2 cell magic
1190 * :ghissue:`5442`: Make a "python2" alias or rename the "python"cell magic.
1190 * :ghissue:`5442`: Make a "python2" alias or rename the "python"cell magic.
1191 * :ghissue:`2495`: non-ascii characters in the path
1191 * :ghissue:`2495`: non-ascii characters in the path
1192 * :ghissue:`4554`: dictDB: Exception due to str to datetime comparission
1192 * :ghissue:`4554`: dictDB: Exception due to str to datetime comparission
1193 * :ghissue:`5006`: Comm code is not run in the same context as notebook code
1193 * :ghissue:`5006`: Comm code is not run in the same context as notebook code
1194 * :ghissue:`5118`: Weird interact behavior
1194 * :ghissue:`5118`: Weird interact behavior
1195 * :ghissue:`5401`: Empty code cells in nbconvert rst output cause problems
1195 * :ghissue:`5401`: Empty code cells in nbconvert rst output cause problems
1196 * :ghissue:`5434`: fix check for empty cells in rst template
1196 * :ghissue:`5434`: fix check for empty cells in rst template
1197 * :ghissue:`4944`: Trouble finding ipynb path in Windows 8
1197 * :ghissue:`4944`: Trouble finding ipynb path in Windows 8
1198 * :ghissue:`4605`: Change the url of Editor Shorcuts in the notebook menu.
1198 * :ghissue:`4605`: Change the url of Editor Shorcuts in the notebook menu.
1199 * :ghissue:`5425`: Update COPYING.txt
1199 * :ghissue:`5425`: Update COPYING.txt
1200 * :ghissue:`5348`: BUG: HistoryAccessor.get_session_info(0) - exception
1200 * :ghissue:`5348`: BUG: HistoryAccessor.get_session_info(0) - exception
1201 * :ghissue:`5293`: Javascript("element.append()") looks broken.
1201 * :ghissue:`5293`: Javascript("element.append()") looks broken.
1202 * :ghissue:`5363`: Disable saving if notebook has stopped loading
1202 * :ghissue:`5363`: Disable saving if notebook has stopped loading
1203 * :ghissue:`5189`: Tooltip pager mode is broken
1203 * :ghissue:`5189`: Tooltip pager mode is broken
1204 * :ghissue:`5330`: Updates to shell reference doc
1204 * :ghissue:`5330`: Updates to shell reference doc
1205 * :ghissue:`5397`: Accordion widget broken
1205 * :ghissue:`5397`: Accordion widget broken
1206 * :ghissue:`5106`: Flexbox CSS specificity bugs
1206 * :ghissue:`5106`: Flexbox CSS specificity bugs
1207 * :ghissue:`5297`: tooltip triggers focus bug
1207 * :ghissue:`5297`: tooltip triggers focus bug
1208 * :ghissue:`5417`: scp checking for existence of directories: directory names are incorrect
1208 * :ghissue:`5417`: scp checking for existence of directories: directory names are incorrect
1209 * :ghissue:`5302`: Parallel engine registration fails for slow engines
1209 * :ghissue:`5302`: Parallel engine registration fails for slow engines
1210 * :ghissue:`5334`: notebook's split-cell shortcut dangerous / incompatible with Neo layout (for instance)
1210 * :ghissue:`5334`: notebook's split-cell shortcut dangerous / incompatible with Neo layout (for instance)
1211 * :ghissue:`5324`: Style of `raw_input` UI is off in notebook
1211 * :ghissue:`5324`: Style of `raw_input` UI is off in notebook
1212 * :ghissue:`5350`: Converting notebooks with spaces in their names to RST gives broken images
1212 * :ghissue:`5350`: Converting notebooks with spaces in their names to RST gives broken images
1213 * :ghissue:`5049`: update quickhelp on adding and removing shortcuts
1213 * :ghissue:`5049`: update quickhelp on adding and removing shortcuts
1214 * :ghissue:`4941`: Eliminating display of intermediate stages in progress bars
1214 * :ghissue:`4941`: Eliminating display of intermediate stages in progress bars
1215 * :ghissue:`5345`: nbconvert to markdown does not use backticks
1215 * :ghissue:`5345`: nbconvert to markdown does not use backticks
1216 * :ghissue:`5357`: catch exception in copystat
1216 * :ghissue:`5357`: catch exception in copystat
1217 * :ghissue:`5351`: Notebook saving fails on smb share
1217 * :ghissue:`5351`: Notebook saving fails on smb share
1218 * :ghissue:`4946`: TeX produced cannot be converted to PDF
1218 * :ghissue:`4946`: TeX produced cannot be converted to PDF
1219 * :ghissue:`5347`: pretty print list too slow
1219 * :ghissue:`5347`: pretty print list too slow
1220 * :ghissue:`5238`: Raw cell placeholder is not removed when you edit the cell
1220 * :ghissue:`5238`: Raw cell placeholder is not removed when you edit the cell
1221 * :ghissue:`5382`: Qtconsole doesn't run in Python 3
1221 * :ghissue:`5382`: Qtconsole doesn't run in Python 3
1222 * :ghissue:`5378`: Unexpected and new conflict between PyFileConfigLoader and IPythonQtConsoleApp
1222 * :ghissue:`5378`: Unexpected and new conflict between PyFileConfigLoader and IPythonQtConsoleApp
1223 * :ghissue:`4945`: Heading/cells positioning problem and cell output wrapping
1223 * :ghissue:`4945`: Heading/cells positioning problem and cell output wrapping
1224 * :ghissue:`5084`: Consistent approach for HTML/JS output on nbviewer
1224 * :ghissue:`5084`: Consistent approach for HTML/JS output on nbviewer
1225 * :ghissue:`4902`: print preview does not work, custom.css not found
1225 * :ghissue:`4902`: print preview does not work, custom.css not found
1226 * :ghissue:`5336`: TypeError in bootstrap-tour.min.js
1226 * :ghissue:`5336`: TypeError in bootstrap-tour.min.js
1227 * :ghissue:`5303`: Changed Hub.registration_timeout to be a config input.
1227 * :ghissue:`5303`: Changed Hub.registration_timeout to be a config input.
1228 * :ghissue:`995`: Paste-able mode in terminal
1228 * :ghissue:`995`: Paste-able mode in terminal
1229 * :ghissue:`5305`: Tuple unpacking for shell escape
1229 * :ghissue:`5305`: Tuple unpacking for shell escape
1230 * :ghissue:`5232`: Make nbconvert html full output like notebook's html.
1230 * :ghissue:`5232`: Make nbconvert html full output like notebook's html.
1231 * :ghissue:`5224`: Audit nbconvert HTML output
1231 * :ghissue:`5224`: Audit nbconvert HTML output
1232 * :ghissue:`5253`: display any output from this session in terminal console
1232 * :ghissue:`5253`: display any output from this session in terminal console
1233 * :ghissue:`5251`: ipython console ignoring some stream messages?
1233 * :ghissue:`5251`: ipython console ignoring some stream messages?
1234 * :ghissue:`4802`: Tour of the notebook UI (was UI elements inline with highlighting)
1234 * :ghissue:`4802`: Tour of the notebook UI (was UI elements inline with highlighting)
1235 * :ghissue:`5103`: Moving Constructor definition to the top like a Function definition
1235 * :ghissue:`5103`: Moving Constructor definition to the top like a Function definition
1236 * :ghissue:`5264`: Test failures on master with Anaconda
1236 * :ghissue:`5264`: Test failures on master with Anaconda
1237 * :ghissue:`4833`: Serve /usr/share/javascript at /_sysassets/javascript/ in notebook
1237 * :ghissue:`4833`: Serve /usr/share/javascript at /_sysassets/javascript/ in notebook
1238 * :ghissue:`5071`: Prevent %pylab from clobbering interactive
1238 * :ghissue:`5071`: Prevent %pylab from clobbering interactive
1239 * :ghissue:`5282`: Exception in widget __del__ methods in Python 3.4.
1239 * :ghissue:`5282`: Exception in widget __del__ methods in Python 3.4.
1240 * :ghissue:`5280`: append Firefox overflow-x fix
1240 * :ghissue:`5280`: append Firefox overflow-x fix
1241 * :ghissue:`5120`: append Firefox overflow-x fix, again
1241 * :ghissue:`5120`: append Firefox overflow-x fix, again
1242 * :ghissue:`4127`: autoreload shouldn't rely on .pyc modification times
1242 * :ghissue:`4127`: autoreload shouldn't rely on .pyc modification times
1243 * :ghissue:`5272`: allow highlighting language to be set from notebook metadata
1243 * :ghissue:`5272`: allow highlighting language to be set from notebook metadata
1244 * :ghissue:`5050`: Notebook cells truncated with Firefox
1244 * :ghissue:`5050`: Notebook cells truncated with Firefox
1245 * :ghissue:`4839`: Error in Session.send_raw()
1245 * :ghissue:`4839`: Error in Session.send_raw()
1246 * :ghissue:`5188`: New events system
1246 * :ghissue:`5188`: New events system
1247 * :ghissue:`5076`: Refactor keyboard handling
1247 * :ghissue:`5076`: Refactor keyboard handling
1248 * :ghissue:`4886`: Refactor and consolidate different keyboard logic in JavaScript code
1248 * :ghissue:`4886`: Refactor and consolidate different keyboard logic in JavaScript code
1249 * :ghissue:`5002`: the green cell border moving forever in Chrome, when there are many code cells.
1249 * :ghissue:`5002`: the green cell border moving forever in Chrome, when there are many code cells.
1250 * :ghissue:`5259`: Codemirror still active in command mode
1250 * :ghissue:`5259`: Codemirror still active in command mode
1251 * :ghissue:`5219`: Output images appear as small thumbnails (Notebook)
1251 * :ghissue:`5219`: Output images appear as small thumbnails (Notebook)
1252 * :ghissue:`4829`: Not able to connect qtconsole in Windows 8
1252 * :ghissue:`4829`: Not able to connect qtconsole in Windows 8
1253 * :ghissue:`5152`: Hide __pycache__ in dashboard directory list
1253 * :ghissue:`5152`: Hide __pycache__ in dashboard directory list
1254 * :ghissue:`5151`: Case-insesitive sort for dashboard list
1254 * :ghissue:`5151`: Case-insesitive sort for dashboard list
1255 * :ghissue:`4603`: Warn when overwriting a notebook with upload
1255 * :ghissue:`4603`: Warn when overwriting a notebook with upload
1256 * :ghissue:`4895`: Improvements to %run completions
1256 * :ghissue:`4895`: Improvements to %run completions
1257 * :ghissue:`3459`: Filename completion when run script with %run
1257 * :ghissue:`3459`: Filename completion when run script with %run
1258 * :ghissue:`5225`: Add JavaScript to nbconvert HTML display priority
1258 * :ghissue:`5225`: Add JavaScript to nbconvert HTML display priority
1259 * :ghissue:`5034`: Audit the places where we call `.html(something)`
1259 * :ghissue:`5034`: Audit the places where we call `.html(something)`
1260 * :ghissue:`5094`: Dancing cells in notebook
1260 * :ghissue:`5094`: Dancing cells in notebook
1261 * :ghissue:`4999`: Notebook focus effects
1261 * :ghissue:`4999`: Notebook focus effects
1262 * :ghissue:`5149`: Clicking on a TextBoxWidget in FF completely breaks dual mode.
1262 * :ghissue:`5149`: Clicking on a TextBoxWidget in FF completely breaks dual mode.
1263 * :ghissue:`5207`: Children fire event
1263 * :ghissue:`5207`: Children fire event
1264 * :ghissue:`5227`: display_method of objects with custom __getattr__
1264 * :ghissue:`5227`: display_method of objects with custom __getattr__
1265 * :ghissue:`5236`: Cursor keys do not work to leave Markdown cell while it's being edited
1265 * :ghissue:`5236`: Cursor keys do not work to leave Markdown cell while it's being edited
1266 * :ghissue:`5205`: Use CTuple traitlet for Widget children
1266 * :ghissue:`5205`: Use CTuple traitlet for Widget children
1267 * :ghissue:`5230`: notebook rename does not respect url prefix
1267 * :ghissue:`5230`: notebook rename does not respect url prefix
1268 * :ghissue:`5218`: Test failures with Python 3 and enabled warnings
1268 * :ghissue:`5218`: Test failures with Python 3 and enabled warnings
1269 * :ghissue:`5115`: Page Breaks for Print Preview Broken by display: flex - Simple CSS Fix
1269 * :ghissue:`5115`: Page Breaks for Print Preview Broken by display: flex - Simple CSS Fix
1270 * :ghissue:`5024`: Make nbconvert HTML output smart about page breaking
1270 * :ghissue:`5024`: Make nbconvert HTML output smart about page breaking
1271 * :ghissue:`4985`: Add automatic Closebrackets function to Codemirror.
1271 * :ghissue:`4985`: Add automatic Closebrackets function to Codemirror.
1272 * :ghissue:`5184`: print '\xa' crashes the interactive shell
1272 * :ghissue:`5184`: print '\xa' crashes the interactive shell
1273 * :ghissue:`5214`: Downloading notebook as Python (.py) fails
1273 * :ghissue:`5214`: Downloading notebook as Python (.py) fails
1274 * :ghissue:`5211`: AttributeError: 'module' object has no attribute '_outputfile'
1274 * :ghissue:`5211`: AttributeError: 'module' object has no attribute '_outputfile'
1275 * :ghissue:`5206`: [CSS?] Inconsistencies in nbconvert divs and IPython Notebook divs?
1275 * :ghissue:`5206`: [CSS?] Inconsistencies in nbconvert divs and IPython Notebook divs?
1276 * :ghissue:`5201`: node != nodejs within Debian packages
1276 * :ghissue:`5201`: node != nodejs within Debian packages
1277 * :ghissue:`5112`: band-aid for completion
1277 * :ghissue:`5112`: band-aid for completion
1278 * :ghissue:`4860`: Completer As-You-Type Broken
1278 * :ghissue:`4860`: Completer As-You-Type Broken
1279 * :ghissue:`5116`: reorganize who knows what about paths
1279 * :ghissue:`5116`: reorganize who knows what about paths
1280 * :ghissue:`4973`: Adding security.js with 1st attempt at is_safe
1280 * :ghissue:`4973`: Adding security.js with 1st attempt at is_safe
1281 * :ghissue:`5164`: test_oinspect.test_calltip_builtin failure with python3.4
1281 * :ghissue:`5164`: test_oinspect.test_calltip_builtin failure with python3.4
1282 * :ghissue:`5127`: Widgets: skip intermediate callbacks during throttling
1282 * :ghissue:`5127`: Widgets: skip intermediate callbacks during throttling
1283 * :ghissue:`5013`: Widget alignment differs between FF and Chrome
1283 * :ghissue:`5013`: Widget alignment differs between FF and Chrome
1284 * :ghissue:`5141`: tornado error static file
1284 * :ghissue:`5141`: tornado error static file
1285 * :ghissue:`5160`: TemporaryWorkingDirectory incompatible with python3.4
1285 * :ghissue:`5160`: TemporaryWorkingDirectory incompatible with python3.4
1286 * :ghissue:`5140`: WIP: %kernels magic
1286 * :ghissue:`5140`: WIP: %kernels magic
1287 * :ghissue:`4987`: Widget lifecycle problems
1287 * :ghissue:`4987`: Widget lifecycle problems
1288 * :ghissue:`5129`: UCS package break latex export on non-ascii
1288 * :ghissue:`5129`: UCS package break latex export on non-ascii
1289 * :ghissue:`4986`: Cell horizontal scrollbar is missing in FF but not in Chrome
1289 * :ghissue:`4986`: Cell horizontal scrollbar is missing in FF but not in Chrome
1290 * :ghissue:`4685`: nbconvert ignores image size metadata
1290 * :ghissue:`4685`: nbconvert ignores image size metadata
1291 * :ghissue:`5155`: Notebook logout button does not work (source typo)
1291 * :ghissue:`5155`: Notebook logout button does not work (source typo)
1292 * :ghissue:`2678`: Ctrl-m keyboard shortcut clash on Chrome OS
1292 * :ghissue:`2678`: Ctrl-m keyboard shortcut clash on Chrome OS
1293 * :ghissue:`5113`: ButtonWidget without caption wrong height.
1293 * :ghissue:`5113`: ButtonWidget without caption wrong height.
1294 * :ghissue:`4778`: add APIs for installing notebook extensions
1294 * :ghissue:`4778`: add APIs for installing notebook extensions
1295 * :ghissue:`5046`: python setup.py failed vs git submodule update worked
1295 * :ghissue:`5046`: python setup.py failed vs git submodule update worked
1296 * :ghissue:`4925`: Notebook manager api fixes
1296 * :ghissue:`4925`: Notebook manager api fixes
1297 * :ghissue:`5073`: Cannot align widgets horizontally in the notebook
1297 * :ghissue:`5073`: Cannot align widgets horizontally in the notebook
1298 * :ghissue:`4996`: require print_method to be a bound method
1298 * :ghissue:`4996`: require print_method to be a bound method
1299 * :ghissue:`4990`: _repr_html_ exception reporting corner case when using type(foo)
1299 * :ghissue:`4990`: _repr_html_ exception reporting corner case when using type(foo)
1300 * :ghissue:`5099`: Notebook: Changing base_project_url results in failed WebSockets call
1300 * :ghissue:`5099`: Notebook: Changing base_project_url results in failed WebSockets call
1301 * :ghissue:`5096`: Client.map is not fault tolerant
1301 * :ghissue:`5096`: Client.map is not fault tolerant
1302 * :ghissue:`4997`: Inconsistent %matplotlib qt behavior
1302 * :ghissue:`4997`: Inconsistent %matplotlib qt behavior
1303 * :ghissue:`5041`: Remove more .html(...) calls.
1303 * :ghissue:`5041`: Remove more .html(...) calls.
1304 * :ghissue:`5078`: Updating JS tests README.md
1304 * :ghissue:`5078`: Updating JS tests README.md
1305 * :ghissue:`4977`: ensure scp destination directories exist (with mkdir -p)
1305 * :ghissue:`4977`: ensure scp destination directories exist (with mkdir -p)
1306 * :ghissue:`3411`: ipython parallel: scp failure.
1306 * :ghissue:`3411`: ipython parallel: scp failure.
1307 * :ghissue:`5064`: Errors during interact display at the terminal, not anywhere in the notebook
1307 * :ghissue:`5064`: Errors during interact display at the terminal, not anywhere in the notebook
1308 * :ghissue:`4921`: Add PDF formatter and handling
1308 * :ghissue:`4921`: Add PDF formatter and handling
1309 * :ghissue:`4920`: Adding PDFFormatter and kernel side handling of PDF display data
1309 * :ghissue:`4920`: Adding PDFFormatter and kernel side handling of PDF display data
1310 * :ghissue:`5048`: Add edit/command mode indicator
1310 * :ghissue:`5048`: Add edit/command mode indicator
1311 * :ghissue:`4889`: Add UI element for indicating command/edit modes
1311 * :ghissue:`4889`: Add UI element for indicating command/edit modes
1312 * :ghissue:`5052`: Add q to toggle the pager.
1312 * :ghissue:`5052`: Add q to toggle the pager.
1313 * :ghissue:`5000`: Closing pager with keyboard in modal UI
1313 * :ghissue:`5000`: Closing pager with keyboard in modal UI
1314 * :ghissue:`5069`: Box model changes broke the Keyboard Shortcuts help modal
1314 * :ghissue:`5069`: Box model changes broke the Keyboard Shortcuts help modal
1315 * :ghissue:`4960`: Interact/Interactive for widget
1315 * :ghissue:`4960`: Interact/Interactive for widget
1316 * :ghissue:`4883`: Implement interact/interactive for widgets
1316 * :ghissue:`4883`: Implement interact/interactive for widgets
1317 * :ghissue:`5038`: Fix multiple press keyboard events
1317 * :ghissue:`5038`: Fix multiple press keyboard events
1318 * :ghissue:`5054`: UnicodeDecodeError: 'ascii' codec can't decode byte 0xc6 in position 1: ordinal not in range(128)
1318 * :ghissue:`5054`: UnicodeDecodeError: 'ascii' codec can't decode byte 0xc6 in position 1: ordinal not in range(128)
1319 * :ghissue:`5031`: Bug during integration of IPython console in Qt application
1319 * :ghissue:`5031`: Bug during integration of IPython console in Qt application
1320 * :ghissue:`5057`: iopubwatcher.py example is broken.
1320 * :ghissue:`5057`: iopubwatcher.py example is broken.
1321 * :ghissue:`4747`: Add event for output_area adding an output
1321 * :ghissue:`4747`: Add event for output_area adding an output
1322 * :ghissue:`5001`: Add directory navigation to dashboard
1322 * :ghissue:`5001`: Add directory navigation to dashboard
1323 * :ghissue:`5016`: Help menu external-link icons break layout in FF
1323 * :ghissue:`5016`: Help menu external-link icons break layout in FF
1324 * :ghissue:`4885`: Modal UI behavior changes
1324 * :ghissue:`4885`: Modal UI behavior changes
1325 * :ghissue:`5009`: notebook signatures don't work
1325 * :ghissue:`5009`: notebook signatures don't work
1326 * :ghissue:`4975`: setup.py changes for 2.0
1326 * :ghissue:`4975`: setup.py changes for 2.0
1327 * :ghissue:`4774`: emit event on appended element on dom
1327 * :ghissue:`4774`: emit event on appended element on dom
1328 * :ghissue:`5020`: Python Lists translated to javascript objects in widgets
1328 * :ghissue:`5020`: Python Lists translated to javascript objects in widgets
1329 * :ghissue:`5003`: Fix pretty reprs of super() objects
1329 * :ghissue:`5003`: Fix pretty reprs of super() objects
1330 * :ghissue:`5012`: Make `SelectionWidget.values` a dict
1330 * :ghissue:`5012`: Make `SelectionWidget.values` a dict
1331 * :ghissue:`4961`: Bug when constructing a selection widget with both values and labels
1331 * :ghissue:`4961`: Bug when constructing a selection widget with both values and labels
1332 * :ghissue:`4283`: A `<` in a markdown cell strips cell content when converting to latex
1332 * :ghissue:`4283`: A `<` in a markdown cell strips cell content when converting to latex
1333 * :ghissue:`4006`: iptest IPython broken
1333 * :ghissue:`4006`: iptest IPython broken
1334 * :ghissue:`4251`: & escaped to &amp; in tex ?
1334 * :ghissue:`4251`: & escaped to &amp; in tex ?
1335 * :ghissue:`5027`: pin lessc to 1.4
1335 * :ghissue:`5027`: pin lessc to 1.4
1336 * :ghissue:`4323`: Take 2: citation2latex filter (using HTMLParser)
1336 * :ghissue:`4323`: Take 2: citation2latex filter (using HTMLParser)
1337 * :ghissue:`4196`: Printing notebook from browser gives 1-page truncated output
1337 * :ghissue:`4196`: Printing notebook from browser gives 1-page truncated output
1338 * :ghissue:`4842`: more subtle kernel indicator
1338 * :ghissue:`4842`: more subtle kernel indicator
1339 * :ghissue:`4057`: No path to notebook examples from Help menu
1339 * :ghissue:`4057`: No path to notebook examples from Help menu
1340 * :ghissue:`5015`: don't write cell.trusted to disk
1340 * :ghissue:`5015`: don't write cell.trusted to disk
1341 * :ghissue:`4617`: Changed url link in Help dropdown menu.
1341 * :ghissue:`4617`: Changed url link in Help dropdown menu.
1342 * :ghissue:`4976`: Container widget layout broken on Firefox
1342 * :ghissue:`4976`: Container widget layout broken on Firefox
1343 * :ghissue:`4981`: Vertical slider layout broken
1343 * :ghissue:`4981`: Vertical slider layout broken
1344 * :ghissue:`4793`: Message spec changes related to `clear_output`
1344 * :ghissue:`4793`: Message spec changes related to `clear_output`
1345 * :ghissue:`4982`: Live readout for slider widgets
1345 * :ghissue:`4982`: Live readout for slider widgets
1346 * :ghissue:`4813`: make help menu a template
1346 * :ghissue:`4813`: make help menu a template
1347 * :ghissue:`4989`: Filename tab completion completely broken
1347 * :ghissue:`4989`: Filename tab completion completely broken
1348 * :ghissue:`1380`: Tab should insert 4 spaces in # comment lines
1348 * :ghissue:`1380`: Tab should insert 4 spaces in # comment lines
1349 * :ghissue:`2888`: spaces vs tabs
1349 * :ghissue:`2888`: spaces vs tabs
1350 * :ghissue:`1193`: Allow resizing figures in notebook
1350 * :ghissue:`1193`: Allow resizing figures in notebook
1351 * :ghissue:`4504`: Allow input transformers to raise SyntaxError
1351 * :ghissue:`4504`: Allow input transformers to raise SyntaxError
1352 * :ghissue:`4697`: Problems with height after toggling header and toolbar...
1352 * :ghissue:`4697`: Problems with height after toggling header and toolbar...
1353 * :ghissue:`4951`: TextWidget to code cell command mode bug.
1353 * :ghissue:`4951`: TextWidget to code cell command mode bug.
1354 * :ghissue:`4809`: Arbitrary scrolling (jumping) in clicks in modal UI for notebook
1354 * :ghissue:`4809`: Arbitrary scrolling (jumping) in clicks in modal UI for notebook
1355 * :ghissue:`4971`: Fixing issues with js tests
1355 * :ghissue:`4971`: Fixing issues with js tests
1356 * :ghissue:`4972`: Work around problem in doctest discovery in Python 3.4 with PyQt
1356 * :ghissue:`4972`: Work around problem in doctest discovery in Python 3.4 with PyQt
1357 * :ghissue:`4892`: IPython.qt test failure with python3.4
1357 * :ghissue:`4892`: IPython.qt test failure with python3.4
1358 * :ghissue:`4863`: BUG: cannot create an OBJECT array from memory buffer
1358 * :ghissue:`4863`: BUG: cannot create an OBJECT array from memory buffer
1359 * :ghissue:`4704`: Subcommand `profile` ignores --ipython-dir
1359 * :ghissue:`4704`: Subcommand `profile` ignores --ipython-dir
1360 * :ghissue:`4845`: Add Origin Checking.
1360 * :ghissue:`4845`: Add Origin Checking.
1361 * :ghissue:`4870`: ipython_directive, report except/warn in block and add :okexcept: :okwarning: options to suppress
1361 * :ghissue:`4870`: ipython_directive, report except/warn in block and add :okexcept: :okwarning: options to suppress
1362 * :ghissue:`4956`: Shift-Enter does not move to next cell
1362 * :ghissue:`4956`: Shift-Enter does not move to next cell
1363 * :ghissue:`4662`: Menu cleanup
1363 * :ghissue:`4662`: Menu cleanup
1364 * :ghissue:`4824`: sign notebooks
1364 * :ghissue:`4824`: sign notebooks
1365 * :ghissue:`4848`: avoid import of nearby temporary with %edit
1365 * :ghissue:`4848`: avoid import of nearby temporary with %edit
1366 * :ghissue:`4731`: %edit files mistakenly import modules in /tmp
1366 * :ghissue:`4731`: %edit files mistakenly import modules in /tmp
1367 * :ghissue:`4950`: Two fixes for file upload related bugs
1367 * :ghissue:`4950`: Two fixes for file upload related bugs
1368 * :ghissue:`4871`: Notebook upload fails after Delete
1368 * :ghissue:`4871`: Notebook upload fails after Delete
1369 * :ghissue:`4825`: File Upload URL set incorrectly
1369 * :ghissue:`4825`: File Upload URL set incorrectly
1370 * :ghissue:`3867`: display.FileLinks should work in the exported html verion of a notebook
1370 * :ghissue:`3867`: display.FileLinks should work in the exported html verion of a notebook
1371 * :ghissue:`4948`: reveal: ipython css overrides reveal themes
1371 * :ghissue:`4948`: reveal: ipython css overrides reveal themes
1372 * :ghissue:`4947`: reveal: slides that are too big?
1372 * :ghissue:`4947`: reveal: slides that are too big?
1373 * :ghissue:`4051`: Test failures with Python 3 and enabled warnings
1373 * :ghissue:`4051`: Test failures with Python 3 and enabled warnings
1374 * :ghissue:`3633`: outstanding issues over in ipython/nbconvert repo
1374 * :ghissue:`3633`: outstanding issues over in ipython/nbconvert repo
1375 * :ghissue:`4087`: Sympy printing in the example notebook
1375 * :ghissue:`4087`: Sympy printing in the example notebook
1376 * :ghissue:`4627`: Document various QtConsole embedding approaches.
1376 * :ghissue:`4627`: Document various QtConsole embedding approaches.
1377 * :ghissue:`4849`: Various unicode fixes (mostly on Windows)
1377 * :ghissue:`4849`: Various unicode fixes (mostly on Windows)
1378 * :ghissue:`3653`: autocompletion in "from package import <tab>"
1378 * :ghissue:`3653`: autocompletion in "from package import <tab>"
1379 * :ghissue:`4583`: overwrite? prompt gets EOFError in 2 process
1379 * :ghissue:`4583`: overwrite? prompt gets EOFError in 2 process
1380 * :ghissue:`4807`: Correct handling of ansi colour codes when nbconverting to latex
1380 * :ghissue:`4807`: Correct handling of ansi colour codes when nbconverting to latex
1381 * :ghissue:`4611`: Document how to compile .less files in dev docs.
1381 * :ghissue:`4611`: Document how to compile .less files in dev docs.
1382 * :ghissue:`4618`: "Editor Shortcuts" link is broken in help menu dropdown notebook
1382 * :ghissue:`4618`: "Editor Shortcuts" link is broken in help menu dropdown notebook
1383 * :ghissue:`4522`: DeprecationWarning: the sets module is deprecated
1383 * :ghissue:`4522`: DeprecationWarning: the sets module is deprecated
1384 * :ghissue:`4368`: No symlink from ipython to ipython3 when inside a python3 virtualenv
1384 * :ghissue:`4368`: No symlink from ipython to ipython3 when inside a python3 virtualenv
1385 * :ghissue:`4234`: Math without $$ doesn't show up when converted to slides
1385 * :ghissue:`4234`: Math without $$ doesn't show up when converted to slides
1386 * :ghissue:`4194`: config.TerminalIPythonApp.nosep does not work
1386 * :ghissue:`4194`: config.TerminalIPythonApp.nosep does not work
1387 * :ghissue:`1491`: prefilter not called for multi-line notebook cells
1387 * :ghissue:`1491`: prefilter not called for multi-line notebook cells
1388 * :ghissue:`4001`: Windows IPython executable /scripts/ipython not working
1388 * :ghissue:`4001`: Windows IPython executable /scripts/ipython not working
1389 * :ghissue:`3959`: think more carefully about text wrapping in nbconvert
1389 * :ghissue:`3959`: think more carefully about text wrapping in nbconvert
1390 * :ghissue:`4907`: Test for traceback depth fails on Windows
1390 * :ghissue:`4907`: Test for traceback depth fails on Windows
1391 * :ghissue:`4906`: Test for IPython.embed() fails on Windows
1391 * :ghissue:`4906`: Test for IPython.embed() fails on Windows
1392 * :ghissue:`4912`: Skip some Windows io failures
1392 * :ghissue:`4912`: Skip some Windows io failures
1393 * :ghissue:`3700`: stdout/stderr should be flushed printing exception output...
1393 * :ghissue:`3700`: stdout/stderr should be flushed printing exception output...
1394 * :ghissue:`1181`: greedy completer bug in terminal console
1394 * :ghissue:`1181`: greedy completer bug in terminal console
1395 * :ghissue:`2032`: check for a few places we should be using DEFAULT_ENCODING
1395 * :ghissue:`2032`: check for a few places we should be using DEFAULT_ENCODING
1396 * :ghissue:`4882`: Too many files open when starting and stopping kernel repeatedly
1396 * :ghissue:`4882`: Too many files open when starting and stopping kernel repeatedly
1397 * :ghissue:`4880`: set profile name from profile_dir
1397 * :ghissue:`4880`: set profile name from profile_dir
1398 * :ghissue:`4238`: parallel.Client() not using profile that notebook was run with?
1398 * :ghissue:`4238`: parallel.Client() not using profile that notebook was run with?
1399 * :ghissue:`4853`: fix setting image height/width from metadata
1399 * :ghissue:`4853`: fix setting image height/width from metadata
1400 * :ghissue:`4786`: Reduce spacing of heading cells
1400 * :ghissue:`4786`: Reduce spacing of heading cells
1401 * :ghissue:`4680`: Minimal pandoc version warning
1401 * :ghissue:`4680`: Minimal pandoc version warning
1402 * :ghissue:`3707`: nbconvert: Remove IPython magic commands from --format="python" output
1402 * :ghissue:`3707`: nbconvert: Remove IPython magic commands from --format="python" output
1403 * :ghissue:`4130`: PDF figures as links from png or svg figures
1403 * :ghissue:`4130`: PDF figures as links from png or svg figures
1404 * :ghissue:`3919`: Allow --profile to be passed a dir.
1404 * :ghissue:`3919`: Allow --profile to be passed a dir.
1405 * :ghissue:`2136`: Handle hard newlines in pretty printer
1405 * :ghissue:`2136`: Handle hard newlines in pretty printer
1406 * :ghissue:`4790`: Notebook modal UI: "merge cell below" key binding, `shift+=`, does not work with some keyboard layouts
1406 * :ghissue:`4790`: Notebook modal UI: "merge cell below" key binding, `shift+=`, does not work with some keyboard layouts
1407 * :ghissue:`4884`: Keyboard shortcut changes
1407 * :ghissue:`4884`: Keyboard shortcut changes
1408 * :ghissue:`1184`: slow handling of keyboard input
1408 * :ghissue:`1184`: slow handling of keyboard input
1409 * :ghissue:`4913`: Mathjax, Markdown, tex, env* and italic
1409 * :ghissue:`4913`: Mathjax, Markdown, tex, env* and italic
1410 * :ghissue:`3972`: nbconvert: Template output testing
1410 * :ghissue:`3972`: nbconvert: Template output testing
1411 * :ghissue:`4903`: use https for all embeds
1411 * :ghissue:`4903`: use https for all embeds
1412 * :ghissue:`4874`: --debug does not work if you set .kernel_cmd
1412 * :ghissue:`4874`: --debug does not work if you set .kernel_cmd
1413 * :ghissue:`4679`: JPG compression for inline pylab
1413 * :ghissue:`4679`: JPG compression for inline pylab
1414 * :ghissue:`4708`: Fix indent and center
1414 * :ghissue:`4708`: Fix indent and center
1415 * :ghissue:`4789`: fix IPython.embed
1415 * :ghissue:`4789`: fix IPython.embed
1416 * :ghissue:`4759`: Application._load_config_files log parameter default fails
1416 * :ghissue:`4759`: Application._load_config_files log parameter default fails
1417 * :ghissue:`3153`: docs / file menu: explain how to exit the notebook
1417 * :ghissue:`3153`: docs / file menu: explain how to exit the notebook
1418 * :ghissue:`4791`: Did updates to ipython_directive bork support for cython magic snippets?
1418 * :ghissue:`4791`: Did updates to ipython_directive bork support for cython magic snippets?
1419 * :ghissue:`4385`: "Part 4 - Markdown Cells.ipynb" nbviewer example seems not well referenced in current online documentation page https://ipython.org/ipython-doc/stable/interactive/notebook.htm
1419 * :ghissue:`4385`: "Part 4 - Markdown Cells.ipynb" nbviewer example seems not well referenced in current online documentation page https://ipython.org/ipython-doc/stable/interactive/notebook.htm
1420 * :ghissue:`4655`: prefer marked to pandoc for markdown2html
1420 * :ghissue:`4655`: prefer marked to pandoc for markdown2html
1421 * :ghissue:`3441`: Fix focus related problems in the notebook
1421 * :ghissue:`3441`: Fix focus related problems in the notebook
1422 * :ghissue:`3402`: Feature Request: Save As (latex, html,..etc) as a menu option in Notebook rather than explicit need to invoke nbconvert
1422 * :ghissue:`3402`: Feature Request: Save As (latex, html,..etc) as a menu option in Notebook rather than explicit need to invoke nbconvert
1423 * :ghissue:`3224`: Revisit layout of notebook area
1423 * :ghissue:`3224`: Revisit layout of notebook area
1424 * :ghissue:`2746`: rerunning a cell with long output (exception) scrolls to much (html notebook)
1424 * :ghissue:`2746`: rerunning a cell with long output (exception) scrolls to much (html notebook)
1425 * :ghissue:`2667`: can't save opened notebook if accidentally delete the notebook in the dashboard
1425 * :ghissue:`2667`: can't save opened notebook if accidentally delete the notebook in the dashboard
1426 * :ghissue:`3026`: Reporting errors from _repr_<type>_ methods
1426 * :ghissue:`3026`: Reporting errors from _repr_<type>_ methods
1427 * :ghissue:`1844`: Notebook does not exist and permalinks
1427 * :ghissue:`1844`: Notebook does not exist and permalinks
1428 * :ghissue:`2450`: [closed PR] Prevent jumping of window to input when output is clicked.
1428 * :ghissue:`2450`: [closed PR] Prevent jumping of window to input when output is clicked.
1429 * :ghissue:`3166`: IPEP 16: Notebook multi directory dashboard and URL mapping
1429 * :ghissue:`3166`: IPEP 16: Notebook multi directory dashboard and URL mapping
1430 * :ghissue:`3691`: Slight misalignment of Notebook menu bar with focus box
1430 * :ghissue:`3691`: Slight misalignment of Notebook menu bar with focus box
1431 * :ghissue:`4875`: Empty tooltip with `object_found = false` still being shown
1431 * :ghissue:`4875`: Empty tooltip with `object_found = false` still being shown
1432 * :ghissue:`4432`: The SSL cert for the MathJax CDN is invalid and URL is not protocol agnostic
1432 * :ghissue:`4432`: The SSL cert for the MathJax CDN is invalid and URL is not protocol agnostic
1433 * :ghissue:`2633`: Help text should leave current cell active
1433 * :ghissue:`2633`: Help text should leave current cell active
1434 * :ghissue:`3976`: DOC: Pandas link on the notebook help menu?
1434 * :ghissue:`3976`: DOC: Pandas link on the notebook help menu?
1435 * :ghissue:`4082`: /new handler redirect cached by browser
1435 * :ghissue:`4082`: /new handler redirect cached by browser
1436 * :ghissue:`4298`: Slow ipython --pylab and ipython notebook startup
1436 * :ghissue:`4298`: Slow ipython --pylab and ipython notebook startup
1437 * :ghissue:`4545`: %store magic not working
1437 * :ghissue:`4545`: %store magic not working
1438 * :ghissue:`4610`: toolbar UI enhancements
1438 * :ghissue:`4610`: toolbar UI enhancements
1439 * :ghissue:`4782`: New modal UI
1439 * :ghissue:`4782`: New modal UI
1440 * :ghissue:`4732`: Accents in notebook names and in command-line (nbconvert)
1440 * :ghissue:`4732`: Accents in notebook names and in command-line (nbconvert)
1441 * :ghissue:`4752`: link broken in docs/examples
1441 * :ghissue:`4752`: link broken in docs/examples
1442 * :ghissue:`4835`: running ipython on python files adds an extra traceback frame
1442 * :ghissue:`4835`: running ipython on python files adds an extra traceback frame
1443 * :ghissue:`4792`: repr_html exception warning on qtconsole with pandas #4745
1443 * :ghissue:`4792`: repr_html exception warning on qtconsole with pandas #4745
1444 * :ghissue:`4834`: function tooltip issues
1444 * :ghissue:`4834`: function tooltip issues
1445 * :ghissue:`4808`: Docstrings in Notebook not displayed properly and introspection
1445 * :ghissue:`4808`: Docstrings in Notebook not displayed properly and introspection
1446 * :ghissue:`4846`: Remove some leftover traces of irunner
1446 * :ghissue:`4846`: Remove some leftover traces of irunner
1447 * :ghissue:`4810`: ipcluster bug in clean_logs flag
1447 * :ghissue:`4810`: ipcluster bug in clean_logs flag
1448 * :ghissue:`4812`: update CodeMirror for the notebook
1448 * :ghissue:`4812`: update CodeMirror for the notebook
1449 * :ghissue:`671`: add migration guide for old IPython config
1449 * :ghissue:`671`: add migration guide for old IPython config
1450 * :ghissue:`4783`: ipython 2dev under windows / (win)python 3.3 experiment
1450 * :ghissue:`4783`: ipython 2dev under windows / (win)python 3.3 experiment
1451 * :ghissue:`4772`: Notebook server info files
1451 * :ghissue:`4772`: Notebook server info files
1452 * :ghissue:`4765`: missing build script for highlight.js
1452 * :ghissue:`4765`: missing build script for highlight.js
1453 * :ghissue:`4787`: non-python kernels run python code with qtconsole
1453 * :ghissue:`4787`: non-python kernels run python code with qtconsole
1454 * :ghissue:`4703`: Math macro in jinja templates.
1454 * :ghissue:`4703`: Math macro in jinja templates.
1455 * :ghissue:`4595`: ipython notebook XSS vulnerable
1455 * :ghissue:`4595`: ipython notebook XSS vulnerable
1456 * :ghissue:`4776`: Manually document py3compat module.
1456 * :ghissue:`4776`: Manually document py3compat module.
1457 * :ghissue:`4686`: For-in loop on an array in cell.js
1457 * :ghissue:`4686`: For-in loop on an array in cell.js
1458 * :ghissue:`3605`: Modal UI
1458 * :ghissue:`3605`: Modal UI
1459 * :ghissue:`4769`: Ipython 2.0 will not startup on py27 on windows
1459 * :ghissue:`4769`: Ipython 2.0 will not startup on py27 on windows
1460 * :ghissue:`4482`: reveal.js converter not including CDN by default?
1460 * :ghissue:`4482`: reveal.js converter not including CDN by default?
1461 * :ghissue:`4761`: ipv6 address triggers cookie exception
1461 * :ghissue:`4761`: ipv6 address triggers cookie exception
1462 * :ghissue:`4580`: rename or remove %profile magic
1462 * :ghissue:`4580`: rename or remove %profile magic
1463 * :ghissue:`4643`: Docstring does not open properly
1463 * :ghissue:`4643`: Docstring does not open properly
1464 * :ghissue:`4714`: Static URLs are not auto-versioned
1464 * :ghissue:`4714`: Static URLs are not auto-versioned
1465 * :ghissue:`2573`: document code mirror keyboard shortcuts
1465 * :ghissue:`2573`: document code mirror keyboard shortcuts
1466 * :ghissue:`4717`: hang in parallel.Client when using SSHAgent
1466 * :ghissue:`4717`: hang in parallel.Client when using SSHAgent
1467 * :ghissue:`4544`: Clarify the requirement for pyreadline on Windows
1467 * :ghissue:`4544`: Clarify the requirement for pyreadline on Windows
1468 * :ghissue:`3451`: revisit REST /new handler to avoid systematic crawling.
1468 * :ghissue:`3451`: revisit REST /new handler to avoid systematic crawling.
1469 * :ghissue:`2922`: File => Save as '.py' saves magic as code
1469 * :ghissue:`2922`: File => Save as '.py' saves magic as code
1470 * :ghissue:`4728`: Copy/Paste stripping broken in version > 0.13.x in QTConsole
1470 * :ghissue:`4728`: Copy/Paste stripping broken in version > 0.13.x in QTConsole
1471 * :ghissue:`4539`: Nbconvert: Latex to PDF conversion fails on notebooks with accented letters
1471 * :ghissue:`4539`: Nbconvert: Latex to PDF conversion fails on notebooks with accented letters
1472 * :ghissue:`4721`: purge_results with jobid crashing - looking for insight
1472 * :ghissue:`4721`: purge_results with jobid crashing - looking for insight
1473 * :ghissue:`4620`: Notebook with ? in title defies autosave, renaming and deletion.
1473 * :ghissue:`4620`: Notebook with ? in title defies autosave, renaming and deletion.
1474 * :ghissue:`4574`: Hash character in notebook name breaks a lot of things
1474 * :ghissue:`4574`: Hash character in notebook name breaks a lot of things
1475 * :ghissue:`4709`: input_prefilter hook not called
1475 * :ghissue:`4709`: input_prefilter hook not called
1476 * :ghissue:`1680`: qtconsole should support --no-banner and custom banner
1476 * :ghissue:`1680`: qtconsole should support --no-banner and custom banner
1477 * :ghissue:`4689`: IOStream IP address configurable
1477 * :ghissue:`4689`: IOStream IP address configurable
1478 * :ghissue:`4698`: Missing "if __name__ == '__main__':" check in /usr/bin/ipython
1478 * :ghissue:`4698`: Missing "if __name__ == '__main__':" check in /usr/bin/ipython
1479 * :ghissue:`4191`: NBConvert: markdown inline and locally referenced files have incorrect file location for latex
1479 * :ghissue:`4191`: NBConvert: markdown inline and locally referenced files have incorrect file location for latex
1480 * :ghissue:`2865`: %%!? does not display the shell execute docstring
1480 * :ghissue:`2865`: %%!? does not display the shell execute docstring
1481 * :ghissue:`1551`: Notebook should be saved before printing
1481 * :ghissue:`1551`: Notebook should be saved before printing
1482 * :ghissue:`4612`: remove `Configurable.created` ?
1482 * :ghissue:`4612`: remove `Configurable.created` ?
1483 * :ghissue:`4629`: Lots of tests fail due to space in sys.executable
1483 * :ghissue:`4629`: Lots of tests fail due to space in sys.executable
1484 * :ghissue:`4644`: Fixed URLs for notebooks
1484 * :ghissue:`4644`: Fixed URLs for notebooks
1485 * :ghissue:`4621`: IPython 1.1.0 Qtconsole syntax highlighting highlights python 2 only built-ins when using python 3
1485 * :ghissue:`4621`: IPython 1.1.0 Qtconsole syntax highlighting highlights python 2 only built-ins when using python 3
1486 * :ghissue:`2923`: Move Delete Button Away from Save Button in the HTML notebook toolbar
1486 * :ghissue:`2923`: Move Delete Button Away from Save Button in the HTML notebook toolbar
1487 * :ghissue:`4615`: UnicodeDecodeError
1487 * :ghissue:`4615`: UnicodeDecodeError
1488 * :ghissue:`4431`: ipython slow in os x mavericks?
1488 * :ghissue:`4431`: ipython slow in os x mavericks?
1489 * :ghissue:`4538`: DOC: document how to change ipcontroller-engine.json in case controller was started with --ip="*"
1489 * :ghissue:`4538`: DOC: document how to change ipcontroller-engine.json in case controller was started with --ip="*"
1490 * :ghissue:`4551`: Serialize methods and closures
1490 * :ghissue:`4551`: Serialize methods and closures
1491 * :ghissue:`4081`: [Nbconvert][reveal] link to font awesome ?
1491 * :ghissue:`4081`: [Nbconvert][reveal] link to font awesome ?
1492 * :ghissue:`4602`: "ipcluster stop" fails after "ipcluster start --daemonize" using python3.3
1492 * :ghissue:`4602`: "ipcluster stop" fails after "ipcluster start --daemonize" using python3.3
1493 * :ghissue:`4578`: NBconvert fails with unicode errors when `--stdout` and file redirection is specified and HTML entities are present
1493 * :ghissue:`4578`: NBconvert fails with unicode errors when `--stdout` and file redirection is specified and HTML entities are present
1494 * :ghissue:`4600`: Renaming new notebook to an exist name silently deletes the old one
1494 * :ghissue:`4600`: Renaming new notebook to an exist name silently deletes the old one
1495 * :ghissue:`4598`: Qtconsole docstring pop-up fails on method containing defaulted enum argument
1495 * :ghissue:`4598`: Qtconsole docstring pop-up fails on method containing defaulted enum argument
1496 * :ghissue:`951`: Remove Tornado monkeypatch
1496 * :ghissue:`951`: Remove Tornado monkeypatch
1497 * :ghissue:`4564`: Notebook save failure
1497 * :ghissue:`4564`: Notebook save failure
1498 * :ghissue:`4562`: nbconvert: Default encoding problem on OS X
1498 * :ghissue:`4562`: nbconvert: Default encoding problem on OS X
1499 * :ghissue:`1675`: add file_to_run=file.ipynb capability to the notebook
1499 * :ghissue:`1675`: add file_to_run=file.ipynb capability to the notebook
1500 * :ghissue:`4516`: `ipython console` doesn't send a `shutdown_request`
1500 * :ghissue:`4516`: `ipython console` doesn't send a `shutdown_request`
1501 * :ghissue:`3043`: can't restart pdb session in ipython
1501 * :ghissue:`3043`: can't restart pdb session in ipython
1502 * :ghissue:`4524`: Fix bug with non ascii passwords in notebook login
1502 * :ghissue:`4524`: Fix bug with non ascii passwords in notebook login
1503 * :ghissue:`1866`: problems rendering an SVG?
1503 * :ghissue:`1866`: problems rendering an SVG?
1504 * :ghissue:`4520`: unicode error when trying Audio('data/Bach Cello Suite #3.wav')
1504 * :ghissue:`4520`: unicode error when trying Audio('data/Bach Cello Suite #3.wav')
1505 * :ghissue:`4493`: Qtconsole cannot print an ISO8601 date at nanosecond precision
1505 * :ghissue:`4493`: Qtconsole cannot print an ISO8601 date at nanosecond precision
1506 * :ghissue:`4502`: intermittent parallel test failure test_purge_everything
1506 * :ghissue:`4502`: intermittent parallel test failure test_purge_everything
1507 * :ghissue:`4495`: firefox 25.0: notebooks report "Notebook save failed", .py script save fails, but .ipynb save succeeds
1507 * :ghissue:`4495`: firefox 25.0: notebooks report "Notebook save failed", .py script save fails, but .ipynb save succeeds
1508 * :ghissue:`4245`: nbconvert latex: code highlighting causes error
1508 * :ghissue:`4245`: nbconvert latex: code highlighting causes error
1509 * :ghissue:`4486`: Test for whether inside virtualenv does not work if directory is symlinked
1509 * :ghissue:`4486`: Test for whether inside virtualenv does not work if directory is symlinked
1510 * :ghissue:`4485`: Incorrect info in "Messaging in IPython" documentation.
1510 * :ghissue:`4485`: Incorrect info in "Messaging in IPython" documentation.
1511 * :ghissue:`4447`: Ipcontroller broken in current HEAD on windows
1511 * :ghissue:`4447`: Ipcontroller broken in current HEAD on windows
1512 * :ghissue:`4241`: Audio display object
1512 * :ghissue:`4241`: Audio display object
1513 * :ghissue:`4463`: Error on empty c.Session.key
1513 * :ghissue:`4463`: Error on empty c.Session.key
1514 * :ghissue:`4454`: UnicodeDecodeError when starting Ipython notebook on a directory containing a file with a non-ascii character
1514 * :ghissue:`4454`: UnicodeDecodeError when starting Ipython notebook on a directory containing a file with a non-ascii character
1515 * :ghissue:`3801`: Autocompletion: Fix issue #3723 -- ordering of completions for magic commands and variables with same name
1515 * :ghissue:`3801`: Autocompletion: Fix issue #3723 -- ordering of completions for magic commands and variables with same name
1516 * :ghissue:`3723`: Code completion: 'matplotlib' and '%matplotlib'
1516 * :ghissue:`3723`: Code completion: 'matplotlib' and '%matplotlib'
1517 * :ghissue:`4396`: Always checkpoint al least once ?
1517 * :ghissue:`4396`: Always checkpoint al least once ?
1518 * :ghissue:`2524`: [Notebook] Clear kernel queue
1518 * :ghissue:`2524`: [Notebook] Clear kernel queue
1519 * :ghissue:`2292`: Client side tests for the notebook
1519 * :ghissue:`2292`: Client side tests for the notebook
1520 * :ghissue:`4424`: Dealing with images in multidirectory environment
1520 * :ghissue:`4424`: Dealing with images in multidirectory environment
1521 * :ghissue:`4388`: Make writing configurable magics easier
1521 * :ghissue:`4388`: Make writing configurable magics easier
1522 * :ghissue:`852`: Notebook should be saved before downloading
1522 * :ghissue:`852`: Notebook should be saved before downloading
1523 * :ghissue:`3708`: ipython profile locate should also work
1523 * :ghissue:`3708`: ipython profile locate should also work
1524 * :ghissue:`1349`: `?` may generate hundreds of cell
1524 * :ghissue:`1349`: `?` may generate hundreds of cell
1525 * :ghissue:`4381`: Using hasattr for trait_names instead of just looking for it directly/using __dir__?
1525 * :ghissue:`4381`: Using hasattr for trait_names instead of just looking for it directly/using __dir__?
1526 * :ghissue:`4361`: Crash Ultratraceback/ session history
1526 * :ghissue:`4361`: Crash Ultratraceback/ session history
1527 * :ghissue:`3044`: IPython notebook autocomplete for filename string converts multiple spaces to a single space
1527 * :ghissue:`3044`: IPython notebook autocomplete for filename string converts multiple spaces to a single space
1528 * :ghissue:`3346`: Up arrow history search shows duplicates in Qtconsole
1528 * :ghissue:`3346`: Up arrow history search shows duplicates in Qtconsole
1529 * :ghissue:`3496`: Fix import errors when running tests from the source directory
1529 * :ghissue:`3496`: Fix import errors when running tests from the source directory
1530 * :ghissue:`4114`: If default profile doesn't exist, can't install mathjax to any location
1530 * :ghissue:`4114`: If default profile doesn't exist, can't install mathjax to any location
1531 * :ghissue:`4335`: TestPylabSwitch.test_qt fails
1531 * :ghissue:`4335`: TestPylabSwitch.test_qt fails
1532 * :ghissue:`4291`: serve like option for nbconvert --to latex
1532 * :ghissue:`4291`: serve like option for nbconvert --to latex
1533 * :ghissue:`1824`: Exception before prompting for password during ssh connection
1533 * :ghissue:`1824`: Exception before prompting for password during ssh connection
1534 * :ghissue:`4309`: Error in nbconvert - closing </code> tag is not inserted in HTML under some circumstances
1534 * :ghissue:`4309`: Error in nbconvert - closing </code> tag is not inserted in HTML under some circumstances
1535 * :ghissue:`4351`: /parallel/apps/launcher.py error
1535 * :ghissue:`4351`: /parallel/apps/launcher.py error
1536 * :ghissue:`3603`: Upcoming issues with nbconvert
1536 * :ghissue:`3603`: Upcoming issues with nbconvert
1537 * :ghissue:`4296`: sync_imports() fails in python 3.3
1537 * :ghissue:`4296`: sync_imports() fails in python 3.3
1538 * :ghissue:`4339`: local mathjax install doesn't work
1538 * :ghissue:`4339`: local mathjax install doesn't work
1539 * :ghissue:`4334`: NotebookApp.webapp_settings static_url_prefix causes crash
1539 * :ghissue:`4334`: NotebookApp.webapp_settings static_url_prefix causes crash
1540 * :ghissue:`4308`: Error when use "ipython notebook" in win7 64 with python2.7.3 64.
1540 * :ghissue:`4308`: Error when use "ipython notebook" in win7 64 with python2.7.3 64.
1541 * :ghissue:`4317`: Relative imports broken in the notebook (Windows)
1541 * :ghissue:`4317`: Relative imports broken in the notebook (Windows)
1542 * :ghissue:`3658`: Saving Notebook clears "Kernel Busy" status from the page and titlebar
1542 * :ghissue:`3658`: Saving Notebook clears "Kernel Busy" status from the page and titlebar
1543 * :ghissue:`4312`: Link broken on ipython-doc stable
1543 * :ghissue:`4312`: Link broken on ipython-doc stable
1544 * :ghissue:`1093`: Add boundary options to %load
1544 * :ghissue:`1093`: Add boundary options to %load
1545 * :ghissue:`3619`: Multi-dir webservice design
1545 * :ghissue:`3619`: Multi-dir webservice design
1546 * :ghissue:`4299`: Nbconvert, default_preprocessors to list of dotted name not list of obj
1546 * :ghissue:`4299`: Nbconvert, default_preprocessors to list of dotted name not list of obj
1547 * :ghissue:`3210`: IPython.parallel tests seem to hang on ShiningPanda
1547 * :ghissue:`3210`: IPython.parallel tests seem to hang on ShiningPanda
1548 * :ghissue:`4280`: MathJax Automatic Line Breaking
1548 * :ghissue:`4280`: MathJax Automatic Line Breaking
1549 * :ghissue:`4039`: Celltoolbar example issue
1549 * :ghissue:`4039`: Celltoolbar example issue
1550 * :ghissue:`4247`: nbconvert --to latex: error when converting greek letter
1550 * :ghissue:`4247`: nbconvert --to latex: error when converting greek letter
1551 * :ghissue:`4273`: %%capture not capturing rich objects like plots (IPython 1.1.0)
1551 * :ghissue:`4273`: %%capture not capturing rich objects like plots (IPython 1.1.0)
1552 * :ghissue:`3866`: Vertical offsets in LaTeX output for nbconvert
1552 * :ghissue:`3866`: Vertical offsets in LaTeX output for nbconvert
1553 * :ghissue:`3631`: xkcd mode for the IPython notebook
1553 * :ghissue:`3631`: xkcd mode for the IPython notebook
1554 * :ghissue:`4243`: Test exclusions not working on Windows
1554 * :ghissue:`4243`: Test exclusions not working on Windows
1555 * :ghissue:`4256`: IPython no longer handles unicode file names
1555 * :ghissue:`4256`: IPython no longer handles unicode file names
1556 * :ghissue:`3656`: Audio displayobject
1556 * :ghissue:`3656`: Audio displayobject
1557 * :ghissue:`4223`: Double output on Ctrl-enter-enter
1557 * :ghissue:`4223`: Double output on Ctrl-enter-enter
1558 * :ghissue:`4184`: nbconvert: use r pygmentize backend when highlighting "%%R" cells
1558 * :ghissue:`4184`: nbconvert: use r pygmentize backend when highlighting "%%R" cells
1559 * :ghissue:`3851`: Adds an explicit newline for pretty-printing.
1559 * :ghissue:`3851`: Adds an explicit newline for pretty-printing.
1560 * :ghissue:`3622`: Drop fakemodule
1560 * :ghissue:`3622`: Drop fakemodule
1561 * :ghissue:`4122`: Nbconvert [windows]: Inconsistent line endings in markdown cells exported to latex
1561 * :ghissue:`4122`: Nbconvert [windows]: Inconsistent line endings in markdown cells exported to latex
1562 * :ghissue:`3819`: nbconvert add extra blank line to code block on Windows.
1562 * :ghissue:`3819`: nbconvert add extra blank line to code block on Windows.
1563 * :ghissue:`4203`: remove spurious print statement from parallel annoted functions
1563 * :ghissue:`4203`: remove spurious print statement from parallel annoted functions
1564 * :ghissue:`4200`: Notebook: merging a heading cell and markdown cell cannot be undone
1564 * :ghissue:`4200`: Notebook: merging a heading cell and markdown cell cannot be undone
1565 * :ghissue:`3747`: ipynb -> ipynb transformer
1565 * :ghissue:`3747`: ipynb -> ipynb transformer
1566 * :ghissue:`4024`: nbconvert markdown issues
1566 * :ghissue:`4024`: nbconvert markdown issues
1567 * :ghissue:`3903`: on Windows, 'ipython3 nbconvert "C:/blabla/first_try.ipynb" --to slides' gives an unexpected result, and '--post serve' fails
1567 * :ghissue:`3903`: on Windows, 'ipython3 nbconvert "C:/blabla/first_try.ipynb" --to slides' gives an unexpected result, and '--post serve' fails
1568 * :ghissue:`4095`: Catch js error in append html in stream/pyerr
1568 * :ghissue:`4095`: Catch js error in append html in stream/pyerr
1569 * :ghissue:`1880`: Add parallelism to test_pr
1569 * :ghissue:`1880`: Add parallelism to test_pr
1570 * :ghissue:`4085`: nbconvert: Fix sphinx preprocessor date format string for Windows
1570 * :ghissue:`4085`: nbconvert: Fix sphinx preprocessor date format string for Windows
1571 * :ghissue:`4156`: Specifying --gui=tk at the command line
1571 * :ghissue:`4156`: Specifying --gui=tk at the command line
1572 * :ghissue:`4146`: Having to prepend 'files/' to markdown image paths is confusing
1572 * :ghissue:`4146`: Having to prepend 'files/' to markdown image paths is confusing
1573 * :ghissue:`3818`: nbconvert can't handle Heading with Chinese characters on Japanese Windows OS.
1573 * :ghissue:`3818`: nbconvert can't handle Heading with Chinese characters on Japanese Windows OS.
1574 * :ghissue:`4134`: multi-line parser fails on ''' in comment, qtconsole and notebook.
1574 * :ghissue:`4134`: multi-line parser fails on ''' in comment, qtconsole and notebook.
1575 * :ghissue:`3998`: sample custom.js needs to be updated
1575 * :ghissue:`3998`: sample custom.js needs to be updated
1576 * :ghissue:`4078`: StoreMagic.autorestore not working in 1.0.0
1576 * :ghissue:`4078`: StoreMagic.autorestore not working in 1.0.0
1577 * :ghissue:`3990`: Buitlin `input` doesn't work over zmq
1577 * :ghissue:`3990`: Buitlin `input` doesn't work over zmq
1578 * :ghissue:`4015`: nbconvert fails to convert all the content of a notebook
1578 * :ghissue:`4015`: nbconvert fails to convert all the content of a notebook
1579 * :ghissue:`4059`: Issues with Ellipsis literal in Python 3
1579 * :ghissue:`4059`: Issues with Ellipsis literal in Python 3
1580 * :ghissue:`2310`: "ZMQError: Interrupted system call" from RichIPythonWidget
1580 * :ghissue:`2310`: "ZMQError: Interrupted system call" from RichIPythonWidget
1581 * :ghissue:`3807`: qtconsole ipython 0.13.2 - html/xhtml export fails
1581 * :ghissue:`3807`: qtconsole ipython 0.13.2 - html/xhtml export fails
1582 * :ghissue:`4103`: Wrong default argument of DirectView.clear
1582 * :ghissue:`4103`: Wrong default argument of DirectView.clear
1583 * :ghissue:`4100`: parallel.client.client references undefined error.EngineError
1583 * :ghissue:`4100`: parallel.client.client references undefined error.EngineError
1584 * :ghissue:`484`: Drop nosepatch
1584 * :ghissue:`484`: Drop nosepatch
1585 * :ghissue:`3350`: Added longlist support in ipdb.
1585 * :ghissue:`3350`: Added longlist support in ipdb.
1586 * :ghissue:`1591`: Keying 'q' doesn't quit the interactive help in Wins7
1586 * :ghissue:`1591`: Keying 'q' doesn't quit the interactive help in Wins7
1587 * :ghissue:`40`: The tests in test_process fail under Windows
1587 * :ghissue:`40`: The tests in test_process fail under Windows
1588 * :ghissue:`3744`: capture rich output as well as stdout/err in capture_output
1588 * :ghissue:`3744`: capture rich output as well as stdout/err in capture_output
1589 * :ghissue:`3742`: %%capture to grab rich display outputs
1589 * :ghissue:`3742`: %%capture to grab rich display outputs
1590 * :ghissue:`3863`: Added working speaker notes for slides.
1590 * :ghissue:`3863`: Added working speaker notes for slides.
1591 * :ghissue:`4013`: Iptest fails in dual python installation
1591 * :ghissue:`4013`: Iptest fails in dual python installation
1592 * :ghissue:`4005`: IPython.start_kernel doesn't work.
1592 * :ghissue:`4005`: IPython.start_kernel doesn't work.
1593 * :ghissue:`4020`: IPython parallel map fails on numpy arrays
1593 * :ghissue:`4020`: IPython parallel map fails on numpy arrays
1594 * :ghissue:`3914`: nbconvert: Transformer tests
1594 * :ghissue:`3914`: nbconvert: Transformer tests
1595 * :ghissue:`3923`: nbconvert: Writer tests
1595 * :ghissue:`3923`: nbconvert: Writer tests
1596 * :ghissue:`3945`: nbconvert: commandline tests fail Win7x64 Py3.3
1596 * :ghissue:`3945`: nbconvert: commandline tests fail Win7x64 Py3.3
1597 * :ghissue:`3937`: make tab visible in codemirror and light red background
1597 * :ghissue:`3937`: make tab visible in codemirror and light red background
1598 * :ghissue:`3935`: No feedback for mixed tabs and spaces
1598 * :ghissue:`3935`: No feedback for mixed tabs and spaces
1599 * :ghissue:`3933`: nbconvert: Post-processor tests
1599 * :ghissue:`3933`: nbconvert: Post-processor tests
1600 * :ghissue:`3977`: unable to complete remote connections for two-process
1600 * :ghissue:`3977`: unable to complete remote connections for two-process
1601 * :ghissue:`3939`: minor checkpoint cleanup
1601 * :ghissue:`3939`: minor checkpoint cleanup
1602 * :ghissue:`3955`: complete on % for magic in notebook
1602 * :ghissue:`3955`: complete on % for magic in notebook
1603 * :ghissue:`3954`: all magics should be listed when completing on %
1603 * :ghissue:`3954`: all magics should be listed when completing on %
1604 * :ghissue:`3980`: nbconvert rst output lacks needed blank lines
1604 * :ghissue:`3980`: nbconvert rst output lacks needed blank lines
1605 * :ghissue:`3968`: TypeError: super() argument 1 must be type, not classobj (Python 2.6.6)
1605 * :ghissue:`3968`: TypeError: super() argument 1 must be type, not classobj (Python 2.6.6)
1606 * :ghissue:`3880`: nbconvert: R&D remaining tests
1606 * :ghissue:`3880`: nbconvert: R&D remaining tests
1607 * :ghissue:`2440`: IPEP 4: Python 3 Compatibility
1607 * :ghissue:`2440`: IPEP 4: Python 3 Compatibility
@@ -1,283 +1,283 b''
1 ========================================
1 ========================================
2 0.9 series
2 0.9 series
3 ========================================
3 ========================================
4
4
5 Release 0.9.1
5 Release 0.9.1
6 =============
6 =============
7
7
8 This release was quickly made to restore compatibility with Python 2.4, which
8 This release was quickly made to restore compatibility with Python 2.4, which
9 version 0.9 accidentally broke. No new features were introduced, other than
9 version 0.9 accidentally broke. No new features were introduced, other than
10 some additional testing support for internal use.
10 some additional testing support for internal use.
11
11
12
12
13 Release 0.9
13 Release 0.9
14 ===========
14 ===========
15
15
16 New features
16 New features
17 ------------
17 ------------
18
18
19 * All furl files and security certificates are now put in a read-only
19 * All furl files and security certificates are now put in a read-only
20 directory named ~/.ipython/security.
20 directory named ~/.ipython/security.
21
21
22 * A single function :func:`get_ipython_dir`, in :mod:`IPython.genutils` that
22 * A single function :func:`get_ipython_dir`, in :mod:`IPython.genutils` that
23 determines the user's IPython directory in a robust manner.
23 determines the user's IPython directory in a robust manner.
24
24
25 * Laurent's WX application has been given a top-level script called
25 * Laurent's WX application has been given a top-level script called
26 ipython-wx, and it has received numerous fixes. We expect this code to be
26 ipython-wx, and it has received numerous fixes. We expect this code to be
27 architecturally better integrated with Gael's WX 'ipython widget' over the
27 architecturally better integrated with Gael's WX 'ipython widget' over the
28 next few releases.
28 next few releases.
29
29
30 * The Editor synchronization work by Vivian De Smedt has been merged in. This
30 * The Editor synchronization work by Vivian De Smedt has been merged in. This
31 code adds a number of new editor hooks to synchronize with editors under
31 code adds a number of new editor hooks to synchronize with editors under
32 Windows.
32 Windows.
33
33
34 * A new, still experimental but highly functional, WX shell by Gael Varoquaux.
34 * A new, still experimental but highly functional, WX shell by Gael Varoquaux.
35 This work was sponsored by Enthought, and while it's still very new, it is
35 This work was sponsored by Enthought, and while it's still very new, it is
36 based on a more cleanly organized architecture of the various IPython
36 based on a more cleanly organized architecture of the various IPython
37 components. We will continue to develop this over the next few releases as a
37 components. We will continue to develop this over the next few releases as a
38 model for GUI components that use IPython.
38 model for GUI components that use IPython.
39
39
40 * Another GUI frontend, Cocoa based (Cocoa is the OSX native GUI framework),
40 * Another GUI frontend, Cocoa based (Cocoa is the OSX native GUI framework),
41 authored by Barry Wark. Currently the WX and the Cocoa ones have slightly
41 authored by Barry Wark. Currently the WX and the Cocoa ones have slightly
42 different internal organizations, but the whole team is working on finding
42 different internal organizations, but the whole team is working on finding
43 what the right abstraction points are for a unified codebase.
43 what the right abstraction points are for a unified codebase.
44
44
45 * As part of the frontend work, Barry Wark also implemented an experimental
45 * As part of the frontend work, Barry Wark also implemented an experimental
46 event notification system that various ipython components can use. In the
46 event notification system that various ipython components can use. In the
47 next release the implications and use patterns of this system regarding the
47 next release the implications and use patterns of this system regarding the
48 various GUI options will be worked out.
48 various GUI options will be worked out.
49
49
50 * IPython finally has a full test system, that can test docstrings with
50 * IPython finally has a full test system, that can test docstrings with
51 IPython-specific functionality. There are still a few pieces missing for it
51 IPython-specific functionality. There are still a few pieces missing for it
52 to be widely accessible to all users (so they can run the test suite at any
52 to be widely accessible to all users (so they can run the test suite at any
53 time and report problems), but it now works for the developers. We are
53 time and report problems), but it now works for the developers. We are
54 working hard on continuing to improve it, as this was probably IPython's
54 working hard on continuing to improve it, as this was probably IPython's
55 major Achilles heel (the lack of proper test coverage made it effectively
55 major Achilles heel (the lack of proper test coverage made it effectively
56 impossible to do large-scale refactoring). The full test suite can now
56 impossible to do large-scale refactoring). The full test suite can now
57 be run using the :command:`iptest` command line program.
57 be run using the :command:`iptest` command line program.
58
58
59 * The notion of a task has been completely reworked. An `ITask` interface has
59 * The notion of a task has been completely reworked. An `ITask` interface has
60 been created. This interface defines the methods that tasks need to
60 been created. This interface defines the methods that tasks need to
61 implement. These methods are now responsible for things like submitting
61 implement. These methods are now responsible for things like submitting
62 tasks and processing results. There are two basic task types:
62 tasks and processing results. There are two basic task types:
63 :class:`IPython.kernel.task.StringTask` (this is the old `Task` object, but
63 :class:`IPython.kernel.task.StringTask` (this is the old `Task` object, but
64 renamed) and the new :class:`IPython.kernel.task.MapTask`, which is based on
64 renamed) and the new :class:`IPython.kernel.task.MapTask`, which is based on
65 a function.
65 a function.
66
66
67 * A new interface, :class:`IPython.kernel.mapper.IMapper` has been defined to
67 * A new interface, :class:`IPython.kernel.mapper.IMapper` has been defined to
68 standardize the idea of a `map` method. This interface has a single `map`
68 standardize the idea of a `map` method. This interface has a single `map`
69 method that has the same syntax as the built-in `map`. We have also defined
69 method that has the same syntax as the built-in `map`. We have also defined
70 a `mapper` factory interface that creates objects that implement
70 a `mapper` factory interface that creates objects that implement
71 :class:`IPython.kernel.mapper.IMapper` for different controllers. Both the
71 :class:`IPython.kernel.mapper.IMapper` for different controllers. Both the
72 multiengine and task controller now have mapping capabilities.
72 multiengine and task controller now have mapping capabilities.
73
73
74 * The parallel function capabilities have been reworks. The major changes are
74 * The parallel function capabilities have been reworks. The major changes are
75 that i) there is now an `@parallel` magic that creates parallel functions,
75 that i) there is now an `@parallel` magic that creates parallel functions,
76 ii) the syntax for multiple variable follows that of `map`, iii) both the
76 ii) the syntax for multiple variable follows that of `map`, iii) both the
77 multiengine and task controller now have a parallel function implementation.
77 multiengine and task controller now have a parallel function implementation.
78
78
79 * All of the parallel computing capabilities from `ipython1-dev` have been
79 * All of the parallel computing capabilities from `ipython1-dev` have been
80 merged into IPython proper. This resulted in the following new subpackages:
80 merged into IPython proper. This resulted in the following new subpackages:
81 :mod:`IPython.kernel`, :mod:`IPython.kernel.core`, :mod:`traitlets.config`,
81 :mod:`IPython.kernel`, :mod:`IPython.kernel.core`, :mod:`traitlets.config`,
82 :mod:`IPython.tools` and :mod:`IPython.testing`.
82 :mod:`IPython.tools` and :mod:`IPython.testing`.
83
83
84 * As part of merging in the `ipython1-dev` stuff, the `setup.py` script and
84 * As part of merging in the `ipython1-dev` stuff, the ``setup.py`` script and
85 friends have been completely refactored. Now we are checking for
85 friends have been completely refactored. Now we are checking for
86 dependencies using the approach that matplotlib uses.
86 dependencies using the approach that matplotlib uses.
87
87
88 * The documentation has been completely reorganized to accept the
88 * The documentation has been completely reorganized to accept the
89 documentation from `ipython1-dev`.
89 documentation from `ipython1-dev`.
90
90
91 * We have switched to using Foolscap for all of our network protocols in
91 * We have switched to using Foolscap for all of our network protocols in
92 :mod:`IPython.kernel`. This gives us secure connections that are both
92 :mod:`IPython.kernel`. This gives us secure connections that are both
93 encrypted and authenticated.
93 encrypted and authenticated.
94
94
95 * We have a brand new `COPYING.txt` files that describes the IPython license
95 * We have a brand new `COPYING.txt` files that describes the IPython license
96 and copyright. The biggest change is that we are putting "The IPython
96 and copyright. The biggest change is that we are putting "The IPython
97 Development Team" as the copyright holder. We give more details about
97 Development Team" as the copyright holder. We give more details about
98 exactly what this means in this file. All developer should read this and use
98 exactly what this means in this file. All developer should read this and use
99 the new banner in all IPython source code files.
99 the new banner in all IPython source code files.
100
100
101 * sh profile: ./foo runs foo as system command, no need to do !./foo anymore
101 * sh profile: ./foo runs foo as system command, no need to do !./foo anymore
102
102
103 * String lists now support ``sort(field, nums = True)`` method (to easily sort
103 * String lists now support ``sort(field, nums = True)`` method (to easily sort
104 system command output). Try it with ``a = !ls -l ; a.sort(1, nums=1)``.
104 system command output). Try it with ``a = !ls -l ; a.sort(1, nums=1)``.
105
105
106 * '%cpaste foo' now assigns the pasted block as string list, instead of string
106 * '%cpaste foo' now assigns the pasted block as string list, instead of string
107
107
108 * The ipcluster script now run by default with no security. This is done
108 * The ipcluster script now run by default with no security. This is done
109 because the main usage of the script is for starting things on localhost.
109 because the main usage of the script is for starting things on localhost.
110 Eventually when ipcluster is able to start things on other hosts, we will put
110 Eventually when ipcluster is able to start things on other hosts, we will put
111 security back.
111 security back.
112
112
113 * 'cd --foo' searches directory history for string foo, and jumps to that dir.
113 * 'cd --foo' searches directory history for string foo, and jumps to that dir.
114 Last part of dir name is checked first. If no matches for that are found,
114 Last part of dir name is checked first. If no matches for that are found,
115 look at the whole path.
115 look at the whole path.
116
116
117
117
118 Bug fixes
118 Bug fixes
119 ---------
119 ---------
120
120
121 * The Windows installer has been fixed. Now all IPython scripts have ``.bat``
121 * The Windows installer has been fixed. Now all IPython scripts have ``.bat``
122 versions created. Also, the Start Menu shortcuts have been updated.
122 versions created. Also, the Start Menu shortcuts have been updated.
123
123
124 * The colors escapes in the multiengine client are now turned off on win32 as
124 * The colors escapes in the multiengine client are now turned off on win32 as
125 they don't print correctly.
125 they don't print correctly.
126
126
127 * The :mod:`IPython.kernel.scripts.ipengine` script was exec'ing
127 * The :mod:`IPython.kernel.scripts.ipengine` script was exec'ing
128 mpi_import_statement incorrectly, which was leading the engine to crash when
128 mpi_import_statement incorrectly, which was leading the engine to crash when
129 mpi was enabled.
129 mpi was enabled.
130
130
131 * A few subpackages had missing ``__init__.py`` files.
131 * A few subpackages had missing ``__init__.py`` files.
132
132
133 * The documentation is only created if Sphinx is found. Previously, the
133 * The documentation is only created if Sphinx is found. Previously, the
134 ``setup.py`` script would fail if it was missing.
134 ``setup.py`` script would fail if it was missing.
135
135
136 * Greedy ``cd`` completion has been disabled again (it was enabled in 0.8.4) as
136 * Greedy ``cd`` completion has been disabled again (it was enabled in 0.8.4) as
137 it caused problems on certain platforms.
137 it caused problems on certain platforms.
138
138
139
139
140 Backwards incompatible changes
140 Backwards incompatible changes
141 ------------------------------
141 ------------------------------
142
142
143 * The ``clusterfile`` options of the :command:`ipcluster` command has been
143 * The ``clusterfile`` options of the :command:`ipcluster` command has been
144 removed as it was not working and it will be replaced soon by something much
144 removed as it was not working and it will be replaced soon by something much
145 more robust.
145 more robust.
146
146
147 * The :mod:`IPython.kernel` configuration now properly find the user's
147 * The :mod:`IPython.kernel` configuration now properly find the user's
148 IPython directory.
148 IPython directory.
149
149
150 * In ipapi, the :func:`make_user_ns` function has been replaced with
150 * In ipapi, the :func:`make_user_ns` function has been replaced with
151 :func:`make_user_namespaces`, to support dict subclasses in namespace
151 :func:`make_user_namespaces`, to support dict subclasses in namespace
152 creation.
152 creation.
153
153
154 * :class:`IPython.kernel.client.Task` has been renamed
154 * :class:`IPython.kernel.client.Task` has been renamed
155 :class:`IPython.kernel.client.StringTask` to make way for new task types.
155 :class:`IPython.kernel.client.StringTask` to make way for new task types.
156
156
157 * The keyword argument `style` has been renamed `dist` in `scatter`, `gather`
157 * The keyword argument `style` has been renamed `dist` in `scatter`, `gather`
158 and `map`.
158 and `map`.
159
159
160 * Renamed the values that the rename `dist` keyword argument can have from
160 * Renamed the values that the rename `dist` keyword argument can have from
161 `'basic'` to `'b'`.
161 `'basic'` to `'b'`.
162
162
163 * IPython has a larger set of dependencies if you want all of its capabilities.
163 * IPython has a larger set of dependencies if you want all of its capabilities.
164 See the `setup.py` script for details.
164 See the ``setup.py`` script for details.
165
165
166 * The constructors for :class:`IPython.kernel.client.MultiEngineClient` and
166 * The constructors for :class:`IPython.kernel.client.MultiEngineClient` and
167 :class:`IPython.kernel.client.TaskClient` no longer take the (ip,port) tuple.
167 :class:`IPython.kernel.client.TaskClient` no longer take the (ip,port) tuple.
168 Instead they take the filename of a file that contains the FURL for that
168 Instead they take the filename of a file that contains the FURL for that
169 client. If the FURL file is in your IPYTHONDIR, it will be found automatically
169 client. If the FURL file is in your IPYTHONDIR, it will be found automatically
170 and the constructor can be left empty.
170 and the constructor can be left empty.
171
171
172 * The asynchronous clients in :mod:`IPython.kernel.asyncclient` are now created
172 * The asynchronous clients in :mod:`IPython.kernel.asyncclient` are now created
173 using the factory functions :func:`get_multiengine_client` and
173 using the factory functions :func:`get_multiengine_client` and
174 :func:`get_task_client`. These return a `Deferred` to the actual client.
174 :func:`get_task_client`. These return a `Deferred` to the actual client.
175
175
176 * The command line options to `ipcontroller` and `ipengine` have changed to
176 * The command line options to `ipcontroller` and `ipengine` have changed to
177 reflect the new Foolscap network protocol and the FURL files. Please see the
177 reflect the new Foolscap network protocol and the FURL files. Please see the
178 help for these scripts for details.
178 help for these scripts for details.
179
179
180 * The configuration files for the kernel have changed because of the Foolscap
180 * The configuration files for the kernel have changed because of the Foolscap
181 stuff. If you were using custom config files before, you should delete them
181 stuff. If you were using custom config files before, you should delete them
182 and regenerate new ones.
182 and regenerate new ones.
183
183
184 Changes merged in from IPython1
184 Changes merged in from IPython1
185 -------------------------------
185 -------------------------------
186
186
187 New features
187 New features
188 ............
188 ............
189
189
190 * Much improved ``setup.py`` and ``setupegg.py`` scripts. Because Twisted and
190 * Much improved ``setup.py`` and ``setupegg.py`` scripts. Because Twisted and
191 zope.interface are now easy installable, we can declare them as dependencies
191 zope.interface are now easy installable, we can declare them as dependencies
192 in our setupegg.py script.
192 in our setupegg.py script.
193
193
194 * IPython is now compatible with Twisted 2.5.0 and 8.x.
194 * IPython is now compatible with Twisted 2.5.0 and 8.x.
195
195
196 * Added a new example of how to use :mod:`ipython1.kernel.asynclient`.
196 * Added a new example of how to use :mod:`ipython1.kernel.asynclient`.
197
197
198 * Initial draft of a process daemon in :mod:`ipython1.daemon`. This has not
198 * Initial draft of a process daemon in :mod:`ipython1.daemon`. This has not
199 been merged into IPython and is still in `ipython1-dev`.
199 been merged into IPython and is still in `ipython1-dev`.
200
200
201 * The ``TaskController`` now has methods for getting the queue status.
201 * The ``TaskController`` now has methods for getting the queue status.
202
202
203 * The ``TaskResult`` objects not have information about how long the task
203 * The ``TaskResult`` objects not have information about how long the task
204 took to run.
204 took to run.
205
205
206 * We are attaching additional attributes to exceptions ``(_ipython_*)`` that
206 * We are attaching additional attributes to exceptions ``(_ipython_*)`` that
207 we use to carry additional info around.
207 we use to carry additional info around.
208
208
209 * New top-level module :mod:`asyncclient` that has asynchronous versions (that
209 * New top-level module :mod:`asyncclient` that has asynchronous versions (that
210 return deferreds) of the client classes. This is designed to users who want
210 return deferreds) of the client classes. This is designed to users who want
211 to run their own Twisted reactor.
211 to run their own Twisted reactor.
212
212
213 * All the clients in :mod:`client` are now based on Twisted. This is done by
213 * All the clients in :mod:`client` are now based on Twisted. This is done by
214 running the Twisted reactor in a separate thread and using the
214 running the Twisted reactor in a separate thread and using the
215 :func:`blockingCallFromThread` function that is in recent versions of Twisted.
215 :func:`blockingCallFromThread` function that is in recent versions of Twisted.
216
216
217 * Functions can now be pushed/pulled to/from engines using
217 * Functions can now be pushed/pulled to/from engines using
218 :meth:`MultiEngineClient.push_function` and
218 :meth:`MultiEngineClient.push_function` and
219 :meth:`MultiEngineClient.pull_function`.
219 :meth:`MultiEngineClient.pull_function`.
220
220
221 * Gather/scatter are now implemented in the client to reduce the work load
221 * Gather/scatter are now implemented in the client to reduce the work load
222 of the controller and improve performance.
222 of the controller and improve performance.
223
223
224 * Complete rewrite of the IPython docuementation. All of the documentation
224 * Complete rewrite of the IPython docuementation. All of the documentation
225 from the IPython website has been moved into docs/source as restructured
225 from the IPython website has been moved into docs/source as restructured
226 text documents. PDF and HTML documentation are being generated using
226 text documents. PDF and HTML documentation are being generated using
227 Sphinx.
227 Sphinx.
228
228
229 * New developer oriented documentation: development guidelines and roadmap.
229 * New developer oriented documentation: development guidelines and roadmap.
230
230
231 * Traditional ``ChangeLog`` has been changed to a more useful ``changes.txt``
231 * Traditional ``ChangeLog`` has been changed to a more useful ``changes.txt``
232 file that is organized by release and is meant to provide something more
232 file that is organized by release and is meant to provide something more
233 relevant for users.
233 relevant for users.
234
234
235 Bug fixes
235 Bug fixes
236 .........
236 .........
237
237
238 * Created a proper ``MANIFEST.in`` file to create source distributions.
238 * Created a proper ``MANIFEST.in`` file to create source distributions.
239
239
240 * Fixed a bug in the ``MultiEngine`` interface. Previously, multi-engine
240 * Fixed a bug in the ``MultiEngine`` interface. Previously, multi-engine
241 actions were being collected with a :class:`DeferredList` with
241 actions were being collected with a :class:`DeferredList` with
242 ``fireononeerrback=1``. This meant that methods were returning
242 ``fireononeerrback=1``. This meant that methods were returning
243 before all engines had given their results. This was causing extremely odd
243 before all engines had given their results. This was causing extremely odd
244 bugs in certain cases. To fix this problem, we have 1) set
244 bugs in certain cases. To fix this problem, we have 1) set
245 ``fireononeerrback=0`` to make sure all results (or exceptions) are in
245 ``fireononeerrback=0`` to make sure all results (or exceptions) are in
246 before returning and 2) introduced a :exc:`CompositeError` exception
246 before returning and 2) introduced a :exc:`CompositeError` exception
247 that wraps all of the engine exceptions. This is a huge change as it means
247 that wraps all of the engine exceptions. This is a huge change as it means
248 that users will have to catch :exc:`CompositeError` rather than the actual
248 that users will have to catch :exc:`CompositeError` rather than the actual
249 exception.
249 exception.
250
250
251 Backwards incompatible changes
251 Backwards incompatible changes
252 ..............................
252 ..............................
253
253
254 * All names have been renamed to conform to the lowercase_with_underscore
254 * All names have been renamed to conform to the lowercase_with_underscore
255 convention. This will require users to change references to all names like
255 convention. This will require users to change references to all names like
256 ``queueStatus`` to ``queue_status``.
256 ``queueStatus`` to ``queue_status``.
257
257
258 * Previously, methods like :meth:`MultiEngineClient.push` and
258 * Previously, methods like :meth:`MultiEngineClient.push` and
259 :meth:`MultiEngineClient.push` used ``*args`` and ``**kwargs``. This was
259 :meth:`MultiEngineClient.push` used ``*args`` and ``**kwargs``. This was
260 becoming a problem as we weren't able to introduce new keyword arguments into
260 becoming a problem as we weren't able to introduce new keyword arguments into
261 the API. Now these methods simple take a dict or sequence. This has also
261 the API. Now these methods simple take a dict or sequence. This has also
262 allowed us to get rid of the ``*All`` methods like :meth:`pushAll` and
262 allowed us to get rid of the ``*All`` methods like :meth:`pushAll` and
263 :meth:`pullAll`. These things are now handled with the ``targets`` keyword
263 :meth:`pullAll`. These things are now handled with the ``targets`` keyword
264 argument that defaults to ``'all'``.
264 argument that defaults to ``'all'``.
265
265
266 * The :attr:`MultiEngineClient.magicTargets` has been renamed to
266 * The :attr:`MultiEngineClient.magicTargets` has been renamed to
267 :attr:`MultiEngineClient.targets`.
267 :attr:`MultiEngineClient.targets`.
268
268
269 * All methods in the MultiEngine interface now accept the optional keyword
269 * All methods in the MultiEngine interface now accept the optional keyword
270 argument ``block``.
270 argument ``block``.
271
271
272 * Renamed :class:`RemoteController` to :class:`MultiEngineClient` and
272 * Renamed :class:`RemoteController` to :class:`MultiEngineClient` and
273 :class:`TaskController` to :class:`TaskClient`.
273 :class:`TaskController` to :class:`TaskClient`.
274
274
275 * Renamed the top-level module from :mod:`api` to :mod:`client`.
275 * Renamed the top-level module from :mod:`api` to :mod:`client`.
276
276
277 * Most methods in the multiengine interface now raise a :exc:`CompositeError`
277 * Most methods in the multiengine interface now raise a :exc:`CompositeError`
278 exception that wraps the user's exceptions, rather than just raising the raw
278 exception that wraps the user's exceptions, rather than just raising the raw
279 user's exception.
279 user's exception.
280
280
281 * Changed the ``setupNS`` and ``resultNames`` in the ``Task`` class to ``push``
281 * Changed the ``setupNS`` and ``resultNames`` in the ``Task`` class to ``push``
282 and ``pull``.
282 and ``pull``.
283
283
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now